Refactor Sudoku field and solver implementation
This commit is contained in:
@@ -0,0 +1,110 @@
|
||||
package parser
|
||||
|
||||
import (
|
||||
"crypto/sha1"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
const validSolvedPuzzle = "123456789456789123789123456234567891567891234891234567345678912678912345912345678"
|
||||
|
||||
func bankRecord(digits string, rating float64) string {
|
||||
hash := fmt.Sprintf("%x", sha1.Sum([]byte(digits)))[:12]
|
||||
return fmt.Sprintf("%s %s %.1f", hash, digits, rating)
|
||||
}
|
||||
|
||||
func TestPuzzleBankParsesLFCRLFAndFinalLine(t *testing.T) {
|
||||
first := "0" + validSolvedPuzzle[1:]
|
||||
data := bankRecord(first, 1.2) + "\r\n\n" + bankRecord(validSolvedPuzzle, 2.5)
|
||||
parser := &PuzzleBank{}
|
||||
if err := parser.Parse([]byte(data)); err != nil {
|
||||
t.Fatalf("Parse() error = %v", err)
|
||||
}
|
||||
if got := len(parser.GetAllFields()); got != 2 {
|
||||
t.Fatalf("field count = %d, want 2", got)
|
||||
}
|
||||
second, err := parser.GetField(1)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if second.GetRating() != 2.5 || !second.IsSolved() {
|
||||
t.Fatalf("second field: rating=%v solved=%v", second.GetRating(), second.IsSolved())
|
||||
}
|
||||
}
|
||||
|
||||
func TestPuzzleBankRejectsMalformedInput(t *testing.T) {
|
||||
valid := bankRecord(validSolvedPuzzle, 1.2)
|
||||
tests := map[string]string{
|
||||
"empty": "\n\r\n",
|
||||
"columns": "broken",
|
||||
"hash length": "abc " + validSolvedPuzzle + " 1.2",
|
||||
"hash mismatch": "ffffffffffff" + valid[12:],
|
||||
"bad puzzle": bankRecord("x"+validSolvedPuzzle[1:], 1.2),
|
||||
"bad rating": strings.TrimSuffix(valid, "1.2") + "nope",
|
||||
}
|
||||
for name, input := range tests {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
if err := (&PuzzleBank{}).Parse([]byte(input)); err == nil || (name != "empty" && !errors.Is(err, ErrInvalidInput)) {
|
||||
t.Fatalf("Parse() error = %v", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestPuzzleBankParseIsAtomicAndDoesNotAppend(t *testing.T) {
|
||||
parser := &PuzzleBank{}
|
||||
valid := bankRecord(validSolvedPuzzle, 1.2)
|
||||
if err := parser.Parse([]byte(valid)); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
original, _ := parser.GetField(0)
|
||||
if err := parser.Parse([]byte(valid + "\ninvalid")); err == nil {
|
||||
t.Fatal("invalid Parse() error = nil")
|
||||
}
|
||||
afterFailure, _ := parser.GetField(0)
|
||||
if afterFailure != original || len(parser.GetAllFields()) != 1 {
|
||||
t.Fatal("failed parse changed previously parsed fields")
|
||||
}
|
||||
if err := parser.Parse([]byte(valid)); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(parser.GetAllFields()) != 1 {
|
||||
t.Fatal("repeated Parse() appended fields")
|
||||
}
|
||||
}
|
||||
|
||||
func TestParserHelperBoundsAndDefensiveSlice(t *testing.T) {
|
||||
parser := &PuzzleString{}
|
||||
if err := parser.Parse([]byte(validSolvedPuzzle)); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for _, index := range []int{-1, 1} {
|
||||
if _, err := parser.GetField(index); !errors.Is(err, ErrFieldIndex) {
|
||||
t.Fatalf("GetField(%d) error = %v", index, err)
|
||||
}
|
||||
}
|
||||
fields := parser.GetAllFields()
|
||||
fields[0] = nil
|
||||
if stored, err := parser.GetField(0); err != nil || stored == nil {
|
||||
t.Fatal("GetAllFields exposed its backing slice")
|
||||
}
|
||||
}
|
||||
|
||||
func TestPuzzleStringValidatesInput(t *testing.T) {
|
||||
parser := &PuzzleString{}
|
||||
if err := parser.Parse([]byte(" \n" + validSolvedPuzzle + "\n")); err != nil {
|
||||
t.Fatalf("Parse() error = %v", err)
|
||||
}
|
||||
if fields := parser.GetAllFields(); len(fields) != 1 || !fields[0].IsSolved() {
|
||||
t.Fatal("valid puzzle string was not parsed")
|
||||
}
|
||||
|
||||
invalid := []string{"", validSolvedPuzzle[:80], "x" + validSolvedPuzzle[1:], "11" + validSolvedPuzzle[2:]}
|
||||
for _, input := range invalid {
|
||||
if err := (&PuzzleString{}).Parse([]byte(input)); err == nil {
|
||||
t.Fatalf("Parse(%q) error = nil", input)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user