Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
62c524f
ignore Rplots.pdf
jamesbriant Sep 28, 2026
26b0ec3
Add test to ensure distinct seeds get distinct outputs
jamesbriant Sep 28, 2026
db95b48
add small jitter to tolerance
jamesbriant Sep 28, 2026
a090837
Implement environment variable handling for project parameters and da…
jamesbriant Sep 28, 2026
9cefdac
allow tests/refactor to have custom environment
jamesbriant Sep 28, 2026
03cef26
Add custom, fixed environment for the golden fixture regression tests
jamesbriant Sep 28, 2026
6feafda
refactor to use the new fixed, refactor only environment
jamesbriant Sep 28, 2026
4ec8a7c
Detect 4th png channel and ignore. Ensure output directories are created
jamesbriant Sep 28, 2026
1d3a147
strengthen tests
jamesbriant Sep 29, 2026
4e3cfa3
distribute tests over multiple CPU cores
jamesbriant Sep 29, 2026
9c33437
Update README inline with chnages to CI and test scripts
jamesbriant Sep 29, 2026
29e0285
Enhance refactor job configuration with matrix strategy and OS-specif…
jamesbriant Sep 29, 2026
652e60d
Make text Windows safe
jamesbriant Sep 29, 2026
b9d5bdd
only MacOS runner compares figures
jamesbriant Sep 29, 2026
bbaf4f9
Update README with latest changes
jamesbriant Sep 29, 2026
beb69ee
Refactor CI configuration to remove Windows support and streamline ma…
jamesbriant Sep 29, 2026
c15a588
reintroduce windows test infrastructure. Requires repo env ENABLE_WIN…
jamesbriant Sep 29, 2026
5928401
update readmes about state of GH runner tests
jamesbriant Sep 29, 2026
19d3330
Move tests to MacOS and introduce new variable switch for ubuntu tests.
jamesbriant Sep 29, 2026
2f56fcd
Update with the latest information regarding tests failing due to fit…
jamesbriant Sep 29, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
184 changes: 172 additions & 12 deletions .github/workflows/r-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -82,10 +82,23 @@ jobs:
print(lints)
quit(status = as.integer(length(lints) > 0L))

# macOS 26 reproduces the reviewed fixtures and is the required refactor platform.
# It runs every contract with strict numerical and decoded-figure parity. See
# tests/refactor/README.md#why-ci-is-intentionally-asymmetric.
refactor:
name: Refactor parity
runs-on: ubuntu-latest
timeout-minutes: 60
name: Refactor parity (macos-26, ${{ matrix.contract }})
runs-on: macos-26
timeout-minutes: 30
strategy:
fail-fast: false
matrix:
contract:
- vqa-demo1/main_001_current
- vqa-demo1/offset_001_current
- vqa-demo2/offset_baseline
- vqa-demo2/offset_current
- vqa-demo2/project_baseline
- vqa-demo2/project_current
steps:
- uses: actions/checkout@v6

Expand All @@ -96,28 +109,175 @@ jobs:
- name: Install rv
shell: bash
run: |
mkdir -p "$HOME/.local/bin"
mkdir -p "$RUNNER_TEMP/rv-bin"
curl -fsSL \
"https://github.com/A2-ai/rv/releases/download/v${RV_VERSION}/rv-v${RV_VERSION}-aarch64-apple-darwin.tar.gz" \
-o "$RUNNER_TEMP/rv.tar.gz"
echo "626e3205a123bbb275017799e63a1548dcd7b8f42f37d09b8df57f9956ecf956 $RUNNER_TEMP/rv.tar.gz" | shasum -a 256 --check
tar -xzf "$RUNNER_TEMP/rv.tar.gz" -C "$RUNNER_TEMP/rv-bin"
echo "$RUNNER_TEMP/rv-bin" >> "$GITHUB_PATH"

- name: Restore R package library
uses: actions/cache@v6
with:
path: tests/refactor/rv/library
key: rv-refactor-${{ env.RV_VERSION }}-${{ runner.os }}-${{ runner.arch }}-r-${{ env.R_VERSION }}-${{ hashFiles('tests/refactor/rproject.toml', 'tests/refactor/rv.lock') }}

