<commit_before>package peer

import (
	"github.com/bmizerany/assert"
	"github.com/ha/doozer"
	"github.com/ha/doozerd/store"
	"os/exec"

	"testing"
)

func TestDoozerNop(t *testing.T) {
	l := mustListen()
	defer l.Close()
	u := mustListenUDP(l.Addr().String())
	defer u.Close()

	go Main("a", "X", "", "", "", nil, u, l, nil, 1e9, 2e9, 3e9, 101)

	cl := dial(l.Addr().String())
	err := cl.Nop()
	assert.Equal(t, nil, err)
}

func TestDoozerGet(t *testing.T) {
	l := mustListen()
	defer l.Close()
	u := mustListenUDP(l.Addr().String())
	defer u.Close()

	go Main("a", "X", "", "", "", nil, u, l, nil, 1e9, 2e9, 3e9, 101)

	cl := dial(l.Addr().String())

	_, err := cl.Set("/x", store.Missing, []byte{'a'})
	assert.Equal(t, nil, err)

	ents, rev, err := cl.Get("/x", nil)
	assert.Equal(t, nil, err)
	assert.NotEqual(t, store.Dir, rev)
	assert.Equal(t, []byte{'a'}, ents)

	//cl.Set("/test/a", store.Missing, []byte{'1'})
	//cl.Set("/test/b", store.Missing, []byte{'2'})
	//cl.Set("/test/c", store.Missing, []byte{'3'})

	//ents, rev, err = cl.Get("/test", 0)
	//sort.SortStrings(ents)
	//assert.Equal(t, store.Dir, rev)
	//assert.Equal(t, nil, err)
	//assert.Equal(t, []string{"a", "b", "c"}, ents)
}

func TestDoozerSet(t *testing.T) {
	l := mustListen()
	defer l.Close()
	u := mustListenUDP(l.Addr().String())
	defer u.Close()

	go Main("a", "X", "", "", "", nil, u, l, nil, 1e9, 2e9, 3e9, 101)

	cl := dial(l.Addr().String())

	for i := byte(0); i < 10; i++ {
		_, err := cl.Set("/x", store.Clobber, []byte{'0' + i})
		assert.Equal(t, nil, err)
	}

	_, err := cl.Set("/x", 0, []byte{'X'})
	assert.Equal(t, &doozer.Error{doozer.ErrOldRev, ""}, err)
}

func TestDoozerGetWithRev(t *testing.T) {
	l := mustListen()
	defer l.Close()
	u := mustListenUDP(l.Addr().String())
	defer u.Close()

	go Main("a", "X", "", "", "", nil, u, l, nil, 1e9, 2e9, 3e9, 101)

	cl := dial(l.Addr().String())

	rev1, err := cl.Set("/x", store.Missing, []byte{'a'})
	assert.Equal(t, nil, err)

	v, rev, err := cl.Get("/x", &rev1) // Use the snapshot.
	assert.Equal(t, nil, err)
	assert.Equal(t, rev1, rev)
	assert.Equal(t, []byte{'a'}, v)

	rev2, err := cl.Set("/x", rev, []byte{'b'})
	assert.Equal(t, nil, err)

	v, rev, err = cl.Get("/x", nil) // Read the new value.
	assert.Equal(t, nil, err)
	assert.Equal(t, rev2, rev)
	assert.Equal(t, []byte{'b'}, v)

	v, rev, err = cl.Get("/x", &rev1) // Read the saved value again.
	assert.Equal(t, nil, err)
	assert.Equal(t, rev1, rev)
	assert.Equal(t, []byte{'a'}, v)
}

func TestDoozerWaitSimple(t *testing.T) {
	l := mustListen()
	defer l.Close()
	u := mustListenUDP(l.Addr().String())
	defer u.Close()

	go Main("a", "X", "", "", "", nil, u, l, nil, 1e9, 2e9, 3e9, 101)

	cl := dial(l.Addr().String())
	var rev int64 = 1

	cl.Set("/test/foo", store.Clobber, []byte("bar"))
	ev, err := cl.Wait("/test/**", rev)
	assert.Equal(t, nil, err)
	assert.Equal(t, "/test/foo", ev.Path)
	assert.Equal(t, []byte("bar"), ev.Body)
	assert.T(t, ev.IsSet())
	rev = ev.Rev + 1

	cl.Set("/test/fun", store.Clobber, []byte("house"))
	ev, err = cl.Wait("/test/**", rev)
	assert.Equal(t, nil, err)
	assert.Equal(t, "/test/fun", ev.Path)
	assert.Equal(t, []byte("house"), ev.Body)
	assert.T(t, ev.IsSet())
	rev = ev.Rev + 1

	cl.Del("/test/foo", store.Clobber)
	ev, err = cl.Wait("/test/**", rev)
	assert.Equal(t, nil, err)
	assert.Equal(t, "/test/foo", ev.Path)
	assert.T(t, ev.IsDel())
}

func TestDoozerWaitWithRev(t *testing.T) {
	l := mustListen()
	defer l.Close()
	u := mustListenUDP(l.Addr().String())
	defer u.Close()

	go Main("a", "X", "", "", "", nil, u, l, nil, 1e9, 2e9, 3e9, 101)

	cl := dial(l.Addr().String())

	// Create some history
	cl.Set("/test/foo", store.Clobber, []byte("bar"))
	cl.Set("/test/fun", store.Clobber, []byte("house"))

	ev, err := cl.Wait("/test/**", 1)
	assert.Equal(t, nil, err)
	assert.Equal(t, "/test/foo", ev.Path)
	assert.Equal(t, []byte("bar"), ev.Body)
	assert.T(t, ev.IsSet())
	rev := ev.Rev + 1

	ev, err = cl.Wait("/test/**", rev)
	assert.Equal(t, nil, err)
	assert.Equal(t, "/test/fun", ev.Path)
	assert.Equal(t, []byte("house"), ev.Body)
	assert.T(t, ev.IsSet())
}

func TestDoozerStat(t *testing.T) {
	l := mustListen()
	defer l.Close()
	u := mustListenUDP(l.Addr().String())
	defer u.Close()

	go Main("a", "X", "", "", "", nil, u, l, nil, 1e9, 2e9, 3e9, 101)

	cl := dial(l.Addr().String())

	cl.Set("/test/foo", store.Clobber, []byte("bar"))
	setRev, _ := cl.Set("/test/fun", store.Clobber, []byte("house"))

	ln, rev, err := cl.Stat("/test", nil)
	assert.Equal(t, nil, err)
	assert.Equal(t, store.Dir, rev)
	assert.Equal(t, int(2), ln)

	ln, rev, err = cl.Stat("/test/fun", nil)
	assert.Equal(t, nil, err)
	assert.Equal(t, setRev, rev)
	assert.Equal(t, int(5), ln)
}

func TestDoozerGetdirOnDir(t *testing.T) {
	l := mustListen()
	defer l.Close()
	u := mustListenUDP(l.Addr().String())
	defer u.Close()

	go Main("a", "X", "", "", "", nil, u, l, nil, 1e9, 2e9, 3e9, 101)

	cl := dial(l.Addr().String())

	cl.Set("/test/a", store.Clobber, []byte("1"))
	cl.Set("/test/b", store.Clobber, []byte("2"))
	cl.Set("/test/c", store.Clobber, []byte("3"))

	rev, err := cl.Rev()
	if err != nil {
		panic(err)
	}

	got, err := cl.Getdir("/test", rev, 0, -1)
	assert.Equal(t, nil, err)
	assert.Equal(t, []string{"a", "b", "c"}, got)
}

func TestDoozerGetdirOnFile(t *testing.T) {
	l := mustListen()
	defer l.Close()
	u := mustListenUDP(l.Addr().String())
	defer u.Close()

	go Main("a", "X", "", "", "", nil, u, l, nil, 1e9, 2e9, 3e9, 101)

	cl := dial(l.Addr().String())

	cl.Set("/test/a", store.Clobber, []byte("1"))

	rev, err := cl.Rev()
	if err != nil {
		panic(err)
	}

	names, err := cl.Getdir("/test/a", rev, 0, -1)
	assert.Equal(t, &doozer.Error{doozer.ErrNotDir, ""}, err)
	assert.Equal(t, []string(nil), names)
}

func TestDoozerGetdirMissing(t *testing.T) {
	l := mustListen()
	defer l.Close()
	u := mustListenUDP(l.Addr().String())
	defer u.Close()

	go Main("a", "X", "", "", "", nil, u, l, nil, 1e9, 2e9, 3e9, 101)

	cl := dial(l.Addr().String())

	rev, err := cl.Rev()
	if err != nil {
		panic(err)
	}

	names, err := cl.Getdir("/not/here", rev, 0, -1)
	assert.Equal(t, &doozer.Error{doozer.ErrNoEnt, ""}, err)
	assert.Equal(t, []string(nil), names)
}

func TestDoozerGetdirOffsetLimit(t *testing.T) {
	l := mustListen()
	defer l.Close()
	u := mustListenUDP(l.Addr().String())
	defer u.Close()

	go Main("a", "X", "", "", "", nil, u, l, nil, 1e9, 2e9, 3e9, 101)

	cl := dial(l.Addr().String())
	cl.Set("/test/a", store.Clobber, []byte("1"))
	cl.Set("/test/b", store.Clobber, []byte("2"))
	cl.Set("/test/c", store.Clobber, []byte("3"))
	cl.Set("/test/d", store.Clobber, []byte("4"))

	rev, err := cl.Rev()
	if err != nil {
		panic(err)
	}

	names, err := cl.Getdir("/test", rev, 1, 2)
	assert.Equal(t, nil, err)
	assert.Equal(t, []string{"b", "c"}, names)
}

func TestPeerShun(t *testing.T) {
	l0 := mustListen()
	defer l0.Close()
	a0 := l0.Addr().String()
	u0 := mustListenUDP(a0)
	defer u0.Close()

	l1 := mustListen()
	defer l1.Close()
	u1 := mustListenUDP(l1.Addr().String())
	defer u1.Close()
	l2 := mustListen()
	defer l2.Close()
	u2 := mustListenUDP(l2.Addr().String())
	defer u2.Close()

	go Main("a", "X", "", "", "", nil, u0, l0, nil, 1e8, 1e7, 1e9, 1e9)
	go Main("a", "Y", "", "", "", dial(a0), u1, l1, nil, 1e8, 1e7, 1e9, 1e9)
	go Main("a", "Z", "", "", "", dial(a0), u2, l2, nil, 1e8, 1e7, 1e9, 1e9)

	cl := dial(l0.Addr().String())
	cl.Set("/ctl/cal/1", store.Missing, nil)
	cl.Set("/ctl/cal/2", store.Missing, nil)

	waitFor(cl, "/ctl/node/X/writable")
	waitFor(cl, "/ctl/node/Y/writable")
	waitFor(cl, "/ctl/node/Z/writable")

	rev, err := cl.Set("/test", store.Clobber, nil)
	if e, ok := err.(*doozer.Error); ok && e.Err == doozer.ErrReadonly {
	} else if err != nil {
		panic(err)
	}

	u1.Close()
	for {
		ev, err := cl.Wait("/ctl/cal/*", rev)
		if err != nil {
			panic(err)
		}
		if ev.IsSet() && len(ev.Body) == 0 {
			break
		}
		rev = ev.Rev + 1
	}
}

func assertDenied(t *testing.T, err error) {
	assert.NotEqual(t, nil, err)
	assert.Equal(t, doozer.ErrOther, err.(*doozer.Error).Err)
	assert.Equal(t, "permission denied", err.(*doozer.Error).Detail)
}

func runDoozer(a ...string) *exec.Cmd {
	path := "/home/kr/src/go/bin/doozerd"
	args := append([]string{path}, a...)
	c := exec.Command(path, args...)
	if err := c.Run(); err != nil {
		panic(err)
	}
	return c
}
<commit_msg>add test for a peer that joins late<commit_after>package peer

import (
	"github.com/bmizerany/assert"
	"github.com/ha/doozer"
	"github.com/ha/doozerd/store"
	"os/exec"

	"testing"
	"time"
)

func TestDoozerNop(t *testing.T) {
	l := mustListen()
	defer l.Close()
	u := mustListenUDP(l.Addr().String())
	defer u.Close()

	go Main("a", "X", "", "", "", nil, u, l, nil, 1e9, 2e9, 3e9, 101)

	cl := dial(l.Addr().String())
	err := cl.Nop()
	assert.Equal(t, nil, err)
}

