dev #10

Merged
李衍志523370910113 merged 238 commits from dev into master 2025-03-05 16:20:39 +08:00
42 changed files with 2569 additions and 311 deletions

View File

@ -16,3 +16,25 @@
4. Install deps by `pdm install && pdm run pre-commit install`
5. Run the cli app by `pdm run app --help`
6. Check other commands or scripts with `pdm run --list`
## How to use?
- `joj3-config-generator convert` function is now supported, currently support one argument as input, it indicates the **convert root**
- default value on the server can be given as `/home/tt/.config/joj`
- **NOTE:** the user should ensure that the ideal `repo.toml` file is in the sub-directory of the **convert root**
- the intended immutable files should be placed at a sub-directory named `immutable_files` at same position as the `repo.toml` file
```shell
$ tree .
.
|- immutable_files
| |-- push.yaml
| |-- release.yaml
|-- repo.toml
```
- sample command on the server
```shell
joj3-config-generator convert /home/tt/.config/joj
```

View File

@ -1,93 +1,41 @@
from typing import List
import os
from typing import Dict
from joj3_config_generator.models import joj1, repo, result, task
from joj3_config_generator.models.const import CACHE_ROOT, JOJ3_CONFIG_ROOT
from joj3_config_generator.processers.repo import (
get_health_check_stage,
get_teapot_stage,
)
from joj3_config_generator.processers.task import get_conf_stage
# FIXME: LLM generated convert function, only for demostration
def convert(repo_conf: repo.Config, task_conf: task.Config) -> result.Config:
# Create the base ResultConf object
result_conf = result.Config(
name=task_conf.task,
log_path=f"{task_conf.task.replace(' ', '_')}.log",
expire_unix_timestamp=(
int(task_conf.release.deadline.timestamp())
if task_conf.release.deadline
else -1
),
stage=result.Stage(stages=[], sandbox_token=repo_conf.sandbox_token),
teapot=result.Teapot(),
name=task_conf.task.name,
# exact folder difference specified by type
log_path=str(CACHE_ROOT / "joj3" / f"{task_conf.task.type_}.log"),
expire_unix_timestamp=int(task_conf.release.end_time.timestamp()),
effective_unix_timestamp=int(task_conf.release.begin_time.timestamp()),
actor_csv_path=str(JOJ3_CONFIG_ROOT / "students.csv"), # students.csv position
max_total_score=repo_conf.max_total_score,
stage=result.Stage(sandbox_token=repo_conf.sandbox_token),
)
current_test = os.environ.get("PYTEST_CURRENT_TEST") is not None
# Construct health check stage
if not repo_conf.force_skip_health_check_on_test or not current_test:
result_conf.stage.stages.append(get_health_check_stage(repo_conf))
cached: Dict[str, None] = {}
# Convert each stage in the task configuration
for task_stage in task_conf.stages:
jon-lee marked this conversation as resolved Outdated

Make this Path.home() default to /home/tt. For now, create a const for this dir.

Make this `Path.home()` default to `/home/tt`. For now, create a const for this dir.

fixed

