Dev/automate tests using data (#267)
All checks were successful
Build on RHEL8 / build (push) Successful in 2m13s
Build on RHEL9 / build (push) Successful in 2m37s
Run tests using data on local RHEL8 / build (push) Successful in 3m12s

- automatically run python tests 
- automatically run test using data files on local runner from gitea
- fixed some of the workflows

---------

Co-authored-by: Erik Fröjdh <erik.frojdh@psi.ch>
This commit is contained in:
2026-01-20 17:20:48 +01:00
committed by GitHub
parent cbefbc43e9
commit b77a576f72
11 changed files with 146 additions and 45 deletions

View File

@@ -32,21 +32,22 @@ jobs:
run: |
sudo apt-get update
sudo apt-get -y install cmake gcc g++
- name: Get conda
uses: conda-incubator/setup-miniconda@v3
with:
python-version: ${{ matrix.python-version }}
environment-file: etc/dev-env.yml
miniforge-version: latest
channels: conda-forge
conda-remove-defaults: "true"
sudo apt-get -y install python3.12 python3.12-dev python3.12-venv python3-pip
sudo apt-get -y install doxygen
python3.12 -m venv venv
source venv/bin/activate
pip install --upgrade pip
pip install breathe
pip install sphinx_rtd_theme sphinx
pip install numpy
pip install furo
- name: Build library
run: |
source venv/bin/activate
mkdir build
cd build
cmake .. -DAARE_SYSTEM_LIBRARIES=ON -DAARE_DOCS=ON
cmake .. -DAARE_PYTHON_BINDINGS=ON -DAARE_DOCS=ON
make -j 2
make docs

View File

@@ -0,0 +1,47 @@
name: Run tests using data on local RHEL8
on:
push: # pull_request only works if pull_request in gitea
workflow_dispatch:
permissions:
contents: read
jobs:
build:
runs-on: "detectors-software-RH8"
steps:
- uses: actions/checkout@v4
- name: Clone aare-test-data repository
run: |
git lfs install
git clone https://gitea.psi.ch/detectors/aare-test-data.git ../aare-test-data
cd ../aare-test-data
git lfs pull
- name: Build library
run: |
source /home/gitea_runner/.bashrc
conda activate aare_test
mkdir build && cd build
cmake .. -DAARE_PYTHON_BINDINGS=ON -DAARE_TESTS=ON
make -j 4
- name: C++ unit tests
working-directory: ${{github.workspace}}/build
env:
AARE_TEST_DATA: ${{github.workspace}}/../aare-test-data
run: ./run_tests [.with-data] # TODO: should we run all tests?
- name: Python unit tests
working-directory: ${{github.workspace}}/build
env:
AARE_TEST_DATA: ${{github.workspace}}/../aare-test-data
run: |
source /home/gitea_runner/.bashrc
conda activate aare_test
python -m pytest ${{github.workspace}}/python/tests/ --with-data # runs all tests

View File

@@ -11,7 +11,7 @@ jobs:
build:
runs-on: "ubuntu-latest"
container:
image: gitea.psi.ch/images/rhel8-developer-gitea-actions
image: gitea.psi.ch/detectors/rhel8-detectors-dev
steps:
# workaround until actions/checkout@v4 is available for RH8
# - uses: actions/checkout@v4
@@ -21,9 +21,15 @@ jobs:
git clone https://${{secrets.GITHUB_TOKEN}}@gitea.psi.ch/${{ github.repository }}.git --branch=${{ github.ref_name }} .
- name: Install dependencies
- name: Install Python dependencies
run: |
dnf install -y cmake python3.12 python3.12-devel python3.12-pip
python3.12 -m pip install --upgrade pip
python3.12 -m pip install pytest
python3.12 -m pip install numpy
python3.12 -m pip install pytest-check
python3.12 -m pip install matplotlib
python3.12 -m pip install boost-histogram
- name: Build library
run: |
@@ -34,3 +40,8 @@ jobs:
- name: C++ unit tests
working-directory: ${{gitea.workspace}}/build
run: ctest
- name: Python unit tests
working-directory: ${{gitea.workspace}}/build
run: |
python3.12 -m pytest ${{gitea.workspace}}/python/tests/

View File

@@ -11,14 +11,19 @@ jobs:
build:
runs-on: "ubuntu-latest"
container:
image: gitea.psi.ch/images/rhel9-developer-gitea-actions
image: gitea.psi.ch/detectors/rhel9-detectors-dev
steps:
- uses: actions/checkout@v4
- name: Install dependencies
- name: Install Python dependencies
run: |
dnf install -y cmake python3.12 python3.12-devel python3.12-pip
python3.12 -m pip install --upgrade pip
python3.12 -m pip install pytest
python3.12 -m pip install numpy
python3.12 -m pip install pytest-check
python3.12 -m pip install matplotlib
python3.12 -m pip install boost-histogram
- name: Build library
run: |
@@ -29,3 +34,8 @@ jobs:
- name: C++ unit tests
working-directory: ${{gitea.workspace}}/build
run: ctest
- name: Python unit tests
working-directory: ${{gitea.workspace}}/build
run: |
python3.12 -m pytest ${{gitea.workspace}}/python/tests/

View File

@@ -55,6 +55,11 @@ jobs:
working-directory: ${{github.workspace}}/build
run: ctest -C ${{env.BUILD_TYPE}} -j4
- name: Python unit tests
working-directory: ${{github.workspace}}/build
run: python -m pytest ${{github.workspace}}/python/tests/
- name: Upload static files as artifact
if: matrix.platform == 'ubuntu-latest'
id: deployment

View File

@@ -15,4 +15,7 @@ dependencies:
- numpy
- matplotlib
- nlohmann_json
- pytest
- pytest-check
- boost-histogram

View File

@@ -193,6 +193,7 @@ Interpolator::interpolate(const ClusterVector<ClusterType> &clusters) const {
std::vector<Photon> photons;
photons.reserve(clusters.size());
size_t cluster_index{};
for (const ClusterType &cluster : clusters) {
auto eta = EtaFunction(cluster);
@@ -202,6 +203,14 @@ Interpolator::interpolate(const ClusterVector<ClusterType> &clusters) const {
photon.y = cluster.y;
photon.energy = static_cast<decltype(photon.energy)>(eta.sum);
try {
// check if eta values are within bounds
transform_eta_values(eta);
} catch (const std::runtime_error &e) {
throw std::runtime_error(
fmt::format("{} for cluster: {}", e.what(), cluster_index));
}
auto uniform_coordinates = transform_eta_values(eta);
if (EtaFunction == &calculate_eta2<typename ClusterType::value_type,
@@ -245,6 +254,8 @@ Interpolator::interpolate(const ClusterVector<ClusterType> &clusters) const {
photon.y += uniform_coordinates.y;
}
++cluster_index;
photons.push_back(photon);
}

View File

@@ -6,7 +6,7 @@ import numpy as np
@pytest.mark.withdata
def test_read_rawfile_with_roi(test_data_path):
with RawFile(test_data_path / "raw/SingleChipROI/Data_master_0.json") as f:
with RawFile(test_data_path / "raw/ROITestData/SingleChipROI/Data_master_0.json") as f:
headers, frames = f.read()
assert headers.size == 10100

View File

@@ -39,6 +39,7 @@ def load_data(test_data_path):
return cv, ground_truths
@pytest.mark.withdata
@pytest.mark.skip(reason="Simple sanity test skips ground truth does not coincide with center pixel")
def test_eta2_interpolation(load_data, check):
"""Test eta2 interpolation on simulated data"""
@@ -72,10 +73,11 @@ def test_eta2_interpolation(load_data, check):
"""
# check within photon hit pixel for all
# TODO: fails as ground truth not in center pixel!!
with check:
assert np.allclose(interpolated_photons["x"], ground_truths[:, 0], atol=5e-1)
assert np.allclose(np.floor(interpolated_photons["x"]), np.floor(ground_truths[:, 0]), atol=0.0)
with check:
assert np.allclose(interpolated_photons["y"], ground_truths[:, 1], atol=5e-1)
assert np.allclose(np.floor(interpolated_photons["y"]), np.floor(ground_truths[:, 1]), atol=0.0)
# check mean and std of residuals
with check:
@@ -88,6 +90,7 @@ def test_eta2_interpolation(load_data, check):
assert residuals_interpolated_y.std() <= 0.05
@pytest.mark.withdata
@pytest.mark.skip(reason="Simple sanity test skips ground truth does not coincide with center pixel")
def test_eta2_interpolation_rosenblatt(load_data, check):
"""Test eta2 interpolation on simulated data using Rosenblatt transform"""
@@ -123,10 +126,12 @@ def test_eta2_interpolation_rosenblatt(load_data, check):
"""
# check within photon hit pixel for all
# TODO: fails as ground truth not in center pixel!!
with check:
assert np.allclose(interpolated_photons["x"], ground_truths[:, 0], atol=5e-1)
assert np.allclose(np.floor(interpolated_photons["x"]), np.floor(ground_truths[:, 0]), atol=0.0)
with check:
assert np.allclose(interpolated_photons["y"], ground_truths[:, 1], atol=5e-1)
assert np.allclose(np.floor(interpolated_photons["y"]), np.floor(ground_truths[:, 1]), atol=0.0)
# check mean and std of residuals
with check:
@@ -140,6 +145,7 @@ def test_eta2_interpolation_rosenblatt(load_data, check):
@pytest.mark.withdata
@pytest.mark.skip(reason="Simple sanity test skips ground truth does not coincide with center pixel")
def test_cross_eta_interpolation(load_data, check):
"""Test cross eta interpolation on simulated data"""
@@ -173,11 +179,11 @@ def test_cross_eta_interpolation(load_data, check):
"""
# check within photon hit pixel for all
# TODO: fails as eta_x = 0, eta_y = 0 is not leading to offset (0.5,0.5)
# TODO: fails as ground truth not in center pixel!!
with check:
assert np.allclose(interpolated_photons["x"], ground_truths[:, 0], atol=5e-1)
assert np.allclose(np.floor(interpolated_photons["x"]), np.floor(ground_truths[:, 0]), atol=0.0)
with check:
assert np.allclose(interpolated_photons["y"], ground_truths[:, 1], atol=5e-1)
assert np.allclose(np.floor(interpolated_photons["y"]), np.floor(ground_truths[:, 1]), atol=0.0)
# check mean and std of residuals
with check:
@@ -190,13 +196,14 @@ def test_cross_eta_interpolation(load_data, check):
assert residuals_interpolated_y.std() <= 0.05
@pytest.mark.withdata
@pytest.mark.skip(reason="Simple sanity test skips ground truth does not coincide with center pixel")
def test_eta3_interpolation(load_data, check):
"""Test eta3 interpolation on simulated data"""
cv, ground_truths = load_data
num_bins = 201
eta_distribution = calculate_eta_distribution(cv, calculate_eta3, edges_x=[-0.5,0.5], edges_y=[-0.5,0.5], nbins=num_bins)
eta_distribution = calculate_eta_distribution(cv, calculate_eta3, edges_x=[-0.6,0.6], edges_y=[-0.6,0.6], nbins=num_bins)
interpolator = Interpolator(eta_distribution, eta_distribution.axes[0].edges, eta_distribution.axes[1].edges, eta_distribution.axes[2].edges)
@@ -223,11 +230,11 @@ def test_eta3_interpolation(load_data, check):
"""
# check within photon hit pixel for all
# TODO: fails as eta_x = 0, eta_y = 0 is not leading to offset (0.5,0.5)
# TODO: fails as ground truth not in center pixel!!
with check:
assert np.allclose(interpolated_photons["x"], ground_truths[:, 0], atol=5e-1)
assert np.allclose(np.floor(interpolated_photons["x"]), np.floor(ground_truths[:, 0]), atol=0.0)
with check:
assert np.allclose(interpolated_photons["y"], ground_truths[:, 1], atol=5e-1)
assert np.allclose(np.floor(interpolated_photons["y"]), np.floor(ground_truths[:, 1]), atol=0.0)
# check mean and std of residuals
with check:
@@ -240,6 +247,7 @@ def test_eta3_interpolation(load_data, check):
assert residuals_interpolated_y.std() <= 0.05
@pytest.mark.withdata
@pytest.mark.skip(reason="Simple sanity test skips ground truth does not coincide with center pixel")
def test_full_eta2_interpolation(load_data, check):
"""Test full eta2 interpolation on simulated data"""
@@ -273,10 +281,11 @@ def test_full_eta2_interpolation(load_data, check):
"""
# check within photon hit pixel for all
# TODO: fails as ground truth not in center pixel!!
with check:
assert np.allclose(interpolated_photons["x"], ground_truths[:, 0], atol=5e-1)
assert np.allclose(np.floor(interpolated_photons["x"]), np.floor(ground_truths[:, 0]), atol=0.0)
with check:
assert np.allclose(interpolated_photons["y"], ground_truths[:, 1], atol=5e-1)
assert np.allclose(np.floor(interpolated_photons["y"]), np.floor(ground_truths[:, 1]), atol=0.0)
# check mean and std of residuals
with check:

View File

@@ -292,7 +292,8 @@ TEST_CASE("check find_geometry", "[.with-data]") {
TEST_CASE("Open multi module file with ROI", "[.with-data]") {
auto fpath = test_data_path() / "raw/ROITestData/SingleChipROI/Data_master_0.json";
auto fpath =
test_data_path() / "raw/ROITestData/SingleChipROI/Data_master_0.json";
REQUIRE(std::filesystem::exists(fpath));
RawFile f(fpath, "r");

View File

@@ -229,7 +229,7 @@ TEST_CASE("Parse a master file in .raw format", "[.integration]") {
}
TEST_CASE("Parse a master file in new .json format",
"[.integration][.width-data]") {
"[.integration][.with-data]") {
auto file_path =
test_data_path() / "raw" / "newmythen03" / "run_87_master_0.json";
@@ -552,7 +552,8 @@ TEST_CASE("Parse a CTB file from stream"){
REQUIRE(f.quad() == 0);
REQUIRE(f.frame_discard_policy() == FrameDiscardPolicy::NoDiscard);
REQUIRE(f.frame_padding() == 1);
REQUIRE(f.total_frames_expected() == 1); //This is Total Frames in the master file
REQUIRE(f.total_frames_expected() ==
1); // This is Total Frames in the master file
REQUIRE(f.exptime() == std::chrono::milliseconds(250));
REQUIRE(f.period() == std::chrono::milliseconds(10));
REQUIRE(f.analog_samples() == std::nullopt); // Analog Flag is 0
@@ -637,7 +638,8 @@ TEST_CASE("Parse v8.0 MYTHEN3 from stream"){
REQUIRE(f.quad() == 0);
REQUIRE(f.frame_discard_policy() == FrameDiscardPolicy::NoDiscard);
REQUIRE(f.frame_padding() == 1);
REQUIRE(f.total_frames_expected() == 1); //This is Total Frames in the master file
REQUIRE(f.total_frames_expected() ==
1); // This is Total Frames in the master file
REQUIRE(f.counter_mask() == 4);
REQUIRE(f.bitdepth() == 32);
@@ -721,7 +723,8 @@ TEST_CASE("Parse a v7.1 Mythen3 from stream"){
REQUIRE(f.quad() == 0);
REQUIRE(f.frame_discard_policy() == FrameDiscardPolicy::NoDiscard);
REQUIRE(f.frame_padding() == 1);
REQUIRE(f.total_frames_expected() == 1); //This is Total Frames in the master file
REQUIRE(f.total_frames_expected() ==
1); // This is Total Frames in the master file
REQUIRE(f.counter_mask() == 0x7);
REQUIRE(f.bitdepth() == 32);