132b708bb922bedf6549e2d84abc13dfcf2fd485
[folly.git] / folly / experimental / logging / test / fatal_test.py
1 #!/usr/bin/env python3
2 #
3 # Copyright 2004-present Facebook, Inc.
4 #
5 # Licensed under the Apache License, Version 2.0 (the "License");
6 # you may not use this file except in compliance with the License.
7 # You may obtain a copy of the License at
8 #
9 #   http://www.apache.org/licenses/LICENSE-2.0
10 #
11 # Unless required by applicable law or agreed to in writing, software
12 # distributed under the License is distributed on an "AS IS" BASIS,
13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 # See the License for the specific language governing permissions and
15 # limitations under the License.
16 #
17 import os
18 import re
19 import signal
20 import subprocess
21 import unittest
22
23
24 class FatalTests(unittest.TestCase):
25     def setUp(self):
26         fatal_helper_env = os.environ.get('FOLLY_FATAL_HELPER')
27         if fatal_helper_env:
28             self.helper = fatal_helper_env
29         else:
30             build_dir = os.path.join(os.getcwd(), 'buck-out', 'gen')
31             self.helper = os.path.join(build_dir, 'folly', 'experimental',
32                                        'logging', 'test', 'fatal_helper')
33
34     def run_helper(self, *args, **kwargs):
35         '''
36         Run the helper.
37         Check that it crashes with SIGABRT and prints nothing on stdout.
38         Returns the data printed to stderr.
39         '''
40         env = kwargs.pop('env', None)
41         if kwargs:
42             raise TypeError('unexpected keyword arguments: %r' %
43                             (list(kwargs.keys())))
44
45         cmd = [self.helper]
46         cmd.extend(args)
47         p = subprocess.Popen(cmd,
48                              stdout=subprocess.PIPE,
49                              stderr=subprocess.PIPE,
50                              env=env)
51         out, err = p.communicate()
52         status = p.returncode
53
54         self.assertEqual(status, -signal.SIGABRT)
55         self.assertEqual(out, b'')
56         return err
57
58     def get_crash_regex(self, msg=b'test program crashing!', glog=True):
59         if glog:
60             prefix = br'^C[0-9]{4} .* FatalHelper.cpp:[0-9]+\] '
61         else:
62             prefix = br'^FATAL:.*FatalHelper.cpp:[0-9]+: '
63         regex = prefix + re.escape(msg) + b'$'
64         return re.compile(regex, re.MULTILINE)
65
66     def test_no_crash(self):
67         # Simple sanity check that the program runs without
68         # crashing when requested
69         subprocess.check_output([self.helper, '--crash=no'])
70
71     def test_async(self):
72         err = self.run_helper('--handler_style=async')
73         self.assertRegex(err, self.get_crash_regex())
74
75     def test_immediate(self):
76         err = self.run_helper('--handler_style=immediate')
77         self.assertRegex(err, self.get_crash_regex())
78
79     def test_none(self):
80         # The fatal message should be printed directly to stderr when there
81         # are no logging handlers configured.
82         err = self.run_helper('--handler_style=none')
83         self.assertRegex(err, self.get_crash_regex(glog=False))
84
85     def test_other_category(self):
86         err = self.run_helper('--category=foo.bar',
87                               '--logging', '.=FATAL')
88         regex = re.compile(
89             br'^C[0-9]{4} .* FatalHelper.cpp:[0-9]+\] '
90             br'crashing to category foo\.bar$',
91             re.MULTILINE)
92         self.assertRegex(err, regex)
93
94     def test_static_init(self):
95         err = self.run_helper(env={'CRASH_DURING_INIT': '1'})
96         regex = self.get_crash_regex(br'crashing during static initialization',
97                                      glog=False)
98         self.assertRegex(err, regex)
99
100     def test_static_destruction(self):
101         err = self.run_helper('--crash=no',
102                               env={'CRASH_DURING_INIT': 'shutdown'})
103         # When crashing during static destruction we may or may not see a
104         # glog-formatted message.  This depends on whether the crashing
105         # destructor runs before or after the code that uninstalls the log
106         # handlers, and it is valid for that to occur in either order.
107         regex = re.compile(br'^(FATAL|C[0-9]{4}).*FatalHelper.cpp:.* '
108                            br'crashing during static destruction$',
109                            re.MULTILINE)
110         self.assertRegex(err, regex)