Skip to content

Commit 968a301

Browse files
rubennortethymikee
andauthored
Fix invalid re-run of tests in watch mode (#7347)
* Add failing e2e test * Stop re-running tests when mtime has not changed * fix test * fix copyright * adjust file accessed check * bring back reading fileMetadata from async callback Co-authored-by: Michał Pierzchała <[email protected]>
1 parent 5d1be03 commit 968a301

File tree

3 files changed

+90
-1
lines changed

3 files changed

+90
-1
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
- `[jest-circus]` [**BREAKING**] Fail tests if a test takes a done callback and have return values ([#9129](https://github.com/facebook/jest/pull/9129))
1414
- `[jest-circus]` [**BREAKING**] Throw a proper error if a test / hook is defined asynchronously ([#8096](https://github.com/facebook/jest/pull/8096))
1515
- `[jest-config, jest-resolve]` [**BREAKING**] Remove support for `browser` field ([#9943](https://github.com/facebook/jest/pull/9943))
16+
- `[jest-haste-map]` Stop reporting files as changed when they are only accessed ([#7347](https://github.com/facebook/jest/pull/7347))
1617
- `[jest-resolve]` Show relative path from root dir for `module not found` errors ([#9963](https://github.com/facebook/jest/pull/9963))
1718

1819
### Chore & Maintenance
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
/**
2+
* Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*
7+
*/
8+
9+
import * as os from 'os';
10+
import * as path from 'path';
11+
import * as fs from 'graceful-fs';
12+
import {cleanup, writeFiles} from '../Utils';
13+
import {runContinuous} from '../runJest';
14+
15+
const DIR = path.resolve(os.tmpdir(), 'watch_mode_no_access');
16+
17+
const sleep = (time: number) =>
18+
new Promise(resolve => setTimeout(resolve, time));
19+
20+
beforeEach(() => cleanup(DIR));
21+
afterAll(() => cleanup(DIR));
22+
23+
const setupFiles = () => {
24+
writeFiles(DIR, {
25+
'__tests__/foo.test.js': `
26+
const foo = require('../foo');
27+
test('foo', () => { expect(typeof foo).toBe('number'); });
28+
`,
29+
'foo.js': `
30+
module.exports = 0;
31+
`,
32+
'package.json': JSON.stringify({
33+
jest: {},
34+
}),
35+
});
36+
};
37+
38+
let testRun: ReturnType<typeof runContinuous>;
39+
40+
afterEach(async () => {
41+
if (testRun) {
42+
await testRun.end();
43+
}
44+
});
45+
46+
test('does not re-run tests when only access time is modified', async () => {
47+
setupFiles();
48+
49+
testRun = runContinuous(DIR, ['--watchAll', '--no-watchman']);
50+
51+
const testCompletedRE = /Ran all test suites./g;
52+
const numberOfTestRuns = (stderr: string): number => {
53+
const matches = stderr.match(testCompletedRE);
54+
return matches ? matches.length : 0;
55+
};
56+
57+
// First run
58+
await testRun.waitUntil(({stderr}) => numberOfTestRuns(stderr) === 1);
59+
60+
// Should re-run the test
61+
const modulePath = path.join(DIR, 'foo.js');
62+
const stat = fs.lstatSync(modulePath);
63+
fs.utimesSync(modulePath, stat.atime.getTime(), stat.mtime.getTime());
64+
65+
await testRun.waitUntil(({stderr}) => numberOfTestRuns(stderr) === 2);
66+
67+
// Should NOT re-run the test
68+
const fakeATime = 1541723621;
69+
fs.utimesSync(modulePath, fakeATime, stat.mtime.getTime());
70+
await sleep(3000);
71+
expect(numberOfTestRuns(testRun.getCurrentOutput().stderr)).toBe(2);
72+
73+
// Should re-run the test
74+
fs.writeFileSync(modulePath, 'module.exports = 1;', {encoding: 'utf-8'});
75+
await testRun.waitUntil(({stderr}) => numberOfTestRuns(stderr) === 3);
76+
});

packages/jest-haste-map/src/index.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -846,6 +846,19 @@ class HasteMap extends EventEmitter {
846846
return;
847847
}
848848

849+
const relativeFilePath = fastPath.relative(rootDir, filePath);
850+
const fileMetadata = hasteMap.files.get(relativeFilePath);
851+
852+
// The file has been accessed, not modified
853+
if (
854+
type === 'change' &&
855+
fileMetadata &&
856+
stat &&
857+
fileMetadata[H.MTIME] === stat.mtime.getTime()
858+
) {
859+
return;
860+
}
861+
849862
changeQueue = changeQueue
850863
.then(() => {
851864
// If we get duplicate events for the same file, ignore them.
@@ -879,7 +892,6 @@ class HasteMap extends EventEmitter {
879892
return null;
880893
};
881894

882-
const relativeFilePath = fastPath.relative(rootDir, filePath);
883895
const fileMetadata = hasteMap.files.get(relativeFilePath);
884896

885897
// If it's not an addition, delete the file and all its metadata

0 commit comments

Comments
 (0)