-
Notifications
You must be signed in to change notification settings - Fork 104
Expand file tree
/
Copy pathrun_tests.sh
More file actions
executable file
·576 lines (506 loc) · 20.3 KB
/
Copy pathrun_tests.sh
File metadata and controls
executable file
·576 lines (506 loc) · 20.3 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
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
#!/bin/bash
# Script to run tests for Hackers iOS app modules
# Usage: ./run_tests.sh [options] [modules...]
#
# Options:
# -v, --verbose Show detailed output
# -h, --help Show this help message
#
# Examples:
# ./run_tests.sh # Run all modules (quiet)
# ./run_tests.sh -v # Run all modules (verbose)
# ./run_tests.sh Domain Data # Run specific modules (quiet)
# ./run_tests.sh -v Feed Comments Settings # Run specific modules (verbose)
# ./run_tests.sh Feed Comments Settings # Feature modules are grouped into one xcodebuild run
set -e # Exit on any error
# Graceful Ctrl-C handling: kill active xcodebuild and exit
INTERRUPTED=false
CURRENT_CHILD_PID=""
on_interrupt() {
# Best-effort cleanup of the current xcodebuild process group
echo
print_status $YELLOW "🛑 Caught interrupt. Cleaning up..."
if [ -n "$CURRENT_CHILD_PID" ]; then
# Best effort: signal the xcodebuild process and its direct children
kill -INT "$CURRENT_CHILD_PID" 2>/dev/null || true
# Try to terminate direct children as well
for kid in $(pgrep -P "$CURRENT_CHILD_PID" 2>/dev/null); do
kill -INT "$kid" 2>/dev/null || true
done
sleep 0.5
kill -TERM "$CURRENT_CHILD_PID" 2>/dev/null || true
for kid in $(pgrep -P "$CURRENT_CHILD_PID" 2>/dev/null); do
kill -TERM "$kid" 2>/dev/null || true
done
sleep 0.5
kill -KILL "$CURRENT_CHILD_PID" 2>/dev/null || true
for kid in $(pgrep -P "$CURRENT_CHILD_PID" 2>/dev/null); do
kill -KILL "$kid" 2>/dev/null || true
done
fi
INTERRUPTED=true
}
# Install traps after functions are defined
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
PURPLE='\033[0;35m'
CYAN='\033[0;36m'
NC='\033[0m' # No Color
# Configuration
BASE_DIR="$(pwd)"
DESTINATION="${CI_DESTINATION:-platform=iOS Simulator,name=iPhone 17 Pro}"
VERBOSE=false
# All available modules
# Format: ModuleName:PackagePath:Scheme:OnlyTestingTarget
ALL_MODULES=(
# Run WhatsNew first to avoid any prior defaults pollution from other modules
"WhatsNew:${BASE_DIR}/Features:Features-Package:WhatsNewTests"
"Domain:${BASE_DIR}/Domain:Domain:"
"Data:${BASE_DIR}/Data:Data:"
"Networking:${BASE_DIR}/Networking:Networking:"
"DesignSystem:${BASE_DIR}/DesignSystem:DesignSystem:"
"Authentication:${BASE_DIR}/Features:Features-Package:AuthenticationTests"
"Shared:${BASE_DIR}/Shared:Shared:"
"Feed:${BASE_DIR}/Features:Features-Package:FeedTests"
"Comments:${BASE_DIR}/Features:Features-Package:CommentsTests"
"Settings:${BASE_DIR}/Features:Features-Package:SettingsTests"
)
# Function to print colored output
print_status() {
local color=$1
local message=$2
echo -e "${color}${message}${NC}"
}
# Now that print helpers exist, install traps
trap on_interrupt INT TERM
# Function to print help
print_help() {
cat << EOF
Hackers iOS App Test Runner
USAGE:
./run_tests.sh [OPTIONS] [MODULES...]
OPTIONS:
-v, --verbose Show detailed xcodebuild output and test details
-h, --help Show this help message
MODULES:
Domain Domain layer tests
Data Data layer tests
Networking Network manager tests
DesignSystem Design system tests
Shared Shared utilities tests
Authentication Authentication feature tests
Feed Feed feature tests
Comments Comments feature tests
Settings Settings feature tests
WhatsNew WhatsNew feature tests
EXAMPLES:
./run_tests.sh # Run all modules (quiet)
./run_tests.sh -v # Run all modules (verbose)
./run_tests.sh Domain Data # Run specific modules
./run_tests.sh -v Feed Comments Settings # Run specific modules (verbose)
EOF
}
# Function to parse test output and extract failures
parse_test_failures() {
local output_file=$1
local module_name=$2
# Swift Testing failures (robust matching)
if grep -qE "(^|[[:space:]])✘ |failed after [0-9.]+ seconds with [0-9]+ issue|recorded an issue at|✘ Test run with [0-9]+ tests .* failed" "$output_file"; then
print_status $RED " 📋 Failed Swift Tests:"
# List failing tests by name from the definitive 'failed after' lines
grep -E '^✘ Test ".*" failed after' "$output_file" | \
sed -E 's/^✘ Test "([^"]+)".*/\1/' | while read -r test_name; do
[ -z "$test_name" ] && continue
print_status $RED " • $test_name"
# Try to find a file:line for this test
local rec_line
rec_line=$(grep -F "✘ Test \"$test_name\" recorded an issue at" "$output_file" | head -1 || true)
if [[ $rec_line =~ recorded\ an\ issue\ at\ ([^:]+):([0-9]+) ]]; then
local file_name="${BASH_REMATCH[1]}"
local line_number="${BASH_REMATCH[2]}"
print_status $PURPLE " └─ $file_name:$line_number"
fi
done
# If no explicit 'failed after' lines (edge case), fall back to recorded lines
if ! grep -qE '^✘ Test ".*" failed after' "$output_file"; then
grep -E '^✘ Test ".*" recorded an issue at' "$output_file" | while read -r line; do
if [[ $line =~ ✘[[:space:]]*Test[[:space:]]*\"([^\"]+)\" ]]; then
local tn="${BASH_REMATCH[1]}"
print_status $RED " • $tn"
fi
if [[ $line =~ recorded\ an\ issue\ at\ ([^:]+):([0-9]+) ]]; then
local file_name="${BASH_REMATCH[1]}"
local line_number="${BASH_REMATCH[2]}"
print_status $PURPLE " └─ $file_name:$line_number"
fi
done
fi
fi
# XCTest failures (older format)
if grep -q "Test Case.*failed" "$output_file"; then
print_status $RED " 📋 Failed XCTests:"
grep "Test Case.*failed" "$output_file" | while read -r line; do
if [[ $line =~ Test\ Case\ \'([^\']+)\'.*failed ]]; then
local test_name="${BASH_REMATCH[1]}"
print_status $RED " • $test_name"
fi
done
fi
# Compilation errors (top 5). Match the compiler diagnostic shape
# (file:line:col: error:) so benign runtime OS diagnostics such as
# "...with error: Error Domain=NSCocoaErrorDomain ..." do not trip this.
if grep -qE "[^[:space:]]+:[0-9]+:[0-9]+:[[:space:]]*error:" "$output_file"; then
print_status $RED " 🔨 Compilation Errors:"
grep -E "[^[:space:]]+:[0-9]+:[0-9]+:[[:space:]]*error:" "$output_file" | head -5 | while read -r line; do
if [[ $line =~ ([^:]+):([0-9]+):[0-9]+:\ error:\ (.+) ]]; then
local file_name=$(basename "${BASH_REMATCH[1]}")
local line_number="${BASH_REMATCH[2]}"
local error_msg="${BASH_REMATCH[3]}"
print_status $RED " • $file_name:$line_number - $error_msg"
fi
done
fi
# Build/Test banners for context (trimmed)
if grep -qE "BUILD FAILED|TEST FAILED" "$output_file"; then
local failure_context=$(grep -A 3 -B 1 "BUILD FAILED\|TEST FAILED" "$output_file" | head -10)
if [ -n "$failure_context" ]; then
print_status $RED " 💥 Build/Test Failure Context:"
echo "$failure_context" | sed 's/^/ /' | head -3
fi
fi
}
# Function to extract and display individual test results
extract_individual_tests() {
local output_file=$1
local module_name=$2
# Extract passed tests from Swift Testing output
grep -E "✔ Test \".*\" passed after" "$output_file" | while read -r line; do
if [[ $line =~ ✔[[:space:]]*Test[[:space:]]*\"([^\"]+)\"[[:space:]]*passed ]]; then
local test_name="${BASH_REMATCH[1]}"
print_status $GREEN " ✅ $test_name"
fi
done
# Extract failed tests from Swift Testing output
grep -E "✘ Test \".*\" failed after" "$output_file" | while read -r line; do
if [[ $line =~ ✘[[:space:]]*Test[[:space:]]*\"([^\"]+)\"[[:space:]]*failed ]]; then
local test_name="${BASH_REMATCH[1]}"
print_status $RED " ❌ $test_name"
fi
done
# Extract XCTest results (older format)
grep -E "Test Case '.*' (passed|failed)" "$output_file" | while read -r line; do
if [[ $line =~ Test\ Case\ \'([^\']+)\'.*passed ]]; then
local test_name="${BASH_REMATCH[1]}"
print_status $GREEN " ✅ $test_name"
elif [[ $line =~ Test\ Case\ \'([^\']+)\'.*failed ]]; then
local test_name="${BASH_REMATCH[1]}"
print_status $RED " ❌ $test_name"
fi
done
}
# Function to run tests for a module
run_module_tests() {
local module_name=$1
local module_path=$2
local test_scheme=$3
local only_testing=$4
local temp_output=$(mktemp)
local start_time=$(date +%s)
local xcodebuild_args=(test -scheme "$test_scheme" -destination "$DESTINATION")
if [ -n "$only_testing" ]; then
for test_filter in $only_testing; do
xcodebuild_args+=("-only-testing:$test_filter")
done
fi
# Per-module environment resets to avoid cross-module state leakage
case "$module_name" in
Features|WhatsNew)
# Ensure a clean suite for whats new defaults
defaults delete com.weiran.hackers.whatsnew.tests >/dev/null 2>&1 || true
;;
Networking)
# Clear global cookie storage to avoid cross-test bleedthrough
xcrun swift -e 'import Foundation; HTTPCookieStorage.shared.cookies?.forEach{ HTTPCookieStorage.shared.deleteCookie($0) }' >/dev/null 2>&1 || true
;;
*) ;;
esac
if [ "$VERBOSE" = true ]; then
print_status $YELLOW "🧪 Running tests for ${module_name}..."
print_status $BLUE " 📁 Path: ${module_path}"
print_status $BLUE " 🎯 Scheme: ${test_scheme}"
if [ -n "$only_testing" ]; then
print_status $BLUE " 🔎 Filter: ${only_testing}"
fi
else
print_status $YELLOW "🧪 Testing ${module_name}..."
fi
# Move to module dir
pushd "$module_path" >/dev/null
# Run the tests and capture output
exit_code=0
if [ "$VERBOSE" = true ]; then
xcodebuild "${xcodebuild_args[@]}" \
> >(tee "$temp_output") 2>&1 &
child_pid=$!
else
xcodebuild "${xcodebuild_args[@]}" \
> "$temp_output" 2>&1 &
child_pid=$!
fi
# Record child PID for cleanup on Ctrl-C
CURRENT_CHILD_PID=$child_pid
# Wait for completion capturing the real xcodebuild status without tripping set -e.
# Do not use `if ! wait ...`; `$?` would be the negated status, masking crashes.
if wait "$child_pid"; then
exit_code=0
else
exit_code=$?
fi
# Return to previous dir
popd >/dev/null
# Clear the tracked child
CURRENT_CHILD_PID=""
local end_time=$(date +%s)
local duration=$((end_time - start_time))
# Some versions of Swift Testing/Xcode can return exit code 0 even with recorded issues,
# or print a spurious "TEST FAILED" banner after a successful Swift Testing run.
# Detect genuine Swift Testing failures and ignore false negatives.
local has_swift_fail=1
local has_xctest_fail=1
local has_compilation_error=1
local has_pass_summary=1
local has_process_crash=1
if grep -qE "(^|[[:space:]])✘ |failed after [0-9.]+ seconds with [0-9]+ issue|recorded an issue at|✘ Test run with [0-9]+ tests .* failed" "$temp_output"; then
has_swift_fail=0
fi
if grep -q "Test Case .*failed" "$temp_output"; then
has_xctest_fail=0
fi
if grep -qE "[^[:space:]]+:[0-9]+:[0-9]+:[[:space:]]*error:" "$temp_output"; then
has_compilation_error=0
fi
if grep -qE "✔ Test run with [0-9]+ tests in [0-9]+ suites passed|Test Suite '.*' passed" "$temp_output"; then
has_pass_summary=0
fi
if [ "${exit_code:-0}" -ge 128 ] || grep -qiE "segmentation fault|abort trap|bus error|trace/bpt trap|killed:|crashed" "$temp_output"; then
has_process_crash=0
fi
# If xcodebuild failed without a test/build failure, only normalize to success when
# the output contains a definitive pass summary and the process itself did not crash.
if [ "${exit_code:-0}" -ne 0 ]; then
if [ $has_swift_fail -ne 0 ] && \
[ $has_xctest_fail -ne 0 ] && \
[ $has_compilation_error -ne 0 ] && \
[ $has_pass_summary -eq 0 ] && \
[ $has_process_crash -ne 0 ]; then
exit_code=0
else
exit_code=1
fi
fi
# If we detected real test failures, compilation errors, or a tool crash, force failure.
if [ $has_swift_fail -eq 0 ] || [ $has_xctest_fail -eq 0 ] || [ $has_compilation_error -eq 0 ] || [ $has_process_crash -eq 0 ]; then
exit_code=1
fi
if [ "$INTERRUPTED" = true ]; then
# Respect user interrupt immediately
return 130
fi
if [ "${exit_code:-0}" -eq 0 ]; then
# Success - extract test summary
local test_summary=$(grep -E "Executed [0-9]+ tests|✔.*tests.*passed" "$temp_output" | tail -1)
local swift_summary=$(grep -E "✔ Test run with [0-9]+ tests" "$temp_output" | tail -1)
if [[ "$only_testing" == *" "* ]]; then
print_status $GREEN "✅ ${module_name} tests passed (${duration}s)"
elif [ -n "$swift_summary" ]; then
print_status $GREEN "✅ ${module_name} - $swift_summary (${duration}s)"
elif [ -n "$test_summary" ]; then
print_status $GREEN "✅ ${module_name} - $test_summary (${duration}s)"
else
print_status $GREEN "✅ ${module_name} tests passed (${duration}s)"
fi
# Show individual test results in non-verbose mode
if [ "$VERBOSE" = false ]; then
extract_individual_tests "$temp_output" "$module_name"
fi
rm "$temp_output"
return 0
else
# Failure - show detailed error information
print_status $RED "❌ ${module_name} tests failed (${duration}s)"
# Show individual test results in non-verbose mode
if [ "$VERBOSE" = false ]; then
extract_individual_tests "$temp_output" "$module_name"
fi
# Parse and display specific failures
parse_test_failures "$temp_output" "$module_name"
if [ $has_process_crash -eq 0 ]; then
print_status $RED " 💥 xcodebuild crashed or was terminated unexpectedly"
fi
# If not verbose, show last few lines for context
if [ "$VERBOSE" = false ]; then
print_status $RED " 🔍 Last few lines of output:"
tail -5 "$temp_output" | sed 's/^/ /'
fi
rm "$temp_output"
return 1
fi
}
# Function to validate module exists
validate_module() {
local module_name=$1
for module in "${ALL_MODULES[@]}"; do
IFS=':' read -r name path scheme only_testing <<< "$module"
if [ "$name" = "$module_name" ]; then
return 0
fi
done
return 1
}
# Function to find module metadata by name
module_metadata() {
local module_name=$1
for module in "${ALL_MODULES[@]}"; do
IFS=':' read -r name path scheme filter <<< "$module"
if [ "$name" = "$module_name" ]; then
echo "$module"
return 0
fi
done
return 1
}
# Parse command line arguments
MODULES_TO_RUN=()
while [[ $# -gt 0 ]]; do
case $1 in
-v|--verbose)
VERBOSE=true
shift
;;
-h|--help)
print_help
exit 0
;;
*)
# Validate module name
if validate_module "$1"; then
MODULES_TO_RUN+=("$1")
else
print_status $RED "Error: Unknown module '$1'"
print_status $YELLOW "Available modules: Domain, Data, Networking, DesignSystem, Shared, Feed, Comments, Settings, WhatsNew"
exit 1
fi
shift
;;
esac
done
# If no modules specified, run all
if [ ${#MODULES_TO_RUN[@]} -eq 0 ]; then
for module in "${ALL_MODULES[@]}"; do
IFS=':' read -r name path scheme only_testing <<< "$module"
MODULES_TO_RUN+=("$name")
done
fi
# Group feature package tests into one xcodebuild invocation when more than one
# feature module is selected. This avoids repeated Features-Package startup cost
# while preserving individual module selection for focused runs.
RUN_GROUPS=()
feature_module_count=0
feature_filters=()
feature_names=()
for module_name in "${MODULES_TO_RUN[@]}"; do
module=$(module_metadata "$module_name")
IFS=':' read -r name path scheme filter <<< "$module"
if [ "$scheme" = "Features-Package" ]; then
feature_module_count=$((feature_module_count + 1))
feature_filters+=("$filter")
feature_names+=("$name")
else
RUN_GROUPS+=("$module")
fi
done
if [ $feature_module_count -eq 1 ]; then
RUN_GROUPS=("$(module_metadata "${feature_names[0]}")" "${RUN_GROUPS[@]}")
elif [ $feature_module_count -gt 1 ]; then
RUN_GROUPS=("Features:${BASE_DIR}/Features:Features-Package:${feature_filters[*]}" "${RUN_GROUPS[@]}")
fi
# Check if we're in the right directory
if [ ! -d "$BASE_DIR" ]; then
print_status $RED "Error: Base directory $BASE_DIR not found"
print_status $YELLOW "Please run this script from the correct location"
exit 1
fi
# Print header
echo
print_status $CYAN "🚀 Hackers iOS Test Runner"
print_status $BLUE "📱 Target: iOS Simulator (iPhone 17 Pro)"
print_status $BLUE "📊 Mode: $([ "$VERBOSE" = true ] && echo "Verbose" || echo "Quiet")"
print_status $BLUE "📦 Modules: ${MODULES_TO_RUN[*]}"
if [ ${#RUN_GROUPS[@]} -ne ${#MODULES_TO_RUN[@]} ]; then
group_names=()
for group in "${RUN_GROUPS[@]}"; do
IFS=':' read -r name path scheme filter <<< "$group"
group_names+=("$name")
done
print_status $BLUE "🧩 Test groups: ${group_names[*]}"
fi
echo
# Track results
total_groups=0
passed_groups=0
failed_groups=()
overall_start_time=$(date +%s)
# Run tests for selected groups
for group in "${RUN_GROUPS[@]}"; do
IFS=':' read -r module_name module_path test_scheme only_testing <<< "$group"
if [ -z "$module_path" ]; then
print_status $RED "Error: Could not find path for test group $module_name"
continue
fi
total_groups=$((total_groups + 1))
if run_module_tests "$module_name" "$module_path" "$test_scheme" "$only_testing"; then
passed_groups=$((passed_groups + 1))
else
# If interrupted, stop the loop immediately
if [ "$INTERRUPTED" = true ]; then
break
fi
failed_groups+=("$module_name")
fi
echo
done
overall_end_time=$(date +%s)
total_duration=$((overall_end_time - overall_start_time))
# If interrupted, exit quickly with a clear message
if [ "$INTERRUPTED" = true ]; then
echo "================================================================"
print_status $YELLOW "🛑 Test run interrupted by user"
echo "================================================================"
exit 130
fi
# Print final summary
echo "================================================================"
print_status $CYAN "📊 FINAL TEST SUMMARY"
echo "================================================================"
print_status $BLUE "⏱️ Total duration: ${total_duration}s"
print_status $BLUE "🎯 Success rate: ${passed_groups}/${total_groups} test groups"
if [ ${#failed_groups[@]} -eq 0 ]; then
print_status $GREEN "🎉 All tests passed successfully!"
print_status $GREEN "✨ ${passed_groups}/${total_groups} test groups completed"
exit 0
else
echo
print_status $RED "❌ Failed test groups (${#failed_groups[@]}):"
for failed_group in "${failed_groups[@]}"; do
print_status $RED " • ${failed_group}"
done
echo
print_status $YELLOW "💡 Tips:"
print_status $YELLOW " • Run with -v flag for detailed output"
print_status $YELLOW " • Test individual modules: ./run_tests.sh ModuleName"
print_status $YELLOW " • Feature tests use the Features-Package scheme with -only-testing:[Module]Tests"
exit 1
fi