Skip to content

Commit 3ca78ba

Browse files
authored
test(lambda): fix sync integration test #5948
## Problem The integration tests for sync is consistently failing. There seems to be some issue with mocking. Since these tests are mocking the process run, we are moving these test to unit test to be executed for every CI ## Solution - Add more test to sync unit test in `sync.test.ts` - Remove sync integration test case from `sam.test.ts` in favor of added unit test in `sync.tes.ts` - Move `runInTerminal()` and `ProcessTerminal` class to a separate file` (more decomposition for `sync.ts` will follow) - Add unit test for `DeployTypeWizard.test.ts` - Add assertion methods to `PrompterTester` class.
1 parent 09510a3 commit 3ca78ba

File tree

12 files changed

+1421
-614
lines changed

12 files changed

+1421
-614
lines changed

packages/core/src/shared/sam/build.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
*/
55

66
import * as vscode from 'vscode'
7-
import { TemplateItem, createTemplatePrompter, getSamCliPathAndVersion, runInTerminal } from './sync'
7+
import { TemplateItem, createTemplatePrompter } from './sync'
88
import { ChildProcess } from '../utilities/processUtils'
99
import { addTelemetryEnvVar } from './cli/samCliInvokerUtils'
1010
import { Wizard } from '../wizards/wizard'
@@ -19,8 +19,9 @@ import globals from '../extensionGlobals'
1919
import { TreeNode } from '../treeview/resourceTreeDataProvider'
2020
import { telemetry } from '../telemetry/telemetry'
2121
import { getSpawnEnv } from '../env/resolveEnv'
22-
import { getProjectRoot, isDotnetRuntime } from './utils'
22+
import { getProjectRoot, getSamCliPathAndVersion, isDotnetRuntime } from './utils'
2323
import { getConfigFileUri, validateSamBuildConfig } from './config'
24+
import { runInTerminal } from './processTerminal'
2425

