Commit 6bc04064530d2138f207be18ba7325863afd46b7
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.
Showing
3 changed files
with
48 additions
and
1 deletions
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: |