Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
41 changes: 40 additions & 1 deletion .github/workflows/python_wheel_build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ on:
schedule:
- cron: '01 1 * * *'
pull_request:
types: [labeled]
types: [opened, synchronize, reopened, labeled]

concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.run_id }}
Expand All @@ -26,6 +26,7 @@ jobs:
contains(github.event.pull_request.labels.*.name, 'build-python-wheels')
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
target: [cp39-manylinux_x86_64, cp310-manylinux_x86_64, cp311-manylinux_x86_64, cp312-manylinux_x86_64, cp313-manylinux_x86_64]
name: ${{ matrix.target }}
Expand All @@ -35,6 +36,44 @@ jobs:
with:
build-tag: ${{ matrix.target }}

test-wheels:
needs: build-wheels
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
name: test-wheel-cp${{ matrix.python-version }}
steps:
- uses: actions/checkout@v4

- name: Download produced wheels
uses: actions/download-artifact@v4
with:
path: wheels
merge-multiple: true

- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}

- name: Install produced wheel
run: |
ls -R wheels
PY_VER=$(python -c "import sys; print(f'cp{sys.version_info.major}{sys.version_info.minor}')")
WHEEL=$(ls wheels/*${PY_VER}*.whl | head -n 1)
echo "Python version: ${PY_VER}, installing wheel: ${WHEEL}"
pip install "$WHEEL"

- name: Install tutorials dependencies
run: |
python -m pip install --no-cache-dir -r test/wheels/requirements-ci.txt

- name: Run tutorials
run: |
pytest -vv --verbosity="4" -rF test/wheels

create-and-upload-wheel-registry:
if: github.event_name != 'pull_request' # The secrets are not available in PR
needs: build-wheels
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ def run(self):
"-Dbuiltin_nlohmannjson=ON -Dbuiltin_tbb=ON -Dbuiltin_xrootd=ON " # builtins
"-Dbuiltin_lz4=ON -Dbuiltin_lzma=ON -Dbuiltin_zstd=ON -Dbuiltin_xxhash=ON " # builtins
"-Dpyroot=ON -Ddataframe=ON -Dxrootd=ON -Dssl=ON -Dimt=ON "
"-Droofit=ON "
"-Droofit=ON -Dmathmore=ON -Dbuiltin_fftw3=ON -Dbuiltin_gsl=ON "
# Next 4 paths represent the structure of the target binaries/headers/libs
# as the target installation directory of the Python environment would expect
f"-DCMAKE_INSTALL_BINDIR={ROOT_BUILD_INTERNAL_DIRNAME}/ROOT/bin "
Expand Down
26 changes: 26 additions & 0 deletions test/wheels/requirements-ci.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# ROOT requirements for third-party Python packages for Python wheels tests

# PyROOT: Interoperability with numpy arrays
numpy
pandas

# PyROOT: ROOT.Numba.Declare decorator
numba
cffi

# Distributed RDataFrame
pyspark # Spark backend
dask>=2022.08.1 # Dask backend
distributed>=2022.08.1 # Dask backend

# Unified Histogram Interface (UHI)
uhi
matplotlib
mplhep

# For testing
pytest

# Other
scikit-learn
xgboost
116 changes: 116 additions & 0 deletions test/wheels/test_tutorials.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
import os
import pathlib
import shutil
import signal
import subprocess
import sys

import pytest
import ROOT

ROOT.gROOT.SetBatch(True)

tutorial_dir = pathlib.Path(str(ROOT.gROOT.GetTutorialDir()))

subdirs = ["analysis/dataframe", "analysis/tree", "hist", "io/ntuple", "roofit/roofit"]

SKIP_TUTORIALS = {
"ntpl004_dimuon.C", # requires reading remote data via HTTP
"ntpl008_import.C", # requires reading remote data via HTTP
"ntpl011_global_temperatures.C", # requires reading remote data via HTTP
"distrdf004_dask_lxbatch.py", # only works on lxplus
"_SQlite", # requires SQLite, not supported yet in ROOT wheels
"h1analysisProxy.C", # helper macro, not meant to run standalone
"hist001_RHist_basics.C", # required RHist, not supported in ROOT wheels
"hist002_RHist_weighted.C", # required RHist, not supported in ROOT wheels
"rf618_mixture_models.py", # fails on CI, to investigate
"rf615_simulation_based_inference.py", # fails on CI, to investigate
}

# ----------------------
# Python tutorials tests
# ----------------------
py_tutorials = []
for sub in subdirs:
sub_path = tutorial_dir / sub
for f in sub_path.rglob("*.py"):
if any(skip in f.name for skip in SKIP_TUTORIALS):
print("Skipping Python tutorial:", f)
continue
py_tutorials.append(f)

py_tutorials = sorted(py_tutorials, key=lambda p: p.name)


def test_tutorials_are_detected():
assert len(py_tutorials) > 0


@pytest.mark.parametrize("tutorial", py_tutorials, ids=lambda p: p.name)
def test_tutorial(tutorial):
env = dict(**os.environ)
# force matplotlib to use a non-GUI backend
env["MPLBACKEND"] = "Agg"
print("Test env:", env)
try:
result = subprocess.run(
[sys.executable, str(tutorial)],
check=True,
env=env,
timeout=60,
capture_output=True,
text=True,
)
print("Test stderr:", result.stderr)

except subprocess.TimeoutExpired:
pytest.skip(f"Tutorial {tutorial} timed out")

except subprocess.CalledProcessError as e:
# read stderr to see if EOFError occurred
if "EOFError" in e.stderr:
pytest.skip(f"Skipping {tutorial.name} (requires user input)")
raise


# ----------------------
# C++ tutorials tests
# ----------------------
cpp_tutorials = []
for sub in subdirs:
sub_path = tutorial_dir / sub
for f in sub_path.rglob("*.C"):
if any(skip in f.name for skip in SKIP_TUTORIALS):
print("Skipping C++ tutorial:", f)
continue
cpp_tutorials.append(f)

cpp_tutorials = sorted(cpp_tutorials, key=lambda p: p.name)


def test_cpp_tutorials_are_detected():
assert len(cpp_tutorials) > 0


@pytest.mark.parametrize("tutorial", cpp_tutorials, ids=lambda p: p.name)
def test_cpp_tutorial(tutorial):
try:
root_exe = shutil.which("root")
result = subprocess.run(
[root_exe, "-b", "-q", str(tutorial)],
check=True,
timeout=60,
capture_output=True,
text=True,
)
print("Test stderr:", result.stderr)

except subprocess.TimeoutExpired:
pytest.skip(f"Tutorial {tutorial} timed out")

except subprocess.CalledProcessError as e:
if e.returncode == -signal.SIGILL or e.returncode == 132:
pytest.fail(f"Failing {tutorial.name} (illegal instruction on this platform)")
elif "EOFError" in e.stderr:
pytest.skip(f"Skipping {tutorial.name} (requires user input)")
raise
7 changes: 0 additions & 7 deletions tutorials/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -551,13 +551,6 @@ set(returncode_1 math/fit/fit2a.C
visualisation/graphics/tmathtext.C visualisation/graphics/tmathtext2.C
visualisation/graphs/gr106_exclusiongraph.C
visualisation/graphs/gr016_struct.C
hist/hist102_TH2_contour_list.C
hist/hist006_TH1_bar_charts.C
hist/hist037_TH2Poly_boxes.C
hist/hist060_TH1_stats.C
hist/hist014_TH1_cumulative.C
hist/hist004_TH1_labels.C
hist/hist036_TH2_labels.C
analysis/tree/h1analysis.C
math/chi2test.C
math/r/SimpleFitting.C)
Expand Down
4 changes: 1 addition & 3 deletions tutorials/hist/hist004_TH1_labels.C
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
/// \date November 2024
/// \author Rene Brun

TCanvas *hist004_TH1_labels()
void hist004_TH1_labels()
{
// Create the histogram
const std::array people{"Jean", "Pierre", "Marie", "Odile", "Sebastien", "Fons", "Rene",
Expand Down Expand Up @@ -56,6 +56,4 @@ TCanvas *hist004_TH1_labels()
pt->AddText(" \">\" to sort by decreasing values");
pt->AddText(" \"<\" to sort by increasing values");
pt->Draw();

return c1;
}
8 changes: 3 additions & 5 deletions tutorials/hist/hist006_TH1_bar_charts.C
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
/// \date November 2024
/// \author Rene Brun

TCanvas *hist006_TH1_bar_charts()
void hist006_TH1_bar_charts()
{
// Try to open first the file cernstaff.root in tutorials/io/tree directory
TString filedir = gROOT->GetTutorialDir();
Expand All @@ -28,14 +28,14 @@ TCanvas *hist006_TH1_bar_charts()
auto file = std::unique_ptr<TFile>(TFile::Open(filename, "READ"));
if (!file) {
Error("hist006_TH1_bar_charts", "file cernstaff.root not found");
return nullptr;
return;
}

// Retrieve the TTree named "T" contained in the file
auto tree = file->Get<TTree>("T");
if (!tree) {
Error("hist006_TH1_bar_charts", "Tree T is not present in file %s", file->GetName());
return nullptr;
return;
}
tree->SetFillColor(45);

Expand Down Expand Up @@ -88,6 +88,4 @@ TCanvas *hist006_TH1_bar_charts()
legend->Draw();

c1->cd();

return c1;
}
4 changes: 1 addition & 3 deletions tutorials/hist/hist014_TH1_cumulative.C
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
#include "TCanvas.h"
#include "TRandom.h"

TCanvas *hist014_TH1_cumulative()
void hist014_TH1_cumulative()
{
TH1 *h = new TH1D("h", "h", 100, -5., 5.);
gRandom->SetSeed();
Expand All @@ -37,6 +37,4 @@ TCanvas *hist014_TH1_cumulative()
c->cd(2);
hc->Draw();
c->Update();

return c;
}
3 changes: 1 addition & 2 deletions tutorials/hist/hist036_TH2_labels.C
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
/// \date July 2016
/// \author Rene Brun

TCanvas *hist036_TH2_labels()
void hist036_TH2_labels()
{
const Int_t nx = 12;
const Int_t ny = 20;
Expand Down Expand Up @@ -44,5 +44,4 @@ TCanvas *hist036_TH2_labels()
pt->AddText(" \">\" to sort by decreasing values");
pt->AddText(" \"<\" to sort by increasing values");
pt->Draw();
return c1;
}
3 changes: 1 addition & 2 deletions tutorials/hist/hist037_TH2Poly_boxes.C
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
/// \date August 2016
/// \author Olivier Couet

TCanvas *hist037_TH2Poly_boxes()
void hist037_TH2Poly_boxes()
{
TCanvas *ch2p2 = new TCanvas("ch2p2", "ch2p2", 600, 400);
gStyle->SetPalette(57);
Expand Down Expand Up @@ -44,5 +44,4 @@ TCanvas *hist037_TH2Poly_boxes()
}

h2p->Draw("COLZ");
return ch2p2;
}
3 changes: 1 addition & 2 deletions tutorials/hist/hist060_TH1_stats.C
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
/// \date August 2016
/// \author Olivier Couet

TCanvas *hist060_TH1_stats()
void hist060_TH1_stats()
{
// Create and plot a test histogram with stats
TCanvas *se = new TCanvas;
Expand Down Expand Up @@ -44,5 +44,4 @@ TCanvas *hist060_TH1_stats()
h->SetStats(0);

se->Modified();
return se;
}
5 changes: 2 additions & 3 deletions tutorials/hist/hist102_TH2_contour_list.C
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@

Double_t SawTooth(Double_t x, Double_t WaveLen);

TCanvas *hist102_TH2_contour_list()
void hist102_TH2_contour_list()
{

const Double_t PI = TMath::Pi();
Expand Down Expand Up @@ -91,7 +91,7 @@ TCanvas *hist102_TH2_contour_list()

if (!conts) {
printf("*** No Contours Were Extracted!\n");
return nullptr;
return;
}

TList *contLevel = nullptr;
Expand Down Expand Up @@ -157,7 +157,6 @@ TCanvas *hist102_TH2_contour_list()
printf("\n\n\tExtracted %d Contours and %d Graphs \n", TotalConts, nGraphs);
gStyle->SetTitleW(0.);
gStyle->SetTitleH(0.);
return c1;
}

Double_t SawTooth(Double_t x, Double_t WaveLen)
Expand Down
Loading