feat: merging from master
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing

This commit is contained in:
张佳澈520370910044 2024-05-05 17:55:18 +08:00
commit 668676b63f
32 changed files with 331 additions and 2296 deletions

View File

@ -17,6 +17,8 @@ steps:
- env - env
- go version - go version
- go env - go env
- git status -v
- git log -1
- name: build - name: build
commands: commands:
- make - make
@ -25,6 +27,7 @@ steps:
CONF_GITEATOKEN: CONF_GITEATOKEN:
from_secret: gitea-token from_secret: gitea-token
commands: commands:
- make prepare-test
- make test - make test
- name: store - name: store
commands: commands:

1
.gitignore vendored
View File

@ -123,3 +123,4 @@ $RECYCLE.BIN/
# Custom rules (everything added below won't be overriden by 'Generate .gitignore File' if you use 'Update' option) # Custom rules (everything added below won't be overriden by 'Generate .gitignore File' if you use 'Update' option)
build/ build/
!examples/**/*.out

28
.gitmodules vendored Normal file
View File

@ -0,0 +1,28 @@
[submodule "examples/cpplint/sillycode"]
path = examples/cpplint/sillycode
url = ssh://git@focs.ji.sjtu.edu.cn:2222/FOCS-dev/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
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
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/keyword/cpplint/sillycode"]
path = examples/keyword/cpplint/sillycode
url = ssh://git@focs.ji.sjtu.edu.cn:2222/FOCS-dev/JOJ3-examples.git
branch = keyword/cpplint/sillycode
[submodule "examples/keyword/clang-tidy/sillycode"]
path = examples/keyword/clang-tidy/sillycode
url = ssh://git@focs.ji.sjtu.edu.cn:2222/FOCS-dev/JOJ3-examples.git
branch = keyword/clang-tidy/sillycode

View File

