bitfab 0.36.3 → 0.36.4

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: deea3052760a7b677216225f0cd030f4931995b1d10e6b7396c97a57a27ab9c1
4
- data.tar.gz: 5f64a512db5c8c79f092e5bbf92e16adfc2e20a4a07c869eafab48fb42db5665
3
+ metadata.gz: 5578e8fdcdd81610b4e65fa94a5e9be88339e816742f6ba7fea81b53867eedf5
4
+ data.tar.gz: 308d35969ac643d1ddcfb76c169a6108906c958275b392f7d159ea647b0bf63c
5
5
  SHA512:
6
- metadata.gz: f9024ab4975b3963e4d2c91aa903b3e4bc7880791aa1f0a4132a9ee847e54e2132339246cbc1067beba8a2130e813738474fcc40a38a1d3b9a0e19ba92633834
7
- data.tar.gz: b3c4b7c252a566192362e6d1a3e4d31bcad55f752a70527fce5de87ebc9ab75c4839a02fd5d67589823ee36003f3fcb49d5bdade0a315dc8a22971b9769ea9c3
6
+ metadata.gz: 2c0511909223595a7792e780d0c77b8c0f5ef2d239e4b2a5dda7946a63fbe4801893ae02ba3392b74520ad9d82844e519d3319a900f22c30bef4857e616c0420
7
+ data.tar.gz: 0ae83e2f731bc2f6fa9f564b6fe8754b7fff82c5ae03a914067cca6fb75f4ddb10e935bba8976de6f0dabe7025812e49d9392e2ace61b5202c02d80b21fe00aa
data/lib/bitfab/client.rb CHANGED
@@ -132,21 +132,27 @@ module Bitfab
132
132
  # +:min_cu+/+:max_cu+ size the branch compute in Neon Compute Units and
133
133
  # +:warmup_sql+ warms its cache. Read the resolved branch inside the
134
134
  # replayed method with +Bitfab.current_replay_branch+.
135
- # @param on_progress [#call, nil] optional callback invoked once per item as
136
- # it finishes, with a running-totals hash { completed:, total:, succeeded:,
137
- # errored: }. Use it to render replay progress (e.g. a terminal progress
138
- # bar). Replay does not know pass/fail yet, so the totals only distinguish
139
- # items whose method ran (:succeeded) from items that raised (:errored).
140
- # A raising callback never crashes the run.
135
+ # @param on_item_finish [#call, nil] optional callback invoked exactly once
136
+ # per item as it finishes, always with a running-totals hash containing
137
+ # { completed:, total:, succeeded:, errored:, item: }. It never receives a
138
+ # whole-run completion event. Replay does not know pass/fail yet, so the
139
+ # totals only distinguish items whose method ran (:succeeded) from items
140
+ # that raised (:errored). A raising callback never crashes the run.
141
+ # @param on_progress [#call, nil] deprecated compatibility callback. It
142
+ # receives the same per-item events plus the legacy item-less +"complete"+
143
+ # event. Ignored when +on_item_finish+ is also provided.
144
+ # @param on_item_start [#call, nil] optional callback invoked when a worker
145
+ # begins processing each item. Pair it with on_item_finish to distinguish
146
+ # queued work from in-flight work. A raising callback never crashes the run.
141
147
  # @return [Hash] with :items, :test_run_id, :test_run_url
142
148
  def replay(receiver, method_name, trace_function_key:, limit: nil, trace_ids: nil, max_concurrency: 10,
143
149
  name: nil, code_change_description: Replay::CODE_CHANGE_UNSET, code_change_files: Replay::CODE_CHANGE_UNSET, experiment_group_id: nil, dataset_id: nil, grader_ids: nil, mock: "marked",
144
- adapt_inputs: nil, mock_override: nil, db_branch: nil, on_progress: nil)
150
+ adapt_inputs: nil, mock_override: nil, db_branch: nil, on_item_start: nil, on_item_finish: nil, on_progress: nil)
145
151
  Replay.run(
146
152
  self, receiver, method_name,
147
153
  trace_function_key:, limit:, trace_ids:, name:, max_concurrency:,
148
154
  code_change_description:, code_change_files:, experiment_group_id:, dataset_id:, grader_ids:, mock:, adapt_inputs:,
149
- mock_override:, db_branch:,
155
+ mock_override:, db_branch:, on_item_start:, on_item_finish:,
150
156
  on_progress:
151
157
  )
