Skip to content

Commit

Permalink
test: build_task and judge_task
Browse files Browse the repository at this point in the history
  • Loading branch information
futrime committed Feb 7, 2025
1 parent 1d98fb8 commit 079654a
Show file tree
Hide file tree
Showing 5 changed files with 215 additions and 8 deletions.
1 change: 0 additions & 1 deletion base_saiblo_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,3 @@ async def start(self) -> None:
This method should be called to start the Saiblo client and begin executing tasks. This
method will block forever.
"""
raise NotImplementedError
2 changes: 0 additions & 2 deletions base_task.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,8 @@ async def execute(self) -> Any:
Returns:
The task execution result
"""
raise NotImplementedError

@property
@abstractmethod
def result(self) -> Optional[Any]:
"""The task execution result."""
raise NotImplementedError
5 changes: 0 additions & 5 deletions base_task_scheduler.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,10 @@ class BaseTaskScheduler(ABC):
@abstractmethod
def idle(self) -> bool:
"""Whether the scheduler is idle."""
raise NotImplementedError

@abstractmethod
async def clean(self) -> None:
"""Cleans up scheduled tasks."""
raise NotImplementedError

@abstractmethod
async def pop_done_task(self) -> BaseTask:
Expand All @@ -26,7 +24,6 @@ async def pop_done_task(self) -> BaseTask:
Returns:
The task that has been finished
"""
raise NotImplementedError

@abstractmethod
async def schedule(self, task: BaseTask) -> None:
Expand All @@ -38,7 +35,6 @@ async def schedule(self, task: BaseTask) -> None:
Args:
task: The task to schedule
"""
raise NotImplementedError

