<commit_before>// Copyright 2012, The gohg Authors. All rights reserved.
// Use of this source code is governed by a BSD style license
// that can be found in the LICENSE.md file.

package gohg

import (
	// "fmt"
	"os"
	"os/exec"
	// "path/filepath"
	"strings"
	"testing"
)

func TestHgClient_Branches(t *testing.T) {
	hct := setup(t)
	defer teardown(t, hct)

	// dropped the revision after the colon for more independent testing
	var expected string = "newbranch                      1:\n" +
		"default                        0:\n"

	f, err := os.Create(hct.RepoRoot() + "/a")
	_, _ = f.Write([]byte{'a', 'a', 'a'})
	f.Sync()
	f.Close()

	cmd := exec.Command(hct.HgExe(), "-R", hct.RepoRoot(), "ci", "-Am\"test\"")
	if err := cmd.Run(); err != nil {
		t.Fatal(err)
	}
	cmd = exec.Command(hct.HgExe(), "-R", hct.RepoRoot(), "branch", "newbranch")
	if err := cmd.Run(); err != nil {
		t.Fatal(err)
	}
	f, err = os.Create(hct.RepoRoot() + "/b")
	_, _ = f.Write([]byte{'b', 'b', 'b'})
	f.Sync()
	f.Close()
	cmd = exec.Command(hct.HgExe(), "-R", hct.RepoRoot(), "ci", "-Am\"test2\"")
	if err := cmd.Run(); err != nil {
		t.Fatal(err)
	}

	got1, err := hct.Branches(nil, nil)
	if err != nil {
		t.Error(err)
	}
	got := extractBranchInfo(got1)
	if string(got) != expected {
		t.Fatalf("Test Branches: expected:\n%s\n but got:\n%s\n", expected, got)
	}

	// test Active option

	expected = "newbranch                      1:\n"
	got1, err = hct.Branches([]HgOption{Active(true)}, nil)
	if err != nil {
		t.Error(err)
	}
	got = extractBranchInfo(got1)
	if string(got) != expected {
		t.Fatalf("Test Branches Active: expected:\n%s\n but got:\n%s\n", expected, got)
	}

	// This test was disabled because of a problem on drone.io.
	// drone.io uses Mercurial v2.0.2.
	// To be investigated.
	// test Closed option

	cmd = exec.Command(hct.HgExe(), "-R", hct.RepoRoot(), "update", "default")
	if err := cmd.Run(); err != nil {
		t.Fatal(err)
	}
	cmd = exec.Command(hct.HgExe(), "-R", hct.RepoRoot(), "ci", "--close-branch",
		"-m\"closed branch newbranch\"")
	if err := cmd.Run(); err != nil {
		t.Fatal(err)
	}
	expected = "newbranch                      1:\n" +
		"default                        2:\n"
	got1, err = hct.Branches([]HgOption{Closed(true)}, nil)
	if err != nil {
		t.Error(err)
	}
	got = extractBranchInfo(got1)
	if string(got) != expected {
		t.Fatalf("Test Branches Closed: expected:\n%s\n but got:\n%s\n", expected, got)
	}

	// // test Mq option

	// // for some reason this method produces returnvalue 255, at least in Linux
	// // fmt.Printf("reporoot: %s\n", hct.RepoRoot())
	// // // cmd = exec.Command(hct.HgExe(), "init --cwd "+hct.RepoRoot()+" --mq")
	// // cmd = exec.Command(hct.HgExe(), "init", "--mq")
	// // if err := cmd.Run(); err != nil {
	// // 	t.Fatal(err)
	// // }

	// // this method does not create files .hgignore and series however,
	// // at least on Linux
	// // // err = hct.Init(Mq(true), Cwd(hct.RepoRoot()))
	// // path, err := filepath.Abs(hct.RepoRoot() + "/.hg/patches")
	// // if err != nil {
	// // 	t.Error(err)
	// // }
	// // err = hct.Init(Destpath(path))
	// err = hct.Init(Mq(true))
	// if err != nil {
	// 	t.Error(err)
	// }
	// // return

	// // and this one then fails on Win 7
	// // cmd = exec.Command(hct.HgExe(), "-R", hct.RepoRoot(), "--mq", "branch", "newmqbranch")
	// cmd = exec.Command(hct.HgExe(), "branch", "newmqbranch", "--mq")
	// if err := cmd.Run(); err != nil {
	// 	t.Fatal(err)
	// }
	// // commit files .hgignore and series
	// cmd = exec.Command(hct.HgExe(), "-R", hct.RepoRoot(), "ci", "--mq", "-Am\"testmq\"")
	// if err := cmd.Run(); err != nil {
	// 	t.Fatal(err)
	// }
	// expected = "newmqbranch                    1:\n" +
	// 	"default                        0:\n"
	// got1, err = hct.Branches(Mq(true))
	// if err != nil {
	// 	t.Error(err)
	// }
	// got = extractBranchInfo(got1)
	// if string(got) != expected {
	// 	t.Fatalf("Test Branches Mq: expected:\n%s\n but got:\n%s\n", expected, got)
	// }
}

func extractBranchInfo(branches []byte) string {
	got := ""
	got2 := strings.Split(string(branches), "\n")
	for _, b := range got2 {
		if len(b) == 0 {
			continue
		}
		got = got + strings.SplitN(string(b), ":", 2)[0] + ":\n"
	}
	return got
}
<commit_msg>brances_test.go: updated comment about test going wrong on drone.io<commit_after>// Copyright 2012, The gohg Authors. All rights reserved.
// Use of this source code is governed by a BSD style license
// that can be found in the LICENSE.md file.

package gohg

import (
	// "fmt"
	"os"
	"os/exec"
	// "path/filepath"
	"strings"
	"testing"
)

func TestHgClient_Branches(t *testing.T) {
	hct := setup(t)
	defer teardown(t, hct)

	// dropped the revision after the colon for more independent testing
	var expected string = "newbranch                      1:\n" +
		"default                        0:\n"

	f, err := os.Create(hct.RepoRoot() + "/a")
	_, _ = f.Write([]byte{'a', 'a', 'a'})
	f.Sync()
	f.Close()

	cmd := exec.Command(hct.HgExe(), "-R", hct.RepoRoot(), "ci", "-Am\"test\"")
	if err := cmd.Run(); err != nil {
		t.Fatal(err)
	}
	cmd = exec.Command(hct.HgExe(), "-R", hct.RepoRoot(), "branch", "newbranch")
	if err := cmd.Run(); err != nil {
		t.Fatal(err)
	}
	f, err = os.Create(hct.RepoRoot() + "/b")
	_, _ = f.Write([]byte{'b', 'b', 'b'})
	f.Sync()
	f.Close()
	cmd = exec.Command(hct.HgExe(), "-R", hct.RepoRoot(), "ci", "-Am\"test2\"")
	if err := cmd.Run(); err != nil {
		t.Fatal(err)
	}

	got1, err := hct.Branches(nil, nil)
	if err != nil {
		t.Error(err)
	}
	got := extractBranchInfo(got1)
	if string(got) != expected {
		t.Fatalf("Test Branches: expected:\n%s\n but got:\n%s\n", expected, got)
	}

	// test Active option

	expected = "newbranch                      1:\n"
	got1, err = hct.Branches([]HgOption{Active(true)}, nil)
	if err != nil {
		t.Error(err)
	}
	got = extractBranchInfo(got1)
	if string(got) != expected {
		t.Fatalf("Test Branches Active: expected:\n%s\n but got:\n%s\n", expected, got)
	}

	// This test causes problems on drone.io.
	// Running builds on app.wercker.com works correctly, even with this test
	// enabled, and also when running all examples.
	// test Closed option

	cmd = exec.Command(hct.HgExe(), "-R", hct.RepoRoot(), "update", "default")
	if err := cmd.Run(); err != nil {
		t.Fatal(err)
	}
	cmd = exec.Command(hct.HgExe(), "-R", hct.RepoRoot(), "ci", "--close-branch",
		"-m\"closed branch newbranch\"")
	if err := cmd.Run(); err != nil {
		t.Fatal(err)
	}
	expected = "newbranch                      1:\n" +
		"default                        2:\n"
	got1, err = hct.Branches([]HgOption{Closed(true)}, nil)
	if err != nil {
		t.Error(err)
	}
	got = extractBranchInfo(got1)
	if string(got) != expected {
		t.Fatalf("Test Branches Closed: expected:\n%s\n but got:\n%s\n", expected, got)
	}

	// // test Mq option

	// // for some reason this method produces returnvalue 255, at least in Linux
	// // fmt.Printf("reporoot: %s\n", hct.RepoRoot())
	// // // cmd = exec.Command(hct.HgExe(), "init --cwd "+hct.RepoRoot()+" --mq")
	// // cmd = exec.Command(hct.HgExe(), "init", "--mq")
	// // if err := cmd.Run(); err != nil {
	// // 	t.Fatal(err)
	// // }

	// // this method does not create files .hgignore and series however,
	// // at least on Linux
	// // // err = hct.Init(Mq(true), Cwd(hct.RepoRoot()))
	// // path, err := filepath.Abs(hct.RepoRoot() + "/.hg/patches")
	// // if err != nil {
	// // 	t.Error(err)
	// // }
	// // err = hct.Init(Destpath(path))
	// err = hct.Init(Mq(true))
	// if err != nil {
	// 	t.Error(err)
	// }
	// // return

	// // and this one then fails on Win 7
	// // cmd = exec.Command(hct.HgExe(), "-R", hct.RepoRoot(), "--mq", "branch", "newmqbranch")
	// cmd = exec.Command(hct.HgExe(), "branch", "newmqbranch", "--mq")
	// if err := cmd.Run(); err != nil {
	// 	t.Fatal(err)
	// }
	// // commit files .hgignore and series
	// cmd = exec.Command(hct.HgExe(), "-R", hct.RepoRoot(), "ci", "--mq", "-Am\"testmq\"")
	// if err := cmd.Run(); err != nil {
	// 	t.Fatal(err)
	// }
	// expected = "newmqbranch                    1:\n" +
	// 	"default                        0:\n"
	// got1, err = hct.Branches(Mq(true))
	// if err != nil {
	// 	t.Error(err)
	// }
	// got = extractBranchInfo(got1)
	// if string(got) != expected {
	// 	t.Fatalf("Test Branches Mq: expected:\n%s\n but got:\n%s\n", expected, got)
	// }
}

func extractBranchInfo(branches []byte) string {
	got := ""
	got2 := strings.Split(string(branches), "\n")
	for _, b := range got2 {
		if len(b) == 0 {
			continue
		}
		got = got + strings.SplitN(string(b), ":", 2)[0] + ":\n"
	}
	return got
}
<|endoftext|>


<commit_before>/*
Copyright 2014 Google Inc. All rights reserved.
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.
*/

// evolution of: https://github.com/GoogleCloudPlatform/kubernetes/blob/release-0.6/pkg/client/cache/fifo.go
package queue

import (
	"fmt"
	"reflect"
	"sync"

	"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
	"github.com/GoogleCloudPlatform/kubernetes/pkg/util/config"
)

type EventType int

const (
	ADD_EVENT EventType = iota
	UPDATE_EVENT
	DELETE_EVENT
	POP_EVENT
)

type Entry struct {
	Value UniqueCopyable
	Event EventType
}

type Copyable interface {
	// return an independent copy (deep clone) of the current object
	Copy() Copyable
}

type UniqueID interface {
	GetUID() string
}

type UniqueCopyable interface {
	Copyable
	UniqueID
}

func (e *Entry) Copy() Copyable {
	return &Entry{Value: e.Value.Copy().(UniqueCopyable), Event: e.Event}
}

// deliver a message
type pigeon func(msg *Entry)

func dead(msg *Entry) {
	// intentionally blank
}

// HistoricalFIFO receives adds and updates from a Reflector, and puts them in a queue for
// FIFO order processing. If multiple adds/updates of a single item happen while
// an item is in the queue before it has been processed, it will only be
// processed once, and when it is processed, the most recent version will be
// processed. This can't be done with a channel.
type HistoricalFIFO struct {
	lock    sync.RWMutex
	cond    sync.Cond
	items   map[string]*Entry // We depend on the property that items in the queue are in the set.
	queue   []string
	carrier pigeon // may be dead, but never nil
}

// panics if obj doesn't implement UniqueCopyable; otherwise returns the same, typecast object
func checkType(obj interface{}) UniqueCopyable {
	if v, ok := obj.(UniqueCopyable); !ok {
		panic(fmt.Sprintf("Illegal object type, expected UniqueCopyable: %T", obj))
	} else {
		return v
	}
}

