diff --git a/src/config_path.h.in b/src/config_path.h.in index 19bda36870..a3d36f4b73 100644 --- a/src/config_path.h.in +++ b/src/config_path.h.in @@ -2,3 +2,4 @@ #define GPHOX_CONFIG_SEARCH_PATHS ".:config:@GPHOX_INSTALL_FULL_DATADIR@/config" #define GPHOX_PTX_DIR "@CMAKE_INSTALL_FULL_LIBDIR@" +#define GPHOX_TEST_GEOM_DIR "@PROJECT_SOURCE_DIR@/tests/geom" diff --git a/tests/geom/sphere_phicut_quarter_shell.gdml b/tests/geom/sphere_phicut_quarter_shell.gdml new file mode 100644 index 0000000000..ebfc2e1b35 --- /dev/null +++ b/tests/geom/sphere_phicut_quarter_shell.gdml @@ -0,0 +1,63 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/u4/tests/CMakeLists.txt b/u4/tests/CMakeLists.txt index 91b7469045..ede142e658 100644 --- a/u4/tests/CMakeLists.txt +++ b/u4/tests/CMakeLists.txt @@ -62,8 +62,9 @@ set(TEST_SOURCES G4ThreeVectorTest.cc - U4PhysicsTableTest.cc + + SpherePhiCutQuarterShellTest.cc ) set(EXPECTED_TO_FAIL_SOURCES @@ -81,6 +82,7 @@ foreach(SRC ${TEST_SOURCES}) get_filename_component(TGT ${SRC} NAME_WE) add_executable(${TGT} ${SRC}) target_link_libraries(${TGT} U4) + target_include_directories(${TGT} PRIVATE ${CMAKE_BINARY_DIR}/src) add_test( NAME ${name}.${TGT} diff --git a/u4/tests/SpherePhiCutQuarterShellTest.cc b/u4/tests/SpherePhiCutQuarterShellTest.cc new file mode 100644 index 0000000000..7c3aeb2041 --- /dev/null +++ b/u4/tests/SpherePhiCutQuarterShellTest.cc @@ -0,0 +1,246 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +using plog::error; +using plog::fatal; +using plog::info; + +#include "G4BooleanSolid.hh" +#include "G4IntersectionSolid.hh" +#include "G4LogicalVolume.hh" +#include "G4PhysicalConstants.hh" +#include "G4Sphere.hh" +#include "G4SubtractionSolid.hh" +#include "G4SystemOfUnits.hh" +#include "G4VPhysicalVolume.hh" +#include "G4VSolid.hh" + +#include "config_path.h" + +#include "U4GDML.h" +#include "U4Solid.h" +#include "U4Volume.h" + +#include "s_csg.h" + +namespace +{ +inline constexpr char testGeomFile[] = GPHOX_TEST_GEOM_DIR "/sphere_phicut_quarter_shell.gdml"; + +enum ConvertOutcome +{ + CONVERT_REJECTED, + CONVERT_ACCEPTED, + CONVERT_ERROR +}; + +bool IsExpectedRejectSignal(int signal) +{ + return signal == SIGABRT || signal == SIGINT; +} + +bool HasNonSpherePrimitive(const sn* nd) +{ + if (nd == nullptr) + return false; + + std::set typecodes; + nd->typecodes(typecodes); + + for (int typecode : typecodes) + { + if (CSG::IsPrimitive(typecode) == false) + continue; + + if (typecode == CSG_SPHERE || typecode == CSG_ZSPHERE) + continue; + + if (typecode == CSG_NOTSUPPORTED || typecode == CSG_UNDEFINED) + continue; + + if (typecode == CSG_PHICUT || typecode == CSG_HALFSPACE) + return true; + } + + return false; +} + +int CountPartialPhiSpheres(const G4VSolid* solid) +{ + const G4Sphere* sphere = dynamic_cast(solid); + if (sphere) + { + double start_phi = sphere->GetStartPhiAngle() / CLHEP::radian; + double delta_phi = sphere->GetDeltaPhiAngle() / CLHEP::radian; + bool partial_phi = start_phi != 0. || delta_phi != 2. * CLHEP::pi; + return partial_phi ? 1 : 0; + } + + const G4BooleanSolid* boolean = dynamic_cast(solid); + if (boolean == nullptr) + return 0; + + const G4VSolid* left = boolean->GetConstituentSolid(0); + const G4VSolid* right = boolean->GetConstituentSolid(1); + return CountPartialPhiSpheres(left) + CountPartialPhiSpheres(right); +} + +ConvertOutcome ConvertInChildProcess(const G4VSolid* solid) +{ + pid_t pid = fork(); + if (pid < 0) + { + perror("fork"); + return CONVERT_ERROR; + } + + if (pid == 0) + { + s_csg* csg = new s_csg; + assert(csg); + + int lvid = 0; + int depth = 0; + int level = 1; + sn* nd = U4Solid::Convert(solid, lvid, depth, level); + int exit_code = 4; + if (nd != nullptr) + { + bool has_phi_cut_representation = HasNonSpherePrimitive(nd); + exit_code = has_phi_cut_representation ? 0 : 5; + if (has_phi_cut_representation == false) + { + std::cerr + << "child conversion produced non-null tree without any non-sphere primitive; " + << "phi-cut geometry is not represented" + << std::endl; + } + } + delete nd; + _exit(exit_code); + } + + int status = 0; + int rc = waitpid(pid, &status, 0); + if (rc != pid) + { + perror("waitpid"); + return CONVERT_ERROR; + } + + if (WIFSIGNALED(status)) + { + int signal = WTERMSIG(status); + if (IsExpectedRejectSignal(signal)) + { + std::cout << "child rejected conversion with expected signal " << signal << std::endl; + return CONVERT_REJECTED; + } + + std::cerr << "child crashed with unexpected signal " << signal << std::endl; + return CONVERT_ERROR; + } + + if (WIFEXITED(status) && WEXITSTATUS(status) == 0) + { + std::cout + << "child converted partial-phi sphere with non-sphere primitive(s) in the CSG tree" + << std::endl; + return CONVERT_ACCEPTED; + } + + if (WIFEXITED(status)) + { + std::cerr << "child exited unexpectedly with status " << WEXITSTATUS(status) << std::endl; + return CONVERT_ERROR; + } + + std::cerr << "child ended in unexpected state " << status << std::endl; + return CONVERT_ERROR; +} +} // namespace + +int main(int argc, char** argv) +{ + static plog::ConsoleAppender consoleAppender; + plog::init(plog::info, &consoleAppender); + + const G4VPhysicalVolume* world = U4GDML::Read(testGeomFile); + LOG_IF(plog::fatal, world == nullptr) + << "failed to load GDML path " << (testGeomFile ? testGeomFile : "-"); + if (world == nullptr) + return EXIT_FAILURE; + + const G4VPhysicalVolume* quarter_shell_pv = U4Volume::FindPV(world, "QuarterShell_pv"); + LOG_IF(plog::fatal, quarter_shell_pv == nullptr) + << "failed to find QuarterShell_pv in GDML path " << testGeomFile; + if (quarter_shell_pv == nullptr) + return EXIT_FAILURE; + + const G4LogicalVolume* quarter_shell_lv = quarter_shell_pv->GetLogicalVolume(); + LOG_IF(plog::fatal, quarter_shell_lv == nullptr) + << "QuarterShell_pv lacks a logical volume"; + if (quarter_shell_lv == nullptr) + return EXIT_FAILURE; + + const G4VSolid* quarter_shell_solid = quarter_shell_lv->GetSolid(); + LOG_IF(plog::fatal, quarter_shell_solid == nullptr) + << "QuarterShell_pv lacks a solid"; + if (quarter_shell_solid == nullptr) + return EXIT_FAILURE; + + const G4IntersectionSolid* intersection = dynamic_cast(quarter_shell_solid); + LOG_IF(plog::fatal, intersection != nullptr) + << "test geometry unexpectedly uses a parent IntersectionSolid"; + if (intersection != nullptr) + return EXIT_FAILURE; + + const G4SubtractionSolid* subtraction = dynamic_cast(quarter_shell_solid); + LOG_IF(plog::fatal, subtraction == nullptr) + << "test geometry expected a subtraction shell solid " << quarter_shell_solid->GetName(); + if (subtraction == nullptr) + return EXIT_FAILURE; + + int partial_phi_spheres = CountPartialPhiSpheres(quarter_shell_solid); + LOG_IF(plog::fatal, partial_phi_spheres == 0) + << "test geometry expected partial-phi sphere primitives"; + if (partial_phi_spheres == 0) + return EXIT_FAILURE; + + ConvertOutcome outcome = ConvertInChildProcess(quarter_shell_solid); + switch (outcome) + { + case CONVERT_REJECTED: + std::cout + << "partial-phi sphere conversion is rejected, matching current fail-fast behavior" + << std::endl; + return EXIT_SUCCESS; + + case CONVERT_ACCEPTED: + std::cout + << "partial-phi sphere conversion succeeded with a non-null CSG tree" + << std::endl; + return EXIT_SUCCESS; + + case CONVERT_ERROR: + break; + } + + std::cerr + << "SpherePhiCutQuarterShellTest could neither confirm fail-fast rejection nor " + << "successful conversion of the partial-phi spherical shell." + << std::endl; + + return EXIT_FAILURE; +}