Skip to content

Latest commit

 

History

History
307 lines (251 loc) · 16.3 KB

File metadata and controls

307 lines (251 loc) · 16.3 KB

Plan: AST Test Coverage Enhancements

Context

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:

  1. Add the existing C tests to the CMake build
  2. Convert Fortran tests to C to eliminate the Fortran/Starlink dependency

Current status: 109 default tests + 20 conditional (PLplot) + 1 optional huge stress test

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)

Test inventory (109 default + 20 conditional PLplot + 1 optional)

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

Phase 1 details (complete)

Created stub headers for Starlink dependencies:

  • cmake/sae_par.h — defines SAI__OK and SAI__ERROR
  • cmake/star/mers.h — inline no-op stubs for errMark, errRlse, errStat, errAnnul, errRep

Created ast_tester/CMakeLists.txt with:

  • WHOLE_ARCHIVE linking 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.

Exit-status audit

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 missing configure_file calls. Running the test under AddressSanitizer then exposed two pre-existing buffer overflows in yamlchan.c/memory.c (stack overrun in ReadPoly's dimd/dimw, and a str - 1 underread in astChrSplit_), fixed by cherry-picking the upstream fixes from PR #39.

Phase 2 conversion approach

Each converted test:

  • Uses the public C API (ast.h) only
  • Uses standard C strings and astMalloc/astFree (no CHR/PSX)
  • Uses astWatch(&status) + astOK for error checking (no EMS)
  • Fortran err_mark/err_rlse simply omitted
  • Fortran channel source/sink callbacks replaced by astToString/astFromString or Channel SinkFile/SourceFile attributes
  • Fortran psx_calloc/psx_free replaced by astMalloc/astFree
  • Fortran 1-based indices adjusted to C 0-based where needed (e.g. astGetCell)

Known differences from Fortran originals

Each converted test file has a header comment documenting any differences. Key issues:

  • testfitstable.c: String column sizes differ because C astMapPut1C stores actual string content without padding, while Fortran CHARACTER variables are stored at their declared length. This affects ColumnSize, ColumnLenC, NAXIS1, TFORM, and TDIM header 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 of 1e-14 not 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=ON and should be treated as a manual test rather than a routine CI test. It allocates two 60001 x 60001 float arrays and can take a very long time to run, especially under sanitizers.

  • testzoommap.c: Simplified immutability error recovery (checks !astOK rather than specific AST__IMMUT code).

  • testfitschan.c: FITS card padding ignored during assertions to match Fortran string comparison rules. Fixed a heap-use-after-free bug in astStore_ (memory.c) exposed by AddressSanitizer during astConvert.

  • testregions.c: Translated automatically and then fixed up to cast astTranN input arrays to (const double *). Several automatic-translation defects were later corrected against testregions.f: the per-section "X tests failed" messages were printed unconditionally (the Fortran if status != OK guard had been dropped); checkBox/checkCircle/checkEllipse each used the wrong FITS header (a single unrelated block had been copied into all three); checkdump compared dump strings with strcmp instead of falling back to astOverlap()==5 as the Fortran does; and four frm1 = astFrame(...) statements had been absorbed into // comments. The astGetRegionDisc check in checkCmpRegion uses tolerances relaxed to 1e-7 (from the Fortran's 1e-9/1e-8) because that disc is fitted to a subsampled boundary mesh and so varies at the ~1e-8 level 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 by ast_tester/CMakeLists.txt's add_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 old ast_tester tcsh script (the two duplicate rows there collapse to 14 unique fixtures). The timj.native reference 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 by ast_tester/CMakeLists.txt's add_simplify_test() helper. Each fixture reads a .map input, runs astSimplify, and diffs the output against a committed .simp reference. 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.

Phase 2 remaining work

Unconverted Fortran tests

  • 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.

Potential future improvements

  • 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:
    • .ps files (20 generated PostScript outputs, ~18 MB)
    • Old build scripts: doplot, makeplot, maketest, moctohtml
    • Duplicate 2dspline.dat (identical to 2dspline_c.dat)
    • asdftest.py (standalone Python test, not integrated)
    • regression.current, regression.out (old diff targets)
    • draw3d-test2.txt, draw3d-test3.txt (unreferenced)

Phase 3 details (complete)

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.

Huge stress test

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.

Transform output oracle

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 (the simplify/*.map and *.simp native dumps), headers.oracle (the *.head FITS headers) and framesets.oracle (the *.ast FrameSet 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_util unit-tests the helpers.
  • Checks: golden forward and golden inverse (every fixture; the inverse section's inputs are the forward outputs); .map/.simp equivalence 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.txt records per-fixture round-trip exceptions (currently tsc.head and tnx-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

Verification

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).