Files
abcmidi/tests/run_test.cmake
Ronan Keryell 1d8d1f621c Unify package version on VERSION file; bump abc2midi to 5.03 (#25)
- abc2midi 5.03 (April 2026): bumped #define VERSION in store.c to reflect this branch's accumulated fixes
(error exit status, missing C:/R:/X: header emission, %%MIDIdef macros allowed anywhere) plus the
build-system additions.
- Single source of truth for the package release date: the VERSION file is now consumed by both build
paths.
  - CMakeLists.txt reads it via file(STRINGS VERSION ABCMIDI_VERSION ...); the previously hard-coded
project(VERSION 2026.02.24) (an unused PROJECT_VERSION) is dropped because the date format isn't
MAJOR.MINOR.PATCH.
  - configure.ac uses m4_esyscmd_s([cat VERSION]) in AC_INIT, replacing the very stale hard-coded
2011-08-03.
  - The two build paths remain independent — no cross-generation between CMake and autoconf.
- Test normalization fix (tests/run_test.cmake): the version-banner regex now requires a capitalized month
and accepts a date with or without a day number. The previous regex was lowercase-permissive and silently
ate PostScript lines like 0.5 setlinewidth 0 0 moveto in yaps output — tests/golden/yaps_coleraine.txt is
regenerated to reflect the now-correctly-preserved PostScript.
- Docs:
  - doc/CHANGES: April 25 2026 entry covering the version bump and VERSION-file unification.
  - doc/readme.txt: per-tool listing updated (abc2midi 5.03 April 2026, midistats 1.03 February 20 2026
synced to source).
  - doc/abc2midi.1: header label bumped to 5.03 April 2026 (content not audited).
  - README.md: new Maintainers / releasing section documenting the release procedure, including the
requirement to run autoreconf -f so the committed configure picks up the new AC_INIT arguments.
2026-04-27 21:13:26 -04:00

182 lines
6.3 KiB
CMake

# Generic test runner for abcmidi golden-file tests.
#
# Required variables (passed via -D on the cmake command line):
# TYPE - one of: abc2midi, abc2abc, midi2abc, midistats, mftext,
# yaps, midicopy, abcmatch
# SAMPLE - path to the input ABC sample file
# GOLDEN - path to the golden reference file
# TMPDIR - working directory for temporary outputs
#
# ABC2MIDI, ABC2ABC, MIDI2ABC, MIDISTATS, MFTEXT, YAPS, MIDICOPY, ABCMATCH
# - absolute paths to the binaries (only those needed for TYPE)
#
# Behaviour:
# - Runs the requested binary (and any pipeline steps required to make the
# output diffable, e.g. mftext for binary MIDI output).
# - Strips non-deterministic lines (version banners, dates) from the output.
# - If the environment variable ABCMIDI_UPDATE_GOLDEN is set, the filtered
# output is written to GOLDEN (used to regenerate references after an
# intentional behavioural change).
# - Otherwise, the filtered output is compared with GOLDEN; on mismatch, a
# unified diff is printed and the test fails.
cmake_minimum_required(VERSION 3.14)
if(NOT TYPE OR NOT SAMPLE OR NOT GOLDEN OR NOT TMPDIR)
message(FATAL_ERROR "run_test.cmake: TYPE, SAMPLE, GOLDEN, TMPDIR are required")
endif()
file(MAKE_DIRECTORY "${TMPDIR}")
get_filename_component(stem "${SAMPLE}" NAME_WE)
set(raw "${TMPDIR}/${TYPE}_${stem}.raw")
set(out "${TMPDIR}/${TYPE}_${stem}.out")
set(midfile "${TMPDIR}/${TYPE}_${stem}.mid")
# --- Command helpers ---------------------------------------------------------
# Run a command; abort with a detailed message on non-zero exit.
function(run_or_die)
execute_process(
COMMAND ${ARGN}
RESULT_VARIABLE rc
OUTPUT_VARIABLE captured_out
ERROR_VARIABLE captured_err
)
if(NOT rc EQUAL 0)
message(FATAL_ERROR
"Command failed (rc=${rc}):\n ${ARGN}\n"
"--- stdout ---\n${captured_out}\n"
"--- stderr ---\n${captured_err}")
endif()
endfunction()
# Run a command and capture its stdout into a file.
function(run_to_file outfile)
execute_process(
COMMAND ${ARGN}
OUTPUT_FILE "${outfile}"
RESULT_VARIABLE rc
ERROR_VARIABLE captured_err
)
if(NOT rc EQUAL 0)
message(FATAL_ERROR
"Command failed (rc=${rc}):\n ${ARGN}\n--- stderr ---\n${captured_err}")
endif()
endfunction()
# Convert ${SAMPLE} to MIDI, then run ${bin} on the resulting MIDI file and
# capture its stdout. Any extra arguments are inserted BEFORE the MIDI path
# (needed e.g. for "midi2abc -f <file>").
function(run_via_mid outfile bin)
run_or_die("${ABC2MIDI}" "${SAMPLE}" -o "${midfile}" -quiet -silent)
run_to_file("${outfile}" "${bin}" ${ARGN} "${midfile}")
endfunction()
# Run ${bin} directly on ${SAMPLE} and capture its stdout. Extra arguments
# are appended after the sample path.
function(run_on_sample outfile bin)
run_to_file("${outfile}" "${bin}" "${SAMPLE}" ${ARGN})
endfunction()
# --- Dispatch: populate ${raw} based on TYPE ---------------------------------
#
# The binary path is derived from TYPE via TOUPPER + double-dereference:
# for TYPE=midistats, ${bin} resolves to ${MIDISTATS}. The only exception is
# abc2midi, where we render the MIDI output through mftext for diffing — it
# is therefore an alias for the mftext test.
string(TOUPPER "${TYPE}" TYPE_UPPER)
if(TYPE STREQUAL "abc2midi")
set(bin "${MFTEXT}")
else()
set(bin "${${TYPE_UPPER}}")
endif()
if(TYPE MATCHES "^(abc2midi|mftext|midistats)$")
run_via_mid("${raw}" "${bin}")
elseif(TYPE STREQUAL "midi2abc")
run_via_mid("${raw}" "${bin}" -f)
elseif(TYPE STREQUAL "abc2abc")
run_on_sample("${raw}" "${bin}")
elseif(TYPE STREQUAL "abcmatch")
# -pitch_hist gives a short, deterministic, useful summary of the tune.
run_on_sample("${raw}" "${bin}" -pitch_hist)
elseif(TYPE STREQUAL "yaps")
set(psfile "${TMPDIR}/yaps_${stem}.ps")
run_or_die("${bin}" "${SAMPLE}" -o "${psfile}")
configure_file("${psfile}" "${raw}" COPYONLY)
elseif(TYPE STREQUAL "midicopy")
# ABC -> MIDI -> midicopy -> mftext
set(copied "${TMPDIR}/midicopy_${stem}_out.mid")
run_or_die("${ABC2MIDI}" "${SAMPLE}" -o "${midfile}" -quiet -silent)
run_or_die("${bin}" "${midfile}" "${copied}")
run_to_file("${raw}" "${MFTEXT}" "${copied}")
else()
message(FATAL_ERROR "Unknown TYPE: ${TYPE}")
endif()
# --- Normalize: strip non-deterministic lines --------------------------------
#
# Each program prints a version line as the first line of stdout. yaps
# PostScript output contains a CreationDate. Filenames embedded in output
# (e.g. yaps "%%Title: /tmp/...") are also volatile and stripped.
file(READ "${raw}" content)
# Strip program version banners. Two date forms are emitted across the
# binaries: "5.02 February 16 2025 abc2midi" (with day) and "5.03 April
# 2026 abc2midi" (month + year only) — the day number is optional.
# The month token must be capitalized to avoid matching PostScript lines
# like "0.8 setlinewidth 0 setlinecap" in yaps output.
string(REGEX REPLACE "[0-9]+\\.[0-9]+ +[A-Z][a-z]+ +([0-9]+ +)?[0-9]+ +[a-z2]+\n" "" content "${content}")
# Strip yaps PostScript volatile headers
string(REGEX REPLACE "%%Title:[^\n]*\n" "%%Title: <stripped>\n" content "${content}")
string(REGEX REPLACE "%%CreationDate:[^\n]*\n" "%%CreationDate: <stripped>\n" content "${content}")
# Strip absolute paths to TMPDIR (filenames embedded in some outputs)
string(REPLACE "${TMPDIR}/" "" content "${content}")
file(WRITE "${out}" "${content}")
# --- Update or compare -------------------------------------------------------
if(DEFINED ENV{ABCMIDI_UPDATE_GOLDEN})
get_filename_component(golden_dir "${GOLDEN}" DIRECTORY)
file(MAKE_DIRECTORY "${golden_dir}")
file(WRITE "${GOLDEN}" "${content}")
message(STATUS "Updated golden: ${GOLDEN}")
return()
endif()
if(NOT EXISTS "${GOLDEN}")
message(FATAL_ERROR
"Golden file does not exist: ${GOLDEN}\n"
"Run 'ABCMIDI_UPDATE_GOLDEN=1 ctest ...' to generate it.")
endif()
execute_process(
COMMAND "${CMAKE_COMMAND}" -E compare_files "${out}" "${GOLDEN}"
RESULT_VARIABLE diff_rc
OUTPUT_QUIET
ERROR_QUIET
)
if(NOT diff_rc EQUAL 0)
execute_process(
COMMAND diff -u "${GOLDEN}" "${out}"
OUTPUT_VARIABLE diff_text
)
message(FATAL_ERROR
"Output differs from golden ${GOLDEN}\n"
"To accept the new output: ABCMIDI_UPDATE_GOLDEN=1 ctest ...\n"
"${diff_text}")
endif()