func TestDoozerGet(t *testing.T) {
	l := mustListen()
	defer l.Close()
	u := mustListenUDP(l.Addr().String())
	defer u.Close()

	go Main("a", "X", "", "", "", nil, u, l, nil, 1e9, 2e9, 3e9, 101)

	cl := dial(l.Addr().String())

	_, err := cl.Set("/x", store.Missing, []byte{'a'})
	assert.Equal(t, nil, err)

	ents, rev, err := cl.Get("/x", nil)
	assert.Equal(t, nil, err)
	assert.NotEqual(t, store.Dir, rev)
	assert.Equal(t, []byte{'a'}, ents)

	//cl.Set("/test/a", store.Missing, []byte{'1'})
	//cl.Set("/test/b", store.Missing, []byte{'2'})
	//cl.Set("/test/c", store.Missing, []byte{'3'})

	//ents, rev, err = cl.Get("/test", 0)
	//sort.SortStrings(ents)
	//assert.Equal(t, store.Dir, rev)
	//assert.Equal(t, nil, err)
	//assert.Equal(t, []string{"a", "b", "c"}, ents)
}

func TestDoozerSet(t *testing.T) {
	l := mustListen()
	defer l.Close()
	u := mustListenUDP(l.Addr().String())
	defer u.Close()

	go Main("a", "X", "", "", "", nil, u, l, nil, 1e9, 2e9, 3e9, 101)

	cl := dial(l.Addr().String())

	for i := byte(0); i < 10; i++ {
		_, err := cl.Set("/x", store.Clobber, []byte{'0' + i})
		assert.Equal(t, nil, err)
	}

	_, err := cl.Set("/x", 0, []byte{'X'})
	assert.Equal(t, &doozer.Error{doozer.ErrOldRev, ""}, err)
}

func TestDoozerGetWithRev(t *testing.T) {
	l := mustListen()
	defer l.Close()
	u := mustListenUDP(l.Addr().String())
	defer u.Close()

	go Main("a", "X", "", "", "", nil, u, l, nil, 1e9, 2e9, 3e9, 101)

	cl := dial(l.Addr().String())

	rev1, err := cl.Set("/x", store.Missing, []byte{'a'})
	assert.Equal(t, nil, err)

	v, rev, err := cl.Get("/x", &rev1) // Use the snapshot.
	assert.Equal(t, nil, err)
	assert.Equal(t, rev1, rev)
	assert.Equal(t, []byte{'a'}, v)

	rev2, err := cl.Set("/x", rev, []byte{'b'})
	assert.Equal(t, nil, err)

	v, rev, err = cl.Get("/x", nil) // Read the new value.
	assert.Equal(t, nil, err)
	assert.Equal(t, rev2, rev)
	assert.Equal(t, []byte{'b'}, v)

	v, rev, err = cl.Get("/x", &rev1) // Read the saved value again.
	assert.Equal(t, nil, err)
	assert.Equal(t, rev1, rev)
	assert.Equal(t, []byte{'a'}, v)
}

func TestDoozerWaitSimple(t *testing.T) {
	l := mustListen()
	defer l.Close()
	u := mustListenUDP(l.Addr().String())
	defer u.Close()

	go Main("a", "X", "", "", "", nil, u, l, nil, 1e9, 2e9, 3e9, 101)

	cl := dial(l.Addr().String())
	var rev int64 = 1

	cl.Set("/test/foo", store.Clobber, []byte("bar"))
	ev, err := cl.Wait("/test/**", rev)
	assert.Equal(t, nil, err)
	assert.Equal(t, "/test/foo", ev.Path)
	assert.Equal(t, []byte("bar"), ev.Body)
	assert.T(t, ev.IsSet())
	rev = ev.Rev + 1

	cl.Set("/test/fun", store.Clobber, []byte("house"))
	ev, err = cl.Wait("/test/**", rev)
	assert.Equal(t, nil, err)
	assert.Equal(t, "/test/fun", ev.Path)
	assert.Equal(t, []byte("house"), ev.Body)
	assert.T(t, ev.IsSet())
	rev = ev.Rev + 1

	cl.Del("/test/foo", store.Clobber)
	ev, err = cl.Wait("/test/**", rev)
	assert.Equal(t, nil, err)
	assert.Equal(t, "/test/foo", ev.Path)
	assert.T(t, ev.IsDel())
}

func TestDoozerWaitWithRev(t *testing.T) {
	l := mustListen()
	defer l.Close()
	u := mustListenUDP(l.Addr().String())
	defer u.Close()

	go Main("a", "X", "", "", "", nil, u, l, nil, 1e9, 2e9, 3e9, 101)

	cl := dial(l.Addr().String())

	// Create some history
	cl.Set("/test/foo", store.Clobber, []byte("bar"))
	cl.Set("/test/fun", store.Clobber, []byte("house"))

	ev, err := cl.Wait("/test/**", 1)
	assert.Equal(t, nil, err)
	assert.Equal(t, "/test/foo", ev.Path)
	assert.Equal(t, []byte("bar"), ev.Body)
	assert.T(t, ev.IsSet())
	rev := ev.Rev + 1

	ev, err = cl.Wait("/test/**", rev)
	assert.Equal(t, nil, err)
	assert.Equal(t, "/test/fun", ev.Path)
	assert.Equal(t, []byte("house"), ev.Body)
	assert.T(t, ev.IsSet())
}

func TestDoozerStat(t *testing.T) {
	l := mustListen()
	defer l.Close()
	u := mustListenUDP(l.Addr().String())
	defer u.Close()

	go Main("a", "X", "", "", "", nil, u, l, nil, 1e9, 2e9, 3e9, 101)

	cl := dial(l.Addr().String())

	cl.Set("/test/foo", store.Clobber, []byte("bar"))
	setRev, _ := cl.Set("/test/fun", store.Clobber, []byte("house"))

	ln, rev, err := cl.Stat("/test", nil)
	assert.Equal(t, nil, err)
	assert.Equal(t, store.Dir, rev)
	assert.Equal(t, int(2), ln)

	ln, rev, err = cl.Stat("/test/fun", nil)
	assert.Equal(t, nil, err)
	assert.Equal(t, setRev, rev)
	assert.Equal(t, int(5), ln)
}

func TestDoozerGetdirOnDir(t *testing.T) {
	l := mustListen()
	defer l.Close()
	u := mustListenUDP(l.Addr().String())
	defer u.Close()

	go Main("a", "X", "", "", "", nil, u, l, nil, 1e9, 2e9, 3e9, 101)

	cl := dial(l.Addr().String())

	cl.Set("/test/a", store.Clobber, []byte("1"))
	cl.Set("/test/b", store.Clobber, []byte("2"))
	cl.Set("/test/c", store.Clobber, []byte("3"))

	rev, err := cl.Rev()
	if err != nil {
		panic(err)
	}

	got, err := cl.Getdir("/test", rev, 0, -1)
	assert.Equal(t, nil, err)
	assert.Equal(t, []string{"a", "b", "c"}, got)
}

func TestDoozerGetdirOnFile(t *testing.T) {
	l := mustListen()
	defer l.Close()
	u := mustListenUDP(l.Addr().String())
	defer u.Close()

	go Main("a", "X", "", "", "", nil, u, l, nil, 1e9, 2e9, 3e9, 101)

	cl := dial(l.Addr().String())

	cl.Set("/test/a", store.Clobber, []byte("1"))

	rev, err := cl.Rev()
	if err != nil {
		panic(err)
	}

	names, err := cl.Getdir("/test/a", rev, 0, -1)
	assert.Equal(t, &doozer.Error{doozer.ErrNotDir, ""}, err)
	assert.Equal(t, []string(nil), names)
}

func TestDoozerGetdirMissing(t *testing.T) {
	l := mustListen()
	defer l.Close()
	u := mustListenUDP(l.Addr().String())
	defer u.Close()

	go Main("a", "X", "", "", "", nil, u, l, nil, 1e9, 2e9, 3e9, 101)

	cl := dial(l.Addr().String())

	rev, err := cl.Rev()
	if err != nil {
		panic(err)
	}

	names, err := cl.Getdir("/not/here", rev, 0, -1)
	assert.Equal(t, &doozer.Error{doozer.ErrNoEnt, ""}, err)
	assert.Equal(t, []string(nil), names)
}

func TestDoozerGetdirOffsetLimit(t *testing.T) {
	l := mustListen()
	defer l.Close()
	u := mustListenUDP(l.Addr().String())
	defer u.Close()

	go Main("a", "X", "", "", "", nil, u, l, nil, 1e9, 2e9, 3e9, 101)

	cl := dial(l.Addr().String())
	cl.Set("/test/a", store.Clobber, []byte("1"))
	cl.Set("/test/b", store.Clobber, []byte("2"))
	cl.Set("/test/c", store.Clobber, []byte("3"))
	cl.Set("/test/d", store.Clobber, []byte("4"))

	rev, err := cl.Rev()
	if err != nil {
		panic(err)
	}

	names, err := cl.Getdir("/test", rev, 1, 2)
	assert.Equal(t, nil, err)
	assert.Equal(t, []string{"b", "c"}, names)
}

func TestPeerShun(t *testing.T) {
	l0 := mustListen()
	defer l0.Close()
	a0 := l0.Addr().String()
	u0 := mustListenUDP(a0)
	defer u0.Close()

	l1 := mustListen()
	defer l1.Close()
	u1 := mustListenUDP(l1.Addr().String())
	defer u1.Close()
	l2 := mustListen()
	defer l2.Close()
	u2 := mustListenUDP(l2.Addr().String())
	defer u2.Close()

	go Main("a", "X", "", "", "", nil, u0, l0, nil, 1e8, 1e7, 1e9, 1e9)
	go Main("a", "Y", "", "", "", dial(a0), u1, l1, nil, 1e8, 1e7, 1e9, 1e9)
	go Main("a", "Z", "", "", "", dial(a0), u2, l2, nil, 1e8, 1e7, 1e9, 1e9)

	cl := dial(l0.Addr().String())
	cl.Set("/ctl/cal/1", store.Missing, nil)
	cl.Set("/ctl/cal/2", store.Missing, nil)

	waitFor(cl, "/ctl/node/X/writable")
	waitFor(cl, "/ctl/node/Y/writable")
	waitFor(cl, "/ctl/node/Z/writable")

	rev, err := cl.Set("/test", store.Clobber, nil)
	if e, ok := err.(*doozer.Error); ok && e.Err == doozer.ErrReadonly {
	} else if err != nil {
		panic(err)
	}

	u1.Close()
	for {
		ev, err := cl.Wait("/ctl/cal/*", rev)
		if err != nil {
			panic(err)
		}
		if ev.IsSet() && len(ev.Body) == 0 {
			break
		}
		rev = ev.Rev + 1
	}
}

func TestPeerLateJoin(t *testing.T) {
	l0 := mustListen()
	defer l0.Close()
	a0 := l0.Addr().String()
	u0 := mustListenUDP(a0)
	defer u0.Close()

	l1 := mustListen()
	defer l1.Close()
	u1 := mustListenUDP(l1.Addr().String())
	defer u1.Close()

	go Main("a", "X", "", "", "", nil, u0, l0, nil, 1e8, 1e7, 1e9, 60)

	cl := dial(l0.Addr().String())
	waitFor(cl, "/ctl/node/X/writable")

	// TODO: this is set slightly higher than the hardcoded interval
	// at which a store is cleaned.  Refactor that to be configurable
	// so we can drop this down to something reasonable
	time.Sleep(1100 * time.Millisecond)

	go Main("a", "Y", "", "", "", dial(a0), u1, l1, nil, 1e8, 1e7, 1e9, 60)
	rev, _ := cl.Set("/ctl/cal/1", store.Missing, nil)
	for {
		ev, err := cl.Wait("/ctl/node/Y/writable", rev)
		if err != nil {
			panic(err)
		}
		if ev.IsSet() && len(ev.Body) == 4 {
			break
		}
		rev = ev.Rev + 1
	}
}

func assertDenied(t *testing.T, err error) {
	assert.NotEqual(t, nil, err)
	assert.Equal(t, doozer.ErrOther, err.(*doozer.Error).Err)
	assert.Equal(t, "permission denied", err.(*doozer.Error).Detail)
}

func runDoozer(a ...string) *exec.Cmd {
	path := "/home/kr/src/go/bin/doozerd"
	args := append([]string{path}, a...)
	c := exec.Command(path, args...)
	if err := c.Run(); err != nil {
		panic(err)
	}
	return c
}
<|endoftext|>