// Add inserts an item, and puts it in the queue. The item is only enqueued
// if it doesn't already exist in the set.
func (f *HistoricalFIFO) Add(id string, v interface{}) {
	obj := checkType(v)
	notifications := []*Entry(nil)
	defer func() {
		for _, e := range notifications {
			f.carrier(e)
		}
	}()

	f.lock.Lock()
	defer f.lock.Unlock()

	if entry, exists := f.items[id]; !exists {
		f.queue = append(f.queue, id)
	} else {
		if entry.Event == DELETE_EVENT || entry.Event == POP_EVENT {
			f.queue = append(f.queue, id)
		}
	}
	notifications = f.merge(id, obj)
	f.cond.Broadcast()
}

// Update is the same as Add in this implementation.
func (f *HistoricalFIFO) Update(id string, obj interface{}) {
	f.Add(id, obj)
}

// Add the item to the store, but only if there exists a prior entry for
// for the object in the store whose event type matches that given, and then
// only enqueued if it doesn't already exist in the set.
func (f *HistoricalFIFO) Readd(id string, v interface{}, t EventType) {
	obj := checkType(v)
	notifications := []*Entry(nil)
	defer func() {
		for _, e := range notifications {
			f.carrier(e)
		}
	}()

	f.lock.Lock()
	defer f.lock.Unlock()

	if entry, exists := f.items[id]; exists {
		if entry.Event != t {
			return
		} else if entry.Event == DELETE_EVENT || entry.Event == POP_EVENT {
			f.queue = append(f.queue, id)
		}
	}
	notifications = f.merge(id, obj)
	f.cond.Broadcast()
}

// Delete removes an item. It doesn't add it to the queue, because
// this implementation assumes the consumer only cares about the objects,
// not the order in which they were created/added.
func (f *HistoricalFIFO) Delete(id string) {
	deleteEvent := (*Entry)(nil)
	defer func() {
		f.carrier(deleteEvent)
	}()

	f.lock.Lock()
	defer f.lock.Unlock()
	entry, exists := f.items[id]
	if exists {
		//TODO(jdef): set a timer to expunge the history for this object
		//or else, simply do garbage collection every n'th merge(), removing
		//expired DELETE entries
		deleteEvent = &Entry{Value: entry.Value, Event: DELETE_EVENT}
		f.items[id] = deleteEvent
	}
}

// List returns a list of all the items.
func (f *HistoricalFIFO) List() []interface{} {
	f.lock.RLock()
	defer f.lock.RUnlock()

	// TODO(jdef): slightly overallocates b/c of deleted items
	list := make([]interface{}, 0, len(f.queue))

	for _, entry := range f.items {
		if entry.Event == DELETE_EVENT || entry.Event == POP_EVENT {
			continue
		}
		list = append(list, entry.Value.Copy())
	}
	return list
}

// ContainedIDs returns a util.StringSet containing all IDs of the stored items.
// This is a snapshot of a moment in time, and one should keep in mind that
// other go routines can add or remove items after you call this.
func (c *HistoricalFIFO) ContainedIDs() util.StringSet {
	c.lock.RLock()
	defer c.lock.RUnlock()
	set := util.StringSet{}
	for id, entry := range c.items {
		if entry.Event == DELETE_EVENT || entry.Event == POP_EVENT {
			continue
		}
		set.Insert(id)
	}
	return set
}

// Get returns the requested item, or sets exists=false.
func (f *HistoricalFIFO) Get(id string) (interface{}, bool) {
	f.lock.RLock()
	defer f.lock.RUnlock()
	entry, exists := f.items[id]
	if exists && !(entry.Event == DELETE_EVENT || entry.Event == POP_EVENT) {
		return entry.Value.Copy(), true
	}
	return nil, false
}

// Pop waits until an item is ready and returns it. If multiple items are
// ready, they are returned in the order in which they were added/updated.
// The item is removed from the queue (and the store) before it is returned,
// so if you don't succesfully process it, you need to add it back with Add().
func (f *HistoricalFIFO) Pop() interface{} {
	popEvent := (*Entry)(nil)
	defer func() {
		f.carrier(popEvent)
	}()

	f.lock.Lock()
	defer f.lock.Unlock()
	for {
		for len(f.queue) == 0 {
			f.cond.Wait()
		}
		id := f.queue[0]
		f.queue = f.queue[1:]
		entry, ok := f.items[id]
		if !ok || entry.Event == DELETE_EVENT || entry.Event == POP_EVENT {
			// Item may have been deleted subsequently.
			continue
		}
		value := entry.Value
		popEvent = &Entry{Value: value, Event: POP_EVENT}
		f.items[id] = popEvent
		return value.Copy()
	}
}

// Replace will delete the contents of 'f', using instead the given map.
// 'f' takes ownersip of the map, you should not reference the map again
// after calling this function. f's queue is reset, too; upon return, it
// will contain the items in the map, in no particular order.
func (f *HistoricalFIFO) Replace(idToObj map[string]interface{}) {
	notifications := make([]*Entry, 0, len(idToObj))
	defer func() {
		for _, e := range notifications {
			f.carrier(e)
		}
	}()

	f.lock.Lock()
	defer f.lock.Unlock()

	f.queue = f.queue[:0]
	for id, v := range f.items {
		if _, exists := idToObj[id]; !exists && v.Event != DELETE_EVENT {
			// a non-deleted entry in the items list that doesn't show up in the
			// new list: mark it as deleted
			e := &Entry{Value: v.Value, Event: DELETE_EVENT}
			f.items[id] = e
			notifications = append(notifications, e)
		}
	}
	for id, v := range idToObj {
		obj := checkType(v)
		f.queue = append(f.queue, id)
		n := f.merge(id, obj)
		notifications = append(notifications, n...)
	}
	if len(f.queue) > 0 {
		f.cond.Broadcast()
	}
}

// expects that caller has already locked around state
func (f *HistoricalFIFO) merge(id string, obj UniqueCopyable) (notifications []*Entry) {
	entry, exists := f.items[id]
	if !exists {
		e := &Entry{Value: obj.Copy().(UniqueCopyable), Event: ADD_EVENT}
		f.items[id] = e
		notifications = append(notifications, e)
	} else {
		if entry.Event != DELETE_EVENT && entry.Value.GetUID() != obj.GetUID() {
			// hidden DELETE!
			// (1) append a DELETE
			// (2) append an ADD
			// .. and notify listeners in that order
			e1 := &Entry{Value: entry.Value, Event: DELETE_EVENT}
			e2 := &Entry{Value: obj.Copy().(UniqueCopyable), Event: ADD_EVENT}
			f.items[id] = e2
			notifications = append(notifications, e1, e2)
		} else if !reflect.DeepEqual(obj, entry.Value) {
			//TODO(jdef): it would be nice if we could rely on resource versions
			//instead of doing a DeepEqual. Maybe someday we'll be able to.
			e := &Entry{Value: obj.Copy().(UniqueCopyable), Event: UPDATE_EVENT}
			f.items[id] = e
			notifications = append(notifications, e)
		}
	}
	return
}

// NewFIFO returns a Store which can be used to queue up items to
// process. If a non-nil Mux is provided, then modifications to the
// the FIFO are delivered on a channel specific to this fifo.
func NewFIFO(mux *config.Mux) *HistoricalFIFO {
	carrier := dead
	if mux != nil {
		//TODO(jdef): append a UUID to "fifo" here?
		ch := mux.Channel("fifo")
		carrier = func(msg *Entry) {
			if msg != nil {
				ch <- msg.Copy()
			}
		}
	}
	f := &HistoricalFIFO{
		items:   map[string]*Entry{},
		queue:   []string{},
		carrier: carrier,
	}
	f.cond.L = &f.lock
	return f
}
<commit_msg>refactor Entry into an interface; simply the storage map; initial implementation of garbage collection for deleted entries<commit_after>/*
Copyright 2014 Google Inc. All rights reserved.
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.
*/

// evolution of: https://github.com/GoogleCloudPlatform/kubernetes/blob/release-0.6/pkg/client/cache/fifo.go
package queue

import (
	"fmt"
	"reflect"
	"sync"
	"time"

	"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
)

type EventType int

const (
	ADD_EVENT EventType = 1 << iota
	UPDATE_EVENT
	DELETE_EVENT
	POP_EVENT
)

type Entry interface {
	Copyable
	Value() UniqueCopyable
	// types is a logically OR'd combination of EventType, e.g. ADD_EVENT|UPDATE_EVENT
	Is(types EventType) bool
}

type entry struct {
	value UniqueCopyable
	event EventType
}

type deletedEntry struct {
	*entry
	expiration time.Time
}

type Copyable interface {
	// return an independent copy (deep clone) of the current object
	Copy() Copyable
}

type UniqueID interface {
	GetUID() string
}

type UniqueCopyable interface {
	Copyable
	UniqueID
}

func (e *entry) Value() UniqueCopyable {
	return e.value
}

func (e *entry) Copy() Copyable {
	if e == nil {
		return nil
	}
	return &entry{e.value.Copy().(UniqueCopyable), e.event}
}

func (e *entry) Is(types EventType) bool {
	return types&e.event != 0
}

func (e *deletedEntry) Copy() Copyable {
	if e == nil {
		return nil
	}
	return &deletedEntry{e.entry.Copy().(*entry), e.expiration}
}

// deliver a message
type pigeon func(msg Entry)

func dead(msg Entry) {
	// intentionally blank
}

// HistoricalFIFO receives adds and updates from a Reflector, and puts them in a queue for
// FIFO order processing. If multiple adds/updates of a single item happen while
// an item is in the queue before it has been processed, it will only be
// processed once, and when it is processed, the most recent version will be
// processed. This can't be done with a channel.
type HistoricalFIFO struct {
	lock      sync.RWMutex
	cond      sync.Cond
	items     map[string]Entry // We depend on the property that items in the queue are in the set.
	queue     []string
	carrier   pigeon // may be dead, but never nil
	gcc       int
	lingerTTL time.Duration
}

// panics if obj doesn't implement UniqueCopyable; otherwise returns the same, typecast object
func checkType(obj interface{}) UniqueCopyable {
	if v, ok := obj.(UniqueCopyable); !ok {
		panic(fmt.Sprintf("Illegal object type, expected UniqueCopyable: %T", obj))
	} else {
		return v
	}
}

// Add inserts an item, and puts it in the queue. The item is only enqueued
// if it doesn't already exist in the set.
func (f *HistoricalFIFO) Add(id string, v interface{}) {
	obj := checkType(v)
	notifications := []Entry(nil)
	defer func() {
		for _, e := range notifications {
			f.carrier(e)
		}
	}()

	f.lock.Lock()
	defer f.lock.Unlock()

	if entry, exists := f.items[id]; !exists {
		f.queue = append(f.queue, id)
	} else {
		if entry.Is(DELETE_EVENT | POP_EVENT) {
			f.queue = append(f.queue, id)
		}
	}
	notifications = f.merge(id, obj)
	f.cond.Broadcast()
}

// Update is the same as Add in this implementation.
func (f *HistoricalFIFO) Update(id string, obj interface{}) {
	f.Add(id, obj)
}

// Add the item to the store, but only if there exists a prior entry for
// for the object in the store whose event type matches that given, and then
// only enqueued if it doesn't already exist in the set.
func (f *HistoricalFIFO) Readd(id string, v interface{}, t EventType) {
	obj := checkType(v)
	notifications := []Entry(nil)
	defer func() {
		for _, e := range notifications {
			f.carrier(e)
		}
	}()

	f.lock.Lock()
	defer f.lock.Unlock()

	if entry, exists := f.items[id]; exists {
		if !entry.Is(t) {
			return
		} else if entry.Is(DELETE_EVENT | POP_EVENT) {
			f.queue = append(f.queue, id)
		}
	}
	notifications = f.merge(id, obj)
	f.cond.Broadcast()
}

// Delete removes an item. It doesn't add it to the queue, because
// this implementation assumes the consumer only cares about the objects,
// not the order in which they were created/added.
func (f *HistoricalFIFO) Delete(id string) {
	deleteEvent := (Entry)(nil)
	defer func() {
		f.carrier(deleteEvent)
	}()

	f.lock.Lock()
	defer f.lock.Unlock()
	item, exists := f.items[id]
	if exists && !item.Is(DELETE_EVENT) {
		e := item.(*entry)
		e.event = DELETE_EVENT
		deleteEvent = &deletedEntry{e, time.Now().Add(f.lingerTTL)}
		f.items[id] = deleteEvent
	}
}

// List returns a list of all the items.
func (f *HistoricalFIFO) List() []interface{} {
	f.lock.RLock()
	defer f.lock.RUnlock()

	// TODO(jdef): slightly overallocates b/c of deleted items
	list := make([]interface{}, 0, len(f.queue))

	for _, entry := range f.items {
		if entry.Is(DELETE_EVENT | POP_EVENT) {
			continue
		}
		list = append(list, entry.Value().Copy())
	}
	return list
}

