refactor(cmd/joj3)!: conf #51

Merged
张泊明518370910136 merged 11 commits from conf into master 2024-10-12 13:21:59 +08:00
6 changed files with 180 additions and 82 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,27 @@ 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,
},
}
}
}
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,9 +33,10 @@ 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]`)
if !conf.Teapot.SkipScoreboard {
cmd := exec.Command("joint-teapot", "joj3-scoreboard", cmd := exec.Command("joint-teapot", "joj3-scoreboard",
envFilePath, conf.OutputPath, actor, conf.GradingRepoName, repoName, envFilePath, conf.Stage.OutputPath, actor, conf.Teapot.GradingRepoName,
runNumber, conf.ScoreboardPath, conf.Name) // #nosec G204 repoName, runNumber, conf.Teapot.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") {
@ -44,11 +49,13 @@ func Run(conf conf.Conf) error {
slog.Error("joint-teapot joj3-scoreboard", "err", err) slog.Error("joint-teapot joj3-scoreboard", "err", err)
return err return err
} }
cmd = exec.Command("joint-teapot", "joj3-failed-table", }
envFilePath, conf.OutputPath, actor, conf.GradingRepoName, repoName, if !conf.Teapot.SkipFailedTable {
runNumber, conf.FailedTablePath, conf.Name) // #nosec G204 cmd := exec.Command("joint-teapot", "joj3-failed-table",
outputBytes, err = cmd.CombinedOutput() envFilePath, conf.Stage.OutputPath, actor, conf.Teapot.GradingRepoName,
output = re.ReplaceAllString(string(outputBytes), "") repoName, runNumber, conf.Teapot.FailedTablePath, conf.Name) // #nosec G204
outputBytes, err := cmd.CombinedOutput()
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
@ -59,10 +66,12 @@ func Run(conf conf.Conf) error {
slog.Error("joint-teapot joj3-failed-table", "err", err) slog.Error("joint-teapot joj3-failed-table", "err", err)
return err return err
} }
cmd = exec.Command("joint-teapot", "joj3-create-result-issue", }
envFilePath, conf.OutputPath, repoName, runNumber, conf.Name) // #nosec G204 if !conf.Teapot.SkipIssue {
outputBytes, err = cmd.CombinedOutput() cmd := exec.Command("joint-teapot", "joj3-create-result-issue",
output = re.ReplaceAllString(string(outputBytes), "") 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") { for _, line := range strings.Split(output, "\n") {
if line == "" { if line == "" {
continue continue
@ -73,5 +82,6 @@ func Run(conf conf.Conf) error {
slog.Error("joint-teapot joj3-create-result-issue", "err", err) slog.Error("joint-teapot joj3-create-result-issue", "err", err)
return err return err
} }
}
return nil return nil
} }

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)
slog.Debug("parser run start", "name", stageParser.Name,
"conf", stageParser.Conf)
parser, ok := parserMap[stageParser.Name]
if !ok { if !ok {
slog.Error("parser not found", "name", stage.ParserName) slog.Error("parser not found", "name", stageParser.Name)
err = fmt.Errorf("parser not found: %s", stage.ParserName) err = fmt.Errorf("parser not found: %s", stageParser.Name)
return return
} }
parserResults, forceQuit, err = parser.Run(executorResults, stage.ParserConf) tmpParserResults, forceQuit, err = parser.Run(
executorResults, stageParser.Conf)
if err != nil { if err != nil {
slog.Error("parser run error", "name", stage.ParserName, "error", err) slog.Error("parser run error", "name", stageParser.Name, "error", err)
return return
} }
slog.Debug("parser run done", "results", parserResults) 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)
}
}
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