feat(cmd/joj3): remove hardcoded teapot part (#83) [force build]
All checks were successful
submodules sync / sync (push) Successful in 35s
build / build (push) Successful in 1m11s
build / trigger-build-image (push) Successful in 7s

This commit is contained in:
张泊明518370910136 2025-02-01 08:51:45 -05:00
parent 290b614159
commit 10ff3ebfa1
GPG Key ID: D47306D7062CDA9D
8 changed files with 37 additions and 212 deletions

View File

@ -95,12 +95,6 @@ These steps are executed in runner-images. We use `sudo -u tt` to elevate the pe
- The parser can return a force quit, which means all the stages after it will be skipped, but the remaining parsers in the current stage will run. - The parser can return a force quit, which means all the stages after it will be skipped, but the remaining parsers in the current stage will run.
5. Generate results. 5. Generate results.
- Once the running of stages is done, it will generate a result file where the path is specified in the configuration file. - Once the running of stages is done, it will generate a result file where the path is specified in the configuration file.
2. Run Joint-Teapot.
1. Generally speaking, it reads the JOJ3 results file and output results on Gitea.
2. With `joint-teapot joj3-all`, it will do the following things:
1. Create/Edit an issue in the submitter's repo to show the results.
2. Update the scoreboard file in grading repo.
3. Update the failed table file in grading repo.
## Components ## Components

34
cmd/joj3/env/env.go vendored
View File

@ -7,23 +7,27 @@ import (
) )
const ( const (
ConfName = "JOJ3_CONF_NAME" ConfName = "JOJ3_CONF_NAME"
Groups = "JOJ3_GROUPS" Groups = "JOJ3_GROUPS"
RunID = "JOJ3_RUN_ID" RunID = "JOJ3_RUN_ID"
CommitMsg = "JOJ3_COMMIT_MSG"
ForceQuitStageName = "JOJ3_FORCE_QUIT_STAGE_NAME"
) )
type Attribute struct { type Attribute struct {
ConfName string ConfName string
Groups string CommitMsg string
RunID string Groups string
Actor string RunID string
Repository string Actor string
Sha string Repository string
Ref string Sha string
Workflow string Ref string
RunNumber string Workflow string
ActorName string RunNumber string
ActorID string ActorName string
ActorID string
ForceQuitStageName string
} }
var Attr Attribute var Attr Attribute
@ -51,4 +55,6 @@ func Set() {
os.Setenv(ConfName, Attr.ConfName) os.Setenv(ConfName, Attr.ConfName)
os.Setenv(Groups, Attr.Groups) os.Setenv(Groups, Attr.Groups)
os.Setenv(RunID, Attr.RunID) os.Setenv(RunID, Attr.RunID)
os.Setenv(CommitMsg, Attr.CommitMsg)
os.Setenv(ForceQuitStageName, Attr.ForceQuitStageName)
} }

View File