// ContainedIDs returns a util.StringSet containing all IDs of the stored items.
// This is a snapshot of a moment in time, and one should keep in mind that
// other go routines can add or remove items after you call this.
func (c *HistoricalFIFO) ContainedIDs() util.StringSet {
	c.lock.RLock()
	defer c.lock.RUnlock()
	set := util.StringSet{}
	for id, entry := range c.items {
		if entry.Is(DELETE_EVENT | POP_EVENT) {
			continue
		}
		set.Insert(id)
	}
	return set
}

// Get returns the requested item, or sets exists=false.
func (f *HistoricalFIFO) Get(id string) (interface{}, bool) {
	f.lock.RLock()
	defer f.lock.RUnlock()
	entry, exists := f.items[id]
	if exists && !entry.Is(DELETE_EVENT|POP_EVENT) {
		return entry.Value().Copy(), true
	}
	return nil, false
}

// Pop waits until an item is ready and returns it. If multiple items are
// ready, they are returned in the order in which they were added/updated.
// The item is removed from the queue (and the store) before it is returned,
// so if you don't succesfully process it, you need to add it back with Add().
func (f *HistoricalFIFO) Pop() interface{} {
	popEvent := (Entry)(nil)
	defer func() {
		f.carrier(popEvent)
	}()

	f.lock.Lock()
	defer f.lock.Unlock()
	for {
		for len(f.queue) == 0 {
			f.cond.Wait()
		}
		id := f.queue[0]
		f.queue = f.queue[1:]
		item, ok := f.items[id]
		if !ok || item.Is(DELETE_EVENT|POP_EVENT) {
			// Item may have been deleted subsequently.
			continue
		}
		value := item.Value()
		popEvent = &entry{value, POP_EVENT}
		f.items[id] = popEvent
		return value.Copy()
	}
}

// Replace will delete the contents of 'f', using instead the given map.
// 'f' takes ownersip of the map, you should not reference the map again
// after calling this function. f's queue is reset, too; upon return, it
// will contain the items in the map, in no particular order.
func (f *HistoricalFIFO) Replace(idToObj map[string]interface{}) {
	notifications := make([]Entry, 0, len(idToObj))
	defer func() {
		for _, e := range notifications {
			f.carrier(e)
		}
	}()

	f.lock.Lock()
	defer f.lock.Unlock()

	f.queue = f.queue[:0]
	now := time.Now()
	for id, v := range f.items {
		if _, exists := idToObj[id]; !exists && !v.Is(DELETE_EVENT) {
			// a non-deleted entry in the items list that doesn't show up in the
			// new list: mark it as deleted
			ent := v.(*entry)
			ent.event = DELETE_EVENT
			e := &deletedEntry{ent, now.Add(f.lingerTTL)}
			f.items[id] = e
			notifications = append(notifications, e)
		}
	}
	for id, v := range idToObj {
		obj := checkType(v)
		f.queue = append(f.queue, id)
		n := f.merge(id, obj)
		notifications = append(notifications, n...)
	}
	if len(f.queue) > 0 {
		f.cond.Broadcast()
	}
}

// garbage collect DELETEd items whose TTL has expired; the IDs of such items are removed
// from the queue. This impl assumes that caller has acquired state lock.
func (f *HistoricalFIFO) gc() {
	now := time.Now()
	deleted := make(map[string]struct{})
	for id, v := range f.items {
		if v.Is(DELETE_EVENT) {
			ent := v.(*deletedEntry)
			if ent.expiration.Before(now) {
				delete(f.items, id)
				deleted[id] = struct{}{}
			}
		}
	}
	// remove deleted items from the queue, will likely (slightly) overallocate here
	queue := make([]string, 0, len(f.queue))
	for _, id := range f.queue {
		if _, exists := deleted[id]; !exists {
			queue = append(queue, id)
		}
	}
	f.queue = queue
}

// Assumes that the caller has acquired the state lock.
func (f *HistoricalFIFO) merge(id string, obj UniqueCopyable) (notifications []Entry) {
	item, exists := f.items[id]
	now := time.Now()
	if !exists {
		e := &entry{obj.Copy().(UniqueCopyable), ADD_EVENT}
		f.items[id] = e
		notifications = append(notifications, e)
	} else {
		if !item.Is(DELETE_EVENT) && item.Value().GetUID() != obj.GetUID() {
			// hidden DELETE!
			// (1) append a DELETE
			// (2) append an ADD
			// .. and notify listeners in that order
			ent := item.(*entry)
			ent.event = DELETE_EVENT
			e1 := &deletedEntry{ent, now.Add(f.lingerTTL)}
			e2 := &entry{obj.Copy().(UniqueCopyable), ADD_EVENT}
			f.items[id] = e2
			notifications = append(notifications, e1, e2)
		} else if !reflect.DeepEqual(obj, item.Value()) {
			//TODO(jdef): it would be nice if we could rely on resource versions
			//instead of doing a DeepEqual. Maybe someday we'll be able to.
			e := &entry{obj.Copy().(UniqueCopyable), UPDATE_EVENT}
			f.items[id] = e
			notifications = append(notifications, e)
		}
	}
	// check for garbage collection
	f.gcc++
	if f.gcc%256 == 0 { //TODO(jdef): extract constant
		f.gcc = 0
		f.gc()
	}
	return
}

// NewFIFO returns a Store which can be used to queue up items to
// process. If a non-nil Mux is provided, then modifications to the
// the FIFO are delivered on a channel specific to this fifo.
func NewFIFO(ch chan<- Entry) *HistoricalFIFO {
	carrier := dead
	if ch != nil {
		carrier = func(msg Entry) {
			if msg != nil {
				ch <- msg.Copy().(Entry)
			}
		}
	}
	f := &HistoricalFIFO{
		items:     map[string]Entry{},
		queue:     []string{},
		carrier:   carrier,
		lingerTTL: 5 * time.Minute, // TODO(jdef): extract constant
	}
	f.cond.L = &f.lock
	return f
}
<|endoftext|>


<commit_before>package bridge

import (
	"sync"

	"github.com/x-cray/marathon-service-registrator/consul"
	"github.com/x-cray/marathon-service-registrator/marathon"
	"github.com/x-cray/marathon-service-registrator/types"

	log "github.com/Sirupsen/logrus"
)

type Bridge struct {
	sync.Mutex

	scheduler             types.SchedulerAdapter
	schedulerServices     map[string]*types.Service
	registry              types.RegistryAdapter
	registryAdvertizeAddr string
	config                *types.Config
}

func New(c *types.Config) (*Bridge, error) {
	marathon, err := marathon.New(c.Marathon)
	if err != nil {
		return nil, err
	}

	consul, err := consul.New(c.Consul, c.DryRun)
	if err != nil {
		return nil, err
	}

	return &Bridge{
		config:    c,
		scheduler: marathon,
		registry:  consul,
	}, nil
}

func (b *Bridge) getCachedService(serviceID, actionText string) *types.Service {
	if service, ok := b.schedulerServices[serviceID]; ok {
		return service
	}

	log.WithField("prefix", "bridge").Warningf("Service %s was not found in scheduler cache (has %d entries). Could not %s.", serviceID, len(b.schedulerServices), actionText)
	return nil
}

func logSkipMessage(ip string) {
	log.WithFields(log.Fields{
		"prefix": "bridge",
		"ip":     ip,
	}).Debug("Skipping event due to unrelated service host")
}

func (b *Bridge) processServiceEvent(event *types.ServiceEvent) error {
	b.Lock()
	defer b.Unlock()

	switch event.Action {
	case types.ServiceStarted:
		// New service is started, we need to refresh service cache.
		_, err := b.refreshSchedulerServices()
		if err != nil {
			return err
		}
	case types.ServiceStopped:
		// Service stopped, deregister and remove it from cache.
		// Only consider services registered on current registry's advertized address.
		if event.IP == b.registryAdvertizeAddr {
			if service := b.getCachedService(event.ServiceID, "deregister"); service != nil {
				b.registry.Deregister(service)
				delete(b.schedulerServices, event.ServiceID)
			}
		} else {
			logSkipMessage(event.IP)
		}
	case types.ServiceWentUp:
		// Service went up, register it.
		if service := b.getCachedService(event.ServiceID, "register"); service != nil {
			// Only consider services registered on current registry's advertized address.
			if service.IP == b.registryAdvertizeAddr {
				b.registry.Register(service)
			} else {
				logSkipMessage(service.IP)
			}
		}
	case types.ServiceWentDown:
		// Service went down, deregister it.
		if service := b.getCachedService(event.ServiceID, "deregister"); service != nil {
			// Only consider services registered on current registry's advertized address.
			if service.IP == b.registryAdvertizeAddr {
				b.registry.Deregister(service)
			} else {
				logSkipMessage(service.IP)
			}
		}
	}

	return nil
}

func (b *Bridge) ProcessSchedulerEvents() error {
	marathonEvents := make(types.EventsChannel, 5)
	err := b.scheduler.ListenForEvents(marathonEvents)
	if err != nil {
		return err
	}

	log.WithField("prefix", "bridge").Info("Registered for scheduler event stream")
	for {
		event := <-marathonEvents
		if event.Action != types.ServiceUnchanged {
			log.WithFields(log.Fields{
				"prefix":  "bridge",
				"service": event.ServiceID,
				"action":  event.Action,
				"event":   event.OriginalEvent,
			}).Debug("Received scheduler event")
			b.processServiceEvent(event)
		}
	}

	return nil
}

// Sync performs full synchronization of Marathon tasks to service registry.
func (b *Bridge) Sync() error {
	b.Lock()
	defer b.Unlock()

	actionsPerformed := false

	// Get services from registry.
	registryServices, err := b.registry.Services()
	if err != nil {
		return err
	}

	log.WithField("prefix", "bridge").Infof("Received %d services from registry", len(registryServices))

	// Build ip:port-indexed service map.
	registryServicesMap := make(map[string]*types.Service)
	for _, service := range registryServices {
		registryServicesMap[service.MapKey()] = service
	}

	schedulerServicesMap, err := b.refreshSchedulerServices()
	if err != nil {
		return err
	}

	// Register scheduler services absent in registry.
	for _, schedulerService := range schedulerServicesMap {
		// If service is not yet registered we need to register it.
		// Only consider services registered on current registry's advertized address.
		if schedulerService.IP == b.registryAdvertizeAddr && registryServicesMap[schedulerService.MapKey()] == nil {
			err := b.registry.Register(schedulerService)
			if err != nil {
				return err
			}
			actionsPerformed = true
		}
	}

	// Deregister dangling services (existing in registry and absent in scheduler).
	for _, registryService := range registryServicesMap {
		// If service is registered and we don't have it in scheduler we need to deregister it.
		if schedulerServicesMap[registryService.MapKey()] == nil {
			err := b.registry.Deregister(registryService)
			if err != nil {
				return err
			}
			actionsPerformed = true
		}
	}

	if !actionsPerformed {
		log.WithField("prefix", "bridge").Info("All services are in sync, no actions performed")
	}

	return nil
}

func (b *Bridge) refreshSchedulerServices() (map[string]*types.Service, error) {
	log.WithField("prefix", "bridge").Info("Refreshing scheduler services")

	// Get registry's advertize address.
	addr, err := b.registry.AdvertiseAddr()
	if err != nil {
		return nil, err
	}

	b.registryAdvertizeAddr = addr

	// Get services from scheduler.
	schedulerServicesArray, err := b.scheduler.Services()
	if err != nil {
		return nil, err
	}

	log.WithField("prefix", "bridge").Infof("Registry advertize address is %s", b.registryAdvertizeAddr)

	// Build 2 maps of services:
	// ServiceID-indexed and ip:port-indexed
	ipPortServices := make(map[string]*types.Service)
	b.schedulerServices = make(map[string]*types.Service)
	for _, service := range schedulerServicesArray {
		ipPortServices[service.MapKey()] = service
		b.schedulerServices[service.ID] = service
	}

	log.WithField("prefix", "bridge").Infof(
		"Received %d services from scheduler, %d are running on registry advertized address",
		len(schedulerServicesArray),
		len(ipPortServices),
	)

	return ipPortServices, nil
}
<commit_msg>Keep terminology consistent<commit_after>package bridge

import (
	"sync"

	"github.com/x-cray/marathon-service-registrator/consul"
	"github.com/x-cray/marathon-service-registrator/marathon"
	"github.com/x-cray/marathon-service-registrator/types"

	log "github.com/Sirupsen/logrus"
)

type Bridge struct {
	sync.Mutex

	scheduler             types.SchedulerAdapter
	schedulerServices     map[string]*types.Service
	registry              types.RegistryAdapter
	registryAdvertizeAddr string
	config                *types.Config
}

