bin.mjs 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134
  1. #!/usr/bin/env node
  2. /*---------------------------------------------------------
  3. * Copyright (C) Microsoft Corporation. All rights reserved.
  4. *--------------------------------------------------------*/
  5. import * as chokidar from 'chokidar';
  6. import { resolve } from 'path';
  7. import { cliArgs, configFileDefault } from './cli/args.mjs';
  8. import { loadDefaultConfigFile, tryLoadConfigFile, } from './cli/config.mjs';
  9. import { Coverage } from './cli/coverage.mjs';
  10. import { platforms } from './cli/platform/index.mjs';
  11. export const args = cliArgs.parseSync();
  12. class CliExpectedError extends Error {
  13. }
  14. main();
  15. async function main() {
  16. let code = 0;
  17. try {
  18. const config = args.config !== configFileDefault
  19. ? await tryLoadConfigFile(resolve(process.cwd(), args.config))
  20. : await loadDefaultConfigFile();
  21. const enabledTests = new Set(args.label?.length
  22. ? args.label.map((label) => {
  23. const found = config.tests.find((c, i) => typeof label === 'string' ? c.label === label : i === label);
  24. if (!found) {
  25. throw new CliExpectedError(`Could not find a configuration with label "${label}"`);
  26. }
  27. return found;
  28. })
  29. : new Set(config.tests));
  30. if (args.watch) {
  31. await watchConfigs(config, enabledTests);
  32. }
  33. else {
  34. code = await runConfigs(config, enabledTests);
  35. }
  36. }
  37. catch (e) {
  38. code = 1;
  39. if (e instanceof CliExpectedError) {
  40. console.error(e.message);
  41. }
  42. else {
  43. console.error(e.stack || e);
  44. }
  45. }
  46. finally {
  47. process.exit(code);
  48. }
  49. }
  50. async function prepareConfigs(config, enabledTests) {
  51. return await Promise.all([...enabledTests].map(async (test, i) => {
  52. for (const platform of platforms) {
  53. const p = await platform.prepare({ args, config, test });
  54. if (p) {
  55. return p;
  56. }
  57. }
  58. throw new CliExpectedError(`Could not find a runner for test configuration ${test.label || i}`);
  59. }));
  60. }
  61. const WATCH_RUN_DEBOUNCE = 500;
  62. async function watchConfigs(config, enabledTests) {
  63. let debounceRun;
  64. let rerun = false;
  65. let running = true;
  66. let prepared;
  67. const runOrDebounce = () => {
  68. if (debounceRun) {
  69. clearTimeout(debounceRun);
  70. }
  71. debounceRun = setTimeout(async () => {
  72. running = true;
  73. rerun = false;
  74. try {
  75. prepared ??= await prepareConfigs(config, enabledTests);
  76. await runPreparedConfigs(config, prepared);
  77. }
  78. finally {
  79. running = false;
  80. if (rerun) {
  81. runOrDebounce();
  82. }
  83. }
  84. }, WATCH_RUN_DEBOUNCE);
  85. };
  86. const watcher = chokidar.watch(args.watchFiles?.length ? args.watchFiles.map(String) : process.cwd(), {
  87. ignored: [
  88. '**/.vscode-test/**',
  89. '**/node_modules/**',
  90. ...(args.watchIgnore || []).map(String),
  91. ],
  92. ignoreInitial: true,
  93. });
  94. watcher.on('all', (evts) => {
  95. if (evts !== 'change') {
  96. prepared = undefined; // invalidate since files will need to be re-scanned
  97. }
  98. if (running) {
  99. rerun = true;
  100. }
  101. else {
  102. runOrDebounce();
  103. }
  104. });
  105. watcher.on('ready', () => {
  106. runOrDebounce();
  107. });
  108. // wait until interrupted
  109. await new Promise(() => {
  110. /* no-op */
  111. });
  112. }
  113. async function runPreparedConfigs(config, prepared) {
  114. const coverage = args.coverage ? new Coverage(config, args) : undefined;
  115. const context = { coverage: coverage?.targetDir };
  116. let code = 0;
  117. for (const p of prepared) {
  118. code = Math.max(code, await p.run(context));
  119. if (args.bail && code !== 0) {
  120. return code;
  121. }
  122. }
  123. await coverage?.write();
  124. return code;
  125. }
  126. /** Runs the given test configurations. */
  127. async function runConfigs(config, enabledTests) {
  128. const prepared = await prepareConfigs(config, enabledTests);
  129. if (args.listConfiguration) {
  130. await new Promise((r) => process.stdout.write(JSON.stringify(prepared.map((p) => p.dumpJson())), r));
  131. return 0;
  132. }
  133. return runPreparedConfigs(config, prepared);
  134. }