diff --git a/.drone.yml b/.drone.yml index 685e2bf..4358d7d 100644 --- a/.drone.yml +++ b/.drone.yml @@ -25,6 +25,7 @@ steps: CONF_GITEATOKEN: from_secret: gitea-token commands: + - make prepare-test - make test - name: store commands: diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..2d32453 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,20 @@ +[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 diff --git a/Makefile b/Makefile index 3008575..5dace32 100644 --- a/Makefile +++ b/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 ./... diff --git a/README.md b/README.md index 3f5d049..ab9e051 100644 --- a/README.md +++ b/README.md @@ -4,9 +4,9 @@ 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`. +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 cgroups v2 for your OS. Check [here](https://stackoverflow.com/a/73376219/13724598). So that you do not need root permission to run `go-judge`. +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 @@ -20,40 +20,47 @@ $ # make sure you are in go-judge directory $ ./tmp/go-judge -enable-grpc -enable-debug -enable-metrics ``` -6. Check the functions of `joj3` with the `make test`, which should pass all the test cases. The cases used here are in `/examples`. +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. 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 -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/sandbox coverage: 0.0% of statements 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/executors/sandbox 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/stage 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/pkg/dummy 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/internal/parsers/dummy 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/success - main_test.go:101: stageResults: [{Name:compile Results:[{Score:0 Comment:}]} {Name:run Results:[{Score:100 Comment:executor status: run time: 1867950 ns, memory: 10813440 bytes} {Score:100 Comment:executor status: run time: 1948947 ns, memory: 10813440 bytes}]}] === RUN TestMain/compile/error - main_test.go:101: stageResults: [{Name:compile Results:[{Score:0 Comment:Unexpected executor status: Nonzero Exit Status.}]}] === RUN TestMain/dummy/success - main_test.go:101: stageResults: [{Name:dummy Results:[{Score:110 Comment:dummy comment + comment from toml conf}]}] === RUN TestMain/dummy/error - main_test.go:101: stageResults: [{Name:dummy Results:[{Score:0 Comment:Unexpected executor status: Nonzero Exit Status. - Stderr: dummy negative score: -1}]}] ---- PASS: TestMain (0.39s) - --- PASS: TestMain/compile/success (0.36s) +=== RUN TestMain/cpplint/sillycode +--- PASS: TestMain (0.42s) + --- PASS: TestMain/compile/success (0.28s) --- PASS: TestMain/compile/error (0.01s) - --- PASS: TestMain/dummy/success (0.02s) + --- PASS: TestMain/dummy/success (0.01s) --- PASS: TestMain/dummy/error (0.01s) + --- PASS: TestMain/cpplint/sillycode (0.11s) PASS -coverage: 68.5% of statements -ok focs.ji.sjtu.edu.cn/git/FOCS-dev/JOJ3/cmd/joj3 0.403s coverage: 68.5% of statements +coverage: 72.4% of statements +ok focs.ji.sjtu.edu.cn/git/FOCS-dev/JOJ3/cmd/joj3 0.425s coverage: 72.4% of statements ``` ### For developers @@ -66,7 +73,7 @@ $ pre-commit install pre-commit installed at .git/hooks/pre-commit ``` -3. You only need to run step 5 and 6 in quick start during development. +3. You only need to run steps 5 and 7 in the quick start during development. If the test cases need to be updated, step 6 is also needed. ## Models diff --git a/cmd/joj3/conf.go b/cmd/joj3/conf.go index ec576ad..ef87bbf 100644 --- a/cmd/joj3/conf.go +++ b/cmd/joj3/conf.go @@ -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 diff --git a/cmd/joj3/main.go b/cmd/joj3/main.go index 154d8cf..4348b73 100644 --- a/cmd/joj3/main.go +++ b/cmd/joj3/main.go @@ -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) diff --git a/cmd/joj3/main_test.go b/cmd/joj3/main_test.go index 39db646..05e78f3 100644 --- a/cmd/joj3/main_test.go +++ b/cmd/joj3/main_test.go @@ -10,72 +10,70 @@ import ( "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 - }{ - {"compile/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"}, - }}, - }}, - {"compile/error", []stage.StageResult{ - {Name: "compile", Results: []stage.ParserResult{ - {Score: 0, Comment: "Unexpected executor status: Nonzero Exit Status\\."}, - }}, - }}, - {"dummy/success", []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"}, - }}, - }}, + tests := []string{ + "compile/success", + "compile/error", + "dummy/success", + "dummy/error", + "cpplint/sillycode", } 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("../../examples/%s", tt)) if err != nil { t.Fatal(err) } @@ -86,20 +84,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) }) } } diff --git a/examples/compile/error b/examples/compile/error new file mode 160000 index 0000000..dc7be30 --- /dev/null +++ b/examples/compile/error @@ -0,0 +1 @@ +Subproject commit dc7be30db1fbfd83ecb78f214cff3f42732a1365 diff --git a/examples/compile/error/conf.toml b/examples/compile/error/conf.toml deleted file mode 100644 index a52ff88..0000000 --- a/examples/compile/error/conf.toml +++ /dev/null @@ -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" diff --git a/examples/compile/success b/examples/compile/success new file mode 160000 index 0000000..21f6df7 --- /dev/null +++ b/examples/compile/success @@ -0,0 +1 @@ +Subproject commit 21f6df70d483f34a99b50335e0a1fbe50d3a82dd diff --git a/examples/compile/success/a.cc b/examples/compile/success/a.cc deleted file mode 100644 index 2c7ca74..0000000 --- a/examples/compile/success/a.cc +++ /dev/null @@ -1,6 +0,0 @@ -#include -int main() { - int a, b; - std::cin >> a >> b; - std::cout << a + b << '\n'; -} diff --git a/examples/compile/success/cases/1.in b/examples/compile/success/cases/1.in deleted file mode 100644 index 2fb73a0..0000000 --- a/examples/compile/success/cases/1.in +++ /dev/null @@ -1 +0,0 @@ -1 1 diff --git a/examples/compile/success/cases/1.out b/examples/compile/success/cases/1.out deleted file mode 100644 index 0cfbf08..0000000 --- a/examples/compile/success/cases/1.out +++ /dev/null @@ -1 +0,0 @@ -2 diff --git a/examples/compile/success/cases/2.in b/examples/compile/success/cases/2.in deleted file mode 100644 index b5f1e36..0000000 --- a/examples/compile/success/cases/2.in +++ /dev/null @@ -1 +0,0 @@ -1024 2048 diff --git a/examples/compile/success/cases/2.out b/examples/compile/success/cases/2.out deleted file mode 100644 index 5fd86fd..0000000 --- a/examples/compile/success/cases/2.out +++ /dev/null @@ -1 +0,0 @@ -3072 diff --git a/examples/compile/success/conf.toml b/examples/compile/success/conf.toml deleted file mode 100644 index e91739a..0000000 --- a/examples/compile/success/conf.toml +++ /dev/null @@ -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" diff --git a/examples/cpplint/sillycode b/examples/cpplint/sillycode new file mode 160000 index 0000000..e650457 --- /dev/null +++ b/examples/cpplint/sillycode @@ -0,0 +1 @@ +Subproject commit e6504575379ef49c1495dc635f7aba36f57dddc8 diff --git a/examples/dummy/error b/examples/dummy/error new file mode 160000 index 0000000..3ebebbc --- /dev/null +++ b/examples/dummy/error @@ -0,0 +1 @@ +Subproject commit 3ebebbc913534fb4c4598d3ed313d1bb3ff29020 diff --git a/examples/dummy/error/conf.toml b/examples/dummy/error/conf.toml deleted file mode 100644 index 7648609..0000000 --- a/examples/dummy/error/conf.toml +++ /dev/null @@ -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" diff --git a/examples/dummy/success b/examples/dummy/success new file mode 160000 index 0000000..4bca6f0 --- /dev/null +++ b/examples/dummy/success @@ -0,0 +1 @@ +Subproject commit 4bca6f0e4b5e263532b8410afecca615e90524e9 diff --git a/examples/dummy/success/conf.toml b/examples/dummy/success/conf.toml deleted file mode 100644 index 7fef08c..0000000 --- a/examples/dummy/success/conf.toml +++ /dev/null @@ -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" diff --git a/internal/parsers/all.go b/internal/parsers/all.go index ecc658c..d305bd6 100644 --- a/internal/parsers/all.go +++ b/internal/parsers/all.go @@ -1,6 +1,7 @@ package parsers import ( + _ "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/resultstatus" diff --git a/internal/parsers/cpplint/meta.go b/internal/parsers/cpplint/meta.go new file mode 100644 index 0000000..33b5188 --- /dev/null +++ b/internal/parsers/cpplint/meta.go @@ -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{}) +} diff --git a/internal/parsers/cpplint/parser.go b/internal/parsers/cpplint/parser.go new file mode 100644 index 0000000..55a7f60 --- /dev/null +++ b/internal/parsers/cpplint/parser.go @@ -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 +}