Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 0 additions & 15 deletions launch_testing/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,21 +44,6 @@ The launch description needs to include a `ReadyToTest` action to signal to the

In the above example, there is no need to delay the start of the tests so the `ReadyToTest` action is a peer to the process under test and will signal to the framework that it's safe to start around the same time the `ExecuteProcess` action is run.

In older style tests, a function called `ready_fn` is declared as an argument to `generate_test_description` and must be plumbed into the launch description with an `OpaqueFunction`.

```python
def generate_test_description(ready_fn):

return launch.LaunchDescription([
launch.actions.ExecuteProcess(
cmd=[path_to_process],
),

# Start tests right away - no need to wait for anything in this example
launch.actions.OpaqueFunction(function=lambda context: ready_fn()),
])
```

#### Active Tests

Any classes that inherit from `unittest.TestCase` and not decorated with the `post_shutdown_test` descriptor will be run concurrently with the proccess under test.
Expand Down
67 changes: 26 additions & 41 deletions launch_testing/launch_testing/loader.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,49 +42,34 @@ def normalize(result):
return result, {}

def wrapper(**kwargs):

fn_args = inspect.getfullargspec(launch_description_fn)

if 'ready_fn' in fn_args.args + fn_args.kwonlyargs:
# This is an old-style launch_description function which expects ready_fn to be passed
# in to the function
# This type of launch description will be deprecated in the future. Warn about it
# here
warnings.warn(
'Passing ready_fn as an argument to generate_test_description will '
'be removed in a future release. Include a launch_testing.actions.ReadyToTest '
'action in the LaunchDescription instead.'
)
return normalize(launch_description_fn(**kwargs))
else:
# This is a new-style launch_description which should contain a ReadyToTest action
ready_fn = kwargs.pop('ready_fn')
result = normalize(launch_description_fn(**kwargs))
# Fish the ReadyToTest action out of the launch description and plumb our
# ready_fn to it

def iterate_ready_to_test_actions(entities):
"""Recursively search LaunchDescription entities for all ReadyToTest actions."""
for entity in entities:
if isinstance(entity, ReadyToTest):
yield entity
# This is a new-style launch_description which should contain a ReadyToTest action
ready_fn = kwargs.pop('ready_fn')
result = normalize(launch_description_fn(**kwargs))
# Fish the ReadyToTest action out of the launch description and plumb our
# ready_fn to it

def iterate_ready_to_test_actions(entities):
"""Recursively search LaunchDescription entities for all ReadyToTest actions."""
for entity in entities:
if isinstance(entity, ReadyToTest):
yield entity
yield from iterate_ready_to_test_actions(
entity.describe_sub_entities()
)
for conditional_sub_entity in entity.describe_conditional_sub_entities():
yield from iterate_ready_to_test_actions(
entity.describe_sub_entities()
conditional_sub_entity[1]
)
for conditional_sub_entity in entity.describe_conditional_sub_entities():
yield from iterate_ready_to_test_actions(
conditional_sub_entity[1]
)

try:
ready_action = next(e for e in iterate_ready_to_test_actions(result[0].entities))
except StopIteration: # No ReadyToTest action found
raise Exception(
'generate_test_description functions without a ready_fn argument must return '
'a LaunchDescription containing a ReadyToTest action'
)
ready_action._add_callback(ready_fn)
return result

try:
ready_action = next(e for e in iterate_ready_to_test_actions(result[0].entities))
except StopIteration: # No ReadyToTest action found
raise Exception(
'generate_test_description functions must return '
'a LaunchDescription containing a ReadyToTest action'
)
ready_action._add_callback(ready_fn)
return result

return wrapper

Expand Down
2 changes: 0 additions & 2 deletions launch_testing/launch_testing/test_runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -292,8 +292,6 @@ def validate(self):

# Check for extra args in generate_test_description
for argname in base_args:
if argname == 'ready_fn':
continue
if argname not in run.param_args.keys():
raise Exception(
"generate_test_description has unexpected extra argument '{}'".format(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,6 @@ class TestLaunchTestRunnerValidation(unittest.TestCase):

def test_catches_bad_signature(self):

# A `generate_test_description` function without a ready_fn argument is allowed because
# it might be a new style function that uses a ReadyToTest action to signal when it's time
# for tests to start.
# If there's no ReadyToTest action, we won't catch that until later because dut.validate()
# doesn't actually invoke the function.
# We will still expect to reject functions with wrong name arguments
Expand All @@ -50,18 +47,10 @@ def test_catches_bad_signature(self):
with self.assertRaisesRegex(Exception, "unexpected extra argument 'misspelled_ready_fn'"):
dut.validate()

dut = LaunchTestRunner(
make_test_run_for_dut(
lambda ready_fn: None
)
)

dut.validate()

def test_too_many_arguments(self):

dut = LaunchTestRunner(
make_test_run_for_dut(lambda ready_fn, extra_arg: None)
make_test_run_for_dut(lambda extra_arg: None)
)

with self.assertRaisesRegex(Exception, "unexpected extra argument 'extra_arg'"):
Expand All @@ -70,7 +59,7 @@ def test_too_many_arguments(self):
def test_bad_parametrization_argument(self):

@launch_testing.parametrize('bad_argument', [1, 2, 3])
def bad_launch_description(ready_fn):
def bad_launch_description():
pass # pragma: no cover

dut = LaunchTestRunner(
Expand Down