diff --git a/.github/workflows/test_upb.yml b/.github/workflows/test_upb.yml index 42c05987145b0..d302edd0e9199 100644 --- a/.github/workflows/test_upb.yml +++ b/.github/workflows/test_upb.yml @@ -299,8 +299,13 @@ jobs: python -m venv env source env/bin/activate echo "VIRTUAL ENV:" $VIRTUAL_ENV - - name: Install numpy - run: pip install numpy + - name: Download Requirements + uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 #4.1.8 + with: + name: requirements + path: requirements + - name: Install requirements + run: pip install -r requirements/requirements.txt - name: Install Protobuf Wheels run: pip install -vvv --no-index --find-links wheels protobuf protobuftests - name: Run the unit tests diff --git a/MODULE.bazel b/MODULE.bazel index c95752147a079..3ed9e06cc5f5d 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -112,6 +112,12 @@ use_repo(maven, "protobuf_maven") bazel_dep(name = "googletest", version = "1.14.0", dev_dependency = True, repo_name = "com_google_googletest") bazel_dep(name = "rules_buf", version = "0.3.0", dev_dependency = True) bazel_dep(name = "rules_testing", version = "0.6.0", dev_dependency = True) +bazel_dep( + name = "abseil-py", + version = "2.1.0", + repo_name = "com_google_absl_py", + dev_dependency = True, +) # rules_proto are needed for @com_google_protobuf_v25.0 used in //compatibility/... tests bazel_dep(name = "rules_proto", version = "4.0.0", dev_dependency = True) diff --git a/WORKSPACE b/WORKSPACE index 3716b137afd09..c49abcf5329fc 100644 --- a/WORKSPACE +++ b/WORKSPACE @@ -193,6 +193,15 @@ load("@pip_deps//:requirements.bzl", "install_deps") install_deps() +http_archive( + name = "com_google_absl_py", + sha256 = "8a3d0830e4eb4f66c4fa907c06edf6ce1c719ced811a12e26d9d3162f8471758", + strip_prefix = "abseil-py-2.1.0", + urls = [ + "https://github.com/abseil/abseil-py/archive/refs/tags/v2.1.0.tar.gz", + ], +) + http_archive( name = "rules_fuzzing", patch_args = ["-p1"], diff --git a/python/dist/BUILD.bazel b/python/dist/BUILD.bazel index 2dc9d1267edfa..381537529f8d8 100644 --- a/python/dist/BUILD.bazel +++ b/python/dist/BUILD.bazel @@ -448,6 +448,7 @@ py_wheel( "//:python_test_srcs", "//python/pb_unit_tests:test_files", "//src/google/protobuf:testdata", + "@com_google_absl_py//absl/testing:parameterized", ], ) diff --git a/python/google/protobuf/internal/_parameterized.py b/python/google/protobuf/internal/_parameterized.py deleted file mode 100755 index 8ead39487e882..0000000000000 --- a/python/google/protobuf/internal/_parameterized.py +++ /dev/null @@ -1,487 +0,0 @@ -#! /usr/bin/env python -# -# Protocol Buffers - Google's data interchange format -# Copyright 2008 Google Inc. All rights reserved. -# -# Use of this source code is governed by a BSD-style -# license that can be found in the LICENSE file or at -# https://developers.google.com/open-source/licenses/bsd - -# TODO: Replace this with the now-open-sourced absl-py version. - -"""Adds support for parameterized tests to Python's unittest TestCase class. - -A parameterized test is a method in a test case that is invoked with different -argument tuples. - -A simple example: - - class AdditionExample(_parameterized.TestCase): - @_parameterized.parameters( - (1, 2, 3), - (4, 5, 9), - (1, 1, 3)) - def testAddition(self, op1, op2, result): - self.assertEqual(result, op1 + op2) - - -Each invocation is a separate test case and properly isolated just -like a normal test method, with its own setUp/tearDown cycle. In the -example above, there are three separate testcases, one of which will -fail due to an assertion error (1 + 1 != 3). - -Parameters for individual test cases can be tuples (with positional parameters) -or dictionaries (with named parameters): - - class AdditionExample(_parameterized.TestCase): - @_parameterized.parameters( - {'op1': 1, 'op2': 2, 'result': 3}, - {'op1': 4, 'op2': 5, 'result': 9}, - ) - def testAddition(self, op1, op2, result): - self.assertEqual(result, op1 + op2) - -If a parameterized test fails, the error message will show the -original test name (which is modified internally) and the arguments -for the specific invocation, which are part of the string returned by -the shortDescription() method on test cases. - -The id method of the test, used internally by the unittest framework, -is also modified to show the arguments. To make sure that test names -stay the same across several invocations, object representations like - - >>> class Foo(object): - ... pass - >>> repr(Foo()) - '<__main__.Foo object at 0x23d8610>' - -are turned into '<__main__.Foo>'. For even more descriptive names, -especially in test logs, you can use the named_parameters decorator. In -this case, only tuples are supported, and the first parameters has to -be a string (or an object that returns an apt name when converted via -str()): - - class NamedExample(_parameterized.TestCase): - @_parameterized.named_parameters( - ('Normal', 'aa', 'aaa', True), - ('EmptyPrefix', '', 'abc', True), - ('BothEmpty', '', '', True)) - def testStartsWith(self, prefix, string, result): - self.assertEqual(result, strings.startswith(prefix)) - -Named tests also have the benefit that they can be run individually -from the command line: - - $ testmodule.py NamedExample.testStartsWithNormal - . - -------------------------------------------------------------------- - Ran 1 test in 0.000s - - OK - -Parameterized Classes -===================== -If invocation arguments are shared across test methods in a single -TestCase class, instead of decorating all test methods -individually, the class itself can be decorated: - - @_parameterized.parameters( - (1, 2, 3) - (4, 5, 9)) - class ArithmeticTest(_parameterized.TestCase): - def testAdd(self, arg1, arg2, result): - self.assertEqual(arg1 + arg2, result) - - def testSubtract(self, arg2, arg2, result): - self.assertEqual(result - arg1, arg2) - -Inputs from Iterables -===================== -If parameters should be shared across several test cases, or are dynamically -created from other sources, a single non-tuple iterable can be passed into -the decorator. This iterable will be used to obtain the test cases: - - class AdditionExample(_parameterized.TestCase): - @_parameterized.parameters( - c.op1, c.op2, c.result for c in testcases - ) - def testAddition(self, op1, op2, result): - self.assertEqual(result, op1 + op2) - - -Single-Argument Test Methods -============================ -If a test method takes only one argument, the single argument does not need to -be wrapped into a tuple: - - class NegativeNumberExample(_parameterized.TestCase): - @_parameterized.parameters( - -1, -3, -4, -5 - ) - def testIsNegative(self, arg): - self.assertTrue(IsNegative(arg)) -""" - -__author__ = 'tmarek@google.com (Torsten Marek)' - -import functools -import re -import types -import unittest -import uuid - -try: - # Since python 3 - import collections.abc as collections_abc -except ImportError: - # Won't work after python 3.8 - import collections as collections_abc - -ADDR_RE = re.compile(r'\<([a-zA-Z0-9_\-\.]+) object at 0x[a-fA-F0-9]+\>') -_SEPARATOR = uuid.uuid1().hex -_FIRST_ARG = object() -_ARGUMENT_REPR = object() -_NAMED = object() -_NAMED_DICT_KEY = 'testcase_name' - - -def _CleanRepr(obj): - return ADDR_RE.sub(r'<\1>', repr(obj)) - - -# Helper function formerly from the unittest module, removed from it in -# Python 2.7. -def _StrClass(cls): - return '%s.%s' % (cls.__module__, cls.__name__) - - -def _NonStringIterable(obj): - return (isinstance(obj, collections_abc.Iterable) and - not isinstance(obj, str)) - - -def _NonStringOrBytesIterable(obj): - return ( - isinstance(obj, collections_abc.Iterable) - and not isinstance(obj, str) - and not isinstance(obj, bytes) - ) - - -def _FormatParameterList(testcase_params): - if isinstance(testcase_params, collections_abc.Mapping): - return ', '.join('%s=%s' % (argname, _CleanRepr(value)) - for argname, value in testcase_params.items()) - elif _NonStringIterable(testcase_params): - return ', '.join(map(_CleanRepr, testcase_params)) - else: - return _FormatParameterList((testcase_params,)) - - -class _ParameterizedTestIter(object): - """Callable and iterable class for producing new test cases.""" - - def __init__(self, test_method, testcases, naming_type, original_name=None): - """Returns concrete test functions for a test and a list of parameters. - - The naming_type is used to determine the name of the concrete - functions as reported by the unittest framework. If naming_type is - _FIRST_ARG, the testcases must be tuples, and the first element must - have a string representation that is a valid Python identifier. - - Args: - test_method: The decorated test method. - testcases: (list of tuple/dict) A list of parameter tuples/dicts for - individual test invocations. - naming_type: The test naming type, either _NAMED or _ARGUMENT_REPR. - original_name: The original test method name. When decorated on a test - method, None is passed to __init__ and test_method.__name__ is used. - Note test_method.__name__ might be different than the original defined - test method because of the use of other decorators. A more accurate - value is set by TestGeneratorMetaclass.__new__ later. - """ - if original_name is None: - original_name = test_method.__name__ - - self._test_method = test_method - self.testcases = testcases - self._naming_type = naming_type - self._original_name = original_name - - def __call__(self, *args, **kwargs): - raise RuntimeError('You appear to be running a parameterized test case ' - 'without having inherited from parameterized.' - 'TestCase. This is bad because none of ' - 'your test cases are actually being run.') - - def __iter__(self): - test_method = self._test_method - naming_type = self._naming_type - - def MakeBoundParamTest(testcase_params): - @functools.wraps(test_method) - def BoundParamTest(self): - if isinstance(testcase_params, collections_abc.Mapping): - test_method(self, **testcase_params) - elif _NonStringIterable(testcase_params): - test_method(self, *testcase_params) - else: - test_method(self, testcase_params) - - if naming_type is _NAMED: - # Signal the metaclass that the name of the test function is unique - # and descriptive. - BoundParamTest.__x_use_name__ = True - - testcase_name = None - if isinstance(testcase_params, collections_abc.Mapping): - if _NAMED_DICT_KEY not in testcase_params: - raise RuntimeError( - 'Dict for named tests must contain key "%s"' % _NAMED_DICT_KEY - ) - # Create a new dict to avoid modifying the supplied testcase_params. - testcase_name = testcase_params[_NAMED_DICT_KEY] - testcase_params = { - k: v for k, v in testcase_params.items() if k != _NAMED_DICT_KEY - } - elif _NonStringOrBytesIterable(testcase_params): - if not isinstance(testcase_params[0], str): - raise RuntimeError( - 'The first element of named test parameters is the test name ' - 'suffix and must be a string' - ) - testcase_name = testcase_params[0] - testcase_params = testcase_params[1:] - else: - raise RuntimeError( - 'Named tests must be passed a dict or non-string iterable.' - ) - - test_method_name = self._original_name - # Support PEP-8 underscore style for test naming if used. - if ( - test_method_name.startswith('test_') - and testcase_name - and not testcase_name.startswith('_') - ): - test_method_name += '_' - - BoundParamTest.__name__ = test_method_name + str(testcase_name) - elif naming_type is _ARGUMENT_REPR: - # __x_extra_id__ is used to pass naming information to the __new__ - # method of TestGeneratorMetaclass. - # The metaclass will make sure to create a unique, but nondescriptive - # name for this test. - BoundParamTest.__x_extra_id__ = '(%s)' % ( - _FormatParameterList(testcase_params),) - else: - raise RuntimeError('%s is not a valid naming type.' % (naming_type,)) - - BoundParamTest.__doc__ = '%s(%s)' % ( - BoundParamTest.__name__, _FormatParameterList(testcase_params)) - if test_method.__doc__: - BoundParamTest.__doc__ += '\n%s' % (test_method.__doc__,) - return BoundParamTest - return (MakeBoundParamTest(c) for c in self.testcases) - - -def _IsSingletonList(testcases): - """True iff testcases contains only a single non-tuple element.""" - return len(testcases) == 1 and not isinstance(testcases[0], tuple) - - -def _ModifyClass(class_object, testcases, naming_type): - assert not getattr(class_object, '_id_suffix', None), ( - 'Cannot add parameters to %s,' - ' which already has parameterized methods.' % (class_object,)) - class_object._id_suffix = id_suffix = {} - # We change the size of __dict__ while we iterate over it, - # which Python 3.x will complain about, so use copy(). - for name, obj in class_object.__dict__.copy().items(): - if (name.startswith(unittest.TestLoader.testMethodPrefix) - and isinstance(obj, types.FunctionType)): - delattr(class_object, name) - methods = {} - _UpdateClassDictForParamTestCase( - methods, - id_suffix, - name, - _ParameterizedTestIter(obj, testcases, naming_type, name), - ) - for name, meth in methods.items(): - setattr(class_object, name, meth) - - -def _ParameterDecorator(naming_type, testcases): - """Implementation of the parameterization decorators. - - Args: - naming_type: The naming type. - testcases: Testcase parameters. - - Returns: - A function for modifying the decorated object. - """ - def _Apply(obj): - if isinstance(obj, type): - _ModifyClass( - obj, - list(testcases) if not isinstance(testcases, collections_abc.Sequence) - else testcases, - naming_type) - return obj - else: - return _ParameterizedTestIter(obj, testcases, naming_type) - - if _IsSingletonList(testcases): - assert _NonStringIterable(testcases[0]), ( - 'Single parameter argument must be a non-string iterable') - testcases = testcases[0] - - return _Apply - - -def parameters(*testcases): # pylint: disable=invalid-name - """A decorator for creating parameterized tests. - - See the module docstring for a usage example. - Args: - *testcases: Parameters for the decorated method, either a single - iterable, or a list of tuples/dicts/objects (for tests - with only one argument). - - Returns: - A test generator to be handled by TestGeneratorMetaclass. - """ - return _ParameterDecorator(_ARGUMENT_REPR, testcases) - - -def named_parameters(*testcases): # pylint: disable=invalid-name - """A decorator for creating parameterized tests. - - See the module docstring for a usage example. For every parameter tuple - passed, the first element of the tuple should be a string and will be appended - to the name of the test method. Each parameter dict passed must have a value - for the key "testcase_name", the string representation of that value will be - appended to the name of the test method. - - Args: - *testcases: Parameters for the decorated method, either a single iterable, - or a list of tuples or dicts. - - Raises: - NoTestsError: Raised when the decorator generates no tests. - - Returns: - A test generator to be handled by TestGeneratorMetaclass. - """ - return _ParameterDecorator(_NAMED, testcases) - - -class TestGeneratorMetaclass(type): - """Metaclass for test cases with test generators. - - A test generator is an iterable in a testcase that produces callables. These - callables must be single-argument methods. These methods are injected into - the class namespace and the original iterable is removed. If the name of the - iterable conforms to the test pattern, the injected methods will be picked - up as tests by the unittest framework. - - In general, it is supposed to be used in conjunction with the - parameters decorator. - """ - - def __new__(mcs, class_name, bases, dct): - dct['_id_suffix'] = id_suffix = {} - for name, obj in dct.copy().items(): - if (name.startswith(unittest.TestLoader.testMethodPrefix) and - _NonStringIterable(obj)): - if isinstance(obj, _ParameterizedTestIter): - # Update the original test method name so it's more accurate. - # The mismatch might happen when another decorator is used inside - # the parameterized decrators, and the inner decorator doesn't - # preserve its __name__. - obj._original_name = name - iterator = iter(obj) - dct.pop(name) - _UpdateClassDictForParamTestCase(dct, id_suffix, name, iterator) - - return type.__new__(mcs, class_name, bases, dct) - - -def _UpdateClassDictForParamTestCase(dct, id_suffix, name, iterator): - """Adds individual test cases to a dictionary. - - Args: - dct: The target dictionary. - id_suffix: The dictionary for mapping names to test IDs. - name: The original name of the test case. - iterator: The iterator generating the individual test cases. - """ - for idx, func in enumerate(iterator): - assert callable(func), 'Test generators must yield callables, got %r' % ( - func,) - if getattr(func, '__x_use_name__', False): - new_name = func.__name__ - else: - new_name = '%s%s%d' % (name, _SEPARATOR, idx) - assert new_name not in dct, ( - 'Name of parameterized test case "%s" not unique' % (new_name,)) - dct[new_name] = func - id_suffix[new_name] = getattr(func, '__x_extra_id__', '') - - -class TestCase(unittest.TestCase, metaclass=TestGeneratorMetaclass): - """Base class for test cases using the parameters decorator.""" - - def _OriginalName(self): - return self._testMethodName.split(_SEPARATOR)[0] - - def __str__(self): - return '%s (%s)' % (self._OriginalName(), _StrClass(self.__class__)) - - def id(self): # pylint: disable=invalid-name - """Returns the descriptive ID of the test. - - This is used internally by the unittesting framework to get a name - for the test to be used in reports. - - Returns: - The test id. - """ - return '%s.%s%s' % (_StrClass(self.__class__), - self._OriginalName(), - self._id_suffix.get(self._testMethodName, '')) - - -def CoopTestCase(other_base_class): - """Returns a new base class with a cooperative metaclass base. - - This enables the TestCase to be used in combination - with other base classes that have custom metaclasses, such as - mox.MoxTestBase. - - Only works with metaclasses that do not override type.__new__. - - Example: - - import mox - - from google.protobuf.internal import _parameterized - - class ExampleTest(parameterized.CoopTestCase(mox.MoxTestBase)): - ... - - Args: - other_base_class: (class) A test case base class. - - Returns: - A new class object. - """ - metaclass = type( - 'CoopMetaclass', - (other_base_class.__metaclass__, - TestGeneratorMetaclass), {}) - return metaclass( - 'CoopTestCase', - (other_base_class, TestCase), {}) diff --git a/python/google/protobuf/internal/decoder_test.py b/python/google/protobuf/internal/decoder_test.py index fc62e9f7f7239..aa683ad140966 100644 --- a/python/google/protobuf/internal/decoder_test.py +++ b/python/google/protobuf/internal/decoder_test.py @@ -18,7 +18,7 @@ from google.protobuf.internal import testing_refleaks from google.protobuf.internal import wire_format -from google.protobuf.internal import _parameterized +from absl.testing import parameterized _INPUT_BYTES = b'\x84r\x12' @@ -26,7 +26,7 @@ @testing_refleaks.TestCase -class DecoderTest(_parameterized.TestCase): +class DecoderTest(parameterized.TestCase): def test_decode_varint_bytes(self): (size, pos) = decoder._DecodeVarint(_INPUT_BYTES, 0) @@ -130,11 +130,11 @@ def test_unknown_message_set_decoder_mismatched_end_group(self): memoryview(b'\054\014'), ) - @_parameterized.parameters(int(0), float(0.0), False, '') + @parameterized.parameters(int(0), float(0.0), False, '') def test_default_scalar(self, value): self.assertTrue(decoder.IsDefaultScalarValue(value)) - @_parameterized.parameters(int(1), float(-0.0), float(1.0), True, 'a') + @parameterized.parameters(int(1), float(-0.0), float(1.0), True, 'a') def test_not_default_scalar(self, value): self.assertFalse(decoder.IsDefaultScalarValue(value)) diff --git a/python/google/protobuf/internal/descriptor_test.py b/python/google/protobuf/internal/descriptor_test.py index 45810c945667f..ff0181345b0c4 100755 --- a/python/google/protobuf/internal/descriptor_test.py +++ b/python/google/protobuf/internal/descriptor_test.py @@ -22,7 +22,7 @@ from google.protobuf.internal import test_util from google.protobuf.internal import testing_refleaks -from google.protobuf.internal import _parameterized +from absl.testing import parameterized from google.protobuf import unittest_custom_options_pb2 from google.protobuf import unittest_features_pb2 from google.protobuf import unittest_import_pb2 @@ -1231,9 +1231,9 @@ def testJsonName(self): @testing_refleaks.TestCase -class FeaturesTest(_parameterized.TestCase): +class FeaturesTest(parameterized.TestCase): - @_parameterized.named_parameters([ + @parameterized.named_parameters([ ('File', lambda: descriptor_pb2.DESCRIPTOR), ('Message', lambda: descriptor_pb2.FeatureSet.DESCRIPTOR), ( diff --git a/python/google/protobuf/internal/message_test.py b/python/google/protobuf/internal/message_test.py index c168256b36ea8..ddc903b0386b5 100755 --- a/python/google/protobuf/internal/message_test.py +++ b/python/google/protobuf/internal/message_test.py @@ -40,7 +40,7 @@ from google.protobuf.internal import testing_refleaks from google.protobuf import descriptor from google.protobuf import message -from google.protobuf.internal import _parameterized +from absl.testing import parameterized from google.protobuf import map_proto2_unittest_pb2 from google.protobuf import map_unittest_pb2 from google.protobuf import unittest_pb2 @@ -50,7 +50,7 @@ warnings.simplefilter('error', DeprecationWarning) -@_parameterized.named_parameters(('_proto2', unittest_pb2), +@parameterized.named_parameters(('_proto2', unittest_pb2), ('_proto3', unittest_proto3_arena_pb2)) @testing_refleaks.TestCase class MessageTest(unittest.TestCase): diff --git a/python/google/protobuf/internal/proto_test.py b/python/google/protobuf/internal/proto_test.py index 6740730e2d6c7..fed5a1fb04955 100644 --- a/python/google/protobuf/internal/proto_test.py +++ b/python/google/protobuf/internal/proto_test.py @@ -17,12 +17,12 @@ from google.protobuf.internal import test_util from google.protobuf.internal import testing_refleaks -from google.protobuf.internal import _parameterized +from absl.testing import parameterized from google.protobuf import unittest_pb2 from google.protobuf import unittest_proto3_arena_pb2 -@_parameterized.named_parameters( +@parameterized.named_parameters( ('_proto2', unittest_pb2), ('_proto3', unittest_proto3_arena_pb2), ) @@ -98,7 +98,7 @@ def test_pytype_allows_unset_self_and_self_underscore_field(self): _EXPECTED_PROTO2 = b'\x06\x08\x00r\x02hi\x06\x08\x01r\x02hi\x06\x08\x02r\x02hi' -@_parameterized.named_parameters( +@parameterized.named_parameters( ('_proto2', unittest_pb2, _EXPECTED_PROTO2), ('_proto3', unittest_proto3_arena_pb2, _EXPECTED_PROTO3), ) diff --git a/python/google/protobuf/internal/reflection_cpp_test.py b/python/google/protobuf/internal/reflection_cpp_test.py index 71e7b18cdd9ee..bb053f781e7b0 100644 --- a/python/google/protobuf/internal/reflection_cpp_test.py +++ b/python/google/protobuf/internal/reflection_cpp_test.py @@ -12,12 +12,12 @@ import unittest from google.protobuf.internal import testing_refleaks -from google.protobuf.internal import _parameterized +from absl.testing import parameterized from google.protobuf import unittest_pb2 from google.protobuf import unittest_proto3_arena_pb2 -@_parameterized.named_parameters( +@parameterized.named_parameters( ('_proto2', unittest_pb2), ('_proto3', unittest_proto3_arena_pb2) ) @testing_refleaks.TestCase diff --git a/python/google/protobuf/internal/reflection_test.py b/python/google/protobuf/internal/reflection_test.py index 9640c0b3330f2..55e79370a6a42 100755 --- a/python/google/protobuf/internal/reflection_test.py +++ b/python/google/protobuf/internal/reflection_test.py @@ -32,7 +32,7 @@ from google.protobuf.internal import test_util from google.protobuf.internal import testing_refleaks from google.protobuf.internal import wire_format -from google.protobuf.internal import _parameterized +from absl.testing import parameterized from google.protobuf import unittest_import_pb2 from google.protobuf import unittest_mset_pb2 from google.protobuf import unittest_pb2 @@ -87,7 +87,7 @@ def EndOfStream(self): return self._pos == len(self._bytes) -@_parameterized.named_parameters( +@parameterized.named_parameters( ('_proto2', unittest_pb2), ('_proto3', unittest_proto3_arena_pb2)) @testing_refleaks.TestCase diff --git a/python/google/protobuf/internal/text_format_test.py b/python/google/protobuf/internal/text_format_test.py index 1f1b5eb8e7814..952c74ad1d010 100644 --- a/python/google/protobuf/internal/text_format_test.py +++ b/python/google/protobuf/internal/text_format_test.py @@ -26,7 +26,7 @@ from google.protobuf.internal import test_util from google.protobuf import descriptor_pool from google.protobuf import text_format -from google.protobuf.internal import _parameterized +from absl.testing import parameterized from google.protobuf import any_test_pb2 from google.protobuf import map_unittest_pb2 from google.protobuf import unittest_mset_pb2 @@ -75,7 +75,7 @@ def RemoveRedundantZeros(self, text): return text -@_parameterized.parameters(unittest_pb2, unittest_proto3_arena_pb2) +@parameterized.parameters(unittest_pb2, unittest_proto3_arena_pb2) class TextFormatMessageToStringTests(TextFormatBase): def testPrintExotic(self, message_module): @@ -610,7 +610,7 @@ def testPrintUnknownFieldsEmbeddedMessageInBytes(self, message_module): as_one_line=True)) -@_parameterized.parameters(unittest_pb2, unittest_proto3_arena_pb2) +@parameterized.parameters(unittest_pb2, unittest_proto3_arena_pb2) class TextFormatMessageToTextBytesTests(TextFormatBase): def testMessageToBytes(self, message_module): @@ -676,7 +676,7 @@ def testEscapedUtf8ASCIIRoundTrip(self, message_module): parsed_message.repeated_string[0])) -@_parameterized.parameters(unittest_pb2, unittest_proto3_arena_pb2) +@parameterized.parameters(unittest_pb2, unittest_proto3_arena_pb2) class TextFormatParserTests(TextFormatBase): def testParseAllFields(self, message_module): @@ -1002,7 +1002,7 @@ def testParseExistingScalarInMessage(self, message_module): text_format.Parse, text, message) -@_parameterized.parameters(unittest_pb2, unittest_proto3_arena_pb2) +@parameterized.parameters(unittest_pb2, unittest_proto3_arena_pb2) class TextFormatMergeTests(TextFormatBase): def testMergeDuplicateScalarsInText(self, message_module): @@ -2163,9 +2163,9 @@ def _Consume(tokenizer): return (_Consume, expected_literal) -class TokenizerTest(_parameterized.TestCase): +class TokenizerTest(parameterized.TestCase): - @_parameterized.named_parameters([ + @parameterized.named_parameters([ dict( testcase_name='_string_double_quotes', text='identifier1:"string1"\n', @@ -2690,7 +2690,7 @@ def testParseDelimitedInvalidScope(self): text_format.Parse('NotGroupLikeScope { b:1 }', msg) # Tests for pretty printer functionality. -@_parameterized.parameters((unittest_pb2), (unittest_proto3_arena_pb2)) +@parameterized.parameters((unittest_pb2), (unittest_proto3_arena_pb2)) class PrettyPrinterTest(TextFormatBase): def testPrettyPrintNoMatch(self, message_module): diff --git a/python/google/protobuf/internal/well_known_types_test.py b/python/google/protobuf/internal/well_known_types_test.py index 1a6a97c1c6dbb..683c2a5eee389 100644 --- a/python/google/protobuf/internal/well_known_types_test.py +++ b/python/google/protobuf/internal/well_known_types_test.py @@ -23,7 +23,7 @@ from google.protobuf import duration_pb2 from google.protobuf import struct_pb2 from google.protobuf import timestamp_pb2 -from google.protobuf.internal import _parameterized +from absl.testing import parameterized from google.protobuf import unittest_pb2 try: @@ -38,7 +38,7 @@ has_zoneinfo = False -class TimeUtilTestBase(_parameterized.TestCase): +class TimeUtilTestBase(parameterized.TestCase): def CheckTimestampConversion(self, message, text): self.assertEqual(text, message.ToJsonString()) @@ -270,7 +270,7 @@ def testTimezoneNaiveMinDatetimeConversion(self): self.assertEqual(naive_min_datetime, ts.ToDatetime()) # Two hours after the Unix Epoch, around the world. - @_parameterized.named_parameters( + @parameterized.named_parameters( ('London', [1970, 1, 1, 2], datetime.timezone.utc), ('Tokyo', [1970, 1, 1, 11], _TZ_JAPAN), ('LA', [1969, 12, 31, 18], _TZ_PACIFIC), @@ -355,7 +355,7 @@ def testTimezoneAwareMinDatetimeConversion(self): ) # Two hours after the Unix Epoch, around the world. - @_parameterized.named_parameters( + @parameterized.named_parameters( ('London', [1970, 1, 1, 2], datetime.timezone.utc), ('Tokyo', [1970, 1, 1, 11], _TZ_JAPAN), ('LA', [1969, 12, 31, 18], _TZ_PACIFIC), @@ -368,7 +368,7 @@ def testTimestampAssignment(self, date_parts, tzinfo): self.assertEqual(0, msg.optional_timestamp.nanos) # Two hours after the Unix Epoch, around the world. - @_parameterized.named_parameters( + @parameterized.named_parameters( ('London', [1970, 1, 1, 2], datetime.timezone.utc), ('Tokyo', [1970, 1, 1, 11], _TZ_JAPAN), ('LA', [1969, 12, 31, 18], _TZ_PACIFIC), @@ -387,7 +387,7 @@ def testTimestampCreation(self, date_parts, tzinfo): self.assertEqual(7200, msg2.optional_timestamp.seconds) self.assertEqual(0, msg2.optional_timestamp.nanos) - @_parameterized.named_parameters( + @parameterized.named_parameters( ( 'tz_aware_min_dt', datetime.datetime(1, 1, 1, tzinfo=datetime.timezone.utc), @@ -426,7 +426,7 @@ def testTimestampAdd(self, old_time, time_delta, expected_sec, expected_nano): self.assertEqual(expected_sec, new_msg3.optional_timestamp.seconds) self.assertEqual(expected_nano, new_msg3.optional_timestamp.nanos) - @_parameterized.named_parameters( + @parameterized.named_parameters( ( 'test1', datetime.datetime(999, 1, 1, tzinfo=datetime.timezone.utc), @@ -593,7 +593,7 @@ def testInvalidDuration(self): with self.assertRaises(TypeError): 123 - msg.optional_duration - @_parameterized.named_parameters( + @parameterized.named_parameters( ('test1', -1999999, -1, -999999000), ('test2', 1999999, 1, 999999000) ) def testDurationAssignment(self, microseconds, expected_sec, expected_nano): @@ -604,7 +604,7 @@ def testDurationAssignment(self, microseconds, expected_sec, expected_nano): self.assertEqual(expected_sec, message.optional_duration.seconds) self.assertEqual(expected_nano, message.optional_duration.nanos) - @_parameterized.named_parameters( + @parameterized.named_parameters( ('test1', -1999999, -1, -999999000), ('test2', 1999999, 1, 999999000) ) def testDurationCreation(self, microseconds, expected_sec, expected_nano): @@ -616,7 +616,7 @@ def testDurationCreation(self, microseconds, expected_sec, expected_nano): self.assertEqual(expected_sec, message.optional_duration.seconds) self.assertEqual(expected_nano, message.optional_duration.nanos) - @_parameterized.named_parameters( + @parameterized.named_parameters( ( 'tz_aware_min_dt', datetime.datetime(1, 1, 1, tzinfo=datetime.timezone.utc), @@ -655,7 +655,7 @@ def testDurationAdd(self, old_time, time_delta, expected_sec, expected_nano): self.assertEqual(expected_sec, msg3.optional_timestamp.seconds) self.assertEqual(expected_nano, msg3.optional_timestamp.nanos) - @_parameterized.named_parameters( + @parameterized.named_parameters( ( 'test1', datetime.datetime(999, 1, 1, tzinfo=datetime.timezone.utc), diff --git a/python/internal.bzl b/python/internal.bzl index b789e5125198d..c9be66e1076e3 100644 --- a/python/internal.bzl +++ b/python/internal.bzl @@ -129,7 +129,10 @@ def internal_py_test(deps = [], **kwargs): """ py_test( imports = ["."], - deps = deps + ["//python:python_test_lib"], + deps = deps + [ + "//python:python_test_lib", + "@com_google_absl_py//absl/testing:parameterized", + ], target_compatible_with = select({ "@system_python//:supported": [], "//conditions:default": ["@platforms//:incompatible"], diff --git a/python/pb_unit_tests/pyproto_test_wrapper.bzl b/python/pb_unit_tests/pyproto_test_wrapper.bzl index 1e2b5daa2db5f..b2710bcb78721 100644 --- a/python/pb_unit_tests/pyproto_test_wrapper.bzl +++ b/python/pb_unit_tests/pyproto_test_wrapper.bzl @@ -11,6 +11,7 @@ def pyproto_test_wrapper(name, deps = []): main = src, data = ["//src/google/protobuf:testdata"], deps = [ + "@com_google_absl_py//absl/testing:parameterized", "//python:_message", "//:python_common_test_protos", "//:python_specific_test_protos", diff --git a/python/requirements.txt b/python/requirements.txt index 153a5054dde13..95c86012b1ef4 100644 --- a/python/requirements.txt +++ b/python/requirements.txt @@ -1,2 +1,3 @@ numpy<=2.1.0 setuptools<=70.3.0 +absl-py==2.*