turbo_tests2 3.0.0 → 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 914b16e54da50fc47c640b1d19c21a3065cdb6b4e81f39efeda2985a7117ad6c
4
- data.tar.gz: 83ec0433e663c137885b0ca442d6a04b378907f16f847b196946d590a204c513
3
+ metadata.gz: d311bffe032ad5025cceb6e487460d7e1c0d1e87cc07134eafb4f41bdc63d515
4
+ data.tar.gz: 0a07a421cad5fadb52d3c417fd0626f80159e4757d8a393fe3ac4912c343dba5
5
5
  SHA512:
6
- metadata.gz: 5e72db711c9e2a8c89428b8da820405eed60bcc645f0f566622179d2bce6e3541afb5f048955062c5b978e7f26be269b8dfe26a9a899137e14972ef25bb1fdf2
7
- data.tar.gz: 7e40c4aaad85157c5741b7883baae2200e5d5d3c73e4324b8b2e21146c98923d4dc933cb4b981e7b95340ec7b50220e454ce1636220176b7969981f1c69bcd8f
6
+ metadata.gz: f9745db1b03fd8ef015807cab9518079449ffa6d033d898141eec7f815edbd1dcb1a95f9bf7fd361d6e429d8573fa0668ef7b4ca295c2ad116d72bbb63d35c53
7
+ data.tar.gz: fe28b347bae32babcdfc80fcf1523b43f54c17dd3726eaeae5e6c4101127685a967e80ef4789c13b47bb16e682282ab5318f9a89b6cce0a16984235128143450
checksums.yaml.gz.sig CHANGED
Binary file
data/CHANGELOG.md CHANGED
@@ -30,6 +30,57 @@ Please file a bug if you notice a violation of semantic versioning.
30
30
 
31
31
  ### Security
32
32
 
33
+ ## [3.1.0] - 2026-05-28
34
+
35
+ - TAG: [v3.1.0][3.1.0t]
36
+ - COVERAGE: 95.01% -- 609/641 lines in 16 files
37
+ - BRANCH COVERAGE: 77.54% -- 107/138 branches in 16 files
38
+ - 36.78% documented
39
+
40
+ ### Added
41
+
42
+ - `-w` / `--workers` aliases for `-n`, matching the worker-count terminology used by other
43
+ parallel test runners.
44
+ - `turbo_tests2 fan`, a generic worker fan-out command that runs an arbitrary command once per
45
+ worker with `TEST_ENV_NUMBER` and `PARALLEL_TEST_GROUPS` set.
46
+ - `--example-status-log FILE`, which converts RSpec example-status persistence data into a
47
+ `parallel_tests`-compatible runtime log so grouping can use example-level timing history.
48
+
49
+ ### Changed
50
+
51
+ - Worker subprocess JSON now forwards RSpec deprecation notifications to the parent reporter.
52
+ - Worker subprocess JSON now forwards RSpec profile output to the parent reporter.
53
+ - Fail-fast runs now report spec groups that were stopped before execution.
54
+ - Interrupted runs now report spec groups that had not finished before shutdown.
55
+
56
+ ### Fixed
57
+
58
+ - Reconstructed failure backtraces now filter internal `turbo_tests2` frames.
59
+ - Coverage was refreshed by adding focused specs for the new CLI, reporting, formatter, and
60
+ grouping behaviors.
61
+
62
+ - RSpec deprecation notification reconstruction now uses the public `from_hash` API so CI passes
63
+ with RSpec versions where `.new` is private.
64
+ - The shim command result object no longer depends on `Struct#keyword_init`, restoring Ruby 2.4
65
+ compatibility.
66
+ - Backtrace output specs now accept JRuby 9.2's legacy `block in <main>` frame wording.
67
+ - GitHub Actions test jobs now force `kettle-test` to use its direct RSpec runner so coverage
68
+ aggregation remains stable while testing `turbo_tests2` itself.
69
+ - GitHub Actions appraisal jobs now pass explicit parent-directory RSpec paths so direct RSpec
70
+ runs execute the real suite instead of finding zero examples from `gemfiles/`.
71
+ - Spawned-process coverage setup now locates `.simplecov_spawn.rb` from the working directory
72
+ instead of `Bundler.root`, so appraisal gemfiles do not point it at `gemfiles/`.
73
+ - The coverage workflow now uses the same hard coverage thresholds as local development.
74
+ - The dedicated coverage workflow now runs RSpec directly so coverage artifacts are written under
75
+ the repository root for upload steps.
76
+ - Removed the advanced CodeQL workflow because GitHub CodeQL default setup is enabled and rejects
77
+ SARIF uploads from advanced configurations.
78
+
79
+ ### Security
80
+
81
+ - Refreshed pinned GitHub Action SHAs.
82
+ - Added checksums for the `v3.0.0` release artifacts.
83
+
33
84
  ## [3.0.0] - 2026-05-22
34
85
 
35
86
  - TAG: [v3.0.0][3.0.0t]
@@ -41,6 +92,8 @@ Please file a bug if you notice a violation of semantic versioning.
41
92
 
42
93
  - Initial release
43
94
 
