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
- go version
- go env
- git status -v
- git log -1
- name: build
commands:
- make
@ -25,6 +27,7 @@ steps:
CONF_GITEATOKEN:
from_secret: gitea-token
commands:
- make prepare-test
- make test
- name: store
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)
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 *.out
prepare-test:
git submodule update --init --remote
test:
go test -coverprofile cover.out -v ./...

101
README.md
View File

@ -2,60 +2,97 @@
## 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
$ make test
go test -v ./...
? focs.ji.sjtu.edu.cn/git/FOCS-dev/JOJ3/cmd/dummy [no test files]
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/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/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/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/success
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_error
main_test.go:96: stageResults: [{Name:compile Results:[{Score:0 Comment:Unexpected executor status: Nonzero Exit Status.}]}]
=== RUN TestMain/dummy
main_test.go:96: stageResults: [{Name:dummy Results:[{Score:110 Comment:dummy comment + comment from toml conf}]}]
=== RUN TestMain/dummy_error
main_test.go:96: stageResults: [{Name:dummy Results:[{Score:0 Comment:Unexpected executor status: Nonzero Exit Status.
Stderr: dummy negative score: -1}]}]
--- PASS: TestMain (0.29s)
--- PASS: TestMain/success (0.27s)
--- PASS: TestMain/compile_error (0.01s)
--- PASS: TestMain/dummy (0.01s)
--- PASS: TestMain/dummy_error (0.01s)
=== 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
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
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.
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).
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
```
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
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`

View File

@ -89,7 +89,7 @@ func commitMsgToConf() (conf Conf, err error) {
return
}
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
conf = parseConfFile("conf.toml")
return

View File

@ -2,7 +2,6 @@ package main
import (
"encoding/json"
"errors"
"log/slog"
"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/stage"
"github.com/go-git/go-git/v5"
"github.com/jinzhu/copier"
)
@ -75,13 +73,8 @@ func outputResult(conf Conf, results []stage.StageResult) error {
func main() {
conf, err := commitMsgToConf()
if err != nil {
// FIXME: just for local testing purpose
if errors.Is(err, git.ErrRepositoryNotExists) {
conf = parseConfFile("conf.toml")
} else {
slog.Error("no conf found", "error", err)
os.Exit(1)
}
slog.Error("no conf found", "error", err)
os.Exit(1)
}
setupSlog(conf)
stages := generateStages(conf)

View File

@ -4,86 +4,95 @@ import (
"encoding/json"
"fmt"
"os"
"path/filepath"
"regexp"
"strings"
"testing"
"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()
if len(actual) != len(want) {
t.Fatalf("len(actual) = %d, want %d", len(actual), len(want))
if len(actual) != len(expected) {
t.Fatalf("len(actual) = %d, expected %d", len(actual), len(expected))
}
for i := range actual {
if actual[i].Name != want[i].Name {
t.Errorf("actual[%d].Name = %s, want = %s", i, actual[i].Name,
want[i].Name)
if actual[i].Name != expected[i].Name {
t.Errorf("actual[%d].Name = %s, expected = %s", i, actual[i].Name,
expected[i].Name)
}
if len(actual[i].Results) != len(want[i].Results) {
t.Fatalf("len(actual[%d].Results) = %d, want = %d", i,
len(actual[i].Results), len(want[i].Results))
if len(actual[i].Results) != len(expected[i].Results) {
t.Fatalf("len(actual[%d].Results) = %d, expected = %d", i,
len(actual[i].Results), len(expected[i].Results))
}
for j := range actual[i].Results {
if actual[i].Results[j].Score != want[i].Results[j].Score {
t.Errorf("actual[%d].Results[%d].Score = %d, want = %d", i, j,
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, expected = %d", i, j,
actual[i].Results[j].Score, expected[i].Results[j].Score)
}
r := regexp.MustCompile(want[i].Results[j].Comment)
if !r.MatchString(actual[i].Results[j].Comment) {
t.Errorf("actual[%d].Results[%d].Comment = %s, want RegExp = %s",
i, j, actual[i].Results[j].Comment,
want[i].Results[j].Comment)
if regex {
r := regexp.MustCompile(expected[i].Results[j].Comment)
if !r.MatchString(actual[i].Results[j].Comment) {
t.Errorf("actual[%d].Results[%d].Comment = %s, expected RegExp = %s",
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) {
tests := []struct {
name string
want []stage.StageResult
}{
{"success", []stage.StageResult{
{Name: "compile", Results: []stage.ParserResult{
{Score: 0, Comment: ""},
}},
{Name: "run", Results: []stage.ParserResult{
{Score: 100, Comment: "executor status: run time: \\d+ ns, memory: \\d+ bytes"},
{Score: 100, Comment: "executor status: run time: \\d+ ns, memory: \\d+ bytes"},
}},
}},
{"clang-tidy", []stage.StageResult{
{Name: "prepare", Results: []stage.ParserResult{
{Score: 0, Comment: ""},
}},
{Name: "clang-tidy", Results: []stage.ParserResult{
{Score: 10, Comment: ""},
}},
}},
{"compile_error", []stage.StageResult{
{Name: "compile", Results: []stage.ParserResult{
{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"},
}},
}},
var tests []string
root := "../../examples/"
err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if info.IsDir() {
if path == root {
return nil
}
path0 := filepath.Join(path, "expected_regex.json")
path1 := filepath.Join(path, "expected.json")
_, err0 := os.Stat(path0)
_, err1 := os.Stat(path1)
if err0 != nil && err1 != nil {
return nil
}
tests = append(tests, strings.TrimPrefix(path, root))
}
return nil
})
if err != nil {
t.Fatal(err)
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Run(tt, func(t *testing.T) {
origDir, err := os.Getwd()
if err != nil {
t.Fatal(err)
}
err = os.Chdir(fmt.Sprintf("../../examples/%s", tt.name))
err = os.Chdir(fmt.Sprintf("%s%s", root, tt))
if err != nil {
t.Fatal(err)
}
@ -94,20 +103,18 @@ func TestMain(t *testing.T) {
}
}()
os.Args = []string{"./joj3"}
main()
outputFile := "joj3_result.json"
data, err := os.ReadFile(outputFile)
if err != nil {
t.Fatal(err)
}
defer os.Remove(outputFile)
var stageResults []stage.StageResult
err = json.Unmarshal(data, &stageResults)
if err != nil {
t.Fatal(err)
main()
stageResults := readStageResults(t, outputFile)
regex := true
expectedFile := "expected_regex.json"
if _, err := os.Stat(expectedFile); os.IsNotExist(err) {
regex = false
expectedFile = "expected.json"
}
t.Logf("stageResults: %+v", stageResults)
compareStageResults(t, stageResults, tt.want)
expectedStageResults := readStageResults(t, expectedFile)
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 (
_ "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/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"
)

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 {
Score int
Comment string
Score int `json:"score"`
Comment string `json:"comment"`
}
type StageResult struct {
Name string
Results []ParserResult
Name string `json:"name"`
Results []ParserResult `json:"results"`
}