Skip to content

Commit 2162dd8

Browse files
committed
rewrote run_changed scripts
1 parent 36b52e9 commit 2162dd8

File tree

6 files changed

+281
-370
lines changed

6 files changed

+281
-370
lines changed

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,8 @@
3838
"pretest:coverage": "mkdirp coverage",
3939
"ci:coverage": "lcov-result-merger 'packages/**/lcov.info' 'lcov-all.info'",
4040
"test:coverage": "lcov-result-merger 'packages/**/lcov.info' | coveralls",
41-
"test:changed": "node scripts/run_changed.js",
42-
"test:changed:firestore": "ts-node-script scripts/run_changed_firestore.ts",
41+
"test:changed": "ts-node-script scripts/ci-test/run_changed_no_firestore.js",
42+
"test:changed:firestore": "ts-node-script scripts/ci-test/run_changed_firestore.ts",
4343
"test:setup": "node tools/config.js",
4444
"test:saucelabs": "node scripts/run_saucelabs.js",
4545
"docgen:js": "node scripts/docgen/generate-docs.js --api js",

scripts/ci-test/run_changed.ts

Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
/**
2+
* @license
3+
* Copyright 2020 Google LLC
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
import { resolve } from 'path';
19+
import { spawn, exec } from 'child-process-promise';
20+
import chalk from 'chalk';
21+
import simpleGit from 'simple-git/promise';
22+
const root = resolve(__dirname, '..');
23+
const git = simpleGit(root);
24+
25+
interface TestTask {
26+
pkgName: string;
27+
reason: TestReason;
28+
}
29+
30+
enum TestReason {
31+
Changed = 'changed',
32+
Dependent = 'dependent',
33+
Global = 'global'
34+
}
35+
36+
export function createTestTask(
37+
pkgName: string,
38+
reason = TestReason.Global
39+
): TestTask {
40+
return {
41+
pkgName,
42+
reason
43+
};
44+
}
45+
46+
// use test:ci command in CI
47+
// const testCommand = !!process.env.CI ? 'test:ci' : 'test';
48+
const testCommand = 'test:ci';
49+
50+
/**
51+
* Changes to these files warrant running all tests.
52+
*/
53+
const fullTestTriggerFiles = [
54+
// Global dependency changes.
55+
'yarn.lock',
56+
// Test/compile/lint configs.
57+
'config/karma.base.js',
58+
'config/mocha.browser.opts',
59+
'config/mocharc.node.js',
60+
'config/tsconfig.base.json',
61+
'config/webpack.test.js',
62+
'config/firestore.rules',
63+
'config/database.rules.json'
64+
];
65+
66+
/**
67+
* These files trigger tests in other dirs
68+
*/
69+
const specialPaths = {
70+
'scripts/emulator-testing/emulators/firestore-emulator.ts': [
71+
'@firebase/firestore'
72+
],
73+
'scripts/emulator-testing/emulators/database-emulator.ts': [
74+
'@firebase/database'
75+
],
76+
'scripts/emulator-testing/emulators/emulator.ts': [
77+
'@firebase/firestore',
78+
'@firebase/database'
79+
],
80+
'scripts/emulator-testing/firestore-test-runner.ts': ['@firebase/firestore'],
81+
'scripts/emulator-testing/database-test-runner.ts': ['@firebase/database'],
82+
'packages/firestore': ['firebase-firestore-integration-test']
83+
};
84+
85+
/**
86+
* Identify modified packages that require tests.
87+
*/
88+
export async function getTestTasks(): Promise<TestTask[]> {
89+
const packageInfo: any[] = JSON.parse(
90+
(await exec('npx lerna la --json', { cwd: root })).stdout
91+
);
92+
93+
const allPackageNames = packageInfo.map(info => info.name);
94+
95+
const depGraph: { [key: string]: any } = JSON.parse(
96+
(await exec('npx lerna ls --graph', { cwd: root })).stdout
97+
);
98+
const diff = await git.diff(['--name-only', 'origin/master...HEAD']);
99+
const changedFiles = diff.split('\n');
100+
const testTasks: TestTask[] = [];
101+
for (const filename of changedFiles) {
102+
// Files that trigger full test suite.
103+
if (fullTestTriggerFiles.includes(filename)) {
104+
console.log(
105+
chalk`{blue Running all tests because ${filename} was modified.}`
106+
);
107+
return allPackageNames.map(pkgName => createTestTask(pkgName));
108+
}
109+
// Files outside a package dir that should trigger its tests.
110+
const specialPathKeys = Object.keys(specialPaths) as Array<
111+
keyof typeof specialPaths
112+
>;
113+
const matchingSpecialPaths = specialPathKeys.filter(path =>
114+
filename.startsWith(path)
115+
);
116+
for (const matchingSpecialPath of matchingSpecialPaths) {
117+
for (const targetPackage of specialPaths[matchingSpecialPath]) {
118+
if (!testTasks.find(t => t.pkgName === targetPackage)) {
119+
testTasks.push(createTestTask(targetPackage, TestReason.Dependent));
120+
}
121+
}
122+
}
123+
// Check for changed files inside package dirs.
124+
const match = filename.match('^(packages(-exp)?/[a-zA-Z0-9-]+)/.*');
125+
if (match && match[1]) {
126+
const changedPkg = require(resolve(root, match[1], 'package.json'));
127+
if (changedPkg) {
128+
const changedPkgName = changedPkg.name;
129+
const task = testTasks.find(t => t.pkgName === changedPkgName);
130+
131+
if (task) {
132+
task.reason = TestReason.Changed;
133+
} else {
134+
testTasks.push(createTestTask(changedPkgName, TestReason.Changed));
135+
}
136+
137+
// Add packages that depend on it.
138+
for (const pkgName of Object.keys(depGraph)) {
139+
if (depGraph[pkgName].includes(changedPkg.name)) {
140+
const depData = packageInfo.find(item => item.name === pkgName);
141+
if (depData) {
142+
const depPkgJson = require(resolve(
143+
depData.location,
144+
'package.json'
145+
));
146+
if (depPkgJson) {
147+
if (!testTasks.find(t => t.pkgName === depPkgJson.name)) {
148+
testTasks.push(
149+
createTestTask(depPkgJson.name, TestReason.Dependent)
150+
);
151+
}
152+
}
153+
}
154+
}
155+
}
156+
}
157+
}
158+
}
159+
if (testTasks.length === 0) {
160+
console.log(
161+
chalk`{green No changes detected in any package. No test tasks is created }`
162+
);
163+
}
164+
165+
return testTasks;
166+
}
167+
168+
export async function runTests(testTasks: TestTask[]) {
169+
try {
170+
if (testTasks.length === 0) {
171+
chalk`{green No test tasks. Skipping all tests }`;
172+
process.exit(0);
173+
}
174+
175+
const lernaCmd = ['lerna', 'run', '--concurrency', '4'];
176+
console.log(chalk`{blue Running tests in:}`);
177+
for (const task of testTasks) {
178+
if (task.reason === TestReason.Changed) {
179+
console.log(chalk`{yellow ${task.pkgName} (contains modified files)}`);
180+
} else if (task.reason === TestReason.Dependent) {
181+
console.log(
182+
chalk`{yellow ${task.pkgName} (depends on modified files)}`
183+
);
184+
} else {
185+
console.log(chalk`{yellow ${task.pkgName} (running all tests)}`);
186+
}
187+
lernaCmd.push('--scope');
188+
lernaCmd.push(task.pkgName);
189+
}
190+
191+
lernaCmd.push(testCommand);
192+
await spawn('npx', lernaCmd, { stdio: 'inherit', cwd: root });
193+
process.exit(0);
194+
} catch (e) {
195+
console.error(chalk`{red ${e}}`);
196+
process.exit(1);
197+
}
198+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/**
2+
* @license
3+
* Copyright 2020 Google LLC
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
import { getTestTasks, runTests } from './run_changed';
19+
20+
const includeOnlyPackages = [
21+
'@firebase/firestore',
22+
'firebase-firestore-integration-test'
23+
];
24+
25+
async function run() {
26+
let testTasks = await getTestTasks();
27+
testTasks = testTasks.filter(t => includeOnlyPackages.includes(t.pkgName));
28+
29+
runTests(testTasks);
30+
}
31+
32+
run();
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
/**
2+
* @license
3+
* Copyright 2020 Google LLC
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
import { runTests, getTestTasks, createTestTask } from './run_changed';
19+
20+
/**
21+
* Always run tests in these paths.
22+
*/
23+
const alwaysRunTestPackages = [
24+
// These tests are very fast.
25+
'firebase-namespace-integration-test'
26+
];
27+
28+
const ignoredPackages = [
29+
'@firebase/firestore',
30+
'firebase-firestore-integration-test'
31+
];
32+
33+
async function run() {
34+
let testTasks = await getTestTasks();
35+
36+
// add alwaysRunTestPackages to tests if they don't already have a task
37+
for (const packageToTest of alwaysRunTestPackages) {
38+
if (!testTasks.find(t => t.pkgName === packageToTest)) {
39+
testTasks.push(createTestTask(packageToTest));
40+
}
41+
}
42+
43+
// remove the ignored packages from the tasks
44+
testTasks = testTasks.filter(t => !ignoredPackages.includes(t.pkgName));
45+
46+
runTests(testTasks);
47+
}
48+
49+
run();

0 commit comments

Comments
 (0)