<commit_before>// Copyright 2014 The Cockroach Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
// implied. See the License for the specific language governing
// permissions and limitations under the License. See the AUTHORS file
// for names of contributors.
//
// Author: Spencer Kimball (spencer.kimball@gmail.com)

/* Package storage_test provides a means of testing store
functionality which depends on a fully-functional KV client. This
cannot be done within the storage package because of circular
dependencies.
*/
package storage_test

import (
	"fmt"
	"reflect"
	"sort"
	"testing"

	gogoproto "code.google.com/p/gogoprotobuf/proto"
	"github.com/cockroachdb/cockroach/client"
	"github.com/cockroachdb/cockroach/gossip"
	"github.com/cockroachdb/cockroach/kv"
	"github.com/cockroachdb/cockroach/proto"
	"github.com/cockroachdb/cockroach/rpc"
	"github.com/cockroachdb/cockroach/storage"
	"github.com/cockroachdb/cockroach/storage/engine"
	"github.com/cockroachdb/cockroach/util/hlc"
)

// createTestStore creates a test store using an in-memory
// engine. Returns the store clock's manual unix nanos time and the
// store. The caller is responsible for closing the store on exit.
func createTestStore(t *testing.T) *storage.Store {
	rpcContext := rpc.NewContext(hlc.NewClock(hlc.UnixNano), rpc.LoadInsecureTLSConfig())
	g := gossip.New(rpcContext)
	manual := hlc.NewManualClock(0)
	clock := hlc.NewClock(manual.UnixNano)
	eng := engine.NewInMem(proto.Attributes{}, 1<<20)
	lSender := kv.NewLocalSender()
	sender := kv.NewTxnCoordSender(lSender, clock)
	db := client.NewKV(sender, nil)
	db.User = storage.UserRoot
	store := storage.NewStore(clock, eng, db, g)
	if err := store.Bootstrap(proto.StoreIdent{StoreID: 1}); err != nil {
		t.Fatal(err)
	}
	lSender.AddStore(store)
	if err := store.BootstrapRange(); err != nil {
		t.Fatal(err)
	}
	if err := store.Start(); err != nil {
		t.Fatal(err)
	}
	return store
}

// getArgs returns a GetRequest and GetResponse pair addressed to
// the default replica for the specified key.
func getArgs(key []byte, raftID int64, storeID int32) (*proto.GetRequest, *proto.GetResponse) {
	args := &proto.GetRequest{
		RequestHeader: proto.RequestHeader{
			Key:     key,
			RaftID:  raftID,
			Replica: proto.Replica{StoreID: storeID},
		},
	}
	reply := &proto.GetResponse{}
	return args, reply
}

// putArgs returns a PutRequest and PutResponse pair addressed to
// the default replica for the specified key / value.
func putArgs(key, value []byte, raftID int64, storeID int32) (*proto.PutRequest, *proto.PutResponse) {
	args := &proto.PutRequest{
		RequestHeader: proto.RequestHeader{
			Key:     key,
			RaftID:  raftID,
			Replica: proto.Replica{StoreID: storeID},
		},
		Value: proto.Value{
			Bytes: value,
		},
	}
	reply := &proto.PutResponse{}
	return args, reply
}

// incrementArgs returns an IncrementRequest and IncrementResponse pair
// addressed to the default replica for the specified key / value.
func incrementArgs(key []byte, inc int64, raftID int64, storeID int32) (*proto.IncrementRequest, *proto.IncrementResponse) {
	args := &proto.IncrementRequest{
		RequestHeader: proto.RequestHeader{
			Key:     key,
			RaftID:  raftID,
			Replica: proto.Replica{StoreID: storeID},
		},
		Increment: inc,
	}
	reply := &proto.IncrementResponse{}
	return args, reply
}

type metaRecord struct {
	key  proto.Key
	desc *proto.RangeDescriptor
}
type metaSlice []metaRecord

// Implementation of sort.Interface.
func (ms metaSlice) Len() int           { return len(ms) }
func (ms metaSlice) Swap(i, j int)      { ms[i], ms[j] = ms[j], ms[i] }
func (ms metaSlice) Less(i, j int) bool { return ms[i].key.Less(ms[j].key) }

func meta1Key(key proto.Key) proto.Key {
	return engine.MakeKey(engine.KeyMeta1Prefix, key)
}

func meta2Key(key proto.Key) proto.Key {
	return engine.MakeKey(engine.KeyMeta2Prefix, key)
}

// TestUpdateRangeAddressing verifies range addressing records are
// correctly updated on creation of new range descriptors.
func TestUpdateRangeAddressing(t *testing.T) {
	store := createTestStore(t)
	// When split is false, merging treats the right range as the merged
	// range. With merging, expNewLeft indicates the addressing keys we
	// expect to be removed.
	testCases := []struct {
		split                   bool
		leftStart, leftEnd      proto.Key
		rightStart, rightEnd    proto.Key
		leftExpNew, rightExpNew []proto.Key
	}{
		// Start out with whole range.
		{false, engine.KeyMin, engine.KeyMax, engine.KeyMin, engine.KeyMax,
			[]proto.Key{}, []proto.Key{meta1Key(engine.KeyMax), meta2Key(engine.KeyMax)}},
		// Split KeyMin-KeyMax at key "a".
		{true, engine.KeyMin, proto.Key("a"), proto.Key("a"), engine.KeyMax,
			[]proto.Key{meta1Key(engine.KeyMax), meta2Key(proto.Key("a"))}, []proto.Key{meta2Key(engine.KeyMax)}},
		// Split "a"-KeyMax at key "z".
		{true, proto.Key("a"), proto.Key("z"), proto.Key("z"), engine.KeyMax,
			[]proto.Key{meta2Key(proto.Key("z"))}, []proto.Key{meta2Key(engine.KeyMax)}},
		// Split "a"-"z" at key "m".
		{true, proto.Key("a"), proto.Key("m"), proto.Key("m"), proto.Key("z"),
			[]proto.Key{meta2Key(proto.Key("m"))}, []proto.Key{meta2Key(proto.Key("z"))}},
		// Split KeyMin-"a" at meta2(m).
		{true, engine.KeyMin, engine.RangeMetaKey(proto.Key("m")), engine.RangeMetaKey(proto.Key("m")), proto.Key("a"),
			[]proto.Key{meta1Key(proto.Key("m"))}, []proto.Key{meta1Key(engine.KeyMax), meta2Key(proto.Key("a"))}},
		// Split meta2(m)-"a" at meta2(z).
		{true, engine.RangeMetaKey(proto.Key("m")), engine.RangeMetaKey(proto.Key("z")), engine.RangeMetaKey(proto.Key("z")), proto.Key("a"),
			[]proto.Key{meta1Key(proto.Key("z"))}, []proto.Key{meta1Key(engine.KeyMax), meta2Key(proto.Key("a"))}},
		// Split meta2(m)-meta2(z) at meta2(r).
		{true, engine.RangeMetaKey(proto.Key("m")), engine.RangeMetaKey(proto.Key("r")), engine.RangeMetaKey(proto.Key("r")), engine.RangeMetaKey(proto.Key("z")),
			[]proto.Key{meta1Key(proto.Key("r"))}, []proto.Key{meta1Key(proto.Key("z"))}},

		// Now, merge all of our splits backwards...

		// Merge meta2(m)-meta2(z).
		{true, engine.RangeMetaKey(proto.Key("m")), engine.RangeMetaKey(proto.Key("r")), engine.RangeMetaKey(proto.Key("m")), engine.RangeMetaKey(proto.Key("z")),
			[]proto.Key{meta1Key(proto.Key("r"))}, []proto.Key{meta1Key(proto.Key("z"))}},
		// Merge meta2(m)-"a".
		{true, engine.RangeMetaKey(proto.Key("m")), engine.RangeMetaKey(proto.Key("z")), engine.RangeMetaKey(proto.Key("m")), proto.Key("a"),
			[]proto.Key{meta1Key(proto.Key("z"))}, []proto.Key{meta1Key(engine.KeyMax), meta2Key(proto.Key("a"))}},
		// Merge KeyMin-"a".
		{true, engine.KeyMin, engine.RangeMetaKey(proto.Key("m")), engine.KeyMin, proto.Key("a"),
			[]proto.Key{meta1Key(proto.Key("m"))}, []proto.Key{meta1Key(engine.KeyMax), meta2Key(proto.Key("a"))}},
		// Merge "a"-"z".
		{true, proto.Key("a"), proto.Key("m"), proto.Key("a"), proto.Key("z"),
			[]proto.Key{meta2Key(proto.Key("m"))}, []proto.Key{meta2Key(proto.Key("z"))}},
		// Merge "a"-KeyMax.
		{true, proto.Key("a"), proto.Key("z"), proto.Key("a"), engine.KeyMax,
			[]proto.Key{meta2Key(proto.Key("z"))}, []proto.Key{meta2Key(engine.KeyMax)}},
		// Merge KeyMin-KeyMax.
		{true, engine.KeyMin, proto.Key("a"), engine.KeyMin, engine.KeyMax,
			[]proto.Key{meta2Key(proto.Key("a"))}, []proto.Key{meta1Key(engine.KeyMax), meta2Key(engine.KeyMax)}},
	}
	expMetas := metaSlice{}

	for i, test := range testCases {
		left := &proto.RangeDescriptor{RaftID: int64(i), StartKey: test.leftStart, EndKey: test.leftEnd}
		right := &proto.RangeDescriptor{RaftID: int64(i), StartKey: test.rightStart, EndKey: test.rightEnd}
		if test.split {
			if err := storage.SplitRangeAddressing(store.DB(), left, right); err != nil {
				t.Fatal(err)
			}
		} else {
			if err := storage.MergeRangeAddressing(store.DB(), left, right); err != nil {
				t.Fatal(err)
			}
		}
		store.DB().Flush()
		// Scan meta keys directly from engine.
		kvs, err := engine.MVCCScan(store.Engine(), engine.KeyMetaPrefix, engine.KeyMetaMax, 0, proto.MaxTimestamp, nil)
		if err != nil {
			t.Fatal(err)
		}
		metas := metaSlice{}
		for _, kv := range kvs {
			scannedDesc := &proto.RangeDescriptor{}
			if err := gogoproto.Unmarshal(kv.Value.Bytes, scannedDesc); err != nil {
				t.Fatal(err)
			}
			metas = append(metas, metaRecord{key: kv.Key, desc: scannedDesc})
		}

		// Continue to build up the expected metas slice, replacing any earlier
		// version of same key.
		addOrRemoveNew := func(keys []proto.Key, desc *proto.RangeDescriptor, add bool) {
			for _, n := range keys {
				found := -1
				for i := range expMetas {
					if expMetas[i].key.Equal(n) {
						found = i
						expMetas[i].desc = desc
						break
					}
				}
				if found == -1 && add {
					expMetas = append(expMetas, metaRecord{key: n, desc: desc})
				} else if found != -1 && !add {
					expMetas = append(expMetas[:i], expMetas[i+1:]...)
				}
			}
		}
		addOrRemoveNew(test.leftExpNew, left, test.split /* on split, add; on merge, remove */)
		addOrRemoveNew(test.rightExpNew, right, true)
		sort.Sort(expMetas)

		if !reflect.DeepEqual(expMetas, metas) {
			t.Errorf("expected metas don't match")
			for i, meta := range expMetas {
				if !meta.key.Equal(metas[i].key) {
					fmt.Printf("%d: expected %q vs %q\n", i, meta.key, metas[i].key)
				}
				if !reflect.DeepEqual(meta.desc, metas[i].desc) {
					fmt.Printf("%d: expected %q vs %q and %s vs %s\n", i, meta.key, metas[i].key, meta.desc, metas[i].desc)
				}
			}
		}
	}
}

// TestUpdateRangeAddressingSplitMeta1 verifies that it's an error to
// attempt to update range addressing records that would allow a split
// of meta1 records.
func TestUpdateRangeAddressingSplitMeta1(t *testing.T) {
	store := createTestStore(t)
	left := &proto.RangeDescriptor{StartKey: engine.KeyMin, EndKey: meta1Key(proto.Key("a"))}
	right := &proto.RangeDescriptor{StartKey: meta1Key(proto.Key("a")), EndKey: engine.KeyMax}
	if err := storage.SplitRangeAddressing(store.DB(), left, right); err == nil {
		t.Error("expected failure trying to update addressing records for meta1 split")
	}
}
<commit_msg>fix test, added more debugging log statements<commit_after>// Copyright 2014 The Cockroach Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
// implied. See the License for the specific language governing
// permissions and limitations under the License. See the AUTHORS file
// for names of contributors.
//
// Author: Spencer Kimball (spencer.kimball@gmail.com)

/* Package storage_test provides a means of testing store
functionality which depends on a fully-functional KV client. This
cannot be done within the storage package because of circular
dependencies.
*/
package storage_test

import (
	"fmt"
	"reflect"
	"sort"
	"testing"

	gogoproto "code.google.com/p/gogoprotobuf/proto"
	"github.com/cockroachdb/cockroach/client"
	"github.com/cockroachdb/cockroach/gossip"
	"github.com/cockroachdb/cockroach/kv"
	"github.com/cockroachdb/cockroach/proto"
	"github.com/cockroachdb/cockroach/rpc"
	"github.com/cockroachdb/cockroach/storage"
	"github.com/cockroachdb/cockroach/storage/engine"
	"github.com/cockroachdb/cockroach/util/hlc"
	"github.com/cockroachdb/cockroach/util/log"
)