<commit_before>// Package ishell implements an interactive shell.
package ishell

import (
	"bytes"
	"fmt"
	"io"
	"log"
	"os"
	"os/exec"
	"runtime"
	"sort"
	"strings"
	"sync"

	"github.com/flynn/go-shlex"
	"gopkg.in/readline.v1"
)

const (
	defaultPrompt     = ">>> "
	defaultNextPrompt = "... "
)

type Shell struct {
	functions   map[string]CmdFunc
	generic     CmdFunc
	reader      *shellReader
	writer      io.Writer
	active      bool
	activeMutex sync.RWMutex
	ignoreCase  bool
	haltChan    chan struct{}
	historyFile string
}

// New creates a new shell with default settings. Uses standard output and default prompt ">> ".
func New() *Shell {
	rl, err := readline.New(defaultPrompt)
	if err != nil {
		log.Println("Shell or operating system not supported.")
		log.Fatal(err)
	}
	shell := &Shell{
		functions: make(map[string]CmdFunc),
		reader: &shellReader{
			scanner:     rl,
			prompt:      defaultPrompt,
			multiPrompt: defaultNextPrompt,
			showPrompt:  true,
			buf:         bytes.NewBuffer(nil),
			completer:   readline.NewPrefixCompleter(),
		},
		writer:   os.Stdout,
		haltChan: make(chan struct{}),
	}
	addDefaultFuncs(shell)
	return shell
}

// Start starts the shell. It reads inputs from standard input and calls registered functions
// accordingly. This function blocks until the shell is stopped.
func (s *Shell) Start() {
	s.start()
}

func (s *Shell) start() {
	if s.Active() {
		return
	}
	s.activeMutex.Lock()
	s.active = true
	s.activeMutex.Unlock()

shell:
	for s.Active() {
		var line []string
		var err error
		read := make(chan struct{})
		go func() {
			line, err = s.read()
			read <- struct{}{}
		}()
		select {
		case <-read:
			break
		case <-s.haltChan:
			continue shell
		}
		if err == io.EOF {
			fmt.Println("EOF")
			break
		} else if err != nil {
			s.Println("Error:", err)
			break
		}

		if len(line) == 0 {
			continue
		}

		err = handleInput(s, line)
		if err1, ok := err.(shellError); ok && err != nil {
			switch err1.level {
			case LevelWarn:
				s.Println("Warning:", err)
				continue shell
			case LevelStop:
				s.Println(err)
				break shell
			case LevelExit:
				s.Println(err)
				os.Exit(1)
			case LevelPanic:
				panic(err)
			}
		} else if !ok && err != nil {
			s.Println("Error:", err)
		}
	}
}

// Active tells if the shell is active. i.e. Start is previously called.
func (s *Shell) Active() bool {
	s.activeMutex.RLock()
	defer s.activeMutex.RUnlock()
	return s.active
}

func handleInput(s *Shell, line []string) error {
	handled, err := s.handleCommand(line)
	if handled || err != nil {
		return err
	}

	// Generic handler
	if s.generic == nil {
		return errNoHandler
	}
	output, err := s.generic(line...)
	if err != nil {
		return err
	}
	if output != "" {
		s.Println(output)
	}
	return nil
}

func (s *Shell) handleCommand(str []string) (bool, error) {
	//	str := strings.SplitN(line, " ", 2)
	cmd := str[0]
	if s.ignoreCase {
		cmd = strings.ToLower(cmd)
	}
	if _, ok := s.functions[cmd]; !ok {
		return false, nil
	}
	output, err := s.functions[cmd](str[1:]...)
	if err != nil {
		return true, err
	}
	if output != "" {
		s.Println(output)
	}
	return true, nil
}

// Stop stops the shell. This will stop the shell from auto reading inputs and calling
// registered functions. A stopped shell is only inactive but totally functional.
// Its functions can still be called.
func (s *Shell) Stop() {
	s.reader.scanner.Close()
	if !s.Active() {
		return
	}
	s.activeMutex.Lock()
	s.active = false
	s.activeMutex.Unlock()
	go func() {
		s.haltChan <- struct{}{}
	}()
}

// ReadLine reads a line from standard input.
func (s *Shell) ReadLine() string {
	line, _ := s.readLine()
	return line
}

func (s *Shell) readLine() (line string, err error) {
	consumer := make(chan lineString)
	s.reader.readLine(consumer)
	ls := <-consumer
	return ls.line, ls.err
}

func (s *Shell) read() ([]string, error) {
	heredoc := false
	eof := ""
	// heredoc multiline
	lines, err := s.readMultiLinesFunc(func(line string) bool {
		if !heredoc {
			if strings.Contains(line, "<<") {
				s := strings.SplitN(line, "<<", 2)
				if eof = strings.TrimSpace(s[1]); eof != "" {
					heredoc = true
					return true
				}
			}
		} else {
			return line != eof
		}
		return strings.HasSuffix(strings.TrimSpace(line), "\\")
	})

	if heredoc {
		s := strings.SplitN(lines, "<<", 2)
		args, err1 := shlex.Split(s[0])

		arg := strings.TrimSuffix(strings.SplitN(s[1], "\n", 2)[1], eof)
		args = append(args, arg)
		if err1 != nil {
			return args, err1
		}
		return args, err
	}

	lines = strings.Replace(lines, "\\\n", " \n", -1)

	args, err1 := shlex.Split(lines)
	if err1 != nil {
		return args, err1
	}

	return args, err
}

// ReadMultiLinesFunc reads multiple lines from standard input. It passes each read line to
// f and stops reading when f returns false.
func (s *Shell) ReadMultiLinesFunc(f func(string) bool) string {
	lines, _ := s.readMultiLinesFunc(f)
	return lines
}

func (s *Shell) readMultiLinesFunc(f func(string) bool) (string, error) {
	lines := bytes.NewBufferString("")
	currentLine := 0
	var err error
	for {
		if currentLine == 1 {
			// from second line, enable next line prompt.
			s.reader.setMultiMode(true)
		}
		var line string
		line, err = s.readLine()
		fmt.Fprint(lines, line)
		if !f(line) || err != nil {
			break
		}
		fmt.Fprintln(lines)
		currentLine++
	}
	if currentLine > 0 {
		// if more than one line is read
		// revert to standard prompt.
		s.reader.setMultiMode(false)
	}
	return lines.String(), err
}

// ReadMultiLines reads multiple lines from standard input. It stops reading when terminator
// is encountered at the end of the line. It returns the lines read including terminator.
// For more control, use ReadMultiLinesFunc.
func (s *Shell) ReadMultiLines(terminator string) string {
	return s.ReadMultiLinesFunc(func(line string) bool {
		if strings.HasSuffix(strings.TrimSpace(line), terminator) {
			return false
		}
		return true
	})
}

// ReadPassword reads password from standard input without echoing the characters.
// If mask is true, each character will be represented with asterisks '*'. Note that
// this only works as expected when the standard input is a terminal.
func (s *Shell) ReadPassword() string {
	return s.reader.readPassword()
}

// Println prints to output and ends with newline character.
func (s *Shell) Println(val ...interface{}) {
	s.reader.buf.Truncate(0)
	fmt.Fprintln(s.writer, val...)
}

// Print prints to output.
func (s *Shell) Print(val ...interface{}) {
	s.reader.buf.Truncate(0)
	fmt.Fprint(s.reader.buf, val...)
	fmt.Fprint(s.writer, val...)
}

// Register registers a function for command. It overwrites existing function, if any.
func (s *Shell) Register(command string, function CmdFunc) {
	s.functions[command] = function

	// readline library does not provide a better way
	// yet than to regenerate the AutoComplete
	// TODO modify when available
	var pcItems []*readline.PrefixCompleter
	for word, _ := range s.functions {
		pcItems = append(pcItems, readline.PcItem(word))
	}

	var err error
	// close current scanner and rebuild it with
	// command in autocomplete
	s.reader.scanner.Close()
	config := s.reader.scanner.Config
	config.AutoComplete = readline.NewPrefixCompleter(pcItems...)
	s.reader.scanner, err = readline.NewEx(config)
	if err != nil {
		log.Fatal(err)
	}
}

// Unregister unregisters a function for a command
func (s *Shell) Unregister(command string) {
	delete(s.functions, command)
}

// RegisterGeneric registers a generic function for all inputs.
// It is called if the shell input could not be handled by any of the
// registered functions. Unlike Register, the entire line is passed as
// first argument to CmdFunc.
func (s *Shell) RegisterGeneric(function CmdFunc) {
	s.generic = function
}

// SetPrompt sets the prompt string. The string to be displayed before the cursor.
func (s *Shell) SetPrompt(prompt string) {
	s.reader.prompt = prompt
	s.reader.scanner.SetPrompt(s.reader.rlPrompt())
}

// SetMultiPrompt sets the prompt string used for multiple lines. The string to be displayed before
// the cursor; starting from the second line of input.
func (s *Shell) SetMultiPrompt(prompt string) {
	s.reader.multiPrompt = prompt
}

// ShowPrompt sets whether prompt should show when requesting input for ReadLine and ReadPassword.
// Defaults to true.
func (s *Shell) ShowPrompt(show bool) {
	s.reader.showPrompt = show
	s.reader.scanner.SetPrompt(s.reader.rlPrompt())
}

// SetHistoryPath sets where readlines history file location. Use an empty
// string to disable history file. It is empty by default.
func (s *Shell) SetHistoryPath(path string) error {
	var err error

	// Using scanner.SetHistoryPath doesn't initialize things properly and
	// history file is never written. Simpler to just create a new readline
	// Instance.
	s.reader.scanner.Close()
	config := s.reader.scanner.Config
	config.HistoryFile = path
	s.reader.scanner, err = readline.NewEx(config)
	return err
}

// SetHomeHistoryPath is a convenience method that sets the history path with a
// $HOME prepended path.
func (s *Shell) SetHomeHistoryPath(path string) {
	home := os.Getenv("HOME")
	abspath := fmt.Sprintf("%s/%s", home, path)
	s.SetHistoryPath(abspath)
}

// SetOut sets the writer to write outputs to.
func (s *Shell) SetOut(writer io.Writer) {
	s.writer = writer
}

// PrintCommands prints a space separated list of registered commands to the shell.
func (s *Shell) PrintCommands() {
	out := strings.Join(s.Commands(), " ")
	if out != "" {
		s.Println("Commands:")
		s.Println(out)
	}
}

// Commands returns a sorted list of all registered commands.
func (s *Shell) Commands() []string {
	var commands []string
	for command := range s.functions {
		commands = append(commands, command)
	}
	sort.Strings(commands)
	return commands
}

// IgnoreCase specifies whether commands should not be case sensitive.
// Defaults to false i.e. commands are case sensitive.
// If true, commands must be registered in lower cases. e.g. shell.Register("cmd", ...)
func (s *Shell) IgnoreCase(ignore bool) {
	s.ignoreCase = ignore
}

// ClearScreen clears the screen. Same behaviour as running 'clear' in unix terminal or 'cls' in windows cmd.
func (s *Shell) ClearScreen() error {
	return clearScreen(s)
}

func clearScreen(s *Shell) error {
	cmd := exec.Command("clear")
	if runtime.GOOS == "windows" {
		cmd = exec.Command("cmd", "/C", "cls")
	}
	cmd.Stdout = s.writer
	return cmd.Run()
}
<commit_msg>Fixed the following error:<commit_after>// Package ishell implements an interactive shell.
package ishell

import (
	"bytes"
	"fmt"
	"io"
	"log"
	"os"
	"os/exec"
	"runtime"
	"sort"
	"strings"
	"sync"

	"github.com/flynn/go-shlex"
	"gopkg.in/readline.v1"
)

const (
	defaultPrompt     = ">>> "
	defaultNextPrompt = "... "
)

type Shell struct {
	functions   map[string]CmdFunc
	generic     CmdFunc
	reader      *shellReader
	writer      io.Writer
	active      bool
	activeMutex sync.RWMutex
	ignoreCase  bool
	haltChan    chan struct{}
	historyFile string
}

// New creates a new shell with default settings. Uses standard output and default prompt ">> ".
func New() *Shell {
	rl, err := readline.New(defaultPrompt)
	if err != nil {
		log.Println("Shell or operating system not supported.")
		log.Fatal(err)
	}
	shell := &Shell{
		functions: make(map[string]CmdFunc),
		reader: &shellReader{
			scanner:     rl,
			prompt:      defaultPrompt,
			multiPrompt: defaultNextPrompt,
			showPrompt:  true,
			buf:         bytes.NewBuffer(nil),
			completer:   readline.NewPrefixCompleter(),
		},
		writer:   os.Stdout,
		haltChan: make(chan struct{}),
	}
	addDefaultFuncs(shell)
	return shell
}

