Skip to content

Commit

Permalink
test(lambda): fix sync integration test #5948
Browse files Browse the repository at this point in the history
## 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.
  • Loading branch information
vicheey authored Nov 11, 2024
1 parent 09510a3 commit 3ca78ba
Show file tree
Hide file tree
Showing 12 changed files with 1,421 additions and 614 deletions.
5 changes: 3 additions & 2 deletions packages/core/src/shared/sam/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
*/

import * as vscode from 'vscode'
import { TemplateItem, createTemplatePrompter, getSamCliPathAndVersion, runInTerminal } from './sync'
import { TemplateItem, createTemplatePrompter } from './sync'
import { ChildProcess } from '../utilities/processUtils'
import { addTelemetryEnvVar } from './cli/samCliInvokerUtils'
import { Wizard } from '../wizards/wizard'
Expand All @@ -19,8 +19,9 @@ import globals from '../extensionGlobals'
import { TreeNode } from '../treeview/resourceTreeDataProvider'
import { telemetry } from '../telemetry/telemetry'
import { getSpawnEnv } from '../env/resolveEnv'
import { getProjectRoot, isDotnetRuntime } from './utils'
import { getProjectRoot, getSamCliPathAndVersion, isDotnetRuntime } from './utils'
import { getConfigFileUri, validateSamBuildConfig } from './config'
import { runInTerminal } from './processTerminal'

export interface BuildParams {
readonly template: TemplateItem
Expand Down
12 changes: 3 additions & 9 deletions packages/core/src/shared/sam/deploy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,9 @@ import { CancellationError } from '../utilities/timeoutUtils'
import { Wizard } from '../wizards/wizard'
import { addTelemetryEnvVar } from './cli/samCliInvokerUtils'
import { validateSamDeployConfig, SamConfig, writeSamconfigGlobal } from './config'
import {
TemplateItem,
createStackPrompter,
createBucketPrompter,
createTemplatePrompter,
getSamCliPathAndVersion,
runInTerminal,
} from './sync'
import { getProjectRoot, getSource } from './utils'
import { TemplateItem, createStackPrompter, createBucketPrompter, createTemplatePrompter } from './sync'
import { getProjectRoot, getSamCliPathAndVersion, getSource } from './utils'
import { runInTerminal } from './processTerminal'

export interface DeployParams {
readonly paramsSource: ParamsSource
Expand Down
146 changes: 146 additions & 0 deletions packages/core/src/shared/sam/processTerminal.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
/*!
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
import * as vscode from 'vscode'

import { ToolkitError, UnknownError } from '../errors'
import globals from '../extensionGlobals'
import { isCloud9 } from '../extensionUtilities'
import { ChildProcess, ChildProcessResult } from '../utilities/processUtils'
import { CancellationError } from '../utilities/timeoutUtils'
import { getLogger } from '../logger'
import { removeAnsi } from '../utilities/textUtilities'
import { isAutomation } from '../vscode/env'

let oldTerminal: ProcessTerminal | undefined
export async function runInTerminal(proc: ChildProcess, cmd: string) {
const handleResult = (result?: ChildProcessResult) => {
if (result && result.exitCode !== 0) {
const message = `sam ${cmd} exited with a non-zero exit code: ${result.exitCode}`
if (result.stderr.includes('is up to date')) {
throw ToolkitError.chain(result.error, message, {
code: 'NoUpdateExitCode',
})
}
throw ToolkitError.chain(result.error, message, {
code: 'NonZeroExitCode',
})
}
}

// `createTerminal` doesn't work on C9 so we use the output channel instead
if (isCloud9()) {
globals.outputChannel.show()

const result = proc.run({
onStdout: (text) => globals.outputChannel.append(removeAnsi(text)),
onStderr: (text) => globals.outputChannel.append(removeAnsi(text)),
})
await proc.send('\n')

return handleResult(await result)
}

// The most recent terminal won't get garbage collected until the next run
if (oldTerminal?.stopped === true) {
oldTerminal.close()
}
const pty = (oldTerminal = new ProcessTerminal(proc))
const terminal = vscode.window.createTerminal({ pty, name: `SAM ${cmd}` })
terminal.sendText('\n')
terminal.show()

const result = await new Promise<ChildProcessResult>((resolve) => pty.onDidExit(resolve))
if (pty.cancelled) {
throw result.error !== undefined
? ToolkitError.chain(result.error, 'SAM CLI was cancelled before exiting', { cancelled: true })
: new CancellationError('user')
} else {
return handleResult(result)
}
}

// This is a decent improvement over using the output channel but it isn't a tty/pty
// SAM CLI uses `click` which has reduced functionality if `os.isatty` returns false
// Historically, Windows lack of a pty-equivalent is why it's not available in libuv
// Maybe it's doable now with the ConPTY API? https://github.com/libuv/libuv/issues/2640
class ProcessTerminal implements vscode.Pseudoterminal {
private readonly onDidCloseEmitter = new vscode.EventEmitter<number | void>()
private readonly onDidWriteEmitter = new vscode.EventEmitter<string>()
private readonly onDidExitEmitter = new vscode.EventEmitter<ChildProcessResult>()
public readonly onDidWrite = this.onDidWriteEmitter.event
public readonly onDidClose = this.onDidCloseEmitter.event
public readonly onDidExit = this.onDidExitEmitter.event

public constructor(private readonly process: ChildProcess) {
// Used in integration tests
if (isAutomation()) {
// Disable because it is a test.
// eslint-disable-next-line aws-toolkits/no-console-log
this.onDidWrite((text) => console.log(text.trim()))
}
}

#cancelled = false
public get cancelled() {
return this.#cancelled
}

public get stopped() {
return this.process.stopped
}

public open(initialDimensions: vscode.TerminalDimensions | undefined): void {
this.process
.run({
onStdout: (text) => this.mapStdio(text),
onStderr: (text) => this.mapStdio(text),
})
.then((result) => this.onDidExitEmitter.fire(result))
.catch((err) =>
this.onDidExitEmitter.fire({ error: UnknownError.cast(err), exitCode: -1, stderr: '', stdout: '' })
)
.finally(() => this.onDidWriteEmitter.fire('\r\nPress any key to close this terminal'))
}

public close(): void {
this.process.stop()
this.onDidCloseEmitter.fire()
}

public handleInput(data: string) {
// ETX
if (data === '\u0003' || this.process.stopped) {
this.#cancelled ||= data === '\u0003'
return this.close()
}

// enter
if (data === '\u000D') {
this.process.send('\n').then(undefined, (e) => {
getLogger().error('ProcessTerminal: process.send() failed: %s', (e as Error).message)
})
this.onDidWriteEmitter.fire('\r\n')
} else {
this.process.send(data).then(undefined, (e) => {
getLogger().error('ProcessTerminal: process.send() failed: %s', (e as Error).message)
})
this.onDidWriteEmitter.fire(data)
}
}

private mapStdio(text: string): void {
const lines = text.split('\n')
const first = lines.shift()

if (first) {
this.onDidWriteEmitter.fire(first)
}

for (const line of lines) {
this.onDidWriteEmitter.fire('\r\n')
this.onDidWriteEmitter.fire(line)
}
}
}
Loading

0 comments on commit 3ca78ba

Please sign in to comment.