70
70
try :
71
71
# pylint: disable=unused-import
72
72
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
74
74
# pylint: enable=unused-import
75
75
except ImportError :
76
76
pass
@@ -207,11 +207,8 @@ def _get_default_randomize_ordering_seed():
207
207
ValueError: Raised when the flag or env value is not one of the options
208
208
above.
209
209
"""
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 :
215
212
return 0
216
213
if randomize == 'random' :
217
214
return random .Random ().randint (1 , 4294967295 )
@@ -239,12 +236,14 @@ def _get_default_randomize_ordering_seed():
239
236
flags .DEFINE_string ('test_tmpdir' , get_default_test_tmpdir (),
240
237
'Directory for temporary testing files' ,
241
238
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' , '' ),
243
241
'If positive, use this as a seed to randomize the '
244
242
'execution order for test cases. If "random", pick a '
245
243
'random seed to use. If 0 or not set, do not randomize '
246
244
'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 )
248
247
flags .DEFINE_string ('xml_output_file' , '' ,
249
248
'File to store XML test results' )
250
249
@@ -485,7 +484,8 @@ def open_text(self, mode='rt', encoding='utf8', errors='strict'):
485
484
'file in text mode' .format (mode ))
486
485
if 't' not in mode :
487
486
mode += 't'
488
- return self ._open (mode , encoding , errors )
487
+ cm = self ._open (mode , encoding , errors ) # type: ContextManager[TextIO]
488
+ return cm
489
489
490
490
def open_bytes (self , mode = 'rb' ):
491
491
# type: (Text) -> ContextManager[BinaryIO]
@@ -506,11 +506,15 @@ def open_bytes(self, mode='rb'):
506
506
'file in binary mode' .format (mode ))
507
507
if 'b' not in mode :
508
508
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
510
511
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.
511
515
@contextlib .contextmanager
512
516
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]] ]
514
518
with io .open (
515
519
self .full_path , mode = mode , encoding = encoding , errors = errors ) as fp :
516
520
yield fp
@@ -534,6 +538,15 @@ def __init__(self, *args, **kwargs):
534
538
super (TestCase , self ).__init__ (* args , ** kwargs )
535
539
# This is to work around missing type stubs in unittest.pyi
536
540
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 )
537
550
538
551
def create_tempdir (self , name = None , cleanup = None ):
539
552
# type: (Optional[Text], Optional[TempFileCleanup]) -> _TempDir
@@ -625,6 +638,32 @@ def create_tempfile(self, file_path=None, content=None, mode='w',
625
638
self ._maybe_add_temp_path_cleanup (cleanup_path , cleanup )
626
639
return tf
627
640
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
+
628
667
@classmethod
629
668
def _get_tempdir_path_cls (cls ):
630
669
# type: () -> Text
0 commit comments