44
- [Unreleased]: https://github.com/galtzo-floss/turbo_tests2/compare/v3.0.0...HEAD
95
+ [Unreleased]: https://github.com/galtzo-floss/turbo_tests2/compare/v3.1.0...HEAD
96
+ [3.1.0]: https://github.com/galtzo-floss/turbo_tests2/compare/v3.0.0...v3.1.0
97
+ [3.1.0t]: https://github.com/galtzo-floss/turbo_tests2/releases/tag/v3.1.0
45
98
  [3.0.0]: https://github.com/galtzo-floss/turbo_tests2/compare/7d4064e5b8acc2f53929fccf7be3eb63f8a9f140...v3.0.0
46
99
  [3.0.0t]: https://github.com/galtzo-floss/turbo_tests2/releases/tag/v3.0.0
data/CONTRIBUTING.md CHANGED
@@ -50,6 +50,22 @@ There are many Rake tasks available as well. You can see them by running:
50
50
  bin/rake -T
51
51
  ```
52
52
 
53
+ ## Code quality checks
54
+
55
+ Run the Reek task when you want a smell check that fails on current findings:
56
+
57
+ ```shell
58
+ bin/rake reek
59
+ ```
60
+
61
+ Refresh the checked-in `REEK` backlog through the rake task, not by redirecting
62
+ the raw `reek` executable output. The rake task uses the project bundle and
63
+ avoids stale generated binstubs shadowing the Reek gem executable:
64
+
65
+ ```shell
66
+ bin/rake reek:update
67
+ ```
68
+
53
69
  ## Environment Variables for Local Development
54
70
 
55
71
  Below are the primary environment variables recognized by stone_checksums (and its integrated tools). Unless otherwise noted, set boolean values to the string "true" to enable.
@@ -91,6 +107,11 @@ For a quick starting point, this repository’s `mise.toml` defines the shared d
91
107
  ## Appraisals
92
108
 
93
109
  From time to time the [appraisal2][🚎appraisal2] gemfiles in `gemfiles/` will need to be updated.
110
+ Generated appraisal and CI workflow floors are controlled by `ruby.test_minimum`
111
+ in `.kettle-jem.yml`; this project was templated with `ruby.test_minimum: 2.4`.
112
+ That value describes the lowest Ruby version expected to run the test/development
113
+ toolchain, and it may be higher than the gemspec runtime floor.
114
+
94
115
  They are created and updated with the commands:
95
116
 
96
117
  ```console
data/LICENSE.md ADDED
@@ -0,0 +1,22 @@
1
+ # License
2
+
3
+ This project is made available under the following license.
4
+ Choose the option that best fits your use case:
5
+
6
+ - [MIT](MIT.md)
7
+
8
+ ## Copyright Notice
9
+
10
+ Copyright (c) 2020-2023 Illia
11
+ Copyright (c) 2020 Ilya Zub
12
+ Copyright (c) 2021 AMHOL
13
+ Copyright (c) 2021 Serge Bedzhyk
14
+ Copyright (c) 2023 Bo Anderson
15
+ Copyright (c) 2023 Dmitiry Zub☀️
16
+ Copyright (c) 2023-2024 Illia
17
+ Copyright (c) 2023 mrudzki
18
+ Copyright (c) 2023-2024 Sebastien Savater
19
+ Copyright (c) 2024 David Rodriguez
20
+ Copyright (c) 2024 Hiroshi SHIBATA
21
+ Copyright (c) 2025 Gareth Jones
22
+ Copyright (c) 2025-2026 Peter H. Boling
data/README.md CHANGED
@@ -9,7 +9,7 @@
9
9
 
10
10
  # 🚀 TurboTests
11
11
 
12
- [![Version][👽versioni]][👽version] [![GitHub tag (latest SemVer)][⛳️tag-img]][⛳️tag] [![License: MIT][📄license-img]][📄license] [![Downloads Rank][👽dl-ranki]][👽dl-rank] [![CodeCov Test Coverage][🏀codecovi]][🏀codecov] [![Coveralls Test Coverage][🏀coveralls-img]][🏀coveralls] [![QLTY Test Coverage][🏀qlty-covi]][🏀qlty-cov] [![QLTY Maintainability][🏀qlty-mnti]][🏀qlty-mnt] [![CI Heads][🚎3-hd-wfi]][🚎3-hd-wf] [![CI Runtime Dependencies @ HEAD][🚎12-crh-wfi]][🚎12-crh-wf] [![CI Current][🚎11-c-wfi]][🚎11-c-wf] [![CI Truffle Ruby][🚎9-t-wfi]][🚎9-t-wf] [![CI JRuby][🚎10-j-wfi]][🚎10-j-wf] [![Deps Locked][🚎13-🔒️-wfi]][🚎13-🔒️-wf] [![Deps Unlocked][🚎14-🔓️-wfi]][🚎14-🔓️-wf] [![CI Test Coverage][🚎2-cov-wfi]][🚎2-cov-wf] [![CI Style][🚎5-st-wfi]][🚎5-st-wf] [![CodeQL][🖐codeQL-img]][🖐codeQL] [![Apache SkyWalking Eyes License Compatibility Check][🚎15-🪪-wfi]][🚎15-🪪-wf]
12
+ [![Version][👽versioni]][👽version] [![GitHub tag (latest SemVer)][⛳️tag-img]][⛳️tag] [![License: MIT][📄license-img]][📄license] [![Downloads Rank][👽dl-ranki]][👽dl-rank] [![CodeCov Test Coverage][🏀codecovi]][🏀codecov] [![Coveralls Test Coverage][🏀coveralls-img]][🏀coveralls] [![QLTY Test Coverage][🏀qlty-covi]][🏀qlty-cov] [![QLTY Maintainability][🏀qlty-mnti]][🏀qlty-mnt] [![CI Heads][🚎3-hd-wfi]][🚎3-hd-wf] [![CI Runtime Dependencies @ HEAD][🚎12-crh-wfi]][🚎12-crh-wf] [![CI Current][🚎11-c-wfi]][🚎11-c-wf] [![CI Truffle Ruby][🚎9-t-wfi]][🚎9-t-wf] [![CI JRuby][🚎10-j-wfi]][🚎10-j-wf] [![Deps Locked][🚎13-🔒️-wfi]][🚎13-🔒️-wf] [![Deps Unlocked][🚎14-🔓️-wfi]][🚎14-🔓️-wf] [![CI Test Coverage][🚎2-cov-wfi]][🚎2-cov-wf] [![CI Style][🚎5-st-wfi]][🚎5-st-wf] [![Apache SkyWalking Eyes License Compatibility Check][🚎15-🪪-wfi]][🚎15-🪪-wf]
13
13
 