func New(c *types.Config) (*Bridge, error) {
	marathon, err := marathon.New(c.Marathon)
	if err != nil {
		return nil, err
	}

	consul, err := consul.New(c.Consul, c.DryRun)
	if err != nil {
		return nil, err
	}

	return &Bridge{
		config:    c,
		scheduler: marathon,
		registry:  consul,
	}, nil
}

func (b *Bridge) getCachedService(serviceID, actionText string) *types.Service {
	if service, ok := b.schedulerServices[serviceID]; ok {
		return service
	}

	log.WithField("prefix", "bridge").Warningf("Service %s was not found in scheduler cache (has %d entries). Could not %s.", serviceID, len(b.schedulerServices), actionText)
	return nil
}

func logSkipMessage(ip string) {
	log.WithFields(log.Fields{
		"prefix": "bridge",
		"ip":     ip,
	}).Debug("Skipping event due to unrelated service host")
}

func (b *Bridge) processServiceEvent(event *types.ServiceEvent) error {
	b.Lock()
	defer b.Unlock()

	switch event.Action {
	case types.ServiceStarted:
		// New service is started, we need to refresh service cache.
		_, err := b.refreshSchedulerServices()
		if err != nil {
			return err
		}
	case types.ServiceStopped:
		// Service stopped, deregister and remove it from cache.
		// Only consider services registered on current registry's advertized address.
		if event.IP == b.registryAdvertizeAddr {
			if service := b.getCachedService(event.ServiceID, "deregister"); service != nil {
				b.registry.Deregister(service)
				delete(b.schedulerServices, event.ServiceID)
			}
		} else {
			logSkipMessage(event.IP)
		}
	case types.ServiceWentUp:
		// Service went up, register it.
		if service := b.getCachedService(event.ServiceID, "register"); service != nil {
			// Only consider services registered on current registry's advertized address.
			if service.IP == b.registryAdvertizeAddr {
				b.registry.Register(service)
			} else {
				logSkipMessage(service.IP)
			}
		}
	case types.ServiceWentDown:
		// Service went down, deregister it.
		if service := b.getCachedService(event.ServiceID, "deregister"); service != nil {
			// Only consider services registered on current registry's advertized address.
			if service.IP == b.registryAdvertizeAddr {
				b.registry.Deregister(service)
			} else {
				logSkipMessage(service.IP)
			}
		}
	}

	return nil
}

func (b *Bridge) ProcessSchedulerEvents() error {
	schedulerEvents := make(types.EventsChannel, 5)
	err := b.scheduler.ListenForEvents(schedulerEvents)
	if err != nil {
		return err
	}

	log.WithField("prefix", "bridge").Info("Registered for scheduler event stream")
	for {
		event := <-schedulerEvents
		if event.Action != types.ServiceUnchanged {
			log.WithFields(log.Fields{
				"prefix":  "bridge",
				"service": event.ServiceID,
				"action":  event.Action,
				"event":   event.OriginalEvent,
			}).Debug("Received scheduler event")
			b.processServiceEvent(event)
		}
	}

	return nil
}

// Sync performs full synchronization of scheduler tasks to service registry.
func (b *Bridge) Sync() error {
	b.Lock()
	defer b.Unlock()

	actionsPerformed := false

	// Get services from registry.
	registryServices, err := b.registry.Services()
	if err != nil {
		return err
	}

	log.WithField("prefix", "bridge").Infof("Received %d services from registry", len(registryServices))

	// Build ip:port-indexed service map.
	registryServicesMap := make(map[string]*types.Service)
	for _, service := range registryServices {
		registryServicesMap[service.MapKey()] = service
	}

	schedulerServicesMap, err := b.refreshSchedulerServices()
	if err != nil {
		return err
	}

	// Register scheduler services absent in registry.
	for _, schedulerService := range schedulerServicesMap {
		// If service is not yet registered we need to register it.
		// Only consider services registered on current registry's advertized address.
		if schedulerService.IP == b.registryAdvertizeAddr && registryServicesMap[schedulerService.MapKey()] == nil {
			err := b.registry.Register(schedulerService)
			if err != nil {
				return err
			}
			actionsPerformed = true
		}
	}

	// Deregister dangling services (existing in registry and absent in scheduler).
	for _, registryService := range registryServicesMap {
		// If service is registered and we don't have it in scheduler we need to deregister it.
		if schedulerServicesMap[registryService.MapKey()] == nil {
			err := b.registry.Deregister(registryService)
			if err != nil {
				return err
			}
			actionsPerformed = true
		}
	}

	if !actionsPerformed {
		log.WithField("prefix", "bridge").Info("All services are in sync, no actions performed")
	}

	return nil
}

func (b *Bridge) refreshSchedulerServices() (map[string]*types.Service, error) {
	log.WithField("prefix", "bridge").Info("Refreshing scheduler services")

	// Get registry's advertize address.
	addr, err := b.registry.AdvertiseAddr()
	if err != nil {
		return nil, err
	}

	b.registryAdvertizeAddr = addr

	// Get services from scheduler.
	schedulerServicesArray, err := b.scheduler.Services()
	if err != nil {
		return nil, err
	}

	log.WithField("prefix", "bridge").Infof("Registry advertize address is %s", b.registryAdvertizeAddr)

	// Build 2 maps of services:
	// ServiceID-indexed and ip:port-indexed
	ipPortServices := make(map[string]*types.Service)
	b.schedulerServices = make(map[string]*types.Service)
	for _, service := range schedulerServicesArray {
		ipPortServices[service.MapKey()] = service
		b.schedulerServices[service.ID] = service
	}

	log.WithField("prefix", "bridge").Infof(
		"Received %d services from scheduler, %d are running on registry advertized address",
		len(schedulerServicesArray),
		len(ipPortServices),
	)

	return ipPortServices, nil
}
<|endoftext|>


<commit_before>package sanntid

import "testing"

func TestParseArrivalData(t *testing.T) {
	exampleContent := []byte(`[
	{
		"RecordedAtTime":"2015-02-27T12:29:41.618+01:00",
		"MonitoringRef":"3010536",
		"MonitoredVehicleJourney":{
			"LineRef":"20",
			"DirectionRef":"2",
			"FramedVehicleJourneyRef":{
				"DataFrameRef":
				"2015-02-27",
				"DatedVehicleJourneyRef":"5803"
			},
			"PublishedLineName":"20",
			"DirectionName":"2",
			"OperatorRef":"Unibuss",
			"OriginName":"Galgeberg (i Jordalgata)",
			"OriginRef":"3010640",
			"DestinationRef":3012501,
			"DestinationName":"Skøyen",
			"OriginAimedDepartureTime":"0001-01-01T00:00:00",
			"DestinationAimedArrivalTime":"0001-01-01T00:00:00",
			"Monitored":true,
			"InCongestion":false,
			"Delay":"PT85S",
			"TrainBlockPart":null,
			"BlockRef":"2010",
			"VehicleRef":"101047",
			"VehicleMode":0,
			"VehicleJourneyName":"20676",
			"MonitoredCall":{
				"VisitNumber":5,
				"VehicleAtStop":false,
				"DestinationDisplay":"Skøyen",
				"AimedArrivalTime":"2015-02-27T12:29:00+01:00",
				"ExpectedArrivalTime":"2015-02-27T12:30:25+01:00",
				"AimedDepartureTime":"2015-02-27T12:29:00+01:00",
				"ExpectedDepartureTime":"2015-02-27T12:30:25+01:00",
				"DeparturePlatformName":"2"
			},
			"VehicleFeatureRef":null
		},
		"Extensions":{
			"IsHub":false,
			"OccupancyData":{
				"OccupancyAvailable":true,
				"OccupancyPercentage":20
			},
			"Deviations":[],
			"LineColour":"E60000"
		}
	}
	]`)
	expected := sanntidArrivalData{
		sanntidMonitoredVehicleJourney{
			"Skøyen",
			sanntidMonitoredCall{
				"2015-02-27T12:30:25+01:00",
				"2",
				"Skøyen",
			},
			"20",
			0,
			2,
		},
	}

	result := parseArrivalData(exampleContent)[0]

	if result.MonitoredVehicleJourney.DestinationName != expected.MonitoredVehicleJourney.DestinationName {
		t.Errorf(
			"Expected MonitoredVehicleJourney.DestinationName == %q (got: %q)",
			expected.MonitoredVehicleJourney.DestinationName,
			result.MonitoredVehicleJourney.DestinationName)
	}
}
<commit_msg>Change expected arrival data to use  type<commit_after>package sanntid

import "testing"

func TestParseArrivalData(t *testing.T) {
	exampleContent := []byte(`[
	{
		"RecordedAtTime":"2015-02-27T12:29:41.618+01:00",
		"MonitoringRef":"3010536",
		"MonitoredVehicleJourney":{
			"LineRef":"20",
			"DirectionRef":"2",
			"FramedVehicleJourneyRef":{
				"DataFrameRef":
				"2015-02-27",
				"DatedVehicleJourneyRef":"5803"
			},
			"PublishedLineName":"20",
			"DirectionName":"2",
			"OperatorRef":"Unibuss",
			"OriginName":"Galgeberg (i Jordalgata)",
			"OriginRef":"3010640",
			"DestinationRef":3012501,
			"DestinationName":"Skøyen",
			"OriginAimedDepartureTime":"0001-01-01T00:00:00",
			"DestinationAimedArrivalTime":"0001-01-01T00:00:00",
			"Monitored":true,
			"InCongestion":false,
			"Delay":"PT85S",
			"TrainBlockPart":null,
			"BlockRef":"2010",
			"VehicleRef":"101047",
			"VehicleMode":0,
			"VehicleJourneyName":"20676",
			"MonitoredCall":{
				"VisitNumber":5,
				"VehicleAtStop":false,
				"DestinationDisplay":"Skøyen",
				"AimedArrivalTime":"2015-02-27T12:29:00+01:00",
				"ExpectedArrivalTime":"2015-02-27T12:30:25+01:00",
				"AimedDepartureTime":"2015-02-27T12:29:00+01:00",
				"ExpectedDepartureTime":"2015-02-27T12:30:25+01:00",
				"DeparturePlatformName":"2"
			},
			"VehicleFeatureRef":null
		},
		"Extensions":{
			"IsHub":false,
			"OccupancyData":{
				"OccupancyAvailable":true,
				"OccupancyPercentage":20
			},
			"Deviations":[],
			"LineColour":"E60000"
		}
	}
	]`)
	expected := sanntidArrivalData{
		sanntidMonitoredVehicleJourney{
			"Skøyen",
			sanntidMonitoredCall{
				"2015-02-27T12:30:25+01:00",
				"2",
				"Skøyen",
			},
			"20",
			0,
			sanntidDirection(2),
		},
	}

	result := parseArrivalData(exampleContent)[0]

	if result.MonitoredVehicleJourney.DestinationName != expected.MonitoredVehicleJourney.DestinationName {
		t.Errorf(
			"Expected MonitoredVehicleJourney.DestinationName == %q (got: %q)",
			expected.MonitoredVehicleJourney.DestinationName,
			result.MonitoredVehicleJourney.DestinationName)
	}
}
<|endoftext|>


<commit_before>package hyperdb

import (
	"errors"
	"github.com/boltdb/bolt"
	"time"
)

const DefaultDataFileName = "hyper.db"

var (
	ErrCannotCloseNilDB     = errors.New("attempted to close nil database")
	ErrCannotCloseNilBoltDB = errors.New("attemted to close nil Bolt database")
)

// DB represents a collection of objects with multitude of attributes,
// that can be retrieved based all possible dimensions.
type DB struct {
	boltHandle *bolt.DB
}

// Open creates and opens a database at the given path.
// If the file doesn't exist, it is created automatically.
func Open(path string) (*DB, error) {
	if path == "" {
		path = DefaultDataFileName
	}
	db, err := bolt.Open(path, 0600, &bolt.Options{Timeout: 1 * time.Second})
	if err != nil {
		return nil, err
	}
	return &DB{boltHandle: db}, nil
}

// Close closes the database.
func (db *DB) Close() error {
	if db == nil {
		return ErrCannotCloseNilDB
	}
	if db.boltHandle == nil {
		return ErrCannotCloseNilBoltDB
	}
	return db.boltHandle.Close()
}

// Add adds an object to the given namespace - indexes all attributes and stores data
// accessible through GetData() method of the HyperObject.
// The function returns unique id of the object stored in hyperdb that can
// later be used to update or remove object in/from the namespace.
func (db *DB) Add(namespace string, obj *HyperObject) (string, error) {
	return "", nil
}

// Update updates the object with specified unique id in the given namespace.
func (db *DB) Update(namespace string, uid string, obj *HyperObject) error {
	return nil
}

