Skip to content

Commit b655230

Browse files
authored
Warn that old-style ready_fn and test attributes will be deprecated (#346)
* Warn that old-style ready_fn and test attributes will be deprecated - Using a ReadyToTest action allows more future flexibility than a ready_fn passed to generate_test_description. Warn that ReadyToTest action will be required in the future - Passing proc_info and proc_output and test args as arguments to test methods is much cleaner than relying on self.proc_info and self.proc_output 'magically' being added to the tests and works more like pytest fixtures. Warn that this is will go away in the future Signed-off-by: Pete Baughman <[email protected]> * Use a property to warn on access instead of a wrapper class - It's a little wasteful to set this on every test instance, because they share the same __class__ object, but it's fast and temporary until this is deprecated Signed-off-by: Pete Baughman <[email protected]> * Only add warning properties to classes once Signed-off-by: Pete Baughman <[email protected]> * Remote ready_fn from some launch_testing tests Signed-off-by: Pete Baughman <[email protected]> * Fix flake8 errors related to import order Signed-off-by: Pete Baughman <[email protected]> * Use the warnings module instead of logging - This may make it easier to see the warnings in CI logs Signed-off-by: Pete Baughman <[email protected]>
1 parent a5ec2a4 commit b655230

File tree

6 files changed

+56
-25
lines changed

6 files changed

+56
-25
lines changed

launch_testing/launch_testing/loader.py

Lines changed: 33 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,22 @@
1515
import functools
1616
import inspect
1717
import itertools
18+
import os
1819
import unittest
20+
import warnings
1921

2022
from .actions import ReadyToTest
2123

2224

25+
# Patch up the warnings module to streamline the warning messages. See
26+
# https://docs.python.org/3/library/warnings.html#warnings.showwarning
27+
def slim_formatwarning(msg, *args, **kwargs):
28+
return 'Warning: ' + str(msg) + os.linesep
29+
30+
31+
warnings.formatwarning = slim_formatwarning
32+
33+
2334
def _normalize_ld(launch_description_fn):
2435
# A launch description fn can return just a launch description, or a tuple of
2536
# (launch_description, test_context). This wrapper function normalizes things
@@ -35,8 +46,15 @@ def wrapper(**kwargs):
3546
fn_args = inspect.getfullargspec(launch_description_fn)
3647

3748
if 'ready_fn' in fn_args.args + fn_args.kwonlyargs:
38-
# This is an old-style launch_description function which epects ready_fn to be passed
49+
# This is an old-style launch_description function which expects ready_fn to be passed
3950
# in to the function
51+
# This type of launch description will be deprecated in the future. Warn about it
52+
# here
53+
warnings.warn(
54+
'Passing ready_fn as an argument to generate_test_description will '
55+
'be removed in a future release. Include a launch_testing.actions.ReadyToTest '
56+
'action in the LaunchDescription instead.'
57+
)
4058
return normalize(launch_description_fn(**kwargs))
4159
else:
4260
# This is a new-style launch_description which should contain a ReadyToTest action
@@ -240,14 +258,23 @@ def _partially_bind_matching_args(unbound_function, arg_candidates):
240258

241259

242260
def _give_attribute_to_tests(data, attr_name, test_suite):
243-
# Test suites can contain other test suites which will eventually contain
244-
# the actual test classes to run. This function will recursively drill down until
245-
# we find the actual tests and give the tests a reference to the process
261+
262+
def _warn_getter(self):
263+
if not hasattr(self, '__warned'):
264+
warnings.warn(
265+
'Automatically adding attributes like self.{0} '
266+
'to the test class will be deprecated in a future release. '
267+
'Instead, add {0} to the test method argument list to '
268+
'access the test object you need'.format(attr_name)
269+
)
270+
setattr(self, '__warned', True)
271+
272+
return data
246273

247274
# The effect of this is that every test will have `self.attr_name` available to it so that
248275
# it can interact with ROS2 or the process exit coes, or IO or whatever data we want
249-
for test in _iterate_tests_in_test_suite(test_suite):
250-
setattr(test, attr_name, data)
276+
for cls in _iterate_test_classes_in_test_suite(test_suite):
277+
setattr(cls, attr_name, property(fget=_warn_getter))
251278

252279

253280
def _iterate_test_classes_in_test_suite(test_suite):

launch_testing/test/launch_testing/examples/good_proc_launch_test.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import launch.actions
2323

2424
import launch_testing
25+
import launch_testing.actions
2526
from launch_testing.asserts import assertSequentialStdout
2627

2728
import pytest
@@ -44,13 +45,13 @@
4445

4546

4647
@pytest.mark.launch_test
47-
def generate_test_description(ready_fn):
48+
def generate_test_description():
4849

4950
return launch.LaunchDescription([
5051
dut_process,
5152

5253
# Start tests right away - no need to wait for anything
53-
launch.actions.OpaqueFunction(function=lambda context: ready_fn()),
54+
launch_testing.actions.ReadyToTest(),
5455
])
5556

5657

launch_testing/test/launch_testing/test_proc_info_assertions.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,14 @@
2020
import launch
2121
import launch.actions
2222
import launch.events.process
23+
import launch_testing.actions
2324
from launch_testing.test_runner import LaunchTestRunner
2425
import launch_testing.util
2526

2627

2728
def test_wait_for_shutdown(source_test_loader):
2829

29-
def generate_test_description(ready_fn):
30+
def generate_test_description():
3031
TEST_PROC_PATH = os.path.join(
3132
ament_index_python.get_package_prefix('launch_testing'),
3233
'lib/launch_testing',
@@ -52,7 +53,7 @@ def generate_test_description(ready_fn):
5253
)
5354
]
5455
),
55-
launch.actions.OpaqueFunction(function=lambda context: ready_fn())
56+
launch_testing.actions.ReadyToTest(),
5657
]), {'good_process': good_process}
5758