152
158
  end
data/lib/bitfab/replay.rb CHANGED
@@ -201,11 +201,11 @@ module Bitfab
201
201
  # and ceiling in Neon Compute Units, and +:warmup_sql+ warms the branch's
202
202
  # cache before the replayed method sees it. Read the resolved branch
203
203
  # inside the method with +Bitfab.current_replay_branch+.
204
- # @param on_progress [#call, nil] optional callback invoked once per item as
204
+ # @param on_item_finish [#call, nil] optional callback invoked once per item as
205
205
  # it finishes, with a running-totals hash { completed:, total:, succeeded:,
206
206
  # errored:, item: } where item is { trace_id:, original_trace_id:,
207
207
  # original_span_id:, error:, duration_ms: } for the single item that just
208
- # settled (source_trace_id/source_span_id remain as deprecated aliases).
208
+ # finished (source_trace_id/source_span_id remain as deprecated aliases).
209
209
  # trace_id is the new server replay trace id, written in after the run
210
210
  # completes (nil during progress callbacks); original_trace_id is the
211
211
  # ORIGINAL (historical) trace that was replayed, error is that item's replay
@@ -213,14 +213,22 @@ module Bitfab
213
213
  # duration_ms is how long that one trace took to replay. Use it to
214
214
  # render replay progress (e.g. a per-trace log). A raising callback never
215
215
  # crashes the run.
216
+ # @param on_progress [#call, nil] deprecated compatibility callback. It
217
+ # receives the same per-item finish events plus the legacy whole-run
218
+ # +"complete"+ event. Ignored when +on_item_finish+ is also provided.
219
+ # @param on_item_start [#call, nil] optional callback invoked when a worker
220
+ # begins processing each item, before replay setup and customer code run.
221
+ # Pair it with on_item_finish to distinguish queued work from in-flight work.
222
+ # A raising callback never crashes the run.
216
223
  # @return [Hash] with :items, :test_run_id, :test_run_url
217
224
  def run(client, receiver, method_name, trace_function_key:, limit: nil, trace_ids: nil, name: nil,
218
225
  max_concurrency: 10, code_change_description: CODE_CHANGE_UNSET, code_change_files: CODE_CHANGE_UNSET, experiment_group_id: nil,
219
226
  dataset_id: nil, grader_ids: nil, mock: "marked",
220
- adapt_inputs: nil, mock_override: nil, db_branch: nil, on_progress: nil)
227
+ adapt_inputs: nil, mock_override: nil, db_branch: nil, on_item_start: nil, on_item_finish: nil, on_progress: nil)
221
228
  unless MOCK_STRATEGIES.include?(mock.to_s)
222
229
  raise ArgumentError, "Invalid mock strategy '#{mock}'. Must be one of: #{MOCK_STRATEGIES.join(", ")}"
223
230
  end
231
+ item_finish_callback = on_item_finish || on_progress
224
232
  if trace_ids
225
233
  raise ArgumentError, "trace_ids must contain at least one trace ID." if trace_ids.empty?
226
234
  if trace_ids.length > 100
@@ -296,7 +304,8 @@ module Bitfab
296
304
 
297
305
  result_items = if server_items.any?
298
306
  process_items(http_client, server_items, receiver, method_name, test_run_id, max_concurrency, mock.to_s,
299
- adapt_inputs, include_db_branch_lease, resolved_db_branch_settings, on_progress:,
307
+ adapt_inputs, include_db_branch_lease, resolved_db_branch_settings, on_item_start:,
308
+ on_item_finish: item_finish_callback,
300
309
  mock_overrides: resolved_overrides)
