|
33 | 33 | _warn_no_match, |
34 | 34 | check_arkane_aec, |
35 | 35 | check_arkane_bacs, |
| 36 | + filter_real_stderr_lines, |
36 | 37 | get_arkane_model_chemistry, |
37 | 38 | ) |
38 | 39 | from unittest.mock import patch |
@@ -869,5 +870,108 @@ def test_dh_rxn_uses_kJmol_e_elect_for_composite_species(self): |
869 | 870 | self.assertAlmostEqual(rxn.dh_rxn298, -1e5, places=6) |
870 | 871 |
|
871 | 872 |
|
| 873 | +class TestFilterRealStderrLines(unittest.TestCase): |
| 874 | + """``filter_real_stderr_lines`` strips harmless shell-init / library noise |
| 875 | + from a subprocess's stderr so ARC doesn't classify a successful Arkane run |
| 876 | + as a failure.""" |
| 877 | + |
| 878 | + def test_open_babel_unusual_valence_warning_is_noise(self): |
| 879 | + """Existing carve-out: Open Babel's InChI-code warnings are harmless.""" |
| 880 | + lines = [ |
| 881 | + "==============================", |
| 882 | + "*** Open Babel Warning in InChI code", |
| 883 | + " #1 :Accepted unusual valence(s): C(2)", |
| 884 | + "==============================", |
| 885 | + ] |
| 886 | + self.assertEqual(filter_real_stderr_lines(lines), []) |
| 887 | + |
| 888 | + def test_lmod_unknown_module_warning_is_noise(self): |
| 889 | + """Regression: a stale ``module load openmpi`` in shell init prints a |
| 890 | + Lmod block to stderr. ARC was treating it as a fatal Arkane failure |
| 891 | + even though Arkane completed successfully.""" |
| 892 | + lines = [ |
| 893 | + 'Lmod has detected the following error: The following module(s) are unknown:', |
| 894 | + '"openmpi"', |
| 895 | + '', |
| 896 | + 'Please check the spelling or version number. Also try "module spider ..."', |
| 897 | + 'It is also possible your cache file is out-of-date; it may help to try:', |
| 898 | + ' $ module --ignore_cache load "openmpi"', |
| 899 | + '', |
| 900 | + 'Also make sure that all modulefiles written in TCL start with the string', |
| 901 | + '#%Module', |
| 902 | + ] |
| 903 | + self.assertEqual(filter_real_stderr_lines(lines), []) |
| 904 | + |
| 905 | + def test_conda_libmamba_solver_load_failure_is_noise(self): |
| 906 | + """A broken `_sqlite3` in the base conda (missing `sqlite3_deserialize`) |
| 907 | + causes conda-libmamba-solver to fail loading and emit a stderr line on |
| 908 | + every `conda run` invocation. The subprocess itself succeeds — conda |
| 909 | + falls back to the classic solver — so this line is benign noise.""" |
| 910 | + lines = [ |
| 911 | + ('Error while loading conda entry point: conda-libmamba-solver ' |
| 912 | + '(/home/alon/miniconda3/lib/python3.11/lib-dynload/' |
| 913 | + '_sqlite3.cpython-311-x86_64-linux-gnu.so: undefined symbol: ' |
| 914 | + 'sqlite3_deserialize)'), |
| 915 | + ] * 4 |
| 916 | + self.assertEqual(filter_real_stderr_lines(lines), []) |
| 917 | + |
| 918 | + def test_conda_entry_point_noise_does_not_mask_real_error(self): |
| 919 | + """Conda-entry-point noise is filtered, but a real traceback emitted by |
| 920 | + the same script must still survive.""" |
| 921 | + lines = [ |
| 922 | + 'Error while loading conda entry point: conda-libmamba-solver (...)', |
| 923 | + 'Traceback (most recent call last):', |
| 924 | + 'RuntimeError: thermo failed', |
| 925 | + ] |
| 926 | + result = filter_real_stderr_lines(lines) |
| 927 | + self.assertEqual( |
| 928 | + result, |
| 929 | + ['Traceback (most recent call last):', 'RuntimeError: thermo failed'], |
| 930 | + ) |
| 931 | + |
| 932 | + def test_real_error_is_preserved(self): |
| 933 | + """A genuine traceback line passes through.""" |
| 934 | + lines = [ |
| 935 | + "==============================", |
| 936 | + "*** Open Babel Warning", |
| 937 | + "Traceback (most recent call last):", |
| 938 | + ' File "arkane/main.py", line 123, in run', |
| 939 | + "RuntimeError: Could not parse output", |
| 940 | + ] |
| 941 | + result = filter_real_stderr_lines(lines) |
| 942 | + self.assertIn("Traceback (most recent call last):", result) |
| 943 | + self.assertIn("RuntimeError: Could not parse output", result) |
| 944 | + self.assertNotIn("==============================", result) |
| 945 | + |
| 946 | + def test_mixed_lmod_and_real_error_keeps_only_real(self): |
| 947 | + """When Lmod noise and a real error coexist, only the real error |
| 948 | + survives. (The user's H/H2/OH composite SP failures should still |
| 949 | + surface even if the shell init is noisy.)""" |
| 950 | + lines = [ |
| 951 | + 'Lmod has detected the following error: ...', |
| 952 | + '"openmpi"', |
| 953 | + '#%Module', |
| 954 | + "AttributeError: 'NoneType' object has no attribute 'thermo'", |
| 955 | + ] |
| 956 | + result = filter_real_stderr_lines(lines) |
| 957 | + self.assertEqual( |
| 958 | + result, |
| 959 | + ["AttributeError: 'NoneType' object has no attribute 'thermo'"], |
| 960 | + ) |
| 961 | + |
| 962 | + def test_empty_and_whitespace_only_lines_dropped(self): |
| 963 | + self.assertEqual(filter_real_stderr_lines(["", " ", "\t"]), []) |
| 964 | + |
| 965 | + def test_accepts_string_as_well_as_list(self): |
| 966 | + """Some call sites pass a multiline string; the helper splits it.""" |
| 967 | + s = ( |
| 968 | + 'Lmod has detected the following error: blah\n' |
| 969 | + '"openmpi"\n' |
| 970 | + '\n' |
| 971 | + 'Genuine error: foo\n' |
| 972 | + ) |
| 973 | + self.assertEqual(filter_real_stderr_lines(s), ["Genuine error: foo"]) |
| 974 | + |
| 975 | + |
872 | 976 | if __name__ == '__main__': |
873 | 977 | unittest.main(testRunner=unittest.TextTestRunner(verbosity=2)) |
0 commit comments