// Remove removes passed object from the given namespace
// based on the unique id of the object.
func (db *DB) Remove(namespace string, uid string) ([]byte, error) {
	return nil, nil
}
<commit_msg>Add comments to contst and errror variables<commit_after>package hyperdb

import (
	"errors"
	"time"

	"github.com/boltdb/bolt"
)

// DefaultDataFileName is the default file name of the database file.
const DefaultDataFileName = "hyper.db"

var (
	// ErrCannotCloseNilDB is an error returned when attempted to close nil database.
	ErrCannotCloseNilDB = errors.New("attempted to close nil database")
	// ErrCannotCloseNilBoltDB is an error returned when attempted to close nil Bolt database.
	ErrCannotCloseNilBoltDB = errors.New("attempted to close nil Bolt database")
)

// DB represents a collection of objects with multitude of attributes,
// that can be retrieved based all possible dimensions.
type DB struct {
	boltHandle *bolt.DB
}

// Open creates and opens a database at the given path.
// If the file doesn't exist, it is created automatically.
func Open(path string) (*DB, error) {
	if path == "" {
		path = DefaultDataFileName
	}
	db, err := bolt.Open(path, 0600, &bolt.Options{Timeout: 1 * time.Second})
	if err != nil {
		return nil, err
	}
	return &DB{boltHandle: db}, nil
}

// Close closes the database.
func (db *DB) Close() error {
	if db == nil {
		return ErrCannotCloseNilDB
	}
	if db.boltHandle == nil {
		return ErrCannotCloseNilBoltDB
	}
	return db.boltHandle.Close()
}

// Add adds an object to the given namespace - indexes all attributes and stores data
// accessible through GetData() method of the HyperObject.
// The function returns unique id of the object stored in hyperdb that can
// later be used to update or remove object in/from the namespace.
func (db *DB) Add(namespace string, obj *HyperObject) (string, error) {
	return "", nil
}

// Update updates the object with specified unique id in the given namespace.
func (db *DB) Update(namespace string, uid string, obj *HyperObject) error {
	return nil
}

// Remove removes passed object from the given namespace
// based on the unique id of the object.
func (db *DB) Remove(namespace string, uid string) ([]byte, error) {
	return nil, nil
}
<|endoftext|>


<commit_before>package main

import (
	"math/rand"
	"fmt"
	"io/ioutil"
	"os"
	// "time"
)

const (
	TRADB = ".trago.db"
	bytes = "abcdefghijklmnopqrstuvwxyz1234567890"
	currentDir = "./"
)

type TraDb struct {
	replicaId string
	version int
	files map[string]FileState
}

type FileState struct {
	size int
	mtime int64
	// TODO: use a hash as well
}

func main() {
	db, err := parseDbFile()
	checkError(err)

	fmt.Println(db)
}

func parseDbFile() (TraDb, error) {
	tradb := TraDb{}

	_, err := ioutil.ReadFile(TRADB)
	if os.IsNotExist(err) {
		fmt.Println("didn't find .trago.db")
		tradb = createDb()
		writeDb(tradb)
	} else {
		return tradb, err
	}

	return tradb, nil
}

func createDb() TraDb {
	replicaId := make([]byte, 16)
	version := 1

	for i, _ := range replicaId {
		replicaId[i] = bytes[rand.Intn(len(bytes))]
	}

	files, err := ioutil.ReadDir(currentDir)
	checkError(err)

	filemap := make(map[string]FileState)
	for _, file := range files {
		if file.IsDir() {
			continue			// ignore directories for now
		}
		fs := FileState{
			size: int(file.Size()),
			mtime: file.ModTime().UnixNano(),
		}
		filemap[file.Name()] = fs
	}

	return TraDb{
		replicaId: string(replicaId),
		version: version,
		files: filemap,
	}
}

func findFiles(path string) {
	files, err := ioutil.ReadDir(path)
	checkError(err)

	for _, file := range files {
		fmt.Println(file.Name())
	}
}

func checkError(err error) {
	if err != nil {
		fmt.Println(err)
		os.Exit(1)
	}
}
<commit_msg>write db to a file on the first run<commit_after>package main

import (
	"math/rand"
	"fmt"
	"io/ioutil"
	"os"
	"strings"
)

const (
	TRADB = ".trago.db"
	bytes = "abcdefghijklmnopqrstuvwxyz1234567890"
	currentDir = "./"
)

type TraDb struct {
	replicaId string
	version int
	files map[string]FileState
}

type FileState struct {
	size int
	mtime int64
	// TODO: use a hash as well
}

func main() {
	db, err := parseDbFile()
	checkError(err)

	fmt.Println(db)
}

func parseDbFile() (TraDb, error) {
	tradb := TraDb{}

	_, err := ioutil.ReadFile(TRADB)
	if os.IsNotExist(err) {
		fmt.Println("didn't find .trago.db")
		tradb = createDb()
		writeDb(tradb)
	} else {
		return tradb, err
	}

	return tradb, nil
}

func createDb() TraDb {
	replicaId := make([]byte, 16)
	version := 1

	for i, _ := range replicaId {
		replicaId[i] = bytes[rand.Intn(len(bytes))]
	}

	files, err := ioutil.ReadDir(currentDir)
	checkError(err)

	filemap := make(map[string]FileState)
	for _, file := range files {
		if file.IsDir() {
			continue			// ignore directories for now
		}
		fs := FileState{
			size: int(file.Size()),
			mtime: file.ModTime().UnixNano(),
		}
		filemap[file.Name()] = fs
	}

	return TraDb{
		replicaId: string(replicaId),
		version: version,
		files: filemap,
	}
}

func writeDb(tradb TraDb) {
	preamble := fmt.Sprintf(
		"# replica ID\n%s\n# version\n%d\n# files\n",
		tradb.replicaId,
		tradb.version,
	)

	fileEntries := make([]string, len(tradb.files))

	i := 0
	for filename, info := range tradb.files {
		fileEntries[i] = fmt.Sprintf(
			"%s %d %d",
			filename,
			info.size,
			info.mtime,
		)
		i = i+1
	}

	entryString := strings.Join(fileEntries, "\n")
	dataToWrite := []byte(preamble + entryString)

	err := ioutil.WriteFile(TRADB, dataToWrite, 0644)
	checkError(err)
}

func findFiles(path string) {
	files, err := ioutil.ReadDir(path)
	checkError(err)

	for _, file := range files {
		fmt.Println(file.Name())
	}
}

func checkError(err error) {
	if err != nil {
		fmt.Println(err)
		os.Exit(1)
	}
}
<|endoftext|>


<commit_before>package main

import (
	"database/sql"
	"log"
	"time"

	"github.com/google/uuid"
	_ "github.com/mattn/go-sqlite3"
)

type Event struct {
	EnvelopeId  uuid.UUID
	Id          uuid.UUID
	Date        string
	Name        string
	Balance     int
	Target      int
	MonthTarget int
	Deleted     bool
}

type Envelope struct {
	// Values in Euro-cents
	Id          uuid.UUID
	Balance     int
	Target      int
	Name        string
	MonthDelta  int
	MonthTarget int
}

type DB struct {
	db     *sql.DB
	Events chan Event
}

func OpenDB() (*DB, error) {
	db, err := sql.Open("sqlite3", "envelopes.sqlite")
	if err != nil {
		return nil, err
	}

	rv := &DB{db, make(chan Event)}

	if err := rv.setup(); err != nil {
		return nil, err
	}

	var count int64
	if err := db.QueryRow("SELECT count(*) FROM envelopes WHERE deleted = 'false'").Scan(&count); err != nil {
		return nil, err
	}
	log.Printf(`DB contains %d envelopes`, count)

	return rv, nil
}

func (d *DB) Close() error {
	return d.db.Close()
}

func (d *DB) setup() error {
	tx, err := d.db.Begin()
	if err != nil {
		return err
	}

	if _, err := tx.Exec(`
		CREATE TABLE IF NOT EXISTS envelopes
		(id UUID PRIMARY KEY, name STRING,
		 balance INTEGER,
		 target INTEGER, monthtarget INTEGER,
		 deleted BOOLEAN)`); err != nil {
		return err
	}

	if _, err := tx.Exec(`
		CREATE TABLE IF NOT EXISTS history
		(id UUID PRIMARY KEY,
		 envelope UUID, date DATETIME, name STRING,
		 balance INTEGER, target INTEGER, monthtarget INTEGER,
		 deleted BOOLEAN,
		 FOREIGN KEY(envelope) REFERENCES envelopes(id))`); err != nil {
		return err
	}

	return tx.Commit()
}

func (d *DB) AllEnvelopes() []*Envelope {
	rv := []*Envelope{}

	rows, err := d.db.Query(`
		SELECT e.id, e.name, e.balance, e.target, e.monthtarget, h.balance
		FROM envelopes AS e LEFT OUTER JOIN
			(SELECT envelope, sum(balance) AS balance, date
			 FROM history
			 WHERE date > DATE('now', 'start of month')
			 GROUP BY envelope) AS h
		ON e.id = h.envelope
		WHERE e.deleted = 'false'`)
	if err != nil {
		log.Printf(`error querying DB: %v`, err)
		return nil
	}
	defer rows.Close()

	for rows.Next() {
		var e Envelope
		var delta sql.NullInt64
		if err := rows.Scan(&e.Id, &e.Name, &e.Balance, &e.Target, &e.MonthTarget, &delta); err != nil {
			log.Printf(`error querying DB: %v`, err)
			return nil
		}
		if delta.Valid {
			e.MonthDelta = int(delta.Int64)
		}
		rv = append(rv, &e)
	}

	return rv
}

func (d *DB) DeleteEnvelope(id uuid.UUID) error {
	evt := Event{
		EnvelopeId: id,
		Id: uuid.New(),
		Date: time.Now().String(),
	}

	d.Events <- evt

	return d.MergeEvent(evt)
}

func (d *DB) Envelope(id uuid.UUID) (*Envelope, error) {
	tx, err := d.db.Begin()
	if err != nil {
		return nil, err
	}
	defer tx.Rollback()

	return d.envelopeWithTx(tx, id)
}

func (d *DB) envelopeWithTx(tx *sql.Tx, id uuid.UUID) (*Envelope, error) {
	e := Envelope{Id: id}

	err := tx.QueryRow(`
		SELECT id, name, balance, target, monthtarget
		FROM envelopes
		WHERE id = $1 AND deleted = 'false'`, id).Scan(&e.Id, &e.Name, &e.Balance, &e.Target, &e.MonthTarget)
	if err == nil {
		return &e, nil
	}

	if _, err := tx.Exec(`
		INSERT INTO envelopes(id, name, balance, target, monthtarget, deleted)
		VALUES ($1, "", 0, 0, 0, 'false')`, e.Id); err != nil {
		return nil, err
	}
	return &e, nil
}

func (d *DB) EnvelopeWithHistory(id uuid.UUID) (*Envelope, []Event, error) {
	events := []Event{}

	tx, err := d.db.Begin()
	if err != nil {
		return nil, events, err
	}
	defer tx.Rollback()

	envelope, err := d.envelopeWithTx(tx, id)
	if err != nil {
		return nil, events, err
	}

	rows, err := tx.Query(`
		SELECT id, envelope, date, name, balance, target, monthtarget, deleted
		FROM history
		WHERE envelope = $1`, id)
	if err != nil {
		return nil, events, err
	}
	defer rows.Close()

	for rows.Next() {
		var e Event
		if err := rows.Scan(&e.Id, &e.EnvelopeId, &e.Date, &e.Name, &e.Balance, &e.Target, &e.MonthTarget, &e.Deleted); err != nil {
			log.Printf(`can't scan event %s: %s`, e.Id, err)
		}
		if e.Deleted {
			e.Name = envelope.Name
		}
		events = append(events, e)
	}

	return envelope, events, nil
}

func (d *DB) MergeEvent(e Event) error {
	log.Printf(`merging event %v`, e)

	tx, err := d.db.Begin()
	if err != nil {
		return err
	}

	env, err := d.envelopeWithTx(tx, e.EnvelopeId)
	if err != nil {
		tx.Rollback()
		return err
	}

	_, err = tx.Exec(`
		INSERT INTO history (id, envelope, name, balance, target, monthtarget, deleted, date)
		VALUES ($1, $2, $3, $4, $5, $6, $7, datetime('now'))`,
		e.Id, e.EnvelopeId, e.Name, e.Balance, e.Target, e.MonthTarget, e.Deleted)
	if err != nil {
		tx.Rollback()
		return err
	}

	if e.Name == "" {
		e.Name = env.Name
	}
	_, err = tx.Exec(`
		UPDATE envelopes
		SET name = $1, balance = $2, target = $3, monthtarget = $4, deleted = $5
		WHERE id = $6`, e.Name, env.Balance+e.Balance, env.Target+e.Target, env.MonthTarget+e.MonthTarget, e.Deleted, env.Id)
	if err != nil {
		tx.Rollback()
		return err
	}

	return tx.Commit()
}