- name: Sync R dependencies
run: rv --config-file tests/refactor/rproject.toml sync --locked

- name: Select refactor library
shell: bash
run: echo "R_LIBS_USER=$(rv --config-file tests/refactor/rproject.toml library)" >> "$GITHUB_ENV"

- name: Run refactor tests
env:
VQA_COMPARE_FIGURES: true
VQA_COMPARE_GOLDEN: true
VQA_NUMERIC_TOLERANCE: 1e-12
VQA_TEST_CONTRACT: ${{ matrix.contract }}
VQA_TEST_DISTINCT_SEED: ${{ matrix.contract == 'vqa-demo2/project_current' }}
run: Rscript --vanilla tests/refactor/testthat.R

# Linux fitting is currently diagnostic: poor-quality inputs can make the legacy
# fitdistr path abort before parity is evaluated. Enable it explicitly while
# investigating with the repository variable ENABLE_UBUNTU_REFACTOR=true.
refactor-ubuntu:
name: Refactor diagnostic (ubuntu-24.04, ${{ matrix.contract }})
if: ${{ vars.ENABLE_UBUNTU_REFACTOR == 'true' }}
continue-on-error: true
runs-on: ubuntu-24.04
timeout-minutes: 30
strategy:
fail-fast: false
matrix:
contract:
- vqa-demo1/main_001_current
- vqa-demo1/offset_001_current
- vqa-demo2/offset_baseline
- vqa-demo2/offset_current
- vqa-demo2/project_baseline
- vqa-demo2/project_current
steps:
- uses: actions/checkout@v6

- uses: r-lib/actions/setup-r@v2
with:
r-version: ${{ env.R_VERSION }}

- name: Install rv
shell: bash
run: |
mkdir -p "$RUNNER_TEMP/rv-bin"
curl -fsSL \
"https://github.com/A2-ai/rv/releases/download/v${RV_VERSION}/rv-v${RV_VERSION}-x86_64-unknown-linux-gnu.tar.gz" \
-o "$RUNNER_TEMP/rv.tar.gz"
echo "$RV_SHA256 $RUNNER_TEMP/rv.tar.gz" | sha256sum --check
tar -xzf "$RUNNER_TEMP/rv.tar.gz" -C "$HOME/.local/bin"
echo "$HOME/.local/bin" >> "$GITHUB_PATH"
tar -xzf "$RUNNER_TEMP/rv.tar.gz" -C "$RUNNER_TEMP/rv-bin"
echo "$RUNNER_TEMP/rv-bin" >> "$GITHUB_PATH"

- name: Restore R package library
uses: actions/cache@v6
with:
path: rv/library
key: rv-${{ env.RV_VERSION }}-${{ runner.os }}-${{ runner.arch }}-r-${{ env.R_VERSION }}-${{ hashFiles('rproject.toml') }}
path: tests/refactor/rv/library
key: rv-refactor-${{ env.RV_VERSION }}-${{ runner.os }}-${{ runner.arch }}-r-${{ env.R_VERSION }}-${{ hashFiles('tests/refactor/rproject.toml', 'tests/refactor/rv.lock') }}

- name: Install system dependencies
shell: bash
run: |
sudo apt-get update
rv sysdeps --only-absent | xargs --no-run-if-empty sudo apt-get install -y
rv --config-file tests/refactor/rproject.toml sysdeps --only-absent | xargs --no-run-if-empty sudo apt-get install -y

- name: Sync R dependencies
run: rv sync
run: rv --config-file tests/refactor/rproject.toml sync --locked

- name: Select refactor library
shell: bash
run: echo "R_LIBS_USER=$(rv --config-file tests/refactor/rproject.toml library)" >> "$GITHUB_ENV"

- name: Run refactor tests
env:
VQA_COMPARE_FIGURES: false
VQA_COMPARE_GOLDEN: false
VQA_NUMERIC_TOLERANCE: 1e-8
VQA_TEST_CONTRACT: ${{ matrix.contract }}
VQA_TEST_DISTINCT_SEED: false
run: Rscript --vanilla tests/refactor/testthat.R