@abstractmethod
async def start(self) -> None:
Expand All @@ -47,4 +43,3 @@ async def start(self) -> None:
This method should be called to start the task scheduler and begin executing tasks. This
method will block forever.
"""
raise NotImplementedError
94 changes: 94 additions & 0 deletions tests/test_build_task.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
"""Tests for the build_task module."""

import pathlib
import shutil
import unittest

import aiohttp
import docker
import docker.models.containers

from agent_code_fetcher import AgentCodeFetcher
from build_result_reporter import BuildResultReporter
from build_task import BuildTaskFactory
from docker_image_builder import DockerImageBuilder

CODE_ID = "7c562b10-287f-44c0-8fc4-0cf853a1859b"
HTTP_BASE_URL = "https://api.dev.saiblo.net"


class TestBuildTask(unittest.IsolatedAsyncioTestCase):
"""Tests for the BuildTask class."""

_build_task_factory: BuildTaskFactory
_docker_client: docker.DockerClient
_session: aiohttp.ClientSession

async def asyncSetUp(self) -> None:
self._docker_client = docker.from_env()
self._session = aiohttp.ClientSession(HTTP_BASE_URL)

self._build_task_factory = BuildTaskFactory(
AgentCodeFetcher(self._session),
DockerImageBuilder(),
BuildResultReporter(self._session),
)

self._clean()

async def asyncTearDown(self) -> None:
self._clean()

await self._session.close()

async def test_execute(self):
"""Test execute()."""
# Arrange.
agent_code_tarball_path = pathlib.Path(f"data/agent_code/{CODE_ID}.tar")
agent_code_tarball_path.parent.mkdir(parents=True, exist_ok=True)
agent_code_tarball_path.touch()

image = self._docker_client.images.pull("hello-world")
image.tag("saiblo-worker-image", CODE_ID)

build_task = self._build_task_factory.create(CODE_ID)

# Assert.
self.assertIsNone(build_task.result)

# Act.
returned_result = await build_task.execute()
property_result = build_task.result

# Assert.
self.assertEqual(property_result, returned_result)

self.assertEqual(returned_result.code_id, CODE_ID)
self.assertTrue(returned_result.image, f"saiblo-worker-image:{CODE_ID}")
self.assertEqual(returned_result.message, "")

def _clean(self) -> None:
"""Clean up the environment."""

shutil.rmtree(
pathlib.Path("data"),
ignore_errors=True,
)

for network in self._docker_client.networks.list():
if network.name is not None and network.name.startswith(
"saiblo-worker-network-"
):
network.remove()

for container in self._docker_client.containers.list(all=True):
assert isinstance(container, docker.models.containers.Container)
if container.name is not None and (
container.name.startswith("saiblo-worker-agent-")
or container.name.startswith("saiblo-worker-game-host-")
):
container.stop(timeout=0)
container.remove(v=True, force=True)

for image in self._docker_client.images.list("saiblo-worker-image"):
image.remove(force=True)
121 changes: 121 additions & 0 deletions tests/test_judge_task.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
"""Tests for the judge_task module."""

import dataclasses
import json
import pathlib
import shutil
import unittest

import aiohttp
import docker
import docker.models.containers

from agent_code_fetcher import AgentCodeFetcher
from build_result_reporter import BuildResultReporter
from docker_image_builder import DockerImageBuilder
from judge_task import JudgeTaskFactory
from match_judger import MatchJudger
from match_result import MatchResult
from match_result_reporter import MatchResultReporter

CODE_ID = "7c562b10-287f-44c0-8fc4-0cf853a1859b"
HTTP_BASE_URL = "https://api.dev.saiblo.net"
MATCH_ID = "7729"


class TestJudgeTask(unittest.IsolatedAsyncioTestCase):
"""Tests for the JudgeTask class."""

_judge_task_factory: JudgeTaskFactory
_docker_client: docker.DockerClient
_session: aiohttp.ClientSession

async def asyncSetUp(self) -> None:
self._docker_client = docker.from_env()
self._session = aiohttp.ClientSession(HTTP_BASE_URL)

self._judge_task_factory = JudgeTaskFactory(
"saiblo-worker-image:game-host",
AgentCodeFetcher(self._session),
DockerImageBuilder(),
BuildResultReporter(self._session),
MatchJudger(),
MatchResultReporter(self._session),
)

self._clean()

async def asyncTearDown(self) -> None:
self._clean()

await self._session.close()

async def test_execute(self):
"""Test execute()."""
# Arrange.
agent_code_tarball_path = pathlib.Path(f"data/agent_code/{CODE_ID}.tar")
agent_code_tarball_path.parent.mkdir(parents=True, exist_ok=True)
agent_code_tarball_path.touch()

image = self._docker_client.images.pull("hello-world")
image.tag("saiblo-worker-image", CODE_ID)

match_replay_path = pathlib.Path(f"data/match_replays/{MATCH_ID}.dat")
match_replay_path.parent.mkdir(parents=True, exist_ok=True)
match_replay_path.touch()

match_result = MatchResult(
match_id=MATCH_ID,
agent_results=[],
error_message="error_message",
replay_file_path=None,
stderr_output="stderr_output",
)
match_result_path = pathlib.Path(f"data/match_results/{MATCH_ID}.json")
match_result_path.parent.mkdir(parents=True, exist_ok=True)
with open(match_result_path, "w", encoding="utf-8") as file:
json.dump(
dataclasses.asdict(match_result),
file,
)

judge_task = self._judge_task_factory.create(MATCH_ID, [CODE_ID, CODE_ID])

# Assert.
self.assertIsNone(judge_task.result)

# Act.
returned_result = await judge_task.execute()
property_match_id = judge_task.match_id
property_result = judge_task.result

# Assert.
self.assertEqual(property_match_id, MATCH_ID)
self.assertEqual(property_result, returned_result)
self.assertEqual(returned_result, match_result)

def _clean(self) -> None:
"""Clean up the environment."""

shutil.rmtree(
pathlib.Path("data"),
ignore_errors=True,
)

for network in self._docker_client.networks.list():
if network.name is not None and network.name.startswith(
"saiblo-worker-network-"
):
network.remove()

for container in self._docker_client.containers.list(all=True):
assert isinstance(container, docker.models.containers.Container)
if container.name is not None and (
container.name.startswith("saiblo-worker-agent-")
or container.name.startswith("saiblo-worker-game-host-")
):
container.stop(timeout=0)
container.remove(v=True, force=True)

for image in self._docker_client.images.list("saiblo-worker-image"):
image.remove(force=True)

0 comments on commit 079654a

Please sign in to comment.