14
14
  `if ci_badges.map(&:color).detect { it != "green"}` ☝️ [let me know][🖼️galtzo-floss], as I may have missed the [discord notification][🖼️galtzo-floss].
15
15
 
@@ -89,24 +89,16 @@ Finished in 2 minute 25.15 seconds (files took 0 seconds to load)
89
89
  ### Compatibility
90
90
 
91
91
  Compatible with MRI Ruby 2.4.0+, and concordant releases of JRuby, and TruffleRuby.
92
+ CI workflows and Appraisals are generated for MRI Ruby 2.4+.
93
+ This test floor is configured by `ruby.test_minimum` in `.kettle-jem.yml` and
94
+ may be higher than the gem's runtime compatibility floor when legacy Rubies are
95
+ not practical for the current toolchain.
92
96
 
93
97
  | 🚚 _Amazing_ test matrix was brought to you by | 🔎 appraisal2 🔎 and the color 💚 green 💚 |
94
98
  |------------------------------------------------|--------------------------------------------------------|
95
99
  | 👟 Check it out! | ✨ [github.com/appraisal-rb/appraisal2][💎appraisal2] ✨ |
96
100
 
97
- <details markdown="1">
98
- <summary>StructuredMerge package family</summary>
99
-
100
- This gem is part of the StructuredMerge Ruby package family. The implementation inventory, layering model, and backend notes live in the [root package-family guide][sm-family-guide]. Shared behavior is defined by the [StructuredMerge fixtures][sm-family-fixtures] and implemented by the [Go][sm-family-go], [Ruby][sm-family-ruby], [Rust][sm-family-rust], and [TypeScript][sm-family-typescript] repositories.
101
101
 
102
- </details>
103
-
104
- [sm-family-guide]: https://github.com/structuredmerge/structuredmerge-ruby#package-family
105
- [sm-family-fixtures]: https://github.com/structuredmerge/structuredmerge-fixtures
106
- [sm-family-go]: https://github.com/structuredmerge/structuredmerge-go
107
- [sm-family-ruby]: https://github.com/structuredmerge/structuredmerge-ruby
108
- [sm-family-rust]: https://github.com/structuredmerge/structuredmerge-rust
109
- [sm-family-typescript]: https://github.com/structuredmerge/structuredmerge-typescript
110
102
 
111
103
  ### Federated DVCS
112
104
 
@@ -406,7 +398,7 @@ For most applications, prefer the [Pessimistic Version Constraint][📌pvc] with
406
398
  For example:
407
399
 
408
400
  ```ruby
409
- spec.add_dependency("turbo_tests2", "~> 0.0")
401
+ spec.add_dependency("turbo_tests2", "~> 3.0")
410
402
  ```
411
403
 
412
404
  <details markdown="1">
@@ -436,7 +428,7 @@ See [LICENSE.md][📄license] for the official copyright notice.
436
428
  <details markdown="1">
437
429
  <summary>Copyright holders</summary>
438
430
 
439
- - Copyright (c) 2020-2023, 2025 Illia
431
+ - Copyright (c) 2020-2023 Illia
440
432
  - Copyright (c) 2020 Ilya Zub
441
433
  - Copyright (c) 2021 AMHOL
442
434
  - Copyright (c) 2021 Serge Bedzhyk
@@ -573,8 +565,6 @@ Thanks for RTFM. ☺️
573
565
  [🏀codecovi]: https://codecov.io/gh/galtzo-floss/turbo_tests2/graph/badge.svg
574
566
  [🏀coveralls]: https://coveralls.io/github/galtzo-floss/turbo_tests2?branch=main
575
567
  [🏀coveralls-img]: https://coveralls.io/repos/github/galtzo-floss/turbo_tests2/badge.svg?branch=main