- name: Run refactor parity tests
run: Rscript tests/refactor/testthat.R
# Keep the Windows runner definition executable, but do not allocate runners by default.
# Existing fixture names containing '>' cannot be checked out on Windows. Once renamed,
# set the repository variable ENABLE_WINDOWS_REFACTOR=true to restore all six shards.
refactor-windows:
name: Refactor portability (windows-2025, ${{ matrix.contract }})
if: ${{ vars.ENABLE_WINDOWS_REFACTOR == 'true' }} # GH repo variable
runs-on: windows-2025
timeout-minutes: 30
strategy:
fail-fast: false
matrix:
contract:
- vqa-demo1/main_001_current
- vqa-demo1/offset_001_current
- vqa-demo2/offset_baseline
- vqa-demo2/offset_current
- vqa-demo2/project_baseline
- vqa-demo2/project_current
steps:
- uses: actions/checkout@v6

- uses: r-lib/actions/setup-r@v2
with:
r-version: ${{ env.R_VERSION }}

- name: Install rv
shell: pwsh
run: |
$archive = Join-Path $env:RUNNER_TEMP "rv.zip"
$bin = Join-Path $env:RUNNER_TEMP "rv-bin"
Invoke-WebRequest `
-Uri "https://github.com/A2-ai/rv/releases/download/v$env:RV_VERSION/rv-v$env:RV_VERSION-x86_64-pc-windows-msvc.zip" `
-OutFile $archive
$actual = (Get-FileHash -Algorithm SHA256 $archive).Hash.ToLowerInvariant()
if ($actual -ne "8adc0d558e0459e58eb88a770082211bcb1e23c2efc1f6ba5da3d5a9ba39877c") {
throw "rv archive checksum mismatch: $actual"
}
Expand-Archive -Path $archive -DestinationPath $bin
$bin | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append

- name: Restore R package library
uses: actions/cache@v6
with:
path: tests/refactor/rv/library
key: rv-refactor-${{ env.RV_VERSION }}-${{ runner.os }}-${{ runner.arch }}-r-${{ env.R_VERSION }}-${{ hashFiles('tests/refactor/rproject.toml', 'tests/refactor/rv.lock') }}

- name: Sync R dependencies
run: rv --config-file tests/refactor/rproject.toml sync --locked

- name: Select refactor library
shell: pwsh
run: |
"R_LIBS_USER=$(rv --config-file tests/refactor/rproject.toml library)" |
Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append

- name: Run refactor tests
env:
VQA_COMPARE_FIGURES: false
VQA_COMPARE_GOLDEN: false
VQA_NUMERIC_TOLERANCE: 1e-8
VQA_TEST_CONTRACT: ${{ matrix.contract }}
VQA_TEST_DISTINCT_SEED: ${{ matrix.contract == 'vqa-demo2/project_current' }}
run: Rscript --vanilla tests/refactor/testthat.R

