diff --git a/src/client/testing/testController/pytest/pytestDiscoveryAdapter.ts b/src/client/testing/testController/pytest/pytestDiscoveryAdapter.ts index 211c322d67f1..b83224d4161b 100644 --- a/src/client/testing/testController/pytest/pytestDiscoveryAdapter.ts +++ b/src/client/testing/testController/pytest/pytestDiscoveryAdapter.ts @@ -55,12 +55,13 @@ export class PytestTestDiscoveryAdapter implements ITestDiscoveryAdapter { const uuid = this.testServer.createUUID(uri.fsPath); const settings = this.configSettings.getSettings(uri); const { pytestArgs } = settings.testing; + const cwd = settings.testing.cwd && settings.testing.cwd.length > 0 ? settings.testing.cwd : uri.fsPath; const pythonPathParts: string[] = process.env.PYTHONPATH?.split(path.delimiter) ?? []; const pythonPathCommand = [fullPluginPath, ...pythonPathParts].join(path.delimiter); const spawnOptions: SpawnOptions = { - cwd: uri.fsPath, + cwd, throwOnStdErr: true, extraVariables: { PYTHONPATH: pythonPathCommand, diff --git a/src/client/testing/testController/pytest/pytestExecutionAdapter.ts b/src/client/testing/testController/pytest/pytestExecutionAdapter.ts index b68b80945ef3..a75a6089627c 100644 --- a/src/client/testing/testController/pytest/pytestExecutionAdapter.ts +++ b/src/client/testing/testController/pytest/pytestExecutionAdapter.ts @@ -84,12 +84,13 @@ export class PytestTestExecutionAdapter implements ITestExecutionAdapter { this.configSettings.isTestExecution(); const settings = this.configSettings.getSettings(uri); const { pytestArgs } = settings.testing; + const cwd = settings.testing.cwd && settings.testing.cwd.length > 0 ? settings.testing.cwd : uri.fsPath; const pythonPathParts: string[] = process.env.PYTHONPATH?.split(path.delimiter) ?? []; const pythonPathCommand = [fullPluginPath, ...pythonPathParts].join(path.delimiter); const spawnOptions: SpawnOptions = { - cwd: uri.fsPath, + cwd, throwOnStdErr: true, extraVariables: { PYTHONPATH: pythonPathCommand, @@ -131,7 +132,7 @@ export class PytestTestExecutionAdapter implements ITestExecutionAdapter { const pytestPort = this.testServer.getPort().toString(); const pytestUUID = uuid.toString(); const launchOptions: LaunchOptions = { - cwd: uri.fsPath, + cwd, args: testArgs, token: spawnOptions.token, testProvider: PYTEST_PROVIDER, @@ -156,7 +157,7 @@ export class PytestTestExecutionAdapter implements ITestExecutionAdapter { return Promise.reject(ex); } - const executionPayload: ExecutionTestPayload = { cwd: uri.fsPath, status: 'success', error: '' }; + const executionPayload: ExecutionTestPayload = { cwd, status: 'success', error: '' }; return executionPayload; } } diff --git a/src/client/testing/testController/unittest/testDiscoveryAdapter.ts b/src/client/testing/testController/unittest/testDiscoveryAdapter.ts index 69438e341f14..6deca55117ea 100644 --- a/src/client/testing/testController/unittest/testDiscoveryAdapter.ts +++ b/src/client/testing/testController/unittest/testDiscoveryAdapter.ts @@ -19,8 +19,6 @@ import { * Wrapper class for unittest test discovery. This is where we call `runTestCommand`. */ export class UnittestTestDiscoveryAdapter implements ITestDiscoveryAdapter { - private cwd: string | undefined; - constructor( public testServer: ITestServer, public configSettings: IConfigurationService, @@ -31,16 +29,16 @@ export class UnittestTestDiscoveryAdapter implements ITestDiscoveryAdapter { public async discoverTests(uri: Uri): Promise { const settings = this.configSettings.getSettings(uri); const { unittestArgs } = settings.testing; + const cwd = settings.testing.cwd && settings.testing.cwd.length > 0 ? settings.testing.cwd : uri.fsPath; const command = buildDiscoveryCommand(unittestArgs); - this.cwd = uri.fsPath; const uuid = this.testServer.createUUID(uri.fsPath); const options: TestCommandOptions = { workspaceFolder: uri, command, - cwd: this.cwd, + cwd, uuid, outChannel: this.outputChannel, }; @@ -57,7 +55,7 @@ export class UnittestTestDiscoveryAdapter implements ITestDiscoveryAdapter { // placeholder until after the rewrite is adopted // TODO: remove after adoption. const discoveryPayload: DiscoveredTestPayload = { - cwd: uri.fsPath, + cwd, status: 'success', }; return discoveryPayload; diff --git a/src/client/testing/testController/unittest/testExecutionAdapter.ts b/src/client/testing/testController/unittest/testExecutionAdapter.ts index bf3038817f8b..4cab941c2608 100644 --- a/src/client/testing/testController/unittest/testExecutionAdapter.ts +++ b/src/client/testing/testController/unittest/testExecutionAdapter.ts @@ -23,8 +23,6 @@ import { startTestIdServer } from '../common/utils'; */ export class UnittestTestExecutionAdapter implements ITestExecutionAdapter { - private cwd: string | undefined; - constructor( public testServer: ITestServer, public configSettings: IConfigurationService, @@ -62,15 +60,15 @@ export class UnittestTestExecutionAdapter implements ITestExecutionAdapter { debugBool?: boolean, ): Promise { const settings = this.configSettings.getSettings(uri); - const { cwd, unittestArgs } = settings.testing; + const { unittestArgs } = settings.testing; + const cwd = settings.testing.cwd && settings.testing.cwd.length > 0 ? settings.testing.cwd : uri.fsPath; const command = buildExecutionCommand(unittestArgs); - this.cwd = cwd || uri.fsPath; const options: TestCommandOptions = { workspaceFolder: uri, command, - cwd: this.cwd, + cwd, uuid, debugBool, testIds, @@ -87,7 +85,7 @@ export class UnittestTestExecutionAdapter implements ITestExecutionAdapter { }); // placeholder until after the rewrite is adopted // TODO: remove after adoption. - const executionPayload: ExecutionTestPayload = { cwd: uri.fsPath, status: 'success', error: '' }; + const executionPayload: ExecutionTestPayload = { cwd, status: 'success', error: '' }; return executionPayload; } } diff --git a/src/test/testing/testController/pytest/pytestDiscoveryAdapter.unit.test.ts b/src/test/testing/testController/pytest/pytestDiscoveryAdapter.unit.test.ts index 0286235be1bf..18212b2d1032 100644 --- a/src/test/testing/testController/pytest/pytestDiscoveryAdapter.unit.test.ts +++ b/src/test/testing/testController/pytest/pytestDiscoveryAdapter.unit.test.ts @@ -105,9 +105,10 @@ suite('pytest test discovery adapter', () => { }); test('Test discovery correctly pulls pytest args from config service settings', async () => { // set up a config service with different pytest args + const expectedPathNew = path.join('other', 'path'); const configServiceNew: IConfigurationService = ({ getSettings: () => ({ - testing: { pytestArgs: ['.', 'abc', 'xyz'] }, + testing: { pytestArgs: ['.', 'abc', 'xyz'], cwd: expectedPathNew }, }), } as unknown) as IConfigurationService; @@ -120,7 +121,7 @@ suite('pytest test discovery adapter', () => { expectedArgs, typeMoq.It.is((options) => { assert.deepEqual(options.extraVariables, expectedExtraVariables); - assert.equal(options.cwd, expectedPath); + assert.equal(options.cwd, expectedPathNew); assert.equal(options.throwOnStdErr, true); return true; }), diff --git a/src/test/testing/testController/pytest/pytestExecutionAdapter.unit.test.ts b/src/test/testing/testController/pytest/pytestExecutionAdapter.unit.test.ts index 9f4c41ba8309..44116fd753b0 100644 --- a/src/test/testing/testController/pytest/pytestExecutionAdapter.unit.test.ts +++ b/src/test/testing/testController/pytest/pytestExecutionAdapter.unit.test.ts @@ -138,6 +138,55 @@ suite('pytest test execution adapter', () => { typeMoq.Times.once(), ); }); + test('pytest execution respects settings.testing.cwd when present', async () => { + const newCwd = path.join('new', 'path'); + configService = ({ + getSettings: () => ({ + testing: { pytestArgs: ['.'], cwd: newCwd }, + }), + isTestExecution: () => false, + } as unknown) as IConfigurationService; + const uri = Uri.file(myTestPath); + const uuid = 'uuid123'; + testServer + .setup((t) => t.onDiscoveryDataReceived(typeMoq.It.isAny(), typeMoq.It.isAny())) + .returns(() => ({ + dispose: () => { + /* no-body */ + }, + })); + testServer.setup((t) => t.createUUID(typeMoq.It.isAny())).returns(() => uuid); + const outputChannel = typeMoq.Mock.ofType(); + const testRun = typeMoq.Mock.ofType(); + adapter = new PytestTestExecutionAdapter(testServer.object, configService, outputChannel.object); + await adapter.runTests(uri, [], false, testRun.object, execFactory.object); + + const pathToPythonFiles = path.join(EXTENSION_ROOT_DIR, 'pythonFiles'); + const pathToPythonScript = path.join(pathToPythonFiles, 'vscode_pytest', 'run_pytest_script.py'); + const expectedArgs = [pathToPythonScript, '--rootdir', myTestPath]; + const expectedExtraVariables = { + PYTHONPATH: pathToPythonFiles, + TEST_UUID: 'uuid123', + TEST_PORT: '12345', + }; + + execService.verify( + (x) => + x.exec( + expectedArgs, + typeMoq.It.is((options) => { + assert.equal(options.extraVariables?.PYTHONPATH, expectedExtraVariables.PYTHONPATH); + assert.equal(options.extraVariables?.TEST_UUID, expectedExtraVariables.TEST_UUID); + assert.equal(options.extraVariables?.TEST_PORT, expectedExtraVariables.TEST_PORT); + assert.equal(options.extraVariables?.RUN_TEST_IDS_PORT, '54321'); + assert.equal(options.cwd, newCwd); + assert.equal(options.throwOnStdErr, true); + return true; + }), + ), + typeMoq.Times.once(), + ); + }); test('Debug launched correctly for pytest', async () => { const uri = Uri.file(myTestPath); const uuid = 'uuid123'; diff --git a/src/test/testing/testController/unittest/testDiscoveryAdapter.unit.test.ts b/src/test/testing/testController/unittest/testDiscoveryAdapter.unit.test.ts index ef21655e93e4..dc883afdf441 100644 --- a/src/test/testing/testController/unittest/testDiscoveryAdapter.unit.test.ts +++ b/src/test/testing/testController/unittest/testDiscoveryAdapter.unit.test.ts @@ -54,4 +54,41 @@ suite('Unittest test discovery adapter', () => { uuid: '123456789', }); }); + test('DiscoverTests should respect settings.testings.cwd when present', async () => { + let options: TestCommandOptions | undefined; + stubConfigSettings = ({ + getSettings: () => ({ + testing: { unittestArgs: ['-v', '-s', '.', '-p', 'test*'], cwd: '/foo' }, + }), + } as unknown) as IConfigurationService; + + const stubTestServer = ({ + sendCommand(opt: TestCommandOptions): Promise { + delete opt.outChannel; + options = opt; + return Promise.resolve(); + }, + onDiscoveryDataReceived: () => { + // no body + }, + createUUID: () => '123456789', + } as unknown) as ITestServer; + + const uri = Uri.file('/foo/bar'); + const newCwd = '/foo'; + const script = path.join(EXTENSION_ROOT_DIR, 'pythonFiles', 'unittestadapter', 'discovery.py'); + + const adapter = new UnittestTestDiscoveryAdapter(stubTestServer, stubConfigSettings, outputChannel.object); + adapter.discoverTests(uri); + + assert.deepStrictEqual(options, { + workspaceFolder: uri, + cwd: newCwd, + command: { + script, + args: ['--udiscovery', '-v', '-s', '.', '-p', 'test*'], + }, + uuid: '123456789', + }); + }); }); diff --git a/src/test/testing/testController/unittest/testExecutionAdapter.unit.test.ts b/src/test/testing/testController/unittest/testExecutionAdapter.unit.test.ts index 88126225a177..4d4a8d0ebee4 100644 --- a/src/test/testing/testController/unittest/testExecutionAdapter.unit.test.ts +++ b/src/test/testing/testController/unittest/testExecutionAdapter.unit.test.ts @@ -62,4 +62,43 @@ suite('Unittest test execution adapter', () => { assert.deepStrictEqual(options, expectedOptions); }); }); + test('runTests should respect settings.testing.cwd when present', async () => { + stubConfigSettings = ({ + getSettings: () => ({ + testing: { unittestArgs: ['-v', '-s', '.', '-p', 'test*'], cwd: '/foo' }, + }), + } as unknown) as IConfigurationService; + let options: TestCommandOptions | undefined; + + const stubTestServer = ({ + sendCommand(opt: TestCommandOptions, runTestIdPort?: string): Promise { + delete opt.outChannel; + options = opt; + assert(runTestIdPort !== undefined); + return Promise.resolve(); + }, + onRunDataReceived: () => { + // no body + }, + createUUID: () => '123456789', + } as unknown) as ITestServer; + + const newCwd = '/foo'; + const uri = Uri.file('/foo/bar'); + const script = path.join(EXTENSION_ROOT_DIR, 'pythonFiles', 'unittestadapter', 'execution.py'); + + const adapter = new UnittestTestExecutionAdapter(stubTestServer, stubConfigSettings, outputChannel.object); + const testIds = ['test1id', 'test2id']; + adapter.runTests(uri, testIds, false).then(() => { + const expectedOptions: TestCommandOptions = { + workspaceFolder: uri, + command: { script, args: ['--udiscovery', '-v', '-s', '.', '-p', 'test*'] }, + cwd: newCwd, + uuid: '123456789', + debugBool: false, + testIds, + }; + assert.deepStrictEqual(options, expectedOptions); + }); + }); });