// createTestStore creates a test store using an in-memory
// engine. Returns the store clock's manual unix nanos time and the
// store. The caller is responsible for closing the store on exit.
func createTestStore(t *testing.T) *storage.Store {
	rpcContext := rpc.NewContext(hlc.NewClock(hlc.UnixNano), rpc.LoadInsecureTLSConfig())
	g := gossip.New(rpcContext)
	manual := hlc.NewManualClock(0)
	clock := hlc.NewClock(manual.UnixNano)
	eng := engine.NewInMem(proto.Attributes{}, 1<<20)
	lSender := kv.NewLocalSender()
	sender := kv.NewTxnCoordSender(lSender, clock)
	db := client.NewKV(sender, nil)
	db.User = storage.UserRoot
	store := storage.NewStore(clock, eng, db, g)
	if err := store.Bootstrap(proto.StoreIdent{StoreID: 1}); err != nil {
		t.Fatal(err)
	}
	lSender.AddStore(store)
	if err := store.BootstrapRange(); err != nil {
		t.Fatal(err)
	}
	if err := store.Start(); err != nil {
		t.Fatal(err)
	}
	return store
}

// getArgs returns a GetRequest and GetResponse pair addressed to
// the default replica for the specified key.
func getArgs(key []byte, raftID int64, storeID int32) (*proto.GetRequest, *proto.GetResponse) {
	args := &proto.GetRequest{
		RequestHeader: proto.RequestHeader{
			Key:     key,
			RaftID:  raftID,
			Replica: proto.Replica{StoreID: storeID},
		},
	}
	reply := &proto.GetResponse{}
	return args, reply
}

// putArgs returns a PutRequest and PutResponse pair addressed to
// the default replica for the specified key / value.
func putArgs(key, value []byte, raftID int64, storeID int32) (*proto.PutRequest, *proto.PutResponse) {
	args := &proto.PutRequest{
		RequestHeader: proto.RequestHeader{
			Key:     key,
			RaftID:  raftID,
			Replica: proto.Replica{StoreID: storeID},
		},
		Value: proto.Value{
			Bytes: value,
		},
	}
	reply := &proto.PutResponse{}
	return args, reply
}

// incrementArgs returns an IncrementRequest and IncrementResponse pair
// addressed to the default replica for the specified key / value.
func incrementArgs(key []byte, inc int64, raftID int64, storeID int32) (*proto.IncrementRequest, *proto.IncrementResponse) {
	args := &proto.IncrementRequest{
		RequestHeader: proto.RequestHeader{
			Key:     key,
			RaftID:  raftID,
			Replica: proto.Replica{StoreID: storeID},
		},
		Increment: inc,
	}
	reply := &proto.IncrementResponse{}
	return args, reply
}

type metaRecord struct {
	key  proto.Key
	desc *proto.RangeDescriptor
}
type metaSlice []metaRecord

// Implementation of sort.Interface.
func (ms metaSlice) Len() int           { return len(ms) }
func (ms metaSlice) Swap(i, j int)      { ms[i], ms[j] = ms[j], ms[i] }
func (ms metaSlice) Less(i, j int) bool { return ms[i].key.Less(ms[j].key) }

func meta1Key(key proto.Key) proto.Key {
	return engine.MakeKey(engine.KeyMeta1Prefix, key)
}

func meta2Key(key proto.Key) proto.Key {
	return engine.MakeKey(engine.KeyMeta2Prefix, key)
}

// TestUpdateRangeAddressing verifies range addressing records are
// correctly updated on creation of new range descriptors.
func TestUpdateRangeAddressing(t *testing.T) {
	store := createTestStore(t)
	// When split is false, merging treats the right range as the merged
	// range. With merging, expNewLeft indicates the addressing keys we
	// expect to be removed.
	testCases := []struct {
		split                   bool
		leftStart, leftEnd      proto.Key
		rightStart, rightEnd    proto.Key
		leftExpNew, rightExpNew []proto.Key
	}{
		// Start out with whole range.
		{false, engine.KeyMin, engine.KeyMax, engine.KeyMin, engine.KeyMax,
			[]proto.Key{}, []proto.Key{meta1Key(engine.KeyMax), meta2Key(engine.KeyMax)}},
		// Split KeyMin-KeyMax at key "a".
		{true, engine.KeyMin, proto.Key("a"), proto.Key("a"), engine.KeyMax,
			[]proto.Key{meta1Key(engine.KeyMax), meta2Key(proto.Key("a"))}, []proto.Key{meta2Key(engine.KeyMax)}},
		// Split "a"-KeyMax at key "z".
		{true, proto.Key("a"), proto.Key("z"), proto.Key("z"), engine.KeyMax,
			[]proto.Key{meta2Key(proto.Key("z"))}, []proto.Key{meta2Key(engine.KeyMax)}},
		// Split "a"-"z" at key "m".
		{true, proto.Key("a"), proto.Key("m"), proto.Key("m"), proto.Key("z"),
			[]proto.Key{meta2Key(proto.Key("m"))}, []proto.Key{meta2Key(proto.Key("z"))}},
		// Split KeyMin-"a" at meta2(m).
		{true, engine.KeyMin, engine.RangeMetaKey(proto.Key("m")), engine.RangeMetaKey(proto.Key("m")), proto.Key("a"),
			[]proto.Key{meta1Key(proto.Key("m"))}, []proto.Key{meta1Key(engine.KeyMax), meta2Key(proto.Key("a"))}},
		// Split meta2(m)-"a" at meta2(z).
		{true, engine.RangeMetaKey(proto.Key("m")), engine.RangeMetaKey(proto.Key("z")), engine.RangeMetaKey(proto.Key("z")), proto.Key("a"),
			[]proto.Key{meta1Key(proto.Key("z"))}, []proto.Key{meta1Key(engine.KeyMax), meta2Key(proto.Key("a"))}},
		// Split meta2(m)-meta2(z) at meta2(r).
		{true, engine.RangeMetaKey(proto.Key("m")), engine.RangeMetaKey(proto.Key("r")), engine.RangeMetaKey(proto.Key("r")), engine.RangeMetaKey(proto.Key("z")),
			[]proto.Key{meta1Key(proto.Key("r"))}, []proto.Key{meta1Key(proto.Key("z"))}},

		// Now, merge all of our splits backwards...

		// Merge meta2(m)-meta2(z).
		{false, engine.RangeMetaKey(proto.Key("m")), engine.RangeMetaKey(proto.Key("r")), engine.RangeMetaKey(proto.Key("m")), engine.RangeMetaKey(proto.Key("z")),
			[]proto.Key{meta1Key(proto.Key("r"))}, []proto.Key{meta1Key(proto.Key("z"))}},
		// Merge meta2(m)-"a".
		{false, engine.RangeMetaKey(proto.Key("m")), engine.RangeMetaKey(proto.Key("z")), engine.RangeMetaKey(proto.Key("m")), proto.Key("a"),
			[]proto.Key{meta1Key(proto.Key("z"))}, []proto.Key{meta1Key(engine.KeyMax), meta2Key(proto.Key("a"))}},
		// Merge KeyMin-"a".
		{false, engine.KeyMin, engine.RangeMetaKey(proto.Key("m")), engine.KeyMin, proto.Key("a"),
			[]proto.Key{meta1Key(proto.Key("m"))}, []proto.Key{meta1Key(engine.KeyMax), meta2Key(proto.Key("a"))}},
		// Merge "a"-"z".
		{false, proto.Key("a"), proto.Key("m"), proto.Key("a"), proto.Key("z"),
			[]proto.Key{meta2Key(proto.Key("m"))}, []proto.Key{meta2Key(proto.Key("z"))}},
		// Merge "a"-KeyMax.
		{false, proto.Key("a"), proto.Key("z"), proto.Key("a"), engine.KeyMax,
			[]proto.Key{meta2Key(proto.Key("z"))}, []proto.Key{meta2Key(engine.KeyMax)}},
		// Merge KeyMin-KeyMax.
		{false, engine.KeyMin, proto.Key("a"), engine.KeyMin, engine.KeyMax,
			[]proto.Key{meta2Key(proto.Key("a"))}, []proto.Key{meta1Key(engine.KeyMax), meta2Key(engine.KeyMax)}},
	}
	expMetas := metaSlice{}

	for i, test := range testCases {
		left := &proto.RangeDescriptor{RaftID: int64(i * 2), StartKey: test.leftStart, EndKey: test.leftEnd}
		right := &proto.RangeDescriptor{RaftID: int64(i*2 + 1), StartKey: test.rightStart, EndKey: test.rightEnd}
		if test.split {
			if err := storage.SplitRangeAddressing(store.DB(), left, right); err != nil {
				t.Fatal(err)
			}
		} else {
			if err := storage.MergeRangeAddressing(store.DB(), left, right); err != nil {
				t.Fatal(err)
			}
		}
		store.DB().Flush()
		// Scan meta keys directly from engine.
		kvs, err := engine.MVCCScan(store.Engine(), engine.KeyMetaPrefix, engine.KeyMetaMax, 0, proto.MaxTimestamp, nil)
		if err != nil {
			t.Fatal(err)
		}
		metas := metaSlice{}
		for _, kv := range kvs {
			scannedDesc := &proto.RangeDescriptor{}
			if err := gogoproto.Unmarshal(kv.Value.Bytes, scannedDesc); err != nil {
				t.Fatal(err)
			}
			metas = append(metas, metaRecord{key: kv.Key, desc: scannedDesc})
		}

		// Continue to build up the expected metas slice, replacing any earlier
		// version of same key.
		addOrRemoveNew := func(keys []proto.Key, desc *proto.RangeDescriptor, add bool) {
			for _, n := range keys {
				found := -1
				for i := range expMetas {
					if expMetas[i].key.Equal(n) {
						found = i
						expMetas[i].desc = desc
						break
					}
				}
				if found == -1 && add {
					expMetas = append(expMetas, metaRecord{key: n, desc: desc})
				} else if found != -1 && !add {
					expMetas = append(expMetas[:found], expMetas[found+1:]...)
				}
			}
		}
		addOrRemoveNew(test.leftExpNew, left, test.split /* on split, add; on merge, remove */)
		addOrRemoveNew(test.rightExpNew, right, true)
		sort.Sort(expMetas)

		if test.split {
			log.V(1).Infof("test case %d: split %q-%q at %q", i, left.StartKey, right.EndKey, left.EndKey)
		} else {
			log.V(1).Infof("test case %d: merge %q-%q + %q-%q", i, left.StartKey, left.EndKey, left.EndKey, right.EndKey)
		}
		for _, meta := range metas {
			log.V(1).Infof("%q", meta.key)
		}
		log.V(1).Infof("")

		if !reflect.DeepEqual(expMetas, metas) {
			t.Errorf("expected metas don't match")
			if len(expMetas) != len(metas) {
				t.Errorf("len(expMetas) != len(metas); %d != %d", len(expMetas), len(metas))
			} else {
				for i, meta := range expMetas {
					if !meta.key.Equal(metas[i].key) {
						fmt.Printf("%d: expected %q vs %q\n", i, meta.key, metas[i].key)
					}
					if !reflect.DeepEqual(meta.desc, metas[i].desc) {
						fmt.Printf("%d: expected %q vs %q and %s vs %s\n", i, meta.key, metas[i].key, meta.desc, metas[i].desc)
					}
				}
			}
		}
	}
}

// TestUpdateRangeAddressingSplitMeta1 verifies that it's an error to
// attempt to update range addressing records that would allow a split
// of meta1 records.
func TestUpdateRangeAddressingSplitMeta1(t *testing.T) {
	store := createTestStore(t)
	left := &proto.RangeDescriptor{StartKey: engine.KeyMin, EndKey: meta1Key(proto.Key("a"))}
	right := &proto.RangeDescriptor{StartKey: meta1Key(proto.Key("a")), EndKey: engine.KeyMax}
	if err := storage.SplitRangeAddressing(store.DB(), left, right); err == nil {
		t.Error("expected failure trying to update addressing records for meta1 split")
	}
}
<|endoftext|>


<commit_before>package storage

import (
	"github.com/tecbot/gorocksdb"
	"math"
	"sort"
	log "github.com/Sirupsen/logrus"
        "bytes"
	"errors"
)

var defaultWriteOptions = gorocksdb.NewDefaultWriteOptions()
var defaultReadOptions = gorocksdb.NewDefaultReadOptions()

const (
	ComparatorName = "TRANSICATOR-V1"
)

type KeyComparator struct {
}

func (k KeyComparator) Compare(a, b []byte) int {
	if len(a) < 1 || len(b) < 1 {
		return 0
	}

	// Do something reasonable if the versions do not match
	vers1 := (int)((a[0] >> 4) & 0xf)
	vers2 := (int)((b[0] >> 4) & 0xf)
	if (vers1 != KeyVersion) || (vers2 != KeyVersion) {
		return vers1 + 100 /* WTF IS THIS */
	}

	// The types don't match, just compare the types
	type1 := a[0] & 0xf
	type2 := b[0] & 0xf

	if type1 < type2 {
		return -1
	}

	if type1 > type2 {
		return 1
	}

	switch type1 {
	case StringKey:
		return bytes.Compare(a,b)
	case IndexKey:
		return k.CompareIndexKey(a, b)
	default:
		return 999
	}

}

