bitfab 0.15.0 → 0.16.1

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: b0e39c364993d34e99e1d7e3c3a878e0fc6fbfe685fcb0335128bfbf7252161b
4
- data.tar.gz: 54bcd62faffceac5c0f67f26f5dedbed0057061019d24d1d789f9988fbf0a44a
3
+ metadata.gz: 0dfedcfce509695e2bb260c885fcfa460aaab8d9c89421379cd7572cd280eb95
4
+ data.tar.gz: e8aaba21b08b4a53c9f89319e7c7a9d9f0a911f0be657058d80fc84add6e4f6e
5
5
  SHA512:
6
- metadata.gz: 04cef22ee4135b8c43e1e35f5ead74f5650d9f49e16b818de05980d01a054b145bf4adbad1c9fc3ec01b0e17130e108df666f75bc8bc9bf4666eb23eb9ba7acc
7
- data.tar.gz: bb4724aff3d7fc6a9f04e1a923fd5aaabb2e808ef2310ef2c1d51e6a246c296653c1ea85509a92e1a1b927c131074e0907c07623ade5affaa6bab8fd20b2464c
6
+ metadata.gz: 89851a89aae7ad24ac24d03335e3641b2715c5a841dfd76d302effd8ca07723d41cf0de6103884795fa96337c79671ed9ca5776a5b71c1aa805c3a87621a59b5
7
+ data.tar.gz: 3fb0c600868f499787e25dc424ce7643ee383f8da1e72fb99ec06a394949fc5ca5f8180e9f40b45791da8f2d4d1b562422bc4921d98643dbefb8c9d03e754893
data/lib/bitfab/client.rb CHANGED
@@ -53,13 +53,18 @@ module Bitfab
53
53
  # @param mock [String] mock strategy for child spans: "none" (default),
54
54
  # "all", or "marked". "all" mocks every child span; "marked" only mocks
55
55
  # spans declared with mock_on_replay: true.
56
+ # @param adapt_inputs [#call, nil] optional hook to reshape recorded inputs
57
+ # onto the method's current signature when its shape changed after the
58
+ # traces were captured. Receives (args, kwargs, ctx) where ctx is
59
+ # { trace_id:, source_span_id: }, and returns [new_args, new_kwargs].
56
60
  # @return [Hash] with :items, :test_run_id, :test_run_url
57
61
  def replay(receiver, method_name, trace_function_key:, limit: nil, trace_ids: nil, max_concurrency: 10,
58
- code_change_description: nil, code_change_files: nil, experiment_group_id: nil, mock: "none")
62
+ code_change_description: nil, code_change_files: nil, experiment_group_id: nil, mock: "none",
63
+ adapt_inputs: nil)
59
64
  Replay.run(
60
65
  self, receiver, method_name,
61
66
  trace_function_key:, limit:, trace_ids:, max_concurrency:,
62
- code_change_description:, code_change_files:, experiment_group_id:, mock:
67
+ code_change_description:, code_change_files:, experiment_group_id:, mock:, adapt_inputs:
63
68
  )
64
69
  end
65
70
 
data/lib/bitfab/replay.rb CHANGED
@@ -62,8 +62,8 @@ module Bitfab
62
62
  # @param method_name [Symbol] the method to replay
63
63
  # @param trace_function_key [String] the trace function key for this method
64
64
  # @param limit [Integer, nil] maximum number of traces to replay (default: 5).
65
- # Mutually exclusive with trace_ids: an explicit ID list already
66
- # determines how many traces replay, so passing both raises.
65
+ # Ignored when trace_ids is passed (with a warning): an explicit ID list
66
+ # already determines how many traces replay.
67
67
  # @param trace_ids [Array<String>, nil] optional list of trace IDs to replay (max 100)
68
68
  # @param max_concurrency [Integer, nil] max threads for parallel replay (default: 10)
69
69
  # @param code_change_description [String, nil] optional rationale for the
@@ -75,9 +75,16 @@ module Bitfab
75
75
  # @param mock [String] mock strategy for child spans: "none" (default),
76
76
  # "all", or "marked". "all" mocks every child span; "marked" only mocks
77
77
  # spans declared with mock_on_replay: true.
78
+ # @param adapt_inputs [#call, nil] optional hook to reshape recorded inputs
79
+ # onto the method's current signature when its shape changed after the
80
+ # traces were captured. Receives (args, kwargs, ctx) where ctx is
81
+ # { trace_id:, source_span_id: }, and returns [new_args, new_kwargs]. Runs
82
+ # per item inside the same rescue as the method, so a raising adapter sets
83
+ # that item's :error rather than crashing the run.
78
84
  # @return [Hash] with :items, :test_run_id, :test_run_url
79
85
  def run(client, receiver, method_name, trace_function_key:, limit: nil, trace_ids: nil, max_concurrency: 10,
80
- code_change_description: nil, code_change_files: nil, experiment_group_id: nil, mock: "none")
86
+ code_change_description: nil, code_change_files: nil, experiment_group_id: nil, mock: "none",
87
+ adapt_inputs: nil)
81
88
  unless MOCK_STRATEGIES.include?(mock.to_s)
82
89
  raise ArgumentError, "Invalid mock strategy '#{mock}'. Must be one of: #{MOCK_STRATEGIES.join(", ")}"
83
90
  end