576
- [🖐codeQL]: https://github.com/galtzo-floss/turbo_tests2/security/code-scanning
577
- [🖐codeQL-img]: https://github.com/galtzo-floss/turbo_tests2/actions/workflows/codeql-analysis.yml/badge.svg
578
568
  [🚎ruby-2.4-wf]: https://github.com/galtzo-floss/turbo_tests2/actions/workflows/ruby-2.4.yml
579
569
  [🚎ruby-2.5-wf]: https://github.com/galtzo-floss/turbo_tests2/actions/workflows/ruby-2.5.yml
580
570
  [🚎ruby-2.6-wf]: https://github.com/galtzo-floss/turbo_tests2/actions/workflows/ruby-2.6.yml
@@ -643,7 +633,7 @@ Thanks for RTFM. ☺️
643
633
  [🤝cb-pulls]: https://codeberg.org/galtzo-floss/turbo_tests2/pulls
644
634
  [🤝cb-donate]: https://donate.codeberg.org/
645
635
  [🤝contributing]: https://github.com/galtzo-floss/turbo_tests2/blob/main/CONTRIBUTING.md
646
- [🏀codecov-g]: https://codecov.io/gh/galtzo-floss/turbo_tests2/graphs/tree.svg
636
+ [🏀codecov-g]: https://codecov.io/gh/galtzo-floss/turbo_tests2/graph/badge.svg
647
637
  [🖐contrib-rocks]: https://contrib.rocks
648
638
  [🖐contributors]: https://github.com/galtzo-floss/turbo_tests2/graphs/contributors
649
639
  [🖐contributors-img]: https://contrib.rocks/image?repo=galtzo-floss/turbo_tests2
@@ -661,7 +651,7 @@ Thanks for RTFM. ☺️
661
651
  [📌gitmoji]: https://gitmoji.dev
662
652
  [📌gitmoji-img]: https://img.shields.io/badge/gitmoji_commits-%20%F0%9F%98%9C%20%F0%9F%98%8D-34495e.svg?style=flat-square
663
653
  [🧮kloc]: https://www.youtube.com/watch?v=dQw4w9WgXcQ
664
- [🧮kloc-img]: https://img.shields.io/badge/KLOC-0.555-FFDD67.svg?style=for-the-badge&logo=YouTube&logoColor=blue
654
+ [🧮kloc-img]: https://img.shields.io/badge/KLOC-0.641-FFDD67.svg?style=for-the-badge&logo=YouTube&logoColor=blue
665
655
  [🔐security]: https://github.com/galtzo-floss/turbo_tests2/blob/main/SECURITY.md
666
656
  [🔐security-img]: https://img.shields.io/badge/security-policy-259D6C.svg?style=flat
667
657
  [📄copyright-notice-explainer]: https://opensource.stackexchange.com/questions/5778/why-do-licenses-such-as-the-mit-license-specify-a-single-year
@@ -670,6 +660,7 @@ Thanks for RTFM. ☺️
670
660
  [📄license-img]: https://img.shields.io/badge/License-MIT-259D6C.svg
671
661
  [📄license-compat]: https://www.apache.org/legal/resolved.html#category-a
672
662
  [📄license-compat-img]: https://img.shields.io/badge/Apache_Compatible:_Category_A-✓-259D6C.svg?style=flat&logo=Apache
663
+
673
664
  [📄ilo-declaration]: https://www.ilo.org/declaration/lang--en/index.htm
674
665
  [📄ilo-declaration-img]: https://img.shields.io/badge/ILO_Fundamental_Principles-✓-259D6C.svg?style=flat
675
666
  [🚎yard-current]: http://rubydoc.info/gems/turbo_tests2
@@ -688,7 +679,7 @@ Thanks for RTFM. ☺️
688
679
  | Package | turbo_tests2 |
689
680
  | Description | 🚀 `turbo_tests2` is a drop-in replacement for `serpapi/turbo_tests` and `grosser/parallel_tests` with incremental summarized output. Source code of `turbo_test2` gem is based on Discourse and Rubygems work in this area (see README file of the source repository). |
690
681
  | Homepage | https://github.com/galtzo-floss/turbo_tests2 |
691
- | Source | https://github.com/galtzo-floss/turbo_tests2 |
682
+ | Source | https://github.com/galtzo-floss/turbo_tests2/tree/v3.0.0 |
692
683
  | License | `MIT` |
693
684
  | Funding | https://github.com/sponsors/pboling, https://issuehunt.io/u/pboling, https://ko-fi.com/pboling, https://liberapay.com/pboling/donate, https://opencollective.com/galtzo-floss, https://patreon.com/galtzo, https://polar.sh/pboling, https://thanks.dev/u/gh/pboling, https://tidelift.com/funding/github/rubygems/turbo_tests2, https://www.buymeacoffee.com/pboling |
694
685
  <!-- kettle-jem:metadata:end -->
@@ -10,12 +10,14 @@ module TurboTests
10
10
 
11
11
  def run
12
12
  handle_shim_command if shim_command?
13
+ handle_fan_command if fan_command?
13
14
 
14
15
  requires = []
15
16
  formatters = []
16
17
  tags = []
17
18
  count = nil
18
19
  runtime_log = nil
20
+ example_status_log = nil
19
21
  verbose = false
20
22
  fail_fast = nil
21
23
  seed = nil
