Compare commits

..

No commits in common. "74ad5d6a8a2c84fe80cd52631bc1e12e5174a3a2" and "dd40a17476ede19c88187224e41d8eb3937af581" have entirely different histories.

8 changed files with 86 additions and 194 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"
GITHUB_ACTIONS="test" go test -coverprofile cover.out -v ./... go test -coverprofile cover.out -v ./...

View File

@ -13,48 +13,16 @@ 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
@ -159,35 +127,6 @@ 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.Stage.Stages { for _, s := range conf.Stages {
if s.Group != "" && group != s.Group { if s.Group != "" && group != s.Group {
continue continue
} }
@ -50,20 +50,12 @@ 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,
Executor: stage.StageExecutor{ ExecutorName: s.Executor.Name,
Name: s.Executor.Name, ExecutorCmds: cmds,
Cmds: cmds, ParserName: s.Parser.Name,
}, ParserConf: s.Parser.With,
Parsers: parsers,
}) })
} }
slog.Debug("stages generated", "stages", stages) slog.Debug("stages generated", "stages", stages)
@ -82,10 +74,7 @@ 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( executors.InitWithConf(conf.SandboxExecServer, conf.SandboxToken)
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)
@ -97,7 +86,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.Stage.OutputPath, results); err != nil { if err := outputResult(conf.OutputPath, results); err != nil {
slog.Error("output result", "error", err) slog.Error("output result", "error", err)
return err return err
} }

View File

@ -12,16 +12,12 @@ import (
) )
func Run(conf conf.Conf) error { func Run(conf conf.Conf) error {
actions := os.Getenv("GITHUB_ACTIONS") if conf.SkipTeapot {
if actions != "true" {
slog.Info("teapot exit", "GITHUB_ACTIONS", actions)
return nil return nil
} }
os.Setenv("LOG_FILE_PATH", conf.Teapot.LogPath) os.Setenv("LOG_FILE_PATH", "/home/tt/.cache/joint-teapot-debug.log")
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")
@ -33,55 +29,49 @@ 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") { if line == "" {
if line == "" { continue
continue
}
slog.Info("joint-teapot joj3-scoreboard", "output", line)
}
if err != nil {
slog.Error("joint-teapot joj3-scoreboard", "err", err)
return err
} }
slog.Info("joint-teapot joj3-scoreboard", "output", line)
} }
if !conf.Teapot.SkipFailedTable { if err != nil {
cmd := exec.Command("joint-teapot", "joj3-failed-table", slog.Error("joint-teapot joj3-scoreboard", "err", err)
envFilePath, conf.Stage.OutputPath, actor, conf.Teapot.GradingRepoName, return err
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") {
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
}
} }
if !conf.Teapot.SkipIssue { cmd = exec.Command("joint-teapot", "joj3-failed-table",
cmd := exec.Command("joint-teapot", "joj3-create-result-issue", envFilePath, conf.OutputPath, actor, conf.GradingRepoName, repoName,
envFilePath, conf.Stage.OutputPath, repoName, runNumber, conf.Name) // #nosec G204 runNumber, conf.FailedTablePath, 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("joint-teapot joj3-create-result-issue", "output", line)
} }
if err != nil { slog.Info("joint-teapot joj3-failed-table", "output", line)
slog.Error("joint-teapot joj3-create-result-issue", "err", err) }
return err if err != nil {
slog.Error("joint-teapot joj3-failed-table", "err", err)
return err
}
cmd = exec.Command("joint-teapot", "joj3-create-result-issue",
envFilePath, conf.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,9 +21,7 @@ const (
) )
type Conf struct { type Conf struct {
PassComment string `default:"🥳Passed!\n"` Cases []struct {
FailComment string `default:"🧐Failed...\n"`
Cases []struct {
IgnoreResultStatus bool IgnoreResultStatus bool
Outputs []struct { Outputs []struct {
Score int Score int
@ -74,9 +72,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 += conf.PassComment comment += "Pass!\n"
} else { } else {
comment += conf.FailComment comment += "Fail!\n"
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```\n", content) comment += fmt.Sprintf("```%s```\n", content)
} else { } else {
comment += "Not found.\n" comment += "Not found.\n"
} }

View File

@ -150,19 +150,12 @@ 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
Executor StageExecutor ExecutorName string
Parsers []StageParser ExecutorCmds []Cmd
ParserName string
ParserConf any
} }
type ParserResult struct { type ParserResult struct {

View File

@ -8,68 +8,51 @@ 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.Executor.Name) slog.Info("executor run start", "name", stage.ExecutorName)
slog.Debug("executor run start", "name", stage.Executor.Name, slog.Debug("executor run start", "name", stage.ExecutorName,
"cmds", stage.Executor.Cmds) "cmds", stage.ExecutorCmds)
executor, ok := executorMap[stage.Executor.Name] executor, ok := executorMap[stage.ExecutorName]
if !ok { if !ok {
slog.Error("executor not found", "name", stage.Executor.Name) slog.Error("executor not found", "name", stage.ExecutorName)
err = fmt.Errorf("executor not found: %s", stage.Executor.Name) err = fmt.Errorf("executor not found: %s", stage.ExecutorName)
return return
} }
executorResults, err = executor.Run(stage.Executor.Cmds) executorResults, err = executor.Run(stage.ExecutorCmds)
if err != nil { if err != nil {
slog.Error("executor run error", "name", stage.Executor.Name, "error", err) slog.Error("executor run error", "name", stage.ExecutorName, "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)
} }
parserResults = []ParserResult{} slog.Info("parser run start", "name", stage.ParserName)
stageForceQuit := false slog.Debug("parser run start", "name", stage.ParserName,
for _, stageParser := range stage.Parsers { "conf", stage.ParserConf)
slog.Info("parser run start", "name", stageParser.Name) parser, ok := parserMap[stage.ParserName]
slog.Debug("parser run start", "name", stageParser.Name, if !ok {
"conf", stageParser.Conf) slog.Error("parser not found", "name", stage.ParserName)
parser, ok := parserMap[stageParser.Name] err = fmt.Errorf("parser not found: %s", stage.ParserName)
if !ok { return
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: stageForceQuit, ForceQuit: forceQuit,
}) })
if stageForceQuit { if forceQuit {
break slog.Error("parser force quit", "name", stage.ParserName)
return
} }
} }
return return