@samitouri / QOS-React / commits / 64cba04bf9

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, ' +