Implement basic testing infrastructure (#19)

* Add configuration for CMake build system alongside autoconf

- Add a modern CMake build system (`CMakeLists.txt`, `CMakePresets.json`) that coexists with the legacy
autoconf/Makefile build
- Shared source files (`midifile.c`, `parseabc.c`, `music_utils.c`, `parser2.c`) are compiled once via OBJECT
libraries and linked into the 8 binaries
- Three presets: `default` (Release), `debug`, `sanitize` (ASan + UBSan)
- Generates `compile_commands.json` for clangd/LSP editor support
- Install rules match the legacy Makefile (binaries, doc files, man pages)
- Pinned to `-std=gnu89` because the codebase mixes K&R `()` and ANSI typed prototypes — in C23/gnu23 (GCC 15+
default), `()` means `(void)`, making these a hard error. Note: **the existing autoconf build is also broken with
GCC 15** for the same reason

```sh
cmake --preset debug
cmake --build --preset debug
cmake --install build/debug --prefix /usr/local

Documentation

- README.md: added Building section with both autoconf and CMake instructions
- doc/readme.txt: added build instructions in the existing preamble
- doc/CHANGES: added changelog entry

Test plan

- All 3 presets configure and build with GCC 15
- Smoke test: abc2midi samples/coleraine.abc produces valid MIDI through mftext
- Sanitizer build (--preset sanitize) runs clean on sample files
- Install layout verified: 8 binaries, 10 doc files, 8 man pages in correct paths
- Build on macOS (untested, should work with AppleClang)

* Implement basic testing infrastructure

The CMake build includes a test suite covering all 8 programs:

- **Smoke tests** verify each binary runs cleanly with `-ver`.
- **Golden-file tests** run each program on a sample input and compare the
  (normalized) output to a checked-in reference. Binary MIDI outputs are
  piped through `mftext` to produce diffable text. Volatile lines (version
  banners, dates, temporary paths) are stripped before comparison.

```sh
ctest --preset debug

ctest --preset debug -L golden
ctest --preset debug -L smoke
```

To regenerate the golden files after an intentional behavioural change,
review the diff, then commit:

```sh
cmake --build build/debug --target update-golden
git diff tests/golden/
```

* Factorize more the test CMake code
This commit is contained in:
Ronan Keryell
2026-04-22 04:42:29 -07:00
committed by GitHub
parent c7f4015945
commit 1d766a85d2
18 changed files with 8421 additions and 0 deletions

149
CMakeLists.txt Normal file
View File

@@ -0,0 +1,149 @@
cmake_minimum_required(VERSION 3.14)
# Read version from the VERSION file, matching the existing convention
file(STRINGS VERSION ABCMIDI_VERSION_STRING LIMIT_COUNT 1)
project(abcmidi
VERSION 2026.02.24
DESCRIPTION "ABC music notation tools: converters between ABC, MIDI, and PostScript"
LANGUAGES C
)
# Modern CMake: export compile_commands.json for clangd/LSP
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
# Default to Release if not specified
if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
set(CMAKE_BUILD_TYPE Release CACHE STRING "Build type" FORCE)
set_property(CACHE CMAKE_BUILD_TYPE
PROPERTY STRINGS Debug Release RelWithDebInfo MinSizeRel)
endif()
# --- Options ---
option(ABCMIDI_SANITIZERS "Enable AddressSanitizer and UndefinedBehaviorSanitizer" OFF)
# --- Compiler configuration ---
# Common compile options applied to every target via an INTERFACE library
add_library(abcmidi_common INTERFACE)
target_compile_definitions(abcmidi_common INTERFACE
ANSILIBS
)
# The codebase mixes K&R empty-parens "()" (meaning unspecified args) with
# ANSI typed prototypes. In C23/gnu23 (GCC 15+ default), "()" means "(void)",
# making these a hard error. Pin to gnu89 until the ANSI migration is complete,
# then switch to c_std_11 or later.
target_compile_options(abcmidi_common INTERFACE
$<$<C_COMPILER_ID:GNU,Clang,AppleClang>:
-std=gnu89
-Wall
>
)
if(ABCMIDI_SANITIZERS)
target_compile_options(abcmidi_common INTERFACE
-fsanitize=address,undefined -fno-omit-frame-pointer)
target_link_options(abcmidi_common INTERFACE
-fsanitize=address,undefined)
endif()
# --- Shared object libraries (compiled once, linked into multiple binaries) ---
add_library(obj_midifile OBJECT midifile.c)
target_link_libraries(obj_midifile PUBLIC abcmidi_common)
add_library(obj_parseabc OBJECT parseabc.c music_utils.c)
target_link_libraries(obj_parseabc PUBLIC abcmidi_common)
add_library(obj_parser2 OBJECT parser2.c)
target_link_libraries(obj_parser2 PUBLIC abcmidi_common)
# --- Executables ---
# abc2midi: ABC notation → MIDI
add_executable(abc2midi
store.c genmidi.c queues.c stresspat.c
)
target_link_libraries(abc2midi PRIVATE
obj_parseabc obj_parser2 obj_midifile abcmidi_common m)
# abc2abc: ABC notation → ABC notation (transposition, reformatting)
add_executable(abc2abc toabc.c)
target_link_libraries(abc2abc PRIVATE obj_parseabc abcmidi_common m)
# midi2abc: MIDI → ABC notation
add_executable(midi2abc midi2abc.c)
target_link_libraries(midi2abc PRIVATE obj_midifile abcmidi_common m)
# midistats: MIDI file statistics
add_executable(midistats midistats.c)
target_link_libraries(midistats PRIVATE obj_midifile abcmidi_common m)
# mftext: MIDI → human-readable text
add_executable(mftext mftext.c crack.c)
target_link_libraries(mftext PRIVATE obj_midifile abcmidi_common m)
# yaps: ABC notation → PostScript
add_executable(yaps
yapstree.c drawtune.c debug.c pslib.c position.c
)
target_link_libraries(yaps PRIVATE
obj_parseabc obj_parser2 abcmidi_common m)
# midicopy: MIDI file filtering/extraction
add_executable(midicopy midicopy.c)
target_link_libraries(midicopy PRIVATE abcmidi_common m)
# abcmatch: ABC tune matching/comparison
add_executable(abcmatch abcmatch.c matchsup.c)
target_link_libraries(abcmatch PRIVATE obj_parseabc abcmidi_common m)
# --- Install ---
include(GNUInstallDirs)
set(ABCMIDI_BINARIES abc2midi abc2abc midi2abc midistats mftext yaps midicopy abcmatch)
install(TARGETS ${ABCMIDI_BINARIES}
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
)
# Match the legacy Makefile: install only top-level doc files, not subdirectories
install(FILES
doc/abcguide.txt
doc/abcmatch.txt
doc/gpl.txt
doc/history.txt
doc/hudsonshift.txt
doc/readme.txt
doc/yapshelp.txt
doc/AUTHORS
doc/CHANGES
VERSION
DESTINATION ${CMAKE_INSTALL_DOCDIR}
)
install(FILES
doc/abc2abc.1
doc/abc2midi.1
doc/abcmatch.1
doc/mftext.1
doc/midi2abc.1
doc/midicopy.1
doc/midistats.1
doc/yaps.1
DESTINATION ${CMAKE_INSTALL_MANDIR}/man1
)
# --- Tests ---
include(CTest)
if(BUILD_TESTING)
add_subdirectory(tests)
endif()
# TODO: install-validation test that verifies all binaries, doc files, and man
# pages are installed to the expected paths.