// Start starts the shell. It reads inputs from standard input and calls registered functions
// accordingly. This function blocks until the shell is stopped.
func (s *Shell) Start() {
	s.start()
}

func (s *Shell) start() {
	if s.Active() {
		return
	}
	s.activeMutex.Lock()
	s.active = true
	s.activeMutex.Unlock()

shell:
	for s.Active() {
		var line []string
		var err error
		read := make(chan struct{})
		go func() {
			line, err = s.read()
			read <- struct{}{}
		}()
		select {
		case <-read:
			break
		case <-s.haltChan:
			continue shell
		}
		if err == io.EOF {
			fmt.Println("EOF")
			break
		} else if err != nil {
			s.Println("Error:", err)
			break
		}

		if len(line) == 0 {
			continue
		}

		err = handleInput(s, line)
		if err1, ok := err.(shellError); ok && err != nil {
			switch err1.level {
			case LevelWarn:
				s.Println("Warning:", err)
				continue shell
			case LevelStop:
				s.Println(err)
				break shell
			case LevelExit:
				s.Println(err)
				os.Exit(1)
			case LevelPanic:
				panic(err)
			}
		} else if !ok && err != nil {
			s.Println("Error:", err)
		}
	}
}

// Active tells if the shell is active. i.e. Start is previously called.
func (s *Shell) Active() bool {
	s.activeMutex.RLock()
	defer s.activeMutex.RUnlock()
	return s.active
}

func handleInput(s *Shell, line []string) error {
	handled, err := s.handleCommand(line)
	if handled || err != nil {
		return err
	}

	// Generic handler
	if s.generic == nil {
		return errNoHandler
	}
	output, err := s.generic(line...)
	if err != nil {
		return err
	}
	if output != "" {
		s.Println(output)
	}
	return nil
}

func (s *Shell) handleCommand(str []string) (bool, error) {
	//	str := strings.SplitN(line, " ", 2)
	cmd := str[0]
	if s.ignoreCase {
		cmd = strings.ToLower(cmd)
	}
	if _, ok := s.functions[cmd]; !ok {
		return false, nil
	}
	output, err := s.functions[cmd](str[1:]...)
	if err != nil {
		return true, err
	}
	if output != "" {
		s.Println(output)
	}
	return true, nil
}

// Stop stops the shell. This will stop the shell from auto reading inputs and calling
// registered functions. A stopped shell is only inactive but totally functional.
// Its functions can still be called.
func (s *Shell) Stop() {
	s.reader.scanner.Close()
	if !s.Active() {
		return
	}
	s.activeMutex.Lock()
	s.active = false
	s.activeMutex.Unlock()
	go func() {
		s.haltChan <- struct{}{}
	}()
}

// ReadLine reads a line from standard input.
func (s *Shell) ReadLine() string {
	line, _ := s.readLine()
	return line
}

func (s *Shell) readLine() (line string, err error) {
	consumer := make(chan lineString)
	s.reader.readLine(consumer)
	ls := <-consumer
	return ls.line, ls.err
}

func (s *Shell) read() ([]string, error) {
	heredoc := false
	eof := ""
	// heredoc multiline
	lines, err := s.readMultiLinesFunc(func(line string) bool {
		if !heredoc {
			if strings.Contains(line, "<<") {
				s := strings.SplitN(line, "<<", 2)
				if eof = strings.TrimSpace(s[1]); eof != "" {
					heredoc = true
					return true
				}
			}
		} else {
			return line != eof
		}
		return strings.HasSuffix(strings.TrimSpace(line), "\\")
	})

	if heredoc {
		s := strings.SplitN(lines, "<<", 2)
		args, err1 := shlex.Split(s[0])

		arg := strings.TrimSuffix(strings.SplitN(s[1], "\n", 2)[1], eof)
		args = append(args, arg)
		if err1 != nil {
			return args, err1
		}
		return args, err
	}

	lines = strings.Replace(lines, "\\\n", " \n", -1)

	args, err1 := shlex.Split(lines)
	if err1 != nil {
		return args, err1
	}

	return args, err
}

// ReadMultiLinesFunc reads multiple lines from standard input. It passes each read line to
// f and stops reading when f returns false.
func (s *Shell) ReadMultiLinesFunc(f func(string) bool) string {
	lines, _ := s.readMultiLinesFunc(f)
	return lines
}

func (s *Shell) readMultiLinesFunc(f func(string) bool) (string, error) {
	lines := bytes.NewBufferString("")
	currentLine := 0
	var err error
	for {
		if currentLine == 1 {
			// from second line, enable next line prompt.
			s.reader.setMultiMode(true)
		}
		var line string
		line, err = s.readLine()
		fmt.Fprint(lines, line)
		if !f(line) || err != nil {
			break
		}
		fmt.Fprintln(lines)
		currentLine++
	}
	if currentLine > 0 {
		// if more than one line is read
		// revert to standard prompt.
		s.reader.setMultiMode(false)
	}
	return lines.String(), err
}

// ReadMultiLines reads multiple lines from standard input. It stops reading when terminator
// is encountered at the end of the line. It returns the lines read including terminator.
// For more control, use ReadMultiLinesFunc.
func (s *Shell) ReadMultiLines(terminator string) string {
	return s.ReadMultiLinesFunc(func(line string) bool {
		if strings.HasSuffix(strings.TrimSpace(line), terminator) {
			return false
		}
		return true
	})
}

// ReadPassword reads password from standard input without echoing the characters.
// If mask is true, each character will be represented with asterisks '*'. Note that
// this only works as expected when the standard input is a terminal.
func (s *Shell) ReadPassword() string {
	return s.reader.readPassword()
}

// Println prints to output and ends with newline character.
func (s *Shell) Println(val ...interface{}) {
	s.reader.buf.Truncate(0)
	fmt.Fprintln(s.writer, val...)
}

// Print prints to output.
func (s *Shell) Print(val ...interface{}) {
	s.reader.buf.Truncate(0)
	fmt.Fprint(s.reader.buf, val...)
	fmt.Fprint(s.writer, val...)
}

// Register registers a function for command. It overwrites existing function, if any.
func (s *Shell) Register(command string, function CmdFunc) {
	s.functions[command] = function

	// readline library does not provide a better way
	// yet than to regenerate the AutoComplete
	// TODO modify when available
	var pcItems []readline.PrefixCompleterInterface
	for word, _ := range s.functions {
		pcItems = append(pcItems, readline.PcItem(word))
	}

	var err error
	// close current scanner and rebuild it with
	// command in autocomplete
	s.reader.scanner.Close()
	config := s.reader.scanner.Config
	config.AutoComplete = readline.NewPrefixCompleter(pcItems...)
	s.reader.scanner, err = readline.NewEx(config)
	if err != nil {
		log.Fatal(err)
	}
}

// Unregister unregisters a function for a command
func (s *Shell) Unregister(command string) {
	delete(s.functions, command)
}

// RegisterGeneric registers a generic function for all inputs.
// It is called if the shell input could not be handled by any of the
// registered functions. Unlike Register, the entire line is passed as
// first argument to CmdFunc.
func (s *Shell) RegisterGeneric(function CmdFunc) {
	s.generic = function
}

// SetPrompt sets the prompt string. The string to be displayed before the cursor.
func (s *Shell) SetPrompt(prompt string) {
	s.reader.prompt = prompt
	s.reader.scanner.SetPrompt(s.reader.rlPrompt())
}

// SetMultiPrompt sets the prompt string used for multiple lines. The string to be displayed before
// the cursor; starting from the second line of input.
func (s *Shell) SetMultiPrompt(prompt string) {
	s.reader.multiPrompt = prompt
}

// ShowPrompt sets whether prompt should show when requesting input for ReadLine and ReadPassword.
// Defaults to true.
func (s *Shell) ShowPrompt(show bool) {
	s.reader.showPrompt = show
	s.reader.scanner.SetPrompt(s.reader.rlPrompt())
}

// SetHistoryPath sets where readlines history file location. Use an empty
// string to disable history file. It is empty by default.
func (s *Shell) SetHistoryPath(path string) error {
	var err error

	// Using scanner.SetHistoryPath doesn't initialize things properly and
	// history file is never written. Simpler to just create a new readline
	// Instance.
	s.reader.scanner.Close()
	config := s.reader.scanner.Config
	config.HistoryFile = path
	s.reader.scanner, err = readline.NewEx(config)
	return err
}

// SetHomeHistoryPath is a convenience method that sets the history path with a
// $HOME prepended path.
func (s *Shell) SetHomeHistoryPath(path string) {
	home := os.Getenv("HOME")
	abspath := fmt.Sprintf("%s/%s", home, path)
	s.SetHistoryPath(abspath)
}

// SetOut sets the writer to write outputs to.
func (s *Shell) SetOut(writer io.Writer) {
	s.writer = writer
}

// PrintCommands prints a space separated list of registered commands to the shell.
func (s *Shell) PrintCommands() {
	out := strings.Join(s.Commands(), " ")
	if out != "" {
		s.Println("Commands:")
		s.Println(out)
	}
}

// Commands returns a sorted list of all registered commands.
func (s *Shell) Commands() []string {
	var commands []string
	for command := range s.functions {
		commands = append(commands, command)
	}
	sort.Strings(commands)
	return commands
}

// IgnoreCase specifies whether commands should not be case sensitive.
// Defaults to false i.e. commands are case sensitive.
// If true, commands must be registered in lower cases. e.g. shell.Register("cmd", ...)
func (s *Shell) IgnoreCase(ignore bool) {
	s.ignoreCase = ignore
}

// ClearScreen clears the screen. Same behaviour as running 'clear' in unix terminal or 'cls' in windows cmd.
func (s *Shell) ClearScreen() error {
	return clearScreen(s)
}

func clearScreen(s *Shell) error {
	cmd := exec.Command("clear")
	if runtime.GOOS == "windows" {
		cmd = exec.Command("cmd", "/C", "cls")
	}
	cmd.Stdout = s.writer
	return cmd.Run()
}
<|endoftext|>


<commit_before>package server

import (
	"bufio"
	"bytes"
	"fmt"
	"net"
	"os"

	"github.com/cashshuffle/cashshuffle/message"
	"github.com/golang/protobuf/proto"
)

const (
	maxMessageLength = 64 * 1024
)

var (
	// breakBytes are the bytes that delimit each protobuf message
	// This represents the character ⏎
	breakBytes = []byte{226, 143, 142}
)

// startPacketInfoChan starts a loop reading messages.
func startPacketInfoChan(c chan *packetInfo) {
	for {
		pi := <-c
		err := pi.processReceivedMessage()
		if err != nil {
			pi.conn.Close()
			fmt.Fprintf(os.Stderr, "[Error] %s\n", err.Error())
		}
	}
}

// processReceivedMessage reads the message and processes it.
func (pi *packetInfo) processReceivedMessage() error {
	// If we are not tracking the connection yet, the user must be
	// registering with the server.
	if pi.tracker.getTrackerData(pi.conn) == nil {
		err := pi.registerClient()
		if err != nil {
			return err
		}

		playerData := pi.tracker.getTrackerData(pi.conn)

		if pi.tracker.getPoolSize(playerData.pool) == pi.tracker.poolSize {
			pi.announceStart()
		}

		return nil
	}

	if err := pi.verifyMessage(); err != nil {
		return err
	}

	err := pi.broadcastMessage()
	return err
}

// processMessages reads messages from the connection and begins processing.
func processMessages(conn net.Conn, c chan *packetInfo, t *tracker) {
	scanner := bufio.NewScanner(conn)
	scanner.Split(bufio.ScanBytes)

	for {
		var b bytes.Buffer

		for scanner.Scan() {
			scanBytes := scanner.Bytes()

			if len(b.String()) > maxMessageLength {
				fmt.Fprintln(os.Stderr, "[Error] message too long")
				return
			}

			b.Write(scanBytes)

			if breakScan(b) {
				b.Truncate(b.Len() - 3)
				break
			}
		}

		if err := scanner.Err(); err != nil {
			fmt.Fprintf(os.Stderr, "[Error] %s\n", err.Error())
			break
		}

		if b.Len() == 0 {
			break
		}

		if err := sendToPacketInfoChan(&b, conn, c, t); err != nil {
			fmt.Fprintf(os.Stderr, "[Error] %s\n", err.Error())
			break
		}
	}
}

