Fix self-loop detection to use the Definition 6 self-relation
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
+123
-6
@@ -455,15 +455,16 @@ class TestForkAfterEnd(unittest.TestCase):
|
||||
class TestSelfLoop(unittest.TestCase):
|
||||
"""Tests self-loop detection from lifecycle traces.
|
||||
|
||||
An activity that completes more than once in a trace
|
||||
is a self-loop.
|
||||
An activity a is a self-loop iff a ->r a holds per
|
||||
Definition 6, that is, an end event of a is followed by
|
||||
a start event of a with no other end event in between.
|
||||
"""
|
||||
|
||||
def setUp(self) -> None:
|
||||
"""Set up trace with self-loop: A repeats.
|
||||
"""Set up trace with self-loop: A repeats at once.
|
||||
|
||||
Trace: As Ae Bs Be As Ae Cs Ce
|
||||
Activity A completes twice.
|
||||
Trace: As Ae As Ae Bs Be Cs Ce
|
||||
A ends and immediately starts again, so A ->r A.
|
||||
|
||||
:return: None.
|
||||
"""
|
||||
@@ -477,8 +478,8 @@ class TestSelfLoop(unittest.TestCase):
|
||||
tuple[tuple[Task, str], ...], int
|
||||
] = {
|
||||
((a, S), (a, E),
|
||||
(b, S), (b, E),
|
||||
(a, S), (a, E),
|
||||
(b, S), (b, E),
|
||||
(c, S), (c, E)): 1,
|
||||
}
|
||||
self.__dfg: RefinedDirectlyFollowsGraph = (
|
||||
@@ -505,6 +506,27 @@ class TestSelfLoop(unittest.TestCase):
|
||||
self.__dfg.self_loops,
|
||||
)
|
||||
|
||||
def test_no_self_edge(self) -> None:
|
||||
"""The self-loop edge A->A is not in the edges.
|
||||
|
||||
:return: None.
|
||||
"""
|
||||
a: Task = self.__tasks["A"]
|
||||
self.assertNotIn((a, a), self.__dfg.edges)
|
||||
|
||||
def test_other_edges(self) -> None:
|
||||
"""The other edges are A->B and B->C.
|
||||
|
||||
:return: None.
|
||||
"""
|
||||
a: Task = self.__tasks["A"]
|
||||
b: Task = self.__tasks["B"]
|
||||
c: Task = self.__tasks["C"]
|
||||
self.assertEqual(
|
||||
self.__dfg.edges,
|
||||
{(a, b), (b, c)},
|
||||
)
|
||||
|
||||
def test_no_self_loop_in_paper(self) -> None:
|
||||
"""Paper example has no self-loops.
|
||||
|
||||
@@ -514,5 +536,100 @@ class TestSelfLoop(unittest.TestCase):
|
||||
self.assertEqual(dfg.self_loops, set())
|
||||
|
||||
|
||||
class TestSelfLoopWithoutRepeatedEnd(unittest.TestCase):
|
||||
"""Tests a self-loop with a single end event.
|
||||
|
||||
Definition 6 only requires an end event of a followed by
|
||||
a start event of a, not a second end event of a.
|
||||
"""
|
||||
|
||||
def setUp(self) -> None:
|
||||
"""Set up trace: As Ae As Bs Be.
|
||||
|
||||
A ends, then A starts again with no end event in
|
||||
between, so A ->r A.
|
||||
|
||||
:return: None.
|
||||
"""
|
||||
self.__tasks: dict[str, Task] = (
|
||||
_make_tasks("A", "B")
|
||||
)
|
||||
a: Task = self.__tasks["A"]
|
||||
b: Task = self.__tasks["B"]
|
||||
traces: dict[
|
||||
tuple[tuple[Task, str], ...], int
|
||||
] = {
|
||||
((a, S), (a, E), (a, S),
|
||||
(b, S), (b, E)): 1,
|
||||
}
|
||||
self.__dfg: RefinedDirectlyFollowsGraph = (
|
||||
RefinedDirectlyFollowsGraph(traces)
|
||||
)
|
||||
|
||||
def test_a_is_self_loop(self) -> None:
|
||||
"""A is a self-loop even though it ends once.
|
||||
|
||||
:return: None.
|
||||
"""
|
||||
self.assertIn(
|
||||
self.__tasks["A"],
|
||||
self.__dfg.self_loops,
|
||||
)
|
||||
|
||||
|
||||
class TestTwoLoopIsNotSelfLoop(unittest.TestCase):
|
||||
"""Tests that a 2-loop is not mistaken for a self-loop.
|
||||
|
||||
An activity repeating after another activity completes
|
||||
forms a 2-loop, not a self-loop.
|
||||
"""
|
||||
|
||||
def setUp(self) -> None:
|
||||
"""Set up trace: As Ae Bs Be As Ae.
|
||||
|
||||
B ends between the end of A and the next start of
|
||||
A, so A ->r A does not hold.
|
||||
|
||||
:return: None.
|
||||
"""
|
||||
self.__tasks: dict[str, Task] = (
|
||||
_make_tasks("A", "B")
|
||||
)
|
||||
a: Task = self.__tasks["A"]
|
||||
b: Task = self.__tasks["B"]
|
||||
traces: dict[
|
||||
tuple[tuple[Task, str], ...], int
|
||||
] = {
|
||||
((a, S), (a, E),
|
||||
(b, S), (b, E),
|
||||
(a, S), (a, E)): 1,
|
||||
}
|
||||
self.__dfg: RefinedDirectlyFollowsGraph = (
|
||||
RefinedDirectlyFollowsGraph(traces)
|
||||
)
|
||||
|
||||
def test_a_not_self_loop(self) -> None:
|
||||
"""A is not a self-loop.
|
||||
|
||||
:return: None.
|
||||
"""
|
||||
self.assertNotIn(
|
||||
self.__tasks["A"],
|
||||
self.__dfg.self_loops,
|
||||
)
|
||||
|
||||
def test_two_loop_edges(self) -> None:
|
||||
"""The edges form the 2-loop A->B and B->A.
|
||||
|
||||
:return: None.
|
||||
"""
|
||||
a: Task = self.__tasks["A"]
|
||||
b: Task = self.__tasks["B"]
|
||||
self.assertEqual(
|
||||
self.__dfg.edges,
|
||||
{(a, b), (b, a)},
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
Reference in New Issue
Block a user