ci-queue 0.97.0 → 0.98.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: 4d17fbfbe9128059d59548415b65bf99e86f6b9a3345ffd35c3e694bd1f5d1ec
4
- data.tar.gz: e864440baac5ff4f930455e565509d78e7900243c251f2b5368e5bd2d8e1e114
3
+ metadata.gz: 020b8bf0b6887aa814e3f6b310bc62888e9a5e0af94c6ed652efc0af2b947ef7
4
+ data.tar.gz: 11e86bbe7b3e6bacfe96b84ae131c40222fb61a1afa9788f953c3a85db02d751
5
5
  SHA512:
6
- metadata.gz: 0a7ecb6a7c387b76fab9d317bd619308cc6d4aa1d46684aa4c8a01546308613e1681fecabbebc4340bd9eb2dc8f68a8ca1e7fe0c19202c8b4224d4d260519bf1
7
- data.tar.gz: d215b3fc1ef18fefa67493f2e447bc01a500224efbc61bc07c23bbb332e3dfe3855a73de16671ad8cdb2d59eeb1704a1765555d765b2d9a50532249b4f4d0280
6
+ metadata.gz: b02b9a927e82321c3792c5a479487ef2a80e1093e23d4614e1e3b9191b9a5b5f9691cbdc78bd2a2a566e97dcbcf6231c3d9df19521bc2408099b6e905c38ecfa
7
+ data.tar.gz: 39cbdb4ef28553b81373f5836a8267b1919815fcab5959893bf002a307b7341e6337e045b00dd8a06e4c93dba2a1b3bd05ca9b96672292b5e25b8f50cc507fbf
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- ci-queue (0.97.0)
4
+ ci-queue (0.98.0)
5
5
  logger
6
6
 
7
7
  GEM
data/README.md CHANGED
@@ -138,6 +138,57 @@ The runner also comes with a tool to investigate leaky tests:
138
138
  minitest-queue --queue path/to/test_order.log --failing-test 'SomeTest#test_something' bisect -Itest test/**/*_test.rb
139
139
  ```
140
140
 
141
+ #### Parallel worker metadata
142
+
143
+ Each result is stamped, as it is recorded in the process that ran the test, with:
144
+
145
+ - `parallel_worker_pid`: the pid of the process that ran the test.
146
+ - `parallel_worker_test_index`: a 0-based monotonic counter of results recorded by that process (requeued executions get their own index). Restarts at 0 in each forked process.
147
+ - `parallel_worker_id`: an identifier injected by the embedding environment (e.g. a Rails parallel-testing worker number); `nil` when not applicable.
148
+
149
+ All three fields are included (nil-safe) in `log/test_data.json` emitted by the test data reporter, making per-worker-process execution order reconstructable downstream (`PARTITION BY parallel_worker_id, parallel_worker_pid ORDER BY parallel_worker_test_index`).
150
+
151
+ The worker id can be provided either programmatically or via the environment:
152
+
153
+ ```ruby
154
+ Minitest::Queue.parallel_worker_id = 3
155
+ ```
156
+
157
+ | Variable | Description |
158
+ |---|---|
159
+ | `CI_QUEUE_PARALLEL_WORKER_ID=N` | Sets `parallel_worker_id` for results produced by this process. Read once per process; the setter takes precedence. |
160
+
161
+ Stamping is first-writer-wins. When ci-queue runs tests in-process (the normal
162
+ `minitest-queue` flow), results are stamped automatically as they are recorded.
163
+ Embedding environments that run tests in forked workers and transport results to
164
+ another process (e.g. over DRb to a central reporting server) must stamp in the
165
+ worker **before** sending:
166
+
167
+ ```ruby
168
+ # in the forked worker, after running the test and before the DRb send
169
+ Minitest::Queue.stamp_parallel_worker_metadata(result)
170
+ ```
171
+
172
+ Otherwise the automatic stamp during reporting would capture the reporting
173
+ process's pid and an arrival-order index interleaved across workers. Pre-stamped
174
+ results pass through reporting untouched.
175
+
176
+ Notes:
177
+
178
+ - `parallel_worker_id` identifies a forked test process *inside* one queue worker.
179
+ It is unrelated to ci-queue's own `--worker` / `CI::Queue::Configuration#worker_id`,
180
+ which identifies the whole queue worker (typically one CI job).
181
+ - The payload intentionally carries no build/job identity — like every other field
182
+ in `log/test_data.json`, scoping to a build/job (e.g. a `job_id` column making
183
+ `(job_id, parallel_worker_id, parallel_worker_pid)` unique across a build) is
184
+ expected to be attached by whatever pipeline ingests the file.
185
+ - Stamping is mutex-guarded, so indexes are unique and gap-free even when results
186
+ are recorded from multiple threads. However, per-worker order reconstruction is
187
+ only meaningful with process-based (forked) workers: under thread-based
188
+ parallelization all threads share one `(parallel_worker_id, parallel_worker_pid)`
189
+ partition and the index reflects record order across threads.
190
+ - This applies to minitest-queue only; rspec-queue does not emit these fields.
191
+
141
192
  ### RSpec [DEPRECATED]
