Skip to content

Commit f1f26b1

Browse files
committed
Add TaskIntegrator tracking class
1 parent 35d6d15 commit f1f26b1

File tree

2 files changed

+471
-2
lines changed

2 files changed

+471
-2
lines changed
Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
import asyncio
2+
from enum import Enum, auto
3+
4+
from eth_utils import (
5+
ValidationError,
6+
)
7+
from eth_utils.toolz import identity
8+
import pytest
9+
10+
from trinity.utils.datastructures import (
11+
BaseTaskCompletion,
12+
TaskIntegrator,
13+
)
14+
15+
DEFAULT_TIMEOUT = 0.05
16+
17+
18+
async def wait(coro, timeout=DEFAULT_TIMEOUT):
19+
return await asyncio.wait_for(coro, timeout=timeout)
20+
21+
22+
class OneTask(Enum):
23+
one = auto()
24+
25+
26+
class TwoTasks(Enum):
27+
Task1 = auto()
28+
Task2 = auto()
29+
30+
31+
IdentityOneCompletion = BaseTaskCompletion.factory(OneTask, identity, lambda x: x - 1)
32+
IdentityTwoCompletion = BaseTaskCompletion.factory(TwoTasks, identity, lambda x: x - 1)
33+
34+
35+
@pytest.mark.asyncio
36+
async def test_simplest_path():
37+
ti = TaskIntegrator(IdentityTwoCompletion)
38+
ti.set_last_completion(3)
39+
ti.prepare((4, ))
40+
ti.finish(TwoTasks.Task1, (4, ))
41+
ti.finish(TwoTasks.Task2, (4, ))
42+
completed = await wait(ti.next_completed())
43+
assert completed == (4, )
44+
45+
46+
@pytest.mark.asyncio
47+
async def test_cannot_finish_before_prepare():
48+
ti = TaskIntegrator(IdentityTwoCompletion)
49+
ti.set_last_completion(3)
50+
with pytest.raises(ValidationError):
51+
ti.finish(TwoTasks.Task1, (4, ))
52+
53+
54+
@pytest.mark.asyncio
55+
async def test_two_steps_simultaneous_complete():
56+
ti = TaskIntegrator(IdentityOneCompletion)
57+
ti.set_last_completion(3)
58+
ti.prepare((4, 5))
59+
ti.finish(OneTask.one, (4, ))
60+
ti.finish(OneTask.one, (5, ))
61+
62+
completed = await wait(ti.next_completed())
63+
assert completed == (4, 5)
64+
65+
66+
@pytest.mark.asyncio
67+
async def test_pruning():
68+
# make a number depend on the mod10, so 4 and 14 both depend on task 3
69+
Mod10Dependency = BaseTaskCompletion.factory(OneTask, identity, lambda x: (x % 10) - 1)
70+
ti = TaskIntegrator(Mod10Dependency, max_depth=2)
71+
ti.set_last_completion(3)
72+
ti.prepare((4, 5, 6))
73+
ti.finish(OneTask.one, (4, 5, 6))
74+
75+
# it's fine to prepare a task that depends up to two back in history
76+
# this depends on 5
77+
ti.prepare((16, ))
78+
# this depends on 4
79+
ti.prepare((15, ))
80+
81+
# but depending 3 back in history should raise a validation error, because it's pruned
82+
with pytest.raises(ValidationError):
83+
# this depends on 3
84+
ti.prepare((14, ))
85+
86+
# test the same concept, but after pruning more than just the starting task...
87+
ti.prepare((7, ))
88+
ti.finish(OneTask.one, (7, ))
89+
90+
ti.prepare((16, ))
91+
ti.prepare((17, ))
92+
with pytest.raises(ValidationError):
93+
ti.prepare((15, ))
94+
95+
96+
@pytest.mark.asyncio
97+
async def test_wait_forever():
98+
ti = TaskIntegrator(IdentityOneCompletion)
99+
try:
100+
finished = await wait(ti.next_completed())
101+
except asyncio.TimeoutError:
102+
pass
103+
else:
104+
assert False, f"No steps should complete, but got {finished!r}"
105+
106+
107+
def test_finish_same_task_twice():
108+
ti = TaskIntegrator(IdentityTwoCompletion)
109+
ti.set_last_completion(1)
110+
ti.prepare((2, ))
111+
ti.finish(TwoTasks.Task1, (2,))
112+
with pytest.raises(ValidationError):
113+
ti.finish(TwoTasks.Task1, (2,))
114+
115+
116+
@pytest.mark.asyncio
117+
async def test_finish_different_entry_at_same_step():
118+
119+
def previous_even_number(num):
120+
return ((num - 1) // 2) * 2
121+
122+
DependsOnEvens = BaseTaskCompletion.factory(OneTask, identity, previous_even_number)
123+
ti = TaskIntegrator(DependsOnEvens)
124+
125+
ti.set_last_completion(2)
126+
127+
ti.prepare((3, 4))
128+
129+
# depends on 2
130+
ti.finish(OneTask.one, (3,))
131+
132+
# also depends on 2
133+
ti.finish(OneTask.one, (4,))
134+
135+
completed = await wait(ti.next_completed())
136+
assert completed == (3, 4)
137+
138+
139+
@pytest.mark.asyncio
140+
async def test_return_original_entry():
141+
# for no particular reason, the id is 3 before the number
142+
DependsOnEvens = BaseTaskCompletion.factory(OneTask, lambda x: x - 3, lambda x: x - 4)
143+
ti = TaskIntegrator(DependsOnEvens)
144+
145+
# translates to id -1
146+
ti.set_last_completion(2)
147+
148+
ti.prepare((3, 4))
149+
150+
# translates to id 0
151+
ti.finish(OneTask.one, (3,))
152+
153+
# translates to id 1
154+
ti.finish(OneTask.one, (4,))
155+
156+
entries = await wait(ti.next_completed())
157+
158+
# make sure that the original task is returned, not the id
159+
assert entries == (3, 4)
160+
161+
162+
def test_finish_with_unrecognized_task():
163+
ti = TaskIntegrator(IdentityTwoCompletion)
164+
ti.set_last_completion(1)
165+
with pytest.raises(ValidationError):
166+
ti.finish('UNRECOGNIZED_TASK', (2,))
167+
168+
169+
def test_finish_before_setting_start_val():
170+
ti = TaskIntegrator(IdentityTwoCompletion)
171+
with pytest.raises(ValidationError):
172+
ti.finish(TwoTasks.Task1, (2,))
173+
174+
175+
def test_finish_too_early():
176+
ti = TaskIntegrator(IdentityTwoCompletion)
177+
ti.set_last_completion(3)
178+
with pytest.raises(ValidationError):
179+
ti.finish(TwoTasks.Task1, (3,))
180+
181+
182+
def test_empty_completion():
183+
ti = TaskIntegrator(IdentityTwoCompletion)
184+
with pytest.raises(ValidationError):
185+
ti.finish(TwoTasks.Task1, tuple())
186+
187+
188+
def test_empty_enum():
189+
190+
class NoTasks(Enum):
191+
pass
192+
193+
with pytest.raises(ValidationError):
194+
BaseTaskCompletion.factory(NoTasks, identity, lambda x: x - 1)

0 commit comments

Comments
 (0)