@ -11,5 +11,8 @@ clean:
rm -rf $(BUILD_DIR)/* rm -rf $(BUILD_DIR)/*
rm -rf *.out rm -rf *.out
prepare-test:
git submodule update --init --remote
test: test:
go test -coverprofile cover.out -v ./... go test -coverprofile cover.out -v ./...

101
README.md
View File

@ -2,60 +2,97 @@
## Quick Start ## Quick Start
To register the sandbox executor, you need to run [`go-judge`](https://github.com/criyle/go-judge) before running this program. Install `go-judge` and start it with `go-judge -enable-grpc` 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).
2. Install [Go](https://go.dev/doc/install). Also, make sure `make` and `git` are installed and all 3 programs are presented in `$PATH`.
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
```
6. Pull submodules. It might be slow, so only run it when necessary.
```bash
$ # make sure you are in JOJ3 directory
$ make prepare-test
```
7. Build binaries in `/cmd`.
```bash
$ make
```
8. Check the functions of `joj3` with the `make test`, which should pass all the test cases. The cases used here are in `/examples`.
Note: you may fail the test if the checking tools are not installed. e.g. For the test case `cpplint/sillycode`, you need to install `cpplint` in `/usr/bin` or `/usr/local/bin`.
```bash ```bash
$ make test $ make test
go test -v ./... go test -coverprofile cover.out -v ./...
? focs.ji.sjtu.edu.cn/git/FOCS-dev/JOJ3/cmd/dummy [no test files] 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/executors [no test files]
? focs.ji.sjtu.edu.cn/git/FOCS-dev/JOJ3/internal/executors/dummy [no test files]
? focs.ji.sjtu.edu.cn/git/FOCS-dev/JOJ3/internal/parsers/dummy [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/internal/parsers [no test files]
? focs.ji.sjtu.edu.cn/git/FOCS-dev/JOJ3/internal/parsers/diff [no test files]
? focs.ji.sjtu.edu.cn/git/FOCS-dev/JOJ3/internal/executors/sandbox [no test files]
? focs.ji.sjtu.edu.cn/git/FOCS-dev/JOJ3/internal/parsers/resultstatus [no test files]
? focs.ji.sjtu.edu.cn/git/FOCS-dev/JOJ3/internal/stage [no test files]
? focs.ji.sjtu.edu.cn/git/FOCS-dev/JOJ3/pkg/dummy [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/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
=== RUN TestMain/success === RUN TestMain/compile/error
main_test.go:96: stageResults: [{Name:compile Results:[{Score:0 Comment:}]} {Name:run Results:[{Score:100 Comment:executor status: run time: 1910200 ns, memory: 13529088 bytes} {Score:100 Comment:executor status: run time: 1703000 ns, memory: 15536128 bytes}]}] === RUN TestMain/compile/success
=== RUN TestMain/compile_error === RUN TestMain/cpplint/sillycode
main_test.go:96: stageResults: [{Name:compile Results:[{Score:0 Comment:Unexpected executor status: Nonzero Exit Status.}]}] === RUN TestMain/dummy/error
=== RUN TestMain/dummy === RUN TestMain/dummy/success
main_test.go:96: stageResults: [{Name:dummy Results:[{Score:110 Comment:dummy comment + comment from toml conf}]}] === RUN TestMain/keyword/clang-tidy/sillycode
=== RUN TestMain/dummy_error === RUN TestMain/keyword/cpplint/sillycode
main_test.go:96: stageResults: [{Name:dummy Results:[{Score:0 Comment:Unexpected executor status: Nonzero Exit Status. --- PASS: TestMain (2.28s)
Stderr: dummy negative score: -1}]}] --- PASS: TestMain/compile/error (0.03s)
--- PASS: TestMain (0.29s) --- PASS: TestMain/compile/success (0.42s)
--- PASS: TestMain/success (0.27s) --- PASS: TestMain/cpplint/sillycode (0.14s)
--- PASS: TestMain/compile_error (0.01s) --- PASS: TestMain/dummy/error (0.01s)
--- PASS: TestMain/dummy (0.01s) --- PASS: TestMain/dummy/success (0.01s)
--- PASS: TestMain/dummy_error (0.01s) --- PASS: TestMain/keyword/clang-tidy/sillycode (1.57s)
--- PASS: TestMain/keyword/cpplint/sillycode (0.11s)
PASS PASS
ok focs.ji.sjtu.edu.cn/git/FOCS-dev/JOJ3/cmd/joj3 0.295s coverage: 74.0% of statements
ok focs.ji.sjtu.edu.cn/git/FOCS-dev/JOJ3/cmd/joj3 2.290s coverage: 74.0% of statements
``` ```
### For developers ### For developers
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), [`goimports`](https://golang.org/x/tools/cmd/goimports), [`gofumpt`](https://github.com/mvdan/gofumpt).
Then install the pre-commit hooks. It will run some checks before you commit.
2. Install the pre-commit hooks. It will run some checks before you commit.
```bash ```bash
$ pre-commit install $ pre-commit install
pre-commit installed at .git/hooks/pre-commit pre-commit installed at .git/hooks/pre-commit
``` ```
3. You only need to run steps 5, 7, and 8 in the quick start during development. If the test cases need to be updated, step 6 is also needed.
## Models ## Models
The program parses the TOML file to run multiple stages.
Each stage contains an executor and parser. The program parses the configuration file to run multiple stages. It can create an issue on Gitea to report the result of each stage after all stages are done.
Executor takes a `Cmd` and returns an `ExecutorResult`. Each stage contains an executor and parser. An executor just executes a command and returns the original result (stdout, stderr, output files). We can limit the time and memory used by each command in the executor. We run all kinds of commands in executors of different stages, including code formatting, static check, compilation, and execution. A parser takes the result and the configuration of the stage to parse the result and return the score and comment. e.g. If in the current stage, the executor runs a `clang-tidy` command, then we can use the clang-tidy parser in the configuration file to parse the stdout of the executor result and check whether some of the rules are followed. We can deduct the score and add some comments based on the result, and return the score and comment as the output of this stage. This stage ends here and the next stage starts.
Parser takes an `ExecutorResult` and its conf and returns a `ParserResult` and `bool` to indicate whether we should skip the rest stages. In codes, an executor takes a `Cmd` and returns an `ExecutorResult`, while a parser takes an `ExecutorResult` and its conf and returns a `ParserResult` and `bool` to indicate whether we should skip the rest stages.
### `Cmd` ### `Cmd`

View File

@ -89,7 +89,7 @@ func commitMsgToConf() (conf Conf, err error) {
return return
} }
msg := commit.Message msg := commit.Message
slog.Info("commit msg to conf", "msg", msg) slog.Debug("commit msg to conf", "msg", msg)
// TODO: parse msg to conf name // TODO: parse msg to conf name
conf = parseConfFile("conf.toml") conf = parseConfFile("conf.toml")
return return

View File

@ -2,7 +2,6 @@ package main
import ( import (
"encoding/json" "encoding/json"
"errors"
"log/slog" "log/slog"
"os" "os"
@ -10,7 +9,6 @@ import (
_ "focs.ji.sjtu.edu.cn/git/FOCS-dev/JOJ3/internal/parsers" _ "focs.ji.sjtu.edu.cn/git/FOCS-dev/JOJ3/internal/parsers"
"focs.ji.sjtu.edu.cn/git/FOCS-dev/JOJ3/internal/stage" "focs.ji.sjtu.edu.cn/git/FOCS-dev/JOJ3/internal/stage"
"github.com/go-git/go-git/v5"
"github.com/jinzhu/copier" "github.com/jinzhu/copier"
) )
@ -75,13 +73,8 @@ func outputResult(conf Conf, results []stage.StageResult) error {
func main() { func main() {
conf, err := commitMsgToConf() conf, err := commitMsgToConf()
if err != nil { if err != nil {
// FIXME: just for local testing purpose slog.Error("no conf found", "error", err)
if errors.Is(err, git.ErrRepositoryNotExists) { os.Exit(1)
conf = parseConfFile("conf.toml")
} else {
slog.Error("no conf found", "error", err)
os.Exit(1)
}
} }
setupSlog(conf) setupSlog(conf)
stages := generateStages(conf) stages := generateStages(conf)

View File

@ -4,86 +4,95 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"os" "os"
"path/filepath"
"regexp" "regexp"
"strings"
"testing" "testing"
"focs.ji.sjtu.edu.cn/git/FOCS-dev/JOJ3/internal/stage" "focs.ji.sjtu.edu.cn/git/FOCS-dev/JOJ3/internal/stage"
) )
func compareStageResults(t *testing.T, actual, want []stage.StageResult) { func compareStageResults(t *testing.T, actual, expected []stage.StageResult, regex bool) {
t.Helper() t.Helper()
if len(actual) != len(want) { if len(actual) != len(expected) {
t.Fatalf("len(actual) = %d, want %d", len(actual), len(want)) t.Fatalf("len(actual) = %d, expected %d", len(actual), len(expected))
} }
for i := range actual { for i := range actual {
if actual[i].Name != want[i].Name { if actual[i].Name != expected[i].Name {
t.Errorf("actual[%d].Name = %s, want = %s", i, actual[i].Name, t.Errorf("actual[%d].Name = %s, expected = %s", i, actual[i].Name,
want[i].Name) expected[i].Name)
} }
if len(actual[i].Results) != len(want[i].Results) { if len(actual[i].Results) != len(expected[i].Results) {
t.Fatalf("len(actual[%d].Results) = %d, want = %d", i, t.Fatalf("len(actual[%d].Results) = %d, expected = %d", i,
len(actual[i].Results), len(want[i].Results)) len(actual[i].Results), len(expected[i].Results))
} }
for j := range actual[i].Results { for j := range actual[i].Results {
if actual[i].Results[j].Score != want[i].Results[j].Score { if actual[i].Results[j].Score != expected[i].Results[j].Score {
t.Errorf("actual[%d].Results[%d].Score = %d, want = %d", i, j, t.Errorf("actual[%d].Results[%d].Score = %d, expected = %d", i, j,
actual[i].Results[j].Score, want[i].Results[j].Score) actual[i].Results[j].Score, expected[i].Results[j].Score)
} }
r := regexp.MustCompile(want[i].Results[j].Comment) if regex {
if !r.MatchString(actual[i].Results[j].Comment) { r := regexp.MustCompile(expected[i].Results[j].Comment)
t.Errorf("actual[%d].Results[%d].Comment = %s, want RegExp = %s", if !r.MatchString(actual[i].Results[j].Comment) {
i, j, actual[i].Results[j].Comment, t.Errorf("actual[%d].Results[%d].Comment = %s, expected RegExp = %s",
want[i].Results[j].Comment) i, j, actual[i].Results[j].Comment,
expected[i].Results[j].Comment)
}
} else if actual[i].Results[j].Comment != expected[i].Results[j].Comment {
t.Errorf("actual[%d].Results[%d].Comment = %s, expected = %s", i, j,
actual[i].Results[j].Comment, expected[i].Results[j].Comment)
} }
} }
} }
} }
func readStageResults(t *testing.T, path string) []stage.StageResult {
t.Helper()
file, err := os.Open(path)
if err != nil {
t.Fatal(err)
}
defer file.Close()
var results []stage.StageResult
err = json.NewDecoder(file).Decode(&results)
if err != nil {
t.Fatal(err)
}
return results
}
func TestMain(t *testing.T) { func TestMain(t *testing.T) {
tests := []struct { var tests []string
name string root := "../../examples/"
want []stage.StageResult err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
}{ if err != nil {
{"success", []stage.StageResult{ return err
{Name: "compile", Results: []stage.ParserResult{ }
{Score: 0, Comment: ""}, if info.IsDir() {
}}, if path == root {
{Name: "run", Results: []stage.ParserResult{ return nil
{Score: 100, Comment: "executor status: run time: \\d+ ns, memory: \\d+ bytes"}, }
{Score: 100, Comment: "executor status: run time: \\d+ ns, memory: \\d+ bytes"}, path0 := filepath.Join(path, "expected_regex.json")
}}, path1 := filepath.Join(path, "expected.json")
}}, _, err0 := os.Stat(path0)
{"clang-tidy", []stage.StageResult{ _, err1 := os.Stat(path1)
{Name: "prepare", Results: []stage.ParserResult{ if err0 != nil && err1 != nil {
{Score: 0, Comment: ""}, return nil
}}, }
{Name: "clang-tidy", Results: []stage.ParserResult{ tests = append(tests, strings.TrimPrefix(path, root))
{Score: 10, Comment: ""}, }
}}, return nil
}}, })
{"compile_error", []stage.StageResult{ if err != nil {
{Name: "compile", Results: []stage.ParserResult{ t.Fatal(err)
{Score: 0, Comment: "Unexpected executor status: Nonzero Exit Status\\."},
}},
}},
{"dummy", []stage.StageResult{
{Name: "dummy", Results: []stage.ParserResult{
{Score: 110, Comment: "dummy comment \\+ comment from toml conf"},
}},
}},
{"dummy_error", []stage.StageResult{
{Name: "dummy", Results: []stage.ParserResult{
{Score: 0, Comment: "Unexpected executor status: Nonzero Exit Status\\.\\s*Stderr: dummy negative score: -1"},
}},
}},
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt, func(t *testing.T) {
origDir, err := os.Getwd() origDir, err := os.Getwd()
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
err = os.Chdir(fmt.Sprintf("../../examples/%s", tt.name)) err = os.Chdir(fmt.Sprintf("%s%s", root, tt))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -94,20 +103,18 @@ func TestMain(t *testing.T) {
} }
}() }()
os.Args = []string{"./joj3"} os.Args = []string{"./joj3"}
main()
outputFile := "joj3_result.json" outputFile := "joj3_result.json"
data, err := os.ReadFile(outputFile)
if err != nil {
t.Fatal(err)
}
defer os.Remove(outputFile) defer os.Remove(outputFile)
var stageResults []stage.StageResult main()
err = json.Unmarshal(data, &stageResults) stageResults := readStageResults(t, outputFile)
if err != nil { regex := true
t.Fatal(err) expectedFile := "expected_regex.json"
if _, err := os.Stat(expectedFile); os.IsNotExist(err) {
regex = false
expectedFile = "expected.json"
} }
t.Logf("stageResults: %+v", stageResults) expectedStageResults := readStageResults(t, expectedFile)
compareStageResults(t, stageResults, tt.want) compareStageResults(t, stageResults, expectedStageResults, regex)
}) })
} }
} }

File diff suppressed because it is too large Load Diff

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

@ -0,0 +1 @@
Subproject commit 21f6df70d483f34a99b50335e0a1fbe50d3a82dd

View File

@ -1,61 +0,0 @@
skipGitea = true
logLevel = 0
[[stages]]
name = "compile"
[stages.executor]
name = "sandbox"
[stages.executor.with.default]
args = ["g++", "b.cc", "-o", "a"]
env = ["PATH=/usr/bin:/bin"]
cpuLimit = 10_000_000_000
memoryLimit = 104_857_600
procLimit = 50
copyInCwd = true
copyOut = ["stdout", "stderr"]
copyOutCached = ["a"]
[stages.executor.with.default.stdin]
content = ""
[stages.executor.with.default.stdout]
name = "stdout"
max = 4_096
[stages.executor.with.default.stderr]
name = "stderr"
max = 4_096
[stages.parser]
name = "result-status"
[stages.parser.with]
score = 100
comment = "compile done"
[[stages]]
name = "run"
[stages.executor]
name = "sandbox"
[stages.executor.with.default]
args = ["./a"]
env = ["PATH=/usr/bin:/bin"]
cpuLimit = 1_000_000_000
memoryLimit = 104_857_600
procLimit = 50
copyOut = ["stdout", "stderr"]
[stages.executor.with.default.stdout]
name = "stdout"
max = 4_096
[stages.executor.with.default.stderr]
name = "stderr"
max = 4_096
[stages.executor.with.default.copyInCached]
a = "a"
[[stages.executor.with.cases]]
[stages.executor.with.cases.stdin]
src = "./cases/1.in"
[[stages.executor.with.cases]]
[stages.executor.with.cases.stdin]
src = "./cases/2.in"
[stages.parser]
name = "diff"
[[stages.parser.with.cases]]
score = 100
stdoutPath = "./cases/1.out"
[[stages.parser.with.cases]]
score = 100
stdoutPath = "./cases/2.out"

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

View File

@ -1,28 +0,0 @@
skipGitea = true
[[stages]]
name = "dummy"
[stages.executor]
name = "sandbox"
[stages.executor.with.default]
args = ["./dummy", "--score", "100"]
env = ["PATH=/usr/bin:/bin"]
cpuLimit = 10_000_000_000
memoryLimit = 104_857_600
procLimit = 50
copyInCwd = true
[stages.executor.with.default.copyIn.dummy]
src = "./../../build/dummy"
copyOut = ["stdout", "stderr"]
[stages.executor.with.default.stdin]
content = ""
[stages.executor.with.default.stdout]
name = "stdout"
max = 4_096
[stages.executor.with.default.stderr]
name = "stderr"
max = 4_096
[stages.parser]
name = "dummy"
[stages.parser.with]
score = 10
comment = " + comment from toml conf"

1
examples/dummy/error Submodule

@ -0,0 +1 @@
Subproject commit 3ebebbc913534fb4c4598d3ed313d1bb3ff29020

@ -0,0 +1 @@
Subproject commit 4bca6f0e4b5e263532b8410afecca615e90524e9

View File

@ -1,28 +0,0 @@
skipGitea = true
[[stages]]
name = "dummy"
[stages.executor]
name = "sandbox"
[stages.executor.with.default]
args = ["./dummy", "--score", "-1"]
env = ["PATH=/usr/bin:/bin"]
cpuLimit = 10_000_000_000
memoryLimit = 104_857_600
procLimit = 50
copyInCwd = true
[stages.executor.with.default.copyIn.dummy]
src = "./../../build/dummy"
copyOut = ["stdout", "stderr"]
[stages.executor.with.default.stdin]
content = ""
[stages.executor.with.default.stdout]
name = "stdout"
max = 4_096
[stages.executor.with.default.stderr]
name = "stderr"
max = 4_096
[stages.parser]
name = "dummy"
[stages.parser.with]
score = 10
comment = " + comment from toml conf"

@ -0,0 +1 @@
Subproject commit 51160e0a0cb01159fa264435865185083d756b06

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

View File

@ -1 +0,0 @@
!*.out

View File

@ -1,6 +0,0 @@
#include <iostream>
int main() {
int a, b;
std::cin >> a >> b;
std::cout << a + b << '\n';
}

View File

@ -1 +0,0 @@
1 1

View File

@ -1 +0,0 @@
2

View File

@ -1 +0,0 @@
1024 2048

View File

@ -1 +0,0 @@
3072

View File

@ -1,60 +0,0 @@
skipGitea = true
[[stages]]
name = "compile"
[stages.executor]
name = "sandbox"
[stages.executor.with.default]
args = ["g++", "a.cc", "-o", "a"]
env = ["PATH=/usr/bin:/bin"]
cpuLimit = 10_000_000_000
memoryLimit = 104_857_600
procLimit = 50
copyInCwd = true
copyOut = ["stdout", "stderr"]
copyOutCached = ["a"]
[stages.executor.with.default.stdin]
content = ""
[stages.executor.with.default.stdout]
name = "stdout"
max = 4_096
[stages.executor.with.default.stderr]
name = "stderr"
max = 4_096
[stages.parser]
name = "result-status"
[stages.parser.with]
score = 100
comment = "compile done"
[[stages]]
name = "run"
[stages.executor]
name = "sandbox"
[stages.executor.with.default]
args = ["./a"]
env = ["PATH=/usr/bin:/bin"]
cpuLimit = 1_000_000_000
memoryLimit = 104_857_600
procLimit = 50
copyOut = ["stdout", "stderr"]
[stages.executor.with.default.stdout]
name = "stdout"
max = 4_096
[stages.executor.with.default.stderr]
name = "stderr"
max = 4_096
[stages.executor.with.default.copyInCached]
a = "a"
[[stages.executor.with.cases]]
[stages.executor.with.cases.stdin]
src = "./cases/1.in"
[[stages.executor.with.cases]]
[stages.executor.with.cases.stdin]
src = "./cases/2.in"
[stages.parser]
name = "diff"
[[stages.parser.with.cases]]
score = 100
stdoutPath = "./cases/1.out"
[[stages.parser.with.cases]]
score = 100
stdoutPath = "./cases/2.out"

View File

@ -2,8 +2,10 @@ package parsers
import ( import (
_ "focs.ji.sjtu.edu.cn/git/FOCS-dev/JOJ3/internal/parsers/clang_tidy" _ "focs.ji.sjtu.edu.cn/git/FOCS-dev/JOJ3/internal/parsers/clang_tidy"
_ "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/diff"
_ "focs.ji.sjtu.edu.cn/git/FOCS-dev/JOJ3/internal/parsers/dummy" _ "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" _ "focs.ji.sjtu.edu.cn/git/FOCS-dev/JOJ3/internal/parsers/resultstatus"
) )

View File

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

View File

@ -0,0 +1,53 @@
package cpplint
import (
"fmt"
"regexp"
"strconv"
"focs.ji.sjtu.edu.cn/git/FOCS-dev/JOJ3/internal/stage"
)
type Conf struct {
Score int
}
type Cpplint struct{}
func Parse(executorResult stage.ExecutorResult, conf Conf) stage.ParserResult {
stderr := executorResult.Files["stderr"]
pattern := `(.+):(\d+): (.+) \[(.+)\] \[(\d)]\n`
re := regexp.MustCompile(pattern)
matches := re.FindAllStringSubmatch(stderr, -1)
score := 0
comment := ""
for _, match := range matches {
fileName := match[1]
lineNum, _ := strconv.Atoi(match[2])
message := match[3]
category := match[4]
confidence, _ := strconv.Atoi(match[5])
score -= confidence
// TODO: add more detailed comment, just re-assemble for now
comment += fmt.Sprintf("%s:%d: %s [%s] [%d]\n",
fileName, lineNum, message, category, confidence)
}
return stage.ParserResult{
Score: score,
Comment: comment,
}
}
func (*Cpplint) Run(results []stage.ExecutorResult, confAny any) (
[]stage.ParserResult, bool, error,
) {
conf, err := stage.DecodeConf[Conf](confAny)
if err != nil {
return nil, true, err
}
var res []stage.ParserResult
for _, result := range results {
res = append(res, Parse(result, *conf))
}
return res, false, nil
}

View File

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

View File

@ -0,0 +1,67 @@
package keyword
import (
"fmt"
"strings"
"focs.ji.sjtu.edu.cn/git/FOCS-dev/JOJ3/internal/stage"
)
type Match struct {
Keyword string
Score int
}
type Conf struct {
FullScore int
MinScore int
Files []string
EndOnMatch bool
Matches []Match
}
type Keyword struct{}
func Parse(executorResult stage.ExecutorResult, conf Conf) (
stage.ParserResult, bool,
) {
score := conf.FullScore
comment := ""
matched := false
for _, file := range conf.Files {
content := executorResult.Files[file]
for _, match := range conf.Matches {
count := strings.Count(content, match.Keyword)
if count > 0 {
matched = true
score -= count * match.Score
comment += fmt.Sprintf(
"Matched keyword %d time(s): %s\n",
count, match.Keyword)
}
}
}
return stage.ParserResult{
Score: max(score, conf.MinScore),
Comment: comment,
}, matched
}
func (*Keyword) 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
end := false
for _, result := range results {
tmp, matched := Parse(result, *conf)
if matched && conf.EndOnMatch {
end = true
}
res = append(res, tmp)
}
return res, end, nil
}

View File

@ -159,11 +159,11 @@ type Stage struct {
} }
type ParserResult struct { type ParserResult struct {
Score int Score int `json:"score"`
Comment string Comment string `json:"comment"`
} }
type StageResult struct { type StageResult struct {
Name string Name string `json:"name"`
Results []ParserResult Results []ParserResult `json:"results"`
} }