Compare commits

...

14 Commits

Author SHA1 Message Date
zzjc1234
a5767cf040 Merge branch 'master' into feat/releasetag
All checks were successful
build / build (push) Successful in 1m5s
build / build (pull_request) Successful in 1m4s
build / trigger-build-image (push) Has been skipped
build / trigger-build-image (pull_request) Has been skipped
2024-10-13 11:38:40 +08:00
cdab5ab4c8
chore: add date & time to version
All checks were successful
build / build (push) Successful in 1m2s
build / trigger-build-image (push) Successful in 6s
2024-10-12 17:44:16 -04:00
f4662eb123
fix(parser/resultdetail): file format 2024-10-12 17:41:08 -04:00
7004c5368e
fix(parser/resultdetail): file format
All checks were successful
build / build (push) Successful in 1m3s
build / trigger-build-image (push) Successful in 7s
2024-10-12 17:34:26 -04:00
5f63301656
feat(parser/resultdetail): human readable units
All checks were successful
build / build (push) Successful in 1m2s
build / trigger-build-image (push) Successful in 11s
2024-10-12 17:24:48 -04:00
d934e9a067
feat(cmd/joj3): run teapot commands in goroutine
All checks were successful
build / build (push) Successful in 1m4s
build / trigger-build-image (push) Successful in 7s
2024-10-12 07:45:19 -04:00
4e3d202fa9
feat(cmd/joj3): pass conf.Conf by pointer
All checks were successful
build / build (push) Successful in 1m4s
build / trigger-build-image (push) Successful in 7s
2024-10-12 07:16:13 -04:00
9cd0e44678
refactor(cmd/joj3): split parse msg & parse conf file
All checks were successful
build / build (push) Successful in 1m4s
build / trigger-build-image (push) Successful in 7s
2024-10-12 06:57:01 -04:00
f8a7ae6067
docs: update OS restriction
All checks were successful
build / build (push) Successful in 1m4s
build / trigger-build-image (push) Successful in 7s
2024-10-12 06:34:40 -04:00
901290d263 feat: check repo size with conf (#56)
All checks were successful
build / build (push) Successful in 1m11s
build / trigger-build-image (push) Successful in 7s
Co-authored-by: zzjc1234 <2359047351@qq.com>
Co-authored-by: Boming Zhang <bomingzh@sjtu.edu.cn>
Reviewed-on: #56
Reviewed-by: 张泊明518370910136 <bomingzh@sjtu.edu.cn>
Co-authored-by: 周赵嘉程521432910016 <zzjc123@sjtu.edu.cn>
Co-committed-by: 周赵嘉程521432910016 <zzjc123@sjtu.edu.cn>
2024-10-12 18:30:54 +08:00
703ff7d4be
fix(parser/resultdetail): add extra newline when showing files
All checks were successful
build / build (push) Successful in 1m5s
build / trigger-build-image (push) Successful in 7s
2024-10-12 02:03:05 -04:00
2a2166f1ad
feat(parser/diff): add pass/fail comment to conf
All checks were successful
build / build (push) Successful in 1m2s
build / trigger-build-image (push) Successful in 9s
2024-10-12 01:53:08 -04:00
ad0918bbde
fix(cmd/joj3): teapot conf
All checks were successful
build / build (push) Successful in 1m4s
build / trigger-build-image (push) Successful in 12s
2024-10-12 01:31:03 -04:00
2d4c7c30a3 refactor(cmd/joj3): conf (#51)
All checks were successful
build / build (push) Successful in 1m6s
build / trigger-build-image (push) Successful in 7s
Reviewed-on: #51
Co-authored-by: Boming Zhang <bomingzh@sjtu.edu.cn>
Co-committed-by: Boming Zhang <bomingzh@sjtu.edu.cn>
2024-10-12 13:21:58 +08:00
12 changed files with 239 additions and 115 deletions

View File

@ -3,7 +3,9 @@
BUILD_DIR = ./build BUILD_DIR = ./build
TMP_DIR = ./tmp TMP_DIR = ./tmp
APPS := $(notdir $(wildcard ./cmd/*)) APPS := $(notdir $(wildcard ./cmd/*))
VERSION := $(shell git rev-parse --short HEAD) COMMIT_HASH := $(shell git rev-parse --short HEAD)
DATE := $(shell date +"%Y%m%d-%H%M%S")
VERSION := $(COMMIT_HASH)-$(DATE)
FLAGS := "-s -w -X main.Version=$(VERSION)" FLAGS := "-s -w -X main.Version=$(VERSION)"
all: build all: build
@ -30,4 +32,4 @@ test:
ci-test: ci-test:
./scripts/prepare_test_repos.sh $(TMP_DIR) ./scripts/prepare_test_repos.sh $(TMP_DIR)
./scripts/run_foreach_test_repos.sh $(TMP_DIR) "sed -i '2i \ \ \"sandboxExecServer\": \"172.17.0.1:5051\",' conf.json" ./scripts/run_foreach_test_repos.sh $(TMP_DIR) "sed -i '2i \ \ \"sandboxExecServer\": \"172.17.0.1:5051\",' conf.json"
go test -coverprofile cover.out -v ./... GITHUB_ACTIONS="test" go test -coverprofile cover.out -v ./...

View File

@ -4,12 +4,16 @@
## Quick Start ## Quick Start
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). 1. Clone this repo in a Linux computer. For Windows, use [WSL 2](https://learn.microsoft.com/en-us/windows/wsl/install).
```bash
$ git clone ssh://git@focs.ji.sjtu.edu.cn:2222/JOJ/JOJ3.git
```
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`.
- If you have problem on connecting to the Go website and Go packages, download Go from [studygolang](https://studygolang.com/dl) and run `go env -w GOPROXY=https://goproxy.io,direct` to set the Go modules mirror proxy after installing Go. - If you have problem on connecting to the Go website and Go packages, download Go from [studygolang](https://studygolang.com/dl) and run `go env -w GOPROXY=https://goproxy.io,direct` to set the Go modules mirror proxy after installing Go.
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`. 3. Enable cgroup v2 for your OS. For WSL2, 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). 4. Clone [go-judge](https://github.com/criyle/go-judge).

View File

@ -13,16 +13,48 @@ import (
"github.com/koding/multiconfig" "github.com/koding/multiconfig"
) )
type ConfStage struct {
Name string
Group string
Executor struct {
Name string
With struct {
Default stage.Cmd
Cases []OptionalCmd
}
}
Parsers []struct {
Name string
With interface{}
}
}
type Conf struct { type Conf struct {
Name string `default:"unknown"`
LogPath string `default:""`
Stage struct {
SandboxExecServer string `default:"localhost:5051"`
SandboxToken string `default:""`
OutputPath string `default:"joj3_result.json"`
Stages []ConfStage
}
Teapot struct {
LogPath string `default:"/home/tt/.cache/joint-teapot-debug.log"`
ScoreboardPath string `default:"scoreboard.csv"`
FailedTablePath string `default:"failed-table.md"`
GradingRepoName string `default:""`
SkipIssue bool `default:"false"`
SkipScoreboard bool `default:"false"`
SkipFailedTable bool `default:"false"`
}
// TODO: remove the following backward compatibility fields
SandboxExecServer string `default:"localhost:5051"` SandboxExecServer string `default:"localhost:5051"`
SandboxToken string `default:""` SandboxToken string `default:""`
LogPath string `default:""`
OutputPath string `default:"joj3_result.json"` OutputPath string `default:"joj3_result.json"`
GradingRepoName string `default:""` GradingRepoName string `default:""`
SkipTeapot bool `default:"true"` SkipTeapot bool `default:"true"`
ScoreboardPath string `default:"scoreboard.csv"` ScoreboardPath string `default:"scoreboard.csv"`
FailedTablePath string `default:"failed-table.md"` FailedTablePath string `default:"failed-table.md"`
Name string `default:"unknown"`
Stages []struct { Stages []struct {
Name string Name string
Group string Group string
@ -112,25 +144,55 @@ func parseConventionalCommit(commit string) (*ConventionalCommit, error) {
return cc, nil return cc, nil
} }
func parseConfFile(path string) (conf Conf, err error) { func ParseConfFile(path string) (conf *Conf, err error) {
conf = new(Conf)
d := &multiconfig.DefaultLoader{} d := &multiconfig.DefaultLoader{}
d.Loader = multiconfig.MultiLoader( d.Loader = multiconfig.MultiLoader(
&multiconfig.TagLoader{}, &multiconfig.TagLoader{},
&multiconfig.JSONLoader{Path: path}, &multiconfig.JSONLoader{Path: path},
) )
d.Validator = multiconfig.MultiValidator(&multiconfig.RequiredValidator{}) d.Validator = multiconfig.MultiValidator(&multiconfig.RequiredValidator{})
if err = d.Load(&conf); err != nil { if err = d.Load(conf); err != nil {
slog.Error("parse stages conf", "error", err) slog.Error("parse stages conf", "error", err)
return return
} }
if err = d.Validate(&conf); err != nil { if err = d.Validate(conf); err != nil {
slog.Error("validate stages conf", "error", err) slog.Error("validate stages conf", "error", err)
return return
} }
// TODO: remove the following backward compatibility codes
if len(conf.Stage.Stages) == 0 {
conf.Stage.SandboxExecServer = conf.SandboxExecServer
conf.Stage.SandboxToken = conf.SandboxToken
conf.Stage.OutputPath = conf.OutputPath
conf.Stage.Stages = make([]ConfStage, len(conf.Stages))
for i, stage := range conf.Stages {
conf.Stage.Stages[i].Name = stage.Name
conf.Stage.Stages[i].Group = stage.Group
conf.Stage.Stages[i].Executor = stage.Executor
conf.Stage.Stages[i].Parsers = []struct {
Name string
With interface{}
}{
{
Name: stage.Parser.Name,
With: stage.Parser.With,
},
}
}
conf.Teapot.GradingRepoName = conf.GradingRepoName
conf.Teapot.ScoreboardPath = conf.ScoreboardPath
conf.Teapot.FailedTablePath = conf.FailedTablePath
if conf.SkipTeapot {
conf.Teapot.SkipScoreboard = true
conf.Teapot.SkipFailedTable = true
conf.Teapot.SkipIssue = true
}
}
return return
} }
func ParseMsg(confRoot, confName, msg string) (conf Conf, group string, err error) { func ParseMsg(confRoot, confName, msg string) (confPath, group string, err error) {
slog.Info("parse msg", "msg", msg) slog.Info("parse msg", "msg", msg)
conventionalCommit, err := parseConventionalCommit(msg) conventionalCommit, err := parseConventionalCommit(msg)
if err != nil { if err != nil {
@ -138,7 +200,7 @@ func ParseMsg(confRoot, confName, msg string) (conf Conf, group string, err erro
} }
slog.Info("conventional commit", "commit", conventionalCommit) slog.Info("conventional commit", "commit", conventionalCommit)
confRoot = filepath.Clean(confRoot) confRoot = filepath.Clean(confRoot)
confPath := filepath.Clean(fmt.Sprintf("%s/%s/%s", confPath = filepath.Clean(fmt.Sprintf("%s/%s/%s",
confRoot, conventionalCommit.Scope, confName)) confRoot, conventionalCommit.Scope, confName))
relPath, err := filepath.Rel(confRoot, confPath) relPath, err := filepath.Rel(confRoot, confPath)
if err != nil { if err != nil {
@ -148,11 +210,6 @@ func ParseMsg(confRoot, confName, msg string) (conf Conf, group string, err erro
err = fmt.Errorf("invalid scope as path: %s", conventionalCommit.Scope) err = fmt.Errorf("invalid scope as path: %s", conventionalCommit.Scope)
return return
} }
slog.Info("try to load conf", "path", confPath)
conf, err = parseConfFile(confPath)
if err != nil {
return
}
groupKeywords := []string{"joj"} groupKeywords := []string{"joj"}
for _, groupKeyword := range groupKeywords { for _, groupKeyword := range groupKeywords {
if strings.Contains( if strings.Contains(
@ -161,7 +218,6 @@ func ParseMsg(confRoot, confName, msg string) (conf Conf, group string, err erro
break break
} }
} }
slog.Debug("conf loaded", "conf", conf)
return return
} }

View File

@ -45,7 +45,7 @@ func mainImpl() error {
return err return err
} }
} }
confObj, group, err := conf.ParseMsg(confRoot, confName, msg) confPath, group, err := conf.ParseMsg(confRoot, confName, msg)
if err != nil { if err != nil {
slog.Error("parse msg", "error", err) slog.Error("parse msg", "error", err)
validScopes, scopeErr := conf.ListValidScopes( validScopes, scopeErr := conf.ListValidScopes(
@ -58,6 +58,13 @@ func mainImpl() error {
"valid scopes", validScopes) "valid scopes", validScopes)
return err return err
} }
slog.Info("try to load conf", "path", confPath)
confObj, err := conf.ParseConfFile(confPath)
if err != nil {
slog.Error("parse conf", "error", err)
return err
}
slog.Debug("conf loaded", "conf", confObj)
if err := setupSlog(confObj.LogPath); err != nil { // after conf is loaded if err := setupSlog(confObj.LogPath); err != nil { // after conf is loaded
slog.Error("setup slog", "error", err) slog.Error("setup slog", "error", err)
return err return err

View File

@ -13,10 +13,10 @@ import (
"github.com/jinzhu/copier" "github.com/jinzhu/copier"
) )
func generateStages(conf conf.Conf, group string) ([]stage.Stage, error) { func generateStages(conf *conf.Conf, group string) ([]stage.Stage, error) {
stages := []stage.Stage{} stages := []stage.Stage{}
existNames := map[string]bool{} existNames := map[string]bool{}
for _, s := range conf.Stages { for _, s := range conf.Stage.Stages {
if s.Group != "" && group != s.Group { if s.Group != "" && group != s.Group {
continue continue
} }
@ -50,12 +50,20 @@ func generateStages(conf conf.Conf, group string) ([]stage.Stage, error) {
if len(s.Executor.With.Cases) == 0 { if len(s.Executor.With.Cases) == 0 {
cmds = []stage.Cmd{defaultCmd} cmds = []stage.Cmd{defaultCmd}
} }
parsers := []stage.StageParser{}
for _, p := range s.Parsers {
parsers = append(parsers, stage.StageParser{
Name: p.Name,
Conf: p.With,
})
}
stages = append(stages, stage.Stage{ stages = append(stages, stage.Stage{
Name: s.Name, Name: s.Name,
ExecutorName: s.Executor.Name, Executor: stage.StageExecutor{
ExecutorCmds: cmds, Name: s.Executor.Name,
ParserName: s.Parser.Name, Cmds: cmds,
ParserConf: s.Parser.With, },
Parsers: parsers,
}) })
} }
slog.Debug("stages generated", "stages", stages) slog.Debug("stages generated", "stages", stages)
@ -73,8 +81,11 @@ func outputResult(outputPath string, results []stage.StageResult) error {
append(content, []byte("\n")...), 0o600) append(content, []byte("\n")...), 0o600)
} }
func Run(conf conf.Conf, group string) error { func Run(conf *conf.Conf, group string) error {
executors.InitWithConf(conf.SandboxExecServer, conf.SandboxToken) executors.InitWithConf(
conf.Stage.SandboxExecServer,
conf.Stage.SandboxToken,
)
stages, err := generateStages(conf, group) stages, err := generateStages(conf, group)
if err != nil { if err != nil {
slog.Error("generate stages", "error", err) slog.Error("generate stages", "error", err)
@ -86,7 +97,7 @@ func Run(conf conf.Conf, group string) error {
slog.Error("run stages", "error", err) slog.Error("run stages", "error", err)
return err return err
} }
if err := outputResult(conf.OutputPath, results); err != nil { if err := outputResult(conf.Stage.OutputPath, results); err != nil {
slog.Error("output result", "error", err) slog.Error("output result", "error", err)
return err return err
} }

View File

@ -7,17 +7,22 @@ import (
"os/exec" "os/exec"
"regexp" "regexp"
"strings" "strings"
"sync"
"github.com/joint-online-judge/JOJ3/cmd/joj3/conf" "github.com/joint-online-judge/JOJ3/cmd/joj3/conf"
) )
func Run(conf conf.Conf) error { func Run(conf *conf.Conf) error {
if conf.SkipTeapot { actions := os.Getenv("GITHUB_ACTIONS")
if actions != "true" {
slog.Info("teapot exit", "GITHUB_ACTIONS", actions)
return nil return nil
} }
os.Setenv("LOG_FILE_PATH", "/home/tt/.cache/joint-teapot-debug.log") os.Setenv("LOG_FILE_PATH", conf.Teapot.LogPath)
os.Setenv("_TYPER_STANDARD_TRACEBACK", "1") os.Setenv("_TYPER_STANDARD_TRACEBACK", "1")
envFilePath := "/home/tt/.config/teapot/teapot.env" envFilePath := "/home/tt/.config/teapot/teapot.env"
// TODO: pass sha to joint-teapot
// sha := os.Getenv("GITHUB_SHA")
actor := os.Getenv("GITHUB_ACTOR") actor := os.Getenv("GITHUB_ACTOR")
repository := os.Getenv("GITHUB_REPOSITORY") repository := os.Getenv("GITHUB_REPOSITORY")
runNumber := os.Getenv("GITHUB_RUN_NUMBER") runNumber := os.Getenv("GITHUB_RUN_NUMBER")
@ -29,49 +34,61 @@ func Run(conf conf.Conf) error {
repoParts := strings.Split(repository, "/") repoParts := strings.Split(repository, "/")
repoName := repoParts[1] repoName := repoParts[1]
re := regexp.MustCompile(`\x1b\[[0-9;]*[a-zA-Z]`) re := regexp.MustCompile(`\x1b\[[0-9;]*[a-zA-Z]`)
cmd := exec.Command("joint-teapot", "joj3-scoreboard", execCommand := func(name string, cmdArgs []string) error {
envFilePath, conf.OutputPath, actor, conf.GradingRepoName, repoName, cmd := exec.Command(name, cmdArgs...) // #nosec G204
runNumber, conf.ScoreboardPath, conf.Name) // #nosec G204 outputBytes, err := cmd.CombinedOutput()
outputBytes, err := cmd.CombinedOutput() output := re.ReplaceAllString(string(outputBytes), "")
output := re.ReplaceAllString(string(outputBytes), "") for _, line := range strings.Split(output, "\n") {
for _, line := range strings.Split(output, "\n") { if line == "" {
if line == "" { continue
continue }
slog.Info(fmt.Sprintf("%s %s", name, cmdArgs[0]), "output", line)
} }
slog.Info("joint-teapot joj3-scoreboard", "output", line)
}
if err != nil {
slog.Error("joint-teapot joj3-scoreboard", "err", err)
return err return err
} }
cmd = exec.Command("joint-teapot", "joj3-failed-table", var wg sync.WaitGroup
envFilePath, conf.OutputPath, actor, conf.GradingRepoName, repoName, var scoreboardErr, failedTableErr, issueErr error
runNumber, conf.FailedTablePath, conf.Name) // #nosec G204 wg.Add(2)
outputBytes, err = cmd.CombinedOutput() go func() {
output = re.ReplaceAllString(string(outputBytes), "") defer wg.Done()
for _, line := range strings.Split(output, "\n") { if !conf.Teapot.SkipScoreboard {
if line == "" { err := execCommand("joint-teapot", []string{
continue "joj3-scoreboard", envFilePath, conf.Stage.OutputPath, actor,
conf.Teapot.GradingRepoName, repoName, runNumber,
conf.Teapot.ScoreboardPath, conf.Name,
})
if err != nil {
scoreboardErr = err
}
} }
slog.Info("joint-teapot joj3-failed-table", "output", line) if !conf.Teapot.SkipFailedTable {
} err := execCommand("joint-teapot", []string{
if err != nil { "joj3-failed-table", envFilePath, conf.Stage.OutputPath, actor,
slog.Error("joint-teapot joj3-failed-table", "err", err) conf.Teapot.GradingRepoName, repoName, runNumber,
return err conf.Teapot.FailedTablePath, conf.Name,
} })
cmd = exec.Command("joint-teapot", "joj3-create-result-issue", if err != nil {
envFilePath, conf.OutputPath, repoName, runNumber, conf.Name) // #nosec G204 failedTableErr = err
outputBytes, err = cmd.CombinedOutput() }
output = re.ReplaceAllString(string(outputBytes), "")
for _, line := range strings.Split(output, "\n") {
if line == "" {
continue
} }
slog.Info("joint-teapot joj3-create-result-issue", "output", line) }()
} go func() {
if err != nil { defer wg.Done()
slog.Error("joint-teapot joj3-create-result-issue", "err", err) if !conf.Teapot.SkipIssue {
return err err := execCommand("joint-teapot", []string{
"joj3-create-result-issue", envFilePath, conf.Stage.OutputPath,
repoName, runNumber, conf.Name,
})
if err != nil {
issueErr = err
}
}
}()
wg.Wait()
if scoreboardErr != nil || failedTableErr != nil || issueErr != nil {
slog.Error("teapot exit", "scoreboardErr", scoreboardErr,
"failedTableErr", failedTableErr, "issueErr", issueErr)
return fmt.Errorf("teapot exit")
} }
return nil return nil
} }

View File

@ -44,6 +44,7 @@ func main() {
checkRelease := flag.Bool("checkRelease", true, "trigger release check") checkRelease := flag.Bool("checkRelease", true, "trigger release check")
rootDir := flag.String("root", "", "") rootDir := flag.String("root", "", "")
repo := flag.String("repo", "", "") repo := flag.String("repo", "", "")
size := flag.Float64("reposize", 2, "size of the repo")
localList := flag.String("localList", "", "") localList := flag.String("localList", "", "")
droneBranch := flag.String("droneBranch", "", "") droneBranch := flag.String("droneBranch", "", "")
releaseCategories := flag.String("releaseCategories", "", "") releaseCategories := flag.String("releaseCategories", "", "")
@ -59,7 +60,7 @@ func main() {
} }
setupSlog() setupSlog()
var err error var err error
err = healthcheck.RepoSize() err = healthcheck.RepoSize(*size)
if err != nil { if err != nil {
fmt.Printf("### Repo Size Check Failed:\n%s\n", err.Error()) fmt.Printf("### Repo Size Check Failed:\n%s\n", err.Error())
} }

View File

@ -21,7 +21,9 @@ const (
) )
type Conf struct { type Conf struct {
Cases []struct { PassComment string `default:"🥳Passed!\n"`
FailComment string `default:"🧐Failed...\n"`
Cases []struct {
IgnoreResultStatus bool IgnoreResultStatus bool
Outputs []struct { Outputs []struct {
Score int Score int
@ -72,9 +74,9 @@ func (*Diff) Run(results []stage.ExecutorResult, confAny any) (
if compareChars(string(answer), result.Files[output.FileName], if compareChars(string(answer), result.Files[output.FileName],
output.CompareSpace) { output.CompareSpace) {
score += output.Score score += output.Score
comment += "Pass!\n" comment += conf.PassComment
} else { } else {
comment += "Fail!\n" comment += conf.FailComment
comment += fmt.Sprintf("Difference found in `%s`.\n", comment += fmt.Sprintf("Difference found in `%s`.\n",
output.FileName) output.FileName)
if !output.AlwaysHide { if !output.AlwaysHide {

View File

@ -29,27 +29,27 @@ func (*ResultDetail) Run(results []stage.ExecutorResult, confAny any) (
var res []stage.ParserResult var res []stage.ParserResult
for _, result := range results { for _, result := range results {
comment := "" comment := ""
// TODO: more human readable units
if conf.ShowExitStatus { if conf.ShowExitStatus {
comment += fmt.Sprintf("Exit Status: %d\n", result.ExitStatus) comment += fmt.Sprintf("Exit Status: `%d`\n", result.ExitStatus)
} }
if conf.ShowError { if conf.ShowError {
comment += fmt.Sprintf("Error: %s\n", result.Error) comment += fmt.Sprintf("Error: `%s`\n", result.Error)
} }
if conf.ShowTime { if conf.ShowTime {
comment += fmt.Sprintf("Time: %d\n", result.Time) comment += fmt.Sprintf("Time: `%d ms`\n", result.Time/1e9)
} }
if conf.ShowMemory { if conf.ShowMemory {
comment += fmt.Sprintf("Memory: %d\n", result.Memory) comment += fmt.Sprintf("Memory: `%.2f MiB`\n",
float64(result.Memory)/(1024*1024))
} }
if conf.ShowRunTime { if conf.ShowRunTime {
comment += fmt.Sprintf("RunTime: %d\n", result.RunTime) comment += fmt.Sprintf("RunTime: `%d ms`\n", result.RunTime/1e9)
} }
for _, file := range conf.ShowFiles { for _, file := range conf.ShowFiles {
content, ok := result.Files[file] content, ok := result.Files[file]
comment += fmt.Sprintf("File: `%s`.\n", file) comment += fmt.Sprintf("File `%s`:\n", file)
if ok { if ok {
comment += fmt.Sprintf("```%s```\n", content) comment += fmt.Sprintf("```\n%s\n```\n", content)
} else { } else {
comment += "Not found.\n" comment += "Not found.\n"
} }

View File

@ -150,12 +150,19 @@ func (r ExecutorResult) String() string {
return fmt.Sprintf("%+v", d) return fmt.Sprintf("%+v", d)
} }
type StageExecutor struct {
Name string
Cmds []Cmd
}
type StageParser struct {
Name string
Conf any
}
type Stage struct { type Stage struct {
Name string Name string
ExecutorName string Executor StageExecutor
ExecutorCmds []Cmd Parsers []StageParser
ParserName string
ParserConf any
} }
type ParserResult struct { type ParserResult struct {

View File

@ -8,51 +8,68 @@ import (
func Run(stages []Stage) (stageResults []StageResult, err error) { func Run(stages []Stage) (stageResults []StageResult, err error) {
var executorResults []ExecutorResult var executorResults []ExecutorResult
var parserResults []ParserResult var parserResults []ParserResult
var tmpParserResults []ParserResult
var forceQuit bool var forceQuit bool
slog.Info("stage run start") slog.Info("stage run start")
for _, stage := range stages { for _, stage := range stages {
slog.Info("stage start", "name", stage.Name) slog.Info("stage start", "name", stage.Name)
slog.Info("executor run start", "name", stage.ExecutorName) slog.Info("executor run start", "name", stage.Executor.Name)
slog.Debug("executor run start", "name", stage.ExecutorName, slog.Debug("executor run start", "name", stage.Executor.Name,
"cmds", stage.ExecutorCmds) "cmds", stage.Executor.Cmds)
executor, ok := executorMap[stage.ExecutorName] executor, ok := executorMap[stage.Executor.Name]
if !ok { if !ok {
slog.Error("executor not found", "name", stage.ExecutorName) slog.Error("executor not found", "name", stage.Executor.Name)
err = fmt.Errorf("executor not found: %s", stage.ExecutorName) err = fmt.Errorf("executor not found: %s", stage.Executor.Name)
return return
} }
executorResults, err = executor.Run(stage.ExecutorCmds) executorResults, err = executor.Run(stage.Executor.Cmds)
if err != nil { if err != nil {
slog.Error("executor run error", "name", stage.ExecutorName, "error", err) slog.Error("executor run error", "name", stage.Executor.Name, "error", err)
return return
} }
slog.Debug("executor run done", "results", executorResults) slog.Debug("executor run done", "results", executorResults)
for _, executorResult := range executorResults { for _, executorResult := range executorResults {
slog.Debug("executor run done", "result.Files", executorResult.Files) slog.Debug("executor run done", "result.Files", executorResult.Files)
} }
slog.Info("parser run start", "name", stage.ParserName) parserResults = []ParserResult{}
slog.Debug("parser run start", "name", stage.ParserName, stageForceQuit := false
"conf", stage.ParserConf) for _, stageParser := range stage.Parsers {
parser, ok := parserMap[stage.ParserName] slog.Info("parser run start", "name", stageParser.Name)
if !ok { slog.Debug("parser run start", "name", stageParser.Name,
slog.Error("parser not found", "name", stage.ParserName) "conf", stageParser.Conf)
err = fmt.Errorf("parser not found: %s", stage.ParserName) parser, ok := parserMap[stageParser.Name]
return if !ok {
slog.Error("parser not found", "name", stageParser.Name)
err = fmt.Errorf("parser not found: %s", stageParser.Name)
return
}
tmpParserResults, forceQuit, err = parser.Run(
executorResults, stageParser.Conf)
if err != nil {
slog.Error("parser run error", "name", stageParser.Name, "error", err)
return
}
stageForceQuit = stageForceQuit || forceQuit
slog.Debug("parser run done", "results", tmpParserResults)
if len(parserResults) == 0 {
parserResults = tmpParserResults
} else {
for i := range len(parserResults) {
parserResults[i].Score += tmpParserResults[i].Score
parserResults[i].Comment += tmpParserResults[i].Comment
}
}
if forceQuit {
slog.Error("parser force quit", "name", stageParser.Name)
}
} }
parserResults, forceQuit, err = parser.Run(executorResults, stage.ParserConf)
if err != nil {
slog.Error("parser run error", "name", stage.ParserName, "error", err)
return
}
slog.Debug("parser run done", "results", parserResults)
stageResults = append(stageResults, StageResult{ stageResults = append(stageResults, StageResult{
Name: stage.Name, Name: stage.Name,
Results: parserResults, Results: parserResults,
ForceQuit: forceQuit, ForceQuit: stageForceQuit,
}) })
if forceQuit { if stageForceQuit {
slog.Error("parser force quit", "name", stage.ParserName) break
return
} }
} }
return return

View File

@ -10,7 +10,7 @@ import (
// RepoSize checks the size of the repository to determine if it is oversized. // RepoSize checks the size of the repository to determine if it is oversized.
// It executes the 'git count-objects -v' command to obtain the size information, // It executes the 'git count-objects -v' command to obtain the size information,
func RepoSize() error { func RepoSize(confSize float64) error {
// TODO: reimplement here when go-git is available // TODO: reimplement here when go-git is available
// https://github.com/go-git/go-git/blob/master/COMPATIBILITY.md // https://github.com/go-git/go-git/blob/master/COMPATIBILITY.md
cmd := exec.Command("git", "count-objects", "-v") cmd := exec.Command("git", "count-objects", "-v")
@ -33,8 +33,8 @@ func RepoSize() error {
sum += size sum += size
} }
} }
if sum > 2048 { if sum > int(confSize*1024) {
return fmt.Errorf("Repository larger than 2MB. Please clean up or contact the teaching team.") return fmt.Errorf("Repository larger than %.1f MiB. Please clean up or contact the teaching team.", confSize)
} }
return nil return nil
} }