This commit is contained in:
parent
7474b5b825
commit
51b68b7c87
|
@ -15,6 +15,7 @@ repos:
|
|||
rev: v0.13.0
|
||||
hooks:
|
||||
- id: yamlfmt
|
||||
exclude: '^tests/'
|
||||
- repo: https://github.com/pdm-project/pdm
|
||||
rev: 2.19.2
|
||||
hooks:
|
||||
|
|
|
@ -1,22 +1,10 @@
|
|||
from joj3_config_generator.models import (
|
||||
Cmd,
|
||||
CmdFile,
|
||||
ExecutorConfig,
|
||||
ExecutorWithConfig,
|
||||
ParserConfig,
|
||||
RepoConfig,
|
||||
ResultConfig,
|
||||
Stage,
|
||||
StageConfig,
|
||||
TaskConfig,
|
||||
TeapotConfig,
|
||||
)
|
||||
from joj3_config_generator.models import joj1, repo, result, task
|
||||
|
||||
|
||||
# FIXME: LLM generated convert function, only for demostration
|
||||
def convert(repo_conf: RepoConfig, task_conf: TaskConfig) -> ResultConfig:
|
||||
def convert(repo_conf: repo.Config, task_conf: task.Config) -> result.Config:
|
||||
# Create the base ResultConf object
|
||||
result_conf = ResultConfig(
|
||||
result_conf = result.Config(
|
||||
name=task_conf.task,
|
||||
log_path=f"{task_conf.task.replace(' ', '_')}.log",
|
||||
expire_unix_timestamp=(
|
||||
|
@ -24,29 +12,31 @@ def convert(repo_conf: RepoConfig, task_conf: TaskConfig) -> ResultConfig:
|
|||
if task_conf.release.deadline
|
||||
else -1
|
||||
),
|
||||
stage=StageConfig(stages=[], sandbox_token=repo_conf.sandbox_token),
|
||||
teapot=TeapotConfig(),
|
||||
stage=result.Stage(stages=[], sandbox_token=repo_conf.sandbox_token),
|
||||
teapot=result.Teapot(),
|
||||
)
|
||||
|
||||
# Convert each stage in the task configuration
|
||||
for task_stage in task_conf.stages:
|
||||
executor_with_config = ExecutorWithConfig(
|
||||
default=Cmd(
|
||||
executor_with_config = result.ExecutorWith(
|
||||
default=result.Cmd(
|
||||
args=task_stage.command.split(),
|
||||
copy_in={file: CmdFile(src=file) for file in task_stage.files.import_},
|
||||
copy_in={
|
||||
file: result.CmdFile(src=file) for file in task_stage.files.import_
|
||||
},
|
||||
copy_out_cached=task_stage.files.export,
|
||||
),
|
||||
cases=[], # You can add cases if needed
|
||||
)
|
||||
conf_stage = Stage(
|
||||
conf_stage = result.StageDetail(
|
||||
name=task_stage.name,
|
||||
group=task_conf.task,
|
||||
executor=ExecutorConfig(
|
||||
executor=result.Executor(
|
||||
name="sandbox",
|
||||
with_=executor_with_config,
|
||||
),
|
||||
parsers=[
|
||||
ParserConfig(name=parser, with_={}) for parser in task_stage.parsers
|
||||
result.Parser(name=parser, with_={}) for parser in task_stage.parsers
|
||||
],
|
||||
)
|
||||
|
||||
|
@ -59,3 +49,8 @@ def convert(repo_conf: RepoConfig, task_conf: TaskConfig) -> ResultConfig:
|
|||
result_conf.stage.stages.append(conf_stage)
|
||||
|
||||
return result_conf
|
||||
|
||||
|
||||
# TODO: implement me
|
||||
def convert_joj1(joj1_conf: joj1.Config) -> task.Config:
|
||||
return task.Config(task="", release=task.Release(deadline=None), stages=[])
|
||||
|
|
|
@ -1,15 +0,0 @@
|
|||
from joj3_config_generator.models.joj1 import Case as Case
|
||||
from joj3_config_generator.models.joj1 import JOJ1Config as JOJ1Config
|
||||
from joj3_config_generator.models.joj1 import Language as Language
|
||||
from joj3_config_generator.models.repo import RepoConfig as RepoConfig
|
||||
from joj3_config_generator.models.repo import RepoFiles as RepoFiles
|
||||
from joj3_config_generator.models.result import Cmd as Cmd
|
||||
from joj3_config_generator.models.result import CmdFile as CmdFile
|
||||
from joj3_config_generator.models.result import ExecutorConfig as ExecutorConfig
|
||||
from joj3_config_generator.models.result import ExecutorWithConfig as ExecutorWithConfig
|
||||
from joj3_config_generator.models.result import ParserConfig as ParserConfig
|
||||
from joj3_config_generator.models.result import ResultConfig as ResultConfig
|
||||
from joj3_config_generator.models.result import Stage as Stage
|
||||
from joj3_config_generator.models.result import StageConfig as StageConfig
|
||||
from joj3_config_generator.models.result import TeapotConfig as TeapotConfig
|
||||
from joj3_config_generator.models.task import TaskConfig as TaskConfig
|
|
@ -23,6 +23,6 @@ class Case(BaseModel):
|
|||
category: Optional[str] = None
|
||||
|
||||
|
||||
class JOJ1Config(BaseModel):
|
||||
class Config(BaseModel):
|
||||
languages: List[Language]
|
||||
cases: List[Case]
|
||||
|
|
|
@ -3,16 +3,16 @@ from typing import List, Optional
|
|||
from pydantic import BaseModel, Field
|
||||
|
||||
|
||||
class RepoFiles(BaseModel):
|
||||
class Files(BaseModel):
|
||||
whitelist_patterns: List[str]
|
||||
whitelist_file: Optional[str]
|
||||
required: List[str]
|
||||
immutable: List[str]
|
||||
|
||||
|
||||
class RepoConfig(BaseModel):
|
||||
class Config(BaseModel):
|
||||
teaching_team: List[str]
|
||||
max_size: float = Field(..., ge=0)
|
||||
release_tags: List[str]
|
||||
files: RepoFiles
|
||||
files: Files
|
||||
sandbox_token: str
|
||||
|
|
|
@ -80,29 +80,29 @@ class OptionalCmd(BaseModel):
|
|||
)
|
||||
|
||||
|
||||
class Stage(BaseModel):
|
||||
name: str
|
||||
group: str
|
||||
executor: "ExecutorConfig"
|
||||
parsers: List["ParserConfig"]
|
||||
|
||||
|
||||
class ExecutorWithConfig(BaseModel):
|
||||
class ExecutorWith(BaseModel):
|
||||
default: Cmd
|
||||
cases: List[OptionalCmd]
|
||||
|
||||
|
||||
class ExecutorConfig(BaseModel):
|
||||
class Executor(BaseModel):
|
||||
name: str
|
||||
with_: ExecutorWithConfig = Field(..., serialization_alias="with")
|
||||
with_: ExecutorWith = Field(..., serialization_alias="with")
|
||||
|
||||
|
||||
class ParserConfig(BaseModel):
|
||||
class Parser(BaseModel):
|
||||
name: str
|
||||
with_: Dict[str, Any] = Field(..., serialization_alias="with")
|
||||
|
||||
|
||||
class StageConfig(BaseModel):
|
||||
class StageDetail(BaseModel):
|
||||
name: str
|
||||
group: str
|
||||
executor: Executor
|
||||
parsers: List[Parser]
|
||||
|
||||
|
||||
class Stage(BaseModel):
|
||||
sandbox_exec_server: str = Field(
|
||||
"172.17.0.1:5051", serialization_alias="sandboxExecServer"
|
||||
)
|
||||
|
@ -110,10 +110,10 @@ class StageConfig(BaseModel):
|
|||
output_path: str = Field(
|
||||
"/tmp/joj3_result.json", serialization_alias="outputPath"
|
||||
) # nosec: B108
|
||||
stages: List[Stage]
|
||||
stages: List[StageDetail]
|
||||
|
||||
|
||||
class TeapotConfig(BaseModel):
|
||||
class Teapot(BaseModel):
|
||||
log_path: str = Field(
|
||||
"/home/tt/.cache/joint-teapot-debug.log", serialization_alias="logPath"
|
||||
)
|
||||
|
@ -127,9 +127,9 @@ class TeapotConfig(BaseModel):
|
|||
skip_failed_table: bool = Field(False, serialization_alias="skipFailedTable")
|
||||
|
||||
|
||||
class ResultConfig(BaseModel):
|
||||
class Config(BaseModel):
|
||||
name: str = "unknown"
|
||||
log_path: str = Field("", serialization_alias="logPath")
|
||||
expire_unix_timestamp: int = Field(-1, serialization_alias="expireUnixTimestamp")
|
||||
stage: StageConfig
|
||||
teapot: TeapotConfig
|
||||
stage: Stage
|
||||
teapot: Teapot
|
||||
|
|
|
@ -31,7 +31,7 @@ class Release(BaseModel):
|
|||
deadline: Optional[datetime] # RFC 3339 formatted date-time with offset
|
||||
|
||||
|
||||
class TaskConfig(BaseModel):
|
||||
class Config(BaseModel):
|
||||
task: str # Task name (e.g., hw3 ex5)
|
||||
release: Release # Release configuration
|
||||
stages: List[Stage] # list of stage configurations
|
||||
|
|
|
@ -47,6 +47,7 @@ excludes = ["tests"]
|
|||
|
||||
[tool.pytest.ini_options]
|
||||
testpaths = ["tests"]
|
||||
xfail_strict=true
|
||||
|
||||
[tool.mypy]
|
||||
plugins = ["pydantic.mypy"]
|
||||
|
|
|
@ -1,42 +0,0 @@
|
|||
import json
|
||||
import os
|
||||
from typing import Any, Dict, List, Tuple
|
||||
|
||||
import pytest
|
||||
import rtoml
|
||||
|
||||
from joj3_config_generator.models import RepoConfig, TaskConfig
|
||||
from tests.utils import safe_id
|
||||
|
||||
|
||||
def read_convert_files(root: str) -> Tuple[RepoConfig, TaskConfig, Dict[str, Any]]:
|
||||
repo_toml_path = os.path.join(root, "repo.toml")
|
||||
task_toml_path = os.path.join(root, "task.toml")
|
||||
result_json_path = os.path.join(root, "task.json")
|
||||
with open(repo_toml_path) as repo_file:
|
||||
repo_toml = repo_file.read()
|
||||
with open(task_toml_path) as task_file:
|
||||
task_toml = task_file.read()
|
||||
with open(result_json_path) as result_file:
|
||||
expected_result: Dict[str, Any] = json.load(result_file)
|
||||
repo_obj = rtoml.loads(repo_toml)
|
||||
task_obj = rtoml.loads(task_toml)
|
||||
return RepoConfig(**repo_obj), TaskConfig(**task_obj), expected_result
|
||||
|
||||
|
||||
def get_test_cases() -> List[Tuple[str, RepoConfig, TaskConfig, Dict[str, Any]]]:
|
||||
test_cases = []
|
||||
tests_dir = os.path.dirname(os.path.realpath(__file__))
|
||||
for dir_name in os.listdir(tests_dir):
|
||||
dir_path = os.path.join(tests_dir, dir_name)
|
||||
if os.path.isdir(dir_path) and dir_name != "__pycache__":
|
||||
repo, task, expected_result = read_convert_files(dir_path)
|
||||
test_cases.append((dir_name, repo, task, expected_result))
|
||||
return test_cases
|
||||
|
||||
|
||||
@pytest.fixture(params=get_test_cases(), ids=safe_id)
|
||||
def test_case(
|
||||
request: pytest.FixtureRequest,
|
||||
) -> Tuple[RepoConfig, TaskConfig, Dict[str, Any]]:
|
||||
return request.param[1:]
|
|
@ -1,10 +1,8 @@
|
|||
from typing import Any, Dict, Tuple
|
||||
|
||||
from joj3_config_generator.convert import convert
|
||||
from joj3_config_generator.models import RepoConfig, TaskConfig
|
||||
from tests.convert.utils import read_convert_files
|
||||
|
||||
|
||||
def test_convert(test_case: Tuple[RepoConfig, TaskConfig, Dict[str, Any]]) -> None:
|
||||
repo, task, expected_result = test_case
|
||||
def test_basic() -> None:
|
||||
repo, task, expected_result = read_convert_files("basic")
|
||||
result = convert(repo, task).model_dump(by_alias=True)
|
||||
assert result == expected_result
|
||||
|
|
25
tests/convert/utils.py
Normal file
25
tests/convert/utils.py
Normal file
|
@ -0,0 +1,25 @@
|
|||
import json
|
||||
import os
|
||||
from typing import Any, Dict, Tuple
|
||||
|
||||
import rtoml
|
||||
|
||||
from joj3_config_generator.models import repo, task
|
||||
|
||||
|
||||
def read_convert_files(
|
||||
case_name: str,
|
||||
) -> Tuple[repo.Config, task.Config, Dict[str, Any]]:
|
||||
root = os.path.dirname(os.path.realpath(__file__))
|
||||
repo_toml_path = os.path.join(root, case_name, "repo.toml")
|
||||
task_toml_path = os.path.join(root, case_name, "task.toml")
|
||||
result_json_path = os.path.join(root, case_name, "task.json")
|
||||
with open(repo_toml_path) as repo_file:
|
||||
repo_toml = repo_file.read()
|
||||
with open(task_toml_path) as task_file:
|
||||
task_toml = task_file.read()
|
||||
with open(result_json_path) as result_file:
|
||||
result: Dict[str, Any] = json.load(result_file)
|
||||
repo_obj = rtoml.loads(repo_toml)
|
||||
task_obj = rtoml.loads(task_toml)
|
||||
return repo.Config(**repo_obj), task.Config(**task_obj), result
|
0
tests/convert_joj1/basic/task.toml
Normal file
0
tests/convert_joj1/basic/task.toml
Normal file
50
tests/convert_joj1/basic/task.yaml
Normal file
50
tests/convert_joj1/basic/task.yaml
Normal file
|
@ -0,0 +1,50 @@
|
|||
languages:
|
||||
- language: cc
|
||||
compiler_args: g++ -O2 -Wall -std=c++11 -o /out/main /in/main.cpp -lm
|
||||
code_file: main.cpp # not necessary in tarball mode
|
||||
execute_file: main # no need to set for an interpreter (will use config in langs.yaml)
|
||||
execute_args: main # the execute args for all test cases
|
||||
- language: c
|
||||
compiler_args: gcc -O2 -Wall -std=c11 -o /out/main /in/main.c -lm
|
||||
code_file: main.c # not necessary in tarball mode
|
||||
execute_file: main # no need to set for an interpreter (will use config in langs.yaml)
|
||||
execute_args: main # the execute args for all test cases
|
||||
default: &default
|
||||
time: 1s
|
||||
memory: 32m
|
||||
score: 10
|
||||
cases:
|
||||
- <<: *default
|
||||
input: case0.in
|
||||
output: case0.out
|
||||
execute_args: -abcd --aaaa bbbb
|
||||
- <<: *default
|
||||
input: case1.in
|
||||
output: case1.out
|
||||
- <<: *default
|
||||
input: case2.in
|
||||
output: case2.out
|
||||
- <<: *default
|
||||
input: case3.in
|
||||
output: case3.out
|
||||
- <<: *default
|
||||
input: case4.in
|
||||
output: case4.out
|
||||
- <<: *default
|
||||
input: case5.in
|
||||
output: case5.out
|
||||
- <<: *default
|
||||
input: case6.in
|
||||
output: case6.out
|
||||
- <<: *default
|
||||
input: case7.in
|
||||
output: case7.out
|
||||
category: sentence
|
||||
- <<: *default
|
||||
input: case8.in
|
||||
output: case8.out
|
||||
category: sentence
|
||||
- <<: *default
|
||||
input: case9.in
|
||||
output: case9.out
|
||||
category: sentence
|
|
@ -1,43 +0,0 @@
|
|||
import json
|
||||
import os
|
||||
from typing import Any, Dict, List, Tuple
|
||||
|
||||
import pytest
|
||||
import rtoml
|
||||
import yaml
|
||||
|
||||
from joj3_config_generator.models import JOJ1Config, TaskConfig
|
||||
from tests.utils import safe_id
|
||||
|
||||
|
||||
def read_convert_joj1_files(root: str) -> Tuple[JOJ1Config, TaskConfig, Dict[str, Any]]:
|
||||
task_yaml_path = os.path.join(root, "task.yaml")
|
||||
task_toml_path = os.path.join(root, "task.toml")
|
||||
expected_json_path = os.path.join(root, "task.json")
|
||||
with open(task_yaml_path) as repo_file:
|
||||
task_yaml = repo_file.read()
|
||||
with open(task_toml_path) as task_file:
|
||||
task_toml = task_file.read()
|
||||
with open(expected_json_path) as result_file:
|
||||
expected_result: Dict[str, Any] = json.load(result_file)
|
||||
joj1_obj = yaml.safe_load(task_yaml)
|
||||
task_obj = rtoml.loads(task_toml)
|
||||
return JOJ1Config(**joj1_obj), TaskConfig(**task_obj), expected_result
|
||||
|
||||
|
||||
def get_test_cases() -> List[Tuple[str, JOJ1Config, TaskConfig, Dict[str, Any]]]:
|
||||
test_cases = []
|
||||
tests_dir = os.path.dirname(os.path.realpath(__file__))
|
||||
for dir_name in os.listdir(tests_dir):
|
||||
dir_path = os.path.join(tests_dir, dir_name)
|
||||
if os.path.isdir(dir_path) and dir_name != "__pycache__":
|
||||
joj1, task, expected_result = read_convert_joj1_files(dir_path)
|
||||
test_cases.append((dir_name, joj1, task, expected_result))
|
||||
return test_cases
|
||||
|
||||
|
||||
@pytest.fixture(params=get_test_cases(), ids=safe_id)
|
||||
def test_case(
|
||||
request: pytest.FixtureRequest,
|
||||
) -> Tuple[JOJ1Config, TaskConfig, Dict[str, Any]]:
|
||||
return request.param[1:]
|
|
@ -1,9 +1,11 @@
|
|||
from typing import Any, Dict, Tuple
|
||||
import pytest
|
||||
|
||||
from joj3_config_generator.models import JOJ1Config, TaskConfig
|
||||
from joj3_config_generator.convert import convert_joj1
|
||||
from tests.convert_joj1.utils import read_convert_joj1_files
|
||||
|
||||
|
||||
def test_convert_joj1(test_case: Tuple[JOJ1Config, TaskConfig, Dict[str, Any]]) -> None:
|
||||
joj1, task, expected_result = test_case
|
||||
result: Dict[str, Any] = {}
|
||||
@pytest.mark.xfail
|
||||
def test_basic() -> None:
|
||||
joj1, expected_result = read_convert_joj1_files("basic")
|
||||
result = convert_joj1(joj1).model_dump(by_alias=True)
|
||||
assert result == expected_result
|
||||
|
|
20
tests/convert_joj1/utils.py
Normal file
20
tests/convert_joj1/utils.py
Normal file
|
@ -0,0 +1,20 @@
|
|||
import os
|
||||
from typing import Any, Dict, Tuple
|
||||
|
||||
import rtoml
|
||||
import yaml
|
||||
|
||||
from joj3_config_generator.models import joj1
|
||||
|
||||
|
||||
def read_convert_joj1_files(case_name: str) -> Tuple[joj1.Config, Dict[str, Any]]:
|
||||
root = os.path.dirname(os.path.realpath(__file__))
|
||||
task_yaml_path = os.path.join(root, case_name, "task.yaml")
|
||||
task_toml_path = os.path.join(root, case_name, "task.toml")
|
||||
with open(task_yaml_path) as repo_file:
|
||||
task_yaml = repo_file.read()
|
||||
with open(task_toml_path) as task_file:
|
||||
task_toml = task_file.read()
|
||||
joj1_obj = yaml.safe_load(task_yaml)
|
||||
task_obj = rtoml.loads(task_toml)
|
||||
return joj1.Config(**joj1_obj), task_obj
|
Loading…
Reference in New Issue
Block a user