func (k KeyComparator) CompareIndexKey(a,b []byte) int {
	aScope, aLsn, aIndex, err := keyToLsnAndOffset(a)
	if err != nil {
		return 999 // Might as well follow the above when things are berzerk
	}
	bScope, bLsn, bIndex, err := keyToLsnAndOffset(b)
	if err != nil {
		return 999
	}

	if aScope == bScope {
		if aLsn < bLsn {
			return -1
		} else if aLsn > bLsn {
			return 1
		}

		if aIndex < bIndex {
			return -1
		} else if aIndex > bIndex {
			return 1
		}
		return 0
	} else {
		return bytes.Compare([]byte(aScope), []byte(bScope))
	}

}

func (k KeyComparator) Name() string {
	return ComparatorName
}

/*
A DB is a handles to a LevelDB database.
*/
type DB struct {
	baseFile string
	db       *gorocksdb.DB
	options  *gorocksdb.Options
}

type readResult struct {
	lsn   int64
	index int32
	data  []byte
}
type readResults []readResult

/*
OpenDB opens a LevelDB database and makes it available for reads and writes.
Opened databases should be closed when done.

The "baseFile" parameter refers to the name of a directory where RocksDB can
store its data. RocksDB will create many files inside this directory. To create
an empty database, make sure that it is empty.

The "cacheSize" parameter specifies the maximum number of bytes to use in memory
for an LRU cache of database contents. How this cache is used is up to RocksDB.
*/
func OpenDB(baseFile string, cacheSize uint) (*DB, error) {
	stor := &DB{
		baseFile: baseFile,
	}

	var err error

	stor.options = gorocksdb.NewDefaultOptions()
	stor.options.SetCreateIfMissing(true)
	stor.options.SetComparator(new(KeyComparator))

	stor.db, err = gorocksdb.OpenDb(stor.options, baseFile)
	if err != nil {
		return nil, err
	}

	log.Infof("Opened LevelDB file in %s", baseFile)

	return stor, nil
}

/*
GetDataPath returns the path of the data directory.
*/
func (s *DB) GetDataPath() string {
	return s.baseFile
}

/*
Close closes the database cleanly.
*/
func (s *DB) Close() {
	log.Infof("Closed DB in %s", s.baseFile)
	s.db.Close()
	s.options.Destroy()
}

/*
Delete deletes all the files used by the database.
*/
func (s *DB) Delete() error {
	options := gorocksdb.NewDefaultOptions()
	defer options.Destroy()
        return gorocksdb.DestroyDb(s.baseFile, options)
}

/*
GetIntMetadata returns metadata with the specified key and converts it
into a uint64. It returns 0 if the key cannot be found.
*/
func (s *DB) GetIntMetadata(key string) (int64, error) {
	keyBuf := stringToKey(StringKey, key)

        val, err := s.db.GetBytes(defaultReadOptions, keyBuf)
	if err != nil {
		return 0, err
	}
	if val == nil {
		return 0, nil
	}

        return bytesToInt(val), err
}

/*
GetMetadata returns the raw metadata matching the specified key,
or nil if the key is not found.
*/
func (s *DB) GetMetadata(key string) ([]byte, error) {
	return s.readMetadataKey(key, defaultReadOptions)
}

func (s *DB) readMetadataKey(key string, ro *gorocksdb.ReadOptions) ([]byte, error) {
	keyBuf := stringToKey(StringKey, key)

	return s.readEntry(keyBuf, ro)
}

/*
SetIntMetadata sets the metadata with the specified key to
the integer value.
*/
func (s *DB) SetIntMetadata(key string, val int64) error {
	keyBuf := stringToKey(StringKey, key)
	valBuf := intToBytes(val)

	return s.putEntry(keyBuf, valBuf, defaultWriteOptions)
}

/*
SetMetadata sets the metadata with the specified key.
*/
func (s *DB) SetMetadata(key string, val []byte) error {
	keyBuf := stringToKey(StringKey, key)

	return s.putEntry(keyBuf, val, defaultWriteOptions)
}

/*
PutEntry writes an entry to the database indexed by scope, lsn, and index in order
*/
func (s *DB) PutEntry(scope string, lsn uint64, index uint32, data []byte) error {
	keyBuf := lsnAndOffsetToKey(IndexKey, scope, lsn, index)

	return s.putEntry(keyBuf, data, defaultWriteOptions)
}

/*
PutEntryAndMetadata writes an entry to the database in the same batch as
a metadata write.
*/
func (s *DB) PutEntryAndMetadata(scope string, lsn uint64, index uint32,
	data []byte, metaKey string, metaVal []byte) (err error) {
	if len(data) == 0 || len(metaVal) == 0{
		err = errors.New("Data or metaval is empty")
		return
	}

	batch := gorocksdb.NewWriteBatch()
	defer batch.Destroy()

	keyBuf := lsnAndOffsetToKey(IndexKey, scope, lsn, index)
	batch.Put(keyBuf, data)

	mkeyBuf := stringToKey(StringKey, metaKey)
	batch.Put(mkeyBuf, metaVal)
	err = s.db.Write(defaultWriteOptions, batch)
	return
}

/*
GetEntry returns what was written by PutEntry.
*/
func (s *DB) GetEntry(scope string, lsn uint64, index uint32) ([]byte, error) {
	keyBuf := lsnAndOffsetToKey(IndexKey, scope, lsn, index)
	return s.readEntry(keyBuf, defaultReadOptions)
}

/*
GetEntries returns entries in sequence number order for the given
scope. The first entry returned will be the first entry that matches the specified
startLSN and startIndex. No more than "limit" entries will be returned.
To retrieve the very next entry after an entry, simply increment the index
by 1.
*/
func (s *DB) GetEntries(scope string, startLSN uint64,
	startIndex uint32, limit int, filter func([]byte) bool) ([][]byte, error) {

	rr, err := s.readOneRange(scope, startLSN, startIndex, limit,
		defaultReadOptions, filter)
	if err != nil {
		return nil, err
	}

	var results [][]byte
	for _, r := range rr {
		results = append(results, r.data)
	}
	return results, nil
}

/*
GetMultiEntries returns entries in sequence number order a list of scopes.
It also returns a set of metadata.
The first entry returned will be the first entry that matches the specified
startLSN and startIndex. No more than "limit" entries will be returned.
To retrieve the very next entry after an entry, simply increment the index
by 1. This method uses a snapshot to guarantee consistency even if data is
being inserted to the database -- as long as the data is being inserted
in LSN order!
The first array returned is the array of entries (again, in "sequence" order).
The second is the array of metadata values, if any, in the order in which
the keys were supplied.
*/
func (s *DB) GetMultiEntries(scopes []string, metadataKeys []string,
	startLSN uint64, startIndex uint32,
	limit int, filter func([]byte) bool) ([][]byte, [][]byte, error) {

	// Do this all inside a level DB snapshot so that we get a repeatable read
	snap := s.db.NewSnapshot()
	defer snap.Release()

	ropts := gorocksdb.NewDefaultReadOptions()
	ropts.SetSnapshot(snap)
	defer ropts.Destroy()

	// Read range for each scope
	var results readResults
	for _, scope := range scopes {
		rr, err := s.readOneRange(scope, startLSN, startIndex, limit, ropts, filter)
		if err != nil {
			return nil, nil, err
		}
		results = append(results, rr...)
	}

	// Read metadata for each key
	var metadata [][]byte
	for _, key := range metadataKeys {
		data, err := s.readMetadataKey(key, ropts)
		if err != nil {
			if err != nil {
				return nil, nil, err
			}
		}
		metadata = append(metadata, data)
	}

	// Sort and then take limit
	sort.Sort(results)

	var final [][]byte
	for count := 0; count < len(results) && count < limit; count++ {
		final = append(final, results[count].data)
	}
	return final, metadata, nil
}

/*
PurgeEntries deletes everything from the database for which "filter" returns
true. It always returns the number of records that were actually deleted
(which could be zero). If there is an error during the purge process,
then a non-nil error will be returned. Be aware that this operation may
take a long time, so it is important to run it in a separate goroutine.
*/
func (s *DB) PurgeEntries(filter func([]byte) bool) (purgeCount uint64, err error) {
	it := s.db.NewIterator(defaultReadOptions)
	defer it.Close()

	it.SeekToFirst()

	var rowCount int
	for it = it; it.Valid(); it.Next() {
		dataBuf := it.Value().Data()
		if filter(dataBuf) {
			err = s.deleteEntry(it.Key().Data())
			if err != nil {
				return
			}
			purgeCount++
		}
		rowCount++
	}
	return
}

func (s *DB) readOneRange(scope string, startLSN uint64,
	startIndex uint32, limit int, ro *gorocksdb.ReadOptions,
	filter func([]byte) bool) (readResults, error) {

	startKeyBuf := lsnAndOffsetToKey(IndexKey, scope, startLSN, startIndex)
	endKeyBuf := lsnAndOffsetToKey(IndexKey, scope, math.MaxInt64, math.MaxInt32)

	it := s.db.NewIterator(ro)
	defer it.Close()

	it.Seek(startKeyBuf)

	var results readResults
	c := new(KeyComparator)

	for it = it; it.Valid() && len(results) < limit; it.Next() {
		iterKey := it.Key().Data()
		if c.Compare(iterKey, endKeyBuf) > 0 {
			// Reached the end of our range
			//fmt.Printf("Stopped due to end of range, iterKey is %s, endKey is %s\n", hex.Dump(iterKey), hex.Dump(endKeyBuf))
			break
		}

		_, iterLSN, iterIx, err := keyToIndex(iterKey)
		if err != nil {
			return nil, err
		}

		newVal := it.Value().Data()

		if filter == nil || filter(newVal) {
			result := readResult{
				lsn:   iterLSN,
				index: iterIx,
				data:  newVal,
			}
			results = append(results, result)
		}
	}

	return results, nil
}

func (s *DB) deleteEntry(key []byte) (err error) {
	err = s.db.Delete(defaultWriteOptions, key)
	return
}

func zeroByteSlice(slice []byte) (allZeros bool) {
	allZeros = true
	for s, _ := range(slice) {
		if s != 0x0 {
			allZeros = false
			return
		}
	}
	return
}

func (s *DB) putEntry(key, value []byte, wo *gorocksdb.WriteOptions) (err error) {
	err = s.db.Put(wo,key,value)
	return
}

func (s *DB) readEntry(key []byte, ro *gorocksdb.ReadOptions) (val []byte, err error) {
	val, err = s.db.GetBytes(ro, key)
	return
}

// Needed to sort read results by LSN and index

func (r readResults) Len() int {
	return len(r)
}

func (r readResults) Less(i, j int) bool {
	if r[i].lsn < r[j].lsn {
		return true
	}
	if r[i].lsn == r[j].lsn && r[i].index < r[j].index {
		return true
	}
	return false
}

func (r readResults) Swap(i, j int) {
	tmp := r[i]
	r[i] = r[j]
	r[j] = tmp
}
<commit_msg>Its not leveldb anymore<commit_after>package storage

import (
	"github.com/tecbot/gorocksdb"
	"math"
	"sort"
	log "github.com/Sirupsen/logrus"
        "bytes"
	"errors"
)

var defaultWriteOptions = gorocksdb.NewDefaultWriteOptions()
var defaultReadOptions = gorocksdb.NewDefaultReadOptions()

const (
	ComparatorName = "TRANSICATOR-V1"
)

type KeyComparator struct {
}

func (k KeyComparator) Compare(a, b []byte) int {
	if len(a) < 1 || len(b) < 1 {
		return 0
	}

	// Do something reasonable if the versions do not match
	vers1 := (int)((a[0] >> 4) & 0xf)
	vers2 := (int)((b[0] >> 4) & 0xf)
	if (vers1 != KeyVersion) || (vers2 != KeyVersion) {
		return vers1 + 100 /* WTF IS THIS */
	}

	// The types don't match, just compare the types
	type1 := a[0] & 0xf
	type2 := b[0] & 0xf

	if type1 < type2 {
		return -1
	}

	if type1 > type2 {
		return 1
	}

	switch type1 {
	case StringKey:
		return bytes.Compare(a,b)
	case IndexKey:
		return k.CompareIndexKey(a, b)
	default:
		return 999
	}

}

func (k KeyComparator) CompareIndexKey(a,b []byte) int {
	aScope, aLsn, aIndex, err := keyToLsnAndOffset(a)
	if err != nil {
		return 999 // Might as well follow the above when things are berzerk
	}
	bScope, bLsn, bIndex, err := keyToLsnAndOffset(b)
	if err != nil {
		return 999
	}

	if aScope == bScope {
		if aLsn < bLsn {
			return -1
		} else if aLsn > bLsn {
			return 1
		}

		if aIndex < bIndex {
			return -1
		} else if aIndex > bIndex {
			return 1
		}
		return 0
	} else {
		return bytes.Compare([]byte(aScope), []byte(bScope))
	}

}

