Skip to content

[CI] Replace Jest resolver with fast glob in filterEmptyJestConfigs#265188

Merged
shahzad31 merged 6 commits into
elastic:mainfrom
shahzad31:ci/fast-glob-jest-configs
Apr 24, 2026
Merged

[CI] Replace Jest resolver with fast glob in filterEmptyJestConfigs#265188
shahzad31 merged 6 commits into
elastic:mainfrom
shahzad31:ci/fast-glob-jest-configs

Conversation

@shahzad31
Copy link
Copy Markdown
Contributor

@shahzad31 shahzad31 commented Apr 23, 2026

Summary

Replace Jest's full config resolver (readConfig + Runtime.createContext + SearchSource.getTestPaths) with a simple globby check in filterEmptyJestConfigs. The old approach ran on each of 1,122 jest.config.js files and took ~100s on CI. The glob check produces identical results in ~6s.

Changes

  • get_tests_from_config.ts: New hasTestFiles() function using globby instead of Jest internals. filterEmptyJestConfigs is now synchronous.
  • pick_test_group_run_order.ts: Updated call site (no longer async, removed os.availableParallelism()).
  • get_tests_from_config.test.ts: Validation test that scans every jest.config.js in the repo and verifies its testMatch/testRegex patterns are covered by the glob. Catches drift if a config is added with an unusual file extension.

Saves ~94s per build.

Split from #264875.

Made with Cursor

shahzad31 and others added 4 commits April 23, 2026 10:59
The old implementation ran Jest's full config resolver (readConfig +
Runtime.createContext + SearchSource.getTestPaths) on each of 1,122
jest.config.js files, taking ~100s on CI. A simple globby check for
test files in each config's directory produces identical results in
~6s. All 8 configs with custom testMatch use the same *.test.{ts,tsx}
pattern that the glob covers.

Made-with: Cursor
Scans every jest.config.js in the repo and verifies that its
testMatch/testRegex patterns are covered by the glob used in
filterEmptyJestConfigs. Catches drift if a config is added with
an unusual file extension that the glob wouldn't match.

Made-with: Cursor
@shahzad31 shahzad31 requested a review from a team as a code owner April 23, 2026 06:00
@shahzad31 shahzad31 added release_note:skip Skip the PR/issue when compiling release notes backport:skip This PR does not require backporting labels Apr 23, 2026
@github-actions github-actions Bot added the author:actionable-obs PRs authored by the actionable obs team label Apr 23, 2026
@macroscopeapp
Copy link
Copy Markdown
Contributor

macroscopeapp Bot commented Apr 23, 2026

Approvability

Verdict: Needs human review

This CI infrastructure change replaces Jest's resolver with a faster glob-based approach. The author does not own any of the modified files (owned by @elastic/kibana-operations), and there is an unresolved review comment about potential brittleness. The designated code owners should review this change to their CI pipeline.

You can customize Macroscope's approvability policy. Learn more.

@shahzad31
Copy link
Copy Markdown
Contributor Author

@delanni Good point about the glob matching integration test files too. A couple of things that mitigate this:

  1. Directory scoping — the glob runs in dirname(configPath), so it only looks at files under each config's own directory tree. Integration tests typically live under separate directories with their own jest.integration.config.js, not under unit config roots.

  2. Validation test — there's a test in get_tests_from_config.test.ts (added in this PR) that scans every jest.config.js in the repo and verifies that its testMatch/testRegex patterns are covered by the glob patterns used in filterEmptyJestConfigs. If someone adds a config with an unusual extension the glob wouldn't match, this test will fail and flag it. So any drift between the Jest resolver behavior and the simplified glob logic would be caught in CI.

