-
Notifications
You must be signed in to change notification settings - Fork 16
/
score.go
140 lines (126 loc) · 3.85 KB
/
score.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
package score
import (
"encoding/json"
"fmt"
"math"
"runtime/debug"
"strings"
"testing"
)
// Fail sets Score to zero.
func (s *Score) Fail() {
s.Score = 0
}
// Inc increments score if score is less than MaxScore.
func (s *Score) Inc() {
if s.Score < s.MaxScore {
s.Score++
}
}
// IncBy increments score n times or until score equals MaxScore.
func (s *Score) IncBy(n int) {
m := int32(n)
if s.Score+m < s.MaxScore {
s.Score += m
} else {
s.Score = s.MaxScore
}
}
// Dec decrements score if score is greater than zero.
func (s *Score) Dec() {
if s.Score > 0 {
s.Score--
}
}
// DecBy decrements score n times or until Score equals zero.
func (s *Score) DecBy(n int) {
m := int32(n)
if s.Score-m > 0 {
s.Score -= m
} else {
s.Score = 0
}
}
// Normalize the score to the given maxScore.
func (s *Score) Normalize(maxScore int) {
f := float64(maxScore) / float64(s.MaxScore)
normScore := float64(s.Score) * f
s.Score = int32(math.Round(normScore))
s.MaxScore = int32(maxScore)
}
// Equal returns true if s equals other. Ignores the Secret field.
func (s *Score) Equal(other *Score) bool {
return other != nil &&
s.TestName == other.TestName &&
s.Score == other.Score &&
s.MaxScore == other.MaxScore &&
s.Weight == other.Weight
}
// RelativeScore returns a string with the following format:
// "TestName: score = x/y = s".
func (s *Score) RelativeScore() string {
return fmt.Sprintf("%s: score = %d/%d = %.1f", s.TestName, s.Score, s.MaxScore, float32(s.Score)/float32(s.MaxScore))
}
// Print prints a JSON representation of the score that can be picked up by QuickFeed.
// To ensure that panic message and stack trace is printed, this method must be called via defer.
// If a test panics, the score will be set to zero, and a panic message will be emitted.
// Note that, if subtests are used, each subtest must defer call the PanicHandler method
// to ensure that panics are caught and handled appropriately.
// The msg parameter is optional, and will be printed in case of a panic.
func (s *Score) Print(t *testing.T, msg ...string) {
if r := recover(); r != nil {
s.fail(t)
printPanicMessage(s.TestName, msg[0], r)
}
// We rely on JSON score objects to start on a new line, since otherwise
// scanning long student generated output lines can be costly.
fmt.Println()
// print JSON score object: {"Secret":"my secret code","TestName": ...}
fmt.Println(s.json())
}
// PanicHandler recovers from a panicking test, resets the score to zero and
// emits an error message. This is only needed when using a single score object
// for multiple subtests each of which may panic, which would prevent the deferred
// Print() call from executing its recovery handler.
//
// This must be called as a deferred function from within a subtest, that is
// within a t.Run() function:
// defer s.PanicHandler(t)
//
// The msg parameter is optional, and will be printed in case of a panic.
func (s *Score) PanicHandler(t *testing.T, msg ...string) {
if r := recover(); r != nil {
s.fail(t)
printPanicMessage(t.Name(), msg[0], r)
}
}
// fail resets the score to zero and fails the provided test.
func (s *Score) fail(t *testing.T) {
// reset score for panicked test functions
s.Score = 0
// fail the test
t.Fail()
}
// json returns a JSON string for the score object.
func (s *Score) json() string {
b, err := json.Marshal(s)
if err != nil {
return fmt.Sprintf("json.Marshal error: %v\n", err)
}
return string(b)
}
func printPanicMessage(testName, msg string, recoverVal interface{}) {
var s strings.Builder
s.WriteString("******************\n")
s.WriteString(testName)
s.WriteString(" panicked: ")
s.WriteString(fmt.Sprintf("%v", recoverVal))
if msg != "" {
s.WriteString("\n\nMessage:\n")
s.WriteString(msg)
}
s.WriteString("\n\nStack trace from panic:\n")
s.WriteString(string(debug.Stack()))
s.WriteString("******************\n")
fmt.Println(s.String())
}