bitfab 0.33.1 → 0.33.3

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: 42832c5915e35905df60dae19b3a619fbaa221940e540618897b569be3db9b05
4
- data.tar.gz: 833c23b94c2a6fa1194056775798d0a8e26ec52436489193f96884a993805f65
3
+ metadata.gz: 44ebaac53c01dd84f179de5559eb3f00836c6c1a51f15520b8f10085bcfd6021
4
+ data.tar.gz: b7255113aef3706aefd3a1017520f24e6127503418965a0db23e70573fdd3d0e
5
5
  SHA512:
6
- metadata.gz: 8e42d4c2c6392450ba1fd8f7cc3dd7ce5c3f8362859474dac1ed8bb4289db9db1ed968f05d524a3aa4110809510334e88df62cc161eff66e7965147b5098695a
7
- data.tar.gz: b432abe485b5a331e750ef2203f2f9d6d8c16b5ff0d0804268218e0a82a3fcc2ea09104381aff75cc1bdb8d4fcb7a5e99708839b61cce8d449371331fa58f325
6
+ metadata.gz: de1b1c86dc8660477b87632d766e6f8fdf858d682248c9bd53d024fb3b423acc18d02d877ff567344e5a80aa686d0ad3e72bb02e8cc7dd099612e28ccdc02ad2
7
+ data.tar.gz: bb0ea7013f448acffa4644a359c0c38198ce9c74caf2b77c3d4d4c80a3f5b6f6f0bd73591ffd653b97c99af6518bc0c12460d69ac65314faf5d047541d731d1c
data/lib/bitfab/client.rb CHANGED
@@ -76,9 +76,14 @@ module Bitfab
76
76
  # @param trace_ids [Array<String>, nil] optional list of trace IDs to replay (max 100)
77
77
  # @param max_concurrency [Integer, nil] max threads for parallel replay (default: 10)
78
78
  # @param code_change_description [String, nil] optional rationale for the
79
- # code change being tested in this replay (stored on the experiment)
79
+ # code change being tested in this replay (stored on the experiment).
80
+ # When supplied without code_change_files, this text is preserved while
81
+ # the SDK automatically captures the files.
82
+ # Pass nil when the replay should have no description; use
83
+ # code_change_files: nil to suppress automatic file capture.
80
84
  # @param code_change_files [Array<Hash>, nil] optional list of edited files,
81
- # each as { path:, before:, after: } (use "" for new/deleted files)
85
+ # each as { path:, before:, after: } (use "" for new/deleted files).
86
+ # Omit to capture automatically, or pass nil to suppress file capture.
82
87
  # @param experiment_group_id [String, nil] optional UUID grouping multiple
83
88
  # replay runs into a single experiment batch
84
89
  # @param dataset_id [String, nil] optional UUID of the dataset this replay
@@ -118,7 +123,7 @@ module Bitfab
118
123
  # A raising callback never crashes the run.
119
124
  # @return [Hash] with :items, :test_run_id, :test_run_url
120
125
  def replay(receiver, method_name, trace_function_key:, limit: nil, trace_ids: nil, max_concurrency: 10,
121
- name: nil, code_change_description: nil, code_change_files: nil, experiment_group_id: nil, dataset_id: nil, grader_ids: nil, mock: "marked",
126
+ 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",
122
127
  adapt_inputs: nil, mock_override: nil, db_branch: nil, on_progress: nil)
