1# Copyright 2017 The Abseil Authors. 2# 3# Licensed under the Apache License, Version 2.0 (the "License"); 4# you may not use this file except in compliance with the License. 5# You may obtain a copy of the License at 6# 7# http://www.apache.org/licenses/LICENSE-2.0 8# 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, 11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12# See the License for the specific language governing permissions and 13# limitations under the License. 14 15"""Test of logging behavior before app.run(), aka flag and logging init().""" 16 17import contextlib 18import io 19import os 20import re 21import sys 22import tempfile 23from unittest import mock 24 25from absl import logging 26from absl.testing import absltest 27 28logging.get_verbosity() # Access --verbosity before flag parsing. 29# Access --logtostderr before flag parsing. 30logging.get_absl_handler().use_absl_log_file() 31 32 33class Error(Exception): 34 pass 35 36 37@contextlib.contextmanager 38def captured_stderr_filename(): 39 """Captures stderr and writes them to a temporary file. 40 41 This uses os.dup/os.dup2 to redirect the stderr fd for capturing standard 42 error of logging at import-time. We cannot mock sys.stderr because on the 43 first log call, a default log handler writing to the mock sys.stderr is 44 registered, and it will never be removed and subsequent logs go to the mock 45 in addition to the real stder. 46 47 Yields: 48 The filename of captured stderr. 49 """ 50 stderr_capture_file_fd, stderr_capture_file_name = tempfile.mkstemp() 51 original_stderr_fd = os.dup(sys.stderr.fileno()) 52 os.dup2(stderr_capture_file_fd, sys.stderr.fileno()) 53 try: 54 yield stderr_capture_file_name 55 finally: 56 os.close(stderr_capture_file_fd) 57 os.dup2(original_stderr_fd, sys.stderr.fileno()) 58 59 60# Pre-initialization (aka "import" / __main__ time) test. 61with captured_stderr_filename() as before_set_verbosity_filename: 62 # Warnings and above go to stderr. 63 logging.debug('Debug message at parse time.') 64 logging.info('Info message at parse time.') 65 logging.error('Error message at parse time.') 66 logging.warning('Warning message at parse time.') 67 try: 68 raise Error('Exception reason.') 69 except Error: 70 logging.exception('Exception message at parse time.') 71 72 73logging.set_verbosity(logging.ERROR) 74with captured_stderr_filename() as after_set_verbosity_filename: 75 # Verbosity is set to ERROR, errors and above go to stderr. 76 logging.debug('Debug message at parse time.') 77 logging.info('Info message at parse time.') 78 logging.warning('Warning message at parse time.') 79 logging.error('Error message at parse time.') 80 81 82class LoggingInitWarningTest(absltest.TestCase): 83 84 def test_captured_pre_init_warnings(self): 85 with open(before_set_verbosity_filename) as stderr_capture_file: 86 captured_stderr = stderr_capture_file.read() 87 self.assertNotIn('Debug message at parse time.', captured_stderr) 88 self.assertNotIn('Info message at parse time.', captured_stderr) 89 90 traceback_re = re.compile( 91 r'\nTraceback \(most recent call last\):.*?Error: Exception reason.', 92 re.MULTILINE | re.DOTALL) 93 if not traceback_re.search(captured_stderr): 94 self.fail( 95 'Cannot find traceback message from logging.exception ' 96 'in stderr:\n{}'.format(captured_stderr)) 97 # Remove the traceback so the rest of the stderr is deterministic. 98 captured_stderr = traceback_re.sub('', captured_stderr) 99 captured_stderr_lines = captured_stderr.splitlines() 100 self.assertLen(captured_stderr_lines, 3) 101 self.assertIn('Error message at parse time.', captured_stderr_lines[0]) 102 self.assertIn('Warning message at parse time.', captured_stderr_lines[1]) 103 self.assertIn('Exception message at parse time.', captured_stderr_lines[2]) 104 105 def test_set_verbosity_pre_init(self): 106 with open(after_set_verbosity_filename) as stderr_capture_file: 107 captured_stderr = stderr_capture_file.read() 108 captured_stderr_lines = captured_stderr.splitlines() 109 110 self.assertNotIn('Debug message at parse time.', captured_stderr) 111 self.assertNotIn('Info message at parse time.', captured_stderr) 112 self.assertNotIn('Warning message at parse time.', captured_stderr) 113 self.assertLen(captured_stderr_lines, 1) 114 self.assertIn('Error message at parse time.', captured_stderr_lines[0]) 115 116 def test_no_more_warnings(self): 117 fake_stderr_type = io.BytesIO if bytes is str else io.StringIO 118 with mock.patch('sys.stderr', new=fake_stderr_type()) as mock_stderr: 119 self.assertMultiLineEqual('', mock_stderr.getvalue()) 120 logging.warning('Hello. hello. hello. Is there anybody out there?') 121 self.assertNotIn('Logging before flag parsing goes to stderr', 122 mock_stderr.getvalue()) 123 logging.info('A major purpose of this executable is merely not to crash.') 124 125 126if __name__ == '__main__': 127 absltest.main() # This calls the app.run() init equivalent. 128