fix: output format

This commit is contained in:
张泊明518370910136 2024-06-15 04:06:32 -04:00
parent ca590175d9
commit 2d5fe25d1a
GPG Key ID: D47306D7062CDA9D
10 changed files with 88 additions and 248 deletions

View File

@ -1,7 +1,6 @@
package main package main
import ( import (
"encoding/json"
"flag" "flag"
"fmt" "fmt"
"log/slog" "log/slog"
@ -38,10 +37,7 @@ func setupSlog() {
// Generally, err is used for runtime errors, and checkRes is used for the result of the checks. // Generally, err is used for runtime errors, and checkRes is used for the result of the checks.
func main() { func main() {
var info []healthcheck.CheckStage
var gitWhitelist, metaFile, releaseTags []string var gitWhitelist, metaFile, releaseTags []string
var tmp healthcheck.CheckStage
rootDir := flag.String("root", "", "") rootDir := flag.String("root", "", "")
repo := flag.String("repo", "", "") repo := flag.String("repo", "", "")
droneBranch := flag.String("droneBranch", "", "") droneBranch := flag.String("droneBranch", "", "")
@ -54,33 +50,35 @@ func main() {
parseMultiValueFlag(&releaseTags, "releaseTags", "") parseMultiValueFlag(&releaseTags, "releaseTags", "")
flag.Parse() flag.Parse()
setupSlog() setupSlog()
tmp = healthcheck.RepoSize() var err error
info = append(info, tmp) err = healthcheck.RepoSize()
tmp = healthcheck.ForbiddenCheck(*rootDir, gitWhitelist, *repo, *droneBranch)
info = append(info, tmp)
tmp = healthcheck.MetaCheck(*rootDir, metaFile)
info = append(info, tmp)
tmp = healthcheck.NonAsciiFiles(*rootDir)
info = append(info, tmp)
tmp = healthcheck.NonAsciiMsg(*rootDir)
info = append(info, tmp)
// TODO: find a way to test the release tag
tmp = healthcheck.CheckReleases(*rootDir, *releaseCategories, *releaseNumber)
info = append(info, tmp)
// FIXME: for drone usage
tmp = healthcheck.Verify(*rootDir, *adminDir)
info = append(info, tmp)
jsonData, err := json.Marshal(info)
if err != nil { if err != nil {
fmt.Fprint(os.Stderr, err) fmt.Printf("## Repo Size Check Failed:\n%s\n", err.Error())
os.Exit(1) }
err = healthcheck.ForbiddenCheck(*rootDir, gitWhitelist, *repo, *droneBranch)
if err != nil {
fmt.Printf("## Forbidden File Check Failed:\n%s\n", err.Error())
}
err = healthcheck.MetaCheck(*rootDir, metaFile)
if err != nil {
fmt.Printf("## Forbidden File Check Failed:\n%s\n", err.Error())
}
err = healthcheck.NonAsciiFiles(*rootDir)
if err != nil {
fmt.Printf("## Non-ASCII Characters File Check Failed:\n%s\n", err.Error())
}
err = healthcheck.NonAsciiMsg(*rootDir)
if err != nil {
fmt.Printf("## Non-ASCII Characters Commit Message Check Failed:\n%s\n", err.Error())
}
// TODO: find a way to test the release tag
err = healthcheck.CheckReleases(*rootDir, *releaseCategories, *releaseNumber)
if err != nil {
fmt.Printf("## Release Tag Check Failed:\n%s\n", err.Error())
}
// FIXME: for drone usage
err = healthcheck.VerifyDirectory(*rootDir, *adminDir)
if err != nil {
fmt.Printf("## Directory File Check Failed:\n%s\n", err.Error())
} }
fmt.Printf("%s", jsonData)
} }

View File

@ -7,41 +7,35 @@ import (
"github.com/criyle/go-judge/envexec" "github.com/criyle/go-judge/envexec"
) )
type Conf struct {
Score int
Comment string
}
type Healthcheck struct{} type Healthcheck struct{}
func Parse(executorResult stage.ExecutorResult, conf Conf) stage.ParserResult { func Parse(executorResult stage.ExecutorResult) (stage.ParserResult, bool) {
stdout := executorResult.Files["stdout"] stdout := executorResult.Files["stdout"]
stderr := executorResult.Files["stderr"] stderr := executorResult.Files["stderr"]
if executorResult.Status != stage.Status(envexec.StatusAccepted) { if executorResult.Status != stage.Status(envexec.StatusAccepted) {
return stage.ParserResult{ return stage.ParserResult{
Score: 0, Score: 0,
Comment: fmt.Sprintf( Comment: fmt.Sprintf(
"Unexpected executor status: %s.\nStderr: %s", "Unexpected executor status: %s.\nStdout: %s\nStderr: %s",
executorResult.Status, stderr, executorResult.Status, stdout, stderr,
), ),
} }, true
} }
return stage.ParserResult{ return stage.ParserResult{
Score: 0, Score: 0,
Comment: stdout, Comment: stdout,
} }, stdout != ""
} }
func (*Healthcheck) Run(results []stage.ExecutorResult, confAny any) ( func (*Healthcheck) Run(results []stage.ExecutorResult, confAny any) (
[]stage.ParserResult, bool, error, []stage.ParserResult, bool, error,
) { ) {
conf, err := stage.DecodeConf[Conf](confAny)
if err != nil {
return nil, true, err
}
var res []stage.ParserResult var res []stage.ParserResult
forceQuit := false
for _, result := range results { for _, result := range results {
res = append(res, Parse(result, *conf)) parserResult, forceQuitResult := Parse(result)
res = append(res, parserResult)
forceQuit = forceQuit || forceQuitResult
} }
return res, false, nil return res, forceQuit, nil
} }

View File

@ -1,6 +1,7 @@
package healthcheck package healthcheck
import ( import (
"fmt"
"log/slog" "log/slog"
"strings" "strings"
"unicode" "unicode"
@ -14,36 +15,23 @@ import (
// Otherwise, it iterates over each character in the message and checks if it is a non-ASCII character. // Otherwise, it iterates over each character in the message and checks if it is a non-ASCII character.
// If a non-ASCII character is found, it returns an error indicating not to use non-ASCII characters in commit messages. // If a non-ASCII character is found, it returns an error indicating not to use non-ASCII characters in commit messages.
// Otherwise, it returns nil indicating that the commit message is valid. // Otherwise, it returns nil indicating that the commit message is valid.
func NonAsciiMsg(root string) (jsonOut CheckStage) { func NonAsciiMsg(root string) error {
jsonOut = CheckStage{
Name: "NonAsciiMsg",
StdOut: "Checking for non-ASCII characters in commit message: ",
ExitCode: 0,
StdErr: "",
}
// cmd := exec.Command("git", "log", "--encoding=UTF-8", "--format=%B") // cmd := exec.Command("git", "log", "--encoding=UTF-8", "--format=%B")
repo, err := git.PlainOpen(root) repo, err := git.PlainOpen(root)
if err != nil { if err != nil {
jsonOut.StdOut += "Failed"
jsonOut.ExitCode = 1
slog.Error("openning git repo", "err", err) slog.Error("openning git repo", "err", err)
return jsonOut return fmt.Errorf("error openning git repo: %v", err)
} }
ref, err := repo.Head() ref, err := repo.Head()
if err != nil { if err != nil {
jsonOut.StdOut += "Failed"
jsonOut.ExitCode = 1
slog.Error("getting reference", "err", err) slog.Error("getting reference", "err", err)
return jsonOut return fmt.Errorf("error getting reference: %v", err)
} }
commits, err := repo.Log(&git.LogOptions{From: ref.Hash()}) commits, err := repo.Log(&git.LogOptions{From: ref.Hash()})
if err != nil { if err != nil {
jsonOut.StdOut += "Failed"
jsonOut.ExitCode = 1
slog.Error("getting commits", "err", err) slog.Error("getting commits", "err", err)
return jsonOut return fmt.Errorf("error getting commits: %v", err)
} }
var msgs []string var msgs []string
@ -52,10 +40,8 @@ func NonAsciiMsg(root string) (jsonOut CheckStage) {
return nil return nil
}) })
if err != nil { if err != nil {
jsonOut.StdOut += "Failed" slog.Error("iterating commits", "err", err)
jsonOut.ExitCode = 1 return fmt.Errorf("error iterating commits: %v", err)
slog.Error("converting log to string", "err", err)
return jsonOut
} }
var nonAsciiMsgs []string var nonAsciiMsgs []string
@ -73,11 +59,7 @@ func NonAsciiMsg(root string) (jsonOut CheckStage) {
} }
} }
if len(nonAsciiMsgs) > 0 { if len(nonAsciiMsgs) > 0 {
jsonOut.StdOut += "Failed" return fmt.Errorf("Non-ASCII characters in commit messages:\n" + strings.Join(nonAsciiMsgs, "\n"))
jsonOut.ExitCode = 105
jsonOut.StdErr = "Non-ASCII characters in commit messages:\n" + strings.Join(nonAsciiMsgs, "\n")
return jsonOut
} }
jsonOut.StdOut += "OK" return nil
return jsonOut
} }