@@ -39,7 +41,9 @@ module TurboTests
39
41
  Options:
40
42
  BANNER
41
43
 
42
- opts.on("-n [PROCESSES]", Integer, "How many processes to use, default: available CPUs") { |n| count = n }
44
+ opts.on("-n [PROCESSES]", "-w [PROCESSES]", "--workers [PROCESSES]", Integer, "How many processes to use, default: available CPUs") do |n|
45
+ count = n
46
+ end
43
47
 
44
48
  opts.on("-r", "--require PATH", "Require a file.") do |filename|
45
49
  requires << filename
@@ -74,6 +78,10 @@ module TurboTests
74
78
  runtime_log = filename
75
79
  end
76
80
 
81
+ opts.on("--example-status-log FILE", "Use RSpec example status persistence timings for grouping") do |filename|
82
+ example_status_log = filename
83
+ end
84
+
77
85
  opts.on("-v", "--verbose", "More output") do
78
86
  verbose = true
79
87
  end
@@ -133,6 +141,7 @@ module TurboTests
133
141
  tags: tags,
134
142
  files: files,
135
143
  runtime_log: runtime_log,
144
+ example_status_log: example_status_log,
136
145
  verbose: verbose,
137
146
  fail_fast: fail_fast,
138
147
  count: count,
@@ -154,6 +163,43 @@ module TurboTests
154
163
  @argv.first == "shim"
155
164
  end
156
165
 
166
+ def fan_command?
167
+ @argv.first == "fan"
168
+ end
169
+
170
+ def handle_fan_command
171
+ args = @argv.drop(1)
172
+ count = nil
173
+ parser = OptionParser.new do |opts|
174
+ opts.banner = "Usage: turbo_tests2 fan [options] COMMAND [ARGS]"
175
+ opts.on("-n [PROCESSES]", "-w [PROCESSES]", "--workers [PROCESSES]", Integer, "How many processes to use, default: available CPUs") do |n|
176
+ count = n
177
+ end
178
+ end
179
+ parser.order!(args)
180
+
181
+ if args.empty?
182
+ warn(parser)
183
+ exit(1)
184
+ end
185
+
186
+ processes = ParallelTests.determine_number_of_processes(count)
187
+ pids = (1..processes).map do |process_id|
188
+ env = {
189
+ "TEST_ENV_NUMBER" => process_id.to_s,
190
+ "PARALLEL_TEST_GROUPS" => processes.to_s,
191
+ }
192
+ Process.spawn(env, *args)
193
+ end
194
+ statuses = pids.map { |pid| Process.wait2(pid).last }
195
+
196
+ exit(statuses.all?(&:success?) ? 0 : 1)
197
+ rescue OptionParser::ParseError => e
198
+ warn(e.message)
199
+ warn(parser)
200
+ exit(1)
201
+ end
202
+
157
203
  def handle_shim_command
158
204
  command = @argv[1]
159
205
  args = @argv.drop(2)
@@ -20,6 +20,12 @@ RSpec::Core::Runner.singleton_class.prepend(RSpecExt)
20
20
  module TurboTests
21
21
  # An RSpec formatter used for each subprocess during parallel test execution
22
22
  class JsonRowsFormatter
23
+ INTERNAL_BACKTRACE_PATTERNS = [
24
+ %r{/bin/turbo_tests2\b},
25
+ %r{/exe/turbo_tests2\b},
26
+ %r{/lib/turbo_tests(?:\.rb|/)},
27
+ ].freeze
28
+
23
29
  RSpec::Core::Formatters.register(
24
30
  self,
25
31
  :start,
@@ -31,6 +37,8 @@ module TurboTests
31
37
  :example_group_finished,
32
38
  :message,
33
39
  :seed,
40
+ :deprecation,
41
+ :dump_profile,
34
42
  )
35
43
 
36
44
  attr_reader :output
@@ -101,19 +109,56 @@ module TurboTests
101
109
  )
102
110
  end
103
111
 
112
+ def deprecation(notification)
113
+ output_row(
114
+ type: :deprecation,
115
+ deprecation: deprecation_to_json(notification),
116
+ )
117
+ end
118
+
119
+ def dump_profile(notification)
120
+ output_row(
121
+ type: :profile,
122
+ profile: profile_to_json(notification),
123
+ )
124
+ end
125
+
104
126
  private
105
127
 
128
+ def profile_to_json(notification)
129
+ {
130
+ duration: notification.duration,
131
+ number_of_examples: notification.number_of_examples,
132
+ examples: notification.examples.map { |example| example_to_json(example) },
133
+ }
134
+ end
135
+
136
+ def deprecation_to_json(notification)
137
+ {
138
+ deprecated: notification.deprecated,
139
+ message: notification.message,
140
+ replacement: notification.replacement,
141
+ call_site: notification.call_site,
142
+ }
143
+ end
144
+
106
145
  def exception_to_json(exception)
107
146
  return unless exception
108
147
 
109
148
  {
110
149
  class_name: exception.class.name.to_s,
111
- backtrace: exception.backtrace,
150
+ backtrace: filtered_backtrace(exception.backtrace),
112
151
  message: exception.message,
113
152
  cause: exception_to_json(exception.cause),
114
153
  }
115
154
  end
116
155
 