func (k KeyComparator) Name() string {
	return ComparatorName
}

/*
A DB is a handle to a RocksDB database.
*/
type DB struct {
	baseFile string
	db       *gorocksdb.DB
	options  *gorocksdb.Options
}

type readResult struct {
	lsn   int64
	index int32
	data  []byte
}
type readResults []readResult

/*
OpenDB opens a RocksDB database and makes it available for reads and writes.
Opened databases should be closed when done.

The "baseFile" parameter refers to the name of a directory where RocksDB can
store its data. RocksDB will create many files inside this directory. To create
an empty database, make sure that it is empty.

The "cacheSize" parameter specifies the maximum number of bytes to use in memory
for an LRU cache of database contents. How this cache is used is up to RocksDB.
*/
func OpenDB(baseFile string, cacheSize uint) (*DB, error) {
	stor := &DB{
		baseFile: baseFile,
	}

	var err error

	stor.options = gorocksdb.NewDefaultOptions()
	stor.options.SetCreateIfMissing(true)
	stor.options.SetComparator(new(KeyComparator))

	stor.db, err = gorocksdb.OpenDb(stor.options, baseFile)
	if err != nil {
		return nil, err
	}

	log.Infof("Opened RocksDB file in %s", baseFile)

	return stor, nil
}

/*
GetDataPath returns the path of the data directory.
*/
func (s *DB) GetDataPath() string {
	return s.baseFile
}

/*
Close closes the database cleanly.
*/
func (s *DB) Close() {
	log.Infof("Closed DB in %s", s.baseFile)
	s.db.Close()
	s.options.Destroy()
}

/*
Delete deletes all the files used by the database.
*/
func (s *DB) Delete() error {
	options := gorocksdb.NewDefaultOptions()
	defer options.Destroy()
        return gorocksdb.DestroyDb(s.baseFile, options)
}

/*
GetIntMetadata returns metadata with the specified key and converts it
into a uint64. It returns 0 if the key cannot be found.
*/
func (s *DB) GetIntMetadata(key string) (int64, error) {
	keyBuf := stringToKey(StringKey, key)

        val, err := s.db.GetBytes(defaultReadOptions, keyBuf)
	if err != nil {
		return 0, err
	}
	if val == nil {
		return 0, nil
	}

        return bytesToInt(val), err
}

/*
GetMetadata returns the raw metadata matching the specified key,
or nil if the key is not found.
*/
func (s *DB) GetMetadata(key string) ([]byte, error) {
	return s.readMetadataKey(key, defaultReadOptions)
}

func (s *DB) readMetadataKey(key string, ro *gorocksdb.ReadOptions) ([]byte, error) {
	keyBuf := stringToKey(StringKey, key)

	return s.readEntry(keyBuf, ro)
}

/*
SetIntMetadata sets the metadata with the specified key to
the integer value.
*/
func (s *DB) SetIntMetadata(key string, val int64) error {
	keyBuf := stringToKey(StringKey, key)
	valBuf := intToBytes(val)

	return s.putEntry(keyBuf, valBuf, defaultWriteOptions)
}

/*
SetMetadata sets the metadata with the specified key.
*/
func (s *DB) SetMetadata(key string, val []byte) error {
	keyBuf := stringToKey(StringKey, key)

	return s.putEntry(keyBuf, val, defaultWriteOptions)
}

/*
PutEntry writes an entry to the database indexed by scope, lsn, and index in order
*/
func (s *DB) PutEntry(scope string, lsn uint64, index uint32, data []byte) error {
	keyBuf := lsnAndOffsetToKey(IndexKey, scope, lsn, index)

	return s.putEntry(keyBuf, data, defaultWriteOptions)
}

/*
PutEntryAndMetadata writes an entry to the database in the same batch as
a metadata write.
*/
func (s *DB) PutEntryAndMetadata(scope string, lsn uint64, index uint32,
	data []byte, metaKey string, metaVal []byte) (err error) {
	if len(data) == 0 || len(metaVal) == 0{
		err = errors.New("Data or metaval is empty")
		return
	}

	batch := gorocksdb.NewWriteBatch()
	defer batch.Destroy()

	keyBuf := lsnAndOffsetToKey(IndexKey, scope, lsn, index)
	batch.Put(keyBuf, data)

	mkeyBuf := stringToKey(StringKey, metaKey)
	batch.Put(mkeyBuf, metaVal)
	err = s.db.Write(defaultWriteOptions, batch)
	return
}

/*
GetEntry returns what was written by PutEntry.
*/
func (s *DB) GetEntry(scope string, lsn uint64, index uint32) ([]byte, error) {
	keyBuf := lsnAndOffsetToKey(IndexKey, scope, lsn, index)
	return s.readEntry(keyBuf, defaultReadOptions)
}

/*
GetEntries returns entries in sequence number order for the given
scope. The first entry returned will be the first entry that matches the specified
startLSN and startIndex. No more than "limit" entries will be returned.
To retrieve the very next entry after an entry, simply increment the index
by 1.
*/
func (s *DB) GetEntries(scope string, startLSN uint64,
	startIndex uint32, limit int, filter func([]byte) bool) ([][]byte, error) {

	rr, err := s.readOneRange(scope, startLSN, startIndex, limit,
		defaultReadOptions, filter)
	if err != nil {
		return nil, err
	}

	var results [][]byte
	for _, r := range rr {
		results = append(results, r.data)
	}
	return results, nil
}

/*
GetMultiEntries returns entries in sequence number order a list of scopes.
It also returns a set of metadata.
The first entry returned will be the first entry that matches the specified
startLSN and startIndex. No more than "limit" entries will be returned.
To retrieve the very next entry after an entry, simply increment the index
by 1. This method uses a snapshot to guarantee consistency even if data is
being inserted to the database -- as long as the data is being inserted
in LSN order!
The first array returned is the array of entries (again, in "sequence" order).
The second is the array of metadata values, if any, in the order in which
the keys were supplied.
*/
func (s *DB) GetMultiEntries(scopes []string, metadataKeys []string,
	startLSN uint64, startIndex uint32,
	limit int, filter func([]byte) bool) ([][]byte, [][]byte, error) {

	// Do this all inside a level DB snapshot so that we get a repeatable read
	snap := s.db.NewSnapshot()
	defer snap.Release()

	ropts := gorocksdb.NewDefaultReadOptions()
	ropts.SetSnapshot(snap)
	defer ropts.Destroy()

	// Read range for each scope
	var results readResults
	for _, scope := range scopes {
		rr, err := s.readOneRange(scope, startLSN, startIndex, limit, ropts, filter)
		if err != nil {
			return nil, nil, err
		}
		results = append(results, rr...)
	}

	// Read metadata for each key
	var metadata [][]byte
	for _, key := range metadataKeys {
		data, err := s.readMetadataKey(key, ropts)
		if err != nil {
			if err != nil {
				return nil, nil, err
			}
		}
		metadata = append(metadata, data)
	}

	// Sort and then take limit
	sort.Sort(results)

	var final [][]byte
	for count := 0; count < len(results) && count < limit; count++ {
		final = append(final, results[count].data)
	}
	return final, metadata, nil
}

/*
PurgeEntries deletes everything from the database for which "filter" returns
true. It always returns the number of records that were actually deleted
(which could be zero). If there is an error during the purge process,
then a non-nil error will be returned. Be aware that this operation may
take a long time, so it is important to run it in a separate goroutine.
*/
func (s *DB) PurgeEntries(filter func([]byte) bool) (purgeCount uint64, err error) {
	it := s.db.NewIterator(defaultReadOptions)
	defer it.Close()

	it.SeekToFirst()

	var rowCount int
	for it = it; it.Valid(); it.Next() {
		dataBuf := it.Value().Data()
		if filter(dataBuf) {
			err = s.deleteEntry(it.Key().Data())
			if err != nil {
				return
			}
			purgeCount++
		}
		rowCount++
	}
	return
}

func (s *DB) readOneRange(scope string, startLSN uint64,
	startIndex uint32, limit int, ro *gorocksdb.ReadOptions,
	filter func([]byte) bool) (readResults, error) {

	startKeyBuf := lsnAndOffsetToKey(IndexKey, scope, startLSN, startIndex)
	endKeyBuf := lsnAndOffsetToKey(IndexKey, scope, math.MaxInt64, math.MaxInt32)

	it := s.db.NewIterator(ro)
	defer it.Close()

	it.Seek(startKeyBuf)

	var results readResults
	c := new(KeyComparator)

	for it = it; it.Valid() && len(results) < limit; it.Next() {
		iterKey := it.Key().Data()
		if c.Compare(iterKey, endKeyBuf) > 0 {
			// Reached the end of our range
			//fmt.Printf("Stopped due to end of range, iterKey is %s, endKey is %s\n", hex.Dump(iterKey), hex.Dump(endKeyBuf))
			break
		}

		_, iterLSN, iterIx, err := keyToIndex(iterKey)
		if err != nil {
			return nil, err
		}

		newVal := it.Value().Data()

		if filter == nil || filter(newVal) {
			result := readResult{
				lsn:   iterLSN,
				index: iterIx,
				data:  newVal,
			}
			results = append(results, result)
		}
	}

	return results, nil
}

func (s *DB) deleteEntry(key []byte) (err error) {
	err = s.db.Delete(defaultWriteOptions, key)
	return
}

func zeroByteSlice(slice []byte) (allZeros bool) {
	allZeros = true
	for s, _ := range(slice) {
		if s != 0x0 {
			allZeros = false
			return
		}
	}
	return
}

func (s *DB) putEntry(key, value []byte, wo *gorocksdb.WriteOptions) (err error) {
	err = s.db.Put(wo,key,value)
	return
}

func (s *DB) readEntry(key []byte, ro *gorocksdb.ReadOptions) (val []byte, err error) {
	val, err = s.db.GetBytes(ro, key)
	return
}

// Needed to sort read results by LSN and index

func (r readResults) Len() int {
	return len(r)
}

func (r readResults) Less(i, j int) bool {
	if r[i].lsn < r[j].lsn {
		return true
	}
	if r[i].lsn == r[j].lsn && r[i].index < r[j].index {
		return true
	}
	return false
}

func (r readResults) Swap(i, j int) {
	tmp := r[i]
	r[i] = r[j]
	r[j] = tmp
}
<|endoftext|>


<commit_before>package resource

import (
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"os"
	"path/filepath"
	"strings"
	"testing"

	"github.com/hashicorp/terraform/config/module"
	"github.com/hashicorp/terraform/terraform"
)

const TestEnvVar = "TF_ACC"

// TestCheckFunc is the callback type used with acceptance tests to check
// the state of a resource. The state passed in is the latest state known,
// or in the case of being after a destroy, it is the last known state when
// it was created.
type TestCheckFunc func(*terraform.State) error

// TestCase is a single acceptance test case used to test the apply/destroy
// lifecycle of a resource in a specific configuration.
//
// When the destroy plan is executed, the config from the last TestStep
// is used to plan it.
type TestCase struct {
	// PreCheck, if non-nil, will be called before any test steps are
	// executed. It will only be executed in the case that the steps
	// would run, so it can be used for some validation before running
	// acceptance tests, such as verifying that keys are setup.
	PreCheck func()

	// Provider is the ResourceProvider that will be under test.
	Providers         map[string]terraform.ResourceProvider
	ProviderFactories map[string]terraform.ResourceProviderFactory

	// CheckDestroy is called after the resource is finally destroyed
	// to allow the tester to test that the resource is truly gone.
	CheckDestroy TestCheckFunc

	// Steps are the apply sequences done within the context of the
	// same state. Each step can have its own check to verify correctness.
	Steps []TestStep
}

// TestStep is a single apply sequence of a test, done within the
// context of a state.
//
// Multiple TestSteps can be sequenced in a Test to allow testing
// potentially complex update logic. In general, simply create/destroy
// tests will only need one step.
type TestStep struct {
	// Config a string of the configuration to give to Terraform.
	Config string

	// Check is called after the Config is applied. Use this step to
	// make your own API calls to check the status of things, and to
	// inspect the format of the ResourceState itself.
	//
	// If an error is returned, the test will fail. In this case, a
	// destroy plan will still be attempted.
	//
	// If this is nil, no check is done on this step.
	Check TestCheckFunc

	// Destroy will create a destroy plan if set to true.
	Destroy bool
}