301
310
  else
302
311
  []
@@ -383,14 +392,12 @@ module Bitfab
383
392
  test_run_id:,
384
393
  test_run_url: full_test_run_url
385
394
  }
386
- # Persist the enriched result two ways so the Bitfab plugin never has to
387
- # parse the replay's stdout (which a dependency's logging can corrupt):
388
- # write it to BITFAB_REPLAY_RESULT_PATH when the plugin set that env var,
389
- # and stream a terminal "complete" progress event. The plugin prefers the
390
- # streamed event and falls back to the file. The event routes through
391
- # on_progress so only progress-reporting runs emit it.
395
+ # Persist the enriched result so the Bitfab plugin never has to parse the
396
+ # replay's stdout, which a dependency's logging can corrupt.
392
397
  write_replay_result_file(result)
393
- if on_progress
398
+ # Preserve the legacy terminal event only for on_progress. on_item_finish
399
+ # is strictly item-scoped, so every invocation always includes an item.
400
+ if on_item_finish.nil? && on_progress
394
401
  errored = result_items.count { |item| !item[:error].nil? }
395
402
  total = result_items.length
396
403
  begin
@@ -631,36 +638,56 @@ module Bitfab
631
638
 
632
639
  # Process all replay items, optionally in parallel using threads.
633
640
  def process_items(http_client, server_items, receiver, method_name, test_run_id, max_concurrency, mock_strategy,
634
- adapt_inputs = nil, include_db_branch_lease = false, db_branch_settings = nil, on_progress: nil,
641
+ adapt_inputs = nil, include_db_branch_lease = false, db_branch_settings = nil, on_item_start: nil, on_item_finish: nil,
635
642
  mock_overrides: [])
636
643
  concurrency = max_concurrency || server_items.length
637
644
 
638
- # Reports running totals once per item as it settles. In the parallel
639
- # path it runs from worker threads, so the mutex both makes the counter
640
- # updates safe and serializes the user's callback (never called
641
- # concurrently). A raising callback is swallowed: progress UI must never
642
- # crash the run.
645
+ # Lifecycle callbacks run from worker threads in the parallel path, so the
646
+ # mutex makes counter updates safe and serializes each callback. A raising
647
+ # callback is swallowed because progress UI must never crash the run.
643
648
  total = server_items.length
644
649
  progress_mutex = Mutex.new
650
+ started = 0
645
651
  completed = 0
646
652
  succeeded = 0
647
653
  errored = 0
648
- # Each event carries the single item that just settled so a progress UI
654
+ # Each event carries the single item that just finished so a progress UI
649
655
  # can render per-trace pass/fail as the run streams. The item's :trace_id
650
656
  # is nil at this stage: the server replay trace id isn't known until run()
651
657
  # writes it in after complete_replay, and the client correlation id is
652
658
  # never surfaced. original_trace_id (the historical trace being replayed,
653
659
  # taken from the server item) is what a UI keys on to identify what just
654
- # settled. source_trace_id/source_span_id are kept as deprecated aliases.
655
- report = lambda do |result, original_trace_id, original_span_id, test_run_id|
656
- return unless on_progress
660
+ # finished. source_trace_id/source_span_id are kept as deprecated aliases.
661
+ report_start = lambda do |original_trace_id, original_span_id|
662
+ progress_mutex.synchronize do
663
+ started += 1
664
+ next unless on_item_start
665
+
666
+ begin
667
+ on_item_start.call({
668
+ type: "started", test_run_id:, started:, completed:, total:, succeeded:, errored:,
669
+ item: {
670
+ original_trace_id:,
671
+ original_span_id:,
672
+ source_trace_id: original_trace_id,
673
+ source_span_id: original_span_id
674
+ }
675
+ })
676
+ rescue => e
677
+ warn "Bitfab: replay on_item_start callback raised: #{e.message}"
678
+ end
679
+ end
680
+ end
657
681
 
