<commit_before>package native

import (
	"encoding/json"
	"fmt"
	"github.com/dotcloud/docker/execdriver"
	"github.com/dotcloud/docker/pkg/cgroups"
	"github.com/dotcloud/docker/pkg/libcontainer"
	"github.com/dotcloud/docker/pkg/libcontainer/nsinit"
	"github.com/dotcloud/docker/pkg/system"
	"io/ioutil"
	"os"
	"os/exec"
	"path/filepath"
	"strconv"
	"strings"
	"syscall"
	"time"
)

const (
	DriverName = "native"
	Version    = "0.1"
)

func init() {
	execdriver.RegisterInitFunc(DriverName, func(args *execdriver.InitArgs) error {
		var (
			container *libcontainer.Container
			ns        = nsinit.NewNsInit(&nsinit.DefaultCommandFactory{}, &nsinit.DefaultStateWriter{args.Root})
		)
		f, err := os.Open(filepath.Join(args.Root, "container.json"))
		if err != nil {
			return err
		}
		if err := json.NewDecoder(f).Decode(&container); err != nil {
			f.Close()
			return err
		}
		f.Close()

		cwd, err := os.Getwd()
		if err != nil {
			return err
		}
		syncPipe, err := nsinit.NewSyncPipeFromFd(0, uintptr(args.Pipe))
		if err != nil {
			return err
		}
		if err := ns.Init(container, cwd, args.Console, syncPipe, args.Args); err != nil {
			return err
		}
		return nil
	})
}

type driver struct {
	root string
}

func NewDriver(root string) (*driver, error) {
	if err := os.MkdirAll(root, 0700); err != nil {
		return nil, err
	}
	return &driver{
		root: root,
	}, nil
}

func (d *driver) Run(c *execdriver.Command, pipes *execdriver.Pipes, startCallback execdriver.StartCallback) (int, error) {
	if err := d.validateCommand(c); err != nil {
		return -1, err
	}
	var (
		term        nsinit.Terminal
		container   = createContainer(c)
		factory     = &dockerCommandFactory{c: c, driver: d}
		stateWriter = &dockerStateWriter{
			callback: startCallback,
			c:        c,
			dsw:      &nsinit.DefaultStateWriter{filepath.Join(d.root, c.ID)},
		}
		ns   = nsinit.NewNsInit(factory, stateWriter)
		args = append([]string{c.Entrypoint}, c.Arguments...)
	)
	if err := d.createContainerRoot(c.ID); err != nil {
		return -1, err
	}
	defer d.removeContainerRoot(c.ID)

	if c.Tty {
		term = &dockerTtyTerm{
			pipes: pipes,
		}
	} else {
		term = &dockerStdTerm{
			pipes: pipes,
		}
	}
	c.Terminal = term
	if err := d.writeContainerFile(container, c.ID); err != nil {
		return -1, err
	}
	return ns.Exec(container, term, args)
}

func (d *driver) Kill(p *execdriver.Command, sig int) error {
	return syscall.Kill(p.Process.Pid, syscall.Signal(sig))
}

func (d *driver) Restore(c *execdriver.Command) error {
	var nspid int
	f, err := os.Open(filepath.Join(d.root, c.ID, "pid"))
	if err != nil {
		return err
	}
	defer d.removeContainerRoot(c.ID)

	if _, err := fmt.Fscanf(f, "%d", &nspid); err != nil {
		f.Close()
		return err
	}
	f.Close()

	if _, err := os.FindProcess(nspid); err != nil {
		return fmt.Errorf("finding existing pid %d %s", nspid, err)
	}
	c.Process = &os.Process{
		Pid: nspid,
	}
	ticker := time.NewTicker(500 * time.Millisecond)
	defer ticker.Stop()

	for _ = range ticker.C {
		if err := syscall.Kill(nspid, 0); err != nil {
			if strings.Contains(err.Error(), "no such process") {
				return nil
			}
			return fmt.Errorf("signal error %s", err)
		}
	}
	return nil
}

func (d *driver) Info(id string) execdriver.Info {
	return &info{
		ID:     id,
		driver: d,
	}
}

func (d *driver) Name() string {
	return fmt.Sprintf("%s-%s", DriverName, Version)
}

// TODO: this can be improved with our driver
// there has to be a better way to do this
func (d *driver) GetPidsForContainer(id string) ([]int, error) {
	pids := []int{}

	subsystem := "devices"
	cgroupRoot, err := cgroups.FindCgroupMountpoint(subsystem)
	if err != nil {
		return pids, err
	}
	cgroupDir, err := cgroups.GetThisCgroupDir(subsystem)
	if err != nil {
		return pids, err
	}

	filename := filepath.Join(cgroupRoot, cgroupDir, id, "tasks")
	if _, err := os.Stat(filename); os.IsNotExist(err) {
		filename = filepath.Join(cgroupRoot, cgroupDir, "docker", id, "tasks")
	}

	output, err := ioutil.ReadFile(filename)
	if err != nil {
		return pids, err
	}
	for _, p := range strings.Split(string(output), "\n") {
		if len(p) == 0 {
			continue
		}
		pid, err := strconv.Atoi(p)
		if err != nil {
			return pids, fmt.Errorf("Invalid pid '%s': %s", p, err)
		}
		pids = append(pids, pid)
	}
	return pids, nil
}

func (d *driver) writeContainerFile(container *libcontainer.Container, id string) error {
	data, err := json.Marshal(container)
	if err != nil {
		return err
	}
	return ioutil.WriteFile(filepath.Join(d.root, id, "container.json"), data, 0655)
}

func (d *driver) createContainerRoot(id string) error {
	return os.MkdirAll(filepath.Join(d.root, id), 0655)
}

func (d *driver) removeContainerRoot(id string) error {
	return os.RemoveAll(filepath.Join(d.root, id))
}

func (d *driver) validateCommand(c *execdriver.Command) error {
	// we need to check the Config of the command to make sure that we
	// do not have any of the lxc-conf variables
	for _, conf := range c.Config {
		if strings.Contains(conf, "lxc") {
			return fmt.Errorf("%s is not supported by the native driver", conf)
		}
	}
	return nil
}

func getEnv(key string, env []string) string {
	for _, pair := range env {
		parts := strings.Split(pair, "=")
		if parts[0] == key {
			return parts[1]
		}
	}
	return ""
}

type dockerCommandFactory struct {
	c      *execdriver.Command
	driver *driver
}

// createCommand will return an exec.Cmd with the Cloneflags set to the proper namespaces
// defined on the container's configuration and use the current binary as the init with the
// args provided
func (d *dockerCommandFactory) Create(container *libcontainer.Container, console string, syncFd uintptr, args []string) *exec.Cmd {
	// we need to join the rootfs because nsinit will setup the rootfs and chroot
	initPath := filepath.Join(d.c.Rootfs, d.c.InitPath)

	d.c.Path = initPath
	d.c.Args = append([]string{
		initPath,
		"-driver", DriverName,
		"-console", console,
		"-pipe", fmt.Sprint(syncFd),
		"-root", filepath.Join(d.driver.root, d.c.ID),
	}, args...)

	// set this to nil so that when we set the clone flags anything else is reset
	d.c.SysProcAttr = nil
	system.SetCloneFlags(&d.c.Cmd, uintptr(nsinit.GetNamespaceFlags(container.Namespaces)))

	d.c.Env = container.Env
	d.c.Dir = d.c.Rootfs

	return &d.c.Cmd
}

type dockerStateWriter struct {
	dsw      nsinit.StateWriter
	c        *execdriver.Command
	callback execdriver.StartCallback
}

func (d *dockerStateWriter) WritePid(pid int) error {
	err := d.dsw.WritePid(pid)
	if d.callback != nil {
		d.callback(d.c)
	}
	return err
}

func (d *dockerStateWriter) DeletePid() error {
	return d.dsw.DeletePid()
}
<commit_msg>Add missing -- when we run dockerinit from native<commit_after>package native

import (
	"encoding/json"
	"fmt"
	"github.com/dotcloud/docker/execdriver"
	"github.com/dotcloud/docker/pkg/cgroups"
	"github.com/dotcloud/docker/pkg/libcontainer"
	"github.com/dotcloud/docker/pkg/libcontainer/nsinit"
	"github.com/dotcloud/docker/pkg/system"
	"io/ioutil"
	"os"
	"os/exec"
	"path/filepath"
	"strconv"
	"strings"
	"syscall"
	"time"
)

const (
	DriverName = "native"
	Version    = "0.1"
)

func init() {
	execdriver.RegisterInitFunc(DriverName, func(args *execdriver.InitArgs) error {
		var (
			container *libcontainer.Container
			ns        = nsinit.NewNsInit(&nsinit.DefaultCommandFactory{}, &nsinit.DefaultStateWriter{args.Root})
		)
		f, err := os.Open(filepath.Join(args.Root, "container.json"))
		if err != nil {
			return err
		}
		if err := json.NewDecoder(f).Decode(&container); err != nil {
			f.Close()
			return err
		}
		f.Close()

		cwd, err := os.Getwd()
		if err != nil {
			return err
		}
		syncPipe, err := nsinit.NewSyncPipeFromFd(0, uintptr(args.Pipe))
		if err != nil {
			return err
		}
		if err := ns.Init(container, cwd, args.Console, syncPipe, args.Args); err != nil {
			return err
		}
		return nil
	})
}

type driver struct {
	root string
}

func NewDriver(root string) (*driver, error) {
	if err := os.MkdirAll(root, 0700); err != nil {
		return nil, err
	}
	return &driver{
		root: root,
	}, nil
}

func (d *driver) Run(c *execdriver.Command, pipes *execdriver.Pipes, startCallback execdriver.StartCallback) (int, error) {
	if err := d.validateCommand(c); err != nil {
		return -1, err
	}
	var (
		term        nsinit.Terminal
		container   = createContainer(c)
		factory     = &dockerCommandFactory{c: c, driver: d}
		stateWriter = &dockerStateWriter{
			callback: startCallback,
			c:        c,
			dsw:      &nsinit.DefaultStateWriter{filepath.Join(d.root, c.ID)},
		}
		ns   = nsinit.NewNsInit(factory, stateWriter)
		args = append([]string{c.Entrypoint}, c.Arguments...)
	)
	if err := d.createContainerRoot(c.ID); err != nil {
		return -1, err
	}
	defer d.removeContainerRoot(c.ID)

	if c.Tty {
		term = &dockerTtyTerm{
			pipes: pipes,
		}
	} else {
		term = &dockerStdTerm{
			pipes: pipes,
		}
	}
	c.Terminal = term
	if err := d.writeContainerFile(container, c.ID); err != nil {
		return -1, err
	}
	return ns.Exec(container, term, args)
}

func (d *driver) Kill(p *execdriver.Command, sig int) error {
	return syscall.Kill(p.Process.Pid, syscall.Signal(sig))
}

func (d *driver) Restore(c *execdriver.Command) error {
	var nspid int
	f, err := os.Open(filepath.Join(d.root, c.ID, "pid"))
	if err != nil {
		return err
	}
	defer d.removeContainerRoot(c.ID)

	if _, err := fmt.Fscanf(f, "%d", &nspid); err != nil {
		f.Close()
		return err
	}
	f.Close()

	if _, err := os.FindProcess(nspid); err != nil {
		return fmt.Errorf("finding existing pid %d %s", nspid, err)
	}
	c.Process = &os.Process{
		Pid: nspid,
	}
	ticker := time.NewTicker(500 * time.Millisecond)
	defer ticker.Stop()

	for _ = range ticker.C {
		if err := syscall.Kill(nspid, 0); err != nil {
			if strings.Contains(err.Error(), "no such process") {
				return nil
			}
			return fmt.Errorf("signal error %s", err)
		}
	}
	return nil
}

func (d *driver) Info(id string) execdriver.Info {
	return &info{
		ID:     id,
		driver: d,
	}
}

func (d *driver) Name() string {
	return fmt.Sprintf("%s-%s", DriverName, Version)
}

// TODO: this can be improved with our driver
// there has to be a better way to do this
func (d *driver) GetPidsForContainer(id string) ([]int, error) {
	pids := []int{}

	subsystem := "devices"
	cgroupRoot, err := cgroups.FindCgroupMountpoint(subsystem)
	if err != nil {
		return pids, err
	}
	cgroupDir, err := cgroups.GetThisCgroupDir(subsystem)
	if err != nil {
		return pids, err
	}

	filename := filepath.Join(cgroupRoot, cgroupDir, id, "tasks")
	if _, err := os.Stat(filename); os.IsNotExist(err) {
		filename = filepath.Join(cgroupRoot, cgroupDir, "docker", id, "tasks")
	}

	output, err := ioutil.ReadFile(filename)
	if err != nil {
		return pids, err
	}
	for _, p := range strings.Split(string(output), "\n") {
		if len(p) == 0 {
			continue
		}
		pid, err := strconv.Atoi(p)
		if err != nil {
			return pids, fmt.Errorf("Invalid pid '%s': %s", p, err)
		}
		pids = append(pids, pid)
	}
	return pids, nil
}

func (d *driver) writeContainerFile(container *libcontainer.Container, id string) error {
	data, err := json.Marshal(container)
	if err != nil {
		return err
	}
	return ioutil.WriteFile(filepath.Join(d.root, id, "container.json"), data, 0655)
}

func (d *driver) createContainerRoot(id string) error {
	return os.MkdirAll(filepath.Join(d.root, id), 0655)
}

func (d *driver) removeContainerRoot(id string) error {
	return os.RemoveAll(filepath.Join(d.root, id))
}

func (d *driver) validateCommand(c *execdriver.Command) error {
	// we need to check the Config of the command to make sure that we
	// do not have any of the lxc-conf variables
	for _, conf := range c.Config {
		if strings.Contains(conf, "lxc") {
			return fmt.Errorf("%s is not supported by the native driver", conf)
		}
	}
	return nil
}

func getEnv(key string, env []string) string {
	for _, pair := range env {
		parts := strings.Split(pair, "=")
		if parts[0] == key {
			return parts[1]
		}
	}
	return ""
}

type dockerCommandFactory struct {
	c      *execdriver.Command
	driver *driver
}

