The CMake build originally had a single minimal installation test (ast_test.c).
The ast_tester/ directory contains 10 C test programs and 32 Fortran test
programs that were only runnable via a Starlink-specific tcsh script. Many AST
classes had no C test coverage — their only tests were Fortran programs
depending on Starlink libraries (EMS, CHR, PSX). The goal is to:
- Add the existing C tests to the CMake build
- Convert Fortran tests to C to eliminate the Fortran/Starlink dependency
| Phase | Status |
|---|---|
| Phase 1: Add existing C tests to CMake | Complete |
| Phase 2 Batch 1: Simple Fortran conversions | Complete (8 tests) |
| Phase 2 Batch 2: Medium Fortran conversions | Complete (6 tests) |
| Phase 2 Batch 3: Larger Fortran conversions | Complete (5 tests) |
| Phase 2 Batch 4: Checkdump-pattern tests | Complete (4 tests) |
| Phase 2 Batch 5: Channel-callback tests | Complete |
| Phase 2 Batch 6: Huge/manual stress tests | Complete (1 test) |
| Phase 2 Batch 7: Final large tests | Complete (2 tests) |
| Phase 2 Batch 8: WCS-conversion regression harness | Complete (14 tests from wcsconverter.f) |
| Phase 2 Batch 9: Simplify regression harness | Complete (3 tests from simplify.f) |
| Phase 2 Batch 10: Plotter smoke tests | Complete (20 tests, conditional on PLplot) |
| Phase 2 Batch 11: .head native round-trip tests | Complete (20 wcsconv tests from .head files) |
| Phase 2 Batch 12: Grid smoke tests (no PLplot) | Complete (21 tests via logging GRF plugin) |
| Phase 2 Batch 13: Resample / QuadApprox coverage | Complete (1 test) |
| Phase 2 Batch 14: astMask sky-curvature coverage | Complete (1 test) |
| Phase 3: CI integration | Complete (tests run via ctest) |
Original test (1):
- ast_test — minimal installation check
Existing C tests added to CMake (10):
- testerror, testobject, testconvert, testresimp, testaxis, testframe, testunitnorm, testsplinemap_c, testyamlchan (conditional), testthreads (conditional)
Fortran tests converted to C (59 default + 20 conditional + 1 optional):
- Batch 1: testzoommap, testnormmap, testmapping, testskyframe, testcmpframe, testlutmap, testratemap, testchannel
- Batch 2: testrate, testspecframe, testflux, testspecflux, testcmpmap, testpolymap
- Batch 3: testchebymap, testunitnormmap, testtrangrid, testmoc, testfitstable
- Batch 4: testframeset, testswitchmap, testtime, testkeymap
- Batch 5: testmocchan, testxmlchan, testtable, teststcschan, testfitschan
- Batch 6: testregions, testrebinseq, testplotter (conditional on PLplot)
- Batch 7: testrebin, teststc
- Batch 8: 14 × wcsconv_* regression-diff tests driven by ported wcsconverter.c
- Batch 9: 3 × simplify_* + 3 × simplify_*_astequal regression-diff tests driven by ported simplify.c
- Batch 10: 20 × plotter_* smoke tests (conditional on PLplot) — run testplotter on each .head file with .attr/.fattr/.box companions to verify astGrid doesn't crash
- Batch 11: 20 × wcsconv_*_native_astequal tests — round-trip .head files through wcsconverter in native encoding and verify semantic equivalence via ast_astequal
Grid smoke tests using logging GRF (21 default):
- Batch 12: testgrid + 20 × grid_* smoke tests — run testgrid on each .head file using the logging GRF plugin (grf_log.c) which provides a virtual viewport with no PLplot dependency. Same .head/.attr/.fattr/.box fixtures as Batch 10.
Coverage gap tests (2 default):
- Batch 13: testresample — exercises astResampleD/F/I across 10 interpolation schemes (NEAREST, LINEAR, SINC, SINCSINC, SINCCOS, SINCGAUSS, GAUSS, SOMB, SOMBCOS, BLOCKAVE), plus bad-pixel handling, variance propagation, AST__NOBAD flag, and astQuadApprox.
- Batch 14: testregionmasking — verifies how astMask treats the great-circle edges of a Polygon defined in a SkyFrame. Establishes a projection-independent ground truth by classifying each pixel centre directly (map pixel to sky, ask the Polygon via astTran2), then compares against astMask. Confirms that for a CAR projection the default (SimpVertices=1) mask linearises the curved edges (wrong at 467/38036 interior pixels in the test geometry) while SimpVertices=0 reproduces the curved boundary exactly; for TAN both agree because gnomonic maps great circles to straight lines. The CAR and TAN GRID<->SKY FrameSets are embedded as native serialised strings (read back with astFromString) so the test does not depend on the FitsChan class.
New C-only regression tests (no Fortran original):
- testslamap — SlaMap transform with an undefined (AST__BAD) conversion argument must return AST__BAD outputs rather than NaN.
- testimmutable — every Mapping class with a publicly settable attribute must allow the change while its reference count is one and report AST__IMMUT once it has been cloned, as SUN/210 requires, for both setting and clearing. Also checks that Invert stays settable on a shared Mapping. PermMap's PermSplit and WcsMap's FITSProj and LonCheck are excluded because they are protected and no astSet call reaches them.
Optional manual stress test:
- testhuge_c
Created stub headers for Starlink dependencies:
cmake/sae_par.h— definesSAI__OKandSAI__ERRORcmake/star/mers.h— inline no-op stubs forerrMark,errRlse,errStat,errAnnul,errRep
Created ast_tester/CMakeLists.txt with:
WHOLE_ARCHIVElinking for satellite libraries (ast_err, ast_grf_*) to resolve symbols referenced by libast at runtime- Conditional tests for YAML and pthreads support
- Data file copying for testsplinemap_c and testcmpmap
Fixed testobject.c to use baseName() for portable __FILE__ comparison
and __LINE__ captures instead of hardcoded line numbers.
Fixed testsplinemap_c.c to check fscanf return values.
Fixed testthreads.c to return NULL from worker() thread function. Later
reworked so each worker watches its own thread-local status and reports the
outcome back through the shared struct, replacing the Starlink EMS calls
(errStat/errAnnul) that are no-ops in the standalone build and so could
never detect the expected lock error.
ctest decides pass/fail purely from each program's exit code (no pass/fail
regex), so a test that always returns 0 reports success even when its own
checks fail. Eight programs ran off the end of main() with no return
statement (an implicit return 0 in C99) and so had been silently passing:
testerror, testobject, testaxis, testframe, testunitnorm,
testsplinemap_c, testthreads, testyamlchan (and testregions, which
returned a hardcoded 0). All now return astOK ? 0 : 1. Making the status
real exposed two genuine failures that had been masked:
- testyamlchan: its input data files (
imaging_wcs.asdf,tanSipWcs.txt,lsst_wcs.txt) existed in the source tree but were never copied into the build directory; added the missingconfigure_filecalls. Running the test under AddressSanitizer then exposed two pre-existing buffer overflows inyamlchan.c/memory.c(stack overrun inReadPoly'sdimd/dimw, and astr - 1underread inastChrSplit_), fixed by cherry-picking the upstream fixes from PR #39.
Each converted test:
- Uses the public C API (
ast.h) only - Uses standard C strings and
astMalloc/astFree(no CHR/PSX) - Uses
astWatch(&status)+astOKfor error checking (no EMS) - Fortran
err_mark/err_rlsesimply omitted - Fortran channel source/sink callbacks replaced by
astToString/astFromStringor ChannelSinkFile/SourceFileattributes - Fortran
psx_calloc/psx_freereplaced byastMalloc/astFree - Fortran 1-based indices adjusted to C 0-based where needed (e.g.
astGetCell)
Each converted test file has a header comment documenting any differences. Key issues:
-
testfitstable.c: String column sizes differ because C
astMapPut1Cstores actual string content without padding, while Fortran CHARACTER variables are stored at their declared length. This affectsColumnSize,ColumnLenC,NAXIS1,TFORM, andTDIMheader values. The test skips hardcoded size checks and verifies round-trips instead. TODO: Decide whether the C API should pad strings for FITS BINTABLE conventions. -
testunitnormmap.c: The
differ()comparison function has an absolute tolerance floor of1e-14not present in the Fortran original, needed because near-zero values can differ between simplified and unsimplified Mappings. -
testhuge.c: This is a faithful C port of the original huge 64-bit stress test. It is built only when
AST_ENABLE_HUGE_TEST=ONand should be treated as a manual test rather than a routine CI test. It allocates two60001 x 60001float arrays and can take a very long time to run, especially under sanitizers. -
testzoommap.c: Simplified immutability error recovery (checks
!astOKrather than specificAST__IMMUTcode). -
testfitschan.c: FITS card padding ignored during assertions to match Fortran string comparison rules. Fixed a
heap-use-after-freebug inastStore_(memory.c) exposed by AddressSanitizer duringastConvert. -
testregions.c: Translated automatically and then fixed up to cast
astTranNinput arrays to(const double *). Several automatic-translation defects were later corrected againsttestregions.f: the per-section "X tests failed" messages were printed unconditionally (the Fortranif status != OKguard had been dropped);checkBox/checkCircle/checkEllipseeach used the wrong FITS header (a single unrelated block had been copied into all three);checkdumpcompared dump strings withstrcmpinstead of falling back toastOverlap()==5as the Fortran does; and fourfrm1 = astFrame(...)statements had been absorbed into//comments. TheastGetRegionDisccheck incheckCmpRegionuses tolerances relaxed to1e-7(from the Fortran's1e-9/1e-8) because that disc is fitted to a subsampled boundary mesh and so varies at the~1e-8level with build/optimisation settings; the Fortran test built against the autoconf AST passes the tight tolerance on the same machine. -
testrebinseq.c: Translated to use
astRebinSeq[I|F|D]depending on the types of the in/out pointers. -
testrebin.c: Collapses 27 Fortran subroutines (9 test groups × 3 type variants each) into 9 parameterized C functions using a DataType enum. Tests astRebinD/F/I across 6 spread functions.
-
teststc.c: Tests STC classes using 10 external XML data files. Uses safeGetC() wrapper and strcmpTrim() for Fortran trailing-space semantics. astTranN arrays use in[ncoord][npoint] layout (Fortran column-major order).
-
wcsconverter.c: C port of the Fortran regression-diff harness. Not a self-validating test — it is a CLI (
wcsconverter <in> <encoding> <out> [<attrs>]) driven byast_tester/CMakeLists.txt'sadd_wcsconv_test()helper, which registers one ctest per fixture that invokes the binary and diffs its output against a committed reference file. Replaces the 16-row regression loop in the oldast_testertcsh script (the two duplicate rows there collapse to 14 unique fixtures). Thetimj.nativereference was regenerated during the port to absorb a 1-ulp noise-level drift in a single MatrixMap inverse cell between the 2018-era reference and current libast output. -
simplify.c: C port of the Fortran simplify harness. Like wcsconverter, it is a CLI (
simplify <in> <out>) driven byast_tester/CMakeLists.txt'sadd_simplify_test()helper. Each fixture reads a.mapinput, runsastSimplify, and diffs the output against a committed.simpreference. Both byte-level string diff and astEqual semantic comparison tests are registered, matching the wcsconverter pattern. Three fixtures: brad, lsst1, rigby. -
ast_astequal.c: Generic semantic equivalence comparator (renamed from wcs_astequal.c). Reads two AST object files and exits 0 if astEqual reports them equivalent. Handles both AST dumps (via astChannel) and FITS encodings (via astFitsChan). Used by both wcsconverter and simplify test harnesses.
- testplot3d.f (1357 lines) — Plot3D (requires PGPLOT; interactive/graphical test that cannot be fully converted without a graphics backend). A C port (testplot3d.c) exists and runs when PLplot is found.
- regression.f (1515 lines) — stdout-diff regression harness. Deferred; most functionality is already covered by individual class tests.
- joye_car_headers/: Contains additional FITS WCS headers (CAR projection variants from W. Joye) that could be added to plotter and wcsconv tests. Note: 3 files (CAR_model, CHIPASS_Equ, cmap_3years_GP_D2) have non-FITS preamble lines ("FITS headers in ...") that would need stripping first.
- Dead file cleanup: The following files are no longer used and could be
removed in a future pass:
.psfiles (20 generated PostScript outputs, ~18 MB)- Old build scripts:
doplot,makeplot,maketest,moctohtml - Duplicate
2dspline.dat(identical to2dspline_c.dat) asdftest.py(standalone Python test, not integrated)regression.current,regression.out(old diff targets)draw3d-test2.txt,draw3d-test3.txt(unreferenced)
The GitHub Actions workflow runs ctest which automatically picks up all
test targets. No workflow changes were needed. Tests conditional on YAML
and pthreads are handled by CMake options.
The original testhuge.f now has a C port in ast_tester/testhuge.c.
Because it is intentionally enormous, it is not built by default.
Enable it with:
cmake -B build-huge -DCMAKE_BUILD_TYPE=Debug -DAST_ENABLE_HUGE_TEST=ON
cmake --build build-huge --target testhuge_c
Run it manually:
./build-huge/ast_tester/testhuge_c
This test is a manual stress test, not a routine regression test. It can consume tens of gigabytes of RAM and may take a very long time to complete. It should not normally be combined with sanitizers.
A tolerance-based, architecture-independent regression oracle records the
current numerical output of AST transforms over three corpora so that future
work (SIMD kernels, refactors) can be checked for unintended changes. See
docs/superpowers/specs/2026-06-27-transform-oracle-design.md.
gen_transform_oracle(built, not a test) scans the fixture tree and writes three committed reference files:simplify_fixtures.oracle(thesimplify/*.mapand*.simpnative dumps),headers.oracle(the*.headFITS headers) andframesets.oracle(the*.astFrameSet dumps).check_transform_oracle(the ctest gate) reads an oracle file as its sole manifest, reloads each named fixture, re-transforms the recorded inputs and compares within tolerance. Four ctests:transform_oracle_selftest(parser),transform_oracle_simplify,transform_oracle_headers,transform_oracle_framesets.test_oracle_utilunit-tests the helpers.- Checks: golden forward and golden inverse (every fixture; the inverse
section's inputs are the forward outputs);
.map/.simpequivalence gated on stored agreement; and, for the GRID-domain corpora, round-trip accuracy (inverse(forward(P)) ~= P) to an absolute pixel tolerance. oracle/transform_oracle_overrides.txtrecords per-fixture round-trip exceptions (currentlytsc.headandtnx-cheb.head, whose inverses do not recover the pixel and are flagged for investigation).
Regenerate after an intentional transform change and commit the diff:
cmake --build build --target gen_transform_oracle
./build/ast_tester/gen_transform_oracle ast_tester/fixtures \
ast_tester/fixtures/oracle/simplify_fixtures.oracle \
ast_tester/fixtures/oracle/headers.oracle \
ast_tester/fixtures/oracle/framesets.oracle
cmake -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build
ctest --test-dir build --output-on-failure
Shows 106 default tests (+ 20 conditional PLplot plotter smoke tests), all passing (minus pre-existing testresimp segfault).