func (d *DB) UpdateEnvelope(id uuid.UUID, name string, dBalance, dTarget, dMonthTarget int) error {
	env, err := d.Envelope(id)
	if err != nil {
		return err
	}

	/* Make parameters relative */
	dBalance -= env.Balance
	dTarget -= env.Target
	dMonthTarget -= env.MonthTarget

	log.Printf(`dB: %d dT: %d dMT: %d`, dBalance, dTarget, dMonthTarget)

	evt := Event{
		EnvelopeId:  env.Id,
		Id:          uuid.New(),
		Date:        time.Now().String(),
		Name:        name,
		Balance:     dBalance,
		Target:      dTarget,
		MonthTarget: dMonthTarget,
		Deleted:     false,
	}

	d.Events <- evt

	return d.MergeEvent(evt)
}
<commit_msg>Debug something<commit_after>package main

import (
	"database/sql"
	"log"
	"time"

	"github.com/google/uuid"
	_ "github.com/mattn/go-sqlite3"
)

type Event struct {
	EnvelopeId  uuid.UUID
	Id          uuid.UUID
	Date        string
	Name        string
	Balance     int
	Target      int
	MonthTarget int
	Deleted     bool
}

type Envelope struct {
	// Values in Euro-cents
	Id          uuid.UUID
	Balance     int
	Target      int
	Name        string
	MonthDelta  int
	MonthTarget int
}

type DB struct {
	db     *sql.DB
	Events chan Event
}

func OpenDB() (*DB, error) {
	db, err := sql.Open("sqlite3", "envelopes.sqlite")
	if err != nil {
		return nil, err
	}

	rv := &DB{db, make(chan Event)}

	if err := rv.setup(); err != nil {
		return nil, err
	}

	var count int64
	if err := db.QueryRow("SELECT count(*) FROM envelopes WHERE deleted = 'false'").Scan(&count); err != nil {
		return nil, err
	}
	log.Printf(`DB contains %d envelopes`, count)

	return rv, nil
}

func (d *DB) Close() error {
	return d.db.Close()
}

func (d *DB) setup() error {
	tx, err := d.db.Begin()
	if err != nil {
		return err
	}

	if _, err := tx.Exec(`
		CREATE TABLE IF NOT EXISTS envelopes
		(id UUID PRIMARY KEY, name STRING,
		 balance INTEGER,
		 target INTEGER, monthtarget INTEGER,
		 deleted BOOLEAN)`); err != nil {
		return err
	}

	if _, err := tx.Exec(`
		CREATE TABLE IF NOT EXISTS history
		(id UUID PRIMARY KEY,
		 envelope UUID, date DATETIME, name STRING,
		 balance INTEGER, target INTEGER, monthtarget INTEGER,
		 deleted BOOLEAN,
		 FOREIGN KEY(envelope) REFERENCES envelopes(id))`); err != nil {
		return err
	}

	return tx.Commit()
}

func (d *DB) AllEnvelopes() []*Envelope {
	rv := []*Envelope{}

	rows, err := d.db.Query(`
		SELECT e.id, e.name, e.balance, e.target, e.monthtarget, h.balance
		FROM envelopes AS e LEFT OUTER JOIN
			(SELECT envelope, sum(balance) AS balance, date
			 FROM history
			 WHERE date > DATE('now', 'start of month')
			 GROUP BY envelope) AS h
		ON e.id = h.envelope
		WHERE e.deleted = 'false'`)
	if err != nil {
		log.Printf(`error querying DB: %v`, err)
		return nil
	}
	defer rows.Close()

	for rows.Next() {
		var e Envelope
		var delta sql.NullInt64
		if err := rows.Scan(&e.Id, &e.Name, &e.Balance, &e.Target, &e.MonthTarget, &delta); err != nil {
			log.Printf(`error querying DB: %v`, err)
			return nil
		}
		if delta.Valid {
			e.MonthDelta = int(delta.Int64)
		}
		rv = append(rv, &e)
	}

	return rv
}

func (d *DB) DeleteEnvelope(id uuid.UUID) error {
	evt := Event{
		EnvelopeId: id,
		Id: uuid.New(),
		Date: time.Now().String(),
	}

	d.Events <- evt

	return d.MergeEvent(evt)
}

func (d *DB) Envelope(id uuid.UUID) (*Envelope, error) {
	tx, err := d.db.Begin()
	if err != nil {
		return nil, err
	}
	defer tx.Rollback()

	return d.envelopeWithTx(tx, id)
}

func (d *DB) envelopeWithTx(tx *sql.Tx, id uuid.UUID) (*Envelope, error) {
	e := Envelope{Id: id}

	err := tx.QueryRow(`
		SELECT id, name, balance, target, monthtarget
		FROM envelopes
		WHERE id = $1 AND deleted = 'false'`, id).Scan(&e.Id, &e.Name, &e.Balance, &e.Target, &e.MonthTarget)
	if err == nil {
		return &e, nil
	}

	log.Printf(`err for %s: %s`, id, err)

	if _, err := tx.Exec(`
		INSERT INTO envelopes(id, name, balance, target, monthtarget, deleted)
		VALUES ($1, "", 0, 0, 0, 'false')`, e.Id); err != nil {
		return nil, err
	}
	return &e, nil
}

func (d *DB) EnvelopeWithHistory(id uuid.UUID) (*Envelope, []Event, error) {
	events := []Event{}

	tx, err := d.db.Begin()
	if err != nil {
		return nil, events, err
	}
	defer tx.Rollback()

	envelope, err := d.envelopeWithTx(tx, id)
	if err != nil {
		return nil, events, err
	}

	rows, err := tx.Query(`
		SELECT id, envelope, date, name, balance, target, monthtarget, deleted
		FROM history
		WHERE envelope = $1`, id)
	if err != nil {
		return nil, events, err
	}
	defer rows.Close()

	for rows.Next() {
		var e Event
		if err := rows.Scan(&e.Id, &e.EnvelopeId, &e.Date, &e.Name, &e.Balance, &e.Target, &e.MonthTarget, &e.Deleted); err != nil {
			log.Printf(`can't scan event %s: %s`, e.Id, err)
		}
		if e.Deleted {
			e.Name = envelope.Name
		}
		events = append(events, e)
	}

	return envelope, events, nil
}

func (d *DB) MergeEvent(e Event) error {
	log.Printf(`merging event %v`, e)

	tx, err := d.db.Begin()
	if err != nil {
		return err
	}

	env, err := d.envelopeWithTx(tx, e.EnvelopeId)
	if err != nil {
		tx.Rollback()
		return err
	}

	_, err = tx.Exec(`
		INSERT INTO history (id, envelope, name, balance, target, monthtarget, deleted, date)
		VALUES ($1, $2, $3, $4, $5, $6, $7, datetime('now'))`,
		e.Id, e.EnvelopeId, e.Name, e.Balance, e.Target, e.MonthTarget, e.Deleted)
	if err != nil {
		tx.Rollback()
		return err
	}

	if e.Name == "" {
		e.Name = env.Name
	}
	_, err = tx.Exec(`
		UPDATE envelopes
		SET name = $1, balance = $2, target = $3, monthtarget = $4, deleted = $5
		WHERE id = $6`, e.Name, env.Balance+e.Balance, env.Target+e.Target, env.MonthTarget+e.MonthTarget, e.Deleted, env.Id)
	if err != nil {
		tx.Rollback()
		return err
	}

	return tx.Commit()
}

func (d *DB) UpdateEnvelope(id uuid.UUID, name string, dBalance, dTarget, dMonthTarget int) error {
	env, err := d.Envelope(id)
	if err != nil {
		return err
	}

	/* Make parameters relative */
	dBalance -= env.Balance
	dTarget -= env.Target
	dMonthTarget -= env.MonthTarget

	log.Printf(`dB: %d dT: %d dMT: %d`, dBalance, dTarget, dMonthTarget)

	evt := Event{
		EnvelopeId:  env.Id,
		Id:          uuid.New(),
		Date:        time.Now().String(),
		Name:        name,
		Balance:     dBalance,
		Target:      dTarget,
		MonthTarget: dMonthTarget,
		Deleted:     false,
	}

	d.Events <- evt

	return d.MergeEvent(evt)
}
<|endoftext|>


<commit_before>package orr

import (
	"encoding/json"
	"fmt"
	"reflect"
	"strconv"
	"strings"
)

// 每个结构必须有key键值，字段名必须为Id，必须为正整数
//
// tags:
//   orr
//     index: 该字段名与结构名(结构名_字段名)，作为辅助hashmap，hashmap的field为该字段值，hashmap的值位obj.Id
//     list: 该字段名与结构名(结构名_字段名)，作为辅助list
//  example: `orr:"index"`

// map,list数据过大的解决方法：
//  当map,list数据较少时(1000个以内)，可以将map,list数据保存为json格式; 当map,list数据超过一定规模时，
//  应使用redis的hashmap或list数据结构
//  可以通过设置用户的relations来决定该数据是否以json格式保存

// Insert用于初次数据到redis中，检查struct的tags，并根据tags的定义，
// 来设置redis的辅助字段
// 返回Id
func Insert(obj interface{}) (int64, error) {
	if reflect.TypeOf(obj).Kind() != reflect.Ptr {
		return -1, fmt.Errorf("param obj MUST be type Ptr.")
	}
	rvobj := reflect.ValueOf(obj).Elem()
	rtobj := reflect.TypeOf(rvobj.Interface())

	if rtobj.Kind() != reflect.Struct {
		return -1, fmt.Errorf("Param obj must be struct type.")
	}

	objName := getTypeName(rtobj)

	// 查看结构体是否有辅助字段
	var (
		idxkeys   []string = make([]string, 0)
		idxfields []string = make([]string, 0)
		vid       reflect.Value
	)
	for i := 0; i < rtobj.NumField(); i++ {
		structfield := rtobj.Field(i)
		if structfield.Anonymous {
			continue
		}

		fieldName := structfield.Name
		tag := structfield.Tag
		if tag == "" || tag == "-" {
			continue
		}

		if tag.Get("orr") == "index" {
			fieldValue := rvobj.Field(i)
			if fieldValue.Kind() != reflect.String {
				return -1, fmt.Errorf("index field must be string type!")
			}
			fv := fieldValue.Interface().(string)
			if unique(objName+"_"+fieldName, fv) != true {
				return -1, fmt.Errorf("field %s has exist value %s.", fieldName, fieldValue)
			}
			idxkeys = append(idxkeys, objName+"_"+strings.ToLower(fieldName))
			idxfields = append(idxfields, fv)
		}
	}

	id := NewId(objName)
	sid := strconv.FormatInt(id, 10)

	bsid := []byte(sid)
	for i := 0; i < len(idxkeys); i++ {
		hsetToRedis(idxkeys[i], idxfields[i], bsid, "map")
	}

	vid = rvobj.FieldByName("Id")
	vid.SetInt(id)
	buf, err := json.Marshal(obj)
	if err != nil {
		ReturnId(objName, id)
		return -1, err
	}

	err = hsetToRedis(objName, sid, buf, "map")
	if err != nil {
		for i := 0; i < len(idxkeys); i++ {
			hdelFromRedis(idxkeys[i], idxfields[i], "map")
		}

		ReturnId(objName, id)
		return -1, err
	}

	return id, nil
}

// 将结构体的field插入到数据库中
// typ should be "key" or "hash"
func InsertKeyField(typ, name string, fn string, id int64, value interface{}) error {
	buf, err := json.Marshal(&value)
	if err != nil {
		return err
	}
	sid := strconv.FormatInt(id, 10)
	switch typ {
	case "key":
		_, err := rconn.Do("SET", name+"_"+fn+"_"+sid, buf)
		return err

	case "hash":
		_, err := rconn.Do("HSET", name+"_"+fn, sid, buf)
		return err

	default:
		return fmt.Errorf("param typ invalid, must be key or hash or list.")
	}

	return nil
}

func BuildRelation() {

}

// 仅更新主hashmap
func Update(obj interface{}, objName string, objId int64) error {
	buf, err := json.Marshal(obj)
	if err != nil {
		return err
	}

	err = hsetToRedis(objName, strconv.FormatInt(objId, 10), buf, "map")
	return err
}

