bitfab 0.64.5 → 0.64.7
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/cloudReplay.py +16 -3
- data/lib/bitfab/http_client.rb +3 -1
- data/lib/bitfab/replay.rb +12 -0
- data/lib/bitfab/replay_invocation.rb +113 -0
- data/lib/bitfab/replay_registry.rb +24 -6
- data/lib/bitfab/version.rb +1 -1
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 1f867438c2e7008a72787a1bf61ad853c22964587223e98b73827dfbc0546675
|
|
4
|
+
data.tar.gz: 87130f50d01f7150a5fca9134c5aa444ad31884efbed998aeb3e575d4759d3c4
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 71bf77c48e836cf432734391762cd0fcfa3f0f38efa7451919dc982a00b8d8f2332b34e5b08d980d044d778f8db2fc8ddacf2f88f754d061637b51452fe56e49
|
|
7
|
+
data.tar.gz: 364a28104d7c944b78bae89c93dacf459240cb877197ff9c4fca25f8d98cac82ddc9b8c418e9e208d6d0152e093789665fff7eba6ee177e8ab132c757ec3f8c8
|
data/lib/bitfab/cloudReplay.py
CHANGED
|
@@ -33,6 +33,8 @@ API_VERSION = "2026-03-10"
|
|
|
33
33
|
REPLAY_COMMAND_ENV = "BITFAB_REPLAY_COMMAND"
|
|
34
34
|
SDK_LANGUAGE_ENV = "BITFAB_SDK_LANGUAGE"
|
|
35
35
|
CHECK_COMMAND_ENV = "BITFAB_REPLAY_CHECK"
|
|
36
|
+
EXECUTION_TARGET_ENV = "BITFAB_REPLAY_EXECUTION_TARGET"
|
|
37
|
+
HOSTED_EXECUTION_TARGET = "hosted"
|
|
36
38
|
REQUEST_VERSION = 3
|
|
37
39
|
OLDEST_DISPATCHED_VERSION = 2
|
|
38
40
|
RESULT_LINE = "bitfab-replay-result "
|
|
@@ -169,6 +171,9 @@ fullOutput (true) match --cloud-timeout and --cloud-full-output. version and id
|
|
|
169
171
|
optional: version names the request format (3 now, 2 still accepted), and id is the
|
|
170
172
|
execution UUID, which must match an execution_id input when the workflow has one.
|
|
171
173
|
|
|
174
|
+
The runner's job exits with the replay's exit code, so the Actions run fails when
|
|
175
|
+
the replay does. Traces that errored fail it only under --fail-on-error.
|
|
176
|
+
|
|
172
177
|
Follow a replay by the execution UUID it prints:
|
|
173
178
|
--cloud-watch ID | --cloud-status ID | --cloud-cancel ID | --cloud-cleanup ID
|
|
174
179
|
|
|
@@ -1727,12 +1732,18 @@ def annotate(result):
|
|
|
1727
1732
|
if result.get("stoppedEarly"):
|
|
1728
1733
|
message = f"Replay stopped early: {result['stoppedEarly']}"
|
|
1729
1734
|
elif result["exitCode"]:
|
|
1730
|
-
message = f"Replay exited {result['exitCode']}
|
|
1735
|
+
message = f"Replay exited {result['exitCode']}, so this job exits with it too"
|
|
1731
1736
|
else:
|
|
1732
1737
|
return
|
|
1733
1738
|
print(f"::warning title=Bitfab replay::{message}", flush=True)
|
|
1734
1739
|
|
|
1735
1740
|
|
|
1741
|
+
def runner_exit_code(result):
|
|
1742
|
+
if result.get("stoppedEarly"):
|
|
1743
|
+
return result["exitCode"] or 1
|
|
1744
|
+
return result["exitCode"]
|
|
1745
|
+
|
|
1746
|
+
|
|
1736
1747
|
def summary_markdown(result):
|
|
1737
1748
|
summary = result_summary(result["stdout"]) or {}
|
|
1738
1749
|
counts = summary.get("counts", {})
|
|
@@ -1767,6 +1778,7 @@ def run_command(directory, args, timeout, experiment, *, summarize=True):
|
|
|
1767
1778
|
with subprocess.Popen(
|
|
1768
1779
|
args,
|
|
1769
1780
|
cwd=directory,
|
|
1781
|
+
env={**os.environ, EXECUTION_TARGET_ENV: HOSTED_EXECUTION_TARGET},
|
|
1770
1782
|
stdout=output,
|
|
1771
1783
|
stderr=subprocess.PIPE,
|
|
1772
1784
|
stdin=subprocess.DEVNULL,
|
|
@@ -2200,8 +2212,9 @@ def main():
|
|
|
2200
2212
|
print_json(configure_secrets(argv[1:]))
|
|
2201
2213
|
return 0
|
|
2202
2214
|
if argv == ["--cloud-execute"]:
|
|
2203
|
-
|
|
2204
|
-
|
|
2215
|
+
result = execute()
|
|
2216
|
+
annotate(result)
|
|
2217
|
+
return runner_exit_code(result)
|
|
2205
2218
|
if "-h" in argv or "--help" in argv:
|
|
2206
2219
|
print(HELP, end="")
|
|
2207
2220
|
return 0
|
data/lib/bitfab/http_client.rb
CHANGED
|
@@ -5,6 +5,7 @@ require "json"
|
|
|
5
5
|
require "uri"
|
|
6
6
|
|
|
7
7
|
require_relative "git_state"
|
|
8
|
+
require_relative "replay_invocation"
|
|
8
9
|
require_relative "compress"
|
|
9
10
|
require_relative "connection_pool"
|
|
10
11
|
require_relative "constants"
|
|
@@ -262,7 +263,7 @@ module Bitfab
|
|
|
262
263
|
# same org and trace function or the server rejects the replay
|
|
263
264
|
def start_replay(trace_function_key, limit, trace_ids: nil, code_change_description: nil,
|
|
264
265
|
code_change_files: nil, experiment_group_id: nil, name: nil, notes: nil, metadata: nil, include_db_branch_lease: false, dataset_ids: nil,
|
|
265
|
-
grader_ids: nil, db_branch_settings: nil, attempts: 1, only_with_assertions: false, skip_assertion_judging: false, include_original_metadata: false, experimental_selective_replay: nil, dry_run: false)
|
|
266
|
+
grader_ids: nil, db_branch_settings: nil, attempts: 1, only_with_assertions: false, skip_assertion_judging: false, include_original_metadata: false, experimental_selective_replay: nil, dry_run: false, invocation: nil)
|
|
266
267
|
payload = {
|
|
267
268
|
"traceFunctionKey" => trace_function_key
|
|
268
269
|
}
|
|
@@ -293,6 +294,7 @@ module Bitfab
|
|
|
293
294
|
payload["includeOriginalMetadata"] = true if include_original_metadata
|
|
294
295
|
payload["experimentalSelectiveReplay"] = experimental_selective_replay if experimental_selective_replay
|
|
295
296
|
payload["dbBranchSettings"] = db_branch_settings unless db_branch_settings.nil?
|
|
297
|
+
payload["invocation"] = invocation unless invocation.nil?
|
|
296
298
|
git_state = GitState.resolved
|
|
297
299
|
payload["git"] = git_state if git_state
|
|
298
300
|
|
data/lib/bitfab/replay.rb
CHANGED
|
@@ -8,6 +8,7 @@ require "timeout"
|
|
|
8
8
|
require_relative "constants"
|
|
9
9
|
require_relative "git_command"
|
|
10
10
|
require_relative "mock_override"
|
|
11
|
+
require_relative "replay_invocation"
|
|
11
12
|
require_relative "serialize"
|
|
12
13
|
require_relative "selective_replay"
|
|
13
14
|
require_relative "traceable"
|
|
@@ -142,6 +143,8 @@ module Bitfab
|
|
|
142
143
|
# Replay historical traces through a traced method and create an experiment.
|
|
143
144
|
module Replay
|
|
144
145
|
CODE_CHANGE_UNSET = Object.new.freeze
|
|
146
|
+
UNSET_DEFAULT = Object.new.freeze
|
|
147
|
+
OPTION_DEFAULTS = {mock: "marked"}.freeze
|
|
145
148
|
PERSISTENCE_TIMEOUT_SECONDS = 30.0
|
|
146
149
|
PERSISTENCE_POLL_SECONDS = 0.1
|
|
147
150
|
JUDGE_ASSERTIONS_DEPRECATION = "judge_assertions is deprecated. Assertion judging is on by default; " \
|
|
@@ -193,6 +196,13 @@ module Bitfab
|
|
|
193
196
|
raise error, cause:
|
|
194
197
|
end
|
|
195
198
|
|
|
199
|
+
def explicit_replay_options(options)
|
|
200
|
+
options.reject do |option, value|
|
|
201
|
+
value.nil? || value == false || value.equal?(ReplayConcurrency::UNSET) ||
|
|
202
|
+
value.equal?(CODE_CHANGE_UNSET) || value == OPTION_DEFAULTS.fetch(option, UNSET_DEFAULT)
|
|
203
|
+
end
|
|
204
|
+
end
|
|
205
|
+
|
|
196
206
|
# Replay historical traces through a method and create an experiment.
|
|
197
207
|
#
|
|
198
208
|
# Fetches the last N traces for the given trace function key, re-runs each
|
|
@@ -289,6 +299,7 @@ module Bitfab
|
|
|
289
299
|
dataset_id: nil, dataset_ids: nil, grader_ids: nil, mock: "marked", attempts: ReplayConcurrency::UNSET, only_with_assertions: false, skip_assertion_judging: false, judge_assertions: nil, dry_run: false,
|
|
290
300
|
adapt_inputs: nil, mock_override: nil, db_branch: nil, on_item_start: nil, on_item_finish: nil, on_progress: nil,
|
|
291
301
|
on_experiment_start: nil, experimental_selective_replay: nil)
|
|
302
|
+
explicit_options = method(:run).parameters.filter_map { |type, option| [option, binding.local_variable_get(option)] if type == :key }
|
|
292
303
|
skip_assertion_judging = resolve_skip_assertion_judging(skip_assertion_judging, judge_assertions)
|
|
293
304
|
concurrency_options = ReplayConcurrency.resolve(concurrency, attempts, max_concurrency)
|
|
294
305
|
attempts = concurrency_options.attempts
|
|
@@ -396,6 +407,7 @@ module Bitfab
|
|
|
396
407
|
include_original_metadata: !adapt_inputs.nil?,
|
|
397
408
|
db_branch_settings: resolved_db_branch_settings,
|
|
398
409
|
dry_run:,
|
|
410
|
+
invocation: ReplayInvocation.safe_build { ReplayInvocation.build(ReplayInvocation.current_cli_flags || ReplayInvocation.option_flag_entries(explicit_replay_options(explicit_options))) },
|
|
399
411
|
**(experimental_selective_replay ? {experimental_selective_replay: SelectiveReplay.wire_options(experimental_selective_replay)} : {})
|
|
400
412
|
)
|
|
401
413
|
experiment_id = replay_data["experimentId"] || replay_data["testRunId"]
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "version"
|
|
4
|
+
|
|
5
|
+
module Bitfab
|
|
6
|
+
module ReplayInvocation
|
|
7
|
+
EXECUTION_TARGET_ENV = "BITFAB_REPLAY_EXECUTION_TARGET"
|
|
8
|
+
HOSTED_EXECUTION_TARGET = "hosted"
|
|
9
|
+
CI_ENV = "CI"
|
|
10
|
+
CI_TRUE_VALUES = %w[true 1].freeze
|
|
11
|
+
CI_PRESENCE_ENV_VARS = %w[GITHUB_ACTIONS GITLAB_CI CIRCLECI BUILDKITE JENKINS_URL TF_BUILD].freeze
|
|
12
|
+
MAX_FLAGS = 100
|
|
13
|
+
MAX_FLAG_NAME_LENGTH = 100
|
|
14
|
+
MAX_FLAG_STRING_LENGTH = 1_000
|
|
15
|
+
MAX_FLAG_LIST_LENGTH = 100
|
|
16
|
+
SECRET_FLAG_NAME = /key|token|secret|password/i
|
|
17
|
+
CLI_FLAGS_KEY = :bitfab_replay_cli_flags
|
|
18
|
+
UNSUPPORTED = Object.new.freeze
|
|
19
|
+
METADATA_FLAG = "metadata"
|
|
20
|
+
OPTION_FLAG_NAMES = {
|
|
21
|
+
max_concurrency: "concurrency",
|
|
22
|
+
dataset_id: "dataset-ids",
|
|
23
|
+
dataset_ids: "dataset-ids"
|
|
24
|
+
}.freeze
|
|
25
|
+
|
|
26
|
+
module_function
|
|
27
|
+
|
|
28
|
+
def environment
|
|
29
|
+
return "cloud_replay" if ENV[EXECUTION_TARGET_ENV] == HOSTED_EXECUTION_TARGET
|
|
30
|
+
return "ci" if CI_TRUE_VALUES.include?(ENV.fetch(CI_ENV, "").downcase)
|
|
31
|
+
return "ci" if CI_PRESENCE_ENV_VARS.any? { |name| !ENV.fetch(name, "").empty? }
|
|
32
|
+
|
|
33
|
+
"local"
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def flag_scalar(value)
|
|
37
|
+
case value
|
|
38
|
+
when String then value[0, MAX_FLAG_STRING_LENGTH]
|
|
39
|
+
when true, false, nil then value
|
|
40
|
+
when Integer then value
|
|
41
|
+
when Float then value.finite? ? value : UNSUPPORTED
|
|
42
|
+
when Symbol then value.to_s[0, MAX_FLAG_STRING_LENGTH]
|
|
43
|
+
else UNSUPPORTED
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def flag_value(value)
|
|
48
|
+
if value.is_a?(Array)
|
|
49
|
+
items = value.first(MAX_FLAG_LIST_LENGTH).map { |item| flag_scalar(item) }
|
|
50
|
+
return (items.any? { |item| item.equal?(UNSUPPORTED) }) ? true : items
|
|
51
|
+
end
|
|
52
|
+
scalar = flag_scalar(value)
|
|
53
|
+
scalar.equal?(UNSUPPORTED) || scalar
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def option_flag_name(option)
|
|
57
|
+
OPTION_FLAG_NAMES.fetch(option.to_sym) { option.to_s.tr("_", "-") }
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def option_flag_entries(options)
|
|
61
|
+
mapped, rest = options.to_a.partition { |option, _value| OPTION_FLAG_NAMES.key?(option.to_sym) }
|
|
62
|
+
(mapped + rest).map { |option, value| [option_flag_name(option), value] }
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def metadata_keys(value)
|
|
66
|
+
keys = case value
|
|
67
|
+
when Hash then value.keys
|
|
68
|
+
when Array then value.map { |item| item.is_a?(Array) ? item.first : item.to_s.split("=", 2).first }
|
|
69
|
+
else return true
|
|
70
|
+
end
|
|
71
|
+
keys.map(&:to_s).uniq.sort
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def flags(entries)
|
|
75
|
+
result = {}
|
|
76
|
+
entries.each do |name, value|
|
|
77
|
+
break if result.length >= MAX_FLAGS
|
|
78
|
+
|
|
79
|
+
name = name.to_s
|
|
80
|
+
next if name.empty? || result.key?(name) || name.length > MAX_FLAG_NAME_LENGTH || SECRET_FLAG_NAME.match?(name)
|
|
81
|
+
|
|
82
|
+
result[name] = flag_value((name == METADATA_FLAG) ? metadata_keys(value) : value)
|
|
83
|
+
end
|
|
84
|
+
result
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def build(entries = {})
|
|
88
|
+
{
|
|
89
|
+
"flags" => flags(entries),
|
|
90
|
+
"sdk" => {"language" => "ruby", "version" => VERSION},
|
|
91
|
+
"environment" => environment
|
|
92
|
+
}
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def safe_build
|
|
96
|
+
yield
|
|
97
|
+
rescue
|
|
98
|
+
nil
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def current_cli_flags
|
|
102
|
+
Thread.current[CLI_FLAGS_KEY]
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def with_cli_flags(cli_flags)
|
|
106
|
+
previous = Thread.current[CLI_FLAGS_KEY]
|
|
107
|
+
Thread.current[CLI_FLAGS_KEY] = cli_flags
|
|
108
|
+
yield
|
|
109
|
+
ensure
|
|
110
|
+
Thread.current[CLI_FLAGS_KEY] = previous
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
end
|
|
@@ -88,6 +88,7 @@ module Bitfab
|
|
|
88
88
|
# Argument parsing and output for the SDK-owned replay command.
|
|
89
89
|
module ReplayCli
|
|
90
90
|
MAX_PINNED_TRACE_IDS = 100
|
|
91
|
+
CLI_INVOCATION_SKIPPED = %i[pipeline seed run from_trace].freeze
|
|
91
92
|
|
|
92
93
|
class ItemsErroredError < StandardError; end
|
|
93
94
|
|
|
@@ -247,12 +248,14 @@ module Bitfab
|
|
|
247
248
|
stderr.puts "[replay] Experiment #{experiment[:experiment_id]}: #{experiment[:experiment_url]}"
|
|
248
249
|
end
|
|
249
250
|
result = begin
|
|
250
|
-
|
|
251
|
-
registration.
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
251
|
+
ReplayInvocation.with_cli_flags(cli_invocation_flags(args)) do
|
|
252
|
+
registration.client.replay(
|
|
253
|
+
registration.receiver,
|
|
254
|
+
registration.method_name,
|
|
255
|
+
trace_function_key: registration.trace_function_key,
|
|
256
|
+
**options
|
|
257
|
+
)
|
|
258
|
+
end
|
|
256
259
|
rescue ReplayError => error
|
|
257
260
|
error.items.each do |item|
|
|
258
261
|
item_error = item[:trace_error] || item[:replay_error] || item[:error]
|
|
@@ -273,6 +276,21 @@ module Bitfab
|
|
|
273
276
|
result
|
|
274
277
|
end
|
|
275
278
|
|
|
279
|
+
def cli_invocation_flags(args)
|
|
280
|
+
args.except(*CLI_INVOCATION_SKIPPED).each_with_object({}) do |(key, value), flags|
|
|
281
|
+
case key
|
|
282
|
+
when :param
|
|
283
|
+
flags["param"] = value.map { |raw| raw.split("=", 2).first }
|
|
284
|
+
when :max_concurrency
|
|
285
|
+
flags["concurrency"] = value
|
|
286
|
+
when :db_branch, :memory_throttle
|
|
287
|
+
flags["#{"no-" unless value}#{key.to_s.tr("_", "-")}"] = true
|
|
288
|
+
else
|
|
289
|
+
flags[key.to_s.tr("_", "-")] = value
|
|
290
|
+
end
|
|
291
|
+
end
|
|
292
|
+
end
|
|
293
|
+
|
|
276
294
|
def fail_if_items_errored(result, dry_run:)
|
|
277
295
|
items = result[:items].reject { |item| item[:carried_over] }
|
|
278
296
|
errored = items.count { |item| item[:error] }
|
data/lib/bitfab/version.rb
CHANGED
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.64.
|
|
4
|
+
version: 0.64.7
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Harvest Team
|
|
@@ -184,6 +184,7 @@ files:
|
|
|
184
184
|
- lib/bitfab/replay_branch.rb
|
|
185
185
|
- lib/bitfab/replay_cli.rb
|
|
186
186
|
- lib/bitfab/replay_concurrency.rb
|
|
187
|
+
- lib/bitfab/replay_invocation.rb
|
|
187
188
|
- lib/bitfab/replay_memory.rb
|
|
188
189
|
- lib/bitfab/replay_processes.rb
|
|
189
190
|
- lib/bitfab/replay_registry.rb
|