2526
export interface BuildParams {
2627
readonly template: TemplateItem

packages/core/src/shared/sam/deploy.ts

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -23,15 +23,9 @@ import { CancellationError } from '../utilities/timeoutUtils'
2323
import { Wizard } from '../wizards/wizard'
2424
import { addTelemetryEnvVar } from './cli/samCliInvokerUtils'
2525
import { validateSamDeployConfig, SamConfig, writeSamconfigGlobal } from './config'
26-
import {
27-
TemplateItem,
28-
createStackPrompter,
29-
createBucketPrompter,
30-
createTemplatePrompter,
31-
getSamCliPathAndVersion,
32-
runInTerminal,
33-
} from './sync'
34-
import { getProjectRoot, getSource } from './utils'
26+
import { TemplateItem, createStackPrompter, createBucketPrompter, createTemplatePrompter } from './sync'
27+
import { getProjectRoot, getSamCliPathAndVersion, getSource } from './utils'
28+
import { runInTerminal } from './processTerminal'
3529

3630
export interface DeployParams {
3731
readonly paramsSource: ParamsSource
Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
/*!
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
import * as vscode from 'vscode'
6+
7+
import { ToolkitError, UnknownError } from '../errors'
8+
import globals from '../extensionGlobals'
9+
import { isCloud9 } from '../extensionUtilities'
10+
import { ChildProcess, ChildProcessResult } from '../utilities/processUtils'
11+
import { CancellationError } from '../utilities/timeoutUtils'
12+
import { getLogger } from '../logger'
13+
import { removeAnsi } from '../utilities/textUtilities'
14+
import { isAutomation } from '../vscode/env'
15+
16+
let oldTerminal: ProcessTerminal | undefined
17+
export async function runInTerminal(proc: ChildProcess, cmd: string) {
18+
const handleResult = (result?: ChildProcessResult) => {
19+
if (result && result.exitCode !== 0) {
20+
const message = `sam ${cmd} exited with a non-zero exit code: ${result.exitCode}`
21+
if (result.stderr.includes('is up to date')) {
22+
throw ToolkitError.chain(result.error, message, {
23+
code: 'NoUpdateExitCode',
24+
})
25+
}
26+
throw ToolkitError.chain(result.error, message, {
27+
code: 'NonZeroExitCode',
28+
})
29+
}
30+
}
31+
32+
// `createTerminal` doesn't work on C9 so we use the output channel instead
33+
if (isCloud9()) {
34+
globals.outputChannel.show()
35+
36+
const result = proc.run({
37+
onStdout: (text) => globals.outputChannel.append(removeAnsi(text)),
38+
onStderr: (text) => globals.outputChannel.append(removeAnsi(text)),
39+
})
40+
await proc.send('\n')
41+
42+
return handleResult(await result)
43+
}
44+
45+
// The most recent terminal won't get garbage collected until the next run
46+
if (oldTerminal?.stopped === true) {
47+
oldTerminal.close()
48+
}
49+
const pty = (oldTerminal = new ProcessTerminal(proc))
50+
const terminal = vscode.window.createTerminal({ pty, name: `SAM ${cmd}` })
51+
terminal.sendText('\n')
52+
terminal.show()
53+
54+
const result = await new Promise<ChildProcessResult>((resolve) => pty.onDidExit(resolve))
55+
if (pty.cancelled) {
56+
throw result.error !== undefined
57+
? ToolkitError.chain(result.error, 'SAM CLI was cancelled before exiting', { cancelled: true })
58+
: new CancellationError('user')
59+
} else {
60+
return handleResult(result)
61+
}
62+
}
63+
64+
// This is a decent improvement over using the output channel but it isn't a tty/pty
65+
// SAM CLI uses `click` which has reduced functionality if `os.isatty` returns false
66+
// Historically, Windows lack of a pty-equivalent is why it's not available in libuv
67+
// Maybe it's doable now with the ConPTY API? https://github.com/libuv/libuv/issues/2640
68+
class ProcessTerminal implements vscode.Pseudoterminal {
69+
private readonly onDidCloseEmitter = new vscode.EventEmitter<number | void>()
70+
private readonly onDidWriteEmitter = new vscode.EventEmitter<string>()
71+
private readonly onDidExitEmitter = new vscode.EventEmitter<ChildProcessResult>()
72+
public readonly onDidWrite = this.onDidWriteEmitter.event
73+
public readonly onDidClose = this.onDidCloseEmitter.event
74+
public readonly onDidExit = this.onDidExitEmitter.event
75+
76+
public constructor(private readonly process: ChildProcess) {
77+
// Used in integration tests
78+
if (isAutomation()) {
79+
// Disable because it is a test.
80+
// eslint-disable-next-line aws-toolkits/no-console-log
81+
this.onDidWrite((text) => console.log(text.trim()))
82+
}
83+
}
84+
85+
#cancelled = false
86+
public get cancelled() {
87+
return this.#cancelled
88+
}
89+
90+
public get stopped() {
91+
return this.process.stopped
92+
}
93+
94+
public open(initialDimensions: vscode.TerminalDimensions | undefined): void {
95+
this.process
96+
.run({
97+
onStdout: (text) => this.mapStdio(text),
98+
onStderr: (text) => this.mapStdio(text),
99+
})
100+
.then((result) => this.onDidExitEmitter.fire(result))
101+
.catch((err) =>
102+
this.onDidExitEmitter.fire({ error: UnknownError.cast(err), exitCode: -1, stderr: '', stdout: '' })
103+
)
104+
.finally(() => this.onDidWriteEmitter.fire('\r\nPress any key to close this terminal'))
105+
}
106+
107+
public close(): void {
108+
this.process.stop()
109+
this.onDidCloseEmitter.fire()
110+
}
111+
112+
public handleInput(data: string) {
113+
// ETX
114+
if (data === '\u0003' || this.process.stopped) {
115+
this.#cancelled ||= data === '\u0003'
116+
return this.close()
117+
}
118+
119+
// enter
120+
if (data === '\u000D') {
121+
this.process.send('\n').then(undefined, (e) => {
122+
getLogger().error('ProcessTerminal: process.send() failed: %s', (e as Error).message)
123+
})
124+
this.onDidWriteEmitter.fire('\r\n')
125+
} else {
126+
this.process.send(data).then(undefined, (e) => {
127+
getLogger().error('ProcessTerminal: process.send() failed: %s', (e as Error).message)
128+
})
129+
this.onDidWriteEmitter.fire(data)
130+
}
131+
}
132+
133+
private mapStdio(text: string): void {
134+
const lines = text.split('\n')
135+
const first = lines.shift()
136+
137+
if (first) {
138+
this.onDidWriteEmitter.fire(first)
139+
}
140+
141+
for (const line of lines) {
142+
this.onDidWriteEmitter.fire('\r\n')
143+
this.onDidWriteEmitter.fire(line)
144+
}
145+
}
146+
}

0 commit comments

Comments
 (0)