func Delete(obj interface{}) error {
	var (
		rvobj reflect.Value
		rtobj reflect.Type
	)

	if reflect.TypeOf(obj).Kind() == reflect.Ptr {
		rvobj = reflect.ValueOf(obj).Elem()
		rtobj = reflect.TypeOf(rvobj.Interface())
	} else {
		rvobj = reflect.ValueOf(obj)
		rtobj = reflect.TypeOf(obj)
	}

	if rtobj.Kind() != reflect.Struct {
		return fmt.Errorf("Param obj must be struct type.")
	}

	var (
		idxfields []string
		idxvalue  []string
	)
	objName := getTypeName(rtobj)

	for i := 0; i < rtobj.NumField(); i++ {
		structfield := rtobj.Field(i)
		if structfield.Anonymous {
			continue
		}

		fieldName := structfield.Name
		tag := structfield.Tag
		if tag == "" || tag == "-" {
			continue
		}

		if tag.Get("orr") == "index" {
			idxfields = append(idxfields, objName+"_"+strings.ToLower(fieldName))
			idxvalue = append(idxvalue, rvobj.Field(i).String())
		}
	}

	id := rvobj.FieldByName("Id").Int()
	sid := strconv.FormatInt(id, 10)
	for i, field := range idxfields {
		rconn.Send("HDEL", field, idxvalue[i])
	}
	rconn.Send("HDEL", objName, sid)
	rconn.Flush()
	/*
		for i := 0; i <= len(idxfields); i++ {
			fmt.Println("rconn receive ", i)
			r, err := rconn.Receive()
			if err != nil {
				fmt.Println(err.Error())
			} else {
				fmt.Println(r)
			}
		}
	*/
	return nil
}

func DeleteKeyField(typ string, name string, fn string, id int64) {
	switch typ {
	case "key":
		rconn.Do("DEL", name+"_"+fn+"_"+strconv.FormatInt(id, 10))
		return

	case "hash":
		rconn.Do("HDEL", name+"_"+fn, strconv.FormatInt(id, 10))
		return

	default:
		panic("param typ invalid, must be key or hash or list.")
	}
}

// 从redis中还原数据
func Select(Id int64, name string, res interface{}) error {
	if reflect.TypeOf(res).Kind() != reflect.Ptr {
		return fmt.Errorf("param res must be Ptr type.")
	}

	reply, err := rconn.Do("HGET", name, strconv.FormatInt(Id, 10))
	if err != nil {
		return err
	}

	err = json.Unmarshal(reply.([]byte), res)
	return err
}

func SelectKeyField(keyTyp string, name string,
	fn string, id int64, res interface{}) (err error) {
	if reflect.TypeOf(res).Kind() != reflect.Ptr {
		return fmt.Errorf("param res must be Ptr type.")
	}
	var reply interface{}

	sid := strconv.FormatInt(id, 10)

	switch keyTyp {
	case "key":
		reply, err = rconn.Do("GET", name+"_"+fn+"_"+sid)
	case "hash":
		reply, err = rconn.Do("HGET", name+"_"+fn, sid)
	default:
		return fmt.Errorf("Not support this keytype: %s", keyTyp)
	}
	if err != nil {
		return err
	}
	if reply == nil {
		res = reflect.New(reflect.TypeOf(res)).Elem()
		return
	}

	err = json.Unmarshal(reply.([]byte), res)
	return err
}

func getLatestId(name string) (string, error) {
	reply, err := rconn.Do("INCR", name)
	if err != nil {
		return "", err
	}

	return reply.(string), nil
}

func parseTag(str string) (typ string, addational_typ string) {
	if str == "-" {
		typ = str
	} else if str != "" {
		tags := strings.Split(str, ";")
		m := make(map[string]string)
		for _, value := range tags {
			v := strings.Split(value, ":")
			k := strings.TrimSpace(strings.ToUpper(v[0]))
			if len(v) == 2 {
				m[k] = v[1]
			} else {
				m[k] = k
			}
		}

		addational_typ = m["NOT NULL"] + " " + m["UNIQUE"]
	}
	return
}

func unique(name string, value string) bool {
	reply, _ := rconn.Do("HGET", name, value)
	if reply != nil {
		return false
	}

	return true
}
<commit_msg>pipeline<commit_after>package orr

import (
	"encoding/json"
	"fmt"
	"reflect"
	"strconv"
	"strings"
)

// 每个结构必须有key键值，字段名必须为Id，必须为正整数
//
// tags:
//   orr
//     index: 该字段名与结构名(结构名_字段名)，作为辅助hashmap，hashmap的field为该字段值，hashmap的值位obj.Id
//     list: 该字段名与结构名(结构名_字段名)，作为辅助list
//  example: `orr:"index"`

// map,list数据过大的解决方法：
//  当map,list数据较少时(1000个以内)，可以将map,list数据保存为json格式; 当map,list数据超过一定规模时，
//  应使用redis的hashmap或list数据结构
//  可以通过设置用户的relations来决定该数据是否以json格式保存

// Insert用于初次数据到redis中，检查struct的tags，并根据tags的定义，
// 来设置redis的辅助字段
// 返回Id
func Insert(obj interface{}) (int64, error) {
	if reflect.TypeOf(obj).Kind() != reflect.Ptr {
		return -1, fmt.Errorf("param obj MUST be type Ptr.")
	}
	rvobj := reflect.ValueOf(obj).Elem()
	rtobj := reflect.TypeOf(rvobj.Interface())

	if rtobj.Kind() != reflect.Struct {
		return -1, fmt.Errorf("Param obj must be struct type.")
	}

	objName := getTypeName(rtobj)

	// 查看结构体是否有辅助字段
	var (
		idxkeys   []string = make([]string, 0)
		idxfields []string = make([]string, 0)
		vid       reflect.Value
	)
	for i := 0; i < rtobj.NumField(); i++ {
		structfield := rtobj.Field(i)
		if structfield.Anonymous {
			continue
		}

		fieldName := structfield.Name
		tag := structfield.Tag
		if tag == "" || tag == "-" {
			continue
		}

		if tag.Get("orr") == "index" {
			fieldValue := rvobj.Field(i)
			if fieldValue.Kind() != reflect.String {
				return -1, fmt.Errorf("index field must be string type!")
			}
			fv := fieldValue.Interface().(string)
			if unique(objName+"_"+fieldName, fv) != true {
				return -1, fmt.Errorf("field %s has exist value %s.", fieldName, fieldValue)
			}
			idxkeys = append(idxkeys, objName+"_"+strings.Replace(strings.ToLower(fieldName), "_", "", -1))
			idxfields = append(idxfields, fv)
		}
	}

	id := NewId(objName)
	sid := strconv.FormatInt(id, 10)

	bsid := []byte(sid)
	for i := 0; i < len(idxkeys); i++ {
		hsetToRedis(idxkeys[i], idxfields[i], bsid, "map")
	}

	vid = rvobj.FieldByName("Id")
	vid.SetInt(id)
	buf, err := json.Marshal(obj)
	if err != nil {
		ReturnId(objName, id)
		return -1, err
	}

	err = hsetToRedis(objName, sid, buf, "map")
	if err != nil {
		for i := 0; i < len(idxkeys); i++ {
			hdelFromRedis(idxkeys[i], idxfields[i], "map")
		}

		ReturnId(objName, id)
		return -1, err
	}

	return id, nil
}

// 将结构体的field插入到数据库中
// typ should be "key" or "hash"
func InsertKeyField(typ, name string, fn string, id int64, value interface{}) error {
	buf, err := json.Marshal(&value)
	if err != nil {
		return err
	}
	sid := strconv.FormatInt(id, 10)
	switch typ {
	case "key":
		_, err := rconn.Do("SET", name+"_"+fn+"_"+sid, buf)
		return err

	case "hash":
		_, err := rconn.Do("HSET", name+"_"+fn, sid, buf)
		return err

	default:
		return fmt.Errorf("param typ invalid, must be key or hash or list.")
	}

	return nil
}

func BuildRelation() {

}

// 仅更新主hashmap
func Update(obj interface{}, objName string, objId int64) error {
	buf, err := json.Marshal(obj)
	if err != nil {
		return err
	}

	err = hsetToRedis(objName, strconv.FormatInt(objId, 10), buf, "map")
	return err
}

func Delete(obj interface{}) error {
	var (
		rvobj reflect.Value
		rtobj reflect.Type
	)

	if reflect.TypeOf(obj).Kind() == reflect.Ptr {
		rvobj = reflect.ValueOf(obj).Elem()
		rtobj = reflect.TypeOf(rvobj.Interface())
	} else {
		rvobj = reflect.ValueOf(obj)
		rtobj = reflect.TypeOf(obj)
	}

	if rtobj.Kind() != reflect.Struct {
		return fmt.Errorf("Param obj must be struct type.")
	}

	var (
		idxfields []string
		idxvalue  []string
	)
	objName := getTypeName(rtobj)

	for i := 0; i < rtobj.NumField(); i++ {
		structfield := rtobj.Field(i)
		if structfield.Anonymous {
			continue
		}

		fieldName := structfield.Name
		tag := structfield.Tag
		if tag == "" || tag == "-" {
			continue
		}

		if tag.Get("orr") == "index" {
			idxfields = append(idxfields, objName+"_"+strings.ToLower(fieldName))
			idxvalue = append(idxvalue, rvobj.Field(i).String())
		}
	}

	id := rvobj.FieldByName("Id").Int()
	sid := strconv.FormatInt(id, 10)
	for i, field := range idxfields {
		rconn.Send("HDEL", field, idxvalue[i])
	}
	rconn.Send("HDEL", objName, sid)
	rconn.Flush()
	/*
		for i := 0; i <= len(idxfields); i++ {
			fmt.Println("rconn receive ", i)
			r, err := rconn.Receive()
			if err != nil {
				fmt.Println(err.Error())
			} else {
				fmt.Println(r)
			}
		}
	*/
	return nil
}

func DeleteKeyField(typ string, name string, fn string, id int64) {
	switch typ {
	case "key":
		rconn.Do("DEL", name+"_"+fn+"_"+strconv.FormatInt(id, 10))
		return

	case "hash":
		rconn.Do("HDEL", name+"_"+fn, strconv.FormatInt(id, 10))
		return

	default:
		panic("param typ invalid, must be key or hash or list.")
	}
}

// 从redis中还原数据
func Select(Id int64, name string, res interface{}) error {
	if reflect.TypeOf(res).Kind() != reflect.Ptr {
		return fmt.Errorf("param res must be Ptr type.")
	}

	reply, err := rconn.Do("HGET", name, strconv.FormatInt(Id, 10))
	if err != nil {
		return err
	}

	err = json.Unmarshal(reply.([]byte), res)
	return err
}

func SelectIndex(name, fn, value string) (int64, error) {
	reply, err := rconn.Do("HGET", name+"_"+fn, value)
	if err != nil || reply == nil {
		return -1, err
	}
	id, err := strconv.ParseInt(string(reply.([]byte)), 10, 64)
	if err != nil {
		return -1, err
	}
	return id, nil
}

func SelectKeyField(keyTyp string, name string,
	fn string, id int64, res interface{}) (err error) {
	if reflect.TypeOf(res).Kind() != reflect.Ptr {
		return fmt.Errorf("param res must be Ptr type.")
	}
	var reply interface{}

	sid := strconv.FormatInt(id, 10)

	switch keyTyp {
	case "key":
		reply, err = rconn.Do("GET", name+"_"+fn+"_"+sid)
	case "hash":
		reply, err = rconn.Do("HGET", name+"_"+fn, sid)
	default:
		return fmt.Errorf("Not support this keytype: %s", keyTyp)
	}
	if err != nil {
		return err
	}
	if reply == nil {
		res = reflect.New(reflect.TypeOf(res)).Elem()
		return
	}

	err = json.Unmarshal(reply.([]byte), res)
	return err
}

func getLatestId(name string) (string, error) {
	reply, err := rconn.Do("INCR", name)
	if err != nil {
		return "", err
	}

	return reply.(string), nil
}

func parseTag(str string) (typ string, addational_typ string) {
	if str == "-" {
		typ = str
	} else if str != "" {
		tags := strings.Split(str, ";")
		m := make(map[string]string)
		for _, value := range tags {
			v := strings.Split(value, ":")
			k := strings.TrimSpace(strings.ToUpper(v[0]))
			if len(v) == 2 {
				m[k] = v[1]
			} else {
				m[k] = k
			}
		}

		addational_typ = m["NOT NULL"] + " " + m["UNIQUE"]
	}
	return
}

func unique(name string, value string) bool {
	reply, _ := rconn.Do("HGET", name, value)
	if reply != nil {
		return false
	}

	return true
}
<|endoftext|>


<commit_before><commit_msg>Pong example<commit_after><|endoftext|>


<commit_before><commit_msg>Make deep copy of objects in the fake bucket<commit_after><|endoftext|>


