Skip to content

Commit b87fdc2

Browse files
authored
tests(codecatalyst): improve reporting when tests fail (#3391)
* Drop remote SSH minver to `0.74.0` * Change messaging when minver is not found
1 parent 29d4338 commit b87fdc2

File tree

7 files changed

+77
-25
lines changed

7 files changed

+77
-25
lines changed

scripts/test/launchTestUtilities.ts

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -125,11 +125,6 @@ async function setupVSCode(): Promise<string> {
125125
await installVSCodeExtension(vsCodeExecutablePath, VSCODE_EXTENSION_ID.java)
126126
await installVSCodeExtension(vsCodeExecutablePath, VSCODE_EXTENSION_ID.javadebug)
127127

128-
// On stable/insiders we can install the ssh extension during the tests but not on minver
129-
if (process.env[envvarVscodeTestVersion] === minimum) {
130-
await installVSCodeExtension(vsCodeExecutablePath, VSCODE_EXTENSION_ID.remotessh)
131-
}
132-
133128
console.log('VS Code Test instance has been set up')
134129
return vsCodeExecutablePath
135130
}

src/codecatalyst/tools.ts

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -48,12 +48,22 @@ export async function ensureDependencies(): Promise<Result<DependencyPaths, Canc
4848
vscodeExtensionMinVersion.remotessh
4949
)
5050

51-
return Result.err(
52-
new ToolkitError('Remote SSH extension not installed', {
53-
cancelled: true,
54-
code: 'MissingExtension',
55-
})
56-
)
51+
if (isExtensionInstalled(VSCODE_EXTENSION_ID.remotessh)) {
52+
return Result.err(
53+
new ToolkitError('Remote SSH extension version is too low', {
54+
cancelled: true,
55+
code: 'ExtensionVersionTooLow',
56+
details: { expected: vscodeExtensionMinVersion.remotessh },
57+
})
58+
)
59+
} else {
60+
return Result.err(
61+
new ToolkitError('Remote SSH extension not installed', {
62+
cancelled: true,
63+
code: 'MissingExtension',
64+
})
65+
)
66+
}
5767
}
5868

5969
const tools = await ensureTools()

src/integrationTest/globalSetup.test.ts

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import { VSCODE_EXTENSION_ID } from '../shared/extensions'
1111
import { getLogger } from '../shared/logger'
1212
import { WinstonToolkitLogger } from '../shared/logger/winstonToolkitLogger'
1313
import { activateExtension } from '../shared/utilities/vsCodeUtils'
14-
import { patchObject, setRunnableTimeout } from '../test/setupUtil'
14+
import { mapTestErrors, normalizeError, patchObject, setRunnableTimeout } from '../test/setupUtil'
1515
import { getTestWindow, resetTestWindow } from '../test/shared/vscode/window'
1616

