1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
const fs = require('fs').promises
const path = require('path')
const test = require('ava')
const {copyFiles, execFilePromise, matchOutput, wait} = require('./util')
const recipes = require('./dag-makefile')
process.chdir(__dirname)
const FILES_DIR = 'dag-files'
const DAG_SCRATCH_DIR = 'dag-scratch'
const DAG_EXPECTED_DIR = 'dag-expected'
const outputs = recipes.map(({target, dependencies, command}) => ({
target,
dependencies,
lines: [command.join(' ')]
}))
function touch(file) {
const now = Date.now()
return fs.utimes(file, now, now)
}
async function makeDag(dir, ...targets) {
const {stdout} = await execFilePromise(
'node', ['../../make.js', '../dag-makefile.js', ...targets],
{cwd: path.resolve(__dirname, dir)}
)
return stdout
}
test.serial('dag from scratch', async t => {
// Build target A
await copyFiles(FILES_DIR, DAG_SCRATCH_DIR)
const output = await makeDag(DAG_SCRATCH_DIR, 'a.txt')
// Check that the result is correct
let result
try { result = await fs.readFile(`${DAG_SCRATCH_DIR}/a.txt`, 'utf8') }
catch { throw new Error('a.txt was not created') }
t.deepEqual(
result,
await fs.readFile(`${DAG_EXPECTED_DIR}/a.txt`, 'utf8'),
`File ${DAG_SCRATCH_DIR}/a.txt has the wrong contents`
)
// Check that the output from make.js is valid
matchOutput(output, outputs)
})
test('dag build again', async t => {
// Copy files from the full build
const dir = 'dag-rebuild'
await copyFiles(DAG_SCRATCH_DIR, dir)
// Store the initial modified times of all files
const files = await fs.readdir(dir)
const initialMtimes = await Promise.all(files.map(async file => {
const {mtimeMs} = await fs.stat(`${dir}/${file}`)
return {file, mtimeMs}
}))
// Wait 100 ms and rebuild so we can see which modified times changed
await wait(100)
const output = await makeDag(dir, 'a.txt')
// Check that the result is correct
let result
try { result = await fs.readFile(`${dir}/a.txt`, 'utf8') }
catch { throw new Error('a.txt was not created') }
t.deepEqual(
result,
await fs.readFile(`${DAG_EXPECTED_DIR}/a.txt`, 'utf8'),
`File ${dir}/a.txt has the wrong contents`
)
// Check that no files were modified
await Promise.all(initialMtimes.map(async ({file, mtimeMs}) => {
const {mtimeMs: newMtime} = await fs.stat(`${dir}/${file}`)
t.deepEqual(mtimeMs, newMtime, `File ${file} should not have been rebuilt`)
}))
// Check that the output from make.js is valid
t.deepEqual(output, '', 'Unexpected make.js output')
})
test('dag touch F and H', async t => {
// Copy files from the full build
const dir = 'dag-touch'
await copyFiles(DAG_SCRATCH_DIR, dir)
// Change the modified times of F and H
await wait(100)
await Promise.all([touch(`${dir}/f.txt`), touch(`${dir}/h.txt`)])
// Store the initial modified times of all files
const files = await fs.readdir(dir)
const initialMtimes = await Promise.all(files.map(async file => {
const {mtimeMs} = await fs.stat(`${dir}/${file}`)
return {file, mtimeMs}
}))
// Wait 100 ms and rebuild so we can see which modified times changed
await wait(100)
const output = await makeDag(dir, 'a.txt')
// Check that the result is correct
let result
try { result = await fs.readFile(`${dir}/a.txt`, 'utf8') }
catch { throw new Error('a.txt was not created') }
t.deepEqual(
result,
await fs.readFile(`${DAG_EXPECTED_DIR}/a.txt`, 'utf8'),
`File ${dir}/a.txt has the wrong contents`
)
// Check that the correct files were modified
const expectedRebuilt = new Set(['a.txt', 'b.txt', 'c.txt', 'd.txt', 'e.txt'])
await Promise.all(initialMtimes.map(async ({file, mtimeMs}) => {
const {mtimeMs: newMtime} = await fs.stat(`${dir}/${file}`)
if (expectedRebuilt.has(file)) {
t.assert(newMtime > mtimeMs, `File ${file} should have been rebuilt`)
}
else t.deepEqual(mtimeMs, newMtime, `File ${file} should not have been rebuilt`)
}))
// Check that the output from make.js is valid
matchOutput(output, outputs.filter(({target}) => expectedRebuilt.has(target)))
})
test('dag B and D from scratch', async t => {
// Build targets B and D
const dir = 'dag-b-and-d'
await copyFiles(FILES_DIR, dir)
const output = await makeDag(dir, 'b.txt', 'd.txt')
// Check that results are correct
await Promise.all(['b.txt', 'd.txt'].map(async file => {
let result
try { result = await fs.readFile(`${dir}/${file}`, 'utf8') }
catch { throw new Error('${file} was not created') }
t.deepEqual(
result,
await fs.readFile(`${DAG_EXPECTED_DIR}/${file}`, 'utf8'),
`File ${dir}/${file} has the wrong contents`
)
}))
// Check that A and C weren't built
await Promise.all(['a.txt', 'c.txt'].map(async file => {
// access() should reject
try { await fs.access(`${dir}/${file}`) }
catch { return }
t.fail(`File ${dir}/${file} should not have been built`)
}))
// Check that the output from make.js is valid
matchOutput(output, outputs.filter(({target}) =>
!['a.txt', 'c.txt'].includes(target)
))
})