Three unrelated issues made the musrview_check tests fail on macOS while
passing on Linux.
1. Unspecified argument evaluation order
TColor::GetColor(rand.Integer(255), rand.Integer(255), rand.Integer(255))
evaluates its (side-effecting) arguments in an unspecified order: clang
goes left-to-right, gcc right-to-left, so the same seed yielded RGB on
one platform and BGR on the other. Visible whenever more runs are
plotted than the startup xml colour list provides, e.g.
test-histo-HAL9500.msr with 16 runs against 10 colours.
The rgb values are now drawn into separate variables first. This keeps
the clang result and changes gcc to match, hence the regenerated
reference PNG for musrview-histo-HAL9500 (99.94% of the differing
pixels were exact R<->B swaps).
Fixed in PMusrCanvas, PFourierCanvas and both mupp PMuppCanvas copies.
2. Fixed size path buffers
char fileName[128] plus strncpy(dst, src, sizeof(dst)) truncates *and*
leaves the buffer unterminated once the path reaches the buffer size.
musrview then failed on a 132 character msr-path in doc/examples/ViewOpts.
musrFT and musrt0 were worse: an unbounded strcpy of the startup file
path into char startup_path_name[128], i.e. a stack buffer overflow.
All path/filename buffers in the drivers are now std::string:
musrview, musrFT, musrt0, musrfit, any2many, addRun, dump_header.
PMusrCanvas::SaveGraphicsAndQuit() takes const Char_t* accordingly.
Along the way in the same files:
- msr2msr_replace() wrote a 256 byte line into char temp[128]
- msr2msr assembled "cp"/"rm" shell commands from paths in a 256 byte
buffer; replaced by std::filesystem
- msr2msr and addRun read lines with getline(buf, N), which silently
abandons the rest of the file on the first over-long line
- addRun: bound the unbounded sscanf "%s" to "%255s"
- dropped scratch buffers that only held a string literal, in favour of
TString::ReplaceAll(const char*, const char*)
3. musrview_check.py left its PNGs behind on failure
The two early error returns skipped the cleanup, and since generated
PNGs were identified by "not in the pre-run snapshot", one leftover file
permanently masked the real output of that test: musrview overwrites the
stale PNG, so it was there, just filtered out. One failure thus poisoned
every later run (30 of the 37 observed failures) after a 15 s poll each.
Cleanup now runs in a finally block on every exit path, and generated
PNGs are detected by mtime instead, which sees rewritten leftovers while
staying safe for a sibling test running concurrently under ctest -j.
Also dropped the MUSRVIEW_PNG_DIR env var and its tmp-dir fallback: it
is read nowhere in src/, so the fallback was dead code that guaranteed
the full 15 s poll before every "no PNGs found" failure.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
If Pillow or numpy aren't importable, fail fast with a clear
**ERROR** message and pip-install hint instead of a bare traceback
from inside pixel_diff().
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Three independent issues made all 72 musrview-* ctest targets fail on a
RHEL9 box while passing elsewhere:
- Pixel-diff tolerance (1%) was tighter than needed; bump to 3%. Real
diffs against the committed reference PNGs measure ~0.08%.
- find_package(Python3) picks the highest-versioned interpreter on the
system, which on this machine is a bare /usr/bin/python3.14 without
Pillow/numpy installed, while the distro's python3 (3.9) has both.
Probe the interpreter CMake found and fall back to PATH's "python3"
if it lacks the required modules.
- musrview reliably writes and closes the PNG before exiting, but under
ctest (not reproducible via a plain shell loop) the new directory
entry could take several seconds to become visible to the checking
script's glob(). Poll for up to ~15s instead of failing on the first
empty glob.
Fixes found while reviewing the cmake setup, with a focus on macOS. All
changes are either platform neutral or guarded by if (APPLE).
Portability / correctness:
* CMP0167 was guarded by 'CMAKE_VERSION GREATER_EQUAL 3.3', a *numeric*
comparison which is true on e.g. cmake 3.22. Since the policy only exists
since cmake 3.30, configuring aborted on RHEL 9 / Ubuntu 22.04, although
cmake_minimum_required allows them. Use 'if (POLICY CMP0167)'.
* CMAKE_REQUIRED_INCLUDES expects directories, not header names. Setting it
to 'math.h' injected a bogus -I<scratch>/math.h into every check_*() and
leaked into the find_package() calls. Dropped, and CMAKE_REQUIRED_LIBRARIES
is now unset after the checks. config.h is unchanged.
* The Qt3 fallback tested 'AND Qt4_FOUND' instead of 'AND NOT Qt4_FOUND'.
* NeXus regression tests were registered unconditionally and therefore failed
in a default build. They are gated on 'nexus' now, and the HDF4 based ones
additionally on HAVE_HDF4.
* HAVE_HDF4 was queried but never defined, i.e. there was no way to switch on
the HDF4 support that PNeXus/PRunDataHandler/dump_header/musrfit implement.
Added option(HAVE_HDF4), to be used together with -Dnexus=1. This is not
academic: the ISIS example data are HDF4 files and test-histo-NeXus only
passes with -DHAVE_HDF4=1.
Do not use ${ROOT_USE_FILE}:
It overwrote CMAKE_CXX_STANDARD with ROOT's value and added ROOT's include
dirs, definitions and recommended compiler flags to *every* target,
including the Qt applications which do not use ROOT at all. Replaced by an
explicit equivalent; the C++ standard is now only raised, never silently
changed. The Qt sub-trees reset flags and include dirs again, mupp/plotter
puts them back since it does use ROOT. ROOT's flags are kept wherever ROOT
headers are used, -fsigned-char and -fsized-deallocation are ABI relevant
there. Verified: the compile flags of all ROOT consuming targets are
byte-identical to before.
Homebrew header shadowing:
Boost_INCLUDE_DIRS and FFTW3_INCLUDE are both /opt/homebrew/include on a
Homebrew system, i.e. the whole 3rd party header tree, and they were passed
to target_include_directories() with BEFORE - ahead of musrfit's own
src/include. They now come from the Boost::headers and FFTW3::FFTW3
imported targets, which contribute them as SYSTEM includes after the
project's own directories.
macOS:
* mupp: do not set the VERSION property on a MACOSX_BUNDLE target. cmake then
names the binary Contents/MacOS/mupp-1.1.0 and only leaves a symlink 'mupp'
beside it, contradicting CFBundleExecutable and not surviving
zipping/codesigning. This is the reason the symlink had to be created by
hand so far. VERSION is still set for the non-APPLE build.
* All four app bundles had an empty CFBundleShortVersionString and
CFBundleVersion; set MACOSX_BUNDLE_SHORT_VERSION_STRING / _BUNDLE_VERSION.
* APPLE is UNIX, so the RPM generator was also active on macOS -> restrict to
'UNIX AND NOT APPLE'. Likewise the /usr/local/lib rpath, which on Apple
silicon should be /opt/homebrew/lib; macOS gets its own branch now.
* mupp: -fpermissive is a GCC option, hand it to GCC only.
* musredit: use VERSION_GREATER_EQUAL for the Darwin version icon switch.
Verified on macOS 26 / arm64 with ROOT 6.40.03, Qt 6.11, cmake 4.4: full
build clean in the default configuration, with -DASlibs -DBMWlibs -DBNMRlibs
-DDummyUserFcn (identical list of 55 targets), and with -Dnexus / -DHAVE_HDF4.
ctest shows no regression at any step; no maxLH test fails in any of the three
NeXus configurations.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Add a Python-based test script that runs musrfit -c on each example
msr-file, extracts the maxLH or chisq value, and checks it against
a reference value with a relative tolerance of 1e-4. Covers all 13
example msr-files (single histogram and asymmetry fits).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>