fixed
executor_with_config = result.ExecutorWith(
default=result.Cmd(
args=task_stage.command.split(),
copy_in={
file: result.LocalFile(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 = result.StageDetail(
name=task_stage.name,
group=task_conf.task,
executor=result.Executor(
name="sandbox",
with_=executor_with_config,
),
parsers=[
result.Parser(name=parser, with_={}) for parser in task_stage.parsers
],
)
if "result-detail" in task_stage.parsers:
result_detail_parser = next(
p for p in conf_stage.parsers if p.name == "result-detail"
)
result_detail_parser.with_.update(task_stage.result_detail)
result_conf.stage.stages.append(conf_stage)
result_conf.stage.stages.append(get_conf_stage(task_conf, task_stage, cached))
if not repo_conf.force_skip_teapot_on_test or not current_test:
result_conf.stage.post_stages.append(get_teapot_stage(repo_conf))
return result_conf
jon-lee marked this conversation as resolved Outdated

where is it used?

where is it used?

this should be storing all the files that are about to be copy in or out

this should be storing all the files that are about to be copy in or out

It is as the input and output for the following functions about parsers

It is as the input and output for the following functions about parsers

so this feature is not implemented?

so this feature is not implemented?
    if not repo_conf.force_skip_health_check_on_test or not current_test:
        result_conf.stage.stages.append(get_health_check_config(repo_conf))
    cached: List[str] = []
    # Convert each stage in the task configuration
    for task_stage in task_conf.stages:
        executor_with_config, cached = get_executor_with_config(task_stage, cached)
        conf_stage = get_conf_stage(task_stage, executor_with_config)
        conf_stage = fix_result_detail(task_stage, conf_stage)
        conf_stage = fix_dummy(task_stage, conf_stage)
        conf_stage = fix_keyword(task_stage, conf_stage)
        conf_stage = fix_file(task_stage, conf_stage)
        conf_stage = fix_diff(task_stage, conf_stage, task_conf)
        result_conf.stage.stages.append(conf_stage)

it is

    for task_stage in task_conf.stages:
        executor_with_config, cached = get_executor_with_config(task_stage, cached)

this is a loop, so this cached will be updated in every round of stage

```python if not repo_conf.force_skip_health_check_on_test or not current_test: result_conf.stage.stages.append(get_health_check_config(repo_conf)) cached: List[str] = [] # Convert each stage in the task configuration for task_stage in task_conf.stages: executor_with_config, cached = get_executor_with_config(task_stage, cached) conf_stage = get_conf_stage(task_stage, executor_with_config) conf_stage = fix_result_detail(task_stage, conf_stage) conf_stage = fix_dummy(task_stage, conf_stage) conf_stage = fix_keyword(task_stage, conf_stage) conf_stage = fix_file(task_stage, conf_stage) conf_stage = fix_diff(task_stage, conf_stage, task_conf) result_conf.stage.stages.append(conf_stage) ``` it is ```python for task_stage in task_conf.stages: executor_with_config, cached = get_executor_with_config(task_stage, cached) ``` this is a loop, so this `cached` will be updated in every round of stage

The return value is unnecessary.

The return value is unnecessary.

I have a lazing coding style here, everything has get imported would get exported, so should maintain this until the end of the loop. Everything is exported in previous stage would be imported in the next stage.

I have a lazing coding style here, everything has get imported would get exported, so should maintain this until the end of the loop. Everything is exported in previous stage would be imported in the next stage.
  1. The return value is unnecessary
  2. It should be a set
1. The return value is unnecessary 2. It should be a `set`

try it yourself

try it yourself

I see why

I see why

resolved.

resolved.
# FIXME: LLM generated convert function, only for demostration
def convert_joj1(joj1_conf: joj1.Config) -> task.Config:
stages = []
for language in joj1_conf.languages:
# Here you might want to create a stage for each language
# You can define a command based on language properties
command = f"run {language.language}"
# Assuming we don't have explicit files, we will set empty ones or default behavior
files = task.Files(import_=[], export=[])
# Score can be derived from the first case or set to a default
score = 0
parsers: List[str] = [] # Define parsers if applicable
if joj1_conf.cases:
score = sum(
case.score for case in joj1_conf.cases
) # Sum scores for all cases
# Creating a stage for each language
stages.append(
task.Stage(
name=language.language,
command=command,
files=files,
score=score,
parsers=parsers,
result_detail=task.ParserResultDetail(), # You can customize this further if needed
)
)
# Assuming no deadline is provided in `joj1`, you can set it accordingly
release_deadline = (
None # Placeholder for future implementation if deadlines are defined
)
return task.Config(
task=joj1_conf.languages[0].language if joj1_conf.languages else "Unnamed Task",
release=task.Release(deadline=release_deadline),
stages=stages,
)
return task.Config()

View File

@ -0,0 +1,39 @@
from pathlib import Path
from typing import Dict, Tuple
import inquirer
import rtoml
import yaml
from joj3_config_generator.models import joj1, repo, task
def load_joj3_toml_answers() -> Dict[str, str]:
questions = [
inquirer.List(
"size",
message="What size do you need?",
choices=["Jumbo", "Large", "Standard", "Medium", "Small", "Micro"],
),
]
answers = inquirer.prompt(questions)
return answers
def load_joj1_yaml(yaml_path: Path) -> joj1.Config:
joj1_obj = yaml.safe_load(yaml_path.read_text())
return joj1.Config(**joj1_obj)
def load_joj3_toml(
root_path: Path, repo_toml_path: Path, task_toml_path: Path
) -> Tuple[repo.Config, task.Config]:
repo_obj = rtoml.loads(repo_toml_path.read_text())
task_obj = rtoml.loads(task_toml_path.read_text())
repo_conf = repo.Config(**repo_obj)
repo_conf.root = root_path
repo_conf.path = repo_toml_path.relative_to(root_path)
task_conf = task.Config(**task_obj)
task_conf.root = root_path
task_conf.path = task_toml_path.relative_to(root_path)
return repo_conf, task_conf

View File

@ -1,48 +1,44 @@
import json
jon-lee marked this conversation as resolved

this file should not be changed

this file should not be changed
from pathlib import Path
import inquirer
import rtoml
import typer
import yaml
from typing_extensions import Annotated
from joj3_config_generator.convert import convert as convert_conf
from joj3_config_generator.convert import convert_joj1 as convert_joj1_conf
from joj3_config_generator.models import joj1, repo, task
from joj3_config_generator.load import (
load_joj1_yaml,
load_joj3_toml,
load_joj3_toml_answers,
)
from joj3_config_generator.models.const import JOJ3_CONFIG_ROOT
from joj3_config_generator.utils.logger import logger
app = typer.Typer(add_completion=False)
@app.command()
def create(toml: typer.FileTextWrite) -> None:
def create(toml_path: Path) -> None:
"""
Create a new JOJ3 toml config file
"""
logger.info("Creating")
questions = [
inquirer.List(
"size",
message="What size do you need?",
choices=["Jumbo", "Large", "Standard", "Medium", "Small", "Micro"],
),
]
answers = inquirer.prompt(questions)
logger.info(answers)
logger.info(f"Creating toml file {toml_path}")
answers = load_joj3_toml_answers()
logger.debug(f"Got answers: {answers}")
toml_path.write_text(rtoml.dumps({}))
@app.command()
def convert_joj1(yaml_file: typer.FileText, toml_file: typer.FileTextWrite) -> None:
def convert_joj1(yaml_path: Path, toml_path: Path) -> None:
"""
Convert a JOJ1 yaml config file to JOJ3 toml config file
"""
logger.info(f"Converting yaml file {yaml_file}")
joj1_obj = yaml.safe_load(yaml_file.read())
joj1_model = joj1.Config(**joj1_obj)
logger.info(f"Converting yaml file {yaml_path}")
joj1_model = load_joj1_yaml(yaml_path)
task_model = convert_joj1_conf(joj1_model)
result_dict = task_model.model_dump(by_alias=True)
toml_file.write(rtoml.dumps(result_dict))
result_dict = task_model.model_dump(mode="json", by_alias=True, exclude_none=True)
toml_path.write_text(rtoml.dumps(result_dict))
@app.command()
@ -50,8 +46,7 @@ def convert(
root: Annotated[
Path,
typer.Argument(
help="root directory of config files, "
"located at /home/tt/.config/joj in JTC"
help=f"root directory of config files, located at {JOJ3_CONFIG_ROOT} in JTC"
),
] = Path(".")
) -> None:
@ -60,9 +55,7 @@ def convert(
"""
logger.info(f"Converting files in {root.absolute()}")
for repo_toml_path in root.glob("**/repo.toml"):
repo_path = repo_toml_path.parent
repo_obj = rtoml.loads(repo_toml_path.read_text())
for task_toml_path in repo_path.glob("**/*.toml"):
for task_toml_path in repo_toml_path.parent.glob("**/*.toml"):
if repo_toml_path == task_toml_path:
continue
toml_name = task_toml_path.name.removesuffix(".toml")
@ -70,15 +63,11 @@ def convert(
logger.info(
f"Converting {repo_toml_path} & {task_toml_path} to {result_json_path}"
)
task_obj = rtoml.loads(task_toml_path.read_text())
repo_conf = repo.Config(**repo_obj)
repo_conf.root = root
repo_conf.path = repo_toml_path.relative_to(root)
task_conf = task.Config(**task_obj)
task_conf.root = root
task_conf.path = task_toml_path.relative_to(root)
repo_conf, task_conf = load_joj3_toml(root, repo_toml_path, task_toml_path)
result_model = convert_conf(repo_conf, task_conf)
result_dict = result_model.model_dump(by_alias=True, exclude_none=True)
result_dict = result_model.model_dump(
mode="json", by_alias=True, exclude_none=True
)
with result_json_path.open("w") as result_file:
json.dump(result_dict, result_file, ensure_ascii=False, indent=4)
result_file.write("\n")

View File

@ -0,0 +1,19 @@
from typing import Union
import humanfriendly
class Memory(int):
jon-lee marked this conversation as resolved Outdated

what compact?

what compact?

no int input, should be done already, so it can be removed. I added that several commit before before you removed that 😇

no int input, should be done already, so it can be removed. I added that several commit before before you removed that 😇
def __new__(cls, value: Union[str, int]) -> "Memory":
if isinstance(value, str):
parsed = humanfriendly.parse_size(value, binary=True)
return super().__new__(cls, parsed)
return super().__new__(cls, value)
class Time(int):
def __new__(cls, value: Union[str, int]) -> "Time":
if isinstance(value, str):
parsed = humanfriendly.parse_timespan(value) * 1_000_000_000 # ns
return super().__new__(cls, round(parsed))
return super().__new__(cls, value)

View File

@ -0,0 +1,11 @@
from pathlib import Path
from joj3_config_generator.models.common import Memory, Time
DEFAULT_CPU_LIMIT = Time("1s")
DEFAULT_MEMORY_LIMIT = Memory("128m")
DEFAULT_FILE_LIMIT = Memory("32m")
JOJ3_CONFIG_ROOT = Path("/home/tt/.config/joj")
TEAPOT_CONFIG_ROOT = Path("/home/tt/.config/teapot")
CACHE_ROOT = Path("/home/tt/.cache")

View File

@ -1,21 +1,29 @@
import socket
from pathlib import Path
from typing import List, Optional
from typing import List
from pydantic import BaseModel, Field
class Files(BaseModel):
whitelist_patterns: List[str]
whitelist_file: Optional[str]
required: List[str]
immutable: List[str]
required: List[str] = []
immutable: List[str] = []
class Groups(BaseModel):
name: List[str] = []
max_count: List[int] = []
time_period_hour: List[int] = []
class Config(BaseModel):
max_size: float = Field(10, ge=0)
files: Files = Files()
sandbox_token: str = Field("")
max_total_score: int = Field(100)
force_skip_health_check_on_test: bool = False
force_skip_teapot_on_test: bool = False
groups: Groups = Groups()
root: Path = Path(".")
path: Path = Path("repo.toml")
teaching_team: List[str]
max_size: float = Field(..., ge=0)
release_tags: List[str]
files: Files
sandbox_token: str
grading_repo_name: str = f"{socket.gethostname().split('-')[0]}-joj"

View File

@ -1,6 +1,12 @@
from typing import Any, Dict, List, Optional, Union
from typing import TYPE_CHECKING, Any, Dict, List, Optional, Union
from pydantic import BaseModel, Field
from pydantic import BaseModel, ConfigDict, Field, field_validator
from joj3_config_generator.models.const import (
DEFAULT_CPU_LIMIT,
DEFAULT_FILE_LIMIT,
DEFAULT_MEMORY_LIMIT,
)
class LocalFile(BaseModel):
@ -17,7 +23,7 @@ class PreparedFile(BaseModel):
class Collector(BaseModel):
jon-lee marked this conversation as resolved Outdated

default value too large, should be 1s 128MB

default value too large, should be 1s 128MB

will fix later, we need to hold some test I reckon?

will fix later, we need to hold some test I reckon?

@bomingzh fixed

@bomingzh fixed
name: str
max: int
max: int = DEFAULT_FILE_LIMIT
pipe: bool = True
@ -33,27 +39,26 @@ class StreamOut(BaseModel):
stream_out: bool = Field(..., alias="streamOut")
InputFile = Union[LocalFile | MemoryFile | PreparedFile | Symlink]
InputFile = Union[LocalFile, MemoryFile, PreparedFile, Symlink]
class Cmd(BaseModel):
args: List[str]
args: List[str] = []
env: List[str] = []
stdin: Optional[Union[InputFile | StreamIn]] = None
stdout: Optional[Union[Collector | StreamOut]] = None
stderr: Optional[Union[Collector | StreamOut]] = None
cpu_limit: int = Field(0, serialization_alias="cpuLimit")
real_cpu_limit: int = Field(0, serialization_alias="realCpuLimit")
clock_limit: int = Field(0, serialization_alias="clockLimit")
memory_limit: int = Field(0, serialization_alias="memoryLimit")
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(2 * DEFAULT_CPU_LIMIT, serialization_alias="clockLimit")
memory_limit: int = Field(DEFAULT_MEMORY_LIMIT, serialization_alias="memoryLimit")
stack_limit: int = Field(0, serialization_alias="stackLimit")
proc_limit: int = Field(0, serialization_alias="procLimit")
proc_limit: int = Field(50, serialization_alias="procLimit")
cpu_rate_limit: int = Field(0, serialization_alias="cpuRateLimit")
cpu_set_limit: str = Field("", serialization_alias="cpuSetLimit")
copy_in: Dict[str, InputFile] = Field({}, serialization_alias="copyIn")
copy_in_cached: Dict[str, str] = Field({}, serialization_alias="copyInCached")
copy_in_dir: str = Field(".", serialization_alias="copyInDir")
copy_out: List[str] = Field([], serialization_alias="copyOut")
copy_out: List[str] = Field(["stdout", "stderr"], serialization_alias="copyOut")
jon-lee marked this conversation as resolved Outdated

What is the conclusion?

What is the conclusion?

should be already solved.

should be already solved.
copy_out_cached: List[str] = Field([], serialization_alias="copyOutCached")
copy_out_max: int = Field(0, serialization_alias="copyOutMax")
copy_out_dir: str = Field("", serialization_alias="copyOutDir")
@ -66,11 +71,10 @@ class Cmd(BaseModel):
class OptionalCmd(BaseModel):
args: Optional[List[str]] = None
env: Optional[List[str]] = None
stdin: Optional[Union[InputFile | StreamIn]] = None
stdout: Optional[Union[Collector | StreamOut]] = None
stderr: Optional[Union[Collector | StreamOut]] = 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")
real_cpu_limit: Optional[int] = Field(None, serialization_alias="realCpuLimit")
clock_limit: Optional[int] = Field(None, serialization_alias="clockLimit")
memory_limit: Optional[int] = Field(None, serialization_alias="memoryLimit")
stack_limit: Optional[int] = Field(None, serialization_alias="stackLimit")
@ -101,23 +105,39 @@ class OptionalCmd(BaseModel):
class ExecutorWith(BaseModel):
default: Cmd
cases: List[OptionalCmd]
default: Cmd = Cmd()
cases: List[OptionalCmd] = []
class Executor(BaseModel):
name: str
with_: ExecutorWith = Field(..., serialization_alias="with")
with_: ExecutorWith = Field(ExecutorWith(), serialization_alias="with")
class Parser(BaseModel):
name: str
with_: Dict[str, Any] = Field(..., serialization_alias="with")
if TYPE_CHECKING:
class Empty(BaseModel):
pass
with_: BaseModel = Field(Empty(), serialization_alias="with")
jon-lee marked this conversation as resolved Outdated

Should it be optional?

Should it be optional?
else:
with_: Dict[str, Any] = Field({}, serialization_alias="with")
model_config = ConfigDict(validate_assignment=True)
@field_validator("with_", mode="before")
@classmethod
def validate_with(cls, v: Any) -> Dict[str, Any]:
if isinstance(v, BaseModel):
return v.model_dump(by_alias=True)
raise ValueError("Must be a BaseModel instance")
class StageDetail(BaseModel):
name: str
group: str
group: str = ""
executor: Executor
parsers: List[Parser]
@ -130,26 +150,74 @@ class Stage(BaseModel):
output_path: str = Field(
"/tmp/joj3_result.json", serialization_alias="outputPath"
) # nosec: B108
stages: List[StageDetail]
class Teapot(BaseModel):
log_path: str = Field(
"/home/tt/.cache/joint-teapot-debug.log", serialization_alias="logPath"
)
scoreboard_path: str = Field("scoreboard.csv", serialization_alias="scoreboardPath")
failed_table_path: str = Field(
"failed-table.md", serialization_alias="failedTablePath"
)
grading_repo_name: str = Field("", serialization_alias="gradingRepoName")
skip_issue: bool = Field(False, serialization_alias="skipIssue")
skip_scoreboard: bool = Field(False, serialization_alias="skipScoreboard")
skip_failed_table: bool = Field(False, serialization_alias="skipFailedTable")
stages: List[StageDetail] = []
pre_stages: List[StageDetail] = Field([], serialization_alias="preStages")
post_stages: List[StageDetail] = Field([], serialization_alias="postStages")
class Config(BaseModel):
name: str = "unknown"
name: str = ""
log_path: str = Field("", serialization_alias="logPath")
expire_unix_timestamp: int = Field(-1, serialization_alias="expireUnixTimestamp")
effective_unix_timestamp: int = Field(
-1, serialization_alias="effectiveUnixTimestamp"
)
actor_csv_path: str = Field("", serialization_alias="actorCsvPath")
max_total_score: int = Field(100, serialization_alias="maxTotalScore")
stage: Stage
teapot: Teapot
class DummyConfig(BaseModel):
score: int = 0
jon-lee marked this conversation as resolved Outdated

Are these Optional necessary?

Are these `Optional` necessary?
comment: Optional[str] = None
force_quit_on_not_accepted: Optional[bool] = Field(
False, serialization_alias="forceQuitOnNotAccepted"
)
class DiffOutputConfig(BaseModel):
jon-lee marked this conversation as resolved

also these Optional?

also these `Optional`?

I guess some of the field have default within JOJ3, so I choose optional just to reduce the length of json previously

I guess some of the field have default within JOJ3, so I choose optional just to reduce the length of json previously

better put all defaults here then we only need to check the code here

better put all defaults here then we only need to check the code here

indeed.

indeed.
score: int = 100
file_name: str = Field("", serialization_alias="fileName")
answer_path: str = Field("", serialization_alias="answerPath")
force_quit_on_diff: bool = Field(False, serialization_alias="forceQuitOnDiff")
always_hide: bool = Field(False, serialization_alias="alwaysHide")
compare_space: bool = Field(False, serialization_alias="compareSpace")
class ResultDetailConfig(BaseModel):
score: int = 0
comment: str = ""
show_files: List[str] = Field([], serialization_alias="showFiles")
show_exit_status: bool = Field(True, serialization_alias="showExitStatus")
show_runtime: bool = Field(True, serialization_alias="showRuntime")
show_memory: bool = Field(False, serialization_alias="showMemory")
class KeywordConfig(BaseModel):
keywords: List[str] = []
score: int = 0
class KeywordMatchConfig(BaseModel):
matches: List[KeywordConfig] = []
class FileConfig(BaseModel):
name: str = ""
class DiffCasesConfig(BaseModel):
outputs: List[DiffOutputConfig] = []
class DiffConfig(BaseModel):
jon-lee marked this conversation as resolved

what needs to be refined?

what needs to be refined?

just feels bit waste since this outputs only have one field before I coded the logic, I wrote that comment without checking the details of diff parser. But it should be the best strategy to show the structures as well as give flexiblity for extension now.

just feels bit waste since this outputs only have one field before I coded the logic, I wrote that comment without checking the details of diff parser. But it should be the best strategy to show the structures as well as give flexiblity for extension now.
name: str = "diff"
cases: List[DiffCasesConfig] = []
class MsgConfig(BaseModel):
msg: str = ""
class ScoreConfig(BaseModel):
score: int = 0

View File

@ -1,8 +1,16 @@
from datetime import datetime
from datetime import datetime, timedelta
jon-lee marked this conversation as resolved Outdated

every field in this file should not be optional. we give an default value here if any field does not exist

every field in this file should not be optional. we give an default value here if any field does not exist

and use underscore naming in this file

and use underscore naming in this file

every field in this file should not be optional. we give an default value here if any field does not exist

fixed

> every field in this file should not be optional. we give an default value here if any field does not exist fixed

and use underscore naming in this file

fixed.

> and use underscore naming in this file fixed.
from enum import Enum
from pathlib import Path
from typing import List, Optional
from typing import Any, Dict, List, Type
from pydantic import BaseModel, Field
from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator
from joj3_config_generator.models.common import Memory, Time
from joj3_config_generator.models.const import (
DEFAULT_CPU_LIMIT,
DEFAULT_FILE_LIMIT,
DEFAULT_MEMORY_LIMIT,
)
class ParserResultDetail(BaseModel):
@ -10,31 +18,136 @@ class ParserResultDetail(BaseModel):
mem: bool = True # Display memory usage
stdout: bool = False # Display stdout messages
stderr: bool = False # Display stderr messages
exit_status: bool = True
class ParserFile(BaseModel):
name: str = ""
class ParserLog(BaseModel):
filename: str
msg: str = ""
level: str = ""
class ParserDummy(BaseModel):
comment: str = ""
score: int = 0
force_quit: bool = False
class ParserKeyword(BaseModel):
keyword: List[str] = []
weight: List[int] = []
class Outputs(BaseModel):
score: int = 0
ignore_spaces: bool = True
hide: bool = False
force_quit: bool = False
class ParserDiff(BaseModel):
output: Outputs = Outputs()
class Files(BaseModel):
import_: List[str] = Field(serialization_alias="import", validation_alias="import")
export: List[str]
import_: List[str] = Field([], alias="import")
export: List[str] = []
class Limit(BaseModel):
mem: int = DEFAULT_MEMORY_LIMIT
cpu: int = DEFAULT_CPU_LIMIT
stdout: int = DEFAULT_FILE_LIMIT
stderr: int = DEFAULT_FILE_LIMIT
model_config = ConfigDict(validate_assignment=True)
@field_validator("cpu", mode="before")
@classmethod
def ensure_time(cls, v: Any) -> Time:
if isinstance(v, str):
return Time(v)
raise ValueError("Must be a string")
@field_validator("mem", "stdout", "stderr", mode="before")
@classmethod
def ensure_mem(cls, v: Any) -> Memory:
if isinstance(v, str):
return Memory(v)
raise ValueError("Must be a string")
class Parser(str, Enum):
CLANG_TIDY = "clangtidy"
jon-lee marked this conversation as resolved Outdated

deprecated

deprecated

@bomingzh any suggestions on the structure?

@bomingzh any suggestions on the structure?
PydanticDeprecatedSince20: Support for class-based `config` is deprecated, use ConfigDict instead. Deprecated in Pydantic V2.0 to be removed in V3.0. See Pydantic V2 Migration Guide at https://errors.pydantic.dev/2.9/migration/
``` PydanticDeprecatedSince20: Support for class-based `config` is deprecated, use ConfigDict instead. Deprecated in Pydantic V2.0 to be removed in V3.0. See Pydantic V2 Migration Guide at https://errors.pydantic.dev/2.9/migration/ ```

fixed with model_config

fixed with `model_config`

str here need to be a StrEnum now.

str here need to be a `StrEnum` now.

But I guess we don't know the set of case in advance, making it dynamic StrEnum is meaningless

But I guess we don't know the set of case in advance, making it dynamic `StrEnum` is meaningless

line changed, the comment is for parsers

line changed, the comment is for `parsers`
CPPCHECK = "cppcheck"
CPPLINT = "cpplint"
jon-lee marked this conversation as resolved Outdated

this should be the StrEnum

this should be the `StrEnum`

It is supported now.

It is supported now.
KEYWORD = "keyword"
RESULT_STATUS = "result-status"
RESULT_DETAIL = "result-detail"
DUMMY = "dummy"
FILE = "file"
DIFF = "diff"
class Stage(BaseModel):
name: str # Stage name
command: str # Command to run
files: Files # Files to import and export
score: int # Score for the task
parsers: List[str] # list of parsers
result_detail: ParserResultDetail = (
ParserResultDetail()
) # for result-detail parser
name: str = "" # Stage name
env: List[str] = []
command: str = "" # Command to run
files: Files = Files()
jon-lee marked this conversation as resolved Outdated

begin_time and end_time to make them match

`begin_time` and `end_time` to make them match

resolved

resolved
in_: str = Field("", alias="in")
out_: str = Field("", alias="out")
score: int = 0
parsers: List[Parser] = [] # list of parsers
limit: Limit = Limit()
dummy: ParserDummy = ParserDummy()
result_status: ParserDummy = Field(ParserDummy(), alias="result-status")
keyword: ParserKeyword = ParserKeyword()
clangtidy: ParserKeyword = ParserKeyword()
cppcheck: ParserKeyword = ParserKeyword()
cpplint: ParserKeyword = ParserKeyword()
result_detail: ParserResultDetail = Field(
ParserResultDetail(), alias="result-detail"
)
file: ParserFile = ParserFile()
skip: List[str] = []
# cases related
cases: Dict[str, "Stage"] = {}
diff: ParserDiff = ParserDiff()
model_config = ConfigDict(extra="allow")
@model_validator(mode="before")
@classmethod
def gather_cases(cls: Type["Stage"], values: Dict[str, Any]) -> Dict[str, Any]:
cases = {k: v for k, v in values.items() if k.startswith("case")}
for key in cases:
values.pop(key)
values["cases"] = {k: v for k, v in cases.items()}
return values
class Release(BaseModel):
deadline: Optional[datetime] # RFC 3339 formatted date-time with offset
end_time: datetime = datetime.now() + timedelta(
days=365
) # RFC 3339 formatted date-time with offset
begin_time: datetime = datetime.fromtimestamp(
0
) # RFC 3339 formatted date-time with offset
class Task(BaseModel):
type_: str = Field("unknown", serialization_alias="type", validation_alias="type")
name: str = "unknown"
class Config(BaseModel):
root: Path = Path(".")
path: Path = Path("task.toml")
task: str # Task name (e.g., hw3 ex5)
release: Release # Release configuration
stages: List[Stage] # list of stage configurations
task: Task = Task() # Task name (e.g., hw3 ex5)
release: Release = Release() # Release configuration
stages: List[Stage] = [] # list of stage configurations

View File

@ -0,0 +1,108 @@
import hashlib
from pathlib import Path
from typing import List
from joj3_config_generator.models import repo, result
from joj3_config_generator.models.const import CACHE_ROOT, TEAPOT_CONFIG_ROOT
def get_teapot_stage(repo_conf: repo.Config) -> result.StageDetail:
args = [
jon-lee marked this conversation as resolved Outdated

make it a field in repo.toml, if it is unset, then use socket.gethostname. We set this value to pass the test.

make it a field in repo.toml, if it is unset, then use `socket.gethostname`. We set this value to pass the test.

resolved.

resolved.
"/usr/local/bin/joint-teapot",
"joj3-all-env",
str(TEAPOT_CONFIG_ROOT / "teapot.env"),
"--grading-repo-name",
repo_conf.grading_repo_name,
"--max-total-score",
str(repo_conf.max_total_score),
]
stage_conf = result.StageDetail(
name="teapot",
executor=result.Executor(
name="local",
with_=result.ExecutorWith(
default=result.Cmd(
args=args,
env=[f"LOG_FILE_PATH={CACHE_ROOT}/joint-teapot-debug.log"],
),
cases=[],
),
),
parsers=[result.Parser(name="log", with_=result.MsgConfig(msg="joj3 summary"))],
)
return stage_conf
def get_health_check_args(repo_conf: repo.Config) -> List[str]:
return [
"/usr/local/bin/repo-health-checker",
"-root=.",
f"-repoSize={str(repo_conf.max_size)}",
*[f"-meta={meta}" for meta in repo_conf.files.required],
f"-checkFileSumList={','.join(get_hashs(repo_conf))}",
f"-checkFileNameList={','.join(repo_conf.files.immutable)}",
]
def get_teapot_check_args(repo_conf: repo.Config) -> List[str]:
return [
"/usr/local/bin/joint-teapot",
"joj3-check-env",
str(TEAPOT_CONFIG_ROOT / "teapot.env"),
"--grading-repo-name",
repo_conf.grading_repo_name,
jon-lee marked this conversation as resolved Outdated

why not args = "hc -root=. "

why not `args = "hc -root=. "`

resolved.

resolved.
"--group-config",
",".join(
f"{name}={max_count}:{time_period}"
for name, max_count, time_period in zip(
jon-lee marked this conversation as resolved Outdated

do we need that default?

do we need that default?

what is the situation internally in JOJ3? I just added that blindly when I reached that stage. Overall I think 100 times per day should be a reasonable and safe default? considering that there may be group works and due date

what is the situation internally in JOJ3? I just added that blindly when I reached that stage. Overall I think 100 times per day should be a reasonable and safe default? considering that there may be group works and due date

They can manually add it if needed. Or just move it to the model default.

They can manually add it if needed. Or just move it to the model default.

removed.

removed.
repo_conf.groups.name,
repo_conf.groups.max_count,
repo_conf.groups.time_period_hour,
)
),
]
def get_health_check_stage(repo_conf: repo.Config) -> result.StageDetail:
health_check_stage = result.StageDetail(
name="Health Check",
group="",
executor=result.Executor(
name="local",
with_=result.ExecutorWith(
default=result.Cmd(),
cases=[
result.OptionalCmd(
args=get_health_check_args(repo_conf),
),
result.OptionalCmd(
args=get_teapot_check_args(repo_conf),
env=[f"LOG_FILE_PATH={CACHE_ROOT}/joint-teapot-debug.log"],
),
],
),
),
parsers=[
result.Parser(name="healthcheck", with_=result.ScoreConfig(score=1)),
result.Parser(name="debug", with_=result.ScoreConfig(score=0)),
],
)
return health_check_stage
def calc_sha256sum(file_path: Path) -> str:
sha256_hash = hashlib.sha256()
with open(file_path, "rb") as f:
for byte_block in iter(lambda: f.read(64 * 1024), b""):
sha256_hash.update(byte_block)
return sha256_hash.hexdigest()
def get_hashs(repo_conf: repo.Config) -> List[str]:
base_dir = (repo_conf.root / repo_conf.path).parent
immutable_dir = base_dir / "immutable_files"
immutable_files = [
immutable_dir / Path(file).name for file in repo_conf.files.immutable
]
return [calc_sha256sum(file) for file in immutable_files]

View File

@ -0,0 +1,206 @@
import re
jon-lee marked this conversation as resolved

Path should not be relative to JOJ3_CONFIG_ROOT in this file, should be relative to task.toml dir

Path should not be relative to `JOJ3_CONFIG_ROOT` in this file, should be relative to `task.toml` dir

I reckon you said things is relative to JOJ3_CONFIG_ROOT in JTC before. we have a task.type in task.toml to mend the path

I reckon you said things is relative to `JOJ3_CONFIG_ROOT` in JTC before. we have a `task.type` in `task.toml` to mend the path

config.path is relative to JOJ3_CONFIG_ROOT.

`config.path` is relative to `JOJ3_CONFIG_ROOT`.

could you explain further? I m not quite sure my understanding is clear.

could you explain further? I m not quite sure my understanding is clear.

In joj3_config_generator/models/task.py, Config.path is relative to JOJ3_CONFIG_ROOT, so task.toml will located at JOJ3_CONFIG_ROOT / task_conf.path in JTC.

In `joj3_config_generator/models/task.py`, `Config.path` is relative to `JOJ3_CONFIG_ROOT`, so `task.toml` will located at `JOJ3_CONFIG_ROOT / task_conf.path` in JTC.
import shlex
jon-lee marked this conversation as resolved

Some with_.update is still using raw dict, not model with model_dump.

Some `with_.update` is still using raw dict, not model with `model_dump`.
from pathlib import Path
from typing import Any, Callable, Dict, List, Tuple
from joj3_config_generator.models import result, task
from joj3_config_generator.models.common import Memory, Time
from joj3_config_generator.models.const import JOJ3_CONFIG_ROOT
from joj3_config_generator.models.task import Parser as ParserEnum
jon-lee marked this conversation as resolved Outdated

ParserEnum

`ParserEnum`
def get_conf_stage(
task_conf: task.Config,
task_stage: task.Stage,
cached: Dict[str, None],
) -> result.StageDetail:
jon-lee marked this conversation as resolved Outdated

BTW, is this outdated?

BTW, is this outdated?

Never heard about this rule.

Never heard about this rule.

@manuel what would be the current intended rule for group?

@manuel what would be the current intended rule for `group`?

seems current strategy is fine, resolved.

seems current strategy is fine, resolved.
conf_stage = result.StageDetail(
name=task_stage.name,
# group is determined by adding between "[]" in the name of the task
group=(
match.group(1)
if (match := re.search(r"\[([^\[\]]+)\]", task_stage.name or ""))
else ""
),
executor=result.Executor(
name="sandbox",
with_=get_executor_with(task_stage, cached),
),
parsers=([result.Parser(name=parser) for parser in task_stage.parsers]),
)
processed_dict = get_processed_dict(task_stage)
for idx, parser in enumerate(task_stage.parsers):
if parser in processed_dict:
fn, parser_model = processed_dict[parser]
jon-lee marked this conversation as resolved Outdated

should loop through conf_stage.parsers here and update the with field according to the parser name.

should loop through `conf_stage.parsers` here and update the `with` field according to the parser name.

I think its already implemented in each of the fix_parsers functions

I think its already implemented in each of the `fix_parsers` functions

No, do not find the parser in the fix_xxx function. Instead, iterate through the parsers here and decide how to fill in the with.

No, do not find the parser in the `fix_xxx` function. Instead, iterate through the parsers here and decide how to fill in the `with`.

resolved.

resolved.

Use a dict to store parser name, field, function to process.

    process_dict: Dict[
        str, Tuple[Callable[[result.ParserConfig, BaseModel], None], BaseModel]
    ] = {
        "clangtidy": (fix_keyword, task_stage.clangtidy),
        "keyword": (fix_keyword, task_stage.keyword),
        "diff": (fix_diff, task_stage.diff),
    }
    for i, parser in enumerate(task_stage.parsers):
        if parser in process_dict:
            func, parser_model = process_dict[parser]
            func(conf_stage.parsers[i], parser_model)
Use a dict to store parser name, field, function to process. ``` process_dict: Dict[ str, Tuple[Callable[[result.ParserConfig, BaseModel], None], BaseModel] ] = { "clangtidy": (fix_keyword, task_stage.clangtidy), "keyword": (fix_keyword, task_stage.keyword), "diff": (fix_diff, task_stage.diff), } for i, parser in enumerate(task_stage.parsers): if parser in process_dict: func, parser_model = process_dict[parser] func(conf_stage.parsers[i], parser_model) ```

resolved.

resolved.
fn(parser_model, conf_stage.parsers[idx])
jon-lee marked this conversation as resolved Outdated

Do we need to support both kinds of names?

Do we need to support both kinds of names?

probably yes, since it is easy for new ta to type it wrong

probably yes, since it is easy for new ta to type it wrong

parsers name should be a str enum, force them to use the correct names

parsers name should be a str enum, force them to use the correct names

ok, then removed.

ok, then removed.
elif parser == ParserEnum.DIFF:
jon-lee marked this conversation as resolved Outdated

underscore

underscore

fixed

fixed
fix_diff(
task_stage,
conf_stage.parsers[idx],
conf_stage.executor,
JOJ3_CONFIG_ROOT / task_conf.path.parent,
)
else:
continue
return conf_stage
def get_processed_dict(
task_stage: task.Stage,
) -> Dict[ParserEnum, Tuple[Callable[[Any, result.Parser], None], Any]]:
processed_dict: Dict[
ParserEnum, Tuple[Callable[[Any, result.Parser], None], Any]
] = {
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),
}
return processed_dict
jon-lee marked this conversation as resolved Outdated

not necessary

not necessary

resolved.

resolved.
def get_executor_with(
task_stage: task.Stage, cached: Dict[str, None]
) -> result.ExecutorWith:
file_import = task_stage.files.import_
copy_in_files = (file for file in file_import if file not in cached)
file_export = task_stage.files.export
copy_out_files = ["stdout", "stderr"]
executor_with_config = result.ExecutorWith(
default=result.Cmd(
args=shlex.split(task_stage.command),
copy_in={
jon-lee marked this conversation as resolved Outdated

do not use getattr, visit the field explictly

do not use `getattr`, visit the field explictly

resolved.

resolved.
file: result.LocalFile(src=str(JOJ3_CONFIG_ROOT / file))
# all copyin files store in this tools folder
# TODO: are there any corner cases?
jon-lee marked this conversation as resolved Outdated

is there a conclusion now? or it should have a prefix TODO: ?

is there a conclusion now? or it should have a prefix `TODO: `?

so far works fine on 280 sides, indicating that pbs in 151 can be resolved with proper guidelines I think.

so far works fine on 280 sides, indicating that pbs in 151 can be resolved with proper guidelines I think.

ok, then add that prefix

ok, then add that prefix
for file in copy_in_files
jon-lee marked this conversation as resolved Outdated

is it in the correct unit?
Give all the fields in Limit a default value and make it non optional in task_stage.limit

is it in the correct unit? Give all the fields in `Limit` a default value and make it non optional in task_stage.limit

resolved

resolved
},
copy_out=copy_out_files,
copy_in_cached={file: file for file in cached},
copy_out_cached=file_export,
cpu_limit=Time(task_stage.limit.cpu),
clock_limit=2 * Time(task_stage.limit.cpu),
memory_limit=Memory(task_stage.limit.mem),
stderr=result.Collector(
name="stderr", pipe=True, max=Memory(task_stage.limit.stderr)
),
stdout=result.Collector(
name="stdout", pipe=True, max=Memory(task_stage.limit.stdout)
),
),
jon-lee marked this conversation as resolved Outdated

The reason for the suffix in keyword_parser_?

The reason for the suffix in `keyword_parser_`?

just forgot to remove, sorry

just forgot to remove, sorry
cases=[],
)
for file in file_export:
cached[file] = None
return executor_with_config
def fix_keyword(
keyword_config: task.ParserKeyword, keyword_parser: result.Parser
) -> None:
if len(keyword_config.keyword) != len(keyword_config.weight):
raise ValueError("Keywords and weights must have the same length")
score_groups: Dict[int, List[str]] = {}
for keyword, score in zip(keyword_config.keyword, keyword_config.weight):
score_groups.setdefault(score, []).append(keyword)
keyword_parser.with_ = result.KeywordMatchConfig(
matches=[
result.KeywordConfig(keywords=keywords, score=score)
for score, keywords in score_groups.items()
]
)
def fix_result_detail(
result_detail_parser_config: task.ParserResultDetail,
result_detail_parser: result.Parser,
) -> None:
show_files = []
if result_detail_parser_config.stdout:
show_files.append("stdout")
if result_detail_parser_config.stderr:
show_files.append("stderr")
result_detail_parser.with_ = result.ResultDetailConfig(
score=0,
comment="",
jon-lee marked this conversation as resolved Outdated
    if task_stage.parsers is not None:
        for parser in task_stage.parsers:

->

for parser in task_stage.parsers or []:
``` if task_stage.parsers is not None: for parser in task_stage.parsers: ``` -> ``` for parser in task_stage.parsers or []: ```

resolved.

resolved.
show_files=show_files,
show_exit_status=result_detail_parser_config.exit_status,
show_runtime=result_detail_parser_config.time,
show_memory=result_detail_parser_config.mem,
)
def fix_dummy(
dummy_parser_config: task.ParserDummy, dummy_parser: result.Parser
) -> None:
# we don't use dummy parser in real application
jon-lee marked this conversation as resolved Outdated

When will it be None?

When will it be None?
dummy_parser.with_ = result.DummyConfig(
score=dummy_parser_config.score,
comment=dummy_parser_config.comment,
force_quit_on_not_accepted=dummy_parser_config.force_quit,
)
def fix_file(file_parser_config: task.ParserFile, file_parser: result.Parser) -> None:
file_parser.with_ = result.FileConfig(name=file_parser_config.name)
def fix_diff(
task_stage: task.Stage,
diff_parser_config: result.Parser,
diff_executor: result.Executor,
bomingzh marked this conversation as resolved Outdated

these fields do not exist now

these fields do not exist now

resolved

resolved
No description provided.
base_dir: Path,
) -> None:
skip = task_stage.skip
cases = task_stage.cases
finalized_cases = [case for case in cases if case not in skip]
jon-lee marked this conversation as resolved Outdated

move continue to the other branch to reduce nesting

move `continue` to the other branch to reduce nesting

I mean

if parser not in keyword_parser:
    continue
if getattr(task_stage, parser, None) is None:
    continue
if score != score_:
    continue
I mean ``` if parser not in keyword_parser: continue ``` ``` if getattr(task_stage, parser, None) is None: continue ```` ``` if score != score_: continue ````

fixed.

fixed.
stage_cases = []
parser_cases = []
for case in finalized_cases:
case_stage = task_stage.cases.get(case) if task_stage.cases else None
jon-lee marked this conversation as resolved Outdated

Is it necessary to rename?

Is it necessary to rename?
if not case_stage:
continue
cpu_limit = case_stage.limit.cpu
clock_limit = 2 * case_stage.limit.cpu
memory_limit = case_stage.limit.mem
command = case_stage.command
stdin = case_stage.in_ if case_stage.in_ != "" else f"{case}.in"
stdout = case_stage.out_ if case_stage.out_ != "" else f"{case}.out"
jon-lee marked this conversation as resolved Outdated

just if task_stage.result_detail.stdout:

just `if task_stage.result_detail.stdout:`
stage_cases.append(
result.OptionalCmd(
stdin=result.LocalFile(
src=str(base_dir / stdin),
),
args=shlex.split(command) if command else None,
cpu_limit=cpu_limit,
clock_limit=clock_limit,
memory_limit=memory_limit,
proc_limit=50,
)
)
diff_output = case_stage.diff.output
jon-lee marked this conversation as resolved Outdated

Pass JOJ3_CONFIG_ROOT / task_conf.path.parent as base_dir in parameters.

Pass `JOJ3_CONFIG_ROOT / task_conf.path.parent` as `base_dir` in parameters.

resolved.

resolved.
parser_cases.append(
result.DiffCasesConfig(
outputs=[
result.DiffOutputConfig(
jon-lee marked this conversation as resolved Outdated

will it be None now?

will it be None now?

nope 😂 bad coding strategy before

nope 😂 bad coding strategy before
score=diff_output.score,
file_name="stdout",
answer_path=str(base_dir / stdout),
force_quit_on_diff=diff_output.force_quit,
always_hide=diff_output.hide,
compare_space=not diff_output.ignore_spaces,
)
]
)
)
diff_executor.with_.cases = stage_cases
diff_parser_config.with_ = result.DiffConfig(name="diff", cases=parser_cases)

View File

@ -2,7 +2,7 @@ import logging
import sys
from sys import stderr
from types import FrameType
from typing import Optional
from typing import Optional, Union
from loguru import logger as logger
@ -10,7 +10,7 @@ from loguru import logger as logger
# recipe from https://loguru.readthedocs.io/en/stable/overview.html#entirely-compatible-with-standard-logging
class InterceptHandler(logging.Handler):
def emit(self, record: logging.LogRecord) -> None:
level: str | int
level: Union[str, int]
try:
level = logger.level(record.levelname).name
except ValueError:

View File

@ -12,6 +12,7 @@ dependencies = [
"pydantic>=2.9.2",
"inquirer>=3.4.0",
"rtoml>=0.11.0",
"humanfriendly>=10.0",
]
requires-python = ">=3.9"
authors = [{ name = "JOJ3-dev", email = "joj3@focs.ji.sjtu.edu.cn" }]

View File

@ -0,0 +1,33 @@
*.avi filter=lfs diff=lfs merge=lfs -text
*.bz2 filter=lfs diff=lfs merge=lfs -text
*.djvu filter=lfs diff=lfs merge=lfs -text
*.doc filter=lfs diff=lfs merge=lfs -text
*.docx filter=lfs diff=lfs merge=lfs -text
*.epub filter=lfs diff=lfs merge=lfs -text
*.gz filter=lfs diff=lfs merge=lfs -text
*.ipynb filter=lfs diff=lfs merge=lfs -text
*.jpeg filter=lfs diff=lfs merge=lfs -text
*.JPEG filter=lfs diff=lfs merge=lfs -text
*.jpg filter=lfs diff=lfs merge=lfs -text
*.JPG filter=lfs diff=lfs merge=lfs -text
*.mkv filter=lfs diff=lfs merge=lfs -text
*.mp4 filter=lfs diff=lfs merge=lfs -text
*.ods filter=lfs diff=lfs merge=lfs -text
*.odt filter=lfs diff=lfs merge=lfs -text
*.otf filter=lfs diff=lfs merge=lfs -text
*.pdf filter=lfs diff=lfs merge=lfs -text
*.PDF filter=lfs diff=lfs merge=lfs -text
*.png filter=lfs diff=lfs merge=lfs -text
*.PNG filter=lfs diff=lfs merge=lfs -text
*.ppt filter=lfs diff=lfs merge=lfs -text
*.pptx filter=lfs diff=lfs merge=lfs -text
*.ps filter=lfs diff=lfs merge=lfs -text
*.rar filter=lfs diff=lfs merge=lfs -text
*.tar filter=lfs diff=lfs merge=lfs -text
*.tgz filter=lfs diff=lfs merge=lfs -text
*.ttf filter=lfs diff=lfs merge=lfs -text
*.webm filter=lfs diff=lfs merge=lfs -text
*.xls filter=lfs diff=lfs merge=lfs -text
*.xlsx filter=lfs diff=lfs merge=lfs -text
*.xz filter=lfs diff=lfs merge=lfs -text
*.zip filter=lfs diff=lfs merge=lfs -text

View File

@ -0,0 +1,23 @@
################################
## White list based gitignore ##
################################
# forbidden
*
.*
# allowed
!.gitignore
!.gitattributes
!.gitea/
!.gitea/issue_template/
!.gitea/workflows/
!*.yaml
!Makefile
!CMakeLists.txt
!h[0-8]/
!*.m
!*.c
!*.cpp
!*.h
!*.md

View File

@ -0,0 +1,19 @@
name: Run JOJ3 on Push
on: [push]
jobs:
run:
container:
runs-on: focs-latest-slim
volumes:
- /home/tt/.config:/home/tt/.config
- /home/tt/.cache:/home/tt/.cache
- /home/tt/.ssh:/home/tt/.ssh
steps:
- name: Check out repository code
uses: actions/checkout@focs
with:
fetch-depth: 0
- name: Run joj3
run: |
sudo -E -u tt joj3 -conf-root /home/tt/.config/joj/tests/homework

View File

@ -0,0 +1,21 @@
name: Run JOJ3 on Release
on:
release:
types: [published]
jobs:
run:
runs-on: focs-latest-slim
container:
volumes:
- /home/tt/.config:/home/tt/.config
- /home/tt/.cache:/home/tt/.cache
- /home/tt/.ssh:/home/tt/.ssh
steps:
- name: Check out repository code
uses: actions/checkout@focs
with:
fetch-depth: 0
- name: Run joj3
run: |
sudo -E -u tt joj3 -conf-root "/home/tt/.config/joj/tests/homework" -conf-name "conf-release.json" -tag "${{ github.ref_name }}"

View File

@ -1,10 +1,16 @@
teaching_team = ["prof_john", "ta_alice", "ta_bob"]
max_size = 50.5
release_tags = ["v1.0", "v2.0", "final"]
grading_repo_name = "ece280-joj"
sandbox_token = "test"
# reconfigure later
max_total_score = 100
max_size = 50.5
# for tests
[groups]
name = ["joj", "run"]
max_count = [1000, 1000]
time_period_hour = [24, 24]
[files]
whitelist_patterns = ["*.py", "*.txt", "*.md"]
whitelist_file = ".whitelist"
required = ["main.py", "README.md"]
immutable = ["config.yaml", "setup.py"]
required = ["README.md", "Changelog.md"]
immutable = [".gitignore", ".gitattributes",".gitea/workflows/push.yaml", ".gitea/workflows/release.yaml"]

View File

@ -1,47 +1,52 @@
{
"name": "hw3 ex5",
"logPath": "hw3_ex5.log",
"expireUnixTimestamp": 1729267140,
"name": "hw7 ex2",
"logPath": "/home/tt/.cache/joj3/homework/h7/e2.log",
"expireUnixTimestamp": 1735574399,
"effectiveUnixTimestamp": 1735487999,
"actorCsvPath": "/home/tt/.config/joj/students.csv",
"maxTotalScore": 100,
"stage": {
"sandboxExecServer": "172.17.0.1:5051",
"sandboxToken": "test",
"outputPath": "/tmp/joj3_result.json",
"stages": [
{
"name": "judge_base",
"group": "hw3 ex5",
"name": "Health Check",
"group": "",
"executor": {
"name": "sandbox",
"name": "local",
"with": {
"default": {
"args": [
"./matlab-joj",
"./h3/ex5.m"
],
"args": [],
"env": [],
"cpuLimit": 0,
"realCpuLimit": 0,
"clockLimit": 0,
"memoryLimit": 0,
"stdin": {
"content": ""
},
"stdout": {
"name": "stdout",
"max": 33554432,
"pipe": true
},
"stderr": {
"name": "stderr",
"max": 33554432,
"pipe": true
},
"cpuLimit": 1000000000,
"clockLimit": 2000000000,
"memoryLimit": 134217728,
"stackLimit": 0,
"procLimit": 0,
"procLimit": 50,
"cpuRateLimit": 0,
"cpuSetLimit": "",
"copyIn": {
"tools/matlab-joj": {
"src": "tools/matlab-joj"
},
"tools/matlab_formatter.py": {
"src": "tools/matlab_formatter.py"
}
},
"copyIn": {},
"copyInCached": {},
"copyInDir": ".",
"copyOut": [],
"copyOutCached": [
"output/ex5_results.txt",
"output/ex5_logs.txt"
"copyOut": [
"stdout",
"stderr"
],
"copyOutCached": [],
"copyOutMax": 0,
"copyOutDir": "",
"tty": false,
@ -49,59 +54,98 @@
"dataSegmentLimit": false,
"addressSpaceLimit": false
},
"cases": []
"cases": [
{
"args": [
"/usr/local/bin/repo-health-checker",
"-root=.",
"-repoSize=50.5",
"-meta=README.md",
"-meta=Changelog.md",
"-checkFileSumList=a5b63323a692d3d8b952442969649b4f823d58dae26429494f613df160710dfc,b1bbad25b830db0a77b15a033f9ca1b7ab44c1d2d05056412bd3e4421645f0bf,2ba059f3977e2e3dee6cacbfbf0ba2578baa1b8e04b4977aec400868b6e49856,bf7d181362affdcc72aac33f3520e4e6371adc48ea62a6f24524b4a3e76724c5",
"-checkFileNameList=.gitignore,.gitattributes,.gitea/workflows/push.yaml,.gitea/workflows/release.yaml"
]
},
{
"args": [
"/usr/local/bin/joint-teapot",
"joj3-check-env",
"/home/tt/.config/teapot/teapot.env",
"--grading-repo-name",
"ece280-joj",
"--group-config",
"joj=1000:24,run=1000:24"
],
"env": [
"LOG_FILE_PATH=/home/tt/.cache/joint-teapot-debug.log"
]
}
]
}
},
"parsers": [
{
"name": "diff",
"with": {}
"name": "healthcheck",
"with": {
"score": 1
}
},
{
"name": "result-detail",
"name": "debug",
"with": {
"time": false,
"mem": false,
"stdout": false,
"stderr": true
"score": 0
}
}
]
},
{
"name": "judge_base2",
"group": "hw3 ex5",
"name": "Compilation",
"group": "",
"executor": {
"name": "sandbox",
"with": {
"default": {
"args": [
"./matlab-joj",
"./h3/ex5.m"
"./tools/compile"
],
"env": [],
"cpuLimit": 0,
"realCpuLimit": 0,
"clockLimit": 0,
"memoryLimit": 0,
"stdin": {
"content": ""
},
"stdout": {
"name": "stdout",
"max": 33554432,
"pipe": true
},
"stderr": {
"name": "stderr",
"max": 33554432,
"pipe": true
},
"cpuLimit": 1000000000,
"clockLimit": 2000000000,
"memoryLimit": 134217728,
"stackLimit": 0,
"procLimit": 0,
"procLimit": 50,
"cpuRateLimit": 0,
"cpuSetLimit": "",
"copyIn": {
"tools/matlab-joj": {
"src": "tools/matlab-joj"
},
"tools/matlab_formatter.py": {
"src": "tools/matlab_formatter.py"
"tools/compile": {
"src": "/home/tt/.config/joj/tools/compile"
}
},
"copyInCached": {},
"copyInDir": ".",
"copyOut": [],
"copyOut": [
"stdout",
"stderr"
],
"copyOutCached": [
"output/ex5_results2.txt",
"output/ex5_logs2.txt"
"h7/build/ex2",
"h7/build/ex2-asan",
"h7/build/ex2-ubsan",
"h7/build/ex2-msan",
"h7/build/compile_commands.json"
],
"copyOutMax": 0,
"copyOutDir": "",
@ -115,29 +159,691 @@
},
"parsers": [
{
"name": "diff",
"with": {}
"name": "result-detail",
"with": {
"score": 0,
"comment": "",
"showFiles": [
"stderr"
],
"showExitStatus": true,
"showRuntime": false,
"showMemory": false
}
},
{
"name": "result-status",
"with": {
"score": 0,
"comment": "Congratulations! Your code compiled successfully.",
"forceQuitOnNotAccepted": true
}
}
]
},
{
"name": "[cq] Filelength",
"group": "cq",
"executor": {
"name": "sandbox",
"with": {
"default": {
"args": [
"./tools/filelength",
"400",
"300",
"*.cpp",
"*.h"
],
"env": [],
"stdin": {
"content": ""
},
"stdout": {
"name": "stdout",
"max": 33554432,
"pipe": true
},
"stderr": {
"name": "stderr",
"max": 33554432,
"pipe": true
},
"cpuLimit": 1000000000,
"clockLimit": 2000000000,
"memoryLimit": 134217728,
"stackLimit": 0,
"procLimit": 50,
"cpuRateLimit": 0,
"cpuSetLimit": "",
"copyIn": {
"tools/filelength": {
"src": "/home/tt/.config/joj/tools/filelength"
}
},
"copyInCached": {
"h7/build/ex2": "h7/build/ex2",
"h7/build/ex2-asan": "h7/build/ex2-asan",
"h7/build/ex2-ubsan": "h7/build/ex2-ubsan",
"h7/build/ex2-msan": "h7/build/ex2-msan",
"h7/build/compile_commands.json": "h7/build/compile_commands.json"
},
"copyInDir": ".",
"copyOut": [
"stdout",
"stderr"
],
"copyOutCached": [],
"copyOutMax": 0,
"copyOutDir": "",
"tty": false,
"strictMemoryLimit": false,
"dataSegmentLimit": false,
"addressSpaceLimit": false
},
"cases": []
}
},
"parsers": [
{
"name": "keyword",
"with": {
"matches": [
{
"keywords": [
"max"
],
"score": 20
},
{
"keywords": [
"recommended"
],
"score": 10
}
]
}
},
{
"name": "result-detail",
"with": {
"time": true,
"mem": true,
"stdout": false,
"stderr": false
"score": 0,
"comment": "",
"showFiles": [
"stdout"
],
"showExitStatus": true,
"showRuntime": false,
"showMemory": false
}
}
]
},
{
"name": "[cq] Clang-tidy",
"group": "cq",
"executor": {
"name": "sandbox",
"with": {
"default": {
"args": [
"run-clang-tidy-18",
"-header-filter=.*",
"-quiet",
"-load=/usr/local/lib/libcodequality.so",
"-p",
"h7/build",
"h7/ex2.cpp"
],
"env": [],
"stdin": {
"content": ""
},
"stdout": {
"name": "stdout",
"max": 4194304,
"pipe": true
},
"stderr": {
"name": "stderr",
"max": 33554432,
"pipe": true
},
"cpuLimit": 1000000000,
"clockLimit": 2000000000,
"memoryLimit": 134217728,
"stackLimit": 0,
"procLimit": 50,
"cpuRateLimit": 0,
"cpuSetLimit": "",
"copyIn": {
"tests/homework/h7/.clang-tidy": {
"src": "/home/tt/.config/joj/tests/homework/h7/.clang-tidy"
}
},
"copyInCached": {
"h7/build/ex2": "h7/build/ex2",
"h7/build/ex2-asan": "h7/build/ex2-asan",
"h7/build/ex2-ubsan": "h7/build/ex2-ubsan",
"h7/build/ex2-msan": "h7/build/ex2-msan",
"h7/build/compile_commands.json": "h7/build/compile_commands.json"
},
"copyInDir": ".",
"copyOut": [
"stdout",
"stderr"
],
"copyOutCached": [],
"copyOutMax": 0,
"copyOutDir": "",
"tty": false,
"strictMemoryLimit": false,
"dataSegmentLimit": false,
"addressSpaceLimit": false
},
"cases": []
}
},
"parsers": [
{
"name": "clangtidy",
"with": {
"matches": [
{
"keywords": [
"codequality-unchecked-malloc-result",
"readability-duplicate-include",
"readability-identifier-naming",
"readability-redundant",
"readability-misplaced-array-index",
"cppcoreguidelines-init-variables",
"bugprone-suspicious-string-compare",
"google-global-names-in-headers",
"clang-diagnostic",
"clang-analyzer",
"misc",
"performance",
"portability"
],
"score": 5
},
{
"keywords": [
"codequality-no-global-variables",
"codequality-no-header-guard",
"codequality-no-fflush-stdin"
],
"score": 20
},
{
"keywords": [
"readability-function-size"
],
"score": 10
},
{
"keywords": [
"readability-misleading-indentation"
],
"score": 15
}
]
}
},
{
"name": "result-detail",
"with": {
"score": 0,
"comment": "",
"showFiles": [
"stdout"
],
"showExitStatus": true,
"showRuntime": false,
"showMemory": false
}
}
]
},
{
"name": "[cq] Cppcheck",
"group": "cq",
"executor": {
"name": "sandbox",
"with": {
"default": {
"args": [
"cppcheck",
"--template={\"file\":\"{file}\",\"line\":{line}, \"column\":{column}, \"severity\":\"{severity}\", \"message\":\"{message}\", \"id\":\"{id}\"}",
"--force",
"--enable=all",
"--suppress=missingIncludeSystem",
"--quiet",
"h7/ex2.cpp"
],
"env": [],
"stdin": {
"content": ""
},
"stdout": {
"name": "stdout",
"max": 33554432,
"pipe": true
},
"stderr": {
"name": "stderr",
"max": 8388608,
"pipe": true
},
"cpuLimit": 1000000000,
"clockLimit": 2000000000,
"memoryLimit": 134217728,
"stackLimit": 0,
"procLimit": 50,
"cpuRateLimit": 0,
"cpuSetLimit": "",
"copyIn": {},
"copyInCached": {
"h7/build/ex2": "h7/build/ex2",
"h7/build/ex2-asan": "h7/build/ex2-asan",
"h7/build/ex2-ubsan": "h7/build/ex2-ubsan",
"h7/build/ex2-msan": "h7/build/ex2-msan",
"h7/build/compile_commands.json": "h7/build/compile_commands.json"
},
"copyInDir": ".",
"copyOut": [
"stdout",
"stderr"
],
"copyOutCached": [],
"copyOutMax": 0,
"copyOutDir": "",
"tty": false,
"strictMemoryLimit": false,
"dataSegmentLimit": false,
"addressSpaceLimit": false
},
"cases": []
}
},
"parsers": [
{
"name": "keyword",
"with": {
"matches": []
}
},
{
"name": "cppcheck",
"with": {
"matches": [
{
"keywords": [
"error"
],
"score": 15
},
{
"keywords": [
"warning",
"portability",
"performance",
"style"
],
"score": 5
}
]
}
},
{
"name": "clangtidy",
"with": {
"matches": []
}
},
{
"name": "result-detail",
"with": {
"score": 0,
"comment": "",
"showFiles": [
"stderr"
],
"showExitStatus": true,
"showRuntime": false,
"showMemory": false
}
},
{
"name": "cpplint",
"with": {
"matches": []
}
},
{
"name": "result-status",
"with": {
"score": 0,
"comment": "",
"forceQuitOnNotAccepted": false
}
},
{
"name": "file",
"with": {
"name": ""
}
},
{
"name": "dummy",
"with": {
"score": 0,
"comment": "",
"forceQuitOnNotAccepted": false
}
},
{
"name": "diff",
"with": {
"name": "diff",
"cases": []
}
}
]
},
{
"name": "[cq] Cpplint",
"group": "cq",
"executor": {
"name": "sandbox",
"with": {
"default": {
"args": [
"cpplint",
"--linelength=120",
"--filter=-legal,-readability/casting,-whitespace,-runtime/printf,-runtime/threadsafe_fn,-runtime/int,-readability/todo,-build/include_subdir,-build/header_guard,-build/include_what_you_use",
"--recursive",
"--exclude=build",
"h7/ex2.cpp"
],
"env": [],
"stdin": {
"content": ""
},
"stdout": {
"name": "stdout",
"max": 68157440,
"pipe": true
},
"stderr": {
"name": "stderr",
"max": 33554432,
"pipe": true
},
"cpuLimit": 1000000000,
"clockLimit": 2000000000,
"memoryLimit": 134217728,
"stackLimit": 0,
"procLimit": 50,
"cpuRateLimit": 0,
"cpuSetLimit": "",
"copyIn": {},
"copyInCached": {
"h7/build/ex2": "h7/build/ex2",
"h7/build/ex2-asan": "h7/build/ex2-asan",
"h7/build/ex2-ubsan": "h7/build/ex2-ubsan",
"h7/build/ex2-msan": "h7/build/ex2-msan",
"h7/build/compile_commands.json": "h7/build/compile_commands.json"
},
"copyInDir": ".",
"copyOut": [
"stdout",
"stderr"
],
"copyOutCached": [],
"copyOutMax": 0,
"copyOutDir": "",
"tty": false,
"strictMemoryLimit": false,
"dataSegmentLimit": false,
"addressSpaceLimit": false
},
"cases": []
}
},
"parsers": [
{
"name": "cpplint",
"with": {
"matches": [
{
"keywords": [
"runtime"
],
"score": 5
},
{
"keywords": [
"readability"
],
"score": 20
},
{
"keywords": [
"build"
],
"score": 10
}
]
}
},
{
"name": "result-detail",
"with": {
"score": 0,
"comment": "",
"showFiles": [
"stderr"
],
"showExitStatus": true,
"showRuntime": false,
"showMemory": false
}
}
]
},
{
"name": "[joj] ex2-asan",
"group": "joj",
"executor": {
"name": "sandbox",
"with": {
"default": {
"args": [
"./h7/build/ex2-asan",
"-a"
],
"env": [],
"stdin": {
"content": ""
},
"stdout": {
"name": "stdout",
"max": 33554432,
"pipe": true
},
"stderr": {
"name": "stderr",
"max": 33554432,
"pipe": true
},
"cpuLimit": 1000000000,
"clockLimit": 2000000000,
"memoryLimit": 134217728,
"stackLimit": 0,
"procLimit": 50,
"cpuRateLimit": 0,
"cpuSetLimit": "",
"copyIn": {},
"copyInCached": {
"h7/build/ex2": "h7/build/ex2",
"h7/build/ex2-asan": "h7/build/ex2-asan",
"h7/build/ex2-ubsan": "h7/build/ex2-ubsan",
"h7/build/ex2-msan": "h7/build/ex2-msan",
"h7/build/compile_commands.json": "h7/build/compile_commands.json"
},
"copyInDir": ".",
"copyOut": [
"stdout",
"stderr"
],
"copyOutCached": [],
"copyOutMax": 0,
"copyOutDir": "",
"tty": false,
"strictMemoryLimit": false,
"dataSegmentLimit": false,
"addressSpaceLimit": false
},
"cases": [
{
"stdin": {
"src": "/home/tt/.config/joj/basic/case0.in"
},
"cpuLimit": 500000000,
"clockLimit": 1000000000,
"memoryLimit": 5242880,
"procLimit": 50
},
{
"stdin": {
"src": "/home/tt/.config/joj/basic/case1.in"
},
"cpuLimit": 1000000000,
"clockLimit": 2000000000,
"memoryLimit": 5242880,
"procLimit": 50
}
]
}
},
"parsers": [
{
"name": "diff",
"with": {
"name": "diff",
"cases": [
{
"outputs": [
{
"score": 5,
"fileName": "stdout",
"answerPath": "/home/tt/.config/joj/basic/case0.out",
"forceQuitOnDiff": false,
"alwaysHide": false,
"compareSpace": false
}
]
},
{
"outputs": [
{
"score": 5,
"fileName": "stdout",
"answerPath": "/home/tt/.config/joj/basic/case1.out",
"forceQuitOnDiff": false,
"alwaysHide": false,
"compareSpace": false
}
]
}
]
}
},
{
"name": "result-detail",
"with": {
"score": 0,
"comment": "",
"showFiles": [
"stderr"
],
"showExitStatus": true,
"showRuntime": true,
"showMemory": true
}
}
]
}
],
"preStages": [],
"postStages": [
{
"name": "teapot",
"group": "",
"executor": {
"name": "local",
"with": {
"default": {
"args": [
"/usr/local/bin/joint-teapot",
"joj3-all-env",
"/home/tt/.config/teapot/teapot.env",
"--grading-repo-name",
"ece280-joj",
"--max-total-score",
"100"
],
"env": [
"LOG_FILE_PATH=/home/tt/.cache/joint-teapot-debug.log"
],
"stdin": {
"content": ""
},
"stdout": {
"name": "stdout",
"max": 33554432,
"pipe": true
},
"stderr": {
"name": "stderr",
"max": 33554432,
"pipe": true
},
"cpuLimit": 1000000000,
"clockLimit": 2000000000,
"memoryLimit": 134217728,
"stackLimit": 0,
"procLimit": 50,
"cpuRateLimit": 0,
"cpuSetLimit": "",
"copyIn": {},
"copyInCached": {},
"copyInDir": ".",
"copyOut": [
"stdout",
"stderr"
],
"copyOutCached": [],
"copyOutMax": 0,
"copyOutDir": "",
"tty": false,
"strictMemoryLimit": false,
"dataSegmentLimit": false,
"addressSpaceLimit": false
},
"cases": []
}
},
"parsers": [
{
"name": "log",
"with": {
"msg": "joj3 summary"
}
}
]
}
]
},
"teapot": {
"logPath": "/home/tt/.cache/joint-teapot-debug.log",
"scoreboardPath": "scoreboard.csv",
"failedTablePath": "failed-table.md",
"gradingRepoName": "",
"skipIssue": false,
"skipScoreboard": false,
"skipFailedTable": false
}
}

View File

@ -1,30 +1,102 @@
task = "hw3 ex5"
# general task configuration
task.name = "hw7 ex2" # task name
task.type = "homework/h7/e2" # remove this task type later
[release]
deadline = "2024-10-18T23:59:00+08:00"
release.end_time = 2024-12-30 23:59:59+08:00
release.begin_time = 2024-12-29 23:59:59+08:00
[[stages]]
name = "judge_base"
command = "./matlab-joj ./h3/ex5.m"
score = 100
parsers = ["diff", "result-detail"]
name = "Compilation"
command = "./tools/compile" # eg. script running cmake commands
files.import = [ "tools/compile" ]
files.export = [ "h7/build/ex2", "h7/build/ex2-asan", "h7/build/ex2-ubsan", "h7/build/ex2-msan", "h7/build/compile_commands.json" ]
score = 1
files.import = ["tools/matlab-joj", "tools/matlab_formatter.py"]
files.export = ["output/ex5_results.txt", "output/ex5_logs.txt"]
result_detail.time = false
result_detail.mem = false
result_detail.stderr = true
# compile parsers
parsers = [ "result-detail", "result-status" ]
result-status.comment = "Congratulations! Your code compiled successfully."
result-detail.exitstatus = true
result-detail.stderr = true
result-detail.time = false
result-detail.mem = false
result-status.force_quit = true
[[stages]]
name = "judge_base2"
command = "./matlab-joj ./h3/ex5.m"
score = 80
parsers = ["diff", "result-detail"]
name = "[cq] Filelength"
command = "./tools/filelength 400 300 *.cpp *.h"
files.import = [ "tools/filelength" ]
files.import = ["tools/matlab-joj", "tools/matlab_formatter.py"]
files.export = ["output/ex5_results2.txt", "output/ex5_logs2.txt"]
parsers = [ "keyword", "result-detail" ]
keyword.keyword = [ "max", "recommended"]
keyword.weight = [ 20, 10 ]
result-detail.exitstatus = true
result-detail.stdout = true
result-detail.time = false
result-detail.mem = false
result_detail.time = true
result_detail.mem = true
result_detail.stderr = false
[[stages]]
name = "[cq] Clang-tidy"
command = "run-clang-tidy-18 -header-filter=.* -quiet -load=/usr/local/lib/libcodequality.so -p h7/build h7/ex2.cpp"
files.import = [ "tests/homework/h7/.clang-tidy", "h7/build/compile_commands.json" ]
limit.stdout = "4m"
parsers = [ "clangtidy", "result-detail" ]
clangtidy.keyword = [ "codequality-unchecked-malloc-result", "codequality-no-global-variables", "codequality-no-header-guard", "codequality-no-fflush-stdin", "readability-function-size", "readability-duplicate-include", "readability-identifier-naming", "readability-redundant", "readability-misleading-indentation", "readability-misplaced-array-index", "cppcoreguidelines-init-variables", "bugprone-suspicious-string-compare", "google-global-names-in-headers", "clang-diagnostic", "clang-analyzer", "misc", "performance", "portability" ]
clangtidy.weight = [ 5, 20, 20, 20, 10, 5, 5, 5, 15, 5, 5, 5, 5, 5, 5, 5, 5, 5]
result-detail.exitstatus = true
result-detail.stdout = true
result-detail.time = false
result-detail.mem = false
[[stages]]
name = "[cq] Cppcheck"
command = "cppcheck --template='{\"file\":\"{file}\",\"line\":{line}, \"column\":{column}, \"severity\":\"{severity}\", \"message\":\"{message}\", \"id\":\"{id}\"}' --force --enable=all --suppress=missingIncludeSystem --quiet h7/ex2.cpp"
limit.stderr = "8m"
parsers = [ "keyword", "cppcheck", "clangtidy", "result-detail", "cpplint", "result-status", "file", "dummy", "diff" ]
cppcheck.keyword = ["error", "warning", "portability", "performance", "style"]
cppcheck.weight = [15, 5, 5, 5, 5]
result-detail.exitstatus = true
result-detail.stderr = true
result-detail.time = false
result-detail.mem = false
[[stages]]
name = "[cq] Cpplint"
command = "cpplint --linelength=120 --filter=-legal,-readability/casting,-whitespace,-runtime/printf,-runtime/threadsafe_fn,-runtime/int,-readability/todo,-build/include_subdir,-build/header_guard,-build/include_what_you_use --recursive --exclude=build h7/ex2.cpp"
limit.stdout = "65m"
jon-lee marked this conversation as resolved Outdated

is it used?

is it used?

testcases in basic is intend to show a comprehensive situation.

testcases in basic is intend to show a comprehensive situation.

if unused, leave a comment

if unused, leave a comment

Then why is there a stdout field in class Limit

Then why is there a `stdout` field in `class Limit`

@bomingzh in toml now should we accept both 32m and 32 or just 32m now?

@bomingzh in toml now should we accept both `32m` and `32` or just `32m` now?

I reckon only 32m would be good

I reckon only `32m` would be good

yes, just str

yes, just str
parsers = [ "cpplint", "result-detail" ]
cpplint.keyword = [ "runtime", "readability", "build" ]
cpplint.weight = [ 5, 20, 10]
result-detail.exitstatus = true
result-detail.stderr = true
result-detail.time = false
result-detail.mem = false
[[stages]]
name = "[joj] ex2-asan"
command="./h7/build/ex2-asan -a"
files.import = [ "h7/build/ex2-asan" ]
limit.mem = "128m"
parsers = [ "diff", "result-detail" ]
result-detail.exitstatus = true
result-detail.stderr = true
# will be removed as long as the name is fixed
case0.diff.output.score = 5
case0.limit.cpu = "0.5s"
case0.limit.mem = "5m"
case0.diff.output.ignore_spaces = true
#case0.limit.stdout = 8
#case0.command = "./h7/build/ex2"
case0.in = "case0.in"
case1.diff.output.score = 5
case1.limit.cpu = "1s"
case1.limit.mem = "5m"
case1.diff.output.ignore_spaces = true
#case1.limit.stdout = 8
#case1.command = "./h7/build/ex2"
case1.in = "case1.in"

View File

@ -0,0 +1,2 @@
force_skip_health_check_on_test = true
force_skip_teapot_on_test = true

View File

@ -0,0 +1,140 @@
{
"name": "hw7 ex2",
"logPath": "/home/tt/.cache/joj3/homework/h7/e2.log",
"expireUnixTimestamp": 1735574399,
"effectiveUnixTimestamp": 1735487999,
"actorCsvPath": "/home/tt/.config/joj/students.csv",
"maxTotalScore": 100,
"stage": {
"sandboxExecServer": "172.17.0.1:5051",
"sandboxToken": "",
"outputPath": "/tmp/joj3_result.json",
"stages": [
{
"name": "[cq] Clang-tidy",
"group": "cq",
"executor": {
"name": "sandbox",
"with": {
"default": {
"args": [
"run-clang-tidy-18",
"-header-filter=.*",
"-quiet",
"-load=/usr/local/lib/libcodequality.so",
"-p",
"h7/build",
"h7/ex2.cpp"
],
"env": [],
"stdin": {
"content": ""
},
"stdout": {
"name": "stdout",
"max": 68157440,
"pipe": true
},
"stderr": {
"name": "stderr",
"max": 33554432,
"pipe": true
},
"cpuLimit": 1000000000,
"clockLimit": 2000000000,
"memoryLimit": 134217728,
"stackLimit": 0,
"procLimit": 50,
"cpuRateLimit": 0,
"cpuSetLimit": "",
"copyIn": {
"tests/homework/h7/.clang-tidy": {
"src": "/home/tt/.config/joj/tests/homework/h7/.clang-tidy"
},
"h7/build/compile_commands.json": {
"src": "/home/tt/.config/joj/h7/build/compile_commands.json"
}
},
"copyInCached": {},
"copyInDir": ".",
"copyOut": [
"stdout",
"stderr"
],
"copyOutCached": [],
"copyOutMax": 0,
"copyOutDir": "",
"tty": false,
"strictMemoryLimit": false,
"dataSegmentLimit": false,
"addressSpaceLimit": false
},
"cases": []
}
},
"parsers": [
{
"name": "clangtidy",
"with": {
"matches": [
{
"keywords": [
"codequality-unchecked-malloc-result",
"readability-duplicate-include",
"readability-identifier-naming",
"readability-redundant",
"readability-misplaced-array-index",
"cppcoreguidelines-init-variables",
"bugprone-suspicious-string-compare",
"google-global-names-in-headers",
"clang-diagnostic",
"clang-analyzer",
"misc",
"performance",
"portability"
],
"score": 5
},
{
"keywords": [
"codequality-no-global-variables",
"codequality-no-header-guard",
"codequality-no-fflush-stdin"
],
"score": 20
},
{
"keywords": [
"readability-function-size"
],
"score": 10
},
{
"keywords": [
"readability-misleading-indentation"
],
"score": 15
}
]
}
},
{
"name": "result-detail",
"with": {
"score": 0,
"comment": "",
"showFiles": [
"stdout"
],
"showExitStatus": true,
"showRuntime": false,
"showMemory": false
}
}
]
}
],
"preStages": [],
"postStages": []
}
}

View File

@ -0,0 +1,20 @@
# general task configuration
task.name = "hw7 ex2" # task name
task.type = "homework/h7/e2" # remove this task type later
release.end_time = 2024-12-30 23:59:59+08:00
release.begin_time = 2024-12-29 23:59:59+08:00
[[stages]]
name = "[cq] Clang-tidy"
command = "run-clang-tidy-18 -header-filter=.* -quiet -load=/usr/local/lib/libcodequality.so -p h7/build h7/ex2.cpp"
files.import = [ "tests/homework/h7/.clang-tidy", "h7/build/compile_commands.json" ]
limit.stdout = "65m"
parsers = [ "clangtidy", "result-detail" ]
clangtidy.keyword = [ "codequality-unchecked-malloc-result", "codequality-no-global-variables", "codequality-no-header-guard", "codequality-no-fflush-stdin", "readability-function-size", "readability-duplicate-include", "readability-identifier-naming", "readability-redundant", "readability-misleading-indentation", "readability-misplaced-array-index", "cppcoreguidelines-init-variables", "bugprone-suspicious-string-compare", "google-global-names-in-headers", "clang-diagnostic", "clang-analyzer", "misc", "performance", "portability" ]
clangtidy.weight = [ 5, 20, 20, 20, 10, 5, 5, 5, 15, 5, 5, 5, 5, 5, 5, 5, 5, 5]
result-detail.exitstatus = true
result-detail.stdout = true
result-detail.time = false
result-detail.mem = false

View File

@ -0,0 +1,2 @@
force_skip_health_check_on_test = true
force_skip_teapot_on_test = true

View File

@ -0,0 +1,110 @@
{
"name": "hw7 ex2",
"logPath": "/home/tt/.cache/joj3/homework/h7/e2.log",
"expireUnixTimestamp": 1735574399,
"effectiveUnixTimestamp": 1735487999,
"actorCsvPath": "/home/tt/.config/joj/students.csv",
"maxTotalScore": 100,
"stage": {
"sandboxExecServer": "172.17.0.1:5051",
"sandboxToken": "",
"outputPath": "/tmp/joj3_result.json",
"stages": [
{
"name": "[cq] Cppcheck",
"group": "cq",
"executor": {
"name": "sandbox",
"with": {
"default": {
"args": [
"cppcheck",
"--template={\"file\":\"{file}\",\"line\":{line}, \"column\":{column}, \"severity\":\"{severity}\", \"message\":\"{message}\", \"id\":\"{id}\"}",
"--force",
"--enable=all",
"--suppress=missingIncludeSystem",
"--quiet",
"h7/ex2.cpp"
],
"env": [],
"stdin": {
"content": ""
},
"stdout": {
"name": "stdout",
"max": 33554432,
"pipe": true
},
"stderr": {
"name": "stderr",
"max": 68157440,
"pipe": true
},
"cpuLimit": 1000000000,
"clockLimit": 2000000000,
"memoryLimit": 134217728,
"stackLimit": 0,
"procLimit": 50,
"cpuRateLimit": 0,
"cpuSetLimit": "",
"copyIn": {},
"copyInCached": {},
"copyInDir": ".",
"copyOut": [
"stdout",
"stderr"
],
"copyOutCached": [],
"copyOutMax": 0,
"copyOutDir": "",
"tty": false,
"strictMemoryLimit": false,
"dataSegmentLimit": false,
"addressSpaceLimit": false
},
"cases": []
}
},
"parsers": [
{
"name": "cppcheck",
"with": {
"matches": [
{
"keywords": [
"error"
],
"score": 15
},
{
"keywords": [
"warning",
"portability",
"performance",
"style"
],
"score": 5
}
]
}
},
{
"name": "result-detail",
"with": {
"score": 0,
"comment": "",
"showFiles": [
"stderr"
],
"showExitStatus": true,
"showRuntime": false,
"showMemory": false
}
}
]
}
],
"preStages": [],
"postStages": []
}
}

View File

@ -0,0 +1,19 @@
# general task configuration
task.name = "hw7 ex2" # task name
task.type = "homework/h7/e2" # remove this task type later
release.end_time = 2024-12-30 23:59:59+08:00
release.begin_time = 2024-12-29 23:59:59+08:00
[[stages]]
name = "[cq] Cppcheck"
command = "cppcheck --template='{\"file\":\"{file}\",\"line\":{line}, \"column\":{column}, \"severity\":\"{severity}\", \"message\":\"{message}\", \"id\":\"{id}\"}' --force --enable=all --suppress=missingIncludeSystem --quiet h7/ex2.cpp"
limit.stderr = "65m"
parsers = [ "cppcheck", "result-detail" ]
cppcheck.keyword = ["error", "warning", "portability", "performance", "style"]
cppcheck.weight = [15, 5, 5, 5, 5]
result-detail.exitstatus = true
result-detail.stderr = true
result-detail.time = false
result-detail.mem = false

View File

@ -0,0 +1,2 @@
force_skip_health_check_on_test = true
force_skip_teapot_on_test = true

View File

@ -0,0 +1,112 @@
{
"name": "hw7 ex2",
"logPath": "/home/tt/.cache/joj3/homework/h7/e2.log",
"expireUnixTimestamp": 1735574399,
"effectiveUnixTimestamp": 1735487999,
"actorCsvPath": "/home/tt/.config/joj/students.csv",
"maxTotalScore": 100,
"stage": {
"sandboxExecServer": "172.17.0.1:5051",
"sandboxToken": "",
"outputPath": "/tmp/joj3_result.json",
"stages": [
{
"name": "[cq] Cpplint",
"group": "cq",
"executor": {
"name": "sandbox",
"with": {
"default": {
"args": [
"cpplint",
"--linelength=120",
"--filter=-legal,-readability/casting,-whitespace,-runtime/printf,-runtime/threadsafe_fn,-runtime/int,-readability/todo,-build/include_subdir,-build/header_guard,-build/include_what_you_use",
"--recursive",
"--exclude=build",
"h7/ex2.cpp"
],
"env": [],
"stdin": {
"content": ""
},
"stdout": {
"name": "stdout",
"max": 68157440,
"pipe": true
},
"stderr": {
"name": "stderr",
"max": 33554432,
"pipe": true
},
"cpuLimit": 1000000000,
"clockLimit": 2000000000,
"memoryLimit": 134217728,
"stackLimit": 0,
"procLimit": 50,
"cpuRateLimit": 0,
"cpuSetLimit": "",
"copyIn": {},
"copyInCached": {},
"copyInDir": ".",
"copyOut": [
"stdout",
"stderr"
],
"copyOutCached": [],
"copyOutMax": 0,
"copyOutDir": "",
"tty": false,
"strictMemoryLimit": false,
"dataSegmentLimit": false,
"addressSpaceLimit": false
},
"cases": []
}
},
"parsers": [
{
"name": "cpplint",
"with": {
"matches": [
{
"keywords": [
"runtime"
],
"score": 5
},
{
"keywords": [
"readability"
],
"score": 20
},
{
"keywords": [
"build"
],
"score": 10
}
]
}
},
{
"name": "result-detail",
"with": {
"score": 0,
"comment": "",
"showFiles": [
"stderr"
],
"showExitStatus": true,
"showRuntime": false,
"showMemory": false
}
}
]
}
],
"preStages": [],
"postStages": []
}
}

View File

@ -0,0 +1,19 @@
# general task configuration
task.name = "hw7 ex2" # task name
task.type = "homework/h7/e2" # remove this task type later
release.end_time = 2024-12-30 23:59:59+08:00
release.begin_time = 2024-12-29 23:59:59+08:00
[[stages]]
name = "[cq] Cpplint"
command = "cpplint --linelength=120 --filter=-legal,-readability/casting,-whitespace,-runtime/printf,-runtime/threadsafe_fn,-runtime/int,-readability/todo,-build/include_subdir,-build/header_guard,-build/include_what_you_use --recursive --exclude=build h7/ex2.cpp"
limit.stdout = "65m"
parsers = [ "cpplint", "result-detail" ]
cpplint.keyword = [ "runtime", "readability", "build" ]
cpplint.weight = [ 5, 20, 10]
result-detail.exitstatus = true
result-detail.stderr = true
result-detail.time = false
result-detail.mem = false

View File

@ -0,0 +1,2 @@
force_skip_health_check_on_test = true
force_skip_teapot_on_test = true

View File

@ -0,0 +1,138 @@
{
"name": "hw7 ex2",
"logPath": "/home/tt/.cache/joj3/homework/h7/e2.log",
"expireUnixTimestamp": 1735574399,
"effectiveUnixTimestamp": 1735487999,
"actorCsvPath": "/home/tt/.config/joj/students.csv",
"maxTotalScore": 100,
"stage": {
"sandboxExecServer": "172.17.0.1:5051",
"sandboxToken": "",
"outputPath": "/tmp/joj3_result.json",
"stages": [
{
"name": "[joj] ex2-asan",
"group": "joj",
"executor": {
"name": "sandbox",
"with": {
"default": {
"args": [
"./h7/build/ex2-asan",
"-a"
],
"env": [],
"stdin": {
"content": ""
},
"stdout": {
"name": "stdout",
"max": 33554432,
"pipe": true
},
"stderr": {
"name": "stderr",
"max": 33554432,
"pipe": true
},
"cpuLimit": 1000000000,
"clockLimit": 2000000000,
"memoryLimit": 68157440,
"stackLimit": 0,
"procLimit": 50,
"cpuRateLimit": 0,
"cpuSetLimit": "",
"copyIn": {
"h7/build/ex2-asan": {
"src": "/home/tt/.config/joj/h7/build/ex2-asan"
}
},
"copyInCached": {},
"copyInDir": ".",
"copyOut": [
"stdout",
"stderr"
],
"copyOutCached": [],
"copyOutMax": 0,
"copyOutDir": "",
"tty": false,
"strictMemoryLimit": false,
"dataSegmentLimit": false,
"addressSpaceLimit": false
},
"cases": [
{
"stdin": {
"src": "/home/tt/.config/joj/diff/case0.in"
},
"cpuLimit": 1000000000,
"clockLimit": 2000000000,
"memoryLimit": 2097152,
"procLimit": 50
},
{
"stdin": {
"src": "/home/tt/.config/joj/diff/case1.in"
},
"cpuLimit": 2000000000,
"clockLimit": 4000000000,
"memoryLimit": 4194304,
"procLimit": 50
}
]
}
},
"parsers": [
{
"name": "diff",
"with": {
"name": "diff",
"cases": [
{
"outputs": [
{
"score": 5,
"fileName": "stdout",
"answerPath": "/home/tt/.config/joj/diff/case0.out",
"forceQuitOnDiff": false,
"alwaysHide": false,
"compareSpace": false
}
]
},
{
"outputs": [
{
"score": 5,
"fileName": "stdout",
"answerPath": "/home/tt/.config/joj/diff/case1.out",
"forceQuitOnDiff": false,
"alwaysHide": false,
"compareSpace": false
}
]
}
]
}
},
{
"name": "result-detail",
"with": {
"score": 0,
"comment": "",
"showFiles": [
"stderr"
],
"showExitStatus": true,
"showRuntime": true,
"showMemory": true
}
}
]
}
],
"preStages": [],
"postStages": []
}
}

View File

@ -0,0 +1,33 @@
# general task configuration
task.name = "hw7 ex2" # task name
task.type = "homework/h7/e2" # remove this task type later
release.end_time = 2024-12-30 23:59:59+08:00
release.begin_time = 2024-12-29 23:59:59+08:00
[[stages]]
name = "[joj] ex2-asan"
command="./h7/build/ex2-asan -a"
files.import = [ "h7/build/ex2-asan" ]
limit.mem = "65m"
parsers = [ "diff", "result-detail" ]
result-detail.exitstatus = true
result-detail.stderr = true
# will be removed as long as the name is fixed
case0.diff.output.score = 5
case0.limit.cpu = "1s"
case0.limit.mem = "2m"
case0.diff.output.ignore_spaces = true
#case0.limit.stdout = 8
#case0.command = "./h7/build/ex2"
case0.in = "case0.in"
case1.diff.output.score = 5
case1.limit.cpu = "2s"
case1.limit.mem = "4m"
case1.diff.output.ignore_spaces = true
#case1.limit.stdout = 8
#case1.command = "./h7/build/ex2"
case1.in = "case1.in"

View File

@ -0,0 +1,2 @@
force_skip_health_check_on_test = true
force_skip_teapot_on_test = true

View File

@ -0,0 +1,109 @@
{
"name": "hw7 ex2",
"logPath": "/home/tt/.cache/joj3/homework/h7/e2.log",
"expireUnixTimestamp": 1735574399,
"effectiveUnixTimestamp": 1735487999,
"actorCsvPath": "/home/tt/.config/joj/students.csv",
"maxTotalScore": 100,
"stage": {
"sandboxExecServer": "172.17.0.1:5051",
"sandboxToken": "",
"outputPath": "/tmp/joj3_result.json",
"stages": [
{
"name": "[cq] Filelength",
"group": "cq",
"executor": {
"name": "sandbox",
"with": {
"default": {
"args": [
"./tools/filelength",
"400",
"300",
"*.cpp",
"*.h"
],
"env": [],
"stdin": {
"content": ""
},
"stdout": {
"name": "stdout",
"max": 33554432,
"pipe": true
},
"stderr": {
"name": "stderr",
"max": 33554432,
"pipe": true
},
"cpuLimit": 1000000000,
"clockLimit": 2000000000,
"memoryLimit": 134217728,
"stackLimit": 0,
"procLimit": 50,
"cpuRateLimit": 0,
"cpuSetLimit": "",
"copyIn": {
"tools/filelength": {
"src": "/home/tt/.config/joj/tools/filelength"
}
},
"copyInCached": {},
"copyInDir": ".",
"copyOut": [
"stdout",
"stderr"
],
"copyOutCached": [],
"copyOutMax": 0,
"copyOutDir": "",
"tty": false,
"strictMemoryLimit": false,
"dataSegmentLimit": false,
"addressSpaceLimit": false
},
"cases": []
}
},
"parsers": [
{
"name": "keyword",
"with": {
"matches": [
{
"keywords": [
"max"
],
"score": 20
},
{
"keywords": [
"recommended"
],
"score": 10
}
]
}
},
{
"name": "result-detail",
"with": {
"score": 0,
"comment": "",
"showFiles": [
"stdout"
],
"showExitStatus": true,
"showRuntime": false,
"showMemory": false
}
}
]
}
],
"preStages": [],
"postStages": []
}
}

View File

@ -0,0 +1,19 @@
# general task configuration
task.name = "hw7 ex2" # task name
task.type = "homework/h7/e2" # remove this task type later
release.end_time = 2024-12-30 23:59:59+08:00
release.begin_time = 2024-12-29 23:59:59+08:00
[[stages]]
name = "[cq] Filelength"
command = "./tools/filelength 400 300 *.cpp *.h"
files.import = [ "tools/filelength" ]
parsers = [ "keyword", "result-detail" ]
keyword.keyword = [ "max", "recommended"]
keyword.weight = [ 20, 10 ]
result-detail.exitstatus = true
result-detail.stdout = true
result-detail.time = false
result-detail.mem = false

View File

@ -0,0 +1,2 @@
force_skip_health_check_on_test = true
force_skip_teapot_on_test = true

View File

@ -0,0 +1,91 @@
{
"name": "hw7 ex2",
"logPath": "/home/tt/.cache/joj3/homework/h7/e2.log",
"expireUnixTimestamp": 1735574399,
"effectiveUnixTimestamp": 1735487999,
"actorCsvPath": "/home/tt/.config/joj/students.csv",
"maxTotalScore": 100,
"stage": {
"sandboxExecServer": "172.17.0.1:5051",
"sandboxToken": "",
"outputPath": "/tmp/joj3_result.json",
"stages": [
{
"name": "[cq] Filelength",
"group": "cq",
"executor": {
"name": "sandbox",
"with": {
"default": {
"args": [
"./tools/filelength",
"400",
"300",
"*.cpp",
"*.h"
],
"env": [],
"stdin": {
"content": ""
},
"stdout": {
"name": "stdout",
"max": 33554432,
"pipe": true
},
"stderr": {
"name": "stderr",
"max": 33554432,
"pipe": true
},
"cpuLimit": 1000000000,
"clockLimit": 2000000000,
"memoryLimit": 134217728,
"stackLimit": 0,
"procLimit": 50,
"cpuRateLimit": 0,
"cpuSetLimit": "",
"copyIn": {
"tools/filelength": {
"src": "/home/tt/.config/joj/tools/filelength"
}
},
"copyInCached": {},
"copyInDir": ".",
"copyOut": [
"stdout",
"stderr"
],
"copyOutCached": [],
"copyOutMax": 0,
"copyOutDir": "",
"tty": false,
"strictMemoryLimit": false,
"dataSegmentLimit": false,
"addressSpaceLimit": false
},
"cases": []
}
},
"parsers": [
{
"name": "result-detail",
"with": {
"score": 0,
"comment": "",
"showFiles": [
"stdout",
"stderr"
],
"showExitStatus": true,
"showRuntime": false,
"showMemory": false
}
}
]
}
],
"preStages": [],
"postStages": []
}
}

View File

@ -0,0 +1,18 @@
# general task configuration
task.name = "hw7 ex2" # task name
task.type = "homework/h7/e2" # remove this task type later
release.end_time = 2024-12-30 23:59:59+08:00
release.begin_time = 2024-12-29 23:59:59+08:00
[[stages]]
name = "[cq] Filelength"
command = "./tools/filelength 400 300 *.cpp *.h"
files.import = [ "tools/filelength" ]
parsers = [ "result-detail" ]
result-detail.exitstatus = true
result-detail.stdout = true
result-detail.stderr = true
result-detail.time = false
result-detail.mem = false

View File

@ -3,3 +3,27 @@ from tests.convert.utils import load_case
def test_basic() -> None:
load_case("basic")
def test_clang_tidy() -> None:
load_case("clang-tidy")
def test_cppcheck() -> None:
load_case("cppcheck")
def test_cpplint() -> None:
load_case("cpplint")
def test_diff() -> None:
load_case("diff")
def test_keyword() -> None:
load_case("keyword")
def test_result_detail() -> None:
load_case("result-detail")

View File

@ -1,32 +1,18 @@
import json
from pathlib import Path
from typing import Any, Dict, Tuple
import rtoml
from joj3_config_generator.convert import convert
from joj3_config_generator.models import repo, task
def read_convert_files(
case_name: str,
) -> Tuple[repo.Config, task.Config, Dict[str, Any]]:
root = Path(__file__).resolve().parent / case_name
repo_toml_path = root / "repo.toml"
repo_toml = repo_toml_path.read_text() if repo_toml_path.exists() else ""
task_toml_path = root / "task.toml"
task_toml = task_toml_path.read_text() if task_toml_path.exists() else ""
result = json.loads((root / "task.json").read_text())
return (
repo.Config(root=root, **rtoml.loads(repo_toml)),
task.Config(root=root, **rtoml.loads(task_toml)),
result,
)
from joj3_config_generator.load import load_joj3_toml
def load_case(case_name: str) -> None:
repo, task, expected_result = read_convert_files(case_name)
result = convert(repo, task).model_dump(
root = Path(__file__).resolve().parent
repo_toml_path = root / case_name / "repo.toml"
task_toml_path = root / case_name / "task.toml"
repo_conf, task_conf = load_joj3_toml(root, repo_toml_path, task_toml_path)
result_json_path = root / case_name / "task.json"
expected_result = json.loads(result_json_path.read_text())
result = convert(repo_conf, task_conf).model_dump(
mode="json", by_alias=True, exclude_none=True
)
assert result == expected_result

View File

@ -1,23 +1,19 @@
from pathlib import Path
from typing import Any, Dict, Tuple
import rtoml
import yaml
from joj3_config_generator.convert import convert_joj1
from joj3_config_generator.models import joj1
def read_convert_joj1_files(case_name: str) -> Tuple[joj1.Config, Dict[str, Any]]:
root = Path(__file__).resolve().parent
task_yaml_path = root / case_name / "task.yaml"
task_yaml = task_yaml_path.read_text()
task_toml_path = root / case_name / "task.toml"
task_toml = task_toml_path.read_text()
return joj1.Config(**yaml.safe_load(task_yaml)), rtoml.loads(task_toml)
from joj3_config_generator.load import load_joj1_yaml
def load_case(case_name: str) -> None:
joj1, expected_result = read_convert_joj1_files(case_name)
result = convert_joj1(joj1).model_dump(by_alias=True, exclude_none=True)
root = Path(__file__).resolve().parent
task_yaml_path = root / case_name / "task.yaml"
task_yaml = load_joj1_yaml(task_yaml_path)
task_toml_path = root / case_name / "task.toml"
task_toml = task_toml_path.read_text()
expected_result = rtoml.loads(task_toml)
result = convert_joj1(task_yaml).model_dump(
mode="json", by_alias=True, exclude_none=True
)
assert result == expected_result