UNPKG

@nlabs/lex

Version:
416 lines (409 loc) 49.9 kB
/** * Copyright (c) 2018-Present, Nitrogen Labs, Inc. * Copyrights licensed under the MIT License. See the accompanying LICENSE file for terms. */ import { execa } from 'execa'; import { existsSync, readFileSync } from 'fs'; import { sync as globSync } from 'glob'; import { resolve as pathResolve } from 'path'; import { LexConfig, getTypeScriptConfigPath } from '../../LexConfig.js'; import { createSpinner } from '../../utils/app.js'; import { resolveBinaryPath } from '../../utils/file.js'; import { log } from '../../utils/log.js'; import { aiFunction } from '../ai/ai.js'; const detectESM = (cwd)=>{ const packageJsonPath = pathResolve(cwd, 'package.json'); if (existsSync(packageJsonPath)) { try { const packageJsonContent = readFileSync(packageJsonPath, 'utf8'); const packageJson = JSON.parse(packageJsonContent); return packageJson.type === 'module'; } catch (_error) { return false; } } return false; }; const defaultExit = (code)=>{ if (process.env.JEST_WORKER_ID || process.env.NODE_ENV === 'test') { return undefined; } process.exit(code); }; export const getTestFilePatterns = (testPathPattern)=>{ const defaultPatterns = [ '**/*.test.*', '**/*.spec.*', '**/*.integration.*' ]; if (!testPathPattern) { return defaultPatterns; } return [ testPathPattern ]; }; const findUncoveredSourceFiles = ()=>{ const sourceFiles = globSync('src/**/*.{ts,tsx,js,jsx}', { cwd: process.cwd(), ignore: [ '**/node_modules/**', '**/dist/**', '**/lib/**', '**/*.test.*', '**/*.spec.*' ] }); const testFiles = globSync('**/*.{test,spec}.{ts,tsx,js,jsx}', { cwd: process.cwd(), ignore: [ '**/node_modules/**', '**/dist/**', '**/lib/**' ] }); return sourceFiles.filter((sourceFile)=>{ const baseName = sourceFile.replace(/\.[^/.]+$/, ''); return !testFiles.some((testFile)=>testFile.includes(baseName)); }); }; const processTestResults = (outputFile)=>{ if (!outputFile) { return null; } try { const content = readFileSync(outputFile, 'utf-8'); return JSON.parse(content); } catch (_error) { return null; } }; export const test = async (options, args, filesOrCallback, callbackParam)=>{ // Backward-compat argument normalization: allow callback as third param let files; let callback = defaultExit; if (typeof filesOrCallback === 'function') { callback = filesOrCallback; } else { files = filesOrCallback; callback = callbackParam || defaultExit; } const { analyze = false, aiAnalyze = false, aiDebug = false, aiGenerate = false, bail, changedFilesWithAncestor, changedSince, ci, cliName = 'Lex', collectCoverageFrom, colors, config, debug = false, debugTests = false, detectOpenHandles, env, errorOnDeprecated, expand, forceExit, generate = false, json, lastCommit, listTests, logHeapUsage, maxWorkers, noStackTrace, notify, onlyChanged, outputFile, passWithNoTests, quiet, removeCache, runInBand, setup, showConfig, silent, testLocationInResults, testNamePattern, testPathPattern, update, useStderr, verbose, watch, watchAll } = options; const useGenerate = generate || aiGenerate; const useAnalyze = analyze || aiAnalyze; const useDebug = debugTests || aiDebug; log(`${cliName} testing...`, 'info', quiet); const spinner = createSpinner(quiet); await LexConfig.parseConfig(options); const { useTypescript } = LexConfig.config; if (useTypescript) { const testConfigPath = getTypeScriptConfigPath('tsconfig.test.json'); if (existsSync(testConfigPath)) { log('Using tsconfig.test.json for testing...', 'info', quiet); } else { LexConfig.checkTestTypescriptConfig(); } } if (useGenerate) { spinner.start('AI is analyzing code to generate test cases...'); try { const uncoveredFiles = findUncoveredSourceFiles(); if (uncoveredFiles.length > 0) { const targetFile = uncoveredFiles[0]; await aiFunction({ context: true, file: targetFile, prompt: `Generate Jest unit tests for this file: ${targetFile}\n\n${readFileSync(targetFile, 'utf-8')}\n\nPlease create comprehensive tests that cover the main functionality. Include test fixtures and mocks where necessary.`, quiet, task: 'test' }); spinner.succeed(`AI test generation suggestions provided for ${targetFile}`); } else { spinner.succeed('All source files appear to have corresponding test files'); } } catch (aiError) { spinner.fail('Could not generate AI test suggestions'); if (!quiet) { // eslint-disable-next-line no-console console.error('AI test generation error:', aiError); } } } const projectJestBin = pathResolve(process.cwd(), 'node_modules/.bin/jest'); let jestPath; if (existsSync(projectJestBin)) { jestPath = projectJestBin; } else { jestPath = resolveBinaryPath('jest'); } if (!jestPath) { log(`\n${cliName} Error: Jest binary not found in Lex's node_modules or monorepo root`, 'error', quiet); log('Please reinstall Lex or check your installation.', 'info', quiet); return 1; } let jestConfigFile; let projectJestConfig = null; if (config) { jestConfigFile = config; } else { const projectJestConfigPath = pathResolve(process.cwd(), 'jest.config.js'); const projectJestConfigCjsPath = pathResolve(process.cwd(), 'jest.config.cjs'); const projectJestConfigMjsPath = pathResolve(process.cwd(), 'jest.config.mjs'); const projectJestConfigJsonPath = pathResolve(process.cwd(), 'jest.config.json'); if (existsSync(projectJestConfigPath)) { jestConfigFile = projectJestConfigPath; if (debug) { log(`Using project Jest config file: ${jestConfigFile}`, 'info', quiet); } } else if (existsSync(projectJestConfigCjsPath)) { jestConfigFile = projectJestConfigCjsPath; if (debug) { log(`Using project Jest config file (CJS): ${jestConfigFile}`, 'info', quiet); } } else if (existsSync(projectJestConfigMjsPath)) { jestConfigFile = projectJestConfigMjsPath; if (debug) { log(`Using project Jest config file (MJS): ${jestConfigFile}`, 'info', quiet); } } else if (existsSync(projectJestConfigJsonPath)) { jestConfigFile = projectJestConfigJsonPath; if (debug) { log(`Using project Jest config file (JSON): ${jestConfigFile}`, 'info', quiet); } } else { // No Jest config file exists in the project // Check if there's a Jest config in lex.config.cjs projectJestConfig = LexConfig.config.jest; const lexDir = LexConfig.getLexDir(); const lexJestConfig = pathResolve(lexDir, 'jest.config.mjs'); if (debug) { log(`Looking for Jest config at: ${lexJestConfig}`, 'info', quiet); log(`File exists: ${existsSync(lexJestConfig)}`, 'info', quiet); } if (existsSync(lexJestConfig)) { jestConfigFile = lexJestConfig; if (projectJestConfig && Object.keys(projectJestConfig).length > 0) { if (debug) { log(`Using Lex Jest config with project Jest config from lex.config.cjs: ${jestConfigFile}`, 'info', quiet); } } else { if (debug) { log(`Using Lex Jest config (no project Jest config found): ${jestConfigFile}`, 'info', quiet); } } } else { if (debug) { log('No Jest config found in project or Lex', 'warn', quiet); } jestConfigFile = ''; } } } const jestSetupFile = setup || pathResolve(process.cwd(), 'jest.setup.js'); const jestOptions = [ '--no-cache' ]; const isESM = detectESM(process.cwd()); let nodeOptions = process.env.NODE_OPTIONS || ''; if (isESM) { if (!nodeOptions.includes('--experimental-vm-modules')) { nodeOptions = `${nodeOptions} --experimental-vm-modules`.trim(); } log('ESM project detected, using --experimental-vm-modules in NODE_OPTIONS', 'info', quiet); } if (jestConfigFile) { jestOptions.push('--config', jestConfigFile); } if (bail) { jestOptions.push('--bail'); } if (changedFilesWithAncestor) { jestOptions.push('--changedFilesWithAncestor'); } if (changedSince) { jestOptions.push('--changedSince'); } if (ci) { jestOptions.push('--ci'); } if (collectCoverageFrom) { jestOptions.push('--collectCoverageFrom', collectCoverageFrom); } if (colors) { jestOptions.push('--colors'); } if (debug) { jestOptions.push('--debug'); } if (detectOpenHandles) { jestOptions.push('--detectOpenHandles'); } if (env) { jestOptions.push('--env'); } if (errorOnDeprecated) { jestOptions.push('--errorOnDeprecated'); } if (expand) { jestOptions.push('--expand'); } if (forceExit) { jestOptions.push('--forceExit'); } if (json) { jestOptions.push('--json'); } if (lastCommit) { jestOptions.push('--lastCommit'); } if (listTests) { jestOptions.push('--listTests'); } if (logHeapUsage) { jestOptions.push('--logHeapUsage'); } if (maxWorkers) { jestOptions.push('--maxWorkers', maxWorkers); } if (noStackTrace) { jestOptions.push('--noStackTrace'); } if (notify) { jestOptions.push('--notify'); } if (onlyChanged) { jestOptions.push('--onlyChanged'); } let tempOutputFile = outputFile; if ((useAnalyze || useDebug) && !outputFile) { tempOutputFile = '.lex-test-results.json'; jestOptions.push('--json', '--outputFile', tempOutputFile); } else if (outputFile) { jestOptions.push('--outputFile', outputFile); } if (passWithNoTests) { jestOptions.push('--passWithNoTests'); } if (runInBand) { jestOptions.push('--runInBand'); } if (showConfig) { jestOptions.push('--showConfig'); } if (silent) { jestOptions.push('--silent'); } if (testLocationInResults) { jestOptions.push('--testLocationInResults'); } if (testNamePattern) { jestOptions.push('--testNamePattern', testNamePattern); } if (testPathPattern) { jestOptions.push('--testPathPattern', testPathPattern); } if (useStderr) { jestOptions.push('--useStderr'); } if (verbose) { jestOptions.push('--verbose'); } if (watchAll) { jestOptions.push('--watchAll'); } if (removeCache) { jestOptions.push('--no-cache'); } if (jestSetupFile && existsSync(jestSetupFile)) { jestOptions.push(`--setupFilesAfterEnv=${jestSetupFile}`); } if (update) { jestOptions.push('--updateSnapshot'); } if (watch) { jestOptions.push('--watch', watch); } if (args) { jestOptions.push(...args); } if (files && files.length > 0) { jestOptions.push(...files); } if (debug) { log(`Jest options: ${jestOptions.join(' ')}`, 'info', quiet); log(`NODE_OPTIONS: ${nodeOptions}`, 'info', quiet); } try { const env = { ...process.env, NODE_OPTIONS: nodeOptions }; await execa(jestPath, jestOptions, { encoding: 'utf8', env, stdio: 'inherit' }); spinner.succeed('Testing completed!'); if (useAnalyze) { spinner.start('AI is analyzing test coverage and suggesting improvements...'); try { const testResults = processTestResults(tempOutputFile); const filePatterns = getTestFilePatterns(testPathPattern); await aiFunction({ context: true, prompt: `Analyze these Jest test results and suggest test coverage improvements: ${JSON.stringify(testResults, null, 2)} Test patterns: ${filePatterns.join(', ')} Please provide: 1. Analysis of current coverage gaps 2. Suggestions for improving test cases 3. Recommendations for additional integration test scenarios 4. Best practices for increasing test effectiveness`, quiet, task: 'optimize' }); spinner.succeed('AI test analysis complete'); } catch (aiError) { spinner.fail('Could not generate AI test analysis'); if (!quiet) { // eslint-disable-next-line no-console console.error('AI analysis error:', aiError); } } } callback(0); return 0; } catch (error) { log(`\n${cliName} Error: Check for unit test errors and/or coverage.`, 'error', quiet); spinner.fail('Testing failed!'); if (useDebug) { spinner.start('AI is analyzing test failures...'); try { const testResults = processTestResults(tempOutputFile); await aiFunction({ context: true, prompt: `Debug these failed Jest tests and suggest fixes: ${JSON.stringify(error.message, null, 2)} Test results: ${JSON.stringify(testResults, null, 2)} Please provide: 1. Analysis of why the tests are failing 2. Specific suggestions to fix each failing test 3. Any potential issues with test fixtures or mocks 4. Code examples for solutions`, quiet, task: 'help' }); spinner.succeed('AI debugging assistance complete'); } catch (aiError) { spinner.fail('Could not generate AI debugging assistance'); if (!quiet) { // eslint-disable-next-line no-console console.error('AI debugging error:', aiError); } } } callback(1); return 1; } }; export default test; //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIi4uLy4uLy4uL3NyYy9jb21tYW5kcy90ZXN0L3Rlc3QudHMiXSwic291cmNlc0NvbnRlbnQiOlsiLyoqXG4gKiBDb3B5cmlnaHQgKGMpIDIwMTgtUHJlc2VudCwgTml0cm9nZW4gTGFicywgSW5jLlxuICogQ29weXJpZ2h0cyBsaWNlbnNlZCB1bmRlciB0aGUgTUlUIExpY2Vuc2UuIFNlZSB0aGUgYWNjb21wYW55aW5nIExJQ0VOU0UgZmlsZSBmb3IgdGVybXMuXG4gKi9cbmltcG9ydCB7ZXhlY2F9IGZyb20gJ2V4ZWNhJztcbmltcG9ydCB7ZXhpc3RzU3luYywgcmVhZEZpbGVTeW5jfSBmcm9tICdmcyc7XG5pbXBvcnQge3N5bmMgYXMgZ2xvYlN5bmN9IGZyb20gJ2dsb2InO1xuaW1wb3J0IHtyZXNvbHZlIGFzIHBhdGhSZXNvbHZlfSBmcm9tICdwYXRoJztcblxuaW1wb3J0IHtMZXhDb25maWcsIGdldFR5cGVTY3JpcHRDb25maWdQYXRofSBmcm9tICcuLi8uLi9MZXhDb25maWcuanMnO1xuaW1wb3J0IHtjcmVhdGVTcGlubmVyfSBmcm9tICcuLi8uLi91dGlscy9hcHAuanMnO1xuaW1wb3J0IHtyZXNvbHZlQmluYXJ5UGF0aH0gZnJvbSAnLi4vLi4vdXRpbHMvZmlsZS5qcyc7XG5pbXBvcnQge2xvZ30gZnJvbSAnLi4vLi4vdXRpbHMvbG9nLmpzJztcbmltcG9ydCB7YWlGdW5jdGlvbn0gZnJvbSAnLi4vYWkvYWkuanMnO1xuXG5jb25zdCBkZXRlY3RFU00gPSAoY3dkOiBzdHJpbmcpOiBib29sZWFuID0+IHtcbiAgY29uc3QgcGFja2FnZUpzb25QYXRoID0gcGF0aFJlc29sdmUoY3dkLCAncGFja2FnZS5qc29uJyk7XG5cbiAgaWYoZXhpc3RzU3luYyhwYWNrYWdlSnNvblBhdGgpKSB7XG4gICAgdHJ5IHtcbiAgICAgIGNvbnN0IHBhY2thZ2VKc29uQ29udGVudCA9IHJlYWRGaWxlU3luYyhwYWNrYWdlSnNvblBhdGgsICd1dGY4Jyk7XG4gICAgICBjb25zdCBwYWNrYWdlSnNvbiA9IEpTT04ucGFyc2UocGFja2FnZUpzb25Db250ZW50KTtcbiAgICAgIHJldHVybiBwYWNrYWdlSnNvbi50eXBlID09PSAnbW9kdWxlJztcbiAgICB9IGNhdGNoKF9lcnJvcikge1xuICAgICAgcmV0dXJuIGZhbHNlO1xuICAgIH1cbiAgfVxuXG4gIHJldHVybiBmYWxzZTtcbn07XG5cbmV4cG9ydCBpbnRlcmZhY2UgVGVzdE9wdGlvbnMge1xuICByZWFkb25seSBhbmFseXplPzogYm9vbGVhbjtcbiAgcmVhZG9ubHkgYWlEZWJ1Zz86IGJvb2xlYW47XG4gIHJlYWRvbmx5IGFpR2VuZXJhdGU/OiBib29sZWFuO1xuICByZWFkb25seSBhaUFuYWx5emU/OiBib29sZWFuO1xuICByZWFkb25seSBiYWlsPzogYm9vbGVhbjtcbiAgcmVhZG9ubHkgY2hhbmdlZEZpbGVzV2l0aEFuY2VzdG9yPzogYm9vbGVhbjtcbiAgcmVhZG9ubHkgY2hhbmdlZFNpbmNlPzogc3RyaW5nO1xuICByZWFkb25seSBjaT86IGJvb2xlYW47XG4gIHJlYWRvbmx5IGNsaU5hbWU/OiBzdHJpbmc7XG4gIHJlYWRvbmx5IGNvbGxlY3RDb3ZlcmFnZUZyb20/OiBzdHJpbmc7XG4gIHJlYWRvbmx5IGNvbG9ycz86IGJvb2xlYW47XG4gIHJlYWRvbmx5IGNvbmZpZz86IHN0cmluZztcbiAgcmVhZG9ubHkgZGVidWc/OiBib29sZWFuO1xuICByZWFkb25seSBkZWJ1Z1Rlc3RzPzogYm9vbGVhbjtcbiAgcmVhZG9ubHkgZGV0ZWN0T3BlbkhhbmRsZXM/OiBib29sZWFuO1xuICByZWFkb25seSBlbnY/OiBzdHJpbmc7XG4gIHJlYWRvbmx5IGVycm9yT25EZXByZWNhdGVkPzogYm9vbGVhbjtcbiAgcmVhZG9ubHkgZXhwYW5kPzogYm9vbGVhbjtcbiAgcmVhZG9ubHkgZm9yY2VFeGl0PzogYm9vbGVhbjtcbiAgcmVhZG9ubHkgZ2VuZXJhdGU/OiBib29sZWFuO1xuICByZWFkb25seSBqc29uPzogYm9vbGVhbjtcbiAgcmVhZG9ubHkgbGFzdENvbW1pdD86IGJvb2xlYW47XG4gIHJlYWRvbmx5IGxpc3RUZXN0cz86IGJvb2xlYW47XG4gIHJlYWRvbmx5IGxvZ0hlYXBVc2FnZT86IGJvb2xlYW47XG4gIHJlYWRvbmx5IG1heFdvcmtlcnM/OiBzdHJpbmc7XG4gIHJlYWRvbmx5IG5vU3RhY2tUcmFjZT86IGJvb2xlYW47XG4gIHJlYWRvbmx5IG5vdGlmeT86IGJvb2xlYW47XG4gIHJlYWRvbmx5IG9ubHlDaGFuZ2VkPzogYm9vbGVhbjtcbiAgcmVhZG9ubHkgb3V0cHV0RmlsZT86IHN0cmluZztcbiAgcmVhZG9ubHkgcGFzc1dpdGhOb1Rlc3RzPzogYm9vbGVhbjtcbiAgcmVhZG9ubHkgcXVpZXQ/OiBib29sZWFuO1xuICByZWFkb25seSByZW1vdmVDYWNoZT86IGJvb2xlYW47XG4gIHJlYWRvbmx5IHJ1bkluQmFuZD86IGJvb2xlYW47XG4gIHJlYWRvbmx5IHNldHVwPzogc3RyaW5nO1xuICByZWFkb25seSBzaG93Q29uZmlnPzogYm9vbGVhbjtcbiAgcmVhZG9ubHkgc2lsZW50PzogYm9vbGVhbjtcbiAgcmVhZG9ubHkgdGVzdExvY2F0aW9uSW5SZXN1bHRzPzogYm9vbGVhbjtcbiAgcmVhZG9ubHkgdGVzdE5hbWVQYXR0ZXJuPzogc3RyaW5nO1xuICByZWFkb25seSB0ZXN0UGF0aFBhdHRlcm4/OiBzdHJpbmc7XG4gIHJlYWRvbmx5IHVwZGF0ZT86IGJvb2xlYW47XG4gIHJlYWRvbmx5IHVzZVN0ZGVycj86IGJvb2xlYW47XG4gIHJlYWRvbmx5IHZlcmJvc2U/OiBib29sZWFuO1xuICByZWFkb25seSB3YXRjaD86IHN0cmluZztcbiAgcmVhZG9ubHkgd2F0Y2hBbGw/OiBib29sZWFuO1xufVxuXG5leHBvcnQgdHlwZSBUZXN0Q2FsbGJhY2sgPSB0eXBlb2YgcHJvY2Vzcy5leGl0O1xuXG5jb25zdCBkZWZhdWx0RXhpdCA9ICgoY29kZT86IG51bWJlcikgPT4ge1xuICBpZihwcm9jZXNzLmVudi5KRVNUX1dPUktFUl9JRCB8fCBwcm9jZXNzLmVudi5OT0RFX0VOViA9PT0gJ3Rlc3QnKSB7XG4gICAgcmV0dXJuIHVuZGVmaW5lZCBhcyBuZXZlcjtcbiAgfVxuXG4gIHByb2Nlc3MuZXhpdChjb2RlKTtcbn0pIGFzIHR5cGVvZiBwcm9jZXNzLmV4aXQ7XG5cbmV4cG9ydCBjb25zdCBnZXRUZXN0RmlsZVBhdHRlcm5zID0gKHRlc3RQYXRoUGF0dGVybj86IHN0cmluZyk6IHN0cmluZ1tdID0+IHtcbiAgY29uc3QgZGVmYXVsdFBhdHRlcm5zID0gWycqKi8qLnRlc3QuKicsICcqKi8qLnNwZWMuKicsICcqKi8qLmludGVncmF0aW9uLionXTtcblxuICBpZighdGVzdFBhdGhQYXR0ZXJuKSB7XG4gICAgcmV0dXJuIGRlZmF1bHRQYXR0ZXJucztcbiAgfVxuXG4gIHJldHVybiBbdGVzdFBhdGhQYXR0ZXJuXTtcbn07XG5cbmNvbnN0IGZpbmRVbmNvdmVyZWRTb3VyY2VGaWxlcyA9ICgpOiBzdHJpbmdbXSA9PiB7XG4gIGNvbnN0IHNvdXJjZUZpbGVzID0gZ2xvYlN5bmMoJ3NyYy8qKi8qLnt0cyx0c3gsanMsanN4fScsIHtcbiAgICBjd2Q6IHByb2Nlc3MuY3dkKCksXG4gICAgaWdub3JlOiBbJyoqL25vZGVfbW9kdWxlcy8qKicsICcqKi9kaXN0LyoqJywgJyoqL2xpYi8qKicsICcqKi8qLnRlc3QuKicsICcqKi8qLnNwZWMuKiddXG4gIH0pO1xuXG4gIGNvbnN0IHRlc3RGaWxlcyA9IGdsb2JTeW5jKCcqKi8qLnt0ZXN0LHNwZWN9Lnt0cyx0c3gsanMsanN4fScsIHtcbiAgICBjd2Q6IHByb2Nlc3MuY3dkKCksXG4gICAgaWdub3JlOiBbJyoqL25vZGVfbW9kdWxlcy8qKicsICcqKi9kaXN0LyoqJywgJyoqL2xpYi8qKiddXG4gIH0pO1xuXG4gIHJldHVybiBzb3VyY2VGaWxlcy5maWx0ZXIoKHNvdXJjZUZpbGUpID0+IHtcbiAgICBjb25zdCBiYXNlTmFtZSA9IHNvdXJjZUZpbGUucmVwbGFjZSgvXFwuW14vLl0rJC8sICcnKTtcbiAgICByZXR1cm4gIXRlc3RGaWxlcy5zb21lKCh0ZXN0RmlsZSkgPT4gdGVzdEZpbGUuaW5jbHVkZXMoYmFzZU5hbWUpKTtcbiAgfSk7XG59O1xuXG5jb25zdCBwcm9jZXNzVGVzdFJlc3VsdHMgPSAob3V0cHV0RmlsZT86IHN0cmluZyk6IGFueSA9PiB7XG4gIGlmKCFvdXRwdXRGaWxlKSB7XG4gICAgcmV0dXJuIG51bGw7XG4gIH1cblxuICB0cnkge1xuICAgIGNvbnN0IGNvbnRlbnQgPSByZWFkRmlsZVN5bmMob3V0cHV0RmlsZSwgJ3V0Zi04Jyk7XG4gICAgcmV0dXJuIEpTT04ucGFyc2UoY29udGVudCk7XG4gIH0gY2F0Y2goX2Vycm9yKSB7XG4gICAgcmV0dXJuIG51bGw7XG4gIH1cbn07XG5cbmV4cG9ydCBjb25zdCB0ZXN0ID0gYXN5bmMgKFxuICBvcHRpb25zOiBUZXN0T3B0aW9ucyxcbiAgYXJncz86IHN0cmluZ1tdLFxuICBmaWxlc09yQ2FsbGJhY2s/OiBzdHJpbmdbXSB8IFRlc3RDYWxsYmFjayxcbiAgY2FsbGJhY2tQYXJhbT86IFRlc3RDYWxsYmFja1xuKTogUHJvbWlzZTxudW1iZXI+ID0+IHtcbiAgLy8gQmFja3dhcmQtY29tcGF0IGFyZ3VtZW50IG5vcm1hbGl6YXRpb246IGFsbG93IGNhbGxiYWNrIGFzIHRoaXJkIHBhcmFtXG4gIGxldCBmaWxlczogc3RyaW5nW10gfCB1bmRlZmluZWQ7XG4gIGxldCBjYWxsYmFjazogVGVzdENhbGxiYWNrID0gZGVmYXVsdEV4aXQ7XG5cbiAgaWYodHlwZW9mIGZpbGVzT3JDYWxsYmFjayA9PT0gJ2Z1bmN0aW9uJykge1xuICAgIGNhbGxiYWNrID0gZmlsZXNPckNhbGxiYWNrIGFzIFRlc3RDYWxsYmFjaztcbiAgfSBlbHNlIHtcbiAgICBmaWxlcyA9IGZpbGVzT3JDYWxsYmFjayBhcyBzdHJpbmdbXSB8IHVuZGVmaW5lZDtcbiAgICBjYWxsYmFjayA9IGNhbGxiYWNrUGFyYW0gfHwgZGVmYXVsdEV4aXQ7XG4gIH1cbiAgY29uc3Qge1xuICAgIGFuYWx5emUgPSBmYWxzZSxcbiAgICBhaUFuYWx5emUgPSBmYWxzZSxcbiAgICBhaURlYnVnID0gZmFsc2UsXG4gICAgYWlHZW5lcmF0ZSA9IGZhbHNlLFxuICAgIGJhaWwsXG4gICAgY2hhbmdlZEZpbGVzV2l0aEFuY2VzdG9yLFxuICAgIGNoYW5nZWRTaW5jZSxcbiAgICBjaSxcbiAgICBjbGlOYW1lID0gJ0xleCcsXG4gICAgY29sbGVjdENvdmVyYWdlRnJvbSxcbiAgICBjb2xvcnMsXG4gICAgY29uZmlnLFxuICAgIGRlYnVnID0gZmFsc2UsXG4gICAgZGVidWdUZXN0cyA9IGZhbHNlLFxuICAgIGRldGVjdE9wZW5IYW5kbGVzLFxuICAgIGVudixcbiAgICBlcnJvck9uRGVwcmVjYXRlZCxcbiAgICBleHBhbmQsXG4gICAgZm9yY2VFeGl0LFxuICAgIGdlbmVyYXRlID0gZmFsc2UsXG4gICAganNvbixcbiAgICBsYXN0Q29tbWl0LFxuICAgIGxpc3RUZXN0cyxcbiAgICBsb2dIZWFwVXNhZ2UsXG4gICAgbWF4V29ya2VycyxcbiAgICBub1N0YWNrVHJhY2UsXG4gICAgbm90aWZ5LFxuICAgIG9ubHlDaGFuZ2VkLFxuICAgIG91dHB1dEZpbGUsXG4gICAgcGFzc1dpdGhOb1Rlc3RzLFxuICAgIHF1aWV0LFxuICAgIHJlbW92ZUNhY2hlLFxuICAgIHJ1bkluQmFuZCxcbiAgICBzZXR1cCxcbiAgICBzaG93Q29uZmlnLFxuICAgIHNpbGVudCxcbiAgICB0ZXN0TG9jYXRpb25JblJlc3VsdHMsXG4gICAgdGVzdE5hbWVQYXR0ZXJuLFxuICAgIHRlc3RQYXRoUGF0dGVybixcbiAgICB1cGRhdGUsXG4gICAgdXNlU3RkZXJyLFxuICAgIHZlcmJvc2UsXG4gICAgd2F0Y2gsXG4gICAgd2F0Y2hBbGxcbiAgfSA9IG9wdGlvbnM7XG5cbiAgY29uc3QgdXNlR2VuZXJhdGUgPSBnZW5lcmF0ZSB8fCBhaUdlbmVyYXRlO1xuICBjb25zdCB1c2VBbmFseXplID0gYW5hbHl6ZSB8fCBhaUFuYWx5emU7XG4gIGNvbnN0IHVzZURlYnVnID0gZGVidWdUZXN0cyB8fCBhaURlYnVnO1xuXG4gIGxvZyhgJHtjbGlOYW1lfSB0ZXN0aW5nLi4uYCwgJ2luZm8nLCBxdWlldCk7XG5cbiAgY29uc3Qgc3Bpbm5lciA9IGNyZWF0ZVNwaW5uZXIocXVpZXQpO1xuXG4gIGF3YWl0IExleENvbmZpZy5wYXJzZUNvbmZpZyhvcHRpb25zKTtcblxuICBjb25zdCB7dXNlVHlwZXNjcmlwdH0gPSBMZXhDb25maWcuY29uZmlnO1xuXG4gIGlmKHVzZVR5cGVzY3JpcHQpIHtcbiAgICBjb25zdCB0ZXN0Q29uZmlnUGF0aCA9IGdldFR5cGVTY3JpcHRDb25maWdQYXRoKCd0c2NvbmZpZy50ZXN0Lmpzb24nKTtcbiAgICBpZihleGlzdHNTeW5jKHRlc3RDb25maWdQYXRoKSkge1xuICAgICAgbG9nKCdVc2luZyB0c2NvbmZpZy50ZXN0Lmpzb24gZm9yIHRlc3RpbmcuLi4nLCAnaW5mbycsIHF1aWV0KTtcbiAgICB9IGVsc2Uge1xuICAgICAgTGV4Q29uZmlnLmNoZWNrVGVzdFR5cGVzY3JpcHRDb25maWcoKTtcbiAgICB9XG4gIH1cblxuICBpZih1c2VHZW5lcmF0ZSkge1xuICAgIHNwaW5uZXIuc3RhcnQoJ0FJIGlzIGFuYWx5emluZyBjb2RlIHRvIGdlbmVyYXRlIHRlc3QgY2FzZXMuLi4nKTtcblxuICAgIHRyeSB7XG4gICAgICBjb25zdCB1bmNvdmVyZWRGaWxlcyA9IGZpbmRVbmNvdmVyZWRTb3VyY2VGaWxlcygpO1xuXG4gICAgICBpZih1bmNvdmVyZWRGaWxlcy5sZW5ndGggPiAwKSB7XG4gICAgICAgIGNvbnN0IHRhcmdldEZpbGUgPSB1bmNvdmVyZWRGaWxlc1swXTtcblxuICAgICAgICBhd2FpdCBhaUZ1bmN0aW9uKHtcbiAgICAgICAgICBjb250ZXh0OiB0cnVlLFxuICAgICAgICAgIGZpbGU6IHRhcmdldEZpbGUsXG4gICAgICAgICAgcHJvbXB0OiBgR2VuZXJhdGUgSmVzdCB1bml0IHRlc3RzIGZvciB0aGlzIGZpbGU6ICR7dGFyZ2V0RmlsZX1cXG5cXG4ke3JlYWRGaWxlU3luYyh0YXJnZXRGaWxlLCAndXRmLTgnKX1cXG5cXG5QbGVhc2UgY3JlYXRlIGNvbXByZWhlbnNpdmUgdGVzdHMgdGhhdCBjb3ZlciB0aGUgbWFpbiBmdW5jdGlvbmFsaXR5LiBJbmNsdWRlIHRlc3QgZml4dHVyZXMgYW5kIG1vY2tzIHdoZXJlIG5lY2Vzc2FyeS5gLFxuICAgICAgICAgIHF1aWV0LFxuICAgICAgICAgIHRhc2s6ICd0ZXN0J1xuICAgICAgICB9KTtcblxuICAgICAgICBzcGlubmVyLnN1Y2NlZWQoYEFJIHRlc3QgZ2VuZXJhdGlvbiBzdWdnZXN0aW9ucyBwcm92aWRlZCBmb3IgJHt0YXJnZXRGaWxlfWApO1xuICAgICAgfSBlbHNlIHtcbiAgICAgICAgc3Bpbm5lci5zdWNjZWVkKCdBbGwgc291cmNlIGZpbGVzIGFwcGVhciB0byBoYXZlIGNvcnJlc3BvbmRpbmcgdGVzdCBmaWxlcycpO1xuICAgICAgfVxuICAgIH0gY2F0Y2goYWlFcnJvcikge1xuICAgICAgc3Bpbm5lci5mYWlsKCdDb3VsZCBub3QgZ2VuZXJhdGUgQUkgdGVzdCBzdWdnZXN0aW9ucycpO1xuICAgICAgaWYoIXF1aWV0KSB7XG4gICAgICAgIC8vIGVzbGludC1kaXNhYmxlLW5leHQtbGluZSBuby1jb25zb2xlXG4gICAgICAgIGNvbnNvbGUuZXJyb3IoJ0FJIHRlc3QgZ2VuZXJhdGlvbiBlcnJvcjonLCBhaUVycm9yKTtcbiAgICAgIH1cbiAgICB9XG4gIH1cblxuICBjb25zdCBwcm9qZWN0SmVzdEJpbiA9IHBhdGhSZXNvbHZlKHByb2Nlc3MuY3dkKCksICdub2RlX21vZHVsZXMvLmJpbi9qZXN0Jyk7XG4gIGxldCBqZXN0UGF0aDogc3RyaW5nO1xuXG4gIGlmKGV4aXN0c1N5bmMocHJvamVjdEplc3RCaW4pKSB7XG4gICAgamVzdFBhdGggPSBwcm9qZWN0SmVzdEJpbjtcbiAgfSBlbHNlIHtcbiAgICBqZXN0UGF0aCA9IHJlc29sdmVCaW5hcnlQYXRoKCdqZXN0Jyk7XG4gIH1cblxuICBpZighamVzdFBhdGgpIHtcbiAgICBsb2coYFxcbiR7Y2xpTmFtZX0gRXJyb3I6IEplc3QgYmluYXJ5IG5vdCBmb3VuZCBpbiBMZXgncyBub2RlX21vZHVsZXMgb3IgbW9ub3JlcG8gcm9vdGAsICdlcnJvcicsIHF1aWV0KTtcbiAgICBsb2coJ1BsZWFzZSByZWluc3RhbGwgTGV4IG9yIGNoZWNrIHlvdXIgaW5zdGFsbGF0aW9uLicsICdpbmZvJywgcXVpZXQpO1xuICAgIHJldHVybiAxO1xuICB9XG5cbiAgbGV0IGplc3RDb25maWdGaWxlOiBzdHJpbmc7XG4gIGxldCBwcm9qZWN0SmVzdENvbmZpZzogYW55ID0gbnVsbDtcblxuICBpZihjb25maWcpIHtcbiAgICBqZXN0Q29uZmlnRmlsZSA9IGNvbmZpZztcbiAgfSBlbHNlIHtcbiAgICBjb25zdCBwcm9qZWN0SmVzdENvbmZpZ1BhdGggPSBwYXRoUmVzb2x2ZShwcm9jZXNzLmN3ZCgpLCAnamVzdC5jb25maWcuanMnKTtcbiAgICBjb25zdCBwcm9qZWN0SmVzdENvbmZpZ0Nqc1BhdGggPSBwYXRoUmVzb2x2ZShwcm9jZXNzLmN3ZCgpLCAnamVzdC5jb25maWcuY2pzJyk7XG4gICAgY29uc3QgcHJvamVjdEplc3RDb25maWdNanNQYXRoID0gcGF0aFJlc29sdmUocHJvY2Vzcy5jd2QoKSwgJ2plc3QuY29uZmlnLm1qcycpO1xuICAgIGNvbnN0IHByb2plY3RKZXN0Q29uZmlnSnNvblBhdGggPSBwYXRoUmVzb2x2ZShwcm9jZXNzLmN3ZCgpLCAnamVzdC5jb25maWcuanNvbicpO1xuXG4gICAgaWYoZXhpc3RzU3luYyhwcm9qZWN0SmVzdENvbmZpZ1BhdGgpKSB7XG4gICAgICBqZXN0Q29uZmlnRmlsZSA9IHByb2plY3RKZXN0Q29uZmlnUGF0aDtcbiAgICAgIGlmKGRlYnVnKSB7XG4gICAgICAgIGxvZyhgVXNpbmcgcHJvamVjdCBKZXN0IGNvbmZpZyBmaWxlOiAke2plc3RDb25maWdGaWxlfWAsICdpbmZvJywgcXVpZXQpO1xuICAgICAgfVxuICAgIH0gZWxzZSBpZihleGlzdHNTeW5jKHByb2plY3RKZXN0Q29uZmlnQ2pzUGF0aCkpIHtcbiAgICAgIGplc3RDb25maWdGaWxlID0gcHJvamVjdEplc3RDb25maWdDanNQYXRoO1xuICAgICAgaWYoZGVidWcpIHtcbiAgICAgICAgbG9nKGBVc2luZyBwcm9qZWN0IEplc3QgY29uZmlnIGZpbGUgKENKUyk6ICR7amVzdENvbmZpZ0ZpbGV9YCwgJ2luZm8nLCBxdWlldCk7XG4gICAgICB9XG4gICAgfSBlbHNlIGlmKGV4aXN0c1N5bmMocHJvamVjdEplc3RDb25maWdNanNQYXRoKSkge1xuICAgICAgamVzdENvbmZpZ0ZpbGUgPSBwcm9qZWN0SmVzdENvbmZpZ01qc1BhdGg7XG4gICAgICBpZihkZWJ1Zykge1xuICAgICAgICBsb2coYFVzaW5nIHByb2plY3QgSmVzdCBjb25maWcgZmlsZSAoTUpTKTogJHtqZXN0Q29uZmlnRmlsZX1gLCAnaW5mbycsIHF1aWV0KTtcbiAgICAgIH1cbiAgICB9IGVsc2UgaWYoZXhpc3RzU3luYyhwcm9qZWN0SmVzdENvbmZpZ0pzb25QYXRoKSkge1xuICAgICAgamVzdENvbmZpZ0ZpbGUgPSBwcm9qZWN0SmVzdENvbmZpZ0pzb25QYXRoO1xuICAgICAgaWYoZGVidWcpIHtcbiAgICAgICAgbG9nKGBVc2luZyBwcm9qZWN0IEplc3QgY29uZmlnIGZpbGUgKEpTT04pOiAke2plc3RDb25maWdGaWxlfWAsICdpbmZvJywgcXVpZXQpO1xuICAgICAgfVxuICAgIH0gZWxzZSB7XG4gICAgICAvLyBObyBKZXN0IGNvbmZpZyBmaWxlIGV4aXN0cyBpbiB0aGUgcHJvamVjdFxuICAgICAgLy8gQ2hlY2sgaWYgdGhlcmUncyBhIEplc3QgY29uZmlnIGluIGxleC5jb25maWcuY2pzXG4gICAgICBwcm9qZWN0SmVzdENvbmZpZyA9IExleENvbmZpZy5jb25maWcuamVzdDtcblxuICAgICAgY29uc3QgbGV4RGlyID0gTGV4Q29uZmlnLmdldExleERpcigpO1xuICAgICAgY29uc3QgbGV4SmVzdENvbmZpZyA9IHBhdGhSZXNvbHZlKGxleERpciwgJ2plc3QuY29uZmlnLm1qcycpO1xuXG4gICAgICBpZihkZWJ1Zykge1xuICAgICAgICBsb2coYExvb2tpbmcgZm9yIEplc3QgY29uZmlnIGF0OiAke2xleEplc3RDb25maWd9YCwgJ2luZm8nLCBxdWlldCk7XG4gICAgICAgIGxvZyhgRmlsZSBleGlzdHM6ICR7ZXhpc3RzU3luYyhsZXhKZXN0Q29uZmlnKX1gLCAnaW5mbycsIHF1aWV0KTtcbiAgICAgIH1cblxuICAgICAgaWYoZXhpc3RzU3luYyhsZXhKZXN0Q29uZmlnKSkge1xuICAgICAgICBqZXN0Q29uZmlnRmlsZSA9IGxleEplc3RDb25maWc7XG4gICAgICAgIGlmKHByb2plY3RKZXN0Q29uZmlnICYmIE9iamVjdC5rZXlzKHByb2plY3RKZXN0Q29uZmlnKS5sZW5ndGggPiAwKSB7XG4gICAgICAgICAgaWYoZGVidWcpIHtcbiAgICAgICAgICAgIGxvZyhgVXNpbmcgTGV4IEplc3QgY29uZmlnIHdpdGggcHJvamVjdCBKZXN0IGNvbmZpZyBmcm9tIGxleC5jb25maWcuY2pzOiAke2plc3RDb25maWdGaWxlfWAsICdpbmZvJywgcXVpZXQpO1xuICAgICAgICAgIH1cbiAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICBpZihkZWJ1Zykge1xuICAgICAgICAgICAgbG9nKGBVc2luZyBMZXggSmVzdCBjb25maWcgKG5vIHByb2plY3QgSmVzdCBjb25maWcgZm91bmQpOiAke2plc3RDb25maWdGaWxlfWAsICdpbmZvJywgcXVpZXQpO1xuICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgICAgfSBlbHNlIHtcbiAgICAgICAgaWYoZGVidWcpIHtcbiAgICAgICAgICBsb2coJ05vIEplc3QgY29uZmlnIGZvdW5kIGluIHByb2plY3Qgb3IgTGV4JywgJ3dhcm4nLCBxdWlldCk7XG4gICAgICAgIH1cbiAgICAgICAgamVzdENvbmZpZ0ZpbGUgPSAnJztcbiAgICAgIH1cbiAgICB9XG4gIH1cblxuICBjb25zdCBqZXN0U2V0dXBGaWxlOiBzdHJpbmcgPSBzZXR1cCB8fCBwYXRoUmVzb2x2ZShwcm9jZXNzLmN3ZCgpLCAnamVzdC5zZXR1cC5qcycpO1xuICBjb25zdCBqZXN0T3B0aW9uczogc3RyaW5nW10gPSBbJy0tbm8tY2FjaGUnXTtcblxuICBjb25zdCBpc0VTTSA9IGRldGVjdEVTTShwcm9jZXNzLmN3ZCgpKTtcbiAgbGV0IG5vZGVPcHRpb25zID0gcHJvY2Vzcy5lbnYuTk9ERV9PUFRJT05TIHx8ICcnO1xuICBpZihpc0VTTSkge1xuICAgIGlmKCFub2RlT3B0aW9ucy5pbmNsdWRlcygnLS1leHBlcmltZW50YWwtdm0tbW9kdWxlcycpKSB7XG4gICAgICBub2RlT3B0aW9ucyA9IGAke25vZGVPcHRpb25zfSAtLWV4cGVyaW1lbnRhbC12bS1tb2R1bGVzYC50cmltKCk7XG4gICAgfVxuICAgIGxvZygnRVNNIHByb2plY3QgZGV0ZWN0ZWQsIHVzaW5nIC0tZXhwZXJpbWVudGFsLXZtLW1vZHVsZXMgaW4gTk9ERV9PUFRJT05TJywgJ2luZm8nLCBxdWlldCk7XG4gIH1cblxuICBpZihqZXN0Q29uZmlnRmlsZSkge1xuICAgIGplc3RPcHRpb25zLnB1c2goJy0tY29uZmlnJywgamVzdENvbmZpZ0ZpbGUpO1xuICB9XG5cbiAgaWYoYmFpbCkge1xuICAgIGplc3RPcHRpb25zLnB1c2goJy0tYmFpbCcpO1xuICB9XG5cbiAgaWYoY2hhbmdlZEZpbGVzV2l0aEFuY2VzdG9yKSB7XG4gICAgamVzdE9wdGlvbnMucHVzaCgnLS1jaGFuZ2VkRmlsZXNXaXRoQW5jZXN0b3InKTtcbiAgfVxuXG4gIGlmKGNoYW5nZWRTaW5jZSkge1xuICAgIGplc3RPcHRpb25zLnB1c2goJy0tY2hhbmdlZFNpbmNlJyk7XG4gIH1cblxuICBpZihjaSkge1xuICAgIGplc3RPcHRpb25zLnB1c2goJy0tY2knKTtcbiAgfVxuXG4gIGlmKGNvbGxlY3RDb3ZlcmFnZUZyb20pIHtcbiAgICBqZXN0T3B0aW9ucy5wdXNoKCctLWNvbGxlY3RDb3ZlcmFnZUZyb20nLCBjb2xsZWN0Q292ZXJhZ2VGcm9tKTtcbiAgfVxuXG4gIGlmKGNvbG9ycykge1xuICAgIGplc3RPcHRpb25zLnB1c2goJy0tY29sb3JzJyk7XG4gIH1cblxuICBpZihkZWJ1Zykge1xuICAgIGplc3RPcHRpb25zLnB1c2goJy0tZGVidWcnKTtcbiAgfVxuXG4gIGlmKGRldGVjdE9wZW5IYW5kbGVzKSB7XG4gICAgamVzdE9wdGlvbnMucHVzaCgnLS1kZXRlY3RPcGVuSGFuZGxlcycpO1xuICB9XG5cbiAgaWYoZW52KSB7XG4gICAgamVzdE9wdGlvbnMucHVzaCgnLS1lbnYnKTtcbiAgfVxuXG4gIGlmKGVycm9yT25EZXByZWNhdGVkKSB7XG4gICAgamVzdE9wdGlvbnMucHVzaCgnLS1lcnJvck9uRGVwcmVjYXRlZCcpO1xuICB9XG5cbiAgaWYoZXhwYW5kKSB7XG4gICAgamVzdE9wdGlvbnMucHVzaCgnLS1leHBhbmQnKTtcbiAgfVxuXG4gIGlmKGZvcmNlRXhpdCkge1xuICAgIGplc3RPcHRpb25zLnB1c2goJy0tZm9yY2VFeGl0Jyk7XG4gIH1cblxuICBpZihqc29uKSB7XG4gICAgamVzdE9wdGlvbnMucHVzaCgnLS1qc29uJyk7XG4gIH1cblxuICBpZihsYXN0Q29tbWl0KSB7XG4gICAgamVzdE9wdGlvbnMucHVzaCgnLS1sYXN0Q29tbWl0Jyk7XG4gIH1cblxuICBpZihsaXN0VGVzdHMpIHtcbiAgICBqZXN0T3B0aW9ucy5wdXNoKCctLWxpc3RUZXN0cycpO1xuICB9XG5cbiAgaWYobG9nSGVhcFVzYWdlKSB7XG4gICAgamVzdE9wdGlvbnMucHVzaCgnLS1sb2dIZWFwVXNhZ2UnKTtcbiAgfVxuXG4gIGlmKG1heFdvcmtlcnMpIHtcbiAgICBqZXN0T3B0aW9ucy5wdXNoKCctLW1heFdvcmtlcnMnLCBtYXhXb3JrZXJzKTtcbiAgfVxuXG4gIGlmKG5vU3RhY2tUcmFjZSkge1xuICAgIGplc3RPcHRpb25zLnB1c2goJy0tbm9TdGFja1RyYWNlJyk7XG4gIH1cblxuICBpZihub3RpZnkpIHtcbiAgICBqZXN0T3B0aW9ucy5wdXNoKCctLW5vdGlmeScpO1xuICB9XG5cbiAgaWYob25seUNoYW5nZWQpIHtcbiAgICBqZXN0T3B0aW9ucy5wdXNoKCctLW9ubHlDaGFuZ2VkJyk7XG4gIH1cblxuICBsZXQgdGVtcE91dHB1dEZpbGUgPSBvdXRwdXRGaWxlO1xuXG4gIGlmKCh1c2VBbmFseXplIHx8IHVzZURlYnVnKSAmJiAhb3V0cHV0RmlsZSkge1xuICAgIHRlbXBPdXRwdXRGaWxlID0gJy5sZXgtdGVzdC1yZXN1bHRzLmpzb24nO1xuICAgIGplc3RPcHRpb25zLnB1c2goJy0tanNvbicsICctLW91dHB1dEZpbGUnLCB0ZW1wT3V0cHV0RmlsZSk7XG4gIH0gZWxzZSBpZihvdXRwdXRGaWxlKSB7XG4gICAgamVzdE9wdGlvbnMucHVzaCgnLS1vdXRwdXRGaWxlJywgb3V0cHV0RmlsZSk7XG4gIH1cblxuICBpZihwYXNzV2l0aE5vVGVzdHMpIHtcbiAgICBqZXN0T3B0aW9ucy5wdXNoKCctLXBhc3NXaXRoTm9UZXN0cycpO1xuICB9XG5cbiAgaWYocnVuSW5CYW5kKSB7XG4gICAgamVzdE9wdGlvbnMucHVzaCgnLS1ydW5JbkJhbmQnKTtcbiAgfVxuXG4gIGlmKHNob3dDb25maWcpIHtcbiAgICBqZXN0T3B0aW9ucy5wdXNoKCctLXNob3dDb25maWcnKTtcbiAgfVxuXG4gIGlmKHNpbGVudCkge1xuICAgIGplc3RPcHRpb25zLnB1c2goJy0tc2lsZW50Jyk7XG4gIH1cblxuICBpZih0ZXN0TG9jYXRpb25JblJlc3VsdHMpIHtcbiAgICBqZXN0T3B0aW9ucy5wdXNoKCctLXRlc3RMb2NhdGlvbkluUmVzdWx0cycpO1xuICB9XG5cbiAgaWYodGVzdE5hbWVQYXR0ZXJuKSB7XG4gICAgamVzdE9wdGlvbnMucHVzaCgnLS10ZXN0TmFtZVBhdHRlcm4nLCB0ZXN0TmFtZVBhdHRlcm4pO1xuICB9XG5cbiAgaWYodGVzdFBhdGhQYXR0ZXJuKSB7XG4gICAgamVzdE9wdGlvbnMucHVzaCgnLS10ZXN0UGF0aFBhdHRlcm4nLCB0ZXN0UGF0aFBhdHRlcm4pO1xuICB9XG5cbiAgaWYodXNlU3RkZXJyKSB7XG4gICAgamVzdE9wdGlvbnMucHVzaCgnLS11c2VTdGRlcnInKTtcbiAgfVxuXG4gIGlmKHZlcmJvc2UpIHtcbiAgICBqZXN0T3B0aW9ucy5wdXNoKCctLXZlcmJvc2UnKTtcbiAgfVxuXG4gIGlmKHdhdGNoQWxsKSB7XG4gICAgamVzdE9wdGlvbnMucHVzaCgnLS13YXRjaEFsbCcpO1xuICB9XG5cbiAgaWYocmVtb3ZlQ2FjaGUpIHtcbiAgICBqZXN0T3B0aW9ucy5wdXNoKCctLW5vLWNhY2hlJyk7XG4gIH1cblxuICBpZihqZXN0U2V0dXBGaWxlICYmIGV4aXN0c1N5bmMoamVzdFNldHVwRmlsZSkpIHtcbiAgICBqZXN0T3B0aW9ucy5wdXNoKGAtLXNldHVwRmlsZXNBZnRlckVudj0ke2plc3RTZXR1cEZpbGV9YCk7XG4gIH1cblxuICBpZih1cGRhdGUpIHtcbiAgICBqZXN0T3B0aW9ucy5wdXNoKCctLXVwZGF0ZVNuYXBzaG90Jyk7XG4gIH1cblxuICBpZih3YXRjaCkge1xuICAgIGplc3RPcHRpb25zLnB1c2goJy0td2F0Y2gnLCB3YXRjaCk7XG4gIH1cblxuICBpZihhcmdzKSB7XG4gICAgamVzdE9wdGlvbnMucHVzaCguLi5hcmdzKTtcbiAgfVxuXG4gIGlmKGZpbGVzICYmIGZpbGVzLmxlbmd0aCA+IDApIHtcbiAgICBqZXN0T3B0aW9ucy5wdXNoKC4uLmZpbGVzKTtcbiAgfVxuXG4gIGlmKGRlYnVnKSB7XG4gICAgbG9nKGBKZXN0IG9wdGlvbnM6ICR7amVzdE9wdGlvbnMuam9pbignICcpfWAsICdpbmZvJywgcXVpZXQpO1xuICAgIGxvZyhgTk9ERV9PUFRJT05TOiAke25vZGVPcHRpb25zfWAsICdpbmZvJywgcXVpZXQpO1xuICB9XG5cbiAgdHJ5IHtcbiAgICBjb25zdCBlbnY6IFJlY29yZDxzdHJpbmcsIHN0cmluZz4gPSB7XG4gICAgICAuLi5wcm9jZXNzLmVudixcbiAgICAgIE5PREVfT1BUSU9OUzogbm9kZU9wdGlvbnNcbiAgICB9O1xuXG4gICAgYXdhaXQgZXhlY2EoamVzdFBhdGgsIGplc3RPcHRpb25zLCB7XG4gICAgICBlbmNvZGluZzogJ3V0ZjgnLFxuICAgICAgZW52LFxuICAgICAgc3RkaW86ICdpbmhlcml0J1xuICAgIH0pO1xuXG4gICAgc3Bpbm5lci5zdWNjZWVkKCdUZXN0aW5nIGNvbXBsZXRlZCEnKTtcblxuICAgIGlmKHVzZUFuYWx5emUpIHtcbiAgICAgIHNwaW5uZXIuc3RhcnQoJ0FJIGlzIGFuYWx5emluZyB0ZXN0IGNvdmVyYWdlIGFuZCBzdWdnZXN0aW5nIGltcHJvdmVtZW50cy4uLicpO1xuXG4gICAgICB0cnkge1xuICAgICAgICBjb25zdCB0ZXN0UmVzdWx0cyA9IHByb2Nlc3NUZXN0UmVzdWx0cyh0ZW1wT3V0cHV0RmlsZSk7XG4gICAgICAgIGNvbnN0IGZpbGVQYXR0ZXJucyA9IGdldFRlc3RGaWxlUGF0dGVybnModGVzdFBhdGhQYXR0ZXJuKTtcblxuICAgICAgICBhd2FpdCBhaUZ1bmN0aW9uKHtcbiAgICAgICAgICBjb250ZXh0OiB0cnVlLFxuICAgICAgICAgIHByb21wdDogYEFuYWx5emUgdGhlc2UgSmVzdCB0ZXN0IHJlc3VsdHMgYW5kIHN1Z2dlc3QgdGVzdCBjb3ZlcmFnZSBpbXByb3ZlbWVudHM6XG5cbiR7SlNPTi5zdHJpbmdpZnkodGVzdFJlc3VsdHMsIG51bGwsIDIpfVxuXG5UZXN0IHBhdHRlcm5zOiAke2ZpbGVQYXR0ZXJucy5qb2luKCcsICcpfVxuXG5QbGVhc2UgcHJvdmlkZTpcbjEuIEFuYWx5c2lzIG9mIGN1cnJlbnQgY292ZXJhZ2UgZ2Fwc1xuMi4gU3VnZ2VzdGlvbnMgZm9yIGltcHJvdmluZyB0ZXN0IGNhc2VzXG4zLiBSZWNvbW1lbmRhdGlvbnMgZm9yIGFkZGl0aW9uYWwgaW50ZWdyYXRpb24gdGVzdCBzY2VuYXJpb3NcbjQuIEJlc3QgcHJhY3RpY2VzIGZvciBpbmNyZWFzaW5nIHRlc3QgZWZmZWN0aXZlbmVzc2AsXG4gICAgICAgICAgcXVpZXQsXG4gICAgICAgICAgdGFzazogJ29wdGltaXplJ1xuICAgICAgICB9KTtcblxuICAgICAgICBzcGlubmVyLnN1Y2NlZWQoJ0FJIHRlc3QgYW5hbHlzaXMgY29tcGxldGUnKTtcbiAgICAgIH0gY2F0Y2goYWlFcnJvcikge1xuICAgICAgICBzcGlubmVyLmZhaWwoJ0NvdWxkIG5vdCBnZW5lcmF0ZSBBSSB0ZXN0IGFuYWx5c2lzJyk7XG4gICAgICAgIGlmKCFxdWlldCkge1xuICAgICAgICAgIC8vIGVzbGludC1kaXNhYmxlLW5leHQtbGluZSBuby1jb25zb2xlXG4gICAgICAgICAgY29uc29sZS5lcnJvcignQUkgYW5hbHlzaXMgZXJyb3I6JywgYWlFcnJvcik7XG4gICAgICAgIH1cbiAgICAgIH1cbiAgICB9XG5cbiAgICBjYWxsYmFjaygwKTtcbiAgICByZXR1cm4gMDtcbiAgfSBjYXRjaChlcnJvcikge1xuICAgIGxvZyhgXFxuJHtjbGlOYW1lfSBFcnJvcjogQ2hlY2sgZm9yIHVuaXQgdGVzdCBlcnJvcnMgYW5kL29yIGNvdmVyYWdlLmAsICdlcnJvcicsIHF1aWV0KTtcblxuICAgIHNwaW5uZXIuZmFpbCgnVGVzdGluZyBmYWlsZWQhJyk7XG5cbiAgICBpZih1c2VEZWJ1Zykge1xuICAgICAgc3Bpbm5lci5zdGFydCgnQUkgaXMgYW5hbHl6aW5nIHRlc3QgZmFpbHVyZXMuLi4nKTtcblxuICAgICAgdHJ5IHtcbiAgICAgICAgY29uc3QgdGVzdFJlc3VsdHMgPSBwcm9jZXNzVGVzdFJlc3VsdHModGVtcE91dHB1dEZpbGUpO1xuXG4gICAgICAgIGF3YWl0IGFpRnVuY3Rpb24oe1xuICAgICAgICAgIGNvbnRleHQ6IHRydWUsXG4gICAgICAgICAgcHJvbXB0OiBgRGVidWcgdGhlc2UgZmFpbGVkIEplc3QgdGVzdHMgYW5kIHN1Z2dlc3QgZml4ZXM6XG5cbiR7SlNPTi5zdHJpbmdpZnkoZXJyb3IubWVzc2FnZSwgbnVsbCwgMil9XG5cblRlc3QgcmVzdWx0czogJHtKU09OLnN0cmluZ2lmeSh0ZXN0UmVzdWx0cywgbnVsbCwgMil9XG5cblBsZWFzZSBwcm92aWRlOlxuMS4gQW5hbHlzaXMgb2Ygd2h5IHRoZSB0ZXN0cyBhcmUgZmFpbGluZ1xuMi4gU3BlY2lmaWMgc3VnZ2VzdGlvbnMgdG8gZml4IGVhY2ggZmFpbGluZyB0ZXN0XG4zLiBBbnkgcG90ZW50aWFsIGlzc3VlcyB3aXRoIHRlc3QgZml4dHVyZXMgb3IgbW9ja3NcbjQuIENvZGUgZXhhbXBsZXMgZm9yIHNvbHV0aW9uc2AsXG4gICAgICAgICAgcXVpZXQsXG4gICAgICAgICAgdGFzazogJ2hlbHAnXG4gICAgICAgIH0pO1xuXG4gICAgICAgIHNwaW5uZXIuc3VjY2VlZCgnQUkgZGVidWdnaW5nIGFzc2lzdGFuY2UgY29tcGxldGUnKTtcbiAgICAgIH0gY2F0Y2goYWlFcnJvcikge1xuICAgICAgICBzcGlubmVyLmZhaWwoJ0NvdWxkIG5vdCBnZW5lcmF0ZSBBSSBkZWJ1Z2dpbmcgYXNzaXN0YW5jZScpO1xuICAgICAgICBpZighcXVpZXQpIHtcbiAgICAgICAgICAvLyBlc2xpbnQtZGlzYWJsZS1uZXh0LWxpbmUgbm8tY29uc29sZVxuICAgICAgICAgIGNvbnNvbGUuZXJyb3IoJ0FJIGRlYnVnZ2luZyBlcnJvcjonLCBhaUVycm9yKTtcbiAgICAgICAgfVxuICAgICAgfVxuICAgIH1cblxuICAgIGNhbGxiYWNrKDEpO1xuICAgIHJldHVybiAxO1xuICB9XG59O1xuXG5leHBvcnQgZGVmYXVsdCB0ZXN0OyJdLCJuYW1lcyI6WyJleGVjYSIsImV4aXN0c1N5bmMiLCJyZWFkRmlsZVN5bmMiLCJzeW5jIiwiZ2xvYlN5bmMiLCJyZXNvbHZlIiwicGF0aFJlc29sdmUiLCJMZXhDb25maWciLCJnZXRUeXBlU2NyaXB0Q29uZmlnUGF0aCIsImNyZWF0ZVNwaW5uZXIiLCJyZXNvbHZlQmluYXJ5UGF0aCIsImxvZyIsImFpRnVuY3Rpb24iLCJkZXRlY3RFU00iLCJjd2QiLCJwYWNrYWdlSnNvblBhdGgiLCJwYWNrYWdlSnNvbkNvbnRlbnQiLCJwYWNrYWdlSnNvbiIsIkpTT04iLCJwYXJzZSIsInR5cGUiLCJfZXJyb3IiLCJkZWZhdWx0RXhpdCIsImNvZGUiLCJwcm9jZXNzIiwiZW52IiwiSkVTVF9XT1JLRVJfSUQiLCJOT0RFX0VOViIsInVuZGVmaW5lZCIsImV4aXQiLCJnZXRUZXN0RmlsZVBhdHRlcm5zIiwidGVzdFBhdGhQYXR0ZXJuIiwiZGVmYXVsdFBhdHRlcm5zIiwiZmluZFVuY292ZXJlZFNvdXJjZUZpbGVzIiwic291cmNlRmlsZXMiLCJpZ25vcmUiLCJ0ZXN0RmlsZXMiLCJmaWx0ZXIiLCJzb3VyY2VGaWxlIiwiYmFzZU5hbWUiLCJyZXBsYWNlIiwic29tZSIsInRlc3RGaWxlIiwiaW5jbHVkZXMiLCJwcm9jZXNzVGVzdFJlc3VsdHMiLCJvdXRwdXRGaWxlIiwiY29udGVudCIsInRlc3QiLCJvcHRpb25zIiwiYXJncyIsImZpbGVzT3JDYWxsYmFjayIsImNhbGxiYWNrUGFyYW0iLCJmaWxlcyIsImNhbGxiYWNrIiwiYW5hbHl6ZSIsImFpQW5hbHl6ZSIsImFpRGVidWciLCJhaUdlbmVyYXRlIiwiYmFpbCIsImNoYW5nZWRGaWxlc1dpdGhBbmNlc3RvciIsImNoYW5nZWRTaW5jZSIsImNpIiwiY2xpTmFtZSIsImNvbGxlY3RDb3ZlcmFnZUZyb20iLCJjb2xvcnMiLCJjb25maWciLCJkZWJ1ZyIsImRlYnVnVGVzdHMiLCJkZXRlY3RPcGVuSGFuZGxlcyIsImVycm9yT25EZXByZWNhdGVkIiwiZXhwYW5kIiwiZm9yY2VFeGl0IiwiZ2VuZXJhdGUiLCJqc29uIiwibGFzdENvbW1pdCIsImxpc3RUZXN0cyIsImxvZ0hlYXBVc2FnZSIsIm1heFdvcmtlcnMiLCJub1N0YWNrVHJhY2UiLCJub3RpZnkiLCJvbmx5Q2hhbmdlZCIsInBhc3NXaXRoTm9UZXN0cyIsInF1aWV0IiwicmVtb3ZlQ2FjaGUiLCJydW5JbkJhbmQiLCJzZXR1cCIsInNob3dDb25maWciLCJzaWxlbnQiLCJ0ZXN0TG9jYXRpb25JblJlc3VsdHMiLCJ0ZXN0TmFtZVBhdHRlcm4iLCJ1cGRhdGUiLCJ1c2VTdGRlcnIiLCJ2ZXJib3NlIiwid2F0Y2giLCJ3YXRjaEFsbCIsInVzZUdlbmVyYXRlIiwidXNlQW5hbHl6ZSIsInVzZURlYnVnIiwic3Bpbm5lciIsInBhcnNlQ29uZmlnIiwidXNlVHlwZXNjcmlwdCIsInRlc3RDb25maWdQYXRoIiwiY2hlY2tUZXN0VHlwZXNjcmlwdENvbmZpZyIsInN0YXJ0IiwidW5jb3ZlcmVkRmlsZXMiLCJsZW5ndGgiLCJ0YXJnZXRGaWxlIiwiY29udGV4dCIsImZpbGUiLCJwcm9tcHQiLCJ0YXNrIiwic3VjY2VlZCIsImFpRXJyb3IiLCJmYWlsIiwiY29uc29sZSIsImVycm9yIiwicHJvamVjdEplc3RCaW4iLCJqZXN0UGF0aCIsImplc3RDb25maWdGaWxlIiwicHJvamVjdEplc3RDb25maWciLCJwcm9qZWN0SmVzdENvbmZpZ1BhdGgiLCJwcm9qZWN0SmVzdENvbmZpZ0Nqc1BhdGgiLCJwcm9qZWN0SmVzdENvbmZpZ01qc1BhdGgiLCJwcm9qZWN0SmVzdENvbmZpZ0pzb25QYXRoIiwiamVzdCIsImxleERpciIsImdldExleERpciIsImxleEplc3RDb25maWciLCJPYmplY3QiLCJrZXlzIiwiamVzdFNldHVwRmlsZSIsImplc3RPcHRpb25zIiwiaXNFU00iLCJub2RlT3B0aW9ucyIsIk5PREVfT1BUSU9OUyIsInRyaW0iLCJwdXNoIiwidGVtcE91dHB1dEZpbGUiLCJqb2luIiwiZW5jb2RpbmciLCJzdGRpbyIsInRlc3RSZXN1bHRzIiwiZmlsZVBhdHRlcm5zIiwic3RyaW5naWZ5IiwibWVzc2FnZSJdLCJtYXBwaW5ncyI6IkFBQUE7OztDQUdDLEdBQ0QsU0FBUUEsS0FBSyxRQUFPLFFBQVE7QUFDNUIsU0FBUUMsVUFBVSxFQUFFQyxZQUFZLFFBQU8sS0FBSztBQUM1QyxTQUFRQyxRQUFRQyxRQUFRLFFBQU8sT0FBTztBQUN0QyxTQUFRQyxXQUFXQyxXQUFX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