版博士V2.0程序
Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.
 
 
 
 

619 строки
20 KiB

  1. import limit from 'p-limit';
  2. import { getSafeTimers, format, isObject, objDisplay, objectAttr, noop, toArray, shuffle } from '@vitest/utils';
  3. import { c as createChainable, g as generateHash, p as processError, a as calculateSuiteHash, s as someTasksAreOnly, i as interpretTaskModes, b as partitionSuiteChildren, h as hasTests, d as hasFailed } from './chunk-tasks.js';
  4. import { relative } from 'pathe';
  5. const fnMap = /* @__PURE__ */ new WeakMap();
  6. const hooksMap = /* @__PURE__ */ new WeakMap();
  7. function setFn(key, fn) {
  8. fnMap.set(key, fn);
  9. }
  10. function getFn(key) {
  11. return fnMap.get(key);
  12. }
  13. function setHooks(key, hooks) {
  14. hooksMap.set(key, hooks);
  15. }
  16. function getHooks(key) {
  17. return hooksMap.get(key);
  18. }
  19. const collectorContext = {
  20. tasks: [],
  21. currentSuite: null
  22. };
  23. function collectTask(task) {
  24. var _a;
  25. (_a = collectorContext.currentSuite) == null ? void 0 : _a.tasks.push(task);
  26. }
  27. async function runWithSuite(suite, fn) {
  28. const prev = collectorContext.currentSuite;
  29. collectorContext.currentSuite = suite;
  30. await fn();
  31. collectorContext.currentSuite = prev;
  32. }
  33. function withTimeout(fn, timeout, isHook = false) {
  34. if (timeout <= 0 || timeout === Infinity)
  35. return fn;
  36. const { setTimeout, clearTimeout } = getSafeTimers();
  37. return (...args) => {
  38. return Promise.race([fn(...args), new Promise((resolve, reject) => {
  39. var _a;
  40. const timer = setTimeout(() => {
  41. clearTimeout(timer);
  42. reject(new Error(makeTimeoutMsg(isHook, timeout)));
  43. }, timeout);
  44. (_a = timer.unref) == null ? void 0 : _a.call(timer);
  45. })]);
  46. };
  47. }
  48. function createTestContext(test, runner) {
  49. var _a;
  50. const context = function() {
  51. throw new Error("done() callback is deprecated, use promise instead");
  52. };
  53. context.meta = test;
  54. context.onTestFailed = (fn) => {
  55. test.onFailed || (test.onFailed = []);
  56. test.onFailed.push(fn);
  57. };
  58. return ((_a = runner.extendTestContext) == null ? void 0 : _a.call(runner, context)) || context;
  59. }
  60. function makeTimeoutMsg(isHook, timeout) {
  61. return `${isHook ? "Hook" : "Test"} timed out in ${timeout}ms.
  62. If this is a long-running ${isHook ? "hook" : "test"}, pass a timeout value as the last argument or configure it globally with "${isHook ? "hookTimeout" : "testTimeout"}".`;
  63. }
  64. const suite = createSuite();
  65. const test = createTest(
  66. function(name, fn, options) {
  67. getCurrentSuite().test.fn.call(this, name, fn, options);
  68. }
  69. );
  70. const describe = suite;
  71. const it = test;
  72. let runner;
  73. let defaultSuite;
  74. function getDefaultSuite() {
  75. return defaultSuite;
  76. }
  77. function getRunner() {
  78. return runner;
  79. }
  80. function clearCollectorContext(currentRunner) {
  81. if (!defaultSuite)
  82. defaultSuite = currentRunner.config.sequence.shuffle ? suite.shuffle("") : suite("");
  83. runner = currentRunner;
  84. collectorContext.tasks.length = 0;
  85. defaultSuite.clear();
  86. collectorContext.currentSuite = defaultSuite;
  87. }
  88. function getCurrentSuite() {
  89. return collectorContext.currentSuite || defaultSuite;
  90. }
  91. function createSuiteHooks() {
  92. return {
  93. beforeAll: [],
  94. afterAll: [],
  95. beforeEach: [],
  96. afterEach: []
  97. };
  98. }
  99. function createSuiteCollector(name, factory = () => {
  100. }, mode, concurrent, shuffle, suiteOptions) {
  101. const tasks = [];
  102. const factoryQueue = [];
  103. let suite2;
  104. initSuite();
  105. const test2 = createTest(function(name2, fn = noop, options = suiteOptions) {
  106. const mode2 = this.only ? "only" : this.skip ? "skip" : this.todo ? "todo" : "run";
  107. if (typeof options === "number")
  108. options = { timeout: options };
  109. const test3 = {
  110. id: "",
  111. type: "test",
  112. name: name2,
  113. mode: mode2,
  114. suite: void 0,
  115. fails: this.fails,
  116. retry: options == null ? void 0 : options.retry
  117. };
  118. if (this.concurrent || concurrent)
  119. test3.concurrent = true;
  120. if (shuffle)
  121. test3.shuffle = true;
  122. const context = createTestContext(test3, runner);
  123. Object.defineProperty(test3, "context", {
  124. value: context,
  125. enumerable: false
  126. });
  127. setFn(test3, withTimeout(
  128. () => fn(context),
  129. (options == null ? void 0 : options.timeout) ?? runner.config.testTimeout
  130. ));
  131. tasks.push(test3);
  132. });
  133. const custom = function(name2 = "") {
  134. const self = this || {};
  135. const task = {
  136. id: "",
  137. name: name2,
  138. type: "custom",
  139. mode: self.only ? "only" : self.skip ? "skip" : self.todo ? "todo" : "run"
  140. };
  141. tasks.push(task);
  142. return task;
  143. };
  144. const collector = {
  145. type: "collector",
  146. name,
  147. mode,
  148. test: test2,
  149. tasks,
  150. collect,
  151. custom,
  152. clear,
  153. on: addHook
  154. };
  155. function addHook(name2, ...fn) {
  156. getHooks(suite2)[name2].push(...fn);
  157. }
  158. function initSuite() {
  159. suite2 = {
  160. id: "",
  161. type: "suite",
  162. name,
  163. mode,
  164. shuffle,
  165. tasks: []
  166. };
  167. setHooks(suite2, createSuiteHooks());
  168. }
  169. function clear() {
  170. tasks.length = 0;
  171. factoryQueue.length = 0;
  172. initSuite();
  173. }
  174. async function collect(file) {
  175. factoryQueue.length = 0;
  176. if (factory)
  177. await runWithSuite(collector, () => factory(test2));
  178. const allChildren = [];
  179. for (const i of [...factoryQueue, ...tasks])
  180. allChildren.push(i.type === "collector" ? await i.collect(file) : i);
  181. suite2.file = file;
  182. suite2.tasks = allChildren;
  183. allChildren.forEach((task) => {
  184. task.suite = suite2;
  185. if (file)
  186. task.file = file;
  187. });
  188. return suite2;
  189. }
  190. collectTask(collector);
  191. return collector;
  192. }
  193. function createSuite() {
  194. function suiteFn(name, factory, options) {
  195. const mode = this.only ? "only" : this.skip ? "skip" : this.todo ? "todo" : "run";
  196. return createSuiteCollector(name, factory, mode, this.concurrent, this.shuffle, options);
  197. }
  198. suiteFn.each = function(cases, ...args) {
  199. const suite2 = this.withContext();
  200. if (Array.isArray(cases) && args.length)
  201. cases = formatTemplateString(cases, args);
  202. return (name, fn, options) => {
  203. const arrayOnlyCases = cases.every(Array.isArray);
  204. cases.forEach((i, idx) => {
  205. const items = Array.isArray(i) ? i : [i];
  206. arrayOnlyCases ? suite2(formatTitle(name, items, idx), () => fn(...items), options) : suite2(formatTitle(name, items, idx), () => fn(i), options);
  207. });
  208. };
  209. };
  210. suiteFn.skipIf = (condition) => condition ? suite.skip : suite;
  211. suiteFn.runIf = (condition) => condition ? suite : suite.skip;
  212. return createChainable(
  213. ["concurrent", "shuffle", "skip", "only", "todo"],
  214. suiteFn
  215. );
  216. }
  217. function createTest(fn) {
  218. const testFn = fn;
  219. testFn.each = function(cases, ...args) {
  220. const test2 = this.withContext();
  221. if (Array.isArray(cases) && args.length)
  222. cases = formatTemplateString(cases, args);
  223. return (name, fn2, options) => {
  224. const arrayOnlyCases = cases.every(Array.isArray);
  225. cases.forEach((i, idx) => {
  226. const items = Array.isArray(i) ? i : [i];
  227. arrayOnlyCases ? test2(formatTitle(name, items, idx), () => fn2(...items), options) : test2(formatTitle(name, items, idx), () => fn2(i), options);
  228. });
  229. };
  230. };
  231. testFn.skipIf = (condition) => condition ? test.skip : test;
  232. testFn.runIf = (condition) => condition ? test : test.skip;
  233. return createChainable(
  234. ["concurrent", "skip", "only", "todo", "fails"],
  235. testFn
  236. );
  237. }
  238. function formatTitle(template, items, idx) {
  239. if (template.includes("%#")) {
  240. template = template.replace(/%%/g, "__vitest_escaped_%__").replace(/%#/g, `${idx}`).replace(/__vitest_escaped_%__/g, "%%");
  241. }
  242. const count = template.split("%").length - 1;
  243. let formatted = format(template, ...items.slice(0, count));
  244. if (isObject(items[0])) {
  245. formatted = formatted.replace(
  246. /\$([$\w_.]+)/g,
  247. (_, key) => objDisplay(objectAttr(items[0], key))
  248. );
  249. }
  250. return formatted;
  251. }
  252. function formatTemplateString(cases, args) {
  253. const header = cases.join("").trim().replace(/ /g, "").split("\n").map((i) => i.split("|"))[0];
  254. const res = [];
  255. for (let i = 0; i < Math.floor(args.length / header.length); i++) {
  256. const oneCase = {};
  257. for (let j = 0; j < header.length; j++)
  258. oneCase[header[j]] = args[i * header.length + j];
  259. res.push(oneCase);
  260. }
  261. return res;
  262. }
  263. async function runSetupFiles(config, runner) {
  264. const files = toArray(config.setupFiles);
  265. if (config.sequence.setupFiles === "parallel") {
  266. await Promise.all(
  267. files.map(async (fsPath) => {
  268. await runner.importFile(fsPath, "setup");
  269. })
  270. );
  271. } else {
  272. for (const fsPath of files)
  273. await runner.importFile(fsPath, "setup");
  274. }
  275. }
  276. const now$1 = Date.now;
  277. async function collectTests(paths, runner) {
  278. const files = [];
  279. const config = runner.config;
  280. for (const filepath of paths) {
  281. const path = relative(config.root, filepath);
  282. const file = {
  283. id: generateHash(path),
  284. name: path,
  285. type: "suite",
  286. mode: "run",
  287. filepath,
  288. tasks: [],
  289. projectName: config.name
  290. };
  291. clearCollectorContext(runner);
  292. try {
  293. const setupStart = now$1();
  294. await runSetupFiles(config, runner);
  295. const collectStart = now$1();
  296. file.setupDuration = collectStart - setupStart;
  297. await runner.importFile(filepath, "collect");
  298. const defaultTasks = await getDefaultSuite().collect(file);
  299. setHooks(file, getHooks(defaultTasks));
  300. for (const c of [...defaultTasks.tasks, ...collectorContext.tasks]) {
  301. if (c.type === "test") {
  302. file.tasks.push(c);
  303. } else if (c.type === "custom") {
  304. file.tasks.push(c);
  305. } else if (c.type === "suite") {
  306. file.tasks.push(c);
  307. } else if (c.type === "collector") {
  308. const suite = await c.collect(file);
  309. if (suite.name || suite.tasks.length)
  310. file.tasks.push(suite);
  311. }
  312. }
  313. file.collectDuration = now$1() - collectStart;
  314. } catch (e) {
  315. const error = processError(e);
  316. file.result = {
  317. state: "fail",
  318. error,
  319. errors: [error]
  320. };
  321. }
  322. calculateSuiteHash(file);
  323. const hasOnlyTasks = someTasksAreOnly(file);
  324. interpretTaskModes(file, config.testNamePattern, hasOnlyTasks, false, config.allowOnly);
  325. files.push(file);
  326. }
  327. return files;
  328. }
  329. let _test;
  330. function setCurrentTest(test) {
  331. _test = test;
  332. }
  333. function getCurrentTest() {
  334. return _test;
  335. }
  336. const now = Date.now;
  337. function updateSuiteHookState(suite, name, state, runner) {
  338. var _a;
  339. if (!suite.result)
  340. suite.result = { state: "run" };
  341. if (!((_a = suite.result) == null ? void 0 : _a.hooks))
  342. suite.result.hooks = {};
  343. const suiteHooks = suite.result.hooks;
  344. if (suiteHooks) {
  345. suiteHooks[name] = state;
  346. updateTask(suite, runner);
  347. }
  348. }
  349. function getSuiteHooks(suite, name, sequence) {
  350. const hooks = getHooks(suite)[name];
  351. if (sequence === "stack" && (name === "afterAll" || name === "afterEach"))
  352. return hooks.slice().reverse();
  353. return hooks;
  354. }
  355. async function callSuiteHook(suite, currentTask, name, runner, args) {
  356. const sequence = runner.config.sequence.hooks;
  357. const callbacks = [];
  358. if (name === "beforeEach" && suite.suite) {
  359. callbacks.push(
  360. ...await callSuiteHook(suite.suite, currentTask, name, runner, args)
  361. );
  362. }
  363. updateSuiteHookState(currentTask, name, "run", runner);
  364. const hooks = getSuiteHooks(suite, name, sequence);
  365. if (sequence === "parallel") {
  366. callbacks.push(...await Promise.all(hooks.map((fn) => fn(...args))));
  367. } else {
  368. for (const hook of hooks)
  369. callbacks.push(await hook(...args));
  370. }
  371. updateSuiteHookState(currentTask, name, "pass", runner);
  372. if (name === "afterEach" && suite.suite) {
  373. callbacks.push(
  374. ...await callSuiteHook(suite.suite, currentTask, name, runner, args)
  375. );
  376. }
  377. return callbacks;
  378. }
  379. const packs = /* @__PURE__ */ new Map();
  380. let updateTimer;
  381. let previousUpdate;
  382. function updateTask(task, runner) {
  383. packs.set(task.id, task.result);
  384. const { clearTimeout, setTimeout } = getSafeTimers();
  385. clearTimeout(updateTimer);
  386. updateTimer = setTimeout(() => {
  387. previousUpdate = sendTasksUpdate(runner);
  388. }, 10);
  389. }
  390. async function sendTasksUpdate(runner) {
  391. var _a;
  392. const { clearTimeout } = getSafeTimers();
  393. clearTimeout(updateTimer);
  394. await previousUpdate;
  395. if (packs.size) {
  396. const p = (_a = runner.onTaskUpdate) == null ? void 0 : _a.call(runner, Array.from(packs));
  397. packs.clear();
  398. return p;
  399. }
  400. }
  401. const callCleanupHooks = async (cleanups) => {
  402. await Promise.all(cleanups.map(async (fn) => {
  403. if (typeof fn !== "function")
  404. return;
  405. await fn();
  406. }));
  407. };
  408. async function runTest(test, runner) {
  409. var _a, _b, _c, _d, _e, _f;
  410. await ((_a = runner.onBeforeRunTest) == null ? void 0 : _a.call(runner, test));
  411. if (test.mode !== "run")
  412. return;
  413. if (((_b = test.result) == null ? void 0 : _b.state) === "fail") {
  414. updateTask(test, runner);
  415. return;
  416. }
  417. const start = now();
  418. test.result = {
  419. state: "run",
  420. startTime: start
  421. };
  422. updateTask(test, runner);
  423. setCurrentTest(test);
  424. const retry = test.retry || 1;
  425. for (let retryCount = 0; retryCount < retry; retryCount++) {
  426. let beforeEachCleanups = [];
  427. try {
  428. await ((_c = runner.onBeforeTryTest) == null ? void 0 : _c.call(runner, test, retryCount));
  429. beforeEachCleanups = await callSuiteHook(test.suite, test, "beforeEach", runner, [test.context, test.suite]);
  430. test.result.retryCount = retryCount;
  431. if (runner.runTest) {
  432. await runner.runTest(test);
  433. } else {
  434. const fn = getFn(test);
  435. if (!fn)
  436. throw new Error("Test function is not found. Did you add it using `setFn`?");
  437. await fn();
  438. }
  439. await ((_d = runner.onAfterTryTest) == null ? void 0 : _d.call(runner, test, retryCount));
  440. test.result.state = "pass";
  441. } catch (e) {
  442. failTask(test.result, e);
  443. }
  444. try {
  445. await callSuiteHook(test.suite, test, "afterEach", runner, [test.context, test.suite]);
  446. await callCleanupHooks(beforeEachCleanups);
  447. } catch (e) {
  448. failTask(test.result, e);
  449. }
  450. if (test.result.state === "pass")
  451. break;
  452. updateTask(test, runner);
  453. }
  454. if (test.result.state === "fail")
  455. await Promise.all(((_e = test.onFailed) == null ? void 0 : _e.map((fn) => fn(test.result))) || []);
  456. if (test.fails) {
  457. if (test.result.state === "pass") {
  458. const error = processError(new Error("Expect test to fail"));
  459. test.result.state = "fail";
  460. test.result.error = error;
  461. test.result.errors = [error];
  462. } else {
  463. test.result.state = "pass";
  464. test.result.error = void 0;
  465. test.result.errors = void 0;
  466. }
  467. }
  468. setCurrentTest(void 0);
  469. test.result.duration = now() - start;
  470. await ((_f = runner.onAfterRunTest) == null ? void 0 : _f.call(runner, test));
  471. updateTask(test, runner);
  472. }
  473. function failTask(result, err) {
  474. result.state = "fail";
  475. const error = processError(err);
  476. result.error = error;
  477. result.errors ?? (result.errors = []);
  478. result.errors.push(error);
  479. }
  480. function markTasksAsSkipped(suite, runner) {
  481. suite.tasks.forEach((t) => {
  482. t.mode = "skip";
  483. t.result = { ...t.result, state: "skip" };
  484. updateTask(t, runner);
  485. if (t.type === "suite")
  486. markTasksAsSkipped(t, runner);
  487. });
  488. }
  489. async function runSuite(suite, runner) {
  490. var _a, _b, _c;
  491. await ((_a = runner.onBeforeRunSuite) == null ? void 0 : _a.call(runner, suite));
  492. if (((_b = suite.result) == null ? void 0 : _b.state) === "fail") {
  493. markTasksAsSkipped(suite, runner);
  494. updateTask(suite, runner);
  495. return;
  496. }
  497. const start = now();
  498. suite.result = {
  499. state: "run",
  500. startTime: start
  501. };
  502. updateTask(suite, runner);
  503. let beforeAllCleanups = [];
  504. if (suite.mode === "skip") {
  505. suite.result.state = "skip";
  506. } else if (suite.mode === "todo") {
  507. suite.result.state = "todo";
  508. } else {
  509. try {
  510. beforeAllCleanups = await callSuiteHook(suite, suite, "beforeAll", runner, [suite]);
  511. if (runner.runSuite) {
  512. await runner.runSuite(suite);
  513. } else {
  514. for (let tasksGroup of partitionSuiteChildren(suite)) {
  515. if (tasksGroup[0].concurrent === true) {
  516. const mutex = limit(runner.config.maxConcurrency);
  517. await Promise.all(tasksGroup.map((c) => mutex(() => runSuiteChild(c, runner))));
  518. } else {
  519. const { sequence } = runner.config;
  520. if (sequence.shuffle || suite.shuffle) {
  521. const suites = tasksGroup.filter((group) => group.type === "suite");
  522. const tests = tasksGroup.filter((group) => group.type === "test");
  523. const groups = shuffle([suites, tests], sequence.seed);
  524. tasksGroup = groups.flatMap((group) => shuffle(group, sequence.seed));
  525. }
  526. for (const c of tasksGroup)
  527. await runSuiteChild(c, runner);
  528. }
  529. }
  530. }
  531. } catch (e) {
  532. failTask(suite.result, e);
  533. }
  534. try {
  535. await callSuiteHook(suite, suite, "afterAll", runner, [suite]);
  536. await callCleanupHooks(beforeAllCleanups);
  537. } catch (e) {
  538. failTask(suite.result, e);
  539. }
  540. }
  541. suite.result.duration = now() - start;
  542. if (suite.mode === "run") {
  543. if (!hasTests(suite)) {
  544. suite.result.state = "fail";
  545. if (!suite.result.error) {
  546. const error = processError(new Error(`No test found in suite ${suite.name}`));
  547. suite.result.error = error;
  548. suite.result.errors = [error];
  549. }
  550. } else if (hasFailed(suite)) {
  551. suite.result.state = "fail";
  552. } else {
  553. suite.result.state = "pass";
  554. }
  555. }
  556. await ((_c = runner.onAfterRunSuite) == null ? void 0 : _c.call(runner, suite));
  557. updateTask(suite, runner);
  558. }
  559. async function runSuiteChild(c, runner) {
  560. if (c.type === "test")
  561. return runTest(c, runner);
  562. else if (c.type === "suite")
  563. return runSuite(c, runner);
  564. }
  565. async function runFiles(files, runner) {
  566. var _a, _b;
  567. for (const file of files) {
  568. if (!file.tasks.length && !runner.config.passWithNoTests) {
  569. if (!((_b = (_a = file.result) == null ? void 0 : _a.errors) == null ? void 0 : _b.length)) {
  570. const error = processError(new Error(`No test suite found in file ${file.filepath}`));
  571. file.result = {
  572. state: "fail",
  573. error,
  574. errors: [error]
  575. };
  576. }
  577. }
  578. await runSuite(file, runner);
  579. }
  580. }
  581. async function startTests(paths, runner) {
  582. var _a, _b, _c, _d;
  583. await ((_a = runner.onBeforeCollect) == null ? void 0 : _a.call(runner, paths));
  584. const files = await collectTests(paths, runner);
  585. (_b = runner.onCollected) == null ? void 0 : _b.call(runner, files);
  586. await ((_c = runner.onBeforeRun) == null ? void 0 : _c.call(runner, files));
  587. await runFiles(files, runner);
  588. await ((_d = runner.onAfterRun) == null ? void 0 : _d.call(runner, files));
  589. await sendTasksUpdate(runner);
  590. return files;
  591. }
  592. const getDefaultHookTimeout = () => getRunner().config.hookTimeout;
  593. const beforeAll = (fn, timeout) => getCurrentSuite().on("beforeAll", withTimeout(fn, timeout ?? getDefaultHookTimeout(), true));
  594. const afterAll = (fn, timeout) => getCurrentSuite().on("afterAll", withTimeout(fn, timeout ?? getDefaultHookTimeout(), true));
  595. const beforeEach = (fn, timeout) => getCurrentSuite().on("beforeEach", withTimeout(fn, timeout ?? getDefaultHookTimeout(), true));
  596. const afterEach = (fn, timeout) => getCurrentSuite().on("afterEach", withTimeout(fn, timeout ?? getDefaultHookTimeout(), true));
  597. const onTestFailed = createTestHook("onTestFailed", (test, handler) => {
  598. test.onFailed || (test.onFailed = []);
  599. test.onFailed.push(handler);
  600. });
  601. function createTestHook(name, handler) {
  602. return (fn) => {
  603. const current = getCurrentTest();
  604. if (!current)
  605. throw new Error(`Hook ${name}() can only be called inside a test`);
  606. handler(current, fn);
  607. };
  608. }
  609. export { afterAll, afterEach, beforeAll, beforeEach, describe, getCurrentSuite, getFn, it, onTestFailed, setFn, startTests, suite, test, updateTask };