1 | "use strict";
|
2 |
|
3 | Object.defineProperty(exports, "__esModule", {
|
4 | value: true
|
5 | });
|
6 | exports.name = exports.description = exports.TEST_DIR = void 0;
|
7 | exports.run = run;
|
8 | exports.selectFlowTestVersions = selectFlowTestVersions;
|
9 | exports.setup = setup;
|
10 | exports.writeFlowConfig = writeFlowConfig;
|
11 |
|
12 | var _colors = _interopRequireDefault(require("colors"));
|
13 |
|
14 | var _node = require("../lib/node.js");
|
15 |
|
16 | var _fileUtils = require("../lib/fileUtils.js");
|
17 |
|
18 | var _github = require("../lib/github.js");
|
19 |
|
20 | var _npmLibDefs = require("../lib/npm/npmLibDefs");
|
21 |
|
22 | var _libDefs = require("../lib/libDefs.js");
|
23 |
|
24 | var _logger = require("../lib/logger");
|
25 |
|
26 | var _isInFlowTypedRepo = _interopRequireDefault(require("../lib/isInFlowTypedRepo"));
|
27 |
|
28 | var _flowVersion = require("../lib/flowVersion");
|
29 |
|
30 | var _git = require("../lib/git");
|
31 |
|
32 | var _got = _interopRequireDefault(require("got"));
|
33 |
|
34 | var semver = _interopRequireWildcard(require("semver"));
|
35 |
|
36 | var _nodeStreamZip = _interopRequireDefault(require("node-stream-zip"));
|
37 |
|
38 | var _ValidationError = require("../lib/ValidationError");
|
39 |
|
40 | function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
|
41 |
|
42 | function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
|
43 |
|
44 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
45 |
|
46 |
|
47 |
|
48 |
|
49 |
|
50 |
|
51 |
|
52 |
|
53 |
|
54 |
|
55 | const BIN_PLATFORM = (() => {
|
56 | const arch = _node.os.arch();
|
57 |
|
58 | switch (_node.os.type()) {
|
59 | case 'Linux':
|
60 | if (arch === 'arm') {
|
61 | return 'linux-arm64';
|
62 | }
|
63 |
|
64 | return 'linux64';
|
65 |
|
66 | case 'Darwin':
|
67 | if (arch === 'arm') {
|
68 | return 'osx-arm64';
|
69 | }
|
70 |
|
71 | return 'osx';
|
72 |
|
73 | case 'Windows_NT':
|
74 | return 'win64';
|
75 |
|
76 | default:
|
77 | throw new Error('Unsupported os.type()! ' + _node.os.type());
|
78 | }
|
79 | })();
|
80 |
|
81 | const PKG_ROOT_DIR = _node.path.join(__dirname, '..', '..');
|
82 |
|
83 | const TEST_DIR = _node.path.join(PKG_ROOT_DIR, '.test-dir');
|
84 |
|
85 | exports.TEST_DIR = TEST_DIR;
|
86 |
|
87 | const BIN_DIR = _node.path.join(PKG_ROOT_DIR, '.flow-bins-cache');
|
88 |
|
89 | const P = Promise;
|
90 |
|
91 |
|
92 |
|
93 |
|
94 |
|
95 |
|
96 | const basePathRegex = new RegExp('definitions/(npm|environments)/(@[^/]*/)?[^/]*/?');
|
97 |
|
98 | async function getTestGroups(repoDirPath, envDirPath, onlyChanged = false) {
|
99 | let libDefs = await (0, _libDefs.getLibDefs)(repoDirPath);
|
100 | let envDefs = await (0, _libDefs.getLibDefs)(envDirPath);
|
101 |
|
102 | if (onlyChanged) {
|
103 | const diff = await (0, _git.getDefinitionsDiff)();
|
104 | const baseDiff = diff.map(d => {
|
105 | if (d.endsWith('CODEOWNERS')) {
|
106 | return '';
|
107 | }
|
108 |
|
109 | const match = d.match(basePathRegex);
|
110 |
|
111 | if (match) {
|
112 | return match[0];
|
113 | }
|
114 |
|
115 | return '';
|
116 | }).filter(d => d !== '');
|
117 | const changedDefs = baseDiff.map(d => {
|
118 | const {
|
119 | pkgName,
|
120 | pkgVersion
|
121 | } = (0, _libDefs.parseRepoDirItem)(d);
|
122 | const {
|
123 | major,
|
124 | minor,
|
125 | patch
|
126 | } = pkgVersion;
|
127 | return {
|
128 | name: pkgName,
|
129 | version: `v${major}.${minor}.${patch}`
|
130 | };
|
131 | });
|
132 | libDefs = [...libDefs, ...envDefs].filter(def => {
|
133 | return changedDefs.some(d => {
|
134 |
|
135 | if (d.version === 'vx.x.x') {
|
136 | return d.name === def.pkgName;
|
137 | }
|
138 |
|
139 |
|
140 | if (def.configPath) {
|
141 | const deps = JSON.parse(_node.fs.readFileSync(def.configPath, 'utf-8')).deps;
|
142 | const isDependantOfChanged = Object.keys(deps).some(dep => dep === d.name && deps[dep].some(s => s === d.version));
|
143 | if (isDependantOfChanged) return true;
|
144 | }
|
145 |
|
146 | return d.name === def.pkgName && d.version === def.pkgVersionStr;
|
147 | });
|
148 | });
|
149 | }
|
150 |
|
151 | return libDefs.map(libDef => {
|
152 | const groupID = `${libDef.pkgName}_${libDef.pkgVersionStr}/${libDef.flowVersionStr}`;
|
153 | const deps = libDef.configPath ? JSON.parse(_node.fs.readFileSync(libDef.configPath, 'utf-8')).deps : {};
|
154 | return {
|
155 | id: groupID,
|
156 | testFilePaths: libDef.testFilePaths,
|
157 | libDefPath: libDef.path,
|
158 | flowVersion: libDef.flowVersion,
|
159 | deps
|
160 | };
|
161 | });
|
162 | }
|
163 |
|
164 | function printSkipMessage(flowVersion, githubUrl) {
|
165 | console.log('==========================================================================================');
|
166 | console.log(`We are temporarily skipping ${flowVersion} due to ${githubUrl}`);
|
167 | console.log('==========================================================================================');
|
168 | }
|
169 |
|
170 |
|
171 |
|
172 | let _flowReleases = [];
|
173 |
|
174 |
|
175 |
|
176 |
|
177 |
|
178 |
|
179 | async function getOrderedFlowBinVersions(numberOfReleases, flowVersion) {
|
180 | (0, _logger.sectionHeader)('Fetching flow binaries...');
|
181 | const IS_WINDOWS = _node.os.type() === 'Windows_NT';
|
182 | const GH_CLIENT = (0, _github.gitHubClient)();
|
183 | const OS_ARCH_FILTER_RE = new RegExp(`flow-${BIN_PLATFORM}-v`);
|
184 |
|
185 |
|
186 | if (_flowReleases.length === 0) {
|
187 | let foundAllFlowVersions = false;
|
188 | let page = 1;
|
189 |
|
190 | while (!foundAllFlowVersions) {
|
191 | try {
|
192 | const pageVersions = await GH_CLIENT.repos.listReleases({
|
193 | owner: 'facebook',
|
194 | repo: 'flow',
|
195 | page,
|
196 | per_page: 50
|
197 | });
|
198 | (0, _logger.listItem)(`Fetched page: ${page}`);
|
199 | page += 1;
|
200 |
|
201 | _flowReleases.push(...pageVersions.data);
|
202 |
|
203 | if (pageVersions.data.length === 0) {
|
204 | foundAllFlowVersions = true;
|
205 | }
|
206 | } catch (e) {
|
207 | console.error(e);
|
208 | foundAllFlowVersions = true;
|
209 | }
|
210 | }
|
211 | }
|
212 |
|
213 | const filteredVersions = selectFlowTestVersions(_flowReleases.map(o => o.tag_name), flowVersion, numberOfReleases);
|
214 |
|
215 | const flowBins = _flowReleases.filter(ver => filteredVersions.includes(ver.tag_name)).filter(rel => {
|
216 | if (rel.tag_name.endsWith('-rc')) {
|
217 | return false;
|
218 | }
|
219 |
|
220 | if (rel.tag_name === 'v0.67.0') {
|
221 | printSkipMessage(rel.tag_name, 'https://github.com/facebook/flow/issues/5922');
|
222 | return false;
|
223 | } else if (rel.tag_name === 'v0.63.0' || rel.tag_name === 'v0.70.0') {
|
224 | printSkipMessage(rel.tag_name, 'https://github.com/flowtype/flow-typed/issues/2422');
|
225 | return false;
|
226 | } else if (semver.lt(rel.tag_name, '0.53.0')) {
|
227 | console.log('flow-typed only supports flow 0.53.0 and newer');
|
228 | return false;
|
229 | } else if (IS_WINDOWS && (semver.eq(rel.tag_name, '0.57.0') || semver.eq(rel.tag_name, '0.57.1') || semver.eq(rel.tag_name, '0.57.2'))) {
|
230 |
|
231 | return false;
|
232 | }
|
233 |
|
234 | return true;
|
235 | }).map(rel => {
|
236 |
|
237 | const binZip = rel.assets.filter(({
|
238 | name
|
239 | }) => {
|
240 | return OS_ARCH_FILTER_RE.test(name) && !/-latest.zip$/.test(name);
|
241 | }).map(asset => asset.browser_download_url);
|
242 |
|
243 | if (binZip.length !== 1) {
|
244 | throw new Error('Unexpected number of ' + BIN_PLATFORM + ' assets for flow-' + rel.tag_name + '! ' + JSON.stringify(binZip));
|
245 | } else {
|
246 | const version = rel.tag_name[0] === 'v' ? rel.tag_name : 'v' + rel.tag_name;
|
247 | return {
|
248 | version,
|
249 | binURL: binZip[0]
|
250 | };
|
251 | }
|
252 | }).sort((a, b) => {
|
253 | return semver.lt(a.version, b.version) ? -1 : 1;
|
254 | });
|
255 |
|
256 | await P.all(flowBins.map(async ({
|
257 | version,
|
258 | binURL
|
259 | }) => {
|
260 | const zipPath = _node.path.join(BIN_DIR, 'flow-' + version + '.zip');
|
261 |
|
262 | const binPath = _node.path.join(BIN_DIR, 'flow-' + version + (IS_WINDOWS ? '.exe' : ''));
|
263 |
|
264 | if (await _node.fs.exists(binPath)) {
|
265 | return;
|
266 | }
|
267 |
|
268 |
|
269 | await new Promise((res, rej) => {
|
270 | console.log(' Fetching flow-%s...', version);
|
271 |
|
272 | _got.default.stream(binURL, {
|
273 | headers: {
|
274 | 'User-Agent': 'flow-typed Test Runner (github.com/flowtype/flow-typed)'
|
275 | }
|
276 | }).on('error', err => rej(err)).pipe(_node.fs.createWriteStream(zipPath).on('close', () => {
|
277 | console.log(' flow-%s finished downloading.', version);
|
278 | res();
|
279 | }));
|
280 | });
|
281 |
|
282 | const flowBinDirPath = _node.path.join(BIN_DIR, 'TMP-flow-' + version);
|
283 |
|
284 | await _node.fs.mkdir(flowBinDirPath);
|
285 | console.log(' Extracting flow-%s...', version);
|
286 | const zip = new _nodeStreamZip.default.async({
|
287 | file: zipPath
|
288 | });
|
289 | const extractedCount = await zip.extract(null, flowBinDirPath);
|
290 | console.log(' Extracted %s entries', extractedCount);
|
291 |
|
292 | if (IS_WINDOWS) {
|
293 | await _node.fs.rename(_node.path.join(flowBinDirPath, 'flow', 'flow.exe'), _node.path.join(BIN_DIR, 'flow-' + version + '.exe'));
|
294 | } else {
|
295 | await _node.fs.rename(_node.path.join(flowBinDirPath, 'flow', 'flow'), _node.path.join(BIN_DIR, 'flow-' + version));
|
296 | await _node.child_process.execP(['chmod', '755', _node.path.join(BIN_DIR, 'flow-' + version)].join(' '));
|
297 | }
|
298 |
|
299 | console.log(' Removing flow-%s artifacts...', version);
|
300 | await P.all([(0, _fileUtils.recursiveRmdir)(flowBinDirPath), _node.fs.unlink(zipPath)]);
|
301 | console.log(' flow-%s complete!', version);
|
302 | }));
|
303 | console.log('Finished fetching Flow binaries.\n');
|
304 | return flowBins.map(bin => bin.version);
|
305 | }
|
306 |
|
307 | const flowNameRegex = /^flow-v[0-9]+.[0-9]+.[0-9]+(\.exe)?$/;
|
308 |
|
309 |
|
310 |
|
311 |
|
312 |
|
313 | function checkFlowFilename(name) {
|
314 | return flowNameRegex.test(name);
|
315 | }
|
316 |
|
317 |
|
318 |
|
319 |
|
320 |
|
321 |
|
322 |
|
323 |
|
324 | async function getCachedFlowBinVersions(numberOfReleases, flowVersion) {
|
325 |
|
326 | const versions = (await _node.fs.readdir(_node.path.join(BIN_DIR))).filter(checkFlowFilename).map(dir => dir.slice('flow-'.length));
|
327 |
|
328 |
|
329 | versions.sort((a, b) => {
|
330 | return semver.lt(a.substring(1), b.substring(1)) ? -1 : 1;
|
331 | });
|
332 | return selectFlowTestVersions(versions, flowVersion, numberOfReleases);
|
333 | }
|
334 |
|
335 | async function writeFlowConfig(repoDirPath, testDirPath, libDefPath, version, depPaths) {
|
336 |
|
337 |
|
338 |
|
339 |
|
340 | const destFlowConfigPath = _node.path.join(testDirPath, '.flowconfig');
|
341 |
|
342 | const flowConfigData = ['[libs]', ...depPaths, _node.path.basename(libDefPath), _node.path.join(repoDirPath, '..', '__util__', 'tdd_framework.js'), '', '[options]', 'include_warnings=true', 'server.max_workers=0', semver.gte(version, '0.200.0') ? 'exact_by_default=true' : '',
|
343 |
|
344 | 'sharedmemory.heap_size=3221225472', semver.lt(version, '0.125.0') ? 'suppress_comment=\\\\(.\\\\|\\n\\\\)*\\\\$FlowExpectedError' : '', '',
|
345 |
|
346 | '[ignore]', _node.path.join(testDirPath, '..', '..', 'node_modules'), '', '[lints]', semver.gte(version, '0.104.0') && semver.lt(version, '0.201.0') ? 'implicit-inexact-object=error' : '', semver.gte(version, '0.201.0') ? 'ambiguous-object-type=error' : ''].join('\n');
|
347 | await _node.fs.writeFile(destFlowConfigPath, flowConfigData);
|
348 | }
|
349 |
|
350 | function testTypeDefinition(flowVer, testDirPath) {
|
351 | return new Promise(res => {
|
352 | const IS_WINDOWS = _node.os.type() === 'Windows_NT';
|
353 |
|
354 | const child = _node.child_process.exec([_node.path.join(BIN_DIR, 'flow-' + flowVer + (IS_WINDOWS ? '.exe' : '')), 'check', '--strip-root', '--all', testDirPath].join(' '));
|
355 |
|
356 | let stdErrOut = '';
|
357 | child.stdout.on('data', data => stdErrOut += data);
|
358 | child.stderr.on('data', data => stdErrOut += data);
|
359 | child.on('error', execError => {
|
360 | res({
|
361 | stdErrOut,
|
362 | errCode: null,
|
363 | execError
|
364 | });
|
365 | });
|
366 | child.on('close', errCode => {
|
367 | res({
|
368 | stdErrOut,
|
369 | errCode,
|
370 | execError: null
|
371 | });
|
372 | });
|
373 | });
|
374 | }
|
375 |
|
376 | async function runFlowTypeDefTests(flowVersionsToRun, groupId, testDirPath) {
|
377 | const errors = [];
|
378 |
|
379 | while (flowVersionsToRun.length > 0) {
|
380 |
|
381 | const testBatch = flowVersionsToRun.slice(0, Math.min(flowVersionsToRun.length, 5)).map(group => (flowVersionsToRun.shift(), group));
|
382 | await P.all(testBatch.map(async flowVer => {
|
383 | const testRunId = groupId + ' (flow-' + flowVer + ')';
|
384 | console.log('Testing %s...', testRunId);
|
385 | const {
|
386 | stdErrOut,
|
387 | errCode,
|
388 | execError
|
389 | } = await testTypeDefinition(flowVer, testDirPath);
|
390 |
|
391 | if (execError !== null) {
|
392 | errors.push(testRunId + ': Error executing Flow process: ' + execError.stack);
|
393 | } else if (!stdErrOut.endsWith('Found 0 errors\n')) {
|
394 | errors.push(testRunId + ': Unexpected Flow errors (' + String(errCode) + '):\n\n' + stdErrOut);
|
395 | }
|
396 | }));
|
397 | }
|
398 |
|
399 | return errors;
|
400 | }
|
401 |
|
402 | async function testLowestCapableFlowVersion(lowerVersions, testDirPath, lowestFlowVersionRan) {
|
403 | let lowerFlowVersionsToRun = lowerVersions;
|
404 | let lowestCapableFlowVersion = lowestFlowVersionRan;
|
405 |
|
406 | while (lowerFlowVersionsToRun.length > 0) {
|
407 | const lowerTestBatch = lowerFlowVersionsToRun.slice(0, Math.min(lowerFlowVersionsToRun.length, 5)).map(group => (lowerFlowVersionsToRun.shift(), group));
|
408 | await P.all(lowerTestBatch.map(async flowVer => {
|
409 | const {
|
410 | stdErrOut,
|
411 | execError
|
412 | } = await testTypeDefinition(flowVer, testDirPath);
|
413 |
|
414 | if (execError !== null || !stdErrOut.endsWith('Found 0 errors\n')) {
|
415 | lowerFlowVersionsToRun = [];
|
416 | } else {
|
417 | lowestCapableFlowVersion = semver.lt(lowestCapableFlowVersion, flowVer) ? lowestCapableFlowVersion : flowVer;
|
418 | }
|
419 | }));
|
420 | }
|
421 |
|
422 | return lowestCapableFlowVersion;
|
423 | }
|
424 |
|
425 | async function findLowestCapableFlowVersion(repoDirPath, orderedFlowVersions, lowestFlowVersionRan, testDirPath, libDefPath, depPaths) {
|
426 | let lowerFlowVersionsToRun = orderedFlowVersions.filter(flowVer => {
|
427 | return semver.lt(flowVer, lowestFlowVersionRan);
|
428 | });
|
429 | lowerFlowVersionsToRun.reverse();
|
430 | await writeFlowConfig(repoDirPath, testDirPath, libDefPath, lowestFlowVersionRan, depPaths);
|
431 | return await testLowestCapableFlowVersion(lowerFlowVersionsToRun, testDirPath, lowestFlowVersionRan);
|
432 | }
|
433 |
|
434 |
|
435 |
|
436 |
|
437 |
|
438 | async function removeTrashFromBinDir() {
|
439 | (await _node.fs.readdir(_node.path.join(BIN_DIR))).filter(name => !checkFlowFilename(name)).forEach(async el => {
|
440 | const dir = _node.path.resolve(BIN_DIR, el);
|
441 |
|
442 | if (!_node.fs.exists(dir)) {
|
443 | await _node.fs.unlink(dir);
|
444 | }
|
445 | });
|
446 | }
|
447 |
|
448 |
|
449 |
|
450 |
|
451 | const CONFIGURATION_CHANGE_VERSIONS = ['v0.104.0',
|
452 | 'v0.125.0',
|
453 | 'v0.200.0'
|
454 | ];
|
455 |
|
456 |
|
457 |
|
458 |
|
459 |
|
460 |
|
461 | function partitionListOfFlowVersionsPerConfigChange(flowVersions) {
|
462 | const result = [];
|
463 | let lastIndex = 0;
|
464 |
|
465 | for (const pivotVersion of CONFIGURATION_CHANGE_VERSIONS) {
|
466 | const index = flowVersions.indexOf(pivotVersion, lastIndex + 1);
|
467 |
|
468 | if (index < 0) {
|
469 | continue;
|
470 | }
|
471 |
|
472 |
|
473 |
|
474 | result.push(flowVersions.slice(lastIndex, index));
|
475 | lastIndex = index;
|
476 | }
|
477 |
|
478 | result.push(flowVersions.slice(lastIndex));
|
479 | return result;
|
480 | }
|
481 |
|
482 |
|
483 |
|
484 |
|
485 |
|
486 |
|
487 |
|
488 |
|
489 |
|
490 |
|
491 | function getDepTestGroups(testGroup) {
|
492 | const flowDirVersion = (0, _flowVersion.extractFlowDirFromFlowDirPath)(testGroup.id);
|
493 | const depBasePath = (0, _npmLibDefs.getNpmLibDefDirFromNested)(testGroup.libDefPath);
|
494 |
|
495 | const getMappedDepPaths = deps => {
|
496 | return Object.keys(deps).map(depName => {
|
497 | const nameSplit = depName.split('/');
|
498 | const scope = nameSplit.length > 1 ? `${nameSplit[0]}/` : '';
|
499 | const packageName = nameSplit.length > 1 ? nameSplit[1] : depName;
|
500 | return deps[depName].map(version => {
|
501 | const flowDirPath = `${depBasePath}${scope}${packageName}_${version}/${flowDirVersion}`;
|
502 | const defPath = `${flowDirPath}/${packageName}_${version}.js`;
|
503 |
|
504 | if (!_node.fs.existsSync(defPath)) {
|
505 | throw new Error(_colors.default.red(`${depName}@${version} cannot be a dependency of ${testGroup.id} because either the dependency@version does't exist or they do not have matching flow version ranges`));
|
506 | }
|
507 |
|
508 |
|
509 | let defDeps;
|
510 |
|
511 | try {
|
512 | defDeps = JSON.parse(_node.fs.readFileSync(`${flowDirPath}/config.json`, 'utf-8')).deps;
|
513 | } catch (e) {}
|
514 |
|
515 | return {
|
516 | main: defPath,
|
517 |
|
518 |
|
519 |
|
520 |
|
521 |
|
522 |
|
523 |
|
524 |
|
525 | deps: [...(defDeps ? getMappedDepPaths(defDeps).reduce((acc, cur) => {
|
526 | return [...acc, cur[0].main, ...cur[0].deps];
|
527 | }, []) : [])]
|
528 | };
|
529 | });
|
530 | });
|
531 | };
|
532 |
|
533 | const mappedDepPaths = getMappedDepPaths(testGroup.deps);
|
534 | const longestDep = mappedDepPaths.reduce((acc, cur) => {
|
535 | if (cur.length > acc) {
|
536 | return cur.length;
|
537 | }
|
538 |
|
539 | return acc;
|
540 | }, 0);
|
541 | const depGroup = [];
|
542 |
|
543 | for (let i = 0, len = longestDep; i < len; i++) {
|
544 | const newGroup = [];
|
545 | mappedDepPaths.forEach(o => {
|
546 | if (!o[i]) {
|
547 | newGroup.push(o[0].main, ...o[0].deps);
|
548 | } else {
|
549 | newGroup.push(o[i].main, ...o[i].deps);
|
550 | }
|
551 | });
|
552 | depGroup.push(newGroup);
|
553 | }
|
554 |
|
555 | return depGroup;
|
556 | }
|
557 |
|
558 |
|
559 |
|
560 |
|
561 |
|
562 |
|
563 |
|
564 | async function runTestGroup(repoDirPath, testGroup, orderedFlowVersions) {
|
565 |
|
566 | const testDirName = testGroup.id.replace(/\//g, '--').replace(/>/g, 'gt').replace(/</g, 'lt').replace(/=/g, 'eq');
|
567 |
|
568 | const testDirPath = _node.path.join(TEST_DIR, testDirName);
|
569 |
|
570 | if (await _node.fs.exists(testDirPath)) {
|
571 | throw new Error(`Trying to run ${testGroup.id}, but test dir already exists! ` + `Bailing out!`);
|
572 | }
|
573 |
|
574 | try {
|
575 | await _node.fs.mkdir(testDirPath);
|
576 |
|
577 | const destLibDefPath = _node.path.join(testDirPath, _node.path.basename(testGroup.libDefPath));
|
578 |
|
579 | const copiedFileNames = new Set();
|
580 | await P.all([P.all(testGroup.testFilePaths.map(async (filePath, idx) => {
|
581 |
|
582 |
|
583 |
|
584 |
|
585 |
|
586 |
|
587 |
|
588 | const fileName = _node.path.basename(filePath);
|
589 |
|
590 | const destBasename = copiedFileNames.has(fileName) ? `${idx}-${fileName}` : fileName;
|
591 | copiedFileNames.add(destBasename);
|
592 | await (0, _fileUtils.copyFile)(filePath, _node.path.join(testDirPath, destBasename));
|
593 | })), await (0, _fileUtils.copyFile)(testGroup.libDefPath, destLibDefPath)]);
|
594 |
|
595 |
|
596 | const testGrpFlowSemVerRange = (0, _flowVersion.toSemverString)(testGroup.flowVersion);
|
597 | const flowVersionsToRun = orderedFlowVersions.filter(flowVer => {
|
598 | return semver.satisfies(flowVer, testGrpFlowSemVerRange);
|
599 | });
|
600 |
|
601 | if (!flowVersionsToRun.length) {
|
602 | return [];
|
603 | }
|
604 |
|
605 | const groups = partitionListOfFlowVersionsPerConfigChange(flowVersionsToRun);
|
606 | const flowErrors = [];
|
607 |
|
608 | const executeTests = async (depPaths = []) => {
|
609 | for (const sublistOfFlowVersions of groups) {
|
610 | const lowestFlowVersionRanInThisGroup = sublistOfFlowVersions[0];
|
611 | await writeFlowConfig(repoDirPath, testDirPath, testGroup.libDefPath, lowestFlowVersionRanInThisGroup, depPaths);
|
612 | const flowErrorsForThisGroup = await runFlowTypeDefTests([...sublistOfFlowVersions], testGroup.id, testDirPath);
|
613 | flowErrors.push(...flowErrorsForThisGroup);
|
614 | }
|
615 |
|
616 | const lowestFlowVersionRan = flowVersionsToRun[0];
|
617 | const lowestCapableFlowVersion = await findLowestCapableFlowVersion(repoDirPath, orderedFlowVersions, lowestFlowVersionRan, testDirPath, testGroup.libDefPath, depPaths);
|
618 |
|
619 | if (lowestCapableFlowVersion !== lowestFlowVersionRan) {
|
620 | console.log(`Tests for ${testGroup.id} ran successfully on flow ${lowestCapableFlowVersion}.
|
621 | Consider setting ${lowestCapableFlowVersion} as the lower bound!`);
|
622 | }
|
623 | };
|
624 |
|
625 | if (Object.keys(testGroup.deps).length > 0) {
|
626 | const depsTestGroups = getDepTestGroups(testGroup);
|
627 |
|
628 | for (const depPaths of depsTestGroups) {
|
629 | await executeTests(depPaths);
|
630 | }
|
631 | } else {
|
632 | await executeTests();
|
633 | }
|
634 |
|
635 | return flowErrors;
|
636 | } finally {
|
637 | if (await _node.fs.exists(testDirPath)) {
|
638 | await (0, _fileUtils.recursiveRmdir)(testDirPath);
|
639 | }
|
640 | }
|
641 | }
|
642 |
|
643 |
|
644 |
|
645 |
|
646 |
|
647 |
|
648 |
|
649 | function selectFlowTestVersions(orderedFlowVersions, flowVersion, numberOfFlowVersions) {
|
650 |
|
651 |
|
652 |
|
653 | if (flowVersion.kind === 'all' || flowVersion.kind === 'specific') {
|
654 | return orderedFlowVersions.slice(0, numberOfFlowVersions);
|
655 | }
|
656 |
|
657 | const {
|
658 | upper,
|
659 | lower
|
660 | } = flowVersion;
|
661 | const selectedFlowVersions = [];
|
662 |
|
663 | const findSelectedFlowVersions = flowVersions => {
|
664 | let versionExceedsLower = false;
|
665 |
|
666 | while (selectedFlowVersions.length < numberOfFlowVersions && !versionExceedsLower) {
|
667 | const version = flowVersions[selectedFlowVersions.length];
|
668 | const [major, minor, patch] = version.substring(1).split('.');
|
669 |
|
670 | if (lower && (lower.major === 'x' || lower.major <= Number(major)) && (lower.minor === 'x' || lower.minor <= Number(minor)) && (lower.patch === 'x' || lower.patch <= Number(patch))) {
|
671 | selectedFlowVersions.push(version);
|
672 | } else {
|
673 | versionExceedsLower = true;
|
674 | }
|
675 | }
|
676 | };
|
677 |
|
678 | if (upper == null) {
|
679 | findSelectedFlowVersions(orderedFlowVersions);
|
680 | } else {
|
681 |
|
682 |
|
683 |
|
684 | const upperFlowVersion = orderedFlowVersions.findIndex(o => {
|
685 | const [major, minor, patch] = o.substring(1).split('.');
|
686 |
|
687 | if ((upper.major === 'x' || upper.major >= Number(major)) && (upper.minor === 'x' || upper.minor >= Number(minor)) && (upper.patch === 'x' || upper.patch >= Number(patch))) {
|
688 | return true;
|
689 | }
|
690 | });
|
691 | const upperBoundFlowVersions = orderedFlowVersions.slice(upperFlowVersion);
|
692 | findSelectedFlowVersions(upperBoundFlowVersions);
|
693 | }
|
694 |
|
695 | return selectedFlowVersions;
|
696 | }
|
697 |
|
698 | async function runTests(repoDirPath, envDirPath, testPatterns, onlyChanged, numberOfFlowVersions) {
|
699 | const testPatternRes = testPatterns.map(patt => new RegExp(patt, 'g'));
|
700 | const testGroups = (await getTestGroups(repoDirPath, envDirPath, onlyChanged)).filter(testGroup => {
|
701 | if (testPatternRes.length === 0) {
|
702 | return true;
|
703 | }
|
704 |
|
705 | for (var i = 0; i < testPatternRes.length; i++) {
|
706 | const pattern = testPatternRes[i];
|
707 |
|
708 | if (testGroup.id.match(pattern) != null) {
|
709 | return true;
|
710 | }
|
711 |
|
712 |
|
713 |
|
714 |
|
715 |
|
716 | const depsList = Object.keys(testGroup.deps);
|
717 |
|
718 | if (depsList.length > 0) {
|
719 | return depsList.some(dep => {
|
720 | return testGroup.deps[dep].some(o => `${dep}_${o}`.match(pattern));
|
721 | });
|
722 | }
|
723 | }
|
724 |
|
725 | return false;
|
726 | });
|
727 |
|
728 | try {
|
729 |
|
730 | if (await _node.fs.exists(TEST_DIR)) {
|
731 | await (0, _fileUtils.recursiveRmdir)(TEST_DIR);
|
732 | }
|
733 |
|
734 | await _node.fs.mkdir(TEST_DIR);
|
735 | const results = new Map();
|
736 |
|
737 | while (testGroups.length > 0) {
|
738 | const testGroup = testGroups.shift();
|
739 |
|
740 | await removeTrashFromBinDir();
|
741 | let orderedFlowVersions;
|
742 |
|
743 | try {
|
744 | orderedFlowVersions = await getOrderedFlowBinVersions(numberOfFlowVersions, testGroup.flowVersion);
|
745 | } catch (e) {
|
746 | orderedFlowVersions = await getCachedFlowBinVersions(numberOfFlowVersions, testGroup.flowVersion);
|
747 | }
|
748 |
|
749 | const testGroupErrors = await runTestGroup(repoDirPath, testGroup, orderedFlowVersions);
|
750 |
|
751 | if (testGroupErrors.length > 0) {
|
752 | const errors = results.get(testGroup.id) || [];
|
753 | testGroupErrors.forEach(err => errors.push(err));
|
754 | results.set(testGroup.id, errors);
|
755 | }
|
756 | }
|
757 |
|
758 | return results;
|
759 | } finally {
|
760 | if (await _node.fs.exists(TEST_DIR)) {
|
761 | await (0, _fileUtils.recursiveRmdir)(TEST_DIR);
|
762 | }
|
763 | }
|
764 | }
|
765 |
|
766 | const name = 'run-tests [testPatterns...]';
|
767 | exports.name = name;
|
768 | const description = 'Run definition tests for library definitions in the flow-typed project';
|
769 | exports.description = description;
|
770 |
|
771 | function setup(yargs) {
|
772 | return yargs.usage(`$0 ${name} - ${description}`).positional('testPatterns', {
|
773 | describe: 'Test patterns',
|
774 | default: []
|
775 | }).options({
|
776 | path: {
|
777 | describe: 'Override default path for libdef root (Mainly for testing purposes)',
|
778 | type: 'string',
|
779 | demandOption: false
|
780 | },
|
781 | onlyChanged: {
|
782 | type: 'boolean',
|
783 | description: 'Run only changed definition tests',
|
784 | demandOption: false
|
785 | },
|
786 | numberOfFlowVersions: {
|
787 | type: 'number',
|
788 | description: 'Only run against the latest X versions of flow',
|
789 | demandOption: false
|
790 | }
|
791 | });
|
792 | }
|
793 |
|
794 | async function run(argv) {
|
795 | if (!(await _node.fs.exists(BIN_DIR))) {
|
796 | await _node.fs.mkdir(BIN_DIR);
|
797 | }
|
798 |
|
799 | if (!(0, _isInFlowTypedRepo.default)()) {
|
800 | console.log('This command only works in a clone of flowtype/flow-typed. ' + 'It is a tool used to run tests of the library definitions in the flow-typed project.');
|
801 | return 1;
|
802 | }
|
803 |
|
804 | const testPatterns = Array.isArray(argv.testPatterns) ? argv.testPatterns.map(String) : [];
|
805 | const onlyChanged = Boolean(argv.onlyChanged);
|
806 | const numberOfFlowVersions = Number(argv.numberOfFlowVersions) || 15;
|
807 | const cwd = process.cwd();
|
808 | const basePath = argv.path ? String(argv.path) : cwd;
|
809 |
|
810 | const cwdDefsNPMPath = _node.path.join(basePath, 'definitions', 'npm');
|
811 |
|
812 | const repoDirPath = (await _node.fs.exists(cwdDefsNPMPath)) ? cwdDefsNPMPath : _node.path.join(__dirname, '..', '..', '..', 'definitions', 'npm');
|
813 |
|
814 | const cwdDefsEnvPath = _node.path.join(basePath, 'definitions', 'environments');
|
815 |
|
816 | const envDirPath = (await _node.fs.exists(cwdDefsEnvPath)) ? cwdDefsEnvPath : _node.path.join(__dirname, '..', '..', '..', 'definitions', 'environments');
|
817 | console.info(`Running ${onlyChanged ? 'changed' : ''} definition tests against latest ${_colors.default.green(numberOfFlowVersions.toString())} flow versions in ${_node.path.join(repoDirPath, '..')}...\n`);
|
818 | let results;
|
819 |
|
820 | try {
|
821 | results = await runTests(repoDirPath, envDirPath, testPatterns, onlyChanged, numberOfFlowVersions);
|
822 | } catch (e) {
|
823 | if (e instanceof _ValidationError.ValidationError) {
|
824 | console.error(e.message);
|
825 | return 1;
|
826 | } else {
|
827 | throw e;
|
828 | }
|
829 | }
|
830 |
|
831 | Array.from(results).forEach(([testGroupName, errors]) => {
|
832 | console.log('ERROR: %s', testGroupName);
|
833 | errors.forEach(err => console.log('* %s\n', err.split('\n').map((line, idx) => {
|
834 | return idx === 0 ? line : ' ' + line;
|
835 | }).join('\n')));
|
836 | });
|
837 |
|
838 | if (results.size === 0) {
|
839 | console.log('\n ✨ All tests passed!');
|
840 | return 0;
|
841 | }
|
842 |
|
843 | return 1;
|
844 | } |
\ | No newline at end of file |