View File

@ -16,7 +16,6 @@ func getForbiddens(root string, fileList []string) ([]string, error) {
var regexList []*regexp.Regexp var regexList []*regexp.Regexp
regexList, err := getRegex(fileList) regexList, err := getRegex(fileList)
if err != nil { if err != nil {
fmt.Println("Error compiling regex:", err)
return nil, err return nil, err
} }
@ -52,24 +51,11 @@ func getForbiddens(root string, fileList []string) ([]string, error) {
// forbiddenCheck checks for forbidden files in the specified root directory. // forbiddenCheck checks for forbidden files in the specified root directory.
// It prints the list of forbidden files found, along with instructions on how to fix them. // It prints the list of forbidden files found, along with instructions on how to fix them.
func ForbiddenCheck(rootDir string, regexList []string, repo string, droneBranch string) (jsonOut CheckStage) { func ForbiddenCheck(rootDir string, regexList []string, repo string, droneBranch string) error {
jsonOut = CheckStage{
Name: "forbiddenFile",
StdOut: "Checking forbidden files: ",
ExitCode: 0,
StdErr: "",
}
// INFO: test case
// regexList := []string{`\.$`, `\.git`, `\.drone.yml`, `Makefile`, `CMakeLists.txt`,`.*\.go`,`.*\.toml`, `.*\.c`, `.*\.cc`, `.*\.cpp`, `.*\.h`, `.*\.md`}
// rootDir = "/Users/zhouzhaojiacheng/Desktop/STUDENT_ORG/TechJI/Dev/joj/JOJ3"
forbids, err := getForbiddens(rootDir, regexList) forbids, err := getForbiddens(rootDir, regexList)
if err != nil { if err != nil {
jsonOut.StdOut += "Failed" slog.Error("getting forbiddens", "error", err)
jsonOut.ExitCode = 1 return fmt.Errorf("error getting forbiddens: %w", err)
slog.Error("get forbiddens", "error", err)
return jsonOut
} }
var message string var message string
@ -84,12 +70,7 @@ func ForbiddenCheck(rootDir string, regexList []string, repo string, droneBranch
message += fmt.Sprint(file, " ") message += fmt.Sprint(file, " ")
} }
message += fmt.Sprint("; do git filter-repo --force --invert-paths --path \\\"\\$i\\\"; done\ngit remote add origin ", repo, "\ngit push --set-upstream origin ", droneBranch, " --force") message += fmt.Sprint("; do git filter-repo --force --invert-paths --path \\\"\\$i\\\"; done\ngit remote add origin ", repo, "\ngit push --set-upstream origin ", droneBranch, " --force")
jsonOut.StdOut += "Failed" return fmt.Errorf(message)
jsonOut.ExitCode = 103
jsonOut.StdErr = message
return jsonOut
} else {
jsonOut.StdOut += "OK"
return jsonOut
} }
return nil
} }

