Skip to content

Commit 06edd9c

Browse files
authored
Merge pull request #127 from yilei/push_up_to_286008946
Push up to 286008946
2 parents 928bb94 + fc407ab commit 06edd9c

12 files changed

+341
-380
lines changed

absl/BUILD

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1+
load(":_build_defs.bzl", "py2and3_test", "py2py3_test_binary")
2+
13
licenses(["notice"]) # Apache 2.0
24

35
exports_files(["LICENSE"])
46

5-
load(":_build_defs.bzl", "py2py3_test_binary", "py2and3_test")
6-
77
py_library(
88
name = "app",
99
srcs = [

absl/CHANGELOG.md

+12
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,18 @@ The format is based on [Keep a Changelog](https://keepachangelog.com).
88

99
Nothing notable unreleased.
1010

11+
## 0.9.0 (2019-12-17)
12+
13+
### Added
14+
15+
* (testing) `TestCase.enter_context`: Allows using context managers in setUp
16+
and having them automatically exited when a test finishes.
17+
18+
### Fixed
19+
20+
* #126: calling `logging.debug(msg, stack_info=...)` no longer throws an
21+
exception in Python 3.8.
22+
1123
## 0.8.1 (2019-10-08)
1224

1325
### Fixed

absl/flags/tests/flags_test.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -626,7 +626,7 @@ def args_list():
626626
'--stderrthreshold fatal',
627627
'--test1',
628628
'--test_random_seed 301',
629-
'--test_randomize_ordering_seed None',
629+
'--test_randomize_ordering_seed ',
630630
'--testcomma_list []',
631631
'--testget1',
632632
'--testget4 None',
@@ -694,7 +694,7 @@ def args_list():
694694
'--stderrthreshold fatal',
695695
'--test1',
696696
'--test_random_seed 301',
697-
'--test_randomize_ordering_seed None',
697+
'--test_randomize_ordering_seed ',
698698
'--testcomma_list []',
699699
'--testget1',
700700
'--testget4 None',

absl/logging/BUILD

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1+
load("//absl:_build_defs.bzl", "py2and3_test", "py2py3_test_binary")
2+
13
licenses(["notice"]) # Apache 2.0
24

35
exports_files(["LICENSE"])
46

5-
load("//absl:_build_defs.bzl", "py2py3_test_binary", "py2and3_test")
6-
77
py_library(
88
name = "logging",
99
srcs = ["__init__.py"],

absl/testing/BUILD

+22-1
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,29 @@
1+
load("//absl:_build_defs.bzl", "py2and3_test", "py2py3_test_binary")
2+
13
licenses(["notice"]) # Apache 2.0
24

35
exports_files(["LICENSE"])
46

5-
load("//absl:_build_defs.bzl", "py2py3_test_binary", "py2and3_test")
7+
config_setting(
8+
name = "osx",
9+
constraint_values = ["//third_party/bazel_platforms/os:osx"],
10+
)
11+
12+
config_setting(
13+
name = "ios",
14+
flag_values = {"//tools/cpp:cc_target_os": "apple"},
15+
)
16+
17+
_absl_test_platform_deps = select({
18+
":osx": [],
19+
":ios": [],
20+
# TODO(b/75911467): Remove after :osx works in host mode
21+
"//tools/cc_target_os:platform_macos": [],
22+
"//conditions:default": [
23+
"//third_party/py/faulthandler",
24+
"//third_party/py/readline", # Enables arrow keys and tab-completion in (pdb).
25+
],
26+
})
627

728
py_library(
829
name = "absltest",

absl/testing/absltest.py

+50-11
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@
7070
try:
7171
# pylint: disable=unused-import
7272
import typing
73-
from typing import AnyStr, Callable, Text, Optional, ContextManager, TextIO, BinaryIO, Union, Type, Tuple, Any, MutableSequence, Sequence, Mapping, MutableMapping, IO, List
73+
from typing import Any, AnyStr, BinaryIO, Callable, ContextManager, IO, Iterator, List, Mapping, MutableMapping, MutableSequence, Optional, Sequence, Text, TextIO, Tuple, Type, Union
7474
# pylint: enable=unused-import
7575
except ImportError:
7676
pass
@@ -207,11 +207,8 @@ def _get_default_randomize_ordering_seed():
207207
ValueError: Raised when the flag or env value is not one of the options
208208
above.
209209
"""
210-
if FLAGS.test_randomize_ordering_seed is not None:
211-
randomize = FLAGS.test_randomize_ordering_seed
212-
else:
213-
randomize = os.environ.get('TEST_RANDOMIZE_ORDERING_SEED')
214-
if randomize is None:
210+
randomize = FLAGS.test_randomize_ordering_seed
211+
if not randomize:
215212
return 0
216213
if randomize == 'random':
217214
return random.Random().randint(1, 4294967295)
@@ -239,12 +236,14 @@ def _get_default_randomize_ordering_seed():
239236
flags.DEFINE_string('test_tmpdir', get_default_test_tmpdir(),
240237
'Directory for temporary testing files',
241238
allow_override_cpp=True)
242-
flags.DEFINE_string('test_randomize_ordering_seed', None,
239+
flags.DEFINE_string('test_randomize_ordering_seed',
240+
os.environ.get('TEST_RANDOMIZE_ORDERING_SEED', ''),
243241
'If positive, use this as a seed to randomize the '
244242
'execution order for test cases. If "random", pick a '
245243
'random seed to use. If 0 or not set, do not randomize '
246244
'test case execution order. This flag also overrides '
247-
'the TEST_RANDOMIZE_ORDERING_SEED environment variable.')
245+
'the TEST_RANDOMIZE_ORDERING_SEED environment variable.',
246+
allow_override_cpp=True)
248247
flags.DEFINE_string('xml_output_file', '',
249248
'File to store XML test results')
250249

@@ -485,7 +484,8 @@ def open_text(self, mode='rt', encoding='utf8', errors='strict'):
485484
'file in text mode'.format(mode))
486485
if 't' not in mode:
487486
mode += 't'
488-
return self._open(mode, encoding, errors)
487+
cm = self._open(mode, encoding, errors) # type: ContextManager[TextIO]
488+
return cm
489489

490490
def open_bytes(self, mode='rb'):
491491
# type: (Text) -> ContextManager[BinaryIO]
@@ -506,11 +506,15 @@ def open_bytes(self, mode='rb'):
506506
'file in binary mode'.format(mode))
507507
if 'b' not in mode:
508508
mode += 'b'
509-
return self._open(mode, encoding=None, errors=None)
509+
cm = self._open(mode, encoding=None, errors=None) # type: ContextManager[BinaryIO]
510+
return cm
510511

512+
# TODO(b/123775699): Once pytype supports typing.Literal, use overload and
513+
# Literal to express more precise return types and remove the type comments in
514+
# open_text and open_bytes.
511515
@contextlib.contextmanager
512516
def _open(self, mode, encoding='utf8', errors='strict'):
513-
# type: (Text, Text, Text) -> Union[TextIO, BinaryIO]
517+
# type: (Text, Text, Text) -> Iterator[Union[IO[Text], IO[bytes]]]
514518
with io.open(
515519
self.full_path, mode=mode, encoding=encoding, errors=errors) as fp:
516520
yield fp
@@ -534,6 +538,15 @@ def __init__(self, *args, **kwargs):
534538
super(TestCase, self).__init__(*args, **kwargs)
535539
# This is to work around missing type stubs in unittest.pyi
536540
self._outcome = getattr(self, '_outcome') # type: Optional[_OutcomeType]
541+
# This is re-initialized by setUp().
542+
self._exit_stack = None
543+
544+
def setUp(self):
545+
super(TestCase, self).setUp()
546+
# NOTE: Only Py3 contextlib has ExitStack
547+
if hasattr(contextlib, 'ExitStack'):
548+
self._exit_stack = contextlib.ExitStack()
549+
self.addCleanup(self._exit_stack.close)
537550

538551
def create_tempdir(self, name=None, cleanup=None):
539552
# type: (Optional[Text], Optional[TempFileCleanup]) -> _TempDir
@@ -625,6 +638,32 @@ def create_tempfile(self, file_path=None, content=None, mode='w',
625638
self._maybe_add_temp_path_cleanup(cleanup_path, cleanup)
626639
return tf
627640

641+
def enter_context(self, manager):
642+
"""Returns the CM's value after registering it with the exit stack.
643+
644+
Entering a context pushes it onto a stack of contexts. The context is exited
645+
when the test completes. Contexts are are exited in the reverse order of
646+
entering. They will always be exited, regardless of test failure/success.
647+
The context stack is specific to the test being run.
648+
649+
This is useful to eliminate per-test boilerplate when context managers
650+
are used. For example, instead of decorating every test with `@mock.patch`,
651+
simply do `self.foo = self.enter_context(mock.patch(...))' in `setUp()`.
652+
653+
NOTE: The context managers will always be exited without any error
654+
information. This is an unfortunate implementation detail due to some
655+
internals of how unittest runs tests.
656+
657+
Args:
658+
manager: The context manager to enter.
659+
"""
660+
# type: (ContextManager[_T]) -> _T
661+
if not self._exit_stack:
662+
raise AssertionError(
663+
'self._exit_stack is not set: enter_context is Py3-only; also make '
664+
'sure that AbslTest.setUp() is called.')
665+
return self._exit_stack.enter_context(manager)
666+
628667
@classmethod
629668
def _get_tempdir_path_cls(cls):
630669
# type: () -> Text

absl/testing/parameterized.py

+4
Original file line numberDiff line numberDiff line change
@@ -281,6 +281,10 @@ def bound_param_test(self):
281281
testcase_params = {k: v for k, v in six.iteritems(testcase_params)
282282
if k != _NAMED_DICT_KEY}
283283
elif _non_string_or_bytes_iterable(testcase_params):
284+
if not isinstance(testcase_params[0], six.string_types):
285+
raise RuntimeError(
286+
'The first element of named test parameters is the test name '
287+
'suffix and must be a string')
284288
testcase_name = testcase_params[0]
285289
testcase_params = testcase_params[1:]
286290
else:

absl/testing/tests/absltest_test.py

+32
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
from __future__ import print_function
2020

2121
import collections
22+
import contextlib
2223
import io
2324
import os
2425
import re
@@ -1466,6 +1467,37 @@ def test_stderr(self):
14661467
self.assertRegex(stderr, 'No such file or directory')
14671468

14681469

1470+
@absltest.skipIf(six.PY2, 'Python 2 does not have ExitStack')
1471+
class EnterContextTest(absltest.TestCase):
1472+
1473+
def setUp(self):
1474+
self.cm_state = 'unset'
1475+
self.cm_value = 'unset'
1476+
1477+
def assert_cm_exited():
1478+
self.assertEqual(self.cm_state, 'exited')
1479+
1480+
# Because cleanup functions are run in reverse order, we have to add
1481+
# our assert-cleanup before the exit stack registers its own cleanup.
1482+
# This ensures we see state after the stack cleanup runs.
1483+
self.addCleanup(assert_cm_exited)
1484+
1485+
super(EnterContextTest, self).setUp()
1486+
self.cm_value = self.enter_context(self.cm_for_test())
1487+
1488+
@contextlib.contextmanager
1489+
def cm_for_test(self):
1490+
try:
1491+
self.cm_state = 'yielded'
1492+
yield 'value'
1493+
finally:
1494+
self.cm_state = 'exited'
1495+
1496+
def test_enter_context(self):
1497+
self.assertEqual(self.cm_value, 'value')
1498+
self.assertEqual(self.cm_state, 'yielded')
1499+
1500+
14691501
class EqualityAssertionTest(absltest.TestCase):
14701502
"""This test verifies that absltest.failIfEqual actually tests __ne__.
14711503

absl/testing/tests/parameterized_test.py

+22
Original file line numberDiff line numberDiff line change
@@ -677,6 +677,28 @@ class _(parameterized.TestCase):
677677
def test_mixed_something(self, unused_obj):
678678
pass
679679

680+
def test_named_test_with_no_name_fails(self):
681+
with self.assertRaises(RuntimeError):
682+
683+
class _(parameterized.TestCase):
684+
685+
@parameterized.named_parameters(
686+
(0,),
687+
)
688+
def test_something(self, unused_obj):
689+
pass
690+
691+
def test_named_test_dict_with_no_name_fails(self):
692+
with self.assertRaises(RuntimeError):
693+
694+
class _(parameterized.TestCase):
695+
696+
@parameterized.named_parameters(
697+
{'unused_obj': 0},
698+
)
699+
def test_something(self, unused_obj):
700+
pass
701+
680702
def test_parameterized_test_iter_has_testcases_property(self):
681703
@parameterized.parameters(1, 2, 3, 4, 5, 6)
682704
def test_something(unused_self, unused_obj): # pylint: disable=invalid-name

0 commit comments

Comments
 (0)