From f26a6e8f9019d2e1517fa854652248f34059a00a Mon Sep 17 00:00:00 2001 From: Mack Wang Date: Tue, 7 Apr 2026 16:14:07 -0700 Subject: [PATCH 1/6] feat: add whitelist character support in repo toml This commit introduces a new key, `health-check.whitelisted-chars` for repo.toml. It allows TAs to configure repo-wide allowed non ASCII chars for the repo-health-checker binary. It results in a new command line switch, `-whitelisted-chars=X,Y,Z`, in the generated task.json. --- joj3_config_generator/models/repo.py | 17 ++ joj3_config_generator/transformers/repo.py | 15 +- tests/convert/test_convert_cases.py | 4 + tests/convert/whitelisted-chars/repo.toml | 1 + tests/convert/whitelisted-chars/task.json | 188 +++++++++++++++++++++ tests/convert/whitelisted-chars/task.toml | 2 + 6 files changed, 224 insertions(+), 3 deletions(-) create mode 100644 tests/convert/whitelisted-chars/repo.toml create mode 100644 tests/convert/whitelisted-chars/task.json create mode 100644 tests/convert/whitelisted-chars/task.toml diff --git a/joj3_config_generator/models/repo.py b/joj3_config_generator/models/repo.py index d97da28..87734a9 100644 --- a/joj3_config_generator/models/repo.py +++ b/joj3_config_generator/models/repo.py @@ -50,6 +50,9 @@ class HealthCheck(StrictBaseModel): required_files: List[str] = Field( [], validation_alias=AliasChoices("required-files", "required_files") ) + whitelisted_chars: List[str] = Field( + [], validation_alias=AliasChoices("whitelisted-chars", "whitelisted_chars") + ) @field_validator("max_size", mode="before") @classmethod @@ -58,6 +61,20 @@ class HealthCheck(StrictBaseModel): return Memory(v) raise ValueError(f'Must be a string, e.g., "256m" or "1g", but got {v}') + @field_validator("whitelisted_chars") + @classmethod + def ensure_non_ascii_single_char_list(cls, chars: List[str]) -> List[str]: + for c in chars: + if len(c) != 1: + raise ValueError( + "Each whitelisted character must be exactly one character" + ) + if c.isascii(): + raise ValueError( + "Each whitelisted character must be a non-ASCII character" + ) + return chars + class Config(StrictBaseModel): root: Path = Field(Path("."), exclude=True) diff --git a/joj3_config_generator/transformers/repo.py b/joj3_config_generator/transformers/repo.py index 7e50439..5eced13 100644 --- a/joj3_config_generator/transformers/repo.py +++ b/joj3_config_generator/transformers/repo.py @@ -123,14 +123,23 @@ def get_check_lists(repo_conf: repo.Config) -> Tuple[List[str], List[str]]: def get_health_check_args(repo_conf: repo.Config) -> List[str]: file_sums, file_paths = get_check_lists(repo_conf) - return [ + args = [ "/usr/local/bin/repo-health-checker", "-root=.", f"-repoSize={str(repo_conf.health_check.max_size / 1024 / 1024)}", # B -> MB *[f"-meta={meta}" for meta in repo_conf.health_check.required_files], - f"-checkFileSumList={','.join(file_sums)}", - f"-checkFileNameList={','.join(file_paths)}", ] + if repo_conf.health_check.whitelisted_chars: + args.append( + f"-whitelisted-chars={','.join(repo_conf.health_check.whitelisted_chars)}" + ) + args.extend( + [ + f"-checkFileSumList={','.join(file_sums)}", + f"-checkFileNameList={','.join(file_paths)}", + ] + ) + return args def get_teapot_check_args(repo_conf: repo.Config, task_conf: task.Config) -> List[str]: diff --git a/tests/convert/test_convert_cases.py b/tests/convert/test_convert_cases.py index 284043a..2beea68 100644 --- a/tests/convert/test_convert_cases.py +++ b/tests/convert/test_convert_cases.py @@ -51,3 +51,7 @@ def test_result_detail() -> None: def test_unnecessary() -> None: load_case("unnecessary") + + +def test_whitelisted_chars() -> None: + load_case("whitelisted-chars") diff --git a/tests/convert/whitelisted-chars/repo.toml b/tests/convert/whitelisted-chars/repo.toml new file mode 100644 index 0000000..b62e42b --- /dev/null +++ b/tests/convert/whitelisted-chars/repo.toml @@ -0,0 +1 @@ +health-check.whitelisted-chars = ["你", "好", "!"] diff --git a/tests/convert/whitelisted-chars/task.json b/tests/convert/whitelisted-chars/task.json new file mode 100644 index 0000000..21b4e90 --- /dev/null +++ b/tests/convert/whitelisted-chars/task.json @@ -0,0 +1,188 @@ +{ + "name": "health check", + "logPath": "/home/tt/.cache/joj3/health/joj3.log", + "actorCsvPath": "/home/tt/.config/joj/students.csv", + "sandboxExecServer": "172.17.0.1:5051", + "sandboxToken": "", + "outputPath": "/tmp/joj3_result.json", + "preStages": [], + "stages": [ + { + "name": "Health Check", + "groups": [], + "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", + "-whitelisted-chars=你,好,!", + "-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": {} + } + ] + } + ], + "postStages": [ + { + "name": "teapot", + "groups": [], + "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": { + "filename": "stdout", + "msg": "joj3 summary", + "level": -4 + } + }, + { + "name": "log", + "with": { + "filename": "stderr", + "msg": "joint-teapot stderr", + "level": 0 + } + }, + { + "name": "debug", + "with": {} + } + ] + } + ] +} diff --git a/tests/convert/whitelisted-chars/task.toml b/tests/convert/whitelisted-chars/task.toml new file mode 100644 index 0000000..e42a6ce --- /dev/null +++ b/tests/convert/whitelisted-chars/task.toml @@ -0,0 +1,2 @@ +name = "health check" +max-total-score = 0 -- 2.30.2 From a59373a97237d202991d93364eb51cbaf40ecb4a Mon Sep 17 00:00:00 2001 From: Mack Wang Date: Tue, 7 Apr 2026 16:30:49 -0700 Subject: [PATCH 2/6] fix: case mangled in whitelist-char tests --- tests/convert/whitelisted-chars/task.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/convert/whitelisted-chars/task.json b/tests/convert/whitelisted-chars/task.json index 21b4e90..60b3c64 100644 --- a/tests/convert/whitelisted-chars/task.json +++ b/tests/convert/whitelisted-chars/task.json @@ -109,7 +109,7 @@ "joj3-all-env", "/home/tt/.config/teapot/teapot.env", "--grading-repo-name", - "joj3-config-generator", + "JOJ3-config-generator", "--max-total-score", "0", "--issue-label-name", -- 2.30.2 From 926f786d8348397244cf7d532580903b21ad8f0e Mon Sep 17 00:00:00 2001 From: Mack Wang Date: Tue, 7 Apr 2026 16:37:23 -0700 Subject: [PATCH 3/6] fix: case mangled in whitelist-char tests --- tests/convert/whitelisted-chars/task.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/convert/whitelisted-chars/task.json b/tests/convert/whitelisted-chars/task.json index 60b3c64..a137647 100644 --- a/tests/convert/whitelisted-chars/task.json +++ b/tests/convert/whitelisted-chars/task.json @@ -70,7 +70,7 @@ "joj3-check-env", "/home/tt/.config/teapot/teapot.env", "--grading-repo-name", - "joj3-config-generator", + "JOJ3-config-generator", "--scoreboard-filename", "scoreboard.csv" ], -- 2.30.2 From 51392cc1564ebd1f946ba1cff4ee090d3ecf26f4 Mon Sep 17 00:00:00 2001 From: Mack Wang Date: Tue, 7 Apr 2026 16:53:37 -0700 Subject: [PATCH 4/6] fix: use camelCase for the cmdline switch generated --- joj3_config_generator/transformers/repo.py | 2 +- tests/convert/whitelisted-chars/task.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/joj3_config_generator/transformers/repo.py b/joj3_config_generator/transformers/repo.py index 5eced13..d5749fc 100644 --- a/joj3_config_generator/transformers/repo.py +++ b/joj3_config_generator/transformers/repo.py @@ -131,7 +131,7 @@ def get_health_check_args(repo_conf: repo.Config) -> List[str]: ] if repo_conf.health_check.whitelisted_chars: args.append( - f"-whitelisted-chars={','.join(repo_conf.health_check.whitelisted_chars)}" + f"-whitelistedChars={','.join(repo_conf.health_check.whitelisted_chars)}" ) args.extend( [ diff --git a/tests/convert/whitelisted-chars/task.json b/tests/convert/whitelisted-chars/task.json index a137647..97ad5a0 100644 --- a/tests/convert/whitelisted-chars/task.json +++ b/tests/convert/whitelisted-chars/task.json @@ -59,7 +59,7 @@ "/usr/local/bin/repo-health-checker", "-root=.", "-repoSize=10.0", - "-whitelisted-chars=你,好,!", + "-whitelistedChars=你,好,!", "-checkFileSumList=", "-checkFileNameList=" ] -- 2.30.2 From 175eeb045ef1fbc196064250aa7384d3bd477860 Mon Sep 17 00:00:00 2001 From: Mack Wang Date: Tue, 21 Apr 2026 18:51:10 -0700 Subject: [PATCH 5/6] feat: use TOML string instead of array for non-ASCII chars --- joj3_config_generator/models/repo.py | 10 +++------- joj3_config_generator/transformers/repo.py | 2 +- tests/convert/whitelisted-chars/repo.toml | 2 +- 3 files changed, 5 insertions(+), 9 deletions(-) diff --git a/joj3_config_generator/models/repo.py b/joj3_config_generator/models/repo.py index 87734a9..18512dd 100644 --- a/joj3_config_generator/models/repo.py +++ b/joj3_config_generator/models/repo.py @@ -50,8 +50,8 @@ class HealthCheck(StrictBaseModel): required_files: List[str] = Field( [], validation_alias=AliasChoices("required-files", "required_files") ) - whitelisted_chars: List[str] = Field( - [], validation_alias=AliasChoices("whitelisted-chars", "whitelisted_chars") + whitelisted_chars: str = Field( + "", validation_alias=AliasChoices("whitelisted-chars", "whitelisted_chars") ) @field_validator("max_size", mode="before") @@ -63,12 +63,8 @@ class HealthCheck(StrictBaseModel): @field_validator("whitelisted_chars") @classmethod - def ensure_non_ascii_single_char_list(cls, chars: List[str]) -> List[str]: + def ensure_non_ascii_chars(cls, chars: str) -> str: for c in chars: - if len(c) != 1: - raise ValueError( - "Each whitelisted character must be exactly one character" - ) if c.isascii(): raise ValueError( "Each whitelisted character must be a non-ASCII character" diff --git a/joj3_config_generator/transformers/repo.py b/joj3_config_generator/transformers/repo.py index d5749fc..82f0f93 100644 --- a/joj3_config_generator/transformers/repo.py +++ b/joj3_config_generator/transformers/repo.py @@ -131,7 +131,7 @@ def get_health_check_args(repo_conf: repo.Config) -> List[str]: ] if repo_conf.health_check.whitelisted_chars: args.append( - f"-whitelistedChars={','.join(repo_conf.health_check.whitelisted_chars)}" + f"-whitelistedChars={','.join(list(repo_conf.health_check.whitelisted_chars))}" ) args.extend( [ diff --git a/tests/convert/whitelisted-chars/repo.toml b/tests/convert/whitelisted-chars/repo.toml index b62e42b..bd66ab0 100644 --- a/tests/convert/whitelisted-chars/repo.toml +++ b/tests/convert/whitelisted-chars/repo.toml @@ -1 +1 @@ -health-check.whitelisted-chars = ["你", "好", "!"] +health-check.whitelisted-chars = "你好!" -- 2.30.2 From a12f7f3b19b2d500640450b71ec54701fb5f2e70 Mon Sep 17 00:00:00 2001 From: Mack Wang Date: Tue, 21 Apr 2026 18:51:45 -0700 Subject: [PATCH 6/6] test: add health-check.whitelisted_chars to convert/full --- tests/convert/full/repo.toml | 1 + tests/convert/full/task.json | 1 + 2 files changed, 2 insertions(+) diff --git a/tests/convert/full/repo.toml b/tests/convert/full/repo.toml index 4c3b171..ab4edcc 100644 --- a/tests/convert/full/repo.toml +++ b/tests/convert/full/repo.toml @@ -2,6 +2,7 @@ sandbox-token = "" # sandbox token health-check.score = 0 # score for health check stage health-check.max-size = "10m" # max size of the repository +health-check.whitelisted-chars = "あいうえお" # allowed non-ASCII characters in healthcheck stage health-check.immutable-path = "immutable" # path for immutable files, relative to the path of repo.toml health-check.required-files = ["README.md", "Changelog.md"] # required files name, case insensitive diff --git a/tests/convert/full/task.json b/tests/convert/full/task.json index d1c3c17..3eeea9e 100644 --- a/tests/convert/full/task.json +++ b/tests/convert/full/task.json @@ -61,6 +61,7 @@ "-repoSize=10.0", "-meta=README.md", "-meta=Changelog.md", + "-whitelistedChars=あ,い,う,え,お", "-checkFileSumList=b1bbad25b830db0a77b15a033f9ca1b7ab44c1d2d05056412bd3e4421645f0bf,2ba059f3977e2e3dee6cacbfbf0ba2578baa1b8e04b4977aec400868b6e49856,3db23f7fb2ca9814617e767ddc41b77073180b3b0b73e87b5f2a6d3129f88f3a,a5b63323a692d3d8b952442969649b4f823d58dae26429494f613df160710dfc", "-checkFileNameList=.gitattributes,.gitea/workflows/push.yaml,.gitea/workflows/release.yaml,.gitignore" ] -- 2.30.2