@@ -88,8 +95,8 @@ module Bitfab
88
95
  end
89
96
  end
90
97
  if limit && trace_ids
91
- raise ArgumentError,
92
- "Pass either limit or trace_ids, not both: an explicit trace ID list already determines how many traces replay."
98
+ warn "Bitfab: limit is ignored when trace_ids is passed: the explicit trace ID list already " \
99
+ "determines how many traces replay."
93
100
  end
94
101
 
95
102
  http_client = client.instance_variable_get(:@http_client)
@@ -111,7 +118,8 @@ module Bitfab
111
118
  server_items = replay_data["items"] || []
112
119
 
113
120
  result_items = if server_items.any?
114
- process_items(http_client, server_items, receiver, method_name, test_run_id, max_concurrency, mock.to_s)
121
+ process_items(http_client, server_items, receiver, method_name, test_run_id, max_concurrency, mock.to_s,
122
+ adapt_inputs)
115
123
  else
116
124
  []
117
125
  end
@@ -183,12 +191,13 @@ module Bitfab
183
191
  end
184
192
 
185
193
  # Process all replay items, optionally in parallel using threads.
186
- def process_items(http_client, server_items, receiver, method_name, test_run_id, max_concurrency, mock_strategy)
194
+ def process_items(http_client, server_items, receiver, method_name, test_run_id, max_concurrency, mock_strategy,
195
+ adapt_inputs = nil)
187
196
  concurrency = max_concurrency || server_items.length
188
197
 
189
198
  if concurrency <= 1
190
199
  server_items.map do |item|
191
- process_single_item(http_client, item, receiver, method_name, test_run_id, mock_strategy)
200
+ process_single_item(http_client, item, receiver, method_name, test_run_id, mock_strategy, adapt_inputs)
192
201
  end
193
202
  else
194
203
  results_mutex = Mutex.new
@@ -202,7 +211,8 @@ module Bitfab
202
211
  item, idx = work_mutex.synchronize { work_queue.shift }
203
212
  break unless item
204
213
 
205
- result = process_single_item(http_client, item, receiver, method_name, test_run_id, mock_strategy)
214
+ result = process_single_item(http_client, item, receiver, method_name, test_run_id, mock_strategy,
215
+ adapt_inputs)
206
216
  results_mutex.synchronize { results[idx] = result }
207
217
  end
208
218
  end
@@ -219,7 +229,8 @@ module Bitfab
219
229
  # deserializing inputs is captured on the returned item's :error rather
220
230
  # than propagated, so one bad trace never aborts the whole replay run
221
231
  # (mirrors the TypeScript and Python SDKs' per-item rescue).
222
- def process_single_item(http_client, server_item, receiver, method_name, test_run_id, mock_strategy)
232
+ def process_single_item(http_client, server_item, receiver, method_name, test_run_id, mock_strategy,
233
+ adapt_inputs = nil)
223
234
  metrics = extract_server_item_metrics(server_item)
224
235
 
225
236
  span = http_client.get_external_span(server_item["externalSpanId"])
@@ -231,6 +242,8 @@ module Bitfab
231
242
  mock_tree = build_mock_tree(tree["root"] || {})
232
243
  end
233
244
 
245
+ adapt_ctx = {trace_id: server_item["traceId"], source_span_id: server_item["externalSpanId"]}
246
+
234
247
  execute_item(
235
248
  item_data,
236
249
  receiver,
@@ -240,7 +253,9 @@ module Bitfab
240
253
  metrics,
241
254
  input_source_trace_id: span["externalTraceId"],
242
255
  mock_strategy:,
243
- mock_tree:
256
+ mock_tree:,
257
+ adapt_inputs:,
258
+ adapt_ctx:
244
259
  )
245
260
  rescue => e
246
261
  warn "Bitfab: replay item for span #{server_item["externalSpanId"]} failed before execution: #{e.message}"
@@ -331,7 +346,7 @@ module Bitfab
331
346
 
332
347
  # Execute a single replay item: deserialize inputs, call method with replay context.
333
348
  def execute_item(item, receiver, method_name, test_run_id, input_source_span_id = nil, metrics = {},
334
- input_source_trace_id: nil, mock_strategy: "none", mock_tree: nil)
349
+ input_source_trace_id: nil, mock_strategy: "none", mock_tree: nil, adapt_inputs: nil, adapt_ctx: nil)
335
350
  args, kwargs = Serialize.deserialize_inputs(item)
336
351
 
337
352
  fn_result = nil
@@ -352,6 +367,13 @@ module Bitfab
352
367
  mock_strategy:,
353
368
  pending_persistence:
354
369
  ) do
370
+ # Reshape recorded inputs onto the current signature when an adapter is
371
+ # supplied. Inside the rescue so a raising adapter surfaces on this
372
+ # item's :error instead of crashing the run; args is reported on :input.
373
+ if adapt_inputs
374
+ ctx = adapt_ctx || {trace_id: nil, source_span_id: input_source_span_id}
375
+ args, kwargs = adapt_inputs.call(args, kwargs, ctx)
376
+ end
355
377
  fn_result = if kwargs.empty?
356
378
  receiver.send(method_name, *args)
357
379
  else
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Bitfab
4
- VERSION = "0.15.0"
4
+ VERSION = "0.16.1"
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.15.0
4
+ version: 0.16.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Harvest Team