682
+ report = lambda do |result, original_trace_id, original_span_id, test_run_id|
658
683
  progress_mutex.synchronize do
659
684
  completed += 1
660
685
  error = result[:error]
661
686
  error.nil? ? (succeeded += 1) : (errored += 1)
687
+ next unless on_item_finish
688
+
662
689
  begin
663
- on_progress.call({
690
+ on_item_finish.call({
664
691
  test_run_id:, completed:, total:, succeeded:, errored:,
665
692
  item: {
666
693
  trace_id: result[:trace_id],
@@ -682,13 +709,14 @@ module Bitfab
682
709
  }
683
710
  })
684
711
  rescue => e
685
- warn "Bitfab: replay on_progress callback raised: #{e.message}"
712
+ warn "Bitfab: replay on_item_finish callback raised: #{e.message}"
686
713
  end
687
714
  end
688
715
  end
689
716
 
690
717
  if concurrency <= 1
691
718
  server_items.map do |item|
719
+ report_start.call(original_trace_id_of(item), original_span_id_of(item))
692
720
  result = process_single_item(http_client, item, receiver, method_name, test_run_id, mock_strategy,
693
721
  adapt_inputs, include_db_branch_lease, db_branch_settings, mock_overrides:)
694
722
  report.call(result, original_trace_id_of(item), original_span_id_of(item), test_run_id)
@@ -706,6 +734,7 @@ module Bitfab
706
734
  item, idx = work_mutex.synchronize { work_queue.shift }
707
735
  break unless item
708
736
 
737
+ report_start.call(original_trace_id_of(item), original_span_id_of(item))
709
738
  result = process_single_item(http_client, item, receiver, method_name, test_run_id, mock_strategy,
710
739
  adapt_inputs, include_db_branch_lease, db_branch_settings, mock_overrides:)
711
740
  results_mutex.synchronize { results[idx] = result }
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Bitfab
4
- VERSION = "0.36.3"
4
+ VERSION = "0.36.4"
5
5
  end
data/lib/bitfab.rb CHANGED
@@ -144,20 +144,18 @@ module Bitfab
144
144
  CurrentTrace.new(entry[:trace_id])
145
145
  end
146
146
 
147
- # A ready-made on_progress callback for replay: writes one @@bitfab:progress
148
- # line per trace to stderr, which the Bitfab plugin polls to report live
149
- # progress while the replay runs in the background, so replay scripts never
150
- # hand-format the protocol. stdout stays the ReplayResult JSON. Never raises
151
- # (progress must not crash a run). The progress hash's item is forwarded
152
- # verbatim through to_json, so the plugin sees the source trace id, local
153
- # replay trace id, outputs, error, and duration of the item that just settled.
147
+ # A ready-made replay lifecycle reporter. Pass it as both on_item_start and
148
+ # on_item_finish so the Bitfab plugin sees in-flight and finished items. It writes
149
+ # @@bitfab:progress lines to stderr, leaving stdout for ReplayResult JSON, and
150
+ # never raises because progress reporting must not crash a run.
154
151
  #
155
152
  # @example
156
- # Bitfab.client.replay(..., on_progress: Bitfab.method(:report_replay_progress))
153
+ # reporter = Bitfab.method(:report_replay_progress)
154
+ # Bitfab.client.replay(..., on_item_start: reporter, on_item_finish: reporter)
157
155
  #
158
156
  # @param progress [Hash] running totals with keys completed, total,
159
157
  # succeeded, errored, and item (the object replay already passes to
160
- # on_progress)
158
+ # on_item_finish)
161
159
  def report_replay_progress(progress)
162
160
  warn "#{BITFAB_PROGRESS_PREFIX}#{Replay.json_safe(progress).to_json}"
163
161
  rescue
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bitfab
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.36.3
4
+ version: 0.36.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Harvest Team