proxygen
fatal_test.py
Go to the documentation of this file.
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(
32  build_dir, "folly", "logging", "test", "fatal_helper"
33  )
34 
35  def run_helper(self, *args, **kwargs):
36  """Run the helper and verify it crashes.
37 
38  Check that it crashes with SIGABRT and prints nothing on stdout.
39  Returns the data printed to stderr.
40  """
41  returncode, out, err = self.run_helper_nochecks(*args, **kwargs)
42  self.assertEqual(returncode, -signal.SIGABRT)
43  self.assertEqual(out, b"")
44  return err
45 
46  def run_helper_nochecks(self, *args, **kwargs):
47  """Run the helper.
48 
49  Returns a tuple of [returncode, stdout_output, stderr_output]
50  """
51  env = kwargs.pop("env", None)
52  if kwargs:
53  raise TypeError("unexpected keyword arguments: %r" % (list(kwargs.keys())))
54 
55  cmd = [self.helper]
56  cmd.extend(args)
57  p = subprocess.Popen(
58  cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=env
59  )
60  out, err = p.communicate()
61  return p.returncode, out, err
62 
63  def is_debug_build(self):
64  returncode, out, err = self.run_helper_nochecks("--check_debug")
65  self.assertEqual(b"", err)
66  self.assertEqual(0, returncode)
67  if out.strip() == b"DEBUG=1":
68  return True
69  elif out.strip() == b"DEBUG=0":
70  return False
71  else:
72  self.fail("unexpected output from --check_debug: {}".format(out))
73 
74  def get_crash_regex(self, msg=b"test program crashing!", glog=True):
75  if glog:
76  prefix = br"^F[0-9]{4} .* FatalHelper.cpp:[0-9]+\] "
77  else:
78  prefix = br"^FATAL:.*FatalHelper.cpp:[0-9]+: "
79  regex = prefix + re.escape(msg) + b"$"
80  return re.compile(regex, re.MULTILINE)
81 
82  def test_no_crash(self):
83  # Simple sanity check that the program runs without
84  # crashing when requested
85  returncode, out, err = self.run_helper_nochecks("--crash=no")
86  self.assertEqual(0, returncode)
87  self.assertEqual(b"", out)
88  self.assertEqual(b"", err)
89 
90  def test_async(self):
91  handler_setings = "default=stream:stream=stderr,async=true"
92  err = self.run_helper("--logging=;" + handler_setings)
93  self.assertRegex(err, self.get_crash_regex())
94 
95  def test_immediate(self):
96  handler_setings = "default=stream:stream=stderr,async=false"
97  err = self.run_helper("--logging=;" + handler_setings)
98  self.assertRegex(err, self.get_crash_regex())
99 
100  def test_none(self):
101  # The fatal message should be printed directly to stderr when there
102  # are no logging handlers configured.
103  err = self.run_helper("--logging=ERR:")
104  self.assertRegex(err, self.get_crash_regex(glog=False))
105 
107  err = self.run_helper("--category=foo.bar", "--logging", ".=FATAL")
108  regex = re.compile(
109  br"^F[0-9]{4} .* FatalHelper.cpp:[0-9]+\] "
110  br"crashing to category foo\.bar$",
111  re.MULTILINE,
112  )
113  self.assertRegex(err, regex)
114 
115  def test_static_init(self):
116  err = self.run_helper(env={"CRASH_DURING_INIT": "1"})
117  regex = self.get_crash_regex(br"crashing during static initialization")
118  self.assertRegex(err, regex)
119 
121  err = self.run_helper("--crash=no", env={"CRASH_DURING_INIT": "shutdown"})
122  # When crashing during static destruction we may or may not see a
123  # glog-formatted message. This depends on whether the crashing
124  # destructor runs before or after the code that uninstalls the log
125  # handlers, and it is valid for that to occur in either order.
126  regex = re.compile(
127  br"^(FATAL|C[0-9]{4}).*FatalHelper.cpp:.* "
128  br"crashing during static destruction$",
129  re.MULTILINE,
130  )
131  self.assertRegex(err, regex)
132 
134  # Specify --crash=no to ensure that the XLOG_IF() check is actually what
135  # triggers the crash.
136  err = self.run_helper("--fail_fatal_xlog_if", "--crash=no")
137  self.assertRegex(err, self.get_crash_regex(b"--fail_fatal_xlog_if specified!"))
138 
140  returncode, out, err = self.run_helper_nochecks(
141  "--fail_dfatal_xlog_if", "--crash=no"
142  )
143  # The "--fail_dfatal_xlog_if" message should be logged regardless of which build
144  # type we are using. However, in debug builds it will not trigger a crash.
145  self.assertRegex(err, self.get_crash_regex(b"--fail_dfatal_xlog_if specified!"))
146  self.assertEqual(b"", out)
147  if self.is_debug_build():
148  self.assertEqual(-signal.SIGABRT, returncode)
149  else:
150  self.assertEqual(0, returncode)
151 
152  def test_xcheck(self):
153  # Specify --crash=no to ensure that the XCHECK() is actually what triggers the
154  # crash.
155  err = self.run_helper("--fail_xcheck", "--crash=no")
156  self.assertRegex(
157  err,
158  self.get_crash_regex(
159  b"Check failed: !FLAGS_fail_xcheck : --fail_xcheck specified!"
160  ),
161  )
162 
163  def test_xcheck_nomsg(self):
164  err = self.run_helper("--fail_xcheck_nomsg", "--crash=no")
165  self.assertRegex(
166  err, self.get_crash_regex(b"Check failed: !FLAGS_fail_xcheck_nomsg ")
167  )
168 
169  def test_xdcheck(self):
170  returncode, out, err = self.run_helper_nochecks("--fail_xdcheck", "--crash=no")
171  self.assertEqual(b"", out)
172  if self.is_debug_build():
173  self.assertRegex(
174  err,
175  self.get_crash_regex(
176  b"Check failed: !FLAGS_fail_xdcheck : --fail_xdcheck specified!"
177  ),
178  )
179  self.assertEqual(-signal.SIGABRT, returncode)
180  else:
181  self.assertEqual(b"", err)
182  self.assertEqual(0, returncode)
def is_debug_build(self)
Definition: fatal_test.py:63
def run_helper(self, args, kwargs)
Definition: fatal_test.py:35
def test_static_destruction(self)
Definition: fatal_test.py:120
def test_other_category(self)
Definition: fatal_test.py:106
def test_dfatal_xlog_if(self)
Definition: fatal_test.py:139
def run_helper_nochecks(self, args, kwargs)
Definition: fatal_test.py:46
def test_no_crash(self)
Definition: fatal_test.py:82
def test_xcheck_nomsg(self)
Definition: fatal_test.py:163
Encoder::MutableCompressedList list
def test_xdcheck(self)
Definition: fatal_test.py:169
def test_immediate(self)
Definition: fatal_test.py:95
def test_fatal_xlog_if(self)
Definition: fatal_test.py:133
def test_static_init(self)
Definition: fatal_test.py:115
def test_async(self)
Definition: fatal_test.py:90
Formatter< false, Args... > format(StringPiece fmt, Args &&...args)
Definition: Format.h:271
def get_crash_regex(self, msg=b"test program crashing!", glog=True)
Definition: fatal_test.py:74