<commit_before><commit_msg>golint: gen/collision_test.go (#262)<commit_after><|endoftext|>


<commit_before>package distconf

import (
	"errors"
	"testing"
	"time"

	"github.com/samuel/go-zookeeper/zk"
	"github.com/signalfx/golib/log"
	"github.com/signalfx/golib/zkplus/zktest"
	. "github.com/smartystreets/goconvey/convey"
	"github.com/stretchr/testify/assert"
)

func TestZkNewlyCreated(t *testing.T) {
	zkServer := zktest.New()
	z, err := Zk(ZkConnectorFunc(func() (ZkConn, <-chan zk.Event, error) {
		return zkServer.Connect()
	}), &ZkConfig{})
	defer z.Close()
	assert.NoError(t, err)
	seenCallbacks := make(chan string, 2)
	callback := func(s string) {
		seenCallbacks <- s
	}
	log.IfErr(log.Panic, z.(Dynamic).Watch("hello", callback))
	assert.Equal(t, 0, len(seenCallbacks))
	assert.Nil(t, z.Write("hello", []byte("test")))
	seenString := <-seenCallbacks
	assert.Equal(t, "hello", seenString)
}

func TestZkConf(t *testing.T) {
	DefaultLogger.Log("TestZkConf")
	zkServer := zktest.New()
	z, err := Zk(ZkConnectorFunc(func() (ZkConn, <-chan zk.Event, error) {
		return zkServer.Connect()
	}), &ZkConfig{})
	defer z.Close()
	assert.NoError(t, err)

	b, err := z.Get("TestZkConf")
	assert.NoError(t, err)
	assert.Nil(t, b)

	assert.NoError(t, z.Write("TestZkConf", nil))

	signalChan := make(chan string, 4)
	DefaultLogger.Log("Setting watches")
	log.IfErr(log.Panic, z.(Dynamic).Watch("TestZkConf", backingCallbackFunction(func(S string) {
		DefaultLogger.Log("Watch fired!")
		assert.Equal(t, "TestZkConf", S)
		signalChan <- S
	})))

	// The write should work and I should get a single signal on the chan
	DefaultLogger.Log("Doing write 1")
	assert.NoError(t, z.Write("TestZkConf", []byte("newval")))
	DefaultLogger.Log("Write done")
	b, err = z.Get("TestZkConf")
	DefaultLogger.Log("Get done")
	assert.NoError(t, err)
	assert.Equal(t, []byte("newval"), b)
	DefaultLogger.Log("Blocking for values")
	res := <-signalChan
	assert.Equal(t, "TestZkConf", res)

	// Should send another signal
	DefaultLogger.Log("Doing write 2")
	assert.NoError(t, z.Write("TestZkConf", []byte("newval_v2")))
	res = <-signalChan
	assert.Equal(t, "TestZkConf", res)

	DefaultLogger.Log("Doing write 3")
	assert.NoError(t, z.Write("TestZkConf", nil))
	res = <-signalChan
	assert.Equal(t, "TestZkConf", res)
}

func TestCloseNormal(t *testing.T) {
	zkServer := zktest.New()
	zkServer.SetErrorCheck(func(s string) error {
		return errors.New("nope")
	})

	z, err := Zk(ZkConnectorFunc(func() (ZkConn, <-chan zk.Event, error) {
		return zkServer.Connect()
	}), nil)
	assert.NoError(t, err)

	z.Close()

	// Should not deadlock
	<-z.(*zkConfig).shouldQuit
}

func TestCallbackMap(t *testing.T) {
	Convey("when callback map is made", t, func() {
		m := callbackMap{
			callbacks: map[string][]backingCallbackFunction{},
		}
		Convey("Unknown key gets should return empty list", func() {
			l := m.get("unknown")
			So(l, ShouldNotBeNil)
			So(len(l), ShouldEqual, 0)
		})
	})
}

func TestErrorReregister(t *testing.T) {
	zkServer := zktest.New()
	zkServer.ChanTimeout = time.Millisecond

	z, err := Zk(ZkConnectorFunc(func() (ZkConn, <-chan zk.Event, error) {
		return zkServer.Connect()
	}), nil)
	assert.NoError(t, err)
	defer z.Close()
	log.IfErr(log.Panic, z.(Dynamic).Watch("hello", func(string) {

	}))
	zkServer.SetErrorCheck(func(s string) error {
		return errors.New("nope")
	})
	z.(*zkConfig).setRefreshDelay(time.Millisecond)
	go func() {
		time.Sleep(time.Millisecond * 10)
		zkServer.SetErrorCheck(nil)
	}()
	z.(*zkConfig).refreshWatches(DefaultLogger)
}

func TestCloseQuitChan(t *testing.T) {
	zkServer := zktest.New()
	zkServer.SetErrorCheck(func(s string) error {
		return errors.New("nope")
	})

	z, err := Zk(ZkConnectorFunc(func() (ZkConn, <-chan zk.Event, error) {
		return zkServer.Connect()
	}), nil)
	assert.NoError(t, err)

	// Should not deadlock
	close(z.(*zkConfig).shouldQuit)

	// Give drain() loop time to exit, for code coverage
	time.Sleep(time.Millisecond * 100)
}

func TestZkConfErrors(t *testing.T) {
	zkServer := zktest.New()
	zkServer.SetErrorCheck(func(s string) error {
		return errors.New("nope")
	})
	zkServer.ChanTimeout = time.Millisecond * 10

	z, err := Zk(ZkConnectorFunc(func() (ZkConn, <-chan zk.Event, error) {
		return zkServer.Connect()
	}), nil)
	defer z.Close()
	assert.NoError(t, err)

	_, err = z.Get("TestZkConfErrors")
	assert.Error(t, err)

	assert.Error(t, z.(Dynamic).Watch("TestZkConfErrors", nil))
	assert.Error(t, z.Write("TestZkConfErrors", nil))
	assert.Error(t, z.(*zkConfig).reregisterWatch("TestZkConfErrors", DefaultLogger))

	z.(*zkConfig).conn.Close()

	//	zkp.GlobalChan <- zk.Event{
	//		State: zk.StateDisconnected,
	//	}
	//	// Let the thread switch back to get code coverage
	time.Sleep(10 * time.Millisecond)
	//	zkp.Close()
}

func TestErrorLoader(t *testing.T) {
	_, err := Zk(ZkConnectorFunc(func() (ZkConn, <-chan zk.Event, error) {
		return nil, nil, errors.New("nope")
	}), nil)
	assert.Error(t, err)
}
<commit_msg>Fix test timeout<commit_after>package distconf

import (
	"errors"
	"testing"
	"time"

	"github.com/samuel/go-zookeeper/zk"
	"github.com/signalfx/golib/log"
	"github.com/signalfx/golib/zkplus/zktest"
	. "github.com/smartystreets/goconvey/convey"
	"github.com/stretchr/testify/assert"
)

func TestZkNewlyCreated(t *testing.T) {
	zkServer := zktest.New()
	z, err := Zk(ZkConnectorFunc(func() (ZkConn, <-chan zk.Event, error) {
		return zkServer.Connect()
	}), &ZkConfig{})
	defer z.Close()
	assert.NoError(t, err)
	seenCallbacks := make(chan string, 2)
	callback := func(s string) {
		seenCallbacks <- s
	}
	log.IfErr(log.Panic, z.(Dynamic).Watch("hello", callback))
	assert.Equal(t, 0, len(seenCallbacks))
	assert.Nil(t, z.Write("hello", []byte("test")))
	seenString := <-seenCallbacks
	assert.Equal(t, "hello", seenString)
}

func TestZkConf(t *testing.T) {
	DefaultLogger.Log("TestZkConf")
	zkServer := zktest.New()
	z, err := Zk(ZkConnectorFunc(func() (ZkConn, <-chan zk.Event, error) {
		return zkServer.Connect()
	}), &ZkConfig{})
	defer z.Close()
	assert.NoError(t, err)

	b, err := z.Get("TestZkConf")
	assert.NoError(t, err)
	assert.Nil(t, b)

	assert.NoError(t, z.Write("TestZkConf", nil))

	signalChan := make(chan string, 4)
	DefaultLogger.Log("Setting watches")
	log.IfErr(log.Panic, z.(Dynamic).Watch("TestZkConf", backingCallbackFunction(func(S string) {
		DefaultLogger.Log("Watch fired!")
		assert.Equal(t, "TestZkConf", S)
		signalChan <- S
	})))

	// The write should work and I should get a single signal on the chan
	DefaultLogger.Log("Doing write 1")
	assert.NoError(t, z.Write("TestZkConf", []byte("newval")))
	DefaultLogger.Log("Write done")
	b, err = z.Get("TestZkConf")
	DefaultLogger.Log("Get done")
	assert.NoError(t, err)
	assert.Equal(t, []byte("newval"), b)
	DefaultLogger.Log("Blocking for values")
	res := <-signalChan
	assert.Equal(t, "TestZkConf", res)

	// Should send another signal
	DefaultLogger.Log("Doing write 2")
	assert.NoError(t, z.Write("TestZkConf", []byte("newval_v2")))
	res = <-signalChan
	assert.Equal(t, "TestZkConf", res)

	DefaultLogger.Log("Doing write 3")
	assert.NoError(t, z.Write("TestZkConf", nil))
	select {
	case res = <-signalChan:
	case <- time.After(time.Second * 3):
	}
	assert.Equal(t, "TestZkConf", res)
}

func TestCloseNormal(t *testing.T) {
	zkServer := zktest.New()
	zkServer.SetErrorCheck(func(s string) error {
		return errors.New("nope")
	})

	z, err := Zk(ZkConnectorFunc(func() (ZkConn, <-chan zk.Event, error) {
		return zkServer.Connect()
	}), nil)
	assert.NoError(t, err)

	z.Close()

	// Should not deadlock
	<-z.(*zkConfig).shouldQuit
}

func TestCallbackMap(t *testing.T) {
	Convey("when callback map is made", t, func() {
		m := callbackMap{
			callbacks: map[string][]backingCallbackFunction{},
		}
		Convey("Unknown key gets should return empty list", func() {
			l := m.get("unknown")
			So(l, ShouldNotBeNil)
			So(len(l), ShouldEqual, 0)
		})
	})
}

func TestErrorReregister(t *testing.T) {
	zkServer := zktest.New()
	zkServer.ChanTimeout = time.Millisecond

	z, err := Zk(ZkConnectorFunc(func() (ZkConn, <-chan zk.Event, error) {
		return zkServer.Connect()
	}), nil)
	assert.NoError(t, err)
	defer z.Close()
	log.IfErr(log.Panic, z.(Dynamic).Watch("hello", func(string) {

	}))
	zkServer.SetErrorCheck(func(s string) error {
		return errors.New("nope")
	})
	z.(*zkConfig).setRefreshDelay(time.Millisecond)
	go func() {
		time.Sleep(time.Millisecond * 10)
		zkServer.SetErrorCheck(nil)
	}()
	z.(*zkConfig).refreshWatches(DefaultLogger)
}

func TestCloseQuitChan(t *testing.T) {
	zkServer := zktest.New()
	zkServer.SetErrorCheck(func(s string) error {
		return errors.New("nope")
	})

	z, err := Zk(ZkConnectorFunc(func() (ZkConn, <-chan zk.Event, error) {
		return zkServer.Connect()
	}), nil)
	assert.NoError(t, err)

	// Should not deadlock
	close(z.(*zkConfig).shouldQuit)

	// Give drain() loop time to exit, for code coverage
	time.Sleep(time.Millisecond * 100)
}

func TestZkConfErrors(t *testing.T) {
	zkServer := zktest.New()
	zkServer.SetErrorCheck(func(s string) error {
		return errors.New("nope")
	})
	zkServer.ChanTimeout = time.Millisecond * 10

	z, err := Zk(ZkConnectorFunc(func() (ZkConn, <-chan zk.Event, error) {
		return zkServer.Connect()
	}), nil)
	defer z.Close()
	assert.NoError(t, err)

	_, err = z.Get("TestZkConfErrors")
	assert.Error(t, err)

	assert.Error(t, z.(Dynamic).Watch("TestZkConfErrors", nil))
	assert.Error(t, z.Write("TestZkConfErrors", nil))
	assert.Error(t, z.(*zkConfig).reregisterWatch("TestZkConfErrors", DefaultLogger))

	z.(*zkConfig).conn.Close()

	//	zkp.GlobalChan <- zk.Event{
	//		State: zk.StateDisconnected,
	//	}
	//	// Let the thread switch back to get code coverage
	time.Sleep(10 * time.Millisecond)
	//	zkp.Close()
}

func TestErrorLoader(t *testing.T) {
	_, err := Zk(ZkConnectorFunc(func() (ZkConn, <-chan zk.Event, error) {
		return nil, nil, errors.New("nope")
	}), nil)
	assert.Error(t, err)
}
<|endoftext|>