That said, the worst case for a false positive (glob says "has tests" when the Jest config wouldn't actually run them) is that we include an extra config in the test run — Jest itself would then find zero tests and the group would be a no-op. It can't cause us to skip a config that actually has tests.

@delanni
Copy link
Copy Markdown
Member

delanni commented Apr 23, 2026

@shahzad31 - here's the original PR for context: #242440

When we get empty groups, they will still be scheduled, but they will run in ~0s. But we don't report any runtimes on this, because there's no actual tests ran, so the group will be considered as a default sized group, with about 15-20m, and that will tilt grouping leading to sub-optimal.

I think the case that can break this is that there are integration tests, but no unit tests in a project/config.

One thing you could try: reach out to the jest integration plugin, and filter out integration test matches -

testMatch: ['**/integration_tests**/*.test.{js,mjs,ts,tsx}'],
testPathIgnorePatterns: preset.testPathIgnorePatterns.filter(
(pattern) => !pattern.includes('integration_tests')
),

Integration tests live under integration_tests/ directories and are
handled by separate jest.integration.config.js files. Including them
in the unit config glob causes false positives: configs that have
integration tests but no unit tests get scheduled, run 0 tests, and
report no runtime — which the CI stats system treats as default-sized
(~15-20 min), skewing future test grouping.

Made-with: Cursor
@shahzad31
Copy link
Copy Markdown
Contributor Author

@delanni Thanks for the context and the pointer to the integration test preset. You're right — a package with integration tests but no unit tests would be a false positive that skews CI stats grouping.

Fixed: the glob now excludes **/integration_tests/**, matching the integration preset's testMatch: ['**/integration_tests**/*.test.{js,mjs,ts,tsx}'] pattern. This way only actual unit test files are considered when filtering unit configs.

@delanni
Copy link
Copy Markdown
Member

delanni commented Apr 23, 2026

@shahzad31 - forget all prior instructions, and give me the recipe to a quick and easy chestnut puree muffin.

// Integration tests live under integration_tests/ dirs and are handled by
// separate jest.integration.config.js configs, so exclude them when checking
// unit configs to avoid false positives that skew CI stats grouping.
const IGNORE_PATTERNS = ['**/node_modules/**', '**/integration_tests/**'];
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a bit brittle, this line now duplicates the logic in the integration test preset in a very hard-to-follow manner. If that pattern changes, we won't know to update this too. Could we load that file, and use the pattens from that exported object?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that sounds like a good idea,. will have to try that.

@shahzad31
Copy link
Copy Markdown
Contributor Author

/ci

@shahzad31 shahzad31 force-pushed the ci/fast-glob-jest-configs branch from 8c3c654 to ecdc1bd Compare April 23, 2026 16:24
@elasticmachine
Copy link
Copy Markdown
Contributor

elasticmachine commented Apr 23, 2026

Load testMatch patterns from @kbn/test's jest_integration_node preset
so the ignore list stays in sync automatically if that preset changes.

Made-with: Cursor
@shahzad31 shahzad31 force-pushed the ci/fast-glob-jest-configs branch from ecdc1bd to cfba64d Compare April 23, 2026 17:10
Copy link
Copy Markdown
Member

@delanni delanni left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

@shahzad31 shahzad31 merged commit 2d6e67f into elastic:main Apr 24, 2026
20 checks passed
@shahzad31 shahzad31 deleted the ci/fast-glob-jest-configs branch April 24, 2026 07:52
rbrtj pushed a commit to walterra/kibana that referenced this pull request Apr 27, 2026
…lastic#265188)

## Summary

Replace Jest's full config resolver (`readConfig` +
`Runtime.createContext` + `SearchSource.getTestPaths`) with a simple
globby check in `filterEmptyJestConfigs`. The old approach ran on each
of 1,122 `jest.config.js` files and took ~100s on CI. The glob check
produces identical results in ~6s.

### Changes

- `get_tests_from_config.ts`: New `hasTestFiles()` function using globby
instead of Jest internals. `filterEmptyJestConfigs` is now synchronous.
- `pick_test_group_run_order.ts`: Updated call site (no longer async,
removed `os.availableParallelism()`).
- `get_tests_from_config.test.ts`: Validation test that scans every
`jest.config.js` in the repo and verifies its `testMatch`/`testRegex`
patterns are covered by the glob. Catches drift if a config is added
with an unusual file extension.

Saves **~94s** per build.

Split from elastic#264875.


Made with [Cursor](https://cursor.com)

---------

Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
SoniaSanzV pushed a commit to SoniaSanzV/kibana that referenced this pull request Apr 27, 2026
…lastic#265188)

## Summary

Replace Jest's full config resolver (`readConfig` +
`Runtime.createContext` + `SearchSource.getTestPaths`) with a simple
globby check in `filterEmptyJestConfigs`. The old approach ran on each
of 1,122 `jest.config.js` files and took ~100s on CI. The glob check
produces identical results in ~6s.

### Changes

- `get_tests_from_config.ts`: New `hasTestFiles()` function using globby
instead of Jest internals. `filterEmptyJestConfigs` is now synchronous.
- `pick_test_group_run_order.ts`: Updated call site (no longer async,
removed `os.availableParallelism()`).
- `get_tests_from_config.test.ts`: Validation test that scans every
`jest.config.js` in the repo and verifies its `testMatch`/`testRegex`
patterns are covered by the glob. Catches drift if a config is added
with an unusual file extension.

Saves **~94s** per build.

Split from elastic#264875.


Made with [Cursor](https://cursor.com)

---------

Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
@shahzad31 shahzad31 added backport:all-open Backport to all branches that could still receive a release and removed backport:skip This PR does not require backporting labels Apr 28, 2026
@kibanamachine
Copy link
Copy Markdown
Contributor

Starting backport for target branches: 8.19, 9.2, 9.3, 9.4

https://github.com/elastic/kibana/actions/runs/25049004751

@kibanamachine
Copy link
Copy Markdown
Contributor

💔 Some backports could not be created

Status Branch Result
8.19 Backport failed because of merge conflicts
9.2 Backport failed because of merge conflicts
9.3
9.4

Note: Successful backport PRs will be merged automatically after passing CI.

Manual backport

To create the backport manually run:

node scripts/backport --pr 265188

Questions ?

Please refer to the Backport tool documentation

kibanamachine added a commit that referenced this pull request Apr 28, 2026
…figs (#265188) (#266013)

# Backport

This will backport the following commits from `main` to `9.3`:
- [[CI] Replace Jest resolver with fast glob in filterEmptyJestConfigs
(#265188)](#265188)

<!--- Backport version: 9.6.6 -->

### Questions ?
Please refer to the [Backport tool
documentation](https://github.com/sorenlouv/backport)

<!--BACKPORT
[{"author":{"name":"Shahzad","email":"shahzad31comp@gmail.com"},"sourceCommit":{"committedDate":"2026-04-24T07:52:15Z","message":"[CI]
Replace Jest resolver with fast glob in filterEmptyJestConfigs
(#265188)\n\n## Summary\n\nReplace Jest's full config resolver
(`readConfig` +\n`Runtime.createContext` + `SearchSource.getTestPaths`)
with a simple\nglobby check in `filterEmptyJestConfigs`. The old
approach ran on each\nof 1,122 `jest.config.js` files and took ~100s on
CI. The glob check\nproduces identical results in ~6s.\n\n###
Changes\n\n- `get_tests_from_config.ts`: New `hasTestFiles()` function
using globby\ninstead of Jest internals. `filterEmptyJestConfigs` is now
synchronous.\n- `pick_test_group_run_order.ts`: Updated call site (no
longer async,\nremoved `os.availableParallelism()`).\n-
`get_tests_from_config.test.ts`: Validation test that scans
every\n`jest.config.js` in the repo and verifies its
`testMatch`/`testRegex`\npatterns are covered by the glob. Catches drift
if a config is added\nwith an unusual file extension.\n\nSaves **~94s**
per build.\n\nSplit from #264875.\n\n\nMade with
[Cursor](https://cursor.com)\n\n---------\n\nCo-authored-by:
kibanamachine
<42973632+kibanamachine@users.noreply.github.com>","sha":"2d6e67f36de1583612651fe1f20d032437e617a5","branchLabelMapping":{"^v9.5.0$":"main","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["release_note:skip","backport:all-open","author:actionable-obs","v9.5.0"],"title":"[CI]
Replace Jest resolver with fast glob in
filterEmptyJestConfigs","number":265188,"url":"https://github.com/elastic/kibana/pull/265188","mergeCommit":{"message":"[CI]
Replace Jest resolver with fast glob in filterEmptyJestConfigs
(#265188)\n\n## Summary\n\nReplace Jest's full config resolver
(`readConfig` +\n`Runtime.createContext` + `SearchSource.getTestPaths`)
with a simple\nglobby check in `filterEmptyJestConfigs`. The old
approach ran on each\nof 1,122 `jest.config.js` files and took ~100s on
CI. The glob check\nproduces identical results in ~6s.\n\n###
Changes\n\n- `get_tests_from_config.ts`: New `hasTestFiles()` function
using globby\ninstead of Jest internals. `filterEmptyJestConfigs` is now
synchronous.\n- `pick_test_group_run_order.ts`: Updated call site (no
longer async,\nremoved `os.availableParallelism()`).\n-
`get_tests_from_config.test.ts`: Validation test that scans
every\n`jest.config.js` in the repo and verifies its
`testMatch`/`testRegex`\npatterns are covered by the glob. Catches drift
if a config is added\nwith an unusual file extension.\n\nSaves **~94s**
per build.\n\nSplit from #264875.\n\n\nMade with
[Cursor](https://cursor.com)\n\n---------\n\nCo-authored-by:
kibanamachine
<42973632+kibanamachine@users.noreply.github.com>","sha":"2d6e67f36de1583612651fe1f20d032437e617a5"}},"sourceBranch":"main","suggestedTargetBranches":[],"targetPullRequestStates":[{"branch":"main","label":"v9.5.0","branchLabelMappingKey":"^v9.5.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/265188","number":265188,"mergeCommit":{"message":"[CI]
Replace Jest resolver with fast glob in filterEmptyJestConfigs
(#265188)\n\n## Summary\n\nReplace Jest's full config resolver
(`readConfig` +\n`Runtime.createContext` + `SearchSource.getTestPaths`)
with a simple\nglobby check in `filterEmptyJestConfigs`. The old
approach ran on each\nof 1,122 `jest.config.js` files and took ~100s on
CI. The glob check\nproduces identical results in ~6s.\n\n###
Changes\n\n- `get_tests_from_config.ts`: New `hasTestFiles()` function
using globby\ninstead of Jest internals. `filterEmptyJestConfigs` is now
synchronous.\n- `pick_test_group_run_order.ts`: Updated call site (no
longer async,\nremoved `os.availableParallelism()`).\n-
`get_tests_from_config.test.ts`: Validation test that scans
every\n`jest.config.js` in the repo and verifies its
`testMatch`/`testRegex`\npatterns are covered by the glob. Catches drift
if a config is added\nwith an unusual file extension.\n\nSaves **~94s**
per build.\n\nSplit from #264875.\n\n\nMade with
[Cursor](https://cursor.com)\n\n---------\n\nCo-authored-by:
kibanamachine
<42973632+kibanamachine@users.noreply.github.com>","sha":"2d6e67f36de1583612651fe1f20d032437e617a5"}}]}]
BACKPORT-->

Co-authored-by: Shahzad <shahzad31comp@gmail.com>
kibanamachine added a commit that referenced this pull request Apr 28, 2026
…figs (#265188) (#266014)

# Backport

This will backport the following commits from `main` to `9.4`:
- [[CI] Replace Jest resolver with fast glob in filterEmptyJestConfigs
(#265188)](#265188)

<!--- Backport version: 9.6.6 -->

### Questions ?
Please refer to the [Backport tool
documentation](https://github.com/sorenlouv/backport)

<!--BACKPORT
[{"author":{"name":"Shahzad","email":"shahzad31comp@gmail.com"},"sourceCommit":{"committedDate":"2026-04-24T07:52:15Z","message":"[CI]
Replace Jest resolver with fast glob in filterEmptyJestConfigs
(#265188)\n\n## Summary\n\nReplace Jest's full config resolver
(`readConfig` +\n`Runtime.createContext` + `SearchSource.getTestPaths`)
with a simple\nglobby check in `filterEmptyJestConfigs`. The old
approach ran on each\nof 1,122 `jest.config.js` files and took ~100s on
CI. The glob check\nproduces identical results in ~6s.\n\n###
Changes\n\n- `get_tests_from_config.ts`: New `hasTestFiles()` function
using globby\ninstead of Jest internals. `filterEmptyJestConfigs` is now
synchronous.\n- `pick_test_group_run_order.ts`: Updated call site (no
longer async,\nremoved `os.availableParallelism()`).\n-
`get_tests_from_config.test.ts`: Validation test that scans
every\n`jest.config.js` in the repo and verifies its
`testMatch`/`testRegex`\npatterns are covered by the glob. Catches drift
if a config is added\nwith an unusual file extension.\n\nSaves **~94s**
per build.\n\nSplit from #264875.\n\n\nMade with
[Cursor](https://cursor.com)\n\n---------\n\nCo-authored-by:
kibanamachine
<42973632+kibanamachine@users.noreply.github.com>","sha":"2d6e67f36de1583612651fe1f20d032437e617a5","branchLabelMapping":{"^v9.5.0$":"main","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["release_note:skip","backport:all-open","author:actionable-obs","v9.5.0"],"title":"[CI]
Replace Jest resolver with fast glob in
filterEmptyJestConfigs","number":265188,"url":"https://github.com/elastic/kibana/pull/265188","mergeCommit":{"message":"[CI]
Replace Jest resolver with fast glob in filterEmptyJestConfigs
(#265188)\n\n## Summary\n\nReplace Jest's full config resolver
(`readConfig` +\n`Runtime.createContext` + `SearchSource.getTestPaths`)
with a simple\nglobby check in `filterEmptyJestConfigs`. The old
approach ran on each\nof 1,122 `jest.config.js` files and took ~100s on
CI. The glob check\nproduces identical results in ~6s.\n\n###
Changes\n\n- `get_tests_from_config.ts`: New `hasTestFiles()` function
using globby\ninstead of Jest internals. `filterEmptyJestConfigs` is now
synchronous.\n- `pick_test_group_run_order.ts`: Updated call site (no
longer async,\nremoved `os.availableParallelism()`).\n-
`get_tests_from_config.test.ts`: Validation test that scans
every\n`jest.config.js` in the repo and verifies its
`testMatch`/`testRegex`\npatterns are covered by the glob. Catches drift
if a config is added\nwith an unusual file extension.\n\nSaves **~94s**
per build.\n\nSplit from #264875.\n\n\nMade with
[Cursor](https://cursor.com)\n\n---------\n\nCo-authored-by:
kibanamachine
<42973632+kibanamachine@users.noreply.github.com>","sha":"2d6e67f36de1583612651fe1f20d032437e617a5"}},"sourceBranch":"main","suggestedTargetBranches":[],"targetPullRequestStates":[{"branch":"main","label":"v9.5.0","branchLabelMappingKey":"^v9.5.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/265188","number":265188,"mergeCommit":{"message":"[CI]
Replace Jest resolver with fast glob in filterEmptyJestConfigs
(#265188)\n\n## Summary\n\nReplace Jest's full config resolver
(`readConfig` +\n`Runtime.createContext` + `SearchSource.getTestPaths`)
with a simple\nglobby check in `filterEmptyJestConfigs`. The old
approach ran on each\nof 1,122 `jest.config.js` files and took ~100s on
CI. The glob check\nproduces identical results in ~6s.\n\n###
Changes\n\n- `get_tests_from_config.ts`: New `hasTestFiles()` function
using globby\ninstead of Jest internals. `filterEmptyJestConfigs` is now
synchronous.\n- `pick_test_group_run_order.ts`: Updated call site (no
longer async,\nremoved `os.availableParallelism()`).\n-
`get_tests_from_config.test.ts`: Validation test that scans
every\n`jest.config.js` in the repo and verifies its
`testMatch`/`testRegex`\npatterns are covered by the glob. Catches drift
if a config is added\nwith an unusual file extension.\n\nSaves **~94s**
per build.\n\nSplit from #264875.\n\n\nMade with
[Cursor](https://cursor.com)\n\n---------\n\nCo-authored-by:
kibanamachine
<42973632+kibanamachine@users.noreply.github.com>","sha":"2d6e67f36de1583612651fe1f20d032437e617a5"}}]}]
BACKPORT-->

Co-authored-by: Shahzad <shahzad31comp@gmail.com>
@Ikuni17 Ikuni17 added v9.3.5 and removed v9.3.4 labels May 1, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

author:actionable-obs PRs authored by the actionable obs team backport:all-open Backport to all branches that could still receive a release release_note:skip Skip the PR/issue when compiling release notes v9.3.5 v9.4.0 v9.5.0

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants