Skip to content

Commit

Permalink
added attributable coverage support to munit and buddy
Browse files Browse the repository at this point in the history
disabled macro additions for utest when used with buddy
added delay to allow coverage data to appear after test results
  • Loading branch information
AlexHaxe committed Jan 16, 2025
1 parent 9d0f960 commit cc73b77
Show file tree
Hide file tree
Showing 4 changed files with 176 additions and 31 deletions.
61 changes: 47 additions & 14 deletions src/HaxeTestController.hx
Original file line number Diff line number Diff line change
Expand Up @@ -57,11 +57,15 @@ class HaxeTestController {
var suiteResults:TestSuiteResults;
var currentTask:Null<TaskExecution>;
var currentRun:Null<TestRun>;
var isCoverageRun:Bool;
var delayForCoverageResults:Int;

public function new(context:ExtensionContext, workspaceFolder:WorkspaceFolder) {
this.context = context;
this.workspaceFolder = workspaceFolder;

delayForCoverageResults = getWaitForCoverage();

channel = Vscode.window.createOutputChannel('${workspaceFolder.name} Tests');
channel.appendLine('Starting test adapter for ${workspaceFolder.name}');

Expand Down Expand Up @@ -92,11 +96,12 @@ class HaxeTestController {

function runHandler(request:TestRunRequest, token:CancellationToken):Thenable<Void> {
if (currentRun != null) {
return Promise.reject();
return Promise.reject("tests already running");
}
channel.appendLine("start running Tests");
token.onCancellationRequested((e) -> cancel());

isCoverageRun = false;
currentRun = controller.createTestRun(request, HAXE_TESTS);
setFilters(request);
setAllStarted(controller.items);
Expand Down Expand Up @@ -141,6 +146,7 @@ class HaxeTestController {
channel.appendLine("start running Tests (with coveraqge)");
token.onCancellationRequested((e) -> cancel());

isCoverageRun = true;
currentRun = controller.createTestRun(request, HAXE_TESTS);
setFilters(request);
setAllStarted(controller.items);
Expand Down Expand Up @@ -177,6 +183,7 @@ class HaxeTestController {
channel.appendLine("start debugging Tests");
token.onCancellationRequested((e) -> cancel());

isCoverageRun = false;
currentRun = controller.createTestRun(request, HAXE_TESTS);
setFilters(request);
setAllStarted(controller.items);
Expand Down Expand Up @@ -278,7 +285,11 @@ class HaxeTestController {
}
var unitTestFolder = Path.directory(uri.fsPath);
var baseFolder = Path.directory(unitTestFolder);
loadFrom(baseFolder);
if (isCoverageRun) {
Timer.delay(() -> loadFrom(baseFolder), delayForCoverageResults);
} else {
loadFrom(baseFolder);
}
}

function loadFrom(baseFolder:String) {
Expand Down Expand Up @@ -362,8 +373,9 @@ class HaxeTestController {
for (item in testItems) {
updateTestState(item.test, item.testItem, item.clazzUri);
}
if (isCoverageUIEnabled()) {
updateTestCoverage(testItems);
final currentTestItems = testItems.map(f -> f.testItem);
if (isCoverageUIEnabled() && isCoverageRun) {
updateTestCoverage(currentTestItems);
}
}

Expand Down Expand Up @@ -465,30 +477,42 @@ class HaxeTestController {
return msg;
}

function updateTestCoverage(testItems:Array<TestItemData>) {
function updateTestCoverage(currentTestItems:Array<TestItem>) {
if (currentRun == null) {
return;
}
// var lcovPath = makeFileName(workspaceFolder.uri.path, Path.join([Data.FOLDER, testItem.id + ".lcov"]));
// var list:TestFilterList = filter.get();
if (currentTestItems.length <= 0) {
return;
}
channel.appendLine("updateTestCoverage");

var filteredTestItems:Null<Array<TestItem>> = null;
if (isAttributableCoverageEnabled() && testItems.length > 0) {
filteredTestItems = testItems.map(f -> f.testItem);
if (isAttributableCoverageEnabled()) {
filteredTestItems = [];
for (item in currentTestItems) {
final lcovFilename = makeFileName(workspaceFolder.uri.path, Path.join([Data.FOLDER, item.id + ".lcov"]));

if (FileSystem.exists(lcovFilename)) {
filteredTestItems.push(item);
}
}
if (filteredTestItems.length <= 0) {
filteredTestItems = null;
}
}
updateTestCoverageExtract(filteredTestItems, getFullCoveragePath());
updateCoverageView(filteredTestItems, getFullCoveragePath());
// for (item in filteredTestItems) {
// final lcovFilename = makeFileName(workspaceFolder.uri.path, Path.join([Data.FOLDER, item.id + ".lcov"]));
//
// updateTestCoverageExtract([item], lcovFilename);
// }
}

function updateTestCoverageExtract(filteredTestItems:Null<Array<TestItem>>, lcovPath:String) {
function updateCoverageView(filteredTestItems:Null<Array<TestItem>>, lcovPath:String) {
if (!FileSystem.exists(lcovPath)) {
return;
}
switch (Report.parse(sys.io.File.getContent(lcovPath))) {
switch (Report.parse(File.getContent(lcovPath))) {
case Failure(failure):
channel.appendLine("failed to parse LCOV data: " + failure);
case Success(data):
Expand All @@ -512,7 +536,8 @@ class HaxeTestController {

function loadDetailedCoverageForTest(testRun:TestRun, fileCoverage:FileCoverage, fromTestItem:TestItem,
token:CancellationToken):Thenable<Array<FileCoverageDetail>> {
final lcovFilename = makeFileName(workspaceFolder.uri.path, Path.join([Data.FOLDER, fromTestItem.id + ".lcov"]));
var regEx = ~/[^a-zA-Z0-9_.-]/g;
final lcovFilename = makeFileName(workspaceFolder.uri.path, Path.join([Data.FOLDER, regEx.replace(fromTestItem.id, "_") + ".lcov"]));
return reportDetailedCoverage(lcovFilename, fileCoverage.uri.fsPath);
}

Expand All @@ -522,7 +547,7 @@ class HaxeTestController {
if (!FileSystem.exists(lcovFileName)) {
return Promise.reject("no coverage data found");
}
switch (Report.parse(sys.io.File.getContent(lcovFileName))) {
switch (Report.parse(File.getContent(lcovFileName))) {
case Failure(failure):
return Promise.reject(failure);
case Success(data):
Expand Down Expand Up @@ -576,6 +601,14 @@ class HaxeTestController {
return attributableCoverageEnabled;
}

function getWaitForCoverage():Int {
var waitForCoverage:Null<Int> = Vscode.workspace.getConfiguration("haxeTestExplorer", workspaceFolder).get("waitForCoverage");
if (waitForCoverage == null) {
return 2000;
}
return waitForCoverage;
}

function getFullCoveragePath():String {
final path = getInstumentFullCoveragePath();
if (FileSystem.exists(path)) {
Expand Down
36 changes: 28 additions & 8 deletions test-adapter/_testadapter/buddy/Injector.hx
Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@
package _testadapter.buddy;
package _testadapter.buddy; #if macro

#if macro
import haxe.macro.Context;
import haxe.macro.Expr;
import haxe.macro.Context; import haxe.macro.Expr; using haxe.macro.ExprTools; using _testadapter.PatchTools; class Injector {

using haxe.macro.ExprTools;
using _testadapter.PatchTools;

class Injector {
public static function buildRunner():Array<Field> {
var coverageEnabled:Null<String> = Context.definedValue("instrument-coverage");
var baseFolder = haxe.io.Path.join([_testadapter.data.Data.FOLDER]);

var fields = Context.getBuildFields();
for (field in fields) {
switch (field.name) {
Expand All @@ -17,6 +14,29 @@ class Injector {
adapterReporter = new _testadapter.buddy.Reporter($v{Sys.getCwd()}, reporter);
this.reporter = adapterReporter;
});
case "mapTestSpec" if (coverageEnabled != null):
field.patch(Start, macro switch (testSpec) {
case It(description, _, _, pos, _):
var suiteId:_testadapter.data.Data.SuiteId = SuiteNameAndPos(testSuite.description, pos.fileName, pos.lineNumber);
adapterReporter.addPosition(suiteId, description, pos.fileName, pos.lineNumber - 1);

beforeEachStack = beforeEachStack.copy();
beforeEachStack.unshift([Sync(_ -> instrument.coverage.Coverage.resetAttributableCoverage())]);
afterEachStack = afterEachStack.copy();
var regEx = ~/[^a-zA-Z0-9_-]/g;
var testCaseName = regEx.replace('${suiteId}_$description.lcov', "_");
var path = haxe.io.Path.join([$v{baseFolder}, testCaseName]);
var lcovReporter = new instrument.coverage.reporter.LcovCoverageReporter(path);
afterEachStack.unshift([
Sync(_ -> instrument.coverage.Coverage.reportAttributableCoverage([lcovReporter]))
);
case _:
});
switch (field.kind) {
case FFun(f):
replaceSpec(f.expr);
case _:
}
case "mapTestSpec":
field.patch(Start, macro switch (testSpec) {
case It(description, _, _, pos, _):
Expand Down
92 changes: 90 additions & 2 deletions test-adapter/_testadapter/munit/Injector.hx
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,92 @@ using _testadapter.PatchTools;
class Injector {
public static function buildRunner():Array<Field> {
var fields = Context.getBuildFields();
var coverageEnabled:Null<String> = Context.definedValue("instrument-coverage");
var baseFolder = haxe.io.Path.join([_testadapter.data.Data.FOLDER]);
for (field in fields) {
if (field.name == "new") {
field.addInit(macro addResultClient(new _testadapter.munit.ResultClient($v{Sys.getCwd()})));
switch (field.name) {
case "new":
field.addInit(macro addResultClient(new _testadapter.munit.ResultClient($v{Sys.getCwd()})));
case "executeTestCases":
field.patch(Replace, macro {
#if (haxe_ver > 4.10)
var isOfType = Std.isOfType;
#else
var isOfType = Std.is;
#end
for (c in clients) {
if (isOfType(c, IAdvancedTestResultClient) && activeHelper.hasNext()) {
var cl:IAdvancedTestResultClient = cast c;
cl.setCurrentTestClass(activeHelper.className);
}
}
activeHelper.before = clearCoverageForBefore(activeHelper.before);
var afterFunc = activeHelper.after;
for (testCaseData in activeHelper) {
if (testCaseData.result.ignore) {
ignoreCount++;
for (c in clients)
c.addIgnore(cast testCaseData.result);
} else {
activeHelper.after = attributeCoverageInAfter(afterFunc, activeHelper.className, testCaseData.result.name);
testCount++; // note we don't include ignored in final test count
tryCallMethod(activeHelper.test, activeHelper.before, emptyParams);
testStartTime = Timer.stamp();
executeTestCase(testCaseData, testCaseData.result.async);
if (!asyncPending)
tryCallMethod(activeHelper.test, activeHelper.after, emptyParams);
else
break;
}
}
});
}
}
if (coverageEnabled != null) {
var extraFields = (macro class {
function clearCoverageForBefore(oldBefore:Null<haxe.Constraints.Function>):haxe.Constraints.Function {
if (oldBefore == null) {
return function() {
instrument.coverage.Coverage.resetAttributableCoverage();
}
}
return function() {
instrument.coverage.Coverage.resetAttributableCoverage();
oldBefore();
}
}

function attributeCoverageInAfter(oldAfter:haxe.Constraints.Function, className:String, testName:String):haxe.Constraints.Function {
var testCaseName = '$className.$testName.lcov';
if (oldAfter == null) {
return function() {
var path = haxe.io.Path.join([$v{baseFolder}, testCaseName]);
var lcovReporter = new instrument.coverage.reporter.LcovCoverageReporter(path);
instrument.coverage.Coverage.reportAttributableCoverage([lcovReporter]);
}
}
return function() {
var path = haxe.io.Path.join([$v{baseFolder}, testCaseName]);
var lcovReporter = new instrument.coverage.reporter.LcovCoverageReporter(path);
instrument.coverage.Coverage.reportAttributableCoverage([lcovReporter]);
oldAfter();
}
}
}).fields;
fields = fields.concat(extraFields);
} else {
var extraFields = (macro class {
inline function clearCoverageForBefore(oldBefore:Null<haxe.Constraints.Function>):Null<haxe.Constraints.Function> {
return oldBefore;
}

inline function attributeCoverageInAfter(oldAfter:Null<haxe.Constraints.Function>, className:String,
testName:String):Null<haxe.Constraints.Function> {
return oldAfter;
}
}).fields;
fields = fields.concat(extraFields);
}
return fields;
}

Expand All @@ -35,6 +116,13 @@ class Injector {
return;
}
});
case "after" | "before":
switch (field.kind) {
case FVar(t, e):
case FFun(f):
case FProp(get, set, t, e):
field.kind = FVar(t, e);
}
case _:
}
}
Expand Down
18 changes: 11 additions & 7 deletions test-adapter/_testadapter/utest/Injector.hx
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,12 @@ using _testadapter.PatchTools;
class Injector {
public static function build():Array<Field> {
var fields = Context.getBuildFields();
final coverageEnabled:Null<String> = Context.definedValue("instrument-coverage");
final baseFolder = haxe.io.Path.join([_testadapter.data.Data.FOLDER]);
var coverageEnabled:Null<String> = Context.definedValue("instrument-coverage");
#if buddy
// skip coverage modifications when buddy is enabled
coverageEnabled = null;
#end
var baseFolder = haxe.io.Path.join([_testadapter.data.Data.FOLDER]);
for (field in fields) {
switch (field.name) {
case "new":
Expand Down Expand Up @@ -104,18 +108,18 @@ class Injector {
}

function attributeCoverageInTeardown(oldTeardown:Null<() -> utest.Async>, className:String, testName:String):() -> utest.Async {
final testCaseName = '$className.$testName.lcov';
var testCaseName = '$className.$testName.lcov';
if (oldTeardown == null) {
return function() {
final path = haxe.io.Path.join([$v{baseFolder}, testCaseName]);
final lcovReporter = new instrument.coverage.reporter.LcovCoverageReporter(path);
var path = haxe.io.Path.join([$v{baseFolder}, testCaseName]);
var lcovReporter = new instrument.coverage.reporter.LcovCoverageReporter(path);
instrument.coverage.Coverage.reportAttributableCoverage([lcovReporter]);
return Async.getResolved();
}
}
return function() {
final path = haxe.io.Path.join([$v{baseFolder}, testCaseName]);
final lcovReporter = new instrument.coverage.reporter.LcovCoverageReporter(path);
var path = haxe.io.Path.join([$v{baseFolder}, testCaseName]);
var lcovReporter = new instrument.coverage.reporter.LcovCoverageReporter(path);
instrument.coverage.Coverage.reportAttributableCoverage([lcovReporter]);
return oldTeardown();
}
Expand Down

0 comments on commit cc73b77

Please sign in to comment.