@ -9,7 +9,6 @@ import (
"github.com/joint-online-judge/JOJ3/cmd/joj3/env" "github.com/joint-online-judge/JOJ3/cmd/joj3/env"
"github.com/joint-online-judge/JOJ3/cmd/joj3/stage" "github.com/joint-online-judge/JOJ3/cmd/joj3/stage"
"github.com/joint-online-judge/JOJ3/cmd/joj3/teapot"
"github.com/joint-online-judge/JOJ3/internal/conf" "github.com/joint-online-judge/JOJ3/internal/conf"
internalStage "github.com/joint-online-judge/JOJ3/internal/stage" internalStage "github.com/joint-online-judge/JOJ3/internal/stage"
) )
@ -33,36 +32,7 @@ func init() {
func mainImpl() (err error) { func mainImpl() (err error) {
confObj := new(conf.Conf) confObj := new(conf.Conf)
var stageResults []internalStage.StageResult
var forceQuitStageName string
var teapotRunResult teapot.RunResult
var commitMsg string
// summarize
defer func() {
totalScore := 0
for _, stageResult := range stageResults {
for _, result := range stageResult.Results {
totalScore += result.Score
}
}
cappedTotalScore := totalScore
if confObj.MaxTotalScore >= 0 {
cappedTotalScore = min(totalScore, confObj.MaxTotalScore)
}
slog.Info(
"joj3 summary",
"totalScore", totalScore,
"cappedTotalScore", cappedTotalScore,
"forceQuit", forceQuitStageName != "",
"forceQuitStageName", forceQuitStageName,
"issue", teapotRunResult.Issue,
"action", teapotRunResult.Action,
"sha", teapotRunResult.Sha,
"commitMsg", commitMsg,
"error", err,
)
}()
if err := setupSlog(confObj); err != nil { // before conf is loaded if err := setupSlog(confObj); err != nil { // before conf is loaded
slog.Error("setup slog", "error", err) slog.Error("setup slog", "error", err)
return err return err
@ -78,11 +48,12 @@ func mainImpl() (err error) {
fallbackConfFileName = confFileName fallbackConfFileName = confFileName
} }
slog.Info("start joj3", "version", Version) slog.Info("start joj3", "version", Version)
commitMsg, err = conf.GetCommitMsg() commitMsg, err := conf.GetCommitMsg()
if err != nil { if err != nil {
slog.Error("get commit msg", "error", err) slog.Error("get commit msg", "error", err)
return err return err
} }
env.Attr.CommitMsg = commitMsg
confPath, confStat, conventionalCommit, err := conf.GetConfPath( confPath, confStat, conventionalCommit, err := conf.GetConfPath(
confFileRoot, confFileName, fallbackConfFileName, commitMsg, tag) confFileRoot, confFileName, fallbackConfFileName, commitMsg, tag)
if err != nil { if err != nil {
@ -118,20 +89,20 @@ func mainImpl() (err error) {
// run stages // run stages
groups := conf.MatchGroups(confObj, conventionalCommit) groups := conf.MatchGroups(confObj, conventionalCommit)
env.Attr.Groups = strings.Join(groups, ",") env.Attr.Groups = strings.Join(groups, ",")
env.Set() _, forceQuitStageName, err := stage.Run(
stageResults, forceQuitStageName, err = stage.Run( confObj,
confObj, groups, groups,
func(
stageResults []internalStage.StageResult,
forceQuitStageName string,
) {
env.Attr.ForceQuitStageName = forceQuitStageName
env.Set()
},
) )
if err != nil { if err != nil {
slog.Error("stage run", "error", err) slog.Error("stage run", "error", err)
} }
// run teapot
teapotRunResult, err = teapot.Run(confObj)
if err != nil {
slog.Error("teapot run", "error", err)
return err
}
if forceQuitStageName != "" { if forceQuitStageName != "" {
slog.Info("stage force quit", "name", forceQuitStageName) slog.Info("stage force quit", "name", forceQuitStageName)
return fmt.Errorf("stage force quit with name %s", forceQuitStageName) return fmt.Errorf("stage force quit with name %s", forceQuitStageName)

View File

@ -132,7 +132,11 @@ func newErrorStageResults(err error) ([]stage.StageResult, string) {
}, "Internal Error" }, "Internal Error"
} }
func Run(conf *conf.Conf, groups []string) ( func Run(
conf *conf.Conf,
groups []string,
onStagesComplete func([]stage.StageResult, string),
) (
stageResults []stage.StageResult, forceQuitStageName string, err error, stageResults []stage.StageResult, forceQuitStageName string, err error,
) { ) {
executors.InitWithConf( executors.InitWithConf(

View File

@ -1,57 +0,0 @@
package teapot
import (
"bufio"
"bytes"
"log/slog"
"os/exec"
"regexp"
"sync"
)
func runCommand(args []string) (
stdoutBuf *bytes.Buffer, err error,
) {
re := regexp.MustCompile(`\x1b\[[0-9;]*[a-zA-Z]`)
cmd := exec.Command("joint-teapot", args...) // #nosec G204
stdoutBuf = new(bytes.Buffer)
cmd.Stdout = stdoutBuf
stderr, err := cmd.StderrPipe()
if err != nil {
slog.Error("stderr pipe", "error", err)
return
}
var wg sync.WaitGroup
wg.Add(1)
scanner := bufio.NewScanner(stderr)
go func() {
for scanner.Scan() {
text := re.ReplaceAllString(scanner.Text(), "")
if text == "" {
continue
}
slog.Info("joint-teapot", "stderr", text)
}
wg.Done()
if scanner.Err() != nil {
slog.Error("stderr scanner", "error", scanner.Err())
}
}()
if err = cmd.Start(); err != nil {
slog.Error("cmd start", "error", err)
return
}
wg.Wait()
if err = cmd.Wait(); err != nil {
if exitErr, ok := err.(*exec.ExitError); ok {
exitCode := exitErr.ExitCode()
slog.Error("cmd completed with non-zero exit code",
"error", err,
"exitCode", exitCode)
} else {
slog.Error("cmd wait", "error", err)
}
return
}
return
}

View File

@ -1,78 +0,0 @@
package teapot
import (
"encoding/json"
"fmt"
"log/slog"
"os"
"strconv"
"strings"
"github.com/joint-online-judge/JOJ3/cmd/joj3/env"
"github.com/joint-online-judge/JOJ3/internal/conf"
)
type RunResult struct {
Issue int `json:"issue"`
Action int `json:"action"`
Sha string `json:"sha"`
}
func Run(conf *conf.Conf) (
runResult RunResult, err error,
) {
if conf.Teapot.Skip {
slog.Info("teapot skip")
return
}
os.Setenv("LOG_FILE_PATH", conf.Teapot.LogPath)
if env.Attr.Actor == "" ||
env.Attr.Repository == "" ||
strings.Count(env.Attr.Repository, "/") != 1 ||
env.Attr.RunNumber == "" {
slog.Error("teapot env not set")
err = fmt.Errorf("teapot env not set")
return
}
repoParts := strings.Split(env.Attr.Repository, "/")
repoName := repoParts[1]
skipIssueArg := "--no-skip-result-issue"
if conf.Teapot.SkipIssue {
skipIssueArg = "--skip-result-issue"
}
skipScoreboardArg := "--no-skip-scoreboard"
if conf.Teapot.SkipScoreboard {
skipScoreboardArg = "--skip-scoreboard"
}
skipFailedTableArg := "--no-skip-failed-table"
if conf.Teapot.SkipFailedTable {
skipFailedTableArg = "--skip-failed-table"
}
submitterInIssueTitleArg := "--no-submitter-in-issue-title"
if conf.Teapot.SubmitterInIssueTitle {
submitterInIssueTitleArg = "--submitter-in-issue-title"
}
args := []string{
"joj3-all", conf.Teapot.EnvFilePath, conf.Stage.OutputPath,
env.Attr.Actor, conf.Teapot.GradingRepoName, repoName,
env.Attr.RunNumber, conf.Teapot.ScoreboardPath,
conf.Teapot.FailedTablePath,
conf.Name, env.Attr.Sha, env.Attr.RunID,
env.Attr.Groups,
"--max-total-score", strconv.Itoa(conf.MaxTotalScore),
skipIssueArg, skipScoreboardArg,
skipFailedTableArg, submitterInIssueTitleArg,
}
stdoutBuf, err := runCommand(args)
if err != nil {
slog.Error("teapot run exec", "error", err)
return
}
if json.Unmarshal(stdoutBuf.Bytes(), &runResult) != nil {
slog.Error("unmarshal teapot result", "error", err,
"stdout", stdoutBuf.String())
return
}
slog.Info("teapot result", "result", runResult)
return
}

View File

@ -44,7 +44,6 @@ var (
checkFileNameList string checkFileNameList string
checkFileSumList string checkFileSumList string
metaFile []string metaFile []string
confPath string
showVersion *bool showVersion *bool
Version string Version string
) )
@ -56,7 +55,6 @@ func init() {
flag.StringVar(&checkFileNameList, "checkFileNameList", "", "comma-separated list of files to check") flag.StringVar(&checkFileNameList, "checkFileNameList", "", "comma-separated list of files to check")
flag.StringVar(&checkFileSumList, "checkFileSumList", "", "comma-separated list of expected checksums") flag.StringVar(&checkFileSumList, "checkFileSumList", "", "comma-separated list of expected checksums")
parseMultiValueFlag(&metaFile, "meta", "meta files to check") parseMultiValueFlag(&metaFile, "meta", "meta files to check")
flag.StringVar(&confPath, "confPath", "", "path to conf file for teapot check") // TODO: remove me
} }
func main() { func main() {

View File

@ -40,19 +40,6 @@ type Conf struct {
Stages []ConfStage Stages []ConfStage
PostStages []ConfStage PostStages []ConfStage
} }
Teapot struct {
Skip bool `default:"false"`
LogPath string `default:"/home/tt/.cache/joint-teapot-debug.log"`
EnvFilePath string `default:"/home/tt/.config/teapot/teapot.env"`
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"`
SubmitterInIssueTitle bool `default:"true"`
Groups []ConfGroup
}
} }
type OptionalCmd struct { type OptionalCmd struct {