diff --git a/common/config/rush/pnpm-lock.yaml b/common/config/rush/pnpm-lock.yaml index 15eeb0d29829..8985073d17f7 100644 --- a/common/config/rush/pnpm-lock.yaml +++ b/common/config/rush/pnpm-lock.yaml @@ -1866,6 +1866,13 @@ packages: resolution: {integrity: sha512-SRXjM+tfsSlA9VuG8hGO2nft2p8zjXCK1VcC6N4NXbBbYbSia9kzCChYQajIjzIqOOOuh5Ock6MmV2oux4jDZQ==} dev: false + /@types/concurrently/6.4.0: + resolution: {integrity: sha512-CYU1eyFHsIa2IZIsb8gfUOdiewfnZcyM2Hg1Zaq95xnmB0Ix/bTRM8SttqZ2Cjy6JGPZLttHjZewVsDg1yvnJg==} + dependencies: + '@types/node': 12.20.37 + chalk: 4.1.2 + dev: false + /@types/connect/3.4.35: resolution: {integrity: sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==} dependencies: @@ -8450,6 +8457,7 @@ packages: version: 0.0.0 dependencies: '@azure/core-tracing': 1.0.0-preview.13 + '@azure/identity': 1.5.2 '@microsoft/api-extractor': 7.18.19 '@types/chai': 4.2.22 '@types/mocha': 7.0.2 @@ -8731,7 +8739,6 @@ packages: name: '@rush-temp/arm-consumption' version: 0.0.0 dependencies: - '@azure/identity': 2.0.0-beta.6 '@microsoft/api-extractor': 7.18.19 '@rollup/plugin-commonjs': 11.0.2_rollup@1.32.1 '@rollup/plugin-json': 4.1.0_rollup@1.32.1 @@ -10830,7 +10837,7 @@ packages: dev: false file:projects/dev-tool.tgz: - resolution: {integrity: sha512-88uEvuABom6N9jceuXkxKLjNwvWdFPpQvEB+D27OFdycBQIift0qrWaav+Ft0ODnHF15ViPR/bJQx3D0Cr3nHA==, tarball: file:projects/dev-tool.tgz} + resolution: {integrity: sha512-kIv+fJbY2f89rQmcriGnUQSMyEfqk0ywz7kxqv71AkbY0EIe02xTQgbUpQkvyM39BLeG4XOM736TsrhK9i9vgw==, tarball: file:projects/dev-tool.tgz} name: '@rush-temp/dev-tool' version: 0.0.0 dependencies: @@ -10840,6 +10847,7 @@ packages: '@rollup/plugin-node-resolve': 8.4.0_rollup@1.32.1 '@types/chai': 4.2.22 '@types/chai-as-promised': 7.1.4 + '@types/concurrently': 6.4.0 '@types/fs-extra': 8.1.2 '@types/minimist': 1.2.2 '@types/mocha': 7.0.2 @@ -10849,6 +10857,7 @@ packages: chai: 4.3.4 chai-as-promised: 7.1.1_chai@4.3.4 chalk: 4.1.2 + concurrently: 6.3.0 dotenv: 8.6.0 eslint: 7.32.0 fs-extra: 8.1.0 diff --git a/common/tools/dev-tool/package.json b/common/tools/dev-tool/package.json index cafd50113b7d..80db2351fd03 100644 --- a/common/tools/dev-tool/package.json +++ b/common/tools/dev-tool/package.json @@ -41,6 +41,7 @@ "private": true, "prettier": "../eslint-plugin-azure-sdk/prettier.json", "dependencies": { + "concurrently": "^6.3.0", "chalk": "~4.1.1", "dotenv": "^8.2.0", "fs-extra": "^8.1.0", @@ -56,6 +57,7 @@ "@rollup/plugin-json": "^4.0.0", "@rollup/plugin-multi-entry": "^3.0.0", "@rollup/plugin-node-resolve": "^8.0.0", + "@types/concurrently": "^6.3.0", "@types/chai": "^4.1.6", "@types/chai-as-promised": "^7.1.0", "@types/fs-extra": "^8.0.0", diff --git a/common/tools/dev-tool/src/commands/index.ts b/common/tools/dev-tool/src/commands/index.ts index c5a209259a9f..44e60a354d5b 100644 --- a/common/tools/dev-tool/src/commands/index.ts +++ b/common/tools/dev-tool/src/commands/index.ts @@ -12,7 +12,9 @@ const log = createPrinter("dev-tool"); export const baseCommands = { about: () => import("./about"), package: () => import("./package"), - samples: () => import("./samples") + samples: () => import("./samples"), + "test-proxy": () => import("./test-proxy"), + run: () => import("./run") } as const; /** diff --git a/common/tools/dev-tool/src/commands/run/index.ts b/common/tools/dev-tool/src/commands/run/index.ts new file mode 100644 index 000000000000..e8970020cd40 --- /dev/null +++ b/common/tools/dev-tool/src/commands/run/index.ts @@ -0,0 +1,12 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license + +import { subCommand, makeCommandInfo } from "../../framework/command"; + +export const commandInfo = makeCommandInfo("run", "run scripts such as test:node"); + +export default subCommand(commandInfo, { + "test:node-ts-input": () => import("./testNodeTSInput"), + "test:node-js-input": () => import("./testNodeJSInput"), + "test:browser": () => import("./testBrowser") +}); diff --git a/common/tools/dev-tool/src/commands/run/testBrowser.ts b/common/tools/dev-tool/src/commands/run/testBrowser.ts new file mode 100644 index 000000000000..fa5bc686c107 --- /dev/null +++ b/common/tools/dev-tool/src/commands/run/testBrowser.ts @@ -0,0 +1,24 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license + +import { leafCommand, makeCommandInfo } from "../../framework/command"; +import { runTestsWithProxyTool } from "../../util/testUtils"; + +export const commandInfo = makeCommandInfo( + "test:browser", + "runs the browser tests using karma with the default and the provided options; starts the proxy-tool in record and playback modes", + { + karma: { + kind: "string", + description: "Karma options (such as --single-run)", + default: "--single-run" + } + } +); + +export default leafCommand(commandInfo, async (options) => { + return runTestsWithProxyTool({ + command: `karma start ${options.karma}`, + name: "browser-tests" + }); +}); diff --git a/common/tools/dev-tool/src/commands/run/testNodeJSInput.ts b/common/tools/dev-tool/src/commands/run/testNodeJSInput.ts new file mode 100644 index 000000000000..781736afefb6 --- /dev/null +++ b/common/tools/dev-tool/src/commands/run/testNodeJSInput.ts @@ -0,0 +1,25 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license + +import { leafCommand, makeCommandInfo } from "../../framework/command"; +import { runTestsWithProxyTool } from "../../util/testUtils"; + +export const commandInfo = makeCommandInfo( + "test:node-js-input", + "runs the node tests using mocha with the default and the provided options; starts the proxy-tool in record and playback modes", + { + mocha: { + kind: "string", + description: + "Mocha options along with the bundled test file(JS) with rollup as expected by mocha", + default: '--timeout 5000000 "dist-esm/test/{,!(browser)/**/}/*.spec.js"' + } + } +); + +export default leafCommand(commandInfo, async (options) => { + return runTestsWithProxyTool({ + command: `nyc mocha -r esm --require source-map-support/register --reporter ../../../common/tools/mocha-multi-reporter.js --full-trace ${options.mocha}`, + name: "node-tests" + }); +}); diff --git a/common/tools/dev-tool/src/commands/run/testNodeTSInput.ts b/common/tools/dev-tool/src/commands/run/testNodeTSInput.ts new file mode 100644 index 000000000000..d68e4856ce1f --- /dev/null +++ b/common/tools/dev-tool/src/commands/run/testNodeTSInput.ts @@ -0,0 +1,25 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license + +import { leafCommand, makeCommandInfo } from "../../framework/command"; +import { runTestsWithProxyTool } from "../../util/testUtils"; + +export const commandInfo = makeCommandInfo( + "test:node-ts-input", + "runs the node tests using mocha with the default and the provided options; starts the proxy-tool in record and playback modes", + { + mocha: { + kind: "string", + description: + "Mocha options along with the test files(glob pattern) in TS as expected by mocha", + default: '--timeout 1200000 --exclude "test/**/browser/*.spec.ts" "test/**/*.spec.ts"' + } + } +); + +export default leafCommand(commandInfo, async (options) => { + return runTestsWithProxyTool({ + command: `mocha -r esm -r ts-node/register --reporter ../../../common/tools/mocha-multi-reporter.js --full-trace ${options.mocha}`, + name: "node-tests" + }); +}); diff --git a/common/tools/dev-tool/src/commands/test-proxy/index.ts b/common/tools/dev-tool/src/commands/test-proxy/index.ts new file mode 100644 index 000000000000..3735e4abf347 --- /dev/null +++ b/common/tools/dev-tool/src/commands/test-proxy/index.ts @@ -0,0 +1,14 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license + +import { subCommand, makeCommandInfo } from "../../framework/command"; + +export const commandInfo = makeCommandInfo( + "test-proxy", + "runs the proxy-tool with the `docker run ...` command" +); + +export default subCommand(commandInfo, { + start: () => import("./start"), + "wait-for-proxy-endpoint": () => import("./waitForProxyEndpoint") +}); diff --git a/common/tools/dev-tool/src/commands/test-proxy/start.ts b/common/tools/dev-tool/src/commands/test-proxy/start.ts new file mode 100644 index 000000000000..f34f14367d8d --- /dev/null +++ b/common/tools/dev-tool/src/commands/test-proxy/start.ts @@ -0,0 +1,18 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +import { leafCommand, makeCommandInfo } from "../../framework/command"; +import { config } from "dotenv"; +import { startProxyTool } from "../../util/testProxyUtils"; +config(); + +export const commandInfo = makeCommandInfo( + "test-proxy", + "runs the proxy-tool with the `docker run ...` command", + {} +); + +export default leafCommand(commandInfo, async (_options) => { + await startProxyTool(); + return true; +}); diff --git a/common/tools/dev-tool/src/commands/test-proxy/waitForProxyEndpoint.ts b/common/tools/dev-tool/src/commands/test-proxy/waitForProxyEndpoint.ts new file mode 100644 index 000000000000..e09db490463a --- /dev/null +++ b/common/tools/dev-tool/src/commands/test-proxy/waitForProxyEndpoint.ts @@ -0,0 +1,19 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +import { leafCommand, makeCommandInfo } from "../../framework/command"; +import { config } from "dotenv"; +import { isProxyToolActive } from "../../util/testProxyUtils"; +import { checkWithTimeout } from "../../util/checkWithTimeout"; +config(); + +export const commandInfo = makeCommandInfo( + "test-proxy", + "waits for the proxy tool to be active or fails in 2 minutes", + {} +); + +export default leafCommand(commandInfo, async (_options) => { + const result = await checkWithTimeout(isProxyToolActive, 1000, 120000); + return result; +}); diff --git a/common/tools/dev-tool/src/util/checkWithTimeout.ts b/common/tools/dev-tool/src/util/checkWithTimeout.ts new file mode 100644 index 000000000000..81d82b537831 --- /dev/null +++ b/common/tools/dev-tool/src/util/checkWithTimeout.ts @@ -0,0 +1,32 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license + +import { createPrinter } from "./printer"; +const log = createPrinter("check-with-timeout"); + +/** + * - Maximum wait duration for the expected event to happen = `10000 ms`(default value is 10 seconds)(= maxWaitTimeInMilliseconds) + * - Keep checking whether the predicate is true after every `1000 ms`(default value is 1 second) (= delayBetweenRetriesInMilliseconds) + */ +export async function checkWithTimeout( + predicate: () => boolean | Promise, + delayBetweenRetriesInMilliseconds: number = 1000, + maxWaitTimeInMilliseconds: number = 10000 +): Promise { + const maxTime = Date.now() + maxWaitTimeInMilliseconds; + while (Date.now() < maxTime) { + if (await predicate()) { + log.info(`checkWithTimeout condition returned true`); + return true; + } + await delay(delayBetweenRetriesInMilliseconds); + } + return false; +} + +async function delay(timeInMs: number) { + return new Promise((resolve) => { + log.info(`waiting for ${timeInMs}ms`); + setTimeout(resolve, timeInMs); + }); +} diff --git a/common/tools/dev-tool/src/util/testProxyUtils.ts b/common/tools/dev-tool/src/util/testProxyUtils.ts new file mode 100644 index 000000000000..457307469f83 --- /dev/null +++ b/common/tools/dev-tool/src/util/testProxyUtils.ts @@ -0,0 +1,87 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +import { spawn } from "child_process"; +import path from "path"; +import { IncomingMessage, request, RequestOptions } from "http"; +import fs from "fs-extra"; +import { createPrinter } from "./printer"; + +const log = createPrinter("test-proxy"); +export async function startProxyTool() { + log.info( + `Attempting to start test proxy at http://localhost:5000 & https://localhost:5001.\n` + ); + + const subprocess = spawn(await getDockerRunCommand(), [], { + shell: true + }); + + const outFileName = "test-proxy-output.log"; + const out = fs.createWriteStream(`./${outFileName}`, { flags: 'a' }); + subprocess.stdout.pipe(out); + subprocess.stderr.pipe(out); + + log.info(`Check the output file "${outFileName}" for test-proxy logs.`); +} + +async function getRootLocation(start?: string): Promise { + start ??= process.cwd(); + if (await fs.pathExists(path.join(start, "rush.json"))) { + return start; + } else { + const nextPath = path.resolve(start, ".."); + if (nextPath === start) { + throw new Error("Reached filesystem root, but no rush.json was found."); + } else { + return getRootLocation(nextPath); + } + } +} + +async function getDockerRunCommand() { + const repoRoot = await getRootLocation(); // /workspaces/azure-sdk-for-js/ + const testProxyRecordingsLocation = "/etc/testproxy"; + const allowLocalhostAccess = "--add-host host.docker.internal:host-gateway"; + const imageToLoad = `azsdkengsys.azurecr.io/engsys/testproxy-lin:${await getImageTag()}`; + return `docker run -v ${repoRoot}:${testProxyRecordingsLocation} -p 5001:5001 -p 5000:5000 ${allowLocalhostAccess} ${imageToLoad}`; +} + +export async function isProxyToolActive() { + try { + await makeRequest("http://localhost:5000/info/available", {}); + log.info(`Proxy tool seems to be active at http://localhost:5000\n`); + return true; + } catch (error) { + return false; + } +} + +async function makeRequest(uri: string, requestOptions: RequestOptions): Promise { + return new Promise((resolve, reject) => { + const req = request(uri, requestOptions, resolve); + req.once("error", reject); + req.end(); + }); +} + +async function getImageTag() { + // Grab the tag from the `/eng/common/testproxy/docker-start-proxy.ps1` file [..is used to run the proxy-tool in the CI] + // + // $SELECTED_IMAGE_TAG = "1147815"; + // (Bot regularly updates the tag in the file above.) + try { + const contentInPWSHScript = await fs.readFile( + `${path.join(await getRootLocation(), "eng/common/testproxy/docker-start-proxy.ps1")}`, + "utf-8" + ); + const tag = contentInPWSHScript.match(/\$SELECTED_IMAGE_TAG \= \"(.*)\"/)![1]; + log.info(`Image tag obtained from the powershell script => ${tag}\n`); + return tag; + } catch (_) { + log.warn( + `Unable to get the image tag from the powershell script, trying "latest" tag instead\n` + ); + return "latest"; + } +} diff --git a/common/tools/dev-tool/src/util/testUtils.ts b/common/tools/dev-tool/src/util/testUtils.ts new file mode 100644 index 000000000000..ffaa0a1acac4 --- /dev/null +++ b/common/tools/dev-tool/src/util/testUtils.ts @@ -0,0 +1,48 @@ +import { isProxyToolActive } from "./testProxyUtils"; +import concurrently from "concurrently"; +import { createPrinter } from "./printer"; + +const log = createPrinter("preparing-proxy-tool"); + +async function shouldRunProxyTool(): Promise { + const mode = process.env.TEST_MODE; + createPrinter("test-info").info(`===TEST_MODE="${mode}"===`); + if (mode === "live") { + return false; // No need to start the proxy tool in the live mode + } else { + const isActive = await isProxyToolActive(); + if (isActive) { + // No need to run a new one if it is already active + // Especially, CI uses this path + log.info( + `Proxy tool seems to be active, not attempting to start the test proxy at http://localhost:5000 & https://localhost:5001.\n` + ); + } + return !isActive; + } +} + +export async function runTestsWithProxyTool(testCommandObj: concurrently.CommandObj) { + if ( + await shouldRunProxyTool() // Boolean to figure out if we need to run just the mocha command or the test-proxy too + ) { + const testProxyCMD = "dev-tool test-proxy start"; + const waitForProxyEndpointCMD = "dev-tool test-proxy wait-for-proxy-endpoint"; + await concurrently( + [ + { command: testProxyCMD }, + { + command: `${waitForProxyEndpointCMD} && ${testCommandObj.command}`, // Waits for the proxy endpoint to be active and then starts running the tests + name: testCommandObj.name + } + ], + { + killOthers: ["failure", "success"], + successCondition: "first" + } + ); + } else { + await concurrently([testCommandObj]); + } + return true; +} diff --git a/eng/common/testproxy/test-proxy-tool.yml b/eng/common/testproxy/test-proxy-tool.yml index 8401c4f5d073..6ad7eb897e4f 100644 --- a/eng/common/testproxy/test-proxy-tool.yml +++ b/eng/common/testproxy/test-proxy-tool.yml @@ -21,7 +21,7 @@ steps: - pwsh: | Start-Process $(Build.BinariesDirectory)/test-proxy/test-proxy.exe ` - -ArgumentList "--storage-location '${{ parameters.rootFolder }}'" ` + -ArgumentList "--storage-location ${{ parameters.rootFolder }}" ` -NoNewWindow -PassThru -RedirectStandardOutput $(Build.SourcesDirectory)/test-proxy.log displayName: 'Run the testproxy - windows' condition: and(succeeded(), eq(variables['Agent.OS'],'Windows_NT')) diff --git a/eng/pipelines/templates/steps/test.yml b/eng/pipelines/templates/steps/test.yml index 8bbe3b0b54ad..ac5fa60611dd 100644 --- a/eng/pipelines/templates/steps/test.yml +++ b/eng/pipelines/templates/steps/test.yml @@ -43,6 +43,12 @@ steps: displayName: "Test libraries" condition: and(succeeded(),eq(variables['TestType'], 'browser')) + - ${{ if eq(parameters.TestProxy, true) }}: + - pwsh: | + cat $(Build.SourcesDirectory)/test-proxy.log + displayName: 'Dump Test Proxy logs' + condition: succeededOrFailed() + # Unlink node_modules folders to significantly improve performance of subsequent tasks # which need to walk the directory tree (and are hardcoded to follow symlinks). # Retry for 30 seconds, since this command may fail with error "Another rush command is already diff --git a/sdk/test-utils/recorder-new/README.md b/sdk/test-utils/recorder-new/README.md index 55f3a8977fe6..58f679f3a90a 100644 --- a/sdk/test-utils/recorder-new/README.md +++ b/sdk/test-utils/recorder-new/README.md @@ -8,22 +8,35 @@ Feature work is being tracked at [#15829](https://github.com/Azure/azure-sdk-for - [Azure SDK Tools Test Proxy](https://github.com/Azure/azure-sdk-tools/tree/main/tools/test-proxy/Azure.Sdk.Tools.TestProxy) - [Using Test Proxy with docker container](https://github.com/Azure/azure-sdk-tools/tree/main/tools/test-proxy/docker#build-and-run) -## Running the proxy server +## Running the test-proxy tool -Run this command +### With the `docker run` command -> `docker run -v /workspaces/azure-sdk-for-js/:/etc/testproxy -p 5001:5001 -p 5000:5000 azsdkengsys.azurecr.io/engsys/testproxy-lin:latest` +- Run this command -Map the root directory of the azure-sdk-for-js repo to `/etc/testproxy` inside the container for an accurate location while generating recordings. + > `docker run -v /workspaces/azure-sdk-for-js/:/etc/testproxy -p 5001:5001 -p 5000:5000 azsdkengsys.azurecr.io/engsys/testproxy-lin:latest` -(Eventually, recorder will trigger this for you!) + Map the root directory of the azure-sdk-for-js repo to `/etc/testproxy` inside the container for an accurate location while generating recordings. -Add `--add-host host.docker.internal:host-gateway` for linux to access host's network(to access `localhost`) through `host.docker.internal`. -Docker for Windows and Mac support `host.docker.internal` as a functioning alias for localhost. + (Eventually, recorder will trigger this for you!) -If the above command doesn't work directly, try [Troubleshooting Access to Public Container Registry](https://github.com/Azure/azure-sdk-tools/tree/main/tools/test-proxy/docker#troubleshooting-access-to-public-container-registry). + Add `--add-host host.docker.internal:host-gateway` for linux to access host's network(to access `localhost`) through `host.docker.internal`. + Docker for Windows and Mac support `host.docker.internal` as a functioning alias for localhost. -Reference: [Using Test Proxy with docker container](https://github.com/Azure/azure-sdk-tools/tree/main/tools/test-proxy/docker#build-and-run) + If the above command doesn't work directly, try [Troubleshooting Access to Public Container Registry](https://github.com/Azure/azure-sdk-tools/tree/main/tools/test-proxy/docker#troubleshooting-access-to-public-container-registry). + + Reference: [Using Test Proxy with docker container](https://github.com/Azure/azure-sdk-tools/tree/main/tools/test-proxy/docker#build-and-run) + +### (OR) With the `dotnet tool` + +- Install [.Net 5.0](https://dotnet.microsoft.com/download) +- Install test-proxy + > `dotnet tool install azure.sdk.tools.testproxy --global --add-source https://pkgs.dev.azure.com/azure-sdk/public/_packaging/azure-sdk-for-net/nuget/v3/index.json --version 1.0.0-dev*` +- After successful installation, run the tool: + + > `test-proxy --storage-location ` + + [ `root-of-the-repo example` - `/workspaces/azure-sdk-for-js` if you're on codespaces] ## Run the tests using recorder-new at `test-utils/testing-recorder-new` diff --git a/sdk/test-utils/recorder-new/karma.conf.js b/sdk/test-utils/recorder-new/karma.conf.js index f8427308fdfa..8b04f2130708 100644 --- a/sdk/test-utils/recorder-new/karma.conf.js +++ b/sdk/test-utils/recorder-new/karma.conf.js @@ -49,7 +49,7 @@ module.exports = function(config) { // inject following environment values into browser testing with window.__env__ // environment values MUST be exported or set with same console running "karma start" // https://www.npmjs.com/package/karma-env-preprocessor - envPreprocessor: ["RECORDINGS_RELATIVE_PATH"], + envPreprocessor: ["RECORDINGS_RELATIVE_PATH", "PROXY_MANUAL_START"], // test results reporter to use // possible values: 'dots', 'progress' @@ -114,10 +114,6 @@ module.exports = function(config) { browserNoActivityTimeout: 600000, browserDisconnectTimeout: 10000, browserDisconnectTolerance: 3, - browserConsoleLogOptions: { - // We would usually hide the logs from the tests, but we don't need to do this inside of the recorder package because we are not recording the tests. - // // terminal: process.env.TEST_MODE !== "record" - }, client: { mocha: { diff --git a/sdk/test-utils/recorder-new/package.json b/sdk/test-utils/recorder-new/package.json index be7d7943b2aa..6b469ce3e66f 100644 --- a/sdk/test-utils/recorder-new/package.json +++ b/sdk/test-utils/recorder-new/package.json @@ -21,20 +21,20 @@ "clean": "rimraf dist dist-esm test-dist typings *.tgz *.log", "extract-api": "echo skipped", "format": "prettier --write --config ../../../.prettierrc.json --ignore-path ../../../.prettierignore \"src/**/*.ts\" \"test/**/*.ts\" \"*.{js,json}\"", - "integration-test:browser": "echo skipped", - "integration-test:node": "echo skipped", + "integration-test:browser": "concurrently \"npm run tests:server\" \"npm run test:browser-with-proxy\" --kill-others --success first", + "integration-test:node": "concurrently \"npm run tests:server\" \"npm run test:node-with-proxy\" --kill-others --success first", + "test:node-with-proxy": "dev-tool run test:node-ts-input --mocha=\"--timeout 1200000 'test/*.spec.ts'\"", + "test:browser-with-proxy": "dev-tool run test:browser", "integration-test": "npm run integration-test:node && npm run integration-test:browser", "tests:server": "cross-env TS_NODE_COMPILER_OPTIONS=\"{\\\"module\\\": \\\"commonjs\\\"}\" ts-node test/utils/server.ts", - "temp-integration-test:browser": "concurrently \"npm run tests:server\" \"karma start --single-run\" --kill-others --success first", - "temp-integration-test:node": "concurrently \"npm run tests:server\" \"nyc mocha -r esm --require ts-node/register --reporter ../../../common/tools/mocha-multi-reporter.js --timeout 5000000 --full-trace 'test/*.spec.ts'\" --kill-others --success first", "lint:fix": "eslint package.json src test --ext .ts --fix --fix-type [problem,suggestion]", "lint": "eslint package.json src test --ext .ts -f html -o recorder-lintReport.html || exit 0", "pack": "npm pack 2>&1", - "unit-test:browser": "echo skipped", - "unit-test:node": "echo skipped", + "unit-test:browser": "npm run integration-test:browser", + "unit-test:node": "npm run integration-test:node", "unit-test": "npm run unit-test:node && npm run unit-test:browser", - "test:browser": "npm run clean && npm run build && npm run temp-integration-test:browser", - "test:node": "npm run clean && npm run build:test && npm run temp-integration-test:node", + "test:browser": "npm run clean && npm run build && npm run integration-test:browser", + "test:node": "npm run clean && npm run build:test && npm run integration-test:node", "test": "npm run clean && npm run build:test && npm run unit-test", "docs": "echo Skipped." }, diff --git a/sdk/test-utils/recorder-new/src/core-v2-recorder.ts b/sdk/test-utils/recorder-new/src/core-v2-recorder.ts index 9d2a5fc73e2a..a973ae5a78b8 100644 --- a/sdk/test-utils/recorder-new/src/core-v2-recorder.ts +++ b/sdk/test-utils/recorder-new/src/core-v2-recorder.ts @@ -12,9 +12,10 @@ import { PipelineResponse, SendRequest } from "@azure/core-rest-pipeline"; -import { env, isPlaybackMode, isRecordMode } from "@azure-tools/test-recorder"; +import { isPlaybackMode, isRecordMode } from "@azure-tools/test-recorder"; import { ensureExistence, + getTestMode, RecorderError, RecorderStartOptions, RecordingStateManager @@ -62,7 +63,7 @@ export class TestProxyHttpClient { public variables: Record; constructor(private testContext?: Test | undefined) { - this.mode = env.TEST_MODE; + this.mode = getTestMode(); if (isRecordMode() || isPlaybackMode()) { if (this.testContext) { this.sessionFile = sessionFilePath(this.testContext); @@ -172,7 +173,7 @@ export class TestProxyHttpClient { } this.recordingId = id; if (isPlaybackMode()) { - this.variables = JSON.parse(rsp.bodyAsText ?? "{}"); + this.variables = rsp.bodyAsText ? JSON.parse(rsp.bodyAsText) : {}; } if (ensureExistence(this.sanitizer, "TestProxyHttpClient.sanitizer", this.mode)) { // Setting the recordingId in the sanitizer, @@ -180,15 +181,15 @@ export class TestProxyHttpClient { this.sanitizer.setRecordingId(this.recordingId); await handleEnvSetup(options.envSetupForPlayback, this.sanitizer); } + // Sanitizers to be added only in record mode + if (isRecordMode() && options.sanitizerOptions) { + // Makes a call to the proxy-tool to add the sanitizers for the current recording id + // Recordings of the current test will be influenced by the sanitizers that are being added here + await this.addSanitizers(options.sanitizerOptions); + } } } } - - if (options.sanitizerOptions) { - // Makes a call to the proxy-tool to add the sanitizers for the current recording id - // Recordings of the current test will be influenced by the sanitizers that are being added here - await this.addSanitizers(options.sanitizerOptions); - } } /** diff --git a/sdk/test-utils/recorder-new/src/index.ts b/sdk/test-utils/recorder-new/src/index.ts index df8b9756880c..a02a2947ecca 100644 --- a/sdk/test-utils/recorder-new/src/index.ts +++ b/sdk/test-utils/recorder-new/src/index.ts @@ -4,4 +4,4 @@ export { recorderHttpPolicy, TestProxyHttpClient } from "./core-v2-recorder"; export { TestProxyHttpClientCoreV1 } from "./core-v1-recorder"; export { relativeRecordingsPath } from "./utils/relativePathCalculator"; -export { SanitizerOptions } from "./utils/utils"; +export { SanitizerOptions, RecorderStartOptions } from "./utils/utils"; diff --git a/sdk/test-utils/recorder-new/src/utils/utils.ts b/sdk/test-utils/recorder-new/src/utils/utils.ts index 93ee3f682f32..cd1573e7643f 100644 --- a/sdk/test-utils/recorder-new/src/utils/utils.ts +++ b/sdk/test-utils/recorder-new/src/utils/utils.ts @@ -1,6 +1,8 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. +import { env, isPlaybackMode } from "@azure-tools/test-recorder"; + /** * A custom error type for failed pipeline requests. */ @@ -263,3 +265,15 @@ export function ensureExistence(thing: T | undefined, label: string, mode: st } return true; // Since we would throw error if undefined } + +/** + * Returns the test mode. + * + * If TEST_MODE is not defined, defaults to playback. + */ +export function getTestMode(): string { + if (isPlaybackMode()) { + return "playback"; + } + return env.TEST_MODE; +} diff --git a/sdk/test-utils/recorder-new/test/testProxyTests.spec.ts b/sdk/test-utils/recorder-new/test/testProxyTests.spec.ts index d00649af23b7..e71d4ca23ed9 100644 --- a/sdk/test-utils/recorder-new/test/testProxyTests.spec.ts +++ b/sdk/test-utils/recorder-new/test/testProxyTests.spec.ts @@ -29,7 +29,11 @@ function getTestServerUrl() { // - In "record" and "playback" modes, we need to hit the localhost of the host network // from the proxy tool running in the docker container. // `host.docker.internal` alias can be used in the docker container to access host's network(localhost) - return !isLiveMode() + // + // if PROXY_MANUAL_START=true, we start the proxy tool using the dotnet tool instead of the `docker run` command + // - in this case, we don't need to hit the localhost using the alias + // - needed for the CI since we have difficulties with the mac machines + return !isLiveMode() && !(env.PROXY_MANUAL_START === "true") ? `http://host.docker.internal:8080` // Accessing host's network(localhost) through docker container : `http://127.0.0.1:8080`; } diff --git a/sdk/test-utils/testing-recorder-new/README.md b/sdk/test-utils/testing-recorder-new/README.md index d58c33d49965..eb3daa3e3a7d 100644 --- a/sdk/test-utils/testing-recorder-new/README.md +++ b/sdk/test-utils/testing-recorder-new/README.md @@ -12,3 +12,4 @@ You will need to create a local `.env` file under the same directory as this rea "STORAGE_CONNECTION_STRING", "STORAGE_SAS_URL", "TABLES_SAS_CONNECTION_STRING" +``` diff --git a/sdk/test-utils/testing-recorder-new/package.json b/sdk/test-utils/testing-recorder-new/package.json index 42942b312677..6bf71de78b93 100644 --- a/sdk/test-utils/testing-recorder-new/package.json +++ b/sdk/test-utils/testing-recorder-new/package.json @@ -17,14 +17,14 @@ "clean": "rimraf dist dist-esm test-dist types *.tgz", "extract-api": "echo skipped", "format": "prettier --write --config ../../../.prettierrc.json --ignore-path ../../../.prettierignore \"src/**/*.ts\" \"test/**/*.ts\" \"*.{js,json}\"", - "integration-test:browser": "karma start --single-run", - "integration-test:node": "mocha -r esm --require ts-node/register --reporter ../../../common/tools/mocha-multi-reporter.js --timeout 1200000 --full-trace \"test/*.spec.ts\"", + "integration-test:browser": "dev-tool run test:browser", + "integration-test:node": "dev-tool run test:node-ts-input --mocha=\"--timeout 1200000 'test/*.spec.ts'\"", "integration-test": "npm run integration-test:node && npm run integration-test:browser", "lint:fix": "eslint package.json src test --ext .ts --fix --fix-type [problem,suggestion]", "lint": "eslint package.json src test --ext .ts -f html -o recorder-lintReport.html || exit 0", "pack": "npm pack 2>&1", - "unit-test:browser": "echo skipped", - "unit-test:node": "echo skipped", + "unit-test:browser": "npm run integration-test:browser", + "unit-test:node": "npm run integration-test:node", "unit-test": "npm run unit-test:node && npm run unit-test:browser", "test:browser": "npm run clean && npm run build && npm run integration-test:browser", "test:node": "npm run clean && npm run build:test && npm run integration-test:node", diff --git a/sdk/test-utils/testing-recorder-new/test/core-v1-test.spec.ts b/sdk/test-utils/testing-recorder-new/test/core-v1-test.spec.ts index 03fb18983c1f..b57952b9e892 100644 --- a/sdk/test-utils/testing-recorder-new/test/core-v1-test.spec.ts +++ b/sdk/test-utils/testing-recorder-new/test/core-v1-test.spec.ts @@ -2,37 +2,44 @@ // Licensed under the MIT license. import { env, isPlaybackMode } from "@azure-tools/test-recorder"; -import { QueueServiceClient, StoragePipelineOptions } from "@azure/storage-queue"; +import { QueueServiceClient } from "@azure/storage-queue"; import { TestProxyHttpClientCoreV1 } from "@azure-tools/test-recorder-new"; import { config } from "dotenv"; +import { RecorderStartOptions } from "@azure-tools/test-recorder-new"; config(); const fakeSASUrl = "https://account_name.queue.core.windows.net/?sv=2020-08-04&ss=bfqt&srt=sco&sp=rwdlacuptfx&se=2026-07-10T07:00:24Z&st=2021-07-09T23:00:24Z&spr=https&sig=fake_sig"; +const recorderOptions: RecorderStartOptions = { + envSetupForPlayback: { + STORAGE_SAS_URL: fakeSASUrl + } +}; + +const getSanitizerOptions = () => { + return { + generalRegexSanitizers: [ + { + regex: env.STORAGE_SAS_URL.split("/")[2], + value: fakeSASUrl.split("/")[2] + }, + { + regex: env.STORAGE_SAS_URL.split("/")[3].split("?")[1], + value: fakeSASUrl.split("/")[3].split("?")[1] + } + ] + }; +}; + describe("Core V1 tests", () => { let recorder: TestProxyHttpClientCoreV1; beforeEach(async function() { recorder = new TestProxyHttpClientCoreV1(this.currentTest); - await recorder.start({ - envSetupForPlayback: { - STORAGE_SAS_URL: fakeSASUrl - } - }); - await recorder.addSanitizers({ - generalRegexSanitizers: [ - { - regex: env.STORAGE_SAS_URL.split("/")[2], - value: fakeSASUrl.split("/")[2] - }, - { - regex: env.STORAGE_SAS_URL.split("/")[3].split("?")[1], - value: fakeSASUrl.split("/")[3].split("?")[1] - } - ] - }); + await recorder.start(recorderOptions); + await recorder.addSanitizers(getSanitizerOptions()); }); afterEach(async () => { @@ -40,9 +47,7 @@ describe("Core V1 tests", () => { }); it("storage-queue create queue", async function() { - const options: StoragePipelineOptions = {}; - options.httpClient = recorder; - const client = new QueueServiceClient(env.STORAGE_SAS_URL, undefined, options); + const client = new QueueServiceClient(env.STORAGE_SAS_URL, undefined, { httpClient: recorder }); if (!isPlaybackMode()) { recorder.variables["queue-name"] = `queue-${Math.ceil(Math.random() * 1000 + 1000)}`; } diff --git a/sdk/test-utils/testing-recorder-new/test/core-v2-test.spec.ts b/sdk/test-utils/testing-recorder-new/test/core-v2-test.spec.ts index 55cca824e000..137aa6a3c0f3 100644 --- a/sdk/test-utils/testing-recorder-new/test/core-v2-test.spec.ts +++ b/sdk/test-utils/testing-recorder-new/test/core-v2-test.spec.ts @@ -3,7 +3,11 @@ import { env, isPlaybackMode } from "@azure-tools/test-recorder"; import { TableEntity, TableClient } from "@azure/data-tables"; -import { TestProxyHttpClient, recorderHttpPolicy } from "@azure-tools/test-recorder-new"; +import { + TestProxyHttpClient, + recorderHttpPolicy, + RecorderStartOptions +} from "@azure-tools/test-recorder-new"; import { config } from "dotenv"; import { createSimpleEntity } from "./utils/utils"; import { SanitizerOptions } from "@azure-tools/test-recorder-new"; @@ -22,17 +26,19 @@ const sanitizerOptions: SanitizerOptions = { generalRegexSanitizers: [{ regex: "abc", value: "fake_abc" }] }; +const recorderOptions: RecorderStartOptions = { + envSetupForPlayback: { + TABLES_SAS_CONNECTION_STRING: fakeConnString + }, + sanitizerOptions +}; + describe("Core V2 tests", () => { let recorder: TestProxyHttpClient; beforeEach(async function() { recorder = new TestProxyHttpClient(this.currentTest); - await recorder.start({ - envSetupForPlayback: { - TABLES_SAS_CONNECTION_STRING: fakeConnString - } - }); - await recorder.addSanitizers(sanitizerOptions); + await recorder.start(recorderOptions); }); afterEach(async () => {