Skip to content

Commit 7f40924

Browse files
committed
Add check_log_for_errors to detect and handle multiple errors
1 parent a1d03f3 commit 7f40924

2 files changed

Lines changed: 95 additions & 1 deletion

File tree

easybuild/tools/run.py

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -589,3 +589,55 @@ def parse_log_for_error(txt, regExp=None, stdout=True, msg=None):
589589
(regExp, '\n'.join([x[0] for x in res])))
590590

591591
return res
592+
593+
def check_log_for_errors(logTxt, regExps):
594+
"""
595+
Check logTxt for messages matching regExps in order and do appropriate action
596+
:param logTxt: String containing the log, will be split into individual lines
597+
:param regExps: List of: regular expressions (as RE or string) to error on,
598+
or tuple of regular expression and action (any of [IGNORE, WARN, ERROR])
599+
"""
600+
global errors_found_in_log
601+
602+
def is_regexp_object(objToTest):
603+
try:
604+
objToTest.match('')
605+
return True
606+
except AttributeError:
607+
return False
608+
609+
assert isinstance(regExps, list), "regExps must be a list" # Avoid accidentally passing a single element
610+
regExpTuples = []
611+
for cur in regExps:
612+
try:
613+
if isinstance(cur, str):
614+
regExpTuples.append((re.compile(cur), ERROR))
615+
elif is_regexp_object(cur):
616+
regExpTuples.append((cur, ERROR))
617+
elif len(cur) != 2:
618+
raise TypeError("Invalid tuple")
619+
elif not isinstance(cur[0], str) and not is_regexp_object(cur[0]):
620+
raise TypeError("Invalid RegExp in tuple")
621+
elif cur[1] not in (IGNORE, WARN, ERROR):
622+
raise TypeError("Invalid action in tuple")
623+
else:
624+
regExpTuples.append((re.compile(cur[0]) if isinstance(cur[0], str) else cur[0], cur[1]))
625+
except TypeError:
626+
raise EasyBuildError("Invalid input: No RegExp or tuple of RegExp and action: %s" % str(cur))
627+
warnings = []
628+
errors = []
629+
for l in logTxt.split('\n'):
630+
for regExp, action in regExpTuples:
631+
m = regExp.search(l)
632+
if m:
633+
if action == ERROR:
634+
errors.append(l)
635+
elif action == WARN:
636+
warnings.append(l)
637+
break
638+
errors_found_in_log += len(warnings) + len(errors)
639+
if warnings:
640+
_log.warning("Found %s potential errors in command output (output: %s)" % (len(warnings), ", ".join(warnings)))
641+
if errors:
642+
raise EasyBuildError("Found %s errors in command output (output: %s)" % (len(errors), ", ".join(errors)))
643+

test/framework/run.py

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,8 @@
4646
import easybuild.tools.utilities
4747
from easybuild.tools.build_log import EasyBuildError, init_logging, stop_logging
4848
from easybuild.tools.filetools import adjust_permissions, read_file, write_file
49-
from easybuild.tools.run import get_output_from_process, run_cmd, run_cmd_qa, parse_log_for_error
49+
from easybuild.tools.run import get_output_from_process, run_cmd, run_cmd_qa, parse_log_for_error, check_log_for_errors
50+
from easybuild.tools.config import ERROR, IGNORE, WARN
5051

5152

5253
class RunTest(EnhancedTestCase):
@@ -508,6 +509,47 @@ def test_run_cmd_stream(self):
508509
])
509510
self.assertEqual(stdout, expected)
510511

512+
def test_check_log_for_errors(self):
513+
fd, logfile = tempfile.mkstemp(suffix='.log', prefix='eb-test-')
514+
os.close(fd)
515+
516+
self.assertErrorRegex(EasyBuildError, "Invalid input:", check_log_for_errors, "", [42])
517+
self.assertErrorRegex(EasyBuildError, "Invalid input:", check_log_for_errors, "", [(42, IGNORE)])
518+
self.assertErrorRegex(EasyBuildError, "Invalid input:", check_log_for_errors, "", [("42", "invalid-mode")])
519+
self.assertErrorRegex(EasyBuildError, "Invalid input:", check_log_for_errors, "", [("42", IGNORE, "")])
520+
521+
input_text = "\n".join(["OK", "error found", "test failed", "msg: allowed-test failed", "enabling -Werror", "the process crashed with 0"])
522+
expected_error_msg = r"Found 2 errors in command output \(output: error found, the process crashed with 0\)"
523+
524+
self.assertErrorRegex(EasyBuildError, expected_error_msg, check_log_for_errors, input_text, [r"\b(error|crashed)\b"])
525+
self.assertErrorRegex(EasyBuildError, expected_error_msg, check_log_for_errors, input_text, [re.compile(r"\b(error|crashed)\b")])
526+
self.assertErrorRegex(EasyBuildError, expected_error_msg, check_log_for_errors, input_text, [(r"\b(error|crashed)\b", ERROR)])
527+
self.assertErrorRegex(EasyBuildError, expected_error_msg, check_log_for_errors, input_text, [(re.compile(r"\b(error|crashed)\b"), ERROR)])
528+
529+
expected_error_msg = "Found 2 potential errors in command output (output: error found, the process crashed with 0)"
530+
init_logging(logfile, silent=True)
531+
check_log_for_errors(input_text, [(r"\b(error|crashed)\b", WARN)])
532+
stop_logging(logfile)
533+
self.assertTrue(expected_error_msg in read_file(logfile))
534+
write_file(logfile, '')
535+
init_logging(logfile, silent=True)
536+
check_log_for_errors(input_text, [(re.compile(r"\b(error|crashed)\b"), WARN)])
537+
stop_logging(logfile)
538+
self.assertTrue(expected_error_msg in read_file(logfile))
539+
540+
expected_error_msg = r"Found 2 errors in command output \(output: error found, test failed\)"
541+
write_file(logfile, '')
542+
init_logging(logfile, silent=True)
543+
self.assertErrorRegex(EasyBuildError, expected_error_msg, check_log_for_errors, input_text, [
544+
r"\berror\b",
545+
(r"\ballowed-test failed\b", IGNORE),
546+
(re.compile(r"\bCRASHED\b", re.I), WARN),
547+
"fail"
548+
])
549+
stop_logging(logfile)
550+
expected_error_msg = "Found 1 potential errors in command output (output: the process crashed with 0)"
551+
self.assertTrue(expected_error_msg in read_file(logfile))
552+
511553

512554
def suite():
513555
""" returns all the testcases in this module """

0 commit comments

Comments
 (0)