WIP: dev #6

Closed
李衍志523370910113 wants to merge 131 commits from dev into master
7 changed files with 325 additions and 696 deletions
Showing only changes of commit b45d65250d - Show all commits

View File

@ -19,35 +19,76 @@ from joj3_config_generator.lib.task import (
get_executorWithConfig,
)
from joj3_config_generator.models import joj1, repo, result, task
from joj3_config_generator.lib.repo import getHealthcheckConfig, getTeapotConfig
from joj3_config_generator.models import (
Cmd,
CmdFile,
ExecutorConfig,
ExecutorWithConfig,
ParserConfig,
Repo,
ResultConfig,
Stage,
StageConfig,
Task,
TeapotConfig,
)
# FIXME: LLM generated convert function, only for demostration
def convert(repo_conf: repo.Config, task_conf: task.Config) -> result.Config:
# Create the base ResultConf object
result_conf = result.Config(
name=task_conf.task,
# TODO: specify the exact folder difference
log_path=f"{task_conf.task.replace(' ', '-')}.log",
# TODO: specify the exact folder difference
log_path=f"{task_conf.task.replace(' ', '-')}.log",
expire_unix_timestamp=(
int(task_conf.release.deadline.timestamp())
if task_conf.release.deadline
else -1
),
stage=result.Stage(stages=[], sandbox_token=repo_conf.sandbox_token),
teapot=result.Teapot(),
stage=StageConfig(stages=[], sandbox_token=repo_conf.sandbox_token),
teapot=getTeapotConfig(repo_conf, task_conf),
)
# Construct healthcheck stage
healthcheck_stage = getHealthcheckConfig(repo_conf)
healthcheck_stage = getHealthcheckConfig(repo_conf, task_conf)
result_conf.stage.stages.append(healthcheck_stage)
cached: list[str] = []
cached = []
# Convert each stage in the task configuration
for task_stage in task_conf.stages:
executor_with_config, cached = get_executorWithConfig(task_stage, cached)
conf_stage = get_conf_stage(task_stage, executor_with_config)
conf_stage = fix_result_detail(task_stage, conf_stage)
conf_stage = fix_dummy(task_stage, conf_stage)
conf_stage = fix_keyword(task_stage, conf_stage)
conf_stage = fix_diff(task_stage, conf_stage)
executor_with_config = result.ExecutorWith(
default=result.Cmd(
args=task_stage.command.split(),
copy_in={
file: result.CmdFile(src=file) for file in task_stage.files.import_
},
copy_out_cached=task_stage.files.export,
),
cases=[], # You can add cases if needed
)
conf_stage = result.StageDetail(
name=task_stage.name,
group=task_conf.task,
executor=result.Executor(
name="sandbox",
with_=executor_with_config,
),
parsers=[
result.Parser(name=parser, with_={}) for parser in task_stage.parsers
],
)
if "result-detail" in task_stage.parsers:
result_detail_parser = next(
p for p in conf_stage.parsers if p.name == "result-detail"
)
if task_stage.result_detail is not None:
result_detail_parser.with_.update(task_stage.result_detail)
result_conf.stage.stages.append(conf_stage)
return result_conf

View File

@ -0,0 +1,11 @@
from joj3_config_generator.models.repo import Repo as Repo
from joj3_config_generator.models.result import Cmd as Cmd
from joj3_config_generator.models.result import CmdFile as CmdFile
from joj3_config_generator.models.result import ExecutorConfig as ExecutorConfig
from joj3_config_generator.models.result import ExecutorWithConfig as ExecutorWithConfig
from joj3_config_generator.models.result import ParserConfig as ParserConfig
from joj3_config_generator.models.result import ResultConfig as ResultConfig
from joj3_config_generator.models.result import Stage as Stage
from joj3_config_generator.models.result import StageConfig as StageConfig
from joj3_config_generator.models.result import TeapotConfig as TeapotConfig
from joj3_config_generator.models.task import Task as Task

View File

