Compare commits

..

No commits in common. "45450b44516e3963081af749a732ba1573481873" and "f88c5b410640eb0eb070eec29ae5ef251a8ba0ba" have entirely different histories.

5 changed files with 70 additions and 111 deletions

View File

@ -92,7 +92,6 @@ def convert(
"""
Convert given dir of JOJ3 toml config files to JOJ3 json config files
"""
app.pretty_exceptions_enable = False
logger.info(f"Converting files in {root.absolute()}")
for repo_toml_path in root.glob("**/repo.toml"):
for task_toml_path in repo_toml_path.parent.glob("**/*.toml"):

View File

@ -43,17 +43,14 @@ class StreamOut(BaseModel):
InputFile = Union[LocalFile, MemoryFile, PreparedFile, Symlink]
Stdin = Union[InputFile, StreamIn]
Stdout = Union[Collector, StreamOut]
Stderr = Union[Collector, StreamOut]
class Cmd(BaseModel):
args: List[str] = []
env: List[str] = [DEFAULT_PATH_ENV]
stdin: Stdin = MemoryFile(content="")
stdout: Stdout = Collector(name="stdout")
stderr: Stderr = Collector(name="stderr")
stdin: Union[InputFile, StreamIn] = MemoryFile(content="")
stdout: Union[Collector, StreamOut] = Collector(name="stdout")
stderr: Union[Collector, StreamOut] = Collector(name="stderr")
cpu_limit: int = Field(DEFAULT_CPU_LIMIT, serialization_alias="cpuLimit")
clock_limit: int = Field(
DEFAULT_CLOCK_LIMIT_MULTIPLIER * DEFAULT_CPU_LIMIT,
@ -80,9 +77,9 @@ class Cmd(BaseModel):
class OptionalCmd(BaseModel):
args: Optional[List[str]] = None
env: Optional[List[str]] = None
stdin: Optional[Stdin] = None
stdout: Optional[Stdout] = None
stderr: Optional[Stderr] = None
stdin: Optional[Union[InputFile, StreamIn]] = None
stdout: Optional[Union[Collector, StreamOut]] = None
stderr: Optional[Union[Collector, StreamOut]] = None
cpu_limit: Optional[int] = Field(None, serialization_alias="cpuLimit")
clock_limit: Optional[int] = Field(None, serialization_alias="clockLimit")
memory_limit: Optional[int] = Field(None, serialization_alias="memoryLimit")

View File

@ -130,7 +130,8 @@ class Parser(str, Enum):
ELF = "elf"
class Case(BaseModel):
class Stage(BaseModel):
name: str = "" # Stage name
env: List[str] = []
command: str = "" # Command to run
files: StageFiles = StageFiles()
@ -139,16 +140,9 @@ class Case(BaseModel):
copy_in_cwd: bool = Field(
True, validation_alias=AliasChoices("copy-in-cwd", "copy_in_cwd")
)
limit: Limit = Limit()
score: int = 0
diff: ParserDiff = ParserDiff()
class Stage(Case):
name: str = "" # stage name
skip: List[str] = []
parsers: List[Parser] = [] # list of parsers
limit: Limit = Limit()
dummy: ParserDummy = ParserDummy()
result_status: ParserDummy = Field(
ParserDummy(), validation_alias=AliasChoices("result-status", "result_status")
@ -163,8 +157,11 @@ class Stage(Case):
validation_alias=AliasChoices("result-detail", "result_detail"),
)
file: ParserFile = ParserFile()
skip: List[str] = []
cases: Dict[str, Case] = {}
# cases related
cases: Dict[str, "Stage"] = {}
diff: ParserDiff = ParserDiff()
model_config = ConfigDict(extra="allow")

View File

@ -2,7 +2,7 @@ import re
import shlex
from functools import partial
from pathlib import Path, PurePosixPath
from typing import Any, Callable, Dict, List, Optional, Tuple
from typing import Any, Callable, Dict, List, Set, Tuple
from joj3_config_generator.models import result, task
from joj3_config_generator.models.common import Memory, Time
@ -11,6 +11,7 @@ from joj3_config_generator.models.const import (
DEFAULT_PATH_ENV,
JOJ3_CONFIG_ROOT,
)
from joj3_config_generator.models.task import Parser as ParserEnum
from joj3_config_generator.utils.logger import logger
@ -52,18 +53,18 @@ def get_parser_handler_map(
executor: result.Executor,
task_root: Path,
task_path: Path,
) -> Dict[task.Parser, Tuple[Callable[[Any, result.Parser], None], Any]]:
) -> Dict[ParserEnum, Tuple[Callable[[Any, result.Parser], None], Any]]:
return {
task.Parser.ELF: (fix_keyword, task_stage.elf),
task.Parser.CLANG_TIDY: (fix_keyword, task_stage.clangtidy),
task.Parser.KEYWORD: (fix_keyword, task_stage.keyword),
task.Parser.CPPCHECK: (fix_keyword, task_stage.cppcheck),
task.Parser.CPPLINT: (fix_keyword, task_stage.cpplint),
task.Parser.RESULT_DETAIL: (fix_result_detail, task_stage.result_detail),
task.Parser.DUMMY: (fix_dummy, task_stage.dummy),
task.Parser.RESULT_STATUS: (fix_dummy, task_stage.result_status),
task.Parser.FILE: (fix_file, task_stage.file),
task.Parser.DIFF: (
ParserEnum.ELF: (fix_keyword, task_stage.elf),
ParserEnum.CLANG_TIDY: (fix_keyword, task_stage.clangtidy),
ParserEnum.KEYWORD: (fix_keyword, task_stage.keyword),
ParserEnum.CPPCHECK: (fix_keyword, task_stage.cppcheck),
ParserEnum.CPPLINT: (fix_keyword, task_stage.cpplint),
ParserEnum.RESULT_DETAIL: (fix_result_detail, task_stage.result_detail),
ParserEnum.DUMMY: (fix_dummy, task_stage.dummy),
ParserEnum.RESULT_STATUS: (fix_dummy, task_stage.result_status),
ParserEnum.FILE: (fix_file, task_stage.file),
ParserEnum.DIFF: (
partial(
fix_diff,
task_stage=task_stage,
@ -177,31 +178,36 @@ def fix_diff(
task_path: Path,
) -> None:
base_dir = JOJ3_CONFIG_ROOT / task_path.parent
# cases not specified in the toml config (auto-detected)
unspecified_cases = get_unspecified_cases(task_root, task_path, task_stage.cases)
# cases specified in toml config but not skipped
specified_cases = [
(case, task_stage.cases[case])
for case in task_stage.cases
if case not in task_stage.skip
# all intended testcases that is detected
testcases = get_testcases(task_root, task_path)
# all testcases that is not specified in the toml config
default_cases = sorted(
testcases.difference(
[
casei
for casei in testcases
if any(casei.endswith(casej) for casej in task_stage.cases)
]
)
)
# those in toml config that is not skipped
valid_cases = [
(casej, task_stage.cases[casei])
for casei in task_stage.cases
for casej in testcases
if (casei not in task_stage.skip and casej.endswith(casei))
]
stage_cases = []
parser_cases = []
for case_name, case in specified_cases:
stdin, stdout = get_stdin_stdout(task_root, task_path, case_name, case)
if stdout is None:
logger.warning(
f"In file {task_root / task_path}, "
f"testcase {case_name} has no corresponding .out file, "
"skipped"
)
continue
for case, case_stage in valid_cases:
cmd = result.OptionalCmd(
stdin=stdin,
args=shlex.split(case.command) if case.command else None,
cpu_limit=case.limit.cpu,
clock_limit=DEFAULT_CLOCK_LIMIT_MULTIPLIER * case.limit.cpu,
memory_limit=case.limit.mem,
stdin=result.LocalFile(
src=str(base_dir / (case_stage.in_ or f"{case}.in"))
),
args=shlex.split(case_stage.command) if case_stage.command else None,
cpu_limit=case_stage.limit.cpu,
clock_limit=DEFAULT_CLOCK_LIMIT_MULTIPLIER * case_stage.limit.cpu,
memory_limit=case_stage.limit.mem,
proc_limit=task_stage.limit.proc,
)
if cmd.args == executor.with_.default.args:
@ -218,22 +224,22 @@ def fix_diff(
parser_case = result.DiffCasesConfig(
outputs=[
result.DiffOutputConfig(
score=case.diff.output.score,
score=case_stage.diff.output.score,
file_name="stdout",
answer_path=stdout,
force_quit_on_diff=case.diff.output.force_quit,
always_hide=case.diff.output.hide,
compare_space=not case.diff.output.ignore_spaces,
max_diff_length=case.diff.output.max_length,
max_diff_lines=case.diff.output.max_lines,
hide_common_prefix=case.diff.output.hide_common_prefix,
answer_path=str(base_dir / (case_stage.out_ or f"{case}.out")),
force_quit_on_diff=case_stage.diff.output.force_quit,
always_hide=case_stage.diff.output.hide,
compare_space=not case_stage.diff.output.ignore_spaces,
max_diff_length=case_stage.diff.output.max_length,
max_diff_lines=case_stage.diff.output.max_lines,
hide_common_prefix=case_stage.diff.output.hide_common_prefix,
)
]
)
parser_cases.append(parser_case)
for case_name in unspecified_cases:
for case in default_cases:
cmd = result.OptionalCmd(
stdin=result.LocalFile(src=str(base_dir / f"{case_name}.in")),
stdin=result.LocalFile(src=str(base_dir / f"{case}.in")),
cpu_limit=None,
clock_limit=None,
memory_limit=None,
@ -245,7 +251,7 @@ def fix_diff(
result.DiffOutputConfig(
score=task_stage.diff.default_score,
file_name="stdout",
answer_path=str(base_dir / f"{case_name}.out"),
answer_path=str(base_dir / f"{case}.out"),
)
]
)
@ -254,9 +260,9 @@ def fix_diff(
diff_parser.with_ = result.DiffConfig(name="diff", cases=parser_cases)
def get_unspecified_cases(
task_root: Path, task_path: Path, cases: Dict[str, task.Case]
) -> List[str]:
def get_testcases(
task_root: Path, task_path: Path
) -> Set[str]: # basedir here should be task_conf.root / task_conf.path
testcases = set()
for testcases_path in (task_root / task_path).parent.glob("**/*.in"):
if not testcases_path.with_suffix(".out").exists():
@ -273,42 +279,4 @@ def get_unspecified_cases(
)
).removesuffix(".in")
)
return sorted(
testcases.difference(
[
casei
for casei in testcases
if any(casei.endswith(casej) for casej in cases)
]
)
)
def get_stdin_stdout(
task_root: Path, task_path: Path, case_name: str, case: task.Case
) -> Tuple[result.Stdin, Optional[str]]:
case_stdout_name = case.out_ if case.out_ else f"{case_name}.out"
stdin: result.Stdin = result.MemoryFile(content="")
stdout = None
for case_stdout_path in (task_root / task_path).parent.glob("**/*.out"):
if case_stdout_path.name != case_stdout_name:
continue
stdout = str(JOJ3_CONFIG_ROOT / case_stdout_path.relative_to(task_root))
case_stdin_path = case_stdout_path.with_suffix(".in")
if case.in_:
case_stdin_path = Path((task_root / task_path).parent / case.in_)
if not case_stdin_path.exists():
logger.warning(
f"In file {task_root / task_path}, "
f"testcase {case_stdout_path} has no .in file, "
"use empty content as stdin"
)
else:
stdin = result.LocalFile(
src=str(
JOJ3_CONFIG_ROOT
/ PurePosixPath(case_stdin_path.relative_to(task_root))
)
)
break
return stdin, stdout
return testcases

View File

@ -94,7 +94,9 @@
},
{
"name": "debug",
"with": {}
"with": {
"score": 0
}
}
]
},
@ -847,10 +849,6 @@
"with": {
"msg": "joj3 summary"
}
},
{
"name": "debug",
"with": {}
}
]
}