box-o-sand/conway/conway_test.go

594 lines
10 KiB
Go
Raw Normal View History

package conway_test
import (
"bytes"
"encoding/base64"
"encoding/json"
"fmt"
"image/png"
"math/rand"
"net/http"
"regexp"
"strings"
2012-12-09 03:44:45 +00:00
"testing"
)
import (
. "github.com/meatballhat/box-o-sand/conway"
)
2012-12-09 03:44:45 +00:00
var TEST_SIMPLE_INITIAL_STATE = NewGameState(16, 16)
func init() {
s := TEST_SIMPLE_INITIAL_STATE
s.Set(0, 0, 1)
s.Set(0, 1, 1)
s.Set(0, 2, 1)
s.Set(1, 0, 1)
s.Set(1, 1, 1)
s.Set(2, 2, 1)
s.Set(3, 0, 1)
s.Set(3, 2, 1)
s.Set(13, 13, 1)
s.Set(13, 14, 1)
s.Set(14, 14, 1)
s.Set(14, 15, 1)
s.Set(15, 13, 1)
s.Set(15, 15, 1)
2012-12-09 03:44:45 +00:00
}
func newTestGameState() *GameState {
2012-12-12 02:04:47 +00:00
state := NewGameState(16, 16)
state.Import(TEST_SIMPLE_INITIAL_STATE)
return state
2012-12-12 02:04:47 +00:00
}
func newTestGameOfLife() *GameOfLife {
2012-12-09 03:44:45 +00:00
game := NewGameOfLife(16, 16)
game.State.Import(TEST_SIMPLE_INITIAL_STATE)
return game
2012-12-12 02:04:47 +00:00
}
func TestLiveCellWithFewerThanTwoLiveNeighborsDies(t *testing.T) {
game := newTestGameOfLife()
2012-12-09 03:44:45 +00:00
2012-12-10 03:16:16 +00:00
game.EvaluateGeneration()
2012-12-09 04:08:40 +00:00
cell := game.State.Get(3, 2)
if cell.Value != 0 {
t.Errorf("%v != 0", cell.Value)
2012-12-10 03:16:16 +00:00
return
2012-12-09 04:08:40 +00:00
}
}
2012-12-09 04:08:40 +00:00
2012-12-10 03:16:16 +00:00
func TestLiveCellWithTwoOrThreeLiveNeighborsLivesOn(t *testing.T) {
game := newTestGameOfLife()
2012-12-10 03:16:16 +00:00
game.EvaluateGeneration()
2012-12-09 04:08:40 +00:00
2012-12-16 21:04:54 +00:00
cell := game.State.Get(0, 2)
if cell.Value != 1 {
t.Errorf("%v != 1", cell.Value)
2012-12-10 03:16:16 +00:00
return
}
}
2012-12-10 03:16:16 +00:00
func TestLiveCellWithMoreThanThreeLiveNeighborsDies(t *testing.T) {
game := newTestGameOfLife()
game.EvaluateGeneration()
cell := game.State.Get(0, 1)
if cell.Value != 0 {
t.Errorf("%v != 0", cell.Value)
return
}
}
2012-12-10 03:33:42 +00:00
func TestDeadCellWithExactlyThreeLiveNeighborsBecomesAlive(t *testing.T) {
game := newTestGameOfLife()
2012-12-10 03:33:42 +00:00
game.EvaluateGeneration()
cell := game.State.Get(3, 1)
if cell.Value != 1 {
t.Errorf("%v != 1", cell.Value)
2012-12-10 03:33:42 +00:00
return
}
}
func TestGameOfLifeCanDisplayItselfAsAString(t *testing.T) {
game := newTestGameOfLife()
grid := fmt.Sprintf("%s\n", game)
gridLen := len(grid)
if gridLen < 256 {
t.Errorf("%v < 256", gridLen)
t.Fail()
}
}
func TestGameOfLifeCanDisplayItselfAsAnImage(t *testing.T) {
game := newTestGameOfLife()
img, err := game.Image(1, 1)
if err != nil {
t.Error(err)
return
}
bounds := img.Bounds()
if bounds.Max.Y < 16 {
t.Errorf("image.Max.Y < 16!: %d", bounds.Max.Y)
return
}
if bounds.Max.X < 16 {
t.Errorf("image.Max.X < 16!: %d", bounds.Max.X)
return
}
}
2012-12-10 03:16:16 +00:00
func TestNewGameOfLifeHasCorrectDimensions(t *testing.T) {
game := newTestGameOfLife()
2012-12-12 02:04:47 +00:00
2012-12-10 03:16:16 +00:00
if game.State.Height() != 16 || game.State.Width() != 16 {
t.Fail()
}
}
2012-12-10 03:16:16 +00:00
func TestNewGameOfLifeHasGameStateOfSameDimensions(t *testing.T) {
game := newTestGameOfLife()
2012-12-10 03:16:16 +00:00
if len(game.State.Rows) < 16 {
t.Fail()
}
if len(game.State.Rows[0].Cells) < 16 {
2012-12-10 03:16:16 +00:00
t.Fail()
}
}
2012-12-10 03:16:16 +00:00
func TestGameStateCanImportState(t *testing.T) {
game := newTestGameOfLife()
if game.State.Get(0, 0).Value != 1 {
2012-12-10 03:16:16 +00:00
t.Fail()
}
if game.State.Get(1, 0).Value != 1 {
2012-12-10 03:16:16 +00:00
t.Fail()
}
if game.State.Get(0, 1).Value != 1 {
2012-12-10 03:16:16 +00:00
t.Fail()
}
if game.State.Get(1, 1).Value != 1 {
2012-12-10 03:16:16 +00:00
t.Fail()
}
if game.State.Get(2, 2).Value != 1 {
2012-12-10 03:16:16 +00:00
t.Fail()
}
2012-12-09 03:44:45 +00:00
}
func TestGameOfLifeCanImportRandomState(t *testing.T) {
game := newTestGameOfLife()
err := game.ImportRandomState()
if err != nil {
t.Error(err)
return
}
zeroCount := 0
for y := 0; y < 16; y++ {
for x := 0; x < 16; x++ {
cell := game.State.Get(x, y)
if cell.Value == 0 {
zeroCount++
}
}
}
if zeroCount == 256 {
t.Fail()
}
}
func TestGameStateCanDisplayItselfAsAString(t *testing.T) {
state := newTestGameState()
grid := fmt.Sprintf("%s\n", state)
lenGrid := len(grid)
if lenGrid < 256 {
t.Errorf("%v < 256", lenGrid)
return
}
nLines := strings.Count(grid, "\n")
if nLines < 16 {
t.Errorf("%v < 16 (lines)", nLines)
return
}
}
func TestGameStateCanDisplayItselfAsAnImage(t *testing.T) {
state := newTestGameState()
img, err := state.Image(1, 1)
if err != nil {
t.Error(err)
return
}
bounds := img.Bounds()
if bounds.Max.Y < 16 {
t.Errorf("image.Max.Y < 16!: %d", bounds.Max.Y)
return
}
if bounds.Max.X < 16 {
t.Errorf("image.Max.X < 16!: %d", bounds.Max.X)
return
}
}
2012-12-09 03:44:45 +00:00
func TestNewGameStatesAreAllDead(t *testing.T) {
state := NewGameState(4, 4)
for x := 0; x < 3; x++ {
for y := 0; y < 3; y++ {
if state.Get(x, y).Value != 0 {
2012-12-09 03:44:45 +00:00
t.Fail()
}
}
}
}
2012-12-09 04:08:40 +00:00
func TestGameStateCanGetCellValueByCoords(t *testing.T) {
state := NewGameState(8, 8)
if state.Get(2, 2).Value != 0 {
2012-12-09 04:08:40 +00:00
t.Fail()
}
}
func TestGameStateCanSetCellValueByCoords(t *testing.T) {
state := NewGameState(8, 8)
state.Set(2, 5, 1)
2012-12-09 04:08:40 +00:00
if state.Get(2, 5).Value != 1 {
2012-12-09 04:08:40 +00:00
t.Fail()
}
}
2012-12-09 03:44:45 +00:00
func TestGameStateCanEnlivenCellsByCoords(t *testing.T) {
state := NewGameState(8, 8)
state.Enliven(0, 1)
2012-12-09 03:44:45 +00:00
if state.Get(0, 1).Value != 1 {
2012-12-09 03:44:45 +00:00
t.Fail()
}
}
func TestGameStateCanDeadenCellsByCoords(t *testing.T) {
state := NewGameState(8, 8)
state.Enliven(0, 1)
2012-12-09 03:44:45 +00:00
if state.Get(0, 1).Value != 1 {
2012-12-09 03:44:45 +00:00
t.Fail()
}
state.Deaden(0, 1)
2012-12-09 03:44:45 +00:00
if state.Get(0, 1).Value != 0 {
2012-12-09 03:44:45 +00:00
t.Fail()
}
}
func TestNewGenerationScoreCardIsAllZeros(t *testing.T) {
gen := NewGenerationScoreCard(4, 4)
for y := 0; y < gen.Height(); y++ {
for x := 0; x < gen.Width(); x++ {
if gen.Get(x, y).Value != 0 {
t.Fail()
}
}
}
}
func TestGenerationScoreCardCalculateResultsInCorrectNeighborCounts(t *testing.T) {
genScore := NewGenerationScoreCard(16, 16)
state := newTestGameState()
genScore.Calculate(state)
cell := genScore.Get(0, 0)
2012-12-16 21:04:54 +00:00
if cell.Value != 4 {
t.Errorf("[0, 0] value %v != 4", cell.Value)
return
}
cell = genScore.Get(3, 2)
if cell.Value != 1 {
t.Errorf("[3, 2] value %d != 1", cell.Value)
return
}
}
func TestStateCanMutate(t *testing.T) {
state := newTestGameState()
curCksum, err := state.Checksum()
if err != nil {
t.Error(err)
return
}
state.Mutate()
newCksum, err := state.Checksum()
if err != nil {
t.Error(err)
return
}
if curCksum == newCksum {
t.Errorf("Checksum did not change! %v", newCksum)
}
}
func TestStateProvidesCellIteratorChannel(t *testing.T) {
state := newTestGameState()
cells, err := state.Cells()
if err != nil {
t.Error(err)
return
}
cellCount := 0
for _ = range cells {
cellCount++
}
if cellCount != 256 {
t.Fail()
}
}
func TestGameStateCellsCanHaveTheirValueSet(t *testing.T) {
state := newTestGameState()
cells, err := state.Cells()
if err != nil {
t.Error(err)
return
}
for cell := range cells {
cell.Value = 1
}
cells, err = state.Cells()
if err != nil {
t.Error(err)
return
}
for cell := range cells {
if cell.Value != 1 {
t.Fail()
}
}
}
func TestGameStateCellsCanBeFetchedByXAndYCoords(t *testing.T) {
state := newTestGameState()
if state.Get(3, 4) == nil {
t.Fail()
}
}
func TestGameOfLifeEmitsGenerationTicksUntilStasis(t *testing.T) {
game := newTestGameOfLife()
genTicks, err := game.Generations()
if err != nil {
t.Error(err)
return
}
nGens := 0
for gen := range genTicks {
if gen.N > 3000 {
t.Errorf("%v is too many generations!", gen.N)
return
}
nGens = gen.N
}
if nGens < 3 {
t.Errorf("%v is too few generations!", nGens)
}
}
func TestGameStateCoordsAreNeverOutOfBounds(t *testing.T) {
state := newTestGameState()
oobX, oobY := state.Width()+rand.Intn(99), state.Height()+rand.Intn(99)
newVal := rand.Intn(9999)
state.Set(oobX, oobY, newVal)
cell := state.Get(oobX, oobY)
if cell == nil {
t.Fail()
}
if cell.Value != newVal {
t.Errorf("(%d, %d) != %d: %d", oobX, oobY, newVal, cell.Value)
}
}
type testResponseWriter struct {
Status int
Headers http.Header
Body []byte
}
func (w *testResponseWriter) WriteHeader(status int) {
w.Status = status
}
func (w *testResponseWriter) Header() http.Header {
return w.Headers
}
func (w *testResponseWriter) Write(bytes []byte) (int, error) {
w.Body = append(w.Body, bytes[:]...)
return len(bytes), nil
}
func newTestResponseWriter() *testResponseWriter {
return &testResponseWriter{
Body: []byte(""),
Headers: make(map[string][]string),
}
}
func TestHandleWebGameRootReturnsIndexPageForGET(t *testing.T) {
req, err := http.NewRequest("GET", "http://localhost:9775/", nil)
if err != nil {
t.Error(err)
return
}
w := newTestResponseWriter()
HandleWebGameRoot(w, req)
if w.Status != http.StatusOK {
t.Errorf("response status != %d: %d", http.StatusOK, w.Status)
return
}
matched, err := regexp.MatchString("<h1>Conway's Game of Life</h1>", string(w.Body))
if err != nil {
t.Error(err)
return
}
if !matched {
t.Errorf("Body did not match expected <h1> element")
return
}
}
func TestHandleWebGameRootReturns405ForNonGET(t *testing.T) {
req, err := http.NewRequest("OPTIONS", "http://localhost:9775/", nil)
if err != nil {
t.Error(err)
return
}
w := newTestResponseWriter()
HandleWebGameRoot(w, req)
if w.Status != http.StatusMethodNotAllowed {
t.Fail()
return
}
matched, err := regexp.MatchString("Nope", string(w.Body))
if err != nil {
t.Error(err)
return
}
if !matched {
t.Fail()
return
}
}
func TestHandleWebGameStateGETReturnsRandomImage(t *testing.T) {
req, err := http.NewRequest("GET", "http://localhost:9775/state", nil)
if err != nil {
t.Error(err)
return
}
w := newTestResponseWriter()
HandleWebGameState(w, req)
if w.Status != http.StatusOK {
t.Errorf("status != %d: %d", http.StatusOK, w.Status)
return
}
webState := &WebGameState{}
err = json.Unmarshal(w.Body, webState)
if err != nil {
t.Error(err)
return
}
imgB64 := []byte(webState.Img)
imgBytes := make([]byte, len(imgB64))
_, err = base64.StdEncoding.Decode(imgBytes, imgB64)
if err != nil {
t.Error(err)
return
}
img, err := png.Decode(bytes.NewBuffer(imgBytes))
if err != nil {
t.Error(err)
return
}
bounds := img.Bounds()
if bounds.Max.Y < 80 || bounds.Max.X < 80 {
t.Errorf("Image has incorrect bounds: %+v", bounds)
return
}
}
func TestHandleWebGameStatePOSTWithStateReturnsNextGeneration(t *testing.T) {
state := newTestGameState()
webGameState := &WebGameState{State: state, Img: ""}
stateJson, err := json.Marshal(webGameState)
if err != nil {
t.Error(err)
return
}
reqBuf := bytes.NewBuffer(stateJson)
req, err := http.NewRequest("POST", "http://localhost:9975/state", reqBuf)
if err != nil {
t.Errorf("err != nil: %+v", err)
return
}
w := newTestResponseWriter()
HandleWebGameState(w, req)
if w.Status != http.StatusCreated {
t.Errorf("response status != %d: %d", http.StatusCreated, w.Status)
return
}
}