Skip to content

Commit e4d3512

Browse files
fieldingclaude
andcommitted
Add conformance tests, fix log sort order and --stat passthrough
- tests/conformance.sh: 14 tests comparing nit output against git on a controlled repo. Covers status, log, diff (unstaged + staged), show, show --stat, and passthrough (branch, --graph, --name-only, --stat). - Fix log sort: use TOPOLOGICAL | TIME (was TIME only), which caused wrong commit order on repos with close timestamps. - Fix --stat passthrough: only treat --stat as a known flag for the show command. Previously 'nit diff --stat' ran nit's diff (ignoring --stat) instead of passing through to git. - Parse command before flags so flag validity is context-aware. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 30b21be commit e4d3512

3 files changed

Lines changed: 236 additions & 4 deletions

File tree

src/cli.zig

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ pub fn run() !void {
3737
return;
3838
}
3939

40+
const cmd = args[1];
41+
4042
var human = false;
4143
var count: usize = 20;
4244
var staged = false;
@@ -48,7 +50,7 @@ pub fn run() !void {
4850
human = true;
4951
} else if (std.mem.eql(u8, arg, "-s") or std.mem.eql(u8, arg, "--staged")) {
5052
staged = true;
51-
} else if (std.mem.eql(u8, arg, "--stat")) {
53+
} else if (std.mem.eql(u8, arg, "--stat") and std.mem.eql(u8, cmd, "show")) {
5254
stat = true;
5355
} else if (std.mem.eql(u8, arg, "-n")) {
5456
// Handled below with value parsing
@@ -72,8 +74,6 @@ pub fn run() !void {
7274
return passthrough(args);
7375
}
7476

75-
const cmd = args[1];
76-
7777
// Passthrough and help skip libgit2 entirely for faster startup
7878
if (std.mem.eql(u8, cmd, "help") or std.mem.eql(u8, cmd, "-h") or std.mem.eql(u8, cmd, "--help")) {
7979
try w.writeAll(usage);

src/cmd/log.zig

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ pub fn run(repo: *c.git_repository, human: bool, count: usize, w: *Writer) !void
1212
defer c.git_revwalk_free(walk);
1313

1414
try git.check(c.git_revwalk_push_head(walk));
15-
_ = c.git_revwalk_sorting(walk, c.GIT_SORT_TIME);
15+
_ = c.git_revwalk_sorting(walk, c.GIT_SORT_TIME | c.GIT_SORT_TOPOLOGICAL);
1616

1717
var oid: c.git_oid = undefined;
1818
var shown: usize = 0;

tests/conformance.sh

Lines changed: 232 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,232 @@
1+
#!/usr/bin/env bash
2+
# conformance.sh - verify nit output matches git for equivalent commands
3+
#
4+
# Creates a test repo with known state, runs nit and git side by side,
5+
# and diffs the output. Any difference is a bug.
6+
#
7+
# Usage: ./tests/conformance.sh [path-to-nit-binary]
8+
9+
set -euo pipefail
10+
11+
NIT="${1:-./zig-out/bin/nit}"
12+
PASS=0
13+
FAIL=0
14+
ERRORS=""
15+
16+
if [ ! -x "$NIT" ]; then
17+
echo "error: nit binary not found at $NIT"
18+
echo " run: zig build -Doptimize=ReleaseFast"
19+
exit 1
20+
fi
21+
22+
# Resolve to absolute path
23+
NIT="$(cd "$(dirname "$NIT")" && pwd)/$(basename "$NIT")"
24+
25+
TMPDIR=$(mktemp -d -t nit-conformance-XXXXXX)
26+
trap "rm -rf $TMPDIR" EXIT
27+
28+
cd "$TMPDIR"
29+
git init -q
30+
git config user.email "test@test.com"
31+
git config user.name "Test User"
32+
33+
# --- Build up a repo with various states ---
34+
35+
# Initial commit
36+
cat > main.py << 'EOF'
37+
"""Main application."""
38+
39+
import os
40+
import sys
41+
42+
def main():
43+
print("hello world")
44+
return 0
45+
46+
def helper(x):
47+
"""Helper function."""
48+
if x > 0:
49+
return x * 2
50+
return 0
51+
52+
if __name__ == "__main__":
53+
sys.exit(main())
54+
EOF
55+
56+
cat > utils.py << 'EOF'
57+
"""Utility functions."""
58+
59+
def add(a, b):
60+
return a + b
61+
62+
def subtract(a, b):
63+
return a - b
64+
65+
def multiply(a, b):
66+
return a * b
67+
EOF
68+
69+
git add main.py utils.py
70+
git commit -q -m "Initial commit"
71+
72+
# Second commit - modify main.py
73+
sed -i.bak 's/hello world/hello nit/' main.py && rm -f main.py.bak
74+
git add main.py
75+
git commit -q -m "Update greeting message"
76+
77+
# Third commit - add a file, modify utils
78+
cat > config.json << 'EOF'
79+
{
80+
"name": "test",
81+
"version": "1.0.0",
82+
"debug": false
83+
}
84+
EOF
85+
86+
cat >> utils.py << 'EOF'
87+
88+
def divide(a, b):
89+
if b == 0:
90+
raise ValueError("division by zero")
91+
return a / b
92+
EOF
93+
94+
git add config.json utils.py
95+
git commit -q -m "Add config and divide function"
96+
97+
# Fourth commit - delete a file
98+
git rm -q config.json
99+
git commit -q -m "Remove config file"
100+
101+
# Now make some unstaged changes for status/diff testing
102+
sed -i.bak 's/hello nit/hello nit v2/' main.py && rm -f main.py.bak
103+
echo "# new file" > newfile.txt
104+
105+
# Stage one change
106+
echo "extra_setting = true" >> utils.py
107+
git add utils.py
108+
109+
# --- Test functions ---
110+
111+
check() {
112+
local name="$1"
113+
local git_cmd="$2"
114+
local nit_cmd="$3"
115+
116+
local git_out nit_out
117+
git_out=$(eval "$git_cmd" 2>&1) || true
118+
nit_out=$(eval "$nit_cmd" 2>&1) || true
119+
120+
if [ "$git_out" = "$nit_out" ]; then
121+
PASS=$((PASS + 1))
122+
echo " PASS: $name"
123+
else
124+
FAIL=$((FAIL + 1))
125+
echo " FAIL: $name"
126+
ERRORS="${ERRORS}\n--- FAIL: $name ---\n"
127+
ERRORS="${ERRORS}git cmd: $git_cmd\n"
128+
ERRORS="${ERRORS}nit cmd: $nit_cmd\n"
129+
ERRORS="${ERRORS}git output:\n$git_out\n"
130+
ERRORS="${ERRORS}nit output:\n$nit_out\n"
131+
fi
132+
}
133+
134+
echo "=== nit conformance tests ==="
135+
echo "repo: $TMPDIR"
136+
echo ""
137+
138+
# --- Status tests ---
139+
echo "status:"
140+
# nit and git may sort differently (libgit2 vs git internals)
141+
# so compare sorted output
142+
check "porcelain entries match (sorted)" \
143+
"git status --porcelain | sort" \
144+
"$NIT status | sort"
145+
146+
# --- Log tests ---
147+
echo ""
148+
echo "log:"
149+
check "oneline format matches" \
150+
"git log --oneline -20" \
151+
"$NIT log -n 20"
152+
153+
check "oneline -5 matches" \
154+
"git log --oneline -5" \
155+
"$NIT log -n 5"
156+
157+
check "oneline -1 matches" \
158+
"git log --oneline -1" \
159+
"$NIT log -n 1"
160+
161+
# --- Diff tests (compare stripped nit vs git -U1, accounting for header differences) ---
162+
echo ""
163+
echo "diff:"
164+
165+
# nit strips file headers to "--- path" and hunk context text.
166+
# Compare just the +/- content lines (exclude nit's "--- path" lines).
167+
check "unstaged change lines match" \
168+
"git diff -U1 | grep '^[+-]' | grep -v '^[+-][+-][+-]'" \
169+
"$NIT diff | grep '^[+-]' | grep -v '^--- '"
170+
171+
check "staged change lines match" \
172+
"git diff --staged -U1 | grep '^[+-]' | grep -v '^[+-][+-][+-]'" \
173+
"$NIT diff -s | grep '^[+-]' | grep -v '^--- '"
174+
175+
# --- Show tests ---
176+
echo ""
177+
echo "show:"
178+
179+
# Compare the commit summary line
180+
check "show HEAD summary matches log" \
181+
"git log --oneline -1" \
182+
"$NIT show 2>&1 | head -1"
183+
184+
# Show a specific revision
185+
SECOND_HASH=$(git log --oneline -3 | tail -1 | cut -d' ' -f1)
186+
check "show specific rev summary" \
187+
"git log --oneline -1 $SECOND_HASH" \
188+
"$NIT show $SECOND_HASH 2>&1 | head -1"
189+
190+
# Show change lines match
191+
check "show HEAD change lines match" \
192+
"git show -U1 HEAD | grep '^[+-]' | grep -v '^[+-][+-][+-]'" \
193+
"$NIT show 2>&1 | grep '^[+-]' | grep -v '^--- '"
194+
195+
# Show --stat
196+
check "show --stat file count matches" \
197+
"git show --stat HEAD | tail -1 | grep -o '[0-9]* file' | head -1" \
198+
"$NIT show --stat 2>&1 | sed -n '2p' | grep -o '[0-9]* file' | head -1"
199+
200+
# --- Passthrough tests ---
201+
echo ""
202+
echo "passthrough:"
203+
204+
check "branch passthrough" \
205+
"git branch" \
206+
"$NIT branch"
207+
208+
check "log --graph passthrough" \
209+
"git log --graph --oneline -3" \
210+
"$NIT log --graph --oneline -3"
211+
212+
check "diff --name-only passthrough" \
213+
"git diff --name-only" \
214+
"$NIT diff --name-only"
215+
216+
check "diff --stat passthrough" \
217+
"git diff --stat" \
218+
"$NIT diff --stat"
219+
220+
# --- Summary ---
221+
echo ""
222+
echo "=== results ==="
223+
echo " passed: $PASS"
224+
echo " failed: $FAIL"
225+
echo " total: $((PASS + FAIL))"
226+
227+
if [ $FAIL -gt 0 ]; then
228+
echo ""
229+
echo "=== failures ==="
230+
printf "$ERRORS"
231+
exit 1
232+
fi

0 commit comments

Comments
 (0)