Skip to content

Commit 3fb666e

Browse files
committed
Add check_log_for_errors to detect and handle multiple errors
1 parent a1d03f3 commit 3fb666e

File tree

2 files changed

+111
-1
lines changed

2 files changed

+111
-1
lines changed

easybuild/tools/run.py

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

test/framework/run.py

Lines changed: 55 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,59 @@ 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([
522+
"OK",
523+
"error found",
524+
"test failed",
525+
"msg: allowed-test failed",
526+
"enabling -Werror",
527+
"the process crashed with 0"
528+
])
529+
expected_error_msg = r"Found 2 errors in command output \(output: error found, the process crashed with 0\)"
530+
531+
self.assertErrorRegex(EasyBuildError, expected_error_msg, check_log_for_errors, input_text,
532+
[r"\b(error|crashed)\b"])
533+
self.assertErrorRegex(EasyBuildError, expected_error_msg, check_log_for_errors, input_text,
534+
[re.compile(r"\b(error|crashed)\b")])
535+
self.assertErrorRegex(EasyBuildError, expected_error_msg, check_log_for_errors, input_text,
536+
[(r"\b(error|crashed)\b", ERROR)])
537+
self.assertErrorRegex(EasyBuildError, expected_error_msg, check_log_for_errors, input_text,
538+
[(re.compile(r"\b(error|crashed)\b"), ERROR)])
539+
540+
expected_error_msg = "Found 2 potential errors in command output " \
541+
"(output: error found, the process crashed with 0)"
542+
init_logging(logfile, silent=True)
543+
check_log_for_errors(input_text, [(r"\b(error|crashed)\b", WARN)])
544+
stop_logging(logfile)
545+
self.assertTrue(expected_error_msg in read_file(logfile))
546+
write_file(logfile, '')
547+
init_logging(logfile, silent=True)
548+
check_log_for_errors(input_text, [(re.compile(r"\b(error|crashed)\b"), WARN)])
549+
stop_logging(logfile)
550+
self.assertTrue(expected_error_msg in read_file(logfile))
551+
552+
expected_error_msg = r"Found 2 errors in command output \(output: error found, test failed\)"
553+
write_file(logfile, '')
554+
init_logging(logfile, silent=True)
555+
self.assertErrorRegex(EasyBuildError, expected_error_msg, check_log_for_errors, input_text, [
556+
r"\berror\b",
557+
(r"\ballowed-test failed\b", IGNORE),
558+
(re.compile(r"\bCRASHED\b", re.I), WARN),
559+
"fail"
560+
])
561+
stop_logging(logfile)
562+
expected_error_msg = "Found 1 potential errors in command output (output: the process crashed with 0)"
563+
self.assertTrue(expected_error_msg in read_file(logfile))
564+
511565

512566
def suite():
513567
""" returns all the testcases in this module """

0 commit comments

Comments
 (0)