Compare commits

..

3 Commits

Author SHA1 Message Date
139eafa644 docs: fix typo
All checks were successful
build / build (push) Successful in 2m13s
build / trigger-build-image (push) Has been skipped
build / build (pull_request) Successful in 2m43s
build / trigger-build-image (pull_request) Has been skipped
2025-06-11 23:03:36 +08:00
8ebc432999 docs: remove non-ascii
All checks were successful
build / build (push) Successful in 1m48s
build / trigger-build-image (push) Has been skipped
build / build (pull_request) Successful in 1m59s
build / trigger-build-image (pull_request) Has been skipped
2025-06-11 23:01:21 +08:00
2a6428feec docs: README
All checks were successful
build / build (push) Successful in 2m38s
build / trigger-build-image (push) Has been skipped
build / build (pull_request) Successful in 2m10s
build / trigger-build-image (pull_request) Has been skipped
2025-06-11 22:33:15 +08:00
60 changed files with 287 additions and 1694 deletions

View File

@ -23,9 +23,9 @@ jobs:
- name: PDM install dependencies
run: |
pdm install
- name: All
- name: Lint
run: |
pdm run all
pdm run lint
- name: Run
run: |
pdm run app --help

View File

@ -1,8 +1,12 @@
# JOJ3-config-generator
# JOJ3-forge
[![Codacy Badge](https://app.codacy.com/project/badge/Grade/a98f9aa020874a93bc791a7616fccf21)](https://app.codacy.com/gh/joint-online-judge/JOJ3-config-generator/dashboard)
[![Codacy Badge](https://app.codacy.com/project/badge/Coverage/a98f9aa020874a93bc791a7616fccf21)](https://app.codacy.com/gh/joint-online-judge/JOJ3-config-generator/dashboard)
## Introduction
JOJ3-forge is a CLI tool that generates configuration files for [JOJ3](https://github.com/joint-online-judge/JOJ3).
## Getting Started
### For users
@ -10,7 +14,7 @@
1. Install [Python>=3.9](https://www.python.org/) and [pip](https://pip.pypa.io/)
2. (Optional) Create a virtual environment, check [here](https://packaging.python.org/en/latest/guides/installing-using-pip-and-virtual-environments/).
3. Install/Upgrade the project by `pip install --force-reinstall --upgrade git+ssh://git@focs.ji.sjtu.edu.cn:2222/JOJ/JOJ3-config-generator.git`
4. Run it by `joj3-config-generator --help`
4. Run it by `joj3-forge --help`
### For developers
@ -23,22 +27,62 @@
## 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`
Run `joj3-forge --help` to get basic CLI usage information.
### `convert`
- `joj3-forge convert` function is now supported, currently support one argument as input, it indicates the **convert root*
- default value on the server should 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
- a sample directory tree as follows
```shell
$ tree .
.
|- immutable_files
| |-- push.yaml
| |-- release.yaml
|-- repo.toml
$ tree -a
home
`-- tt
|-- .cache
`-- .config
`-- joj
|-- hidden
| |-- ex1
| | |-- case1.in
| | |-- case1.out
| | |-- conf.json
| | `-- conf.toml
| |-- immutable_files
| | |-- push.yaml
| | `-- release.yaml
| |-- p1
| | |-- case1.in
| | |-- case1.out
| | |-- conf.json
| | `-- conf.toml
| `-- repo.toml
|-- students
| |-- ex1
| | |-- case1.in
| | |-- case1.out
| | |-- conf.json
| | `-- conf.toml
| |-- immutable_files
| | |-- push.yaml
| | `-- release.yaml
| |-- p1
| | |-- case1.in
| | |-- case1.out
| | |-- conf.json
| | `-- conf.toml
| `-- repo.toml
|-- students.csv
`-- tools
|-- .clang-tidy
|-- compile
`-- helper.sh
```
- sample command on the server
```shell
joj3-config-generator convert /home/tt/.config/joj
joj3-forge convert /home/tt/.config/joj
```

View File

@ -2,11 +2,7 @@ import os
from typing import Dict
from joj3_config_generator.models import answer, joj1, repo, result, task
from joj3_config_generator.models.const import (
ACTOR_CSV_PATH,
JOJ3_LOG_BASE_PATH,
JOJ3_LOG_FILENAME,
)
from joj3_config_generator.models.const import ACTOR_CSV_PATH, JOJ3_LOG_PATH
from joj3_config_generator.transformers.answer import get_task_conf_from_answers
from joj3_config_generator.transformers.joj1 import get_task_conf_from_joj1
from joj3_config_generator.transformers.repo import (
@ -27,10 +23,9 @@ def convert_joj1_conf(joj1_conf: joj1.Config) -> task.Config:
def convert_joj3_conf(repo_conf: repo.Config, task_conf: task.Config) -> result.Config:
# Create the base ResultConf object
result_conf = result.Config(
name=task_conf.name,
name=task_conf.task.name,
# exact folder difference specified by type
log_path=str(JOJ3_LOG_BASE_PATH / task_conf.suffix / JOJ3_LOG_FILENAME),
# TODO: remove these 2 fields, will be handled in the health check stage
log_path=str(JOJ3_LOG_PATH),
expire_unix_timestamp=int(task_conf.release.end_time.timestamp()),
effective_unix_timestamp=int(task_conf.release.begin_time.timestamp()),
actor_csv_path=str(ACTOR_CSV_PATH), # students.csv position

View File

@ -5,7 +5,7 @@ from typing import Any, Dict, Tuple, Type, cast
import inquirer
import tomli
import yaml
from pydantic import AliasChoices, BaseModel
from pydantic import BaseModel
from joj3_config_generator.models import answer, joj1, repo, task
from joj3_config_generator.models.common import Memory, Time
@ -83,16 +83,8 @@ def load_joj3_toml(
f"{current_path}.{field_name}" if current_path else field_name
)
toml_field_name = field_name
if field_info.validation_alias:
if isinstance(field_info.validation_alias, str):
if field_info.validation_alias in input_dict:
toml_field_name = field_info.validation_alias
elif isinstance(field_info.validation_alias, AliasChoices):
for choice in field_info.validation_alias.choices:
if choice in input_dict:
if isinstance(choice, str):
toml_field_name = choice
break
if field_info.alias in input_dict:
toml_field_name = field_info.alias
if toml_field_name not in input_dict:
continue
toml_value = input_dict[toml_field_name]

View File

@ -95,12 +95,6 @@ def convert(
app.pretty_exceptions_enable = False
logger.info(f"Converting files in {root.absolute()}")
for repo_toml_path in root.glob("**/repo.toml"):
if not any(p != repo_toml_path for p in repo_toml_path.parent.glob("*.toml")):
fallback_toml_path = repo_toml_path.parent / "conf.toml"
if not fallback_toml_path.exists():
fallback_toml_path.write_text(
'name = "health check"\nmax-total-score = 0\n'
)
for task_toml_path in repo_toml_path.parent.glob("**/*.toml"):
if repo_toml_path == task_toml_path:
continue

View File

@ -13,7 +13,6 @@ DEFAULT_PATH_ENV = "PATH=/usr/bin:/bin:/usr/local/bin"
JOJ3_CONFIG_ROOT = PurePosixPath("/home/tt/.config/joj")
TEAPOT_CONFIG_ROOT = PurePosixPath("/home/tt/.config/teapot")
CACHE_ROOT = PurePosixPath("/home/tt/.cache")
JOJ3_LOG_BASE_PATH = CACHE_ROOT / "joj3"
JOJ3_LOG_FILENAME = "joj3.log"
JOJ3_LOG_PATH = CACHE_ROOT / "joj3" / "joj3.log"
TEAPOT_LOG_PATH = CACHE_ROOT / "joint-teapot-debug.log"
ACTOR_CSV_PATH = JOJ3_CONFIG_ROOT / "students.csv"

View File

@ -1,10 +1,8 @@
import os
import socket
from pathlib import Path
from typing import Any, List
from typing import List
from pydantic import AliasChoices, BaseModel, Field, field_validator, model_validator
from joj3_config_generator.models.common import Memory
from pydantic import AliasChoices, BaseModel, Field
class Files(BaseModel):
@ -22,115 +20,42 @@ class Groups(BaseModel):
)
class Label(BaseModel):
name: str = "Kind/Testing"
color: str = "#795548"
exclusive: bool = False
class Issue(BaseModel):
label: Label = Label()
show_submitter: bool = Field(
True, validation_alias=AliasChoices("show-submitter", "show_submitter")
)
class HealthCheck(BaseModel):
score: int = 0
max_size: int = Field(
Memory("10m"), validation_alias=AliasChoices("max-size", "max_size")
)
immutable_path: Path = Field(
Path("immutable"),
validation_alias=AliasChoices("immutable-path", "immutable_path"),
)
required_files: List[str] = Field(
[], validation_alias=AliasChoices("required-files", "required_files")
)
@field_validator("max_size", mode="before")
@classmethod
def ensure_mem_type(cls, v: Any) -> Memory:
if isinstance(v, str):
return Memory(v)
raise ValueError(f'Must be a string, e.g., "256m" or "1g", but got {v}')
class Config(BaseModel):
root: Path = Field(Path("."), exclude=True)
path: Path = Field(Path("repo.toml"), exclude=True)
force_skip_health_check_on_test: bool = Field(
False,
validation_alias=AliasChoices(
"force-skip-health-check-on-test", "force_skip_health_check_on_test"
),
exclude=True,
)
force_skip_teapot_on_test: bool = Field(
False,
validation_alias=AliasChoices(
"force-skip-teapot-on-test", "force_skip_teapot_on_test"
),
exclude=True,
)
grading_repo_name: str = Field(
"",
validation_alias=AliasChoices("grading-repo-name", "grading_repo_name"),
max_size: float = Field(
10, ge=0, validation_alias=AliasChoices("max-size", "max_size")
)
files: Files = Files()
sandbox_token: str = Field(
"", validation_alias=AliasChoices("sandbox-token", "sandbox_token")
)
max_total_score: int = Field(
100, validation_alias=AliasChoices("max-total-score", "max_total_score")
)
groups: Groups = Groups()
issue: Issue = Issue()
health_check: HealthCheck = Field(
HealthCheck(), validation_alias=AliasChoices("health-check", "health_check")
force_skip_health_check_on_test: bool = Field(
False,
validation_alias=AliasChoices(
"force-skip-health-check-on-test", "force_skip_health_check_on_test"
),
)
# TODO: remove files, max_size, health_check_score, and immutable_path in the future
files: Files = Files()
max_size: float = Field(
10, ge=0, validation_alias=AliasChoices("max-size", "max_size")
force_skip_teapot_on_test: bool = Field(
False,
validation_alias=AliasChoices(
"force-skip-teapot-on-test", "force_skip_teapot_on_test"
),
)
groups: Groups = Groups()
root: Path = Path(".")
path: Path = Path("repo.toml")
grading_repo_name: str = Field(
f"{socket.gethostname().split('-')[0]}-joj",
validation_alias=AliasChoices("grading-repo-name", "grading_repo_name"),
)
health_check_score: int = Field(
0, validation_alias=AliasChoices("health-check-score", "health_check_score")
)
immutable_path: Path = Field(
Path("immutable"),
validation_alias=AliasChoices("immutable-path", "immutable_path"),
submitter_in_issue_title: bool = Field(
True,
validation_alias=AliasChoices(
"submitter-in-issue-title", "submitter_in_issue_title"
),
)
# TODO: remove gitea_token and gitea_org in the future
gitea_token: str = Field(
"", validation_alias=AliasChoices("gitea-token", "gitea_token")
)
gitea_org: str = Field("", validation_alias=AliasChoices("gitea-org", "gitea_org"))
@model_validator(mode="after")
def set_grading_repo_name_from_cwd(self) -> "Config":
if not self.grading_repo_name:
course_env = os.getenv("COURSE")
if course_env:
self.grading_repo_name = f"{course_env}-joj"
else:
self.grading_repo_name = Path.cwd().name
return self
# TODO: remove this validator in the future
@model_validator(mode="after")
def set_health_check(self) -> "Config":
if "health_check_score" in self.model_fields_set:
self.health_check.score = self.health_check_score
if "max_size" in self.model_fields_set:
self.health_check.max_size = Memory(f"{self.max_size}m")
if "immutable_path" in self.model_fields_set:
self.health_check.immutable_path = self.immutable_path
if (
"files" in self.model_fields_set
and "required" in self.files.model_fields_set
):
self.health_check.required_files = self.files.required
return self

View File

@ -177,12 +177,6 @@ class Config(BaseModel):
class DummyConfig(BaseModel):
score: int = 0
comment: Optional[str] = None
force_quit: Optional[bool] = Field(False, serialization_alias="forceQuit")
class ResultStatusConfig(BaseModel):
score: int = 0
comment: Optional[str] = None
force_quit_on_not_accepted: Optional[bool] = Field(
@ -192,7 +186,7 @@ class ResultStatusConfig(BaseModel):
class DiffOutputConfig(BaseModel):
score: int = 100
filename: str = Field("", serialization_alias="filename")
file_name: str = Field("", serialization_alias="fileName")
answer_path: str = Field("", serialization_alias="answerPath")
compare_space: bool = Field(False, serialization_alias="compareSpace")
always_hide: bool = Field(False, serialization_alias="alwaysHide")
@ -223,7 +217,6 @@ class KeywordConfig(BaseModel):
class KeywordMatchConfig(BaseModel):
score: int = 0
matches: List[KeywordConfig] = []

View File

@ -1,8 +1,7 @@
import re
from datetime import datetime, timezone
from enum import Enum
from pathlib import Path
from typing import Any, Dict, List, Optional, Type
from typing import Any, Dict, List, Type
from pydantic import (
AliasChoices,
@ -16,7 +15,6 @@ from pydantic import (
from joj3_config_generator.models.common import Memory, Time
from joj3_config_generator.models.const import (
DEFAULT_CASE_SCORE,
DEFAULT_CLOCK_LIMIT_MULTIPLIER,
DEFAULT_CPU_LIMIT,
DEFAULT_FILE_LIMIT,
DEFAULT_MEMORY_LIMIT,
@ -27,9 +25,9 @@ from joj3_config_generator.models.repo import Groups
class ParserResultDetail(BaseModel):
cpu_time: bool = Field(
True, validation_alias=AliasChoices("cpu-time", "cpu_time", "cpu")
True, validation_alias=AliasChoices("cpu-time", "cpu_time")
) # Display CPU time
time: bool = True # Display wall-clock time
time: bool = True # Display run time
mem: bool = True # Display memory usage
stdout: bool = False # Display stdout messages
stderr: bool = False # Display stderr messages
@ -40,12 +38,6 @@ class ParserResultDetail(BaseModel):
False, validation_alias=AliasChoices("proc-peak", "proc_peak")
) # Display peak process count
error: bool = False # Display error messages
code_block: bool = Field(
True, validation_alias=AliasChoices("code-block", "code_block")
) # Display file in code block
max_length: int = Field(
2048, validation_alias=AliasChoices("max-length", "max_length")
) # Max output length of each file
class ParserFile(BaseModel):
@ -66,16 +58,7 @@ class ParserDummy(BaseModel):
)
class ParserResultStatus(BaseModel):
comment: str = ""
score: int = 0
force_quit: bool = Field(
True, validation_alias=AliasChoices("force-quit", "force_quit")
)
class ParserKeyword(BaseModel):
score: int = 0
keyword: List[str] = []
weight: List[int] = []
@ -99,75 +82,40 @@ class ParserDiffOutputs(BaseModel):
class ParserDiff(BaseModel):
score: int = DEFAULT_CASE_SCORE
ignore_spaces: bool = Field(
True, validation_alias=AliasChoices("ignore-spaces", "ignore_spaces")
)
hide: bool = False
force_quit: bool = Field(
False, validation_alias=AliasChoices("force-quit", "force_quit")
)
max_length: int = Field(
2048, validation_alias=AliasChoices("max-length", "max_length")
)
max_lines: int = Field(50, validation_alias=AliasChoices("max-lines", "max_lines"))
hide_common_prefix: bool = Field(
False, validation_alias=AliasChoices("hide-common-prefix", "hide_common_prefix")
)
# remove below codes when migration is complete
output: ParserDiffOutputs = ParserDiffOutputs()
default_score: int = Field(
DEFAULT_CASE_SCORE,
validation_alias=AliasChoices("default-score", "default_score"),
)
@model_validator(mode="after")
def copy_output_fields(self) -> "ParserDiff":
if "default_score" in self.model_fields_set:
self.score = self.default_score
if not isinstance(self.output, ParserDiffOutputs):
return self
for field_name, field_value in self.output.model_dump().items():
if field_name in self.output.model_fields_set and hasattr(self, field_name):
setattr(self, field_name, field_value)
return self
class StageFiles(BaseModel):
import_: List[str] = Field([], validation_alias="import")
export: List[str] = []
import_map: Dict[str, str] = Field(
{}, validation_alias=AliasChoices("import-map", "import_map")
)
class Limit(BaseModel):
mem: int = DEFAULT_MEMORY_LIMIT
cpu: int = DEFAULT_CPU_LIMIT
time: int = 0
stdout: int = DEFAULT_FILE_LIMIT
stderr: int = DEFAULT_FILE_LIMIT
proc: int = DEFAULT_PROC_LIMIT
@field_validator("cpu", "time", mode="before")
model_config = ConfigDict(validate_assignment=True)
@field_validator("cpu", mode="before")
@classmethod
def ensure_time_type(cls, v: Any) -> Time:
def ensure_time(cls, v: Any) -> Time:
if isinstance(v, str):
return Time(v)
raise ValueError(f'Must be a string, e.g., "1s" or "100ms", but got {v}')
raise ValueError("Must be a string")
@field_validator("mem", "stdout", "stderr", mode="before")
@classmethod
def ensure_mem_type(cls, v: Any) -> Memory:
def ensure_mem(cls, v: Any) -> Memory:
if isinstance(v, str):
return Memory(v)
raise ValueError(f'Must be a string, e.g., "256m" or "1g", but got {v}')
@model_validator(mode="after")
def set_time_if_not_set(self) -> "Limit":
if self.time == 0:
self.time = DEFAULT_CLOCK_LIMIT_MULTIPLIER * self.cpu
return self
raise ValueError("Must be a string")
class Parser(str, Enum):
@ -193,17 +141,18 @@ class Case(BaseModel):
True, validation_alias=AliasChoices("copy-in-cwd", "copy_in_cwd")
)
limit: Limit = Limit()
score: int = 0
diff: ParserDiff = ParserDiff()
class Stage(Case):
name: str = "" # stage name
skip: List[str] = []
parsers: List[Parser] = [] # list of parsers
dummy: ParserDummy = ParserDummy()
result_status: ParserResultStatus = Field(
ParserResultStatus(),
validation_alias=AliasChoices("result-status", "result_status"),
result_status: ParserDummy = Field(
ParserDummy(), validation_alias=AliasChoices("result-status", "result_status")
)
keyword: ParserKeyword = ParserKeyword()
clangtidy: ParserKeyword = ParserKeyword()
@ -215,7 +164,6 @@ class Stage(Case):
validation_alias=AliasChoices("result-detail", "result_detail"),
)
file: ParserFile = ParserFile()
# diff: ParserDiff = ParserDiff() # inherited from Case
cases: Dict[str, Case] = {}
@ -247,58 +195,17 @@ class Release(BaseModel):
) # timestamp = 0, no begin time
class SubmissionTime(BaseModel):
begin: Optional[datetime] = None
end: Optional[datetime] = None
class Task(BaseModel):
name: str = "unknown"
class Penalties(BaseModel):
hours: List[float] = []
factors: List[float] = []
class Config(BaseModel):
root: Path = Field(Path("."), exclude=True)
path: Path = Field(Path("task.toml"), exclude=True)
suffix: str = Field("", exclude=True)
task: Task = Task() # TODO: remove it in the future
name: str = "unknown" # Task name (e.g., hw3 ex5)
max_total_score: Optional[int] = Field(
None, validation_alias=AliasChoices("max-total-score", "max_total_score")
)
scoreboard: str = "scoreboard.csv"
scoreboard_column_by_ref: bool = Field(
False,
validation_alias=AliasChoices(
"scoreboard-column-by-ref", "scoreboard_column_by_ref"
),
)
time: SubmissionTime = SubmissionTime() # Valid time configuration
root: Path = Path(".")
path: Path = Path("task.toml")
task: Task = Task() # Task name (e.g., hw3 ex5)
release: Release = Release() # Release configuration
groups: Groups = Groups()
penalties: Penalties = Penalties()
stages: List[Stage] = [] # list of stage configurations
# TODO: remove this validator in the future
@model_validator(mode="after")
def set_name(self) -> "Config":
if "task" in self.model_fields_set and "name" in self.task.model_fields_set:
self.name = self.task.name
return self
@model_validator(mode="after")
def set_suffix(self) -> "Config":
if not self.suffix:
self.suffix = re.split(r"[-_/\s]+", self.name)[0]
return self
@model_validator(mode="after")
def set_scoreboard(self) -> "Config":
if self.scoreboard == "auto":
self.scoreboard = f"scoreboard-{self.suffix}.csv"
return self
groups: Groups = Groups()
max_total_score: int = Field(
100, validation_alias=AliasChoices("max-total-score", "max_total_score")
)

View File

@ -9,16 +9,16 @@ def get_task_conf_from_answers(answers: answer.Answers) -> task.Config:
if answers.template_file_content:
toml_dict = tomli.loads(answers.template_file_content)
return task.Config(
name=answers.name,
task=task.Task(name=answers.name),
stages=toml_dict["stages"],
)
language = answers.language
transformer_dict = get_transformer_dict()
if language not in transformer_dict:
return task.Config(name=answers.name, stages=[])
return task.Config(task=task.Task(name=answers.name), stages=[])
transformer = transformer_dict[language]
stages = transformer(language)
return task.Config(name=answers.name, stages=stages)
return task.Config(task=task.Task(name=answers.name), stages=stages)
def get_transformer_dict() -> Dict[

View File

@ -1,27 +1,9 @@
import hashlib
import sys
from pathlib import Path
from typing import List, Tuple
from typing import List
from joj3_config_generator.models import common, repo, result, task
from joj3_config_generator.models.const import (
CACHE_ROOT,
TEAPOT_CONFIG_ROOT,
TEAPOT_LOG_PATH,
)
from joj3_config_generator.utils.logger import logger
def get_teapot_env(repo_conf: repo.Config) -> List[str]:
res = [
f"REPOS_DIR={CACHE_ROOT}",
f"LOG_FILE_PATH={TEAPOT_LOG_PATH}",
]
if repo_conf.gitea_org:
res.append(f"GITEA_ORG_NAME={repo_conf.gitea_org}")
if repo_conf.gitea_token:
res.append(f"GITEA_ACCESS_TOKEN={repo_conf.gitea_token}")
return res
from joj3_config_generator.models.const import TEAPOT_CONFIG_ROOT, TEAPOT_LOG_PATH
def get_teapot_post_stage(
@ -35,38 +17,13 @@ def get_teapot_post_stage(
repo_conf.grading_repo_name,
"--max-total-score",
(
str(task_conf.max_total_score)
if task_conf.max_total_score is not None
else str(repo_conf.max_total_score)
str(repo_conf.max_total_score)
if not task_conf.max_total_score
else str(task_conf.max_total_score)
),
"--issue-label-name",
repo_conf.issue.label.name,
"--issue-label-color",
repo_conf.issue.label.color,
"--scoreboard-filename",
task_conf.scoreboard,
]
if task_conf.scoreboard_column_by_ref:
args.append("--scoreboard-column-by-ref")
if repo_conf.issue.label.exclusive:
args.append("--issue-label-exclusive")
if not repo_conf.issue.show_submitter:
if not repo_conf.submitter_in_issue_title:
args.append("--no-submitter-in-issue-title")
if task_conf.time.end:
args.extend(
[
"--end-time",
task_conf.time.end.strftime("%Y-%m-%dT%H:%M:%S"),
]
)
if task_conf.penalties.hours:
penalty_config = ",".join(
f"{hour}={factor}"
for hour, factor in zip(
task_conf.penalties.hours, task_conf.penalties.factors
)
)
args.extend(["--penalty-config", penalty_config])
stage_conf = result.StageDetail(
name="teapot",
@ -75,7 +32,7 @@ def get_teapot_post_stage(
with_=result.ExecutorWith(
default=result.Cmd(
args=args,
env=get_teapot_env(repo_conf),
env=[f"LOG_FILE_PATH={TEAPOT_LOG_PATH}"],
cpu_limit=common.Time("30s"),
clock_limit=common.Time("60s"),
),
@ -90,64 +47,14 @@ def get_teapot_post_stage(
return stage_conf
def get_check_lists(repo_conf: repo.Config) -> Tuple[List[str], List[str]]:
base_dir = (repo_conf.root / repo_conf.path).parent
immutable_dir = base_dir / "immutable_files"
immutable_files = []
file_sums = []
file_paths = []
for file in repo_conf.files.immutable:
file_path = immutable_dir / Path(file).name
if not file_path.exists():
file_path = immutable_dir / Path(file).name
logger.warning(f"Immutable file not found: {file_path}")
if not file_path.exists():
logger.warning(f"Immutable file not found: {file_path}")
logger.warning(f"Skipping {file}")
continue
immutable_files.append(file_path)
file_sums.append(calc_sha256sum(file_path))
file_paths.append(file)
new_immutable_dir = (
repo_conf.root / repo_conf.path
).parent / repo_conf.health_check.immutable_path
if not new_immutable_dir.exists():
file_names = [Path(file).name for file in file_paths]
for file_path in sorted(immutable_dir.glob("**/*")):
file_path_name = file_path.name
if file_path_name not in file_names:
logger.error(
f"Immutable file exists in {immutable_dir}, "
f"but not found in health check: {file_path}."
)
logger.error(
f"Recommnd to move immutable files (keeping nested structure) to {new_immutable_dir}."
)
sys.exit(1)
return file_sums, file_paths
immutable_dir = new_immutable_dir
file_sums = []
file_paths = []
for file_path in sorted(immutable_dir.glob("**/*")):
if file_path.is_dir():
continue
if not file_path.exists():
logger.warning(f"Immutable file not found: {file_path}")
continue
file_sums.append(calc_sha256sum(file_path))
file_paths.append(file_path.relative_to(immutable_dir).as_posix())
return file_sums, file_paths
def get_health_check_args(repo_conf: repo.Config) -> List[str]:
file_sums, file_paths = get_check_lists(repo_conf)
return [
"/usr/local/bin/repo-health-checker",
"-root=.",
f"-repoSize={str(repo_conf.health_check.max_size / 1024 / 1024)}", # B -> MB
*[f"-meta={meta}" for meta in repo_conf.health_check.required_files],
f"-checkFileSumList={','.join(file_sums)}",
f"-checkFileNameList={','.join(file_paths)}",
f"-repoSize={str(repo_conf.max_size)}",
*[f"-meta={meta}" for meta in repo_conf.files.required],
f"-checkFileSumList={','.join(get_hashes(repo_conf))}",
f"-checkFileNameList={','.join(repo_conf.files.immutable)}",
]
@ -158,34 +65,28 @@ def get_teapot_check_args(repo_conf: repo.Config, task_conf: task.Config) -> Lis
str(TEAPOT_CONFIG_ROOT / "teapot.env"),
"--grading-repo-name",
repo_conf.grading_repo_name,
"--scoreboard-filename",
task_conf.scoreboard,
]
if repo_conf.groups.name:
group_str = lambda groups: ",".join(
group_config_str = ",".join(
f"{name}={max_count}:{time_period}"
for name, max_count, time_period in zip(
groups.name,
groups.max_count,
groups.time_period_hour,
repo_conf.groups.name,
repo_conf.groups.max_count,
repo_conf.groups.time_period_hour,
)
)
group_config = group_str(
task_conf.groups if task_conf.groups.name else repo_conf.groups
)
res.extend(["--group-config", group_config])
if task_conf.time.begin:
res.extend(["--begin-time", task_conf.time.begin.strftime("%Y-%m-%dT%H:%M:%S")])
if task_conf.time.end:
res.extend(["--end-time", task_conf.time.end.strftime("%Y-%m-%dT%H:%M:%S")])
if task_conf.penalties.hours:
penalty_config = ",".join(
f"{hour}={factor}"
for hour, factor in zip(
task_conf.penalties.hours, task_conf.penalties.factors
if task_conf.groups.name:
overwrite_group_config_str = ",".join(
f"{name}={max_count}:{time_period}"
for name, max_count, time_period in zip(
task_conf.groups.name,
task_conf.groups.max_count,
task_conf.groups.time_period_hour,
)
)
res.extend(["--penalty-config", penalty_config])
res.extend(["--group-config", overwrite_group_config_str])
else:
res.extend(["--group-config", group_config_str])
return res
@ -199,8 +100,7 @@ def get_health_check_stage(
name="local",
with_=result.ExecutorWith(
default=result.Cmd(
cpu_limit=common.Time("10s"),
clock_limit=common.Time("20s"),
cpu_limit=common.Time("10s"), clock_limit=common.Time("20s")
),
cases=[
result.OptionalCmd(
@ -208,7 +108,7 @@ def get_health_check_stage(
),
result.OptionalCmd(
args=get_teapot_check_args(repo_conf, task_conf),
env=get_teapot_env(repo_conf),
env=[f"LOG_FILE_PATH={TEAPOT_LOG_PATH}"],
),
],
),
@ -216,7 +116,7 @@ def get_health_check_stage(
parsers=[
result.Parser(
name="healthcheck",
with_=result.ScoreConfig(score=repo_conf.health_check.score),
with_=result.ScoreConfig(score=repo_conf.health_check_score),
),
result.Parser(name="debug"),
],
@ -230,3 +130,12 @@ def calc_sha256sum(file_path: Path) -> str:
for byte_block in iter(lambda: f.read(64 * 1024), b""):
sha256_hash.update(byte_block)
return sha256_hash.hexdigest()
def get_hashes(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

@ -2,10 +2,15 @@ import re
import shlex
from functools import partial
from pathlib import Path, PurePosixPath
from typing import Any, Callable, Dict, List, Optional, Tuple, Union
from typing import Any, Callable, Dict, List, Optional, Tuple
from joj3_config_generator.models import result, task
from joj3_config_generator.models.const import DEFAULT_PATH_ENV, JOJ3_CONFIG_ROOT
from joj3_config_generator.models.common import Memory, Time
from joj3_config_generator.models.const import (
DEFAULT_CLOCK_LIMIT_MULTIPLIER,
DEFAULT_PATH_ENV,
JOJ3_CONFIG_ROOT,
)
from joj3_config_generator.utils.logger import logger
@ -56,7 +61,7 @@ def get_parser_handler_map(
task.Parser.CPPLINT: (fix_keyword, task_stage.cpplint),
task.Parser.RESULT_DETAIL: (fix_result_detail, task_stage.result_detail),
task.Parser.DUMMY: (fix_dummy, task_stage.dummy),
task.Parser.RESULT_STATUS: (fix_result_status, task_stage.result_status),
task.Parser.RESULT_STATUS: (fix_dummy, task_stage.result_status),
task.Parser.FILE: (fix_file, task_stage.file),
task.Parser.DIFF: (
partial(
@ -74,38 +79,33 @@ def get_parser_handler_map(
def get_executor_with(
task_stage: task.Stage, cached: Dict[str, None]
) -> result.ExecutorWith:
copy_in: dict[
str,
Union[result.LocalFile, result.MemoryFile, result.PreparedFile, result.Symlink],
] = {
file: result.LocalFile(src=str(JOJ3_CONFIG_ROOT / file))
for file in task_stage.files.import_
if file not in cached
}
for src, dst in task_stage.files.import_map.items():
if dst in cached:
continue
copy_in[dst] = result.LocalFile(src=str(JOJ3_CONFIG_ROOT / src))
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),
env=[DEFAULT_PATH_ENV, *task_stage.env],
copy_in=copy_in,
copy_in={
file: result.LocalFile(src=str(JOJ3_CONFIG_ROOT / file))
# all copyin files store in this tools folder
# TODO: are there any corner cases?
for file in copy_in_files
},
copy_in_dir="." if task_stage.copy_in_cwd else "",
copy_out=copy_out_files,
copy_in_cached={file: file for file in cached},
copy_out_cached=file_export,
cpu_limit=task_stage.limit.cpu,
clock_limit=task_stage.limit.time,
memory_limit=task_stage.limit.mem,
cpu_limit=Time(task_stage.limit.cpu),
clock_limit=DEFAULT_CLOCK_LIMIT_MULTIPLIER * Time(task_stage.limit.cpu),
memory_limit=Memory(task_stage.limit.mem),
proc_limit=task_stage.limit.proc,
stderr=result.Collector(
name="stderr", pipe=True, max=task_stage.limit.stderr
name="stderr", pipe=True, max=Memory(task_stage.limit.stderr)
),
stdout=result.Collector(
name="stdout", pipe=True, max=task_stage.limit.stdout
name="stdout", pipe=True, max=Memory(task_stage.limit.stdout)
),
),
cases=[],
@ -124,11 +124,10 @@ def fix_keyword(
for keyword, score in zip(keyword_config.keyword, keyword_config.weight):
score_groups.setdefault(score, []).append(keyword)
keyword_parser.with_ = result.KeywordMatchConfig(
score=keyword_config.score,
matches=[
result.KeywordConfig(keywords=keywords, score=score)
for score, keywords in score_groups.items()
],
]
)
@ -151,29 +150,17 @@ def fix_result_detail(
show_memory=result_detail_parser_config.mem,
show_error=result_detail_parser_config.error,
show_proc_peak=result_detail_parser_config.proc_peak,
files_in_code_block=result_detail_parser_config.code_block,
max_file_length=result_detail_parser_config.max_length,
)
def fix_dummy(
dummy_parser_config: task.ParserDummy, dummy_parser: result.Parser
) -> None:
# we don't use dummy parser in real application
dummy_parser.with_ = result.DummyConfig(
score=dummy_parser_config.score,
comment=dummy_parser_config.comment,
force_quit=dummy_parser_config.force_quit,
)
def fix_result_status(
result_status_parser_config: task.ParserResultStatus,
result_status_parser: result.Parser,
) -> None:
result_status_parser.with_ = result.ResultStatusConfig(
score=result_status_parser_config.score,
comment=result_status_parser_config.comment,
force_quit_on_not_accepted=result_status_parser_config.force_quit,
force_quit_on_not_accepted=dummy_parser_config.force_quit,
)
@ -193,7 +180,11 @@ def fix_diff(
# cases not specified in the toml config (auto-detected)
unspecified_cases = get_unspecified_cases(task_root, task_path, task_stage.cases)
# cases specified in toml config but not skipped
specified_cases = [(case, task_stage.cases[case]) for case in task_stage.cases]
specified_cases = [
(case, task_stage.cases[case])
for case in task_stage.cases
if case not in task_stage.skip
]
stage_cases = []
parser_cases = []
for case_name, case in specified_cases:
@ -209,7 +200,7 @@ def fix_diff(
stdin=stdin,
args=shlex.split(case.command) if case.command else None,
cpu_limit=case.limit.cpu,
clock_limit=case.limit.time,
clock_limit=DEFAULT_CLOCK_LIMIT_MULTIPLIER * case.limit.cpu,
memory_limit=case.limit.mem,
proc_limit=task_stage.limit.proc,
)
@ -224,24 +215,18 @@ def fix_diff(
if cmd.proc_limit == executor.with_.default.proc_limit:
cmd.proc_limit = None
stage_cases.append(cmd)
def get_diff_attribute(attribute_name: str) -> Any:
if case.diff and attribute_name in case.diff.model_fields_set:
return getattr(case.diff, attribute_name)
return getattr(task_stage.diff, attribute_name)
parser_case = result.DiffCasesConfig(
outputs=[
result.DiffOutputConfig(
score=get_diff_attribute("score"),
filename="stdout",
score=case.diff.output.score,
file_name="stdout",
answer_path=stdout,
compare_space=not get_diff_attribute("ignore_spaces"),
always_hide=get_diff_attribute("hide"),
force_quit_on_diff=get_diff_attribute("force_quit"),
max_diff_length=get_diff_attribute("max_length"),
max_diff_lines=get_diff_attribute("max_lines"),
hide_common_prefix=get_diff_attribute("hide_common_prefix"),
force_quit_on_diff=case.diff.output.force_quit,
always_hide=case.diff.output.hide,
compare_space=not case.diff.output.ignore_spaces,
max_diff_length=case.diff.output.max_length,
max_diff_lines=case.diff.output.max_lines,
hide_common_prefix=case.diff.output.hide_common_prefix,
)
]
)
@ -249,20 +234,18 @@ def fix_diff(
for case_name in unspecified_cases:
cmd = result.OptionalCmd(
stdin=result.LocalFile(src=str(base_dir / f"{case_name}.in")),
cpu_limit=None,
clock_limit=None,
memory_limit=None,
proc_limit=None,
)
stage_cases.append(cmd)
parser_case = result.DiffCasesConfig(
outputs=[
result.DiffOutputConfig(
score=task_stage.diff.score,
filename="stdout",
score=task_stage.diff.default_score,
file_name="stdout",
answer_path=str(base_dir / f"{case_name}.out"),
compare_space=not task_stage.diff.ignore_spaces,
always_hide=task_stage.diff.hide,
force_quit_on_diff=task_stage.diff.force_quit,
max_diff_length=task_stage.diff.max_length,
max_diff_lines=task_stage.diff.max_lines,
hide_common_prefix=task_stage.diff.hide_common_prefix,
)
]
)
@ -304,22 +287,16 @@ def get_unspecified_cases(
def get_stdin_stdout(
task_root: Path, task_path: Path, case_name: str, case: task.Case
) -> Tuple[result.Stdin, Optional[str]]:
base_dir = (task_root / task_path).parent
case_stdout_name = case.out_ if case.out_ else f"{case_name}.out"
stdin: result.Stdin = result.MemoryFile(content="")
stdout = None
for case_stdout_path in base_dir.glob("**/*.out"):
if not case.out_: # if not set, look for .out files with case name
if case_stdout_path.name != f"{case_name}.out":
continue
else: # if set, look for .out files with the same relative path
if PurePosixPath(case.out_) != PurePosixPath(case_stdout_path).relative_to(
base_dir
):
for case_stdout_path in (task_root / task_path).parent.glob("**/*.out"):
if case_stdout_path.name != case_stdout_name:
continue
stdout = str(JOJ3_CONFIG_ROOT / case_stdout_path.relative_to(task_root))
case_stdin_path = case_stdout_path.with_suffix(".in")
if case.in_:
case_stdin_path = Path(base_dir / case.in_)
case_stdin_path = Path((task_root / task_path).parent / case.in_)
if not case_stdin_path.exists():
logger.warning(
f"In file {task_root / task_path}, "

View File

@ -3,13 +3,14 @@ grading-repo-name = "ece280-joj"
sandbox-token = "test"
# reconfigure later
max-total-score = 1000
health-check.max-size = "50.5m"
health-check.immutable-path = "immutable"
health-check.required-files = ["README.md", "Changelog.md"]
max-size = 50.5
# for tests
[groups]
name = ["joj", "run"]
max-count = [1000, 1000]
time-period-hour = [24, 24]
[files]
required = ["README.md", "Changelog.md"]
immutable = [".gitignore", ".gitattributes",".gitea/workflows/push.yaml", ".gitea/workflows/release.yaml"]

View File

@ -1,8 +1,8 @@
{
"name": "hw7 ex2",
"logPath": "/home/tt/.cache/joj3/hw7/joj3.log",
"expireUnixTimestamp": 0,
"effectiveUnixTimestamp": 0,
"logPath": "/home/tt/.cache/joj3/joj3.log",
"expireUnixTimestamp": 1735574399,
"effectiveUnixTimestamp": 1735487999,
"actorCsvPath": "/home/tt/.config/joj/students.csv",
"maxTotalScore": 10245871,
"stage": {
@ -64,8 +64,8 @@
"-repoSize=50.5",
"-meta=README.md",
"-meta=Changelog.md",
"-checkFileSumList=b1bbad25b830db0a77b15a033f9ca1b7ab44c1d2d05056412bd3e4421645f0bf,2ba059f3977e2e3dee6cacbfbf0ba2578baa1b8e04b4977aec400868b6e49856,3db23f7fb2ca9814617e767ddc41b77073180b3b0b73e87b5f2a6d3129f88f3a,a5b63323a692d3d8b952442969649b4f823d58dae26429494f613df160710dfc",
"-checkFileNameList=.gitattributes,.gitea/workflows/push.yaml,.gitea/workflows/release.yaml,.gitignore"
"-checkFileSumList=a5b63323a692d3d8b952442969649b4f823d58dae26429494f613df160710dfc,b1bbad25b830db0a77b15a033f9ca1b7ab44c1d2d05056412bd3e4421645f0bf,2ba059f3977e2e3dee6cacbfbf0ba2578baa1b8e04b4977aec400868b6e49856,3db23f7fb2ca9814617e767ddc41b77073180b3b0b73e87b5f2a6d3129f88f3a",
"-checkFileNameList=.gitignore,.gitattributes,.gitea/workflows/push.yaml,.gitea/workflows/release.yaml"
]
},
{
@ -75,19 +75,10 @@
"/home/tt/.config/teapot/teapot.env",
"--grading-repo-name",
"ece280-joj",
"--scoreboard-filename",
"scoreboard-hw7.csv",
"--group-config",
"Manuel=500:24,Boming=501:48,Nuvole=502:72",
"--begin-time",
"2024-12-29T23:59:59",
"--end-time",
"2024-12-30T23:59:59",
"--penalty-config",
"24.0=0.5,48.0=0.25,72.0=0.1"
"Manuel=500:24,Boming=501:48,Nuvole=502:72"
],
"env": [
"REPOS_DIR=/home/tt/.cache",
"LOG_FILE_PATH=/home/tt/.cache/joint-teapot-debug.log"
]
}
@ -145,9 +136,6 @@
"copyIn": {
"tools/compile": {
"src": "/home/tt/.config/joj/tools/compile"
},
"h7/Makefile": {
"src": "/home/tt/.config/joj/tools/Makefile"
}
},
"copyInCached": {},
@ -272,7 +260,6 @@
{
"name": "keyword",
"with": {
"score": 0,
"matches": [
{
"keywords": [
@ -381,7 +368,6 @@
{
"name": "clangtidy",
"with": {
"score": 0,
"matches": [
{
"keywords": [
@ -512,14 +498,12 @@
{
"name": "keyword",
"with": {
"score": 0,
"matches": []
}
},
{
"name": "cppcheck",
"with": {
"score": 0,
"matches": [
{
"keywords": [
@ -542,7 +526,6 @@
{
"name": "clangtidy",
"with": {
"score": 0,
"matches": []
}
},
@ -568,7 +551,6 @@
{
"name": "cpplint",
"with": {
"score": 0,
"matches": []
}
},
@ -577,7 +559,7 @@
"with": {
"score": 0,
"comment": "",
"forceQuitOnNotAccepted": true
"forceQuitOnNotAccepted": false
}
},
{
@ -591,7 +573,7 @@
"with": {
"score": 0,
"comment": "",
"forceQuit": false
"forceQuitOnNotAccepted": false
}
},
{
@ -669,7 +651,6 @@
{
"name": "cpplint",
"with": {
"score": 0,
"matches": [
{
"keywords": [
@ -741,7 +722,7 @@
"pipe": true
},
"cpuLimit": 1000000000,
"clockLimit": 100000000000,
"clockLimit": 2000000000,
"memoryLimit": 134217728,
"stackLimit": 0,
"procLimit": 50,
@ -768,27 +749,7 @@
"dataSegmentLimit": false,
"addressSpaceLimit": false
},
"cases": [
{
"stdin": {
"content": ""
},
"cpuLimit": 500000000,
"clockLimit": 1000000000,
"memoryLimit": 5242880
},
{
"stdin": {
"content": ""
},
"memoryLimit": 5242880
},
{
"stdin": {
"content": ""
}
}
]
"cases": []
}
},
"parsers": [
@ -796,53 +757,7 @@
"name": "diff",
"with": {
"name": "diff",
"cases": [
{
"outputs": [
{
"score": 10,
"filename": "stdout",
"answerPath": "/home/tt/.config/joj/basic/cases/case0.out",
"compareSpace": false,
"alwaysHide": false,
"forceQuitOnDiff": false,
"maxDiffLength": 2048,
"maxDiffLines": 50,
"hideCommonPrefix": false
}
]
},
{
"outputs": [
{
"score": 5,
"filename": "stdout",
"answerPath": "/home/tt/.config/joj/basic/cases/case1.out",
"compareSpace": false,
"alwaysHide": false,
"forceQuitOnDiff": false,
"maxDiffLength": 2048,
"maxDiffLines": 50,
"hideCommonPrefix": false
}
]
},
{
"outputs": [
{
"score": 10,
"filename": "stdout",
"answerPath": "/home/tt/.config/joj/basic/cases/case2.out",
"compareSpace": false,
"alwaysHide": true,
"forceQuitOnDiff": false,
"maxDiffLength": 2048,
"maxDiffLines": 50,
"hideCommonPrefix": false
}
]
}
]
"cases": []
}
},
{
@ -883,20 +798,9 @@
"--grading-repo-name",
"ece280-joj",
"--max-total-score",
"10245871",
"--issue-label-name",
"Kind/Testing",
"--issue-label-color",
"#795548",
"--scoreboard-filename",
"scoreboard-hw7.csv",
"--end-time",
"2024-12-30T23:59:59",
"--penalty-config",
"24.0=0.5,48.0=0.25,72.0=0.1"
"10245871"
],
"env": [
"REPOS_DIR=/home/tt/.cache",
"LOG_FILE_PATH=/home/tt/.cache/joint-teapot-debug.log"
],
"stdin": {

View File

@ -2,13 +2,8 @@
task.name = "hw7 ex2" # task name
max-total-score = 10245871
time.end = 2024-12-30 23:59:59+08:00
time.begin = 2024-12-29 23:59:59+08:00
scoreboard = "auto"
[penalties]
hours = [ 24, 48, 72 ]
factors = [ 0.5, 0.25, 0.1 ]
release.end-time = 2024-12-30 23:59:59+08:00
release.begin-time = 2024-12-29 23:59:59+08:00
[groups]
name = ["Manuel", "Boming", "Nuvole"]
@ -20,8 +15,8 @@ name = "Compilation"
env = [ "CC=clang", "CXX=clang++" ]
command = "./tools/compile" # eg. script running cmake commands
files.import = [ "tools/compile" ]
files.import-map = { "tools/Makefile" = "h7/Makefile" }
files.export = [ "h7/build/ex2", "h7/build/ex2-asan", "h7/build/ex2-ubsan", "h7/build/ex2-msan", "h7/build/compile_commands.json" ]
score = 1
# compile parsers
parsers = [ "result-detail", "result-status" ]
@ -91,29 +86,24 @@ command="./h7/build/ex2-asan -a"
copy-in-cwd = false
files.import = [ "h7/build/ex2-asan" ]
limit.mem = "128m"
limit.time = "100s"
diff.score = 10
parsers = [ "diff", "result-detail" ]
result-detail.exit-status = 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.time = "1s"
case0.limit.mem = "5m"
case0.diff.ignore-spaces = true
case0.diff.output.ignore-spaces = true
#case0.limit.stdout = 8
#case0.command = "./h7/build/ex2"
case0.in = "case0.in"
case1.diff.score = 5
case1.diff.output.score = 5
case1.limit.cpu = "1s"
case1.limit.mem = "5m"
case1.diff.ignore-spaces = true
case1.diff.output.ignore-spaces = true
#case1.limit.stdout = 8
#case1.command = "./h7/build/ex2"
case1.in = "case1.in"
case2.diff.hide = true

View File

@ -1,8 +1,8 @@
{
"name": "hw7 ex2",
"logPath": "/home/tt/.cache/joj3/hw7/joj3.log",
"expireUnixTimestamp": 0,
"effectiveUnixTimestamp": 0,
"logPath": "/home/tt/.cache/joj3/joj3.log",
"expireUnixTimestamp": 1735574399,
"effectiveUnixTimestamp": 1735487999,
"actorCsvPath": "/home/tt/.config/joj/students.csv",
"maxTotalScore": 100,
"stage": {
@ -78,7 +78,6 @@
{
"name": "clangtidy",
"with": {
"score": 0,
"matches": [
{
"keywords": [

View File

@ -1,8 +1,8 @@
# general task configuration
task.name = "hw7 ex2" # task name
time.end = 2024-12-30 23:59:59+08:00
time.begin = 2024-12-29 23:59:59+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 = "[cq] Clang-tidy"

View File

@ -1,8 +1,8 @@
{
"name": "hw7 ex2",
"logPath": "/home/tt/.cache/joj3/hw7/joj3.log",
"expireUnixTimestamp": 0,
"effectiveUnixTimestamp": 0,
"logPath": "/home/tt/.cache/joj3/joj3.log",
"expireUnixTimestamp": 1735574399,
"effectiveUnixTimestamp": 1735487999,
"actorCsvPath": "/home/tt/.config/joj/students.csv",
"maxTotalScore": 100,
"stage": {
@ -71,7 +71,6 @@
{
"name": "cppcheck",
"with": {
"score": 0,
"matches": [
{
"keywords": [

View File

@ -1,8 +1,8 @@
# general task configuration
task.name = "hw7 ex2" # task name
time.end = 2024-12-30 23:59:59+08:00
time.begin = 2024-12-29 23:59:59+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 = "[cq] Cppcheck"

View File

@ -1,8 +1,8 @@
{
"name": "hw7 ex2",
"logPath": "/home/tt/.cache/joj3/hw7/joj3.log",
"expireUnixTimestamp": 0,
"effectiveUnixTimestamp": 0,
"logPath": "/home/tt/.cache/joj3/joj3.log",
"expireUnixTimestamp": 1735574399,
"effectiveUnixTimestamp": 1735487999,
"actorCsvPath": "/home/tt/.config/joj/students.csv",
"maxTotalScore": 100,
"stage": {
@ -70,7 +70,6 @@
{
"name": "cpplint",
"with": {
"score": 0,
"matches": [
{
"keywords": [

View File

@ -1,8 +1,8 @@
# general task configuration
task.name = "hw7 ex2" # task name
time.end = 2024-12-30 23:59:59+08:00
time.begin = 2024-12-29 23:59:59+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 = "[cq] Cpplint"

View File

@ -1,8 +1,8 @@
{
"name": "hw7 ex2",
"logPath": "/home/tt/.cache/joj3/hw7/joj3.log",
"expireUnixTimestamp": 0,
"effectiveUnixTimestamp": 0,
"logPath": "/home/tt/.cache/joj3/joj3.log",
"expireUnixTimestamp": 1735574399,
"effectiveUnixTimestamp": 1735487999,
"actorCsvPath": "/home/tt/.config/joj/students.csv",
"maxTotalScore": 100,
"stage": {
@ -144,7 +144,7 @@
"outputs": [
{
"score": 5,
"filename": "stdout",
"fileName": "stdout",
"answerPath": "/home/tt/.config/joj/diff/case0.out",
"compareSpace": false,
"alwaysHide": false,
@ -159,7 +159,7 @@
"outputs": [
{
"score": 123214122421,
"filename": "stdout",
"fileName": "stdout",
"answerPath": "/home/tt/.config/joj/diff/case1.out",
"compareSpace": false,
"alwaysHide": false,
@ -174,7 +174,7 @@
"outputs": [
{
"score": 1232131,
"filename": "stdout",
"fileName": "stdout",
"answerPath": "/home/tt/.config/joj/diff/case9.out",
"compareSpace": false,
"alwaysHide": false,
@ -189,7 +189,7 @@
"outputs": [
{
"score": 92321,
"filename": "stdout",
"fileName": "stdout",
"answerPath": "/home/tt/.config/joj/diff/task1/subtask1/case11.out",
"compareSpace": false,
"alwaysHide": false,
@ -204,7 +204,7 @@
"outputs": [
{
"score": 823131,
"filename": "stdout",
"fileName": "stdout",
"answerPath": "/home/tt/.config/joj/diff/task1/subtask1/case10.out",
"compareSpace": false,
"alwaysHide": false,
@ -219,7 +219,7 @@
"outputs": [
{
"score": 2590,
"filename": "stdout",
"fileName": "stdout",
"answerPath": "/home/tt/.config/joj/diff/task1/case5.out",
"compareSpace": false,
"alwaysHide": false,
@ -234,7 +234,7 @@
"outputs": [
{
"score": 100,
"filename": "stdout",
"fileName": "stdout",
"answerPath": "/home/tt/.config/joj/diff/case2.out",
"compareSpace": false,
"alwaysHide": false,
@ -249,7 +249,7 @@
"outputs": [
{
"score": 100,
"filename": "stdout",
"fileName": "stdout",
"answerPath": "/home/tt/.config/joj/diff/task1/case4.out",
"compareSpace": false,
"alwaysHide": false,
@ -264,7 +264,7 @@
"outputs": [
{
"score": 100,
"filename": "stdout",
"fileName": "stdout",
"answerPath": "/home/tt/.config/joj/diff/task2/case6.out",
"compareSpace": false,
"alwaysHide": false,
@ -279,7 +279,7 @@
"outputs": [
{
"score": 100,
"filename": "stdout",
"fileName": "stdout",
"answerPath": "/home/tt/.config/joj/diff/task2/case7.out",
"compareSpace": false,
"alwaysHide": false,
@ -294,7 +294,7 @@
"outputs": [
{
"score": 100,
"filename": "stdout",
"fileName": "stdout",
"answerPath": "/home/tt/.config/joj/diff/task2/case8.out",
"compareSpace": false,
"alwaysHide": false,

View File

@ -1,8 +1,8 @@
# general task configuration
task.name = "hw7 ex2" # task name
time.end = 2024-12-30 23:59:59+08:00
time.begin = 2024-12-29 23:59:59+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 = "[joj] ex2-asan"
@ -17,26 +17,26 @@ parsers = [ "diff", "result-detail" ]
result-detail.exit-status = true
result-detail.stderr = true
diff.score = 100
diff.default-score = 100
case0.diff.score = 5
case0.diff.output.score = 5
case0.limit.cpu = "1s"
case0.limit.mem = "2m"
case0.diff.ignore-spaces = true
case0.diff.output.ignore-spaces = true
case0.command = "./h7/build/ex2"
case0.in = "case0.in"
case1.diff.score = 123214122421
case1.diff.output.score = 123214122421
case1.limit.cpu = "2s"
case1.limit.mem = "4m"
case1.diff.ignore-spaces = true
case1.diff.output.ignore-spaces = true
case1.command = "./h7/build/ex2"
case9.diff.score = 1232131
case9.diff.output.score = 1232131
case9.limit.mem = "10m"
case11.diff.score = 92321
case11.diff.output.score = 92321
case10.diff.score = 823131
case10.diff.output.score = 823131
case5.diff.score = 2590
case5.diff.output.score = 2590

View File

@ -1,8 +1,8 @@
{
"name": "hw7 ex2",
"logPath": "/home/tt/.cache/joj3/hw7/joj3.log",
"expireUnixTimestamp": 0,
"effectiveUnixTimestamp": 0,
"logPath": "/home/tt/.cache/joj3/joj3.log",
"expireUnixTimestamp": 1735574399,
"effectiveUnixTimestamp": 1735487999,
"actorCsvPath": "/home/tt/.config/joj/students.csv",
"maxTotalScore": 100,
"stage": {
@ -74,7 +74,6 @@
{
"name": "elf",
"with": {
"score": 0,
"matches": [
{
"keywords": [

View File

@ -1,8 +1,8 @@
# general task configuration
task.name = "hw7 ex2" # task name
time.end = 2024-12-30 23:59:59+08:00
time.begin = 2024-12-29 23:59:59+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 = "[cq] elf"

View File

@ -1,182 +0,0 @@
{
"name": "health check",
"logPath": "/home/tt/.cache/joj3/health/joj3.log",
"expireUnixTimestamp": 0,
"effectiveUnixTimestamp": 0,
"actorCsvPath": "/home/tt/.config/joj/students.csv",
"maxTotalScore": 100,
"stage": {
"sandboxExecServer": "172.17.0.1:5051",
"sandboxToken": "",
"outputPath": "/tmp/joj3_result.json",
"stages": [
{
"name": "Health Check",
"group": "",
"executor": {
"name": "local",
"with": {
"default": {
"args": [],
"env": [
"PATH=/usr/bin:/bin:/usr/local/bin"
],
"stdin": {
"content": ""
},
"stdout": {
"name": "stdout",
"max": 33554432,
"pipe": true
},
"stderr": {
"name": "stderr",
"max": 33554432,
"pipe": true
},
"cpuLimit": 10000000000,
"clockLimit": 20000000000,
"memoryLimit": 268435456,
"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": [
{
"args": [
"/usr/local/bin/repo-health-checker",
"-root=.",
"-repoSize=10.0",
"-checkFileSumList=",
"-checkFileNameList="
]
},
{
"args": [
"/usr/local/bin/joint-teapot",
"joj3-check-env",
"/home/tt/.config/teapot/teapot.env",
"--grading-repo-name",
"JOJ3-config-generator",
"--scoreboard-filename",
"scoreboard.csv"
],
"env": [
"REPOS_DIR=/home/tt/.cache",
"LOG_FILE_PATH=/home/tt/.cache/joint-teapot-debug.log"
]
}
]
}
},
"parsers": [
{
"name": "healthcheck",
"with": {
"score": 0
}
},
{
"name": "debug",
"with": {}
}
]
}
],
"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",
"JOJ3-config-generator",
"--max-total-score",
"0",
"--issue-label-name",
"Kind/Testing",
"--issue-label-color",
"#795548",
"--scoreboard-filename",
"scoreboard.csv"
],
"env": [
"REPOS_DIR=/home/tt/.cache",
"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": 30000000000,
"clockLimit": 60000000000,
"memoryLimit": 268435456,
"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"
}
},
{
"name": "debug",
"with": {}
}
]
}
]
}
}

View File

@ -1,2 +0,0 @@
name = "health check"
max-total-score = 0

View File

@ -1,33 +0,0 @@
*.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

@ -1,19 +0,0 @@
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

@ -1,21 +0,0 @@
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 "${{ gitea.ref_name }}"

View File

@ -1,23 +0,0 @@
################################
## 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

@ -1,23 +0,0 @@
sandbox-token = "" # sandbox token
health-check.score = 0 # score for health check stage
health-check.max-size = "10m" # max size of the repository
health-check.immutable-path = "immutable" # path for immutable files, relative to the path of repo.toml
health-check.required-files = ["README.md", "Changelog.md"] # required files name, case insensitive
issue.label.name = "Kind/Testing" # label for issues
issue.label.color = "#795548" # color for the label
issue.label.exclusive = false # whether the label is exclusive
issue.show-submitter = true # whether to show submitter in the issue title
# fields below can be overridden by task.toml
max-total-score = 100 # maximum total score for the task
# submission count limit groups
# explanation of the following config:
# in last 1 hour, total submission <= 50 times
# in last 24 hours, submission includes group "joj" <= 1000 times
# in last 2 hours, submission includes group "run" <= 100 times
groups.name = ["", "joj", "run"] # names of the groups
groups.max-count = [50, 1000, 100] # maximum submission count for each group
groups.time-period-hour = [1, 24, 2] # time period in hour for each group

View File

@ -1,512 +0,0 @@
{
"name": "hw7 ex3",
"logPath": "/home/tt/.cache/joj3/hw7/joj3.log",
"expireUnixTimestamp": 0,
"effectiveUnixTimestamp": 0,
"actorCsvPath": "/home/tt/.config/joj/students.csv",
"maxTotalScore": 100,
"stage": {
"sandboxExecServer": "172.17.0.1:5051",
"sandboxToken": "",
"outputPath": "/tmp/joj3_result.json",
"stages": [
{
"name": "Health Check",
"group": "",
"executor": {
"name": "local",
"with": {
"default": {
"args": [],
"env": [
"PATH=/usr/bin:/bin:/usr/local/bin"
],
"stdin": {
"content": ""
},
"stdout": {
"name": "stdout",
"max": 33554432,
"pipe": true
},
"stderr": {
"name": "stderr",
"max": 33554432,
"pipe": true
},
"cpuLimit": 10000000000,
"clockLimit": 20000000000,
"memoryLimit": 268435456,
"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": [
{
"args": [
"/usr/local/bin/repo-health-checker",
"-root=.",
"-repoSize=10.0",
"-meta=README.md",
"-meta=Changelog.md",
"-checkFileSumList=b1bbad25b830db0a77b15a033f9ca1b7ab44c1d2d05056412bd3e4421645f0bf,2ba059f3977e2e3dee6cacbfbf0ba2578baa1b8e04b4977aec400868b6e49856,3db23f7fb2ca9814617e767ddc41b77073180b3b0b73e87b5f2a6d3129f88f3a,a5b63323a692d3d8b952442969649b4f823d58dae26429494f613df160710dfc",
"-checkFileNameList=.gitattributes,.gitea/workflows/push.yaml,.gitea/workflows/release.yaml,.gitignore"
]
},
{
"args": [
"/usr/local/bin/joint-teapot",
"joj3-check-env",
"/home/tt/.config/teapot/teapot.env",
"--grading-repo-name",
"JOJ3-config-generator",
"--scoreboard-filename",
"scoreboard-hw7.csv",
"--group-config",
"joj=1000:24,run=100:1",
"--begin-time",
"2024-12-29T23:59:59",
"--end-time",
"2024-12-30T23:59:59",
"--penalty-config",
"24.0=0.75,48.0=0.5,72.0=0.25"
],
"env": [
"REPOS_DIR=/home/tt/.cache",
"LOG_FILE_PATH=/home/tt/.cache/joint-teapot-debug.log"
]
}
]
}
},
"parsers": [
{
"name": "healthcheck",
"with": {
"score": 0
}
},
{
"name": "debug",
"with": {}
}
]
},
{
"name": "Generate yes.txt [no]",
"group": "no",
"executor": {
"name": "sandbox",
"with": {
"default": {
"args": [
"sh",
"-c",
"yes | head -n 10 > yes.txt"
],
"env": [
"PATH=/usr/bin:/bin:/usr/local/bin",
"THE_ANSWER=42"
],
"stdin": {
"content": ""
},
"stdout": {
"name": "stdout",
"max": 33554432,
"pipe": true
},
"stderr": {
"name": "stderr",
"max": 33554432,
"pipe": true
},
"cpuLimit": 1000000000,
"clockLimit": 2000000000,
"memoryLimit": 268435456,
"stackLimit": 0,
"procLimit": 50,
"cpuRateLimit": 0,
"cpuSetLimit": "",
"copyIn": {
"tools/filelength": {
"src": "/home/tt/.config/joj/tools/filelength"
},
"h7/Makefile": {
"src": "/home/tt/.config/joj/tools/Makefile"
}
},
"copyInCached": {},
"copyInDir": ".",
"copyOut": [
"stdout",
"stderr"
],
"copyOutCached": [
"yes.txt"
],
"copyOutMax": 0,
"copyOutDir": "",
"tty": false,
"strictMemoryLimit": false,
"dataSegmentLimit": false,
"addressSpaceLimit": false
},
"cases": [
{
"stdin": {
"src": "/home/tt/.config/joj/full/cases/case0.in"
}
},
{
"stdin": {
"src": "/home/tt/.config/joj/full/cases/case2.in"
},
"cpuLimit": 2000000000,
"memoryLimit": 536870912
},
{
"stdin": {
"src": "/home/tt/.config/joj/full/cases/case1.in"
}
},
{
"stdin": {
"src": "/home/tt/.config/joj/full/other/cases/case3.in"
}
}
]
}
},
"parsers": [
{
"name": "result-status",
"with": {
"score": 0,
"comment": "Congrats! There is a yes.txt file generated!",
"forceQuitOnNotAccepted": true
}
},
{
"name": "result-detail",
"with": {
"score": 0,
"comment": "",
"showExxecutorStatus": true,
"showExitStatus": true,
"showError": false,
"showTime": true,
"showMemory": true,
"showRuntime": true,
"showProcPeak": false,
"showFiles": [],
"filesInCodeBlock": true,
"maxFileLength": 2048
}
},
{
"name": "keyword",
"with": {
"score": 0,
"matches": [
{
"keywords": [
"aaa"
],
"score": 20
},
{
"keywords": [
"bbb"
],
"score": 10
},
{
"keywords": [
"ccc"
],
"score": 5
}
]
}
},
{
"name": "clangtidy",
"with": {
"score": 0,
"matches": [
{
"keywords": [
"clang-diagnostic",
"clang-analyzer",
"misc",
"performance",
"portability"
],
"score": 5
}
]
}
},
{
"name": "cppcheck",
"with": {
"score": 0,
"matches": [
{
"keywords": [
"error"
],
"score": 15
},
{
"keywords": [
"warning",
"portability",
"performance",
"style"
],
"score": 5
}
]
}
},
{
"name": "cpplint",
"with": {
"score": 0,
"matches": [
{
"keywords": [
"runtime"
],
"score": 5
},
{
"keywords": [
"readability"
],
"score": 20
},
{
"keywords": [
"build"
],
"score": 10
}
]
}
},
{
"name": "elf",
"with": {
"score": 0,
"matches": [
{
"keywords": [
"Parentheses"
],
"score": 100
},
{
"keywords": [
"Length"
],
"score": 300
},
{
"keywords": [
"Arity"
],
"score": 50
},
{
"keywords": [
"Repetitive"
],
"score": 80
}
]
}
},
{
"name": "dummy",
"with": {
"score": 0,
"comment": "",
"forceQuit": false
}
},
{
"name": "diff",
"with": {
"name": "diff",
"cases": [
{
"outputs": [
{
"score": 5,
"filename": "stdout",
"answerPath": "/home/tt/.config/joj/full/cases/case0.out",
"compareSpace": false,
"alwaysHide": false,
"forceQuitOnDiff": false,
"maxDiffLength": 2048,
"maxDiffLines": 50,
"hideCommonPrefix": false
}
]
},
{
"outputs": [
{
"score": 5,
"filename": "stdout",
"answerPath": "/home/tt/.config/joj/full/cases/case2.out",
"compareSpace": false,
"alwaysHide": false,
"forceQuitOnDiff": false,
"maxDiffLength": 2048,
"maxDiffLines": 50,
"hideCommonPrefix": false
}
]
},
{
"outputs": [
{
"score": 5,
"filename": "stdout",
"answerPath": "/home/tt/.config/joj/full/cases/case1.out",
"compareSpace": false,
"alwaysHide": false,
"forceQuitOnDiff": false,
"maxDiffLength": 2048,
"maxDiffLines": 50,
"hideCommonPrefix": false
}
]
},
{
"outputs": [
{
"score": 5,
"filename": "stdout",
"answerPath": "/home/tt/.config/joj/full/other/cases/case3.out",
"compareSpace": false,
"alwaysHide": false,
"forceQuitOnDiff": false,
"maxDiffLength": 2048,
"maxDiffLines": 50,
"hideCommonPrefix": false
}
]
}
]
}
}
]
}
],
"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",
"JOJ3-config-generator",
"--max-total-score",
"100",
"--issue-label-name",
"Kind/Testing",
"--issue-label-color",
"#795548",
"--scoreboard-filename",
"scoreboard-hw7.csv",
"--scoreboard-column-by-ref",
"--end-time",
"2024-12-30T23:59:59",
"--penalty-config",
"24.0=0.75,48.0=0.5,72.0=0.25"
],
"env": [
"REPOS_DIR=/home/tt/.cache",
"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": 30000000000,
"clockLimit": 60000000000,
"memoryLimit": 268435456,
"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"
}
},
{
"name": "debug",
"with": {}
}
]
}
]
}
}

View File

@ -1,206 +0,0 @@
name = "hw7 ex3" # task name, will be shown in the issue title
# scoreboard file name in grading repo, "auto" for automatic generation, default: scoreboard.csv
scoreboard = "auto"
# scoreboard = "scoreboard-42.csv" # use this if you want to specify a custom scoreboard
# whether to use gitea ref as scoreboard column, instead of the task name, default: false
scoreboard-column-by-ref = true
# task triggered not in this time period will not pass the health check
time.begin = 2024-12-29 23:59:59 # begin time, default: no start time, do not check
time.end = 2024-12-30 23:59:59 # end time, default: no end time, do not check
# explanation of the following config:
# if the submission is within 0-24 hours late from time.end,
# the final score will be multiplied by 0.75
# if the submission is within 24-48 hours, multiplied by 0.5
# if the submission is within 48-72 hours, multiplied by 0.25
penalties.hours = [24, 48, 72] # penalty hours for late submissions
penalties.factors = [0.75, 0.5, 0.25] # penalty factors for late submissions
# check repo.toml, fields below can override repo.toml
max-total-score = 100
groups.name = ["joj", "run"]
groups.max-count = [1000, 100]
groups.time-period-hour = [24, 1]
# list of stages
[[stages]]
# stage name, content in the `[]` set the group
# conventional commit message needs to contain the group name to run it
# group can be omitted so that every commit will run this stage
# e.g. commit msg "test(hw7): run yes" will not run this stage
# commit msg "test(hw7): run yes [no]" will run this stage
name = "Generate yes.txt [no]"
# ===================================================
# ========== executor related config start ==========
# ===================================================
# executor runs the command in a limited sandbox, each stage uses a unique new sandbox
# by default the sandbox does not share files and env vars with the host
# so we need to set env vars and import files to it, and export files for later stages
# limits can be applied on time, memory, file size, process count
# environment variables, will be set in the sandbox
# by default, "PATH=/usr/bin:/bin:/usr/local/bin" will be inserted in the front
env = ["THE_ANSWER=42"]
command = "sh -c 'yes | head -n 10 > yes.txt'" # command to run in the sandbox, use `sh -c` to run shell commands
# files to import into the sandbox, relative to `/home/tt/.config/joj/`
# e.g. this will copy `/home/tt/.config/joj/tools/filelength` in host
# to `/w/tools/filelength` in the sandbox as work dir in sandbox is `/w`
files.import = [ "tools/filelength" ]
# files to import into the sandbox
# key is the path in the host, value is the path in the sandbox
# e.g. this will copy `/home/tt/.config/joj/tools/Makefile` in host
# to `/w/h7/Makefile` in the sandbox
files.import-map = { "tools/Makefile" = "h7/Makefile" }
# files to export from the sandbox, relative to `/w`, will be imported to later
# stages automatically
files.export = [ "yes.txt" ]
# whether to copy all files from the current working directory from host (i.e. the whole repo)
# you can set it as false if you are in the run stage of a compiled language, as the binary is all you need
copy-in-cwd = true # default: true
# normally, you DO NOT need to change these default limits
limit.cpu = "1s" # CPU time limit, default: "1s"
limit.mem = "256m" # memory limit, default: "256m"
limit.time = "2s" # wall-clock time limit, if not set, will be 2x CPU time
limit.stdout = "32m" # stdout size limit, default: "32m"
limit.stderr = "32m" # stderr size limit, default: "32m"
limit.proc = 50 # process limit, default: 50
# =================================================
# ========== executor related config end ==========
# =================================================
# =================================================
# ========== parser related config start ==========
# =================================================
# parser parses the output of the executor and generates comments
# parsers to use for this stage
# parsers will be run in the order they are listed,
# which defines the order of the output comment
# all possible parsers are listed here
# usually, only one of the match keywords style parsers should be used in one stage
parsers = [
"result-status", # check if result status is Accepted
"result-detail", # show result details (CPU time, memory, etc.)
# ========== match keywords style parsers start ==========
"keyword", # match keywords in the output and score them
"clangtidy", # parse clang-tidy output, and match keywords
"cppcheck", # parse cppcheck output, and match keywords
"cpplint", # parse cpplint output, and match keywords
"elf", # parse elf (static analyzer for elm) output, and match keywords
# ========== match keywords style parsers end ==========
"dummy", # dummy parser, used to show a comment
"diff", # diff the output with the expected output
]
result-status.score = 0 # score added if result status is Accepted, default: 0
result-status.comment = "Congrats! There is a yes.txt file generated!" # comment to show if result status is Accepted, default: ""
result-status.force-quit = true # whether to force quit the stage if result status is not Accepted, default: true
result-detail.cpu-time = true # show CPU time, default: true
result-detail.mem = true # show memory usage, default: true
result-detail.time = true # show wall-clock time, default: true
result-detail.stdout = false # show stdout content, default: false
result-detail.stderr = false # show stderr content, default: false
result-detail.exit-status = true # show exit status, default: true
result-detail.proc-peak = false # show peak process count, default: false
result-detail.error = false # show error message, default: false
result-detail.code-block = true # show result in a code block, default: true
result-detail.max-length = 2048 # maximum length of the stdout/stderr content to show, longer content will be truncated, default: 2048
# explanation of the following config:
# if the output is "aaa bbb ccc aaa", then the score will be: 0 - 20 - 10 - 5 - 20 = -55
keyword.score = 0 # base score, default: 0
keyword.keyword = [ "aaa", "bbb", "ccc" ] # list of keywords to match in stdout & stderr
keyword.weight = [ 20, 10, 5 ] # weight for each keyword, will be deducted for each keyword found
# similar to keyword, but will only match check name in clang-tidy
clangtidy.score = 0
clangtidy.keyword = [ "clang-diagnostic", "clang-analyzer", "misc", "performance", "portability" ]
clangtidy.weight = [ 5, 5, 5, 5, 5 ]
# similar to keyword, but will only match record ID & severity in cppcheck
cppcheck.score = 0
cppcheck.keyword = [ "error", "warning", "portability", "performance", "style" ]
cppcheck.weight = [ 15, 5, 5, 5, 5 ]
# similar to keyword, but will only match category in cpplint
cpplint.score = 0
cpplint.keyword = [ "runtime", "readability", "build" ]
cpplint.weight = [ 5, 20, 10 ]
# similar to keyword, but will only match kind in elf
elf.score = 0
elf.keyword = [ "Parentheses", "Length", "Arity", "Repetitive" ]
elf.weight = [ 100, 300, 50, 80 ]
# dummy parser, it will not parse the result from the command, always give the same output
dummy.score = 0 # score to add, default: 0
dummy.comment = "" # comment to show, default: ""
dummy.force-quit = false # whether to force quit the stage, default: false
diff.score = 5 # default score for each case, default: 5
diff.ignore-spaces = true # ignore spaces in diff, default: true
diff.hide = false # whether to hide the diff output, default: false
diff.force-quit = false # whether to force quit the stage if there is a difference, default: false
diff.max-length = 2048 # maximum length of the diff output, longer content will be truncated, default: 2048
diff.max-lines = 50 # maximum number of lines to show in the diff output, longer content will be truncated, default: 50
diff.hide-common-prefix = false # whether to hide the common prefix in the diff output, thus the first different line will be shown, default: false
# override when there are more than 1 cases in this stage
# for quality check stages, there is only 1 case so this is not needed
# previous fields without `case0.` prefix will be used as default for all cases
# and for run stages, multiple cases will be run with different inputs and outputs
# specific cases can be overridden here with these `case<x>.` prefix
# if no `case<x>.in` and `case<x>.out` is specified here,
# it will look for files with name `case<x>.in` and `case<x>.out`
# recursively in the directory of this toml file, and set them as corresponding
# `case<x>.in` and `case<x>.out` automatically, use the default value defined above
case0.in = "cases/case0.in" # file will be used as stdin, relative to this toml file
case0.out = "cases/case0.out" # file will be used to run diff with stdout, relative to this toml file
# the following fields just show the default values
case0.env = []
case0.command = ""
case0.files.import = []
case0.files.import-map = {}
case0.files.export = []
case0.copy-in-cwd = true
case0.limit.cpu = "1s"
case0.limit.mem = "256m"
case0.limit.time = "2s"
case0.limit.stdout = "32m"
case0.limit.stderr = "32m"
case0.limit.proc = 50
case0.diff.score = 5
case0.diff.ignore-spaces = true
case0.diff.hide = false
case0.diff.force-quit = false
case0.diff.max-length = 2048
case0.diff.max-lines = 50
case0.diff.hide-common-prefix = false
# the following 2 lines can be omitted as they can be detected automatically
# case1.in = "cases/case1.in"
# case1.out = "cases/case1.out"
# and you can only override part of the fields
case2.limit.cpu = "2s" # override CPU time limit for case2
case2.limit.mem = "512m" # override memory limit for case2
# also, you can put .in and .out files in other directories
# case3.in = "other/cases/case3.in"
# case3.out = "other/cases/case3.out"
# ===============================================
# ========== parser related config end ==========
# ===============================================
# all supported fields are listed here in 1 stage, but usually you need multiple stages
# for a real world example, please refer to playground repo

View File

@ -1,8 +1,8 @@
{
"name": "hw7 ex2",
"logPath": "/home/tt/.cache/joj3/hw7/joj3.log",
"expireUnixTimestamp": 0,
"effectiveUnixTimestamp": 0,
"logPath": "/home/tt/.cache/joj3/joj3.log",
"expireUnixTimestamp": 1735574399,
"effectiveUnixTimestamp": 1735487999,
"actorCsvPath": "/home/tt/.config/joj/students.csv",
"maxTotalScore": 100,
"stage": {
@ -73,7 +73,6 @@
{
"name": "keyword",
"with": {
"score": 0,
"matches": [
{
"keywords": [

View File

@ -1,8 +1,8 @@
# general task configuration
task.name = "hw7 ex2" # task name
time.end = 2024-12-30 23:59:59+08:00
time.begin = 2024-12-29 23:59:59+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 = "[cq] Filelength"

View File

@ -1,8 +1,8 @@
{
"name": "hw7 ex2",
"logPath": "/home/tt/.cache/joj3/hw7/joj3.log",
"expireUnixTimestamp": 0,
"effectiveUnixTimestamp": 0,
"logPath": "/home/tt/.cache/joj3/joj3.log",
"expireUnixTimestamp": 1735574399,
"effectiveUnixTimestamp": 1735487999,
"actorCsvPath": "/home/tt/.config/joj/students.csv",
"maxTotalScore": 100,
"stage": {

View File

@ -1,8 +1,8 @@
# general task configuration
task.name = "hw7 ex2" # task name
time.end = 2024-12-30 23:59:59+08:00
time.begin = 2024-12-29 23:59:59+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 = "[cq] Filelength"

View File

@ -21,18 +21,6 @@ def test_diff() -> None:
load_case("diff")
def test_elf() -> None:
load_case("elf")
def test_empty() -> None:
load_case("empty")
def test_full() -> None:
load_case("full")
def test_keyword() -> None:
load_case("keyword")
@ -41,5 +29,5 @@ def test_result_detail() -> None:
load_case("result-detail")
def test_unnecessary() -> None:
load_case("unnecessary")
def test_elf() -> None:
load_case("elf")

View File

@ -1,6 +1,6 @@
{
"name": "hw7 ex2",
"logPath": "/home/tt/.cache/joj3/hw7/joj3.log",
"logPath": "/home/tt/.cache/joj3/joj3.log",
"expireUnixTimestamp": 0,
"effectiveUnixTimestamp": 0,
"actorCsvPath": "/home/tt/.config/joj/students.csv",

View File

@ -1,3 +1,4 @@
[task]
name = "hw7 ex2"
[[stages]]