From 36ba445a22373e79fc86f3de7c8c5a87cf2ee66d Mon Sep 17 00:00:00 2001 From: Imogen Date: Tue, 26 Dec 2023 21:51:30 +0100 Subject: [PATCH 1/2] factor team test out of test cli command --- algobattle/cli.py | 129 +++++++++++++++++++++++++--------------------- 1 file changed, 69 insertions(+), 60 deletions(-) diff --git a/algobattle/cli.py b/algobattle/cli.py index c97e1532..d7408ba3 100644 --- a/algobattle/cli.py +++ b/algobattle/cli.py @@ -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 BuildError, EncodableModel, ExceptionInfo, Role, RunningTimer, BaseModel, TempDir, timestamp @@ -427,6 +427,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( @@ -438,66 +501,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] = {} - - 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 - ) + all_errors: dict[str, TestErrors] = {} - 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") From 9551db3040a678b858804a1fc510b72809389ec4 Mon Sep 17 00:00:00 2001 From: Imogen Date: Tue, 26 Dec 2023 21:51:48 +0100 Subject: [PATCH 2/2] package multiple team's programs at once --- algobattle/cli.py | 53 ++++++++++++++++++++++------------------------- 1 file changed, 25 insertions(+), 28 deletions(-) diff --git a/algobattle/cli.py b/algobattle/cli.py index d7408ba3..8fa5c2ee 100644 --- a/algobattle/cli.py +++ b/algobattle/cli.py @@ -600,7 +600,12 @@ 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[ @@ -608,41 +613,33 @@ def package_programs( ] = 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):