Skip to content

Commit b17dcdb

Browse files
committed
Merge branch 'fix/skip-sut-coroutines'
* fix/skip-sut-coroutines: Skip SUT coroutines instead of aborting the whole module
2 parents 8d6e8bc + 5b0cac2 commit b17dcdb

4 files changed

Lines changed: 44 additions & 17 deletions

File tree

src/pynguin/analyses/module.py

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,6 @@
8282
from pynguin.utils.exceptions import (
8383
ConstraintValidationError,
8484
ConstructionFailedException,
85-
CoroutineFoundException,
8685
)
8786
from pynguin.utils.generic.genericaccessibleobject import (
8887
GenericAccessibleObject,
@@ -1407,10 +1406,16 @@ def __analyse_function(
14071406
LOGGER.debug("Skipping function %s from analysis", func_name)
14081407
return
14091408
if inspect.iscoroutinefunction(func) or inspect.isasyncgenfunction(func):
1409+
# Pynguin cannot execute coroutines (see issue #62 / MR !59), so we skip
1410+
# them instead of aborting the whole module and still test its
1411+
# synchronous members.
14101412
if add_to_test:
1411-
raise CoroutineFoundException("Found coroutine in SUT: %s", func_name)
1412-
# Coroutine outside the SUT are not problematic, just exclude them.
1413-
LOGGER.debug("Skipping coroutine %s outside of SUT", func_name)
1413+
LOGGER.warning(
1414+
"Skipping coroutine %s: Pynguin only tests the non-async parts of this module.",
1415+
func_name,
1416+
)
1417+
else:
1418+
LOGGER.debug("Skipping coroutine %s outside of SUT", func_name)
14141419
return
14151420

14161421
LOGGER.debug("Analysing function %s", func_name)
@@ -1610,10 +1615,16 @@ def __analyse_method(
16101615
LOGGER.debug("Skipping method %s from analysis", method_name)
16111616
return
16121617
if inspect.iscoroutinefunction(method) or inspect.isasyncgenfunction(method):
1618+
# Pynguin cannot execute coroutines (see issue #62 / MR !59), so we skip
1619+
# them instead of aborting the whole module and still test its
1620+
# synchronous members.
16131621
if add_to_test:
1614-
raise CoroutineFoundException("Found coroutine in SUT: %s", method_name)
1615-
# Coroutine outside the SUT are not problematic, just exclude them.
1616-
LOGGER.debug("Skipping coroutine %s outside of SUT", method_name)
1622+
LOGGER.warning(
1623+
"Skipping coroutine %s: Pynguin only tests the non-async parts of this module.",
1624+
method_name,
1625+
)
1626+
else:
1627+
LOGGER.debug("Skipping coroutine %s outside of SUT", method_name)
16171628
return
16181629

16191630
LOGGER.debug("Analysing method %s.%s", type_info.full_name, method_name)

src/pynguin/generator.py

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@
8181
TestCaseExecutor,
8282
)
8383
from pynguin.utils import randomness
84-
from pynguin.utils.exceptions import ConfigurationException, CoroutineFoundException
84+
from pynguin.utils.exceptions import ConfigurationException
8585
from pynguin.utils.llm import LLM, LLMProvider, extract_code
8686
from pynguin.utils.report import (
8787
get_coverage_report,
@@ -164,11 +164,6 @@ def _setup_test_cluster() -> ModuleTestCluster | None:
164164
ex.name,
165165
)
166166
return None
167-
except CoroutineFoundException as ex:
168-
_LOGGER.exception(
169-
"Pynguin does not support test generation for coroutines (async def): %s", ex
170-
)
171-
return None
172167

173168
if test_cluster.num_accessible_objects_under_test() == 0:
174169
_LOGGER.error("SUT contains nothing we can test.")

tests/analyses/test_module.py

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
from pynguin.analyses.typesystem import ANY, AnyType, ProperType, TypeInfo, UnionType
3030
from pynguin.configuration import ElementVisibility
3131
from pynguin.ga.operators.selection import RandomSelection, RankSelection
32-
from pynguin.utils.exceptions import ConstructionFailedException, CoroutineFoundException
32+
from pynguin.utils.exceptions import ConstructionFailedException
3333
from pynguin.utils.generic.genericaccessibleobject import (
3434
GenericAccessibleObject,
3535
GenericCallableAccessibleObject,
@@ -619,9 +619,18 @@ def test_enums():
619619
"module_name",
620620
["async_func", "async_gen", "async_class_gen", "async_class_method"],
621621
)
622-
def test_analyse_async_function_or_method(module_name):
623-
with pytest.raises(CoroutineFoundException):
624-
generate_test_cluster(f"tests.fixtures.cluster.{module_name}")
622+
def test_analyse_async_function_or_method_does_not_abort(module_name):
623+
# A coroutine in the SUT must be skipped, not abort the whole module.
624+
cluster = generate_test_cluster(f"tests.fixtures.cluster.{module_name}")
625+
assert cluster is not None
626+
627+
628+
def test_analyse_mixed_async_and_sync_keeps_sync():
629+
cluster = generate_test_cluster("tests.fixtures.cluster.async_and_sync")
630+
names = {getattr(obj, "function_name", None) for obj in cluster.accessible_objects_under_test}
631+
# The synchronous function stays testable, the coroutine is skipped.
632+
assert "sync_bar" in names
633+
assert "async_foo" not in names
625634

626635

627636
def test_analyse_async_as_dependency():
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# This file is part of Pynguin.
2+
#
3+
# SPDX-FileCopyrightText: 2019–2026 Pynguin Contributors
4+
#
5+
# SPDX-License-Identifier: MIT
6+
#
7+
async def async_foo():
8+
return 45
9+
10+
11+
def sync_bar(x):
12+
return x + 1

0 commit comments

Comments
 (0)