ava 0.16.0 → 0.18.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/api.js +297 -265
- package/cli.js +15 -179
- package/index.js +5 -98
- package/index.js.flow +201 -0
- package/lib/assert.js +87 -53
- package/lib/ava-error.js +4 -8
- package/lib/ava-files.js +282 -0
- package/lib/babel-config.js +35 -73
- package/lib/beautify-stack.js +17 -16
- package/lib/caching-precompiler.js +72 -87
- package/lib/cli.js +181 -0
- package/lib/code-excerpt.js +57 -0
- package/lib/colors.js +6 -2
- package/lib/concurrent.js +62 -75
- package/lib/enhance-assert.js +57 -49
- package/lib/extract-stack.js +10 -0
- package/lib/fork.js +67 -68
- package/lib/format-assert-error.js +72 -0
- package/lib/globals.js +3 -8
- package/lib/hook.js +15 -20
- package/lib/logger.js +59 -82
- package/lib/main.js +90 -0
- package/lib/prefix-title.js +21 -0
- package/lib/process-adapter.js +108 -0
- package/lib/reporters/mini.js +260 -257
- package/lib/reporters/tap.js +80 -85
- package/lib/reporters/verbose.js +142 -115
- package/lib/run-status.js +110 -152
- package/lib/runner.js +125 -137
- package/lib/sequence.js +68 -84
- package/lib/serialize-error.js +68 -4
- package/lib/snapshot-state.js +30 -0
- package/lib/test-collection.js +144 -156
- package/lib/test-worker.js +45 -95
- package/lib/test.js +289 -318
- package/lib/throws-helper.js +9 -9
- package/lib/validate-test.js +48 -0
- package/lib/watcher.js +258 -297
- package/package.json +63 -53
- package/profile.js +68 -55
- package/readme.md +215 -101
- package/types/generated.d.ts +848 -228
- package/types/make.js +54 -23
- package/lib/send.js +0 -16
package/lib/watcher.js
CHANGED
|
@@ -1,357 +1,318 @@
|
|
|
1
1
|
'use strict';
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
2
|
+
const nodePath = require('path');
|
|
3
|
+
const debug = require('debug')('ava:watcher');
|
|
4
|
+
const diff = require('lodash.difference');
|
|
5
|
+
const chokidar = require('chokidar');
|
|
6
|
+
const flatten = require('arr-flatten');
|
|
7
|
+
const union = require('array-union');
|
|
8
|
+
const uniq = require('array-uniq');
|
|
9
|
+
const AvaFiles = require('./ava-files');
|
|
10
10
|
|
|
11
11
|
function rethrowAsync(err) {
|
|
12
|
-
// Don't swallow exceptions. Note that any
|
|
13
|
-
// been logged
|
|
14
|
-
setImmediate(
|
|
12
|
+
// Don't swallow exceptions. Note that any
|
|
13
|
+
// expected error should already have been logged
|
|
14
|
+
setImmediate(() => {
|
|
15
15
|
throw err;
|
|
16
16
|
});
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
this.runVector = 0;
|
|
30
|
-
this.run = function (specificFiles) {
|
|
31
|
-
if (this.runVector > 0) {
|
|
32
|
-
var cleared = this.clearLogOnNextRun && logger.clear();
|
|
33
|
-
if (!cleared) {
|
|
34
|
-
logger.reset();
|
|
35
|
-
logger.section();
|
|
36
|
-
}
|
|
37
|
-
this.clearLogOnNextRun = true;
|
|
38
|
-
|
|
39
|
-
logger.reset();
|
|
40
|
-
logger.start();
|
|
19
|
+
class Debouncer {
|
|
20
|
+
constructor(watcher) {
|
|
21
|
+
this.watcher = watcher;
|
|
22
|
+
this.timer = null;
|
|
23
|
+
this.repeat = false;
|
|
24
|
+
}
|
|
25
|
+
debounce() {
|
|
26
|
+
if (this.timer) {
|
|
27
|
+
this.again = true;
|
|
28
|
+
return;
|
|
41
29
|
}
|
|
42
30
|
|
|
43
|
-
|
|
31
|
+
const timer = this.timer = setTimeout(() => {
|
|
32
|
+
this.watcher.busy.then(() => {
|
|
33
|
+
// Do nothing if debouncing was canceled while waiting for the busy
|
|
34
|
+
// promise to fulfil
|
|
35
|
+
if (this.timer !== timer) {
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (this.again) {
|
|
40
|
+
this.timer = null;
|
|
41
|
+
this.again = false;
|
|
42
|
+
this.debounce();
|
|
43
|
+
} else {
|
|
44
|
+
this.watcher.runAfterChanges();
|
|
45
|
+
this.timer = null;
|
|
46
|
+
this.again = false;
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
}, 10);
|
|
50
|
+
}
|
|
51
|
+
cancel() {
|
|
52
|
+
if (this.timer) {
|
|
53
|
+
clearTimeout(this.timer);
|
|
54
|
+
this.timer = null;
|
|
55
|
+
this.again = false;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
44
59
|
|
|
45
|
-
|
|
60
|
+
class TestDependency {
|
|
61
|
+
constructor(file, sources) {
|
|
62
|
+
this.file = file;
|
|
63
|
+
this.sources = sources;
|
|
64
|
+
}
|
|
65
|
+
contains(source) {
|
|
66
|
+
return this.sources.indexOf(source) !== -1;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
46
69
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
70
|
+
class Watcher {
|
|
71
|
+
constructor(logger, api, files, sources) {
|
|
72
|
+
this.debouncer = new Debouncer(this);
|
|
73
|
+
this.avaFiles = new AvaFiles({
|
|
74
|
+
files,
|
|
75
|
+
sources
|
|
76
|
+
});
|
|
51
77
|
|
|
52
|
-
|
|
78
|
+
this.clearLogOnNextRun = true;
|
|
79
|
+
this.runVector = 0;
|
|
80
|
+
this.run = specificFiles => {
|
|
81
|
+
if (this.runVector > 0) {
|
|
82
|
+
const cleared = this.clearLogOnNextRun && logger.clear();
|
|
83
|
+
if (!cleared) {
|
|
84
|
+
logger.reset();
|
|
85
|
+
logger.section();
|
|
86
|
+
}
|
|
87
|
+
this.clearLogOnNextRun = true;
|
|
53
88
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
// run, together with the remaining specific files.
|
|
57
|
-
var remainingFiles = diff(specificFiles, exclusiveFiles);
|
|
58
|
-
specificFiles = this.filesWithExclusiveTests.concat(remainingFiles);
|
|
89
|
+
logger.reset();
|
|
90
|
+
logger.start();
|
|
59
91
|
}
|
|
60
|
-
}
|
|
61
92
|
|
|
62
|
-
|
|
63
|
-
this.busy = api.run(specificFiles || files, {
|
|
64
|
-
runOnlyExclusive: runOnlyExclusive
|
|
65
|
-
}).then(function (runStatus) {
|
|
66
|
-
runStatus.previousFailCount = self.sumPreviousFailures(currentVector);
|
|
67
|
-
logger.finish(runStatus);
|
|
93
|
+
const currentVector = this.runVector += 1;
|
|
68
94
|
|
|
69
|
-
|
|
70
|
-
self.clearLogOnNextRun = self.clearLogOnNextRun && badCounts === 0;
|
|
71
|
-
}, rethrowAsync);
|
|
72
|
-
};
|
|
95
|
+
let runOnlyExclusive = false;
|
|
73
96
|
|
|
74
|
-
|
|
75
|
-
|
|
97
|
+
if (specificFiles) {
|
|
98
|
+
const exclusiveFiles = specificFiles.filter(file => this.filesWithExclusiveTests.indexOf(file) !== -1);
|
|
76
99
|
|
|
77
|
-
|
|
78
|
-
this.trackExclusivity(api);
|
|
100
|
+
runOnlyExclusive = exclusiveFiles.length !== this.filesWithExclusiveTests.length;
|
|
79
101
|
|
|
80
|
-
|
|
81
|
-
|
|
102
|
+
if (runOnlyExclusive) {
|
|
103
|
+
// The test files that previously contained exclusive tests are always
|
|
104
|
+
// run, together with the remaining specific files.
|
|
105
|
+
const remainingFiles = diff(specificFiles, exclusiveFiles);
|
|
106
|
+
specificFiles = this.filesWithExclusiveTests.concat(remainingFiles);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
82
109
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
110
|
+
this.busy = api.run(specificFiles || files, {runOnlyExclusive})
|
|
111
|
+
.then(runStatus => {
|
|
112
|
+
runStatus.previousFailCount = this.sumPreviousFailures(currentVector);
|
|
113
|
+
logger.finish(runStatus);
|
|
87
114
|
|
|
88
|
-
|
|
115
|
+
const badCounts = runStatus.failCount + runStatus.rejectionCount + runStatus.exceptionCount;
|
|
116
|
+
this.clearLogOnNextRun = this.clearLogOnNextRun && badCounts === 0;
|
|
117
|
+
})
|
|
118
|
+
.catch(rethrowAsync);
|
|
119
|
+
};
|
|
89
120
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
var patterns = this.avaFiles.getChokidarPatterns();
|
|
93
|
-
|
|
94
|
-
chokidar.watch(patterns.paths, {
|
|
95
|
-
ignored: patterns.ignored,
|
|
96
|
-
ignoreInitial: true
|
|
97
|
-
}).on('all', function (event, path) {
|
|
98
|
-
if (event === 'add' || event === 'change' || event === 'unlink') {
|
|
99
|
-
debug('Detected %s of %s', event, path);
|
|
100
|
-
self.dirtyStates[path] = event;
|
|
101
|
-
self.debouncer.debounce();
|
|
102
|
-
}
|
|
103
|
-
});
|
|
104
|
-
};
|
|
121
|
+
this.testDependencies = [];
|
|
122
|
+
this.trackTestDependencies(api, sources);
|
|
105
123
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
var isSource = this.avaFiles.makeSourceMatcher();
|
|
124
|
+
this.filesWithExclusiveTests = [];
|
|
125
|
+
this.trackExclusivity(api);
|
|
109
126
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
};
|
|
127
|
+
this.filesWithFailures = [];
|
|
128
|
+
this.trackFailures(api);
|
|
113
129
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
130
|
+
this.dirtyStates = {};
|
|
131
|
+
this.watchFiles();
|
|
132
|
+
this.rerunAll();
|
|
133
|
+
}
|
|
134
|
+
watchFiles() {
|
|
135
|
+
const patterns = this.avaFiles.getChokidarPatterns();
|
|
136
|
+
|
|
137
|
+
chokidar.watch(patterns.paths, {
|
|
138
|
+
ignored: patterns.ignored,
|
|
139
|
+
ignoreInitial: true
|
|
140
|
+
}).on('all', (event, path) => {
|
|
141
|
+
if (event === 'add' || event === 'change' || event === 'unlink') {
|
|
142
|
+
debug('Detected %s of %s', event, path);
|
|
143
|
+
this.dirtyStates[path] = event;
|
|
144
|
+
this.debouncer.debounce();
|
|
145
|
+
}
|
|
118
146
|
});
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
147
|
+
}
|
|
148
|
+
trackTestDependencies(api) {
|
|
149
|
+
const relative = absPath => nodePath.relative(process.cwd(), absPath);
|
|
150
|
+
|
|
151
|
+
api.on('test-run', runStatus => {
|
|
152
|
+
runStatus.on('dependencies', (file, dependencies) => {
|
|
153
|
+
const sourceDeps = dependencies.map(relative).filter(this.avaFiles.isSource);
|
|
154
|
+
this.updateTestDependencies(file, sourceDeps);
|
|
155
|
+
});
|
|
126
156
|
});
|
|
127
|
-
|
|
128
|
-
return;
|
|
129
157
|
}
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
return
|
|
158
|
+
updateTestDependencies(file, sources) {
|
|
159
|
+
if (sources.length === 0) {
|
|
160
|
+
this.testDependencies = this.testDependencies.filter(dep => dep.file !== file);
|
|
161
|
+
return;
|
|
134
162
|
}
|
|
135
163
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
if (!isUpdate) {
|
|
142
|
-
this.testDependencies.push(new TestDependency(file, sources));
|
|
143
|
-
}
|
|
144
|
-
};
|
|
145
|
-
|
|
146
|
-
Watcher.prototype.trackExclusivity = function (api) {
|
|
147
|
-
var self = this;
|
|
148
|
-
|
|
149
|
-
api.on('stats', function (stats) {
|
|
150
|
-
self.updateExclusivity(stats.file, stats.hasExclusive);
|
|
151
|
-
});
|
|
152
|
-
};
|
|
153
|
-
|
|
154
|
-
Watcher.prototype.updateExclusivity = function (file, hasExclusiveTests) {
|
|
155
|
-
var index = this.filesWithExclusiveTests.indexOf(file);
|
|
156
|
-
|
|
157
|
-
if (hasExclusiveTests && index === -1) {
|
|
158
|
-
this.filesWithExclusiveTests.push(file);
|
|
159
|
-
} else if (!hasExclusiveTests && index !== -1) {
|
|
160
|
-
this.filesWithExclusiveTests.splice(index, 1);
|
|
161
|
-
}
|
|
162
|
-
};
|
|
163
|
-
|
|
164
|
-
Watcher.prototype.trackFailures = function (api) {
|
|
165
|
-
var self = this;
|
|
164
|
+
const isUpdate = this.testDependencies.some(dep => {
|
|
165
|
+
if (dep.file !== file) {
|
|
166
|
+
return false;
|
|
167
|
+
}
|
|
166
168
|
|
|
167
|
-
|
|
168
|
-
files.forEach(function (file) {
|
|
169
|
-
self.pruneFailures(nodePath.relative('.', file));
|
|
170
|
-
});
|
|
169
|
+
dep.sources = sources;
|
|
171
170
|
|
|
172
|
-
|
|
173
|
-
runStatus.on('error', function (err) {
|
|
174
|
-
self.countFailure(err.file, currentVector);
|
|
175
|
-
});
|
|
176
|
-
runStatus.on('test', function (result) {
|
|
177
|
-
if (result.error) {
|
|
178
|
-
self.countFailure(result.file, currentVector);
|
|
179
|
-
}
|
|
171
|
+
return true;
|
|
180
172
|
});
|
|
181
|
-
});
|
|
182
|
-
};
|
|
183
|
-
|
|
184
|
-
Watcher.prototype.pruneFailures = function (file) {
|
|
185
|
-
this.filesWithFailures = this.filesWithFailures.filter(function (state) {
|
|
186
|
-
return state.file !== file;
|
|
187
|
-
});
|
|
188
|
-
};
|
|
189
173
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
if (state.file !== file) {
|
|
193
|
-
return false;
|
|
174
|
+
if (!isUpdate) {
|
|
175
|
+
this.testDependencies.push(new TestDependency(file, sources));
|
|
194
176
|
}
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
if (!isUpdate) {
|
|
201
|
-
this.filesWithFailures.push({
|
|
202
|
-
file: file,
|
|
203
|
-
vector: vector,
|
|
204
|
-
count: 1
|
|
177
|
+
}
|
|
178
|
+
trackExclusivity(api) {
|
|
179
|
+
api.on('stats', stats => {
|
|
180
|
+
this.updateExclusivity(stats.file, stats.hasExclusive);
|
|
205
181
|
});
|
|
206
182
|
}
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
Watcher.prototype.sumPreviousFailures = function (beforeVector) {
|
|
210
|
-
var total = 0;
|
|
183
|
+
updateExclusivity(file, hasExclusiveTests) {
|
|
184
|
+
const index = this.filesWithExclusiveTests.indexOf(file);
|
|
211
185
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
186
|
+
if (hasExclusiveTests && index === -1) {
|
|
187
|
+
this.filesWithExclusiveTests.push(file);
|
|
188
|
+
} else if (!hasExclusiveTests && index !== -1) {
|
|
189
|
+
this.filesWithExclusiveTests.splice(index, 1);
|
|
215
190
|
}
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
191
|
+
}
|
|
192
|
+
trackFailures(api) {
|
|
193
|
+
api.on('test-run', (runStatus, files) => {
|
|
194
|
+
files.forEach(file => {
|
|
195
|
+
this.pruneFailures(nodePath.relative(process.cwd(), file));
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
const currentVector = this.runVector;
|
|
199
|
+
runStatus.on('error', err => {
|
|
200
|
+
this.countFailure(err.file, currentVector);
|
|
201
|
+
});
|
|
202
|
+
runStatus.on('test', result => {
|
|
203
|
+
if (result.error) {
|
|
204
|
+
this.countFailure(result.file, currentVector);
|
|
205
|
+
}
|
|
206
|
+
});
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
pruneFailures(file) {
|
|
210
|
+
this.filesWithFailures = this.filesWithFailures.filter(state => state.file !== file);
|
|
211
|
+
}
|
|
212
|
+
countFailure(file, vector) {
|
|
213
|
+
const isUpdate = this.filesWithFailures.some(state => {
|
|
214
|
+
if (state.file !== file) {
|
|
215
|
+
return false;
|
|
216
|
+
}
|
|
231
217
|
|
|
232
|
-
|
|
233
|
-
|
|
218
|
+
state.count++;
|
|
219
|
+
return true;
|
|
220
|
+
});
|
|
234
221
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
222
|
+
if (!isUpdate) {
|
|
223
|
+
this.filesWithFailures.push({
|
|
224
|
+
file,
|
|
225
|
+
vector,
|
|
226
|
+
count: 1
|
|
227
|
+
});
|
|
239
228
|
}
|
|
229
|
+
}
|
|
230
|
+
sumPreviousFailures(beforeVector) {
|
|
231
|
+
let total = 0;
|
|
240
232
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
// Cancel the debouncer again, it might have restarted while waiting for
|
|
246
|
-
// the busy promise to fulfil.
|
|
247
|
-
self.debouncer.cancel();
|
|
248
|
-
self.clearLogOnNextRun = false;
|
|
249
|
-
self.rerunAll();
|
|
233
|
+
this.filesWithFailures.forEach(state => {
|
|
234
|
+
if (state.vector < beforeVector) {
|
|
235
|
+
total += state.count;
|
|
236
|
+
}
|
|
250
237
|
});
|
|
251
|
-
});
|
|
252
|
-
};
|
|
253
|
-
|
|
254
|
-
Watcher.prototype.rerunAll = function () {
|
|
255
|
-
this.dirtyStates = {};
|
|
256
|
-
this.run();
|
|
257
|
-
};
|
|
258
|
-
|
|
259
|
-
Watcher.prototype.runAfterChanges = function () {
|
|
260
|
-
var dirtyStates = this.dirtyStates;
|
|
261
|
-
this.dirtyStates = {};
|
|
262
|
-
|
|
263
|
-
var dirtyPaths = Object.keys(dirtyStates);
|
|
264
|
-
var dirtyTests = dirtyPaths.filter(this.isTest);
|
|
265
|
-
var dirtySources = diff(dirtyPaths, dirtyTests);
|
|
266
|
-
var addedOrChangedTests = dirtyTests.filter(function (path) {
|
|
267
|
-
return dirtyStates[path] !== 'unlink';
|
|
268
|
-
});
|
|
269
|
-
var unlinkedTests = diff(dirtyTests, addedOrChangedTests);
|
|
270
238
|
|
|
271
|
-
|
|
272
|
-
// No need to rerun tests if the only change is that tests were deleted.
|
|
273
|
-
if (unlinkedTests.length === dirtyPaths.length) {
|
|
274
|
-
return;
|
|
239
|
+
return total;
|
|
275
240
|
}
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
241
|
+
cleanUnlinkedTests(unlinkedTests) {
|
|
242
|
+
unlinkedTests.forEach(testFile => {
|
|
243
|
+
this.updateTestDependencies(testFile, []);
|
|
244
|
+
this.updateExclusivity(testFile, false);
|
|
245
|
+
this.pruneFailures(testFile);
|
|
246
|
+
});
|
|
281
247
|
}
|
|
248
|
+
observeStdin(stdin) {
|
|
249
|
+
stdin.resume();
|
|
250
|
+
stdin.setEncoding('utf8');
|
|
282
251
|
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
debug('%s is a dependency of %s', path, dep.file);
|
|
289
|
-
return dep.file;
|
|
290
|
-
});
|
|
291
|
-
}, this).filter(function (tests) {
|
|
292
|
-
return tests.length > 0;
|
|
293
|
-
});
|
|
252
|
+
stdin.on('data', data => {
|
|
253
|
+
data = data.trim().toLowerCase();
|
|
254
|
+
if (data !== 'r' && data !== 'rs') {
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
294
257
|
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
258
|
+
// Cancel the debouncer, it might rerun specific tests whereas *all* tests
|
|
259
|
+
// need to be rerun
|
|
260
|
+
this.debouncer.cancel();
|
|
261
|
+
this.busy.then(() => {
|
|
262
|
+
// Cancel the debouncer again, it might have restarted while waiting for
|
|
263
|
+
// the busy promise to fulfil
|
|
264
|
+
this.debouncer.cancel();
|
|
265
|
+
this.clearLogOnNextRun = false;
|
|
266
|
+
this.rerunAll();
|
|
267
|
+
});
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
rerunAll() {
|
|
271
|
+
this.dirtyStates = {};
|
|
299
272
|
this.run();
|
|
300
|
-
return;
|
|
301
273
|
}
|
|
274
|
+
runAfterChanges() {
|
|
275
|
+
const dirtyStates = this.dirtyStates;
|
|
276
|
+
this.dirtyStates = {};
|
|
302
277
|
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
this.watcher = watcher;
|
|
309
|
-
this.timer = null;
|
|
310
|
-
this.repeat = false;
|
|
311
|
-
}
|
|
278
|
+
const dirtyPaths = Object.keys(dirtyStates);
|
|
279
|
+
const dirtyTests = dirtyPaths.filter(this.avaFiles.isTest);
|
|
280
|
+
const dirtySources = diff(dirtyPaths, dirtyTests);
|
|
281
|
+
const addedOrChangedTests = dirtyTests.filter(path => dirtyStates[path] !== 'unlink');
|
|
282
|
+
const unlinkedTests = diff(dirtyTests, addedOrChangedTests);
|
|
312
283
|
|
|
313
|
-
|
|
314
|
-
if (this.timer) {
|
|
315
|
-
this.again = true;
|
|
316
|
-
return;
|
|
317
|
-
}
|
|
284
|
+
this.cleanUnlinkedTests(unlinkedTests);
|
|
318
285
|
|
|
319
|
-
|
|
286
|
+
// No need to rerun tests if the only change is that tests were deleted
|
|
287
|
+
if (unlinkedTests.length === dirtyPaths.length) {
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
320
290
|
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
return;
|
|
327
|
-
}
|
|
291
|
+
if (dirtySources.length === 0) {
|
|
292
|
+
// Run any new or changed tests
|
|
293
|
+
this.run(addedOrChangedTests);
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
328
296
|
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
297
|
+
// Try to find tests that depend on the changed source files
|
|
298
|
+
const testsBySource = dirtySources.map(path => {
|
|
299
|
+
return this.testDependencies.filter(dep => dep.contains(path)).map(dep => {
|
|
300
|
+
debug('%s is a dependency of %s', path, dep.file);
|
|
301
|
+
return dep.file;
|
|
302
|
+
});
|
|
303
|
+
}, this).filter(tests => tests.length > 0);
|
|
304
|
+
|
|
305
|
+
// Rerun all tests if source files were changed that could not be traced to
|
|
306
|
+
// specific tests
|
|
307
|
+
if (testsBySource.length !== dirtySources.length) {
|
|
308
|
+
debug('Sources remain that cannot be traced to specific tests. Rerunning all tests');
|
|
309
|
+
this.run();
|
|
310
|
+
return;
|
|
311
|
+
}
|
|
341
312
|
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
clearTimeout(this.timer);
|
|
345
|
-
this.timer = null;
|
|
346
|
-
this.again = false;
|
|
313
|
+
// Run all affected tests
|
|
314
|
+
this.run(union(addedOrChangedTests, uniq(flatten(testsBySource))));
|
|
347
315
|
}
|
|
348
|
-
};
|
|
349
|
-
|
|
350
|
-
function TestDependency(file, sources) {
|
|
351
|
-
this.file = file;
|
|
352
|
-
this.sources = sources;
|
|
353
316
|
}
|
|
354
317
|
|
|
355
|
-
|
|
356
|
-
return this.sources.indexOf(source) !== -1;
|
|
357
|
-
};
|
|
318
|
+
module.exports = Watcher;
|