// sendToPacketInfoChan takes a byte buffer containing a protobuf message,
// unmarshals it, creates a packetInfo, then sends it over the packetInfo
// channel.
func sendToPacketInfoChan(b *bytes.Buffer, conn net.Conn, c chan *packetInfo, t *tracker) error {
	defer b.Reset()

	pdata := new(message.Packets)

	err := proto.Unmarshal(b.Bytes(), pdata)
	if err != nil {
		if debugMode {
			fmt.Println("[Error] Unmarshal failed:", b.Bytes())
		}
		return err
	}

	if debugMode {
		fmt.Println("[Received]", pdata)
	}

	data := &packetInfo{
		message: pdata,
		conn:    conn,
		tracker: t,
	}

	c <- data

	return nil
}

// breakScan checks if a byte sequence is the break point on the scanner.
func breakScan(buf bytes.Buffer) bool {
	len := buf.Len()

	if len > 3 {
		payload := buf.Bytes()
		bs := []byte{
			payload[len-3],
			payload[len-2],
			payload[len-1],
		}

		for i := range bs {
			if bs[i] != breakBytes[i] {
				return false
			}
		}

		return true
	}

	return false
}
<commit_msg>Continue on<commit_after>package server

import (
	"bufio"
	"bytes"
	"fmt"
	"net"
	"os"

	"github.com/cashshuffle/cashshuffle/message"
	"github.com/golang/protobuf/proto"
)

const (
	maxMessageLength = 64 * 1024
)

var (
	// breakBytes are the bytes that delimit each protobuf message
	// This represents the character ⏎
	breakBytes = []byte{226, 143, 142}
)

// startPacketInfoChan starts a loop reading messages.
func startPacketInfoChan(c chan *packetInfo) {
	for {
		pi := <-c
		err := pi.processReceivedMessage()
		if err != nil {
			pi.conn.Close()
			fmt.Fprintf(os.Stderr, "[Error] %s\n", err.Error())
		}
	}
}

// processReceivedMessage reads the message and processes it.
func (pi *packetInfo) processReceivedMessage() error {
	// If we are not tracking the connection yet, the user must be
	// registering with the server.
	if pi.tracker.getTrackerData(pi.conn) == nil {
		err := pi.registerClient()
		if err != nil {
			return err
		}

		playerData := pi.tracker.getTrackerData(pi.conn)

		if pi.tracker.getPoolSize(playerData.pool) == pi.tracker.poolSize {
			pi.announceStart()
		}

		return nil
	}

	if err := pi.verifyMessage(); err != nil {
		return err
	}

	err := pi.broadcastMessage()
	return err
}

// processMessages reads messages from the connection and begins processing.
func processMessages(conn net.Conn, c chan *packetInfo, t *tracker) {
	scanner := bufio.NewScanner(conn)
	scanner.Split(bufio.ScanBytes)

	for {
		var b bytes.Buffer

		for scanner.Scan() {
			scanBytes := scanner.Bytes()

			if len(b.String()) > maxMessageLength {
				fmt.Fprintln(os.Stderr, "[Error] message too long")
				return
			}

			b.Write(scanBytes)

			if breakScan(b) {
				b.Truncate(b.Len() - 3)
				break
			}
		}

		if err := scanner.Err(); err != nil {
			fmt.Fprintf(os.Stderr, "[Error] %s\n", err.Error())
			break
		}

		if b.Len() == 0 {
			continue
		}

		if err := sendToPacketInfoChan(&b, conn, c, t); err != nil {
			fmt.Fprintf(os.Stderr, "[Error] %s\n", err.Error())
			break
		}
	}
}

// sendToPacketInfoChan takes a byte buffer containing a protobuf message,
// unmarshals it, creates a packetInfo, then sends it over the packetInfo
// channel.
func sendToPacketInfoChan(b *bytes.Buffer, conn net.Conn, c chan *packetInfo, t *tracker) error {
	defer b.Reset()

	pdata := new(message.Packets)

	err := proto.Unmarshal(b.Bytes(), pdata)
	if err != nil {
		if debugMode {
			fmt.Println("[Error] Unmarshal failed:", b.Bytes())
		}
		return err
	}

	if debugMode {
		fmt.Println("[Received]", pdata)
	}

	data := &packetInfo{
		message: pdata,
		conn:    conn,
		tracker: t,
	}

	c <- data

	return nil
}

// breakScan checks if a byte sequence is the break point on the scanner.
func breakScan(buf bytes.Buffer) bool {
	len := buf.Len()

	if len > 3 {
		payload := buf.Bytes()
		bs := []byte{
			payload[len-3],
			payload[len-2],
			payload[len-1],
		}

		for i := range bs {
			if bs[i] != breakBytes[i] {
				return false
			}
		}

		return true
	}

	return false
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"strconv"
	"strings"
)

func validateResourceProperty(r Resource, value interface{}, t Template, context []string) (bool, []Failure) {
	if properties, ok := value.(map[string]interface{}); ok {
		return r.Validate(t, properties, context)
	}

	return false, []Failure{NewFailure(fmt.Sprintf("Invalid type %T for nested resource %s", value, r.AwsType), context)}
}

func validateProperty(s Schema, value interface{}, t Template, context []string) (bool, []Failure) {
	if resource, ok := s.Type.(Resource); ok {
		return validateResourceProperty(resource, value, t, context)
	}

	if ok := validateValueType(s.Type, value, t, context); !ok {
		if complex, ok := value.(map[string]interface{}); ok {
			return validateBuiltinFns(complex, t, context)
		}

		return false, []Failure{NewInvalidTypeFailure(s.Type, value, context)}
	}

	if s.ValidateFunc != nil {
		return s.ValidateFunc(value, t, context)
	}

	return true, nil
}

func validateValueType(valueType interface{}, value interface{}, t Template, context []string) bool {
	switch valueType {
	case TypeBool:
		if _, ok := value.(bool); ok {
			return true
		}
	case TypeEnum:
		fallthrough
	case TypeString:
		if _, ok := value.(string); ok {
			return true
		}
	case TypeInteger:
		if _, ok := value.(float64); ok {
			return true
		}
	}

	return false
}

func validateRef(value interface{}, t Template, context []string) (bool, []Failure) {
	if ref, ok := value.(string); ok {
		if _, ok := t.Resources[ref]; ok {
			// ref is to a resource and we've found it
			// TODO: validate resource ref value is correct type for property
			return true, nil
		} else if _, ok := t.Parameters[ref]; ok {
			// ref is to a parameter and we've found it
			// TODO: validate parameter type is correct for property
			return true, nil
		}

		return false, []Failure{NewFailure(fmt.Sprintf("Ref '%s' is not a resource or parameter", ref), context)}
	}

	return false, []Failure{NewFailure(fmt.Sprintf("Ref has invalid value '%s'", value), context)}
}

func validateFind(value interface{}, t Template, context []string) (bool, []Failure) {
	return false, []Failure{NewFailure("Value is an Fn::Find but this isn't supported yet", context)}
}

func validateJoin(value interface{}, t Template, context []string) (bool, []Failure) {
	return false, []Failure{NewFailure("Value is an Fn::Join but this isn't supported yet", context)}
}

func validateGetAtt(value interface{}, t Template, context []string) (bool, []Failure) {
	if items, ok := value.([]interface{}); ok {
		if len(items) != 2 {
			return false, []Failure{NewFailure(fmt.Sprintf("GetAtt has incorrect number of arguments (expected: 2, actual: %s)", len(items)), context)}
		}

		if resourceID, ok := items[0].(string); ok {
			if _, ok := t.Resources[resourceID]; ok {
				if _, ok := items[1].(string); ok {
					// TODO: Check attr is actually a valid attribute for the resource type
					return true, nil
				}
			} else {
				// resource not found
				return false, []Failure{NewFailure(fmt.Sprintf("GetAtt '%s' is not a resource", resourceID), context)}
			}
		} else {
			// resource not a string
			return false, []Failure{NewFailure(fmt.Sprintf("GetAtt '%s' is not a valid resource name", items[0]), context)}
		}
	}

	return false, []Failure{NewFailure(fmt.Sprintf("GetAtt has invalid value '%s'", value), context)}
}

func validateBuiltinFns(value map[string]interface{}, t Template, context []string) (bool, []Failure) {
	if ref, ok := value["Ref"]; ok {
		return validateRef(ref, t, context)
	}

	if find, ok := value["Fn::Find"]; ok {
		return validateFind(find, t, context)
	}

	if join, ok := value["Fn::Join"]; ok {
		return validateJoin(join, t, context)
	}

	if getatt, ok := value["Fn::GetAtt"]; ok {
		return validateGetAtt(getatt, t, context)
	}

	return false, []Failure{NewFailure("Value is a map but isn't a builtin", context)}
}

type ValidateFunc func(interface{}, Template, []string) (bool, []Failure)

type Schema struct {
	Array        bool
	Required     bool
	Type         interface{}
	ValidateFunc ValidateFunc
}

func (s Schema) Validate(value interface{}, t Template, context []string) (bool, []Failure) {
	if !s.Required && value == nil {
		return true, nil
	}

	failures := make([]Failure, 0, 20)

	if s.Array {
		for i, item := range value.([]interface{}) {
			if ok, errs := validateProperty(s, item, t, append(context, strconv.Itoa(i))); !ok {
				failures = append(failures, errs...)
			}
		}
	} else {
		if ok, errs := validateProperty(s, value, t, context); !ok {
			failures = append(failures, errs...)
		}
	}

	return len(failures) == 0, failures
}

//go:generate stringer -type=ValueType

type ValueType int

const (
	TypeEnum ValueType = iota
	TypeString
	TypeBool
	TypeInteger
)

func EnumSchema(options ...string) Schema {
	return Schema{
		Type: TypeEnum,
		ValidateFunc: func(value interface{}, t Template, context []string) (bool, []Failure) {
			if str, ok := value.(string); ok {
				found := false
				for _, option := range options {
					if option == str {
						found = true
						break
					}
				}

				if found {
					return true, nil
				} else {
					return false, []Failure{NewFailure(fmt.Sprintf("Invalid enum option %s, expected one of [%s]", str, strings.Join(options, ", ")), context)}
				}
			}

			return false, []Failure{NewInvalidTypeFailure(TypeEnum, value, context)}
		},
	}
}

func ArrayOf(schema Schema) Schema {
	schema.Array = true
	return schema
}

func Required(schema Schema) Schema {
	schema.Required = true
	return schema
}
<commit_msg>Basic recursive Join support<commit_after>package main

import (
	"fmt"
	"strconv"
	"strings"
)

func validateResourceProperty(r Resource, value interface{}, t Template, context []string) (bool, []Failure) {
	if properties, ok := value.(map[string]interface{}); ok {
		return r.Validate(t, properties, context)
	}

	return false, []Failure{NewFailure(fmt.Sprintf("Invalid type %T for nested resource %s", value, r.AwsType), context)}
}

func validateProperty(s Schema, value interface{}, t Template, context []string) (bool, []Failure) {
	if resource, ok := s.Type.(Resource); ok {
		return validateResourceProperty(resource, value, t, context)
	}

	if ok := validateValueType(s.Type, value, t, context); !ok {
		if complex, ok := value.(map[string]interface{}); ok {
			return validateBuiltinFns(complex, t, context)
		}

		return false, []Failure{NewInvalidTypeFailure(s.Type, value, context)}
	}

	if s.ValidateFunc != nil {
		return s.ValidateFunc(value, t, context)
	}

	return true, nil
}

func validateValueType(valueType interface{}, value interface{}, t Template, context []string) bool {
	switch valueType {
	case TypeBool:
		if _, ok := value.(bool); ok {
			return true
		}
	case TypeEnum:
		fallthrough
	case TypeString:
		if _, ok := value.(string); ok {
			return true
		}
	case TypeInteger:
		if _, ok := value.(float64); ok {
			return true
		}
	}

	return false
}

func validateRef(value interface{}, t Template, context []string) (bool, []Failure) {
	if ref, ok := value.(string); ok {
		if _, ok := t.Resources[ref]; ok {
			// ref is to a resource and we've found it
			// TODO: validate resource ref value is correct type for property
			return true, nil
		} else if _, ok := t.Parameters[ref]; ok {
			// ref is to a parameter and we've found it
			// TODO: validate parameter type is correct for property
			return true, nil
		}

		return false, []Failure{NewFailure(fmt.Sprintf("Ref '%s' is not a resource or parameter", ref), context)}
	}

	return false, []Failure{NewFailure(fmt.Sprintf("Ref has invalid value '%s'", value), context)}
}