@ -1,18 +1,40 @@
import hashlib
import shlex
import socket
from pathlib import Path
import os
import tempfile
from joj3_config_generator.models import joj1, repo, result, task
from dotenv import load_dotenv
from joj3_config_generator.models import (
Cmd,
CmdFile,
ExecutorConfig,
ExecutorWithConfig,
ParserConfig,
Repo,
ResultConfig,
Stage,
StageConfig,
Task,
TeapotConfig,
)
def get_temp_directory() -> str:
return tempfile.mkdtemp(prefix="repo-checker-")
def getGradingRepoName() -> str:
host_name = socket.gethostname()
return f"{host_name.split('-')[0]}-joj"
path = os.path.expanduser("~/.config/teapot/teapot.env")
if os.path.exists(path):
load_dotenv(path)
repo_name = os.environ.get("GITEA_ORG_NAME")
if repo_name is not None:
return f"{repo_name.split('-')[0]}-joj"
return "ece482-joj"
def getTeapotConfig(repo_conf: repo.Config, task_conf: task.Config) -> result.Teapot:
teapot = result.Teapot(
def getTeapotConfig(repo_conf: Repo, task_conf: Task) -> TeapotConfig:
teapot = TeapotConfig(
# TODO: fix the log path
log_path=f"{task_conf.task.replace(' ', '-')}-joint-teapot-debug.log",
scoreboard_path=f"{task_conf.task.replace(' ', '-')}-scoreboard.csv",
@ -22,7 +44,7 @@ def getTeapotConfig(repo_conf: repo.Config, task_conf: task.Config) -> result.Te
return teapot
def getHealthcheckCmd(repo_conf: repo.Config) -> result.Cmd:
def getHealthcheckCmd(repo_conf: Repo) -> Cmd:
repoSize = repo_conf.max_size
immutable = repo_conf.files.immutable
repo_size = f"-repoSize={str(repoSize)} "
@ -38,7 +60,7 @@ def getHealthcheckCmd(repo_conf: repo.Config) -> result.Cmd:
else:
immutable_files = immutable_files + name + ","
# FIXME: need to make solution and make things easier to edit with global scope
chore = f"./repo-health-checker -root=. "
chore = f"/{get_temp_directory}/repo-health-checker -root=. "
args = ""
args = args + chore
args = args + repo_size
@ -49,25 +71,27 @@ def getHealthcheckCmd(repo_conf: repo.Config) -> result.Cmd:
args = args + immutable_files
cmd = result.Cmd(
args=shlex.split(args),
cmd = Cmd(
args=args.split(),
# FIXME: easier to edit within global scope
copy_in={
f"./repo-health-checker": result.CmdFile(src=f"./repo-health-checker")
f"/{get_temp_directory()}/repo-health-checker": CmdFile(
src=f"/{get_temp_directory()}/repo-health-checker"
)
},
)
return cmd
def getHealthcheckConfig(repo_conf: repo.Config) -> result.StageDetail:
healthcheck_stage = result.StageDetail(
def getHealthcheckConfig(repo_conf: Repo, task_conf: Task) -> Stage:
healthcheck_stage = Stage(

hard coded path not acceptable

hard coded path not acceptable

thats what I wanted to discuss, in what form would be better? @manuel @bomingzh
my suggestions is to add more fields related to this in repo.toml or task.toml, I think store it globally in some *.py file is still hardcoded

thats what I wanted to discuss, in what form would be better? @manuel @bomingzh my suggestions is to add more fields related to this in `repo.toml` or `task.toml`, I think store it globally in some `*.py` file is still hardcoded

just let them enter path relative to project root for immutable files

just let them enter path relative to project root for immutable files

giving prompt? Wouldn't it be better to export field in *.toml file? they may accidnetally input wrong things I guess

giving prompt? Wouldn't it be better to export field in `*.toml` file? they may accidnetally input wrong things I guess

just throw error on wrong input

just throw error on wrong input

I mean, they input \home\tt\.config\ instead of \home\tt\.config\joj such things may happen, but will cost time to figure out what happened? So I think better put them in *.toml.

I mean, they input `\home\tt\.config\` instead of `\home\tt\.config\joj` such things may happen, but will cost time to figure out what happened? So I think better put them in `*.toml`.

just make the dir relative to git repo root, if the file does not exist, throw error

just make the dir relative to git repo root, if the file does not exist, throw error

any path in toml should probably be defined relative to /home/tt/.config/joj and be in a subdirectory. this is where all config must be.
then they can specify projects/p2/.cang-tidy or tool/compile

using project_root as too tricky as it could mean going up (../../) which is hard to read/figure out quickly.

as all JOJ config files must be in $HOME/.config/joj taking it as root makes sense, is general, and consistent with the approach we have used so far

any path in toml should probably be defined relative to `/home/tt/.config/joj` and be in a subdirectory. this is where all config must be. then they can specify `projects/p2/.cang-tidy` or `tool/compile` using `project_root` as too tricky as it could mean going up (`../../`) which is hard to read/figure out quickly. as all JOJ config files must be in `$HOME/.config/joj` taking it as root makes sense, is general, and consistent with the approach we have used so far

bit mixed here, if fixed, can I hardcode it?

bit mixed here, if fixed, can I hardcode it?
name="healthcheck",
group="",
executor=result.Executor(
executor=ExecutorConfig(
name="sandbox",
with_=result.ExecutorWith(default=getHealthcheckCmd(repo_conf), cases=[]),
with_=ExecutorWithConfig(default=getHealthcheckCmd(repo_conf), cases=[]),
),
parsers=[result.Parser(name="healthcheck", with_={"score": 0, "comment": ""})],
parsers=[ParserConfig(name="healthcheck", with_={"score": 0, "comment": ""})],
)
return healthcheck_stage
@ -81,10 +105,7 @@ def calc_sha256sum(file_path: str) -> str:
def get_hash(immutable_files: list[str]) -> str: # input should be a list
# FIXME: should be finalized when get into the server
current_file_path = Path(__file__).resolve()
project_root = current_file_path.parents[2]
file_path = f"{project_root}/tests/immutable_file/"
file_path = "../immutable_file/" # TODO: change this when things are on the server
immutable_hash = []
for i, file in enumerate(immutable_files):
immutable_files[i] = file_path + file.rsplit("/", 1)[-1]

View File

@ -14,31 +14,17 @@ class ParserResultDetail(BaseModel):
class ParserDummy(BaseModel):
comment: Optional[str] = ""
score: Optional[int] = 0
forcequit: Optional[bool] = True
class ParserKeyword(BaseModel):
keyword: Optional[list[str]] = []
weight: Optional[list[int]] = []
class Outputs(BaseModel):
score: Optional[int] = 0
ignorespaces: Optional[bool] = False
hide: Optional[bool] = False
forcequit: Optional[bool] = True
class ParserDiff(BaseModel):
output: Optional[Outputs] = Outputs()
keyword: Optional[list[str]] = None
weight: Optional[list[int]] = None
class Files(BaseModel):
import_: Optional[List[str]] = Field(
[], serialization_alias="import", validation_alias="import"
)
export: Optional[List[str]] = []
import_: Optional[List[str]] = Field(serialization_alias="import", validation_alias="import")
export: Optional[List[str]]
class Limit(BaseModel):
@ -49,36 +35,20 @@ class Limit(BaseModel):
class Stage(BaseModel):
name: Optional[str] = None # Stage name
command: Optional[str] = None # Command to run
name: str # Stage name
command: str # Command to run
files: Optional[Files] = None
score: Optional[int] = 0
parsers: Optional[list[str]] = [] # list of parsers
limit: Optional[Limit] = Limit()
parsers: list[str] # list of parsers
limit: Optional[Limit] = None
dummy: Optional[ParserDummy] = ParserDummy()
result_status: Optional[ParserDummy] = Field(ParserDummy(), alias="result-status")
keyword: Optional[ParserKeyword] = ParserKeyword()
clangtidy: Optional[ParserKeyword] = ParserKeyword()
cppcheck: Optional[ParserKeyword] = ParserKeyword()
# FIXME: determine cpplint type
cpplint: Optional[ParserKeyword] = ParserKeyword()
result_detail: Optional[ParserResultDetail] = Field(
ParserResultDetail(), alias="result-detail"
)
skip: Optional[list[str]] = []
diff: Optional[ParserDiff] = ParserDiff()
cases: Optional[Dict[str, "Stage"]] = {}
class Config:
extra = "allow"
@root_validator(pre=True)
def gather_cases(cls: Type["Stage"], values: Dict[str, Any]) -> Dict[str, Any]:
cases = {k: v for k, v in values.items() if k.startswith("case")}
for key in cases:
values.pop(key)
values["cases"] = {k: Stage(**v) for k, v in cases.items()}
return values
class Release(BaseModel):

View File

@ -6,5 +6,5 @@ sandbox_token = "test"
[files]
whitelist_patterns = ["*.py", "*.txt", "*.md"]
whitelist_file = ".whitelist"
required = [ "Changelog.md", "Readme.md" ]
immutable = [".gitignore", ".gitattributes", ".gitea/workflows/push.yaml", ".gitea/workflows/release.yaml" ]
required = ["main.py", "README.md"]
immutable = [".gitignore", ".gitattributes", "push.yaml", "release.yaml"]

File diff suppressed because it is too large Load Diff

View File

@ -1,31 +1,20 @@
# p2 repo config
task="p2 m3" # task name
# general task configuration

make this basic test as simple as possible, and create new test cases for each kind of stage

make this basic test as simple as possible, and create new test cases for each kind of stage
task="Homework 1 exercise 2" # task name
release.deadline = 2024-10-12 23:59:00+08:00
release.stages = [ "compile" ]
[files]
immutable = [".gitignore", ".gitattributes", ".gitea/workflows/push.yaml", ".gitea/workflows/release.yaml" ]
required = [ "Changelog.md", "Readme.md" ]
[[stages]]
name = "Abuse of strings detected"
command = "./strdetect src/"
files.import = [ "tools/strdetec" ]
parsers = [ "result-status" ]
[[stages]]
name = "Compilation"
command = "compile"
files.import = [ "tools/compile" ]
files.export = [ "build/onecard", "build/asan", "build/ubsan", "build/msan", "build/compile_commands.json" ]
command = "make.sh" # eg. script running cmake commands
files.import = [ "tools/make.sh", "src/main.c", "src/task.h", "srcCMakelist.txt" ]
files.export = [ "driver", "p2", "p2-msan" ]
limit.cpu = 180 # p2 takes long to compile
limit.stderr = 128
# compile parsers
parsers = [ "result-detail", "dummy", "result-status" ]
result-status.comment = "Congratulations! Your code compiled successfully."
result-status.score = 1
dummy.comment = "\n\n### Details\n"
result-detail.exitstatus = true
result-detail.stderr = true
@ -33,28 +22,26 @@ result-detail.time = false
result-detail.mem = false
[[stages]]
name = "[cq] Filelength"
command = "./file-length 400 300 *.c *.h"
files.import = [ "tools/filelength" ]
name = "File length check"
command = "./file-length 500 400 *.c *.h" # command to run
files.import = [ "tools/file-length" ]
parsers = [ "keyword", "dummy", "result-detail" ]
keyword.keyword = [ "max", "recommended"]
keyword.weight = [ 20, 10 ]
dummy.comment = "\n\n### Details\n"
result-detail.exitstatus = true
result-detail.stdout = true
keyword.keyword = [ "max", "recommend"] # keywords caught by corresponding JOJ plugin
keyword.weight = [ 50, 20 ] # weight of each keyword
result-detail.exitstatus = false
result-detail.stderr = true
result-detail.time = false
result-detail.mem = false
[[stages]]
name = "[cq] Clang-tidy"
name = "Clang-tidy checks"
command = "run-clang-tidy-18 -header-filter=.* -quiet -load=/usr/local/lib/libcodequality.so -p build"
files.import = [ "projects/p2/.clang-tidy", "build/compile_commands.json" ]
limit.stdout = 65
parsers = [ "clangtidy", "dummy", "result-detail" ]
clangtidy.keyword = [ "codequality-unchecked-malloc-result", "codequality-no-global-variables", "codequality-no-header-guard", "codequality-no-fflush-stdin", "readability-function-size", "readability-duplicate-include", "readability-identifier-naming", "readability-redundant", "readability-misleading-indentation", "readability-misplaced-array-index", "cppcoreguidelines-init-variables", "bugprone-suspicious-string-compare", "google-global-names-in-headers", "clang-diagnostic", "clang-analyzer", "misc", "performance", "portability" ]
clangtidy.weight = [ 5, 20, 20, 20, 10, 5, 5, 5, 15, 5, 5, 5, 5, 5, 5, 5, 5, 5]
clangtidy.keyword = [ "codequality-no-global-variables", "codequality-no-header-guard", "readability-function-size", "readability-duplicate-include", "readability-identifier-naming", "readability-redundant", "readability-misleading-indentation", "readability-misplaced-array-index", "cppcoreguidelines-init-variables", "bugprone-suspicious-string-compare", "google-global-names-in-headers", "clang-diagnostic", "clang-analyzer", "misc performance" ]
clangtidy.weight = [10, 10, 50, 10, 5, 5, 10, 5, 5, 8, 5, 5, 5, 5, 8]
dummy.comment = "\n\n### Details\n"
result-detail.exitstatus = true
result-detail.stdout = true
@ -62,13 +49,13 @@ result-detail.time = false
result-detail.mem = false
[[stages]]
name = "[cq] Cppcheck"
command = "cppcheck --template='{\"file\":\"{file}\",\"line\":{line}, \"column\":{column}, \"severity\":\"{severity}\", \"message\":\"{message}\", \"id\":\"{id}\"}' --force --enable=all --suppress=missingIncludeSystem --quiet ./"
name = "Cppcheck check"
command = "cppcheck --template='{\"file\":\"{file}\",\"line\":{line}, \"column\":{column}, \"severity\":\"{severity}\", \"message\":\"{message}\", \"id\":\"{id}\"}' --force --enable=all --quiet ./"
limit.stderr = 65
parsers = [ "cppcheck", "dummy", "result-detail" ]
cppcheck.keyword = ["error", "warning", "portability", "performance", "style"]
cppcheck.weight = [15, 5, 5, 5, 5]
cppcheck.weight = [20, 10, 15, 15, 10]
dummy.comment = "\n\n### Details\n"
result-detail.exitstatus = true
result-detail.stderr = true
@ -76,62 +63,57 @@ result-detail.time = false
result-detail.mem = false
[[stages]]
name = "[cq] Cpplint"
name = "Cpplint check"
command = "cpplint --linelength=120 --filter=-legal,-readability/casting,-whitespace,-runtime/printf,-runtime/threadsafe_fn,-readability/todo,-build/include_subdir,-build/header_guard --recursive --exclude=build ."
limit.stdout = 65
parsers = [ "cpplint", "dummy", "result-detail" ]
cpplint.keyword = [ "runtime", "readability", "build" ]
cpplint.weight = [ 5, 20, 10]
cpplint.weight = [ 10, 20, 15]
dummy.comment = "\n\n### Details\n"
result-detail.exitstatus = true
result-detail.stderr = true
result-detail.stdout = true
result-detail.time = false
result-detail.mem = false
[[stages]]
name = "[run] onecard"
group = "run"
command="./onecard -a"
files.import = [ "build/onecard" ]
name = "judge-base"
command="./driver ./mumsh"
limit.cpu = 3
limit.mem = 75
score = 10
parsers = [ "result-status", "result-detail" ]
result-status.score = 1
result-status.forcequit = false
parsers = ["diff", "dummy", "result-detail"]
dummy.comment = "\n\n### Details\n"
result-detail.exitstatus = true
result-detail.stderr = true
case4.score = 15
case4.limit.cpu = 30
case4.limit.mem = 10
case4.limit.stdout = 8
case5.score = 25
case8.limit.stderr = 128
[[stages]]
name = "[run] address sanitizer"
group = "run"
command="./asan -a"
files.import = [ "build/asan" ]
name = "judge-msan"
command="./driver ./mumsh-msan"
limit.cpu = 10 # default cpu limit (in sec) for each test case
limit.mem = 500 # set default mem limit (in MB) for all OJ test cases
score = 10
skip = ["case0", "case11"]
parsers = [ "result-status", "result-detail" ]
result-status.score = 1
result-status.forcequit = false
parsers = ["diff", "dummy", "result-detail"]
dummy.comment = "\n\n### Details\n"
result-detail.exitstatus = true
result-detail.stderr = true
[[stages]]
name = "[run] memory sanitizer"
group = "run"
command="./msan -a"
files.import = [ "build/msan" ]
case4.score = 15
case4.limit.cpu = 30
case4.limit.mem = 10
parsers = [ "result-status", "result-detail" ]
result-status.score = 1
result-status.forcequit = false
result-detail.exitstatus = true
result-detail.stderr = true
case5.diff.output.ignorespaces = false
[[stages]]
name = "[run] undefined behavior sanitizer"
command="./ubsan -a"
files.import = [ "build/ubsan" ]
parsers = [ "result-status", "result-detail" ]
result-status.score = 1
result-status.forcequit = false
result-detail.exitstatus = true
result-detail.stderr = true
case6.diff.output.hide = true