// Test performs an acceptance test on a resource.
//
// Tests are not run unless an environmental variable "TF_ACC" is
// set to some non-empty value. This is to avoid test cases surprising
// a user by creating real resources.
//
// Tests will fail unless the verbose flag (`go test -v`, or explicitly
// the "-test.v" flag) is set. Because some acceptance tests take quite
// long, we require the verbose flag so users are able to see progress
// output.
func Test(t TestT, c TestCase) {
	// We only run acceptance tests if an env var is set because they're
	// slow and generally require some outside configuration.
	if os.Getenv(TestEnvVar) == "" {
		t.Skip(fmt.Sprintf(
			"Acceptance tests skipped unless env '%s' set",
			TestEnvVar))
		return
	}

	// We require verbose mode so that the user knows what is going on.
	if !testTesting && !testing.Verbose() {
		t.Fatal("Acceptance tests must be run with the -v flag on tests")
		return
	}

	// Run the PreCheck if we have it
	if c.PreCheck != nil {
		c.PreCheck()
	}

	// Build our context options that we can
	ctxProviders := c.ProviderFactories
	if ctxProviders == nil {
		ctxProviders = make(map[string]terraform.ResourceProviderFactory)
		for k, p := range c.Providers {
			ctxProviders[k] = terraform.ResourceProviderFactoryFixed(p)
		}
	}
	opts := terraform.ContextOpts{Providers: ctxProviders}

	// A single state variable to track the lifecycle, starting with no state
	var state *terraform.State

	// Go through each step and run it
	for i, step := range c.Steps {
		var err error
		log.Printf("[WARN] Test: Executing step %d", i)
		state, err = testStep(opts, state, step)
		if err != nil {
			t.Error(fmt.Sprintf(
				"Step %d error: %s", i, err))
			break
		}
	}

	// If we have a state, then run the destroy
	if state != nil {
		destroyStep := TestStep{
			Config:  c.Steps[len(c.Steps)-1].Config,
			Check:   c.CheckDestroy,
			Destroy: true,
		}

		log.Printf("[WARN] Test: Executing destroy step")
		state, err := testStep(opts, state, destroyStep)
		if err != nil {
			t.Error(fmt.Sprintf(
				"Error destroying resource! WARNING: Dangling resources\n"+
					"may exist. The full state and error is shown below.\n\n"+
					"Error: %s\n\nState: %s",
				err,
				state))
		}
	} else {
		log.Printf("[WARN] Skipping destroy test since there is no state.")
	}
}

func testStep(
	opts terraform.ContextOpts,
	state *terraform.State,
	step TestStep) (*terraform.State, error) {
	cfgPath, err := ioutil.TempDir("", "tf-test")
	if err != nil {
		return state, fmt.Errorf(
			"Error creating temporary directory for config: %s", err)
	}
	defer os.RemoveAll(cfgPath)

	// Write the configuration
	cfgF, err := os.Create(filepath.Join(cfgPath, "main.tf"))
	if err != nil {
		return state, fmt.Errorf(
			"Error creating temporary file for config: %s", err)
	}

	_, err = io.Copy(cfgF, strings.NewReader(step.Config))
	cfgF.Close()
	if err != nil {
		return state, fmt.Errorf(
			"Error creating temporary file for config: %s", err)
	}

	// Parse the configuration
	mod, err := module.NewTreeModule("", cfgPath)
	if err != nil {
		return state, fmt.Errorf(
			"Error loading configuration: %s", err)
	}

	// Load the modules
	modStorage := &module.FolderStorage{
		StorageDir: filepath.Join(cfgPath, ".tfmodules"),
	}
	err = mod.Load(modStorage, module.GetModeGet)
	if err != nil {
		return state, fmt.Errorf("Error downloading modules: %s", err)
	}

	// Build the context
	opts.Module = mod
	opts.State = state
	opts.Destroy = step.Destroy
	ctx := terraform.NewContext(&opts)
	if ws, es := ctx.Validate(); len(ws) > 0 || len(es) > 0 {
		estrs := make([]string, len(es))
		for i, e := range es {
			estrs[i] = e.Error()
		}
		return state, fmt.Errorf(
			"Configuration is invalid.\n\nWarnings: %#v\n\nErrors: %#v",
			ws, estrs)
	}

	// Refresh!
	state, err = ctx.Refresh()
	if err != nil {
		return state, fmt.Errorf(
			"Error refreshing: %s", err)
	}

	// Plan!
	if p, err := ctx.Plan(); err != nil {
		return state, fmt.Errorf(
			"Error planning: %s", err)
	} else {
		log.Printf("[WARN] Test: Step plan: %s", p)
	}

	// Apply!
	state, err = ctx.Apply()
	if err != nil {
		return state, fmt.Errorf("Error applying: %s", err)
	}

	// Check! Excitement!
	if step.Check != nil {
		if err = step.Check(state); err != nil {
			err = fmt.Errorf("Check failed: %s", err)
		}
	}

	// Verify that Plan is now empty and we don't have a perpetual diff issue
	if p, err := ctx.Plan(); err != nil {
		return state, fmt.Errorf("Error on follow-up plan: %s", err)
	} else {
		if p.Diff != nil && !p.Diff.Empty() {
			return state, fmt.Errorf(
				"After applying this step, the plan was not empty:\n\n%s", p)
		}
	}

	return state, err
}

// ComposeTestCheckFunc lets you compose multiple TestCheckFuncs into
// a single TestCheckFunc.
//
// As a user testing their provider, this lets you decompose your checks
// into smaller pieces more easily.
func ComposeTestCheckFunc(fs ...TestCheckFunc) TestCheckFunc {
	return func(s *terraform.State) error {
		for _, f := range fs {
			if err := f(s); err != nil {
				return err
			}
		}

		return nil
	}
}

func TestCheckResourceAttr(name, key, value string) TestCheckFunc {
	return func(s *terraform.State) error {
		ms := s.RootModule()
		rs, ok := ms.Resources[name]
		if !ok {
			return fmt.Errorf("Not found: %s", name)
		}

		is := rs.Primary
		if is == nil {
			return fmt.Errorf("No primary instance: %s", name)
		}

		if is.Attributes[key] != value {
			return fmt.Errorf(
				"%s: Attribute '%s' expected %#v, got %#v",
				name,
				key,
				value,
				is.Attributes[key])
		}

		return nil
	}
}

// TestCheckResourceAttrPtr is like TestCheckResourceAttr except the
// value is a pointer so that it can be updated while the test is running.
// It will only be dereferenced at the point this step is run.
func TestCheckResourceAttrPtr(name string, key string, value *string) TestCheckFunc {
	return func(s *terraform.State) error {
		return TestCheckResourceAttr(name, key, *value)(s)
	}
}

// TestT is the interface used to handle the test lifecycle of a test.
//
// Users should just use a *testing.T object, which implements this.
type TestT interface {
	Error(args ...interface{})
	Fatal(args ...interface{})
	Skip(args ...interface{})
}

// This is set to true by unit tests to alter some behavior
var testTesting = false
<commit_msg>helper/resource: docs<commit_after>package resource

import (
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"os"
	"path/filepath"
	"strings"
	"testing"

	"github.com/hashicorp/terraform/config/module"
	"github.com/hashicorp/terraform/terraform"
)

const TestEnvVar = "TF_ACC"

// TestCheckFunc is the callback type used with acceptance tests to check
// the state of a resource. The state passed in is the latest state known,
// or in the case of being after a destroy, it is the last known state when
// it was created.
type TestCheckFunc func(*terraform.State) error

// TestCase is a single acceptance test case used to test the apply/destroy
// lifecycle of a resource in a specific configuration.
//
// When the destroy plan is executed, the config from the last TestStep
// is used to plan it.
type TestCase struct {
	// PreCheck, if non-nil, will be called before any test steps are
	// executed. It will only be executed in the case that the steps
	// would run, so it can be used for some validation before running
	// acceptance tests, such as verifying that keys are setup.
	PreCheck func()

	// Providers is the ResourceProvider that will be under test.
	//
	// Alternately, ProviderFactories can be specified for the providers
	// that are valid. This takes priority over Providers.
	//
	// The end effect of each is the same: specifying the providers that
	// are used within the tests.
	Providers         map[string]terraform.ResourceProvider
	ProviderFactories map[string]terraform.ResourceProviderFactory

	// CheckDestroy is called after the resource is finally destroyed
	// to allow the tester to test that the resource is truly gone.
	CheckDestroy TestCheckFunc

	// Steps are the apply sequences done within the context of the
	// same state. Each step can have its own check to verify correctness.
	Steps []TestStep
}

// TestStep is a single apply sequence of a test, done within the
// context of a state.
//
// Multiple TestSteps can be sequenced in a Test to allow testing
// potentially complex update logic. In general, simply create/destroy
// tests will only need one step.
type TestStep struct {
	// Config a string of the configuration to give to Terraform.
	Config string

	// Check is called after the Config is applied. Use this step to
	// make your own API calls to check the status of things, and to
	// inspect the format of the ResourceState itself.
	//
	// If an error is returned, the test will fail. In this case, a
	// destroy plan will still be attempted.
	//
	// If this is nil, no check is done on this step.
	Check TestCheckFunc

	// Destroy will create a destroy plan if set to true.
	Destroy bool
}

// Test performs an acceptance test on a resource.
//
// Tests are not run unless an environmental variable "TF_ACC" is
// set to some non-empty value. This is to avoid test cases surprising
// a user by creating real resources.
//
// Tests will fail unless the verbose flag (`go test -v`, or explicitly
// the "-test.v" flag) is set. Because some acceptance tests take quite
// long, we require the verbose flag so users are able to see progress
// output.
func Test(t TestT, c TestCase) {
	// We only run acceptance tests if an env var is set because they're
	// slow and generally require some outside configuration.
	if os.Getenv(TestEnvVar) == "" {
		t.Skip(fmt.Sprintf(
			"Acceptance tests skipped unless env '%s' set",
			TestEnvVar))
		return
	}

	// We require verbose mode so that the user knows what is going on.
	if !testTesting && !testing.Verbose() {
		t.Fatal("Acceptance tests must be run with the -v flag on tests")
		return
	}

	// Run the PreCheck if we have it
	if c.PreCheck != nil {
		c.PreCheck()
	}

	// Build our context options that we can
	ctxProviders := c.ProviderFactories
	if ctxProviders == nil {
		ctxProviders = make(map[string]terraform.ResourceProviderFactory)
		for k, p := range c.Providers {
			ctxProviders[k] = terraform.ResourceProviderFactoryFixed(p)
		}
	}
	opts := terraform.ContextOpts{Providers: ctxProviders}

	// A single state variable to track the lifecycle, starting with no state
	var state *terraform.State

	// Go through each step and run it
	for i, step := range c.Steps {
		var err error
		log.Printf("[WARN] Test: Executing step %d", i)
		state, err = testStep(opts, state, step)
		if err != nil {
			t.Error(fmt.Sprintf(
				"Step %d error: %s", i, err))
			break
		}
	}

	// If we have a state, then run the destroy
	if state != nil {
		destroyStep := TestStep{
			Config:  c.Steps[len(c.Steps)-1].Config,
			Check:   c.CheckDestroy,
			Destroy: true,
		}

		log.Printf("[WARN] Test: Executing destroy step")
		state, err := testStep(opts, state, destroyStep)
		if err != nil {
			t.Error(fmt.Sprintf(
				"Error destroying resource! WARNING: Dangling resources\n"+
					"may exist. The full state and error is shown below.\n\n"+
					"Error: %s\n\nState: %s",
				err,
				state))
		}
	} else {
		log.Printf("[WARN] Skipping destroy test since there is no state.")
	}
}

func testStep(
	opts terraform.ContextOpts,
	state *terraform.State,
	step TestStep) (*terraform.State, error) {
	cfgPath, err := ioutil.TempDir("", "tf-test")
	if err != nil {
		return state, fmt.Errorf(
			"Error creating temporary directory for config: %s", err)
	}
	defer os.RemoveAll(cfgPath)

	// Write the configuration
	cfgF, err := os.Create(filepath.Join(cfgPath, "main.tf"))
	if err != nil {
		return state, fmt.Errorf(
			"Error creating temporary file for config: %s", err)
	}

	_, err = io.Copy(cfgF, strings.NewReader(step.Config))
	cfgF.Close()
	if err != nil {
		return state, fmt.Errorf(
			"Error creating temporary file for config: %s", err)
	}

	// Parse the configuration
	mod, err := module.NewTreeModule("", cfgPath)
	if err != nil {
		return state, fmt.Errorf(
			"Error loading configuration: %s", err)
	}

	// Load the modules
	modStorage := &module.FolderStorage{
		StorageDir: filepath.Join(cfgPath, ".tfmodules"),
	}
	err = mod.Load(modStorage, module.GetModeGet)
	if err != nil {
		return state, fmt.Errorf("Error downloading modules: %s", err)
	}

	// Build the context
	opts.Module = mod
	opts.State = state
	opts.Destroy = step.Destroy
	ctx := terraform.NewContext(&opts)
	if ws, es := ctx.Validate(); len(ws) > 0 || len(es) > 0 {
		estrs := make([]string, len(es))
		for i, e := range es {
			estrs[i] = e.Error()
		}
		return state, fmt.Errorf(
			"Configuration is invalid.\n\nWarnings: %#v\n\nErrors: %#v",
			ws, estrs)
	}

	// Refresh!
	state, err = ctx.Refresh()
	if err != nil {
		return state, fmt.Errorf(
			"Error refreshing: %s", err)
	}

	// Plan!
	if p, err := ctx.Plan(); err != nil {
		return state, fmt.Errorf(
			"Error planning: %s", err)
	} else {
		log.Printf("[WARN] Test: Step plan: %s", p)
	}

	// Apply!
	state, err = ctx.Apply()
	if err != nil {
		return state, fmt.Errorf("Error applying: %s", err)
	}

	// Check! Excitement!
	if step.Check != nil {
		if err = step.Check(state); err != nil {
			err = fmt.Errorf("Check failed: %s", err)
		}
	}

	// Verify that Plan is now empty and we don't have a perpetual diff issue
	if p, err := ctx.Plan(); err != nil {
		return state, fmt.Errorf("Error on follow-up plan: %s", err)
	} else {
		if p.Diff != nil && !p.Diff.Empty() {
			return state, fmt.Errorf(
				"After applying this step, the plan was not empty:\n\n%s", p)
		}
	}

	return state, err
}

