From 40c3d907a89c1e50d1acd7f24a4d44b53361bfff Mon Sep 17 00:00:00 2001 From: Thomas Kittelmann Date: Sat, 7 Nov 2026 16:48:40 +0000 Subject: [PATCH] Test expressions across modules, fix two error messages New test G4MCPLTests/testexpressions: valid expressions of various value types (bool, integer, floating point, string) and invalid ones (syntax errors, unknown variables, domain errors) given to MCPLGen, MCPLWriter (--mcpl), the heat map writer (--heatmap) and the sb_mcplextra_filterfile and sb_mcplextra_filterview commands. The expressions are parsed in the ExprParser libraries, but used in Python modules, so this tests that the ExprParser exceptions can be caught across modules, also on Intel macOS (where type information is compared by address), rather than crashing. Also fix the error message for invalid quantity expressions of the heat map writer (it showed the filter expression), and a typo in the error message for invalid user flag expressions of MCPLWriter. Assisted-by: Claude --- .../data/pkgs/G4/G4HeatMap/pymod__impl/mod.cc | 2 +- .../G4MCPLPlugins/pymod__MCPLWriter/mod.cc | 2 +- .../pkgs_val/UnitTests/G4MCPLTests/pkg.info | 2 +- .../G4MCPLTests/scripts/testexpressions | 102 ++++++++++++++++++ .../G4MCPLTests/scripts/testexpressions.log | 74 +++++++++++++ 5 files changed, 179 insertions(+), 3 deletions(-) create mode 100755 src/simplebuild_dgcode/data/pkgs_val/UnitTests/G4MCPLTests/scripts/testexpressions create mode 100644 src/simplebuild_dgcode/data/pkgs_val/UnitTests/G4MCPLTests/scripts/testexpressions.log diff --git a/src/simplebuild_dgcode/data/pkgs/G4/G4HeatMap/pymod__impl/mod.cc b/src/simplebuild_dgcode/data/pkgs/G4/G4HeatMap/pymod__impl/mod.cc index 40fd126..1ff559e 100644 --- a/src/simplebuild_dgcode/data/pkgs/G4/G4HeatMap/pymod__impl/mod.cc +++ b/src/simplebuild_dgcode/data/pkgs/G4/G4HeatMap/pymod__impl/mod.cc @@ -288,7 +288,7 @@ namespace DMWriter { try { m_eval_quantity = m_expr_builder.createEvaluator(m_expr_quantity); } catch (ExprParser::InputError& e) { - printf("\nHeatMapWriter ERROR: Invalid quantity expression \"%s\"\n",m_expr_filter.c_str()); + printf("\nHeatMapWriter ERROR: Invalid quantity expression \"%s\"\n",m_expr_quantity.c_str()); printf("HeatMapWriter ERROR: %s : %s\n\n",e.epType(),e.epWhat()); throw; } diff --git a/src/simplebuild_dgcode/data/pkgs/MCPL/G4MCPLPlugins/pymod__MCPLWriter/mod.cc b/src/simplebuild_dgcode/data/pkgs/MCPL/G4MCPLPlugins/pymod__MCPLWriter/mod.cc index af7dc4f..14f3c58 100644 --- a/src/simplebuild_dgcode/data/pkgs/MCPL/G4MCPLPlugins/pymod__MCPLWriter/mod.cc +++ b/src/simplebuild_dgcode/data/pkgs/MCPL/G4MCPLPlugins/pymod__MCPLWriter/mod.cc @@ -533,7 +533,7 @@ namespace G4MCPLWriter { if (!m_expr_flags.empty()) m_eval_flags = m_expr_builder->createEvaluator(m_expr_flags); } catch (EP::InputError& e) { - printf("\nMCPLWriter ERROR: Invalid expression for user-flas \"%s\"\n",m_expr_flags.c_str()); + printf("\nMCPLWriter ERROR: Invalid expression for user flags \"%s\"\n",m_expr_flags.c_str()); printf("MCPLWriter ERROR: %s : %s\n\n",e.epType(),e.epWhat()); if (m_sd) m_sd->abort(); diff --git a/src/simplebuild_dgcode/data/pkgs_val/UnitTests/G4MCPLTests/pkg.info b/src/simplebuild_dgcode/data/pkgs_val/UnitTests/G4MCPLTests/pkg.info index 9971f72..ae03ce2 100644 --- a/src/simplebuild_dgcode/data/pkgs_val/UnitTests/G4MCPLTests/pkg.info +++ b/src/simplebuild_dgcode/data/pkgs_val/UnitTests/G4MCPLTests/pkg.info @@ -1,4 +1,4 @@ -package(USEPKG G4Launcher G4MCPLPlugins MCPLExtra MCPLTestData G4StdGeometries G4StdGenerators USEEXT MCPL) +package(USEPKG G4Launcher G4MCPLPlugins G4HeatMap Mesh3D MCPLExtra MCPLTestData G4StdGeometries G4StdGenerators USEEXT MCPL) ########################################################## diff --git a/src/simplebuild_dgcode/data/pkgs_val/UnitTests/G4MCPLTests/scripts/testexpressions b/src/simplebuild_dgcode/data/pkgs_val/UnitTests/G4MCPLTests/scripts/testexpressions new file mode 100755 index 0000000..91fdbf8 --- /dev/null +++ b/src/simplebuild_dgcode/data/pkgs_val/UnitTests/G4MCPLTests/scripts/testexpressions @@ -0,0 +1,102 @@ +#!/usr/bin/env python3 + +#Tests of expressions (G4ExprParser, MCPLExprParser) given to MCPLGen, +#MCPLWriter (--mcpl), G4HeatMap (--heatmap) and the sb_mcplextra_filterfile +#and sb_mcplextra_filterview commands. +# +#The expressions are parsed and evaluated in the ExprParser libraries, but used +#(and errors are caught) in Python modules. This tests that this works across +#modules, also on platforms which compare type information by address (e.g. +#Intel macOS): +# +# * Valid expressions of various value types (bool, integer, floating point, +# string) work. +# * Invalid expressions (syntax errors, unknown variables, domain errors) give +# a clean error message (the ExprParser exception is caught by its base class +# in another module), rather than a crash. + +import subprocess +import pathlib +import mcpl + +infile = 'MCPLTestData/miscphys.mcpl.gz' +infile_path = subprocess.run( [ 'sb_core_finddata', 'MCPLTestData', 'miscphys.mcpl.gz' ], + capture_output = True, text = True, check = True ).stdout.strip() + +def run( title, cmd, expect_ok, markers ): + print() + print( f'==> {title}' ) + r = subprocess.run( cmd, capture_output = True, text = True, check = False ) + out = r.stdout + r.stderr + #Only show the relevant messages: + for line in out.splitlines(): + for m in markers: + if m in line: + print( ' ', line[line.index(m):].strip() ) + break + #A negative return code means the process was killed by a signal (e.g. an + #uncaught exception leading to abort()): + assert r.returncode >= 0, f'command crashed (signal {-r.returncode}):\n' + out + assert 'terminate called' not in out and 'libc++abi' not in out, 'uncaught exception:\n' + out + if expect_ok: + assert r.returncode == 0, 'command failed:\n' + out + else: + assert r.returncode != 0, 'command unexpectedly succeeded:\n' + out + print( ' (failed as expected)' ) + return out + +def nparticles( fn ): + return mcpl.MCPLFile( fn ).nparticles + +def sim( title, *args, expect_ok = True ): + for f in pathlib.Path('.').glob('out.mcpl*'): + f.unlink() + run( title, [ 'sb_g4mcpltests_mcplstatsums', f'input_file={infile}' ] + list(args), + expect_ok, ( 'MCPLGen ERROR', 'MCPLWriter ERROR', 'HeatMapWriter ERROR' ) ) + if expect_ok and pathlib.Path('out.mcpl.gz').exists(): + print( f' Output particles: {nparticles("out.mcpl.gz")}' ) + +#Valid expressions of different value types, in MCPLGen, MCPLWriter and +#G4HeatMap: +sim( 'MCPLGen filter (floating point)', 'input_filter=ekin > 1MeV', + '--mcpl=grabsrc to out.mcpl' ) +sim( 'MCPLGen filter (integer)', 'input_filter=pdgcode == 2112', + '--mcpl=grabsrc to out.mcpl' ) +sim( 'MCPLGen filter (bool, combined)', 'input_filter=is_neutron && neutron_wl > 1Aa', + '--mcpl=grabsrc to out.mcpl' ) +sim( 'MCPLWriter filter (integer)', '--mcpl=grabsrc where trk.pdgcode == 22 to out.mcpl' ) +sim( 'MCPLWriter filter (floating point)', '--mcpl=grabsrc where step.pre.ekin > 1 MeV to out.mcpl' ) +sim( 'MCPLWriter filter (string)', '--mcpl=grabsrc where step.volname == "World" to out.mcpl' ) +sim( 'MCPLWriter user flags (integer)', '--mcpl=grabsrc withflags trk.pdgcode to out.mcpl' ) +sim( 'Heat map (floating point, with filter)', + '--heatmap=step.steplength where trk.is_neutron to heatmap' ) +import Mesh3D +print( ' Heat map events: %i'%Mesh3D.Mesh3D( 'heatmap.mesh3d' ).stats['nevts'] ) + +#Invalid expressions: +sim( 'MCPLGen filter with syntax error', 'input_filter=ekin >', expect_ok = False ) +sim( 'MCPLGen filter with unknown variable', 'input_filter=nosuchvariable > 1', + expect_ok = False ) +sim( 'MCPLGen filter with domain error', 'input_filter=ekin > 1/0', expect_ok = False ) +sim( 'MCPLWriter filter with syntax error', '--mcpl=grabsrc where trk.pdgcode == to out.mcpl', + expect_ok = False ) +sim( 'MCPLWriter filter with unknown variable', '--mcpl=grabsrc where trk.nosuchvariable to out.mcpl', + expect_ok = False ) +sim( 'MCPLWriter user flags with unknown variable', + '--mcpl=grabsrc withflags trk.nosuchvariable to out.mcpl', expect_ok = False ) +sim( 'Heat map with unknown variable', '--heatmap=step.nosuchvariable to heatmap', + expect_ok = False ) +sim( 'Heat map filter with syntax error', '--heatmap=step.steplength where trk.pdgcode == to heatmap', + expect_ok = False ) + +#The sb_mcplextra_filterfile and sb_mcplextra_filterview commands: +def mcplextra( title, cmd, expect_ok ): + run( title, cmd, expect_ok, ( 'in filter expression', 'Error', 'ERROR' ) ) +mcplextra( 'sb_mcplextra_filterfile', [ 'sb_mcplextra_filterfile', '-n', + infile_path, 'filtered.mcpl', 'is_neutron' ], True ) +print( f' Output particles: {nparticles("filtered.mcpl")}' ) +mcplextra( 'sb_mcplextra_filterfile with invalid expression', + [ 'sb_mcplextra_filterfile', '-n', infile_path, 'filtered2.mcpl', 'ekin >' ], False ) +mcplextra( 'sb_mcplextra_filterview', [ 'sb_mcplextra_filterview', 'is_neutron', infile_path ], True ) +mcplextra( 'sb_mcplextra_filterview with invalid expression', + [ 'sb_mcplextra_filterview', 'nosuchvariable > 1', infile_path ], False ) diff --git a/src/simplebuild_dgcode/data/pkgs_val/UnitTests/G4MCPLTests/scripts/testexpressions.log b/src/simplebuild_dgcode/data/pkgs_val/UnitTests/G4MCPLTests/scripts/testexpressions.log new file mode 100644 index 0000000..6d90edb --- /dev/null +++ b/src/simplebuild_dgcode/data/pkgs_val/UnitTests/G4MCPLTests/scripts/testexpressions.log @@ -0,0 +1,74 @@ + +==> MCPLGen filter (floating point) + Output particles: 110 + +==> MCPLGen filter (integer) + Output particles: 15 + +==> MCPLGen filter (bool, combined) + Output particles: 10 + +==> MCPLWriter filter (integer) + Output particles: 15 + +==> MCPLWriter filter (floating point) + Output particles: 110 + +==> MCPLWriter filter (string) + Output particles: 195 + +==> MCPLWriter user flags (integer) + Output particles: 195 + +==> Heat map (floating point, with filter) + Heat map events: 195 + +==> MCPLGen filter with syntax error + MCPLGen ERROR - ParseError in filter expression : missing arguments for operator : > + (failed as expected) + +==> MCPLGen filter with unknown variable + MCPLGen ERROR - ParseError in filter expression : Unknown or badly placed identifier : nosuchvariable + (failed as expected) + +==> MCPLGen filter with domain error + MCPLGen ERROR - DomainError in filter expression : Division with value which is always zero + (failed as expected) + +==> MCPLWriter filter with syntax error + MCPLWriter ERROR: Invalid filter expression "trk.is_primary && ( step.stepnbr==1 && (trk.pdgcode ==) )" + MCPLWriter ERROR: ParseError : missing arguments for operator : && + (failed as expected) + +==> MCPLWriter filter with unknown variable + MCPLWriter ERROR: Invalid filter expression "trk.is_primary && ( step.stepnbr==1 && (trk.nosuchvariable) )" + MCPLWriter ERROR: ParseError : unknown trk property : "nosuchvariable" + (failed as expected) + +==> MCPLWriter user flags with unknown variable + MCPLWriter ERROR: Invalid expression for user flags "trk.nosuchvariable" + MCPLWriter ERROR: ParseError : unknown trk property : "nosuchvariable" + (failed as expected) + +==> Heat map with unknown variable + HeatMapWriter ERROR: Invalid quantity expression "step.nosuchvariable" + HeatMapWriter ERROR: ParseError : unknown step property : "nosuchvariable" + (failed as expected) + +==> Heat map filter with syntax error + HeatMapWriter ERROR: Invalid filter expression "trk.pdgcode ==" + HeatMapWriter ERROR: ParseError : missing arguments for operator : == + (failed as expected) + +==> sb_mcplextra_filterfile + Output particles: 15 + +==> sb_mcplextra_filterfile with invalid expression + in filter expression : missing arguments for operator : > + (failed as expected) + +==> sb_mcplextra_filterview + +==> sb_mcplextra_filterview with invalid expression + in filter expression : Unknown or badly placed identifier : nosuchvariable + (failed as expected)