bitfab 0.38.1 → 0.38.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: 2816ce4e3bcd11bb1335ba7dac6b36376277d17075cf853d26c9befa231a758b
4
- data.tar.gz: 7e923c2efb8dfd38edbdd592972a8f236a3fa81cdfabcbd46b8303ba4160b01e
3
+ metadata.gz: 28a82b98d8327aead543061c18b95a22b2100f89e0289ffdd6ad85a262e08447
4
+ data.tar.gz: 18c101e18d509cc2a5fb880210d3d5cfaee0f9311c7521abeaa2716bdeca563f
5
5
  SHA512:
6
- metadata.gz: 15a2df82260f61733b90da3c110b195c92373c187c3eddce5faa041f44499bb008ec5a14cd27dab22b63e2973540010cc2e2f10fcc2dcd75481405d018a107f6
7
- data.tar.gz: 13968f6a9f8d3c88ea2845d0d59e0cc1d31ffadc1054311addeedc1e4ba92d94aea504f0f5d11e9891fd0a233e92d285e635e5d8e85e46ebfdf553cdedc45005
6
+ metadata.gz: 960e4f390e26e953cb555a1a359fd7e3b505172a74e513fe69f281dc7e1111b03e8ac93451154d632dd225e36cdf33f5c54dc3dc59dbd7379a7af3672f7fb87b
7
+ data.tar.gz: ac599caf319a0dc2814147faee28efd5940609bdc2200d696db4bcb3581fc72fafe443bb50f65204ffb46eea5fa8f0ff9806dd78c42a8d3599e1429263d2ab71
data/lib/bitfab/client.rb CHANGED
@@ -8,6 +8,7 @@ require_relative "http_client"
8
8
  require_relative "replay"
9
9
  require_relative "span_context"
10
10
  require_relative "serialize"
11
+ require_relative "timestamp"
11
12
  require_relative "warn_once"
12
13
 
13
14
  module Bitfab
@@ -122,14 +123,15 @@ module Bitfab
122
123
  # traces were captured. Receives (args, kwargs, ctx) where ctx is
123
124
  # { original_trace_id:, original_span_id: } (with deprecated
124
125
  # source_trace_id/source_span_id aliases), and returns [new_args, new_kwargs].
125
- # @param mock_override [Hash, Array<Hash>, nil] optional selective mock
126
- # override(s), each a { match:, value: } hash. match is a callable:
126
+ # @param mock_override [Hash, Proc, Array<Hash, Proc>, nil] optional selective
127
+ # mock override(s), as { match:, value: } hashes or global resolvers. match is a callable:
127
128
  # match.call(node) selects spans to substitute (node is
128
129
  # { trace_function_key:, span_name:, type:, original_span_id: }). value is
129
130
  # EITHER a flat value injected directly OR a callable invoked with
130
131
  # { node:, inputs:, kwargs:, get_original_output: }; its result becomes
131
132
  # the span's output (full replacement), so downstream real code runs against it. The
132
- # first matching override wins. Per-call overrides take precedence over
133
+ # first matching override that does not return NO_MOCK_OVERRIDE wins.
134
+ # Per-call overrides take precedence over
133
135
  # those registered via register_mock_override, and both take precedence
134
136
  # over the base mock strategy. The root span is never overridden.
135
137
  # @param db_branch [Boolean, Hash, nil] +true+ or a Hash enables database
@@ -169,10 +171,9 @@ module Bitfab
169
171
  # clear_mock_overrides to reset. Per-call replay(mock_override:) overrides
170
172
  # take precedence, and both take precedence over the base mock strategy.
171
173
  #
172
- # Accepts either the (match, value) positional form or the keyword form
173
- # (a { match:, value: } hash is also accepted). `match` must be callable;
174
- # `value` is either a flat value injected directly or a callable invoked
175
- # with the ctx hash.
174
+ # Accepts the (match, value) positional form, the keyword/hash form, one
175
+ # global resolver callable, or (trace_function_key, override_or_resolver).
176
+ # A resolver can return Bitfab::NO_MOCK_OVERRIDE to decline the span.
176
177
  #
177
178
  # @example keyword form (primary), flat value
178
179
  # client.register_mock_override(
@@ -186,19 +187,43 @@ module Bitfab
186
187
  # ->(ctx) { {label: "refund", inputs: ctx[:inputs]} }
187
188
  # )
188
189
  #
189
- # @param positional [Array] either (match, value) or a single
190
- # { match:, value: } hash
190
+ # @param positional [Array] a (match, value) pair, one { match:, value: }
191
+ # hash, one global resolver, or a trace function key followed by a
192
+ # resolver or override hash
191
193
  # @param match [#call, nil] keyword form matcher
192
194
  # @param value [Object, #call, nil] keyword form injected value or producer
193
195
  # @return [void]