// ComposeTestCheckFunc lets you compose multiple TestCheckFuncs into
// a single TestCheckFunc.
//
// As a user testing their provider, this lets you decompose your checks
// into smaller pieces more easily.
func ComposeTestCheckFunc(fs ...TestCheckFunc) TestCheckFunc {
	return func(s *terraform.State) error {
		for _, f := range fs {
			if err := f(s); err != nil {
				return err
			}
		}

		return nil
	}
}

func TestCheckResourceAttr(name, key, value string) TestCheckFunc {
	return func(s *terraform.State) error {
		ms := s.RootModule()
		rs, ok := ms.Resources[name]
		if !ok {
			return fmt.Errorf("Not found: %s", name)
		}

		is := rs.Primary
		if is == nil {
			return fmt.Errorf("No primary instance: %s", name)
		}

		if is.Attributes[key] != value {
			return fmt.Errorf(
				"%s: Attribute '%s' expected %#v, got %#v",
				name,
				key,
				value,
				is.Attributes[key])
		}

		return nil
	}
}

// TestCheckResourceAttrPtr is like TestCheckResourceAttr except the
// value is a pointer so that it can be updated while the test is running.
// It will only be dereferenced at the point this step is run.
func TestCheckResourceAttrPtr(name string, key string, value *string) TestCheckFunc {
	return func(s *terraform.State) error {
		return TestCheckResourceAttr(name, key, *value)(s)
	}
}

// TestT is the interface used to handle the test lifecycle of a test.
//
// Users should just use a *testing.T object, which implements this.
type TestT interface {
	Error(args ...interface{})
	Fatal(args ...interface{})
	Skip(args ...interface{})
}

// This is set to true by unit tests to alter some behavior
var testTesting = false
<|endoftext|>


<commit_before>package server

import (
	"encoding/json"
	"github.com/buger/jsonparser"
	"github.com/imdario/mergo"
	"github.com/pkg/errors"
	"go.uber.org/zap"
)

// Merges data passed through in JSON form into the existing server object.
// Any changes to the build settings will apply immediately in the environment
// if the environment supports it.
//
// The server will be marked as requiring a rebuild on the next boot sequence,
// it is up to the specific environment to determine what needs to happen when
// that is the case.
func (s *Server) UpdateDataStructure(data []byte, background bool) error {
	src := new(Server)
	if err := json.Unmarshal(data, src); err != nil {
		return errors.WithStack(err)
	}

	// Don't allow obviously corrupted data to pass through into this function. If the UUID
	// doesn't match something has gone wrong and the API is attempting to meld this server
	// instance into a totally different one, which would be bad.
	if src.Uuid != "" && s.Uuid != "" && src.Uuid != s.Uuid {
		return errors.New("attempting to merge a data stack with an invalid UUID")
	}

	// Merge the new data object that we have received with the existing server data object
	// and then save it to the disk so it is persistent.
	if err := mergo.Merge(s, src, mergo.WithOverride); err != nil {
		return errors.WithStack(err)
	}

	// Mergo can't quite handle this boolean value correctly, so for now we'll just
	// handle this edge case manually since none of the other data passed through in this
	// request is going to be boolean. Allegedly.
	if v, err := jsonparser.GetBoolean(data, "container", "oom_disabled"); err != nil {
		if err != jsonparser.KeyPathNotFoundError {
			return errors.WithStack(err)
		}
	} else {
		s.Container.OomDisabled = v
	}

	// Mergo also cannot handle this boolean value.
	if v, err := jsonparser.GetBoolean(data, "suspended"); err != nil {
		if err != jsonparser.KeyPathNotFoundError {
			return errors.WithStack(err)
		}
	} else {
		s.Suspended = v
	}

	// Environment and Mappings should be treated as a full update at all times, never a
	// true patch, otherwise we can't know what we're passing along.
	if src.EnvVars != nil && len(src.EnvVars) > 0 {
		s.EnvVars = src.EnvVars
	}

	if src.Allocations.Mappings != nil && len(src.Allocations.Mappings) > 0 {
		s.Allocations.Mappings = src.Allocations.Mappings
	}

	if background {
		s.runBackgroundActions()
	}

	return nil
}

// Runs through different actions once a server's configuration has been persisted
// to the disk. This function does not return anything as any failures should be logged
// but have no effect on actually updating the server itself.
//
// These tasks run in independent threads where relevant to speed up any updates
// that need to happen.
func (s *Server) runBackgroundActions() {
	// Update the environment in place, allowing memory and CPU usage to be adjusted
	// on the fly without the user needing to reboot (theoretically).
	go func(server *Server) {
		if err := server.Environment.InSituUpdate(); err != nil {
			zap.S().Warnw(
				"failed to perform in-situ update of server environment",
				zap.String("server", server.Uuid),
				zap.Error(err),
			)
		}
	}(s)

	// Check if the server is now suspended, and if so and the process is not terminated
	// yet, do it immediately.
	go func(server *Server) {
		if server.Suspended && server.GetState() != ProcessOfflineState {
			zap.S().Infow("server suspended with running process state, terminating now", zap.String("server", server.Uuid))

			if err := server.Environment.WaitForStop(10, true); err != nil {
				zap.S().Warnw(
					"failed to stop server environment after seeing suspension",
					zap.String("server", server.Uuid),
					zap.Error(err),
				)
			}
		}
	}(s)
}
<commit_msg>Make mounts not get overridden when a server is updated<commit_after>package server

import (
	"encoding/json"
	"github.com/buger/jsonparser"
	"github.com/imdario/mergo"
	"github.com/pkg/errors"
	"go.uber.org/zap"
)

// Merges data passed through in JSON form into the existing server object.
// Any changes to the build settings will apply immediately in the environment
// if the environment supports it.
//
// The server will be marked as requiring a rebuild on the next boot sequence,
// it is up to the specific environment to determine what needs to happen when
// that is the case.
func (s *Server) UpdateDataStructure(data []byte, background bool) error {
	src := new(Server)
	if err := json.Unmarshal(data, src); err != nil {
		return errors.WithStack(err)
	}

	// Don't allow obviously corrupted data to pass through into this function. If the UUID
	// doesn't match something has gone wrong and the API is attempting to meld this server
	// instance into a totally different one, which would be bad.
	if src.Uuid != "" && s.Uuid != "" && src.Uuid != s.Uuid {
		return errors.New("attempting to merge a data stack with an invalid UUID")
	}

	// Merge the new data object that we have received with the existing server data object
	// and then save it to the disk so it is persistent.
	if err := mergo.Merge(s, src, mergo.WithOverride); err != nil {
		return errors.WithStack(err)
	}

	// Mergo can't quite handle this boolean value correctly, so for now we'll just
	// handle this edge case manually since none of the other data passed through in this
	// request is going to be boolean. Allegedly.
	if v, err := jsonparser.GetBoolean(data, "container", "oom_disabled"); err != nil {
		if err != jsonparser.KeyPathNotFoundError {
			return errors.WithStack(err)
		}
	} else {
		s.Container.OomDisabled = v
	}

	// Mergo also cannot handle this boolean value.
	if v, err := jsonparser.GetBoolean(data, "suspended"); err != nil {
		if err != jsonparser.KeyPathNotFoundError {
			return errors.WithStack(err)
		}
	} else {
		s.Suspended = v
	}

	// Environment and Mappings should be treated as a full update at all times, never a
	// true patch, otherwise we can't know what we're passing along.
	if src.EnvVars != nil && len(src.EnvVars) > 0 {
		s.EnvVars = src.EnvVars
	}

	if src.Allocations.Mappings != nil && len(src.Allocations.Mappings) > 0 {
		s.Allocations.Mappings = src.Allocations.Mappings
	}

	if src.Mounts != nil && len(src.Mounts) > 0 {
		s.Mounts = src.Mounts
	}

	if background {
		s.runBackgroundActions()
	}

	return nil
}

// Runs through different actions once a server's configuration has been persisted
// to the disk. This function does not return anything as any failures should be logged
// but have no effect on actually updating the server itself.
//
// These tasks run in independent threads where relevant to speed up any updates
// that need to happen.
func (s *Server) runBackgroundActions() {
	// Update the environment in place, allowing memory and CPU usage to be adjusted
	// on the fly without the user needing to reboot (theoretically).
	go func(server *Server) {
		if err := server.Environment.InSituUpdate(); err != nil {
			zap.S().Warnw(
				"failed to perform in-situ update of server environment",
				zap.String("server", server.Uuid),
				zap.Error(err),
			)
		}
	}(s)

	// Check if the server is now suspended, and if so and the process is not terminated
	// yet, do it immediately.
	go func(server *Server) {
		if server.Suspended && server.GetState() != ProcessOfflineState {
			zap.S().Infow("server suspended with running process state, terminating now", zap.String("server", server.Uuid))

			if err := server.Environment.WaitForStop(10, true); err != nil {
				zap.S().Warnw(
					"failed to stop server environment after seeing suspension",
					zap.String("server", server.Uuid),
					zap.Error(err),
				)
			}
		}
	}(s)
}
<|endoftext|>


<commit_before>package sudoku

import (
	"log"
	"testing"
)

func TestForcingChains(t *testing.T) {

	//Steps to test this:
	//* In the forcing chain helper, calculate the steps once, then
	//pass them in each time in a list of ~10 calls to solveTechniqueTEstHelper that we know are valid here.
	//* VERIFY MANUALLY that each step that is returned is actually a valid application of forcingchains.

	options := solveTechniqueTestHelperOptions{
		checkAllSteps: true,
	}

	grid, solver, steps := humanSolveTechniqueTestHelperStepGenerator(t,
		"forcingchain_test1.sdk", "Forcing Chain", options)

	options.stepsToCheck.grid = grid
	options.stepsToCheck.solver = solver
	options.stepsToCheck.steps = steps

	//OK, now we'll walk through all of the options in a loop and make sure they all show
	//up in the solve steps.

	type loopOptions struct {
		targetCells  []cellRef
		targetNums   IntSlice
		pointerCells []cellRef
		pointerNums  IntSlice
		description  string
	}

	//Tester puzzle: http://www.komoroske.com/sudoku/index.php?puzzle=Q6Ur5iYGINSUFcyocqaY6G91DpttiqYzs

	tests := []loopOptions{
		{
			targetCells:  []cellRef{{0, 1}},
			targetNums:   IntSlice([]int{7}),
			pointerCells: []cellRef{{1, 0}},
			pointerNums:  IntSlice([]int{1, 2}),
			description:  "cell (1,0) only has two options, 1 and 2, and if you put either one in and see the chain of implications it leads to, both ones end up with 7 in cell (0,1), so we can just fill that number in",
		},
		{
			targetCells:  []cellRef{{1, 0}},
			targetNums:   IntSlice([]int{1}),
			pointerCells: []cellRef{{0, 6}},
			pointerNums:  IntSlice([]int{3, 7}),
			//Explicitly don't test description after the first one.
		},
		{
			targetCells:  []cellRef{{5, 1}},
			targetNums:   IntSlice([]int{1}),
			pointerCells: []cellRef{{0, 6}},
			pointerNums:  IntSlice([]int{3, 7}),
		},
		{
			targetCells:  []cellRef{{8, 3}},
			targetNums:   IntSlice([]int{7}),
			pointerCells: []cellRef{{7, 8}},
			pointerNums:  IntSlice([]int{2, 7}),
		},
		//Skipping 0,1 / 7 / 5,7 / 1,3 because I think implications force it wrong.
		//The reason is because there is an inconsistency down that branch... just one implication
		//step beyond when it finds a match. Hmmmm... interesting test case.
		//Of course, the number being filled _IS_ right... is that a coincidence? I wondder
		//if other ones in this set that I'm considering valid would ahve the same problem...
		//... I think they would, right? By definition one of hte branches leads to invalidity. This
		//technique is about finding a universal before you find that invalidity.
		{
			targetCells:  []cellRef{{0, 1}},
			targetNums:   IntSlice([]int{7}),
			pointerCells: []cellRef{{5, 7}},
			pointerNums:  IntSlice([]int{1, 3}),
		},
		{
			targetCells:  []cellRef{{8, 3}},
			targetNums:   IntSlice([]int{7}),
			pointerCells: []cellRef{{5, 7}},
			pointerNums:  IntSlice([]int{1, 3}),
		},
		{
			targetCells:  []cellRef{{1, 8}},
			targetNums:   IntSlice([]int{4}),
			pointerCells: []cellRef{{4, 0}},
			pointerNums:  IntSlice([]int{1, 2}),
		},
		//This next one's particularly long implication chain
		{
			targetCells:  []cellRef{{0, 1}},
			targetNums:   IntSlice([]int{7}),
			pointerCells: []cellRef{{4, 0}},
			pointerNums:  IntSlice([]int{1, 2}),
		},
		//Another particularly long one
		{
			targetCells:  []cellRef{{1, 0}},
			targetNums:   IntSlice([]int{1}),
			pointerCells: []cellRef{{0, 1}},
			pointerNums:  IntSlice([]int{2, 7}),
		},
		{
			targetCells:  []cellRef{{5, 1}},
			targetNums:   IntSlice([]int{1}),
			pointerCells: []cellRef{{0, 1}},
			pointerNums:  IntSlice([]int{2, 7}),
		},

		//All of these are missing... what?
		//Oh, we fail as soon as we notice they don't all match.
		// 0,6 /3 / 1,0 / 1,2
		// 0,1 / 7 / 5,1 / 1,2
		// 0,6 / 3 / 5,1 / 1,2
		//8,3 / 7 / 8,7 / 1,2
		// 0,1 /7 / 5,4 / 2,3
		// 8,3 / 7 / 5,4 / 2,3

		{
			targetCells:  []cellRef{{0, 1}},
			targetNums:   IntSlice([]int{7}),
			pointerCells: []cellRef{{1, 0}},
			pointerNums:  IntSlice([]int{1, 2}),
		},
		//Another particularly long one
		{
			targetCells:  []cellRef{{5, 1}},
			targetNums:   IntSlice([]int{1}),
			pointerCells: []cellRef{{0, 6}},
			pointerNums:  IntSlice([]int{3, 7}),
		},
		//Another particularly long one
		{
			targetCells:  []cellRef{{1, 0}},
			targetNums:   IntSlice([]int{1}),
			pointerCells: []cellRef{{0, 6}},
			pointerNums:  IntSlice([]int{3, 7}),
		},
		//Another particularly long one
		{
			targetCells:  []cellRef{{5, 1}},
			targetNums:   IntSlice([]int{1}),
			pointerCells: []cellRef{{0, 6}},
			pointerNums:  IntSlice([]int{3, 7}),
		},
		//Another particularly long one
		{
			targetCells:  []cellRef{{8, 3}},
			targetNums:   IntSlice([]int{7}),
			pointerCells: []cellRef{{7, 8}},
			pointerNums:  IntSlice([]int{2, 7}),
		},
		{
			targetCells:  []cellRef{{1, 8}},
			targetNums:   IntSlice([]int{4}),
			pointerCells: []cellRef{{1, 0}},
			pointerNums:  IntSlice([]int{1, 2}),
		},
	}

	for _, step := range steps {
		log.Println(step)
	}

	for _, test := range tests {

		options.targetCells = test.targetCells
		options.targetNums = test.targetNums
		options.pointerCells = test.pointerCells
		options.pointerNums = test.pointerNums
		options.description = test.description

		humanSolveTechniqueTestHelper(t, "forcingchain_test1.sdk", "Forcing Chain", options)
	}

	if len(tests) != len(steps) {
		t.Error("We didn't have enough tests for all of the steps that forcing chains returned. Got", len(tests), "expected", len(steps))
	}

	//TODO: test all other valid steps that could be found at this grid state for this technique.

}
<commit_msg>Took notes on the next TODO<commit_after>package sudoku