// createCommand will return an exec.Cmd with the Cloneflags set to the proper namespaces
// defined on the container's configuration and use the current binary as the init with the
// args provided
func (d *dockerCommandFactory) Create(container *libcontainer.Container, console string, syncFd uintptr, args []string) *exec.Cmd {
	// we need to join the rootfs because nsinit will setup the rootfs and chroot
	initPath := filepath.Join(d.c.Rootfs, d.c.InitPath)

	d.c.Path = initPath
	d.c.Args = append([]string{
		initPath,
		"-driver", DriverName,
		"-console", console,
		"-pipe", fmt.Sprint(syncFd),
		"-root", filepath.Join(d.driver.root, d.c.ID),
		"--",
	}, args...)

	// set this to nil so that when we set the clone flags anything else is reset
	d.c.SysProcAttr = nil
	system.SetCloneFlags(&d.c.Cmd, uintptr(nsinit.GetNamespaceFlags(container.Namespaces)))

	d.c.Env = container.Env
	d.c.Dir = d.c.Rootfs

	return &d.c.Cmd
}

type dockerStateWriter struct {
	dsw      nsinit.StateWriter
	c        *execdriver.Command
	callback execdriver.StartCallback
}

func (d *dockerStateWriter) WritePid(pid int) error {
	err := d.dsw.WritePid(pid)
	if d.callback != nil {
		d.callback(d.c)
	}
	return err
}

func (d *dockerStateWriter) DeletePid() error {
	return d.dsw.DeletePid()
}
<|endoftext|>


<commit_before>package e2e

import (
	"testing"

	"github.com/stretchr/testify/assert"

	"k8s.io/apimachinery/pkg/types"

	"github.com/argoproj/argo-cd/errors"
	"github.com/argoproj/argo-cd/test/e2e/fixture"
	"github.com/argoproj/argo-cd/test/fixture/test"

	. "github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
	. "github.com/argoproj/argo-cd/test/e2e/fixture/app"
)

func TestAutoSyncSelfHealDisabled(t *testing.T) {
	Given(t).
		Path(guestbookPath).
		When().
		// app should be auto-synced once created
		CreateFromFile(func(app *Application) {
			app.Spec.SyncPolicy = &SyncPolicy{Automated: &SyncPolicyAutomated{SelfHeal: false}}
		}).
		Then().
		Expect(SyncStatusIs(SyncStatusCodeSynced)).
		// app should be auto-synced if git change detected
		When().
		PatchFile("guestbook-ui-deployment.yaml", `[{"op": "replace", "path": "/spec/revisionHistoryLimit", "value": 1}]`).
		Refresh(RefreshTypeNormal).
		Then().
		Expect(SyncStatusIs(SyncStatusCodeSynced)).
		// app should not be auto-synced if k8s change detected
		When().
		And(func() {
			errors.FailOnErr(fixture.KubeClientset.AppsV1().Deployments(fixture.DeploymentNamespace()).Patch(
				"guestbook-ui", types.MergePatchType, []byte(`{"spec": {"revisionHistoryLimit": 0}}`)))
		}).
		Then().
		Expect(SyncStatusIs(SyncStatusCodeOutOfSync))
}

