Skip to content

Commit

Permalink
feat: Add python based tests (#975)
Browse files Browse the repository at this point in the history
A number of unit tests based on the ``pytest`` library are shipped with the
repository. They are located under ``$REPO_ROOT/Examples/Python/tests``, and
intend to cover the public API of the python bindings. A set of tests also
executed the standalone example scripts.

To run these python based tests, ``pytest`` needs to be installed. It can be
installed via ``pip install pytest``. It is recommended to install this package
in [virtual environment](https://realpython.com/python-virtual-environments-a-primer/). You can then simply run ``pytest`` from the
repository root.
  • Loading branch information
paulgessinger committed Sep 30, 2021
1 parent 162bad9 commit 51b62f8
Show file tree
Hide file tree
Showing 14 changed files with 1,638 additions and 1 deletion.
10 changes: 10 additions & 0 deletions .github/workflows/builds.yml
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,16 @@ jobs:
- name: Examples
if: contains(matrix.image, 'ubuntu')
run: ${SETUP} && ./CI/run_examples.sh
- name: Python level tests
if: contains(matrix.image, 'ubuntu')
shell: bash
run: >
${SETUP}
&& source /usr/local/bin/thisroot.sh
&& source /usr/local/bin/thisdd4hep_only.sh
&& source build/python/setup.sh
&& pip3 install pytest
&& pytest -rFs
- name: Install
run: ${SETUP} && cmake --build build -- install
- uses: actions/upload-artifact@v2
Expand Down
155 changes: 155 additions & 0 deletions Examples/Python/tests/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
from pathlib import Path
import sys
import os
import tempfile


sys.path += [
str(Path(__file__).parent.parent.parent.parent / "Examples/Scripts/Python/"),
str(Path(__file__).parent),
]

import helpers

import pytest

import acts
import acts.examples

u = acts.UnitConstants


def kwargsConstructor(cls, *args, **kwargs):
return cls(*args, **kwargs)


def configKwConstructor(cls, *args, **kwargs):
assert hasattr(cls, "Config")
_kwargs = {}
if "level" in kwargs:
_kwargs["level"] = kwargs.pop("level")
config = cls.Config()
for k, v in kwargs.items():
setattr(config, k, v)
return cls(*args, config=config, **_kwargs)


def configPosConstructor(cls, *args, **kwargs):
assert hasattr(cls, "Config")
_kwargs = {}
if "level" in kwargs:
_kwargs["level"] = kwargs.pop("level")
config = cls.Config()
for k, v in kwargs.items():
setattr(config, k, v)

return cls(config, *args, **_kwargs)


@pytest.fixture(params=[configPosConstructor, configKwConstructor, kwargsConstructor])
def conf_const(request):
return request.param


@pytest.fixture
def rng():
return acts.examples.RandomNumbers(seed=42)


@pytest.fixture
def basic_prop_seq(rng):
def _basic_prop_seq_factory(geo, s=None):
if s is None:
s = acts.examples.Sequencer(events=10, numThreads=1)

nav = acts.Navigator(trackingGeometry=geo)
stepper = acts.StraightLineStepper()

prop = acts.examples.ConcretePropagator(acts.Propagator(stepper, nav))
alg = acts.examples.PropagationAlgorithm(
propagatorImpl=prop,
level=acts.logging.INFO,
randomNumberSvc=rng,
ntests=10,
sterileLogger=False,
propagationStepCollection="propagation-steps",
)
s.addAlgorithm(alg)
return s, alg

return _basic_prop_seq_factory


@pytest.fixture
def trk_geo(request):
detector, geo, contextDecorators = acts.examples.GenericDetector.create()
yield geo


@pytest.fixture
def ptcl_gun(rng):
def _factory(s):
evGen = acts.examples.EventGenerator(
level=acts.logging.INFO,
generators=[
acts.examples.EventGenerator.Generator(
multiplicity=acts.examples.FixedMultiplicityGenerator(n=2),
vertex=acts.examples.GaussianVertexGenerator(
stddev=acts.Vector4(0, 0, 0, 0), mean=acts.Vector4(0, 0, 0, 0)
),
particles=acts.examples.ParametricParticleGenerator(
p=(1 * u.GeV, 10 * u.GeV),
eta=(-2, 2),
phi=(0, 360 * u.degree),
randomizeCharge=True,
numParticles=2,
),
)
],
outputParticles="particles_input",
randomNumbers=rng,
)

s.addReader(evGen)

return evGen

return _factory


@pytest.fixture
def fatras(ptcl_gun, trk_geo, rng):
def _factory(s):
evGen = ptcl_gun(s)

field = acts.ConstantBField(acts.Vector3(0, 0, 2 * acts.UnitConstants.T))
simAlg = acts.examples.FatrasSimulation(
level=acts.logging.INFO,
inputParticles=evGen.config.outputParticles,
outputParticlesInitial="particles_initial",
outputParticlesFinal="particles_final",
outputSimHits="simhits",
randomNumbers=rng,
trackingGeometry=trk_geo,
magneticField=field,
generateHitsOnSensitive=True,
)

s.addAlgorithm(simAlg)

# Digitization
digiCfg = acts.examples.DigitizationConfig(
acts.examples.readDigiConfigFromJson(
"Examples/Algorithms/Digitization/share/default-smearing-config-generic.json"
),
trackingGeometry=trk_geo,
randomNumbers=rng,
inputSimHits=simAlg.config.outputSimHits,
)
digiAlg = acts.examples.DigitizationAlgorithm(digiCfg, acts.logging.INFO)

s.addAlgorithm(digiAlg)

return evGen, simAlg, digiAlg

return _factory
60 changes: 60 additions & 0 deletions Examples/Python/tests/helpers/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import os
from typing import List, Union

from acts.examples import BareAlgorithm

try:
import ROOT

rootEnabled = True
except ImportError:
rootEnabled = False

if "ROOTSYS" in os.environ: # ROOT seems to be set up, but no PyROOT
import warnings

warnings.warn(
"ROOT likely built without/with incompatible PyROOT. Skipping tests that need ROOT"
)

dd4hepEnabled = "DD4hep_DIR" in os.environ

if dd4hepEnabled:
try:
import acts.examples.dd4hep
except ImportError:
dd4hepEnabled = False

try:
import acts.examples.hepmc3

hepmc3Enabled = True
except ImportError:
hepmc3Enabled = False

isCI = os.environ.get("CI", "false") == "true"


class AssertCollectionExistsAlg(BareAlgorithm):
events_seen = 0
collections: List[str]

def __init__(
self,
collections: Union[List[str], str],
name="check_alg",
level=acts.logging.INFO,
*args,
**kwargs,
):
if isinstance(collections, str):
self.collections = [collections]
else:
self.collections = collections
BareAlgorithm.__init__(self, name=name, level=level, *args, **kwargs)

def execute(self, ctx):
for collection in self.collections:
assert ctx.eventStore.exists(collection), f"{collection} does not exist"
self.events_seen += 1
return acts.examples.ProcessCode.SUCCESS
83 changes: 83 additions & 0 deletions Examples/Python/tests/test_algorithms.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import pytest

import acts

from acts.examples import (
TutorialVertexFinderAlgorithm,
AdaptiveMultiVertexFinderAlgorithm,
VertexFitterAlgorithm,
IterativeVertexFinderAlgorithm,
SpacePointMaker,
TrackFindingAlgorithm,
SeedingAlgorithm,
TrackParamsEstimationAlgorithm,
EventGenerator,
FatrasSimulation,
MaterialMapping,
TruthSeedSelector,
TruthTrackFinder,
ParticleSelector,
TruthVertexFinder,
ParticleSmearing,
TrackSelector,
TrackFittingAlgorithm,
SurfaceSortingAlgorithm,
ParticlesPrinter,
HitsPrinter,
TrackParametersPrinter,
PropagationAlgorithm,
DigitizationAlgorithm,
SmearingAlgorithm,
PlanarSteppingAlgorithm,
)


from helpers import hepmc3Enabled


@pytest.mark.parametrize(
"alg",
[
TutorialVertexFinderAlgorithm,
AdaptiveMultiVertexFinderAlgorithm,
VertexFitterAlgorithm,
IterativeVertexFinderAlgorithm,
SpacePointMaker,
TrackFindingAlgorithm,
SeedingAlgorithm,
TrackParamsEstimationAlgorithm,
EventGenerator,
FatrasSimulation,
MaterialMapping,
TruthSeedSelector,
TruthTrackFinder,
ParticleSelector,
TruthVertexFinder,
ParticleSmearing,
TrackSelector,
TrackFittingAlgorithm,
SurfaceSortingAlgorithm,
ParticlesPrinter,
HitsPrinter,
TrackParametersPrinter,
PropagationAlgorithm,
# GeantinoRecording,
PlanarSteppingAlgorithm,
# EventRecording,
],
)
def test_algorithm_interface(alg):
assert hasattr(alg, "Config")


@pytest.mark.skipif(not hepmc3Enabled, reason="HepMC3 not set up")
def test_hepmc_algorithms():
from acts.examples.hepmc3 import HepMCProcessExtractor

assert hasattr(HepMCProcessExtractor, "Config")


def test_special_algorithm_interfaces():
# just assert they exists
assert DigitizationAlgorithm
assert SmearingAlgorithm
60 changes: 60 additions & 0 deletions Examples/Python/tests/test_base.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import pytest

import acts

import acts.examples


def test_logging():
for l in ("VERBOSE", "DEBUG", "INFO", "WARNING", "ERROR", "FATAL"):
assert hasattr(acts.logging, l)
assert hasattr(acts.logging.Level, l)


def test_pgd_particle():
assert len(acts.PdgParticle.__members__) == 16


def test_algebra():
v3 = acts.Vector3(1, 2, 3)
with pytest.raises(TypeError):
acts.Vector3(1, 2, 3, 4)
with pytest.raises(TypeError):
acts.Vector3(1, 2)

v3 = acts.Vector3([1, 2, 3])
with pytest.raises(TypeError):
acts.Vector3([1, 2, 3, 4])
with pytest.raises(TypeError):
acts.Vector3([1, 2])
with pytest.raises(TypeError):
acts.Vector3()

v4 = acts.Vector4(1, 2, 3, 4)
with pytest.raises(TypeError):
v4 = acts.Vector4(1, 2, 3)
v4 = acts.Vector4([1, 2, 3, 4])
with pytest.raises(TypeError):
acts.Vector4([1, 2, 3])
with pytest.raises(TypeError):
acts.Vector4()


def test_empty_sequencer(conf_const):
s = acts.examples.Sequencer()
with pytest.raises(RuntimeError):
s.run()

s = conf_const(acts.examples.Sequencer, events=1)
s.run()


def test_random_number():
rnd = acts.examples.RandomNumbers(seed=42)


def test_constructors():
s1 = acts.examples.Sequencer()
print(s1)
s2 = acts.examples.Sequencer()
print(s2)

0 comments on commit 51b62f8

Please sign in to comment.