Skip to content

Commit

Permalink
Merge pull request #158 from ImogenBits/package_all
Browse files Browse the repository at this point in the history
Package multiple programs at once
  • Loading branch information
Benezivas authored Dec 27, 2023
2 parents c492eb0 + 650c433 commit 1c0dc31
Showing 1 changed file with 94 additions and 88 deletions.
182 changes: 94 additions & 88 deletions algobattle/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@
from tomlkit.items import Table as TomlTable

from algobattle.battle import Battle
from algobattle.match import AlgobattleConfig, EmptyUi, Match, MatchConfig, MatchupStr, Ui, ProjectConfig
from algobattle.match import AlgobattleConfig, EmptyUi, Match, MatchConfig, MatchupStr, TeamInfo, Ui, ProjectConfig
from algobattle.problem import Instance, Problem, Solution
from algobattle.program import Generator, Matchup, Solver
from algobattle.util import (
Expand Down Expand Up @@ -440,6 +440,69 @@ class TestErrors(BaseModel):
generator_run: ExceptionInfo | None = None
solver_run: ExceptionInfo | None = None

def ok(self) -> bool:
"""Return whether the test passed with no problems."""
return not (self.generator_build or self.solver_build or self.generator_run or self.solver_run)


def test_team(config: AlgobattleConfig, team: str, size: int | None = None) -> TestErrors:
problem = config.loaded_problem
console.print(f"Testing programs of team {team}")
errors = TestErrors()
instance = None

async def gen_builder() -> Generator:
with console.status("Building generator"):
return await Generator.build(
config.teams[team].generator, problem=problem, config=config.as_prog_config(), team_name=team
)

try:
with run_async_fn(gen_builder) as gen:
console.print("[success]Generator built successfully")
with console.status("Running generator"):
instance = gen.test(size)
if isinstance(instance, ExceptionInfo):
console.print("[error]Generator didn't run successfully")
errors.generator_run = instance
instance = None
else:
console.print("[success]Generator ran successfully")
except BuildError as e:
console.print("[error]Generator didn't build successfully")
errors.generator_build = ExceptionInfo.from_exception(e)
instance = None

sol_error = None

async def sol_builder() -> Solver:
with console.status("Building solver"):
return await Solver.build(
config.teams[team].solver, problem=problem, config=config.as_prog_config(), team_name=team
)

try:
with run_async_fn(sol_builder) as sol:
console.print("[success]Solver built successfully")

instance = instance or cast(Instance, problem.test_instance)
if instance:
with console.status("Running solver"):
sol_error = sol.test(instance)
if isinstance(sol_error, ExceptionInfo):
console.print("[error]Solver didn't run successfully")
errors.solver_run = sol_error
else:
console.print("[success]Solver ran successfully")
else:
console.print("[warning]Cannot test running the solver")
except BuildError as e:
console.print("[error]Solver didn't build successfully")
errors.solver_build = ExceptionInfo.from_exception(e)
instance = None

return errors


@app.command()
def test(
Expand All @@ -451,66 +514,12 @@ def test(
console.print("[error]The folder does not contain an Algobattle project")
raise Abort
config = AlgobattleConfig.from_file(project)
problem = config.loaded_problem
all_errors: dict[str, Any] = {}
all_errors: dict[str, TestErrors] = {}

for team, team_info in config.teams.items():
console.print(f"Testing programs of team {team}")
errors = TestErrors()
instance = None

async def gen_builder() -> Generator:
with console.status("Building generator"):
return await Generator.build(
team_info.generator, problem=problem, config=config.as_prog_config(), team_name=team
)

try:
with run_async_fn(gen_builder) as gen:
console.print("[success]Generator built successfully")
with console.status("Running generator"):
instance = gen.test(size)
if isinstance(instance, ExceptionInfo):
console.print("[error]Generator didn't run successfully")
errors.generator_run = instance
instance = None
else:
console.print("[success]Generator ran successfully")
except BuildError as e:
console.print("[error]Generator didn't build successfully")
errors.generator_build = ExceptionInfo.from_exception(e)
instance = None

sol_error = None

async def sol_builder() -> Solver:
with console.status("Building solver"):
return await Solver.build(
team_info.solver, problem=problem, config=config.as_prog_config(), team_name=team
)

try:
with run_async_fn(sol_builder) as sol:
console.print("[success]Solver built successfully")

instance = instance or cast(Instance, problem.test_instance)
if instance:
with console.status("Running solver"):
sol_error = sol.test(instance)
if isinstance(sol_error, ExceptionInfo):
console.print("[error]Solver didn't run successfully")
errors.solver_run = sol_error
else:
console.print("[success]Solver ran successfully")
else:
console.print("[warning]Cannot test running the solver")
except BuildError as e:
console.print("[error]Solver didn't build successfully")
errors.solver_build = ExceptionInfo.from_exception(e)
instance = None

if errors != TestErrors():
all_errors[team] = errors.model_dump(exclude_defaults=True)
for team in config.teams.keys():
res = test_team(config, team, size)
if not res.ok():
all_errors[team] = res

if all_errors:
err_path = config.project.results.joinpath(f"test-{timestamp()}.json")
Expand Down Expand Up @@ -604,49 +613,46 @@ def package_problem(
@packager.command("programs")
def package_programs(
project: Annotated[Path, Argument(help="The project folder to use.")] = Path(),
team: Annotated[Optional[str], Option(help="Name of team whose programs should be packaged.")] = None,
team: Annotated[
Optional[str],
Option(
help="Name of team whose programs should be packaged. If None are specified, every team's are packaged."
),
] = None,
generator: Annotated[bool, Option(help="Wether to package the generator")] = True,
solver: Annotated[bool, Option(help="Wether to package the solver")] = True,
test_programs: Annotated[
bool, Option("--test/--no-test", help="Whether to test the programs before packaging them")
] = True,
) -> None:
config = AlgobattleConfig.from_file(project)
if team is None:
match list(config.teams.keys()):
case []:
console.print("[error]The config file doesn't contain a team[/]")
raise Abort
case [name]:
team = name
case _:
console.print(
"[error]The Config file contains multiple teams[/], specify whose programs you want to package"
)
raise Abort
if team not in config.teams:
if not config.teams:
console.print("[error]The project config file doesn't contain any teams[/]")
raise Abort
if team is not None and team not in config.teams:
console.print("[erorr]The selected team isn't in the config file[/]")
raise Abort
if test_programs:
test_result = test(project)
if test_result == "error":
console.print("Stopping program packaging since they do not pass tests")
raise Abort
out = project.parent if project.is_file() else project

def _package_program(role: Role) -> None:
with console.status(f"Packaging {team}'s {role}"), ZipFile(out / f"{team} {role}.prog", "w") as zipfile:
program_root: Path = getattr(config.teams[team], role)
def _package_program(name: str, info: TeamInfo, role: Role) -> None:
with console.status(f"Packaging {name}'s {role}"), ZipFile(out / f"{name} {role}.prog", "w") as zipfile:
program_root: Path = getattr(info, role)
for file in program_root.rglob("*"):
if file.is_dir():
continue
zipfile.write(file, file.relative_to(program_root))
console.print(f"[success]Packaged {team}'s {role}")

if generator:
_package_program(Role.generator)
if solver:
_package_program(Role.solver)
console.print(f"[success]Packaged {name}'s {role}")

for name, info in [(team, config.teams[team])] if team else config.teams.items():
if test_programs:
test_result = test_team(config, name)
if not test_result.ok():
console.print(f"[error]Team {name} does not pass tests")
continue
if generator:
_package_program(name, info, Role.generator)
if solver:
_package_program(name, info, Role.solver)


class TimerTotalColumn(ProgressColumn):
Expand Down

0 comments on commit 1c0dc31

Please sign in to comment.