Skip to content

Commit 9efc6a0

Browse files
committed
ci(e2e): allows a diff dir for each config
1 parent 02b1439 commit 9efc6a0

11 files changed

Lines changed: 159 additions & 86 deletions

File tree

.travis.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ language: node_js
22

33
env:
44
- TS_JEST_E2E_WORKDIR=/tmp/ts-jest-e2e-workdir
5+
- TS_JEST_E2E_OPTIMIZATIONS=1
56

67
cache:
78
npm: true

appveyor.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ install:
2424
# - set CI=true
2525
# Our E2E work dir
2626
- set TS_JEST_E2E_WORKDIR=%APPDATA%\ts-jest-e2e
27+
- set TS_JEST_E2E_OPTIMIZATIONS=1
2728
- npm ci --ignore-scripts
2829
- npm run clean -- --when-ci-commit-message
2930

e2e/__helpers__/test-case/__hooks-source__.js.hbs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,13 @@
33
const fs = require('fs')
44
const path = require('path')
55
const root = __dirname
6-
const writeProcessIoTo = {{writeProcessIoTo}}
6+
const writeProcessIoTo = path.resolve(root, {{writeProcessIoTo}})
77

88
exports.afterProcess = function (args, result) {
99
// const source = args[0]
1010
const filePath = args[1]
1111
const relPath = path.relative(root, filePath)
12-
if (writeProcessIoTo && filePath.startsWith(`${root}${path.sep}`)) {
12+
if (filePath.startsWith(`${root}${path.sep}`)) {
1313
const dest = `${path.join(writeProcessIoTo, relPath)}.json`
1414
const segments = relPath.split(path.sep)
1515
segments.pop()

e2e/__helpers__/test-case/run-descriptor.ts

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,10 @@ export default class RunDescriptor {
2626
}
2727

2828
get sourcePackageJson() {
29-
return this._sourcePackageJson || (this._sourcePackageJson = require(join(this.sourceDir, 'package.json')))
29+
try {
30+
return this._sourcePackageJson || (this._sourcePackageJson = require(join(this.sourceDir, 'package.json')))
31+
} catch (err) {}
32+
return {}
3033
}
3134

3235
get templateName(): string {
@@ -45,8 +48,18 @@ export default class RunDescriptor {
4548
if (logUnlessStatus != null && logUnlessStatus !== result.status) {
4649
// tslint:disable-next-line:no-console
4750
console.log(
48-
`Output of test run in "${this.name}" using template "${this.templateName}" (exit code: ${result.status}):\n\n`,
51+
'='.repeat(70),
52+
'\n',
53+
`Test exited with unexpected status in "${this.name}" using template "${this.templateName}" (exit code: ${
54+
result.status
55+
}):\n`,
56+
result.context.cmd,
57+
result.context.args.join(' '),
58+
'\n\n',
4959
result.output.trim(),
60+
'\n',
61+
'='.repeat(70),
62+
'\n',
5063
)
5164
}
5265
return result

e2e/__helpers__/test-case/run-result.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ export default class RunResult {
2020
args: string[]
2121
env: { [key: string]: string }
2222
config: jest.InitialOptions
23+
digest: string
2324
}>,
2425
) {}
2526
get logFilePath() {
@@ -59,11 +60,14 @@ export default class RunResult {
5960
return normalizeJestOutput(this.stdout)
6061
}
6162
get cmdLine() {
62-
return this.normalize(
63-
[this.context.cmd, ...this.context.args]
64-
.filter(a => !['-u', '--updateSnapshot', '--runInBand', '--'].includes(a))
65-
.join(' '),
63+
const args = [this.context.cmd, ...this.context.args].filter(
64+
a => !['-u', '--updateSnapshot', '--runInBand', '--'].includes(a),
6665
)
66+
const configIndex = args.indexOf('--config')
67+
if (configIndex !== -1) {
68+
args.splice(configIndex, 2)
69+
}
70+
return this.normalize(args.join(' '))
6771
}
6872

6973
ioFor(relFilePath: string): ProcessedFileIo {

e2e/__helpers__/test-case/runtime.ts

Lines changed: 107 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
import { sync as spawnSync } from 'cross-spawn'
2+
import { createHash } from 'crypto'
3+
import stringifyJson from 'fast-json-stable-stringify'
24
import {
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'
1822
import merge from 'lodash.merge'
1923
import { join, relative, resolve, sep } from 'path'
2024

2125
import * as Paths from '../../../scripts/lib/paths'
2226

2327
import RunResult from './run-result'
2428
import { PreparedTest, RunTestOptions } from './types'
25-
import { templateNameForPath } from './utils'
29+
import { enableOptimizations, templateNameForPath } from './utils'
2630

2731
const TEMPLATE_EXCLUDED_ITEMS = ['node_modules', 'package-lock.json']
2832

@@ -49,25 +53,18 @@ function hooksSourceWith(vars: Record<string, any>): string {
4953
}
5054

5155
export 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
}

e2e/__helpers__/test-case/types.ts

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import RunResult from './run-result'
55
export interface RunTestOptions {
66
template?: string
77
env?: {}
8-
args?: string[]
98
inject?: (() => any) | string
109
writeIo?: boolean
1110
jestConfig?: jest.ProjectConfig | any
@@ -30,6 +29,4 @@ export interface PreparedTest {
3029
workdir: string
3130
templateDir: string
3231
sourceDir: string
33-
ioDir: string
34-
hooksFile: string
3532
}

e2e/__helpers__/test-case/utils.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,3 +47,7 @@ export function normalizeJestOutput(output: string): string {
4747
export function escapeRegex(s: string) {
4848
return s.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&')
4949
}
50+
51+
export function enableOptimizations() {
52+
return !!process.env.TS_JEST_E2E_OPTIMIZATIONS
53+
}

0 commit comments

Comments
 (0)