bitfab 0.68.0 → 0.69.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 +19 -10
- data/lib/bitfab/cloudReplay.py +49 -27
- data/lib/bitfab/replay.rb +72 -70
- data/lib/bitfab/replay_cli.rb +2 -1
- data/lib/bitfab/replay_registry.rb +31 -13
- 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: 7c5044f5cc55988582b4a4428242ac2c0ea12fb87ba228054ad7c01a08e760ca
|
|
4
|
+
data.tar.gz: f410e0a0560c11fd3185c4f237bb30d37ac16ed5dcb3c0d4beb2d08d5b62f863
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 680f7e7b023a94951496fba7b6b2aef5bb61f4250d3a3cfa5d2db3dae7a91038a0c3f03474419eb65e9defa0fc0c3811d7ee4346a4d9b50c15f2fa29a3bda703
|
|
7
|
+
data.tar.gz: b6230d97bb985ad5748e7e45121f89fdc4cbf65861eaa7ac815bd028bd34d43774a36f4ab3941b54e830121c7c097e2ee98da013834d783a69147a7b3a4dc570
|
data/lib/bitfab/client.rb
CHANGED
|
@@ -140,15 +140,23 @@ module Bitfab
|
|
|
140
140
|
# such as an environment override or a forced feature flag. Kept on the
|
|
141
141
|
# experiment next to its name (max 10,000 characters).
|
|
142
142
|
# @param max_concurrency [Integer, nil] max threads for parallel replay (default: 10)
|
|
143
|
-
# @param
|
|
144
|
-
#
|
|
145
|
-
#
|
|
146
|
-
# the
|
|
147
|
-
#
|
|
148
|
-
#
|
|
149
|
-
#
|
|
150
|
-
#
|
|
151
|
-
#
|
|
143
|
+
# @param code_change [String] required. Which code change this replay
|
|
144
|
+
# tests, stored on the experiment so it can be reviewed alongside the
|
|
145
|
+
# results. A replay without it raises ArgumentError before any request.
|
|
146
|
+
# "working-tree" is the uncommitted changes vs HEAD; "last-branch" the
|
|
147
|
+
# diff against the branch this one was cut from, such as the branch below
|
|
148
|
+
# it in a stack, else the default branch; "default-branch" the diff
|
|
149
|
+
# against where HEAD split from the default branch (origin/HEAD, else
|
|
150
|
+
# origin/main, origin/master, main, master); "skip" no code change, such
|
|
151
|
+
# as for a baseline run; "manual" the files in code_change_files. The
|
|
152
|
+
# three git choices include uncommitted edits and untracked files, and
|
|
153
|
+
# warn and attach nothing when they find no changed files.
|
|
154
|
+
# @param code_change_description [String, nil] description of the code
|
|
155
|
+
# change, stored on the experiment. Defaults to one generated from the
|
|
156
|
+
# captured files. Not accepted with "skip".
|
|
157
|
+
# @param code_change_files [Array<Hash>, nil] the edited files when
|
|
158
|
+
# code_change is "manual", and only then, each as { path:, before:, after: }
|
|
159
|
+
# (use "" for new/deleted files).
|
|
152
160
|
# @param experiment_group_id [String, nil] optional UUID grouping multiple
|
|
153
161
|
# replay runs into a single experiment batch. The run joins that group,
|
|
154
162
|
# and an id no group has yet creates it
|
|
@@ -214,7 +222,7 @@ module Bitfab
|
|
|
214
222
|
# @return [Hash] with :items, :experiment_id, :experiment_url, and the
|
|
215
223
|
# deprecated aliases :test_run_id and :test_run_url carrying the same values
|
|
216
224
|
def replay(receiver, method_name = nil, trace_function_key:, limit: nil, trace_ids: nil, concurrency: nil, max_concurrency: ReplayConcurrency::UNSET,
|
|
217
|
-
name: nil, notes: nil, metadata: nil, code_change_description:
|
|
225
|
+
name: nil, notes: nil, metadata: nil, code_change: nil, code_change_description: nil, code_change_files: nil, experiment_group_id: nil, experiment_group_name: nil, 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,
|
|
218
226
|
adapt_inputs: nil, mock_override: nil, db_branch: nil, on_item_start: nil, on_item_finish: nil, on_progress: nil,
|
|
219
227
|
on_experiment_start: nil, experimental_selective_replay: nil)
|
|
220
228
|
Replay.run(
|
|
@@ -234,6 +242,7 @@ module Bitfab
|
|
|
234
242
|
skip_assertion_judging:,
|
|
235
243
|
judge_assertions:,
|
|
236
244
|
dry_run:,
|
|
245
|
+
code_change:,
|
|
237
246
|
code_change_description:,
|
|
238
247
|
code_change_files:,
|
|
239
248
|
experiment_group_id:,
|
data/lib/bitfab/cloudReplay.py
CHANGED
|
@@ -147,8 +147,9 @@ RENAMED_FLAGS = {
|
|
|
147
147
|
"--cloud-include": "nothing: new files that git does not ignore are already in the snapshot",
|
|
148
148
|
}
|
|
149
149
|
SEED_FLAGS = ("--seed", "--cases", "--from-trace", "--run")
|
|
150
|
-
PATH_FLAGS = ("--registry", "--params", "--code-change")
|
|
151
|
-
|
|
150
|
+
PATH_FLAGS = ("--registry", "--params", "--code-change-file")
|
|
151
|
+
CODE_CHANGE_FLAG = "--code-change"
|
|
152
|
+
BRANCH_CODE_CHANGES = ("last-branch", "default-branch")
|
|
152
153
|
CHECKOUT_DEPTH = 2
|
|
153
154
|
CODE_CHANGE_PATH_ENV = "BITFAB_CODE_CHANGE_PATH"
|
|
154
155
|
CODE_CHANGE_MAX_FILES = 60
|
|
@@ -1022,6 +1023,21 @@ def branch_point(root):
|
|
|
1022
1023
|
return None
|
|
1023
1024
|
|
|
1024
1025
|
|
|
1026
|
+
def default_branch_point(root):
|
|
1027
|
+
default = default_branch(root)
|
|
1028
|
+
sha = quiet_git(root, "merge-base", "HEAD", default) if default else ""
|
|
1029
|
+
return {"sha": sha, "ref": default} if sha else None
|
|
1030
|
+
|
|
1031
|
+
|
|
1032
|
+
def code_change_base(root, replay):
|
|
1033
|
+
kind = flag_value(replay, CODE_CHANGE_FLAG)
|
|
1034
|
+
if kind == "last-branch":
|
|
1035
|
+
return branch_point(root)
|
|
1036
|
+
if kind == "default-branch":
|
|
1037
|
+
return default_branch_point(root)
|
|
1038
|
+
return None
|
|
1039
|
+
|
|
1040
|
+
|
|
1025
1041
|
def replay_artifact(path):
|
|
1026
1042
|
return path == ".bitfab" or path.startswith(".bitfab/")
|
|
1027
1043
|
|
|
@@ -1126,11 +1142,10 @@ def snapshot(
|
|
|
1126
1142
|
}
|
|
1127
1143
|
if preview:
|
|
1128
1144
|
return summary
|
|
1129
|
-
|
|
1145
|
+
branch_base = str((request.get("codeChangeBase") or {}).get("sha", ""))
|
|
1130
1146
|
extra_parent = (
|
|
1131
|
-
["-p",
|
|
1132
|
-
if
|
|
1133
|
-
and SHA.fullmatch(str(code_change_base.get("sha", "")))
|
|
1147
|
+
["-p", branch_base]
|
|
1148
|
+
if SHA.fullmatch(branch_base) and branch_base != head
|
|
1134
1149
|
else []
|
|
1135
1150
|
)
|
|
1136
1151
|
sha = git(
|
|
@@ -1461,9 +1476,9 @@ def submit(root, repo, workflow, parsed, path):
|
|
|
1461
1476
|
branch = current_branch(root)
|
|
1462
1477
|
if branch:
|
|
1463
1478
|
request["branch"] = branch
|
|
1464
|
-
|
|
1465
|
-
if
|
|
1466
|
-
request["codeChangeBase"] =
|
|
1479
|
+
base = code_change_base(root, parsed["replay"])
|
|
1480
|
+
if base:
|
|
1481
|
+
request["codeChangeBase"] = base
|
|
1467
1482
|
check_arguments(parsed["replay"])
|
|
1468
1483
|
allowed = allowed_files(root, parsed)
|
|
1469
1484
|
if "--cloud-preview" in parsed["switches"]:
|
|
@@ -2121,15 +2136,19 @@ def check_out_snapshot_base(root, commit, branch):
|
|
|
2121
2136
|
return base
|
|
2122
2137
|
|
|
2123
2138
|
|
|
2124
|
-
def
|
|
2139
|
+
def flag_value(args, flag):
|
|
2125
2140
|
for index, value in enumerate(args):
|
|
2126
|
-
if value.startswith("
|
|
2141
|
+
if value.startswith(f"{flag}="):
|
|
2127
2142
|
return value.partition("=")[2]
|
|
2128
|
-
if value ==
|
|
2143
|
+
if value == flag and index + 1 < len(args):
|
|
2129
2144
|
return args[index + 1]
|
|
2130
2145
|
return None
|
|
2131
2146
|
|
|
2132
2147
|
|
|
2148
|
+
def replay_name(args):
|
|
2149
|
+
return flag_value(args, "--name")
|
|
2150
|
+
|
|
2151
|
+
|
|
2133
2152
|
def git_blob(root, ref, path):
|
|
2134
2153
|
try:
|
|
2135
2154
|
size = int(git(root, "cat-file", "-s", f"{ref}:{path}"))
|
|
@@ -2209,7 +2228,10 @@ def branch_code_change(root, base, commit, request):
|
|
|
2209
2228
|
and SHA.fullmatch(sha)
|
|
2210
2229
|
and isinstance(ref, str)
|
|
2211
2230
|
and BRANCH_NAME.fullmatch(ref)
|
|
2212
|
-
and
|
|
2231
|
+
and (
|
|
2232
|
+
sha == base
|
|
2233
|
+
or quiet_git(root, "rev-parse", "--verify", "-q", f"{commit}^2") == sha
|
|
2234
|
+
)
|
|
2213
2235
|
):
|
|
2214
2236
|
return None
|
|
2215
2237
|
return snapshot_code_change(
|
|
@@ -2217,6 +2239,17 @@ def branch_code_change(root, base, commit, request):
|
|
|
2217
2239
|
)
|
|
2218
2240
|
|
|
2219
2241
|
|
|
2242
|
+
def runner_code_change(root, base, commit, request):
|
|
2243
|
+
kind = flag_value(request["args"], CODE_CHANGE_FLAG)
|
|
2244
|
+
if kind == "working-tree":
|
|
2245
|
+
change = snapshot_code_change(root, base, commit, replay_name(request["args"]))
|
|
2246
|
+
elif kind in BRANCH_CODE_CHANGES:
|
|
2247
|
+
change = branch_code_change(root, base, commit, request)
|
|
2248
|
+
else:
|
|
2249
|
+
return None
|
|
2250
|
+
return change or {"files": []}
|
|
2251
|
+
|
|
2252
|
+
|
|
2220
2253
|
def write_code_change(change):
|
|
2221
2254
|
handle, path = tempfile.mkstemp(
|
|
2222
2255
|
prefix="bitfab-code-change-",
|
|
@@ -2247,22 +2280,11 @@ def execute():
|
|
|
2247
2280
|
if environment:
|
|
2248
2281
|
workflow_environment_conflicts(root, environment)
|
|
2249
2282
|
print(f"Setting {', '.join(sorted(environment))} from the request", flush=True)
|
|
2250
|
-
|
|
2251
|
-
explicit_change = bool(names & set(CODE_CHANGE_FLAGS))
|
|
2252
|
-
change = (
|
|
2253
|
-
None
|
|
2254
|
-
if explicit_change or base is None
|
|
2255
|
-
else snapshot_code_change(root, base, commit, replay_name(request["args"]))
|
|
2256
|
-
or branch_code_change(root, base, commit, request)
|
|
2257
|
-
)
|
|
2283
|
+
change = None if base is None else runner_code_change(root, base, commit, request)
|
|
2258
2284
|
run_environment = dict(environment)
|
|
2259
|
-
if change:
|
|
2285
|
+
if change is not None:
|
|
2260
2286
|
run_environment[CODE_CHANGE_PATH_ENV] = write_code_change(change)
|
|
2261
|
-
args = [
|
|
2262
|
-
*replay_command(),
|
|
2263
|
-
*request["args"],
|
|
2264
|
-
*([] if explicit_change or change else ["--no-code-change"]),
|
|
2265
|
-
]
|
|
2287
|
+
args = [*replay_command(), *request["args"]]
|
|
2266
2288
|
if dry_run(request["args"]):
|
|
2267
2289
|
run_check_command(directory, environment)
|
|
2268
2290
|
experiment = {}
|
data/lib/bitfab/replay.rb
CHANGED
|
@@ -142,7 +142,12 @@ module Bitfab
|
|
|
142
142
|
|
|
143
143
|
# Replay historical traces through a traced method and create an experiment.
|
|
144
144
|
module Replay
|
|
145
|
-
|
|
145
|
+
# The values Client#replay accepts for +code_change:+.
|
|
146
|
+
CODE_CHANGE_TYPES = %w[working-tree last-branch default-branch skip manual].freeze
|
|
147
|
+
# The +code_change:+ choices, as named in the errors that refuse a replay.
|
|
148
|
+
CODE_CHANGE_CHOICES = '"working-tree" (uncommitted changes vs HEAD), "last-branch" (the diff against the ' \
|
|
149
|
+
'branch this one was cut from), "default-branch" (the diff against the default branch), "skip" (no code ' \
|
|
150
|
+
'change), or "manual" (pass the files yourself)'
|
|
146
151
|
UNSET_DEFAULT = Object.new.freeze
|
|
147
152
|
OPTION_DEFAULTS = {mock: "marked"}.freeze
|
|
148
153
|
PERSISTENCE_TIMEOUT_SECONDS = 30.0
|
|
@@ -199,7 +204,7 @@ module Bitfab
|
|
|
199
204
|
def explicit_replay_options(options)
|
|
200
205
|
options.reject do |option, value|
|
|
201
206
|
value.nil? || value == false || value.equal?(ReplayConcurrency::UNSET) ||
|
|
202
|
-
value
|
|
207
|
+
value == OPTION_DEFAULTS.fetch(option, UNSET_DEFAULT)
|
|
203
208
|
end
|
|
204
209
|
end
|
|
205
210
|
|
|
@@ -225,15 +230,23 @@ module Bitfab
|
|
|
225
230
|
# such as an environment override or a forced feature flag. Kept on the
|
|
226
231
|
# experiment next to its name (max 10,000 characters).
|
|
227
232
|
# @param max_concurrency [Integer, nil] max threads for parallel replay (default: 10)
|
|
228
|
-
# @param
|
|
229
|
-
#
|
|
230
|
-
#
|
|
231
|
-
# the
|
|
232
|
-
#
|
|
233
|
-
#
|
|
234
|
-
#
|
|
235
|
-
#
|
|
236
|
-
#
|
|
233
|
+
# @param code_change [String] required. Which code change this replay
|
|
234
|
+
# tests, stored on the experiment so it can be reviewed alongside the
|
|
235
|
+
# results. A replay without it raises ArgumentError before any request.
|
|
236
|
+
# "working-tree" is the uncommitted changes vs HEAD; "last-branch" the
|
|
237
|
+
# diff against the branch this one was cut from, such as the branch below
|
|
238
|
+
# it in a stack, else the default branch; "default-branch" the diff
|
|
239
|
+
# against where HEAD split from the default branch (origin/HEAD, else
|
|
240
|
+
# origin/main, origin/master, main, master); "skip" no code change, such
|
|
241
|
+
# as for a baseline run; "manual" the files in code_change_files. The
|
|
242
|
+
# three git choices include uncommitted edits and untracked files, and
|
|
243
|
+
# warn and attach nothing when they find no changed files.
|
|
244
|
+
# @param code_change_description [String, nil] description of the code
|
|
245
|
+
# change, stored on the experiment. Defaults to one generated from the
|
|
246
|
+
# captured files. Not accepted with "skip".
|
|
247
|
+
# @param code_change_files [Array<Hash>, nil] the edited files when
|
|
248
|
+
# code_change is "manual", and only then, each as { path:, before:, after: }
|
|
249
|
+
# (empty string for new/deleted files).
|
|
237
250
|
# @param experiment_group_id [String, nil] optional UUID grouping multiple
|
|
238
251
|
# replay runs into a single experiment batch. The run joins that group,
|
|
239
252
|
# and an id no group has yet creates it
|
|
@@ -300,10 +313,11 @@ module Bitfab
|
|
|
300
313
|
# The experiment fields are nil for a dry run, which creates no experiment,
|
|
301
314
|
# and progress events then carry a nil :experiment_id too.
|
|
302
315
|
def run(client, receiver, method_name, trace_function_key:, limit: nil, trace_ids: nil, name: nil, notes: nil, metadata: nil,
|
|
303
|
-
concurrency: nil, max_concurrency: ReplayConcurrency::UNSET, code_change_description:
|
|
316
|
+
concurrency: nil, max_concurrency: ReplayConcurrency::UNSET, code_change: nil, code_change_description: nil, code_change_files: nil, experiment_group_id: nil, experiment_group_name: nil,
|
|
304
317
|
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,
|
|
305
318
|
adapt_inputs: nil, mock_override: nil, db_branch: nil, on_item_start: nil, on_item_finish: nil, on_progress: nil,
|
|
306
319
|
on_experiment_start: nil, experimental_selective_replay: nil)
|
|
320
|
+
validate_code_change(code_change, code_change_description, code_change_files)
|
|
307
321
|
explicit_options = method(:run).parameters.filter_map { |type, option| [option, binding.local_variable_get(option)] if type == :key }
|
|
308
322
|
skip_assertion_judging = resolve_skip_assertion_judging(skip_assertion_judging, judge_assertions)
|
|
309
323
|
concurrency_options = ReplayConcurrency.resolve(concurrency, attempts, max_concurrency)
|
|
@@ -382,18 +396,7 @@ module Bitfab
|
|
|
382
396
|
|
|
383
397
|
include_db_branch_lease = db_branch_enabled?(db_branch) && !dry_run
|
|
384
398
|
|
|
385
|
-
|
|
386
|
-
# wins, and explicit nil opts out. Preserve a caller-supplied description
|
|
387
|
-
# while filling only the omitted files.
|
|
388
|
-
if code_change_files.equal?(CODE_CHANGE_UNSET)
|
|
389
|
-
captured = resolve_auto_code_change(name)
|
|
390
|
-
unless captured.nil?
|
|
391
|
-
code_change_files = captured[1]
|
|
392
|
-
code_change_description = captured[0] if code_change_description.equal?(CODE_CHANGE_UNSET)
|
|
393
|
-
end
|
|
394
|
-
end
|
|
395
|
-
code_change_description = nil if code_change_description.equal?(CODE_CHANGE_UNSET)
|
|
396
|
-
code_change_files = nil if code_change_files.equal?(CODE_CHANGE_UNSET)
|
|
399
|
+
code_change_description, code_change_files = resolve_code_change(code_change, code_change_description, code_change_files, name)
|
|
397
400
|
|
|
398
401
|
resolved_db_branch_settings = db_branch_settings(db_branch)
|
|
399
402
|
replay_data = http_client.start_replay(
|
|
@@ -591,10 +594,6 @@ module Bitfab
|
|
|
591
594
|
result
|
|
592
595
|
end
|
|
593
596
|
|
|
594
|
-
# Read the code-change payload the replay wrapper captured and pointed us at
|
|
595
|
-
# via BITFAB_CODE_CHANGE_PATH. Returns [description, files] or nil.
|
|
596
|
-
# Best-effort: any error (no env var, missing file, bad JSON) yields nil so
|
|
597
|
-
# the replay proceeds with no code change rather than raising.
|
|
598
597
|
CC_MAX_FILES = 60
|
|
599
598
|
CC_MAX_FILE_BYTES = 500_000
|
|
600
599
|
CC_MAX_TOTAL_BYTES = 2_000_000
|
|
@@ -655,21 +654,36 @@ module Bitfab
|
|
|
655
654
|
settings.empty? ? nil : settings
|
|
656
655
|
end
|
|
657
656
|
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
657
|
+
def validate_code_change(code_change, description, files)
|
|
658
|
+
if code_change.nil?
|
|
659
|
+
raise ArgumentError, "replay needs a code_change naming the code this run tests: #{CODE_CHANGE_CHOICES}."
|
|
660
|
+
end
|
|
661
|
+
unless CODE_CHANGE_TYPES.include?(code_change)
|
|
662
|
+
raise ArgumentError, "Unknown code_change #{code_change.inspect}. Pass #{CODE_CHANGE_CHOICES}."
|
|
663
|
+
end
|
|
664
|
+
if code_change == "manual" && files.nil?
|
|
665
|
+
raise ArgumentError,
|
|
666
|
+
'code_change "manual" needs code_change_files: the path and before/after contents of each edited file.'
|
|
667
|
+
end
|
|
668
|
+
if code_change != "manual" && !files.nil?
|
|
669
|
+
raise ArgumentError, "code_change_files only applies with code_change \"manual\" (got \"#{code_change}\")."
|
|
670
|
+
end
|
|
671
|
+
if code_change == "skip" && !description.nil?
|
|
672
|
+
raise ArgumentError, 'code_change "skip" attaches no code change, so it cannot take a code_change_description.'
|
|
673
|
+
end
|
|
674
|
+
code_change
|
|
675
|
+
end
|
|
676
|
+
|
|
677
|
+
def resolve_code_change(code_change, description, files, label = nil)
|
|
678
|
+
return [nil, nil] if code_change == "skip"
|
|
679
|
+
return [description, files] if code_change == "manual"
|
|
680
|
+
|
|
681
|
+
captured = read_code_change_file || capture_code_change_from_git(Dir.pwd, code_change, label)
|
|
682
|
+
if captured.nil? || captured[1].nil? || captured[1].empty?
|
|
683
|
+
warn "Bitfab: code_change \"#{code_change}\" found no changed files, so this replay has no code change attached."
|
|
684
|
+
return [description, nil]
|
|
685
|
+
end
|
|
686
|
+
[description || captured[0], captured[1]]
|
|
673
687
|
end
|
|
674
688
|
|
|
675
689
|
def read_code_change_file
|
|
@@ -679,8 +693,6 @@ module Bitfab
|
|
|
679
693
|
begin
|
|
680
694
|
parsed = JSON.parse(File.read(path))
|
|
681
695
|
files = parsed["files"]
|
|
682
|
-
# A malformed payload (non-array, or entries that aren't hashes) must
|
|
683
|
-
# yield no code change, never crash normalize_code_change_files.
|
|
684
696
|
files = nil unless files.is_a?(Array) && files.all?(Hash)
|
|
685
697
|
description = parsed["description"]
|
|
686
698
|
description = nil unless description.is_a?(String)
|
|
@@ -700,18 +712,20 @@ module Bitfab
|
|
|
700
712
|
!git(root, ["rev-parse", "--verify", "#{ref}^{object}"]).nil?
|
|
701
713
|
end
|
|
702
714
|
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
mb = git(root, ["merge-base", "HEAD", forced])
|
|
709
|
-
return [mb.strip, "vs #{forced}"] if mb && !mb.strip.empty?
|
|
710
|
-
|
|
711
|
-
rp = git(root, ["rev-parse", "--verify", forced])
|
|
712
|
-
return (rp && !rp.strip.empty?) ? [rp.strip, "vs #{forced}"] : nil
|
|
715
|
+
def cc_resolve_base(root, type)
|
|
716
|
+
case type
|
|
717
|
+
when "last-branch" then cc_resolve_branch_point(root)
|
|
718
|
+
when "default-branch" then cc_resolve_default_branch_point(root)
|
|
719
|
+
else cc_ref_exists?(root, "HEAD") ? ["HEAD", "uncommitted (vs HEAD)"] : nil
|
|
713
720
|
end
|
|
714
|
-
|
|
721
|
+
end
|
|
722
|
+
|
|
723
|
+
def cc_resolve_default_branch_point(root)
|
|
724
|
+
default = cc_default_branch(root)
|
|
725
|
+
return nil if default.nil?
|
|
726
|
+
|
|
727
|
+
base = (git(root, ["merge-base", "HEAD", default]) || "").strip
|
|
728
|
+
base.empty? ? nil : [base, "vs #{default}"]
|
|
715
729
|
end
|
|
716
730
|
|
|
717
731
|
def cc_list_changes(root, base)
|
|
@@ -794,22 +808,15 @@ module Bitfab
|
|
|
794
808
|
0
|
|
795
809
|
end
|
|
796
810
|
|
|
797
|
-
def capture_code_change_from_git(cwd, label = nil)
|
|
811
|
+
def capture_code_change_from_git(cwd, type, label = nil)
|
|
798
812
|
root = git(cwd, ["rev-parse", "--show-toplevel"])
|
|
799
813
|
return nil if root.nil? || root.strip.empty?
|
|
800
814
|
|
|
801
815
|
root = root.strip
|
|
802
|
-
resolved = cc_resolve_base(root)
|
|
816
|
+
resolved = cc_resolve_base(root, type)
|
|
803
817
|
return nil if resolved.nil?
|
|
804
818
|
|
|
805
819
|
entries = cc_list_changes(root, resolved[0])
|
|
806
|
-
if entries.empty? && resolved[0] == "HEAD"
|
|
807
|
-
branch_point = cc_resolve_branch_point(root)
|
|
808
|
-
if branch_point
|
|
809
|
-
resolved = branch_point
|
|
810
|
-
entries = cc_list_changes(root, branch_point[0])
|
|
811
|
-
end
|
|
812
|
-
end
|
|
813
820
|
return nil if entries.empty?
|
|
814
821
|
|
|
815
822
|
base, against = resolved
|
|
@@ -819,15 +826,10 @@ module Bitfab
|
|
|
819
826
|
entries.each do |status, before_path, path|
|
|
820
827
|
break if files.length >= CC_MAX_FILES
|
|
821
828
|
|
|
822
|
-
# Skip oversized files by size BEFORE reading their full contents, so a
|
|
823
|
-
# huge changed file can't OOM/stall replay just to be discarded.
|
|
824
829
|
before_bytes = (status == "A") ? 0 : cc_blob_bytes(root, base, before_path)
|
|
825
830
|
after_bytes = (status == "D") ? 0 : cc_working_bytes(root, path)
|
|
826
831
|
next if before_bytes > CC_MAX_FILE_BYTES || after_bytes > CC_MAX_FILE_BYTES
|
|
827
832
|
|
|
828
|
-
# Normalize CRLF -> LF on both sides: git's blob is already LF-normalized
|
|
829
|
-
# (autocrlf clean), so a raw CRLF working file would otherwise skew every
|
|
830
|
-
# line as changed. Comparing/storing LF keeps the diff meaningful.
|
|
831
833
|
before = (status == "A") ? "" : (git(root, ["show", "#{base}:#{before_path}"]) || "").gsub("\r\n", "\n")
|
|
832
834
|
after = (status == "D") ? "" : cc_read_working_file(root, path).gsub("\r\n", "\n")
|
|
833
835
|
next if before == after
|
data/lib/bitfab/replay_cli.rb
CHANGED
|
@@ -19,8 +19,7 @@ module Bitfab
|
|
|
19
19
|
# Project-owned registry of production replay roots.
|
|
20
20
|
class ReplayRegistry
|
|
21
21
|
OPTION_NAMES = %i[
|
|
22
|
-
limit trace_ids name notes metadata concurrency max_concurrency
|
|
23
|
-
code_change_files experiment_group_id experiment_group_name dataset_id dataset_ids grader_ids mock
|
|
22
|
+
limit trace_ids name notes metadata concurrency max_concurrency experiment_group_id experiment_group_name dataset_id dataset_ids grader_ids mock
|
|
24
23
|
mock_override adapt_inputs db_branch attempts only_with_assertions skip_assertion_judging judge_assertions dry_run on_item_finish
|
|
25
24
|
].freeze
|
|
26
25
|
|
|
@@ -141,6 +140,7 @@ module Bitfab
|
|
|
141
140
|
def run(registry, argv: ARGV, stdout: $stdout, stderr: $stderr)
|
|
142
141
|
args = parse(registry, argv.dup)
|
|
143
142
|
return SeedCli.run(registry, argv:, stdout:, stderr:) if args[:seed] || args[:from_trace]
|
|
143
|
+
require_code_change(args)
|
|
144
144
|
stderr.puts "Bitfab: #{Replay::JUDGE_ASSERTIONS_DEPRECATION}" if args[:judge_assertions]
|
|
145
145
|
|
|
146
146
|
registration = registry.fetch(args.fetch(:pipeline))
|
|
@@ -203,13 +203,11 @@ module Bitfab
|
|
|
203
203
|
end
|
|
204
204
|
end
|
|
205
205
|
|
|
206
|
-
|
|
207
|
-
|
|
206
|
+
options[:code_change] = args.fetch(:code_change)
|
|
207
|
+
if args[:code_change_file]
|
|
208
|
+
code_change = load_code_change(args[:code_change_file])
|
|
208
209
|
options[:code_change_description] = code_change.fetch("description")
|
|
209
210
|
options[:code_change_files] = code_change.fetch("files")
|
|
210
|
-
elsif args[:no_code_change]
|
|
211
|
-
options[:code_change_description] = nil
|
|
212
|
-
options[:code_change_files] = nil
|
|
213
211
|
end
|
|
214
212
|
|
|
215
213
|
if options[:concurrency] || args[:primitive] || args.key?(:memory_throttle)
|
|
@@ -305,6 +303,7 @@ module Bitfab
|
|
|
305
303
|
if argv.include?("--db-branch") && argv.include?("--no-db-branch")
|
|
306
304
|
raise OptionParser::InvalidArgument, "--db-branch and --no-db-branch cannot be used together"
|
|
307
305
|
end
|
|
306
|
+
raise ArgumentError, "--no-code-change was replaced by --code-change skip." if argv.include?("--no-code-change")
|
|
308
307
|
args = {}
|
|
309
308
|
parser = OptionParser.new do |options|
|
|
310
309
|
options.banner = "Usage: bitfab-replay <#{registry ? registry.names.join("|") : "pipeline"}> [options]"
|
|
@@ -338,7 +337,19 @@ module Bitfab
|
|
|
338
337
|
options.on("--max-concurrency N", Integer) do |value|
|
|
339
338
|
args[:max_concurrency] = positive_integer("--max-concurrency", value)
|
|
340
339
|
end
|
|
341
|
-
options.on("--code-change
|
|
340
|
+
options.on("--code-change working-tree|last-branch|default-branch|skip|manual",
|
|
341
|
+
"Required: the code change this run tests; working-tree is the uncommitted changes vs HEAD, " \
|
|
342
|
+
"last-branch the diff against the branch this one was cut from, default-branch the diff against the " \
|
|
343
|
+
"default branch, skip none, manual the edits in --code-change-file.") do |value|
|
|
344
|
+
unless Replay::CODE_CHANGE_TYPES.include?(value)
|
|
345
|
+
raise ArgumentError,
|
|
346
|
+
"Unknown --code-change '#{value}'. Pass #{Replay::CODE_CHANGE_CHOICES}. " \
|
|
347
|
+
"A file of edits goes in --code-change manual --code-change-file PATH."
|
|
348
|
+
end
|
|
349
|
+
args[:code_change] = value
|
|
350
|
+
end
|
|
351
|
+
options.on("--code-change-file PATH",
|
|
352
|
+
"With --code-change manual: a JSON file of { description, files }.") { |value| args[:code_change_file] = value }
|
|
342
353
|
options.on("--experiment-group-id UUID",
|
|
343
354
|
"Add this run to an experiment group. A UUID no group has yet creates the group.") { |value| args[:experiment_group_id] = value }
|
|
344
355
|
options.on("--experiment-group-name NAME",
|
|
@@ -360,7 +371,6 @@ module Bitfab
|
|
|
360
371
|
options.on("--mock STRATEGY", %w[none all marked]) { |value| args[:mock] = value }
|
|
361
372
|
options.on("--db-branch") { args[:db_branch] = true }
|
|
362
373
|
options.on("--no-db-branch") { args[:db_branch] = false }
|
|
363
|
-
options.on("--no-code-change") { args[:no_code_change] = true }
|
|
364
374
|
options.on("--params PATH") { |value| args[:params] = value }
|
|
365
375
|
options.on("--param NAME=VALUE") { |value| (args[:param] ||= []) << value }
|
|
366
376
|
end
|
|
@@ -369,9 +379,6 @@ module Bitfab
|
|
|
369
379
|
raise OptionParser::InvalidArgument,
|
|
370
380
|
"--trace-ids and --dataset-ids select different replay sources and cannot be used together"
|
|
371
381
|
end
|
|
372
|
-
if args[:code_change] && args[:no_code_change]
|
|
373
|
-
raise OptionParser::InvalidArgument, "--code-change and --no-code-change cannot be used together"
|
|
374
|
-
end
|
|
375
382
|
pipeline = argv.shift
|
|
376
383
|
known = pipeline && (registry.nil? || registry.names.include?(pipeline))
|
|
377
384
|
raise OptionParser::MissingArgument, parser.banner unless known
|
|
@@ -380,6 +387,17 @@ module Bitfab
|
|
|
380
387
|
args.merge(pipeline:)
|
|
381
388
|
end
|
|
382
389
|
|
|
390
|
+
def require_code_change(args)
|
|
391
|
+
if args[:code_change].nil?
|
|
392
|
+
raise ArgumentError,
|
|
393
|
+
"--code-change is required: #{Replay::CODE_CHANGE_CHOICES}. \"manual\" takes the edits from --code-change-file PATH."
|
|
394
|
+
end
|
|
395
|
+
return args if (args[:code_change] == "manual") == !args[:code_change_file].nil?
|
|
396
|
+
|
|
397
|
+
raise ArgumentError,
|
|
398
|
+
"--code-change-file goes with --code-change manual, and --code-change manual needs --code-change-file PATH."
|
|
399
|
+
end
|
|
400
|
+
|
|
383
401
|
def registry_dataset_ids(options)
|
|
384
402
|
dataset_id = options[:dataset_id]
|
|
385
403
|
dataset_ids = options[:dataset_ids]
|
|
@@ -416,7 +434,7 @@ module Bitfab
|
|
|
416
434
|
def load_code_change(path)
|
|
417
435
|
value = JSON.parse(File.read(path))
|
|
418
436
|
unless value.is_a?(Hash) && value["description"].is_a?(String) && value["files"].is_a?(Array)
|
|
419
|
-
raise ArgumentError, "Invalid --code-change
|
|
437
|
+
raise ArgumentError, "Invalid --code-change-file '#{path}': expected { description, files }."
|
|
420
438
|
end
|
|
421
439
|
|
|
422
440
|
value
|
data/lib/bitfab/version.rb
CHANGED