-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathMakefile.qa
More file actions
962 lines (834 loc) · 45.3 KB
/
Makefile.qa
File metadata and controls
962 lines (834 loc) · 45.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
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
# LessUI Quality Assurance
# Static analysis, testing, and code formatting
#
# This makefile provides quality assurance tools for the LessUI codebase:
# - Unit tests (Docker-based, using Ubuntu 24.04)
# - Static analysis (clang-tidy for C code, shellcheck for scripts)
# - Code formatting (clang-format with project style)
#
# Quick start:
# make test - Run all unit tests (recommended)
# make lint - Run all linting checks
# make format - Auto-format code (modifies files)
#
# The main makefile forwards to this file for test/lint/format targets.
# Run 'make -f Makefile.qa help' for complete target list.
.PHONY: help lint lint-code lint-full lint-shell lint-log-newlines lint-unsafe-functions analyze analyze-native test test-native test-asan test-asan-native format format-native format-check format-shell format-shell-check format-make format-make-check format-prettier format-prettier-check clean-qa clean-tests docker-build docker-pull docker-test docker-test-asan docker-test-verbose docker-test-quiet docker-lint docker-analyze docker-format docker-format-check docker-shell lint-native report coverage docker-coverage clean-coverage check-clang-tidy check-shellcheck check-clang-format check-shfmt check-mbake check-prettier check-scan-build
help:
@echo "LessUI Quality Assurance Tools"
@echo ""
@echo "Main targets (use these):"
@echo " make test - Run unit tests (Docker, recommended)"
@echo " make test-asan - Run tests with AddressSanitizer + UBSan (Docker)"
@echo " make coverage - Run tests with coverage analysis (Docker)"
@echo " make lint - Run ALL linting checks (clang-tidy, format-check, shellcheck)"
@echo " make format - Format all code (C, shell, makefiles)"
@echo ""
@echo "Individual lint targets:"
@echo " make lint-code - Run clang-tidy on workspace/all/"
@echo " make lint-full - Run clang-tidy on entire workspace (verbose)"
@echo " make analyze - Run Clang Static Analyzer (deep analysis)"
@echo " make format-check - Check if C code is formatted (no changes)"
@echo " make lint-shell - Run shellcheck on shell scripts"
@echo ""
@echo "Formatting targets:"
@echo " make format-shell - Format shell scripts with shfmt"
@echo " make format-shell-check - Check shell script formatting"
@echo " make format-make - Format makefiles with mbake"
@echo " make format-make-check - Check makefile formatting"
@echo " make format-prettier - Format JSON, YAML, Markdown with prettier"
@echo " make format-prettier-check - Check JSON, YAML, Markdown formatting"
@echo ""
@echo "Docker targets:"
@echo " make docker-test - Run tests in Docker container (Ubuntu 24.04)"
@echo " make docker-test-asan - Run tests with sanitizers in Docker"
@echo " make docker-coverage - Run coverage analysis in Docker container"
@echo " make docker-analyze - Run static analysis in Docker container"
@echo " make docker-build - Build Docker image"
@echo " make docker-shell - Enter Docker container for debugging"
@echo ""
@echo "Other:"
@echo " make clean-qa - Clean QA artifacts"
@echo " make clean-coverage - Clean coverage data and reports"
@echo ""
@echo "All QA tools run in Docker - no local installation required."
# clang-tidy configuration
# Checks for: bugs, performance issues, readability, security
# Configuration in .clang-tidy file
# workspace/all/ is the primary focus (platform-independent code)
CLANG_TIDY = clang-tidy
TIDY_FLAGS = --quiet
# Source files to analyze
# Note: clock.c is excluded because it uses GCC nested functions (non-standard)
# All other files are linted on all platforms
TIDY_SOURCES = workspace/all/launcher/*.c \
workspace/all/player/*.c \
workspace/all/common/*.c \
workspace/all/minput/*.c \
workspace/all/syncsettings/*.c
# Auto-detect SDL2 include path (works on both macOS and Ubuntu)
SDL2_CFLAGS := $(shell sdl2-config --cflags 2>/dev/null)
SDL2_PREFIX := $(shell sdl2-config --prefix 2>/dev/null)
# Desktop platform configuration for linting (works on macOS and Linux)
# Need parent include dir for <SDL2/SDL.h> style includes on macOS
ifneq ($(SDL2_PREFIX),)
TIDY_SDL_INCLUDES = -I$(SDL2_PREFIX)/include $(SDL2_CFLAGS)
else
TIDY_SDL_INCLUDES = $(SDL2_CFLAGS)
endif
# Compiler flags for clang-tidy (needs to understand how to compile the code)
# Uses tg5040 platform (SDL2-based, representative of modern platforms)
# Enable logging macros so arguments are seen as used by the analyzer
TIDY_COMPILE_FLAGS = -DPLATFORM=\"tg5040\" \
-DSDCARD_PATH=\"/mnt/SDCARD\" \
-DUSE_SDL2 \
-DENABLE_INFO_LOGS \
-DENABLE_DEBUG_LOGS \
-DBUILD_DATE=\"$(shell date +%Y%m%d)\" \
-DBUILD_HASH=\"tidy\" \
-I workspace/all/common \
-I workspace/all/launcher \
-I workspace/all/player \
-isystem workspace/all/vendor/stb \
-I workspace/tg5040/platform \
-I workspace/tg5040/libmsettings \
-I workspace/all/player/libretro-common/include \
-iquote workspace/all/player/libretro-common/include \
-I tests/support \
$(TIDY_SDL_INCLUDES) \
-std=gnu99
# Check if tools are installed (should be available in Docker container)
check-clang-tidy:
@which $(CLANG_TIDY) > /dev/null || (echo "Error: clang-tidy not installed" && exit 1)
check-shellcheck:
@which shellcheck > /dev/null || (echo "Error: shellcheck not installed" && exit 1)
# Run all linting checks (uses Docker for consistency with CI)
lint: docker-lint
# Check for unsafe string functions (sprintf, strcat, strcpy, gets)
# These should use safer alternatives: snprintf, strncat, strncpy, fgets
lint-unsafe-functions:
@echo "Checking for unsafe string functions..."
@UNSAFE_FILES=$$(grep -rnE '\b(sprintf|strcat|strcpy|gets)\(' \
workspace/all/common \
workspace/all/launcher \
workspace/all/player \
workspace/all/utils \
--include='*.c' 2>/dev/null | \
grep -vE 'snprintf|strncat|strncpy|fgets' | \
grep -v 'libretro-common' | \
grep -v 'parson' | \
grep -v 'vendor' | \
grep -v '// sprintf' || true); \
if [ -n "$$UNSAFE_FILES" ]; then \
echo "✗ Found unsafe string functions:"; \
echo "$$UNSAFE_FILES" | sed 's/^/ /'; \
echo ""; \
echo "Use safer alternatives:"; \
echo " sprintf → snprintf"; \
echo " strcat → strncat or snprintf"; \
echo " strcpy → strncpy or SAFE_STRCPY"; \
echo " gets → fgets"; \
exit 1; \
else \
echo "✓ No unsafe string functions found"; \
fi
# Check for LOG_* calls with trailing \n (newlines are added automatically)
lint-log-newlines:
@echo "Checking for LOG_* calls with trailing newlines..."
@BAD_LOGS=$$(grep -rnE 'LOG_(error|warn|info|debug|errno)\([^)]*\\n"' \
workspace \
--include='*.c' 2>/dev/null | \
grep -v 'libretro-common' | \
grep -v 'parson' | \
grep -v 'vendor' | \
grep -v '^\s*//' || true); \
if [ -n "$$BAD_LOGS" ]; then \
echo "✗ Found LOG_* calls with trailing \\n:"; \
echo "$$BAD_LOGS" | sed 's/^/ /'; \
echo ""; \
echo "Remove \\n from log messages (newlines are added automatically)"; \
exit 1; \
else \
echo "✓ No LOG_* calls with trailing newlines"; \
fi
# Run clang-tidy on common code (platform-independent, highest priority)
# Filters out compiler noise ("X warnings generated") to show only actual issues
lint-code: check-clang-tidy
@echo "Running clang-tidy on workspace/all/ (common code)..."
@echo "Checking for: bugs, performance, portability, security (see .clang-tidy)"
@echo ""
@FILE_COUNT=0; \
TMPFILE=$$(mktemp); \
for file in $(TIDY_SOURCES); do \
if [ -f "$$file" ]; then \
FILE_COUNT=$$((FILE_COUNT + 1)); \
if ! $(CLANG_TIDY) $(TIDY_FLAGS) "$$file" -- $(TIDY_COMPILE_FLAGS) > "$$TMPFILE" 2>&1; then \
grep -Ev "(warnings? generated|Use -header-filter|Suppressed .* warnings)" "$$TMPFILE" || true; \
rm -f "$$TMPFILE"; \
exit 1; \
fi; \
fi; \
done; \
rm -f "$$TMPFILE"; \
echo ""; \
echo "✓ Static analysis complete ($$FILE_COUNT files checked, 0 issues)"
# Run clang-tidy on entire workspace (includes platform-specific code)
lint-full: check-clang-tidy
@echo "Running clang-tidy on entire workspace..."
@find workspace -name "*.c" -type f \
-not -path "*/libretro-common/*" \
-not -path "*/cores/*" \
-not -path "*/toolchains/*" \
-not -path "*/other/*" \
-exec $(CLANG_TIDY) $(TIDY_FLAGS) {} -- $(TIDY_COMPILE_FLAGS) \;
# Clang Static Analyzer (deep dataflow analysis)
# Finds: NULL derefs, memory leaks, dead code, logic errors
# Runs in Docker by default for consistency with CI
SCAN_BUILD = scan-build
ANALYZE_FLAGS = -DPLATFORM=\"desktop\" -DSDCARD_PATH=\"../../desktop/FAKESD\" -DENABLE_INFO_LOGS -DENABLE_DEBUG_LOGS \
-I workspace/all/common -I workspace/all/launcher -I workspace/all/player \
-I workspace/desktop/platform \
-I workspace/all/player/libretro-common/include \
-iquote workspace/all/player/libretro-common/include \
$(SDL2_CFLAGS) -DUSE_SDL2 -std=gnu99
check-scan-build:
@which $(SCAN_BUILD) > /dev/null || (echo "Error: scan-build not installed" && exit 1)
# Default: use Docker for consistency
analyze: docker-analyze
# Native analysis (for advanced use)
analyze-native: check-scan-build
@echo "================================="
@echo "Running Clang Static Analyzer..."
@echo "================================="
@echo ""
@echo "Analyzing player.c..."
@rm -rf /tmp/lessui-analysis
@$(SCAN_BUILD) -o /tmp/lessui-analysis --status-bugs \
gcc -c workspace/all/player/player.c $(ANALYZE_FLAGS) \
-o /tmp/player.o 2>&1 | grep -v "note:" || true
@echo ""
@echo "Analyzing launcher.c..."
@$(SCAN_BUILD) -o /tmp/lessui-analysis --status-bugs \
gcc -c workspace/all/launcher/launcher.c $(ANALYZE_FLAGS) \
-o /tmp/launcher.o 2>&1 | grep -v "note:" || true
@echo ""
@echo "Analyzing common/*.c..."
@for file in workspace/all/common/*.c; do \
$(SCAN_BUILD) -o /tmp/lessui-analysis --status-bugs \
gcc -c $$file $(ANALYZE_FLAGS) \
-o /tmp/common.o 2>&1 | grep -v "note:" || true; \
done
@echo ""
@echo "================================="
@if ls /tmp/lessui-analysis/*/index.html >/dev/null 2>&1; then \
echo "⚠️ Issues found! View report:"; \
echo " open /tmp/lessui-analysis/*/index.html"; \
else \
echo "✓ No issues found"; \
fi
@echo "================================="
###########################################################
# Test Configuration
TEST_CFLAGS = -std=c99 -Wall -Wextra -Wno-unused-parameter
TEST_INCLUDES = -I tests/support -I tests/vendor/unity -I workspace/all/common -I workspace/all/launcher -I workspace/all/player -I workspace/all/vendor/stb
TEST_UNITY = tests/vendor/unity/unity.c
PATHS_STUB = tests/support/paths_stub.c
# All test executables (built from tests/unit/ and tests/integration/)
TEST_EXECUTABLES = tests/utils_test tests/nointro_parser_test tests/pad_test tests/gfx_text_test tests/audio_resampler_test tests/player_paths_test tests/launcher_utils_test tests/m3u_parser_test tests/launcher_file_utils_test tests/map_parser_test tests/collection_parser_test tests/recent_parser_test tests/recent_writer_test tests/recent_runtime_test tests/directory_utils_test tests/binary_file_utils_test tests/ui_layout_test tests/str_compare_test tests/effect_system_test tests/effect_generate_test tests/player_utils_test tests/player_config_test tests/player_options_test tests/platform_variant_test tests/launcher_entry_test tests/directory_index_test tests/player_archive_test tests/player_memory_test tests/player_state_test tests/launcher_launcher_test tests/cpu_test tests/player_input_test tests/launcher_state_test tests/player_menu_test tests/player_env_test tests/player_game_test tests/player_scaler_test tests/player_core_test tests/launcher_directory_test tests/launcher_navigation_test tests/launcher_thumbnail_test tests/launcher_context_test tests/emu_cache_test tests/res_cache_test tests/render_common_test tests/integration_workflows_test tests/log_test tests/sync_manager_test
# Default targets: use Docker for consistency
test: docker-test
format: docker-format
# Native test target (for Linux or if Docker unavailable)
test-native: $(TEST_EXECUTABLES)
@echo "Running unit tests (native)..."
@for test in $(TEST_EXECUTABLES); do \
./$$test; \
echo ""; \
done
@echo "✓ All tests passed"
# Build logging system tests
# Enable INFO and DEBUG logs so we can test all log levels
tests/log_test: tests/unit/all/common/test_log.c workspace/all/common/log.c $(TEST_UNITY)
@echo "Building logging system tests..."
@$(CC) $(TEST_INCLUDES) $(TEST_CFLAGS) -D_DEFAULT_SOURCE -DENABLE_INFO_LOGS -DENABLE_DEBUG_LOGS -o $@ $^ -lpthread
# Build comprehensive utils tests
tests/utils_test: tests/unit/all/common/test_utils.c workspace/all/common/utils.c $(PATHS_STUB) workspace/all/common/nointro_parser.c workspace/all/common/log.c $(TEST_UNITY)
@echo "Building comprehensive utils tests..."
@$(CC) -o $@ $^ $(TEST_INCLUDES) $(TEST_CFLAGS)
# Build No-Intro name parser tests
tests/nointro_parser_test: tests/unit/all/common/test_nointro_parser.c workspace/all/common/nointro_parser.c workspace/all/common/utils.c $(PATHS_STUB) workspace/all/common/log.c $(TEST_UNITY)
@echo "Building No-Intro name parser tests..."
@$(CC) -o $@ $^ $(TEST_INCLUDES) $(TEST_CFLAGS)
# Build PAD (input) tests
tests/pad_test: tests/unit/all/common/test_api_pad.c workspace/all/common/pad.c $(TEST_UNITY)
@echo "Building PAD input tests..."
@$(CC) -o $@ $^ $(TEST_INCLUDES) $(TEST_CFLAGS)
# Build GFX text utility tests (uses fff for TTF mocking)
tests/gfx_text_test: tests/unit/all/common/test_gfx_text.c workspace/all/common/gfx_text.c workspace/all/common/utils.c $(PATHS_STUB) workspace/all/common/nointro_parser.c workspace/all/common/log.c tests/support/sdl_fakes.c $(TEST_UNITY)
@echo "Building GFX text utility tests..."
@$(CC) -o $@ $^ $(TEST_INCLUDES) -I tests/vendor/fff $(TEST_CFLAGS) -DUNIT_TEST_BUILD
# Build audio resampler tests (pure algorithm, no mocking needed)
tests/audio_resampler_test: tests/unit/all/common/test_audio_resampler.c workspace/all/common/audio_resampler.c $(TEST_UNITY)
@echo "Building audio resampler tests..."
@$(CC) -o $@ $^ $(TEST_INCLUDES) $(TEST_CFLAGS)
# Build Player path generation tests (pure sprintf logic)
tests/player_paths_test: tests/unit/all/player/test_player_paths.c workspace/all/player/player_paths.c workspace/all/common/utils.c $(PATHS_STUB) workspace/all/common/nointro_parser.c workspace/all/common/log.c $(TEST_UNITY)
@echo "Building Player path generation tests..."
@$(CC) -o $@ $^ $(TEST_INCLUDES) $(TEST_CFLAGS)
# Build LessUI launcher utility tests (pure string logic)
tests/launcher_utils_test: tests/unit/all/launcher/test_launcher_utils.c workspace/all/launcher/launcher_utils.c workspace/all/common/utils.c $(PATHS_STUB) workspace/all/common/nointro_parser.c workspace/all/common/log.c $(TEST_UNITY)
@echo "Building LessUI launcher utility tests..."
@$(CC) -o $@ $^ $(TEST_INCLUDES) $(TEST_CFLAGS)
# Build M3U parser tests (uses file mocking with GCC --wrap, Docker-only)
tests/m3u_parser_test: tests/unit/all/launcher/test_m3u_parser.c workspace/all/launcher/launcher_m3u.c workspace/all/common/utils.c $(PATHS_STUB) workspace/all/common/nointro_parser.c workspace/all/common/log.c tests/support/fs_mocks.c $(TEST_UNITY)
@echo "Building M3U parser tests..."
@$(CC) -o $@ $^ $(TEST_INCLUDES) $(TEST_CFLAGS) -D_POSIX_C_SOURCE=200809L -Wl,--wrap=exists -Wl,--wrap=fopen -Wl,--wrap=fclose -Wl,--wrap=fgets
# Build LessUI file utility tests (uses file mocking with GCC --wrap, Docker-only)
tests/launcher_file_utils_test: tests/unit/all/launcher/test_launcher_file_utils.c workspace/all/launcher/launcher_file_utils.c workspace/all/common/utils.c $(PATHS_STUB) workspace/all/common/nointro_parser.c workspace/all/common/log.c tests/support/fs_mocks.c $(TEST_UNITY)
@echo "Building LessUI file utility tests..."
@$(CC) -o $@ $^ $(TEST_INCLUDES) $(TEST_CFLAGS) -Wl,--wrap=exists -Wl,--wrap=fopen -Wl,--wrap=fclose -Wl,--wrap=fgets
# Build map.txt parser tests (uses file mocking with GCC --wrap, Docker-only)
tests/map_parser_test: tests/unit/all/launcher/test_map_parser.c workspace/all/launcher/launcher_map.c workspace/all/common/stb_ds_impl.c workspace/all/common/utils.c $(PATHS_STUB) workspace/all/common/nointro_parser.c workspace/all/common/log.c tests/support/fs_mocks.c $(TEST_UNITY)
@echo "Building map.txt parser tests..."
@$(CC) -o $@ $^ $(TEST_INCLUDES) $(TEST_CFLAGS) -D_POSIX_C_SOURCE=200809L -Wl,--wrap=access -Wl,--wrap=exists -Wl,--wrap=fopen -Wl,--wrap=fclose -Wl,--wrap=fgets
# Build collection parser tests (uses file mocking with GCC --wrap, Docker-only)
tests/collection_parser_test: tests/unit/all/launcher/test_collection_parser.c workspace/all/launcher/collection_parser.c workspace/all/common/utils.c $(PATHS_STUB) workspace/all/common/nointro_parser.c workspace/all/common/log.c tests/support/fs_mocks.c $(TEST_UNITY)
@echo "Building collection parser tests..."
@$(CC) -o $@ $^ $(TEST_INCLUDES) $(TEST_CFLAGS) -D_POSIX_C_SOURCE=200809L -Wl,--wrap=exists -Wl,--wrap=fopen -Wl,--wrap=fclose -Wl,--wrap=fgets
# Build recent.txt file tests (uses file mocking with GCC --wrap, Docker-only)
tests/recent_parser_test: tests/unit/all/launcher/test_recent_parser.c workspace/all/launcher/recent_file.c workspace/all/common/utils.c $(PATHS_STUB) workspace/all/common/nointro_parser.c workspace/all/common/log.c tests/support/fs_mocks.c $(TEST_UNITY)
@echo "Building recent.txt parser tests..."
@$(CC) -o $@ $^ $(TEST_INCLUDES) $(TEST_CFLAGS) -D_POSIX_C_SOURCE=200809L -Wl,--wrap=exists -Wl,--wrap=fopen -Wl,--wrap=fclose -Wl,--wrap=fgets
# Build recent.txt writer tests (uses real temp files, no --wrap needed)
tests/recent_writer_test: tests/unit/all/launcher/test_recent_writer.c workspace/all/launcher/recent_file.c workspace/all/common/utils.c $(PATHS_STUB) workspace/all/common/nointro_parser.c workspace/all/common/log.c tests/support/test_temp.c $(TEST_UNITY)
@echo "Building recent.txt writer tests..."
@$(CC) -o $@ $^ $(TEST_INCLUDES) $(TEST_CFLAGS) -D_GNU_SOURCE
# Build recent runtime tests (Recent struct and array operations)
tests/recent_runtime_test: tests/unit/all/launcher/test_recent_runtime.c workspace/all/launcher/recent_file.c workspace/all/common/stb_ds_impl.c workspace/all/common/utils.c $(PATHS_STUB) workspace/all/common/nointro_parser.c workspace/all/common/log.c $(TEST_UNITY)
@echo "Building recent runtime tests..."
@$(CC) -o $@ $^ $(TEST_INCLUDES) $(TEST_CFLAGS) -D_POSIX_C_SOURCE=200809L
# Build directory utility tests (uses real temp directories, no --wrap needed)
tests/directory_utils_test: tests/unit/all/launcher/test_directory_utils.c workspace/all/launcher/launcher_file_utils.c workspace/all/common/utils.c $(PATHS_STUB) workspace/all/common/nointro_parser.c workspace/all/common/log.c $(TEST_UNITY)
@echo "Building directory utility tests..."
@$(CC) -o $@ $^ $(TEST_INCLUDES) $(TEST_CFLAGS) -D_DEFAULT_SOURCE
# Build binary file I/O tests (uses real temp files, no --wrap needed)
tests/binary_file_utils_test: tests/unit/all/common/test_binary_file_utils.c workspace/all/common/binary_file_utils.c workspace/all/common/log.c tests/support/test_temp.c $(TEST_UNITY)
@echo "Building binary file I/O tests..."
@$(CC) -o $@ $^ $(TEST_INCLUDES) $(TEST_CFLAGS) -D_GNU_SOURCE
# Build UI layout / DP system tests
# Tests the PPI calculation, row fitting algorithm, and derived size calculations
tests/ui_layout_test: tests/unit/all/common/test_ui_layout.c workspace/all/common/ui_layout.c workspace/all/common/log.c $(TEST_UNITY)
@echo "Building UI layout tests..."
@$(CC) -o $@ $^ $(TEST_INCLUDES) $(TEST_CFLAGS) -lm
# Build string comparison tests (natural sort, pure algorithm)
tests/str_compare_test: tests/unit/all/launcher/test_str_compare.c workspace/all/launcher/launcher_str_compare.c $(TEST_UNITY)
@echo "Building string comparison tests..."
@$(CC) -o $@ $^ $(TEST_INCLUDES) $(TEST_CFLAGS)
# Build effect system tests (includes effect_system.c directly to avoid SDL deps)
tests/effect_system_test: tests/unit/all/common/test_effect_system.c $(TEST_UNITY)
@echo "Building effect system tests..."
@$(CC) -o $@ $^ $(TEST_INCLUDES) $(TEST_CFLAGS)
# Build effect pattern generation tests (includes effect_generate.c directly)
tests/effect_generate_test: tests/unit/all/common/test_effect_generate.c $(TEST_UNITY)
@echo "Building effect pattern generation tests..."
@$(CC) -o $@ $^ $(TEST_INCLUDES) $(TEST_CFLAGS)
# Build player utility tests (pure functions extracted from player.c)
tests/player_utils_test: tests/unit/all/player/test_player_utils.c workspace/all/player/player_utils.c workspace/all/common/utils.c $(PATHS_STUB) workspace/all/common/nointro_parser.c workspace/all/common/log.c $(TEST_UNITY)
@echo "Building player utility tests..."
@$(CC) -o $@ $^ $(TEST_INCLUDES) $(TEST_CFLAGS)
# Build player configuration tests (config path and option utilities)
tests/player_config_test: tests/unit/all/player/test_player_config.c workspace/all/player/player_config.c workspace/all/player/player_paths.c workspace/all/common/utils.c $(PATHS_STUB) workspace/all/common/nointro_parser.c workspace/all/common/log.c $(TEST_UNITY)
@echo "Building player configuration tests..."
@$(CC) -o $@ $^ $(TEST_INCLUDES) $(TEST_CFLAGS)
# Build player option management tests (option list search and manipulation)
tests/player_options_test: tests/unit/all/player/test_player_options.c workspace/all/player/player_options.c $(TEST_UNITY)
@echo "Building player option management tests..."
@$(CC) -o $@ $^ $(TEST_INCLUDES) $(TEST_CFLAGS) -D_POSIX_C_SOURCE=200809L
# Build platform variant tests (platform detection and device info)
tests/platform_variant_test: tests/unit/all/common/test_platform_variant.c workspace/all/common/platform_variant.c $(TEST_UNITY)
@echo "Building platform variant tests..."
@$(CC) -o $@ $^ $(TEST_INCLUDES) $(TEST_CFLAGS)
# Build Launcher entry type tests (Entry struct and array operations)
tests/launcher_entry_test: tests/unit/all/launcher/test_launcher_entry.c workspace/all/launcher/launcher_entry.c workspace/all/common/stb_ds_impl.c workspace/all/launcher/launcher_str_compare.c workspace/all/common/utils.c $(PATHS_STUB) workspace/all/common/nointro_parser.c workspace/all/common/log.c $(TEST_UNITY)
@echo "Building Launcher entry tests..."
@$(CC) -o $@ $^ $(TEST_INCLUDES) $(TEST_CFLAGS) -D_POSIX_C_SOURCE=200809L
# Build directory indexing tests (alias application, filtering, duplicate detection)
tests/directory_index_test: tests/unit/all/launcher/test_directory_index.c workspace/all/launcher/directory_index.c workspace/all/launcher/launcher_entry.c workspace/all/launcher/launcher_map.c workspace/all/common/stb_ds_impl.c workspace/all/launcher/launcher_str_compare.c workspace/all/common/utils.c $(PATHS_STUB) workspace/all/common/nointro_parser.c workspace/all/common/log.c $(TEST_UNITY)
@echo "Building directory indexing tests..."
@$(CC) -o $@ $^ $(TEST_INCLUDES) $(TEST_CFLAGS) -D_POSIX_C_SOURCE=200809L
# Build archive extraction tests (uses 7z binary via system())
tests/player_archive_test: tests/unit/all/player/test_player_archive.c workspace/all/player/player_archive.c workspace/all/player/player_game.c workspace/all/common/utils.c $(PATHS_STUB) workspace/all/common/nointro_parser.c workspace/all/common/log.c $(TEST_UNITY)
@echo "Building archive extraction tests..."
@$(CC) -o $@ $^ $(TEST_INCLUDES) $(TEST_CFLAGS) -D_POSIX_C_SOURCE=200809L
# Build memory persistence tests (SRAM/RTC with mock core callbacks, uses real temp files)
tests/player_memory_test: tests/unit/all/player/test_player_memory.c workspace/all/player/player_memory.c tests/support/libretro_mocks.c tests/support/test_temp.c $(TEST_UNITY)
@echo "Building memory persistence tests..."
@$(CC) -o $@ $^ $(TEST_INCLUDES) $(TEST_CFLAGS) -D_GNU_SOURCE
# Build save state system tests (with mock core callbacks, uses real temp files)
tests/player_state_test: tests/unit/all/player/test_player_state.c workspace/all/player/player_state.c workspace/all/player/player_paths.c workspace/all/common/utils.c $(PATHS_STUB) workspace/all/common/nointro_parser.c workspace/all/common/log.c tests/support/libretro_mocks.c $(TEST_UNITY)
@echo "Building save state system tests..."
@$(CC) -o $@ $^ $(TEST_INCLUDES) $(TEST_CFLAGS) -D_DEFAULT_SOURCE
# Build launcher command construction tests (pure string/file operations)
tests/launcher_launcher_test: tests/unit/all/launcher/test_launcher_launcher.c workspace/all/launcher/launcher_launcher.c $(TEST_UNITY)
@echo "Building launcher command tests..."
@$(CC) -o $@ $^ $(TEST_INCLUDES) $(TEST_CFLAGS)
# Build CPU scaling tests (pure algorithm, no external dependencies)
tests/cpu_test: tests/unit/all/common/test_cpu.c workspace/all/common/cpu.c $(TEST_UNITY)
@echo "Building CPU scaling tests..."
@$(CC) -o $@ $^ $(TEST_INCLUDES) $(TEST_CFLAGS)
# Build sync manager tests (vsync measurement and mode switching)
# Note: Uses test stub for getMicroseconds, not utils.c version
tests/sync_manager_test: tests/unit/all/player/test_sync_manager.c workspace/all/player/sync_manager.c workspace/all/common/log.c $(TEST_UNITY)
@echo "Building sync manager tests..."
@$(CC) -o $@ $^ $(TEST_INCLUDES) $(TEST_CFLAGS) -lm
# Build input handling tests (pure state queries and mapping lookups)
tests/player_input_test: tests/unit/all/player/test_player_input.c workspace/all/player/player_input.c $(TEST_UNITY)
@echo "Building input handling tests..."
@$(CC) -o $@ $^ $(TEST_INCLUDES) $(TEST_CFLAGS)
# Build launcher state persistence tests (path decomposition, resume paths)
tests/launcher_state_test: tests/unit/all/launcher/test_launcher_state.c workspace/all/launcher/launcher_state.c workspace/all/common/stb_ds_impl.c $(TEST_UNITY)
@echo "Building launcher state tests..."
@$(CC) -o $@ $^ $(TEST_INCLUDES) $(TEST_CFLAGS)
# Build menu context system tests (validates context pattern for testing)
# Uses SDL shim headers and fff-based mocking infrastructure
# Note: Uses menu_state_stub.c which provides testable menu functions without SDL dependencies
tests/player_menu_test: tests/unit/all/player/test_player_menu.c \
workspace/all/player/player_context.c \
tests/support/menu_state_stub.c \
tests/support/sdl_fakes.c \
workspace/all/common/utils.c $(PATHS_STUB) \
workspace/all/common/nointro_parser.c \
workspace/all/common/log.c \
$(TEST_UNITY)
@echo "Building menu context tests..."
@$(CC) -o $@ $^ -I tests/support/SDL $(TEST_INCLUDES) -I workspace/all/player/libretro-common/include -I tests/vendor/fff $(TEST_CFLAGS) -D_DEFAULT_SOURCE
# Build env callback tests (tests environment callback handlers in player_env.c)
tests/player_env_test: tests/unit/all/player/test_player_env.c \
workspace/all/player/player_env.c \
workspace/all/common/utils.c $(PATHS_STUB) \
workspace/all/common/nointro_parser.c \
workspace/all/common/log.c \
$(TEST_UNITY)
@echo "Building environment callback tests..."
@$(CC) -o $@ $^ $(TEST_INCLUDES) -I workspace/all/player/libretro-common/include $(TEST_CFLAGS) -D_DEFAULT_SOURCE
# Build game file handling tests (ZIP parsing, M3U detection, extension matching)
tests/player_game_test: tests/unit/all/player/test_player_game.c \
workspace/all/player/player_game.c \
workspace/all/common/utils.c $(PATHS_STUB) \
workspace/all/common/nointro_parser.c \
workspace/all/common/log.c \
$(TEST_UNITY)
@echo "Building game file handling tests..."
@$(CC) -o $@ $^ $(TEST_INCLUDES) $(TEST_CFLAGS) -DPLAYER_GAME_TEST
# Build video scaler calculation tests (pure geometry math)
tests/player_scaler_test: tests/unit/all/player/test_player_scaler.c \
workspace/all/player/player_scaler.c \
$(TEST_UNITY)
@echo "Building video scaler tests..."
@$(CC) -o $@ $^ $(TEST_INCLUDES) $(TEST_CFLAGS)
# Build core AV info processing tests (aspect ratio calculation, game info building)
tests/player_core_test: tests/unit/all/player/test_player_core.c \
workspace/all/player/player_core.c \
$(TEST_UNITY)
@echo "Building core AV info tests..."
@$(CC) -o $@ $^ $(TEST_INCLUDES) -I workspace/all/player/libretro-common/include $(TEST_CFLAGS)
# Build Launcher directory building tests (uses real temp directories, no --wrap needed)
tests/launcher_directory_test: tests/unit/all/launcher/test_launcher_directory.c \
workspace/all/launcher/launcher_directory.c \
workspace/all/launcher/launcher_file_utils.c \
workspace/all/launcher/launcher_entry.c \
workspace/all/launcher/launcher_str_compare.c \
workspace/all/common/stb_ds_impl.c \
workspace/all/common/utils.c $(PATHS_STUB) \
workspace/all/common/nointro_parser.c \
workspace/all/common/log.c \
$(TEST_UNITY)
@echo "Building Launcher directory tests..."
@$(CC) -o $@ $^ $(TEST_INCLUDES) $(TEST_CFLAGS) -D_DEFAULT_SOURCE
# Build Launcher navigation tests (pure navigation logic, uses real temp dirs)
tests/launcher_navigation_test: tests/unit/all/launcher/test_launcher_navigation.c \
workspace/all/launcher/launcher_navigation.c \
workspace/all/launcher/launcher_context.c \
workspace/all/launcher/launcher_launcher.c \
workspace/all/launcher/launcher_entry.c \
workspace/all/launcher/launcher_str_compare.c \
workspace/all/common/stb_ds_impl.c \
workspace/all/common/utils.c $(PATHS_STUB) \
workspace/all/common/nointro_parser.c \
workspace/all/common/log.c \
$(TEST_UNITY)
@echo "Building Launcher navigation tests..."
@$(CC) -o $@ $^ $(TEST_INCLUDES) $(TEST_CFLAGS) -D_DEFAULT_SOURCE
# Build Launcher thumbnail cache tests (pure cache logic and fade animation math)
tests/launcher_thumbnail_test: tests/unit/all/launcher/test_launcher_thumbnail.c \
workspace/all/launcher/launcher_thumbnail.c \
$(TEST_UNITY)
@echo "Building Launcher thumbnail cache tests..."
@$(CC) -o $@ $^ $(TEST_INCLUDES) $(TEST_CFLAGS)
# Build Launcher context tests (context lifecycle and accessor functions)
tests/launcher_context_test: tests/unit/all/launcher/test_launcher_context.c \
workspace/all/launcher/launcher_context.c \
$(TEST_UNITY)
@echo "Building Launcher context tests..."
@$(CC) -o $@ $^ $(TEST_INCLUDES) $(TEST_CFLAGS)
# Build emulator cache tests (O(1) emu lookup caching)
tests/emu_cache_test: tests/unit/all/launcher/test_emu_cache.c \
workspace/all/launcher/launcher_emu_cache.c \
workspace/all/common/stb_ds_impl.c \
workspace/all/common/utils.c $(PATHS_STUB) \
workspace/all/common/nointro_parser.c \
workspace/all/common/log.c \
$(TEST_UNITY)
@echo "Building emulator cache tests..."
@$(CC) -o $@ $^ $(TEST_INCLUDES) $(TEST_CFLAGS) -D_DEFAULT_SOURCE
# Build .res thumbnail cache tests (O(1) thumbnail lookup caching)
tests/res_cache_test: tests/unit/all/launcher/test_res_cache.c \
workspace/all/launcher/launcher_res_cache.c \
workspace/all/common/stb_ds_impl.c \
workspace/all/common/utils.c $(PATHS_STUB) \
workspace/all/common/nointro_parser.c \
workspace/all/common/log.c \
$(TEST_UNITY)
@echo "Building .res thumbnail cache tests..."
@$(CC) -o $@ $^ $(TEST_INCLUDES) $(TEST_CFLAGS) -D_DEFAULT_SOURCE
# Build render common tests (pure math functions, no dependencies)
tests/render_common_test: tests/unit/all/common/test_render_common.c \
workspace/all/common/render_common.c \
$(TEST_UNITY)
@echo "Building render common tests..."
@$(CC) -o $@ $^ $(TEST_INCLUDES) $(TEST_CFLAGS)
# Build integration tests (tests multiple components working together with real file I/O)
tests/integration_workflows_test: tests/integration/test_workflows.c \
tests/integration/integration_support.c \
workspace/all/launcher/launcher_m3u.c \
workspace/all/launcher/launcher_map.c \
workspace/all/launcher/collection_parser.c \
workspace/all/launcher/recent_file.c \
workspace/all/launcher/launcher_file_utils.c \
workspace/all/common/binary_file_utils.c \
workspace/all/common/stb_ds_impl.c \
workspace/all/player/player_paths.c \
workspace/all/common/utils.c $(PATHS_STUB) \
workspace/all/common/nointro_parser.c \
workspace/all/common/log.c \
$(TEST_UNITY)
@echo "Building integration tests..."
@$(CC) -o $@ $^ $(TEST_INCLUDES) -I tests/integration $(TEST_CFLAGS) -D_POSIX_C_SOURCE=200809L -D_DEFAULT_SOURCE
clean-tests:
rm -f tests/log_test $(TEST_EXECUTABLES) tests/*.o tests/**/*.o tests/integration/*.o
###########################################################
# Code Formatting
CLANG_FORMAT = clang-format
# Note: vendor/ is excluded - it contains third-party code
FORMAT_PATHS = workspace/all/launcher/*.c \
workspace/all/launcher/*.h \
workspace/all/player/*.c \
workspace/all/player/*.h \
workspace/all/common/*.c \
workspace/all/common/*.h \
workspace/all/paks/Tools/*/src/*.c \
workspace/all/syncsettings/*.c \
workspace/*/platform/platform.c \
workspace/*/platform/platform.h \
workspace/desktop/platform/msettings.h
check-clang-format:
@which $(CLANG_FORMAT) > /dev/null || (echo "Error: clang-format not installed" && exit 1)
# Format code in-place (MODIFIES FILES) - Native implementation
# Sub-targets can run in parallel with: make -j4 -f Makefile.qa format-native
format-native: check-clang-format check-shfmt check-mbake check-prettier format-c format-sh format-makefiles format-json
@echo ""
@echo "NOTE: Files were modified. Review changes with 'git diff'"
.PHONY: format-c format-sh format-makefiles format-json
format-c: check-clang-format
@echo "Formatting C code with clang-format..."
@$(CLANG_FORMAT) -i $(FORMAT_PATHS)
@echo "✓ C code formatted"
format-sh: check-shfmt
@echo "Formatting shell scripts with shfmt..."
@find skeleton workspace -name "*.sh" -type f \
-not -path "*/cores/*" \
-not -path "*/toolchains/*" \
-not -path "*/other/*" \
-not -path "*/desktop/FAKESD/*" \
-not -path "*/em_ui.sh" \
-exec shfmt -w -i 0 -bn -ci {} \;
@shfmt -w -i 0 -bn -ci $(ROOT_SCRIPTS)
@echo "✓ Shell scripts formatted"
format-makefiles: check-mbake
@echo "Formatting makefiles with mbake..."
@find . \( -name "Makefile" -o -name "Makefile.*" \) \
-not -path "*/other/*" \
-not -path "*/toolchains/*" \
-not -path "*/cores/*" \
-not -path "*/libretro-common/*" \
-not -path "*/parson/*" \
-exec sh -c 'mbake format "$$1" 2>/dev/null || true' _ {} \;
@echo "✓ Makefiles formatted"
format-json: check-prettier
@echo "Formatting JSON, YAML, and Markdown with prettier..."
@prettier --write $(PRETTIER_IGNORE) $(PRETTIER_PATTERNS) 2>/dev/null || true
@echo "✓ Prettier formatting complete"
# Check if code is formatted (no modifications)
format-check: check-clang-format
@echo "Checking code formatting..."
@$(CLANG_FORMAT) --dry-run -Werror $(FORMAT_PATHS) 2>&1 || \
(echo "✗ Code is not formatted. Run 'make -f Makefile.qa format' to fix." && exit 1)
@echo "✓ Code is properly formatted"
###########################################################
# Shell Script Formatting (shfmt)
check-shfmt:
@which shfmt > /dev/null || (echo "Error: shfmt not installed" && exit 1)
# Format shell scripts in-place
# -i 0 = use tabs, -bn = binary ops start of line, -ci = indent switch cases
format-shell: check-shfmt
@echo "Formatting shell scripts with shfmt..."
@find skeleton workspace -name "*.sh" -type f \
-not -path "*/cores/*" \
-not -path "*/toolchains/*" \
-not -path "*/other/*" \
-not -path "*/desktop/FAKESD/*" \
-not -path "*/em_ui.sh" \
-exec shfmt -w -i 0 -bn -ci {} \;
@shfmt -w -i 0 -bn -ci $(ROOT_SCRIPTS)
@for script in $(BIN_SCRIPTS); do \
[ -f "$$script" ] && shfmt -w -i 0 -bn -ci "$$script"; \
done
@echo "✓ Shell scripts formatted"
# Check shell script formatting (no modifications)
format-shell-check: check-shfmt
@echo "Checking shell script formatting..."
@find skeleton workspace -name "*.sh" -type f \
-not -path "*/cores/*" \
-not -path "*/toolchains/*" \
-not -path "*/other/*" \
-not -path "*/desktop/FAKESD/*" \
-not -path "*/em_ui.sh" \
-exec shfmt -d -i 0 -bn -ci {} \; || \
(echo "✗ Shell scripts not formatted. Run 'make -f Makefile.qa format-shell' to fix." && exit 1)
@shfmt -d -i 0 -bn -ci $(ROOT_SCRIPTS) || \
(echo "✗ Shell scripts not formatted. Run 'make -f Makefile.qa format-shell' to fix." && exit 1)
@for script in $(BIN_SCRIPTS); do \
if [ -f "$$script" ]; then \
shfmt -d -i 0 -bn -ci "$$script" || \
(echo "✗ Shell scripts not formatted. Run 'make -f Makefile.qa format-shell' to fix." && exit 1); \
fi; \
done
@echo "✓ Shell scripts properly formatted"
###########################################################
# Makefile Formatting (mbake)
# Find all makefiles we maintain (exclude third-party)
# Note: Don't use MAKEFILES as variable name - it's special in GNU make
MAKEFILE_PATHS = $(shell find . \( -name "Makefile" -o -name "Makefile.*" \) 2>/dev/null | \
grep -v "./workspace/.*/other/" | \
grep -v "./workspace/.*/toolchains/" | \
grep -v "./workspace/.*/cores/" | \
grep -v "./workspace/all/player/libretro-common/" | \
grep -v "./workspace/all/utils/parson/")
check-mbake:
@which mbake > /dev/null || (echo "Error: mbake not installed" && exit 1)
# Format makefiles in-place
format-make: check-mbake
@echo "Formatting makefiles with mbake..."
@find . \( -name "Makefile" -o -name "Makefile.*" \) \
-not -path "*/other/*" \
-not -path "*/toolchains/*" \
-not -path "*/cores/*" \
-not -path "*/libretro-common/*" \
-not -path "*/parson/*" \
-exec sh -c 'mbake format "$$1" 2>/dev/null || true' _ {} \;
@echo "✓ Makefiles formatted"
# Check makefile formatting (no modifications)
format-make-check: check-mbake
@echo "Checking makefile formatting..."
@FAILED=0; \
find . \( -name "Makefile" -o -name "Makefile.*" \) \
-not -path "*/other/*" \
-not -path "*/toolchains/*" \
-not -path "*/cores/*" \
-not -path "*/libretro-common/*" \
-not -path "*/parson/*" \
-exec sh -c 'mbake format --check "$$1" 2>/dev/null || exit 1' _ {} \; || FAILED=1; \
if [ $$FAILED -eq 1 ]; then \
echo "✗ Makefiles not formatted. Run 'make -f Makefile.qa format-make' to fix."; \
exit 1; \
fi
@echo "✓ Makefiles properly formatted"
###########################################################
# Prettier Formatting (JSON, YAML, Markdown)
check-prettier:
@which prettier > /dev/null || (echo "Error: prettier not installed" && exit 1)
# Files to format with prettier (exclude third-party, build outputs, and templates)
PRETTIER_PATTERNS = "workspace/all/paks/**/*.json" ".github/**/*.yml" "*.md" "docs/**/*.md" \
"!workspace/all/paks/Tools/WiFi/res/**"
PRETTIER_IGNORE = --ignore-path .gitignore
# Format JSON, YAML, and Markdown in-place
format-prettier: check-prettier
@echo "Formatting JSON, YAML, and Markdown with prettier..."
@prettier --write $(PRETTIER_IGNORE) $(PRETTIER_PATTERNS) 2>/dev/null || true
@echo "✓ Prettier formatting complete"
# Check prettier formatting (no modifications)
format-prettier-check: check-prettier
@echo "Checking JSON, YAML, and Markdown formatting..."
@prettier --check $(PRETTIER_IGNORE) $(PRETTIER_PATTERNS) || \
(echo "✗ Files not formatted. Run 'make -f Makefile.qa format-prettier' to fix." && exit 1)
@echo "✓ JSON, YAML, and Markdown properly formatted"
###########################################################
# Shell Script Linting
# Find all shell scripts we maintain (exclude third-party code)
# Excludes:
# - workspace/*/other/* (third-party SDL, DTC, etc.)
# - workspace/desktop/FAKESD/* (test data)
# - cores, toolchains (third-party)
SHELL_SCRIPTS = $(shell find skeleton workspace -name "*.sh" -type f \
-not -path "*/cores/*" \
-not -path "*/toolchains/*" \
-not -path "*/other/*" \
-not -path "*/desktop/FAKESD/*" \
-not -path "*/em_ui.sh" \
2>/dev/null)
# Shell scripts in bin/ directories (no .sh extension by convention)
BIN_SCRIPTS = $(shell find workspace/all/paks -path "*/bin/*" -type f \
-exec sh -c 'head -1 "{}" | grep -q "^\#!/bin/sh" && echo "{}"' \; \
2>/dev/null)
ROOT_SCRIPTS = commits.sh
lint-shell: check-shellcheck
@echo "Running shellcheck on shell scripts..."
@echo "Checking $(shell echo $(SHELL_SCRIPTS) $(BIN_SCRIPTS) $(ROOT_SCRIPTS) | wc -w | tr -d ' ') scripts (forgiving rules)"
@echo ""
@ERROR_COUNT=0; \
for script in $(ROOT_SCRIPTS) $(SHELL_SCRIPTS) $(BIN_SCRIPTS); do \
if [ -f "$$script" ]; then \
shellcheck -S warning "$$script" || ERROR_COUNT=$$((ERROR_COUNT + 1)); \
fi; \
done; \
if [ $$ERROR_COUNT -gt 0 ]; then \
echo ""; \
echo "Found issues in $$ERROR_COUNT script(s)"; \
echo "Note: Using forgiving rules (see .shellcheckrc)"; \
exit 1; \
else \
echo "✓ All shell scripts passed"; \
fi
###########################################################
# Reporting and Cleanup
clean-qa:
@echo "Cleaning QA artifacts..."
rm -f clang-tidy-report.txt
# Generate static analysis report file
report: check-clang-tidy
@echo "Generating static analysis report..."
@for file in $(TIDY_SOURCES); do \
if [ -f "$$file" ]; then \
$(CLANG_TIDY) "$$file" -- $(TIDY_COMPILE_FLAGS) 2>&1 || true; \
fi; \
done > clang-tidy-report.txt
@echo "Report saved to clang-tidy-report.txt"
@wc -l clang-tidy-report.txt
###########################################################
# Docker QA (recommended for consistency with CI)
DEV_IMAGE = lessui-dev
GHCR_IMAGE = ghcr.io/lessui-hq/lessui-dev:latest
DEV_RUN = docker run --rm -v $(shell pwd):/lessui -w /lessui $(DEV_IMAGE)
# Set FORCE_DOCKER_BUILD=1 to build locally instead of pulling from GHCR
FORCE_DOCKER_BUILD ?= 0
# Pull prebuilt image from GHCR and tag it locally
docker-pull:
@echo "Pulling dev image from GHCR..."
@docker pull $(GHCR_IMAGE)
@docker tag $(GHCR_IMAGE) $(DEV_IMAGE)
@echo "✓ Dev image ready (from GHCR)"
# Default: pull from GHCR. Use FORCE_DOCKER_BUILD=1 to build locally.
docker-build:
ifeq ($(FORCE_DOCKER_BUILD),1)
@echo "Building dev Docker image locally..."
@docker build -q -t $(DEV_IMAGE) -f Dockerfile .
@echo "✓ Dev image ready (built locally)"
else
@$(MAKE) -f Makefile.qa docker-pull
endif
docker-test: docker-build
@echo "Running tests in Docker container (Ubuntu 24.04)..."
@echo ""
$(DEV_RUN) ./scripts/run-tests.sh
# Run tests with verbose output (shows all test output)
docker-test-verbose: docker-build
$(DEV_RUN) ./scripts/run-tests.sh -v
# Run tests with quiet output (summary only)
docker-test-quiet: docker-build
$(DEV_RUN) ./scripts/run-tests.sh -q
docker-lint: docker-build
@echo "Running linting in Docker container (Ubuntu 24.04)..."
$(DEV_RUN) make -j4 -f Makefile.qa lint-native
docker-analyze: docker-build
@echo "Running static analysis in Docker container (Ubuntu 24.04)..."
$(DEV_RUN) make -f Makefile.qa analyze-native
docker-format: docker-build
@echo "Running formatter in Docker container (Ubuntu 24.04)..."
$(DEV_RUN) make -j4 -f Makefile.qa format-native
docker-format-check: docker-build
@echo "Checking formatting in Docker container (Ubuntu 24.04)..."
$(DEV_RUN) make -f Makefile.qa format-check
docker-shell: docker-build
@echo "Entering dev container shell (Ubuntu 24.04)..."
docker run --rm -it -v $(shell pwd):/lessui -w /lessui $(DEV_IMAGE) /bin/bash
# Native targets (runs directly on host)
# Sub-targets can run in parallel with: make -j4 -f Makefile.qa lint-native
lint-native: lint-unsafe-functions lint-log-newlines lint-code format-check format-shell-check format-make-check format-prettier-check lint-shell
@echo ""
@echo "================================="
@echo "✓ All linting checks passed"
@echo "================================="
###########################################################
# Code Coverage Analysis
# Uses gcov/lcov to measure test coverage
# Runs in Docker by default for consistency
# Coverage output directory
COVERAGE_DIR = coverage
# Default: use Docker for coverage
coverage: docker-coverage
# Docker coverage target
docker-coverage: docker-build
@echo "Running coverage analysis in Docker container..."
@echo ""
$(DEV_RUN) ./scripts/run-coverage.sh
# Clean coverage artifacts
clean-coverage:
@echo "Cleaning coverage data..."
rm -rf $(COVERAGE_DIR)
find . -name "*.gcno" -delete 2>/dev/null || true
find . -name "*.gcda" -delete 2>/dev/null || true
find . -name "*.gcov" -delete 2>/dev/null || true
@echo "✓ Coverage data cleaned"
###########################################################
# Sanitizer Testing (AddressSanitizer, UndefinedBehaviorSanitizer)
#
# ASan catches: buffer overflows, use-after-free, memory leaks
# UBSan catches: integer overflow, null pointer deref, misaligned access
#
# These run in Docker to ensure GCC sanitizer support
###########################################################
# Sanitizer flags for GCC
TEST_CFLAGS_ASAN = -std=c99 -Wall -Wextra -Wno-unused-parameter \
-fsanitize=address,undefined \
-fno-omit-frame-pointer -g \
-DASAN_BUILD
# Environment variables for ASan runtime
ASAN_OPTIONS_ENV = ASAN_OPTIONS=detect_leaks=1:halt_on_error=0:print_stats=1
# Default: use Docker for sanitizer testing
test-asan: docker-test-asan
# Docker ASan target
docker-test-asan: docker-build
@echo "============================================="
@echo "Running tests with AddressSanitizer + UBSan"
@echo "============================================="
@echo ""
@echo "ASan detects: buffer overflow, use-after-free, memory leaks"
@echo "UBSan detects: integer overflow, null deref, misaligned access"
@echo ""
$(DEV_RUN) ./scripts/run-tests.sh --asan
# Native ASan target (for Linux hosts with GCC)
test-asan-native:
@echo "Building and running tests with sanitizers..."
@echo ""
$(MAKE) -f Makefile.qa clean-tests
$(MAKE) -f Makefile.qa test-native TEST_CFLAGS="$(TEST_CFLAGS_ASAN)"