diff --git a/docs/receiving/diff-hunks-6cc6795e89f4/README.md b/docs/receiving/diff-hunks-6cc6795e89f4/README.md new file mode 100644 index 0000000..83e765e --- /dev/null +++ b/docs/receiving/diff-hunks-6cc6795e89f4/README.md @@ -0,0 +1,107 @@ +# Complete unified hunks before automatic target selection + +Final native product source: `40860534a8fdc760b105ac9bc7d14ac9087172dd`, composed with canonical main `cec50d8df4499ba509615e179d82ef3861379bd2`. All 54 focused current-main checks pass, with one existing skip and seven subtests. Independent receiving passes all 11 native Git/GNU fixtures and four malformed CLI cases; the installed console also passes independent positive/refusal receiving. Earlier negative evidence, including the v2 whole-suite deadline failure, remains unchanged. + +## Observed developer failure + +Contributor: `hamon-6cc6795e89f4/estate_production`, an external ChatGPT source contributor under Jacob's current HAMON execution mandate. This is not a resident Za identity or a native goal lease. + +The baseline is canonical TestPilot commit `eacb9907f51aeb9360da654c60cbbd7cf99ad877`, obtained through an ordinary native Git clone into a separate bare repository and worktree. No existing checkout or service was changed. + +A real disposable Git project changes `selected()` from returning one to returning two. Increasing each declared hunk length by one leaves a context record missing. Git rejects that damaged patch with `git apply --reverse --check`. The baseline parser nevertheless counts the empty token after the final LF as a context line and returns a selected target. The native `targets` command succeeds, and the native `run` command generates and executes a passing test, returns exit 0, and publishes its result. + +The original result was retained from its first execution without replay. See `baseline-native-run/`: the damaged diff, exact post-change source, two scripted model responses, generated patch, result report and byte/hash receiving manifest. Its report records two scripted model calls, one passing generated test, a complete JUnit result and status `passed`. No provider call or paid inference occurred. + +Other baseline failures expose missing file headers, malformed or incomplete ranges, exhausted-side counter underflow, extra body records and a damaged later file reaching source selection. The frozen 24-case receiver reports 20 failed negative assertions and four passing compatibility controls. These are expected before-fix failures, not a passing baseline. + +## Source change and behavior + +`parse_unified_diff` now checks the complete declared old/new body counts before it returns any file changes. A damaged later hunk rejects the whole automatic selection before source is read, tests start or model calls occur. Header-shaped deleted or added source lines remain body records while their counts are pending. + +The parser retains LF record boundaries, including Unicode separators inside source literals. It removes only the synthetic empty token after a terminal LF, keeps an actual final record without LF, supports CRLF records and preserves the existing tolerance for a genuinely blank context record whose leading space was stripped. Complete zero-context insertion/deletion, new/deleted files and Git no-newline markers retain their target identities. + +Invalid hunk structure raises `DiffFormatError`, a `ValueError`. The existing `targets` error path returns exit 2 with no success JSON. A narrow additional `run` catch returns exit 2 and a useful input-error message, without a traceback or output directory. + +The physical-line, Git filename decoding, source-root boundary, Python encoding, function mapping, model, sandbox and report implementations are unchanged. Explicit `--target` selection remains governed by its existing resolver, which already parses the supplied diff and wraps malformed input as `TargetSelectionError`. It continues to select exactly the named functions; a diff cannot add targets to that request. + +This validates unified-hunk structure and declared counts. It does not compare removed/context text against a repository revision, apply a patch, or certify that a syntactically complete patch matches the current checkout. The parser is still a target selector, not a substitute for Git's content/application checks. + +## Native qualification + +Runtime: Python 3.14.4, pytest 9.0.2 and Git 2.53.0 on ThinkPad. + +- `baseline.json` and `baseline.txt` retain the original command, pinned source/test hashes and the complete 20-fail / 4-pass result. +- `candidate-v1-suite.json` and `candidate-v1-suite.txt` retain the exact candidate source hashes and the whole current native suite: **398 passed, 4 skipped, 18 subtests passed; exit 0; 229.11 seconds**. +- The new receiver uses actual Git-generated patches and Git's independent rejection of the corrupted patch. It also invokes the real native file/stdin CLI paths and verifies no success output/result is published for damaged input. +- Existing project tests cover Git-quoted and non-UTF-8 filenames, source encoding cookies, physical lines, source boundaries and explicit target selection. Those modules were not edited to obtain the passing result. +- `git diff --check` passed. + +The four skipped cases remain skips; no platform coverage is inferred from them. Native verification is not a deployed-service or model-quality claim. + +## Coordination and integration + +The sole additive advisory source scope is recorded at `/srv/hamon-estate/coord/hamon-6cc6795e89f4-testpilot-hunks.json`. The parent worker received the proposed boundary before production edits. Current project issues and hunk topic searches, the complete 1,141-leaf current source tree, and native advisory records showed no matching hunk-validation claimant. Existing coverage-option, process-cancellation and saved-run comparison/finder owners retain their paths. + +One ordinary GitHub issue creation attempt at 2026-10-08 17:09:41 UTC returned a secondary content-creation rate limit (HTTP 403; request `FD45:22FE9A:1FD6707:68B258B:6AC7CE53`). No issue was created, and no alternate identity, route or immediate retry was used. The refusal is preserved in `github-issue-refusal.json`. + +Current state: implemented, independently received and installed through the declared console entry point in a private versioned native environment. Canonical source publication remains a separate parent-owned step. No managed-service deployment or native goal mutation is claimed. Earlier checkpoint evidence and publication refusal remain intact. + + +## Revision 2: respond to native receiving + +The original production candidate and passing whole-suite result remain immutable in commit `9265ae42bde5b6bd559584a11cbb7c30b7849743`. Evidence-only successor `628f9e3eb0a597d79e8fa9ad58fff62c9681d121` retained the original baseline Python bytes as inert `sample.py.txt`; `retained-paths.json` records that relocation without changing the original execution receipt. + +Independent receiving found that ten ordinary Git/GNU unified-diff cases preserved the baseline's complete file/function mappings, while a valid Git email patch's terminal signature was rejected. Its missing-script CLI challenge also showed automatic mode loading model configuration before discovering the malformed diff. These original successes and failures are retained separately under `docs/receiving/git-compat-6cc6795e89f4/`; they are not rewritten as passes. + +Additional native checks exposed decimal range conversion errors escaping the dedicated input-error class. `oversize-native-run-v1.json` retains the actual CLI exit 1/traceback and absence of an output directory. `range-v1-negative.*` preserves four failing field cases before the numeric fix. `footer-preflight-v1-negative.*` records five failures/four positive controls with only that numeric fix applied: valid mail rejected, a signature accidentally satisfying a missing old record, and model-configuration errors preceding diff rejection. + +Revision 2 makes three bounded changes: + +- Range integer conversion failures become `DiffFormatError`, so all four old/new start/count fields use the clean invalid-diff error path. +- Automatic CLI mode parses hunk structure before `make_client`, alongside the existing explicit-target preflight. The model implementation and runtime loop are unchanged. +- A standard Git email envelope may carry the exact terminal `-- ` delimiter followed by its Git version signature. That metadata cannot complete a missing body record. An identical deleted source record remains a body record, and a footer-shaped extra deletion outside the envelope is still rejected. + +`candidate-v2-targeted.*` records **37 passed, exit 0, 8.18 seconds** with exact frozen source/test hashes. The receiver includes actual Git-generated and Git-accepted email patches, a legitimate deleted `- ` source line, missing old/new/both records, a damaged later file, four oversized range fields, and native preflight against missing model configuration with new and pre-existing output directories. Existing output bytes remain unchanged on refusal. + +Compatibility is claimed for ordinary unified diffs and the standard single Git email envelope/version signature exercised here. This is not a general mail/mbox or custom-signature parser. Placement and repetition of `\\ No newline at end of file` markers retain the existing tolerance; this change does not claim full marker-grammar validation. Source counts do not prove that context/deletions match the current checkout. + + +## Revision 3: qualify the actual custom signature and current main + +The independent receiver's original Git mail fixture uses the valid `--signature=HAMON Receiving Fixture` option. Revision 2's version-only footer gate still rejected that exact input. `independent-receiving-v2.json` retains the unresolved mail case alongside ten passing ordinary Git/GNU cases and four clean native CLI refusals. The original receiver patches were not regenerated or weakened. + +`custom-signature-v2-negative.*` adds native Git custom-signature controls: two failed assertions and three positive controls before the final fix. The final parser recognizes a terminal plain-text signature under the standard Git envelope. Signature lookahead refuses body/file/hunk prefixes, so a footer cannot conceal a later damaged hunk, and pending counts are rejected before consuming the delimiter. This supports the actual default-version and custom-text fixtures without turning the target parser into a general mail/mbox parser. + +Canonical main advanced during receiving through accepted coverage-choice PR #40. It is a single first-parent merge onto the original base, with a CLI `--no-coverage` option and the existing constructor's coverage argument. The final isolated integration worktree merges that accepted source without conflicts; its coverage option and tests are preserved unchanged. The parser and its automatic preflight remain the only new production behavior in this contribution. + +`candidate-v3-integrated-targeted.*` records **49 passed, 1 skipped; exit 0; 19.49 seconds** across all 44 hunk cases and the six coverage CLI cases. Its receipt pins the current parser, composed CLI and both test files. The one coverage skip remains a skip; no untested platform result is inferred. + +`candidate-v2-suite.*` records the complete earlier frozen v2 run accurately: **1 failed, 410 passed, 4 skipped, 18 subtests passed; exit 1; 301.15 seconds**. The failure was `test_real_pytest_timeout_then_fresh_success`: the child pytest process did not reach its helper marker within the fixture's one-second deadline. No hunk assertion failed in that whole-suite run. + +`deadline-isolated-receiving.json` and its two raw outputs retain one controlled sequential replay against original base and current integration. Both pass, with identical `testpilot/sandbox.py` and deadline-test bytes: original base 1 passed in 3.12 seconds; current integration 1 passed in 2.66 seconds. Recorded load averages were roughly 14-16 on eight CPUs. Load is context, not proof of causality, and isolated passes do not relabel the earlier whole-suite failure. Neither the sandbox nor its timeout fixture was edited. + +## Final current-main composition and installed native product + +Accepted Git display-settings PR #42 advanced canonical main to `cec50d8df4499ba509615e179d82ef3861379bd2`. Its raw Git diff flags and all corresponding tests are preserved unchanged in production source commit `40860534a8fdc760b105ac9bc7d14ac9087172dd`. The only production delta from that canonical base is the unified-hunk parser and automatic CLI preflight. + +`candidate-current-main-receiving.json` and its raw output record **54 passed, 1 skipped, 7 subtests passed; exit 0; 62.70 seconds**, covering all hunk cases plus accepted coverage and Git acquisition behavior. Independent v3 evidence in the sibling receiving directory records **11/11** original Git/GNU fixtures and **4/4** clean malformed CLI refusals. These focused results do not replace or relabel the retained v2 full-suite failure. + +The declared `testpilot.__main__:main` console is installed at: + +```text +/home/jacob/testpilot-native-6cc6795e89f4/40860534a8fd/bin/testpilot +``` + +The human launch guide is `/home/jacob/testpilot-native-6cc6795e89f4/README.md`. A source archive at the exact production commit was built and installed offline without dependencies or provider calls. All 11 installed Python source files match that archive. Wheel SHA256: `83c756b33833e004c6421c203888c54862a4eb4995dba1e08bfea3128dec9c4b`; installation receipt SHA256: `86d74c235a0d451fb42712015fb60f3a18c52b98407d67104a0b3ae4f974c0f0`. + +`native-console/` retains the exact installation logs, byte provenance, launch guide, and actual installed-console execution. Its valid Git fixture completes a scripted run with one generated passing test and a real report. Damaged input returns clean exit 2 before deliberately missing model configuration, with both new output absent and all existing result bytes unchanged. The original native receipt SHA256 is `5b7ccec59416d704a155b082d33147cc12c342a08236acb0a8eee2cba6ba3890`. + +`native-console/independent/` preserves an independent prewritten contract, raw receiving receipt and inert receiver source. Outside the source checkout, with PYTHONPATH/PYTHONHOME removed, the installed console passes **2/2** cases: the original real-Git fixture selects all ten expected functions; malformed run exits 2 before missing model setup, pytest or output creation. Its 11 installed Python files and console remain unchanged. Original receipt SHA256: `2c5aa3f6ceccb407ba8cb83160552ffe06c8884a2035a001bbca4c00ab998214`. + +The installed console also selects its own final production delta correctly: exactly `testpilot/__main__.py::main` and `testpilot/diff.py::parse_unified_diff`. All executable fixture source retained here uses inert `.py.txt` names so receiving evidence does not become a production target. This evidence-only successor does not change installed product bytes. + +## Final test-fixture containment review + +Root review at evidence commit `385ded4f4c02b57f98a07ef4beb4a1d2e8a0bf1f` found that the new test helpers inherited Git routing/configuration variables. The retained `fixture-containment-baseline-385ded4.json` records two private sentinel controls: inherited repository/worktree settings changed the sentinel config, and an inherited index path changed the sentinel index. No user repository was used. These baseline failures remain unchanged. + +The test-only successor introduces one shared fixture environment helper: discard inherited `GIT_*` variables, disable system config and use the null file as global config. Both Git fixture commands and fixture CLI invocations use that environment. Product and installed package bytes are unchanged. `candidate-fixture-isolation-receiving.*` records **44 passed in 20.73 seconds, exit 0**, with before/after source hashes. Root independent same-method replay on exact test-fix commit `2585b7857b8087a4f96275294482bee4fecfc316` passes **2/2** controls: all private external repository/worktree and index sentinel bytes remain unchanged, and each fixture owns its own Git directory. The original two baseline failures, final candidate receipt and identical receiving method (retained as inert `fixture-containment-receiver.py.txt`) are preserved. Method SHA256: `410434b1b63f468b15c6b70b4460bce4f92e73aaf7779720e9a15fbc3ab8118c`. diff --git a/docs/receiving/diff-hunks-6cc6795e89f4/baseline-native-run/damaged.diff b/docs/receiving/diff-hunks-6cc6795e89f4/baseline-native-run/damaged.diff new file mode 100644 index 0000000..e1d1a47 --- /dev/null +++ b/docs/receiving/diff-hunks-6cc6795e89f4/baseline-native-run/damaged.diff @@ -0,0 +1,8 @@ +diff --git a/sample.py b/sample.py +index 419cbd0..578716e 100644 +--- a/sample.py ++++ b/sample.py +@@ -1,3 +1,3 @@ + def selected(): +- return 1 ++ return 2 diff --git a/docs/receiving/diff-hunks-6cc6795e89f4/baseline-native-run/project/sample.py.txt b/docs/receiving/diff-hunks-6cc6795e89f4/baseline-native-run/project/sample.py.txt new file mode 100644 index 0000000..578716e --- /dev/null +++ b/docs/receiving/diff-hunks-6cc6795e89f4/baseline-native-run/project/sample.py.txt @@ -0,0 +1,2 @@ +def selected(): + return 2 diff --git a/docs/receiving/diff-hunks-6cc6795e89f4/baseline-native-run/receiving.json b/docs/receiving/diff-hunks-6cc6795e89f4/baseline-native-run/receiving.json new file mode 100644 index 0000000..93fe8e0 --- /dev/null +++ b/docs/receiving/diff-hunks-6cc6795e89f4/baseline-native-run/receiving.json @@ -0,0 +1,110 @@ +{ + "at": "2026-10-08T17:11:42.020979+00:00", + "original_test_path": "/tmp/pytest-of-jacob/pytest-194/test_native_run_cli_refuses_da0", + "copied_after_execution_without_replay": true, + "files": [ + { + "path": "damaged.diff", + "bytes": 158, + "sha256": "93e6854a4ae3f14e2b800fa8f93bcf92b11fd3afaf2d70026b7daac6ddc654e0" + }, + { + "path": "project/sample.py", + "bytes": 29, + "sha256": "0a39e6db6d5e99a973953cc715febd5036bc8da95a19d78b1f91a26c3361a1ca" + }, + { + "path": "script/01_plan.md", + "bytes": 28, + "sha256": "3047ef940de7a0e92dad4a76d76bc5abfcc02619aece28518dff33bac6d735ba" + }, + { + "path": "script/02_tests.md", + "bytes": 113, + "sha256": "b47b02e00d70fe8a81da2aae2e86543d28c4286b3b97c38295d566f1bc7e8c2b" + }, + { + "path": "result/report.json", + "bytes": 4028, + "sha256": "cdad77c59819bc009605a096de77ff3cba9481945f3b7433cecadd7f74ccd771" + }, + { + "path": "result/testpilot.patch", + "bytes": 253, + "sha256": "02912f066519b84d64a9966b216ceef68ecc479720784f53011e8b8b48ecc903" + } + ], + "status": "passed", + "test_count": 1, + "final": { + "returncode": 0, + "timed_out": false, + "duration_s": 0.416, + "timeout_s": 60.0, + "output": ". [100%]\n1 passed in 0.02s\n", + "cases": [ + { + "nodeid": "tests.test_testpilot_generated::test_selected", + "outcome": "passed", + "message": "", + "generated_file": "tests/test_testpilot_generated.py" + } + ], + "coverage": null, + "generated_files": [ + "tests/test_testpilot_generated.py" + ], + "junit_available": true, + "passed": 1, + "failed": 0, + "errors": 0, + "skipped": 0, + "ok": true, + "summary": "1 passed, 0 failed, 0 errors, 0 skipped (rc=0); generated: 1 passed of 1 collected", + "generated": { + "collected": 1, + "passed": 1, + "failed": 0, + "error": 0, + "skipped": 0 + } + }, + "ledger": { + "total_tokens": 280, + "total_cost_usd": 0.0, + "cost_is_complete": false, + "tokens_estimated": true, + "by_model": { + "nvidia/nemotron-3-super-120b-a12b": { + "calls": 1, + "prompt_tokens": 93, + "completion_tokens": 7, + "cost_usd": 0.0 + }, + "nvidia/Nemotron-3_5-Lightning": { + "calls": 1, + "prompt_tokens": 151, + "completion_tokens": 29, + "cost_usd": 0.0 + } + }, + "entries": [ + { + "role": "planner", + "model": "nvidia/nemotron-3-super-120b-a12b", + "prompt_tokens": 93, + "completion_tokens": 7, + "estimated": true, + "cost_usd": 0.0 + }, + { + "role": "editor", + "model": "nvidia/Nemotron-3_5-Lightning", + "prompt_tokens": 151, + "completion_tokens": 29, + "estimated": true, + "cost_usd": 0.0 + } + ] + } +} diff --git a/docs/receiving/diff-hunks-6cc6795e89f4/baseline-native-run/result/report.json b/docs/receiving/diff-hunks-6cc6795e89f4/baseline-native-run/result/report.json new file mode 100644 index 0000000..4888737 --- /dev/null +++ b/docs/receiving/diff-hunks-6cc6795e89f4/baseline-native-run/result/report.json @@ -0,0 +1,145 @@ +{ + "status": "passed", + "changed_functions": [ + { + "path": "sample.py", + "module": "sample", + "qualname": "selected", + "lineno": 1, + "end_lineno": 2, + "changed_lines": [ + 2 + ], + "source": "def selected():\n return 2", + "is_method": false + } + ], + "repair_rounds_used": 0, + "max_repair_rounds": 0, + "test_files": { + "tests/test_testpilot_generated.py": "from sample import selected\ndef test_selected():\n assert selected() == 2\n" + }, + "tests_written": 1, + "final": { + "returncode": 0, + "timed_out": false, + "duration_s": 0.416, + "timeout_s": 60.0, + "output": ". [100%]\n1 passed in 0.02s\n", + "cases": [ + { + "nodeid": "tests.test_testpilot_generated::test_selected", + "outcome": "passed", + "message": "", + "generated_file": "tests/test_testpilot_generated.py" + } + ], + "coverage": null, + "generated_files": [ + "tests/test_testpilot_generated.py" + ], + "junit_available": true, + "passed": 1, + "failed": 0, + "errors": 0, + "skipped": 0, + "ok": true, + "summary": "1 passed, 0 failed, 0 errors, 0 skipped (rc=0); generated: 1 passed of 1 collected", + "generated": { + "collected": 1, + "passed": 1, + "failed": 0, + "error": 0, + "skipped": 0 + } + }, + "patch": "diff --git a/tests/test_testpilot_generated.py b/tests/test_testpilot_generated.py\nnew file mode 100644\n--- /dev/null\n+++ b/tests/test_testpilot_generated.py\n@@ -0,0 +1,3 @@\n+from sample import selected\n+def test_selected():\n+ assert selected() == 2\n", + "coverage": null, + "ledger": { + "total_tokens": 280, + "total_cost_usd": 0.0, + "cost_is_complete": false, + "tokens_estimated": true, + "by_model": { + "nvidia/nemotron-3-super-120b-a12b": { + "calls": 1, + "prompt_tokens": 93, + "completion_tokens": 7, + "cost_usd": 0.0 + }, + "nvidia/Nemotron-3_5-Lightning": { + "calls": 1, + "prompt_tokens": 151, + "completion_tokens": 29, + "cost_usd": 0.0 + } + }, + "entries": [ + { + "role": "planner", + "model": "nvidia/nemotron-3-super-120b-a12b", + "prompt_tokens": 93, + "completion_tokens": 7, + "estimated": true, + "cost_usd": 0.0 + }, + { + "role": "editor", + "model": "nvidia/Nemotron-3_5-Lightning", + "prompt_tokens": 151, + "completion_tokens": 29, + "estimated": true, + "cost_usd": 0.0 + } + ] + }, + "rounds": [ + { + "round": 0, + "kind": "generate", + "files": [ + "tests/test_testpilot_generated.py" + ], + "result": { + "returncode": 0, + "timed_out": false, + "duration_s": 0.416, + "timeout_s": 60.0, + "output": ". [100%]\n1 passed in 0.02s\n", + "cases": [ + { + "nodeid": "tests.test_testpilot_generated::test_selected", + "outcome": "passed", + "message": "", + "generated_file": "tests/test_testpilot_generated.py" + } + ], + "coverage": null, + "generated_files": [ + "tests/test_testpilot_generated.py" + ], + "junit_available": true, + "passed": 1, + "failed": 0, + "errors": 0, + "skipped": 0, + "ok": true, + "summary": "1 passed, 0 failed, 0 errors, 0 skipped (rc=0); generated: 1 passed of 1 collected", + "generated": { + "collected": 1, + "passed": 1, + "failed": 0, + "error": 0, + "skipped": 0 + } + }, + "warnings": [], + "contents": { + "tests/test_testpilot_generated.py": "from sample import selected\ndef test_selected():\n assert selected() == 2\n" + } + } + ], + "plan": "Check selected returns two.\n", + "message": "", + "ok": true +} \ No newline at end of file diff --git a/docs/receiving/diff-hunks-6cc6795e89f4/baseline-native-run/result/testpilot.patch b/docs/receiving/diff-hunks-6cc6795e89f4/baseline-native-run/result/testpilot.patch new file mode 100644 index 0000000..b00c451 --- /dev/null +++ b/docs/receiving/diff-hunks-6cc6795e89f4/baseline-native-run/result/testpilot.patch @@ -0,0 +1,8 @@ +diff --git a/tests/test_testpilot_generated.py b/tests/test_testpilot_generated.py +new file mode 100644 +--- /dev/null ++++ b/tests/test_testpilot_generated.py +@@ -0,0 +1,3 @@ ++from sample import selected ++def test_selected(): ++ assert selected() == 2 diff --git a/docs/receiving/diff-hunks-6cc6795e89f4/baseline-native-run/retained-paths.json b/docs/receiving/diff-hunks-6cc6795e89f4/baseline-native-run/retained-paths.json new file mode 100644 index 0000000..926b2e3 --- /dev/null +++ b/docs/receiving/diff-hunks-6cc6795e89f4/baseline-native-run/retained-paths.json @@ -0,0 +1,11 @@ +{ + "reason": "Keep an inert retained fixture out of TestPilot automatic Python target selection.", + "original_receipt": "receiving.json", + "original_receipt_sha256": "a7856b415faceafd450f683fb5045ec32837a67c01511bc9cfba87b995b4819b", + "original_source_path": "project/sample.py", + "retained_source_path": "project/sample.py.txt", + "sha256": "0a39e6db6d5e99a973953cc715febd5036bc8da95a19d78b1f91a26c3361a1ca", + "source_bytes_unchanged": true, + "original_receipt_unchanged": true, + "original_git_commit": "9265ae42bde5b6bd559584a11cbb7c30b7849743" +} diff --git a/docs/receiving/diff-hunks-6cc6795e89f4/baseline-native-run/script/01_plan.md b/docs/receiving/diff-hunks-6cc6795e89f4/baseline-native-run/script/01_plan.md new file mode 100644 index 0000000..5d07126 --- /dev/null +++ b/docs/receiving/diff-hunks-6cc6795e89f4/baseline-native-run/script/01_plan.md @@ -0,0 +1 @@ +Check selected returns two. diff --git a/docs/receiving/diff-hunks-6cc6795e89f4/baseline-native-run/script/02_tests.md b/docs/receiving/diff-hunks-6cc6795e89f4/baseline-native-run/script/02_tests.md new file mode 100644 index 0000000..b564f0f --- /dev/null +++ b/docs/receiving/diff-hunks-6cc6795e89f4/baseline-native-run/script/02_tests.md @@ -0,0 +1,5 @@ +```python tests/test_selected.py +from sample import selected +def test_selected(): + assert selected() == 2 +``` diff --git a/docs/receiving/diff-hunks-6cc6795e89f4/baseline.json b/docs/receiving/diff-hunks-6cc6795e89f4/baseline.json new file mode 100644 index 0000000..5494777 --- /dev/null +++ b/docs/receiving/diff-hunks-6cc6795e89f4/baseline.json @@ -0,0 +1,48 @@ +{ + "at": "2026-10-08T17:09:22.297675+00:00", + "base": "eacb9907f51aeb9360da654c60cbbd7cf99ad877", + "argv": [ + "/usr/bin/python3", + "-B", + "-m", + "pytest", + "-q", + "tests/test_diff_hunk_validation.py", + "--tb=short" + ], + "exit": 1, + "python": "3.14.4 (main, Aug 20 2026, 10:41:58) [GCC 15.2.0]", + "source_sha256": { + "testpilot/diff.py": "2cf38707ea175bb1f04d55335de344ae943458658055332b4aa68f27763214c0", + "testpilot/__main__.py": "319bc6e8b06615d657530eb35fa2f7f3e6a883a36c98ea1540fdf4c2815eac55", + "tests/test_diff_hunk_validation.py": "1c07374aea3ccdb1245cc24ac322f87fc06ad8f1b4ac87c3ba8e5930b8802085" + }, + "result_tail": [ + "tests/test_diff_hunk_validation.py:146: in test_native_run_cli_refuses_damaged_file_before_model_tests_or_output", + " assert run.returncode == 2", + "E AssertionError: assert 0 == 2", + "E + where 0 = CompletedProcess(args=['/usr/bin/python3', '-B', '-m', 'testpilot', 'run', '--repo', '/tmp/pytest-of-jacob/pytest-194/...da0/result/report.md, /tmp/pytest-of-jacob/pytest-194/test_native_run_cli_refuses_da0/result/report.html\\n', stderr='').returncode", + "=========================== short test summary info ============================", + "FAILED tests/test_diff_hunk_validation.py::test_inconsistent_hunks_are_not_partial_success[truncated-addition]", + "FAILED tests/test_diff_hunk_validation.py::test_inconsistent_hunks_are_not_partial_success[missing-context-at-eof]", + "FAILED tests/test_diff_hunk_validation.py::test_inconsistent_hunks_are_not_partial_success[extra-addition]", + "FAILED tests/test_diff_hunk_validation.py::test_inconsistent_hunks_are_not_partial_success[extra-deletion]", + "FAILED tests/test_diff_hunk_validation.py::test_inconsistent_hunks_are_not_partial_success[extra-context]", + "FAILED tests/test_diff_hunk_validation.py::test_inconsistent_hunks_are_not_partial_success[old-range-underflow]", + "FAILED tests/test_diff_hunk_validation.py::test_inconsistent_hunks_are_not_partial_success[new-range-underflow]", + "FAILED tests/test_diff_hunk_validation.py::test_inconsistent_hunks_are_not_partial_success[context-with-no-old-lines]", + "FAILED tests/test_diff_hunk_validation.py::test_inconsistent_hunks_are_not_partial_success[invalid-body-prefix]", + "FAILED tests/test_diff_hunk_validation.py::test_inconsistent_hunks_are_not_partial_success[malformed-range]", + "FAILED tests/test_diff_hunk_validation.py::test_inconsistent_hunks_are_not_partial_success[missing-file-headers]", + "FAILED tests/test_diff_hunk_validation.py::test_inconsistent_hunks_are_not_partial_success[missing-old-header]", + "FAILED tests/test_diff_hunk_validation.py::test_inconsistent_hunks_are_not_partial_success[headers-without-hunk]", + "FAILED tests/test_diff_hunk_validation.py::test_inconsistent_hunks_are_not_partial_success[next-file-before-end]", + "FAILED tests/test_diff_hunk_validation.py::test_inconsistent_hunks_are_not_partial_success[next-hunk-before-end]", + "FAILED tests/test_diff_hunk_validation.py::test_inconsistent_hunks_are_not_partial_success[nonempty-range-at-zero]", + "FAILED tests/test_diff_hunk_validation.py::test_actual_git_patch_and_corrupted_counter_have_distinct_outcomes", + "FAILED tests/test_diff_hunk_validation.py::test_invalid_later_file_rejects_before_source_reads_and_execution", + "FAILED tests/test_diff_hunk_validation.py::test_native_targets_cli_refuses_damaged_stdin_without_success_json", + "FAILED tests/test_diff_hunk_validation.py::test_native_run_cli_refuses_damaged_file_before_model_tests_or_output", + "20 failed, 4 passed in 1.75s" + ] +} diff --git a/docs/receiving/diff-hunks-6cc6795e89f4/baseline.txt b/docs/receiving/diff-hunks-6cc6795e89f4/baseline.txt new file mode 100644 index 0000000..d626652 --- /dev/null +++ b/docs/receiving/diff-hunks-6cc6795e89f4/baseline.txt @@ -0,0 +1,129 @@ +FFFFFFFFFFFFFFFFFFFF.... [100%] +=================================== FAILURES =================================== +_____ test_inconsistent_hunks_are_not_partial_success[truncated-addition] ______ +tests/test_diff_hunk_validation.py:44: in test_inconsistent_hunks_are_not_partial_success + with pytest.raises(ValueError, match="diff"): + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +E Failed: DID NOT RAISE +___ test_inconsistent_hunks_are_not_partial_success[missing-context-at-eof] ____ +tests/test_diff_hunk_validation.py:44: in test_inconsistent_hunks_are_not_partial_success + with pytest.raises(ValueError, match="diff"): + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +E Failed: DID NOT RAISE +_______ test_inconsistent_hunks_are_not_partial_success[extra-addition] ________ +tests/test_diff_hunk_validation.py:44: in test_inconsistent_hunks_are_not_partial_success + with pytest.raises(ValueError, match="diff"): + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +E Failed: DID NOT RAISE +_______ test_inconsistent_hunks_are_not_partial_success[extra-deletion] ________ +tests/test_diff_hunk_validation.py:44: in test_inconsistent_hunks_are_not_partial_success + with pytest.raises(ValueError, match="diff"): + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +E Failed: DID NOT RAISE +________ test_inconsistent_hunks_are_not_partial_success[extra-context] ________ +tests/test_diff_hunk_validation.py:44: in test_inconsistent_hunks_are_not_partial_success + with pytest.raises(ValueError, match="diff"): + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +E Failed: DID NOT RAISE +_____ test_inconsistent_hunks_are_not_partial_success[old-range-underflow] _____ +tests/test_diff_hunk_validation.py:44: in test_inconsistent_hunks_are_not_partial_success + with pytest.raises(ValueError, match="diff"): + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +E Failed: DID NOT RAISE +_____ test_inconsistent_hunks_are_not_partial_success[new-range-underflow] _____ +tests/test_diff_hunk_validation.py:44: in test_inconsistent_hunks_are_not_partial_success + with pytest.raises(ValueError, match="diff"): + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +E Failed: DID NOT RAISE +__ test_inconsistent_hunks_are_not_partial_success[context-with-no-old-lines] __ +tests/test_diff_hunk_validation.py:44: in test_inconsistent_hunks_are_not_partial_success + with pytest.raises(ValueError, match="diff"): + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +E Failed: DID NOT RAISE +_____ test_inconsistent_hunks_are_not_partial_success[invalid-body-prefix] _____ +tests/test_diff_hunk_validation.py:44: in test_inconsistent_hunks_are_not_partial_success + with pytest.raises(ValueError, match="diff"): + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +E Failed: DID NOT RAISE +_______ test_inconsistent_hunks_are_not_partial_success[malformed-range] _______ +tests/test_diff_hunk_validation.py:44: in test_inconsistent_hunks_are_not_partial_success + with pytest.raises(ValueError, match="diff"): + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +E Failed: DID NOT RAISE +____ test_inconsistent_hunks_are_not_partial_success[missing-file-headers] _____ +tests/test_diff_hunk_validation.py:44: in test_inconsistent_hunks_are_not_partial_success + with pytest.raises(ValueError, match="diff"): + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +E Failed: DID NOT RAISE +_____ test_inconsistent_hunks_are_not_partial_success[missing-old-header] ______ +tests/test_diff_hunk_validation.py:44: in test_inconsistent_hunks_are_not_partial_success + with pytest.raises(ValueError, match="diff"): + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +E Failed: DID NOT RAISE +____ test_inconsistent_hunks_are_not_partial_success[headers-without-hunk] _____ +tests/test_diff_hunk_validation.py:44: in test_inconsistent_hunks_are_not_partial_success + with pytest.raises(ValueError, match="diff"): + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +E Failed: DID NOT RAISE +____ test_inconsistent_hunks_are_not_partial_success[next-file-before-end] _____ +tests/test_diff_hunk_validation.py:44: in test_inconsistent_hunks_are_not_partial_success + with pytest.raises(ValueError, match="diff"): + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +E Failed: DID NOT RAISE +____ test_inconsistent_hunks_are_not_partial_success[next-hunk-before-end] _____ +tests/test_diff_hunk_validation.py:44: in test_inconsistent_hunks_are_not_partial_success + with pytest.raises(ValueError, match="diff"): + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +E Failed: DID NOT RAISE +___ test_inconsistent_hunks_are_not_partial_success[nonempty-range-at-zero] ____ +tests/test_diff_hunk_validation.py:44: in test_inconsistent_hunks_are_not_partial_success + with pytest.raises(ValueError, match="diff"): + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +E Failed: DID NOT RAISE +______ test_actual_git_patch_and_corrupted_counter_have_distinct_outcomes ______ +tests/test_diff_hunk_validation.py:88: in test_actual_git_patch_and_corrupted_counter_have_distinct_outcomes + with pytest.raises(ValueError, match="diff"): + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +E Failed: DID NOT RAISE +______ test_invalid_later_file_rejects_before_source_reads_and_execution _______ +tests/test_diff_hunk_validation.py:105: in test_invalid_later_file_rejects_before_source_reads_and_execution + TestPilot(client).run(repo, damaged) +testpilot/loop.py:418: in run + ??? +testpilot/diff.py:255: in changed_functions + ??? +tests/test_diff_hunk_validation.py:99: in forbidden + raise AssertionError("damaged diff reached source or test execution") +E AssertionError: damaged diff reached source or test execution +______ test_native_targets_cli_refuses_damaged_stdin_without_success_json ______ +tests/test_diff_hunk_validation.py:125: in test_native_targets_cli_refuses_damaged_stdin_without_success_json + assert invalid.returncode == 2 +E assert 0 == 2 +E + where 0 = CompletedProcess(args=['/usr/bin/python3', '-B', '-m', 'testpilot', 'targets', '--repo', '/tmp/pytest-of-jacob/pytest-... 2\n ],\n "source": "def selected():\\n return 2",\n "is_method": false\n }\n ]\n}\n', stderr='').returncode +____ test_native_run_cli_refuses_damaged_file_before_model_tests_or_output _____ +tests/test_diff_hunk_validation.py:146: in test_native_run_cli_refuses_damaged_file_before_model_tests_or_output + assert run.returncode == 2 +E AssertionError: assert 0 == 2 +E + where 0 = CompletedProcess(args=['/usr/bin/python3', '-B', '-m', 'testpilot', 'run', '--repo', '/tmp/pytest-of-jacob/pytest-194/...da0/result/report.md, /tmp/pytest-of-jacob/pytest-194/test_native_run_cli_refuses_da0/result/report.html\n', stderr='').returncode +=========================== short test summary info ============================ +FAILED tests/test_diff_hunk_validation.py::test_inconsistent_hunks_are_not_partial_success[truncated-addition] +FAILED tests/test_diff_hunk_validation.py::test_inconsistent_hunks_are_not_partial_success[missing-context-at-eof] +FAILED tests/test_diff_hunk_validation.py::test_inconsistent_hunks_are_not_partial_success[extra-addition] +FAILED tests/test_diff_hunk_validation.py::test_inconsistent_hunks_are_not_partial_success[extra-deletion] +FAILED tests/test_diff_hunk_validation.py::test_inconsistent_hunks_are_not_partial_success[extra-context] +FAILED tests/test_diff_hunk_validation.py::test_inconsistent_hunks_are_not_partial_success[old-range-underflow] +FAILED tests/test_diff_hunk_validation.py::test_inconsistent_hunks_are_not_partial_success[new-range-underflow] +FAILED tests/test_diff_hunk_validation.py::test_inconsistent_hunks_are_not_partial_success[context-with-no-old-lines] +FAILED tests/test_diff_hunk_validation.py::test_inconsistent_hunks_are_not_partial_success[invalid-body-prefix] +FAILED tests/test_diff_hunk_validation.py::test_inconsistent_hunks_are_not_partial_success[malformed-range] +FAILED tests/test_diff_hunk_validation.py::test_inconsistent_hunks_are_not_partial_success[missing-file-headers] +FAILED tests/test_diff_hunk_validation.py::test_inconsistent_hunks_are_not_partial_success[missing-old-header] +FAILED tests/test_diff_hunk_validation.py::test_inconsistent_hunks_are_not_partial_success[headers-without-hunk] +FAILED tests/test_diff_hunk_validation.py::test_inconsistent_hunks_are_not_partial_success[next-file-before-end] +FAILED tests/test_diff_hunk_validation.py::test_inconsistent_hunks_are_not_partial_success[next-hunk-before-end] +FAILED tests/test_diff_hunk_validation.py::test_inconsistent_hunks_are_not_partial_success[nonempty-range-at-zero] +FAILED tests/test_diff_hunk_validation.py::test_actual_git_patch_and_corrupted_counter_have_distinct_outcomes +FAILED tests/test_diff_hunk_validation.py::test_invalid_later_file_rejects_before_source_reads_and_execution +FAILED tests/test_diff_hunk_validation.py::test_native_targets_cli_refuses_damaged_stdin_without_success_json +FAILED tests/test_diff_hunk_validation.py::test_native_run_cli_refuses_damaged_file_before_model_tests_or_output +20 failed, 4 passed in 1.75s diff --git a/docs/receiving/diff-hunks-6cc6795e89f4/candidate-current-main-receiving.json b/docs/receiving/diff-hunks-6cc6795e89f4/candidate-current-main-receiving.json new file mode 100644 index 0000000..6cc5c13 --- /dev/null +++ b/docs/receiving/diff-hunks-6cc6795e89f4/candidate-current-main-receiving.json @@ -0,0 +1,29 @@ +{ + "source_commit": "40860534a8fdc760b105ac9bc7d14ac9087172dd", + "canonical_main": "cec50d8df4499ba509615e179d82ef3861379bd2", + "command": [ + "/usr/bin/python3", + "-B", + "-m", + "pytest", + "-q", + "--tb=short", + "tests/test_diff_hunk_validation.py", + "tests/test_cli_coverage.py", + "tests/test_cli_git_diff_config.py" + ], + "cwd": "/home/jacob/testpilot-hunks-integration-6cc6795e89f4", + "started": "2026-10-08T17:55:48.558773+00:00", + "finished": "2026-10-08T17:56:52.642857+00:00", + "exit": 0, + "files_sha256": { + "testpilot/diff.py": "586f5b981d2c8d66e47cd8c5566ace994b152cde87f43db9b6e1c02ac5337490", + "testpilot/__main__.py": "45572d34cff56b521d4a48a5db90164fa70ca71d5090e35b8fd0880aecde4420", + "tests/test_diff_hunk_validation.py": "fe9d56da3bca14d31e5f6eb764b20dd9e54ddf0ca9169c75c3c5a6d6dcc09d89", + "tests/test_cli_coverage.py": "d8ebf61e1eacdd51915cfeea1c925bc144ef0481da0ac6d8ffbcc8d4b10e765b", + "tests/test_cli_git_diff_config.py": "f31e4b275c06b1064ac6da89e7463e4647197ab9232d6df71a46fd8de162dd45" + }, + "files_unchanged": true, + "output_sha256": "f7f6a53f00f485feba100a3c13e7f74245a6a0a68a210cd3f0df8301d4c1b23b", + "summary": "54 passed, 1 skipped, 7 subtests passed in 62.70s (0:01:02)" +} diff --git a/docs/receiving/diff-hunks-6cc6795e89f4/candidate-current-main-receiving.txt b/docs/receiving/diff-hunks-6cc6795e89f4/candidate-current-main-receiving.txt new file mode 100644 index 0000000..bfb3152 --- /dev/null +++ b/docs/receiving/diff-hunks-6cc6795e89f4/candidate-current-main-receiving.txt @@ -0,0 +1,2 @@ +..............................................s........ [100%] +54 passed, 1 skipped, 7 subtests passed in 62.70s (0:01:02) diff --git a/docs/receiving/diff-hunks-6cc6795e89f4/candidate-fixture-isolation-receiving.json b/docs/receiving/diff-hunks-6cc6795e89f4/candidate-fixture-isolation-receiving.json new file mode 100644 index 0000000..26b8f35 --- /dev/null +++ b/docs/receiving/diff-hunks-6cc6795e89f4/candidate-fixture-isolation-receiving.json @@ -0,0 +1,28 @@ +{ + "started_at": "2026-10-08T18:43:24.822325+00:00", + "finished_at": "2026-10-08T18:43:48.292880+00:00", + "command": [ + "/usr/bin/python3", + "-B", + "-m", + "pytest", + "-q", + "--tb=short", + "tests/test_diff_hunk_validation.py" + ], + "source_base": "385ded4f4c02b57f98a07ef4beb4a1d2e8a0bf1f", + "purpose": "Only test helpers sanitize inherited Git environment; product source unchanged", + "exit": 0, + "seconds": 23.468345599016175, + "sha256_before": { + "testpilot/diff.py": "586f5b981d2c8d66e47cd8c5566ace994b152cde87f43db9b6e1c02ac5337490", + "testpilot/__main__.py": "45572d34cff56b521d4a48a5db90164fa70ca71d5090e35b8fd0880aecde4420", + "tests/test_diff_hunk_validation.py": "af9f03bf39b00eb042a2cf7e64e76a810aef8093860d71357890318a08380e26" + }, + "sha256_after": { + "testpilot/diff.py": "586f5b981d2c8d66e47cd8c5566ace994b152cde87f43db9b6e1c02ac5337490", + "testpilot/__main__.py": "45572d34cff56b521d4a48a5db90164fa70ca71d5090e35b8fd0880aecde4420", + "tests/test_diff_hunk_validation.py": "af9f03bf39b00eb042a2cf7e64e76a810aef8093860d71357890318a08380e26" + }, + "output_sha256": "0e191574acdc75d99c4706070b20ef3268a1a65fe126469075a3d6a09cb70b2d" +} diff --git a/docs/receiving/diff-hunks-6cc6795e89f4/candidate-fixture-isolation-receiving.txt b/docs/receiving/diff-hunks-6cc6795e89f4/candidate-fixture-isolation-receiving.txt new file mode 100644 index 0000000..bba70dd --- /dev/null +++ b/docs/receiving/diff-hunks-6cc6795e89f4/candidate-fixture-isolation-receiving.txt @@ -0,0 +1,2 @@ +............................................ [100%] +44 passed in 20.73s diff --git a/docs/receiving/diff-hunks-6cc6795e89f4/candidate-v1-suite.json b/docs/receiving/diff-hunks-6cc6795e89f4/candidate-v1-suite.json new file mode 100644 index 0000000..fed0fe4 --- /dev/null +++ b/docs/receiving/diff-hunks-6cc6795e89f4/candidate-v1-suite.json @@ -0,0 +1,26 @@ +{ + "at": "2026-10-08T17:14:31.245760+00:00", + "argv": [ + "/usr/bin/python3", + "-B", + "-m", + "pytest", + "-q", + "--tb=short" + ], + "exit": 0, + "source_sha256": { + "testpilot/diff.py": "b67f7a47f187491090e921f80d790a8eaa16684997f8ac08d504a3a1eff5659f", + "testpilot/__main__.py": "67013a86b7a36c7d0fc56668d29dd97e084d4da4ce358b546633829be840c020", + "tests/test_diff_hunk_validation.py": "1c07374aea3ccdb1245cc24ac322f87fc06ad8f1b4ac87c3ba8e5930b8802085" + }, + "result_tail": [ + "...................................................... [ 13%]", + "........................................................................ [ 31%]", + "........................................................................ [ 49%]", + "..................................s..................................... [ 67%]", + "........................................................................ [ 85%]", + "............................................s.......s...... [100%]", + "398 passed, 4 skipped, 18 subtests passed in 229.11s (0:03:49)" + ] +} diff --git a/docs/receiving/diff-hunks-6cc6795e89f4/candidate-v1-suite.txt b/docs/receiving/diff-hunks-6cc6795e89f4/candidate-v1-suite.txt new file mode 100644 index 0000000..390366e --- /dev/null +++ b/docs/receiving/diff-hunks-6cc6795e89f4/candidate-v1-suite.txt @@ -0,0 +1,7 @@ +...................................................... [ 13%] +........................................................................ [ 31%] +........................................................................ [ 49%] +..................................s..................................... [ 67%] +........................................................................ [ 85%] +............................................s.......s...... [100%] +398 passed, 4 skipped, 18 subtests passed in 229.11s (0:03:49) diff --git a/docs/receiving/diff-hunks-6cc6795e89f4/candidate-v2-suite.json b/docs/receiving/diff-hunks-6cc6795e89f4/candidate-v2-suite.json new file mode 100644 index 0000000..a346f5b --- /dev/null +++ b/docs/receiving/diff-hunks-6cc6795e89f4/candidate-v2-suite.json @@ -0,0 +1,24 @@ +{ + "command": [ + "/usr/bin/python3", + "-B", + "-m", + "pytest", + "-q", + "--tb=short" + ], + "cwd": "/home/jacob/testpilot-hunks-6cc6795e89f4", + "source_commit": "0dc429c3012b03df2e703fad2c35aca452069cf1", + "started": "2026-10-08T17:35:16.700603+00:00", + "finished": "2026-10-08T17:40:20.998349+00:00", + "elapsed_seconds": 304.3, + "exit": 1, + "files_sha256": { + "testpilot/diff.py": "3995b19d6d68867efa1ef73f9f20db93f95d34f05cdc9b760e9f71d7e2caa69a", + "testpilot/__main__.py": "f5e644cb4f6bb9278657939bd6dc51390a974f7f8834efb8459e11fbce4a95cd", + "tests/test_diff_hunk_validation.py": "1ac50251037a2a4d355fdb1993c9c8ee4d2a0f892b29e947e4068969800ccf0a" + }, + "files_unchanged": true, + "output_sha256": "adfacc2268d6d98d54ff56acf42244d46ad56d794220182a479884a5dc0ead08", + "summary": "1 failed, 410 passed, 4 skipped, 18 subtests passed in 301.15s (0:05:01)" +} diff --git a/docs/receiving/diff-hunks-6cc6795e89f4/candidate-v2-suite.txt b/docs/receiving/diff-hunks-6cc6795e89f4/candidate-v2-suite.txt new file mode 100644 index 0000000..22b3d92 --- /dev/null +++ b/docs/receiving/diff-hunks-6cc6795e89f4/candidate-v2-suite.txt @@ -0,0 +1,17 @@ +...................................................... [ 13%] +........................................................................ [ 30%] +........................................................................ [ 47%] +...............................................s........................ [ 65%] +........................................................................ [ 82%] +.........................................................s.......s.....F [100%] +=================================== FAILURES =================================== +_________________ test_real_pytest_timeout_then_fresh_success __________________ +tests/test_sandbox_deadline.py:103: in test_real_pytest_timeout_then_fresh_success + assert marker.exists(), "the actual pytest test must start its helper" +E AssertionError: the actual pytest test must start its helper +E assert False +E + where False = exists() +E + where exists = PosixPath('/tmp/pytest-of-jacob/pytest-211/test_real_pytest_timeout_then_0/pytest-helper.pid').exists +=========================== short test summary info ============================ +FAILED tests/test_sandbox_deadline.py::test_real_pytest_timeout_then_fresh_success +1 failed, 410 passed, 4 skipped, 18 subtests passed in 301.15s (0:05:01) diff --git a/docs/receiving/diff-hunks-6cc6795e89f4/candidate-v2-targeted.json b/docs/receiving/diff-hunks-6cc6795e89f4/candidate-v2-targeted.json new file mode 100644 index 0000000..247b122 --- /dev/null +++ b/docs/receiving/diff-hunks-6cc6795e89f4/candidate-v2-targeted.json @@ -0,0 +1,21 @@ +{ + "command": [ + "/usr/bin/python3", + "-B", + "-m", + "pytest", + "-q", + "--tb=short", + "tests/test_diff_hunk_validation.py" + ], + "cwd": "/home/jacob/testpilot-hunks-6cc6795e89f4", + "started": "2026-10-08T17:33:16.635916+00:00", + "finished": "2026-10-08T17:33:27.318761+00:00", + "exit": 0, + "files_sha256": { + "testpilot/diff.py": "3995b19d6d68867efa1ef73f9f20db93f95d34f05cdc9b760e9f71d7e2caa69a", + "testpilot/__main__.py": "f5e644cb4f6bb9278657939bd6dc51390a974f7f8834efb8459e11fbce4a95cd", + "tests/test_diff_hunk_validation.py": "1ac50251037a2a4d355fdb1993c9c8ee4d2a0f892b29e947e4068969800ccf0a" + }, + "output_sha256": "5bb920c804fd5b240b8d2d66a73a811beeb1d3d08111f0ae2c1efcdf5bfc8991" +} diff --git a/docs/receiving/diff-hunks-6cc6795e89f4/candidate-v2-targeted.txt b/docs/receiving/diff-hunks-6cc6795e89f4/candidate-v2-targeted.txt new file mode 100644 index 0000000..86297d1 --- /dev/null +++ b/docs/receiving/diff-hunks-6cc6795e89f4/candidate-v2-targeted.txt @@ -0,0 +1,2 @@ +..................................... [100%] +37 passed in 8.18s diff --git a/docs/receiving/diff-hunks-6cc6795e89f4/candidate-v3-integrated-targeted.json b/docs/receiving/diff-hunks-6cc6795e89f4/candidate-v3-integrated-targeted.json new file mode 100644 index 0000000..5db6dd9 --- /dev/null +++ b/docs/receiving/diff-hunks-6cc6795e89f4/candidate-v3-integrated-targeted.json @@ -0,0 +1,26 @@ +{ + "command": [ + "/usr/bin/python3", + "-B", + "-m", + "pytest", + "-q", + "--tb=short", + "tests/test_diff_hunk_validation.py", + "tests/test_cli_coverage.py" + ], + "cwd": "/home/jacob/testpilot-hunks-integration-6cc6795e89f4", + "started": "2026-10-08T17:42:15.019328+00:00", + "finished": "2026-10-08T17:42:35.377969+00:00", + "exit": 0, + "canonical_base": "191cca4e416286a9e1fa4b5d5daf5fc30b936db6", + "files_sha256": { + "testpilot/diff.py": "586f5b981d2c8d66e47cd8c5566ace994b152cde87f43db9b6e1c02ac5337490", + "testpilot/__main__.py": "32bb84da429e297e82aa9121e54764e615eadfd9ea8400b7f0ce804560d7cf16", + "tests/test_diff_hunk_validation.py": "fe9d56da3bca14d31e5f6eb764b20dd9e54ddf0ca9169c75c3c5a6d6dcc09d89", + "tests/test_cli_coverage.py": "d8ebf61e1eacdd51915cfeea1c925bc144ef0481da0ac6d8ffbcc8d4b10e765b" + }, + "files_unchanged": true, + "output_sha256": "bb697bd096c056db918fdb3819a74c8dff6769fc33d1a348db1e25f23d727ed8", + "summary": "49 passed, 1 skipped in 19.49s" +} diff --git a/docs/receiving/diff-hunks-6cc6795e89f4/candidate-v3-integrated-targeted.txt b/docs/receiving/diff-hunks-6cc6795e89f4/candidate-v3-integrated-targeted.txt new file mode 100644 index 0000000..f3506ca --- /dev/null +++ b/docs/receiving/diff-hunks-6cc6795e89f4/candidate-v3-integrated-targeted.txt @@ -0,0 +1,2 @@ +..............................................s... [100%] +49 passed, 1 skipped in 19.49s diff --git a/docs/receiving/diff-hunks-6cc6795e89f4/custom-signature-v2-negative.json b/docs/receiving/diff-hunks-6cc6795e89f4/custom-signature-v2-negative.json new file mode 100644 index 0000000..17379de --- /dev/null +++ b/docs/receiving/diff-hunks-6cc6795e89f4/custom-signature-v2-negative.json @@ -0,0 +1,24 @@ +{ + "command": [ + "/usr/bin/python3", + "-B", + "-m", + "pytest", + "-q", + "--tb=short", + "tests/test_diff_hunk_validation.py", + "-k", + "custom" + ], + "cwd": "/home/jacob/testpilot-hunks-integration-6cc6795e89f4", + "started": "2026-10-08T17:40:47.753945+00:00", + "finished": "2026-10-08T17:40:53.490840+00:00", + "exit": 1, + "checkpoint": "V2 parser; current accepted coverage CLI composed separately; new actual Git custom-signature fixture cases", + "files_sha256": { + "testpilot/diff.py": "3995b19d6d68867efa1ef73f9f20db93f95d34f05cdc9b760e9f71d7e2caa69a", + "testpilot/__main__.py": "32bb84da429e297e82aa9121e54764e615eadfd9ea8400b7f0ce804560d7cf16", + "tests/test_diff_hunk_validation.py": "b3ef8b04a1b195ea67cc502e187d39996e1a2ecad286c47a9a90e3a6555e52bd" + }, + "output_sha256": "f98b30b2ad09188be2b48e3ea25cf6c30cf57257ea71ee73326e95a4ea797ba5" +} diff --git a/docs/receiving/diff-hunks-6cc6795e89f4/custom-signature-v2-negative.txt b/docs/receiving/diff-hunks-6cc6795e89f4/custom-signature-v2-negative.txt new file mode 100644 index 0000000..c0beb73 --- /dev/null +++ b/docs/receiving/diff-hunks-6cc6795e89f4/custom-signature-v2-negative.txt @@ -0,0 +1,21 @@ +FF... [100%] +=================================== FAILURES =================================== +_ test_actual_git_mail_footer_preserves_complete_target_selection[custom-text] _ +tests/test_diff_hunk_validation.py:241: in test_actual_git_mail_footer_preserves_complete_target_selection + selected = changed_functions(repo, patch) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +testpilot/diff.py:318: in changed_functions + for fc in parse_unified_diff(diff_text): + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +testpilot/diff.py:215: in parse_unified_diff + raise invalid("body record exceeds the declared hunk ranges") +E testpilot.diff.DiffFormatError: invalid unified diff at record 15: body record exceeds the declared hunk ranges +_ test_git_mail_footer_never_completes_a_missing_body_record[custom-text-old] __ +tests/test_diff_hunk_validation.py:257: in test_git_mail_footer_never_completes_a_missing_body_record + with pytest.raises(DiffFormatError, match="diff"): + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +E Failed: DID NOT RAISE +=========================== short test summary info ============================ +FAILED tests/test_diff_hunk_validation.py::test_actual_git_mail_footer_preserves_complete_target_selection[custom-text] +FAILED tests/test_diff_hunk_validation.py::test_git_mail_footer_never_completes_a_missing_body_record[custom-text-old] +2 failed, 3 passed, 37 deselected in 2.95s diff --git a/docs/receiving/diff-hunks-6cc6795e89f4/deadline-isolated-baseline.txt b/docs/receiving/diff-hunks-6cc6795e89f4/deadline-isolated-baseline.txt new file mode 100644 index 0000000..0a650ee --- /dev/null +++ b/docs/receiving/diff-hunks-6cc6795e89f4/deadline-isolated-baseline.txt @@ -0,0 +1,2 @@ +. [100%] +1 passed in 3.12s diff --git a/docs/receiving/diff-hunks-6cc6795e89f4/deadline-isolated-current.txt b/docs/receiving/diff-hunks-6cc6795e89f4/deadline-isolated-current.txt new file mode 100644 index 0000000..6536bcd --- /dev/null +++ b/docs/receiving/diff-hunks-6cc6795e89f4/deadline-isolated-current.txt @@ -0,0 +1,2 @@ +. [100%] +1 passed in 2.66s diff --git a/docs/receiving/diff-hunks-6cc6795e89f4/deadline-isolated-receiving.json b/docs/receiving/diff-hunks-6cc6795e89f4/deadline-isolated-receiving.json new file mode 100644 index 0000000..d99681a --- /dev/null +++ b/docs/receiving/diff-hunks-6cc6795e89f4/deadline-isolated-receiving.json @@ -0,0 +1,82 @@ +{ + "scope": "One controlled sequential replay of the unchanged native deadline fixture on original base and current integration. No deadline or sandbox edits; load readings are context, not proof of causality.", + "original_full_suite_failure": "candidate-v2-suite.json / candidate-v2-suite.txt", + "receiving": [ + { + "variant": "baseline", + "cwd": "/home/jacob/testpilot-hunks-deadline-baseline-6cc6795e89f4", + "command": [ + "/usr/bin/python3", + "-B", + "-m", + "pytest", + "-q", + "--tb=short", + "tests/test_sandbox_deadline.py::test_real_pytest_timeout_then_fresh_success" + ], + "before": { + "at": "2026-10-08T17:45:09.355874+00:00", + "load_1_5_15": [ + 14.41015625, + 16.4677734375, + 16.33740234375 + ], + "cpu_count": 8 + }, + "after": { + "at": "2026-10-08T17:45:15.500296+00:00", + "load_1_5_15": [ + 14.69775390625, + 16.49365234375, + 16.3466796875 + ] + }, + "elapsed_seconds": 6.144, + "exit": 0, + "files_sha256": { + "testpilot/sandbox.py": "8d5aadafff5188473ec45eb1cd8f01fb562836ff05bf12a27939cde83b33472a", + "tests/test_sandbox_deadline.py": "6a9abfdc79bcb2cc3102d94b6b61d20ea09b0f89d5c7c5842387ce77c136d7e1" + }, + "output_sha256": "efd82ce3b5ad31657e7eb28c8e129cb14185c784f4d957bb4b0e70170268b09a", + "summary": "1 passed in 3.12s" + }, + { + "variant": "current", + "cwd": "/home/jacob/testpilot-hunks-integration-6cc6795e89f4", + "command": [ + "/usr/bin/python3", + "-B", + "-m", + "pytest", + "-q", + "--tb=short", + "tests/test_sandbox_deadline.py::test_real_pytest_timeout_then_fresh_success" + ], + "before": { + "at": "2026-10-08T17:45:15.500489+00:00", + "load_1_5_15": [ + 14.69775390625, + 16.49365234375, + 16.3466796875 + ], + "cpu_count": 8 + }, + "after": { + "at": "2026-10-08T17:45:18.858640+00:00", + "load_1_5_15": [ + 14.2412109375, + 16.369140625, + 16.30712890625 + ] + }, + "elapsed_seconds": 3.358, + "exit": 0, + "files_sha256": { + "testpilot/sandbox.py": "8d5aadafff5188473ec45eb1cd8f01fb562836ff05bf12a27939cde83b33472a", + "tests/test_sandbox_deadline.py": "6a9abfdc79bcb2cc3102d94b6b61d20ea09b0f89d5c7c5842387ce77c136d7e1" + }, + "output_sha256": "13bca62673148710b531a24ec87c2103ff741d3bd74fed53ae70dac529468609", + "summary": "1 passed in 2.66s" + } + ] +} diff --git a/docs/receiving/diff-hunks-6cc6795e89f4/fixture-containment-baseline-385ded4.json b/docs/receiving/diff-hunks-6cc6795e89f4/fixture-containment-baseline-385ded4.json new file mode 100644 index 0000000..0572668 --- /dev/null +++ b/docs/receiving/diff-hunks-6cc6795e89f4/fixture-containment-baseline-385ded4.json @@ -0,0 +1,191 @@ +{ + "schema": "hamon.independent-fixture-containment.v1", + "source_head": "385ded4f4c02b57f98a07ef4beb4a1d2e8a0bf1f", + "test_sha256": "fe9d56da3bca14d31e5f6eb764b20dd9e54ddf0ca9169c75c3c5a6d6dcc09d89", + "reviewer": "estate-6cc6795e89f4/root", + "method": "Invoke the exact authored _git helper with privately owned external Git directory/worktree and index controls; compare all sentinel bytes before and after. No user repository or external provider is a test target.", + "results": [ + { + "case": "git_dir_and_work_tree", + "private_sentinel_only": true, + "inherited_routing_keys": [ + "GIT_DIR", + "GIT_WORK_TREE" + ], + "commands": [ + { + "args": [ + "init", + "-q" + ], + "returncode": 0, + "stdout": "", + "stderr": "" + }, + { + "args": [ + "config", + "core.autocrlf", + "false" + ], + "returncode": 0, + "stdout": "", + "stderr": "" + }, + { + "args": [ + "add", + "sample.py" + ], + "returncode": 0, + "stdout": "", + "stderr": "" + } + ], + "error": null, + "sentinel_before": { + ".git/HEAD": "f6f2b945f6c411b02ba3da9c7ace88dcf71b6af65ba2e0d89aa82900042b5a10", + ".git/config": "cfe7ba1238c9a78be7535d7c63bcaf5a4d5011d46b07c9b45d3bbf7d6c312dfe", + ".git/description": "85ab6c163d43a17ea9cf7788308bca1466f1b0a8d1cc92e26e9bf63da4062aee", + ".git/hooks/applypatch-msg.sample": "0223497a0b8b033aa58a3a521b8629869386cf7ab0e2f101963d328aa62193f7", + ".git/hooks/commit-msg.sample": "1f74d5e9292979b573ebd59741d46cb93ff391acdd083d340b94370753d92437", + ".git/hooks/fsmonitor-watchman.sample": "e0549964e93897b519bd8e333c037e51fff0f88ba13e086a331592bf801fa1d0", + ".git/hooks/post-update.sample": "81765af2daef323061dcbc5e61fc16481cb74b3bac9ad8a174b186523586f6c5", + ".git/hooks/pre-applypatch.sample": "e15c5b469ea3e0a695bea6f2c82bcf8e62821074939ddd85b77e0007ff165475", + ".git/hooks/pre-commit.sample": "57185b7b9f05239d7ab52db045f5b89eb31348d7b2177eab214f5eb872e1971b", + ".git/hooks/pre-merge-commit.sample": "d3825a70337940ebbd0a5c072984e13245920cdf8898bd225c8d27a6dfc9cb53", + ".git/hooks/pre-push.sample": "ecce9c7e04d3f5dd9d8ada81753dd1d549a9634b26770042b58dda00217d086a", + ".git/hooks/pre-rebase.sample": "4febce867790052338076f4e66cc47efb14879d18097d1d61c8261859eaaa7b3", + ".git/hooks/pre-receive.sample": "a4c3d2b9c7bb3fd8d1441c31bd4ee71a595d66b44fcf49ddb310252320169989", + ".git/hooks/prepare-commit-msg.sample": "e9ddcaa4189fddd25ed97fc8c789eca7b6ca16390b2392ae3276f0c8e1aa4619", + ".git/hooks/push-to-checkout.sample": "a53d0741798b287c6dd7afa64aee473f305e65d3f49463bb9d7408ec3b12bf5f", + ".git/hooks/sendemail-validate.sample": "44ebfc923dc5466bc009602f0ecf067b9c65459abfe8868ddc49b78e6ced7a92", + ".git/hooks/update.sample": "8d5f2fa83e103cf08b57eaa67521df9194f45cbdbcb37da52ad586097a14d106", + ".git/index": "b4568b9f9f671ade27bcf15d78adf2694a9376277109377c936d23c11532d74e", + ".git/info/exclude": "6671fe83b7a07c8932ee89164d1f2793b2318058eb8b98dc5c06ee0a5a3b0ec1", + ".git/objects/cc/0164cd333fb5641b4b7d5470801c92c0d8cc97": "f5355023927a43219fba4df718a5066e7dcb66be566f45d4c8cdcc31f5e31a68", + "sample.py": "d5dfe92bc7b6cb302abb83bf2266c3a3bf41c80ea6619c4ea79af29c710b98f7" + }, + "sentinel_after": { + ".git/HEAD": "f6f2b945f6c411b02ba3da9c7ace88dcf71b6af65ba2e0d89aa82900042b5a10", + ".git/config": "c5419ba1836894f40fb10ce7c802a329da5645a44774f8d0819a5966b4a336d0", + ".git/description": "85ab6c163d43a17ea9cf7788308bca1466f1b0a8d1cc92e26e9bf63da4062aee", + ".git/hooks/applypatch-msg.sample": "0223497a0b8b033aa58a3a521b8629869386cf7ab0e2f101963d328aa62193f7", + ".git/hooks/commit-msg.sample": "1f74d5e9292979b573ebd59741d46cb93ff391acdd083d340b94370753d92437", + ".git/hooks/fsmonitor-watchman.sample": "e0549964e93897b519bd8e333c037e51fff0f88ba13e086a331592bf801fa1d0", + ".git/hooks/post-update.sample": "81765af2daef323061dcbc5e61fc16481cb74b3bac9ad8a174b186523586f6c5", + ".git/hooks/pre-applypatch.sample": "e15c5b469ea3e0a695bea6f2c82bcf8e62821074939ddd85b77e0007ff165475", + ".git/hooks/pre-commit.sample": "57185b7b9f05239d7ab52db045f5b89eb31348d7b2177eab214f5eb872e1971b", + ".git/hooks/pre-merge-commit.sample": "d3825a70337940ebbd0a5c072984e13245920cdf8898bd225c8d27a6dfc9cb53", + ".git/hooks/pre-push.sample": "ecce9c7e04d3f5dd9d8ada81753dd1d549a9634b26770042b58dda00217d086a", + ".git/hooks/pre-rebase.sample": "4febce867790052338076f4e66cc47efb14879d18097d1d61c8261859eaaa7b3", + ".git/hooks/pre-receive.sample": "a4c3d2b9c7bb3fd8d1441c31bd4ee71a595d66b44fcf49ddb310252320169989", + ".git/hooks/prepare-commit-msg.sample": "e9ddcaa4189fddd25ed97fc8c789eca7b6ca16390b2392ae3276f0c8e1aa4619", + ".git/hooks/push-to-checkout.sample": "a53d0741798b287c6dd7afa64aee473f305e65d3f49463bb9d7408ec3b12bf5f", + ".git/hooks/sendemail-validate.sample": "44ebfc923dc5466bc009602f0ecf067b9c65459abfe8868ddc49b78e6ced7a92", + ".git/hooks/update.sample": "8d5f2fa83e103cf08b57eaa67521df9194f45cbdbcb37da52ad586097a14d106", + ".git/index": "b4568b9f9f671ade27bcf15d78adf2694a9376277109377c936d23c11532d74e", + ".git/info/exclude": "6671fe83b7a07c8932ee89164d1f2793b2318058eb8b98dc5c06ee0a5a3b0ec1", + ".git/objects/cc/0164cd333fb5641b4b7d5470801c92c0d8cc97": "f5355023927a43219fba4df718a5066e7dcb66be566f45d4c8cdcc31f5e31a68", + "sample.py": "d5dfe92bc7b6cb302abb83bf2266c3a3bf41c80ea6619c4ea79af29c710b98f7" + }, + "changed_sentinel_paths": [ + ".git/config" + ], + "fixture_has_own_git_directory": false, + "passed": false + }, + { + "case": "external_index", + "private_sentinel_only": true, + "inherited_routing_keys": [ + "GIT_INDEX_FILE" + ], + "commands": [ + { + "args": [ + "init", + "-q" + ], + "returncode": 0, + "stdout": "", + "stderr": "" + }, + { + "args": [ + "config", + "core.autocrlf", + "false" + ], + "returncode": 0, + "stdout": "", + "stderr": "" + }, + { + "args": [ + "add", + "sample.py" + ], + "returncode": 0, + "stdout": "", + "stderr": "" + } + ], + "error": null, + "sentinel_before": { + ".git/HEAD": "f6f2b945f6c411b02ba3da9c7ace88dcf71b6af65ba2e0d89aa82900042b5a10", + ".git/config": "cfe7ba1238c9a78be7535d7c63bcaf5a4d5011d46b07c9b45d3bbf7d6c312dfe", + ".git/description": "85ab6c163d43a17ea9cf7788308bca1466f1b0a8d1cc92e26e9bf63da4062aee", + ".git/hooks/applypatch-msg.sample": "0223497a0b8b033aa58a3a521b8629869386cf7ab0e2f101963d328aa62193f7", + ".git/hooks/commit-msg.sample": "1f74d5e9292979b573ebd59741d46cb93ff391acdd083d340b94370753d92437", + ".git/hooks/fsmonitor-watchman.sample": "e0549964e93897b519bd8e333c037e51fff0f88ba13e086a331592bf801fa1d0", + ".git/hooks/post-update.sample": "81765af2daef323061dcbc5e61fc16481cb74b3bac9ad8a174b186523586f6c5", + ".git/hooks/pre-applypatch.sample": "e15c5b469ea3e0a695bea6f2c82bcf8e62821074939ddd85b77e0007ff165475", + ".git/hooks/pre-commit.sample": "57185b7b9f05239d7ab52db045f5b89eb31348d7b2177eab214f5eb872e1971b", + ".git/hooks/pre-merge-commit.sample": "d3825a70337940ebbd0a5c072984e13245920cdf8898bd225c8d27a6dfc9cb53", + ".git/hooks/pre-push.sample": "ecce9c7e04d3f5dd9d8ada81753dd1d549a9634b26770042b58dda00217d086a", + ".git/hooks/pre-rebase.sample": "4febce867790052338076f4e66cc47efb14879d18097d1d61c8261859eaaa7b3", + ".git/hooks/pre-receive.sample": "a4c3d2b9c7bb3fd8d1441c31bd4ee71a595d66b44fcf49ddb310252320169989", + ".git/hooks/prepare-commit-msg.sample": "e9ddcaa4189fddd25ed97fc8c789eca7b6ca16390b2392ae3276f0c8e1aa4619", + ".git/hooks/push-to-checkout.sample": "a53d0741798b287c6dd7afa64aee473f305e65d3f49463bb9d7408ec3b12bf5f", + ".git/hooks/sendemail-validate.sample": "44ebfc923dc5466bc009602f0ecf067b9c65459abfe8868ddc49b78e6ced7a92", + ".git/hooks/update.sample": "8d5f2fa83e103cf08b57eaa67521df9194f45cbdbcb37da52ad586097a14d106", + ".git/index": "8e7abe784214b71ff749e3da55714cab297590bc47106ab50f0e1972431becef", + ".git/info/exclude": "6671fe83b7a07c8932ee89164d1f2793b2318058eb8b98dc5c06ee0a5a3b0ec1", + ".git/objects/cc/0164cd333fb5641b4b7d5470801c92c0d8cc97": "f5355023927a43219fba4df718a5066e7dcb66be566f45d4c8cdcc31f5e31a68", + "external.index": "8e7abe784214b71ff749e3da55714cab297590bc47106ab50f0e1972431becef", + "sample.py": "d5dfe92bc7b6cb302abb83bf2266c3a3bf41c80ea6619c4ea79af29c710b98f7" + }, + "sentinel_after": { + ".git/HEAD": "f6f2b945f6c411b02ba3da9c7ace88dcf71b6af65ba2e0d89aa82900042b5a10", + ".git/config": "cfe7ba1238c9a78be7535d7c63bcaf5a4d5011d46b07c9b45d3bbf7d6c312dfe", + ".git/description": "85ab6c163d43a17ea9cf7788308bca1466f1b0a8d1cc92e26e9bf63da4062aee", + ".git/hooks/applypatch-msg.sample": "0223497a0b8b033aa58a3a521b8629869386cf7ab0e2f101963d328aa62193f7", + ".git/hooks/commit-msg.sample": "1f74d5e9292979b573ebd59741d46cb93ff391acdd083d340b94370753d92437", + ".git/hooks/fsmonitor-watchman.sample": "e0549964e93897b519bd8e333c037e51fff0f88ba13e086a331592bf801fa1d0", + ".git/hooks/post-update.sample": "81765af2daef323061dcbc5e61fc16481cb74b3bac9ad8a174b186523586f6c5", + ".git/hooks/pre-applypatch.sample": "e15c5b469ea3e0a695bea6f2c82bcf8e62821074939ddd85b77e0007ff165475", + ".git/hooks/pre-commit.sample": "57185b7b9f05239d7ab52db045f5b89eb31348d7b2177eab214f5eb872e1971b", + ".git/hooks/pre-merge-commit.sample": "d3825a70337940ebbd0a5c072984e13245920cdf8898bd225c8d27a6dfc9cb53", + ".git/hooks/pre-push.sample": "ecce9c7e04d3f5dd9d8ada81753dd1d549a9634b26770042b58dda00217d086a", + ".git/hooks/pre-rebase.sample": "4febce867790052338076f4e66cc47efb14879d18097d1d61c8261859eaaa7b3", + ".git/hooks/pre-receive.sample": "a4c3d2b9c7bb3fd8d1441c31bd4ee71a595d66b44fcf49ddb310252320169989", + ".git/hooks/prepare-commit-msg.sample": "e9ddcaa4189fddd25ed97fc8c789eca7b6ca16390b2392ae3276f0c8e1aa4619", + ".git/hooks/push-to-checkout.sample": "a53d0741798b287c6dd7afa64aee473f305e65d3f49463bb9d7408ec3b12bf5f", + ".git/hooks/sendemail-validate.sample": "44ebfc923dc5466bc009602f0ecf067b9c65459abfe8868ddc49b78e6ced7a92", + ".git/hooks/update.sample": "8d5f2fa83e103cf08b57eaa67521df9194f45cbdbcb37da52ad586097a14d106", + ".git/index": "8e7abe784214b71ff749e3da55714cab297590bc47106ab50f0e1972431becef", + ".git/info/exclude": "6671fe83b7a07c8932ee89164d1f2793b2318058eb8b98dc5c06ee0a5a3b0ec1", + ".git/objects/cc/0164cd333fb5641b4b7d5470801c92c0d8cc97": "f5355023927a43219fba4df718a5066e7dcb66be566f45d4c8cdcc31f5e31a68", + "external.index": "6f48c3b25e1dd7d4bde3cc36428159e01aa3ea22347a2ec81a194d88066535da", + "sample.py": "d5dfe92bc7b6cb302abb83bf2266c3a3bf41c80ea6619c4ea79af29c710b98f7" + }, + "changed_sentinel_paths": [ + "external.index" + ], + "fixture_has_own_git_directory": true, + "passed": false + } + ], + "passed": false +} diff --git a/docs/receiving/diff-hunks-6cc6795e89f4/fixture-containment-candidate-2585b785.json b/docs/receiving/diff-hunks-6cc6795e89f4/fixture-containment-candidate-2585b785.json new file mode 100644 index 0000000..d1b3813 --- /dev/null +++ b/docs/receiving/diff-hunks-6cc6795e89f4/fixture-containment-candidate-2585b785.json @@ -0,0 +1,187 @@ +{ + "schema": "hamon.independent-fixture-containment.v1", + "source_head": "2585b7857b8087a4f96275294482bee4fecfc316", + "test_sha256": "af9f03bf39b00eb042a2cf7e64e76a810aef8093860d71357890318a08380e26", + "reviewer": "estate-6cc6795e89f4/root", + "method": "Invoke the exact authored _git helper with privately owned external Git directory/worktree and index controls; compare all sentinel bytes before and after. No user repository or external provider is a test target.", + "results": [ + { + "case": "git_dir_and_work_tree", + "private_sentinel_only": true, + "inherited_routing_keys": [ + "GIT_DIR", + "GIT_WORK_TREE" + ], + "commands": [ + { + "args": [ + "init", + "-q" + ], + "returncode": 0, + "stdout": "", + "stderr": "" + }, + { + "args": [ + "config", + "core.autocrlf", + "false" + ], + "returncode": 0, + "stdout": "", + "stderr": "" + }, + { + "args": [ + "add", + "sample.py" + ], + "returncode": 0, + "stdout": "", + "stderr": "" + } + ], + "error": null, + "sentinel_before": { + ".git/HEAD": "f6f2b945f6c411b02ba3da9c7ace88dcf71b6af65ba2e0d89aa82900042b5a10", + ".git/config": "cfe7ba1238c9a78be7535d7c63bcaf5a4d5011d46b07c9b45d3bbf7d6c312dfe", + ".git/description": "85ab6c163d43a17ea9cf7788308bca1466f1b0a8d1cc92e26e9bf63da4062aee", + ".git/hooks/applypatch-msg.sample": "0223497a0b8b033aa58a3a521b8629869386cf7ab0e2f101963d328aa62193f7", + ".git/hooks/commit-msg.sample": "1f74d5e9292979b573ebd59741d46cb93ff391acdd083d340b94370753d92437", + ".git/hooks/fsmonitor-watchman.sample": "e0549964e93897b519bd8e333c037e51fff0f88ba13e086a331592bf801fa1d0", + ".git/hooks/post-update.sample": "81765af2daef323061dcbc5e61fc16481cb74b3bac9ad8a174b186523586f6c5", + ".git/hooks/pre-applypatch.sample": "e15c5b469ea3e0a695bea6f2c82bcf8e62821074939ddd85b77e0007ff165475", + ".git/hooks/pre-commit.sample": "57185b7b9f05239d7ab52db045f5b89eb31348d7b2177eab214f5eb872e1971b", + ".git/hooks/pre-merge-commit.sample": "d3825a70337940ebbd0a5c072984e13245920cdf8898bd225c8d27a6dfc9cb53", + ".git/hooks/pre-push.sample": "ecce9c7e04d3f5dd9d8ada81753dd1d549a9634b26770042b58dda00217d086a", + ".git/hooks/pre-rebase.sample": "4febce867790052338076f4e66cc47efb14879d18097d1d61c8261859eaaa7b3", + ".git/hooks/pre-receive.sample": "a4c3d2b9c7bb3fd8d1441c31bd4ee71a595d66b44fcf49ddb310252320169989", + ".git/hooks/prepare-commit-msg.sample": "e9ddcaa4189fddd25ed97fc8c789eca7b6ca16390b2392ae3276f0c8e1aa4619", + ".git/hooks/push-to-checkout.sample": "a53d0741798b287c6dd7afa64aee473f305e65d3f49463bb9d7408ec3b12bf5f", + ".git/hooks/sendemail-validate.sample": "44ebfc923dc5466bc009602f0ecf067b9c65459abfe8868ddc49b78e6ced7a92", + ".git/hooks/update.sample": "8d5f2fa83e103cf08b57eaa67521df9194f45cbdbcb37da52ad586097a14d106", + ".git/index": "d4bb73e56fe4d4d18e6507d2525651f52856617cf26a626f128b582e1ef7ca3d", + ".git/info/exclude": "6671fe83b7a07c8932ee89164d1f2793b2318058eb8b98dc5c06ee0a5a3b0ec1", + ".git/objects/cc/0164cd333fb5641b4b7d5470801c92c0d8cc97": "f5355023927a43219fba4df718a5066e7dcb66be566f45d4c8cdcc31f5e31a68", + "sample.py": "d5dfe92bc7b6cb302abb83bf2266c3a3bf41c80ea6619c4ea79af29c710b98f7" + }, + "sentinel_after": { + ".git/HEAD": "f6f2b945f6c411b02ba3da9c7ace88dcf71b6af65ba2e0d89aa82900042b5a10", + ".git/config": "cfe7ba1238c9a78be7535d7c63bcaf5a4d5011d46b07c9b45d3bbf7d6c312dfe", + ".git/description": "85ab6c163d43a17ea9cf7788308bca1466f1b0a8d1cc92e26e9bf63da4062aee", + ".git/hooks/applypatch-msg.sample": "0223497a0b8b033aa58a3a521b8629869386cf7ab0e2f101963d328aa62193f7", + ".git/hooks/commit-msg.sample": "1f74d5e9292979b573ebd59741d46cb93ff391acdd083d340b94370753d92437", + ".git/hooks/fsmonitor-watchman.sample": "e0549964e93897b519bd8e333c037e51fff0f88ba13e086a331592bf801fa1d0", + ".git/hooks/post-update.sample": "81765af2daef323061dcbc5e61fc16481cb74b3bac9ad8a174b186523586f6c5", + ".git/hooks/pre-applypatch.sample": "e15c5b469ea3e0a695bea6f2c82bcf8e62821074939ddd85b77e0007ff165475", + ".git/hooks/pre-commit.sample": "57185b7b9f05239d7ab52db045f5b89eb31348d7b2177eab214f5eb872e1971b", + ".git/hooks/pre-merge-commit.sample": "d3825a70337940ebbd0a5c072984e13245920cdf8898bd225c8d27a6dfc9cb53", + ".git/hooks/pre-push.sample": "ecce9c7e04d3f5dd9d8ada81753dd1d549a9634b26770042b58dda00217d086a", + ".git/hooks/pre-rebase.sample": "4febce867790052338076f4e66cc47efb14879d18097d1d61c8261859eaaa7b3", + ".git/hooks/pre-receive.sample": "a4c3d2b9c7bb3fd8d1441c31bd4ee71a595d66b44fcf49ddb310252320169989", + ".git/hooks/prepare-commit-msg.sample": "e9ddcaa4189fddd25ed97fc8c789eca7b6ca16390b2392ae3276f0c8e1aa4619", + ".git/hooks/push-to-checkout.sample": "a53d0741798b287c6dd7afa64aee473f305e65d3f49463bb9d7408ec3b12bf5f", + ".git/hooks/sendemail-validate.sample": "44ebfc923dc5466bc009602f0ecf067b9c65459abfe8868ddc49b78e6ced7a92", + ".git/hooks/update.sample": "8d5f2fa83e103cf08b57eaa67521df9194f45cbdbcb37da52ad586097a14d106", + ".git/index": "d4bb73e56fe4d4d18e6507d2525651f52856617cf26a626f128b582e1ef7ca3d", + ".git/info/exclude": "6671fe83b7a07c8932ee89164d1f2793b2318058eb8b98dc5c06ee0a5a3b0ec1", + ".git/objects/cc/0164cd333fb5641b4b7d5470801c92c0d8cc97": "f5355023927a43219fba4df718a5066e7dcb66be566f45d4c8cdcc31f5e31a68", + "sample.py": "d5dfe92bc7b6cb302abb83bf2266c3a3bf41c80ea6619c4ea79af29c710b98f7" + }, + "changed_sentinel_paths": [], + "fixture_has_own_git_directory": true, + "passed": true + }, + { + "case": "external_index", + "private_sentinel_only": true, + "inherited_routing_keys": [ + "GIT_INDEX_FILE" + ], + "commands": [ + { + "args": [ + "init", + "-q" + ], + "returncode": 0, + "stdout": "", + "stderr": "" + }, + { + "args": [ + "config", + "core.autocrlf", + "false" + ], + "returncode": 0, + "stdout": "", + "stderr": "" + }, + { + "args": [ + "add", + "sample.py" + ], + "returncode": 0, + "stdout": "", + "stderr": "" + } + ], + "error": null, + "sentinel_before": { + ".git/HEAD": "f6f2b945f6c411b02ba3da9c7ace88dcf71b6af65ba2e0d89aa82900042b5a10", + ".git/config": "cfe7ba1238c9a78be7535d7c63bcaf5a4d5011d46b07c9b45d3bbf7d6c312dfe", + ".git/description": "85ab6c163d43a17ea9cf7788308bca1466f1b0a8d1cc92e26e9bf63da4062aee", + ".git/hooks/applypatch-msg.sample": "0223497a0b8b033aa58a3a521b8629869386cf7ab0e2f101963d328aa62193f7", + ".git/hooks/commit-msg.sample": "1f74d5e9292979b573ebd59741d46cb93ff391acdd083d340b94370753d92437", + ".git/hooks/fsmonitor-watchman.sample": "e0549964e93897b519bd8e333c037e51fff0f88ba13e086a331592bf801fa1d0", + ".git/hooks/post-update.sample": "81765af2daef323061dcbc5e61fc16481cb74b3bac9ad8a174b186523586f6c5", + ".git/hooks/pre-applypatch.sample": "e15c5b469ea3e0a695bea6f2c82bcf8e62821074939ddd85b77e0007ff165475", + ".git/hooks/pre-commit.sample": "57185b7b9f05239d7ab52db045f5b89eb31348d7b2177eab214f5eb872e1971b", + ".git/hooks/pre-merge-commit.sample": "d3825a70337940ebbd0a5c072984e13245920cdf8898bd225c8d27a6dfc9cb53", + ".git/hooks/pre-push.sample": "ecce9c7e04d3f5dd9d8ada81753dd1d549a9634b26770042b58dda00217d086a", + ".git/hooks/pre-rebase.sample": "4febce867790052338076f4e66cc47efb14879d18097d1d61c8261859eaaa7b3", + ".git/hooks/pre-receive.sample": "a4c3d2b9c7bb3fd8d1441c31bd4ee71a595d66b44fcf49ddb310252320169989", + ".git/hooks/prepare-commit-msg.sample": "e9ddcaa4189fddd25ed97fc8c789eca7b6ca16390b2392ae3276f0c8e1aa4619", + ".git/hooks/push-to-checkout.sample": "a53d0741798b287c6dd7afa64aee473f305e65d3f49463bb9d7408ec3b12bf5f", + ".git/hooks/sendemail-validate.sample": "44ebfc923dc5466bc009602f0ecf067b9c65459abfe8868ddc49b78e6ced7a92", + ".git/hooks/update.sample": "8d5f2fa83e103cf08b57eaa67521df9194f45cbdbcb37da52ad586097a14d106", + ".git/index": "b02a34bf99e7990405e6a678e1bd598cdba69d0bfc29908d98fb8dca14d6c57a", + ".git/info/exclude": "6671fe83b7a07c8932ee89164d1f2793b2318058eb8b98dc5c06ee0a5a3b0ec1", + ".git/objects/cc/0164cd333fb5641b4b7d5470801c92c0d8cc97": "f5355023927a43219fba4df718a5066e7dcb66be566f45d4c8cdcc31f5e31a68", + "external.index": "b02a34bf99e7990405e6a678e1bd598cdba69d0bfc29908d98fb8dca14d6c57a", + "sample.py": "d5dfe92bc7b6cb302abb83bf2266c3a3bf41c80ea6619c4ea79af29c710b98f7" + }, + "sentinel_after": { + ".git/HEAD": "f6f2b945f6c411b02ba3da9c7ace88dcf71b6af65ba2e0d89aa82900042b5a10", + ".git/config": "cfe7ba1238c9a78be7535d7c63bcaf5a4d5011d46b07c9b45d3bbf7d6c312dfe", + ".git/description": "85ab6c163d43a17ea9cf7788308bca1466f1b0a8d1cc92e26e9bf63da4062aee", + ".git/hooks/applypatch-msg.sample": "0223497a0b8b033aa58a3a521b8629869386cf7ab0e2f101963d328aa62193f7", + ".git/hooks/commit-msg.sample": "1f74d5e9292979b573ebd59741d46cb93ff391acdd083d340b94370753d92437", + ".git/hooks/fsmonitor-watchman.sample": "e0549964e93897b519bd8e333c037e51fff0f88ba13e086a331592bf801fa1d0", + ".git/hooks/post-update.sample": "81765af2daef323061dcbc5e61fc16481cb74b3bac9ad8a174b186523586f6c5", + ".git/hooks/pre-applypatch.sample": "e15c5b469ea3e0a695bea6f2c82bcf8e62821074939ddd85b77e0007ff165475", + ".git/hooks/pre-commit.sample": "57185b7b9f05239d7ab52db045f5b89eb31348d7b2177eab214f5eb872e1971b", + ".git/hooks/pre-merge-commit.sample": "d3825a70337940ebbd0a5c072984e13245920cdf8898bd225c8d27a6dfc9cb53", + ".git/hooks/pre-push.sample": "ecce9c7e04d3f5dd9d8ada81753dd1d549a9634b26770042b58dda00217d086a", + ".git/hooks/pre-rebase.sample": "4febce867790052338076f4e66cc47efb14879d18097d1d61c8261859eaaa7b3", + ".git/hooks/pre-receive.sample": "a4c3d2b9c7bb3fd8d1441c31bd4ee71a595d66b44fcf49ddb310252320169989", + ".git/hooks/prepare-commit-msg.sample": "e9ddcaa4189fddd25ed97fc8c789eca7b6ca16390b2392ae3276f0c8e1aa4619", + ".git/hooks/push-to-checkout.sample": "a53d0741798b287c6dd7afa64aee473f305e65d3f49463bb9d7408ec3b12bf5f", + ".git/hooks/sendemail-validate.sample": "44ebfc923dc5466bc009602f0ecf067b9c65459abfe8868ddc49b78e6ced7a92", + ".git/hooks/update.sample": "8d5f2fa83e103cf08b57eaa67521df9194f45cbdbcb37da52ad586097a14d106", + ".git/index": "b02a34bf99e7990405e6a678e1bd598cdba69d0bfc29908d98fb8dca14d6c57a", + ".git/info/exclude": "6671fe83b7a07c8932ee89164d1f2793b2318058eb8b98dc5c06ee0a5a3b0ec1", + ".git/objects/cc/0164cd333fb5641b4b7d5470801c92c0d8cc97": "f5355023927a43219fba4df718a5066e7dcb66be566f45d4c8cdcc31f5e31a68", + "external.index": "b02a34bf99e7990405e6a678e1bd598cdba69d0bfc29908d98fb8dca14d6c57a", + "sample.py": "d5dfe92bc7b6cb302abb83bf2266c3a3bf41c80ea6619c4ea79af29c710b98f7" + }, + "changed_sentinel_paths": [], + "fixture_has_own_git_directory": true, + "passed": true + } + ], + "passed": true +} diff --git a/docs/receiving/diff-hunks-6cc6795e89f4/fixture-containment-receiver.py.txt b/docs/receiving/diff-hunks-6cc6795e89f4/fixture-containment-receiver.py.txt new file mode 100644 index 0000000..7f6bb36 --- /dev/null +++ b/docs/receiving/diff-hunks-6cc6795e89f4/fixture-containment-receiver.py.txt @@ -0,0 +1,81 @@ +"""Independently receive authored Git fixture containment on private sentinels.""" +import hashlib, importlib.util, json, os, subprocess, sys +from pathlib import Path +source = Path(sys.argv[1]).resolve() +out = Path(sys.argv[2]).resolve() +out.mkdir(parents=True, exist_ok=False) +test = source / "tests/test_diff_hunk_validation.py" +original_env = dict(os.environ) +clean = {k:v for k,v in original_env.items() if not k.startswith("GIT_")} +clean.update(GIT_CONFIG_NOSYSTEM="1", GIT_CONFIG_GLOBAL=os.devnull, PYTHONDONTWRITEBYTECODE="1") +os.environ.clear() +os.environ.update(clean) +sys.path.insert(0, str(source)) +spec = importlib.util.spec_from_file_location("private_fixture_receiver", test) +module = importlib.util.module_from_spec(spec) +spec.loader.exec_module(module) +def git(path, *args): + result = subprocess.run(["git", "-C", str(path), *args], env=clean, capture_output=True, timeout=15) + if result.returncode: + raise RuntimeError(result.stderr.decode("utf8", "replace")) + return result +def hashes(path): + return {p.relative_to(path).as_posix():hashlib.sha256(p.read_bytes()).hexdigest() + for p in sorted(path.rglob("*")) if p.is_file()} +results = [] +for case in ("git_dir_and_work_tree", "external_index"): + root = out / case + sentinel, fixture = root / "sentinel", root / "fixture" + sentinel.mkdir(parents=True) + fixture.mkdir() + git(sentinel, "init", "-q") + (sentinel / "sample.py").write_text("def sentinel():\n return 7\n", encoding="utf8") + git(sentinel, "add", "sample.py") + (fixture / "sample.py").write_text("def selected():\n return 1\n", encoding="utf8") + if case == "external_index": + index = sentinel / "external.index" + index.write_bytes((sentinel / ".git/index").read_bytes()) + extra = {"GIT_INDEX_FILE":str(index)} + else: + extra = {"GIT_DIR":str(sentinel / ".git"), "GIT_WORK_TREE":str(sentinel)} + before = hashes(sentinel) + env = dict(clean) + env.update(extra) + os.environ.clear() + os.environ.update(env) + commands, failure = [], None + for args in (("init", "-q"), ("config", "core.autocrlf", "false"), ("add", "sample.py")): + try: + result = module._git(fixture, *args) + commands.append({"args":list(args), "returncode":result.returncode, + "stdout":result.stdout, "stderr":result.stderr}) + except Exception as exc: + failure = {"type":type(exc).__name__, "message":str(exc)} + break + os.environ.clear() + os.environ.update(clean) + after = hashes(sentinel) + changed = sorted(k for k in before.keys() | after.keys() if before.get(k) != after.get(k)) + result = { + "case":case, "private_sentinel_only":True, "inherited_routing_keys":sorted(extra), + "commands":commands, "error":failure, "sentinel_before":before, + "sentinel_after":after, "changed_sentinel_paths":changed, + "fixture_has_own_git_directory":(fixture / ".git").is_dir(), + "passed":not changed and failure is None and (fixture / ".git").is_dir() + } + results.append(result) +os.environ.clear() +os.environ.update(original_env) +receipt = { + "schema":"hamon.independent-fixture-containment.v1", + "source_head":git(source, "rev-parse", "HEAD").stdout.decode().strip(), + "test_sha256":hashlib.sha256(test.read_bytes()).hexdigest(), + "reviewer":"estate-6cc6795e89f4/root", + "method":"Invoke the exact authored _git helper with privately owned external Git directory/worktree and index controls; compare all sentinel bytes before and after. No user repository or external provider is a test target.", + "results":results, "passed":all(r["passed"] for r in results) +} +(out / "receipt.json").write_text(json.dumps(receipt, indent=2) + "\n", encoding="utf8") +print(json.dumps({"receipt":str(out/"receipt.json"),"source_head":receipt["source_head"], + "test_sha256":receipt["test_sha256"],"passed":receipt["passed"], + "cases":[{"case":r["case"],"passed":r["passed"],"changed":r["changed_sentinel_paths"], + "fixture_has_own_git_directory":r["fixture_has_own_git_directory"]} for r in results]})) diff --git a/docs/receiving/diff-hunks-6cc6795e89f4/footer-preflight-v1-negative.json b/docs/receiving/diff-hunks-6cc6795e89f4/footer-preflight-v1-negative.json new file mode 100644 index 0000000..8a70f47 --- /dev/null +++ b/docs/receiving/diff-hunks-6cc6795e89f4/footer-preflight-v1-negative.json @@ -0,0 +1,24 @@ +{ + "command": [ + "/usr/bin/python3", + "-B", + "-m", + "pytest", + "-q", + "--tb=short", + "tests/test_diff_hunk_validation.py", + "-k", + "mail or diff_error_precedes" + ], + "cwd": "/home/jacob/testpilot-hunks-6cc6795e89f4", + "started": "2026-10-08T17:31:55.494381+00:00", + "finished": "2026-10-08T17:31:58.994074+00:00", + "exit": 1, + "checkpoint": "V1 footer/preflight behavior, with only independent numeric conversion normalization already applied", + "files_sha256": { + "testpilot/diff.py": "f2c4d8a5d9f75d74ad31b11d6b321695bc7b1d8d19d7716300d79b8170814c97", + "testpilot/__main__.py": "67013a86b7a36c7d0fc56668d29dd97e084d4da4ce358b546633829be840c020", + "tests/test_diff_hunk_validation.py": "1ac50251037a2a4d355fdb1993c9c8ee4d2a0f892b29e947e4068969800ccf0a" + }, + "output_sha256": "72be823f6cf814572e29654620e71e9ee59e18d64b6568a19967413f1384aa4b" +} diff --git a/docs/receiving/diff-hunks-6cc6795e89f4/footer-preflight-v1-negative.txt b/docs/receiving/diff-hunks-6cc6795e89f4/footer-preflight-v1-negative.txt new file mode 100644 index 0000000..96bbe45 --- /dev/null +++ b/docs/receiving/diff-hunks-6cc6795e89f4/footer-preflight-v1-negative.txt @@ -0,0 +1,44 @@ +.FF...FFF [100%] +=================================== FAILURES =================================== +_______ test_actual_git_mail_footer_preserves_complete_target_selection ________ +tests/test_diff_hunk_validation.py:240: in test_actual_git_mail_footer_preserves_complete_target_selection + selected = changed_functions(repo, patch) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +testpilot/diff.py:306: in changed_functions + for fc in parse_unified_diff(diff_text): + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +testpilot/diff.py:203: in parse_unified_diff + raise invalid("body record exceeds the declared hunk ranges") +E testpilot.diff.DiffFormatError: invalid unified diff at record 15: body record exceeds the declared hunk ranges +_______ test_git_mail_footer_never_completes_a_missing_body_record[old] ________ +tests/test_diff_hunk_validation.py:256: in test_git_mail_footer_never_completes_a_missing_body_record + with pytest.raises(DiffFormatError, match="diff"): + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +E Failed: DID NOT RAISE +_____ test_mail_can_delete_source_text_shaped_like_the_signature_delimiter _____ +tests/test_diff_hunk_validation.py:269: in test_mail_can_delete_source_text_shaped_like_the_signature_delimiter + selected = changed_functions(repo, patch) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +testpilot/diff.py:306: in changed_functions + for fc in parse_unified_diff(diff_text): + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +testpilot/diff.py:203: in parse_unified_diff + raise invalid("body record exceeds the declared hunk ranges") +E testpilot.diff.DiffFormatError: invalid unified diff at record 17: body record exceeds the declared hunk ranges +___ test_native_diff_error_precedes_model_config_and_preserves_output[False] ___ +tests/test_diff_hunk_validation.py:290: in test_native_diff_error_precedes_model_config_and_preserves_output + assert run.returncode == 2 +E AssertionError: assert 1 == 2 +E + where 1 = CompletedProcess(args=['/usr/bin/python3', '-B', '-m', 'testpilot', 'run', '--repo', '/tmp/pytest-of-jacob/pytest-209/...No such file or directory: \'/tmp/pytest-of-jacob/pytest-209/test_native_diff_error_precede0/missing-model-config\'\n').returncode +___ test_native_diff_error_precedes_model_config_and_preserves_output[True] ____ +tests/test_diff_hunk_validation.py:290: in test_native_diff_error_precedes_model_config_and_preserves_output + assert run.returncode == 2 +E AssertionError: assert 1 == 2 +E + where 1 = CompletedProcess(args=['/usr/bin/python3', '-B', '-m', 'testpilot', 'run', '--repo', '/tmp/pytest-of-jacob/pytest-209/...No such file or directory: \'/tmp/pytest-of-jacob/pytest-209/test_native_diff_error_precede1/missing-model-config\'\n').returncode +=========================== short test summary info ============================ +FAILED tests/test_diff_hunk_validation.py::test_actual_git_mail_footer_preserves_complete_target_selection +FAILED tests/test_diff_hunk_validation.py::test_git_mail_footer_never_completes_a_missing_body_record[old] +FAILED tests/test_diff_hunk_validation.py::test_mail_can_delete_source_text_shaped_like_the_signature_delimiter +FAILED tests/test_diff_hunk_validation.py::test_native_diff_error_precedes_model_config_and_preserves_output[False] +FAILED tests/test_diff_hunk_validation.py::test_native_diff_error_precedes_model_config_and_preserves_output[True] +5 failed, 4 passed, 28 deselected in 2.31s diff --git a/docs/receiving/diff-hunks-6cc6795e89f4/github-issue-refusal.json b/docs/receiving/diff-hunks-6cc6795e89f4/github-issue-refusal.json new file mode 100644 index 0000000..88292c7 --- /dev/null +++ b/docs/receiving/diff-hunks-6cc6795e89f4/github-issue-refusal.json @@ -0,0 +1,9 @@ +{ + "error": "GitHub API error 403: {\"message\":\"You have exceeded a secondary rate limit and have been temporarily blocked from content creation. Please retry your request again later. For more on scraping GitHub and how it may affect your rights, please review our Terms of Service (https://docs.github.com/en/site-policy/github-terms/github-terms-of-service) If you reach out to GitHub Support for help, please include the request ID FD45:22FE9A:1FD6707:68B258B:6AC7CE53 and timestamp 2026-10-08 17:09:41 UTC.\",\"documentation_url\":\"https://docs.github.com/rest/overview/rate-limits-for-the-rest-api#about-secondary-rate-limits\",\"status\":\"403\"}", + "error_code": "FORBIDDEN", + "error_data": { + "message": "You have exceeded a secondary rate limit and have been temporarily blocked from content creation. Please retry your request again later. For more on scraping GitHub and how it may affect your rights, please review our Terms of Service (https://docs.github.com/en/site-policy/github-terms/github-terms-of-service) If you reach out to GitHub Support for help, please include the request ID FD45:22FE9A:1FD6707:68B258B:6AC7CE53 and timestamp 2026-10-08 17:09:41 UTC.", + "documentation_url": "https://docs.github.com/rest/overview/rate-limits-for-the-rest-api#about-secondary-rate-limits", + "status": "403" + } +} diff --git a/docs/receiving/diff-hunks-6cc6795e89f4/native-console/README.md b/docs/receiving/diff-hunks-6cc6795e89f4/native-console/README.md new file mode 100644 index 0000000..0e06fba --- /dev/null +++ b/docs/receiving/diff-hunks-6cc6795e89f4/native-console/README.md @@ -0,0 +1,29 @@ +# TestPilot: qualified native build on ThinkPad + +The installed project console command is: + +``` +/home/jacob/testpilot-native-6cc6795e89f4/40860534a8fd/bin/testpilot +``` + +This versioned environment installs the project's declared `testpilot.__main__:main` entry point from source commit `40860534a8fdc760b105ac9bc7d14ac9087172dd`. The source includes canonical TestPilot main `cec50d8df4499ba509615e179d82ef3861379bd2` and the received complete-hunk input boundary. Native Python is 3.14.4; the environment uses the recorded system pytest 9.0.2. All eleven installed Python source files match the exact Git archive. + +To inspect the retained real Git example: + +``` +/home/jacob/testpilot-native-6cc6795e89f4/40860534a8fd/bin/testpilot targets --repo /home/jacob/testpilot-native-6cc6795e89f4/receiving/project --git-base HEAD --json +``` + +To inspect the supported native commands: + +``` +/home/jacob/testpilot-native-6cc6795e89f4/40860534a8fd/bin/testpilot --help +``` + +The actual local ScriptedModel receiving run generated and executed one passing test. Its report is `receiving/valid-result/report.html` (also JSON and Markdown), with the emitted patch alongside it. The changed sample function and authored replies remain in `receiving/project` and `receiving/script`. No live provider call or paid inference was used. + +The damaged-diff receiving cases exited 2 before loading a nonexistent model configuration. They preserved every successful result file and created no new result directory. `receiving/receiving.json` and the raw console outputs retain those observations. + +`artifacts/installation.json` records the exact Git tree, source archive, built wheel, installed source hashes, interpreter, entry point and offline build/install commands. The wheel SHA-256 is `83c756b33833e004c6421c203888c54862a4eb4995dba1e08bfea3128dec9c4b`. + +The source contribution and its receiving history live at `/home/jacob/testpilot-hunks-integration-6cc6795e89f4`; canonical source publication is coordinated separately by the parent worker. This versioned installation is directly runnable by its absolute command path. diff --git a/docs/receiving/diff-hunks-6cc6795e89f4/native-console/artifacts/build-wheel.log b/docs/receiving/diff-hunks-6cc6795e89f4/native-console/artifacts/build-wheel.log new file mode 100644 index 0000000..940148c --- /dev/null +++ b/docs/receiving/diff-hunks-6cc6795e89f4/native-console/artifacts/build-wheel.log @@ -0,0 +1,9 @@ +Processing ./build-source + Preparing metadata (pyproject.toml): started + Preparing metadata (pyproject.toml): finished with status 'done' +Building wheels for collected packages: testpilot + Building wheel for testpilot (pyproject.toml): started + Building wheel for testpilot (pyproject.toml): finished with status 'done' + Created wheel for testpilot: filename=testpilot-0.1.0-py3-none-any.whl size=55316 sha256=83c756b33833e004c6421c203888c54862a4eb4995dba1e08bfea3128dec9c4b + Stored in directory: /tmp/pip-ephem-wheel-cache-fc5pqs_a/wheels/46/5c/9c/415c6dfeef6ad89c660f547b2510b702e1057c6d86ff553dfa +Successfully built testpilot diff --git a/docs/receiving/diff-hunks-6cc6795e89f4/native-console/artifacts/install-wheel.log b/docs/receiving/diff-hunks-6cc6795e89f4/native-console/artifacts/install-wheel.log new file mode 100644 index 0000000..3fe7716 --- /dev/null +++ b/docs/receiving/diff-hunks-6cc6795e89f4/native-console/artifacts/install-wheel.log @@ -0,0 +1,3 @@ +Processing ./artifacts/testpilot-0.1.0-py3-none-any.whl +Installing collected packages: testpilot +Successfully installed testpilot-0.1.0 diff --git a/docs/receiving/diff-hunks-6cc6795e89f4/native-console/artifacts/installation.json b/docs/receiving/diff-hunks-6cc6795e89f4/native-console/artifacts/installation.json new file mode 100644 index 0000000..f796eb4 --- /dev/null +++ b/docs/receiving/diff-hunks-6cc6795e89f4/native-console/artifacts/installation.json @@ -0,0 +1,126 @@ +{ + "schema": "hamon.native_product_installation.v1", + "at": "2026-10-08T17:59:10.549966+00:00", + "worker": "hamon-6cc6795e89f4/estate_production", + "repository": "Jacob-Met/testpilot", + "source_head": "40860534a8fdc760b105ac9bc7d14ac9087172dd", + "source_tree": "ba7272658e66a5ff2e02674352eb5046b33c26f4", + "canonical_base": "cec50d8df4499ba509615e179d82ef3861379bd2", + "source_worktree": "/home/jacob/testpilot-hunks-integration-6cc6795e89f4", + "release": "/home/jacob/testpilot-native-6cc6795e89f4/40860534a8fd", + "console": "/home/jacob/testpilot-native-6cc6795e89f4/40860534a8fd/bin/testpilot", + "source_archive": { + "path": "/home/jacob/testpilot-native-6cc6795e89f4/artifacts/source-40860534a8fd.tar", + "bytes": 16189440, + "sha256": "b8f1052a80b2803f29c79f427afee447cbf1e5dd30c1d2deb2cbe66a5c349e8c" + }, + "wheel": { + "path": "/home/jacob/testpilot-native-6cc6795e89f4/artifacts/testpilot-0.1.0-py3-none-any.whl", + "bytes": 55316, + "sha256": "83c756b33833e004c6421c203888c54862a4eb4995dba1e08bfea3128dec9c4b" + }, + "installed_console_sha256": "6ae5527b2002612e544456d5df3ee22f0a2aacb72301bc3e27faf7c911094cb2", + "runtime": { + "python": "3.14.4 (main, Aug 20 2026, 10:41:58) [GCC 15.2.0]", + "prefix": "/home/jacob/testpilot-native-6cc6795e89f4/40860534a8fd", + "module": "/home/jacob/testpilot-native-6cc6795e89f4/40860534a8fd/lib/python3.14/site-packages/testpilot", + "project_version": "0.1.0", + "pytest_version": "9.0.2", + "pip_version": "25.1.1", + "source_files": { + "__init__.py": "65c63b80d7da34a83a6049a79c24b8335878cd49cc79951fdefc5f1532ae841f", + "__main__.py": "45572d34cff56b521d4a48a5db90164fa70ca71d5090e35b8fd0880aecde4420", + "_pytest_generated.py": "d65352d7bade965d7b9dc73efe5d4d01844fa8c7474a1bdcdb3b708f6decf768", + "diff.py": "586f5b981d2c8d66e47cd8c5566ace994b152cde87f43db9b6e1c02ac5337490", + "html_report.py": "f2b65ee9201f9e91ad183fcb8f6b8bf6e69c77b1a066fcd90bb431951e41a2f6", + "loop.py": "7e2336ff4d3e88aa8f575e4d7c04f3fe1cc1bf3023f0d230941ab63cead675a8", + "model.py": "f6ae697f1f5d686ffdcb96f5451d1a9600d7d41c7bcf324d7e8c276a107e8673", + "recheck.py": "3eab07bd639994f897f76c0cd2b941d5e294c6d7c8132c82deb800e44d7079e6", + "recheck_html.py": "6cc81b1d1e63282708a4cad448224bac5359519c4955d82c6244afa80738e633", + "sandbox.py": "8d5aadafff5188473ec45eb1cd8f01fb562836ff05bf12a27939cde83b33472a", + "targets.py": "35add23585b0e88246288572f49bd857ebf4af83a221b9129e784e9b456c1ab0" + }, + "entry_points": [ + "EntryPoint(name='testpilot', value='testpilot.__main__:main', group='console_scripts')" + ] + }, + "steps": [ + { + "name": "source-archive", + "command": [ + "git", + "-C", + "/home/jacob/testpilot-hunks-integration-6cc6795e89f4", + "archive", + "--format=tar", + "--output", + "/home/jacob/testpilot-native-6cc6795e89f4/artifacts/source-40860534a8fd.tar", + "40860534a8fdc760b105ac9bc7d14ac9087172dd" + ], + "exit": 0, + "log": "/home/jacob/testpilot-native-6cc6795e89f4/artifacts/source-archive.log", + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + { + "name": "venv-bootstrap", + "command": [ + "/usr/bin/python3", + "-m", + "venv", + "--system-site-packages", + "/home/jacob/testpilot-native-6cc6795e89f4/40860534a8fd" + ], + "exit": 0, + "log": "/home/jacob/testpilot-native-6cc6795e89f4/artifacts/venv-bootstrap.log", + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + { + "name": "build-wheel", + "command": [ + "/home/jacob/testpilot-native-6cc6795e89f4/40860534a8fd/bin/python", + "-m", + "pip", + "wheel", + "--no-index", + "--no-deps", + "--no-build-isolation", + "--no-cache-dir", + "--wheel-dir", + "/home/jacob/testpilot-native-6cc6795e89f4/artifacts", + "/home/jacob/testpilot-native-6cc6795e89f4/build-source" + ], + "exit": 0, + "log": "/home/jacob/testpilot-native-6cc6795e89f4/artifacts/build-wheel.log", + "sha256": "9d97755a682ffb49bf53653e6ad5d9932f7db350ee3a954fc4e45a62ee5ff039" + }, + { + "name": "install-wheel", + "command": [ + "/home/jacob/testpilot-native-6cc6795e89f4/40860534a8fd/bin/python", + "-m", + "pip", + "install", + "--no-index", + "--no-deps", + "--no-cache-dir", + "--no-compile", + "/home/jacob/testpilot-native-6cc6795e89f4/artifacts/testpilot-0.1.0-py3-none-any.whl" + ], + "exit": 0, + "log": "/home/jacob/testpilot-native-6cc6795e89f4/artifacts/install-wheel.log", + "sha256": "d6e8e349e8f167d1515d7f4689e9f0eb530f574b9bffe0a0aa757127e14d88fe" + }, + { + "name": "installed-help", + "command": [ + "/home/jacob/testpilot-native-6cc6795e89f4/40860534a8fd/bin/testpilot", + "--help" + ], + "exit": 0, + "log": "/home/jacob/testpilot-native-6cc6795e89f4/artifacts/installed-help.log", + "sha256": "b47b803f077188d47e7ed173248144474dbf4d256b5d3c7d8b17b945eeaec015" + } + ], + "scope": "Exclusive versioned native venv through declared project console script; recorded system pytest reused, no global PATH/service/other project installation change; all bootstrap/build/install inputs offline", + "receiving": "Help and source-byte identity verified; actual valid/invalid console flow receiving follows" +} diff --git a/docs/receiving/diff-hunks-6cc6795e89f4/native-console/artifacts/installed-help.log b/docs/receiving/diff-hunks-6cc6795e89f4/native-console/artifacts/installed-help.log new file mode 100644 index 0000000..bd3f2b3 --- /dev/null +++ b/docs/receiving/diff-hunks-6cc6795e89f4/native-console/artifacts/installed-help.log @@ -0,0 +1,12 @@ +usage: testpilot [-h] {run,targets,recheck} ... + +positional arguments: + {run,targets,recheck} + run generate, run and repair tests for a diff + targets inspect changed functions without generating or + running tests + recheck rerun exact tests from a saved report without model + calls + +options: + -h, --help show this help message and exit diff --git a/docs/receiving/diff-hunks-6cc6795e89f4/native-console/artifacts/source-archive.log b/docs/receiving/diff-hunks-6cc6795e89f4/native-console/artifacts/source-archive.log new file mode 100644 index 0000000..e69de29 diff --git a/docs/receiving/diff-hunks-6cc6795e89f4/native-console/artifacts/steps.json b/docs/receiving/diff-hunks-6cc6795e89f4/native-console/artifacts/steps.json new file mode 100644 index 0000000..4f8a571 --- /dev/null +++ b/docs/receiving/diff-hunks-6cc6795e89f4/native-console/artifacts/steps.json @@ -0,0 +1,77 @@ +[ + { + "name": "source-archive", + "command": [ + "git", + "-C", + "/home/jacob/testpilot-hunks-integration-6cc6795e89f4", + "archive", + "--format=tar", + "--output", + "/home/jacob/testpilot-native-6cc6795e89f4/artifacts/source-40860534a8fd.tar", + "40860534a8fdc760b105ac9bc7d14ac9087172dd" + ], + "exit": 0, + "log": "/home/jacob/testpilot-native-6cc6795e89f4/artifacts/source-archive.log", + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + { + "name": "venv-bootstrap", + "command": [ + "/usr/bin/python3", + "-m", + "venv", + "--system-site-packages", + "/home/jacob/testpilot-native-6cc6795e89f4/40860534a8fd" + ], + "exit": 0, + "log": "/home/jacob/testpilot-native-6cc6795e89f4/artifacts/venv-bootstrap.log", + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + { + "name": "build-wheel", + "command": [ + "/home/jacob/testpilot-native-6cc6795e89f4/40860534a8fd/bin/python", + "-m", + "pip", + "wheel", + "--no-index", + "--no-deps", + "--no-build-isolation", + "--no-cache-dir", + "--wheel-dir", + "/home/jacob/testpilot-native-6cc6795e89f4/artifacts", + "/home/jacob/testpilot-native-6cc6795e89f4/build-source" + ], + "exit": 0, + "log": "/home/jacob/testpilot-native-6cc6795e89f4/artifacts/build-wheel.log", + "sha256": "9d97755a682ffb49bf53653e6ad5d9932f7db350ee3a954fc4e45a62ee5ff039" + }, + { + "name": "install-wheel", + "command": [ + "/home/jacob/testpilot-native-6cc6795e89f4/40860534a8fd/bin/python", + "-m", + "pip", + "install", + "--no-index", + "--no-deps", + "--no-cache-dir", + "--no-compile", + "/home/jacob/testpilot-native-6cc6795e89f4/artifacts/testpilot-0.1.0-py3-none-any.whl" + ], + "exit": 0, + "log": "/home/jacob/testpilot-native-6cc6795e89f4/artifacts/install-wheel.log", + "sha256": "d6e8e349e8f167d1515d7f4689e9f0eb530f574b9bffe0a0aa757127e14d88fe" + }, + { + "name": "installed-help", + "command": [ + "/home/jacob/testpilot-native-6cc6795e89f4/40860534a8fd/bin/testpilot", + "--help" + ], + "exit": 0, + "log": "/home/jacob/testpilot-native-6cc6795e89f4/artifacts/installed-help.log", + "sha256": "b47b803f077188d47e7ed173248144474dbf4d256b5d3c7d8b17b945eeaec015" + } +] diff --git a/docs/receiving/diff-hunks-6cc6795e89f4/native-console/artifacts/venv-bootstrap.log b/docs/receiving/diff-hunks-6cc6795e89f4/native-console/artifacts/venv-bootstrap.log new file mode 100644 index 0000000..e69de29 diff --git a/docs/receiving/diff-hunks-6cc6795e89f4/native-console/independent/installed-console-contract.json b/docs/receiving/diff-hunks-6cc6795e89f4/native-console/independent/installed-console-contract.json new file mode 100644 index 0000000..eea0bd0 --- /dev/null +++ b/docs/receiving/diff-hunks-6cc6795e89f4/native-console/independent/installed-console-contract.json @@ -0,0 +1,135 @@ +{ + "schema": "hamon.independent_installed_console_contract.v1", + "at": "2026-10-08T18:03:26.884812+00:00", + "reviewer": "hamon-6cc6795e89f4/coordination_evidence", + "source_head": "40860534a8fdc760b105ac9bc7d14ac9087172dd", + "source_tree": "ba7272658e66a5ff2e02674352eb5046b33c26f4", + "console": "/home/jacob/testpilot-native-6cc6795e89f4/40860534a8fd/bin/testpilot", + "console_sha256": "6ae5527b2002612e544456d5df3ee22f0a2aacb72301bc3e27faf7c911094cb2", + "installed_package": "/home/jacob/testpilot-native-6cc6795e89f4/40860534a8fd/lib/python3.14/site-packages/testpilot", + "installed_source_pins": { + "testpilot/__init__.py": { + "git_sha256": "65c63b80d7da34a83a6049a79c24b8335878cd49cc79951fdefc5f1532ae841f", + "installed_sha256": "65c63b80d7da34a83a6049a79c24b8335878cd49cc79951fdefc5f1532ae841f", + "match": true + }, + "testpilot/__main__.py": { + "git_sha256": "45572d34cff56b521d4a48a5db90164fa70ca71d5090e35b8fd0880aecde4420", + "installed_sha256": "45572d34cff56b521d4a48a5db90164fa70ca71d5090e35b8fd0880aecde4420", + "match": true + }, + "testpilot/_pytest_generated.py": { + "git_sha256": "d65352d7bade965d7b9dc73efe5d4d01844fa8c7474a1bdcdb3b708f6decf768", + "installed_sha256": "d65352d7bade965d7b9dc73efe5d4d01844fa8c7474a1bdcdb3b708f6decf768", + "match": true + }, + "testpilot/diff.py": { + "git_sha256": "586f5b981d2c8d66e47cd8c5566ace994b152cde87f43db9b6e1c02ac5337490", + "installed_sha256": "586f5b981d2c8d66e47cd8c5566ace994b152cde87f43db9b6e1c02ac5337490", + "match": true + }, + "testpilot/html_report.py": { + "git_sha256": "f2b65ee9201f9e91ad183fcb8f6b8bf6e69c77b1a066fcd90bb431951e41a2f6", + "installed_sha256": "f2b65ee9201f9e91ad183fcb8f6b8bf6e69c77b1a066fcd90bb431951e41a2f6", + "match": true + }, + "testpilot/loop.py": { + "git_sha256": "7e2336ff4d3e88aa8f575e4d7c04f3fe1cc1bf3023f0d230941ab63cead675a8", + "installed_sha256": "7e2336ff4d3e88aa8f575e4d7c04f3fe1cc1bf3023f0d230941ab63cead675a8", + "match": true + }, + "testpilot/model.py": { + "git_sha256": "f6ae697f1f5d686ffdcb96f5451d1a9600d7d41c7bcf324d7e8c276a107e8673", + "installed_sha256": "f6ae697f1f5d686ffdcb96f5451d1a9600d7d41c7bcf324d7e8c276a107e8673", + "match": true + }, + "testpilot/recheck.py": { + "git_sha256": "3eab07bd639994f897f76c0cd2b941d5e294c6d7c8132c82deb800e44d7079e6", + "installed_sha256": "3eab07bd639994f897f76c0cd2b941d5e294c6d7c8132c82deb800e44d7079e6", + "match": true + }, + "testpilot/recheck_html.py": { + "git_sha256": "6cc81b1d1e63282708a4cad448224bac5359519c4955d82c6244afa80738e633", + "installed_sha256": "6cc81b1d1e63282708a4cad448224bac5359519c4955d82c6244afa80738e633", + "match": true + }, + "testpilot/sandbox.py": { + "git_sha256": "8d5aadafff5188473ec45eb1cd8f01fb562836ff05bf12a27939cde83b33472a", + "installed_sha256": "8d5aadafff5188473ec45eb1cd8f01fb562836ff05bf12a27939cde83b33472a", + "match": true + }, + "testpilot/targets.py": { + "git_sha256": "35add23585b0e88246288572f49bd857ebf4af83a221b9129e784e9b456c1ab0", + "installed_sha256": "35add23585b0e88246288572f49bd857ebf4af83a221b9129e784e9b456c1ab0", + "match": true + } + }, + "receiver_sha256": "fcae956355b52221ec7205338494b3e35c072b52f81b5a06082b1506af024828", + "fixture_provenance_sha256": "f65a99c33d8102f50ae076553da1efb7b291f4b89d75802a5c5cb41474c298b7", + "positive": { + "path": "/home/jacob/testpilot-git-compat-6cc6795e89f4/docs/receiving/git-compat-6cc6795e89f4/patches/git_default.diff", + "sha256": "e63cd0f0a8fc9f08aece050dfc5a92d5e19dac4b982f4c7787bf4f474572db0f", + "committed_blob": "59bd4aa37cf28edf1a82cf550f2bcf24be1f0f6f" + }, + "positive_expected_targets": [ + [ + "alpha.py", + "first" + ], + [ + "alpha.py", + "second" + ], + [ + "beta.py", + "beta" + ], + [ + "crlf.py", + "crlf_fn" + ], + [ + "drop.py", + "removedline" + ], + [ + "insert.py", + "insertedline" + ], + [ + "new.py", + "gamma_unique" + ], + [ + "odd\t\u00e4.py", + "odd_fn" + ], + [ + "payload.py", + "payload" + ], + [ + "unicode.py", + "unicode_fn" + ] + ], + "negative": { + "path": "/home/jacob/testpilot-git-compat-6cc6795e89f4/docs/receiving/git-compat-6cc6795e89f4/patches/malformed_unfinished_tail.diff", + "sha256": "37e73553137f56a839e75050e5072aee77bbca6941867f2c8257fb5ab5c4ec93", + "committed_blob": "192dbf89f6dc2372893ae2b79ed5d8f7992f00e0" + }, + "negative_expected": { + "returncode": 2, + "diagnostic": "invalid unified diff", + "model_setup": false, + "pytest_collection": false, + "output_created": false + }, + "environment_changes": { + "PYTHONPATH": "removed", + "PYTHONHOME": "removed", + "PYTHONDONTWRITEBYTECODE": "1" + }, + "cwd": "/home/jacob/testpilot-publication-6cc6795e89f4", + "scope": "Two installed-console cases only: real native Git multi-file target selection and early malformed-input refusal; no model call, no full suite, no product-source mutation." +} diff --git a/docs/receiving/diff-hunks-6cc6795e89f4/native-console/independent/installed-console-receiving.json b/docs/receiving/diff-hunks-6cc6795e89f4/native-console/independent/installed-console-receiving.json new file mode 100644 index 0000000..6ca61fb --- /dev/null +++ b/docs/receiving/diff-hunks-6cc6795e89f4/native-console/independent/installed-console-receiving.json @@ -0,0 +1,98 @@ +{ + "schema": "hamon.independent_installed_console_receiving.v1", + "at": "2026-10-08T18:03:28.847380+00:00", + "reviewer": "hamon-6cc6795e89f4/coordination_evidence", + "source_head": "40860534a8fdc760b105ac9bc7d14ac9087172dd", + "source_tree": "ba7272658e66a5ff2e02674352eb5046b33c26f4", + "contract_path": "/home/jacob/testpilot-publication-6cc6795e89f4/installed-console-contract.json", + "contract_sha256": "401e6a2210e29e4b4f3833aa54e78d7602825f65663f89c1ab9c08bd29dd2745", + "console": "/home/jacob/testpilot-native-6cc6795e89f4/40860534a8fd/bin/testpilot", + "console_sha256": "6ae5527b2002612e544456d5df3ee22f0a2aacb72301bc3e27faf7c911094cb2", + "positive": { + "command": [ + "/home/jacob/testpilot-native-6cc6795e89f4/40860534a8fd/bin/testpilot", + "targets", + "--repo", + "/home/jacob/testpilot-git-compat-6cc6795e89f4-data/fixture", + "--diff", + "/home/jacob/testpilot-git-compat-6cc6795e89f4/docs/receiving/git-compat-6cc6795e89f4/patches/git_default.diff", + "--json" + ], + "returncode": 0, + "stdout": "{\n \"changed_functions\": [\n {\n \"path\": \"alpha.py\",\n \"module\": \"alpha\",\n \"qualname\": \"first\",\n \"lineno\": 1,\n \"end_lineno\": 2,\n \"changed_lines\": [\n 2\n ],\n \"source\": \"def first():\\n return 2\",\n \"is_method\": false\n },\n {\n \"path\": \"alpha.py\",\n \"module\": \"alpha\",\n \"qualname\": \"second\",\n \"lineno\": 19,\n \"end_lineno\": 20,\n \"changed_lines\": [\n 20\n ],\n \"source\": \"def second():\\n return 4\",\n \"is_method\": false\n },\n {\n \"path\": \"beta.py\",\n \"module\": \"beta\",\n \"qualname\": \"beta\",\n \"lineno\": 1,\n \"end_lineno\": 2,\n \"changed_lines\": [\n 2\n ],\n \"source\": \"def beta():\\n return 11\",\n \"is_method\": false\n },\n {\n \"path\": \"crlf.py\",\n \"module\": \"crlf\",\n \"qualname\": \"crlf_fn\",\n \"lineno\": 1,\n \"end_lineno\": 2,\n \"changed_lines\": [\n 2\n ],\n \"source\": \"def crlf_fn():\\n return 21\",\n \"is_method\": false\n },\n {\n \"path\": \"drop.py\",\n \"module\": \"drop\",\n \"qualname\": \"removedline\",\n \"lineno\": 1,\n \"end_lineno\": 3,\n \"changed_lines\": [],\n \"source\": \"def removedline():\\n keep = 1\\n return keep\",\n \"is_method\": false\n },\n {\n \"path\": \"insert.py\",\n \"module\": \"insert\",\n \"qualname\": \"insertedline\",\n \"lineno\": 1,\n \"end_lineno\": 3,\n \"changed_lines\": [\n 2\n ],\n \"source\": \"def insertedline():\\n inserted = 50\\n return 51\",\n \"is_method\": false\n },\n {\n \"path\": \"new.py\",\n \"module\": \"new\",\n \"qualname\": \"gamma_unique\",\n \"lineno\": 1,\n \"end_lineno\": 2,\n \"changed_lines\": [\n 1,\n 2\n ],\n \"source\": \"def gamma_unique():\\n return 'new-post-change-constant'\",\n \"is_method\": false\n },\n {\n \"path\": \"odd\\t\\u00e4.py\",\n \"module\": \"odd\\t\\u00e4\",\n \"qualname\": \"odd_fn\",\n \"lineno\": 1,\n \"end_lineno\": 2,\n \"changed_lines\": [\n 2\n ],\n \"source\": \"def odd_fn():\\n return 41\",\n \"is_method\": false\n },\n {\n \"path\": \"payload.py\",\n \"module\": \"payload\",\n \"qualname\": \"payload\",\n \"lineno\": 1,\n \"end_lineno\": 5,\n \"changed_lines\": [\n 2,\n 3,\n 4\n ],\n \"source\": \"def payload():\\n return \\\"\\\"\\\"new\\n--- new literal\\n+++ new literal\\n\\\"\\\"\\\"\",\n \"is_method\": false\n },\n {\n \"path\": \"unicode.py\",\n \"module\": \"unicode\",\n \"qualname\": \"unicode_fn\",\n \"lineno\": 1,\n \"end_lineno\": 2,\n \"changed_lines\": [\n 2\n ],\n \"source\": \"def unicode_fn():\\n return \\\"new\\u2028tail\\u2029end\\\"\",\n \"is_method\": false\n }\n ]\n}\n", + "stderr": "", + "observed_targets": [ + [ + "alpha.py", + "first" + ], + [ + "alpha.py", + "second" + ], + [ + "beta.py", + "beta" + ], + [ + "crlf.py", + "crlf_fn" + ], + [ + "drop.py", + "removedline" + ], + [ + "insert.py", + "insertedline" + ], + [ + "new.py", + "gamma_unique" + ], + [ + "odd\t\u00e4.py", + "odd_fn" + ], + [ + "payload.py", + "payload" + ], + [ + "unicode.py", + "unicode_fn" + ] + ], + "pass": true + }, + "negative": { + "command": [ + "/home/jacob/testpilot-native-6cc6795e89f4/40860534a8fd/bin/testpilot", + "run", + "--repo", + "/home/jacob/testpilot-git-compat-6cc6795e89f4-data/fixture", + "--diff", + "/home/jacob/testpilot-git-compat-6cc6795e89f4/docs/receiving/git-compat-6cc6795e89f4/patches/malformed_unfinished_tail.diff", + "--script", + "/home/jacob/testpilot-publication-6cc6795e89f4/deliberately-missing-model-replies", + "--out", + "/home/jacob/testpilot-publication-6cc6795e89f4/installed-malformed-result" + ], + "returncode": 2, + "stdout": "", + "stderr": "testpilot: invalid diff format: invalid unified diff at record 13: incomplete hunk (1 old and 1 new records missing)\n", + "output_absent": true, + "pytest_tripwire_absent": true, + "missing_model_script_absent": true, + "pass": true + }, + "source_unchanged": true, + "summary": { + "cases": 2, + "passed": 2, + "selected_functions": 10, + "installed_source_files": 11, + "source_unchanged": true + }, + "limitations": "Two installed-console cases only: real native Git multi-file target selection and early malformed-input refusal; no model call, no full suite, no product-source mutation." +} diff --git a/docs/receiving/diff-hunks-6cc6795e89f4/native-console/independent/receive_installed_console.py.txt b/docs/receiving/diff-hunks-6cc6795e89f4/native-console/independent/receive_installed_console.py.txt new file mode 100644 index 0000000..32a9f23 --- /dev/null +++ b/docs/receiving/diff-hunks-6cc6795e89f4/native-console/independent/receive_installed_console.py.txt @@ -0,0 +1,105 @@ +from pathlib import Path +from datetime import datetime, timezone +import hashlib, json, os, subprocess, sys +HERE = Path("/home/jacob/testpilot-publication-6cc6795e89f4") +SRC = Path("/home/jacob/testpilot-hunks-integration-6cc6795e89f4") +HEAD = "40860534a8fdc760b105ac9bc7d14ac9087172dd" +CONSOLE = Path("/home/jacob/testpilot-native-6cc6795e89f4/40860534a8fd/bin/testpilot") +PKG = CONSOLE.parent.parent / "lib/python3.14/site-packages/testpilot" +EVIDENCE = Path("/home/jacob/testpilot-git-compat-6cc6795e89f4/docs/receiving/git-compat-6cc6795e89f4") +FIX = json.loads((EVIDENCE / "fixture-provenance.json").read_text()) +CASE = next(x for x in FIX["cases"] if x["name"] == "git_default") +def sha(b): return hashlib.sha256(b).hexdigest() +def git(*args): + return subprocess.run(["git","-C",str(SRC),*args],check=True,capture_output=True).stdout +def pin_source(): + pairs = {} + for item in git("ls-tree","-r","--name-only",HEAD,"testpilot").decode().splitlines(): + if item.endswith(".py"): + original = git("show",HEAD+":"+item) + installed = (PKG / Path(item).relative_to("testpilot")).read_bytes() + pairs[item] = {"git_sha256":sha(original),"installed_sha256":sha(installed),"match":original==installed} + assert len(pairs) == 11 and all(x["match"] for x in pairs.values()) + return pairs +def fixture_pin(path): + path = Path(path) + relative = str(path.relative_to(EVIDENCE)) + native = path.read_bytes() + expected = git("show",HEAD+":docs/receiving/git-compat-6cc6795e89f4/"+relative) + assert native == expected, relative + return {"path":str(path),"sha256":sha(native),"committed_blob":git("rev-parse",HEAD+":docs/receiving/git-compat-6cc6795e89f4/"+relative).decode().strip()} +positive_pin = fixture_pin(CASE["patch"]) +assert positive_pin["sha256"] == CASE["sha256"] +negative_pin = fixture_pin(FIX["malformed"]) +tripwire = Path(FIX["tripwire"]) +out = HERE / "installed-malformed-result" +missing_script = HERE / "deliberately-missing-model-replies" +assert not tripwire.exists() and not out.exists() and not missing_script.exists() +console_sha = sha(CONSOLE.read_bytes()) +assert console_sha == "6ae5527b2002612e544456d5df3ee22f0a2aacb72301bc3e27faf7c911094cb2" +before = pin_source() +contract = { + "schema":"hamon.independent_installed_console_contract.v1", + "at":datetime.now(timezone.utc).isoformat(), + "reviewer":"hamon-6cc6795e89f4/coordination_evidence", + "source_head":HEAD, + "source_tree":git("rev-parse",HEAD+"^{tree}").decode().strip(), + "console":str(CONSOLE),"console_sha256":console_sha, + "installed_package":str(PKG),"installed_source_pins":before, + "receiver_sha256":sha(Path(__file__).read_bytes()), + "fixture_provenance_sha256":sha((EVIDENCE/"fixture-provenance.json").read_bytes()), + "positive":positive_pin,"positive_expected_targets":CASE["expected_targets"], + "negative":negative_pin, + "negative_expected":{"returncode":2,"diagnostic":"invalid unified diff","model_setup":False,"pytest_collection":False,"output_created":False}, + "environment_changes":{"PYTHONPATH":"removed","PYTHONHOME":"removed","PYTHONDONTWRITEBYTECODE":"1"}, + "cwd":str(HERE), + "scope":"Two installed-console cases only: real native Git multi-file target selection and early malformed-input refusal; no model call, no full suite, no product-source mutation." +} +contract_path = HERE / "installed-console-contract.json" +contract_path.write_text(json.dumps(contract,indent=2,ensure_ascii=True)+"\n") +env = os.environ.copy() +env.pop("PYTHONPATH",None) +env.pop("PYTHONHOME",None) +env["PYTHONDONTWRITEBYTECODE"] = "1" +def run(args): + command = [str(CONSOLE),*map(str,args)] + p = subprocess.run(command,cwd=HERE,env=env,capture_output=True,timeout=20) + return {"command":command,"returncode":p.returncode, + "stdout":p.stdout.decode("utf-8","backslashreplace"), + "stderr":p.stderr.decode("utf-8","backslashreplace")} +positive = run(["targets","--repo",FIX["repo"],"--diff",CASE["patch"],"--json"]) +try: + selected = json.loads(positive["stdout"])["changed_functions"] + observed = sorted([[x["path"],x["qualname"]] for x in selected]) +except Exception as exc: + observed = [] + positive["receiver_parse_error"] = type(exc).__name__+": "+str(exc) +positive["observed_targets"] = observed +positive["pass"] = positive["returncode"] == 0 and observed == CASE["expected_targets"] and positive["stderr"] == "" +negative = run(["run","--repo",FIX["repo"],"--diff",FIX["malformed"],"--script",missing_script,"--out",out]) +combined = negative["stdout"]+negative["stderr"] +negative["output_absent"] = not out.exists() +negative["pytest_tripwire_absent"] = not tripwire.exists() +negative["missing_model_script_absent"] = not missing_script.exists() +negative["pass"] = (negative["returncode"] == 2 and "invalid unified diff" in combined.lower() + and not any(x in combined for x in ("Traceback","FileNotFoundError","No such file")) + and negative["output_absent"] and negative["pytest_tripwire_absent"] and negative["missing_model_script_absent"]) +after = pin_source() +unchanged = before == after and sha(CONSOLE.read_bytes()) == console_sha +record = { + "schema":"hamon.independent_installed_console_receiving.v1", + "at":datetime.now(timezone.utc).isoformat(), + "reviewer":contract["reviewer"],"source_head":HEAD,"source_tree":contract["source_tree"], + "contract_path":str(contract_path),"contract_sha256":sha(contract_path.read_bytes()), + "console":str(CONSOLE),"console_sha256":console_sha, + "positive":positive,"negative":negative, + "source_unchanged":unchanged, + "summary":{"cases":2,"passed":int(positive["pass"])+int(negative["pass"]),"selected_functions":len(observed),"installed_source_files":len(before),"source_unchanged":unchanged}, + "limitations":contract["scope"] +} +target = HERE / "installed-console-receiving.json" +target.write_text(json.dumps(record,indent=2,ensure_ascii=True)+"\n") +print(json.dumps({"receipt":str(target),"receipt_sha256":sha(target.read_bytes()), + "contract_sha256":record["contract_sha256"],"receiver_sha256":contract["receiver_sha256"], + "summary":record["summary"]})) +sys.exit(0 if positive["pass"] and negative["pass"] and unchanged else 1) diff --git a/docs/receiving/diff-hunks-6cc6795e89f4/native-console/independent/retained-files.json b/docs/receiving/diff-hunks-6cc6795e89f4/native-console/independent/retained-files.json new file mode 100644 index 0000000..97f5a46 --- /dev/null +++ b/docs/receiving/diff-hunks-6cc6795e89f4/native-console/independent/retained-files.json @@ -0,0 +1,17 @@ +[ + { + "source": "/home/jacob/testpilot-publication-6cc6795e89f4/installed-console-receiving.json", + "retained": "installed-console-receiving.json", + "sha256": "2c5aa3f6ceccb407ba8cb83160552ffe06c8884a2035a001bbca4c00ab998214" + }, + { + "source": "/home/jacob/testpilot-publication-6cc6795e89f4/installed-console-contract.json", + "retained": "installed-console-contract.json", + "sha256": "401e6a2210e29e4b4f3833aa54e78d7602825f65663f89c1ab9c08bd29dd2745" + }, + { + "source": "/home/jacob/testpilot-publication-6cc6795e89f4/receive_installed_console.py", + "retained": "receive_installed_console.py.txt", + "sha256": "fcae956355b52221ec7205338494b3e35c072b52f81b5a06082b1506af024828" + } +] diff --git a/docs/receiving/diff-hunks-6cc6795e89f4/native-console/installed-own-change-receiving.json b/docs/receiving/diff-hunks-6cc6795e89f4/native-console/installed-own-change-receiving.json new file mode 100644 index 0000000..ec9800f --- /dev/null +++ b/docs/receiving/diff-hunks-6cc6795e89f4/native-console/installed-own-change-receiving.json @@ -0,0 +1,23 @@ +{ + "command": [ + "/home/jacob/testpilot-native-6cc6795e89f4/40860534a8fd/bin/testpilot", + "targets", + "--repo", + "/home/jacob/testpilot-hunks-integration-6cc6795e89f4", + "--git-base", + "cec50d8df4499ba509615e179d82ef3861379bd2", + "--json" + ], + "exit": 0, + "targets": [ + [ + "testpilot/__main__.py", + "main" + ], + [ + "testpilot/diff.py", + "parse_unified_diff" + ] + ], + "output_sha256": "af765465c66afc862e82d864307a4f80398f5706b734b241c0216eba8f69091c" +} diff --git a/docs/receiving/diff-hunks-6cc6795e89f4/native-console/installed-own-change-targets.json b/docs/receiving/diff-hunks-6cc6795e89f4/native-console/installed-own-change-targets.json new file mode 100644 index 0000000..40636c4 --- /dev/null +++ b/docs/receiving/diff-hunks-6cc6795e89f4/native-console/installed-own-change-targets.json @@ -0,0 +1,116 @@ +{ + "changed_functions": [ + { + "path": "testpilot/__main__.py", + "module": "testpilot.__main__", + "qualname": "main", + "lineno": 81, + "end_lineno": 162, + "changed_lines": [ + 134, + 135, + 136, + 137, + 138, + 139, + 156, + 157, + 158 + ], + "source": "def main(argv: list[str] | None = None) -> int:\n ap = argparse.ArgumentParser(prog=\"testpilot\")\n sub = ap.add_subparsers(dest=\"cmd\", required=True)\n r = sub.add_parser(\"run\", help=\"generate, run and repair tests for a diff\")\n r.add_argument(\"--repo\", required=True)\n g = r.add_mutually_exclusive_group(required=True)\n g.add_argument(\"--diff\", help=\"unified diff file ('-' for stdin)\")\n g.add_argument(\"--git-base\", help=\"diff the working tree against this git ref\")\n r.add_argument(\"--backend\", choices=[\"scripted\", \"tokenfactory\", \"openai\"], default=None,\n help=\"default: $TESTPILOT_BACKEND or scripted\")\n r.add_argument(\"--script\", help=\"dir of canned responses for the scripted backend\")\n r.add_argument(\"--base-url\", help=\"override OpenAI-compatible base URL\")\n r.add_argument(\"--rounds\", type=int, default=3, help=\"max repair rounds (default 3)\")\n r.add_argument(\"--timeout\", type=float, default=60.0, help=\"pytest timeout per run, seconds\")\n r.add_argument(\"--no-coverage\", dest=\"coverage\", action=\"store_const\", const=False, default=None,\n help=\"run pytest without optional coverage measurement (default: automatic when installed)\")\n r.add_argument(\"--python\", type=_python_executable, metavar=\"EXECUTABLE\",\n help=\"project Python for all test runs; path or PATH command (default: this interpreter)\")\n r.add_argument(\"--max-tokens\", type=int, default=None, help=\"total token budget for the run\")\n r.add_argument(\"--out\", default=\"testpilot-out\")\n r.add_argument(\"--target\", action=\"append\", metavar=\"PATH.py::QUALNAME\",\n help=\"select this function explicitly; repeat for caller order, instead of automatic diff selection\")\n\n preview = sub.add_parser(\"targets\", help=\"inspect changed functions without generating or running tests\")\n preview.add_argument(\"--repo\", required=True)\n source = preview.add_mutually_exclusive_group(required=True)\n source.add_argument(\"--diff\", help=\"unified diff file ('-' for stdin)\")\n source.add_argument(\"--git-base\", help=\"diff the working tree against this git ref\")\n preview.add_argument(\"--json\", action=\"store_true\", help=\"include full native target records and source\")\n preview.add_argument(\"--target\", action=\"append\", metavar=\"PATH.py::QUALNAME\",\n help=\"inspect this explicit function; repeat instead of automatic diff selection\")\n recheck = sub.add_parser(\"recheck\", help=\"rerun exact tests from a saved report without model calls\")\n recheck.add_argument(\"--repo\", required=True, help=\"current project checkout\")\n recheck.add_argument(\"--report\", required=True, help=\"saved report.json or recheck.json\")\n recheck.add_argument(\"--out\", required=True, help=\"new result directory outside the checked repository\")\n recheck.add_argument(\"--timeout\", type=float, default=60.0, help=\"pytest timeout, seconds (default 60)\")\n recheck.add_argument(\"--python\", type=_python_executable, metavar=\"EXECUTABLE\",\n help=\"project Python; path or PATH command (default: this interpreter)\")\n a = ap.parse_args(argv)\n\n if a.cmd == \"recheck\":\n return run_recheck_command(a)\n\n if a.cmd == \"targets\":\n return _preview_targets(a)\n\n diff_text = _read_diff(a)\n if a.target is not None:\n try:\n resolve_targets(a.repo, diff_text, a.target)\n except TargetSelectionError as e:\n print(f\"testpilot: invalid target selection: {e}\", file=sys.stderr)\n return 2\n else:\n try:\n parse_unified_diff(diff_text)\n except DiffFormatError as e:\n print(f\"testpilot: invalid diff format: {e}\", file=sys.stderr)\n return 2\n try:\n client = make_client(a.backend, script=a.script, base_url=a.base_url)\n except ModelError as e:\n print(f\"testpilot: {e}\", file=sys.stderr)\n return 2\n pilot = TestPilot(client, RoutingConfig.from_env(), max_repair_rounds=a.rounds, timeout_s=a.timeout,\n max_total_tokens=a.max_tokens, coverage=a.coverage, python=a.python)\n try:\n res = (pilot.run(a.repo, diff_text) if a.target is None\n else pilot.run(a.repo, diff_text, targets=a.target))\n except TargetSelectionError as e:\n print(f\"testpilot: invalid target selection: {e}\", file=sys.stderr)\n return 2\n except DiffSourceError as e:\n print(f\"testpilot: invalid diff source: {e}\", file=sys.stderr)\n return 2\n except DiffFormatError as e:\n print(f\"testpilot: invalid diff format: {e}\", file=sys.stderr)\n return 2\n paths = write_outputs(res, a.out)\n print(render_report(res).split(\"\\n## Patch\")[0])\n print(f\"wrote {', '.join(str(p) for p in paths.values())}\")\n return 0 if res.ok else 1", + "is_method": false + }, + { + "path": "testpilot/diff.py", + "module": "testpilot.diff", + "qualname": "parse_unified_diff", + "lineno": 99, + "end_lineno": 221, + "changed_lines": [ + 100, + 102, + 103, + 104, + 109, + 110, + 113, + 114, + 115, + 116, + 117, + 118, + 119, + 120, + 121, + 122, + 123, + 124, + 125, + 126, + 127, + 128, + 129, + 130, + 131, + 132, + 133, + 134, + 135, + 136, + 138, + 141, + 142, + 148, + 149, + 150, + 155, + 156, + 157, + 158, + 162, + 163, + 166, + 167, + 169, + 172, + 173, + 174, + 176, + 177, + 180, + 181, + 190, + 192, + 193, + 194, + 195, + 196, + 197, + 198, + 199, + 200, + 201, + 202, + 203, + 204, + 205, + 206, + 207, + 208, + 210, + 211, + 212, + 213, + 214, + 215, + 216, + 217, + 218, + 219, + 220 + ], + "source": "def parse_unified_diff(text: str) -> list[FileChange]:\n \"\"\"Parse complete git diff / diff -u hunks into changed line sets.\n\n Declared old/new counts must match the complete body. A damaged later hunk\n rejects the whole diff, so callers never act on a partial target selection.\n Header-looking source lines remain body records while counts are pending.\n \"\"\"\n files: list[FileChange] = []\n cur: FileChange | None = None\n pending_old: str | None = None\n pending_header = False\n saw_hunk = False\n old_left = new_left = 0\n new_ln = 0\n\n def invalid(reason: str) -> DiffFormatError:\n return DiffFormatError(f\"invalid unified diff at record {record_number}: {reason}\")\n\n # Git records use LF, not Unicode separators. The token after a terminal LF\n # is not a blank context record and must not satisfy a missing hunk line.\n records = text.split(\"\\n\")\n if records[-1] == \"\":\n records.pop()\n mail_envelope = bool(records and _MAIL_FROM.fullmatch(records[0].removesuffix(\"\\r\")))\n record_number = 0\n for record_number, line in enumerate(records, 1):\n line = line.removesuffix(\"\\r\") # A saved diff may use CRLF records.\n # A Git mail's terminal plain-text signature is metadata. A deleted\n # source line can be \"-- \"; the footer cannot fill a missing old line.\n if mail_envelope and saw_hunk and line == \"-- \" and record_number < len(records):\n signature = records[record_number].removesuffix(\"\\r\")\n if signature and all(\n not records[index].removesuffix(\"\\r\").startswith((\"+\", \"-\", \" \", \"@@\", \"diff --git \"))\n for index in range(record_number, len(records))\n ):\n if old_left or new_left:\n raise invalid(\"signature footer before a complete hunk\")\n continue\n if cur is not None and (old_left > 0 or new_left > 0):\n if line == \"\\\\ No newline at end of file\":\n continue\n if line.startswith(\"+\"):\n if new_left == 0:\n raise invalid(\"addition exceeds the new range\")\n cur.added_lines.add(new_ln)\n cur.touched_lines.add(new_ln)\n new_ln += 1\n new_left -= 1\n elif line.startswith(\"-\"):\n if old_left == 0:\n raise invalid(\"deletion exceeds the old range\")\n # Anchor a pure deletion to the surrounding new-file lines.\n for anchor in (new_ln - 1, new_ln):\n if anchor >= 1:\n cur.touched_lines.add(anchor)\n old_left -= 1\n elif line.startswith(\" \") or line == \"\":\n # Retain support for a blank context line with its space stripped.\n if old_left == 0 or new_left == 0:\n raise invalid(\"context exceeds a declared range\")\n new_ln += 1\n new_left -= 1\n old_left -= 1\n else:\n raise invalid(\"incomplete hunk or invalid body prefix\")\n continue\n if line.startswith(\"diff --git \"):\n if pending_header or (cur is not None and not saw_hunk):\n raise invalid(\"file headers have no complete hunk\")\n cur, pending_old = None, None\n saw_hunk = False\n continue\n if line.startswith(\"--- \"):\n if pending_header or (cur is not None and not saw_hunk):\n raise invalid(\"file headers have no complete hunk\")\n cur = None\n pending_old = _strip_path(line[4:])\n pending_header = True\n saw_hunk = False\n continue\n if line.startswith(\"+++ \"):\n if not pending_header:\n raise invalid(\"new file header has no old file header\")\n new_path = _strip_path(line[4:])\n cur = FileChange(\n path=new_path or pending_old or \"\",\n old_path=pending_old,\n is_new=pending_old is None,\n is_deleted=new_path is None,\n )\n files.append(cur)\n pending_header = False\n continue\n if line.startswith(\"@@\"):\n m = _HUNK.match(line)\n if m is None or cur is None:\n raise invalid(\"malformed hunk range or missing file headers\")\n try:\n old_start, new_start = int(m.group(1)), int(m.group(3))\n old_left = int(m.group(2)) if m.group(2) is not None else 1\n new_left = int(m.group(4)) if m.group(4) is not None else 1\n except ValueError as exc:\n raise invalid(\"hunk range is not a supported integer\") from exc\n if (old_left and old_start == 0) or (new_left and new_start == 0):\n raise invalid(\"nonempty hunk ranges must start at line one or later\")\n if old_left == 0 and new_left == 0:\n raise invalid(\"hunk has no source records\")\n saw_hunk = True\n new_ln = new_start\n if new_left == 0: # \"+N,0\" means \"after line N\".\n new_ln += 1\n continue\n if line == \"\\\\ No newline at end of file\":\n if not saw_hunk:\n raise invalid(\"newline marker has no hunk\")\n continue\n if saw_hunk and line.startswith((\"+\", \"-\", \" \")):\n raise invalid(\"body record exceeds the declared hunk ranges\")\n if old_left or new_left:\n raise invalid(f\"incomplete hunk ({old_left} old and {new_left} new records missing)\")\n if pending_header or (cur is not None and not saw_hunk):\n raise invalid(\"file headers have no complete hunk\")\n return files", + "is_method": false + } + ] +} diff --git a/docs/receiving/diff-hunks-6cc6795e89f4/native-console/receiving/commands.json b/docs/receiving/diff-hunks-6cc6795e89f4/native-console/receiving/commands.json new file mode 100644 index 0000000..71803de --- /dev/null +++ b/docs/receiving/diff-hunks-6cc6795e89f4/native-console/receiving/commands.json @@ -0,0 +1,179 @@ +[ + { + "kind": "git", + "args": [ + "init", + "-q" + ], + "exit": 0, + "stdout": "", + "stderr": "" + }, + { + "kind": "git", + "args": [ + "config", + "core.autocrlf", + "false" + ], + "exit": 0, + "stdout": "", + "stderr": "" + }, + { + "kind": "git", + "args": [ + "add", + "sample.py" + ], + "exit": 0, + "stdout": "", + "stderr": "" + }, + { + "kind": "git", + "args": [ + "-c", + "user.name=TestPilot native receiving", + "-c", + "user.email=testpilot-native@example.invalid", + "commit", + "-qm", + "Original fixture source" + ], + "exit": 0, + "stdout": "", + "stderr": "" + }, + { + "kind": "git", + "args": [ + "diff", + "--no-ext-diff", + "--no-textconv", + "--no-color", + "--src-prefix=a/", + "--dst-prefix=b/", + "HEAD", + "--", + "sample.py" + ], + "exit": 0, + "stdout": "diff --git a/sample.py b/sample.py\nindex 419cbd0..578716e 100644\n--- a/sample.py\n+++ b/sample.py\n@@ -1,2 +1,2 @@\n def selected():\n- return 1\n+ return 2\n", + "stderr": "" + }, + { + "kind": "git", + "args": [ + "apply", + "--reverse", + "--check", + "-" + ], + "exit": 0, + "stdout": "", + "stderr": "" + }, + { + "kind": "git", + "args": [ + "apply", + "--reverse", + "--check", + "-" + ], + "exit": 128, + "stdout": "", + "stderr": "error: corrupt patch at line 9\n" + }, + { + "name": "installed-targets", + "at": "2026-10-08T18:01:12.800001+00:00", + "command": [ + "/home/jacob/testpilot-native-6cc6795e89f4/40860534a8fd/bin/testpilot", + "targets", + "--repo", + "/home/jacob/testpilot-native-6cc6795e89f4/receiving/project", + "--git-base", + "HEAD", + "--json" + ], + "cwd": "/home/jacob/testpilot-native-6cc6795e89f4/receiving", + "exit": 0, + "stdout_sha256": "cb010fe951762bf18eba8c7889933fec8cf199113bc1ce53ec5516656d42e900", + "stderr_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + { + "name": "installed-valid-run", + "at": "2026-10-08T18:01:13.674280+00:00", + "command": [ + "/home/jacob/testpilot-native-6cc6795e89f4/40860534a8fd/bin/testpilot", + "run", + "--repo", + "/home/jacob/testpilot-native-6cc6795e89f4/receiving/project", + "--diff", + "/home/jacob/testpilot-native-6cc6795e89f4/receiving/valid.diff", + "--backend", + "scripted", + "--script", + "/home/jacob/testpilot-native-6cc6795e89f4/receiving/script", + "--rounds", + "0", + "--no-coverage", + "--out", + "/home/jacob/testpilot-native-6cc6795e89f4/receiving/valid-result" + ], + "cwd": "/home/jacob/testpilot-native-6cc6795e89f4/receiving", + "exit": 0, + "stdout_sha256": "ec492bd514346d55ad9d5d2948e384a5b2aed56897d0f476e7bd30870a799378", + "stderr_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + { + "name": "installed-invalid-new", + "at": "2026-10-08T18:01:18.177196+00:00", + "command": [ + "/home/jacob/testpilot-native-6cc6795e89f4/40860534a8fd/bin/testpilot", + "run", + "--repo", + "/home/jacob/testpilot-native-6cc6795e89f4/receiving/project", + "--diff", + "/home/jacob/testpilot-native-6cc6795e89f4/receiving/damaged.diff", + "--backend", + "scripted", + "--script", + "/home/jacob/testpilot-native-6cc6795e89f4/receiving/nonexistent-model-config", + "--rounds", + "0", + "--out", + "/home/jacob/testpilot-native-6cc6795e89f4/receiving/refused-result" + ], + "cwd": "/home/jacob/testpilot-native-6cc6795e89f4/receiving", + "exit": 2, + "stdout_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "stderr_sha256": "e0dcf9b14862a303da4290fbcb41cac786fcc39780d05580a1631f1cef77c66e" + }, + { + "name": "installed-invalid-existing", + "at": "2026-10-08T18:01:18.580558+00:00", + "command": [ + "/home/jacob/testpilot-native-6cc6795e89f4/40860534a8fd/bin/testpilot", + "run", + "--repo", + "/home/jacob/testpilot-native-6cc6795e89f4/receiving/project", + "--diff", + "/home/jacob/testpilot-native-6cc6795e89f4/receiving/damaged.diff", + "--backend", + "scripted", + "--script", + "/home/jacob/testpilot-native-6cc6795e89f4/receiving/nonexistent-model-config", + "--rounds", + "0", + "--out", + "/home/jacob/testpilot-native-6cc6795e89f4/receiving/valid-result" + ], + "cwd": "/home/jacob/testpilot-native-6cc6795e89f4/receiving", + "exit": 2, + "stdout_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "stderr_sha256": "e0dcf9b14862a303da4290fbcb41cac786fcc39780d05580a1631f1cef77c66e" + } +] diff --git a/docs/receiving/diff-hunks-6cc6795e89f4/native-console/receiving/damaged.diff b/docs/receiving/diff-hunks-6cc6795e89f4/native-console/receiving/damaged.diff new file mode 100644 index 0000000..e1d1a47 --- /dev/null +++ b/docs/receiving/diff-hunks-6cc6795e89f4/native-console/receiving/damaged.diff @@ -0,0 +1,8 @@ +diff --git a/sample.py b/sample.py +index 419cbd0..578716e 100644 +--- a/sample.py ++++ b/sample.py +@@ -1,3 +1,3 @@ + def selected(): +- return 1 ++ return 2 diff --git a/docs/receiving/diff-hunks-6cc6795e89f4/native-console/receiving/installed-invalid-existing.stderr.txt b/docs/receiving/diff-hunks-6cc6795e89f4/native-console/receiving/installed-invalid-existing.stderr.txt new file mode 100644 index 0000000..49fb62b --- /dev/null +++ b/docs/receiving/diff-hunks-6cc6795e89f4/native-console/receiving/installed-invalid-existing.stderr.txt @@ -0,0 +1 @@ +testpilot: invalid diff format: invalid unified diff at record 8: incomplete hunk (1 old and 1 new records missing) diff --git a/docs/receiving/diff-hunks-6cc6795e89f4/native-console/receiving/installed-invalid-existing.stdout.txt b/docs/receiving/diff-hunks-6cc6795e89f4/native-console/receiving/installed-invalid-existing.stdout.txt new file mode 100644 index 0000000..e69de29 diff --git a/docs/receiving/diff-hunks-6cc6795e89f4/native-console/receiving/installed-invalid-new.stderr.txt b/docs/receiving/diff-hunks-6cc6795e89f4/native-console/receiving/installed-invalid-new.stderr.txt new file mode 100644 index 0000000..49fb62b --- /dev/null +++ b/docs/receiving/diff-hunks-6cc6795e89f4/native-console/receiving/installed-invalid-new.stderr.txt @@ -0,0 +1 @@ +testpilot: invalid diff format: invalid unified diff at record 8: incomplete hunk (1 old and 1 new records missing) diff --git a/docs/receiving/diff-hunks-6cc6795e89f4/native-console/receiving/installed-invalid-new.stdout.txt b/docs/receiving/diff-hunks-6cc6795e89f4/native-console/receiving/installed-invalid-new.stdout.txt new file mode 100644 index 0000000..e69de29 diff --git a/docs/receiving/diff-hunks-6cc6795e89f4/native-console/receiving/installed-targets.stderr.txt b/docs/receiving/diff-hunks-6cc6795e89f4/native-console/receiving/installed-targets.stderr.txt new file mode 100644 index 0000000..e69de29 diff --git a/docs/receiving/diff-hunks-6cc6795e89f4/native-console/receiving/installed-targets.stdout.txt b/docs/receiving/diff-hunks-6cc6795e89f4/native-console/receiving/installed-targets.stdout.txt new file mode 100644 index 0000000..3d468e7 --- /dev/null +++ b/docs/receiving/diff-hunks-6cc6795e89f4/native-console/receiving/installed-targets.stdout.txt @@ -0,0 +1,16 @@ +{ + "changed_functions": [ + { + "path": "sample.py", + "module": "sample", + "qualname": "selected", + "lineno": 1, + "end_lineno": 2, + "changed_lines": [ + 2 + ], + "source": "def selected():\n return 2", + "is_method": false + } + ] +} diff --git a/docs/receiving/diff-hunks-6cc6795e89f4/native-console/receiving/installed-valid-run.stderr.txt b/docs/receiving/diff-hunks-6cc6795e89f4/native-console/receiving/installed-valid-run.stderr.txt new file mode 100644 index 0000000..e69de29 diff --git a/docs/receiving/diff-hunks-6cc6795e89f4/native-console/receiving/installed-valid-run.stdout.txt b/docs/receiving/diff-hunks-6cc6795e89f4/native-console/receiving/installed-valid-run.stdout.txt new file mode 100644 index 0000000..8db5266 --- /dev/null +++ b/docs/receiving/diff-hunks-6cc6795e89f4/native-console/receiving/installed-valid-run.stdout.txt @@ -0,0 +1,12 @@ +# TestPilot report: **passed** + +- Changed functions: sample.py::selected +- Tests written: 1 in tests/test_testpilot_generated.py +- Repair rounds used: 0/0 +- Final run: 1 passed, 0 failed, 0 errors, 0 skipped (rc=0); generated: 1 passed of 1 collected in 1.217s +- Coverage: unavailable +- Tokens: 280 (estimated); cost: unpriced (set TESTPILOT_PRICES) + - `nvidia/nemotron-3-super-120b-a12b`: 1 calls, 93 in / 7 out + - `nvidia/Nemotron-3_5-Lightning`: 1 calls, 151 in / 29 out + +wrote /home/jacob/testpilot-native-6cc6795e89f4/receiving/valid-result/testpilot.patch, /home/jacob/testpilot-native-6cc6795e89f4/receiving/valid-result/report.json, /home/jacob/testpilot-native-6cc6795e89f4/receiving/valid-result/report.md, /home/jacob/testpilot-native-6cc6795e89f4/receiving/valid-result/report.html diff --git a/docs/receiving/diff-hunks-6cc6795e89f4/native-console/receiving/receiving.json b/docs/receiving/diff-hunks-6cc6795e89f4/native-console/receiving/receiving.json new file mode 100644 index 0000000..dd71e5b --- /dev/null +++ b/docs/receiving/diff-hunks-6cc6795e89f4/native-console/receiving/receiving.json @@ -0,0 +1,114 @@ +{ + "schema": "hamon.installed_product_receiving.v1", + "at": "2026-10-08T18:01:18.878935+00:00", + "console": "/home/jacob/testpilot-native-6cc6795e89f4/40860534a8fd/bin/testpilot", + "source_head": "40860534a8fdc760b105ac9bc7d14ac9087172dd", + "wheel_sha256": "83c756b33833e004c6421c203888c54862a4eb4995dba1e08bfea3128dec9c4b", + "source_fixture_git_head": "15d9d9d72e77b2f2bc847ae9ec0f2a306dc3f9bd", + "valid_git_apply_check": true, + "damaged_git_apply_refused": true, + "targets": 1, + "valid_run": { + "exit": 0, + "report_status": "passed", + "ok": true, + "final": { + "returncode": 0, + "timed_out": false, + "duration_s": 1.217, + "timeout_s": 60.0, + "output": ". [100%]\n1 passed in 0.03s\n", + "cases": [ + { + "nodeid": "tests.test_testpilot_generated::test_selected", + "outcome": "passed", + "message": "", + "generated_file": "tests/test_testpilot_generated.py" + } + ], + "coverage": null, + "generated_files": [ + "tests/test_testpilot_generated.py" + ], + "junit_available": true, + "passed": 1, + "failed": 0, + "errors": 0, + "skipped": 0, + "ok": true, + "summary": "1 passed, 0 failed, 0 errors, 0 skipped (rc=0); generated: 1 passed of 1 collected", + "generated": { + "collected": 1, + "passed": 1, + "failed": 0, + "error": 0, + "skipped": 0 + } + }, + "tests_written": 1, + "ledger": { + "total_tokens": 280, + "total_cost_usd": 0.0, + "cost_is_complete": false, + "tokens_estimated": true, + "by_model": { + "nvidia/nemotron-3-super-120b-a12b": { + "calls": 1, + "prompt_tokens": 93, + "completion_tokens": 7, + "cost_usd": 0.0 + }, + "nvidia/Nemotron-3_5-Lightning": { + "calls": 1, + "prompt_tokens": 151, + "completion_tokens": 29, + "cost_usd": 0.0 + } + }, + "entries": [ + { + "role": "planner", + "model": "nvidia/nemotron-3-super-120b-a12b", + "prompt_tokens": 93, + "completion_tokens": 7, + "estimated": true, + "cost_usd": 0.0 + }, + { + "role": "editor", + "model": "nvidia/Nemotron-3_5-Lightning", + "prompt_tokens": 151, + "completion_tokens": 29, + "estimated": true, + "cost_usd": 0.0 + } + ] + }, + "output": "/home/jacob/testpilot-native-6cc6795e89f4/receiving/valid-result", + "output_files_sha256": { + "testpilot.patch": "02912f066519b84d64a9966b216ceef68ecc479720784f53011e8b8b48ecc903", + "report.md": "08652aa33d5d7c6c04fc97c05ffd3f4990a7b926f4c62f5917a6bffce910086e", + "report.html": "a759d64aecc36e213297aec71e96b80a35261ef4276ee107763ce214fa212ed2", + "report.json": "2743e3e27891263b29fced6533cf57075faf3a5f800faf1e386f29be2be23f87" + } + }, + "refusals": [ + { + "name": "installed-invalid-new", + "exit": 2, + "new_output_absent": true, + "existing_outputs_unchanged": true, + "nonexistent_model_config_not_opened": true + }, + { + "name": "installed-invalid-existing", + "exit": 2, + "new_output_absent": true, + "existing_outputs_unchanged": true, + "nonexistent_model_config_not_opened": true + } + ], + "original_source_preserved": true, + "scope": "Actual installed console through local ScriptedModel fixtures only; no external model/provider call", + "commands": "/home/jacob/testpilot-native-6cc6795e89f4/receiving/commands.json" +} diff --git a/docs/receiving/diff-hunks-6cc6795e89f4/native-console/receiving/sample.py.txt b/docs/receiving/diff-hunks-6cc6795e89f4/native-console/receiving/sample.py.txt new file mode 100644 index 0000000..578716e --- /dev/null +++ b/docs/receiving/diff-hunks-6cc6795e89f4/native-console/receiving/sample.py.txt @@ -0,0 +1,2 @@ +def selected(): + return 2 diff --git a/docs/receiving/diff-hunks-6cc6795e89f4/native-console/receiving/script/01_plan.md b/docs/receiving/diff-hunks-6cc6795e89f4/native-console/receiving/script/01_plan.md new file mode 100644 index 0000000..5d07126 --- /dev/null +++ b/docs/receiving/diff-hunks-6cc6795e89f4/native-console/receiving/script/01_plan.md @@ -0,0 +1 @@ +Check selected returns two. diff --git a/docs/receiving/diff-hunks-6cc6795e89f4/native-console/receiving/script/02_tests.md b/docs/receiving/diff-hunks-6cc6795e89f4/native-console/receiving/script/02_tests.md new file mode 100644 index 0000000..b564f0f --- /dev/null +++ b/docs/receiving/diff-hunks-6cc6795e89f4/native-console/receiving/script/02_tests.md @@ -0,0 +1,5 @@ +```python tests/test_selected.py +from sample import selected +def test_selected(): + assert selected() == 2 +``` diff --git a/docs/receiving/diff-hunks-6cc6795e89f4/native-console/receiving/valid-result/report.html b/docs/receiving/diff-hunks-6cc6795e89f4/native-console/receiving/valid-result/report.html new file mode 100644 index 0000000..602c5e3 --- /dev/null +++ b/docs/receiving/diff-hunks-6cc6795e89f4/native-console/receiving/valid-result/report.html @@ -0,0 +1,112 @@ +TestPilot run review · passed

TestPilot / Run review

Passed

passed

The runner reported a passing selected suite. Passing tests do not establish that the changed code is correct.

Both downloads are embedded in this file. Opening, reviewing or downloading does not run tests or apply the patch.

Generated cases passed / collected
1 / 1
Repair rounds used / limit
0 / 0
Tokens · estimated
280

Final recorded result

1 passed, 0 failed, 0 errors, 0 skipped (rc=0); generated: 1 passed of 1 collected

Return code: 0 · Duration: 1.217 s · Timeout: no

Recorded selected-suite case counts
PassedFailedErrorsSkipped
1000

Scroll the table horizontally to read all columns.

Recorded generated-case counts
CollectedPassedFailedErrorsSkipped
11000

Scroll the table horizontally to read all columns.

Recorded test cases

  1. passed tests.test_testpilot_generated::test_selected Generated test

    No diagnostic message recorded.

Recorded pytest output

The runner retains the last 3,000 characters in its report. This is the recorded tail, not a complete process log.

.                                                                        [100%]
+1 passed in 0.03s
+

Final generated files

Reported authored-test count: 1. This count is separate from verified generated-case execution above.

tests/test_testpilot_generated.py
from sample import selected
+def test_selected():
+    assert selected() == 2
+

Repair history

Each round keeps its recorded source snapshot and result. A model response without a new run does not add an execution.

  1. Round 0 · generate

    Tests at this round

    tests/test_testpilot_generated.py
    from sample import selected
    +def test_selected():
    +    assert selected() == 2
    +

    1 passed, 0 failed, 0 errors, 0 skipped (rc=0); generated: 1 passed of 1 collected

    Return code: 0 · Duration: 1.217 s · Timeout: no

    Recorded selected-suite case counts
    PassedFailedErrorsSkipped
    1000

    Scroll the table horizontally to read all columns.

    Recorded generated-case counts
    CollectedPassedFailedErrorsSkipped
    11000

    Scroll the table horizontally to read all columns.

    Recorded test cases

    1. passed tests.test_testpilot_generated::test_selected Generated test

      No diagnostic message recorded.

    Recorded pytest output

    The runner retains the last 3,000 characters in its report. This is the recorded tail, not a complete process log.

    .                                                                        [100%]
    +1 passed in 0.03s
    +

Plan and selected source

Model test plan

Check selected returns two.
+
sample.py::selected lines 1–2
def selected():
+    return 2

Coverage

Coverage unavailable. This report contains no comparable before/after measurement.

Model and token ledger

Token counts are estimated from text length.

Cost is unpriced or incomplete. The recorded subtotal ($0.0 USD) does not establish the full cost.

Calls in recorded order
CallRoleModelPrompt tokensCompletion tokensToken sourceRecorded cost (USD)
1plannernvidia/nemotron-3-super-120b-a12b937estimated0.0
2editornvidia/Nemotron-3_5-Lightning15129estimated0.0

Scroll the table horizontally to read all columns.

Exact recorded patch

diff --git a/tests/test_testpilot_generated.py b/tests/test_testpilot_generated.py
+new file mode 100644
+--- /dev/null
++++ b/tests/test_testpilot_generated.py
+@@ -0,0 +1,3 @@
++from sample import selected
++def test_selected():
++    assert selected() == 2
+

This file reviews the recorded TestPilot result. It does not rerun the repository, verify a model diagnosis, or change any source. Text is displayed literally; HTML-incompatible controls and surrogate characters use visible escapes. Embedded downloads retain the original artifact bytes.

Use your browser’s Print command to print the report. No scripts, remote resources, storage or account are required.

diff --git a/docs/receiving/diff-hunks-6cc6795e89f4/native-console/receiving/valid-result/report.json b/docs/receiving/diff-hunks-6cc6795e89f4/native-console/receiving/valid-result/report.json new file mode 100644 index 0000000..55ed7b5 --- /dev/null +++ b/docs/receiving/diff-hunks-6cc6795e89f4/native-console/receiving/valid-result/report.json @@ -0,0 +1,145 @@ +{ + "status": "passed", + "changed_functions": [ + { + "path": "sample.py", + "module": "sample", + "qualname": "selected", + "lineno": 1, + "end_lineno": 2, + "changed_lines": [ + 2 + ], + "source": "def selected():\n return 2", + "is_method": false + } + ], + "repair_rounds_used": 0, + "max_repair_rounds": 0, + "test_files": { + "tests/test_testpilot_generated.py": "from sample import selected\ndef test_selected():\n assert selected() == 2\n" + }, + "tests_written": 1, + "final": { + "returncode": 0, + "timed_out": false, + "duration_s": 1.217, + "timeout_s": 60.0, + "output": ". [100%]\n1 passed in 0.03s\n", + "cases": [ + { + "nodeid": "tests.test_testpilot_generated::test_selected", + "outcome": "passed", + "message": "", + "generated_file": "tests/test_testpilot_generated.py" + } + ], + "coverage": null, + "generated_files": [ + "tests/test_testpilot_generated.py" + ], + "junit_available": true, + "passed": 1, + "failed": 0, + "errors": 0, + "skipped": 0, + "ok": true, + "summary": "1 passed, 0 failed, 0 errors, 0 skipped (rc=0); generated: 1 passed of 1 collected", + "generated": { + "collected": 1, + "passed": 1, + "failed": 0, + "error": 0, + "skipped": 0 + } + }, + "patch": "diff --git a/tests/test_testpilot_generated.py b/tests/test_testpilot_generated.py\nnew file mode 100644\n--- /dev/null\n+++ b/tests/test_testpilot_generated.py\n@@ -0,0 +1,3 @@\n+from sample import selected\n+def test_selected():\n+ assert selected() == 2\n", + "coverage": null, + "ledger": { + "total_tokens": 280, + "total_cost_usd": 0.0, + "cost_is_complete": false, + "tokens_estimated": true, + "by_model": { + "nvidia/nemotron-3-super-120b-a12b": { + "calls": 1, + "prompt_tokens": 93, + "completion_tokens": 7, + "cost_usd": 0.0 + }, + "nvidia/Nemotron-3_5-Lightning": { + "calls": 1, + "prompt_tokens": 151, + "completion_tokens": 29, + "cost_usd": 0.0 + } + }, + "entries": [ + { + "role": "planner", + "model": "nvidia/nemotron-3-super-120b-a12b", + "prompt_tokens": 93, + "completion_tokens": 7, + "estimated": true, + "cost_usd": 0.0 + }, + { + "role": "editor", + "model": "nvidia/Nemotron-3_5-Lightning", + "prompt_tokens": 151, + "completion_tokens": 29, + "estimated": true, + "cost_usd": 0.0 + } + ] + }, + "rounds": [ + { + "round": 0, + "kind": "generate", + "files": [ + "tests/test_testpilot_generated.py" + ], + "result": { + "returncode": 0, + "timed_out": false, + "duration_s": 1.217, + "timeout_s": 60.0, + "output": ". [100%]\n1 passed in 0.03s\n", + "cases": [ + { + "nodeid": "tests.test_testpilot_generated::test_selected", + "outcome": "passed", + "message": "", + "generated_file": "tests/test_testpilot_generated.py" + } + ], + "coverage": null, + "generated_files": [ + "tests/test_testpilot_generated.py" + ], + "junit_available": true, + "passed": 1, + "failed": 0, + "errors": 0, + "skipped": 0, + "ok": true, + "summary": "1 passed, 0 failed, 0 errors, 0 skipped (rc=0); generated: 1 passed of 1 collected", + "generated": { + "collected": 1, + "passed": 1, + "failed": 0, + "error": 0, + "skipped": 0 + } + }, + "warnings": [], + "contents": { + "tests/test_testpilot_generated.py": "from sample import selected\ndef test_selected():\n assert selected() == 2\n" + } + } + ], + "plan": "Check selected returns two.\n", + "message": "", + "ok": true +} \ No newline at end of file diff --git a/docs/receiving/diff-hunks-6cc6795e89f4/native-console/receiving/valid-result/report.md b/docs/receiving/diff-hunks-6cc6795e89f4/native-console/receiving/valid-result/report.md new file mode 100644 index 0000000..6493d58 --- /dev/null +++ b/docs/receiving/diff-hunks-6cc6795e89f4/native-console/receiving/valid-result/report.md @@ -0,0 +1,23 @@ +# TestPilot report: **passed** + +- Changed functions: sample.py::selected +- Tests written: 1 in tests/test_testpilot_generated.py +- Repair rounds used: 0/0 +- Final run: 1 passed, 0 failed, 0 errors, 0 skipped (rc=0); generated: 1 passed of 1 collected in 1.217s +- Coverage: unavailable +- Tokens: 280 (estimated); cost: unpriced (set TESTPILOT_PRICES) + - `nvidia/nemotron-3-super-120b-a12b`: 1 calls, 93 in / 7 out + - `nvidia/Nemotron-3_5-Lightning`: 1 calls, 151 in / 29 out + +## Patch + +```diff +diff --git a/tests/test_testpilot_generated.py b/tests/test_testpilot_generated.py +new file mode 100644 +--- /dev/null ++++ b/tests/test_testpilot_generated.py +@@ -0,0 +1,3 @@ ++from sample import selected ++def test_selected(): ++ assert selected() == 2 +``` diff --git a/docs/receiving/diff-hunks-6cc6795e89f4/native-console/receiving/valid-result/testpilot.patch b/docs/receiving/diff-hunks-6cc6795e89f4/native-console/receiving/valid-result/testpilot.patch new file mode 100644 index 0000000..b00c451 --- /dev/null +++ b/docs/receiving/diff-hunks-6cc6795e89f4/native-console/receiving/valid-result/testpilot.patch @@ -0,0 +1,8 @@ +diff --git a/tests/test_testpilot_generated.py b/tests/test_testpilot_generated.py +new file mode 100644 +--- /dev/null ++++ b/tests/test_testpilot_generated.py +@@ -0,0 +1,3 @@ ++from sample import selected ++def test_selected(): ++ assert selected() == 2 diff --git a/docs/receiving/diff-hunks-6cc6795e89f4/native-console/receiving/valid.diff b/docs/receiving/diff-hunks-6cc6795e89f4/native-console/receiving/valid.diff new file mode 100644 index 0000000..844978d --- /dev/null +++ b/docs/receiving/diff-hunks-6cc6795e89f4/native-console/receiving/valid.diff @@ -0,0 +1,8 @@ +diff --git a/sample.py b/sample.py +index 419cbd0..578716e 100644 +--- a/sample.py ++++ b/sample.py +@@ -1,2 +1,2 @@ + def selected(): +- return 1 ++ return 2 diff --git a/docs/receiving/diff-hunks-6cc6795e89f4/native-console/retained-files.json b/docs/receiving/diff-hunks-6cc6795e89f4/native-console/retained-files.json new file mode 100644 index 0000000..c61883b --- /dev/null +++ b/docs/receiving/diff-hunks-6cc6795e89f4/native-console/retained-files.json @@ -0,0 +1,168 @@ +{ + "at": "2026-10-08T18:03:23.142875+00:00", + "policy": "Byte-identical copies of actual native installation and console receiving, without replay. Live example project is retained at the original path; source bytes are also retained as inert sample.py.txt. Wheel and source archive remain at original versioned artifact paths named by installation.json.", + "files": [ + { + "original": "/home/jacob/testpilot-native-6cc6795e89f4/README.md", + "retained": "README.md", + "bytes": 2106, + "sha256": "8ccb7067a4eedbe7d02e1f1d82480232451e5ce2bb72a4db23c5c2dced024657" + }, + { + "original": "/home/jacob/testpilot-native-6cc6795e89f4/artifacts/installation.json", + "retained": "artifacts/installation.json", + "bytes": 5516, + "sha256": "86d74c235a0d451fb42712015fb60f3a18c52b98407d67104a0b3ae4f974c0f0" + }, + { + "original": "/home/jacob/testpilot-native-6cc6795e89f4/artifacts/steps.json", + "retained": "artifacts/steps.json", + "bytes": 2381, + "sha256": "9cdf8e42b6a1fba90f52176bc9e178401b42b03e95fc214b37192619e09b8ab4" + }, + { + "original": "/home/jacob/testpilot-native-6cc6795e89f4/artifacts/build-wheel.log", + "retained": "artifacts/build-wheel.log", + "bytes": 635, + "sha256": "9d97755a682ffb49bf53653e6ad5d9932f7db350ee3a954fc4e45a62ee5ff039" + }, + { + "original": "/home/jacob/testpilot-native-6cc6795e89f4/artifacts/install-wheel.log", + "retained": "artifacts/install-wheel.log", + "bytes": 136, + "sha256": "d6e8e349e8f167d1515d7f4689e9f0eb530f574b9bffe0a0aa757127e14d88fe" + }, + { + "original": "/home/jacob/testpilot-native-6cc6795e89f4/artifacts/installed-help.log", + "retained": "artifacts/installed-help.log", + "bytes": 443, + "sha256": "b47b803f077188d47e7ed173248144474dbf4d256b5d3c7d8b17b945eeaec015" + }, + { + "original": "/home/jacob/testpilot-native-6cc6795e89f4/artifacts/source-archive.log", + "retained": "artifacts/source-archive.log", + "bytes": 0, + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + { + "original": "/home/jacob/testpilot-native-6cc6795e89f4/artifacts/venv-bootstrap.log", + "retained": "artifacts/venv-bootstrap.log", + "bytes": 0, + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + { + "original": "/home/jacob/testpilot-native-6cc6795e89f4/receiving/commands.json", + "retained": "receiving/commands.json", + "bytes": 4786, + "sha256": "5c2aeeff1f22d8ddefc3400ae3fc8a52949a6fb0532d3d4d400d7c56fe9dd554" + }, + { + "original": "/home/jacob/testpilot-native-6cc6795e89f4/receiving/damaged.diff", + "retained": "receiving/damaged.diff", + "bytes": 158, + "sha256": "93e6854a4ae3f14e2b800fa8f93bcf92b11fd3afaf2d70026b7daac6ddc654e0" + }, + { + "original": "/home/jacob/testpilot-native-6cc6795e89f4/receiving/installed-invalid-existing.stderr.txt", + "retained": "receiving/installed-invalid-existing.stderr.txt", + "bytes": 116, + "sha256": "e0dcf9b14862a303da4290fbcb41cac786fcc39780d05580a1631f1cef77c66e" + }, + { + "original": "/home/jacob/testpilot-native-6cc6795e89f4/receiving/installed-invalid-existing.stdout.txt", + "retained": "receiving/installed-invalid-existing.stdout.txt", + "bytes": 0, + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + { + "original": "/home/jacob/testpilot-native-6cc6795e89f4/receiving/installed-invalid-new.stderr.txt", + "retained": "receiving/installed-invalid-new.stderr.txt", + "bytes": 116, + "sha256": "e0dcf9b14862a303da4290fbcb41cac786fcc39780d05580a1631f1cef77c66e" + }, + { + "original": "/home/jacob/testpilot-native-6cc6795e89f4/receiving/installed-invalid-new.stdout.txt", + "retained": "receiving/installed-invalid-new.stdout.txt", + "bytes": 0, + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + { + "original": "/home/jacob/testpilot-native-6cc6795e89f4/receiving/installed-targets.stderr.txt", + "retained": "receiving/installed-targets.stderr.txt", + "bytes": 0, + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + { + "original": "/home/jacob/testpilot-native-6cc6795e89f4/receiving/installed-targets.stdout.txt", + "retained": "receiving/installed-targets.stdout.txt", + "bytes": 288, + "sha256": "cb010fe951762bf18eba8c7889933fec8cf199113bc1ce53ec5516656d42e900" + }, + { + "original": "/home/jacob/testpilot-native-6cc6795e89f4/receiving/installed-valid-run.stderr.txt", + "retained": "receiving/installed-valid-run.stderr.txt", + "bytes": 0, + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + { + "original": "/home/jacob/testpilot-native-6cc6795e89f4/receiving/installed-valid-run.stdout.txt", + "retained": "receiving/installed-valid-run.stdout.txt", + "bytes": 796, + "sha256": "ec492bd514346d55ad9d5d2948e384a5b2aed56897d0f476e7bd30870a799378" + }, + { + "original": "/home/jacob/testpilot-native-6cc6795e89f4/receiving/receiving.json", + "retained": "receiving/receiving.json", + "bytes": 3653, + "sha256": "5b7ccec59416d704a155b082d33147cc12c342a08236acb0a8eee2cba6ba3890" + }, + { + "original": "/home/jacob/testpilot-native-6cc6795e89f4/receiving/sample.py.txt", + "retained": "receiving/sample.py.txt", + "bytes": 29, + "sha256": "0a39e6db6d5e99a973953cc715febd5036bc8da95a19d78b1f91a26c3361a1ca" + }, + { + "original": "/home/jacob/testpilot-native-6cc6795e89f4/receiving/valid.diff", + "retained": "receiving/valid.diff", + "bytes": 158, + "sha256": "234259bdac29b208cd5c517cd37ac2cda56d6b2cb264f6d266f0d3ed0e73956d" + }, + { + "original": "/home/jacob/testpilot-native-6cc6795e89f4/receiving/script/01_plan.md", + "retained": "receiving/script/01_plan.md", + "bytes": 28, + "sha256": "3047ef940de7a0e92dad4a76d76bc5abfcc02619aece28518dff33bac6d735ba" + }, + { + "original": "/home/jacob/testpilot-native-6cc6795e89f4/receiving/script/02_tests.md", + "retained": "receiving/script/02_tests.md", + "bytes": 113, + "sha256": "b47b02e00d70fe8a81da2aae2e86543d28c4286b3b97c38295d566f1bc7e8c2b" + }, + { + "original": "/home/jacob/testpilot-native-6cc6795e89f4/receiving/valid-result/report.html", + "retained": "receiving/valid-result/report.html", + "bytes": 19968, + "sha256": "a759d64aecc36e213297aec71e96b80a35261ef4276ee107763ce214fa212ed2" + }, + { + "original": "/home/jacob/testpilot-native-6cc6795e89f4/receiving/valid-result/report.json", + "retained": "receiving/valid-result/report.json", + "bytes": 4028, + "sha256": "2743e3e27891263b29fced6533cf57075faf3a5f800faf1e386f29be2be23f87" + }, + { + "original": "/home/jacob/testpilot-native-6cc6795e89f4/receiving/valid-result/report.md", + "retained": "receiving/valid-result/report.md", + "bytes": 752, + "sha256": "08652aa33d5d7c6c04fc97c05ffd3f4990a7b926f4c62f5917a6bffce910086e" + }, + { + "original": "/home/jacob/testpilot-native-6cc6795e89f4/receiving/valid-result/testpilot.patch", + "retained": "receiving/valid-result/testpilot.patch", + "bytes": 253, + "sha256": "02912f066519b84d64a9966b216ceef68ecc479720784f53011e8b8b48ecc903" + } + ] +} diff --git a/docs/receiving/diff-hunks-6cc6795e89f4/oversize-native-run-v1.json b/docs/receiving/diff-hunks-6cc6795e89f4/oversize-native-run-v1.json new file mode 100644 index 0000000..83f9898 --- /dev/null +++ b/docs/receiving/diff-hunks-6cc6795e89f4/oversize-native-run-v1.json @@ -0,0 +1,18 @@ +{ + "at": "2026-10-08T17:23:44.136758+00:00", + "candidate": "628f9e3eb0a597d79e8fa9ad58fff62c9681d121", + "diff_sha256": "e2a58cefcb14e376724555cc17abf2adc9cd74e7c6fe4f05a1546291391d8f7c", + "input_description": "One5000-digit old hunk start, one-line old/new range and complete replacement body", + "parser": { + "accepted": false, + "error_type": "ValueError", + "is_diff_format_error": false, + "message": "Exceeds the limit (4300 digits) for integer string conversion: value has 5000 digits; use sys.set_int_max_str_digits() to increase the limit" + }, + "run": { + "exit": 1, + "stdout": "", + "stderr": "Traceback (most recent call last):\n File \"\", line 198, in _run_module_as_main\n File \"\", line 88, in _run_code\n File \"/home/jacob/testpilot-hunks-6cc6795e89f4/testpilot/__main__.py\", line 154, in \n raise SystemExit(main())\n ~~~~^^\n File \"/home/jacob/testpilot-hunks-6cc6795e89f4/testpilot/__main__.py\", line 136, in main\n res = (pilot.run(a.repo, diff_text) if a.target is None\n ~~~~~~~~~^^^^^^^^^^^^^^^^^^^\n File \"/home/jacob/testpilot-hunks-6cc6795e89f4/testpilot/loop.py\", line 418, in run\n funcs = (changed_functions(repo, diff_text) if selection is None\n ~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^\n File \"/home/jacob/testpilot-hunks-6cc6795e89f4/testpilot/diff.py\", line 303, in changed_functions\n for fc in parse_unified_diff(diff_text):\n ~~~~~~~~~~~~~~~~~~^^^^^^^^^^^\n File \"/home/jacob/testpilot-hunks-6cc6795e89f4/testpilot/diff.py\", line 183, in parse_unified_diff\n old_start, new_start = int(m.group(1)), int(m.group(3))\n ~~~^^^^^^^^^^^^\nValueError: Exceeds the limit (4300 digits) for integer string conversion: value has 5000 digits; use sys.set_int_max_str_digits() to increase the limit\n", + "output_exists": false + } +} diff --git a/docs/receiving/diff-hunks-6cc6795e89f4/range-v1-negative.json b/docs/receiving/diff-hunks-6cc6795e89f4/range-v1-negative.json new file mode 100644 index 0000000..73e923d --- /dev/null +++ b/docs/receiving/diff-hunks-6cc6795e89f4/range-v1-negative.json @@ -0,0 +1,21 @@ +{ + "command": [ + "/usr/bin/python3", + "-B", + "-m", + "pytest", + "-q", + "--tb=short", + "tests/test_diff_hunk_validation.py", + "-k", + "unsupported_hunk_integer" + ], + "cwd": "/home/jacob/testpilot-hunks-6cc6795e89f4", + "started": "2026-10-08T17:28:22.245370+00:00", + "finished": "2026-10-08T17:28:25.332076+00:00", + "exit": 1, + "expected_before_fix": "Four failing cases: old/new starts/counts raise plain ValueError instead of DiffFormatError", + "source_sha256": "b67f7a47f187491090e921f80d790a8eaa16684997f8ac08d504a3a1eff5659f", + "test_sha256": "522b0202242774da52fba2077d47f27e16430c641bd968505fed9310a00d73d6", + "output_sha256": "5e0b1577f230fc1aa224c25545e5bb20728525fd1b1d09b47f62f39cc31a4ed8" +} diff --git a/docs/receiving/diff-hunks-6cc6795e89f4/range-v1-negative.txt b/docs/receiving/diff-hunks-6cc6795e89f4/range-v1-negative.txt new file mode 100644 index 0000000..3edf817 --- /dev/null +++ b/docs/receiving/diff-hunks-6cc6795e89f4/range-v1-negative.txt @@ -0,0 +1,36 @@ +FFFF [100%] +=================================== FAILURES =================================== +___ test_unsupported_hunk_integer_has_clean_parser_and_run_errors[old-start] ___ +tests/test_diff_hunk_validation.py:204: in test_unsupported_hunk_integer_has_clean_parser_and_run_errors + parse_unified_diff(damaged) +testpilot/diff.py:183: in parse_unified_diff + old_start, new_start = int(m.group(1)), int(m.group(3)) + ^^^^^^^^^^^^^^^ +E ValueError: Exceeds the limit (4300 digits) for integer string conversion: value has 4301 digits; use sys.set_int_max_str_digits() to increase the limit +___ test_unsupported_hunk_integer_has_clean_parser_and_run_errors[old-count] ___ +tests/test_diff_hunk_validation.py:204: in test_unsupported_hunk_integer_has_clean_parser_and_run_errors + parse_unified_diff(damaged) +testpilot/diff.py:184: in parse_unified_diff + old_left = int(m.group(2)) if m.group(2) is not None else 1 + ^^^^^^^^^^^^^^^ +E ValueError: Exceeds the limit (4300 digits) for integer string conversion: value has 4301 digits; use sys.set_int_max_str_digits() to increase the limit +___ test_unsupported_hunk_integer_has_clean_parser_and_run_errors[new-start] ___ +tests/test_diff_hunk_validation.py:204: in test_unsupported_hunk_integer_has_clean_parser_and_run_errors + parse_unified_diff(damaged) +testpilot/diff.py:183: in parse_unified_diff + old_start, new_start = int(m.group(1)), int(m.group(3)) + ^^^^^^^^^^^^^^^ +E ValueError: Exceeds the limit (4300 digits) for integer string conversion: value has 4301 digits; use sys.set_int_max_str_digits() to increase the limit +___ test_unsupported_hunk_integer_has_clean_parser_and_run_errors[new-count] ___ +tests/test_diff_hunk_validation.py:204: in test_unsupported_hunk_integer_has_clean_parser_and_run_errors + parse_unified_diff(damaged) +testpilot/diff.py:185: in parse_unified_diff + new_left = int(m.group(4)) if m.group(4) is not None else 1 + ^^^^^^^^^^^^^^^ +E ValueError: Exceeds the limit (4300 digits) for integer string conversion: value has 4301 digits; use sys.set_int_max_str_digits() to increase the limit +=========================== short test summary info ============================ +FAILED tests/test_diff_hunk_validation.py::test_unsupported_hunk_integer_has_clean_parser_and_run_errors[old-start] +FAILED tests/test_diff_hunk_validation.py::test_unsupported_hunk_integer_has_clean_parser_and_run_errors[old-count] +FAILED tests/test_diff_hunk_validation.py::test_unsupported_hunk_integer_has_clean_parser_and_run_errors[new-start] +FAILED tests/test_diff_hunk_validation.py::test_unsupported_hunk_integer_has_clean_parser_and_run_errors[new-count] +4 failed, 24 deselected in 1.21s diff --git a/docs/receiving/git-compat-6cc6795e89f4/EXPECTATIONS.md b/docs/receiving/git-compat-6cc6795e89f4/EXPECTATIONS.md new file mode 100644 index 0000000..41485c6 --- /dev/null +++ b/docs/receiving/git-compat-6cc6795e89f4/EXPECTATIONS.md @@ -0,0 +1,13 @@ +# Independent native Git compatibility receiving + +Author: estate-6cc6795e89f4/coordination_evidence. Expectations fixed before reading or importing the repair candidate. The original eacb9907 parser and README were inspected. Candidate commit supplied by owner: 9265ae42bde5b6bd559584a11cbb7c30b7849743. + +The oracle is actual native Git output and git apply --check against its matching old fixture tree; GNU diff -u is also exercised. A Git validity check and TestPilot semantic target expectations are separate: this review does not ask TestPilot to implement git apply or verify source content. The parser must continue to accept complete valid hunks under supported git-diff/diff-u input and preserve target mapping. + +Fixed cases: multi-file and disjoint multi-hunk Python changes; zero, one, default and function context; additions, deletions and pure deletion anchors; files without a final newline; CRLF bodies; literal +++/--- body text; a UTF-8 filename with a tab; Unicode line separators inside a Python string; a binary patch mixed with Python changes; mode-only, rename-only and copy-only metadata; GNU diff -u with timestamp headers. Git mail-format envelope is an additional compatibility observation with original-parser control, separately distinguished from the documented git diff/diff-u contract. + +Expected target names come from deliberately authored fixture functions. Metadata-only changes and binary content must not invent Python changed functions. Every valid case must pass native Git apply --check (with --unidiff-zero for U0) before being counted as a valid compatibility case. All generated diff bytes, Git version, commands and raw stdout/stderr are retained. + +A malformed hunk with an impossible declared body balance must be rejected before CLI run effects. Expect exit2, a concise format-error diagnostic, no traceback and no new output directory/report. Existing output sentinel bytes must remain identical. A missing model-script path and a fixture pytest collection tripwire distinguish early diff refusal from later model or subprocess work. The negative is verified with native Git as well. Both automatic selection and an explicit current target are checked, because a context diff stays part of the request under explicit selection. + +No live services, external model, credentials, network, project source or other worker's tests will be modified. Review scripts and fixture repositories are local receiving artifacts. Do not repeat the owner's full 398-test suite. diff --git a/docs/receiving/git-compat-6cc6795e89f4/REVIEW-v1.md b/docs/receiving/git-compat-6cc6795e89f4/REVIEW-v1.md new file mode 100644 index 0000000..16ea0f3 --- /dev/null +++ b/docs/receiving/git-compat-6cc6795e89f4/REVIEW-v1.md @@ -0,0 +1,28 @@ +# Independent TestPilot receiving — frozen v1 findings + +Review worker: estate-6cc6795e89f4/coordination_evidence. +Source commit: 9265ae42bde5b6bd559584a11cbb7c30b7849743. +Source-equivalent evidence head: 628f9e3eb0a597d79e8fa9ad58fff62c9681d121. +Base: eacb9907f51aeb9360da654c60cbbd7cf99ad877. +Native Python 3.14.4; Git 2.53.0; observed 2026-10-08T17:23:33.674123+00:00. +Expectations and fixture commands were written before candidate import; pre-import-contract.json pins their bytes. Candidate source files stayed unchanged. + +## Qualified compatibility + +All 10 documented git diff / GNU diff -u cases passed. Every complete positive patch was generated by the native tool and independently passed git apply --check against its matching old fixture tree before TestPilot saw it. Candidate and original parser agree on complete file records, added/touched line sets, source spans and functions; independently authored function-name expectations also match. + +The cases cover multiple files and separated hunks, U0/U1/default/function context, additions/deletions/pure-deletion anchors, no final newline, CRLF, literal +++/--- in body text, UTF-8/tab filename quoting, Unicode string separators, binary content mixed with Python, mode-only/rename-only/copy-only metadata and GNU timestamp headers. Binary and metadata-only changes do not invent changed Python functions. + +## Retained findings + +1. An additional real git format-patch envelope passes Git apply --check and the original parser, but v1 rejects the legal signature separator `-- ` after the final completed hunk as a body overrun at record 158. This is an additional compatibility observation, separately distinguished from the documented git diff / diff -u cases above. The exact generated patch is patches/git_mail_envelope.diff. +2. A malformed appended hunk declares two old/two new body records and supplies only one of each. Git refuses it with exit 128; candidate API refuses it as an incomplete hunk. Automatic CLI run constructs/loads the ScriptedModel configuration before reaching this check: a deliberately nonexistent script directory causes exit 1 and a FileNotFoundError traceback from make_client, instead of a clean format error. This proves configuration-load/error ordering, not an external model call. With an explicit existing target, the established resolve_targets path already parses the supplied diff and produces the clean format error with exit 2. +3. Both automatic and explicit negative cases preserve all existing output sentinel bytes, create no new output/report directory, and leave the fixture pytest collection tripwire absent. No model request or subprocess test effect was observed. + +## Evidence and reproduction + +independent-receiving.json contains every raw API/CLI outcome and full comparisons. fixture-provenance.json contains native Git/diff commands and stdout/stderr, old/new fixture commits and every patch SHA256. expectations and both scripts are stored as inert .py.txt files; baseline_diff.py.txt preserves the exact original parser bytes. + +The fixture Git repositories live outside the project at /home/jacob/testpilot-git-compat-6cc6795e89f4-data. A new receiving directory can reproduce the setup by running build_fixtures.py.txt once, then receive_candidate.py.txt. The builder deliberately refuses an already-existing data root; this preserves original evidence. + +These are v1 findings. The source owner retains repair/integration scope and will decide the bounded follow-up with the integration lead. This review did not change product source, publish to GitHub, or repeat the owner's full suite. diff --git a/docs/receiving/git-compat-6cc6795e89f4/REVIEW-v2.md b/docs/receiving/git-compat-6cc6795e89f4/REVIEW-v2.md new file mode 100644 index 0000000..a36d0ac --- /dev/null +++ b/docs/receiving/git-compat-6cc6795e89f4/REVIEW-v2.md @@ -0,0 +1,9 @@ +# Independent native receiving — v2 result + +Frozen source: dece74cdcefe125bbb3b38f5f9ca4a92097b998b, received as f97e984 in the independent worktree. diff.py SHA2563995b19d6d68867efa1ef73f9f20db93f95d34f05cdc9b760e9f71d7e2caa69a; __main__.py SHA256f5e644cb4f6bb9278657939bd6dc51390a974f7f8834efb8459e11fbce4a95cd. Both stayed unchanged across this receiving run. + +The exact original patch bytes and independently frozen target expectations were reused; native Git validation from fixture-provenance.json remains the oracle. All10 documented git diff/GNU diff-u cases pass with complete original/candidate file and function mappings equal. All4 malformed CLI cases now return clean format-error exit2 before nonexistent model-script loading; new output directories remain absent, existing report/patch sentinel bytes are identical, and the pytest collection tripwire is absent. The API still rejects the native-Git-refused unfinished tail. + +The additional mail-envelope case still fails at record158. The native Git-generated patch uses --signature='HAMON Receiving Fixture'. V2's footer branch accepts only a terminal _GIT_SIGNATURE_VERSION line followed by blanks, so the legal custom signature does not match. This is an explicit residual optional compatibility observation, distinguished from the10 documented diff cases. The integration lead and source owner have the exact mismatch for scope judgment; this receipt makes no claim that arbitrary Git mail signatures are supported. + +V1 evidence is unchanged. Separate v2 artifacts are receive_candidate_v2.py.txt, pre-import-contract-v2.json and independent-receiving-v2.json. The diagnostic assertion now requires the actual 'invalid unified diff' text, which strengthens error classification without changing the previously frozen behavioral expectations. No product source was edited by the independent reviewer and no full-suite run was repeated here. diff --git a/docs/receiving/git-compat-6cc6795e89f4/REVIEW-v3.md b/docs/receiving/git-compat-6cc6795e89f4/REVIEW-v3.md new file mode 100644 index 0000000..84d76ec --- /dev/null +++ b/docs/receiving/git-compat-6cc6795e89f4/REVIEW-v3.md @@ -0,0 +1,34 @@ +# Final independent TestPilot receiving — v3 + +Frozen product source: d40d8fd054262b9aae4544d98d691568fd4e1dd3. +Exact source-equivalent integration head received: d801fe3b409ce40507df75b5d268063dab463c48, incorporating the source owner's accepted main 191cca4. +Independent worktree: /home/jacob/testpilot-git-compat-v3-6cc6795e89f4. + +Result: all 11 original positive patch cases pass, all 4 malformed CLI cases pass, malformed API refusal passes, and source hash guards pass. Native receiver exit 0 in 9.53 seconds. + +## What was received + +The original expectations, native Git/GNU diff bytes, fixture repositories, Git apply oracle and baseline parser were retained. This run changes only the candidate source identity and writes separate v3 outputs. It does not replace either v1 or v2 evidence. + +The 10 documented git diff / GNU diff -u cases cover multiple files and separated hunks; zero, one, default and function context; additions, deletions and pure-deletion anchors; no final newline; CRLF; literal +++/--- body text; a UTF-8/tab filename; Unicode separators inside a Python string; binary content beside Python; mode-only, rename-only and copy-only metadata; and GNU timestamp headers. The original parser and v3 agree on every file record, line set, function, source span and independently authored target-name expectation. + +The additional native git format-patch case with the plain custom signature 'HAMON Receiving Fixture' now passes with the same exact bytes that failed in v1 and v2. The receiver's coverage is this complete frozen fixture set; the source owner's additional footer ambiguity and integer-boundary tests remain separately attributable. + +The malformed unfinished-tail patch is still refused by the API and by native Git. Automatic and explicit CLI modes both return a clean format diagnostic with exit 2, before attempting to load the deliberately missing model script. Existing report/patch sentinel bytes remain identical, new output directories remain absent and the pytest collection tripwire is absent. + +## Original observations across revisions + +| Receiving revision | Documented positive cases | Original custom-mail case | Automatic malformed CLI | Explicit malformed CLI | +| --- | --- | --- | --- | --- | +| v1 | 10/10 | Refused | 0/2 clean format refusals | 2/2 | +| v2 | 10/10 | Refused | 2/2 | 2/2 | +| v3 | 10/10 | Passed | 2/2 | 2/2 | + +## Source and evidence + +diff.py SHA256: 586f5b981d2c8d66e47cd8c5566ace994b152cde87f43db9b6e1c02ac5337490 +__main__.py SHA256: 32bb84da429e297e82aa9121e54764e615eadfd9ea8400b7f0ce804560d7cf16 + +Both hashes were checked before candidate import and after receiving, with no intervening source change. independent-receiving-v3.json contains full raw API/CLI observations and comparisons. pre-import-contract-v3.json binds the original expectations, builder, receiver and fixture provenance. receive_candidate_v3.py.txt is inert source for reproducing this receiving step. + +No product source was edited by this reviewer, no source publication was attempted and the owner's full suite was not repeated here. The source owner can receive this evidence commit into the final integration branch alongside its own targeted and full-suite evidence. diff --git a/docs/receiving/git-compat-6cc6795e89f4/baseline_diff.py.txt b/docs/receiving/git-compat-6cc6795e89f4/baseline_diff.py.txt new file mode 100644 index 0000000..29dd325 --- /dev/null +++ b/docs/receiving/git-compat-6cc6795e89f4/baseline_diff.py.txt @@ -0,0 +1,258 @@ +"""Parse a unified diff and map changed lines onto Python functions (via ``ast``). + +Only the *new* side of the diff matters: TestPilot writes tests for the code as +it exists after the change, so line numbers refer to the post-change file in the +working tree. +""" +from __future__ import annotations + +import ast +import os +import re +import tokenize +from dataclasses import asdict, dataclass, field +from pathlib import Path, PurePosixPath + +_HUNK = re.compile(r"^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@") + + +@dataclass +class FileChange: + """Changed lines for one file, in new-file line numbers.""" + + path: str + old_path: str | None + added_lines: set[int] = field(default_factory=set) + # added lines plus anchor lines around pure deletions + touched_lines: set[int] = field(default_factory=set) + is_new: bool = False + is_deleted: bool = False + + +@dataclass +class ChangedFunction: + path: str + module: str + qualname: str + lineno: int + end_lineno: int + changed_lines: list[int] + source: str + is_method: bool + + @property + def import_name(self) -> str: + """Top-level name to import from ``module`` (class name for methods).""" + return self.qualname.split(".")[0] + + def to_dict(self) -> dict: + return asdict(self) + + +def _unquote_git_path(raw: str) -> str: + """Decode Git's C-quoted filename bytes, including octal UTF-8 sequences.""" + escapes = {"a": 7, "b": 8, "t": 9, "n": 10, "v": 11, "f": 12, "r": 13, + '"': 34, "\\": 92} + decoded = bytearray() + index = 1 # Opening double quote. + while index < len(raw): + char = raw[index] + index += 1 + if char == '"': + if index != len(raw) or 0 in decoded: + raise ValueError("invalid Git-quoted diff path") + return os.fsdecode(bytes(decoded)) + if char != "\\": + decoded.extend(os.fsencode(char)) + continue + if index >= len(raw): + break + escaped = raw[index] + index += 1 + if escaped in escapes: + decoded.append(escapes[escaped]) + elif (escaped in "0123" and index + 1 < len(raw) + and all(digit in "01234567" for digit in raw[index:index + 2])): + decoded.append(int(escaped + raw[index:index + 2], 8)) + index += 2 + else: + raise ValueError("invalid escape in Git-quoted diff path") + raise ValueError("unterminated Git-quoted diff path") + + +def _strip_path(raw: str) -> str | None: + p = raw.split("\t")[0].strip() + if p.startswith('"'): + p = _unquote_git_path(p) + if p == "/dev/null": + return None + if p.startswith(("a/", "b/")): + p = p[2:] + return p + + +def parse_unified_diff(text: str) -> list[FileChange]: + """Parse ``git diff`` / ``diff -u`` output into per-file changed line sets. + + Hunk line counters are tracked so that removed lines whose content starts + with ``-- `` are not mistaken for file headers. + """ + files: list[FileChange] = [] + cur: FileChange | None = None + pending_old: str | None = None + old_left = new_left = 0 + new_ln = 0 + # Git hunk counts use LF-delimited records, not Unicode separators. + for line in text.split("\n"): + if cur is not None and (old_left > 0 or new_left > 0): + if line.startswith("\\"): # "\ No newline at end of file" + continue + if line.startswith("+"): + cur.added_lines.add(new_ln) + cur.touched_lines.add(new_ln) + new_ln += 1 + new_left -= 1 + elif line.startswith("-"): + # anchor a pure deletion to the surrounding new-file lines + for anchor in (new_ln - 1, new_ln): + if anchor >= 1: + cur.touched_lines.add(anchor) + old_left -= 1 + else: # context line (" " prefix, or blank if whitespace was stripped) + new_ln += 1 + new_left -= 1 + old_left -= 1 + continue + if line.startswith("diff --git "): + cur, pending_old = None, None + continue + if line.startswith("--- "): + pending_old = _strip_path(line[4:]) + continue + if line.startswith("+++ "): + new_path = _strip_path(line[4:]) + cur = FileChange( + path=new_path or pending_old or "", + old_path=pending_old, + is_new=pending_old is None, + is_deleted=new_path is None, + ) + files.append(cur) + continue + m = _HUNK.match(line) + if m and cur is not None: + old_left = int(m.group(2)) if m.group(2) is not None else 1 + new_left = int(m.group(4)) if m.group(4) is not None else 1 + new_ln = int(m.group(3)) + if new_left == 0: # pure deletion hunk: "+N,0" means "after line N" + new_ln += 1 + return files + + +def is_test_path(path: str) -> bool: + p = PurePosixPath(path) + return ( + "tests" in p.parts + or "test" in p.parts + or p.name.startswith("test_") + or p.name.endswith("_test.py") + or p.name == "conftest.py" + ) + + +def module_name(path: str) -> str: + parts = list(PurePosixPath(path).with_suffix("").parts) + if parts and parts[0] == "src": + parts = parts[1:] + if parts and parts[-1] == "__init__": + parts = parts[:-1] + return ".".join(parts) + + +def _iter_functions(body, prefix: str = "", in_class: bool = False): + """Yield (qualname, node, is_method) for top-level functions and methods. + + Nested functions are attributed to their enclosing function. Functions + defined under ``if``/``try``/``with``/``match`` blocks at module/class level count. + """ + for node in body: + if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)): + yield prefix + node.name, node, in_class + elif isinstance(node, ast.ClassDef): + yield from _iter_functions(node.body, prefix + node.name + ".", True) + elif isinstance(node, ast.Match): + for case in node.cases: + yield from _iter_functions(case.body, prefix, in_class) + else: + for attr in ("body", "orelse", "finalbody"): + sub = getattr(node, attr, None) + if isinstance(sub, list): + yield from _iter_functions(sub, prefix, in_class) + for handler in getattr(node, "handlers", None) or []: + yield from _iter_functions(handler.body, prefix, in_class) + + +def functions_touching(source: str, path: str, lines: set[int], added: set[int]) -> list[ChangedFunction]: + tree = ast.parse(source, filename=path) + # Python physical lines use LF/CRLF/CR; other separators can be literals. + src_lines = source.replace("\r\n", "\n").replace("\r", "\n").split("\n") + out: list[ChangedFunction] = [] + for qualname, node, is_method in _iter_functions(tree.body): + start = min([node.lineno] + [d.lineno for d in node.decorator_list]) + end = node.end_lineno or node.lineno + if not any(start <= ln <= end for ln in lines): + continue + out.append( + ChangedFunction( + path=path, + module=module_name(path), + qualname=qualname, + lineno=start, + end_lineno=end, + changed_lines=sorted(ln for ln in added if start <= ln <= end), + source="\n".join(src_lines[start - 1 : end]), + is_method=is_method, + ) + ) + return out + + +class DiffSourceError(ValueError): + """A diff selects a source path outside the requested repository.""" + + +def _source_path(repo: Path, raw: str) -> Path: + """Resolve a relative diff path without admitting an outside source file.""" + relative = Path(raw) + if relative.anchor or ".." in relative.parts: + raise DiffSourceError(f"diff source must be repository-relative: {raw!r}") + try: + resolved = (repo / relative).resolve() + except (OSError, RuntimeError, ValueError) as exc: + raise DiffSourceError(f"cannot resolve diff source: {raw!r}") from exc + if not resolved.is_relative_to(repo): + raise DiffSourceError(f"diff source resolves outside repository: {raw!r}") + return resolved + + +def changed_functions(repo: str | Path, diff_text: str, include_tests: bool = False) -> list[ChangedFunction]: + """Return touched Python functions, rejecting selected sources outside ``repo``. + + Paths must be relative without parent traversal. Symlinks are supported + when their resolved destination remains inside the resolved repository. + Source encoding follows Python's BOM and encoding-cookie rules. + """ + repo = Path(repo).resolve() + result: list[ChangedFunction] = [] + for fc in parse_unified_diff(diff_text): + if fc.is_deleted or not fc.path.endswith(".py"): + continue + if not include_tests and is_test_path(fc.path): + continue + f = _source_path(repo, fc.path) + if not f.is_file(): + continue + with tokenize.open(f) as source_file: + source = source_file.read() + result.extend(functions_touching(source, fc.path, fc.touched_lines, fc.added_lines)) + return result diff --git a/docs/receiving/git-compat-6cc6795e89f4/build_fixtures.py.txt b/docs/receiving/git-compat-6cc6795e89f4/build_fixtures.py.txt new file mode 100644 index 0000000..17fe0d4 --- /dev/null +++ b/docs/receiving/git-compat-6cc6795e89f4/build_fixtures.py.txt @@ -0,0 +1,131 @@ +from pathlib import Path +from datetime import datetime, timezone +import hashlib, json, os, shutil, subprocess, sys +HERE = Path(__file__).resolve().parent +DATA = Path("/home/jacob/testpilot-git-compat-6cc6795e89f4-data") +DATA.mkdir(exist_ok=False) +REPO = DATA / "fixture" +REPO.mkdir() +commands = [] +def run(argv, cwd, ok=(0,)): + p = subprocess.run(argv, cwd=cwd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + commands.append({"argv":list(map(str, argv)), "cwd":str(cwd), "returncode":p.returncode, + "stdout":p.stdout.decode("utf-8", "backslashreplace"), + "stderr":p.stderr.decode("utf-8", "backslashreplace")}) + if p.returncode not in ok: + raise RuntimeError(commands[-1]) + return p.stdout +def git(*args): + return run(["git", "-c", "core.autocrlf=false", "-c", "core.fileMode=true", *args], REPO) +odd = "odd\t\u00e4.py" +old = { + "alpha.py": ("def first():\n return 1\n\n" + "".join("# gap %02d\n" % n for n in range(14)) + + "\ndef second():\n return 3\n\ndef unchanged():\n return 99\n").encode(), + "beta.py": b"def beta():\n return 10", + "crlf.py": b"def crlf_fn():\r\n return 20\r\n", + "payload.py": b'def payload():\n return """old\n--- old literal\n+++ old literal\n"""\n', + "unicode.py": 'def unicode_fn():\n return "old\u2028tail\u2029end"\n'.encode(), + odd: b"def odd_fn():\n return 40\n", + "drop.py": b"def removedline():\n keep = 1\n drop = 2\n return keep\n", + "insert.py": b"def insertedline():\n return 51\n", + "deleted.py": b"def obsolete_unique():\n return 'deleted-old-constant'\n", + "mode.py": b"def mode_untouched():\n return 624\n", + "rename_before.py": b"def renamed_untouched():\n return 725\n", + "copy_from.py": b"def copied_untouched():\n return 826\n", + "blob.bin": bytes(range(256)) * 4, +} +for rel, data in old.items(): + (REPO / rel).write_bytes(data) +git("init", "--quiet") +git("add", "-A") +git("-c", "user.name=HAMON Receiving Fixture", "-c", "user.email=fixture@localhost", "commit", "--quiet", "-m", "fixture before") +old_commit = git("rev-parse", "HEAD").decode().strip() +new = dict(old) +new["alpha.py"] = old["alpha.py"].replace(b"return 1\n", b"return 2\n").replace(b"return 3\n", b"return 4\n") +new["beta.py"] = old["beta.py"].replace(b"10", b"11") +new["crlf.py"] = old["crlf.py"].replace(b"20", b"21") +new["payload.py"] = old["payload.py"].replace(b"old", b"new") +new["unicode.py"] = old["unicode.py"].replace(b"old", b"new") +new[odd] = old[odd].replace(b"40", b"41") +new["drop.py"] = old["drop.py"].replace(b" drop = 2\n", b"") +new["insert.py"] = old["insert.py"].replace(b" return", b" inserted = 50\n return") +new["new.py"] = b"def gamma_unique():\n return 'new-post-change-constant'\n" +del new["deleted.py"] +new["rename_after.py"] = new.pop("rename_before.py") +new["copy_to.py"] = new["copy_from.py"] +new["blob.bin"] = bytes(reversed(range(256))) * 4 +for rel in old.keys() - new.keys(): + (REPO / rel).unlink() +for rel, data in new.items(): + (REPO / rel).write_bytes(data) +(REPO / "mode.py").chmod(0o755) +git("add", "-A") +git("-c", "user.name=HAMON Receiving Fixture", "-c", "user.email=fixture@localhost", "commit", "--quiet", "-m", "fixture after") +new_commit = git("rev-parse", "HEAD").decode().strip() +ORACLE = DATA / "oracle-before" +git("worktree", "add", "--quiet", "--detach", str(ORACLE), old_commit) +(PATCHES := HERE / "patches").mkdir() +cases = [] +full_targets = sorted([["alpha.py", "first"], ["alpha.py", "second"], ["beta.py", "beta"], + ["crlf.py", "crlf_fn"], ["payload.py", "payload"], ["unicode.py", "unicode_fn"], + [odd, "odd_fn"], ["drop.py", "removedline"], ["insert.py", "insertedline"], + ["new.py", "gamma_unique"]]) +def add_case(name, patch, expected, zero=False, optional=False, require=()): + for marker in require: + assert marker in patch, (name, marker) + path = PATCHES / (name + ".diff") + path.write_bytes(patch) + argv = ["git", "-c", "core.autocrlf=false", "apply", "--check"] + if zero: + argv.append("--unidiff-zero") + argv.append(str(path)) + run(argv, ORACLE) + cases.append({"name":name, "patch":str(path), "sha256":hashlib.sha256(patch).hexdigest(), + "expected_targets":sorted(expected), "git_valid":True, "optional_envelope":optional}) +def diff(extra=(), paths=()): + args = ["diff", "--no-ext-diff", "--binary", "-M", "-C", "--find-copies-harder", *extra, old_commit, new_commit] + if paths: args += ["--", *paths] + return git(*args) +for name, extra, zero in [("git_default", (), False), ("git_zero", ("-U0",), True), + ("git_one", ("-U1",), False), ("git_function", ("-W",), False)]: + add_case(name, diff(extra), full_targets, zero=zero, + require=(b"GIT binary patch", b"rename from ", b"copy from ", b"old mode ")) +add_case("git_binary_python", diff(paths=("alpha.py", "blob.bin")), [["alpha.py","first"],["alpha.py","second"]], + require=(b"GIT binary patch",)) +add_case("git_metadata_only", diff(paths=("mode.py","rename_before.py","rename_after.py","copy_from.py","copy_to.py")), [], + require=(b"rename from ",b"copy from ",b"old mode ")) +for name, path, qual in [("git_no_newline", "beta.py", "beta"), ("git_crlf","crlf.py","crlf_fn"), + ("git_quoted_path", odd, "odd_fn")]: + add_case(name, diff(paths=(path,)), [[path,qual]], + require=((b"\\ No newline at end of file",) if name=="git_no_newline" else ())) +mail = git("format-patch", "--stdout", "--binary", "-M", "-C", "--find-copies-harder", "--subject-prefix=PATCH", "--signature=HAMON Receiving Fixture", "-1", new_commit) +add_case("git_mail_envelope", mail, full_targets, optional=True, require=(b"Subject: [PATCH]", b"\n-- \n")) +GNU = DATA / "gnu" +(GNU / "a").mkdir(parents=True) +(GNU / "b").mkdir() +(GNU / "a/alpha.py").write_bytes(old["alpha.py"]) +(GNU / "b/alpha.py").write_bytes(new["alpha.py"]) +gnu = run(["diff", "-u", "a/alpha.py", "b/alpha.py"], GNU, ok=(1,)) +assert b"--- a/alpha.py\t" in gnu and b"+++ b/alpha.py\t" in gnu +add_case("gnu_diff_u_timestamps", gnu, [["alpha.py","first"],["alpha.py","second"]]) +malformed = diff(("-U0",), ("alpha.py",)) + b"@@ -900,2 +900,2 @@\n-ghost\n+ghost\n" +bad = PATCHES / "malformed_unfinished_tail.diff" +bad.write_bytes(malformed) +rejected = subprocess.run(["git","apply","--check","--unidiff-zero",str(bad)],cwd=ORACLE,capture_output=True) +commands.append({"argv":["git","apply","--check","--unidiff-zero",str(bad)], "cwd":str(ORACLE), + "returncode":rejected.returncode,"stdout":rejected.stdout.decode(),"stderr":rejected.stderr.decode()}) +assert rejected.returncode != 0, "negative must be refused by actual Git" +baseline = git("--git-dir=/home/jacob/testpilot-hunks-6cc6795e89f4.git", "show", + "eacb9907f51aeb9360da654c60cbbd7cf99ad877:testpilot/diff.py") +(HERE / "baseline_diff.py.txt").write_bytes(baseline) +tripwire = DATA / "pytest-tripwire" +(REPO / "conftest.py").write_text("from pathlib import Path\nPath(" + repr(str(tripwire)) + ").write_text('pytest started')\n", encoding="utf-8") +record = {"frozen_at":datetime.now(timezone.utc).isoformat(), "candidate_source_commit":"9265ae42bde5b6bd559584a11cbb7c30b7849743", + "candidate_evidence_head":"628f9e3eb0a597d79e8fa9ad58fff62c9681d121", + "old_fixture_commit":old_commit,"new_fixture_commit":new_commit, "repo":str(REPO), + "oracle":str(ORACLE), "tripwire":str(tripwire), "malformed":str(bad), "cases":cases, + "baseline_sha256":hashlib.sha256(baseline).hexdigest(), "commands":commands} +(HERE / "fixture-provenance.json").write_text(json.dumps(record,indent=2,sort_keys=True,ensure_ascii=True)+"\n",encoding="utf-8") +print(json.dumps({"case_count":len(cases), "git_version":git("--version").decode().strip(), + "all_positive_git_apply_checks_passed":True, "negative_git_returncode":rejected.returncode, + "fixture_provenance":str(HERE / "fixture-provenance.json")})) diff --git a/docs/receiving/git-compat-6cc6795e89f4/fixture-provenance.json b/docs/receiving/git-compat-6cc6795e89f4/fixture-provenance.json new file mode 100644 index 0000000..b066b2c --- /dev/null +++ b/docs/receiving/git-compat-6cc6795e89f4/fixture-provenance.json @@ -0,0 +1,928 @@ +{ + "baseline_sha256": "2cf38707ea175bb1f04d55335de344ae943458658055332b4aa68f27763214c0", + "candidate_evidence_head": "628f9e3eb0a597d79e8fa9ad58fff62c9681d121", + "candidate_source_commit": "9265ae42bde5b6bd559584a11cbb7c30b7849743", + "cases": [ + { + "expected_targets": [ + [ + "alpha.py", + "first" + ], + [ + "alpha.py", + "second" + ], + [ + "beta.py", + "beta" + ], + [ + "crlf.py", + "crlf_fn" + ], + [ + "drop.py", + "removedline" + ], + [ + "insert.py", + "insertedline" + ], + [ + "new.py", + "gamma_unique" + ], + [ + "odd\t\u00e4.py", + "odd_fn" + ], + [ + "payload.py", + "payload" + ], + [ + "unicode.py", + "unicode_fn" + ] + ], + "git_valid": true, + "name": "git_default", + "optional_envelope": false, + "patch": "/home/jacob/testpilot-git-compat-6cc6795e89f4/docs/receiving/git-compat-6cc6795e89f4/patches/git_default.diff", + "sha256": "e63cd0f0a8fc9f08aece050dfc5a92d5e19dac4b982f4c7787bf4f474572db0f" + }, + { + "expected_targets": [ + [ + "alpha.py", + "first" + ], + [ + "alpha.py", + "second" + ], + [ + "beta.py", + "beta" + ], + [ + "crlf.py", + "crlf_fn" + ], + [ + "drop.py", + "removedline" + ], + [ + "insert.py", + "insertedline" + ], + [ + "new.py", + "gamma_unique" + ], + [ + "odd\t\u00e4.py", + "odd_fn" + ], + [ + "payload.py", + "payload" + ], + [ + "unicode.py", + "unicode_fn" + ] + ], + "git_valid": true, + "name": "git_zero", + "optional_envelope": false, + "patch": "/home/jacob/testpilot-git-compat-6cc6795e89f4/docs/receiving/git-compat-6cc6795e89f4/patches/git_zero.diff", + "sha256": "0107f3f423d6f8e4150587ae8f649f8fb146176d01ddca27e37fbbd2b57e02d9" + }, + { + "expected_targets": [ + [ + "alpha.py", + "first" + ], + [ + "alpha.py", + "second" + ], + [ + "beta.py", + "beta" + ], + [ + "crlf.py", + "crlf_fn" + ], + [ + "drop.py", + "removedline" + ], + [ + "insert.py", + "insertedline" + ], + [ + "new.py", + "gamma_unique" + ], + [ + "odd\t\u00e4.py", + "odd_fn" + ], + [ + "payload.py", + "payload" + ], + [ + "unicode.py", + "unicode_fn" + ] + ], + "git_valid": true, + "name": "git_one", + "optional_envelope": false, + "patch": "/home/jacob/testpilot-git-compat-6cc6795e89f4/docs/receiving/git-compat-6cc6795e89f4/patches/git_one.diff", + "sha256": "5cf9bc987e7021c0002aa89e22fc1af01dc42f3628b87745880c6aff839f9070" + }, + { + "expected_targets": [ + [ + "alpha.py", + "first" + ], + [ + "alpha.py", + "second" + ], + [ + "beta.py", + "beta" + ], + [ + "crlf.py", + "crlf_fn" + ], + [ + "drop.py", + "removedline" + ], + [ + "insert.py", + "insertedline" + ], + [ + "new.py", + "gamma_unique" + ], + [ + "odd\t\u00e4.py", + "odd_fn" + ], + [ + "payload.py", + "payload" + ], + [ + "unicode.py", + "unicode_fn" + ] + ], + "git_valid": true, + "name": "git_function", + "optional_envelope": false, + "patch": "/home/jacob/testpilot-git-compat-6cc6795e89f4/docs/receiving/git-compat-6cc6795e89f4/patches/git_function.diff", + "sha256": "9fc48c9a310a702951d64df8ff928fd2135b69373fa25bf8a0ab12c6788ab177" + }, + { + "expected_targets": [ + [ + "alpha.py", + "first" + ], + [ + "alpha.py", + "second" + ] + ], + "git_valid": true, + "name": "git_binary_python", + "optional_envelope": false, + "patch": "/home/jacob/testpilot-git-compat-6cc6795e89f4/docs/receiving/git-compat-6cc6795e89f4/patches/git_binary_python.diff", + "sha256": "0579a99651579908d9153bc12a0661e8b5081438055bdf3d5bab61a9cb787335" + }, + { + "expected_targets": [], + "git_valid": true, + "name": "git_metadata_only", + "optional_envelope": false, + "patch": "/home/jacob/testpilot-git-compat-6cc6795e89f4/docs/receiving/git-compat-6cc6795e89f4/patches/git_metadata_only.diff", + "sha256": "5fc98cf71c98a4b1dab24b32c06820ea5b27e4088559e3adea93b0c474eba286" + }, + { + "expected_targets": [ + [ + "beta.py", + "beta" + ] + ], + "git_valid": true, + "name": "git_no_newline", + "optional_envelope": false, + "patch": "/home/jacob/testpilot-git-compat-6cc6795e89f4/docs/receiving/git-compat-6cc6795e89f4/patches/git_no_newline.diff", + "sha256": "3ba147b42f25628fc5ac2ec4f4cb73cae632127a75c8c009049ace99477210be" + }, + { + "expected_targets": [ + [ + "crlf.py", + "crlf_fn" + ] + ], + "git_valid": true, + "name": "git_crlf", + "optional_envelope": false, + "patch": "/home/jacob/testpilot-git-compat-6cc6795e89f4/docs/receiving/git-compat-6cc6795e89f4/patches/git_crlf.diff", + "sha256": "672b210a94fb9319e86d29d91027d732011c0cb34986539007d3012a4aa76ab6" + }, + { + "expected_targets": [ + [ + "odd\t\u00e4.py", + "odd_fn" + ] + ], + "git_valid": true, + "name": "git_quoted_path", + "optional_envelope": false, + "patch": "/home/jacob/testpilot-git-compat-6cc6795e89f4/docs/receiving/git-compat-6cc6795e89f4/patches/git_quoted_path.diff", + "sha256": "369169bf2ce73a58e47ff3af1e352a9a450f65fd8985ea1be9d74dde0ad859a9" + }, + { + "expected_targets": [ + [ + "alpha.py", + "first" + ], + [ + "alpha.py", + "second" + ], + [ + "beta.py", + "beta" + ], + [ + "crlf.py", + "crlf_fn" + ], + [ + "drop.py", + "removedline" + ], + [ + "insert.py", + "insertedline" + ], + [ + "new.py", + "gamma_unique" + ], + [ + "odd\t\u00e4.py", + "odd_fn" + ], + [ + "payload.py", + "payload" + ], + [ + "unicode.py", + "unicode_fn" + ] + ], + "git_valid": true, + "name": "git_mail_envelope", + "optional_envelope": true, + "patch": "/home/jacob/testpilot-git-compat-6cc6795e89f4/docs/receiving/git-compat-6cc6795e89f4/patches/git_mail_envelope.diff", + "sha256": "c997fa37a532088118d9caee74cad1a589ce0b0948c9653b733cbde7a3a2c21e" + }, + { + "expected_targets": [ + [ + "alpha.py", + "first" + ], + [ + "alpha.py", + "second" + ] + ], + "git_valid": true, + "name": "gnu_diff_u_timestamps", + "optional_envelope": false, + "patch": "/home/jacob/testpilot-git-compat-6cc6795e89f4/docs/receiving/git-compat-6cc6795e89f4/patches/gnu_diff_u_timestamps.diff", + "sha256": "90735a0300b765bbf11cb51beca006a26dfcb71f2b5e4c00be8ac262d2742066" + } + ], + "commands": [ + { + "argv": [ + "git", + "-c", + "core.autocrlf=false", + "-c", + "core.fileMode=true", + "init", + "--quiet" + ], + "cwd": "/home/jacob/testpilot-git-compat-6cc6795e89f4-data/fixture", + "returncode": 0, + "stderr": "", + "stdout": "" + }, + { + "argv": [ + "git", + "-c", + "core.autocrlf=false", + "-c", + "core.fileMode=true", + "add", + "-A" + ], + "cwd": "/home/jacob/testpilot-git-compat-6cc6795e89f4-data/fixture", + "returncode": 0, + "stderr": "", + "stdout": "" + }, + { + "argv": [ + "git", + "-c", + "core.autocrlf=false", + "-c", + "core.fileMode=true", + "-c", + "user.name=HAMON Receiving Fixture", + "-c", + "user.email=fixture@localhost", + "commit", + "--quiet", + "-m", + "fixture before" + ], + "cwd": "/home/jacob/testpilot-git-compat-6cc6795e89f4-data/fixture", + "returncode": 0, + "stderr": "", + "stdout": "" + }, + { + "argv": [ + "git", + "-c", + "core.autocrlf=false", + "-c", + "core.fileMode=true", + "rev-parse", + "HEAD" + ], + "cwd": "/home/jacob/testpilot-git-compat-6cc6795e89f4-data/fixture", + "returncode": 0, + "stderr": "", + "stdout": "5337d8707ac0ab30f2b7a9e75df84ca11a5f26e0\n" + }, + { + "argv": [ + "git", + "-c", + "core.autocrlf=false", + "-c", + "core.fileMode=true", + "add", + "-A" + ], + "cwd": "/home/jacob/testpilot-git-compat-6cc6795e89f4-data/fixture", + "returncode": 0, + "stderr": "", + "stdout": "" + }, + { + "argv": [ + "git", + "-c", + "core.autocrlf=false", + "-c", + "core.fileMode=true", + "-c", + "user.name=HAMON Receiving Fixture", + "-c", + "user.email=fixture@localhost", + "commit", + "--quiet", + "-m", + "fixture after" + ], + "cwd": "/home/jacob/testpilot-git-compat-6cc6795e89f4-data/fixture", + "returncode": 0, + "stderr": "", + "stdout": "" + }, + { + "argv": [ + "git", + "-c", + "core.autocrlf=false", + "-c", + "core.fileMode=true", + "rev-parse", + "HEAD" + ], + "cwd": "/home/jacob/testpilot-git-compat-6cc6795e89f4-data/fixture", + "returncode": 0, + "stderr": "", + "stdout": "b9c650347018c5dc7f4bd22a7779c19606fac02b\n" + }, + { + "argv": [ + "git", + "-c", + "core.autocrlf=false", + "-c", + "core.fileMode=true", + "worktree", + "add", + "--quiet", + "--detach", + "/home/jacob/testpilot-git-compat-6cc6795e89f4-data/oracle-before", + "5337d8707ac0ab30f2b7a9e75df84ca11a5f26e0" + ], + "cwd": "/home/jacob/testpilot-git-compat-6cc6795e89f4-data/fixture", + "returncode": 0, + "stderr": "", + "stdout": "" + }, + { + "argv": [ + "git", + "-c", + "core.autocrlf=false", + "-c", + "core.fileMode=true", + "diff", + "--no-ext-diff", + "--binary", + "-M", + "-C", + "--find-copies-harder", + "5337d8707ac0ab30f2b7a9e75df84ca11a5f26e0", + "b9c650347018c5dc7f4bd22a7779c19606fac02b" + ], + "cwd": "/home/jacob/testpilot-git-compat-6cc6795e89f4-data/fixture", + "returncode": 0, + "stderr": "", + "stdout": "diff --git a/alpha.py b/alpha.py\nindex c146194..d9f6196 100644\n--- a/alpha.py\n+++ b/alpha.py\n@@ -1,5 +1,5 @@\n def first():\n- return 1\n+ return 2\n \n # gap 00\n # gap 01\n@@ -17,7 +17,7 @@ def first():\n # gap 13\n \n def second():\n- return 3\n+ return 4\n \n def unchanged():\n return 99\ndiff --git a/beta.py b/beta.py\nindex 602f556..92177fc 100644\n--- a/beta.py\n+++ b/beta.py\n@@ -1,2 +1,2 @@\n def beta():\n- return 10\n\\ No newline at end of file\n+ return 11\n\\ No newline at end of file\ndiff --git a/blob.bin b/blob.bin\nindex c8b49c8cd518e58491924bfc364ff26e01a85009..991c98a05128d446b067c2ae39bd7dff0a11686a 100644\nGIT binary patch\nliteral 1024\nzcmezW@9&@AzkdGs{_X3R&!0Yic>nJ0o7b;izIguZ>66Eg9zM8#@9v%3w{G6Je(ma&\nz%a<-*IDhW!nbW6Eo;ZH&=#j&R4j$OQZ||PnyLRr_zHRH4&6_rESif%Vn$@dTu2{Zo\nz>5|2Z7A}}SZ|F(<6Xm4w6X>Mw4sIRN7sjjN5C@(85\nzDK083$j{5o$mNlr>kh>weniH?el2oDPl2@VPj@b~le@%HlcaCdWcadvWa\nzu(z|dv9_|bFgG(bF*Y(Z(AU$|(bm$`P*+n`QC3n^ke8E{k(QE_5El~_5f%~@;OFDz\na;pXDxU}s}xVP;}v_&@6Z(fA+1=^p@?&i)Vp\n\nliteral 1024\nzcmZQzWMXDvWn<^yMC+6cQE@6%&_`l#-T_m6KOcR8m$^Ra4i{)Y8_`)zddH\nzG%_|ZH8Z!cw6eCbwX=6{baHlab#wRd^z!!c_45x13RUz\nzF>}`JIdkXDU$Ah|;w4L$Enl&6)#^2C*R9{Mant54TeofBv2)k%J$v`nJ0o7b;izIguZ>66Eg9zM8#@9v%3w{G6Je(ma&\nz%a<-*IDhW!nbW6Eo;ZH&=#j&R4j$OQZ||PnyLRr_zHRH4&6_rESif%Vn$@dTu2{Zo\nz>5|2Z7A}}SZ|F(<6Xm4w6X>Mw4sIRN7sjjN5C@(85\nzDK083$j{5o$mNlr>kh>weniH?el2oDPl2@VPj@b~le@%HlcaCdWcadvWa\nzu(z|dv9_|bFgG(bF*Y(Z(AU$|(bm$`P*+n`QC3n^ke8E{k(QE_5El~_5f%~@;OFDz\na;pXDxU}s}xVP;}v_&@6Z(fA+1=^p@?&i)Vp\n\nliteral 1024\nzcmZQzWMXDvWn<^yMC+6cQE@6%&_`l#-T_m6KOcR8m$^Ra4i{)Y8_`)zddH\nzG%_|ZH8Z!cw6eCbwX=6{baHlab#wRd^z!!c_45x13RUz\nzF>}`JIdkXDU$Ah|;w4L$Enl&6)#^2C*R9{Mant54TeofBv2)k%J$v`nJ0o7b;izIguZ>66Eg9zM8#@9v%3w{G6Je(ma&\nz%a<-*IDhW!nbW6Eo;ZH&=#j&R4j$OQZ||PnyLRr_zHRH4&6_rESif%Vn$@dTu2{Zo\nz>5|2Z7A}}SZ|F(<6Xm4w6X>Mw4sIRN7sjjN5C@(85\nzDK083$j{5o$mNlr>kh>weniH?el2oDPl2@VPj@b~le@%HlcaCdWcadvWa\nzu(z|dv9_|bFgG(bF*Y(Z(AU$|(bm$`P*+n`QC3n^ke8E{k(QE_5El~_5f%~@;OFDz\na;pXDxU}s}xVP;}v_&@6Z(fA+1=^p@?&i)Vp\n\nliteral 1024\nzcmZQzWMXDvWn<^yMC+6cQE@6%&_`l#-T_m6KOcR8m$^Ra4i{)Y8_`)zddH\nzG%_|ZH8Z!cw6eCbwX=6{baHlab#wRd^z!!c_45x13RUz\nzF>}`JIdkXDU$Ah|;w4L$Enl&6)#^2C*R9{Mant54TeofBv2)k%J$v`nJ0o7b;izIguZ>66Eg9zM8#@9v%3w{G6Je(ma&\nz%a<-*IDhW!nbW6Eo;ZH&=#j&R4j$OQZ||PnyLRr_zHRH4&6_rESif%Vn$@dTu2{Zo\nz>5|2Z7A}}SZ|F(<6Xm4w6X>Mw4sIRN7sjjN5C@(85\nzDK083$j{5o$mNlr>kh>weniH?el2oDPl2@VPj@b~le@%HlcaCdWcadvWa\nzu(z|dv9_|bFgG(bF*Y(Z(AU$|(bm$`P*+n`QC3n^ke8E{k(QE_5El~_5f%~@;OFDz\na;pXDxU}s}xVP;}v_&@6Z(fA+1=^p@?&i)Vp\n\nliteral 1024\nzcmZQzWMXDvWn<^yMC+6cQE@6%&_`l#-T_m6KOcR8m$^Ra4i{)Y8_`)zddH\nzG%_|ZH8Z!cw6eCbwX=6{baHlab#wRd^z!!c_45x13RUz\nzF>}`JIdkXDU$Ah|;w4L$Enl&6)#^2C*R9{Mant54TeofBv2)k%J$v`nJ0o7b;izIguZ>66Eg9zM8#@9v%3w{G6Je(ma&\nz%a<-*IDhW!nbW6Eo;ZH&=#j&R4j$OQZ||PnyLRr_zHRH4&6_rESif%Vn$@dTu2{Zo\nz>5|2Z7A}}SZ|F(<6Xm4w6X>Mw4sIRN7sjjN5C@(85\nzDK083$j{5o$mNlr>kh>weniH?el2oDPl2@VPj@b~le@%HlcaCdWcadvWa\nzu(z|dv9_|bFgG(bF*Y(Z(AU$|(bm$`P*+n`QC3n^ke8E{k(QE_5El~_5f%~@;OFDz\na;pXDxU}s}xVP;}v_&@6Z(fA+1=^p@?&i)Vp\n\nliteral 1024\nzcmZQzWMXDvWn<^yMC+6cQE@6%&_`l#-T_m6KOcR8m$^Ra4i{)Y8_`)zddH\nzG%_|ZH8Z!cw6eCbwX=6{baHlab#wRd^z!!c_45x13RUz\nzF>}`JIdkXDU$Ah|;w4L$Enl&6)#^2C*R9{Mant54TeofBv2)k%J$v`\nDate: Thu, 8 Oct 2026 17:22:18 +0000\nSubject: [PATCH] fixture after\n\n---\n alpha.py | 4 ++--\n beta.py | 2 +-\n blob.bin | Bin 1024 -> 1024 bytes\n copy_from.py => copy_to.py | 0\n crlf.py | 2 +-\n deleted.py | 2 --\n drop.py | 1 -\n insert.py | 1 +\n mode.py | 0\n new.py | 2 ++\n \"odd\\t\\303\\244.py\" | 2 +-\n payload.py | 6 +++---\n rename_before.py => rename_after.py | 0\n unicode.py | 2 +-\n 14 files changed, 12 insertions(+), 12 deletions(-)\n copy copy_from.py => copy_to.py (100%)\n delete mode 100644 deleted.py\n mode change 100644 => 100755 mode.py\n create mode 100644 new.py\n rename rename_before.py => rename_after.py (100%)\n\ndiff --git a/alpha.py b/alpha.py\nindex c146194..d9f6196 100644\n--- a/alpha.py\n+++ b/alpha.py\n@@ -1,5 +1,5 @@\n def first():\n- return 1\n+ return 2\n \n # gap 00\n # gap 01\n@@ -17,7 +17,7 @@ def first():\n # gap 13\n \n def second():\n- return 3\n+ return 4\n \n def unchanged():\n return 99\ndiff --git a/beta.py b/beta.py\nindex 602f556..92177fc 100644\n--- a/beta.py\n+++ b/beta.py\n@@ -1,2 +1,2 @@\n def beta():\n- return 10\n\\ No newline at end of file\n+ return 11\n\\ No newline at end of file\ndiff --git a/blob.bin b/blob.bin\nindex c8b49c8cd518e58491924bfc364ff26e01a85009..991c98a05128d446b067c2ae39bd7dff0a11686a 100644\nGIT binary patch\nliteral 1024\nzcmezW@9&@AzkdGs{_X3R&!0Yic>nJ0o7b;izIguZ>66Eg9zM8#@9v%3w{G6Je(ma&\nz%a<-*IDhW!nbW6Eo;ZH&=#j&R4j$OQZ||PnyLRr_zHRH4&6_rESif%Vn$@dTu2{Zo\nz>5|2Z7A}}SZ|F(<6Xm4w6X>Mw4sIRN7sjjN5C@(85\nzDK083$j{5o$mNlr>kh>weniH?el2oDPl2@VPj@b~le@%HlcaCdWcadvWa\nzu(z|dv9_|bFgG(bF*Y(Z(AU$|(bm$`P*+n`QC3n^ke8E{k(QE_5El~_5f%~@;OFDz\na;pXDxU}s}xVP;}v_&@6Z(fA+1=^p@?&i)Vp\n\nliteral 1024\nzcmZQzWMXDvWn<^yMC+6cQE@6%&_`l#-T_m6KOcR8m$^Ra4i{)Y8_`)zddH\nzG%_|ZH8Z!cw6eCbwX=6{baHlab#wRd^z!!c_45x13RUz\nzF>}`JIdkXDU$Ah|;w4L$Enl&6)#^2C*R9{Mant54TeofBv2)k%J$v` str:\n \"\"\"Top-level name to import from ``module`` (class name for methods).\"\"\"\n return self.qualname.split(\".\")[0]\n\n def to_dict(self) -> dict:\n return asdict(self)\n\n\ndef _unquote_git_path(raw: str) -> str:\n \"\"\"Decode Git's C-quoted filename bytes, including octal UTF-8 sequences.\"\"\"\n escapes = {\"a\": 7, \"b\": 8, \"t\": 9, \"n\": 10, \"v\": 11, \"f\": 12, \"r\": 13,\n '\"': 34, \"\\\\\": 92}\n decoded = bytearray()\n index = 1 # Opening double quote.\n while index < len(raw):\n char = raw[index]\n index += 1\n if char == '\"':\n if index != len(raw) or 0 in decoded:\n raise ValueError(\"invalid Git-quoted diff path\")\n return os.fsdecode(bytes(decoded))\n if char != \"\\\\\":\n decoded.extend(os.fsencode(char))\n continue\n if index >= len(raw):\n break\n escaped = raw[index]\n index += 1\n if escaped in escapes:\n decoded.append(escapes[escaped])\n elif (escaped in \"0123\" and index + 1 < len(raw)\n and all(digit in \"01234567\" for digit in raw[index:index + 2])):\n decoded.append(int(escaped + raw[index:index + 2], 8))\n index += 2\n else:\n raise ValueError(\"invalid escape in Git-quoted diff path\")\n raise ValueError(\"unterminated Git-quoted diff path\")\n\n\ndef _strip_path(raw: str) -> str | None:\n p = raw.split(\"\\t\")[0].strip()\n if p.startswith('\"'):\n p = _unquote_git_path(p)\n if p == \"/dev/null\":\n return None\n if p.startswith((\"a/\", \"b/\")):\n p = p[2:]\n return p\n\n\ndef parse_unified_diff(text: str) -> list[FileChange]:\n \"\"\"Parse ``git diff`` / ``diff -u`` output into per-file changed line sets.\n\n Hunk line counters are tracked so that removed lines whose content starts\n with ``-- `` are not mistaken for file headers.\n \"\"\"\n files: list[FileChange] = []\n cur: FileChange | None = None\n pending_old: str | None = None\n old_left = new_left = 0\n new_ln = 0\n # Git hunk counts use LF-delimited records, not Unicode separators.\n for line in text.split(\"\\n\"):\n if cur is not None and (old_left > 0 or new_left > 0):\n if line.startswith(\"\\\\\"): # \"\\ No newline at end of file\"\n continue\n if line.startswith(\"+\"):\n cur.added_lines.add(new_ln)\n cur.touched_lines.add(new_ln)\n new_ln += 1\n new_left -= 1\n elif line.startswith(\"-\"):\n # anchor a pure deletion to the surrounding new-file lines\n for anchor in (new_ln - 1, new_ln):\n if anchor >= 1:\n cur.touched_lines.add(anchor)\n old_left -= 1\n else: # context line (\" \" prefix, or blank if whitespace was stripped)\n new_ln += 1\n new_left -= 1\n old_left -= 1\n continue\n if line.startswith(\"diff --git \"):\n cur, pending_old = None, None\n continue\n if line.startswith(\"--- \"):\n pending_old = _strip_path(line[4:])\n continue\n if line.startswith(\"+++ \"):\n new_path = _strip_path(line[4:])\n cur = FileChange(\n path=new_path or pending_old or \"\",\n old_path=pending_old,\n is_new=pending_old is None,\n is_deleted=new_path is None,\n )\n files.append(cur)\n continue\n m = _HUNK.match(line)\n if m and cur is not None:\n old_left = int(m.group(2)) if m.group(2) is not None else 1\n new_left = int(m.group(4)) if m.group(4) is not None else 1\n new_ln = int(m.group(3))\n if new_left == 0: # pure deletion hunk: \"+N,0\" means \"after line N\"\n new_ln += 1\n return files\n\n\ndef is_test_path(path: str) -> bool:\n p = PurePosixPath(path)\n return (\n \"tests\" in p.parts\n or \"test\" in p.parts\n or p.name.startswith(\"test_\")\n or p.name.endswith(\"_test.py\")\n or p.name == \"conftest.py\"\n )\n\n\ndef module_name(path: str) -> str:\n parts = list(PurePosixPath(path).with_suffix(\"\").parts)\n if parts and parts[0] == \"src\":\n parts = parts[1:]\n if parts and parts[-1] == \"__init__\":\n parts = parts[:-1]\n return \".\".join(parts)\n\n\ndef _iter_functions(body, prefix: str = \"\", in_class: bool = False):\n \"\"\"Yield (qualname, node, is_method) for top-level functions and methods.\n\n Nested functions are attributed to their enclosing function. Functions\n defined under ``if``/``try``/``with``/``match`` blocks at module/class level count.\n \"\"\"\n for node in body:\n if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):\n yield prefix + node.name, node, in_class\n elif isinstance(node, ast.ClassDef):\n yield from _iter_functions(node.body, prefix + node.name + \".\", True)\n elif isinstance(node, ast.Match):\n for case in node.cases:\n yield from _iter_functions(case.body, prefix, in_class)\n else:\n for attr in (\"body\", \"orelse\", \"finalbody\"):\n sub = getattr(node, attr, None)\n if isinstance(sub, list):\n yield from _iter_functions(sub, prefix, in_class)\n for handler in getattr(node, \"handlers\", None) or []:\n yield from _iter_functions(handler.body, prefix, in_class)\n\n\ndef functions_touching(source: str, path: str, lines: set[int], added: set[int]) -> list[ChangedFunction]:\n tree = ast.parse(source, filename=path)\n # Python physical lines use LF/CRLF/CR; other separators can be literals.\n src_lines = source.replace(\"\\r\\n\", \"\\n\").replace(\"\\r\", \"\\n\").split(\"\\n\")\n out: list[ChangedFunction] = []\n for qualname, node, is_method in _iter_functions(tree.body):\n start = min([node.lineno] + [d.lineno for d in node.decorator_list])\n end = node.end_lineno or node.lineno\n if not any(start <= ln <= end for ln in lines):\n continue\n out.append(\n ChangedFunction(\n path=path,\n module=module_name(path),\n qualname=qualname,\n lineno=start,\n end_lineno=end,\n changed_lines=sorted(ln for ln in added if start <= ln <= end),\n source=\"\\n\".join(src_lines[start - 1 : end]),\n is_method=is_method,\n )\n )\n return out\n\n\nclass DiffSourceError(ValueError):\n \"\"\"A diff selects a source path outside the requested repository.\"\"\"\n\n\ndef _source_path(repo: Path, raw: str) -> Path:\n \"\"\"Resolve a relative diff path without admitting an outside source file.\"\"\"\n relative = Path(raw)\n if relative.anchor or \"..\" in relative.parts:\n raise DiffSourceError(f\"diff source must be repository-relative: {raw!r}\")\n try:\n resolved = (repo / relative).resolve()\n except (OSError, RuntimeError, ValueError) as exc:\n raise DiffSourceError(f\"cannot resolve diff source: {raw!r}\") from exc\n if not resolved.is_relative_to(repo):\n raise DiffSourceError(f\"diff source resolves outside repository: {raw!r}\")\n return resolved\n\n\ndef changed_functions(repo: str | Path, diff_text: str, include_tests: bool = False) -> list[ChangedFunction]:\n \"\"\"Return touched Python functions, rejecting selected sources outside ``repo``.\n\n Paths must be relative without parent traversal. Symlinks are supported\n when their resolved destination remains inside the resolved repository.\n Source encoding follows Python's BOM and encoding-cookie rules.\n \"\"\"\n repo = Path(repo).resolve()\n result: list[ChangedFunction] = []\n for fc in parse_unified_diff(diff_text):\n if fc.is_deleted or not fc.path.endswith(\".py\"):\n continue\n if not include_tests and is_test_path(fc.path):\n continue\n f = _source_path(repo, fc.path)\n if not f.is_file():\n continue\n with tokenize.open(f) as source_file:\n source = source_file.read()\n result.extend(functions_touching(source, fc.path, fc.touched_lines, fc.added_lines))\n return result\n" + } + ], + "frozen_at": "2026-10-08T17:22:18.817535+00:00", + "malformed": "/home/jacob/testpilot-git-compat-6cc6795e89f4/docs/receiving/git-compat-6cc6795e89f4/patches/malformed_unfinished_tail.diff", + "new_fixture_commit": "b9c650347018c5dc7f4bd22a7779c19606fac02b", + "old_fixture_commit": "5337d8707ac0ab30f2b7a9e75df84ca11a5f26e0", + "oracle": "/home/jacob/testpilot-git-compat-6cc6795e89f4-data/oracle-before", + "repo": "/home/jacob/testpilot-git-compat-6cc6795e89f4-data/fixture", + "tripwire": "/home/jacob/testpilot-git-compat-6cc6795e89f4-data/pytest-tripwire" +} diff --git a/docs/receiving/git-compat-6cc6795e89f4/independent-receiving-v2.json b/docs/receiving/git-compat-6cc6795e89f4/independent-receiving-v2.json new file mode 100644 index 0000000..806a592 --- /dev/null +++ b/docs/receiving/git-compat-6cc6795e89f4/independent-receiving-v2.json @@ -0,0 +1,3827 @@ +{ + "candidate_source_commit": "dece74cdcefe125bbb3b38f5f9ca4a92097b998b", + "expectations_sha256": "2b57e4d9767a2af3159417c29f34725daf23ebf2ca3ffd3b3ed23c8f7c496b6b", + "negative_api": { + "baseline": { + "files": [ + { + "added_lines": [ + 2, + 20, + 900 + ], + "is_deleted": false, + "is_new": false, + "old_path": "alpha.py", + "path": "alpha.py", + "touched_lines": [ + 1, + 2, + 19, + 20, + 899, + 900 + ] + } + ], + "functions": [ + { + "changed_lines": [ + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "alpha.py", + "qualname": "first", + "source": "def first():\n return 2" + }, + { + "changed_lines": [ + 20 + ], + "end_lineno": 20, + "is_method": false, + "lineno": 19, + "path": "alpha.py", + "qualname": "second", + "source": "def second():\n return 4" + } + ], + "ok": true, + "targets": [ + [ + "alpha.py", + "first" + ], + [ + "alpha.py", + "second" + ] + ] + }, + "candidate": { + "error_type": "DiffFormatError", + "message": "invalid unified diff at record 13: incomplete hunk (1 old and 1 new records missing)", + "ok": false, + "traceback": "Traceback (most recent call last):\n File \"/home/jacob/testpilot-git-compat-6cc6795e89f4/docs/receiving/git-compat-6cc6795e89f4/receive_candidate_v2.py.txt\", line 40, in observe\n files = normalized_files(module,text)\n File \"/home/jacob/testpilot-git-compat-6cc6795e89f4/docs/receiving/git-compat-6cc6795e89f4/receive_candidate_v2.py.txt\", line 33, in normalized_files\n for x in module.parse_unified_diff(text)]\n ~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^\n File \"/home/jacob/testpilot-git-compat-6cc6795e89f4/testpilot/diff.py\", line 217, in parse_unified_diff\n raise invalid(f\"incomplete hunk ({old_left} old and {new_left} new records missing)\")\ntestpilot.diff.DiffFormatError: invalid unified diff at record 13: incomplete hunk (1 old and 1 new records missing)\n" + } + }, + "negative_cli": [ + { + "argv": [ + "/usr/bin/python3", + "-B", + "-m", + "testpilot", + "run", + "--repo", + "/home/jacob/testpilot-git-compat-6cc6795e89f4-data/fixture", + "--diff", + "/home/jacob/testpilot-git-compat-6cc6795e89f4/docs/receiving/git-compat-6cc6795e89f4/patches/malformed_unfinished_tail.diff", + "--script", + "/home/jacob/testpilot-git-compat-6cc6795e89f4-data/deliberately_missing_model_replies", + "--out", + "/home/jacob/testpilot-git-compat-6cc6795e89f4-data/out_v2_auto_new" + ], + "name": "auto_new", + "out_names": [], + "output_preserved": true, + "pass": true, + "pytest_tripwire_absent": true, + "returncode": 2, + "stderr": "testpilot: invalid diff format: invalid unified diff at record 13: incomplete hunk (1 old and 1 new records missing)\n", + "stdout": "" + }, + { + "argv": [ + "/usr/bin/python3", + "-B", + "-m", + "testpilot", + "run", + "--repo", + "/home/jacob/testpilot-git-compat-6cc6795e89f4-data/fixture", + "--diff", + "/home/jacob/testpilot-git-compat-6cc6795e89f4/docs/receiving/git-compat-6cc6795e89f4/patches/malformed_unfinished_tail.diff", + "--script", + "/home/jacob/testpilot-git-compat-6cc6795e89f4-data/deliberately_missing_model_replies", + "--out", + "/home/jacob/testpilot-git-compat-6cc6795e89f4-data/out_v2_auto_existing" + ], + "name": "auto_existing", + "out_names": [ + "report.html", + "report.json", + "report.md", + "testpilot.patch" + ], + "output_preserved": true, + "pass": true, + "pytest_tripwire_absent": true, + "returncode": 2, + "stderr": "testpilot: invalid diff format: invalid unified diff at record 13: incomplete hunk (1 old and 1 new records missing)\n", + "stdout": "" + }, + { + "argv": [ + "/usr/bin/python3", + "-B", + "-m", + "testpilot", + "run", + "--repo", + "/home/jacob/testpilot-git-compat-6cc6795e89f4-data/fixture", + "--diff", + "/home/jacob/testpilot-git-compat-6cc6795e89f4/docs/receiving/git-compat-6cc6795e89f4/patches/malformed_unfinished_tail.diff", + "--script", + "/home/jacob/testpilot-git-compat-6cc6795e89f4-data/deliberately_missing_model_replies", + "--out", + "/home/jacob/testpilot-git-compat-6cc6795e89f4-data/out_v2_explicit_new", + "--target", + "alpha.py::first" + ], + "name": "explicit_new", + "out_names": [], + "output_preserved": true, + "pass": true, + "pytest_tripwire_absent": true, + "returncode": 2, + "stderr": "testpilot: invalid target selection: cannot parse supplied diff: invalid unified diff at record 13: incomplete hunk (1 old and 1 new records missing)\n", + "stdout": "" + }, + { + "argv": [ + "/usr/bin/python3", + "-B", + "-m", + "testpilot", + "run", + "--repo", + "/home/jacob/testpilot-git-compat-6cc6795e89f4-data/fixture", + "--diff", + "/home/jacob/testpilot-git-compat-6cc6795e89f4/docs/receiving/git-compat-6cc6795e89f4/patches/malformed_unfinished_tail.diff", + "--script", + "/home/jacob/testpilot-git-compat-6cc6795e89f4-data/deliberately_missing_model_replies", + "--out", + "/home/jacob/testpilot-git-compat-6cc6795e89f4-data/out_v2_explicit_existing", + "--target", + "alpha.py::first" + ], + "name": "explicit_existing", + "out_names": [ + "report.html", + "report.json", + "report.md", + "testpilot.patch" + ], + "output_preserved": true, + "pass": true, + "pytest_tripwire_absent": true, + "returncode": 2, + "stderr": "testpilot: invalid target selection: cannot parse supplied diff: invalid unified diff at record 13: incomplete hunk (1 old and 1 new records missing)\n", + "stdout": "" + } + ], + "observed_at": "2026-10-08T17:36:11.005641+00:00", + "positive_cases": [ + { + "baseline": { + "files": [ + { + "added_lines": [ + 2, + 20 + ], + "is_deleted": false, + "is_new": false, + "old_path": "alpha.py", + "path": "alpha.py", + "touched_lines": [ + 1, + 2, + 19, + 20 + ] + }, + { + "added_lines": [ + 2 + ], + "is_deleted": false, + "is_new": false, + "old_path": "beta.py", + "path": "beta.py", + "touched_lines": [ + 1, + 2 + ] + }, + { + "added_lines": [ + 2 + ], + "is_deleted": false, + "is_new": false, + "old_path": "crlf.py", + "path": "crlf.py", + "touched_lines": [ + 1, + 2 + ] + }, + { + "added_lines": [], + "is_deleted": true, + "is_new": false, + "old_path": "deleted.py", + "path": "deleted.py", + "touched_lines": [ + 1 + ] + }, + { + "added_lines": [], + "is_deleted": false, + "is_new": false, + "old_path": "drop.py", + "path": "drop.py", + "touched_lines": [ + 2, + 3 + ] + }, + { + "added_lines": [ + 2 + ], + "is_deleted": false, + "is_new": false, + "old_path": "insert.py", + "path": "insert.py", + "touched_lines": [ + 2 + ] + }, + { + "added_lines": [ + 1, + 2 + ], + "is_deleted": false, + "is_new": true, + "old_path": null, + "path": "new.py", + "touched_lines": [ + 1, + 2 + ] + }, + { + "added_lines": [ + 2 + ], + "is_deleted": false, + "is_new": false, + "old_path": "odd\t\u00e4.py", + "path": "odd\t\u00e4.py", + "touched_lines": [ + 1, + 2 + ] + }, + { + "added_lines": [ + 2, + 3, + 4 + ], + "is_deleted": false, + "is_new": false, + "old_path": "payload.py", + "path": "payload.py", + "touched_lines": [ + 1, + 2, + 3, + 4 + ] + }, + { + "added_lines": [ + 2 + ], + "is_deleted": false, + "is_new": false, + "old_path": "unicode.py", + "path": "unicode.py", + "touched_lines": [ + 1, + 2 + ] + } + ], + "functions": [ + { + "changed_lines": [ + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "alpha.py", + "qualname": "first", + "source": "def first():\n return 2" + }, + { + "changed_lines": [ + 20 + ], + "end_lineno": 20, + "is_method": false, + "lineno": 19, + "path": "alpha.py", + "qualname": "second", + "source": "def second():\n return 4" + }, + { + "changed_lines": [ + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "beta.py", + "qualname": "beta", + "source": "def beta():\n return 11" + }, + { + "changed_lines": [ + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "crlf.py", + "qualname": "crlf_fn", + "source": "def crlf_fn():\n return 21" + }, + { + "changed_lines": [], + "end_lineno": 3, + "is_method": false, + "lineno": 1, + "path": "drop.py", + "qualname": "removedline", + "source": "def removedline():\n keep = 1\n return keep" + }, + { + "changed_lines": [ + 2 + ], + "end_lineno": 3, + "is_method": false, + "lineno": 1, + "path": "insert.py", + "qualname": "insertedline", + "source": "def insertedline():\n inserted = 50\n return 51" + }, + { + "changed_lines": [ + 1, + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "new.py", + "qualname": "gamma_unique", + "source": "def gamma_unique():\n return 'new-post-change-constant'" + }, + { + "changed_lines": [ + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "odd\t\u00e4.py", + "qualname": "odd_fn", + "source": "def odd_fn():\n return 41" + }, + { + "changed_lines": [ + 2, + 3, + 4 + ], + "end_lineno": 5, + "is_method": false, + "lineno": 1, + "path": "payload.py", + "qualname": "payload", + "source": "def payload():\n return \"\"\"new\n--- new literal\n+++ new literal\n\"\"\"" + }, + { + "changed_lines": [ + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "unicode.py", + "qualname": "unicode_fn", + "source": "def unicode_fn():\n return \"new\u2028tail\u2029end\"" + } + ], + "ok": true, + "targets": [ + [ + "alpha.py", + "first" + ], + [ + "alpha.py", + "second" + ], + [ + "beta.py", + "beta" + ], + [ + "crlf.py", + "crlf_fn" + ], + [ + "drop.py", + "removedline" + ], + [ + "insert.py", + "insertedline" + ], + [ + "new.py", + "gamma_unique" + ], + [ + "odd\t\u00e4.py", + "odd_fn" + ], + [ + "payload.py", + "payload" + ], + [ + "unicode.py", + "unicode_fn" + ] + ] + }, + "candidate": { + "files": [ + { + "added_lines": [ + 2, + 20 + ], + "is_deleted": false, + "is_new": false, + "old_path": "alpha.py", + "path": "alpha.py", + "touched_lines": [ + 1, + 2, + 19, + 20 + ] + }, + { + "added_lines": [ + 2 + ], + "is_deleted": false, + "is_new": false, + "old_path": "beta.py", + "path": "beta.py", + "touched_lines": [ + 1, + 2 + ] + }, + { + "added_lines": [ + 2 + ], + "is_deleted": false, + "is_new": false, + "old_path": "crlf.py", + "path": "crlf.py", + "touched_lines": [ + 1, + 2 + ] + }, + { + "added_lines": [], + "is_deleted": true, + "is_new": false, + "old_path": "deleted.py", + "path": "deleted.py", + "touched_lines": [ + 1 + ] + }, + { + "added_lines": [], + "is_deleted": false, + "is_new": false, + "old_path": "drop.py", + "path": "drop.py", + "touched_lines": [ + 2, + 3 + ] + }, + { + "added_lines": [ + 2 + ], + "is_deleted": false, + "is_new": false, + "old_path": "insert.py", + "path": "insert.py", + "touched_lines": [ + 2 + ] + }, + { + "added_lines": [ + 1, + 2 + ], + "is_deleted": false, + "is_new": true, + "old_path": null, + "path": "new.py", + "touched_lines": [ + 1, + 2 + ] + }, + { + "added_lines": [ + 2 + ], + "is_deleted": false, + "is_new": false, + "old_path": "odd\t\u00e4.py", + "path": "odd\t\u00e4.py", + "touched_lines": [ + 1, + 2 + ] + }, + { + "added_lines": [ + 2, + 3, + 4 + ], + "is_deleted": false, + "is_new": false, + "old_path": "payload.py", + "path": "payload.py", + "touched_lines": [ + 1, + 2, + 3, + 4 + ] + }, + { + "added_lines": [ + 2 + ], + "is_deleted": false, + "is_new": false, + "old_path": "unicode.py", + "path": "unicode.py", + "touched_lines": [ + 1, + 2 + ] + } + ], + "functions": [ + { + "changed_lines": [ + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "alpha.py", + "qualname": "first", + "source": "def first():\n return 2" + }, + { + "changed_lines": [ + 20 + ], + "end_lineno": 20, + "is_method": false, + "lineno": 19, + "path": "alpha.py", + "qualname": "second", + "source": "def second():\n return 4" + }, + { + "changed_lines": [ + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "beta.py", + "qualname": "beta", + "source": "def beta():\n return 11" + }, + { + "changed_lines": [ + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "crlf.py", + "qualname": "crlf_fn", + "source": "def crlf_fn():\n return 21" + }, + { + "changed_lines": [], + "end_lineno": 3, + "is_method": false, + "lineno": 1, + "path": "drop.py", + "qualname": "removedline", + "source": "def removedline():\n keep = 1\n return keep" + }, + { + "changed_lines": [ + 2 + ], + "end_lineno": 3, + "is_method": false, + "lineno": 1, + "path": "insert.py", + "qualname": "insertedline", + "source": "def insertedline():\n inserted = 50\n return 51" + }, + { + "changed_lines": [ + 1, + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "new.py", + "qualname": "gamma_unique", + "source": "def gamma_unique():\n return 'new-post-change-constant'" + }, + { + "changed_lines": [ + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "odd\t\u00e4.py", + "qualname": "odd_fn", + "source": "def odd_fn():\n return 41" + }, + { + "changed_lines": [ + 2, + 3, + 4 + ], + "end_lineno": 5, + "is_method": false, + "lineno": 1, + "path": "payload.py", + "qualname": "payload", + "source": "def payload():\n return \"\"\"new\n--- new literal\n+++ new literal\n\"\"\"" + }, + { + "changed_lines": [ + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "unicode.py", + "qualname": "unicode_fn", + "source": "def unicode_fn():\n return \"new\u2028tail\u2029end\"" + } + ], + "ok": true, + "targets": [ + [ + "alpha.py", + "first" + ], + [ + "alpha.py", + "second" + ], + [ + "beta.py", + "beta" + ], + [ + "crlf.py", + "crlf_fn" + ], + [ + "drop.py", + "removedline" + ], + [ + "insert.py", + "insertedline" + ], + [ + "new.py", + "gamma_unique" + ], + [ + "odd\t\u00e4.py", + "odd_fn" + ], + [ + "payload.py", + "payload" + ], + [ + "unicode.py", + "unicode_fn" + ] + ] + }, + "case": { + "expected_targets": [ + [ + "alpha.py", + "first" + ], + [ + "alpha.py", + "second" + ], + [ + "beta.py", + "beta" + ], + [ + "crlf.py", + "crlf_fn" + ], + [ + "drop.py", + "removedline" + ], + [ + "insert.py", + "insertedline" + ], + [ + "new.py", + "gamma_unique" + ], + [ + "odd\t\u00e4.py", + "odd_fn" + ], + [ + "payload.py", + "payload" + ], + [ + "unicode.py", + "unicode_fn" + ] + ], + "git_valid": true, + "name": "git_default", + "optional_envelope": false, + "patch": "/home/jacob/testpilot-git-compat-6cc6795e89f4/docs/receiving/git-compat-6cc6795e89f4/patches/git_default.diff", + "sha256": "e63cd0f0a8fc9f08aece050dfc5a92d5e19dac4b982f4c7787bf4f474572db0f" + }, + "cli": { + "argv": [ + "/usr/bin/python3", + "-B", + "-m", + "testpilot", + "targets", + "--repo", + "/home/jacob/testpilot-git-compat-6cc6795e89f4-data/fixture", + "--diff", + "/home/jacob/testpilot-git-compat-6cc6795e89f4/docs/receiving/git-compat-6cc6795e89f4/patches/git_default.diff" + ], + "returncode": 0, + "stderr": "", + "stdout": "10 changed Python functions outside tests\n\"alpha.py\":1-2 \"first\"\n\"alpha.py\":19-20 \"second\"\n\"beta.py\":1-2 \"beta\"\n\"crlf.py\":1-2 \"crlf_fn\"\n\"drop.py\":1-3 \"removedline\"\n\"insert.py\":1-3 \"insertedline\"\n\"new.py\":1-2 \"gamma_unique\"\n\"odd\\t\\u00e4.py\":1-2 \"odd_fn\"\n\"payload.py\":1-5 \"payload\"\n\"unicode.py\":1-2 \"unicode_fn\"\n" + }, + "pass": true + }, + { + "baseline": { + "files": [ + { + "added_lines": [ + 2, + 20 + ], + "is_deleted": false, + "is_new": false, + "old_path": "alpha.py", + "path": "alpha.py", + "touched_lines": [ + 1, + 2, + 19, + 20 + ] + }, + { + "added_lines": [ + 2 + ], + "is_deleted": false, + "is_new": false, + "old_path": "beta.py", + "path": "beta.py", + "touched_lines": [ + 1, + 2 + ] + }, + { + "added_lines": [ + 2 + ], + "is_deleted": false, + "is_new": false, + "old_path": "crlf.py", + "path": "crlf.py", + "touched_lines": [ + 1, + 2 + ] + }, + { + "added_lines": [], + "is_deleted": true, + "is_new": false, + "old_path": "deleted.py", + "path": "deleted.py", + "touched_lines": [ + 1 + ] + }, + { + "added_lines": [], + "is_deleted": false, + "is_new": false, + "old_path": "drop.py", + "path": "drop.py", + "touched_lines": [ + 2, + 3 + ] + }, + { + "added_lines": [ + 2 + ], + "is_deleted": false, + "is_new": false, + "old_path": "insert.py", + "path": "insert.py", + "touched_lines": [ + 2 + ] + }, + { + "added_lines": [ + 1, + 2 + ], + "is_deleted": false, + "is_new": true, + "old_path": null, + "path": "new.py", + "touched_lines": [ + 1, + 2 + ] + }, + { + "added_lines": [ + 2 + ], + "is_deleted": false, + "is_new": false, + "old_path": "odd\t\u00e4.py", + "path": "odd\t\u00e4.py", + "touched_lines": [ + 1, + 2 + ] + }, + { + "added_lines": [ + 2, + 3, + 4 + ], + "is_deleted": false, + "is_new": false, + "old_path": "payload.py", + "path": "payload.py", + "touched_lines": [ + 1, + 2, + 3, + 4 + ] + }, + { + "added_lines": [ + 2 + ], + "is_deleted": false, + "is_new": false, + "old_path": "unicode.py", + "path": "unicode.py", + "touched_lines": [ + 1, + 2 + ] + } + ], + "functions": [ + { + "changed_lines": [ + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "alpha.py", + "qualname": "first", + "source": "def first():\n return 2" + }, + { + "changed_lines": [ + 20 + ], + "end_lineno": 20, + "is_method": false, + "lineno": 19, + "path": "alpha.py", + "qualname": "second", + "source": "def second():\n return 4" + }, + { + "changed_lines": [ + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "beta.py", + "qualname": "beta", + "source": "def beta():\n return 11" + }, + { + "changed_lines": [ + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "crlf.py", + "qualname": "crlf_fn", + "source": "def crlf_fn():\n return 21" + }, + { + "changed_lines": [], + "end_lineno": 3, + "is_method": false, + "lineno": 1, + "path": "drop.py", + "qualname": "removedline", + "source": "def removedline():\n keep = 1\n return keep" + }, + { + "changed_lines": [ + 2 + ], + "end_lineno": 3, + "is_method": false, + "lineno": 1, + "path": "insert.py", + "qualname": "insertedline", + "source": "def insertedline():\n inserted = 50\n return 51" + }, + { + "changed_lines": [ + 1, + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "new.py", + "qualname": "gamma_unique", + "source": "def gamma_unique():\n return 'new-post-change-constant'" + }, + { + "changed_lines": [ + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "odd\t\u00e4.py", + "qualname": "odd_fn", + "source": "def odd_fn():\n return 41" + }, + { + "changed_lines": [ + 2, + 3, + 4 + ], + "end_lineno": 5, + "is_method": false, + "lineno": 1, + "path": "payload.py", + "qualname": "payload", + "source": "def payload():\n return \"\"\"new\n--- new literal\n+++ new literal\n\"\"\"" + }, + { + "changed_lines": [ + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "unicode.py", + "qualname": "unicode_fn", + "source": "def unicode_fn():\n return \"new\u2028tail\u2029end\"" + } + ], + "ok": true, + "targets": [ + [ + "alpha.py", + "first" + ], + [ + "alpha.py", + "second" + ], + [ + "beta.py", + "beta" + ], + [ + "crlf.py", + "crlf_fn" + ], + [ + "drop.py", + "removedline" + ], + [ + "insert.py", + "insertedline" + ], + [ + "new.py", + "gamma_unique" + ], + [ + "odd\t\u00e4.py", + "odd_fn" + ], + [ + "payload.py", + "payload" + ], + [ + "unicode.py", + "unicode_fn" + ] + ] + }, + "candidate": { + "files": [ + { + "added_lines": [ + 2, + 20 + ], + "is_deleted": false, + "is_new": false, + "old_path": "alpha.py", + "path": "alpha.py", + "touched_lines": [ + 1, + 2, + 19, + 20 + ] + }, + { + "added_lines": [ + 2 + ], + "is_deleted": false, + "is_new": false, + "old_path": "beta.py", + "path": "beta.py", + "touched_lines": [ + 1, + 2 + ] + }, + { + "added_lines": [ + 2 + ], + "is_deleted": false, + "is_new": false, + "old_path": "crlf.py", + "path": "crlf.py", + "touched_lines": [ + 1, + 2 + ] + }, + { + "added_lines": [], + "is_deleted": true, + "is_new": false, + "old_path": "deleted.py", + "path": "deleted.py", + "touched_lines": [ + 1 + ] + }, + { + "added_lines": [], + "is_deleted": false, + "is_new": false, + "old_path": "drop.py", + "path": "drop.py", + "touched_lines": [ + 2, + 3 + ] + }, + { + "added_lines": [ + 2 + ], + "is_deleted": false, + "is_new": false, + "old_path": "insert.py", + "path": "insert.py", + "touched_lines": [ + 2 + ] + }, + { + "added_lines": [ + 1, + 2 + ], + "is_deleted": false, + "is_new": true, + "old_path": null, + "path": "new.py", + "touched_lines": [ + 1, + 2 + ] + }, + { + "added_lines": [ + 2 + ], + "is_deleted": false, + "is_new": false, + "old_path": "odd\t\u00e4.py", + "path": "odd\t\u00e4.py", + "touched_lines": [ + 1, + 2 + ] + }, + { + "added_lines": [ + 2, + 3, + 4 + ], + "is_deleted": false, + "is_new": false, + "old_path": "payload.py", + "path": "payload.py", + "touched_lines": [ + 1, + 2, + 3, + 4 + ] + }, + { + "added_lines": [ + 2 + ], + "is_deleted": false, + "is_new": false, + "old_path": "unicode.py", + "path": "unicode.py", + "touched_lines": [ + 1, + 2 + ] + } + ], + "functions": [ + { + "changed_lines": [ + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "alpha.py", + "qualname": "first", + "source": "def first():\n return 2" + }, + { + "changed_lines": [ + 20 + ], + "end_lineno": 20, + "is_method": false, + "lineno": 19, + "path": "alpha.py", + "qualname": "second", + "source": "def second():\n return 4" + }, + { + "changed_lines": [ + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "beta.py", + "qualname": "beta", + "source": "def beta():\n return 11" + }, + { + "changed_lines": [ + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "crlf.py", + "qualname": "crlf_fn", + "source": "def crlf_fn():\n return 21" + }, + { + "changed_lines": [], + "end_lineno": 3, + "is_method": false, + "lineno": 1, + "path": "drop.py", + "qualname": "removedline", + "source": "def removedline():\n keep = 1\n return keep" + }, + { + "changed_lines": [ + 2 + ], + "end_lineno": 3, + "is_method": false, + "lineno": 1, + "path": "insert.py", + "qualname": "insertedline", + "source": "def insertedline():\n inserted = 50\n return 51" + }, + { + "changed_lines": [ + 1, + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "new.py", + "qualname": "gamma_unique", + "source": "def gamma_unique():\n return 'new-post-change-constant'" + }, + { + "changed_lines": [ + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "odd\t\u00e4.py", + "qualname": "odd_fn", + "source": "def odd_fn():\n return 41" + }, + { + "changed_lines": [ + 2, + 3, + 4 + ], + "end_lineno": 5, + "is_method": false, + "lineno": 1, + "path": "payload.py", + "qualname": "payload", + "source": "def payload():\n return \"\"\"new\n--- new literal\n+++ new literal\n\"\"\"" + }, + { + "changed_lines": [ + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "unicode.py", + "qualname": "unicode_fn", + "source": "def unicode_fn():\n return \"new\u2028tail\u2029end\"" + } + ], + "ok": true, + "targets": [ + [ + "alpha.py", + "first" + ], + [ + "alpha.py", + "second" + ], + [ + "beta.py", + "beta" + ], + [ + "crlf.py", + "crlf_fn" + ], + [ + "drop.py", + "removedline" + ], + [ + "insert.py", + "insertedline" + ], + [ + "new.py", + "gamma_unique" + ], + [ + "odd\t\u00e4.py", + "odd_fn" + ], + [ + "payload.py", + "payload" + ], + [ + "unicode.py", + "unicode_fn" + ] + ] + }, + "case": { + "expected_targets": [ + [ + "alpha.py", + "first" + ], + [ + "alpha.py", + "second" + ], + [ + "beta.py", + "beta" + ], + [ + "crlf.py", + "crlf_fn" + ], + [ + "drop.py", + "removedline" + ], + [ + "insert.py", + "insertedline" + ], + [ + "new.py", + "gamma_unique" + ], + [ + "odd\t\u00e4.py", + "odd_fn" + ], + [ + "payload.py", + "payload" + ], + [ + "unicode.py", + "unicode_fn" + ] + ], + "git_valid": true, + "name": "git_zero", + "optional_envelope": false, + "patch": "/home/jacob/testpilot-git-compat-6cc6795e89f4/docs/receiving/git-compat-6cc6795e89f4/patches/git_zero.diff", + "sha256": "0107f3f423d6f8e4150587ae8f649f8fb146176d01ddca27e37fbbd2b57e02d9" + }, + "cli": { + "argv": [ + "/usr/bin/python3", + "-B", + "-m", + "testpilot", + "targets", + "--repo", + "/home/jacob/testpilot-git-compat-6cc6795e89f4-data/fixture", + "--diff", + "/home/jacob/testpilot-git-compat-6cc6795e89f4/docs/receiving/git-compat-6cc6795e89f4/patches/git_zero.diff" + ], + "returncode": 0, + "stderr": "", + "stdout": "10 changed Python functions outside tests\n\"alpha.py\":1-2 \"first\"\n\"alpha.py\":19-20 \"second\"\n\"beta.py\":1-2 \"beta\"\n\"crlf.py\":1-2 \"crlf_fn\"\n\"drop.py\":1-3 \"removedline\"\n\"insert.py\":1-3 \"insertedline\"\n\"new.py\":1-2 \"gamma_unique\"\n\"odd\\t\\u00e4.py\":1-2 \"odd_fn\"\n\"payload.py\":1-5 \"payload\"\n\"unicode.py\":1-2 \"unicode_fn\"\n" + }, + "pass": true + }, + { + "baseline": { + "files": [ + { + "added_lines": [ + 2, + 20 + ], + "is_deleted": false, + "is_new": false, + "old_path": "alpha.py", + "path": "alpha.py", + "touched_lines": [ + 1, + 2, + 19, + 20 + ] + }, + { + "added_lines": [ + 2 + ], + "is_deleted": false, + "is_new": false, + "old_path": "beta.py", + "path": "beta.py", + "touched_lines": [ + 1, + 2 + ] + }, + { + "added_lines": [ + 2 + ], + "is_deleted": false, + "is_new": false, + "old_path": "crlf.py", + "path": "crlf.py", + "touched_lines": [ + 1, + 2 + ] + }, + { + "added_lines": [], + "is_deleted": true, + "is_new": false, + "old_path": "deleted.py", + "path": "deleted.py", + "touched_lines": [ + 1 + ] + }, + { + "added_lines": [], + "is_deleted": false, + "is_new": false, + "old_path": "drop.py", + "path": "drop.py", + "touched_lines": [ + 2, + 3 + ] + }, + { + "added_lines": [ + 2 + ], + "is_deleted": false, + "is_new": false, + "old_path": "insert.py", + "path": "insert.py", + "touched_lines": [ + 2 + ] + }, + { + "added_lines": [ + 1, + 2 + ], + "is_deleted": false, + "is_new": true, + "old_path": null, + "path": "new.py", + "touched_lines": [ + 1, + 2 + ] + }, + { + "added_lines": [ + 2 + ], + "is_deleted": false, + "is_new": false, + "old_path": "odd\t\u00e4.py", + "path": "odd\t\u00e4.py", + "touched_lines": [ + 1, + 2 + ] + }, + { + "added_lines": [ + 2, + 3, + 4 + ], + "is_deleted": false, + "is_new": false, + "old_path": "payload.py", + "path": "payload.py", + "touched_lines": [ + 1, + 2, + 3, + 4 + ] + }, + { + "added_lines": [ + 2 + ], + "is_deleted": false, + "is_new": false, + "old_path": "unicode.py", + "path": "unicode.py", + "touched_lines": [ + 1, + 2 + ] + } + ], + "functions": [ + { + "changed_lines": [ + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "alpha.py", + "qualname": "first", + "source": "def first():\n return 2" + }, + { + "changed_lines": [ + 20 + ], + "end_lineno": 20, + "is_method": false, + "lineno": 19, + "path": "alpha.py", + "qualname": "second", + "source": "def second():\n return 4" + }, + { + "changed_lines": [ + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "beta.py", + "qualname": "beta", + "source": "def beta():\n return 11" + }, + { + "changed_lines": [ + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "crlf.py", + "qualname": "crlf_fn", + "source": "def crlf_fn():\n return 21" + }, + { + "changed_lines": [], + "end_lineno": 3, + "is_method": false, + "lineno": 1, + "path": "drop.py", + "qualname": "removedline", + "source": "def removedline():\n keep = 1\n return keep" + }, + { + "changed_lines": [ + 2 + ], + "end_lineno": 3, + "is_method": false, + "lineno": 1, + "path": "insert.py", + "qualname": "insertedline", + "source": "def insertedline():\n inserted = 50\n return 51" + }, + { + "changed_lines": [ + 1, + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "new.py", + "qualname": "gamma_unique", + "source": "def gamma_unique():\n return 'new-post-change-constant'" + }, + { + "changed_lines": [ + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "odd\t\u00e4.py", + "qualname": "odd_fn", + "source": "def odd_fn():\n return 41" + }, + { + "changed_lines": [ + 2, + 3, + 4 + ], + "end_lineno": 5, + "is_method": false, + "lineno": 1, + "path": "payload.py", + "qualname": "payload", + "source": "def payload():\n return \"\"\"new\n--- new literal\n+++ new literal\n\"\"\"" + }, + { + "changed_lines": [ + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "unicode.py", + "qualname": "unicode_fn", + "source": "def unicode_fn():\n return \"new\u2028tail\u2029end\"" + } + ], + "ok": true, + "targets": [ + [ + "alpha.py", + "first" + ], + [ + "alpha.py", + "second" + ], + [ + "beta.py", + "beta" + ], + [ + "crlf.py", + "crlf_fn" + ], + [ + "drop.py", + "removedline" + ], + [ + "insert.py", + "insertedline" + ], + [ + "new.py", + "gamma_unique" + ], + [ + "odd\t\u00e4.py", + "odd_fn" + ], + [ + "payload.py", + "payload" + ], + [ + "unicode.py", + "unicode_fn" + ] + ] + }, + "candidate": { + "files": [ + { + "added_lines": [ + 2, + 20 + ], + "is_deleted": false, + "is_new": false, + "old_path": "alpha.py", + "path": "alpha.py", + "touched_lines": [ + 1, + 2, + 19, + 20 + ] + }, + { + "added_lines": [ + 2 + ], + "is_deleted": false, + "is_new": false, + "old_path": "beta.py", + "path": "beta.py", + "touched_lines": [ + 1, + 2 + ] + }, + { + "added_lines": [ + 2 + ], + "is_deleted": false, + "is_new": false, + "old_path": "crlf.py", + "path": "crlf.py", + "touched_lines": [ + 1, + 2 + ] + }, + { + "added_lines": [], + "is_deleted": true, + "is_new": false, + "old_path": "deleted.py", + "path": "deleted.py", + "touched_lines": [ + 1 + ] + }, + { + "added_lines": [], + "is_deleted": false, + "is_new": false, + "old_path": "drop.py", + "path": "drop.py", + "touched_lines": [ + 2, + 3 + ] + }, + { + "added_lines": [ + 2 + ], + "is_deleted": false, + "is_new": false, + "old_path": "insert.py", + "path": "insert.py", + "touched_lines": [ + 2 + ] + }, + { + "added_lines": [ + 1, + 2 + ], + "is_deleted": false, + "is_new": true, + "old_path": null, + "path": "new.py", + "touched_lines": [ + 1, + 2 + ] + }, + { + "added_lines": [ + 2 + ], + "is_deleted": false, + "is_new": false, + "old_path": "odd\t\u00e4.py", + "path": "odd\t\u00e4.py", + "touched_lines": [ + 1, + 2 + ] + }, + { + "added_lines": [ + 2, + 3, + 4 + ], + "is_deleted": false, + "is_new": false, + "old_path": "payload.py", + "path": "payload.py", + "touched_lines": [ + 1, + 2, + 3, + 4 + ] + }, + { + "added_lines": [ + 2 + ], + "is_deleted": false, + "is_new": false, + "old_path": "unicode.py", + "path": "unicode.py", + "touched_lines": [ + 1, + 2 + ] + } + ], + "functions": [ + { + "changed_lines": [ + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "alpha.py", + "qualname": "first", + "source": "def first():\n return 2" + }, + { + "changed_lines": [ + 20 + ], + "end_lineno": 20, + "is_method": false, + "lineno": 19, + "path": "alpha.py", + "qualname": "second", + "source": "def second():\n return 4" + }, + { + "changed_lines": [ + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "beta.py", + "qualname": "beta", + "source": "def beta():\n return 11" + }, + { + "changed_lines": [ + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "crlf.py", + "qualname": "crlf_fn", + "source": "def crlf_fn():\n return 21" + }, + { + "changed_lines": [], + "end_lineno": 3, + "is_method": false, + "lineno": 1, + "path": "drop.py", + "qualname": "removedline", + "source": "def removedline():\n keep = 1\n return keep" + }, + { + "changed_lines": [ + 2 + ], + "end_lineno": 3, + "is_method": false, + "lineno": 1, + "path": "insert.py", + "qualname": "insertedline", + "source": "def insertedline():\n inserted = 50\n return 51" + }, + { + "changed_lines": [ + 1, + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "new.py", + "qualname": "gamma_unique", + "source": "def gamma_unique():\n return 'new-post-change-constant'" + }, + { + "changed_lines": [ + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "odd\t\u00e4.py", + "qualname": "odd_fn", + "source": "def odd_fn():\n return 41" + }, + { + "changed_lines": [ + 2, + 3, + 4 + ], + "end_lineno": 5, + "is_method": false, + "lineno": 1, + "path": "payload.py", + "qualname": "payload", + "source": "def payload():\n return \"\"\"new\n--- new literal\n+++ new literal\n\"\"\"" + }, + { + "changed_lines": [ + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "unicode.py", + "qualname": "unicode_fn", + "source": "def unicode_fn():\n return \"new\u2028tail\u2029end\"" + } + ], + "ok": true, + "targets": [ + [ + "alpha.py", + "first" + ], + [ + "alpha.py", + "second" + ], + [ + "beta.py", + "beta" + ], + [ + "crlf.py", + "crlf_fn" + ], + [ + "drop.py", + "removedline" + ], + [ + "insert.py", + "insertedline" + ], + [ + "new.py", + "gamma_unique" + ], + [ + "odd\t\u00e4.py", + "odd_fn" + ], + [ + "payload.py", + "payload" + ], + [ + "unicode.py", + "unicode_fn" + ] + ] + }, + "case": { + "expected_targets": [ + [ + "alpha.py", + "first" + ], + [ + "alpha.py", + "second" + ], + [ + "beta.py", + "beta" + ], + [ + "crlf.py", + "crlf_fn" + ], + [ + "drop.py", + "removedline" + ], + [ + "insert.py", + "insertedline" + ], + [ + "new.py", + "gamma_unique" + ], + [ + "odd\t\u00e4.py", + "odd_fn" + ], + [ + "payload.py", + "payload" + ], + [ + "unicode.py", + "unicode_fn" + ] + ], + "git_valid": true, + "name": "git_one", + "optional_envelope": false, + "patch": "/home/jacob/testpilot-git-compat-6cc6795e89f4/docs/receiving/git-compat-6cc6795e89f4/patches/git_one.diff", + "sha256": "5cf9bc987e7021c0002aa89e22fc1af01dc42f3628b87745880c6aff839f9070" + }, + "cli": { + "argv": [ + "/usr/bin/python3", + "-B", + "-m", + "testpilot", + "targets", + "--repo", + "/home/jacob/testpilot-git-compat-6cc6795e89f4-data/fixture", + "--diff", + "/home/jacob/testpilot-git-compat-6cc6795e89f4/docs/receiving/git-compat-6cc6795e89f4/patches/git_one.diff" + ], + "returncode": 0, + "stderr": "", + "stdout": "10 changed Python functions outside tests\n\"alpha.py\":1-2 \"first\"\n\"alpha.py\":19-20 \"second\"\n\"beta.py\":1-2 \"beta\"\n\"crlf.py\":1-2 \"crlf_fn\"\n\"drop.py\":1-3 \"removedline\"\n\"insert.py\":1-3 \"insertedline\"\n\"new.py\":1-2 \"gamma_unique\"\n\"odd\\t\\u00e4.py\":1-2 \"odd_fn\"\n\"payload.py\":1-5 \"payload\"\n\"unicode.py\":1-2 \"unicode_fn\"\n" + }, + "pass": true + }, + { + "baseline": { + "files": [ + { + "added_lines": [ + 2, + 20 + ], + "is_deleted": false, + "is_new": false, + "old_path": "alpha.py", + "path": "alpha.py", + "touched_lines": [ + 1, + 2, + 19, + 20 + ] + }, + { + "added_lines": [ + 2 + ], + "is_deleted": false, + "is_new": false, + "old_path": "beta.py", + "path": "beta.py", + "touched_lines": [ + 1, + 2 + ] + }, + { + "added_lines": [ + 2 + ], + "is_deleted": false, + "is_new": false, + "old_path": "crlf.py", + "path": "crlf.py", + "touched_lines": [ + 1, + 2 + ] + }, + { + "added_lines": [], + "is_deleted": true, + "is_new": false, + "old_path": "deleted.py", + "path": "deleted.py", + "touched_lines": [ + 1 + ] + }, + { + "added_lines": [], + "is_deleted": false, + "is_new": false, + "old_path": "drop.py", + "path": "drop.py", + "touched_lines": [ + 2, + 3 + ] + }, + { + "added_lines": [ + 2 + ], + "is_deleted": false, + "is_new": false, + "old_path": "insert.py", + "path": "insert.py", + "touched_lines": [ + 2 + ] + }, + { + "added_lines": [ + 1, + 2 + ], + "is_deleted": false, + "is_new": true, + "old_path": null, + "path": "new.py", + "touched_lines": [ + 1, + 2 + ] + }, + { + "added_lines": [ + 2 + ], + "is_deleted": false, + "is_new": false, + "old_path": "odd\t\u00e4.py", + "path": "odd\t\u00e4.py", + "touched_lines": [ + 1, + 2 + ] + }, + { + "added_lines": [ + 2, + 3, + 4 + ], + "is_deleted": false, + "is_new": false, + "old_path": "payload.py", + "path": "payload.py", + "touched_lines": [ + 1, + 2, + 3, + 4 + ] + }, + { + "added_lines": [ + 2 + ], + "is_deleted": false, + "is_new": false, + "old_path": "unicode.py", + "path": "unicode.py", + "touched_lines": [ + 1, + 2 + ] + } + ], + "functions": [ + { + "changed_lines": [ + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "alpha.py", + "qualname": "first", + "source": "def first():\n return 2" + }, + { + "changed_lines": [ + 20 + ], + "end_lineno": 20, + "is_method": false, + "lineno": 19, + "path": "alpha.py", + "qualname": "second", + "source": "def second():\n return 4" + }, + { + "changed_lines": [ + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "beta.py", + "qualname": "beta", + "source": "def beta():\n return 11" + }, + { + "changed_lines": [ + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "crlf.py", + "qualname": "crlf_fn", + "source": "def crlf_fn():\n return 21" + }, + { + "changed_lines": [], + "end_lineno": 3, + "is_method": false, + "lineno": 1, + "path": "drop.py", + "qualname": "removedline", + "source": "def removedline():\n keep = 1\n return keep" + }, + { + "changed_lines": [ + 2 + ], + "end_lineno": 3, + "is_method": false, + "lineno": 1, + "path": "insert.py", + "qualname": "insertedline", + "source": "def insertedline():\n inserted = 50\n return 51" + }, + { + "changed_lines": [ + 1, + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "new.py", + "qualname": "gamma_unique", + "source": "def gamma_unique():\n return 'new-post-change-constant'" + }, + { + "changed_lines": [ + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "odd\t\u00e4.py", + "qualname": "odd_fn", + "source": "def odd_fn():\n return 41" + }, + { + "changed_lines": [ + 2, + 3, + 4 + ], + "end_lineno": 5, + "is_method": false, + "lineno": 1, + "path": "payload.py", + "qualname": "payload", + "source": "def payload():\n return \"\"\"new\n--- new literal\n+++ new literal\n\"\"\"" + }, + { + "changed_lines": [ + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "unicode.py", + "qualname": "unicode_fn", + "source": "def unicode_fn():\n return \"new\u2028tail\u2029end\"" + } + ], + "ok": true, + "targets": [ + [ + "alpha.py", + "first" + ], + [ + "alpha.py", + "second" + ], + [ + "beta.py", + "beta" + ], + [ + "crlf.py", + "crlf_fn" + ], + [ + "drop.py", + "removedline" + ], + [ + "insert.py", + "insertedline" + ], + [ + "new.py", + "gamma_unique" + ], + [ + "odd\t\u00e4.py", + "odd_fn" + ], + [ + "payload.py", + "payload" + ], + [ + "unicode.py", + "unicode_fn" + ] + ] + }, + "candidate": { + "files": [ + { + "added_lines": [ + 2, + 20 + ], + "is_deleted": false, + "is_new": false, + "old_path": "alpha.py", + "path": "alpha.py", + "touched_lines": [ + 1, + 2, + 19, + 20 + ] + }, + { + "added_lines": [ + 2 + ], + "is_deleted": false, + "is_new": false, + "old_path": "beta.py", + "path": "beta.py", + "touched_lines": [ + 1, + 2 + ] + }, + { + "added_lines": [ + 2 + ], + "is_deleted": false, + "is_new": false, + "old_path": "crlf.py", + "path": "crlf.py", + "touched_lines": [ + 1, + 2 + ] + }, + { + "added_lines": [], + "is_deleted": true, + "is_new": false, + "old_path": "deleted.py", + "path": "deleted.py", + "touched_lines": [ + 1 + ] + }, + { + "added_lines": [], + "is_deleted": false, + "is_new": false, + "old_path": "drop.py", + "path": "drop.py", + "touched_lines": [ + 2, + 3 + ] + }, + { + "added_lines": [ + 2 + ], + "is_deleted": false, + "is_new": false, + "old_path": "insert.py", + "path": "insert.py", + "touched_lines": [ + 2 + ] + }, + { + "added_lines": [ + 1, + 2 + ], + "is_deleted": false, + "is_new": true, + "old_path": null, + "path": "new.py", + "touched_lines": [ + 1, + 2 + ] + }, + { + "added_lines": [ + 2 + ], + "is_deleted": false, + "is_new": false, + "old_path": "odd\t\u00e4.py", + "path": "odd\t\u00e4.py", + "touched_lines": [ + 1, + 2 + ] + }, + { + "added_lines": [ + 2, + 3, + 4 + ], + "is_deleted": false, + "is_new": false, + "old_path": "payload.py", + "path": "payload.py", + "touched_lines": [ + 1, + 2, + 3, + 4 + ] + }, + { + "added_lines": [ + 2 + ], + "is_deleted": false, + "is_new": false, + "old_path": "unicode.py", + "path": "unicode.py", + "touched_lines": [ + 1, + 2 + ] + } + ], + "functions": [ + { + "changed_lines": [ + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "alpha.py", + "qualname": "first", + "source": "def first():\n return 2" + }, + { + "changed_lines": [ + 20 + ], + "end_lineno": 20, + "is_method": false, + "lineno": 19, + "path": "alpha.py", + "qualname": "second", + "source": "def second():\n return 4" + }, + { + "changed_lines": [ + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "beta.py", + "qualname": "beta", + "source": "def beta():\n return 11" + }, + { + "changed_lines": [ + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "crlf.py", + "qualname": "crlf_fn", + "source": "def crlf_fn():\n return 21" + }, + { + "changed_lines": [], + "end_lineno": 3, + "is_method": false, + "lineno": 1, + "path": "drop.py", + "qualname": "removedline", + "source": "def removedline():\n keep = 1\n return keep" + }, + { + "changed_lines": [ + 2 + ], + "end_lineno": 3, + "is_method": false, + "lineno": 1, + "path": "insert.py", + "qualname": "insertedline", + "source": "def insertedline():\n inserted = 50\n return 51" + }, + { + "changed_lines": [ + 1, + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "new.py", + "qualname": "gamma_unique", + "source": "def gamma_unique():\n return 'new-post-change-constant'" + }, + { + "changed_lines": [ + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "odd\t\u00e4.py", + "qualname": "odd_fn", + "source": "def odd_fn():\n return 41" + }, + { + "changed_lines": [ + 2, + 3, + 4 + ], + "end_lineno": 5, + "is_method": false, + "lineno": 1, + "path": "payload.py", + "qualname": "payload", + "source": "def payload():\n return \"\"\"new\n--- new literal\n+++ new literal\n\"\"\"" + }, + { + "changed_lines": [ + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "unicode.py", + "qualname": "unicode_fn", + "source": "def unicode_fn():\n return \"new\u2028tail\u2029end\"" + } + ], + "ok": true, + "targets": [ + [ + "alpha.py", + "first" + ], + [ + "alpha.py", + "second" + ], + [ + "beta.py", + "beta" + ], + [ + "crlf.py", + "crlf_fn" + ], + [ + "drop.py", + "removedline" + ], + [ + "insert.py", + "insertedline" + ], + [ + "new.py", + "gamma_unique" + ], + [ + "odd\t\u00e4.py", + "odd_fn" + ], + [ + "payload.py", + "payload" + ], + [ + "unicode.py", + "unicode_fn" + ] + ] + }, + "case": { + "expected_targets": [ + [ + "alpha.py", + "first" + ], + [ + "alpha.py", + "second" + ], + [ + "beta.py", + "beta" + ], + [ + "crlf.py", + "crlf_fn" + ], + [ + "drop.py", + "removedline" + ], + [ + "insert.py", + "insertedline" + ], + [ + "new.py", + "gamma_unique" + ], + [ + "odd\t\u00e4.py", + "odd_fn" + ], + [ + "payload.py", + "payload" + ], + [ + "unicode.py", + "unicode_fn" + ] + ], + "git_valid": true, + "name": "git_function", + "optional_envelope": false, + "patch": "/home/jacob/testpilot-git-compat-6cc6795e89f4/docs/receiving/git-compat-6cc6795e89f4/patches/git_function.diff", + "sha256": "9fc48c9a310a702951d64df8ff928fd2135b69373fa25bf8a0ab12c6788ab177" + }, + "cli": { + "argv": [ + "/usr/bin/python3", + "-B", + "-m", + "testpilot", + "targets", + "--repo", + "/home/jacob/testpilot-git-compat-6cc6795e89f4-data/fixture", + "--diff", + "/home/jacob/testpilot-git-compat-6cc6795e89f4/docs/receiving/git-compat-6cc6795e89f4/patches/git_function.diff" + ], + "returncode": 0, + "stderr": "", + "stdout": "10 changed Python functions outside tests\n\"alpha.py\":1-2 \"first\"\n\"alpha.py\":19-20 \"second\"\n\"beta.py\":1-2 \"beta\"\n\"crlf.py\":1-2 \"crlf_fn\"\n\"drop.py\":1-3 \"removedline\"\n\"insert.py\":1-3 \"insertedline\"\n\"new.py\":1-2 \"gamma_unique\"\n\"odd\\t\\u00e4.py\":1-2 \"odd_fn\"\n\"payload.py\":1-5 \"payload\"\n\"unicode.py\":1-2 \"unicode_fn\"\n" + }, + "pass": true + }, + { + "baseline": { + "files": [ + { + "added_lines": [ + 2, + 20 + ], + "is_deleted": false, + "is_new": false, + "old_path": "alpha.py", + "path": "alpha.py", + "touched_lines": [ + 1, + 2, + 19, + 20 + ] + } + ], + "functions": [ + { + "changed_lines": [ + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "alpha.py", + "qualname": "first", + "source": "def first():\n return 2" + }, + { + "changed_lines": [ + 20 + ], + "end_lineno": 20, + "is_method": false, + "lineno": 19, + "path": "alpha.py", + "qualname": "second", + "source": "def second():\n return 4" + } + ], + "ok": true, + "targets": [ + [ + "alpha.py", + "first" + ], + [ + "alpha.py", + "second" + ] + ] + }, + "candidate": { + "files": [ + { + "added_lines": [ + 2, + 20 + ], + "is_deleted": false, + "is_new": false, + "old_path": "alpha.py", + "path": "alpha.py", + "touched_lines": [ + 1, + 2, + 19, + 20 + ] + } + ], + "functions": [ + { + "changed_lines": [ + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "alpha.py", + "qualname": "first", + "source": "def first():\n return 2" + }, + { + "changed_lines": [ + 20 + ], + "end_lineno": 20, + "is_method": false, + "lineno": 19, + "path": "alpha.py", + "qualname": "second", + "source": "def second():\n return 4" + } + ], + "ok": true, + "targets": [ + [ + "alpha.py", + "first" + ], + [ + "alpha.py", + "second" + ] + ] + }, + "case": { + "expected_targets": [ + [ + "alpha.py", + "first" + ], + [ + "alpha.py", + "second" + ] + ], + "git_valid": true, + "name": "git_binary_python", + "optional_envelope": false, + "patch": "/home/jacob/testpilot-git-compat-6cc6795e89f4/docs/receiving/git-compat-6cc6795e89f4/patches/git_binary_python.diff", + "sha256": "0579a99651579908d9153bc12a0661e8b5081438055bdf3d5bab61a9cb787335" + }, + "cli": { + "argv": [ + "/usr/bin/python3", + "-B", + "-m", + "testpilot", + "targets", + "--repo", + "/home/jacob/testpilot-git-compat-6cc6795e89f4-data/fixture", + "--diff", + "/home/jacob/testpilot-git-compat-6cc6795e89f4/docs/receiving/git-compat-6cc6795e89f4/patches/git_binary_python.diff" + ], + "returncode": 0, + "stderr": "", + "stdout": "2 changed Python functions outside tests\n\"alpha.py\":1-2 \"first\"\n\"alpha.py\":19-20 \"second\"\n" + }, + "pass": true + }, + { + "baseline": { + "files": [], + "functions": [], + "ok": true, + "targets": [] + }, + "candidate": { + "files": [], + "functions": [], + "ok": true, + "targets": [] + }, + "case": { + "expected_targets": [], + "git_valid": true, + "name": "git_metadata_only", + "optional_envelope": false, + "patch": "/home/jacob/testpilot-git-compat-6cc6795e89f4/docs/receiving/git-compat-6cc6795e89f4/patches/git_metadata_only.diff", + "sha256": "5fc98cf71c98a4b1dab24b32c06820ea5b27e4088559e3adea93b0c474eba286" + }, + "cli": { + "argv": [ + "/usr/bin/python3", + "-B", + "-m", + "testpilot", + "targets", + "--repo", + "/home/jacob/testpilot-git-compat-6cc6795e89f4-data/fixture", + "--diff", + "/home/jacob/testpilot-git-compat-6cc6795e89f4/docs/receiving/git-compat-6cc6795e89f4/patches/git_metadata_only.diff" + ], + "returncode": 0, + "stderr": "", + "stdout": "0 changed Python functions outside tests\n" + }, + "pass": true + }, + { + "baseline": { + "files": [ + { + "added_lines": [ + 2 + ], + "is_deleted": false, + "is_new": false, + "old_path": "beta.py", + "path": "beta.py", + "touched_lines": [ + 1, + 2 + ] + } + ], + "functions": [ + { + "changed_lines": [ + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "beta.py", + "qualname": "beta", + "source": "def beta():\n return 11" + } + ], + "ok": true, + "targets": [ + [ + "beta.py", + "beta" + ] + ] + }, + "candidate": { + "files": [ + { + "added_lines": [ + 2 + ], + "is_deleted": false, + "is_new": false, + "old_path": "beta.py", + "path": "beta.py", + "touched_lines": [ + 1, + 2 + ] + } + ], + "functions": [ + { + "changed_lines": [ + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "beta.py", + "qualname": "beta", + "source": "def beta():\n return 11" + } + ], + "ok": true, + "targets": [ + [ + "beta.py", + "beta" + ] + ] + }, + "case": { + "expected_targets": [ + [ + "beta.py", + "beta" + ] + ], + "git_valid": true, + "name": "git_no_newline", + "optional_envelope": false, + "patch": "/home/jacob/testpilot-git-compat-6cc6795e89f4/docs/receiving/git-compat-6cc6795e89f4/patches/git_no_newline.diff", + "sha256": "3ba147b42f25628fc5ac2ec4f4cb73cae632127a75c8c009049ace99477210be" + }, + "cli": { + "argv": [ + "/usr/bin/python3", + "-B", + "-m", + "testpilot", + "targets", + "--repo", + "/home/jacob/testpilot-git-compat-6cc6795e89f4-data/fixture", + "--diff", + "/home/jacob/testpilot-git-compat-6cc6795e89f4/docs/receiving/git-compat-6cc6795e89f4/patches/git_no_newline.diff" + ], + "returncode": 0, + "stderr": "", + "stdout": "1 changed Python function outside tests\n\"beta.py\":1-2 \"beta\"\n" + }, + "pass": true + }, + { + "baseline": { + "files": [ + { + "added_lines": [ + 2 + ], + "is_deleted": false, + "is_new": false, + "old_path": "crlf.py", + "path": "crlf.py", + "touched_lines": [ + 1, + 2 + ] + } + ], + "functions": [ + { + "changed_lines": [ + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "crlf.py", + "qualname": "crlf_fn", + "source": "def crlf_fn():\n return 21" + } + ], + "ok": true, + "targets": [ + [ + "crlf.py", + "crlf_fn" + ] + ] + }, + "candidate": { + "files": [ + { + "added_lines": [ + 2 + ], + "is_deleted": false, + "is_new": false, + "old_path": "crlf.py", + "path": "crlf.py", + "touched_lines": [ + 1, + 2 + ] + } + ], + "functions": [ + { + "changed_lines": [ + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "crlf.py", + "qualname": "crlf_fn", + "source": "def crlf_fn():\n return 21" + } + ], + "ok": true, + "targets": [ + [ + "crlf.py", + "crlf_fn" + ] + ] + }, + "case": { + "expected_targets": [ + [ + "crlf.py", + "crlf_fn" + ] + ], + "git_valid": true, + "name": "git_crlf", + "optional_envelope": false, + "patch": "/home/jacob/testpilot-git-compat-6cc6795e89f4/docs/receiving/git-compat-6cc6795e89f4/patches/git_crlf.diff", + "sha256": "672b210a94fb9319e86d29d91027d732011c0cb34986539007d3012a4aa76ab6" + }, + "cli": { + "argv": [ + "/usr/bin/python3", + "-B", + "-m", + "testpilot", + "targets", + "--repo", + "/home/jacob/testpilot-git-compat-6cc6795e89f4-data/fixture", + "--diff", + "/home/jacob/testpilot-git-compat-6cc6795e89f4/docs/receiving/git-compat-6cc6795e89f4/patches/git_crlf.diff" + ], + "returncode": 0, + "stderr": "", + "stdout": "1 changed Python function outside tests\n\"crlf.py\":1-2 \"crlf_fn\"\n" + }, + "pass": true + }, + { + "baseline": { + "files": [ + { + "added_lines": [ + 2 + ], + "is_deleted": false, + "is_new": false, + "old_path": "odd\t\u00e4.py", + "path": "odd\t\u00e4.py", + "touched_lines": [ + 1, + 2 + ] + } + ], + "functions": [ + { + "changed_lines": [ + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "odd\t\u00e4.py", + "qualname": "odd_fn", + "source": "def odd_fn():\n return 41" + } + ], + "ok": true, + "targets": [ + [ + "odd\t\u00e4.py", + "odd_fn" + ] + ] + }, + "candidate": { + "files": [ + { + "added_lines": [ + 2 + ], + "is_deleted": false, + "is_new": false, + "old_path": "odd\t\u00e4.py", + "path": "odd\t\u00e4.py", + "touched_lines": [ + 1, + 2 + ] + } + ], + "functions": [ + { + "changed_lines": [ + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "odd\t\u00e4.py", + "qualname": "odd_fn", + "source": "def odd_fn():\n return 41" + } + ], + "ok": true, + "targets": [ + [ + "odd\t\u00e4.py", + "odd_fn" + ] + ] + }, + "case": { + "expected_targets": [ + [ + "odd\t\u00e4.py", + "odd_fn" + ] + ], + "git_valid": true, + "name": "git_quoted_path", + "optional_envelope": false, + "patch": "/home/jacob/testpilot-git-compat-6cc6795e89f4/docs/receiving/git-compat-6cc6795e89f4/patches/git_quoted_path.diff", + "sha256": "369169bf2ce73a58e47ff3af1e352a9a450f65fd8985ea1be9d74dde0ad859a9" + }, + "cli": { + "argv": [ + "/usr/bin/python3", + "-B", + "-m", + "testpilot", + "targets", + "--repo", + "/home/jacob/testpilot-git-compat-6cc6795e89f4-data/fixture", + "--diff", + "/home/jacob/testpilot-git-compat-6cc6795e89f4/docs/receiving/git-compat-6cc6795e89f4/patches/git_quoted_path.diff" + ], + "returncode": 0, + "stderr": "", + "stdout": "1 changed Python function outside tests\n\"odd\\t\\u00e4.py\":1-2 \"odd_fn\"\n" + }, + "pass": true + }, + { + "baseline": { + "files": [ + { + "added_lines": [ + 2, + 20 + ], + "is_deleted": false, + "is_new": false, + "old_path": "alpha.py", + "path": "alpha.py", + "touched_lines": [ + 1, + 2, + 19, + 20 + ] + }, + { + "added_lines": [ + 2 + ], + "is_deleted": false, + "is_new": false, + "old_path": "beta.py", + "path": "beta.py", + "touched_lines": [ + 1, + 2 + ] + }, + { + "added_lines": [ + 2 + ], + "is_deleted": false, + "is_new": false, + "old_path": "crlf.py", + "path": "crlf.py", + "touched_lines": [ + 1, + 2 + ] + }, + { + "added_lines": [], + "is_deleted": true, + "is_new": false, + "old_path": "deleted.py", + "path": "deleted.py", + "touched_lines": [ + 1 + ] + }, + { + "added_lines": [], + "is_deleted": false, + "is_new": false, + "old_path": "drop.py", + "path": "drop.py", + "touched_lines": [ + 2, + 3 + ] + }, + { + "added_lines": [ + 2 + ], + "is_deleted": false, + "is_new": false, + "old_path": "insert.py", + "path": "insert.py", + "touched_lines": [ + 2 + ] + }, + { + "added_lines": [ + 1, + 2 + ], + "is_deleted": false, + "is_new": true, + "old_path": null, + "path": "new.py", + "touched_lines": [ + 1, + 2 + ] + }, + { + "added_lines": [ + 2 + ], + "is_deleted": false, + "is_new": false, + "old_path": "odd\t\u00e4.py", + "path": "odd\t\u00e4.py", + "touched_lines": [ + 1, + 2 + ] + }, + { + "added_lines": [ + 2, + 3, + 4 + ], + "is_deleted": false, + "is_new": false, + "old_path": "payload.py", + "path": "payload.py", + "touched_lines": [ + 1, + 2, + 3, + 4 + ] + }, + { + "added_lines": [ + 2 + ], + "is_deleted": false, + "is_new": false, + "old_path": "unicode.py", + "path": "unicode.py", + "touched_lines": [ + 1, + 2 + ] + } + ], + "functions": [ + { + "changed_lines": [ + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "alpha.py", + "qualname": "first", + "source": "def first():\n return 2" + }, + { + "changed_lines": [ + 20 + ], + "end_lineno": 20, + "is_method": false, + "lineno": 19, + "path": "alpha.py", + "qualname": "second", + "source": "def second():\n return 4" + }, + { + "changed_lines": [ + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "beta.py", + "qualname": "beta", + "source": "def beta():\n return 11" + }, + { + "changed_lines": [ + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "crlf.py", + "qualname": "crlf_fn", + "source": "def crlf_fn():\n return 21" + }, + { + "changed_lines": [], + "end_lineno": 3, + "is_method": false, + "lineno": 1, + "path": "drop.py", + "qualname": "removedline", + "source": "def removedline():\n keep = 1\n return keep" + }, + { + "changed_lines": [ + 2 + ], + "end_lineno": 3, + "is_method": false, + "lineno": 1, + "path": "insert.py", + "qualname": "insertedline", + "source": "def insertedline():\n inserted = 50\n return 51" + }, + { + "changed_lines": [ + 1, + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "new.py", + "qualname": "gamma_unique", + "source": "def gamma_unique():\n return 'new-post-change-constant'" + }, + { + "changed_lines": [ + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "odd\t\u00e4.py", + "qualname": "odd_fn", + "source": "def odd_fn():\n return 41" + }, + { + "changed_lines": [ + 2, + 3, + 4 + ], + "end_lineno": 5, + "is_method": false, + "lineno": 1, + "path": "payload.py", + "qualname": "payload", + "source": "def payload():\n return \"\"\"new\n--- new literal\n+++ new literal\n\"\"\"" + }, + { + "changed_lines": [ + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "unicode.py", + "qualname": "unicode_fn", + "source": "def unicode_fn():\n return \"new\u2028tail\u2029end\"" + } + ], + "ok": true, + "targets": [ + [ + "alpha.py", + "first" + ], + [ + "alpha.py", + "second" + ], + [ + "beta.py", + "beta" + ], + [ + "crlf.py", + "crlf_fn" + ], + [ + "drop.py", + "removedline" + ], + [ + "insert.py", + "insertedline" + ], + [ + "new.py", + "gamma_unique" + ], + [ + "odd\t\u00e4.py", + "odd_fn" + ], + [ + "payload.py", + "payload" + ], + [ + "unicode.py", + "unicode_fn" + ] + ] + }, + "candidate": { + "error_type": "DiffFormatError", + "message": "invalid unified diff at record 158: body record exceeds the declared hunk ranges", + "ok": false, + "traceback": "Traceback (most recent call last):\n File \"/home/jacob/testpilot-git-compat-6cc6795e89f4/docs/receiving/git-compat-6cc6795e89f4/receive_candidate_v2.py.txt\", line 40, in observe\n files = normalized_files(module,text)\n File \"/home/jacob/testpilot-git-compat-6cc6795e89f4/docs/receiving/git-compat-6cc6795e89f4/receive_candidate_v2.py.txt\", line 33, in normalized_files\n for x in module.parse_unified_diff(text)]\n ~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^\n File \"/home/jacob/testpilot-git-compat-6cc6795e89f4/testpilot/diff.py\", line 215, in parse_unified_diff\n raise invalid(\"body record exceeds the declared hunk ranges\")\ntestpilot.diff.DiffFormatError: invalid unified diff at record 158: body record exceeds the declared hunk ranges\n" + }, + "case": { + "expected_targets": [ + [ + "alpha.py", + "first" + ], + [ + "alpha.py", + "second" + ], + [ + "beta.py", + "beta" + ], + [ + "crlf.py", + "crlf_fn" + ], + [ + "drop.py", + "removedline" + ], + [ + "insert.py", + "insertedline" + ], + [ + "new.py", + "gamma_unique" + ], + [ + "odd\t\u00e4.py", + "odd_fn" + ], + [ + "payload.py", + "payload" + ], + [ + "unicode.py", + "unicode_fn" + ] + ], + "git_valid": true, + "name": "git_mail_envelope", + "optional_envelope": true, + "patch": "/home/jacob/testpilot-git-compat-6cc6795e89f4/docs/receiving/git-compat-6cc6795e89f4/patches/git_mail_envelope.diff", + "sha256": "c997fa37a532088118d9caee74cad1a589ce0b0948c9653b733cbde7a3a2c21e" + }, + "cli": { + "argv": [ + "/usr/bin/python3", + "-B", + "-m", + "testpilot", + "targets", + "--repo", + "/home/jacob/testpilot-git-compat-6cc6795e89f4-data/fixture", + "--diff", + "/home/jacob/testpilot-git-compat-6cc6795e89f4/docs/receiving/git-compat-6cc6795e89f4/patches/git_mail_envelope.diff" + ], + "returncode": 2, + "stderr": "testpilot: cannot inspect targets: invalid unified diff at record 158: body record exceeds the declared hunk ranges\n", + "stdout": "" + }, + "pass": false + }, + { + "baseline": { + "files": [ + { + "added_lines": [ + 2, + 20 + ], + "is_deleted": false, + "is_new": false, + "old_path": "alpha.py", + "path": "alpha.py", + "touched_lines": [ + 1, + 2, + 19, + 20 + ] + } + ], + "functions": [ + { + "changed_lines": [ + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "alpha.py", + "qualname": "first", + "source": "def first():\n return 2" + }, + { + "changed_lines": [ + 20 + ], + "end_lineno": 20, + "is_method": false, + "lineno": 19, + "path": "alpha.py", + "qualname": "second", + "source": "def second():\n return 4" + } + ], + "ok": true, + "targets": [ + [ + "alpha.py", + "first" + ], + [ + "alpha.py", + "second" + ] + ] + }, + "candidate": { + "files": [ + { + "added_lines": [ + 2, + 20 + ], + "is_deleted": false, + "is_new": false, + "old_path": "alpha.py", + "path": "alpha.py", + "touched_lines": [ + 1, + 2, + 19, + 20 + ] + } + ], + "functions": [ + { + "changed_lines": [ + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "alpha.py", + "qualname": "first", + "source": "def first():\n return 2" + }, + { + "changed_lines": [ + 20 + ], + "end_lineno": 20, + "is_method": false, + "lineno": 19, + "path": "alpha.py", + "qualname": "second", + "source": "def second():\n return 4" + } + ], + "ok": true, + "targets": [ + [ + "alpha.py", + "first" + ], + [ + "alpha.py", + "second" + ] + ] + }, + "case": { + "expected_targets": [ + [ + "alpha.py", + "first" + ], + [ + "alpha.py", + "second" + ] + ], + "git_valid": true, + "name": "gnu_diff_u_timestamps", + "optional_envelope": false, + "patch": "/home/jacob/testpilot-git-compat-6cc6795e89f4/docs/receiving/git-compat-6cc6795e89f4/patches/gnu_diff_u_timestamps.diff", + "sha256": "90735a0300b765bbf11cb51beca006a26dfcb71f2b5e4c00be8ac262d2742066" + }, + "cli": { + "argv": [ + "/usr/bin/python3", + "-B", + "-m", + "testpilot", + "targets", + "--repo", + "/home/jacob/testpilot-git-compat-6cc6795e89f4-data/fixture", + "--diff", + "/home/jacob/testpilot-git-compat-6cc6795e89f4/docs/receiving/git-compat-6cc6795e89f4/patches/gnu_diff_u_timestamps.diff" + ], + "returncode": 0, + "stderr": "", + "stdout": "2 changed Python functions outside tests\n\"alpha.py\":1-2 \"first\"\n\"alpha.py\":19-20 \"second\"\n" + }, + "pass": true + } + ], + "python": "3.14.4 (main, Aug 20 2026, 10:41:58) [GCC 15.2.0]", + "summary": { + "documented_cases": 10, + "documented_cases_passed": 10, + "malformed_cli_cases": 4, + "malformed_cli_cases_passed": 4, + "negative_api_refuses": true, + "source_unchanged": { + "testpilot/__main__.py": true, + "testpilot/diff.py": true + }, + "valid_cases": 11, + "valid_cases_passed": 10 + }, + "worktree": "/home/jacob/testpilot-git-compat-6cc6795e89f4" +} diff --git a/docs/receiving/git-compat-6cc6795e89f4/independent-receiving-v3.json b/docs/receiving/git-compat-6cc6795e89f4/independent-receiving-v3.json new file mode 100644 index 0000000..7141e9e --- /dev/null +++ b/docs/receiving/git-compat-6cc6795e89f4/independent-receiving-v3.json @@ -0,0 +1,4113 @@ +{ + "candidate_source_commit": "d40d8fd054262b9aae4544d98d691568fd4e1dd3", + "expectations_sha256": "2b57e4d9767a2af3159417c29f34725daf23ebf2ca3ffd3b3ed23c8f7c496b6b", + "negative_api": { + "baseline": { + "files": [ + { + "added_lines": [ + 2, + 20, + 900 + ], + "is_deleted": false, + "is_new": false, + "old_path": "alpha.py", + "path": "alpha.py", + "touched_lines": [ + 1, + 2, + 19, + 20, + 899, + 900 + ] + } + ], + "functions": [ + { + "changed_lines": [ + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "alpha.py", + "qualname": "first", + "source": "def first():\n return 2" + }, + { + "changed_lines": [ + 20 + ], + "end_lineno": 20, + "is_method": false, + "lineno": 19, + "path": "alpha.py", + "qualname": "second", + "source": "def second():\n return 4" + } + ], + "ok": true, + "targets": [ + [ + "alpha.py", + "first" + ], + [ + "alpha.py", + "second" + ] + ] + }, + "candidate": { + "error_type": "DiffFormatError", + "message": "invalid unified diff at record 13: incomplete hunk (1 old and 1 new records missing)", + "ok": false, + "traceback": "Traceback (most recent call last):\n File \"/home/jacob/testpilot-git-compat-v3-6cc6795e89f4/docs/receiving/git-compat-6cc6795e89f4/receive_candidate_v3.py.txt\", line 40, in observe\n files = normalized_files(module,text)\n File \"/home/jacob/testpilot-git-compat-v3-6cc6795e89f4/docs/receiving/git-compat-6cc6795e89f4/receive_candidate_v3.py.txt\", line 33, in normalized_files\n for x in module.parse_unified_diff(text)]\n ~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^\n File \"/home/jacob/testpilot-git-compat-v3-6cc6795e89f4/testpilot/diff.py\", line 218, in parse_unified_diff\n raise invalid(f\"incomplete hunk ({old_left} old and {new_left} new records missing)\")\ntestpilot.diff.DiffFormatError: invalid unified diff at record 13: incomplete hunk (1 old and 1 new records missing)\n" + } + }, + "negative_cli": [ + { + "argv": [ + "/usr/bin/python3", + "-B", + "-m", + "testpilot", + "run", + "--repo", + "/home/jacob/testpilot-git-compat-6cc6795e89f4-data/fixture", + "--diff", + "/home/jacob/testpilot-git-compat-6cc6795e89f4/docs/receiving/git-compat-6cc6795e89f4/patches/malformed_unfinished_tail.diff", + "--script", + "/home/jacob/testpilot-git-compat-6cc6795e89f4-data/deliberately_missing_model_replies", + "--out", + "/home/jacob/testpilot-git-compat-6cc6795e89f4-data/out_v3_auto_new" + ], + "name": "auto_new", + "out_names": [], + "output_preserved": true, + "pass": true, + "pytest_tripwire_absent": true, + "returncode": 2, + "stderr": "testpilot: invalid diff format: invalid unified diff at record 13: incomplete hunk (1 old and 1 new records missing)\n", + "stdout": "" + }, + { + "argv": [ + "/usr/bin/python3", + "-B", + "-m", + "testpilot", + "run", + "--repo", + "/home/jacob/testpilot-git-compat-6cc6795e89f4-data/fixture", + "--diff", + "/home/jacob/testpilot-git-compat-6cc6795e89f4/docs/receiving/git-compat-6cc6795e89f4/patches/malformed_unfinished_tail.diff", + "--script", + "/home/jacob/testpilot-git-compat-6cc6795e89f4-data/deliberately_missing_model_replies", + "--out", + "/home/jacob/testpilot-git-compat-6cc6795e89f4-data/out_v3_auto_existing" + ], + "name": "auto_existing", + "out_names": [ + "report.html", + "report.json", + "report.md", + "testpilot.patch" + ], + "output_preserved": true, + "pass": true, + "pytest_tripwire_absent": true, + "returncode": 2, + "stderr": "testpilot: invalid diff format: invalid unified diff at record 13: incomplete hunk (1 old and 1 new records missing)\n", + "stdout": "" + }, + { + "argv": [ + "/usr/bin/python3", + "-B", + "-m", + "testpilot", + "run", + "--repo", + "/home/jacob/testpilot-git-compat-6cc6795e89f4-data/fixture", + "--diff", + "/home/jacob/testpilot-git-compat-6cc6795e89f4/docs/receiving/git-compat-6cc6795e89f4/patches/malformed_unfinished_tail.diff", + "--script", + "/home/jacob/testpilot-git-compat-6cc6795e89f4-data/deliberately_missing_model_replies", + "--out", + "/home/jacob/testpilot-git-compat-6cc6795e89f4-data/out_v3_explicit_new", + "--target", + "alpha.py::first" + ], + "name": "explicit_new", + "out_names": [], + "output_preserved": true, + "pass": true, + "pytest_tripwire_absent": true, + "returncode": 2, + "stderr": "testpilot: invalid target selection: cannot parse supplied diff: invalid unified diff at record 13: incomplete hunk (1 old and 1 new records missing)\n", + "stdout": "" + }, + { + "argv": [ + "/usr/bin/python3", + "-B", + "-m", + "testpilot", + "run", + "--repo", + "/home/jacob/testpilot-git-compat-6cc6795e89f4-data/fixture", + "--diff", + "/home/jacob/testpilot-git-compat-6cc6795e89f4/docs/receiving/git-compat-6cc6795e89f4/patches/malformed_unfinished_tail.diff", + "--script", + "/home/jacob/testpilot-git-compat-6cc6795e89f4-data/deliberately_missing_model_replies", + "--out", + "/home/jacob/testpilot-git-compat-6cc6795e89f4-data/out_v3_explicit_existing", + "--target", + "alpha.py::first" + ], + "name": "explicit_existing", + "out_names": [ + "report.html", + "report.json", + "report.md", + "testpilot.patch" + ], + "output_preserved": true, + "pass": true, + "pytest_tripwire_absent": true, + "returncode": 2, + "stderr": "testpilot: invalid target selection: cannot parse supplied diff: invalid unified diff at record 13: incomplete hunk (1 old and 1 new records missing)\n", + "stdout": "" + } + ], + "observed_at": "2026-10-08T17:48:23.748174+00:00", + "positive_cases": [ + { + "baseline": { + "files": [ + { + "added_lines": [ + 2, + 20 + ], + "is_deleted": false, + "is_new": false, + "old_path": "alpha.py", + "path": "alpha.py", + "touched_lines": [ + 1, + 2, + 19, + 20 + ] + }, + { + "added_lines": [ + 2 + ], + "is_deleted": false, + "is_new": false, + "old_path": "beta.py", + "path": "beta.py", + "touched_lines": [ + 1, + 2 + ] + }, + { + "added_lines": [ + 2 + ], + "is_deleted": false, + "is_new": false, + "old_path": "crlf.py", + "path": "crlf.py", + "touched_lines": [ + 1, + 2 + ] + }, + { + "added_lines": [], + "is_deleted": true, + "is_new": false, + "old_path": "deleted.py", + "path": "deleted.py", + "touched_lines": [ + 1 + ] + }, + { + "added_lines": [], + "is_deleted": false, + "is_new": false, + "old_path": "drop.py", + "path": "drop.py", + "touched_lines": [ + 2, + 3 + ] + }, + { + "added_lines": [ + 2 + ], + "is_deleted": false, + "is_new": false, + "old_path": "insert.py", + "path": "insert.py", + "touched_lines": [ + 2 + ] + }, + { + "added_lines": [ + 1, + 2 + ], + "is_deleted": false, + "is_new": true, + "old_path": null, + "path": "new.py", + "touched_lines": [ + 1, + 2 + ] + }, + { + "added_lines": [ + 2 + ], + "is_deleted": false, + "is_new": false, + "old_path": "odd\t\u00e4.py", + "path": "odd\t\u00e4.py", + "touched_lines": [ + 1, + 2 + ] + }, + { + "added_lines": [ + 2, + 3, + 4 + ], + "is_deleted": false, + "is_new": false, + "old_path": "payload.py", + "path": "payload.py", + "touched_lines": [ + 1, + 2, + 3, + 4 + ] + }, + { + "added_lines": [ + 2 + ], + "is_deleted": false, + "is_new": false, + "old_path": "unicode.py", + "path": "unicode.py", + "touched_lines": [ + 1, + 2 + ] + } + ], + "functions": [ + { + "changed_lines": [ + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "alpha.py", + "qualname": "first", + "source": "def first():\n return 2" + }, + { + "changed_lines": [ + 20 + ], + "end_lineno": 20, + "is_method": false, + "lineno": 19, + "path": "alpha.py", + "qualname": "second", + "source": "def second():\n return 4" + }, + { + "changed_lines": [ + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "beta.py", + "qualname": "beta", + "source": "def beta():\n return 11" + }, + { + "changed_lines": [ + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "crlf.py", + "qualname": "crlf_fn", + "source": "def crlf_fn():\n return 21" + }, + { + "changed_lines": [], + "end_lineno": 3, + "is_method": false, + "lineno": 1, + "path": "drop.py", + "qualname": "removedline", + "source": "def removedline():\n keep = 1\n return keep" + }, + { + "changed_lines": [ + 2 + ], + "end_lineno": 3, + "is_method": false, + "lineno": 1, + "path": "insert.py", + "qualname": "insertedline", + "source": "def insertedline():\n inserted = 50\n return 51" + }, + { + "changed_lines": [ + 1, + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "new.py", + "qualname": "gamma_unique", + "source": "def gamma_unique():\n return 'new-post-change-constant'" + }, + { + "changed_lines": [ + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "odd\t\u00e4.py", + "qualname": "odd_fn", + "source": "def odd_fn():\n return 41" + }, + { + "changed_lines": [ + 2, + 3, + 4 + ], + "end_lineno": 5, + "is_method": false, + "lineno": 1, + "path": "payload.py", + "qualname": "payload", + "source": "def payload():\n return \"\"\"new\n--- new literal\n+++ new literal\n\"\"\"" + }, + { + "changed_lines": [ + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "unicode.py", + "qualname": "unicode_fn", + "source": "def unicode_fn():\n return \"new\u2028tail\u2029end\"" + } + ], + "ok": true, + "targets": [ + [ + "alpha.py", + "first" + ], + [ + "alpha.py", + "second" + ], + [ + "beta.py", + "beta" + ], + [ + "crlf.py", + "crlf_fn" + ], + [ + "drop.py", + "removedline" + ], + [ + "insert.py", + "insertedline" + ], + [ + "new.py", + "gamma_unique" + ], + [ + "odd\t\u00e4.py", + "odd_fn" + ], + [ + "payload.py", + "payload" + ], + [ + "unicode.py", + "unicode_fn" + ] + ] + }, + "candidate": { + "files": [ + { + "added_lines": [ + 2, + 20 + ], + "is_deleted": false, + "is_new": false, + "old_path": "alpha.py", + "path": "alpha.py", + "touched_lines": [ + 1, + 2, + 19, + 20 + ] + }, + { + "added_lines": [ + 2 + ], + "is_deleted": false, + "is_new": false, + "old_path": "beta.py", + "path": "beta.py", + "touched_lines": [ + 1, + 2 + ] + }, + { + "added_lines": [ + 2 + ], + "is_deleted": false, + "is_new": false, + "old_path": "crlf.py", + "path": "crlf.py", + "touched_lines": [ + 1, + 2 + ] + }, + { + "added_lines": [], + "is_deleted": true, + "is_new": false, + "old_path": "deleted.py", + "path": "deleted.py", + "touched_lines": [ + 1 + ] + }, + { + "added_lines": [], + "is_deleted": false, + "is_new": false, + "old_path": "drop.py", + "path": "drop.py", + "touched_lines": [ + 2, + 3 + ] + }, + { + "added_lines": [ + 2 + ], + "is_deleted": false, + "is_new": false, + "old_path": "insert.py", + "path": "insert.py", + "touched_lines": [ + 2 + ] + }, + { + "added_lines": [ + 1, + 2 + ], + "is_deleted": false, + "is_new": true, + "old_path": null, + "path": "new.py", + "touched_lines": [ + 1, + 2 + ] + }, + { + "added_lines": [ + 2 + ], + "is_deleted": false, + "is_new": false, + "old_path": "odd\t\u00e4.py", + "path": "odd\t\u00e4.py", + "touched_lines": [ + 1, + 2 + ] + }, + { + "added_lines": [ + 2, + 3, + 4 + ], + "is_deleted": false, + "is_new": false, + "old_path": "payload.py", + "path": "payload.py", + "touched_lines": [ + 1, + 2, + 3, + 4 + ] + }, + { + "added_lines": [ + 2 + ], + "is_deleted": false, + "is_new": false, + "old_path": "unicode.py", + "path": "unicode.py", + "touched_lines": [ + 1, + 2 + ] + } + ], + "functions": [ + { + "changed_lines": [ + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "alpha.py", + "qualname": "first", + "source": "def first():\n return 2" + }, + { + "changed_lines": [ + 20 + ], + "end_lineno": 20, + "is_method": false, + "lineno": 19, + "path": "alpha.py", + "qualname": "second", + "source": "def second():\n return 4" + }, + { + "changed_lines": [ + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "beta.py", + "qualname": "beta", + "source": "def beta():\n return 11" + }, + { + "changed_lines": [ + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "crlf.py", + "qualname": "crlf_fn", + "source": "def crlf_fn():\n return 21" + }, + { + "changed_lines": [], + "end_lineno": 3, + "is_method": false, + "lineno": 1, + "path": "drop.py", + "qualname": "removedline", + "source": "def removedline():\n keep = 1\n return keep" + }, + { + "changed_lines": [ + 2 + ], + "end_lineno": 3, + "is_method": false, + "lineno": 1, + "path": "insert.py", + "qualname": "insertedline", + "source": "def insertedline():\n inserted = 50\n return 51" + }, + { + "changed_lines": [ + 1, + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "new.py", + "qualname": "gamma_unique", + "source": "def gamma_unique():\n return 'new-post-change-constant'" + }, + { + "changed_lines": [ + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "odd\t\u00e4.py", + "qualname": "odd_fn", + "source": "def odd_fn():\n return 41" + }, + { + "changed_lines": [ + 2, + 3, + 4 + ], + "end_lineno": 5, + "is_method": false, + "lineno": 1, + "path": "payload.py", + "qualname": "payload", + "source": "def payload():\n return \"\"\"new\n--- new literal\n+++ new literal\n\"\"\"" + }, + { + "changed_lines": [ + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "unicode.py", + "qualname": "unicode_fn", + "source": "def unicode_fn():\n return \"new\u2028tail\u2029end\"" + } + ], + "ok": true, + "targets": [ + [ + "alpha.py", + "first" + ], + [ + "alpha.py", + "second" + ], + [ + "beta.py", + "beta" + ], + [ + "crlf.py", + "crlf_fn" + ], + [ + "drop.py", + "removedline" + ], + [ + "insert.py", + "insertedline" + ], + [ + "new.py", + "gamma_unique" + ], + [ + "odd\t\u00e4.py", + "odd_fn" + ], + [ + "payload.py", + "payload" + ], + [ + "unicode.py", + "unicode_fn" + ] + ] + }, + "case": { + "expected_targets": [ + [ + "alpha.py", + "first" + ], + [ + "alpha.py", + "second" + ], + [ + "beta.py", + "beta" + ], + [ + "crlf.py", + "crlf_fn" + ], + [ + "drop.py", + "removedline" + ], + [ + "insert.py", + "insertedline" + ], + [ + "new.py", + "gamma_unique" + ], + [ + "odd\t\u00e4.py", + "odd_fn" + ], + [ + "payload.py", + "payload" + ], + [ + "unicode.py", + "unicode_fn" + ] + ], + "git_valid": true, + "name": "git_default", + "optional_envelope": false, + "patch": "/home/jacob/testpilot-git-compat-6cc6795e89f4/docs/receiving/git-compat-6cc6795e89f4/patches/git_default.diff", + "sha256": "e63cd0f0a8fc9f08aece050dfc5a92d5e19dac4b982f4c7787bf4f474572db0f" + }, + "cli": { + "argv": [ + "/usr/bin/python3", + "-B", + "-m", + "testpilot", + "targets", + "--repo", + "/home/jacob/testpilot-git-compat-6cc6795e89f4-data/fixture", + "--diff", + "/home/jacob/testpilot-git-compat-6cc6795e89f4/docs/receiving/git-compat-6cc6795e89f4/patches/git_default.diff" + ], + "returncode": 0, + "stderr": "", + "stdout": "10 changed Python functions outside tests\n\"alpha.py\":1-2 \"first\"\n\"alpha.py\":19-20 \"second\"\n\"beta.py\":1-2 \"beta\"\n\"crlf.py\":1-2 \"crlf_fn\"\n\"drop.py\":1-3 \"removedline\"\n\"insert.py\":1-3 \"insertedline\"\n\"new.py\":1-2 \"gamma_unique\"\n\"odd\\t\\u00e4.py\":1-2 \"odd_fn\"\n\"payload.py\":1-5 \"payload\"\n\"unicode.py\":1-2 \"unicode_fn\"\n" + }, + "pass": true + }, + { + "baseline": { + "files": [ + { + "added_lines": [ + 2, + 20 + ], + "is_deleted": false, + "is_new": false, + "old_path": "alpha.py", + "path": "alpha.py", + "touched_lines": [ + 1, + 2, + 19, + 20 + ] + }, + { + "added_lines": [ + 2 + ], + "is_deleted": false, + "is_new": false, + "old_path": "beta.py", + "path": "beta.py", + "touched_lines": [ + 1, + 2 + ] + }, + { + "added_lines": [ + 2 + ], + "is_deleted": false, + "is_new": false, + "old_path": "crlf.py", + "path": "crlf.py", + "touched_lines": [ + 1, + 2 + ] + }, + { + "added_lines": [], + "is_deleted": true, + "is_new": false, + "old_path": "deleted.py", + "path": "deleted.py", + "touched_lines": [ + 1 + ] + }, + { + "added_lines": [], + "is_deleted": false, + "is_new": false, + "old_path": "drop.py", + "path": "drop.py", + "touched_lines": [ + 2, + 3 + ] + }, + { + "added_lines": [ + 2 + ], + "is_deleted": false, + "is_new": false, + "old_path": "insert.py", + "path": "insert.py", + "touched_lines": [ + 2 + ] + }, + { + "added_lines": [ + 1, + 2 + ], + "is_deleted": false, + "is_new": true, + "old_path": null, + "path": "new.py", + "touched_lines": [ + 1, + 2 + ] + }, + { + "added_lines": [ + 2 + ], + "is_deleted": false, + "is_new": false, + "old_path": "odd\t\u00e4.py", + "path": "odd\t\u00e4.py", + "touched_lines": [ + 1, + 2 + ] + }, + { + "added_lines": [ + 2, + 3, + 4 + ], + "is_deleted": false, + "is_new": false, + "old_path": "payload.py", + "path": "payload.py", + "touched_lines": [ + 1, + 2, + 3, + 4 + ] + }, + { + "added_lines": [ + 2 + ], + "is_deleted": false, + "is_new": false, + "old_path": "unicode.py", + "path": "unicode.py", + "touched_lines": [ + 1, + 2 + ] + } + ], + "functions": [ + { + "changed_lines": [ + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "alpha.py", + "qualname": "first", + "source": "def first():\n return 2" + }, + { + "changed_lines": [ + 20 + ], + "end_lineno": 20, + "is_method": false, + "lineno": 19, + "path": "alpha.py", + "qualname": "second", + "source": "def second():\n return 4" + }, + { + "changed_lines": [ + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "beta.py", + "qualname": "beta", + "source": "def beta():\n return 11" + }, + { + "changed_lines": [ + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "crlf.py", + "qualname": "crlf_fn", + "source": "def crlf_fn():\n return 21" + }, + { + "changed_lines": [], + "end_lineno": 3, + "is_method": false, + "lineno": 1, + "path": "drop.py", + "qualname": "removedline", + "source": "def removedline():\n keep = 1\n return keep" + }, + { + "changed_lines": [ + 2 + ], + "end_lineno": 3, + "is_method": false, + "lineno": 1, + "path": "insert.py", + "qualname": "insertedline", + "source": "def insertedline():\n inserted = 50\n return 51" + }, + { + "changed_lines": [ + 1, + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "new.py", + "qualname": "gamma_unique", + "source": "def gamma_unique():\n return 'new-post-change-constant'" + }, + { + "changed_lines": [ + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "odd\t\u00e4.py", + "qualname": "odd_fn", + "source": "def odd_fn():\n return 41" + }, + { + "changed_lines": [ + 2, + 3, + 4 + ], + "end_lineno": 5, + "is_method": false, + "lineno": 1, + "path": "payload.py", + "qualname": "payload", + "source": "def payload():\n return \"\"\"new\n--- new literal\n+++ new literal\n\"\"\"" + }, + { + "changed_lines": [ + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "unicode.py", + "qualname": "unicode_fn", + "source": "def unicode_fn():\n return \"new\u2028tail\u2029end\"" + } + ], + "ok": true, + "targets": [ + [ + "alpha.py", + "first" + ], + [ + "alpha.py", + "second" + ], + [ + "beta.py", + "beta" + ], + [ + "crlf.py", + "crlf_fn" + ], + [ + "drop.py", + "removedline" + ], + [ + "insert.py", + "insertedline" + ], + [ + "new.py", + "gamma_unique" + ], + [ + "odd\t\u00e4.py", + "odd_fn" + ], + [ + "payload.py", + "payload" + ], + [ + "unicode.py", + "unicode_fn" + ] + ] + }, + "candidate": { + "files": [ + { + "added_lines": [ + 2, + 20 + ], + "is_deleted": false, + "is_new": false, + "old_path": "alpha.py", + "path": "alpha.py", + "touched_lines": [ + 1, + 2, + 19, + 20 + ] + }, + { + "added_lines": [ + 2 + ], + "is_deleted": false, + "is_new": false, + "old_path": "beta.py", + "path": "beta.py", + "touched_lines": [ + 1, + 2 + ] + }, + { + "added_lines": [ + 2 + ], + "is_deleted": false, + "is_new": false, + "old_path": "crlf.py", + "path": "crlf.py", + "touched_lines": [ + 1, + 2 + ] + }, + { + "added_lines": [], + "is_deleted": true, + "is_new": false, + "old_path": "deleted.py", + "path": "deleted.py", + "touched_lines": [ + 1 + ] + }, + { + "added_lines": [], + "is_deleted": false, + "is_new": false, + "old_path": "drop.py", + "path": "drop.py", + "touched_lines": [ + 2, + 3 + ] + }, + { + "added_lines": [ + 2 + ], + "is_deleted": false, + "is_new": false, + "old_path": "insert.py", + "path": "insert.py", + "touched_lines": [ + 2 + ] + }, + { + "added_lines": [ + 1, + 2 + ], + "is_deleted": false, + "is_new": true, + "old_path": null, + "path": "new.py", + "touched_lines": [ + 1, + 2 + ] + }, + { + "added_lines": [ + 2 + ], + "is_deleted": false, + "is_new": false, + "old_path": "odd\t\u00e4.py", + "path": "odd\t\u00e4.py", + "touched_lines": [ + 1, + 2 + ] + }, + { + "added_lines": [ + 2, + 3, + 4 + ], + "is_deleted": false, + "is_new": false, + "old_path": "payload.py", + "path": "payload.py", + "touched_lines": [ + 1, + 2, + 3, + 4 + ] + }, + { + "added_lines": [ + 2 + ], + "is_deleted": false, + "is_new": false, + "old_path": "unicode.py", + "path": "unicode.py", + "touched_lines": [ + 1, + 2 + ] + } + ], + "functions": [ + { + "changed_lines": [ + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "alpha.py", + "qualname": "first", + "source": "def first():\n return 2" + }, + { + "changed_lines": [ + 20 + ], + "end_lineno": 20, + "is_method": false, + "lineno": 19, + "path": "alpha.py", + "qualname": "second", + "source": "def second():\n return 4" + }, + { + "changed_lines": [ + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "beta.py", + "qualname": "beta", + "source": "def beta():\n return 11" + }, + { + "changed_lines": [ + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "crlf.py", + "qualname": "crlf_fn", + "source": "def crlf_fn():\n return 21" + }, + { + "changed_lines": [], + "end_lineno": 3, + "is_method": false, + "lineno": 1, + "path": "drop.py", + "qualname": "removedline", + "source": "def removedline():\n keep = 1\n return keep" + }, + { + "changed_lines": [ + 2 + ], + "end_lineno": 3, + "is_method": false, + "lineno": 1, + "path": "insert.py", + "qualname": "insertedline", + "source": "def insertedline():\n inserted = 50\n return 51" + }, + { + "changed_lines": [ + 1, + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "new.py", + "qualname": "gamma_unique", + "source": "def gamma_unique():\n return 'new-post-change-constant'" + }, + { + "changed_lines": [ + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "odd\t\u00e4.py", + "qualname": "odd_fn", + "source": "def odd_fn():\n return 41" + }, + { + "changed_lines": [ + 2, + 3, + 4 + ], + "end_lineno": 5, + "is_method": false, + "lineno": 1, + "path": "payload.py", + "qualname": "payload", + "source": "def payload():\n return \"\"\"new\n--- new literal\n+++ new literal\n\"\"\"" + }, + { + "changed_lines": [ + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "unicode.py", + "qualname": "unicode_fn", + "source": "def unicode_fn():\n return \"new\u2028tail\u2029end\"" + } + ], + "ok": true, + "targets": [ + [ + "alpha.py", + "first" + ], + [ + "alpha.py", + "second" + ], + [ + "beta.py", + "beta" + ], + [ + "crlf.py", + "crlf_fn" + ], + [ + "drop.py", + "removedline" + ], + [ + "insert.py", + "insertedline" + ], + [ + "new.py", + "gamma_unique" + ], + [ + "odd\t\u00e4.py", + "odd_fn" + ], + [ + "payload.py", + "payload" + ], + [ + "unicode.py", + "unicode_fn" + ] + ] + }, + "case": { + "expected_targets": [ + [ + "alpha.py", + "first" + ], + [ + "alpha.py", + "second" + ], + [ + "beta.py", + "beta" + ], + [ + "crlf.py", + "crlf_fn" + ], + [ + "drop.py", + "removedline" + ], + [ + "insert.py", + "insertedline" + ], + [ + "new.py", + "gamma_unique" + ], + [ + "odd\t\u00e4.py", + "odd_fn" + ], + [ + "payload.py", + "payload" + ], + [ + "unicode.py", + "unicode_fn" + ] + ], + "git_valid": true, + "name": "git_zero", + "optional_envelope": false, + "patch": "/home/jacob/testpilot-git-compat-6cc6795e89f4/docs/receiving/git-compat-6cc6795e89f4/patches/git_zero.diff", + "sha256": "0107f3f423d6f8e4150587ae8f649f8fb146176d01ddca27e37fbbd2b57e02d9" + }, + "cli": { + "argv": [ + "/usr/bin/python3", + "-B", + "-m", + "testpilot", + "targets", + "--repo", + "/home/jacob/testpilot-git-compat-6cc6795e89f4-data/fixture", + "--diff", + "/home/jacob/testpilot-git-compat-6cc6795e89f4/docs/receiving/git-compat-6cc6795e89f4/patches/git_zero.diff" + ], + "returncode": 0, + "stderr": "", + "stdout": "10 changed Python functions outside tests\n\"alpha.py\":1-2 \"first\"\n\"alpha.py\":19-20 \"second\"\n\"beta.py\":1-2 \"beta\"\n\"crlf.py\":1-2 \"crlf_fn\"\n\"drop.py\":1-3 \"removedline\"\n\"insert.py\":1-3 \"insertedline\"\n\"new.py\":1-2 \"gamma_unique\"\n\"odd\\t\\u00e4.py\":1-2 \"odd_fn\"\n\"payload.py\":1-5 \"payload\"\n\"unicode.py\":1-2 \"unicode_fn\"\n" + }, + "pass": true + }, + { + "baseline": { + "files": [ + { + "added_lines": [ + 2, + 20 + ], + "is_deleted": false, + "is_new": false, + "old_path": "alpha.py", + "path": "alpha.py", + "touched_lines": [ + 1, + 2, + 19, + 20 + ] + }, + { + "added_lines": [ + 2 + ], + "is_deleted": false, + "is_new": false, + "old_path": "beta.py", + "path": "beta.py", + "touched_lines": [ + 1, + 2 + ] + }, + { + "added_lines": [ + 2 + ], + "is_deleted": false, + "is_new": false, + "old_path": "crlf.py", + "path": "crlf.py", + "touched_lines": [ + 1, + 2 + ] + }, + { + "added_lines": [], + "is_deleted": true, + "is_new": false, + "old_path": "deleted.py", + "path": "deleted.py", + "touched_lines": [ + 1 + ] + }, + { + "added_lines": [], + "is_deleted": false, + "is_new": false, + "old_path": "drop.py", + "path": "drop.py", + "touched_lines": [ + 2, + 3 + ] + }, + { + "added_lines": [ + 2 + ], + "is_deleted": false, + "is_new": false, + "old_path": "insert.py", + "path": "insert.py", + "touched_lines": [ + 2 + ] + }, + { + "added_lines": [ + 1, + 2 + ], + "is_deleted": false, + "is_new": true, + "old_path": null, + "path": "new.py", + "touched_lines": [ + 1, + 2 + ] + }, + { + "added_lines": [ + 2 + ], + "is_deleted": false, + "is_new": false, + "old_path": "odd\t\u00e4.py", + "path": "odd\t\u00e4.py", + "touched_lines": [ + 1, + 2 + ] + }, + { + "added_lines": [ + 2, + 3, + 4 + ], + "is_deleted": false, + "is_new": false, + "old_path": "payload.py", + "path": "payload.py", + "touched_lines": [ + 1, + 2, + 3, + 4 + ] + }, + { + "added_lines": [ + 2 + ], + "is_deleted": false, + "is_new": false, + "old_path": "unicode.py", + "path": "unicode.py", + "touched_lines": [ + 1, + 2 + ] + } + ], + "functions": [ + { + "changed_lines": [ + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "alpha.py", + "qualname": "first", + "source": "def first():\n return 2" + }, + { + "changed_lines": [ + 20 + ], + "end_lineno": 20, + "is_method": false, + "lineno": 19, + "path": "alpha.py", + "qualname": "second", + "source": "def second():\n return 4" + }, + { + "changed_lines": [ + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "beta.py", + "qualname": "beta", + "source": "def beta():\n return 11" + }, + { + "changed_lines": [ + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "crlf.py", + "qualname": "crlf_fn", + "source": "def crlf_fn():\n return 21" + }, + { + "changed_lines": [], + "end_lineno": 3, + "is_method": false, + "lineno": 1, + "path": "drop.py", + "qualname": "removedline", + "source": "def removedline():\n keep = 1\n return keep" + }, + { + "changed_lines": [ + 2 + ], + "end_lineno": 3, + "is_method": false, + "lineno": 1, + "path": "insert.py", + "qualname": "insertedline", + "source": "def insertedline():\n inserted = 50\n return 51" + }, + { + "changed_lines": [ + 1, + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "new.py", + "qualname": "gamma_unique", + "source": "def gamma_unique():\n return 'new-post-change-constant'" + }, + { + "changed_lines": [ + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "odd\t\u00e4.py", + "qualname": "odd_fn", + "source": "def odd_fn():\n return 41" + }, + { + "changed_lines": [ + 2, + 3, + 4 + ], + "end_lineno": 5, + "is_method": false, + "lineno": 1, + "path": "payload.py", + "qualname": "payload", + "source": "def payload():\n return \"\"\"new\n--- new literal\n+++ new literal\n\"\"\"" + }, + { + "changed_lines": [ + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "unicode.py", + "qualname": "unicode_fn", + "source": "def unicode_fn():\n return \"new\u2028tail\u2029end\"" + } + ], + "ok": true, + "targets": [ + [ + "alpha.py", + "first" + ], + [ + "alpha.py", + "second" + ], + [ + "beta.py", + "beta" + ], + [ + "crlf.py", + "crlf_fn" + ], + [ + "drop.py", + "removedline" + ], + [ + "insert.py", + "insertedline" + ], + [ + "new.py", + "gamma_unique" + ], + [ + "odd\t\u00e4.py", + "odd_fn" + ], + [ + "payload.py", + "payload" + ], + [ + "unicode.py", + "unicode_fn" + ] + ] + }, + "candidate": { + "files": [ + { + "added_lines": [ + 2, + 20 + ], + "is_deleted": false, + "is_new": false, + "old_path": "alpha.py", + "path": "alpha.py", + "touched_lines": [ + 1, + 2, + 19, + 20 + ] + }, + { + "added_lines": [ + 2 + ], + "is_deleted": false, + "is_new": false, + "old_path": "beta.py", + "path": "beta.py", + "touched_lines": [ + 1, + 2 + ] + }, + { + "added_lines": [ + 2 + ], + "is_deleted": false, + "is_new": false, + "old_path": "crlf.py", + "path": "crlf.py", + "touched_lines": [ + 1, + 2 + ] + }, + { + "added_lines": [], + "is_deleted": true, + "is_new": false, + "old_path": "deleted.py", + "path": "deleted.py", + "touched_lines": [ + 1 + ] + }, + { + "added_lines": [], + "is_deleted": false, + "is_new": false, + "old_path": "drop.py", + "path": "drop.py", + "touched_lines": [ + 2, + 3 + ] + }, + { + "added_lines": [ + 2 + ], + "is_deleted": false, + "is_new": false, + "old_path": "insert.py", + "path": "insert.py", + "touched_lines": [ + 2 + ] + }, + { + "added_lines": [ + 1, + 2 + ], + "is_deleted": false, + "is_new": true, + "old_path": null, + "path": "new.py", + "touched_lines": [ + 1, + 2 + ] + }, + { + "added_lines": [ + 2 + ], + "is_deleted": false, + "is_new": false, + "old_path": "odd\t\u00e4.py", + "path": "odd\t\u00e4.py", + "touched_lines": [ + 1, + 2 + ] + }, + { + "added_lines": [ + 2, + 3, + 4 + ], + "is_deleted": false, + "is_new": false, + "old_path": "payload.py", + "path": "payload.py", + "touched_lines": [ + 1, + 2, + 3, + 4 + ] + }, + { + "added_lines": [ + 2 + ], + "is_deleted": false, + "is_new": false, + "old_path": "unicode.py", + "path": "unicode.py", + "touched_lines": [ + 1, + 2 + ] + } + ], + "functions": [ + { + "changed_lines": [ + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "alpha.py", + "qualname": "first", + "source": "def first():\n return 2" + }, + { + "changed_lines": [ + 20 + ], + "end_lineno": 20, + "is_method": false, + "lineno": 19, + "path": "alpha.py", + "qualname": "second", + "source": "def second():\n return 4" + }, + { + "changed_lines": [ + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "beta.py", + "qualname": "beta", + "source": "def beta():\n return 11" + }, + { + "changed_lines": [ + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "crlf.py", + "qualname": "crlf_fn", + "source": "def crlf_fn():\n return 21" + }, + { + "changed_lines": [], + "end_lineno": 3, + "is_method": false, + "lineno": 1, + "path": "drop.py", + "qualname": "removedline", + "source": "def removedline():\n keep = 1\n return keep" + }, + { + "changed_lines": [ + 2 + ], + "end_lineno": 3, + "is_method": false, + "lineno": 1, + "path": "insert.py", + "qualname": "insertedline", + "source": "def insertedline():\n inserted = 50\n return 51" + }, + { + "changed_lines": [ + 1, + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "new.py", + "qualname": "gamma_unique", + "source": "def gamma_unique():\n return 'new-post-change-constant'" + }, + { + "changed_lines": [ + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "odd\t\u00e4.py", + "qualname": "odd_fn", + "source": "def odd_fn():\n return 41" + }, + { + "changed_lines": [ + 2, + 3, + 4 + ], + "end_lineno": 5, + "is_method": false, + "lineno": 1, + "path": "payload.py", + "qualname": "payload", + "source": "def payload():\n return \"\"\"new\n--- new literal\n+++ new literal\n\"\"\"" + }, + { + "changed_lines": [ + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "unicode.py", + "qualname": "unicode_fn", + "source": "def unicode_fn():\n return \"new\u2028tail\u2029end\"" + } + ], + "ok": true, + "targets": [ + [ + "alpha.py", + "first" + ], + [ + "alpha.py", + "second" + ], + [ + "beta.py", + "beta" + ], + [ + "crlf.py", + "crlf_fn" + ], + [ + "drop.py", + "removedline" + ], + [ + "insert.py", + "insertedline" + ], + [ + "new.py", + "gamma_unique" + ], + [ + "odd\t\u00e4.py", + "odd_fn" + ], + [ + "payload.py", + "payload" + ], + [ + "unicode.py", + "unicode_fn" + ] + ] + }, + "case": { + "expected_targets": [ + [ + "alpha.py", + "first" + ], + [ + "alpha.py", + "second" + ], + [ + "beta.py", + "beta" + ], + [ + "crlf.py", + "crlf_fn" + ], + [ + "drop.py", + "removedline" + ], + [ + "insert.py", + "insertedline" + ], + [ + "new.py", + "gamma_unique" + ], + [ + "odd\t\u00e4.py", + "odd_fn" + ], + [ + "payload.py", + "payload" + ], + [ + "unicode.py", + "unicode_fn" + ] + ], + "git_valid": true, + "name": "git_one", + "optional_envelope": false, + "patch": "/home/jacob/testpilot-git-compat-6cc6795e89f4/docs/receiving/git-compat-6cc6795e89f4/patches/git_one.diff", + "sha256": "5cf9bc987e7021c0002aa89e22fc1af01dc42f3628b87745880c6aff839f9070" + }, + "cli": { + "argv": [ + "/usr/bin/python3", + "-B", + "-m", + "testpilot", + "targets", + "--repo", + "/home/jacob/testpilot-git-compat-6cc6795e89f4-data/fixture", + "--diff", + "/home/jacob/testpilot-git-compat-6cc6795e89f4/docs/receiving/git-compat-6cc6795e89f4/patches/git_one.diff" + ], + "returncode": 0, + "stderr": "", + "stdout": "10 changed Python functions outside tests\n\"alpha.py\":1-2 \"first\"\n\"alpha.py\":19-20 \"second\"\n\"beta.py\":1-2 \"beta\"\n\"crlf.py\":1-2 \"crlf_fn\"\n\"drop.py\":1-3 \"removedline\"\n\"insert.py\":1-3 \"insertedline\"\n\"new.py\":1-2 \"gamma_unique\"\n\"odd\\t\\u00e4.py\":1-2 \"odd_fn\"\n\"payload.py\":1-5 \"payload\"\n\"unicode.py\":1-2 \"unicode_fn\"\n" + }, + "pass": true + }, + { + "baseline": { + "files": [ + { + "added_lines": [ + 2, + 20 + ], + "is_deleted": false, + "is_new": false, + "old_path": "alpha.py", + "path": "alpha.py", + "touched_lines": [ + 1, + 2, + 19, + 20 + ] + }, + { + "added_lines": [ + 2 + ], + "is_deleted": false, + "is_new": false, + "old_path": "beta.py", + "path": "beta.py", + "touched_lines": [ + 1, + 2 + ] + }, + { + "added_lines": [ + 2 + ], + "is_deleted": false, + "is_new": false, + "old_path": "crlf.py", + "path": "crlf.py", + "touched_lines": [ + 1, + 2 + ] + }, + { + "added_lines": [], + "is_deleted": true, + "is_new": false, + "old_path": "deleted.py", + "path": "deleted.py", + "touched_lines": [ + 1 + ] + }, + { + "added_lines": [], + "is_deleted": false, + "is_new": false, + "old_path": "drop.py", + "path": "drop.py", + "touched_lines": [ + 2, + 3 + ] + }, + { + "added_lines": [ + 2 + ], + "is_deleted": false, + "is_new": false, + "old_path": "insert.py", + "path": "insert.py", + "touched_lines": [ + 2 + ] + }, + { + "added_lines": [ + 1, + 2 + ], + "is_deleted": false, + "is_new": true, + "old_path": null, + "path": "new.py", + "touched_lines": [ + 1, + 2 + ] + }, + { + "added_lines": [ + 2 + ], + "is_deleted": false, + "is_new": false, + "old_path": "odd\t\u00e4.py", + "path": "odd\t\u00e4.py", + "touched_lines": [ + 1, + 2 + ] + }, + { + "added_lines": [ + 2, + 3, + 4 + ], + "is_deleted": false, + "is_new": false, + "old_path": "payload.py", + "path": "payload.py", + "touched_lines": [ + 1, + 2, + 3, + 4 + ] + }, + { + "added_lines": [ + 2 + ], + "is_deleted": false, + "is_new": false, + "old_path": "unicode.py", + "path": "unicode.py", + "touched_lines": [ + 1, + 2 + ] + } + ], + "functions": [ + { + "changed_lines": [ + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "alpha.py", + "qualname": "first", + "source": "def first():\n return 2" + }, + { + "changed_lines": [ + 20 + ], + "end_lineno": 20, + "is_method": false, + "lineno": 19, + "path": "alpha.py", + "qualname": "second", + "source": "def second():\n return 4" + }, + { + "changed_lines": [ + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "beta.py", + "qualname": "beta", + "source": "def beta():\n return 11" + }, + { + "changed_lines": [ + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "crlf.py", + "qualname": "crlf_fn", + "source": "def crlf_fn():\n return 21" + }, + { + "changed_lines": [], + "end_lineno": 3, + "is_method": false, + "lineno": 1, + "path": "drop.py", + "qualname": "removedline", + "source": "def removedline():\n keep = 1\n return keep" + }, + { + "changed_lines": [ + 2 + ], + "end_lineno": 3, + "is_method": false, + "lineno": 1, + "path": "insert.py", + "qualname": "insertedline", + "source": "def insertedline():\n inserted = 50\n return 51" + }, + { + "changed_lines": [ + 1, + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "new.py", + "qualname": "gamma_unique", + "source": "def gamma_unique():\n return 'new-post-change-constant'" + }, + { + "changed_lines": [ + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "odd\t\u00e4.py", + "qualname": "odd_fn", + "source": "def odd_fn():\n return 41" + }, + { + "changed_lines": [ + 2, + 3, + 4 + ], + "end_lineno": 5, + "is_method": false, + "lineno": 1, + "path": "payload.py", + "qualname": "payload", + "source": "def payload():\n return \"\"\"new\n--- new literal\n+++ new literal\n\"\"\"" + }, + { + "changed_lines": [ + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "unicode.py", + "qualname": "unicode_fn", + "source": "def unicode_fn():\n return \"new\u2028tail\u2029end\"" + } + ], + "ok": true, + "targets": [ + [ + "alpha.py", + "first" + ], + [ + "alpha.py", + "second" + ], + [ + "beta.py", + "beta" + ], + [ + "crlf.py", + "crlf_fn" + ], + [ + "drop.py", + "removedline" + ], + [ + "insert.py", + "insertedline" + ], + [ + "new.py", + "gamma_unique" + ], + [ + "odd\t\u00e4.py", + "odd_fn" + ], + [ + "payload.py", + "payload" + ], + [ + "unicode.py", + "unicode_fn" + ] + ] + }, + "candidate": { + "files": [ + { + "added_lines": [ + 2, + 20 + ], + "is_deleted": false, + "is_new": false, + "old_path": "alpha.py", + "path": "alpha.py", + "touched_lines": [ + 1, + 2, + 19, + 20 + ] + }, + { + "added_lines": [ + 2 + ], + "is_deleted": false, + "is_new": false, + "old_path": "beta.py", + "path": "beta.py", + "touched_lines": [ + 1, + 2 + ] + }, + { + "added_lines": [ + 2 + ], + "is_deleted": false, + "is_new": false, + "old_path": "crlf.py", + "path": "crlf.py", + "touched_lines": [ + 1, + 2 + ] + }, + { + "added_lines": [], + "is_deleted": true, + "is_new": false, + "old_path": "deleted.py", + "path": "deleted.py", + "touched_lines": [ + 1 + ] + }, + { + "added_lines": [], + "is_deleted": false, + "is_new": false, + "old_path": "drop.py", + "path": "drop.py", + "touched_lines": [ + 2, + 3 + ] + }, + { + "added_lines": [ + 2 + ], + "is_deleted": false, + "is_new": false, + "old_path": "insert.py", + "path": "insert.py", + "touched_lines": [ + 2 + ] + }, + { + "added_lines": [ + 1, + 2 + ], + "is_deleted": false, + "is_new": true, + "old_path": null, + "path": "new.py", + "touched_lines": [ + 1, + 2 + ] + }, + { + "added_lines": [ + 2 + ], + "is_deleted": false, + "is_new": false, + "old_path": "odd\t\u00e4.py", + "path": "odd\t\u00e4.py", + "touched_lines": [ + 1, + 2 + ] + }, + { + "added_lines": [ + 2, + 3, + 4 + ], + "is_deleted": false, + "is_new": false, + "old_path": "payload.py", + "path": "payload.py", + "touched_lines": [ + 1, + 2, + 3, + 4 + ] + }, + { + "added_lines": [ + 2 + ], + "is_deleted": false, + "is_new": false, + "old_path": "unicode.py", + "path": "unicode.py", + "touched_lines": [ + 1, + 2 + ] + } + ], + "functions": [ + { + "changed_lines": [ + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "alpha.py", + "qualname": "first", + "source": "def first():\n return 2" + }, + { + "changed_lines": [ + 20 + ], + "end_lineno": 20, + "is_method": false, + "lineno": 19, + "path": "alpha.py", + "qualname": "second", + "source": "def second():\n return 4" + }, + { + "changed_lines": [ + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "beta.py", + "qualname": "beta", + "source": "def beta():\n return 11" + }, + { + "changed_lines": [ + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "crlf.py", + "qualname": "crlf_fn", + "source": "def crlf_fn():\n return 21" + }, + { + "changed_lines": [], + "end_lineno": 3, + "is_method": false, + "lineno": 1, + "path": "drop.py", + "qualname": "removedline", + "source": "def removedline():\n keep = 1\n return keep" + }, + { + "changed_lines": [ + 2 + ], + "end_lineno": 3, + "is_method": false, + "lineno": 1, + "path": "insert.py", + "qualname": "insertedline", + "source": "def insertedline():\n inserted = 50\n return 51" + }, + { + "changed_lines": [ + 1, + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "new.py", + "qualname": "gamma_unique", + "source": "def gamma_unique():\n return 'new-post-change-constant'" + }, + { + "changed_lines": [ + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "odd\t\u00e4.py", + "qualname": "odd_fn", + "source": "def odd_fn():\n return 41" + }, + { + "changed_lines": [ + 2, + 3, + 4 + ], + "end_lineno": 5, + "is_method": false, + "lineno": 1, + "path": "payload.py", + "qualname": "payload", + "source": "def payload():\n return \"\"\"new\n--- new literal\n+++ new literal\n\"\"\"" + }, + { + "changed_lines": [ + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "unicode.py", + "qualname": "unicode_fn", + "source": "def unicode_fn():\n return \"new\u2028tail\u2029end\"" + } + ], + "ok": true, + "targets": [ + [ + "alpha.py", + "first" + ], + [ + "alpha.py", + "second" + ], + [ + "beta.py", + "beta" + ], + [ + "crlf.py", + "crlf_fn" + ], + [ + "drop.py", + "removedline" + ], + [ + "insert.py", + "insertedline" + ], + [ + "new.py", + "gamma_unique" + ], + [ + "odd\t\u00e4.py", + "odd_fn" + ], + [ + "payload.py", + "payload" + ], + [ + "unicode.py", + "unicode_fn" + ] + ] + }, + "case": { + "expected_targets": [ + [ + "alpha.py", + "first" + ], + [ + "alpha.py", + "second" + ], + [ + "beta.py", + "beta" + ], + [ + "crlf.py", + "crlf_fn" + ], + [ + "drop.py", + "removedline" + ], + [ + "insert.py", + "insertedline" + ], + [ + "new.py", + "gamma_unique" + ], + [ + "odd\t\u00e4.py", + "odd_fn" + ], + [ + "payload.py", + "payload" + ], + [ + "unicode.py", + "unicode_fn" + ] + ], + "git_valid": true, + "name": "git_function", + "optional_envelope": false, + "patch": "/home/jacob/testpilot-git-compat-6cc6795e89f4/docs/receiving/git-compat-6cc6795e89f4/patches/git_function.diff", + "sha256": "9fc48c9a310a702951d64df8ff928fd2135b69373fa25bf8a0ab12c6788ab177" + }, + "cli": { + "argv": [ + "/usr/bin/python3", + "-B", + "-m", + "testpilot", + "targets", + "--repo", + "/home/jacob/testpilot-git-compat-6cc6795e89f4-data/fixture", + "--diff", + "/home/jacob/testpilot-git-compat-6cc6795e89f4/docs/receiving/git-compat-6cc6795e89f4/patches/git_function.diff" + ], + "returncode": 0, + "stderr": "", + "stdout": "10 changed Python functions outside tests\n\"alpha.py\":1-2 \"first\"\n\"alpha.py\":19-20 \"second\"\n\"beta.py\":1-2 \"beta\"\n\"crlf.py\":1-2 \"crlf_fn\"\n\"drop.py\":1-3 \"removedline\"\n\"insert.py\":1-3 \"insertedline\"\n\"new.py\":1-2 \"gamma_unique\"\n\"odd\\t\\u00e4.py\":1-2 \"odd_fn\"\n\"payload.py\":1-5 \"payload\"\n\"unicode.py\":1-2 \"unicode_fn\"\n" + }, + "pass": true + }, + { + "baseline": { + "files": [ + { + "added_lines": [ + 2, + 20 + ], + "is_deleted": false, + "is_new": false, + "old_path": "alpha.py", + "path": "alpha.py", + "touched_lines": [ + 1, + 2, + 19, + 20 + ] + } + ], + "functions": [ + { + "changed_lines": [ + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "alpha.py", + "qualname": "first", + "source": "def first():\n return 2" + }, + { + "changed_lines": [ + 20 + ], + "end_lineno": 20, + "is_method": false, + "lineno": 19, + "path": "alpha.py", + "qualname": "second", + "source": "def second():\n return 4" + } + ], + "ok": true, + "targets": [ + [ + "alpha.py", + "first" + ], + [ + "alpha.py", + "second" + ] + ] + }, + "candidate": { + "files": [ + { + "added_lines": [ + 2, + 20 + ], + "is_deleted": false, + "is_new": false, + "old_path": "alpha.py", + "path": "alpha.py", + "touched_lines": [ + 1, + 2, + 19, + 20 + ] + } + ], + "functions": [ + { + "changed_lines": [ + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "alpha.py", + "qualname": "first", + "source": "def first():\n return 2" + }, + { + "changed_lines": [ + 20 + ], + "end_lineno": 20, + "is_method": false, + "lineno": 19, + "path": "alpha.py", + "qualname": "second", + "source": "def second():\n return 4" + } + ], + "ok": true, + "targets": [ + [ + "alpha.py", + "first" + ], + [ + "alpha.py", + "second" + ] + ] + }, + "case": { + "expected_targets": [ + [ + "alpha.py", + "first" + ], + [ + "alpha.py", + "second" + ] + ], + "git_valid": true, + "name": "git_binary_python", + "optional_envelope": false, + "patch": "/home/jacob/testpilot-git-compat-6cc6795e89f4/docs/receiving/git-compat-6cc6795e89f4/patches/git_binary_python.diff", + "sha256": "0579a99651579908d9153bc12a0661e8b5081438055bdf3d5bab61a9cb787335" + }, + "cli": { + "argv": [ + "/usr/bin/python3", + "-B", + "-m", + "testpilot", + "targets", + "--repo", + "/home/jacob/testpilot-git-compat-6cc6795e89f4-data/fixture", + "--diff", + "/home/jacob/testpilot-git-compat-6cc6795e89f4/docs/receiving/git-compat-6cc6795e89f4/patches/git_binary_python.diff" + ], + "returncode": 0, + "stderr": "", + "stdout": "2 changed Python functions outside tests\n\"alpha.py\":1-2 \"first\"\n\"alpha.py\":19-20 \"second\"\n" + }, + "pass": true + }, + { + "baseline": { + "files": [], + "functions": [], + "ok": true, + "targets": [] + }, + "candidate": { + "files": [], + "functions": [], + "ok": true, + "targets": [] + }, + "case": { + "expected_targets": [], + "git_valid": true, + "name": "git_metadata_only", + "optional_envelope": false, + "patch": "/home/jacob/testpilot-git-compat-6cc6795e89f4/docs/receiving/git-compat-6cc6795e89f4/patches/git_metadata_only.diff", + "sha256": "5fc98cf71c98a4b1dab24b32c06820ea5b27e4088559e3adea93b0c474eba286" + }, + "cli": { + "argv": [ + "/usr/bin/python3", + "-B", + "-m", + "testpilot", + "targets", + "--repo", + "/home/jacob/testpilot-git-compat-6cc6795e89f4-data/fixture", + "--diff", + "/home/jacob/testpilot-git-compat-6cc6795e89f4/docs/receiving/git-compat-6cc6795e89f4/patches/git_metadata_only.diff" + ], + "returncode": 0, + "stderr": "", + "stdout": "0 changed Python functions outside tests\n" + }, + "pass": true + }, + { + "baseline": { + "files": [ + { + "added_lines": [ + 2 + ], + "is_deleted": false, + "is_new": false, + "old_path": "beta.py", + "path": "beta.py", + "touched_lines": [ + 1, + 2 + ] + } + ], + "functions": [ + { + "changed_lines": [ + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "beta.py", + "qualname": "beta", + "source": "def beta():\n return 11" + } + ], + "ok": true, + "targets": [ + [ + "beta.py", + "beta" + ] + ] + }, + "candidate": { + "files": [ + { + "added_lines": [ + 2 + ], + "is_deleted": false, + "is_new": false, + "old_path": "beta.py", + "path": "beta.py", + "touched_lines": [ + 1, + 2 + ] + } + ], + "functions": [ + { + "changed_lines": [ + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "beta.py", + "qualname": "beta", + "source": "def beta():\n return 11" + } + ], + "ok": true, + "targets": [ + [ + "beta.py", + "beta" + ] + ] + }, + "case": { + "expected_targets": [ + [ + "beta.py", + "beta" + ] + ], + "git_valid": true, + "name": "git_no_newline", + "optional_envelope": false, + "patch": "/home/jacob/testpilot-git-compat-6cc6795e89f4/docs/receiving/git-compat-6cc6795e89f4/patches/git_no_newline.diff", + "sha256": "3ba147b42f25628fc5ac2ec4f4cb73cae632127a75c8c009049ace99477210be" + }, + "cli": { + "argv": [ + "/usr/bin/python3", + "-B", + "-m", + "testpilot", + "targets", + "--repo", + "/home/jacob/testpilot-git-compat-6cc6795e89f4-data/fixture", + "--diff", + "/home/jacob/testpilot-git-compat-6cc6795e89f4/docs/receiving/git-compat-6cc6795e89f4/patches/git_no_newline.diff" + ], + "returncode": 0, + "stderr": "", + "stdout": "1 changed Python function outside tests\n\"beta.py\":1-2 \"beta\"\n" + }, + "pass": true + }, + { + "baseline": { + "files": [ + { + "added_lines": [ + 2 + ], + "is_deleted": false, + "is_new": false, + "old_path": "crlf.py", + "path": "crlf.py", + "touched_lines": [ + 1, + 2 + ] + } + ], + "functions": [ + { + "changed_lines": [ + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "crlf.py", + "qualname": "crlf_fn", + "source": "def crlf_fn():\n return 21" + } + ], + "ok": true, + "targets": [ + [ + "crlf.py", + "crlf_fn" + ] + ] + }, + "candidate": { + "files": [ + { + "added_lines": [ + 2 + ], + "is_deleted": false, + "is_new": false, + "old_path": "crlf.py", + "path": "crlf.py", + "touched_lines": [ + 1, + 2 + ] + } + ], + "functions": [ + { + "changed_lines": [ + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "crlf.py", + "qualname": "crlf_fn", + "source": "def crlf_fn():\n return 21" + } + ], + "ok": true, + "targets": [ + [ + "crlf.py", + "crlf_fn" + ] + ] + }, + "case": { + "expected_targets": [ + [ + "crlf.py", + "crlf_fn" + ] + ], + "git_valid": true, + "name": "git_crlf", + "optional_envelope": false, + "patch": "/home/jacob/testpilot-git-compat-6cc6795e89f4/docs/receiving/git-compat-6cc6795e89f4/patches/git_crlf.diff", + "sha256": "672b210a94fb9319e86d29d91027d732011c0cb34986539007d3012a4aa76ab6" + }, + "cli": { + "argv": [ + "/usr/bin/python3", + "-B", + "-m", + "testpilot", + "targets", + "--repo", + "/home/jacob/testpilot-git-compat-6cc6795e89f4-data/fixture", + "--diff", + "/home/jacob/testpilot-git-compat-6cc6795e89f4/docs/receiving/git-compat-6cc6795e89f4/patches/git_crlf.diff" + ], + "returncode": 0, + "stderr": "", + "stdout": "1 changed Python function outside tests\n\"crlf.py\":1-2 \"crlf_fn\"\n" + }, + "pass": true + }, + { + "baseline": { + "files": [ + { + "added_lines": [ + 2 + ], + "is_deleted": false, + "is_new": false, + "old_path": "odd\t\u00e4.py", + "path": "odd\t\u00e4.py", + "touched_lines": [ + 1, + 2 + ] + } + ], + "functions": [ + { + "changed_lines": [ + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "odd\t\u00e4.py", + "qualname": "odd_fn", + "source": "def odd_fn():\n return 41" + } + ], + "ok": true, + "targets": [ + [ + "odd\t\u00e4.py", + "odd_fn" + ] + ] + }, + "candidate": { + "files": [ + { + "added_lines": [ + 2 + ], + "is_deleted": false, + "is_new": false, + "old_path": "odd\t\u00e4.py", + "path": "odd\t\u00e4.py", + "touched_lines": [ + 1, + 2 + ] + } + ], + "functions": [ + { + "changed_lines": [ + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "odd\t\u00e4.py", + "qualname": "odd_fn", + "source": "def odd_fn():\n return 41" + } + ], + "ok": true, + "targets": [ + [ + "odd\t\u00e4.py", + "odd_fn" + ] + ] + }, + "case": { + "expected_targets": [ + [ + "odd\t\u00e4.py", + "odd_fn" + ] + ], + "git_valid": true, + "name": "git_quoted_path", + "optional_envelope": false, + "patch": "/home/jacob/testpilot-git-compat-6cc6795e89f4/docs/receiving/git-compat-6cc6795e89f4/patches/git_quoted_path.diff", + "sha256": "369169bf2ce73a58e47ff3af1e352a9a450f65fd8985ea1be9d74dde0ad859a9" + }, + "cli": { + "argv": [ + "/usr/bin/python3", + "-B", + "-m", + "testpilot", + "targets", + "--repo", + "/home/jacob/testpilot-git-compat-6cc6795e89f4-data/fixture", + "--diff", + "/home/jacob/testpilot-git-compat-6cc6795e89f4/docs/receiving/git-compat-6cc6795e89f4/patches/git_quoted_path.diff" + ], + "returncode": 0, + "stderr": "", + "stdout": "1 changed Python function outside tests\n\"odd\\t\\u00e4.py\":1-2 \"odd_fn\"\n" + }, + "pass": true + }, + { + "baseline": { + "files": [ + { + "added_lines": [ + 2, + 20 + ], + "is_deleted": false, + "is_new": false, + "old_path": "alpha.py", + "path": "alpha.py", + "touched_lines": [ + 1, + 2, + 19, + 20 + ] + }, + { + "added_lines": [ + 2 + ], + "is_deleted": false, + "is_new": false, + "old_path": "beta.py", + "path": "beta.py", + "touched_lines": [ + 1, + 2 + ] + }, + { + "added_lines": [ + 2 + ], + "is_deleted": false, + "is_new": false, + "old_path": "crlf.py", + "path": "crlf.py", + "touched_lines": [ + 1, + 2 + ] + }, + { + "added_lines": [], + "is_deleted": true, + "is_new": false, + "old_path": "deleted.py", + "path": "deleted.py", + "touched_lines": [ + 1 + ] + }, + { + "added_lines": [], + "is_deleted": false, + "is_new": false, + "old_path": "drop.py", + "path": "drop.py", + "touched_lines": [ + 2, + 3 + ] + }, + { + "added_lines": [ + 2 + ], + "is_deleted": false, + "is_new": false, + "old_path": "insert.py", + "path": "insert.py", + "touched_lines": [ + 2 + ] + }, + { + "added_lines": [ + 1, + 2 + ], + "is_deleted": false, + "is_new": true, + "old_path": null, + "path": "new.py", + "touched_lines": [ + 1, + 2 + ] + }, + { + "added_lines": [ + 2 + ], + "is_deleted": false, + "is_new": false, + "old_path": "odd\t\u00e4.py", + "path": "odd\t\u00e4.py", + "touched_lines": [ + 1, + 2 + ] + }, + { + "added_lines": [ + 2, + 3, + 4 + ], + "is_deleted": false, + "is_new": false, + "old_path": "payload.py", + "path": "payload.py", + "touched_lines": [ + 1, + 2, + 3, + 4 + ] + }, + { + "added_lines": [ + 2 + ], + "is_deleted": false, + "is_new": false, + "old_path": "unicode.py", + "path": "unicode.py", + "touched_lines": [ + 1, + 2 + ] + } + ], + "functions": [ + { + "changed_lines": [ + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "alpha.py", + "qualname": "first", + "source": "def first():\n return 2" + }, + { + "changed_lines": [ + 20 + ], + "end_lineno": 20, + "is_method": false, + "lineno": 19, + "path": "alpha.py", + "qualname": "second", + "source": "def second():\n return 4" + }, + { + "changed_lines": [ + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "beta.py", + "qualname": "beta", + "source": "def beta():\n return 11" + }, + { + "changed_lines": [ + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "crlf.py", + "qualname": "crlf_fn", + "source": "def crlf_fn():\n return 21" + }, + { + "changed_lines": [], + "end_lineno": 3, + "is_method": false, + "lineno": 1, + "path": "drop.py", + "qualname": "removedline", + "source": "def removedline():\n keep = 1\n return keep" + }, + { + "changed_lines": [ + 2 + ], + "end_lineno": 3, + "is_method": false, + "lineno": 1, + "path": "insert.py", + "qualname": "insertedline", + "source": "def insertedline():\n inserted = 50\n return 51" + }, + { + "changed_lines": [ + 1, + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "new.py", + "qualname": "gamma_unique", + "source": "def gamma_unique():\n return 'new-post-change-constant'" + }, + { + "changed_lines": [ + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "odd\t\u00e4.py", + "qualname": "odd_fn", + "source": "def odd_fn():\n return 41" + }, + { + "changed_lines": [ + 2, + 3, + 4 + ], + "end_lineno": 5, + "is_method": false, + "lineno": 1, + "path": "payload.py", + "qualname": "payload", + "source": "def payload():\n return \"\"\"new\n--- new literal\n+++ new literal\n\"\"\"" + }, + { + "changed_lines": [ + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "unicode.py", + "qualname": "unicode_fn", + "source": "def unicode_fn():\n return \"new\u2028tail\u2029end\"" + } + ], + "ok": true, + "targets": [ + [ + "alpha.py", + "first" + ], + [ + "alpha.py", + "second" + ], + [ + "beta.py", + "beta" + ], + [ + "crlf.py", + "crlf_fn" + ], + [ + "drop.py", + "removedline" + ], + [ + "insert.py", + "insertedline" + ], + [ + "new.py", + "gamma_unique" + ], + [ + "odd\t\u00e4.py", + "odd_fn" + ], + [ + "payload.py", + "payload" + ], + [ + "unicode.py", + "unicode_fn" + ] + ] + }, + "candidate": { + "files": [ + { + "added_lines": [ + 2, + 20 + ], + "is_deleted": false, + "is_new": false, + "old_path": "alpha.py", + "path": "alpha.py", + "touched_lines": [ + 1, + 2, + 19, + 20 + ] + }, + { + "added_lines": [ + 2 + ], + "is_deleted": false, + "is_new": false, + "old_path": "beta.py", + "path": "beta.py", + "touched_lines": [ + 1, + 2 + ] + }, + { + "added_lines": [ + 2 + ], + "is_deleted": false, + "is_new": false, + "old_path": "crlf.py", + "path": "crlf.py", + "touched_lines": [ + 1, + 2 + ] + }, + { + "added_lines": [], + "is_deleted": true, + "is_new": false, + "old_path": "deleted.py", + "path": "deleted.py", + "touched_lines": [ + 1 + ] + }, + { + "added_lines": [], + "is_deleted": false, + "is_new": false, + "old_path": "drop.py", + "path": "drop.py", + "touched_lines": [ + 2, + 3 + ] + }, + { + "added_lines": [ + 2 + ], + "is_deleted": false, + "is_new": false, + "old_path": "insert.py", + "path": "insert.py", + "touched_lines": [ + 2 + ] + }, + { + "added_lines": [ + 1, + 2 + ], + "is_deleted": false, + "is_new": true, + "old_path": null, + "path": "new.py", + "touched_lines": [ + 1, + 2 + ] + }, + { + "added_lines": [ + 2 + ], + "is_deleted": false, + "is_new": false, + "old_path": "odd\t\u00e4.py", + "path": "odd\t\u00e4.py", + "touched_lines": [ + 1, + 2 + ] + }, + { + "added_lines": [ + 2, + 3, + 4 + ], + "is_deleted": false, + "is_new": false, + "old_path": "payload.py", + "path": "payload.py", + "touched_lines": [ + 1, + 2, + 3, + 4 + ] + }, + { + "added_lines": [ + 2 + ], + "is_deleted": false, + "is_new": false, + "old_path": "unicode.py", + "path": "unicode.py", + "touched_lines": [ + 1, + 2 + ] + } + ], + "functions": [ + { + "changed_lines": [ + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "alpha.py", + "qualname": "first", + "source": "def first():\n return 2" + }, + { + "changed_lines": [ + 20 + ], + "end_lineno": 20, + "is_method": false, + "lineno": 19, + "path": "alpha.py", + "qualname": "second", + "source": "def second():\n return 4" + }, + { + "changed_lines": [ + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "beta.py", + "qualname": "beta", + "source": "def beta():\n return 11" + }, + { + "changed_lines": [ + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "crlf.py", + "qualname": "crlf_fn", + "source": "def crlf_fn():\n return 21" + }, + { + "changed_lines": [], + "end_lineno": 3, + "is_method": false, + "lineno": 1, + "path": "drop.py", + "qualname": "removedline", + "source": "def removedline():\n keep = 1\n return keep" + }, + { + "changed_lines": [ + 2 + ], + "end_lineno": 3, + "is_method": false, + "lineno": 1, + "path": "insert.py", + "qualname": "insertedline", + "source": "def insertedline():\n inserted = 50\n return 51" + }, + { + "changed_lines": [ + 1, + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "new.py", + "qualname": "gamma_unique", + "source": "def gamma_unique():\n return 'new-post-change-constant'" + }, + { + "changed_lines": [ + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "odd\t\u00e4.py", + "qualname": "odd_fn", + "source": "def odd_fn():\n return 41" + }, + { + "changed_lines": [ + 2, + 3, + 4 + ], + "end_lineno": 5, + "is_method": false, + "lineno": 1, + "path": "payload.py", + "qualname": "payload", + "source": "def payload():\n return \"\"\"new\n--- new literal\n+++ new literal\n\"\"\"" + }, + { + "changed_lines": [ + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "unicode.py", + "qualname": "unicode_fn", + "source": "def unicode_fn():\n return \"new\u2028tail\u2029end\"" + } + ], + "ok": true, + "targets": [ + [ + "alpha.py", + "first" + ], + [ + "alpha.py", + "second" + ], + [ + "beta.py", + "beta" + ], + [ + "crlf.py", + "crlf_fn" + ], + [ + "drop.py", + "removedline" + ], + [ + "insert.py", + "insertedline" + ], + [ + "new.py", + "gamma_unique" + ], + [ + "odd\t\u00e4.py", + "odd_fn" + ], + [ + "payload.py", + "payload" + ], + [ + "unicode.py", + "unicode_fn" + ] + ] + }, + "case": { + "expected_targets": [ + [ + "alpha.py", + "first" + ], + [ + "alpha.py", + "second" + ], + [ + "beta.py", + "beta" + ], + [ + "crlf.py", + "crlf_fn" + ], + [ + "drop.py", + "removedline" + ], + [ + "insert.py", + "insertedline" + ], + [ + "new.py", + "gamma_unique" + ], + [ + "odd\t\u00e4.py", + "odd_fn" + ], + [ + "payload.py", + "payload" + ], + [ + "unicode.py", + "unicode_fn" + ] + ], + "git_valid": true, + "name": "git_mail_envelope", + "optional_envelope": true, + "patch": "/home/jacob/testpilot-git-compat-6cc6795e89f4/docs/receiving/git-compat-6cc6795e89f4/patches/git_mail_envelope.diff", + "sha256": "c997fa37a532088118d9caee74cad1a589ce0b0948c9653b733cbde7a3a2c21e" + }, + "cli": { + "argv": [ + "/usr/bin/python3", + "-B", + "-m", + "testpilot", + "targets", + "--repo", + "/home/jacob/testpilot-git-compat-6cc6795e89f4-data/fixture", + "--diff", + "/home/jacob/testpilot-git-compat-6cc6795e89f4/docs/receiving/git-compat-6cc6795e89f4/patches/git_mail_envelope.diff" + ], + "returncode": 0, + "stderr": "", + "stdout": "10 changed Python functions outside tests\n\"alpha.py\":1-2 \"first\"\n\"alpha.py\":19-20 \"second\"\n\"beta.py\":1-2 \"beta\"\n\"crlf.py\":1-2 \"crlf_fn\"\n\"drop.py\":1-3 \"removedline\"\n\"insert.py\":1-3 \"insertedline\"\n\"new.py\":1-2 \"gamma_unique\"\n\"odd\\t\\u00e4.py\":1-2 \"odd_fn\"\n\"payload.py\":1-5 \"payload\"\n\"unicode.py\":1-2 \"unicode_fn\"\n" + }, + "pass": true + }, + { + "baseline": { + "files": [ + { + "added_lines": [ + 2, + 20 + ], + "is_deleted": false, + "is_new": false, + "old_path": "alpha.py", + "path": "alpha.py", + "touched_lines": [ + 1, + 2, + 19, + 20 + ] + } + ], + "functions": [ + { + "changed_lines": [ + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "alpha.py", + "qualname": "first", + "source": "def first():\n return 2" + }, + { + "changed_lines": [ + 20 + ], + "end_lineno": 20, + "is_method": false, + "lineno": 19, + "path": "alpha.py", + "qualname": "second", + "source": "def second():\n return 4" + } + ], + "ok": true, + "targets": [ + [ + "alpha.py", + "first" + ], + [ + "alpha.py", + "second" + ] + ] + }, + "candidate": { + "files": [ + { + "added_lines": [ + 2, + 20 + ], + "is_deleted": false, + "is_new": false, + "old_path": "alpha.py", + "path": "alpha.py", + "touched_lines": [ + 1, + 2, + 19, + 20 + ] + } + ], + "functions": [ + { + "changed_lines": [ + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "alpha.py", + "qualname": "first", + "source": "def first():\n return 2" + }, + { + "changed_lines": [ + 20 + ], + "end_lineno": 20, + "is_method": false, + "lineno": 19, + "path": "alpha.py", + "qualname": "second", + "source": "def second():\n return 4" + } + ], + "ok": true, + "targets": [ + [ + "alpha.py", + "first" + ], + [ + "alpha.py", + "second" + ] + ] + }, + "case": { + "expected_targets": [ + [ + "alpha.py", + "first" + ], + [ + "alpha.py", + "second" + ] + ], + "git_valid": true, + "name": "gnu_diff_u_timestamps", + "optional_envelope": false, + "patch": "/home/jacob/testpilot-git-compat-6cc6795e89f4/docs/receiving/git-compat-6cc6795e89f4/patches/gnu_diff_u_timestamps.diff", + "sha256": "90735a0300b765bbf11cb51beca006a26dfcb71f2b5e4c00be8ac262d2742066" + }, + "cli": { + "argv": [ + "/usr/bin/python3", + "-B", + "-m", + "testpilot", + "targets", + "--repo", + "/home/jacob/testpilot-git-compat-6cc6795e89f4-data/fixture", + "--diff", + "/home/jacob/testpilot-git-compat-6cc6795e89f4/docs/receiving/git-compat-6cc6795e89f4/patches/gnu_diff_u_timestamps.diff" + ], + "returncode": 0, + "stderr": "", + "stdout": "2 changed Python functions outside tests\n\"alpha.py\":1-2 \"first\"\n\"alpha.py\":19-20 \"second\"\n" + }, + "pass": true + } + ], + "python": "3.14.4 (main, Aug 20 2026, 10:41:58) [GCC 15.2.0]", + "summary": { + "documented_cases": 10, + "documented_cases_passed": 10, + "malformed_cli_cases": 4, + "malformed_cli_cases_passed": 4, + "negative_api_refuses": true, + "source_unchanged": { + "testpilot/__main__.py": true, + "testpilot/diff.py": true + }, + "valid_cases": 11, + "valid_cases_passed": 11 + }, + "worktree": "/home/jacob/testpilot-git-compat-v3-6cc6795e89f4" +} diff --git a/docs/receiving/git-compat-6cc6795e89f4/independent-receiving.json b/docs/receiving/git-compat-6cc6795e89f4/independent-receiving.json new file mode 100644 index 0000000..2bf532e --- /dev/null +++ b/docs/receiving/git-compat-6cc6795e89f4/independent-receiving.json @@ -0,0 +1,3827 @@ +{ + "candidate_source_commit": "9265ae42bde5b6bd559584a11cbb7c30b7849743", + "expectations_sha256": "2b57e4d9767a2af3159417c29f34725daf23ebf2ca3ffd3b3ed23c8f7c496b6b", + "negative_api": { + "baseline": { + "files": [ + { + "added_lines": [ + 2, + 20, + 900 + ], + "is_deleted": false, + "is_new": false, + "old_path": "alpha.py", + "path": "alpha.py", + "touched_lines": [ + 1, + 2, + 19, + 20, + 899, + 900 + ] + } + ], + "functions": [ + { + "changed_lines": [ + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "alpha.py", + "qualname": "first", + "source": "def first():\n return 2" + }, + { + "changed_lines": [ + 20 + ], + "end_lineno": 20, + "is_method": false, + "lineno": 19, + "path": "alpha.py", + "qualname": "second", + "source": "def second():\n return 4" + } + ], + "ok": true, + "targets": [ + [ + "alpha.py", + "first" + ], + [ + "alpha.py", + "second" + ] + ] + }, + "candidate": { + "error_type": "DiffFormatError", + "message": "invalid unified diff at record 13: incomplete hunk (1 old and 1 new records missing)", + "ok": false, + "traceback": "Traceback (most recent call last):\n File \"/home/jacob/testpilot-git-compat-6cc6795e89f4/docs/receiving/git-compat-6cc6795e89f4/receive_candidate.py.txt\", line 40, in observe\n files = normalized_files(module,text)\n File \"/home/jacob/testpilot-git-compat-6cc6795e89f4/docs/receiving/git-compat-6cc6795e89f4/receive_candidate.py.txt\", line 33, in normalized_files\n for x in module.parse_unified_diff(text)]\n ~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^\n File \"/home/jacob/testpilot-git-compat-6cc6795e89f4/testpilot/diff.py\", line 202, in parse_unified_diff\n raise invalid(f\"incomplete hunk ({old_left} old and {new_left} new records missing)\")\ntestpilot.diff.DiffFormatError: invalid unified diff at record 13: incomplete hunk (1 old and 1 new records missing)\n" + } + }, + "negative_cli": [ + { + "argv": [ + "/usr/bin/python3", + "-B", + "-m", + "testpilot", + "run", + "--repo", + "/home/jacob/testpilot-git-compat-6cc6795e89f4-data/fixture", + "--diff", + "/home/jacob/testpilot-git-compat-6cc6795e89f4/docs/receiving/git-compat-6cc6795e89f4/patches/malformed_unfinished_tail.diff", + "--script", + "/home/jacob/testpilot-git-compat-6cc6795e89f4-data/deliberately_missing_model_replies", + "--out", + "/home/jacob/testpilot-git-compat-6cc6795e89f4-data/out_auto_new" + ], + "name": "auto_new", + "out_names": [], + "output_preserved": true, + "pass": false, + "pytest_tripwire_absent": true, + "returncode": 1, + "stderr": "Traceback (most recent call last):\n File \"\", line 198, in _run_module_as_main\n File \"\", line 88, in _run_code\n File \"/home/jacob/testpilot-git-compat-6cc6795e89f4/testpilot/__main__.py\", line 154, in \n raise SystemExit(main())\n ~~~~^^\n File \"/home/jacob/testpilot-git-compat-6cc6795e89f4/testpilot/__main__.py\", line 129, in main\n client = make_client(a.backend, script=a.script, base_url=a.base_url)\n File \"/home/jacob/testpilot-git-compat-6cc6795e89f4/testpilot/model.py\", line 235, in make_client\n return ScriptedModel.from_dir(script)\n ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^\n File \"/home/jacob/testpilot-git-compat-6cc6795e89f4/testpilot/model.py\", line 94, in from_dir\n files = sorted(p for p in Path(path).iterdir() if p.suffix in (\".md\", \".txt\") and p.is_file())\n ~~~~~~~~~~~~~~~~~~^^\n File \"/usr/lib/python3.14/pathlib/__init__.py\", line 837, in iterdir\n with os.scandir(root_dir) as scandir_it:\n ~~~~~~~~~~^^^^^^^^^^\nFileNotFoundError: [Errno 2] No such file or directory: '/home/jacob/testpilot-git-compat-6cc6795e89f4-data/deliberately_missing_model_replies'\n", + "stdout": "" + }, + { + "argv": [ + "/usr/bin/python3", + "-B", + "-m", + "testpilot", + "run", + "--repo", + "/home/jacob/testpilot-git-compat-6cc6795e89f4-data/fixture", + "--diff", + "/home/jacob/testpilot-git-compat-6cc6795e89f4/docs/receiving/git-compat-6cc6795e89f4/patches/malformed_unfinished_tail.diff", + "--script", + "/home/jacob/testpilot-git-compat-6cc6795e89f4-data/deliberately_missing_model_replies", + "--out", + "/home/jacob/testpilot-git-compat-6cc6795e89f4-data/out_auto_existing" + ], + "name": "auto_existing", + "out_names": [ + "report.html", + "report.json", + "report.md", + "testpilot.patch" + ], + "output_preserved": true, + "pass": false, + "pytest_tripwire_absent": true, + "returncode": 1, + "stderr": "Traceback (most recent call last):\n File \"\", line 198, in _run_module_as_main\n File \"\", line 88, in _run_code\n File \"/home/jacob/testpilot-git-compat-6cc6795e89f4/testpilot/__main__.py\", line 154, in \n raise SystemExit(main())\n ~~~~^^\n File \"/home/jacob/testpilot-git-compat-6cc6795e89f4/testpilot/__main__.py\", line 129, in main\n client = make_client(a.backend, script=a.script, base_url=a.base_url)\n File \"/home/jacob/testpilot-git-compat-6cc6795e89f4/testpilot/model.py\", line 235, in make_client\n return ScriptedModel.from_dir(script)\n ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^\n File \"/home/jacob/testpilot-git-compat-6cc6795e89f4/testpilot/model.py\", line 94, in from_dir\n files = sorted(p for p in Path(path).iterdir() if p.suffix in (\".md\", \".txt\") and p.is_file())\n ~~~~~~~~~~~~~~~~~~^^\n File \"/usr/lib/python3.14/pathlib/__init__.py\", line 837, in iterdir\n with os.scandir(root_dir) as scandir_it:\n ~~~~~~~~~~^^^^^^^^^^\nFileNotFoundError: [Errno 2] No such file or directory: '/home/jacob/testpilot-git-compat-6cc6795e89f4-data/deliberately_missing_model_replies'\n", + "stdout": "" + }, + { + "argv": [ + "/usr/bin/python3", + "-B", + "-m", + "testpilot", + "run", + "--repo", + "/home/jacob/testpilot-git-compat-6cc6795e89f4-data/fixture", + "--diff", + "/home/jacob/testpilot-git-compat-6cc6795e89f4/docs/receiving/git-compat-6cc6795e89f4/patches/malformed_unfinished_tail.diff", + "--script", + "/home/jacob/testpilot-git-compat-6cc6795e89f4-data/deliberately_missing_model_replies", + "--out", + "/home/jacob/testpilot-git-compat-6cc6795e89f4-data/out_explicit_new", + "--target", + "alpha.py::first" + ], + "name": "explicit_new", + "out_names": [], + "output_preserved": true, + "pass": true, + "pytest_tripwire_absent": true, + "returncode": 2, + "stderr": "testpilot: invalid target selection: cannot parse supplied diff: invalid unified diff at record 13: incomplete hunk (1 old and 1 new records missing)\n", + "stdout": "" + }, + { + "argv": [ + "/usr/bin/python3", + "-B", + "-m", + "testpilot", + "run", + "--repo", + "/home/jacob/testpilot-git-compat-6cc6795e89f4-data/fixture", + "--diff", + "/home/jacob/testpilot-git-compat-6cc6795e89f4/docs/receiving/git-compat-6cc6795e89f4/patches/malformed_unfinished_tail.diff", + "--script", + "/home/jacob/testpilot-git-compat-6cc6795e89f4-data/deliberately_missing_model_replies", + "--out", + "/home/jacob/testpilot-git-compat-6cc6795e89f4-data/out_explicit_existing", + "--target", + "alpha.py::first" + ], + "name": "explicit_existing", + "out_names": [ + "report.html", + "report.json", + "report.md", + "testpilot.patch" + ], + "output_preserved": true, + "pass": true, + "pytest_tripwire_absent": true, + "returncode": 2, + "stderr": "testpilot: invalid target selection: cannot parse supplied diff: invalid unified diff at record 13: incomplete hunk (1 old and 1 new records missing)\n", + "stdout": "" + } + ], + "observed_at": "2026-10-08T17:23:33.674123+00:00", + "positive_cases": [ + { + "baseline": { + "files": [ + { + "added_lines": [ + 2, + 20 + ], + "is_deleted": false, + "is_new": false, + "old_path": "alpha.py", + "path": "alpha.py", + "touched_lines": [ + 1, + 2, + 19, + 20 + ] + }, + { + "added_lines": [ + 2 + ], + "is_deleted": false, + "is_new": false, + "old_path": "beta.py", + "path": "beta.py", + "touched_lines": [ + 1, + 2 + ] + }, + { + "added_lines": [ + 2 + ], + "is_deleted": false, + "is_new": false, + "old_path": "crlf.py", + "path": "crlf.py", + "touched_lines": [ + 1, + 2 + ] + }, + { + "added_lines": [], + "is_deleted": true, + "is_new": false, + "old_path": "deleted.py", + "path": "deleted.py", + "touched_lines": [ + 1 + ] + }, + { + "added_lines": [], + "is_deleted": false, + "is_new": false, + "old_path": "drop.py", + "path": "drop.py", + "touched_lines": [ + 2, + 3 + ] + }, + { + "added_lines": [ + 2 + ], + "is_deleted": false, + "is_new": false, + "old_path": "insert.py", + "path": "insert.py", + "touched_lines": [ + 2 + ] + }, + { + "added_lines": [ + 1, + 2 + ], + "is_deleted": false, + "is_new": true, + "old_path": null, + "path": "new.py", + "touched_lines": [ + 1, + 2 + ] + }, + { + "added_lines": [ + 2 + ], + "is_deleted": false, + "is_new": false, + "old_path": "odd\t\u00e4.py", + "path": "odd\t\u00e4.py", + "touched_lines": [ + 1, + 2 + ] + }, + { + "added_lines": [ + 2, + 3, + 4 + ], + "is_deleted": false, + "is_new": false, + "old_path": "payload.py", + "path": "payload.py", + "touched_lines": [ + 1, + 2, + 3, + 4 + ] + }, + { + "added_lines": [ + 2 + ], + "is_deleted": false, + "is_new": false, + "old_path": "unicode.py", + "path": "unicode.py", + "touched_lines": [ + 1, + 2 + ] + } + ], + "functions": [ + { + "changed_lines": [ + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "alpha.py", + "qualname": "first", + "source": "def first():\n return 2" + }, + { + "changed_lines": [ + 20 + ], + "end_lineno": 20, + "is_method": false, + "lineno": 19, + "path": "alpha.py", + "qualname": "second", + "source": "def second():\n return 4" + }, + { + "changed_lines": [ + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "beta.py", + "qualname": "beta", + "source": "def beta():\n return 11" + }, + { + "changed_lines": [ + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "crlf.py", + "qualname": "crlf_fn", + "source": "def crlf_fn():\n return 21" + }, + { + "changed_lines": [], + "end_lineno": 3, + "is_method": false, + "lineno": 1, + "path": "drop.py", + "qualname": "removedline", + "source": "def removedline():\n keep = 1\n return keep" + }, + { + "changed_lines": [ + 2 + ], + "end_lineno": 3, + "is_method": false, + "lineno": 1, + "path": "insert.py", + "qualname": "insertedline", + "source": "def insertedline():\n inserted = 50\n return 51" + }, + { + "changed_lines": [ + 1, + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "new.py", + "qualname": "gamma_unique", + "source": "def gamma_unique():\n return 'new-post-change-constant'" + }, + { + "changed_lines": [ + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "odd\t\u00e4.py", + "qualname": "odd_fn", + "source": "def odd_fn():\n return 41" + }, + { + "changed_lines": [ + 2, + 3, + 4 + ], + "end_lineno": 5, + "is_method": false, + "lineno": 1, + "path": "payload.py", + "qualname": "payload", + "source": "def payload():\n return \"\"\"new\n--- new literal\n+++ new literal\n\"\"\"" + }, + { + "changed_lines": [ + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "unicode.py", + "qualname": "unicode_fn", + "source": "def unicode_fn():\n return \"new\u2028tail\u2029end\"" + } + ], + "ok": true, + "targets": [ + [ + "alpha.py", + "first" + ], + [ + "alpha.py", + "second" + ], + [ + "beta.py", + "beta" + ], + [ + "crlf.py", + "crlf_fn" + ], + [ + "drop.py", + "removedline" + ], + [ + "insert.py", + "insertedline" + ], + [ + "new.py", + "gamma_unique" + ], + [ + "odd\t\u00e4.py", + "odd_fn" + ], + [ + "payload.py", + "payload" + ], + [ + "unicode.py", + "unicode_fn" + ] + ] + }, + "candidate": { + "files": [ + { + "added_lines": [ + 2, + 20 + ], + "is_deleted": false, + "is_new": false, + "old_path": "alpha.py", + "path": "alpha.py", + "touched_lines": [ + 1, + 2, + 19, + 20 + ] + }, + { + "added_lines": [ + 2 + ], + "is_deleted": false, + "is_new": false, + "old_path": "beta.py", + "path": "beta.py", + "touched_lines": [ + 1, + 2 + ] + }, + { + "added_lines": [ + 2 + ], + "is_deleted": false, + "is_new": false, + "old_path": "crlf.py", + "path": "crlf.py", + "touched_lines": [ + 1, + 2 + ] + }, + { + "added_lines": [], + "is_deleted": true, + "is_new": false, + "old_path": "deleted.py", + "path": "deleted.py", + "touched_lines": [ + 1 + ] + }, + { + "added_lines": [], + "is_deleted": false, + "is_new": false, + "old_path": "drop.py", + "path": "drop.py", + "touched_lines": [ + 2, + 3 + ] + }, + { + "added_lines": [ + 2 + ], + "is_deleted": false, + "is_new": false, + "old_path": "insert.py", + "path": "insert.py", + "touched_lines": [ + 2 + ] + }, + { + "added_lines": [ + 1, + 2 + ], + "is_deleted": false, + "is_new": true, + "old_path": null, + "path": "new.py", + "touched_lines": [ + 1, + 2 + ] + }, + { + "added_lines": [ + 2 + ], + "is_deleted": false, + "is_new": false, + "old_path": "odd\t\u00e4.py", + "path": "odd\t\u00e4.py", + "touched_lines": [ + 1, + 2 + ] + }, + { + "added_lines": [ + 2, + 3, + 4 + ], + "is_deleted": false, + "is_new": false, + "old_path": "payload.py", + "path": "payload.py", + "touched_lines": [ + 1, + 2, + 3, + 4 + ] + }, + { + "added_lines": [ + 2 + ], + "is_deleted": false, + "is_new": false, + "old_path": "unicode.py", + "path": "unicode.py", + "touched_lines": [ + 1, + 2 + ] + } + ], + "functions": [ + { + "changed_lines": [ + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "alpha.py", + "qualname": "first", + "source": "def first():\n return 2" + }, + { + "changed_lines": [ + 20 + ], + "end_lineno": 20, + "is_method": false, + "lineno": 19, + "path": "alpha.py", + "qualname": "second", + "source": "def second():\n return 4" + }, + { + "changed_lines": [ + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "beta.py", + "qualname": "beta", + "source": "def beta():\n return 11" + }, + { + "changed_lines": [ + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "crlf.py", + "qualname": "crlf_fn", + "source": "def crlf_fn():\n return 21" + }, + { + "changed_lines": [], + "end_lineno": 3, + "is_method": false, + "lineno": 1, + "path": "drop.py", + "qualname": "removedline", + "source": "def removedline():\n keep = 1\n return keep" + }, + { + "changed_lines": [ + 2 + ], + "end_lineno": 3, + "is_method": false, + "lineno": 1, + "path": "insert.py", + "qualname": "insertedline", + "source": "def insertedline():\n inserted = 50\n return 51" + }, + { + "changed_lines": [ + 1, + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "new.py", + "qualname": "gamma_unique", + "source": "def gamma_unique():\n return 'new-post-change-constant'" + }, + { + "changed_lines": [ + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "odd\t\u00e4.py", + "qualname": "odd_fn", + "source": "def odd_fn():\n return 41" + }, + { + "changed_lines": [ + 2, + 3, + 4 + ], + "end_lineno": 5, + "is_method": false, + "lineno": 1, + "path": "payload.py", + "qualname": "payload", + "source": "def payload():\n return \"\"\"new\n--- new literal\n+++ new literal\n\"\"\"" + }, + { + "changed_lines": [ + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "unicode.py", + "qualname": "unicode_fn", + "source": "def unicode_fn():\n return \"new\u2028tail\u2029end\"" + } + ], + "ok": true, + "targets": [ + [ + "alpha.py", + "first" + ], + [ + "alpha.py", + "second" + ], + [ + "beta.py", + "beta" + ], + [ + "crlf.py", + "crlf_fn" + ], + [ + "drop.py", + "removedline" + ], + [ + "insert.py", + "insertedline" + ], + [ + "new.py", + "gamma_unique" + ], + [ + "odd\t\u00e4.py", + "odd_fn" + ], + [ + "payload.py", + "payload" + ], + [ + "unicode.py", + "unicode_fn" + ] + ] + }, + "case": { + "expected_targets": [ + [ + "alpha.py", + "first" + ], + [ + "alpha.py", + "second" + ], + [ + "beta.py", + "beta" + ], + [ + "crlf.py", + "crlf_fn" + ], + [ + "drop.py", + "removedline" + ], + [ + "insert.py", + "insertedline" + ], + [ + "new.py", + "gamma_unique" + ], + [ + "odd\t\u00e4.py", + "odd_fn" + ], + [ + "payload.py", + "payload" + ], + [ + "unicode.py", + "unicode_fn" + ] + ], + "git_valid": true, + "name": "git_default", + "optional_envelope": false, + "patch": "/home/jacob/testpilot-git-compat-6cc6795e89f4/docs/receiving/git-compat-6cc6795e89f4/patches/git_default.diff", + "sha256": "e63cd0f0a8fc9f08aece050dfc5a92d5e19dac4b982f4c7787bf4f474572db0f" + }, + "cli": { + "argv": [ + "/usr/bin/python3", + "-B", + "-m", + "testpilot", + "targets", + "--repo", + "/home/jacob/testpilot-git-compat-6cc6795e89f4-data/fixture", + "--diff", + "/home/jacob/testpilot-git-compat-6cc6795e89f4/docs/receiving/git-compat-6cc6795e89f4/patches/git_default.diff" + ], + "returncode": 0, + "stderr": "", + "stdout": "10 changed Python functions outside tests\n\"alpha.py\":1-2 \"first\"\n\"alpha.py\":19-20 \"second\"\n\"beta.py\":1-2 \"beta\"\n\"crlf.py\":1-2 \"crlf_fn\"\n\"drop.py\":1-3 \"removedline\"\n\"insert.py\":1-3 \"insertedline\"\n\"new.py\":1-2 \"gamma_unique\"\n\"odd\\t\\u00e4.py\":1-2 \"odd_fn\"\n\"payload.py\":1-5 \"payload\"\n\"unicode.py\":1-2 \"unicode_fn\"\n" + }, + "pass": true + }, + { + "baseline": { + "files": [ + { + "added_lines": [ + 2, + 20 + ], + "is_deleted": false, + "is_new": false, + "old_path": "alpha.py", + "path": "alpha.py", + "touched_lines": [ + 1, + 2, + 19, + 20 + ] + }, + { + "added_lines": [ + 2 + ], + "is_deleted": false, + "is_new": false, + "old_path": "beta.py", + "path": "beta.py", + "touched_lines": [ + 1, + 2 + ] + }, + { + "added_lines": [ + 2 + ], + "is_deleted": false, + "is_new": false, + "old_path": "crlf.py", + "path": "crlf.py", + "touched_lines": [ + 1, + 2 + ] + }, + { + "added_lines": [], + "is_deleted": true, + "is_new": false, + "old_path": "deleted.py", + "path": "deleted.py", + "touched_lines": [ + 1 + ] + }, + { + "added_lines": [], + "is_deleted": false, + "is_new": false, + "old_path": "drop.py", + "path": "drop.py", + "touched_lines": [ + 2, + 3 + ] + }, + { + "added_lines": [ + 2 + ], + "is_deleted": false, + "is_new": false, + "old_path": "insert.py", + "path": "insert.py", + "touched_lines": [ + 2 + ] + }, + { + "added_lines": [ + 1, + 2 + ], + "is_deleted": false, + "is_new": true, + "old_path": null, + "path": "new.py", + "touched_lines": [ + 1, + 2 + ] + }, + { + "added_lines": [ + 2 + ], + "is_deleted": false, + "is_new": false, + "old_path": "odd\t\u00e4.py", + "path": "odd\t\u00e4.py", + "touched_lines": [ + 1, + 2 + ] + }, + { + "added_lines": [ + 2, + 3, + 4 + ], + "is_deleted": false, + "is_new": false, + "old_path": "payload.py", + "path": "payload.py", + "touched_lines": [ + 1, + 2, + 3, + 4 + ] + }, + { + "added_lines": [ + 2 + ], + "is_deleted": false, + "is_new": false, + "old_path": "unicode.py", + "path": "unicode.py", + "touched_lines": [ + 1, + 2 + ] + } + ], + "functions": [ + { + "changed_lines": [ + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "alpha.py", + "qualname": "first", + "source": "def first():\n return 2" + }, + { + "changed_lines": [ + 20 + ], + "end_lineno": 20, + "is_method": false, + "lineno": 19, + "path": "alpha.py", + "qualname": "second", + "source": "def second():\n return 4" + }, + { + "changed_lines": [ + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "beta.py", + "qualname": "beta", + "source": "def beta():\n return 11" + }, + { + "changed_lines": [ + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "crlf.py", + "qualname": "crlf_fn", + "source": "def crlf_fn():\n return 21" + }, + { + "changed_lines": [], + "end_lineno": 3, + "is_method": false, + "lineno": 1, + "path": "drop.py", + "qualname": "removedline", + "source": "def removedline():\n keep = 1\n return keep" + }, + { + "changed_lines": [ + 2 + ], + "end_lineno": 3, + "is_method": false, + "lineno": 1, + "path": "insert.py", + "qualname": "insertedline", + "source": "def insertedline():\n inserted = 50\n return 51" + }, + { + "changed_lines": [ + 1, + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "new.py", + "qualname": "gamma_unique", + "source": "def gamma_unique():\n return 'new-post-change-constant'" + }, + { + "changed_lines": [ + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "odd\t\u00e4.py", + "qualname": "odd_fn", + "source": "def odd_fn():\n return 41" + }, + { + "changed_lines": [ + 2, + 3, + 4 + ], + "end_lineno": 5, + "is_method": false, + "lineno": 1, + "path": "payload.py", + "qualname": "payload", + "source": "def payload():\n return \"\"\"new\n--- new literal\n+++ new literal\n\"\"\"" + }, + { + "changed_lines": [ + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "unicode.py", + "qualname": "unicode_fn", + "source": "def unicode_fn():\n return \"new\u2028tail\u2029end\"" + } + ], + "ok": true, + "targets": [ + [ + "alpha.py", + "first" + ], + [ + "alpha.py", + "second" + ], + [ + "beta.py", + "beta" + ], + [ + "crlf.py", + "crlf_fn" + ], + [ + "drop.py", + "removedline" + ], + [ + "insert.py", + "insertedline" + ], + [ + "new.py", + "gamma_unique" + ], + [ + "odd\t\u00e4.py", + "odd_fn" + ], + [ + "payload.py", + "payload" + ], + [ + "unicode.py", + "unicode_fn" + ] + ] + }, + "candidate": { + "files": [ + { + "added_lines": [ + 2, + 20 + ], + "is_deleted": false, + "is_new": false, + "old_path": "alpha.py", + "path": "alpha.py", + "touched_lines": [ + 1, + 2, + 19, + 20 + ] + }, + { + "added_lines": [ + 2 + ], + "is_deleted": false, + "is_new": false, + "old_path": "beta.py", + "path": "beta.py", + "touched_lines": [ + 1, + 2 + ] + }, + { + "added_lines": [ + 2 + ], + "is_deleted": false, + "is_new": false, + "old_path": "crlf.py", + "path": "crlf.py", + "touched_lines": [ + 1, + 2 + ] + }, + { + "added_lines": [], + "is_deleted": true, + "is_new": false, + "old_path": "deleted.py", + "path": "deleted.py", + "touched_lines": [ + 1 + ] + }, + { + "added_lines": [], + "is_deleted": false, + "is_new": false, + "old_path": "drop.py", + "path": "drop.py", + "touched_lines": [ + 2, + 3 + ] + }, + { + "added_lines": [ + 2 + ], + "is_deleted": false, + "is_new": false, + "old_path": "insert.py", + "path": "insert.py", + "touched_lines": [ + 2 + ] + }, + { + "added_lines": [ + 1, + 2 + ], + "is_deleted": false, + "is_new": true, + "old_path": null, + "path": "new.py", + "touched_lines": [ + 1, + 2 + ] + }, + { + "added_lines": [ + 2 + ], + "is_deleted": false, + "is_new": false, + "old_path": "odd\t\u00e4.py", + "path": "odd\t\u00e4.py", + "touched_lines": [ + 1, + 2 + ] + }, + { + "added_lines": [ + 2, + 3, + 4 + ], + "is_deleted": false, + "is_new": false, + "old_path": "payload.py", + "path": "payload.py", + "touched_lines": [ + 1, + 2, + 3, + 4 + ] + }, + { + "added_lines": [ + 2 + ], + "is_deleted": false, + "is_new": false, + "old_path": "unicode.py", + "path": "unicode.py", + "touched_lines": [ + 1, + 2 + ] + } + ], + "functions": [ + { + "changed_lines": [ + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "alpha.py", + "qualname": "first", + "source": "def first():\n return 2" + }, + { + "changed_lines": [ + 20 + ], + "end_lineno": 20, + "is_method": false, + "lineno": 19, + "path": "alpha.py", + "qualname": "second", + "source": "def second():\n return 4" + }, + { + "changed_lines": [ + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "beta.py", + "qualname": "beta", + "source": "def beta():\n return 11" + }, + { + "changed_lines": [ + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "crlf.py", + "qualname": "crlf_fn", + "source": "def crlf_fn():\n return 21" + }, + { + "changed_lines": [], + "end_lineno": 3, + "is_method": false, + "lineno": 1, + "path": "drop.py", + "qualname": "removedline", + "source": "def removedline():\n keep = 1\n return keep" + }, + { + "changed_lines": [ + 2 + ], + "end_lineno": 3, + "is_method": false, + "lineno": 1, + "path": "insert.py", + "qualname": "insertedline", + "source": "def insertedline():\n inserted = 50\n return 51" + }, + { + "changed_lines": [ + 1, + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "new.py", + "qualname": "gamma_unique", + "source": "def gamma_unique():\n return 'new-post-change-constant'" + }, + { + "changed_lines": [ + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "odd\t\u00e4.py", + "qualname": "odd_fn", + "source": "def odd_fn():\n return 41" + }, + { + "changed_lines": [ + 2, + 3, + 4 + ], + "end_lineno": 5, + "is_method": false, + "lineno": 1, + "path": "payload.py", + "qualname": "payload", + "source": "def payload():\n return \"\"\"new\n--- new literal\n+++ new literal\n\"\"\"" + }, + { + "changed_lines": [ + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "unicode.py", + "qualname": "unicode_fn", + "source": "def unicode_fn():\n return \"new\u2028tail\u2029end\"" + } + ], + "ok": true, + "targets": [ + [ + "alpha.py", + "first" + ], + [ + "alpha.py", + "second" + ], + [ + "beta.py", + "beta" + ], + [ + "crlf.py", + "crlf_fn" + ], + [ + "drop.py", + "removedline" + ], + [ + "insert.py", + "insertedline" + ], + [ + "new.py", + "gamma_unique" + ], + [ + "odd\t\u00e4.py", + "odd_fn" + ], + [ + "payload.py", + "payload" + ], + [ + "unicode.py", + "unicode_fn" + ] + ] + }, + "case": { + "expected_targets": [ + [ + "alpha.py", + "first" + ], + [ + "alpha.py", + "second" + ], + [ + "beta.py", + "beta" + ], + [ + "crlf.py", + "crlf_fn" + ], + [ + "drop.py", + "removedline" + ], + [ + "insert.py", + "insertedline" + ], + [ + "new.py", + "gamma_unique" + ], + [ + "odd\t\u00e4.py", + "odd_fn" + ], + [ + "payload.py", + "payload" + ], + [ + "unicode.py", + "unicode_fn" + ] + ], + "git_valid": true, + "name": "git_zero", + "optional_envelope": false, + "patch": "/home/jacob/testpilot-git-compat-6cc6795e89f4/docs/receiving/git-compat-6cc6795e89f4/patches/git_zero.diff", + "sha256": "0107f3f423d6f8e4150587ae8f649f8fb146176d01ddca27e37fbbd2b57e02d9" + }, + "cli": { + "argv": [ + "/usr/bin/python3", + "-B", + "-m", + "testpilot", + "targets", + "--repo", + "/home/jacob/testpilot-git-compat-6cc6795e89f4-data/fixture", + "--diff", + "/home/jacob/testpilot-git-compat-6cc6795e89f4/docs/receiving/git-compat-6cc6795e89f4/patches/git_zero.diff" + ], + "returncode": 0, + "stderr": "", + "stdout": "10 changed Python functions outside tests\n\"alpha.py\":1-2 \"first\"\n\"alpha.py\":19-20 \"second\"\n\"beta.py\":1-2 \"beta\"\n\"crlf.py\":1-2 \"crlf_fn\"\n\"drop.py\":1-3 \"removedline\"\n\"insert.py\":1-3 \"insertedline\"\n\"new.py\":1-2 \"gamma_unique\"\n\"odd\\t\\u00e4.py\":1-2 \"odd_fn\"\n\"payload.py\":1-5 \"payload\"\n\"unicode.py\":1-2 \"unicode_fn\"\n" + }, + "pass": true + }, + { + "baseline": { + "files": [ + { + "added_lines": [ + 2, + 20 + ], + "is_deleted": false, + "is_new": false, + "old_path": "alpha.py", + "path": "alpha.py", + "touched_lines": [ + 1, + 2, + 19, + 20 + ] + }, + { + "added_lines": [ + 2 + ], + "is_deleted": false, + "is_new": false, + "old_path": "beta.py", + "path": "beta.py", + "touched_lines": [ + 1, + 2 + ] + }, + { + "added_lines": [ + 2 + ], + "is_deleted": false, + "is_new": false, + "old_path": "crlf.py", + "path": "crlf.py", + "touched_lines": [ + 1, + 2 + ] + }, + { + "added_lines": [], + "is_deleted": true, + "is_new": false, + "old_path": "deleted.py", + "path": "deleted.py", + "touched_lines": [ + 1 + ] + }, + { + "added_lines": [], + "is_deleted": false, + "is_new": false, + "old_path": "drop.py", + "path": "drop.py", + "touched_lines": [ + 2, + 3 + ] + }, + { + "added_lines": [ + 2 + ], + "is_deleted": false, + "is_new": false, + "old_path": "insert.py", + "path": "insert.py", + "touched_lines": [ + 2 + ] + }, + { + "added_lines": [ + 1, + 2 + ], + "is_deleted": false, + "is_new": true, + "old_path": null, + "path": "new.py", + "touched_lines": [ + 1, + 2 + ] + }, + { + "added_lines": [ + 2 + ], + "is_deleted": false, + "is_new": false, + "old_path": "odd\t\u00e4.py", + "path": "odd\t\u00e4.py", + "touched_lines": [ + 1, + 2 + ] + }, + { + "added_lines": [ + 2, + 3, + 4 + ], + "is_deleted": false, + "is_new": false, + "old_path": "payload.py", + "path": "payload.py", + "touched_lines": [ + 1, + 2, + 3, + 4 + ] + }, + { + "added_lines": [ + 2 + ], + "is_deleted": false, + "is_new": false, + "old_path": "unicode.py", + "path": "unicode.py", + "touched_lines": [ + 1, + 2 + ] + } + ], + "functions": [ + { + "changed_lines": [ + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "alpha.py", + "qualname": "first", + "source": "def first():\n return 2" + }, + { + "changed_lines": [ + 20 + ], + "end_lineno": 20, + "is_method": false, + "lineno": 19, + "path": "alpha.py", + "qualname": "second", + "source": "def second():\n return 4" + }, + { + "changed_lines": [ + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "beta.py", + "qualname": "beta", + "source": "def beta():\n return 11" + }, + { + "changed_lines": [ + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "crlf.py", + "qualname": "crlf_fn", + "source": "def crlf_fn():\n return 21" + }, + { + "changed_lines": [], + "end_lineno": 3, + "is_method": false, + "lineno": 1, + "path": "drop.py", + "qualname": "removedline", + "source": "def removedline():\n keep = 1\n return keep" + }, + { + "changed_lines": [ + 2 + ], + "end_lineno": 3, + "is_method": false, + "lineno": 1, + "path": "insert.py", + "qualname": "insertedline", + "source": "def insertedline():\n inserted = 50\n return 51" + }, + { + "changed_lines": [ + 1, + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "new.py", + "qualname": "gamma_unique", + "source": "def gamma_unique():\n return 'new-post-change-constant'" + }, + { + "changed_lines": [ + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "odd\t\u00e4.py", + "qualname": "odd_fn", + "source": "def odd_fn():\n return 41" + }, + { + "changed_lines": [ + 2, + 3, + 4 + ], + "end_lineno": 5, + "is_method": false, + "lineno": 1, + "path": "payload.py", + "qualname": "payload", + "source": "def payload():\n return \"\"\"new\n--- new literal\n+++ new literal\n\"\"\"" + }, + { + "changed_lines": [ + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "unicode.py", + "qualname": "unicode_fn", + "source": "def unicode_fn():\n return \"new\u2028tail\u2029end\"" + } + ], + "ok": true, + "targets": [ + [ + "alpha.py", + "first" + ], + [ + "alpha.py", + "second" + ], + [ + "beta.py", + "beta" + ], + [ + "crlf.py", + "crlf_fn" + ], + [ + "drop.py", + "removedline" + ], + [ + "insert.py", + "insertedline" + ], + [ + "new.py", + "gamma_unique" + ], + [ + "odd\t\u00e4.py", + "odd_fn" + ], + [ + "payload.py", + "payload" + ], + [ + "unicode.py", + "unicode_fn" + ] + ] + }, + "candidate": { + "files": [ + { + "added_lines": [ + 2, + 20 + ], + "is_deleted": false, + "is_new": false, + "old_path": "alpha.py", + "path": "alpha.py", + "touched_lines": [ + 1, + 2, + 19, + 20 + ] + }, + { + "added_lines": [ + 2 + ], + "is_deleted": false, + "is_new": false, + "old_path": "beta.py", + "path": "beta.py", + "touched_lines": [ + 1, + 2 + ] + }, + { + "added_lines": [ + 2 + ], + "is_deleted": false, + "is_new": false, + "old_path": "crlf.py", + "path": "crlf.py", + "touched_lines": [ + 1, + 2 + ] + }, + { + "added_lines": [], + "is_deleted": true, + "is_new": false, + "old_path": "deleted.py", + "path": "deleted.py", + "touched_lines": [ + 1 + ] + }, + { + "added_lines": [], + "is_deleted": false, + "is_new": false, + "old_path": "drop.py", + "path": "drop.py", + "touched_lines": [ + 2, + 3 + ] + }, + { + "added_lines": [ + 2 + ], + "is_deleted": false, + "is_new": false, + "old_path": "insert.py", + "path": "insert.py", + "touched_lines": [ + 2 + ] + }, + { + "added_lines": [ + 1, + 2 + ], + "is_deleted": false, + "is_new": true, + "old_path": null, + "path": "new.py", + "touched_lines": [ + 1, + 2 + ] + }, + { + "added_lines": [ + 2 + ], + "is_deleted": false, + "is_new": false, + "old_path": "odd\t\u00e4.py", + "path": "odd\t\u00e4.py", + "touched_lines": [ + 1, + 2 + ] + }, + { + "added_lines": [ + 2, + 3, + 4 + ], + "is_deleted": false, + "is_new": false, + "old_path": "payload.py", + "path": "payload.py", + "touched_lines": [ + 1, + 2, + 3, + 4 + ] + }, + { + "added_lines": [ + 2 + ], + "is_deleted": false, + "is_new": false, + "old_path": "unicode.py", + "path": "unicode.py", + "touched_lines": [ + 1, + 2 + ] + } + ], + "functions": [ + { + "changed_lines": [ + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "alpha.py", + "qualname": "first", + "source": "def first():\n return 2" + }, + { + "changed_lines": [ + 20 + ], + "end_lineno": 20, + "is_method": false, + "lineno": 19, + "path": "alpha.py", + "qualname": "second", + "source": "def second():\n return 4" + }, + { + "changed_lines": [ + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "beta.py", + "qualname": "beta", + "source": "def beta():\n return 11" + }, + { + "changed_lines": [ + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "crlf.py", + "qualname": "crlf_fn", + "source": "def crlf_fn():\n return 21" + }, + { + "changed_lines": [], + "end_lineno": 3, + "is_method": false, + "lineno": 1, + "path": "drop.py", + "qualname": "removedline", + "source": "def removedline():\n keep = 1\n return keep" + }, + { + "changed_lines": [ + 2 + ], + "end_lineno": 3, + "is_method": false, + "lineno": 1, + "path": "insert.py", + "qualname": "insertedline", + "source": "def insertedline():\n inserted = 50\n return 51" + }, + { + "changed_lines": [ + 1, + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "new.py", + "qualname": "gamma_unique", + "source": "def gamma_unique():\n return 'new-post-change-constant'" + }, + { + "changed_lines": [ + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "odd\t\u00e4.py", + "qualname": "odd_fn", + "source": "def odd_fn():\n return 41" + }, + { + "changed_lines": [ + 2, + 3, + 4 + ], + "end_lineno": 5, + "is_method": false, + "lineno": 1, + "path": "payload.py", + "qualname": "payload", + "source": "def payload():\n return \"\"\"new\n--- new literal\n+++ new literal\n\"\"\"" + }, + { + "changed_lines": [ + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "unicode.py", + "qualname": "unicode_fn", + "source": "def unicode_fn():\n return \"new\u2028tail\u2029end\"" + } + ], + "ok": true, + "targets": [ + [ + "alpha.py", + "first" + ], + [ + "alpha.py", + "second" + ], + [ + "beta.py", + "beta" + ], + [ + "crlf.py", + "crlf_fn" + ], + [ + "drop.py", + "removedline" + ], + [ + "insert.py", + "insertedline" + ], + [ + "new.py", + "gamma_unique" + ], + [ + "odd\t\u00e4.py", + "odd_fn" + ], + [ + "payload.py", + "payload" + ], + [ + "unicode.py", + "unicode_fn" + ] + ] + }, + "case": { + "expected_targets": [ + [ + "alpha.py", + "first" + ], + [ + "alpha.py", + "second" + ], + [ + "beta.py", + "beta" + ], + [ + "crlf.py", + "crlf_fn" + ], + [ + "drop.py", + "removedline" + ], + [ + "insert.py", + "insertedline" + ], + [ + "new.py", + "gamma_unique" + ], + [ + "odd\t\u00e4.py", + "odd_fn" + ], + [ + "payload.py", + "payload" + ], + [ + "unicode.py", + "unicode_fn" + ] + ], + "git_valid": true, + "name": "git_one", + "optional_envelope": false, + "patch": "/home/jacob/testpilot-git-compat-6cc6795e89f4/docs/receiving/git-compat-6cc6795e89f4/patches/git_one.diff", + "sha256": "5cf9bc987e7021c0002aa89e22fc1af01dc42f3628b87745880c6aff839f9070" + }, + "cli": { + "argv": [ + "/usr/bin/python3", + "-B", + "-m", + "testpilot", + "targets", + "--repo", + "/home/jacob/testpilot-git-compat-6cc6795e89f4-data/fixture", + "--diff", + "/home/jacob/testpilot-git-compat-6cc6795e89f4/docs/receiving/git-compat-6cc6795e89f4/patches/git_one.diff" + ], + "returncode": 0, + "stderr": "", + "stdout": "10 changed Python functions outside tests\n\"alpha.py\":1-2 \"first\"\n\"alpha.py\":19-20 \"second\"\n\"beta.py\":1-2 \"beta\"\n\"crlf.py\":1-2 \"crlf_fn\"\n\"drop.py\":1-3 \"removedline\"\n\"insert.py\":1-3 \"insertedline\"\n\"new.py\":1-2 \"gamma_unique\"\n\"odd\\t\\u00e4.py\":1-2 \"odd_fn\"\n\"payload.py\":1-5 \"payload\"\n\"unicode.py\":1-2 \"unicode_fn\"\n" + }, + "pass": true + }, + { + "baseline": { + "files": [ + { + "added_lines": [ + 2, + 20 + ], + "is_deleted": false, + "is_new": false, + "old_path": "alpha.py", + "path": "alpha.py", + "touched_lines": [ + 1, + 2, + 19, + 20 + ] + }, + { + "added_lines": [ + 2 + ], + "is_deleted": false, + "is_new": false, + "old_path": "beta.py", + "path": "beta.py", + "touched_lines": [ + 1, + 2 + ] + }, + { + "added_lines": [ + 2 + ], + "is_deleted": false, + "is_new": false, + "old_path": "crlf.py", + "path": "crlf.py", + "touched_lines": [ + 1, + 2 + ] + }, + { + "added_lines": [], + "is_deleted": true, + "is_new": false, + "old_path": "deleted.py", + "path": "deleted.py", + "touched_lines": [ + 1 + ] + }, + { + "added_lines": [], + "is_deleted": false, + "is_new": false, + "old_path": "drop.py", + "path": "drop.py", + "touched_lines": [ + 2, + 3 + ] + }, + { + "added_lines": [ + 2 + ], + "is_deleted": false, + "is_new": false, + "old_path": "insert.py", + "path": "insert.py", + "touched_lines": [ + 2 + ] + }, + { + "added_lines": [ + 1, + 2 + ], + "is_deleted": false, + "is_new": true, + "old_path": null, + "path": "new.py", + "touched_lines": [ + 1, + 2 + ] + }, + { + "added_lines": [ + 2 + ], + "is_deleted": false, + "is_new": false, + "old_path": "odd\t\u00e4.py", + "path": "odd\t\u00e4.py", + "touched_lines": [ + 1, + 2 + ] + }, + { + "added_lines": [ + 2, + 3, + 4 + ], + "is_deleted": false, + "is_new": false, + "old_path": "payload.py", + "path": "payload.py", + "touched_lines": [ + 1, + 2, + 3, + 4 + ] + }, + { + "added_lines": [ + 2 + ], + "is_deleted": false, + "is_new": false, + "old_path": "unicode.py", + "path": "unicode.py", + "touched_lines": [ + 1, + 2 + ] + } + ], + "functions": [ + { + "changed_lines": [ + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "alpha.py", + "qualname": "first", + "source": "def first():\n return 2" + }, + { + "changed_lines": [ + 20 + ], + "end_lineno": 20, + "is_method": false, + "lineno": 19, + "path": "alpha.py", + "qualname": "second", + "source": "def second():\n return 4" + }, + { + "changed_lines": [ + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "beta.py", + "qualname": "beta", + "source": "def beta():\n return 11" + }, + { + "changed_lines": [ + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "crlf.py", + "qualname": "crlf_fn", + "source": "def crlf_fn():\n return 21" + }, + { + "changed_lines": [], + "end_lineno": 3, + "is_method": false, + "lineno": 1, + "path": "drop.py", + "qualname": "removedline", + "source": "def removedline():\n keep = 1\n return keep" + }, + { + "changed_lines": [ + 2 + ], + "end_lineno": 3, + "is_method": false, + "lineno": 1, + "path": "insert.py", + "qualname": "insertedline", + "source": "def insertedline():\n inserted = 50\n return 51" + }, + { + "changed_lines": [ + 1, + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "new.py", + "qualname": "gamma_unique", + "source": "def gamma_unique():\n return 'new-post-change-constant'" + }, + { + "changed_lines": [ + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "odd\t\u00e4.py", + "qualname": "odd_fn", + "source": "def odd_fn():\n return 41" + }, + { + "changed_lines": [ + 2, + 3, + 4 + ], + "end_lineno": 5, + "is_method": false, + "lineno": 1, + "path": "payload.py", + "qualname": "payload", + "source": "def payload():\n return \"\"\"new\n--- new literal\n+++ new literal\n\"\"\"" + }, + { + "changed_lines": [ + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "unicode.py", + "qualname": "unicode_fn", + "source": "def unicode_fn():\n return \"new\u2028tail\u2029end\"" + } + ], + "ok": true, + "targets": [ + [ + "alpha.py", + "first" + ], + [ + "alpha.py", + "second" + ], + [ + "beta.py", + "beta" + ], + [ + "crlf.py", + "crlf_fn" + ], + [ + "drop.py", + "removedline" + ], + [ + "insert.py", + "insertedline" + ], + [ + "new.py", + "gamma_unique" + ], + [ + "odd\t\u00e4.py", + "odd_fn" + ], + [ + "payload.py", + "payload" + ], + [ + "unicode.py", + "unicode_fn" + ] + ] + }, + "candidate": { + "files": [ + { + "added_lines": [ + 2, + 20 + ], + "is_deleted": false, + "is_new": false, + "old_path": "alpha.py", + "path": "alpha.py", + "touched_lines": [ + 1, + 2, + 19, + 20 + ] + }, + { + "added_lines": [ + 2 + ], + "is_deleted": false, + "is_new": false, + "old_path": "beta.py", + "path": "beta.py", + "touched_lines": [ + 1, + 2 + ] + }, + { + "added_lines": [ + 2 + ], + "is_deleted": false, + "is_new": false, + "old_path": "crlf.py", + "path": "crlf.py", + "touched_lines": [ + 1, + 2 + ] + }, + { + "added_lines": [], + "is_deleted": true, + "is_new": false, + "old_path": "deleted.py", + "path": "deleted.py", + "touched_lines": [ + 1 + ] + }, + { + "added_lines": [], + "is_deleted": false, + "is_new": false, + "old_path": "drop.py", + "path": "drop.py", + "touched_lines": [ + 2, + 3 + ] + }, + { + "added_lines": [ + 2 + ], + "is_deleted": false, + "is_new": false, + "old_path": "insert.py", + "path": "insert.py", + "touched_lines": [ + 2 + ] + }, + { + "added_lines": [ + 1, + 2 + ], + "is_deleted": false, + "is_new": true, + "old_path": null, + "path": "new.py", + "touched_lines": [ + 1, + 2 + ] + }, + { + "added_lines": [ + 2 + ], + "is_deleted": false, + "is_new": false, + "old_path": "odd\t\u00e4.py", + "path": "odd\t\u00e4.py", + "touched_lines": [ + 1, + 2 + ] + }, + { + "added_lines": [ + 2, + 3, + 4 + ], + "is_deleted": false, + "is_new": false, + "old_path": "payload.py", + "path": "payload.py", + "touched_lines": [ + 1, + 2, + 3, + 4 + ] + }, + { + "added_lines": [ + 2 + ], + "is_deleted": false, + "is_new": false, + "old_path": "unicode.py", + "path": "unicode.py", + "touched_lines": [ + 1, + 2 + ] + } + ], + "functions": [ + { + "changed_lines": [ + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "alpha.py", + "qualname": "first", + "source": "def first():\n return 2" + }, + { + "changed_lines": [ + 20 + ], + "end_lineno": 20, + "is_method": false, + "lineno": 19, + "path": "alpha.py", + "qualname": "second", + "source": "def second():\n return 4" + }, + { + "changed_lines": [ + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "beta.py", + "qualname": "beta", + "source": "def beta():\n return 11" + }, + { + "changed_lines": [ + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "crlf.py", + "qualname": "crlf_fn", + "source": "def crlf_fn():\n return 21" + }, + { + "changed_lines": [], + "end_lineno": 3, + "is_method": false, + "lineno": 1, + "path": "drop.py", + "qualname": "removedline", + "source": "def removedline():\n keep = 1\n return keep" + }, + { + "changed_lines": [ + 2 + ], + "end_lineno": 3, + "is_method": false, + "lineno": 1, + "path": "insert.py", + "qualname": "insertedline", + "source": "def insertedline():\n inserted = 50\n return 51" + }, + { + "changed_lines": [ + 1, + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "new.py", + "qualname": "gamma_unique", + "source": "def gamma_unique():\n return 'new-post-change-constant'" + }, + { + "changed_lines": [ + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "odd\t\u00e4.py", + "qualname": "odd_fn", + "source": "def odd_fn():\n return 41" + }, + { + "changed_lines": [ + 2, + 3, + 4 + ], + "end_lineno": 5, + "is_method": false, + "lineno": 1, + "path": "payload.py", + "qualname": "payload", + "source": "def payload():\n return \"\"\"new\n--- new literal\n+++ new literal\n\"\"\"" + }, + { + "changed_lines": [ + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "unicode.py", + "qualname": "unicode_fn", + "source": "def unicode_fn():\n return \"new\u2028tail\u2029end\"" + } + ], + "ok": true, + "targets": [ + [ + "alpha.py", + "first" + ], + [ + "alpha.py", + "second" + ], + [ + "beta.py", + "beta" + ], + [ + "crlf.py", + "crlf_fn" + ], + [ + "drop.py", + "removedline" + ], + [ + "insert.py", + "insertedline" + ], + [ + "new.py", + "gamma_unique" + ], + [ + "odd\t\u00e4.py", + "odd_fn" + ], + [ + "payload.py", + "payload" + ], + [ + "unicode.py", + "unicode_fn" + ] + ] + }, + "case": { + "expected_targets": [ + [ + "alpha.py", + "first" + ], + [ + "alpha.py", + "second" + ], + [ + "beta.py", + "beta" + ], + [ + "crlf.py", + "crlf_fn" + ], + [ + "drop.py", + "removedline" + ], + [ + "insert.py", + "insertedline" + ], + [ + "new.py", + "gamma_unique" + ], + [ + "odd\t\u00e4.py", + "odd_fn" + ], + [ + "payload.py", + "payload" + ], + [ + "unicode.py", + "unicode_fn" + ] + ], + "git_valid": true, + "name": "git_function", + "optional_envelope": false, + "patch": "/home/jacob/testpilot-git-compat-6cc6795e89f4/docs/receiving/git-compat-6cc6795e89f4/patches/git_function.diff", + "sha256": "9fc48c9a310a702951d64df8ff928fd2135b69373fa25bf8a0ab12c6788ab177" + }, + "cli": { + "argv": [ + "/usr/bin/python3", + "-B", + "-m", + "testpilot", + "targets", + "--repo", + "/home/jacob/testpilot-git-compat-6cc6795e89f4-data/fixture", + "--diff", + "/home/jacob/testpilot-git-compat-6cc6795e89f4/docs/receiving/git-compat-6cc6795e89f4/patches/git_function.diff" + ], + "returncode": 0, + "stderr": "", + "stdout": "10 changed Python functions outside tests\n\"alpha.py\":1-2 \"first\"\n\"alpha.py\":19-20 \"second\"\n\"beta.py\":1-2 \"beta\"\n\"crlf.py\":1-2 \"crlf_fn\"\n\"drop.py\":1-3 \"removedline\"\n\"insert.py\":1-3 \"insertedline\"\n\"new.py\":1-2 \"gamma_unique\"\n\"odd\\t\\u00e4.py\":1-2 \"odd_fn\"\n\"payload.py\":1-5 \"payload\"\n\"unicode.py\":1-2 \"unicode_fn\"\n" + }, + "pass": true + }, + { + "baseline": { + "files": [ + { + "added_lines": [ + 2, + 20 + ], + "is_deleted": false, + "is_new": false, + "old_path": "alpha.py", + "path": "alpha.py", + "touched_lines": [ + 1, + 2, + 19, + 20 + ] + } + ], + "functions": [ + { + "changed_lines": [ + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "alpha.py", + "qualname": "first", + "source": "def first():\n return 2" + }, + { + "changed_lines": [ + 20 + ], + "end_lineno": 20, + "is_method": false, + "lineno": 19, + "path": "alpha.py", + "qualname": "second", + "source": "def second():\n return 4" + } + ], + "ok": true, + "targets": [ + [ + "alpha.py", + "first" + ], + [ + "alpha.py", + "second" + ] + ] + }, + "candidate": { + "files": [ + { + "added_lines": [ + 2, + 20 + ], + "is_deleted": false, + "is_new": false, + "old_path": "alpha.py", + "path": "alpha.py", + "touched_lines": [ + 1, + 2, + 19, + 20 + ] + } + ], + "functions": [ + { + "changed_lines": [ + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "alpha.py", + "qualname": "first", + "source": "def first():\n return 2" + }, + { + "changed_lines": [ + 20 + ], + "end_lineno": 20, + "is_method": false, + "lineno": 19, + "path": "alpha.py", + "qualname": "second", + "source": "def second():\n return 4" + } + ], + "ok": true, + "targets": [ + [ + "alpha.py", + "first" + ], + [ + "alpha.py", + "second" + ] + ] + }, + "case": { + "expected_targets": [ + [ + "alpha.py", + "first" + ], + [ + "alpha.py", + "second" + ] + ], + "git_valid": true, + "name": "git_binary_python", + "optional_envelope": false, + "patch": "/home/jacob/testpilot-git-compat-6cc6795e89f4/docs/receiving/git-compat-6cc6795e89f4/patches/git_binary_python.diff", + "sha256": "0579a99651579908d9153bc12a0661e8b5081438055bdf3d5bab61a9cb787335" + }, + "cli": { + "argv": [ + "/usr/bin/python3", + "-B", + "-m", + "testpilot", + "targets", + "--repo", + "/home/jacob/testpilot-git-compat-6cc6795e89f4-data/fixture", + "--diff", + "/home/jacob/testpilot-git-compat-6cc6795e89f4/docs/receiving/git-compat-6cc6795e89f4/patches/git_binary_python.diff" + ], + "returncode": 0, + "stderr": "", + "stdout": "2 changed Python functions outside tests\n\"alpha.py\":1-2 \"first\"\n\"alpha.py\":19-20 \"second\"\n" + }, + "pass": true + }, + { + "baseline": { + "files": [], + "functions": [], + "ok": true, + "targets": [] + }, + "candidate": { + "files": [], + "functions": [], + "ok": true, + "targets": [] + }, + "case": { + "expected_targets": [], + "git_valid": true, + "name": "git_metadata_only", + "optional_envelope": false, + "patch": "/home/jacob/testpilot-git-compat-6cc6795e89f4/docs/receiving/git-compat-6cc6795e89f4/patches/git_metadata_only.diff", + "sha256": "5fc98cf71c98a4b1dab24b32c06820ea5b27e4088559e3adea93b0c474eba286" + }, + "cli": { + "argv": [ + "/usr/bin/python3", + "-B", + "-m", + "testpilot", + "targets", + "--repo", + "/home/jacob/testpilot-git-compat-6cc6795e89f4-data/fixture", + "--diff", + "/home/jacob/testpilot-git-compat-6cc6795e89f4/docs/receiving/git-compat-6cc6795e89f4/patches/git_metadata_only.diff" + ], + "returncode": 0, + "stderr": "", + "stdout": "0 changed Python functions outside tests\n" + }, + "pass": true + }, + { + "baseline": { + "files": [ + { + "added_lines": [ + 2 + ], + "is_deleted": false, + "is_new": false, + "old_path": "beta.py", + "path": "beta.py", + "touched_lines": [ + 1, + 2 + ] + } + ], + "functions": [ + { + "changed_lines": [ + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "beta.py", + "qualname": "beta", + "source": "def beta():\n return 11" + } + ], + "ok": true, + "targets": [ + [ + "beta.py", + "beta" + ] + ] + }, + "candidate": { + "files": [ + { + "added_lines": [ + 2 + ], + "is_deleted": false, + "is_new": false, + "old_path": "beta.py", + "path": "beta.py", + "touched_lines": [ + 1, + 2 + ] + } + ], + "functions": [ + { + "changed_lines": [ + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "beta.py", + "qualname": "beta", + "source": "def beta():\n return 11" + } + ], + "ok": true, + "targets": [ + [ + "beta.py", + "beta" + ] + ] + }, + "case": { + "expected_targets": [ + [ + "beta.py", + "beta" + ] + ], + "git_valid": true, + "name": "git_no_newline", + "optional_envelope": false, + "patch": "/home/jacob/testpilot-git-compat-6cc6795e89f4/docs/receiving/git-compat-6cc6795e89f4/patches/git_no_newline.diff", + "sha256": "3ba147b42f25628fc5ac2ec4f4cb73cae632127a75c8c009049ace99477210be" + }, + "cli": { + "argv": [ + "/usr/bin/python3", + "-B", + "-m", + "testpilot", + "targets", + "--repo", + "/home/jacob/testpilot-git-compat-6cc6795e89f4-data/fixture", + "--diff", + "/home/jacob/testpilot-git-compat-6cc6795e89f4/docs/receiving/git-compat-6cc6795e89f4/patches/git_no_newline.diff" + ], + "returncode": 0, + "stderr": "", + "stdout": "1 changed Python function outside tests\n\"beta.py\":1-2 \"beta\"\n" + }, + "pass": true + }, + { + "baseline": { + "files": [ + { + "added_lines": [ + 2 + ], + "is_deleted": false, + "is_new": false, + "old_path": "crlf.py", + "path": "crlf.py", + "touched_lines": [ + 1, + 2 + ] + } + ], + "functions": [ + { + "changed_lines": [ + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "crlf.py", + "qualname": "crlf_fn", + "source": "def crlf_fn():\n return 21" + } + ], + "ok": true, + "targets": [ + [ + "crlf.py", + "crlf_fn" + ] + ] + }, + "candidate": { + "files": [ + { + "added_lines": [ + 2 + ], + "is_deleted": false, + "is_new": false, + "old_path": "crlf.py", + "path": "crlf.py", + "touched_lines": [ + 1, + 2 + ] + } + ], + "functions": [ + { + "changed_lines": [ + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "crlf.py", + "qualname": "crlf_fn", + "source": "def crlf_fn():\n return 21" + } + ], + "ok": true, + "targets": [ + [ + "crlf.py", + "crlf_fn" + ] + ] + }, + "case": { + "expected_targets": [ + [ + "crlf.py", + "crlf_fn" + ] + ], + "git_valid": true, + "name": "git_crlf", + "optional_envelope": false, + "patch": "/home/jacob/testpilot-git-compat-6cc6795e89f4/docs/receiving/git-compat-6cc6795e89f4/patches/git_crlf.diff", + "sha256": "672b210a94fb9319e86d29d91027d732011c0cb34986539007d3012a4aa76ab6" + }, + "cli": { + "argv": [ + "/usr/bin/python3", + "-B", + "-m", + "testpilot", + "targets", + "--repo", + "/home/jacob/testpilot-git-compat-6cc6795e89f4-data/fixture", + "--diff", + "/home/jacob/testpilot-git-compat-6cc6795e89f4/docs/receiving/git-compat-6cc6795e89f4/patches/git_crlf.diff" + ], + "returncode": 0, + "stderr": "", + "stdout": "1 changed Python function outside tests\n\"crlf.py\":1-2 \"crlf_fn\"\n" + }, + "pass": true + }, + { + "baseline": { + "files": [ + { + "added_lines": [ + 2 + ], + "is_deleted": false, + "is_new": false, + "old_path": "odd\t\u00e4.py", + "path": "odd\t\u00e4.py", + "touched_lines": [ + 1, + 2 + ] + } + ], + "functions": [ + { + "changed_lines": [ + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "odd\t\u00e4.py", + "qualname": "odd_fn", + "source": "def odd_fn():\n return 41" + } + ], + "ok": true, + "targets": [ + [ + "odd\t\u00e4.py", + "odd_fn" + ] + ] + }, + "candidate": { + "files": [ + { + "added_lines": [ + 2 + ], + "is_deleted": false, + "is_new": false, + "old_path": "odd\t\u00e4.py", + "path": "odd\t\u00e4.py", + "touched_lines": [ + 1, + 2 + ] + } + ], + "functions": [ + { + "changed_lines": [ + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "odd\t\u00e4.py", + "qualname": "odd_fn", + "source": "def odd_fn():\n return 41" + } + ], + "ok": true, + "targets": [ + [ + "odd\t\u00e4.py", + "odd_fn" + ] + ] + }, + "case": { + "expected_targets": [ + [ + "odd\t\u00e4.py", + "odd_fn" + ] + ], + "git_valid": true, + "name": "git_quoted_path", + "optional_envelope": false, + "patch": "/home/jacob/testpilot-git-compat-6cc6795e89f4/docs/receiving/git-compat-6cc6795e89f4/patches/git_quoted_path.diff", + "sha256": "369169bf2ce73a58e47ff3af1e352a9a450f65fd8985ea1be9d74dde0ad859a9" + }, + "cli": { + "argv": [ + "/usr/bin/python3", + "-B", + "-m", + "testpilot", + "targets", + "--repo", + "/home/jacob/testpilot-git-compat-6cc6795e89f4-data/fixture", + "--diff", + "/home/jacob/testpilot-git-compat-6cc6795e89f4/docs/receiving/git-compat-6cc6795e89f4/patches/git_quoted_path.diff" + ], + "returncode": 0, + "stderr": "", + "stdout": "1 changed Python function outside tests\n\"odd\\t\\u00e4.py\":1-2 \"odd_fn\"\n" + }, + "pass": true + }, + { + "baseline": { + "files": [ + { + "added_lines": [ + 2, + 20 + ], + "is_deleted": false, + "is_new": false, + "old_path": "alpha.py", + "path": "alpha.py", + "touched_lines": [ + 1, + 2, + 19, + 20 + ] + }, + { + "added_lines": [ + 2 + ], + "is_deleted": false, + "is_new": false, + "old_path": "beta.py", + "path": "beta.py", + "touched_lines": [ + 1, + 2 + ] + }, + { + "added_lines": [ + 2 + ], + "is_deleted": false, + "is_new": false, + "old_path": "crlf.py", + "path": "crlf.py", + "touched_lines": [ + 1, + 2 + ] + }, + { + "added_lines": [], + "is_deleted": true, + "is_new": false, + "old_path": "deleted.py", + "path": "deleted.py", + "touched_lines": [ + 1 + ] + }, + { + "added_lines": [], + "is_deleted": false, + "is_new": false, + "old_path": "drop.py", + "path": "drop.py", + "touched_lines": [ + 2, + 3 + ] + }, + { + "added_lines": [ + 2 + ], + "is_deleted": false, + "is_new": false, + "old_path": "insert.py", + "path": "insert.py", + "touched_lines": [ + 2 + ] + }, + { + "added_lines": [ + 1, + 2 + ], + "is_deleted": false, + "is_new": true, + "old_path": null, + "path": "new.py", + "touched_lines": [ + 1, + 2 + ] + }, + { + "added_lines": [ + 2 + ], + "is_deleted": false, + "is_new": false, + "old_path": "odd\t\u00e4.py", + "path": "odd\t\u00e4.py", + "touched_lines": [ + 1, + 2 + ] + }, + { + "added_lines": [ + 2, + 3, + 4 + ], + "is_deleted": false, + "is_new": false, + "old_path": "payload.py", + "path": "payload.py", + "touched_lines": [ + 1, + 2, + 3, + 4 + ] + }, + { + "added_lines": [ + 2 + ], + "is_deleted": false, + "is_new": false, + "old_path": "unicode.py", + "path": "unicode.py", + "touched_lines": [ + 1, + 2 + ] + } + ], + "functions": [ + { + "changed_lines": [ + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "alpha.py", + "qualname": "first", + "source": "def first():\n return 2" + }, + { + "changed_lines": [ + 20 + ], + "end_lineno": 20, + "is_method": false, + "lineno": 19, + "path": "alpha.py", + "qualname": "second", + "source": "def second():\n return 4" + }, + { + "changed_lines": [ + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "beta.py", + "qualname": "beta", + "source": "def beta():\n return 11" + }, + { + "changed_lines": [ + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "crlf.py", + "qualname": "crlf_fn", + "source": "def crlf_fn():\n return 21" + }, + { + "changed_lines": [], + "end_lineno": 3, + "is_method": false, + "lineno": 1, + "path": "drop.py", + "qualname": "removedline", + "source": "def removedline():\n keep = 1\n return keep" + }, + { + "changed_lines": [ + 2 + ], + "end_lineno": 3, + "is_method": false, + "lineno": 1, + "path": "insert.py", + "qualname": "insertedline", + "source": "def insertedline():\n inserted = 50\n return 51" + }, + { + "changed_lines": [ + 1, + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "new.py", + "qualname": "gamma_unique", + "source": "def gamma_unique():\n return 'new-post-change-constant'" + }, + { + "changed_lines": [ + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "odd\t\u00e4.py", + "qualname": "odd_fn", + "source": "def odd_fn():\n return 41" + }, + { + "changed_lines": [ + 2, + 3, + 4 + ], + "end_lineno": 5, + "is_method": false, + "lineno": 1, + "path": "payload.py", + "qualname": "payload", + "source": "def payload():\n return \"\"\"new\n--- new literal\n+++ new literal\n\"\"\"" + }, + { + "changed_lines": [ + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "unicode.py", + "qualname": "unicode_fn", + "source": "def unicode_fn():\n return \"new\u2028tail\u2029end\"" + } + ], + "ok": true, + "targets": [ + [ + "alpha.py", + "first" + ], + [ + "alpha.py", + "second" + ], + [ + "beta.py", + "beta" + ], + [ + "crlf.py", + "crlf_fn" + ], + [ + "drop.py", + "removedline" + ], + [ + "insert.py", + "insertedline" + ], + [ + "new.py", + "gamma_unique" + ], + [ + "odd\t\u00e4.py", + "odd_fn" + ], + [ + "payload.py", + "payload" + ], + [ + "unicode.py", + "unicode_fn" + ] + ] + }, + "candidate": { + "error_type": "DiffFormatError", + "message": "invalid unified diff at record 158: body record exceeds the declared hunk ranges", + "ok": false, + "traceback": "Traceback (most recent call last):\n File \"/home/jacob/testpilot-git-compat-6cc6795e89f4/docs/receiving/git-compat-6cc6795e89f4/receive_candidate.py.txt\", line 40, in observe\n files = normalized_files(module,text)\n File \"/home/jacob/testpilot-git-compat-6cc6795e89f4/docs/receiving/git-compat-6cc6795e89f4/receive_candidate.py.txt\", line 33, in normalized_files\n for x in module.parse_unified_diff(text)]\n ~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^\n File \"/home/jacob/testpilot-git-compat-6cc6795e89f4/testpilot/diff.py\", line 200, in parse_unified_diff\n raise invalid(\"body record exceeds the declared hunk ranges\")\ntestpilot.diff.DiffFormatError: invalid unified diff at record 158: body record exceeds the declared hunk ranges\n" + }, + "case": { + "expected_targets": [ + [ + "alpha.py", + "first" + ], + [ + "alpha.py", + "second" + ], + [ + "beta.py", + "beta" + ], + [ + "crlf.py", + "crlf_fn" + ], + [ + "drop.py", + "removedline" + ], + [ + "insert.py", + "insertedline" + ], + [ + "new.py", + "gamma_unique" + ], + [ + "odd\t\u00e4.py", + "odd_fn" + ], + [ + "payload.py", + "payload" + ], + [ + "unicode.py", + "unicode_fn" + ] + ], + "git_valid": true, + "name": "git_mail_envelope", + "optional_envelope": true, + "patch": "/home/jacob/testpilot-git-compat-6cc6795e89f4/docs/receiving/git-compat-6cc6795e89f4/patches/git_mail_envelope.diff", + "sha256": "c997fa37a532088118d9caee74cad1a589ce0b0948c9653b733cbde7a3a2c21e" + }, + "cli": { + "argv": [ + "/usr/bin/python3", + "-B", + "-m", + "testpilot", + "targets", + "--repo", + "/home/jacob/testpilot-git-compat-6cc6795e89f4-data/fixture", + "--diff", + "/home/jacob/testpilot-git-compat-6cc6795e89f4/docs/receiving/git-compat-6cc6795e89f4/patches/git_mail_envelope.diff" + ], + "returncode": 2, + "stderr": "testpilot: cannot inspect targets: invalid unified diff at record 158: body record exceeds the declared hunk ranges\n", + "stdout": "" + }, + "pass": false + }, + { + "baseline": { + "files": [ + { + "added_lines": [ + 2, + 20 + ], + "is_deleted": false, + "is_new": false, + "old_path": "alpha.py", + "path": "alpha.py", + "touched_lines": [ + 1, + 2, + 19, + 20 + ] + } + ], + "functions": [ + { + "changed_lines": [ + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "alpha.py", + "qualname": "first", + "source": "def first():\n return 2" + }, + { + "changed_lines": [ + 20 + ], + "end_lineno": 20, + "is_method": false, + "lineno": 19, + "path": "alpha.py", + "qualname": "second", + "source": "def second():\n return 4" + } + ], + "ok": true, + "targets": [ + [ + "alpha.py", + "first" + ], + [ + "alpha.py", + "second" + ] + ] + }, + "candidate": { + "files": [ + { + "added_lines": [ + 2, + 20 + ], + "is_deleted": false, + "is_new": false, + "old_path": "alpha.py", + "path": "alpha.py", + "touched_lines": [ + 1, + 2, + 19, + 20 + ] + } + ], + "functions": [ + { + "changed_lines": [ + 2 + ], + "end_lineno": 2, + "is_method": false, + "lineno": 1, + "path": "alpha.py", + "qualname": "first", + "source": "def first():\n return 2" + }, + { + "changed_lines": [ + 20 + ], + "end_lineno": 20, + "is_method": false, + "lineno": 19, + "path": "alpha.py", + "qualname": "second", + "source": "def second():\n return 4" + } + ], + "ok": true, + "targets": [ + [ + "alpha.py", + "first" + ], + [ + "alpha.py", + "second" + ] + ] + }, + "case": { + "expected_targets": [ + [ + "alpha.py", + "first" + ], + [ + "alpha.py", + "second" + ] + ], + "git_valid": true, + "name": "gnu_diff_u_timestamps", + "optional_envelope": false, + "patch": "/home/jacob/testpilot-git-compat-6cc6795e89f4/docs/receiving/git-compat-6cc6795e89f4/patches/gnu_diff_u_timestamps.diff", + "sha256": "90735a0300b765bbf11cb51beca006a26dfcb71f2b5e4c00be8ac262d2742066" + }, + "cli": { + "argv": [ + "/usr/bin/python3", + "-B", + "-m", + "testpilot", + "targets", + "--repo", + "/home/jacob/testpilot-git-compat-6cc6795e89f4-data/fixture", + "--diff", + "/home/jacob/testpilot-git-compat-6cc6795e89f4/docs/receiving/git-compat-6cc6795e89f4/patches/gnu_diff_u_timestamps.diff" + ], + "returncode": 0, + "stderr": "", + "stdout": "2 changed Python functions outside tests\n\"alpha.py\":1-2 \"first\"\n\"alpha.py\":19-20 \"second\"\n" + }, + "pass": true + } + ], + "python": "3.14.4 (main, Aug 20 2026, 10:41:58) [GCC 15.2.0]", + "summary": { + "documented_cases": 10, + "documented_cases_passed": 10, + "malformed_cli_cases": 4, + "malformed_cli_cases_passed": 2, + "negative_api_refuses": true, + "source_unchanged": { + "testpilot/__main__.py": true, + "testpilot/diff.py": true + }, + "valid_cases": 11, + "valid_cases_passed": 10 + }, + "worktree": "/home/jacob/testpilot-git-compat-6cc6795e89f4" +} diff --git a/docs/receiving/git-compat-6cc6795e89f4/patches/git_binary_python.diff b/docs/receiving/git-compat-6cc6795e89f4/patches/git_binary_python.diff new file mode 100644 index 0000000..f744814 --- /dev/null +++ b/docs/receiving/git-compat-6cc6795e89f4/patches/git_binary_python.diff @@ -0,0 +1,39 @@ +diff --git a/alpha.py b/alpha.py +index c146194..d9f6196 100644 +--- a/alpha.py ++++ b/alpha.py +@@ -1,5 +1,5 @@ + def first(): +- return 1 ++ return 2 + + # gap 00 + # gap 01 +@@ -17,7 +17,7 @@ def first(): + # gap 13 + + def second(): +- return 3 ++ return 4 + + def unchanged(): + return 99 +diff --git a/blob.bin b/blob.bin +index c8b49c8cd518e58491924bfc364ff26e01a85009..991c98a05128d446b067c2ae39bd7dff0a11686a 100644 +GIT binary patch +literal 1024 +zcmezW@9&@AzkdGs{_X3R&!0Yic>nJ0o7b;izIguZ>66Eg9zM8#@9v%3w{G6Je(ma& +z%a<-*IDhW!nbW6Eo;ZH&=#j&R4j$OQZ||PnyLRr_zHRH4&6_rESif%Vn$@dTu2{Zo +z>5|2Z7A}}SZ|F(<6Xm4w6X>Mw4sIRN7sjjN5C@(85 +zDK083$j{5o$mNlr>kh>weniH?el2oDPl2@VPj@b~le@%HlcaCdWcadvWa +zu(z|dv9_|bFgG(bF*Y(Z(AU$|(bm$`P*+n`QC3n^ke8E{k(QE_5El~_5f%~@;OFDz +a;pXDxU}s}xVP;}v_&@6Z(fA+1=^p@?&i)Vp + +literal 1024 +zcmZQzWMXDvWn<^yMC+6cQE@6%&_`l#-T_m6KOcR8m$^Ra4i{)Y8_`)zddH +zG%_|ZH8Z!cw6eCbwX=6{baHlab#wRd^z!!c_45x13RUz +zF>}`JIdkXDU$Ah|;w4L$Enl&6)#^2C*R9{Mant54TeofBv2)k%J$v`nJ0o7b;izIguZ>66Eg9zM8#@9v%3w{G6Je(ma& +z%a<-*IDhW!nbW6Eo;ZH&=#j&R4j$OQZ||PnyLRr_zHRH4&6_rESif%Vn$@dTu2{Zo +z>5|2Z7A}}SZ|F(<6Xm4w6X>Mw4sIRN7sjjN5C@(85 +zDK083$j{5o$mNlr>kh>weniH?el2oDPl2@VPj@b~le@%HlcaCdWcadvWa +zu(z|dv9_|bFgG(bF*Y(Z(AU$|(bm$`P*+n`QC3n^ke8E{k(QE_5El~_5f%~@;OFDz +a;pXDxU}s}xVP;}v_&@6Z(fA+1=^p@?&i)Vp + +literal 1024 +zcmZQzWMXDvWn<^yMC+6cQE@6%&_`l#-T_m6KOcR8m$^Ra4i{)Y8_`)zddH +zG%_|ZH8Z!cw6eCbwX=6{baHlab#wRd^z!!c_45x13RUz +zF>}`JIdkXDU$Ah|;w4L$Enl&6)#^2C*R9{Mant54TeofBv2)k%J$v`nJ0o7b;izIguZ>66Eg9zM8#@9v%3w{G6Je(ma& +z%a<-*IDhW!nbW6Eo;ZH&=#j&R4j$OQZ||PnyLRr_zHRH4&6_rESif%Vn$@dTu2{Zo +z>5|2Z7A}}SZ|F(<6Xm4w6X>Mw4sIRN7sjjN5C@(85 +zDK083$j{5o$mNlr>kh>weniH?el2oDPl2@VPj@b~le@%HlcaCdWcadvWa +zu(z|dv9_|bFgG(bF*Y(Z(AU$|(bm$`P*+n`QC3n^ke8E{k(QE_5El~_5f%~@;OFDz +a;pXDxU}s}xVP;}v_&@6Z(fA+1=^p@?&i)Vp + +literal 1024 +zcmZQzWMXDvWn<^yMC+6cQE@6%&_`l#-T_m6KOcR8m$^Ra4i{)Y8_`)zddH +zG%_|ZH8Z!cw6eCbwX=6{baHlab#wRd^z!!c_45x13RUz +zF>}`JIdkXDU$Ah|;w4L$Enl&6)#^2C*R9{Mant54TeofBv2)k%J$v` +Date: Thu, 8 Oct 2026 17:22:18 +0000 +Subject: [PATCH] fixture after + +--- + alpha.py | 4 ++-- + beta.py | 2 +- + blob.bin | Bin 1024 -> 1024 bytes + copy_from.py => copy_to.py | 0 + crlf.py | 2 +- + deleted.py | 2 -- + drop.py | 1 - + insert.py | 1 + + mode.py | 0 + new.py | 2 ++ + "odd\t\303\244.py" | 2 +- + payload.py | 6 +++--- + rename_before.py => rename_after.py | 0 + unicode.py | 2 +- + 14 files changed, 12 insertions(+), 12 deletions(-) + copy copy_from.py => copy_to.py (100%) + delete mode 100644 deleted.py + mode change 100644 => 100755 mode.py + create mode 100644 new.py + rename rename_before.py => rename_after.py (100%) + +diff --git a/alpha.py b/alpha.py +index c146194..d9f6196 100644 +--- a/alpha.py ++++ b/alpha.py +@@ -1,5 +1,5 @@ + def first(): +- return 1 ++ return 2 + + # gap 00 + # gap 01 +@@ -17,7 +17,7 @@ def first(): + # gap 13 + + def second(): +- return 3 ++ return 4 + + def unchanged(): + return 99 +diff --git a/beta.py b/beta.py +index 602f556..92177fc 100644 +--- a/beta.py ++++ b/beta.py +@@ -1,2 +1,2 @@ + def beta(): +- return 10 +\ No newline at end of file ++ return 11 +\ No newline at end of file +diff --git a/blob.bin b/blob.bin +index c8b49c8cd518e58491924bfc364ff26e01a85009..991c98a05128d446b067c2ae39bd7dff0a11686a 100644 +GIT binary patch +literal 1024 +zcmezW@9&@AzkdGs{_X3R&!0Yic>nJ0o7b;izIguZ>66Eg9zM8#@9v%3w{G6Je(ma& +z%a<-*IDhW!nbW6Eo;ZH&=#j&R4j$OQZ||PnyLRr_zHRH4&6_rESif%Vn$@dTu2{Zo +z>5|2Z7A}}SZ|F(<6Xm4w6X>Mw4sIRN7sjjN5C@(85 +zDK083$j{5o$mNlr>kh>weniH?el2oDPl2@VPj@b~le@%HlcaCdWcadvWa +zu(z|dv9_|bFgG(bF*Y(Z(AU$|(bm$`P*+n`QC3n^ke8E{k(QE_5El~_5f%~@;OFDz +a;pXDxU}s}xVP;}v_&@6Z(fA+1=^p@?&i)Vp + +literal 1024 +zcmZQzWMXDvWn<^yMC+6cQE@6%&_`l#-T_m6KOcR8m$^Ra4i{)Y8_`)zddH +zG%_|ZH8Z!cw6eCbwX=6{baHlab#wRd^z!!c_45x13RUz +zF>}`JIdkXDU$Ah|;w4L$Enl&6)#^2C*R9{Mant54TeofBv2)k%J$v`nJ0o7b;izIguZ>66Eg9zM8#@9v%3w{G6Je(ma& +z%a<-*IDhW!nbW6Eo;ZH&=#j&R4j$OQZ||PnyLRr_zHRH4&6_rESif%Vn$@dTu2{Zo +z>5|2Z7A}}SZ|F(<6Xm4w6X>Mw4sIRN7sjjN5C@(85 +zDK083$j{5o$mNlr>kh>weniH?el2oDPl2@VPj@b~le@%HlcaCdWcadvWa +zu(z|dv9_|bFgG(bF*Y(Z(AU$|(bm$`P*+n`QC3n^ke8E{k(QE_5El~_5f%~@;OFDz +a;pXDxU}s}xVP;}v_&@6Z(fA+1=^p@?&i)Vp + +literal 1024 +zcmZQzWMXDvWn<^yMC+6cQE@6%&_`l#-T_m6KOcR8m$^Ra4i{)Y8_`)zddH +zG%_|ZH8Z!cw6eCbwX=6{baHlab#wRd^z!!c_45x13RUz +zF>}`JIdkXDU$Ah|;w4L$Enl&6)#^2C*R9{Mant54TeofBv2)k%J$v`nJ0o7b;izIguZ>66Eg9zM8#@9v%3w{G6Je(ma& +z%a<-*IDhW!nbW6Eo;ZH&=#j&R4j$OQZ||PnyLRr_zHRH4&6_rESif%Vn$@dTu2{Zo +z>5|2Z7A}}SZ|F(<6Xm4w6X>Mw4sIRN7sjjN5C@(85 +zDK083$j{5o$mNlr>kh>weniH?el2oDPl2@VPj@b~le@%HlcaCdWcadvWa +zu(z|dv9_|bFgG(bF*Y(Z(AU$|(bm$`P*+n`QC3n^ke8E{k(QE_5El~_5f%~@;OFDz +a;pXDxU}s}xVP;}v_&@6Z(fA+1=^p@?&i)Vp + +literal 1024 +zcmZQzWMXDvWn<^yMC+6cQE@6%&_`l#-T_m6KOcR8m$^Ra4i{)Y8_`)zddH +zG%_|ZH8Z!cw6eCbwX=6{baHlab#wRd^z!!c_45x13RUz +zF>}`JIdkXDU$Ah|;w4L$Enl&6)#^2C*R9{Mant54TeofBv2)k%J$v` int: except TargetSelectionError as e: print(f"testpilot: invalid target selection: {e}", file=sys.stderr) return 2 + else: + try: + parse_unified_diff(diff_text) + except DiffFormatError as e: + print(f"testpilot: invalid diff format: {e}", file=sys.stderr) + return 2 try: client = make_client(a.backend, script=a.script, base_url=a.base_url) except ModelError as e: @@ -147,6 +153,9 @@ def main(argv: list[str] | None = None) -> int: except DiffSourceError as e: print(f"testpilot: invalid diff source: {e}", file=sys.stderr) return 2 + except DiffFormatError as e: + print(f"testpilot: invalid diff format: {e}", file=sys.stderr) + return 2 paths = write_outputs(res, a.out) print(render_report(res).split("\n## Patch")[0]) print(f"wrote {', '.join(str(p) for p in paths.values())}") diff --git a/testpilot/diff.py b/testpilot/diff.py index 29dd325..ab4d562 100644 --- a/testpilot/diff.py +++ b/testpilot/diff.py @@ -14,6 +14,7 @@ from pathlib import Path, PurePosixPath _HUNK = re.compile(r"^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@") +_MAIL_FROM = re.compile(r"From (?:[0-9a-f]{40}|[0-9a-f]{64}) Mon Sep 17 00:00:00 2001") @dataclass @@ -91,45 +92,93 @@ def _strip_path(raw: str) -> str | None: return p +class DiffFormatError(ValueError): + """A unified diff has incomplete headers, ranges, or hunk records.""" + + def parse_unified_diff(text: str) -> list[FileChange]: - """Parse ``git diff`` / ``diff -u`` output into per-file changed line sets. + """Parse complete git diff / diff -u hunks into changed line sets. - Hunk line counters are tracked so that removed lines whose content starts - with ``-- `` are not mistaken for file headers. + Declared old/new counts must match the complete body. A damaged later hunk + rejects the whole diff, so callers never act on a partial target selection. + Header-looking source lines remain body records while counts are pending. """ files: list[FileChange] = [] cur: FileChange | None = None pending_old: str | None = None + pending_header = False + saw_hunk = False old_left = new_left = 0 new_ln = 0 - # Git hunk counts use LF-delimited records, not Unicode separators. - for line in text.split("\n"): + + def invalid(reason: str) -> DiffFormatError: + return DiffFormatError(f"invalid unified diff at record {record_number}: {reason}") + + # Git records use LF, not Unicode separators. The token after a terminal LF + # is not a blank context record and must not satisfy a missing hunk line. + records = text.split("\n") + if records[-1] == "": + records.pop() + mail_envelope = bool(records and _MAIL_FROM.fullmatch(records[0].removesuffix("\r"))) + record_number = 0 + for record_number, line in enumerate(records, 1): + line = line.removesuffix("\r") # A saved diff may use CRLF records. + # A Git mail's terminal plain-text signature is metadata. A deleted + # source line can be "-- "; the footer cannot fill a missing old line. + if mail_envelope and saw_hunk and line == "-- " and record_number < len(records): + signature = records[record_number].removesuffix("\r") + if signature and all( + not records[index].removesuffix("\r").startswith(("+", "-", " ", "@@", "diff --git ")) + for index in range(record_number, len(records)) + ): + if old_left or new_left: + raise invalid("signature footer before a complete hunk") + continue if cur is not None and (old_left > 0 or new_left > 0): - if line.startswith("\\"): # "\ No newline at end of file" + if line == "\\ No newline at end of file": continue if line.startswith("+"): + if new_left == 0: + raise invalid("addition exceeds the new range") cur.added_lines.add(new_ln) cur.touched_lines.add(new_ln) new_ln += 1 new_left -= 1 elif line.startswith("-"): - # anchor a pure deletion to the surrounding new-file lines + if old_left == 0: + raise invalid("deletion exceeds the old range") + # Anchor a pure deletion to the surrounding new-file lines. for anchor in (new_ln - 1, new_ln): if anchor >= 1: cur.touched_lines.add(anchor) old_left -= 1 - else: # context line (" " prefix, or blank if whitespace was stripped) + elif line.startswith(" ") or line == "": + # Retain support for a blank context line with its space stripped. + if old_left == 0 or new_left == 0: + raise invalid("context exceeds a declared range") new_ln += 1 new_left -= 1 old_left -= 1 + else: + raise invalid("incomplete hunk or invalid body prefix") continue if line.startswith("diff --git "): + if pending_header or (cur is not None and not saw_hunk): + raise invalid("file headers have no complete hunk") cur, pending_old = None, None + saw_hunk = False continue if line.startswith("--- "): + if pending_header or (cur is not None and not saw_hunk): + raise invalid("file headers have no complete hunk") + cur = None pending_old = _strip_path(line[4:]) + pending_header = True + saw_hunk = False continue if line.startswith("+++ "): + if not pending_header: + raise invalid("new file header has no old file header") new_path = _strip_path(line[4:]) cur = FileChange( path=new_path or pending_old or "", @@ -138,14 +187,37 @@ def parse_unified_diff(text: str) -> list[FileChange]: is_deleted=new_path is None, ) files.append(cur) + pending_header = False continue - m = _HUNK.match(line) - if m and cur is not None: - old_left = int(m.group(2)) if m.group(2) is not None else 1 - new_left = int(m.group(4)) if m.group(4) is not None else 1 - new_ln = int(m.group(3)) - if new_left == 0: # pure deletion hunk: "+N,0" means "after line N" + if line.startswith("@@"): + m = _HUNK.match(line) + if m is None or cur is None: + raise invalid("malformed hunk range or missing file headers") + try: + old_start, new_start = int(m.group(1)), int(m.group(3)) + old_left = int(m.group(2)) if m.group(2) is not None else 1 + new_left = int(m.group(4)) if m.group(4) is not None else 1 + except ValueError as exc: + raise invalid("hunk range is not a supported integer") from exc + if (old_left and old_start == 0) or (new_left and new_start == 0): + raise invalid("nonempty hunk ranges must start at line one or later") + if old_left == 0 and new_left == 0: + raise invalid("hunk has no source records") + saw_hunk = True + new_ln = new_start + if new_left == 0: # "+N,0" means "after line N". new_ln += 1 + continue + if line == "\\ No newline at end of file": + if not saw_hunk: + raise invalid("newline marker has no hunk") + continue + if saw_hunk and line.startswith(("+", "-", " ")): + raise invalid("body record exceeds the declared hunk ranges") + if old_left or new_left: + raise invalid(f"incomplete hunk ({old_left} old and {new_left} new records missing)") + if pending_header or (cur is not None and not saw_hunk): + raise invalid("file headers have no complete hunk") return files diff --git a/tests/test_diff_hunk_validation.py b/tests/test_diff_hunk_validation.py new file mode 100644 index 0000000..0befbe4 --- /dev/null +++ b/tests/test_diff_hunk_validation.py @@ -0,0 +1,307 @@ +"""Receive complete unified hunks before selecting or executing Python targets.""" +from __future__ import annotations + +import json +import os +from pathlib import Path +import re +import subprocess +import sys + +import pytest + +from testpilot.diff import DiffFormatError, changed_functions, parse_unified_diff +from testpilot.loop import TestPilot +from testpilot.model import ScriptedModel + + +ROOT = Path(__file__).resolve().parents[1] +HEADER = "--- a/sample.py\n+++ b/sample.py\n" +VALID = HEADER + "@@ -1,2 +1,2 @@\n def selected():\n- return 1\n+ return 2\n" +INVALID = [ + pytest.param(VALID.rsplit("+ return 2", 1)[0], id="truncated-addition"), + pytest.param(VALID.replace("-1,2 +1,2", "-1,3 +1,3"), id="missing-context-at-eof"), + pytest.param(VALID + "+extra\n", id="extra-addition"), + pytest.param(VALID + "-extra\n", id="extra-deletion"), + pytest.param(VALID + "-- \n2.53.0\n\n", id="mail-footer-outside-envelope"), + pytest.param(VALID + " extra\n", id="extra-context"), + pytest.param(HEADER + "@@ -0,0 +1,1 @@\n-deleted\n+added\n", id="old-range-underflow"), + pytest.param(HEADER + "@@ -1,1 +0,0 @@\n+added\n-deleted\n", id="new-range-underflow"), + pytest.param(HEADER + "@@ -0,0 +1,2 @@\n context\n+added\n", id="context-with-no-old-lines"), + pytest.param(HEADER + "@@ -1,1 +1,1 @@\nunprefixed\n", id="invalid-body-prefix"), + pytest.param(HEADER + "@@ -one +two @@\n-old\n+new\n", id="malformed-range"), + pytest.param("@@ -1 +1 @@\n-old\n+new\n", id="missing-file-headers"), + pytest.param("+++ b/sample.py\n@@ -1 +1 @@\n-old\n+new\n", id="missing-old-header"), + pytest.param(HEADER, id="headers-without-hunk"), + pytest.param(VALID.replace("-1,2 +1,2", "-1,4 +1,4") + VALID, id="next-file-before-end"), + pytest.param(VALID.replace("-1,2 +1,2", "-1,4 +1,4") + + "@@ -5 +5 @@\n-old\n+new\n", id="next-hunk-before-end"), + pytest.param(HEADER + "@@ -0,1 +1,1 @@\n-old\n+new\n", id="nonempty-range-at-zero"), +] + + +@pytest.mark.parametrize("diff_text", INVALID) +def test_inconsistent_hunks_are_not_partial_success(diff_text): + with pytest.raises(ValueError, match="diff"): + parse_unified_diff(diff_text) + + +def _fixture_env(): + env = {key: value for key, value in os.environ.items() if not key.startswith("GIT_")} + env.update(GIT_CONFIG_NOSYSTEM="1", GIT_CONFIG_GLOBAL=os.devnull) + return env + + +def _git(repo, *args, check=True, input=None): + return subprocess.run( + ["git", "-C", str(repo), *args], check=check, capture_output=True, + text=True, input=input, timeout=15, env=_fixture_env(), + ) + + +@pytest.fixture +def git_change(tmp_path): + repo = tmp_path / "project" + repo.mkdir() + _git(repo, "init", "-q") + _git(repo, "config", "core.autocrlf", "false") + source = repo / "sample.py" + source.write_text("def selected():\n return 1\n", encoding="utf-8") + _git(repo, "add", "sample.py") + source.write_text("def selected():\n return 2\n", encoding="utf-8") + diff_text = _git(repo, "diff", "--no-ext-diff", "--no-textconv", "--", "sample.py").stdout + return repo, diff_text + + +def _missing_context(diff_text): + match = re.search(r"@@ -(\d+),(\d+) \+(\d+),(\d+) @@", diff_text) + assert match is not None + old_start, old_count, new_start, new_count = map(int, match.groups()) + changed = f"@@ -{old_start},{old_count + 1} +{new_start},{new_count + 1} @@" + return diff_text[:match.start()] + changed + diff_text[match.end():] + + +def test_actual_git_patch_and_corrupted_counter_have_distinct_outcomes(git_change): + repo, real_diff = git_change + reverse = _git(repo, "apply", "--reverse", "--check", "-", input=real_diff) + assert reverse.returncode == 0 + selected = changed_functions(repo, real_diff) + assert [(f.qualname, f.changed_lines, f.source) for f in selected] == [ + ("selected", [2], "def selected():\n return 2") + ] + corrupted = _missing_context(real_diff) + refusal = _git(repo, "apply", "--reverse", "--check", "-", input=corrupted, check=False) + assert refusal.returncode != 0 + with pytest.raises(ValueError, match="diff"): + changed_functions(repo, corrupted) + + +def test_invalid_later_file_rejects_before_source_reads_and_execution(git_change, monkeypatch): + repo, real_diff = git_change + calls = [] + client = ScriptedModel(["must remain unused"]) + + def forbidden(*args, **kwargs): + calls.append("source-or-pytest") + raise AssertionError("damaged diff reached source or test execution") + + monkeypatch.setattr("testpilot.diff.tokenize.open", forbidden) + monkeypatch.setattr("testpilot.loop.run_pytest", forbidden) + damaged = real_diff + HEADER.replace("sample.py", "other.py") + "@@ -1 +1 @@\n-old\n" + with pytest.raises(ValueError, match="diff"): + TestPilot(client).run(repo, damaged) + assert calls == [] + assert client.calls == [] + + +def _cli(repo, *args, stdin=None): + env = _fixture_env() + env.update(PYTHONDONTWRITEBYTECODE="1", PYTHONPATH=str(ROOT)) + return subprocess.run( + [sys.executable, "-B", "-m", "testpilot", *map(str, args)], + cwd=repo.parent, env=env, input=stdin, capture_output=True, text=True, timeout=30, + ) + + +def test_native_targets_cli_refuses_damaged_stdin_without_success_json(git_change): + repo, real_diff = git_change + valid = _cli(repo, "targets", "--repo", repo, "--diff", "-", "--json", stdin=real_diff) + assert valid.returncode == 0, valid.stderr + assert len(json.loads(valid.stdout)["changed_functions"]) == 1 + invalid = _cli(repo, "targets", "--repo", repo, "--diff", "-", "--json", + stdin=_missing_context(real_diff)) + assert invalid.returncode == 2 + assert invalid.stdout == "" + assert "testpilot: cannot inspect targets:" in invalid.stderr + assert "diff" in invalid.stderr + assert "Traceback" not in invalid.stderr + + +def test_native_run_cli_refuses_damaged_file_before_model_tests_or_output(git_change): + repo, real_diff = git_change + inputs = repo.parent / "script" + inputs.mkdir() + (inputs / "01_plan.md").write_text("Check selected returns two.\n", encoding="utf-8") + fence = chr(96) * 3 + (inputs / "02_tests.md").write_text( + fence + "python tests/test_selected.py\nfrom sample import selected\n" + "def test_selected():\n assert selected() == 2\n" + fence + "\n", encoding="utf-8") + patch = repo.parent / "damaged.diff" + patch.write_text(_missing_context(real_diff), encoding="utf-8") + output = repo.parent / "result" + run = _cli(repo, "run", "--repo", repo, "--diff", patch, + "--backend", "scripted", "--script", inputs, "--rounds", 0, "--out", output) + assert run.returncode == 2 + assert run.stdout == "" + assert "testpilot: invalid diff format:" in run.stderr + assert "Traceback" not in run.stderr + assert not output.exists() + assert (repo / "sample.py").read_text(encoding="utf-8") == "def selected():\n return 2\n" + + +@pytest.mark.parametrize("newline", ["\n", "\r\n"]) +def test_empty_context_and_no_newline_markers_preserve_complete_hunks(newline): + diff_text = ( + HEADER + "@@ -1,3 +1,3 @@\n" + " def selected():\n\n- return 1\n\\ No newline at end of file\n" + "+ return 2\n\\ No newline at end of file\n" + ).replace("\n", newline) + files = parse_unified_diff(diff_text) + assert len(files) == 1 + assert files[0].added_lines == {3} + assert files[0].touched_lines == {2, 3} + + +def test_valid_final_record_without_lf_is_counted_once(): + assert parse_unified_diff(VALID.rstrip("\n"))[0].added_lines == {2} + + +def test_new_deleted_and_zero_context_hunks_from_git(tmp_path): + repo = tmp_path / "project" + repo.mkdir() + _git(repo, "init", "-q") + (repo / "gone.py").write_text("def gone():\n return 0\n", encoding="utf-8") + (repo / "kept.py").write_text("def kept():\n discard = 1\n return 2\n", encoding="utf-8") + _git(repo, "add", ".") + (repo / "gone.py").unlink() + (repo / "kept.py").write_text("def kept():\n return 2\n", encoding="utf-8") + (repo / "new.py").write_text('def fresh():\n return "left\u2028right"', encoding="utf-8") + _git(repo, "add", "-N", "new.py") + diff_text = _git(repo, "diff", "--no-ext-diff", "--no-textconv", "--unified=0").stdout + assert _git(repo, "apply", "--reverse", "--check", "--unidiff-zero", "-", input=diff_text).returncode == 0 + files = parse_unified_diff(diff_text) + assert {f.path for f in files if f.is_deleted} == {"gone.py"} + assert {f.path for f in files if f.is_new} == {"new.py"} + selected = changed_functions(repo, diff_text) + assert [(f.qualname, f.changed_lines) for f in selected] == [("kept", []), ("fresh", [1, 2])] + + +@pytest.mark.parametrize("range_field", [0, 1, 2, 3], + ids=["old-start", "old-count", "new-start", "new-count"]) +def test_unsupported_hunk_integer_has_clean_parser_and_run_errors(git_change, monkeypatch, range_field): + digit_limit = getattr(sys, "get_int_max_str_digits", lambda: 0)() + if not digit_limit: + pytest.skip("this runtime does not limit decimal integer conversion") + monkeypatch.setenv("PYTHONINTMAXSTRDIGITS", str(digit_limit)) + repo, _ = git_change + fields = ["1", "2", "1", "2"] + fields[range_field] = "9" * (digit_limit + 1) + header = f"@@ -{fields[0]},{fields[1]} +{fields[2]},{fields[3]} @@" + damaged = VALID.replace("@@ -1,2 +1,2 @@", header) + with pytest.raises(DiffFormatError, match="hunk range"): + parse_unified_diff(damaged) + script = repo.parent / "script" + script.mkdir() + (script / "01.md").write_text("Must remain unused.\n", encoding="utf-8") + patch = repo.parent / "oversize.diff" + patch.write_text(damaged, encoding="utf-8") + output = repo.parent / "result" + run = _cli(repo, "run", "--repo", repo, "--diff", patch, + "--backend", "scripted", "--script", script, "--rounds", 0, "--out", output) + assert run.returncode == 2 + assert run.stdout == "" + assert "testpilot: invalid diff format:" in run.stderr + assert "Traceback" not in run.stderr + assert not output.exists() + + +def _mail_patch(repo, signature=None): + identity = ("-c", "user.name=TestPilot fixture", "-c", "user.email=testpilot@example.invalid") + _git(repo, *identity, "commit", "-qm", "Before change") + _git(repo, "add", "sample.py") + _git(repo, *identity, "commit", "-qm", "Change selected") + version = _git(repo, "--version").stdout.removeprefix("git version ").strip() + return _git(repo, "format-patch", "--stdout", "--no-stat", + "--signature=" + (version if signature is None else signature), "-1").stdout + + +@pytest.fixture(params=[None, "HAMON Receiving Fixture"], ids=["git-version", "custom-text"]) +def mail_change(git_change, request): + repo, _ = git_change + return repo, _mail_patch(repo, request.param) + + +def test_actual_git_mail_footer_preserves_complete_target_selection(mail_change): + repo, patch = mail_change + assert patch.startswith("From ") and "\n-- \n" in patch + assert _git(repo, "apply", "--reverse", "--check", "-", input=patch).returncode == 0 + selected = changed_functions(repo, patch) + assert [(f.qualname, f.changed_lines, f.source) for f in selected] == [ + ("selected", [2], "def selected():\n return 2") + ] + + +@pytest.mark.parametrize("missing", ["old", "new", "both", "later-file", "after-footer"]) +def test_git_mail_footer_never_completes_a_missing_body_record(mail_change, missing): + _, patch = mail_change + if missing == "later-file": + body, footer = patch.rsplit("\n-- \n", 1) + patch = body + "\n" + HEADER.replace("sample.py", "other.py") + "@@ -1 +1 @@\n-old\n-- \n" + footer + elif missing == "after-footer": + patch += HEADER.replace("sample.py", "other.py") + "@@ -1 +1 @@\n-old\n" + else: + old_count = 3 if missing in ("old", "both") else 2 + new_count = 3 if missing in ("new", "both") else 2 + patch = patch.replace("@@ -1,2 +1,2 @@", f"@@ -1,{old_count} +1,{new_count} @@") + with pytest.raises(DiffFormatError, match="diff"): + parse_unified_diff(patch) + + +def test_mail_can_delete_source_text_shaped_like_the_signature_delimiter(git_change): + repo, _ = git_change + source = repo / "sample.py" + source.write_text("def selected():\n return '''before\n- \nafter'''\n", encoding="utf-8") + _git(repo, "add", "sample.py") + source.write_text("def selected():\n return '''after'''\n", encoding="utf-8") + patch = _mail_patch(repo) + assert patch.count("\n-- \n") == 2 + assert _git(repo, "apply", "--reverse", "--check", "-", input=patch).returncode == 0 + selected = changed_functions(repo, patch) + assert [(f.qualname, f.changed_lines, f.source) for f in selected] == [ + ("selected", [2], "def selected():\n return '''after'''") + ] + + +@pytest.mark.parametrize("existing_output", [False, True]) +def test_native_diff_error_precedes_model_config_and_preserves_output(git_change, existing_output): + repo, real_diff = git_change + patch = repo.parent / "damaged.diff" + patch.write_text(_missing_context(real_diff), encoding="utf-8") + output = repo.parent / "result" + retained = {} + if existing_output: + output.mkdir() + for name in ("report.json", "report.html", "report.md", "testpilot.patch"): + content = ("retain original " + name + "\n").encode("utf-8") + (output / name).write_bytes(content) + retained[name] = content + run = _cli(repo, "run", "--repo", repo, "--diff", patch, "--backend", "scripted", + "--script", repo.parent / "missing-model-config", "--rounds", 0, "--out", output) + assert run.returncode == 2 + assert run.stdout == "" + assert "testpilot: invalid diff format:" in run.stderr + assert "Traceback" not in run.stderr + if existing_output: + assert {p.name: p.read_bytes() for p in output.iterdir()} == retained + else: + assert not output.exists()