From 96a7f69d3339b29de3136bc0efdb27a755c63d38 Mon Sep 17 00:00:00 2001 From: Victor Savkin Date: Mon, 15 Jun 2020 14:08:19 -0400 Subject: [PATCH] feat(core): add second way of capturing output --- e2e/jest/src/jest.test.ts | 16 +++- e2e/next/src/next.test.ts | 4 +- e2e/node/src/node.test.ts | 24 +++-- e2e/react/src/react.test.ts | 16 +++- e2e/utils/index.ts | 8 +- e2e/web/src/web.test.ts | 4 +- .../src/tasks-runner/task-orchestrator.ts | 88 +++++++++++++++++-- 7 files changed, 135 insertions(+), 25 deletions(-) diff --git a/e2e/jest/src/jest.test.ts b/e2e/jest/src/jest.test.ts index 077b8515a6..e570fa5035 100644 --- a/e2e/jest/src/jest.test.ts +++ b/e2e/jest/src/jest.test.ts @@ -26,9 +26,13 @@ forEachCli(() => { runCLIAsync(`generate @nrwl/angular:component test --project ${mylib}`), ]); const appResult = await runCLIAsync(`test ${myapp} --no-watch`); - expect(appResult.stderr).toContain('Test Suites: 3 passed, 3 total'); + expect(appResult.combinedOutput).toContain( + 'Test Suites: 3 passed, 3 total' + ); const libResult = await runCLIAsync(`test ${mylib}`); - expect(libResult.stderr).toContain('Test Suites: 3 passed, 3 total'); + expect(libResult.combinedOutput).toContain( + 'Test Suites: 3 passed, 3 total' + ); done(); }, 45000); @@ -66,7 +70,9 @@ forEachCli(() => { ); const appResult = await runCLIAsync(`test ${mylib} --no-watch`); - expect(appResult.stderr).toContain('Test Suites: 1 passed, 1 total'); + expect(appResult.combinedOutput).toContain( + 'Test Suites: 1 passed, 1 total' + ); done(); }, 45000); @@ -84,7 +90,9 @@ forEachCli(() => { ` ); const appResult = await runCLIAsync(`test ${mylib} --no-watch`); - expect(appResult.stderr).toContain('Test Suites: 1 passed, 1 total'); + expect(appResult.combinedOutput).toContain( + 'Test Suites: 1 passed, 1 total' + ); done(); }, 45000); }); diff --git a/e2e/next/src/next.test.ts b/e2e/next/src/next.test.ts index ee3f91a672..333d09fbba 100644 --- a/e2e/next/src/next.test.ts +++ b/e2e/next/src/next.test.ts @@ -232,7 +232,9 @@ async function checkApp( } const testResults = await runCLIAsync(`test ${appName}`); - expect(testResults.stderr).toContain('Test Suites: 1 passed, 1 total'); + expect(testResults.combinedOutput).toContain( + 'Test Suites: 1 passed, 1 total' + ); if (supportUi()) { const e2eResults = runCLI(`e2e ${appName}-e2e --headless`); diff --git a/e2e/node/src/node.test.ts b/e2e/node/src/node.test.ts index 5ab28de143..bb846b5a6b 100644 --- a/e2e/node/src/node.test.ts +++ b/e2e/node/src/node.test.ts @@ -82,7 +82,9 @@ forEachCli((currentCLIName) => { updateFile(`apps/${nodeapp}/src/assets/file.txt`, ``); const jestResult = await runCLIAsync(`test ${nodeapp}`); - expect(jestResult.stderr).toContain('Test Suites: 1 passed, 1 total'); + expect(jestResult.combinedOutput).toContain( + 'Test Suites: 1 passed, 1 total' + ); await runCLIAsync(`build ${nodeapp}`); checkFilesExist( @@ -192,7 +194,9 @@ forEachCli((currentCLIName) => { updateFile(`apps/${nestapp}/src/assets/file.txt`, ``); const jestResult = await runCLIAsync(`test ${nestapp}`); - expect(jestResult.stderr).toContain('Test Suites: 2 passed, 2 total'); + expect(jestResult.combinedOutput).toContain( + 'Test Suites: 2 passed, 2 total' + ); await runCLIAsync(`build ${nestapp}`); @@ -256,7 +260,9 @@ forEachCli((currentCLIName) => { expect(lintResults).toContain('All files pass linting.'); const jestResult = await runCLIAsync(`test ${nodelib}`); - expect(jestResult.stderr).toContain('Test Suites: 1 passed, 1 total'); + expect(jestResult.combinedOutput).toContain( + 'Test Suites: 1 passed, 1 total' + ); }, 60000); it('should be able to generate a publishable node library', async () => { @@ -358,7 +364,9 @@ forEachCli((currentCLIName) => { expect(lintResults).toContain('All files pass linting.'); const jestResult = await runCLIAsync(`test ${nestlib}`); - expect(jestResult.stderr).toContain('Test Suites: 1 passed, 1 total'); + expect(jestResult.combinedOutput).toContain( + 'Test Suites: 1 passed, 1 total' + ); }, 60000); it('should be able to generate a nest library w/ controller', async () => { @@ -371,7 +379,9 @@ forEachCli((currentCLIName) => { expect(lintResults).toContain('All files pass linting.'); const jestResult = await runCLIAsync(`test ${nestlib}`); - expect(jestResult.stderr).toContain('Test Suites: 1 passed, 1 total'); + expect(jestResult.combinedOutput).toContain( + 'Test Suites: 1 passed, 1 total' + ); }, 60000); it('should be able to generate a nest library w/ controller and service', async () => { @@ -384,7 +394,9 @@ forEachCli((currentCLIName) => { expect(lintResults).toContain('All files pass linting.'); const jestResult = await runCLIAsync(`test ${nestlib}`); - expect(jestResult.stderr).toContain('Test Suites: 2 passed, 2 total'); + expect(jestResult.combinedOutput).toContain( + 'Test Suites: 2 passed, 2 total' + ); }, 60000); }); diff --git a/e2e/react/src/react.test.ts b/e2e/react/src/react.test.ts index 575fc27907..3bedcf52c0 100644 --- a/e2e/react/src/react.test.ts +++ b/e2e/react/src/react.test.ts @@ -36,7 +36,9 @@ forEachCli((currentCLIName) => { updateFile(mainPath, `import '@proj/${libName}';\n` + readFile(mainPath)); const libTestResults = await runCLIAsync(`test ${libName}`); - expect(libTestResults.stderr).toContain('Test Suites: 1 passed, 1 total'); + expect(libTestResults.combinedOutput).toContain( + 'Test Suites: 1 passed, 1 total' + ); await testGeneratedApp(appName, { checkStyles: true, @@ -210,10 +212,14 @@ forEachCli((currentCLIName) => { runCLI(`g @nrwl/react:redux orange --project=${libName}`); const appTestResults = await runCLIAsync(`test ${appName}`); - expect(appTestResults.stderr).toContain('Test Suites: 2 passed, 2 total'); + expect(appTestResults.combinedOutput).toContain( + 'Test Suites: 2 passed, 2 total' + ); const libTestResults = await runCLIAsync(`test ${libName}`); - expect(libTestResults.stderr).toContain('Test Suites: 2 passed, 2 total'); + expect(libTestResults.combinedOutput).toContain( + 'Test Suites: 2 passed, 2 total' + ); }, 120000); it('should be able to use JSX', async () => { @@ -306,7 +312,9 @@ forEachCli((currentCLIName) => { } const testResults = await runCLIAsync(`test ${appName}`); - expect(testResults.stderr).toContain('Test Suites: 1 passed, 1 total'); + expect(testResults.combinedOutput).toContain( + 'Test Suites: 1 passed, 1 total' + ); if (opts.checkE2E) { const e2eResults = runCLI(`e2e ${appName}-e2e`); diff --git a/e2e/utils/index.ts b/e2e/utils/index.ts index 7c5f7f513a..475c68ab60 100644 --- a/e2e/utils/index.ts +++ b/e2e/utils/index.ts @@ -172,19 +172,19 @@ export function runCommandAsync( silenceError: false, env: process.env, } -): Promise<{ stdout: string; stderr: string }> { +): Promise<{ stdout: string; stderr: string; combinedOutput: string }> { return new Promise((resolve, reject) => { exec( command, { cwd: tmpProjPath(), - env: process.env, + env: { ...process.env, FORCE_COLOR: 'false' }, }, (err, stdout, stderr) => { if (!opts.silenceError && err) { reject(err); } - resolve({ stdout, stderr }); + resolve({ stdout, stderr, combinedOutput: `${stdout}${stderr}` }); } ); }); @@ -196,7 +196,7 @@ export function runCLIAsync( silenceError: false, env: process.env, } -): Promise<{ stdout: string; stderr: string }> { +): Promise<{ stdout: string; stderr: string; combinedOutput: string }> { return runCommandAsync(`./node_modules/.bin/nx ${command}`, opts); } diff --git a/e2e/web/src/web.test.ts b/e2e/web/src/web.test.ts index 2ff2086fe6..5ed6f9f874 100644 --- a/e2e/web/src/web.test.ts +++ b/e2e/web/src/web.test.ts @@ -48,7 +48,9 @@ forEachCli((currentCLIName) => { `` ); const testResults = await runCLIAsync(`test ${appName}`); - expect(testResults.stderr).toContain('Test Suites: 1 passed, 1 total'); + expect(testResults.combinedOutput).toContain( + 'Test Suites: 1 passed, 1 total' + ); const lintE2eResults = runCLI(`lint ${appName}-e2e`); expect(lintE2eResults).toContain('All files pass linting.'); diff --git a/packages/workspace/src/tasks-runner/task-orchestrator.ts b/packages/workspace/src/tasks-runner/task-orchestrator.ts index a95c09b07f..12d333c556 100644 --- a/packages/workspace/src/tasks-runner/task-orchestrator.ts +++ b/packages/workspace/src/tasks-runner/task-orchestrator.ts @@ -42,8 +42,10 @@ export class TaskOrchestrator { function takeFromQueue() { if (left.length > 0) { const task = left.pop(); - return that - .forkProcess(task) + const p = that.pipeOutputCapture(task) + ? that.forkProcessPipeOutputCapture(task) + : that.forkProcessDirectOutputCapture(task); + return p .then((code) => { res.push({ task, @@ -122,7 +124,82 @@ export class TaskOrchestrator { }, []); } - private forkProcess(task: Task) { + private pipeOutputCapture(task: Task) { + try { + const p = this.projectGraph.nodes[task.target.project]; + const b = p.data.architect[task.target.target].builder; + // this is temporary. we simply want to assess if pipeOutputCapture + // works well before making it configurable + return ( + this.cache.temporaryOutputPath(task) && + (b === '@nrwl/workspace:run-commands' || b === '@nrwl/cypress:cypress') + ); + } catch (e) { + return false; + } + } + + private forkProcessPipeOutputCapture(task: Task) { + const taskOutputs = getOutputs(this.projectGraph.nodes, task); + const outputPath = this.cache.temporaryOutputPath(task); + return new Promise((res, rej) => { + try { + this.options.lifeCycle.startTask(task); + const forwardOutput = this.shouldForwardOutput(outputPath, task); + const env = this.envForForkedProcess(task, undefined, forwardOutput); + const args = this.getCommandArgs(task); + const commandLine = `${this.cli} ${args.join(' ')}`; + + if (forwardOutput) { + output.logCommand(commandLine); + } + const p = fork(this.getCommand(), args, { + stdio: ['inherit', 'pipe', 'pipe', 'ipc'], + env, + }); + + let out = []; + let outWithErr = []; + p.stdout.on('data', (chunk) => { + process.stdout.write(chunk); + out.push(chunk.toString()); + outWithErr.push(chunk.toString()); + }); + p.stderr.on('data', (chunk) => { + process.stderr.write(chunk); + outWithErr.push(chunk.toString()); + }); + p.on('exit', (code) => { + // we didn't print any output as we were running the command + // print all the collected output| + if (!forwardOutput) { + output.logCommand(commandLine); + process.stdout.write(outWithErr.join('')); + } + if (outputPath && code === 0) { + fs.writeFileSync(outputPath, outWithErr.join('')); + this.cache + .put(task, outputPath, taskOutputs) + .then(() => { + this.options.lifeCycle.endTask(task, code); + res(code); + }) + .catch((e) => { + rej(e); + }); + } else { + this.options.lifeCycle.endTask(task, code); + res(code); + } + }); + } catch (e) { + console.error(e); + rej(e); + } + }); + } + + private forkProcessDirectOutputCapture(task: Task) { const taskOutputs = getOutputs(this.projectGraph.nodes, task); const outputPath = this.cache.temporaryOutputPath(task); return new Promise((res, rej) => { @@ -187,8 +264,9 @@ export class TaskOrchestrator { ...parseEnv(`${task.projectRoot}/.env`), ...parseEnv(`${task.projectRoot}/.local.env`), }; - - const env = { ...envsFromFiles, ...process.env }; + const forceColor = + process.env.FORCE_COLOR === undefined ? 'true' : process.env.FORCE_COLOR; + const env = { ...envsFromFiles, FORCE_COLOR: forceColor, ...process.env }; if (outputPath) { env.NX_TERMINAL_OUTPUT_PATH = outputPath; if (this.options.captureStderr) {