123
128
  Replay.run(
124
129
  self, receiver, method_name,
@@ -11,6 +11,9 @@ require_relative "warn_once"
11
11
 
12
12
  module Bitfab
13
13
  class HttpClient
14
+ REPLAY_DB_BRANCH_REQUEST_TIMEOUT_SECONDS = 300
15
+ private_constant :REPLAY_DB_BRANCH_REQUEST_TIMEOUT_SECONDS
16
+
14
17
  attr_reader :service_url
15
18
 
16
19
  def initialize(api_key:, service_url: nil, timeout: 120)
@@ -131,6 +134,7 @@ module Bitfab
131
134
  payload["codeChangeFiles"] = normalize_code_change_files(code_change_files) unless code_change_files.nil?
132
135
  payload["experimentGroupId"] = experiment_group_id unless experiment_group_id.nil?
133
136
  payload["includeDbBranchLease"] = true if include_db_branch_lease
137
+ payload["lazyDbBranchLease"] = true if include_db_branch_lease
134
138
  payload["datasetId"] = dataset_id unless dataset_id.nil?
135
139
  payload["graderIds"] = grader_ids unless grader_ids.nil?
136
140
  payload["dbBranchSettings"] = db_branch_settings unless db_branch_settings.nil?
@@ -143,7 +147,7 @@ module Bitfab
143
147
  # sits above the server's own ceiling so the server's error is the one
144
148
  # callers see. Net::HTTP would otherwise default to 60s here, which the
145
149
  # branch creation alone can outlast.
146
- timeout = include_db_branch_lease ? 300 : 30
150
+ timeout = include_db_branch_lease ? REPLAY_DB_BRANCH_REQUEST_TIMEOUT_SECONDS : 30
147
151
  request("/api/sdk/replay/start", payload, timeout:)
148
152
  end
149
153
 
@@ -194,6 +198,16 @@ module Bitfab
194
198
  request("/api/sdk/replay/releaseDbBranchLease", {"neonBranchId" => neon_branch_id}, timeout: 30)
195
199
  end
196
200
 
201
+ def resolve_db_branch_lease(test_run_id, trace_id, db_branch_settings = nil)
202
+ payload = {"testRunId" => test_run_id, "traceId" => trace_id}
203
+ payload["dbBranchSettings"] = db_branch_settings unless db_branch_settings.nil?
204
+ request(
205
+ "/api/sdk/replay/resolveDbBranchLease",
206
+ payload,
207
+ timeout: REPLAY_DB_BRANCH_REQUEST_TIMEOUT_SECONDS
208
+ )
209
+ end
210
+
197
211
  # Send an external trace (fire-and-forget in background thread).
198
212
  def send_external_trace(payload)
199
213
  merged = payload.merge("sdkVersion" => VERSION)
data/lib/bitfab/replay.rb CHANGED
@@ -81,6 +81,8 @@ module Bitfab
81
81
 
82
82
  # Replay historical traces through a traced method and create a test run.
83
83
  module Replay
84
+ CODE_CHANGE_UNSET = Object.new.freeze
85
+
84
86
  module_function
85
87
 
86
88
  # Replay historical traces through a method and create a test run.
@@ -99,9 +101,14 @@ module Bitfab
99
101
  # @param name [String, nil] optional display name for the resulting experiment/test run
100
102
  # @param max_concurrency [Integer, nil] max threads for parallel replay (default: 10)
101
103
  # @param code_change_description [String, nil] optional rationale for the
102
- # code change being tested in this replay (stored on the experiment)
104
+ # code change being tested in this replay (stored on the experiment).
105
+ # When supplied without code_change_files, this text is preserved while
106
+ # the SDK automatically captures the files.
107
+ # Pass nil when the replay should have no description; use
108
+ # code_change_files: nil to suppress automatic file capture.
103
109
  # @param code_change_files [Array<Hash>, nil] optional list of edited files,
104
- # each as { path:, before:, after: } (empty string for new/deleted files)
110
+ # each as { path:, before:, after: } (empty string for new/deleted files).
111
+ # Omit to capture automatically, or pass nil to suppress file capture.
105
112
  # @param experiment_group_id [String, nil] optional UUID grouping multiple
106
113
  # replay runs into a single experiment batch
107
114
  # @param dataset_id [String, nil] optional UUID of the dataset this replay
@@ -140,7 +147,7 @@ module Bitfab
140
147
  # crashes the run.
141
148
  # @return [Hash] with :items, :test_run_id, :test_run_url
142
149
  def run(client, receiver, method_name, trace_function_key:, limit: nil, trace_ids: nil, name: nil,
143
- max_concurrency: 10, code_change_description: nil, code_change_files: nil, experiment_group_id: nil,
150
+ max_concurrency: 10, code_change_description: CODE_CHANGE_UNSET, code_change_files: CODE_CHANGE_UNSET, experiment_group_id: nil,
144
151
  dataset_id: nil, grader_ids: nil, mock: "marked",
145
152
  adapt_inputs: nil, mock_override: nil, db_branch: nil, on_progress: nil)
146
153
  unless MOCK_STRATEGIES.include?(mock.to_s)
@@ -187,19 +194,20 @@ module Bitfab
187
194
 
188
195
  include_db_branch_lease = !db_branch.nil?
189
196
 
190
- # An explicit code change always wins; only when the caller passed neither
191
- # field do we fall back to the payload the replay wrapper captured from git
192
- # and handed over via BITFAB_CODE_CHANGE_PATH. This is what makes an
193
- # experiment show its diff even when the run happened outside the
194
- # assistant's make-change rails.
195
- if code_change_description.nil? && code_change_files.nil?
197
+ # code_change_files controls capture: omitted auto-captures, an array
198
+ # wins, and explicit nil opts out. Preserve a caller-supplied description
199
+ # while filling only the omitted files.
200
+ if code_change_files.equal?(CODE_CHANGE_UNSET)
196
201
  captured = resolve_auto_code_change(name)
197
202
  unless captured.nil?
198
- code_change_description = captured[0]
199
203
  code_change_files = captured[1]
204
+ code_change_description = captured[0] if code_change_description.equal?(CODE_CHANGE_UNSET)
200
205
  end
201
206
  end
207
+ code_change_description = nil if code_change_description.equal?(CODE_CHANGE_UNSET)
208
+ code_change_files = nil if code_change_files.equal?(CODE_CHANGE_UNSET)
202
209
 
210
+ resolved_db_branch_settings = db_branch_settings(db_branch)
203
211
  replay_data = http_client.start_replay(
204
212
  trace_function_key,
205
213
  effective_limit,
@@ -211,7 +219,7 @@ module Bitfab
211
219
  include_db_branch_lease:,
212
220
  dataset_id:,
213
221
  grader_ids:,
214
- db_branch_settings: db_branch_settings(db_branch)
222
+ db_branch_settings: resolved_db_branch_settings
215
223
  )
216
224
  test_run_id = replay_data["testRunId"]
217
225
  test_run_url = replay_data["testRunUrl"]
@@ -219,7 +227,8 @@ module Bitfab
219
227
 
220
228
  result_items = if server_items.any?
221
229
  process_items(http_client, server_items, receiver, method_name, test_run_id, max_concurrency, mock.to_s,
222
- adapt_inputs, include_db_branch_lease, on_progress:, mock_overrides: resolved_overrides)
230
+ adapt_inputs, include_db_branch_lease, resolved_db_branch_settings, on_progress:,
231
+ mock_overrides: resolved_overrides)
223
232
  else
