11import { sync as spawnSync } from 'cross-spawn'
2+ import { createHash } from 'crypto'
3+ import stringifyJson from 'fast-json-stable-stringify'
24import {
35 copySync ,
46 ensureSymlinkSync ,
@@ -11,18 +13,20 @@ import {
1113 readdirSync ,
1214 realpathSync ,
1315 removeSync ,
16+ renameSync ,
1417 statSync ,
1518 symlinkSync ,
1619 writeFileSync ,
1720} from 'fs-extra'
21+ import { stringify as stringifyJson5 } from 'json5'
1822import merge from 'lodash.merge'
1923import { join , relative , resolve , sep } from 'path'
2024
2125import * as Paths from '../../../scripts/lib/paths'
2226
2327import RunResult from './run-result'
2428import { PreparedTest , RunTestOptions } from './types'
25- import { templateNameForPath } from './utils'
29+ import { enableOptimizations , templateNameForPath } from './utils'
2630
2731const TEMPLATE_EXCLUDED_ITEMS = [ 'node_modules' , 'package-lock.json' ]
2832
@@ -49,25 +53,18 @@ function hooksSourceWith(vars: Record<string, any>): string {
4953}
5054
5155export function run ( name : string , options : RunTestOptions = { } ) : RunResult {
52- const {
53- args = [ ] ,
54- env = { } ,
55- template,
56- inject,
57- writeIo,
58- noCache,
59- jestConfigPath : configFile = 'jest.config.js' ,
60- } = options
61- const { workdir : dir , sourceDir, hooksFile, ioDir } = prepareTest (
56+ const { env = { } , template, inject, writeIo, noCache, jestConfigPath : configFile = 'jest.config.js' } = options
57+ const { workdir : dir , sourceDir } = prepareTest (
6258 name ,
6359 template || templateNameForPath ( join ( Paths . e2eSourceDir , name ) ) ,
6460 options ,
6561 )
6662 const pkg = readJsonSync ( join ( dir , 'package.json' ) )
6763
64+ const jestConfigPath = ( path : string = dir ) => resolve ( path , configFile )
65+
6866 // grab base configuration
69- const jestConfigPath = resolve ( dir , configFile )
70- let baseConfig : jest . InitialOptions = require ( jestConfigPath )
67+ let baseConfig : jest . InitialOptions = require ( jestConfigPath ( ) )
7168 if ( configFile === 'package.json' ) baseConfig = ( baseConfig as any ) . jest
7269
7370 const extraConfig = { } as jest . InitialOptions
@@ -81,25 +78,14 @@ export function run(name: string, options: RunTestOptions = {}): RunResult {
8178 if ( process . argv . find ( v => [ '--updateSnapshot' , '-u' ] . includes ( v ) ) ) {
8279 cmdArgs . push ( '-u' )
8380 }
84- cmdArgs . push ( ...args )
8581 if ( ! inject && pkg . scripts && pkg . scripts . test ) {
86- if ( cmdArgs . length ) {
87- cmdArgs . unshift ( '--' )
88- }
89- cmdArgs = [ 'npm' , '-s' , 'run' , 'test' , ...cmdArgs ]
82+ cmdArgs = [ 'npm' , '-s' , 'run' , 'test' , '--' , ...cmdArgs ]
9083 shortCmd = 'npm'
9184 } else {
92- cmdArgs . unshift ( join ( dir , 'node_modules' , '.bin' , 'jest' ) )
85+ cmdArgs . unshift ( join ( 'node_modules' , '.bin' , 'jest' ) )
9386 shortCmd = 'jest'
9487 }
9588
96- // check/merge config
97- if ( cmdArgs . includes ( '--config' ) ) {
98- throw new Error ( `Extend config using tsJestConfig and jestConfig options, not thru args.` )
99- }
100- if ( cmdArgs . includes ( '--no-cache' ) ) {
101- throw new Error ( `Use the noCache option to disable cache, not thru args.` )
102- }
10389 // extends config
10490 if ( options . jestConfig ) {
10591 merge ( extraConfig , options . jestConfig )
@@ -110,21 +96,39 @@ export function run(name: string, options: RunTestOptions = {}): RunResult {
11096 merge ( tsJestConfig , options . tsJestConfig )
11197 }
11298
99+ // cache dir
113100 if ( noCache || writeIo ) {
114101 cmdArgs . push ( '--no-cache' )
102+ extraConfig . cacheDirectory = undefined
115103 } else if ( ! ( baseConfig . cacheDirectory || extraConfig . cacheDirectory ) ) {
116104 // force the cache directory if not set
117105 extraConfig . cacheDirectory = join ( Paths . cacheDir , `e2e-${ template } ` )
118106 }
119107
120- // write final config
108+ // build final config and create dir suffix based on it
121109 const finalConfig = merge ( { } , baseConfig , extraConfig )
122- if ( Object . keys ( extraConfig ) . length !== 0 ) {
110+ const digest = createHash ( 'sha1' )
111+ . update ( stringifyJson ( finalConfig ) )
112+ . digest ( 'hex' )
113+ // this must be in the same path hierarchy as dir
114+ const nextDirPrefix = `${ dir } -${ digest . substr ( 0 , 7 ) } .`
115+ let index = 1
116+ while ( existsSync ( `${ nextDirPrefix } ${ index } ` ) ) index ++
117+ const nextDir = `${ nextDirPrefix } ${ index } `
118+
119+ // move the directory related to config digest
120+ renameSync ( dir , nextDir )
121+
122+ // write final config
123+ // FIXME: sounds like the json fail to be encoded as an arg
124+ if ( false /* enableOptimizations() */ ) {
125+ cmdArgs . push ( '--config' , JSON . stringify ( finalConfig ) )
126+ } else if ( Object . keys ( extraConfig ) . length !== 0 ) {
123127 if ( configFile === 'package.json' ) {
124128 pkg . jest = finalConfig
125- outputJsonSync ( jestConfigPath , pkg )
129+ outputJsonSync ( jestConfigPath ( nextDir ) , pkg )
126130 } else {
127- outputFileSync ( jestConfigPath , `module.exports = ${ JSON . stringify ( finalConfig , null , 2 ) } ` , 'utf8' )
131+ outputFileSync ( jestConfigPath ( nextDir ) , `module.exports = ${ JSON . stringify ( finalConfig , null , 2 ) } ` , 'utf8' )
128132 }
129133 }
130134
@@ -134,48 +138,74 @@ export function run(name: string, options: RunTestOptions = {}): RunResult {
134138 }
135139
136140 const cmd = cmdArgs . shift ( ) as string
141+ if ( cmdArgs [ cmdArgs . length - 1 ] === '--' ) cmdArgs . pop ( )
137142
138- // Add both process.env which is the standard and custom env variables
139- const mergedEnv : any = {
140- ...process . env ,
141- ...env ,
142- }
143+ // extend env
144+ const localEnv : any = { ...env }
143145 if ( inject ) {
144146 const injected = typeof inject === 'function' ? `(${ inject . toString ( ) } ).apply(this);` : inject
145- mergedEnv . __TS_JEST_EVAL = injected
147+ localEnv . __TS_JEST_EVAL = injected
146148 }
147149 if ( writeIo ) {
148- mergedEnv . TS_JEST_HOOKS = hooksFile
150+ localEnv . TS_JEST_HOOKS = defaultHooksFile ( '.' )
149151 }
150152
151- const result = spawnSync ( cmd , cmdArgs , {
152- cwd : dir ,
153- env : mergedEnv ,
154- } )
153+ // arguments to give to spawn
154+ const spawnOptions : { env : Record < string , string > ; cwd : string } = { env : localEnv } as any
155+
156+ // create started script for debugging
157+ if ( ! enableOptimizations ( ) ) {
158+ outputFileSync (
159+ join ( nextDir , '__launch.js' ) ,
160+ `
161+ const { execFile } = require('child_process')
162+ const cmd = ${ stringifyJson5 ( cmd , null , 2 ) }
163+ const args = ${ stringifyJson5 ( cmdArgs , null , 2 ) }
164+ const options = ${ stringifyJson5 ( spawnOptions , null , 2 ) }
165+ options.env = Object.assign({}, process.env, options.env)
166+ execFile(cmd, args, options)
167+ ` ,
168+ 'utf8' ,
169+ )
170+ }
171+
172+ // extend env with our env
173+ spawnOptions . env = { ...process . env , ...localEnv }
174+ spawnOptions . cwd = nextDir
175+
176+ // run jest
177+ const result = spawnSync ( cmd , cmdArgs , spawnOptions )
155178
156179 // we need to copy each snapshot which does NOT exists in the source dir
157- readdirSync ( dir ) . forEach ( item => {
158- if ( item === 'node_modules' || ! statSync ( join ( dir , item ) ) . isDirectory ( ) ) {
159- return
160- }
161- const srcDir = join ( sourceDir , item )
162- const wrkDir = join ( dir , item )
163- copySync ( wrkDir , srcDir , {
164- overwrite : false ,
165- filter : from => {
166- return relative ( sourceDir , from )
167- . split ( sep )
168- . includes ( '__snapshots__' )
169- } ,
180+ if ( ! enableOptimizations ( ) ) {
181+ readdirSync ( nextDir ) . forEach ( item => {
182+ if ( item === 'node_modules' || ! statSync ( join ( nextDir , item ) ) . isDirectory ( ) ) {
183+ return
184+ }
185+ const srcDir = join ( sourceDir , item )
186+ const wrkDir = join ( nextDir , item )
187+
188+ // do not try to copy a linked root snapshots
189+ if ( item === '__snapshots__' && existsSync ( srcDir ) ) return
190+
191+ copySync ( wrkDir , srcDir , {
192+ overwrite : false ,
193+ filter : from => {
194+ return relative ( sourceDir , from )
195+ . split ( sep )
196+ . includes ( '__snapshots__' )
197+ } ,
198+ } )
170199 } )
171- } )
200+ }
172201
173- return new RunResult ( realpathSync ( dir ) , result , {
202+ return new RunResult ( realpathSync ( nextDir ) , result , {
174203 cmd : shortCmd ,
175204 args : cmdArgs ,
176- env : mergedEnv ,
177- ioDir : writeIo ? ioDir : undefined ,
205+ env : localEnv ,
206+ ioDir : writeIo ? ioDirForPath ( nextDir ) : undefined ,
178207 config : finalConfig ,
208+ digest,
179209 } )
180210}
181211
@@ -247,18 +277,19 @@ export function prepareTest(name: string, template: string, options: RunTestOpti
247277 // create the special files
248278 outputFileSync ( join ( caseWorkdir , '__eval.ts' ) , EVAL_SOURCE , 'utf8' )
249279 let ioDir ! : string
280+ // hooks
250281 if ( options . writeIo ) {
251- ioDir = join ( caseWorkdir , '__io__' )
282+ ioDir = ioDirForPath ( caseWorkdir )
252283 mkdirpSync ( ioDir )
284+ const hooksFile = defaultHooksFile ( caseWorkdir )
285+ outputFileSync (
286+ hooksFile ,
287+ hooksSourceWith ( {
288+ writeProcessIoTo : ioDirForPath ( '.' ) || false ,
289+ } ) ,
290+ 'utf8' ,
291+ )
253292 }
254- const hooksFile = join ( caseWorkdir , '__hooks.js' )
255- outputFileSync (
256- hooksFile ,
257- hooksSourceWith ( {
258- writeProcessIoTo : ioDir || false ,
259- } ) ,
260- 'utf8' ,
261- )
262293
263294 // create a package.json if it does not exists, and/or enforce the package name
264295 const pkgFile = join ( caseWorkdir , 'package.json' )
@@ -268,5 +299,13 @@ export function prepareTest(name: string, template: string, options: RunTestOpti
268299 pkg . version = `0.0.0-mock0`
269300 outputJsonSync ( pkgFile , pkg , { spaces : 2 } )
270301
271- return { workdir : caseWorkdir , templateDir, sourceDir, hooksFile, ioDir }
302+ return { workdir : caseWorkdir , templateDir, sourceDir }
303+ }
304+
305+ function ioDirForPath ( path : string ) {
306+ return join ( path , '__io__' )
307+ }
308+
309+ function defaultHooksFile ( path : string ) {
310+ return join ( path , '__hooks.js' )
272311}
0 commit comments