diff --git a/src/client/testing/common/managers/baseTestManager.ts b/src/client/testing/common/managers/baseTestManager.ts index 96bdcbf01811..64026feee56a 100644 --- a/src/client/testing/common/managers/baseTestManager.ts +++ b/src/client/testing/common/managers/baseTestManager.ts @@ -30,7 +30,7 @@ import { IServiceContainer } from '../../../ioc/types'; import { EventName } from '../../../telemetry/constants'; import { sendTelemetryEvent } from '../../../telemetry/index'; import { TestDiscoveryTelemetry, TestRunTelemetry } from '../../../telemetry/types'; -import { IPythonTestMessage, ITestDiagnosticService, WorkspaceTestStatus } from '../../types'; +import { IPythonTestFailMessage, IPythonTestMessage, ITestDiagnosticService, WorkspaceTestStatus } from '../../types'; import { copyDesiredTestResults } from '../testUtils'; import { CANCELLATION_REASON, CommandSource, TEST_OUTPUT_CHANNEL } from './../constants'; import { @@ -415,7 +415,7 @@ export abstract class BaseTestManager implements ITestManager { fs.arePathsSame(fileUri.fsPath, Uri.file(msg.testFilePath).fsPath) && msg.status !== TestStatus.Pass ) { - const diagnostic = this.createDiagnostics(msg); + const diagnostic = this.createDiagnostics(msg as IPythonTestFailMessage); newDiagnostics.push(diagnostic); } } @@ -491,24 +491,25 @@ export abstract class BaseTestManager implements ITestManager { }); } - private createDiagnostics(message: IPythonTestMessage): Diagnostic { - const stackStart = message.locationStack![0]; - const diagPrefix = this.unitTestDiagnosticService.getMessagePrefix(message.status!); + private createDiagnostics(message: IPythonTestFailMessage): Diagnostic { + const stackStart = message.locationStack[0]; + const diagMsg = this.getDiagnosticMessage(message); const severity = this.unitTestDiagnosticService.getSeverity(message.severity)!; - const diagMsg = message.message ? message.message.split('\n')[0] : ''; - const diagnostic = new Diagnostic( - stackStart.location.range, - `${diagPrefix ? `${diagPrefix}: ` : 'Ok'}${diagMsg}`, - severity, - ); + const diagnostic = new Diagnostic(stackStart.location.range, diagMsg, severity); diagnostic.code = message.code; diagnostic.source = message.provider; const relatedInfoArr: DiagnosticRelatedInformation[] = []; - for (const frameDetails of message.locationStack!) { + for (const frameDetails of message.locationStack) { const relatedInfo = new DiagnosticRelatedInformation(frameDetails.location, frameDetails.lineText); relatedInfoArr.push(relatedInfo); } diagnostic.relatedInformation = relatedInfoArr; return diagnostic; } + + private getDiagnosticMessage(message: IPythonTestFailMessage): string { + const diagPrefix = this.unitTestDiagnosticService.getMessagePrefix(message.status)!; + const diagMsg = message.message ? message.message.split('\n')[0] : ''; + return `${diagPrefix}: ${diagMsg}`; + } } diff --git a/src/client/testing/pytest/services/testMessageService.ts b/src/client/testing/pytest/services/testMessageService.ts index 21195045771b..44489229d5c0 100644 --- a/src/client/testing/pytest/services/testMessageService.ts +++ b/src/client/testing/pytest/services/testMessageService.ts @@ -12,7 +12,13 @@ import { IFileSystem } from '../../../common/platform/types'; import { Product } from '../../../common/types'; import { IServiceContainer } from '../../../ioc/types'; import { FlattenedTestFunction, ITestMessageService, Tests, TestStatus } from '../../common/types'; -import { ILocationStackFrameDetails, IPythonTestMessage, PythonTestMessageSeverity } from '../../types'; +import { + ILocationStackFrameDetails, + IPythonTestFailMessage, + IPythonTestMessage, + IPythonTestPassMessage, + PythonTestMessageSeverity, +} from '../../types'; @injectable() export class TestMessageService implements ITestMessageService { @@ -33,12 +39,16 @@ export class TestMessageService implements ITestMessageService { }, []); const messages: IPythonTestMessage[] = []; for (const tf of testFuncs) { + if (tf.testFunction.status === undefined) { + // The test results were not updated after the test run. + continue; + } const nameToRun = tf.testFunction.nameToRun; const provider = ProductNames.get(Product.pytest)!; - const status = tf.testFunction.status!; + const status = tf.testFunction.status; if (status === TestStatus.Pass) { // If the test passed, there's not much to do with it. - const msg: IPythonTestMessage = { + const msg: IPythonTestPassMessage = { code: nameToRun, severity: PythonTestMessageSeverity.Pass, provider: provider, @@ -59,7 +69,7 @@ export class TestMessageService implements ITestMessageService { severity = PythonTestMessageSeverity.Skip; } - const msg: IPythonTestMessage = { + const msg: IPythonTestFailMessage = { code: nameToRun, message: message, severity: severity, diff --git a/src/client/testing/types.ts b/src/client/testing/types.ts index f0b6edc584a5..764569ccc412 100644 --- a/src/client/testing/types.ts +++ b/src/client/testing/types.ts @@ -169,17 +169,27 @@ export interface ITestDiagnosticService { getSeverity(unitTestSeverity: PythonTestMessageSeverity): DiagnosticSeverity | undefined; } -export interface IPythonTestMessage { - code: string | undefined; - message?: string; +interface IPythonTestMessageCommon { + code: string; + testFilePath: string; + status: TestStatus; severity: PythonTestMessageSeverity; - provider: string | undefined; - traceback?: string; testTime: number; - status?: TestStatus; - locationStack?: ILocationStackFrameDetails[]; - testFilePath: string; + provider: string; +} +export interface IPythonTestPassMessage extends IPythonTestMessageCommon { + status: TestStatus.Pass; + severity: PythonTestMessageSeverity.Pass; +} +export interface IPythonTestFailMessage extends IPythonTestMessageCommon { + status: Exclude; + severity: PythonTestMessageSeverity.Error | PythonTestMessageSeverity.Skip; + // The following are failure-specific. + message?: string; + traceback?: string; + locationStack: ILocationStackFrameDetails[]; } +export type IPythonTestMessage = IPythonTestPassMessage | IPythonTestFailMessage; export enum PythonTestMessageSeverity { Error, Failure, diff --git a/src/test/testing/pytest/pytest.testMessageService.test.ts b/src/test/testing/pytest/pytest.testMessageService.test.ts index bad0ec0bde45..efdf6c7fe680 100644 --- a/src/test/testing/pytest/pytest.testMessageService.test.ts +++ b/src/test/testing/pytest/pytest.testMessageService.test.ts @@ -25,6 +25,7 @@ import { XUnitParser } from '../../../client/testing/common/xUnitParser'; import { TestMessageService } from '../../../client/testing/pytest/services/testMessageService'; import { ILocationStackFrameDetails, + IPythonTestFailMessage, IPythonTestMessage, PythonTestMessageSeverity, } from '../../../client/testing/types'; @@ -54,8 +55,10 @@ async function testMessageProperties( imported: boolean = false, status: TestStatus, ) { + const failure = message as IPythonTestFailMessage; + const expectedFailure = expectedMessage as IPythonTestFailMessage; assert.equal(message.code, expectedMessage.code, 'IPythonTestMessage code'); - assert.equal(message.message, expectedMessage.message, 'IPythonTestMessage message'); + assert.equal(failure.message, expectedFailure.message, 'IPythonTestMessage message'); assert.equal(message.severity, expectedMessage.severity, 'IPythonTestMessage severity'); assert.equal(message.provider, expectedMessage.provider, 'IPythonTestMessage provider'); assert.isNumber(message.testTime, 'IPythonTestMessage testTime'); @@ -63,36 +66,36 @@ async function testMessageProperties( assert.equal(message.testFilePath, expectedMessage.testFilePath, 'IPythonTestMessage testFilePath'); if (status !== TestStatus.Pass) { assert.equal( - message.locationStack![0].lineText, - expectedMessage.locationStack![0].lineText, + failure.locationStack[0].lineText, + expectedFailure.locationStack[0].lineText, 'IPythonTestMessage line text', ); assert.equal( - message.locationStack![0].location.uri.fsPath, - expectedMessage.locationStack![0].location.uri.fsPath, + failure.locationStack[0].location.uri.fsPath, + expectedFailure.locationStack[0].location.uri.fsPath, 'IPythonTestMessage locationStack fsPath', ); if (status !== TestStatus.Skipped) { assert.equal( - message.locationStack![1].lineText, - expectedMessage.locationStack![1].lineText, + failure.locationStack[1].lineText, + expectedFailure.locationStack[1].lineText, 'IPythonTestMessage line text', ); assert.equal( - message.locationStack![1].location.uri.fsPath, - expectedMessage.locationStack![1].location.uri.fsPath, + failure.locationStack[1].location.uri.fsPath, + expectedFailure.locationStack[1].location.uri.fsPath, 'IPythonTestMessage locationStack fsPath', ); } if (imported) { assert.equal( - message.locationStack![2].lineText, - expectedMessage.locationStack![2].lineText, + failure.locationStack[2].lineText, + expectedFailure.locationStack[2].lineText, 'IPythonTestMessage imported line text', ); assert.equal( - message.locationStack![2].location.uri.fsPath, - expectedMessage.locationStack![2].location.uri.fsPath, + failure.locationStack[2].location.uri.fsPath, + expectedFailure.locationStack[2].location.uri.fsPath, 'IPythonTestMessage imported location fsPath', ); } @@ -223,14 +226,14 @@ suite('Unit Tests - PyTest - TestMessageService', () => { const expectedLocationStack = await getExpectedLocationStackFromTestDetails(td); expectedMessage = { code: td.nameToRun, - message: td.message, severity: expectedSeverity, provider: ProductNames.get(Product.pytest)!, testTime: 0, status: td.status, + message: td.message, locationStack: expectedLocationStack, testFilePath: path.join(UNITTEST_TEST_FILES_PATH, td.fileName), - }; + } as IPythonTestFailMessage; testMessage = testMessages.find((tm) => tm.code === td.nameToRun)!; }); test('Message', async () => {