import (
	"log"
	"testing"
)

func TestForcingChains(t *testing.T) {

	//Steps to test this:
	//* In the forcing chain helper, calculate the steps once, then
	//pass them in each time in a list of ~10 calls to solveTechniqueTEstHelper that we know are valid here.
	//* VERIFY MANUALLY that each step that is returned is actually a valid application of forcingchains.

	options := solveTechniqueTestHelperOptions{
		checkAllSteps: true,
	}

	grid, solver, steps := humanSolveTechniqueTestHelperStepGenerator(t,
		"forcingchain_test1.sdk", "Forcing Chain", options)

	options.stepsToCheck.grid = grid
	options.stepsToCheck.solver = solver
	options.stepsToCheck.steps = steps

	//OK, now we'll walk through all of the options in a loop and make sure they all show
	//up in the solve steps.

	type loopOptions struct {
		targetCells  []cellRef
		targetNums   IntSlice
		pointerCells []cellRef
		pointerNums  IntSlice
		description  string
	}

	//Tester puzzle: http://www.komoroske.com/sudoku/index.php?puzzle=Q6Ur5iYGINSUFcyocqaY6G91DpttiqYzs

	tests := []loopOptions{
		{
			targetCells:  []cellRef{{0, 1}},
			targetNums:   IntSlice([]int{7}),
			pointerCells: []cellRef{{1, 0}},
			pointerNums:  IntSlice([]int{1, 2}),
			description:  "cell (1,0) only has two options, 1 and 2, and if you put either one in and see the chain of implications it leads to, both ones end up with 7 in cell (0,1), so we can just fill that number in",
		},
		{
			targetCells:  []cellRef{{1, 0}},
			targetNums:   IntSlice([]int{1}),
			pointerCells: []cellRef{{0, 6}},
			pointerNums:  IntSlice([]int{3, 7}),
			//Explicitly don't test description after the first one.
		},
		{
			targetCells:  []cellRef{{5, 1}},
			targetNums:   IntSlice([]int{1}),
			pointerCells: []cellRef{{0, 6}},
			pointerNums:  IntSlice([]int{3, 7}),
		},
		{
			targetCells:  []cellRef{{8, 3}},
			targetNums:   IntSlice([]int{7}),
			pointerCells: []cellRef{{7, 8}},
			pointerNums:  IntSlice([]int{2, 7}),
		},
		//TODO: update this comment
		//Skipping 0,1 / 7 / 5,7 / 1,3 because I think implications force it wrong.
		//The reason is because there is an inconsistency down that branch... just one implication
		//step beyond when it finds a match. Hmmmm... interesting test case.
		//Of course, the number being filled _IS_ right... is that a coincidence? I wondder
		//if other ones in this set that I'm considering valid would ahve the same problem...
		//... I think they would, right? By definition one of hte branches leads to invalidity. This
		//technique is about finding a universal before you find that invalidity.
		{
			targetCells:  []cellRef{{0, 1}},
			targetNums:   IntSlice([]int{7}),
			pointerCells: []cellRef{{5, 7}},
			pointerNums:  IntSlice([]int{1, 3}),
		},
		{
			targetCells:  []cellRef{{8, 3}},
			targetNums:   IntSlice([]int{7}),
			pointerCells: []cellRef{{5, 7}},
			pointerNums:  IntSlice([]int{1, 3}),
		},
		{
			targetCells:  []cellRef{{1, 8}},
			targetNums:   IntSlice([]int{4}),
			pointerCells: []cellRef{{4, 0}},
			pointerNums:  IntSlice([]int{1, 2}),
		},
		//This next one's particularly long implication chain
		{
			targetCells:  []cellRef{{0, 1}},
			targetNums:   IntSlice([]int{7}),
			pointerCells: []cellRef{{4, 0}},
			pointerNums:  IntSlice([]int{1, 2}),
		},
		//Another particularly long one
		{
			targetCells:  []cellRef{{1, 0}},
			targetNums:   IntSlice([]int{1}),
			pointerCells: []cellRef{{0, 1}},
			pointerNums:  IntSlice([]int{2, 7}),
		},
		{
			targetCells:  []cellRef{{5, 1}},
			targetNums:   IntSlice([]int{1}),
			pointerCells: []cellRef{{0, 1}},
			pointerNums:  IntSlice([]int{2, 7}),
		},

		//All of these are missing... what?
		//Oh, we fail as soon as we notice they don't all match.
		//We haven't seen this set again... flakey?
		//Next step: do the manual check for a 'normal' run to see which is missing
		// 0,6 /3 / 1,0 / 1,2
		// 0,1 / 7 / 5,1 / 1,2
		// 0,6 / 3 / 5,1 / 1,2
		//8,3 / 7 / 8,7 / 1,2
		// 0,1 /7 / 5,4 / 2,3
		// 8,3 / 7 / 5,4 / 2,3

		{
			targetCells:  []cellRef{{0, 1}},
			targetNums:   IntSlice([]int{7}),
			pointerCells: []cellRef{{1, 0}},
			pointerNums:  IntSlice([]int{1, 2}),
		},
		//Another particularly long one
		{
			targetCells:  []cellRef{{5, 1}},
			targetNums:   IntSlice([]int{1}),
			pointerCells: []cellRef{{0, 6}},
			pointerNums:  IntSlice([]int{3, 7}),
		},
		//Another particularly long one
		{
			targetCells:  []cellRef{{1, 0}},
			targetNums:   IntSlice([]int{1}),
			pointerCells: []cellRef{{0, 6}},
			pointerNums:  IntSlice([]int{3, 7}),
		},
		//Another particularly long one
		{
			targetCells:  []cellRef{{5, 1}},
			targetNums:   IntSlice([]int{1}),
			pointerCells: []cellRef{{0, 6}},
			pointerNums:  IntSlice([]int{3, 7}),
		},
		//Another particularly long one
		{
			targetCells:  []cellRef{{8, 3}},
			targetNums:   IntSlice([]int{7}),
			pointerCells: []cellRef{{7, 8}},
			pointerNums:  IntSlice([]int{2, 7}),
		},
		{
			targetCells:  []cellRef{{1, 8}},
			targetNums:   IntSlice([]int{4}),
			pointerCells: []cellRef{{1, 0}},
			pointerNums:  IntSlice([]int{1, 2}),
		},
	}

	for _, step := range steps {
		log.Println(step)
	}

	for _, test := range tests {

		options.targetCells = test.targetCells
		options.targetNums = test.targetNums
		options.pointerCells = test.pointerCells
		options.pointerNums = test.pointerNums
		options.description = test.description

		humanSolveTechniqueTestHelper(t, "forcingchain_test1.sdk", "Forcing Chain", options)
	}

	if len(tests) != len(steps) {
		t.Error("We didn't have enough tests for all of the steps that forcing chains returned. Got", len(tests), "expected", len(steps))
	}

	//TODO: test all other valid steps that could be found at this grid state for this technique.

}
<|endoftext|>


<commit_before>package sudoku

import (
	"log"
	"testing"
)