156
+ def filtered_backtrace(backtrace)
157
+ Array(backtrace).reject do |line|
158
+ INTERNAL_BACKTRACE_PATTERNS.any? { |pattern| line.match?(pattern) }
159
+ end
160
+ end
161
+
117
162
  def execution_result_to_json(result)
118
163
  {
119
164
  example_skipped?: result.example_skipped?,
@@ -121,6 +166,7 @@ module TurboTests
121
166
  status: result.status,
122
167
  pending_fixed?: result.pending_fixed?,
123
168
  exception: exception_to_json(result.exception || result.pending_exception),
169
+ run_time: result.respond_to?(:run_time) ? result.run_time : nil,
124
170
  }
125
171
  end
126
172
 
@@ -164,7 +210,7 @@ module TurboTests
164
210
  end
165
211
 
166
212
  def output_row(obj)
167
- output.puts ENV["RSPEC_FORMATTER_OUTPUT_ID"] + obj.to_json
213
+ output.puts "#{ENV.fetch("RSPEC_FORMATTER_OUTPUT_ID", "")}#{obj.to_json}"
168
214
  output.flush
169
215
  end
170
216
  end
@@ -127,6 +127,27 @@ module TurboTests
127
127
  message(error_message)
128
128
  end
129
129
 
130
+ def deprecation(deprecation)
131
+ notification = RSpec::Core::Notifications::DeprecationNotification.from_hash(
132
+ deprecated: deprecation[:deprecated],
133
+ message: deprecation[:message],
134
+ replacement: deprecation[:replacement],
135
+ call_site: deprecation[:call_site],
136
+ )
137
+ delegate_to_formatters(:deprecation, notification)
138
+ end
139
+
140
+ def profile(profile)
141
+ examples = profile[:examples].map { |example| FakeExample.from_obj(example) }
142
+ notification = RSpec::Core::Notifications::ProfileNotification.new(
143
+ profile[:duration],
144
+ examples,
145
+ profile[:number_of_examples],
146
+ {},
147
+ )
148
+ delegate_to_formatters(:dump_profile, notification)
149
+ end
150
+
130
151
  def finish
131
152
  end_time = RSpec::Core::Time.now
132
153
 
@@ -29,6 +29,7 @@ module TurboTests
29
29
 
30
30
  start_time = opts.fetch(:start_time) { RSpec::Core::Time.now }
31
31
  runtime_log = opts.fetch(:runtime_log, nil)
32
+ example_status_log = opts.fetch(:example_status_log, nil)
32
33
  verbose = opts.fetch(:verbose, false)
33
34
  fail_fast = opts.fetch(:fail_fast, nil)
34
35
  count = opts.fetch(:count, nil)
@@ -39,7 +40,10 @@ module TurboTests
39
40
 
40
41
  use_runtime_info = files == ["spec"]
41
42
 
42
- if use_runtime_info
43
+ if example_status_log
44
+ runtime_log = runtime_log_from_example_status(example_status_log)
45
+ parallel_options[:runtime_log] = runtime_log
46
+ elsif use_runtime_info
43
47
  parallel_options[:runtime_log] = runtime_log
44
48
  else
45
49
  parallel_options[:group_by] = :filesize
@@ -56,6 +60,7 @@ module TurboTests
56
60
  files: files,
57
61
  tags: tags,
58
62
  runtime_log: runtime_log,
63
+ example_status_log: example_status_log,
59
64
  verbose: verbose,
60
65
  fail_fast: fail_fast,
61
66
  count: count,
@@ -67,6 +72,21 @@ module TurboTests
67
72
  nice: nice,
68
73
  ).run
69
74
  end
75
+
76
+ def runtime_log_from_example_status(example_status_log)
77
+ statuses = RSpec::Core::ExampleStatusPersister.load_from(example_status_log)
78
+ runtimes = statuses.each_with_object(Hash.new(0.0)) do |status, sums|
79
+ next unless status.fetch(:status).match?(/pass/i)
80
+
81
+ file_name = RSpec::Core::Example.parse_id(status.fetch(:example_id)).first
82
+ sums[file_name] += status.fetch(:run_time).to_s[/\d+(\.\d+)?/].to_f
83
+ end
84
+
85
+ path = File.join("tmp", "turbo_tests2_example_status_runtime.log")
86
+ FileUtils.mkdir_p(File.dirname(path))
87
+ File.write(path, runtimes.sort.map { |file, runtime| "#{file}:#{runtime}" }.join("\n"))
88
+ path
89
+ end
70
90
  end
71
91
 
72
92
  def initialize(**opts)
@@ -97,6 +117,7 @@ module TurboTests
97
117
  @messages = Thread::Queue.new
98
118
  @threads = []
99
119
  @wait_threads = []
120
+ @exited_process_ids = []
100
121
  @error = false
101
122
  @print_failed_group = opts[:print_failed_group]
102
123
  end
@@ -113,6 +134,7 @@ module TurboTests
113
134
  @num_processes,
114
135
  **@parallel_options,
115
136
  )
137
+ @tests_in_groups = tests_in_groups
116
138
 
