refactor(parser/diff): split to smaller functions
This commit is contained in:
parent
c3ba14d321
commit
997e7dde20
|
@ -7,24 +7,28 @@ import "github.com/joint-online-judge/JOJ3/internal/stage"
|
|||
|
||||
var name = "diff"
|
||||
|
||||
type Output struct {
|
||||
Score int
|
||||
Filename string
|
||||
AnswerPath string
|
||||
CompareSpace bool
|
||||
AlwaysHide bool
|
||||
ForceQuitOnDiff bool
|
||||
MaxDiffLength int `default:"2048"` // just for reference
|
||||
MaxDiffLines int `default:"50"` // just for reference
|
||||
HideCommonPrefix bool
|
||||
}
|
||||
|
||||
type Case struct {
|
||||
Outputs []Output
|
||||
}
|
||||
|
||||
type Conf struct {
|
||||
PassComment string `default:"🥳Passed!"`
|
||||
FailComment string `default:"🧐Failed..."`
|
||||
FailOnNotAccepted bool `default:"true"`
|
||||
ForceQuitOnFailed bool `default:"false"`
|
||||
Cases []struct {
|
||||
Outputs []struct {
|
||||
Score int
|
||||
Filename string
|
||||
AnswerPath string
|
||||
CompareSpace bool
|
||||
AlwaysHide bool
|
||||
ForceQuitOnDiff bool
|
||||
MaxDiffLength int `default:"2048"` // just for reference
|
||||
MaxDiffLines int `default:"50"` // just for reference
|
||||
HideCommonPrefix bool
|
||||
}
|
||||
}
|
||||
Cases []Case
|
||||
}
|
||||
|
||||
type Diff struct{}
|
||||
|
|
|
@ -9,7 +9,7 @@ import (
|
|||
"github.com/joint-online-judge/JOJ3/internal/stage"
|
||||
)
|
||||
|
||||
func (*Diff) Run(results []stage.ExecutorResult, confAny any) (
|
||||
func (d *Diff) Run(results []stage.ExecutorResult, confAny any) (
|
||||
[]stage.ParserResult, bool, error,
|
||||
) {
|
||||
conf, err := stage.DecodeConf[Conf](confAny)
|
||||
|
@ -24,124 +24,168 @@ func (*Diff) Run(results []stage.ExecutorResult, confAny any) (
|
|||
forceQuit := false
|
||||
for i, caseConf := range conf.Cases {
|
||||
result := results[i]
|
||||
score := 0
|
||||
comment := ""
|
||||
if conf.FailOnNotAccepted &&
|
||||
result.Status != stage.StatusAccepted {
|
||||
if conf.ForceQuitOnFailed {
|
||||
forceQuit = true
|
||||
}
|
||||
comment += conf.FailComment + "\n"
|
||||
comment += "Executor status not `Accepted`\n"
|
||||
} else {
|
||||
for _, output := range caseConf.Outputs {
|
||||
answer, err := os.ReadFile(output.AnswerPath)
|
||||
if err != nil {
|
||||
return nil, true, err
|
||||
}
|
||||
answerStr := string(answer)
|
||||
resultStr := result.Files[output.Filename]
|
||||
isSame := stringsEqual(
|
||||
answerStr,
|
||||
resultStr,
|
||||
output.CompareSpace,
|
||||
)
|
||||
slog.Debug(
|
||||
"compare",
|
||||
"filename", output.Filename,
|
||||
"answerPath", output.AnswerPath,
|
||||
"actualLength", len(resultStr),
|
||||
"answerLength", len(answerStr),
|
||||
"index", i,
|
||||
"isSame", isSame,
|
||||
)
|
||||
// If no difference, assign score
|
||||
if isSame {
|
||||
score += output.Score
|
||||
comment += conf.PassComment + "\n"
|
||||
} else {
|
||||
if output.ForceQuitOnDiff || conf.ForceQuitOnFailed {
|
||||
forceQuit = true
|
||||
}
|
||||
comment += conf.FailComment + "\n"
|
||||
comment += fmt.Sprintf("Difference found in `%s`\n",
|
||||
output.Filename)
|
||||
if !output.AlwaysHide {
|
||||
if output.MaxDiffLength == 0 { // real default value
|
||||
output.MaxDiffLength = 2048
|
||||
}
|
||||
if output.MaxDiffLines == 0 { // real default value
|
||||
output.MaxDiffLines = 50
|
||||
}
|
||||
// Convert answer to string and split by lines
|
||||
truncated := false
|
||||
if len(answerStr) > output.MaxDiffLength {
|
||||
answerStr = answerStr[:output.MaxDiffLength]
|
||||
truncated = true
|
||||
}
|
||||
if len(resultStr) > output.MaxDiffLength {
|
||||
resultStr = resultStr[:output.MaxDiffLength]
|
||||
truncated = true
|
||||
}
|
||||
answerLines := strings.Split(answerStr, "\n")
|
||||
resultLines := strings.Split(resultStr, "\n")
|
||||
commonPrefixLineCount := 0
|
||||
if output.HideCommonPrefix {
|
||||
n := 0
|
||||
for ; n < len(answerLines) &&
|
||||
n < len(resultLines) &&
|
||||
stringsEqual(
|
||||
answerLines[n],
|
||||
resultLines[n],
|
||||
output.CompareSpace,
|
||||
); n += 1 {
|
||||
}
|
||||
if n > 0 {
|
||||
answerLines = answerLines[n-1:]
|
||||
resultLines = resultLines[n-1:]
|
||||
commonPrefixLineCount = n
|
||||
}
|
||||
}
|
||||
if len(answerLines) > output.MaxDiffLines {
|
||||
answerLines = answerLines[:output.MaxDiffLines]
|
||||
truncated = true
|
||||
}
|
||||
if len(resultLines) > output.MaxDiffLines {
|
||||
resultLines = resultLines[:output.MaxDiffLines]
|
||||
truncated = true
|
||||
}
|
||||
diffs := patienceDiff(
|
||||
answerLines,
|
||||
resultLines,
|
||||
func(a, b string) bool {
|
||||
return stringsEqual(a, b, output.CompareSpace)
|
||||
})
|
||||
diffOutput := diffText(diffs)
|
||||
diffOutput = strings.TrimSuffix(diffOutput, "\n ")
|
||||
if truncated {
|
||||
diffOutput += "\n\n(truncated)"
|
||||
}
|
||||
if commonPrefixLineCount > 0 {
|
||||
diffOutput = fmt.Sprintf(
|
||||
"(%d line(s) of common prefix hidden)\n\n",
|
||||
commonPrefixLineCount,
|
||||
) + diffOutput
|
||||
}
|
||||
comment += fmt.Sprintf(
|
||||
"```diff\n%s\n```\n",
|
||||
diffOutput,
|
||||
)
|
||||
} else {
|
||||
comment += "(content hidden)\n"
|
||||
}
|
||||
}
|
||||
}
|
||||
parserResult, fq, err := d.processCase(caseConf, result, conf, i)
|
||||
if err != nil {
|
||||
return nil, true, err
|
||||
}
|
||||
res = append(res, parserResult)
|
||||
if fq {
|
||||
forceQuit = true
|
||||
}
|
||||
res = append(res, stage.ParserResult{
|
||||
Score: score,
|
||||
Comment: comment,
|
||||
})
|
||||
}
|
||||
|
||||
return res, forceQuit, nil
|
||||
}
|
||||
|
||||
// processCase handles a single test case.
|
||||
func (d *Diff) processCase(caseConf Case, result stage.ExecutorResult, conf *Conf, index int) (stage.ParserResult, bool, error) {
|
||||
score := 0
|
||||
comment := ""
|
||||
forceQuit := false
|
||||
|
||||
if conf.FailOnNotAccepted && result.Status != stage.StatusAccepted {
|
||||
if conf.ForceQuitOnFailed {
|
||||
forceQuit = true
|
||||
}
|
||||
comment += conf.FailComment + "\n"
|
||||
comment += "Executor status not `Accepted`\n"
|
||||
} else {
|
||||
for _, output := range caseConf.Outputs {
|
||||
outputScore, outputComment, outputForceQuit, err := d.processOutput(output, result, conf, index)
|
||||
if err != nil {
|
||||
return stage.ParserResult{}, true, err
|
||||
}
|
||||
score += outputScore
|
||||
comment += outputComment
|
||||
if outputForceQuit {
|
||||
forceQuit = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return stage.ParserResult{
|
||||
Score: score,
|
||||
Comment: comment,
|
||||
}, forceQuit, nil
|
||||
}
|
||||
|
||||
// processOutput handles a single output comparison.
|
||||
func (d *Diff) processOutput(output Output, result stage.ExecutorResult, conf *Conf, index int) (int, string, bool, error) {
|
||||
answer, err := os.ReadFile(output.AnswerPath)
|
||||
if err != nil {
|
||||
return 0, "", true, err
|
||||
}
|
||||
answerStr := string(answer)
|
||||
resultStr := result.Files[output.Filename]
|
||||
|
||||
isSame := stringsEqual(
|
||||
answerStr,
|
||||
resultStr,
|
||||
output.CompareSpace,
|
||||
)
|
||||
|
||||
slog.Debug(
|
||||
"compare",
|
||||
"filename", output.Filename,
|
||||
"answerPath", output.AnswerPath,
|
||||
"actualLength", len(resultStr),
|
||||
"answerLength", len(answerStr),
|
||||
"index", index,
|
||||
"isSame", isSame,
|
||||
)
|
||||
|
||||
if isSame {
|
||||
return output.Score, conf.PassComment + "\n", false, nil
|
||||
}
|
||||
|
||||
// They are different.
|
||||
forceQuit := false
|
||||
if output.ForceQuitOnDiff || conf.ForceQuitOnFailed {
|
||||
forceQuit = true
|
||||
}
|
||||
comment := conf.FailComment + "\n"
|
||||
comment += fmt.Sprintf("Difference found in `%s`\n", output.Filename)
|
||||
|
||||
if !output.AlwaysHide {
|
||||
diffComment := d.generateDiffComment(answerStr, resultStr, output)
|
||||
comment += diffComment
|
||||
} else {
|
||||
comment += "(content hidden)\n"
|
||||
}
|
||||
|
||||
return 0, comment, forceQuit, nil
|
||||
}
|
||||
|
||||
// generateDiffComment generates a diff comment for the given strings.
|
||||
func (d *Diff) generateDiffComment(answerStr, resultStr string, output Output) string {
|
||||
if output.MaxDiffLength == 0 { // real default value
|
||||
output.MaxDiffLength = 2048
|
||||
}
|
||||
if output.MaxDiffLines == 0 { // real default value
|
||||
output.MaxDiffLines = 50
|
||||
}
|
||||
|
||||
truncated := false
|
||||
if len(answerStr) > output.MaxDiffLength {
|
||||
answerStr = answerStr[:output.MaxDiffLength]
|
||||
truncated = true
|
||||
}
|
||||
if len(resultStr) > output.MaxDiffLength {
|
||||
resultStr = resultStr[:output.MaxDiffLength]
|
||||
truncated = true
|
||||
}
|
||||
|
||||
answerLines := strings.Split(answerStr, "\n")
|
||||
resultLines := strings.Split(resultStr, "\n")
|
||||
commonPrefixLineCount := 0
|
||||
|
||||
if output.HideCommonPrefix {
|
||||
n := 0
|
||||
for ; n < len(answerLines) &&
|
||||
n < len(resultLines) &&
|
||||
stringsEqual(
|
||||
answerLines[n],
|
||||
resultLines[n],
|
||||
output.CompareSpace,
|
||||
); n += 1 {
|
||||
}
|
||||
if n > 0 {
|
||||
answerLines = answerLines[n-1:]
|
||||
resultLines = resultLines[n-1:]
|
||||
commonPrefixLineCount = n
|
||||
}
|
||||
}
|
||||
|
||||
if len(answerLines) > output.MaxDiffLines {
|
||||
answerLines = answerLines[:output.MaxDiffLines]
|
||||
truncated = true
|
||||
}
|
||||
if len(resultLines) > output.MaxDiffLines {
|
||||
resultLines = resultLines[:output.MaxDiffLines]
|
||||
truncated = true
|
||||
}
|
||||
|
||||
diffs := patienceDiff(
|
||||
answerLines,
|
||||
resultLines,
|
||||
func(a, b string) bool {
|
||||
return stringsEqual(a, b, output.CompareSpace)
|
||||
})
|
||||
diffOutput := diffText(diffs)
|
||||
diffOutput = strings.TrimSuffix(diffOutput, "\n ")
|
||||
|
||||
if truncated {
|
||||
diffOutput += "\n\n(truncated)"
|
||||
}
|
||||
if commonPrefixLineCount > 0 {
|
||||
diffOutput = fmt.Sprintf(
|
||||
"(%d line(s) of common prefix hidden)\n\n",
|
||||
commonPrefixLineCount,
|
||||
) + diffOutput
|
||||
}
|
||||
|
||||
return fmt.Sprintf(
|
||||
"```diff\n%s\n```\n",
|
||||
diffOutput,
|
||||
)
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user