5859
# This is kind of a weird test-within-a-test, but it's the easiest way to get
@@ -82,7 +83,7 @@ def test_02_check_when_process_exits(self, proc_info, good_process):
8283

8384
def test_wait_for_startup(source_test_loader):
8485

85-
def generate_test_description(ready_fn):
86+
def generate_test_description():
8687
TEST_PROC_PATH = os.path.join(
8788
ament_index_python.get_package_prefix('launch_testing'),
8889
'lib/launch_testing',
@@ -99,7 +100,7 @@ def generate_test_description(ready_fn):
99100
period=10.0,
100101
actions=[good_process]
101102
),
102-
launch.actions.OpaqueFunction(function=lambda context: ready_fn())
103+
launch_testing.actions.ReadyToTest(),
103104
]), {'good_process': good_process}
104105

105106
# This is kind of a weird test-within-a-test, but it's the easiest way to get

launch_testing/test/launch_testing/test_resolve_process.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import launch.actions
2222
import launch.substitutions
2323
import launch_testing
24+
import launch_testing.actions
2425
from launch_testing.loader import LoadTestsFromPythonModule
2526
from launch_testing.test_runner import LaunchTestRunner
2627
import launch_testing.util
@@ -86,7 +87,7 @@ def setUpClass(cls):
8687
proc_env = os.environ.copy()
8788
proc_env['PYTHONUNBUFFERED'] = '1'
8889

89-
def generate_test_description(ready_fn):
90+
def generate_test_description():
9091
no_arg_proc = launch.actions.ExecuteProcess(
9192
cmd=[sys.executable],
9293
env=proc_env
@@ -106,7 +107,7 @@ def generate_test_description(ready_fn):
106107
no_arg_proc,
107108
one_arg_proc,
108109
two_arg_proc,
109-
launch.actions.OpaqueFunction(function=lambda ctx: ready_fn())
110+
launch_testing.actions.ReadyToTest(),
110111
])
111112

112113
return (ld, locals())

launch_testing/test/launch_testing/test_runner_results.py

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import launch
2121
import launch.actions
2222
import launch_testing
23+
import launch_testing.actions
2324
from launch_testing.loader import TestRun as TR
2425
from launch_testing.test_runner import LaunchTestRunner
2526
import mock
@@ -29,7 +30,7 @@
2930
# indicate failure
3031
def test_dut_that_shuts_down(capsys):
3132