117
139
  subprocess_opts = {
118
140
  record_runtime: @record_runtime,
@@ -150,6 +172,7 @@ module TurboTests
150
172
  Kernel.exit
151
173
  else
152
174
  puts "\nShutting down subprocesses..."
175
+ report_unfinished_groups("Groups not finished")
153
176
  @wait_threads.each do |wait_thr|
154
177
  begin
155
178
  child_pid = wait_thr.pid
@@ -318,6 +341,7 @@ module TurboTests
318
341
  @reporter.example_failed(example)
319
342
  @failure_count += 1
320
343
  if fail_fast_met
344
+ report_unfinished_groups("Groups stopped by fail-fast")
321
345
  @threads.each(&:kill)
322
346
  break
323
347
  end
@@ -328,6 +352,10 @@ module TurboTests
328
352
  else
329
353
  @reporter.message(message[:message])
330
354
  end
355
+ when "deprecation"
356
+ @reporter.deprecation(message[:deprecation])
357
+ when "profile"
358
+ @reporter.profile(message[:profile])
331
359
  when "seed"
332
360
  when "close"
333
361
  when "error"
@@ -335,6 +363,7 @@ module TurboTests
335
363
  nil
336
364
  when "exit"
337
365
  exited += 1
366
+ @exited_process_ids << message[:process_id]
338
367
  break if exited == @num_processes
339
368
  else
340
369
  warn("Unhandled message in main process: #{message}")
@@ -357,5 +386,20 @@ module TurboTests
357
386
  puts "Group that failed: #{failing_group}"
358
387
  end
359
388
  end
389
+
390
+ def report_unfinished_groups(label)
391
+ groups = Array(@tests_in_groups)
392
+ unfinished_groups = groups.each_with_index.with_object([]) do |(tests, index), unfinished|
393
+ process_id = index + 1
394
+ unfinished << tests unless @exited_process_ids.include?(process_id)
395
+ end
396
+
397
+ return if unfinished_groups.empty?
398
+
399
+ puts "#{label}:"
400
+ unfinished_groups.each_with_index do |tests, index|
401
+ puts " #{index + 1}) #{tests.join(" ")}"
402
+ end
403
+ end
360
404
  end
361
405
  end
@@ -2,7 +2,16 @@
2
2
 
3
3
  module TurboTests
4
4
  class Shim
5
- Result = Struct.new(:status, :path, :message, :exit_code, keyword_init: true)
5
+ class Result
6
+ attr_reader :status, :path, :message, :exit_code
7
+
8
+ def initialize(status:, path:, message:, exit_code:)
9
+ @status = status
10
+ @path = path
11
+ @message = message
12
+ @exit_code = exit_code
13
+ end
14
+ end
6
15
 
7
16
  DEFAULT_RELATIVE_PATH = File.join("bin", "turbo_tests")
8
17
  MANAGED_MARKER = "Generated by turbo_tests2 shim install"
@@ -2,7 +2,7 @@
2
2
 
3
3
  module TurboTests
4
4
  module Version
5
- VERSION = "3.0.0"
5
+ VERSION = "3.1.0"
6
6
  end
7
7
  VERSION = Version::VERSION # Traditional Constant Location
8
8
  end
data/lib/turbo_tests.rb CHANGED
@@ -52,6 +52,7 @@ module TurboTests
52
52
  :pending_fixed?,
53
53
  :exception,
54
54
  :pending_exception,
55
+ :run_time,
55
56
  )
56
57
  class FakeExecutionResult
57
58
  class << self
@@ -63,6 +64,7 @@ module TurboTests
63
64
  obj[:pending_fixed?],
64
65
  FakeException.from_obj(obj[:exception]),
65
66
  FakeException.from_obj(obj[:exception]),
67
+ obj[:run_time],
66
68
  )
67
69
  end
68
70
  end
@@ -1,26 +1,30 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "bundler"
4
3
  require "rspec/core"
5
4
 
6
5
  RSpec.shared_context("with simplecov spawn coverage") do
7
6
  let(:simplecov_spawn_path) do
8
- File.expand_path(".simplecov_spawn.rb", Bundler.root.to_s)
7
+ [Dir.pwd, File.expand_path("..", Dir.pwd)]
8
+ .map { |dir| File.expand_path(".simplecov_spawn.rb", dir) }
9
+ .find { |path| File.file?(path) }
9
10
  end
10
11
 
11
12
  around do |example|
12
13
  original_rubyopt = ENV.fetch("RUBYOPT", nil)
14
+ original_cov_min_hard = ENV.fetch("K_SOUP_COV_MIN_HARD", nil)
13
15
  begin
14
16
  if defined?(SimpleCov) && SimpleCov.running
15
17
  spawn_path = simplecov_spawn_path
16
18
  raise ArgumentError, "Expected SimpleCov spawn shim at #{spawn_path}" unless File.file?(spawn_path)
17
19
 
20
+ ENV["K_SOUP_COV_MIN_HARD"] = "false"
18
21
  ENV["RUBYOPT"] = ["-r#{spawn_path}", original_rubyopt].compact.join(" ").strip
19
22
  end
20
23
 
21
24
  example.run
22
25
  ensure
23
26
  ENV["RUBYOPT"] = original_rubyopt
27
+ ENV["K_SOUP_COV_MIN_HARD"] = original_cov_min_hard
24
28
  end
25
29
  end
26
30
  end