func validateFind(value interface{}, t Template, context []string) (bool, []Failure) {
	return false, []Failure{NewFailure("Value is an Fn::Find but this isn't supported yet", context)}
}

func validateJoin(value interface{}, t Template, context []string) (bool, []Failure) {
	if items, ok := value.([]interface{}); ok {
		if len(items) != 2 {
			return false, []Failure{NewFailure(fmt.Sprintf("Join has incorrect number of arguments (expected: 2, actual: %s)", len(items)), context)}
		}

		_, ok := items[0].(string)
		if !ok {
			return false, []Failure{NewFailure(fmt.Sprintf("Join '%s' is not a valid delimiter", items[0]), context)}
		}

		parts, ok := items[1].([]interface{})
		if !ok {
			return false, []Failure{NewFailure(fmt.Sprintf("Join items are not valid: %s", items[1]), context)}
		}

		failures := make([]Failure, 0, len(parts))
		for i, part := range parts {
			if ok, errs := validateProperty(Schema{Type: TypeString}, part, t, append(context, "Join", "1", strconv.Itoa(i))); !ok {
				failures = append(failures, errs...)
			}
		}
		return len(failures) == 0, failures
	}

	return false, []Failure{NewFailure(fmt.Sprintf("GetAtt has invalid value '%s'", value), context)}
}

func validateGetAtt(value interface{}, t Template, context []string) (bool, []Failure) {
	if items, ok := value.([]interface{}); ok {
		if len(items) != 2 {
			return false, []Failure{NewFailure(fmt.Sprintf("GetAtt has incorrect number of arguments (expected: 2, actual: %s)", len(items)), context)}
		}

		if resourceID, ok := items[0].(string); ok {
			if _, ok := t.Resources[resourceID]; ok {
				if _, ok := items[1].(string); ok {
					// TODO: Check attr is actually a valid attribute for the resource type
					return true, nil
				}
			} else {
				// resource not found
				return false, []Failure{NewFailure(fmt.Sprintf("GetAtt '%s' is not a resource", resourceID), context)}
			}
		} else {
			// resource not a string
			return false, []Failure{NewFailure(fmt.Sprintf("GetAtt '%s' is not a valid resource name", items[0]), context)}
		}
	}

	return false, []Failure{NewFailure(fmt.Sprintf("GetAtt has invalid value '%s'", value), context)}
}

func validateBuiltinFns(value map[string]interface{}, t Template, context []string) (bool, []Failure) {
	if ref, ok := value["Ref"]; ok {
		return validateRef(ref, t, context)
	}

	if find, ok := value["Fn::Find"]; ok {
		return validateFind(find, t, context)
	}

	if join, ok := value["Fn::Join"]; ok {
		return validateJoin(join, t, context)
	}

	if getatt, ok := value["Fn::GetAtt"]; ok {
		return validateGetAtt(getatt, t, context)
	}

	return false, []Failure{NewFailure("Value is a map but isn't a builtin", context)}
}

type ValidateFunc func(interface{}, Template, []string) (bool, []Failure)

type Schema struct {
	Array        bool
	Required     bool
	Type         interface{}
	ValidateFunc ValidateFunc
}

func (s Schema) Validate(value interface{}, t Template, context []string) (bool, []Failure) {
	if !s.Required && value == nil {
		return true, nil
	}

	failures := make([]Failure, 0, 20)

	if s.Array {
		for i, item := range value.([]interface{}) {
			if ok, errs := validateProperty(s, item, t, append(context, strconv.Itoa(i))); !ok {
				failures = append(failures, errs...)
			}
		}
	} else {
		if ok, errs := validateProperty(s, value, t, context); !ok {
			failures = append(failures, errs...)
		}
	}

	return len(failures) == 0, failures
}

//go:generate stringer -type=ValueType

type ValueType int

const (
	TypeEnum ValueType = iota
	TypeString
	TypeBool
	TypeInteger
)

func EnumSchema(options ...string) Schema {
	return Schema{
		Type: TypeEnum,
		ValidateFunc: func(value interface{}, t Template, context []string) (bool, []Failure) {
			if str, ok := value.(string); ok {
				found := false
				for _, option := range options {
					if option == str {
						found = true
						break
					}
				}

				if found {
					return true, nil
				}

				return false, []Failure{NewFailure(fmt.Sprintf("Invalid enum option %s, expected one of [%s]", str, strings.Join(options, ", ")), context)}
			}

			return false, []Failure{NewInvalidTypeFailure(TypeEnum, value, context)}
		},
	}
}

func ArrayOf(schema Schema) Schema {
	schema.Array = true
	return schema
}

func Required(schema Schema) Schema {
	schema.Required = true
	return schema
}
<|endoftext|>


<commit_before>package apptail

import (
	"github.com/ActiveState/log"
	"logyard/clients/docker_events"
	"sync"
)

const ID_LENGTH = 12

type dockerListener struct {
	waiters map[string]chan bool
	mux     sync.Mutex
}

var DockerListener *dockerListener

func init() {
	DockerListener = new(dockerListener)
	DockerListener.waiters = make(map[string]chan bool)
}

func (l *dockerListener) WaitForContainer(id string) {
	var total int
	id = id[:ID_LENGTH]

	if len(id) != ID_LENGTH {
		log.Fatalf("Invalid docker ID length: %v", len(id))
	}

	// Add a wait channel
	func() {
		l.mux.Lock()
		defer l.mux.Unlock()
		if _, ok := l.waiters[id]; ok {
			panic("already added")
		}
		l.waiters[id] = make(chan bool)
		total = len(l.waiters)
	}()

	// Wait
	log.Infof("Waiting for container %v to exit (waiters count: %d)", id, total)
	<-l.waiters[id]

	func() {
		l.mux.Lock()
		defer l.mux.Unlock()
		delete(l.waiters, id)
	}()
}

func (l *dockerListener) Listen() {
	for evt := range docker_events.Stream() {
		if len(evt.Id) != ID_LENGTH {
			log.Fatalf("Invalid docker ID length: %v", len(evt.Id))
		}

		// Notify container stop events by closing the appropriate ch.
		if !(evt.Status == "die" || evt.Status == "kill") {
			continue
		}
		l.mux.Lock()
		if ch, ok := l.waiters[evt.Id]; ok {
			l.mux.Unlock()
			close(ch)
		} else {
			l.mux.Unlock()
		}
	}
}
<commit_msg>Bug #101463: fix close of the same channel<commit_after>package apptail

import (
	"github.com/ActiveState/log"
	"logyard/clients/docker_events"
	"sync"
)

const ID_LENGTH = 12

type dockerListener struct {
	waiters map[string]chan bool
	mux     sync.Mutex
}

var DockerListener *dockerListener

func init() {
	DockerListener = new(dockerListener)
	DockerListener.waiters = make(map[string]chan bool)
}

func (l *dockerListener) WaitForContainer(id string) {
	var total int
	ch := make(chan bool)
	id = id[:ID_LENGTH]

	if len(id) != ID_LENGTH {
		log.Fatalf("Invalid docker ID length: %v", len(id))
	}

	// Add a wait channel
	func() {
		l.mux.Lock()
		defer l.mux.Unlock()
		if _, ok := l.waiters[id]; ok {
			panic("already added")
		}
		l.waiters[id] = ch
		total = len(l.waiters)
	}()

	// Wait
	log.Infof("Waiting for container %v to exit (total waiters: %d)", id, total)
	<-ch
}

func (l *dockerListener) Listen() {
	for evt := range docker_events.Stream() {
		if len(evt.Id) != ID_LENGTH {
			log.Fatalf("Invalid docker ID length: %v", len(evt.Id))
		}

		// Notify container stop events by closing the appropriate ch.
		if !(evt.Status == "die" || evt.Status == "kill") {
			continue
		}
		l.mux.Lock()
		if ch, ok := l.waiters[evt.Id]; ok {
			close(ch)
			delete(l.waiters, evt.Id)
		}
		l.mux.Unlock()
	}
}
<|endoftext|>


<commit_before>package main

import (
	"path/filepath"
	"fmt"
	"io"
	"os"
	"strings"
)

func copyTree(src, dest string) error {
	return filepath.Walk(src, func(path string, info os.FileInfo, err error) error {
		if err != nil {
			return err
		}
		rootLess := path[len(src):]
		target := filepath.Join(dest, rootLess)
		mode := info.Mode()
		switch {
		case mode.IsDir():
			err := os.Mkdir(target, mode.Perm())
			if err != nil {
				return err
			}
		case mode.IsRegular():
			srcFile, err := os.Open(path)
			if err != nil {
				return err
			}
			destFile, err := os.Create(target)
			if err != nil {
				return err
			}
			if _, err := io.Copy(destFile, srcFile); err != nil {
				return err
			}
		case mode&os.ModeSymlink == os.ModeSymlink:
			// TODO(krnowak): preserve absolute paths of symlinks
			symTarget, err := os.Readlink(path)
			if err != nil {
				return err
			}
			if !filepath.IsAbs(symTarget) {
				dirPart := filepath.Dir(path)
				testPath := filepath.Join(dirPart, symTarget)
				symTarget = filepath.Clean(testPath)
			}
			if strings.HasPrefix(symTarget, src) {
				relTarget, err := filepath.Rel(filepath.Dir(path), symTarget)
				if err != nil {
					return err
				}
				if err := os.Symlink(relTarget, target); err != nil {
					return err
				}
			} else {
				return fmt.Errorf("Symlink %s points to %s, which is outside asset %s", path, symTarget, src)
			}
		default:
			return fmt.Errorf("Unsupported node (%s) in assets, only regular files, directories and symlinks pointing to node inside asset are supported.", path, mode.String())
		}
		return nil
	})
}

// TODO(krnowak): Add placeholders - <PROJDIR>, <TMPDIR>, <GOPATH>?
// First one for sure will be useful, maybe GOPATH too, not sure about
// TMPDIR, rather not.
func PrepareAssets(assets []string, rootfs string) error {
	for _, asset := range assets {
		splitAsset := filepath.SplitList(asset)
		if len(splitAsset) != 2 {
			return fmt.Errorf("Malformed asset option: '%v' - expected two absolute paths separated with %v", asset, listSeparator())
		}
		ACIAsset := splitAsset[0]
		localAsset := splitAsset[1]
		if !filepath.IsAbs(ACIAsset) {
			return fmt.Errorf("Malformed asset option: '%v' - ACI asset has to be absolute path", asset)
		}
		if !filepath.IsAbs(localAsset) {
			return fmt.Errorf("Malformed asset option: '%v' - local asset has to be absolute path", asset)
		}
		fi, err := os.Stat(localAsset)
		if err != nil {
			return fmt.Errorf("Error stating %v: %v", localAsset, err)
		}
		if fi.Mode().IsDir() || fi.Mode().IsRegular() {
			ACIBase := filepath.Base(ACIAsset)
			ACIAssetSubPath := filepath.Join(rootfs, filepath.Dir(ACIAsset))
			err := os.MkdirAll(ACIAssetSubPath, 0755)
			if err != nil {
				return fmt.Errorf("Failed to create directory tree for asset '%v': %v", asset, err)
			}
			err = copyTree(localAsset, filepath.Join(ACIAssetSubPath, ACIBase))
			if err != nil {
				return fmt.Errorf("Failed to copy assets for '%v': %v", asset, err)
			}
		} else {
			return fmt.Errorf("Can't handle %v - not a file, not a dir", fi.Name())
		}
	}
	return nil
}
<commit_msg>Preserve absolute symlink targets<commit_after>package main

import (
	"path/filepath"
	"fmt"
	"io"
	"os"
	"strings"
)

