feat: merging from master
This commit is contained in:
commit
668676b63f
|
@ -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
1
.gitignore
vendored
|
@ -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
28
.gitmodules
vendored
Normal 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
|
3
Makefile
3
Makefile
|
@ -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
101
README.md
|
@ -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`
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
1
examples/compile/error
Submodule
1
examples/compile/error
Submodule
|
@ -0,0 +1 @@
|
|||
Subproject commit dc7be30db1fbfd83ecb78f214cff3f42732a1365
|
1
examples/compile/success
Submodule
1
examples/compile/success
Submodule
|
@ -0,0 +1 @@
|
|||
Subproject commit 21f6df70d483f34a99b50335e0a1fbe50d3a82dd
|
|
@ -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"
|
1
examples/cpplint/sillycode
Submodule
1
examples/cpplint/sillycode
Submodule
|
@ -0,0 +1 @@
|
|||
Subproject commit a7001564a22f9807119efb7b8f4cf6f74d4c12fc
|
|
@ -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
1
examples/dummy/error
Submodule
|
@ -0,0 +1 @@
|
|||
Subproject commit 3ebebbc913534fb4c4598d3ed313d1bb3ff29020
|
1
examples/dummy/success
Submodule
1
examples/dummy/success
Submodule
|
@ -0,0 +1 @@
|
|||
Subproject commit 4bca6f0e4b5e263532b8410afecca615e90524e9
|
|
@ -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"
|
1
examples/keyword/clang-tidy/sillycode
Submodule
1
examples/keyword/clang-tidy/sillycode
Submodule
|
@ -0,0 +1 @@
|
|||
Subproject commit 51160e0a0cb01159fa264435865185083d756b06
|
1
examples/keyword/cpplint/sillycode
Submodule
1
examples/keyword/cpplint/sillycode
Submodule
|
@ -0,0 +1 @@
|
|||
Subproject commit f43b0b3a7b873ee935b19e4e5f26a8ceda7d3d61
|
1
examples/success/.gitignore
vendored
1
examples/success/.gitignore
vendored
|
@ -1 +0,0 @@
|
|||
!*.out
|
|
@ -1,6 +0,0 @@
|
|||
#include <iostream>
|
||||
int main() {
|
||||
int a, b;
|
||||
std::cin >> a >> b;
|
||||
std::cout << a + b << '\n';
|
||||
}
|
|
@ -1 +0,0 @@
|
|||
1 1
|
|
@ -1 +0,0 @@
|
|||
2
|
|
@ -1 +0,0 @@
|
|||
1024 2048
|
|
@ -1 +0,0 @@
|
|||
3072
|
|
@ -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"
|
|
@ -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"
|
||||
)
|
||||
|
||||
|
|
9
internal/parsers/cpplint/meta.go
Normal file
9
internal/parsers/cpplint/meta.go
Normal 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{})
|
||||
}
|
53
internal/parsers/cpplint/parser.go
Normal file
53
internal/parsers/cpplint/parser.go
Normal 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
|
||||
}
|
9
internal/parsers/keyword/meta.go
Normal file
9
internal/parsers/keyword/meta.go
Normal 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{})
|
||||
}
|
67
internal/parsers/keyword/parser.go
Normal file
67
internal/parsers/keyword/parser.go
Normal 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
|
||||
}
|
|
@ -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"`
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user