Merge branch 'master' into feat/reposize
Some checks failed
build / build (pull_request) Failing after 1m13s
build / build (push) Failing after 1m15s
build / trigger-build-image (pull_request) Has been skipped
build / trigger-build-image (push) Has been skipped

This commit is contained in:
zzjc1234 2024-10-12 17:59:34 +08:00
commit 74ad5d6a8a
8 changed files with 194 additions and 86 deletions

View File

@ -30,4 +30,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

@ -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
@ -127,6 +159,35 @@ func parseConfFile(path string) (conf Conf, err error) {
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
} }

View File

@ -16,7 +16,7 @@ import (
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)
@ -74,7 +82,10 @@ func outputResult(outputPath string, results []stage.StageResult) error {
} }
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

@ -12,12 +12,16 @@ import (
) )
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 +33,55 @@ 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", if !conf.Teapot.SkipScoreboard {
envFilePath, conf.OutputPath, actor, conf.GradingRepoName, repoName, cmd := exec.Command("joint-teapot", "joj3-scoreboard",
runNumber, conf.ScoreboardPath, conf.Name) // #nosec G204 envFilePath, conf.Stage.OutputPath, actor, conf.Teapot.GradingRepoName,
outputBytes, err := cmd.CombinedOutput() repoName, runNumber, conf.Teapot.ScoreboardPath, conf.Name) // #nosec G204
output := re.ReplaceAllString(string(outputBytes), "") outputBytes, err := cmd.CombinedOutput()
for _, line := range strings.Split(output, "\n") { output := re.ReplaceAllString(string(outputBytes), "")
if line == "" { for _, line := range strings.Split(output, "\n") {
continue if line == "" {
continue
}
slog.Info("joint-teapot joj3-scoreboard", "output", line)
} }
slog.Info("joint-teapot joj3-scoreboard", "output", line) if err != nil {
} slog.Error("joint-teapot joj3-scoreboard", "err", err)
if err != nil { return err
slog.Error("joint-teapot joj3-scoreboard", "err", err)
return err
}
cmd = exec.Command("joint-teapot", "joj3-failed-table",
envFilePath, conf.OutputPath, actor, conf.GradingRepoName, repoName,
runNumber, conf.FailedTablePath, conf.Name) // #nosec G204
outputBytes, err = cmd.CombinedOutput()
output = re.ReplaceAllString(string(outputBytes), "")
for _, line := range strings.Split(output, "\n") {
if line == "" {
continue
} }
slog.Info("joint-teapot joj3-failed-table", "output", line)
} }
if err != nil { if !conf.Teapot.SkipFailedTable {
slog.Error("joint-teapot joj3-failed-table", "err", err) cmd := exec.Command("joint-teapot", "joj3-failed-table",
return err envFilePath, conf.Stage.OutputPath, actor, conf.Teapot.GradingRepoName,
} repoName, runNumber, conf.Teapot.FailedTablePath, conf.Name) // #nosec G204
cmd = exec.Command("joint-teapot", "joj3-create-result-issue", outputBytes, err := cmd.CombinedOutput()
envFilePath, conf.OutputPath, repoName, runNumber, conf.Name) // #nosec G204 output := re.ReplaceAllString(string(outputBytes), "")
outputBytes, err = cmd.CombinedOutput() for _, line := range strings.Split(output, "\n") {
output = re.ReplaceAllString(string(outputBytes), "") if line == "" {
for _, line := range strings.Split(output, "\n") { continue
if line == "" { }
continue slog.Info("joint-teapot joj3-failed-table", "output", line)
}
if err != nil {
slog.Error("joint-teapot joj3-failed-table", "err", err)
return err
} }
slog.Info("joint-teapot joj3-create-result-issue", "output", line)
} }
if err != nil { if !conf.Teapot.SkipIssue {
slog.Error("joint-teapot joj3-create-result-issue", "err", err) cmd := exec.Command("joint-teapot", "joj3-create-result-issue",
return err envFilePath, conf.Stage.OutputPath, repoName, runNumber, conf.Name) // #nosec G204
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)
}
if err != nil {
slog.Error("joint-teapot joj3-create-result-issue", "err", err)
return err
}
} }
return nil return nil
} }

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

@ -49,7 +49,7 @@ func (*ResultDetail) Run(results []stage.ExecutorResult, confAny any) (
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("```%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