Skip to content

Commit

Permalink
Add check_log_for_errors to detect and handle multiple errors
Browse files Browse the repository at this point in the history
  • Loading branch information
Flamefire committed Dec 10, 2019
1 parent a1d03f3 commit 73c02f0
Show file tree
Hide file tree
Showing 2 changed files with 111 additions and 1 deletion.
56 changes: 56 additions & 0 deletions easybuild/tools/run.py
Original file line number Diff line number Diff line change
Expand Up @@ -589,3 +589,59 @@ def parse_log_for_error(txt, regExp=None, stdout=True, msg=None):
(regExp, '\n'.join([x[0] for x in res])))

return res


def check_log_for_errors(logTxt, regExps):
"""
Check logTxt for messages matching regExps in order and do appropriate action
:param logTxt: String containing the log, will be split into individual lines
:param regExps: List of: regular expressions (as RE or string) to error on,
or tuple of regular expression and action (any of [IGNORE, WARN, ERROR])
"""
global errors_found_in_log

def is_regexp_object(objToTest):
try:
objToTest.match('')
return True
except AttributeError:
return False

# Avoid accidentally passing a single element
assert isinstance(regExps, list), "regExps must be a list"
regExpTuples = []
for cur in regExps:
try:
if isinstance(cur, str):
regExpTuples.append((re.compile(cur), ERROR))
elif is_regexp_object(cur):
regExpTuples.append((cur, ERROR))
elif len(cur) != 2:
raise TypeError("Invalid tuple")
elif not isinstance(cur[0], str) and not is_regexp_object(cur[0]):
raise TypeError("Invalid RegExp in tuple")
elif cur[1] not in (IGNORE, WARN, ERROR):
raise TypeError("Invalid action in tuple")
else:
regExpTuples.append((re.compile(cur[0]) if isinstance(cur[0], str) else cur[0], cur[1]))
except TypeError:
raise EasyBuildError("Invalid input: No RegExp or tuple of RegExp and action: %s" % str(cur))
warnings = []
errors = []
for l in logTxt.split('\n'):
for regExp, action in regExpTuples:
m = regExp.search(l)
if m:
if action == ERROR:
errors.append(l)
elif action == WARN:
warnings.append(l)
break
errors_found_in_log += len(warnings) + len(errors)
if warnings:
_log.warning("Found %s potential errors in command output (output: %s)" %
(len(warnings), ", ".join(warnings)))
if errors:
raise EasyBuildError("Found %s errors in command output (output: %s)" %
(len(errors), ", ".join(errors)))

56 changes: 55 additions & 1 deletion test/framework/run.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,8 @@
import easybuild.tools.utilities
from easybuild.tools.build_log import EasyBuildError, init_logging, stop_logging
from easybuild.tools.filetools import adjust_permissions, read_file, write_file
from easybuild.tools.run import get_output_from_process, run_cmd, run_cmd_qa, parse_log_for_error
from easybuild.tools.run import get_output_from_process, run_cmd, run_cmd_qa, parse_log_for_error, check_log_for_errors
from easybuild.tools.config import ERROR, IGNORE, WARN


class RunTest(EnhancedTestCase):
Expand Down Expand Up @@ -508,6 +509,59 @@ def test_run_cmd_stream(self):
])
self.assertEqual(stdout, expected)

def test_check_log_for_errors(self):
fd, logfile = tempfile.mkstemp(suffix='.log', prefix='eb-test-')
os.close(fd)

self.assertErrorRegex(EasyBuildError, "Invalid input:", check_log_for_errors, "", [42])
self.assertErrorRegex(EasyBuildError, "Invalid input:", check_log_for_errors, "", [(42, IGNORE)])
self.assertErrorRegex(EasyBuildError, "Invalid input:", check_log_for_errors, "", [("42", "invalid-mode")])
self.assertErrorRegex(EasyBuildError, "Invalid input:", check_log_for_errors, "", [("42", IGNORE, "")])

input_text = "\n".join([
"OK",
"error found",
"test failed",
"msg: allowed-test failed",
"enabling -Werror",
"the process crashed with 0"
])
expected_error_msg = r"Found 2 errors in command output \(output: error found, the process crashed with 0\)"

self.assertErrorRegex(EasyBuildError, expected_error_msg, check_log_for_errors, input_text,
[r"\b(error|crashed)\b"])
self.assertErrorRegex(EasyBuildError, expected_error_msg, check_log_for_errors, input_text,
[re.compile(r"\b(error|crashed)\b")])
self.assertErrorRegex(EasyBuildError, expected_error_msg, check_log_for_errors, input_text,
[(r"\b(error|crashed)\b", ERROR)])
self.assertErrorRegex(EasyBuildError, expected_error_msg, check_log_for_errors, input_text,
[(re.compile(r"\b(error|crashed)\b"), ERROR)])

expected_error_msg = "Found 2 potential errors in command output " \
"(output: error found, the process crashed with 0)"
init_logging(logfile, silent=True)
check_log_for_errors(input_text, [(r"\b(error|crashed)\b", WARN)])
stop_logging(logfile)
self.assertTrue(expected_error_msg in read_file(logfile))
write_file(logfile, '')
init_logging(logfile, silent=True)
check_log_for_errors(input_text, [(re.compile(r"\b(error|crashed)\b"), WARN)])
stop_logging(logfile)
self.assertTrue(expected_error_msg in read_file(logfile))

expected_error_msg = r"Found 2 errors in command output \(output: error found, test failed\)"
write_file(logfile, '')
init_logging(logfile, silent=True)
self.assertErrorRegex(EasyBuildError, expected_error_msg, check_log_for_errors, input_text, [
r"\berror\b",
(r"\ballowed-test failed\b", IGNORE),
(re.compile(r"\bCRASHED\b", re.I), WARN),
"fail"
])
stop_logging(logfile)
expected_error_msg = "Found 1 potential errors in command output (output: the process crashed with 0)"
self.assertTrue(expected_error_msg in read_file(logfile))


def suite():
""" returns all the testcases in this module """
Expand Down

0 comments on commit 73c02f0

Please sign in to comment.