func copyTree(src, dest, imageAssetDir string) error {
	return filepath.Walk(src, func(path string, info os.FileInfo, err error) error {
		if err != nil {
			return err
		}
		rootLess := path[len(src):]
		target := filepath.Join(dest, rootLess)
		mode := info.Mode()
		switch {
		case mode.IsDir():
			err := os.Mkdir(target, mode.Perm())
			if err != nil {
				return err
			}
		case mode.IsRegular():
			srcFile, err := os.Open(path)
			if err != nil {
				return err
			}
			destFile, err := os.Create(target)
			if err != nil {
				return err
			}
			if _, err := io.Copy(destFile, srcFile); err != nil {
				return err
			}
		case mode&os.ModeSymlink == os.ModeSymlink:
			symTarget, err := os.Readlink(path)
			if err != nil {
				return err
			}
			absolute := true
			if !filepath.IsAbs(symTarget) {
				absolute = false
				dirPart := filepath.Dir(path)
				testPath := filepath.Join(dirPart, symTarget)
				symTarget = filepath.Clean(testPath)
			}
			if strings.HasPrefix(symTarget, src) {
				var err error;
				linkTarget := ""
				if absolute {
					linkTarget, err = filepath.Rel(src, symTarget)
					if err == nil {
						linkTarget = filepath.Join(imageAssetDir, linkTarget)
						linkTarget = filepath.Clean(linkTarget)
					}
				} else {
					linkTarget, err = filepath.Rel(filepath.Dir(path), symTarget)
				}
				if err != nil {
					return err
				}
				if err := os.Symlink(linkTarget, target); err != nil {
					return err
				}
			} else {
				return fmt.Errorf("Symlink %s points to %s, which is outside asset %s", path, symTarget, src)
			}
		default:
			return fmt.Errorf("Unsupported node (%s) in assets, only regular files, directories and symlinks pointing to node inside asset are supported.", path, mode.String())
		}
		return nil
	})
}

// TODO(krnowak): Add placeholders - <PROJDIR>, <TMPDIR>, <GOPATH>?
// First one for sure will be useful, maybe GOPATH too, not sure about
// TMPDIR, rather not.
func PrepareAssets(assets []string, rootfs string) error {
	for _, asset := range assets {
		splitAsset := filepath.SplitList(asset)
		if len(splitAsset) != 2 {
			return fmt.Errorf("Malformed asset option: '%v' - expected two absolute paths separated with %v", asset, listSeparator())
		}
		ACIAsset := splitAsset[0]
		localAsset := splitAsset[1]
		if !filepath.IsAbs(ACIAsset) {
			return fmt.Errorf("Malformed asset option: '%v' - ACI asset has to be absolute path", asset)
		}
		if !filepath.IsAbs(localAsset) {
			return fmt.Errorf("Malformed asset option: '%v' - local asset has to be absolute path", asset)
		}
		fi, err := os.Stat(localAsset)
		if err != nil {
			return fmt.Errorf("Error stating %v: %v", localAsset, err)
		}
		if fi.Mode().IsDir() || fi.Mode().IsRegular() {
			ACIAssetSubPath := filepath.Join(rootfs, filepath.Dir(ACIAsset))
			err := os.MkdirAll(ACIAssetSubPath, 0755)
			if err != nil {
				return fmt.Errorf("Failed to create directory tree for asset '%v': %v", asset, err)
			}
			err = copyTree(localAsset, filepath.Join(rootfs, ACIAsset), ACIAsset)
			if err != nil {
				return fmt.Errorf("Failed to copy assets for '%v': %v", asset, err)
			}
		} else {
			return fmt.Errorf("Can't handle %v - not a file, not a dir", fi.Name())
		}
	}
	return nil
}
<|endoftext|>


<commit_before>package aws

import (
	"fmt"
	"strconv"
	"testing"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/service/autoscaling"
	"github.com/hashicorp/terraform/helper/resource"
	"github.com/hashicorp/terraform/terraform"
)

func TestAccAWSASGNotification_basic(t *testing.T) {
	var asgn autoscaling.DescribeNotificationConfigurationsOutput

	resource.Test(t, resource.TestCase{
		PreCheck:     func() { testAccPreCheck(t) },
		Providers:    testAccProviders,
		CheckDestroy: testAccCheckASGNDestroy,
		Steps: []resource.TestStep{
			resource.TestStep{
				Config: testAccASGNotificationConfig_basic,
				Check: resource.ComposeTestCheckFunc(
					testAccCheckASGNotificationExists("aws_autoscaling_notification.example", []string{"foobar1-terraform-test"}, &asgn),
					testAccCheckAWSASGNotificationAttributes("aws_autoscaling_notification.example", &asgn),
				),
			},
		},
	})
}

func TestAccAWSASGNotification_update(t *testing.T) {
	var asgn autoscaling.DescribeNotificationConfigurationsOutput

	resource.Test(t, resource.TestCase{
		PreCheck:     func() { testAccPreCheck(t) },
		Providers:    testAccProviders,
		CheckDestroy: testAccCheckASGNDestroy,
		Steps: []resource.TestStep{
			resource.TestStep{
				Config: testAccASGNotificationConfig_basic,
				Check: resource.ComposeTestCheckFunc(
					testAccCheckASGNotificationExists("aws_autoscaling_notification.example", []string{"foobar1-terraform-test"}, &asgn),
					testAccCheckAWSASGNotificationAttributes("aws_autoscaling_notification.example", &asgn),
				),
			},

			resource.TestStep{
				Config: testAccASGNotificationConfig_update,
				Check: resource.ComposeTestCheckFunc(
					testAccCheckASGNotificationExists("aws_autoscaling_notification.example", []string{"foobar1-terraform-test", "barfoo-terraform-test"}, &asgn),
					testAccCheckAWSASGNotificationAttributes("aws_autoscaling_notification.example", &asgn),
				),
			},
		},
	})
}

func TestAccAWSASGNotification_Pagination(t *testing.T) {
	var asgn autoscaling.DescribeNotificationConfigurationsOutput

	resource.Test(t, resource.TestCase{
		PreCheck:     func() { testAccPreCheck(t) },
		Providers:    testAccProviders,
		CheckDestroy: testAccCheckASGNDestroy,
		Steps: []resource.TestStep{
			resource.TestStep{
				Config: testAccASGNotificationConfig_pagination,
				Check: resource.ComposeTestCheckFunc(
					testAccCheckASGNotificationExists("aws_autoscaling_notification.example",
						[]string{
							"foobar3-terraform-test-0",
							"foobar3-terraform-test-1",
							"foobar3-terraform-test-2",
							"foobar3-terraform-test-3",
							"foobar3-terraform-test-4",
							"foobar3-terraform-test-5",
							"foobar3-terraform-test-6",
							"foobar3-terraform-test-7",
							"foobar3-terraform-test-8",
							"foobar3-terraform-test-9",
							"foobar3-terraform-test-10",
							"foobar3-terraform-test-11",
							"foobar3-terraform-test-12",
							"foobar3-terraform-test-13",
							"foobar3-terraform-test-14",
							"foobar3-terraform-test-15",
							"foobar3-terraform-test-16",
							"foobar3-terraform-test-17",
							"foobar3-terraform-test-18",
							"foobar3-terraform-test-19",
						}, &asgn),
					testAccCheckAWSASGNotificationAttributes("aws_autoscaling_notification.example", &asgn),
				),
			},
		},
	})
}

func testAccCheckASGNotificationExists(n string, groups []string, asgn *autoscaling.DescribeNotificationConfigurationsOutput) resource.TestCheckFunc {
	return func(s *terraform.State) error {
		rs, ok := s.RootModule().Resources[n]
		if !ok {
			return fmt.Errorf("Not found: %s", n)
		}

		if rs.Primary.ID == "" {
			return fmt.Errorf("No ASG Notification ID is set")
		}

		conn := testAccProvider.Meta().(*AWSClient).autoscalingconn
		opts := &autoscaling.DescribeNotificationConfigurationsInput{
			AutoScalingGroupNames: aws.StringSlice(groups),
			MaxRecords:            aws.Int64(100),
		}

		resp, err := conn.DescribeNotificationConfigurations(opts)
		if err != nil {
			return fmt.Errorf("Error describing notifications: %s", err)
		}

		*asgn = *resp

		return nil
	}
}

func testAccCheckASGNDestroy(s *terraform.State) error {
	for _, rs := range s.RootModule().Resources {
		if rs.Type != "aws_autoscaling_notification" {
			continue
		}

		groups := []*string{aws.String("foobar1-terraform-test")}
		conn := testAccProvider.Meta().(*AWSClient).autoscalingconn
		opts := &autoscaling.DescribeNotificationConfigurationsInput{
			AutoScalingGroupNames: groups,
		}

		resp, err := conn.DescribeNotificationConfigurations(opts)
		if err != nil {
			return fmt.Errorf("Error describing notifications")
		}

		if len(resp.NotificationConfigurations) != 0 {
			return fmt.Errorf("Error finding notification descriptions")
		}

	}
	return nil
}

func testAccCheckAWSASGNotificationAttributes(n string, asgn *autoscaling.DescribeNotificationConfigurationsOutput) resource.TestCheckFunc {
	return func(s *terraform.State) error {
		rs, ok := s.RootModule().Resources[n]
		if !ok {
			return fmt.Errorf("Not found: %s", n)
		}

		if rs.Primary.ID == "" {
			return fmt.Errorf("No ASG Notification ID is set")
		}

		if len(asgn.NotificationConfigurations) == 0 {
			return fmt.Errorf("Error: no ASG Notifications found")
		}

		// build a unique list of groups, notification types
		gRaw := make(map[string]bool)
		nRaw := make(map[string]bool)

		for _, n := range asgn.NotificationConfigurations {
			if *n.TopicARN == rs.Primary.Attributes["topic_arn"] {
				gRaw[*n.AutoScalingGroupName] = true
				nRaw[*n.NotificationType] = true
			}
		}

		// Grab the keys here as the list of Groups
		var gList []string
		for k, _ := range gRaw {
			gList = append(gList, k)
		}

		// Grab the keys here as the list of Types
		var nList []string
		for k, _ := range nRaw {
			nList = append(nList, k)
		}

		typeCount, _ := strconv.Atoi(rs.Primary.Attributes["notifications.#"])

		if len(nList) != typeCount {
			return fmt.Errorf("Error: Bad ASG Notification count, expected (%d), got (%d)", typeCount, len(nList))
		}

		groupCount, _ := strconv.Atoi(rs.Primary.Attributes["group_names.#"])

		if len(gList) != groupCount {
			return fmt.Errorf("Error: Bad ASG Group count, expected (%d), got (%d)", typeCount, len(gList))
		}

		return nil
	}
}

const testAccASGNotificationConfig_basic = `
resource "aws_sns_topic" "topic_example" {
  name = "user-updates-topic"
}

resource "aws_launch_configuration" "foobar" {
  name = "foobarautoscaling-terraform-test"
  image_id = "ami-21f78e11"
  instance_type = "t1.micro"
}

resource "aws_autoscaling_group" "bar" {
  availability_zones = ["us-west-2a"]
  name = "foobar1-terraform-test"
  max_size = 1
  min_size = 1
  health_check_grace_period = 100
  health_check_type = "ELB"
  desired_capacity = 1
  force_delete = true
  termination_policies = ["OldestInstance"]
  launch_configuration = "${aws_launch_configuration.foobar.name}"
}

resource "aws_autoscaling_notification" "example" {
  group_names     = ["${aws_autoscaling_group.bar.name}"]
  notifications  = [
	"autoscaling:EC2_INSTANCE_LAUNCH",
	"autoscaling:EC2_INSTANCE_TERMINATE",
  ]
  topic_arn = "${aws_sns_topic.topic_example.arn}"
}
`

const testAccASGNotificationConfig_update = `
resource "aws_sns_topic" "topic_example" {
  name = "user-updates-topic"
}

resource "aws_launch_configuration" "foobar" {
  name = "foobarautoscaling-terraform-test"
  image_id = "ami-21f78e11"
  instance_type = "t1.micro"
}

resource "aws_autoscaling_group" "bar" {
  availability_zones = ["us-west-2a"]
  name = "foobar1-terraform-test"
  max_size = 1
  min_size = 1
  health_check_grace_period = 100
  health_check_type = "ELB"
  desired_capacity = 1
  force_delete = true
  termination_policies = ["OldestInstance"]
  launch_configuration = "${aws_launch_configuration.foobar.name}"
}

resource "aws_autoscaling_group" "foo" {
  availability_zones = ["us-west-2b"]
  name = "barfoo-terraform-test"
  max_size = 1
  min_size = 1
  health_check_grace_period = 200
  health_check_type = "ELB"
  desired_capacity = 1
  force_delete = true
  termination_policies = ["OldestInstance"]
  launch_configuration = "${aws_launch_configuration.foobar.name}"
}

resource "aws_autoscaling_notification" "example" {
	group_names     = [
	"${aws_autoscaling_group.bar.name}",
	"${aws_autoscaling_group.foo.name}",
	]
	notifications  = [
		"autoscaling:EC2_INSTANCE_LAUNCH",
		"autoscaling:EC2_INSTANCE_TERMINATE",
		"autoscaling:EC2_INSTANCE_LAUNCH_ERROR"
	]
	topic_arn = "${aws_sns_topic.topic_example.arn}"
}`

