From adeaab3041e8dd5b9ccf9de025df7f27049ac231 Mon Sep 17 00:00:00 2001 From: mnoah1 Date: Tue, 25 Jun 2024 20:03:46 +0000 Subject: [PATCH 1/2] Handle parameterized test results --- src/language-tools/java.ts | 11 ++++++- src/language-tools/python.ts | 11 ++++++- src/test-runner/run-tracker.ts | 15 ++++++++- src/test/suite/language-tools/java.test.ts | 11 +++++++ src/test/suite/language-tools/python.test.ts | 11 +++++++ src/test/suite/run-tracker.test.ts | 32 ++++++++++++++++++++ 6 files changed, 88 insertions(+), 3 deletions(-) diff --git a/src/language-tools/java.ts b/src/language-tools/java.ts index 6caa0ef..b17ea05 100644 --- a/src/language-tools/java.ts +++ b/src/language-tools/java.ts @@ -11,6 +11,7 @@ const JAVA_TEST_REGEX = /@Test\s+.*\s+public void (?\w+)|public class (?(Test\w*|\w+Test))\s+extends/ const PACKAGE_NAME_REGEX = /package\s+(?([a-zA-Z_][a-zA-Z0-9_]*)(\.[a-zA-Z_][a-zA-Z0-9_]*)*);/ +const PARAMETERIZED_TEST_REGEX = /^(?.*)\[(?.*)\]$/ export class JavaLanguageTools implements LanguageTools { /** @@ -22,7 +23,15 @@ export class JavaLanguageTools implements LanguageTools { if (testFinishData.dataKind === TestFinishDataKind.JUnitStyleTestCaseData) { const testCaseData = testFinishData.data as JUnitStyleTestCaseData if (testCaseData.className !== undefined) { - return `${testCaseData.className}.${testFinishData.displayName}` + let testCaseName = testFinishData.displayName + + // In case of a parameterized test, keep the method name. + const match = testCaseName.match(PARAMETERIZED_TEST_REGEX) + if (match?.groups?.lookupKey) { + testCaseName = match.groups.lookupKey + } + + return `${testCaseData.className}.${testCaseName}` } else { return testFinishData.displayName } diff --git a/src/language-tools/python.ts b/src/language-tools/python.ts index 2832300..2879bb5 100644 --- a/src/language-tools/python.ts +++ b/src/language-tools/python.ts @@ -8,6 +8,7 @@ import {BaseLanguageTools} from './base' import {JUnitStyleTestCaseData, TestFinishDataKind} from '../bsp/bsp-ext' const TEST_FILE_REGEX = /^(test_.+\.py|.+_test\.py)$/ +const PARAMETERIZED_TEST_REGEX = /^(?.*)\[(?.*)\]$/ export class PythonLanguageTools extends BaseLanguageTools @@ -23,7 +24,15 @@ export class PythonLanguageTools mapTestFinishDataToLookupKey(testFinishData: TestFinish): string | undefined { if (testFinishData.dataKind === TestFinishDataKind.JUnitStyleTestCaseData) { const testCaseData = testFinishData.data as JUnitStyleTestCaseData - return `${testCaseData.className}.${testFinishData.displayName}` + let testCaseName = testFinishData.displayName + + // In case of a parameterized test, keep the method name. + const match = testCaseName.match(PARAMETERIZED_TEST_REGEX) + if (match?.groups?.lookupKey) { + testCaseName = match.groups.lookupKey + } + + return `${testCaseData.className}.${testCaseName}` } return undefined } diff --git a/src/test-runner/run-tracker.ts b/src/test-runner/run-tracker.ts index 7aeada2..1860d62 100644 --- a/src/test-runner/run-tracker.ts +++ b/src/test-runner/run-tracker.ts @@ -128,6 +128,7 @@ export class TestRunTracker implements TaskOriginHandlers { /** * Updates an item's status, both within this test run tracker and in the test explorer UI. + * This may be called more than once for the same test item, in which case only the highest status (per TestCaseStatus enum) will be retained. * @param item TestItem for which the status will be updated. * @param status New status value. * @param message (optional) Message to be shown to report an outcome. Only applicable for Failed and Errored states. @@ -137,6 +138,13 @@ export class TestRunTracker implements TaskOriginHandlers { status: TestCaseStatus, message?: vscode.TestMessage ) { + const currentStatus = this.status.get(item) + if (currentStatus && status < currentStatus) { + // Only update if the new status is ranked higher than the existing one. + // This allows multiple updates to be made to a test item, while only showing the highest status in the UI. + return + } + this.status.set(item, status) switch (status) { case TestCaseStatus.Started: @@ -395,7 +403,9 @@ export class TestRunTracker implements TaskOriginHandlers { } } -function formatTestResultMessage(result): vscode.TestMessage | undefined { +function formatTestResultMessage( + result: TestFinish +): vscode.TestMessage | undefined { let message = // Ignore 'null' string as well. // TODO(IDE-1133): Ensure server does not convert null values to string. @@ -405,6 +415,9 @@ function formatTestResultMessage(result): vscode.TestMessage | undefined { if (result.dataKind === TestFinishDataKind.JUnitStyleTestCaseData) { const testCaseData = result.data as JUnitStyleTestCaseData + if (result.displayName) { + message += `${ANSI_CODES.RED}[TEST CASE]${ANSI_CODES.RESET} ${result.displayName}\n\n` + } if (testCaseData.errorType && testCaseData.fullError !== 'null') { message += `${ANSI_CODES.RED}[ERROR TYPE]${ANSI_CODES.RESET} ${testCaseData.errorType}\n\n` } diff --git a/src/test/suite/language-tools/java.test.ts b/src/test/suite/language-tools/java.test.ts index 5db57a6..524eb3c 100644 --- a/src/test/suite/language-tools/java.test.ts +++ b/src/test/suite/language-tools/java.test.ts @@ -126,6 +126,17 @@ suite('Java Language Tools', () => { status: TestStatus.Failed, }) assert.strictEqual(result, undefined) + + result = languageTools.mapTestFinishDataToLookupKey({ + displayName: 'myTest[example1]', + status: TestStatus.Failed, + dataKind: TestFinishDataKind.JUnitStyleTestCaseData, + data: { + time: 0, + className: 'com.example.ClassName', + }, + }) + assert.strictEqual(result, 'com.example.ClassName.myTest') }) test('map test case info to lookup key', async () => { diff --git a/src/test/suite/language-tools/python.test.ts b/src/test/suite/language-tools/python.test.ts index 6c510de..f025c7d 100644 --- a/src/test/suite/language-tools/python.test.ts +++ b/src/test/suite/language-tools/python.test.ts @@ -114,6 +114,17 @@ suite('Python Language Tools', () => { }) assert.strictEqual(result, 'my.example.test_example.test_method') + result = languageTools.mapTestFinishDataToLookupKey({ + displayName: 'test_method[example1]', + status: TestStatus.Failed, + dataKind: TestFinishDataKind.JUnitStyleTestCaseData, + data: { + time: 0, + className: 'my.example.test_example', + }, + }) + assert.strictEqual(result, 'my.example.test_example.test_method') + result = languageTools.mapTestFinishDataToLookupKey({ displayName: 'pytest', status: TestStatus.Failed, diff --git a/src/test/suite/run-tracker.test.ts b/src/test/suite/run-tracker.test.ts index 5ee6df5..84184fa 100644 --- a/src/test/suite/run-tracker.test.ts +++ b/src/test/suite/run-tracker.test.ts @@ -258,6 +258,38 @@ suite('Test Run Tracker', () => { assert.equal(runSpy.failed.getCall(0).args[1], message) }) + test('multiple updates', async () => { + const testItem = createdTestItems[0] + const message: vscode.TestMessage = { + message: 'Test failed due to assertion error', + location: new vscode.Location( + vscode.Uri.file('/path/to/file'), + new vscode.Range(1, 1, 1, 10) + ), + } + await testRunner.updateStatus(testItem, TestCaseStatus.Failed, message) + assert.equal(runSpy.failed.callCount, 1) + assert.equal(runSpy.failed.getCall(0).args[0], testItem) + assert.equal(runSpy.failed.getCall(0).args[1], message) + + // Calls with a lower rank status should not update the status. + await testRunner.updateStatus(testItem, TestCaseStatus.Passed, message) + assert.equal(runSpy.passed.callCount, 0) + assert.equal(runSpy.enqueued.callCount, 0) + assert.equal(runSpy.failed.callCount, 1) + + await testRunner.updateStatus(testItem, TestCaseStatus.Pending, message) + assert.equal(runSpy.passed.callCount, 0) + assert.equal(runSpy.enqueued.callCount, 0) + assert.equal(runSpy.failed.callCount, 1) + + // Additional failures should update the status. + await testRunner.updateStatus(testItem, TestCaseStatus.Failed, message) + assert.equal(runSpy.failed.callCount, 2) + assert.equal(runSpy.failed.getCall(0).args[0], testItem) + assert.equal(runSpy.failed.getCall(0).args[1], message) + }) + test('skip test item', async () => { const testItem = createdTestItems[0] await testRunner.updateStatus(testItem, TestCaseStatus.Skipped) From ef38c73e5111b2a995cba8a57d49233015373b5f Mon Sep 17 00:00:00 2001 From: mnoah1 Date: Tue, 2 Jul 2024 20:03:45 +0000 Subject: [PATCH 2/2] Additional tests --- src/language-tools/java.ts | 12 +- src/language-tools/python.ts | 12 +- src/test/suite/language-tools/java.test.ts | 277 ++++++++++++++++--- src/test/suite/language-tools/python.test.ts | 269 +++++++++++++++--- 4 files changed, 491 insertions(+), 79 deletions(-) diff --git a/src/language-tools/java.ts b/src/language-tools/java.ts index b17ea05..d43ffaa 100644 --- a/src/language-tools/java.ts +++ b/src/language-tools/java.ts @@ -11,7 +11,7 @@ const JAVA_TEST_REGEX = /@Test\s+.*\s+public void (?\w+)|public class (?(Test\w*|\w+Test))\s+extends/ const PACKAGE_NAME_REGEX = /package\s+(?([a-zA-Z_][a-zA-Z0-9_]*)(\.[a-zA-Z_][a-zA-Z0-9_]*)*);/ -const PARAMETERIZED_TEST_REGEX = /^(?.*)\[(?.*)\]$/ +const PARAMETERIZED_TEST_REGEX = /^(?.*?)(?=\[.*?\])(.*)$/ export class JavaLanguageTools implements LanguageTools { /** @@ -20,7 +20,10 @@ export class JavaLanguageTools implements LanguageTools { * @returns Lookup key to find this test case in the TestRunTracker. */ mapTestFinishDataToLookupKey(testFinishData: TestFinish): string | undefined { - if (testFinishData.dataKind === TestFinishDataKind.JUnitStyleTestCaseData) { + if ( + testFinishData.dataKind === TestFinishDataKind.JUnitStyleTestCaseData && + testFinishData.data + ) { const testCaseData = testFinishData.data as JUnitStyleTestCaseData if (testCaseData.className !== undefined) { let testCaseName = testFinishData.displayName @@ -31,7 +34,10 @@ export class JavaLanguageTools implements LanguageTools { testCaseName = match.groups.lookupKey } - return `${testCaseData.className}.${testCaseName}` + // Use the class name as the base, and append the test case name if available. + let result = testCaseData.className + if (testCaseName.length > 0) result += `.${testCaseName}` + return result } else { return testFinishData.displayName } diff --git a/src/language-tools/python.ts b/src/language-tools/python.ts index 2879bb5..d7e7913 100644 --- a/src/language-tools/python.ts +++ b/src/language-tools/python.ts @@ -8,7 +8,7 @@ import {BaseLanguageTools} from './base' import {JUnitStyleTestCaseData, TestFinishDataKind} from '../bsp/bsp-ext' const TEST_FILE_REGEX = /^(test_.+\.py|.+_test\.py)$/ -const PARAMETERIZED_TEST_REGEX = /^(?.*)\[(?.*)\]$/ +const PARAMETERIZED_TEST_REGEX = /^(?.*?)(?=\[.*?\])(.*)$/ export class PythonLanguageTools extends BaseLanguageTools @@ -22,7 +22,10 @@ export class PythonLanguageTools * @returns Lookup key to find this test case in the TestRunTracker. */ mapTestFinishDataToLookupKey(testFinishData: TestFinish): string | undefined { - if (testFinishData.dataKind === TestFinishDataKind.JUnitStyleTestCaseData) { + if ( + testFinishData.dataKind === TestFinishDataKind.JUnitStyleTestCaseData && + testFinishData.data + ) { const testCaseData = testFinishData.data as JUnitStyleTestCaseData let testCaseName = testFinishData.displayName @@ -32,7 +35,10 @@ export class PythonLanguageTools testCaseName = match.groups.lookupKey } - return `${testCaseData.className}.${testCaseName}` + // Use the class name as the base, and append the test case name if available. + let result = testCaseData.className + if (testCaseName.length > 0) result += `.${testCaseName}` + return result } return undefined } diff --git a/src/test/suite/language-tools/java.test.ts b/src/test/suite/language-tools/java.test.ts index 524eb3c..fec635a 100644 --- a/src/test/suite/language-tools/java.test.ts +++ b/src/test/suite/language-tools/java.test.ts @@ -99,45 +99,252 @@ suite('Java Language Tools', () => { assert.strictEqual(result.testCases.length, 0) }) - test('map test finish data to lookup key', async () => { - let result = languageTools.mapTestFinishDataToLookupKey({ - displayName: 'myTest', - status: TestStatus.Failed, - dataKind: TestFinishDataKind.JUnitStyleTestCaseData, - data: { - time: 0, - className: 'com.example.ClassName', + const testCases = [ + { + description: 'test method within a class', + input: { + displayName: 'myTest', + status: TestStatus.Failed, + dataKind: TestFinishDataKind.JUnitStyleTestCaseData, + data: { + time: 0, + className: 'com.example.ClassName', + }, }, - }) - assert.strictEqual(result, 'com.example.ClassName.myTest') - - result = languageTools.mapTestFinishDataToLookupKey({ - displayName: 'com.example.MySuite', - status: TestStatus.Failed, - dataKind: TestFinishDataKind.JUnitStyleTestCaseData, - data: { - time: 0, + expected: 'com.example.ClassName.myTest', + }, + { + description: 'suite level test case', + input: { + displayName: 'com.example.MySuite', + status: TestStatus.Failed, + dataKind: TestFinishDataKind.JUnitStyleTestCaseData, + data: { + time: 0, + }, }, - }) - assert.strictEqual(result, 'com.example.MySuite') - - result = languageTools.mapTestFinishDataToLookupKey({ - displayName: 'com.example.MySuite', - status: TestStatus.Failed, - }) - assert.strictEqual(result, undefined) - - result = languageTools.mapTestFinishDataToLookupKey({ - displayName: 'myTest[example1]', - status: TestStatus.Failed, - dataKind: TestFinishDataKind.JUnitStyleTestCaseData, - data: { - time: 0, - className: 'com.example.ClassName', + expected: 'com.example.MySuite', + }, + { + description: 'no dataKind provided', + input: { + displayName: 'com.example.MySuite', + status: TestStatus.Failed, + }, + expected: undefined, + }, + { + description: 'parameterized test cases', + input: { + displayName: 'myTest[example1]', + status: TestStatus.Failed, + dataKind: TestFinishDataKind.JUnitStyleTestCaseData, + data: { + time: 0, + className: 'com.example.ClassName', + }, + }, + expected: 'com.example.ClassName.myTest', + }, + { + description: 'parameterized test with special characters', + input: { + displayName: 'testMethod[example1!@#]', + status: TestStatus.Passed, + dataKind: TestFinishDataKind.JUnitStyleTestCaseData, + data: { + time: 1, + className: 'com.example.SpecialCharsExample', + }, + }, + expected: 'com.example.SpecialCharsExample.testMethod', + }, + { + description: 'parameterized test with spaces', + input: { + displayName: 'testMethod[example with spaces]', + status: TestStatus.Skipped, + dataKind: TestFinishDataKind.JUnitStyleTestCaseData, + data: { + time: 0.5, + className: 'com.example.SpaceTestExample', + }, + }, + expected: 'com.example.SpaceTestExample.testMethod', + }, + { + description: 'parameterized test with multiple brackets', + input: { + displayName: 'testMethod[example[inner]]', + status: TestStatus.Failed, + dataKind: TestFinishDataKind.JUnitStyleTestCaseData, + data: { + time: 2, + className: 'com.example.MultiBracketTestExample', + }, + }, + expected: 'com.example.MultiBracketTestExample.testMethod', + }, + { + description: 'parameterized test with numbers', + input: { + displayName: 'testMethod[12345]', + status: TestStatus.Passed, + dataKind: TestFinishDataKind.JUnitStyleTestCaseData, + data: { + time: 0.1, + className: 'com.example.NumericTestExample', + }, + }, + expected: 'com.example.NumericTestExample.testMethod', + }, + { + description: 'parameterized test with empty brackets', + input: { + displayName: 'testMethod[]', + status: TestStatus.Failed, + dataKind: TestFinishDataKind.JUnitStyleTestCaseData, + data: { + time: 1.5, + className: 'com.example.EmptyBracketTestExample', + }, }, + expected: 'com.example.EmptyBracketTestExample.testMethod', + }, + { + description: 'parameterized test with special symbols', + input: { + displayName: 'testMethod[!@#$%^&*()]', + status: TestStatus.Skipped, + dataKind: TestFinishDataKind.JUnitStyleTestCaseData, + data: { + time: 3, + className: 'com.example.SymbolsTestExample', + }, + }, + expected: 'com.example.SymbolsTestExample.testMethod', + }, + { + description: 'parameterized test with long name', + input: { + displayName: 'testMethod[averylongsubtestnamethatisunusuallylong]', + status: TestStatus.Failed, + dataKind: TestFinishDataKind.JUnitStyleTestCaseData, + data: { + time: 2.5, + className: 'com.example.LongNameTestExample', + }, + }, + expected: 'com.example.LongNameTestExample.testMethod', + }, + { + description: 'parameterized test with nested brackets', + input: { + displayName: 'testMethod[example[nested[brackets]]]', + status: TestStatus.Passed, + dataKind: TestFinishDataKind.JUnitStyleTestCaseData, + data: { + time: 0.2, + className: 'com.example.NestedBracketsTestExample', + }, + }, + expected: 'com.example.NestedBracketsTestExample.testMethod', + }, + { + description: 'successful tests with data', + input: { + displayName: 'mySuccessfulTest', + status: TestStatus.Passed, + dataKind: TestFinishDataKind.JUnitStyleTestCaseData, + data: { + time: 1, + className: 'com.example.SuccessClass', + }, + }, + expected: 'com.example.SuccessClass.mySuccessfulTest', + }, + { + description: 'tests with no className', + input: { + displayName: 'myTestWithoutClass', + status: TestStatus.Failed, + dataKind: TestFinishDataKind.JUnitStyleTestCaseData, + data: { + time: 2, + }, + }, + expected: 'myTestWithoutClass', + }, + { + description: 'unknown dataKind', + input: { + displayName: 'unknownTest', + status: TestStatus.Failed, + dataKind: 'UnknownDataKind', + data: { + time: 0, + className: 'com.example.UnknownClass', + }, + }, + expected: undefined, + }, + { + description: 'null data gracefully', + input: { + displayName: 'nullDataTest', + status: TestStatus.Failed, + dataKind: TestFinishDataKind.JUnitStyleTestCaseData, + data: null, + }, + expected: undefined, + }, + { + description: 'numeric displayName', + input: { + displayName: '123456', + status: TestStatus.Failed, + dataKind: TestFinishDataKind.JUnitStyleTestCaseData, + data: { + time: 0, + className: 'com.example.ClassName', + }, + }, + expected: 'com.example.ClassName.123456', + }, + { + description: 'special characters in displayName', + input: { + displayName: '!@#$%^&*()', + status: TestStatus.Failed, + dataKind: TestFinishDataKind.JUnitStyleTestCaseData, + data: { + time: 0, + className: 'com.example.ClassName', + }, + }, + expected: 'com.example.ClassName.!@#$%^&*()', + }, + { + description: 'empty string as displayName', + input: { + displayName: '', + status: TestStatus.Failed, + dataKind: TestFinishDataKind.JUnitStyleTestCaseData, + data: { + time: 0, + className: 'com.example.ClassName', + }, + }, + expected: 'com.example.ClassName', + }, + ] + + for (const testCase of testCases) { + test(`map test finish data to lookup key: ${testCase.description}`, async () => { + const result = languageTools.mapTestFinishDataToLookupKey(testCase.input) + assert.strictEqual(result, testCase.expected) }) - assert.strictEqual(result, 'com.example.ClassName.myTest') - }) + } test('map test case info to lookup key', async () => { let testInfo = testController.createTestItem('test1', 'test1') diff --git a/src/test/suite/language-tools/python.test.ts b/src/test/suite/language-tools/python.test.ts index f025c7d..2f5a569 100644 --- a/src/test/suite/language-tools/python.test.ts +++ b/src/test/suite/language-tools/python.test.ts @@ -88,49 +88,242 @@ suite('Python Language Tools', () => { assert.ok(executeCommandStub.notCalled) }) - test('map test finish data to lookup key', async () => { - let result = languageTools.mapTestFinishDataToLookupKey({ - displayName: 'test_method', - status: TestStatus.Failed, - dataKind: TestFinishDataKind.JUnitStyleTestCaseData, - data: { - time: 0, - className: 'my.example.test_example.TestMyClass', + const testCases = [ + { + description: 'method within a class', + input: { + displayName: 'test_method', + status: TestStatus.Failed, + dataKind: TestFinishDataKind.JUnitStyleTestCaseData, + data: { + time: 0, + className: 'my.example.test_example.TestMyClass', + }, }, - }) - assert.strictEqual( - result, - 'my.example.test_example.TestMyClass.test_method' - ) - - result = languageTools.mapTestFinishDataToLookupKey({ - displayName: 'test_method', - status: TestStatus.Failed, - dataKind: TestFinishDataKind.JUnitStyleTestCaseData, - data: { - time: 0, - className: 'my.example.test_example', + expected: 'my.example.test_example.TestMyClass.test_method', + }, + { + description: 'standalone method', + input: { + displayName: 'test_method', + status: TestStatus.Failed, + dataKind: TestFinishDataKind.JUnitStyleTestCaseData, + data: { + time: 0, + className: 'my.example.test_example', + }, }, - }) - assert.strictEqual(result, 'my.example.test_example.test_method') - - result = languageTools.mapTestFinishDataToLookupKey({ - displayName: 'test_method[example1]', - status: TestStatus.Failed, - dataKind: TestFinishDataKind.JUnitStyleTestCaseData, - data: { - time: 0, - className: 'my.example.test_example', + expected: 'my.example.test_example.test_method', + }, + { + description: 'parameterized test result', + input: { + displayName: 'test_method[example1]', + status: TestStatus.Failed, + dataKind: TestFinishDataKind.JUnitStyleTestCaseData, + data: { + time: 0, + className: 'my.example.test_example', + }, }, - }) - assert.strictEqual(result, 'my.example.test_example.test_method') + expected: 'my.example.test_example.test_method', + }, + { + description: 'parameterized test with special characters', + input: { + displayName: 'test_method[example1!@#]', + status: TestStatus.Passed, + dataKind: TestFinishDataKind.JUnitStyleTestCaseData, + data: { + time: 1, + className: 'my.special.chars_example', + }, + }, + expected: 'my.special.chars_example.test_method', + }, + { + description: 'parameterized test with spaces', + input: { + displayName: 'test_method[example with spaces]', + status: TestStatus.Skipped, + dataKind: TestFinishDataKind.JUnitStyleTestCaseData, + data: { + time: 0.5, + className: 'my.space.test_example', + }, + }, + expected: 'my.space.test_example.test_method', + }, + { + description: 'parameterized test with multiple brackets', + input: { + displayName: 'test_method[example[inner]]', + status: TestStatus.Failed, + dataKind: TestFinishDataKind.JUnitStyleTestCaseData, + data: { + time: 2, + className: 'my.multibracket.test_example', + }, + }, + expected: 'my.multibracket.test_example.test_method', + }, + { + description: 'parameterized test with numbers', + input: { + displayName: 'test_method[12345]', + status: TestStatus.Passed, + dataKind: TestFinishDataKind.JUnitStyleTestCaseData, + data: { + time: 0.1, + className: 'my.numeric.test_example', + }, + }, + expected: 'my.numeric.test_example.test_method', + }, + { + description: 'parameterized test with empty brackets', + input: { + displayName: 'test_method[]', + status: TestStatus.Failed, + dataKind: TestFinishDataKind.JUnitStyleTestCaseData, + data: { + time: 1.5, + className: 'my.emptybracket.test_example', + }, + }, + expected: 'my.emptybracket.test_example.test_method', + }, + { + description: 'parameterized test with special symbols', + input: { + displayName: 'test_method[!@#$%^&*()]', + status: TestStatus.Skipped, + dataKind: TestFinishDataKind.JUnitStyleTestCaseData, + data: { + time: 3, + className: 'my.symbols.test_example', + }, + }, + expected: 'my.symbols.test_example.test_method', + }, + { + description: 'parameterized test with long name', + input: { + displayName: 'test_method[averylongsubtestnamethatisunusuallylong]', + status: TestStatus.Failed, + dataKind: TestFinishDataKind.JUnitStyleTestCaseData, + data: { + time: 2.5, + className: 'my.longname.test_example', + }, + }, + expected: 'my.longname.test_example.test_method', + }, + { + description: 'parameterized test with nested brackets', + input: { + displayName: 'test_method[example[nested[brackets]]]', + status: TestStatus.Passed, + dataKind: TestFinishDataKind.JUnitStyleTestCaseData, + data: { + time: 0.2, + className: 'my.nestedbrackets.test_example', + }, + }, + expected: 'my.nestedbrackets.test_example.test_method', + }, + { + description: 'result with no data', + input: { + displayName: 'pytest', + status: TestStatus.Failed, + }, + expected: undefined, + }, + { + description: 'successful test case', + input: { + displayName: 'test_successful_method', + status: TestStatus.Passed, + dataKind: TestFinishDataKind.JUnitStyleTestCaseData, + data: { + time: 1, + className: 'my.example.test_example.TestMyClass', + }, + }, + expected: 'my.example.test_example.TestMyClass.test_successful_method', + }, + { + description: 'unknown dataKind', + input: { + displayName: 'unknown_test', + status: TestStatus.Failed, + dataKind: 'UnknownDataKind', + data: { + time: 0, + className: 'my.example.test_example.UnknownClass', + }, + }, + expected: undefined, + }, + { + description: 'method with special characters', + input: { + displayName: 'test_method_with_special_chars!@#', + status: TestStatus.Failed, + dataKind: TestFinishDataKind.JUnitStyleTestCaseData, + data: { + time: 0, + className: 'my.example.test_example.TestMyClass', + }, + }, + expected: + 'my.example.test_example.TestMyClass.test_method_with_special_chars!@#', + }, + { + description: 'method with numeric displayName', + input: { + displayName: '123456', + status: TestStatus.Failed, + dataKind: TestFinishDataKind.JUnitStyleTestCaseData, + data: { + time: 0, + className: 'my.example.test_example.TestMyClass', + }, + }, + expected: 'my.example.test_example.TestMyClass.123456', + }, + { + description: 'empty string as displayName', + input: { + displayName: '', + status: TestStatus.Failed, + dataKind: TestFinishDataKind.JUnitStyleTestCaseData, + data: { + time: 0, + className: 'my.example.test_example.TestMyClass', + }, + }, + expected: 'my.example.test_example.TestMyClass', + }, + { + description: 'null data gracefully', + input: { + displayName: 'null_data_test', + status: TestStatus.Failed, + dataKind: TestFinishDataKind.JUnitStyleTestCaseData, + data: null, + }, + expected: undefined, + }, + ] - result = languageTools.mapTestFinishDataToLookupKey({ - displayName: 'pytest', - status: TestStatus.Failed, + for (const testCase of testCases) { + test(`map test finish data to lookup key: ${testCase.description}`, async () => { + const result = languageTools.mapTestFinishDataToLookupKey(testCase.input) + assert.strictEqual(result, testCase.expected) }) - assert.strictEqual(result, undefined) - }) + } test('map test case info to lookup key', async () => { let testInfo = testController.createTestItem('test1', 'test1')