View File

@ -19,7 +19,7 @@ func getMetas(rootDir string, fileList []string) ([]string, string, error) {
files, err := os.ReadDir(rootDir) files, err := os.ReadDir(rootDir)
if err != nil { if err != nil {
return nil, "", fmt.Errorf("Error reading directory:%w", err) return nil, "", fmt.Errorf("error reading directory: %w", err)
} }
matched := false matched := false
@ -60,28 +60,14 @@ func getMetas(rootDir string, fileList []string) ([]string, string, error) {
// metaCheck performs a check for metadata files in the specified root directory. // metaCheck performs a check for metadata files in the specified root directory.
// It prints a message if any required metadata files are missing. // It prints a message if any required metadata files are missing.
func MetaCheck(rootDir string, fileList []string) (jsonOut CheckStage) { func MetaCheck(rootDir string, fileList []string) error {
unmatchedList, umatchedRes, err := getMetas(rootDir, fileList) unmatchedList, umatchedRes, err := getMetas(rootDir, fileList)
jsonOut = CheckStage{
Name: "metaFile",
StdOut: "Checking the existence of meta file: ",
ExitCode: 0,
StdErr: "",
}
if err != nil { if err != nil {
jsonOut.ExitCode = 1 slog.Error("getting metas", "err", err)
slog.Error("get metas", "err", err) return fmt.Errorf("error getting metas: %w", err)
return jsonOut
} }
if len(unmatchedList) != 0 {
if len(unmatchedList) == 0 { return fmt.Errorf("%d important project files missing\n"+umatchedRes, len(unmatchedList))
jsonOut.StdOut += "OK"
return jsonOut
} else {
jsonOut.ExitCode = 104
jsonOut.StdOut += "Failed"
jsonOut.StdErr = fmt.Sprintf("%d important project files missing\n"+umatchedRes, len(unmatchedList))
return jsonOut
} }
return nil
} }

View File

@ -61,31 +61,14 @@ func getNonAscii(root string) ([]string, error) {
// nonAsciiFiles checks for non-ASCII characters in files within the specified root directory. // nonAsciiFiles checks for non-ASCII characters in files within the specified root directory.
// It prints a message with the paths to files containing non-ASCII characters, if any. // It prints a message with the paths to files containing non-ASCII characters, if any.
func NonAsciiFiles(root string) (jsonOut CheckStage) { func NonAsciiFiles(root string) error {
jsonOut = CheckStage{
Name: "NonAsciiFiles",
StdOut: "Checking for non-ascii files: ",
ExitCode: 0,
StdErr: "",
}
nonAscii, err := getNonAscii(root) nonAscii, err := getNonAscii(root)
if err != nil { if err != nil {
jsonOut.StdOut += "Failed" slog.Error("getting non-ascii", "err", err)
jsonOut.ExitCode = 1 return fmt.Errorf("error getting non-ascii: %w", err)
slog.Error("get non-ascii", "err", err)
return jsonOut
} }
var nonAsciiRes string
if len(nonAscii) > 0 { if len(nonAscii) > 0 {
nonAsciiRes = fmt.Sprintf("Non-ASCII characters found in the following files:\n" + strings.Join(nonAscii, "\n")) return fmt.Errorf("Non-ASCII characters found in the following files:\n" + strings.Join(nonAscii, "\n"))
jsonOut.ExitCode = 105
jsonOut.StdOut += "Failed"
} else {
jsonOut.StdOut += "OK"
} }
return nil
jsonOut.StdErr = nonAsciiRes
return jsonOut
} }

View File

@ -2,7 +2,6 @@ package healthcheck
import ( import (
"fmt" "fmt"
"log"
"github.com/go-git/go-git/v5" "github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/plumbing" "github.com/go-git/go-git/v5/plumbing"
@ -19,12 +18,12 @@ func catTags(all []string) (out string) {
func getTagsFromRepo(repoPath string) ([]string, error) { func getTagsFromRepo(repoPath string) ([]string, error) {
repo, err := git.PlainOpen(repoPath) repo, err := git.PlainOpen(repoPath)
if err != nil { if err != nil {
return nil, fmt.Errorf("Cannot open repo: %v", err) return nil, fmt.Errorf("error opening repo: %v", err)
} }
refs, err := repo.Tags() refs, err := repo.Tags()
if err != nil { if err != nil {
return nil, fmt.Errorf("Cannot get tags: %v", err) return nil, fmt.Errorf("error getting tags: %v", err)
} }
var tags []string var tags []string
@ -33,27 +32,18 @@ func getTagsFromRepo(repoPath string) ([]string, error) {
return nil return nil
}) })
if err != nil { if err != nil {
return nil, fmt.Errorf("Error while iterating tags: %v", err) return nil, fmt.Errorf("error iterating tags: %v", err)
} }
return tags, nil return tags, nil
} }
func CheckReleases(repoPath string, category string, n int) (jsonOut CheckStage) { func CheckReleases(repoPath string, category string, n int) error {
jsonOut = CheckStage{
Name: "ReleaseCheck",
StdOut: "Checking release tag: ",
ExitCode: 0,
StdErr: "",
}
tags, err := getTagsFromRepo(repoPath) tags, err := getTagsFromRepo(repoPath)
if err != nil { if err != nil {
log.Fatalf("Error in getting tags: %v", err) return fmt.Errorf("error getting tags: %v", err)
} }
var prefix string var prefix string
switch category { switch category {
case "exam": case "exam":
prefix = "e" prefix = "e"
@ -64,7 +54,6 @@ func CheckReleases(repoPath string, category string, n int) (jsonOut CheckStage)
default: default:
prefix = "a" prefix = "a"
} }
target := prefix + fmt.Sprintf("%d", n) target := prefix + fmt.Sprintf("%d", n)
found := false found := false
for _, tag := range tags { for _, tag := range tags {
@ -75,14 +64,7 @@ func CheckReleases(repoPath string, category string, n int) (jsonOut CheckStage)
} }
if !found { if !found {
tagList := catTags(tags) tagList := catTags(tags)
jsonOut.ExitCode = 107 return fmt.Errorf("Wrong release tag '%s'. Please use one of %s.", target, tagList)
jsonOut.StdOut = "Failed"
jsonOut.StdErr = fmt.Sprintf("wrong release tag '%s', please use one of %s aborting", target, tagList)
return jsonOut
} }
return nil
jsonOut.StdOut += "OK"
jsonOut.ExitCode = 0
jsonOut.StdErr = "Fine"
return jsonOut
} }

View File

@ -1,6 +1,7 @@
package healthcheck package healthcheck
import ( import (
"fmt"
"log/slog" "log/slog"
"os/exec" "os/exec"
"strconv" "strconv"
@ -9,25 +10,15 @@ 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() (jsonOut CheckStage) { func RepoSize() error {
jsonOut = CheckStage{
Name: "RepoSize",
StdOut: "Checking repository size: ",
ExitCode: 0,
StdErr: "",
}
// 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")
output, err := cmd.CombinedOutput() output, err := cmd.CombinedOutput()
if err != nil { if err != nil {
jsonOut.StdOut += "Failed"
jsonOut.ExitCode = 1
slog.Error("running git command:", "err", err) slog.Error("running git command:", "err", err)
return jsonOut return fmt.Errorf("error running git command: %w", err)
} }
lines := strings.Split(string(output), "\n") lines := strings.Split(string(output), "\n")
var sum int var sum int
for _, line := range lines { for _, line := range lines {
@ -36,21 +27,14 @@ func RepoSize() (jsonOut CheckStage) {
sizeStr := fields[1] sizeStr := fields[1]
size, err := strconv.Atoi(sizeStr) size, err := strconv.Atoi(sizeStr)
if err != nil { if err != nil {
jsonOut.StdOut += "Failed"
jsonOut.ExitCode = 1
slog.Error("running git command:", "err", err) slog.Error("running git command:", "err", err)
return jsonOut return fmt.Errorf("error running git command: %w", err)
} }
sum += size sum += size
} }
} }
if sum > 2048 {
if sum <= 2048 { return fmt.Errorf("Repository larger than 2MB. Please clean up or contact the teaching team.")
jsonOut.StdOut += "OK"
return jsonOut
} }
jsonOut.StdOut += "Failed" return nil
jsonOut.ExitCode = 100
jsonOut.StdErr = "repository larger than 2MB, please clean up or contact the teaching team."
return jsonOut
} }

View File

@ -5,15 +5,6 @@ import (
"regexp" "regexp"
) )
// For ExitCode, see https://focs.ji.sjtu.edu.cn/git/TAs/resources/src/branch/drone/dronelib.checks
// 1 for unrecoverable error and 0 for succeses
type CheckStage struct {
Name string `json:"name"`
StdOut string `json:"stdout"`
ExitCode int `json:"exit code"`
StdErr string `json:"stderr"`
}
// addExt appends the specified extension to each file name in the given fileList. // addExt appends the specified extension to each file name in the given fileList.
// It modifies the original fileList in place. // It modifies the original fileList in place.
func addExt(fileList []string, ext string) { func addExt(fileList []string, ext string) {

View File

@ -47,68 +47,27 @@ func filesMatch(file1, file2 string) (bool, error) {
return true, nil return true, nil
} }
// compareDirectories compares the contents of two directories. // VerifyDirectory checks if the contents of two directories are identical.
func compareDirectories(dir1, dir2 string, jsonOut *CheckStage) error { func VerifyDirectory(rootDir, compareDir string) error {
allMatch := true err := filepath.Walk(rootDir, func(path string, info os.FileInfo, err error) error {
var message string
err := filepath.Walk(dir1, func(path string, info os.FileInfo, err error) error {
if err != nil { if err != nil {
return err return fmt.Errorf("error walking directory: %w", err)
} }
if !info.IsDir() { if !info.IsDir() {
relPath, _ := filepath.Rel(dir1, path) relPath, _ := filepath.Rel(rootDir, path)
file2 := filepath.Join(dir2, relPath) file2 := filepath.Join(compareDir, relPath)
if !fileExists(file2) { if !fileExists(file2) {
// fmt.Printf("File %s in %s is missing in %s\n, please immediately revert your changes!\n", relPath, dir1, dir2) return fmt.Errorf("File %s in %s is missing in %s. Please immediately revert your changes!\n", relPath, rootDir, compareDir)
message += "File missing"
allMatch = false
jsonOut.StdOut += "Failed"
jsonOut.ExitCode = 101
jsonOut.StdErr = message
return nil
} }
// fmt.Printf("Checking integrity of %s:\n", path)
match, err := filesMatch(path, file2) match, err := filesMatch(path, file2)
if err != nil { if err != nil {
return err return fmt.Errorf("error matching files: %w", err)
} }
if !match { if !match {
// fmt.Printf("File %s in %s is not identical to %s\nPlease revert your changes or contact the teaching team if you have a valid reason for adjusting them.\n", relPath, dir1, dir2) return fmt.Errorf("File %s in %s is not identical to %s. Please revert your changes or contact the teaching team if you have a valid reason for adjusting them.\n", relPath, rootDir, compareDir)
message += "File is not identical"
allMatch = false
jsonOut.StdOut += "Failed"
jsonOut.ExitCode = 101
jsonOut.StdErr = message
} }
} }
return nil return nil
}) })
if allMatch {
jsonOut.StdOut += "OK!"
} else {
jsonOut.StdOut += "Failed!"
}
return err return err
} }
// Verify checks if the contents of two directories are identical.
func Verify(rootDir, compareDir string) CheckStage {
jsonOut := CheckStage{
Name: "verifyFile",
StdOut: "Checking files to be verified: ",
ExitCode: 0,
StdErr: "",
}
err := compareDirectories(rootDir, compareDir, &jsonOut)
// fmt.Println("Comparison finished ")
if err != nil {
fmt.Println("Error:", err)
}
return jsonOut
}