Build infra for tracking dev-specific failures (#8228)
I'll plan to change all of our console.error and component-tree expects to expectDev. It's a little annoying that we need to make sure tests don't throw (see my change to normalizeCodeLocInfo) but any alternative would seem to require two separate test runs or a much more cumbersome syntax.
Ben Alpert committed
Nov 13, 2016 at 14:00 UTC
64cba04bf976f2f11b428bb9ec1108cd2da70e53
7 files changed
+87
-18
.eslintrc.js
+9
-3
@@ -1,5 +1,7 @@
1
+'use strict';
2
+
3
const OFF = 0;
2
-const WARNING = 1;
4
+// const WARNING = 1;
5
const ERROR = 2;
6
7
module.exports = {
@@ -13,7 +15,7 @@ module.exports = {
15
],
16
17
ecmaFeatures: {
16
- modules: false
18
+ modules: false,
19
},
20
21
// We're stricter than the default config, mostly. We'll override a few rules
@@ -70,5 +72,9 @@ module.exports = {
72
// CUSTOM RULES
73
// the second argument of warning/invariant should be a literal string
74
'react-internal/warning-and-invariant-args': ERROR,
73
- }
75
+ },
76
+
77
+ globals: {
78
+ expectDev: true,
79
+ },
80
};
package.json
+1
@@ -60,6 +60,7 @@
60
"gzip-js": "~0.3.2",
61
"jest": "^15.1.1",
62
"jest-config": "^15.1.1",
63
+ "jest-jasmine2": "^15.1.1",
64
"jest-runtime": "^15.1.1",
65
"loose-envify": "^1.1.0",
66
"merge-stream": "^1.0.0",
scripts/fiber/record-tests
+46
-6
@@ -27,19 +27,51 @@ function wrapRunnerFile(runnerPath) {
27
filename,
28
`
29
'use strict';
30
+ var runnerPath = ${JSON.stringify(runnerPath)};
31
var wrap = require(${JSON.stringify(__filename)}).wrapRunner;
31
- var original = require(${JSON.stringify(runnerPath)});
32
- module.exports = wrap(original);
32
+ module.exports = wrap(runnerPath);
33
`
34
);
35
return filename;
36
}
37
38
-function wrapRunner(original) {
39
- return function runner(config, environment, runtime, testPath) {
40
- return original(config, environment, runtime, testPath)
38
+function wrapRunner(originalPath) {
39
+ const original = require(originalPath);
40
+ // Assuming originalPath is .../jest-jasmine2/build/index.js
41
+ const JasmineReporter = require(
42
+ path.join(path.dirname(originalPath), 'reporter.js')
43
+ );
44
+
45
+ // For each spec, we store whether there was any expectDev() failure. This
46
+ // relies on the results being returned in the same order as they are run.
47
+ const hadDevFailures = [];
48
+ let environment;
49
+
50
+ const oldSpecStarted = JasmineReporter.prototype.specStarted;
51
+ JasmineReporter.prototype.specStarted = function(result) {
52
+ oldSpecStarted.apply(this, arguments);
53
+
54
+ environment.global.__suppressDevFailures = true;
55
+ environment.global.__hadDevFailures = false;
56
+ };
57
+
58
+ const oldSpecDone = JasmineReporter.prototype.specDone;
59
+ JasmineReporter.prototype.specDone = function(result) {
60
+ oldSpecDone.apply(this, arguments);
61
+
62
+ environment.global.__suppressDevFailures = false;
63
+ hadDevFailures.push(environment.global.__hadDevFailures);
64
+ };
65
+
66
+ return function runner(config, env, runtime, testPath) {
67
+ environment = env;
68
+ return original(config, env, runtime, testPath)
69
.then((results) => {
70
results.failureMessage = null;
71
+ hadDevFailures.forEach((hadFailures, i) => {
72
+ results.testResults[i].hadDevFailures = hadFailures;
73
+ });
74
+ hadDevFailures.length = 0;
75
return results;
76
});
77
};
@@ -91,7 +123,11 @@ function recordTests(trackFacts) {
123
.then((runResults) => {
124
const passing = formatResults(
125
runResults,
94
- (file, test) => test.status === 'passed'
126
+ (file, test) => test.status === 'passed' && !test.hadDevFailures
127
+ );
128
+ const passingExceptDev = formatResults(
129
+ runResults,
130
+ (file, test) => test.status === 'passed' && test.hadDevFailures
131
);
132
const failing = formatResults(
133
runResults,
@@ -101,6 +137,10 @@ function recordTests(trackFacts) {
137
path.join(__dirname, 'tests-passing.txt'),
138
passing + '\n'
139
);
140
+ fs.writeFileSync(
141
+ path.join(__dirname, 'tests-passing-except-dev.txt'),
142
+ passingExceptDev + '\n'
143
+ );
144
fs.writeFileSync(
145
path.join(__dirname, 'tests-failing.txt'),
146
failing + '\n'
scripts/fiber/tests-failing.txt
-4
@@ -505,10 +505,6 @@ src/renderers/shared/shared/__tests__/ReactTreeTraversal-test.js
505
* should leave to the window
506
* should leave to the window from the shallowest
507
508
-src/renderers/shared/stack/reconciler/__tests__/ReactChildReconciler-test.js
509
-* warns for duplicated keys
510
-* warns for duplicated keys with component stack info
511
-
508
src/renderers/shared/stack/reconciler/__tests__/ReactComponent-test.js
509
* should throw on invalid render targets
510
* should throw when supplying a ref outside of render method
scripts/fiber/tests-passing-except-dev.txt
new
+3
@@ -0,0 +1,3 @@
1
+src/renderers/shared/stack/reconciler/__tests__/ReactChildReconciler-test.js
2
+* warns for duplicated keys
3
+* warns for duplicated keys with component stack info
scripts/jest/test-framework-setup.js
+23
@@ -58,3 +58,26 @@ env.afterEach(() => {
58
expect(console.error).toBeReset();
59
expect(console.error).toNotHaveBeenCalled();
60
});
61
+
62
+function wrapDevMatcher(obj, name) {
63
+ const original = obj[name];
64
+ obj[name] = function devMatcher() {
65
+ try {
66
+ original.apply(this, arguments);
67
+ } catch (e) {
68
+ global.__hadDevFailures = e.stack;
69
+ }
70
+ };
71
+}
72
+
73
+const expectDev = function expectDev(actual) {
74
+ const expectation = expect(actual);
75
+ if (global.__suppressDevFailures) {
76
+ Object.keys(expectation).forEach((name) => {
77
+ wrapDevMatcher(expectation, name);
78
+ wrapDevMatcher(expectation.not, name);
79
+ });
80
+ }
81
+ return expectation;
82
+};
83
+global.expectDev = expectDev;
src/renderers/shared/stack/reconciler/__tests__/ReactChildReconciler-test.js
+5
-5
@@ -19,7 +19,7 @@ var ReactTestUtils;
19
20
describe('ReactChildReconciler', () => {
21
function normalizeCodeLocInfo(str) {
22
- return str.replace(/\(at .+?:\d+\)/g, '(at **)');
22
+ return str && str.replace(/\(at .+?:\d+\)/g, '(at **)');
23
}
24
25
beforeEach(() => {
@@ -40,8 +40,8 @@ describe('ReactChildReconciler', () => {
40
41
ReactTestUtils.renderIntoDocument(<Component />);
42
43
- expect(console.error.calls.count()).toBe(1);
44
- expect(console.error.calls.argsFor(0)[0]).toContain(
43
+ expectDev(console.error.calls.count()).toBe(1);
44
+ expectDev(console.error.calls.argsFor(0)[0]).toContain(
45
'Child keys must be unique; when two children share a key, only the first child will be used.'
46
);
47
});
@@ -69,8 +69,8 @@ describe('ReactChildReconciler', () => {
69
70
ReactTestUtils.renderIntoDocument(<GrandParent />);
71
72
- expect(console.error.calls.count()).toBe(1);
73
- expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe(
72
+ expectDev(console.error.calls.count()).toBe(1);
73
+ expectDev(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe(
74
'Warning: flattenChildren(...): ' +
75
'Encountered two children with the same key, `1`. ' +
76
'Child keys must be unique; when two children share a key, ' +