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
35 changed files with 194 additions and 451 deletions

View File

@ -23,9 +23,9 @@ jobs:
- name: PDM install dependencies - name: PDM install dependencies
run: | run: |
pdm install pdm install
- name: All - name: Lint
run: | run: |
pdm run all pdm run lint
- name: Run - name: Run
run: | run: |
pdm run app --help 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/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) [![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 ## Getting Started
### For users ### For users
@ -10,7 +14,7 @@
1. Install [Python>=3.9](https://www.python.org/) and [pip](https://pip.pypa.io/) 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/). 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` 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 ### For developers
@ -23,22 +27,62 @@
## How to use? ## How to use?
- `joj3-config-generator convert` function is now supported, currently support one argument as input, it indicates the **convert root** Run `joj3-forge --help` to get basic CLI usage information.
- default value on the server can be given as `/home/tt/.config/joj`
### `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** - **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 - 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 ```shell
$ tree . $ tree -a
. home
|- immutable_files `-- tt
| |-- push.yaml |-- .cache
| |-- release.yaml `-- .config
|-- repo.toml `-- 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 - sample command on the server
```shell ```shell
joj3-config-generator convert /home/tt/.config/joj joj3-forge convert /home/tt/.config/joj
``` ```

View File

@ -26,8 +26,8 @@ def convert_joj3_conf(repo_conf: repo.Config, task_conf: task.Config) -> result.
name=task_conf.task.name, name=task_conf.task.name,
# exact folder difference specified by type # exact folder difference specified by type
log_path=str(JOJ3_LOG_PATH), log_path=str(JOJ3_LOG_PATH),
expire_unix_timestamp=0, # will be handled in the health check stage expire_unix_timestamp=int(task_conf.release.end_time.timestamp()),
effective_unix_timestamp=0, # will be handled in the health check stage effective_unix_timestamp=int(task_conf.release.begin_time.timestamp()),
actor_csv_path=str(ACTOR_CSV_PATH), # students.csv position actor_csv_path=str(ACTOR_CSV_PATH), # students.csv position
max_total_score=( max_total_score=(
repo_conf.max_total_score repo_conf.max_total_score

View File

@ -1,8 +1,8 @@
import os import socket
from pathlib import Path from pathlib import Path
from typing import List from typing import List
from pydantic import AliasChoices, BaseModel, Field, model_validator from pydantic import AliasChoices, BaseModel, Field
class Files(BaseModel): class Files(BaseModel):
@ -20,18 +20,6 @@ class Groups(BaseModel):
) )
class Label(BaseModel):
name: str = "Kind/Testing"
color: str = "#795548"
class Issue(BaseModel):
label: Label = Label()
show_submitter: bool = Field(
True, validation_alias=AliasChoices("show-submitter", "show_submitter")
)
class Config(BaseModel): class Config(BaseModel):
max_size: float = Field( max_size: float = Field(
10, ge=0, validation_alias=AliasChoices("max-size", "max_size") 10, ge=0, validation_alias=AliasChoices("max-size", "max_size")
@ -40,10 +28,6 @@ class Config(BaseModel):
sandbox_token: str = Field( sandbox_token: str = Field(
"", validation_alias=AliasChoices("sandbox-token", "sandbox_token") "", validation_alias=AliasChoices("sandbox-token", "sandbox_token")
) )
gitea_token: str = Field(
"", validation_alias=AliasChoices("gitea-token", "gitea_token")
)
gitea_org: str = Field("", validation_alias=AliasChoices("gitea-org", "gitea_org"))
max_total_score: int = Field( max_total_score: int = Field(
100, validation_alias=AliasChoices("max-total-score", "max_total_score") 100, validation_alias=AliasChoices("max-total-score", "max_total_score")
) )
@ -63,24 +47,15 @@ class Config(BaseModel):
root: Path = Path(".") root: Path = Path(".")
path: Path = Path("repo.toml") path: Path = Path("repo.toml")
grading_repo_name: str = Field( grading_repo_name: str = Field(
"", f"{socket.gethostname().split('-')[0]}-joj",
validation_alias=AliasChoices("grading-repo-name", "grading_repo_name"), validation_alias=AliasChoices("grading-repo-name", "grading_repo_name"),
) )
health_check_score: int = Field( health_check_score: int = Field(
0, validation_alias=AliasChoices("health-check-score", "health_check_score") 0, validation_alias=AliasChoices("health-check-score", "health_check_score")
) )
issue: Issue = Issue() submitter_in_issue_title: bool = Field(
immutable_path: Path = Field( True,
Path("immutable_files"), validation_alias=AliasChoices(
validation_alias=AliasChoices("immutable-path", "immutable_path"), "submitter-in-issue-title", "submitter_in_issue_title"
),
) )
@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

View File

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

View File

@ -1,8 +1,7 @@
import re
from datetime import datetime, timezone from datetime import datetime, timezone
from enum import Enum from enum import Enum
from pathlib import Path from pathlib import Path
from typing import Any, Dict, List, Optional, Type from typing import Any, Dict, List, Type
from pydantic import ( from pydantic import (
AliasChoices, AliasChoices,
@ -39,12 +38,6 @@ class ParserResultDetail(BaseModel):
False, validation_alias=AliasChoices("proc-peak", "proc_peak") False, validation_alias=AliasChoices("proc-peak", "proc_peak")
) # Display peak process count ) # Display peak process count
error: bool = False # Display error messages 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): class ParserFile(BaseModel):
@ -65,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): class ParserKeyword(BaseModel):
score: int = 0
keyword: List[str] = [] keyword: List[str] = []
weight: List[int] = [] weight: List[int] = []
@ -98,46 +82,16 @@ class ParserDiffOutputs(BaseModel):
class ParserDiff(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() output: ParserDiffOutputs = ParserDiffOutputs()
default_score: int = Field( default_score: int = Field(
DEFAULT_CASE_SCORE, DEFAULT_CASE_SCORE,
validation_alias=AliasChoices("default-score", "default_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): class StageFiles(BaseModel):
import_: List[str] = Field([], validation_alias="import") import_: List[str] = Field([], validation_alias="import")
export: List[str] = [] export: List[str] = []
import_map: Dict[str, str] = Field(
{}, validation_alias=AliasChoices("import-map", "import_map")
)
class Limit(BaseModel): class Limit(BaseModel):
@ -187,6 +141,7 @@ class Case(BaseModel):
True, validation_alias=AliasChoices("copy-in-cwd", "copy_in_cwd") True, validation_alias=AliasChoices("copy-in-cwd", "copy_in_cwd")
) )
limit: Limit = Limit() limit: Limit = Limit()
score: int = 0
diff: ParserDiff = ParserDiff() diff: ParserDiff = ParserDiff()
@ -196,9 +151,8 @@ class Stage(Case):
parsers: List[Parser] = [] # list of parsers parsers: List[Parser] = [] # list of parsers
dummy: ParserDummy = ParserDummy() dummy: ParserDummy = ParserDummy()
result_status: ParserResultStatus = Field( result_status: ParserDummy = Field(
ParserResultStatus(), ParserDummy(), validation_alias=AliasChoices("result-status", "result_status")
validation_alias=AliasChoices("result-status", "result_status"),
) )
keyword: ParserKeyword = ParserKeyword() keyword: ParserKeyword = ParserKeyword()
clangtidy: ParserKeyword = ParserKeyword() clangtidy: ParserKeyword = ParserKeyword()
@ -210,7 +164,6 @@ class Stage(Case):
validation_alias=AliasChoices("result-detail", "result_detail"), validation_alias=AliasChoices("result-detail", "result_detail"),
) )
file: ParserFile = ParserFile() file: ParserFile = ParserFile()
# diff: ParserDiff = ParserDiff() # inherited from Case
cases: Dict[str, Case] = {} cases: Dict[str, Case] = {}
@ -242,37 +195,17 @@ class Release(BaseModel):
) # timestamp = 0, no begin time ) # timestamp = 0, no begin time
class SubmissionTime(BaseModel):
begin: Optional[datetime] = None
end: Optional[datetime] = None
class Task(BaseModel): class Task(BaseModel):
name: str = "unknown" name: str = "unknown"
class Penalties(BaseModel):
hours: List[float] = []
factors: List[float] = []
class Config(BaseModel): class Config(BaseModel):
root: Path = Path(".") root: Path = Path(".")
path: Path = Path("task.toml") path: Path = Path("task.toml")
task: Task = Task() # Task name (e.g., hw3 ex5) task: Task = Task() # Task name (e.g., hw3 ex5)
time: SubmissionTime = SubmissionTime() # Valid time configuration
release: Release = Release() # Release configuration release: Release = Release() # Release configuration
stages: List[Stage] = [] # list of stage configurations stages: List[Stage] = [] # list of stage configurations
groups: Groups = Groups() groups: Groups = Groups()
penalties: Penalties = Penalties() max_total_score: int = Field(
max_total_score: Optional[int] = Field( 100, validation_alias=AliasChoices("max-total-score", "max_total_score")
None, validation_alias=AliasChoices("max-total-score", "max_total_score")
) )
scoreboard: str = "scoreboard.csv"
@model_validator(mode="after")
def set_scoreboard(self) -> "Config":
if self.scoreboard == "auto":
suffix = re.split(r"[-_/\s]+", self.task.name)[0]
self.scoreboard = f"scoreboard-{suffix}.csv"
return self

View File

@ -1,26 +1,9 @@
import hashlib import hashlib
from pathlib import Path 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 import common, repo, result, task
from joj3_config_generator.models.const import ( from joj3_config_generator.models.const import TEAPOT_CONFIG_ROOT, TEAPOT_LOG_PATH
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
def get_teapot_post_stage( def get_teapot_post_stage(
@ -34,34 +17,13 @@ def get_teapot_post_stage(
repo_conf.grading_repo_name, repo_conf.grading_repo_name,
"--max-total-score", "--max-total-score",
( (
str(task_conf.max_total_score) str(repo_conf.max_total_score)
if task_conf.max_total_score is not None if not task_conf.max_total_score
else str(repo_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 not repo_conf.issue.show_submitter: if not repo_conf.submitter_in_issue_title:
args.append("--no-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( stage_conf = result.StageDetail(
name="teapot", name="teapot",
@ -70,7 +32,7 @@ def get_teapot_post_stage(
with_=result.ExecutorWith( with_=result.ExecutorWith(
default=result.Cmd( default=result.Cmd(
args=args, args=args,
env=get_teapot_env(repo_conf), env=[f"LOG_FILE_PATH={TEAPOT_LOG_PATH}"],
cpu_limit=common.Time("30s"), cpu_limit=common.Time("30s"),
clock_limit=common.Time("60s"), clock_limit=common.Time("60s"),
), ),
@ -85,45 +47,14 @@ def get_teapot_post_stage(
return stage_conf 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_names = []
for file in repo_conf.files.immutable:
file_path = immutable_dir / Path(file).name
if not file_path.exists():
logger.warning(f"Immutable file not found: {file_path}")
continue
immutable_files.append(file_path)
file_sums.append(calc_sha256sum(file_path))
file_names.append(file)
immutable_dir = (repo_conf.root / repo_conf.path).parent / repo_conf.immutable_path
if not immutable_dir.exists():
return file_sums, file_names
file_sums = []
file_names = []
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_names.append(file_path.relative_to(immutable_dir).as_posix())
return file_sums, file_names
def get_health_check_args(repo_conf: repo.Config) -> List[str]: def get_health_check_args(repo_conf: repo.Config) -> List[str]:
file_sums, file_names = get_check_lists(repo_conf)
return [ return [
"/usr/local/bin/repo-health-checker", "/usr/local/bin/repo-health-checker",
"-root=.", "-root=.",
f"-repoSize={str(repo_conf.max_size)}", f"-repoSize={str(repo_conf.max_size)}",
*[f"-meta={meta}" for meta in repo_conf.files.required], *[f"-meta={meta}" for meta in repo_conf.files.required],
f"-checkFileSumList={','.join(file_sums)}", f"-checkFileSumList={','.join(get_hashes(repo_conf))}",
f"-checkFileNameList={','.join(file_names)}", f"-checkFileNameList={','.join(repo_conf.files.immutable)}",
] ]
@ -134,34 +65,28 @@ def get_teapot_check_args(repo_conf: repo.Config, task_conf: task.Config) -> Lis
str(TEAPOT_CONFIG_ROOT / "teapot.env"), str(TEAPOT_CONFIG_ROOT / "teapot.env"),
"--grading-repo-name", "--grading-repo-name",
repo_conf.grading_repo_name, repo_conf.grading_repo_name,
"--scoreboard-filename",
task_conf.scoreboard,
] ]
if repo_conf.groups.name: if repo_conf.groups.name:
group_str = lambda groups: ",".join( group_config_str = ",".join(
f"{name}={max_count}:{time_period}" f"{name}={max_count}:{time_period}"
for name, max_count, time_period in zip( for name, max_count, time_period in zip(
groups.name, repo_conf.groups.name,
groups.max_count, repo_conf.groups.max_count,
groups.time_period_hour, repo_conf.groups.time_period_hour,
) )
) )
group_config = group_str( if task_conf.groups.name:
task_conf.groups if task_conf.groups.name else repo_conf.groups overwrite_group_config_str = ",".join(
) f"{name}={max_count}:{time_period}"
res.extend(["--group-config", group_config]) for name, max_count, time_period in zip(
if task_conf.time.begin: task_conf.groups.name,
res.extend(["--begin-time", task_conf.time.begin.strftime("%Y-%m-%dT%H:%M:%S")]) task_conf.groups.max_count,
if task_conf.time.end: task_conf.groups.time_period_hour,
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
) )
) )
res.extend(["--penalty-config", penalty_config]) res.extend(["--group-config", overwrite_group_config_str])
else:
res.extend(["--group-config", group_config_str])
return res return res
@ -175,8 +100,7 @@ def get_health_check_stage(
name="local", name="local",
with_=result.ExecutorWith( with_=result.ExecutorWith(
default=result.Cmd( default=result.Cmd(
cpu_limit=common.Time("10s"), cpu_limit=common.Time("10s"), clock_limit=common.Time("20s")
clock_limit=common.Time("20s"),
), ),
cases=[ cases=[
result.OptionalCmd( result.OptionalCmd(
@ -184,7 +108,7 @@ def get_health_check_stage(
), ),
result.OptionalCmd( result.OptionalCmd(
args=get_teapot_check_args(repo_conf, task_conf), args=get_teapot_check_args(repo_conf, task_conf),
env=get_teapot_env(repo_conf), env=[f"LOG_FILE_PATH={TEAPOT_LOG_PATH}"],
), ),
], ],
), ),
@ -206,3 +130,12 @@ def calc_sha256sum(file_path: Path) -> str:
for byte_block in iter(lambda: f.read(64 * 1024), b""): for byte_block in iter(lambda: f.read(64 * 1024), b""):
sha256_hash.update(byte_block) sha256_hash.update(byte_block)
return sha256_hash.hexdigest() 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,7 +2,7 @@ import re
import shlex import shlex
from functools import partial from functools import partial
from pathlib import Path, PurePosixPath 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 import result, task
from joj3_config_generator.models.common import Memory, Time from joj3_config_generator.models.common import Memory, Time
@ -61,7 +61,7 @@ def get_parser_handler_map(
task.Parser.CPPLINT: (fix_keyword, task_stage.cpplint), task.Parser.CPPLINT: (fix_keyword, task_stage.cpplint),
task.Parser.RESULT_DETAIL: (fix_result_detail, task_stage.result_detail), task.Parser.RESULT_DETAIL: (fix_result_detail, task_stage.result_detail),
task.Parser.DUMMY: (fix_dummy, task_stage.dummy), 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.FILE: (fix_file, task_stage.file),
task.Parser.DIFF: ( task.Parser.DIFF: (
partial( partial(
@ -79,25 +79,20 @@ def get_parser_handler_map(
def get_executor_with( def get_executor_with(
task_stage: task.Stage, cached: Dict[str, None] task_stage: task.Stage, cached: Dict[str, None]
) -> result.ExecutorWith: ) -> result.ExecutorWith:
copy_in: dict[ file_import = task_stage.files.import_
str, copy_in_files = (file for file in file_import if file not in cached)
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_export = task_stage.files.export file_export = task_stage.files.export
copy_out_files = ["stdout", "stderr"] copy_out_files = ["stdout", "stderr"]
executor_with_config = result.ExecutorWith( executor_with_config = result.ExecutorWith(
default=result.Cmd( default=result.Cmd(
args=shlex.split(task_stage.command), args=shlex.split(task_stage.command),
env=[DEFAULT_PATH_ENV, *task_stage.env], 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_in_dir="." if task_stage.copy_in_cwd else "",
copy_out=copy_out_files, copy_out=copy_out_files,
copy_in_cached={file: file for file in cached}, copy_in_cached={file: file for file in cached},
@ -129,11 +124,10 @@ def fix_keyword(
for keyword, score in zip(keyword_config.keyword, keyword_config.weight): for keyword, score in zip(keyword_config.keyword, keyword_config.weight):
score_groups.setdefault(score, []).append(keyword) score_groups.setdefault(score, []).append(keyword)
keyword_parser.with_ = result.KeywordMatchConfig( keyword_parser.with_ = result.KeywordMatchConfig(
score=keyword_config.score,
matches=[ matches=[
result.KeywordConfig(keywords=keywords, score=score) result.KeywordConfig(keywords=keywords, score=score)
for score, keywords in score_groups.items() for score, keywords in score_groups.items()
], ]
) )
@ -156,29 +150,17 @@ def fix_result_detail(
show_memory=result_detail_parser_config.mem, show_memory=result_detail_parser_config.mem,
show_error=result_detail_parser_config.error, show_error=result_detail_parser_config.error,
show_proc_peak=result_detail_parser_config.proc_peak, 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( def fix_dummy(
dummy_parser_config: task.ParserDummy, dummy_parser: result.Parser dummy_parser_config: task.ParserDummy, dummy_parser: result.Parser
) -> None: ) -> None:
# we don't use dummy parser in real application
dummy_parser.with_ = result.DummyConfig( dummy_parser.with_ = result.DummyConfig(
score=dummy_parser_config.score, score=dummy_parser_config.score,
comment=dummy_parser_config.comment, comment=dummy_parser_config.comment,
force_quit=dummy_parser_config.force_quit, force_quit_on_not_accepted=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,
) )
@ -233,24 +215,18 @@ def fix_diff(
if cmd.proc_limit == executor.with_.default.proc_limit: if cmd.proc_limit == executor.with_.default.proc_limit:
cmd.proc_limit = None cmd.proc_limit = None
stage_cases.append(cmd) 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( parser_case = result.DiffCasesConfig(
outputs=[ outputs=[
result.DiffOutputConfig( result.DiffOutputConfig(
score=get_diff_attribute("score"), score=case.diff.output.score,
filename="stdout", file_name="stdout",
answer_path=stdout, answer_path=stdout,
compare_space=not get_diff_attribute("ignore_spaces"), force_quit_on_diff=case.diff.output.force_quit,
always_hide=get_diff_attribute("hide"), always_hide=case.diff.output.hide,
force_quit_on_diff=get_diff_attribute("force_quit"), compare_space=not case.diff.output.ignore_spaces,
max_diff_length=get_diff_attribute("max_length"), max_diff_length=case.diff.output.max_length,
max_diff_lines=get_diff_attribute("max_lines"), max_diff_lines=case.diff.output.max_lines,
hide_common_prefix=get_diff_attribute("hide_common_prefix"), hide_common_prefix=case.diff.output.hide_common_prefix,
) )
] ]
) )
@ -258,20 +234,18 @@ def fix_diff(
for case_name in unspecified_cases: for case_name in unspecified_cases:
cmd = result.OptionalCmd( cmd = result.OptionalCmd(
stdin=result.LocalFile(src=str(base_dir / f"{case_name}.in")), 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) stage_cases.append(cmd)
parser_case = result.DiffCasesConfig( parser_case = result.DiffCasesConfig(
outputs=[ outputs=[
result.DiffOutputConfig( result.DiffOutputConfig(
score=task_stage.diff.score, score=task_stage.diff.default_score,
filename="stdout", file_name="stdout",
answer_path=str(base_dir / f"{case_name}.out"), 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,
) )
] ]
) )

View File

@ -5,8 +5,6 @@ sandbox-token = "test"
max-total-score = 1000 max-total-score = 1000
max-size = 50.5 max-size = 50.5
immutable_path = "immutable"
# for tests # for tests
[groups] [groups]
name = ["joj", "run"] name = ["joj", "run"]

View File

@ -1,8 +1,8 @@
{ {
"name": "hw7 ex2", "name": "hw7 ex2",
"logPath": "/home/tt/.cache/joj3/joj3.log", "logPath": "/home/tt/.cache/joj3/joj3.log",
"expireUnixTimestamp": 0, "expireUnixTimestamp": 1735574399,
"effectiveUnixTimestamp": 0, "effectiveUnixTimestamp": 1735487999,
"actorCsvPath": "/home/tt/.config/joj/students.csv", "actorCsvPath": "/home/tt/.config/joj/students.csv",
"maxTotalScore": 10245871, "maxTotalScore": 10245871,
"stage": { "stage": {
@ -64,8 +64,8 @@
"-repoSize=50.5", "-repoSize=50.5",
"-meta=README.md", "-meta=README.md",
"-meta=Changelog.md", "-meta=Changelog.md",
"-checkFileSumList=b1bbad25b830db0a77b15a033f9ca1b7ab44c1d2d05056412bd3e4421645f0bf,2ba059f3977e2e3dee6cacbfbf0ba2578baa1b8e04b4977aec400868b6e49856,3db23f7fb2ca9814617e767ddc41b77073180b3b0b73e87b5f2a6d3129f88f3a,a5b63323a692d3d8b952442969649b4f823d58dae26429494f613df160710dfc", "-checkFileSumList=a5b63323a692d3d8b952442969649b4f823d58dae26429494f613df160710dfc,b1bbad25b830db0a77b15a033f9ca1b7ab44c1d2d05056412bd3e4421645f0bf,2ba059f3977e2e3dee6cacbfbf0ba2578baa1b8e04b4977aec400868b6e49856,3db23f7fb2ca9814617e767ddc41b77073180b3b0b73e87b5f2a6d3129f88f3a",
"-checkFileNameList=.gitattributes,.gitea/workflows/push.yaml,.gitea/workflows/release.yaml,.gitignore" "-checkFileNameList=.gitignore,.gitattributes,.gitea/workflows/push.yaml,.gitea/workflows/release.yaml"
] ]
}, },
{ {
@ -75,17 +75,10 @@
"/home/tt/.config/teapot/teapot.env", "/home/tt/.config/teapot/teapot.env",
"--grading-repo-name", "--grading-repo-name",
"ece280-joj", "ece280-joj",
"--scoreboard-filename",
"scoreboard-hw7.csv",
"--group-config", "--group-config",
"Manuel=500:24,Boming=501:48,Nuvole=502:72", "Manuel=500:24,Boming=501:48,Nuvole=502:72"
"--end-time",
"2024-12-30T23:59:59",
"--penalty-config",
"24.0=0.5,48.0=0.25,72.0=0.1"
], ],
"env": [ "env": [
"REPOS_DIR=/home/tt/.cache",
"LOG_FILE_PATH=/home/tt/.cache/joint-teapot-debug.log" "LOG_FILE_PATH=/home/tt/.cache/joint-teapot-debug.log"
] ]
} }
@ -143,9 +136,6 @@
"copyIn": { "copyIn": {
"tools/compile": { "tools/compile": {
"src": "/home/tt/.config/joj/tools/compile" "src": "/home/tt/.config/joj/tools/compile"
},
"h7/Makefile": {
"src": "/home/tt/.config/joj/tools/Makefile"
} }
}, },
"copyInCached": {}, "copyInCached": {},
@ -270,7 +260,6 @@
{ {
"name": "keyword", "name": "keyword",
"with": { "with": {
"score": 0,
"matches": [ "matches": [
{ {
"keywords": [ "keywords": [
@ -379,7 +368,6 @@
{ {
"name": "clangtidy", "name": "clangtidy",
"with": { "with": {
"score": 0,
"matches": [ "matches": [
{ {
"keywords": [ "keywords": [
@ -510,14 +498,12 @@
{ {
"name": "keyword", "name": "keyword",
"with": { "with": {
"score": 0,
"matches": [] "matches": []
} }
}, },
{ {
"name": "cppcheck", "name": "cppcheck",
"with": { "with": {
"score": 0,
"matches": [ "matches": [
{ {
"keywords": [ "keywords": [
@ -540,7 +526,6 @@
{ {
"name": "clangtidy", "name": "clangtidy",
"with": { "with": {
"score": 0,
"matches": [] "matches": []
} }
}, },
@ -566,7 +551,6 @@
{ {
"name": "cpplint", "name": "cpplint",
"with": { "with": {
"score": 0,
"matches": [] "matches": []
} }
}, },
@ -575,7 +559,7 @@
"with": { "with": {
"score": 0, "score": 0,
"comment": "", "comment": "",
"forceQuitOnNotAccepted": true "forceQuitOnNotAccepted": false
} }
}, },
{ {
@ -589,7 +573,7 @@
"with": { "with": {
"score": 0, "score": 0,
"comment": "", "comment": "",
"forceQuit": false "forceQuitOnNotAccepted": false
} }
}, },
{ {
@ -667,7 +651,6 @@
{ {
"name": "cpplint", "name": "cpplint",
"with": { "with": {
"score": 0,
"matches": [ "matches": [
{ {
"keywords": [ "keywords": [
@ -766,27 +749,7 @@
"dataSegmentLimit": false, "dataSegmentLimit": false,
"addressSpaceLimit": false "addressSpaceLimit": false
}, },
"cases": [ "cases": []
{
"stdin": {
"content": ""
},
"cpuLimit": 500000000,
"clockLimit": 1000000000,
"memoryLimit": 5242880
},
{
"stdin": {
"content": ""
},
"memoryLimit": 5242880
},
{
"stdin": {
"content": ""
}
}
]
} }
}, },
"parsers": [ "parsers": [
@ -794,53 +757,7 @@
"name": "diff", "name": "diff",
"with": { "with": {
"name": "diff", "name": "diff",
"cases": [ "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
}
]
}
]
} }
}, },
{ {
@ -881,20 +798,9 @@
"--grading-repo-name", "--grading-repo-name",
"ece280-joj", "ece280-joj",
"--max-total-score", "--max-total-score",
"10245871", "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"
], ],
"env": [ "env": [
"REPOS_DIR=/home/tt/.cache",
"LOG_FILE_PATH=/home/tt/.cache/joint-teapot-debug.log" "LOG_FILE_PATH=/home/tt/.cache/joint-teapot-debug.log"
], ],
"stdin": { "stdin": {

View File

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

View File

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

View File

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

View File

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

View File

@ -1,8 +1,8 @@
# general task configuration # general task configuration
task.name = "hw7 ex2" # task name task.name = "hw7 ex2" # task name
time.end = 2024-12-30 23:59:59+08:00 release.end-time = 2024-12-30 23:59:59+08:00
time.begin = 2024-12-29 23:59:59+08:00 release.begin-time = 2024-12-29 23:59:59+08:00
[[stages]] [[stages]]
name = "[cq] Cppcheck" name = "[cq] Cppcheck"

View File

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

View File

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

View File

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

View File

@ -1,7 +1,7 @@
# general task configuration # general task configuration
task.name = "hw7 ex2" # task name task.name = "hw7 ex2" # task name
time.end = 2024-12-30 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 release.begin-time = 2024-12-29 23:59:59+08:00
[[stages]] [[stages]]
@ -17,26 +17,26 @@ parsers = [ "diff", "result-detail" ]
result-detail.exit-status = true result-detail.exit-status = true
result-detail.stderr = 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.cpu = "1s"
case0.limit.mem = "2m" case0.limit.mem = "2m"
case0.diff.ignore-spaces = true case0.diff.output.ignore-spaces = true
case0.command = "./h7/build/ex2" case0.command = "./h7/build/ex2"
case0.in = "case0.in" case0.in = "case0.in"
case1.diff.score = 123214122421 case1.diff.output.score = 123214122421
case1.limit.cpu = "2s" case1.limit.cpu = "2s"
case1.limit.mem = "4m" case1.limit.mem = "4m"
case1.diff.ignore-spaces = true case1.diff.output.ignore-spaces = true
case1.command = "./h7/build/ex2" case1.command = "./h7/build/ex2"
case9.diff.score = 1232131 case9.diff.output.score = 1232131
case9.limit.mem = "10m" 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", "name": "hw7 ex2",
"logPath": "/home/tt/.cache/joj3/joj3.log", "logPath": "/home/tt/.cache/joj3/joj3.log",
"expireUnixTimestamp": 0, "expireUnixTimestamp": 1735574399,
"effectiveUnixTimestamp": 0, "effectiveUnixTimestamp": 1735487999,
"actorCsvPath": "/home/tt/.config/joj/students.csv", "actorCsvPath": "/home/tt/.config/joj/students.csv",
"maxTotalScore": 100, "maxTotalScore": 100,
"stage": { "stage": {
@ -74,7 +74,6 @@
{ {
"name": "elf", "name": "elf",
"with": { "with": {
"score": 0,
"matches": [ "matches": [
{ {
"keywords": [ "keywords": [

View File

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

View File

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

View File

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

View File

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

View File

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