-
Notifications
You must be signed in to change notification settings - Fork 201
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
chore: test harness interface (#7248)
Extract common logic across Terraform and AWS CDK targets when running tests. This is an initial step towards allowing custom platforms to provide test running capabilities. ## Checklist - [x] Title matches [Winglang's style guide](https://www.winglang.io/contributing/start-here/pull_requests#how-are-pull-request-titles-formatted) - [x] Description explains motivation and solution - [x] Tests added (always) - [x] Docs updated (only required for features) - [x] Added `pr/e2e-full` label if this feature requires end-to-end testing *By submitting this pull request, I confirm that my contribution is made under the terms of the [Wing Cloud Contribution License](https://github.com/winglang/wing/blob/main/CONTRIBUTION_LICENSE.md)*.
- Loading branch information
Showing
5 changed files
with
189 additions
and
195 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
import { ITestRunnerClient } from "@winglang/sdk/lib/std"; | ||
|
||
/** | ||
* API for running wing tests. | ||
*/ | ||
export interface ITestHarness { | ||
/** | ||
* Deploys the test program synthesized in the given directory and return an `ITestRunnerClient` | ||
* that can be used to run the tests. | ||
* @param synthDir - The directory containing the synthesized test program. | ||
*/ | ||
deploy(synthDir: string): Promise<ITestRunnerClient>; | ||
|
||
/** | ||
* Cleans up the test harness after the tests have been run. | ||
* @param synthDir - The directory containing the synthesized test program. | ||
*/ | ||
cleanup(synthDir: string): Promise<void>; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
import { readFile, rm } from "fs/promises"; | ||
import { ITestRunnerClient } from "@winglang/sdk/lib/std"; | ||
import { Util } from "@winglang/sdk/lib/util"; | ||
import { ITestHarness } from "./api"; | ||
import { withSpinner } from "../../../util"; | ||
import { execCapture } from "../util"; | ||
|
||
const ENV_WING_TEST_RUNNER_FUNCTION_IDENTIFIERS_AWSCDK = "WingTestRunnerFunctionArns"; | ||
|
||
export class AwsCdkTestHarness implements ITestHarness { | ||
public async deploy(synthDir: string): Promise<ITestRunnerClient> { | ||
try { | ||
await execCapture("cdk version --ci true", { cwd: synthDir }); | ||
} catch (err) { | ||
throw new Error( | ||
"AWS-CDK is not installed. Please install AWS-CDK to run tests in the cloud (npm i -g aws-cdk)." | ||
); | ||
} | ||
|
||
await withSpinner("cdk deploy", () => | ||
execCapture("cdk deploy --require-approval never --ci true -O ./output.json --app . ", { | ||
cwd: synthDir, | ||
}) | ||
); | ||
|
||
const stackName = process.env.CDK_STACK_NAME! + Util.sha256(synthDir).slice(-8); | ||
const testArns = await this.getFunctionArnsOutput(synthDir, stackName); | ||
|
||
const { TestRunnerClient } = await import("@winglang/sdk/lib/shared-aws/test-runner.inflight"); | ||
const runner = new TestRunnerClient({ $tests: testArns }); | ||
return runner; | ||
} | ||
|
||
public async cleanup(synthDir: string): Promise<void> { | ||
await withSpinner("aws-cdk destroy", async () => { | ||
await rm(synthDir.concat("/output.json")); | ||
await execCapture("cdk destroy -f --ci true --app ./", { cwd: synthDir }); | ||
}); | ||
|
||
await rm(synthDir, { recursive: true, force: true }); | ||
} | ||
|
||
private async getFunctionArnsOutput(synthDir: string, stackName: string) { | ||
const file = await readFile(synthDir.concat("/output.json")); | ||
const parsed = JSON.parse(Buffer.from(file).toString()); | ||
return parsed[stackName][ENV_WING_TEST_RUNNER_FUNCTION_IDENTIFIERS_AWSCDK]; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
import { rm } from "fs/promises"; | ||
import { BuiltinPlatform, determineTargetFromPlatforms } from "@winglang/compiler"; | ||
import { ITestRunnerClient } from "@winglang/sdk/lib/std"; | ||
import { ITestHarness } from "./api"; | ||
import { withSpinner } from "../../../util"; | ||
import { TestOptions } from "../test"; | ||
import { execCapture } from "../util"; | ||
|
||
const ENV_WING_TEST_RUNNER_FUNCTION_IDENTIFIERS = "WING_TEST_RUNNER_FUNCTION_IDENTIFIERS"; | ||
const PARALLELISM = { [BuiltinPlatform.TF_AZURE]: 5 }; | ||
const targetFolder: Record<string, string> = { | ||
[BuiltinPlatform.TF_AWS]: "shared-aws", | ||
[BuiltinPlatform.TF_AZURE]: "shared-azure", | ||
[BuiltinPlatform.TF_GCP]: "shared-gcp", | ||
}; | ||
|
||
export class TerraformTestHarness implements ITestHarness { | ||
private readonly options: TestOptions; | ||
private readonly parallelism: string; | ||
|
||
constructor(options: TestOptions) { | ||
this.options = options; | ||
const p = PARALLELISM[options.platform[0]]; | ||
this.parallelism = p ? `-parallelism=${p}` : ""; | ||
} | ||
|
||
public async deploy(synthDir: string): Promise<ITestRunnerClient> { | ||
// Check if Terraform is installed | ||
const tfVersion = await execCapture("terraform version", { cwd: synthDir }); | ||
const installed = tfVersion.startsWith("Terraform v"); | ||
if (!installed) { | ||
throw new Error( | ||
"Terraform is not installed. Please install Terraform to run tests in the cloud." | ||
); | ||
} | ||
|
||
// Initialize Terraform | ||
await withSpinner("terraform init", () => execCapture("terraform init", { cwd: synthDir })); | ||
|
||
// Apply Terraform | ||
await withSpinner("terraform apply", () => | ||
execCapture(`terraform apply -auto-approve ${this.parallelism}`, { cwd: synthDir }) | ||
); | ||
|
||
// Get the test runner function ARNs | ||
const output = await execCapture("terraform output -json", { cwd: synthDir }); | ||
const parsed = JSON.parse(output); | ||
const testArns = parsed[ENV_WING_TEST_RUNNER_FUNCTION_IDENTIFIERS]?.value; | ||
if (!testArns) { | ||
throw new Error(`terraform output ${ENV_WING_TEST_RUNNER_FUNCTION_IDENTIFIERS} not found`); | ||
} | ||
|
||
// Create the test runner client | ||
const target = determineTargetFromPlatforms(this.options.platform); | ||
const testRunnerPath = `@winglang/sdk/lib/${targetFolder[target]}/test-runner.inflight`; | ||
const { TestRunnerClient } = await import(testRunnerPath); | ||
const runner = new TestRunnerClient({ $tests: testArns }); | ||
return runner; | ||
} | ||
|
||
public async cleanup(synthDir: string): Promise<void> { | ||
try { | ||
await withSpinner("terraform destroy", () => | ||
execCapture(`terraform destroy -auto-approve ${this.parallelism}`, { cwd: synthDir }) | ||
); | ||
|
||
await rm(synthDir, { recursive: true, force: true }); | ||
} catch (e) { | ||
console.error(e); | ||
} | ||
} | ||
} |
Oops, something went wrong.