Compare commits
47 Commits
docs/READM
...
master
Author | SHA1 | Date | |
---|---|---|---|
ea894953ae | |||
dcb2035be3 | |||
8b16214be4 | |||
8f96115e6e | |||
7e6ee1b63e | |||
dc7682a94f | |||
ba720ebe3f | |||
88d864d49d | |||
7c764ab2cb | |||
6a7c5eb5f8 | |||
6769e19d83 | |||
26824fd93c | |||
c7b02f17a2 | |||
f4248a59a5 | |||
8f5b23555c | |||
bb4f2b1364 | |||
10a5efb293 | |||
c428d51706 | |||
4d3f77d6d0 | |||
66551898be | |||
1313a24690 | |||
087f62c304 | |||
53395359cd | |||
d785216396 | |||
8f1a82dc81 | |||
a49bc19b1e | |||
1b4637d01c | |||
875089cabd | |||
8d52febbc5 | |||
fd7d09e7b2 | |||
29952a9d2d | |||
2bcf4f8c60 | |||
e2dc094263 | |||
42bbf3ce39 | |||
c6e2c63024 | |||
cae592c5cf | |||
52f82a4afc | |||
d8073e4eb6 | |||
0b39aa7112 | |||
b46bc950f7 | |||
340ba5d0c5 | |||
ed43414b86 | |||
367a79850c | |||
5404313199 | |||
c6b833fbd5 | |||
b14d83c37b | |||
2c1ad47f14 |
.gitea/workflows
README.mdjoj3_config_generator
tests
convert
__init__.py
basic
clang-tidy
cppcheck
cpplint
diff
elf
empty
keyword
result-detail
test_convert_cases.pyunnecessary
convert_joj1
create
|
@ -23,9 +23,9 @@ jobs:
|
||||||
- name: PDM install dependencies
|
- name: PDM install dependencies
|
||||||
run: |
|
run: |
|
||||||
pdm install
|
pdm install
|
||||||
- name: Lint
|
- name: All
|
||||||
run: |
|
run: |
|
||||||
pdm run lint
|
pdm run all
|
||||||
- name: Run
|
- name: Run
|
||||||
run: |
|
run: |
|
||||||
pdm run app --help
|
pdm run app --help
|
||||||
|
|
66
README.md
66
README.md
|
@ -1,12 +1,8 @@
|
||||||
# JOJ3-forge
|
# JOJ3-config-generator
|
||||||
|
|
||||||
[](https://app.codacy.com/gh/joint-online-judge/JOJ3-config-generator/dashboard)
|
[](https://app.codacy.com/gh/joint-online-judge/JOJ3-config-generator/dashboard)
|
||||||
[](https://app.codacy.com/gh/joint-online-judge/JOJ3-config-generator/dashboard)
|
[](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
|
||||||
|
@ -14,7 +10,7 @@ JOJ3-forge is a CLI tool that generates configuration files for [JOJ3](https://g
|
||||||
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-forge --help`
|
4. Run it by `joj3-config-generator --help`
|
||||||
|
|
||||||
### For developers
|
### For developers
|
||||||
|
|
||||||
|
@ -27,62 +23,22 @@ JOJ3-forge is a CLI tool that generates configuration files for [JOJ3](https://g
|
||||||
|
|
||||||
## How to use?
|
## How to use?
|
||||||
|
|
||||||
Run `joj3-forge --help` to get basic CLI usage information.
|
- `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`
|
||||||
### `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 -a
|
$ tree .
|
||||||
home
|
.
|
||||||
`-- tt
|
|- immutable_files
|
||||||
|-- .cache
|
| |-- push.yaml
|
||||||
`-- .config
|
| |-- release.yaml
|
||||||
`-- joj
|
|-- repo.toml
|
||||||
|-- 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-forge convert /home/tt/.config/joj
|
joj3-config-generator convert /home/tt/.config/joj
|
||||||
```
|
```
|
||||||
|
|
|
@ -2,7 +2,11 @@ import os
|
||||||
from typing import Dict
|
from typing import Dict
|
||||||
|
|
||||||
from joj3_config_generator.models import answer, joj1, repo, result, task
|
from joj3_config_generator.models import answer, joj1, repo, result, task
|
||||||
from joj3_config_generator.models.const import ACTOR_CSV_PATH, JOJ3_LOG_PATH
|
from joj3_config_generator.models.const import (
|
||||||
|
ACTOR_CSV_PATH,
|
||||||
|
JOJ3_LOG_BASE_PATH,
|
||||||
|
JOJ3_LOG_FILENAME,
|
||||||
|
)
|
||||||
from joj3_config_generator.transformers.answer import get_task_conf_from_answers
|
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.joj1 import get_task_conf_from_joj1
|
||||||
from joj3_config_generator.transformers.repo import (
|
from joj3_config_generator.transformers.repo import (
|
||||||
|
@ -23,9 +27,10 @@ def convert_joj1_conf(joj1_conf: joj1.Config) -> task.Config:
|
||||||
def convert_joj3_conf(repo_conf: repo.Config, task_conf: task.Config) -> result.Config:
|
def convert_joj3_conf(repo_conf: repo.Config, task_conf: task.Config) -> result.Config:
|
||||||
# Create the base ResultConf object
|
# Create the base ResultConf object
|
||||||
result_conf = result.Config(
|
result_conf = result.Config(
|
||||||
name=task_conf.task.name,
|
name=task_conf.name,
|
||||||
# exact folder difference specified by type
|
# exact folder difference specified by type
|
||||||
log_path=str(JOJ3_LOG_PATH),
|
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
|
||||||
expire_unix_timestamp=int(task_conf.release.end_time.timestamp()),
|
expire_unix_timestamp=int(task_conf.release.end_time.timestamp()),
|
||||||
effective_unix_timestamp=int(task_conf.release.begin_time.timestamp()),
|
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
|
||||||
|
|
|
@ -5,7 +5,7 @@ from typing import Any, Dict, Tuple, Type, cast
|
||||||
import inquirer
|
import inquirer
|
||||||
import tomli
|
import tomli
|
||||||
import yaml
|
import yaml
|
||||||
from pydantic import BaseModel
|
from pydantic import AliasChoices, BaseModel
|
||||||
|
|
||||||
from joj3_config_generator.models import answer, joj1, repo, task
|
from joj3_config_generator.models import answer, joj1, repo, task
|
||||||
from joj3_config_generator.models.common import Memory, Time
|
from joj3_config_generator.models.common import Memory, Time
|
||||||
|
@ -83,8 +83,16 @@ def load_joj3_toml(
|
||||||
f"{current_path}.{field_name}" if current_path else field_name
|
f"{current_path}.{field_name}" if current_path else field_name
|
||||||
)
|
)
|
||||||
toml_field_name = field_name
|
toml_field_name = field_name
|
||||||
if field_info.alias in input_dict:
|
if field_info.validation_alias:
|
||||||
toml_field_name = field_info.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 toml_field_name not in input_dict:
|
if toml_field_name not in input_dict:
|
||||||
continue
|
continue
|
||||||
toml_value = input_dict[toml_field_name]
|
toml_value = input_dict[toml_field_name]
|
||||||
|
|
|
@ -95,6 +95,12 @@ def convert(
|
||||||
app.pretty_exceptions_enable = False
|
app.pretty_exceptions_enable = False
|
||||||
logger.info(f"Converting files in {root.absolute()}")
|
logger.info(f"Converting files in {root.absolute()}")
|
||||||
for repo_toml_path in root.glob("**/repo.toml"):
|
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 = "invalid commit"\nmax-total-score = 0\n'
|
||||||
|
)
|
||||||
for task_toml_path in repo_toml_path.parent.glob("**/*.toml"):
|
for task_toml_path in repo_toml_path.parent.glob("**/*.toml"):
|
||||||
if repo_toml_path == task_toml_path:
|
if repo_toml_path == task_toml_path:
|
||||||
continue
|
continue
|
||||||
|
|
|
@ -13,6 +13,7 @@ DEFAULT_PATH_ENV = "PATH=/usr/bin:/bin:/usr/local/bin"
|
||||||
JOJ3_CONFIG_ROOT = PurePosixPath("/home/tt/.config/joj")
|
JOJ3_CONFIG_ROOT = PurePosixPath("/home/tt/.config/joj")
|
||||||
TEAPOT_CONFIG_ROOT = PurePosixPath("/home/tt/.config/teapot")
|
TEAPOT_CONFIG_ROOT = PurePosixPath("/home/tt/.config/teapot")
|
||||||
CACHE_ROOT = PurePosixPath("/home/tt/.cache")
|
CACHE_ROOT = PurePosixPath("/home/tt/.cache")
|
||||||
JOJ3_LOG_PATH = CACHE_ROOT / "joj3" / "joj3.log"
|
JOJ3_LOG_BASE_PATH = CACHE_ROOT / "joj3"
|
||||||
|
JOJ3_LOG_FILENAME = "joj3.log"
|
||||||
TEAPOT_LOG_PATH = CACHE_ROOT / "joint-teapot-debug.log"
|
TEAPOT_LOG_PATH = CACHE_ROOT / "joint-teapot-debug.log"
|
||||||
ACTOR_CSV_PATH = JOJ3_CONFIG_ROOT / "students.csv"
|
ACTOR_CSV_PATH = JOJ3_CONFIG_ROOT / "students.csv"
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
import socket
|
import os
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import List
|
from typing import Any, List
|
||||||
|
|
||||||
from pydantic import AliasChoices, BaseModel, Field
|
from pydantic import AliasChoices, BaseModel, Field, field_validator, model_validator
|
||||||
|
|
||||||
|
from joj3_config_generator.models.common import Memory
|
||||||
|
|
||||||
|
|
||||||
class Files(BaseModel):
|
class Files(BaseModel):
|
||||||
|
@ -20,42 +22,115 @@ 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):
|
class Config(BaseModel):
|
||||||
max_size: float = Field(
|
root: Path = Field(Path("."), exclude=True)
|
||||||
10, ge=0, validation_alias=AliasChoices("max-size", "max_size")
|
path: Path = Field(Path("repo.toml"), exclude=True)
|
||||||
)
|
|
||||||
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")
|
|
||||||
)
|
|
||||||
force_skip_health_check_on_test: bool = Field(
|
force_skip_health_check_on_test: bool = Field(
|
||||||
False,
|
False,
|
||||||
validation_alias=AliasChoices(
|
validation_alias=AliasChoices(
|
||||||
"force-skip-health-check-on-test", "force_skip_health_check_on_test"
|
"force-skip-health-check-on-test", "force_skip_health_check_on_test"
|
||||||
),
|
),
|
||||||
|
exclude=True,
|
||||||
)
|
)
|
||||||
force_skip_teapot_on_test: bool = Field(
|
force_skip_teapot_on_test: bool = Field(
|
||||||
False,
|
False,
|
||||||
validation_alias=AliasChoices(
|
validation_alias=AliasChoices(
|
||||||
"force-skip-teapot-on-test", "force_skip_teapot_on_test"
|
"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"),
|
||||||
|
)
|
||||||
|
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()
|
groups: Groups = Groups()
|
||||||
root: Path = Path(".")
|
issue: Issue = Issue()
|
||||||
path: Path = Path("repo.toml")
|
|
||||||
grading_repo_name: str = Field(
|
health_check: HealthCheck = Field(
|
||||||
f"{socket.gethostname().split('-')[0]}-joj",
|
HealthCheck(), validation_alias=AliasChoices("health-check", "health_check")
|
||||||
validation_alias=AliasChoices("grading-repo-name", "grading_repo_name"),
|
)
|
||||||
|
# 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")
|
||||||
)
|
)
|
||||||
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")
|
||||||
)
|
)
|
||||||
submitter_in_issue_title: bool = Field(
|
immutable_path: Path = Field(
|
||||||
True,
|
Path("immutable"),
|
||||||
validation_alias=AliasChoices(
|
validation_alias=AliasChoices("immutable-path", "immutable_path"),
|
||||||
"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
|
||||||
|
|
|
@ -177,6 +177,12 @@ 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(
|
||||||
|
@ -186,7 +192,7 @@ class DummyConfig(BaseModel):
|
||||||
|
|
||||||
class DiffOutputConfig(BaseModel):
|
class DiffOutputConfig(BaseModel):
|
||||||
score: int = 100
|
score: int = 100
|
||||||
file_name: str = Field("", serialization_alias="fileName")
|
filename: 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")
|
||||||
|
@ -217,6 +223,7 @@ class KeywordConfig(BaseModel):
|
||||||
|
|
||||||
|
|
||||||
class KeywordMatchConfig(BaseModel):
|
class KeywordMatchConfig(BaseModel):
|
||||||
|
score: int = 0
|
||||||
matches: List[KeywordConfig] = []
|
matches: List[KeywordConfig] = []
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
|
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, Type
|
from typing import Any, Dict, List, Optional, Type
|
||||||
|
|
||||||
from pydantic import (
|
from pydantic import (
|
||||||
AliasChoices,
|
AliasChoices,
|
||||||
|
@ -15,6 +16,7 @@ from pydantic import (
|
||||||
from joj3_config_generator.models.common import Memory, Time
|
from joj3_config_generator.models.common import Memory, Time
|
||||||
from joj3_config_generator.models.const import (
|
from joj3_config_generator.models.const import (
|
||||||
DEFAULT_CASE_SCORE,
|
DEFAULT_CASE_SCORE,
|
||||||
|
DEFAULT_CLOCK_LIMIT_MULTIPLIER,
|
||||||
DEFAULT_CPU_LIMIT,
|
DEFAULT_CPU_LIMIT,
|
||||||
DEFAULT_FILE_LIMIT,
|
DEFAULT_FILE_LIMIT,
|
||||||
DEFAULT_MEMORY_LIMIT,
|
DEFAULT_MEMORY_LIMIT,
|
||||||
|
@ -25,9 +27,9 @@ from joj3_config_generator.models.repo import Groups
|
||||||
|
|
||||||
class ParserResultDetail(BaseModel):
|
class ParserResultDetail(BaseModel):
|
||||||
cpu_time: bool = Field(
|
cpu_time: bool = Field(
|
||||||
True, validation_alias=AliasChoices("cpu-time", "cpu_time")
|
True, validation_alias=AliasChoices("cpu-time", "cpu_time", "cpu")
|
||||||
) # Display CPU time
|
) # Display CPU time
|
||||||
time: bool = True # Display run time
|
time: bool = True # Display wall-clock time
|
||||||
mem: bool = True # Display memory usage
|
mem: bool = True # Display memory usage
|
||||||
stdout: bool = False # Display stdout messages
|
stdout: bool = False # Display stdout messages
|
||||||
stderr: bool = False # Display stderr messages
|
stderr: bool = False # Display stderr messages
|
||||||
|
@ -38,6 +40,12 @@ 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):
|
||||||
|
@ -58,7 +66,16 @@ 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] = []
|
||||||
|
|
||||||
|
@ -82,40 +99,75 @@ 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):
|
||||||
mem: int = DEFAULT_MEMORY_LIMIT
|
mem: int = DEFAULT_MEMORY_LIMIT
|
||||||
cpu: int = DEFAULT_CPU_LIMIT
|
cpu: int = DEFAULT_CPU_LIMIT
|
||||||
|
time: int = 0
|
||||||
stdout: int = DEFAULT_FILE_LIMIT
|
stdout: int = DEFAULT_FILE_LIMIT
|
||||||
stderr: int = DEFAULT_FILE_LIMIT
|
stderr: int = DEFAULT_FILE_LIMIT
|
||||||
proc: int = DEFAULT_PROC_LIMIT
|
proc: int = DEFAULT_PROC_LIMIT
|
||||||
|
|
||||||
model_config = ConfigDict(validate_assignment=True)
|
@field_validator("cpu", "time", mode="before")
|
||||||
|
|
||||||
@field_validator("cpu", mode="before")
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def ensure_time(cls, v: Any) -> Time:
|
def ensure_time_type(cls, v: Any) -> Time:
|
||||||
if isinstance(v, str):
|
if isinstance(v, str):
|
||||||
return Time(v)
|
return Time(v)
|
||||||
raise ValueError("Must be a string")
|
raise ValueError(f'Must be a string, e.g., "1s" or "100ms", but got {v}')
|
||||||
|
|
||||||
@field_validator("mem", "stdout", "stderr", mode="before")
|
@field_validator("mem", "stdout", "stderr", mode="before")
|
||||||
@classmethod
|
@classmethod
|
||||||
def ensure_mem(cls, v: Any) -> Memory:
|
def ensure_mem_type(cls, v: Any) -> Memory:
|
||||||
if isinstance(v, str):
|
if isinstance(v, str):
|
||||||
return Memory(v)
|
return Memory(v)
|
||||||
raise ValueError("Must be a string")
|
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
|
||||||
|
|
||||||
|
|
||||||
class Parser(str, Enum):
|
class Parser(str, Enum):
|
||||||
|
@ -141,18 +193,17 @@ 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()
|
||||||
|
|
||||||
|
|
||||||
class Stage(Case):
|
class Stage(Case):
|
||||||
name: str = "" # stage name
|
name: str = "" # stage name
|
||||||
skip: List[str] = []
|
|
||||||
|
|
||||||
parsers: List[Parser] = [] # list of parsers
|
parsers: List[Parser] = [] # list of parsers
|
||||||
dummy: ParserDummy = ParserDummy()
|
dummy: ParserDummy = ParserDummy()
|
||||||
result_status: ParserDummy = Field(
|
result_status: ParserResultStatus = Field(
|
||||||
ParserDummy(), validation_alias=AliasChoices("result-status", "result_status")
|
ParserResultStatus(),
|
||||||
|
validation_alias=AliasChoices("result-status", "result_status"),
|
||||||
)
|
)
|
||||||
keyword: ParserKeyword = ParserKeyword()
|
keyword: ParserKeyword = ParserKeyword()
|
||||||
clangtidy: ParserKeyword = ParserKeyword()
|
clangtidy: ParserKeyword = ParserKeyword()
|
||||||
|
@ -164,6 +215,7 @@ 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] = {}
|
||||||
|
|
||||||
|
@ -195,17 +247,52 @@ 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 = Field(Path("."), exclude=True)
|
||||||
path: Path = Path("task.toml")
|
path: Path = Field(Path("task.toml"), exclude=True)
|
||||||
task: Task = Task() # Task name (e.g., hw3 ex5)
|
suffix: str = Field("", exclude=True)
|
||||||
release: Release = Release() # Release configuration
|
|
||||||
stages: List[Stage] = [] # list of stage configurations
|
task: Task = Task() # TODO: remove it in the future
|
||||||
groups: Groups = Groups()
|
name: str = "unknown" # Task name (e.g., hw3 ex5)
|
||||||
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"
|
||||||
|
time: SubmissionTime = SubmissionTime() # Valid time configuration
|
||||||
|
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
|
||||||
|
|
|
@ -9,16 +9,16 @@ def get_task_conf_from_answers(answers: answer.Answers) -> task.Config:
|
||||||
if answers.template_file_content:
|
if answers.template_file_content:
|
||||||
toml_dict = tomli.loads(answers.template_file_content)
|
toml_dict = tomli.loads(answers.template_file_content)
|
||||||
return task.Config(
|
return task.Config(
|
||||||
task=task.Task(name=answers.name),
|
name=answers.name,
|
||||||
stages=toml_dict["stages"],
|
stages=toml_dict["stages"],
|
||||||
)
|
)
|
||||||
language = answers.language
|
language = answers.language
|
||||||
transformer_dict = get_transformer_dict()
|
transformer_dict = get_transformer_dict()
|
||||||
if language not in transformer_dict:
|
if language not in transformer_dict:
|
||||||
return task.Config(task=task.Task(name=answers.name), stages=[])
|
return task.Config(name=answers.name, stages=[])
|
||||||
transformer = transformer_dict[language]
|
transformer = transformer_dict[language]
|
||||||
stages = transformer(language)
|
stages = transformer(language)
|
||||||
return task.Config(task=task.Task(name=answers.name), stages=stages)
|
return task.Config(name=answers.name, stages=stages)
|
||||||
|
|
||||||
|
|
||||||
def get_transformer_dict() -> Dict[
|
def get_transformer_dict() -> Dict[
|
||||||
|
|
|
@ -1,9 +1,26 @@
|
||||||
import hashlib
|
import hashlib
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import List
|
from typing import List, Tuple
|
||||||
|
|
||||||
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 TEAPOT_CONFIG_ROOT, TEAPOT_LOG_PATH
|
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
|
||||||
|
|
||||||
|
|
||||||
def get_teapot_post_stage(
|
def get_teapot_post_stage(
|
||||||
|
@ -17,13 +34,36 @@ def get_teapot_post_stage(
|
||||||
repo_conf.grading_repo_name,
|
repo_conf.grading_repo_name,
|
||||||
"--max-total-score",
|
"--max-total-score",
|
||||||
(
|
(
|
||||||
str(repo_conf.max_total_score)
|
str(task_conf.max_total_score)
|
||||||
if not task_conf.max_total_score
|
if task_conf.max_total_score is not None
|
||||||
else str(task_conf.max_total_score)
|
else str(repo_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.submitter_in_issue_title:
|
if repo_conf.issue.label.exclusive:
|
||||||
|
args.append("--issue-label-exclusive")
|
||||||
|
if not repo_conf.issue.show_submitter:
|
||||||
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",
|
||||||
|
@ -32,7 +72,7 @@ def get_teapot_post_stage(
|
||||||
with_=result.ExecutorWith(
|
with_=result.ExecutorWith(
|
||||||
default=result.Cmd(
|
default=result.Cmd(
|
||||||
args=args,
|
args=args,
|
||||||
env=[f"LOG_FILE_PATH={TEAPOT_LOG_PATH}"],
|
env=get_teapot_env(repo_conf),
|
||||||
cpu_limit=common.Time("30s"),
|
cpu_limit=common.Time("30s"),
|
||||||
clock_limit=common.Time("60s"),
|
clock_limit=common.Time("60s"),
|
||||||
),
|
),
|
||||||
|
@ -47,14 +87,47 @@ 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.health_check.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.health_check.max_size / 1024 / 1024)}", # B -> MB
|
||||||
*[f"-meta={meta}" for meta in repo_conf.files.required],
|
*[f"-meta={meta}" for meta in repo_conf.health_check.required_files],
|
||||||
f"-checkFileSumList={','.join(get_hashes(repo_conf))}",
|
f"-checkFileSumList={','.join(file_sums)}",
|
||||||
f"-checkFileNameList={','.join(repo_conf.files.immutable)}",
|
f"-checkFileNameList={','.join(file_names)}",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@ -65,28 +138,34 @@ 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_config_str = ",".join(
|
group_str = lambda groups: ",".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(
|
||||||
repo_conf.groups.name,
|
groups.name,
|
||||||
repo_conf.groups.max_count,
|
groups.max_count,
|
||||||
repo_conf.groups.time_period_hour,
|
groups.time_period_hour,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
if task_conf.groups.name:
|
group_config = group_str(
|
||||||
overwrite_group_config_str = ",".join(
|
task_conf.groups if task_conf.groups.name else repo_conf.groups
|
||||||
f"{name}={max_count}:{time_period}"
|
)
|
||||||
for name, max_count, time_period in zip(
|
res.extend(["--group-config", group_config])
|
||||||
task_conf.groups.name,
|
if task_conf.time.begin:
|
||||||
task_conf.groups.max_count,
|
res.extend(["--begin-time", task_conf.time.begin.strftime("%Y-%m-%dT%H:%M:%S")])
|
||||||
task_conf.groups.time_period_hour,
|
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
|
||||||
)
|
)
|
||||||
res.extend(["--group-config", overwrite_group_config_str])
|
)
|
||||||
else:
|
res.extend(["--penalty-config", penalty_config])
|
||||||
res.extend(["--group-config", group_config_str])
|
|
||||||
return res
|
return res
|
||||||
|
|
||||||
|
|
||||||
|
@ -100,7 +179,8 @@ 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"), clock_limit=common.Time("20s")
|
cpu_limit=common.Time("10s"),
|
||||||
|
clock_limit=common.Time("20s"),
|
||||||
),
|
),
|
||||||
cases=[
|
cases=[
|
||||||
result.OptionalCmd(
|
result.OptionalCmd(
|
||||||
|
@ -108,7 +188,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=[f"LOG_FILE_PATH={TEAPOT_LOG_PATH}"],
|
env=get_teapot_env(repo_conf),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
@ -116,7 +196,7 @@ def get_health_check_stage(
|
||||||
parsers=[
|
parsers=[
|
||||||
result.Parser(
|
result.Parser(
|
||||||
name="healthcheck",
|
name="healthcheck",
|
||||||
with_=result.ScoreConfig(score=repo_conf.health_check_score),
|
with_=result.ScoreConfig(score=repo_conf.health_check.score),
|
||||||
),
|
),
|
||||||
result.Parser(name="debug"),
|
result.Parser(name="debug"),
|
||||||
],
|
],
|
||||||
|
@ -130,12 +210,3 @@ 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]
|
|
||||||
|
|
|
@ -2,15 +2,10 @@ 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
|
from typing import Any, Callable, Dict, List, Optional, Tuple, Union
|
||||||
|
|
||||||
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.const import DEFAULT_PATH_ENV, JOJ3_CONFIG_ROOT
|
||||||
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
|
from joj3_config_generator.utils.logger import logger
|
||||||
|
|
||||||
|
|
||||||
|
@ -61,7 +56,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_dummy, task_stage.result_status),
|
task.Parser.RESULT_STATUS: (fix_result_status, 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,33 +74,38 @@ 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:
|
||||||
file_import = task_stage.files.import_
|
copy_in: dict[
|
||||||
copy_in_files = (file for file in file_import if file not in cached)
|
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_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},
|
||||||
copy_out_cached=file_export,
|
copy_out_cached=file_export,
|
||||||
cpu_limit=Time(task_stage.limit.cpu),
|
cpu_limit=task_stage.limit.cpu,
|
||||||
clock_limit=DEFAULT_CLOCK_LIMIT_MULTIPLIER * Time(task_stage.limit.cpu),
|
clock_limit=task_stage.limit.time,
|
||||||
memory_limit=Memory(task_stage.limit.mem),
|
memory_limit=task_stage.limit.mem,
|
||||||
proc_limit=task_stage.limit.proc,
|
proc_limit=task_stage.limit.proc,
|
||||||
stderr=result.Collector(
|
stderr=result.Collector(
|
||||||
name="stderr", pipe=True, max=Memory(task_stage.limit.stderr)
|
name="stderr", pipe=True, max=task_stage.limit.stderr
|
||||||
),
|
),
|
||||||
stdout=result.Collector(
|
stdout=result.Collector(
|
||||||
name="stdout", pipe=True, max=Memory(task_stage.limit.stdout)
|
name="stdout", pipe=True, max=task_stage.limit.stdout
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
cases=[],
|
cases=[],
|
||||||
|
@ -124,10 +124,11 @@ 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()
|
||||||
]
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -150,17 +151,29 @@ 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_on_not_accepted=dummy_parser_config.force_quit,
|
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,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -180,11 +193,7 @@ def fix_diff(
|
||||||
# cases not specified in the toml config (auto-detected)
|
# cases not specified in the toml config (auto-detected)
|
||||||
unspecified_cases = get_unspecified_cases(task_root, task_path, task_stage.cases)
|
unspecified_cases = get_unspecified_cases(task_root, task_path, task_stage.cases)
|
||||||
# cases specified in toml config but not skipped
|
# cases specified in toml config but not skipped
|
||||||
specified_cases = [
|
specified_cases = [(case, task_stage.cases[case]) for case in task_stage.cases]
|
||||||
(case, task_stage.cases[case])
|
|
||||||
for case in task_stage.cases
|
|
||||||
if case not in task_stage.skip
|
|
||||||
]
|
|
||||||
stage_cases = []
|
stage_cases = []
|
||||||
parser_cases = []
|
parser_cases = []
|
||||||
for case_name, case in specified_cases:
|
for case_name, case in specified_cases:
|
||||||
|
@ -200,7 +209,7 @@ def fix_diff(
|
||||||
stdin=stdin,
|
stdin=stdin,
|
||||||
args=shlex.split(case.command) if case.command else None,
|
args=shlex.split(case.command) if case.command else None,
|
||||||
cpu_limit=case.limit.cpu,
|
cpu_limit=case.limit.cpu,
|
||||||
clock_limit=DEFAULT_CLOCK_LIMIT_MULTIPLIER * case.limit.cpu,
|
clock_limit=case.limit.time,
|
||||||
memory_limit=case.limit.mem,
|
memory_limit=case.limit.mem,
|
||||||
proc_limit=task_stage.limit.proc,
|
proc_limit=task_stage.limit.proc,
|
||||||
)
|
)
|
||||||
|
@ -215,18 +224,24 @@ 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=case.diff.output.score,
|
score=get_diff_attribute("score"),
|
||||||
file_name="stdout",
|
filename="stdout",
|
||||||
answer_path=stdout,
|
answer_path=stdout,
|
||||||
force_quit_on_diff=case.diff.output.force_quit,
|
compare_space=not get_diff_attribute("ignore_spaces"),
|
||||||
always_hide=case.diff.output.hide,
|
always_hide=get_diff_attribute("hide"),
|
||||||
compare_space=not case.diff.output.ignore_spaces,
|
force_quit_on_diff=get_diff_attribute("force_quit"),
|
||||||
max_diff_length=case.diff.output.max_length,
|
max_diff_length=get_diff_attribute("max_length"),
|
||||||
max_diff_lines=case.diff.output.max_lines,
|
max_diff_lines=get_diff_attribute("max_lines"),
|
||||||
hide_common_prefix=case.diff.output.hide_common_prefix,
|
hide_common_prefix=get_diff_attribute("hide_common_prefix"),
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
@ -234,18 +249,20 @@ 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.default_score,
|
score=task_stage.diff.score,
|
||||||
file_name="stdout",
|
filename="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,
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
@ -287,16 +304,22 @@ def get_unspecified_cases(
|
||||||
def get_stdin_stdout(
|
def get_stdin_stdout(
|
||||||
task_root: Path, task_path: Path, case_name: str, case: task.Case
|
task_root: Path, task_path: Path, case_name: str, case: task.Case
|
||||||
) -> Tuple[result.Stdin, Optional[str]]:
|
) -> Tuple[result.Stdin, Optional[str]]:
|
||||||
case_stdout_name = case.out_ if case.out_ else f"{case_name}.out"
|
base_dir = (task_root / task_path).parent
|
||||||
stdin: result.Stdin = result.MemoryFile(content="")
|
stdin: result.Stdin = result.MemoryFile(content="")
|
||||||
stdout = None
|
stdout = None
|
||||||
for case_stdout_path in (task_root / task_path).parent.glob("**/*.out"):
|
for case_stdout_path in base_dir.glob("**/*.out"):
|
||||||
if case_stdout_path.name != case_stdout_name:
|
if not case.out_: # if not set, look for .out files with case name
|
||||||
continue
|
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
|
||||||
|
):
|
||||||
|
continue
|
||||||
stdout = str(JOJ3_CONFIG_ROOT / case_stdout_path.relative_to(task_root))
|
stdout = str(JOJ3_CONFIG_ROOT / case_stdout_path.relative_to(task_root))
|
||||||
case_stdin_path = case_stdout_path.with_suffix(".in")
|
case_stdin_path = case_stdout_path.with_suffix(".in")
|
||||||
if case.in_:
|
if case.in_:
|
||||||
case_stdin_path = Path((task_root / task_path).parent / case.in_)
|
case_stdin_path = Path(base_dir / case.in_)
|
||||||
if not case_stdin_path.exists():
|
if not case_stdin_path.exists():
|
||||||
logger.warning(
|
logger.warning(
|
||||||
f"In file {task_root / task_path}, "
|
f"In file {task_root / task_path}, "
|
||||||
|
|
0
tests/convert/__init__.py
Normal file
0
tests/convert/__init__.py
Normal file
0
tests/convert/basic/cases/case0.out
Normal file
0
tests/convert/basic/cases/case0.out
Normal file
0
tests/convert/basic/cases/case1.out
Normal file
0
tests/convert/basic/cases/case1.out
Normal file
0
tests/convert/basic/cases/case2.out
Normal file
0
tests/convert/basic/cases/case2.out
Normal file
|
@ -3,14 +3,13 @@ grading-repo-name = "ece280-joj"
|
||||||
sandbox-token = "test"
|
sandbox-token = "test"
|
||||||
# reconfigure later
|
# reconfigure later
|
||||||
max-total-score = 1000
|
max-total-score = 1000
|
||||||
max-size = 50.5
|
|
||||||
|
health-check.max-size = "50.5m"
|
||||||
|
health-check.immutable-path = "immutable"
|
||||||
|
health-check.required-files = ["README.md", "Changelog.md"]
|
||||||
|
|
||||||
# for tests
|
# for tests
|
||||||
[groups]
|
[groups]
|
||||||
name = ["joj", "run"]
|
name = ["joj", "run"]
|
||||||
max-count = [1000, 1000]
|
max-count = [1000, 1000]
|
||||||
time-period-hour = [24, 24]
|
time-period-hour = [24, 24]
|
||||||
|
|
||||||
[files]
|
|
||||||
required = ["README.md", "Changelog.md"]
|
|
||||||
immutable = [".gitignore", ".gitattributes",".gitea/workflows/push.yaml", ".gitea/workflows/release.yaml"]
|
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
{
|
{
|
||||||
"name": "hw7 ex2",
|
"name": "hw7 ex2",
|
||||||
"logPath": "/home/tt/.cache/joj3/joj3.log",
|
"logPath": "/home/tt/.cache/joj3/hw7/joj3.log",
|
||||||
"expireUnixTimestamp": 1735574399,
|
"expireUnixTimestamp": 0,
|
||||||
"effectiveUnixTimestamp": 1735487999,
|
"effectiveUnixTimestamp": 0,
|
||||||
"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=a5b63323a692d3d8b952442969649b4f823d58dae26429494f613df160710dfc,b1bbad25b830db0a77b15a033f9ca1b7ab44c1d2d05056412bd3e4421645f0bf,2ba059f3977e2e3dee6cacbfbf0ba2578baa1b8e04b4977aec400868b6e49856,3db23f7fb2ca9814617e767ddc41b77073180b3b0b73e87b5f2a6d3129f88f3a",
|
"-checkFileSumList=b1bbad25b830db0a77b15a033f9ca1b7ab44c1d2d05056412bd3e4421645f0bf,2ba059f3977e2e3dee6cacbfbf0ba2578baa1b8e04b4977aec400868b6e49856,3db23f7fb2ca9814617e767ddc41b77073180b3b0b73e87b5f2a6d3129f88f3a,a5b63323a692d3d8b952442969649b4f823d58dae26429494f613df160710dfc",
|
||||||
"-checkFileNameList=.gitignore,.gitattributes,.gitea/workflows/push.yaml,.gitea/workflows/release.yaml"
|
"-checkFileNameList=.gitattributes,.gitea/workflows/push.yaml,.gitea/workflows/release.yaml,.gitignore"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -75,10 +75,19 @@
|
||||||
"/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",
|
||||||
|
"--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"
|
||||||
],
|
],
|
||||||
"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"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -136,6 +145,9 @@
|
||||||
"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": {},
|
||||||
|
@ -260,6 +272,7 @@
|
||||||
{
|
{
|
||||||
"name": "keyword",
|
"name": "keyword",
|
||||||
"with": {
|
"with": {
|
||||||
|
"score": 0,
|
||||||
"matches": [
|
"matches": [
|
||||||
{
|
{
|
||||||
"keywords": [
|
"keywords": [
|
||||||
|
@ -368,6 +381,7 @@
|
||||||
{
|
{
|
||||||
"name": "clangtidy",
|
"name": "clangtidy",
|
||||||
"with": {
|
"with": {
|
||||||
|
"score": 0,
|
||||||
"matches": [
|
"matches": [
|
||||||
{
|
{
|
||||||
"keywords": [
|
"keywords": [
|
||||||
|
@ -498,12 +512,14 @@
|
||||||
{
|
{
|
||||||
"name": "keyword",
|
"name": "keyword",
|
||||||
"with": {
|
"with": {
|
||||||
|
"score": 0,
|
||||||
"matches": []
|
"matches": []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "cppcheck",
|
"name": "cppcheck",
|
||||||
"with": {
|
"with": {
|
||||||
|
"score": 0,
|
||||||
"matches": [
|
"matches": [
|
||||||
{
|
{
|
||||||
"keywords": [
|
"keywords": [
|
||||||
|
@ -526,6 +542,7 @@
|
||||||
{
|
{
|
||||||
"name": "clangtidy",
|
"name": "clangtidy",
|
||||||
"with": {
|
"with": {
|
||||||
|
"score": 0,
|
||||||
"matches": []
|
"matches": []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -551,6 +568,7 @@
|
||||||
{
|
{
|
||||||
"name": "cpplint",
|
"name": "cpplint",
|
||||||
"with": {
|
"with": {
|
||||||
|
"score": 0,
|
||||||
"matches": []
|
"matches": []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -559,7 +577,7 @@
|
||||||
"with": {
|
"with": {
|
||||||
"score": 0,
|
"score": 0,
|
||||||
"comment": "",
|
"comment": "",
|
||||||
"forceQuitOnNotAccepted": false
|
"forceQuitOnNotAccepted": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -573,7 +591,7 @@
|
||||||
"with": {
|
"with": {
|
||||||
"score": 0,
|
"score": 0,
|
||||||
"comment": "",
|
"comment": "",
|
||||||
"forceQuitOnNotAccepted": false
|
"forceQuit": false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -651,6 +669,7 @@
|
||||||
{
|
{
|
||||||
"name": "cpplint",
|
"name": "cpplint",
|
||||||
"with": {
|
"with": {
|
||||||
|
"score": 0,
|
||||||
"matches": [
|
"matches": [
|
||||||
{
|
{
|
||||||
"keywords": [
|
"keywords": [
|
||||||
|
@ -722,7 +741,7 @@
|
||||||
"pipe": true
|
"pipe": true
|
||||||
},
|
},
|
||||||
"cpuLimit": 1000000000,
|
"cpuLimit": 1000000000,
|
||||||
"clockLimit": 2000000000,
|
"clockLimit": 100000000000,
|
||||||
"memoryLimit": 134217728,
|
"memoryLimit": 134217728,
|
||||||
"stackLimit": 0,
|
"stackLimit": 0,
|
||||||
"procLimit": 50,
|
"procLimit": 50,
|
||||||
|
@ -749,7 +768,27 @@
|
||||||
"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": [
|
||||||
|
@ -757,7 +796,53 @@
|
||||||
"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
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -798,9 +883,20 @@
|
||||||
"--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": {
|
||||||
|
|
|
@ -2,8 +2,13 @@
|
||||||
task.name = "hw7 ex2" # task name
|
task.name = "hw7 ex2" # task name
|
||||||
|
|
||||||
max-total-score = 10245871
|
max-total-score = 10245871
|
||||||
release.end-time = 2024-12-30 23:59:59+08:00
|
time.end = 2024-12-30 23:59:59+08:00
|
||||||
release.begin-time = 2024-12-29 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 ]
|
||||||
|
|
||||||
[groups]
|
[groups]
|
||||||
name = ["Manuel", "Boming", "Nuvole"]
|
name = ["Manuel", "Boming", "Nuvole"]
|
||||||
|
@ -15,8 +20,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" ]
|
||||||
|
@ -86,24 +91,29 @@ command="./h7/build/ex2-asan -a"
|
||||||
copy-in-cwd = false
|
copy-in-cwd = false
|
||||||
files.import = [ "h7/build/ex2-asan" ]
|
files.import = [ "h7/build/ex2-asan" ]
|
||||||
limit.mem = "128m"
|
limit.mem = "128m"
|
||||||
|
limit.time = "100s"
|
||||||
|
|
||||||
|
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.time = "1s"
|
||||||
case0.limit.mem = "5m"
|
case0.limit.mem = "5m"
|
||||||
case0.diff.output.ignore-spaces = true
|
case0.diff.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.output.score = 5
|
case1.diff.score = 5
|
||||||
case1.limit.cpu = "1s"
|
case1.limit.cpu = "1s"
|
||||||
case1.limit.mem = "5m"
|
case1.limit.mem = "5m"
|
||||||
case1.diff.output.ignore-spaces = true
|
case1.diff.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
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
{
|
{
|
||||||
"name": "hw7 ex2",
|
"name": "hw7 ex2",
|
||||||
"logPath": "/home/tt/.cache/joj3/joj3.log",
|
"logPath": "/home/tt/.cache/joj3/hw7/joj3.log",
|
||||||
"expireUnixTimestamp": 1735574399,
|
"expireUnixTimestamp": 0,
|
||||||
"effectiveUnixTimestamp": 1735487999,
|
"effectiveUnixTimestamp": 0,
|
||||||
"actorCsvPath": "/home/tt/.config/joj/students.csv",
|
"actorCsvPath": "/home/tt/.config/joj/students.csv",
|
||||||
"maxTotalScore": 100,
|
"maxTotalScore": 100,
|
||||||
"stage": {
|
"stage": {
|
||||||
|
@ -78,6 +78,7 @@
|
||||||
{
|
{
|
||||||
"name": "clangtidy",
|
"name": "clangtidy",
|
||||||
"with": {
|
"with": {
|
||||||
|
"score": 0,
|
||||||
"matches": [
|
"matches": [
|
||||||
{
|
{
|
||||||
"keywords": [
|
"keywords": [
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
# general task configuration
|
# general task configuration
|
||||||
task.name = "hw7 ex2" # task name
|
task.name = "hw7 ex2" # task name
|
||||||
|
|
||||||
release.end-time = 2024-12-30 23:59:59+08:00
|
time.end = 2024-12-30 23:59:59+08:00
|
||||||
release.begin-time = 2024-12-29 23:59:59+08:00
|
time.begin = 2024-12-29 23:59:59+08:00
|
||||||
|
|
||||||
[[stages]]
|
[[stages]]
|
||||||
name = "[cq] Clang-tidy"
|
name = "[cq] Clang-tidy"
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
{
|
{
|
||||||
"name": "hw7 ex2",
|
"name": "hw7 ex2",
|
||||||
"logPath": "/home/tt/.cache/joj3/joj3.log",
|
"logPath": "/home/tt/.cache/joj3/hw7/joj3.log",
|
||||||
"expireUnixTimestamp": 1735574399,
|
"expireUnixTimestamp": 0,
|
||||||
"effectiveUnixTimestamp": 1735487999,
|
"effectiveUnixTimestamp": 0,
|
||||||
"actorCsvPath": "/home/tt/.config/joj/students.csv",
|
"actorCsvPath": "/home/tt/.config/joj/students.csv",
|
||||||
"maxTotalScore": 100,
|
"maxTotalScore": 100,
|
||||||
"stage": {
|
"stage": {
|
||||||
|
@ -71,6 +71,7 @@
|
||||||
{
|
{
|
||||||
"name": "cppcheck",
|
"name": "cppcheck",
|
||||||
"with": {
|
"with": {
|
||||||
|
"score": 0,
|
||||||
"matches": [
|
"matches": [
|
||||||
{
|
{
|
||||||
"keywords": [
|
"keywords": [
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
# general task configuration
|
# general task configuration
|
||||||
task.name = "hw7 ex2" # task name
|
task.name = "hw7 ex2" # task name
|
||||||
|
|
||||||
release.end-time = 2024-12-30 23:59:59+08:00
|
time.end = 2024-12-30 23:59:59+08:00
|
||||||
release.begin-time = 2024-12-29 23:59:59+08:00
|
time.begin = 2024-12-29 23:59:59+08:00
|
||||||
|
|
||||||
[[stages]]
|
[[stages]]
|
||||||
name = "[cq] Cppcheck"
|
name = "[cq] Cppcheck"
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
{
|
{
|
||||||
"name": "hw7 ex2",
|
"name": "hw7 ex2",
|
||||||
"logPath": "/home/tt/.cache/joj3/joj3.log",
|
"logPath": "/home/tt/.cache/joj3/hw7/joj3.log",
|
||||||
"expireUnixTimestamp": 1735574399,
|
"expireUnixTimestamp": 0,
|
||||||
"effectiveUnixTimestamp": 1735487999,
|
"effectiveUnixTimestamp": 0,
|
||||||
"actorCsvPath": "/home/tt/.config/joj/students.csv",
|
"actorCsvPath": "/home/tt/.config/joj/students.csv",
|
||||||
"maxTotalScore": 100,
|
"maxTotalScore": 100,
|
||||||
"stage": {
|
"stage": {
|
||||||
|
@ -70,6 +70,7 @@
|
||||||
{
|
{
|
||||||
"name": "cpplint",
|
"name": "cpplint",
|
||||||
"with": {
|
"with": {
|
||||||
|
"score": 0,
|
||||||
"matches": [
|
"matches": [
|
||||||
{
|
{
|
||||||
"keywords": [
|
"keywords": [
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
# general task configuration
|
# general task configuration
|
||||||
task.name = "hw7 ex2" # task name
|
task.name = "hw7 ex2" # task name
|
||||||
|
|
||||||
release.end-time = 2024-12-30 23:59:59+08:00
|
time.end = 2024-12-30 23:59:59+08:00
|
||||||
release.begin-time = 2024-12-29 23:59:59+08:00
|
time.begin = 2024-12-29 23:59:59+08:00
|
||||||
|
|
||||||
[[stages]]
|
[[stages]]
|
||||||
name = "[cq] Cpplint"
|
name = "[cq] Cpplint"
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
{
|
{
|
||||||
"name": "hw7 ex2",
|
"name": "hw7 ex2",
|
||||||
"logPath": "/home/tt/.cache/joj3/joj3.log",
|
"logPath": "/home/tt/.cache/joj3/hw7/joj3.log",
|
||||||
"expireUnixTimestamp": 1735574399,
|
"expireUnixTimestamp": 0,
|
||||||
"effectiveUnixTimestamp": 1735487999,
|
"effectiveUnixTimestamp": 0,
|
||||||
"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,
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
# general task configuration
|
# general task configuration
|
||||||
task.name = "hw7 ex2" # task name
|
task.name = "hw7 ex2" # task name
|
||||||
|
|
||||||
release.end-time = 2024-12-30 23:59:59+08:00
|
time.end = 2024-12-30 23:59:59+08:00
|
||||||
release.begin-time = 2024-12-29 23:59:59+08:00
|
time.begin = 2024-12-29 23:59:59+08:00
|
||||||
|
|
||||||
[[stages]]
|
[[stages]]
|
||||||
name = "[joj] ex2-asan"
|
name = "[joj] ex2-asan"
|
||||||
|
@ -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.default-score = 100
|
diff.score = 100
|
||||||
|
|
||||||
case0.diff.output.score = 5
|
case0.diff.score = 5
|
||||||
case0.limit.cpu = "1s"
|
case0.limit.cpu = "1s"
|
||||||
case0.limit.mem = "2m"
|
case0.limit.mem = "2m"
|
||||||
case0.diff.output.ignore-spaces = true
|
case0.diff.ignore-spaces = true
|
||||||
case0.command = "./h7/build/ex2"
|
case0.command = "./h7/build/ex2"
|
||||||
case0.in = "case0.in"
|
case0.in = "case0.in"
|
||||||
|
|
||||||
case1.diff.output.score = 123214122421
|
case1.diff.score = 123214122421
|
||||||
case1.limit.cpu = "2s"
|
case1.limit.cpu = "2s"
|
||||||
case1.limit.mem = "4m"
|
case1.limit.mem = "4m"
|
||||||
case1.diff.output.ignore-spaces = true
|
case1.diff.ignore-spaces = true
|
||||||
case1.command = "./h7/build/ex2"
|
case1.command = "./h7/build/ex2"
|
||||||
|
|
||||||
case9.diff.output.score = 1232131
|
case9.diff.score = 1232131
|
||||||
case9.limit.mem = "10m"
|
case9.limit.mem = "10m"
|
||||||
|
|
||||||
case11.diff.output.score = 92321
|
case11.diff.score = 92321
|
||||||
|
|
||||||
case10.diff.output.score = 823131
|
case10.diff.score = 823131
|
||||||
|
|
||||||
case5.diff.output.score = 2590
|
case5.diff.score = 2590
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
{
|
{
|
||||||
"name": "hw7 ex2",
|
"name": "hw7 ex2",
|
||||||
"logPath": "/home/tt/.cache/joj3/joj3.log",
|
"logPath": "/home/tt/.cache/joj3/hw7/joj3.log",
|
||||||
"expireUnixTimestamp": 1735574399,
|
"expireUnixTimestamp": 0,
|
||||||
"effectiveUnixTimestamp": 1735487999,
|
"effectiveUnixTimestamp": 0,
|
||||||
"actorCsvPath": "/home/tt/.config/joj/students.csv",
|
"actorCsvPath": "/home/tt/.config/joj/students.csv",
|
||||||
"maxTotalScore": 100,
|
"maxTotalScore": 100,
|
||||||
"stage": {
|
"stage": {
|
||||||
|
@ -74,6 +74,7 @@
|
||||||
{
|
{
|
||||||
"name": "elf",
|
"name": "elf",
|
||||||
"with": {
|
"with": {
|
||||||
|
"score": 0,
|
||||||
"matches": [
|
"matches": [
|
||||||
{
|
{
|
||||||
"keywords": [
|
"keywords": [
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
# general task configuration
|
# general task configuration
|
||||||
task.name = "hw7 ex2" # task name
|
task.name = "hw7 ex2" # task name
|
||||||
|
|
||||||
release.end-time = 2024-12-30 23:59:59+08:00
|
time.end = 2024-12-30 23:59:59+08:00
|
||||||
release.begin-time = 2024-12-29 23:59:59+08:00
|
time.begin = 2024-12-29 23:59:59+08:00
|
||||||
|
|
||||||
[[stages]]
|
[[stages]]
|
||||||
name = "[cq] elf"
|
name = "[cq] elf"
|
||||||
|
|
0
tests/convert/empty/repo.toml
Normal file
0
tests/convert/empty/repo.toml
Normal file
182
tests/convert/empty/task.json
Normal file
182
tests/convert/empty/task.json
Normal file
|
@ -0,0 +1,182 @@
|
||||||
|
{
|
||||||
|
"name": "invalid commit",
|
||||||
|
"logPath": "/home/tt/.cache/joj3/invalid/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": {}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
2
tests/convert/empty/task.toml
Normal file
2
tests/convert/empty/task.toml
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
name = "invalid commit"
|
||||||
|
max-total-score = 0
|
|
@ -1,8 +1,8 @@
|
||||||
{
|
{
|
||||||
"name": "hw7 ex2",
|
"name": "hw7 ex2",
|
||||||
"logPath": "/home/tt/.cache/joj3/joj3.log",
|
"logPath": "/home/tt/.cache/joj3/hw7/joj3.log",
|
||||||
"expireUnixTimestamp": 1735574399,
|
"expireUnixTimestamp": 0,
|
||||||
"effectiveUnixTimestamp": 1735487999,
|
"effectiveUnixTimestamp": 0,
|
||||||
"actorCsvPath": "/home/tt/.config/joj/students.csv",
|
"actorCsvPath": "/home/tt/.config/joj/students.csv",
|
||||||
"maxTotalScore": 100,
|
"maxTotalScore": 100,
|
||||||
"stage": {
|
"stage": {
|
||||||
|
@ -73,6 +73,7 @@
|
||||||
{
|
{
|
||||||
"name": "keyword",
|
"name": "keyword",
|
||||||
"with": {
|
"with": {
|
||||||
|
"score": 0,
|
||||||
"matches": [
|
"matches": [
|
||||||
{
|
{
|
||||||
"keywords": [
|
"keywords": [
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
# general task configuration
|
# general task configuration
|
||||||
task.name = "hw7 ex2" # task name
|
task.name = "hw7 ex2" # task name
|
||||||
|
|
||||||
release.end-time = 2024-12-30 23:59:59+08:00
|
time.end = 2024-12-30 23:59:59+08:00
|
||||||
release.begin-time = 2024-12-29 23:59:59+08:00
|
time.begin = 2024-12-29 23:59:59+08:00
|
||||||
|
|
||||||
[[stages]]
|
[[stages]]
|
||||||
name = "[cq] Filelength"
|
name = "[cq] Filelength"
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
{
|
{
|
||||||
"name": "hw7 ex2",
|
"name": "hw7 ex2",
|
||||||
"logPath": "/home/tt/.cache/joj3/joj3.log",
|
"logPath": "/home/tt/.cache/joj3/hw7/joj3.log",
|
||||||
"expireUnixTimestamp": 1735574399,
|
"expireUnixTimestamp": 0,
|
||||||
"effectiveUnixTimestamp": 1735487999,
|
"effectiveUnixTimestamp": 0,
|
||||||
"actorCsvPath": "/home/tt/.config/joj/students.csv",
|
"actorCsvPath": "/home/tt/.config/joj/students.csv",
|
||||||
"maxTotalScore": 100,
|
"maxTotalScore": 100,
|
||||||
"stage": {
|
"stage": {
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
# general task configuration
|
# general task configuration
|
||||||
task.name = "hw7 ex2" # task name
|
task.name = "hw7 ex2" # task name
|
||||||
|
|
||||||
release.end-time = 2024-12-30 23:59:59+08:00
|
time.end = 2024-12-30 23:59:59+08:00
|
||||||
release.begin-time = 2024-12-29 23:59:59+08:00
|
time.begin = 2024-12-29 23:59:59+08:00
|
||||||
|
|
||||||
[[stages]]
|
[[stages]]
|
||||||
name = "[cq] Filelength"
|
name = "[cq] Filelength"
|
||||||
|
|
|
@ -21,6 +21,14 @@ def test_diff() -> None:
|
||||||
load_case("diff")
|
load_case("diff")
|
||||||
|
|
||||||
|
|
||||||
|
def test_elf() -> None:
|
||||||
|
load_case("elf")
|
||||||
|
|
||||||
|
|
||||||
|
def test_empty() -> None:
|
||||||
|
load_case("empty")
|
||||||
|
|
||||||
|
|
||||||
def test_keyword() -> None:
|
def test_keyword() -> None:
|
||||||
load_case("keyword")
|
load_case("keyword")
|
||||||
|
|
||||||
|
@ -29,5 +37,5 @@ def test_result_detail() -> None:
|
||||||
load_case("result-detail")
|
load_case("result-detail")
|
||||||
|
|
||||||
|
|
||||||
def test_elf() -> None:
|
def test_unnecessary() -> None:
|
||||||
load_case("elf")
|
load_case("unnecessary")
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "hw7 ex2",
|
"name": "hw7 ex2",
|
||||||
"logPath": "/home/tt/.cache/joj3/joj3.log",
|
"logPath": "/home/tt/.cache/joj3/hw7/joj3.log",
|
||||||
"expireUnixTimestamp": 0,
|
"expireUnixTimestamp": 0,
|
||||||
"effectiveUnixTimestamp": 0,
|
"effectiveUnixTimestamp": 0,
|
||||||
"actorCsvPath": "/home/tt/.config/joj/students.csv",
|
"actorCsvPath": "/home/tt/.config/joj/students.csv",
|
||||||
|
|
0
tests/convert_joj1/__init__.py
Normal file
0
tests/convert_joj1/__init__.py
Normal file
0
tests/create/__init__.py
Normal file
0
tests/create/__init__.py
Normal file
|
@ -1,4 +1,3 @@
|
||||||
[task]
|
|
||||||
name = "hw7 ex2"
|
name = "hw7 ex2"
|
||||||
|
|
||||||
[[stages]]
|
[[stages]]
|
||||||
|
|
Loading…
Reference in New Issue
Block a user