142
193
 
143
194
  The rspec-queue runner is deprecated. The minitest-queue runner continues to be supported and is actively being improved. At Shopify, we strongly recommend that new projects set up their test suite using Minitest rather than RSpec.
@@ -2,7 +2,7 @@
2
2
 
3
3
  module CI
4
4
  module Queue
5
- VERSION = '0.97.0'
5
+ VERSION = '0.98.0'
6
6
  DEV_SCRIPTS_ROOT = ::File.expand_path('../../../../../redis', __FILE__)
7
7
  RELEASE_SCRIPTS_ROOT = ::File.expand_path('../redis', __FILE__)
8
8
  end
@@ -49,7 +49,7 @@ module Minitest
49
49
  end
50
50
 
51
51
  def test_duration
52
- @test.time
52
+ @test.time || 0.0
53
53
  end
54
54
 
55
55
  def test_start_timestamp
@@ -73,6 +73,22 @@ module Minitest
73
73
  @test.source_location.last
74
74
  end
75
75
 
76
+ # The parallel_worker_* fields are stamped by
77
+ # Minitest::Queue.stamp_parallel_worker_metadata in the worker process
78
+ # that ran the test. They are nil for embedders that don't produce
79
+ # them (results lacking the accessors, or no worker id configured).
80
+ def parallel_worker_id
81
+ @test.parallel_worker_id if @test.respond_to?(:parallel_worker_id)
82
+ end
83
+
84
+ def parallel_worker_test_index
85
+ @test.parallel_worker_test_index if @test.respond_to?(:parallel_worker_test_index)
86
+ end
87
+
88
+ def parallel_worker_pid
89
+ @test.parallel_worker_pid if @test.respond_to?(:parallel_worker_pid)
90
+ end
91
+
76
92
  # Error class only considers failures wheras the other error fields also consider skips
77
93
  def error_class
78
94
  return nil unless @test.failure
@@ -119,6 +135,9 @@ module Minitest
119
135
  test_finish_timestamp: test_finish_timestamp,
120
136
  test_file_path: test_file_path,
121
137
  test_file_line_number: test_file_line_number,
138
+ parallel_worker_id: parallel_worker_id,
139
+ parallel_worker_test_index: parallel_worker_test_index,
140
+ parallel_worker_pid: parallel_worker_pid,
122
141
  error_class: error_class,
123
142
  error_message: error_message,
124
143
  error_file_path: error_file_path,
@@ -111,10 +111,22 @@ module Minitest
111
111
  attr_accessor :queue_id, :queue_entry
112
112
  end
113
113
 
114
+ # Per-worker-process execution metadata, stamped on each result in the
115
+ # process that ran the test (before any DRb send in embedding
116
+ # environments), so that per-process execution order is reconstructable
117
+ # downstream: PARTITION BY parallel_worker_id, parallel_worker_pid
118
+ # ORDER BY parallel_worker_test_index.
119
+ module ParallelWorkerMetadata
120
+ attr_accessor :parallel_worker_id, :parallel_worker_test_index, :parallel_worker_pid
121
+ end
122
+
114
123
  module Queue
115
124
  extend ::CI::Queue::OutputHelpers
116
125
  attr_writer :run_command_formatter, :project_root
117
126
 
127
+ PARALLEL_WORKER_METADATA_MUTEX = Mutex.new
128
+ private_constant :PARALLEL_WORKER_METADATA_MUTEX
129
+
118
130
  def run_command_formatter
119
131
  @run_command_formatter ||= if defined?(Rails) && defined?(Rails::TestUnitRailtie)
120
132
  RAILS_RUN_COMMAND_FORMATTER
@@ -155,6 +167,60 @@ module Minitest
155
167
  end
156
168
 
157
169
  class << self
