|
| 1 | +#!/usr/bin/env bash |
| 2 | +# tests/scripts/test-rc-diff-is-empty-net.sh — c9e9 unit test |
| 3 | +# |
| 4 | +# Drives the PRODUCTION rc_diff_is_empty_net (review-coverage-lib.sh) over REAL git |
| 5 | +# fixtures. This is the SHARED "genuinely-empty net diff" exemption helper, a sibling |
| 6 | +# of rc_diff_is_tickets_only, recognizing a review-exempt class distinct from the |
| 7 | +# I-1 empty-file-list fail-closed case: a 2+-parent MERGE whose combined (--cc) diff |
| 8 | +# is EMPTY (rc==0, zero file entries) carries no net change to review. |
| 9 | +# |
| 10 | +# SAFETY CRUX (preserve exactly): rc==0 is MANDATORY. Emptiness alone NEVER implies |
| 11 | +# "genuinely empty" — an uncomputable diff-tree (bad sha, rc!=0) also produces empty |
| 12 | +# output and MUST be ERROR (2), so every caller fails closed on doubt. |
| 13 | +# |
| 14 | +# E1 clean 2-parent empty-net merge -> EXEMPT (0) |
| 15 | +# E2 clean octopus (3-parent) empty-net merge -> EXEMPT (0) |
| 16 | +# E3 evil 2-parent merge (own content in merge) -> NOT exempt (1) |
| 17 | +# NOTE: a CLEAN auto-merge of disjoint, non-conflicting changes has an EMPTY |
| 18 | +# --cc combined diff by definition (--cc only shows paths differing from ALL |
| 19 | +# parents), so it is correctly empty-net/EXEMPT — the not-exempt cases are the |
| 20 | +# merges that introduce OWN content: conflict-resolution (E4) and evil (E3/E6). |
| 21 | +# E4 conflict-resolution merge (own content) -> NOT exempt (1) |
| 22 | +# E5 single-parent content commit -> NOT exempt (1) (not a merge) |
| 23 | +# E6 evil octopus (own content in merge) -> NOT exempt (1) |
| 24 | +# E7 bogus / unknown SHA -> ERROR (2) (fail closed) |
| 25 | + |
| 26 | +set -uo pipefail |
| 27 | +export GIT_CONFIG_GLOBAL=/dev/null GIT_CONFIG_SYSTEM=/dev/null GIT_TERMINAL_PROMPT=0 |
| 28 | +REPO_ROOT="$(git rev-parse --show-toplevel)" |
| 29 | +LIB="$REPO_ROOT/plugins/dso/scripts/lib/review-coverage-lib.sh" # shim-exempt: test sources the lib under test |
| 30 | + |
| 31 | +PASS=0; FAIL=0 |
| 32 | +_pass() { echo "PASS: $1"; PASS=$((PASS+1)); } |
| 33 | +_fail() { echo "FAIL: $1 ($2)"; FAIL=$((FAIL+1)); } |
| 34 | + |
| 35 | +_W="$(mktemp -d "${TMPDIR:-/tmp}/dso-emptynet.XXXXXX")"; trap 'rm -rf "$_W"' EXIT |
| 36 | +R="$_W/repo"; mkdir -p "$R" |
| 37 | +( |
| 38 | + cd "$R" || exit 1 |
| 39 | + git init -q -b main |
| 40 | + git config user.email t@e.st; git config user.name t; git config commit.gpgsign false |
| 41 | + echo base > README.md; git add README.md; git commit -q -m base |
| 42 | + base_sha="$(git rev-parse HEAD)" |
| 43 | + |
| 44 | + # E1 clean 2-parent empty-net merge: a side branch whose change is identical to |
| 45 | + # what main already has (so the merge introduces NO net change in the combined diff). |
| 46 | + git checkout -q -b e1side "$base_sha" |
| 47 | + echo shared > shared.txt; git add shared.txt; git commit -q -m e1-side |
| 48 | + git checkout -q main |
| 49 | + echo shared > shared.txt; git add shared.txt; git commit -q -m e1-main |
| 50 | + git merge -q --no-ff -m "e1 empty-net merge" e1side || true |
| 51 | + git rev-parse HEAD > "$_W/E1" |
| 52 | + e1_after="$(git rev-parse HEAD)" |
| 53 | + |
| 54 | + # E2 clean octopus (3-parent) empty-net merge. Two side branches that each add a |
| 55 | + # file ALSO added identically on main before the merge -> combined --cc diff empty. |
| 56 | + git checkout -q -b e2a "$e1_after" |
| 57 | + echo o1 > oct1.txt; git add oct1.txt; git commit -q -m e2a |
| 58 | + git checkout -q -b e2b "$e1_after" |
| 59 | + echo o2 > oct2.txt; git add oct2.txt; git commit -q -m e2b |
| 60 | + git checkout -q main |
| 61 | + echo o1 > oct1.txt; echo o2 > oct2.txt; git add oct1.txt oct2.txt; git commit -q -m e2-main |
| 62 | + git merge -q --no-ff -m "e2 octopus empty-net" e2a e2b || true |
| 63 | + git rev-parse HEAD > "$_W/E2" |
| 64 | + e2_after="$(git rev-parse HEAD)" |
| 65 | + |
| 66 | + # E3 evil 2-parent merge: a 2-parent merge whose merge commit carries OWN content |
| 67 | + # (a file no parent has) -> --cc combined diff is NON-empty (differs from both |
| 68 | + # parents). A clean auto-merge of disjoint changes would be empty-net by --cc |
| 69 | + # semantics, so the not-exempt case must introduce content IN the merge itself. |
| 70 | + git checkout -q -b e3side "$e2_after" |
| 71 | + echo c3 > content3.txt; git add content3.txt; git commit -q -m e3-side |
| 72 | + git checkout -q main |
| 73 | + echo unrel > unrel3.txt; git add unrel3.txt; git commit -q -m e3-main |
| 74 | + git merge -q --no-ff --no-commit e3side >/dev/null 2>&1 || true |
| 75 | + echo evil3 > evil3.txt; git add -A |
| 76 | + git commit -q -m "e3 evil 2-parent merge" |
| 77 | + git rev-parse HEAD > "$_W/E3" |
| 78 | + e3_after="$(git rev-parse HEAD)" |
| 79 | + |
| 80 | + # E4 conflict-resolution merge with OWN content: both sides edit the same line |
| 81 | + # differently; the merge commit carries a resolution -> non-empty combined diff. |
| 82 | + echo line > conflict.txt; git add conflict.txt; git commit -q -m e4-seed |
| 83 | + e4_seed="$(git rev-parse HEAD)" |
| 84 | + git checkout -q -b e4side "$e4_seed" |
| 85 | + echo sideval > conflict.txt; git add conflict.txt; git commit -q -m e4-side |
| 86 | + git checkout -q main |
| 87 | + echo mainval > conflict.txt; git add conflict.txt; git commit -q -m e4-main |
| 88 | + git merge -q --no-ff -m "e4 conflict merge" e4side >/dev/null 2>&1 || { |
| 89 | + echo resolved > conflict.txt; git add conflict.txt |
| 90 | + git commit -q --no-edit |
| 91 | + } |
| 92 | + git rev-parse HEAD > "$_W/E4" |
| 93 | + e4_after="$(git rev-parse HEAD)" |
| 94 | + |
| 95 | + # E5 single-parent content commit (not a merge). |
| 96 | + echo e5 > five.txt; git add five.txt; git commit -q -m e5-single |
| 97 | + git rev-parse HEAD > "$_W/E5" |
| 98 | + e5_after="$(git rev-parse HEAD)" |
| 99 | + |
| 100 | + # E6 evil octopus: octopus merge that carries OWN content (a file no parent has) |
| 101 | + # -> combined diff non-empty. |
| 102 | + git checkout -q -b e6a "$e5_after" |
| 103 | + echo a6 > e6a.txt; git add e6a.txt; git commit -q -m e6a |
| 104 | + git checkout -q -b e6b "$e5_after" |
| 105 | + echo b6 > e6b.txt; git add e6b.txt; git commit -q -m e6b |
| 106 | + git checkout -q main |
| 107 | + git merge -q --no-ff --no-commit e6a e6b >/dev/null 2>&1 || true |
| 108 | + echo evil > evil6.txt; git add -A |
| 109 | + git commit -q -m "e6 evil octopus" |
| 110 | + git rev-parse HEAD > "$_W/E6" |
| 111 | +) || { echo "FIXTURE SETUP FAILED"; exit 1; } |
| 112 | + |
| 113 | +# shellcheck source=/dev/null |
| 114 | +source "$LIB" |
| 115 | + |
| 116 | +_verdict() { # _verdict <sha> ; echoes rc |
| 117 | + ( cd "$R" && rc_diff_is_empty_net "$1" >/dev/null 2>&1; echo $? ) |
| 118 | +} |
| 119 | + |
| 120 | +_assert() { # _assert <name> <got> <want> |
| 121 | + if [[ "$2" == "$3" ]]; then _pass "$1"; else _fail "$1" "rc=$2 want $3"; fi |
| 122 | +} |
| 123 | +_assert "E1 clean 2-parent empty-net merge -> exempt" "$(_verdict "$(cat "$_W/E1")")" 0 |
| 124 | +_assert "E2 clean octopus empty-net merge -> exempt" "$(_verdict "$(cat "$_W/E2")")" 0 |
| 125 | +_assert "E3 content merge -> not exempt" "$(_verdict "$(cat "$_W/E3")")" 1 |
| 126 | +_assert "E4 conflict-resolution merge -> not exempt" "$(_verdict "$(cat "$_W/E4")")" 1 |
| 127 | +_assert "E5 single-parent content commit -> not exempt" "$(_verdict "$(cat "$_W/E5")")" 1 |
| 128 | +_assert "E6 evil octopus -> not exempt" "$(_verdict "$(cat "$_W/E6")")" 1 |
| 129 | +_assert "E7 bogus SHA -> error (fail closed)" "$(_verdict "deadbeefdeadbeefdeadbeefdeadbeefdeadbeef")" 2 |
| 130 | + |
| 131 | +echo "" |
| 132 | +echo "=== test-rc-diff-is-empty-net.sh: PASS=$PASS FAIL=$FAIL ===" |
| 133 | +[[ $FAIL -eq 0 ]] |
0 commit comments