From c740defa8e3da9e2a2180f808547bfc0402378fd Mon Sep 17 00:00:00 2001 From: Anton Gilgur Date: Tue, 17 Mar 2020 17:31:31 -0400 Subject: [PATCH 1/2] (refactor): split build tests into separate files per fixture - better organized that way as the tests are somewhat independent of one another and previously even I was confused where to put different types of tests - also refactor some test naming and some redundant usage of flags - especially for 0-config, where flags shouldn't be used - and remove testEnvironment comment as it's already set in jest config (optim): now that it's one fixture per file, we only need to copy the fixture directory once per file, which should be a speed boost - and sometimes may not need to re-run `tsdx build` when not necessary - also the different test files get parallelized, which is also faster - before: 53s avg on my machine, after: 37s avg on my machine - avg for running all tests - pretty BIG difference --- test/tests/tsdx-build-default.test.js | 75 ++++++++++ test/tests/tsdx-build-invalid.test.js | 40 +++++ test/tests/tsdx-build-withTsconfig.test.js | 63 ++++++++ test/tests/tsdx-build.test.js | 164 --------------------- 4 files changed, 178 insertions(+), 164 deletions(-) create mode 100644 test/tests/tsdx-build-default.test.js create mode 100644 test/tests/tsdx-build-invalid.test.js create mode 100644 test/tests/tsdx-build-withTsconfig.test.js delete mode 100644 test/tests/tsdx-build.test.js diff --git a/test/tests/tsdx-build-default.test.js b/test/tests/tsdx-build-default.test.js new file mode 100644 index 000000000..5650e5417 --- /dev/null +++ b/test/tests/tsdx-build-default.test.js @@ -0,0 +1,75 @@ +const shell = require('shelljs'); +const util = require('../fixtures/util'); + +shell.config.silent = false; + +const fixtureName = 'build-default'; +const stageName = `stage-${fixtureName}`; + +describe('tsdx build :: zero-config defaults', () => { + beforeAll(() => { + util.teardownStage(stageName); + util.setupStageWithFixture(stageName, fixtureName); + }); + + it('should compile files into a dist directory', () => { + const output = shell.exec('node ../dist/index.js build'); + + expect(shell.test('-f', 'dist/index.js')).toBeTruthy(); + expect( + shell.test('-f', 'dist/build-default.cjs.development.js') + ).toBeTruthy(); + expect( + shell.test('-f', 'dist/build-default.cjs.production.min.js') + ).toBeTruthy(); + expect(shell.test('-f', 'dist/build-default.esm.js')).toBeTruthy(); + + expect(shell.test('-f', 'dist/index.d.ts')).toBeTruthy(); + + expect(output.code).toBe(0); + }); + + it('should create the library correctly', () => { + const lib = require(`../../${stageName}/dist`); + expect(lib.foo()).toBe('bar'); + expect(lib.__esModule).toBe(true); + }); + + it('should clean the dist directory before rebuilding', () => { + shell.mv('package.json', 'package-og.json'); + shell.mv('package2.json', 'package.json'); + + const output = shell.exec('node ../dist/index.js build'); + expect(shell.test('-f', 'dist/index.js')).toBeTruthy(); + + // build-default files have been cleaned out + expect( + shell.test('-f', 'dist/build-default.cjs.development.js') + ).toBeFalsy(); + expect( + shell.test('-f', 'dist/build-default.cjs.production.min.js') + ).toBeFalsy(); + expect(shell.test('-f', 'dist/build-default.esm.js')).toBeFalsy(); + + // build-default-2 files have been added + expect( + shell.test('-f', 'dist/build-default-2.cjs.development.js') + ).toBeTruthy(); + expect( + shell.test('-f', 'dist/build-default-2.cjs.production.min.js') + ).toBeTruthy(); + expect(shell.test('-f', 'dist/build-default-2.esm.js')).toBeTruthy(); + + expect(shell.test('-f', 'dist/index.d.ts')).toBeTruthy(); + + expect(output.code).toBe(0); + + // reset package.json files + shell.mv('package.json', 'package2.json'); + shell.mv('package-og.json', 'package.json'); + }); + + afterAll(() => { + util.teardownStage(stageName); + }); +}); diff --git a/test/tests/tsdx-build-invalid.test.js b/test/tests/tsdx-build-invalid.test.js new file mode 100644 index 000000000..164b63845 --- /dev/null +++ b/test/tests/tsdx-build-invalid.test.js @@ -0,0 +1,40 @@ +const shell = require('shelljs'); +const util = require('../fixtures/util'); + +shell.config.silent = false; + +const fixtureName = 'build-invalid'; +const stageName = `stage-${fixtureName}`; + +describe('tsdx build :: invalid build', () => { + beforeAll(() => { + util.teardownStage(stageName); + util.setupStageWithFixture(stageName, fixtureName); + }); + + it('should fail gracefully with exit code 1 when build failed', () => { + const code = shell.exec('node ../dist/index.js build').code; + expect(code).toBe(1); + }); + + it('should only transpile and not type check', () => { + const code = shell.exec('node ../dist/index.js build --transpileOnly').code; + + expect(shell.test('-f', 'dist/index.js')).toBeTruthy(); + expect( + shell.test('-f', 'dist/build-invalid.cjs.development.js') + ).toBeTruthy(); + expect( + shell.test('-f', 'dist/build-invalid.cjs.production.min.js') + ).toBeTruthy(); + expect(shell.test('-f', 'dist/build-invalid.esm.js')).toBeTruthy(); + + expect(shell.test('-f', 'dist/index.d.ts')).toBeTruthy(); + + expect(code).toBe(0); + }); + + afterAll(() => { + util.teardownStage(stageName); + }); +}); diff --git a/test/tests/tsdx-build-withTsconfig.test.js b/test/tests/tsdx-build-withTsconfig.test.js new file mode 100644 index 000000000..be4d72567 --- /dev/null +++ b/test/tests/tsdx-build-withTsconfig.test.js @@ -0,0 +1,63 @@ +const shell = require('shelljs'); +const util = require('../fixtures/util'); + +shell.config.silent = false; + +const fixtureName = 'build-withTsconfig'; +const stageName = `stage-${fixtureName}`; + +describe('tsdx build :: build with custom tsconfig.json options', () => { + beforeAll(() => { + util.teardownStage(stageName); + util.setupStageWithFixture(stageName, fixtureName); + }); + + it('should use the declarationDir when set', () => { + const output = shell.exec('node ../dist/index.js build'); + + expect(shell.test('-f', 'dist/index.js')).toBeTruthy(); + expect( + shell.test('-f', 'dist/build-withtsconfig.cjs.development.js') + ).toBeTruthy(); + expect( + shell.test('-f', 'dist/build-withtsconfig.cjs.production.min.js') + ).toBeTruthy(); + expect(shell.test('-f', 'dist/build-withtsconfig.esm.js')).toBeTruthy(); + + expect(shell.test('-f', 'dist/index.d.ts')).toBeFalsy(); + expect(shell.test('-f', 'typings/index.d.ts')).toBeTruthy(); + expect(shell.test('-f', 'typings/index.d.ts.map')).toBeTruthy(); + + expect(output.code).toBe(0); + }); + + it('should set __esModule according to esModuleInterop', () => { + const lib = require(`../../${stageName}/dist/build-withtsconfig.cjs.production.min.js`); + // if esModuleInterop: false, no __esModule is added, therefore undefined + expect(lib.__esModule).toBe(undefined); + }); + + it('should read custom --tsconfig path', () => { + const output = shell.exec( + 'node ../dist/index.js build --format cjs --tsconfig ./src/tsconfig.json' + ); + + expect(shell.test('-f', 'dist/index.js')).toBeTruthy(); + expect( + shell.test('-f', 'dist/build-withtsconfig.cjs.development.js') + ).toBeTruthy(); + expect( + shell.test('-f', 'dist/build-withtsconfig.cjs.production.min.js') + ).toBeTruthy(); + + expect(shell.test('-f', 'dist/index.d.ts')).toBeFalsy(); + expect(shell.test('-f', 'typingsCustom/index.d.ts')).toBeTruthy(); + expect(shell.test('-f', 'typingsCustom/index.d.ts.map')).toBeTruthy(); + + expect(output.code).toBe(0); + }); + + afterAll(() => { + util.teardownStage(stageName); + }); +}); diff --git a/test/tests/tsdx-build.test.js b/test/tests/tsdx-build.test.js deleted file mode 100644 index b1663c658..000000000 --- a/test/tests/tsdx-build.test.js +++ /dev/null @@ -1,164 +0,0 @@ -/** - * @jest-environment node - */ - -const shell = require('shelljs'); -const util = require('../fixtures/util'); - -shell.config.silent = false; - -const stageName = 'stage-build'; - -describe('tsdx build', () => { - beforeAll(() => { - util.teardownStage(stageName); - }); - - it('should compile files into a dist directory', () => { - util.setupStageWithFixture(stageName, 'build-default'); - - const output = shell.exec('node ../dist/index.js build --format esm,cjs'); - - expect(shell.test('-f', 'dist/index.js')).toBeTruthy(); - expect( - shell.test('-f', 'dist/build-default.cjs.development.js') - ).toBeTruthy(); - expect( - shell.test('-f', 'dist/build-default.cjs.production.min.js') - ).toBeTruthy(); - expect(shell.test('-f', 'dist/build-default.esm.js')).toBeTruthy(); - - expect(shell.test('-f', 'dist/index.d.ts')).toBeTruthy(); - - expect(output.code).toBe(0); - }); - - it('should create the library correctly', () => { - util.setupStageWithFixture(stageName, 'build-default'); - - shell.exec('node ../dist/index.js build'); - - const lib = require(`../../${stageName}/dist`); - expect(lib.foo()).toBe('bar'); - expect(lib.__esModule).toBe(true); - }); - - it('should clean the dist directory before rebuilding', () => { - util.setupStageWithFixture(stageName, 'build-default'); - - shell.mv('package.json', 'package-og.json'); - shell.mv('package2.json', 'package.json'); - - const output = shell.exec('node ../dist/index.js build --format esm,cjs'); - expect(shell.test('-f', 'dist/index.js')).toBeTruthy(); - - // build-default files have been cleaned out - expect( - shell.test('-f', 'dist/build-default.cjs.development.js') - ).toBeFalsy(); - expect( - shell.test('-f', 'dist/build-default.cjs.production.min.js') - ).toBeFalsy(); - expect(shell.test('-f', 'dist/build-default.esm.js')).toBeFalsy(); - - // build-default-2 files have been added - expect( - shell.test('-f', 'dist/build-default-2.cjs.development.js') - ).toBeTruthy(); - expect( - shell.test('-f', 'dist/build-default-2.cjs.production.min.js') - ).toBeTruthy(); - expect(shell.test('-f', 'dist/build-default-2.esm.js')).toBeTruthy(); - - expect(shell.test('-f', 'dist/index.d.ts')).toBeTruthy(); - - expect(output.code).toBe(0); - - // reset package.json files - shell.mv('package.json', 'package2.json'); - shell.mv('package-og.json', 'package.json'); - }); - - it('should fail gracefully with exit code 1 when build failed', () => { - util.setupStageWithFixture(stageName, 'build-invalid'); - const code = shell.exec('node ../dist/index.js build').code; - expect(code).toBe(1); - }); - - it('should only transpile and not type check', () => { - util.setupStageWithFixture(stageName, 'build-invalid'); - const code = shell.exec('node ../dist/index.js build --transpileOnly').code; - - expect(shell.test('-f', 'dist/index.js')).toBeTruthy(); - expect( - shell.test('-f', 'dist/build-invalid.cjs.development.js') - ).toBeTruthy(); - expect( - shell.test('-f', 'dist/build-invalid.cjs.production.min.js') - ).toBeTruthy(); - expect(shell.test('-f', 'dist/build-invalid.esm.js')).toBeTruthy(); - - expect(shell.test('-f', 'dist/index.d.ts')).toBeTruthy(); - - expect(code).toBe(0); - }); - - it('should use the declarationDir when set in tsconfig', () => { - util.setupStageWithFixture(stageName, 'build-withTsconfig'); - - const output = shell.exec('node ../dist/index.js build --format esm,cjs'); - - expect(shell.test('-f', 'dist/index.js')).toBeTruthy(); - expect( - shell.test('-f', 'dist/build-withtsconfig.cjs.development.js') - ).toBeTruthy(); - expect( - shell.test('-f', 'dist/build-withtsconfig.cjs.production.min.js') - ).toBeTruthy(); - expect(shell.test('-f', 'dist/build-withtsconfig.esm.js')).toBeTruthy(); - - expect(shell.test('-f', 'dist/index.d.ts')).toBeFalsy(); - expect(shell.test('-f', 'typings/index.d.ts')).toBeTruthy(); - expect(shell.test('-f', 'typings/index.d.ts.map')).toBeTruthy(); - - expect(output.code).toBe(0); - }); - - it('should set __esModule according to esModuleInterop in tsconfig', () => { - util.setupStageWithFixture(stageName, 'build-withTsconfig'); - - const output = shell.exec('node ../dist/index.js build --format cjs'); - - const lib = require(`../../${stageName}/dist/build-withtsconfig.cjs.production.min.js`); - // if esModuleInterop: false, no __esModule is added, therefore undefined - expect(lib.__esModule).toBe(undefined); - - expect(output.code).toBe(0); - }); - - it('should read custom --tsconfig path', () => { - util.setupStageWithFixture(stageName, 'build-withTsconfig'); - - const output = shell.exec( - 'node ../dist/index.js build --format cjs --tsconfig ./src/tsconfig.json' - ); - - expect(shell.test('-f', 'dist/index.js')).toBeTruthy(); - expect( - shell.test('-f', 'dist/build-withtsconfig.cjs.development.js') - ).toBeTruthy(); - expect( - shell.test('-f', 'dist/build-withtsconfig.cjs.production.min.js') - ).toBeTruthy(); - - expect(shell.test('-f', 'dist/index.d.ts')).toBeFalsy(); - expect(shell.test('-f', 'typingsCustom/index.d.ts')).toBeTruthy(); - expect(shell.test('-f', 'typingsCustom/index.d.ts.map')).toBeTruthy(); - - expect(output.code).toBe(0); - }); - - afterEach(() => { - util.teardownStage(stageName); - }); -}); From 9d844cd219389dcd576913c2c81e597764d7370e Mon Sep 17 00:00:00 2001 From: Anton Gilgur Date: Wed, 25 Mar 2020 18:52:46 -0400 Subject: [PATCH 2/2] (fix/test): resolve reproducibility issues w/ execWithCache - had removed some duplicate execs in the previous commit in order to speed up tests so that exec wouldn't need to be re-run - but this caused reproducibility issues, as if you change a test to `it.only` temporarily, it would fail hard as nothing would be exec'd if it weren't the first test - this uses an `execWithCache` function to instead naive "cache" the output and not re-run the same command twice-in-a-row - only works with sequential tests, but good enough for now - similar to a beforeAll() for a group of the same test, but no nesting or confusing variables in describe scope - N.B. no need for stageName in cache function or something more complex because each stage is parallelized as multi-process --- test/tests/tsdx-build-default.test.js | 13 ++++++++-- test/tests/tsdx-build-invalid.test.js | 9 ++++--- test/tests/tsdx-build-withTsconfig.test.js | 9 +++++-- test/utils/shell.js | 29 ++++++++++++++++++++++ 4 files changed, 52 insertions(+), 8 deletions(-) create mode 100644 test/utils/shell.js diff --git a/test/tests/tsdx-build-default.test.js b/test/tests/tsdx-build-default.test.js index 5650e5417..31e53135a 100644 --- a/test/tests/tsdx-build-default.test.js +++ b/test/tests/tsdx-build-default.test.js @@ -1,5 +1,6 @@ const shell = require('shelljs'); const util = require('../fixtures/util'); +const { execWithCache } = require('../utils/shell'); shell.config.silent = false; @@ -13,7 +14,7 @@ describe('tsdx build :: zero-config defaults', () => { }); it('should compile files into a dist directory', () => { - const output = shell.exec('node ../dist/index.js build'); + const output = execWithCache('node ../dist/index.js build'); expect(shell.test('-f', 'dist/index.js')).toBeTruthy(); expect( @@ -30,16 +31,24 @@ describe('tsdx build :: zero-config defaults', () => { }); it('should create the library correctly', () => { + const output = execWithCache('node ../dist/index.js build'); + const lib = require(`../../${stageName}/dist`); expect(lib.foo()).toBe('bar'); expect(lib.__esModule).toBe(true); + + expect(output.code).toBe(0); }); it('should clean the dist directory before rebuilding', () => { + let output = execWithCache('node ../dist/index.js build'); + expect(output.code).toBe(0); + shell.mv('package.json', 'package-og.json'); shell.mv('package2.json', 'package.json'); - const output = shell.exec('node ../dist/index.js build'); + // cache bust because we want to re-run this command with new package.json + output = execWithCache('node ../dist/index.js build', { noCache: true }); expect(shell.test('-f', 'dist/index.js')).toBeTruthy(); // build-default files have been cleaned out diff --git a/test/tests/tsdx-build-invalid.test.js b/test/tests/tsdx-build-invalid.test.js index 164b63845..98d0552d5 100644 --- a/test/tests/tsdx-build-invalid.test.js +++ b/test/tests/tsdx-build-invalid.test.js @@ -1,5 +1,6 @@ const shell = require('shelljs'); const util = require('../fixtures/util'); +const { execWithCache } = require('../utils/shell'); shell.config.silent = false; @@ -13,12 +14,12 @@ describe('tsdx build :: invalid build', () => { }); it('should fail gracefully with exit code 1 when build failed', () => { - const code = shell.exec('node ../dist/index.js build').code; - expect(code).toBe(1); + const output = execWithCache('node ../dist/index.js build'); + expect(output.code).toBe(1); }); it('should only transpile and not type check', () => { - const code = shell.exec('node ../dist/index.js build --transpileOnly').code; + const output = execWithCache('node ../dist/index.js build --transpileOnly'); expect(shell.test('-f', 'dist/index.js')).toBeTruthy(); expect( @@ -31,7 +32,7 @@ describe('tsdx build :: invalid build', () => { expect(shell.test('-f', 'dist/index.d.ts')).toBeTruthy(); - expect(code).toBe(0); + expect(output.code).toBe(0); }); afterAll(() => { diff --git a/test/tests/tsdx-build-withTsconfig.test.js b/test/tests/tsdx-build-withTsconfig.test.js index be4d72567..53b928d4e 100644 --- a/test/tests/tsdx-build-withTsconfig.test.js +++ b/test/tests/tsdx-build-withTsconfig.test.js @@ -1,5 +1,6 @@ const shell = require('shelljs'); const util = require('../fixtures/util'); +const { execWithCache } = require('../utils/shell'); shell.config.silent = false; @@ -13,7 +14,7 @@ describe('tsdx build :: build with custom tsconfig.json options', () => { }); it('should use the declarationDir when set', () => { - const output = shell.exec('node ../dist/index.js build'); + const output = execWithCache('node ../dist/index.js build'); expect(shell.test('-f', 'dist/index.js')).toBeTruthy(); expect( @@ -32,13 +33,17 @@ describe('tsdx build :: build with custom tsconfig.json options', () => { }); it('should set __esModule according to esModuleInterop', () => { + const output = execWithCache('node ../dist/index.js build'); + const lib = require(`../../${stageName}/dist/build-withtsconfig.cjs.production.min.js`); // if esModuleInterop: false, no __esModule is added, therefore undefined expect(lib.__esModule).toBe(undefined); + + expect(output.code).toBe(0); }); it('should read custom --tsconfig path', () => { - const output = shell.exec( + const output = execWithCache( 'node ../dist/index.js build --format cjs --tsconfig ./src/tsconfig.json' ); diff --git a/test/utils/shell.js b/test/utils/shell.js new file mode 100644 index 000000000..c539f1b00 --- /dev/null +++ b/test/utils/shell.js @@ -0,0 +1,29 @@ +// this file contains helper utils for working with shell.js functions +const shell = require('shelljs'); + +shell.config.silent = true; + +// simple shell.exec "cache" that doesn't re-run the same command twice in a row +let prevCommand = ''; +let prevCommandOutput = {}; +function execWithCache(command, { noCache = false } = {}) { + // return the old output + if (!noCache && prevCommand === command) return prevCommandOutput; + + const output = shell.exec(command); + + // reset if command is not to be cached + if (noCache) { + prevCommand = ''; + prevCommandOutput = {}; + } else { + prevCommand = command; + prevCommandOutput = output; + } + + return output; +} + +module.exports = { + execWithCache, +};