Compare commits

...

94 Commits

Author SHA1 Message Date
06ce9ad92b fix: remove pointer in elf Case
Some checks failed
build / build (pull_request) Failing after 43s
build / trigger-build-image (pull_request) Has been skipped
build / build (push) Failing after 45s
build / trigger-build-image (push) Has been skipped
2024-10-04 17:15:31 +08:00
d5454a1abd feat: elf parser
only parse json output to struct
2024-10-04 17:15:31 +08:00
4a56cd81aa
docs: update for NonAsciiMsg
All checks were successful
build / build (push) Successful in 1m17s
build / trigger-build-image (push) Successful in 5s
2024-10-04 02:11:21 -04:00
dee296143f
feat: non-ascii not in the first line of commit msg
All checks were successful
build / build (push) Successful in 1m20s
build / trigger-build-image (push) Successful in 5s
2024-10-04 02:06:32 -04:00
5c2fe4ffcb
feat: hint valid scopes
All checks were successful
build / build (push) Successful in 1m17s
build / trigger-build-image (push) Successful in 5s
2024-10-03 05:20:34 -04:00
64ae632003
feat: list group keywords
All checks were successful
build / build (push) Successful in 1m15s
build / trigger-build-image (push) Successful in 5s
2024-10-02 19:24:30 -04:00
f5ba04b8e7
feat: more logs
All checks were successful
build / build (push) Successful in 1m17s
build / trigger-build-image (push) Successful in 5s
2024-10-02 16:45:54 -04:00
d723d98de0
feat: parse conventional commits
All checks were successful
build / build (push) Successful in 1m14s
build / trigger-build-image (push) Successful in 6s
2024-10-02 08:21:24 -04:00
7cc6747a25
ci: update golang to 1.23.2
All checks were successful
build / build (push) Successful in 1m26s
build / trigger-build-image (push) Successful in 5s
2024-10-02 04:44:24 -04:00
5e4786c3b5
feat: log debug log path
All checks were successful
build / build (push) Successful in 1m14s
build / trigger-build-image (push) Successful in 7s
2024-10-01 16:52:18 -04:00
4666eb7375
feat: more debug log
All checks were successful
build / build (push) Successful in 1m13s
build / trigger-build-image (push) Successful in 6s
2024-10-01 16:26:37 -04:00
02d0f49070
feat: more logs
All checks were successful
build / build (push) Successful in 1m13s
build / trigger-build-image (push) Successful in 5s
2024-10-01 16:07:11 -04:00
049a5e6e7e feat: debug log to file & info log to stderr (#43) (#45)
All checks were successful
build / build (push) Successful in 1m8s
build / trigger-build-image (push) Successful in 5s
Reviewed-on: #45
Co-authored-by: Boming Zhang <bomingzh@sjtu.edu.cn>
Co-committed-by: Boming Zhang <bomingzh@sjtu.edu.cn>
2024-10-02 03:57:31 +08:00
28bf70d0a9 fix: release check (#44)
All checks were successful
build / build (push) Successful in 1m17s
build / trigger-build-image (push) Successful in 5s
Co-authored-by: zzjc1234 <2359047351@qq.com>
Reviewed-on: #44
Reviewed-by: 张泊明518370910136 <bomingzh@sjtu.edu.cn>
Co-authored-by: 周赵嘉程521432910016 <zzjc123@sjtu.edu.cn>
Co-committed-by: 周赵嘉程521432910016 <zzjc123@sjtu.edu.cn>
2024-10-01 23:30:07 +08:00
914ad6c7e3
fix: add comment on wrong result status
All checks were successful
build / build (push) Successful in 1m17s
build / trigger-build-image (push) Successful in 5s
2024-10-01 05:04:07 -04:00
18b7193bc0
feat: non null slice on parser result
All checks were successful
build / build (push) Successful in 1m26s
build / trigger-build-image (push) Successful in 6s
2024-10-01 04:16:11 -04:00
2029cc8ae9
feat: log meta conf
All checks were successful
build / build (push) Successful in 1m19s
build / trigger-build-image (push) Successful in 5s
2024-10-01 01:42:44 -04:00
6456a64bca
feat: log on meta conf not found 2024-10-01 01:41:44 -04:00
d8ed5809cb
feat: limit meta conf loaders
All checks were successful
build / build (push) Successful in 1m12s
build / trigger-build-image (push) Successful in 6s
2024-10-01 01:36:30 -04:00
79446e7826
fix: remove debug info
All checks were successful
build / build (push) Successful in 1m23s
build / trigger-build-image (push) Successful in 6s
2024-10-01 01:33:53 -04:00
0826a3d14a
feat: use -version for version
All checks were successful
build / build (push) Successful in 1m16s
build / trigger-build-image (push) Successful in 6s
2024-10-01 01:15:31 -04:00
ff8f700506
chore: shorter pre-commit
All checks were successful
build / build (push) Successful in 1m22s
build / trigger-build-image (push) Successful in 5s
2024-10-01 01:06:13 -04:00
198d329c8e
feat: show version
All checks were successful
build / build (push) Successful in 1m17s
build / trigger-build-image (push) Successful in 5s
2024-10-01 00:52:19 -04:00
43a799e958
fix: pre-commit hook 2024-10-01 00:51:49 -04:00
fa2d98007e
chore: unify YAML file extension
All checks were successful
build / build (push) Successful in 1m19s
build / trigger-build-image (push) Successful in 5s
2024-09-30 23:48:01 -04:00
fc68f7bcb4
ci: build image rely on build 2024-09-30 23:47:15 -04:00
5038cf118d
fix: deprecated grpc.Dial
All checks were successful
build-image / create-empty-commit (push) Successful in 5s
checks / build (push) Successful in 1m14s
2024-09-30 23:38:42 -04:00
0471834bca
chore: update deps
Some checks failed
build-image / create-empty-commit (push) Successful in 5s
checks / build (push) Failing after 49s
2024-09-30 23:36:08 -04:00
a16418f3a6
chore: update deps
Some checks failed
build-image / create-empty-commit (push) Successful in 5s
checks / build (push) Failing after 51s
2024-09-30 23:33:30 -04:00
16cbf4c1ef
docs: go report badge
All checks were successful
build-image / create-empty-commit (push) Successful in 5s
checks / build (push) Successful in 1m30s
2024-09-30 22:27:10 -04:00
97556cf220
docs: add MIT license
All checks were successful
build-image / create-empty-commit (push) Successful in 5s
checks / build (push) Successful in 1m20s
2024-09-30 22:23:04 -04:00
b40072a485
feat: rename module
All checks were successful
build-image / create-empty-commit (push) Successful in 5s
checks / build (push) Successful in 1m19s
2024-09-30 22:04:18 -04:00
2a55fe1c3d
docs: add test badge
All checks were successful
build-image / create-empty-commit (push) Successful in 5s
checks / build (push) Successful in 1m21s
2024-09-30 18:21:58 -04:00
2b5769545d
fix: url typo
All checks were successful
build-image / create-empty-commit (push) Successful in 6s
checks / build (push) Successful in 1m21s
2024-09-30 17:45:14 -04:00
a9a829da3f
feat: rename module
Some checks failed
build-image / create-empty-commit (push) Failing after 4s
checks / build (push) Successful in 1m20s
2024-09-30 17:38:40 -04:00
4c00904f25
feat: debug log executor result files
All checks were successful
build-image / create-empty-commit (push) Successful in 5s
checks / build (push) Successful in 1m14s
2024-09-30 01:51:55 -04:00
e2bf6d3cf1
fix: diff parser skip case on result status error
Some checks failed
build-image / create-empty-commit (push) Successful in 6s
checks / build (push) Failing after 18s
2024-09-30 01:30:30 -04:00
dd8dc68eeb
feat: check result status in diff parser
All checks were successful
build-image / create-empty-commit (push) Successful in 6s
checks / build (push) Successful in 1m17s
2024-09-30 01:04:02 -04:00
c13b3465c1
chore: update pre-commit hooks
All checks were successful
build-image / create-empty-commit (push) Successful in 6s
checks / build (push) Successful in 1m14s
2024-09-30 00:58:34 -04:00
a5a4a649dc
feat: debug log diff
All checks were successful
build-image / create-empty-commit (push) Successful in 5s
checks / build (push) Successful in 1m21s
2024-09-30 00:49:39 -04:00
3682a36fde
feat: decode conf error msg 2024-09-30 00:49:07 -04:00
7b3edb2361
revert: "style: unify error msg"
This reverts commit 6c8332af29.
2024-09-30 00:49:06 -04:00
6c8332af29
style: unify error msg
Some checks failed
build-image / create-empty-commit (push) Successful in 5s
checks / build (push) Failing after 1m17s
2024-09-29 18:22:18 -04:00
7ad3a3ab1f
feat: accept all formats of meta-conf
All checks were successful
build-image / create-empty-commit (push) Successful in 6s
checks / build (push) Successful in 1m25s
2024-09-29 06:20:03 -04:00
03065b0b69
docs: joj3 cli
All checks were successful
build-image / create-empty-commit (push) Successful in 6s
checks / build (push) Successful in 1m12s
2024-09-26 07:58:41 -04:00
9800becc96
feat: more info logs 2024-09-26 05:43:37 -04:00
f665e5b2b1
feat: cli arg to specify msg 2024-09-26 05:38:42 -04:00
7ce4d29d0e
docs: meta-conf
All checks were successful
build-image / create-empty-commit (push) Successful in 6s
checks / build (push) Successful in 1m22s
2024-09-26 03:45:59 -04:00
6af7be6cb1
docs: skip teapot steps with metadata
All checks were successful
build-image / create-empty-commit (push) Successful in 5s
checks / build (push) Successful in 1m14s
2024-09-26 03:38:22 -04:00
863e562743
chore: pass neccessary conf only 2024-09-26 02:30:03 -04:00
4b4c83a609
ci: remove checkout
All checks were successful
build-image / create-empty-commit (push) Successful in 5s
checks / build (push) Successful in 1m11s
2024-09-26 02:20:34 -04:00
5893b54054
ci: trigger runner-images gitea actions
Some checks failed
build-image / create-empty-commit (push) Failing after 9s
checks / build (push) Successful in 1m15s
2024-09-26 02:18:57 -04:00
1bb0ddd253
feat: meta conf flag
All checks were successful
checks / build (push) Successful in 1m21s
2024-09-26 01:06:51 -04:00
3a8e3dfcf4
fix: CopyInDir option
All checks were successful
checks / build (push) Successful in 1m22s
2024-09-25 04:10:11 -04:00
d172fae912
feat: example meta conf
All checks were successful
checks / build (push) Successful in 1m16s
2024-09-25 03:34:33 -04:00
8992d9df7a
docs: update usage of make prepare-test
All checks were successful
checks / build (push) Successful in 1m13s
2024-09-22 01:37:10 -04:00
a66dbc29e4
feat: use CopyInDir instead of CopyInCwd 2024-09-22 01:36:07 -04:00
b7a1ad0833
fix: typo
All checks were successful
checks / build (push) Successful in 1m13s
2024-09-21 21:12:06 -04:00
5cf3d5f9ce
feat: less output in scripts 2024-09-21 21:10:03 -04:00
9765ea1007
fix: typo 2024-09-21 21:10:03 -04:00
5083b02a6f
docs: binary & executor & parser 2024-09-21 21:10:03 -04:00
9e5c933fbd
fix: check only latest commit for non-ascii chars (#38)
according to [this](https://focs.ji.sjtu.edu.cn/mm/focs-team/pl/nsfhoutszfds5c9wpfrmfxb51c) discussion in *joj-dev*

Reviewed-on: FOCS-dev/JOJ3#38
Co-authored-by: Mack Wang <mac-wang@outlook.com>
Co-committed-by: Mack Wang <mac-wang@outlook.com>
2024-09-21 21:09:56 -04:00
61dada5c40
ci: use gitea actions instead of drone (#40)
Reviewed-on: FOCS-dev/JOJ3#40
2024-09-21 21:09:39 -04:00
2291264df0
fix: remove typo log 2024-09-21 21:09:39 -04:00
d597c73dd2
feat: use tag & json loader only 2024-09-21 21:09:39 -04:00
1253404c49
feat: use conf.toml -> conf.json 2024-09-21 21:09:39 -04:00
d03927457a
feat: score & comment conf in result-status and dummy parser 2024-09-21 21:09:39 -04:00
d5a96515ec
chore: rename parser secret -> dummy 2024-09-21 21:09:39 -04:00
3cc96821ad
chore: remove set -x in scripts 2024-09-12 18:48:02 -04:00
5922d795b3
fix: log typo 2024-09-12 18:47:27 -04:00
967a859c9f
feat: quit on more errors 2024-09-12 04:36:57 -04:00
3ccdd394b2
fix: add sanity check back to verify files 2024-09-11 19:07:49 -04:00
5a860f1203 feat: repo health check (#16) (#17)
- repo size
- forbidden files
- meta files
- ascii character in files
- integrity check
- ascii character in the commit message
- release tag check

Co-authored-by: Boming Zhang <bomingzh@sjtu.edu.cn>
Co-authored-by: zzjc1234 <2359047351@qq.com>
Co-authored-by: Hydraallen <wangruiallen@gmail.com>
Reviewed-on: FOCS-dev/JOJ3#17
Co-authored-by: 周赵嘉程521432910016 <zzjc123@sjtu.edu.cn>
Co-committed-by: 周赵嘉程521432910016 <zzjc123@sjtu.edu.cn>
2024-09-11 20:09:27 +08:00
52491478a4
feat: diff parser with multiple outputs in one case 2024-09-11 07:06:11 -04:00
eac7a62ebe
chore: add source 2024-09-11 06:07:07 -04:00
zzjc1234
2c742852fb
chore: update submodules 2024-09-11 05:12:08 -04:00
776de48d03
chore: git submodule files 2024-09-11 05:12:08 -04:00
zzjc1234
fdf84546da
chore: gitmodules 2024-09-11 05:12:08 -04:00
zzjc1234
1d90d72978
feat: myers 2024-09-11 05:12:08 -04:00
zzjc1234
6fbbe7ece0
feat: option ignoring whitespace 2024-09-11 05:12:08 -04:00
zzjc1234
96c71bcd63
feat: option ignoring whitespace 2024-09-11 05:12:08 -04:00
zzjc1234
756a531bac
feat: diff blocks 2024-09-11 05:12:08 -04:00
zzjc1234
fd69aebac5
feat: diff 2024-09-11 05:12:08 -04:00
9f0e9bf9f3
feat: simple secret parser 2024-09-10 22:23:12 -04:00
2928e6321f
chore: rename dummy -> sample 2024-09-10 22:03:25 -04:00
f81581b928 revert: 27a2aac537
All checks were successful
continuous-integration/drone/push Build is passing
revert feat: return 1 on force quit
2024-07-19 19:38:17 +08:00
27a2aac537
feat: return 1 on force quit
Some checks failed
continuous-integration/drone/push Build is failing
2024-07-19 07:12:18 -04:00
e4091f9ba4
chore: debug log file content in keyword parser
All checks were successful
continuous-integration/drone/push Build is passing
2024-07-19 06:28:23 -04:00
739069a18f
feat: convert local file to memory file
All checks were successful
continuous-integration/drone/push Build is passing
2024-06-30 08:50:36 -04:00
97f89d210b
chore: update for deployment
All checks were successful
continuous-integration/drone/push Build is passing
2024-06-30 07:39:28 -04:00
b608a22cfd
feat: read sandbox conf
All checks were successful
continuous-integration/drone/push Build is passing
2024-06-30 02:42:51 -04:00
2f8604f647
chore: rename test names
All checks were successful
continuous-integration/drone/push Build is passing
2024-06-20 05:07:10 -04:00
c5d31d9a1a
chore: update submodule
All checks were successful
continuous-integration/drone/push Build is passing
2024-06-20 04:20:14 -04:00
9445f93e44
chore: run foreach test repos script 2024-06-20 04:18:41 -04:00
83 changed files with 2054 additions and 350 deletions

View File

@ -1,31 +0,0 @@
---
kind: pipeline
type: ssh
name: CI
server:
host: 111.186.59.59
user: drone
ssh_key:
from_secret: joj3-test
steps:
- name: prepare
commands:
- go env -w GOPROXY=https://goproxy.cn,direct
- whoami
- pwd
- env
- go version
- go env
- git status -v
- git log -1
- name: build
commands:
- make
- name: test
commands:
- make prepare-test
- make test
- name: store
commands:
- cp build/joj3 /home/drone/.local/bin/joj3

View File

@ -0,0 +1,65 @@
---
name: build
on:
- push
- pull_request
jobs:
build:
container:
image: focs.ji.sjtu.edu.cn:5000/gitea/runner-images:ubuntu-latest
volumes:
- /home/actions/.ssh:/root/.ssh
steps:
- name: Check out repository code
uses: https://gitea.com/BoYanZh/checkout@focs
- name: Setup Go 1.23.1
run: |
wget -q https://studygolang.com/dl/golang/go1.23.2.linux-amd64.tar.gz
rm -rf /usr/local/go
tar -C /usr/local -xzf go1.23.2.linux-amd64.tar.gz
rm -rf go1.23.2.linux-amd64.tar.gz
echo "PATH=$PATH:/usr/local/go/bin:/root/go/bin" >> $GITHUB_ENV
- name: Display Go version
run: go version
- name: Prepare
run: |
go env -w GO111MODULE=on
go env -w GOPROXY=https://goproxy.io,direct
chown -R root:root /root/.ssh
- name: Setup golangci-lint
run: |
wget -q https://ghp.ci/https://github.com/golangci/golangci-lint/releases/download/v1.61.0/golangci-lint-1.61.0-linux-amd64.tar.gz
tar -C /tmp -xzf golangci-lint-1.61.0-linux-amd64.tar.gz
rm -rf golangci-lint-1.61.0-linux-amd64.tar.gz
mkdir -p /root/go/bin
mv /tmp/golangci-lint-1.61.0-linux-amd64/golangci-lint /root/go/bin
- name: Lint
run: make lint
- name: Build
run: make build
- name: Version
run: ./build/joj3 -version
- name: Test
run: |
make prepare-test
make ci-test
trigger-build-image:
container:
image: focs.ji.sjtu.edu.cn:5000/gitea/runner-images:ubuntu-latest
volumes:
- /home/actions/.ssh:/root/.ssh
needs: build
if: github.ref == 'refs/heads/master'
steps:
- name: Set up Git
run: |
git config --global user.name "gitea-actions[bot]"
git config --global user.email "gitea-actions[bot]@users.noreply.gitea.com"
chown -R root:root /root/.ssh
- name: Clone, Commit and Push
run: |
git clone ssh://git@focs.ji.sjtu.edu.cn:2222/JOJ/runner-images.git runner-images
cd runner-images
git commit --allow-empty -m "chore: trigger gitea actions by JOJ3"
git push

66
.gitmodules vendored
View File

@ -1,36 +1,72 @@
[submodule "examples/diff/basic"]
path = examples/diff/basic
url = ssh://git@focs.ji.sjtu.edu.cn:2222/JOJ/JOJ3-examples.git
branch = diff/basic
[submodule "examples/diff/complex"]
path = examples/diff/complex
url = ssh://git@focs.ji.sjtu.edu.cn:2222/JOJ/JOJ3-examples.git
branch = diff/complex
[submodule "examples/cpplint/sillycode"]
path = examples/cpplint/sillycode
url = ssh://git@focs.ji.sjtu.edu.cn:2222/FOCS-dev/JOJ3-examples.git
url = ssh://git@focs.ji.sjtu.edu.cn:2222/JOJ/JOJ3-examples.git
branch = cpplint/sillycode
[submodule "examples/compile/success"]
path = examples/compile/success
url = ssh://git@focs.ji.sjtu.edu.cn:2222/FOCS-dev/JOJ3-examples.git
url = ssh://git@focs.ji.sjtu.edu.cn:2222/JOJ/JOJ3-examples.git
branch = compile/success
[submodule "examples/compile/error"]
path = examples/compile/error
url = ssh://git@focs.ji.sjtu.edu.cn:2222/FOCS-dev/JOJ3-examples.git
url = ssh://git@focs.ji.sjtu.edu.cn:2222/JOJ/JOJ3-examples.git
branch = compile/error
[submodule "examples/dummy/success"]
path = examples/dummy/success
url = ssh://git@focs.ji.sjtu.edu.cn:2222/FOCS-dev/JOJ3-examples.git
branch = dummy/success
[submodule "examples/dummy/error"]
path = examples/dummy/error
url = ssh://git@focs.ji.sjtu.edu.cn:2222/FOCS-dev/JOJ3-examples.git
branch = dummy/error
[submodule "examples/sample/success"]
path = examples/sample/success
url = ssh://git@focs.ji.sjtu.edu.cn:2222/JOJ/JOJ3-examples.git
branch = sample/success
[submodule "examples/sample/error"]
path = examples/sample/error
url = ssh://git@focs.ji.sjtu.edu.cn:2222/JOJ/JOJ3-examples.git
branch = sample/error
[submodule "examples/keyword/cpplint/sillycode"]
path = examples/keyword/cpplint/sillycode
url = ssh://git@focs.ji.sjtu.edu.cn:2222/FOCS-dev/JOJ3-examples.git
url = ssh://git@focs.ji.sjtu.edu.cn:2222/JOJ/JOJ3-examples.git
branch = keyword/cpplint/sillycode
[submodule "examples/clangtidy/sillycode"]
path = examples/clangtidy/sillycode
url = ssh://git@focs.ji.sjtu.edu.cn:2222/FOCS-dev/JOJ3-examples.git
url = ssh://git@focs.ji.sjtu.edu.cn:2222/JOJ/JOJ3-examples.git
branch = clangtidy/sillycode
[submodule "examples/keyword/clangtidy/sillycode"]
path = examples/keyword/clangtidy/sillycode
url = ssh://git@focs.ji.sjtu.edu.cn:2222/FOCS-dev/JOJ3-examples.git
url = ssh://git@focs.ji.sjtu.edu.cn:2222/JOJ/JOJ3-examples.git
branch = keyword/clangtidy/sillycode
[submodule "examples/healthcheck/asciifile"]
path = examples/healthcheck/asciifile
url = ssh://git@focs.ji.sjtu.edu.cn:2222/JOJ/JOJ3-examples.git
branch = healthcheck/asciifile
[submodule "examples/healthcheck/asciimsg"]
path = examples/healthcheck/asciimsg
url = ssh://git@focs.ji.sjtu.edu.cn:2222/JOJ/JOJ3-examples.git
branch = healthcheck/asciimsg
[submodule "examples/healthcheck/forbiddenfile"]
path = examples/healthcheck/forbiddenfile
url = ssh://git@focs.ji.sjtu.edu.cn:2222/JOJ/JOJ3-examples.git
branch = healthcheck/forbiddenfile
[submodule "examples/healthcheck/meta"]
path = examples/healthcheck/meta
url = ssh://git@focs.ji.sjtu.edu.cn:2222/JOJ/JOJ3-examples.git
branch = healthcheck/meta
[submodule "examples/healthcheck/release"]
path = examples/healthcheck/release
url = ssh://git@focs.ji.sjtu.edu.cn:2222/JOJ/JOJ3-examples.git
branch = healthcheck/release
[submodule "examples/healthcheck/reposize"]
path = examples/healthcheck/reposize
url = ssh://git@focs.ji.sjtu.edu.cn:2222/JOJ/JOJ3-examples.git
branch = healthcheck/reposize
[submodule "examples/healthcheck/repoverify"]
path = examples/healthcheck/repoverify
url = ssh://git@focs.ji.sjtu.edu.cn:2222/JOJ/JOJ3-examples.git
branch = healthcheck/repoverify
[submodule "examples/cppcheck/sillycode"]
path = examples/cppcheck/sillycode
url = ssh://git@focs.ji.sjtu.edu.cn:2222/FOCS-dev/JOJ3-examples.git
url = ssh://git@focs.ji.sjtu.edu.cn:2222/JOJ/JOJ3-examples.git
branch = cppcheck/sillycode

View File

@ -1,24 +1,37 @@
---
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.5.0
rev: v4.6.0
hooks:
- id: check-yaml
- id: end-of-file-fixer
- id: trailing-whitespace
- repo: https://github.com/mrtazz/checkmake.git
rev: 0.2.2
hooks:
- id: checkmake
- repo: https://github.com/jumanjihouse/pre-commit-hook-yamlfmt
rev: 0.2.3
hooks:
- id: yamlfmt
- repo: https://github.com/Bahjat/pre-commit-golang
rev: v1.0.2
hooks:
- id: gofumpt
- repo: https://github.com/dnephin/pre-commit-golang
rev: v0.5.1
hooks:
- id: go-fmt
- id: go-imports
- id: golangci-lint
- id: go-unit-tests
- id: go-mod-tidy
- repo: local
hooks:
- id: make-build
name: make build
entry: make build
language: system
always_run: true
stages: [commit]
pass_filenames: false
- id: make-test
name: make test
entry: make test
language: system
always_run: true
stages: [commit]
pass_filenames: false

21
LICENSE Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2024 Joint Online Judge
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -1,9 +1,10 @@
.PHONY: all build clean prepare-test test
.PHONY: all build clean lint prepare-test test ci-test
BUILD_DIR = ./build
TMP_DIR = ./tmp
APPS := $(notdir $(wildcard ./cmd/*))
FLAGS := "-s -w"
VERSION := $(shell git rev-parse --short HEAD)
FLAGS := "-s -w -X main.Version=$(VERSION)"
all: build
@ -15,9 +16,17 @@ clean:
rm -rf $(TMP_DIR)/*
rm -rf *.out
lint:
golangci-lint run
prepare-test:
git submodule update --init --remote
test:
./scripts/prepare_test_repos.sh $(TMP_DIR)
go test -coverprofile cover.out -v ./...
ci-test:
./scripts/prepare_test_repos.sh $(TMP_DIR)
./scripts/run_foreach_test_repos.sh $(TMP_DIR) "sed -i '2i \ \ \"sandboxExecServer\": \"172.17.0.1:5051\",' conf.json"
go test -coverprofile cover.out -v ./...

120
README.md
View File

@ -1,5 +1,7 @@
# JOJ3
[![Go Report Card](https://goreportcard.com/badge/github.com/joint-online-judge/JOJ3)](https://goreportcard.com/report/github.com/joint-online-judge/JOJ3)
## Quick Start
1. Make sure you are in a Unix-like OS (Linux, MacOS). For Windows, use [WSL 2](https://learn.microsoft.com/en-us/windows/wsl/install).
@ -9,18 +11,20 @@
3. Enable cgroup v2 for your OS. Check [here](https://stackoverflow.com/a/73376219/13724598). So that you do not need root permission to run `go-judge`.
4. Clone [go-judge](https://github.com/criyle/go-judge).
```bash
$ git clone https://github.com/criyle/go-judge && cd go-judge
$ go build -o ./tmp/go-judge ./cmd/go-judge
```
5. Run `go-judge`.
```bash
$ # make sure you are in go-judge directory
$ ./tmp/go-judge -enable-grpc -enable-debug -enable-metrics
$ ./tmp/go-judge -http-addr 0.0.0.0:5050 -grpc-addr 0.0.0.0:5051 -monitor-addr 0.0.0.0:5052 -enable-grpc -enable-debug -enable-metrics
```
6. Pull submodules. It might be slow, so only run it when necessary.
6. Pull submodules. It might be slow, so only run it when the test branches are out of date.
```bash
$ # make sure you are in JOJ3 directory
@ -40,45 +44,18 @@ Note: you may fail the test if the checking tools are not installed. e.g. For th
```bash
$ make test
go test -coverprofile cover.out -v ./...
focs.ji.sjtu.edu.cn/git/FOCS-dev/JOJ3/cmd/dummy coverage: 0.0% of statements
? focs.ji.sjtu.edu.cn/git/FOCS-dev/JOJ3/internal/executors [no test files]
? focs.ji.sjtu.edu.cn/git/FOCS-dev/JOJ3/internal/parsers [no test files]
? focs.ji.sjtu.edu.cn/git/FOCS-dev/JOJ3/pkg/healthcheck [no test files]
focs.ji.sjtu.edu.cn/git/FOCS-dev/JOJ3/internal/executors/dummy coverage: 0.0% of statements
focs.ji.sjtu.edu.cn/git/FOCS-dev/JOJ3/internal/parsers/cpplint coverage: 0.0% of statements
focs.ji.sjtu.edu.cn/git/FOCS-dev/JOJ3/internal/parsers/diff coverage: 0.0% of statements
focs.ji.sjtu.edu.cn/git/FOCS-dev/JOJ3/internal/stage coverage: 0.0% of statements
focs.ji.sjtu.edu.cn/git/FOCS-dev/JOJ3/pkg/dummy coverage: 0.0% of statements
focs.ji.sjtu.edu.cn/git/FOCS-dev/JOJ3/internal/parsers/keyword coverage: 0.0% of statements
focs.ji.sjtu.edu.cn/git/FOCS-dev/JOJ3/internal/parsers/dummy coverage: 0.0% of statements
focs.ji.sjtu.edu.cn/git/FOCS-dev/JOJ3/internal/executors/sandbox coverage: 0.0% of statements
focs.ji.sjtu.edu.cn/git/FOCS-dev/JOJ3/internal/parsers/resultstatus coverage: 0.0% of statements
=== RUN TestMain
=== RUN TestMain/compile/error
=== RUN TestMain/compile/success
=== RUN TestMain/cpplint/sillycode
=== RUN TestMain/dummy/error
=== RUN TestMain/dummy/success
=== RUN TestMain/keyword/clang-tidy/sillycode
=== RUN TestMain/keyword/cpplint/sillycode
--- PASS: TestMain (2.28s)
--- PASS: TestMain/compile/error (0.03s)
--- PASS: TestMain/compile/success (0.42s)
--- PASS: TestMain/cpplint/sillycode (0.14s)
--- PASS: TestMain/dummy/error (0.01s)
--- PASS: TestMain/dummy/success (0.01s)
--- PASS: TestMain/keyword/clang-tidy/sillycode (1.57s)
--- PASS: TestMain/keyword/cpplint/sillycode (0.11s)
...
PASS
coverage: 74.0% of statements
ok focs.ji.sjtu.edu.cn/git/FOCS-dev/JOJ3/cmd/joj3 2.290s coverage: 74.0% of statements
ok github.com/joint-online-judge/JOJ3/cmd/joj3 2.290s coverage: 74.0% of statements
```
### For developers
1. Install [`pre-commit`](https://pre-commit.com/), [`golangci-lint`](https://golangci-lint.run), [`goimports`](https://golang.org/x/tools/cmd/goimports), [`gofumpt`](https://github.com/mvdan/gofumpt).
1. Install [`pre-commit`](https://pre-commit.com/), [`golangci-lint`](https://golangci-lint.run).
2. Install the pre-commit hooks. It will run some checks before you commit.
```bash
$ pre-commit install
pre-commit installed at .git/hooks/pre-commit
@ -100,7 +77,7 @@ Check `Cmd` at <https://github.com/criyle/go-judge#rest-api-interface>.
Some difference:
- `CopyInCwd bool`: set to `true` to add everything in the current working directory to `CopyIn`.
- `CopyInDir string`: set to non-empty string to add everything in that directory to `CopyIn`.
- `CopyInCached map[string]string`: key: file name in the sandbox, value: file name used in `CopyOutCached`.
- `LocalFile`: now supports the relative path
@ -112,3 +89,78 @@ Check the `Result` at <https://github.com/criyle/go-judge#rest-api-interface>.
- `Score int`: score of the stage.
- `Comment string`: comment on the stage.
## Binaries (under `/cmd` and `/pkg`)
### JOJ3
JOJ3 itself.
#### CLI arguments
1. `-meta-conf` to specify the meta configuration file path. JOJ3 will try to match the commit message by the `regex`, once matched, the configuration file in the `filename` will be load and run the whole process.
2. `-msg` to specify message to trigger the running. If empty, JOJ3 will use git commit message on HEAD.
### Sample
Just a sample on how to write an executable that can be called by the executor.
### HealthCheck
The repohealth check will return a json list to for check result. The structure follows the score-comment pattern.
HealthCheck currently includes, `reposize`, `forbidden file`, `Metafile existence`, `non-ascii character` in file and message, `release tag`, and `ci files invariance` check.
The workflow is `joj3` pass cli args to healthcheck binary. See `./cmd/healthcheck/main.go` to view all flags.
## Executors (under `/internal/executors`)
### Dummy
Do not execute any command. Just return empty `ExecutorResult` slice.
### Sandbox
Run the commands in `go-judge` and output the `ExecutorResult` slice.
## Parsers (under `/internal/parsers`)
### Clang Tidy
Parser for `clang-tidy`, check `/examples/clangtidy` on how to call `clang-tidy` with proper parameters.
### Cppcheck
Parser for `cppcheck`, check `/examples/cppcheck` on how to call `cppcheck` with proper parameters.
### Cpplint
Parser for `cpplint`, check `/examples/cpplint` on how to call `cpplint` with proper parameters.
### Diff
Compare the specified output of `ExecutorResult` with the content of the answer file. If they are the same, then score will be given. Just like a normal online judge system.
### Dummy
Does not parse the output of `ExecutorResult`. It just output what is set inside the configuration file as score and comment. Currently it is used to output metadata for `joint-teapot`.
In `joint-teapot`, it will take the content before `-` of the comment of the first stage with name `metadata` as the exercise name and record in the scoreboard. (e.g. If the comment is `p2-s2-0xdeadbeef`, then the exercise name is `p2`.)
The comment in `metadata` can also be used to skip teapot commands. With `skip-teapot` in the comment, teapot will not run. And with `skip-scoreboard`, `skip-failed-table`, and `skip-result-issue`, only the corresponding step will be skipped, while the others will be executed. (e.g. If the comment is `p2-s2-0xdeadbeef-skip-scoreboard-skip-result-issue`, then only failed table step in teapot will run.)
### Healthcheck
Parser for the `healthcheck` binary mentioned before.
### Keyword
Match the given keyword from the specified output of `ExecutorResult`. For each match, a deduction of score is given. Can be useful if we do not have a specific parser for a code quality tool. Check `/examples/keyword`.
### Result Status
Only check if all the status of the executor is `StatusAccepted`. Can be used to check whether the command run in the executor exit normally.
### Sample
Parser for the `sample` binary mentioned before. Only used as a sample.

89
cmd/healthcheck/main.go Normal file
View File

@ -0,0 +1,89 @@
package main
import (
"flag"
"fmt"
"log/slog"
"os"
"github.com/joint-online-judge/JOJ3/pkg/healthcheck"
)
// parseMultiValueFlag parses a multi-value command-line flag and appends its values to the provided slice.
// It registers a flag with the specified name and description, associating it with a multiStringValue receiver.
func parseMultiValueFlag(values *[]string, flagName, description string) {
flag.Var((*multiStringValue)(values), flagName, description)
}
type multiStringValue []string
// Set appends a new value to the multiStringValue slice.
// It satisfies the flag.Value interface, allowing multiStringValue to be used as a flag value.
func (m *multiStringValue) Set(value string) error {
*m = append(*m, value)
return nil
}
func (m *multiStringValue) String() string {
return fmt.Sprintf("%v", *m)
}
func setupSlog() {
opts := &slog.HandlerOptions{}
handler := slog.NewTextHandler(os.Stderr, opts)
logger := slog.New(handler)
slog.SetDefault(logger)
}
var Version string
// Generally, err is used for runtime errors, and checkRes is used for the result of the checks.
func main() {
var gitWhitelist, metaFile []string
showVersion := flag.Bool("version", false, "print current version")
rootDir := flag.String("root", "", "")
repo := flag.String("repo", "", "")
localList := flag.String("localList", "", "")
droneBranch := flag.String("droneBranch", "", "")
releaseCategories := flag.String("releaseCategories", "", "")
releaseNumber := flag.Int("releaseNumber", 0, "")
checkFileNameList := flag.String("checkFileNameList", "", "Comma-separated list of files to check.")
checkFileSumList := flag.String("checkFileSumList", "", "Comma-separated list of expected checksums.")
parseMultiValueFlag(&gitWhitelist, "whitelist", "")
parseMultiValueFlag(&metaFile, "meta", "")
flag.Parse()
if *showVersion {
fmt.Println(Version)
return
}
setupSlog()
var err error
err = healthcheck.RepoSize()
if err != nil {
fmt.Printf("## Repo Size Check Failed:\n%s\n", err.Error())
}
err = healthcheck.ForbiddenCheck(*rootDir, gitWhitelist, *localList, *repo, *droneBranch)
if err != nil {
fmt.Printf("## Forbidden File Check Failed:\n%s\n", err.Error())
}
err = healthcheck.MetaCheck(*rootDir, metaFile)
if err != nil {
fmt.Printf("## Forbidden File Check Failed:\n%s\n", err.Error())
}
err = healthcheck.NonAsciiFiles(*rootDir, *localList)
if err != nil {
fmt.Printf("## Non-ASCII Characters File Check Failed:\n%s\n", err.Error())
}
err = healthcheck.NonAsciiMsg(*rootDir)
if err != nil {
fmt.Printf("## Non-ASCII Characters Commit Message Check Failed:\n%s\n", err.Error())
}
err = healthcheck.CheckTags(*rootDir, *releaseCategories, *releaseNumber)
if err != nil {
fmt.Printf("## Release Tag Check Failed:\n%s\n", err.Error())
}
err = healthcheck.VerifyFiles(*rootDir, *checkFileNameList, *checkFileSumList)
if err != nil {
fmt.Printf("## Repo File Check Failed:\n%s\n", err.Error())
}
}

View File

@ -1,19 +1,25 @@
package main
import (
"fmt"
"log/slog"
"os"
"path/filepath"
"regexp"
"strings"
"focs.ji.sjtu.edu.cn/git/FOCS-dev/JOJ3/internal/stage"
"github.com/go-git/go-git/v5"
"github.com/joint-online-judge/JOJ3/internal/stage"
"github.com/koding/multiconfig"
)
type Conf struct {
LogLevel int `default:"0"`
SandboxExecServer string `default:"localhost:5051"`
SandboxToken string `default:""`
LogPath string `default:""`
OutputPath string `default:"joj3_result.json"`
Stages []struct {
Name string
Group string
Executor struct {
Name string
With struct {
@ -46,7 +52,7 @@ type OptionalCmd struct {
CopyIn *map[string]stage.CmdFile
CopyInCached *map[string]string
CopyInCwd *bool
CopyInDir *string
CopyOut *[]string
CopyOutCached *[]string
@ -59,33 +65,107 @@ type OptionalCmd struct {
AddressSpaceLimit *bool
}
func parseConfFile(path string) Conf {
m := multiconfig.NewWithPath(path)
conf := Conf{}
err := m.Load(&conf)
if err != nil {
slog.Error("parse stages conf", "error", err)
os.Exit(1)
}
return conf
type ConventionalCommit struct {
Type string
Scope string
Description string
Body string
Footer string
}
func commitMsgToConf() (conf Conf, err error) {
r, err := git.PlainOpen(".")
if err != nil {
func parseConventionalCommit(commit string) (*ConventionalCommit, error) {
re := regexp.MustCompile(`^(\w+)(\(([^)]+)\))?!?: (.+)(\n\n(.+))?(\n\n(.+))?$`)
matches := re.FindStringSubmatch(strings.TrimSpace(commit))
if matches == nil {
return nil, fmt.Errorf("invalid conventional commit format")
}
cc := &ConventionalCommit{
Type: matches[1],
Scope: matches[3],
Description: strings.TrimSpace(matches[4]),
Body: strings.TrimSpace(matches[6]),
Footer: strings.TrimSpace(matches[8]),
}
return cc, nil
}
func parseConfFile(path string) (conf Conf, err error) {
d := &multiconfig.DefaultLoader{}
d.Loader = multiconfig.MultiLoader(
&multiconfig.TagLoader{},
&multiconfig.JSONLoader{Path: path},
)
d.Validator = multiconfig.MultiValidator(&multiconfig.RequiredValidator{})
if err = d.Load(&conf); err != nil {
slog.Error("parse stages conf", "error", err)
return
}
ref, err := r.Head()
if err != nil {
if err = d.Validate(&conf); err != nil {
slog.Error("validate stages conf", "error", err)
return
}
commit, err := r.CommitObject(ref.Hash())
if err != nil {
return
}
msg := commit.Message
slog.Debug("commit msg to conf", "msg", msg)
// TODO: parse msg to conf name
conf = parseConfFile("conf.toml")
return
}
func parseMsg(confRoot, confName, msg string) (conf Conf, group string, err error) {
slog.Info("parse msg", "msg", msg)
conventionalCommit, err := parseConventionalCommit(msg)
if err != nil {
return
}
slog.Info("conventional commit", "commit", conventionalCommit)
confRoot = filepath.Clean(confRoot)
confPath := filepath.Clean(fmt.Sprintf("%s/%s/%s",
confRoot, conventionalCommit.Scope, confName))
relPath, err := filepath.Rel(confRoot, confPath)
if err != nil {
return
}
if strings.HasPrefix(relPath, "..") {
err = fmt.Errorf("invalid scope as path: %s", conventionalCommit.Scope)
return
}
slog.Info("try to load conf", "path", confPath)
conf, err = parseConfFile(confPath)
if err != nil {
return
}
groupKeywords := []string{"joj"}
for _, groupKeyword := range groupKeywords {
if strings.Contains(
strings.ToLower(conventionalCommit.Description), groupKeyword) {
group = groupKeyword
break
}
}
slog.Debug("conf loaded", "conf", conf)
return
}
func listValidScopes(confRoot, confName, msg string) ([]string, error) {
conventionalCommit, err := parseConventionalCommit(msg)
if err != nil {
return []string{}, err
}
slog.Info("conventional commit", "commit", conventionalCommit)
confRoot = filepath.Clean(confRoot)
validScopes := []string{}
err = filepath.Walk(confRoot, func(path string, info os.FileInfo, err error) error {
if err != nil {
slog.Error("list valid scopes", "error", err)
return err
}
if info.IsDir() {
confPath := filepath.Join(path, confName)
if _, err := os.Stat(confPath); err == nil {
relPath, err := filepath.Rel(confRoot, path)
if err != nil {
return err
}
validScopes = append(validScopes, relPath)
}
}
return nil
})
return validScopes, err
}

86
cmd/joj3/conf_test.go Normal file
View File

@ -0,0 +1,86 @@
package main
import (
"reflect"
"testing"
)
func TestParseConventionalCommit(t *testing.T) {
tests := []struct {
name string
commit string
want *ConventionalCommit
wantErr bool
}{
{
name: "Simple feat commit",
commit: "feat: add new feature",
want: &ConventionalCommit{
Type: "feat",
Description: "add new feature",
},
wantErr: false,
},
{
name: "Commit with scope",
commit: "fix(core): resolve memory leak",
want: &ConventionalCommit{
Type: "fix",
Scope: "core",
Description: "resolve memory leak",
},
wantErr: false,
},
{
name: "Breaking change commit",
commit: "feat(api)!: redesign user authentication",
want: &ConventionalCommit{
Type: "feat",
Scope: "api",
Description: "redesign user authentication",
},
wantErr: false,
},
{
name: "Commit with body",
commit: "docs: update README\n\nAdd installation instructions and improve examples",
want: &ConventionalCommit{
Type: "docs",
Description: "update README",
Body: "Add installation instructions and improve examples",
},
wantErr: false,
},
{
name: "Full commit with body and footer",
commit: "feat(auth)!: implement OAuth2\n\nThis commit adds OAuth2 support to the authentication system.\n\nBREAKING CHANGE: Previous authentication tokens are no longer valid.",
want: &ConventionalCommit{
Type: "feat",
Scope: "auth",
Description: "implement OAuth2",
Body: "This commit adds OAuth2 support to the authentication system.",
Footer: "BREAKING CHANGE: Previous authentication tokens are no longer valid.",
},
wantErr: false,
},
{
name: "Invalid commit format",
commit: "This is not a valid conventional commit",
want: nil,
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := parseConventionalCommit(tt.commit)
if (err != nil) != tt.wantErr {
t.Errorf("ParseConventionalCommit() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("ParseConventionalCommit() = %v, want %v", got, tt.want)
}
})
}
}

77
cmd/joj3/log.go Normal file
View File

@ -0,0 +1,77 @@
package main
import (
"context"
"log/slog"
"os"
)
type multiHandler struct {
handlers []slog.Handler
}
func (h *multiHandler) Enabled(ctx context.Context, level slog.Level) bool {
for _, handler := range h.handlers {
if handler.Enabled(ctx, level) {
return true
}
}
return false
}
func (h *multiHandler) Handle(ctx context.Context, r slog.Record) error {
for _, handler := range h.handlers {
if handler.Enabled(ctx, r.Level) {
if err := handler.Handle(ctx, r); err != nil {
return err
}
}
}
return nil
}
func (h *multiHandler) WithAttrs(attrs []slog.Attr) slog.Handler {
handlers := make([]slog.Handler, len(h.handlers))
for i, handler := range h.handlers {
handlers[i] = handler.WithAttrs(attrs)
}
return &multiHandler{handlers: handlers}
}
func (h *multiHandler) WithGroup(name string) slog.Handler {
handlers := make([]slog.Handler, len(h.handlers))
for i, handler := range h.handlers {
handlers[i] = handler.WithGroup(name)
}
return &multiHandler{handlers: handlers}
}
func setupSlog(logPath string) error {
handlers := []slog.Handler{}
if logPath != "" {
// File handler for debug logs
debugFile, err := os.OpenFile(logPath,
os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0o600)
if err != nil {
return err
}
debugHandler := slog.NewTextHandler(debugFile, &slog.HandlerOptions{
Level: slog.LevelDebug,
})
handlers = append(handlers, debugHandler)
}
// Stderr handler for info logs and above
stderrHandler := slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{
Level: slog.LevelInfo,
})
handlers = append(handlers, stderrHandler)
// Create a multi-handler
multiHandler := &multiHandler{handlers: handlers}
// Set the default logger
logger := slog.New(multiHandler)
slog.SetDefault(logger)
if logPath != "" {
slog.Info("debug log", "path", logPath)
}
return nil
}

View File

@ -2,28 +2,42 @@ package main
import (
"encoding/json"
"flag"
"fmt"
"log/slog"
"os"
_ "focs.ji.sjtu.edu.cn/git/FOCS-dev/JOJ3/internal/executors"
_ "focs.ji.sjtu.edu.cn/git/FOCS-dev/JOJ3/internal/parsers"
"focs.ji.sjtu.edu.cn/git/FOCS-dev/JOJ3/internal/stage"
"github.com/joint-online-judge/JOJ3/internal/executors"
_ "github.com/joint-online-judge/JOJ3/internal/parsers"
"github.com/joint-online-judge/JOJ3/internal/stage"
"github.com/go-git/go-git/v5"
"github.com/jinzhu/copier"
)
func setupSlog(conf Conf) {
lvl := new(slog.LevelVar)
lvl.Set(slog.Level(conf.LogLevel))
opts := &slog.HandlerOptions{Level: lvl}
handler := slog.NewTextHandler(os.Stderr, opts)
logger := slog.New(handler)
slog.SetDefault(logger)
func getCommitMsg() (msg string, err error) {
r, err := git.PlainOpen(".")
if err != nil {
return
}
ref, err := r.Head()
if err != nil {
return
}
commit, err := r.CommitObject(ref.Hash())
if err != nil {
return
}
msg = commit.Message
return
}
func generateStages(conf Conf) []stage.Stage {
func generateStages(conf Conf, group string) ([]stage.Stage, error) {
stages := []stage.Stage{}
for _, s := range conf.Stages {
if s.Group != "" && group != "" && group != s.Group {
continue
}
var cmds []stage.Cmd
defaultCmd := s.Executor.With.Default
for _, optionalCmd := range s.Executor.With.Cases {
@ -31,7 +45,7 @@ func generateStages(conf Conf) []stage.Stage {
err := copier.Copy(&cmd, &optionalCmd)
if err != nil {
slog.Error("generate stages", "error", err)
os.Exit(1)
return stages, err
}
// since these 3 values are pointers, copier will always copy
// them, so we need to check them manually
@ -49,7 +63,6 @@ func generateStages(conf Conf) []stage.Stage {
if len(s.Executor.With.Cases) == 0 {
cmds = []stage.Cmd{defaultCmd}
}
slog.Debug("parse stages conf", "cmds", cmds)
stages = append(stages, stage.Stage{
Name: s.Name,
ExecutorName: s.Executor.Name,
@ -58,29 +71,91 @@ func generateStages(conf Conf) []stage.Stage {
ParserConf: s.Parser.With,
})
}
return stages
slog.Debug("stages generated", "stages", stages)
return stages, nil
}
func outputResult(conf Conf, results []stage.StageResult) error {
func outputResult(outputPath string, results []stage.StageResult) error {
slog.Info("output result start", "path", outputPath)
slog.Debug("output result start", "path", outputPath, "results", results)
content, err := json.Marshal(results)
if err != nil {
return err
}
return os.WriteFile(conf.OutputPath,
return os.WriteFile(outputPath,
append(content, []byte("\n")...), 0o600)
}
func main() {
conf, err := commitMsgToConf()
var (
confRoot string
confName string
msg string
showVersion *bool
Version string = "debug"
)
func init() {
flag.StringVar(&confRoot, "conf-root", ".", "root path for all config files")
flag.StringVar(&confName, "conf-name", "conf.json", "filename for config files")
flag.StringVar(&msg, "msg", "", "message to trigger the running, leave empty to use git commit message on HEAD")
showVersion = flag.Bool("version", false, "print current version")
}
func mainImpl() error {
if err := setupSlog(""); err != nil { // before conf is loaded
return err
}
flag.Parse()
if *showVersion {
fmt.Println(Version)
return nil
}
slog.Info("start joj3", "version", Version)
if msg == "" {
var err error
msg, err = getCommitMsg()
if err != nil {
slog.Error("no conf found", "error", err)
slog.Error("get commit msg", "error", err)
return err
}
}
conf, group, err := parseMsg(confRoot, confName, msg)
if err != nil {
slog.Error("parse msg", "error", err)
validScopes, scopeErr := listValidScopes(
confRoot, confName, msg)
if scopeErr != nil {
slog.Error("list valid scopes", "error", scopeErr)
return scopeErr
}
slog.Info("hint: valid scopes in commit message", "scopes", validScopes)
return err
}
if err := setupSlog(conf.LogPath); err != nil { // after conf is loaded
return err
}
executors.InitWithConf(conf.SandboxExecServer, conf.SandboxToken)
stages, err := generateStages(conf, group)
if err != nil {
slog.Error("generate stages", "error", err)
return err
}
defer stage.Cleanup()
results, err := stage.Run(stages)
if err != nil {
slog.Error("run stages", "error", err)
return err
}
if err := outputResult(conf.OutputPath, results); err != nil {
slog.Error("output result", "error", err)
return err
}
return nil
}
func main() {
if err := mainImpl(); err != nil {
slog.Error("main exit", "error", err)
os.Exit(1)
}
setupSlog(conf)
stages := generateStages(conf)
defer stage.Cleanup()
results := stage.Run(stages)
if err := outputResult(conf, results); err != nil {
slog.Error("output result", "error", err)
}
}

View File

@ -9,7 +9,7 @@ import (
"strings"
"testing"
"focs.ji.sjtu.edu.cn/git/FOCS-dev/JOJ3/internal/stage"
"github.com/joint-online-judge/JOJ3/internal/stage"
)
func compareStageResults(t *testing.T, actual, expected []stage.StageResult, regex bool) {
@ -63,7 +63,7 @@ func readStageResults(t *testing.T, path string) []stage.StageResult {
func TestMain(t *testing.T) {
var tests []string
root := "../../tmp/submodules/JOJ3-examples"
root := "../../tmp/submodules/JOJ3-examples/examples/"
err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err

View File

@ -6,13 +6,20 @@ import (
"fmt"
"os"
"focs.ji.sjtu.edu.cn/git/FOCS-dev/JOJ3/pkg/dummy"
"github.com/joint-online-judge/JOJ3/pkg/sample"
)
var Version string
func main() {
showVersion := flag.Bool("version", false, "print current version")
score := flag.Int("score", 0, "score")
flag.Parse()
res, err := dummy.Run(dummy.Conf{Score: *score})
if *showVersion {
fmt.Println(Version)
return
}
res, err := sample.Run(sample.Conf{Score: *score})
if err != nil {
fmt.Fprint(os.Stderr, err)
os.Exit(1)

@ -1 +1 @@
Subproject commit 34f1bcd9cf3e2905ecfd95284b25eae2568d38df
Subproject commit 30b179812335b427a431c303ed3453779f898796

@ -1 +1 @@
Subproject commit 9f0d88eff4e9d7d3ac58f460609272f7d2e527e7
Subproject commit b16b4a9679bb97d8703a3c70d3b75dd06e9fd4df

@ -1 +1 @@
Subproject commit a90071148cb7e39cf3e93b18a1e71632d7e974a3
Subproject commit ca21e3ef7980339e433c6531d8e2aae9678eff2d

@ -1 +1 @@
Subproject commit d33b07f18bf36c77dcf9a012d265dc7bd45fac9f
Subproject commit 866e73258c2d8f87b0000754a16f9b9c7de0c0fa

@ -1 +1 @@
Subproject commit 6d47ed4f6f7b8632784513edd8a206689503fc56
Subproject commit af355d91abf1ed62f19e0703dad55d18eb1252f8

1
examples/diff/basic Submodule

@ -0,0 +1 @@
Subproject commit cc5d991cb401b0c8cfd8e03a76fb9994158d2819

1
examples/diff/complex Submodule

@ -0,0 +1 @@
Subproject commit f29fbdac51f851e782f0db13f8640c6ba0aec49d

@ -1 +0,0 @@
Subproject commit 9cf0d994aa613dc38f2bc99f42d0dd7509c3d42c

@ -1 +0,0 @@
Subproject commit c2fe51b4cd485339fd18924196b01eed6848ba1d

@ -0,0 +1 @@
Subproject commit 146b2f829a91594c89f1d1379d312b91c5f97a55

@ -0,0 +1 @@
Subproject commit ffd9ab13c7e403d0afc7f1dfc86ec131f9992498

@ -0,0 +1 @@
Subproject commit b93a0254c2842e4d9c3a4b8f832cd6b17bf284f9

@ -0,0 +1 @@
Subproject commit b4693716ee97bd5d9814c512b036aabeb12c7fd6

@ -0,0 +1 @@
Subproject commit 50ec9ccbb6d7fe9b5352a018b6ac6445ac5c354f

@ -0,0 +1 @@
Subproject commit 9c010a0f8d501223f94d677a4aed70f0162092e2

@ -0,0 +1 @@
Subproject commit 99d95c2841517f36a2063c641ea2e1b9b6190c76

@ -1 +1 @@
Subproject commit 9de8a32bc3a55f0652d487421384d0a163bf0aaa
Subproject commit 1f962c75faccc9e703a8cd46e0a939551306bd4c

@ -1 +1 @@
Subproject commit 9be8c3c329342059f49683a4e810938e8321adbe
Subproject commit 507ae70e81c08a6becdc8219a2c62b5aa8c3db69

1
examples/sample/error Submodule

@ -0,0 +1 @@
Subproject commit 31b319157fb221636fcdf8d6a752a4a5e46d6a6d

@ -0,0 +1 @@
Subproject commit 7a98095a322419939bffa8636207090493108a3c

39
go.mod
View File

@ -1,24 +1,24 @@
module focs.ji.sjtu.edu.cn/git/FOCS-dev/JOJ3
module github.com/joint-online-judge/JOJ3
go 1.22.1
go 1.23.1
require (
github.com/criyle/go-judge v1.8.2
github.com/go-git/go-git/v5 v5.11.0
github.com/criyle/go-judge v1.8.5
github.com/go-git/go-git/v5 v5.12.0
github.com/jinzhu/copier v0.4.0
github.com/koding/multiconfig v0.0.0-20171124222453-69c27309b2d7
github.com/mitchellh/mapstructure v1.5.0
google.golang.org/grpc v1.62.0
google.golang.org/grpc v1.67.0
)
require (
dario.cat/mergo v1.0.0 // indirect
github.com/BurntSushi/toml v1.3.2 // indirect
github.com/Microsoft/go-winio v0.6.1 // indirect
github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371 // indirect
github.com/cloudflare/circl v1.3.3 // indirect
github.com/ProtonMail/go-crypto v1.0.0 // indirect
github.com/cloudflare/circl v1.4.0 // indirect
github.com/creack/pty v1.1.21 // indirect
github.com/criyle/go-sandbox v0.10.1 // indirect
github.com/criyle/go-sandbox v0.10.4 // indirect
github.com/cyphar/filepath-securejoin v0.2.4 // indirect
github.com/emirpasic/gods v1.18.1 // indirect
github.com/fatih/camelcase v1.0.0 // indirect
@ -26,22 +26,21 @@ require (
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
github.com/go-git/go-billy/v5 v5.5.0 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
github.com/kevinburke/ssh_config v1.2.0 // indirect
github.com/pjbgf/sha1cd v0.3.0 // indirect
github.com/sergi/go-diff v1.1.0 // indirect
github.com/skeema/knownhosts v1.2.1 // indirect
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect
github.com/skeema/knownhosts v1.2.2 // indirect
github.com/xanzy/ssh-agent v0.3.3 // indirect
golang.org/x/crypto v0.20.0 // indirect
golang.org/x/mod v0.12.0 // indirect
golang.org/x/net v0.21.0 // indirect
golang.org/x/sync v0.6.0 // indirect
golang.org/x/sys v0.17.0 // indirect
golang.org/x/text v0.14.0 // indirect
golang.org/x/tools v0.13.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240228224816-df926f6c8641 // indirect
google.golang.org/protobuf v1.32.0 // indirect
golang.org/x/crypto v0.27.0 // indirect
golang.org/x/mod v0.17.0 // indirect
golang.org/x/net v0.29.0 // indirect
golang.org/x/sync v0.8.0 // indirect
golang.org/x/sys v0.25.0 // indirect
golang.org/x/text v0.18.0 // indirect
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142 // indirect
google.golang.org/protobuf v1.34.2 // indirect
gopkg.in/warnings.v0 v0.1.2 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
)

87
go.sum
View File

@ -5,21 +5,22 @@ github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbi
github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY=
github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow=
github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM=
github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371 h1:kkhsdkhsCvIsutKu5zLMgWtgh9YxGCNAw8Ad8hjwfYg=
github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0=
github.com/ProtonMail/go-crypto v1.0.0 h1:LRuvITjQWX+WIfr930YHG2HNfjR1uOfyf5vE0kC2U78=
github.com/ProtonMail/go-crypto v1.0.0/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0=
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0=
github.com/cloudflare/circl v1.3.3 h1:fE/Qz0QdIGqeWfnwq0RE0R7MI51s0M2E4Ga9kq5AEMs=
github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA=
github.com/cloudflare/circl v1.4.0 h1:BV7h5MgrktNzytKmWjpOtdYrf0lkkbF8YMlBGPhJQrY=
github.com/cloudflare/circl v1.4.0/go.mod h1:PDRU+oXvdD7KCtgKxW95M5Z8BpSCJXQORiZFnBQS5QU=
github.com/creack/pty v1.1.21 h1:1/QdRyBaHHJP61QkWMXlOIBfsgdDeeKfK8SYVUWJKf0=
github.com/creack/pty v1.1.21/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
github.com/criyle/go-judge v1.8.2 h1:dGjLqJRBifqsLVZj1scr23zdM4wPe98HTIVgYzPuRxA=
github.com/criyle/go-judge v1.8.2/go.mod h1:3RgsMp21D+UvXzkpOGsVFbLe2T2Lwk8jPEmCntQrvHQ=
github.com/criyle/go-sandbox v0.10.1 h1:z9Il/UXQwKEvIwdr1wVheWWWAqGWtdTItBmEsWqFqT4=
github.com/criyle/go-sandbox v0.10.1/go.mod h1:ivPw/HEh5unxVRlXJxCgkgTCuy+cxTkQDX7D2XQf/kg=
github.com/criyle/go-judge v1.8.5 h1:4G1kMvIdYQ5S9EajcrYNyCYovE80Rgkt8Pg0dCxb2EM=
github.com/criyle/go-judge v1.8.5/go.mod h1:+0lDizAqZugQ4NQvpYmR5ujpVaRUqEl/9ZiZ84nrOIU=
github.com/criyle/go-sandbox v0.10.4 h1:EHJrJj5V/VSrjm1Y0ZJAea5zPASoOtn1CPZRzhTUYIM=
github.com/criyle/go-sandbox v0.10.4/go.mod h1:sYJUuTmJ72Jilkc1/PO7eDdpJq3rOZ55o8MxzP80vw0=
github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg=
github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@ -33,22 +34,18 @@ github.com/fatih/camelcase v1.0.0 h1:hxNvNX/xYBp0ovncs8WyWZrOrpBNub/JfaMvbURyft8
github.com/fatih/camelcase v1.0.0/go.mod h1:yN2Sb0lFhZJUdVvtELVWefmrXpuZESvPmqwoZc+/fpc=
github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo=
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
github.com/gliderlabs/ssh v0.3.5 h1:OcaySEmAQJgyYcArR+gGGTHCyE7nvhEMTlYY+Dp8CpY=
github.com/gliderlabs/ssh v0.3.5/go.mod h1:8XB4KraRrX39qHhT6yxPsHedjA08I/uBVwj4xC+/+z4=
github.com/gliderlabs/ssh v0.3.7 h1:iV3Bqi942d9huXnzEF2Mt+CY9gLu8DNM4Obd+8bODRE=
github.com/gliderlabs/ssh v0.3.7/go.mod h1:zpHEXBstFnQYtGnB8k8kQLol82umzn/2/snG7alWVD8=
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI=
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic=
github.com/go-git/go-billy/v5 v5.5.0 h1:yEY4yhzCDuMGSv83oGxiBotRzhwhNr8VZyphhiu+mTU=
github.com/go-git/go-billy/v5 v5.5.0/go.mod h1:hmexnoNsr2SJU1Ju67OaNz5ASJY3+sHgFRpCtpDCKow=
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4=
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII=
github.com/go-git/go-git/v5 v5.11.0 h1:XIZc1p+8YzypNr34itUfSvYJcv+eYdTnTvOZ2vD3cA4=
github.com/go-git/go-git/v5 v5.11.0/go.mod h1:6GFcX2P3NM7FPBfpePbpLd21XxsgdAt+lKqXmCUiUCY=
github.com/go-git/go-git/v5 v5.12.0 h1:7Md+ndsjrzZxbddRDZjF14qK+NN56sy6wkqaVrjZtys=
github.com/go-git/go-git/v5 v5.12.0/go.mod h1:FTM9VKtnI2m65hNI/TenDDDnUf2Q9FHnXYjuz9i5OEY=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
@ -78,16 +75,16 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8=
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/skeema/knownhosts v1.2.1 h1:SHWdIUa82uGZz+F+47k8SY4QhhI291cXCpopT1lK2AQ=
github.com/skeema/knownhosts v1.2.1/go.mod h1:xYbVRSPxqBZFrdmDyMmsOs+uX1UZC3nTN3ThzgDxUwo=
github.com/skeema/knownhosts v1.2.2 h1:Iug2P4fLmDw9f41PB6thxUkNUkJzB5i+1/exaj40L3A=
github.com/skeema/knownhosts v1.2.2/go.mod h1:xYbVRSPxqBZFrdmDyMmsOs+uX1UZC3nTN3ThzgDxUwo=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM=
github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
@ -96,12 +93,12 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
golang.org/x/crypto v0.20.0 h1:jmAMJJZXr5KiCw05dfYK9QnqaqKLYXijU23lsEdcQqg=
golang.org/x/crypto v0.20.0/go.mod h1:Xwo95rrVNIoSMx9wa1JroENMToLWn3RNVrTBpLHgZPQ=
golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A=
golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc=
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
@ -109,13 +106,13 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4=
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo=
golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@ -129,15 +126,15 @@ golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34=
golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
golang.org/x/term v0.17.0 h1:mkTF7LCd6WGJNL3K1Ad7kwxNfYAW6a8a8QqtMblp/4U=
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
golang.org/x/term v0.24.0 h1:Mh5cbb+Zk2hqqXNO7S1iTjEphVL+jb8ZWaqh/g+JWkM=
golang.org/x/term v0.24.0/go.mod h1:lOBK/LVxemqiMij05LGJ0tzNr8xlmwBRJ81PX6wVLH8=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
@ -145,24 +142,21 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224=
golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ=
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240228224816-df926f6c8641 h1:DKU1r6Tj5s1vlU/moGhuGz7E3xRfwjdAfDzbsaQJtEY=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240228224816-df926f6c8641/go.mod h1:UCOku4NytXMJuLQE5VuqA5lX3PcHCBo8pxNyvkf4xBs=
google.golang.org/grpc v1.62.0 h1:HQKZ/fa1bXkX1oFOvSjmZEUL8wLSaZTjCcLAlmZRtdk=
google.golang.org/grpc v1.62.0/go.mod h1:IWTG0VlJLCh1SkC58F7np9ka9mx/WNkjl4PGJaiq+QE=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I=
google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142 h1:e7S5W7MGGLaSu8j3YjdezkZ+m1/Nm0uRVRMEMGk26Xs=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU=
google.golang.org/grpc v1.67.0 h1:IdH9y6PF5MPSdAntIcpjQ+tXO41pcQsfZV2RxtQgVcw=
google.golang.org/grpc v1.67.0/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA=
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
@ -170,7 +164,6 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EV
gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME=
gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=

View File

@ -1,9 +1,14 @@
package executors
import (
_ "focs.ji.sjtu.edu.cn/git/FOCS-dev/JOJ3/internal/executors/dummy"
_ "focs.ji.sjtu.edu.cn/git/FOCS-dev/JOJ3/internal/executors/sandbox"
_ "github.com/joint-online-judge/JOJ3/internal/executors/dummy"
"github.com/joint-online-judge/JOJ3/internal/executors/sandbox"
)
// this file does nothing but imports to ensure all the init() functions
// in the subpackages are called
// overwrite the default registered executors
func InitWithConf(sandboxExecServer, sandboxToken string) {
sandbox.InitWithConf(sandboxExecServer, sandboxToken)
}

View File

@ -1,8 +1,8 @@
package dummy
import (
"focs.ji.sjtu.edu.cn/git/FOCS-dev/JOJ3/internal/stage"
"github.com/criyle/go-judge/envexec"
"github.com/joint-online-judge/JOJ3/internal/stage"
)
type Dummy struct{}
@ -11,9 +11,9 @@ func (e *Dummy) Run(cmds []stage.Cmd) ([]stage.ExecutorResult, error) {
var res []stage.ExecutorResult
for range cmds {
res = append(res, stage.ExecutorResult{
Status: stage.Status(envexec.StatusInvalid),
Status: stage.Status(envexec.StatusAccepted),
ExitStatus: 0,
Error: "I'm a dummy",
Error: "",
Time: 0,
Memory: 0,
RunTime: 0,

View File

@ -1,6 +1,6 @@
package dummy
import "focs.ji.sjtu.edu.cn/git/FOCS-dev/JOJ3/internal/stage"
import "github.com/joint-online-judge/JOJ3/internal/stage"
var name = "dummy"

View File

@ -1,12 +1,13 @@
package sandbox
import (
"log/slog"
"os"
"path/filepath"
"strings"
"focs.ji.sjtu.edu.cn/git/FOCS-dev/JOJ3/internal/stage"
"github.com/criyle/go-judge/pb"
"github.com/joint-online-judge/JOJ3/internal/stage"
)
// copied from https://github.com/criyle/go-judge/blob/master/cmd/go-judge-shell/grpc.go
@ -27,7 +28,7 @@ func convertPBCmd(cmd []stage.Cmd) []*pb.Request_CmdType {
CpuSetLimit: c.CPUSetLimit,
DataSegmentLimit: c.DataSegmentLimit,
AddressSpaceLimit: c.AddressSpaceLimit,
CopyIn: convertPBCopyIn(c.CopyIn, c.CopyInCwd),
CopyIn: convertPBCopyIn(c.CopyIn, c.CopyInDir),
CopyOut: convertPBCopyOut(c.CopyOut),
CopyOutCached: convertPBCopyOut(c.CopyOutCached),
CopyOutMax: c.CopyOutMax,
@ -38,9 +39,11 @@ func convertPBCmd(cmd []stage.Cmd) []*pb.Request_CmdType {
return ret
}
func convertPBCopyIn(copyIn map[string]stage.CmdFile, copyInCwd bool) map[string]*pb.Request_File {
if copyInCwd {
_ = filepath.Walk(".",
func convertPBCopyIn(
copyIn map[string]stage.CmdFile, copyInDir string,
) map[string]*pb.Request_File {
if copyInDir != "" {
_ = filepath.Walk(copyInDir,
func(path string, info os.FileInfo, err error) error {
if err != nil {
return nil
@ -113,7 +116,12 @@ func convertPBFile(i stage.CmdFile) *pb.Request_File {
i.Src = &absPath
}
}
return &pb.Request_File{File: &pb.Request_File_Local{Local: &pb.Request_LocalFile{Src: *i.Src}}}
s, err := os.ReadFile(*i.Src)
if err != nil {
s = []byte{}
slog.Error("read file error", "path", *i.Src, "error", err)
}
return &pb.Request_File{File: &pb.Request_File_Memory{Memory: &pb.Request_MemoryFile{Content: s}}}
case i.Content != nil:
s := strToBytes(*i.Content)
return &pb.Request_File{File: &pb.Request_File_Memory{Memory: &pb.Request_MemoryFile{Content: s}}}

View File

@ -5,8 +5,8 @@ import (
"fmt"
"log/slog"
"focs.ji.sjtu.edu.cn/git/FOCS-dev/JOJ3/internal/stage"
"github.com/criyle/go-judge/pb"
"github.com/joint-online-judge/JOJ3/internal/stage"
)
type Sandbox struct {
@ -18,6 +18,7 @@ type Sandbox struct {
func (e *Sandbox) Run(cmds []stage.Cmd) ([]stage.ExecutorResult, error) {
var err error
if e.execClient == nil {
slog.Debug("create exec client", "server", e.execServer)
e.execClient, err = createExecClient(e.execServer, e.token)
if err != nil {
return nil, err

View File

@ -27,7 +27,7 @@ func createGRPCConnection(addr, token string) (*grpc.ClientConn, error) {
if token != "" {
opts = append(opts, grpc.WithPerRPCCredentials(newTokenAuth(token)))
}
return grpc.Dial(addr, opts...)
return grpc.NewClient(addr, opts...)
}
type tokenAuth struct {

View File

@ -1,16 +1,24 @@
package sandbox
import (
"focs.ji.sjtu.edu.cn/git/FOCS-dev/JOJ3/internal/stage"
"github.com/joint-online-judge/JOJ3/internal/stage"
)
var name = "sandbox"
func init() {
stage.RegisterExecutor(name, &Sandbox{
// TODO: read from conf
execServer: "localhost:5051",
token: "",
cachedMap: make(map[string]string),
})
}
// overwrite the default registered executor
func InitWithConf(execServer, token string) {
stage.RegisterExecutor(name, &Sandbox{
execServer: execServer,
token: token,
cachedMap: make(map[string]string),
})
}

View File

@ -1,13 +1,15 @@
package parsers
import (
_ "focs.ji.sjtu.edu.cn/git/FOCS-dev/JOJ3/internal/parsers/clangtidy"
_ "focs.ji.sjtu.edu.cn/git/FOCS-dev/JOJ3/internal/parsers/cppcheck"
_ "focs.ji.sjtu.edu.cn/git/FOCS-dev/JOJ3/internal/parsers/cpplint"
_ "focs.ji.sjtu.edu.cn/git/FOCS-dev/JOJ3/internal/parsers/diff"
_ "focs.ji.sjtu.edu.cn/git/FOCS-dev/JOJ3/internal/parsers/dummy"
_ "focs.ji.sjtu.edu.cn/git/FOCS-dev/JOJ3/internal/parsers/keyword"
_ "focs.ji.sjtu.edu.cn/git/FOCS-dev/JOJ3/internal/parsers/resultstatus"
_ "github.com/joint-online-judge/JOJ3/internal/parsers/clangtidy"
_ "github.com/joint-online-judge/JOJ3/internal/parsers/cppcheck"
_ "github.com/joint-online-judge/JOJ3/internal/parsers/cpplint"
_ "github.com/joint-online-judge/JOJ3/internal/parsers/diff"
_ "github.com/joint-online-judge/JOJ3/internal/parsers/dummy"
_ "github.com/joint-online-judge/JOJ3/internal/parsers/healthcheck"
_ "github.com/joint-online-judge/JOJ3/internal/parsers/keyword"
_ "github.com/joint-online-judge/JOJ3/internal/parsers/resultstatus"
_ "github.com/joint-online-judge/JOJ3/internal/parsers/sample"
)
// this file does nothing but imports to ensure all the init() functions

View File

@ -1,6 +1,6 @@
package clangtidy
import "focs.ji.sjtu.edu.cn/git/FOCS-dev/JOJ3/internal/stage"
import "github.com/joint-online-judge/JOJ3/internal/stage"
var name = "clangtidy"

View File

@ -4,8 +4,8 @@ import (
"fmt"
"strings"
"focs.ji.sjtu.edu.cn/git/FOCS-dev/JOJ3/internal/stage"
"github.com/criyle/go-judge/envexec"
"github.com/joint-online-judge/JOJ3/internal/stage"
)
type Match struct {

View File

@ -1,6 +1,6 @@
package cppcheck
import "focs.ji.sjtu.edu.cn/git/FOCS-dev/JOJ3/internal/stage"
import "github.com/joint-online-judge/JOJ3/internal/stage"
var name = "cppcheck"

View File

@ -5,8 +5,8 @@ import (
"fmt"
"strings"
"focs.ji.sjtu.edu.cn/git/FOCS-dev/JOJ3/internal/stage"
"github.com/criyle/go-judge/envexec"
"github.com/joint-online-judge/JOJ3/internal/stage"
)
type CppCheck struct{}

View File

@ -29,7 +29,7 @@ func severityFromString(severityString string) (Severity, error) {
case "information":
return INFORMATION, nil
default:
return UNKNOWN, fmt.Errorf("unkown severity type \"%s\" for cppcheck", severityString)
return UNKNOWN, fmt.Errorf("unknown severity type \"%s\" for cppcheck", severityString)
}
}

View File

@ -1,6 +1,6 @@
package cpplint
import "focs.ji.sjtu.edu.cn/git/FOCS-dev/JOJ3/internal/stage"
import "github.com/joint-online-judge/JOJ3/internal/stage"
var name = "cpplint"

View File

@ -5,7 +5,7 @@ import (
"regexp"
"strconv"
"focs.ji.sjtu.edu.cn/git/FOCS-dev/JOJ3/internal/stage"
"github.com/joint-online-judge/JOJ3/internal/stage"
)
type Conf struct {

View File

@ -1,6 +1,6 @@
package diff
import "focs.ji.sjtu.edu.cn/git/FOCS-dev/JOJ3/internal/stage"
import "github.com/joint-online-judge/JOJ3/internal/stage"
var name = "diff"

View File

@ -2,15 +2,33 @@ package diff
import (
"fmt"
"log/slog"
"os"
"strings"
"unicode"
"focs.ji.sjtu.edu.cn/git/FOCS-dev/JOJ3/internal/stage"
"github.com/criyle/go-judge/envexec"
"github.com/joint-online-judge/JOJ3/internal/stage"
)
// operation represents the type of edit operation.
type operation uint
const (
INSERT operation = iota + 1
DELETE
MOVE
)
type Conf struct {
Cases []struct {
IgnoreResultStatus bool
Outputs []struct {
Score int
StdoutPath string
FileName string
AnswerPath string
IgnoreWhitespace bool
}
}
}
@ -26,25 +44,206 @@ func (*Diff) Run(results []stage.ExecutorResult, confAny any) (
if len(conf.Cases) != len(results) {
return nil, true, fmt.Errorf("cases number not match")
}
var res []stage.ParserResult
forceQuit := false
for i, caseConf := range conf.Cases {
result := results[i]
score := 0
stdout, err := os.ReadFile(caseConf.StdoutPath)
comment := ""
if !caseConf.IgnoreResultStatus &&
result.Status != stage.Status(envexec.StatusAccepted) {
forceQuit = true
comment += fmt.Sprintf(
"Unexpected executor status: %s.", result.Status,
)
} else {
for _, output := range caseConf.Outputs {
answer, err := os.ReadFile(output.AnswerPath)
if err != nil {
return nil, true, err
}
// TODO: more compare strategies
if string(stdout) == result.Files["stdout"] {
score = caseConf.Score
slog.Debug("compare", "filename", output.FileName,
"answer path", output.AnswerPath,
"actual", result.Files[output.FileName],
"answer", string(answer))
// If no difference, assign score
if compareChars(string(answer), result.Files[output.FileName], output.IgnoreWhitespace) {
score += output.Score
} else {
// Convert answer to string and split by lines
stdoutLines := strings.Split(string(answer), "\n")
resultLines := strings.Split(result.Files[output.FileName], "\n")
// Generate Myers diff
diffOps := myersDiff(stdoutLines, resultLines)
// Generate diff block with surrounding context
diffOutput := generateDiffWithContext(stdoutLines, resultLines, diffOps)
comment += fmt.Sprintf(
"difference found in %s:\n```diff\n%s```\n",
output.FileName, diffOutput,
)
}
}
}
res = append(res, stage.ParserResult{
Score: score,
Comment: fmt.Sprintf(
"executor status: run time: %d ns, memory: %d bytes",
result.RunTime, result.Memory,
),
Comment: comment,
})
}
return res, false, nil
return res, forceQuit, nil
}
// compareChars compares two strings character by character, optionally ignoring whitespace.
func compareChars(stdout, result string, ignoreWhitespace bool) bool {
if ignoreWhitespace {
stdout = removeWhitespace(stdout)
result = removeWhitespace(result)
}
return stdout == result
}
// removeWhitespace removes all whitespace characters from the string.
func removeWhitespace(s string) string {
var b strings.Builder
for _, r := range s {
if !unicode.IsSpace(r) {
b.WriteRune(r)
}
}
return b.String()
}
// myersDiff computes the Myers' diff between two slices of strings.
// src: https://github.com/cj1128/myers-diff/blob/master/main.go
func myersDiff(src, dst []string) []operation {
n := len(src)
m := len(dst)
max := n + m
var trace []map[int]int
var x, y int
loop:
for d := 0; d <= max; d++ {
v := make(map[int]int, d+2)
trace = append(trace, v)
if d == 0 {
t := 0
for len(src) > t && len(dst) > t && src[t] == dst[t] {
t++
}
v[0] = t
if t == len(src) && len(src) == len(dst) {
break loop
}
continue
}
lastV := trace[d-1]
for k := -d; k <= d; k += 2 {
if k == -d || (k != d && lastV[k-1] < lastV[k+1]) {
x = lastV[k+1]
} else {
x = lastV[k-1] + 1
}
y = x - k
for x < n && y < m && src[x] == dst[y] {
x, y = x+1, y+1
}
v[k] = x
if x == n && y == m {
break loop
}
}
}
var script []operation
x = n
y = m
var k, prevK, prevX, prevY int
for d := len(trace) - 1; d > 0; d-- {
k = x - y
lastV := trace[d-1]
if k == -d || (k != d && lastV[k-1] < lastV[k+1]) {
prevK = k + 1
} else {
prevK = k - 1
}
prevX = lastV[prevK]
prevY = prevX - prevK
for x > prevX && y > prevY {
script = append(script, MOVE)
x -= 1
y -= 1
}
if x == prevX {
script = append(script, INSERT)
} else {
script = append(script, DELETE)
}
x, y = prevX, prevY
}
if trace[0][0] != 0 {
for i := 0; i < trace[0][0]; i++ {
script = append(script, MOVE)
}
}
return reverse(script)
}
// reverse reverses a slice of operations.
func reverse(s []operation) []operation {
result := make([]operation, len(s))
for i, v := range s {
result[len(s)-1-i] = v
}
return result
}
// generateDiffWithContext creates a diff block with surrounding context from stdout and result.
func generateDiffWithContext(stdoutLines, resultLines []string, ops []operation) string {
var diffBuilder strings.Builder
srcIndex, dstIndex := 0, 0
for _, op := range ops {
switch op {
case INSERT:
if dstIndex < len(resultLines) {
diffBuilder.WriteString(fmt.Sprintf("+ %s\n", resultLines[dstIndex]))
dstIndex++
}
case MOVE:
if srcIndex < len(stdoutLines) {
diffBuilder.WriteString(fmt.Sprintf(" %s\n", stdoutLines[srcIndex]))
srcIndex++
dstIndex++
}
case DELETE:
if srcIndex < len(stdoutLines) {
diffBuilder.WriteString(fmt.Sprintf("- %s\n", stdoutLines[srcIndex]))
srcIndex++
}
}
}
return diffBuilder.String()
}

View File

@ -1,6 +1,6 @@
package dummy
import "focs.ji.sjtu.edu.cn/git/FOCS-dev/JOJ3/internal/stage"
import "github.com/joint-online-judge/JOJ3/internal/stage"
var name = "dummy"

View File

@ -1,12 +1,7 @@
package dummy
import (
"encoding/json"
"fmt"
"focs.ji.sjtu.edu.cn/git/FOCS-dev/JOJ3/internal/stage"
"focs.ji.sjtu.edu.cn/git/FOCS-dev/JOJ3/pkg/dummy"
"github.com/criyle/go-judge/envexec"
"github.com/joint-online-judge/JOJ3/internal/stage"
)
type Conf struct {
@ -16,32 +11,6 @@ type Conf struct {
type Dummy struct{}
func Parse(executorResult stage.ExecutorResult, conf Conf) stage.ParserResult {
stdout := executorResult.Files["stdout"]
stderr := executorResult.Files["stderr"]
if executorResult.Status != stage.Status(envexec.StatusAccepted) {
return stage.ParserResult{
Score: 0,
Comment: fmt.Sprintf(
"Unexpected executor status: %s.\nStderr: %s",
executorResult.Status, stderr,
),
}
}
var dummyResult dummy.Result
err := json.Unmarshal([]byte(stdout), &dummyResult)
if err != nil {
return stage.ParserResult{
Score: 0,
Comment: fmt.Sprintf("Failed to parse result: %s", err),
}
}
return stage.ParserResult{
Score: dummyResult.Score + conf.Score,
Comment: dummyResult.Comment + conf.Comment,
}
}
func (*Dummy) Run(results []stage.ExecutorResult, confAny any) (
[]stage.ParserResult, bool, error,
) {
@ -50,8 +19,8 @@ func (*Dummy) Run(results []stage.ExecutorResult, confAny any) (
return nil, true, err
}
var res []stage.ParserResult
for _, result := range results {
res = append(res, Parse(result, *conf))
for range results {
res = append(res, stage.ParserResult{Score: conf.Score, Comment: conf.Comment})
}
return res, false, nil
}

View File

@ -0,0 +1,9 @@
package elf
import "focs.ji.sjtu.edu.cn/git/FOCS-dev/JOJ3/internal/stage"
var name = "elf"
func init() {
stage.RegisterParser(name, &Elf{})
}

View File

@ -0,0 +1,41 @@
package elf
type Toplevel struct {
Title string `json:"title"`
Modules []Module `json:"modules"`
}
type Module struct {
Entries []Entry `json:"entries"`
DebugInfo string `json:"debug_info"`
}
type Entry []any
type Report struct {
File string `json:"file"`
Name string `json:"name"`
Cases []Case `json:"cases" mapstructure:"cases"`
}
type Case struct {
Binders []Binder `mapstructure:"binders"`
Context string `mapstructure:"context"`
Depths int `mapstructure:"depths"`
Code string `mapstructure:"code"`
Plain int `mapstructure:"plain"`
Weighed float64 `mapstructure:"weighed"`
Detail string `mapstructure:"detail"`
SimilarityRate float64 `mapstructure:"similarity_rate"`
Sources []Source `mapstructure:"srcs"`
}
type Binder struct {
Binder string `json:"binder"`
Pos string `json:"pos"`
}
type Source struct {
Context string `json:"context"`
Code string `json:"code"`
}

View File

@ -0,0 +1,96 @@
package elf
import (
"encoding/json"
"fmt"
"log/slog"
"focs.ji.sjtu.edu.cn/git/FOCS-dev/JOJ3/internal/stage"
"github.com/criyle/go-judge/envexec"
"github.com/mitchellh/mapstructure"
)
type Conf struct {
Score int
Comment string
}
type Elf struct{}
func Parse(executorResult stage.ExecutorResult, conf Conf) stage.ParserResult {
stdout := executorResult.Files["stdout"]
stderr := executorResult.Files["stderr"]
if executorResult.Status != stage.Status(envexec.StatusAccepted) {
return stage.ParserResult{
Score: 0,
Comment: fmt.Sprintf(
"Unexpected executor status: %s.\nStderr: %s",
executorResult.Status, stderr,
),
}
}
var topLevel Toplevel
err := json.Unmarshal([]byte(stdout), &topLevel)
if err != nil {
return stage.ParserResult{
Score: 0,
Comment: fmt.Sprintf("Failed to parse result: %s", err),
}
}
for _, module := range topLevel.Modules {
for _, entry := range module.Entries {
kind := entry[0].(string)
report := Report{}
err := mapstructure.Decode(entry[1], &report)
if err != nil {
slog.Error("elf parse", "mapstructure decode err", err)
}
slog.Debug("elf parse", "report file", report.File)
slog.Debug("elf parse", "report name", report.Name)
slog.Debug("elf parse", "report kind", kind)
for _, caseObj := range report.Cases {
switch kind {
case "ParenDep":
slog.Debug("elf parse", "binders", caseObj.Binders)
slog.Debug("elf parse", "context", caseObj.Context)
slog.Debug("elf parse", "depths", caseObj.Depths)
slog.Debug("elf parse", "code", caseObj.Code)
case "CodeLen":
slog.Debug("elf parse", "binders", caseObj.Binders)
slog.Debug("elf parse", "context", caseObj.Context)
slog.Debug("elf parse", "plain", caseObj.Plain)
slog.Debug("elf parse", "weighed", caseObj.Weighed)
slog.Debug("elf parse", "code", caseObj.Code)
case "OverArity":
slog.Debug("elf parse", "binders", caseObj.Binders)
slog.Debug("elf parse", "context", caseObj.Context)
slog.Debug("elf parse", "detail", caseObj.Detail)
slog.Debug("elf parse", "code", caseObj.Code)
case "CodeDup":
slog.Debug("elf parse", "similarity rate", caseObj.SimilarityRate)
for _, source := range caseObj.Sources {
slog.Debug("elf parse", "context", source.Context, "code", source.Code)
}
}
}
}
}
return stage.ParserResult{
Score: conf.Score,
Comment: conf.Comment,
}
}
func (*Elf) Run(results []stage.ExecutorResult, confAny any) (
[]stage.ParserResult, bool, error,
) {
conf, err := stage.DecodeConf[Conf](confAny)
if err != nil {
return nil, true, err
}
var res []stage.ParserResult
for _, result := range results {
res = append(res, Parse(result, *conf))
}
return res, false, nil
}

View File

@ -0,0 +1,9 @@
package healthcheck
import "github.com/joint-online-judge/JOJ3/internal/stage"
var name = "healthcheck"
func init() {
stage.RegisterParser(name, &Healthcheck{})
}

View File

@ -0,0 +1,41 @@
package healthcheck
import (
"fmt"
"github.com/criyle/go-judge/envexec"
"github.com/joint-online-judge/JOJ3/internal/stage"
)
type Healthcheck struct{}
func Parse(executorResult stage.ExecutorResult) (stage.ParserResult, bool) {
stdout := executorResult.Files["stdout"]
stderr := executorResult.Files["stderr"]
if executorResult.Status != stage.Status(envexec.StatusAccepted) {
return stage.ParserResult{
Score: 0,
Comment: fmt.Sprintf(
"Unexpected executor status: %s.\nStdout: %s\nStderr: %s",
executorResult.Status, stdout, stderr,
),
}, true
}
return stage.ParserResult{
Score: 0,
Comment: stdout,
}, stdout != ""
}
func (*Healthcheck) Run(results []stage.ExecutorResult, confAny any) (
[]stage.ParserResult, bool, error,
) {
var res []stage.ParserResult
forceQuit := false
for _, result := range results {
parserResult, forceQuitResult := Parse(result)
res = append(res, parserResult)
forceQuit = forceQuit || forceQuitResult
}
return res, forceQuit, nil
}

View File

@ -1,6 +1,6 @@
package keyword
import "focs.ji.sjtu.edu.cn/git/FOCS-dev/JOJ3/internal/stage"
import "github.com/joint-online-judge/JOJ3/internal/stage"
var name = "keyword"

View File

@ -4,7 +4,7 @@ import (
"fmt"
"strings"
"focs.ji.sjtu.edu.cn/git/FOCS-dev/JOJ3/internal/stage"
"github.com/joint-online-judge/JOJ3/internal/stage"
)
type Match struct {

View File

@ -1,6 +1,6 @@
package resultstatus
import "focs.ji.sjtu.edu.cn/git/FOCS-dev/JOJ3/internal/stage"
import "github.com/joint-online-judge/JOJ3/internal/stage"
var name = "result-status"

View File

@ -3,26 +3,28 @@ package resultstatus
import (
"fmt"
"focs.ji.sjtu.edu.cn/git/FOCS-dev/JOJ3/internal/stage"
"github.com/criyle/go-judge/envexec"
"github.com/joint-online-judge/JOJ3/internal/stage"
)
type Conf struct{}
type Conf struct {
Score int
Comment string
}
type ResultStatus struct{}
func (*ResultStatus) Run(results []stage.ExecutorResult, confAny any) (
[]stage.ParserResult, bool, error,
) {
// TODO: more conf options
_, err := stage.DecodeConf[Conf](confAny)
conf, err := stage.DecodeConf[Conf](confAny)
if err != nil {
return nil, true, err
}
forceQuit := false
var res []stage.ParserResult
for _, result := range results {
comment := ""
comment := conf.Comment
if result.Status != stage.Status(envexec.StatusAccepted) {
forceQuit = true
comment = fmt.Sprintf(
@ -30,7 +32,7 @@ func (*ResultStatus) Run(results []stage.ExecutorResult, confAny any) (
)
}
res = append(res, stage.ParserResult{
Score: 0,
Score: conf.Score,
Comment: comment,
})
}

View File

@ -0,0 +1,9 @@
package sample
import "github.com/joint-online-judge/JOJ3/internal/stage"
var name = "sample"
func init() {
stage.RegisterParser(name, &Sample{})
}

View File

@ -0,0 +1,57 @@
package sample
import (
"encoding/json"
"fmt"
"github.com/criyle/go-judge/envexec"
"github.com/joint-online-judge/JOJ3/internal/stage"
"github.com/joint-online-judge/JOJ3/pkg/sample"
)
type Conf struct {
Score int
Comment string
}
type Sample struct{}
func Parse(executorResult stage.ExecutorResult, conf Conf) stage.ParserResult {
stdout := executorResult.Files["stdout"]
stderr := executorResult.Files["stderr"]
if executorResult.Status != stage.Status(envexec.StatusAccepted) {
return stage.ParserResult{
Score: 0,
Comment: fmt.Sprintf(
"Unexpected executor status: %s.\nStderr: %s",
executorResult.Status, stderr,
),
}
}
var sampleResult sample.Result
err := json.Unmarshal([]byte(stdout), &sampleResult)
if err != nil {
return stage.ParserResult{
Score: 0,
Comment: fmt.Sprintf("Failed to parse result: %s", err),
}
}
return stage.ParserResult{
Score: sampleResult.Score + conf.Score,
Comment: sampleResult.Comment + conf.Comment,
}
}
func (*Sample) Run(results []stage.ExecutorResult, confAny any) (
[]stage.ParserResult, bool, error,
) {
conf, err := stage.DecodeConf[Conf](confAny)
if err != nil {
return nil, true, err
}
var res []stage.ParserResult
for _, result := range results {
res = append(res, Parse(result, *conf))
}
return res, false, nil
}

View File

@ -1,9 +1,9 @@
package stage
import (
"encoding/json"
"fmt"
"strconv"
"time"
"github.com/criyle/go-judge/envexec"
)
@ -47,7 +47,7 @@ type Cmd struct {
CopyIn map[string]CmdFile `json:"copyIn"`
CopyInCached map[string]string `json:"copyInCached"`
CopyInCwd bool `json:"copyInCwd"`
CopyInDir string `json:"copyInDir"`
CopyOut []string `json:"copyOut"`
CopyOutCached []string `json:"copyOutCached"`
@ -126,8 +126,8 @@ func (r ExecutorResult) String() string {
Status Status
ExitStatus int
Error string
Time time.Duration
RunTime time.Duration
Time uint64
RunTime uint64
Memory envexec.Size
Files map[string]string
FileIDs map[string]string
@ -137,8 +137,8 @@ func (r ExecutorResult) String() string {
Status: r.Status,
ExitStatus: r.ExitStatus,
Error: r.Error,
Time: time.Duration(r.Time),
RunTime: time.Duration(r.RunTime),
Time: r.Time,
RunTime: r.RunTime,
Memory: envexec.Size(r.Memory),
Files: make(map[string]string),
FileIDs: r.FileIDs,
@ -163,8 +163,17 @@ type ParserResult struct {
Comment string `json:"comment"`
}
type NonNullSlice[T any] []T
func (s NonNullSlice[T]) MarshalJSON() ([]byte, error) {
if len(s) == 0 {
return []byte("[]"), nil
}
return json.Marshal([]T(s))
}
type StageResult struct {
Name string `json:"name"`
Results []ParserResult `json:"results"`
Results NonNullSlice[ParserResult] `json:"results"`
ForceQuit bool `json:"force_quit"`
}

View File

@ -1,35 +1,48 @@
package stage
import (
"fmt"
"log/slog"
)
func Run(stages []Stage) []StageResult {
stageResults := []StageResult{}
func Run(stages []Stage) (stageResults []StageResult, err error) {
var executorResults []ExecutorResult
var parserResults []ParserResult
var forceQuit bool
slog.Info("stage run start")
for _, stage := range stages {
slog.Debug("stage start", "name", stage.Name)
slog.Debug("executor run start", "cmds", stage.ExecutorCmds)
slog.Info("stage start", "name", stage.Name)
slog.Info("executor run start", "name", stage.ExecutorName)
slog.Debug("executor run start", "name", stage.ExecutorName,
"cmds", stage.ExecutorCmds)
executor, ok := executorMap[stage.ExecutorName]
if !ok {
slog.Error("executor not found", "name", stage.ExecutorName)
break
err = fmt.Errorf("executor not found: %s", stage.ExecutorName)
return
}
executorResults, err := executor.Run(stage.ExecutorCmds)
executorResults, err = executor.Run(stage.ExecutorCmds)
if err != nil {
slog.Error("executor run error", "name", stage.ExecutorName, "error", err)
break
return
}
slog.Debug("executor run done", "results", executorResults)
slog.Debug("parser run start", "conf", stage.ParserConf)
for _, executorResult := range executorResults {
slog.Debug("executor run done", "result.Files", executorResult.Files)
}
slog.Info("parser run start", "name", stage.ParserName)
slog.Debug("parser run start", "name", stage.ParserName,
"conf", stage.ParserConf)
parser, ok := parserMap[stage.ParserName]
if !ok {
slog.Error("parser not found", "name", stage.ParserName)
break
err = fmt.Errorf("parser not found: %s", stage.ParserName)
return
}
parserResults, forceQuit, err := parser.Run(executorResults, stage.ParserConf)
parserResults, forceQuit, err = parser.Run(executorResults, stage.ParserConf)
if err != nil {
slog.Error("parser run error", "name", stage.ExecutorName, "error", err)
break
slog.Error("parser run error", "name", stage.ParserName, "error", err)
return
}
slog.Debug("parser run done", "results", parserResults)
stageResults = append(stageResults, StageResult{
@ -38,19 +51,19 @@ func Run(stages []Stage) []StageResult {
ForceQuit: forceQuit,
})
if forceQuit {
break
slog.Error("parser force quit", "name", stage.ParserName)
return
}
}
return stageResults
return
}
func Cleanup() {
slog.Info("stage cleanup start")
for name, executor := range executorMap {
slog.Debug("executor cleanup start", "name", name)
err := executor.Cleanup()
if err != nil {
slog.Error("executor cleanup error", "name", name, "error", err)
}
slog.Debug("executor cleanup done", "name", name)
}
}

View File

@ -1,12 +1,16 @@
package stage
import "github.com/mitchellh/mapstructure"
import (
"fmt"
"github.com/mitchellh/mapstructure"
)
func DecodeConf[T any](confAny any) (*T, error) {
var conf T
err := mapstructure.Decode(confAny, &conf)
if err != nil {
return nil, err
return nil, fmt.Errorf("failed to decode conf: %w", err)
}
return &conf, nil
}

76
pkg/healthcheck/commit.go Normal file
View File

@ -0,0 +1,76 @@
package healthcheck
import (
"fmt"
"log/slog"
"strings"
"unicode"
"github.com/go-git/go-git/v5"
)
func checkMsg(msg string) bool {
// List of prefixes to ignore in the commit message
ignoredPrefixes := []string{
"Co-authored-by:",
"Reviewed-by:",
"Co-committed-by:",
"Reviewed-on:",
}
// Split message by lines and ignore specific lines with prefixes
lines := strings.Split(msg, "\n")
for i, line := range lines {
trimmedLine := strings.TrimSpace(line)
ignore := false
if i != 0 {
for _, prefix := range ignoredPrefixes {
if strings.HasPrefix(trimmedLine, prefix) {
ignore = true
break
}
}
}
if ignore {
continue
}
// Check for non-ASCII characters in the rest of the lines
for _, c := range line {
if c > unicode.MaxASCII {
return false
}
}
}
return true
}
// nonAsciiMsg checks for non-ASCII characters in the commit message.
// It iterates over each character in the message and checks if it is a non-ASCII character.
// If a non-ASCII character is found, it returns an error indicating not to use non-ASCII characters in commit messages.
// Otherwise, it returns nil indicating that the commit message is valid.
// It skips the non-ASCII characters check for lines starting with specific keywords like "Co-authored-by", "Reviewed-by", and "Co-committed-by".
func NonAsciiMsg(root string) error {
repo, err := git.PlainOpen(root)
if err != nil {
slog.Error("opening git repo", "err", err)
return fmt.Errorf("error opening git repo: %v", err)
}
ref, err := repo.Head()
if err != nil {
slog.Error("getting reference", "err", err)
return fmt.Errorf("error getting reference: %v", err)
}
commit, err := repo.CommitObject(ref.Hash())
if err != nil {
slog.Error("getting latest commit", "err", err)
return fmt.Errorf("error getting latest commit: %v", err)
}
msg := commit.Message
if !checkMsg(msg) {
return fmt.Errorf("Non-ASCII characters in commit messages:\n%s", msg)
}
return nil
}

View File

@ -0,0 +1,83 @@
package healthcheck
import (
"testing"
)
func TestCheckMsg(t *testing.T) {
tests := []struct {
name string
message string
expected bool
}{
{
name: "Valid ASCII message",
message: "This is a valid commit message",
expected: true,
},
{
name: "Message with non-ASCII character",
message: "This message contains a non-ASCII character: é",
expected: false,
},
{
name: "Message with ignored prefix",
message: "First line\nCo-authored-by: John Doe <john@example.com>\nThis is a valid message",
expected: true,
},
{
name: "Message with ignored prefix and non-ASCII character in content",
message: "First line\nCo-authored-by: John Doe <john@example.com>\nThis message has a non-ASCII character: ñ",
expected: false,
},
{
name: "Message with ignored prefix in the first line",
message: "Co-authored-by: Jöhn Döe <john@example.com>",
expected: false,
},
{
name: "Multi-line message with all valid ASCII",
message: "First line\nSecond line\nThird line",
expected: true,
},
{
name: "Multi-line message with non-ASCII in middle",
message: "First line\nSecond line with ö\nThird line",
expected: false,
},
{
name: "Multi-line message with non-ASCII in the first line",
message: "First line with ö\nSecond line",
expected: false,
},
{
name: "Message with multiple ignored prefixes",
message: "First line\nCo-authored-by: John Doe <john@example.com>\nReviewed-by: Jane Smith <jane@example.com>\nValid content",
expected: true,
},
{
name: "Empty message",
message: "",
expected: true,
},
{
name: "Message with only whitespace",
message: " \n \t ",
expected: true,
},
{
name: "Message with non-ASCII after ignored prefix",
message: "First line\nCo-authored-by: John Doe <john@example.com>\nReviewed-by: Jöhn Döe <john@example.com>",
expected: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := checkMsg(tt.message)
if result != tt.expected {
t.Errorf("checkMsg(%q) = %v, want %v", tt.message, result, tt.expected)
}
})
}
}

View File

@ -0,0 +1,90 @@
package healthcheck
import (
"bufio"
"fmt"
"log/slog"
"os"
"path/filepath"
"regexp"
"strings"
)
// getForbiddens retrieves a list of forbidden files in the specified root directory.
// It searches for files that do not match the specified regex patterns in the given file list.
func getForbiddens(root string, fileList []string, localList string) ([]string, error) {
var matches []string
var regexList []*regexp.Regexp
regexList, err := getRegex(fileList)
if err != nil {
return nil, err
}
var dirs []string
if localList != "" {
file, err := os.Open(localList)
if err != nil {
return nil, fmt.Errorf("Failed to open file %s: %v\n", localList, err)
}
defer file.Close()
scanner := bufio.NewScanner(file)
for scanner.Scan() {
dirs = append(dirs, scanner.Text())
}
if err := scanner.Err(); err != nil {
return nil, fmt.Errorf("Error reading file %s: %v\n", localList, err)
}
}
err = filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if info.IsDir() {
if info.Name() == ".git" || info.Name() == ".gitea" || info.Name() == "ci" || (localList != "" && inString(info.Name(), dirs)) {
return filepath.SkipDir
}
} else {
match := false
for _, regex := range regexList {
if regex.MatchString(info.Name()) {
match = true
break
}
}
if !match {
matches = append(matches, path)
}
}
return nil
})
return matches, err
}
// forbiddenCheck checks for forbidden files in the specified root directory.
// It prints the list of forbidden files found, along with instructions on how to fix them.
func ForbiddenCheck(rootDir string, regexList []string, localList string, repo string, droneBranch string) error {
forbids, err := getForbiddens(rootDir, regexList, localList)
if err != nil {
slog.Error("getting forbiddens", "error", err)
return fmt.Errorf("error getting forbiddens: %w", err)
}
if len(forbids) > 0 {
return fmt.Errorf("The following forbidden files were found: %s\n\nTo fix it, first make a backup of your repository and then run the following commands:\nfor i in %s%s",
strings.Join(forbids, ", "),
strings.Join(forbids, " "),
fmt.Sprint(
"; do git filter-repo --force --invert-paths --path \"$i\"; done\ngit remote add origin ",
repo, "\ngit push --set-upstream origin ",
droneBranch, " --force"))
}
return nil
}

View File

@ -1 +0,0 @@
package healthcheck

73
pkg/healthcheck/meta.go Normal file
View File

@ -0,0 +1,73 @@
package healthcheck
import (
"fmt"
"log/slog"
"os"
)
// getMetas retrieves a list of metadata files that are expected to exist in the specified root directory.
// It checks for the existence of each file in the fileList and provides instructions if any file is missing.
func getMetas(rootDir string, fileList []string) ([]string, string, error) {
addExt(fileList, "\\.*")
regexList, err := getRegex(fileList)
var unmatchedList []string
if err != nil {
return nil, "", err
}
files, err := os.ReadDir(rootDir)
if err != nil {
return nil, "", fmt.Errorf("error reading directory: %w", err)
}
matched := false
umatchedRes := ""
// TODO: it seems that there is no good find substitution now
// modify current code if exist a better solution
for i, regex := range regexList {
for _, file := range files {
if file.IsDir() {
continue
}
if regex.MatchString(file.Name()) {
matched = true
break
}
}
if !matched {
unmatchedList = append(unmatchedList, fileList[i])
str := fmt.Sprint("\tno ", fileList[i], " file found")
switch fileList[i] {
case "readme\\.*":
str += ", please refer to https://www.makeareadme.com/ for more information"
case "changelog\\.*":
str += ", please refer to https://keepachangelog.com/en/1.1.0/ for more information"
default:
str += ""
}
str += "\n"
umatchedRes += str
}
}
return unmatchedList, umatchedRes, nil
}
// metaCheck performs a check for metadata files in the specified root directory.
// It prints a message if any required metadata files are missing.
func MetaCheck(rootDir string, fileList []string) error {
unmatchedList, umatchedRes, err := getMetas(rootDir, fileList)
if err != nil {
slog.Error("getting metas", "err", err)
return fmt.Errorf("error getting metas: %w", err)
}
if len(unmatchedList) != 0 {
return fmt.Errorf("%d important project files missing\n"+umatchedRes, len(unmatchedList))
}
return nil
}

View File

@ -0,0 +1,93 @@
package healthcheck
import (
"bufio"
"fmt"
"log/slog"
"os"
"path/filepath"
"strings"
"unicode"
)
// getNonAscii retrieves a list of files in the specified root directory that contain non-ASCII characters.
// It searches for non-ASCII characters in each file's content and returns a list of paths to files containing non-ASCII characters.
func getNonAscii(root string, localList string) ([]string, error) {
var nonAscii []string
var dirs []string
if localList != "" {
file, err := os.Open(localList)
if err != nil {
return nil, fmt.Errorf("Failed to open file %s: %v\n", localList, err)
}
defer file.Close()
scanner := bufio.NewScanner(file)
for scanner.Scan() {
dirs = append(dirs, scanner.Text())
}
if err := scanner.Err(); err != nil {
return nil, fmt.Errorf("Error reading file %s: %v\n", localList, err)
}
}
err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if info.IsDir() {
if info.Name() == ".git" || info.Name() == ".gitea" || info.Name() == "ci" || (localList != "" && inString(info.Name(), dirs)) {
return filepath.SkipDir
} else {
return nil
}
}
if info.Name() == "healthcheck" {
return nil
}
file, err := os.Open(path)
if err != nil {
return err
}
defer file.Close()
scanner := bufio.NewScanner(file)
for scanner.Scan() {
cont := true
for _, c := range scanner.Text() {
if c > unicode.MaxASCII {
nonAscii = append(nonAscii, "\t"+path)
cont = false
break
}
}
if !cont {
break
}
}
return nil
})
return nonAscii, err
}
// nonAsciiFiles checks for non-ASCII characters in files within the specified root directory.
// It prints a message with the paths to files containing non-ASCII characters, if any.
func NonAsciiFiles(root string, localList string) error {
nonAscii, err := getNonAscii(root, localList)
if err != nil {
slog.Error("getting non-ascii", "err", err)
return fmt.Errorf("error getting non-ascii: %w", err)
}
if len(nonAscii) > 0 {
return fmt.Errorf("Non-ASCII characters found in the following files:\n%s",
strings.Join(nonAscii, "\n"))
}
return nil
}

View File

@ -0,0 +1,40 @@
package healthcheck
import (
"fmt"
"log/slog"
"os/exec"
"strconv"
"strings"
)
// RepoSize checks the size of the repository to determine if it is oversized.
// It executes the 'git count-objects -v' command to obtain the size information,
func RepoSize() error {
// TODO: reimplement here when go-git is available
// https://github.com/go-git/go-git/blob/master/COMPATIBILITY.md
cmd := exec.Command("git", "count-objects", "-v")
output, err := cmd.CombinedOutput()
if err != nil {
slog.Error("running git command:", "err", err)
return fmt.Errorf("error running git command: %w", err)
}
lines := strings.Split(string(output), "\n")
var sum int
for _, line := range lines {
if strings.Contains(line, "size") {
fields := strings.Fields(line)
sizeStr := fields[1]
size, err := strconv.Atoi(sizeStr)
if err != nil {
slog.Error("running git command:", "err", err)
return fmt.Errorf("error running git command: %w", err)
}
sum += size
}
}
if sum > 2048 {
return fmt.Errorf("Repository larger than 2MB. Please clean up or contact the teaching team.")
}
return nil
}

66
pkg/healthcheck/tag.go Normal file
View File

@ -0,0 +1,66 @@
package healthcheck
import (
"fmt"
"strings"
"github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/plumbing"
)
func getTagsFromRepo(repoPath string) ([]string, error) {
repo, err := git.PlainOpen(repoPath)
if err != nil {
return nil, fmt.Errorf("error opening repo: %v", err)
}
refs, err := repo.Tags()
if err != nil {
return nil, fmt.Errorf("error getting tags: %v", err)
}
var tags []string
err = refs.ForEach(func(ref *plumbing.Reference) error {
tags = append(tags, ref.Name().Short())
return nil
})
if err != nil {
return nil, fmt.Errorf("error iterating tags: %v", err)
}
return tags, nil
}
func CheckTags(repoPath string, category string, n int) error {
// INFO: if category not specified, skipping this check by default
if category == "" {
return nil
}
tags, err := getTagsFromRepo(repoPath)
if err != nil {
return fmt.Errorf("error getting tags: %v", err)
}
var prefix string
switch category {
case "exam":
prefix = "e"
case "project":
prefix = "p"
case "homework":
prefix = "h"
default:
prefix = "a"
}
target := prefix + fmt.Sprintf("%d", n)
found := false
for _, tag := range tags {
if tag == target {
found = true
break
}
}
if !found {
return fmt.Errorf("Wrong release tag '%s' or missing release tags. Please use one of '%s'.", strings.Join(tags, "', '"), target)
}
return nil
}

38
pkg/healthcheck/utils.go Normal file
View File

@ -0,0 +1,38 @@
package healthcheck
import (
"fmt"
"regexp"
)
func inString(str1 string, strList []string) bool {
for _, str := range strList {
if str1 == str {
return true
}
}
return false
}
// addExt appends the specified extension to each file name in the given fileList.
// It modifies the original fileList in place.
func addExt(fileList []string, ext string) {
for i, file := range fileList {
fileList[i] = file + ext
}
}
// getRegex compiles each regex pattern in the fileList into a []*regexp.Regexp slice.
// It returns a slice containing compiled regular expressions.
func getRegex(fileList []string) ([]*regexp.Regexp, error) {
var regexList []*regexp.Regexp
for _, pattern := range fileList {
regex, err := regexp.Compile("(?i)" + pattern)
if err != nil {
return nil, fmt.Errorf("Error compiling regex:%w", err)
}
regexList = append(regexList, regex)
}
return regexList, nil
}

63
pkg/healthcheck/verify.go Normal file
View File

@ -0,0 +1,63 @@
package healthcheck
import (
"crypto/sha256"
"encoding/hex"
"fmt"
"io"
"os"
"path/filepath"
"strings"
)
// getChecksum calculates the SHA-256 checksum of a file
func getChecksum(filePath string) (string, error) {
// Open the file
file, err := os.Open(filePath)
if err != nil {
return "", err
}
defer file.Close()
// Calculate SHA-256
hash := sha256.New()
if _, err := io.Copy(hash, file); err != nil {
return "", err
}
return hex.EncodeToString(hash.Sum(nil)), nil
}
// checkFileChecksum checks if a single file's checksum matches the expected value
func checkFileChecksum(rootDir, fileName, expectedChecksum string) error {
filePath := filepath.Join(rootDir, strings.TrimSpace(fileName))
actualChecksum, err := getChecksum(filePath)
if err != nil {
return fmt.Errorf("Error reading file %s: %v", filePath, err)
}
if actualChecksum != expectedChecksum {
return fmt.Errorf("Checksum for %s failed. Expected %s, but got %s. Please revert your changes or contact the teaching team if you have a valid reason for adjusting them.", filePath, expectedChecksum, actualChecksum)
}
return nil
}
func VerifyFiles(rootDir string, checkFileNameList string, checkFileSumList string) error {
if len(checkFileNameList) == 0 {
return nil
}
fileNames := strings.Split(checkFileNameList, ",")
checkSums := strings.Split(checkFileSumList, ",")
// Check if the number of files matches the number of checksums
if len(fileNames) != len(checkSums) {
return fmt.Errorf("Error: The number of files and checksums do not match.")
}
// Check each file's checksum
for i, fileName := range fileNames {
expectedChecksum := strings.TrimSpace(checkSums[i])
err := checkFileChecksum(rootDir, fileName, expectedChecksum)
if err != nil {
return err
}
}
return nil
}

View File

@ -1,4 +1,4 @@
package dummy
package sample
import (
"fmt"
@ -19,10 +19,10 @@ func Run(conf Conf) (res Result, err error) {
// the sandbox, the logs will not show in drone output directly.
// If there are more kinds of errors need to be handled separately, add
// more fields in the Result struct, don't mess everything up in Stderr.
err = fmt.Errorf("dummy negative score: %d", conf.Score)
err = fmt.Errorf("sample negative score: %d", conf.Score)
return
}
res.Score = conf.Score
res.Comment = "dummy comment"
res.Comment = "sample comment"
return
}

View File

@ -1,6 +1,6 @@
#!/usr/bin/env bash
set -ex
set -e
declare -A repo_names
tmp_dir=${1:-./tmp}
submodules_dir="$tmp_dir/submodules"
@ -18,14 +18,14 @@ for submodule in $submodules; do
else
cd $repo_dir
git fetch --all
cd -
cd - > /dev/null
fi
fi
repo_names[$repo_name]=1
cd $repo_dir
git checkout -q $branch
git reset -q --hard origin/$branch
cd -
cd - > /dev/null
submodule_dir="$submodules_dir/$repo_name/$submodule"
mkdir -p $submodule_dir
cp -rT $repo_dir $submodule_dir

View File

@ -0,0 +1,21 @@
#!/usr/bin/env bash
set -e
tmp_dir=${1:-./tmp}
JOJ3=$(git rev-parse --show-toplevel)/build/joj3
command=${2:-$JOJ3}
submodules_dir="$tmp_dir/submodules"
submodules=$(git config --file .gitmodules --get-regexp path | awk '{ print $2 }')
for submodule in $submodules; do
url=$(git config --file .gitmodules --get-regexp "submodule.$submodule.url" | awk '{ print $2 }')
repo_name=$(echo $url | rev | cut -d'/' -f 1 | rev | cut -d'.' -f 1)
submodule_dir="$submodules_dir/$repo_name/$submodule"
cd $submodule_dir
eval "$command"
if [[ $command == $JOJ3 ]]; then
if [ -f "./expected.json" ]; then
mv -f "joj3_result.json" "expected.json"
fi
fi
cd - > /dev/null
done