func TestAutoSyncSelfHealEnabled(t *testing.T) {
	test.Flaky(t)
	Given(t).
		Path(guestbookPath).
		When().
		// app should be auto-synced once created
		CreateFromFile(func(app *Application) {
			app.Spec.SyncPolicy = &SyncPolicy{Automated: &SyncPolicyAutomated{SelfHeal: true}}
		}).
		Then().
		Expect(OperationPhaseIs(OperationSucceeded)).
		Expect(SyncStatusIs(SyncStatusCodeSynced)).
		When().
		// app should be auto-synced once k8s change detected
		And(func() {
			errors.FailOnErr(fixture.KubeClientset.AppsV1().Deployments(fixture.DeploymentNamespace()).Patch(
				"guestbook-ui", types.MergePatchType, []byte(`{"spec": {"revisionHistoryLimit": 0}}`)))
		}).
		Refresh(RefreshTypeNormal).
		Then().
		Expect(OperationPhaseIs(OperationSucceeded)).
		Expect(SyncStatusIs(SyncStatusCodeSynced)).
		When().
		// app should be attempted to auto-synced once and marked with error after failed attempt detected
		PatchFile("guestbook-ui-deployment.yaml", `[{"op": "replace", "path": "/spec/revisionHistoryLimit", "value": "badValue"}]`).
		// Trigger refresh twice to make sure controller notices previously failed sync attempt before expectation timeout expires
		Refresh(RefreshTypeNormal).
		Refresh(RefreshTypeNormal).
		Then().
		Expect(OperationPhaseIs(OperationFailed)).
		Expect(SyncStatusIs(SyncStatusCodeOutOfSync)).
		Expect(Condition(ApplicationConditionSyncError, "Failed sync attempt")).
		When().
		// SyncError condition should be removed after successful sync
		PatchFile("guestbook-ui-deployment.yaml", `[{"op": "replace", "path": "/spec/revisionHistoryLimit", "value": 1}]`).
		// Trigger refresh twice to make sure controller notices successful attempt and removes condition
		Refresh(RefreshTypeNormal).
		Refresh(RefreshTypeNormal).
		Then().
		Expect(OperationPhaseIs(OperationSucceeded)).
		Expect(SyncStatusIs(SyncStatusCodeSynced)).
		And(func(app *Application) {
			assert.Len(t, app.Status.Conditions, 0)
		})

}
<commit_msg>Fix TestAutoSyncSelfHealEnabled test (#2857)<commit_after>package e2e

import (
	"testing"

	"github.com/stretchr/testify/assert"

	"k8s.io/apimachinery/pkg/types"

	"github.com/argoproj/argo-cd/errors"
	. "github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
	"github.com/argoproj/argo-cd/test/e2e/fixture"
	. "github.com/argoproj/argo-cd/test/e2e/fixture/app"
)

func TestAutoSyncSelfHealDisabled(t *testing.T) {
	Given(t).
		Path(guestbookPath).
		When().
		// app should be auto-synced once created
		CreateFromFile(func(app *Application) {
			app.Spec.SyncPolicy = &SyncPolicy{Automated: &SyncPolicyAutomated{SelfHeal: false}}
		}).
		Then().
		Expect(SyncStatusIs(SyncStatusCodeSynced)).
		// app should be auto-synced if git change detected
		When().
		PatchFile("guestbook-ui-deployment.yaml", `[{"op": "replace", "path": "/spec/revisionHistoryLimit", "value": 1}]`).
		Refresh(RefreshTypeNormal).
		Then().
		Expect(SyncStatusIs(SyncStatusCodeSynced)).
		// app should not be auto-synced if k8s change detected
		When().
		And(func() {
			errors.FailOnErr(fixture.KubeClientset.AppsV1().Deployments(fixture.DeploymentNamespace()).Patch(
				"guestbook-ui", types.MergePatchType, []byte(`{"spec": {"revisionHistoryLimit": 0}}`)))
		}).
		Then().
		Expect(SyncStatusIs(SyncStatusCodeOutOfSync))
}

func TestAutoSyncSelfHealEnabled(t *testing.T) {
	Given(t).
		Path(guestbookPath).
		When().
		// app should be auto-synced once created
		CreateFromFile(func(app *Application) {
			app.Spec.SyncPolicy = &SyncPolicy{Automated: &SyncPolicyAutomated{SelfHeal: true}}
		}).
		Then().
		Expect(OperationPhaseIs(OperationSucceeded)).
		Expect(SyncStatusIs(SyncStatusCodeSynced)).
		When().
		// app should be auto-synced once k8s change detected
		And(func() {
			errors.FailOnErr(fixture.KubeClientset.AppsV1().Deployments(fixture.DeploymentNamespace()).Patch(
				"guestbook-ui", types.MergePatchType, []byte(`{"spec": {"revisionHistoryLimit": 0}}`)))
		}).
		Refresh(RefreshTypeNormal).
		Then().
		Expect(OperationPhaseIs(OperationSucceeded)).
		Expect(SyncStatusIs(SyncStatusCodeSynced)).
		When().
		// app should be attempted to auto-synced once and marked with error after failed attempt detected
		PatchFile("guestbook-ui-deployment.yaml", `[{"op": "replace", "path": "/spec/revisionHistoryLimit", "value": "badValue"}]`).
		// Trigger refresh twice to make sure controller notices previously failed sync attempt before expectation timeout expires
		Refresh(RefreshTypeNormal).
		Refresh(RefreshTypeNormal).
		Then().
		Expect(OperationPhaseIs(OperationFailed)).
		Expect(SyncStatusIs(SyncStatusCodeOutOfSync)).
		Expect(Condition(ApplicationConditionSyncError, "Failed sync attempt")).
		When().
		// SyncError condition should be removed after successful sync
		PatchFile("guestbook-ui-deployment.yaml", `[{"op": "replace", "path": "/spec/revisionHistoryLimit", "value": 1}]`).
		// Trigger refresh twice to make sure controller notices successful attempt and removes condition
		Refresh(RefreshTypeNormal).
		Refresh(RefreshTypeNormal).
		Then().
		Expect(OperationPhaseIs(OperationSucceeded)).
		Expect(SyncStatusIs(SyncStatusCodeSynced)).
		And(func(app *Application) {
			assert.Len(t, app.Status.Conditions, 0)
		})

}
<|endoftext|>


<commit_before>package test

import (
	"fmt"
	"os"
	"os/exec"
	"path"
	"testing"
	"time"

	"go.pedge.io/dlog"

	"github.com/libopenstorage/openstorage/api"
	"github.com/libopenstorage/openstorage/volume"
	"github.com/portworx/kvdb"
	"github.com/portworx/kvdb/mem"
	"github.com/stretchr/testify/require"
)

func init() {
	kv, err := kvdb.New(mem.Name, "driver_test", []string{}, nil, dlog.Panicf)
	if err != nil {
		dlog.Panicf("Failed to intialize KVDB")
	}
	err = kvdb.SetInstance(kv)
	if err != nil {
		dlog.Panicf("Failed to set KVDB instance")
	}
}

// Context maintains current device state. It gets passed into tests
// so that tests can build on other tests' work
type Context struct {
	volume.VolumeDriver
	volID      string
	snapID     string
	mountPath  string
	devicePath string
	Filesystem api.FSType
	testPath   string
	testFile   string
	Encrypted  bool
	Passphrase string
}

// NewContext returns a new Context
func NewContext(d volume.VolumeDriver) *Context {
	return &Context{
		VolumeDriver: d,
		volID:        "",
		snapID:       "",
		Filesystem:   api.FSType_FS_TYPE_NONE,
		testPath:     path.Join("/tmp/openstorage/mount/", d.Name()),
		testFile:     path.Join("/tmp/", d.Name()),
	}
}

// RunShort runs the short test suite
func RunShort(t *testing.T, ctx *Context) {
	create(t, ctx)
	inspect(t, ctx)
	set(t, ctx)
	enumerate(t, ctx)
	attach(t, ctx)
	mount(t, ctx)
	io(t, ctx)
	unmount(t, ctx)
	detach(t, ctx)
	delete(t, ctx)
	runEnd(t, ctx)
}

// Run runs the complete test suite
func Run(t *testing.T, ctx *Context) {
	RunShort(t, ctx)
	RunSnap(t, ctx)
	runEnd(t, ctx)
}

func runEnd(t *testing.T, ctx *Context) {
	time.Sleep(time.Second * 2)
	os.RemoveAll(ctx.testPath)
	os.Remove(ctx.testFile)
	shutdown(t, ctx)
}

// RunSnap runs only the snap related tests
func RunSnap(t *testing.T, ctx *Context) {
	snap(t, ctx)
	snapInspect(t, ctx)
	snapEnumerate(t, ctx)
	snapDiff(t, ctx)
	snapDelete(t, ctx)
	detach(t, ctx)
	delete(t, ctx)
}

func create(t *testing.T, ctx *Context) {
	fmt.Println("create")

	volID, err := ctx.Create(
		&api.VolumeLocator{Name: "foo", VolumeLabels: map[string]string{"oh": "create"}},
		nil,
		&api.VolumeSpec{
			Size:       1 * 1024 * 1024 * 1024,
			HaLevel:    1,
			Format:     ctx.Filesystem,
			Encrypted:  ctx.Encrypted,
			Passphrase: ctx.Passphrase,
		})

	require.NoError(t, err, "Failed in Create")
	ctx.volID = volID
}

func inspect(t *testing.T, ctx *Context) {
	fmt.Println("inspect")

	vols, err := ctx.Inspect([]string{ctx.volID})
	require.NoError(t, err, "Failed in Inspect")
	require.NotNil(t, vols, "Nil vols")
	require.Equal(t, len(vols), 1, "Expect 1 volume actual %v volumes", len(vols))
	require.Equal(t, vols[0].Id, ctx.volID, "Expect volID %v actual %v", ctx.volID, vols[0].Id)

	vols, err = ctx.Inspect([]string{string("shouldNotExist")})
	require.Equal(t, 0, len(vols), "Expect 0 volume actual %v volumes", len(vols))
}

func set(t *testing.T, ctx *Context) {
	fmt.Println("update")

	vols, err := ctx.Inspect([]string{ctx.volID})
	require.NoError(t, err, "Failed in Inspect")
	require.NotNil(t, vols, "Nil vols")
	require.Equal(t, len(vols), 1, "Expect 1 volume actual %v volumes", len(vols))
	require.Equal(t, vols[0].Id, ctx.volID, "Expect volID %v actual %v", ctx.volID, vols[0].Id)

	vols[0].Locator.VolumeLabels["UpdateTest"] = "Success"
	err = ctx.Set(ctx.volID, vols[0].Locator, nil)
	require.NoError(t, err, "Failed in Update")

	vols, err = ctx.Inspect([]string{ctx.volID})
	require.NoError(t, err, "Failed in Inspect")
	require.NotNil(t, vols, "Nil vols")
	require.Equal(t, len(vols), 1, "Expect 1 volume actual %v volumes", len(vols))
	require.Equal(t, vols[0].Locator.VolumeLabels["UpdateTest"], "Success",
		"Expect Label %v actual %v", "UpdateTest", vols[0].Locator.VolumeLabels)
}

func enumerate(t *testing.T, ctx *Context) {
	fmt.Println("enumerate")

	vols, err := ctx.Enumerate(&api.VolumeLocator{}, nil)
	require.NoError(t, err, "Failed in Enumerate")
	require.NotNil(t, vols, "Nil vols")
	require.Equal(t, 1, len(vols), "Expect 1 volume actual %v volumes", len(vols))
	require.Equal(t, vols[0].Id, ctx.volID, "Expect volID %v actual %v", ctx.volID, vols[0].Id)

	vols, err = ctx.Enumerate(&api.VolumeLocator{Name: "foo"}, nil)
	require.NoError(t, err, "Failed in Enumerate")
	require.NotNil(t, vols, "Nil vols")
	require.Equal(t, len(vols), 1, "Expect 1 volume actual %v volumes", len(vols))
	require.Equal(t, vols[0].Id, ctx.volID, "Expect volID %v actual %v", ctx.volID, vols[0].Id)

	vols, err = ctx.Enumerate(&api.VolumeLocator{Name: "shouldNotExist"}, nil)
	require.Equal(t, len(vols), 0, "Expect 0 volume actual %v volumes", len(vols))
}

func waitReady(t *testing.T, ctx *Context) error {
	total := time.Minute * 5
	inc := time.Second * 2
	elapsed := time.Second * 0
	vols, err := ctx.Inspect([]string{ctx.volID})
	for err == nil && len(vols) == 1 && vols[0].Status != api.VolumeStatus_VOLUME_STATUS_UP && elapsed < total {
		time.Sleep(inc)
		elapsed += inc
		vols, err = ctx.Inspect([]string{ctx.volID})
	}
	if err != nil {
		return err
	}
	if len(vols) != 1 {
		return fmt.Errorf("Expect one volume from inspect got %v", len(vols))
	}
	if vols[0].Status != api.VolumeStatus_VOLUME_STATUS_UP {
		return fmt.Errorf("Timed out waiting for volume status %v", vols)
	}
	return err
}

func attach(t *testing.T, ctx *Context) {
	fmt.Println("attach")
	err := waitReady(t, ctx)
	require.NoError(t, err, "Volume status is not up")
	p, err := ctx.Attach(ctx.volID, nil)
	if err != nil {
		require.Equal(t, err, volume.ErrNotSupported, "Error on attach %v", err)
	}
	ctx.devicePath = p

	p, err = ctx.Attach(ctx.volID, nil)
	if err == nil {
		require.Equal(t, p, ctx.devicePath, "Multiple calls to attach if not errored should return the same path")
	}
}

func detach(t *testing.T, ctx *Context) {
	fmt.Println("detach")
	err := ctx.Detach(ctx.volID)
	if err != nil {
		require.Equal(t, ctx.devicePath, "", "Error on detach %s: %v", ctx.devicePath, err)
	}
	ctx.devicePath = ""
}

func mount(t *testing.T, ctx *Context) {
	fmt.Println("mount")

	err := os.MkdirAll(ctx.testPath, 0755)

	err = ctx.Mount(ctx.volID, ctx.testPath)
	require.NoError(t, err, "Failed in mount %v", ctx.testPath)

	ctx.mountPath = ctx.testPath
}

func multiMount(t *testing.T, ctx *Context) {
	ctx2 := *ctx
	create(t, ctx)
	attach(t, ctx)
	mount(t, ctx)

	create(t, &ctx2)
	attach(t, &ctx2)

	err := ctx2.Mount(ctx2.volID, ctx2.testPath)
	require.Error(t, err, "Mount of different devices to same path must fail")

	unmount(t, ctx)
	detach(t, ctx)
	delete(t, ctx)

	detach(t, &ctx2)
	delete(t, &ctx2)
}

func unmount(t *testing.T, ctx *Context) {
	fmt.Println("unmount")

	require.NotEqual(t, ctx.mountPath, "", "Device is not mounted")

	err := ctx.Unmount(ctx.volID, ctx.mountPath)
	require.NoError(t, err, "Failed in unmount %v", ctx.mountPath)

	ctx.mountPath = ""
}

func shutdown(t *testing.T, ctx *Context) {
	fmt.Println("shutdown")
	ctx.Shutdown()
}

func io(t *testing.T, ctx *Context) {
	fmt.Println("io")
	require.NotEqual(t, ctx.mountPath, "", "Device is not mounted")

	cmd := exec.Command("dd", "if=/dev/urandom", fmt.Sprintf("of=%s", ctx.testFile), "bs=1M", "count=10")
	o, err := cmd.CombinedOutput()
	require.NoError(t, err, "Failed to run dd %s", string(o))

	cmd = exec.Command("dd", fmt.Sprintf("if=%s", ctx.testFile), fmt.Sprintf("of=%s/xx", ctx.mountPath))
	o, err = cmd.CombinedOutput()
	require.NoError(t, err, "Failed to run dd on mountpoint %s/xx : %s",
		ctx.mountPath, string(o))

	cmd = exec.Command("diff", ctx.testFile, fmt.Sprintf("%s/xx", ctx.mountPath))
	o, err = cmd.CombinedOutput()
	require.NoError(t, err, "data mismatch %s", string(o))
}

func detachBad(t *testing.T, ctx *Context) {
	err := ctx.Detach(ctx.volID)
	require.True(t, (err == nil || err == volume.ErrNotSupported),
		"Detach on mounted device should fail")
}

func deleteBad(t *testing.T, ctx *Context) {
	fmt.Println("deleteBad")
	require.NotEqual(t, ctx.mountPath, "", "Device is not mounted")

	err := ctx.Delete(ctx.volID)
	require.Error(t, err, "Delete on mounted device must fail")
}

func delete(t *testing.T, ctx *Context) {
	fmt.Println("delete")
	err := ctx.Delete(ctx.volID)
	require.NoError(t, err, "Delete failed")
	ctx.volID = ""
}

func snap(t *testing.T, ctx *Context) {
	fmt.Println("snap")
	if ctx.volID == "" {
		create(t, ctx)
	}
	attach(t, ctx)
	labels := map[string]string{"oh": "snap"}
	require.NotEqual(t, ctx.volID, "", "invalid volume ID")
	id, err := ctx.Snapshot(ctx.volID, false,
		&api.VolumeLocator{Name: "snappy", VolumeLabels: labels})
	require.NoError(t, err, "Failed in creating a snapshot")
	ctx.snapID = id
}

func snapInspect(t *testing.T, ctx *Context) {
	fmt.Println("snapInspect")

	snaps, err := ctx.Inspect([]string{ctx.snapID})
	require.NoError(t, err, "Failed in Inspect")
	require.NotNil(t, snaps, "Nil snaps")
	require.Equal(t, len(snaps), 1, "Expect 1 snaps actual %v snaps", len(snaps))
	require.Equal(t, snaps[0].Id, ctx.snapID, "Expect snapID %v actual %v", ctx.snapID, snaps[0].Id)

	snaps, err = ctx.Inspect([]string{string("shouldNotExist")})
	require.Equal(t, 0, len(snaps), "Expect 0 snaps actual %v snaps", len(snaps))
}

func snapEnumerate(t *testing.T, ctx *Context) {
	fmt.Println("snapEnumerate")

	snaps, err := ctx.SnapEnumerate(nil, nil)
	require.NoError(t, err, "Failed in snapEnumerate")
	require.NotNil(t, snaps, "Nil snaps")
	require.Equal(t, 1, len(snaps), "Expect 1 snaps actual %v snaps", len(snaps))
	require.Equal(t, snaps[0].Id, ctx.snapID, "Expect snapID %v actual %v", ctx.snapID, snaps[0].Id)
	labels := snaps[0].Locator.VolumeLabels

	snaps, err = ctx.SnapEnumerate([]string{ctx.volID}, nil)
	require.NoError(t, err, "Failed in snapEnumerate")
	require.NotNil(t, snaps, "Nil snaps")
	require.Equal(t, len(snaps), 1, "Expect 1 snap actual %v snaps", len(snaps))
	require.Equal(t, snaps[0].Id, ctx.snapID, "Expect snapID %v actual %v", ctx.snapID, snaps[0].Id)

	snaps, err = ctx.SnapEnumerate([]string{string("shouldNotExist")}, nil)
	require.Equal(t, len(snaps), 0, "Expect 0 snap actual %v snaps", len(snaps))

	snaps, err = ctx.SnapEnumerate(nil, labels)
	require.NoError(t, err, "Failed in snapEnumerate")
	require.NotNil(t, snaps, "Nil snaps")
	require.Equal(t, len(snaps), 1, "Expect 1 snap actual %v snaps", len(snaps))
	require.Equal(t, snaps[0].Id, ctx.snapID, "Expect snapID %v actual %v", ctx.snapID, snaps[0].Id)
}

func snapDiff(t *testing.T, ctx *Context) {
	fmt.Println("snapDiff")
}

func snapDelete(t *testing.T, ctx *Context) {
	fmt.Println("snapDelete")
}
<commit_msg>Add AttachOptions as a part of test context for volume driver unit tests<commit_after>package test

import (
	"fmt"
	"os"
	"os/exec"
	"path"
	"testing"
	"time"

	"go.pedge.io/dlog"

	"github.com/libopenstorage/openstorage/api"
	"github.com/libopenstorage/openstorage/volume"
	"github.com/portworx/kvdb"
	"github.com/portworx/kvdb/mem"
	"github.com/stretchr/testify/require"
)

func init() {
	kv, err := kvdb.New(mem.Name, "driver_test", []string{}, nil, dlog.Panicf)
	if err != nil {
		dlog.Panicf("Failed to intialize KVDB")
	}
	err = kvdb.SetInstance(kv)
	if err != nil {
		dlog.Panicf("Failed to set KVDB instance")
	}
}

// Context maintains current device state. It gets passed into tests
// so that tests can build on other tests' work
type Context struct {
	volume.VolumeDriver
	volID      string
	snapID     string
	mountPath  string
	devicePath string
	Filesystem api.FSType
	testPath   string
	testFile   string
	Encrypted  bool
	Passphrase string
	AttachOptions map[string]string
}

// NewContext returns a new Context
func NewContext(d volume.VolumeDriver) *Context {
	return &Context{
		VolumeDriver: d,
		volID:        "",
		snapID:       "",
		Filesystem:   api.FSType_FS_TYPE_NONE,
		testPath:     path.Join("/tmp/openstorage/mount/", d.Name()),
		testFile:     path.Join("/tmp/", d.Name()),
	}
}

// RunShort runs the short test suite
func RunShort(t *testing.T, ctx *Context) {
	create(t, ctx)
	inspect(t, ctx)
	set(t, ctx)
	enumerate(t, ctx)
	attach(t, ctx)
	mount(t, ctx)
	io(t, ctx)
	unmount(t, ctx)
	detach(t, ctx)
	delete(t, ctx)
	runEnd(t, ctx)
}

// Run runs the complete test suite
func Run(t *testing.T, ctx *Context) {
	RunShort(t, ctx)
	RunSnap(t, ctx)
	runEnd(t, ctx)
}

func runEnd(t *testing.T, ctx *Context) {
	time.Sleep(time.Second * 2)
	os.RemoveAll(ctx.testPath)
	os.Remove(ctx.testFile)
	shutdown(t, ctx)
}

// RunSnap runs only the snap related tests
func RunSnap(t *testing.T, ctx *Context) {
	snap(t, ctx)
	snapInspect(t, ctx)
	snapEnumerate(t, ctx)
	snapDiff(t, ctx)
	snapDelete(t, ctx)
	detach(t, ctx)
	delete(t, ctx)
}

func create(t *testing.T, ctx *Context) {
	fmt.Println("create")

	volID, err := ctx.Create(
		&api.VolumeLocator{Name: "foo", VolumeLabels: map[string]string{"oh": "create"}},
		nil,
		&api.VolumeSpec{
			Size:       1 * 1024 * 1024 * 1024,
			HaLevel:    1,
			Format:     ctx.Filesystem,
			Encrypted:  ctx.Encrypted,
			Passphrase: ctx.Passphrase,
		})

	require.NoError(t, err, "Failed in Create")
	ctx.volID = volID
}

func inspect(t *testing.T, ctx *Context) {
	fmt.Println("inspect")

	vols, err := ctx.Inspect([]string{ctx.volID})
	require.NoError(t, err, "Failed in Inspect")
	require.NotNil(t, vols, "Nil vols")
	require.Equal(t, len(vols), 1, "Expect 1 volume actual %v volumes", len(vols))
	require.Equal(t, vols[0].Id, ctx.volID, "Expect volID %v actual %v", ctx.volID, vols[0].Id)

	vols, err = ctx.Inspect([]string{string("shouldNotExist")})
	require.Equal(t, 0, len(vols), "Expect 0 volume actual %v volumes", len(vols))
}

func set(t *testing.T, ctx *Context) {
	fmt.Println("update")

	vols, err := ctx.Inspect([]string{ctx.volID})
	require.NoError(t, err, "Failed in Inspect")
	require.NotNil(t, vols, "Nil vols")
	require.Equal(t, len(vols), 1, "Expect 1 volume actual %v volumes", len(vols))
	require.Equal(t, vols[0].Id, ctx.volID, "Expect volID %v actual %v", ctx.volID, vols[0].Id)

	vols[0].Locator.VolumeLabels["UpdateTest"] = "Success"
	err = ctx.Set(ctx.volID, vols[0].Locator, nil)
	require.NoError(t, err, "Failed in Update")

	vols, err = ctx.Inspect([]string{ctx.volID})
	require.NoError(t, err, "Failed in Inspect")
	require.NotNil(t, vols, "Nil vols")
	require.Equal(t, len(vols), 1, "Expect 1 volume actual %v volumes", len(vols))
	require.Equal(t, vols[0].Locator.VolumeLabels["UpdateTest"], "Success",
		"Expect Label %v actual %v", "UpdateTest", vols[0].Locator.VolumeLabels)
}

func enumerate(t *testing.T, ctx *Context) {
	fmt.Println("enumerate")

	vols, err := ctx.Enumerate(&api.VolumeLocator{}, nil)
	require.NoError(t, err, "Failed in Enumerate")
	require.NotNil(t, vols, "Nil vols")
	require.Equal(t, 1, len(vols), "Expect 1 volume actual %v volumes", len(vols))
	require.Equal(t, vols[0].Id, ctx.volID, "Expect volID %v actual %v", ctx.volID, vols[0].Id)

	vols, err = ctx.Enumerate(&api.VolumeLocator{Name: "foo"}, nil)
	require.NoError(t, err, "Failed in Enumerate")
	require.NotNil(t, vols, "Nil vols")
	require.Equal(t, len(vols), 1, "Expect 1 volume actual %v volumes", len(vols))
	require.Equal(t, vols[0].Id, ctx.volID, "Expect volID %v actual %v", ctx.volID, vols[0].Id)

	vols, err = ctx.Enumerate(&api.VolumeLocator{Name: "shouldNotExist"}, nil)
	require.Equal(t, len(vols), 0, "Expect 0 volume actual %v volumes", len(vols))
}

func waitReady(t *testing.T, ctx *Context) error {
	total := time.Minute * 5
	inc := time.Second * 2
	elapsed := time.Second * 0
	vols, err := ctx.Inspect([]string{ctx.volID})
	for err == nil && len(vols) == 1 && vols[0].Status != api.VolumeStatus_VOLUME_STATUS_UP && elapsed < total {
		time.Sleep(inc)
		elapsed += inc
		vols, err = ctx.Inspect([]string{ctx.volID})
	}
	if err != nil {
		return err
	}
	if len(vols) != 1 {
		return fmt.Errorf("Expect one volume from inspect got %v", len(vols))
	}
	if vols[0].Status != api.VolumeStatus_VOLUME_STATUS_UP {
		return fmt.Errorf("Timed out waiting for volume status %v", vols)
	}
	return err
}

func attach(t *testing.T, ctx *Context) {
	fmt.Println("attach")
	err := waitReady(t, ctx)
	require.NoError(t, err, "Volume status is not up")
	p, err := ctx.Attach(ctx.volID, ctx.AttachOptions)
	if err != nil {
		require.Equal(t, err, volume.ErrNotSupported, "Error on attach %v", err)
	}
	ctx.devicePath = p

	p, err = ctx.Attach(ctx.volID, ctx.AttachOptions)
	if err == nil {
		require.Equal(t, p, ctx.devicePath, "Multiple calls to attach if not errored should return the same path")
	}
}

func detach(t *testing.T, ctx *Context) {
	fmt.Println("detach")
	err := ctx.Detach(ctx.volID)
	if err != nil {
		require.Equal(t, ctx.devicePath, "", "Error on detach %s: %v", ctx.devicePath, err)
	}
	ctx.devicePath = ""
}

func mount(t *testing.T, ctx *Context) {
	fmt.Println("mount")

	err := os.MkdirAll(ctx.testPath, 0755)

	err = ctx.Mount(ctx.volID, ctx.testPath)
	require.NoError(t, err, "Failed in mount %v", ctx.testPath)

	ctx.mountPath = ctx.testPath
}

func multiMount(t *testing.T, ctx *Context) {
	ctx2 := *ctx
	create(t, ctx)
	attach(t, ctx)
	mount(t, ctx)

	create(t, &ctx2)
	attach(t, &ctx2)

	err := ctx2.Mount(ctx2.volID, ctx2.testPath)
	require.Error(t, err, "Mount of different devices to same path must fail")

	unmount(t, ctx)
	detach(t, ctx)
	delete(t, ctx)

	detach(t, &ctx2)
	delete(t, &ctx2)
}

func unmount(t *testing.T, ctx *Context) {
	fmt.Println("unmount")

	require.NotEqual(t, ctx.mountPath, "", "Device is not mounted")

	err := ctx.Unmount(ctx.volID, ctx.mountPath)
	require.NoError(t, err, "Failed in unmount %v", ctx.mountPath)

	ctx.mountPath = ""
}

func shutdown(t *testing.T, ctx *Context) {
	fmt.Println("shutdown")
	ctx.Shutdown()
}

func io(t *testing.T, ctx *Context) {
	fmt.Println("io")
	require.NotEqual(t, ctx.mountPath, "", "Device is not mounted")

	cmd := exec.Command("dd", "if=/dev/urandom", fmt.Sprintf("of=%s", ctx.testFile), "bs=1M", "count=10")
	o, err := cmd.CombinedOutput()
	require.NoError(t, err, "Failed to run dd %s", string(o))

	cmd = exec.Command("dd", fmt.Sprintf("if=%s", ctx.testFile), fmt.Sprintf("of=%s/xx", ctx.mountPath))
	o, err = cmd.CombinedOutput()
	require.NoError(t, err, "Failed to run dd on mountpoint %s/xx : %s",
		ctx.mountPath, string(o))

	cmd = exec.Command("diff", ctx.testFile, fmt.Sprintf("%s/xx", ctx.mountPath))
	o, err = cmd.CombinedOutput()
	require.NoError(t, err, "data mismatch %s", string(o))
}

func detachBad(t *testing.T, ctx *Context) {
	err := ctx.Detach(ctx.volID)
	require.True(t, (err == nil || err == volume.ErrNotSupported),
		"Detach on mounted device should fail")
}

func deleteBad(t *testing.T, ctx *Context) {
	fmt.Println("deleteBad")
	require.NotEqual(t, ctx.mountPath, "", "Device is not mounted")

	err := ctx.Delete(ctx.volID)
	require.Error(t, err, "Delete on mounted device must fail")
}

func delete(t *testing.T, ctx *Context) {
	fmt.Println("delete")
	err := ctx.Delete(ctx.volID)
	require.NoError(t, err, "Delete failed")
	ctx.volID = ""
}

func snap(t *testing.T, ctx *Context) {
	fmt.Println("snap")
	if ctx.volID == "" {
		create(t, ctx)
	}
	attach(t, ctx)
	labels := map[string]string{"oh": "snap"}
	require.NotEqual(t, ctx.volID, "", "invalid volume ID")
	id, err := ctx.Snapshot(ctx.volID, false,
		&api.VolumeLocator{Name: "snappy", VolumeLabels: labels})
	require.NoError(t, err, "Failed in creating a snapshot")
	ctx.snapID = id
}

func snapInspect(t *testing.T, ctx *Context) {
	fmt.Println("snapInspect")

	snaps, err := ctx.Inspect([]string{ctx.snapID})
	require.NoError(t, err, "Failed in Inspect")
	require.NotNil(t, snaps, "Nil snaps")
	require.Equal(t, len(snaps), 1, "Expect 1 snaps actual %v snaps", len(snaps))
	require.Equal(t, snaps[0].Id, ctx.snapID, "Expect snapID %v actual %v", ctx.snapID, snaps[0].Id)

	snaps, err = ctx.Inspect([]string{string("shouldNotExist")})
	require.Equal(t, 0, len(snaps), "Expect 0 snaps actual %v snaps", len(snaps))
}

func snapEnumerate(t *testing.T, ctx *Context) {
	fmt.Println("snapEnumerate")

	snaps, err := ctx.SnapEnumerate(nil, nil)
	require.NoError(t, err, "Failed in snapEnumerate")
	require.NotNil(t, snaps, "Nil snaps")
	require.Equal(t, 1, len(snaps), "Expect 1 snaps actual %v snaps", len(snaps))
	require.Equal(t, snaps[0].Id, ctx.snapID, "Expect snapID %v actual %v", ctx.snapID, snaps[0].Id)
	labels := snaps[0].Locator.VolumeLabels

	snaps, err = ctx.SnapEnumerate([]string{ctx.volID}, nil)
	require.NoError(t, err, "Failed in snapEnumerate")
	require.NotNil(t, snaps, "Nil snaps")
	require.Equal(t, len(snaps), 1, "Expect 1 snap actual %v snaps", len(snaps))
	require.Equal(t, snaps[0].Id, ctx.snapID, "Expect snapID %v actual %v", ctx.snapID, snaps[0].Id)

	snaps, err = ctx.SnapEnumerate([]string{string("shouldNotExist")}, nil)
	require.Equal(t, len(snaps), 0, "Expect 0 snap actual %v snaps", len(snaps))

	snaps, err = ctx.SnapEnumerate(nil, labels)
	require.NoError(t, err, "Failed in snapEnumerate")
	require.NotNil(t, snaps, "Nil snaps")
	require.Equal(t, len(snaps), 1, "Expect 1 snap actual %v snaps", len(snaps))
	require.Equal(t, snaps[0].Id, ctx.snapID, "Expect snapID %v actual %v", ctx.snapID, snaps[0].Id)
}

func snapDiff(t *testing.T, ctx *Context) {
	fmt.Println("snapDiff")
}

func snapDelete(t *testing.T, ctx *Context) {
	fmt.Println("snapDelete")
}
<|endoftext|>


<commit_before>package expr

import (
	"testing"

	u "github.com/araddon/gou"
	"github.com/araddon/qlbridge/lex"
	"github.com/bmizerany/assert"
)

var (
	_ = u.EMPTY
)

func init() {
	lex.IDENTITY_CHARS = lex.IDENTITY_SQL_CHARS
}

func parseFilterQlTest(t *testing.T, ql string) {
	req, err := ParseFilterQL(ql)
	assert.Tf(t, err == nil && req != nil, "Must parse: %s  \n\t%v", ql, err)
}

func TestFilterQlLexOnly(t *testing.T) {

	parseFilterQlTest(t, `
		FILTER x > 7
	`)

	parseFilterQlTest(t, `
		FILTER
			AND (
				NAME != NULL
				, tostring(fieldname) == "hello"
			)

			LIMIT 100
	`)
}

func TestFilterQLAstCheck(t *testing.T) {

	ql := `
		FILTER
			AND (
				NAME != NULL,
				tostring(fieldname) == "hello",
			)

		LIMIT 100
	`
	req, err := ParseFilterQL(ql)
	assert.Tf(t, err == nil && req != nil, "Must parse: %s  \n\t%v", ql, err)
	assert.Tf(t, len(req.Filter.Filters) == 2, "has 2 filters: %#v", req.Filter)
	f1 := req.Filter.Filters[0]
	assert.Tf(t, f1.Expr != nil, "")
	assert.Tf(t, f1.Expr.String() == "NAME != NULL", "%v", f1.Expr)
	assert.Tf(t, req.Limit == 100, "wanted limit=100: %v", req.Limit)

	ql = `
    SELECT *
    FROM users
    WHERE
      domain(url) == "google.com"
      OR momentum > 20
    ALIAS my_filter_name
	`
	req, err = ParseFilterQL(ql)
	assert.Tf(t, err == nil && req != nil, "Must parse: %s  \n\t%v", ql, err)
	assert.Tf(t, req.Alias == "my_filter_name", "has alias: %q", req.Alias)
	assert.Tf(t, len(req.Filter.Filters) == 1, "has 1 filters: %#v", req.Filter)
	fs := req.Filter.Filters[0]
	assert.Tf(t, fs.Expr != nil, "")
	assert.Tf(t, fs.Expr.String() == `domain(url) == "google.com" OR momentum > 20`, "%v", fs.Expr)

	ql = `
    FILTER
      AND (
          -- Lets make sure the date is good
          daysago(datefield) < 100
          -- as well as domain
          , domain(url) == "google.com"
          , INCLUDE my_other_named_filter
          , OR (
              momentum > 20
             , propensity > 50
          )
          , NOT ( score > 20 )
       )
    ALIAS my_filter_name
	`
	req, err = ParseFilterQL(ql)
	assert.Tf(t, err == nil && req != nil, "Must parse: %s  \n\t%v", ql, err)
	assert.Tf(t, req.Alias == "my_filter_name", "has alias: %q", req.Alias)
	assert.Equalf(t, len(req.Filter.Filters), 5, "expected 5 filters: %#v", req.Filter)
	f5 := req.Filter.Filters[4]
	assert.Tf(t, f5.Expr != nil, "")
	assert.Equal(t, f5.Expr.String(), "NOT (score > 20)")
	assert.Equalf(t, f5.Expr.NodeType(), UnaryNodeType, "%s != %s", f5.Expr.NodeType(), UnaryNodeType)

	// Make sure we support following features
	//  - naked single valid expressions that are compound
	//  - naked expression syntax (ie, not AND())
	ql = `
		FILTER
			NAME != NULL
			AND
			tostring(fieldname) == "hello"
	`
	req, err = ParseFilterQL(ql)
	assert.Tf(t, err == nil && req != nil, "Must parse: %s  \n\t%v", ql, err)
	assert.Tf(t, len(req.Filter.Filters) == 1, "has 1 filter expr: %#v", req.Filter.Filters)

	ql = `
    FILTER
      AND (
          EXISTS datefield
       )
    ALIAS my_filter_name
	`
	req, err = ParseFilterQL(ql)
	assert.Tf(t, err == nil && req != nil, "Must parse: %s  \n\t%v", ql, err)
	assert.Tf(t, req.Alias == "my_filter_name", "has alias: %q", req.Alias)
	assert.Tf(t, len(req.Filter.Filters) == 1, "has 1 filters: %#v", req.Filter)
	f1 = req.Filter.Filters[0]
	assert.Tf(t, f1.Expr != nil, "")
	assert.Tf(t, f1.Expr.String() == "EXISTS datefield", "%#v", f1.Expr.String())
}
<commit_msg>WIP add test for filterql keywords<commit_after>package expr

import (
	"testing"

	u "github.com/araddon/gou"
	"github.com/araddon/qlbridge/lex"
	"github.com/bmizerany/assert"
)

var (
	_ = u.EMPTY
)

func init() {
	lex.IDENTITY_CHARS = lex.IDENTITY_SQL_CHARS
}

func parseFilterQlTest(t *testing.T, ql string) {
	req, err := ParseFilterQL(ql)
	assert.Tf(t, err == nil && req != nil, "Must parse: %s  \n\t%v", ql, err)
}

func TestFilterQlLexOnly(t *testing.T) {

	parseFilterQlTest(t, `
		FILTER x > 7
	`)

	parseFilterQlTest(t, `
		FILTER
			AND (
				NAME != NULL
				, tostring(fieldname) == "hello"
			)

			LIMIT 100
	`)
}

func TestFilterQLAstCheck(t *testing.T) {

	ql := `
		FILTER
			AND (
				NAME != NULL,
				tostring(fieldname) == "hello",
			)

		LIMIT 100
	`
	req, err := ParseFilterQL(ql)
	assert.Tf(t, err == nil && req != nil, "Must parse: %s  \n\t%v", ql, err)
	assert.Tf(t, len(req.Filter.Filters) == 2, "has 2 filters: %#v", req.Filter)
	f1 := req.Filter.Filters[0]
	assert.Tf(t, f1.Expr != nil, "")
	assert.Tf(t, f1.Expr.String() == "NAME != NULL", "%v", f1.Expr)
	assert.Tf(t, req.Limit == 100, "wanted limit=100: %v", req.Limit)

	ql = `
    SELECT *
    FROM users
    WHERE
      domain(url) == "google.com"
      OR momentum > 20
    ALIAS my_filter_name
	`
	req, err = ParseFilterQL(ql)
	assert.Tf(t, err == nil && req != nil, "Must parse: %s  \n\t%v", ql, err)
	assert.Tf(t, req.Alias == "my_filter_name", "has alias: %q", req.Alias)
	assert.Tf(t, len(req.Filter.Filters) == 1, "has 1 filters: %#v", req.Filter)
	fs := req.Filter.Filters[0]
	assert.Tf(t, fs.Expr != nil, "")
	assert.Tf(t, fs.Expr.String() == `domain(url) == "google.com" OR momentum > 20`, "%v", fs.Expr)

	ql = `
    FILTER
      AND (
          -- Lets make sure the date is good
          daysago(datefield) < 100
          -- as well as domain
          , domain(url) == "google.com"
          , INCLUDE my_other_named_filter
          , OR (
              momentum > 20
             , propensity > 50
          )
          , NOT ( score > 20 )
       )
    ALIAS my_filter_name
	`
	req, err = ParseFilterQL(ql)
	assert.Tf(t, err == nil && req != nil, "Must parse: %s  \n\t%v", ql, err)
	assert.Tf(t, req.Alias == "my_filter_name", "has alias: %q", req.Alias)
	assert.Equalf(t, len(req.Filter.Filters), 5, "expected 5 filters: %#v", req.Filter)
	f5 := req.Filter.Filters[4]
	assert.Tf(t, f5.Expr != nil, "")
	assert.Equal(t, f5.Expr.String(), "NOT (score > 20)")
	assert.Equalf(t, f5.Expr.NodeType(), UnaryNodeType, "%s != %s", f5.Expr.NodeType(), UnaryNodeType)

	// Make sure we support following features
	//  - naked single valid expressions that are compound
	//  - naked expression syntax (ie, not AND())
	ql = `
		FILTER
			NAME != NULL
			AND
			tostring(fieldname) == "hello"
	`
	req, err = ParseFilterQL(ql)
	assert.Tf(t, err == nil && req != nil, "Must parse: %s  \n\t%v", ql, err)
	assert.Tf(t, len(req.Filter.Filters) == 1, "has 1 filter expr: %#v", req.Filter.Filters)

	ql = `
    FILTER
      AND (
          EXISTS datefield
       )
    ALIAS my_filter_name
	`
	req, err = ParseFilterQL(ql)
	assert.Tf(t, err == nil && req != nil, "Must parse: %s  \n\t%v", ql, err)
	assert.Tf(t, req.Alias == "my_filter_name", "has alias: %q", req.Alias)
	assert.Tf(t, len(req.Filter.Filters) == 1, "has 1 filters: %#v", req.Filter)
	f1 = req.Filter.Filters[0]
	assert.Tf(t, f1.Expr != nil, "")
	assert.Tf(t, f1.Expr.String() == "EXISTS datefield", "%#v", f1.Expr.String())
}

func TestFilterQLKeywords(t *testing.T) {
	ql := `
		FILTER
		  -- Test filter
			AND (
				created < "now-24h",
				deleted == false
			)
		FROM accounts
		ALIAS new_accounts
		LIMIT 100
	`
	req, err := ParseFilterQL(ql)
	assert.Equal(t, nil, err)
	assert.NotEqual(t, nil, req)
}
<|endoftext|>


<commit_before>package main

import (
  "bytes"
	"bufio"
	"net/http"
	"os"
	"os/exec"
	"github.com/go-martini/martini"
	"github.com/yvasiyarov/gorelic"
)

func main() {
	var (
		newrelicLicense = os.Getenv("NEWRELIC_LICENSE")
		newrelicName    = os.Getenv("NEWRELIC_NAME")
	)
	if newrelicLicense != "" && newrelicName != "" {
		agent := gorelic.NewAgent()
		agent.Verbose = true
		agent.NewrelicLicense = os.Getenv("NEWRELIC_LICENSE")
		agent.NewrelicName = os.Getenv("NEWRELIC_NAME")
		agent.Run()
	}

	m := martini.Classic()
	m.Get("/", func() string {
		return "Hello world!"
	})

	m.Post("**", func(res http.ResponseWriter, req *http.Request) (int, string) {
		var (
			contentTypeHeader []string
			contentType       string
		)
		contentTypeHeader = req.Header["Content-Type"]
		if len(contentTypeHeader) > 0 {
			contentType = contentTypeHeader[0]
		} else {
			return 400, "Content-Type header is mandatory"
		}
		if contentType == "application/x-php" {
			return php(res, req)
		}
		return 400, "Content-Type not currently supported"
	})

	m.Run()
}

func php(res http.ResponseWriter, req *http.Request) (int, string) {
	path, err := exec.LookPath("php")
	if err != nil {
		return 500, err.Error()
	}
	cmd := exec.Command(path, "-l")
	cmd.Stdin = bufio.NewReader(req.Body)
	var (
    out bytes.Buffer
  )
	cmd.Stdout = &out
	err = cmd.Run()
	if err != nil {
//		return 500, err.Error()
		return 500, out.String()
	}
	return 200, out.String()
}
<commit_msg>tweaks to handling result from `php -l`<commit_after>package main

import (
  "bytes"
	"bufio"
	"net/http"
	"os"
	"os/exec"
	"github.com/go-martini/martini"
	"github.com/yvasiyarov/gorelic"
)

func main() {
	var (
		newrelicLicense = os.Getenv("NEWRELIC_LICENSE")
		newrelicName    = os.Getenv("NEWRELIC_NAME")
	)
	if newrelicLicense != "" && newrelicName != "" {
		agent := gorelic.NewAgent()
		agent.Verbose = true
		agent.NewrelicLicense = os.Getenv("NEWRELIC_LICENSE")
		agent.NewrelicName = os.Getenv("NEWRELIC_NAME")
		agent.Run()
	}

	m := martini.Classic()
	m.Get("/", func() string {
		return "Hello world!"
	})

	m.Post("**", func(res http.ResponseWriter, req *http.Request) (int, string) {
		var (
			contentTypeHeader []string
			contentType       string
		)
		contentTypeHeader = req.Header["Content-Type"]
		if len(contentTypeHeader) > 0 {
			contentType = contentTypeHeader[0]
		} else {
			return 400, "Content-Type header is mandatory"
		}
		if contentType == "application/x-php" {
			return php(res, req)
		}
		return 415, "Content-Type not currently supported"
	})

	m.Run()
}

func php(res http.ResponseWriter, req *http.Request) (int, string) {
	path, err := exec.LookPath("php")
	if err != nil {
		return 500, err.Error()
	}
	cmd := exec.Command(path, "-l")
	cmd.Stdin = bufio.NewReader(req.Body)
	var (
    out bytes.Buffer
  )
	cmd.Stdout = &out
	cmd.Stderr = &out
	err = cmd.Run()
	if err != nil {
		return 200, out.String() + err.Error()
	}
	return 204, ""
}
<|endoftext|>


<commit_before>package dutycal

import (
	"crypto/sha256"
	"database/cassandra"
	"encoding/binary"
	"encoding/hex"
	"fmt"
	"time"
)

var kEventAllColumns [][]byte = [][]byte{
	[]byte("title"), []byte("description"), []byte("owner"),
	[]byte("start"), []byte("end"),
}

type Event struct {
	db   *cassandra.RetryCassandraClient
	conf *DutyCalConfig

	Id          string
	Title       string
	Description string
	Start       time.Time
	Duration    time.Duration
	Owner       string
	Required    bool
}

func getWeekFromTimestamp(ts time.Time) int64 {
	return ts.Unix() / (7 * 24 * 60 * 60)
}

// Create a new event with the speicfied details.
func CreateEvent(db *cassandra.RetryCassandraClient, conf *DutyCalConfig,
	title, description, owner string,
	start time.Time, duration time.Duration, required bool) *Event {
	return &Event{
		db:   db,
		conf: conf,

		Title:       title,
		Description: description,
		Start:       start,
		Duration:    duration,
		Owner:       owner,
		Required:    required,
	}
}

// Recreate in-memory event object from database. The record "id" is read
// from the database designated as "db" as specified in the configuration
// "conf". If "quorum" is specified, a quorum read from the database will
// be performed rather than just reading from a single replica.
func FetchEvent(db *cassandra.RetryCassandraClient, conf *DutyCalConfig,
	id string, quorum bool) (rv *Event, err error) {
	var cp *cassandra.ColumnParent = cassandra.NewColumnParent()
	var pred *cassandra.SlicePredicate = cassandra.NewSlicePredicate()
	var cl cassandra.ConsistencyLevel

	var r []*cassandra.ColumnOrSuperColumn
	var ire *cassandra.InvalidRequestException
	var ue *cassandra.UnavailableException
	var te *cassandra.TimedOutException

	cp.ColumnFamily = conf.GetEventsColumnFamily()
	pred.ColumnNames = kEventAllColumns

	if quorum {
		cl = cassandra.ConsistencyLevel_QUORUM
	} else {
		cl = cassandra.ConsistencyLevel_ONE
	}

	r, ire, ue, te, err = db.GetSlice([]byte(id), cp, pred, cl)
	if ire != nil {
		err = fmt.Errorf("Invalid request error fetching event %s: %s",
			id, ire.Why)
		return
	}
	if ue != nil {
		err = fmt.Errorf("Cassandra unavailable fetching event %s", id)
		return
	}
	if te != nil {
		err = fmt.Errorf("Request for %s timed out: %s",
			id, te.String())
		return
	}
	if err != nil {
		return
	}

	rv = &Event{Id: id}
	err = rv.extractFromColumns(r)
	return
}

// Retrieve a list of all events between the two specified dates.
func FetchEventRange(db *cassandra.RetryCassandraClient, conf *DutyCalConfig,
	from, to time.Time, quorum bool) ([]*Event, error) {
	var parent *cassandra.ColumnParent
	var clause *cassandra.IndexClause
	var predicate *cassandra.SlicePredicate
	var expr *cassandra.IndexExpression
	var cl cassandra.ConsistencyLevel

	var res []*cassandra.KeySlice
	var ire *cassandra.InvalidRequestException
	var ue *cassandra.UnavailableException
	var te *cassandra.TimedOutException
	var err error

	var ks *cassandra.KeySlice
	var rv []*Event
	var duration time.Duration

	if from.After(to) {
		duration = from.Sub(to)
	} else {
		duration = to.Sub(from)
	}

	parent = cassandra.NewColumnParent()
	parent.ColumnFamily = conf.GetEventsColumnFamily()
	clause = cassandra.NewIndexClause()
	clause.StartKey = make([]byte, 0)
	clause.Count = int32((conf.GetMaxEventsPerDay()*int32(duration.Hours()))/24) + 1
	predicate = cassandra.NewSlicePredicate()
	predicate.ColumnNames = kEventAllColumns

	expr = cassandra.NewIndexExpression()
	expr.ColumnName = []byte("week")
	expr.Op = cassandra.IndexOperator_EQ
	expr.Value = make([]byte, 8)
	binary.BigEndian.PutUint64(expr.Value, uint64(getWeekFromTimestamp(from)))
	clause.Expressions = append(clause.Expressions, expr)

	expr = cassandra.NewIndexExpression()
	expr.ColumnName = []byte("start")
	expr.Op = cassandra.IndexOperator_LTE
	expr.Value = make([]byte, 8)
	binary.BigEndian.PutUint64(expr.Value, uint64(to.Unix()*1000))
	clause.Expressions = append(clause.Expressions, expr)

	expr = cassandra.NewIndexExpression()
	expr.ColumnName = []byte("end")
	expr.Op = cassandra.IndexOperator_GTE
	expr.Value = make([]byte, 8)
	binary.BigEndian.PutUint64(expr.Value, uint64(from.Unix()*1000))
	clause.Expressions = append(clause.Expressions, expr)

	if quorum {
		cl = cassandra.ConsistencyLevel_QUORUM
	} else {
		cl = cassandra.ConsistencyLevel_ONE
	}

	res, ire, ue, te, err = db.GetIndexedSlices(
		parent, clause, predicate, cl)
	if err != nil {
		return []*Event{}, err
	}
	if ire != nil {
		err = fmt.Errorf(
			"Invalid request error in index reading from %s to %s: %s",
			from.String(), to.String(), ire.Why)
		return rv, err
	}
	if ue != nil {
		err = fmt.Errorf("Cassandra unavailable when reading from %s to %s",
			from.String(), to.String())
		return rv, err
	}
	if te != nil {
		err = fmt.Errorf("Cassandra timed out when reading from %s to %s: %s",
			from.String(), to.String(), te.String())
		return rv, err
	}

	for _, ks = range res {
		var e *Event = &Event{
			Id: string(ks.Key),
		}

		err = e.extractFromColumns(ks.Columns)
		if err != nil {
			return rv, err
		}

		rv = append(rv, e)
	}

	return rv, nil
}

// Extract event data from a number of columns.
func (e *Event) extractFromColumns(r []*cassandra.ColumnOrSuperColumn) error {
	var cos *cassandra.ColumnOrSuperColumn
	var end time.Time

	for _, cos = range r {
		var col *cassandra.Column = cos.Column
		var cname string

		if col == nil {
			continue
		}

		cname = string(col.Name)

		if cname == "title" {
			e.Title = string(col.Value)
		} else if cname == "description" {
			e.Description = string(col.Value)
		} else if cname == "owner" {
			e.Owner = string(col.Value)
		} else if cname == "start" {
			var start int64

			start = int64(binary.BigEndian.Uint64(col.Value))
			e.Start = time.Unix(start/1000, (start%1000)*1000)
		} else if cname == "end" {
			var end_ts int64

			end_ts = int64(binary.BigEndian.Uint64(col.Value))
			end = time.Unix(end_ts/1000, (end_ts%1000)*1000)
		} else if cname == "required" {
			e.Required = (len(col.Value) > 0 && col.Value[0] > 0)
		}
	}

	if e.Start.After(end) {
		e.Duration = e.Start.Sub(end)
		e.Start = end
	} else {
		e.Duration = end.Sub(e.Start)
	}

	return nil
}

// Generate an event ID (but don't overwrite it).
func (e *Event) genEventID() string {
	var etitle [sha256.Size224]byte
	if len(e.Title) == 0 {
		return ""
	}
	etitle = sha256.Sum224([]byte(e.Title))
	return fmt.Sprintf("%08X:%08X:%s.%s", getWeekFromTimestamp(e.Start),
		e.Start.Unix(), e.Duration.String(), hex.EncodeToString(etitle[:]))
}

// Write the modified event object to the database.
func (e *Event) Sync() error {
	var ire *cassandra.InvalidRequestException
	var ue *cassandra.UnavailableException
	var te *cassandra.TimedOutException
	var mmap map[string]map[string][]*cassandra.Mutation
	var mutations []*cassandra.Mutation
	var mutation *cassandra.Mutation
	var col *cassandra.Column
	var ts int64
	var err error

	if len(e.Id) == 0 {
		e.Id = e.genEventID()
	}

	ts = time.Now().UnixNano()

	col = cassandra.NewColumn()
	col.Name = []byte("title")
	col.Value = []byte(e.Title)
	col.Timestamp = ts

	mutation = cassandra.NewMutation()
	mutation.ColumnOrSupercolumn = cassandra.NewColumnOrSuperColumn()
	mutation.ColumnOrSupercolumn.Column = col
	mutations = append(mutations, mutation)

	col = cassandra.NewColumn()
	col.Name = []byte("description")
	col.Value = []byte(e.Description)
	col.Timestamp = ts

	mutation = cassandra.NewMutation()
	mutation.ColumnOrSupercolumn = cassandra.NewColumnOrSuperColumn()
	mutation.ColumnOrSupercolumn.Column = col
	mutations = append(mutations, mutation)

	col = cassandra.NewColumn()
	col.Name = []byte("owner")
	col.Value = []byte(e.Owner)
	col.Timestamp = ts

	mutation = cassandra.NewMutation()
	mutation.ColumnOrSupercolumn = cassandra.NewColumnOrSuperColumn()
	mutation.ColumnOrSupercolumn.Column = col
	mutations = append(mutations, mutation)

	col = cassandra.NewColumn()
	col.Name = []byte("start")
	binary.BigEndian.PutUint64(
		col.Value, uint64(e.Start.Unix()*1000))
	col.Timestamp = ts

	mutation = cassandra.NewMutation()
	mutation.ColumnOrSupercolumn = cassandra.NewColumnOrSuperColumn()
	mutation.ColumnOrSupercolumn.Column = col
	mutations = append(mutations, mutation)

	col = cassandra.NewColumn()
	col.Name = []byte("end")
	col.Value = make([]byte, 8)
	binary.BigEndian.PutUint64(
		col.Value, uint64(e.Start.Add(e.Duration).Unix()*1000))
	col.Timestamp = ts

	mutation = cassandra.NewMutation()
	mutation.ColumnOrSupercolumn = cassandra.NewColumnOrSuperColumn()
	mutation.ColumnOrSupercolumn.Column = col
	mutations = append(mutations, mutation)

	col = cassandra.NewColumn()
	col.Name = []byte("required")
	if e.Required {
		col.Value = []byte{1}
	} else {
		col.Value = []byte{0}
	}
	col.Timestamp = ts

	mutation = cassandra.NewMutation()
	mutation.ColumnOrSupercolumn = cassandra.NewColumnOrSuperColumn()
	mutation.ColumnOrSupercolumn.Column = col
	mutations = append(mutations, mutation)

	col = cassandra.NewColumn()
	col.Name = []byte("week")
	col.Value = make([]byte, 8)
	binary.BigEndian.PutUint64(
		col.Value, uint64(getWeekFromTimestamp(e.Start)))
	col.Timestamp = ts

	mutation = cassandra.NewMutation()
	mutation.ColumnOrSupercolumn = cassandra.NewColumnOrSuperColumn()
	mutation.ColumnOrSupercolumn.Column = col
	mutations = append(mutations, mutation)

	col = cassandra.NewColumn()
	col.Name = []byte("end")
	col.Value = make([]byte, 8)
	binary.BigEndian.PutUint64(
		col.Value, uint64(e.Start.Add(e.Duration).Unix()*1000))
	col.Timestamp = ts

	mutation = cassandra.NewMutation()
	mutation.ColumnOrSupercolumn = cassandra.NewColumnOrSuperColumn()
	mutation.ColumnOrSupercolumn.Column = col
	mutations = append(mutations, mutation)

	mmap = make(map[string]map[string][]*cassandra.Mutation)
	mmap[e.conf.GetKeyspace()] = make(map[string][]*cassandra.Mutation)
	mmap[e.conf.GetKeyspace()][e.conf.GetEventsColumnFamily()] = mutations

	ire, ue, te, err = e.db.AtomicBatchMutate(mmap,
		cassandra.ConsistencyLevel_QUORUM)
	if err != nil {
		return err
	}
	if ire != nil {
		return fmt.Errorf("Invalid request error in batch mutation: %s",
			ire.Why)
	}
	if ue != nil {
		return fmt.Errorf("Cassandra unavailable when updating %s",
			e.Id)
	}
	if te != nil {
		return fmt.Errorf("Cassandra timed out when updating %s: %s",
			e.Id, te.String())
	}

	return nil
}
<commit_msg>Include the time zone offset in the week computation.<commit_after>package dutycal

import (
	"crypto/sha256"
	"database/cassandra"
	"encoding/binary"
	"encoding/hex"
	"fmt"
	"time"
)

var kEventAllColumns [][]byte = [][]byte{
	[]byte("title"), []byte("description"), []byte("owner"),
	[]byte("start"), []byte("end"),
}

type Event struct {
	db   *cassandra.RetryCassandraClient
	conf *DutyCalConfig

	Id          string
	Title       string
	Description string
	Start       time.Time
	Duration    time.Duration
	Owner       string
	Required    bool
}

func getWeekFromTimestamp(ts time.Time) int64 {
	var offset int
	_, offset = ts.Zone()
	return (ts.Unix() + offset) / (7 * 24 * 60 * 60)
}

// Create a new event with the speicfied details.
func CreateEvent(db *cassandra.RetryCassandraClient, conf *DutyCalConfig,
	title, description, owner string,
	start time.Time, duration time.Duration, required bool) *Event {
	return &Event{
		db:   db,
		conf: conf,

		Title:       title,
		Description: description,
		Start:       start,
		Duration:    duration,
		Owner:       owner,
		Required:    required,
	}
}

// Recreate in-memory event object from database. The record "id" is read
// from the database designated as "db" as specified in the configuration
// "conf". If "quorum" is specified, a quorum read from the database will
// be performed rather than just reading from a single replica.
func FetchEvent(db *cassandra.RetryCassandraClient, conf *DutyCalConfig,
	id string, quorum bool) (rv *Event, err error) {
	var cp *cassandra.ColumnParent = cassandra.NewColumnParent()
	var pred *cassandra.SlicePredicate = cassandra.NewSlicePredicate()
	var cl cassandra.ConsistencyLevel

	var r []*cassandra.ColumnOrSuperColumn
	var ire *cassandra.InvalidRequestException
	var ue *cassandra.UnavailableException
	var te *cassandra.TimedOutException

	cp.ColumnFamily = conf.GetEventsColumnFamily()
	pred.ColumnNames = kEventAllColumns

	if quorum {
		cl = cassandra.ConsistencyLevel_QUORUM
	} else {
		cl = cassandra.ConsistencyLevel_ONE
	}

	r, ire, ue, te, err = db.GetSlice([]byte(id), cp, pred, cl)
	if ire != nil {
		err = fmt.Errorf("Invalid request error fetching event %s: %s",
			id, ire.Why)
		return
	}
	if ue != nil {
		err = fmt.Errorf("Cassandra unavailable fetching event %s", id)
		return
	}
	if te != nil {
		err = fmt.Errorf("Request for %s timed out: %s",
			id, te.String())
		return
	}
	if err != nil {
		return
	}

	rv = &Event{Id: id}
	err = rv.extractFromColumns(r)
	return
}

// Retrieve a list of all events between the two specified dates.
func FetchEventRange(db *cassandra.RetryCassandraClient, conf *DutyCalConfig,
	from, to time.Time, quorum bool) ([]*Event, error) {
	var parent *cassandra.ColumnParent
	var clause *cassandra.IndexClause
	var predicate *cassandra.SlicePredicate
	var expr *cassandra.IndexExpression
	var cl cassandra.ConsistencyLevel

	var res []*cassandra.KeySlice
	var ire *cassandra.InvalidRequestException
	var ue *cassandra.UnavailableException
	var te *cassandra.TimedOutException
	var err error

	var ks *cassandra.KeySlice
	var rv []*Event
	var duration time.Duration

	if from.After(to) {
		duration = from.Sub(to)
	} else {
		duration = to.Sub(from)
	}

	parent = cassandra.NewColumnParent()
	parent.ColumnFamily = conf.GetEventsColumnFamily()
	clause = cassandra.NewIndexClause()
	clause.StartKey = make([]byte, 0)
	clause.Count = int32((conf.GetMaxEventsPerDay()*int32(duration.Hours()))/24) + 1
	predicate = cassandra.NewSlicePredicate()
	predicate.ColumnNames = kEventAllColumns

	expr = cassandra.NewIndexExpression()
	expr.ColumnName = []byte("week")
	expr.Op = cassandra.IndexOperator_EQ
	expr.Value = make([]byte, 8)
	binary.BigEndian.PutUint64(expr.Value, uint64(getWeekFromTimestamp(from)))
	clause.Expressions = append(clause.Expressions, expr)

	expr = cassandra.NewIndexExpression()
	expr.ColumnName = []byte("start")
	expr.Op = cassandra.IndexOperator_LTE
	expr.Value = make([]byte, 8)
	binary.BigEndian.PutUint64(expr.Value, uint64(to.Unix()*1000))
	clause.Expressions = append(clause.Expressions, expr)

	expr = cassandra.NewIndexExpression()
	expr.ColumnName = []byte("end")
	expr.Op = cassandra.IndexOperator_GTE
	expr.Value = make([]byte, 8)
	binary.BigEndian.PutUint64(expr.Value, uint64(from.Unix()*1000))
	clause.Expressions = append(clause.Expressions, expr)

	if quorum {
		cl = cassandra.ConsistencyLevel_QUORUM
	} else {
		cl = cassandra.ConsistencyLevel_ONE
	}

	res, ire, ue, te, err = db.GetIndexedSlices(
		parent, clause, predicate, cl)
	if err != nil {
		return []*Event{}, err
	}
	if ire != nil {
		err = fmt.Errorf(
			"Invalid request error in index reading from %s to %s: %s",
			from.String(), to.String(), ire.Why)
		return rv, err
	}
	if ue != nil {
		err = fmt.Errorf("Cassandra unavailable when reading from %s to %s",
			from.String(), to.String())
		return rv, err
	}
	if te != nil {
		err = fmt.Errorf("Cassandra timed out when reading from %s to %s: %s",
			from.String(), to.String(), te.String())
		return rv, err
	}

	for _, ks = range res {
		var e *Event = &Event{
			Id: string(ks.Key),
		}

		err = e.extractFromColumns(ks.Columns)
		if err != nil {
			return rv, err
		}

		rv = append(rv, e)
	}

	return rv, nil
}

// Extract event data from a number of columns.
func (e *Event) extractFromColumns(r []*cassandra.ColumnOrSuperColumn) error {
	var cos *cassandra.ColumnOrSuperColumn
	var end time.Time

	for _, cos = range r {
		var col *cassandra.Column = cos.Column
		var cname string

		if col == nil {
			continue
		}

		cname = string(col.Name)

		if cname == "title" {
			e.Title = string(col.Value)
		} else if cname == "description" {
			e.Description = string(col.Value)
		} else if cname == "owner" {
			e.Owner = string(col.Value)
		} else if cname == "start" {
			var start int64

			start = int64(binary.BigEndian.Uint64(col.Value))
			e.Start = time.Unix(start/1000, (start%1000)*1000)
		} else if cname == "end" {
			var end_ts int64

			end_ts = int64(binary.BigEndian.Uint64(col.Value))
			end = time.Unix(end_ts/1000, (end_ts%1000)*1000)
		} else if cname == "required" {
			e.Required = (len(col.Value) > 0 && col.Value[0] > 0)
		}
	}

	if e.Start.After(end) {
		e.Duration = e.Start.Sub(end)
		e.Start = end
	} else {
		e.Duration = end.Sub(e.Start)
	}

	return nil
}

// Generate an event ID (but don't overwrite it).
func (e *Event) genEventID() string {
	var etitle [sha256.Size224]byte
	if len(e.Title) == 0 {
		return ""
	}
	etitle = sha256.Sum224([]byte(e.Title))
	return fmt.Sprintf("%08X:%08X:%s.%s", getWeekFromTimestamp(e.Start),
		e.Start.Unix(), e.Duration.String(), hex.EncodeToString(etitle[:]))
}

// Write the modified event object to the database.
func (e *Event) Sync() error {
	var ire *cassandra.InvalidRequestException
	var ue *cassandra.UnavailableException
	var te *cassandra.TimedOutException
	var mmap map[string]map[string][]*cassandra.Mutation
	var mutations []*cassandra.Mutation
	var mutation *cassandra.Mutation
	var col *cassandra.Column
	var ts int64
	var err error

	if len(e.Id) == 0 {
		e.Id = e.genEventID()
	}

	ts = time.Now().UnixNano()

	col = cassandra.NewColumn()
	col.Name = []byte("title")
	col.Value = []byte(e.Title)
	col.Timestamp = ts

	mutation = cassandra.NewMutation()
	mutation.ColumnOrSupercolumn = cassandra.NewColumnOrSuperColumn()
	mutation.ColumnOrSupercolumn.Column = col
	mutations = append(mutations, mutation)

	col = cassandra.NewColumn()
	col.Name = []byte("description")
	col.Value = []byte(e.Description)
	col.Timestamp = ts

	mutation = cassandra.NewMutation()
	mutation.ColumnOrSupercolumn = cassandra.NewColumnOrSuperColumn()
	mutation.ColumnOrSupercolumn.Column = col
	mutations = append(mutations, mutation)

	col = cassandra.NewColumn()
	col.Name = []byte("owner")
	col.Value = []byte(e.Owner)
	col.Timestamp = ts

	mutation = cassandra.NewMutation()
	mutation.ColumnOrSupercolumn = cassandra.NewColumnOrSuperColumn()
	mutation.ColumnOrSupercolumn.Column = col
	mutations = append(mutations, mutation)

	col = cassandra.NewColumn()
	col.Name = []byte("start")
	binary.BigEndian.PutUint64(
		col.Value, uint64(e.Start.Unix()*1000))
	col.Timestamp = ts

	mutation = cassandra.NewMutation()
	mutation.ColumnOrSupercolumn = cassandra.NewColumnOrSuperColumn()
	mutation.ColumnOrSupercolumn.Column = col
	mutations = append(mutations, mutation)

	col = cassandra.NewColumn()
	col.Name = []byte("end")
	col.Value = make([]byte, 8)
	binary.BigEndian.PutUint64(
		col.Value, uint64(e.Start.Add(e.Duration).Unix()*1000))
	col.Timestamp = ts

	mutation = cassandra.NewMutation()
	mutation.ColumnOrSupercolumn = cassandra.NewColumnOrSuperColumn()
	mutation.ColumnOrSupercolumn.Column = col
	mutations = append(mutations, mutation)

	col = cassandra.NewColumn()
	col.Name = []byte("required")
	if e.Required {
		col.Value = []byte{1}
	} else {
		col.Value = []byte{0}
	}
	col.Timestamp = ts

	mutation = cassandra.NewMutation()
	mutation.ColumnOrSupercolumn = cassandra.NewColumnOrSuperColumn()
	mutation.ColumnOrSupercolumn.Column = col
	mutations = append(mutations, mutation)

	col = cassandra.NewColumn()
	col.Name = []byte("week")
	col.Value = make([]byte, 8)
	binary.BigEndian.PutUint64(
		col.Value, uint64(getWeekFromTimestamp(e.Start)))
	col.Timestamp = ts

	mutation = cassandra.NewMutation()
	mutation.ColumnOrSupercolumn = cassandra.NewColumnOrSuperColumn()
	mutation.ColumnOrSupercolumn.Column = col
	mutations = append(mutations, mutation)

	col = cassandra.NewColumn()
	col.Name = []byte("end")
	col.Value = make([]byte, 8)
	binary.BigEndian.PutUint64(
		col.Value, uint64(e.Start.Add(e.Duration).Unix()*1000))
	col.Timestamp = ts

	mutation = cassandra.NewMutation()
	mutation.ColumnOrSupercolumn = cassandra.NewColumnOrSuperColumn()
	mutation.ColumnOrSupercolumn.Column = col
	mutations = append(mutations, mutation)

	mmap = make(map[string]map[string][]*cassandra.Mutation)
	mmap[e.conf.GetKeyspace()] = make(map[string][]*cassandra.Mutation)
	mmap[e.conf.GetKeyspace()][e.conf.GetEventsColumnFamily()] = mutations

	ire, ue, te, err = e.db.AtomicBatchMutate(mmap,
		cassandra.ConsistencyLevel_QUORUM)
	if err != nil {
		return err
	}
	if ire != nil {
		return fmt.Errorf("Invalid request error in batch mutation: %s",
			ire.Why)
	}
	if ue != nil {
		return fmt.Errorf("Cassandra unavailable when updating %s",
			e.Id)
	}
	if te != nil {
		return fmt.Errorf("Cassandra timed out when updating %s: %s",
			e.Id, te.String())
	}

	return nil
}
<|endoftext|>


<commit_before>// Copyright 2018 Keybase Inc. All rights reserved.
// Use of this source code is governed by a BSD
// license that can be found in the LICENSE file.

package test

import (
	"fmt"
	"testing"
	"time"

	"github.com/keybase/client/go/protocol/keybase1"
)

func TestEditHistorySimple(t *testing.T) {
	// Bob writes one file.
	expectedEdits1 := []expectedEdit{
		{
			"alice,bob",
			keybase1.FolderType_PRIVATE,
			"bob",
			[]string{"/keybase/private/alice,bob/a/b"},
		},
	}
	// Alice writes one file.
	expectedEdits2 := []expectedEdit{
		{
			"alice,bob",
			keybase1.FolderType_PRIVATE,
			"alice",
			[]string{"/keybase/private/alice,bob/a/c"},
		},
		expectedEdits1[0],
	}
	// Bob overwrites his first file.
	expectedEdits3 := []expectedEdit{
		expectedEdits1[0],
		expectedEdits2[0],
	}

	test(t,
		users("alice", "bob"),
		as(alice,
			mkdir("a"),
		),
		as(bob,
			mkfile("a/b", "hello"),
		),
		as(bob,
			checkUserEditHistory(expectedEdits1),
		),
		as(alice,
			checkUserEditHistory(expectedEdits1),
		),
		as(alice,
			addTime(1*time.Minute),
			mkfile("a/c", "hello2"),
		),
		as(bob,
			checkUserEditHistory(expectedEdits2),
		),
		as(alice,
			checkUserEditHistory(expectedEdits2),
		),
		as(bob,
			addTime(1*time.Minute),
			write("a/b", "hello again"),
		),
		as(bob,
			checkUserEditHistory(expectedEdits3),
		),
		as(alice,
			checkUserEditHistory(expectedEdits3),
		),
	)
}

func TestEditHistoryMultiTlf(t *testing.T) {
	// Bob writes one file to private.
	expectedEdits1 := []expectedEdit{
		{
			"alice,bob",
			keybase1.FolderType_PRIVATE,
			"bob",
			[]string{"/keybase/private/alice,bob/a"},
		},
	}
	// Alice writes one file to public.
	expectedEdits2 := []expectedEdit{
		{
			"alice,bob",
			keybase1.FolderType_PUBLIC,
			"alice",
			[]string{"/keybase/public/alice,bob/b"},
		},
		expectedEdits1[0],
	}
	// Bob writes one file to team TLF.
	expectedEdits3 := []expectedEdit{
		{
			"ab",
			keybase1.FolderType_TEAM,
			"bob",
			[]string{"/keybase/team/ab/c"},
		},
		expectedEdits2[0],
		expectedEdits1[0],
	}

	test(t,
		users("alice", "bob"),
		team("ab", "alice,bob", ""),
		as(bob,
			mkfile("a", "hello"),
		),
		as(bob,
			checkUserEditHistory(expectedEdits1),
		),
		as(alice,
			checkUserEditHistory(expectedEdits1),
		),
		inPublicTlf("alice,bob"),
		as(alice,
			addTime(1*time.Minute),
			mkfile("b", "hello"),
		),
		as(bob,
			checkUserEditHistory(expectedEdits2),
		),
		as(alice,
			checkUserEditHistory(expectedEdits2),
		),
		inSingleTeamTlf("ab"),
		as(bob,
			addTime(1*time.Minute),
			write("c", "hello again"),
		),
		as(bob,
			checkUserEditHistory(expectedEdits3),
		),
		as(alice,
			checkUserEditHistory(expectedEdits3),
		),
	)
}

func TestEditHistorySelfClusters(t *testing.T) {
	// Bob writes one file to private.
	expectedEdits1 := []expectedEdit{
		{
			"alice,bob",
			keybase1.FolderType_PRIVATE,
			"bob",
			[]string{"/keybase/private/alice,bob/a"},
		},
	}
	// Alice writes to ten team TLFs, but bob should still see his own
	// write from above.
	expectedEdits2Alice := make([]expectedEdit, 0, 10)
	expectedEdits2Bob := make([]expectedEdit, 0, 10)
	for i := 9; i >= 0; i-- {
		team := fmt.Sprintf("ab%d", i)
		e := expectedEdit{
			team,
			keybase1.FolderType_TEAM,
			"alice",
			[]string{fmt.Sprintf("/keybase/team/%s/a", team)},
		}
		expectedEdits2Alice = append(expectedEdits2Alice, e)
		expectedEdits2Bob = append(expectedEdits2Bob, e)
	}
	expectedEdits2Bob[9] = expectedEdits1[0]

	test(t,
		users("alice", "bob"),
		team("ab0", "alice,bob", ""),
		team("ab1", "alice,bob", ""),
		team("ab2", "alice,bob", ""),
		team("ab3", "alice,bob", ""),
		team("ab4", "alice,bob", ""),
		team("ab5", "alice,bob", ""),
		team("ab6", "alice,bob", ""),
		team("ab7", "alice,bob", ""),
		team("ab8", "alice,bob", ""),
		team("ab9", "alice,bob", ""),
		as(bob,
			mkfile("a", "hello"),
		),
		as(bob,
			checkUserEditHistory(expectedEdits1),
		),
		as(alice,
			checkUserEditHistory(expectedEdits1),
		),
		inSingleTeamTlf("ab0"),
		as(alice,
			addTime(1*time.Minute),
			mkfile("a", "hello"),
		),
		inSingleTeamTlf("ab1"),
		as(alice,
			addTime(1*time.Minute),
			mkfile("a", "hello"),
		),
		inSingleTeamTlf("ab2"),
		as(alice,
			addTime(1*time.Minute),
			mkfile("a", "hello"),
		),
		inSingleTeamTlf("ab3"),
		as(alice,
			addTime(1*time.Minute),
			mkfile("a", "hello"),
		),
		inSingleTeamTlf("ab4"),
		as(alice,
			addTime(1*time.Minute),
			mkfile("a", "hello"),
		),
		inSingleTeamTlf("ab5"),
		as(alice,
			addTime(1*time.Minute),
			mkfile("a", "hello"),
		),
		inSingleTeamTlf("ab6"),
		as(alice,
			addTime(1*time.Minute),
			mkfile("a", "hello"),
		),
		inSingleTeamTlf("ab7"),
		as(alice,
			addTime(1*time.Minute),
			mkfile("a", "hello"),
		),
		inSingleTeamTlf("ab8"),
		as(alice,
			addTime(1*time.Minute),
			mkfile("a", "hello"),
		),
		inSingleTeamTlf("ab9"),
		as(alice,
			addTime(1*time.Minute),
			mkfile("a", "hello"),
		),
		as(bob,
			checkUserEditHistory(expectedEdits2Bob),
		),
		as(alice,
			checkUserEditHistory(expectedEdits2Alice),
		),
	)
}
<commit_msg>test: unflushed edit history test<commit_after>// Copyright 2018 Keybase Inc. All rights reserved.
// Use of this source code is governed by a BSD
// license that can be found in the LICENSE file.

package test

import (
	"fmt"
	"testing"
	"time"

	"github.com/keybase/client/go/protocol/keybase1"
)

func TestEditHistorySimple(t *testing.T) {
	// Bob writes one file.
	expectedEdits1 := []expectedEdit{
		{
			"alice,bob",
			keybase1.FolderType_PRIVATE,
			"bob",
			[]string{"/keybase/private/alice,bob/a/b"},
		},
	}
	// Alice writes one file.
	expectedEdits2 := []expectedEdit{
		{
			"alice,bob",
			keybase1.FolderType_PRIVATE,
			"alice",
			[]string{"/keybase/private/alice,bob/a/c"},
		},
		expectedEdits1[0],
	}
	// Bob overwrites his first file.
	expectedEdits3 := []expectedEdit{
		expectedEdits1[0],
		expectedEdits2[0],
	}

	test(t,
		users("alice", "bob"),
		as(alice,
			mkdir("a"),
		),
		as(bob,
			mkfile("a/b", "hello"),
		),
		as(bob,
			checkUserEditHistory(expectedEdits1),
		),
		as(alice,
			checkUserEditHistory(expectedEdits1),
		),
		as(alice,
			addTime(1*time.Minute),
			mkfile("a/c", "hello2"),
		),
		as(bob,
			checkUserEditHistory(expectedEdits2),
		),
		as(alice,
			checkUserEditHistory(expectedEdits2),
		),
		as(bob,
			addTime(1*time.Minute),
			write("a/b", "hello again"),
		),
		as(bob,
			checkUserEditHistory(expectedEdits3),
		),
		as(alice,
			checkUserEditHistory(expectedEdits3),
		),
	)
}

func TestEditHistoryMultiTlf(t *testing.T) {
	// Bob writes one file to private.
	expectedEdits1 := []expectedEdit{
		{
			"alice,bob",
			keybase1.FolderType_PRIVATE,
			"bob",
			[]string{"/keybase/private/alice,bob/a"},
		},
	}
	// Alice writes one file to public.
	expectedEdits2 := []expectedEdit{
		{
			"alice,bob",
			keybase1.FolderType_PUBLIC,
			"alice",
			[]string{"/keybase/public/alice,bob/b"},
		},
		expectedEdits1[0],
	}
	// Bob writes one file to team TLF.
	expectedEdits3 := []expectedEdit{
		{
			"ab",
			keybase1.FolderType_TEAM,
			"bob",
			[]string{"/keybase/team/ab/c"},
		},
		expectedEdits2[0],
		expectedEdits1[0],
	}

	test(t,
		users("alice", "bob"),
		team("ab", "alice,bob", ""),
		as(bob,
			mkfile("a", "hello"),
		),
		as(bob,
			checkUserEditHistory(expectedEdits1),
		),
		as(alice,
			checkUserEditHistory(expectedEdits1),
		),
		inPublicTlf("alice,bob"),
		as(alice,
			addTime(1*time.Minute),
			mkfile("b", "hello"),
		),
		as(bob,
			checkUserEditHistory(expectedEdits2),
		),
		as(alice,
			checkUserEditHistory(expectedEdits2),
		),
		inSingleTeamTlf("ab"),
		as(bob,
			addTime(1*time.Minute),
			write("c", "hello again"),
		),
		as(bob,
			checkUserEditHistory(expectedEdits3),
		),
		as(alice,
			checkUserEditHistory(expectedEdits3),
		),
	)
}

func TestEditHistorySelfClusters(t *testing.T) {
	// Bob writes one file to private.
	expectedEdits1 := []expectedEdit{
		{
			"alice,bob",
			keybase1.FolderType_PRIVATE,
			"bob",
			[]string{"/keybase/private/alice,bob/a"},
		},
	}
	// Alice writes to ten team TLFs, but bob should still see his own
	// write from above.
	expectedEdits2Alice := make([]expectedEdit, 0, 10)
	expectedEdits2Bob := make([]expectedEdit, 0, 10)
	for i := 9; i >= 0; i-- {
		team := fmt.Sprintf("ab%d", i)
		e := expectedEdit{
			team,
			keybase1.FolderType_TEAM,
			"alice",
			[]string{fmt.Sprintf("/keybase/team/%s/a", team)},
		}
		expectedEdits2Alice = append(expectedEdits2Alice, e)
		expectedEdits2Bob = append(expectedEdits2Bob, e)
	}
	expectedEdits2Bob[9] = expectedEdits1[0]

	test(t,
		users("alice", "bob"),
		team("ab0", "alice,bob", ""),
		team("ab1", "alice,bob", ""),
		team("ab2", "alice,bob", ""),
		team("ab3", "alice,bob", ""),
		team("ab4", "alice,bob", ""),
		team("ab5", "alice,bob", ""),
		team("ab6", "alice,bob", ""),
		team("ab7", "alice,bob", ""),
		team("ab8", "alice,bob", ""),
		team("ab9", "alice,bob", ""),
		as(bob,
			mkfile("a", "hello"),
		),
		as(bob,
			checkUserEditHistory(expectedEdits1),
		),
		as(alice,
			checkUserEditHistory(expectedEdits1),
		),
		inSingleTeamTlf("ab0"),
		as(alice,
			addTime(1*time.Minute),
			mkfile("a", "hello"),
		),
		inSingleTeamTlf("ab1"),
		as(alice,
			addTime(1*time.Minute),
			mkfile("a", "hello"),
		),
		inSingleTeamTlf("ab2"),
		as(alice,
			addTime(1*time.Minute),
			mkfile("a", "hello"),
		),
		inSingleTeamTlf("ab3"),
		as(alice,
			addTime(1*time.Minute),
			mkfile("a", "hello"),
		),
		inSingleTeamTlf("ab4"),
		as(alice,
			addTime(1*time.Minute),
			mkfile("a", "hello"),
		),
		inSingleTeamTlf("ab5"),
		as(alice,
			addTime(1*time.Minute),
			mkfile("a", "hello"),
		),
		inSingleTeamTlf("ab6"),
		as(alice,
			addTime(1*time.Minute),
			mkfile("a", "hello"),
		),
		inSingleTeamTlf("ab7"),
		as(alice,
			addTime(1*time.Minute),
			mkfile("a", "hello"),
		),
		inSingleTeamTlf("ab8"),
		as(alice,
			addTime(1*time.Minute),
			mkfile("a", "hello"),
		),
		inSingleTeamTlf("ab9"),
		as(alice,
			addTime(1*time.Minute),
			mkfile("a", "hello"),
		),
		as(bob,
			checkUserEditHistory(expectedEdits2Bob),
		),
		as(alice,
			checkUserEditHistory(expectedEdits2Alice),
		),
	)
}

func TestEditHistoryUnflushed(t *testing.T) {
	// Bob writes one file.
	expectedEdits1 := []expectedEdit{
		{
			"alice,bob",
			keybase1.FolderType_PRIVATE,
			"bob",
			[]string{"/keybase/private/alice,bob/a/b"},
		},
	}
	// Alice and Bob both write a second file, but alice's is unflushed.
	expectedEdits2Alice := []expectedEdit{
		{
			"alice,bob",
			keybase1.FolderType_PRIVATE,
			"alice",
			[]string{"/keybase/private/alice,bob/a/c"},
		},
		expectedEdits1[0],
	}
	expectedEdits2Bob := []expectedEdit{
		{
			"alice,bob",
			keybase1.FolderType_PRIVATE,
			"bob",
			[]string{
				"/keybase/private/alice,bob/a/d",
				"/keybase/private/alice,bob/a/b",
			},
		},
	}
	// Alice runs CR and flushes her journal.
	expectedEdits3 := []expectedEdit{
		expectedEdits2Alice[0],
		expectedEdits2Bob[0],
	}

	test(t, journal(),
		users("alice", "bob"),
		as(alice,
			enableJournal(),
			mkdir("a"),
		),
		as(bob,
			mkfile("a/b", "hello"),
		),
		as(bob,
			checkUserEditHistory(expectedEdits1),
		),
		as(alice,
			checkUserEditHistory(expectedEdits1),
		),
		as(alice,
			pauseJournal(),
			addTime(1*time.Minute),
			mkfile("a/c", "hello2"),
		),
		as(bob,
			addTime(1*time.Minute),
			mkfile("a/d", "hello"),
		),
		as(bob,
			checkUserEditHistory(expectedEdits2Bob),
		),
		as(alice, noSync(),
			checkUserEditHistory(expectedEdits2Alice),
		),
		as(alice, noSync(),
			resumeJournal(),
			// This should kick off conflict resolution.
			flushJournal(),
		),
		as(alice,
			checkUserEditHistory(expectedEdits3),
		),
		as(bob,
			checkUserEditHistory(expectedEdits3),
		),
	)
}
<|endoftext|>


<commit_before>package packer

import (
	"github.com/mitchellh/iochan"
	"io"
	"strings"
	"sync"
)

// RemoteCmd represents a remote command being prepared or run.
type RemoteCmd struct {
	// Command is the command to run remotely. This is executed as if
	// it were a shell command, so you are expected to do any shell escaping
	// necessary.
	Command string

	// Stdin specifies the process's standard input. If Stdin is
	// nil, the process reads from an empty bytes.Buffer.
	Stdin io.Reader

	// Stdout and Stderr represent the process's standard output and
	// error.
	//
	// If either is nil, it will be set to ioutil.Discard.
	Stdout io.Writer
	Stderr io.Writer

	// This will be set to true when the remote command has exited. It
	// shouldn't be set manually by the user, but there is no harm in
	// doing so.
	Exited bool

	// Once Exited is true, this will contain the exit code of the process.
	ExitStatus int

	// Internal locks and such used for safely setting some shared variables
	l      sync.Mutex
	exitCh chan struct{}
}

// A Communicator is the interface used to communicate with the machine
// that exists that will eventually be packaged into an image. Communicators
// allow you to execute remote commands, upload files, etc.
//
// Communicators must be safe for concurrency, meaning multiple calls to
// Start or any other method may be called at the same time.
type Communicator interface {
	// Start takes a RemoteCmd and starts it. The RemoteCmd must not be
	// modified after being used with Start, and it must not be used with
	// Start again. The Start method returns immediately once the command
	// is started. It does not wait for the command to complete. The
	// RemoteCmd.Exited field should be used for this.
	Start(*RemoteCmd) error

	// Upload uploads a file to the machine to the given path with the
	// contents coming from the given reader. This method will block until
	// it completes.
	Upload(string, io.Reader) error

	// Download downloads a file from the machine from the given remote path
	// with the contents writing to the given writer. This method will
	// block until it completes.
	Download(string, io.Writer) error
}

// StartWithUi runs the remote command and streams the output to any
// configured Writers for stdout/stderr, while also writing each line
// as it comes to a Ui.
func (r *RemoteCmd) StartWithUi(c Communicator, ui Ui) error {
	stdout_r, stdout_w := io.Pipe()
	stderr_r, stderr_w := io.Pipe()
	defer stdout_w.Close()
	defer stderr_w.Close()

	// Retain the original stdout/stderr that we can replace back in.
	originalStdout := r.Stdout
	originalStderr := r.Stderr
	defer func() {
		r.Stdout = originalStdout
		r.Stderr = originalStderr
	}()

	// Set the writers for the output so that we get it streamed to us
	if r.Stdout == nil {
		r.Stdout = stdout_w
	} else {
		r.Stdout = io.MultiWriter(r.Stdout, stdout_w)
	}

	if r.Stderr == nil {
		r.Stderr = stderr_w
	} else {
		r.Stderr = io.MultiWriter(r.Stderr, stderr_w)
	}

	// Start the command
	if err := c.Start(r); err != nil {
		return err
	}

	// Create the channels we'll use for data
	exitCh := make(chan int, 1)
	stdoutCh := iochan.DelimReader(stdout_r, '\n')
	stderrCh := iochan.DelimReader(stderr_r, '\n')

	// Start the goroutine to watch for the exit
	go func() {
		defer stdout_w.Close()
		defer stderr_w.Close()
		r.Wait()
		exitCh <- r.ExitStatus
	}()

	// Loop and get all our output
OutputLoop:
	for {
		select {
		case output := <-stderrCh:
			ui.Message(strings.TrimSpace(output))
		case output := <-stdoutCh:
			ui.Message(strings.TrimSpace(output))
		case <-exitCh:
			break OutputLoop
		}
	}

	// Make sure we finish off stdout/stderr because we may have gotten
	// a message from the exit channel before finishing these first.
	for output := range stdoutCh {
		ui.Message(strings.TrimSpace(output))
	}

	for output := range stderrCh {
		ui.Message(strings.TrimSpace(output))
	}

	return nil
}

// SetExited is a helper for setting that this process is exited. This
// should be called by communicators who are running a remote command in
// order to set that the command is done.
func (r *RemoteCmd) SetExited(status int) {
	r.l.Lock()
	defer r.l.Unlock()

	if r.exitCh == nil {
		r.exitCh = make(chan struct{})
	}

	r.Exited = true
	r.ExitStatus = status
	close(r.exitCh)
}

// Wait waits for the remote command to complete.
func (r *RemoteCmd) Wait() {
	// Make sure our condition variable is initialized.
	r.l.Lock()
	if r.exitCh == nil {
		r.exitCh = make(chan struct{})
	}
	r.l.Unlock()

	<-r.exitCh
}
<commit_msg>packer: Don't output up to \r with remote command, lost anyways<commit_after>package packer

import (
	"github.com/mitchellh/iochan"
	"io"
	"strings"
	"sync"
)

// RemoteCmd represents a remote command being prepared or run.
type RemoteCmd struct {
	// Command is the command to run remotely. This is executed as if
	// it were a shell command, so you are expected to do any shell escaping
	// necessary.
	Command string

	// Stdin specifies the process's standard input. If Stdin is
	// nil, the process reads from an empty bytes.Buffer.
	Stdin io.Reader

	// Stdout and Stderr represent the process's standard output and
	// error.
	//
	// If either is nil, it will be set to ioutil.Discard.
	Stdout io.Writer
	Stderr io.Writer

	// This will be set to true when the remote command has exited. It
	// shouldn't be set manually by the user, but there is no harm in
	// doing so.
	Exited bool

	// Once Exited is true, this will contain the exit code of the process.
	ExitStatus int

	// Internal locks and such used for safely setting some shared variables
	l      sync.Mutex
	exitCh chan struct{}
}

// A Communicator is the interface used to communicate with the machine
// that exists that will eventually be packaged into an image. Communicators
// allow you to execute remote commands, upload files, etc.
//
// Communicators must be safe for concurrency, meaning multiple calls to
// Start or any other method may be called at the same time.
type Communicator interface {
	// Start takes a RemoteCmd and starts it. The RemoteCmd must not be
	// modified after being used with Start, and it must not be used with
	// Start again. The Start method returns immediately once the command
	// is started. It does not wait for the command to complete. The
	// RemoteCmd.Exited field should be used for this.
	Start(*RemoteCmd) error

	// Upload uploads a file to the machine to the given path with the
	// contents coming from the given reader. This method will block until
	// it completes.
	Upload(string, io.Reader) error

	// Download downloads a file from the machine from the given remote path
	// with the contents writing to the given writer. This method will
	// block until it completes.
	Download(string, io.Writer) error
}

// StartWithUi runs the remote command and streams the output to any
// configured Writers for stdout/stderr, while also writing each line
// as it comes to a Ui.
func (r *RemoteCmd) StartWithUi(c Communicator, ui Ui) error {
	stdout_r, stdout_w := io.Pipe()
	stderr_r, stderr_w := io.Pipe()
	defer stdout_w.Close()
	defer stderr_w.Close()

	// Retain the original stdout/stderr that we can replace back in.
	originalStdout := r.Stdout
	originalStderr := r.Stderr
	defer func() {
		r.Stdout = originalStdout
		r.Stderr = originalStderr
	}()

	// Set the writers for the output so that we get it streamed to us
	if r.Stdout == nil {
		r.Stdout = stdout_w
	} else {
		r.Stdout = io.MultiWriter(r.Stdout, stdout_w)
	}

	if r.Stderr == nil {
		r.Stderr = stderr_w
	} else {
		r.Stderr = io.MultiWriter(r.Stderr, stderr_w)
	}

	// Start the command
	if err := c.Start(r); err != nil {
		return err
	}

	// Create the channels we'll use for data
	exitCh := make(chan int, 1)
	stdoutCh := iochan.DelimReader(stdout_r, '\n')
	stderrCh := iochan.DelimReader(stderr_r, '\n')

	// Start the goroutine to watch for the exit
	go func() {
		defer stdout_w.Close()
		defer stderr_w.Close()
		r.Wait()
		exitCh <- r.ExitStatus
	}()

	// Loop and get all our output
OutputLoop:
	for {
		select {
		case output := <-stderrCh:
			ui.Message(r.cleanOutputLine(output))
		case output := <-stdoutCh:
			ui.Message(r.cleanOutputLine(output))
		case <-exitCh:
			break OutputLoop
		}
	}

	// Make sure we finish off stdout/stderr because we may have gotten
	// a message from the exit channel before finishing these first.
	for output := range stdoutCh {
		ui.Message(strings.TrimSpace(output))
	}

	for output := range stderrCh {
		ui.Message(strings.TrimSpace(output))
	}

	return nil
}

// SetExited is a helper for setting that this process is exited. This
// should be called by communicators who are running a remote command in
// order to set that the command is done.
func (r *RemoteCmd) SetExited(status int) {
	r.l.Lock()
	defer r.l.Unlock()

	if r.exitCh == nil {
		r.exitCh = make(chan struct{})
	}

	r.Exited = true
	r.ExitStatus = status
	close(r.exitCh)
}

// Wait waits for the remote command to complete.
func (r *RemoteCmd) Wait() {
	// Make sure our condition variable is initialized.
	r.l.Lock()
	if r.exitCh == nil {
		r.exitCh = make(chan struct{})
	}
	r.l.Unlock()

	<-r.exitCh
}

// cleanOutputLine cleans up a line so that '\r' don't muck up the
// UI output when we're reading from a remote command.
func (r *RemoteCmd) cleanOutputLine(line string) string {
	// Trim surrounding whitespace
	line = strings.TrimSpace(line)

	// Trim up to the first carriage return, since that text would be
	// lost anyways.
	idx := strings.LastIndex(line, "\r")
	if idx > -1 {
		line = line[idx+1:]
	}

	return line
}
<|endoftext|>