refactor-complete:
name: Refactor checks
if: always()
needs: [refactor, refactor-windows]
runs-on: ubuntu-latest
steps:
- name: Confirm all refactor shards passed
env:
MATRIX_RESULT: ${{ needs.refactor.result }}
WINDOWS_RESULT: ${{ needs.refactor-windows.result }}
run: |
test "$MATRIX_RESULT" = success
test "$WINDOWS_RESULT" = success || test "$WINDOWS_RESULT" = skipped
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,13 @@ data/*
!data/vqa-demo2-min/**

CHANGELOG_ps.md
Rplots.pdf

# R project files
## rv
rv.lock
!tests/refactor/rv.lock
tests/refactor/rv/

## R
.Rhistory
Expand Down
10 changes: 8 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,8 +86,14 @@ You can read more about VQA in detail in this open access [publication](https://

## Developer Setup

GitHub Actions validates both minimal demo projects, runs the standard and refactor parity
test suites, and lints the actively refactored R code on pull requests and pushes to `main`.
GitHub Actions validates both minimal demo projects, runs the standard tests, checks all
six golden refactor contracts on macOS 26, and lints the actively refactored R code on
pull requests and pushes to `main`. Ubuntu refactor diagnostics are paused while the
legacy parametric-fitting failures are investigated; set the repository variable
`ENABLE_UBUNTU_REFACTOR=true` to enable all six non-blocking Ubuntu shards. Windows checks
are paused pending Windows-safe fixture filenames; set `ENABLE_WINDOWS_REFACTOR=true` to
enable them. The rationale and restoration criteria are documented in
[`tests/refactor/README.md`](tests/refactor/README.md#why-ci-is-intentionally-asymmetric).

If you are working on the codebase itself, use the tracked `pre-commit` configuration to run formatting and linting automatically before commits.

Expand Down
3 changes: 3 additions & 0 deletions includes/functions.R
Original file line number Diff line number Diff line change
Expand Up @@ -5390,6 +5390,9 @@ unix.friendly <- function(sometext) {
str <- gsub('_-_', '-', str) # Remove redundant underscores+hyphens
str <- gsub("]", "", str) # Remove square brackets
str <- gsub("\\[", "", str) # Remove square brackets
if (.Platform$OS.type == "windows") {
str <- gsub('[<>:"|?*]', '_', str)
}
friendly <- tolower(str)

return(friendly)
Expand Down
54 changes: 45 additions & 9 deletions params.R
Original file line number Diff line number Diff line change
Expand Up @@ -114,12 +114,37 @@ wd
params.loaded <- function() {}
global.params.loaded<-""

# Load separate parameter file specifying
# project and assessment to be analyzed.
# CRITICAL: many file paths and other parameters
# depend on these two parameters
params.pa.file <- paste0(BASEDIR_PSFILES, "params.pa.R")
source(params.pa.file)
# Load separate parameter file specifying
# project and assessment to be analyzed. The environment bridge is temporary:
# Phase 2 replaces this legacy edge with an in-process runner.
legacy.data.root <- Sys.getenv("VQA_DATA_ROOT", "")
legacy.project <- Sys.getenv("VQA_PROJECT", "")
legacy.assessment <- Sys.getenv("VQA_ASSESSMENT", "")
legacy.selection <- c(legacy.data.root, legacy.project, legacy.assessment)

if (any(nzchar(legacy.selection))) {
if (!all(nzchar(legacy.selection))) {
stop_quietly("ERROR: VQA_DATA_ROOT, VQA_PROJECT, and VQA_ASSESSMENT must be supplied together.\n")
}
if (any(!grepl("^[[:alnum:]_-]+$", legacy.selection[-1L]))) {
stop_quietly("ERROR: VQA_PROJECT and VQA_ASSESSMENT may contain only letters, numbers, '_' and '-'.\n")
}
if (!file.exists(file.path(SRCDIR, "params", paste0("params.", legacy.project, ".R")))) {
stop_quietly("ERROR: VQA_PROJECT has no project parameter file.\n")
}
legacy.data.root <- normalizePath(legacy.data.root, mustWork = TRUE)
if (!dir.exists(file.path(legacy.data.root, legacy.assessment))) {
stop_quietly("ERROR: VQA_ASSESSMENT directory is not present in VQA_DATA_ROOT.\n")
}
PROJ <- legacy.project
ASSESS <- legacy.assessment
PARAMS.USE.ASSESS <- FALSE
IMPORT.USE.ASSESS <- FALSE
BASEDIR_PSFILES <- SRCDIR
} else {
params.pa.file <- paste0(BASEDIR_PSFILES, "params.pa.R")
source(params.pa.file)
}

# Throw intelligible error if PROJ or ASSESS not properly set
if ( !all(sapply(c("PROJ", "ASSESS"), exists)) ) {
Expand Down Expand Up @@ -159,7 +184,11 @@ LIB.LOAD.SILENT <- TRUE

# Project base data directory
# This is used mostly by qh.net.R to build qh.net input directory paths
DATA_BASEDIR_PROJ <- paste0( DATA_BASEDIR_FINAL, PROJ, "/" )
if (nzchar(legacy.data.root)) {
DATA_BASEDIR_PROJ <- paste0(legacy.data.root, "/")
} else {
DATA_BASEDIR_PROJ <- paste0( DATA_BASEDIR_FINAL, PROJ, "/" )
}

# Main data base directory (= assessment data directory)
# In general, everthing live here
Expand Down Expand Up @@ -1590,12 +1619,19 @@ if ( file.exists(params.proj.file) ) {
cat("\nWARNING: project-specific parameters file '", params.proj.file, "' not found!\n\n", sep="")
}

legacy.seed <- Sys.getenv("VQA_TEST_SEED", "")
if (nzchar(legacy.seed)) {
seed <- suppressWarnings(as.integer(legacy.seed))
if (is.na(seed) || as.character(seed) != legacy.seed) {
stop_quietly("ERROR: VQA_TEST_SEED must be an integer.\n")
}
set.seed <- TRUE
}

#####################################
#####################################
# Load parameter confirmation messages
#####################################
#####################################

source( paste0( SRCDIR, "params.conf.general.R") )


45 changes: 43 additions & 2 deletions tests/refactor/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,59 @@ Fixtures were generated from the corresponding non-`-min` legacy project.
The legacy code is not run by these tests and may be deleted once the fixtures
have been reviewed.

Run the suite from the repository root:
The suite has a temporary frozen dependency environment so that its numerical
golden fixtures do not change as CRAN packages are updated. Sync and run it from
the repository root with:

```sh
Rscript tests/refactor/testthat.R
rv --config-file tests/refactor/rproject.toml sync --locked
R_LIBS_USER="$(rv --config-file tests/refactor/rproject.toml library)" \
Rscript --vanilla tests/refactor/testthat.R
```

This uses `tests/refactor/rv.lock` and `tests/refactor/rv/library`. Normal
development and the standard CI checks continue to use the root
`rproject.toml` and `rv/library`. Remove the refactor configuration and lock
when this regression suite is retired.

With no contract selected, the runner executes the six assessments in parallel using half
the detected logical CPUs, capped at six workers. Set `VQA_TEST_WORKERS=1` for the original
sequential behaviour or another positive integer to choose a different limit.

CSV and XLSX outputs are compared as tables, rather than raw files. The XLSX
`Meta` sheet's `Analysis date:` row is excluded because it records execution
time, not an analysis result. PNG files are decoded and compared by dimensions,
channels, and pixel values; PNG metadata and compression timestamps therefore
do not affect the result.

### Why CI is intentionally asymmetric

The golden fixtures were reviewed on a modern Apple Silicon macOS environment. They are a
regression contract for the refactor, not a claim that legacy optimizers produce identical
floating-point results on every operating system. CI therefore assigns each environment a
different, explicit responsibility:

| Environment | Current check | Reason |
| --- | --- | --- |
| Ubuntu 24.04 | Disabled, with the complete six-shard diagnostic retained | Poor-quality inputs can make the legacy `fitdistr()` path abort before output comparison; set `ENABLE_UBUNTU_REFACTOR=true` to investigate without blocking required checks. |
| macOS 26 | All six contracts: strict tables, decoded figures, and repeatability | This runner reproduces the reviewed golden fixtures and is the temporary canonical parity environment. |
| Windows 2025 | Disabled, with the complete six-shard job retained | Existing fixture names containing `>` fail during checkout before R starts. |

Restore Ubuntu as a required portability check only after the legacy parametric-fitting
methodology has been made robust for sparse, zero-heavy, and extreme-value inputs. Restore
Windows by renaming incompatible fixtures and setting the repository variable
`ENABLE_WINDOWS_REFACTOR=true`. The distinct-seed assertion remains active in the macOS 26
`vqa-demo2/project_current` shard.

CI selects one assessment per job with `VQA_TEST_CONTRACT=project/assessment`. The optional
Ubuntu diagnostic uses `VQA_COMPARE_GOLDEN=false`: it requires the golden result-file
manifest, then compares two same-seed runs on Linux. macOS 26 retains strict golden
numerical and figure parity.
`VQA_NUMERIC_TOLERANCE` sets the table-comparison tolerance, and
`VQA_COMPARE_FIGURES=false` skips platform-rendered figure comparisons. Local runs keep
the strict golden comparison, compare figures, and exercise every assessment by default.
The distinct-seed check can be selected independently with `VQA_TEST_DISTINCT_SEED`.

## Creating or refreshing a fixture

Use the non-`-min` legacy project as the source and run it in a disposable
Expand Down
Loading
Loading