From b527cf0e265ecea171cb3486b66ee1edfcc767f8 Mon Sep 17 00:00:00 2001 From: Rico Huijbers Date: Fri, 6 Jan 2023 13:29:08 +0100 Subject: [PATCH 1/3] chore(cli): move integ tests to separate package Clean up the CLI integration tests a bit, by moving them to a separate package. This has the following benefits: - More clearly isolates the tests and keeps them separate from the CLI package proper. - Allows normal specification of dependencies of the integration tests themselves, via a proper `package.json` and Yarn lockfile. - Makes it more clear what reusable files are part of the CLI, vs what reusable files are part of the integration tests. - Makes it more clear/easier to download an old copy of the tests. Also makes the following improvement: - Publishes the test artifacts to CodeArtifact, so that we don't need to do crazy shell hijacking shenanigans in order to fake NPM/Maven/NuGet/PyPI repositories from a set of artifacts. This is not the final form of our testing infrastructure. Right now, a fresh CodeArtifact repo is created for every test with all packages in it. In an actual pipeline, we would be better off publishing to a CodeArtifact repo once and all tests pulling from that. That will be forthcoming in the future. --- lerna.json | 1 + package.json | 1 + .../@aws-cdk-testing/cli-integ/.eslintrc.js | 4 + .../@aws-cdk-testing/cli-integ/.gitignore | 20 + .../@aws-cdk-testing/cli-integ/.npmignore | 32 + packages/@aws-cdk-testing/cli-integ/LICENSE | 201 +++++++ packages/@aws-cdk-testing/cli-integ/NOTICE | 16 + packages/@aws-cdk-testing/cli-integ/README.md | 151 +++++ .../cli-integ/bin/apply-patches | 19 + .../cli-integ/bin/download-and-run-old-tests | 52 ++ .../cli-integ/bin/query-github | 2 + .../cli-integ/bin/query-github.ts | 58 ++ .../@aws-cdk-testing/cli-integ/bin/run-suite | 2 + .../cli-integ/bin/run-suite.ts | 136 +++++ .../cli-integ/bin/stage-distribution | 2 + .../cli-integ/bin/stage-distribution.ts | 259 ++++++++ .../@aws-cdk-testing/cli-integ/bin/test-root | 2 + .../cli-integ/bin/test-root.ts | 3 + ...est-cli-regression-against-current-code.sh | 11 + ...t-cli-regression-against-latest-release.sh | 11 + .../entrypoints}/test-cli-regression.bash | 0 .../cli-integ/entrypoints/test.sh | 12 + .../@aws-cdk-testing/cli-integ/jest.config.js | 10 + .../cli-integ/lib}/aws.ts | 11 +- .../cli-integ/lib}/corking.ts | 4 + .../@aws-cdk-testing/cli-integ/lib/files.ts | 81 +++ .../@aws-cdk-testing/cli-integ/lib/github.ts | 43 ++ .../@aws-cdk-testing/cli-integ/lib/index.ts | 12 + .../cli-integ/lib/integ-test.ts} | 38 +- .../@aws-cdk-testing/cli-integ/lib/lists.ts | 9 + .../cli-integ/lib}/memoize.ts | 0 .../@aws-cdk-testing/cli-integ/lib/npm.ts | 13 + .../lib/package-sources/release-source.ts | 77 +++ .../lib/package-sources/repo-source.ts | 106 ++++ .../lib/package-sources/repo-tools/npm | 2 + .../lib/package-sources/repo-tools/npm.ts | 48 ++ .../cli-integ/lib/package-sources/source.ts | 30 + .../lib/package-sources/subprocess.ts | 15 + .../cli-integ/lib/resource-pool.ts | 144 +++++ .../cli-integ/lib/resources.ts | 4 + .../@aws-cdk-testing/cli-integ/lib/shell.ts | 168 ++++++ .../cli-integ/lib/staging/codeartifact.ts | 282 +++++++++ .../cli-integ/lib/staging/maven.ts | 87 +++ .../cli-integ/lib/staging/npm.ts | 62 ++ .../cli-integ/lib/staging/nuget.ts | 75 +++ .../cli-integ/lib/staging/parallel-shell.ts | 52 ++ .../cli-integ/lib/staging/pypi.ts | 50 ++ .../cli-integ/lib/staging/usage-dir.ts | 99 +++ .../cli-integ/lib/with-aws.ts | 63 ++ .../cli-integ/lib/with-cdk-app.ts} | 297 +-------- .../cli-integ/lib/with-packages.ts | 15 + .../cli-integ/lib/with-sam.ts} | 44 +- .../cli-integ/lib/with-temporary-directory.ts | 35 ++ .../@aws-cdk-testing/cli-integ/lib/xpmutex.ts | 218 +++++++ .../@aws-cdk-testing/cli-integ/package.json | 73 +++ .../cli-integ/resources/cdk-apps}/app/app.js | 0 .../resources/cdk-apps}/app/cdk.json | 0 .../resources/cdk-apps}/app/docker/Dockerfile | 0 .../cdk-apps}/app/docker/Dockerfile.Custom | 0 .../resources/cdk-apps}/app/lambda/index.js | 0 .../cdk-apps}/app/lambda/response.json | 0 .../resources/cdk-apps}/app/nested-stack.js | 0 .../cdk-apps}/cfn-include-app/.gitignore | 0 .../cdk-apps}/cfn-include-app/cdk.json | 0 .../cfn-include-app/cfn-include-app.js | 0 .../cfn-include-app/example-template.json | 0 .../sam_cdk_integ_app/bin/test-app.js | 0 .../cdk-apps}/sam_cdk_integ_app/cdk.json | 0 .../sam_cdk_integ_app/lib/nested-stack.js | 0 .../sam_cdk_integ_app/lib/test-stack.js | 0 .../.no-packagejson-validator | 0 .../DockerImageFunctionConstruct/Dockerfile | 0 .../DockerImageFunctionConstruct/app.js | 0 .../DockerImageFunctionConstruct/package.json | 0 .../src/go/GoFunctionConstruct/go.mod | 0 .../src/go/GoFunctionConstruct/go.sum | 0 .../src/go/GoFunctionConstruct/main.go | 0 .../.no-packagejson-validator | 0 .../src/nodejs/NodeJsFunctionConstruct/app.ts | 0 .../NodeJsFunctionConstruct/package-lock.json | 0 .../NodeJsFunctionConstruct/package.json | 0 .../src/python/Function/app.py | 0 .../src/python/Function/requirements.txt | 0 .../python/Layer/layer_version_dependency.py | 0 .../src/python/Layer/requirements.txt | 0 .../src/rest-api-definition.yaml | 0 .../cli-regression-patches/v1.119.0/NOTES.md | 0 .../v1.119.0/cli.integtest.js | 0 .../cli-regression-patches/v1.130.0/NOTES.md | 0 .../v1.130.0/app/app.js | 0 .../v1.130.0/bootstrapping.integtest.js | 0 .../cli-regression-patches/v1.44.0/NOTES.md | 0 .../v1.44.0/bootstrapping.integtest.js | 0 .../cli-regression-patches/v1.44.0/test.sh | 0 .../cli-regression-patches/v1.61.1/NOTES.md | 0 .../v1.61.1/skip-tests.txt | 0 .../cli-regression-patches/v1.62.0/NOTES.md | 0 .../v1.62.0/aws-helpers.js | 0 .../cli-regression-patches/v1.63.0/NOTES.md | 0 .../v1.63.0/skip-tests.txt | 0 .../cli-regression-patches/v1.64.0/NOTES.md | 0 .../v1.64.0/cdk-helpers.js | 0 .../v1.64.0/cli.integtest.js | 0 .../cli-regression-patches/v1.64.1/NOTES.md | 0 .../v1.64.1/cdk-helpers.js | 0 .../v1.64.1/cli.integtest.js | 0 .../cli-regression-patches/v1.67.0/NOTES.md | 0 .../v1.67.0/cdk-helpers.js | 0 .../0.36.0/InitStack.template.json | 0 .../cloud-assemblies/0.36.0/cdk.out | 0 .../cloud-assemblies/0.36.0/manifest.json | 0 .../InitStack.template.json | 0 .../1.10.0-lookup-default-vpc/cdk.out | 0 .../manifest.json.js | 0 .../InitStack.template.json | 0 .../1.10.0-request-azs/cdk.out | 0 .../1.10.0-request-azs/manifest.json.js | 0 .../cli-integ/resources/integ.jest.config.js | 25 + .../cli-integ}/skip-tests.txt | 0 .../cli-integ/test/resource-pool.test.ts | 84 +++ .../tests/cli-integ-tests}/README.md | 1 - .../bootstrapping.integtest.ts | 5 +- .../tests/cli-integ-tests}/cli.integtest.ts | 137 +++-- .../init-csharp/init-csharp.integtest.ts | 16 + .../init-fsharp/init-fsharp.integtest.ts | 15 + .../tests/init-java/init-java.integtest.ts | 14 + .../init-javascript.integtest.ts | 15 + .../init-python/init-python.integtest.ts | 20 + .../init-typescript-app.integtest.ts | 58 ++ .../init-typescript-lib.integtest.ts | 13 + .../uberpackage/uberpackage.integtest.ts | 3 +- .../@aws-cdk-testing/cli-integ/tsconfig.json | 26 + packages/aws-cdk/.npmignore | 8 +- packages/aws-cdk/lib/api/cxapp/exec.ts | 19 +- packages/aws-cdk/lib/api/util/rwlock.ts | 184 ++++++ packages/aws-cdk/lib/cli.ts | 18 +- packages/aws-cdk/test/api/exec.test.ts | 28 +- .../test/api/logs/logs-monitor.test.ts | 2 +- .../integ/cli-regression-patches/README.md | 54 -- .../aws-cdk/test/integ/cli/jest.config.js | 15 - packages/aws-cdk/test/integ/cli/test.sh | 14 +- .../aws-cdk/test/integ/common/jest-test.bash | 18 - packages/aws-cdk/test/integ/github-helpers.ts | 38 -- .../test/integ/helpers/resource-pool.test.ts | 47 -- .../test/integ/helpers/resource-pool.ts | 95 --- packages/aws-cdk/test/integ/init/common.bash | 25 - packages/aws-cdk/test/integ/init/test-all.sh | 12 - .../test/integ/init/test-csharp-app.sh | 8 +- .../aws-cdk/test/integ/init/test-csharp.sh | 26 - .../aws-cdk/test/integ/init/test-fsharp.sh | 28 +- .../test/integ/init/test-generate-only.sh | 22 - packages/aws-cdk/test/integ/init/test-go.sh | 30 - packages/aws-cdk/test/integ/init/test-java.sh | 29 +- .../test/integ/init/test-javascript.sh | 29 +- .../test/integ/init/test-python-app.sh | 8 +- .../test/integ/init/test-python-stackname.sh | 7 - .../aws-cdk/test/integ/init/test-python.sh | 33 - .../test/integ/init/test-typescript-app.sh | 9 +- .../test/integ/init/test-typescript-lib.sh | 9 +- .../integ/init/test-typescript-versions.sh | 33 +- .../test/integ/init/test-typescript.sh | 33 - packages/aws-cdk/test/integ/run-against-dist | 55 +- .../aws-cdk/test/integ/run-against-dist.bash | 147 ----- .../aws-cdk/test/integ/run-against-release | 43 +- packages/aws-cdk/test/integ/run-against-repo | 22 - .../aws-cdk/test/integ/run-wrappers/dist/pip_ | 15 - .../test/integ/run-wrappers/repo/dotnet | 3 - .../aws-cdk/test/integ/run-wrappers/repo/mvn | 3 - .../aws-cdk/test/integ/run-wrappers/repo/pip_ | 4 - ...est-cli-regression-against-current-code.sh | 17 +- ...t-cli-regression-against-latest-release.sh | 16 +- .../aws-cdk/test/integ/typescript-versions.ts | 10 - .../test/integ/uberpackage/jest.config.js | 15 - .../aws-cdk/test/integ/uberpackage/test.sh | 16 +- packages/aws-cdk/test/rwlock.test.ts | 52 ++ packages/aws-cdk/test/util.ts | 4 + .../aws-cdk/test/util/stack-monitor.test.ts | 2 +- .../cdk-release/lib/lifecycles/bump.ts | 42 +- tools/@aws-cdk/cdk-release/test/bump.test.ts | 16 +- tools/@aws-cdk/pkglint/lib/rules.ts | 1 + yarn.lock | 562 +++++++++++++++++- 181 files changed, 4259 insertions(+), 1361 deletions(-) create mode 100644 packages/@aws-cdk-testing/cli-integ/.eslintrc.js create mode 100644 packages/@aws-cdk-testing/cli-integ/.gitignore create mode 100644 packages/@aws-cdk-testing/cli-integ/.npmignore create mode 100644 packages/@aws-cdk-testing/cli-integ/LICENSE create mode 100644 packages/@aws-cdk-testing/cli-integ/NOTICE create mode 100644 packages/@aws-cdk-testing/cli-integ/README.md create mode 100755 packages/@aws-cdk-testing/cli-integ/bin/apply-patches create mode 100755 packages/@aws-cdk-testing/cli-integ/bin/download-and-run-old-tests create mode 100755 packages/@aws-cdk-testing/cli-integ/bin/query-github create mode 100644 packages/@aws-cdk-testing/cli-integ/bin/query-github.ts create mode 100755 packages/@aws-cdk-testing/cli-integ/bin/run-suite create mode 100644 packages/@aws-cdk-testing/cli-integ/bin/run-suite.ts create mode 100755 packages/@aws-cdk-testing/cli-integ/bin/stage-distribution create mode 100644 packages/@aws-cdk-testing/cli-integ/bin/stage-distribution.ts create mode 100755 packages/@aws-cdk-testing/cli-integ/bin/test-root create mode 100644 packages/@aws-cdk-testing/cli-integ/bin/test-root.ts create mode 100755 packages/@aws-cdk-testing/cli-integ/entrypoints/test-cli-regression-against-current-code.sh create mode 100755 packages/@aws-cdk-testing/cli-integ/entrypoints/test-cli-regression-against-latest-release.sh rename packages/{aws-cdk/test/integ => @aws-cdk-testing/cli-integ/entrypoints}/test-cli-regression.bash (100%) create mode 100755 packages/@aws-cdk-testing/cli-integ/entrypoints/test.sh create mode 100644 packages/@aws-cdk-testing/cli-integ/jest.config.js rename packages/{aws-cdk/test/integ/helpers => @aws-cdk-testing/cli-integ/lib}/aws.ts (99%) rename packages/{aws-cdk/test/integ/helpers => @aws-cdk-testing/cli-integ/lib}/corking.ts (90%) create mode 100644 packages/@aws-cdk-testing/cli-integ/lib/files.ts create mode 100644 packages/@aws-cdk-testing/cli-integ/lib/github.ts create mode 100644 packages/@aws-cdk-testing/cli-integ/lib/index.ts rename packages/{aws-cdk/test/integ/helpers/test-helpers.ts => @aws-cdk-testing/cli-integ/lib/integ-test.ts} (59%) create mode 100644 packages/@aws-cdk-testing/cli-integ/lib/lists.ts rename packages/{aws-cdk/test/integ/helpers => @aws-cdk-testing/cli-integ/lib}/memoize.ts (100%) create mode 100644 packages/@aws-cdk-testing/cli-integ/lib/npm.ts create mode 100644 packages/@aws-cdk-testing/cli-integ/lib/package-sources/release-source.ts create mode 100644 packages/@aws-cdk-testing/cli-integ/lib/package-sources/repo-source.ts create mode 100644 packages/@aws-cdk-testing/cli-integ/lib/package-sources/repo-tools/npm create mode 100644 packages/@aws-cdk-testing/cli-integ/lib/package-sources/repo-tools/npm.ts create mode 100644 packages/@aws-cdk-testing/cli-integ/lib/package-sources/source.ts create mode 100644 packages/@aws-cdk-testing/cli-integ/lib/package-sources/subprocess.ts create mode 100644 packages/@aws-cdk-testing/cli-integ/lib/resource-pool.ts create mode 100644 packages/@aws-cdk-testing/cli-integ/lib/resources.ts create mode 100644 packages/@aws-cdk-testing/cli-integ/lib/shell.ts create mode 100644 packages/@aws-cdk-testing/cli-integ/lib/staging/codeartifact.ts create mode 100644 packages/@aws-cdk-testing/cli-integ/lib/staging/maven.ts create mode 100644 packages/@aws-cdk-testing/cli-integ/lib/staging/npm.ts create mode 100644 packages/@aws-cdk-testing/cli-integ/lib/staging/nuget.ts create mode 100644 packages/@aws-cdk-testing/cli-integ/lib/staging/parallel-shell.ts create mode 100644 packages/@aws-cdk-testing/cli-integ/lib/staging/pypi.ts create mode 100644 packages/@aws-cdk-testing/cli-integ/lib/staging/usage-dir.ts create mode 100644 packages/@aws-cdk-testing/cli-integ/lib/with-aws.ts rename packages/{aws-cdk/test/integ/helpers/cdk.ts => @aws-cdk-testing/cli-integ/lib/with-cdk-app.ts} (65%) create mode 100644 packages/@aws-cdk-testing/cli-integ/lib/with-packages.ts rename packages/{aws-cdk/test/integ/helpers/sam.ts => @aws-cdk-testing/cli-integ/lib/with-sam.ts} (88%) create mode 100644 packages/@aws-cdk-testing/cli-integ/lib/with-temporary-directory.ts create mode 100644 packages/@aws-cdk-testing/cli-integ/lib/xpmutex.ts create mode 100644 packages/@aws-cdk-testing/cli-integ/package.json rename packages/{aws-cdk/test/integ/cli => @aws-cdk-testing/cli-integ/resources/cdk-apps}/app/app.js (100%) rename packages/{aws-cdk/test/integ/cli => @aws-cdk-testing/cli-integ/resources/cdk-apps}/app/cdk.json (100%) rename packages/{aws-cdk/test/integ/cli => @aws-cdk-testing/cli-integ/resources/cdk-apps}/app/docker/Dockerfile (100%) rename packages/{aws-cdk/test/integ/cli => @aws-cdk-testing/cli-integ/resources/cdk-apps}/app/docker/Dockerfile.Custom (100%) rename packages/{aws-cdk/test/integ/cli => @aws-cdk-testing/cli-integ/resources/cdk-apps}/app/lambda/index.js (100%) rename packages/{aws-cdk/test/integ/cli => @aws-cdk-testing/cli-integ/resources/cdk-apps}/app/lambda/response.json (100%) rename packages/{aws-cdk/test/integ/cli => @aws-cdk-testing/cli-integ/resources/cdk-apps}/app/nested-stack.js (100%) rename packages/{aws-cdk/test/integ/uberpackage => @aws-cdk-testing/cli-integ/resources/cdk-apps}/cfn-include-app/.gitignore (100%) rename packages/{aws-cdk/test/integ/uberpackage => @aws-cdk-testing/cli-integ/resources/cdk-apps}/cfn-include-app/cdk.json (100%) rename packages/{aws-cdk/test/integ/uberpackage => @aws-cdk-testing/cli-integ/resources/cdk-apps}/cfn-include-app/cfn-include-app.js (100%) rename packages/{aws-cdk/test/integ/uberpackage => @aws-cdk-testing/cli-integ/resources/cdk-apps}/cfn-include-app/example-template.json (100%) rename packages/{aws-cdk/test/integ/cli => @aws-cdk-testing/cli-integ/resources/cdk-apps}/sam_cdk_integ_app/bin/test-app.js (100%) rename packages/{aws-cdk/test/integ/cli => @aws-cdk-testing/cli-integ/resources/cdk-apps}/sam_cdk_integ_app/cdk.json (100%) rename packages/{aws-cdk/test/integ/cli => @aws-cdk-testing/cli-integ/resources/cdk-apps}/sam_cdk_integ_app/lib/nested-stack.js (100%) rename packages/{aws-cdk/test/integ/cli => @aws-cdk-testing/cli-integ/resources/cdk-apps}/sam_cdk_integ_app/lib/test-stack.js (100%) rename packages/{aws-cdk/test/integ/cli => @aws-cdk-testing/cli-integ/resources/cdk-apps}/sam_cdk_integ_app/src/docker/DockerImageFunctionConstruct/.no-packagejson-validator (100%) rename packages/{aws-cdk/test/integ/cli => @aws-cdk-testing/cli-integ/resources/cdk-apps}/sam_cdk_integ_app/src/docker/DockerImageFunctionConstruct/Dockerfile (100%) rename packages/{aws-cdk/test/integ/cli => @aws-cdk-testing/cli-integ/resources/cdk-apps}/sam_cdk_integ_app/src/docker/DockerImageFunctionConstruct/app.js (100%) rename packages/{aws-cdk/test/integ/cli => @aws-cdk-testing/cli-integ/resources/cdk-apps}/sam_cdk_integ_app/src/docker/DockerImageFunctionConstruct/package.json (100%) rename packages/{aws-cdk/test/integ/cli => @aws-cdk-testing/cli-integ/resources/cdk-apps}/sam_cdk_integ_app/src/go/GoFunctionConstruct/go.mod (100%) rename packages/{aws-cdk/test/integ/cli => @aws-cdk-testing/cli-integ/resources/cdk-apps}/sam_cdk_integ_app/src/go/GoFunctionConstruct/go.sum (100%) rename packages/{aws-cdk/test/integ/cli => @aws-cdk-testing/cli-integ/resources/cdk-apps}/sam_cdk_integ_app/src/go/GoFunctionConstruct/main.go (100%) rename packages/{aws-cdk/test/integ/cli => @aws-cdk-testing/cli-integ/resources/cdk-apps}/sam_cdk_integ_app/src/nodejs/NodeJsFunctionConstruct/.no-packagejson-validator (100%) rename packages/{aws-cdk/test/integ/cli => @aws-cdk-testing/cli-integ/resources/cdk-apps}/sam_cdk_integ_app/src/nodejs/NodeJsFunctionConstruct/app.ts (100%) rename packages/{aws-cdk/test/integ/cli => @aws-cdk-testing/cli-integ/resources/cdk-apps}/sam_cdk_integ_app/src/nodejs/NodeJsFunctionConstruct/package-lock.json (100%) rename packages/{aws-cdk/test/integ/cli => @aws-cdk-testing/cli-integ/resources/cdk-apps}/sam_cdk_integ_app/src/nodejs/NodeJsFunctionConstruct/package.json (100%) rename packages/{aws-cdk/test/integ/cli => @aws-cdk-testing/cli-integ/resources/cdk-apps}/sam_cdk_integ_app/src/python/Function/app.py (100%) rename packages/{aws-cdk/test/integ/cli => @aws-cdk-testing/cli-integ/resources/cdk-apps}/sam_cdk_integ_app/src/python/Function/requirements.txt (100%) rename packages/{aws-cdk/test/integ/cli => @aws-cdk-testing/cli-integ/resources/cdk-apps}/sam_cdk_integ_app/src/python/Layer/layer_version_dependency.py (100%) rename packages/{aws-cdk/test/integ/cli => @aws-cdk-testing/cli-integ/resources/cdk-apps}/sam_cdk_integ_app/src/python/Layer/requirements.txt (100%) rename packages/{aws-cdk/test/integ/cli => @aws-cdk-testing/cli-integ/resources/cdk-apps}/sam_cdk_integ_app/src/rest-api-definition.yaml (100%) rename packages/{aws-cdk/test/integ => @aws-cdk-testing/cli-integ/resources}/cli-regression-patches/v1.119.0/NOTES.md (100%) rename packages/{aws-cdk/test/integ => @aws-cdk-testing/cli-integ/resources}/cli-regression-patches/v1.119.0/cli.integtest.js (100%) rename packages/{aws-cdk/test/integ => @aws-cdk-testing/cli-integ/resources}/cli-regression-patches/v1.130.0/NOTES.md (100%) rename packages/{aws-cdk/test/integ => @aws-cdk-testing/cli-integ/resources}/cli-regression-patches/v1.130.0/app/app.js (100%) rename packages/{aws-cdk/test/integ => @aws-cdk-testing/cli-integ/resources}/cli-regression-patches/v1.130.0/bootstrapping.integtest.js (100%) rename packages/{aws-cdk/test/integ => @aws-cdk-testing/cli-integ/resources}/cli-regression-patches/v1.44.0/NOTES.md (100%) rename packages/{aws-cdk/test/integ => @aws-cdk-testing/cli-integ/resources}/cli-regression-patches/v1.44.0/bootstrapping.integtest.js (100%) rename packages/{aws-cdk/test/integ => @aws-cdk-testing/cli-integ/resources}/cli-regression-patches/v1.44.0/test.sh (100%) rename packages/{aws-cdk/test/integ => @aws-cdk-testing/cli-integ/resources}/cli-regression-patches/v1.61.1/NOTES.md (100%) rename packages/{aws-cdk/test/integ => @aws-cdk-testing/cli-integ/resources}/cli-regression-patches/v1.61.1/skip-tests.txt (100%) rename packages/{aws-cdk/test/integ => @aws-cdk-testing/cli-integ/resources}/cli-regression-patches/v1.62.0/NOTES.md (100%) rename packages/{aws-cdk/test/integ => @aws-cdk-testing/cli-integ/resources}/cli-regression-patches/v1.62.0/aws-helpers.js (100%) rename packages/{aws-cdk/test/integ => @aws-cdk-testing/cli-integ/resources}/cli-regression-patches/v1.63.0/NOTES.md (100%) rename packages/{aws-cdk/test/integ => @aws-cdk-testing/cli-integ/resources}/cli-regression-patches/v1.63.0/skip-tests.txt (100%) rename packages/{aws-cdk/test/integ => @aws-cdk-testing/cli-integ/resources}/cli-regression-patches/v1.64.0/NOTES.md (100%) rename packages/{aws-cdk/test/integ => @aws-cdk-testing/cli-integ/resources}/cli-regression-patches/v1.64.0/cdk-helpers.js (100%) rename packages/{aws-cdk/test/integ => @aws-cdk-testing/cli-integ/resources}/cli-regression-patches/v1.64.0/cli.integtest.js (100%) rename packages/{aws-cdk/test/integ => @aws-cdk-testing/cli-integ/resources}/cli-regression-patches/v1.64.1/NOTES.md (100%) rename packages/{aws-cdk/test/integ => @aws-cdk-testing/cli-integ/resources}/cli-regression-patches/v1.64.1/cdk-helpers.js (100%) rename packages/{aws-cdk/test/integ => @aws-cdk-testing/cli-integ/resources}/cli-regression-patches/v1.64.1/cli.integtest.js (100%) rename packages/{aws-cdk/test/integ => @aws-cdk-testing/cli-integ/resources}/cli-regression-patches/v1.67.0/NOTES.md (100%) rename packages/{aws-cdk/test/integ => @aws-cdk-testing/cli-integ/resources}/cli-regression-patches/v1.67.0/cdk-helpers.js (100%) rename packages/{aws-cdk/test/integ/cli => @aws-cdk-testing/cli-integ/resources}/cloud-assemblies/0.36.0/InitStack.template.json (100%) rename packages/{aws-cdk/test/integ/cli => @aws-cdk-testing/cli-integ/resources}/cloud-assemblies/0.36.0/cdk.out (100%) rename packages/{aws-cdk/test/integ/cli => @aws-cdk-testing/cli-integ/resources}/cloud-assemblies/0.36.0/manifest.json (100%) rename packages/{aws-cdk/test/integ/cli => @aws-cdk-testing/cli-integ/resources}/cloud-assemblies/1.10.0-lookup-default-vpc/InitStack.template.json (100%) rename packages/{aws-cdk/test/integ/cli => @aws-cdk-testing/cli-integ/resources}/cloud-assemblies/1.10.0-lookup-default-vpc/cdk.out (100%) rename packages/{aws-cdk/test/integ/cli => @aws-cdk-testing/cli-integ/resources}/cloud-assemblies/1.10.0-lookup-default-vpc/manifest.json.js (100%) rename packages/{aws-cdk/test/integ/cli => @aws-cdk-testing/cli-integ/resources}/cloud-assemblies/1.10.0-request-azs/InitStack.template.json (100%) rename packages/{aws-cdk/test/integ/cli => @aws-cdk-testing/cli-integ/resources}/cloud-assemblies/1.10.0-request-azs/cdk.out (100%) rename packages/{aws-cdk/test/integ/cli => @aws-cdk-testing/cli-integ/resources}/cloud-assemblies/1.10.0-request-azs/manifest.json.js (100%) create mode 100644 packages/@aws-cdk-testing/cli-integ/resources/integ.jest.config.js rename packages/{aws-cdk/test/integ/helpers => @aws-cdk-testing/cli-integ}/skip-tests.txt (100%) create mode 100644 packages/@aws-cdk-testing/cli-integ/test/resource-pool.test.ts rename packages/{aws-cdk/test/integ/cli => @aws-cdk-testing/cli-integ/tests/cli-integ-tests}/README.md (99%) rename packages/{aws-cdk/test/integ/cli => @aws-cdk-testing/cli-integ/tests/cli-integ-tests}/bootstrapping.integtest.ts (98%) rename packages/{aws-cdk/test/integ/cli => @aws-cdk-testing/cli-integ/tests/cli-integ-tests}/cli.integtest.ts (93%) create mode 100644 packages/@aws-cdk-testing/cli-integ/tests/init-csharp/init-csharp.integtest.ts create mode 100644 packages/@aws-cdk-testing/cli-integ/tests/init-fsharp/init-fsharp.integtest.ts create mode 100644 packages/@aws-cdk-testing/cli-integ/tests/init-java/init-java.integtest.ts create mode 100644 packages/@aws-cdk-testing/cli-integ/tests/init-javascript/init-javascript.integtest.ts create mode 100644 packages/@aws-cdk-testing/cli-integ/tests/init-python/init-python.integtest.ts create mode 100644 packages/@aws-cdk-testing/cli-integ/tests/init-typescript-app/init-typescript-app.integtest.ts create mode 100644 packages/@aws-cdk-testing/cli-integ/tests/init-typescript-lib/init-typescript-lib.integtest.ts rename packages/{aws-cdk/test/integ => @aws-cdk-testing/cli-integ/tests}/uberpackage/uberpackage.integtest.ts (69%) create mode 100644 packages/@aws-cdk-testing/cli-integ/tsconfig.json create mode 100644 packages/aws-cdk/lib/api/util/rwlock.ts delete mode 100644 packages/aws-cdk/test/integ/cli-regression-patches/README.md delete mode 100644 packages/aws-cdk/test/integ/cli/jest.config.js delete mode 100755 packages/aws-cdk/test/integ/common/jest-test.bash delete mode 100644 packages/aws-cdk/test/integ/github-helpers.ts delete mode 100644 packages/aws-cdk/test/integ/helpers/resource-pool.test.ts delete mode 100644 packages/aws-cdk/test/integ/helpers/resource-pool.ts delete mode 100644 packages/aws-cdk/test/integ/init/common.bash delete mode 100755 packages/aws-cdk/test/integ/init/test-all.sh delete mode 100755 packages/aws-cdk/test/integ/init/test-csharp.sh delete mode 100755 packages/aws-cdk/test/integ/init/test-generate-only.sh delete mode 100755 packages/aws-cdk/test/integ/init/test-go.sh delete mode 100755 packages/aws-cdk/test/integ/init/test-python-stackname.sh delete mode 100755 packages/aws-cdk/test/integ/init/test-python.sh delete mode 100755 packages/aws-cdk/test/integ/init/test-typescript.sh delete mode 100644 packages/aws-cdk/test/integ/run-against-dist.bash delete mode 100755 packages/aws-cdk/test/integ/run-against-repo delete mode 100755 packages/aws-cdk/test/integ/run-wrappers/dist/pip_ delete mode 100755 packages/aws-cdk/test/integ/run-wrappers/repo/dotnet delete mode 100755 packages/aws-cdk/test/integ/run-wrappers/repo/mvn delete mode 100755 packages/aws-cdk/test/integ/run-wrappers/repo/pip_ delete mode 100644 packages/aws-cdk/test/integ/typescript-versions.ts delete mode 100644 packages/aws-cdk/test/integ/uberpackage/jest.config.js create mode 100644 packages/aws-cdk/test/rwlock.test.ts diff --git a/lerna.json b/lerna.json index 0867af78081e7..fca71760dbe9a 100644 --- a/lerna.json +++ b/lerna.json @@ -6,6 +6,7 @@ "packages/*", "packages/@aws-cdk/*", "packages/@aws-cdk-containers/*", + "packages/@aws-cdk-testing/*", "packages/@aws-cdk/*/lambda-packages/*", "tools/*", "tools/@aws-cdk/*", diff --git a/package.json b/package.json index 478a4b85ceb49..09fa69afe1e0c 100644 --- a/package.json +++ b/package.json @@ -68,6 +68,7 @@ "packages/*", "packages/@aws-cdk/*", "packages/@aws-cdk-containers/*", + "packages/@aws-cdk-testing/*", "packages/@aws-cdk/*/lambda-packages/*", "tools/*", "tools/@aws-cdk/*", diff --git a/packages/@aws-cdk-testing/cli-integ/.eslintrc.js b/packages/@aws-cdk-testing/cli-integ/.eslintrc.js new file mode 100644 index 0000000000000..77f8007f9fc90 --- /dev/null +++ b/packages/@aws-cdk-testing/cli-integ/.eslintrc.js @@ -0,0 +1,4 @@ +const baseConfig = require('@aws-cdk/cdk-build-tools/config/eslintrc'); +baseConfig.ignorePatterns.push('resources/**/*'); +baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; +module.exports = baseConfig; diff --git a/packages/@aws-cdk-testing/cli-integ/.gitignore b/packages/@aws-cdk-testing/cli-integ/.gitignore new file mode 100644 index 0000000000000..eb8b474f4ca2a --- /dev/null +++ b/packages/@aws-cdk-testing/cli-integ/.gitignore @@ -0,0 +1,20 @@ +*.js +*.js.map +*.d.ts +!resources/**/* +node_modules +dist + +.LAST_BUILD +.nyc_output +coverage +nyc.config.js +.LAST_PACKAGE +*.snk + +!.eslintrc.js +!jest.config.js + +.DS_Store + +junit.xml diff --git a/packages/@aws-cdk-testing/cli-integ/.npmignore b/packages/@aws-cdk-testing/cli-integ/.npmignore new file mode 100644 index 0000000000000..b2a3ba40d9011 --- /dev/null +++ b/packages/@aws-cdk-testing/cli-integ/.npmignore @@ -0,0 +1,32 @@ +# Don't include original .ts files when doing `npm pack` +*.ts +!*.d.ts +coverage +.nyc_output +*.tgz + +dist +.LAST_PACKAGE +.LAST_BUILD +*.snk + +!test/integ/run-wrappers/dist + +*.tsbuildinfo + +jest.config.js +tsconfig.json +.eslintrc.js + +!test/integ/cli/jest.config.js +!test/integ/uberpackage/jest.config.js +!resources/ +!resources/**/* + +.DS_Store + +junit.xml + +# exclude cdk artifacts +**/cdk.out +test/ diff --git a/packages/@aws-cdk-testing/cli-integ/LICENSE b/packages/@aws-cdk-testing/cli-integ/LICENSE new file mode 100644 index 0000000000000..9b722c65c5481 --- /dev/null +++ b/packages/@aws-cdk-testing/cli-integ/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2018-2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/packages/@aws-cdk-testing/cli-integ/NOTICE b/packages/@aws-cdk-testing/cli-integ/NOTICE new file mode 100644 index 0000000000000..0dd703eaedb4a --- /dev/null +++ b/packages/@aws-cdk-testing/cli-integ/NOTICE @@ -0,0 +1,16 @@ +AWS Cloud Development Kit (AWS CDK) +Copyright 2018-2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Third party attributions of this package can be found in the THIRD_PARTY_LICENSES file diff --git a/packages/@aws-cdk-testing/cli-integ/README.md b/packages/@aws-cdk-testing/cli-integ/README.md new file mode 100644 index 0000000000000..deafd6a5a8841 --- /dev/null +++ b/packages/@aws-cdk-testing/cli-integ/README.md @@ -0,0 +1,151 @@ +# CDK CLI integration test + + +--- + +![cdk-constructs: Experimental](https://img.shields.io/badge/cdk--constructs-experimental-important.svg?style=for-the-badge) + +> The APIs of higher level constructs in this module are experimental and under active development. +> They are subject to non-backward compatible changes or removal in any future version. These are +> not subject to the [Semantic Versioning](https://semver.org/) model and breaking changes will be +> announced in the release notes. This means that while you may use them, you may need to update +> your source code when upgrading to a newer version of this package. + +--- + + + +This package contains CDK CLI integration test suites, as well as helper tools necessary to run those suites against various different distributions of the CDK (in the source repository, against a build directory, or against published releases). + +> The tools and tests themselves should arguably be in different packages, but I want to prevent package proliferation. For now we'll keep them together. + +## Tests + +The tests themselves are in the `tests/` directory: + +```text +tests/ +├── cli-integ-tests +├── init-csharp +├── init-fsharp +├── init-java +├── init-javascript +├── init-python +├── init-typescript-app +├── init-typescript-lib +└── uberpackage +``` + +Each subdirectory contains one test **suite**, and in the development pipeline each suite is run individually in a CodeBuild job, all in parallel. This requires manual configuration in the pipeline: to add a new suite, first add a suite here, then add the suite to the pipeline as well. The safest strategy is to add a trivially succeeding suite first (for example, a single test with `expect(true).toBeTruthy()`), add it to the pipeline, and then write the actual tests. + +Test suites are written as a collection of Jest tests, and they are run using Jest, using the code in the `lib/` directory as helpers. + +### Running a test suite + +You run a suite using the `bin/run-suite` tool. You must select either a version of the CLI and framework which can be `npm install`ed, or point to the root of the source tree: + +```shell +# Use the given source tree +$ bin/run-suite --use-source=/path/to/repo-root + +# Automatically determine the source tree root +$ bin/run-suite -a + +# Run against a released version +$ bin/run-suite --use-cli-release=2.34.5 +``` + +To run a specific test, add `-t` and a substring of the test name. For example: + +```shell +bin/run-suite -a cli-integ-tests -t 'load old assemblies' +``` + +## Tools + +There are a number of tools in the `bin/` directory. They are: + +```text +bin/ +├── apply-patches +├── query-github +├── run-suite +├── stage-distribution +└── test-root +``` + +* `apply-patches`: used for regression testing. Applies patches to historical versions of the tests to fix false positive test failures. +* `query-github`: used for regression testing. Queries GitHub for previously released versions. +* `run-suite`: run one of the test suites in the `tests/` directory. +* `stage-distribution`: used for testing in the pipeline. Uploads candidate release binaries to CodeArtifact so that they can be installed using `npm install`, `pip install`, etc. +* `test-root`: return the directory containing all tests (used for applying patches). + +## Regression testing + +The regression testing mechanism is somewhat involved and therefore deserves its own section. The principle is not too hard to explain though: + +*We run the previous version of the CLI integ tests against the new candidate release of the CLI, to make sure we didn't accidentally introduce any breaking behavior*. + +This is slightly complicated by two facts: + +* (1) Both the CLI and the framework may have changed, and an incompatibility may have arisen between the framework and CLI. Newer CLIs must always support older framework versions. We therefore run two flavors of the integration tests: + * Old tests, new CLI, new framework + * Old tests, new CLI, old framework + +The testing matrix looks like this: + +```text + OLD TESTS NEW TESTS + + CLI CLI + Old New Old New + ┌────────┬────────┐ ┌────────┬────────┐ + F'WORK │ prev │ │ F'WORK │ │ │ + Old │ rls │ regr │ Old │ (N/A) │ ? │ + │ integ │ │ │ │ │ + ├────────┼────────┤ ├────────┼────────┤ + │ │ │ │ │ cur │ + New │ (N/A) │ regr │ New │ (N/A) │ rls │ + │ │ │ │ │ integ │ + └────────┴────────┘ └────────┴────────┘ +``` + +We are covering everything except "new tests, new CLI, old framework", which is not clear that it even makes sense to test because some new features may rely on framework support which will not be in the old version yet. + +* (2) Sometimes, old tests will fail on newer releases when we introduce breaking changes to the framework or CLI for something serious (such as security reasons), or maybe because we had a bug in an old version that happened to pass, but now the test needs to be updated in order to pass a bugfix. + +For this case we have a patching mechanism, so that in a NEW release of the tools, we include files that are copied over an OLD release of the test, that allows them to pass. For the simplest case there is a mechanism to suppress the run of a single test, so that we can skip the running of one test for one release. For more complicated cases we copy in patched `.js` source files which will replace old source files. (Patches are considered part of the *tools*, not part of the *tests*). + +### Mechanism + +To run the tests in a regressory fashion, do the following: + +* Download the current `@aws-cdk-testing/cli-integ` artifact at `V1`. +* Determine the previous version `V0` (use `query-github` for this). +* Download the previous `@aws-cdk-testing/cli-integ` artifact at `V0`. +* From the `V1` artifact, apply the `V0` patch set. +* Run the `V0` tests with the `--framework-version` option: + +```shell +# Old tests, new CLI, new framework +V0/bin/run-suite --use-cli-release=V1 --framework-version=V1 [...] + +# Old tests, new CLI, old framework +V0/bin/run-suite --use-cli-release=V1 --framework-version=V0 [...] +``` + +### Patching + +To patch a previous set of tests to make them pass with a new release, add a directory to `resources/cli-regression-patches`. The simplest method is to add a `skip-tests.txt` file: + +```shell +# The version of the tests that are currently failing (V0 in the paragraph above) +export VERSION=X.Y.Z + +mkdir -p resources/cli-regression-patches/v${VERSION} +cp skip-tests.txt resources/cli-regression-patches/v${VERSION}/ +``` + +Now edit `resources/cli-regression-patches/vX.Y.Z/skip-tests.txt` and put the name of the test you want to skip on a line by itself. + +If you need to replace source files, it's probably best to stick compiled `.js` files in here. `.ts` source files wouldn't compile because they'd be missing `imports`. diff --git a/packages/@aws-cdk-testing/cli-integ/bin/apply-patches b/packages/@aws-cdk-testing/cli-integ/bin/apply-patches new file mode 100755 index 0000000000000..b27d0c93dc9e7 --- /dev/null +++ b/packages/@aws-cdk-testing/cli-integ/bin/apply-patches @@ -0,0 +1,19 @@ +#!/bin/bash +# Written in bash just because that's easier with all the file manipulation +set -eu +scriptdir=$(cd $(dirname $0) && pwd) +version="$1" +target_dir="$2" + + +if [[ ! -f "$2/skip-tests.txt" ]]; then + echo "$2: does not look like a test root directory." >&2 + exit 1 +fi + + +candidate_dir="${scriptdir}/../resources/cli-regression-patches/v${version}" + +if [[ -f "$candidate_dir" ]]; then + cp -R "${candidate_dir}/"* "$2" +fi \ No newline at end of file diff --git a/packages/@aws-cdk-testing/cli-integ/bin/download-and-run-old-tests b/packages/@aws-cdk-testing/cli-integ/bin/download-and-run-old-tests new file mode 100755 index 0000000000000..ad72ce50c0991 --- /dev/null +++ b/packages/@aws-cdk-testing/cli-integ/bin/download-and-run-old-tests @@ -0,0 +1,52 @@ +#!/bin/bash +set -eu +# Download old tests and run them. Written in bash, just because. Needs to do contortions, see below. +# +# Usage: +# +# download-and-run-old-tests [...args to run-suite...] +set -x + +scriptdir=$(cd $(dirname $0) && pwd) + +version="$1" +target_directory="old_tests" +shift + +rm -rf $target_directory && mkdir $target_directory + +# The old tests package MUST be 'npm install 'ed as a dependency, but MUST NOT +# end up in a `node_modules` directory. +# +# - MUST be 'npm install'ed: we need transitive dependencies as well. +# - as a dependency: if we check out the source and do an `npm install --production` in the +# package.json directory, NPM will still try to resolve devDependencies (even though it doesn't +# need to install them), and the devDeps do not exist on npmjs. +# - MUST NOT end up in `node_modules`: Jest 27 will ignore all tests that have `node_modules` in +# the path, and this behavior is not configurable before Jest 28. Unfortunately, because of TypeScript +# typing issues, we cannot move past Jest 27. +# +# To achieve this, do an `npm install ` then follow up with an `mv` to move the files out. +if ! npm install --prefix $target_directory --no-save @aws-cdk-testing/cli-integ@$version > npm.log 2>&1; then + cat npm.log >&2 + # Catch a "package does not exist" error, have to do it this way because for some reason, + # 'npm view ' doesn't exit with an error... :s + if grep -q 'code ETARGET' npm.log; then + echo "During migration, @aws-cdk-testing/cli-integ@$version does not exist yet." >&2 + + + # Do create an empty junit.xml file -- if we don't, then the "upload report" phase will fail + # if there are 0 files to upload. + echo '' > junit.xml + exit 0 + fi + exit 1 +fi + +mv $($target_directory/node_modules/.bin/test-root)/* $target_directory + +# Apply new patches to old tests +${scriptdir}/apply-patches $version $target_directory + +# Run the suite from the old tests +exec $target_directory/bin/run-suite "$@" \ No newline at end of file diff --git a/packages/@aws-cdk-testing/cli-integ/bin/query-github b/packages/@aws-cdk-testing/cli-integ/bin/query-github new file mode 100755 index 0000000000000..fbb9b1fa5f199 --- /dev/null +++ b/packages/@aws-cdk-testing/cli-integ/bin/query-github @@ -0,0 +1,2 @@ +#!/usr/bin/env node +require('./query-github.js'); diff --git a/packages/@aws-cdk-testing/cli-integ/bin/query-github.ts b/packages/@aws-cdk-testing/cli-integ/bin/query-github.ts new file mode 100644 index 0000000000000..33da60c57f618 --- /dev/null +++ b/packages/@aws-cdk-testing/cli-integ/bin/query-github.ts @@ -0,0 +1,58 @@ +// eslint-disable-next-line jest/no-jest-import +import * as yargs from 'yargs'; +import { fetchPreviousVersion } from '../lib/github'; + +async function main() { + const args = await yargs + .option('token', { + descripton: 'GitHub token (default: from environment GITHUB_TOKEN)', + alias: 't', + type: 'string', + requiresArg: true, + }) + .command('last-release', 'Query the last release', cmd => cmd + .option('prior-to', { + description: 'Return the most recent release before the given version', + alias: 'p', + type: 'string', + requiresArg: true, + }) + .option('major', { + description: 'Return the most recent release that matches', + alias: 'm', + type: 'string', + requiresArg: true, + })) + .demandCommand() + .help() + .strictOptions() + .showHelpOnFail(false) + .argv; + + const command = args._[0]; + + const token = args.token ?? process.env.GITHUB_TOKEN; + if (!token) { + throw new Error('Either pass --token or set GITHUB_TOKEN.'); + } + + switch (command) { + case 'last-release': + if (args['prior-to'] && args.major) { + throw new Error('Cannot pass both `--prior-to and --major at the same time'); + } + + // eslint-disable-next-line no-console + console.log(await fetchPreviousVersion(token, { + priorTo: args['prior-to'], + majorVersion: args.major, + })); + break; + } +} + +main().catch(e => { + // eslint-disable-next-line no-console + console.error(e); + process.exitCode = 1; +}); \ No newline at end of file diff --git a/packages/@aws-cdk-testing/cli-integ/bin/run-suite b/packages/@aws-cdk-testing/cli-integ/bin/run-suite new file mode 100755 index 0000000000000..d4a0aacaa4a54 --- /dev/null +++ b/packages/@aws-cdk-testing/cli-integ/bin/run-suite @@ -0,0 +1,2 @@ +#!/usr/bin/env node +require('./run-suite.js'); \ No newline at end of file diff --git a/packages/@aws-cdk-testing/cli-integ/bin/run-suite.ts b/packages/@aws-cdk-testing/cli-integ/bin/run-suite.ts new file mode 100644 index 0000000000000..9f26ca12b705b --- /dev/null +++ b/packages/@aws-cdk-testing/cli-integ/bin/run-suite.ts @@ -0,0 +1,136 @@ +/* eslint-disable no-console */ +import * as path from 'path'; +// eslint-disable-next-line jest/no-jest-import +import * as jest from 'jest'; +import * as yargs from 'yargs'; +import { ReleasePackageSourceSetup } from '../lib/package-sources/release-source'; +import { RepoPackageSourceSetup, autoFindRoot } from '../lib/package-sources/repo-source'; +import { IPackageSourceSetup } from '../lib/package-sources/source'; +import { serializeForSubprocess } from '../lib/package-sources/subprocess'; + +async function main() { + const args = await yargs + .usage('$0 ') + .positional('SUITENAME', { + descripton: 'Name of the test suite to run', + type: 'string', + demandOption: true, + }) + .option('test', { + descripton: 'Test pattern to selectively run tests', + alias: 't', + type: 'string', + requiresArg: true, + }) + .option('use-source', { + descripton: 'Use TypeScript packages from the given source repository (or "auto")', + alias: 'S', + type: 'string', + requiresArg: true, + }) + .option('use-cli-release', { + descripton: 'Run the current tests against the CLI at the given version', + alias: 'u', + type: 'string', + requiresArg: true, + }) + .option('auto-source', { + alias: 'a', + description: 'Automatically find the source tree from the current working directory', + type: 'boolean', + requiresArg: false, + }) + .option('runInBand', { + descripton: 'Run all tests in one Node process', + alias: 'i', + type: 'boolean', + }) + .options('framework-version', { + description: 'Framework version to use, if different than the CLI version (not all suites respect this)', + alias: 'f', + type: 'string', + }) + .options('verbose', { + alias: 'v', + description: 'Run in verbose mode', + type: 'boolean', + requiresArg: false, + }) + .options('passWithNoTests', { + description: 'Allow passing if the test suite is not found (default true when IS_CANARY mode, false otherwise)', + type: 'boolean', + requiresArg: false, + }) + .help() + .strictOptions() + .showHelpOnFail(false) + .argv; + + const suiteName = args._[0] as string; + if (!suiteName) { + throw new Error('Usage: run-suite '); + } + + let packageSource: undefined | IPackageSourceSetup; + function usePackageSource(s: IPackageSourceSetup) { + if (packageSource) { + throw new Error('Cannot specify two package sources'); + } + packageSource = s; + } + + if (args['use-source'] || args['auto-source']) { + if (args['framework-version']) { + throw new Error('Cannot use --framework-version with --use-source'); + } + + const root = args['use-source'] && args['use-source'] !== 'auto' + ? args['use-source'] + : await autoFindRoot(); + + usePackageSource(new RepoPackageSourceSetup(root)); + } else if (args['use-cli-release']) { + usePackageSource(new ReleasePackageSourceSetup(args['use-cli-release'], args['framework-version'])); + } + if (!packageSource) { + throw new Error('Specify either --use-source or --use-cli-release'); + } + + console.log(`Package source: ${packageSource.description}`); + console.log(`Test suite: ${suiteName}`); + + await packageSource.prepare(); + serializeForSubprocess(packageSource); + + if (args.verbose) { + process.env.VERBOSE = '1'; + } + + // Motivation behind this behavior: when adding a new test suite to the pipeline, because of the way our + // Pipeline package works, the suite would be added to the pipeline AND as a canary immediately. The canary + // would fail until the package was actually released, so for canaries we make an exception so that the initial + // canary would succeed even if the suite wasn't yet available. The fact that the suite is not optional in + // the pipeline protects us from typos. + const passWithNoTests = args.passWithNoTests ?? !!process.env.IS_CANARY; + + // Communicate with the config file (integ.jest.config.js) + process.env.TEST_SUITE_NAME = suiteName; + + try { + await jest.run([ + ...args.runInBand ? ['-i'] : [], + ...args.test ? ['-t', args.test] : [], + ...args.verbose ? ['--verbose'] : [], + ...passWithNoTests ? ['--passWithNoTests'] : [], + ], path.resolve(__dirname, '..', 'resources', 'integ.jest.config.js')); + + } finally { + await packageSource.cleanup(); + } +} + +main().catch(e => { + // eslint-disable-next-line no-console + console.error(e); + process.exitCode = 1; +}); diff --git a/packages/@aws-cdk-testing/cli-integ/bin/stage-distribution b/packages/@aws-cdk-testing/cli-integ/bin/stage-distribution new file mode 100755 index 0000000000000..99b7ad3606f7b --- /dev/null +++ b/packages/@aws-cdk-testing/cli-integ/bin/stage-distribution @@ -0,0 +1,2 @@ +#!/usr/bin/env node +require('./stage-distribution.js'); diff --git a/packages/@aws-cdk-testing/cli-integ/bin/stage-distribution.ts b/packages/@aws-cdk-testing/cli-integ/bin/stage-distribution.ts new file mode 100644 index 0000000000000..5bc53c28caead --- /dev/null +++ b/packages/@aws-cdk-testing/cli-integ/bin/stage-distribution.ts @@ -0,0 +1,259 @@ +/* eslint-disable no-console */ +import * as path from 'path'; +import * as fs from 'fs-extra'; +import * as glob from 'glob'; +import * as yargs from 'yargs'; +import { shell } from '../lib'; +import { TestRepository } from '../lib/staging/codeartifact'; +import { uploadJavaPackages, mavenLogin } from '../lib/staging/maven'; +import { uploadNpmPackages, npmLogin } from '../lib/staging/npm'; +import { uploadDotnetPackages, nugetLogin } from '../lib/staging/nuget'; +import { uploadPythonPackages, pypiLogin } from '../lib/staging/pypi'; +import { UsageDir } from '../lib/staging/usage-dir'; + +async function main() { + await yargs + .usage('$0 ') + .option('npm', { + description: 'Upload NPM packages only', + type: 'boolean', + requiresArg: false, + }) + .option('python', { + description: 'Upload Python packages only', + type: 'boolean', + requiresArg: false, + }) + .option('java', { + description: 'Upload Java packages only', + type: 'boolean', + requiresArg: false, + }) + .option('dotnet', { + description: 'Upload Dotnet packages only', + type: 'boolean', + requiresArg: false, + }) + .command('publish ', 'Publish a given directory', cmd => cmd + .positional('DIRECTORY', { + descripton: 'Directory distribution', + type: 'string', + demandOption: true, + }) + .option('name', { + alias: 'n', + description: 'Name of the repository to create (default: generate unique name)', + type: 'string', + requiresArg: true, + }), async (args) => { + + await validateDirectory(args); + const repo = await (args.name ? TestRepository.newWithName(args.name) : TestRepository.newRandom()); + const usageDir = UsageDir.default(); + + await doLogin(repo, usageDir, args); + await publish(repo, usageDir, args); + + header('Done'); + usageDir.advertise(); + }) + .command('login', 'Login to a given repository', cmd => cmd + .option('name', { + alias: 'n', + description: 'Name of the repository to log in to', + type: 'string', + requiresArg: true, + demandOption: true, + }), async (args) => { + + const repo = TestRepository.existing(args.name); + const usageDir = UsageDir.default(); + + await doLogin(repo, usageDir, args); + + usageDir.advertise(); + }) + .command('run ', 'Publish and run a command', cmd => cmd + .positional('DIRECTORY', { + descripton: 'Directory distribution', + type: 'string', + demandOption: true, + }) + .positional('COMMAND', { + alias: 'c', + description: 'Run the given command with the packages staged', + type: 'string', + array: true, + demandOption: true, + }) + .option('cleanup', { + alias: 'C', + description: 'Cleanup the repository afterwards', + type: 'boolean', + default: true, + requiresArg: false, + }), async (args) => { + + await validateDirectory(args); + const repo = await TestRepository.newRandom(); + const usageDir = UsageDir.default(); + + await doLogin(repo, usageDir, args); + await publish(repo, usageDir, args); + + try { + await usageDir.activateInCurrentProcess(); + + await shell(args.COMMAND ?? [], { + shell: true, + show: 'always', + }); + + } finally { + if (args.cleanup) { + await repo.delete(); + } + } + }) + .command('cleanup', 'Clean up testing repository', cmd => cmd + .option('name', { + alias: 'n', + description: 'Name of the repository to cleanup (default: most recent)', + type: 'string', + requiresArg: true, + }), async (args) => { + + const usageDir = UsageDir.default(); + + let repositoryName = args.name; + if (!repositoryName) { + repositoryName = (await usageDir.currentEnv()).CODEARTIFACT_REPO; + } + + if (!repositoryName) { + console.log(`No --name given and no $CODEARTIFACT_REPO found in ${usageDir.directory}, nothing cleaned up`); + return; + } + + const repo = TestRepository.existing(repositoryName); + await repo.delete(); + }) + .command('gc', 'Clean up day-old testing repositories', cmd => cmd, async () => { + await TestRepository.gc(); + }) + .demandCommand(1, 'You must supply a command') + .help() + .strictOptions() + .showHelpOnFail(false) + .parse(); +} + +async function validateDirectory(args: { + DIRECTORY: string, +}) { + if (!await fs.pathExists(path.join(args.DIRECTORY, 'build.json'))) { + throw new Error(`${args.DIRECTORY} does not look like a CDK dist directory (build.json missing)`); + } +} + +async function doLogin(repo: TestRepository, usageDir: UsageDir, args: { + npm?: boolean; + python?: boolean; + java?: boolean; + dotnet?: boolean; +}) { + const login = await repo.loginInformation(); + + const oldEnv = await usageDir.currentEnv(); + + await usageDir.clean(); + await usageDir.addToEnv({ + CODEARTIFACT_REPO: login.repositoryName, + }); + + if (oldEnv.BUILD_VERSION) { + await usageDir.addToEnv({ + BUILD_VERSION: oldEnv.BUILD_VERSION, + }); + } + + const doRepo = whichRepos(args); + + await doRepo.npm(() => npmLogin(login, usageDir)); + await doRepo.python(() => pypiLogin(login, usageDir)); + await doRepo.java(() => mavenLogin(login, usageDir)); + await doRepo.dotnet(() => nugetLogin(login, usageDir)); +} + +async function publish(repo: TestRepository, usageDir: UsageDir, args: { + DIRECTORY: string, + npm?: boolean; + python?: boolean; + java?: boolean; + dotnet?: boolean; +}) { + const directory = `${args.DIRECTORY}`; + const login = await repo.loginInformation(); + + const doRepo = whichRepos(args); + + const buildJson = await fs.readJson(path.join(directory, 'build.json')); + await usageDir.addToEnv({ + BUILD_VERSION: buildJson.version, + }); + + await doRepo.npm(async () => { + header('NPM'); + await uploadNpmPackages(glob.sync(path.join(directory, 'js', '*.tgz')), login, usageDir); + }); + + await doRepo.python(async () => { + header('Python'); + await uploadPythonPackages(glob.sync(path.join(directory, 'python', '*')), login); + }); + + await doRepo.java(async () => { + header('Java'); + await uploadJavaPackages(glob.sync(path.join(directory, 'java', '**', '*.pom')), login, usageDir); + }); + + await doRepo.dotnet(async () => { + header('.NET'); + await uploadDotnetPackages(glob.sync(path.join(directory, 'dotnet', '**', '*.nupkg')), usageDir); + }); + + console.log('🛍 Configuring packages for upstream versions'); + await repo.markAllUpstreamAllow(); +} + +function whichRepos(args: { + npm?: boolean; + python?: boolean; + java?: boolean; + dotnet?: boolean; +}) { + const all = args.npm === undefined && args.python === undefined && args.java === undefined && args.dotnet === undefined; + + const invoke = (block: () => Promise) => block(); + const skip = () => { }; + + return { + npm: args.npm || all ? invoke : skip, + python: args.python || all ? invoke : skip, + java: args.java || all ? invoke : skip, + dotnet: args.dotnet || all ? invoke : skip, + }; +} + +function header(caption: string) { + console.log(''); + console.log('/'.repeat(70)); + console.log(`// ${caption}`); + console.log(''); +} + +main().catch(e => { + // eslint-disable-next-line no-console + console.error(e); + process.exitCode = 1; +}); diff --git a/packages/@aws-cdk-testing/cli-integ/bin/test-root b/packages/@aws-cdk-testing/cli-integ/bin/test-root new file mode 100755 index 0000000000000..d14248e4d4019 --- /dev/null +++ b/packages/@aws-cdk-testing/cli-integ/bin/test-root @@ -0,0 +1,2 @@ +#!/usr/bin/env node +require('./test-root.js'); diff --git a/packages/@aws-cdk-testing/cli-integ/bin/test-root.ts b/packages/@aws-cdk-testing/cli-integ/bin/test-root.ts new file mode 100644 index 0000000000000..244194a7eb31c --- /dev/null +++ b/packages/@aws-cdk-testing/cli-integ/bin/test-root.ts @@ -0,0 +1,3 @@ +import * as path from 'path'; +// eslint-disable-next-line no-console +console.log(path.resolve(__dirname, '..')); \ No newline at end of file diff --git a/packages/@aws-cdk-testing/cli-integ/entrypoints/test-cli-regression-against-current-code.sh b/packages/@aws-cdk-testing/cli-integ/entrypoints/test-cli-regression-against-current-code.sh new file mode 100755 index 0000000000000..ff50aa5f69a2b --- /dev/null +++ b/packages/@aws-cdk-testing/cli-integ/entrypoints/test-cli-regression-against-current-code.sh @@ -0,0 +1,11 @@ +#!/bin/bash +# +# Run our integration tests in regression mode against the +# candidate version of the framework, which is the one we just packed. +# +set -euo pipefail +integdir=$(cd $(dirname $0) && pwd) + +source ${integdir}/test-cli-regression.bash + +run_regression_against_framework_version CANDIDATE_VERSION diff --git a/packages/@aws-cdk-testing/cli-integ/entrypoints/test-cli-regression-against-latest-release.sh b/packages/@aws-cdk-testing/cli-integ/entrypoints/test-cli-regression-against-latest-release.sh new file mode 100755 index 0000000000000..8b670eb7b793d --- /dev/null +++ b/packages/@aws-cdk-testing/cli-integ/entrypoints/test-cli-regression-against-latest-release.sh @@ -0,0 +1,11 @@ +#!/bin/bash +# +# Run our integration tests in regression mode against the +# previous version of the framework, relative to the version being packed now. +# +set -euo pipefail +integdir=$(cd $(dirname $0) && pwd) + +source ${integdir}/test-cli-regression.bash + +run_regression_against_framework_version PREVIOUS_VERSION diff --git a/packages/aws-cdk/test/integ/test-cli-regression.bash b/packages/@aws-cdk-testing/cli-integ/entrypoints/test-cli-regression.bash similarity index 100% rename from packages/aws-cdk/test/integ/test-cli-regression.bash rename to packages/@aws-cdk-testing/cli-integ/entrypoints/test-cli-regression.bash diff --git a/packages/@aws-cdk-testing/cli-integ/entrypoints/test.sh b/packages/@aws-cdk-testing/cli-integ/entrypoints/test.sh new file mode 100755 index 0000000000000..bf0ec0a7c5c68 --- /dev/null +++ b/packages/@aws-cdk-testing/cli-integ/entrypoints/test.sh @@ -0,0 +1,12 @@ +#!/bin/bash +set -euo pipefail +scriptdir=$(cd $(dirname $0) && pwd) + +echo '~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~' +echo 'CLI Integration Tests' +echo '~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~' + +cd $scriptdir + +source ../common/jest-test.bash +invokeJest "$@" diff --git a/packages/@aws-cdk-testing/cli-integ/jest.config.js b/packages/@aws-cdk-testing/cli-integ/jest.config.js new file mode 100644 index 0000000000000..30ba8fce114e2 --- /dev/null +++ b/packages/@aws-cdk-testing/cli-integ/jest.config.js @@ -0,0 +1,10 @@ +const baseConfig = require('@aws-cdk/cdk-build-tools/config/jest.config'); +module.exports = { + ...baseConfig, + coverageThreshold: { + global: { + statements: 20, + branches: 2, + }, + }, +}; diff --git a/packages/aws-cdk/test/integ/helpers/aws.ts b/packages/@aws-cdk-testing/cli-integ/lib/aws.ts similarity index 99% rename from packages/aws-cdk/test/integ/helpers/aws.ts rename to packages/@aws-cdk-testing/cli-integ/lib/aws.ts index beea3fca64768..289b87bf955a1 100644 --- a/packages/aws-cdk/test/integ/helpers/aws.ts +++ b/packages/@aws-cdk-testing/cli-integ/lib/aws.ts @@ -232,14 +232,14 @@ retry.abort = (e: Error): Error => { return e; }; -export async function sleep(ms: number) { - return new Promise(ok => setTimeout(ok, ms)); -} - export function outputFromStack(key: string, stack: AWS.CloudFormation.Stack): string | undefined { return (stack.Outputs ?? []).find(o => o.OutputKey === key)?.OutputValue; } +export async function sleep(ms: number) { + return new Promise(ok => setTimeout(ok, ms)); +} + function chainableCredentials(region: string): AWS.Credentials | undefined { const profileName = process.env.AWS_PROFILE; @@ -287,5 +287,4 @@ function chainableCredentials(region: string): AWS.Credentials | undefined { } return undefined; - -} +} \ No newline at end of file diff --git a/packages/aws-cdk/test/integ/helpers/corking.ts b/packages/@aws-cdk-testing/cli-integ/lib/corking.ts similarity index 90% rename from packages/aws-cdk/test/integ/helpers/corking.ts rename to packages/@aws-cdk-testing/cli-integ/lib/corking.ts index c3970586ead03..f083270a028f5 100644 --- a/packages/aws-cdk/test/integ/helpers/corking.ts +++ b/packages/@aws-cdk-testing/cli-integ/lib/corking.ts @@ -25,4 +25,8 @@ export class MemoryStream extends stream.Writable { return new Promise(ok => strm.once('drain', ok)); } } + + public toString() { + return this.buffer().toString(); + } } diff --git a/packages/@aws-cdk-testing/cli-integ/lib/files.ts b/packages/@aws-cdk-testing/cli-integ/lib/files.ts new file mode 100644 index 0000000000000..186b1b0ff15f5 --- /dev/null +++ b/packages/@aws-cdk-testing/cli-integ/lib/files.ts @@ -0,0 +1,81 @@ +import * as os from 'os'; +import * as path from 'path'; +import * as fs from 'fs-extra'; + +export async function rmFile(filename: string) { + if (await fs.pathExists(filename)) { + await fs.unlink(filename); + } +} + +export async function addToFile(filename: string, line: string) { + let contents = await fs.pathExists(filename) ? await fs.readFile(filename, { encoding: 'utf-8' }) : ''; + if (!contents.endsWith('\n')) { + contents += '\n'; + } + contents += line + '\n'; + + await writeFile(filename, contents); +} + +export async function writeFile(filename: string, contents: string) { + await fs.mkdirp(path.dirname(filename)); + await fs.writeFile(filename, contents, { encoding: 'utf-8' }); +} + +export async function copyDirectoryContents(dir: string, target: string) { + for (const file of await fs.readdir(path.join(dir), { encoding: 'utf-8' })) { + await fs.copyFile(path.join(dir, file), path.join(target, file)); + } +} + +export function findUp(name: string, directory: string = process.cwd()): string | undefined { + const absoluteDirectory = path.resolve(directory); + + const file = path.join(directory, name); + if (fs.existsSync(file)) { + return file; + } + + const { root } = path.parse(absoluteDirectory); + if (absoluteDirectory == root) { + return undefined; + } + + return findUp(name, path.dirname(absoluteDirectory)); +} + + +/** + * Docker-safe home directory + */ +export function homeDir() { + return os.userInfo().homedir ?? os.homedir(); +} + +export async function loadLines(filename: string): Promise { + return await fs.pathExists(filename) ? (await fs.readFile(filename, { encoding: 'utf-8' })).trim().split('\n') : []; +} + +export async function writeLines(filename: string, lines: string[]) { + // Must end in a newline or our bash script won't read it properly + await fs.writeFile(filename, lines.join('\n') + '\n', { encoding: 'utf-8' }); +} + +/** + * Update a spaceless ini file in place + */ +export function updateIniKey(lines: string[], key: string, value: string) { + const prefix = `${key}=`; + let found = false; + for (let i = 0; i < lines.length; i++) { + if (lines[i].startsWith(prefix)) { + lines[i] = prefix + value; + found = true; + break; + } + } + if (!found) { + lines.push(prefix + value); + } +} diff --git a/packages/@aws-cdk-testing/cli-integ/lib/github.ts b/packages/@aws-cdk-testing/cli-integ/lib/github.ts new file mode 100644 index 0000000000000..b483e5fb2fce8 --- /dev/null +++ b/packages/@aws-cdk-testing/cli-integ/lib/github.ts @@ -0,0 +1,43 @@ +import { Octokit } from '@octokit/rest'; +import * as semver from 'semver'; + +export async function fetchPreviousVersion(token: string, options?: { + priorTo?: string, + majorVersion?: string, +}) { + const github = new Octokit({ auth: token }); + const releases = await github.repos.listReleases({ + owner: 'aws', + repo: 'aws-cdk', + }); + + // this returns a list in descending order, newest releases first + // opts for same major version where possible, falling back otherwise + // to previous major versions. + let previousMVRelease = undefined; + for (const release of releases.data) { + const version = release.name?.replace('v', ''); + if (!version) { continue; } + + // Any old version is fine + if (!options?.majorVersion && !options?.priorTo) { + return version; + } + + if (options?.majorVersion && `${semver.major(version)}` === options.majorVersion) { + return version; + } + + if (options?.priorTo && semver.lt(version, options?.priorTo) && semver.major(version) === semver.major(options.priorTo)) { + return version; + } + + // Otherwise return the most recent version that didn't match any + if (!previousMVRelease) { + previousMVRelease = version; + } + } + if (previousMVRelease) { return previousMVRelease; } + + throw new Error(`Unable to find previous version given ${JSON.stringify(options)}`); +}; \ No newline at end of file diff --git a/packages/@aws-cdk-testing/cli-integ/lib/index.ts b/packages/@aws-cdk-testing/cli-integ/lib/index.ts new file mode 100644 index 0000000000000..32ab6ebcb83b4 --- /dev/null +++ b/packages/@aws-cdk-testing/cli-integ/lib/index.ts @@ -0,0 +1,12 @@ +export * from './aws'; +export * from './corking'; +export * from './integ-test'; +export * from './memoize'; +export * from './resource-pool'; +export * from './with-sam'; +export * from './shell'; +export * from './with-aws'; +export * from './with-cdk-app'; +export * from './with-packages'; +export * from './with-temporary-directory'; +export * from './resources'; \ No newline at end of file diff --git a/packages/aws-cdk/test/integ/helpers/test-helpers.ts b/packages/@aws-cdk-testing/cli-integ/lib/integ-test.ts similarity index 59% rename from packages/aws-cdk/test/integ/helpers/test-helpers.ts rename to packages/@aws-cdk-testing/cli-integ/lib/integ-test.ts index 5dc65f6ed492e..731bddd7e2e91 100644 --- a/packages/aws-cdk/test/integ/helpers/test-helpers.ts +++ b/packages/@aws-cdk-testing/cli-integ/lib/integ-test.ts @@ -2,9 +2,13 @@ import * as fs from 'fs'; import * as path from 'path'; import { MemoryStream } from './corking'; -const SKIP_TESTS = fs.readFileSync(path.join(__dirname, 'skip-tests.txt'), { encoding: 'utf-8' }).split('\n'); +const SKIP_TESTS = fs.readFileSync(path.join(__dirname, '..', 'skip-tests.txt'), { encoding: 'utf-8' }).split('\n'); -export type TestContext = { readonly output: NodeJS.WritableStream; }; +export interface TestContext { + readonly randomString: string; + readonly output: NodeJS.WritableStream; + log(s: string): void; +}; /** * A wrapper for jest's 'test' which takes regression-disabled tests into account and prints a banner @@ -31,19 +35,24 @@ export function integTest( output.write(`${name}\n`); output.write('================================================================\n'); - let success = true; try { - return await callback({ output }); + return await callback({ + output, + randomString: randomString(), + log(s: string) { + output.write(`${s}\n`); + }, + }); } catch (e) { - await output.flushTo(process.stderr); - process.stderr.write(`❌ ${e.toString()}\n`); - success = false; + output.write(e.message); + output.write(e.stack); + // Print output only if the test fails. Use 'console.log' so the output is buffered by + // jest and prints without a stack trace (if verbose: false). + // eslint-disable-next-line no-console + console.log(output.buffer().toString()); + // await output.flushTo(process.stderr); + // process.stderr.write(`❌ ${e.toString()}\n`); throw e; - } finally { - if (success) { - // Show people there's progress - process.stderr.write('✅'); - } } }, timeoutMillis); } @@ -51,3 +60,8 @@ export function integTest( function shouldSkip(testName: string) { return SKIP_TESTS.includes(testName); } + +export function randomString() { + // Crazy + return Math.random().toString(36).replace(/[^a-z0-9]+/g, ''); +} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/cli-integ/lib/lists.ts b/packages/@aws-cdk-testing/cli-integ/lib/lists.ts new file mode 100644 index 0000000000000..69f504a15474f --- /dev/null +++ b/packages/@aws-cdk-testing/cli-integ/lib/lists.ts @@ -0,0 +1,9 @@ +export function chunk(n: number, xs: A[]): A[][] { + const ret = new Array(); + + for (let i = 0; i < xs.length; i += n) { + ret.push(xs.slice(i, i + n)); + } + + return ret; +} \ No newline at end of file diff --git a/packages/aws-cdk/test/integ/helpers/memoize.ts b/packages/@aws-cdk-testing/cli-integ/lib/memoize.ts similarity index 100% rename from packages/aws-cdk/test/integ/helpers/memoize.ts rename to packages/@aws-cdk-testing/cli-integ/lib/memoize.ts diff --git a/packages/@aws-cdk-testing/cli-integ/lib/npm.ts b/packages/@aws-cdk-testing/cli-integ/lib/npm.ts new file mode 100644 index 0000000000000..991599f010ba8 --- /dev/null +++ b/packages/@aws-cdk-testing/cli-integ/lib/npm.ts @@ -0,0 +1,13 @@ +import { spawnSync } from 'child_process'; + +const MINIMUM_VERSION = '3.9'; + +/** + * Use NPM preinstalled on the machine to look up a list of TypeScript versions + */ +export function typescriptVersionsSync(): string[] { + const { stdout } = spawnSync('npm', ['--silent', 'view', `typescript@>=${MINIMUM_VERSION}`, 'version', '--json']); + + const versions: string[] = JSON.parse(stdout); + return Array.from(new Set(versions.map(v => v.split('.').slice(0, 2).join('.')))); +} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/cli-integ/lib/package-sources/release-source.ts b/packages/@aws-cdk-testing/cli-integ/lib/package-sources/release-source.ts new file mode 100644 index 0000000000000..7d097630e26de --- /dev/null +++ b/packages/@aws-cdk-testing/cli-integ/lib/package-sources/release-source.ts @@ -0,0 +1,77 @@ +import * as os from 'os'; +import * as path from 'path'; +import * as fs from 'fs-extra'; +import { copyDirectoryContents } from '../files'; +import { shell, rimraf, addToShellPath } from '../shell'; +import { IPackageSourceSetup, IPackageSource } from './source'; + +export class ReleasePackageSourceSetup implements IPackageSourceSetup { + readonly name = 'release'; + readonly description = `release @ ${this.version}`; + + private tempDir?: string; + + constructor(private readonly version: string, private readonly frameworkVersion?: string) { + } + + public async prepare(): Promise { + this.tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'tmpcdk')); + fs.mkdirSync(this.tempDir, { recursive: true }); + + await shell(['node', require.resolve('npm'), 'install', `aws-cdk@${this.version}`], { + cwd: this.tempDir, + }); + + process.env.CDK_CLI_PATH = this.tempDir; + process.env.VERSION = this.version; + process.env.FRAMEWORK_VERSION = this.frameworkVersion ?? this.version; + } + + public async cleanup(): Promise { + if (this.tempDir) { + rimraf(this.tempDir); + } + } +} + +export class ReleasePackageSource implements IPackageSource { + private readonly cliPath: string; + private readonly version: string; + + constructor() { + this.cliPath = process.env.CDK_CLI_PATH!; + this.version = process.env.VERSION!; + } + + public async makeCliAvailable() { + addToShellPath(path.join(this.cliPath, 'node_modules', '.bin')); + } + + public assertJsiiPackagesAvailable() { + } + + public async initializeDotnetPackages(currentDir: string) { + if (process.env.CWD_FILES_DIR) { + await copyDirectoryContents(process.env.CWD_FILES_DIR, currentDir); + } + } + + public majorVersion() { + return this.version.split('.')[0] as string; + } + + public requestedFrameworkVersion() { + return process.env.FRAMEWORK_VERSION!; + } + + public requestedAlphaVersion(): string { + const frameworkVersion = this.requestedFrameworkVersion(); + if (frameworkVersion.includes('-rc.')) { + // For a pipeline release + return frameworkVersion.replace(/-rc\.\d+$/, '-alpha.999'); + } else { + // For a stable release + return `${frameworkVersion}-alpha.0`; + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/cli-integ/lib/package-sources/repo-source.ts b/packages/@aws-cdk-testing/cli-integ/lib/package-sources/repo-source.ts new file mode 100644 index 0000000000000..d1d445d7bf931 --- /dev/null +++ b/packages/@aws-cdk-testing/cli-integ/lib/package-sources/repo-source.ts @@ -0,0 +1,106 @@ +import * as os from 'os'; +import * as path from 'path'; +import * as fs from 'fs-extra'; +import { findUp } from '../files'; +import { shell, addToShellPath } from '../shell'; +import { IPackageSourceSetup, IPackageSource } from './source'; + +export class RepoPackageSourceSetup implements IPackageSourceSetup { + readonly name = 'repo'; + readonly description = `repo(${this.repoRoot})`; + + constructor(private readonly repoRoot: string) { + } + + public async prepare(): Promise { + if (!await fs.pathExists(path.join(this.repoRoot, 'package.json')) || !await fs.pathExists(path.join(this.repoRoot, 'yarn.lock'))) { + throw new Error(`${this.repoRoot}: does not look like the repository root`); + } + + process.env.REPO_ROOT = this.repoRoot; + process.env.REPO_PACKAGE_MAP = await writePackageMap(this.repoRoot); + addToShellPath(path.resolve(__dirname, 'repo-tools')); + } + + public async cleanup(): Promise { + } +} + +export class RepoPackageSource implements IPackageSource { + private readonly repoRoot: string; + + constructor() { + this.repoRoot = process.env.REPO_ROOT as string; + } + + public async makeCliAvailable() { + addToShellPath(path.join(this.repoRoot, 'packages', 'aws-cdk', 'bin')); + } + + public assertJsiiPackagesAvailable() { + throw new Error('jsii client packages are not available when using REPO source'); + } + + public async initializeDotnetPackages() { + } + + public majorVersion() { + const releaseJson = fs.readJsonSync(path.resolve(this.repoRoot, 'release.json')); + return releaseJson.majorVersion; + } + + public requestedFrameworkVersion(): string { + return '*'; + } + + public requestedAlphaVersion(): string { + return '*'; + } +} + +async function writePackageMap(repoRoot: string): Promise { + const packages = await findYarnPackages(repoRoot); + const fileName = path.join(os.tmpdir(), 'package-map.json'); + await fs.writeJson(fileName, packages); + return fileName; +} + +/** + * Cache monorepo discovery results, we only want to do this once per run + */ +const YARN_MONOREPO_CACHE: Record = {}; + +/** + * Return a { name -> directory } packages found in a Yarn monorepo + * + * Cached in YARN_MONOREPO_CACHE. + */ +async function findYarnPackages(root: string): Promise> { + if (!(root in YARN_MONOREPO_CACHE)) { + const output: YarnWorkspacesOutput = JSON.parse(await shell(['yarn', 'workspaces', '--silent', 'info'], { + captureStderr: false, + cwd: root, + show: 'error', + })); + + const ret: Record = {}; + for (const [k, v] of Object.entries(output)) { + ret[k] = path.join(root, v.location); + } + YARN_MONOREPO_CACHE[root] = ret; + } + return YARN_MONOREPO_CACHE[root]; +} + +/** + * Find the root directory of the repo from the current directory + */ +export async function autoFindRoot() { + const found = await findUp('release.json'); + if (!found) { + throw new Error(`Could not determine repository root: 'release.json' not found from ${process.cwd()}`); + } + return path.dirname(found); +} + +type YarnWorkspacesOutput = Record; diff --git a/packages/@aws-cdk-testing/cli-integ/lib/package-sources/repo-tools/npm b/packages/@aws-cdk-testing/cli-integ/lib/package-sources/repo-tools/npm new file mode 100644 index 0000000000000..ab3229febddc1 --- /dev/null +++ b/packages/@aws-cdk-testing/cli-integ/lib/package-sources/repo-tools/npm @@ -0,0 +1,2 @@ +#!/usr/bin/env node +require('./npm.js'); \ No newline at end of file diff --git a/packages/@aws-cdk-testing/cli-integ/lib/package-sources/repo-tools/npm.ts b/packages/@aws-cdk-testing/cli-integ/lib/package-sources/repo-tools/npm.ts new file mode 100644 index 0000000000000..5efcc3e4a865f --- /dev/null +++ b/packages/@aws-cdk-testing/cli-integ/lib/package-sources/repo-tools/npm.ts @@ -0,0 +1,48 @@ +import * as child_process from 'child_process'; +import * as fs from 'fs-extra'; + +let argv = process.argv.slice(2); + +// eslint-disable-next-line no-console +console.log('fake npm'); + +if (argv[0] === 'install') { + if (!process.env.REPO_PACKAGE_MAP) { + throw new Error('REPO_PACKAGE_MAP not set'); + } + const repoPackageMap = fs.readJsonSync(process.env.REPO_PACKAGE_MAP, { encoding: 'utf-8' }); + + // Replace paths in the 'package.json' in the current directory + if (fs.pathExistsSync('package.json')) { + const packageJson = fs.readJsonSync('package.json', { encoding: 'utf-8' }); + for (const deps of [packageJson.dependencies ?? {}, packageJson.devDependencies ?? {}]) { + for (const [name, version] of Object.entries(deps)) { + deps[name] = repoPackageMap[name] ?? version; + } + } + fs.writeJsonSync('package.json', packageJson, { encoding: 'utf-8' }); + } + + // Replace package names on the command line + argv = argv.map(x => repoPackageMap[x] ?? x); +} + +//////////////////////////////////////////////////////////////////////// +// Shell out to original npm + +const child = child_process.spawn('node', [require.resolve('npm'), ...argv], { + shell: false, + stdio: ['ignore', 'inherit', 'inherit'], +}); + +child.once('error', e => { + // eslint-disable-next-line no-console + console.error(e); + process.exitCode = 1; +}); + +child.once('close', code => { + if (code) { + process.exitCode = code; + } +}); \ No newline at end of file diff --git a/packages/@aws-cdk-testing/cli-integ/lib/package-sources/source.ts b/packages/@aws-cdk-testing/cli-integ/lib/package-sources/source.ts new file mode 100644 index 0000000000000..21e970701db94 --- /dev/null +++ b/packages/@aws-cdk-testing/cli-integ/lib/package-sources/source.ts @@ -0,0 +1,30 @@ +export interface IPackageSourceSetup { + readonly name: string; + readonly description: string; + + prepare(): Promise; + cleanup(): Promise; +} + +export interface IPackageSource { + makeCliAvailable(): Promise; + + assertJsiiPackagesAvailable(): void; + majorVersion(): string; + + initializeDotnetPackages(targetDir: string): Promise; + + /** + * Framework version if it's different than the CLI version + * + * Not all tests will respect this. + */ + requestedFrameworkVersion(): string; + + /** + * Versions of alpha packages if different than the CLI version + * + * Not all tests will respect this. + */ + requestedAlphaVersion(): string; +} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/cli-integ/lib/package-sources/subprocess.ts b/packages/@aws-cdk-testing/cli-integ/lib/package-sources/subprocess.ts new file mode 100644 index 0000000000000..09f8fdd231a0b --- /dev/null +++ b/packages/@aws-cdk-testing/cli-integ/lib/package-sources/subprocess.ts @@ -0,0 +1,15 @@ +import { ReleasePackageSource } from './release-source'; +import { RepoPackageSource } from './repo-source'; +import { IPackageSourceSetup, IPackageSource } from './source'; + +export function serializeForSubprocess(s: IPackageSourceSetup) { + process.env.PACKAGE_SOURCE = s.name; +} + +export function packageSourceInSubprocess(): IPackageSource { + switch (process.env.PACKAGE_SOURCE) { + case 'repo': return new RepoPackageSource(); + case 'release': return new ReleasePackageSource(); + default: throw new Error(`Unrecognized package source: ${process.env.PACKAGE_SOURCE}`); + } +} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/cli-integ/lib/resource-pool.ts b/packages/@aws-cdk-testing/cli-integ/lib/resource-pool.ts new file mode 100644 index 0000000000000..1c10f54be562d --- /dev/null +++ b/packages/@aws-cdk-testing/cli-integ/lib/resource-pool.ts @@ -0,0 +1,144 @@ +import { ILock, XpMutex, XpMutexPool } from './xpmutex'; + +/** + * A class that holds a pool of resources and gives them out and returns them on-demand + * + * The resources will be given out front to back, when they are returned + * the most recently returned version will be given out again (for best + * cache coherency). + * + * If there are multiple consumers waiting for a resource, consumers are serviced + * in FIFO order for most fairness. + */ +export class ResourcePool { + public static withResources(name: string, resources: A[]) { + const pool = XpMutexPool.fromName(name); + return new ResourcePool(pool, resources); + } + + private readonly resources: ReadonlyArray; + private readonly mutexes: Record = {}; + private readonly locks: Record = {}; + + private constructor(private readonly pool: XpMutexPool, resources: A[]) { + if (resources.length === 0) { + throw new Error('Must have at least one resource in the pool'); + } + + // Shuffle to reduce contention + resources = [...resources]; + fisherYatesShuffle(resources); + this.resources = resources; + + for (const res of resources) { + this.mutexes[res] = this.pool.mutex(res); + } + } + + /** + * Take one value from the resource pool + * + * If no such value is currently available, wait until it is. + */ + public async take(): Promise> { + while (true) { + // Start a wait on the unlock now -- if the unlock signal comes after + // we try to acquire but before we start the wait, we might miss it. + const wait = this.pool.awaitUnlock(5000); + + for (const res of this.unlockedResources()) { + const lease = await this.tryObtainLease(res); + if (lease) { + // Ignore the wait (count as handled) + wait.then(() => {}, () => {}); + return lease; + } + } + + // None available, wait until one gets unlocked then try again + await wait; + } + } + + /** + * Execute a block using a single resource from the pool + */ + public async using(block: (x: A) => B | Promise): Promise { + const lease = await this.take(); + try { + return await block(lease.value); + } finally { + await lease.dispose(); + } + } + + private async tryObtainLease(value: A) { + const lock = await this.mutexes[value].tryAcquire(); + if (!lock) { + return undefined; + } + + this.locks[value] = lock; + return this.makeLease(value); + } + + private makeLease(value: A): ILease { + let disposed = false; + return { + value, + dispose: () => { + if (disposed) { + throw new Error('Calling dispose() on an already-disposed lease.'); + } + disposed = true; + return this.returnValue(value); + }, + }; + } + + /** + * When a value is returned: + * + * - If someone's waiting for it, give it to them + * - Otherwise put it back into the pool + */ + private async returnValue(value: string) { + const lock = this.locks[value]; + delete this.locks[value]; + await lock?.release(); + } + + /** + * Return all resources that we definitely don't own the locks for + */ + private unlockedResources(): A[] { + return this.resources.filter(res => !this.locks[res]); + } +} + +/** + * A single value taken from the pool + */ +export interface ILease { + /** + * The value obtained by the lease + */ + readonly value: A; + + /** + * Return the leased value to the pool + */ + dispose(): Promise; +} + +/** + * Shuffle an array in-place + */ +function fisherYatesShuffle(xs: A[]) { + for (let i = xs.length - 1; i >= 1; i--) { + const j = Math.floor(Math.random() * i); + const h = xs[j]; + xs[j] = xs[i]; + xs[i] = h; + } +} diff --git a/packages/@aws-cdk-testing/cli-integ/lib/resources.ts b/packages/@aws-cdk-testing/cli-integ/lib/resources.ts new file mode 100644 index 0000000000000..39ab24ab48f58 --- /dev/null +++ b/packages/@aws-cdk-testing/cli-integ/lib/resources.ts @@ -0,0 +1,4 @@ +import * as path from 'path'; + +export const RESOURCES_DIR = path.resolve(__dirname, '..', 'resources'); + diff --git a/packages/@aws-cdk-testing/cli-integ/lib/shell.ts b/packages/@aws-cdk-testing/cli-integ/lib/shell.ts new file mode 100644 index 0000000000000..a969f9d99fc11 --- /dev/null +++ b/packages/@aws-cdk-testing/cli-integ/lib/shell.ts @@ -0,0 +1,168 @@ +import * as child_process from 'child_process'; +import * as fs from 'fs'; +import * as path from 'path'; +import { TestContext } from './integ-test'; +import { TemporaryDirectoryContext } from './with-temporary-directory'; + +/** + * A shell command that does what you want + * + * Is platform-aware, handles errors nicely. + */ +export async function shell(command: string[], options: ShellOptions = {}): Promise { + if (options.modEnv && options.env) { + throw new Error('Use either env or modEnv but not both'); + } + + // Always output the command + (options.output ?? process.stdout).write(`💻 ${command.join(' ')}\n`); + + let output: NodeJS.WritableStream | undefined = options.output ?? process.stdout; + switch (options.show ?? 'always') { + case 'always': + break; + case 'never': + case 'error': + output = undefined; + break; + } + + if (process.env.VERBOSE) { + output = process.stdout; + } + + const env = options.env ?? (options.modEnv ? { ...process.env, ...options.modEnv } : process.env); + + const child = child_process.spawn(command[0], command.slice(1), { + ...options, + env, + // Need this for Windows where we want .cmd and .bat to be found as well. + shell: true, + stdio: ['ignore', 'pipe', 'pipe'], + }); + + return new Promise((resolve, reject) => { + const stdout = new Array(); + const stderr = new Array(); + + child.stdout!.on('data', chunk => { + output?.write(chunk); + stdout.push(chunk); + }); + + child.stderr!.on('data', chunk => { + output?.write(chunk); + if (options.captureStderr ?? true) { + stderr.push(chunk); + } + }); + + child.once('error', reject); + + child.once('close', code => { + const stderrOutput = Buffer.concat(stderr).toString('utf-8'); + const stdoutOutput = Buffer.concat(stdout).toString('utf-8'); + const out = (options.onlyStderr ? stderrOutput : stdoutOutput + stderrOutput).trim(); + if (code === 0 || options.allowErrExit) { + resolve(out); + } else { + if (options.show === 'error') { + (options.output ?? process.stdout).write(out + '\n'); + } + reject(new Error(`'${command.join(' ')}' exited with error code ${code}.`)); + } + }); + }); +} + +export interface ShellOptions extends child_process.SpawnOptions { + /** + * Properties to add to 'env' + */ + readonly modEnv?: Record; + + /** + * Don't fail when exiting with an error + * + * @default false + */ + readonly allowErrExit?: boolean; + + /** + * Whether to capture stderr + * + * @default true + */ + readonly captureStderr?: boolean; + + /** + * Pass output here + * + * @default stdout unless quiet=true + */ + readonly output?: NodeJS.WritableStream; + + /** + * Only return stderr. For example, this is used to validate + * that when CI=true, all logs are sent to stdout. + * + * @default false + */ + readonly onlyStderr?: boolean; + + /** + * Don't log to stdout + * + * @default always + */ + readonly show?: 'always' | 'never' | 'error'; +} + +export class ShellHelper { + public static fromContext(context: TestContext & TemporaryDirectoryContext) { + return new ShellHelper(context.integTestDir, context.output); + } + + constructor( + private readonly _cwd: string, + private readonly _output: NodeJS.WritableStream) { } + + public async shell(command: string[], options: Omit = {}): Promise { + return shell(command, { + output: this._output, + cwd: this._cwd, + ...options, + }); + } +} + +/** + * rm -rf reimplementation, don't want to depend on an NPM package for this + */ +export function rimraf(fsPath: string) { + try { + const isDir = fs.lstatSync(fsPath).isDirectory(); + + if (isDir) { + for (const file of fs.readdirSync(fsPath)) { + rimraf(path.join(fsPath, file)); + } + fs.rmdirSync(fsPath); + } else { + fs.unlinkSync(fsPath); + } + } catch (e) { + // We will survive ENOENT + if (e.code !== 'ENOENT') { throw e; } + } +} + +export function addToShellPath(x: string) { + const parts = process.env.PATH?.split(':') ?? []; + + if (!parts.includes(x)) { + parts.unshift(x); + } + + process.env.PATH = parts.join(':'); +} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/cli-integ/lib/staging/codeartifact.ts b/packages/@aws-cdk-testing/cli-integ/lib/staging/codeartifact.ts new file mode 100644 index 0000000000000..75c00a97ad9f0 --- /dev/null +++ b/packages/@aws-cdk-testing/cli-integ/lib/staging/codeartifact.ts @@ -0,0 +1,282 @@ +import * as AWS from 'aws-sdk'; +import { sleep } from '../aws'; + +const COLLECT_BY_TAG = 'collect-by'; +const REPO_LIFETIME_MS = 24 * 3600 * 1000; // One day + +export class TestRepository { + public static readonly DEFAULT_DOMAIN = 'test-cdk'; + + public static async newRandom() { + const qualifier = Math.random().toString(36).replace(/[^a-z0-9]+/g, ''); + + const repo = new TestRepository(`test-${qualifier}`); + await repo.prepare(); + return repo; + } + + public static async newWithName(name: string) { + const repo = new TestRepository(name); + await repo.prepare(); + return repo; + } + + public static existing(repositoryName: string) { + return new TestRepository(repositoryName); + } + + /** + * Garbage collect repositories + */ + public static async gc() { + if (!await TestRepository.existing('*dummy*').domainExists()) { + return; + } + + const codeArtifact = new AWS.CodeArtifact(); + + let nextToken: string | undefined; + do { + const page = await codeArtifact.listRepositories({ nextToken }).promise(); + + for (const repo of page.repositories ?? []) { + const tags = await codeArtifact.listTagsForResource({ resourceArn: repo.arn! }).promise(); + const collectable = tags?.tags?.find(t => t.key === COLLECT_BY_TAG && Number(t.value) < Date.now()); + if (collectable) { + // eslint-disable-next-line no-console + console.log('Deleting', repo.name); + await codeArtifact.deleteRepository({ + domain: repo.domainName!, + repository: repo.name!, + }).promise(); + } + } + + nextToken = page.nextToken; + } while (nextToken); + } + + public readonly npmUpstream = 'npm-upstream'; + public readonly pypiUpstream = 'pypi-upstream'; + public readonly nugetUpstream = 'nuget-upstream'; + public readonly mavenUpstream = 'maven-upstream'; + public readonly domain = TestRepository.DEFAULT_DOMAIN; + + private readonly codeArtifact = new AWS.CodeArtifact(); + + private _loginInformation: LoginInformation | undefined; + + private constructor(public readonly repositoryName: string) { + } + + public async prepare() { + await this.ensureDomain(); + await this.ensureUpstreams(); + + await this.ensureRepository(this.repositoryName, { + description: 'Testing repository', + upstreams: [ + this.npmUpstream, + this.pypiUpstream, + this.nugetUpstream, + this.mavenUpstream, + ], + tags: { + [COLLECT_BY_TAG]: `${Date.now() + REPO_LIFETIME_MS}`, + }, + }); + } + + public async loginInformation(): Promise { + if (this._loginInformation) { + return this._loginInformation; + } + + this._loginInformation = { + authToken: (await this.codeArtifact.getAuthorizationToken({ domain: this.domain, durationSeconds: 12 * 3600 }).promise()).authorizationToken!, + repositoryName: this.repositoryName, + npmEndpoint: (await this.codeArtifact.getRepositoryEndpoint({ domain: this.domain, repository: this.repositoryName, format: 'npm' }).promise()).repositoryEndpoint!, + mavenEndpoint: (await this.codeArtifact.getRepositoryEndpoint({ domain: this.domain, repository: this.repositoryName, format: 'maven' }).promise()).repositoryEndpoint!, + nugetEndpoint: (await this.codeArtifact.getRepositoryEndpoint({ domain: this.domain, repository: this.repositoryName, format: 'nuget' }).promise()).repositoryEndpoint!, + pypiEndpoint: (await this.codeArtifact.getRepositoryEndpoint({ domain: this.domain, repository: this.repositoryName, format: 'pypi' }).promise()).repositoryEndpoint!, + }; + return this._loginInformation; + } + + public async delete() { + try { + await this.codeArtifact.deleteRepository({ + domain: this.domain, + repository: this.repositoryName, + }).promise(); + + // eslint-disable-next-line no-console + console.log('Deleted', this.repositoryName); + } catch (e) { + if (e.code !== 'ResourceNotFoundException') { throw e; } + // Okay + } + } + + /** + * List all packages and mark them as "allow upstream versions". + * + * If we don't do this and we publish `foo@2.3.4-rc.0`, then we can't + * download `foo@2.3.0` anymore because by default CodeArtifact will + * block different versions from the same package. + */ + public async markAllUpstreamAllow() { + for await (const pkg of this.listPackages({ upstream: 'BLOCK' })) { + await retryThrottled(() => this.codeArtifact.putPackageOriginConfiguration({ + domain: this.domain, + repository: this.repositoryName, + + format: pkg.format!, + package: pkg.package!, + namespace: pkg.namespace!, + restrictions: { + publish: 'ALLOW', + upstream: 'ALLOW', + }, + }).promise()); + } + } + + private async ensureDomain() { + if (await this.domainExists()) { return; } + await this.codeArtifact.createDomain({ + domain: this.domain, + tags: [{ key: 'testing', value: 'true' }], + }).promise(); + } + + private async ensureUpstreams() { + await this.ensureRepository(this.npmUpstream, { + description: 'The upstream repository for NPM', + external: 'public:npmjs', + }); + await this.ensureRepository(this.mavenUpstream, { + description: 'The upstream repository for Maven', + external: 'public:maven-central', + }); + await this.ensureRepository(this.nugetUpstream, { + description: 'The upstream repository for NuGet', + external: 'public:nuget-org', + }); + await this.ensureRepository(this.pypiUpstream, { + description: 'The upstream repository for PyPI', + external: 'public:pypi', + }); + } + + private async ensureRepository(name: string, options?: { + readonly description?: string, + readonly external?: string, + readonly upstreams?: string[], + readonly tags?: Record, + }) { + if (await this.repositoryExists(name)) { return; } + + await this.codeArtifact.createRepository({ + domain: this.domain, + repository: name, + description: options?.description, + upstreams: options?.upstreams?.map(repositoryName => ({ repositoryName })), + tags: options?.tags ? Object.entries(options.tags).map(([key, value]) => ({ key, value })) : undefined, + }).promise(); + + if (options?.external) { + const externalConnection = options.external; + await retry(() => this.codeArtifact.associateExternalConnection({ + domain: this.domain, + repository: name, + externalConnection, + }).promise()); + } + } + + private async domainExists() { + try { + await this.codeArtifact.describeDomain({ domain: this.domain }).promise(); + return true; + } catch (e) { + if (e.code !== 'ResourceNotFoundException') { throw e; } + return false; + } + } + + private async repositoryExists(name: string) { + try { + await this.codeArtifact.describeRepository({ domain: this.domain, repository: name }).promise(); + return true; + } catch (e) { + if (e.code !== 'ResourceNotFoundException') { throw e; } + return false; + } + } + + private async* listPackages(filter: Pick = {}) { + let response = await retryThrottled(() => this.codeArtifact.listPackages({ + domain: this.domain, + repository: this.repositoryName, + ...filter, + }).promise()); + + while (true) { + for (const p of response.packages ?? []) { + yield p; + } + + if (!response.nextToken) { + break; + } + + response = await retryThrottled(() => this.codeArtifact.listPackages({ + domain: this.domain, + repository: this.repositoryName, + ...filter, + nextToken: response.nextToken, + }).promise()); + } + } +} + +async function retry(block: () => Promise) { + let attempts = 3; + while (true) { + try { + return await block(); + } catch (e) { + if (attempts-- === 0) { throw e; } + // eslint-disable-next-line no-console + console.debug(e.message); + await sleep(500); + } + } +} + +async function retryThrottled(block: () => Promise) { + let time = 100; + let attempts = 15; + while (true) { + try { + return await block(); + } catch (e) { + // eslint-disable-next-line no-console + console.debug(e.message); + if (e.code !== 'ThrottlingException') { throw e; } + if (attempts-- === 0) { throw e; } + await sleep(Math.floor(Math.random() * time)); + time *= 2; + } + } +} + +export interface LoginInformation { + readonly authToken: string; + readonly repositoryName: string; + readonly npmEndpoint: string; + readonly mavenEndpoint: string; + readonly nugetEndpoint: string; + readonly pypiEndpoint: string; +} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/cli-integ/lib/staging/maven.ts b/packages/@aws-cdk-testing/cli-integ/lib/staging/maven.ts new file mode 100644 index 0000000000000..18bb6a83f1255 --- /dev/null +++ b/packages/@aws-cdk-testing/cli-integ/lib/staging/maven.ts @@ -0,0 +1,87 @@ +/* eslint-disable no-console */ +import * as path from 'path'; +import { writeFile } from '../files'; +import { shell } from '../shell'; +import { LoginInformation } from './codeartifact'; +import { parallelShell } from './parallel-shell'; +import { UsageDir } from './usage-dir'; + +// Do not try to JIT the Maven binary +const NO_JIT = '-XX:+TieredCompilation -XX:TieredStopAtLevel=1'; + +export async function mavenLogin(login: LoginInformation, usageDir: UsageDir) { + await writeMavenSettingsFile(settingsFile(usageDir), login); + + // Write env var + // Twiddle JVM settings a bit to make Maven survive running on a CodeBuild box. + await usageDir.addToEnv({ + MAVEN_OPTS: `-Duser.home=${usageDir.directory} ${NO_JIT} ${process.env.MAVEN_OPTS ?? ''}`.trim(), + }); +} + +function settingsFile(usageDir: UsageDir) { + // If we configure usageDir as a fake home directory Maven will find this file. + // (No other way to configure the settings file as part of the environment). + return path.join(usageDir.directory, '.m2', 'settings.xml'); +} + +export async function uploadJavaPackages(packages: string[], login: LoginInformation, usageDir: UsageDir) { + await parallelShell(packages, async (pkg, output) => { + console.log(`⏳ ${pkg}`); + + await shell(['mvn', + `--settings=${settingsFile(usageDir)}`, + 'org.apache.maven.plugins:maven-deploy-plugin:3.0.0:deploy-file', + `-Durl=${login.mavenEndpoint}`, + '-DrepositoryId=codeartifact', + `-DpomFile=${pkg}`, + `-Dfile=${pkg.replace(/.pom$/, '.jar')}`, + `-Dsources=${pkg.replace(/.pom$/, '-sources.jar')}`, + `-Djavadoc=${pkg.replace(/.pom$/, '-javadoc.jar')}`], { + output, + modEnv: { + // Do not try to JIT the Maven binary + MAVEN_OPTS: `${NO_JIT} ${process.env.MAVEN_OPTS ?? ''}`.trim(), + }, + }); + + console.log(`✅ ${pkg}`); + }, + (pkg, output) => { + if (output.toString().includes('409 Conflict')) { + console.log(`❌ ${pkg}: already exists. Skipped.`); + return 'skip'; + } + return 'fail'; + }); +} + +export async function writeMavenSettingsFile(filename: string, login: LoginInformation) { + await writeFile(filename, ` + + + + codeartifact + aws + ${login.authToken} + + + + + default + + + codeartifact + ${login.mavenEndpoint} + + + + + + default + + `); +} diff --git a/packages/@aws-cdk-testing/cli-integ/lib/staging/npm.ts b/packages/@aws-cdk-testing/cli-integ/lib/staging/npm.ts new file mode 100644 index 0000000000000..74db6eddc76a5 --- /dev/null +++ b/packages/@aws-cdk-testing/cli-integ/lib/staging/npm.ts @@ -0,0 +1,62 @@ +/* eslint-disable no-console */ +import * as path from 'path'; +import { updateIniKey, loadLines, writeLines } from '../files'; +import { shell } from '../shell'; +import { LoginInformation } from './codeartifact'; +import { parallelShell } from './parallel-shell'; +import { UsageDir } from './usage-dir'; + +export async function npmLogin(login: LoginInformation, usageDir: UsageDir) { + // Creating an ~/.npmrc that references an envvar is what you're supposed to do. (https://docs.npmjs.com/private-modules/ci-server-config) + await writeNpmLoginToken(usageDir, login.npmEndpoint, '${NPM_TOKEN}'); + + // Add variables to env file + await usageDir.addToEnv(npmEnv(usageDir, login)); +} + +function npmEnv(usageDir: UsageDir, login: LoginInformation) { + return { + npm_config_userconfig: path.join(usageDir.directory, '.npmrc'), + npm_config_registry: login.npmEndpoint, + npm_config_always_auth: 'true', // Necessary for NPM 6, otherwise it will sometimes not pass the token + NPM_TOKEN: login.authToken, + }; +} + +export async function uploadNpmPackages(packages: string[], login: LoginInformation, usageDir: UsageDir) { + await parallelShell(packages, async (pkg, output) => { + console.log(`⏳ ${pkg}`); + + // path.resolve() is required -- if the filename ends up looking like `js/bla.tgz` then NPM thinks it's a short form GitHub name. + await shell(['node', require.resolve('npm'), 'publish', path.resolve(pkg)], { + modEnv: npmEnv(usageDir, login), + show: 'error', + output, + }); + + console.log(`✅ ${pkg}`); + }, (pkg, output) => { + if (output.toString().includes('code EPUBLISHCONFLICT')) { + console.log(`❌ ${pkg}: already exists. Skipped.`); + return 'skip'; + } + if (output.toString().includes('code EPRIVATE')) { + console.log(`❌ ${pkg}: is private. Skipped.`); + return 'skip'; + } + return 'fail'; + }); +} + +async function writeNpmLoginToken(usageDir: UsageDir, endpoint: string, token: string) { + const rcFile = path.join(usageDir.directory, '.npmrc'); + const lines = await loadLines(rcFile); + + const key = `${endpoint.replace(/^https:/, '')}:_authToken`; + updateIniKey(lines, key, token); + + await writeLines(rcFile, lines); + return rcFile; +} + +// Environment variable, .npmrc in same directory as package.json or in home dir \ No newline at end of file diff --git a/packages/@aws-cdk-testing/cli-integ/lib/staging/nuget.ts b/packages/@aws-cdk-testing/cli-integ/lib/staging/nuget.ts new file mode 100644 index 0000000000000..91b4cde286f64 --- /dev/null +++ b/packages/@aws-cdk-testing/cli-integ/lib/staging/nuget.ts @@ -0,0 +1,75 @@ +/* eslint-disable no-console */ +import { writeFile } from '../files'; +import { shell } from '../shell'; +import { LoginInformation } from './codeartifact'; +import { parallelShell } from './parallel-shell'; +import { UsageDir } from './usage-dir'; + +export async function nugetLogin(login: LoginInformation, usageDir: UsageDir) { + // NuGet.Config MUST live in the current directory or in the home directory, and there is no environment + // variable to configure its location. + await writeNuGetConfigFile(usageDir.cwdFile('NuGet.Config'), login); +} + +export async function uploadDotnetPackages(packages: string[], usageDir: UsageDir) { + await usageDir.copyCwdFileHere('NuGet.Config'); + + await parallelShell(packages, async (pkg, output) => { + console.log(`⏳ ${pkg}`); + + await shell(['dotnet', 'nuget', 'push', + pkg, + '--source', 'CodeArtifact', + '--no-symbols', + '--force-english-output', + '--disable-buffering', + '--timeout', '600', + '--skip-duplicate'], { + output, + }); + + console.log(`✅ ${pkg}`); + }, + (pkg, output) => { + if (output.toString().includes('Conflict')) { + console.log(`❌ ${pkg}: already exists. Skipped.`); + return 'skip'; + } + if (output.includes('System.Threading.AbandonedMutexException')) { + console.log(`♻️ ${pkg}: AbandonedMutexException. Probably a sign of throttling, retrying.`); + return 'retry'; + } + if (output.includes('Too Many Requests')) { + console.log(`♻️ ${pkg}: Too many requests. Retrying.`); + return 'retry'; + } + if (output.includes('System.IO.IOException: The system cannot open the device or file specified.')) { + console.log(`♻️ ${pkg}: Some error that we've seen before as a result of throttling. Retrying.`); + return 'retry'; + } + return 'fail'; + }); +} + +async function writeNuGetConfigFile(filename: string, login: LoginInformation) { + // `dotnet nuget push` has an `--api-key` parameter, but CodeArtifact + // does not support that. We must authenticate with Basic auth. + await writeFile(filename, ` + + + + + + + + + + + + + + +`); +} + +// NuGet.Config in current directory \ No newline at end of file diff --git a/packages/@aws-cdk-testing/cli-integ/lib/staging/parallel-shell.ts b/packages/@aws-cdk-testing/cli-integ/lib/staging/parallel-shell.ts new file mode 100644 index 0000000000000..53242e73a1239 --- /dev/null +++ b/packages/@aws-cdk-testing/cli-integ/lib/staging/parallel-shell.ts @@ -0,0 +1,52 @@ +import PQueue from 'p-queue'; +import { sleep } from '../aws'; +import { MemoryStream } from '../corking'; + + +export type ErrorResponse = 'fail' | 'skip' | 'retry'; + +/** + * Run a function in parallel with cached output + */ +export async function parallelShell( + inputs: A[], + block: (x: A, output: NodeJS.WritableStream) => Promise, + swallowError?: (x: A, output: string) => ErrorResponse, +) { + // Limit to 10 for now, too many instances of Maven exhaust the CodeBuild instance memory + const q = new PQueue({ concurrency: Number(process.env.CONCURRENCY) || 10 }); + await q.addAll(inputs.map(input => async () => { + let attempts = 10; + let sleepMs = 500; + while (true) { + const output = new MemoryStream(); + try { + await block(input, output); + return; + } catch (e) { + switch (swallowError?.(input, output.toString())) { + case 'skip': + return; + + case 'retry': + if (--attempts > 0) { + await sleep(Math.floor(Math.random() * sleepMs)); + sleepMs *= 2; + continue; + } + break; + + case 'fail': + case undefined: + break; + } + + // eslint-disable-next-line no-console + console.error(output.toString()); + throw e; + } + } + })); + + await q.onEmpty(); +} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/cli-integ/lib/staging/pypi.ts b/packages/@aws-cdk-testing/cli-integ/lib/staging/pypi.ts new file mode 100644 index 0000000000000..6308a95ce3b5b --- /dev/null +++ b/packages/@aws-cdk-testing/cli-integ/lib/staging/pypi.ts @@ -0,0 +1,50 @@ +/* eslint-disable no-console */ +import * as path from 'path'; +import { writeFile } from '../files'; +import { shell } from '../shell'; +import { LoginInformation } from './codeartifact'; +import { parallelShell } from './parallel-shell'; +import { UsageDir } from './usage-dir'; + +export async function pypiLogin(login: LoginInformation, usageDir: UsageDir) { + // Write pip config file and set environment var + await writeFile(path.join(usageDir.directory, 'pip.conf'), [ + '[global]', + `index-url = https://aws:${login.authToken}@${login.pypiEndpoint.replace(/^https:\/\//, '')}simple/`, + ].join('\n')); + await usageDir.addToEnv({ + PIP_CONFIG_FILE: `${usageDir.directory}/pip.conf`, + }); +} + +export async function uploadPythonPackages(packages: string[], login: LoginInformation) { + await shell(['pip', 'install', 'twine'], { show: 'error' }); + + // Even though twine supports uploading all packages in one go, we have to upload them + // individually since CodeArtifact does not support Twine's `--skip-existing`. Fun beans. + await parallelShell(packages, async (pkg, output) => { + console.log(`⏳ ${pkg}`); + + await shell(['twine', 'upload', '--verbose', pkg], { + modEnv: { + TWINE_USERNAME: 'aws', + TWINE_PASSWORD: login.authToken, + TWINE_REPOSITORY_URL: login.pypiEndpoint, + }, + show: 'error', + output, + }); + + console.log(`✅ ${pkg}`); + }, (pkg, output) => { + if (output.toString().includes('This package is configured to block new versions') || output.toString().includes('409 Conflict')) { + console.log(`❌ ${pkg}: already exists. Skipped.`); + return 'skip'; + } + if (output.includes('429 Too Many Requests ')) { + console.log(`♻️ ${pkg}: 429 Too Many Requests. Retrying.`); + return 'retry'; + } + return 'fail'; + }); +} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/cli-integ/lib/staging/usage-dir.ts b/packages/@aws-cdk-testing/cli-integ/lib/staging/usage-dir.ts new file mode 100644 index 0000000000000..055d85f875cb8 --- /dev/null +++ b/packages/@aws-cdk-testing/cli-integ/lib/staging/usage-dir.ts @@ -0,0 +1,99 @@ +import * as path from 'path'; +import * as fs from 'fs-extra'; +import { copyDirectoryContents, homeDir, loadLines, updateIniKey, writeLines } from '../files'; + +export const DEFAULT_USAGE_DIR = path.join(homeDir(), '.codeartifact/usage'); + +/** + * The usage directory is where we write per-session config files to access the CodeArtifact repository. + * + * Some config files may be written in a system-global location, but they will not be active unless the + * contents of this directory have been sourced/copied into the current terminal. + * + * CONTRACT + * + * There are two special entries: + * + * - `env`, a file with `key=value` entries for environment variables to set. + * - `cwd/`, a directory with files that need to be copied into the current directory before each command. + * + * Other than these, code may write tempfiles to this directory if it wants, but there is no meaning + * implied for other files. + */ +export class UsageDir { + public static default() { + return new UsageDir(DEFAULT_USAGE_DIR); + } + + public readonly envFile: string; + public readonly cwdDir: string; + + private constructor(public readonly directory: string) { + this.envFile = path.join(this.directory, 'env'); + this.cwdDir = path.join(this.directory, 'cwd'); + } + + public async clean() { + await fs.rm(this.directory, { recursive: true, force: true }); + await fs.mkdirp(path.join(this.directory, 'cwd')); + await fs.writeFile(path.join(this.directory, 'env'), '', { encoding: 'utf-8' }); + + await this.addToEnv({ + CWD_FILES_DIR: path.join(this.directory, 'cwd'), + }); + + // Write a bash helper to load these settings + await fs.writeFile(path.join(this.directory, 'activate.bash'), [ + `while read -u10 line; do [[ -z $line ]] || export "$line"; done 10<${this.directory}/env`, + 'cp -R $CWD_FILES_DIR/ .', // Copy files from directory even if it is empty + ].join('\n'), { encoding: 'utf-8' }); + } + + public async addToEnv(settings: Record) { + const lines = await loadLines(this.envFile); + for (const [k, v] of Object.entries(settings)) { + updateIniKey(lines, k, v); + } + await writeLines(this.envFile, lines); + } + + public async currentEnv(): Promise> { + const lines = await loadLines(this.envFile); + + const splitter = /^([a-zA-Z0-9_-]+)\s*=\s*(.*)$/; + + const ret: Record = {}; + for (const line of lines) { + const m = line.match(splitter); + if (m) { + ret[m[1]] = m[2]; + } + } + return ret; + } + + public cwdFile(filename: string) { + return path.join(this.cwdDir, filename); + } + + public async activateInCurrentProcess() { + for (const [k, v] of Object.entries(await this.currentEnv())) { + process.env[k] = v; + } + + await copyDirectoryContents(this.cwdDir, '.'); + } + + public async copyCwdFileHere(...filenames: string[]) { + for (const file of filenames) { + await fs.copyFile(path.join(this.cwdDir, file), file); + } + } + + public advertise() { + // eslint-disable-next-line no-console + console.log('To activate these settings in the current terminal:'); + // eslint-disable-next-line no-console + console.log(` source ${this.directory}/activate.bash`); + } +} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/cli-integ/lib/with-aws.ts b/packages/@aws-cdk-testing/cli-integ/lib/with-aws.ts new file mode 100644 index 0000000000000..3a86fb05f39df --- /dev/null +++ b/packages/@aws-cdk-testing/cli-integ/lib/with-aws.ts @@ -0,0 +1,63 @@ +import { AwsClients } from './aws'; +import { TestContext } from './integ-test'; +import { ResourcePool } from './resource-pool'; + +export type AwsContext = { readonly aws: AwsClients }; + +/** + * Higher order function to execute a block with an AWS client setup + * + * Allocate the next region from the REGION pool and dispose it afterwards. + */ +export function withAws(block: (context: A & AwsContext) => Promise) { + return (context: A) => regionPool().using(async (region) => { + const aws = await AwsClients.forRegion(region, context.output); + await sanityCheck(aws); + + return block({ ...context, aws }); + }); +} + +let _regionPool: undefined | ResourcePool; +export function regionPool(): ResourcePool { + if (_regionPool !== undefined) { + return _regionPool; + } + + const REGIONS = process.env.AWS_REGIONS + ? process.env.AWS_REGIONS.split(',') + : [process.env.AWS_REGION ?? process.env.AWS_DEFAULT_REGION ?? 'us-east-1']; + + // eslint-disable-next-line no-console + console.log(`Using regions: ${REGIONS}\n`); + + _regionPool = ResourcePool.withResources('aws_regions', REGIONS); + return _regionPool; +} + +/** + * Perform a one-time quick sanity check that the AWS clients has properly configured credentials + * + * If we don't do this, calls are going to fail and they'll be retried and everything will take + * forever before the user notices a simple misconfiguration. + * + * We can't check for the presence of environment variables since credentials could come from + * anywhere, so do simple account retrieval. + * + * Only do it once per process. + */ +async function sanityCheck(aws: AwsClients) { + if (sanityChecked === undefined) { + try { + await aws.account(); + sanityChecked = true; + } catch (e) { + sanityChecked = false; + throw new Error(`AWS credentials probably not configured, got error: ${e.message}`); + } + } + if (!sanityChecked) { + throw new Error('AWS credentials probably not configured, see previous error'); + } +} +let sanityChecked: boolean | undefined; \ No newline at end of file diff --git a/packages/aws-cdk/test/integ/helpers/cdk.ts b/packages/@aws-cdk-testing/cli-integ/lib/with-cdk-app.ts similarity index 65% rename from packages/aws-cdk/test/integ/helpers/cdk.ts rename to packages/@aws-cdk-testing/cli-integ/lib/with-cdk-app.ts index 6b95f2d5d44f1..efc14dbaf17c0 100644 --- a/packages/aws-cdk/test/integ/helpers/cdk.ts +++ b/packages/@aws-cdk-testing/cli-integ/lib/with-cdk-app.ts @@ -1,80 +1,14 @@ -import * as child_process from 'child_process'; +/* eslint-disable no-console */ import * as fs from 'fs'; import * as os from 'os'; import * as path from 'path'; import { outputFromStack, AwsClients } from './aws'; -import { memoize0 } from './memoize'; -import { ResourcePool } from './resource-pool'; -import { TestContext } from './test-helpers'; - -const REGIONS = process.env.AWS_REGIONS - ? process.env.AWS_REGIONS.split(',') - : [process.env.AWS_REGION ?? process.env.AWS_DEFAULT_REGION ?? 'us-east-1']; - -export const FRAMEWORK_VERSION = process.env.FRAMEWORK_VERSION ?? '*'; - -export let MAJOR_VERSION = FRAMEWORK_VERSION.split('.')[0]; -if (MAJOR_VERSION === '*') { - if (process.env.REPO_ROOT) { - // eslint-disable-next-line @typescript-eslint/no-require-imports - const releaseJson = require(path.resolve(process.env.REPO_ROOT, 'release.json')); - MAJOR_VERSION = `${releaseJson.majorVersion}`; - } else { - // eslint-disable-next-line no-console - console.error('[WARNING] Have to guess at major version. Guessing version 1 to not break anything, but this should not happen'); - MAJOR_VERSION = '1'; - } -} - -process.stdout.write(`Using regions: ${REGIONS}\n`); -process.stdout.write(`Using framework version: ${FRAMEWORK_VERSION} (major version ${MAJOR_VERSION})\n`); - -const REGION_POOL = new ResourcePool(REGIONS); - - -/** - * Cache monorepo discovery results, we only want to do this once per run - */ -const YARN_MONOREPO_CACHE: Record = {}; - -/** - * Return a { name -> directory } packages found in a Yarn monorepo - * - * Cached in YARN_MONOREPO_CACHE. - */ -export async function findYarnPackages(root: string): Promise> { - if (!(root in YARN_MONOREPO_CACHE)) { - const output: YarnWorkspacesOutput = JSON.parse(await shell(['yarn', 'workspaces', '--silent', 'info'], { - captureStderr: false, - cwd: root, - })); - - const ret: Record = {}; - for (const [k, v] of Object.entries(output)) { - ret[k] = path.join(root, v.location); - } - YARN_MONOREPO_CACHE[root] = ret; - } - return YARN_MONOREPO_CACHE[root]; -} - -type YarnWorkspacesOutput = Record; - -export type AwsContext = { readonly aws: AwsClients }; - -/** - * Higher order function to execute a block with an AWS client setup - * - * Allocate the next region from the REGION pool and dispose it afterwards. - */ -export function withAws(block: (context: A & AwsContext) => Promise) { - return (context: A) => REGION_POOL.using(async (region) => { - const aws = await AwsClients.forRegion(region, context.output); - await sanityCheck(aws); - - return block({ ...context, aws }); - }); -} +import { TestContext } from './integ-test'; +import { IPackageSource } from './package-sources/source'; +import { packageSourceInSubprocess } from './package-sources/subprocess'; +import { RESOURCES_DIR } from './resources'; +import { shell, ShellOptions, ShellHelper, rimraf } from './shell'; +import { AwsContext, withAws } from './with-aws'; /** * Higher order function to execute a block with a CDK app fixture @@ -86,7 +20,7 @@ export function withAws(block: (context: A & AwsContext) */ export function withCdkApp(block: (context: TestFixture) => Promise) { return async (context: A) => { - const randy = randomString(); + const randy = context.randomString; const stackNamePrefix = `cdktest-${randy}`; const integTestDir = path.join(os.tmpdir(), `cdk-integ-${randy}`); @@ -94,18 +28,19 @@ export function withCdkApp(block: (context: context.output.write(` Test directory: ${integTestDir}\n`); context.output.write(` Region: ${context.aws.region}\n`); - await cloneDirectory(path.join(__dirname, '..', 'cli', 'app'), integTestDir, context.output); + await cloneDirectory(path.join(RESOURCES_DIR, 'cdk-apps', 'app'), integTestDir, context.output); const fixture = new TestFixture( integTestDir, stackNamePrefix, context.output, - context.aws); + context.aws, + context.randomString); let success = true; try { - const installationVersion = FRAMEWORK_VERSION; + const installationVersion = fixture.packages.requestedFrameworkVersion(); - if (MAJOR_VERSION === '1') { + if (fixture.packages.majorVersion() === '1') { await installNpmPackages(fixture, { '@aws-cdk/core': installationVersion, '@aws-cdk/aws-sns': installationVersion, @@ -134,7 +69,7 @@ export function withCdkApp(block: (context: throw e; } finally { if (process.env.INTEG_NO_CLEAN) { - process.stderr.write(`Left test directory in '${integTestDir}' ($INTEG_NO_CLEAN)\n`); + context.log(`Left test directory in '${integTestDir}' ($INTEG_NO_CLEAN)\n`); } else { await fixture.dispose(success); } @@ -149,7 +84,7 @@ export function withMonolithicCfnIncludeCdkApp(block: (co throw new Error('The UBERPACKAGE environment variable is required for running this test!'); } - const randy = randomString(); + const randy = context.randomString; const stackNamePrefix = `cdk-uber-cfn-include-${randy}`; const integTestDir = path.join(os.tmpdir(), `cdk-uber-cfn-include-${randy}`); @@ -157,18 +92,19 @@ export function withMonolithicCfnIncludeCdkApp(block: (co context.output.write(` Test directory: ${integTestDir}\n`); const awsClients = await AwsClients.default(context.output); - await cloneDirectory(path.join(__dirname, '..', 'uberpackage', 'cfn-include-app'), integTestDir, context.output); + await cloneDirectory(path.join(RESOURCES_DIR, 'cdk-apps', 'cfn-include-app'), integTestDir, context.output); const fixture = new TestFixture( integTestDir, stackNamePrefix, context.output, awsClients, + context.randomString, ); let success = true; try { await installNpmPackages(fixture, { - [uberPackage]: FRAMEWORK_VERSION ?? '*', + [uberPackage]: fixture.packages.requestedFrameworkVersion(), }); await block(fixture); @@ -177,7 +113,7 @@ export function withMonolithicCfnIncludeCdkApp(block: (co throw e; } finally { if (process.env.INTEG_NO_CLEAN) { - process.stderr.write(`Left test directory in '${integTestDir}' ($INTEG_NO_CLEAN)\n`); + context.log(`Left test directory in '${integTestDir}' ($INTEG_NO_CLEAN)`); } else { await fixture.dispose(success); } @@ -199,40 +135,6 @@ export function withDefaultFixture(block: (context: TestFixture) => Promise; - - /** - * Don't fail when exiting with an error - * - * @default false - */ - allowErrExit?: boolean; - - /** - * Whether to capture stderr - * - * @default true - */ - captureStderr?: boolean; - - /** - * Pass output here - */ - output?: NodeJS.WritableStream; - - /** - * Only return stderr. For example, this is used to validate - * that when CI=true, all logs are sent to stdout. - * - * @default false - */ - onlyStderr?: boolean; -} - export interface CdkCliOptions extends ShellOptions { options?: string[]; neverRequireApproval?: boolean; @@ -315,29 +217,27 @@ export interface CdkModernBootstrapCommandOptions extends CommonCdkBootstrapComm readonly customPermissionsBoundary?: string; } -export class TestFixture { - public readonly qualifier = randomString().slice(0, 10); +export class TestFixture extends ShellHelper { + public readonly qualifier = this.randomString.slice(0, 10); private readonly bucketsToDelete = new Array(); + public readonly packages: IPackageSource; constructor( public readonly integTestDir: string, public readonly stackNamePrefix: string, public readonly output: NodeJS.WritableStream, - public readonly aws: AwsClients) { + public readonly aws: AwsClients, + public readonly randomString: string) { + + super(integTestDir, output); + + this.packages = packageSourceInSubprocess(); } public log(s: string) { this.output.write(`${s}\n`); } - public async shell(command: string[], options: Omit = {}): Promise { - return shell(command, { - output: this.output, - cwd: this.integTestDir, - ...options, - }); - } - public async cdkDeploy(stackNames: string | string[], options: CdkCliOptions = {}) { stackNames = typeof stackNames === 'string' ? [stackNames] : stackNames; @@ -445,13 +345,15 @@ export class TestFixture { public async cdk(args: string[], options: CdkCliOptions = {}) { const verbose = options.verbose ?? true; + await this.packages.makeCliAvailable(); + return this.shell(['cdk', ...(verbose ? ['-v'] : []), ...args], { ...options, modEnv: { AWS_REGION: this.aws.region, AWS_DEFAULT_REGION: this.aws.region, STACK_NAME_PREFIX: this.stackNamePrefix, - PACKAGE_LAYOUT_VERSION: MAJOR_VERSION, + PACKAGE_LAYOUT_VERSION: this.packages.majorVersion(), ...options.modEnv, }, }); @@ -559,33 +461,6 @@ export class TestFixture { } } -/** - * Perform a one-time quick sanity check that the AWS clients has properly configured credentials - * - * If we don't do this, calls are going to fail and they'll be retried and everything will take - * forever before the user notices a simple misconfiguration. - * - * We can't check for the presence of environment variables since credentials could come from - * anywhere, so do simple account retrieval. - * - * Only do it once per process. - */ -async function sanityCheck(aws: AwsClients) { - if (sanityChecked === undefined) { - try { - await aws.account(); - sanityChecked = true; - } catch (e) { - sanityChecked = false; - throw new Error(`AWS credentials probably not configured, got error: ${e.message}`); - } - } - if (!sanityChecked) { - throw new Error('AWS credentials probably not configured, see previous error'); - } -} -let sanityChecked: boolean | undefined; - /** * Make sure that the given environment is bootstrapped * @@ -611,89 +486,10 @@ async function ensureBootstrapped(fixture: TestFixture) { ALREADY_BOOTSTRAPPED_IN_THIS_RUN.add(envSpecifier); } -/** - * A shell command that does what you want - * - * Is platform-aware, handles errors nicely. - */ -export async function shell(command: string[], options: ShellOptions = {}): Promise { - if (options.modEnv && options.env) { - throw new Error('Use either env or modEnv but not both'); - } - - options.output?.write(`💻 ${command.join(' ')}\n`); - - const env = options.env ?? (options.modEnv ? { ...process.env, ...options.modEnv } : undefined); - - const child = child_process.spawn(command[0], command.slice(1), { - ...options, - env, - // Need this for Windows where we want .cmd and .bat to be found as well. - shell: true, - stdio: ['ignore', 'pipe', 'pipe'], - }); - - return new Promise((resolve, reject) => { - const stdout = new Array(); - const stderr = new Array(); - - child.stdout!.on('data', chunk => { - options.output?.write(chunk); - stdout.push(chunk); - }); - - child.stderr!.on('data', chunk => { - options.output?.write(chunk); - if (options.captureStderr ?? true) { - stderr.push(chunk); - } - }); - - child.once('error', reject); - - child.once('close', code => { - const stderrOutput = Buffer.concat(stderr).toString('utf-8'); - const stdoutOutput = Buffer.concat(stdout).toString('utf-8'); - const output = (options.onlyStderr ? stderrOutput : stdoutOutput + stderrOutput).trim(); - if (code === 0 || options.allowErrExit) { - resolve(output); - } else { - reject(new Error(`'${command.join(' ')}' exited with error code ${code}. Output: \n${output}`)); - } - }); - }); -} - function defined(x: A): x is NonNullable { return x !== undefined; } -/** - * rm -rf reimplementation, don't want to depend on an NPM package for this - */ -export function rimraf(fsPath: string) { - try { - const isDir = fs.lstatSync(fsPath).isDirectory(); - - if (isDir) { - for (const file of fs.readdirSync(fsPath)) { - rimraf(path.join(fsPath, file)); - } - fs.rmdirSync(fsPath); - } else { - fs.unlinkSync(fsPath); - } - } catch (e) { - // We will survive ENOENT - if (e.code !== 'ENOENT') { throw e; } - } -} - -export function randomString() { - // Crazy - return Math.random().toString(36).replace(/[^a-z0-9]+/g, ''); -} - /** * Install the given NPM packages, identified by their names and versions * @@ -713,17 +509,6 @@ export function randomString() { * for Node's dependency lookup mechanism). */ export async function installNpmPackages(fixture: TestFixture, packages: Record) { - if (process.env.REPO_ROOT) { - const monoRepo = await findYarnPackages(process.env.REPO_ROOT); - - // Replace the install target with the physical location of this package - for (const key of Object.keys(packages)) { - if (key in monoRepo) { - packages[key] = monoRepo[key]; - } - } - } - fs.writeFileSync(path.join(fixture.integTestDir, 'package.json'), JSON.stringify({ name: 'cdk-integ-tests', private: true, @@ -732,25 +517,7 @@ export async function installNpmPackages(fixture: TestFixture, packages: Record< }, undefined, 2), { encoding: 'utf-8' }); // Now install that `package.json` using NPM7 - const npm7 = await installNpm7(); - await fixture.shell([npm7, 'install']); + await fixture.shell(['node', require.resolve('npm'), 'install']); } -/** - * Install NPM7 somewhere on the machine and return the path to its binary. - * - * - We install NPM7 explicitly so we don't have to depend on the environment. - * - The install is cached so we don't have to install it over and over again - * for every test. - */ -const installNpm7 = memoize0(async (): Promise => { - const installDir = path.join(os.tmpdir(), 'cdk-integ-npm7'); - await shell(['rm', '-rf', installDir]); - await shell(['mkdir', '-p', installDir]); - - await shell(['npm', 'install', 'npm@7'], { cwd: installDir }); - - return path.join(installDir, 'node_modules', '.bin', 'npm'); -}); - const ALREADY_BOOTSTRAPPED_IN_THIS_RUN = new Set(); diff --git a/packages/@aws-cdk-testing/cli-integ/lib/with-packages.ts b/packages/@aws-cdk-testing/cli-integ/lib/with-packages.ts new file mode 100644 index 0000000000000..f835fee3b333a --- /dev/null +++ b/packages/@aws-cdk-testing/cli-integ/lib/with-packages.ts @@ -0,0 +1,15 @@ +import { IPackageSource } from './package-sources/source'; +import { packageSourceInSubprocess } from './package-sources/subprocess'; + +export interface PackageContext { + readonly packages: IPackageSource; +} + +export function withPackages(block: (context: A & PackageContext) => Promise) { + return async (context: A) => { + return block({ + ...context, + packages: packageSourceInSubprocess(), + }); + }; +} \ No newline at end of file diff --git a/packages/aws-cdk/test/integ/helpers/sam.ts b/packages/@aws-cdk-testing/cli-integ/lib/with-sam.ts similarity index 88% rename from packages/aws-cdk/test/integ/helpers/sam.ts rename to packages/@aws-cdk-testing/cli-integ/lib/with-sam.ts index 13683f88caf2e..1016e4b113923 100644 --- a/packages/aws-cdk/test/integ/helpers/sam.ts +++ b/packages/@aws-cdk-testing/cli-integ/lib/with-sam.ts @@ -3,15 +3,11 @@ import * as fs from 'fs'; import * as os from 'os'; import * as path from 'path'; import axios from 'axios'; -import { AwsClients } from './aws'; -import { - AwsContext, - cloneDirectory, FRAMEWORK_VERSION, installNpmPackages, - MAJOR_VERSION, - randomString, rimraf, ShellOptions, - TestFixture, withAws, -} from './cdk'; -import { TestContext } from './test-helpers'; +import { TestContext } from './integ-test'; +import { RESOURCES_DIR } from './resources'; +import { ShellOptions, rimraf } from './shell'; +import { AwsContext, withAws } from './with-aws'; +import { cloneDirectory, installNpmPackages, TestFixture } from './with-cdk-app'; export interface ActionOutput { @@ -20,32 +16,32 @@ export interface ActionOutput { shellOutput?: string; } - /** * Higher order function to execute a block with a SAM Integration CDK app fixture */ export function withSamIntegrationCdkApp(block: (context: SamIntegrationTestFixture) => Promise) { return async (context: A) => { - const randy = randomString(); + const randy = context.randomString; const stackNamePrefix = `cdktest-${randy}`; const integTestDir = path.join(os.tmpdir(), `cdk-integ-${randy}`); - context.output.write(` Stack prefix: ${stackNamePrefix}\n`); - context.output.write(` Test directory: ${integTestDir}\n`); - context.output.write(` Region: ${context.aws.region}\n`); + context.log(` Stack prefix: ${stackNamePrefix}\n`); + context.log(` Test directory: ${integTestDir}\n`); + context.log(` Region: ${context.aws.region}\n`); - await cloneDirectory(path.join(__dirname, '..', 'cli', 'sam_cdk_integ_app'), integTestDir, context.output); + await cloneDirectory(path.join(RESOURCES_DIR, 'cdk-apps', 'sam_cdk_integ_app'), integTestDir, context.output); const fixture = new SamIntegrationTestFixture( integTestDir, stackNamePrefix, context.output, - context.aws); + context.aws, + context.randomString); let success = true; try { - const installationVersion = FRAMEWORK_VERSION; + const installationVersion = fixture.packages.requestedFrameworkVersion(); - if (MAJOR_VERSION === '1') { + if (fixture.packages.majorVersion() === '1') { await installNpmPackages(fixture, { '@aws-cdk/aws-iam': installationVersion, '@aws-cdk/aws-apigateway': installationVersion, @@ -58,7 +54,7 @@ export function withSamIntegrationCdkApp(blo 'constructs': '^3', }); } else { - const alphaInstallationVersion = installationVersion.includes('rc') ? installationVersion.replace('rc', 'alpha') : `${installationVersion}-alpha.0`; + const alphaInstallationVersion = fixture.packages.requestedAlphaVersion(); await installNpmPackages(fixture, { 'aws-cdk-lib': installationVersion, '@aws-cdk/aws-lambda-go-alpha': alphaInstallationVersion, @@ -76,7 +72,7 @@ export function withSamIntegrationCdkApp(blo throw e; } finally { if (process.env.INTEG_NO_CLEAN) { - process.stderr.write(`Left test directory in '${integTestDir}' ($INTEG_NO_CLEAN)\n`); + context.log(`Left test directory in '${integTestDir}' ($INTEG_NO_CLEAN)\n`); } else { await fixture.dispose(success); } @@ -121,14 +117,6 @@ export function withSamIntegrationFixture(block: (context: SamIntegrationTestFix } export class SamIntegrationTestFixture extends TestFixture { - constructor( - public readonly integTestDir: string, - public readonly stackNamePrefix: string, - public readonly output: NodeJS.WritableStream, - public readonly aws: AwsClients) { - super(integTestDir, stackNamePrefix, output, aws); - } - public async samShell(command: string[], filter?: string, action?: () => any, options: Omit = {}): Promise { return shellWithAction(command, filter, action, { output: this.output, diff --git a/packages/@aws-cdk-testing/cli-integ/lib/with-temporary-directory.ts b/packages/@aws-cdk-testing/cli-integ/lib/with-temporary-directory.ts new file mode 100644 index 0000000000000..0ff0336940114 --- /dev/null +++ b/packages/@aws-cdk-testing/cli-integ/lib/with-temporary-directory.ts @@ -0,0 +1,35 @@ +import * as fs from 'fs'; +import * as os from 'os'; +import * as path from 'path'; +import { TestContext } from './integ-test'; +import { rimraf } from './shell'; + +export interface TemporaryDirectoryContext { + readonly integTestDir: string; +} + +export function withTemporaryDirectory(block: (context: A & TemporaryDirectoryContext) => Promise) { + return async (context: A) => { + const integTestDir = path.join(os.tmpdir(), `cdk-integ-${context.randomString}`); + + fs.mkdirSync(integTestDir, { recursive: true }); + + try { + await block({ + ...context, + integTestDir, + }); + + // Clean up in case of success + if (process.env.SKIP_CLEANUP) { + context.log(`Left test directory in '${integTestDir}' ($SKIP_CLEANUP)\n`); + } else { + rimraf(integTestDir); + } + } catch (e) { + context.log(`Left test directory in '${integTestDir}'\n`); + throw e; + } + }; +} + diff --git a/packages/@aws-cdk-testing/cli-integ/lib/xpmutex.ts b/packages/@aws-cdk-testing/cli-integ/lib/xpmutex.ts new file mode 100644 index 0000000000000..6780215b5fdc6 --- /dev/null +++ b/packages/@aws-cdk-testing/cli-integ/lib/xpmutex.ts @@ -0,0 +1,218 @@ +import { watch, promises as fs, mkdirSync } from 'fs'; +import * as os from 'os'; +import * as path from 'path'; + +export class XpMutexPool { + public static fromDirectory(directory: string) { + mkdirSync(directory, { recursive: true }); + return new XpMutexPool(directory); + } + + public static fromName(name: string) { + return XpMutexPool.fromDirectory(path.join(os.tmpdir(), name)); + } + + private readonly queuedResolvers = new Set<() => void>(); + private watcher: ReturnType | undefined; + + private constructor(public readonly directory: string) { + this.startWatch(); + } + + public mutex(name: string) { + return new XpMutex(this, name); + } + + /** + * Await an unlock event + * + * (An unlock event is when a file in the directory gets deleted, with a tiny + * random sleep attached to it). + */ + public awaitUnlock(maxWaitMs?: number): Promise { + const wait = new Promise(ok => { + this.queuedResolvers.add(async () => { + await randomSleep(10); + ok(); + }); + }); + + if (maxWaitMs) { + return Promise.race([wait, sleep(maxWaitMs)]); + } else { + return wait; + } + } + + private startWatch() { + this.watcher = watch(this.directory); + (this.watcher as any).unref(); // @types doesn't know about this but it exists + this.watcher.on('change', async (eventType, fname) => { + // Only trigger on 'deletes'. + // After receiving the event, we check if the file exists. + // - If no: the file was deleted! Huzzah, this counts as a wakeup. + // - If yes: either the file was just created (in which case we don't need to wakeup) + // or the event was due to a delete but someone raced us to it and claimed the + // file already (in which case we also don't need to wake up). + if (eventType === 'rename' && !await fileExists(path.join(this.directory, fname.toString()))) { + this.notifyWaiters(); + } + }); + this.watcher.on('error', async (e) => { + // eslint-disable-next-line no-console + console.error(e); + await randomSleep(100); + this.startWatch(); + }); + } + + private notifyWaiters() { + for (const promise of this.queuedResolvers) { + promise(); + } + this.queuedResolvers.clear(); + } +} + +/** + * Cross-process mutex + * + * Uses the presence of a file on disk and `fs.watch` to represent the mutex + * and discover unlocks. + */ +export class XpMutex { + private readonly fileName: string; + + constructor(private readonly pool: XpMutexPool, public readonly mutexName: string) { + this.fileName = path.join(pool.directory, `${mutexName}.mutex`); + } + + /** + * Try to acquire the lock (may fail) + */ + public async tryAcquire(): Promise { + while (true) { + // Acquire lock by being the one to create the file + try { + return await this.writePidFile('wx'); // Fails if the file already exists + } catch (e) { + if (e.code !== 'EEXIST') { throw e; } + } + + // File already exists. Read the contents, see if it's an existent PID (if so, the lock is taken) + const ownerPid = await this.readPidFile(); + if (ownerPid === undefined) { + // File got deleted just now, maybe we can acquire it again + continue; + } + if (processExists(ownerPid)) { + return undefined; + } + + // If not, the lock is stale and will never be released anymore. We may + // delete it and acquire it anyway, but we may be racing someone else trying + // to do the same. Solve this as follows: + // - Try to acquire a lock that gives us permissions to declare the existing lock stale. + // - Sleep a small random period to reduce contention on this operation + await randomSleep(10); + const innerMux = new XpMutex(this.pool, `${this.mutexName}.${ownerPid}`); + const innerLock = await innerMux.tryAcquire(); + if (!innerLock) { + return undefined; + } + + // We may not release the 'inner lock' we used to acquire the rights to declare the other + // lock stale until we release the actual lock itself. If we did, other contenders might + // see it released while they're still in this fallback block and accidentally steal + // from a new legitimate owner. + return this.writePidFile('w', innerLock); // Force write lock file, attach inner lock as well + } + } + + /** + * Acquire the lock, waiting until we can + */ + public async acquire(): Promise { + while (true) { + // Start the wait here, so we don't miss the signal if it comes after + // we try but before we sleep. + // + // We also periodically retry anyway since we may have missed the delete + // signal due to unfortunate timing. + const wait = this.pool.awaitUnlock(5000); + + const lock = await this.acquire(); + if (lock) { + // Ignore the wait (count as handled) + wait.then(() => {}, () => {}); + return lock; + } + + await wait; + await randomSleep(100); + } + } + + private async readPidFile(): Promise { + const deadLine = Date.now() + 1000; + while (Date.now() < deadLine) { + let contents; + try { + contents = await fs.readFile(this.fileName, { encoding: 'utf-8' }); + } catch (e) { + if (e.code === 'ENOENT') { return undefined; } + throw e; + } + + // Retry until we've seen the full contents + if (contents.endsWith('.')) { return parseInt(contents.substring(0, contents.length - 1), 10); } + await sleep(10); + } + + throw new Error(`${this.fileName} was never completely written`); + } + + private async writePidFile(mode: string, additionalLock?: ILock): Promise { + const fd = await fs.open(this.fileName, mode); // May fail if the file already exists + await fd.write(`${process.pid}.`); // Period guards against partial reads + await fd.close(); + + return { + release: async () => { + await fs.unlink(this.fileName); + await additionalLock?.release(); + }, + }; + } +} + +export interface ILock { + release(): Promise; +} + +async function fileExists(fileName: string) { + try { + await fs.stat(fileName); + return true; + } catch (e) { + if (e.code === 'ENOENT') { return false; } + throw e; + } +} + +function processExists(pid: number) { + try { + process.kill(pid, 0); + return true; + } catch (e) { + return false; + } +} + +function sleep(ms: number): Promise { + return new Promise(ok => (setTimeout(ok, ms) as any).unref()); +} + +function randomSleep(ms: number) { + return sleep(Math.floor(Math.random() * ms)); +} diff --git a/packages/@aws-cdk-testing/cli-integ/package.json b/packages/@aws-cdk-testing/cli-integ/package.json new file mode 100644 index 0000000000000..e3699ba7d547e --- /dev/null +++ b/packages/@aws-cdk-testing/cli-integ/package.json @@ -0,0 +1,73 @@ +{ + "name": "@aws-cdk-testing/cli-integ", + "description": "Integration tests for the AWS CDK CLI", + "version": "0.0.0", + "bin": { + "run-suite": "bin/run-suite", + "download-and-run-old-tests": "bin/download-and-run-old-tests", + "query-github": "bin/query-github", + "apply-patches": "bin/apply-patches", + "test-root": "bin/test-root", + "stage-distribution": "bin/stage-distribution" + }, + "scripts": { + "build": "cdk-build", + "watch": "cdk-watch", + "lint": "cdk-lint", + "pkglint": "pkglint -f", + "test": "cdk-test", + "package": "cdk-package", + "build+test": "yarn build && yarn test", + "build+extract": "yarn build", + "build+test+package": "yarn build+test && yarn package", + "build+test+extract": "yarn build+test" + }, + "author": { + "name": "Amazon Web Services", + "url": "https://aws.amazon.com", + "organization": true + }, + "license": "Apache-2.0", + "devDependencies": { + "@types/node": "^14.18.31", + "@aws-cdk/cdk-build-tools": "0.0.0", + "@types/semver": "^7.3.12", + "@types/yargs": "^17.0.13", + "@types/fs-extra": "^9.0.0", + "@types/glob": "^7.2.0", + "@types/npm": "^7.19.0", + "@aws-cdk/pkglint": "0.0.0" + }, + "dependencies": { + "@octokit/rest": "^18.12.0", + "jest": "^27.5.1", + "aws-sdk": "^2.1211.0", + "axios": "^0.27.2", + "jest-junit": "^14.0.0", + "semver": "^7.3.8", + "ts-mock-imports": "^1.3.8", + "yargs": "^17.5.0", + "glob": "^7.2.3", + "p-queue": "^6.6.2", + "fs-extra": "^9.1.0", + "npm": "^7.19.0" + }, + "repository": { + "url": "https://github.com/aws/aws-cdk.git", + "type": "git", + "directory": "packages/@aws-cdk-testing/cli-integ" + }, + "keywords": [ + "aws", + "cdk" + ], + "homepage": "https://github.com/aws/aws-cdk", + "engines": { + "node": ">= 14.15.0" + }, + "stability": "experimental", + "maturity": "experimental", + "publishConfig": { + "tag": "latest" + } +} diff --git a/packages/aws-cdk/test/integ/cli/app/app.js b/packages/@aws-cdk-testing/cli-integ/resources/cdk-apps/app/app.js similarity index 100% rename from packages/aws-cdk/test/integ/cli/app/app.js rename to packages/@aws-cdk-testing/cli-integ/resources/cdk-apps/app/app.js diff --git a/packages/aws-cdk/test/integ/cli/app/cdk.json b/packages/@aws-cdk-testing/cli-integ/resources/cdk-apps/app/cdk.json similarity index 100% rename from packages/aws-cdk/test/integ/cli/app/cdk.json rename to packages/@aws-cdk-testing/cli-integ/resources/cdk-apps/app/cdk.json diff --git a/packages/aws-cdk/test/integ/cli/app/docker/Dockerfile b/packages/@aws-cdk-testing/cli-integ/resources/cdk-apps/app/docker/Dockerfile similarity index 100% rename from packages/aws-cdk/test/integ/cli/app/docker/Dockerfile rename to packages/@aws-cdk-testing/cli-integ/resources/cdk-apps/app/docker/Dockerfile diff --git a/packages/aws-cdk/test/integ/cli/app/docker/Dockerfile.Custom b/packages/@aws-cdk-testing/cli-integ/resources/cdk-apps/app/docker/Dockerfile.Custom similarity index 100% rename from packages/aws-cdk/test/integ/cli/app/docker/Dockerfile.Custom rename to packages/@aws-cdk-testing/cli-integ/resources/cdk-apps/app/docker/Dockerfile.Custom diff --git a/packages/aws-cdk/test/integ/cli/app/lambda/index.js b/packages/@aws-cdk-testing/cli-integ/resources/cdk-apps/app/lambda/index.js similarity index 100% rename from packages/aws-cdk/test/integ/cli/app/lambda/index.js rename to packages/@aws-cdk-testing/cli-integ/resources/cdk-apps/app/lambda/index.js diff --git a/packages/aws-cdk/test/integ/cli/app/lambda/response.json b/packages/@aws-cdk-testing/cli-integ/resources/cdk-apps/app/lambda/response.json similarity index 100% rename from packages/aws-cdk/test/integ/cli/app/lambda/response.json rename to packages/@aws-cdk-testing/cli-integ/resources/cdk-apps/app/lambda/response.json diff --git a/packages/aws-cdk/test/integ/cli/app/nested-stack.js b/packages/@aws-cdk-testing/cli-integ/resources/cdk-apps/app/nested-stack.js similarity index 100% rename from packages/aws-cdk/test/integ/cli/app/nested-stack.js rename to packages/@aws-cdk-testing/cli-integ/resources/cdk-apps/app/nested-stack.js diff --git a/packages/aws-cdk/test/integ/uberpackage/cfn-include-app/.gitignore b/packages/@aws-cdk-testing/cli-integ/resources/cdk-apps/cfn-include-app/.gitignore similarity index 100% rename from packages/aws-cdk/test/integ/uberpackage/cfn-include-app/.gitignore rename to packages/@aws-cdk-testing/cli-integ/resources/cdk-apps/cfn-include-app/.gitignore diff --git a/packages/aws-cdk/test/integ/uberpackage/cfn-include-app/cdk.json b/packages/@aws-cdk-testing/cli-integ/resources/cdk-apps/cfn-include-app/cdk.json similarity index 100% rename from packages/aws-cdk/test/integ/uberpackage/cfn-include-app/cdk.json rename to packages/@aws-cdk-testing/cli-integ/resources/cdk-apps/cfn-include-app/cdk.json diff --git a/packages/aws-cdk/test/integ/uberpackage/cfn-include-app/cfn-include-app.js b/packages/@aws-cdk-testing/cli-integ/resources/cdk-apps/cfn-include-app/cfn-include-app.js similarity index 100% rename from packages/aws-cdk/test/integ/uberpackage/cfn-include-app/cfn-include-app.js rename to packages/@aws-cdk-testing/cli-integ/resources/cdk-apps/cfn-include-app/cfn-include-app.js diff --git a/packages/aws-cdk/test/integ/uberpackage/cfn-include-app/example-template.json b/packages/@aws-cdk-testing/cli-integ/resources/cdk-apps/cfn-include-app/example-template.json similarity index 100% rename from packages/aws-cdk/test/integ/uberpackage/cfn-include-app/example-template.json rename to packages/@aws-cdk-testing/cli-integ/resources/cdk-apps/cfn-include-app/example-template.json diff --git a/packages/aws-cdk/test/integ/cli/sam_cdk_integ_app/bin/test-app.js b/packages/@aws-cdk-testing/cli-integ/resources/cdk-apps/sam_cdk_integ_app/bin/test-app.js similarity index 100% rename from packages/aws-cdk/test/integ/cli/sam_cdk_integ_app/bin/test-app.js rename to packages/@aws-cdk-testing/cli-integ/resources/cdk-apps/sam_cdk_integ_app/bin/test-app.js diff --git a/packages/aws-cdk/test/integ/cli/sam_cdk_integ_app/cdk.json b/packages/@aws-cdk-testing/cli-integ/resources/cdk-apps/sam_cdk_integ_app/cdk.json similarity index 100% rename from packages/aws-cdk/test/integ/cli/sam_cdk_integ_app/cdk.json rename to packages/@aws-cdk-testing/cli-integ/resources/cdk-apps/sam_cdk_integ_app/cdk.json diff --git a/packages/aws-cdk/test/integ/cli/sam_cdk_integ_app/lib/nested-stack.js b/packages/@aws-cdk-testing/cli-integ/resources/cdk-apps/sam_cdk_integ_app/lib/nested-stack.js similarity index 100% rename from packages/aws-cdk/test/integ/cli/sam_cdk_integ_app/lib/nested-stack.js rename to packages/@aws-cdk-testing/cli-integ/resources/cdk-apps/sam_cdk_integ_app/lib/nested-stack.js diff --git a/packages/aws-cdk/test/integ/cli/sam_cdk_integ_app/lib/test-stack.js b/packages/@aws-cdk-testing/cli-integ/resources/cdk-apps/sam_cdk_integ_app/lib/test-stack.js similarity index 100% rename from packages/aws-cdk/test/integ/cli/sam_cdk_integ_app/lib/test-stack.js rename to packages/@aws-cdk-testing/cli-integ/resources/cdk-apps/sam_cdk_integ_app/lib/test-stack.js diff --git a/packages/aws-cdk/test/integ/cli/sam_cdk_integ_app/src/docker/DockerImageFunctionConstruct/.no-packagejson-validator b/packages/@aws-cdk-testing/cli-integ/resources/cdk-apps/sam_cdk_integ_app/src/docker/DockerImageFunctionConstruct/.no-packagejson-validator similarity index 100% rename from packages/aws-cdk/test/integ/cli/sam_cdk_integ_app/src/docker/DockerImageFunctionConstruct/.no-packagejson-validator rename to packages/@aws-cdk-testing/cli-integ/resources/cdk-apps/sam_cdk_integ_app/src/docker/DockerImageFunctionConstruct/.no-packagejson-validator diff --git a/packages/aws-cdk/test/integ/cli/sam_cdk_integ_app/src/docker/DockerImageFunctionConstruct/Dockerfile b/packages/@aws-cdk-testing/cli-integ/resources/cdk-apps/sam_cdk_integ_app/src/docker/DockerImageFunctionConstruct/Dockerfile similarity index 100% rename from packages/aws-cdk/test/integ/cli/sam_cdk_integ_app/src/docker/DockerImageFunctionConstruct/Dockerfile rename to packages/@aws-cdk-testing/cli-integ/resources/cdk-apps/sam_cdk_integ_app/src/docker/DockerImageFunctionConstruct/Dockerfile diff --git a/packages/aws-cdk/test/integ/cli/sam_cdk_integ_app/src/docker/DockerImageFunctionConstruct/app.js b/packages/@aws-cdk-testing/cli-integ/resources/cdk-apps/sam_cdk_integ_app/src/docker/DockerImageFunctionConstruct/app.js similarity index 100% rename from packages/aws-cdk/test/integ/cli/sam_cdk_integ_app/src/docker/DockerImageFunctionConstruct/app.js rename to packages/@aws-cdk-testing/cli-integ/resources/cdk-apps/sam_cdk_integ_app/src/docker/DockerImageFunctionConstruct/app.js diff --git a/packages/aws-cdk/test/integ/cli/sam_cdk_integ_app/src/docker/DockerImageFunctionConstruct/package.json b/packages/@aws-cdk-testing/cli-integ/resources/cdk-apps/sam_cdk_integ_app/src/docker/DockerImageFunctionConstruct/package.json similarity index 100% rename from packages/aws-cdk/test/integ/cli/sam_cdk_integ_app/src/docker/DockerImageFunctionConstruct/package.json rename to packages/@aws-cdk-testing/cli-integ/resources/cdk-apps/sam_cdk_integ_app/src/docker/DockerImageFunctionConstruct/package.json diff --git a/packages/aws-cdk/test/integ/cli/sam_cdk_integ_app/src/go/GoFunctionConstruct/go.mod b/packages/@aws-cdk-testing/cli-integ/resources/cdk-apps/sam_cdk_integ_app/src/go/GoFunctionConstruct/go.mod similarity index 100% rename from packages/aws-cdk/test/integ/cli/sam_cdk_integ_app/src/go/GoFunctionConstruct/go.mod rename to packages/@aws-cdk-testing/cli-integ/resources/cdk-apps/sam_cdk_integ_app/src/go/GoFunctionConstruct/go.mod diff --git a/packages/aws-cdk/test/integ/cli/sam_cdk_integ_app/src/go/GoFunctionConstruct/go.sum b/packages/@aws-cdk-testing/cli-integ/resources/cdk-apps/sam_cdk_integ_app/src/go/GoFunctionConstruct/go.sum similarity index 100% rename from packages/aws-cdk/test/integ/cli/sam_cdk_integ_app/src/go/GoFunctionConstruct/go.sum rename to packages/@aws-cdk-testing/cli-integ/resources/cdk-apps/sam_cdk_integ_app/src/go/GoFunctionConstruct/go.sum diff --git a/packages/aws-cdk/test/integ/cli/sam_cdk_integ_app/src/go/GoFunctionConstruct/main.go b/packages/@aws-cdk-testing/cli-integ/resources/cdk-apps/sam_cdk_integ_app/src/go/GoFunctionConstruct/main.go similarity index 100% rename from packages/aws-cdk/test/integ/cli/sam_cdk_integ_app/src/go/GoFunctionConstruct/main.go rename to packages/@aws-cdk-testing/cli-integ/resources/cdk-apps/sam_cdk_integ_app/src/go/GoFunctionConstruct/main.go diff --git a/packages/aws-cdk/test/integ/cli/sam_cdk_integ_app/src/nodejs/NodeJsFunctionConstruct/.no-packagejson-validator b/packages/@aws-cdk-testing/cli-integ/resources/cdk-apps/sam_cdk_integ_app/src/nodejs/NodeJsFunctionConstruct/.no-packagejson-validator similarity index 100% rename from packages/aws-cdk/test/integ/cli/sam_cdk_integ_app/src/nodejs/NodeJsFunctionConstruct/.no-packagejson-validator rename to packages/@aws-cdk-testing/cli-integ/resources/cdk-apps/sam_cdk_integ_app/src/nodejs/NodeJsFunctionConstruct/.no-packagejson-validator diff --git a/packages/aws-cdk/test/integ/cli/sam_cdk_integ_app/src/nodejs/NodeJsFunctionConstruct/app.ts b/packages/@aws-cdk-testing/cli-integ/resources/cdk-apps/sam_cdk_integ_app/src/nodejs/NodeJsFunctionConstruct/app.ts similarity index 100% rename from packages/aws-cdk/test/integ/cli/sam_cdk_integ_app/src/nodejs/NodeJsFunctionConstruct/app.ts rename to packages/@aws-cdk-testing/cli-integ/resources/cdk-apps/sam_cdk_integ_app/src/nodejs/NodeJsFunctionConstruct/app.ts diff --git a/packages/aws-cdk/test/integ/cli/sam_cdk_integ_app/src/nodejs/NodeJsFunctionConstruct/package-lock.json b/packages/@aws-cdk-testing/cli-integ/resources/cdk-apps/sam_cdk_integ_app/src/nodejs/NodeJsFunctionConstruct/package-lock.json similarity index 100% rename from packages/aws-cdk/test/integ/cli/sam_cdk_integ_app/src/nodejs/NodeJsFunctionConstruct/package-lock.json rename to packages/@aws-cdk-testing/cli-integ/resources/cdk-apps/sam_cdk_integ_app/src/nodejs/NodeJsFunctionConstruct/package-lock.json diff --git a/packages/aws-cdk/test/integ/cli/sam_cdk_integ_app/src/nodejs/NodeJsFunctionConstruct/package.json b/packages/@aws-cdk-testing/cli-integ/resources/cdk-apps/sam_cdk_integ_app/src/nodejs/NodeJsFunctionConstruct/package.json similarity index 100% rename from packages/aws-cdk/test/integ/cli/sam_cdk_integ_app/src/nodejs/NodeJsFunctionConstruct/package.json rename to packages/@aws-cdk-testing/cli-integ/resources/cdk-apps/sam_cdk_integ_app/src/nodejs/NodeJsFunctionConstruct/package.json diff --git a/packages/aws-cdk/test/integ/cli/sam_cdk_integ_app/src/python/Function/app.py b/packages/@aws-cdk-testing/cli-integ/resources/cdk-apps/sam_cdk_integ_app/src/python/Function/app.py similarity index 100% rename from packages/aws-cdk/test/integ/cli/sam_cdk_integ_app/src/python/Function/app.py rename to packages/@aws-cdk-testing/cli-integ/resources/cdk-apps/sam_cdk_integ_app/src/python/Function/app.py diff --git a/packages/aws-cdk/test/integ/cli/sam_cdk_integ_app/src/python/Function/requirements.txt b/packages/@aws-cdk-testing/cli-integ/resources/cdk-apps/sam_cdk_integ_app/src/python/Function/requirements.txt similarity index 100% rename from packages/aws-cdk/test/integ/cli/sam_cdk_integ_app/src/python/Function/requirements.txt rename to packages/@aws-cdk-testing/cli-integ/resources/cdk-apps/sam_cdk_integ_app/src/python/Function/requirements.txt diff --git a/packages/aws-cdk/test/integ/cli/sam_cdk_integ_app/src/python/Layer/layer_version_dependency.py b/packages/@aws-cdk-testing/cli-integ/resources/cdk-apps/sam_cdk_integ_app/src/python/Layer/layer_version_dependency.py similarity index 100% rename from packages/aws-cdk/test/integ/cli/sam_cdk_integ_app/src/python/Layer/layer_version_dependency.py rename to packages/@aws-cdk-testing/cli-integ/resources/cdk-apps/sam_cdk_integ_app/src/python/Layer/layer_version_dependency.py diff --git a/packages/aws-cdk/test/integ/cli/sam_cdk_integ_app/src/python/Layer/requirements.txt b/packages/@aws-cdk-testing/cli-integ/resources/cdk-apps/sam_cdk_integ_app/src/python/Layer/requirements.txt similarity index 100% rename from packages/aws-cdk/test/integ/cli/sam_cdk_integ_app/src/python/Layer/requirements.txt rename to packages/@aws-cdk-testing/cli-integ/resources/cdk-apps/sam_cdk_integ_app/src/python/Layer/requirements.txt diff --git a/packages/aws-cdk/test/integ/cli/sam_cdk_integ_app/src/rest-api-definition.yaml b/packages/@aws-cdk-testing/cli-integ/resources/cdk-apps/sam_cdk_integ_app/src/rest-api-definition.yaml similarity index 100% rename from packages/aws-cdk/test/integ/cli/sam_cdk_integ_app/src/rest-api-definition.yaml rename to packages/@aws-cdk-testing/cli-integ/resources/cdk-apps/sam_cdk_integ_app/src/rest-api-definition.yaml diff --git a/packages/aws-cdk/test/integ/cli-regression-patches/v1.119.0/NOTES.md b/packages/@aws-cdk-testing/cli-integ/resources/cli-regression-patches/v1.119.0/NOTES.md similarity index 100% rename from packages/aws-cdk/test/integ/cli-regression-patches/v1.119.0/NOTES.md rename to packages/@aws-cdk-testing/cli-integ/resources/cli-regression-patches/v1.119.0/NOTES.md diff --git a/packages/aws-cdk/test/integ/cli-regression-patches/v1.119.0/cli.integtest.js b/packages/@aws-cdk-testing/cli-integ/resources/cli-regression-patches/v1.119.0/cli.integtest.js similarity index 100% rename from packages/aws-cdk/test/integ/cli-regression-patches/v1.119.0/cli.integtest.js rename to packages/@aws-cdk-testing/cli-integ/resources/cli-regression-patches/v1.119.0/cli.integtest.js diff --git a/packages/aws-cdk/test/integ/cli-regression-patches/v1.130.0/NOTES.md b/packages/@aws-cdk-testing/cli-integ/resources/cli-regression-patches/v1.130.0/NOTES.md similarity index 100% rename from packages/aws-cdk/test/integ/cli-regression-patches/v1.130.0/NOTES.md rename to packages/@aws-cdk-testing/cli-integ/resources/cli-regression-patches/v1.130.0/NOTES.md diff --git a/packages/aws-cdk/test/integ/cli-regression-patches/v1.130.0/app/app.js b/packages/@aws-cdk-testing/cli-integ/resources/cli-regression-patches/v1.130.0/app/app.js similarity index 100% rename from packages/aws-cdk/test/integ/cli-regression-patches/v1.130.0/app/app.js rename to packages/@aws-cdk-testing/cli-integ/resources/cli-regression-patches/v1.130.0/app/app.js diff --git a/packages/aws-cdk/test/integ/cli-regression-patches/v1.130.0/bootstrapping.integtest.js b/packages/@aws-cdk-testing/cli-integ/resources/cli-regression-patches/v1.130.0/bootstrapping.integtest.js similarity index 100% rename from packages/aws-cdk/test/integ/cli-regression-patches/v1.130.0/bootstrapping.integtest.js rename to packages/@aws-cdk-testing/cli-integ/resources/cli-regression-patches/v1.130.0/bootstrapping.integtest.js diff --git a/packages/aws-cdk/test/integ/cli-regression-patches/v1.44.0/NOTES.md b/packages/@aws-cdk-testing/cli-integ/resources/cli-regression-patches/v1.44.0/NOTES.md similarity index 100% rename from packages/aws-cdk/test/integ/cli-regression-patches/v1.44.0/NOTES.md rename to packages/@aws-cdk-testing/cli-integ/resources/cli-regression-patches/v1.44.0/NOTES.md diff --git a/packages/aws-cdk/test/integ/cli-regression-patches/v1.44.0/bootstrapping.integtest.js b/packages/@aws-cdk-testing/cli-integ/resources/cli-regression-patches/v1.44.0/bootstrapping.integtest.js similarity index 100% rename from packages/aws-cdk/test/integ/cli-regression-patches/v1.44.0/bootstrapping.integtest.js rename to packages/@aws-cdk-testing/cli-integ/resources/cli-regression-patches/v1.44.0/bootstrapping.integtest.js diff --git a/packages/aws-cdk/test/integ/cli-regression-patches/v1.44.0/test.sh b/packages/@aws-cdk-testing/cli-integ/resources/cli-regression-patches/v1.44.0/test.sh similarity index 100% rename from packages/aws-cdk/test/integ/cli-regression-patches/v1.44.0/test.sh rename to packages/@aws-cdk-testing/cli-integ/resources/cli-regression-patches/v1.44.0/test.sh diff --git a/packages/aws-cdk/test/integ/cli-regression-patches/v1.61.1/NOTES.md b/packages/@aws-cdk-testing/cli-integ/resources/cli-regression-patches/v1.61.1/NOTES.md similarity index 100% rename from packages/aws-cdk/test/integ/cli-regression-patches/v1.61.1/NOTES.md rename to packages/@aws-cdk-testing/cli-integ/resources/cli-regression-patches/v1.61.1/NOTES.md diff --git a/packages/aws-cdk/test/integ/cli-regression-patches/v1.61.1/skip-tests.txt b/packages/@aws-cdk-testing/cli-integ/resources/cli-regression-patches/v1.61.1/skip-tests.txt similarity index 100% rename from packages/aws-cdk/test/integ/cli-regression-patches/v1.61.1/skip-tests.txt rename to packages/@aws-cdk-testing/cli-integ/resources/cli-regression-patches/v1.61.1/skip-tests.txt diff --git a/packages/aws-cdk/test/integ/cli-regression-patches/v1.62.0/NOTES.md b/packages/@aws-cdk-testing/cli-integ/resources/cli-regression-patches/v1.62.0/NOTES.md similarity index 100% rename from packages/aws-cdk/test/integ/cli-regression-patches/v1.62.0/NOTES.md rename to packages/@aws-cdk-testing/cli-integ/resources/cli-regression-patches/v1.62.0/NOTES.md diff --git a/packages/aws-cdk/test/integ/cli-regression-patches/v1.62.0/aws-helpers.js b/packages/@aws-cdk-testing/cli-integ/resources/cli-regression-patches/v1.62.0/aws-helpers.js similarity index 100% rename from packages/aws-cdk/test/integ/cli-regression-patches/v1.62.0/aws-helpers.js rename to packages/@aws-cdk-testing/cli-integ/resources/cli-regression-patches/v1.62.0/aws-helpers.js diff --git a/packages/aws-cdk/test/integ/cli-regression-patches/v1.63.0/NOTES.md b/packages/@aws-cdk-testing/cli-integ/resources/cli-regression-patches/v1.63.0/NOTES.md similarity index 100% rename from packages/aws-cdk/test/integ/cli-regression-patches/v1.63.0/NOTES.md rename to packages/@aws-cdk-testing/cli-integ/resources/cli-regression-patches/v1.63.0/NOTES.md diff --git a/packages/aws-cdk/test/integ/cli-regression-patches/v1.63.0/skip-tests.txt b/packages/@aws-cdk-testing/cli-integ/resources/cli-regression-patches/v1.63.0/skip-tests.txt similarity index 100% rename from packages/aws-cdk/test/integ/cli-regression-patches/v1.63.0/skip-tests.txt rename to packages/@aws-cdk-testing/cli-integ/resources/cli-regression-patches/v1.63.0/skip-tests.txt diff --git a/packages/aws-cdk/test/integ/cli-regression-patches/v1.64.0/NOTES.md b/packages/@aws-cdk-testing/cli-integ/resources/cli-regression-patches/v1.64.0/NOTES.md similarity index 100% rename from packages/aws-cdk/test/integ/cli-regression-patches/v1.64.0/NOTES.md rename to packages/@aws-cdk-testing/cli-integ/resources/cli-regression-patches/v1.64.0/NOTES.md diff --git a/packages/aws-cdk/test/integ/cli-regression-patches/v1.64.0/cdk-helpers.js b/packages/@aws-cdk-testing/cli-integ/resources/cli-regression-patches/v1.64.0/cdk-helpers.js similarity index 100% rename from packages/aws-cdk/test/integ/cli-regression-patches/v1.64.0/cdk-helpers.js rename to packages/@aws-cdk-testing/cli-integ/resources/cli-regression-patches/v1.64.0/cdk-helpers.js diff --git a/packages/aws-cdk/test/integ/cli-regression-patches/v1.64.0/cli.integtest.js b/packages/@aws-cdk-testing/cli-integ/resources/cli-regression-patches/v1.64.0/cli.integtest.js similarity index 100% rename from packages/aws-cdk/test/integ/cli-regression-patches/v1.64.0/cli.integtest.js rename to packages/@aws-cdk-testing/cli-integ/resources/cli-regression-patches/v1.64.0/cli.integtest.js diff --git a/packages/aws-cdk/test/integ/cli-regression-patches/v1.64.1/NOTES.md b/packages/@aws-cdk-testing/cli-integ/resources/cli-regression-patches/v1.64.1/NOTES.md similarity index 100% rename from packages/aws-cdk/test/integ/cli-regression-patches/v1.64.1/NOTES.md rename to packages/@aws-cdk-testing/cli-integ/resources/cli-regression-patches/v1.64.1/NOTES.md diff --git a/packages/aws-cdk/test/integ/cli-regression-patches/v1.64.1/cdk-helpers.js b/packages/@aws-cdk-testing/cli-integ/resources/cli-regression-patches/v1.64.1/cdk-helpers.js similarity index 100% rename from packages/aws-cdk/test/integ/cli-regression-patches/v1.64.1/cdk-helpers.js rename to packages/@aws-cdk-testing/cli-integ/resources/cli-regression-patches/v1.64.1/cdk-helpers.js diff --git a/packages/aws-cdk/test/integ/cli-regression-patches/v1.64.1/cli.integtest.js b/packages/@aws-cdk-testing/cli-integ/resources/cli-regression-patches/v1.64.1/cli.integtest.js similarity index 100% rename from packages/aws-cdk/test/integ/cli-regression-patches/v1.64.1/cli.integtest.js rename to packages/@aws-cdk-testing/cli-integ/resources/cli-regression-patches/v1.64.1/cli.integtest.js diff --git a/packages/aws-cdk/test/integ/cli-regression-patches/v1.67.0/NOTES.md b/packages/@aws-cdk-testing/cli-integ/resources/cli-regression-patches/v1.67.0/NOTES.md similarity index 100% rename from packages/aws-cdk/test/integ/cli-regression-patches/v1.67.0/NOTES.md rename to packages/@aws-cdk-testing/cli-integ/resources/cli-regression-patches/v1.67.0/NOTES.md diff --git a/packages/aws-cdk/test/integ/cli-regression-patches/v1.67.0/cdk-helpers.js b/packages/@aws-cdk-testing/cli-integ/resources/cli-regression-patches/v1.67.0/cdk-helpers.js similarity index 100% rename from packages/aws-cdk/test/integ/cli-regression-patches/v1.67.0/cdk-helpers.js rename to packages/@aws-cdk-testing/cli-integ/resources/cli-regression-patches/v1.67.0/cdk-helpers.js diff --git a/packages/aws-cdk/test/integ/cli/cloud-assemblies/0.36.0/InitStack.template.json b/packages/@aws-cdk-testing/cli-integ/resources/cloud-assemblies/0.36.0/InitStack.template.json similarity index 100% rename from packages/aws-cdk/test/integ/cli/cloud-assemblies/0.36.0/InitStack.template.json rename to packages/@aws-cdk-testing/cli-integ/resources/cloud-assemblies/0.36.0/InitStack.template.json diff --git a/packages/aws-cdk/test/integ/cli/cloud-assemblies/0.36.0/cdk.out b/packages/@aws-cdk-testing/cli-integ/resources/cloud-assemblies/0.36.0/cdk.out similarity index 100% rename from packages/aws-cdk/test/integ/cli/cloud-assemblies/0.36.0/cdk.out rename to packages/@aws-cdk-testing/cli-integ/resources/cloud-assemblies/0.36.0/cdk.out diff --git a/packages/aws-cdk/test/integ/cli/cloud-assemblies/0.36.0/manifest.json b/packages/@aws-cdk-testing/cli-integ/resources/cloud-assemblies/0.36.0/manifest.json similarity index 100% rename from packages/aws-cdk/test/integ/cli/cloud-assemblies/0.36.0/manifest.json rename to packages/@aws-cdk-testing/cli-integ/resources/cloud-assemblies/0.36.0/manifest.json diff --git a/packages/aws-cdk/test/integ/cli/cloud-assemblies/1.10.0-lookup-default-vpc/InitStack.template.json b/packages/@aws-cdk-testing/cli-integ/resources/cloud-assemblies/1.10.0-lookup-default-vpc/InitStack.template.json similarity index 100% rename from packages/aws-cdk/test/integ/cli/cloud-assemblies/1.10.0-lookup-default-vpc/InitStack.template.json rename to packages/@aws-cdk-testing/cli-integ/resources/cloud-assemblies/1.10.0-lookup-default-vpc/InitStack.template.json diff --git a/packages/aws-cdk/test/integ/cli/cloud-assemblies/1.10.0-lookup-default-vpc/cdk.out b/packages/@aws-cdk-testing/cli-integ/resources/cloud-assemblies/1.10.0-lookup-default-vpc/cdk.out similarity index 100% rename from packages/aws-cdk/test/integ/cli/cloud-assemblies/1.10.0-lookup-default-vpc/cdk.out rename to packages/@aws-cdk-testing/cli-integ/resources/cloud-assemblies/1.10.0-lookup-default-vpc/cdk.out diff --git a/packages/aws-cdk/test/integ/cli/cloud-assemblies/1.10.0-lookup-default-vpc/manifest.json.js b/packages/@aws-cdk-testing/cli-integ/resources/cloud-assemblies/1.10.0-lookup-default-vpc/manifest.json.js similarity index 100% rename from packages/aws-cdk/test/integ/cli/cloud-assemblies/1.10.0-lookup-default-vpc/manifest.json.js rename to packages/@aws-cdk-testing/cli-integ/resources/cloud-assemblies/1.10.0-lookup-default-vpc/manifest.json.js diff --git a/packages/aws-cdk/test/integ/cli/cloud-assemblies/1.10.0-request-azs/InitStack.template.json b/packages/@aws-cdk-testing/cli-integ/resources/cloud-assemblies/1.10.0-request-azs/InitStack.template.json similarity index 100% rename from packages/aws-cdk/test/integ/cli/cloud-assemblies/1.10.0-request-azs/InitStack.template.json rename to packages/@aws-cdk-testing/cli-integ/resources/cloud-assemblies/1.10.0-request-azs/InitStack.template.json diff --git a/packages/aws-cdk/test/integ/cli/cloud-assemblies/1.10.0-request-azs/cdk.out b/packages/@aws-cdk-testing/cli-integ/resources/cloud-assemblies/1.10.0-request-azs/cdk.out similarity index 100% rename from packages/aws-cdk/test/integ/cli/cloud-assemblies/1.10.0-request-azs/cdk.out rename to packages/@aws-cdk-testing/cli-integ/resources/cloud-assemblies/1.10.0-request-azs/cdk.out diff --git a/packages/aws-cdk/test/integ/cli/cloud-assemblies/1.10.0-request-azs/manifest.json.js b/packages/@aws-cdk-testing/cli-integ/resources/cloud-assemblies/1.10.0-request-azs/manifest.json.js similarity index 100% rename from packages/aws-cdk/test/integ/cli/cloud-assemblies/1.10.0-request-azs/manifest.json.js rename to packages/@aws-cdk-testing/cli-integ/resources/cloud-assemblies/1.10.0-request-azs/manifest.json.js diff --git a/packages/@aws-cdk-testing/cli-integ/resources/integ.jest.config.js b/packages/@aws-cdk-testing/cli-integ/resources/integ.jest.config.js new file mode 100644 index 0000000000000..e72d95ff70da6 --- /dev/null +++ b/packages/@aws-cdk-testing/cli-integ/resources/integ.jest.config.js @@ -0,0 +1,25 @@ +const path = require('path'); + +const rootDir = path.resolve(__dirname, '..', 'tests', process.env.TEST_SUITE_NAME); + +if (rootDir.includes('node_modules')) { + // Jest < 28 under no circumstances supports loading test if there's node_modules anywhere in the path, + // and Jest >= 28 requires a newer TypeScript version than the one we support. + throw new Error(`This package must not be 'npm install'ed (found node_modules in dir: ${rootDir})`); +} + +module.exports = { + rootDir, + testMatch: [`**/*.integtest.js`], + moduleFileExtensions: ["js"], + + testEnvironment: "node", + testTimeout: 300000, + + // Affects test.concurrent(), these are self-limiting anyway + maxConcurrency: 10, + reporters: [ + "default", + [ "jest-junit", { suiteName: "jest tests", outputDirectory: "coverage" } ] + ] +}; diff --git a/packages/aws-cdk/test/integ/helpers/skip-tests.txt b/packages/@aws-cdk-testing/cli-integ/skip-tests.txt similarity index 100% rename from packages/aws-cdk/test/integ/helpers/skip-tests.txt rename to packages/@aws-cdk-testing/cli-integ/skip-tests.txt diff --git a/packages/@aws-cdk-testing/cli-integ/test/resource-pool.test.ts b/packages/@aws-cdk-testing/cli-integ/test/resource-pool.test.ts new file mode 100644 index 0000000000000..18f153778bb5f --- /dev/null +++ b/packages/@aws-cdk-testing/cli-integ/test/resource-pool.test.ts @@ -0,0 +1,84 @@ +import { sleep } from '../lib'; +import { ResourcePool } from '../lib/resource-pool'; + +const POOL_NAME = 'resource-pool.test'; + +test('take and dispose', async () => { + const pool = ResourcePool.withResources(POOL_NAME, ['a']); + const take1 = pool.take(); + const take2 = pool.take(); + + let released = false; + + const lease1 = await take1; + // awaiting 'take2' would now block but we add an async + // handler to it to flip a boolean to see when it gets activated. + void(take2.then(() => released = true)); + + expect(lease1.value).toEqual('a'); + await waitTick(); + expect(released).toEqual(false); + + await lease1.dispose(); + await waitTick(); // This works because setImmediate is scheduled in LIFO order + + const lease2 = await take2; + await lease2.dispose(); + expect(released).toEqual(true); +}); + +test('double dispose throws', async () => { + const pool = ResourcePool.withResources(POOL_NAME, ['a']); + const lease = await pool.take(); + + await lease.dispose(); + expect(() => lease.dispose()).toThrow(); +}); + +test('somewhat balance', async () => { + const counters = { + a: 0, + b: 0, + c: 0, + d: 0, + e: 0, + }; + const N = 100; + let maxConcurrency = 0; + let concurrency = 0; + + const keys = Object.keys(counters) as Array ; + const pool = ResourcePool.withResources(POOL_NAME, keys); + await Promise.all(Array.from(range(N)).map(() => + pool.using(async (x) => { + counters[x] += 1; + concurrency += 1; + maxConcurrency = Math.max(maxConcurrency, concurrency); + try { + await sleep(10); + } finally { + concurrency -= 1; + } + }), + )); + + // Regardless of which resource(s) we used, the total count should add up to N + const sum = Object.values(counters).reduce((a, b) => a + b, 0); + expect(sum).toEqual(N); + // There was concurrency + expect(maxConcurrency).toBeGreaterThan(2); + // All counters are used + for (const count of Object.values(counters)) { + expect(count).toBeGreaterThan(0); + } +}); + +function waitTick() { + return new Promise(setImmediate); +} + +function* range(n: number) { + for (let i = 0; i < n; i++) { + yield i; + } +} \ No newline at end of file diff --git a/packages/aws-cdk/test/integ/cli/README.md b/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/README.md similarity index 99% rename from packages/aws-cdk/test/integ/cli/README.md rename to packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/README.md index 0a174245e8504..a26666bff695e 100644 --- a/packages/aws-cdk/test/integ/cli/README.md +++ b/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/README.md @@ -17,7 +17,6 @@ Running against a failing dist build: .../CMkBR4V$ package/test/integ/run-against-dist package/test/integ/cli/test.sh ``` - ## Adding tests Even though tests are now written in TypeScript, this does not diff --git a/packages/aws-cdk/test/integ/cli/bootstrapping.integtest.ts b/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/bootstrapping.integtest.ts similarity index 98% rename from packages/aws-cdk/test/integ/cli/bootstrapping.integtest.ts rename to packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/bootstrapping.integtest.ts index ba23674212afa..2b0547aeb2479 100644 --- a/packages/aws-cdk/test/integ/cli/bootstrapping.integtest.ts +++ b/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/bootstrapping.integtest.ts @@ -1,12 +1,11 @@ import * as fs from 'fs'; import * as path from 'path'; -import { randomString, withDefaultFixture } from '../helpers/cdk'; -import { integTest } from '../helpers/test-helpers'; +import { integTest, randomString, withDefaultFixture } from '../../lib'; const timeout = process.env.CODEBUILD_BUILD_ID ? // if the process is running in CodeBuild 3_600_000 : // 1 hour 600_000; // 10 minutes -jest.setTimeout(timeout); +jest.setTimeout(timeout); // Includes the time to acquire locks process.stdout.write(`bootstrapping.integtest.ts: Setting jest time out to ${timeout} ms`); integTest('can bootstrap without execution', withDefaultFixture(async (fixture) => { diff --git a/packages/aws-cdk/test/integ/cli/cli.integtest.ts b/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/cli.integtest.ts similarity index 93% rename from packages/aws-cdk/test/integ/cli/cli.integtest.ts rename to packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/cli.integtest.ts index bac932e23ea4b..6d868b8f1db33 100644 --- a/packages/aws-cdk/test/integ/cli/cli.integtest.ts +++ b/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/cli.integtest.ts @@ -1,12 +1,9 @@ import { promises as fs, existsSync } from 'fs'; import * as os from 'os'; import * as path from 'path'; -import { retry, sleep } from '../helpers/aws'; -import { cloneDirectory, MAJOR_VERSION, shell, withDefaultFixture } from '../helpers/cdk'; -import { randomInteger, withSamIntegrationFixture } from '../helpers/sam'; -import { integTest } from '../helpers/test-helpers'; +import { integTest, cloneDirectory, shell, withDefaultFixture, retry, sleep, randomInteger, withSamIntegrationFixture, RESOURCES_DIR } from '../../lib'; -jest.setTimeout(600_000); +jest.setTimeout(60 * 60_000); // Includes the time to acquire locks describe('ci', () => { integTest('output to stderr', withDefaultFixture(async (fixture) => { @@ -486,86 +483,88 @@ integTest('deploy with notification ARN', withDefaultFixture(async (fixture) => } })); -if (MAJOR_VERSION === '1') { - // NOTE: this doesn't currently work with modern-style synthesis, as the bootstrap - // role by default will not have permission to iam:PassRole the created role. - integTest('deploy with role', withDefaultFixture(async (fixture) => { - const roleName = `${fixture.stackNamePrefix}-test-role`; - - await deleteRole(); +// NOTE: this doesn't currently work with modern-style synthesis, as the bootstrap +// role by default will not have permission to iam:PassRole the created role. +integTest('deploy with role', withDefaultFixture(async (fixture) => { + if (fixture.packages.majorVersion() !== '1') { + return; // Nothing to do + } - const createResponse = await fixture.aws.iam('createRole', { + const roleName = `${fixture.stackNamePrefix}-test-role`; + + await deleteRole(); + + const createResponse = await fixture.aws.iam('createRole', { + RoleName: roleName, + AssumeRolePolicyDocument: JSON.stringify({ + Version: '2012-10-17', + Statement: [{ + Action: 'sts:AssumeRole', + Principal: { Service: 'cloudformation.amazonaws.com' }, + Effect: 'Allow', + }, { + Action: 'sts:AssumeRole', + Principal: { AWS: (await fixture.aws.sts('getCallerIdentity', {})).Arn }, + Effect: 'Allow', + }], + }), + }); + const roleArn = createResponse.Role.Arn; + try { + await fixture.aws.iam('putRolePolicy', { RoleName: roleName, - AssumeRolePolicyDocument: JSON.stringify({ + PolicyName: 'DefaultPolicy', + PolicyDocument: JSON.stringify({ Version: '2012-10-17', Statement: [{ - Action: 'sts:AssumeRole', - Principal: { Service: 'cloudformation.amazonaws.com' }, - Effect: 'Allow', - }, { - Action: 'sts:AssumeRole', - Principal: { AWS: (await fixture.aws.sts('getCallerIdentity', {})).Arn }, + Action: '*', + Resource: '*', Effect: 'Allow', }], }), }); - const roleArn = createResponse.Role.Arn; - try { - await fixture.aws.iam('putRolePolicy', { - RoleName: roleName, - PolicyName: 'DefaultPolicy', - PolicyDocument: JSON.stringify({ - Version: '2012-10-17', - Statement: [{ - Action: '*', - Resource: '*', - Effect: 'Allow', - }], - }), - }); - await retry(fixture.output, 'Trying to assume fresh role', retry.forSeconds(300), async () => { - await fixture.aws.sts('assumeRole', { - RoleArn: roleArn, - RoleSessionName: 'testing', - }); + await retry(fixture.output, 'Trying to assume fresh role', retry.forSeconds(300), async () => { + await fixture.aws.sts('assumeRole', { + RoleArn: roleArn, + RoleSessionName: 'testing', }); + }); - // In principle, the role has replicated from 'us-east-1' to wherever we're testing. - // Give it a little more sleep to make sure CloudFormation is not hitting a box - // that doesn't have it yet. - await sleep(5000); + // In principle, the role has replicated from 'us-east-1' to wherever we're testing. + // Give it a little more sleep to make sure CloudFormation is not hitting a box + // that doesn't have it yet. + await sleep(5000); - await fixture.cdkDeploy('test-2', { - options: ['--role-arn', roleArn], - }); + await fixture.cdkDeploy('test-2', { + options: ['--role-arn', roleArn], + }); - // Immediately delete the stack again before we delete the role. - // - // Since roles are sticky, if we delete the role before the stack, subsequent DeleteStack - // operations will fail when CloudFormation tries to assume the role that's already gone. - await fixture.cdkDestroy('test-2'); + // Immediately delete the stack again before we delete the role. + // + // Since roles are sticky, if we delete the role before the stack, subsequent DeleteStack + // operations will fail when CloudFormation tries to assume the role that's already gone. + await fixture.cdkDestroy('test-2'); - } finally { - await deleteRole(); - } + } finally { + await deleteRole(); + } - async function deleteRole() { - try { - for (const policyName of (await fixture.aws.iam('listRolePolicies', { RoleName: roleName })).PolicyNames) { - await fixture.aws.iam('deleteRolePolicy', { - RoleName: roleName, - PolicyName: policyName, - }); - } - await fixture.aws.iam('deleteRole', { RoleName: roleName }); - } catch (e) { - if (e.message.indexOf('cannot be found') > -1) { return; } - throw e; + async function deleteRole() { + try { + for (const policyName of (await fixture.aws.iam('listRolePolicies', { RoleName: roleName })).PolicyNames) { + await fixture.aws.iam('deleteRolePolicy', { + RoleName: roleName, + PolicyName: policyName, + }); } + await fixture.aws.iam('deleteRole', { RoleName: roleName }); + } catch (e) { + if (e.message.indexOf('cannot be found') > -1) { return; } + throw e; } - })); -} + } +})); integTest('cdk diff', withDefaultFixture(async (fixture) => { const diff1 = await fixture.cdk(['diff', fixture.fullStackName('test-1')]); @@ -791,7 +790,7 @@ integTest('failed deploy does not hang', withDefaultFixture(async (fixture) => { integTest('can still load old assemblies', withDefaultFixture(async (fixture) => { const cxAsmDir = path.join(os.tmpdir(), 'cdk-integ-cx'); - const testAssembliesDirectory = path.join(__dirname, 'cloud-assemblies'); + const testAssembliesDirectory = path.join(RESOURCES_DIR, 'cloud-assemblies'); for (const asmdir of await listChildDirs(testAssembliesDirectory)) { fixture.log(`ASSEMBLY ${asmdir}`); await cloneDirectory(asmdir, cxAsmDir); diff --git a/packages/@aws-cdk-testing/cli-integ/tests/init-csharp/init-csharp.integtest.ts b/packages/@aws-cdk-testing/cli-integ/tests/init-csharp/init-csharp.integtest.ts new file mode 100644 index 0000000000000..a36b1d628347e --- /dev/null +++ b/packages/@aws-cdk-testing/cli-integ/tests/init-csharp/init-csharp.integtest.ts @@ -0,0 +1,16 @@ +import { integTest, withTemporaryDirectory, ShellHelper, withPackages } from '../../lib'; + +['app', 'sample-app'].forEach(template => { + integTest(`init C♯ ${template}`, withTemporaryDirectory(withPackages(async (context) => { + context.packages.assertJsiiPackagesAvailable(); + + const shell = ShellHelper.fromContext(context); + await context.packages.makeCliAvailable(); + + await shell.shell(['cdk', 'init', '-l', 'csharp', template]); + await context.packages.initializeDotnetPackages(context.integTestDir); + await shell.shell(['cdk', 'synth']); + }))); +}); + + diff --git a/packages/@aws-cdk-testing/cli-integ/tests/init-fsharp/init-fsharp.integtest.ts b/packages/@aws-cdk-testing/cli-integ/tests/init-fsharp/init-fsharp.integtest.ts new file mode 100644 index 0000000000000..fdc1fca2d4cd1 --- /dev/null +++ b/packages/@aws-cdk-testing/cli-integ/tests/init-fsharp/init-fsharp.integtest.ts @@ -0,0 +1,15 @@ +import { integTest, withTemporaryDirectory, ShellHelper, withPackages } from '../../lib'; + +['app', 'sample-app'].forEach(template => { + integTest(`init F♯ ${template}`, withTemporaryDirectory(withPackages(async (context) => { + context.packages.assertJsiiPackagesAvailable(); + + const shell = ShellHelper.fromContext(context); + await context.packages.makeCliAvailable(); + + await shell.shell(['cdk', 'init', '-l', 'fsharp', template]); + await context.packages.initializeDotnetPackages(context.integTestDir); + await shell.shell(['cdk', 'synth']); + }))); +}); + diff --git a/packages/@aws-cdk-testing/cli-integ/tests/init-java/init-java.integtest.ts b/packages/@aws-cdk-testing/cli-integ/tests/init-java/init-java.integtest.ts new file mode 100644 index 0000000000000..48d5b38b1e2db --- /dev/null +++ b/packages/@aws-cdk-testing/cli-integ/tests/init-java/init-java.integtest.ts @@ -0,0 +1,14 @@ +import { integTest, withTemporaryDirectory, ShellHelper, withPackages } from '../../lib'; + +['app', 'sample-app'].forEach(template => { + integTest(`init java ${template}`, withTemporaryDirectory(withPackages(async (context) => { + context.packages.assertJsiiPackagesAvailable(); + + const shell = ShellHelper.fromContext(context); + await context.packages.makeCliAvailable(); + + await shell.shell(['cdk', 'init', '-l', 'java', template]); + await shell.shell(['mvn', 'package']); + await shell.shell(['cdk', 'synth']); + }))); +}); diff --git a/packages/@aws-cdk-testing/cli-integ/tests/init-javascript/init-javascript.integtest.ts b/packages/@aws-cdk-testing/cli-integ/tests/init-javascript/init-javascript.integtest.ts new file mode 100644 index 0000000000000..8fc4be4bf64fb --- /dev/null +++ b/packages/@aws-cdk-testing/cli-integ/tests/init-javascript/init-javascript.integtest.ts @@ -0,0 +1,15 @@ +import { integTest, withTemporaryDirectory, ShellHelper, withPackages } from '../../lib'; + +['app', 'sample-app'].forEach(template => { + integTest(`init javascript ${template}`, withTemporaryDirectory(withPackages(async (context) => { + const shell = ShellHelper.fromContext(context); + await context.packages.makeCliAvailable(); + + await shell.shell(['cdk', 'init', '-l', 'javascript', template]); + await shell.shell(['npm', 'prune']); + await shell.shell(['npm', 'ls']); // this will fail if we have unmet peer dependencies + await shell.shell(['npm', 'run', 'test']); + + await shell.shell(['cdk', 'synth']); + }))); +}); diff --git a/packages/@aws-cdk-testing/cli-integ/tests/init-python/init-python.integtest.ts b/packages/@aws-cdk-testing/cli-integ/tests/init-python/init-python.integtest.ts new file mode 100644 index 0000000000000..f7839fd9a0e15 --- /dev/null +++ b/packages/@aws-cdk-testing/cli-integ/tests/init-python/init-python.integtest.ts @@ -0,0 +1,20 @@ +import * as path from 'path'; +import { integTest, withTemporaryDirectory, ShellHelper, withPackages } from '../../lib'; + +['app', 'sample-app'].forEach(template => { + integTest(`init python ${template}`, withTemporaryDirectory(withPackages(async (context) => { + context.packages.assertJsiiPackagesAvailable(); + + const shell = ShellHelper.fromContext(context); + await context.packages.makeCliAvailable(); + + await shell.shell(['cdk', 'init', '-l', 'python', template]); + const venvPath = path.resolve(context.integTestDir, '.venv'); + const venv = { PATH: `${venvPath}/bin:${process.env.PATH}`, VIRTUAL_ENV: venvPath }; + + await shell.shell([`${venvPath}/bin/pip`, 'install', '-r', 'requirements.txt'], { modEnv: venv }); + await shell.shell([`${venvPath}/bin/pip`, 'install', '-r', 'requirements-dev.txt'], { modEnv: venv }); + await shell.shell([`${venvPath}/bin/pytest`], { modEnv: venv }); + await shell.shell(['cdk', 'synth'], { modEnv: venv }); + }))); +}); \ No newline at end of file diff --git a/packages/@aws-cdk-testing/cli-integ/tests/init-typescript-app/init-typescript-app.integtest.ts b/packages/@aws-cdk-testing/cli-integ/tests/init-typescript-app/init-typescript-app.integtest.ts new file mode 100644 index 0000000000000..158764ef3c973 --- /dev/null +++ b/packages/@aws-cdk-testing/cli-integ/tests/init-typescript-app/init-typescript-app.integtest.ts @@ -0,0 +1,58 @@ +import { promises as fs } from 'fs'; +import * as path from 'path'; +import { integTest, withTemporaryDirectory, ShellHelper, withPackages, TemporaryDirectoryContext } from '../../lib'; +import { typescriptVersionsSync } from '../../lib/npm'; + +['app', 'sample-app'].forEach(template => { + integTest(`typescript init ${template}`, withTemporaryDirectory(withPackages(async (context) => { + const shell = ShellHelper.fromContext(context); + await context.packages.makeCliAvailable(); + + await shell.shell(['cdk', 'init', '-l', 'typescript', template]); + + await shell.shell(['npm', 'prune']); + await shell.shell(['npm', 'ls']); // this will fail if we have unmet peer dependencies + await shell.shell(['npm', 'run', 'build']); + await shell.shell(['npm', 'run', 'test']); + + await shell.shell(['cdk', 'synth']); + }))); +}); + +/** + * Test our generated code with various versions of TypeScript + */ +typescriptVersionsSync().forEach(tsVersion => { + integTest(`typescript ${tsVersion} init app`, withTemporaryDirectory(withPackages(async (context) => { + const shell = ShellHelper.fromContext(context); + await context.packages.makeCliAvailable(); + + await shell.shell(['node', '--version']); + await shell.shell(['npm', '--version']); + + await shell.shell(['cdk', 'init', '-l', 'typescript', 'app', '--generate-only']); + + // Necessary because recent versions of ts-jest require TypeScript>=4.3 but we + // still want to test with older versions as well. + await removeDevDependencies(context); + + await shell.shell(['npm', 'install', '--save-dev', `typescript@${tsVersion}`]); + await shell.shell(['npm', 'install']); // Older versions of npm require this to be a separate step from the one above + await shell.shell(['npx', 'tsc', '--version']); + await shell.shell(['npm', 'prune']); + await shell.shell(['npm', 'ls']); // this will fail if we have unmet peer dependencies + + // We just removed the 'jest' dependency so remove the tests as well because they won't compile + await shell.shell(['rm', '-rf', 'test/']); + + await shell.shell(['npm', 'run', 'build']); + await shell.shell(['cdk', 'synth']); + }))); +}); + +async function removeDevDependencies(context: TemporaryDirectoryContext) { + const filename = path.join(context.integTestDir, 'package.json'); + const pj = JSON.parse(await fs.readFile(filename, { encoding: 'utf-8' })); + delete pj.devDependencies; + await fs.writeFile(filename, JSON.stringify(pj, undefined, 2), { encoding: 'utf-8' }); +} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/cli-integ/tests/init-typescript-lib/init-typescript-lib.integtest.ts b/packages/@aws-cdk-testing/cli-integ/tests/init-typescript-lib/init-typescript-lib.integtest.ts new file mode 100644 index 0000000000000..12472c82709cc --- /dev/null +++ b/packages/@aws-cdk-testing/cli-integ/tests/init-typescript-lib/init-typescript-lib.integtest.ts @@ -0,0 +1,13 @@ +import { integTest, withTemporaryDirectory, ShellHelper, withPackages } from '../../lib'; + +integTest('typescript init lib', withTemporaryDirectory(withPackages(async (context) => { + const shell = ShellHelper.fromContext(context); + await context.packages.makeCliAvailable(); + + await shell.shell(['cdk', 'init', '-l', 'typescript', 'lib']); + + await shell.shell(['npm', 'prune']); + await shell.shell(['npm', 'ls']); // this will fail if we have unmet peer dependencies + await shell.shell(['npm', 'run', 'build']); + await shell.shell(['npm', 'run', 'test']); +}))); \ No newline at end of file diff --git a/packages/aws-cdk/test/integ/uberpackage/uberpackage.integtest.ts b/packages/@aws-cdk-testing/cli-integ/tests/uberpackage/uberpackage.integtest.ts similarity index 69% rename from packages/aws-cdk/test/integ/uberpackage/uberpackage.integtest.ts rename to packages/@aws-cdk-testing/cli-integ/tests/uberpackage/uberpackage.integtest.ts index 6521e4c8113a0..ee924b783f377 100644 --- a/packages/aws-cdk/test/integ/uberpackage/uberpackage.integtest.ts +++ b/packages/@aws-cdk-testing/cli-integ/tests/uberpackage/uberpackage.integtest.ts @@ -1,5 +1,4 @@ -import { withMonolithicCfnIncludeCdkApp } from '../helpers/cdk'; -import { integTest } from '../helpers/test-helpers'; +import { integTest, withMonolithicCfnIncludeCdkApp } from '../../lib'; jest.setTimeout(600_000); diff --git a/packages/@aws-cdk-testing/cli-integ/tsconfig.json b/packages/@aws-cdk-testing/cli-integ/tsconfig.json new file mode 100644 index 0000000000000..f03765e562835 --- /dev/null +++ b/packages/@aws-cdk-testing/cli-integ/tsconfig.json @@ -0,0 +1,26 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["es2019", "es2020", "dom"], + "strict": true, + "alwaysStrict": true, + "declaration": true, + "inlineSourceMap": true, + "inlineSources": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "resolveJsonModule": true, + "composite": true, + "incremental": true + }, + "include": [ + "**/*.ts", + "**/*.d.ts" + ], + "exclude": [ + "resources/**/*" + ] +} diff --git a/packages/aws-cdk/.npmignore b/packages/aws-cdk/.npmignore index 7c198b16e73f8..2a474fe68b670 100644 --- a/packages/aws-cdk/.npmignore +++ b/packages/aws-cdk/.npmignore @@ -11,9 +11,6 @@ dist .LAST_BUILD *.snk -!test/integ/cli/**/*.js -!test/integ/run-wrappers/dist - *.tsbuildinfo jest.config.js @@ -24,10 +21,7 @@ tsconfig.json !lib/init-templates/**/tsconfig.json !lib/init-templates/**/jest.config.js -!test/integ/cli/jest.config.js -!test/integ/uberpackage/jest.config.js -!test/integ/cli-regression-patches/**/* -!test/integ/cli/sam_cdk_integ_app/**/*.ts +!test/integ/**/* .DS_Store diff --git a/packages/aws-cdk/lib/api/cxapp/exec.ts b/packages/aws-cdk/lib/api/cxapp/exec.ts index d4bb217135d59..d39e511d4cbbd 100644 --- a/packages/aws-cdk/lib/api/cxapp/exec.ts +++ b/packages/aws-cdk/lib/api/cxapp/exec.ts @@ -11,9 +11,15 @@ import { loadTree, some } from '../../tree'; import { splitBySize } from '../../util/objects'; import { versionNumber } from '../../version'; import { SdkProvider } from '../aws-auth'; +import { RWLock, ILock } from '../util/rwlock'; + +export interface ExecProgramResult { + readonly assembly: cxapi.CloudAssembly; + readonly lock: ILock; +} /** Invokes the cloud executable and returns JSON output */ -export async function execProgram(aws: SdkProvider, config: Configuration): Promise { +export async function execProgram(aws: SdkProvider, config: Configuration): Promise { const env: { [key: string]: string } = { }; const context = config.context.all; @@ -62,7 +68,11 @@ export async function execProgram(aws: SdkProvider, config: Configuration): Prom // bypass "synth" if app points to a cloud assembly if (await fs.pathExists(app) && (await fs.stat(app)).isDirectory()) { debug('--app points to a cloud assembly, so we bypass synth'); - return createAssembly(app); + + // Acquire a read lock on this directory + const lock = await new RWLock(app).acquireRead(); + + return { assembly: createAssembly(app), lock }; } const commandLine = await guessExecutable(appToArray(app)); @@ -80,6 +90,9 @@ export async function execProgram(aws: SdkProvider, config: Configuration): Prom debug('outdir:', outdir); env[cxapi.OUTDIR_ENV] = outdir; + // Acquire a read lock on the output directory + const writerLock = await new RWLock(outdir).acquireWrite(); + // Send version information env[cxapi.CLI_ASM_VERSION_ENV] = cxschema.Manifest.version(); env[cxapi.CLI_VERSION_ENV] = versionNumber(); @@ -107,7 +120,7 @@ export async function execProgram(aws: SdkProvider, config: Configuration): Prom contextOverflowCleanup(contextOverflowLocation, assembly); - return assembly; + return { assembly, lock: await writerLock.convertToReaderLock() }; function createAssembly(appDir: string) { try { diff --git a/packages/aws-cdk/lib/api/util/rwlock.ts b/packages/aws-cdk/lib/api/util/rwlock.ts new file mode 100644 index 0000000000000..6667bd17afe06 --- /dev/null +++ b/packages/aws-cdk/lib/api/util/rwlock.ts @@ -0,0 +1,184 @@ +import { promises as fs } from 'fs'; +import * as path from 'path'; + +/** + * A single-writer/multi-reader lock on a directory + * + * It uses marker files with PIDs in them as a locking marker; the PIDs will be + * checked for liveness, so that if the process exits without cleaning up the + * files the lock is implicitly released. + * + * This class is not 100% race safe, but in practice it should be a lot + * better than the 0 protection we have today. + */ +export class RWLock { + private readonly pidString: string; + private readonly writerFile: string; + private readonly readerFile: string; + + constructor(public readonly directory: string) { + this.pidString = `${process.pid}`; + + this.writerFile = path.join(this.directory, 'synth.lock'); + this.readerFile = path.join(this.directory, `read.${this.pidString}.lock`); + } + + /** + * Acquire a writer lock. + * + * No other readers or writers must exist for the given directory. + */ + public async acquireWrite(): Promise { + await this.assertNoOtherWriters(); + + const readers = await this.currentReaders(); + if (readers.length > 0) { + throw new Error(`Other CLIs (PID=${readers}) are currently reading from ${this.directory}. Invoke the CLI in sequence, or use '--output' to synth into different directories.`); + } + + await writeFileAtomic(this.writerFile, this.pidString); + + return { + release: async () => { + await deleteFile(this.writerFile); + }, + convertToReaderLock: async () => { + // Acquire the read lock before releasing the write lock. Slightly less + // chance of racing! + const ret = await this.doAcquireRead(); + await deleteFile(this.writerFile); + return ret; + }, + }; + } + + /** + * Acquire a read lock + * + * Will fail if there are any writers. + */ + public async acquireRead(): Promise { + await this.assertNoOtherWriters(); + return this.doAcquireRead(); + } + + /** + * Do the actual acquiring of a read lock. + */ + private async doAcquireRead(): Promise { + await writeFileAtomic(this.readerFile, this.pidString); + return { + release: async () => { + await deleteFile(this.readerFile); + }, + }; + } + + private async assertNoOtherWriters() { + const writer = await this.currentWriter(); + if (writer) { + throw new Error(`Another CLI (PID=${writer}) is currently synthing to ${this.directory}. Invoke the CLI in sequence, or use '--output' to synth into different directories.`); + } + } + + /** + * Check the current writer (if any) + */ + private async currentWriter(): Promise { + const contents = await readFileIfExists(this.writerFile); + if (!contents) { return undefined; } + + const pid = parseInt(contents, 10); + if (!processExists(pid)) { + // Do cleanup of a stray file now + await deleteFile(this.writerFile); + return undefined; + } + + return pid; + } + + /** + * Check the current readers (if any) + */ + private async currentReaders(): Promise { + const re = /^read\.([^.]+)\.lock$/; + const ret = new Array(); + + let children; + try { + children = await fs.readdir(this.directory, { encoding: 'utf-8' }); + } catch (e) { + // Can't be locked if the directory doesn't exist + if (e.code === 'ENOENT') { return []; } + throw e; + } + + for (const fname of children) { + const m = fname.match(re); + if (m) { + const pid = parseInt(m[1], 10); + if (processExists(pid)) { + ret.push(pid); + } else { + // Do cleanup of a stray file now + await deleteFile(path.join(this.directory, fname)); + } + } + } + return ret; + } +} + +/** + * An acquired lock + */ +export interface ILock { + release(): Promise; +} + +/** + * An acquired writer lock + */ +export interface IWriterLock extends ILock { + /** + * Convert the writer lock to a reader lock + */ + convertToReaderLock(): Promise; +} + +async function readFileIfExists(filename: string): Promise { + try { + return await fs.readFile(filename, { encoding: 'utf-8' }); + } catch (e) { + if (e.code === 'ENOENT') { return undefined; } + throw e; + } +} + +async function writeFileAtomic(filename: string, contents: string): Promise { + await fs.mkdir(path.dirname(filename), { recursive: true }); + const tmpFile = `${filename}.${process.pid}`; + await fs.writeFile(tmpFile, contents, { encoding: 'utf-8' }); + await fs.rename(tmpFile, filename); +} + +async function deleteFile(filename: string) { + try { + await fs.unlink(filename); + } catch (e) { + if (e.code === 'ENOENT') { + return; + } + throw e; + } +} + +function processExists(pid: number) { + try { + process.kill(pid, 0); + return true; + } catch (e) { + return false; + } +} \ No newline at end of file diff --git a/packages/aws-cdk/lib/cli.ts b/packages/aws-cdk/lib/cli.ts index 32dfb3e0dc32f..c38bee7f7f26d 100644 --- a/packages/aws-cdk/lib/cli.ts +++ b/packages/aws-cdk/lib/cli.ts @@ -24,8 +24,9 @@ import { displayNotices, refreshNotices } from '../lib/notices'; import { Command, Configuration, Settings } from '../lib/settings'; import * as version from '../lib/version'; import { DeploymentMethod } from './api'; -import { enableTracing } from './util/tracing'; +import { ILock } from './api/util/rwlock'; import { checkForPlatformWarnings } from './platform-warnings'; +import { enableTracing } from './util/tracing'; // https://github.com/yargs/yargs/issues/1929 // https://github.com/evanw/esbuild/issues/1492 @@ -329,10 +330,19 @@ async function initCommandLine() { const cloudFormation = new CloudFormationDeployments({ sdkProvider }); + let outDirLock: ILock | undefined; const cloudExecutable = new CloudExecutable({ configuration, sdkProvider, - synthesizer: execProgram, + synthesizer: async (aws, config) => { + // Invoke 'execProgram', and copy the lock for the directory in the global + // variable here. It will be released when the CLI exits. Locks are not re-entrant + // so release it if we have to synthesize more than once (because of context lookups). + await outDirLock?.release(); + const { assembly, lock } = await execProgram(aws, config); + outDirLock = lock; + return assembly; + }, }); /** Function to load plug-ins, using configurations additively. */ @@ -373,6 +383,10 @@ async function initCommandLine() { try { return await main(cmd, argv); } finally { + // If we locked the 'cdk.out' directory, release it here. + await outDirLock?.release(); + + // Do PSAs here await version.displayVersionMessage(); if (shouldDisplayNotices()) { diff --git a/packages/aws-cdk/test/api/exec.test.ts b/packages/aws-cdk/test/api/exec.test.ts index b9c5637acce1a..e52b4485da2cd 100644 --- a/packages/aws-cdk/test/api/exec.test.ts +++ b/packages/aws-cdk/test/api/exec.test.ts @@ -90,7 +90,8 @@ test('cli does not throw when manifest version = schema version', async () => { config.settings.set(['app'], 'cdk.out'); - await execProgram(sdkProvider, config); + const { lock } = await execProgram(sdkProvider, config); + await lock.release(); }, TEN_SECOND_TIMEOUT); @@ -107,7 +108,8 @@ test('cli does not throw when manifest version < schema version', async () => { // greater that the version created in the manifest, which is what we are testing for. const mockVersionNumber = ImportMock.mockFunction(cxschema.Manifest, 'version', semver.inc(currentSchemaVersion, 'major')); try { - await execProgram(sdkProvider, config); + const { lock } = await execProgram(sdkProvider, config); + await lock.release(); } finally { mockVersionNumber.restore(); } @@ -128,9 +130,11 @@ test('bypasses synth when app points to a cloud assembly', async () => { writeOutputAssembly(); // WHEN - const cloudAssembly = await execProgram(sdkProvider, config); + const { assembly: cloudAssembly, lock } = await execProgram(sdkProvider, config); expect(cloudAssembly.artifacts).toEqual([]); expect(cloudAssembly.directory).toEqual('cdk.out'); + + await lock.release(); }); test('the application set in --app is executed', async () => { @@ -142,7 +146,8 @@ test('the application set in --app is executed', async () => { }); // WHEN - await execProgram(sdkProvider, config); + const { lock } = await execProgram(sdkProvider, config); + await lock.release(); }); test('the application set in --app is executed as-is if it contains a filename that does not exist', async () => { @@ -154,7 +159,8 @@ test('the application set in --app is executed as-is if it contains a filename t }); // WHEN - await execProgram(sdkProvider, config); + const { lock } = await execProgram(sdkProvider, config); + await lock.release(); }); test('the application set in --app is executed with arguments', async () => { @@ -166,7 +172,8 @@ test('the application set in --app is executed with arguments', async () => { }); // WHEN - await execProgram(sdkProvider, config); + const { lock } = await execProgram(sdkProvider, config); + await lock.release(); }); test('application set in --app as `*.js` always uses handler on windows', async () => { @@ -179,7 +186,8 @@ test('application set in --app as `*.js` always uses handler on windows', async }); // WHEN - await execProgram(sdkProvider, config); + const { lock } = await execProgram(sdkProvider, config); + await lock.release(); }); test('application set in --app is `*.js` and executable', async () => { @@ -191,7 +199,8 @@ test('application set in --app is `*.js` and executable', async () => { }); // WHEN - await execProgram(sdkProvider, config); + const { lock } = await execProgram(sdkProvider, config); + await lock.release(); }); test('cli throws when the `build` script fails', async () => { @@ -220,7 +229,8 @@ test('cli does not throw when the `build` script succeeds', async () => { }); // WHEN - await execProgram(sdkProvider, config); + const { lock } = await execProgram(sdkProvider, config); + await lock.release(); }, TEN_SECOND_TIMEOUT); diff --git a/packages/aws-cdk/test/api/logs/logs-monitor.test.ts b/packages/aws-cdk/test/api/logs/logs-monitor.test.ts index eed3cf2683540..7f5e54a89b300 100644 --- a/packages/aws-cdk/test/api/logs/logs-monitor.test.ts +++ b/packages/aws-cdk/test/api/logs/logs-monitor.test.ts @@ -1,6 +1,6 @@ import { blue, yellow } from 'chalk'; import { CloudWatchLogEventMonitor } from '../../../lib/api/logs/logs-monitor'; -import { sleep } from '../../integ/helpers/aws'; +import { sleep } from '../../util'; import { MockSdk } from '../../util/mock-sdk'; let sdk: MockSdk; diff --git a/packages/aws-cdk/test/integ/cli-regression-patches/README.md b/packages/aws-cdk/test/integ/cli-regression-patches/README.md deleted file mode 100644 index c930255e85809..0000000000000 --- a/packages/aws-cdk/test/integ/cli-regression-patches/README.md +++ /dev/null @@ -1,54 +0,0 @@ -Regression Test Patches -======================== - -The regression test suite will use the test suite of an OLD version -of the CLI when testing a NEW version of the CLI, to make sure the -old tests still pass. - -Sometimes though, the old tests won't pass. This can happen when we -introduce breaking changes to the framework or CLI (for something serious, -such as security reasons), or maybe because we had a bug in an old -version that happened to pass, but now the test needs to be updated -in order to pass a bugfix. - -## Mechanism - -The files in this directory will be copied over the test directory -so that you can exclude tests from running, or patch up test running -scripts. - -Files will be copied like so: - -``` -aws-cdk/test/integ/cli-regression-patches/vX.Y.Z/* - -# will be copied into - -aws-cdk/test/integ/cli -``` - -For example, to skip a certain integration test during regression -testing, create the following file: - -``` -cli-regression-patches/vX.Y.Z/skip-tests.txt -``` - -If you need to replace source files, it's probably best to stick -compiled `.js` files in here. `.ts` source files wouldn't compile -because they'd be missing `imports`. - -## Versioning - -The patch sets are versioned, so that they will only be applied for -a certain version of the tests and will automatically age out when -we proceed past that release. - -The version in the directory name needs to be named after the -version that contains the *tests* we're running, that need to be -patched. - -So for example, if we are running regression tests for release -candidate `1.45.0`, we would use the tests from released version -`1.44.0`, and so you would call the patch directory `v1.44.0`. - diff --git a/packages/aws-cdk/test/integ/cli/jest.config.js b/packages/aws-cdk/test/integ/cli/jest.config.js deleted file mode 100644 index 1e3fe3d13f96b..0000000000000 --- a/packages/aws-cdk/test/integ/cli/jest.config.js +++ /dev/null @@ -1,15 +0,0 @@ -module.exports = { - moduleFileExtensions: [ - "js", - ], - testMatch: [ - "**/*.integtest.js", - ], - testEnvironment: "node", - bail: 1, - verbose: true, - reporters: [ - "default", - [ "jest-junit", { suiteName: "jest tests", outputDirectory: "coverage" } ] - ] -}; diff --git a/packages/aws-cdk/test/integ/cli/test.sh b/packages/aws-cdk/test/integ/cli/test.sh index bf0ec0a7c5c68..1d33ee1502bbd 100755 --- a/packages/aws-cdk/test/integ/cli/test.sh +++ b/packages/aws-cdk/test/integ/cli/test.sh @@ -1,12 +1,6 @@ #!/bin/bash -set -euo pipefail -scriptdir=$(cd $(dirname $0) && pwd) +set -eu +# This is a backwards compatibility script. All logic has moved to '@aws-cdk-testing/cli-integ' +# and should be called from there directly. -echo '~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~' -echo 'CLI Integration Tests' -echo '~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~' - -cd $scriptdir - -source ../common/jest-test.bash -invokeJest "$@" +exec ${INTEG_TOOLS}/bin/run-suite --use-cli-release=$VERSION cli-integ-tests diff --git a/packages/aws-cdk/test/integ/common/jest-test.bash b/packages/aws-cdk/test/integ/common/jest-test.bash deleted file mode 100755 index 7a96a9f845155..0000000000000 --- a/packages/aws-cdk/test/integ/common/jest-test.bash +++ /dev/null @@ -1,18 +0,0 @@ -function invokeJest() { - # Install these dependencies that the tests (written in Jest) need. - # Only if we're not running from the repo, because if we are the - # dependencies have already been installed by the containing 'aws-cdk' package's - # package.json. - if ! npx --no-install jest --version; then - echo 'Looks like we need to install jest first. Hold on.' >& 2 - npm install --prefix .. jest@^27 jest-junit@^14 aws-sdk@^2 axios@^0.27.2 - fi - - # This must --runInBand because parallelism is arranged for inside the tests - # themselves and they must run in the same process in order to coordinate to - # make sure no 2 tests use the same region at the same time. - # - # Jest is run in a weird way because npx started (NPM 8?) to change directory - # into 'package.json' root, which we don't want here. - $(npx which jest) --runInBand --verbose "$@" -} diff --git a/packages/aws-cdk/test/integ/github-helpers.ts b/packages/aws-cdk/test/integ/github-helpers.ts deleted file mode 100644 index d5a9b6cb41df7..0000000000000 --- a/packages/aws-cdk/test/integ/github-helpers.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { Octokit } from '@octokit/rest'; -import * as semver from 'semver'; - -module.exports.fetchPreviousVersion = async function(base: string) { - const token = process.env.GITHUB_TOKEN; - if (!token) { - throw new Error('GITHUB_TOKEN must be set'); - } - - const github = new Octokit({ auth: token }); - const releases = await github.repos.listReleases({ - owner: 'aws', - repo: 'aws-cdk', - }); - - // this returns a list in descending order, newest releases first - // opts for same major version where possible, falling back otherwise - // to previous major versions. - let previousMVRelease = undefined; - for (const release of releases.data) { - const version = release.name?.replace('v', ''); - if (version && semver.lt(version, base)) { - if (semver.major(version) === semver.major(base)) { - return version; - } else if (!previousMVRelease) { - previousMVRelease = version; - } - } - } - if (previousMVRelease) { return previousMVRelease; } - - throw new Error(`Unable to find previous version of ${base}`); -}; - -// eslint-disable-next-line @typescript-eslint/no-require-imports -require('make-runnable/custom')({ - printOutputFrame: false, -}); diff --git a/packages/aws-cdk/test/integ/helpers/resource-pool.test.ts b/packages/aws-cdk/test/integ/helpers/resource-pool.test.ts deleted file mode 100644 index edae1a1ed170a..0000000000000 --- a/packages/aws-cdk/test/integ/helpers/resource-pool.test.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { ResourcePool } from './resource-pool'; - -test('take and dispose', async () => { - const pool = new ResourcePool(['a']); - - const take1 = pool.take(); - const take2 = pool.take(); - - let released = false; - - const lease1 = await take1; - // awaiting 'take2' would now block but we add an async - // handler to it to flip a boolean to see when it gets activated. - void(take2.then(() => released = true)); - - expect(lease1.value).toEqual('a'); - await waitTick(); - expect(released).toEqual(false); - - lease1.dispose(); - await waitTick(); // This works because setImmediate is scheduled in LIFO order - expect(released).toEqual(true); -}); - -test('double dispose throws', async () => { - const pool = new ResourcePool(['a']); - const lease = await pool.take(); - - lease.dispose(); - expect(() => lease.dispose()).toThrow(); -}); - -test('more consumers than values', async () => { - const pool = new ResourcePool(['a', 'b']); - - const values = await Promise.all([ - pool.using(x => x), - pool.using(x => x), - pool.using(x => x), - ]); - - expect(values).toEqual(['a', 'b', 'a']); -}); - -function waitTick() { - return new Promise(setImmediate); -} \ No newline at end of file diff --git a/packages/aws-cdk/test/integ/helpers/resource-pool.ts b/packages/aws-cdk/test/integ/helpers/resource-pool.ts deleted file mode 100644 index 444a0bc193edd..0000000000000 --- a/packages/aws-cdk/test/integ/helpers/resource-pool.ts +++ /dev/null @@ -1,95 +0,0 @@ -/** - * A class that holds a pool of resources and gives them out and returns them on-demand - * - * The resources will be given out front to back, when they are returned - * the most recently returned version will be given out again (for best - * cache coherency). - * - * If there are multiple consumers waiting for a resource, consumers are serviced - * in FIFO order for most fairness. - */ -export class ResourcePool { - private readonly resources: A[]; - private readonly waiters: Array<(x: A) => void> = []; - - constructor(resources: A[]) { - if (resources.length === 0) { - throw new Error('Must have at least one resource in the pool'); - } - this.resources = [...resources]; - } - - /** - * Take one value from the resource pool - * - * If no such value is currently available, wait until it is. - */ - public take(): Promise> { - const next = this.resources.shift(); - if (next !== undefined) { - return Promise.resolve(this.makeLease(next)); - } else { - return new Promise(ok => { - this.waiters.push((resource) => ok(this.makeLease(resource))); - }); - } - } - - /** - * Execute a block using a single resource from the pool - */ - public async using(block: (x: A) => B | Promise): Promise { - const lease = await this.take(); - try { - return await block(lease.value); - } finally { - lease.dispose(); - } - } - - private makeLease(value: A): ILease { - let disposed = false; - return { - value, - dispose: () => { - if (disposed) { - throw new Error('Calling dispose() on an already-disposed lease.'); - } - disposed = true; - this.returnValue(value); - }, - }; - } - - /** - * When a value is returned: - * - * - If someone's waiting for it, give it to them - * - Otherwise put it back into the pool - */ - private returnValue(value: A) { - const nextWaiter = this.waiters.shift(); - if (nextWaiter !== undefined) { - // Execute in the next tick, otherwise the call stack is going to get very - // confusing. - setImmediate(() => nextWaiter(value)); - } else { - this.resources.unshift(value); - } - } -} - -/** - * A single value taken from the pool - */ -export interface ILease { - /** - * The value obtained by the lease - */ - readonly value: A; - - /** - * Return the leased value to the pool - */ - dispose(): void; -} \ No newline at end of file diff --git a/packages/aws-cdk/test/integ/init/common.bash b/packages/aws-cdk/test/integ/init/common.bash deleted file mode 100644 index f41f02b5215e8..0000000000000 --- a/packages/aws-cdk/test/integ/init/common.bash +++ /dev/null @@ -1,25 +0,0 @@ -set -eu -init_test_dir="${init_test_dir:=/tmp/cdk-init-test}" - -function setup() { - rm -rf $init_test_dir - mkdir -p $init_test_dir - cd $init_test_dir -} - -function log() { - echo >&2 "| $@" -} - -function header() { - log - log "============================================================================================" - log $@ - log "============================================================================================" -} - -function assert_no_hook_files() { - compgen "\*.hook.\*" || return 0 - echo "'cdk init' left hook files in the template directory!" >&2 - exit 1 -} diff --git a/packages/aws-cdk/test/integ/init/test-all.sh b/packages/aws-cdk/test/integ/init/test-all.sh deleted file mode 100755 index f191d37bf4296..0000000000000 --- a/packages/aws-cdk/test/integ/init/test-all.sh +++ /dev/null @@ -1,12 +0,0 @@ -#!/bin/bash -set -eu -scriptdir=$(cd $(dirname $0) && pwd) -$scriptdir/test-csharp.sh -$scriptdir/test-fsharp.sh -$scriptdir/test-java.sh -$scriptdir/test-javascript.sh -$scriptdir/test-python.sh -$scriptdir/test-typescript.sh -$scriptdir/test-go.sh - -echo "SUCCESS" diff --git a/packages/aws-cdk/test/integ/init/test-csharp-app.sh b/packages/aws-cdk/test/integ/init/test-csharp-app.sh index 19bd5c91e4ef9..c32d86b7ffbd8 100755 --- a/packages/aws-cdk/test/integ/init/test-csharp-app.sh +++ b/packages/aws-cdk/test/integ/init/test-csharp-app.sh @@ -1,6 +1,6 @@ #!/bin/bash -# Naming shim for backwards compatibility with legacy -# tests and canaries. set -eu -scriptdir=$(cd $(dirname $0) && pwd) -exec $scriptdir/test-csharp.sh +# This is a backwards compatibility script. All logic has moved to '@aws-cdk-testing/cli-integ' +# and should be called from there directly. + +exec ${INTEG_TOOLS}/bin/run-suite --use-cli-release=$VERSION init-csharp diff --git a/packages/aws-cdk/test/integ/init/test-csharp.sh b/packages/aws-cdk/test/integ/init/test-csharp.sh deleted file mode 100755 index ea82e3b63f38c..0000000000000 --- a/packages/aws-cdk/test/integ/init/test-csharp.sh +++ /dev/null @@ -1,26 +0,0 @@ -#!/bin/bash -#------------------------------------------------------------------ -# setup -#------------------------------------------------------------------ -set -e -scriptdir=$(cd $(dirname $0) && pwd) -source ${scriptdir}/common.bash - -header C# - -#------------------------------------------------------------------ - -if [[ "${1:-}" == "" ]]; then - templates="app sample-app" -else - templates="$@" -fi - -for template in $templates; do - echo "Trying C# template $template" - - setup - - cdk init -l csharp $template - cdk synth -done diff --git a/packages/aws-cdk/test/integ/init/test-fsharp.sh b/packages/aws-cdk/test/integ/init/test-fsharp.sh index f8ad463703672..7ea578e300c8d 100755 --- a/packages/aws-cdk/test/integ/init/test-fsharp.sh +++ b/packages/aws-cdk/test/integ/init/test-fsharp.sh @@ -1,26 +1,6 @@ #!/bin/bash -#------------------------------------------------------------------ -# setup -#------------------------------------------------------------------ -set -e -scriptdir=$(cd $(dirname $0) && pwd) -source ${scriptdir}/common.bash +set -eu +# This is a backwards compatibility script. All logic has moved to '@aws-cdk-testing/cli-integ' +# and should be called from there directly. -header F# - -#------------------------------------------------------------------ - -if [[ "${1:-}" == "" ]]; then - templates="app sample-app" -else - templates="$@" -fi - -for template in $templates; do - echo "Trying F# template $template" - - setup - - cdk init -l fsharp $template - cdk synth -done +exec ${INTEG_TOOLS}/bin/run-suite --use-cli-release=$VERSION init-fsharp diff --git a/packages/aws-cdk/test/integ/init/test-generate-only.sh b/packages/aws-cdk/test/integ/init/test-generate-only.sh deleted file mode 100755 index f09ab29fcf537..0000000000000 --- a/packages/aws-cdk/test/integ/init/test-generate-only.sh +++ /dev/null @@ -1,22 +0,0 @@ -#!/bin/bash -#------------------------------------------------------------------ -# setup -#------------------------------------------------------------------ -set -e -scriptdir=$(cd $(dirname $0) && pwd) -source ${scriptdir}/common.bash - -header "--generate-only" - -#------------------------------------------------------------------ - -echo "Trying --generate-only" - -setup - -cdk init -l javascript --generate-only - -if [ -d .git ] -then - fail "git shouldn't have been initialized" -fi diff --git a/packages/aws-cdk/test/integ/init/test-go.sh b/packages/aws-cdk/test/integ/init/test-go.sh deleted file mode 100755 index 58959855073f0..0000000000000 --- a/packages/aws-cdk/test/integ/init/test-go.sh +++ /dev/null @@ -1,30 +0,0 @@ -#!/bin/bash -#------------------------------------------------------------------ -# setup -#------------------------------------------------------------------ -set -eu -scriptdir=$(cd $(dirname $0) && pwd) -source ${scriptdir}/common.bash -dist_root=$(cd ${DIST_ROOT:-.} && pwd) - -header Go - -#------------------------------------------------------------------ - -if [[ "${1:-}" == "" ]]; then - templates="app sample-app" -else - templates="$@" -fi - -for template in $templates; do - echo "Trying Go template $template" - - setup - - cdk init -l go $template - go mod edit -replace github.com/aws/aws-cdk-go/awscdk=$dist_root/go/awscdk - go mod tidy - go test - cdk synth -done diff --git a/packages/aws-cdk/test/integ/init/test-java.sh b/packages/aws-cdk/test/integ/init/test-java.sh index d2c95984fb178..404256230b64f 100755 --- a/packages/aws-cdk/test/integ/init/test-java.sh +++ b/packages/aws-cdk/test/integ/init/test-java.sh @@ -1,27 +1,6 @@ #!/bin/bash -#------------------------------------------------------------------ -# setup -#------------------------------------------------------------------ -set -e -scriptdir=$(cd $(dirname $0) && pwd) -source ${scriptdir}/common.bash +set -eu +# This is a backwards compatibility script. All logic has moved to '@aws-cdk-testing/cli-integ' +# and should be called from there directly. -header Java - -#------------------------------------------------------------------ - -if [[ "${1:-}" == "" ]]; then - templates="app sample-app" -else - templates="$@" -fi - -for template in $templates; do - echo "Trying Java template $template" - - setup - - cdk init -l java $template - mvn package - cdk synth -done +exec ${INTEG_TOOLS}/bin/run-suite --use-cli-release=$VERSION init-java diff --git a/packages/aws-cdk/test/integ/init/test-javascript.sh b/packages/aws-cdk/test/integ/init/test-javascript.sh index 511403e19efcd..8a544ff669ace 100755 --- a/packages/aws-cdk/test/integ/init/test-javascript.sh +++ b/packages/aws-cdk/test/integ/init/test-javascript.sh @@ -1,27 +1,6 @@ #!/bin/bash -#------------------------------------------------------------------ -# setup -#------------------------------------------------------------------ -set -e -scriptdir=$(cd $(dirname $0) && pwd) -source ${scriptdir}/common.bash +set -eu +# This is a backwards compatibility script. All logic has moved to '@aws-cdk-testing/cli-integ' +# and should be called from there directly. -header Javascript - -#------------------------------------------------------------------ - -if [[ "${1:-}" == "" ]]; then - templates="app sample-app" -else - templates="$@" -fi - -for template in $templates; do - echo "Trying Javascript template $template" - - setup - - cdk init -l javascript $template - npm run test - cdk synth -done +exec ${INTEG_TOOLS}/bin/run-suite --use-cli-release=$VERSION init-javascript diff --git a/packages/aws-cdk/test/integ/init/test-python-app.sh b/packages/aws-cdk/test/integ/init/test-python-app.sh index 3f130fcb24aaa..a6cb2be1f3af8 100755 --- a/packages/aws-cdk/test/integ/init/test-python-app.sh +++ b/packages/aws-cdk/test/integ/init/test-python-app.sh @@ -1,6 +1,6 @@ #!/bin/bash -# Naming shim for backwards compatibility with legacy -# tests and canaries. set -eu -scriptdir=$(cd $(dirname $0) && pwd) -exec $scriptdir/test-python.sh +# This is a backwards compatibility script. All logic has moved to '@aws-cdk-testing/cli-integ' +# and should be called from there directly. + +exec ${INTEG_TOOLS}/bin/run-suite --use-cli-release=$VERSION init-python diff --git a/packages/aws-cdk/test/integ/init/test-python-stackname.sh b/packages/aws-cdk/test/integ/init/test-python-stackname.sh deleted file mode 100755 index 415c9bdb25cf2..0000000000000 --- a/packages/aws-cdk/test/integ/init/test-python-stackname.sh +++ /dev/null @@ -1,7 +0,0 @@ -#!/bin/bash -# Naming shim for backwards compatibility with legacy -# tests and canaries. -set -eu -scriptdir=$(cd $(dirname $0) && pwd) -init_test_dir=/tmp/cdkInitTest -source $scriptdir/test-python.sh diff --git a/packages/aws-cdk/test/integ/init/test-python.sh b/packages/aws-cdk/test/integ/init/test-python.sh deleted file mode 100755 index d40d94d4534ca..0000000000000 --- a/packages/aws-cdk/test/integ/init/test-python.sh +++ /dev/null @@ -1,33 +0,0 @@ -#!/bin/bash -#------------------------------------------------------------------ -# setup -#------------------------------------------------------------------ -set -eu -scriptdir=$(cd $(dirname $0) && pwd) -source ${scriptdir}/common.bash - -header Python - -#------------------------------------------------------------------ - -if [[ "${1:-}" == "" ]]; then - templates="app sample-app" -else - templates="$@" -fi - -for template in $templates; do - echo "Trying Python template $template" - - setup - - cdk init -l python $template - - source .venv/bin/activate - type -p pip - pip install -r requirements.txt - ./.venv/bin/pip install -r requirements-dev.txt - pytest - - cdk synth -done diff --git a/packages/aws-cdk/test/integ/init/test-typescript-app.sh b/packages/aws-cdk/test/integ/init/test-typescript-app.sh index 7db05ee7e5dad..68ff7bc6f5b2c 100755 --- a/packages/aws-cdk/test/integ/init/test-typescript-app.sh +++ b/packages/aws-cdk/test/integ/init/test-typescript-app.sh @@ -1,7 +1,6 @@ #!/bin/bash -# Naming shim for backwards compatibility with legacy -# tests and canaries. set -eu -scriptdir=$(cd $(dirname $0) && pwd) -# Run both app templates -exec $scriptdir/test-typescript.sh app sample-app +# This is a backwards compatibility script. All logic has moved to '@aws-cdk-testing/cli-integ' +# and should be called from there directly. + +exec ${INTEG_TOOLS}/bin/run-suite --use-cli-release=$VERSION init-typescript-app diff --git a/packages/aws-cdk/test/integ/init/test-typescript-lib.sh b/packages/aws-cdk/test/integ/init/test-typescript-lib.sh index 514f6bf9fb800..7bae427df1e66 100755 --- a/packages/aws-cdk/test/integ/init/test-typescript-lib.sh +++ b/packages/aws-cdk/test/integ/init/test-typescript-lib.sh @@ -1,7 +1,6 @@ #!/bin/bash -# Naming shim for backwards compatibility with legacy -# tests and canaries. set -eu -scriptdir=$(cd $(dirname $0) && pwd) -# Run only lib template -exec $scriptdir/test-typescript.sh lib +# This is a backwards compatibility script. All logic has moved to '@aws-cdk-testing/cli-integ' +# and should be called from there directly. + +exec ${INTEG_TOOLS}/bin/run-suite --use-cli-release=$VERSION init-typescript-lib diff --git a/packages/aws-cdk/test/integ/init/test-typescript-versions.sh b/packages/aws-cdk/test/integ/init/test-typescript-versions.sh index da5793661a97e..33b52b062070b 100755 --- a/packages/aws-cdk/test/integ/init/test-typescript-versions.sh +++ b/packages/aws-cdk/test/integ/init/test-typescript-versions.sh @@ -3,34 +3,7 @@ # setup #------------------------------------------------------------------ set -eu -scriptdir=$(cd $(dirname $0) && pwd) -integdir=$(dirname $scriptdir) -source ${scriptdir}/common.bash -header TypeScript Versions - -#------------------------------------------------------------------ - -MIN_SUPPORTED_TS_VERSION=${1:-"3.9"} -SUPPORTED_TS_VERSIONS=$(node ${integdir}/typescript-versions.js ${MIN_SUPPORTED_TS_VERSION}) - -for version in $SUPPORTED_TS_VERSIONS; do - header TypeScript v$version - - setup - - set -x - node --version - npm --version - - cdk init -l typescript sample-app --generate-only - sed '/\"devDependencies\"/,/}/ d; /^$/d' package.json > package.json.new && rm package.json && mv package.json.new package.json - npm install --save-dev typescript@$version - npm install # Older versions of npm require this to be a separate step from the one above - npm prune && npm ls - rm test/* - npm run build - cdk synth - - set +x -done +# This is a backwards compatibility script. All logic has moved to '@aws-cdk-testing/cli-integ', +# and in fact has been integrated into the regular TypeScript tests. +exit 0 \ No newline at end of file diff --git a/packages/aws-cdk/test/integ/init/test-typescript.sh b/packages/aws-cdk/test/integ/init/test-typescript.sh deleted file mode 100755 index 14fa77d4463dd..0000000000000 --- a/packages/aws-cdk/test/integ/init/test-typescript.sh +++ /dev/null @@ -1,33 +0,0 @@ -#!/bin/bash -#------------------------------------------------------------------ -# setup -#------------------------------------------------------------------ -set -eu -scriptdir=$(cd $(dirname $0) && pwd) -source ${scriptdir}/common.bash - -header TypeScript - -#------------------------------------------------------------------ - -if [[ "${1:-}" == "" ]]; then - templates="app sample-app lib" -else - templates="$@" -fi - -for template in $templates; do - echo "Trying TypeScript template $template" - - setup - - cdk init -l typescript $template - npm prune && npm ls # this will fail if we have unmet peer dependencies - npm run build - npm run test - - # Can't run `cdk synth` on libraries - if [[ $template != "lib" ]]; then - cdk synth - fi -done diff --git a/packages/aws-cdk/test/integ/run-against-dist b/packages/aws-cdk/test/integ/run-against-dist index c7ade80dbb1f3..5fb4abea341eb 100755 --- a/packages/aws-cdk/test/integ/run-against-dist +++ b/packages/aws-cdk/test/integ/run-against-dist @@ -1,48 +1,19 @@ #!/bin/bash -# Run a given test against the packages found in the CDK distribution. -# -# - Set up verdaccio and publish all NPM tarballs found in the given directory to it. -# - Install the CLI from that Verdaccio fixture somewhere and put it -# on the PATH. -# - Prepare the various package managers to take their packages from dist/ as well. -# -# Parameter: DIST_ROOT, if different than the current directory. set -eu -scriptdir=$(cd $(dirname $0) && pwd) -source $scriptdir/run-against-dist.bash +# This is a backwards compatibility script. All logic has moved to '@aws-cdk-testing/cli-integ' +# and should be called from there directly. -# If DIST_ROOT is not given, guess at a sane location. -if [[ "${DIST_ROOT:-}" == "" && -f $scriptdir/../../../dist/build.json ]]; then - DIST_ROOT=$scriptdir/../../../dist -fi +export VERSION=$(node -pe "require('./build.json').version") -dist_root=$(cd ${DIST_ROOT:-.} && pwd) +# The package MUST be 'npm install'ed from the package repository (`npm install --production` +# will not work because that will resolve devDependencies even though it will not install them), +# but it may not live in a 'node_modules' directory because Jest 27 does not support that. Do contortions. +export INTEG_TOOLS=cli_integ +rm -rf $INTEG_TOOLS && mkdir $INTEG_TOOLS +npm install --prefix $INTEG_TOOLS --no-save ./js/aws-cdk-testing-cli-integ-*.tgz +mv $($INTEG_TOOLS/node_modules/.bin/test-root)/* $INTEG_TOOLS -if [[ ! -f $dist_root/build.json ]]; then - echo "$dist_root does not seem to be a built CDK distribution (change directory or use DIST_ROOT)" >&2 - exit 1 -fi +# Do a breakpoint for testing +codebuild-breakpoint -export CANDIDATE_VERSION=$(node -p "require('${dist_root}/build.json').version") - -# FRAMEWORK_VERSION is the version that will be 'npm install'ed by the tests -if [[ "${FRAMEWORK_VERSION:-}" = "" ]]; then - export FRAMEWORK_VERSION=${CANDIDATE_VERSION} -fi -export TEST_RUNNER=dist - -serve_npm_packages -install_cli - -prepare_java_packages -prepare_nuget_packages -prepare_python_packages - -# Install additional tool wrappers before running the target script -export PATH="$scriptdir/run-wrappers/dist:$PATH" -hash -r - -# Run target script -# NOTE: no 'exec' because we need to shutdown verdaccio only AFTER we've -# run the subscript. -"$@" +CONCURRENCY=5 exec ${INTEG_TOOLS}/bin/stage-distribution run . "$@" diff --git a/packages/aws-cdk/test/integ/run-against-dist.bash b/packages/aws-cdk/test/integ/run-against-dist.bash deleted file mode 100644 index 84162031f5068..0000000000000 --- a/packages/aws-cdk/test/integ/run-against-dist.bash +++ /dev/null @@ -1,147 +0,0 @@ -# Helper functions to go with 'run-against-dist' -# NPM Workspace. Will have CDK CLI and verdaccio installed into it. -npmws=/tmp/cdk-rundist -rm -rf $npmws -mkdir -p $npmws - -set -x - -# This script must create 1 or 2 traps, and the 'trap' command will replace -# the previous trap, so get some 'dynamic traps' mechanism in place -TRAPS=() - -function run_traps() { - for cmd in "${TRAPS[@]}"; do - echo "cleanup: $cmd" >&2 - eval "$cmd" - done -} - -trap run_traps EXIT - -function log() { - echo >&2 "| $@" -} - -function header() { - log - log "============================================================================================" - log $@ - log "============================================================================================" -} - -function serve_npm_packages() { - if [ -n "${SERVE_NPM_TARBALLS_PID:-}" ]; then - log >&2 "Verdaccio is already running" - return 1 - fi - - #------------------------------------------------------------------------------ - # Start a mock npm repository from the given tarballs - #------------------------------------------------------------------------------ - header "Starting local NPM Repository (Serving version ${CANDIDATE_VERSION})" - - tarballs_glob="$dist_root/js/*.tgz" - - if [[ -f package.json ]]; then - echo "Do not run this script in a directory with a package.json! It will most likely break!" >&2 - # Cowardly not running 'exit 1' because I'm not sure I won't mess up the build/canaries by doing so - fi - - # When using '--daemon', 'npm install' first so the files are permanent, or - # 'npx' will remove them too soon. - npm install serve-npm-tarballs - eval $(npx serve-npm-tarballs --glob "${tarballs_glob}" --daemon) - TRAPS+=("kill $SERVE_NPM_TARBALLS_PID") -} - -function install_cli() { - echo "Installing CLI aws-cdk@${CANDIDATE_VERSION}" - (cd ${npmws} && npm install --prefix $npmws aws-cdk@${CANDIDATE_VERSION}) - export PATH=$npmws/node_modules/.bin:$PATH -} - -function prepare_java_packages() { - log "Preparing Maven packages..." - - # copy the maven staging repo to the maven local repo and set as M2 home - # this ensures that the canary builds against the build artifacts, not maven central - if [ ! -d $dist_root/java ]; then - echo "Maven packages missing at $dist_root/java" >&2 - exit 1 - fi - - # Rename all maven-metadata.xml* files to maven-metadata-local.xml* - # This is necessary for Maven to find them correctly after we've rsync'ed - # them into place: - # https://github.com/sonatype/sonatype-aether/blob/master/aether-impl/src/main/java/org/sonatype/aether/impl/internal/SimpleLocalRepositoryManager.java#L114 - for f in $(find $dist_root/java -name maven-metadata.xml\*); do - mv "$f" "$(echo "$f" | sed s/metadata\.xml/metadata-local.xml/)" - done - - export MAVEN_CONFIG=${MAVEN_CONFIG:-$HOME/.m2} - rsync -a $dist_root/java/ ${MAVEN_CONFIG}/repository -} - -function prepare_nuget_packages() { - # For NuGet, we wrap the "dotnet" CLI command to use local packages. - log "Writing new NuGet configuration..." - - local NUGET_SOURCE=$dist_root/dotnet - - if [ ! -d "$NUGET_SOURCE" ]; then - echo "NuGet packages missing at $NUGET_SOURCE" >&2 - exit 1 - fi - - mkdir -p $HOME/.nuget/NuGet - if [ -f $HOME/.nuget/NuGet/NuGet.Config ]; then - echo "⚠️ Saving previous NuGet.Config to $HOME/.nuget/NuGet/NuGet.Config.bak" - mv $HOME/.nuget/NuGet/NuGet.Config $HOME/.nuget/NuGet/NuGet.Config.bak - fi - - TRAPS+=('clean_up_nuget_config') - - cat > $HOME/.nuget/NuGet/NuGet.Config < - - - - - - -EOF -} - -function clean_up_nuget_config() { - log "Restoring NuGet configuration" - if [ -f $HOME/.nuget/NuGet/NuGet.Config.bak ]; then - log "-> Restoring $HOME/.nuget/NuGet/NuGet.Config from $HOME/.nuget/NuGet/NuGet.Config.bak" - mv -f $HOME/.nuget/NuGet/NuGet.Config.bak $HOME/.nuget/NuGet/NuGet.Config - else - log "-> Removing $HOME/.nuget/NuGet/NuGet.Config" - rm -f $HOME/.nuget/NuGet/NuGet.Config - fi -} - -# pip_install REQUIREMENTS_FILE -function prepare_python_packages() { - log "Hijacking 'pip install' command..." - - # We can't use a $PATH hijack, because we'll be creating a venv - # later which will re-hijack ours with a 'pip' binary. Use a function instead, - # the real logic will reside in "pip_" - - export PYTHON_WHEELS=$dist_root/python - - if [ ! -d "$PYTHON_WHEELS" ]; then - echo "Python build artifacts missing at $PYTHON_WHEELS" >&2 - exit 1 - fi - - export -f pip -} - -function pip() { - pip_ "$@" -} diff --git a/packages/aws-cdk/test/integ/run-against-release b/packages/aws-cdk/test/integ/run-against-release index 5bfcbbfae7e4f..3602333135990 100755 --- a/packages/aws-cdk/test/integ/run-against-release +++ b/packages/aws-cdk/test/integ/run-against-release @@ -1,37 +1,18 @@ #!/bin/bash -# Run a given test against the publicly released packages -# -# - Install the latest CLI somewhere and put it on the PATH. -# - Run the script set -eu -scriptdir=$(cd $(dirname $0) && pwd) -CLI_INSTALLER="${CLI_INSTALLER:=npm}" +# This is a backwards compatibility script. All logic has moved to '@aws-cdk-testing/cli-integ' +# and should be called from there directly in the future. -if [ ${CLI_INSTALLER} = "npm" ]; then - install_command="install" -elif [ ${CLI_INSTALLER} = "yarn" ]; then - install_command="add" -else - echo "CLI_INSTALLER can only be set to either npm or yarn (got ${CLI_INSTALLER})" - exit 1 -fi +# The package MUST be 'npm install'ed from the package repository (`npm install --production` +# will not work because that will resolve devDependencies even though it will not install them), +# but it may not live in a 'node_modules' directory because Jest 27 does not support that. Do contortions. +export INTEG_TOOLS=cli_integ +rm -rf $INTEG_TOOLS && mkdir $INTEG_TOOLS +npm install --prefix $INTEG_TOOLS --no-save @aws-cdk-testing/cli-integ@${RELEASE_TAG:-latest} +mv $($INTEG_TOOLS/node_modules/.bin/test-root)/* $INTEG_TOOLS -# NPM Workspace. Will have CDK CLI installed into it. -npmws=/tmp/cdk-runrelease -rm -rf $npmws -mkdir -p $npmws +# Find the latest CLI version in this tag +npm view aws-cdk@${RELEASE_TAG:-latest} --json > release.json +export VERSION=$(node -pe "require('./release.json').version") -# Install the CLI and put it on the PATH -(cd $npmws && ${CLI_INSTALLER} ${install_command} aws-cdk@${RELEASE_TAG:-latest}) - -# FRAMEWORK_VERSION is the version that will be 'npm install'ed by the tests -if [[ "${FRAMEWORK_VERSION:-}" = "" ]]; then - cli_version=$(cd $npmws && node -p "require('aws-cdk/package.json').version") - export FRAMEWORK_VERSION=${cli_version} -fi - -export PATH=$npmws/node_modules/.bin:$PATH -export TEST_RUNNER=release - -# Run the inner script exec "$@" diff --git a/packages/aws-cdk/test/integ/run-against-repo b/packages/aws-cdk/test/integ/run-against-repo deleted file mode 100755 index 420850f2cd951..0000000000000 --- a/packages/aws-cdk/test/integ/run-against-repo +++ /dev/null @@ -1,22 +0,0 @@ -#!/bin/bash -# Script to run a given test against the monorepo. -# -# Set up paths so that `cdk` binary is on the path, and make it -# so that `npm install` symlinks from the monorepo package directories. -set -eu - -cli_dir=$(cd $(dirname $0)/../.. && pwd) -repo_root=$(cd $cli_dir/../.. && pwd) - -if [[ ! -f $repo_root/package.json ]]; then - echo "$repo_root does not seem to be the root of the aws-cdk repository." >&2 - exit 1 -fi - -export REPO_ROOT="$repo_root" -export ORIGINAL_NPM="$(type -p npm)" -export TEST_RUNNER=repo - -export PATH="$cli_dir/bin:$cli_dir/test/integ/run-wrappers/repo:$PATH" -hash -r -exec "$@" diff --git a/packages/aws-cdk/test/integ/run-wrappers/dist/pip_ b/packages/aws-cdk/test/integ/run-wrappers/dist/pip_ deleted file mode 100755 index ce580ea30a5f7..0000000000000 --- a/packages/aws-cdk/test/integ/run-wrappers/dist/pip_ +++ /dev/null @@ -1,15 +0,0 @@ -#!/bin/bash -set -eu -if [[ "${1:-}" == "install" ]]; then - # We will receive `pip install -r requirements.txt` and need to - # install the packages from the local dist. We previously just - # installed everything under $PYTHON_WHEELS ($PYTHON_WHEELS/*.whl). - # However, there is a bug that arises on v2 when we install everything, - # including both `aws-cdk-lib` and `cx-api`, because the latter is also - # packaged in the former and causes conflicts. This can lead to errors like: - # "Unknown type: aws-cdk-lib.cx_api.CloudAssembly" - # Since we only need aws-cdk-lib anyway (for now), just install it alone. - exec pip install ${PYTHON_WHEELS}/aws_cdk_lib*.whl -fi - -exec pip "$@" diff --git a/packages/aws-cdk/test/integ/run-wrappers/repo/dotnet b/packages/aws-cdk/test/integ/run-wrappers/repo/dotnet deleted file mode 100755 index 7ab610a0e7afd..0000000000000 --- a/packages/aws-cdk/test/integ/run-wrappers/repo/dotnet +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/bash -echo "$0: cannot build this kind of project against the repo. Only pure NPM projects are supported. Use 'run-against-dist' instead." >&2 -exit 1 diff --git a/packages/aws-cdk/test/integ/run-wrappers/repo/mvn b/packages/aws-cdk/test/integ/run-wrappers/repo/mvn deleted file mode 100755 index 7ab610a0e7afd..0000000000000 --- a/packages/aws-cdk/test/integ/run-wrappers/repo/mvn +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/bash -echo "$0: cannot build this kind of project against the repo. Only pure NPM projects are supported. Use 'run-against-dist' instead." >&2 -exit 1 diff --git a/packages/aws-cdk/test/integ/run-wrappers/repo/pip_ b/packages/aws-cdk/test/integ/run-wrappers/repo/pip_ deleted file mode 100755 index 9553f9cefe2d9..0000000000000 --- a/packages/aws-cdk/test/integ/run-wrappers/repo/pip_ +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/bash -echo "$0: cannot build this kind of project against the repo. Only pure NPM projects are supported. Use 'run-against-dist' instead." >&2 -exit 1 - diff --git a/packages/aws-cdk/test/integ/test-cli-regression-against-current-code.sh b/packages/aws-cdk/test/integ/test-cli-regression-against-current-code.sh index ff50aa5f69a2b..c735580c9fd58 100755 --- a/packages/aws-cdk/test/integ/test-cli-regression-against-current-code.sh +++ b/packages/aws-cdk/test/integ/test-cli-regression-against-current-code.sh @@ -1,11 +1,12 @@ #!/bin/bash -# -# Run our integration tests in regression mode against the -# candidate version of the framework, which is the one we just packed. -# -set -euo pipefail -integdir=$(cd $(dirname $0) && pwd) +set -eu +set -x +# This is a backwards compatibilty script. All logic has moved to '@aws-cdk-testing/cli-integ' +# and should be called from there directly. -source ${integdir}/test-cli-regression.bash +# Contract: '@aws-cdk-testing/cli-integ' package is installed in ${INTEG_TOOLS} +previous=$(${INTEG_TOOLS}/bin/query-github last-release --token $GITHUB_TOKEN --prior-to $VERSION) +echo "Previous version is: $previous" -run_regression_against_framework_version CANDIDATE_VERSION +# Old tests, new CLI, new framework +exec $INTEG_TOOLS/bin/download-and-run-old-tests "$previous" --use-cli-release=$VERSION cli-integ-tests diff --git a/packages/aws-cdk/test/integ/test-cli-regression-against-latest-release.sh b/packages/aws-cdk/test/integ/test-cli-regression-against-latest-release.sh index 8b670eb7b793d..8c828fb2138ce 100755 --- a/packages/aws-cdk/test/integ/test-cli-regression-against-latest-release.sh +++ b/packages/aws-cdk/test/integ/test-cli-regression-against-latest-release.sh @@ -1,11 +1,11 @@ #!/bin/bash -# -# Run our integration tests in regression mode against the -# previous version of the framework, relative to the version being packed now. -# -set -euo pipefail -integdir=$(cd $(dirname $0) && pwd) +set -eu +# This is a backwards compatibility script. All logic has moved to '@aws-cdk-testing/cli-integ' +# and should be called from there directly. -source ${integdir}/test-cli-regression.bash +# Contract: '@aws-cdk-testing/cli-integ' package is installed in ${INTEG_TOOLS} +previous=$(${INTEG_TOOLS}/bin/query-github last-release --token $GITHUB_TOKEN --prior-to $VERSION) +echo "Previous version is: $previous" -run_regression_against_framework_version PREVIOUS_VERSION +# Old tests, new CLI, old framework +exec $INTEG_TOOLS/bin/download-and-run-old-tests "$previous" --use-cli-release=$VERSION --framework-version=$previous cli-integ-tests diff --git a/packages/aws-cdk/test/integ/typescript-versions.ts b/packages/aws-cdk/test/integ/typescript-versions.ts deleted file mode 100644 index cf86653b99793..0000000000000 --- a/packages/aws-cdk/test/integ/typescript-versions.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { spawnSync } from 'child_process'; - -const minSupportedVersion = process.argv.slice(2, 3); - -const { stdout } = spawnSync('npm', ['--silent', 'view', `typescript@>=${minSupportedVersion}`, 'version', '--json']); - -const versions: string[] = JSON.parse(stdout); -const minorVersions = Array.from(new Set(versions.map(v => v.split('.').slice(0, 2).join('.')))); - -process.stdout.write(minorVersions.join(' ')); diff --git a/packages/aws-cdk/test/integ/uberpackage/jest.config.js b/packages/aws-cdk/test/integ/uberpackage/jest.config.js deleted file mode 100644 index 1e3fe3d13f96b..0000000000000 --- a/packages/aws-cdk/test/integ/uberpackage/jest.config.js +++ /dev/null @@ -1,15 +0,0 @@ -module.exports = { - moduleFileExtensions: [ - "js", - ], - testMatch: [ - "**/*.integtest.js", - ], - testEnvironment: "node", - bail: 1, - verbose: true, - reporters: [ - "default", - [ "jest-junit", { suiteName: "jest tests", outputDirectory: "coverage" } ] - ] -}; diff --git a/packages/aws-cdk/test/integ/uberpackage/test.sh b/packages/aws-cdk/test/integ/uberpackage/test.sh index 41761b349580e..005ad2ffe1544 100755 --- a/packages/aws-cdk/test/integ/uberpackage/test.sh +++ b/packages/aws-cdk/test/integ/uberpackage/test.sh @@ -1,14 +1,6 @@ #!/bin/bash +set -eu +# This is a backwards compatibility script. All logic has moved to '@aws-cdk-testing/cli-integ' +# and should be called from there directly. -set -euo pipefail - -scriptdir=$(cd $(dirname $0) && pwd) - -echo '~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~' -echo 'UberCDK Integration Tests' -echo '~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~' - -cd $scriptdir - -source ../common/jest-test.bash -invokeJest "$@" +exec ${INTEG_TOOLS}/bin/run-suite --use-cli-release=$VERSION uberpackage diff --git a/packages/aws-cdk/test/rwlock.test.ts b/packages/aws-cdk/test/rwlock.test.ts new file mode 100644 index 0000000000000..d241eaaf96844 --- /dev/null +++ b/packages/aws-cdk/test/rwlock.test.ts @@ -0,0 +1,52 @@ +import * as os from 'os'; +import * as path from 'path'; +import { RWLock } from '../lib/api/util/rwlock'; + +function testDir() { + return path.join(os.tmpdir(), 'rwlock-tests'); +} + +test('writer lock excludes other locks', async () => { + // GIVEN + const lock = new RWLock(testDir()); + const w = await lock.acquireWrite(); + + // WHEN + try { + await expect(lock.acquireWrite()).rejects.toThrow(/currently synthing/); + await expect(lock.acquireRead()).rejects.toThrow(/currently synthing/); + } finally { + await w.release(); + } +}); + +test('reader lock allows other readers but not writers', async () => { + // GIVEN + const lock = new RWLock(testDir()); + const r = await lock.acquireRead(); + + // WHEN + try { + await expect(lock.acquireWrite()).rejects.toThrow(/currently reading/); + + const r2 = await lock.acquireRead(); + await r2.release(); + } finally { + await r.release(); + } +}); + +test('can convert writer to reader lock', async () => { + // GIVEN + const lock = new RWLock(testDir()); + const w = await lock.acquireWrite(); + + // WHEN + const r = await w.convertToReaderLock(); + try { + const r2 = await lock.acquireRead(); + await r2.release(); + } finally { + await r.release(); + } +}); \ No newline at end of file diff --git a/packages/aws-cdk/test/util.ts b/packages/aws-cdk/test/util.ts index 751c1c1ad6bba..97d7e519e9be2 100644 --- a/packages/aws-cdk/test/util.ts +++ b/packages/aws-cdk/test/util.ts @@ -239,3 +239,7 @@ export function withMocked(obj: A, key: function isPromise(object: any): object is Promise { return Promise.resolve(object) === object; } + +export async function sleep(ms: number) { + return new Promise(ok => setTimeout(ok, ms)); +} diff --git a/packages/aws-cdk/test/util/stack-monitor.test.ts b/packages/aws-cdk/test/util/stack-monitor.test.ts index 7e2644b481b9f..12bc5ac80007e 100644 --- a/packages/aws-cdk/test/util/stack-monitor.test.ts +++ b/packages/aws-cdk/test/util/stack-monitor.test.ts @@ -1,5 +1,5 @@ import { StackActivityMonitor, IActivityPrinter, StackActivity } from '../../lib/api/util/cloudformation/stack-activity-monitor'; -import { sleep } from '../integ/helpers/aws'; +import { sleep } from '../util'; import { MockSdk } from './mock-sdk'; let sdk: MockSdk; diff --git a/tools/@aws-cdk/cdk-release/lib/lifecycles/bump.ts b/tools/@aws-cdk/cdk-release/lib/lifecycles/bump.ts index 0a6417cb080d6..055c6bdee3cb9 100644 --- a/tools/@aws-cdk/cdk-release/lib/lifecycles/bump.ts +++ b/tools/@aws-cdk/cdk-release/lib/lifecycles/bump.ts @@ -28,7 +28,7 @@ export async function bump(args: BumpOptions, currentVersion: Versions): Promise const newVersion: Versions = { stableVersion: newStableVersion, - alphaVersion: bumpAlphaReleaseVersion(currentVersion, releaseType), + alphaVersion: bumpAlphaReleaseVersion(currentVersion, newStableVersion, releaseType), }; notify(args, @@ -108,19 +108,43 @@ function getTypePriority(type: string): number { /** * https://github.com/aws/aws-cdk/issues/15581 * We version any alpha modules in one of two ways, depending on the main/stable release. - * If the main release is itself a prerelease (e.g., 2.0.0-rc.17), - * we will increment the current alpha release. + * + * If the main release is itself a prerelease (e.g., 2.0.0-rc.17): + * - if the current alpha version has the same major.minor.patch version as the current stable, + * we probably have a long-running RC candidate that we are actually releasing. Increment the + * current alpha release. + * - if not, then we are probably coming up with a testing RC version for the pipeline. We must + * come up with an alpha version that can never be released publicly, because our version numbers + * must never be the same as any publicly released package. Use '2.0.0-alpha.999' for those. + * * If the main release is not a prerelease, we use the main release version, but with an alpha tag. + * + * This logic is mirrored in the integ tests. */ -function bumpAlphaReleaseVersion(currentVersion: Versions, releaseType: semver.ReleaseType): string | undefined { - if (!currentVersion.alphaVersion) { return undefined; } +function bumpAlphaReleaseVersion(previousVersions: Versions, currentStable: string, releaseType: semver.ReleaseType): string | undefined { + if (!previousVersions.alphaVersion) { return undefined; } + + let newAlphaVersion; + if (releaseType.startsWith('pre')) { + // Prerelease, either long-running or just a unique one to test + const stableV = semver.parse(currentStable); + const alphaV = semver.parse(previousVersions.alphaVersion); + if (!stableV || !alphaV) { + throw new Error(`Could not parse either ${currentStable} or ${previousVersions.alphaVersion} as a version`); + } - const newAlphaVersion = releaseType.startsWith('pre') - ? semver.inc(currentVersion.alphaVersion, releaseType, 'alpha') - : semver.inc(currentVersion.stableVersion, 'pre' + releaseType as semver.ReleaseType, 'alpha'); + if (stableV?.compareMain(alphaV) === 0) { + newAlphaVersion = semver.inc(previousVersions.alphaVersion, releaseType, 'alpha'); + } else { + newAlphaVersion = semver.inc(previousVersions.stableVersion, releaseType as semver.ReleaseType, 'alpha')?.replace(/0$/, '999'); + } + } else { + // Stable release, add `-alpha.0` to the end of the stable release version + newAlphaVersion = semver.inc(previousVersions.stableVersion, 'pre' + releaseType as semver.ReleaseType, 'alpha'); + } if (!newAlphaVersion) { - throw new Error('Could not increment alpha version: ' + currentVersion.alphaVersion); + throw new Error('Could not increment alpha version: ' + previousVersions.alphaVersion); } return newAlphaVersion; } diff --git a/tools/@aws-cdk/cdk-release/test/bump.test.ts b/tools/@aws-cdk/cdk-release/test/bump.test.ts index df0d08eaae828..234b8d0c6fdfd 100644 --- a/tools/@aws-cdk/cdk-release/test/bump.test.ts +++ b/tools/@aws-cdk/cdk-release/test/bump.test.ts @@ -45,7 +45,7 @@ describe('stable versions', () => { describe('alpha versions', () => { - test('for prerelease, bumps existing alpha counter as a prerelease', async () => { + test('long-running prerelease: bumps existing alpha counter as a prerelease', async () => { const currentVersion = { stableVersion: '1.2.0-rc.4', alphaVersion: '1.2.0-alpha.0' }; const bumpedVersion = await bump({ releaseAs: 'minor', versionFile: 'version.json', prerelease: 'rc' }, currentVersion); @@ -55,8 +55,18 @@ describe('alpha versions', () => { }); }); - test('for normal releases, bumps alpha as a prerelease of stable release', async () => { - const currentVersion = { stableVersion: '1.2.0', alphaVersion: '1.1.0-alpha.0' }; + test('one-off prerelease: alpha is a prerelease of stable release with crazy alpha tag', async () => { + const currentVersion = { stableVersion: '1.2.0', alphaVersion: '1.2.0-alpha.0' }; + const bumpedVersion = await bump({ releaseAs: 'minor', versionFile: 'version.json', prerelease: 'rc' }, currentVersion); + + expect(bumpedVersion).toEqual({ + stableVersion: '1.3.0-rc.0', + alphaVersion: '1.3.0-alpha.999', + }); + }); + + test('normal release: alpha is a prerelease of stable release with realistic alpha tag', async () => { + const currentVersion = { stableVersion: '1.2.0', alphaVersion: '1.2.0-alpha.0' }; const bumpedVersion = await bump({ releaseAs: 'minor', versionFile: 'version.json' }, currentVersion); expect(bumpedVersion).toEqual({ diff --git a/tools/@aws-cdk/pkglint/lib/rules.ts b/tools/@aws-cdk/pkglint/lib/rules.ts index 9f257954fcabc..0351e1e4a7a47 100644 --- a/tools/@aws-cdk/pkglint/lib/rules.ts +++ b/tools/@aws-cdk/pkglint/lib/rules.ts @@ -1676,6 +1676,7 @@ export class UbergenPackageVisibility extends ValidationRule { 'cdk', 'cdk-assets', '@aws-cdk/integ-runner', + '@aws-cdk-testing/cli-integ', ]; public validate(pkg: PackageJson): void { diff --git a/yarn.lock b/yarn.lock index d3c6021ad406e..10f4d31070545 100644 --- a/yarn.lock +++ b/yarn.lock @@ -347,6 +347,11 @@ resolved "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== +"@colors/colors@1.5.0": + version "1.5.0" + resolved "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz#bb504579c1cae923e6576a4f5da43d25f97bdbd9" + integrity sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ== + "@cspotcode/source-map-support@^0.8.0": version "0.8.1" resolved "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz#00629c35a688e05a88b1cda684fb9d5e73f000a1" @@ -537,6 +542,11 @@ resolved "https://registry.npmjs.org/@iarna/toml/-/toml-2.2.5.tgz#b32366c89b43c6f8cefbdefac778b9c828e3ba8c" integrity sha512-trnsAYxU3xnS1gPHPyU961coFyLkh4gAD/0zQ5mymY4yOZ+CYvsPqUbOFSw0aDM4y0tV7tiFxL/1XfXPNC6IPg== +"@isaacs/string-locale-compare@^1.0.1", "@isaacs/string-locale-compare@^1.1.0": + version "1.1.0" + resolved "https://registry.npmjs.org/@isaacs/string-locale-compare/-/string-locale-compare-1.1.0.tgz#291c227e93fd407a96ecd59879a35809120e432b" + integrity sha512-SQ7Kzhh9+D+ZW9MA0zkYv3VXhIDNx+LzM6EJ+/65I3QY+enU6Itte7E5XX7EWrqLW2FN4n06GWzBnPoC3th2aQ== + "@istanbuljs/load-nyc-config@^1.0.0": version "1.1.0" resolved "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz#fd3db1d59ecf7cf121e80650bb86712f9b55eced" @@ -1488,11 +1498,67 @@ "@nodelib/fs.scandir" "2.1.5" fastq "^1.6.0" -"@npmcli/ci-detect@^1.0.0": +"@npmcli/arborist@^2.3.0", "@npmcli/arborist@^2.5.0", "@npmcli/arborist@^2.9.0": + version "2.10.0" + resolved "https://registry.npmjs.org/@npmcli/arborist/-/arborist-2.10.0.tgz#424c2d73a7ae59c960b0cc7f74fed043e4316c2c" + integrity sha512-CLnD+zXG9oijEEzViimz8fbOoFVb7hoypiaf7p6giJhvYtrxLAyY3cZAMPIFQvsG731+02eMDp3LqVBNo7BaZA== + dependencies: + "@isaacs/string-locale-compare" "^1.0.1" + "@npmcli/installed-package-contents" "^1.0.7" + "@npmcli/map-workspaces" "^1.0.2" + "@npmcli/metavuln-calculator" "^1.1.0" + "@npmcli/move-file" "^1.1.0" + "@npmcli/name-from-folder" "^1.0.1" + "@npmcli/node-gyp" "^1.0.1" + "@npmcli/package-json" "^1.0.1" + "@npmcli/run-script" "^1.8.2" + bin-links "^2.2.1" + cacache "^15.0.3" + common-ancestor-path "^1.0.1" + json-parse-even-better-errors "^2.3.1" + json-stringify-nice "^1.1.4" + mkdirp "^1.0.4" + mkdirp-infer-owner "^2.0.0" + npm-install-checks "^4.0.0" + npm-package-arg "^8.1.5" + npm-pick-manifest "^6.1.0" + npm-registry-fetch "^11.0.0" + pacote "^11.3.5" + parse-conflict-json "^1.1.1" + proc-log "^1.0.0" + promise-all-reject-late "^1.0.0" + promise-call-limit "^1.0.1" + read-package-json-fast "^2.0.2" + readdir-scoped-modules "^1.1.0" + rimraf "^3.0.2" + semver "^7.3.5" + ssri "^8.0.1" + treeverse "^1.0.4" + walk-up-path "^1.0.0" + +"@npmcli/ci-detect@^1.0.0", "@npmcli/ci-detect@^1.2.0", "@npmcli/ci-detect@^1.3.0": version "1.4.0" resolved "https://registry.npmjs.org/@npmcli/ci-detect/-/ci-detect-1.4.0.tgz#18478bbaa900c37bfbd8a2006a6262c62e8b0fe1" integrity sha512-3BGrt6FLjqM6br5AhWRKTr3u5GIVkjRYeAFrMp3HjnfICrg4xOrVRwFavKT6tsp++bq5dluL5t8ME/Nha/6c1Q== +"@npmcli/config@^2.3.0": + version "2.4.0" + resolved "https://registry.npmjs.org/@npmcli/config/-/config-2.4.0.tgz#1447b0274f9502871dabd3ab1d8302472d515b1f" + integrity sha512-fwxu/zaZnvBJohXM3igzqa3P1IVYWi5N343XcKvKkJbAx+rTqegS5tAul4NLiMPQh6WoS5a4er6oo/ieUx1f4g== + dependencies: + ini "^2.0.0" + mkdirp-infer-owner "^2.0.0" + nopt "^5.0.0" + semver "^7.3.4" + walk-up-path "^1.0.0" + +"@npmcli/disparity-colors@^1.0.1": + version "1.0.1" + resolved "https://registry.npmjs.org/@npmcli/disparity-colors/-/disparity-colors-1.0.1.tgz#b23c864c9658f9f0318d5aa6d17986619989535c" + integrity sha512-kQ1aCTTU45mPXN+pdAaRxlxr3OunkyztjbbxDY/aIcPS5CnCUrx+1+NvA6pTcYR7wmLZe37+Mi5v3nfbwPxq3A== + dependencies: + ansi-styles "^4.3.0" + "@npmcli/fs@^1.0.0": version "1.1.1" resolved "https://registry.npmjs.org/@npmcli/fs/-/fs-1.1.1.tgz#72f719fe935e687c56a4faecf3c03d06ba593257" @@ -1509,7 +1575,7 @@ "@gar/promisify" "^1.1.3" semver "^7.3.5" -"@npmcli/git@^2.1.0": +"@npmcli/git@^2.0.7", "@npmcli/git@^2.1.0": version "2.1.0" resolved "https://registry.npmjs.org/@npmcli/git/-/git-2.1.0.tgz#2fbd77e147530247d37f325930d457b3ebe894f6" integrity sha512-/hBFX/QG1b+N7PZBFs0bi+evgRZcK9nWBxQKZkGoXUT5hJSwl5c4d7y8/hm+NQZRPhQ67RzFaj5UM9YeyKoryw== @@ -1546,7 +1612,26 @@ npm-bundled "^1.1.1" npm-normalize-package-bin "^1.0.1" -"@npmcli/move-file@^1.0.1": +"@npmcli/map-workspaces@^1.0.2", "@npmcli/map-workspaces@^1.0.4": + version "1.0.4" + resolved "https://registry.npmjs.org/@npmcli/map-workspaces/-/map-workspaces-1.0.4.tgz#915708b55afa25e20bc2c14a766c124c2c5d4cab" + integrity sha512-wVR8QxhyXsFcD/cORtJwGQodeeaDf0OxcHie8ema4VgFeqwYkFsDPnSrIRSytX8xR6nKPAH89WnwTcaU608b/Q== + dependencies: + "@npmcli/name-from-folder" "^1.0.1" + glob "^7.1.6" + minimatch "^3.0.4" + read-package-json-fast "^2.0.1" + +"@npmcli/metavuln-calculator@^1.1.0": + version "1.1.1" + resolved "https://registry.npmjs.org/@npmcli/metavuln-calculator/-/metavuln-calculator-1.1.1.tgz#2f95ff3c6d88b366dd70de1c3f304267c631b458" + integrity sha512-9xe+ZZ1iGVaUovBVFI9h3qW+UuECUzhvZPxK9RaEA2mjU26o5D0JloGYWwLYvQELJNmBdQB6rrpuN8jni6LwzQ== + dependencies: + cacache "^15.0.5" + pacote "^11.1.11" + semver "^7.3.2" + +"@npmcli/move-file@^1.0.1", "@npmcli/move-file@^1.1.0": version "1.1.2" resolved "https://registry.npmjs.org/@npmcli/move-file/-/move-file-1.1.2.tgz#1a82c3e372f7cae9253eb66d72543d6b8685c674" integrity sha512-1SUf/Cg2GzGDyaf15aR9St9TWlb+XvbZXWpDx8YKs7MLzMH/BCeopv+y9vzrzgkfykCGuWOlSu3mZhj2+FQcrg== @@ -1562,7 +1647,12 @@ mkdirp "^1.0.4" rimraf "^3.0.2" -"@npmcli/node-gyp@^1.0.2": +"@npmcli/name-from-folder@^1.0.1": + version "1.0.1" + resolved "https://registry.npmjs.org/@npmcli/name-from-folder/-/name-from-folder-1.0.1.tgz#77ecd0a4fcb772ba6fe927e2e2e155fbec2e6b1a" + integrity sha512-qq3oEfcLFwNfEYOQ8HLimRGKlD8WSeGEdtUa7hmzpR8Sa7haL1KVQrvgO6wqMjhWFFVjgtrh1gIxDz+P8sjUaA== + +"@npmcli/node-gyp@^1.0.1", "@npmcli/node-gyp@^1.0.2": version "1.0.3" resolved "https://registry.npmjs.org/@npmcli/node-gyp/-/node-gyp-1.0.3.tgz#a912e637418ffc5f2db375e93b85837691a43a33" integrity sha512-fnkhw+fmX65kiLqk6E3BFLXNC26rUhK90zVwe2yncPliVT/Qos3xjhTLE59Df8KnPlcwIERXKVlU1bXoUQ+liA== @@ -1572,6 +1662,13 @@ resolved "https://registry.npmjs.org/@npmcli/node-gyp/-/node-gyp-2.0.0.tgz#8c20e53e34e9078d18815c1d2dda6f2420d75e35" integrity sha512-doNI35wIe3bBaEgrlPfdJPaCpUR89pJWep4Hq3aRdh6gKazIVWfs0jHttvSSoq47ZXgC7h73kDsUl8AoIQUB+A== +"@npmcli/package-json@^1.0.1": + version "1.0.1" + resolved "https://registry.npmjs.org/@npmcli/package-json/-/package-json-1.0.1.tgz#1ed42f00febe5293c3502fd0ef785647355f6e89" + integrity sha512-y6jnu76E9C23osz8gEMBayZmaZ69vFOIk8vR1FJL/wbEJ54+9aVG9rLTjQKSXfgYZEr50nw1txBBFfBZZe+bYg== + dependencies: + json-parse-even-better-errors "^2.3.1" + "@npmcli/promise-spawn@^1.2.0", "@npmcli/promise-spawn@^1.3.2": version "1.3.2" resolved "https://registry.npmjs.org/@npmcli/promise-spawn/-/promise-spawn-1.3.2.tgz#42d4e56a8e9274fba180dabc0aea6e38f29274f5" @@ -1586,7 +1683,7 @@ dependencies: infer-owner "^1.0.4" -"@npmcli/run-script@^1.8.2": +"@npmcli/run-script@^1.8.2", "@npmcli/run-script@^1.8.3", "@npmcli/run-script@^1.8.4", "@npmcli/run-script@^1.8.6": version "1.8.6" resolved "https://registry.npmjs.org/@npmcli/run-script/-/run-script-1.8.6.tgz#18314802a6660b0d4baa4c3afe7f1ad39d8c28b7" integrity sha512-e42bVZnC6VluBZBAFEr3YrdqSspG3bgilyg4nSLBJ7TRGNCzxHa92XAHxQBLYg0BmgwO4b2mf3h/l5EkEWRn3g== @@ -1912,7 +2009,7 @@ dependencies: "@types/node" "*" -"@types/fs-extra@^9.0.13": +"@types/fs-extra@^9.0.0", "@types/fs-extra@^9.0.13": version "9.0.13" resolved "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-9.0.13.tgz#7594fbae04fe7f1918ce8b3d213f74ff44ac1f45" integrity sha512-nEnwB++1u5lVDM2UI4c1+5R+FYaKfaAzS4OococimjVm3nQw3TuzH5UNsocrcTBbhnerblyHj4A49qXbIiZdpA== @@ -2046,6 +2143,11 @@ resolved "https://registry.npmjs.org/@types/node/-/node-18.11.15.tgz#de0e1fbd2b22b962d45971431e2ae696643d3f5d" integrity sha512-VkhBbVo2+2oozlkdHXLrb3zjsRkpdnaU2bXmX8Wgle3PUi569eLRaHGlgETQHR7lLL1w7GiG3h9SnePhxNDecw== +"@types/node@^14.18.31": + version "14.18.35" + resolved "https://registry.npmjs.org/@types/node/-/node-14.18.35.tgz#879c4659cb7b3fe515844f029c75079c941bb65c" + integrity sha512-2ATO8pfhG1kDvw4Lc4C0GXIMSQFFJBCo/R1fSgTwmUlq5oy95LXyjDQinsRVgQY6gp6ghh3H91wk9ES5/5C+Tw== + "@types/node@^14.18.34": version "14.18.34" resolved "https://registry.npmjs.org/@types/node/-/node-14.18.34.tgz#cd2e6fa0dbfb08a62582a7b967558e73c32061ec" @@ -2061,6 +2163,13 @@ resolved "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.1.tgz#d3357479a0fdfdd5907fe67e17e0a85c906e1301" integrity sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw== +"@types/npm@^7.19.0": + version "7.19.0" + resolved "https://registry.npmjs.org/@types/npm/-/npm-7.19.0.tgz#a62382cea8ca8ef8452553e3d5daa68a331cda70" + integrity sha512-K/w+k8SnDjdQoK2fkUl9fHLAiVVmdFgdZ2/iGFuaaQC+wwaNdDQRTFaoCEYYrfCMbuVkhL3Lgqbi+p5d5I1lSg== + dependencies: + "@types/node" "*" + "@types/parse-json@^4.0.0": version "4.0.0" resolved "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0" @@ -2179,6 +2288,13 @@ dependencies: "@types/yargs-parser" "*" +"@types/yargs@^17.0.13": + version "17.0.13" + resolved "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.13.tgz#34cced675ca1b1d51fcf4d34c3c6f0fa142a5c76" + integrity sha512-9sWaruZk2JGxIQU+IhI1fhPYRcQ0UuTNuKuCW9bR5fp7qi2Llf7WDzNa17Cy7TKnh3cdxDOiyTu6gaLS0eDatg== + dependencies: + "@types/yargs-parser" "*" + "@types/yarnpkg__lockfile@^1.1.5": version "1.1.5" resolved "https://registry.npmjs.org/@types/yarnpkg__lockfile/-/yarnpkg__lockfile-1.1.5.tgz#9639020e1fb65120a2f4387db8f1e8b63efdf229" @@ -2360,7 +2476,7 @@ abab@^2.0.3, abab@^2.0.5: resolved "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz#41b80f2c871d19686216b82309231cfd3cb3d291" integrity sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA== -abbrev@1, abbrev@^1.0.0: +abbrev@1, abbrev@^1.0.0, abbrev@~1.1.1: version "1.1.1" resolved "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q== @@ -2483,7 +2599,7 @@ ansi-styles@^3.2.1: dependencies: color-convert "^1.9.0" -ansi-styles@^4.0.0, ansi-styles@^4.1.0: +ansi-styles@^4.0.0, ansi-styles@^4.1.0, ansi-styles@^4.3.0: version "4.3.0" resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== @@ -2495,6 +2611,16 @@ ansi-styles@^5.0.0: resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz#07449690ad45777d1924ac2abb2fc8895dba836b" integrity sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA== +ansicolors@~0.3.2: + version "0.3.2" + resolved "https://registry.npmjs.org/ansicolors/-/ansicolors-0.3.2.tgz#665597de86a9ffe3aa9bfbe6cae5c6ea426b4979" + integrity sha512-QXu7BPrP29VllRxH8GwB7x5iX5qWKAAMLqKQGWTeLWVlNHNOpVMJ91dsxQAIWXpjuW5wqvxu3Jd/nRjrJ+0pqg== + +ansistyles@~0.1.3: + version "0.1.3" + resolved "https://registry.npmjs.org/ansistyles/-/ansistyles-0.1.3.tgz#5de60415bda071bb37127854c864f41b23254539" + integrity sha512-6QWEyvMgIXX0eO972y7YPBLSBsq7UWKFAoNNTLGaOJ9bstcEL9sCbcjf96dVfNDdUsRoGOK82vWFJlKApXds7g== + anymatch@^3.0.3, anymatch@~3.1.2: version "3.1.3" resolved "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz#790c58b19ba1720a84205b57c618d5ad8524973e" @@ -2559,11 +2685,19 @@ archiver@^5.3.1: tar-stream "^2.2.0" zip-stream "^4.1.0" -archy@^1.0.0: +archy@^1.0.0, archy@~1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz#f9c8c13757cc1dd7bc379ac77b2c62a5c2868c40" integrity sha512-Xg+9RwCg/0p32teKdGMPTPnVXKD0w3DfHnFTficozsAgsvq2XenPJq/MYpzzQ/v8zrOyJn6Ds39VA4JIDwFfqw== +are-we-there-yet@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz#372e0e7bd279d8e94c653aaa1f67200884bf3e1c" + integrity sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw== + dependencies: + delegates "^1.0.0" + readable-stream "^3.6.0" + are-we-there-yet@^3.0.0: version "3.0.1" resolved "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-3.0.1.tgz#679df222b278c64f2cdba1175cdc00b0d96164bd" @@ -2864,7 +2998,19 @@ before-after-hook@^2.2.0: resolved "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.2.3.tgz#c51e809c81a4e354084422b9b26bad88249c517c" integrity sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ== -binary-extensions@^2.0.0: +bin-links@^2.2.1: + version "2.3.0" + resolved "https://registry.npmjs.org/bin-links/-/bin-links-2.3.0.tgz#1ff241c86d2c29b24ae52f49544db5d78a4eb967" + integrity sha512-JzrOLHLwX2zMqKdyYZjkDgQGT+kHDkIhv2/IK2lJ00qLxV4TmFoHi8drDBb6H5Zrz1YfgHkai4e2MGPqnoUhqA== + dependencies: + cmd-shim "^4.0.1" + mkdirp-infer-owner "^2.0.0" + npm-normalize-package-bin "^1.0.0" + read-cmd-shim "^2.0.0" + rimraf "^3.0.0" + write-file-atomic "^3.0.3" + +binary-extensions@^2.0.0, binary-extensions@^2.2.0: version "2.2.0" resolved "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d" integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA== @@ -3002,7 +3148,7 @@ bytes@3.1.2: resolved "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5" integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg== -cacache@^15.0.5, cacache@^15.2.0: +cacache@^15.0.3, cacache@^15.0.5, cacache@^15.2.0, cacache@^15.3.0: version "15.3.0" resolved "https://registry.npmjs.org/cacache/-/cacache-15.3.0.tgz#dc85380fb2f556fe3dda4c719bfa0ec875a7f1eb" integrity sha512-VVdYzXEn+cnbXpFgWs5hTT7OScegHVmLhJIR8Ufqk3iFD6A6j5iSX1KuBTfNEv4tdJWE2PzA6IVFtcLC7fN9wQ== @@ -3223,6 +3369,13 @@ ci-info@^3.2.0: resolved "https://registry.npmjs.org/ci-info/-/ci-info-3.7.0.tgz#6d01b3696c59915b6ce057e4aa4adfc2fa25f5ef" integrity sha512-2CpRNYmImPx+RXKLq6jko/L07phmS9I02TyqkcNU20GCF/GgaWvc58hPtjxDX8lPpkdwc9sNh72V9k00S7ezog== +cidr-regex@^3.1.1: + version "3.1.1" + resolved "https://registry.npmjs.org/cidr-regex/-/cidr-regex-3.1.1.tgz#ba1972c57c66f61875f18fd7dd487469770b571d" + integrity sha512-RBqYd32aDwbCMFJRL6wHOlDNYJsPNTt8vC82ErHF5vKt8QQzxm1FrkW8s/R5pVrXMf17sba09Uoy91PKiddAsw== + dependencies: + ip-regex "^4.1.0" + cint@^8.2.1: version "8.2.1" resolved "https://registry.npmjs.org/cint/-/cint-8.2.1.tgz#70386b1b48e2773d0d63166a55aff94ef4456a12" @@ -3254,6 +3407,14 @@ cli-color@^2.0.0: memoizee "^0.4.15" timers-ext "^0.1.7" +cli-columns@^3.1.2: + version "3.1.2" + resolved "https://registry.npmjs.org/cli-columns/-/cli-columns-3.1.2.tgz#6732d972979efc2ae444a1f08e08fa139c96a18e" + integrity sha512-iQYpDgpPPmCjn534ikQOhi+ydP6uMar+DtJ6a0In4aGL/PKqWfao75s6eF81quQQaz7isGz+goNECLARRZswdg== + dependencies: + string-width "^2.0.0" + strip-ansi "^3.0.1" + cli-cursor@^3.1.0: version "3.1.0" resolved "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz#264305a7ae490d1d03bf0c9ba7c925d1753af307" @@ -3266,6 +3427,15 @@ cli-spinners@^2.5.0: resolved "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.7.0.tgz#f815fd30b5f9eaac02db604c7a231ed7cb2f797a" integrity sha512-qu3pN8Y3qHNgE2AFweciB1IfMnmZ/fsNTEE+NOFjmGB2F/7rLhnhzppvpCnN4FovtP26k8lHyy9ptEbNwWFLzw== +cli-table3@^0.6.0: + version "0.6.3" + resolved "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.3.tgz#61ab765aac156b52f222954ffc607a6f01dbeeb2" + integrity sha512-w5Jac5SykAeZJKntOxJCrm63Eg5/4dhMWIcuTbo9rpE+brgaSZo0RuNJZeOyMgsUdhDeojvgyQLmjI+K50ZGyg== + dependencies: + string-width "^4.2.0" + optionalDependencies: + "@colors/colors" "1.5.0" + cli-table@^0.3.11: version "0.3.11" resolved "https://registry.npmjs.org/cli-table/-/cli-table-0.3.11.tgz#ac69cdecbe81dccdba4889b9a18b7da312a9d3ee" @@ -3331,7 +3501,7 @@ clone@^2.1.2: resolved "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz#1b7f4b9f591f1e8f83670401600345a02887435f" integrity sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w== -cmd-shim@^4.1.0: +cmd-shim@^4.0.1, cmd-shim@^4.1.0: version "4.1.0" resolved "https://registry.npmjs.org/cmd-shim/-/cmd-shim-4.1.0.tgz#b3a904a6743e9fede4148c6f3800bf2a08135bdd" integrity sha512-lb9L7EM4I/ZRVuljLPEtUJOP+xiQVknZ4ZMpMgEp4JzNldPb27HU03hi6K1/6CoIuit/Zm/LQXySErFeXxDprw== @@ -3381,7 +3551,7 @@ color-name@^1.1.4, color-name@~1.1.4: resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== -color-support@^1.1.3: +color-support@^1.1.2, color-support@^1.1.3: version "1.1.3" resolved "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz#93834379a1cc9a0c61f82f52f0d04322251bd5a2" integrity sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg== @@ -3399,6 +3569,14 @@ columnify@^1.5.4: strip-ansi "^6.0.1" wcwidth "^1.0.0" +columnify@~1.5.4: + version "1.5.4" + resolved "https://registry.npmjs.org/columnify/-/columnify-1.5.4.tgz#4737ddf1c7b69a8a7c340570782e947eec8e78bb" + integrity sha512-rFl+iXVT1nhLQPfGDw+3WcS8rmm7XsLKUmhsGE3ihzzpIikeGrTaZPIRKYWeLsLBypsHzjXIvYEltVUZS84XxQ== + dependencies: + strip-ansi "^3.0.0" + wcwidth "^1.0.0" + combined-stream@^1.0.6, combined-stream@^1.0.8, combined-stream@~1.0.6: version "1.0.8" resolved "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" @@ -3432,6 +3610,11 @@ comment-json@4.2.2: has-own-prop "^2.0.0" repeat-string "^1.6.1" +common-ancestor-path@^1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/common-ancestor-path/-/common-ancestor-path-1.0.1.tgz#4f7d2d1394d91b7abdf51871c62f71eadb0182a7" + integrity sha512-L3sHRo1pXXEqX8VU28kfgUY+YGsk09hPqZiZmLacNib6XNTCM8ubYeT7ryXQw8asB1sKgcU5lkB7ONug08aB8w== + commondir@^1.0.1: version "1.0.1" resolved "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b" @@ -4866,6 +5049,11 @@ fast-memoize@^2.5.2: resolved "https://registry.npmjs.org/fast-memoize/-/fast-memoize-2.5.2.tgz#79e3bb6a4ec867ea40ba0e7146816f6cdce9b57e" integrity sha512-Ue0LwpDYErFbmNnZSF0UH6eImUwDmogUO1jyE+JbN2gsQz/jICm1Ve7t9QT0rNSsfJt+Hs4/S3GnsDVjL4HVrw== +fastest-levenshtein@^1.0.12: + version "1.0.16" + resolved "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz#210e61b6ff181de91ea9b3d1b84fdedd47e034e5" + integrity sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg== + fastq@^1.6.0: version "1.14.0" resolved "https://registry.npmjs.org/fastq/-/fastq-1.14.0.tgz#107f69d7295b11e0fccc264e1fc6389f623731ce" @@ -5168,6 +5356,21 @@ functions-have-names@^1.2.2: resolved "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz#0404fe4ee2ba2f607f0e0ec3c80bae994133b834" integrity sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ== +gauge@^3.0.0: + version "3.0.2" + resolved "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz#03bf4441c044383908bcfa0656ad91803259b395" + integrity sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q== + dependencies: + aproba "^1.0.3 || ^2.0.0" + color-support "^1.1.2" + console-control-strings "^1.0.0" + has-unicode "^2.0.1" + object-assign "^4.1.1" + signal-exit "^3.0.0" + string-width "^4.2.3" + strip-ansi "^6.0.1" + wide-align "^1.1.2" + gauge@^4.0.3: version "4.0.4" resolved "https://registry.npmjs.org/gauge/-/gauge-4.0.4.tgz#52ff0652f2bbf607a989793d53b751bef2328dce" @@ -5452,7 +5655,7 @@ got@^9.6.0: to-readable-stream "^1.0.0" url-parse-lax "^3.0.0" -graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.10, graceful-fs@^4.2.2, graceful-fs@^4.2.3, graceful-fs@^4.2.4, graceful-fs@^4.2.6, graceful-fs@^4.2.9: +graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.10, graceful-fs@^4.2.2, graceful-fs@^4.2.3, graceful-fs@^4.2.4, graceful-fs@^4.2.6, graceful-fs@^4.2.8, graceful-fs@^4.2.9: version "4.2.10" resolved "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz#147d3a006da4ca3ce14728c7aefc287c367d7a6c" integrity sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA== @@ -5573,7 +5776,7 @@ hosted-git-info@^2.1.4: resolved "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz#dffc0bf9a21c02209090f2aa69429e1414daf3f9" integrity sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw== -hosted-git-info@^4.0.0, hosted-git-info@^4.0.1: +hosted-git-info@^4.0.0, hosted-git-info@^4.0.1, hosted-git-info@^4.0.2: version "4.1.0" resolved "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz#827b82867e9ff1c8d0c4d9d53880397d2c86d224" integrity sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA== @@ -5784,7 +5987,7 @@ ini@~3.0.0: resolved "https://registry.npmjs.org/ini/-/ini-3.0.1.tgz#c76ec81007875bc44d544ff7a11a55d12294102d" integrity sha512-it4HyVAUTKBc6m8e1iXWvXSTdndF7HbdN713+kvLrymxTaU4AUBWrJ4vEooP+V7fexnVD3LKcBshjGGPefSMUQ== -init-package-json@^2.0.2: +init-package-json@^2.0.2, init-package-json@^2.0.5: version "2.0.5" resolved "https://registry.npmjs.org/init-package-json/-/init-package-json-2.0.5.tgz#78b85f3c36014db42d8f32117252504f68022646" integrity sha512-u1uGAtEFu3VA6HNl/yUWw57jmKEMx8SKOxHhxjGnOFUiIlFnohKDFg4ZrPpv9wWqk44nDxGJAtqjdQFm+9XXQA== @@ -5830,6 +6033,11 @@ interpret@^1.0.0: resolved "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz#665ab8bc4da27a774a40584e812e3e0fa45b1a1e" integrity sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA== +ip-regex@^4.1.0: + version "4.3.0" + resolved "https://registry.npmjs.org/ip-regex/-/ip-regex-4.3.0.tgz#687275ab0f57fa76978ff8f4dddc8a23d5990db5" + integrity sha512-B9ZWJxHHOHUhUjCPrMpLD4xEq35bUTClHM1S6CBU5ixQnkZmwipwgc96vAd7AAGM9TGHvJR+Uss+/Ak6UphK+Q== + ip@^1.1.5: version "1.1.8" resolved "https://registry.npmjs.org/ip/-/ip-1.1.8.tgz#ae05948f6b075435ed3307acce04629da8cdbf48" @@ -5892,6 +6100,13 @@ is-ci@^2.0.0: dependencies: ci-info "^2.0.0" +is-cidr@^4.0.2: + version "4.0.2" + resolved "https://registry.npmjs.org/is-cidr/-/is-cidr-4.0.2.tgz#94c7585e4c6c77ceabf920f8cde51b8c0fda8814" + integrity sha512-z4a1ENUajDbEl/Q6/pVBpTR1nBjjEE1X7qb7bmWYanNnPoKAvUCPFKeXV6Fe4mgTkWKBqiHIcwsI3SndiO5FeA== + dependencies: + cidr-regex "^3.1.1" + is-core-module@^2.5.0, is-core-module@^2.8.1, is-core-module@^2.9.0: version "2.11.0" resolved "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz#ad4cb3e3863e814523c96f3f58d26cc570ff0144" @@ -6449,6 +6664,16 @@ jest-junit@^13, jest-junit@^13.2.0: uuid "^8.3.2" xml "^1.0.1" +jest-junit@^14.0.0: + version "14.0.1" + resolved "https://registry.npmjs.org/jest-junit/-/jest-junit-14.0.1.tgz#5b357d6f5d333459585d628a24cd48b5bbc92ba2" + integrity sha512-h7/wwzPbllgpQhhVcRzRC76/cc89GlazThoV1fDxcALkf26IIlRsu/AcTG64f4nR2WPE3Cbd+i/sVf+NCUHrWQ== + dependencies: + mkdirp "^1.0.4" + strip-ansi "^6.0.1" + uuid "^8.3.2" + xml "^1.0.1" + jest-leak-detector@^27.5.1: version "27.5.1" resolved "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-27.5.1.tgz#6ec9d54c3579dd6e3e66d70e3498adf80fde3fb8" @@ -6887,6 +7112,11 @@ json-stable-stringify-without-jsonify@^1.0.1: resolved "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw== +json-stringify-nice@^1.1.4: + version "1.1.4" + resolved "https://registry.npmjs.org/json-stringify-nice/-/json-stringify-nice-1.1.4.tgz#2c937962b80181d3f317dd39aa323e14f5a60a67" + integrity sha512-5Z5RFW63yxReJ7vANgW6eZFGWaQvnPE3WNmZoOJrSkGju2etKA2L5rrOa1sm877TVTFt57A80BH1bArcmlLfPw== + json-stringify-safe@^5.0.1, json-stringify-safe@~5.0.1: version "5.0.1" resolved "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" @@ -6960,6 +7190,16 @@ jszip@^3.10.1: readable-stream "~2.3.6" setimmediate "^1.0.5" +just-diff-apply@^3.0.0: + version "3.1.2" + resolved "https://registry.npmjs.org/just-diff-apply/-/just-diff-apply-3.1.2.tgz#710d8cda00c65dc4e692df50dbe9bac5581c2193" + integrity sha512-TCa7ZdxCeq6q3Rgms2JCRHTCfWAETPZ8SzYUbkYF6KR3I03sN29DaOIC+xyWboIcMvjAsD5iG2u/RWzHD8XpgQ== + +just-diff@^3.0.1: + version "3.1.1" + resolved "https://registry.npmjs.org/just-diff/-/just-diff-3.1.1.tgz#d50c597c6fd4776495308c63bdee1b6839082647" + integrity sha512-sdMWKjRq8qWZEjDcVA6llnUT8RDEBIfOiGpYFPYa9u+2c39JCsejktSP7mj5eRid5EIvTzIpQ2kDOCw1Nq9BjQ== + just-extend@^4.0.2: version "4.2.1" resolved "https://registry.npmjs.org/just-extend/-/just-extend-4.2.1.tgz#ef5e589afb61e5d66b24eca749409a8939a8c744" @@ -7066,7 +7306,7 @@ levn@~0.3.0: prelude-ls "~1.1.2" type-check "~0.3.2" -libnpmaccess@^4.0.1: +libnpmaccess@^4.0.1, libnpmaccess@^4.0.2: version "4.0.3" resolved "https://registry.npmjs.org/libnpmaccess/-/libnpmaccess-4.0.3.tgz#dfb0e5b0a53c315a2610d300e46b4ddeb66e7eec" integrity sha512-sPeTSNImksm8O2b6/pf3ikv4N567ERYEpeKRPSmqlNt1dTZbvgpJIzg5vAhXHpw2ISBsELFRelk0jEahj1c6nQ== @@ -7085,7 +7325,70 @@ libnpmconfig@^1.2.1: find-up "^3.0.0" ini "^1.3.5" -libnpmpublish@^4.0.0: +libnpmdiff@^2.0.4: + version "2.0.4" + resolved "https://registry.npmjs.org/libnpmdiff/-/libnpmdiff-2.0.4.tgz#bb1687992b1a97a8ea4a32f58ad7c7f92de53b74" + integrity sha512-q3zWePOJLHwsLEUjZw3Kyu/MJMYfl4tWCg78Vl6QGSfm4aXBUSVzMzjJ6jGiyarsT4d+1NH4B1gxfs62/+y9iQ== + dependencies: + "@npmcli/disparity-colors" "^1.0.1" + "@npmcli/installed-package-contents" "^1.0.7" + binary-extensions "^2.2.0" + diff "^5.0.0" + minimatch "^3.0.4" + npm-package-arg "^8.1.1" + pacote "^11.3.0" + tar "^6.1.0" + +libnpmexec@^2.0.1: + version "2.0.1" + resolved "https://registry.npmjs.org/libnpmexec/-/libnpmexec-2.0.1.tgz#729ae3e15a3ba225964ccf248117a75d311eeb73" + integrity sha512-4SqBB7eJvJWmUKNF42Q5qTOn20DRjEE4TgvEh2yneKlAiRlwlhuS9MNR45juWwmoURJlf2K43bozlVt7OZiIOw== + dependencies: + "@npmcli/arborist" "^2.3.0" + "@npmcli/ci-detect" "^1.3.0" + "@npmcli/run-script" "^1.8.4" + chalk "^4.1.0" + mkdirp-infer-owner "^2.0.0" + npm-package-arg "^8.1.2" + pacote "^11.3.1" + proc-log "^1.0.0" + read "^1.0.7" + read-package-json-fast "^2.0.2" + walk-up-path "^1.0.0" + +libnpmfund@^1.1.0: + version "1.1.0" + resolved "https://registry.npmjs.org/libnpmfund/-/libnpmfund-1.1.0.tgz#ee91313905b3194b900530efa339bc3f9fc4e5c4" + integrity sha512-Kfmh3pLS5/RGKG5WXEig8mjahPVOxkik6lsbH4iX0si1xxNi6eeUh/+nF1MD+2cgalsQif3O5qyr6mNz2ryJrQ== + dependencies: + "@npmcli/arborist" "^2.5.0" + +libnpmhook@^6.0.2: + version "6.0.3" + resolved "https://registry.npmjs.org/libnpmhook/-/libnpmhook-6.0.3.tgz#1d7f0d7e6a7932fbf7ce0881fdb0ed8bf8748a30" + integrity sha512-3fmkZJibIybzmAvxJ65PeV3NzRc0m4xmYt6scui5msocThbEp4sKFT80FhgrCERYDjlUuFahU6zFNbJDHbQ++g== + dependencies: + aproba "^2.0.0" + npm-registry-fetch "^11.0.0" + +libnpmorg@^2.0.2: + version "2.0.3" + resolved "https://registry.npmjs.org/libnpmorg/-/libnpmorg-2.0.3.tgz#4e605d4113dfa16792d75343824a0625c76703bc" + integrity sha512-JSGl3HFeiRFUZOUlGdiNcUZOsUqkSYrg6KMzvPZ1WVZ478i47OnKSS0vkPmX45Pai5mTKuwIqBMcGWG7O8HfdA== + dependencies: + aproba "^2.0.0" + npm-registry-fetch "^11.0.0" + +libnpmpack@^2.0.1: + version "2.0.1" + resolved "https://registry.npmjs.org/libnpmpack/-/libnpmpack-2.0.1.tgz#d3eac25cc8612f4e7cdeed4730eee339ba51c643" + integrity sha512-He4/jxOwlaQ7YG7sIC1+yNeXeUDQt8RLBvpI68R3RzPMZPa4/VpxhlDo8GtBOBDYoU8eq6v1wKL38sq58u4ibQ== + dependencies: + "@npmcli/run-script" "^1.8.3" + npm-package-arg "^8.1.0" + pacote "^11.2.6" + +libnpmpublish@^4.0.0, libnpmpublish@^4.0.1: version "4.0.2" resolved "https://registry.npmjs.org/libnpmpublish/-/libnpmpublish-4.0.2.tgz#be77e8bf5956131bcb45e3caa6b96a842dec0794" integrity sha512-+AD7A2zbVeGRCFI2aO//oUmapCwy7GHqPXFJh3qpToSRNU+tXKJ2YFUgjt04LPPAf2dlEH95s6EhIHM1J7bmOw== @@ -7096,6 +7399,32 @@ libnpmpublish@^4.0.0: semver "^7.1.3" ssri "^8.0.1" +libnpmsearch@^3.1.1: + version "3.1.2" + resolved "https://registry.npmjs.org/libnpmsearch/-/libnpmsearch-3.1.2.tgz#aee81b9e4768750d842b627a3051abc89fdc15f3" + integrity sha512-BaQHBjMNnsPYk3Bl6AiOeVuFgp72jviShNBw5aHaHNKWqZxNi38iVNoXbo6bG/Ccc/m1To8s0GtMdtn6xZ1HAw== + dependencies: + npm-registry-fetch "^11.0.0" + +libnpmteam@^2.0.3: + version "2.0.4" + resolved "https://registry.npmjs.org/libnpmteam/-/libnpmteam-2.0.4.tgz#9dbe2e18ae3cb97551ec07d2a2daf9944f3edc4c" + integrity sha512-FPrVJWv820FZFXaflAEVTLRWZrerCvfe7ZHSMzJ/62EBlho2KFlYKjyNEsPW3JiV7TLSXi3vo8u0gMwIkXSMTw== + dependencies: + aproba "^2.0.0" + npm-registry-fetch "^11.0.0" + +libnpmversion@^1.2.1: + version "1.2.1" + resolved "https://registry.npmjs.org/libnpmversion/-/libnpmversion-1.2.1.tgz#689aa7fe0159939b3cbbf323741d34976f4289e9" + integrity sha512-AA7x5CFgBFN+L4/JWobnY5t4OAHjQuPbAwUYJ7/NtHuyLut5meb+ne/aj0n7PWNiTGCJcRw/W6Zd2LoLT7EZuQ== + dependencies: + "@npmcli/git" "^2.0.7" + "@npmcli/run-script" "^1.8.4" + json-parse-even-better-errors "^2.3.1" + semver "^7.3.5" + stringify-package "^1.0.1" + license-checker@^25.0.1: version "25.0.1" resolved "https://registry.npmjs.org/license-checker/-/license-checker-25.0.1.tgz#4d14504478a5240a857bb3c21cd0491a00d761fa" @@ -7412,7 +7741,7 @@ make-fetch-happen@^8.0.9: socks-proxy-agent "^5.0.0" ssri "^8.0.0" -make-fetch-happen@^9.0.1: +make-fetch-happen@^9.0.1, make-fetch-happen@^9.1.0: version "9.1.0" resolved "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-9.1.0.tgz#53085a09e7971433e6765f7971bf63f4e05cb968" integrity sha512-+zopwDy7DNknmwPQplem5lAZX/eCOzSvSNNcSKm5eVwTkOBzoktEfXsa9L23J/GIRhxRsaxzkPEhrJEpE2F4Gg== @@ -7791,7 +8120,7 @@ ms@2.1.2: resolved "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== -ms@^2.0.0, ms@^2.1.1: +ms@^2.0.0, ms@^2.1.1, ms@^2.1.2: version "2.1.3" resolved "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== @@ -7908,7 +8237,7 @@ node-gyp@^5.0.2: tar "^4.4.12" which "^1.3.1" -node-gyp@^7.1.0: +node-gyp@^7.1.0, node-gyp@^7.1.2: version "7.1.2" resolved "https://registry.npmjs.org/node-gyp/-/node-gyp-7.1.2.tgz#21a810aebb187120251c3bcec979af1587b188ae" integrity sha512-CbpcIo7C3eMu3dL1c3d0xw449fHIGALIJsRP4DDPHpyiW8vcriNY7ubh9TE4zEKfSxscY7PjeFnshE7h75ynjQ== @@ -8031,6 +8360,13 @@ normalize-url@^6.1.0: resolved "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz#40d0885b535deffe3f3147bec877d05fe4c5668a" integrity sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A== +npm-audit-report@^2.1.5: + version "2.1.5" + resolved "https://registry.npmjs.org/npm-audit-report/-/npm-audit-report-2.1.5.tgz#a5b8850abe2e8452fce976c8960dd432981737b5" + integrity sha512-YB8qOoEmBhUH1UJgh1xFAv7Jg1d+xoNhsDYiFQlEFThEBui0W1vIz2ZK6FVg4WZjwEdl7uBQlm1jy3MUfyHeEw== + dependencies: + chalk "^4.0.0" + npm-bundled@^1.1.1, npm-bundled@^1.1.2: version "1.1.2" resolved "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.1.2.tgz#944c78789bd739035b70baa2ca5cc32b8d860bc1" @@ -8118,7 +8454,7 @@ npm-normalize-package-bin@^2.0.0: resolved "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-2.0.0.tgz#9447a1adaaf89d8ad0abe24c6c84ad614a675fff" integrity sha512-awzfKUO7v0FscrSpRoogyNm0sajikhBWpU0QMrW09AMi9n1PoKU6WaIqUzuJSQnpciZZmJ/jMZ2Egfmb/9LiWQ== -npm-package-arg@^8.0.0, npm-package-arg@^8.0.1, npm-package-arg@^8.1.0, npm-package-arg@^8.1.2, npm-package-arg@^8.1.5: +npm-package-arg@^8.0.0, npm-package-arg@^8.0.1, npm-package-arg@^8.1.0, npm-package-arg@^8.1.1, npm-package-arg@^8.1.2, npm-package-arg@^8.1.5: version "8.1.5" resolved "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-8.1.5.tgz#3369b2d5fe8fdc674baa7f1786514ddc15466e44" integrity sha512-LhgZrg0n0VgvzVdSm1oiZworPbTxYHUJCgtsJW8mGvlDpxTM1vSJc3m5QZeUkhAHIzbz3VCHd/R4osi1L1Tg/Q== @@ -8157,7 +8493,7 @@ npm-packlist@^5.1.0: npm-bundled "^2.0.0" npm-normalize-package-bin "^2.0.0" -npm-pick-manifest@^6.0.0, npm-pick-manifest@^6.1.1: +npm-pick-manifest@^6.0.0, npm-pick-manifest@^6.1.0, npm-pick-manifest@^6.1.1: version "6.1.1" resolved "https://registry.npmjs.org/npm-pick-manifest/-/npm-pick-manifest-6.1.1.tgz#7b5484ca2c908565f43b7f27644f36bb816f5148" integrity sha512-dBsdBtORT84S8V8UTad1WlUyKIY9iMsAmqxHbLdeEeBNMLQDlDWWra3wYUx9EBEIiG/YwAy0XyNHDd2goAsfuA== @@ -8177,6 +8513,13 @@ npm-pick-manifest@^7.0.0: npm-package-arg "^9.0.0" semver "^7.3.5" +npm-profile@^5.0.3: + version "5.0.4" + resolved "https://registry.npmjs.org/npm-profile/-/npm-profile-5.0.4.tgz#73e5bd1d808edc2c382d7139049cc367ac43161b" + integrity sha512-OKtU7yoAEBOnc8zJ+/uo5E4ugPp09sopo+6y1njPp+W99P8DvQon3BJYmpvyK2Bf1+3YV5LN1bvgXRoZ1LUJBA== + dependencies: + npm-registry-fetch "^11.0.0" + npm-registry-fetch@^11.0.0: version "11.0.0" resolved "https://registry.npmjs.org/npm-registry-fetch/-/npm-registry-fetch-11.0.0.tgz#68c1bb810c46542760d62a6a965f85a702d43a76" @@ -8223,6 +8566,87 @@ npm-run-path@^4.0.1: dependencies: path-key "^3.0.0" +npm-user-validate@^1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/npm-user-validate/-/npm-user-validate-1.0.1.tgz#31428fc5475fe8416023f178c0ab47935ad8c561" + integrity sha512-uQwcd/tY+h1jnEaze6cdX/LrhWhoBxfSknxentoqmIuStxUExxjWd3ULMLFPiFUrZKbOVMowH6Jq2FRWfmhcEw== + +npm@^7.19.0: + version "7.24.2" + resolved "https://registry.npmjs.org/npm/-/npm-7.24.2.tgz#861117af8241bea592289f22407230e5300e59ca" + integrity sha512-120p116CE8VMMZ+hk8IAb1inCPk4Dj3VZw29/n2g6UI77urJKVYb7FZUDW8hY+EBnfsjI/2yrobBgFyzo7YpVQ== + dependencies: + "@isaacs/string-locale-compare" "^1.1.0" + "@npmcli/arborist" "^2.9.0" + "@npmcli/ci-detect" "^1.2.0" + "@npmcli/config" "^2.3.0" + "@npmcli/map-workspaces" "^1.0.4" + "@npmcli/package-json" "^1.0.1" + "@npmcli/run-script" "^1.8.6" + abbrev "~1.1.1" + ansicolors "~0.3.2" + ansistyles "~0.1.3" + archy "~1.0.0" + cacache "^15.3.0" + chalk "^4.1.2" + chownr "^2.0.0" + cli-columns "^3.1.2" + cli-table3 "^0.6.0" + columnify "~1.5.4" + fastest-levenshtein "^1.0.12" + glob "^7.2.0" + graceful-fs "^4.2.8" + hosted-git-info "^4.0.2" + ini "^2.0.0" + init-package-json "^2.0.5" + is-cidr "^4.0.2" + json-parse-even-better-errors "^2.3.1" + libnpmaccess "^4.0.2" + libnpmdiff "^2.0.4" + libnpmexec "^2.0.1" + libnpmfund "^1.1.0" + libnpmhook "^6.0.2" + libnpmorg "^2.0.2" + libnpmpack "^2.0.1" + libnpmpublish "^4.0.1" + libnpmsearch "^3.1.1" + libnpmteam "^2.0.3" + libnpmversion "^1.2.1" + make-fetch-happen "^9.1.0" + minipass "^3.1.3" + minipass-pipeline "^1.2.4" + mkdirp "^1.0.4" + mkdirp-infer-owner "^2.0.0" + ms "^2.1.2" + node-gyp "^7.1.2" + nopt "^5.0.0" + npm-audit-report "^2.1.5" + npm-install-checks "^4.0.0" + npm-package-arg "^8.1.5" + npm-pick-manifest "^6.1.1" + npm-profile "^5.0.3" + npm-registry-fetch "^11.0.0" + npm-user-validate "^1.0.1" + npmlog "^5.0.1" + opener "^1.5.2" + pacote "^11.3.5" + parse-conflict-json "^1.1.1" + qrcode-terminal "^0.12.0" + read "~1.0.7" + read-package-json "^4.1.1" + read-package-json-fast "^2.0.3" + readdir-scoped-modules "^1.1.0" + rimraf "^3.0.2" + semver "^7.3.5" + ssri "^8.0.1" + tar "^6.1.11" + text-table "~0.2.0" + tiny-relative-date "^1.3.0" + treeverse "^1.0.4" + validate-npm-package-name "~3.0.0" + which "^2.0.2" + write-file-atomic "^3.0.3" + npmlog@^4.1.2: version "4.1.2" resolved "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz#08a7f2a8bf734604779a9efa4ad5cc717abb954b" @@ -8233,6 +8657,16 @@ npmlog@^4.1.2: gauge "~2.7.3" set-blocking "~2.0.0" +npmlog@^5.0.1: + version "5.0.1" + resolved "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz#f06678e80e29419ad67ab964e0fa69959c1eb8b0" + integrity sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw== + dependencies: + are-we-there-yet "^2.0.0" + console-control-strings "^1.1.0" + gauge "^3.0.0" + set-blocking "^2.0.0" + npmlog@^6.0.0: version "6.0.2" resolved "https://registry.npmjs.org/npmlog/-/npmlog-6.0.2.tgz#c8166017a42f2dea92d6453168dd865186a70830" @@ -8286,7 +8720,7 @@ oauth-sign@~0.9.0: resolved "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455" integrity sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ== -object-assign@^4.1.0: +object-assign@^4.1.0, object-assign@^4.1.1: version "4.1.1" resolved "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== @@ -8357,6 +8791,11 @@ open@^7.4.2: is-docker "^2.0.0" is-wsl "^2.1.1" +opener@^1.5.2: + version "1.5.2" + resolved "https://registry.npmjs.org/opener/-/opener-1.5.2.tgz#5d37e1f35077b9dcac4301372271afdeb2a13598" + integrity sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A== + optionator@^0.8.1: version "0.8.3" resolved "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz#84fa1d036fe9d3c7e21d99884b601167ec8fb495" @@ -8578,7 +9017,7 @@ package-json@^6.3.0: registry-url "^5.0.0" semver "^6.2.0" -pacote@^11.2.6: +pacote@^11.1.11, pacote@^11.2.6, pacote@^11.3.0, pacote@^11.3.1, pacote@^11.3.5: version "11.3.5" resolved "https://registry.npmjs.org/pacote/-/pacote-11.3.5.tgz#73cf1fc3772b533f575e39efa96c50be8c3dc9d2" integrity sha512-fT375Yczn4zi+6Hkk2TBe1x1sP8FgFsEIZ2/iWaXY2r/NkhDJfxbcn5paz1+RTFCyNf+dPnaoBDJoAxXSU8Bkg== @@ -8642,6 +9081,15 @@ parent-module@^1.0.0: dependencies: callsites "^3.0.0" +parse-conflict-json@^1.1.1: + version "1.1.1" + resolved "https://registry.npmjs.org/parse-conflict-json/-/parse-conflict-json-1.1.1.tgz#54ec175bde0f2d70abf6be79e0e042290b86701b" + integrity sha512-4gySviBiW5TRl7XHvp1agcS7SOe0KZOjC//71dzZVWJrY9hCrgtvl5v3SyIxCZ4fZF47TxD9nfzmxcx76xmbUw== + dependencies: + json-parse-even-better-errors "^2.3.0" + just-diff "^3.0.1" + just-diff-apply "^3.0.0" + parse-github-url@^1.0.2: version "1.0.2" resolved "https://registry.npmjs.org/parse-github-url/-/parse-github-url-1.0.2.tgz#242d3b65cbcdda14bb50439e3242acf6971db395" @@ -8908,6 +9356,11 @@ pretty-ms@^7.0.1: dependencies: parse-ms "^2.1.0" +proc-log@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/proc-log/-/proc-log-1.0.0.tgz#0d927307401f69ed79341e83a0b2c9a13395eb77" + integrity sha512-aCk8AO51s+4JyuYGg3Q/a6gnrlDO09NpVWePtjp7xwphcoQ04x5WAfCyugcsbLooWcMJ87CLkD4+604IckEdhg== + proc-log@^2.0.0, proc-log@^2.0.1: version "2.0.1" resolved "https://registry.npmjs.org/proc-log/-/proc-log-2.0.1.tgz#8f3f69a1f608de27878f91f5c688b225391cb685" @@ -8951,6 +9404,16 @@ projen@^0.65.59: yargs "^16.2.0" zlib "^1.0.5" +promise-all-reject-late@^1.0.0: + version "1.0.1" + resolved "https://registry.npmjs.org/promise-all-reject-late/-/promise-all-reject-late-1.0.1.tgz#f8ebf13483e5ca91ad809ccc2fcf25f26f8643c2" + integrity sha512-vuf0Lf0lOxyQREH7GDIOUMLS7kz+gs8i6B+Yi8dC68a2sychGrHTJYghMBD6k7eUcH0H5P73EckCA48xijWqXw== + +promise-call-limit@^1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/promise-call-limit/-/promise-call-limit-1.0.1.tgz#4bdee03aeb85674385ca934da7114e9bcd3c6e24" + integrity sha512-3+hgaa19jzCGLuSCbieeRsu5C2joKfYn8pY6JAuXFRVfF4IO+L7UPpFWNTeWT9pM7uhskvbPPd/oEOktCn317Q== + promise-inflight@^1.0.1: version "1.0.1" resolved "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz#98472870bf228132fcbdd868129bad12c3c029e3" @@ -9074,6 +9537,11 @@ q@^1.5.1: resolved "https://registry.npmjs.org/q/-/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7" integrity sha512-kV/CThkXo6xyFEZUugw/+pIOywXcDbFYgSct5cT3gqlbkBE1SJdwy6UQoZvodiWF/ckQLZyDE/Bu1M6gVu5lVw== +qrcode-terminal@^0.12.0: + version "0.12.0" + resolved "https://registry.npmjs.org/qrcode-terminal/-/qrcode-terminal-0.12.0.tgz#bb5b699ef7f9f0505092a3748be4464fe71b5819" + integrity sha512-EXtzRZmC+YGmGlDFbXKxQiMZNwCLEO6BANKXG4iCtSIM0yqc/pappSx3RIKr4r0uh5JsBckOXeKrB3Iz7mdQpQ== + qs@^6.9.4: version "6.11.0" resolved "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz#fd0d963446f7a65e1367e01abd85429453f0c37a" @@ -9175,7 +9643,7 @@ read-installed@~4.0.3: optionalDependencies: graceful-fs "^4.1.2" -read-package-json-fast@^2.0.1, read-package-json-fast@^2.0.3: +read-package-json-fast@^2.0.1, read-package-json-fast@^2.0.2, read-package-json-fast@^2.0.3: version "2.0.3" resolved "https://registry.npmjs.org/read-package-json-fast/-/read-package-json-fast-2.0.3.tgz#323ca529630da82cb34b36cc0b996693c98c2b83" integrity sha512-W/BKtbL+dUjTuRL2vziuYhp76s5HZ9qQhd/dKfWIZveD0O40453QNyZhC0e63lqZrAQ4jiOapVoeJ7JrszenQQ== @@ -9268,7 +9736,7 @@ read-pkg@^5.2.0: parse-json "^5.0.0" type-fest "^0.6.0" -read@1, read@^1.0.4, read@~1.0.1: +read@1, read@^1.0.4, read@^1.0.7, read@~1.0.1, read@~1.0.7: version "1.0.7" resolved "https://registry.npmjs.org/read/-/read-1.0.7.tgz#b3da19bd052431a97671d44a42634adf710b40c4" integrity sha512-rSOKNYUmaxy0om1BNjMN4ezNT6VKK+2xF4GBhc81mkH7L60i6dp8qPYrkndNLT3QPphoII3maL9PVC9XmhHwVQ== @@ -9314,7 +9782,7 @@ readdir-glob@^1.0.0: dependencies: minimatch "^5.1.0" -readdir-scoped-modules@^1.0.0: +readdir-scoped-modules@^1.0.0, readdir-scoped-modules@^1.1.0: version "1.1.0" resolved "https://registry.npmjs.org/readdir-scoped-modules/-/readdir-scoped-modules-1.1.0.tgz#8d45407b4f870a0dcaebc0e28670d18e74514309" integrity sha512-asaikDeqAQg7JifRsZn1NJZXo9E+VwlyCfbkZhwyISinqk5zNS6266HS5kah6P0SaQKGF6SkNnZVHUzHFYxYDw== @@ -10052,7 +10520,7 @@ string-length@^4.0.1: char-regex "^1.0.2" strip-ansi "^6.0.0" -string-width@*, string-width@^1.0.1, "string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.2, string-width@^4.2.3: +string-width@*, string-width@^1.0.1, "string-width@^1.0.2 || 2 || 3 || 4", string-width@^2.0.0, string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.2, string-width@^4.2.3: version "4.2.3" resolved "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -10117,7 +10585,7 @@ stringify-package@^1.0.1: resolved "https://registry.npmjs.org/stringify-package/-/stringify-package-1.0.1.tgz#e5aa3643e7f74d0f28628b72f3dad5cecfc3ba85" integrity sha512-sa4DUQsYciMP1xhKWGuFM04fB0LG/9DlluZoSVywUMRNvzid6XucHK0/90xGxRoHrAaROrcHK1aPKaijCtSrhg== -strip-ansi@^3.0.1: +strip-ansi@^3.0.0, strip-ansi@^3.0.1: version "3.0.1" resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" integrity sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg== @@ -10327,7 +10795,7 @@ text-extensions@^1.0.0: resolved "https://registry.npmjs.org/text-extensions/-/text-extensions-1.9.0.tgz#1853e45fee39c945ce6f6c36b2d659b5aabc2a26" integrity sha512-wiBrwC1EhBelW12Zy26JeOUkQ5mRu+5o8rpsJk5+2t+Y5vE7e842qtZDQ2g1NpX/29HdyFeJ4nSIhI47ENSxlQ== -text-table@^0.2.0: +text-table@^0.2.0, text-table@~0.2.0: version "0.2.0" resolved "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" integrity sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw== @@ -10365,6 +10833,11 @@ timers-ext@^0.1.7: es5-ext "~0.10.46" next-tick "1" +tiny-relative-date@^1.3.0: + version "1.3.0" + resolved "https://registry.npmjs.org/tiny-relative-date/-/tiny-relative-date-1.3.0.tgz#fa08aad501ed730f31cc043181d995c39a935e07" + integrity sha512-MOQHpzllWxDCHHaDno30hhLfbouoYlOI8YlMNtvKe1zXbjEVhbcEovQxvZrPvtiYW630GQDoMMarCnjfyfHA+A== + tmp@^0.0.33: version "0.0.33" resolved "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9" @@ -10439,6 +10912,11 @@ treeify@^1.1.0: resolved "https://registry.npmjs.org/treeify/-/treeify-1.1.0.tgz#4e31c6a463accd0943879f30667c4fdaff411bb8" integrity sha512-1m4RA7xVAJrSGrrXGs0L3YTwyvBs2S8PbRHaLZAkFw7JR8oIFwYtysxlBZhYIa7xSyiYJKZ3iGrrk55cGA3i9A== +treeverse@^1.0.4: + version "1.0.4" + resolved "https://registry.npmjs.org/treeverse/-/treeverse-1.0.4.tgz#a6b0ebf98a1bca6846ddc7ecbc900df08cb9cd5f" + integrity sha512-whw60l7r+8ZU8Tu/Uc2yxtc4ZTZbR/PF3u1IPNKGQ6p8EICLb3Z2lAgoqw9bqYd8IkgnsaOcLzYHFckjqNsf0g== + trim-newlines@^3.0.0: version "3.0.1" resolved "https://registry.npmjs.org/trim-newlines/-/trim-newlines-3.0.1.tgz#260a5d962d8b752425b32f3a7db0dcacd176c144" @@ -10871,7 +11349,7 @@ validate-npm-package-license@^3.0.1, validate-npm-package-license@^3.0.4: spdx-correct "^3.0.0" spdx-expression-parse "^3.0.0" -validate-npm-package-name@^3.0.0: +validate-npm-package-name@^3.0.0, validate-npm-package-name@~3.0.0: version "3.0.0" resolved "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-3.0.0.tgz#5fa912d81eb7d0c74afc140de7317f0ca7df437e" integrity sha512-M6w37eVCMMouJ9V/sdPGnC5H4uDr73/+xdq0FBLO3TFFX1+7wiUY6Es328NN+y43tmY+doUdN9g9J21vqB7iLw== @@ -10921,6 +11399,11 @@ w3c-xmlserializer@^2.0.0: dependencies: xml-name-validator "^3.0.0" +walk-up-path@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/walk-up-path/-/walk-up-path-1.0.0.tgz#d4745e893dd5fd0dbb58dd0a4c6a33d9c9fec53e" + integrity sha512-hwj/qMDUEjCU5h0xr90KGCf0tg0/LgJbmOWgrWKYlcJZM7XvquvUJZ0G/HMGr7F7OQMOUuPHWP9JpriinkAlkg== + walkdir@^0.4.1: version "0.4.1" resolved "https://registry.npmjs.org/walkdir/-/walkdir-0.4.1.tgz#dc119f83f4421df52e3061e514228a2db20afa39" @@ -11026,7 +11509,7 @@ which@^2.0.1, which@^2.0.2: dependencies: isexe "^2.0.0" -wide-align@^1.1.0, wide-align@^1.1.5: +wide-align@^1.1.0, wide-align@^1.1.2, wide-align@^1.1.5: version "1.1.5" resolved "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz#df1d4c206854369ecf3c9a4898f1b23fbd9d15d3" integrity sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg== @@ -11307,6 +11790,19 @@ yargs@^17.1.1, yargs@^17.6.2: y18n "^5.0.5" yargs-parser "^21.1.1" +yargs@^17.5.0: + version "17.6.0" + resolved "https://registry.npmjs.org/yargs/-/yargs-17.6.0.tgz#e134900fc1f218bc230192bdec06a0a5f973e46c" + integrity sha512-8H/wTDqlSwoSnScvV2N/JHfLWOKuh5MVla9hqLjK3nsfyy6Y4kDSYSvkU5YCUEPOSnRXfIyx3Sq+B/IWudTo4g== + dependencies: + cliui "^8.0.1" + escalade "^3.1.1" + get-caller-file "^2.0.5" + require-directory "^2.1.1" + string-width "^4.2.3" + y18n "^5.0.5" + yargs-parser "^21.0.0" + yn@3.1.1: version "3.1.1" resolved "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50" From 18a570ff94178c946963b08649ee56ab8ee942d0 Mon Sep 17 00:00:00 2001 From: Rico Hermans Date: Tue, 17 Jan 2023 12:02:40 +0100 Subject: [PATCH 2/3] Update packages/@aws-cdk-testing/cli-integ/lib/with-aws.ts Co-authored-by: Naumel <104374999+Naumel@users.noreply.github.com> --- packages/@aws-cdk-testing/cli-integ/lib/with-aws.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/@aws-cdk-testing/cli-integ/lib/with-aws.ts b/packages/@aws-cdk-testing/cli-integ/lib/with-aws.ts index 3a86fb05f39df..a18b6d6db4af4 100644 --- a/packages/@aws-cdk-testing/cli-integ/lib/with-aws.ts +++ b/packages/@aws-cdk-testing/cli-integ/lib/with-aws.ts @@ -36,7 +36,7 @@ export function regionPool(): ResourcePool { } /** - * Perform a one-time quick sanity check that the AWS clients has properly configured credentials + * Perform a one-time quick sanity check that the AWS clients have properly configured credentials * * If we don't do this, calls are going to fail and they'll be retried and everything will take * forever before the user notices a simple misconfiguration. From 4c5f96db7134ce2886543e0aa94787772d4dd49b Mon Sep 17 00:00:00 2001 From: Rico Huijbers Date: Tue, 17 Jan 2023 12:13:21 +0100 Subject: [PATCH 3/3] Incorporate some comments --- packages/@aws-cdk-testing/cli-integ/bin/run-suite.ts | 2 +- packages/@aws-cdk-testing/cli-integ/lib/integ-test.ts | 2 -- packages/@aws-cdk-testing/cli-integ/lib/xpmutex.ts | 8 ++++---- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/packages/@aws-cdk-testing/cli-integ/bin/run-suite.ts b/packages/@aws-cdk-testing/cli-integ/bin/run-suite.ts index 9f26ca12b705b..46a19e44bf47b 100644 --- a/packages/@aws-cdk-testing/cli-integ/bin/run-suite.ts +++ b/packages/@aws-cdk-testing/cli-integ/bin/run-suite.ts @@ -24,7 +24,7 @@ async function main() { }) .option('use-source', { descripton: 'Use TypeScript packages from the given source repository (or "auto")', - alias: 'S', + alias: 's', type: 'string', requiresArg: true, }) diff --git a/packages/@aws-cdk-testing/cli-integ/lib/integ-test.ts b/packages/@aws-cdk-testing/cli-integ/lib/integ-test.ts index 731bddd7e2e91..e529591c50d1e 100644 --- a/packages/@aws-cdk-testing/cli-integ/lib/integ-test.ts +++ b/packages/@aws-cdk-testing/cli-integ/lib/integ-test.ts @@ -50,8 +50,6 @@ export function integTest( // jest and prints without a stack trace (if verbose: false). // eslint-disable-next-line no-console console.log(output.buffer().toString()); - // await output.flushTo(process.stderr); - // process.stderr.write(`❌ ${e.toString()}\n`); throw e; } }, timeoutMillis); diff --git a/packages/@aws-cdk-testing/cli-integ/lib/xpmutex.ts b/packages/@aws-cdk-testing/cli-integ/lib/xpmutex.ts index 6780215b5fdc6..4687164a1b5ee 100644 --- a/packages/@aws-cdk-testing/cli-integ/lib/xpmutex.ts +++ b/packages/@aws-cdk-testing/cli-integ/lib/xpmutex.ts @@ -12,7 +12,7 @@ export class XpMutexPool { return XpMutexPool.fromDirectory(path.join(os.tmpdir(), name)); } - private readonly queuedResolvers = new Set<() => void>(); + private readonly waitingResolvers = new Set<() => void>(); private watcher: ReturnType | undefined; private constructor(public readonly directory: string) { @@ -31,7 +31,7 @@ export class XpMutexPool { */ public awaitUnlock(maxWaitMs?: number): Promise { const wait = new Promise(ok => { - this.queuedResolvers.add(async () => { + this.waitingResolvers.add(async () => { await randomSleep(10); ok(); }); @@ -67,10 +67,10 @@ export class XpMutexPool { } private notifyWaiters() { - for (const promise of this.queuedResolvers) { + for (const promise of this.waitingResolvers) { promise(); } - this.queuedResolvers.clear(); + this.waitingResolvers.clear(); } }