Skip to content

Commit 13363da

Browse files
committed
feature: merge valgrind branch
1 parent 6e1168a commit 13363da

File tree

6 files changed

+204
-5
lines changed

6 files changed

+204
-5
lines changed

.github/workflows/ci.yml

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ jobs:
3232
run: |
3333
if command -v apt-get >/dev/null 2>&1; then
3434
apt-get update
35-
apt-get install -y build-essential git make
35+
apt-get install -y build-essential git make valgrind
3636
fi
3737
echo "All dependencies installed successfully"
3838
@@ -50,6 +50,13 @@ jobs:
5050
make test
5151
echo "✅ Tests completed successfully"
5252
53+
- name: run valgrind memory leak tests
54+
shell: sh
55+
run: |
56+
echo "Running valgrind memory leak tests..."
57+
make valgrind
58+
echo "✅ Valgrind tests completed - no memory leaks detected"
59+
5360
- name: clean build
5461
shell: sh
5562
run: |
@@ -70,4 +77,5 @@ jobs:
7077
echo "🎉 All checks passed"
7178
echo "- Build successful"
7279
echo "- All tests passing"
80+
echo "- Valgrind memory leak tests passing"
7381
echo "- Clean target working"

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
obj/
2+
obj-debug/
23
bin/

Makefile

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,24 @@ TARGET := mini-ccstatus
1212
OBJ_DIR := obj
1313
BIN_DIR := bin
1414
OBJECTS := $(OBJ_DIR)/mini-ccstatus.o $(OBJ_DIR)/cJSON.o
15+
16+
# Debug build configuration (for valgrind and debugging)
17+
CFLAGS_DEBUG := -g -O0 $(WARNFLAGS)
18+
TARGET_DEBUG := mini-ccstatus-debug
19+
OBJ_DIR_DEBUG := obj-debug
20+
OBJECTS_DEBUG := $(OBJ_DIR_DEBUG)/mini-ccstatus.o $(OBJ_DIR_DEBUG)/cJSON.o
21+
22+
# Test scripts
1523
DEMO_SCRIPT := tests/stdout.sh
1624
TEST_SCRIPT := tests/coverage.sh
1725
TEST_MEMORY := tests/memory.sh
26+
TEST_VALGRIND := tests/valgrind.sh
1827
FIXTURES := fixtures/*.json
1928

2029
default: $(BIN_DIR)/$(TARGET) demo
2130

2231
.PHONY: all
23-
all: clean default test
32+
all: clean default test valgrind
2433

2534
$(BIN_DIR)/$(TARGET): $(OBJECTS) | $(BIN_DIR)
2635
$(CC) $(OBJECTS) $(LDFLAGS) -o $@
@@ -34,6 +43,22 @@ $(OBJ_DIR)/cJSON.o: lib/cjson/cJSON.c lib/cjson/cJSON.h | $(OBJ_DIR)
3443
$(OBJ_DIR) $(BIN_DIR):
3544
mkdir -p $@
3645

46+
# Debug build targets
47+
$(BIN_DIR)/$(TARGET_DEBUG): $(OBJECTS_DEBUG) | $(BIN_DIR)
48+
$(CC) $(OBJECTS_DEBUG) $(LDFLAGS) -o $@
49+
50+
$(OBJ_DIR_DEBUG)/mini-ccstatus.o: mini-ccstatus.c lib/cjson/cJSON.h | $(OBJ_DIR_DEBUG)
51+
$(CC) $(CPPFLAGS) $(CFLAGS_DEBUG) -I. -Ilib -c $< -o $@
52+
53+
$(OBJ_DIR_DEBUG)/cJSON.o: lib/cjson/cJSON.c lib/cjson/cJSON.h | $(OBJ_DIR_DEBUG)
54+
$(CC) $(CPPFLAGS) $(CFLAGS_DEBUG) -I. -Ilib -c $< -o $@
55+
56+
$(OBJ_DIR_DEBUG):
57+
mkdir -p $@
58+
59+
.PHONY: debug
60+
debug: $(BIN_DIR)/$(TARGET_DEBUG)
61+
3762
.PHONY: test
3863
test: $(BIN_DIR)/$(TARGET) $(TEST_SCRIPT) $(TEST_MEMORY) $(FIXTURES)
3964
$(TEST_SCRIPT)
@@ -44,6 +69,10 @@ demo: $(BIN_DIR)/$(TARGET) $(DEMO_SCRIPT)
4469
$(DEMO_SCRIPT)
4570
VERBOSE=true $(DEMO_SCRIPT)
4671

72+
.PHONY: valgrind
73+
valgrind: $(TEST_VALGRIND)
74+
$(TEST_VALGRIND)
75+
4776
.PHONY: clean
4877
clean:
49-
rm -rfv $(BIN_DIR) $(OBJ_DIR)
78+
rm -rfv $(BIN_DIR) $(OBJ_DIR) $(OBJ_DIR_DEBUG)

README.md

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,12 @@ Install build tools:
1515

1616
**Debian/Ubuntu:**
1717
```bash
18-
sudo apt-get install build-essential
18+
sudo apt-get install build-essential valgrind
1919
```
2020

2121
**Fedora:**
2222
```bash
23-
sudo dnf install gcc make
23+
sudo dnf install gcc make valgrind
2424
```
2525

2626
### Building
@@ -37,6 +37,9 @@ make demo
3737
# Run the shell-based regression tests
3838
make test
3939

40+
# Run memory checks
41+
make valgrind
42+
4043
# Clean bin/ and obj/
4144
make clean
4245
```

test-valgrind-exit-codes.sh

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
#!/usr/bin/env bash
2+
# Quick test to verify valgrind exit code behavior
3+
4+
set -u
5+
6+
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
7+
BIN_DEBUG="$ROOT/bin/mini-ccstatus-debug"
8+
9+
echo "Testing valgrind exit code behavior..."
10+
echo ""
11+
12+
# Test 1: Valid JSON (program should succeed, no leaks)
13+
echo "Test 1: Valid JSON, no leaks"
14+
echo '{}' | valgrind --error-exitcode=99 --leak-check=full --quiet "$BIN_DEBUG" 2>&1 > /dev/null
15+
exit_code=$?
16+
echo " Exit code: $exit_code (expected 0)"
17+
echo ""
18+
19+
# Test 2: Invalid JSON (program should fail with exit 4, but no leaks)
20+
echo "Test 2: Invalid JSON, no leaks expected"
21+
echo 'invalid json' | valgrind --error-exitcode=99 --leak-check=full --quiet "$BIN_DEBUG" 2>&1 > /dev/null
22+
exit_code=$?
23+
echo " Exit code: $exit_code (expected 4 if no leaks, 99 if leaks)"
24+
echo ""
25+
26+
echo "If test 2 returns 4, valgrind passes through program exit codes correctly"
27+
echo "If test 2 returns 99, there's a memory leak on error paths"

tests/valgrind.sh

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
#!/usr/bin/env bash
2+
# Copyright (c) 2025 Michele Tavella <meeghele@proton.me>
3+
# Licensed under the MIT License. See LICENSE file for details.
4+
5+
set -euo pipefail
6+
7+
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")"/.. && pwd)"
8+
BIN_DEBUG="$ROOT/bin/mini-ccstatus-debug"
9+
FIXTURES_DIR="$ROOT/fixtures"
10+
11+
PASS=0
12+
FAIL=0
13+
14+
# Colors for test output
15+
RED='\033[0;31m'
16+
GREEN='\033[0;32m'
17+
YELLOW='\033[0;33m'
18+
BLUE='\033[0;34m'
19+
NC='\033[0m' # No Color
20+
21+
# Check if valgrind is installed
22+
if ! command -v valgrind &> /dev/null; then
23+
echo -e "${RED}error: valgrind is not installed${NC}"
24+
echo "Install with: sudo apt-get install valgrind (Debian/Ubuntu)"
25+
echo " or: sudo dnf install valgrind (Fedora)"
26+
exit 1
27+
fi
28+
29+
# Build debug version if needed
30+
if [[ ! -x "$BIN_DEBUG" ]]; then
31+
echo -e "${BLUE}Building debug version...${NC}"
32+
make -C "$ROOT" debug || {
33+
echo -e "${RED}error: failed to build debug version${NC}"
34+
exit 1
35+
}
36+
fi
37+
38+
test_passed() {
39+
echo -e "${GREEN}${NC} $1"
40+
PASS=$((PASS + 1))
41+
}
42+
43+
test_failed() {
44+
echo -e "${RED}${NC} $1"
45+
FAIL=$((FAIL + 1))
46+
}
47+
48+
# Valgrind options for strict leak checking
49+
VALGRIND_OPTS=(
50+
--leak-check=full
51+
--show-leak-kinds=all
52+
--track-origins=yes
53+
--error-exitcode=99
54+
--errors-for-leak-kinds=all
55+
--quiet
56+
)
57+
58+
run_valgrind_test() {
59+
local test_name="$1"
60+
local input_source="$2"
61+
local prog_exit=0
62+
local output
63+
64+
# Run valgrind and capture output.
65+
# Valgrind wraps the program, so we need to distinguish between:
66+
# - Program failure (exit code from mini-ccstatus) - OK, not our concern
67+
# - Memory errors (valgrind exits with 99) - FAIL
68+
# Temporarily disable set -e so program errors don't exit the script
69+
set +e
70+
output=$(eval "$input_source" | NO_COLOR=1 valgrind "${VALGRIND_OPTS[@]}" "$BIN_DEBUG" 2>&1)
71+
prog_exit=$?
72+
set -e
73+
74+
# If exit code is 99, valgrind found memory errors
75+
# Any other exit code means either success (0) or program error (not valgrind error)
76+
if [[ "$prog_exit" -eq 99 ]]; then
77+
# Valgrind detected memory issues
78+
test_failed "$test_name"
79+
echo -e "${YELLOW} valgrind detected memory issues:${NC}"
80+
echo "$output" | sed 's/^/ /'
81+
return 1
82+
else
83+
# No valgrind errors (program may have failed, but that's OK)
84+
test_passed "$test_name"
85+
return 0
86+
fi
87+
}
88+
89+
echo "Running valgrind memory leak tests..."
90+
echo "========================================"
91+
echo ""
92+
93+
# Test all JSON fixtures
94+
if [[ -d "$FIXTURES_DIR" ]]; then
95+
for fixture in "$FIXTURES_DIR"/*.json; do
96+
if [[ -f "$fixture" ]]; then
97+
fixture_name=$(basename "$fixture")
98+
run_valgrind_test "Fixture: $fixture_name" "cat '$fixture'"
99+
fi
100+
done
101+
else
102+
echo -e "${YELLOW}warning: fixtures directory not found${NC}"
103+
fi
104+
105+
# Test empty input (EOF immediately)
106+
run_valgrind_test "Empty input (immediate EOF)" "echo -n ''"
107+
108+
# Test minimal valid JSON
109+
run_valgrind_test "Minimal valid JSON" "echo '{}'"
110+
111+
# Test invalid JSON (should still not leak)
112+
run_valgrind_test "Invalid JSON (no leaks on error path)" "echo 'not valid json'"
113+
114+
# Test large valid JSON (stress test)
115+
run_valgrind_test "Large valid JSON object" "echo '{\"model\":{\"display_name\":\"test\",\"id\":\"test-id\"},\"cwd\":\"/test\",\"workspace\":{\"project_dir\":\"/test\"},\"version\":\"1.0.0\",\"cost\":{\"total_cost_usd\":0.5,\"total_duration_ms\":1000,\"total_api_duration_ms\":500,\"total_lines_added\":100,\"total_lines_removed\":50},\"exceeds_200k_tokens\":false}'"
116+
117+
# Summary
118+
echo ""
119+
echo "========================================"
120+
TOTAL=$((PASS + FAIL))
121+
echo "Results: $PASS/$TOTAL passed"
122+
123+
if [[ "$FAIL" -gt 0 ]]; then
124+
echo -e "${RED}FAILED${NC}: $FAIL test(s) failed"
125+
echo ""
126+
echo "Valgrind detected memory issues. Please fix memory leaks before committing."
127+
exit 1
128+
else
129+
echo -e "${GREEN}SUCCESS${NC}: All tests passed - no memory leaks detected!"
130+
exit 0
131+
fi

0 commit comments

Comments
 (0)