Commit 6bc04064530d2138f207be18ba7325863afd46b7

Authored by Christian Herdtweck
1 parent 3c3b7093

tests: Add warnings to log_helper test

Encountered an example where a 3rd-party library issued a warning that
messed up the json output. Create test to reproduce this.
tests/common/log_helper/log_helper_test_imported.py
@@ -4,6 +4,7 @@ by the main test file @@ -4,6 +4,7 @@ by the main test file
4 """ 4 """
5 5
6 from oletools.common.log_helper import log_helper 6 from oletools.common.log_helper import log_helper
  7 +import warnings
7 8
8 DEBUG_MESSAGE = 'imported: debug log' 9 DEBUG_MESSAGE = 'imported: debug log'
9 INFO_MESSAGE = 'imported: info log' 10 INFO_MESSAGE = 'imported: info log'
@@ -11,7 +12,10 @@ WARNING_MESSAGE = 'imported: warning log' @@ -11,7 +12,10 @@ WARNING_MESSAGE = 'imported: warning log'
11 ERROR_MESSAGE = 'imported: error log' 12 ERROR_MESSAGE = 'imported: error log'
12 CRITICAL_MESSAGE = 'imported: critical log' 13 CRITICAL_MESSAGE = 'imported: critical log'
13 RESULT_MESSAGE = 'imported: result log' 14 RESULT_MESSAGE = 'imported: result log'
  15 +
14 RESULT_TYPE = 'imported: result' 16 RESULT_TYPE = 'imported: result'
  17 +ACTUAL_WARNING = 'Feature XYZ provided by this module might be deprecated at '\
  18 + 'some point in the future ... or not'
15 19
16 logger = log_helper.get_or_create_silent_logger('test_imported') 20 logger = log_helper.get_or_create_silent_logger('test_imported')
17 21
@@ -27,3 +31,7 @@ def log(): @@ -27,3 +31,7 @@ def log():
27 logger.error(ERROR_MESSAGE) 31 logger.error(ERROR_MESSAGE)
28 logger.critical(CRITICAL_MESSAGE) 32 logger.critical(CRITICAL_MESSAGE)
29 logger.info(RESULT_MESSAGE, type=RESULT_TYPE) 33 logger.info(RESULT_MESSAGE, type=RESULT_TYPE)
  34 +
  35 +
  36 +def warn():
  37 + warnings.warn(ACTUAL_WARNING)
tests/common/log_helper/log_helper_test_main.py
@@ -2,6 +2,7 @@ @@ -2,6 +2,7 @@
2 2
3 import sys 3 import sys
4 import logging 4 import logging
  5 +import warnings
5 from tests.common.log_helper import log_helper_test_imported 6 from tests.common.log_helper import log_helper_test_imported
6 from oletools.common.log_helper import log_helper 7 from oletools.common.log_helper import log_helper
7 8
@@ -11,7 +12,9 @@ WARNING_MESSAGE = 'main: warning log' @@ -11,7 +12,9 @@ WARNING_MESSAGE = 'main: warning log'
11 ERROR_MESSAGE = 'main: error log' 12 ERROR_MESSAGE = 'main: error log'
12 CRITICAL_MESSAGE = 'main: critical log' 13 CRITICAL_MESSAGE = 'main: critical log'
13 RESULT_MESSAGE = 'main: result log' 14 RESULT_MESSAGE = 'main: result log'
  15 +
14 RESULT_TYPE = 'main: result' 16 RESULT_TYPE = 'main: result'
  17 +ACTUAL_WARNING = 'Warnings can pop up anywhere, have to be prepared!'
15 18
16 logger = log_helper.get_or_create_silent_logger('test_main') 19 logger = log_helper.get_or_create_silent_logger('test_main')
17 20
@@ -24,7 +27,8 @@ def enable_logging(): @@ -24,7 +27,8 @@ def enable_logging():
24 27
25 def main(args): 28 def main(args):
26 """ 29 """
27 - Try to cover possible logging scenarios. For each scenario covered, here's the expected args and outcome: 30 + Try to cover possible logging scenarios. For each scenario covered, here's
  31 + the expected args and outcome:
