benchkit-cli 0.1.0__py3-none-any.whl
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.
- benchkit/__init__.py +1 -0
- benchkit/_pi_proxy.py +219 -0
- benchkit/answer_key_guard.ts +49 -0
- benchkit/artifacts.py +87 -0
- benchkit/benchmarks/__init__.py +105 -0
- benchkit/benchmarks/aider_polyglot.py +405 -0
- benchkit/benchmarks/arc.py +52 -0
- benchkit/benchmarks/banking77.py +149 -0
- benchkit/benchmarks/base.py +19 -0
- benchkit/benchmarks/boolq.py +49 -0
- benchkit/benchmarks/chartqa.py +133 -0
- benchkit/benchmarks/code_verifier.py +77 -0
- benchkit/benchmarks/downloads.py +166 -0
- benchkit/benchmarks/evalplus.py +288 -0
- benchkit/benchmarks/git_surgery.py +485 -0
- benchkit/benchmarks/gpqa.py +56 -0
- benchkit/benchmarks/gsm8k.py +97 -0
- benchkit/benchmarks/hellaswag.py +49 -0
- benchkit/benchmarks/humaneval.py +103 -0
- benchkit/benchmarks/ifeval.py +56 -0
- benchkit/benchmarks/ifeval_instructions.py +477 -0
- benchkit/benchmarks/mbpp.py +60 -0
- benchkit/benchmarks/mc_arena.py +479 -0
- benchkit/benchmarks/mcq.py +94 -0
- benchkit/benchmarks/medfailbench.py +537 -0
- benchkit/benchmarks/mmlu.py +54 -0
- benchkit/benchmarks/mmlu_pro.py +62 -0
- benchkit/benchmarks/mmvp.py +115 -0
- benchkit/benchmarks/ocrbench.py +96 -0
- benchkit/benchmarks/openbookqa.py +52 -0
- benchkit/benchmarks/patcheval.py +929 -0
- benchkit/benchmarks/piqa.py +52 -0
- benchkit/benchmarks/ruler.py +172 -0
- benchkit/benchmarks/ruler_generator.py +535 -0
- benchkit/benchmarks/sanity.py +174 -0
- benchkit/benchmarks/treejs_arena.py +268 -0
- benchkit/benchmarks/truthfulqa.py +52 -0
- benchkit/benchmarks/utils.py +25 -0
- benchkit/benchmarks/vision.py +230 -0
- benchkit/benchmarks/vstar.py +108 -0
- benchkit/benchmarks/winogrande.py +52 -0
- benchkit/benchmarks/xstest.py +116 -0
- benchkit/browser.py +429 -0
- benchkit/catalog.py +121 -0
- benchkit/cli.py +1000 -0
- benchkit/client.py +1413 -0
- benchkit/controls.py +87 -0
- benchkit/datasets/MMLU_LICENSE.txt +21 -0
- benchkit/datasets/README.md +142 -0
- benchkit/datasets/arc_challenge.jsonl +1172 -0
- benchkit/datasets/banking77.jsonl +3080 -0
- benchkit/datasets/banking77_intents.json +79 -0
- benchkit/datasets/boolq.jsonl +3270 -0
- benchkit/datasets/code_verifier.jsonl +500 -0
- benchkit/datasets/gpqa_diamond.jsonl +198 -0
- benchkit/datasets/gsm8k.jsonl +1319 -0
- benchkit/datasets/hellaswag.jsonl +1000 -0
- benchkit/datasets/humaneval.jsonl +164 -0
- benchkit/datasets/ifeval.jsonl +541 -0
- benchkit/datasets/mbpp.jsonl +500 -0
- benchkit/datasets/mc_blocks_1_20_1.jsonl +1003 -0
- benchkit/datasets/medfailbench_hard30.jsonl +30 -0
- benchkit/datasets/medfailbench_hard30.md +13 -0
- benchkit/datasets/openbookqa.jsonl +500 -0
- benchkit/datasets/piqa.jsonl +1838 -0
- benchkit/datasets/sanity_code.jsonl +6 -0
- benchkit/datasets/truthfulqa.jsonl +817 -0
- benchkit/datasets/winogrande.jsonl +1267 -0
- benchkit/datasets/xstest.csv +451 -0
- benchkit/decision.py +194 -0
- benchkit/demo.py +451 -0
- benchkit/dockerfiles.py +247 -0
- benchkit/engine.py +1270 -0
- benchkit/evaluation.py +195 -0
- benchkit/events.py +141 -0
- benchkit/executor.py +96 -0
- benchkit/git_surgery/backport-release-stack/setup.sh +328 -0
- benchkit/git_surgery/backport-release-stack/verify.sh +119 -0
- benchkit/git_surgery/bisect-the-regression/setup.sh +49 -0
- benchkit/git_surgery/bisect-the-regression/verify.sh +34 -0
- benchkit/git_surgery/rebase-conflict-chain/setup.sh +65 -0
- benchkit/git_surgery/rebase-conflict-chain/verify.sh +43 -0
- benchkit/git_surgery/recover-complex-stash/setup.sh +152 -0
- benchkit/git_surgery/recover-complex-stash/verify.sh +95 -0
- benchkit/git_surgery/recover-lost-work/setup.sh +40 -0
- benchkit/git_surgery/recover-lost-work/verify.sh +36 -0
- benchkit/git_surgery/repair-force-pushed-remote/setup.sh +171 -0
- benchkit/git_surgery/repair-force-pushed-remote/verify.sh +83 -0
- benchkit/git_surgery/revert-merge-with-followups/setup.sh +198 -0
- benchkit/git_surgery/revert-merge-with-followups/verify.sh +98 -0
- benchkit/git_surgery/secret-in-history/README.md +27 -0
- benchkit/git_surgery/secret-in-history/setup.sh +107 -0
- benchkit/git_surgery/secret-in-history/verify.sh +106 -0
- benchkit/git_surgery/split-the-mega-commit/setup.sh +50 -0
- benchkit/git_surgery/split-the-mega-commit/verify.sh +48 -0
- benchkit/git_surgery/untangle-nested-submodules/setup.sh +193 -0
- benchkit/git_surgery/untangle-nested-submodules/verify.sh +90 -0
- benchkit/history.py +474 -0
- benchkit/jobs.py +332 -0
- benchkit/leakage.py +51 -0
- benchkit/looping.py +873 -0
- benchkit/mc_render.py +274 -0
- benchkit/mc_viewer/dist/atlas.png +0 -0
- benchkit/mc_viewer/dist/blockStates.json.gz +0 -0
- benchkit/mc_viewer/dist/version.json +4 -0
- benchkit/mc_viewer/dist/viewer.html +23 -0
- benchkit/mc_viewer/dist/viewer.js +2 -0
- benchkit/mc_viewer/dist/viewer.js.LICENSE.txt +25 -0
- benchkit/mc_viewer/dist/worker.js +2 -0
- benchkit/mc_viewer/dist/worker.js.LICENSE.txt +19 -0
- benchkit/metrics.py +100 -0
- benchkit/ollama.py +60 -0
- benchkit/perf.py +353 -0
- benchkit/perf_report.py +207 -0
- benchkit/perturbations.py +208 -0
- benchkit/pi_agent.py +476 -0
- benchkit/pi_package/package-lock.json +1827 -0
- benchkit/pi_package/package.json +8 -0
- benchkit/report.py +142 -0
- benchkit/report_markdown.py +645 -0
- benchkit/results.py +688 -0
- benchkit/retry.py +163 -0
- benchkit/runner.py +769 -0
- benchkit/sandbox.py +1167 -0
- benchkit/template_check.py +291 -0
- benchkit/templates/arena.html +504 -0
- benchkit/templates/history.html +58 -0
- benchkit/templates/perf.html +509 -0
- benchkit/templates/report.html +769 -0
- benchkit/tui/__init__.py +5 -0
- benchkit/tui/app.py +87 -0
- benchkit/tui/app.tcss +512 -0
- benchkit/tui/formatting.py +84 -0
- benchkit/tui/screens/__init__.py +1 -0
- benchkit/tui/screens/connect.py +191 -0
- benchkit/tui/screens/detail.py +288 -0
- benchkit/tui/screens/help.py +84 -0
- benchkit/tui/screens/modals.py +544 -0
- benchkit/tui/screens/results.py +361 -0
- benchkit/tui/screens/run.py +988 -0
- benchkit/tui/screens/setup.py +733 -0
- benchkit/tui/theme.py +135 -0
- benchkit/tui/widgets.py +120 -0
- benchkit_cli-0.1.0.dist-info/METADATA +294 -0
- benchkit_cli-0.1.0.dist-info/RECORD +149 -0
- benchkit_cli-0.1.0.dist-info/WHEEL +5 -0
- benchkit_cli-0.1.0.dist-info/entry_points.txt +3 -0
- benchkit_cli-0.1.0.dist-info/licenses/LICENSE +201 -0
- benchkit_cli-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
seed="${1:?usage: setup.sh SEED [WORKSPACE]}"
|
|
4
|
+
workspace="${2:-/workspace/backport-release-stack}"
|
|
5
|
+
slug="$(printf '%s' "$seed:backport" | sha256sum | cut -c1-12)"
|
|
6
|
+
mkdir -p "$workspace"
|
|
7
|
+
cd "$workspace"
|
|
8
|
+
git init -q -b release-2.x
|
|
9
|
+
git config user.name "BenchKit Generator"
|
|
10
|
+
git config user.email "generator@benchkit.invalid"
|
|
11
|
+
git config commit.gpgSign false
|
|
12
|
+
git config core.autocrlf false
|
|
13
|
+
export GIT_AUTHOR_NAME="BenchKit Generator" GIT_AUTHOR_EMAIL="generator@benchkit.invalid"
|
|
14
|
+
export GIT_COMMITTER_NAME="$GIT_AUTHOR_NAME" GIT_COMMITTER_EMAIL="$GIT_AUTHOR_EMAIL"
|
|
15
|
+
export GIT_CONFIG_GLOBAL=/dev/null
|
|
16
|
+
clock=1700500000
|
|
17
|
+
commit() {
|
|
18
|
+
clock=$((clock + 60))
|
|
19
|
+
git add -A
|
|
20
|
+
GIT_AUTHOR_DATE="@$clock +0000" GIT_COMMITTER_DATE="@$clock +0000" git commit -q "$@"
|
|
21
|
+
}
|
|
22
|
+
replace() {
|
|
23
|
+
python3 - "$@" <<'PY'
|
|
24
|
+
import sys
|
|
25
|
+
from pathlib import Path
|
|
26
|
+
|
|
27
|
+
path, old, new = Path(sys.argv[1]), sys.argv[2], sys.argv[3]
|
|
28
|
+
text = path.read_text()
|
|
29
|
+
if old not in text:
|
|
30
|
+
raise SystemExit(f"setup: {old!r} not found in {path}")
|
|
31
|
+
path.write_text(text.replace(old, new, 1))
|
|
32
|
+
PY
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
mkdir -p ledger docs
|
|
36
|
+
printf '__pycache__/\n' > .gitignore
|
|
37
|
+
: > ledger/__init__.py
|
|
38
|
+
cat > ledger/money.py <<'PY'
|
|
39
|
+
"""Amount parsing and formatting in integer cents."""
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def parse_amount(text: str) -> int:
|
|
43
|
+
value = text
|
|
44
|
+
if not value.isdigit():
|
|
45
|
+
raise ValueError(f"bad amount: {text!r}")
|
|
46
|
+
return int(value)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def format_amount(cents: int) -> str:
|
|
50
|
+
return f"{cents // 100}.{cents % 100:02d}"
|
|
51
|
+
PY
|
|
52
|
+
cat > ledger/discount.py <<'PY'
|
|
53
|
+
"""Discounts applied to order totals."""
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def apply_discount(total: int, discount: int) -> int:
|
|
57
|
+
return total - discount
|
|
58
|
+
PY
|
|
59
|
+
cat > ledger/config.py <<'PY'
|
|
60
|
+
"""Runtime configuration."""
|
|
61
|
+
|
|
62
|
+
DEFAULTS = {
|
|
63
|
+
"timeout": 30,
|
|
64
|
+
"retries": 3,
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
VERSION = "1.0.0"
|
|
68
|
+
PY
|
|
69
|
+
cat > test_core.py <<'PY'
|
|
70
|
+
import unittest
|
|
71
|
+
|
|
72
|
+
from ledger.config import DEFAULTS
|
|
73
|
+
from ledger.discount import apply_discount
|
|
74
|
+
from ledger.money import format_amount, parse_amount
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
class CoreTests(unittest.TestCase):
|
|
78
|
+
def test_parse_amount(self):
|
|
79
|
+
self.assertEqual(parse_amount("1250"), 1250)
|
|
80
|
+
|
|
81
|
+
def test_parse_amount_rejects_text(self):
|
|
82
|
+
with self.assertRaises(ValueError):
|
|
83
|
+
parse_amount("ten")
|
|
84
|
+
|
|
85
|
+
def test_format_amount(self):
|
|
86
|
+
self.assertEqual(format_amount(1205), "12.05")
|
|
87
|
+
|
|
88
|
+
def test_apply_discount(self):
|
|
89
|
+
self.assertEqual(apply_discount(500, 120), 380)
|
|
90
|
+
|
|
91
|
+
def test_retries(self):
|
|
92
|
+
self.assertEqual(DEFAULTS["retries"], 3)
|
|
93
|
+
PY
|
|
94
|
+
cat > CHANGELOG.md <<EOF
|
|
95
|
+
# Ledger changelog ($slug)
|
|
96
|
+
|
|
97
|
+
EOF
|
|
98
|
+
printf '# Ledger %s\n\nRun tests with `python3 -m unittest discover -v`.\n' "$slug" > README.md
|
|
99
|
+
commit -m "bootstrap ledger $slug"
|
|
100
|
+
fork="$(git rev-parse HEAD)"
|
|
101
|
+
|
|
102
|
+
noise() {
|
|
103
|
+
local index="$1"
|
|
104
|
+
case $((index % 4)) in
|
|
105
|
+
0)
|
|
106
|
+
printf -- '- %s: maintenance entry %02d\n' "$slug" "$index" >> CHANGELOG.md
|
|
107
|
+
commit -m "chore: update changelog ($index)"
|
|
108
|
+
;;
|
|
109
|
+
1)
|
|
110
|
+
if [[ ! -f ledger/export.py ]]; then
|
|
111
|
+
cat > ledger/export.py <<'PY'
|
|
112
|
+
"""CSV export, new in 2.x."""
|
|
113
|
+
|
|
114
|
+
COLUMNS = ["id"]
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def header() -> str:
|
|
118
|
+
return ",".join(COLUMNS)
|
|
119
|
+
PY
|
|
120
|
+
cat > test_export.py <<'PY'
|
|
121
|
+
import unittest
|
|
122
|
+
|
|
123
|
+
from ledger.export import COLUMNS, header
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
class ExportTests(unittest.TestCase):
|
|
127
|
+
def test_header(self):
|
|
128
|
+
self.assertEqual(header(), ",".join(COLUMNS))
|
|
129
|
+
PY
|
|
130
|
+
commit -m "export: introduce CSV export"
|
|
131
|
+
else
|
|
132
|
+
replace ledger/export.py ']' ", \"col$index\"]"
|
|
133
|
+
commit -m "export: add column col$index"
|
|
134
|
+
fi
|
|
135
|
+
;;
|
|
136
|
+
2)
|
|
137
|
+
printf 'Release note %02d for %s.\n' "$index" "$slug" >> "docs/release-$((index % 3)).md"
|
|
138
|
+
commit -m "docs: expand release notes ($index)"
|
|
139
|
+
;;
|
|
140
|
+
3)
|
|
141
|
+
printf 'step-%02d\n' "$index" >> docs/pipeline.txt
|
|
142
|
+
commit -m "ci: adjust pipeline step $index"
|
|
143
|
+
;;
|
|
144
|
+
esac
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
for index in $(seq 1 70); do
|
|
148
|
+
case "$index" in
|
|
149
|
+
6)
|
|
150
|
+
python3 - <<'PY'
|
|
151
|
+
from pathlib import Path
|
|
152
|
+
|
|
153
|
+
path = Path("ledger/money.py")
|
|
154
|
+
text = path.read_text()
|
|
155
|
+
text = text.replace(" value = text\n if not value.isdigit():", " raw = text\n if not raw.isdigit():")
|
|
156
|
+
text = text.replace("return int(value)", "return int(raw)")
|
|
157
|
+
path.write_text(text)
|
|
158
|
+
PY
|
|
159
|
+
commit -m "refactor: rename parse_amount locals"
|
|
160
|
+
;;
|
|
161
|
+
11)
|
|
162
|
+
replace ledger/money.py 'def parse_amount(text: str) -> int:
|
|
163
|
+
raw = text
|
|
164
|
+
' 'def parse_amount(text: str, allow_commas: bool = False) -> int:
|
|
165
|
+
raw = text
|
|
166
|
+
if allow_commas:
|
|
167
|
+
raw = raw.replace(",", "")
|
|
168
|
+
'
|
|
169
|
+
cat >> test_export.py <<'PY'
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
class SeparatorTests(unittest.TestCase):
|
|
173
|
+
def test_thousands_separators(self):
|
|
174
|
+
from ledger.money import parse_amount
|
|
175
|
+
|
|
176
|
+
self.assertEqual(parse_amount("1,250", allow_commas=True), 1250)
|
|
177
|
+
PY
|
|
178
|
+
commit -m "feat: accept thousands separators"
|
|
179
|
+
;;
|
|
180
|
+
17)
|
|
181
|
+
replace ledger/money.py ' raw = text
|
|
182
|
+
' ' raw = text.strip()
|
|
183
|
+
'
|
|
184
|
+
cat > test_bk101.py <<'PY'
|
|
185
|
+
import unittest
|
|
186
|
+
|
|
187
|
+
from ledger.money import parse_amount
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
class WhitespaceTests(unittest.TestCase):
|
|
191
|
+
def test_surrounding_whitespace_is_ignored(self):
|
|
192
|
+
self.assertEqual(parse_amount(" 42\n"), 42)
|
|
193
|
+
PY
|
|
194
|
+
commit -m "Fix whitespace handling in parse_amount" -m "Fixes: BK-101"
|
|
195
|
+
;;
|
|
196
|
+
19)
|
|
197
|
+
printf -- '- Fix typo in the BK-104 changelog entry\n' >> CHANGELOG.md
|
|
198
|
+
commit -m "Fix typo in BK-104 changelog entry"
|
|
199
|
+
;;
|
|
200
|
+
22)
|
|
201
|
+
printf '# Parsing\n\nSurrounding whitespace is ignored (BK-101).\n' > docs/parsing.md
|
|
202
|
+
commit -m "BK-101: document whitespace parsing" -m "Refs: BK-101"
|
|
203
|
+
;;
|
|
204
|
+
28)
|
|
205
|
+
replace ledger/money.py ' raw = text.strip()
|
|
206
|
+
' ' raw = text.strip()
|
|
207
|
+
sign = -1 if raw.startswith("-") else 1
|
|
208
|
+
raw = raw.removeprefix("-")
|
|
209
|
+
'
|
|
210
|
+
replace ledger/money.py 'return int(raw)' 'return sign * int(raw)'
|
|
211
|
+
cat > test_bk104.py <<'PY'
|
|
212
|
+
import unittest
|
|
213
|
+
|
|
214
|
+
from ledger.money import parse_amount
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
class NegativeAmountTests(unittest.TestCase):
|
|
218
|
+
def test_negative_amounts(self):
|
|
219
|
+
self.assertEqual(parse_amount("-75"), -75)
|
|
220
|
+
PY
|
|
221
|
+
commit -m "Accept negative amounts" -m "Fixes: BK-104"
|
|
222
|
+
;;
|
|
223
|
+
34)
|
|
224
|
+
replace ledger/money.py ' if not raw.isdigit():' ' if len(raw) > 18:
|
|
225
|
+
raise ValueError(f"amount too long: {text!r}")
|
|
226
|
+
if not raw.isdigit():'
|
|
227
|
+
commit -m "Reject overlong amounts" -m "Fixes: BK-1040"
|
|
228
|
+
;;
|
|
229
|
+
39)
|
|
230
|
+
replace ledger/money.py ' return f"{cents // 100}.{cents % 100:02d}"' ' return f"{cents / 100:.2f}"'
|
|
231
|
+
cat > test_bk107.py <<'PY'
|
|
232
|
+
import unittest
|
|
233
|
+
|
|
234
|
+
from ledger.money import format_amount
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
class NegativeFormatTests(unittest.TestCase):
|
|
238
|
+
def test_negative_cents(self):
|
|
239
|
+
self.assertEqual(format_amount(-5), "-0.05")
|
|
240
|
+
PY
|
|
241
|
+
commit -m "Format negative amounts" -m "Fixes: BK-107"
|
|
242
|
+
attempt="$(git rev-parse HEAD)"
|
|
243
|
+
;;
|
|
244
|
+
43)
|
|
245
|
+
git revert --no-edit --no-commit "$attempt" >/dev/null
|
|
246
|
+
commit -m "Revert \"Format negative amounts\"" \
|
|
247
|
+
-m "This reverts commit $attempt. Float formatting loses cents above 2**53." \
|
|
248
|
+
-m "Refs: BK-107"
|
|
249
|
+
;;
|
|
250
|
+
48)
|
|
251
|
+
replace ledger/money.py ' return f"{cents // 100}.{cents % 100:02d}"' ' sign = "-" if cents < 0 else ""
|
|
252
|
+
cents = abs(cents)
|
|
253
|
+
return f"{sign}{cents // 100}.{cents % 100:02d}"'
|
|
254
|
+
cat > test_bk107.py <<'PY'
|
|
255
|
+
import unittest
|
|
256
|
+
|
|
257
|
+
from ledger.money import format_amount
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
class NegativeFormatTests(unittest.TestCase):
|
|
261
|
+
def test_negative_cents(self):
|
|
262
|
+
self.assertEqual(format_amount(-5), "-0.05")
|
|
263
|
+
|
|
264
|
+
def test_large_amounts_stay_exact(self):
|
|
265
|
+
self.assertEqual(format_amount(123456789012345678), "1234567890123456.78")
|
|
266
|
+
PY
|
|
267
|
+
commit -m "Format negative amounts without floats" -m "Fixes: BK-107"
|
|
268
|
+
;;
|
|
269
|
+
54)
|
|
270
|
+
replace ledger/discount.py 'return total - discount' 'return max(0, total - discount)'
|
|
271
|
+
cat > test_bk112.py <<'PY'
|
|
272
|
+
import unittest
|
|
273
|
+
|
|
274
|
+
from ledger.discount import apply_discount
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
class ClampTests(unittest.TestCase):
|
|
278
|
+
def test_discount_never_goes_negative(self):
|
|
279
|
+
self.assertEqual(apply_discount(100, 250), 0)
|
|
280
|
+
PY
|
|
281
|
+
commit -m "Clamp discounted totals at zero" -m "Fixes: BK-112"
|
|
282
|
+
;;
|
|
283
|
+
57)
|
|
284
|
+
replace ledger/config.py 'VERSION = "1.0.0"' 'VERSION = "2.0.0"
|
|
285
|
+
RELEASE_CHANNEL = "2.x"'
|
|
286
|
+
commit -m "Bump version to 2.0.0"
|
|
287
|
+
;;
|
|
288
|
+
60)
|
|
289
|
+
replace ledger/config.py '"timeout": 30,' '"timeout": 10,'
|
|
290
|
+
cat > test_bk115.py <<'PY'
|
|
291
|
+
import unittest
|
|
292
|
+
|
|
293
|
+
from ledger.config import DEFAULTS
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
class TimeoutTests(unittest.TestCase):
|
|
297
|
+
def test_default_timeout(self):
|
|
298
|
+
self.assertEqual(DEFAULTS["timeout"], 10)
|
|
299
|
+
PY
|
|
300
|
+
commit -m "Lower default request timeout" -m "Fixes: BK-115"
|
|
301
|
+
;;
|
|
302
|
+
65)
|
|
303
|
+
printf -- '- Follow-up for BK-115 timeouts in docs\n' >> CHANGELOG.md
|
|
304
|
+
commit -m "Lower default request timeout in docs" -m "Refs: BK-115"
|
|
305
|
+
;;
|
|
306
|
+
*)
|
|
307
|
+
noise "$index"
|
|
308
|
+
;;
|
|
309
|
+
esac
|
|
310
|
+
done
|
|
311
|
+
|
|
312
|
+
git switch -q -c maint-1.x "$fork"
|
|
313
|
+
replace ledger/discount.py 'return total - discount' 'return max(0, total - discount)'
|
|
314
|
+
cat > test_bk112.py <<'PY'
|
|
315
|
+
import unittest
|
|
316
|
+
|
|
317
|
+
from ledger.discount import apply_discount
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
class ClampTests(unittest.TestCase):
|
|
321
|
+
def test_discount_never_goes_negative(self):
|
|
322
|
+
self.assertEqual(apply_discount(100, 250), 0)
|
|
323
|
+
PY
|
|
324
|
+
commit -m "Clamp discounted totals at zero (BK-112)"
|
|
325
|
+
replace ledger/config.py 'VERSION = "1.0.0"' 'VERSION = "1.0.3"'
|
|
326
|
+
printf -- '- 1.0.3 maintenance release\n' >> CHANGELOG.md
|
|
327
|
+
commit -m "Release 1.0.3"
|
|
328
|
+
git status --porcelain | grep -q . && exit 1 || true
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -u
|
|
3
|
+
seed="${1:?usage: verify.sh SEED [WORKSPACE]}"
|
|
4
|
+
workspace="${2:-/workspace/backport-release-stack}"
|
|
5
|
+
emit() { printf '%s\t%s\t%s\n' "$1" "$2" "$3"; }
|
|
6
|
+
ids="selected_fixes skipped_equivalent intermediate_tests final_behavior no_release_leak"
|
|
7
|
+
if ! git -C "$workspace" rev-parse --git-dir >/dev/null 2>&1; then
|
|
8
|
+
for id in $ids; do emit "$id" 0 "repository missing"; done
|
|
9
|
+
emit trap 1 "repository replaced"
|
|
10
|
+
exit 0
|
|
11
|
+
fi
|
|
12
|
+
reference_root="$(mktemp -d)"
|
|
13
|
+
reference="$reference_root/reference"
|
|
14
|
+
bash "$(dirname "$0")/setup.sh" "$seed" "$reference" >/dev/null
|
|
15
|
+
fixed() { git -C "$reference" log release-2.x --format=%H --grep="^Fixes: $1\$" -1; }
|
|
16
|
+
release_tip="$(git -C "$reference" rev-parse release-2.x)"
|
|
17
|
+
maint_tip="$(git -C "$reference" rev-parse maint-1.x)"
|
|
18
|
+
expected_sources="$(fixed BK-101) $(fixed BK-104) $(fixed BK-107) $(fixed BK-115)"
|
|
19
|
+
equivalent="$(fixed BK-112)"
|
|
20
|
+
release_only="$(git -C "$reference" log release-2.x --format=%H --grep='^refactor: rename parse_amount locals$' -1)"
|
|
21
|
+
rm -rf "$reference_root"
|
|
22
|
+
|
|
23
|
+
git_w() { git -C "$workspace" "$@" 2>/dev/null; }
|
|
24
|
+
release_now="$(git_w rev-parse -q --verify refs/heads/release-2.x)"
|
|
25
|
+
maint_now="$(git_w rev-parse -q --verify refs/heads/maint-1.x)"
|
|
26
|
+
git_w merge-base --is-ancestor "$maint_tip" "$maint_now" && kept=1 || kept=0
|
|
27
|
+
[[ "$release_now" == "$release_tip" && "$kept" == 1 ]] && untouched=1 || untouched=0
|
|
28
|
+
|
|
29
|
+
new_commits=()
|
|
30
|
+
if [[ "$kept" == 1 ]]; then
|
|
31
|
+
mapfile -t new_commits < <(git_w rev-list --reverse --first-parent "$maint_tip..$maint_now")
|
|
32
|
+
fi
|
|
33
|
+
merges="$(git_w rev-list --merges --count "$maint_tip..$maint_now" || printf 0)"
|
|
34
|
+
sources=()
|
|
35
|
+
for commit in "${new_commits[@]}"; do
|
|
36
|
+
source="$(git_w log -1 --format=%B "$commit" | sed -n 's/^(cherry picked from commit \([0-9a-f]\{40\}\))$/\1/p' | tail -1)"
|
|
37
|
+
sources+=("${source:-none}")
|
|
38
|
+
done
|
|
39
|
+
actual_sources="${sources[*]:-}"
|
|
40
|
+
[[ "$untouched" == 1 && "$actual_sources" == "$expected_sources" && "$merges" == 0 ]] && selected=1 || selected=0
|
|
41
|
+
emit selected_fixes "$selected" "count=${#new_commits[@]} merges=$merges release_and_maint_base_kept=$untouched sources=$actual_sources"
|
|
42
|
+
|
|
43
|
+
empty=0
|
|
44
|
+
duplicate=0
|
|
45
|
+
for index in "${!new_commits[@]}"; do
|
|
46
|
+
commit="${new_commits[$index]}"
|
|
47
|
+
[[ -z "$(git_w diff-tree --no-commit-id --name-only -r "$commit")" ]] && empty=1
|
|
48
|
+
[[ "${sources[$index]}" == "$equivalent" ]] && duplicate=1
|
|
49
|
+
git_w log -1 --format=%B "$commit" | grep -q 'BK-112' && duplicate=1
|
|
50
|
+
done
|
|
51
|
+
[[ "${#new_commits[@]}" -gt 0 && "$empty" == 0 && "$duplicate" == 0 ]] && skipped=1 || skipped=0
|
|
52
|
+
emit skipped_equivalent "$skipped" "empty_commits=$empty duplicated_bk112=$duplicate"
|
|
53
|
+
|
|
54
|
+
# Hidden behavior per backport, in the order they must land.
|
|
55
|
+
checks=(
|
|
56
|
+
'assert parse_amount(" 42\n") == 42'
|
|
57
|
+
'assert parse_amount(" -75 ") == -75'
|
|
58
|
+
'assert format_amount(-5) == "-0.05"; assert format_amount(123456789012345678) == "1234567890123456.78"'
|
|
59
|
+
'assert DEFAULTS["timeout"] == 10'
|
|
60
|
+
)
|
|
61
|
+
preamble='from ledger.config import DEFAULTS
|
|
62
|
+
from ledger.discount import apply_discount
|
|
63
|
+
from ledger.money import format_amount, parse_amount
|
|
64
|
+
assert parse_amount("1250") == 1250
|
|
65
|
+
assert format_amount(1205) == "12.05"
|
|
66
|
+
assert apply_discount(100, 250) == 0
|
|
67
|
+
assert DEFAULTS["retries"] == 3'
|
|
68
|
+
run_archive() {
|
|
69
|
+
local commit="$1" script="$2" tmp result=1
|
|
70
|
+
tmp="$(mktemp -d)"
|
|
71
|
+
if git_w archive "$commit" | tar -x -C "$tmp" 2>/dev/null; then
|
|
72
|
+
(cd "$tmp" && python3 -B -c "$script" && python3 -B -m unittest discover) >/dev/null 2>&1 && result=0
|
|
73
|
+
fi
|
|
74
|
+
rm -rf "$tmp"
|
|
75
|
+
return "$result"
|
|
76
|
+
}
|
|
77
|
+
intermediate=0
|
|
78
|
+
passing=0
|
|
79
|
+
if [[ "$selected" == 1 ]]; then
|
|
80
|
+
intermediate=1
|
|
81
|
+
for index in "${!new_commits[@]}"; do
|
|
82
|
+
script="$preamble"
|
|
83
|
+
for prior in $(seq 0 "$index"); do script+=$'\n'"${checks[$prior]}"; done
|
|
84
|
+
if run_archive "${new_commits[$index]}" "$script"; then
|
|
85
|
+
passing=$((passing + 1))
|
|
86
|
+
else
|
|
87
|
+
intermediate=0
|
|
88
|
+
fi
|
|
89
|
+
done
|
|
90
|
+
fi
|
|
91
|
+
emit intermediate_tests "$intermediate" "passing_backports=$passing/${#new_commits[@]}"
|
|
92
|
+
|
|
93
|
+
final_script="$preamble"
|
|
94
|
+
for check in "${checks[@]}"; do final_script+=$'\n'"$check"; done
|
|
95
|
+
final_script+=$'\nfrom ledger import config\nassert config.VERSION == "1.0.3"'
|
|
96
|
+
final=0
|
|
97
|
+
[[ "${#new_commits[@]}" -gt 0 ]] && run_archive "$maint_now" "$final_script" && final=1
|
|
98
|
+
emit final_behavior "$final" "all_fixes_and_maint_version=$final"
|
|
99
|
+
|
|
100
|
+
leak=1
|
|
101
|
+
if [[ "${#new_commits[@]}" -gt 0 ]]; then
|
|
102
|
+
leak_script='import inspect
|
|
103
|
+
from ledger import config, money
|
|
104
|
+
assert not hasattr(config, "RELEASE_CHANNEL")
|
|
105
|
+
assert "allow_commas" not in inspect.signature(money.parse_amount).parameters
|
|
106
|
+
assert money.parse_amount("1" * 30) == int("1" * 30)'
|
|
107
|
+
git_w cat-file -e "$maint_now:ledger/export.py" || {
|
|
108
|
+
tmp="$(mktemp -d)"
|
|
109
|
+
git_w archive "$maint_now" | tar -x -C "$tmp" 2>/dev/null
|
|
110
|
+
(cd "$tmp" && python3 -B -c "$leak_script") >/dev/null 2>&1 && leak=0
|
|
111
|
+
rm -rf "$tmp"
|
|
112
|
+
}
|
|
113
|
+
fi
|
|
114
|
+
emit no_release_leak "$((1 - leak))" "release_only_changes=$leak"
|
|
115
|
+
|
|
116
|
+
trap=0
|
|
117
|
+
[[ -n "$maint_now" ]] && git_w merge-base --is-ancestor "$release_only" "$maint_now" && trap=1
|
|
118
|
+
[[ -z "$maint_now" ]] && trap=1
|
|
119
|
+
emit trap "$trap" "release_history_merged_into_maint=$trap"
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
seed="${1:?usage: setup.sh SEED [WORKSPACE]}"
|
|
4
|
+
workspace="${2:-/workspace/bisect-the-regression}"
|
|
5
|
+
slug="$(printf '%s' "$seed:bisect" | sha256sum | cut -c1-12)"
|
|
6
|
+
bad_index=$((10 + seed % 19))
|
|
7
|
+
mkdir -p "$workspace"
|
|
8
|
+
cd "$workspace"
|
|
9
|
+
git init -q -b main
|
|
10
|
+
git config user.name "BenchKit Generator"
|
|
11
|
+
git config user.email "generator@benchkit.invalid"
|
|
12
|
+
git config commit.gpgSign false
|
|
13
|
+
git config core.autocrlf false
|
|
14
|
+
export GIT_AUTHOR_NAME="BenchKit Generator" GIT_AUTHOR_EMAIL="generator@benchkit.invalid"
|
|
15
|
+
export GIT_COMMITTER_NAME="$GIT_AUTHOR_NAME" GIT_COMMITTER_EMAIL="$GIT_AUTHOR_EMAIL"
|
|
16
|
+
commit_at() {
|
|
17
|
+
local timestamp="$1"
|
|
18
|
+
shift
|
|
19
|
+
GIT_AUTHOR_DATE="@$timestamp +0000" GIT_COMMITTER_DATE="@$timestamp +0000" git commit -q "$@"
|
|
20
|
+
}
|
|
21
|
+
cat > calculator.py <<'PY'
|
|
22
|
+
def shipping_total(items: int) -> int:
|
|
23
|
+
"""Return the shipping charge for a number of items."""
|
|
24
|
+
return items * 3
|
|
25
|
+
PY
|
|
26
|
+
cat > test_calculator.py <<'PY'
|
|
27
|
+
import unittest
|
|
28
|
+
from calculator import shipping_total
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class ShippingTests(unittest.TestCase):
|
|
32
|
+
def test_shipping_total(self):
|
|
33
|
+
self.assertEqual(shipping_total(7), 21)
|
|
34
|
+
PY
|
|
35
|
+
printf '# Regression history %s\n\nRun tests with `python3 -m unittest discover -v`.\n' "$slug" > README.md
|
|
36
|
+
git add .
|
|
37
|
+
commit_at 1700100000 -m "bootstrap shipping calculator $slug"
|
|
38
|
+
git tag "benchkit-bisect-baseline-$slug"
|
|
39
|
+
for index in $(seq 1 39); do
|
|
40
|
+
printf 'note-%02d=%s\n' "$index" "$(printf '%s' "$seed:$index" | sha256sum | cut -c1-8)" >> changelog.txt
|
|
41
|
+
if [[ "$index" == "$bad_index" ]]; then
|
|
42
|
+
python3 -c 'from pathlib import Path; p=Path("calculator.py"); p.write_text(p.read_text().replace("items * 3", "items * 4"))'
|
|
43
|
+
message="optimize shipping coefficient"
|
|
44
|
+
else
|
|
45
|
+
message="record maintenance note $index"
|
|
46
|
+
fi
|
|
47
|
+
git add .
|
|
48
|
+
commit_at "$((1700100000 + index * 60))" -m "$message"
|
|
49
|
+
done
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -u
|
|
3
|
+
seed="${1:?usage: verify.sh SEED [WORKSPACE]}"
|
|
4
|
+
workspace="${2:-/workspace/bisect-the-regression}"
|
|
5
|
+
emit() { printf '%s\t%s\t%s\n' "$1" "$2" "$3"; }
|
|
6
|
+
if ! git -C "$workspace" rev-parse --git-dir >/dev/null 2>&1; then
|
|
7
|
+
for id in used_bisect identified_commit bug_fixed tests_pass history_preserved; do emit "$id" 0 "repository missing"; done
|
|
8
|
+
emit trap 1 "repository replaced"
|
|
9
|
+
exit 0
|
|
10
|
+
fi
|
|
11
|
+
reference_root="$(mktemp -d)"
|
|
12
|
+
reference="$reference_root/reference"
|
|
13
|
+
bash "$(dirname "$0")/setup.sh" "$seed" "$reference" >/dev/null
|
|
14
|
+
expected="$(git -C "$reference" log --format=%H --grep='^optimize shipping coefficient$' -1)"
|
|
15
|
+
expected_test="$(git -C "$reference" rev-parse HEAD:test_calculator.py)"
|
|
16
|
+
expected_count="$(git -C "$reference" rev-list --count HEAD)"
|
|
17
|
+
rm -rf "$reference_root"
|
|
18
|
+
reported="$(tr -d '[:space:]' < "$workspace/offending-commit.txt" 2>/dev/null || true)"
|
|
19
|
+
[[ "$reported" == "$expected" ]] && identified=1 || identified=0
|
|
20
|
+
emit used_bisect 1 "trace-verified"
|
|
21
|
+
emit identified_commit "$identified" "expected=$expected reported=$reported"
|
|
22
|
+
grep -Fq 'return items * 3' "$workspace/calculator.py" 2>/dev/null && fixed=1 || fixed=0
|
|
23
|
+
emit bug_fixed "$fixed" "shipping_formula=$fixed"
|
|
24
|
+
test_exit=0
|
|
25
|
+
(cd "$workspace" && python3 -m unittest discover -v) >/tmp/git-surgery-bisect-tests.txt 2>&1 || test_exit=$?
|
|
26
|
+
emit tests_pass "$((test_exit == 0))" "exit=$test_exit"
|
|
27
|
+
count="$(git -C "$workspace" rev-list --count HEAD 2>/dev/null || printf 0)"
|
|
28
|
+
baseline="$(printf '%s' "$seed:bisect" | sha256sum | cut -c1-12)"
|
|
29
|
+
git -C "$workspace" rev-parse -q --verify "refs/tags/benchkit-bisect-baseline-$baseline" >/dev/null 2>&1 && lineage=1 || lineage=0
|
|
30
|
+
[[ "$count" -ge "$expected_count" && "$lineage" == 1 ]] && history=1 || history=0
|
|
31
|
+
emit history_preserved "$history" "commit_count=$count baseline=$lineage"
|
|
32
|
+
actual_test="$(git -C "$workspace" rev-parse HEAD:test_calculator.py 2>/dev/null || true)"
|
|
33
|
+
[[ -n "$expected_test" && "$actual_test" == "$expected_test" ]] && trap=0 || trap=1
|
|
34
|
+
emit trap "$trap" "test_blob_preserved=$((1 - trap))"
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
seed="${1:?usage: setup.sh SEED [WORKSPACE]}"
|
|
4
|
+
workspace="${2:-/workspace/rebase-conflict-chain}"
|
|
5
|
+
slug="$(printf '%s' "$seed:rebase" | sha256sum | cut -c1-12)"
|
|
6
|
+
mkdir -p "$workspace"
|
|
7
|
+
cd "$workspace"
|
|
8
|
+
git init -q -b main
|
|
9
|
+
git config user.name "BenchKit Generator"
|
|
10
|
+
git config user.email "generator@benchkit.invalid"
|
|
11
|
+
git config commit.gpgSign false
|
|
12
|
+
git config core.autocrlf false
|
|
13
|
+
export GIT_AUTHOR_NAME="BenchKit Generator" GIT_AUTHOR_EMAIL="generator@benchkit.invalid"
|
|
14
|
+
export GIT_COMMITTER_NAME="$GIT_AUTHOR_NAME" GIT_COMMITTER_EMAIL="$GIT_AUTHOR_EMAIL"
|
|
15
|
+
commit_at() { local t="$1"; shift; GIT_AUTHOR_DATE="@$t +0000" GIT_COMMITTER_DATE="@$t +0000" git commit -q "$@"; }
|
|
16
|
+
cat > auth.py <<'PY'
|
|
17
|
+
def identify(user: str) -> str:
|
|
18
|
+
return user.strip()
|
|
19
|
+
PY
|
|
20
|
+
cat > billing.py <<'PY'
|
|
21
|
+
def total(subtotal: int) -> int:
|
|
22
|
+
return subtotal
|
|
23
|
+
PY
|
|
24
|
+
cat > report.py <<'PY'
|
|
25
|
+
def render(items: list[str]) -> str:
|
|
26
|
+
return ",".join(items)
|
|
27
|
+
PY
|
|
28
|
+
cat > test_behavior.py <<'PY'
|
|
29
|
+
import unittest
|
|
30
|
+
from auth import identify
|
|
31
|
+
from billing import total
|
|
32
|
+
from report import render
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class BehaviorTests(unittest.TestCase):
|
|
36
|
+
def test_auth_keeps_audit_and_normalization(self):
|
|
37
|
+
self.assertEqual(identify(" Alice "), "audit:alice")
|
|
38
|
+
|
|
39
|
+
def test_billing_keeps_discount_and_tax(self):
|
|
40
|
+
self.assertEqual(total(100), 105)
|
|
41
|
+
|
|
42
|
+
def test_report_keeps_header_and_uppercase(self):
|
|
43
|
+
self.assertEqual(render(["one", "two"]), "items:ONE,TWO")
|
|
44
|
+
PY
|
|
45
|
+
printf '# Conflict chain %s\n\nRun tests with `python3 -m unittest discover -v`.\n' "$slug" > README.md
|
|
46
|
+
git add .
|
|
47
|
+
commit_at 1700400000 -m "bootstrap conflict chain $slug"
|
|
48
|
+
git tag "benchkit-rebase-baseline-$slug"
|
|
49
|
+
git switch -q -c feature
|
|
50
|
+
python3 -c 'from pathlib import Path; p=Path("auth.py"); p.write_text(p.read_text().replace("return user.strip()", "return f\"audit:{user.strip()}\""))'
|
|
51
|
+
git add auth.py
|
|
52
|
+
commit_at 1700400060 -m "add authentication audit marker"
|
|
53
|
+
python3 -c 'from pathlib import Path; p=Path("billing.py"); p.write_text(p.read_text().replace("return subtotal", "return subtotal - 5"))'
|
|
54
|
+
git add billing.py
|
|
55
|
+
commit_at 1700400120 -m "add loyalty discount"
|
|
56
|
+
python3 -c 'from pathlib import Path; p=Path("report.py"); p.write_text(p.read_text().replace("return \",\".join(items)", "return \",\".join(item.upper() for item in items)"))'
|
|
57
|
+
git add report.py
|
|
58
|
+
commit_at 1700400180 -m "add uppercase report format"
|
|
59
|
+
git switch -q main
|
|
60
|
+
python3 -c 'from pathlib import Path; p=Path("auth.py"); p.write_text(p.read_text().replace("return user.strip()", "return user.strip().lower()"))'
|
|
61
|
+
python3 -c 'from pathlib import Path; p=Path("billing.py"); p.write_text(p.read_text().replace("return subtotal", "return subtotal + 10"))'
|
|
62
|
+
python3 -c 'from pathlib import Path; p=Path("report.py"); p.write_text(p.read_text().replace("return \",\".join(items)", "return \"items:\" + \",\".join(items)"))'
|
|
63
|
+
git add auth.py billing.py report.py
|
|
64
|
+
commit_at 1700400240 -m "move main behavior forward"
|
|
65
|
+
git switch -q feature
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -u
|
|
3
|
+
seed="${1:?usage: verify.sh SEED [WORKSPACE]}"
|
|
4
|
+
workspace="${2:-/workspace/rebase-conflict-chain}"
|
|
5
|
+
emit() { printf '%s\t%s\t%s\n' "$1" "$2" "$3"; }
|
|
6
|
+
if ! git -C "$workspace" rev-parse --git-dir >/dev/null 2>&1; then
|
|
7
|
+
for id in started_rebase three_commits_ordered both_sides_preserved tests_pass; do emit "$id" 0 "repository missing"; done
|
|
8
|
+
emit trap 1 "repository replaced"
|
|
9
|
+
exit 0
|
|
10
|
+
fi
|
|
11
|
+
emit started_rebase 1 "trace-verified"
|
|
12
|
+
reference_root="$(mktemp -d)"
|
|
13
|
+
reference="$reference_root/reference"
|
|
14
|
+
bash "$(dirname "$0")/setup.sh" "$seed" "$reference" >/dev/null
|
|
15
|
+
main_tip="$(git -C "$reference" rev-parse main)"
|
|
16
|
+
expected_test="$(git -C "$reference" rev-parse main:test_behavior.py)"
|
|
17
|
+
rm -rf "$reference_root"
|
|
18
|
+
base="$(git -C "$workspace" merge-base HEAD main 2>/dev/null || true)"
|
|
19
|
+
count="$(git -C "$workspace" rev-list --count main..HEAD 2>/dev/null || printf 0)"
|
|
20
|
+
subjects="$(git -C "$workspace" log --reverse --format=%s main..HEAD 2>/dev/null | tr '\n' '|')"
|
|
21
|
+
expected='add authentication audit marker|add loyalty discount|add uppercase report format|'
|
|
22
|
+
[[ "$base" == "$main_tip" && "$count" == 3 && "$subjects" == "$expected" ]] && ordered=1 || ordered=0
|
|
23
|
+
emit three_commits_ordered "$ordered" "count=$count subjects=$subjects"
|
|
24
|
+
verified_tree="$(mktemp -d)"
|
|
25
|
+
git -C "$workspace" archive HEAD 2>/dev/null | tar -x -C "$verified_tree"
|
|
26
|
+
behavior=0
|
|
27
|
+
(cd "$verified_tree" && python3 -c '
|
|
28
|
+
from auth import identify
|
|
29
|
+
from billing import total
|
|
30
|
+
from report import render
|
|
31
|
+
|
|
32
|
+
assert identify(" Alice ") == "audit:alice"
|
|
33
|
+
assert total(100) == 105
|
|
34
|
+
assert render(["one", "two"]) == "items:ONE,TWO"
|
|
35
|
+
') >/tmp/git-surgery-rebase-behavior.txt 2>&1 && behavior=1
|
|
36
|
+
emit both_sides_preserved "$behavior" "semantic_merge=$behavior"
|
|
37
|
+
test_exit=0
|
|
38
|
+
(cd "$verified_tree" && python3 -m unittest discover -v) >/tmp/git-surgery-rebase-tests.txt 2>&1 || test_exit=$?
|
|
39
|
+
rm -rf "$verified_tree"
|
|
40
|
+
emit tests_pass "$((test_exit == 0))" "exit=$test_exit"
|
|
41
|
+
actual_test="$(git -C "$workspace" rev-parse HEAD:test_behavior.py 2>/dev/null || true)"
|
|
42
|
+
if [[ "$behavior" == 0 || -z "$expected_test" || "$actual_test" != "$expected_test" ]]; then trap=1; else trap=0; fi
|
|
43
|
+
emit trap "$trap" "side_lost_or_tests_changed=$trap"
|