Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add hints for failing conditions #172

Merged
merged 7 commits into from
Aug 22, 2023
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 13 additions & 4 deletions checks.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,20 @@ import (
// the conditions for a check, and its message and points (autogenerated or
// otherwise).
type check struct {
Message string
Points int
Message string
Mobmaker55 marked this conversation as resolved.
Show resolved Hide resolved
Hint string
Points int

Fail []cond
Pass []cond
PassOverride []cond
}

// cond, or condition, is the parameters for a given test within a check.
type cond struct {
Type string
Hint string
Type string

Path string
Cmd string
User string
Expand Down Expand Up @@ -55,6 +59,11 @@ func (c cond) requireArgs(args ...interface{}) {
continue
}

// Ignore hint fields, they only show up in the scoring report
if vType.Field(i).Name == "Hint" {
continue
}

required := false
for _, a := range args {
if vType.Field(i).Name == a {
Expand All @@ -68,7 +77,7 @@ func (c cond) requireArgs(args ...interface{}) {
fail(c.Type+":", "missing required argument '"+vType.Field(i).Name+"'")
}
} else if v.Field(i).String() != "" {
warn(c.Type+":", "specifying unnecessary argument '"+vType.Field(i).Name+"'")
warn(c.Type+":", "specifying unused argument '"+vType.Field(i).Name+"'")
}
}
}
Expand Down
9 changes: 7 additions & 2 deletions configs.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,10 +53,10 @@ func parseConfig(configContent string) {

// Print warnings for impossible checks and undefined check types.
for i, check := range conf.Check {
allConditions := append(append(append([]cond{}, check.Pass[:]...), check.Fail[:]...), check.PassOverride[:]...)
if len(allConditions) == 0 {
if len(check.Pass) == 0 && len(check.PassOverride) == 0 {
warn("Check " + fmt.Sprintf("%d", i+1) + " does not define any possible ways to pass!")
}
allConditions := append(append(append([]cond{}, check.Pass[:]...), check.Fail[:]...), check.PassOverride[:]...)
for j, cond := range allConditions {
if cond.Type == "" {
warn("Check " + fmt.Sprintf("%d condition %d", i+1, j+1) + " does not have a check type!")
Expand Down Expand Up @@ -169,6 +169,11 @@ func obfuscateConfig() {
if err := obfuscateData(&conf.Check[i].Message); err != nil {
fail(err.Error())
}
if conf.Check[i].Hint != "" {
if err := obfuscateData(&conf.Check[i].Hint); err != nil {
fail(err.Error())
}
}
for j := range check.Pass {
if err := obfuscateCond(&conf.Check[i].Pass[j]); err != nil {
fail(err.Error())
Expand Down
2 changes: 1 addition & 1 deletion docs/regex.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Regular Expressions

Many of the checks in `aeacus` require regular expression (regex) strings as input. This may seem inconvenient if you want to score something simple, but we think it significantly increases the overall quality of checks. Each regex is applied to each line of the input file, so currently, no multi-line regexes are currently possible.
Many of the checks in `aeacus` use regular expression (regex) strings as input. This may seem inconvenient if you want to score something simple, but we think it significantly increases the overall quality of checks. Each regex is applied to each line of the input file, so currently, no multi-line regexes are currently possible.

> We're using the Golang Regular Expression package ([documentation here](https://godocs.io/regexp)). It uses RE2 syntax, which is also generally the same as Perl, Python, and other languages.

Expand Down
10 changes: 5 additions & 5 deletions output.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ func debug(p ...interface{}) {
} else {
printStr = printer(color.FgMagenta, "DBUG", toPrint)
}
fmt.Printf(printStr)
fmt.Print(printStr)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As it was Printf() before, this is just a sanity check, but it is intentional that it is Print() and not Println() yeah?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah sorry had this change lying around for a while, it is supposed to be Print. Printf was kind of like a format string vulnerability lol, not that those exist in go. The newline is added in toPrint via Sprintln.

}
}

Expand All @@ -104,24 +104,24 @@ func info(p ...interface{}) {
} else {
printStr = printer(color.FgCyan, "INFO", toPrint)
}
fmt.Printf(printStr)
fmt.Print(printStr)
}
}

func blue(head string, p ...interface{}) {
toPrint := fmt.Sprintln(p...)
printStr := printer(color.FgCyan, head, toPrint)
fmt.Printf(printStr)
fmt.Print(printStr)
}

func red(head string, p ...interface{}) {
toPrint := fmt.Sprintln(p...)
fmt.Printf(printer(color.FgRed, head, toPrint))
fmt.Print(printer(color.FgRed, head, toPrint))
}

func green(head string, p ...interface{}) {
toPrint := fmt.Sprintln(p...)
fmt.Printf(printer(color.FgGreen, head, toPrint))
fmt.Print(printer(color.FgGreen, head, toPrint))
}

func printer(colorChosen color.Attribute, messageType, toPrint string) string {
Expand Down
60 changes: 43 additions & 17 deletions score.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ type imageData struct {
TotalPoints int
Penalties []scoreItem
Points []scoreItem
Hints []hintItem
}

// connData represents the current connectivity state of the image to the
Expand All @@ -46,10 +47,19 @@ type connData struct {
// scoreItem is the scoring report representation of a check, containing only
// the message and points associated with it.
type scoreItem struct {
Index int
Message string
Points int
}

// hintItem is the scoring report representation of a hint, which can contain
// multiple messages.
type hintItem struct {
Index int
Messages []string
Points int
}

// config is a representation of the TOML configuration typically
// specific in scoring.conf.
type config struct {
Expand Down Expand Up @@ -209,19 +219,25 @@ func scoreCheck(check check) {
status := false
failed := false

// If a fail condition passes, the check fails, no other checks required.
// Create hint var in case any checks have hints
hint := hintItem{
Index: checkCount,
Points: check.Points,
}

// If a Fail condition passes, the check fails, no other checks required.
if len(check.Fail) > 0 {
failed = checkFails(&check)
failed = checkOr(&check, &hint)
}

// If a PassOverride succeeds, that overrides the Pass checks
if !failed && len(check.PassOverride) > 0 {
status = checkPassOverrides(&check)
status = checkOr(&check, &hint)
}

// Finally, we check the normal pass checks
// Finally, we check the normal Pass checks
if !failed && !status && len(check.Pass) > 0 {
status = checkPass(&check)
status = checkAnd(&check, &hint)
}

if status {
Expand All @@ -231,18 +247,31 @@ func scoreCheck(check check) {
pass(fmt.Sprintf("Check passed: %s - %d pts", check.Message, check.Points))
obfuscateData(&check.Message)
}
image.Points = append(image.Points, scoreItem{check.Message, check.Points})
image.Points = append(image.Points, scoreItem{checkCount, check.Message, check.Points})
image.Contribs += check.Points
} else {
if verboseEnabled {
deobfuscateData(&check.Message)
fail(fmt.Sprintf("Penalty triggered: %s - %d pts", check.Message, check.Points))
obfuscateData(&check.Message)
}
image.Penalties = append(image.Penalties, scoreItem{check.Message, check.Points})
image.Penalties = append(image.Penalties, scoreItem{checkCount, check.Message, check.Points})
image.Detracts += check.Points
}
image.Score += check.Points
} else {
// If there is a check-wide hint, add to start of hint messages.
if check.Hint != "" {
hints := []string{check.Hint}
hints = append(hints, hint.Messages...)
hint.Messages = hints
}

// If the check failed, and there are hints, see if we should display them.
// All hints triggered (based on which conditions ran) are displayed in sequential order.
if len(hint.Messages) > 0 {
image.Hints = append(image.Hints, hint)
}
}

// If check is not a penalty, add to total
Expand All @@ -252,27 +281,24 @@ func scoreCheck(check check) {
}
}

func checkFails(check *check) bool {
func checkOr(check *check, hint *hintItem) bool {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we have a line or two to explain what checkOr and checkAnd are designed to do? it is a slightly less direct name than previously

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I saw that the functions were identical for checkFails/PassOverride because they were both OR/|| behavior, in that on the first true, return. Whereas pass is AND/&& because everything needs to be true otherwise return false. Sure I'll add a comment

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually good catch I messed that up and forgot to change it to take a list of conds rather than a check

for _, cond := range check.Fail {
if runCheck(cond) {
return true
}
}
return false
}

func checkPassOverrides(check *check) bool {
for _, cond := range check.PassOverride {
if runCheck(cond) {
return true
if cond.Hint != "" {
hint.Messages = append(hint.Messages, cond.Hint)
}
}
return false
}

func checkPass(check *check) bool {
func checkAnd(check *check, hint *hintItem) bool {
for _, cond := range check.Pass {
if !runCheck(cond) {
if cond.Hint != "" {
hint.Messages = append(hint.Messages, cond.Hint)
}
return false
}
}
Expand Down
40 changes: 29 additions & 11 deletions web.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ func genReport(img *imageData) {
displayTeamID = html.EscapeString(teamID)
}

header := `<!DOCTYPE html> <html> <head> <meta http-equiv="refresh" content="60"> <title>Aeacus Scoring Report</title> <style type="text/css"> h1 { text-align: center; } h2 { text-align: center; } body { font-family: Arial, Verdana, sans-serif; font-size: 14px; margin: 0; padding: 0; width: 100%; height: 100%; background: url('./img/background.png'); background-size: cover; background-attachment: fixed; background-position: top center; background-color: #336699; } .red {color: red;} .green {color: green;} .blue {color: blue;} .main { margin-top: 10px; margin-bottom: 10px; margin-left: auto; margin-right: auto; padding: 0px; border-radius: 12px; background-color: white; width: 900px; max-width: 100%; min-width: 600px; box-shadow: 0px 0px 12px #003366; } .text { padding: 12px; -webkit-touch-callout: none; -webkit-user-select: none; -khtml-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; } .center { text-align: center; } .binary { position: relative; overflow: hidden; } .binary::before { position: absolute; top: -1000px; left: -1000px; display: block; width: 500%; height: 300%; -webkit-transform: rotate(-45deg); -moz-transform: rotate(-45deg); -ms-transform: rotate(-45deg); transform: rotate(-45deg); content: attr(data-binary); opacity: 0.15; line-height: 2em; letter-spacing: 2px; color: #369; font-size: 10px; pointer-events: none; } </style> <meta http-equiv="refresh"> </head> <body><div class="main"><div class="text"><div class="binary" data-binary="` + displayTeamID + `"><p align=center style="width:100%;text-align:center"><img align=middle style="width:180px; float:middle" src="./img/logo.png"></p>`
header := `<!DOCTYPE html> <html> <head> <meta http-equiv="refresh" content="60"> <title>Aeacus Scoring Report</title> <style type="text/css"> h1 { text-align: center; } h2 { text-align: center; } body { font-family: Arial, Verdana, sans-serif; font-size: 14px; margin: 0; padding: 0; width: 100%; height: 100%; background: url('./img/background.png'); background-size: cover; background-attachment: fixed; background-position: top center; background-color: #336699; } ul { margin-top: 0.5rem; } .red {color: red;} .green {color: green;} .blue {color: blue;} .gray {color: gray;} .main { margin-top: 10px; margin-bottom: 10px; margin-left: auto; margin-right: auto; padding: 0px; border-radius: 12px; background-color: white; width: 900px; max-width: 100%; min-width: 600px; box-shadow: 0px 0px 12px #003366; } .text { padding: 12px; -webkit-touch-callout: none; -webkit-user-select: none; -khtml-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; } .center { text-align: center; } .binary { position: relative; overflow: hidden; } .binary::before { position: absolute; top: -1000px; left: -1000px; display: block; width: 500%; height: 300%; -webkit-transform: rotate(-45deg); -moz-transform: rotate(-45deg); -ms-transform: rotate(-45deg); transform: rotate(-45deg); content: attr(data-binary); opacity: 0.15; line-height: 2em; letter-spacing: 2px; color: #369; font-size: 10px; pointer-events: none; } </style> <meta http-equiv="refresh"> </head> <body><div class="main"><div class="text"><div class="binary" data-binary="` + displayTeamID + `"><p align=center style="width:100%;text-align:center"><img align=middle style="width:180px; float:middle" src="./img/logo.png"></p>`
footer := `</p> <br> <p align=center style="text-align:center"> The Aeacus project is free and open source software. This project is in no way endorsed or affiliated with the Air Force Association or the University of Texas at San Antonio. </p> </div> </div> </div> </body> </html>`

genTime := time.Now()
Expand All @@ -29,11 +29,11 @@ func genReport(img *imageData) {
htmlFile.WriteString(header)
htmlFile.WriteString("<h1>" + html.EscapeString(conf.Title) + "</h1>")
htmlFile.WriteString("<h2>Report Generated At: " + genTime.Format("2006/01/02 15:04:05 MST") + " </h2>")
htmlFile.WriteString(`<script>var bin = document.querySelectorAll('.binary'); [].forEach.call(bin, function(el) { el.dataset.binary = Array(10000).join(el.dataset.binary + ' ') }); var currentdate = new Date().getTime(); gendate = Date.parse('0000/00/00 00:00:00 UTC'); diff = Math.abs(currentdate - gendate); if ( gendate > 0 && diff > 1000 * 60 * 5 ) { document.write('<span style="color:red"><h2>WARNING: CSS Scoring service may not be running</h2></span>'); } </script>`)
htmlFile.WriteString(`<script>var bin = document.querySelectorAll('.binary'); [].forEach.call(bin, function(el) { el.dataset.binary = Array(10000).join(el.dataset.binary + ' ') }); var currentdate = new Date().getTime(); gendate = Date.parse('0000/00/00 00:00:00 UTC'); diff = Math.abs(currentdate - gendate); if ( gendate > 0 && diff > 1000 * 60 * 5 ) { document.write('<span class="red"><h2>WARNING: CSS Scoring service may not be running</h2></span>'); } </script>`)

if conf.Remote != "" {
if teamID == "" {
htmlFile.WriteString(`<h3 class="center">Current Team ID: <span style="color: red">N/A</span></h3>`)
htmlFile.WriteString(`<h3 class="center">Current Team ID: <span class="red">N/A</span></h3>`)
} else {
htmlFile.WriteString(`<h3 class="center">Current Team ID: ` + html.EscapeString(teamID) + `</h3>`)
}
Expand All @@ -45,19 +45,19 @@ func genReport(img *imageData) {
htmlFile.WriteString(`<a href="` + html.EscapeString(conf.Remote) + `">Click here to view the public scoreboard</a><br>`)
htmlFile.WriteString(`<a href="` + html.EscapeString(conf.Remote) + `/announcements` + `">Click here to view the announcements</a><br>`)

htmlFile.WriteString(`<p><h3>Connection Status: <span style="color:` + conn.OverallColor + `">` + conn.OverallStatus + `<span></h3>`)
htmlFile.WriteString(`<p><h3>Connection Status: <span class="` + conn.OverallColor + `">` + conn.OverallStatus + `<span></h3>`)

htmlFile.WriteString(`Internet Connectivity Check: <span style="color:` + conn.NetColor + `">` + conn.NetStatus + `</span><br>`)
htmlFile.WriteString(`Aeacus Server Connection Status: <span style="color:` + conn.ServerColor + `">` + conn.ServerStatus + `</span></p>`)
htmlFile.WriteString(`Internet Connectivity Check: <span class="` + conn.NetColor + `">` + conn.NetStatus + `</span><br>`)
htmlFile.WriteString(`Aeacus Server Connection Status: <span class="` + conn.ServerColor + `">` + conn.ServerStatus + `</span></p>`)
} else {
htmlFile.WriteString(`<p><h3>Connection Status: <span style="color:` + conn.OverallColor + `">` + conn.OverallStatus + `<span></h3>`)
htmlFile.WriteString(`Internet Connectivity Check: <span style="color:grey">N/A</span><br>`)
htmlFile.WriteString(`Aeacus Server Connection Status: <span style="color:grey">N/A</span><br>`)
htmlFile.WriteString(`<p><h3>Connection Status: <span class="` + conn.OverallColor + `">` + conn.OverallStatus + `<span></h3>`)
htmlFile.WriteString(`Internet Connectivity Check: <span class="gray">N/A</span><br>`)
htmlFile.WriteString(`Aeacus Server Connection Status: <span class="gray">N/A</span><br>`)
}

htmlFile.WriteString(fmt.Sprintf(`<h3> %d penalties assessed, for a loss of %.0f points: </h3> <p> <span style="color:red">`, len(img.Penalties), math.Abs(float64(img.Detracts))))
htmlFile.WriteString(fmt.Sprintf(`<h3> %d penalties assessed, for a loss of %.0f points: </h3> <p> <span class="red">`, len(img.Penalties), math.Abs(float64(img.Detracts))))

// for each penalty
// for each penalty:
for _, penalty := range img.Penalties {
deobfuscateData(&penalty.Message)
htmlFile.WriteString(fmt.Sprintf("%s - %.0f pts<br>", html.EscapeString(penalty.Message), math.Abs(float64(penalty.Points))))
Expand All @@ -73,6 +73,24 @@ func genReport(img *imageData) {
obfuscateData(&point.Message)
}

htmlFile.WriteString("</p>")
// for each hint:
for _, hint := range img.Hints {
if len(hint.Messages) == 1 {
deobfuscateData(&hint.Messages[0])
htmlFile.WriteString(fmt.Sprintf("<span class='gray'>(%d pts) <b>Hint</b>: %s</span><br>", hint.Points, html.EscapeString(hint.Messages[0])))
obfuscateData(&hint.Messages[0])
} else if len(hint.Messages) > 1 {
htmlFile.WriteString(fmt.Sprintf("<p style='margin-bottom: 0'><span class='gray'>(%d pts) <b>Hints:</b></span></p><ul>", hint.Points))
for i := range hint.Messages {
deobfuscateData(&hint.Messages[i])
htmlFile.WriteString(fmt.Sprintf("<li><span class='gray'>%s</span></li>", html.EscapeString(hint.Messages[i])))
obfuscateData(&hint.Messages[i])
}
htmlFile.WriteString("</ul></span>")
}
}

htmlFile.WriteString(footer)

info("Writing HTML to ScoringReport.html...")
Expand Down
Loading