bitfab 0.14.0 → 0.15.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 +4 -4
- data/lib/bitfab/client.rb +18 -2
- data/lib/bitfab/replay.rb +74 -11
- data/lib/bitfab/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b0e39c364993d34e99e1d7e3c3a878e0fc6fbfe685fcb0335128bfbf7252161b
|
|
4
|
+
data.tar.gz: 54bcd62faffceac5c0f67f26f5dedbed0057061019d24d1d789f9988fbf0a44a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 04cef22ee4135b8c43e1e35f5ead74f5650d9f49e16b818de05980d01a054b145bf4adbad1c9fc3ec01b0e17130e108df666f75bc8bc9bf4666eb23eb9ba7acc
|
|
7
|
+
data.tar.gz: bb4724aff3d7fc6a9f04e1a923fd5aaabb2e808ef2310ef2c1d51e6a246c296653c1ea85509a92e1a1b927c131074e0907c07623ade5affaa6bab8fd20b2464c
|
data/lib/bitfab/client.rb
CHANGED
|
@@ -183,12 +183,24 @@ module Bitfab
|
|
|
183
183
|
pending << span_thread if span_thread
|
|
184
184
|
pending.each { |t| t.join(5) }
|
|
185
185
|
|
|
186
|
-
send_trace_completion(
|
|
186
|
+
completion_thread = send_trace_completion(
|
|
187
187
|
trace_function_key:,
|
|
188
188
|
trace_id:,
|
|
189
189
|
started_at:,
|
|
190
190
|
ended_at:
|
|
191
191
|
)
|
|
192
|
+
|
|
193
|
+
# In replay, persistence is correctness: the replay runner joins
|
|
194
|
+
# these threads before calling complete_replay, or the server's
|
|
195
|
+
# trace-ID mapping races the uploads and every item's trace_id
|
|
196
|
+
# comes back nil. The 5s join above is best-effort only; this
|
|
197
|
+
# hands the full set (span uploads + trace completion) to the
|
|
198
|
+
# runner. No-op outside replay, where sends stay fire-and-forget.
|
|
199
|
+
persistence = ReplayContext.current&.dig(:pending_persistence)
|
|
200
|
+
if persistence
|
|
201
|
+
persistence.concat(pending)
|
|
202
|
+
persistence << completion_thread if completion_thread
|
|
203
|
+
end
|
|
192
204
|
else
|
|
193
205
|
@pending_span_mutex.synchronize do
|
|
194
206
|
@pending_span_threads[trace_id] << span_thread if span_thread && @pending_span_threads.key?(trace_id)
|
|
@@ -314,10 +326,14 @@ module Bitfab
|
|
|
314
326
|
payload["testRunId"] = trace_state[:test_run_id]
|
|
315
327
|
end
|
|
316
328
|
|
|
317
|
-
@http_client.send_external_trace(payload)
|
|
329
|
+
completion_thread = @http_client.send_external_trace(payload)
|
|
318
330
|
|
|
319
331
|
# Clean up trace state
|
|
320
332
|
TraceState.delete(trace_id)
|
|
333
|
+
|
|
334
|
+
# Returned so the replay path can join it — trace completions must be
|
|
335
|
+
# persisted before complete_replay builds the trace-ID mapping.
|
|
336
|
+
completion_thread
|
|
321
337
|
end
|
|
322
338
|
|
|
323
339
|
def send_span(trace_function_key:, trace_id:, span_id:, parent_span_id:,
|
data/lib/bitfab/replay.rb
CHANGED
|
@@ -22,8 +22,12 @@ module Bitfab
|
|
|
22
22
|
|
|
23
23
|
# Execute a block with replay context set on the current thread.
|
|
24
24
|
# The context is automatically cleared when the block completes.
|
|
25
|
+
#
|
|
26
|
+
# pending_persistence, when given, collects the root span's persistence
|
|
27
|
+
# threads (span uploads + trace completion) so the replay runner can join
|
|
28
|
+
# them before complete_replay builds the trace-ID mapping.
|
|
25
29
|
def with_context(test_run_id:, input_source_span_id: nil, input_source_trace_id: nil, trace_id: nil,
|
|
26
|
-
mock_tree: nil, mock_strategy: nil)
|
|
30
|
+
mock_tree: nil, mock_strategy: nil, pending_persistence: nil)
|
|
27
31
|
previous = Thread.current[REPLAY_CONTEXT_KEY]
|
|
28
32
|
ctx = {
|
|
29
33
|
test_run_id:,
|
|
@@ -31,6 +35,7 @@ module Bitfab
|
|
|
31
35
|
input_source_trace_id:,
|
|
32
36
|
trace_id:
|
|
33
37
|
}
|
|
38
|
+
ctx[:pending_persistence] = pending_persistence if pending_persistence
|
|
34
39
|
if mock_tree
|
|
35
40
|
ctx[:mock_tree] = mock_tree
|
|
36
41
|
ctx[:mock_strategy] = mock_strategy || "none"
|
|
@@ -111,17 +116,63 @@ module Bitfab
|
|
|
111
116
|
[]
|
|
112
117
|
end
|
|
113
118
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
+
# Every item joined its own trace-persistence threads (span uploads +
|
|
120
|
+
# completion) in execute_item, so all replay traces are on the server
|
|
121
|
+
# by now — no flush needed, and complete_replay's trace-ID mapping is
|
|
122
|
+
# deterministic. complete_replay failures propagate: a missing mapping
|
|
123
|
+
# means verdicts can't be persisted, which callers must hear about
|
|
124
|
+
# loudly.
|
|
125
|
+
complete_response = http_client.complete_replay(test_run_id)
|
|
126
|
+
trace_id_map = complete_response&.dig("traceIds")
|
|
127
|
+
|
|
128
|
+
if trace_id_map.nil?
|
|
129
|
+
# Older servers don't return the mapping. Preserve the legacy
|
|
130
|
+
# nil-trace_id behavior but say why.
|
|
131
|
+
warn "Bitfab: server did not return replay trace IDs; item trace_id " \
|
|
132
|
+
"will be nil (server upgrade required for verdict persistence)"
|
|
133
|
+
result_items.each { |item| item[:trace_id] = nil }
|
|
134
|
+
else
|
|
135
|
+
# Map each item's locally-generated trace ID to the server's trace
|
|
136
|
+
# row ID. A completed item with no mapping means its trace was sent
|
|
137
|
+
# but the server has no record — a nil trace_id blocks verdict
|
|
138
|
+
# persistence and the Studio experiments view downstream, so this
|
|
139
|
+
# must never be silent.
|
|
140
|
+
#
|
|
141
|
+
# Severity splits on scope:
|
|
142
|
+
# - ALL completed items missing: systemic (the replayed method is
|
|
143
|
+
# not traced, or uploads are wholesale broken). Raise; the run's
|
|
144
|
+
# results are unusable for persistence.
|
|
145
|
+
# - SOME completed items missing: per-item upload failure (transient
|
|
146
|
+
# network blip, one oversized payload). Nil those items and warn
|
|
147
|
+
# loudly, but return the run so callers can persist verdicts for
|
|
148
|
+
# the items that landed.
|
|
149
|
+
missing = []
|
|
150
|
+
completed_count = 0
|
|
119
151
|
result_items.each do |item|
|
|
120
|
-
|
|
152
|
+
next unless item[:trace_id]
|
|
153
|
+
|
|
154
|
+
mapped = trace_id_map[item[:trace_id]]
|
|
155
|
+
if item[:error].nil?
|
|
156
|
+
completed_count += 1
|
|
157
|
+
missing << item[:trace_id] if mapped.nil?
|
|
158
|
+
end
|
|
159
|
+
item[:trace_id] = mapped
|
|
160
|
+
end
|
|
161
|
+
if missing.any?
|
|
162
|
+
trace_count = complete_response["traceCount"]
|
|
163
|
+
server_count = trace_count.nil? ? "" : " The server persisted #{trace_count} trace(s) for this run."
|
|
164
|
+
if missing.length == completed_count
|
|
165
|
+
raise "Replay completed but the server has no persisted trace for " \
|
|
166
|
+
"any of the #{completed_count} completed item(s) " \
|
|
167
|
+
"(test_run_id #{test_run_id}).#{server_count} Trace uploads were " \
|
|
168
|
+
"joined, so either the uploads failed or the replayed method is " \
|
|
169
|
+
"not traced (no root span was emitted)."
|
|
170
|
+
end
|
|
171
|
+
warn "Bitfab: server has no persisted trace for #{missing.length} of " \
|
|
172
|
+
"#{completed_count} completed replay item(s) " \
|
|
173
|
+
"(test_run_id #{test_run_id}).#{server_count} Their trace_id is nil " \
|
|
174
|
+
"and verdicts cannot be persisted for them. Missing: #{missing.join(", ")}"
|
|
121
175
|
end
|
|
122
|
-
rescue => e
|
|
123
|
-
warn "Bitfab: Failed to complete replay: #{e.message}"
|
|
124
|
-
result_items.each { |item| item[:trace_id] = nil }
|
|
125
176
|
end
|
|
126
177
|
|
|
127
178
|
{
|
|
@@ -286,6 +337,11 @@ module Bitfab
|
|
|
286
337
|
fn_result = nil
|
|
287
338
|
fn_error = nil
|
|
288
339
|
sdk_trace_id = SecureRandom.uuid
|
|
340
|
+
# Collects the root span's persistence threads (span uploads + trace
|
|
341
|
+
# completion). Joined below so this item's trace is on the server
|
|
342
|
+
# before run() calls complete_replay — otherwise the server's trace-ID
|
|
343
|
+
# mapping races the uploads and the item's trace_id comes back nil.
|
|
344
|
+
pending_persistence = []
|
|
289
345
|
|
|
290
346
|
ReplayContext.with_context(
|
|
291
347
|
test_run_id:,
|
|
@@ -293,7 +349,8 @@ module Bitfab
|
|
|
293
349
|
input_source_trace_id:,
|
|
294
350
|
trace_id: sdk_trace_id,
|
|
295
351
|
mock_tree:,
|
|
296
|
-
mock_strategy
|
|
352
|
+
mock_strategy:,
|
|
353
|
+
pending_persistence:
|
|
297
354
|
) do
|
|
298
355
|
fn_result = if kwargs.empty?
|
|
299
356
|
receiver.send(method_name, *args)
|
|
@@ -304,6 +361,12 @@ module Bitfab
|
|
|
304
361
|
fn_error = e.message
|
|
305
362
|
end
|
|
306
363
|
|
|
364
|
+
# Wait for this item's trace (spans + completion) to be fully persisted
|
|
365
|
+
# before the item resolves. Runs on the error path too — a raising
|
|
366
|
+
# method still emits a root span whose trace must land before
|
|
367
|
+
# complete_replay. Joins are bounded by the HTTP layer's own timeouts.
|
|
368
|
+
pending_persistence.each(&:join)
|
|
369
|
+
|
|
307
370
|
{
|
|
308
371
|
input: args,
|
|
309
372
|
result: fn_result,
|
data/lib/bitfab/version.rb
CHANGED