-
Notifications
You must be signed in to change notification settings - Fork 96
Add support for non-branch coverage goals in DynaMOSA #142
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 12 commits
c3f4d79
4567cb0
e116d58
c54978a
7d30d0f
209ec1d
7346aaa
1566327
91c3112
a1c3539
3213ab5
472406c
3595600
516c14d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -167,11 +167,22 @@ def __init__( | |
| ) -> None: | ||
| self._archive = archive | ||
| branch_fitness_functions: OrderedSet[bg.BranchCoverageTestFitness] = OrderedSet() | ||
| non_branch_fitness_functions: OrderedSet[ff.FitnessFunction] = OrderedSet() | ||
|
|
||
| for fit in fitness_functions: | ||
| assert isinstance(fit, bg.BranchCoverageTestFitness) | ||
| branch_fitness_functions.add(fit) | ||
| if isinstance(fit, bg.BranchCoverageTestFitness): | ||
| branch_fitness_functions.add(fit) | ||
| else: | ||
| non_branch_fitness_functions.add(fit) | ||
|
|
||
| self._graph = _BranchFitnessGraph(branch_fitness_functions, subject_properties) | ||
| self._current_goals: OrderedSet[bg.BranchCoverageTestFitness] = self._graph.root_branches | ||
|
|
||
| # Start with branch root goals | ||
| self._current_goals: OrderedSet[ff.FitnessFunction] = OrderedSet(self._graph.root_branches) | ||
|
|
||
| # Store non-branch goals separately (DO NOT activate yet) | ||
| self._non_branch_goals: OrderedSet[ff.FitnessFunction] = non_branch_fitness_functions | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The name might cause confusion with |
||
|
|
||
| self._archive.add_goals(self._current_goals) # type: ignore[arg-type] | ||
|
|
||
| @property | ||
|
|
@@ -194,7 +205,7 @@ def update(self, solutions: list[tcc.TestCaseChromosome]) -> None: | |
| while new_goals_added: | ||
| self._archive.update(solutions) | ||
| covered = self._archive.covered_goals | ||
| new_goals: OrderedSet[bg.BranchCoverageTestFitness] = OrderedSet() | ||
| new_goals: OrderedSet[ff.FitnessFunction] = OrderedSet() | ||
| new_goals_added = False | ||
| for old_goal in self._current_goals: | ||
| if old_goal in covered: | ||
|
|
@@ -208,6 +219,16 @@ def update(self, solutions: list[tcc.TestCaseChromosome]) -> None: | |
| self._current_goals = new_goals | ||
| self._archive.add_goals(self._current_goals) # type: ignore[arg-type] | ||
| self._logger.debug("current goals after update: %s", self._current_goals) | ||
| # Add non-branch goals ONLY after all branch goals are covered | ||
| if len(self._archive.uncovered_goals) == 0: | ||
| added = False | ||
| for goal in self._non_branch_goals: | ||
| if goal not in self._current_goals: | ||
| self._current_goals.add(goal) | ||
| added = True | ||
|
|
||
| if added: | ||
| self._archive.add_goals(self._current_goals) # type: ignore[arg-type] | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For simplicity, let's assume the second |
||
|
|
||
|
|
||
| class _BranchFitnessGraph: | ||
|
|
||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Unit tests with heavy mocking are great to test some behaviour of your code in isolation. In this case it is tested, that initially "non-branch" goals are not active, which makes sense if it is intended as it is in this case. Even if that is also covered with a unit test with heavy mocking, it is still not tested that the behaviour is the same for non-mocked stuff. In general, using a simple non-mocked example with a real archive, real goals and a real subject is preferrable here. Even if all of that is added, I would still not be convinced that now DynaMOSA + LineCoverage works. This must be tested with an integration test. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,52 @@ | ||
| # This file is part of Pynguin. | ||
| # | ||
| # SPDX-FileCopyrightText: 2019–2026 Pynguin Contributors | ||
| # | ||
| # SPDX-License-Identifier: MIT | ||
| # | ||
| """Tests for non-branch goal handling in DynaMOSA.""" | ||
|
|
||
| from typing import ClassVar | ||
|
|
||
| from pynguin.ga.algorithms.dynamosaalgorithm import _GoalsManager # noqa: PLC2701 | ||
| from pynguin.utils.orderedset import OrderedSet | ||
|
|
||
|
|
||
| def test_non_branch_goals_added_after_branch_completion(): | ||
| """Ensure non-branch goals activate only after branch goals are covered.""" | ||
|
|
||
| class DummyGoal: | ||
| """Simple dummy goal.""" | ||
|
|
||
| class DummyArchive: | ||
| """Minimal archive mock.""" | ||
|
|
||
| def __init__(self) -> None: | ||
| """Initialize archive state.""" | ||
| self.covered_goals = set() | ||
| self.uncovered_goals = set() | ||
|
|
||
| def update(self, solutions) -> None: | ||
| """Mock update.""" | ||
| return | ||
|
|
||
| def add_goals(self, goals) -> None: | ||
| """Track uncovered goals.""" | ||
| self.uncovered_goals = set(goals) | ||
|
|
||
| class DummySubject: | ||
| """Minimal subject properties mock.""" | ||
|
|
||
| existing_predicates: ClassVar[dict] = {} | ||
| existing_code_objects: ClassVar[dict] = {} | ||
|
|
||
| non_branch_goal = DummyGoal() | ||
|
|
||
| manager = _GoalsManager( | ||
| OrderedSet([non_branch_goal]), | ||
| DummyArchive(), | ||
| DummySubject(), | ||
| ) | ||
|
|
||
| # Initially, non-branch goals should NOT be active | ||
| assert non_branch_goal not in manager.current_goals |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In the current implementation,
DynaMOSAwill not work if onefitness_functionsis notBranchCoverageTestFitness. If this stays like this, we should still ensure that at least one of thefitness_functionsis aBranchCoverageTestFitness.