clang-tidy parser and executor #26
|  | @ -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
									
									
								
							
							
						
						
									
										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) | # 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
									
								
							
							
						
						
									
										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 $(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
									
									
									
									
									
								
							
							
						
						
									
										101
									
								
								README.md
									
									
									
									
									
								
							|  | @ -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` | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -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 | ||||||
|  |  | ||||||
|  | @ -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) | ||||||
|  |  | ||||||
|  | @ -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 | ||||||
| 
					
					zjc_he marked this conversation as resolved
					
						
						
							Outdated
						
					
				 | |||||||
|  | 	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
											
										
									
								
							
							
								
								
									
										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 ( | 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" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
							
								
								
									
										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 { | 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"` | ||||||
| } | } | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue
	
	Block a user
	
ditto