224
233
  []
225
234
  end
@@ -528,7 +537,8 @@ module Bitfab
528
537
 
529
538
  # Process all replay items, optionally in parallel using threads.
530
539
  def process_items(http_client, server_items, receiver, method_name, test_run_id, max_concurrency, mock_strategy,
531
- adapt_inputs = nil, include_db_branch_lease = false, on_progress: nil, mock_overrides: [])
540
+ adapt_inputs = nil, include_db_branch_lease = false, db_branch_settings = nil, on_progress: nil,
541
+ mock_overrides: [])
532
542
  concurrency = max_concurrency || server_items.length
533
543
 
534
544
  # Reports running totals once per item as it settles. In the parallel
@@ -584,7 +594,7 @@ module Bitfab
584
594
  if concurrency <= 1
585
595
  server_items.map do |item|
586
596
  result = process_single_item(http_client, item, receiver, method_name, test_run_id, mock_strategy,
587
- adapt_inputs, include_db_branch_lease, mock_overrides:)
597
+ adapt_inputs, include_db_branch_lease, db_branch_settings, mock_overrides:)
588
598
  report.call(result, original_trace_id_of(item), original_span_id_of(item), test_run_id)
589
599
  result
590
600
  end
@@ -601,7 +611,7 @@ module Bitfab
601
611
  break unless item