const testAccASGNotificationConfig_pagination = `
resource "aws_sns_topic" "user_updates" {
  name = "user-updates-topic"
}

resource "aws_launch_configuration" "foobar" {
  image_id = "ami-21f78e11"
  instance_type = "t1.micro"
}

resource "aws_autoscaling_group" "bar" {
  availability_zones = ["us-west-2a"]
  count = 20
  name = "foobar3-terraform-test-${count.index}"
  max_size = 1
  min_size = 0
  health_check_grace_period = 300
  health_check_type = "ELB"
  desired_capacity = 0
  force_delete = true
  termination_policies = ["OldestInstance"]
  launch_configuration = "${aws_launch_configuration.foobar.name}"
}

resource "aws_autoscaling_notification" "example" {
  group_names = [
    "${aws_autoscaling_group.bar.*.name}",
  ]
  notifications  = [
    "autoscaling:EC2_INSTANCE_LAUNCH",
    "autoscaling:EC2_INSTANCE_TERMINATE",
    "autoscaling:TEST_NOTIFICATION"
  ]
	topic_arn = "${aws_sns_topic.user_updates.arn}"
}`
<commit_msg>provider/aws: randomize ASG Notification test names<commit_after>package aws

import (
	"fmt"
	"strconv"
	"testing"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/service/autoscaling"
        "github.com/hashicorp/terraform/helper/acctest"
	"github.com/hashicorp/terraform/helper/resource"
	"github.com/hashicorp/terraform/terraform"
)

func TestAccAWSASGNotification_basic(t *testing.T) {
	var asgn autoscaling.DescribeNotificationConfigurationsOutput

        rName := acctest.RandString(5)

	resource.Test(t, resource.TestCase{
		PreCheck:     func() { testAccPreCheck(t) },
		Providers:    testAccProviders,
		CheckDestroy: testAccCheckASGNDestroy,
		Steps: []resource.TestStep{
			resource.TestStep{
                                Config: testAccASGNotificationConfig_basic(rName),
				Check: resource.ComposeTestCheckFunc(
                                        testAccCheckASGNotificationExists("aws_autoscaling_notification.example", []string{"foobar1-terraform-test-" + rName}, &asgn),
					testAccCheckAWSASGNotificationAttributes("aws_autoscaling_notification.example", &asgn),
				),
			},
		},
	})
}

func TestAccAWSASGNotification_update(t *testing.T) {
	var asgn autoscaling.DescribeNotificationConfigurationsOutput

        rName := acctest.RandString(5)

	resource.Test(t, resource.TestCase{
		PreCheck:     func() { testAccPreCheck(t) },
		Providers:    testAccProviders,
		CheckDestroy: testAccCheckASGNDestroy,
		Steps: []resource.TestStep{
			resource.TestStep{
                                Config: testAccASGNotificationConfig_basic(rName),
				Check: resource.ComposeTestCheckFunc(
                                        testAccCheckASGNotificationExists("aws_autoscaling_notification.example", []string{"foobar1-terraform-test-" + rName}, &asgn),
					testAccCheckAWSASGNotificationAttributes("aws_autoscaling_notification.example", &asgn),
				),
			},

			resource.TestStep{
                                Config: testAccASGNotificationConfig_update(rName),
				Check: resource.ComposeTestCheckFunc(
                                        testAccCheckASGNotificationExists("aws_autoscaling_notification.example", []string{"foobar1-terraform-test-" + rName, "barfoo-terraform-test-" + rName}, &asgn),
					testAccCheckAWSASGNotificationAttributes("aws_autoscaling_notification.example", &asgn),
				),
			},
		},
	})
}

func TestAccAWSASGNotification_Pagination(t *testing.T) {
	var asgn autoscaling.DescribeNotificationConfigurationsOutput

	resource.Test(t, resource.TestCase{
		PreCheck:     func() { testAccPreCheck(t) },
		Providers:    testAccProviders,
		CheckDestroy: testAccCheckASGNDestroy,
		Steps: []resource.TestStep{
			resource.TestStep{
				Config: testAccASGNotificationConfig_pagination,
				Check: resource.ComposeTestCheckFunc(
					testAccCheckASGNotificationExists("aws_autoscaling_notification.example",
						[]string{
							"foobar3-terraform-test-0",
							"foobar3-terraform-test-1",
							"foobar3-terraform-test-2",
							"foobar3-terraform-test-3",
							"foobar3-terraform-test-4",
							"foobar3-terraform-test-5",
							"foobar3-terraform-test-6",
							"foobar3-terraform-test-7",
							"foobar3-terraform-test-8",
							"foobar3-terraform-test-9",
							"foobar3-terraform-test-10",
							"foobar3-terraform-test-11",
							"foobar3-terraform-test-12",
							"foobar3-terraform-test-13",
							"foobar3-terraform-test-14",
							"foobar3-terraform-test-15",
							"foobar3-terraform-test-16",
							"foobar3-terraform-test-17",
							"foobar3-terraform-test-18",
							"foobar3-terraform-test-19",
						}, &asgn),
					testAccCheckAWSASGNotificationAttributes("aws_autoscaling_notification.example", &asgn),
				),
			},
		},
	})
}

func testAccCheckASGNotificationExists(n string, groups []string, asgn *autoscaling.DescribeNotificationConfigurationsOutput) resource.TestCheckFunc {
	return func(s *terraform.State) error {
		rs, ok := s.RootModule().Resources[n]
		if !ok {
			return fmt.Errorf("Not found: %s", n)
		}

		if rs.Primary.ID == "" {
			return fmt.Errorf("No ASG Notification ID is set")
		}

		conn := testAccProvider.Meta().(*AWSClient).autoscalingconn
		opts := &autoscaling.DescribeNotificationConfigurationsInput{
			AutoScalingGroupNames: aws.StringSlice(groups),
			MaxRecords:            aws.Int64(100),
		}

		resp, err := conn.DescribeNotificationConfigurations(opts)
		if err != nil {
			return fmt.Errorf("Error describing notifications: %s", err)
		}

		*asgn = *resp

		return nil
	}
}

func testAccCheckASGNDestroy(s *terraform.State) error {
	for _, rs := range s.RootModule().Resources {
		if rs.Type != "aws_autoscaling_notification" {
			continue
		}

		groups := []*string{aws.String("foobar1-terraform-test")}
		conn := testAccProvider.Meta().(*AWSClient).autoscalingconn
		opts := &autoscaling.DescribeNotificationConfigurationsInput{
			AutoScalingGroupNames: groups,
		}

		resp, err := conn.DescribeNotificationConfigurations(opts)
		if err != nil {
			return fmt.Errorf("Error describing notifications")
		}

		if len(resp.NotificationConfigurations) != 0 {
			return fmt.Errorf("Error finding notification descriptions")
		}

	}
	return nil
}

func testAccCheckAWSASGNotificationAttributes(n string, asgn *autoscaling.DescribeNotificationConfigurationsOutput) resource.TestCheckFunc {
	return func(s *terraform.State) error {
		rs, ok := s.RootModule().Resources[n]
		if !ok {
			return fmt.Errorf("Not found: %s", n)
		}

		if rs.Primary.ID == "" {
			return fmt.Errorf("No ASG Notification ID is set")
		}

		if len(asgn.NotificationConfigurations) == 0 {
			return fmt.Errorf("Error: no ASG Notifications found")
		}

		// build a unique list of groups, notification types
		gRaw := make(map[string]bool)
		nRaw := make(map[string]bool)

		for _, n := range asgn.NotificationConfigurations {
			if *n.TopicARN == rs.Primary.Attributes["topic_arn"] {
				gRaw[*n.AutoScalingGroupName] = true
				nRaw[*n.NotificationType] = true
			}
		}

		// Grab the keys here as the list of Groups
		var gList []string
		for k, _ := range gRaw {
			gList = append(gList, k)
		}

		// Grab the keys here as the list of Types
		var nList []string
		for k, _ := range nRaw {
			nList = append(nList, k)
		}

		typeCount, _ := strconv.Atoi(rs.Primary.Attributes["notifications.#"])

		if len(nList) != typeCount {
			return fmt.Errorf("Error: Bad ASG Notification count, expected (%d), got (%d)", typeCount, len(nList))
		}

		groupCount, _ := strconv.Atoi(rs.Primary.Attributes["group_names.#"])

		if len(gList) != groupCount {
			return fmt.Errorf("Error: Bad ASG Group count, expected (%d), got (%d)", typeCount, len(gList))
		}

		return nil
	}
}

func testAccASGNotificationConfig_basic(rName string) string {
        return fmt.Sprintf(`
resource "aws_sns_topic" "topic_example" {
  name = "user-updates-topic-%s"
}

resource "aws_launch_configuration" "foobar" {
  name = "foobarautoscaling-terraform-test-%s"
  image_id = "ami-21f78e11"
  instance_type = "t1.micro"
}

resource "aws_autoscaling_group" "bar" {
  availability_zones = ["us-west-2a"]
  name = "foobar1-terraform-test-%s"
  max_size = 1
  min_size = 1
  health_check_grace_period = 100
  health_check_type = "ELB"
  desired_capacity = 1
  force_delete = true
  termination_policies = ["OldestInstance"]
  launch_configuration = "${aws_launch_configuration.foobar.name}"
}

resource "aws_autoscaling_notification" "example" {
  group_names     = ["${aws_autoscaling_group.bar.name}"]
  notifications  = [
	"autoscaling:EC2_INSTANCE_LAUNCH",
	"autoscaling:EC2_INSTANCE_TERMINATE",
  ]
  topic_arn = "${aws_sns_topic.topic_example.arn}"
}
`, rName, rName, rName)
}

func testAccASGNotificationConfig_update(rName string) string {
        return fmt.Sprintf(`
resource "aws_sns_topic" "topic_example" {
  name = "user-updates-topic-%s"
}

resource "aws_launch_configuration" "foobar" {
  name = "foobarautoscaling-terraform-test-%s"
  image_id = "ami-21f78e11"
  instance_type = "t1.micro"
}

resource "aws_autoscaling_group" "bar" {
  availability_zones = ["us-west-2a"]
  name = "foobar1-terraform-test-%s"
  max_size = 1
  min_size = 1
  health_check_grace_period = 100
  health_check_type = "ELB"
  desired_capacity = 1
  force_delete = true
  termination_policies = ["OldestInstance"]
  launch_configuration = "${aws_launch_configuration.foobar.name}"
}

resource "aws_autoscaling_group" "foo" {
  availability_zones = ["us-west-2b"]
  name = "barfoo-terraform-test-%s"
  max_size = 1
  min_size = 1
  health_check_grace_period = 200
  health_check_type = "ELB"
  desired_capacity = 1
  force_delete = true
  termination_policies = ["OldestInstance"]
  launch_configuration = "${aws_launch_configuration.foobar.name}"
}

resource "aws_autoscaling_notification" "example" {
	group_names     = [
	"${aws_autoscaling_group.bar.name}",
	"${aws_autoscaling_group.foo.name}",
	]
	notifications  = [
		"autoscaling:EC2_INSTANCE_LAUNCH",
		"autoscaling:EC2_INSTANCE_TERMINATE",
		"autoscaling:EC2_INSTANCE_LAUNCH_ERROR"
	]
	topic_arn = "${aws_sns_topic.topic_example.arn}"
}`, rName, rName, rName, rName)
}

const testAccASGNotificationConfig_pagination = `
resource "aws_sns_topic" "user_updates" {
  name = "user-updates-topic"
}

resource "aws_launch_configuration" "foobar" {
  image_id = "ami-21f78e11"
  instance_type = "t1.micro"
}

resource "aws_autoscaling_group" "bar" {
  availability_zones = ["us-west-2a"]
  count = 20
  name = "foobar3-terraform-test-${count.index}"
  max_size = 1
  min_size = 0
  health_check_grace_period = 300
  health_check_type = "ELB"
  desired_capacity = 0
  force_delete = true
  termination_policies = ["OldestInstance"]
  launch_configuration = "${aws_launch_configuration.foobar.name}"
}

resource "aws_autoscaling_notification" "example" {
  group_names = [
    "${aws_autoscaling_group.bar.*.name}",
  ]
  notifications  = [
    "autoscaling:EC2_INSTANCE_LAUNCH",
    "autoscaling:EC2_INSTANCE_TERMINATE",
    "autoscaling:TEST_NOTIFICATION"
  ]
	topic_arn = "${aws_sns_topic.user_updates.arn}"
}`
<|endoftext|>