diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 6e53ed8ddab..2bc5e0661f8 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -274,7 +274,7 @@ jobs: needs: - build if: github.event_name == 'push' && needs.build.outputs.e2e-changed == 'true' - uses: ./.github/workflows/tf-aws-test.yml + uses: ./.github/workflows/sdk-spec-test.yml secrets: inherit console-preview: diff --git a/.github/workflows/tf-aws-test.yml b/.github/workflows/sdk-spec-test.yml similarity index 98% rename from .github/workflows/tf-aws-test.yml rename to .github/workflows/sdk-spec-test.yml index 0c9a7d4d1dd..032d9ad18d7 100644 --- a/.github/workflows/tf-aws-test.yml +++ b/.github/workflows/sdk-spec-test.yml @@ -187,7 +187,7 @@ jobs: echo $COMPATIBILITY fi cd ${{ matrix.test.directory }} - $WING_CLI test --snapshots=deploy -t ${{ matrix.target }} $COMPATIBILITY *.test.w -o ../../../../out/${{ matrix.test.name }}-${{ matrix.target }}.json + $WING_CLI test --snapshots=deploy -t ${{ matrix.target }} -p 10 $COMPATIBILITY *.test.w -o ../../../../out/${{ matrix.test.name }}-${{ matrix.target }}.json - name: Upload Artifacts if: ${{ env.LOCAL_BUILD == 'true' }} diff --git a/apps/wing/package.json b/apps/wing/package.json index 8c0687af070..894cbc69809 100644 --- a/apps/wing/package.json +++ b/apps/wing/package.json @@ -34,6 +34,7 @@ "dependencies": { "@npmcli/arborist": "^7.2.0", "@segment/analytics-node": "^1.1.0", + "@supercharge/promise-pool": "^3.2.0", "@wingconsole/app": "workspace:^", "@wingconsole/server": "workspace:^", "@winglang/compiler": "workspace:^", diff --git a/apps/wing/src/cli.ts b/apps/wing/src/cli.ts index 684859ad75c..8299300d180 100644 --- a/apps/wing/src/cli.ts +++ b/apps/wing/src/cli.ts @@ -4,7 +4,7 @@ import { satisfies } from "compare-versions"; import { optionallyDisplayDisclaimer } from "./analytics/disclaimer"; import { exportAnalytics } from "./analytics/export"; import { SNAPSHOTS_HELP } from "./commands/test/snapshots-help"; -import { currentPackage, projectTemplateNames } from "./util"; +import { currentPackage, projectTemplateNames, DEFAULT_PARALLEL_SIZE } from "./util"; export const PACKAGE_VERSION = currentPackage.version; if (PACKAGE_VERSION == "0.0.0" && !process.env.DEBUG) { @@ -235,6 +235,14 @@ async function main() { .preset(3) .argParser(parseInt) ) + .addOption( + new Option( + "-p, --parallel [batch]", + `Number of tests to be executed on parallel- if not specified- ${DEFAULT_PARALLEL_SIZE} will run on parallel, 0 to run all at once` + ) + .preset(DEFAULT_PARALLEL_SIZE) + .argParser(parseInt) + ) .hook("preAction", progressHook) .hook("preAction", collectAnalyticsHook) .action(runSubCommand("test", "test/test")); diff --git a/apps/wing/src/commands/test/test.test.ts b/apps/wing/src/commands/test/test.test.ts index 7d8d483a7a9..283955f2cd3 100644 --- a/apps/wing/src/commands/test/test.test.ts +++ b/apps/wing/src/commands/test/test.test.ts @@ -295,7 +295,7 @@ describe("test-filter option", () => { }); }); -describe("retry option", () => { +describe("retry and parallel options", () => { let logSpy: SpyInstance; beforeEach(() => { @@ -358,6 +358,80 @@ describe("retry option", () => { const retryLogs = logSpy.mock.calls.filter((args) => args[0].includes("Retrying")); expect(retryLogs.length).toBe(3); }); + + test("wing test --parallel [batch]", async () => { + const outDir = await fsPromises.mkdtemp(join(tmpdir(), "-wing-batch-test")); + + process.chdir(outDir); + + fs.writeFileSync( + "t1.test.w", + ` +bring util; + +test "t1" { + util.sleep(2s); + assert(true); +} + ` + ); + fs.writeFileSync( + "t2.test.w", + ` +bring util; + +test "t2" { + util.sleep(1s); + assert(true); +} + ` + ); + + const startingTime = Date.now(); + await wingTest(["t1.test.w", "t2.test.w"], { + clean: true, + platform: [BuiltinPlatform.SIM], + parallel: 1, + }); + expect(Date.now() - startingTime).toBeGreaterThanOrEqual(3 * 1000); + }); + + test("wing test --parallel 2", async () => { + const outDir = await fsPromises.mkdtemp(join(tmpdir(), "-wing-batch-test")); + + process.chdir(outDir); + + fs.writeFileSync( + "t1.test.w", + ` +bring util; + +test "t1" { +util.sleep(2s); +assert(true); +} + ` + ); + fs.writeFileSync( + "t2.test.w", + ` +bring util; + +test "t2" { +util.sleep(2s); +assert(true); +} + ` + ); + + const startingTime = Date.now(); + await wingTest(["t1.test.w", "t2.test.w"], { + clean: true, + platform: [BuiltinPlatform.SIM], + parallel: 2, + }); + expect(Date.now() - startingTime).toBeLessThan(4 * 1000); + }); }); const EXAMPLE_TEST_RESULTS: Array = [ diff --git a/apps/wing/src/commands/test/test.ts b/apps/wing/src/commands/test/test.ts index 22ee4f8209f..c662cee76a5 100644 --- a/apps/wing/src/commands/test/test.ts +++ b/apps/wing/src/commands/test/test.ts @@ -2,6 +2,7 @@ import * as cp from "child_process"; import { existsSync, readFile, readFileSync, realpathSync, rm, rmSync, statSync } from "fs"; import { basename, join, relative, resolve } from "path"; import { promisify } from "util"; +import { PromisePool } from "@supercharge/promise-pool"; import { BuiltinPlatform, determineTargetFromPlatforms } from "@winglang/compiler"; import { std, simulator } from "@winglang/sdk"; import { LogLevel } from "@winglang/sdk/lib/std"; @@ -53,6 +54,11 @@ export interface TestOptions extends CompileOptions { * Determine snapshot behavior. */ readonly snapshots?: SnapshotMode; + + /** + * Number of tests to be run in parallel. 0 or undefined will run all at once. + */ + readonly parallel?: number; } const TEST_FILE_PATTERNS = ["**/*.test.w", "**/{main,*.main}.{w,ts}"]; @@ -134,7 +140,11 @@ export async function test(entrypoints: string[], options: TestOptions): Promise }); } }; - await Promise.all(selectedEntrypoints.map(testFile)); + + await PromisePool.withConcurrency(options.parallel || selectedEntrypoints.length) + .for(selectedEntrypoints) + .process(testFile); + const testDuration = Date.now() - startTime; printResults(results, testDuration); if (options.outputFile) { diff --git a/apps/wing/src/util.ts b/apps/wing/src/util.ts index 227008c630e..d94ac820f9a 100644 --- a/apps/wing/src/util.ts +++ b/apps/wing/src/util.ts @@ -5,6 +5,8 @@ import { tmpdir } from "os"; import { join } from "path"; import { promisify } from "util"; +export const DEFAULT_PARALLEL_SIZE = 10; + /** * Normalize windows paths to be posix-like. */ diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 04ac1b72078..54502e2e3e7 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -248,6 +248,9 @@ importers: '@segment/analytics-node': specifier: ^1.1.0 version: 1.1.1 + '@supercharge/promise-pool': + specifier: ^3.2.0 + version: 3.2.0 '@wingconsole/app': specifier: workspace:^ version: link:../wing-console/console/app @@ -9991,6 +9994,11 @@ packages: file-system-cache: 2.3.0 dev: true + /@supercharge/promise-pool@3.2.0: + resolution: {integrity: sha512-pj0cAALblTZBPtMltWOlZTQSLT07jIaFNeM8TWoJD1cQMgDB9mcMlVMoetiB35OzNJpqQ2b+QEtwiR9f20mADg==} + engines: {node: '>=8'} + dev: false + /@swc/core-darwin-arm64@1.4.2: resolution: {integrity: sha512-1uSdAn1MRK5C1m/TvLZ2RDvr0zLvochgrZ2xL+lRzugLlCTlSA+Q4TWtrZaOz+vnnFVliCpw7c7qu0JouhgQIw==} engines: {node: '>=10'} @@ -14192,7 +14200,7 @@ packages: dependencies: semver: 7.5.4 shelljs: 0.8.5 - typescript: 5.5.0-dev.20240507 + typescript: 5.5.0-dev.20240508 dev: true /dset@3.1.2: @@ -22856,8 +22864,8 @@ packages: engines: {node: '>=14.17'} hasBin: true - /typescript@5.5.0-dev.20240507: - resolution: {integrity: sha512-5LtqwNqq8gx+AD5pTThWksRzORSFBNdUGmndlPmQ/fa34MY/1vsE5A/sFM6JD29X8uFiATeIJQepsO+l+tRJ0A==} + /typescript@5.5.0-dev.20240508: + resolution: {integrity: sha512-5hkQ+5UaeQFyG4Z4UiupCIQo2pcUZ+e8OZlIxeiljeqei989RyA99QnF+hS0pxZpxvOt/yJCTN9zIdT2m/O1WA==} engines: {node: '>=14.17'} hasBin: true dev: true