From cfeaf492335f1df9cf01eae3dab114e1701cb426 Mon Sep 17 00:00:00 2001 From: Pierre Vanduynslager Date: Wed, 15 Aug 2018 16:12:18 -0400 Subject: [PATCH] test: use real writable stream This way plugins can pipe a stream to `stdout` and `stderr` in integration tests. --- package.json | 1 + test/index.test.js | 139 ++++++++++++++++++++++++++++++++------- test/integration.test.js | 3 +- 3 files changed, 119 insertions(+), 24 deletions(-) diff --git a/package.json b/package.json index 1f147ba0..5a9f6421 100644 --- a/package.json +++ b/package.json @@ -65,6 +65,7 @@ "p-retry": "^2.0.0", "proxyquire": "^2.0.0", "sinon": "^6.0.0", + "stream-buffers": "^3.0.2", "tempy": "^0.2.1", "xo": "^0.22.0" }, diff --git a/test/index.test.js b/test/index.test.js index 0c8eb63e..a57187ef 100644 --- a/test/index.test.js +++ b/test/index.test.js @@ -1,6 +1,7 @@ import test from 'ava'; import proxyquire from 'proxyquire'; import {spy, stub} from 'sinon'; +import {WritableStreamBuffer} from 'stream-buffers'; import AggregateError from 'aggregate-error'; import SemanticReleaseError from '@semantic-release/error'; import {COMMIT_NAME, COMMIT_EMAIL} from '../lib/definitions/constants'; @@ -78,8 +79,8 @@ test('Plugins are called with expected values', async t => { const result = await semanticRelease(options, { cwd, env, - stdout: {write: () => {}}, - stderr: {write: () => {}}, + stdout: new WritableStreamBuffer(), + stderr: new WritableStreamBuffer(), }); t.is(verifyConditions1.callCount, 1); @@ -215,7 +216,14 @@ test('Use custom tag format', async t => { './lib/get-logger': () => t.context.logger, 'env-ci': () => ({isCi: true, branch: 'master', isPr: false}), }); - t.truthy(await semanticRelease(options, {cwd, env: {}, stdout: {write: () => {}}, stderr: {write: () => {}}})); + t.truthy( + await semanticRelease(options, { + cwd, + env: {}, + stdout: new WritableStreamBuffer(), + stderr: new WritableStreamBuffer(), + }) + ); // Verify the tag has been created on the local and remote repo and reference the gitHead t.is(await gitTagHead(nextRelease.gitTag, {cwd}), nextRelease.gitHead); @@ -260,7 +268,14 @@ test('Use new gitHead, and recreate release notes if a prepare plugin create a c 'env-ci': () => ({isCi: true, branch: 'master', isPr: false}), }); - t.truthy(await semanticRelease(options, {cwd, env: {}, stdout: {write: () => {}}, stderr: {write: () => {}}})); + t.truthy( + await semanticRelease(options, { + cwd, + env: {}, + stdout: new WritableStreamBuffer(), + stderr: new WritableStreamBuffer(), + }) + ); t.is(generateNotes.callCount, 2); t.deepEqual(generateNotes.args[0][1].nextRelease, nextRelease); @@ -318,7 +333,9 @@ test('Call all "success" plugins even if one errors out', async t => { 'env-ci': () => ({isCi: true, branch: 'master', isPr: false}), }); - await t.throws(semanticRelease(options, {cwd, env: {}, stdout: {write: () => {}}, stderr: {write: () => {}}})); + await t.throws( + semanticRelease(options, {cwd, env: {}, stdout: new WritableStreamBuffer(), stderr: new WritableStreamBuffer()}) + ); t.is(success1.callCount, 1); t.deepEqual(success1.args[0][1].releases, [{...release, ...nextRelease, notes, pluginName: '[Function: proxy]'}]); @@ -350,7 +367,9 @@ test('Log all "verifyConditions" errors', async t => { 'env-ci': () => ({isCi: true, branch: 'master', isPr: false}), }); const errors = [ - ...(await t.throws(semanticRelease(options, {cwd, env: {}, stdout: {write: () => {}}, stderr: {write: () => {}}}))), + ...(await t.throws( + semanticRelease(options, {cwd, env: {}, stdout: new WritableStreamBuffer(), stderr: new WritableStreamBuffer()}) + )), ]; t.deepEqual(errors, [error1, error2, error3]); @@ -396,7 +415,9 @@ test('Log all "verifyRelease" errors', async t => { 'env-ci': () => ({isCi: true, branch: 'master', isPr: false}), }); const errors = [ - ...(await t.throws(semanticRelease(options, {cwd, env: {}, stdout: {write: () => {}}, stderr: {write: () => {}}}))), + ...(await t.throws( + semanticRelease(options, {cwd, env: {}, stdout: new WritableStreamBuffer(), stderr: new WritableStreamBuffer()}) + )), ]; t.deepEqual(errors, [error1, error2]); @@ -445,7 +466,14 @@ test('Dry-run skips publish and success', async t => { './lib/get-logger': () => t.context.logger, 'env-ci': () => ({isCi: true, branch: 'master', isPr: false}), }); - t.truthy(await semanticRelease(options, {cwd, env: {}, stdout: {write: () => {}}, stderr: {write: () => {}}})); + t.truthy( + await semanticRelease(options, { + cwd, + env: {}, + stdout: new WritableStreamBuffer(), + stderr: new WritableStreamBuffer(), + }) + ); t.not(t.context.log.args[0][0], 'This run was not triggered in a known CI environment, running in dry-run mode.'); t.is(verifyConditions.callCount, 1); @@ -484,7 +512,9 @@ test('Dry-run skips fail', async t => { 'env-ci': () => ({isCi: true, branch: 'master', isPr: false}), }); const errors = [ - ...(await t.throws(semanticRelease(options, {cwd, env: {}, stdout: {write: () => {}}, stderr: {write: () => {}}}))), + ...(await t.throws( + semanticRelease(options, {cwd, env: {}, stdout: new WritableStreamBuffer(), stderr: new WritableStreamBuffer()}) + )), ]; t.deepEqual(errors, [error1, error2]); @@ -532,7 +562,14 @@ test('Force a dry-run if not on a CI and "noCi" is not explicitly set', async t './lib/get-logger': () => t.context.logger, 'env-ci': () => ({isCi: false, branch: 'master'}), }); - t.truthy(await semanticRelease(options, {cwd, env: {}, stdout: {write: () => {}}, stderr: {write: () => {}}})); + t.truthy( + await semanticRelease(options, { + cwd, + env: {}, + stdout: new WritableStreamBuffer(), + stderr: new WritableStreamBuffer(), + }) + ); t.is(t.context.log.args[1][0], 'This run was not triggered in a known CI environment, running in dry-run mode.'); t.is(verifyConditions.callCount, 1); @@ -575,7 +612,14 @@ test('Dry-run does not print changelog if "generateNotes" return "undefined"', a './lib/get-logger': () => t.context.logger, 'env-ci': () => ({isCi: true, branch: 'master', isPr: false}), }); - t.truthy(await semanticRelease(options, {cwd, env: {}, stdout: {write: () => {}}, stderr: {write: () => {}}})); + t.truthy( + await semanticRelease(options, { + cwd, + env: {}, + stdout: new WritableStreamBuffer(), + stderr: new WritableStreamBuffer(), + }) + ); t.deepEqual(t.context.log.args[t.context.log.args.length - 1], ['Release note for version 2.0.0:']); }); @@ -619,7 +663,14 @@ test('Allow local releases with "noCi" option', async t => { './lib/get-logger': () => t.context.logger, 'env-ci': () => ({isCi: false, branch: 'master', isPr: true}), }); - t.truthy(await semanticRelease(options, {cwd, env: {}, stdout: {write: () => {}}, stderr: {write: () => {}}})); + t.truthy( + await semanticRelease(options, { + cwd, + env: {}, + stdout: new WritableStreamBuffer(), + stderr: new WritableStreamBuffer(), + }) + ); t.not(t.context.log.args[0][0], 'This run was not triggered in a known CI environment, running in dry-run mode.'); t.not( @@ -671,7 +722,14 @@ test('Accept "undefined" value returned by the "generateNotes" plugins', async t './lib/get-logger': () => t.context.logger, 'env-ci': () => ({isCi: true, branch: 'master', isPr: false}), }); - t.truthy(await semanticRelease(options, {cwd, env: {}, stdout: {write: () => {}}, stderr: {write: () => {}}})); + t.truthy( + await semanticRelease(options, { + cwd, + env: {}, + stdout: new WritableStreamBuffer(), + stderr: new WritableStreamBuffer(), + }) + ); t.is(analyzeCommits.callCount, 1); t.deepEqual(analyzeCommits.args[0][1].lastRelease, lastRelease); @@ -700,7 +758,10 @@ test('Returns false if triggered by a PR', async t => { }); t.false( - await semanticRelease({cwd, repositoryUrl}, {cwd, env: {}, stdout: {write: () => {}}, stderr: {write: () => {}}}) + await semanticRelease( + {cwd, repositoryUrl}, + {cwd, env: {}, stdout: new WritableStreamBuffer(), stderr: new WritableStreamBuffer()} + ) ); t.is( t.context.log.args[t.context.log.args.length - 1][0], @@ -728,7 +789,7 @@ test('Returns false if triggered on an outdated clone', async t => { t.false( await semanticRelease( {repositoryUrl}, - {cwd: repoDir, env: {}, stdout: {write: () => {}}, stderr: {write: () => {}}} + {cwd: repoDir, env: {}, stdout: new WritableStreamBuffer(), stderr: new WritableStreamBuffer()} ) ); t.deepEqual(t.context.log.args[t.context.log.args.length - 1], [ @@ -757,7 +818,14 @@ test('Returns false if not running from the configured branch', async t => { 'env-ci': () => ({isCi: true, branch: 'other-branch', isPr: false}), }); - t.false(await semanticRelease(options, {cwd, env: {}, stdout: {write: () => {}}, stderr: {write: () => {}}})); + t.false( + await semanticRelease(options, { + cwd, + env: {}, + stdout: new WritableStreamBuffer(), + stderr: new WritableStreamBuffer(), + }) + ); t.is( t.context.log.args[1][0], 'This test run was triggered on the branch other-branch, while semantic-release is configured to only publish from master, therefore a new version won’t be published.' @@ -794,7 +862,14 @@ test('Returns false if there is no relevant changes', async t => { 'env-ci': () => ({isCi: true, branch: 'master', isPr: false}), }); - t.false(await semanticRelease(options, {cwd, env: {}, stdout: {write: () => {}}, stderr: {write: () => {}}})); + t.false( + await semanticRelease(options, { + cwd, + env: {}, + stdout: new WritableStreamBuffer(), + stderr: new WritableStreamBuffer(), + }) + ); t.is(analyzeCommits.callCount, 1); t.is(verifyRelease.callCount, 0); t.is(generateNotes.callCount, 0); @@ -841,7 +916,12 @@ test('Exclude commits with [skip release] or [release skip] from analysis', asyn './lib/get-logger': () => t.context.logger, 'env-ci': () => ({isCi: true, branch: 'master', isPr: false}), }); - await semanticRelease(options, {cwd, env: {}, stdout: {write: () => {}}, stderr: {write: () => {}}}); + await semanticRelease(options, { + cwd, + env: {}, + stdout: new WritableStreamBuffer(), + stderr: new WritableStreamBuffer(), + }); t.is(analyzeCommits.callCount, 1); t.is(analyzeCommits.args[0][1].commits.length, 2); @@ -865,7 +945,9 @@ test('Log both plugins errors and errors thrown by "fail" plugin', async t => { 'env-ci': () => ({isCi: true, branch: 'master', isPr: false}), }); - await t.throws(semanticRelease(options, {cwd, env: {}, stdout: {write: () => {}}, stderr: {write: () => {}}})); + await t.throws( + semanticRelease(options, {cwd, env: {}, stdout: new WritableStreamBuffer(), stderr: new WritableStreamBuffer()}) + ); t.is(t.context.error.args[t.context.error.args.length - 1][0], 'ERR Plugin error'); t.is(t.context.error.args[t.context.error.args.length - 3][1], failError1); @@ -888,7 +970,9 @@ test('Call "fail" only if a plugin returns a SemanticReleaseError', async t => { 'env-ci': () => ({isCi: true, branch: 'master', isPr: false}), }); - await t.throws(semanticRelease(options, {cwd, env: {}, stdout: {write: () => {}}, stderr: {write: () => {}}})); + await t.throws( + semanticRelease(options, {cwd, env: {}, stdout: new WritableStreamBuffer(), stderr: new WritableStreamBuffer()}) + ); t.true(fail.notCalled); t.is(t.context.error.args[t.context.error.args.length - 1][1], pluginError); @@ -903,7 +987,9 @@ test('Throw SemanticReleaseError if repositoryUrl is not set and cannot be found 'env-ci': () => ({isCi: true, branch: 'master', isPr: false}), }); const errors = [ - ...(await t.throws(semanticRelease({}, {cwd, env: {}, stdout: {write: () => {}}, stderr: {write: () => {}}}))), + ...(await t.throws( + semanticRelease({}, {cwd, env: {}, stdout: new WritableStreamBuffer(), stderr: new WritableStreamBuffer()}) + )), ]; // Verify error code and type @@ -939,7 +1025,7 @@ test('Throw an Error if plugin returns an unexpected value', async t => { 'env-ci': () => ({isCi: true, branch: 'master', isPr: false}), }); const error = await t.throws( - semanticRelease(options, {cwd, env: {}, stdout: {write: () => {}}, stderr: {write: () => {}}}), + semanticRelease(options, {cwd, env: {}, stdout: new WritableStreamBuffer(), stderr: new WritableStreamBuffer()}), Error ); t.regex(error.details, /string/); @@ -974,7 +1060,14 @@ test('Get all commits including the ones not in the shallow clone', async t => { './lib/get-logger': () => t.context.logger, 'env-ci': () => ({isCi: true, branch: 'master', isPr: false}), }); - t.truthy(await semanticRelease(options, {cwd, env: {}, stdout: {write: () => {}}, stderr: {write: () => {}}})); + t.truthy( + await semanticRelease(options, { + cwd, + env: {}, + stdout: new WritableStreamBuffer(), + stderr: new WritableStreamBuffer(), + }) + ); t.is(analyzeCommits.args[0][1].commits.length, 3); }); diff --git a/test/integration.test.js b/test/integration.test.js index 194e1b7e..08eea934 100644 --- a/test/integration.test.js +++ b/test/integration.test.js @@ -4,6 +4,7 @@ import test from 'ava'; import {escapeRegExp} from 'lodash'; import {writeJson, readJson} from 'fs-extra'; import execa from 'execa'; +import {WritableStreamBuffer} from 'stream-buffers'; import {SECRET_REPLACEMENT} from '../lib/definitions/constants'; import {gitHead as getGitHead, gitTagHead, gitRepo, gitCommits, gitRemoteTagHead, gitPush} from './helpers/git-utils'; import gitbox from './helpers/gitbox'; @@ -490,7 +491,7 @@ test('Run via JS API', async t => { t.log('Commit a feature'); await gitCommits(['feat: Initial commit'], {cwd}); t.log('$ Call semantic-release via API'); - await semanticRelease(undefined, {cwd, env, stdout: {write: () => {}}, stderr: {write: () => {}}}); + await semanticRelease(undefined, {cwd, env, stdout: new WritableStreamBuffer(), stderr: new WritableStreamBuffer()}); // Verify package.json and has been updated t.is((await readJson(path.resolve(cwd, 'package.json'))).version, version);