1717
// ASSUMPTION: Tests are not run concurrently
@@ -26,10 +26,8 @@ export async function mochaGlobalSetup(this: Mocha.Runner) {
2626
this.on('hook', hook => setRunnableTimeout(hook, maxTestDuration))
2727
this.on('test', test => setRunnableTimeout(test, maxTestDuration))
2828

29-
// Mocha won't show the full error chain
30-
this.on('fail', (test, err) => {
31-
getLogger().error(`test "${test.title}" failed: %s`, err)
32-
})
29+
// Shows the full error chain when tests fail
30+
mapTestErrors(this, normalizeError)
3331

3432
// Set up a listener for proxying login requests
3533
patchWindow()

src/shared/extensions.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ export const VSCODE_EXTENSION_ID = {
2626
}
2727

2828
export const vscodeExtensionMinVersion = {
29-
remotessh: '0.98.0',
29+
remotessh: '0.74.0',
3030
}
3131

3232
/**

src/test/globalSetup.test.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ import { FakeExtensionContext, FakeMemento } from './fakeExtensionContext'
2323
import { TestLogger } from './testLogger'
2424
import * as testUtil from './testUtil'
2525
import { getTestWindow, resetTestWindow } from './shared/vscode/window'
26-
import { setRunnableTimeout } from './setupUtil'
26+
import { mapTestErrors, normalizeError, setRunnableTimeout } from './setupUtil'
2727

2828
const testReportDir = join(__dirname, '../../../.test-reports')
2929
const testLogOutput = join(testReportDir, 'testLog.log')
@@ -32,15 +32,18 @@ const maxTestDuration = 30_000
3232

3333
// Expectation: Tests are not run concurrently
3434
let testLogger: TestLogger | undefined
35-
let openExternalStub: sinon.SinonStub<Parameters<typeof vscode['env']['openExternal']>, Thenable<boolean>>
35+
let openExternalStub: sinon.SinonStub<Parameters<(typeof vscode)['env']['openExternal']>, Thenable<boolean>>
3636

37-
export async function mochaGlobalSetup(this: Mocha.Context) {
37+
export async function mochaGlobalSetup(this: Mocha.Runner) {
3838
// Clean up and set up test logs
3939
try {
4040
await remove(testLogOutput)
4141
} catch (e) {}
4242
mkdirpSync(testReportDir)
4343

44+
// Shows the full error chain when tests fail
45+
mapTestErrors(this, normalizeError)
46+
4447
// Extension activation has many side-effects such as changing globals
4548
// For stability in tests we will wait until the extension has activated prior to injecting mocks
4649
const activationLogger = (msg: string, ...meta: any[]) => console.log(format(msg, ...meta))

src/test/setupUtil.ts

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import * as vscode from 'vscode'
99
import { getLogger } from '../shared/logger'
1010
import { hasKey } from '../shared/utilities/tsUtils'
1111
import { getTestWindow, printPendingUiElements } from './shared/vscode/window'
12+
import { ToolkitError, formatError } from '../shared/errors'
1213

1314
const runnableTimeout = Symbol('runnableTimeout')
1415

@@ -54,6 +55,49 @@ export function setRunnableTimeout(test: Mocha.Runnable, maxTestDuration: number
5455
return test
5556
}
5657

58+
export function skipTest(testOrCtx: Mocha.Context | Mocha.Test | undefined, reason?: string) {
59+
const test =
60+
testOrCtx?.type === 'test' ? (testOrCtx as Mocha.Test) : (testOrCtx as Mocha.Context | undefined)?.currentTest
61+
62+
if (test) {
63+
test.title += ` (skipped${reason ? ` - ${reason}` : ''})`
64+
test.skip()
65+
}
66+
}
67+
68+
export function skipSuite(suite: Mocha.Suite, reason?: string) {
69+
suite.eachTest(test => skipTest(test, reason))
70+
}
71+
72+
export function mapTestErrors(runner: Mocha.Runner, fn: (err: unknown, test: Mocha.Test) => any) {
73+
return runner.prependListener('fail', (test, err) => {
74+
test.err = fn(err, test) || err
75+
})
76+
}
77+
78+
/**
79+
* Formats any known sub-classes of {@link Error} for better compatability with test reporters.
80+
*
81+
* Most test reporters will only output the name + message + stack trace so any relevant
82+
* info must go into those fields.
83+
*/
84+
export function normalizeError(err?: unknown) {
85+
if (err instanceof ToolkitError) {
86+
// Error has to be mutated to show up in the report:
87+
// https://github.com/michaelleeallen/mocha-junit-reporter/blob/4b17772f8da33d580fafa4d124e5c11142a70c1f/index.js#L262
88+
//
89+
// We'll just patch the message/stack trace even though it's arguably incorrect (and looks kind of ugly)
90+
// Once `cause` is more common in the JS ecosystem we'll start to see support from test reporters
91+
92+
return Object.assign(err, {
93+
message: formatError(err).replace(`${err.name}: `, ''),
94+
stack: err.stack?.replace(err.message, err.trace.replace(`${err.name}: `, '') + '\n'),
95+
})
96+
}
97+
98+
return err
99+
}
100+
57101
export function patchObject<T extends Record<string, any>, U extends keyof T>(
58102
obj: T,
59103
key: U,

src/testE2E/codecatalyst/client.test.ts

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,9 @@ import { waitUntil } from '../../shared/utilities/timeoutUtils'
2222
import { AccessDeniedException } from '@aws-sdk/client-sso-oidc'
2323
import { GetDevEnvironmentRequest } from 'aws-sdk/clients/codecatalyst'
2424
import { getTestWindow } from '../../test/shared/vscode/window'
25-
import { patchObject, registerAuthHook, using } from '../../test/setupUtil'
25+
import { patchObject, registerAuthHook, skipTest, using } from '../../test/setupUtil'
2626
import { isExtensionInstalled } from '../../shared/utilities/vsCodeUtils'
27-
import { VSCODE_EXTENSION_ID, vscodeExtensionMinVersion } from '../../shared/extensions'
27+
import { VSCODE_EXTENSION_ID } from '../../shared/extensions'
2828
import { captureEventOnce } from '../../test/testUtil'
2929
import { toStream } from '../../shared/utilities/collectionUtils'
3030
import { toCollection } from '../../shared/utilities/asyncCollection'
@@ -194,8 +194,8 @@ describe('Test how this codebase uses the CodeCatalyst API', function () {
194194
})
195195

196196
it('prompts to install the ssh extension if not available', async function () {
197-
if (isExtensionInstalled(VSCODE_EXTENSION_ID.remotessh, vscodeExtensionMinVersion.remotessh)) {
198-
this.skip()
197+
if (isExtensionInstalled(VSCODE_EXTENSION_ID.remotessh)) {
198+
skipTest(this, 'remote ssh already installed')
199199
}
200200

201201
await assert.rejects(
@@ -226,7 +226,9 @@ describe('Test how this codebase uses the CodeCatalyst API', function () {
226226

227227
it('connects to a running Dev Environment', async function () {
228228
// Get necessary objects to run the ssh command.
229-
const { SessionProcess, hostname, sshPath } = await prepareDevEnvConnection(client, { ...defaultDevEnv })
229+
const { SessionProcess, hostname, sshPath } = await prepareDevEnvConnection(client, {
230+
...defaultDevEnv,
231+
})
230232

231233
// Through the actual ssh connection, run 'ls' command in the dev env.
232234
const lsOutput = (await new SessionProcess(sshPath, [hostname, 'ls', '/projects']).run()).stdout

0 commit comments

Comments
 (0)