170
+ # Identifies which parallel worker this process is (e.g. Rails'
171
+ # parallel testing worker number). Injected by the embedding
172
+ # environment via this setter or the CI_QUEUE_PARALLEL_WORKER_ID
173
+ # environment variable; nil when not applicable.
174
+ attr_writer :parallel_worker_id
175
+
176
+ def parallel_worker_id
177
+ return @parallel_worker_id if @parallel_worker_id
178
+
179
+ # Memoized per process: re-read after a fork, but not on every test.
180
+ if @parallel_worker_id_env_pid != Process.pid
181
+ @parallel_worker_id_env_pid = Process.pid
182
+ @parallel_worker_id_from_env = parallel_worker_id_from_env
183
+ end
184
+ @parallel_worker_id_from_env
185
+ end
186
+
187
+ # Stamps per-process execution metadata on a result. Must be called
188
+ # in the process that ran the test so pid and per-process test index
189
+ # are captured worker-side.
190
+ #
191
+ # First writer wins: results that were already stamped are left
192
+ # untouched. Embedding environments that run tests in forked workers
193
+ # and transport results to another process (e.g. over DRb) MUST call
194
+ # this in the worker before sending, otherwise the stamp applied
195
+ # during reporting would carry the reporting process's pid and an
196
+ # arrival-order index interleaved across workers.
197
+ #
198
+ # The counter is mutex-guarded: results recorded from multiple threads
199
+ # (e.g. a DRb server dispatching each call on its own thread) get
200
+ # unique, gap-free indexes reflecting record order in this process.
201
+ # Returns the result when it was stamped, nil when it was skipped.
202
+ def stamp_parallel_worker_metadata(result)
203
+ return unless result.respond_to?(:parallel_worker_pid=)
204
+ return if result.parallel_worker_pid # already stamped worker-side
205
+
206
+ PARALLEL_WORKER_METADATA_MUTEX.synchronize do
207
+ pid = Process.pid
208
+ if @parallel_worker_metadata_pid != pid
209
+ # Restart the per-process counter in forked workers so that
210
+ # (parallel_worker_id, parallel_worker_pid, parallel_worker_test_index)
211
+ # always reflects execution order within a single process.
212
+ @parallel_worker_metadata_pid = pid
213
+ @parallel_worker_next_test_index = 0
214
+ end
215
+
216
+ result.parallel_worker_id = parallel_worker_id
217
+ result.parallel_worker_pid = pid
218
+ result.parallel_worker_test_index = @parallel_worker_next_test_index
219
+ @parallel_worker_next_test_index += 1
220
+ end
221
+ result
222
+ end
223
+
158
224
  def queue
159
225
  Minitest.queue
160
226
  end
@@ -179,6 +245,8 @@ module Minitest
179
245
  end
180
246
 
181
247
  def handle_test_result(reporter, example, result)
248
+ stamp_parallel_worker_metadata(result)
249
+
182
250
  if result.respond_to?(:queue_id=)
183
251
  result.queue_id = example.id
184
252
  result.queue_entry = example.queue_entry if result.respond_to?(:queue_entry=)
@@ -213,6 +281,17 @@ module Minitest
213
281
 
214
282
  private
215
283
 
284
+ def parallel_worker_id_from_env
285
+ value = ENV['CI_QUEUE_PARALLEL_WORKER_ID']
286
+ return nil if value.nil? || value.empty?
287
+
288
+ begin
289
+ Integer(value)
290
+ rescue ArgumentError
291
+ value
292
+ end
293
+ end
294
+
216
295
  def report_load_stats(queue)
217
296
  return unless CI::Queue.debug?
218
297
  return unless queue.respond_to?(:file_loader)
@@ -441,6 +520,7 @@ module Minitest
441
520
  if result
442
521
  result.start_timestamp = start_timestamp
443
522
  result.finish_timestamp = current_timestamp
523
+ result.time ||= (result.finish_timestamp - result.start_timestamp).to_f
444
524
  end
445
525
  end
446
526
 
@@ -608,11 +688,13 @@ if defined? Minitest::Result
608
688
  Minitest::Result.prepend(Minitest::Flakiness)
609
689
  Minitest::Result.prepend(Minitest::WithTimestamps)
610
690
  Minitest::Result.prepend(Minitest::ResultMetadata)
691
+ Minitest::Result.prepend(Minitest::ParallelWorkerMetadata)
611
692
  else
612
693
  Minitest::Test.prepend(Minitest::Requeueing)
613
694
  Minitest::Test.prepend(Minitest::Flakiness)
614
695
  Minitest::Test.prepend(Minitest::WithTimestamps)
615
696
  Minitest::Test.prepend(Minitest::ResultMetadata)
697
+ Minitest::Test.prepend(Minitest::ParallelWorkerMetadata)
616
698
 
617
699
  module MinitestBackwardCompatibility
618
700
  def source_location
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ci-queue
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.97.0
4
+ version: 0.98.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jean Boussier