forked from anz-bank/decimal
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathdecimalSuite_test.go
More file actions
263 lines (241 loc) · 8.21 KB
/
decimalSuite_test.go
File metadata and controls
263 lines (241 loc) · 8.21 KB
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
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
package decimal
import (
"bufio"
"fmt"
"os"
"regexp"
"testing"
)
type decValContainer struct {
val1, val2, val3, expected, calculated Decimal64
calculatedString string
parseError error
}
type testCaseStrings struct {
testName string
testFunc string
val1 string
val2 string
val3 string
expectedResult string
rounding string
}
const PrintFiles bool = true
const PrintTests bool = false
const RunTests bool = true
const IgnorePanics bool = false
const IgnoreRounding bool = false
var tests = []string{
"dectest/ddAdd.decTest",
"dectest/ddMultiply.decTest",
"dectest/ddFMA.decTest",
"dectest/ddClass.decTest",
// TODO: Implement following tests
"dectest/ddCompare.decTest",
"dectest/ddAbs.decTest",
// "dectest/ddCopysign.decTest",
"dectest/ddDivide.decTest",
// "dectest/ddLogB.decTest",
// "dectest/ddMin.decTest",
// "dectest/ddMinMag.decTest",
// "dectest/ddMinus.decTest",
}
func (testVal testCaseStrings) String() string {
return fmt.Sprintf("%s %s %v %v %v -> %v\n", testVal.testName, testVal.testFunc, testVal.val1, testVal.val2, testVal.val3, testVal.expectedResult)
}
var supportedRounding = []string{"half_up", "half_even"}
var ignoredFunctions = []string{"apply"}
// TODO(joshcarp): This test cannot fail. Proper assertions will be added once the whole suite passes
// TestFromSuite is the master tester for the dectest suite.
func TestFromSuite(t *testing.T) {
if RunTests {
for _, file := range tests {
if PrintFiles {
fmt.Println("starting test:", file)
}
f, _ := os.Open(file)
scanner := bufio.NewScanner(f)
numTests := 0
failedTests := 0
var roundingSupported bool
var scannedContext Context64
for scanner.Scan() {
testVal := getInput(scanner.Text())
if testVal.rounding != "" {
roundingSupported = isInList(testVal.rounding, supportedRounding)
if roundingSupported {
scannedContext = setRoundingFromString(testVal.rounding)
}
}
if testVal.testFunc != "" && roundingSupported {
numTests++
dec64vals := convertToDec64(testVal)
testErr := runTest(scannedContext, dec64vals, testVal)
if PrintTests {
fmt.Printf("%s\n", testVal)
}
if testErr != nil {
fmt.Println(testErr)
fmt.Println("Rounding mode:", supportedRounding[scannedContext.roundingMode])
failedTests++
if dec64vals.parseError != nil {
fmt.Println(dec64vals.parseError)
}
}
}
}
if PrintFiles {
fmt.Println("Number of tests ran:", numTests, "Number of failed tests:", failedTests)
}
}
fmt.Printf("decimalSuite_test settings (These should only be true for debug):\n Ignore Rounding errors: %v\n Ignore Panics: %v\n", IgnoreRounding, IgnorePanics)
}
}
func isInList(s string, list []string) bool {
for _, item := range list {
if item == s {
return true
}
}
return false
}
func setRoundingFromString(s string) Context64 {
switch s {
case "half_even":
return Context64{roundHalfEven}
case "half_up":
return Context64{roundHalfUp}
case "default":
return DefaultContext
default:
panic("Rounding not supported" + s)
}
}
func isRoundingErr(res, expected Decimal64) bool {
resP := res.getParts()
expectedP := expected.getParts()
sigDiff := int64(resP.significand.lo - expectedP.significand.lo)
expDiff := resP.exp - expectedP.exp
if (sigDiff == 1 || sigDiff == -1) && (expDiff == 1 || expDiff == -1 || expDiff == 0) {
return true
}
if resP.significand.lo == maxSig && resP.exp == expMax && expectedP.fl == flInf {
return true
}
return false
}
// getInput gets the test file and extracts test using regex, then returns a map object and a list of test names.
func getInput(line string) testCaseStrings {
testRegex := regexp.MustCompile(
`(?P<testName>dd[\w]*)` + // first capturing group: testfunc made of anything that isn't a whitespace
`(?:\s*)` + // match any whitespace (?: non capturing group)
`(?P<testFunc>[\S]*)` + // testfunc made of anything that isn't a whitespace
`(?:\s*\'?)` + // after can be any number of spaces and quotations if they exist (?: non capturing group)
`(?P<val1>\+?-?[^\t\f\v\' ]*)` + // first test val is anything that isnt a whitespace or a quoteation mark
`(?:'?\s*'?)` + // match any quotation marks and any space (?: non capturing group)
`(?P<val2>\+?-?[^\t\f\v\' ]*)` + // second test val is anything that isnt a whitespace or a quoteation mark
`(?:'?\s*'?)` +
`(?P<val3>\+?-?[^->]?[^\t\f\v\' ]*)` + //testvals3 same as 1 but specifically dont match with '->'
`(?:'?\s*->\s*'?)` + // matches the indicator to answer and surrounding whitespaces (?: non capturing group)
`(?P<expectedResult>\+?-?[^\r\n\t\f\v\' ]*)`) // matches the answer that's anything that is plus minus but not quotations
// Add regex to match to rounding: rounding mode her
// capturing gorups are testName, testFunc, val1, val2, and expectedResult)
ans := testRegex.FindStringSubmatch(line)
if len(ans) == 0 {
roundingRegex := regexp.MustCompile(`(?:rounding:[\s]*)(?P<rounding>[\S]*)`)
ans = roundingRegex.FindStringSubmatch(line)
if len(ans) == 0 {
return testCaseStrings{}
}
return testCaseStrings{rounding: ans[1]}
}
if isInList(ans[2], ignoredFunctions) {
return testCaseStrings{}
}
data := testCaseStrings{
testName: ans[1],
testFunc: ans[2],
val1: ans[3],
val2: ans[4],
val3: ans[5],
expectedResult: ans[6],
}
return data
}
// convertToDec64 converts the map object strings to decimal64s.
func convertToDec64(testvals testCaseStrings) (dec64vals decValContainer) {
var err1, err2, err3, expectedErr error
dec64vals.val1, err1 = ParseDecimal64(testvals.val1)
dec64vals.val2, err2 = ParseDecimal64(testvals.val2)
dec64vals.val3, err3 = ParseDecimal64(testvals.val3)
dec64vals.expected, expectedErr = ParseDecimal64(testvals.expectedResult)
if err1 != nil || err2 != nil || expectedErr != nil {
dec64vals.parseError = fmt.Errorf("error parsing in test: %s: \nval 1:%s: \nval 2: %s \nval 3: %s\nexpected: %s ",
testvals.testName,
err1,
err2,
err3,
expectedErr)
}
return
}
// runTest completes the tests and returns a boolean and string on if the test passes.
func runTest(context Context64, testVals decValContainer, testValStrings testCaseStrings) error {
calculatedContainer := execOp(context, testVals.val1, testVals.val2, testVals.val3, testValStrings.testFunc)
calcRestul := calculatedContainer.calculated
if calculatedContainer.calculatedString != "" {
if testValStrings.testFunc == "compare" && calculatedContainer.calculatedString == "-2" && testVals.expected.IsNaN() {
return nil
}
if calculatedContainer.calculatedString != testValStrings.expectedResult {
return fmt.Errorf(
"failed:\n%scalculated result: %s",
testValStrings,
calculatedContainer.calculatedString)
}
} else if calcRestul.IsNaN() || testVals.expected.IsNaN() {
if testVals.expected.String() != calcRestul.String() {
return fmt.Errorf(
"failed NaN TEST:\n%scalculated result: %v",
testValStrings,
calcRestul)
}
return nil
} else if testVals.expected.Cmp(calcRestul) != 0 && !(isRoundingErr(calcRestul, testVals.expected) && IgnoreRounding) {
return fmt.Errorf(
"failed:\n%scalculated result: %v",
testValStrings,
calcRestul)
}
return nil
}
// TODO: get runTest to run more functions such as FMA.
// execOp returns the calculated answer to the operation as Decimal64.
func execOp(context Context64, a, b, c Decimal64, op string) decValContainer {
if IgnorePanics {
defer func() {
if r := recover(); r != nil {
fmt.Println("failed", r, a, b)
}
}()
}
switch op {
case "add":
return decValContainer{calculated: context.Add(a, b)}
case "multiply":
return decValContainer{calculated: context.Mul(a, b)}
case "abs":
return decValContainer{calculated: a.Abs()}
case "divide":
return decValContainer{calculated: context.Quo(a, b)}
case "fma":
return decValContainer{calculated: context.FMA(a, b, c)}
case "compare":
return decValContainer{calculatedString: fmt.Sprintf("%d", int64(a.Cmp(b)))}
case "class":
return decValContainer{calculatedString: a.Class()}
default:
fmt.Println("end of execOp, no tests ran", op)
}
return decValContainer{calculated: Zero64}
}