194
196
  def register_mock_override(*positional, match: nil, value: VALUE_UNSET)
195
197
  if match.nil? && value.equal?(VALUE_UNSET)
196
- if positional.length == 1 && positional[0].is_a?(Hash)
198
+ if positional.length == 2 && positional[0].is_a?(String)
199
+ trace_function_key, keyed_override = positional
200
+ if keyed_override.is_a?(Hash) && keyed_override[:match].respond_to?(:call) && keyed_override.key?(:value)
201
+ keyed_match = keyed_override[:match]
202
+ @mock_overrides << {
203
+ match: ->(node) { node[:trace_function_key] == trace_function_key && keyed_match.call(node) },
204
+ value: keyed_override[:value]
205
+ }
206
+ return nil
207
+ end
208
+ if keyed_override.respond_to?(:call)
209
+ @mock_overrides << {
210
+ match: ->(node) { node[:trace_function_key] == trace_function_key },
211
+ value: keyed_override
212
+ }
213
+ return nil
214
+ end
215
+ raise ArgumentError,
216
+ "register_mock_override(trace_function_key, override) requires " \
217
+ "a { match:, value: } hash or callable resolver."
218
+ elsif positional.length == 1 && positional[0].is_a?(Hash)
197
219
  override = positional[0]
198
220
  match = override[:match]
199
221
  # Distinguish an omitted :value from an explicit `value: nil`: the
200
222
  # key's presence, not a nil read, is what marks it provided.
201
223
  value = override.key?(:value) ? override[:value] : VALUE_UNSET
224
+ elsif positional.length == 1 && positional[0].respond_to?(:call)
225
+ @mock_overrides << {match: ->(_) { true }, value: positional[0]}
226
+ return nil
202
227
  elsif positional.length == 2
203
228
  match, value = positional
204
229
  end
@@ -309,7 +334,7 @@ module Bitfab
309
334
  span_id = SecureRandom.uuid
310
335
  parent_span_id = parent&.dig(:span_id)
311
336
  is_root_span = parent_span_id.nil?
312
- started_at = Time.now.utc.strftime("%Y-%m-%dT%H:%M:%S.%3NZ")
337
+ started_at = Bitfab.now_iso_timestamp
313
338
  resolved_test_run_id = replay_ctx&.dig(:test_run_id)
314
339
  resolved_input_source_span_id = replay_ctx&.dig(:input_source_span_id)
315
340
  resolved_input_source_trace_id = replay_ctx&.dig(:input_source_trace_id)
@@ -388,7 +413,7 @@ module Bitfab
388
413
  finalized = true
389
414
 
390
415
  begin
391
- ended_at = Time.now.utc.strftime("%Y-%m-%dT%H:%M:%S.%3NZ")
416
+ ended_at = Bitfab.now_iso_timestamp
392
417
 
393
418
  send_span(
394
419
  trace_function_key:,
@@ -782,8 +807,9 @@ module Bitfab
782
807
  type: span_type,
783
808
  original_span_id: mock_entry && mock_entry[:source_span_id]
784
809
  }
785
- override = overrides.find { |o| o[:match].call(node) }
786
- if override
810
+ overrides.each do |override|
811
+ next unless override[:match].call(node)
812
+
787
813
  value = override[:value]
788
814
  # value is EITHER a flat value (injected directly) OR a callable
789
815
  # invoked with the ctx hash. The result IS the span's output (full
@@ -791,7 +817,10 @@ module Bitfab
791
817
  # override always injects, execute_span short-circuits even when this
792
818
  # is nil (nil != MOCK_REPLAY_MISS). To inject a proc as the literal
793
819
  # output, wrap it in a callable value.
794
- return [value, "override"] unless value.respond_to?(:call)
820
+ unless value.respond_to?(:call)
821
+ return [value, "override"] unless value.equal?(NO_MOCK_OVERRIDE)
822
+ next
823
+ end
795
824
 
796
825
  get_original_output = lambda do
797
826
  unless mock_entry
@@ -799,7 +828,8 @@ module Bitfab
799
828
  end
800
829
  resolve_recorded_output(mock_entry, replay_ctx)
801
830
  end
802
- return [value.call({node:, inputs: args, kwargs:, get_original_output:}), "override"]
831
+ resolved = value.call({node:, inputs: args, kwargs:, get_original_output:})
832
+ return [resolved, "override"] unless resolved.equal?(NO_MOCK_OVERRIDE)
803
833
  end
804
834
  end
805
835
 
@@ -860,7 +890,7 @@ module Bitfab
860
890
  def send_mocked_span(trace_function_key:, trace_id:, span_id:, parent_span_id:,
861
891
  span_name:, span_type:, function_name:, args:, kwargs:, mocked_output:,
862
892
  started_at:, test_run_id:, input_source_span_id:, mock_source:)
