UNPKG

@aws-cdk/integ-runner

Version:

CDK Integration Testing Tool

483 lines 72.5 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.IntegTestRunner = void 0; const path = require("path"); const cdk_cli_wrapper_1 = require("@aws-cdk/cdk-cli-wrapper"); const cloud_assembly_schema_1 = require("@aws-cdk/cloud-assembly-schema"); const chokidar = require("chokidar"); const fs = require("fs-extra"); const workerpool = require("workerpool"); const runner_base_1 = require("./runner-base"); const logger = require("../logger"); const utils_1 = require("../utils"); const common_1 = require("../workers/common"); /** * An integration test runner that orchestrates executing * integration tests */ class IntegTestRunner extends runner_base_1.IntegRunner { constructor(options, destructiveChanges) { super(options); this._destructiveChanges = destructiveChanges; } async actualTests() { const actualTestSuite = await this.actualTestSuite(); // We don't want new tests written in the legacy mode. // If there is no existing snapshot _and_ this is a legacy // test then point the user to the new `IntegTest` construct if (!this.hasSnapshot() && actualTestSuite.type === 'legacy-test-suite') { throw new Error(`${this.testName} is a new test. Please use the IntegTest construct ` + 'to configure the test\n' + 'https://github.com/aws/aws-cdk/tree/main/packages/%40aws-cdk/integ-tests-alpha'); } return actualTestSuite.testSuite; } createCdkContextJson() { if (!fs.existsSync(this.cdkContextPath)) { fs.writeFileSync(this.cdkContextPath, JSON.stringify({ watch: {}, }, undefined, 2)); } } /** * When running integration tests with the update path workflow * it is important that the snapshot that is deployed is the current snapshot * from the upstream branch. In order to guarantee that, first checkout the latest * (to the user) snapshot from upstream * * It is not straightforward to figure out what branch the current * working branch was created from. This is a best effort attempt to do so. * This assumes that there is an 'origin'. `git remote show origin` returns a list of * all branches and we then search for one that starts with `HEAD branch: ` */ checkoutSnapshot() { // We use the directory that contains the snapshot to run git commands in // We don't change the cwd for executing git, but instead use the -C flag // @see https://git-scm.com/docs/git#Documentation/git.txt--Cltpathgt // This way we are guaranteed to operate under the correct git repo, even // when executing integ-runner from outside the repo under test. const gitCwd = path.dirname(this.snapshotDir); const git = ['git', '-C', gitCwd]; // https://git-scm.com/docs/git-merge-base let baseBranch = undefined; // try to find the base branch that the working branch was created from try { const origin = (0, utils_1.exec)([...git, 'remote', 'show', 'origin']); const originLines = origin.split('\n'); for (const line of originLines) { if (line.trim().startsWith('HEAD branch: ')) { baseBranch = line.trim().split('HEAD branch: ')[1]; } } } catch (e) { logger.warning('%s\n%s', 'Could not determine git origin branch.', `You need to manually checkout the snapshot directory ${this.snapshotDir}` + 'from the merge-base (https://git-scm.com/docs/git-merge-base)'); logger.warning('error: %s', (0, common_1.formatError)(e)); } // if we found the base branch then get the merge-base (most recent common commit) // and checkout the snapshot using that commit if (baseBranch) { const relativeSnapshotDir = path.relative(gitCwd, this.snapshotDir); const checkoutCommand = [...git, 'checkout', [...git, 'merge-base', 'HEAD', baseBranch], '--', relativeSnapshotDir]; try { (0, utils_1.execWithSubShell)(checkoutCommand); } catch (e) { logger.warning('%s\n%s', `Could not checkout snapshot directory '${this.snapshotDir}'. Please verify the following command completes correctly:`, (0, utils_1.renderCommand)(checkoutCommand), ''); logger.warning('error: %s', (0, common_1.formatError)(e)); } } } /** * Runs cdk deploy --watch for an integration test * * This is meant to be run on a single test and will not create a snapshot */ async watchIntegTest(options) { const actualTestSuite = await this.actualTestSuite(); const actualTestCase = actualTestSuite.testSuite[options.testCaseName]; if (!actualTestCase) { throw new Error(`Did not find test case name '${options.testCaseName}' in '${Object.keys(actualTestSuite.testSuite)}'`); } const enableForVerbosityLevel = (needed = 1) => { const verbosity = options.verbosity ?? 0; return (verbosity >= needed) ? true : undefined; }; try { await this.watch({ ...this.defaultArgs, progress: cdk_cli_wrapper_1.StackActivityProgress.BAR, hotswap: cdk_cli_wrapper_1.HotswapMode.FALL_BACK, deploymentMethod: 'direct', profile: this.profile, requireApproval: cloud_assembly_schema_1.RequireApproval.NEVER, traceLogs: enableForVerbosityLevel(2) ?? false, verbose: enableForVerbosityLevel(3), debug: enableForVerbosityLevel(4), watch: true, }, options.testCaseName, options.verbosity ?? 0); } catch (e) { throw e; } } /** * Orchestrates running integration tests. Currently this includes * * 1. (if update workflow is enabled) Deploying the snapshot test stacks * 2. Deploying the integration test stacks * 2. Saving the snapshot (if successful) * 3. Destroying the integration test stacks (if clean=false) * * The update workflow exists to check for cases where a change would cause * a failure to an existing stack, but not for a newly created stack. */ async runIntegTestCase(options) { let assertionResults; const actualTestSuite = await this.actualTestSuite(); const actualTestCase = actualTestSuite.testSuite[options.testCaseName]; if (!actualTestCase) { throw new Error(`Did not find test case name '${options.testCaseName}' in '${Object.keys(actualTestSuite.testSuite)}'`); } const clean = options.clean ?? true; const updateWorkflowEnabled = (options.updateWorkflow ?? true) && (actualTestCase.stackUpdateWorkflow ?? true); const enableForVerbosityLevel = (needed = 1) => { const verbosity = options.verbosity ?? 0; return (verbosity >= needed) ? true : undefined; }; try { if (!options.dryRun && (actualTestCase.cdkCommandOptions?.deploy?.enabled ?? true)) { assertionResults = await this.deploy({ ...this.defaultArgs, profile: this.profile, requireApproval: cloud_assembly_schema_1.RequireApproval.NEVER, verbose: enableForVerbosityLevel(3), debug: enableForVerbosityLevel(4), }, updateWorkflowEnabled, options.testCaseName); } // only create the snapshot if there are no failed assertion results // (i.e. no failures) if (!Object.values(assertionResults ?? {}).some(result => result.status === 'fail')) { await this.createSnapshot(); } } catch (e) { throw e; } finally { if (!options.dryRun) { if (clean && (actualTestCase.cdkCommandOptions?.destroy?.enabled ?? true)) { await this.destroy(options.testCaseName, { ...this.defaultArgs, profile: this.profile, all: true, force: true, app: this.cdkApp, output: path.relative(this.directory, this.cdkOutDir), ...actualTestCase.cdkCommandOptions?.destroy?.args, context: this.getContext(actualTestCase.cdkCommandOptions?.destroy?.args?.context), verbose: enableForVerbosityLevel(3), debug: enableForVerbosityLevel(4), }); } } this.cleanup(); } return assertionResults; } /** * Perform a integ test case stack destruction */ async destroy(testCaseName, destroyArgs) { const actualTestCase = (await this.actualTestSuite()).testSuite[testCaseName]; try { if (actualTestCase.hooks?.preDestroy) { actualTestCase.hooks.preDestroy.forEach(cmd => { (0, utils_1.exec)((0, utils_1.chunks)(cmd), { cwd: path.dirname(this.snapshotDir), }); }); } await this.cdk.destroy({ ...destroyArgs, }); if (actualTestCase.hooks?.postDestroy) { actualTestCase.hooks.postDestroy.forEach(cmd => { (0, utils_1.exec)((0, utils_1.chunks)(cmd), { cwd: path.dirname(this.snapshotDir), }); }); } } catch (e) { this.parseError(e, actualTestCase.cdkCommandOptions?.destroy?.expectError ?? false, actualTestCase.cdkCommandOptions?.destroy?.expectedMessage); } } async watch(watchArgs, testCaseName, verbosity) { const actualTestSuite = await this.actualTestSuite(); const actualTestCase = actualTestSuite.testSuite[testCaseName]; if (actualTestCase.hooks?.preDeploy) { actualTestCase.hooks.preDeploy.forEach(cmd => { (0, utils_1.exec)((0, utils_1.chunks)(cmd), { cwd: path.dirname(this.snapshotDir), }); }); } const deployArgs = { ...watchArgs, lookups: actualTestSuite.enableLookups, stacks: [ ...actualTestCase.stacks, ...actualTestCase.assertionStack ? [actualTestCase.assertionStack] : [], ], output: path.relative(this.directory, this.cdkOutDir), outputsFile: path.relative(this.directory, path.join(this.cdkOutDir, 'assertion-results.json')), ...actualTestCase?.cdkCommandOptions?.deploy?.args, context: { ...this.getContext(actualTestCase?.cdkCommandOptions?.deploy?.args?.context), }, app: this.cdkApp, }; const destroyMessage = { additionalMessages: [ 'After you are done you must manually destroy the deployed stacks', ` ${[ ...process.env.AWS_REGION ? [`AWS_REGION=${process.env.AWS_REGION}`] : [], 'cdk destroy', `-a '${this.cdkApp}'`, deployArgs.stacks.join(' '), `--profile ${deployArgs.profile}`, ].join(' ')}`, ], }; workerpool.workerEmit(destroyMessage); if (watchArgs.verbose) { // if `-vvv` (or above) is used then print out the command that was used // this allows users to manually run the command workerpool.workerEmit({ additionalMessages: [ 'Repro:', ` ${[ 'cdk synth', `-a '${this.cdkApp}'`, `-o '${this.cdkOutDir}'`, ...Object.entries(this.getContext()).flatMap(([k, v]) => typeof v !== 'object' ? [`-c '${k}=${v}'`] : []), deployArgs.stacks.join(' '), `--outputs-file ${deployArgs.outputsFile}`, `--profile ${deployArgs.profile}`, '--hotswap-fallback', ].join(' ')}`, ], }); } const assertionResults = path.join(this.cdkOutDir, 'assertion-results.json'); const watcher = chokidar.watch([this.cdkOutDir], { cwd: this.directory, }); watcher.on('all', (event, file) => { // we only care about changes to the `assertion-results.json` file. If there // are assertions then this will change on every deployment if (assertionResults.endsWith(file) && (event === 'add' || event === 'change')) { const start = Date.now(); if (actualTestCase.hooks?.postDeploy) { actualTestCase.hooks.postDeploy.forEach(cmd => { (0, utils_1.exec)((0, utils_1.chunks)(cmd), { cwd: path.dirname(this.snapshotDir), }); }); } if (actualTestCase.assertionStack && actualTestCase.assertionStackName) { const res = this.processAssertionResults(assertionResults, actualTestCase.assertionStackName, actualTestCase.assertionStack); if (res && Object.values(res).some(r => r.status === 'fail')) { workerpool.workerEmit({ reason: common_1.DiagnosticReason.ASSERTION_FAILED, testName: `${testCaseName} (${watchArgs.profile}`, message: (0, common_1.formatAssertionResults)(res), duration: (Date.now() - start) / 1000, }); } else { workerpool.workerEmit({ reason: common_1.DiagnosticReason.TEST_SUCCESS, testName: `${testCaseName}`, message: res ? (0, common_1.formatAssertionResults)(res) : 'NO ASSERTIONS', duration: (Date.now() - start) / 1000, }); } // emit the destroy message after every run // so that it's visible to the user workerpool.workerEmit(destroyMessage); } } }); await new Promise(resolve => { watcher.on('ready', async () => { resolve({}); }); }); const { promise: waiter, resolve } = (0, utils_1.promiseWithResolvers)(); await this.cdk.watch(deployArgs, { // if `-v` (or above) is passed then stream the logs onStdout: (message) => { if (verbosity > 0) { process.stdout.write(message); } }, // if `-v` (or above) is passed then stream the logs onStderr: (message) => { if (verbosity > 0) { process.stderr.write(message); } }, onClose: async (code) => { if (code !== 0) { throw new Error('Watch exited with error'); } await watcher.close(); resolve(code); }, }); await waiter; } /** * Perform a integ test case deployment, including * performing the update workflow */ async deploy(deployArgs, updateWorkflowEnabled, testCaseName) { const actualTestCase = (await this.actualTestSuite()).testSuite[testCaseName]; try { if (actualTestCase.hooks?.preDeploy) { actualTestCase.hooks.preDeploy.forEach(cmd => { (0, utils_1.exec)((0, utils_1.chunks)(cmd), { cwd: path.dirname(this.snapshotDir), }); }); } // if the update workflow is not disabled, first // perform a deployment with the existing snapshot // then perform a deployment (which will be a stack update) // with the current integration test // We also only want to run the update workflow if there is an existing // snapshot (otherwise there is nothing to update) const expectedTestSuite = await this.expectedTestSuite(); if (updateWorkflowEnabled && this.hasSnapshot() && (expectedTestSuite && testCaseName in expectedTestSuite?.testSuite)) { // make sure the snapshot is the latest from 'origin' this.checkoutSnapshot(); const expectedTestCase = expectedTestSuite.testSuite[testCaseName]; await this.cdk.deploy({ ...deployArgs, stacks: expectedTestCase.stacks, ...expectedTestCase?.cdkCommandOptions?.deploy?.args, context: this.getContext(expectedTestCase?.cdkCommandOptions?.deploy?.args?.context), app: path.relative(this.directory, this.snapshotDir), lookups: expectedTestSuite?.enableLookups, }); } // now deploy the "actual" test. await this.cdk.deploy({ ...deployArgs, lookups: (await this.actualTestSuite()).enableLookups, stacks: [ ...actualTestCase.stacks, ], output: path.relative(this.directory, this.cdkOutDir), ...actualTestCase?.cdkCommandOptions?.deploy?.args, context: this.getContext(actualTestCase?.cdkCommandOptions?.deploy?.args?.context), app: this.cdkApp, }); // If there are any assertions // deploy the assertion stack as well // This is separate from the above deployment because we want to // set `rollback: false`. This allows the assertion stack to deploy all the // assertions instead of failing at the first failed assertion // combining it with the above deployment would prevent any replacement updates if (actualTestCase.assertionStack) { await this.cdk.deploy({ ...deployArgs, lookups: (await this.actualTestSuite()).enableLookups, stacks: [ actualTestCase.assertionStack, ], rollback: false, output: path.relative(this.directory, this.cdkOutDir), ...actualTestCase?.cdkCommandOptions?.deploy?.args, outputsFile: path.relative(this.directory, path.join(this.cdkOutDir, 'assertion-results.json')), context: this.getContext(actualTestCase?.cdkCommandOptions?.deploy?.args?.context), app: this.cdkApp, }); } if (actualTestCase.hooks?.postDeploy) { actualTestCase.hooks.postDeploy.forEach(cmd => { (0, utils_1.exec)((0, utils_1.chunks)(cmd), { cwd: path.dirname(this.snapshotDir), }); }); } if (actualTestCase.assertionStack && actualTestCase.assertionStackName) { return this.processAssertionResults(path.join(this.cdkOutDir, 'assertion-results.json'), actualTestCase.assertionStackName, actualTestCase.assertionStack); } } catch (e) { this.parseError(e, actualTestCase.cdkCommandOptions?.deploy?.expectError ?? false, actualTestCase.cdkCommandOptions?.deploy?.expectedMessage); } return; } /** * Process the outputsFile which contains the assertions results as stack * outputs */ processAssertionResults(file, assertionStackName, assertionStackId) { const results = {}; if (fs.existsSync(file)) { try { const outputs = fs.readJSONSync(file); if (assertionStackName in outputs) { for (const [assertionId, result] of Object.entries(outputs[assertionStackName])) { if (assertionId.startsWith('AssertionResults')) { const assertionResult = JSON.parse(result.replace(/\n/g, '\\n')); if (assertionResult.status === 'fail' || assertionResult.status === 'success') { results[assertionId] = assertionResult; } } } } } catch (e) { // if there are outputs, but they cannot be processed, then throw an error // so that the test fails results[assertionStackId] = { status: 'fail', message: `error processing assertion results: ${e}`, }; } finally { // remove the outputs file so it is not part of the snapshot // it will contain env specific information from values // resolved at deploy time fs.unlinkSync(file); } } return Object.keys(results).length > 0 ? results : undefined; } /** * Parses an error message returned from a CDK command */ parseError(e, expectError, expectedMessage) { if (expectError) { if (expectedMessage) { const message = e.message; if (!message.match(expectedMessage)) { throw (e); } } } else { throw e; } } } exports.IntegTestRunner = IntegTestRunner; //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW50ZWctdGVzdC1ydW5uZXIuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJpbnRlZy10ZXN0LXJ1bm5lci50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7QUFBQSw2QkFBNkI7QUFFN0IsOERBQThFO0FBRTlFLDBFQUFpRTtBQUNqRSxxQ0FBcUM7QUFDckMsK0JBQStCO0FBQy9CLHlDQUF5QztBQUV6QywrQ0FBNEM7QUFDNUMsb0NBQW9DO0FBQ3BDLG9DQUErRjtBQUUvRiw4Q0FBMEY7QUEwRDFGOzs7R0FHRztBQUNILE1BQWEsZUFBZ0IsU0FBUSx5QkFBVztJQUM5QyxZQUFZLE9BQTJCLEVBQUUsa0JBQXdDO1FBQy9FLEtBQUssQ0FBQyxPQUFPLENBQUMsQ0FBQztRQUNmLElBQUksQ0FBQyxtQkFBbUIsR0FBRyxrQkFBa0IsQ0FBQztJQUNoRCxDQUFDO0lBRU0sS0FBSyxDQUFDLFdBQVc7UUFDdEIsTUFBTSxlQUFlLEdBQUcsTUFBTSxJQUFJLENBQUMsZUFBZSxFQUFFLENBQUM7UUFDckQsc0RBQXNEO1FBQ3RELDBEQUEwRDtRQUMxRCw0REFBNEQ7UUFDNUQsSUFBSSxDQUFDLElBQUksQ0FBQyxXQUFXLEVBQUUsSUFBSSxlQUFlLENBQUMsSUFBSSxLQUFLLG1CQUFtQixFQUFFLENBQUM7WUFDeEUsTUFBTSxJQUFJLEtBQUssQ0FBQyxHQUFHLElBQUksQ0FBQyxRQUFRLHFEQUFxRDtnQkFDbkYseUJBQXlCO2dCQUN6QixnRkFBZ0YsQ0FDakYsQ0FBQztRQUNKLENBQUM7UUFFRCxPQUFPLGVBQWUsQ0FBQyxTQUFTLENBQUM7SUFDbkMsQ0FBQztJQUVNLG9CQUFvQjtRQUN6QixJQUFJLENBQUMsRUFBRSxDQUFDLFVBQVUsQ0FBQyxJQUFJLENBQUMsY0FBYyxDQUFDLEVBQUUsQ0FBQztZQUN4QyxFQUFFLENBQUMsYUFBYSxDQUFDLElBQUksQ0FBQyxjQUFjLEVBQUUsSUFBSSxDQUFDLFNBQVMsQ0FBQztnQkFDbkQsS0FBSyxFQUFFLEVBQUc7YUFD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QztZQUNOLE1BQU0sQ0FBQyxDQUFDO1FBQ1YsQ0FBQztJQUNILENBQUM7Q0FDRjtBQWhnQkQsMENBZ2dCQyIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCAqIGFzIHBhdGggZnJvbSAncGF0aCc7XG5pbXBvcnQgdHlwZSB7IERlcGxveU9wdGlvbnMgfSBmcm9tICdAYXdzLWNkay9jZGstY2xpLXdyYXBwZXInO1xuaW1wb3J0IHsgSG90c3dhcE1vZGUsIFN0YWNrQWN0aXZpdHlQcm9ncmVzcyB9IGZyb20gJ0Bhd3MtY2RrL2Nkay1jbGktd3JhcHBlcic7XG5pbXBvcnQgdHlwZSB7IERlc3Ryb3lPcHRpb25zLCBUZXN0Q2FzZSB9IGZyb20gJ0Bhd3MtY2RrL2Nsb3VkLWFzc2VtYmx5LXNjaGVtYSc7XG5pbXBvcnQgeyBSZXF1aXJlQXBwcm92YWwgfSBmcm9tICdAYXdzLWNkay9jbG91ZC1hc3NlbWJseS1zY2hlbWEnO1xuaW1wb3J0ICogYXMgY2hva2lkYXIgZnJvbSAnY2hva2lkYXInO1xuaW1wb3J0ICogYXMgZnMgZnJvbSAnZnMtZXh0cmEnO1xuaW1wb3J0ICogYXMgd29ya2VycG9vbCBmcm9tICd3b3JrZXJwb29sJztcbmltcG9ydCB0eXBlIHsgSW50ZWdSdW5uZXJPcHRpb25zIH0gZnJvbSAnLi9ydW5uZXItYmFzZSc7XG5pbXBvcnQgeyBJbnRlZ1J1bm5lciB9IGZyb20gJy4vcnVubmVyLWJhc2UnO1xuaW1wb3J0ICogYXMgbG9nZ2VyIGZyb20gJy4uL2xvZ2dlcic7XG5pbXBvcnQgeyBjaHVua3MsIGV4ZWMsIGV4ZWNXaXRoU3ViU2hlbGwsIHByb21pc2VXaXRoUmVzb2x2ZXJzLCByZW5kZXJDb21tYW5kIH0gZnJvbSAnLi4vdXRpbHMnO1xuaW1wb3J0IHR5cGUgeyBEZXN0cnVjdGl2ZUNoYW5nZSwgQXNzZXJ0aW9uUmVzdWx0cywgQXNzZXJ0aW9uUmVzdWx0IH0gZnJvbSAnLi4vd29ya2Vycy9jb21tb24nO1xuaW1wb3J0IHsgRGlhZ25vc3RpY1JlYXNvbiwgZm9ybWF0QXNzZXJ0aW9uUmVzdWx0cywgZm9ybWF0RXJyb3IgfSBmcm9tICcuLi93b3JrZXJzL2NvbW1vbic7XG5cbmV4cG9ydCBpbnRlcmZhY2UgQ29tbW9uT3B0aW9ucyB7XG4gIC8qKlxuICAgKiBUaGUgbmFtZSBvZiB0aGUgdGVzdCBjYXNlXG4gICAqL1xuICByZWFkb25seSB0ZXN0Q2FzZU5hbWU6IHN0cmluZztcblxuICAvKipcbiAgICogVGhlIGxldmVsIG9mIHZlcmJvc2l0eSBmb3IgbG9nZ2luZy5cbiAgICpcbiAgICogQGRlZmF1bHQgMFxuICAgKi9cbiAgcmVhZG9ubHkgdmVyYm9zaXR5PzogbnVtYmVyO1xufVxuXG5leHBvcnQgaW50ZXJmYWNlIFdhdGNoT3B0aW9ucyBleHRlbmRzIENvbW1vbk9wdGlvbnMge1xuXG59XG5cbi8qKlxuICogT3B0aW9ucyBmb3IgdGhlIGludGVncmF0aW9uIHRlc3QgcnVubmVyXG4gKi9cbmV4cG9ydCBpbnRlcmZhY2UgUnVuT3B0aW9ucyBleHRlbmRzIENvbW1vbk9wdGlvbnMge1xuICAvKipcbiAgICogV2hldGhlciBvciBub3QgdG8gcnVuIGBjZGsgZGVzdHJveWAgYW5kIGNsZWFudXAgdGhlXG4gICAqIGludGVncmF0aW9uIHRlc3Qgc3RhY2tzLlxuICAgKlxuICAgKiBTZXQgdGhpcyB0byBmYWxzZSBpZiB5b3UgbmVlZCB0byBwZXJmb3JtIGFueSB2YWxpZGF0aW9uXG4gICAqIG9yIHRyb3VibGVzaG9vdGluZyBhZnRlciBkZXBsb3ltZW50LlxuICAgKlxuICAgKiBAZGVmYXVsdCB0cnVlXG4gICAqL1xuICByZWFkb25seSBjbGVhbj86IGJvb2xlYW47XG5cbiAgLyoqXG4gICAqIElmIHNldCB0byB0cnVlLCB0aGUgaW50ZWdyYXRpb24gdGVzdCB3aWxsIG5vdCBkZXBsb3lcbiAgICogYW55dGhpbmcgYW5kIHdpbGwgc2ltcGx5IHVwZGF0ZSB0aGUgc25hcHNob3QuXG4gICAqXG4gICAqIFlvdSBzaG91bGQgTk9UIHVzZSB0aGlzIG1ldGhvZCBzaW5jZSB5b3UgYXJlIGVzc2VudGlhbGx5XG4gICAqIGJ5cGFzc2luZyB0aGUgaW50ZWdyYXRpb24gdGVzdC5cbiAgICpcbiAgICogQGRlZmF1bHQgZmFsc2VcbiAgICovXG4gIHJlYWRvbmx5IGRyeVJ1bj86IGJvb2xlYW47XG5cbiAgLyoqXG4gICAqIElmIHRoaXMgaXMgc2V0IHRvIGZhbHNlIHRoZW4gdGhlIHN0YWNrIHVwZGF0ZSB3b3JrZmxvdyB3aWxsXG4gICAqIG5vdCBiZSBydW5cbiAgICpcbiAgICogVGhlIHVwZGF0ZSB3b3JrZmxvdyBleGlzdHMgdG8gY2hlY2sgZm9yIGNhc2VzIHdoZXJlIGEgY2hhbmdlIHdvdWxkIGNhdXNlXG4gICAqIGEgZmFpbHVyZSB0byBhbiBleGlzdGluZyBzdGFjaywgYnV0IG5vdCBmb3IgYSBuZXdseSBjcmVhdGVkIHN0YWNrLlxuICAgKlxuICAgKiBAZGVmYXVsdCB0cnVlXG4gICAqL1xuICByZWFkb25seSB1cGRhdGVXb3JrZmxvdz86IGJvb2xlYW47XG59XG5cbi8qKlxuICogQW4gaW50ZWdyYXRpb24gdGVzdCBydW5uZXIgdGhhdCBvcmNoZXN0cmF0ZXMgZXhlY3V0aW5nXG4gKiBpbnRlZ3JhdGlvbiB0ZXN0c1xuICovXG5leHBvcnQgY2xhc3MgSW50ZWdUZXN0UnVubmVyIGV4dGVuZHMgSW50ZWdSdW5uZXIge1xuICBjb25zdHJ1Y3RvcihvcHRpb25zOiBJbnRlZ1J1bm5lck9wdGlvbnMsIGRlc3RydWN0aXZlQ2hhbmdlcz86IERlc3RydWN0aXZlQ2hhbmdlW10pIHtcbiAgICBzdXBlcihvcHRpb25zKTtcbiAgICB0aGlzLl9kZXN0cnVjdGl2ZUNoYW5nZXMgPSBkZXN0cnVjdGl2ZUNoYW5nZXM7XG4gIH1cblxuICBwdWJsaWMgYXN5bmMgYWN0dWFsVGVzdHMoKTogUHJvbWlzZTx7IFt0ZXN0TmFtZTogc3RyaW5nXTogVGVzdENhc2UgfSB8IHVuZGVmaW5lZD4ge1xuICAgIGNvbnN0IGFjdHVhbFRlc3RTdWl0ZSA9IGF3YWl0IHRoaXMuYWN0dWFsVGVzdFN1aXRlKCk7XG4gICAgLy8gV2UgZG9uJ3Qgd2FudCBuZXcgdGVzdHMgd3JpdHRlbiBpbiB0aGUgbGVnYWN5IG1vZGUuXG4gICAgLy8gSWYgdGhlcmUgaXMgbm8gZXhpc3Rpbmcgc25hcHNob3QgX2FuZF8gdGhpcyBpcyBhIGxlZ2FjeVxuICAgIC8vIHRlc3QgdGhlbiBwb2ludCB0aGUgdXNlciB0byB0aGUgbmV3IGBJbnRlZ1Rlc3RgIGNvbnN0cnVjdFxuICAgIGlmICghdGhpcy5oYXNTbmFwc2hvdCgpICYmIGFjdHVhbFRlc3RTdWl0ZS50eXBlID09PSAnbGVnYWN5LXRlc3Qtc3VpdGUnKSB7XG4gICAgICB0aHJvdyBuZXcgRXJyb3IoYCR7dGhpcy50ZXN0TmFtZX0gaXMgYSBuZXcgdGVzdC4gUGxlYXNlIHVzZSB0aGUgSW50ZWdUZXN0IGNvbnN0cnVjdCBgICtcbiAgICAgICAgJ3RvIGNvbmZpZ3VyZSB0aGUgdGVzdFxcbicgK1xuICAgICAgICAnaHR0cHM6Ly9naXRodWIuY29tL2F3cy9hd3MtY2RrL3RyZWUvbWFpbi9wYWNrYWdlcy8lNDBhd3MtY2RrL2ludGVnLXRlc3RzLWFscGhhJyxcbiAgICAgICk7XG4gICAgfVxuXG4gICAgcmV0dXJuIGFjdHVhbFRlc3RTdWl0ZS50ZXN0U3VpdGU7XG4gIH1cblxuICBwdWJsaWMgY3JlYXRlQ2RrQ29udGV4dEpzb24oKTogdm9pZCB7XG4gICAgaWYgKCFmcy5leGlzdHNTeW5jKHRoaXMuY2RrQ29udGV4dFBhdGgpKSB7XG4gICAgICBmcy53cml0ZUZpbGVTeW5jKHRoaXMuY2RrQ29udGV4dFBhdGgsIEpTT04uc3RyaW5naWZ5KHtcbiAgICAgICAgd2F0Y2g6IHsgfSxcbiAgICAgIH0sIHVuZGVmaW5lZCwgMikpO1xuICAgIH1cbiAgfVxuXG4gIC8qKlxuICAgKiBXaGVuIHJ1bm5pbmcgaW50ZWdyYXRpb24gdGVzdHMgd2l0aCB0aGUgdXBkYXRlIHBhdGggd29ya2Zsb3dcbiAgICogaXQgaXMgaW1wb3J0YW50IHRoYXQgdGhlIHNuYXBzaG90IHRoYXQgaXMgZGVwbG95ZWQgaXMgdGhlIGN1cnJlbnQgc25hcHNob3RcbiAgICogZnJvbSB0aGUgdXBzdHJlYW0gYnJhbmNoLiBJbiBvcmRlciB0byBndWFyYW50ZWUgdGhhdCwgZmlyc3QgY2hlY2tvdXQgdGhlIGxhdGVzdFxuICAgKiAodG8gdGhlIHVzZXIpIHNuYXBzaG90IGZyb20gdXBzdHJlYW1cbiAgICpcbiAgICogSXQgaXMgbm90IHN0cmFpZ2h0Zm9yd2FyZCB0byBmaWd1cmUgb3V0IHdoYXQgYnJhbmNoIHRoZSBjdXJyZW50XG4gICAqIHdvcmtpbmcgYnJhbmNoIHdhcyBjcmVhdGVkIGZyb20uIFRoaXMgaXMgYSBiZXN0IGVmZm9ydCBhdHRlbXB0IHRvIGRvIHNvLlxuICAgKiBUaGlzIGFzc3VtZXMgdGhhdCB0aGVyZSBpcyBhbiAnb3JpZ2luJy4gYGdpdCByZW1vdGUgc2hvdyBvcmlnaW5gIHJldHVybnMgYSBsaXN0IG9mXG4gICAqIGFsbCBicmFuY2hlcyBhbmQgd2UgdGhlbiBzZWFyY2ggZm9yIG9uZSB0aGF0IHN0YXJ0cyB3aXRoIGBIRUFEIGJyYW5jaDogYFxuICAgKi9cbiAgcHJpdmF0ZSBjaGVja291dFNuYXBzaG90KCk6IHZvaWQge1xuICAgIC8vIFdlIHVzZSB0aGUgZGlyZWN0b3J5IHRoYXQgY29udGFpbnMgdGhlIHNuYXBzaG90IHRvIHJ1biBnaXQgY29tbWFuZHMgaW5cbiAgICAvLyBXZSBkb24ndCBjaGFuZ2UgdGhlIGN3ZCBmb3IgZXhlY3V0aW5nIGdpdCwgYnV0IGluc3RlYWQgdXNlIHRoZSAtQyBmbGFnXG4gICAgLy8gQHNlZSBodHRwczovL2dpdC1zY20uY29tL2RvY3MvZ2l0I0RvY3VtZW50YXRpb24vZ2l0LnR4dC0tQ2x0cGF0aGd0XG4gICAgLy8gVGhpcyB3YXkgd2UgYXJlIGd1YXJhbnRlZWQgdG8gb3BlcmF0ZSB1bmRlciB0aGUgY29ycmVjdCBnaXQgcmVwbywgZXZlblxuICAgIC8vIHdoZW4gZXhlY3V0aW5nIGludGVnLXJ1bm5lciBmcm9tIG91dHNpZGUgdGhlIHJlcG8gdW5kZXIgdGVzdC5cbiAgICBjb25zdCBnaXRDd2QgPSBwYXRoLmRpcm5hbWUodGhpcy5zbmFwc2hvdERpcik7XG4gICAgY29uc3QgZ2l0ID0gWydnaXQnLCAnLUMnLCBnaXRDd2RdO1xuXG4gICAgLy8gaHR0cHM6Ly9naXQtc2NtLmNvbS9kb2NzL2dpdC1tZXJnZS1iYXNlXG4gICAgbGV0IGJhc2VCcmFuY2g6IHN0cmluZyB8IHVuZGVmaW5lZCA9IHVuZGVmaW5lZDtcbiAgICAvLyB0cnkgdG8gZmluZCB0aGUgYmFzZSBicmFuY2ggdGhhdCB0aGUgd29ya2luZyBicmFuY2ggd2FzIGNyZWF0ZWQgZnJvbVxuICAgIHRyeSB7XG4gICAgICBjb25zdCBvcmlnaW46IHN0cmluZyA9IGV4ZWMoWy4uLmdpdCwgJ3JlbW90ZScsICdzaG93JywgJ29yaWdpbiddKTtcbiAgICAgIGNvbnN0IG9yaWdpbkxpbmVzID0gb3JpZ2luLnNwbGl0KCdcXG4nKTtcbiAgICAgIGZvciAoY29uc3QgbGluZSBvZiBvcmlnaW5MaW5lcykge1xuICAgICAgICBpZiAobGluZS50cmltKCkuc3RhcnRzV2l0aCgnSEVBRCBicmFuY2g6ICcpKSB7XG4gICAgICAgICAgYmFzZUJyYW5jaCA9IGxpbmUudHJpbSgpLnNwbGl0KCdIRUFEIGJyYW5jaDogJylbMV07XG4gICAgICAgIH1cbiAgICAgIH1cbiAgICB9IGNhdGNoIChlKSB7XG4gICAgICBsb2dnZXIud2FybmluZygnJXNcXG4lcycsXG4gICAgICAgICdDb3VsZCBub3QgZGV0ZXJtaW5lIGdpdCBvcmlnaW4gYnJhbmNoLicsXG4gICAgICAgIGBZb3UgbmVlZCB0byBtYW51YWxseSBjaGVja291dCB0aGUgc25hcHNob3QgZGlyZWN0b3J5ICR7dGhpcy5zbmFwc2hvdERpcn1gICtcbiAgICAgICAgJ2Zyb20gdGhlIG1lcmdlLWJhc2UgKGh0dHBzOi8vZ2l0LXNjbS5jb20vZG9jcy9naXQtbWVyZ