From 1885c07456ce9f58a68e409921023a397420764a Mon Sep 17 00:00:00 2001 From: yuxuan-ms Date: Mon, 2 Dec 2024 16:12:47 +0800 Subject: [PATCH] Change the default name function of parameters testcase. --- doc/newsfragments/3160_changed.case_name.rst | 1 + testplan/common/utils/reporting.py | 2 +- testplan/testing/multitest/parametrization.py | 37 +++++++++++++++++-- testplan/testing/multitest/suite.py | 4 +- .../testing/multitest/test_parametrization.py | 21 +++++------ 5 files changed, 47 insertions(+), 18 deletions(-) create mode 100755 doc/newsfragments/3160_changed.case_name.rst diff --git a/doc/newsfragments/3160_changed.case_name.rst b/doc/newsfragments/3160_changed.case_name.rst new file mode 100755 index 000000000..1be5f1a32 --- /dev/null +++ b/doc/newsfragments/3160_changed.case_name.rst @@ -0,0 +1 @@ +Limit the length of parameterization testcase name to 255 characters. If the name length exceeds 255 characters, index-suffixed names (e.g., {func_name[:240]} 1, {func_name[:240]} 2) will be used. \ No newline at end of file diff --git a/testplan/common/utils/reporting.py b/testplan/common/utils/reporting.py index 12dd069e3..6ae133564 100644 --- a/testplan/common/utils/reporting.py +++ b/testplan/common/utils/reporting.py @@ -26,7 +26,7 @@ def __new__(cls): cls.__instance = object.__new__(cls) return cls.__instance - def __str__(self): + def __repr__(self): return self.descr diff --git a/testplan/testing/multitest/parametrization.py b/testplan/testing/multitest/parametrization.py index 528e8de93..47a53f9d5 100644 --- a/testplan/testing/multitest/parametrization.py +++ b/testplan/testing/multitest/parametrization.py @@ -9,6 +9,7 @@ from testplan.common.utils import callable as callable_utils from testplan.common.utils import convert, interface from testplan.testing import tagging +from typing import Callable, Optional # Although any string will be processed as normal, it's a good # approach to warn the user if the generated method name is not a @@ -206,8 +207,11 @@ def _generated(self, env, result): ) # Users request the feature that when `name_func` set to `None`, # then simply append integer suffixes to the names of testcases - _generated.name = ( - name_func(name, kwargs) if name_func is not None else f"{name} {idx}" + _generated.name = _parametrization_report_name_func_wrapper( + name_func=name_func, + name=name, + kwargs=kwargs, + index=idx, ) if hasattr(function, "__xfail__"): @@ -267,7 +271,7 @@ def _check_tag_func(tag_func): ) -def _parametrization_name_func_wrapper(func_name, kwargs): +def _parametrization_name_func_wrapper(func_name: str, kwargs: dict): """ Make sure that name generation doesn't end up with invalid / unreadable attribute names/types etc. @@ -291,6 +295,31 @@ def _parametrization_name_func_wrapper(func_name, kwargs): return generated_name +def _parametrization_report_name_func_wrapper( + name_func: Optional[Callable], name: str, kwargs: dict, index: int +): + """ + Make sure that generated name is not too long, + if it is, then use index suffixed names e.g. "{func_name} 1", "{func_name} 2", will be used. + """ + if name_func: + generated_name = name_func(name, kwargs) + if not isinstance(generated_name, str): + raise ValueError( + "The return value of name_func must be a string, " + f"it is of type: {type(generated_name)}, value: {generated_name}" + ) + if len(generated_name) <= MAX_METHOD_NAME_LENGTH: + return generated_name + else: + warnings.warn( + f"The name name_func returned ({generated_name}) is too long, using index suffixed names." + ) + + generated_name = f"{name[:MAX_METHOD_NAME_LENGTH - 10]} {index}" + return generated_name + + def parametrization_name_func(func_name, kwargs): """ Method name generator for parametrized testcases. @@ -321,7 +350,7 @@ def default_name_func(func_name, kwargs): >>> import collections >>> default_name_func('Test Method', collections.OrderedDict(('foo', 5), ('bar', 10))) - 'Test Method {foo:5, bar:10}' + 'Test Method ' :param func_name: Name of the parametrization target function. :type func_name: ``str`` diff --git a/testplan/testing/multitest/suite.py b/testplan/testing/multitest/suite.py index 9d03ee7c5..ea9115511 100644 --- a/testplan/testing/multitest/suite.py +++ b/testplan/testing/multitest/suite.py @@ -581,9 +581,9 @@ def _validate_testcase(func): if len(func.name) > defaults.MAX_TEST_NAME_LENGTH: warnings.warn( - 'Name defined for testcase "{}" is too long,' + f'Name defined for testcase "{func.name}" is too long,' ' consider customizing testcase name with argument "name_func"' - " in @testcase decorator.".format(func.__name__) + " in @testcase decorator." ) diff --git a/tests/functional/testplan/testing/multitest/test_parametrization.py b/tests/functional/testplan/testing/multitest/test_parametrization.py index 4ce973faf..9e0a0abdd 100644 --- a/tests/functional/testplan/testing/multitest/test_parametrization.py +++ b/tests/functional/testplan/testing/multitest/test_parametrization.py @@ -1,11 +1,9 @@ import sys import logging -from contextlib import contextmanager from unittest import mock -from imp import reload +from importlib import reload import pytest - from testplan.defaults import MAX_TEST_NAME_LENGTH from testplan.testing.multitest import MultiTest, testsuite, testcase from testplan.testing.multitest.parametrization import ( @@ -24,8 +22,8 @@ LOGGER = logging.getLogger() -@contextmanager -def module_reloaded(mod): +@pytest.fixture() +def suite_reloaded(): """ If uncaught exception raised, Testplan process should abort. However, if the process is managed by PyTest for testing purpose, then the @@ -33,6 +31,7 @@ def module_reloaded(mod): modules still exist in memory, some global variables need to be reset. """ yield + mod = "testplan.testing.multitest.suite" if mod in sys.modules: reload(sys.modules[mod]) @@ -214,7 +213,7 @@ def test_sample(self, env, result, a, b): ), ), ) -def test_invalid_parametrization(val, msg): +def test_invalid_parametrization(suite_reloaded, val, msg): """Correct arguments should be passed to parametrized testcases.""" with pytest.raises(ParametrizationError): @@ -227,7 +226,7 @@ def sample_test(self, env, result, a, b, c=3): pytest.fail(msg) -def test_duplicate_parametrization_template_definition(): +def test_duplicate_parametrization_template_definition(suite_reloaded): """No duplicate name of testcase or parametrization template allowed.""" with pytest.raises(ValueError): @@ -297,7 +296,7 @@ def sample(self, env, result, test__val): ( ( ("#@)$*@#%", "a-b"), - ["sample_test ", "sample_test "], + ["sample_test ", "sample_test "], ["sample_test__0", "sample_test__1"], "Should use original method name + index fallback if" " generated names are not valid Python attribute names.", @@ -305,8 +304,8 @@ def sample(self, env, result, test__val): ( ("a" * MAX_METHOD_NAME_LENGTH, "b" * MAX_METHOD_NAME_LENGTH), [ - "sample_test ".format("a" * MAX_METHOD_NAME_LENGTH), - "sample_test ".format("b" * MAX_METHOD_NAME_LENGTH), + "sample_test 0", + "sample_test 1", ], ["sample_test__0", "sample_test__1"], "Should use original method name + index fallback if" @@ -451,7 +450,7 @@ def sample_test(self, env, result, a, b): ), ), ) -def test_invalid_name_func(name_func, msg, err): +def test_invalid_name_func(suite_reloaded, name_func, msg, err): """Custom naming function should be correctly defined.""" with pytest.raises(err):