diff --git a/.gitignore b/.gitignore index 44e49cc..5f5c29e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,6 @@ node_modules -allure-results package-lock.json -ex.js -spa-report .idea -playground.* \ No newline at end of file +playground.* +built +coverage \ No newline at end of file diff --git a/.npmignore b/.npmignore index 5d0bd8e..f5da6e0 100644 --- a/.npmignore +++ b/.npmignore @@ -1,13 +1,7 @@ -a.js -ex.js -tsconfig.json -po +lib specs -util -config -node_modules -allure-results -protractor.conf.js -package-lock.json -unit_specs -lol.js \ No newline at end of file +coverage +playground.* +tsconfig.json +jest.config.js +readme.md \ No newline at end of file diff --git a/bin/rerun b/bin/rerun deleted file mode 100755 index 94e0838..0000000 --- a/bin/rerun +++ /dev/null @@ -1,61 +0,0 @@ -#!/usr/bin/env node - -process.title = 'protractor-rerun' - -const path = require('path') -const fs = require('fs') -const {getFilesList, buildExeRun, getRunCommand} = require('../lib') - -const argv = require('minimist')(process.argv.slice(2)) - -if(argv.protractor) { - const defaultBuilder = { - maxSessionCount: argv.maxSessionCount || 5, - attemptsCount: argv.attemptsCount || 2, - stackAnalize: () => true, - everyCycleCallback: async () => true, - grepWord: argv.grepWord || '', - longestProcessTime: 450000, - debugProcess: false, - formCommanWithOption: undefined, - pollTime: 1000 - } - const defaultConfigPath = path.resolve(process.cwd(), './protractor.conf.js') - const defaultSpecDir = path.resolve(process.cwd(), './specs') - - if(argv.configPath) { - if(!fs.existsSync(argv.configPath)) { - process.stdout.write('Config file was not found :' + argv.configPath) - process.exit(1) - } - } - if(!fs.existsSync(defaultConfigPath)) { - process.stdout.write('Default config file path is : ' + defaultConfigPath) - process.stdout.write('Default config file was not found') - process.exit(1) - } - - if(argv.specDir) { - if(!fs.existsSync(argv.specDir)) { - process.stdout.write('Spec dir was not found: ' + argv.specDir) - process.exit(1) - } - } - - if(!fs.existsSync(defaultSpecDir)) { - process.stdout.write('Default spec dit path is : ' + defaultSpecDir) - process.stdout.write('Default spec dit was not found') - process.exit(1) - } - - const specFiles = getFilesList(argv.specDir ? argv.specDir : defaultSpecDir) - .filter((file) => file.includes(defaultBuilder.grepWord)) - - if(!specFiles.length) { - process.stdout.write('Spec files was not fould') - } - - buildExeRun(defaultBuilder)(specFiles.map((file) => getRunCommand(file, - argv.configPath ? argv.configPath : defaultConfigPath - ))) -} diff --git a/config/protractor.conf.ts b/config/protractor.conf.ts deleted file mode 100644 index 07326a7..0000000 --- a/config/protractor.conf.ts +++ /dev/null @@ -1,33 +0,0 @@ -/* tslint:disable:object-literal-sort-keys */ -import { browser, ExpectedConditions as EC, Config } from 'protractor' -declare const global: any - -const conf: Config = { - specs: ['./specs/**/*.spec.ts'], - framework: 'mocha', - - logLevel: 'ERROR', - - mochaOpts: { - timeout: 25000, - reporter: 'mocha-allure-reporter' - }, - - // multiCapabilities: [{ - // browserName: 'chrome', - // maxInstances: 5, - // shardTestFiles: true - // }], - // restartBrowserBetweenTests: true, - allScriptsTimeout: 30 * 1000, - // restartBrowserBetweenTests: true, - SELENIUM_PROMISE_MANAGER: false, - onPrepare: async () => { - browser.waitForAngularEnabled(false) - }, - beforeEach: async () => { - browser.waitForAngularEnabled(false) - } -} - -exports.config = conf \ No newline at end of file diff --git a/jest.config.js b/jest.config.js new file mode 100644 index 0000000..f176fa1 --- /dev/null +++ b/jest.config.js @@ -0,0 +1,5 @@ +module.exports = { + preset: 'ts-jest', + testEnvironment: 'node', + testTimeout: 10000 +}; \ No newline at end of file diff --git a/lib/commandExecutorBuilder.js b/lib/commandExecutorBuilder.js deleted file mode 100644 index a6046b3..0000000 --- a/lib/commandExecutorBuilder.js +++ /dev/null @@ -1,14 +0,0 @@ -const {buildExecRunner} = require('./execProc') -const {buildSpawnRunner} = require('./spawnProc') - -function buildCommandExecutor(failedByAssert, {spawn = false, ...runOpts}) { - if(spawn) { - return buildSpawnRunner(failedByAssert, runOpts) - } else { - return buildExecRunner(failedByAssert, runOpts) - } -} - -module.exports = { - buildCommandExecutor -} diff --git a/lib/commandExecutorBuilder.ts b/lib/commandExecutorBuilder.ts new file mode 100644 index 0000000..90e9312 --- /dev/null +++ b/lib/commandExecutorBuilder.ts @@ -0,0 +1,15 @@ +import {buildExecRunner} from './execProc' +// import {buildSpawnRunner} from './spawnProc'; + +function buildCommandExecutor(notRetriable, {spawn = false, ...runOpts}) { + return buildExecRunner(notRetriable, runOpts) + // TODO will be implemented + // if (spawn) { + // return buildSpawnRunner(notRetriable, runOpts) + // } else { + // } +} + +export { + buildCommandExecutor +} diff --git a/lib/error.ts b/lib/error.ts new file mode 100644 index 0000000..83f4224 --- /dev/null +++ b/lib/error.ts @@ -0,0 +1,11 @@ +class ProcessRerunError extends Error { + constructor(type, message) { + super(message); + this.name = `${type}${this.constructor.name}`; + Error.captureStackTrace(this, this.constructor); + } +} + +export { + ProcessRerunError +} \ No newline at end of file diff --git a/lib/exec/index.ts b/lib/exec/index.ts new file mode 100644 index 0000000..4e93782 --- /dev/null +++ b/lib/exec/index.ts @@ -0,0 +1,37 @@ +import {exec} from 'child_process'; +import {millisecondsToMinutes} from '../utils' +import {logger} from '../logger'; +import {ProcessRerunError} from '../error'; + +function execute(cmd: string, executionHolder: {stackTrace: string}, execOpts = {}, debugProcess) { + const startTime = +Date.now(); + if ((typeof cmd) !== 'string') { + throw new ProcessRerunError('Type', `cmd (first argument should be a string), current type is ${typeof cmd}`); + } + if ((typeof executionHolder) !== 'object') { + throw new ProcessRerunError('Type', `executionHolder (second argument should be an object), current type is ${typeof executionHolder}`); + } + if (executionHolder === null) { + throw new ProcessRerunError('Type', `executionHolder (second argument should be an object), current type is null`); + } + + const execProc = exec(cmd, execOpts, (error, stdout, stderr) => { + + logger.info('___________________________________________________________________________'); + logger.info(`command for process: ${cmd}`); + logger.info(`process duration: ${millisecondsToMinutes(+Date.now() - startTime)}`); + logger.info(`PID: ${execProc.pid}`); + logger.info(`stdout: ${stdout}`); + if (stderr) logger.error(`stderr: ${stderr}`); + if (error) logger.error(`error: ${error}`); + logger.info('___________________________________________________________________________'); + + executionHolder.stackTrace += `${stdout}${stderr}`; + }); + + return execProc; +} + +export { + execute +} \ No newline at end of file diff --git a/lib/execProc.js b/lib/execProc.js deleted file mode 100644 index 7b6a3b8..0000000 --- a/lib/execProc.js +++ /dev/null @@ -1,130 +0,0 @@ -const {exec} = require('child_process'); -const {returnStringType} = require('./helpers'); -const {millisecondsToMinutes} = require('./utils') - -function buildExecRunner(failedByAssert, runOpts) { - const { - addSpecificOptionsBeforeRun, - currentExecutionVariable, - longestProcessTime, - debugProcess, - reformatCommand, - stackAnalize, - execOpts = {maxBuffer: 1000 * 1024} - } = runOpts; - - return (cmd, index) => new Promise((resolve) => { - let additionalOpts = null; - let originalCmd = cmd; - let specificCallBack = null; - let executionStack = ''; - /** - * @now this variable will be used for process kill if time more than @longestProcessTime - */ - const startTime = +Date.now(); - - /** - * @param {undefined|function} addSpecificOptions if function cmd will go to this function as argument - */ - if (addSpecificOptionsBeforeRun) { - const cmdObj = addSpecificOptionsBeforeRun(cmd); - cmd = cmdObj.cmd; - specificCallBack = cmdObj.cmdExecutableCB; - additionalOpts = cmd.replace(originalCmd, ''); - } - - if (currentExecutionVariable) { - if (cmd.includes(currentExecutionVariable)) { - cmd = cmd.replace(new RegExp(`${currentExecutionVariable}=\\d+`, 'ig'), `${currentExecutionVariable}=${index}`); - } else { - cmd = `${currentExecutionVariable}=${index} ${cmd}`; - } - } - - const execProc = exec(cmd, execOpts, (error, stdout, stderr) => { - if (debugProcess) { - console.log('___________________________________________________________________________'); - console.log(`command for process: ${cmd}`); - console.log(`process duration: ${millisecondsToMinutes(+Date.now() - startTime)}`); - console.log(`PID: ${execProc.pid}`); - console.log(`stdout: ${stdout}`); - console.error(`stderr: ${stderr}`); - console.error(`error: ${error}`); - console.log('___________________________________________________________________________'); - } - executionStack += `${stdout}${stderr}`; - }); - - const killTooLongExecution = (procWhatShouldBeKilled) => { - const executionTime = +Date.now() - startTime; - if (executionTime > longestProcessTime) { - if (debugProcess) { - console.log(`Process killed due to long time execution: ${millisecondsToMinutes(executionTime)}`); - } - procWhatShouldBeKilled.kill(); - } - }; - - const watcher = setInterval(() => killTooLongExecution(execProc), 5000); - - execProc.on('exit', (code, signal) => { - if (debugProcess) { - console.log(`EXIT PROCESS: PID="${execProc.pid}", code="${code}" and signal="${signal}"`); - } - }); - - execProc.on('error', (e) => { - if (debugProcess) { - console.log(`ERROR PROCESS: PID="${execProc.pid}"`); - } - console.error(e); - }); - - execProc.on('close', async (code, signal) => { - if (debugProcess) { - console.log(`CLOSE PROCESS: PID="${execProc.pid}", code="${code}" and signal="${signal}"`); - } - // clear watcher interval - clearInterval(watcher); - - let commandToRerun = null; - - // if process code 0 - exit as a success result - if (code === 0) { - resolve(commandToRerun); - return; - } - // stackAnalize - check that stack contains or not contains some specific data - if (stackAnalize && stackAnalize(executionStack)) { - commandToRerun = cmd; - } else if (reformatCommand) { - commandToRerun = cmd; - } else { - failedByAssert.push(cmd); - } - - // if code === 0 do nothing, success - if (specificCallBack) { - if (specificCallBack.then || returnStringType(specificCallBack) === '[object AsyncFunction]') { - await specificCallBack(); - } else { - specificCallBack(); - } - } - - if (reformatCommand && commandToRerun) { - commandToRerun = reformatCommand(commandToRerun, executionStack, failedByAssert); - } - // addSpecificOptionsBeforeRun was defined - we should remove useless opts what will be added in next iteration - if (commandToRerun && additionalOpts) { - commandToRerun = commandToRerun.replace(additionalOpts, ''); - } - - resolve(commandToRerun); - }) - }); -} - -module.exports = { - buildExecRunner -}; diff --git a/lib/execProc.ts b/lib/execProc.ts new file mode 100644 index 0000000..d8ac52f --- /dev/null +++ b/lib/execProc.ts @@ -0,0 +1,121 @@ +import {returnStringType} from './helpers'; +import {millisecondsToMinutes} from './utils' +import {execute} from './exec'; +import {logger} from './logger'; +import {ProcessRerunError} from './error'; + +function buildExecRunner(notRetriable, runOpts) { + const { + formCommanWithOption, + currentExecutionVariable, + longestProcessTime, + debugProcess, + processResultAnalyzer, + execOpts = {maxBuffer: 1000 * 1024}, + pollTime, + successExitCode = 0 + } = runOpts; + + if ((typeof successExitCode) !== 'number') { + throw new ProcessRerunError('Type', 'successExitCode should be a number'); + } + + return (cmd, index) => new Promise((resolve) => { + // TODO + let additionalOpts = null; + let originalCmd = cmd; + let onErrorCloseHandler = null; + + const executionHolder = {stackTrace: ''}; + /** + * @now this variable will be used for process kill if time more than @longestProcessTime + */ + const startTime = +Date.now(); + + /** + * @param {undefined|function} addSpecificOptions if function cmd will go to this function as argument + */ + if (formCommanWithOption) { + const cmdObj = formCommanWithOption(cmd); + cmd = cmdObj.cmd; + onErrorCloseHandler = cmdObj.onErrorCloseHandler; + additionalOpts = cmd.replace(originalCmd, ''); + } + + if (currentExecutionVariable) { + if (cmd.includes(currentExecutionVariable)) { + cmd = cmd.replace(new RegExp(`${currentExecutionVariable}=\\d+`, 'ig'), `${currentExecutionVariable}=${index}`); + } else { + cmd = `${currentExecutionVariable}=${index} ${cmd}`; + } + } + const execProc = execute(cmd, executionHolder, execOpts, debugProcess); + + const killTooLongExecution = (procWhatShouldBeKilled) => { + const executionTime = +Date.now() - startTime; + if (executionTime > longestProcessTime) { + if (debugProcess) { + logger.info(`Process killed due to long time execution: ${millisecondsToMinutes(executionTime)}`); + } + procWhatShouldBeKilled.kill(); + } + }; + + const watcher = setInterval(() => killTooLongExecution(execProc), pollTime); + + execProc.on('exit', (code, signal) => { + if (debugProcess) { + logger.info(`EXIT PROCESS: PID="${execProc.pid}", code="${code}" and signal="${signal}"`); + } + }); + + execProc.on('error', (e) => { + if (debugProcess) { + logger.info(`ERROR PROCESS: PID="${execProc.pid}"`); + } + logger.error(e); + }); + + execProc.on('close', async (code, signal) => { + if (debugProcess) { + logger.info(`CLOSE PROCESS: PID="${execProc.pid}", code="${code}" and signal="${signal}"`); + } + + // clear watcher interval + clearInterval(watcher); + + if (onErrorCloseHandler) { + if (returnStringType(onErrorCloseHandler).includes('Function')) { + await onErrorCloseHandler(); + } + } + + // if process code 0 - exit as a success result + if (code === successExitCode) { + return resolve(null); + } + + // processResultAnalyzer - check that stack contains or not contains some specific data + if (processResultAnalyzer) { + const countInNotRetriableBeforeAnalyzation = notRetriable.length; + + const processResultAnalyzerResultCommandOrNull = processResultAnalyzer( + cmd, + executionHolder.stackTrace, + notRetriable + ); + + if (!processResultAnalyzerResultCommandOrNull && countInNotRetriableBeforeAnalyzation === notRetriable.length) { + notRetriable.push(cmd); + } + return resolve(processResultAnalyzerResultCommandOrNull); + } + + return resolve(cmd); + }) + }); +} + +export { + buildExecRunner +}; diff --git a/lib/helpers.js b/lib/helpers.ts similarity index 62% rename from lib/helpers.js rename to lib/helpers.ts index dc419d6..2239412 100644 --- a/lib/helpers.js +++ b/lib/helpers.ts @@ -1,17 +1,6 @@ -const fs = require('fs') -const path = require('path') -/** - * Current example for process-rerun with protractor framework - * - * - * getFormedRunCommand('./spec/test.file.spec.js', './protractor.conf.js') - * @param {string} file path to spec file - * @param {string} conf path to config file - * @returns {string} - */ -const getFormedRunCommand = (file, conf = path.resolve(process.cwd(), './protractor.conf.js')) => { - return `${path.resolve(process.cwd(), './node_modules/.bin/protractor')} ${conf} --specs ${file}` -} +import * as fs from 'fs'; +import * as path from 'path'; +import {ProcessRerunError} from './error'; /** * @param {string} dir a path to the director what should be read @@ -20,25 +9,31 @@ const getFormedRunCommand = (file, conf = path.resolve(process.cwd(), './protrac * @param {boolean} ignoreSubDirs option, directories what should be exclude from files list * @returns {array} */ -const getFilesList = function(dir, fileList = [], directoryToSkip = [], ignoreSubDirs) { +const getFilesList = function(dir: string, fileList: string[] = [], directoryToSkip = [], ignoreSubDirs?: boolean) { + if (!fs.existsSync(dir)) { + throw new ProcessRerunError('FileSystem', `${dir} does not exists`); + } const files = fs.readdirSync(dir) + files.forEach(function(file) { const isDirr = fs.statSync(path.join(dir, file)).isDirectory() + const shouldBeExcluded = (Array.isArray(directoryToSkip) && directoryToSkip.includes(file)) || (typeof directoryToSkip === 'string' && file.includes(directoryToSkip)) || (directoryToSkip instanceof RegExp && file.match(directoryToSkip)) - if(shouldBeExcluded) {return } + if (shouldBeExcluded) {return } - if(isDirr && !ignoreSubDirs) { + if (isDirr && !ignoreSubDirs) { fileList = getFilesList(path.join(dir, file), fileList, directoryToSkip, ignoreSubDirs) - } else { + } else if (!isDirr) { fileList.push(path.join(dir, file)) } - }) - return fileList + }); + + return fileList; } /** @@ -48,7 +43,7 @@ const getFilesList = function(dir, fileList = [], directoryToSkip = [], ignoreSu */ const getPollTime = (timeVal) => { - return typeof timeVal === 'number' ? timeVal : 1000 + return typeof timeVal === 'number' && !isNaN(timeVal) && isFinite(timeVal) ? timeVal : 1000; } /** * await sleep(5000) @@ -58,8 +53,7 @@ const sleep = (time) => new Promise((res) => setTimeout(res, time)) const returnStringType = (arg) => Object.prototype.toString.call(arg) -module.exports = { - getFormedRunCommand, +export { getPollTime, sleep, getFilesList, diff --git a/lib/index.js b/lib/index.js deleted file mode 100644 index e2977a2..0000000 --- a/lib/index.js +++ /dev/null @@ -1,170 +0,0 @@ -const {buildCommandExecutor} = require('./commandExecutorBuilder') -const {sleep, getFormedRunCommand, getFilesList, getPollTime} = require('./helpers') - -function reRunnerBuilder(runOptions) { - - const failedByAssert = [] - let currentSessionCount = 0 - - let {attemptsCount} = runOptions - const { - stackAnalize, - grepWord, - debugProcess, - reformatCommand, - intervalPoll, - everyCycleCallback, - specsDir, - longestProcessTime, - spawn, - formCommanWithOption: addSpecificOptionsBeforeRun, - currentExecutionVariable - } = runOptions - - // maxSessionCount should be "let", because it will increment and decrement - let {maxSessionCount} = runOptions - - /** - * @param {string} cmd command what should be executed - * @returns {Promise|Promise} return null if command executed successful or cmd if something went wrong - */ - - const executeCommandAsync = buildCommandExecutor(failedByAssert, { - spawn, - addSpecificOptionsBeforeRun, - currentExecutionVariable, - longestProcessTime, - debugProcess, - reformatCommand, - stackAnalize - }) - - - async function reRunner(commandsArray) { - // if run arr was not defined as argument commandsArray will defined as default array - commandsArray = (commandsArray || getFilesList(specsDir) - .map((file) => getFormedRunCommand(file))) - .filter(function(cmd) {return cmd.includes(grepWord)}) - - if(debugProcess) { - console.log(`Attempts count is: ${attemptsCount}`) - } - - if(typeof attemptsCount !== 'number') { - console.warn('attemptsCount should be a number, 2 will be used as a default') - attemptsCount = 2 - } - - const failedCommands = await new Array(attemptsCount) - // create array with current length - .fill(attemptsCount) - // execute run - .reduce((resolver, /*current*/ current, index) => { - - if(debugProcess) { - console.info('>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>') - console.info(`Execution number: ${index}`) - console.info('>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>') - } - - return resolver.then((resolvedCommandsArray) => { - if(debugProcess) { - console.info('=========================================================================') - console.info(`Processes count: ${resolvedCommandsArray.length}`) - console.info('=========================================================================') - } - return runCommandsArray(resolvedCommandsArray, [], index) - - .then((failedCommandsArray) => { - return failedCommandsArray - }) - }) - }, Promise.resolve(commandsArray)) - - /** - * @param {Array} commands command array what should be executed - * @param {Array} failedCommands array what will contains failed commands - * @returns {void} - */ - - async function runCommand(commands, failedCommands, runIndex) { - if(maxSessionCount > currentSessionCount && commands.length) { - currentSessionCount += 1 - const result = await executeCommandAsync(commands.splice(0, 1)[0], runIndex).catch(console.error) - if(result) { - failedCommands.push(result) - } - currentSessionCount -= 1 - } - } - - async function runCommandsArray(commands, failedCommands, executionCount) { - const asserter = setInterval(() => runCommand(commands, failedCommands, executionCount), intervalPoll); - - do { - if(commands.length) {await runCommand(commands, failedCommands, executionCount)} - if(currentSessionCount) {await sleep(2000)} - } while(commands.length || currentSessionCount) - - if(everyCycleCallback && typeof everyCycleCallback === 'function') { - try { - await everyCycleCallback() - } catch(e) { - console.log(e) - } - } - - clearInterval(asserter) - return failedCommands - } - - const combinedFailedProcesses = [...failedCommands, ...failedByAssert] - - console.log('Failed processes count:', combinedFailedProcesses.length) - - return { - failedCommands, - failedByAssert - } - } - - return reRunner -} - -module.exports = { - buildExeRun: ({ - maxSessionCount = 5, - attemptsCount = 2, - stackAnalize, - everyCycleCallback, - reformatCommand, - grepWord = '', - longestProcessTime = 450000, - debugProcess = false, - formCommanWithOption, - pollTime = 1000, - currentExecutionVariable, - spawn = false - } = {}) => { - - const reformattedArgs = { - formCommanWithOption, - debugProcess, - longestProcessTime, - maxSessionCount, - attemptsCount, - reformatCommand, - stackAnalize, - grepWord, - spawn, - currentExecutionVariable, - everyCycleCallback, - intervalPoll: getPollTime(pollTime) - }; - - return reRunnerBuilder(reformattedArgs) - }, - sleep, - getFilesList, - getRunCommand: getFormedRunCommand -} diff --git a/lib/index.ts b/lib/index.ts new file mode 100644 index 0000000..7bb52b5 --- /dev/null +++ b/lib/index.ts @@ -0,0 +1,168 @@ +import {buildCommandExecutor} from './commandExecutorBuilder'; +import {sleep, getFilesList, getPollTime} from './helpers'; +import {logger, setLogLevel} from './logger'; + +function reRunnerBuilder(runOptions) { + const notRetriable = []; + let currentSessionCount = 0; + + const { + attemptsCount, + processResultAnalyzer, + // @deprecated + debugProcess, + pollTime, + everyCycleCallback, + longestProcessTime, + spawn, + formCommanWithOption, + currentExecutionVariable, + maxThreads, + logLevel = 'ERROR' + } = runOptions; + + setLogLevel(logLevel); + + const executeCommandAsync = buildCommandExecutor(notRetriable, { + spawn: false, + formCommanWithOption, + currentExecutionVariable, + longestProcessTime, + debugProcess, + processResultAnalyzer, + pollTime + }); + + async function reRunner(commandsArray) { + /** + * @see + * remove likt to initial commands array + * commandsArray = [...commandsArray] + */ + commandsArray = [...commandsArray]; + + if (debugProcess) { + logger.info(`Threads count is: ${maxThreads}`); + logger.info(`Commands count: ${commandsArray.length}`); + logger.info(`Attempts count is: ${attemptsCount}`); + } + + + async function runCommand(commands, retriable, runIndex) { + if (maxThreads > currentSessionCount && commands.length) { + currentSessionCount += 1 + const result = await executeCommandAsync(commands.splice(0, 1)[0], runIndex).catch(console.error); + if (result) { + retriable.push(result); + } + currentSessionCount -= 1 + } + } + + async function runCommandsArray(commands, retriable, executionCount) { + const asserter = setInterval(() => runCommand(commands, retriable, executionCount), pollTime); + + do { + if (commands.length) {await runCommand(commands, retriable, executionCount)} + if (currentSessionCount) {await sleep(pollTime)} + } while (commands.length || currentSessionCount) + + if (everyCycleCallback && typeof everyCycleCallback === 'function') { + try { + await everyCycleCallback() + } catch (e) { + logger.error(e) + } + } + + clearInterval(asserter) + return retriable + } + let resolvedCommandsArray = [...commandsArray]; + const retriable = []; + for (let index = 0; index < attemptsCount; index++) { + resolvedCommandsArray = await runCommandsArray(resolvedCommandsArray, [], index); + if (!resolvedCommandsArray.length) { + break; + } + if (debugProcess) { + logger.info('>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>') + logger.info(`Execution cycle: ${index}`) + logger.info('>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>') + logger.info('=========================================================================') + logger.info(`Processes count: ${resolvedCommandsArray.length}`) + logger.info('=========================================================================') + } + } + retriable.push(...resolvedCommandsArray); + + if (debugProcess) { + logger.info('>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>') + logger.info('Failed processes count:', retriable.length + notRetriable.length); + logger.info('Not retriable processes count:', notRetriable.length); + logger.info('Retriable processes count:', retriable.length); + logger.info('>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>') + } + + return { + retriable, + notRetriable + } as {retriable: string[]; notRetriable: string[]} + } + + return reRunner +} + +interface IBuildOpts { + logLevel?: 'ERROR' | 'WARN' | 'INFO' | 'VERBOSE'; + maxThreads?: number, + attemptsCount?: number, + longestProcessTime?: number; + pollTime?: number; + debugProcess?: boolean; + processResultAnalyzer?: (originalCommand: string, stack: string, notRetriable: any[]) => string | null; + everyCycleCallback?: () => void; + formCommanWithOption?: (cmd: string) => {cmd: string; onErrorCloseHandler: () => void}; + currentExecutionVariable?: string; +} + + +interface IRunner { + (commands: string[]): Promise<{notRetriable: string[], retriable: string[]}>; +} + +const buildRunner = ({ + maxThreads = 5, + attemptsCount = 2, + longestProcessTime = 450000, + pollTime = 1000, + debugProcess = false, + ...rest +}: IBuildOpts = {}): IRunner => { + + const reformattedArgs = { + debugProcess, + longestProcessTime, + maxThreads, + attemptsCount, + pollTime: getPollTime(pollTime), + ...rest + }; + + return reRunnerBuilder(reformattedArgs) +} + +const getReruner = function(optsObj) { + return buildRunner(optsObj) +}; + +const getSpecFilesArr = getFilesList; + +export { + buildRunner, + getFilesList, + getReruner, + getSpecFilesArr, + IBuildOpts, + IRunner +} diff --git a/lib/logger.ts b/lib/logger.ts new file mode 100644 index 0000000..55f8265 --- /dev/null +++ b/lib/logger.ts @@ -0,0 +1,67 @@ +function wrapInGreen(txt: string) { + return `\u001b[34m${txt}\u001b[0m`; +} + +function wrapInRed(txt: string) { + return `\u001b[31m${txt}\u001b[0m`; +} + +function wrapInBlue(txt: string) { + return `\u001b[34m${txt}\u001b[0m`; +} + +function wrapInYellow(txt: string) { + return `\u001b[33m${txt}\u001b[0m`; +} + +function wrapInMagenta(txt: string) { + return `\x1b[35m${txt}\u001b[0m`; +} + +const colors = { + red: (text: string) => wrapInRed(text), + magenta: (text: string) => wrapInMagenta(text), + green: (text: string) => wrapInGreen(text), + yellow: (text: string) => wrapInYellow(text), + blue: (text: string) => wrapInBlue(text) +}; + +const logger = { + // 'ERROR' | 'WARN' | 'INFO' | 'VERBOSE'; + logLevel: 'ERROR', + log(...args) { + if (this.logLevel === 'VERBOSE') { + console.log(colors.green('LOG: '), ...args); + } + }, + info(...args) { + if (this.logLevel === 'VERBOSE' || this.logLevel === 'INFO') { + console.info(colors.yellow('INFO: '), ...args); + } + }, + warn(...args) { + if (this.logLevel === 'VERBOSE' || this.logLevel === 'INFO' || this.logLevel === 'WARN') { + + console.warn(colors.magenta('WARNING: '), ...args); + } + }, + error(...args) { + if (this.logLevel === 'VERBOSE' || this.logLevel === 'INFO' || this.logLevel === 'WARN' || this.logLevel === 'ERROR') { + console.error(colors.red('ERROR: '), ...args); + } + }, + setLogLevel(level) { + this.logLevel = level; + } +}; + + +function setLogLevel(level: 'ERROR' | 'WARN' | 'INFO' | 'VERBOSE') { + logger.setLogLevel(level); +} + +export { + logger, + colors, + setLogLevel +} \ No newline at end of file diff --git a/lib/mochaProgrammatically.js b/lib/mochaProgrammatically.js deleted file mode 100644 index eca221d..0000000 --- a/lib/mochaProgrammatically.js +++ /dev/null @@ -1,3 +0,0 @@ -const Mocha = require('mocha') - -const itSpecNameRegex = /(?<=it\(')(\d|\w|\s)+/ig \ No newline at end of file diff --git a/lib/spawnProc.js b/lib/spawnProc.ts similarity index 55% rename from lib/spawnProc.js rename to lib/spawnProc.ts index 5ed694f..d0b4753 100644 --- a/lib/spawnProc.js +++ b/lib/spawnProc.ts @@ -1,21 +1,21 @@ -const {spawn} = require('child_process') -const {returnStringType} = require('./helpers') +import {spawn} from 'child_process'; +import {returnStringType} from './helpers'; -function buildSpawnRunner(failedByAssert, runOpts) { +function buildSpawnRunner(notRetriable, runOpts) { const { - addSpecificOptionsBeforeRun, + formCommanWithOption, longestProcessTime, debugProcess, - reformatCommand, - stackAnalize + processResultAnalyzer } = runOpts const executeCommandAsync = (cmd) => new Promise((resolve) => { + let watcher = null let originalComman = null let originalCmd = cmd - let specificCallBack = null + let onErrorCloseHandler = null let executionStack = '' /** @@ -24,22 +24,22 @@ function buildSpawnRunner(failedByAssert, runOpts) { const now = +Date.now() const killTooLongExecution = (proc) => { - if(Date.now() - now >= longestProcessTime) { + if (Date.now() - now >= longestProcessTime) { proc.kill() } } /** * @param {undefined|function} addSpecificOptions if function cmd will go to this function as argument */ - if(addSpecificOptionsBeforeRun) { - const cmdObj = addSpecificOptionsBeforeRun(cmd) + if (formCommanWithOption) { + const cmdObj = formCommanWithOption(cmd) originalComman = {...cmd} cmd = cmdObj.cmd - specificCallBack = cmdObj.cmdExecutableCB + onErrorCloseHandler = cmdObj.cmdExecutableCB } - if(debugProcess) {console.log(cmd)} + if (debugProcess) {console.log(cmd)} const proc = spawn(cmd.command, cmd.args, cmd.options) @@ -57,34 +57,29 @@ function buildSpawnRunner(failedByAssert, runOpts) { proc.on('close', async (code) => { let commandToRerun = null - if(debugProcess) {console.log(executionStack, code)} + if (debugProcess) {console.log(executionStack, code)} // if process code 0 - exit as a success result - if(code === 0) { + if (code === 0) { resolve(commandToRerun); return } - // stackAnalize - check that stack contains or not contains some specific data - if(code !== 0 && stackAnalize && stackAnalize(executionStack)) { - commandToRerun = cmd - } else if(code !== 0 && reformatCommand) { + // processResultAnalyzer - check that stack contains or not contains some specific data + if (code !== 0 && processResultAnalyzer && processResultAnalyzer(executionStack)) { commandToRerun = cmd } else { - failedByAssert.push(cmd) + notRetriable.push(cmd) } // if code === 0 do nothing, success - if(specificCallBack) { - if(specificCallBack.then || returnStringType(specificCallBack) === '[object AsyncFunction]') { - await specificCallBack() + if (onErrorCloseHandler) { + if (onErrorCloseHandler.then || returnStringType(onErrorCloseHandler) === '[object AsyncFunction]') { + await onErrorCloseHandler() } else { - specificCallBack() + onErrorCloseHandler() } } - if(reformatCommand && commandToRerun) { - commandToRerun = reformatCommand(commandToRerun, executionStack) - } - // addSpecificOptionsBeforeRun was defined - we should remove useless opts what will be added in next iteration + // formCommanWithOption was defined - we should remove useless opts what will be added in next iteration // if(additionalOpts) { // commandToRerun = commandToRerun.replace(additionalOpts, '') // } @@ -95,6 +90,6 @@ function buildSpawnRunner(failedByAssert, runOpts) { return executeCommandAsync } -module.exports = { +export { buildSpawnRunner } diff --git a/lib/utils.js b/lib/utils.js deleted file mode 100644 index 0eb01bf..0000000 --- a/lib/utils.js +++ /dev/null @@ -1,30 +0,0 @@ -function executionWatcher(debugProcess, currentTime, limitTime, intervalWatcher, processWhatShouldBeKilled, resolver, resolverArg) { - if(+Date.now() - currentTime > limitTime) { - if(debugProcess) { - console.log('_______________________________________________________________ \n') - console.log('Process what was started just was killed \n') - console.log('Command is: ', cmd) - console.log('_______________________________________________________________ \n') - } - - clearInterval(intervalWatcher) - - if(processWhatShouldBeKilled) { - processWhatShouldBeKilled.kill() - } - if(resolver) { - resolver(resolverArg) - } - } -}; - -function millisecondsToMinutes(milliseconds) { - const minutes = Math.floor(milliseconds / 60000); - const seconds = ((milliseconds % 60000) / 1000).toFixed(0); - return (seconds === '60' ? (minutes + 1) + ":00" : minutes + ":" + (seconds < 10 ? "0" : "") + seconds); -} - -module.exports = { - executionWatcher, - millisecondsToMinutes -} \ No newline at end of file diff --git a/lib/utils.ts b/lib/utils.ts new file mode 100644 index 0000000..96880b4 --- /dev/null +++ b/lib/utils.ts @@ -0,0 +1,32 @@ +import {logger} from './logger'; + +function executionWatcher(debugProcess, currentTime, limitTime, intervalWatcher, processWhatShouldBeKilled, resolver, resolverArg, cmd) { + if (+Date.now() - currentTime > limitTime) { + if (debugProcess) { + logger.log('_______________________________________________________________ \n') + logger.log('Process what was started just was killed \n') + logger.log('Command is: ', cmd) + logger.log('_______________________________________________________________ \n') + } + + clearInterval(intervalWatcher) + + if (processWhatShouldBeKilled) { + processWhatShouldBeKilled.kill() + } + if (resolver) { + resolver(resolverArg) + } + } +}; + +function millisecondsToMinutes(milliseconds) { + const minutes = Math.floor(milliseconds / 60000) as number; + const seconds = ((milliseconds % 60000) / 1000).toFixed(0); + return (seconds === '60' ? (minutes + 1) + ":00" : minutes + ":" + (+seconds < 10 ? "0" : "") + seconds); +} + +export { + executionWatcher, + millisecondsToMinutes, +} \ No newline at end of file diff --git a/lol.js b/lol.js deleted file mode 100644 index 5ee6183..0000000 --- a/lol.js +++ /dev/null @@ -1,164 +0,0 @@ -// temp base modules -const Mocha = require('mocha') -const fs = require('fs') -const path = require('path') -const {getSpecFilesArr} = require('./rerun') -// temp base modules - - -// temp hardcoded values -const everyCycleCallback = null -const intervalPoll = 500 -const debugProcess = false -const maxSessionCount = 5 -const executionTimesCount = 3 -// temp hardcoded values - - -let currentSessionCount = 0 - - - - - -const itSpecNameRegex = /(?<=it\(')(\d|\w|\s)+/ig - - -const filesWithGrepOpts = [] - - -var testDir = path.relative(__dirname, './mocha_specs') - -// Add each .js file to the mocha instance -getSpecFilesArr(testDir).forEach(function(file) { - - const fileContent = fs.readFileSync(file, {encoding: 'utf8'}) - - const stepGreps = fileContent.match(itSpecNameRegex) - - stepGreps.forEach(function(grepItem) { - filesWithGrepOpts.push({file, grepItem}) - }) - - // mocha.addFile( - // path.join(testDir, file) - // ); -}); - - -async function executeCommandAsync(cmd, runIndex) { - - // console.log(cmd) - if(!cmd) return - - return new Promise(function(resolve) { - const mocha = new Mocha() - console.log(cmd.file) - mocha.addFile(cmd.file) - mocha.grep(cmd.grepItem) - console.log(cmd) - - mocha.run(function(failures) { - console.log('!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! 11111') - if(cmd.file === 'mocha_specs/2.spec.js', cmd.grepItem === 'it 2 second describe ') { - console.log('should fail', '!!!!!!!!!!!!!!!!!!!!!', failures) - process.exit(0) - } - console.log('FAILERS', failures) - resolve(true) - }) - - }) -} - - -const item = { - file: 'mocha_specs/2.spec.js', - grepItem: 'it 2 second describe ' -} - - - - - - - -executeCommandAsync(item) - - - - -// runCommandsArray(filesWithGrepOpts, [], 0).then(console.log) - - - - - - -async function runCommand(commands, failedCommands, runIndex) { - if(maxSessionCount > currentSessionCount && commands.length) { - currentSessionCount += 1 - const result = await executeCommandAsync(commands.splice(0, 1)[0], runIndex).catch(console.error) - if(result) { - failedCommands.push(result) - } - currentSessionCount -= 1 - } -} - - - - -async function runCommandsArray(commands, failedCommands, executionCount) { - - const asserter = setInterval(() => runCommand(commands, failedCommands, executionCount), intervalPoll); - - do { - if(commands.length) {await runCommand(commands, failedCommands, executionCount)} - if(currentSessionCount) {await sleep(2000)} - } while(commands.length || currentSessionCount) - - if(everyCycleCallback && typeof everyCycleCallback === 'function') { - try { - await everyCycleCallback() - } catch(e) { - console.log(e) - } - } - - clearInterval(asserter) - return failedCommands -} - - - -/* -const executionTimes = new Array(executionTimesCount).fill('noop') - .reduce((resolver, current, index) => { - - if(debugProcess) { - console.info('>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>') - console.info(`Execution number: ${index}`) - console.info('>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>') - } - - return resolver.then((resolvedCommandsArray) => { - if(debugProcess) { - console.info('=========================================================================') - console.info(`Processes count: ${resolvedCommandsArray.length}`) - console.info('=========================================================================') - } - return runCommandsArray(resolvedCommandsArray, [], index) - - .then((failedCommandsArray) => { - return failedCommandsArray - }) - }) - }, Promise.resolve(filesWithGrepOpts)) - -*/ - -// // Run the tests. -// mocha.run(function(failures) { -// process.exitCode = failures ? 1 : 0; // exit with non-zero status if there were failures -// }); \ No newline at end of file diff --git a/mocha_specs/1.spec.js b/mocha_specs/1.spec.js deleted file mode 100644 index 1f8957f..0000000 --- a/mocha_specs/1.spec.js +++ /dev/null @@ -1,14 +0,0 @@ -const {expect} = require('chai') - -describe('First describe', () => { - it('it 1 first describe ', () => { - expect(1).to.eql(1) - }) - it('it 2 first describe ', () => { - expect(1).to.eql(2) - }) - it('it 3 first describe ', () => { - expect(1).to.eql(3) - }) -}) - diff --git a/mocha_specs/2.spec.js b/mocha_specs/2.spec.js deleted file mode 100644 index 1bfbec8..0000000 --- a/mocha_specs/2.spec.js +++ /dev/null @@ -1,13 +0,0 @@ -const {expect} = require('chai') - -describe('Second describe', () => { - it('it 1 second describe ', () => { - expect(1).to.eql(1) - }) - it('it 2 second describe ', () => { - expect(1).to.eql(2) - }) - it('it 3 second describe ', () => { - expect(1).to.eql(3) - }) -}) \ No newline at end of file diff --git a/mocha_specs/3.spec.js b/mocha_specs/3.spec.js deleted file mode 100644 index 5f30e5e..0000000 --- a/mocha_specs/3.spec.js +++ /dev/null @@ -1,13 +0,0 @@ -const {expect} = require('chai') - -describe('Third describe', () => { - it('it 1 third describe ', () => { - expect(1).to.eql(1) - }) - it('it 2 third describe ', () => { - expect(1).to.eql(2) - }) - it('it 3 third describe ', () => { - expect(1).to.eql(3) - }) -}) \ No newline at end of file diff --git a/package.json b/package.json index b4c351e..ceb6ba3 100644 --- a/package.json +++ b/package.json @@ -1,19 +1,18 @@ { "name": "process-rerun", - "version": "0.0.17", - "bin": { - "protractor-rerun": "./bin/rerun" - }, + "version": "0.1.10", "repository": { "type": "git", "url": "git+https://github.com/potapovDim/protractor-rerun.git" }, - "main": "./rerun.js", + "main": "./built/index.js", "scripts": { - "test:local": "protractor ./protractor.conf.js", "tscw": "tsc -w", "validate": "npm ls", - "unit": "mocha --timeout 60000 $(find ./unit_specs/ -path '*.spec.js')" + "test": "jest", + "test:cover": "jest --collect-coverage", + "build": "tsc", + "postinstall": "node ./postinstall.js" }, "author": { "name": "Dmytro Potapov", @@ -27,20 +26,18 @@ "selenium", "rerun", "failed-tests", - "protractor-rerun" + "protractor-rerun", + "parallel execution" ], "devDependencies": { - "@types/chai": "^4.1.2", - "@types/mocha": "2.2.41", - "@types/node": "8.0.24", - "awb": "^0.4.84", - "chai": "^4.1.1", - "mocha": "^5.2.0", - "mocha-allure-reporter": "^1.4.0", - "protractor": "^5.3.0", + "@types/jest": "^26.0.10", + "@types/node": "^14.6.2", + "assertior": "0.0.11", + "jest": "^26.4.2", + "ts-jest": "^26.3.0", "ts-node": "^5.0.1", "tslint": "^5.9.1", - "typescript": "2.5.1" + "typescript": "^4.0.2" }, "engines": { "node": ">=8.9.0" diff --git a/po/base.po.ts b/po/base.po.ts deleted file mode 100644 index 519e682..0000000 --- a/po/base.po.ts +++ /dev/null @@ -1,17 +0,0 @@ -import {$, browser, ExpectedConditions as EC} from 'protractor' -// import { stepAllure } from '../util/decorator' - - -class BasePo { - constructor() { - // some code - } - - // @stepAllure('Set test input') - public async setInput(selector: string, value: string) { - await browser.wait(EC.visibilityOf($(selector)), 1000) - await $(selector).sendKeys(value) - } -} - -export {BasePo} diff --git a/postinstall.js b/postinstall.js new file mode 100644 index 0000000..b81ea43 --- /dev/null +++ b/postinstall.js @@ -0,0 +1,7 @@ +console.log(` +🛠 +debugProcess prop is not supproted, +debugProcess - will be removed in 0.1.11 version, +please, use logLevel prop with one of 'ERROR' | 'WARN' | 'INFO' | 'VERBOSE'; +🛠 +`) \ No newline at end of file diff --git a/protractor.conf.js b/protractor.conf.js deleted file mode 100644 index af964df..0000000 --- a/protractor.conf.js +++ /dev/null @@ -1,5 +0,0 @@ -require('ts-node').register({ - app: './' -}) - -module.exports = require('./config/protractor.conf') \ No newline at end of file diff --git a/readme.md b/readme.md index fcaa791..0cb55c6 100644 --- a/readme.md +++ b/readme.md @@ -1,99 +1,80 @@ -What is the problem ? -When some process failed we need tool for rerun that process controled times with some params. -In common cases we use protractor so next example for protractor +# process-rerun + +The purpose of this library is - build simple and flexible interface for parallel command execution with rerun (on fail) possibility ![npm downloads](https://img.shields.io/npm/dm/process-rerun.svg?style=flat-square) -# From command line -```sh -./node_modules/.bin/process-rerun --protractor --configPath=./protractor.conf.js --specDir=./specs -``` +## API -# From js -```js -const { getReruner, getSpecFilesArr } = require('process-rerun') +### buildRunner(buildOpts): returns rerunner: function(string[]): {retriable: string[]; notRetriable: string[]} -/* - @{pathToSpecDirectory} string // './specs' - @{emptyArr} epmty arr // [] - @{skipFolders} if some folders should be excluded ['folderB','folderB'] - getSpecFilesArr(pathToSpecDirectory, emptyArr, skipFolders) params -*/ -const specsArr = getSpecFilesArr('./specs') -// return all files in folder and subFolders -/* -[ - 'specs/1.spec.ts', - 'specs/2.spec.ts', - 'specs/3.spec.ts', - 'specs/4.spec.ts', - 'specs/5.spec.ts', - 'specs/6.spec.ts', - 'specs/7.spec.ts', - 'specs/8.spec.ts', - 'specs/9.spec.ts' -] -*/ -// now we need commands array -const formCommand = (filePath) => `./node_modules/.bin/protractor ./protractor.conf.js --specs ${filePath}` -const commandsArray = specsArr.map(filePath) -/* -[ './node_modules/.bin/protractor ./protractor.conf.js --specs specs/1.spec.ts', - './node_modules/.bin/protractor ./protractor.conf.js --specs specs/2.spec.ts', - './node_modules/.bin/protractor ./protractor.conf.js --specs specs/3.spec.ts', - './node_modules/.bin/protractor ./protractor.conf.js --specs specs/4.spec.ts', - './node_modules/.bin/protractor ./protractor.conf.js --specs specs/5.spec.ts', - './node_modules/.bin/protractor ./protractor.conf.js --specs specs/6.spec.ts', - './node_modules/.bin/protractor ./protractor.conf.js --specs specs/7.spec.ts', - './node_modules/.bin/protractor ./protractor.conf.js --specs specs/8.spec.ts', - './node_modules/.bin/protractor ./protractor.conf.js --specs specs/9.spec.ts' ] -*/ +arguments | description +--- | --- +**`buildOpts`** | Type: `object`
Options for executor +**`buildOpts.maxThreads`** | Type: `number`,
How many threads can be executed in same time
**Default threads count is 5** +**`buildOpts.attemptsCount`** | Type: `number`,
How many times can we try to execute command for success result **in next cycle will be executed only faild command, success commands will not be reexecuted**
**Default attempts count is 2** +**`buildOpts.pollTime`** | Type: `number` ,
Period for recheck about free thread
**Default is 1 second** +**`buildOpts.logLevel`** | Type: `string`, one of 'ERROR', 'WARN', 'INFO', 'VERBOSE',
ERROR - only errors, WARN - errors and warnings, INFO - errors, warnings and information, VERBOSE - full logging
**Default is 'ERROR'** +**`buildOpts.currentExecutionVariable`** | Type: `string`, will be execution variable with execution index for every cycle will be ++
+**`buildOpts.everyCycleCallback`** | Type: `function`,
Optional. everyCycleCallback will be executed after cycle, before next execution cycle.
**Default is false** +**`buildOpts.processResultAnalyzer`** | Type: `function`,
Optional. processResultAnalyzer is a function where arguments are original command, execution stack trace and notRetriable array processResultAnalyzer should return a new command what will be executed in next cycle or **null** - if satisfactory result
+**`buildOpts.longestProcessTime`** | Type: `number`,
In case if command execution time is longer than longest Process Time - executor will kill it automatically and will try to execute this command again.
**Default time is 45 seconds** -// now we need runner -/* - getReruner(obj) params - @{everyCycleCallback} function, will execute after full cycle done, before next cycle - @{maxSessionCount} number, for example we have hub for 10 browsers, so maxSessionCount equal 10 - @{attemptsCount} number, hom many times will reruned failed processes - @{stackAnalize} function, if stack trace includes some math this process will not go to rerun scope -*/ -const cycleCB = () => console.log('Cycle done') -const stackAnalize = (stack) => !stack.includes('ASSERTION ERROR') +#### usage example -const runner = getReruner({ - everyCycleCallback: cycleCB, - maxSessionCount: 1, - attemptsCount: 3, - stackAnalize: stackAnalize, - debugProcess: processEnv.DEBUG_PROCESS - }) +```js +const {buildRunner} = require('process-rerun'); + +async function execCommands() { + const runner = buildRunner({ + maxThreads: 10, // ten threads + attemptsCount: 2, // will try to pass all commands two times, one main and one times rerun + longestProcessTime: 60 * 1000,// if command process execution time is longre than 1 minute will kill it and try to pass in next cycle + pollTime: 1000, // will check free thread every second + // @deprecated + debugProcess: true, // all information will be in console output + everyCycleCallback: () => console.log('Cycle done'), + processResultAnalyzer: (cmd, stackTrace, notRetriableArr) => { + if (stackTrace.includes('Should be re executed')) { + return cmd; + } + notRetriableArr.push(cmd) + }, //true - command will be reexecuted + }); + const result = await runner([ + `node -e 'console.log("Success first")'`, + `node -e 'console.log("Success second")'`, + `node -e 'console.log("Failed first"); process.exit(1)'`, + `node -e 'console.log("Success third")'`, + `node -e 'console.log("Failed second"); process.exit(1)'`, + ]) -getReruner().then((results) => console.log(results)) -// return array with failed processes + console.log(result); + /* + { + retriable: [ + `node -e 'console.log("Failed first"); process.exit(1)' --opt1=opt1value --opt1=opt1value`, + `node -e 'console.log("Failed second"); process.exit(1)' --opt1=opt1value --opt1=opt1value` + ], + notRetriable: [] + } + */ +} ``` +### getFilesList(dir: string; fileList?:array; directoryToSkip?: string[]|string|regex; ignoreSubDirs?: boolean)
returns array with paths to files -Note about command re-format functions. +arguments | description +--- | --- +**`dir`** | Type: `string` , *required*
Directory what will be used as a root +**`fileList`** | Type: `Array` ,
This array will be used as a target for push new file +**`directoryToSkip`** | Type: `Array|string|regex`,
Exlude some directory +**`ignoreSubDirs`** | Type: `boolean`,
In case of true - sub directories will be ignored -There are two command re-format functions, that you can pass to __getReruner__. -```js -const formCommanWithOption = (cmd) => { - return { - cmd: `${cmd} --someArgument=value --beforeRunFlag`, - cmdExecutableCB: () => { /* make something specific after command has run */ } - } -} +#### usage exampele -const reformatCommand = (cmd) => `${cmd} --someArgument=value --afterFirstRunFlag` +```js +const {getFilesList} = require('process-rerun'); -const runner = getReruner({ - formCommanWithOption, - reformatCommand, - /* other arguments */ - }) +const readmePath = getFilesList(__dirname).find((filePath) => filePath.include('readme.md')); ``` -__formCommanWithOption__ - will help you to run your commands on some specific environment, -with some specific flags, etc. Also it allows you to execute some callback, after your command has run. - -__reformatCommand__ - in another hand, allows you to add some specific options to command -after it failed during first execution diff --git a/rerun.js b/rerun.js deleted file mode 100644 index a41d001..0000000 --- a/rerun.js +++ /dev/null @@ -1,13 +0,0 @@ - -const {getFilesList, buildExeRun} = require('./lib') - -module.exports = { - getReruner: function(optsObj) { - return buildExeRun(optsObj) - }, - getFilesList, - getSpecFilesArr: getFilesList, - getSpecCommands: function(pathToSpecDir, getRunCommandPattern) { - return walkSync(pathToSpecDir).map(getRunCommandPattern) - } -} \ No newline at end of file diff --git a/specs/1.spec.ts b/specs/1.spec.ts deleted file mode 100644 index 73035c0..0000000 --- a/specs/1.spec.ts +++ /dev/null @@ -1,13 +0,0 @@ - -import { browser } from 'protractor' -import { BasePo } from '../po/base.po' -import { expect } from 'chai' - -describe('Spec 1 describe', () => { - beforeEach(async () => browser.get('https://google.com')) - - it(`Spec 1 it`, async () => { - await new BasePo().setInput('#lst-ib', 'test spec 1') - expect('true').to.eql([]) - }) -}) diff --git a/specs/2.spec.ts b/specs/2.spec.ts deleted file mode 100644 index 3d63150..0000000 --- a/specs/2.spec.ts +++ /dev/null @@ -1,20 +0,0 @@ - -import { browser, $, ExpectedConditions as EC } from 'protractor' -import { expect } from 'chai' -// import { attachFile } from '../util/attachFile' -import { BasePo } from '../po/base.po' -const sleep = (timeMs) => new Promise(res => setTimeout(res, timeMs)) - -require('protractor/built/logger').Logger.prototype.info - -describe('Spec 2 describe', () => { - - beforeEach(async () => browser.get('https://google.com')) - - it(`Spec 2 it`, async () => { - await sleep(5000) - await new BasePo().setInput('#lst-ibDSADA', 'test spec 2') - expect('true').to.eql(true) - }) -}) - diff --git a/specs/3.spec.ts b/specs/3.spec.ts deleted file mode 100644 index 0ffac71..0000000 --- a/specs/3.spec.ts +++ /dev/null @@ -1,25 +0,0 @@ - -import { browser, $, ExpectedConditions as EC } from 'protractor' -import { expect } from 'chai' -import { BasePo } from '../po/base.po' - -const sleep = (timeMs) => new Promise(res => setTimeout(res, timeMs)) -describe('Spec 3 describe', () => { - - beforeEach(async () => browser.get('https://google.com')) - - it(`Spec 3 it first `, async () => { - await sleep(15000) - await new BasePo().setInput('#lst-ib', 'test spec 3') - }) - - it(`Spec 3 it second`, async () => { - await sleep(500) - await new BasePo().setInput('#lst-ib', 'test spec 3') - }) - - it(`Spec 3 it third`, async () => { - await sleep(2500) - await new BasePo().setInput('#lst-ib', 'test spec 3') - }) -}) diff --git a/specs/4.spec.ts b/specs/4.spec.ts deleted file mode 100644 index a9256ed..0000000 --- a/specs/4.spec.ts +++ /dev/null @@ -1,25 +0,0 @@ - -import { browser, $, ExpectedConditions as EC } from 'protractor' -import { expect } from 'chai' -import { BasePo } from '../po/base.po' - -const sleep = (timeMs) => new Promise(res => setTimeout(res, timeMs)) - -describe('Spec 4 describe', () => { - - beforeEach(async () => browser.get('https://google.com')) - - it(`Spec 4 it first`, async function() { - await sleep(10000) - this.retries(4) - expect('true').to.eql({}) - await new BasePo().setInput('#lst-ib', 'test spec 4') - }) - - it(`Spec 4 it second`, async function() { - await sleep(10000) - this.retries(4) - await new BasePo().setInput('#lst-ib', 'test spec 4') - expect('true').to.eql({}) - }) -}) diff --git a/specs/5.spec.ts b/specs/5.spec.ts deleted file mode 100644 index ca84471..0000000 --- a/specs/5.spec.ts +++ /dev/null @@ -1,13 +0,0 @@ - -import { browser, $, ExpectedConditions as EC } from 'protractor' -import { expect } from 'chai' -import { BasePo } from '../po/base.po' -describe('Spec 5 describe', () => { - - beforeEach(async () => browser.get('https://google.com')) - - it(`Spec 5 it`, async () => { - await new BasePo().setInput('#lst-ib', 'test spec 5') - expect('true').to.eql({ 1: 1 }) - }) -}) diff --git a/specs/6.spec.ts b/specs/6.spec.ts deleted file mode 100644 index a64b5a3..0000000 --- a/specs/6.spec.ts +++ /dev/null @@ -1,17 +0,0 @@ - -import { browser, $, ExpectedConditions as EC } from 'protractor' -import { expect } from 'chai' -import { BasePo } from '../po/base.po' - -describe('Spec 6 describe', () => { - - beforeEach(async () => browser.get('https://google.com')) - - it(`Spec 7 it`, async () => { - const sleep = (timeMs) => new Promise(res => setTimeout(res, timeMs)) - await sleep(1000) - await new BasePo().setInput('#lst-ib', 'test spec 5') - expect('true').to.eql({ 1: 112321241240129940219 }) - }) -}) - diff --git a/specs/7.spec.ts b/specs/7.spec.ts deleted file mode 100644 index c055059..0000000 --- a/specs/7.spec.ts +++ /dev/null @@ -1,15 +0,0 @@ - -import { browser, $, ExpectedConditions as EC } from 'protractor' -import { expect } from 'chai' -import { BasePo } from '../po/base.po' - -describe('Spec 7 describe', () => { - - beforeEach(async () => browser.get('https://google.com')) - - it(`Spec 8 it`, async () => { - await new BasePo().setInput('#lst-ib', 'test spec 5') - expect('true').to.eql({ test___111: 112321241240129940219 }) - }) -}) - diff --git a/specs/8.spec.ts b/specs/8.spec.ts deleted file mode 100644 index 624de24..0000000 --- a/specs/8.spec.ts +++ /dev/null @@ -1,17 +0,0 @@ - -import { browser, $, ExpectedConditions as EC } from 'protractor' -import { expect } from 'chai' -import { BasePo } from '../po/base.po' - -describe('Spec 8 describe', () => { - - beforeEach(async () => browser.get('https://google.com')) - - it(`Spec 8 it`, async () => { - const sleep = (timeMs) => new Promise(res => setTimeout(res, timeMs)) - await sleep(18000) - await new BasePo().setInput('#lst-ib', 'test spec 5') - expect(true).to.eql({ test___111: 112321241240129940219 }) - }) -}) - diff --git a/specs/9.spec.ts b/specs/9.spec.ts deleted file mode 100644 index 94db56b..0000000 --- a/specs/9.spec.ts +++ /dev/null @@ -1,23 +0,0 @@ - -import { browser, $, ExpectedConditions as EC } from 'protractor' -import { expect } from 'chai' -import { BasePo } from '../po/base.po' - -describe('Spec 9 describe', () => { - - beforeEach(async () => browser.get('https://google.com')) - - it(`Spec 9 it first`, async () => { - const sleep = (timeMs) => new Promise(res => setTimeout(res, timeMs)) - await sleep(20000) - await new BasePo().setInput('#lst-ibdsada', 'test spec 9') - expect([true, false, true]).to.eql({ test___111: 112321241240129940219 }) - }) - - it(`Spec 9 it second`, async () => { - const sleep = (timeMs) => new Promise(res => setTimeout(res, timeMs)) - await sleep(21) - await new BasePo().setInput('#lst-ibdsada', 'test spec 9') - expect([true, false, true]).to.eql({ test___111: 112321241240129940219 }) - }) -}) diff --git a/specs/exec.index.spec.ts b/specs/exec.index.spec.ts new file mode 100644 index 0000000..9107933 --- /dev/null +++ b/specs/exec.index.spec.ts @@ -0,0 +1,35 @@ +import {expect} from 'assertior'; +import {execute} from '../lib/exec'; +import {sleep} from '../lib/helpers'; +import {ProcessRerunError} from '../lib/error'; + +function wrap(...args) { + // @ts-ignore + return execute(...args); +} + +test('[P] exec', async function() { + const holder = {stackTrace: ''}; + + async function waiter() { + do { + await sleep(1) + } while (!holder.stackTrace); + } + + wrap(`node -e 'console.log("Hello")'`, holder); + // + await waiter() + + expect(holder.stackTrace).stringIncludesSubstring('Hello'); +}); + +test('[N] exec', async function() { + const holder = {stackTrace: ''}; + try { + wrap({}, holder); + } catch (error) { + expect(error instanceof ProcessRerunError).toEqual(true); + } +}); + diff --git a/specs/helpers.spec.ts b/specs/helpers.spec.ts new file mode 100644 index 0000000..8e0bdbb --- /dev/null +++ b/specs/helpers.spec.ts @@ -0,0 +1,43 @@ +import {expect} from 'assertior'; +import * as path from 'path'; +import {getPollTime, returnStringType, getFilesList} from '../lib/helpers'; +import {getSpecFilesArr} from '../lib'; +import {ProcessRerunError} from '../lib/error'; + +test('[P] getPollTime', function() { + expect(getPollTime(1)).toEqual(1); + expect(getPollTime('')).toEqual(1000); + expect(getPollTime(NaN)).toEqual(1000); + expect(getPollTime(Infinity)).toEqual(1000); +}); + +test('[P] returnStringType', function() { + expect(returnStringType('')).toEqual('[object String]') +}); + +test('[P] getFilesList', function() { + expect(getFilesList(__dirname)).toBeNotEmptyArray(); +}); + +test('[P] getFilesList ignoreSubDirs', function() { + expect(getFilesList( + path.resolve(__dirname, '../node_modules'), + [], + null, + true + )).toBeEmptyArray(); +}); + +test('[P] getSpecFilesArr', function() { + expect(getSpecFilesArr( + path.resolve(__dirname, '../node_modules'), + )).toBeNotEmptyArray(); +}); + +test('[N] getFilesList', function() { + try { + getFilesList('/dir/does/not_exists/for_sure'); + } catch (error) { + expect(error instanceof ProcessRerunError).toEqual(true); + } +}); \ No newline at end of file diff --git a/specs/index.spec.ts b/specs/index.spec.ts new file mode 100644 index 0000000..a0104af --- /dev/null +++ b/specs/index.spec.ts @@ -0,0 +1,72 @@ +import {expect} from 'assertior'; +import {buildRunner, getReruner} from '../lib'; + +test('[p] longestProcessTime kill procs', async function() { + const commands = [ + `node -e 'setTimeout(() => console.log("Success first"), 5000)'`, + `node -e 'setTimeout(() => console.log("Success second"), 5000)'` + ]; + const runner = buildRunner({ + maxThreads: 4, + attemptsCount: 1, + longestProcessTime: 1000, + pollTime: 10 + }); + const result = await runner(commands); + expect(result.notRetriable).toDeepEqual([]); + expect(result.retriable).toDeepEqual(commands); +}); + +test('[p] longestProcessTime ', async function() { + const commands = [ + `node -e 'setTimeout(() => console.log("Success first"), 5000)'`, + `node -e 'setTimeout(() => console.log("Success second"), 5000)'` + ]; + const runner = getReruner({ + maxThreads: 4, + attemptsCount: 1, + longestProcessTime: 10000, + pollTime: 10 + }); + const result = await runner(commands); + expect(result.notRetriable).toDeepEqual([]); + expect(result.retriable).toDeepEqual([]); + expect(result.retriable).toNotDeepEqual(commands); + expect(result.retriable.length).toEqual(0); +}); + +test('[p] longestProcessTime kill processResultAnalyzer return null notRetriable auto push', async function() { + const commands = [ + `node -e 'setTimeout(() => console.log("Success first"), 5000)'`, + `node -e 'setTimeout(() => console.log("Success second"), 5000)'` + ]; + const runner = buildRunner({ + maxThreads: 4, + attemptsCount: 1, + longestProcessTime: 10, + processResultAnalyzer: () => null, + pollTime: 10 + }); + const result = await runner(commands); + expect(result.notRetriable).toDeepEqual(commands); + expect(result.retriable).toDeepEqual([]); + expect(result.retriable).toNotDeepEqual(commands); + expect(result.retriable.length).toEqual(0); +}); + +test('[p] longestProcessTime kill processResultAnalyzer return cmd notRetriable auto push', async function() { + const commands = [ + `node -e 'setTimeout(() => console.log("Success first"), 5000)'`, + `node -e 'setTimeout(() => console.log("Success second"), 5000)'` + ]; + const runner = buildRunner({ + maxThreads: 4, + attemptsCount: 1, + longestProcessTime: 10, + processResultAnalyzer: (cmd) => cmd, + pollTime: 10 + }); + const result = await runner(commands); + expect(result.notRetriable).toDeepEqual([]); + expect(result.retriable).toDeepEqual(commands); +}); \ No newline at end of file diff --git a/specs/logger.spec.ts b/specs/logger.spec.ts new file mode 100644 index 0000000..e4b1071 --- /dev/null +++ b/specs/logger.spec.ts @@ -0,0 +1,123 @@ +import {expect} from 'assertior'; +import {logger, setLogLevel} from '../lib/logger'; + +function mockConsoleApi() { + let callLog = 0; + let callInfo = 0; + let callWarn = 0; + let callErr = 0; + + const orLog = console.log; + const orInfo = console.info; + const orWarn = console.warn; + const orErr = console.error; + + console.log = function() { + callLog++; + }; + + console.info = function() { + callInfo++; + } + + console.warn = function() { + callWarn++; + } + + console.error = function() { + callErr++; + } + + return { + restore() { + console.log = orLog.bind(console); + console.info = orInfo.bind(console); + console.warn = orWarn.bind(console); + console.error = orErr.bind(console); + }, + getLogCalls: () => callLog, + getInfoCalls: () => callInfo, + getErrCalls: () => callErr, + getWarnCalls: () => callWarn, + } +} + +test('[P] ERROR', function() { + const mocked = mockConsoleApi(); + setLogLevel('ERROR'); + logger.log(1); + logger.info(1); + logger.warn(1); + logger.error(1); + expect(mocked.getErrCalls()).toEqual(1); + expect(mocked.getWarnCalls()).toEqual(0); + expect(mocked.getInfoCalls()).toEqual(0); + expect(mocked.getLogCalls()).toEqual(0); + mocked.restore(); +}); + +test('[P] WARNING', function() { + const mocked = mockConsoleApi(); + setLogLevel('WARN'); + logger.log(1); + logger.info(1); + logger.warn(1); + logger.error(1); + expect(mocked.getErrCalls()).toEqual(1); + expect(mocked.getWarnCalls()).toEqual(1); + expect(mocked.getInfoCalls()).toEqual(0); + expect(mocked.getLogCalls()).toEqual(0); + mocked.restore(); +}); + +test('[P] INFO', function() { + const mocked = mockConsoleApi(); + setLogLevel('INFO'); + logger.log(1); + logger.info(1); + logger.warn(1); + logger.error(1); + expect(mocked.getErrCalls()).toEqual(1); + expect(mocked.getWarnCalls()).toEqual(1); + expect(mocked.getInfoCalls()).toEqual(1); + expect(mocked.getLogCalls()).toEqual(0); + mocked.restore(); +}); + +test('[P] VERBOSE', function() { + const mocked = mockConsoleApi(); + setLogLevel('VERBOSE'); + logger.log(1); + logger.info(1); + logger.warn(1); + logger.error(1); + expect(mocked.getErrCalls()).toEqual(1); + expect(mocked.getWarnCalls()).toEqual(1); + expect(mocked.getInfoCalls()).toEqual(1); + expect(mocked.getLogCalls()).toEqual(1); + mocked.restore(); +}); + +test.only('[P] SWHITCH LOG LEVELS', function() { + const mocked = mockConsoleApi(); + setLogLevel('ERROR'); + logger.log(1); + logger.info(1); + logger.warn(1); + logger.error(1); + expect(mocked.getErrCalls()).toEqual(1); + expect(mocked.getWarnCalls()).toEqual(0); + expect(mocked.getInfoCalls()).toEqual(0); + expect(mocked.getLogCalls()).toEqual(0); + setLogLevel('VERBOSE'); + logger.log(1); + logger.info(1); + logger.warn(1); + logger.error(1); + expect(mocked.getErrCalls()).toEqual(2); + expect(mocked.getWarnCalls()).toEqual(1); + expect(mocked.getInfoCalls()).toEqual(1); + expect(mocked.getLogCalls()).toEqual(1); + + mocked.restore(); +}); diff --git a/tsconfig.json b/tsconfig.json index 0289b1b..cae024a 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,17 +1,19 @@ { "compilerOptions": { "module": "commonjs", - "target": "es2017", + "target": "es2018", "sourceMap": true, - "mapRoot": "built", "outDir": "built", - "experimentalDecorators": true, - "lib": [ - "dom", - "es2017" - ] + "declaration": true, + "experimentalDecorators": true }, + "include": [ + "lib" + ], "exclude": [ - "node_modules" + "node_modules", + "built", + "specs", + "example" ] } \ No newline at end of file diff --git a/unit_specs/exec/commandExecutorBuilder.spec.js b/unit_specs/exec/commandExecutorBuilder.spec.js deleted file mode 100644 index 5e846ae..0000000 --- a/unit_specs/exec/commandExecutorBuilder.spec.js +++ /dev/null @@ -1,161 +0,0 @@ -const {expect} = require('chai') - -const {buildCommandExecutor} = require('../../lib/commandExecutorBuilder') - -describe('buildCommandExecutor', () => { - - it('addSpecificOptionsBeforeRun', async () => { - { - const cmd = 'node -e "console.log(\'test\')"' - const failedByAssert = [] - let holder = null - - const addSpecificOptionsBeforeRun = (cmd) => { - const reformatedCmd = `${cmd} && echo "FOO"` - holder = {cmd: reformatedCmd} - const cmdExecutableCB = () => {holder.exec = true} - return {cmd, cmdExecutableCB} - } - - const executeCommandAsync = buildCommandExecutor(failedByAssert, {addSpecificOptionsBeforeRun}) - await executeCommandAsync(cmd) - expect(holder.cmd).to.eq(`${cmd} && echo "FOO"`) - expect(holder.exec).to.eq(undefined) - } - { - const cmd = 'node -e "console.log(\'test\'); process.exit(100)"' - const failedByAssert = [] - let holder = null - - const addSpecificOptionsBeforeRun = (cmd) => { - const reformatedCmd = `${cmd} && echo "FOO"` - holder = {cmd: reformatedCmd} - const cmdExecutableCB = () => {holder.exec = true} - return {cmd, cmdExecutableCB} - } - - const executeCommandAsync = buildCommandExecutor(failedByAssert, {addSpecificOptionsBeforeRun}) - await executeCommandAsync(cmd) - expect(holder.cmd).to.eq(`${cmd} && echo "FOO"`) - expect(holder.exec).to.eq(true) - } - }) - - it('reformatCommand', async () => { - const failedByAssert = [] - let holder = null - - const cmd = `node -e "console.log('test'); process.exit(1)"` - - const reformatCommand = (cmd, stack) => { - holder = {} - holder.cmd = cmd - holder.stack = stack - } - const executeCommandAsync = buildCommandExecutor(failedByAssert, {reformatCommand}) - await executeCommandAsync(cmd) - expect(holder.cmd).to.eq(cmd) - expect(holder.stack).to.eql('test\n') - }) - - it('stackAnalize', async () => { - const failedByAssert = [] - let holder = null - const cmd = `node -e "console.log('test'); process.exit(1)"` - - const reformatCommand = (cmd, stack) => { - if(!holder) holder = {} - holder.cmd = cmd - holder.stack = stack - } - const stackAnalize = (stack) => { - holder = {} - holder.fromStackAnalize = stack - return false - } - const executeCommandAsync = buildCommandExecutor(failedByAssert, { - reformatCommand, stackAnalize - }) - await executeCommandAsync(cmd) - expect(holder.fromStackAnalize).to.eq('test\n') - expect(holder.cmd).to.eq(cmd) - expect(holder.stack).to.eq('test\n') - }) - - it('longestProcessTime', async () => { - { - const failedByAssert = [] - let holder = null - const stackAnalize = (stack) => { - holder = {} - holder.fromStackAnalize = stack - return false - } - const cmd = `node -e "(async function() {await new Promise((res) => setTimeout(() => { - res(process.exit(1)) - }, 25000))})()"` - - const executeCommandAsync = buildCommandExecutor(failedByAssert, {stackAnalize, longestProcessTime: 1}) - await executeCommandAsync(cmd) - expect(holder).not.to.eq(null) - } - { - const failedByAssert = [] - let holder = null - const stackAnalize = (stack) => { - holder = {} - holder.fromStackAnalize = stack - return false - } - const cmd = `node -e "(async function() {await new Promise((res) => setTimeout(() => { - res(process.exit(1)) - }, 1))})()"` - - const executeCommandAsync = buildCommandExecutor(failedByAssert, {stackAnalize, longestProcessTime: 10000}) - await executeCommandAsync(cmd) - expect(holder).to.not.eq(null) - } - }) - - it('currentExecutionVariable', async () => { - const failedByAssert = [] - let holder = null - const cmd = `node -e "console.log('test'); process.exit(1)"` - - const stackAnalize = (stack) => { - holder = {} - holder.fromStackAnalize = stack - return false - } - - const reformatCommand = (cmd, stack) => { - if(!holder) holder = {} - holder.cmd = cmd - holder.stack = stack - } - const currentExecutionVariable = 'CURRENT_COUNT' - - const executeCommandAsync = buildCommandExecutor(failedByAssert, { - stackAnalize, - reformatCommand, - currentExecutionVariable - }) - - const expetedReformatedCommandFirstIteration = `${currentExecutionVariable}=${1} ${cmd}` - const expetedReformatedCommandSecondIteration = `${currentExecutionVariable}=${2} ${cmd}` - - // first iteration - await executeCommandAsync(cmd, 1) - - expect(holder.fromStackAnalize).to.eq('test\n') - expect(holder.cmd).to.eq(expetedReformatedCommandFirstIteration) - expect(holder.stack).to.eq('test\n') - - await executeCommandAsync(expetedReformatedCommandFirstIteration, 2) - - expect(holder.fromStackAnalize).to.eq('test\n') - expect(holder.cmd).to.eq(expetedReformatedCommandSecondIteration) - expect(holder.stack).to.eq('test\n') - - }) -}) \ No newline at end of file diff --git a/unit_specs/exec/helper.spec.js b/unit_specs/exec/helper.spec.js deleted file mode 100644 index 3e6d4de..0000000 --- a/unit_specs/exec/helper.spec.js +++ /dev/null @@ -1,14 +0,0 @@ -const {getPollTime, getFormedRunCommand} = require('../../lib/helpers') -const {expect} = require('chai') -const path = require('path') - -describe('helper functions ', () => { - it('getPollTime', () => { - expect(getPollTime('das')).to.eq(1000) - expect(getPollTime(null)).to.eq(1000) - expect(getPollTime({})).to.eq(1000) - expect(getPollTime()).to.eq(1000) - expect(getPollTime(1)).to.eq(1) - expect(getPollTime([])).to.eq(1000) - }) -}) diff --git a/unit_specs/exec/index.spec.js b/unit_specs/exec/index.spec.js deleted file mode 100644 index 29dab61..0000000 --- a/unit_specs/exec/index.spec.js +++ /dev/null @@ -1,101 +0,0 @@ -const {expect} = require('chai') -const {buildExeRun} = require('../../lib') - -describe('kernel', () => { - it('buildExeRun basic execution', async () => { - // all negative - { - const cmd = `node -e "console.log('test'); process.exit(1)"` - const cmds = [cmd, cmd, cmd] - const reRunner = buildExeRun() - const failedCmds = await reRunner(cmds) - expect(failedCmds.failedByAssert.length).to.eq(3) - } - // all positive - { - const cmd = `node -e "console.log('test')"` - const cmds = [cmd, cmd, cmd] - const reRunner = buildExeRun() - const failedCmds = await reRunner(cmds) - expect(failedCmds.failedByAssert.length).to.eq(0) - expect(failedCmds.failedCommands.length).to.eq(0) - } - }) - it('buildExeRun currentExecutionVariable', async () => { - // all negative - { - const cmd = `node -e "console.log('test'); process.exit(1)"` - const cmds = [cmd] - const reRunner = buildExeRun({currentExecutionVariable: 'CURRENT_EXECUTION_COUNT'}) - const failedCmds = await reRunner(cmds) - expect(failedCmds.failedByAssert.length).to.eq(1) - expect(failedCmds.failedCommands.every((failedCmd) => failedCmd.includes('CURRENT_EXECUTION_COUNT=0'))).to.eq(true) - } - // + stackAnalize - { - const cmd = `node -e "console.log('test'); process.exit(1)"` - const stackAnalize = () => true - const attemptsCount = 15 - const cmds = [cmd] - const reRunner = buildExeRun({ - stackAnalize, - attemptsCount, - currentExecutionVariable: 'CURRENT_EXECUTION_COUNT' - }) - const failedCmds = await reRunner(cmds) - expect(failedCmds.failedCommands.length).to.eq(1) - expect(failedCmds.failedCommands.every((failedCmd) => failedCmd.includes(`CURRENT_EXECUTION_COUNT=${attemptsCount - 1}`))).to.eq(true) - } - }) - it('formCommanWithOption', async () => { - let holder = null - const cmd = `node -e "console.log('test'); process.exit(1)"` - const stackAnalize = () => true - const attemptsCount = 2 - const formCommanWithOption = (cmd) => { - return { - cmd: `TEST_ENV=test ${cmd}`, - cmdExecutableCB: () => holder = true - } - } - const cmds = [cmd] - const reRunner = buildExeRun({ - stackAnalize, - formCommanWithOption, - attemptsCount - }) - const failedCmds = await reRunner(cmds) - expect(failedCmds.failedByAssert).to.eql([]) - expect(failedCmds.failedCommands).to.eql([`${cmd}`]) - expect(holder).to.eq(true) - }) - - it('failedByAssert', async () => { - { - const cmd = `node -e "console.log('test'); process.exit(1)"` - const cmds = [cmd, cmd, cmd] - const reRunner = buildExeRun() - const result = await reRunner(cmds) - expect(result.failedByAssert.length).to.eq(3) - expect(result.failedCommands.length).to.eq(0) - } - { - const cmd = `node -e "console.log('test'); process.exit(0)"` - const cmds = [cmd, cmd, cmd] - const reRunner = buildExeRun() - const result = await reRunner(cmds) - expect(result.failedByAssert.length).to.eq(0) - expect(result.failedCommands.length).to.eq(0) - } - { - const cmd = `node -e "console.log('test'); process.exit(1)"` - const cmdToRerrun = `node -e "console.log('should be rerruned'); process.exit(1)"` - const cmds = [cmd, cmd, cmdToRerrun] - const stackAnalize = (stack) => stack.includes('should be rerruned') - const reRunner = buildExeRun({stackAnalize}) - const result = await reRunner(cmds) - expect(result.failedByAssert.length).to.eq(2) - expect(result.failedCommands.length).to.eq(1) - } - }) -}) \ No newline at end of file diff --git a/unit_specs/spawn/commandExecutorBuilder.spec.js b/unit_specs/spawn/commandExecutorBuilder.spec.js deleted file mode 100644 index 83e5e4c..0000000 --- a/unit_specs/spawn/commandExecutorBuilder.spec.js +++ /dev/null @@ -1,144 +0,0 @@ -const {expect} = require('chai') - -const {buildCommandExecutor} = require('../../lib/commandExecutorBuilder') - -describe('buildCommandExecutor spawn', () => { - - it('addSpecificOptionsBeforeRun', async () => { - { - const cmd = {command: 'node', args: ['-e', "console.log(\'test\')"]} - const failedByAssert = [] - let holder = null - - const addSpecificOptionsBeforeRun = (cmd) => { - cmd.args.push('&&', 'echo', '"FOO"') - holder = {} - holder.cmd = {...cmd} - const cmdExecutableCB = () => {holder.exec = true} - return {cmd, cmdExecutableCB} - } - - const executeCommandAsync = buildCommandExecutor(failedByAssert, {spawn: true, addSpecificOptionsBeforeRun}) - await executeCommandAsync(cmd) - expect(holder.cmd.args).to.contains('&&') - expect(holder.cmd.args).to.contains('echo') - expect(holder.cmd.args).to.contains('"FOO"') - expect(holder.exec).to.eq(undefined) - } - { - const cmd = {command: 'node', args: ['-e', "console.log(\'test\'); process.exit(100)"]} - const failedByAssert = [] - let holder = null - - const addSpecificOptionsBeforeRun = (cmd) => { - cmd.args.push('&&', 'echo', '"FOO"') - holder = {} - holder.cmd = {...cmd} - const cmdExecutableCB = () => {holder.exec = true} - return {cmd, cmdExecutableCB} - } - - const executeCommandAsync = buildCommandExecutor(failedByAssert, {spawn: true, addSpecificOptionsBeforeRun}) - await executeCommandAsync(cmd) - expect(holder.cmd.args).to.contains('&&') - expect(holder.cmd.args).to.contains('echo') - expect(holder.cmd.args).to.contains('"FOO"') - expect(holder.exec).to.eq(true) - } - }) - - it('reformatCommand', async () => { - const failedByAssert = [] - let holder = null - - const cmd = {command: 'node', args: ['-e', "console.log(\'test\'); process.exit(1)"]} - - const reformatCommand = (cmd, stack) => { - holder = {} - holder.cmd = cmd - holder.stack = stack - } - - const executeCommandAsync = buildCommandExecutor(failedByAssert, {spawn: true, reformatCommand}) - await executeCommandAsync(cmd) - expect(holder.cmd).to.eq(cmd) - expect(holder.stack).to.eql('test\n') - }) - - it('stackAnalize', async () => { - const failedByAssert = [] - let holder = null - - const cmd = {command: 'node', args: ['-e', "console.log('test'); process.exit(1)"]} - - const reformatCommand = (cmd, stack) => { - if(!holder) holder = {} - holder.cmd = cmd - holder.stack = stack - } - - const stackAnalize = (stack) => { - holder = {} - holder.fromStackAnalize = stack - return false - } - const executeCommandAsync = buildCommandExecutor(failedByAssert, { - spawn: true, - reformatCommand, - stackAnalize - }) - await executeCommandAsync(cmd) - expect(holder.fromStackAnalize).to.eq('test\n') - expect(holder.cmd).to.eq(cmd) - expect(holder.stack).to.eq('test\n') - }) - - it('longestProcessTime', async () => { - { - const failedByAssert = [] - let holder = null - const stackAnalize = (stack) => { - holder = {} - holder.fromStackAnalize = stack - return false - } - const cmd = { - command: 'node', args: ['-e', `(async function() {await new Promise((res) => setTimeout(() => { - res(process.exit(1)) - }, 25000))})()` - ] - } - - const executeCommandAsync = buildCommandExecutor(failedByAssert, { - spawn: true, - stackAnalize, - longestProcessTime: 1 - }) - await executeCommandAsync(cmd) - expect(holder).to.eql({fromStackAnalize: ''}) - } - { - const failedByAssert = [] - let holder = null - const stackAnalize = (stack) => { - holder = {} - holder.fromStackAnalize = stack - return false - } - const cmd = { - command: 'node', args: ['-e', `(async function() {await new Promise((res) => setTimeout(() => { - res(process.exit(1)) - }, 25000))})()` - ] - } - - const executeCommandAsync = buildCommandExecutor(failedByAssert, { - spawn: true, - stackAnalize, - longestProcessTime: 10000 - }) - await executeCommandAsync(cmd) - expect(holder).to.not.eq(null) - } - }) -}) \ No newline at end of file diff --git a/util/attachFile.ts b/util/attachFile.ts deleted file mode 100644 index 278d865..0000000 --- a/util/attachFile.ts +++ /dev/null @@ -1,8 +0,0 @@ -import * as path from 'path' - -declare const allure: any - -export const attachFile = () => { - allure.addArgument('File path', path.basename(__filename)) - process.stdout.write('FAIL') -}