diff --git a/src/HaxeTestController.hx b/src/HaxeTestController.hx index 07067df..242ff2e 100644 --- a/src/HaxeTestController.hx +++ b/src/HaxeTestController.hx @@ -57,11 +57,15 @@ class HaxeTestController { var suiteResults:TestSuiteResults; var currentTask:Null; var currentRun:Null; + 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}'); @@ -92,11 +96,12 @@ class HaxeTestController { function runHandler(request:TestRunRequest, token:CancellationToken):Thenable { 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); @@ -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); @@ -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); @@ -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) { @@ -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); } } @@ -465,18 +477,30 @@ class HaxeTestController { return msg; } - function updateTestCoverage(testItems:Array) { + function updateTestCoverage(currentTestItems:Array) { 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> = 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"])); // @@ -484,11 +508,11 @@ class HaxeTestController { // } } - function updateTestCoverageExtract(filteredTestItems:Null>, lcovPath:String) { + function updateCoverageView(filteredTestItems:Null>, 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): @@ -512,7 +536,8 @@ class HaxeTestController { function loadDetailedCoverageForTest(testRun:TestRun, fileCoverage:FileCoverage, fromTestItem:TestItem, token:CancellationToken):Thenable> { - 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); } @@ -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): @@ -576,6 +601,14 @@ class HaxeTestController { return attributableCoverageEnabled; } + function getWaitForCoverage():Int { + var waitForCoverage:Null = Vscode.workspace.getConfiguration("haxeTestExplorer", workspaceFolder).get("waitForCoverage"); + if (waitForCoverage == null) { + return 2000; + } + return waitForCoverage; + } + function getFullCoveragePath():String { final path = getInstumentFullCoveragePath(); if (FileSystem.exists(path)) { diff --git a/test-adapter/_testadapter/buddy/Injector.hx b/test-adapter/_testadapter/buddy/Injector.hx index c7a5b51..eb9f560 100644 --- a/test-adapter/_testadapter/buddy/Injector.hx +++ b/test-adapter/_testadapter/buddy/Injector.hx @@ -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 { + var coverageEnabled:Null = 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) { @@ -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, _): diff --git a/test-adapter/_testadapter/munit/Injector.hx b/test-adapter/_testadapter/munit/Injector.hx index 0adefeb..7e99067 100644 --- a/test-adapter/_testadapter/munit/Injector.hx +++ b/test-adapter/_testadapter/munit/Injector.hx @@ -9,11 +9,92 @@ using _testadapter.PatchTools; class Injector { public static function buildRunner():Array { var fields = Context.getBuildFields(); + var coverageEnabled:Null = 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 { + 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):Null { + return oldBefore; + } + + inline function attributeCoverageInAfter(oldAfter:Null, className:String, + testName:String):Null { + return oldAfter; + } + }).fields; + fields = fields.concat(extraFields); + } return fields; } @@ -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 _: } } diff --git a/test-adapter/_testadapter/utest/Injector.hx b/test-adapter/_testadapter/utest/Injector.hx index 0ae5e12..0f4bd13 100644 --- a/test-adapter/_testadapter/utest/Injector.hx +++ b/test-adapter/_testadapter/utest/Injector.hx @@ -10,8 +10,12 @@ using _testadapter.PatchTools; class Injector { public static function build():Array { var fields = Context.getBuildFields(); - final coverageEnabled:Null = Context.definedValue("instrument-coverage"); - final baseFolder = haxe.io.Path.join([_testadapter.data.Data.FOLDER]); + var coverageEnabled:Null = 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": @@ -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(); }