32-
def generate_test_description(ready_fn):
33+
def generate_test_description():
3334
TEST_PROC_PATH = os.path.join(
3435
ament_index_python.get_package_prefix('launch_testing'),
3536
'lib/launch_testing',
@@ -41,7 +42,7 @@ def generate_test_description(ready_fn):
4142
cmd=[sys.executable, TEST_PROC_PATH]
4243
),
4344

44-
launch.actions.OpaqueFunction(function=lambda context: ready_fn()),
45+
launch_testing.actions.ReadyToTest(),
4546
])
4647

4748
with mock.patch('launch_testing.test_runner._RunnerWorker._run_test'):
@@ -64,7 +65,7 @@ def test_dut_that_has_exception(capsys):
6465
# This is the same as above, but we also want to check we get extra output from processes
6566
# that had an exit code
6667

67-
def generate_test_description(ready_fn):
68+
def generate_test_description():
6869
TEST_PROC_PATH = os.path.join(
6970
ament_index_python.get_package_prefix('launch_testing'),
7071
'lib/launch_testing',
@@ -88,7 +89,7 @@ def generate_test_description(ready_fn):
8889
cmd=[sys.executable, EXIT_PROC_PATH, '--silent']
8990
),
9091

91-
launch.actions.OpaqueFunction(function=lambda context: ready_fn()),
92+
launch_testing.actions.ReadyToTest(),
9293
])
9394

9495
with mock.patch('launch_testing.test_runner._RunnerWorker._run_test'):
@@ -119,13 +120,13 @@ def test_ok(self):
119120
'good_proc'
120121
)
121122

122-
def generate_test_description(ready_fn):
123+
def generate_test_description():
123124
return launch.LaunchDescription([
124125
launch.actions.ExecuteProcess(
125126
cmd=[sys.executable, TEST_PROC_PATH]
126127
),
127128

128-
launch.actions.OpaqueFunction(function=lambda context: ready_fn()),
129+
launch_testing.actions.ReadyToTest(),
129130
])
130131

131132
runner = LaunchTestRunner(
@@ -146,7 +147,7 @@ def test_parametrized_run_with_one_failure(source_test_loader):
146147

147148
# Test Data
148149
@launch_testing.parametrize('arg_val', [1, 2, 3, 4, 5])
149-
def generate_test_description(arg_val, ready_fn):
150+
def generate_test_description(arg_val):
150151
TEST_PROC_PATH = os.path.join(
151152
ament_index_python.get_package_prefix('launch_testing'),
152153
'lib/launch_testing',
@@ -162,7 +163,7 @@ def generate_test_description(arg_val, ready_fn):
162163
cmd=[sys.executable, TEST_PROC_PATH],
163164
env=proc_env,
164165
),
165-
launch.actions.OpaqueFunction(function=lambda context: ready_fn())
166+
launch_testing.actions.ReadyToTest(),
166167
])
167168

168169
def test_fail_on_two(self, proc_output, arg_val):
@@ -192,7 +193,7 @@ def test_fail_on_three(self, arg_val):
192193
def test_skipped_launch_description(source_test_loader):
193194

194195
@unittest.skip('skip reason string')
195-
def generate_test_description(ready_fn):
196+
def generate_test_description():
196197
raise Exception('This should never be invoked') # pragma: no cover
197198

198199
def test_fail_always(self):

launch_testing/test/launch_testing/test_xml_output.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ class TestHost(unittest.TestCase):
9999

100100
def test_fail_results_serialize(self):
101101

102-
def generate_test_description(ready_fn):
102+
def generate_test_description():
103103
raise Exception('This should never be invoked') # pragma: no cover
104104

105105
def test_fail_always(self):
@@ -140,7 +140,7 @@ def test_skip_results_serialize(self):
140140
# This checks the case where all unit tests are skipped because of a skip
141141
# decorator on the generate_test_description function
142142
@unittest.skip('skip reason string')
143-
def generate_test_description(ready_fn):
143+
def generate_test_description():
144144
raise Exception('This should never be invoked') # pragma: no cover
145145

146146
def test_fail_always(self):

0 commit comments

Comments
 (0)