-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathtest_helpers.sh
More file actions
231 lines (204 loc) · 7.13 KB
/
test_helpers.sh
File metadata and controls
231 lines (204 loc) · 7.13 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
#!/usr/bin/env bash
# Shared test helpers for all test files in this repository.
#
# Usage:
# source test_helpers.sh
# expect_success "test name" command [args...]
# expect_success_output "test name" "expected output" command [args...]
# expect_failure "test name" command [args...]
# expect_failure_output "test name" "expected output" command [args...]
# print_results
# Source actions_helpers.sh to get check_contains and check_contains_pattern
TEST_HELPERS_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# shellcheck source=actions_helpers.sh
source "$TEST_HELPERS_DIR/actions_helpers.sh"
PASS=0
FAIL=0
_run_test() {
local name="$1"
local expected_exit="$2" # exact code, or "nonzero" for any non-zero
local expected_output="$3"
shift 3
local output exit_code
output=$("$@" 2>&1) && exit_code=0 || exit_code=$?
if [ "$expected_exit" = "nonzero" ]; then
if [ "$exit_code" -eq 0 ]; then
echo "FAIL: $name — expected non-zero exit, got 0"
echo " output: $output"
FAIL=$((FAIL + 1))
return
fi
elif [ "$exit_code" -ne "$expected_exit" ]; then
echo "FAIL: $name — expected exit $expected_exit, got $exit_code"
echo " output: $output"
FAIL=$((FAIL + 1))
return
fi
if [ -n "$expected_output" ] && ! printf '%s\n' "$output" | check_contains "$expected_output"; then
echo "FAIL: $name — expected output containing: $expected_output"
echo " actual: $output"
FAIL=$((FAIL + 1))
return
fi
echo "PASS: $name"
PASS=$((PASS + 1))
}
# expect_success "test name" command [args...]
# Asserts the command exits 0.
expect_success() {
local name="$1"; shift
_run_test "$name" 0 "" "$@"
}
# expect_success_output "test name" "expected output substring" command [args...]
# Asserts the command exits 0 and output contains the expected substring.
expect_success_output() {
local name="$1"; shift
local expected_output="$1"; shift
_run_test "$name" 0 "$expected_output" "$@"
}
# expect_failure "test name" command [args...]
# Asserts the command exits non-zero.
expect_failure() {
local name="$1"; shift
_run_test "$name" "nonzero" "" "$@"
}
# expect_failure_output "test name" "expected output substring" command [args...]
# Asserts the command exits non-zero and output contains the expected substring.
expect_failure_output() {
local name="$1"; shift
local expected_output="$1"; shift
_run_test "$name" "nonzero" "$expected_output" "$@"
}
# expect_step_output "test name" "key" "expected_value"
# Asserts that GITHUB_OUTPUT contains key=expected_value.
# Checks the last occurrence of the key so tests that reuse the same file work.
expect_step_output() {
local name="$1" key="$2" expected="$3"
local actual=""
if [ ! -f "${GITHUB_OUTPUT}" ]; then
echo "FAIL: $name — GITHUB_OUTPUT file does not exist"
FAIL=$((FAIL + 1))
return
fi
if check_contains "${key}=" "${GITHUB_OUTPUT}"; then
actual=$(grep --fixed-strings "${key}=" "${GITHUB_OUTPUT}" | tail -1 | cut -d= -f2-)
fi
if [ "$actual" = "$expected" ]; then
echo "PASS: $name"
PASS=$((PASS + 1))
else
echo "FAIL: $name — expected output $key=$expected, got '$actual'"
FAIL=$((FAIL + 1))
fi
}
# expect_files_in_commit file1 [file2 ...]
# Asserts that all listed files are in the last commit
expect_files_in_commit() {
for file in "$@"; do
if ! git diff --name-only HEAD~1 HEAD | grep --quiet --fixed-strings --line-regexp "$file"; then
echo "Expected $file to be in commit, but it wasn't" >&2
return 1
fi
done
}
# expect_files_not_in_commit file1 [file2 ...]
# Asserts that none of the listed files are in the last commit
expect_files_not_in_commit() {
for file in "$@"; do
if git diff --name-only HEAD~1 HEAD | grep --quiet --fixed-strings --line-regexp "$file"; then
echo "Expected $file NOT to be in commit, but it was" >&2
return 1
fi
done
}
# expect_diff "test name" original_content expected_diff transformation_command
# Runs transformation_command on original_content and verifies the diff exactly matches expected_diff.
# Uses unified diff format with 2 lines of context to show where changes are made.
# Context lines have a leading space, change lines have + or - prefixes.
#
# This helper enforces that there is exactly one hunk in the diff. A hunk is a contiguous
# block of changes. If the transformation produces changes in multiple separate locations,
# the test will fail. This ensures test expectations are focused and prevents accidentally
# missing unexpected changes elsewhere in the output.
#
# Example:
# expect_diff "adds version header" \
# "$original" \
# " ## [Unreleased]
#
#+## [1.0.0] - 2026-04-04
#+
# ### Added" \
# test_update_changelog "1.0.0" "2026-04-04" "$original"
expect_diff() {
local name="$1" original="$2" expected_diff="$3"
shift 3
# Create temp directory for before/after comparison
local tmpdir
tmpdir=$(mktemp -d)
# Write original content to file
echo "$original" > "$tmpdir/before"
# Run transformation command and capture output
local output exit_code
output=$("$@" 2>&1)
exit_code=$?
# Fail if transformation command failed
if [ "$exit_code" -ne 0 ]; then
echo "FAIL: $name — command failed with exit code $exit_code"
echo " output: $output"
FAIL=$((FAIL + 1))
rm -rf "$tmpdir"
return
fi
# Write transformed content to file
echo "$output" > "$tmpdir/after"
# Generate unified diff with 2 lines of context
# diff exits with 0 (identical), 1 (differ), or 2 (error)
local actual_diff diff_exit
actual_diff=$(diff --unified=2 "$tmpdir/before" "$tmpdir/after" 2>&1) && diff_exit=0 || diff_exit=$?
# Exit code 2 means error (not just files differing)
if [ "$diff_exit" -eq 2 ]; then
echo "FAIL: $name — diff command failed"
echo " diff error: $actual_diff"
FAIL=$((FAIL + 1))
rm -rf "$tmpdir"
return
fi
# Verify there is only one hunk (lines starting with @@)
# Multiple hunks indicate changes in multiple separate locations, which would make
# the test less precise and could hide unexpected modifications.
local hunk_count
hunk_count=$(echo "$actual_diff" | grep -c '^@@' || true)
if [ "$hunk_count" -gt 1 ]; then
echo "FAIL: $name — expected single hunk, found $hunk_count hunks"
echo " This indicates changes in multiple separate locations."
echo " Full diff:"
echo "$actual_diff"
FAIL=$((FAIL + 1))
rm -rf "$tmpdir"
return
fi
# Extract diff content: remove file headers (--- and +++), and hunk markers (@@)
# Keep everything else including context lines (which have a leading space)
local actual_changes
actual_changes=$(echo "$actual_diff" | grep -v '^---' | grep -v '^+++' | grep -v '^@@' || true)
# Compare extracted changes exactly against expected
if [ "$actual_changes" = "$expected_diff" ]; then
echo "PASS: $name"
PASS=$((PASS + 1))
else
echo "FAIL: $name — diff doesn't match expected"
echo " expected:"
echo "$expected_diff"
echo " actual:"
echo "$actual_changes"
FAIL=$((FAIL + 1))
fi
# Clean up temp directory
rm -rf "$tmpdir"
}
print_results() {
echo ""
echo "Results: $PASS passed, $FAIL failed"
[ "$FAIL" -eq 0 ] || exit 1
}