@@ -4,4 +4,3 @@ module TurboTests
4
4
  end
5
5
  VERSION: String
6
6
  end
7
-
data.tar.gz.sig CHANGED
Binary file
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: turbo_tests2
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.0.0
4
+ version: 3.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Illia
@@ -110,6 +110,9 @@ dependencies:
110
110
  - - "~>"
111
111
  - !ruby/object:Gem::Version
112
112
  version: '2.0'
113
+ - - ">="
114
+ - !ruby/object:Gem::Version
115
+ version: 2.0.5
113
116
  type: :development
114
117
  prerelease: false
115
118
  version_requirements: !ruby/object:Gem::Requirement
@@ -117,6 +120,9 @@ dependencies:
117
120
  - - "~>"
118
121
  - !ruby/object:Gem::Version
119
122
  version: '2.0'
123
+ - - ">="
124
+ - !ruby/object:Gem::Version
125
+ version: 2.0.5
120
126
  - !ruby/object:Gem::Dependency
121
127
  name: bundler-audit
122
128
  requirement: !ruby/object:Gem::Requirement
@@ -194,7 +200,7 @@ dependencies:
194
200
  version: '2.0'
195
201
  - - ">="
196
202
  - !ruby/object:Gem::Version
197
- version: 2.0.0
203
+ version: 2.0.1
198
204
  type: :development
199
205
  prerelease: false
200
206
  version_requirements: !ruby/object:Gem::Requirement
@@ -204,7 +210,7 @@ dependencies:
204
210
  version: '2.0'
205
211
  - - ">="
206
212
  - !ruby/object:Gem::Version
207
- version: 2.0.0
213
+ version: 2.0.1
208
214
  - !ruby/object:Gem::Dependency
209
215
  name: ruby-progressbar
210
216
  requirement: !ruby/object:Gem::Requirement
@@ -245,20 +251,20 @@ dependencies:
245
251
  requirements:
246
252
  - - "~>"
247
253
  - !ruby/object:Gem::Version
248
- version: '1.0'
254
+ version: '2.0'
249
255
  - - ">="
250
256
  - !ruby/object:Gem::Version
251
- version: 1.0.3
257
+ version: 2.0.0
252
258
  type: :development
253
259
  prerelease: false
254
260
  version_requirements: !ruby/object:Gem::Requirement
255
261
  requirements:
256
262
  - - "~>"
257
263
  - !ruby/object:Gem::Version
258
- version: '1.0'
264
+ version: '2.0'
259
265
  - - ">="
260
266
  - !ruby/object:Gem::Version
261
- version: 1.0.3
267
+ version: 2.0.0
262
268
  description: "\U0001F680 `turbo_tests2` is a drop-in replacement for `serpapi/turbo_tests`
263
269
  and `grosser/parallel_tests` with incremental summarized output. Source code of
264
270
  `turbo_test2` gem is based on Discourse and Rubygems work in this area (see README
@@ -274,6 +280,7 @@ extra_rdoc_files:
274
280
  - CODE_OF_CONDUCT.md
275
281
  - CONTRIBUTING.md
276
282
  - FUNDING.md
283
+ - LICENSE.md
277
284
  - README.md
278
285
  - RUBOCOP.md
279
286
  - SECURITY.md
@@ -283,6 +290,7 @@ files:
283
290
  - CODE_OF_CONDUCT.md
284
291
  - CONTRIBUTING.md
285
292
  - FUNDING.md
293
+ - LICENSE.md
286
294
  - README.md
287
295
  - RUBOCOP.md
288
296
  - SECURITY.md
@@ -304,10 +312,10 @@ licenses:
304
312
  - MIT
305
313
  metadata:
306
314
  homepage_uri: https://turbo-tests2.galtzo.com
307
- source_code_uri: https://github.com/galtzo-floss/turbo_tests2/tree/v3.0.0
308
- changelog_uri: https://github.com/galtzo-floss/turbo_tests2/blob/v3.0.0/CHANGELOG.md
315
+ source_code_uri: https://github.com/galtzo-floss/turbo_tests2/tree/v3.1.0
316
+ changelog_uri: https://github.com/galtzo-floss/turbo_tests2/blob/v3.1.0/CHANGELOG.md
309
317
  bug_tracker_uri: https://github.com/galtzo-floss/turbo_tests2/issues
310
- documentation_uri: https://www.rubydoc.info/gems/turbo_tests2/3.0.0
318
+ documentation_uri: https://www.rubydoc.info/gems/turbo_tests2/3.1.0
311
319
  funding_uri: https://github.com/sponsors/pboling
312
320
  wiki_uri: https://github.com/galtzo-floss/turbo_tests2/wiki
313
321
  news_uri: https://www.railsbling.com/tags/turbo_tests2
@@ -337,7 +345,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
337
345
  - !ruby/object:Gem::Version
338
346
  version: '0'
339
347
  requirements: []
340
- rubygems_version: 4.0.11
348
+ rubygems_version: 4.0.10
341
349
  specification_version: 4
342
350
  summary: "\U0001F680 `turbo_tests2` is a drop-in replacement for `serpapi/turbo_tests`
343
351
  and `grosser/parallel_tests` with incremental summarized output"
metadata.gz.sig CHANGED
Binary file