From 3757f3cbe248d871f0f39789eaea1fd88e042aed Mon Sep 17 00:00:00 2001 From: eman2673 Date: Sun, 20 Feb 2022 05:38:06 -0500 Subject: [PATCH] runtime: Enable customization of parallel workers (#1588) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [#1044] allow assignment of work using api hook * [#1044] Using array instead of iterable adding id to IWorker for easy lookup of in progress pickles * [#1044] Adding type for parallelCanAssign Simplifying tests * [#1044] Using variable for workers instead of repeating _.values. No need for oneWokeWorker check. inProgressPickles is adequate for starters * [#1044] Mansplaining the custom worker assignment process * [#1044] Making casing consistent * Instead of killing the job, make sure at least 1 worker is assigned * Detailing example a bit more. Slight adjustments for fallback to serial processing * Put close worker back. No longer reused * Utilizing ParallelAssignmentValidator type for ISupportCodeLibrary.parallelCanAssign 1 member in IWorker to cover all states * Moving idle state to worker ready message Defaulting state to new * [#1044] Emitting warning for all workers idle Correct contradiction in README.md Simplifying example in README.md * Resolving some minor README issues * [#1044] Refactoring out nextPickleIndex + README example as test * Omitting complex 3 cause cannot guaranty the worker as both will be ready for work * Dropping use of _.values to iterate for waking workers * copy the pickleIds to leave the passed argument intact * Parsing test cases to verify order and parallelism * Using spawn tag to get errorOutput for warning validation Explicit step definitions for parallel test verification * Simplify tests (#4) Cleaning up feature file * Resolve conflicts (#5) * Cleaning up feature file * Cleaning up feature file * Use new messages without protobuf dependencies, and Markdown support. (#1645) * Start refactoring the code to use the new messages from the json-schema branch. To use those messages, first `npm link` every @cucumber/* module we depend on in the monorepo. Then, `npm link [all the modules]` from this repo. * Everything compiles * Fix import of messages module * Fix import of messages in coordinator * Update predictableTimestamp to work with new messages * Fix tests related to capture groups * Fix some more tests * Fix another spec * All unit tests passing * cck fixes * Make more scenarios pass * Fix import * Export Status * All features passing * npm test is now passing * Update @cucumber dependencies * Add support for Markdown * update yarn lockfile * Fix npm dependencies * Use .feature.md extension. Update dependencies. Co-authored-by: aurelien-reeves Co-authored-by: davidgoss * Update @cucumber/* dependencies (#1671) * Bump @cucumber/html-formatter * cli: have gherkin emit uris relative to the cwd (#1672) * pass relativeTo to gherkin-streams * fix json formatter * remove more usages of relative * another one * another one * last one * lint * fix this test * fix this test * add changelog Co-authored-by: Aslak Hellesøy <1000+aslakhellesoy@users.noreply.github.com> * Revert "cli: have gherkin emit uris relative to the cwd (#1672)" This reverts commit 5a21c229e284a41aea43bdaedece273845a18284. * cli: relative path fix again, now with windows (#1673) * generate html report on runs * reinstate original change This reverts commit 8a54a1b383bfe63d89365eec3988528bb3e0d8f0. * update test * fix pickle filter for windows * debt: add retry for publish tests (#1674) * add tag to feature * retry config for feature-test run * chore(deps): update dependency @types/bluebird to v3.5.35 (#1676) Co-authored-by: Renovate Bot * chore(deps): update dependency @types/lodash to v4.14.170 (#1678) Co-authored-by: Renovate Bot * format: report total steps correctly in progress bar (#1669) * make cck fail: remove reordering of testCase messages * add new function to deal with testCase * dont emit testCase from PickleRunner * include in result * fix up some tests * move tests to right places * emit test cases from serial runtime * scrappy impl to get serial working * remove unused field * refactor structures, fix tests * make coordinator api more promisey * start to hook up parallel * assemble test cases without ITestStep * remove unused function * TestCase is source of truth * TestCaseRunner is more accurate than PickleRunner? * make parallel runtime work with this * add explanatory comment * fix progress bar formatter counts * changelog * remove temp tag Co-authored-by: Aurélien Reeves * clarify changelog entry audience * cleanup Co-authored-by: Aurélien Reeves * chore(deps): update dependency @types/node to v14.17.1 (#1680) Co-authored-by: Renovate Bot * chore(deps): update dependency @types/express to v4.17.12 (#1677) Co-authored-by: Renovate Bot * chore(deps): update dependency @types/semver to v7.3.6 (#1679) Co-authored-by: Renovate Bot * chore(deps): update unit test packages (#1684) Co-authored-by: Renovate Bot * chore(deps): update dependency typescript to v4.3.2 (#1682) Co-authored-by: Renovate Bot * chore(deps): update dependency sinon to v11 (#1686) Co-authored-by: Renovate Bot * Publish reports to https://reports.cucumber.io * Revert "Publish reports to https://reports.cucumber.io" This reverts commit 85b0f1a290e61016048df40c0844bf31d8b5844d. * docs: more clarification on setDefaultTimeout * docs: mention that coord process.env copies to workers (#1693) Co-authored-by: Aurélien Reeves * docs: call out change of after hook result in migration guide (#1692) * document change of after hook result * better wording * whoops * Update docs/migration.md Co-authored-by: Aurélien Reeves Co-authored-by: Aurélien Reeves * Empty rerun file exits running no scenarios (#1302) (#1568) Co-authored-by: Aslak Hellesøy <1000+aslakhellesoy@users.noreply.github.com> Co-authored-by: Aurélien Reeves Co-authored-by: David Goss * chore(deps): update eslint packages (#1683) * chore(deps): update eslint packages * autofix prettier Co-authored-by: Renovate Bot Co-authored-by: Charles Rudolph * Update migration guide links (#1694) * typescript: type this as IWorld in user functions (#1690) * type this as any in user fns, add test * update changelog * setWorldConstructor for completeness * use generics to do it right * Update CHANGELOG.md * use a clearer generic type name * Pass --tags correctly, remove duplication * Revert "Pass --tags correctly, remove duplication" This reverts commit dbcb1778a2e80acd39d92113811d61d13560fd84. * debt: add things to main entry point that people need (#1697) * ensure hook parameters are exported * dont need to mark this arg as possibly undefined * export runtime options * expose formatter options * build: only audit production dependencies * chore: remove redundant profile config * Release 7.3.0 * 7.3.0 * refactor documentation (part 1) (#1699) * add export of cli --help * dont need note about sync * update world docs * document retry * document profiles * start to trim stuff from cli * more on profiles * document parallel * add linsk to readmr * Fixed reports banner to point to https://cucumber.io/docs/cucumber/environment-variables/ (#1703) * Add more arrow function warnings (#1705) * Add more arrow function warnings * Update links * fix(cli): allow targetting same file multiple times (#1708) * fix(cli): allow targetting same file multiple times * Add example to "run multiple scenarios" scenario outline * Update CHANGELOG.md * Deduplicate deduplicate message Co-authored-by: David Goss * update supported node versions (#1704) * update supported node versions * fix changelog * update package json Co-authored-by: David Goss * chore(deps): update dependency ts-node to v10 (#1687) Co-authored-by: Renovate Bot * chore(deps): update dependency fs-extra to v10 (#1685) Co-authored-by: Renovate Bot * chore(deps): update dependency tsd to v0.17.0 (#1681) * chore(deps): update dependency tsd to v0.17.0 * Add @tsd/typescript to dependency-lint ignore list Co-authored-by: Renovate Bot Co-authored-by: aurelien-reeves * cli: remove deprecated retryTagFilter option (#1713) * remove retryTagFilter camelCased option * add changelog entry * [WIP] remove lodash (#1709) * remove lodash wip * compiles * most unit tests + lint * passing unit tests * fix features * fix feature tests Co-authored-by: David Goss Co-authored-by: Aurélien Reeves * fix(deps): update dependency commander to v8 (#1720) * fix(deps): update dependency commander to v8 * Fix commander upgrade issue Co-authored-by: Renovate Bot Co-authored-by: aurelien-reeves * chore(deps): update dependency @types/node to v14.17.4 (#1715) Co-authored-by: Renovate Bot * chore(deps): update dependency typescript to v4.3.5 (#1716) Co-authored-by: Renovate Bot * chore(deps): update dependency mocha to v9 (#1719) Co-authored-by: Renovate Bot * chore(deps): update dependency @types/chai to v4.2.19 (#1714) Co-authored-by: Renovate Bot * chore(deps): update eslint packages (#1718) Co-authored-by: Renovate Bot * chore(deps): update cucumber packages (#1717) Co-authored-by: Renovate Bot * docs: fix node version mentioned in example * Fix import in docs * expose promise timeout helper (#1566) * Update CONTRIBUTING.md * Update CONTRIBUTING.md * IParameterTypeDefinition fix (#1733) Co-authored-by: Ludek Novy * bringing back v6/5 props (#1732) * bringing back v6/5 props * changelog update * Update CHANGELOG.md Co-authored-by: David Goss Co-authored-by: Ludek Novy Co-authored-by: David Goss * remove support for generators (#1725) * Explain how to use yarn to list commands (#1730) Co-authored-by: Matt Wynne * Add a 'reindent' test helper (#1722) * Upgrade dependencies (#1736) * (deps) update dependency lint (#1726) * chore: update changelog on main * add release step to edit+publish gh release * Bump reindent-template-literals to 1.1.0 (#1742) * chore(deps): update dependency @types/express to v4.17.13 (#1744) Co-authored-by: Renovate Bot * chore(deps): pin dependency reindent-template-literals to 1.1.0 (#1743) Co-authored-by: Renovate Bot * chore(deps): update dependency @types/glob to v7.1.4 (#1746) Co-authored-by: Renovate Bot * chore(deps): update dependency @types/mustache to v4.1.2 (#1747) Co-authored-by: Renovate Bot * chore(deps): update dependency @types/fs-extra to v9.0.12 (#1745) Co-authored-by: Renovate Bot * chore(deps): update dependency @types/node to v14.17.6 (#1749) Co-authored-by: Renovate Bot * chore(deps): update dependency @types/mz to v2.7.4 (#1748) Co-authored-by: Renovate Bot * chore(deps): update dependency @types/progress to v2.0.4 (#1750) Co-authored-by: Renovate Bot * chore(deps): update dependency @types/resolve to v1.20.1 (#1751) Co-authored-by: Renovate Bot * chore(deps): update dependency @types/node to v14.17.7 (#1757) Co-authored-by: Renovate Bot * chore(deps): update dependency @types/semver to v7.3.8 (#1752) Co-authored-by: Renovate Bot * chore(deps): update dependency @types/stream-buffers to v3.0.4 (#1753) Co-authored-by: Renovate Bot * chore(deps): update dependency @types/tmp to v0.2.1 (#1754) Co-authored-by: Renovate Bot * chore(deps): update eslint packages (#1756) Co-authored-by: Renovate Bot * chore(deps): update dependency @types/verror to v1.10.5 (#1755) Co-authored-by: Renovate Bot * chore(deps): update unit test packages (#1758) Co-authored-by: Renovate Bot * chore(deps): update dependency ts-node to v10.1.0 (#1760) Co-authored-by: Renovate Bot * chore(deps): update dependency @types/mocha to v9 (#1761) Co-authored-by: Renovate Bot * Make parameter type generic for value checker (#1764) * Add retro notes from new contributors ensemble (#1765) * Add retro folder and notes from last Friday * Add retro doc from the previous new-contributors session * Add some more actions from previous retro * Add pointer to retro-tools * Use youtube link for stream which is permanent * Move retro stuff into docs folder * Fix link to issue * Tweak CONTRIBUTING guide to be more beginner-friendly (#1767) As mentioned in the new contributors ensemble retro[1] [1]: https://github.com/cucumber/cucumber-js/blob/main/docs/retro/2021/07/17.md#what-should-we-decide--change-for-next-time * docs: minor fixes for the styling consistency (#1769) * Yarn to npm (#1774) * Change yarn to npm Co-authored-by: Matt Wynne * Changed the contributing guide use to npm Co-authored-by: Matt Wynne * fixed the autoformat from vscode * updated the build.yml to now work npm commands * fixed the update-dependencies * try using npm 7 with all node versions Co-authored-by: Matt Wynne Co-authored-by: David Goss * Use typescript incremental build to speed up compilation (#1766) Co-authored-by: David Goss * chore(deps): update dependency @types/node to v14.17.12 (#1778) Co-authored-by: Renovate Bot * chore(deps): update dependency @types/progress to v2.0.5 (#1779) Co-authored-by: Renovate Bot * chore(deps): update dependency mocha to v9.1.1 (#1780) Co-authored-by: Renovate Bot * chore(deps): update dependency ts-node to v10.2.1 (#1781) Co-authored-by: Renovate Bot * chore(deps): update dependency typescript to v4.4.2 (#1782) Co-authored-by: Renovate Bot * chore(deps): update dependency @types/node to v14.17.14 (#1785) Co-authored-by: Renovate Bot * chore(deps): update eslint packages (#1787) Co-authored-by: Renovate Bot * chore(deps): update eslint packages (#1786) * chore(deps): update eslint packages * Fix linting errors Co-authored-by: Renovate Bot Co-authored-by: aurelien-reeves * Deactivate renovate dependency dashboard As discussed at our last community meeting, we do not want those dashboards. * chore: update @cucumber/* dependencies, fix willBeRetried usage (#1776) * latest dependencies * make it just about compile * fix test case runner * fix summary helper * fix formatters (ish) * fix last bit in formatters * update fixtures for feature tests * fix attachments cck * hook up retry cck * lint * update doc * update lockfile * Add configuration cli option (#1794) * Config file Option update * Add --config option in the argv parser * Add a scenario in profiles.feature * Add unit tests and refactorize profile_loader * Consider the new --config option when loading profiles * Add some documentation * Add an entry in the changelog Co-authored-by: deepziem <54252717+deepziem@users.noreply.github.com> * feat: add pickleStep to step hook function arg (#1775) * add to interface * implement * update api ref * update changelgo * add test * Increase precision of test case duration measurements. (#1793) * fix(formatter): Enable calling parseTestCaseAttempt on test cases that haven't completed (#1531) * fix(formatter): Enable calling parseTestCaseAttempt on test cases that haven't completed yet * Instanciate a proper TestStepResult when parsing TestCaseAttempt * Add unit tests * Refactor testCaseAttemptParser unit tests Co-authored-by: Aurélien Reeves Co-authored-by: Aslak Hellesøy <1000+aslakhellesoy@users.noreply.github.com> * add ESM support (take 2) (#1649) * Revert "temporarily revert ESM change (#1647)" This reverts commit 084c1f258097d1e424c3ecddf8bd980d8d8f5229. * add failing scenario for deep imports * define entry point with dot * make deep imports work via export patterns * move doc to own file * link to doc from readme * add changelog entry * add example to doc * remove confusing comment * remove cli option, use import by default * update documentation * remove redundant describe * fix ordering * Update features/esm.feature Co-authored-by: Aurélien Reeves * Update features/esm.feature Co-authored-by: Aurélien Reeves * simplify tagging * use import only if a javascript file * add note about no transpilers * inline to avoid confusing reassignment * whoops, re-add try/catch * use require with transpilers; import otherwise * remove pointless return * support .cjs config file * type and import the importer * actually dont import - causes issues Co-authored-by: Aurélien Reeves * docs: add rule to keywords for i18n command (#1800) * add rule to keywords for i18n command * Fix i18n example Co-authored-by: aurelien-reeves * set correct version * debt: remove --predictable-ids option (#1801) * WIP * fix up testing * add changelog entry * chore(deps): update dependency @types/fs-extra to v9.0.13 (#1803) Co-authored-by: Renovate Bot * chore(deps): update dependency @types/node to v14.17.20 (#1804) Co-authored-by: Renovate Bot * chore(deps): update dependency typescript to v4.4.3 (#1805) Co-authored-by: Renovate Bot * chore(deps): update dependency @sinonjs/fake-timers to v8 (#1810) Co-authored-by: Renovate Bot * chore(deps): update eslint packages (#1809) Co-authored-by: Renovate Bot * chore(deps): update dependency coffeescript to v2.6.0 (#1808) Co-authored-by: Renovate Bot * fix(deps): update cucumber packages (#1807) Co-authored-by: Renovate Bot * chore(deps): update unit test packages (#1806) Co-authored-by: Renovate Bot * chore(deps): update cucumber packages (major) (#1791) * chore(deps): update cucumber packages * Implement new CCK tests from CCK 8.0.0 * Bump compatibility-kit to v9.0.0 Co-authored-by: Renovate Bot Co-authored-by: aurelien-reeves * fix(deps): update cucumber packages (major) (#1811) * chore(deps): update cucumber packages * Implement new CCK tests from CCK 8.0.0 * Bump compatibility-kit to v9.0.0 * fix(deps): update cucumber packages * Fix requires of cucumber-expressions GeneratedExpression class Co-authored-by: Renovate Bot Co-authored-by: aurelien-reeves * Fix link to 7.3.1 * support: re-add setDefinitionFunctionWrapper (minus generator step logic) (#1795) * Revert "remove support for generators (#1725)" This reverts commit a2dcce6a45dd20bf7a52def5ea9b5f4a3743a642. * Remove bluebird and related dependencies * Remove support for generator functions * Update mocha config * Add forbid-pending to mocharc too * Update migration and api_reference documents * Update changelog * Update CHANGELOG entry * Fix dependency audit issue * List formatters in help command (#1798) * feature/list-formatters-in-help-command adding documentation field to Formatter class * feature/list-formatters-in-help-command refactoring getConstructorByType method to hold a Record * feature/list-formatters-in-help-command improving return statement to deal with cases where the default formatter should be returned * feature/list-formatters-in-help-command after running lint fix * feature/list-formatters-in-help-command fixing ternary so logic does not invoke load customFormatter * feature/list-formatters-in-help-command creating class that will hold the description of the formatters * feature/list-formatters-in-help-command adding logic to extract the correct documentation for each formatter and altering the IFormatter type to have this field * feature/list-formatters-in-help-command reverting changes made by adding the documentation field to the formatter object * feature/list-formatters-in-help-command adding documentation field to html formatter * feature/list-formatters-in-help-command adding documentation member to json/message/progress/rerun/summary/usage formatters * feature/list-formatters-in-help-command removing formatterDocumentationHelper class as it is no longer needed * feature/list-formatters-in-help-command adding documentation field to rerun formatter * feature/list-formatters-in-help-command fixing return type of getConstructorByType method and running linter * feature/list-formatters-in-help-command removing unnecessary await * feature/list-formatters-in-help-command creating Formatters class to hold different formatters and extracting them from the builder class * feature/list-formatters-in-help-command adding documentation field to progress-bar/snippets/usage-json formatters * feature/list-formatters-in-help-command added method in formatters class to help build the documentation string * feature/list-formatters-in-help-command used recently added method to list all available formatters * feature/list-formatters-in-help-command adding documentation to snippets/progress-bar/usage-json formatters * feature/list-formatters-in-help-command adding new line to format option so that formatters will appear on new line * feature/list/formatters-in-help-command converting documentation field inside formatter to be public and static. Refactoring buildFormatterDocumentationString * feature/list/formatters-in-help-command indenting formatters and removing extra space * feature/list-formatters-in-help-command refactoring building the documentation string * feature/list-formatters-in-help-command adding feature to changelog * (docs,snippets): redo formatter docs, new loading strategy for snippet syntaxes (#1812) * start the formatters doc * document summary formatter * document progress formatter * progress bar * regenerate gifs * clutching at straws here * optimise gifs * edit out the summary failure one for mpw * describe unhappy path for summary * html formatter doco * usage and usage-json doco * replace html formatter screenshot * try again? * update git attrs * wip snippets doco * readd png * better version of html screenshot * better again * document message and json formatters * fix messages link * more info on snippets * finish up snippets * rerun docs * finish up * load snippet syntax in same way as formatters * clarify what the options are * tweak rerun docs * differentiate retry vs rerun in docs * simplify readme * add changelog * make promise interface return a promise * add example output for snippet interfaces * runtime: don't fail the test run for undefined/ambiguous when in dry run (#1814) * update scenario (failing) * clarify * change logic * refactor to share logic across serial+parallel * add changelog * add doco for dry run * add link to changelog entry * Fix github diff link to use main instead of master * Delete .whitesource We have renovate.json * Upgrade dependencies 20211018 (#1820) * Update @cucumber dependencies * Update mocha,ts-node,typescript * fall back to require where file doesnt have a native js extension (#1819) Co-authored-by: Aslak Hellesøy <1000+aslakhellesoy@users.noreply.github.com> * Add Release workflow - see https://github.com/cucumber/.github/blob/main/RELEASING.md * Add missing changelog and contributor entries * Add missing comma * Format changelog (#1821) * Format changelog * Fix links * Fix release workflow * Update release process, use .yaml extension for workflows * Release 8.0.0-rc.1 * Refactor build helpers (#1826) * Extract functions into their own files * Allow injection of exclusion filter to make easier to test * Make sure we always exclude ourselves * chore(deps): pin dependencies (#1827) Co-authored-by: Renovate Bot * chore(deps): pin dependencies (#1828) Co-authored-by: Renovate Bot * chore(deps): pin dependency mocha to 9.1.3 (#1829) Co-authored-by: Renovate Bot * chore(deps): update dependency @types/node to v14.17.32 (#1830) Co-authored-by: Renovate Bot * chore(deps): update dependency @types/semver to v7.3.9 (#1831) Co-authored-by: Renovate Bot * chore(deps): update dependency @types/tmp to v0.2.2 (#1832) Co-authored-by: Renovate Bot * chore(deps): update dependency coffeescript to v2.6.1 (#1833) Co-authored-by: Renovate Bot * chore(deps): update dependency @cucumber/compatibility-kit to v9.1.2 (#1834) Co-authored-by: Renovate Bot * chore(deps): update dependency ts-node to v10.4.0 (#1836) Co-authored-by: Renovate Bot * chore(deps): update dependency @types/glob to v7.2.0 (#1835) Co-authored-by: Renovate Bot * chore(deps): update dependency tsd to v0.18.0 (#1837) Co-authored-by: Renovate Bot * chore(deps): update eslint packages (#1838) Co-authored-by: Renovate Bot * Ignore OS X files * chore(deps): update dependency @types/node to v16 (#1839) * Factor out instructions about dependency upgrades into central file * Update RELEASING.md * Fix-1735 Parentheses in developers' paths break cucumber's own tests WIP (#1824) * Extract functions into their own files * Allow injection of exclusion filter to make easier to test * Make sure we always exclude ourselves * Add unit test for getDefinitionLineAndUri * -adds regex pattern for stack traces -removes dependencies for StackFram library * - adds "source-map-support" dependency - progress towards fixing bug for paths with parentheses Cucumber's own features fail when parent directory contains parentheses #1735 - gets accurate line numbers for Error stacks in typescript Co-authored-by: Blaise Pabon Co-authored-by: Matt Wynne * update cspotcode/source-map-support * remove .DS_Store * updates unit test to support paths on windows Co-authored-by: Matt Wynne Co-authored-by: Blaise Pabon Co-authored-by: Dane Parchment * Removes assertion for a failing test that's no longer needed Co-authored-by: Matt Wynne Co-authored-by: Blaise Pabon Co-authored-by: Dane Parchment * Removes exception for the custom stack trace feature Co-authored-by: Matt Wynne Co-authored-by: Blaise Pabon Co-authored-by: Dane Parchment * Updates changelog Co-authored-by: Matt Wynne Co-authored-by: Blaise Pabon Co-authored-by: Dane Parchment * fixed linting for previous commit Co-authored-by: Matt Wynne Co-authored-by: Blaise Pabon Co-authored-by: Dane Parchment Co-authored-by: Matt Wynne Co-authored-by: Blaise Pabon Co-authored-by: Matt Wynne Co-authored-by: Dane Parchment * chore(deps): update dependency @types/node to v16.11.11 (#1854) Co-authored-by: Renovate Bot * fix(deps): update dependency @cucumber/create-meta to v6.0.4 (#1856) Co-authored-by: Renovate Bot * chore(deps): update eslint packages (#1855) Co-authored-by: Renovate Bot * chore(deps): update dependency tsd to v0.19.0 (#1857) Co-authored-by: Renovate Bot * chore(deps): update dependency eslint-plugin-promise to v5.2.0 (#1861) Co-authored-by: Renovate Bot * chore(deps): update unit test packages (#1860) Co-authored-by: Renovate Bot * chore(deps): update unit test packages (#1859) Co-authored-by: Renovate Bot * chore(deps): update dependency typescript to v4.5.2 (#1858) Co-authored-by: Renovate Bot * Update esm.md (#1862) Move the chapter about configuration file at the top of the doc to give it more visibility * Smoother onboarding for Windows developers (#1863) * Add a warning for Windows developers The tests won't work if you don't have "Developer Mode" enabled. See #1852 Co-authored-by: Aurelien Reeves * Explain about Developer Mode in contributing guide * Use cross-platform command for copying files * Update changelog * No need to npx in a node script Co-authored-by: Aurelien Reeves * api: add runCucumber function internally (#1849) * Export version number of cucumber-js (#1866) * Export version number of cucumber-js * Update CHANGELOG.md * Add package.json to node module exports (#1870) * Add package.json to node module exports * Update changelog * Add a scenario to validate we can export package.json and version numbers * Use template literal rather than string concatenation in direct_imports.feature * Change entry in the changelog * handle spaces in the absolute path (#1845) (#1847) * put quotes around the absolute path (#1845) added quotes to wrap the path to summary.txt to ensure that paths containing spaces are read properly Co-authored-by: Matt Wynne Co-authored-by: Blaise Pabon Co-authored-by: Dane Parchment * fix indentations in feature file * fixed the bug but needs unit testing * fixed linting * adds unit testing for handling paths with quotes * adds the fix to option splitter files * updated changelog and removed wip tag Co-authored-by: Matt Wynne Co-authored-by: Blaise Pabon Co-authored-by: Dane Parchment Co-authored-by: Aslak Hellesøy <1000+aslakhellesoy@users.noreply.github.com> * Update contributing guide * chore: use new ci-environment package instead of create-meta (#1868) * install lib * WIP * bump other cucumber deps * finish impl * add changelog * redundant comment * update library, simplify mapping * simplify again * update changelog * build: add build artifact for reports * Replace 1 instance of regex with cucumber expression (#1872) * Replace regex with cucumber expression. We decided to split the step definition into two. So that the patterns used be simpler. Co-authored-by: Blaise Pabon Co-authored-by: Matt Wynne Co-authored-by: Dane Parchment Jr * Fix linting issues Co-authored-by: Blaise Pabon Co-authored-by: Matt Wynne Co-authored-by: Dane Parchment Jr Co-authored-by: aurelien-reeves * Replace 2 instances of regex with cucumber expression (#1873) * Replace 2 instance of regex with cucumber expression * Fixing linting issues * Optimizing const string * Making Prettier: from " to ' * chore(deps): pin dependencies (#1874) Co-authored-by: Renovate Bot * chore(deps): update dependency @types/tmp to v0.2.3 (#1876) Co-authored-by: Renovate Bot * chore(deps): update dependency express to v4.17.2 (#1877) Co-authored-by: Renovate Bot * chore(deps): update dependency @types/node to v16.11.17 (#1875) Co-authored-by: Renovate Bot * chore(deps): update dependency tsd to v0.19.1 (#1879) Co-authored-by: Renovate Bot * chore(deps): update dependency prettier to v2.5.1 (#1878) Co-authored-by: Renovate Bot * chore(deps): update dependency typescript to v4.5.4 (#1880) Co-authored-by: Renovate Bot Co-authored-by: David Goss * fix(deps): update dependency @cucumber/ci-environment to v8.0.1 (#1881) Co-authored-by: Renovate Bot Co-authored-by: David Goss * chore(deps): update unit test packages (#1882) Co-authored-by: Renovate Bot Co-authored-by: David Goss * chore(deps): update eslint packages (major) (#1840) * chore(deps): update eslint packages * Update eslint configuration - remove plugins which prevent upgrading eslint - update the configuration based on the one from cucumber-expression - update a piece of code to make linting happy Note: some rules have been deactivated to make the update of eslint possible without breaking our build. Those rules may be deactivated later as part of dedicated pull requests. * Activate eslint-plugin-simple-import-sort * Add simple-import-sort to dependency-lint ignore list * Revert "Add simple-import-sort to dependency-lint ignore list" This reverts commit 1bd2f3212b3c9ddf666b11710c524a9fb4a2e36c. * Revert "Activate eslint-plugin-simple-import-sort" This reverts commit a0075e73cd735942e03cb99eda5b73c53a4eb31a. * Remove eslint-plugin-simple-import-sort Co-authored-by: Renovate Bot Co-authored-by: aurelien-reeves * chore: remove defunct npm script * fix: update colors@1.4.0 cli-table2@0.6.1 (#1886) * Update package.json A Security Vuln was identified in the Colors package for >1.4.0, offending packages being `1.4.1`, `1.4.44-liberty` - [source1](https://twitter.com/snyksec/status/1480286811482206216?ref_src=twsrc%5Egoogle%7Ctwcamp%5Eserp%7Ctwgr%5Etweet) - [source2](https://twitter.com/snyksec/status/1480286811482206216?ref_src=twsrc%5Egoogle%7Ctwcamp%5Eserp%7Ctwgr%5Etweet) - [source3](https://security.snyk.io/vuln/SNYK-JS-COLORS-2331906) This PR pins the color package to `1.4.0` as advised on the [snyk page](https://snyk.io/blog/open-source-maintainer-pulls-the-plug-on-npm-packages-colors-and-faker-now-what/) * chore: update changelog * fix: update and pin cli-table3@0.6.1 * chore: update CHANGELOG * chore: update lockfile with new pinned versions * Release 8.0.0-rc.2 * Add a retro Co-authored-by: Blaise Pabon Co-authored-by: Kate Dames * Extract prettier config from eslintrc (#1893) This is a more conventional place to store prettier config, and it means that VSCode's prettier plugin can automatically find it. * chore: bump dependency with vulnerability * build: only build on main and for PRs * chore: switch from colors to chalk (#1895) * swap out dependencies * reimpl * add changelog entry * remove unused import * Replace some uses of `any` type (#1892) * Replace use of `any` type with `messages.Envelope` Part of #1648 Co-authored-by: Kate Dames Co-authored-by: Emmanuel Ola <54866720+eoola@users.noreply.github.com> * Replace use of `any` with a custom World in CCK example Part of #1648 Co-authored-by: Emmanuel Ola <54866720+eoola@users.noreply.github.com> Co-authored-by: Blaise Pabon Co-authored-by: Kate Dames * Replace another use of `any` with a custom type Co-authored-by: Blaise Pabon Co-authored-by: Kate Dames * Replace another use of `any` type Co-authored-by: Blaise Pabon Co-authored-by: Kate Dames Co-authored-by: Kate Dames Co-authored-by: Emmanuel Ola <54866720+eoola@users.noreply.github.com> Co-authored-by: Blaise Pabon * docs: improve profiles documentation (#1897) * Update profiles.md * Update docs/profiles.md Co-authored-by: Aurélien Reeves Co-authored-by: Aurélien Reeves * Add new-contributors retro Co-authored-by: Blaise Pabon Co-authored-by: Kate Dames * Consolidate retro files * Removing usage of lodash Co-authored-by: Aslak Hellesøy <1000+aslakhellesoy@users.noreply.github.com> Co-authored-by: aurelien-reeves Co-authored-by: davidgoss Co-authored-by: Aslak Hellesøy Co-authored-by: David Goss Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Renovate Bot Co-authored-by: jshifflet Co-authored-by: Charles Rudolph Co-authored-by: Nico Jansen Co-authored-by: Matt Wynne Co-authored-by: Ludek <13610612+ludeknovy@users.noreply.github.com> Co-authored-by: Ludek Novy Co-authored-by: Cucumber Ensemble <87445349+cucumber-ensemble@users.noreply.github.com> Co-authored-by: Matt Wynne Co-authored-by: 16sheep Co-authored-by: Dmytro Shpakovskyi Co-authored-by: abelalmeida Co-authored-by: deepziem <54252717+deepziem@users.noreply.github.com> Co-authored-by: Joaquín Sorianello Co-authored-by: Jan Molak <1089173+jan-molak@users.noreply.github.com> Co-authored-by: David Goss Co-authored-by: Tomer Ben-Rachel Co-authored-by: Emmanuel Ola <54866720+eoola@users.noreply.github.com> Co-authored-by: Blaise Pabon Co-authored-by: Dane Parchment Co-authored-by: Karla Aparecida Justen Co-authored-by: Manny Co-authored-by: Kate Dames Co-authored-by: Michael Morris <35374244+michaelm-rsi@users.noreply.github.com> * update feature helper * change table structure * reduce timeout, attempt to make more reliable on windows * fix timing issue, larger time window to reduce flakes * Update CHANGELOG.md * update docs * increase time to decrease chance of flakes Co-authored-by: Aslak Hellesøy <1000+aslakhellesoy@users.noreply.github.com> Co-authored-by: aurelien-reeves Co-authored-by: davidgoss Co-authored-by: Aslak Hellesøy Co-authored-by: David Goss Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Renovate Bot Co-authored-by: jshifflet Co-authored-by: Charles Rudolph Co-authored-by: Nico Jansen Co-authored-by: Matt Wynne Co-authored-by: Ludek <13610612+ludeknovy@users.noreply.github.com> Co-authored-by: Ludek Novy Co-authored-by: Cucumber Ensemble <87445349+cucumber-ensemble@users.noreply.github.com> Co-authored-by: Matt Wynne Co-authored-by: 16sheep Co-authored-by: Dmytro Shpakovskyi Co-authored-by: abelalmeida Co-authored-by: deepziem <54252717+deepziem@users.noreply.github.com> Co-authored-by: Joaquín Sorianello Co-authored-by: Jan Molak <1089173+jan-molak@users.noreply.github.com> Co-authored-by: David Goss Co-authored-by: Tomer Ben-Rachel Co-authored-by: Emmanuel Ola <54866720+eoola@users.noreply.github.com> Co-authored-by: Blaise Pabon Co-authored-by: Dane Parchment Co-authored-by: Karla Aparecida Justen Co-authored-by: Manny Co-authored-by: Kate Dames Co-authored-by: Michael Morris <35374244+michaelm-rsi@users.noreply.github.com> --- CHANGELOG.md | 2 + docs/parallel.md | 39 ++++++ docs/support_files/api_reference.md | 15 +++ features/parallel_custom_assign.feature | 69 +++++++++++ features/step_definitions/parallel_steps.ts | 57 +++++++++ src/cli/helpers_spec.ts | 1 + src/index.ts | 1 + src/runtime/parallel/README.md | 23 +++- src/runtime/parallel/coordinator.ts | 115 +++++++++++++++--- src/support_code_library_builder/index.ts | 7 ++ .../parallel_can_assign_helpers.ts | 19 +++ .../parallel_can_assign_helpers_spec.ts | 72 +++++++++++ src/support_code_library_builder/types.ts | 7 +- 13 files changed, 409 insertions(+), 18 deletions(-) create mode 100644 features/parallel_custom_assign.feature create mode 100644 features/step_definitions/parallel_steps.ts create mode 100644 src/support_code_library_builder/parallel_can_assign_helpers.ts create mode 100644 src/support_code_library_builder/parallel_can_assign_helpers_spec.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index ec7f52065..06a06bf44 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,8 @@ Please see [CONTRIBUTING.md](https://github.com/cucumber/cucumber/blob/master/CO ### Added - Cucumber Expressions now support a wider array of parameter types (see [documentation](https://github.com/cucumber/cucumber-expressions#parameter-types)) - Improved styling and usability on report from `html` formatter +- Support for customising work assignment when running in parallel ([#1044](https://github.com/cucumber/cucumber-js/issues/1044) + [#1588](https://github.com/cucumber/cucumber-js/pull/1588)) - Add a new option to `--format-options`: `printAttachments`. See [./docs/cli.md#printing-attachments-details](https://github.com/cucumber/cucumber-js/blob/main/docs/cli.md#printing-attachments-details) for more info. ([#1136](https://github.com/cucumber/cucumber-js/issues/1136) diff --git a/docs/parallel.md b/docs/parallel.md index 889c027d8..a730f1c5e 100644 --- a/docs/parallel.md +++ b/docs/parallel.md @@ -27,3 +27,42 @@ When using parallel mode, the last line of the summary output differentiates bet ### Hooks When using parallel mode, any `BeforeAll` and `AfterAll` hooks you have defined will run _once per worker_. + +### Custom work assignment + +If you would like to prevent specific sets of scenarios from running in parallel you can use `setParallelCanAssign`. + +Example: +```javascript +setParallelCanAssign(function(pickleInQuestion, picklesInProgress) { + // Only one pickle with the word example in the name can run at a time + if (pickleInQuestion.name.includes("example")) { + return picklesInProgress.every(p => !p.name.includes("example")); + } + // No other restrictions + return true; +}) +``` + +For convenience, the following helpers exist to build a `canAssignFn`: + +```javascript +import { setParallelCanAssign } from '@cucumber/cucumber' +import { atMostOnePicklePerTag } from '@cucumber/cucumber/lib/support_code_library_builder/parallel_can_assign_helpers' + +const myTagRule = atMostOnePicklePerTag(["@tag1", "@tag2"]); + +// Only one pickle with @tag1 can run at a time +// AND only one pickle with @tag2 can run at a time +setParallelCanAssign(myTagRule) + +// If you want to join a tag rule with other rules you can compose them like so: +const myCustomRule = function(pickleInQuestion, picklesInProgress) { + // ... +}; + +setParallelCanAssign(function(pickleInQuestion, picklesInProgress) { + return myCustomRule(pickleInQuestion, picklesInProgress) && + myTagRule(pickleInQuestion, picklesInProgress); +}) +``` diff --git a/docs/support_files/api_reference.md b/docs/support_files/api_reference.md index c3fa28d45..dee006a94 100644 --- a/docs/support_files/api_reference.md +++ b/docs/support_files/api_reference.md @@ -164,6 +164,21 @@ When used, the result is wrapped again to ensure it has the same length of the o --- +### setParallelCanAssign(canAssignFn) + +Set the function used to determine if a pickle can be executed based on currently executing pickles. + +The `canAssignFn` function is expected to take 2 arguments: + +- `pickleInQuestion` is the a pickle we are checking if its okay to run +- `picklesInProgress` is an array of pickles currently being executed + +And returns true if the pickle can be executed, false otherwise. + +See examples in our [parallel](../parallel.md) documentation. + +--- + #### `setWorldConstructor(constructor)` Set a custom world constructor, to override the default world constructor: diff --git a/features/parallel_custom_assign.feature b/features/parallel_custom_assign.feature new file mode 100644 index 000000000..1594e6769 --- /dev/null +++ b/features/parallel_custom_assign.feature @@ -0,0 +1,69 @@ +Feature: Running scenarios in parallel with custom assignment + + @spawn + Scenario: Bad parallel assignment helper uses 1 worker + Given a file named "features/step_definitions/cucumber_steps.js" with: + """ + const {Given, setParallelCanAssign} = require('@cucumber/cucumber') + + setParallelCanAssign(() => false) + + Given('slow step', (done) => setTimeout(done, 50)) + """ + And a file named "features/a.feature" with: + """ + Feature: only one worker works + Scenario: someone must do work + Given slow step + + Scenario: even if it's all the work + Given slow step + """ + When I run cucumber-js with `--parallel 2` + Then the error output contains the text: + """ + WARNING: All workers went idle 2 time(s). Consider revising handler passed to setParallelCanAssign. + """ + And no pickles run at the same time + + Scenario: assignment is appropriately applied + Given a file named "features/step_definitions/cucumber_steps.js" with: + """ + const {Given, setParallelCanAssign} = require('@cucumber/cucumber') + const {atMostOnePicklePerTag} = require('@cucumber/cucumber/lib/support_code_library_builder/parallel_can_assign_helpers') + + setParallelCanAssign(atMostOnePicklePerTag(["@complex", "@simple"])) + + Given('complex step', (done) => setTimeout(done, 3000)) + Given('simple step', (done) => setTimeout(done, 2000)) + """ + And a file named "features/a.feature" with: + """ + Feature: adheres to setParallelCanAssign handler + @complex + Scenario: complex1 + Given complex step + + @complex + Scenario: complex2 + Given complex step + + @simple + Scenario: simple1 + Given simple step + + @simple + Scenario: simple2 + Given simple step + + @simple + Scenario: simple3 + Given simple step + """ + When I run cucumber-js with `--parallel 2` + Then it passes + And the following sets of pickles execute at the same time: + | complex1, simple1 | + | complex1, simple2 | + | complex2, simple2 | + | complex2, simple3 | \ No newline at end of file diff --git a/features/step_definitions/parallel_steps.ts b/features/step_definitions/parallel_steps.ts new file mode 100644 index 000000000..e97c156f7 --- /dev/null +++ b/features/step_definitions/parallel_steps.ts @@ -0,0 +1,57 @@ +import { DataTable, Then } from '../../' +import { World } from '../support/world' +import messages from '@cucumber/messages' +import { expect } from 'chai' + +function getSetsOfPicklesRunningAtTheSameTime( + envelopes: messages.Envelope[] +): string[] { + const pickleIdToName: Record = {} + const testCaseIdToPickleId: Record = {} + const testCaseStarteIdToPickleId: Record = {} + let currentRunningPickleIds: string[] = [] + const result: string[] = [] + envelopes.forEach((envelope) => { + if (envelope.pickle != null) { + pickleIdToName[envelope.pickle.id] = envelope.pickle.name + } else if (envelope.testCase != null) { + testCaseIdToPickleId[envelope.testCase.id] = envelope.testCase.pickleId + } else if (envelope.testCaseStarted != null) { + const pickleId = testCaseIdToPickleId[envelope.testCaseStarted.testCaseId] + testCaseStarteIdToPickleId[envelope.testCaseStarted.id] = pickleId + currentRunningPickleIds.push(pickleId) + if (currentRunningPickleIds.length > 1) { + const setOfPickleNames = currentRunningPickleIds + .map((x) => pickleIdToName[x]) + .sort() + .join(', ') + result.push(setOfPickleNames) + } + } else if (envelope.testCaseFinished != null) { + const pickleId = + testCaseStarteIdToPickleId[envelope.testCaseFinished.testCaseStartedId] + currentRunningPickleIds = currentRunningPickleIds.filter( + (x) => x != pickleId + ) + } + }) + return result +} + +Then('no pickles run at the same time', function (this: World) { + const actualSets = getSetsOfPicklesRunningAtTheSameTime( + this.lastRun.envelopes + ) + expect(actualSets).to.eql([]) +}) + +Then( + 'the following sets of pickles execute at the same time:', + function (this: World, dataTable: DataTable) { + const expectedSets = dataTable.raw().map((row) => row[0]) + const actualSets = getSetsOfPicklesRunningAtTheSameTime( + this.lastRun.envelopes + ) + expect(actualSets).to.eql(expectedSets) + } +) diff --git a/src/cli/helpers_spec.ts b/src/cli/helpers_spec.ts index c5c151657..5e9e19af0 100644 --- a/src/cli/helpers_spec.ts +++ b/src/cli/helpers_spec.ts @@ -80,6 +80,7 @@ function testEmitSupportCodeMessages( parameterTypeRegistry: new ParameterTypeRegistry(), undefinedParameterTypes: [], World: null, + parallelCanAssign: () => true, }, supportCode ), diff --git a/src/index.ts b/src/index.ts index efa1e813e..5db98a80d 100644 --- a/src/index.ts +++ b/src/index.ts @@ -41,6 +41,7 @@ export const Given = methods.Given export const setDefaultTimeout = methods.setDefaultTimeout export const setDefinitionFunctionWrapper = methods.setDefinitionFunctionWrapper export const setWorldConstructor = methods.setWorldConstructor +export const setParallelCanAssign = methods.setParallelCanAssign export const Then = methods.Then export const When = methods.When export { diff --git a/src/runtime/parallel/README.md b/src/runtime/parallel/README.md index 08f2e084e..826c20a68 100644 --- a/src/runtime/parallel/README.md +++ b/src/runtime/parallel/README.md @@ -1,11 +1,32 @@ Parallelization is achieved by having multiple child processes running scenarios. +#### Customizable work assignment +Cucumber exposes customization of worker assignment via `setParallelCanAssign`. +This can be used to prevent specific test cases from running at the same time. + +The coordinator doesn't reorder work as it skips un-assignable tests. Also, it always +returns to the beginning of the unprocessed list when attempting to make assignments +to an idle worker. + +Custom work assignment prioritizes your definition of assignable work over efficiency. +The exception to this rule is if all remaining work is un-assignable, such that all +workers are idle. In this case Cucumber assigns the next test to the first worker +before continuing to utilize the handler to determine assignable work. Workers become +idle after checking all remaining test cases against the handler. Assignment is +attempted on all idle workers when a busy worker becomes `ready`. + #### Coordinator - load all features, generate test cases - broadcast `test-run-started` - create workers and for each worker - send an `initialize` command - - when a worker outputs a `ready` command, send it a `run` command with a test case. If there are no more test cases, send a `finalize` command + - when a worker outputs a `ready` command + - if there are no more test cases, send a `finalize` command + - identify the next processable test case (the next test by default) + - when there are no processable test cases all idle workers remain idle + - send a `run` command with the test case to an idle worker + - repeat if there are still idle workers + - if all workers become idle and there are more tests, process the next test case - when a worker outputs an `event` command, broadcast the event to the formatters, and on `test-case-finished` update the overall result diff --git a/src/runtime/parallel/coordinator.ts b/src/runtime/parallel/coordinator.ts index a4672dd7d..1ac910d4e 100644 --- a/src/runtime/parallel/coordinator.ts +++ b/src/runtime/parallel/coordinator.ts @@ -27,9 +27,22 @@ export interface INewCoordinatorOptions { numberOfWorkers: number } +const enum WorkerState { + 'idle', + 'closed', + 'running', + 'new', +} + interface IWorker { - closed: boolean + state: WorkerState process: ChildProcess + id: string +} + +interface IPicklePlacement { + index: number + pickle: messages.Pickle } export default class Coordinator implements IRuntime { @@ -38,17 +51,18 @@ export default class Coordinator implements IRuntime { private readonly eventDataCollector: EventDataCollector private readonly stopwatch: ITestRunStopwatch private onFinish: (success: boolean) => void - private nextPickleIdIndex: number private readonly options: IRuntimeOptions private readonly newId: IdGenerator.NewId private readonly pickleIds: string[] private assembledTestCases: IAssembledTestCases + private inProgressPickles: Record private workers: Record private readonly supportCodeLibrary: ISupportCodeLibrary private readonly supportCodePaths: string[] private readonly supportCodeRequiredModules: string[] private readonly numberOfWorkers: number private success: boolean + private idleInterventions: number constructor({ cwd, @@ -71,20 +85,23 @@ export default class Coordinator implements IRuntime { this.supportCodeLibrary = supportCodeLibrary this.supportCodePaths = supportCodePaths this.supportCodeRequiredModules = supportCodeRequiredModules - this.pickleIds = pickleIds + this.pickleIds = Array.from(pickleIds) this.numberOfWorkers = numberOfWorkers - this.nextPickleIdIndex = 0 this.success = true this.workers = {} + this.inProgressPickles = {} + this.idleInterventions = 0 } parseWorkerMessage(worker: IWorker, message: ICoordinatorReport): void { if (message.ready) { - this.giveWork(worker) + worker.state = WorkerState.idle + this.awakenWorkers(worker) } else if (doesHaveValue(message.jsonEnvelope)) { const envelope = messages.parseEnvelope(message.jsonEnvelope) this.eventBroadcaster.emit('envelope', envelope) if (doesHaveValue(envelope.testCaseFinished)) { + delete this.inProgressPickles[worker.id] this.parseTestCaseResult(envelope.testCaseFinished) } } else { @@ -94,6 +111,26 @@ export default class Coordinator implements IRuntime { } } + awakenWorkers(triggeringWorker: IWorker): void { + Object.values(this.workers).forEach((worker) => { + if (worker.state === WorkerState.idle) { + this.giveWork(worker) + } + return worker.state !== WorkerState.idle + }) + + let wip: Boolean = false + for (const p in this.inProgressPickles) { + wip = true + break + } + + if (!wip && this.pickleIds.length > 0) { + this.giveWork(triggeringWorker, true) + this.idleInterventions++ + } + } + startWorker(id: string, total: number): void { const workerProcess = fork(runWorkerPath, [], { cwd: this.cwd, @@ -105,13 +142,13 @@ export default class Coordinator implements IRuntime { }, stdio: ['inherit', 'inherit', 'inherit', 'ipc'], }) - const worker = { closed: false, process: workerProcess } + const worker = { state: WorkerState.new, process: workerProcess, id } this.workers[id] = worker worker.process.on('message', (message: ICoordinatorReport) => { this.parseWorkerMessage(worker, message) }) worker.process.on('close', (exitCode) => { - worker.closed = true + worker.state = WorkerState.closed this.onWorkerProcessClose(exitCode) }) const initializeCommand: IWorkerCommand = { @@ -143,7 +180,10 @@ export default class Coordinator implements IRuntime { if (!success) { this.success = false } - if (Object.values(this.workers).every((x) => x.closed)) { + + if ( + Object.values(this.workers).every((x) => x.state === WorkerState.closed) + ) { const envelope: messages.Envelope = { testRunFinished: { timestamp: this.stopwatch.timestamp(), @@ -184,23 +224,65 @@ export default class Coordinator implements IRuntime { supportCodeLibrary: this.supportCodeLibrary, }) return await new Promise((resolve) => { - for (let i = 0; i <= this.numberOfWorkers; i++) { + for (let i = 0; i < this.numberOfWorkers; i++) { this.startWorker(i.toString(), this.numberOfWorkers) } - this.onFinish = resolve + this.onFinish = (status) => { + if (this.idleInterventions > 0) { + console.warn( + `WARNING: All workers went idle ${this.idleInterventions} time(s). Consider revising handler passed to setParallelCanAssign.` + ) + } + + resolve(status) + } }) } - giveWork(worker: IWorker): void { - if (this.nextPickleIdIndex === this.pickleIds.length) { + nextPicklePlacement(): IPicklePlacement { + for (let index = 0; index < this.pickleIds.length; index++) { + const placement = this.placementAt(index) + if ( + this.supportCodeLibrary.parallelCanAssign( + placement.pickle, + Object.values(this.inProgressPickles) + ) + ) { + return placement + } + } + + return null + } + + placementAt(index: number): IPicklePlacement { + return { + index, + pickle: this.eventDataCollector.getPickle(this.pickleIds[index]), + } + } + + giveWork(worker: IWorker, force: boolean = false): void { + if (this.pickleIds.length < 1) { const finalizeCommand: IWorkerCommand = { finalize: true } + worker.state = WorkerState.running worker.process.send(finalizeCommand) return } - const pickleId = this.pickleIds[this.nextPickleIdIndex] - this.nextPickleIdIndex += 1 - const pickle = this.eventDataCollector.getPickle(pickleId) - const testCase = this.assembledTestCases[pickleId] + + const picklePlacement = force + ? this.placementAt(0) + : this.nextPicklePlacement() + + if (picklePlacement === null) { + return + } + + const { index: nextPickleIndex, pickle } = picklePlacement + + this.pickleIds.splice(nextPickleIndex, 1) + this.inProgressPickles[worker.id] = pickle + const testCase = this.assembledTestCases[pickle.id] const gherkinDocument = this.eventDataCollector.getGherkinDocument( pickle.uri ) @@ -216,6 +298,7 @@ export default class Coordinator implements IRuntime { gherkinDocument, }, } + worker.state = WorkerState.running worker.process.send(runCommand) } } diff --git a/src/support_code_library_builder/index.ts b/src/support_code_library_builder/index.ts index f0061f6a9..d297a9e48 100644 --- a/src/support_code_library_builder/index.ts +++ b/src/support_code_library_builder/index.ts @@ -27,6 +27,7 @@ import { ISupportCodeLibrary, TestCaseHookFunction, TestStepHookFunction, + ParallelAssignmentValidator, } from './types' import World from './world' import { ICanonicalSupportCodeIds } from '../runtime/parallel/command_types' @@ -90,6 +91,7 @@ export class SupportCodeLibraryBuilder { private parameterTypeRegistry: ParameterTypeRegistry private stepDefinitionConfigs: IStepDefinitionConfig[] private World: any + private parallelCanAssign: ParallelAssignmentValidator constructor() { const defineStep = this.defineStep.bind(this) @@ -124,6 +126,9 @@ export class SupportCodeLibraryBuilder { setWorldConstructor: (fn) => { this.World = fn }, + setParallelCanAssign: (fn: ParallelAssignmentValidator): void => { + this.parallelCanAssign = fn + }, Then: defineStep, When: defineStep, } @@ -414,6 +419,7 @@ export class SupportCodeLibraryBuilder { undefinedParameterTypes: stepDefinitionsResult.undefinedParameterTypes, stepDefinitions: stepDefinitionsResult.stepDefinitions, World: this.World, + parallelCanAssign: this.parallelCanAssign, } } @@ -430,6 +436,7 @@ export class SupportCodeLibraryBuilder { this.defaultTimeout = 5000 this.parameterTypeRegistry = new ParameterTypeRegistry() this.stepDefinitionConfigs = [] + this.parallelCanAssign = () => true this.World = World } } diff --git a/src/support_code_library_builder/parallel_can_assign_helpers.ts b/src/support_code_library_builder/parallel_can_assign_helpers.ts new file mode 100644 index 000000000..f697d2998 --- /dev/null +++ b/src/support_code_library_builder/parallel_can_assign_helpers.ts @@ -0,0 +1,19 @@ +import * as messages from '@cucumber/messages' +import { ParallelAssignmentValidator } from './types' + +function hasTag(pickle: messages.Pickle, tagName: string): boolean { + return pickle.tags.some((t) => t.name == tagName) +} + +export function atMostOnePicklePerTag( + tagNames: string[] +): ParallelAssignmentValidator { + return (inQuestion: messages.Pickle, inProgress: messages.Pickle[]) => { + return tagNames.every((tagName) => { + return ( + !hasTag(inQuestion, tagName) || + inProgress.every((p) => !hasTag(p, tagName)) + ) + }) + } +} diff --git a/src/support_code_library_builder/parallel_can_assign_helpers_spec.ts b/src/support_code_library_builder/parallel_can_assign_helpers_spec.ts new file mode 100644 index 000000000..f6d819b49 --- /dev/null +++ b/src/support_code_library_builder/parallel_can_assign_helpers_spec.ts @@ -0,0 +1,72 @@ +import { atMostOnePicklePerTag } from './parallel_can_assign_helpers' +import * as messages from '@cucumber/messages' +import { expect } from 'chai' + +function pickleWithTags(tagNames: string[]): messages.Pickle { + return { + id: 'test', + name: '', + uri: '', + steps: [], + language: null, + astNodeIds: [], + tags: tagNames.map((tagName) => ({ name: tagName, astNodeId: null })), + } +} + +describe('parallel can assign helpers', () => { + describe('atMostOnePicklePerTag()', () => { + const testCanAssignFn = atMostOnePicklePerTag(['@complex', '@simple']) + + it('returns true if no pickles in progress', () => { + // Arrange + const inQuestion = pickleWithTags(['@complex']) + const inProgress: messages.Pickle[] = [] + + // Act + const result = testCanAssignFn(inQuestion, inProgress) + + // Assert + expect(result).to.eql(true) + }) + + it('returns true if pickle in question does not any of the given tags', () => { + // Arrange + const inQuestion = pickleWithTags([]) + const inProgress: messages.Pickle[] = [ + pickleWithTags(['@complex']), + pickleWithTags(['@simple']), + ] + + // Act + const result = testCanAssignFn(inQuestion, inProgress) + + // Assert + expect(result).to.eql(true) + }) + + it('returns true if pickle in question has one of the given tags but no other pickles in progress do', () => { + // Arrange + const inQuestion = pickleWithTags(['@complex']) + const inProgress: messages.Pickle[] = [pickleWithTags(['@simple'])] + + // Act + const result = testCanAssignFn(inQuestion, inProgress) + + // Assert + expect(result).to.eql(true) + }) + + it('returns false if pickle in question has one of the given tags and a pickle in progress also has that tag', () => { + // Arrange + const inQuestion = pickleWithTags(['@complex']) + const inProgress: messages.Pickle[] = [pickleWithTags(['@complex'])] + + // Act + const result = testCanAssignFn(inQuestion, inProgress) + + // Assert + expect(result).to.eql(false) + }) + }) +}) diff --git a/src/support_code_library_builder/types.ts b/src/support_code_library_builder/types.ts index 9f24f0ccb..9caf8b99c 100644 --- a/src/support_code_library_builder/types.ts +++ b/src/support_code_library_builder/types.ts @@ -7,7 +7,10 @@ import { ParameterTypeRegistry } from '@cucumber/cucumber-expressions' import { IWorld } from './world' export type DefineStepPattern = string | RegExp - +export type ParallelAssignmentValidator = ( + pickle: messages.Pickle, + runningPickles: messages.Pickle[] +) => boolean export interface ITestCaseHookParameter { gherkinDocument: messages.GherkinDocument pickle: messages.Pickle @@ -79,6 +82,7 @@ export interface IDefineSupportCodeMethods { ) => void) setDefaultTimeout: (milliseconds: number) => void setDefinitionFunctionWrapper: (fn: Function) => void + setParallelCanAssign: (fn: ParallelAssignmentValidator) => void setWorldConstructor: (fn: any) => void After: ((code: TestCaseHookFunction) => void) & (( @@ -167,4 +171,5 @@ export interface ISupportCodeLibrary { readonly undefinedParameterTypes: messages.UndefinedParameterType[] readonly parameterTypeRegistry: ParameterTypeRegistry readonly World: any + readonly parallelCanAssign: ParallelAssignmentValidator }