-
Notifications
You must be signed in to change notification settings - Fork 7
Expand file tree
/
Copy pathrunsamples.sh
More file actions
205 lines (173 loc) · 7.18 KB
/
runsamples.sh
File metadata and controls
205 lines (173 loc) · 7.18 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
#!/bin/sh
# lug - Embedded DSL for PE grammar parser combinators in C++
# Copyright (c) 2017-2025 Jesse W. Towner
# See LICENSE.md file for license details
# Usage: runsamples.sh <testplan1> [<testplan2> ...]
# Print usage if no arguments are provided
usage() {
printf "Usage: runsamples.sh <testplan1> [<testplan2> ...]\n\n"
printf "Runs the given sample testplan files. Each testplan file\n"
printf "should contain test groups and commands in the format:\n\n"
printf " [GROUP-NAME]\n"
printf " COMMAND <S> OUTPUT-FILE\n\n"
printf "Where:\n"
printf " * [GROUP-NAME] Defines a named group of related tests\n"
printf " * COMMAND The shell command to execute, may contain an input file pattern\n"
printf " * <S> Expected exit status code (<0> for success) in angle brackets\n"
printf " * OUTPUT-FILE Path of file containing expected program output\n\n"
printf "Special Patterns:\n"
printf " * %%PATTERN%% Input file pattern (e.g. %%*.txt%%) in shell command\n"
printf " * @ Replaced by current group name in expected output paths\n"
printf " * %% Replaced by input file (without extension) in expected output paths\n\n"
printf "Rules:\n"
printf " * Lines starting with # are comments\n"
printf " * Blank or empty lines are ignored\n"
printf " * Commands must be in a named group\n"
printf " * Commands are run in order specified from the testplan file's directory\n"
printf " * Expected output files are relative to the testplan file's directory\n\n"
printf "Example Testplan:\n"
printf " # samples/demo/.testplan\n"
printf " [demo-tests]\n"
printf " demo --verbose %%*.txt%% <0> @.%%.out\n"
printf " demo --verbose %%bad-tests/*.txt%% <1> bad-tests/@.%%.out\n"
printf " demo --format --verbose %%*.txt%% <0> @.%%.formatted\n\n"
printf " $ ./runsamples.sh samples/demo/.testplan\n\n"
printf " This will:\n"
printf " 1. Find all .txt files in the testplan directory\n"
printf " 2. Run each command replacing the input pattern with matching files\n"
printf " 3. Compare exit status codes of commands with expected value\n"
printf " 4. Compare output against files matching \"(bad-tests/)?demo-tests.*.(out|formatted)\"\n"
printf " 5. Print results to the console\n"
}
# Trim leading and trailing whitespace from a string
trim() {
echo "$1" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//'
}
# Parse the specified component from a test command line
parse_test_command() {
echo "$1" | sed -E "s/^(.+)<(-?[[:digit:]]+)>(.+)$/\\$2/"
}
# Run a test command and compare the output to the expected output
run_test_command() {
test_dir="$1"
test_command="$2"
test_expected_status="$3"
test_expected_outfile="$4"
# Skip test if expected output file does not exist
if [ ! -f "$test_dir/$test_expected_outfile" ]; then
printf "[SKIP] %s (NO OUTPUT FILE: \"%s\")\n" "$test_command" "$test_dir/$test_expected_outfile"
return 1
fi
# Run test command and capture output
test_actual_output=$(cd "$test_dir" && eval "./$test_command" 2>&1)
test_status=$?
test_pass=0
# Check if command failed
test_expected_status=$((test_expected_status + 0))
if [ $test_status -ne $test_expected_status ]; then
printf "[FAIL] %s (EXIT STATUS: %d) (EXPECTED: %d)\n" "$test_command" $test_status $test_expected_status
test_pass=1
fi
# Fail test if output does not match expected output
if ! printf "%s\n" "$test_actual_output" | diff - "$test_dir/$test_expected_outfile" >/dev/null 2>&1; then
printf "[FAIL] %s (OUTPUT MISMATCH)\n" "$test_command"
printf "Diff between expected and actual:\n"
printf "%s\n" "$test_actual_output" | diff -y "$test_dir/$test_expected_outfile" - | cat -n | grep -v -e '($'
test_pass=1
fi
# Return 1 if test failed
if [ $test_pass -ne 0 ]; then
return 1
fi
# Print PASS if test passed
printf "[PASS] %s\n" "$test_command"
return 0
}
if [ $# -eq 0 ]; then
usage
exit 1
fi
run_status=0
printf "running samples\n"
printf "=============================================\n"
# Run each testplan file
for testplan_file in "$@"; do
# Print usage if help flag is provided
if [ "$testplan_file" = "-h" ] || [ "$testplan_file" = "--help" ]; then
usage
exit 1
fi
# Skip if file does not exist
if [ ! -f "$testplan_file" ]; then
printf "Error: Test file '%s' not found\n" "$testplan_file"
continue
fi
# Get directory of testplan file
testplan_dir=$(dirname "$testplan_file")
if [ -z "$testplan_dir" ] || [ "$testplan_dir" = "." ]; then
testplan_dir="."
fi
# Current group of tests
current_group=""
# Read file line by line, skipping comments and empty lines
while IFS= read -r line <&3 || [ -n "$line" ]; do
# Skip comments and blank or empty lines
[ -z "$line" ] || echo "$line" | grep -q "^[[:space:]]*#\|^[[:space:]]*$" && continue
# Check if this is a group header
group=$(echo "$line" | sed 's/^[[:space:]]*\[\(.*\)\][[:space:]]*$/\1/')
if [ "$group" != "$line" ]; then
current_group=$(trim "$group")
printf "\n[%s]\n" "$current_group"
printf "=============================================\n"
continue
fi
# Split line into command, status, and outfile
command=$(parse_test_command "$line" 1)
status=$(parse_test_command "$line" 2)
outfile=$(parse_test_command "$line" 3)
if [ "$command" = "$line" ] || [ "$status" = "$line" ] || [ "$outfile" = "$line" ]; then
printf "Error: Invalid test command: %s\n" "$line"
run_status=1
continue
fi
# Strip leading and trailing whitespace from command and outfile
command=$(trim "$command")
outfile=$(trim "$outfile")
# Skip test if no group is defined yet
if [ -z "$current_group" ]; then
printf "Error: No group defined for test command: %s <%s> %s\n" "$command" "$status" "$outfile"
run_status=1
continue
fi
# Extract input pattern if it exists
pattern=$(echo "$command" | sed -E 's/.*%([^%]+)%.*/\1/')
if [ "$pattern" = "$command" ]; then
# Handle commands without input pattern
resolved_outfile=$(echo "$outfile" | sed "s/@/${current_group}/g")
run_test_command "$testplan_dir" "$command" "$status" "$resolved_outfile" || run_status=1
else
# Extract directory part from input pattern and transform for use in sed
pattern_dir=$(dirname "$pattern")
pattern_dir=$(trim "$pattern_dir")
if [ -n "$pattern_dir" ]; then
pattern_dir=$(echo "$pattern_dir/" | sed 's/\//\\\//g')
fi
# Process each matching input file
for infile in "$testplan_dir"/$pattern; do
[ -f "$infile" ] || continue
# Get base name and base name without extension
infile_base=$(basename "$infile")
infile_base_no_ext=$(echo "$infile_base" | sed 's/\.[^.]*$//')
# Replace the input pattern in the command with the actual input file path relative to the testplan directory
resolved_command=$(echo "$command" | sed -E "s/%[^%]+%/${pattern_dir}${infile_base}/g")
# Replace special tokens in output file path
resolved_outfile=$(echo "$outfile" | sed -e "s/@/${current_group}/g" -e "s/%/${infile_base_no_ext}/g")
# Execute command with input file
run_test_command "$testplan_dir" "$resolved_command" "$status" "$resolved_outfile" || run_status=1
done
fi
done 3< "$testplan_file"
done
printf "\n=============================================\n"
printf "all done\n"
exit $run_status