Skip to content
Closed
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
43 changes: 25 additions & 18 deletions tests/unit/test_logging.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,12 +84,23 @@


class StatusResponseGenerator:
"""Generator for actor run status responses to simulate changing status over time."""
"""Generator for actor run status responses to simulate changing status over time.

A status watcher polls the plain endpoint once a second while `wait_for_finish` polls it several times a second
with `waitForFinish`. Driving one sequence from both would make the messages a watcher observes depend on which
poll got there first, so the two are answered separately: plain requests walk the sequence, and `waitForFinish`
reports the run finished only once they have walked all of it.
"""

_requests_for_first_status = 3
"""Plain requests answered with the first status before the sequence moves on.

`call` spends two of them on setup - one in `get_status_message_watcher`, one in `get_streamed_log` - so the
watcher's own first poll lands inside this window whichever order the three arrive in.
"""

def __init__(self) -> None:
self.current_status_index = 0
self.requests_for_current_status = 0
self.min_requests_per_status = 5
self.plain_requests = 0

self.statuses: list[tuple[str, ActorJobStatus, bool]] = [
('Initial message', 'RUNNING', False),
Expand Down Expand Up @@ -129,21 +140,17 @@ def _create_minimal_run_data(self, message: str, status: ActorJobStatus, *, is_t
'containerUrl': 'https://test.runs.apify.net',
}

def get_response(self, _request: Request) -> Response:
if self.current_status_index < len(self.statuses):
message, status, is_terminal = self.statuses[self.current_status_index]
def get_response(self, request: Request) -> Response:
if 'waitForFinish' in request.args:
# Hold the run open until the plain sequence has run out, so a watcher polling alongside always sees
# every message. With nothing watching there are no plain requests, and the run reads as finished.
requests_to_walk_all = self._requests_for_first_status + len(self.statuses) - 1
still_walking = 0 < self.plain_requests < requests_to_walk_all
message, status, is_terminal = self.statuses[0] if still_walking else self.statuses[-1]
else:
message, status, is_terminal = self.statuses[-1]

self.requests_for_current_status += 1

if (
self.requests_for_current_status >= self.min_requests_per_status
and self.current_status_index < len(self.statuses) - 1
and not is_terminal
):
self.current_status_index += 1
self.requests_for_current_status = 0
self.plain_requests += 1
index = max(0, self.plain_requests - self._requests_for_first_status)
message, status, is_terminal = self.statuses[min(index, len(self.statuses) - 1)]

status_data = {'data': self._create_minimal_run_data(message, status, is_terminal=is_terminal)}

Expand Down
Loading