602
612
 
603
613
  result = process_single_item(http_client, item, receiver, method_name, test_run_id, mock_strategy,
604
- adapt_inputs, include_db_branch_lease, mock_overrides:)
614
+ adapt_inputs, include_db_branch_lease, db_branch_settings, mock_overrides:)
605
615
  results_mutex.synchronize { results[idx] = result }
606
616
  report.call(result, original_trace_id_of(item), original_span_id_of(item), test_run_id)
607
617
  end
@@ -620,7 +630,7 @@ module Bitfab
620
630
  # than propagated, so one bad trace never aborts the whole replay run
621
631
  # (mirrors the TypeScript and Python SDKs' per-item rescue).
622
632
  def process_single_item(http_client, server_item, receiver, method_name, test_run_id, mock_strategy,
623
- adapt_inputs = nil, include_db_branch_lease = false, mock_overrides: [])
633
+ adapt_inputs = nil, include_db_branch_lease = false, db_branch_settings = nil, mock_overrides: [])
624
634
  metrics = extract_server_item_metrics(server_item)
625
635
  # The ORIGINAL (historical) trace/span this item replays. Canonical
626
636
  # keys are originalTraceId/originalSpanId; older servers send them under
@@ -638,6 +648,13 @@ module Bitfab
638
648
  # for a branch, so running their method against live data would produce a
639
649
  # result that looks valid and is not. Fail the item instead, loudly.
640
650
  lease_error = include_db_branch_lease ? server_item["dbBranchLeaseError"] : nil
651
+ db_snapshot_ref = server_item["dbSnapshotRef"]
652
+ if include_db_branch_lease && lease.nil? && lease_error.nil?
653
+ resolved = http_client.resolve_db_branch_lease(test_run_id, original_trace_id, db_branch_settings)
654
+ lease = resolved["lease"]
655
+ lease_error = resolved["leaseError"]
656
+ db_snapshot_ref = resolved["dbSnapshotRef"] || db_snapshot_ref
657
+ end
641
658
  if lease_error
642
659
  raise "Replay requested a database branch for trace #{original_trace_id} but it " \
643
660
  "could not be resolved (#{lease_error["code"]}): #{lease_error["message"]}. " \
@@ -709,7 +726,7 @@ module Bitfab
709
726
  adapt_ctx:,
710
727
  db_branch_lease: lease,
711
728
  source_bitfab_trace_id: original_trace_id,
712
- db_snapshot_ref: server_item["dbSnapshotRef"]
729
+ db_snapshot_ref:
713
730
  )
714
731
  rescue => e
715
732
  warn "Bitfab: replay item for span #{original_span_id} failed before execution: #{e.message}"
@@ -727,7 +744,7 @@ module Bitfab
727
744
  # Deprecated aliases for original_trace_id/original_span_id.
728
745
  source_trace_id: original_trace_id,
729
746
  source_span_id: original_span_id,
730
- db_snapshot_ref: server_item["dbSnapshotRef"]
747
+ db_snapshot_ref:
731
748
  }
732
749
  ensure
733
750
  release_db_branch_lease(http_client, lease) if lease
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Bitfab
4
- VERSION = "0.33.1"
4
+ VERSION = "0.33.3"
5
5
  end
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.33.1
4
+ version: 0.33.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Harvest Team