28 - Log without enabling: ['<level>'] 32 - Log without enabling: ['<level>']
29 * logging when being imported - should never print 33 * logging when being imported - should never print
30 - Log as JSON without enabling: ['as-json', '<level>'] 34 - Log as JSON without enabling: ['as-json', '<level>']
@@ -35,6 +39,8 @@ def main(args): @@ -35,6 +39,8 @@ def main(args):
35 * logging as JSON when being run as script - should log messages as JSON 39 * logging as JSON when being run as script - should log messages as JSON
36 - Enable, log as JSON and throw: ['enable', 'as-json', 'throw', '<level>'] 40 - Enable, log as JSON and throw: ['enable', 'as-json', 'throw', '<level>']
37 * should produce JSON-compatible output, even after an unhandled exception 41 * should produce JSON-compatible output, even after an unhandled exception
  42 + - Enable, log as JSON and warn: ['enable', 'as-json', 'warn', '<level>']
  43 + * should produce JSON-compatible output, even after a warning
38 """ 44 """
39 45
40 # the level should always be the last argument passed 46 # the level should always be the last argument passed
@@ -42,6 +48,7 @@ def main(args): @@ -42,6 +48,7 @@ def main(args):
42 use_json = 'as-json' in args 48 use_json = 'as-json' in args
43 throw = 'throw' in args 49 throw = 'throw' in args
44 percent_autoformat = '%-autoformat' in args 50 percent_autoformat = '%-autoformat' in args
  51 + warn = 'warn' in args
45 52
46 log_helper_test_imported.logger.setLevel(logging.ERROR) 53 log_helper_test_imported.logger.setLevel(logging.ERROR)
47 54
@@ -53,6 +60,10 @@ def main(args): @@ -53,6 +60,10 @@ def main(args):
53 if throw: 60 if throw:
54 raise Exception('An exception occurred before ending the logging') 61 raise Exception('An exception occurred before ending the logging')
55 62
  63 + if warn:
  64 + warnings.warn(ACTUAL_WARNING)
  65 + log_helper_test_imported.warn()
  66 +
56 log_helper.end_logging() 67 log_helper.end_logging()
57 68
58 69
tests/common/log_helper/test_log_helper.py
@@ -142,6 +142,32 @@ class TestLogHelper(unittest.TestCase): @@ -142,6 +142,32 @@ class TestLogHelper(unittest.TestCase):
142 self.assertIn('INFO:test_main:main: info log', output) 142 self.assertIn('INFO:test_main:main: info log', output)
143 self.assertIn('INFO:test_imported:imported: info log', output) 143 self.assertIn('INFO:test_imported:imported: info log', output)
144 144
  145 + def test_json_correct_on_warnings(self):
  146 + """
  147 + Test that even on warnings our JSON is always correct
  148 + """
  149 + output = self._run_test(['enable', 'as-json', 'warn', 'warning'])
  150 + expected_messages = [
  151 + log_helper_test_main.WARNING_MESSAGE,
  152 + log_helper_test_main.ERROR_MESSAGE,
  153 + log_helper_test_main.CRITICAL_MESSAGE,
  154 + log_helper_test_imported.WARNING_MESSAGE,
  155 + log_helper_test_imported.ERROR_MESSAGE,
  156 + log_helper_test_imported.CRITICAL_MESSAGE,
  157 + ]
  158 +
  159 + for msg in expected_messages:
  160 + self.assertIn(msg, output)
  161 +
  162 + # last two entries of output should be warnings
  163 + jout = json.loads(output)
  164 + self.assertEqual(jout[-2]['level'], 'WARNING')
  165 + self.assertEqual(jout[-1]['level'], 'WARNING')
  166 + self.assertEqual(jout[-2]['type'], 'warning')
  167 + self.assertEqual(jout[-1]['type'], 'warning')
  168 + self.assertIn(log_helper_test_main.ACTUAL_WARNING, jout[-2]['msg'])
  169 + self.assertIn(log_helper_test_imported.ACTUAL_WARNING, jout[-1]['msg'])
  170 +
145 def _assert_json_messages(self, output, messages): 171 def _assert_json_messages(self, output, messages):
146 try: 172 try:
147 json_data = json.loads(output) 173 json_data = json.loads(output)
@@ -163,6 +189,8 @@ class TestLogHelper(unittest.TestCase): @@ -163,6 +189,8 @@ class TestLogHelper(unittest.TestCase):
163 When arg `run_third_party` is `True`, we do not run the `TEST_FILE` as 189 When arg `run_third_party` is `True`, we do not run the `TEST_FILE` as
164 main moduel but the `TEST_FILE_3RD_PARTY` and return contents of 190 main moduel but the `TEST_FILE_3RD_PARTY` and return contents of
165 `stderr` instead of `stdout`. 191 `stderr` instead of `stdout`.
  192 +
  193 + TODO: use tests.utils.call_and_capture
166 """ 194 """
167 all_args = [PYTHON_EXECUTABLE, ] 195 all_args = [PYTHON_EXECUTABLE, ]
168 if run_third_party: 196 if run_third_party: