LbAPLocal 0.12.2__tar.gz → 0.14.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {lbaplocal-0.12.2 → lbaplocal-0.14.0}/.gitlab-ci.yml +7 -1
- {lbaplocal-0.12.2 → lbaplocal-0.14.0}/PKG-INFO +1 -1
- {lbaplocal-0.12.2 → lbaplocal-0.14.0}/src/LbAPLocal/cli/legacy.py +14 -6
- {lbaplocal-0.12.2 → lbaplocal-0.14.0}/src/LbAPLocal/cli/main.py +101 -2
- {lbaplocal-0.12.2 → lbaplocal-0.14.0}/src/LbAPLocal/cli/wizards.py +8 -4
- {lbaplocal-0.12.2 → lbaplocal-0.14.0}/src/LbAPLocal/handlers/anaprod.py +4 -2
- lbaplocal-0.14.0/src/LbAPLocal/reason_analysis.py +281 -0
- lbaplocal-0.14.0/src/LbAPLocal/reason_driver.py +196 -0
- {lbaplocal-0.12.2 → lbaplocal-0.14.0}/src/LbAPLocal/testing.py +3 -2
- {lbaplocal-0.12.2 → lbaplocal-0.14.0}/src/LbAPLocal/utils.py +18 -0
- {lbaplocal-0.12.2 → lbaplocal-0.14.0}/src/LbAPLocal.egg-info/PKG-INFO +1 -1
- {lbaplocal-0.12.2 → lbaplocal-0.14.0}/src/LbAPLocal.egg-info/SOURCES.txt +5 -0
- {lbaplocal-0.12.2 → lbaplocal-0.14.0}/src/LbAPLocal.egg-info/scm_file_list.json +6 -1
- lbaplocal-0.14.0/src/LbAPLocal.egg-info/scm_version.json +8 -0
- {lbaplocal-0.12.2 → lbaplocal-0.14.0}/tests/test_cli.py +10 -0
- {lbaplocal-0.12.2 → lbaplocal-0.14.0}/tests/test_cwl_cli.py +4 -2
- {lbaplocal-0.12.2 → lbaplocal-0.14.0}/tests/test_handlers.py +1 -1
- lbaplocal-0.14.0/tests/test_parse_yaml_catalog.py +87 -0
- lbaplocal-0.14.0/tests/test_reason_analysis.py +529 -0
- lbaplocal-0.14.0/tests/test_reason_driver_integration.py +67 -0
- lbaplocal-0.12.2/src/LbAPLocal.egg-info/scm_version.json +0 -8
- {lbaplocal-0.12.2 → lbaplocal-0.14.0}/.coveragerc +0 -0
- {lbaplocal-0.12.2 → lbaplocal-0.14.0}/.gitattributes +0 -0
- {lbaplocal-0.12.2 → lbaplocal-0.14.0}/.gitignore +0 -0
- {lbaplocal-0.12.2 → lbaplocal-0.14.0}/.pre-commit-config.yaml +0 -0
- {lbaplocal-0.12.2 → lbaplocal-0.14.0}/CONTRIBUTING.md +0 -0
- {lbaplocal-0.12.2 → lbaplocal-0.14.0}/LICENSE +0 -0
- {lbaplocal-0.12.2 → lbaplocal-0.14.0}/README.md +0 -0
- {lbaplocal-0.12.2 → lbaplocal-0.14.0}/example.json +0 -0
- {lbaplocal-0.12.2 → lbaplocal-0.14.0}/pyproject.toml +0 -0
- {lbaplocal-0.12.2 → lbaplocal-0.14.0}/setup.cfg +0 -0
- {lbaplocal-0.12.2 → lbaplocal-0.14.0}/src/LbAPLocal/__init__.py +0 -0
- {lbaplocal-0.12.2 → lbaplocal-0.14.0}/src/LbAPLocal/cli/__init__.py +0 -0
- {lbaplocal-0.12.2 → lbaplocal-0.14.0}/src/LbAPLocal/cli/cvmfs.py +0 -0
- {lbaplocal-0.12.2 → lbaplocal-0.14.0}/src/LbAPLocal/cli/decfile_lexer.py +0 -0
- {lbaplocal-0.12.2 → lbaplocal-0.14.0}/src/LbAPLocal/cli/generate.py +0 -0
- {lbaplocal-0.12.2 → lbaplocal-0.14.0}/src/LbAPLocal/cli/mc_lookup.py +0 -0
- {lbaplocal-0.12.2 → lbaplocal-0.14.0}/src/LbAPLocal/cli/mc_wizard.py +0 -0
- {lbaplocal-0.12.2 → lbaplocal-0.14.0}/src/LbAPLocal/cli/textual_ui.py +0 -0
- {lbaplocal-0.12.2 → lbaplocal-0.14.0}/src/LbAPLocal/cwl/__init__.py +0 -0
- {lbaplocal-0.12.2 → lbaplocal-0.14.0}/src/LbAPLocal/cwl/app_runner.py +0 -0
- {lbaplocal-0.12.2 → lbaplocal-0.14.0}/src/LbAPLocal/cwl/generate_replica_map.py +0 -0
- {lbaplocal-0.12.2 → lbaplocal-0.14.0}/src/LbAPLocal/cwl/quickjs_engine.py +0 -0
- {lbaplocal-0.12.2 → lbaplocal-0.14.0}/src/LbAPLocal/cwl/rewrite.py +0 -0
- {lbaplocal-0.12.2 → lbaplocal-0.14.0}/src/LbAPLocal/cwl/runner.py +0 -0
- {lbaplocal-0.12.2 → lbaplocal-0.14.0}/src/LbAPLocal/handlers/__init__.py +0 -0
- {lbaplocal-0.12.2 → lbaplocal-0.14.0}/src/LbAPLocal/handlers/mc_request.py +0 -0
- {lbaplocal-0.12.2 → lbaplocal-0.14.0}/src/LbAPLocal/job_slice.py +0 -0
- {lbaplocal-0.12.2 → lbaplocal-0.14.0}/src/LbAPLocal/log_parsing.py +0 -0
- {lbaplocal-0.12.2 → lbaplocal-0.14.0}/src/LbAPLocal/models.py +0 -0
- {lbaplocal-0.12.2 → lbaplocal-0.14.0}/src/LbAPLocal.egg-info/dependency_links.txt +0 -0
- {lbaplocal-0.12.2 → lbaplocal-0.14.0}/src/LbAPLocal.egg-info/entry_points.txt +0 -0
- {lbaplocal-0.12.2 → lbaplocal-0.14.0}/src/LbAPLocal.egg-info/requires.txt +0 -0
- {lbaplocal-0.12.2 → lbaplocal-0.14.0}/src/LbAPLocal.egg-info/top_level.txt +0 -0
- {lbaplocal-0.12.2 → lbaplocal-0.14.0}/tests/capture_fixtures.py +0 -0
- {lbaplocal-0.12.2 → lbaplocal-0.14.0}/tests/cassettes/test_execute/test_run_test[2016_MagDown_PromptMC_D02KK-2444].yaml +0 -0
- {lbaplocal-0.12.2 → lbaplocal-0.14.0}/tests/cassettes/test_execute/test_run_test[2016_MagDown_PromptMC_D02KK_noautoconf-2444].yaml +0 -0
- {lbaplocal-0.12.2 → lbaplocal-0.14.0}/tests/cassettes/test_execute/test_run_test[2016_MagUp_PromptMC_D02KK-2393].yaml +0 -0
- {lbaplocal-0.12.2 → lbaplocal-0.14.0}/tests/cassettes/test_execute/test_run_test[2016_MagUp_PromptMC_D02KK_noautoconf-2393].yaml +0 -0
- {lbaplocal-0.12.2 → lbaplocal-0.14.0}/tests/conftest.py +0 -0
- {lbaplocal-0.12.2 → lbaplocal-0.14.0}/tests/data/data-pkg-repo/.gitignore +0 -0
- {lbaplocal-0.12.2 → lbaplocal-0.14.0}/tests/data/data-pkg-repo/.gitlab-ci.yml +0 -0
- {lbaplocal-0.12.2 → lbaplocal-0.14.0}/tests/data/data-pkg-repo/AnalysisProductions.xenv +0 -0
- {lbaplocal-0.12.2 → lbaplocal-0.14.0}/tests/data/data-pkg-repo/BsToJpsiPhi/Bc2Bspi2JpsiPhi.py +0 -0
- {lbaplocal-0.12.2 → lbaplocal-0.14.0}/tests/data/data-pkg-repo/BsToJpsiPhi/Bd2JpsiKstar.py +0 -0
- {lbaplocal-0.12.2 → lbaplocal-0.14.0}/tests/data/data-pkg-repo/BsToJpsiPhi/Bd2JpsiKstar_stripping.py +0 -0
- {lbaplocal-0.12.2 → lbaplocal-0.14.0}/tests/data/data-pkg-repo/BsToJpsiPhi/Bs2DsPi.py +0 -0
- {lbaplocal-0.12.2 → lbaplocal-0.14.0}/tests/data/data-pkg-repo/BsToJpsiPhi/Bs2JpsiPhi.py +0 -0
- {lbaplocal-0.12.2 → lbaplocal-0.14.0}/tests/data/data-pkg-repo/BsToJpsiPhi/Bs2JpsiPhi_Prompt.py +0 -0
- {lbaplocal-0.12.2 → lbaplocal-0.14.0}/tests/data/data-pkg-repo/BsToJpsiPhi/Bs2JpsiPhi_Prompt_PVMixer.py +0 -0
- {lbaplocal-0.12.2 → lbaplocal-0.14.0}/tests/data/data-pkg-repo/BsToJpsiPhi/Bs2JpsiPhi_Swave.py +0 -0
- {lbaplocal-0.12.2 → lbaplocal-0.14.0}/tests/data/data-pkg-repo/BsToJpsiPhi/Bu2JpsiKplus.py +0 -0
- {lbaplocal-0.12.2 → lbaplocal-0.14.0}/tests/data/data-pkg-repo/BsToJpsiPhi/Bu2JpsiKplus_stripping.py +0 -0
- {lbaplocal-0.12.2 → lbaplocal-0.14.0}/tests/data/data-pkg-repo/BsToJpsiPhi/LbJpsipK.py +0 -0
- {lbaplocal-0.12.2 → lbaplocal-0.14.0}/tests/data/data-pkg-repo/BsToJpsiPhi/README.md +0 -0
- {lbaplocal-0.12.2 → lbaplocal-0.14.0}/tests/data/data-pkg-repo/BsToJpsiPhi/helpers/__init__.py +0 -0
- {lbaplocal-0.12.2 → lbaplocal-0.14.0}/tests/data/data-pkg-repo/BsToJpsiPhi/helpers/stripping.py +0 -0
- {lbaplocal-0.12.2 → lbaplocal-0.14.0}/tests/data/data-pkg-repo/BsToJpsiPhi/helpers/tuple_maker.py +0 -0
- {lbaplocal-0.12.2 → lbaplocal-0.14.0}/tests/data/data-pkg-repo/BsToJpsiPhi/info.yaml +0 -0
- {lbaplocal-0.12.2 → lbaplocal-0.14.0}/tests/data/data-pkg-repo/BsToJpsiPhi/sequence_setup.py +0 -0
- {lbaplocal-0.12.2 → lbaplocal-0.14.0}/tests/data/data-pkg-repo/COPYING +0 -0
- {lbaplocal-0.12.2 → lbaplocal-0.14.0}/tests/data/data-pkg-repo/RDs/README.md +0 -0
- {lbaplocal-0.12.2 → lbaplocal-0.14.0}/tests/data/data-pkg-repo/RDs/Run1_Signal.py +0 -0
- {lbaplocal-0.12.2 → lbaplocal-0.14.0}/tests/data/data-pkg-repo/RDs/Run2_Signal.py +0 -0
- {lbaplocal-0.12.2 → lbaplocal-0.14.0}/tests/data/data-pkg-repo/RDs/helpers/__init__.py +0 -0
- {lbaplocal-0.12.2 → lbaplocal-0.14.0}/tests/data/data-pkg-repo/RDs/helpers/signal_tuple_maker.py +0 -0
- {lbaplocal-0.12.2 → lbaplocal-0.14.0}/tests/data/data-pkg-repo/RDs/info.yaml +0 -0
- {lbaplocal-0.12.2 → lbaplocal-0.14.0}/tests/data/data-pkg-repo/README.md +0 -0
- {lbaplocal-0.12.2 → lbaplocal-0.14.0}/tests/data/data-pkg-repo/__init__.py +0 -0
- {lbaplocal-0.12.2 → lbaplocal-0.14.0}/tests/data/data-pkg-repo/cbtesting/2011_datatype.py +0 -0
- {lbaplocal-0.12.2 → lbaplocal-0.14.0}/tests/data/data-pkg-repo/cbtesting/2012_datatype.py +0 -0
- {lbaplocal-0.12.2 → lbaplocal-0.14.0}/tests/data/data-pkg-repo/cbtesting/2015_datatype.py +0 -0
- {lbaplocal-0.12.2 → lbaplocal-0.14.0}/tests/data/data-pkg-repo/cbtesting/2016_datatype.py +0 -0
- {lbaplocal-0.12.2 → lbaplocal-0.14.0}/tests/data/data-pkg-repo/cbtesting/2017_datatype.py +0 -0
- {lbaplocal-0.12.2 → lbaplocal-0.14.0}/tests/data/data-pkg-repo/cbtesting/2018_datatype.py +0 -0
- {lbaplocal-0.12.2 → lbaplocal-0.14.0}/tests/data/data-pkg-repo/cbtesting/B0ToK0ee.py +0 -0
- {lbaplocal-0.12.2 → lbaplocal-0.14.0}/tests/data/data-pkg-repo/cbtesting/B0ToK0mumu.py +0 -0
- {lbaplocal-0.12.2 → lbaplocal-0.14.0}/tests/data/data-pkg-repo/cbtesting/BpToKee.py +0 -0
- {lbaplocal-0.12.2 → lbaplocal-0.14.0}/tests/data/data-pkg-repo/cbtesting/BpToKmumu.py +0 -0
- {lbaplocal-0.12.2 → lbaplocal-0.14.0}/tests/data/data-pkg-repo/cbtesting/LbToL0ee.py +0 -0
- {lbaplocal-0.12.2 → lbaplocal-0.14.0}/tests/data/data-pkg-repo/cbtesting/LbToL0eeSS.py +0 -0
- {lbaplocal-0.12.2 → lbaplocal-0.14.0}/tests/data/data-pkg-repo/cbtesting/LbToL0emu.py +0 -0
- {lbaplocal-0.12.2 → lbaplocal-0.14.0}/tests/data/data-pkg-repo/cbtesting/LbToL0emuSS.py +0 -0
- {lbaplocal-0.12.2 → lbaplocal-0.14.0}/tests/data/data-pkg-repo/cbtesting/LbToL0mumu.py +0 -0
- {lbaplocal-0.12.2 → lbaplocal-0.14.0}/tests/data/data-pkg-repo/cbtesting/LbToL0mumuSS.py +0 -0
- {lbaplocal-0.12.2 → lbaplocal-0.14.0}/tests/data/data-pkg-repo/cbtesting/README.md +0 -0
- {lbaplocal-0.12.2 → lbaplocal-0.14.0}/tests/data/data-pkg-repo/cbtesting/__init__.py +0 -0
- {lbaplocal-0.12.2 → lbaplocal-0.14.0}/tests/data/data-pkg-repo/cbtesting/general_options.py +0 -0
- {lbaplocal-0.12.2 → lbaplocal-0.14.0}/tests/data/data-pkg-repo/cbtesting/helpers/MC_tuple_maker.py +0 -0
- {lbaplocal-0.12.2 → lbaplocal-0.14.0}/tests/data/data-pkg-repo/cbtesting/helpers/__init__.py +0 -0
- {lbaplocal-0.12.2 → lbaplocal-0.14.0}/tests/data/data-pkg-repo/cbtesting/helpers/decay_descriptors.py +0 -0
- {lbaplocal-0.12.2 → lbaplocal-0.14.0}/tests/data/data-pkg-repo/cbtesting/helpers/locations.py +0 -0
- {lbaplocal-0.12.2 → lbaplocal-0.14.0}/tests/data/data-pkg-repo/cbtesting/helpers/related_info.py +0 -0
- {lbaplocal-0.12.2 → lbaplocal-0.14.0}/tests/data/data-pkg-repo/cbtesting/helpers/stripping.py +0 -0
- {lbaplocal-0.12.2 → lbaplocal-0.14.0}/tests/data/data-pkg-repo/cbtesting/helpers/tags.py +0 -0
- {lbaplocal-0.12.2 → lbaplocal-0.14.0}/tests/data/data-pkg-repo/cbtesting/helpers/tuple_maker.py +0 -0
- {lbaplocal-0.12.2 → lbaplocal-0.14.0}/tests/data/data-pkg-repo/cbtesting/info.yaml +0 -0
- {lbaplocal-0.12.2 → lbaplocal-0.14.0}/tests/data/data-pkg-repo/cbtesting/mc_general_options_DST.py +0 -0
- {lbaplocal-0.12.2 → lbaplocal-0.14.0}/tests/data/data-pkg-repo/cbtesting/mc_general_options_FDST.py +0 -0
- {lbaplocal-0.12.2 → lbaplocal-0.14.0}/tests/data/data-pkg-repo/cbtesting/mc_general_options_FMDST.py +0 -0
- {lbaplocal-0.12.2 → lbaplocal-0.14.0}/tests/data/data-pkg-repo/cbtesting/mc_general_options_MDST.py +0 -0
- {lbaplocal-0.12.2 → lbaplocal-0.14.0}/tests/data/data-pkg-repo/cbtesting/mc_samples/MC_Bd2JpsiKs_ee.py +0 -0
- {lbaplocal-0.12.2 → lbaplocal-0.14.0}/tests/data/data-pkg-repo/cbtesting/mc_samples/MC_Bd2JpsiKs_mm.py +0 -0
- {lbaplocal-0.12.2 → lbaplocal-0.14.0}/tests/data/data-pkg-repo/cbtesting/mc_samples/MC_Bd2Ksee.py +0 -0
- {lbaplocal-0.12.2 → lbaplocal-0.14.0}/tests/data/data-pkg-repo/cbtesting/mc_samples/MC_Bd2Ksmm.py +0 -0
- {lbaplocal-0.12.2 → lbaplocal-0.14.0}/tests/data/data-pkg-repo/cbtesting/mc_samples/MC_Bd2PsiKs_ee.py +0 -0
- {lbaplocal-0.12.2 → lbaplocal-0.14.0}/tests/data/data-pkg-repo/cbtesting/mc_samples/MC_Bd2PsiKs_mm.py +0 -0
- {lbaplocal-0.12.2 → lbaplocal-0.14.0}/tests/data/data-pkg-repo/cbtesting/mc_samples/MC_Bu2JpsiK_ee.py +0 -0
- {lbaplocal-0.12.2 → lbaplocal-0.14.0}/tests/data/data-pkg-repo/cbtesting/mc_samples/MC_Bu2JpsiK_mm.py +0 -0
- {lbaplocal-0.12.2 → lbaplocal-0.14.0}/tests/data/data-pkg-repo/cbtesting/mc_samples/MC_Lb2JpsiL_ee.py +0 -0
- {lbaplocal-0.12.2 → lbaplocal-0.14.0}/tests/data/data-pkg-repo/cbtesting/mc_samples/MC_Lb2JpsiL_mm.py +0 -0
- {lbaplocal-0.12.2 → lbaplocal-0.14.0}/tests/data/data-pkg-repo/cbtesting/mc_samples/MC_Lb2Lee.py +0 -0
- {lbaplocal-0.12.2 → lbaplocal-0.14.0}/tests/data/data-pkg-repo/cbtesting/mc_samples/MC_Lb2Lem.py +0 -0
- {lbaplocal-0.12.2 → lbaplocal-0.14.0}/tests/data/data-pkg-repo/cbtesting/mc_samples/MC_Lb2Lmm.py +0 -0
- {lbaplocal-0.12.2 → lbaplocal-0.14.0}/tests/data/data-pkg-repo/cbtesting/mc_samples/MC_Lb2PsiL_ee.py +0 -0
- {lbaplocal-0.12.2 → lbaplocal-0.14.0}/tests/data/data-pkg-repo/cbtesting/mc_samples/MC_Lb2PsiL_mm.py +0 -0
- {lbaplocal-0.12.2 → lbaplocal-0.14.0}/tests/data/data-pkg-repo/cbtesting/sequence_setup.py +0 -0
- {lbaplocal-0.12.2 → lbaplocal-0.14.0}/tests/data/data-pkg-repo/cbtesting/stripping_seq.py +0 -0
- {lbaplocal-0.12.2 → lbaplocal-0.14.0}/tests/data/data-pkg-repo/executabletests/2016_MagDown_PromptMC_D02KK_autoconf.py +0 -0
- {lbaplocal-0.12.2 → lbaplocal-0.14.0}/tests/data/data-pkg-repo/executabletests/2016_MagUp_PromptMC_D02KK_autoconf.py +0 -0
- {lbaplocal-0.12.2 → lbaplocal-0.14.0}/tests/data/data-pkg-repo/executabletests/info.yaml +0 -0
- {lbaplocal-0.12.2 → lbaplocal-0.14.0}/tests/data/data-pkg-repo/executabletests/ntuple_options.py +0 -0
- {lbaplocal-0.12.2 → lbaplocal-0.14.0}/tests/data/example-logs/error-failed-to-read-file.log +0 -0
- {lbaplocal-0.12.2 → lbaplocal-0.14.0}/tests/data/example-logs/error-illegal-instruction.log +0 -0
- {lbaplocal-0.12.2 → lbaplocal-0.14.0}/tests/data/example-logs/error-missing-shared-library.log +0 -0
- {lbaplocal-0.12.2 → lbaplocal-0.14.0}/tests/data/example-logs/error-platform-unsupported.log +0 -0
- {lbaplocal-0.12.2 → lbaplocal-0.14.0}/tests/data/example-logs/error-related-info-missing.log +0 -0
- {lbaplocal-0.12.2 → lbaplocal-0.14.0}/tests/data/example-logs/good-DaVinci_00110296_00000038_1.log +0 -0
- {lbaplocal-0.12.2 → lbaplocal-0.14.0}/tests/data/example-logs/good-DaVinci_00110296_00000194_1.log +0 -0
- {lbaplocal-0.12.2 → lbaplocal-0.14.0}/tests/data/example-logs/good-Gauss_00104988_00000011_1.log +0 -0
- {lbaplocal-0.12.2 → lbaplocal-0.14.0}/tests/fixture_loader.py +0 -0
- {lbaplocal-0.12.2 → lbaplocal-0.14.0}/tests/fixtures/executabletests/2016_MagDown_PromptMC_D02KK/query_result.json +0 -0
- {lbaplocal-0.12.2 → lbaplocal-0.14.0}/tests/fixtures/executabletests/2016_MagUp_PromptMC_D02KK/query_result.json +0 -0
- {lbaplocal-0.12.2 → lbaplocal-0.14.0}/tests/fixtures/executabletests/manifest.yaml +0 -0
- {lbaplocal-0.12.2 → lbaplocal-0.14.0}/tests/reslice_fixture.py +0 -0
- {lbaplocal-0.12.2 → lbaplocal-0.14.0}/tests/test-input-dst/00070767_00000711_7.AllStreams_slice.dst +0 -0
- {lbaplocal-0.12.2 → lbaplocal-0.14.0}/tests/test-input-dst/00070793_00000685_7.AllStreams_slice.dst +0 -0
- {lbaplocal-0.12.2 → lbaplocal-0.14.0}/tests/test_app_runner.py +0 -0
- {lbaplocal-0.12.2 → lbaplocal-0.14.0}/tests/test_cwl_runner.py +0 -0
- {lbaplocal-0.12.2 → lbaplocal-0.14.0}/tests/test_execute.py +0 -0
- {lbaplocal-0.12.2 → lbaplocal-0.14.0}/tests/test_generate.py +0 -0
- {lbaplocal-0.12.2 → lbaplocal-0.14.0}/tests/test_get_mc.py +0 -0
- {lbaplocal-0.12.2 → lbaplocal-0.14.0}/tests/test_input_resolution.py +0 -0
- {lbaplocal-0.12.2 → lbaplocal-0.14.0}/tests/test_local_datapkg.py +0 -0
- {lbaplocal-0.12.2 → lbaplocal-0.14.0}/tests/test_log_parse.py +0 -0
- {lbaplocal-0.12.2 → lbaplocal-0.14.0}/tests/test_mc_lookup.py +0 -0
- {lbaplocal-0.12.2 → lbaplocal-0.14.0}/tests/test_no_dirac_cwl.py +0 -0
- {lbaplocal-0.12.2 → lbaplocal-0.14.0}/tests/test_quickjs_engine.py +0 -0
|
@@ -16,7 +16,7 @@ pixi:
|
|
|
16
16
|
parallel:
|
|
17
17
|
matrix:
|
|
18
18
|
- task: pre-commit run --all-files
|
|
19
|
-
- task: pytest --runslow
|
|
19
|
+
- task: pytest --runslow --cov-report=xml
|
|
20
20
|
rules:
|
|
21
21
|
- when: always
|
|
22
22
|
image: gitlab-registry.cern.ch/lhcb-docker/os-base/alma9-devel:latest
|
|
@@ -28,6 +28,12 @@ pixi:
|
|
|
28
28
|
- source ~/.bashrc || true
|
|
29
29
|
script:
|
|
30
30
|
- pixi run $task
|
|
31
|
+
coverage: '/TOTAL.*\s+(\d+%)$/'
|
|
32
|
+
artifacts:
|
|
33
|
+
reports:
|
|
34
|
+
coverage_report:
|
|
35
|
+
coverage_format: cobertura
|
|
36
|
+
path: coverage.xml
|
|
31
37
|
|
|
32
38
|
test-build:
|
|
33
39
|
stage: test
|
|
@@ -20,7 +20,7 @@ from typing import Iterable
|
|
|
20
20
|
|
|
21
21
|
import click
|
|
22
22
|
from apd.authentication import get_auth_headers
|
|
23
|
-
from LbAPCommon import
|
|
23
|
+
from LbAPCommon import render_yaml, validate_yaml
|
|
24
24
|
|
|
25
25
|
from ..utils import (
|
|
26
26
|
LBAPI_HOST,
|
|
@@ -28,6 +28,7 @@ from ..utils import (
|
|
|
28
28
|
check_production,
|
|
29
29
|
inside_ap_datapkg,
|
|
30
30
|
logging_subprocess_run,
|
|
31
|
+
parse_yaml_with_catalog,
|
|
31
32
|
production_name_type,
|
|
32
33
|
production_to_versions,
|
|
33
34
|
validate_environment,
|
|
@@ -63,7 +64,14 @@ def main():
|
|
|
63
64
|
|
|
64
65
|
NOTE: This is the legacy CLI. Consider using the new 'lb-ap' command instead.
|
|
65
66
|
"""
|
|
66
|
-
|
|
67
|
+
click.secho(
|
|
68
|
+
"WARNING: lb-ap-legacy will be retired soon; please switch to 'lb-ap'.\n"
|
|
69
|
+
"If anything stops you from using 'lb-ap', please open an issue so it "
|
|
70
|
+
"can be resolved ASAP:\n"
|
|
71
|
+
"https://gitlab.cern.ch/lhcb-dpa/analysis-productions/lbaplocal/-/work_items",
|
|
72
|
+
fg="yellow",
|
|
73
|
+
err=True,
|
|
74
|
+
)
|
|
67
75
|
|
|
68
76
|
|
|
69
77
|
@main.command()
|
|
@@ -274,7 +282,7 @@ def list(production_name):
|
|
|
274
282
|
# Get rendered yaml and find all the production names
|
|
275
283
|
with open(os.path.join(production_name, "info.yaml"), "rt") as fp:
|
|
276
284
|
raw_yaml = fp.read()
|
|
277
|
-
job_data =
|
|
285
|
+
job_data = parse_yaml_with_catalog(render_yaml(raw_yaml))
|
|
278
286
|
for job_name in job_data:
|
|
279
287
|
click.echo(f"* {job_name}")
|
|
280
288
|
else:
|
|
@@ -299,7 +307,7 @@ def render(production_name):
|
|
|
299
307
|
render = render_yaml(raw_yaml)
|
|
300
308
|
click.echo(render)
|
|
301
309
|
try:
|
|
302
|
-
job_data =
|
|
310
|
+
job_data = parse_yaml_with_catalog(render)
|
|
303
311
|
warnings = validate_yaml(job_data, ".", production_name)
|
|
304
312
|
except Exception:
|
|
305
313
|
click.secho("Rendered YAML has errors!", fg="red")
|
|
@@ -327,7 +335,7 @@ def validate(production_name):
|
|
|
327
335
|
render = render_yaml(raw_yaml)
|
|
328
336
|
|
|
329
337
|
try:
|
|
330
|
-
job_data =
|
|
338
|
+
job_data = parse_yaml_with_catalog(render)
|
|
331
339
|
except Exception as e:
|
|
332
340
|
click.secho("Error parsing YAML!", fg="red")
|
|
333
341
|
raise click.ClickException(str(e))
|
|
@@ -401,7 +409,7 @@ def test(production_name, job_name, dependent_input, n_events, validate):
|
|
|
401
409
|
with open(os.path.join(production_name, "info.yaml"), "rt") as fp:
|
|
402
410
|
raw_yaml = fp.read()
|
|
403
411
|
# TODO do we really need to redo parsing and validating when prepare test already does it?
|
|
404
|
-
prod_data =
|
|
412
|
+
prod_data = parse_yaml_with_catalog(render_yaml(raw_yaml))
|
|
405
413
|
yaml_warnings = validate_yaml(prod_data, ".", production_name)
|
|
406
414
|
try:
|
|
407
415
|
job_data = prod_data[job_name]
|
|
@@ -1830,6 +1830,13 @@ def test(
|
|
|
1830
1830
|
help="Validate and render configuration without executing the test",
|
|
1831
1831
|
),
|
|
1832
1832
|
] = False,
|
|
1833
|
+
no_reasons: Annotated[
|
|
1834
|
+
bool,
|
|
1835
|
+
typer.Option(
|
|
1836
|
+
"--no-reasons",
|
|
1837
|
+
help="Skip LHCbDIRAC reason analysis of failed steps",
|
|
1838
|
+
),
|
|
1839
|
+
] = False,
|
|
1833
1840
|
):
|
|
1834
1841
|
"""
|
|
1835
1842
|
🧪 Test a job using CWL workflow
|
|
@@ -1896,7 +1903,9 @@ def test(
|
|
|
1896
1903
|
)
|
|
1897
1904
|
raise typer.Exit(code=1)
|
|
1898
1905
|
|
|
1899
|
-
from LbAPCommon import
|
|
1906
|
+
from LbAPCommon import render_yaml, validate_yaml
|
|
1907
|
+
|
|
1908
|
+
from ..utils import parse_yaml_with_catalog
|
|
1900
1909
|
|
|
1901
1910
|
raw_yaml = info_yaml_path.read_text()
|
|
1902
1911
|
rendered = render_yaml(raw_yaml)
|
|
@@ -1908,7 +1917,7 @@ def test(
|
|
|
1908
1917
|
syntax = Syntax(rendered, "yaml", theme="monokai", line_numbers=True)
|
|
1909
1918
|
console.print(syntax)
|
|
1910
1919
|
|
|
1911
|
-
jobs_data =
|
|
1920
|
+
jobs_data = parse_yaml_with_catalog(rendered, production_name, None)
|
|
1912
1921
|
|
|
1913
1922
|
if job_name not in jobs_data:
|
|
1914
1923
|
available_jobs = list(jobs_data.keys())
|
|
@@ -1948,6 +1957,7 @@ def test(
|
|
|
1948
1957
|
pick_smallest=pick_smallest,
|
|
1949
1958
|
pretty=pretty,
|
|
1950
1959
|
download=download,
|
|
1960
|
+
no_reasons=no_reasons,
|
|
1951
1961
|
)
|
|
1952
1962
|
|
|
1953
1963
|
|
|
@@ -2296,6 +2306,13 @@ def parse_log_cmd(
|
|
|
2296
2306
|
help="Minimum number of instances required to show WARNING messages",
|
|
2297
2307
|
),
|
|
2298
2308
|
] = 5,
|
|
2309
|
+
reasons: Annotated[
|
|
2310
|
+
bool,
|
|
2311
|
+
typer.Option(
|
|
2312
|
+
"--reasons",
|
|
2313
|
+
help="Also classify failures with LHCbDIRAC's reason engine (via lb-dirac)",
|
|
2314
|
+
),
|
|
2315
|
+
] = False,
|
|
2299
2316
|
):
|
|
2300
2317
|
"""Analyse a Gaudi log file and show a summary with advice.
|
|
2301
2318
|
|
|
@@ -2309,10 +2326,61 @@ def parse_log_cmd(
|
|
|
2309
2326
|
analysis = analyse_log(log_text, suppress=suppress)
|
|
2310
2327
|
render_log_analysis(analysis, console)
|
|
2311
2328
|
|
|
2329
|
+
if reasons:
|
|
2330
|
+
from ..reason_analysis import (
|
|
2331
|
+
ReasonAnalysisUnavailable,
|
|
2332
|
+
render_reason_analysis,
|
|
2333
|
+
run_reason_analysis,
|
|
2334
|
+
steps_from_log_files,
|
|
2335
|
+
)
|
|
2336
|
+
|
|
2337
|
+
try:
|
|
2338
|
+
reason_result = run_reason_analysis(steps_from_log_files([log_file]))
|
|
2339
|
+
except ReasonAnalysisUnavailable as e:
|
|
2340
|
+
console.print(f"[dim]Reason analysis unavailable: {e}[/dim]")
|
|
2341
|
+
else:
|
|
2342
|
+
render_reason_analysis(reason_result, console)
|
|
2343
|
+
|
|
2312
2344
|
if analysis.has_fatal_issues:
|
|
2313
2345
|
raise typer.Exit(code=1)
|
|
2314
2346
|
|
|
2315
2347
|
|
|
2348
|
+
@app.command(name="analyse-job")
|
|
2349
|
+
def analyse_job_cmd(
|
|
2350
|
+
path: Annotated[
|
|
2351
|
+
Path,
|
|
2352
|
+
typer.Argument(
|
|
2353
|
+
help="Run/output directory or a single Gaudi log file to analyse",
|
|
2354
|
+
exists=True,
|
|
2355
|
+
),
|
|
2356
|
+
],
|
|
2357
|
+
):
|
|
2358
|
+
"""Classify failures using LHCbDIRAC's reason engine (runs via lb-dirac).
|
|
2359
|
+
|
|
2360
|
+
Scans application logs (pairing them with prodConf JSONs when present)
|
|
2361
|
+
against the same curated failure patterns used by the production
|
|
2362
|
+
TransformationDebugAgent.
|
|
2363
|
+
"""
|
|
2364
|
+
from ..reason_analysis import (
|
|
2365
|
+
ReasonAnalysisUnavailable,
|
|
2366
|
+
render_reason_analysis,
|
|
2367
|
+
run_reason_analysis,
|
|
2368
|
+
steps_from_path,
|
|
2369
|
+
)
|
|
2370
|
+
|
|
2371
|
+
steps = steps_from_path(path)
|
|
2372
|
+
if not steps:
|
|
2373
|
+
err_console.print(f"[bold red]✗ No .log files found under:[/bold red] {path}")
|
|
2374
|
+
raise typer.Exit(code=1)
|
|
2375
|
+
console.print(f"[dim]Analysing {len(steps)} step log(s) via lb-dirac…[/dim]")
|
|
2376
|
+
try:
|
|
2377
|
+
analysis = run_reason_analysis(steps)
|
|
2378
|
+
except ReasonAnalysisUnavailable as e:
|
|
2379
|
+
err_console.print(f"[bold red]✗ Reason analysis unavailable:[/bold red] {e}")
|
|
2380
|
+
raise typer.Exit(code=1)
|
|
2381
|
+
render_reason_analysis(analysis, console)
|
|
2382
|
+
|
|
2383
|
+
|
|
2316
2384
|
# ============================================================================
|
|
2317
2385
|
# Reproducer Commands
|
|
2318
2386
|
# ============================================================================
|
|
@@ -3877,6 +3945,13 @@ def cwl_test(
|
|
|
3877
3945
|
help="Download input files locally instead of using remote protocol where available",
|
|
3878
3946
|
),
|
|
3879
3947
|
] = False,
|
|
3948
|
+
no_reasons: Annotated[
|
|
3949
|
+
bool,
|
|
3950
|
+
typer.Option(
|
|
3951
|
+
"--no-reasons",
|
|
3952
|
+
help="Skip LHCbDIRAC reason analysis of failed steps",
|
|
3953
|
+
),
|
|
3954
|
+
] = False,
|
|
3880
3955
|
):
|
|
3881
3956
|
"""
|
|
3882
3957
|
🧪 Test a job using CWL workflow
|
|
@@ -4557,6 +4632,7 @@ def cwl_test(
|
|
|
4557
4632
|
|
|
4558
4633
|
# Analyse log files to show what went wrong
|
|
4559
4634
|
_show_log_analysis_for_files(log_files, err_console)
|
|
4635
|
+
_maybe_show_reason_analysis(log_files, err_console, no_reasons)
|
|
4560
4636
|
|
|
4561
4637
|
raise typer.Exit(code=1)
|
|
4562
4638
|
|
|
@@ -4707,6 +4783,29 @@ def _show_log_analysis_for_files(log_files: list[Path], console) -> None:
|
|
|
4707
4783
|
)
|
|
4708
4784
|
|
|
4709
4785
|
|
|
4786
|
+
def _maybe_show_reason_analysis(
|
|
4787
|
+
log_files: list[Path], console, no_reasons: bool
|
|
4788
|
+
) -> None:
|
|
4789
|
+
"""Run the LHCbDIRAC reason engine over failed-run logs. Never raises."""
|
|
4790
|
+
from ..reason_analysis import (
|
|
4791
|
+
ReasonAnalysisUnavailable,
|
|
4792
|
+
reason_analysis_disabled,
|
|
4793
|
+
render_reason_analysis,
|
|
4794
|
+
run_reason_analysis,
|
|
4795
|
+
steps_from_log_files,
|
|
4796
|
+
)
|
|
4797
|
+
|
|
4798
|
+
if no_reasons or reason_analysis_disabled() or not log_files:
|
|
4799
|
+
return
|
|
4800
|
+
console.print("\n[dim]Running LHCbDIRAC reason analysis (lb-dirac)…[/dim]")
|
|
4801
|
+
try:
|
|
4802
|
+
analysis = run_reason_analysis(steps_from_log_files(log_files))
|
|
4803
|
+
except ReasonAnalysisUnavailable as e:
|
|
4804
|
+
console.print(f"[dim]Reason analysis unavailable: {e}[/dim]")
|
|
4805
|
+
return
|
|
4806
|
+
render_reason_analysis(analysis, console)
|
|
4807
|
+
|
|
4808
|
+
|
|
4710
4809
|
# ============================================================================
|
|
4711
4810
|
# Helper Functions
|
|
4712
4811
|
# ============================================================================
|
|
@@ -247,7 +247,9 @@ def test_wizard(productions: Optional[List[str]] = None) -> dict:
|
|
|
247
247
|
Returns:
|
|
248
248
|
Dictionary with test parameters or None if cancelled
|
|
249
249
|
"""
|
|
250
|
-
from LbAPCommon import
|
|
250
|
+
from LbAPCommon import render_yaml
|
|
251
|
+
|
|
252
|
+
from ..utils import parse_yaml_with_catalog
|
|
251
253
|
|
|
252
254
|
welcome_wizard()
|
|
253
255
|
|
|
@@ -271,7 +273,7 @@ def test_wizard(productions: Optional[List[str]] = None) -> dict:
|
|
|
271
273
|
if info_yaml_path.exists():
|
|
272
274
|
with open(info_yaml_path, "rt") as fp:
|
|
273
275
|
raw_yaml = fp.read()
|
|
274
|
-
job_data =
|
|
276
|
+
job_data = parse_yaml_with_catalog(render_yaml(raw_yaml))
|
|
275
277
|
jobs = list(job_data.keys())
|
|
276
278
|
|
|
277
279
|
if jobs:
|
|
@@ -338,7 +340,9 @@ def cwl_generate_wizard(productions: Optional[List[str]] = None) -> dict:
|
|
|
338
340
|
Returns:
|
|
339
341
|
Dictionary with generation parameters or None if cancelled
|
|
340
342
|
"""
|
|
341
|
-
from LbAPCommon import
|
|
343
|
+
from LbAPCommon import render_yaml
|
|
344
|
+
|
|
345
|
+
from ..utils import parse_yaml_with_catalog
|
|
342
346
|
|
|
343
347
|
welcome_wizard()
|
|
344
348
|
|
|
@@ -372,7 +376,7 @@ def cwl_generate_wizard(productions: Optional[List[str]] = None) -> dict:
|
|
|
372
376
|
if info_yaml_path.exists():
|
|
373
377
|
with open(info_yaml_path, "rt") as fp:
|
|
374
378
|
raw_yaml = fp.read()
|
|
375
|
-
job_data =
|
|
379
|
+
job_data = parse_yaml_with_catalog(render_yaml(raw_yaml))
|
|
376
380
|
jobs = list(job_data.keys())
|
|
377
381
|
|
|
378
382
|
if jobs:
|
|
@@ -43,12 +43,14 @@ class APHandler:
|
|
|
43
43
|
return [dirname(f) for f in folders]
|
|
44
44
|
|
|
45
45
|
def list_jobs(self, production: str) -> list[str]:
|
|
46
|
-
from LbAPCommon import
|
|
46
|
+
from LbAPCommon import render_yaml
|
|
47
|
+
|
|
48
|
+
from LbAPLocal.utils import parse_yaml_with_catalog
|
|
47
49
|
|
|
48
50
|
info_yaml = Path(production) / "info.yaml"
|
|
49
51
|
raw = info_yaml.read_text()
|
|
50
52
|
rendered = render_yaml(raw)
|
|
51
|
-
jobs =
|
|
53
|
+
jobs = parse_yaml_with_catalog(rendered, production, None)
|
|
52
54
|
return list(jobs.keys())
|
|
53
55
|
|
|
54
56
|
def generate_request_yaml(
|
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
###############################################################################
|
|
2
|
+
# (c) Copyright 2026 CERN for the benefit of the LHCb Collaboration #
|
|
3
|
+
# #
|
|
4
|
+
# This software is distributed under the terms of the GNU General Public #
|
|
5
|
+
# Licence version 3 (GPL Version 3), copied verbatim in the file "COPYING". #
|
|
6
|
+
# #
|
|
7
|
+
# In applying this licence, CERN does not waive the privileges and immunities #
|
|
8
|
+
# granted to it by virtue of its status as an Intergovernmental Organization #
|
|
9
|
+
# or submit itself to any jurisdiction. #
|
|
10
|
+
###############################################################################
|
|
11
|
+
"""Host side of the LHCbDIRAC reason-engine integration.
|
|
12
|
+
|
|
13
|
+
Discovers local run artifacts, runs ``reason_driver.py`` inside the
|
|
14
|
+
``lb-dirac`` (CVMFS LHCbDIRAC) environment, parses its JSON output and
|
|
15
|
+
renders the result with Rich. See the accepted design
|
|
16
|
+
"LbAPLocal Log Analysis via lb-dirac".
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from __future__ import annotations
|
|
20
|
+
|
|
21
|
+
import importlib.util
|
|
22
|
+
import json
|
|
23
|
+
import os
|
|
24
|
+
import subprocess
|
|
25
|
+
import tempfile
|
|
26
|
+
from collections.abc import Iterable
|
|
27
|
+
from dataclasses import dataclass
|
|
28
|
+
from pathlib import Path
|
|
29
|
+
|
|
30
|
+
SCHEMA_VERSION = 1
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class ReasonAnalysisUnavailable(RuntimeError):
|
|
34
|
+
"""Reason analysis could not run; callers degrade gracefully."""
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class ReasonAnalysisError(ReasonAnalysisUnavailable):
|
|
38
|
+
"""The driver produced output we cannot interpret."""
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@dataclass(frozen=True)
|
|
42
|
+
class ReasonHit:
|
|
43
|
+
type: str
|
|
44
|
+
category: str
|
|
45
|
+
match: str
|
|
46
|
+
explanation: str | None
|
|
47
|
+
issue_url: str | None
|
|
48
|
+
scope: str
|
|
49
|
+
priority: int
|
|
50
|
+
count: int
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
@dataclass(frozen=True)
|
|
54
|
+
class StepReasons:
|
|
55
|
+
name: str
|
|
56
|
+
mode: str
|
|
57
|
+
reasons: list[ReasonHit]
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
@dataclass(frozen=True)
|
|
61
|
+
class ReasonAnalysis:
|
|
62
|
+
lhcbdirac_version: str | None
|
|
63
|
+
steps: list[StepReasons]
|
|
64
|
+
errors: list[str]
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def parse_driver_output(text: str) -> ReasonAnalysis:
|
|
68
|
+
"""Parse and validate the driver's JSON output.
|
|
69
|
+
|
|
70
|
+
Raises:
|
|
71
|
+
ReasonAnalysisError: on invalid JSON, schema mismatch, or missing fields.
|
|
72
|
+
"""
|
|
73
|
+
try:
|
|
74
|
+
data = json.loads(text)
|
|
75
|
+
except json.JSONDecodeError as e:
|
|
76
|
+
raise ReasonAnalysisError(f"driver returned invalid JSON: {e}") from e
|
|
77
|
+
if not isinstance(data, dict):
|
|
78
|
+
raise ReasonAnalysisError(
|
|
79
|
+
f"driver output is not a JSON object: {type(data).__name__}"
|
|
80
|
+
)
|
|
81
|
+
if data.get("schema_version") != SCHEMA_VERSION:
|
|
82
|
+
raise ReasonAnalysisError(
|
|
83
|
+
"unexpected schema_version: "
|
|
84
|
+
f"{data.get('schema_version')!r} (expected {SCHEMA_VERSION})"
|
|
85
|
+
)
|
|
86
|
+
try:
|
|
87
|
+
steps = []
|
|
88
|
+
for raw_step in data.get("steps", []):
|
|
89
|
+
reasons = [
|
|
90
|
+
ReasonHit(
|
|
91
|
+
type=r["type"],
|
|
92
|
+
category=r["category"],
|
|
93
|
+
match=r["match"],
|
|
94
|
+
explanation=r.get("explanation"),
|
|
95
|
+
issue_url=r.get("issue_url"),
|
|
96
|
+
scope=r.get("scope", "unattributed"),
|
|
97
|
+
priority=int(r.get("priority", 0)),
|
|
98
|
+
count=int(r.get("count", 1)),
|
|
99
|
+
)
|
|
100
|
+
for r in raw_step.get("reasons", [])
|
|
101
|
+
]
|
|
102
|
+
steps.append(
|
|
103
|
+
StepReasons(
|
|
104
|
+
name=raw_step["name"],
|
|
105
|
+
mode=raw_step.get("mode", "flat"),
|
|
106
|
+
reasons=reasons,
|
|
107
|
+
)
|
|
108
|
+
)
|
|
109
|
+
engine = data.get("engine") or {}
|
|
110
|
+
return ReasonAnalysis(
|
|
111
|
+
lhcbdirac_version=engine.get("lhcbdirac_version"),
|
|
112
|
+
steps=steps,
|
|
113
|
+
errors=[str(e) for e in data.get("errors", [])],
|
|
114
|
+
)
|
|
115
|
+
except (KeyError, TypeError, ValueError, AttributeError) as e:
|
|
116
|
+
raise ReasonAnalysisError(f"malformed driver output: {e!r}") from e
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
@dataclass(frozen=True)
|
|
120
|
+
class StepInput:
|
|
121
|
+
name: str
|
|
122
|
+
log: Path
|
|
123
|
+
prodconf: Path | None
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def _find_prodconf(log: Path) -> Path | None:
|
|
127
|
+
"""Pair a step log with its prodConf JSON, mirroring job_slice.find_failed_step."""
|
|
128
|
+
exact = log.parent / f"prodConf_{log.stem}.json"
|
|
129
|
+
if exact.is_file():
|
|
130
|
+
return exact
|
|
131
|
+
candidates = sorted(log.parent.glob(f"prodConf*{log.stem}*.json"))
|
|
132
|
+
return candidates[0] if candidates else None
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def steps_from_log_files(log_files: Iterable[Path]) -> list[StepInput]:
|
|
136
|
+
"""Build driver step inputs from application log paths."""
|
|
137
|
+
steps = []
|
|
138
|
+
for log in set(log_files):
|
|
139
|
+
steps.append(StepInput(name=log.stem, log=log, prodconf=_find_prodconf(log)))
|
|
140
|
+
steps.sort(key=lambda s: s.name)
|
|
141
|
+
return steps
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def steps_from_path(path: Path) -> list[StepInput]:
|
|
145
|
+
"""Discover step inputs from a run directory or a single log file.
|
|
146
|
+
|
|
147
|
+
A nonexistent path yields an empty list; callers decide how to report that.
|
|
148
|
+
"""
|
|
149
|
+
if path.is_file():
|
|
150
|
+
return steps_from_log_files([path])
|
|
151
|
+
return steps_from_log_files(path.rglob("*.log"))
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
DRIVER_TIMEOUT = 120.0
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def reason_analysis_disabled() -> bool:
|
|
158
|
+
"""True when the user opted out via LBAP_NO_REASON_ANALYSIS."""
|
|
159
|
+
return bool(os.environ.get("LBAP_NO_REASON_ANALYSIS"))
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def run_reason_analysis(
|
|
163
|
+
steps: list[StepInput], *, timeout: float = DRIVER_TIMEOUT
|
|
164
|
+
) -> ReasonAnalysis:
|
|
165
|
+
"""Run reason_driver.py under lb-dirac and return the parsed result.
|
|
166
|
+
|
|
167
|
+
Raises:
|
|
168
|
+
ReasonAnalysisUnavailable: for every infrastructure failure --
|
|
169
|
+
missing lb-dirac, timeout, driver crash, or malformed output.
|
|
170
|
+
"""
|
|
171
|
+
if not steps:
|
|
172
|
+
raise ReasonAnalysisUnavailable("no log files to analyse")
|
|
173
|
+
spec = importlib.util.find_spec("LbAPLocal.reason_driver")
|
|
174
|
+
if spec is None or spec.origin is None:
|
|
175
|
+
raise ReasonAnalysisUnavailable("could not locate LbAPLocal.reason_driver")
|
|
176
|
+
|
|
177
|
+
request = {
|
|
178
|
+
"steps": [
|
|
179
|
+
{
|
|
180
|
+
"name": s.name,
|
|
181
|
+
"log": str(s.log),
|
|
182
|
+
"prodconf": str(s.prodconf) if s.prodconf else None,
|
|
183
|
+
}
|
|
184
|
+
for s in steps
|
|
185
|
+
]
|
|
186
|
+
}
|
|
187
|
+
with tempfile.NamedTemporaryFile(
|
|
188
|
+
"w", suffix=".json", prefix="lbap-reasons-", delete=False
|
|
189
|
+
) as f:
|
|
190
|
+
json.dump(request, f)
|
|
191
|
+
request_path = f.name
|
|
192
|
+
try:
|
|
193
|
+
try:
|
|
194
|
+
result = subprocess.run(
|
|
195
|
+
["lb-dirac", "python", spec.origin, request_path],
|
|
196
|
+
capture_output=True,
|
|
197
|
+
text=True,
|
|
198
|
+
timeout=timeout,
|
|
199
|
+
)
|
|
200
|
+
except FileNotFoundError as e:
|
|
201
|
+
raise ReasonAnalysisUnavailable(
|
|
202
|
+
"lb-dirac not found (is CVMFS available?)"
|
|
203
|
+
) from e
|
|
204
|
+
except subprocess.TimeoutExpired as e:
|
|
205
|
+
raise ReasonAnalysisUnavailable(
|
|
206
|
+
f"reason analysis timed out after {timeout:.0f}s"
|
|
207
|
+
) from e
|
|
208
|
+
if result.returncode != 0:
|
|
209
|
+
detail = (result.stderr or result.stdout or "").strip()
|
|
210
|
+
raise ReasonAnalysisUnavailable(
|
|
211
|
+
f"driver failed (exit {result.returncode}): {detail[:500]}"
|
|
212
|
+
)
|
|
213
|
+
return parse_driver_output(result.stdout)
|
|
214
|
+
finally:
|
|
215
|
+
os.unlink(request_path)
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
_CATEGORY_STYLES = {
|
|
219
|
+
"KnownUnrecoverableFailure": "red",
|
|
220
|
+
"UnknownFailure": "red",
|
|
221
|
+
"EventTimeout": "dark_orange",
|
|
222
|
+
"RecoverableFailureWithManualIntervention": "yellow",
|
|
223
|
+
"WorkerNodeIssue": "yellow",
|
|
224
|
+
"RecoverableFailure": "yellow",
|
|
225
|
+
}
|
|
226
|
+
_MAX_MATCH_DISPLAY_LINES = 6
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
def render_reason_analysis(analysis: ReasonAnalysis, console) -> None:
|
|
230
|
+
"""Render a ReasonAnalysis with Rich panels (one per step with findings)."""
|
|
231
|
+
from rich.console import Group
|
|
232
|
+
from rich.panel import Panel
|
|
233
|
+
from rich.text import Text
|
|
234
|
+
|
|
235
|
+
for step in analysis.steps:
|
|
236
|
+
if not step.reasons:
|
|
237
|
+
console.print(
|
|
238
|
+
f"[dim]Reason analysis ({step.name}): "
|
|
239
|
+
"no known failure patterns matched[/dim]"
|
|
240
|
+
)
|
|
241
|
+
continue
|
|
242
|
+
parts = []
|
|
243
|
+
border = "yellow"
|
|
244
|
+
for hit in step.reasons:
|
|
245
|
+
style = _CATEGORY_STYLES.get(hit.category, "yellow")
|
|
246
|
+
if style == "red":
|
|
247
|
+
border = "red"
|
|
248
|
+
header = Text()
|
|
249
|
+
header.append(hit.type, style=f"bold {style}")
|
|
250
|
+
header.append(f" ({hit.category}, {hit.scope}", style="dim")
|
|
251
|
+
if hit.count > 1:
|
|
252
|
+
header.append(f", {hit.count}×", style="dim")
|
|
253
|
+
header.append(")", style="dim")
|
|
254
|
+
parts.append(header)
|
|
255
|
+
if hit.explanation:
|
|
256
|
+
parts.append(Text(hit.explanation))
|
|
257
|
+
if hit.issue_url:
|
|
258
|
+
parts.append(Text(hit.issue_url, style="cyan underline"))
|
|
259
|
+
match_lines = hit.match.strip().splitlines()
|
|
260
|
+
snippet = "\n".join(match_lines[:_MAX_MATCH_DISPLAY_LINES])
|
|
261
|
+
if len(match_lines) > _MAX_MATCH_DISPLAY_LINES:
|
|
262
|
+
snippet += (
|
|
263
|
+
f"\n… ({len(match_lines) - _MAX_MATCH_DISPLAY_LINES} more lines)"
|
|
264
|
+
)
|
|
265
|
+
parts.append(Text(snippet, style="dim"))
|
|
266
|
+
parts.append(Text())
|
|
267
|
+
console.print(
|
|
268
|
+
Panel(
|
|
269
|
+
Group(*parts),
|
|
270
|
+
title=f"[bold]Reason analysis: {step.name}[/bold]"
|
|
271
|
+
+ (" [dim](unsegmented)[/dim]" if step.mode == "flat" else ""),
|
|
272
|
+
title_align="left",
|
|
273
|
+
border_style=border,
|
|
274
|
+
)
|
|
275
|
+
)
|
|
276
|
+
for err in analysis.errors:
|
|
277
|
+
console.print(f"[dim]Reason analysis: {err}[/dim]")
|
|
278
|
+
if analysis.lhcbdirac_version:
|
|
279
|
+
console.print(
|
|
280
|
+
f"[dim]Reason engine: LHCbDIRAC {analysis.lhcbdirac_version}[/dim]"
|
|
281
|
+
)
|