Compare commits
94 Commits
6798c2a19a
...
06ce9ad92b
Author | SHA1 | Date | |
---|---|---|---|
06ce9ad92b | |||
d5454a1abd | |||
4a56cd81aa | |||
dee296143f | |||
5c2fe4ffcb | |||
64ae632003 | |||
f5ba04b8e7 | |||
d723d98de0 | |||
7cc6747a25 | |||
5e4786c3b5 | |||
4666eb7375 | |||
02d0f49070 | |||
049a5e6e7e | |||
28bf70d0a9 | |||
914ad6c7e3 | |||
18b7193bc0 | |||
2029cc8ae9 | |||
6456a64bca | |||
d8ed5809cb | |||
79446e7826 | |||
0826a3d14a | |||
ff8f700506 | |||
198d329c8e | |||
43a799e958 | |||
fa2d98007e | |||
fc68f7bcb4 | |||
5038cf118d | |||
0471834bca | |||
a16418f3a6 | |||
16cbf4c1ef | |||
97556cf220 | |||
b40072a485 | |||
2a55fe1c3d | |||
2b5769545d | |||
a9a829da3f | |||
4c00904f25 | |||
e2bf6d3cf1 | |||
dd8dc68eeb | |||
c13b3465c1 | |||
a5a4a649dc | |||
3682a36fde | |||
7b3edb2361 | |||
6c8332af29 | |||
7ad3a3ab1f | |||
03065b0b69 | |||
9800becc96 | |||
f665e5b2b1 | |||
7ce4d29d0e | |||
6af7be6cb1 | |||
863e562743 | |||
4b4c83a609 | |||
5893b54054 | |||
1bb0ddd253 | |||
3a8e3dfcf4 | |||
d172fae912 | |||
8992d9df7a | |||
a66dbc29e4 | |||
b7a1ad0833 | |||
5cf3d5f9ce | |||
9765ea1007 | |||
5083b02a6f | |||
9e5c933fbd | |||
61dada5c40 | |||
2291264df0 | |||
d597c73dd2 | |||
1253404c49 | |||
d03927457a | |||
d5a96515ec | |||
3cc96821ad | |||
5922d795b3 | |||
967a859c9f | |||
3ccdd394b2 | |||
5a860f1203 | |||
52491478a4 | |||
eac7a62ebe | |||
![]() |
2c742852fb | ||
776de48d03 | |||
![]() |
fdf84546da | ||
![]() |
1d90d72978 | ||
![]() |
6fbbe7ece0 | ||
![]() |
96c71bcd63 | ||
![]() |
756a531bac | ||
![]() |
fd69aebac5 | ||
9f0e9bf9f3 | |||
2928e6321f | |||
f81581b928 | |||
27a2aac537 | |||
e4091f9ba4 | |||
739069a18f | |||
97f89d210b | |||
b608a22cfd | |||
2f8604f647 | |||
c5d31d9a1a | |||
9445f93e44 |
31
.drone.yml
31
.drone.yml
|
@ -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
|
65
.gitea/workflows/build.yaml
Normal file
65
.gitea/workflows/build.yaml
Normal 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
66
.gitmodules
vendored
|
@ -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
|
||||
|
|
|
@ -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
21
LICENSE
Normal 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.
|
13
Makefile
13
Makefile
|
@ -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
120
README.md
|
@ -1,5 +1,7 @@
|
|||
# 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
89
cmd/healthcheck/main.go
Normal 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())
|
||||
}
|
||||
}
|
136
cmd/joj3/conf.go
136
cmd/joj3/conf.go
|
@ -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"`
|
||||
OutputPath string `default:"joj3_result.json"`
|
||||
Stages []struct {
|
||||
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
86
cmd/joj3/conf_test.go
Normal 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
77
cmd/joj3/log.go
Normal 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
|
||||
}
|
127
cmd/joj3/main.go
127
cmd/joj3/main.go
|
@ -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("get commit msg", "error", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
conf, group, err := parseMsg(confRoot, confName, msg)
|
||||
if err != nil {
|
||||
slog.Error("no conf found", "error", err)
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
1
examples/diff/basic
Submodule
|
@ -0,0 +1 @@
|
|||
Subproject commit cc5d991cb401b0c8cfd8e03a76fb9994158d2819
|
1
examples/diff/complex
Submodule
1
examples/diff/complex
Submodule
|
@ -0,0 +1 @@
|
|||
Subproject commit f29fbdac51f851e782f0db13f8640c6ba0aec49d
|
|
@ -1 +0,0 @@
|
|||
Subproject commit 9cf0d994aa613dc38f2bc99f42d0dd7509c3d42c
|
|
@ -1 +0,0 @@
|
|||
Subproject commit c2fe51b4cd485339fd18924196b01eed6848ba1d
|
1
examples/healthcheck/asciifile
Submodule
1
examples/healthcheck/asciifile
Submodule
|
@ -0,0 +1 @@
|
|||
Subproject commit 146b2f829a91594c89f1d1379d312b91c5f97a55
|
1
examples/healthcheck/asciimsg
Submodule
1
examples/healthcheck/asciimsg
Submodule
|
@ -0,0 +1 @@
|
|||
Subproject commit ffd9ab13c7e403d0afc7f1dfc86ec131f9992498
|
1
examples/healthcheck/forbiddenfile
Submodule
1
examples/healthcheck/forbiddenfile
Submodule
|
@ -0,0 +1 @@
|
|||
Subproject commit b93a0254c2842e4d9c3a4b8f832cd6b17bf284f9
|
1
examples/healthcheck/meta
Submodule
1
examples/healthcheck/meta
Submodule
|
@ -0,0 +1 @@
|
|||
Subproject commit b4693716ee97bd5d9814c512b036aabeb12c7fd6
|
1
examples/healthcheck/release
Submodule
1
examples/healthcheck/release
Submodule
|
@ -0,0 +1 @@
|
|||
Subproject commit 50ec9ccbb6d7fe9b5352a018b6ac6445ac5c354f
|
1
examples/healthcheck/reposize
Submodule
1
examples/healthcheck/reposize
Submodule
|
@ -0,0 +1 @@
|
|||
Subproject commit 9c010a0f8d501223f94d677a4aed70f0162092e2
|
1
examples/healthcheck/repoverify
Submodule
1
examples/healthcheck/repoverify
Submodule
|
@ -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
1
examples/sample/error
Submodule
|
@ -0,0 +1 @@
|
|||
Subproject commit 31b319157fb221636fcdf8d6a752a4a5e46d6a6d
|
1
examples/sample/success
Submodule
1
examples/sample/success
Submodule
|
@ -0,0 +1 @@
|
|||
Subproject commit 7a98095a322419939bffa8636207090493108a3c
|
39
go.mod
39
go.mod
|
@ -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
87
go.sum
|
@ -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=
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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"
|
||||
|
||||
|
|
|
@ -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}}}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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),
|
||||
})
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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"
|
||||
|
||||
|
|
|
@ -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{}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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"
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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"
|
||||
|
||||
|
|
|
@ -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 {
|
||||
Score int
|
||||
StdoutPath string
|
||||
IgnoreResultStatus bool
|
||||
Outputs []struct {
|
||||
Score int
|
||||
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)
|
||||
if err != nil {
|
||||
return nil, true, err
|
||||
}
|
||||
// TODO: more compare strategies
|
||||
if string(stdout) == result.Files["stdout"] {
|
||||
score = caseConf.Score
|
||||
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
|
||||
}
|
||||
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,
|
||||
),
|
||||
Score: score,
|
||||
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()
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
9
internal/parsers/healthcheck/meta.go
Normal file
9
internal/parsers/healthcheck/meta.go
Normal 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{})
|
||||
}
|
41
internal/parsers/healthcheck/parser.go
Normal file
41
internal/parsers/healthcheck/parser.go
Normal 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
|
||||
}
|
|
@ -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"
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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"
|
||||
|
||||
|
|
|
@ -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,
|
||||
})
|
||||
}
|
||||
|
|
9
internal/parsers/sample/meta.go
Normal file
9
internal/parsers/sample/meta.go
Normal 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{})
|
||||
}
|
57
internal/parsers/sample/parser.go
Normal file
57
internal/parsers/sample/parser.go
Normal 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
|
||||
}
|
|
@ -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 StageResult struct {
|
||||
Name string `json:"name"`
|
||||
Results []ParserResult `json:"results"`
|
||||
ForceQuit bool `json:"force_quit"`
|
||||
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 NonNullSlice[ParserResult] `json:"results"`
|
||||
ForceQuit bool `json:"force_quit"`
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
76
pkg/healthcheck/commit.go
Normal 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
|
||||
}
|
83
pkg/healthcheck/commit_test.go
Normal file
83
pkg/healthcheck/commit_test.go
Normal 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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
90
pkg/healthcheck/forbidden.go
Normal file
90
pkg/healthcheck/forbidden.go
Normal 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
|
||||
}
|
|
@ -1 +0,0 @@
|
|||
package healthcheck
|
73
pkg/healthcheck/meta.go
Normal file
73
pkg/healthcheck/meta.go
Normal 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
|
||||
}
|
93
pkg/healthcheck/nonascii.go
Normal file
93
pkg/healthcheck/nonascii.go
Normal 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
|
||||
}
|
40
pkg/healthcheck/reposize.go
Normal file
40
pkg/healthcheck/reposize.go
Normal 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
66
pkg/healthcheck/tag.go
Normal 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
38
pkg/healthcheck/utils.go
Normal 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
63
pkg/healthcheck/verify.go
Normal 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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
|
|
21
scripts/run_foreach_test_repos.sh
Executable file
21
scripts/run_foreach_test_repos.sh
Executable 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
|
Loading…
Reference in New Issue
Block a user