863
- ended_at = Time.now.utc.strftime("%Y-%m-%dT%H:%M:%S.%3NZ")
893
+ ended_at = Bitfab.now_iso_timestamp
864
894
  send_span(
865
895
  trace_function_key:,
866
896
  trace_id:,
@@ -1,6 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Bitfab
4
+ # Return from an override resolver to continue to the next override and then
5
+ # the replay's base mock strategy.
6
+ NO_MOCK_OVERRIDE = Object.new.freeze
7
+
4
8
  # Selective mock overrides for replay. Mirrors the TypeScript SDK's
5
9
  # mockOverride feature.
6
10
  #
@@ -10,8 +14,10 @@ module Bitfab
10
14
  # substituted output. This is a third mock mode alongside "run real code" and
11
15
  # "replay recorded output" (see MOCK_STRATEGIES in replay.rb).
12
16
  #
13
- # An override is a (match, value) pair, represented as a symbol-keyed hash
14
- # { match:, value: }:
17
+ # An override is a (match, value) pair represented as { match:, value: }, or
18
+ # one global resolver invoked for every child span. A resolver can route on
19
+ # ctx[:node][:trace_function_key] and return Bitfab::NO_MOCK_OVERRIDE to
20
+ # decline the current span.
15
21
  #
16
22
  # - match is a callable (proc/lambda): match.call(node) -> boolean. node is a
17
23
  # structural-metadata hash { trace_function_key:, span_name:, type:,
@@ -38,9 +44,8 @@ module Bitfab
38
44
  module MockOverride
39
45
  module_function
40
46
 
41
- # Normalize the per-call `mock_override` option (a single override hash, an
42
- # array of them, or nil) into an array. First match wins downstream, so
43
- # order is preserved.
47
+ # Normalize the per-call `mock_override` option (an override hash, a global
48
+ # resolver callable, an array, or nil) into an ordered override array.
44
49
  #
45
50
  # Each override is validated: it must be a hash with a callable `:match` and
46
51
  # must include a `:value` key. A forgotten `:value` raises rather than
@@ -49,12 +54,15 @@ module Bitfab
49
54
  # `register_mock_override`, and the Python/TypeScript SDKs, where the per-call
50
55
  # override object requires a value.
51
56
  #
52
- # @param mock_override [Hash, Array<Hash>, nil]
57
+ # @param mock_override [Hash, Proc, Array<Hash, Proc>, nil]
53
58
  # @return [Array<Hash>]
54
59
  def normalize(mock_override)
55
60
  return [] if mock_override.nil?
56
61
 
57
62
  overrides = mock_override.is_a?(Array) ? mock_override : [mock_override]
63
+ overrides = overrides.map do |override|
64
+ override.respond_to?(:call) ? {match: ->(_) { true }, value: override} : override
65
+ end
58
66
  overrides.each do |override|
59
67
  unless override.is_a?(Hash) && override[:match].respond_to?(:call)
60
68
  raise ArgumentError,
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative "timestamp"
4
+
3
5
  module Bitfab
4
6
  # Handle to the current active span, allowing context to be added.
5
7
  class CurrentSpan
@@ -171,7 +173,7 @@ module Bitfab
171
173
  def create(trace_id, test_run_id: nil, input_source_trace_id: nil)
172
174
  @states_mutex.synchronize do
173
175
  @states[trace_id] ||= begin
174
- started_at = Time.now.utc.strftime("%Y-%m-%dT%H:%M:%S.%3NZ")
176
+ started_at = Bitfab.now_iso_timestamp
175
177
  {
176
178
  trace_id:,
177
179
  started_at:,
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "time"
4
+
5
+ module Bitfab
6
+ @timestamp_mutex = Mutex.new
7
+ @last_timestamp_us = 0
8
+
9
+ def self.now_iso_timestamp
10
+ wall_clock_us = (Time.now.to_r * 1_000_000).to_i
11
+ timestamp_us = @timestamp_mutex.synchronize do
12
+ @last_timestamp_us = [wall_clock_us, @last_timestamp_us + 1].max
13
+ end
14
+ Time.at(Rational(timestamp_us, 1_000_000)).utc.strftime("%Y-%m-%dT%H:%M:%S.%6NZ")
15
+ end
16
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Bitfab
4
- VERSION = "0.38.1"
4
+ VERSION = "0.38.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.38.1
4
+ version: 0.38.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Harvest Team
@@ -153,6 +153,7 @@ files:
153
153
  - lib/bitfab/replay_registry.rb
154
154
  - lib/bitfab/serialize.rb
155
155
  - lib/bitfab/span_context.rb
156
+ - lib/bitfab/timestamp.rb
156
157
  - lib/bitfab/traceable.rb
157
158
  - lib/bitfab/transport.rb
158
159
  - lib/bitfab/version.rb