-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtest.sh
More file actions
executable file
·331 lines (296 loc) · 8.71 KB
/
test.sh
File metadata and controls
executable file
·331 lines (296 loc) · 8.71 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
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
#!/bin/bash
# Test script for the Reckon project.
USAGE="Usage: test.sh [options]";
HELP_TEXT=$(cat << EOS
Tests the Reckon library and scount application.
${USAGE}
Options:
[--all] Execute the entire test suite and all automated checks.
[--check] Execute various consistency checks.
[--coverage] Report code coverage metrics for the test runs.
Targets must have been built with debug symbols.
[-f|--functionality] Execute functionality tests.
[-i|--integration] Execute integration tests.
[-l|--lint] Perform static code analysis with a linter.
[--stop-on-failure] Stop the test execution immediately after the
first failure is encountered.
[-u|--unit] Execute unit tests.
[-?|--help] Show this help message.
EOS
)
# Arg flags
ARG_ALL=false;
ARG_CHECK=false;
ARG_COVERAGE=false;
ARG_FUNCTIONALITY=false;
ARG_INTEGRATION=false;
ARG_LINT=false;
ARG_STOP_ON_FAILURE=false;
ARG_UNIT=false;
ARG_SHOW_HELP=false;
# Parse all arguments given to this script
for arg in "$@"; do
case $arg in
--all)
ARG_ALL=true;
ARG_CHECK=true;
ARG_LINT=true;
shift
;;
--check)
ARG_CHECK=true;
shift
;;
--coverage)
ARG_COVERAGE=true;
shift
;;
-f|--functionality)
ARG_FUNCTIONALITY=true;
shift
;;
-i|--integration)
ARG_INTEGRATION=true;
shift
;;
-l|--lint)
ARG_LINT=true;
shift
;;
--stop-on-failure)
ARG_STOP_ON_FAILURE=true;
shift
;;
-u|--unit)
ARG_UNIT=true;
shift
;;
-\?|--help)
ARG_SHOW_HELP=true;
shift
;;
*)
# Unknown Argument
echo "Unknown argument: '$arg'";
echo "$USAGE";
echo "";
echo "Run 'test.sh --help' for more information";
exit 1;
;;
esac
done
# Check if help is triggered
if [[ $ARG_SHOW_HELP == true ]]; then
echo "$HELP_TEXT";
exit 0;
fi
# Need to prematurely create the build dir if it does not exist
# so it can be used as a volume by Docker when using
# an isolated test execution
if ! [ -d "build" ]; then
mkdir "build";
fi
has_apt=false;
if command -v "apt" &> /dev/null; then
has_apt=true;
fi
# Ensure the required executables are available
if ! command -v "cmake" &> /dev/null; then
echo "ERROR: Could not find the 'cmake' executable.";
echo "ERROR: Please make sure that CMake is correctly installed";
if [[ $has_apt == true ]]; then
echo "Perhaps you can install it with 'sudo apt install cmake'";
fi
exit 1;
fi
if ! command -v "ctest" &> /dev/null; then
echo "ERROR: Could not find the 'ctest' executable.";
echo "ERROR: Please make sure that CMake and CTest are correctly installed";
exit 1;
fi
if [[ $ARG_COVERAGE == true ]]; then
if ! command -v "lcov" &> /dev/null; then
echo "ERROR: Could not find command 'lcov' to generate test coverage data";
if [[ $has_apt == true ]]; then
echo "Perhaps you can install it with 'sudo apt install lcov'";
fi
exit 1;
fi
if ! command -v "genhtml" &> /dev/null; then
echo "ERROR: Could not find command 'genhtml' to generate test coverage report";
exit 1;
fi
fi
if [[ $ARG_CHECK == true ]]; then
echo "Performing source code checks";
check_failed=false;
check_found_files=false;
while IFS= read -r file; do
check_found_files=true;
pattern='^[[:space:]]*void[[:space:]]+test[[:alnum:]_]*'\
'[[:space:]]*\([[:space:]]*void[[:space:]]*\)';
defs="$(grep -Eo "$pattern" "$file" |wc -l)";
pattern='RUN_TEST[[:space:]]*\([[:space:]]*test[[:alnum:]'\
'_]*[[:space:]]*\)[[:space:]]*;';
runs="$(grep -Eo "$pattern" "$file" |wc -l)";
if [[ "$defs" != "$runs" ]]; then
echo -n "Error: Test consistency: ";
echo -n "'${file}' defines ${defs} test function(s) ";
echo "but has ${runs} RUN_TEST() usage(s)";
check_failed=true;
fi
done < <(find . -type f -name 'test_*.c' -path '*/tests/*');
if [[ $check_found_files == false ]]; then
echo "Note: C test consistency: no matching files found (*/tests/*/test_*.c)";
fi
if [[ $check_failed == true ]]; then
echo "Did you forget to register test functions via the RUN_TEST() macro?";
exit 1;
fi
echo "Consistency of test code: OK";
if [[ $ARG_ALL == false ]]; then
exit 0;
fi
fi
# Check if the build dir is empty
if [ -z "$(ls -A build)" ]; then
# Build the tests first
build_args=();
if [[ $ARG_COVERAGE == true ]]; then
build_args+=("--debug");
build_args+=("--coverage");
fi
echo "Building project before test run execution";
if ! bash build.sh "${build_args[@]}"; then
exit $?;
fi
fi
if [[ $ARG_LINT == true ]]; then
if ! command -v "run-clang-tidy" &> /dev/null; then
echo "ERROR: Could not find command 'run-clang-tidy'";
if [[ $has_apt == true ]]; then
echo "Perhaps you can install it with 'sudo apt install clang-tidy'";
fi
exit 1;
fi
echo "Performing static code analysis";
lint_out=$(run-clang-tidy -quiet -config '' -p build "${PWD}/src" 2> /dev/null);
tidy_stat=$?;
if (( tidy_stat != 0 )); then
echo "Static code analysis found the following issues:";
lint_out="$(echo "$lint_out" |grep --invert-match '^clang-tidy-')";
echo "$lint_out";
exit $tidy_stat;
else
echo "No issues found in source files";
fi
if [[ $ARG_ALL == false ]]; then
exit $tidy_stat;
fi
fi
COV_INCL_PATH="$PWD";
BUILD_DIR_COV_DATA="cov";
FILE_COV_DATA_MERGED="merged.info";
BUILD_DIR_COV_REPORT="coverage_report";
VERSION="$(cat VERSION)";
if [[ "$VERSION" == *"-dev" ]]; then
VERSION+=" (Development Version)";
fi
cd "build" || exit 1;
if [[ $ARG_COVERAGE == true ]]; then
if ! cmake --build . --target clean_coverage_data 1>/dev/null; then
echo "Warning: Failed to clean existing coverage data files";
fi
fi
BUILD_CONFIGURATION="";
# Determine the build configuration of the last build.
if [ -f "CMakeCache.txt" ]; then
BUILD_CONFIGURATION="$(grep --max-count=1 CMAKE_BUILD_TYPE CMakeCache.txt \
| cut --delimiter='=' --fields=2)";
fi
if [ -z "$BUILD_CONFIGURATION" ]; then
if [ -d "bin/Debug" ]; then
BUILD_CONFIGURATION="Debug";
fi
if [ -d "bin/Release" ]; then
if [[ "$BUILD_CONFIGURATION" == "Debug" ]]; then
echo "Warning: Multiple build configurations found. Using 'Release' for testing.";
fi
BUILD_CONFIGURATION="Release";
fi
fi
if [ -z "$BUILD_CONFIGURATION" ]; then
echo "Error: No build configuration found.";
exit 1;
fi
CTEST_LABEL_OPT="";
CTEST_LABEL_OPT_ARG="(";
if [[ $ARG_UNIT == true ]]; then
CTEST_LABEL_OPT_ARG+="Unit";
fi
if [[ $ARG_INTEGRATION == true ]]; then
if [[ "$CTEST_LABEL_OPT_ARG" != "(" ]]; then
CTEST_LABEL_OPT_ARG+="|";
fi
CTEST_LABEL_OPT_ARG+="Integration";
fi
if [[ $ARG_FUNCTIONALITY == true ]]; then
if [[ "$CTEST_LABEL_OPT_ARG" != "(" ]]; then
CTEST_LABEL_OPT_ARG+="|";
fi
CTEST_LABEL_OPT_ARG+="Functionality";
fi
if [[ "$CTEST_LABEL_OPT_ARG" != "(" ]]; then
CTEST_LABEL_OPT="--label-regex";
CTEST_LABEL_OPT_ARG+=")";
else
CTEST_LABEL_OPT_ARG="";
fi
CTEST_ARG_STOP_ON_FAILURE="";
if [[ $ARG_STOP_ON_FAILURE == true ]]; then
CTEST_ARG_STOP_ON_FAILURE="--stop-on-failure";
fi
# UB-Sanitizer options.
export UBSAN_OPTIONS="halt_on_error=1:print_stacktrace=1";
# Run tests with CTest
ctest --output-on-failure \
--build-config "$BUILD_CONFIGURATION" \
$CTEST_ARG_STOP_ON_FAILURE \
$CTEST_LABEL_OPT $CTEST_LABEL_OPT_ARG;
ctest_status=$?;
if (( ctest_status == 0 )); then
if [[ $ARG_COVERAGE == true ]]; then
echo "Collecting coverage data";
if ! cmake --build . --target collect_coverage_data 1>/dev/null; then
echo "Failed to collect test coverage data";
exit 1;
fi
echo "Generating test coverage report";
genhtml_opt_args=();
genhtml_version=($(genhtml --version |grep -o -e '[0-9.]'));
genhtml_version="${genhtml_version[0]}";
if (( genhtml_version >= 2 )); then
genhtml_opt_args+=("--header-title");
genhtml_opt_args+=("Reckon Test Coverage");
genhtml_opt_args+=("--footer");
genhtml_opt_args+=("Reckon - ${VERSION}");
fi
if ! genhtml --quiet --output-directory "$BUILD_DIR_COV_REPORT" \
--prefix "$COV_INCL_PATH" \
--title "Reckon Test Coverage" \
"${genhtml_opt_args[@]}" \
"${BUILD_DIR_COV_DATA}/${FILE_COV_DATA_MERGED}"; then
echo "Failed to generate test coverage report";
exit 1;
fi
report_label="build/${BUILD_DIR_COV_REPORT}/index.html";
if tty --silent; then
report_url="file://${COV_INCL_PATH}/${report_label}";
report_link="\e]8;;${report_url}\e\\\\${report_label}\e]8;;\e\\\\";
else
report_link="$report_label";
fi
echo -e "Wrote HTML report to $report_link";
fi
fi
exit $ctest_status;