func TestForcingChains(t *testing.T) {

	//Steps to test this:
	//* In the forcing chain helper, calculate the steps once, then
	//pass them in each time in a list of ~10 calls to solveTechniqueTEstHelper that we know are valid here.
	//* VERIFY MANUALLY that each step that is returned is actually a valid application of forcingchains.

	options := solveTechniqueTestHelperOptions{
		checkAllSteps: true,
	}

	grid, solver, steps := humanSolveTechniqueTestHelperStepGenerator(t,
		"forcingchain_test1.sdk", "Forcing Chain", options)

	options.stepsToCheck.grid = grid
	options.stepsToCheck.solver = solver
	options.stepsToCheck.steps = steps

	//OK, now we'll walk through all of the options in a loop and make sure they all show
	//up in the solve steps.

	type loopOptions struct {
		targetCells  []cellRef
		targetNums   IntSlice
		pointerCells []cellRef
		pointerNums  IntSlice
		description  string
	}

	//Tester puzzle: http://www.komoroske.com/sudoku/index.php?puzzle=Q6Ur5iYGINSUFcyocqaY6G91DpttiqYzs

	tests := []loopOptions{
		{
			targetCells:  []cellRef{{0, 1}},
			targetNums:   IntSlice([]int{7}),
			pointerCells: []cellRef{{1, 0}},
			pointerNums:  IntSlice([]int{1, 2}),
			description:  "cell (1,0) only has two options, 1 and 2, and if you put either one in and see the chain of implications it leads to, both ones end up with 7 in cell (0,1), so we can just fill that number in",
		},
		{
			targetCells:  []cellRef{{1, 0}},
			targetNums:   IntSlice([]int{1}),
			pointerCells: []cellRef{{0, 6}},
			pointerNums:  IntSlice([]int{3, 7}),
			//Explicitly don't test description after the first one.
		},
		{
			targetCells:  []cellRef{{5, 1}},
			targetNums:   IntSlice([]int{1}),
			pointerCells: []cellRef{{0, 6}},
			pointerNums:  IntSlice([]int{3, 7}),
		},
		{
			targetCells:  []cellRef{{8, 3}},
			targetNums:   IntSlice([]int{7}),
			pointerCells: []cellRef{{7, 8}},
			pointerNums:  IntSlice([]int{2, 7}),
		},
		//TODO: update this comment
		//Skipping 0,1 / 7 / 5,7 / 1,3 because I think implications force it wrong.
		//The reason is because there is an inconsistency down that branch... just one implication
		//step beyond when it finds a match. Hmmmm... interesting test case.
		//Of course, the number being filled _IS_ right... is that a coincidence? I wondder
		//if other ones in this set that I'm considering valid would ahve the same problem...
		//... I think they would, right? By definition one of hte branches leads to invalidity. This
		//technique is about finding a universal before you find that invalidity.
		{
			targetCells:  []cellRef{{0, 1}},
			targetNums:   IntSlice([]int{7}),
			pointerCells: []cellRef{{5, 7}},
			pointerNums:  IntSlice([]int{1, 3}),
		},
		{
			targetCells:  []cellRef{{8, 3}},
			targetNums:   IntSlice([]int{7}),
			pointerCells: []cellRef{{5, 7}},
			pointerNums:  IntSlice([]int{1, 3}),
		},
		{
			targetCells:  []cellRef{{1, 8}},
			targetNums:   IntSlice([]int{4}),
			pointerCells: []cellRef{{4, 0}},
			pointerNums:  IntSlice([]int{1, 2}),
		},
		//This next one's particularly long implication chain
		{
			targetCells:  []cellRef{{0, 1}},
			targetNums:   IntSlice([]int{7}),
			pointerCells: []cellRef{{4, 0}},
			pointerNums:  IntSlice([]int{1, 2}),
		},
		//Another particularly long one
		{
			targetCells:  []cellRef{{1, 0}},
			targetNums:   IntSlice([]int{1}),
			pointerCells: []cellRef{{0, 1}},
			pointerNums:  IntSlice([]int{2, 7}),
		},
		{
			targetCells:  []cellRef{{5, 1}},
			targetNums:   IntSlice([]int{1}),
			pointerCells: []cellRef{{0, 1}},
			pointerNums:  IntSlice([]int{2, 7}),
		},

		//All of these are missing... what?
		//Oh, we fail as soon as we notice they don't all match.
		//We haven't seen this set again... flakey?
		//Next step: do the manual check for a 'normal' run to see which is missing

		//8,3 / 7 / 5,7 / 1,3

		//Next step: look at the one's that AREN't in tests that are matching, see if they're valid.
		//Also, it's very suspicious that there are always 17 steps returned, and some random subset
		//of what I presume are all valid options.

		{
			targetCells:  []cellRef{{0, 1}},
			targetNums:   IntSlice([]int{7}),
			pointerCells: []cellRef{{1, 0}},
			pointerNums:  IntSlice([]int{1, 2}),
		},
		//Another particularly long one
		{
			targetCells:  []cellRef{{5, 1}},
			targetNums:   IntSlice([]int{1}),
			pointerCells: []cellRef{{0, 6}},
			pointerNums:  IntSlice([]int{3, 7}),
		},
		//Another particularly long one
		{
			targetCells:  []cellRef{{1, 0}},
			targetNums:   IntSlice([]int{1}),
			pointerCells: []cellRef{{0, 6}},
			pointerNums:  IntSlice([]int{3, 7}),
		},
		//Another particularly long one
		{
			targetCells:  []cellRef{{5, 1}},
			targetNums:   IntSlice([]int{1}),
			pointerCells: []cellRef{{0, 6}},
			pointerNums:  IntSlice([]int{3, 7}),
		},
		//Another particularly long one
		{
			targetCells:  []cellRef{{8, 3}},
			targetNums:   IntSlice([]int{7}),
			pointerCells: []cellRef{{7, 8}},
			pointerNums:  IntSlice([]int{2, 7}),
		},
		{
			targetCells:  []cellRef{{1, 8}},
			targetNums:   IntSlice([]int{4}),
			pointerCells: []cellRef{{1, 0}},
			pointerNums:  IntSlice([]int{1, 2}),
		},
		{
			targetCells:  []cellRef{{0, 6}},
			targetNums:   IntSlice([]int{3}),
			pointerCells: []cellRef{{1, 0}},
			pointerNums:  IntSlice([]int{1, 2}),
		},
		{
			targetCells:  []cellRef{{0, 1}},
			targetNums:   IntSlice([]int{7}),
			pointerCells: []cellRef{{5, 1}},
			pointerNums:  IntSlice([]int{1, 2}),
		},
		{
			targetCells:  []cellRef{{0, 6}},
			targetNums:   IntSlice([]int{3}),
			pointerCells: []cellRef{{5, 1}},
			pointerNums:  IntSlice([]int{1, 2}),
		},
		{
			targetCells:  []cellRef{{8, 3}},
			targetNums:   IntSlice([]int{7}),
			pointerCells: []cellRef{{8, 7}},
			pointerNums:  IntSlice([]int{1, 2}),
		},
		{
			targetCells:  []cellRef{{0, 1}},
			targetNums:   IntSlice([]int{7}),
			pointerCells: []cellRef{{5, 4}},
			pointerNums:  IntSlice([]int{2, 3}),
		},
		{
			targetCells:  []cellRef{{8, 3}},
			targetNums:   IntSlice([]int{7}),
			pointerCells: []cellRef{{5, 4}},
			pointerNums:  IntSlice([]int{2, 3}),
		},
	}

	for _, step := range steps {
		log.Println(step)
	}

	for _, test := range tests {

		options.targetCells = test.targetCells
		options.targetNums = test.targetNums
		options.pointerCells = test.pointerCells
		options.pointerNums = test.pointerNums
		options.description = test.description

		humanSolveTechniqueTestHelper(t, "forcingchain_test1.sdk", "Forcing Chain", options)
	}

	if len(tests) != len(steps) {
		t.Error("We didn't have enough tests for all of the steps that forcing chains returned. Got", len(tests), "expected", len(steps))
	}

	//TODO: test all other valid steps that could be found at this grid state for this technique.

}
<commit_msg>TESTS FAIL. yet another test, this is the last one I've see that I don't yet have captured.<commit_after>package sudoku

import (
	"log"
	"testing"
)

func TestForcingChains(t *testing.T) {

	//Steps to test this:
	//* In the forcing chain helper, calculate the steps once, then
	//pass them in each time in a list of ~10 calls to solveTechniqueTEstHelper that we know are valid here.
	//* VERIFY MANUALLY that each step that is returned is actually a valid application of forcingchains.

	options := solveTechniqueTestHelperOptions{
		checkAllSteps: true,
	}

	grid, solver, steps := humanSolveTechniqueTestHelperStepGenerator(t,
		"forcingchain_test1.sdk", "Forcing Chain", options)

	options.stepsToCheck.grid = grid
	options.stepsToCheck.solver = solver
	options.stepsToCheck.steps = steps

	//OK, now we'll walk through all of the options in a loop and make sure they all show
	//up in the solve steps.

	type loopOptions struct {
		targetCells  []cellRef
		targetNums   IntSlice
		pointerCells []cellRef
		pointerNums  IntSlice
		description  string
	}

	//Tester puzzle: http://www.komoroske.com/sudoku/index.php?puzzle=Q6Ur5iYGINSUFcyocqaY6G91DpttiqYzs

	tests := []loopOptions{
		{
			targetCells:  []cellRef{{0, 1}},
			targetNums:   IntSlice([]int{7}),
			pointerCells: []cellRef{{1, 0}},
			pointerNums:  IntSlice([]int{1, 2}),
			description:  "cell (1,0) only has two options, 1 and 2, and if you put either one in and see the chain of implications it leads to, both ones end up with 7 in cell (0,1), so we can just fill that number in",
		},
		{
			targetCells:  []cellRef{{1, 0}},
			targetNums:   IntSlice([]int{1}),
			pointerCells: []cellRef{{0, 6}},
			pointerNums:  IntSlice([]int{3, 7}),
			//Explicitly don't test description after the first one.
		},
		{
			targetCells:  []cellRef{{5, 1}},
			targetNums:   IntSlice([]int{1}),
			pointerCells: []cellRef{{0, 6}},
			pointerNums:  IntSlice([]int{3, 7}),
		},
		{
			targetCells:  []cellRef{{8, 3}},
			targetNums:   IntSlice([]int{7}),
			pointerCells: []cellRef{{7, 8}},
			pointerNums:  IntSlice([]int{2, 7}),
		},
		//TODO: update this comment
		//Skipping 0,1 / 7 / 5,7 / 1,3 because I think implications force it wrong.
		//The reason is because there is an inconsistency down that branch... just one implication
		//step beyond when it finds a match. Hmmmm... interesting test case.
		//Of course, the number being filled _IS_ right... is that a coincidence? I wondder
		//if other ones in this set that I'm considering valid would ahve the same problem...
		//... I think they would, right? By definition one of hte branches leads to invalidity. This
		//technique is about finding a universal before you find that invalidity.
		{
			targetCells:  []cellRef{{0, 1}},
			targetNums:   IntSlice([]int{7}),
			pointerCells: []cellRef{{5, 7}},
			pointerNums:  IntSlice([]int{1, 3}),
		},
		{
			targetCells:  []cellRef{{8, 3}},
			targetNums:   IntSlice([]int{7}),
			pointerCells: []cellRef{{5, 7}},
			pointerNums:  IntSlice([]int{1, 3}),
		},
		{
			targetCells:  []cellRef{{1, 8}},
			targetNums:   IntSlice([]int{4}),
			pointerCells: []cellRef{{4, 0}},
			pointerNums:  IntSlice([]int{1, 2}),
		},
		//This next one's particularly long implication chain
		{
			targetCells:  []cellRef{{0, 1}},
			targetNums:   IntSlice([]int{7}),
			pointerCells: []cellRef{{4, 0}},
			pointerNums:  IntSlice([]int{1, 2}),
		},
		//Another particularly long one
		{
			targetCells:  []cellRef{{1, 0}},
			targetNums:   IntSlice([]int{1}),
			pointerCells: []cellRef{{0, 1}},
			pointerNums:  IntSlice([]int{2, 7}),
		},
		{
			targetCells:  []cellRef{{5, 1}},
			targetNums:   IntSlice([]int{1}),
			pointerCells: []cellRef{{0, 1}},
			pointerNums:  IntSlice([]int{2, 7}),
		},

		//All of these are missing... what?
		//Oh, we fail as soon as we notice they don't all match.
		//We haven't seen this set again... flakey?
		//Next step: do the manual check for a 'normal' run to see which is missing

		//Next step: look at the one's that AREN't in tests that are matching, see if they're valid.
		//Also, it's very suspicious that there are always 17 steps returned, and some random subset
		//of what I presume are all valid options.

		{
			targetCells:  []cellRef{{0, 1}},
			targetNums:   IntSlice([]int{7}),
			pointerCells: []cellRef{{1, 0}},
			pointerNums:  IntSlice([]int{1, 2}),
		},
		//Another particularly long one
		{
			targetCells:  []cellRef{{5, 1}},
			targetNums:   IntSlice([]int{1}),
			pointerCells: []cellRef{{0, 6}},
			pointerNums:  IntSlice([]int{3, 7}),
		},
		//Another particularly long one
		{
			targetCells:  []cellRef{{1, 0}},
			targetNums:   IntSlice([]int{1}),
			pointerCells: []cellRef{{0, 6}},
			pointerNums:  IntSlice([]int{3, 7}),
		},
		//Another particularly long one
		{
			targetCells:  []cellRef{{5, 1}},
			targetNums:   IntSlice([]int{1}),
			pointerCells: []cellRef{{0, 6}},
			pointerNums:  IntSlice([]int{3, 7}),
		},
		//Another particularly long one
		{
			targetCells:  []cellRef{{8, 3}},
			targetNums:   IntSlice([]int{7}),
			pointerCells: []cellRef{{7, 8}},
			pointerNums:  IntSlice([]int{2, 7}),
		},
		{
			targetCells:  []cellRef{{1, 8}},
			targetNums:   IntSlice([]int{4}),
			pointerCells: []cellRef{{1, 0}},
			pointerNums:  IntSlice([]int{1, 2}),
		},
		{
			targetCells:  []cellRef{{0, 6}},
			targetNums:   IntSlice([]int{3}),
			pointerCells: []cellRef{{1, 0}},
			pointerNums:  IntSlice([]int{1, 2}),
		},
		{
			targetCells:  []cellRef{{0, 1}},
			targetNums:   IntSlice([]int{7}),
			pointerCells: []cellRef{{5, 1}},
			pointerNums:  IntSlice([]int{1, 2}),
		},
		{
			targetCells:  []cellRef{{0, 6}},
			targetNums:   IntSlice([]int{3}),
			pointerCells: []cellRef{{5, 1}},
			pointerNums:  IntSlice([]int{1, 2}),
		},
		{
			targetCells:  []cellRef{{8, 3}},
			targetNums:   IntSlice([]int{7}),
			pointerCells: []cellRef{{8, 7}},
			pointerNums:  IntSlice([]int{1, 2}),
		},
		{
			targetCells:  []cellRef{{0, 1}},
			targetNums:   IntSlice([]int{7}),
			pointerCells: []cellRef{{5, 4}},
			pointerNums:  IntSlice([]int{2, 3}),
		},
		{
			targetCells:  []cellRef{{8, 3}},
			targetNums:   IntSlice([]int{7}),
			pointerCells: []cellRef{{5, 4}},
			pointerNums:  IntSlice([]int{2, 3}),
		},
		{
			targetCells:  []cellRef{{8, 3}},
			targetNums:   IntSlice([]int{7}),
			pointerCells: []cellRef{{5, 7}},
			pointerNums:  IntSlice([]int{1, 3}),
		},
	}

	for _, step := range steps {
		log.Println(step)
	}

	for _, test := range tests {

		options.targetCells = test.targetCells
		options.targetNums = test.targetNums
		options.pointerCells = test.pointerCells
		options.pointerNums = test.pointerNums
		options.description = test.description

		humanSolveTechniqueTestHelper(t, "forcingchain_test1.sdk", "Forcing Chain", options)
	}

	if len(tests) != len(steps) {
		t.Error("We didn't have enough tests for all of the steps that forcing chains returned. Got", len(tests), "expected", len(steps))
	}

	//TODO: test all other valid steps that could be found at this grid state for this technique.

}
<|endoftext|>