<commit_before>package action_test

import (
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"

	. "github.com/maximilien/bosh-softlayer-cpi/action"

	boshlog "github.com/cloudfoundry/bosh-agent/logger"

	fakecmd "github.com/cloudfoundry/bosh-agent/platform/commands/fakes"
	fakesys "github.com/cloudfoundry/bosh-agent/system/fakes"

	fakeslclient "github.com/maximilien/softLayer-go/client/fakes"

	bslcdisk "github.com/maximilien/bosh-softlayer-cpi/softlayer/disk"
	bslcstem "github.com/maximilien/bosh-softlayer-cpi/softlayer/stemcell"
	bslcvm "github.com/maximilien/bosh-softlayer-cpi/softlayer/vm"
)

var _ = Describe("concreteFactory", func() {
	var (
		softLayerClient *fakeslclient.FakeSoftLayerClient
		fs              *fakesys.FakeFileSystem
		cmdRunner       *fakesys.FakeCmdRunner
		compressor      *fakecmd.FakeCompressor
		logger          boshlog.Logger

		options = ConcreteFactoryOptions{
			StemcellsDir: "/tmp/stemcells",
		}

		factory Factory
	)

	var (
		agentEnvServiceFactory bslcvm.AgentEnvServiceFactory

		stemcellFinder bslcstem.Finder
		vmFinder       bslcvm.Finder
	)

	BeforeEach(func() {
		softLayerClient = fakeslclient.NewFakeSoftLayerClient("fake-username", "fake-api-key")
		fs = fakesys.NewFakeFileSystem()
		cmdRunner = fakesys.NewFakeCmdRunner()
		compressor = fakecmd.NewFakeCompressor()
		logger = boshlog.NewLogger(boshlog.LevelNone)

		factory = NewConcreteFactory(
			softLayerClient,
			options,
			logger,
		)
	})

	BeforeEach(func() {
		agentEnvServiceFactory = bslcvm.NewSoftLayerAgentEnvServiceFactory(logger)

		stemcellFinder = bslcstem.NewFSFinder(softLayerClient, logger)

		vmFinder = bslcvm.NewSoftLayerFinder(
			softLayerClient,
			agentEnvServiceFactory,
			logger,
		)
	})

	It("returns error if action cannot be created", func() {
		action, err := factory.Create("fake-unknown-action")
		Expect(err).To(HaveOccurred())
		Expect(action).To(BeNil())
	})

	It("create_stemcell", func() {
		stemcellImporter := bslcstem.NewFSImporter(logger)

		action, err := factory.Create("create_stemcell")
		Expect(err).ToNot(HaveOccurred())
		Expect(action).To(Equal(NewCreateStemcell(stemcellImporter)))
	})

	It("delete_stemcell", func() {
		action, err := factory.Create("delete_stemcell")
		Expect(err).ToNot(HaveOccurred())
		Expect(action).To(Equal(NewDeleteStemcell(stemcellFinder)))
	})

	It("create_vm", func() {
		vmCreator := bslcvm.NewSoftLayerCreator(
			softLayerClient,
			agentEnvServiceFactory,
			options.Agent,
			logger,
		)

		action, err := factory.Create("create_vm")
		Expect(err).ToNot(HaveOccurred())
		Expect(action).To(Equal(NewCreateVM(stemcellFinder, vmCreator)))
	})

	It("delete_vm", func() {
		action, err := factory.Create("delete_vm")
		Expect(err).ToNot(HaveOccurred())
		Expect(action).To(Equal(NewDeleteVM(vmFinder)))
	})

	It("has_vm", func() {
		action, err := factory.Create("has_vm")
		Expect(err).ToNot(HaveOccurred())
		Expect(action).To(Equal(NewHasVM(vmFinder)))
	})

	It("reboot_vm", func() {
		action, err := factory.Create("reboot_vm")
		Expect(err).ToNot(HaveOccurred())
		Expect(action).To(Equal(NewRebootVM()))
	})

	It("set_vm_metadata", func() {
		action, err := factory.Create("set_vm_metadata")
		Expect(err).ToNot(HaveOccurred())
		Expect(action).To(Equal(NewSetVMMetadata()))
	})

	It("configure_networks", func() {
		action, err := factory.Create("configure_networks")
		Expect(err).ToNot(HaveOccurred())
		Expect(action).To(Equal(NewConfigureNetworks()))
	})

	It("create_disk", func() {
		diskCreator := bslcdisk.NewFSCreator(
			"/tmp/disks",
			fs,
			cmdRunner,
			logger,
		)

		action, err := factory.Create("create_disk")
		Expect(err).ToNot(HaveOccurred())
		Expect(action).To(Equal(NewCreateDisk(diskCreator)))
	})

	It("delete_disk", func() {
		action, err := factory.Create("delete_disk")
		Expect(err).ToNot(HaveOccurred())
		Expect(action).To(BeNil())
	})

	It("attach_disk", func() {
		action, err := factory.Create("attach_disk")
		Expect(err).ToNot(HaveOccurred())
		Expect(action).To(BeNil())
	})

	It("detach_disk", func() {
		action, err := factory.Create("detach_disk")
		Expect(err).ToNot(HaveOccurred())
		Expect(action).To(BeNil())
	})

	It("returns error because CPI machine is not self-aware if action is current_vm_id", func() {
		action, err := factory.Create("current_vm_id")
		Expect(err).To(HaveOccurred())
		Expect(action).To(BeNil())
	})

	It("returns error because snapshotting is not implemented if action is snapshot_disk", func() {
		action, err := factory.Create("snapshot_disk")
		Expect(err).To(HaveOccurred())
		Expect(action).To(BeNil())
	})

	It("returns error because snapshotting is not implemented if action is delete_snapshot", func() {
		action, err := factory.Create("delete_snapshot")
		Expect(err).To(HaveOccurred())
		Expect(action).To(BeNil())
	})

	It("returns error since CPI should not keep state if action is get_disks", func() {
		action, err := factory.Create("get_disks")
		Expect(err).To(HaveOccurred())
		Expect(action).To(BeNil())
	})

	It("returns error because ping is not official CPI method if action is ping", func() {
		action, err := factory.Create("ping")
		Expect(err).To(HaveOccurred())
		Expect(action).To(BeNil())
	})
})
<commit_msg>disabling all disk related tests since disk actions are not yet implemented<commit_after>package action_test

import (
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"

	. "github.com/maximilien/bosh-softlayer-cpi/action"

	boshlog "github.com/cloudfoundry/bosh-agent/logger"

	fakecmd "github.com/cloudfoundry/bosh-agent/platform/commands/fakes"
	fakesys "github.com/cloudfoundry/bosh-agent/system/fakes"

	fakeslclient "github.com/maximilien/softLayer-go/client/fakes"

	bslcdisk "github.com/maximilien/bosh-softlayer-cpi/softlayer/disk"
	bslcstem "github.com/maximilien/bosh-softlayer-cpi/softlayer/stemcell"
	bslcvm "github.com/maximilien/bosh-softlayer-cpi/softlayer/vm"
)

var _ = Describe("concreteFactory", func() {
	var (
		softLayerClient *fakeslclient.FakeSoftLayerClient
		fs              *fakesys.FakeFileSystem
		cmdRunner       *fakesys.FakeCmdRunner
		compressor      *fakecmd.FakeCompressor
		logger          boshlog.Logger

		options = ConcreteFactoryOptions{
			StemcellsDir: "/tmp/stemcells",
		}

		factory Factory
	)

	var (
		agentEnvServiceFactory bslcvm.AgentEnvServiceFactory

		stemcellFinder bslcstem.Finder
		vmFinder       bslcvm.Finder
	)

	BeforeEach(func() {
		softLayerClient = fakeslclient.NewFakeSoftLayerClient("fake-username", "fake-api-key")
		fs = fakesys.NewFakeFileSystem()
		cmdRunner = fakesys.NewFakeCmdRunner()
		compressor = fakecmd.NewFakeCompressor()
		logger = boshlog.NewLogger(boshlog.LevelNone)

		factory = NewConcreteFactory(
			softLayerClient,
			options,
			logger,
		)
	})

	BeforeEach(func() {
		agentEnvServiceFactory = bslcvm.NewSoftLayerAgentEnvServiceFactory(logger)

		stemcellFinder = bslcstem.NewFSFinder(softLayerClient, logger)

		vmFinder = bslcvm.NewSoftLayerFinder(
			softLayerClient,
			agentEnvServiceFactory,
			logger,
		)
	})

	It("returns error if action cannot be created", func() {
		action, err := factory.Create("fake-unknown-action")
		Expect(err).To(HaveOccurred())
		Expect(action).To(BeNil())
	})

	It("create_stemcell", func() {
		stemcellImporter := bslcstem.NewFSImporter(logger)

		action, err := factory.Create("create_stemcell")
		Expect(err).ToNot(HaveOccurred())
		Expect(action).To(Equal(NewCreateStemcell(stemcellImporter)))
	})

	It("delete_stemcell", func() {
		action, err := factory.Create("delete_stemcell")
		Expect(err).ToNot(HaveOccurred())
		Expect(action).To(Equal(NewDeleteStemcell(stemcellFinder)))
	})

	It("create_vm", func() {
		vmCreator := bslcvm.NewSoftLayerCreator(
			softLayerClient,
			agentEnvServiceFactory,
			options.Agent,
			logger,
		)

		action, err := factory.Create("create_vm")
		Expect(err).ToNot(HaveOccurred())
		Expect(action).To(Equal(NewCreateVM(stemcellFinder, vmCreator)))
	})

	It("delete_vm", func() {
		action, err := factory.Create("delete_vm")
		Expect(err).ToNot(HaveOccurred())
		Expect(action).To(Equal(NewDeleteVM(vmFinder)))
	})

	It("has_vm", func() {
		action, err := factory.Create("has_vm")
		Expect(err).ToNot(HaveOccurred())
		Expect(action).To(Equal(NewHasVM(vmFinder)))
	})

	It("reboot_vm", func() {
		action, err := factory.Create("reboot_vm")
		Expect(err).ToNot(HaveOccurred())
		Expect(action).To(Equal(NewRebootVM()))
	})

	It("set_vm_metadata", func() {
		action, err := factory.Create("set_vm_metadata")
		Expect(err).ToNot(HaveOccurred())
		Expect(action).To(Equal(NewSetVMMetadata()))
	})

	It("configure_networks", func() {
		action, err := factory.Create("configure_networks")
		Expect(err).ToNot(HaveOccurred())
		Expect(action).To(Equal(NewConfigureNetworks()))
	})

	XIt("create_disk", func() {
		diskCreator := bslcdisk.NewFSCreator(
			"/tmp/disks",
			fs,
			cmdRunner,
			logger,
		)

		action, err := factory.Create("create_disk")
		Expect(err).ToNot(HaveOccurred())
		Expect(action).To(Equal(NewCreateDisk(diskCreator)))
	})

	XIt("delete_disk", func() {
		action, err := factory.Create("delete_disk")
		Expect(err).ToNot(HaveOccurred())
		Expect(action).To(BeNil())
	})

	XIt("attach_disk", func() {
		action, err := factory.Create("attach_disk")
		Expect(err).ToNot(HaveOccurred())
		Expect(action).To(BeNil())
	})

	XIt("detach_disk", func() {
		action, err := factory.Create("detach_disk")
		Expect(err).ToNot(HaveOccurred())
		Expect(action).To(BeNil())
	})

	It("returns error because CPI machine is not self-aware if action is current_vm_id", func() {
		action, err := factory.Create("current_vm_id")
		Expect(err).To(HaveOccurred())
		Expect(action).To(BeNil())
	})

	It("returns error because snapshotting is not implemented if action is snapshot_disk", func() {
		action, err := factory.Create("snapshot_disk")
		Expect(err).To(HaveOccurred())
		Expect(action).To(BeNil())
	})

	It("returns error because snapshotting is not implemented if action is delete_snapshot", func() {
		action, err := factory.Create("delete_snapshot")
		Expect(err).To(HaveOccurred())
		Expect(action).To(BeNil())
	})

	It("returns error since CPI should not keep state if action is get_disks", func() {
		action, err := factory.Create("get_disks")
		Expect(err).To(HaveOccurred())
		Expect(action).To(BeNil())
	})

	It("returns error because ping is not official CPI method if action is ping", func() {
		action, err := factory.Create("ping")
		Expect(err).To(HaveOccurred())
		Expect(action).To(BeNil())
	})
})
<|endoftext|>


<commit_before>package v3action_test

import (
	"errors"

	. "code.cloudfoundry.org/cli/actor/v3action"
	"code.cloudfoundry.org/cli/actor/v3action/v3actionfakes"
	"code.cloudfoundry.org/cli/api/cloudcontroller/ccv3"
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
)

var _ = Describe("LogCacheURL", func() {
	var (
		actor                     *Actor
		fakeCloudControllerClient *v3actionfakes.FakeCloudControllerClient
		fakeConfig                *v3actionfakes.FakeConfig
		actualLogCacheURL         string
	)

	JustBeforeEach(func() {
		actualLogCacheURL = actor.LogCacheURL()
	})

	BeforeEach(func() {
		actor, fakeCloudControllerClient, fakeConfig, _, _ = NewTestActor()
		fakeConfig.TargetReturns("https://api.the-best-domain.com")
	})

	When("fakeCloudControllerClient.GetInfo() succeeds", func() {
		When("there is a log cache url", func() {
			var configuredLogcacheURL string
			BeforeEach(func() {
				configuredLogcacheURL = "https://logcache.up-to-date-logging.com"
				fakeCloudControllerClient.GetInfoReturns(ccv3.Info{
					Links: ccv3.InfoLinks{
						LogCache: ccv3.APILink{
							HREF: configuredLogcacheURL,
						}}}, ccv3.ResourceLinks{}, ccv3.Warnings{}, nil)
			})
			It("uses it", func() {
				Expect(actualLogCacheURL).To(Equal(configuredLogcacheURL))
			})
		})

		When("there is no log cache url", func() {
			BeforeEach(func() {
				fakeCloudControllerClient.GetInfoReturns(ccv3.Info{
					Links: ccv3.InfoLinks{},
				}, ccv3.ResourceLinks{}, ccv3.Warnings{}, nil)
			})
			It("uses the target", func() {
				Expect(actualLogCacheURL).To(Equal("https://logcache.the-best-domain.com"))
			})
		})
	})

	When("fakeCloudControllerClient.GetInfo() fails", func() {
		BeforeEach(func() {
			fakeCloudControllerClient.GetInfoReturns(ccv3.Info{
				Links: ccv3.InfoLinks{},
			}, ccv3.ResourceLinks{}, ccv3.Warnings{}, errors.New("awf splatz!"))
		})
		It("uses the target", func() {
			Expect(actualLogCacheURL).To(Equal("https://logcache.the-best-domain.com"))
		})
	})
})
<commit_msg>More 'log-cache' hyphenating<commit_after>package v3action_test

import (
	"errors"

	. "code.cloudfoundry.org/cli/actor/v3action"
	"code.cloudfoundry.org/cli/actor/v3action/v3actionfakes"
	"code.cloudfoundry.org/cli/api/cloudcontroller/ccv3"
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
)

var _ = Describe("LogCacheURL", func() {
	var (
		actor                     *Actor
		fakeCloudControllerClient *v3actionfakes.FakeCloudControllerClient
		fakeConfig                *v3actionfakes.FakeConfig
		actualLogCacheURL         string
	)

	JustBeforeEach(func() {
		actualLogCacheURL = actor.LogCacheURL()
	})

	BeforeEach(func() {
		actor, fakeCloudControllerClient, fakeConfig, _, _ = NewTestActor()
		fakeConfig.TargetReturns("https://api.the-best-domain.com")
	})

	When("fakeCloudControllerClient.GetInfo() succeeds", func() {
		When("there is a log cache url", func() {
			var configuredLogcacheURL string
			BeforeEach(func() {
				configuredLogcacheURL = "https://log-cache.up-to-date-logging.com"
				fakeCloudControllerClient.GetInfoReturns(ccv3.Info{
					Links: ccv3.InfoLinks{
						LogCache: ccv3.APILink{
							HREF: configuredLogcacheURL,
						}}}, ccv3.ResourceLinks{}, ccv3.Warnings{}, nil)
			})
			It("uses it", func() {
				Expect(actualLogCacheURL).To(Equal(configuredLogcacheURL))
			})
		})

		When("there is no log cache url", func() {
			BeforeEach(func() {
				fakeCloudControllerClient.GetInfoReturns(ccv3.Info{
					Links: ccv3.InfoLinks{},
				}, ccv3.ResourceLinks{}, ccv3.Warnings{}, nil)
			})
			It("uses the target", func() {
				Expect(actualLogCacheURL).To(Equal("https://log-cache.the-best-domain.com"))
			})
		})
	})

	When("fakeCloudControllerClient.GetInfo() fails", func() {
		BeforeEach(func() {
			fakeCloudControllerClient.GetInfoReturns(ccv3.Info{
				Links: ccv3.InfoLinks{},
			}, ccv3.ResourceLinks{}, ccv3.Warnings{}, errors.New("awf splatz!"))
		})
		It("uses the target", func() {
			Expect(actualLogCacheURL).To(Equal("https://log-cache.the-best-domain.com"))
		})
	})
})
<|endoftext|>


<commit_before>package filesys

import (
	"sync"

	"github.com/chrislusf/seaweedfs/weed/util"
	"github.com/seaweedfs/fuse/fs"
)

type FsCache struct {
	root *FsNode
}
type FsNode struct {
	parent       *FsNode
	node         fs.Node
	name         string
	childrenLock sync.RWMutex
	children     map[string]*FsNode
}

func newFsCache(root fs.Node) *FsCache {
	return &FsCache{
		root: &FsNode{
			node: root,
		},
	}
}

func (c *FsCache) GetFsNode(path util.FullPath) fs.Node {
	t := c.root
	for _, p := range path.Split() {
		t = t.findChild(p)
		if t == nil {
			return nil
		}
	}
	return t.node
}

func (c *FsCache) SetFsNode(path util.FullPath, node fs.Node) {
	t := c.root
	for _, p := range path.Split() {
		t = t.ensureChild(p)
	}
	t.node = node
}

func (c *FsCache) EnsureFsNode(path util.FullPath, genNodeFn func() fs.Node) fs.Node {
	t := c.GetFsNode(path)
	if t != nil {
		return t
	}
	t = genNodeFn()
	c.SetFsNode(path, t)
	return t
}

func (c *FsCache) DeleteFsNode(path util.FullPath) {
	t := c.root
	for _, p := range path.Split() {
		t = t.findChild(p)
		if t == nil {
			return
		}
	}
	if t.parent != nil {
		t.parent.disconnectChild(t)
	}
	t.deleteSelf()
}

// oldPath and newPath are full path including the new name
func (c *FsCache) Move(oldPath util.FullPath, newPath util.FullPath) *FsNode {

	// find old node
	src := c.root
	for _, p := range oldPath.Split() {
		src = src.findChild(p)
		if src == nil {
			return src
		}
	}
	if src.parent != nil {
		src.parent.disconnectChild(src)
	}

	// find new node
	target := c.root
	for _, p := range newPath.Split() {
		target = target.ensureChild(p)
	}
	parent := target.parent
	src.name = target.name
	if dir, ok := src.node.(*Dir); ok {
		dir.name = target.name // target is not Dir, but a shortcut
	}
	if f, ok := src.node.(*File); ok {
		f.Name = target.name
		if f.entry != nil {
			f.entry.Name = f.Name
		}
	}
	parent.disconnectChild(target)

	target.deleteSelf()

	src.connectToParent(parent)

	return src
}

func (n *FsNode) connectToParent(parent *FsNode) {
	n.parent = parent
	oldNode := parent.findChild(n.name)
	if oldNode != nil {
		oldNode.deleteSelf()
	}
	if dir, ok := n.node.(*Dir); ok {
		dir.parent = parent.node.(*Dir)
	}
	if f, ok := n.node.(*File); ok {
		f.dir = parent.node.(*Dir)
	}
	n.childrenLock.Lock()
	parent.children[n.name] = n
	n.childrenLock.Unlock()
}

func (n *FsNode) findChild(name string) *FsNode {
	n.childrenLock.RLock()
	defer n.childrenLock.RUnlock()

	child, found := n.children[name]
	if found {
		return child
	}
	return nil
}

func (n *FsNode) ensureChild(name string) *FsNode {
	n.childrenLock.Lock()
	defer n.childrenLock.Unlock()

	if n.children == nil {
		n.children = make(map[string]*FsNode)
	}
	child, found := n.children[name]
	if found {
		return child
	}
	t := &FsNode{
		parent:   n,
		node:     nil,
		name:     name,
		children: nil,
	}
	n.children[name] = t
	return t
}

func (n *FsNode) disconnectChild(child *FsNode) {
	n.childrenLock.Lock()
	delete(n.children, child.name)
	n.childrenLock.Unlock()
	child.parent = nil
}

func (n *FsNode) deleteSelf() {
	n.childrenLock.Lock()
	for _, child := range n.children {
		child.deleteSelf()
	}
	n.children = nil
	n.childrenLock.Unlock()

	n.node = nil
	n.parent = nil

}
<commit_msg>add locking to fs cache<commit_after>package filesys

import (
	"sync"

	"github.com/chrislusf/seaweedfs/weed/util"
	"github.com/seaweedfs/fuse/fs"
)

type FsCache struct {
	root *FsNode
	sync.RWMutex
}
type FsNode struct {
	parent       *FsNode
	node         fs.Node
	name         string
	childrenLock sync.RWMutex
	children     map[string]*FsNode
}

func newFsCache(root fs.Node) *FsCache {
	return &FsCache{
		root: &FsNode{
			node: root,
		},
	}
}

func (c *FsCache) GetFsNode(path util.FullPath) fs.Node {

	c.RLock()
	defer c.RUnlock()

	return c.doGetFsNode(path)
}

func (c *FsCache) doGetFsNode(path util.FullPath) fs.Node {
	t := c.root
	for _, p := range path.Split() {
		t = t.findChild(p)
		if t == nil {
			return nil
		}
	}
	return t.node
}

func (c *FsCache) SetFsNode(path util.FullPath, node fs.Node) {

	c.Lock()
	defer c.Unlock()

	c.doSetFsNode(path, node)
}

func (c *FsCache) doSetFsNode(path util.FullPath, node fs.Node) {
	t := c.root
	for _, p := range path.Split() {
		t = t.ensureChild(p)
	}
	t.node = node
}

func (c *FsCache) EnsureFsNode(path util.FullPath, genNodeFn func() fs.Node) fs.Node {

	c.Lock()
	defer c.Unlock()

	t := c.doGetFsNode(path)
	if t != nil {
		return t
	}
	t = genNodeFn()
	c.doSetFsNode(path, t)
	return t
}

func (c *FsCache) DeleteFsNode(path util.FullPath) {

	c.Lock()
	defer c.Unlock()

	t := c.root
	for _, p := range path.Split() {
		t = t.findChild(p)
		if t == nil {
			return
		}
	}
	if t.parent != nil {
		t.parent.disconnectChild(t)
	}
	t.deleteSelf()
}

// oldPath and newPath are full path including the new name
func (c *FsCache) Move(oldPath util.FullPath, newPath util.FullPath) *FsNode {

	c.Lock()
	defer c.Unlock()

	// find old node
	src := c.root
	for _, p := range oldPath.Split() {
		src = src.findChild(p)
		if src == nil {
			return src
		}
	}
	if src.parent != nil {
		src.parent.disconnectChild(src)
	}

	// find new node
	target := c.root
	for _, p := range newPath.Split() {
		target = target.ensureChild(p)
	}
	parent := target.parent
	src.name = target.name
	if dir, ok := src.node.(*Dir); ok {
		dir.name = target.name // target is not Dir, but a shortcut
	}
	if f, ok := src.node.(*File); ok {
		f.Name = target.name
		if f.entry != nil {
			f.entry.Name = f.Name
		}
	}
	parent.disconnectChild(target)

	target.deleteSelf()

	src.connectToParent(parent)

	return src
}

func (n *FsNode) connectToParent(parent *FsNode) {
	n.parent = parent
	oldNode := parent.findChild(n.name)
	if oldNode != nil {
		oldNode.deleteSelf()
	}
	if dir, ok := n.node.(*Dir); ok {
		dir.parent = parent.node.(*Dir)
	}
	if f, ok := n.node.(*File); ok {
		f.dir = parent.node.(*Dir)
	}
	n.childrenLock.Lock()
	parent.children[n.name] = n
	n.childrenLock.Unlock()
}

func (n *FsNode) findChild(name string) *FsNode {
	n.childrenLock.RLock()
	defer n.childrenLock.RUnlock()

	child, found := n.children[name]
	if found {
		return child
	}
	return nil
}

func (n *FsNode) ensureChild(name string) *FsNode {
	n.childrenLock.Lock()
	defer n.childrenLock.Unlock()

	if n.children == nil {
		n.children = make(map[string]*FsNode)
	}
	child, found := n.children[name]
	if found {
		return child
	}
	t := &FsNode{
		parent:   n,
		node:     nil,
		name:     name,
		children: nil,
	}
	n.children[name] = t
	return t
}

func (n *FsNode) disconnectChild(child *FsNode) {
	n.childrenLock.Lock()
	delete(n.children, child.name)
	n.childrenLock.Unlock()
	child.parent = nil
}

func (n *FsNode) deleteSelf() {
	n.childrenLock.Lock()
	for _, child := range n.children {
		child.deleteSelf()
	}
	n.children = nil
	n.childrenLock.Unlock()

	n.node = nil
	n.parent = nil

}
<|endoftext|>


<commit_before>package main

import (
	"encoding/json"
	"github.com/gorilla/mux"
	"log"
	"net/http"
)

type Post struct {
	Id    int
	Title string
	Body  string
}

func main() {
	r := mux.NewRouter()
	r.HandleFunc("/posts", postsHandler).Methods("GET")
	r.HandleFunc("/", postsHandler).Methods("GET")

	http.Handle("/", r)

	log.Println("Server started. Listening...")
	http.ListenAndServe(":3000", nil)
}

func postsHandler(w http.ResponseWriter, r *http.Request) {
	post := Post{Id: 1, Title: "This is a test post", Body: "This is the body."}

	out, err := json.Marshal(post)
	if err != nil {
		panic(err)
	}

	log.Printf("Post: %v", string(out))

	w.Write([]byte(out))
}
<commit_msg>Tried connecting to the database using gorp.<commit_after>package main

import (
	"database/sql"
	"encoding/json"
	"github.com/coopernurse/gorp"
	"github.com/gorilla/mux"
	_ "github.com/ziutek/mymysql/godrv"
	"log"
	"net/http"
)

type Post struct {
	Id    int    `db:"id"`
	Title string `db:"title"`
	Body  string `db:"body"`
}

func main() {
	r := mux.NewRouter()
	r.HandleFunc("/posts", postsHandler).Methods("GET")
	r.HandleFunc("/", postsHandler).Methods("GET")

	http.Handle("/", r)

	log.Println("Server started. Listening...")
	http.ListenAndServe(":3000", nil)
}

func postsHandler(w http.ResponseWriter, r *http.Request) {
	out, err := json.Marshal(posts())

	if err != nil {
		panic(err)
	}

	log.Printf("Post: %v", string(out))

	w.Write([]byte(out))
}

func posts() []Post {
	db, err := sql.Open("mymysql", "tcp:localhost:3306*simple_api_development/root/")

	dbmap := &gorp.DbMap{Db: db, Dialect: gorp.MySQLDialect{"InnoDB", "UTF8"}}
	defer dbmap.Db.Close()

	var posts []Post
	_, err = dbmap.Select(&posts, "select id, title, body from posts order by id")

	if err != nil {
		log.Printf("Error: ", err)
		return nil
	}

	return posts
}
<|endoftext|>


<commit_before>/*
Copyright (c) 2019 VMware, Inc. All Rights Reserved.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package object_test

import (
	"context"
	"fmt"

	"github.com/vmware/govmomi/find"
	"github.com/vmware/govmomi/object"
	"github.com/vmware/govmomi/simulator"
	"github.com/vmware/govmomi/vim25"
	"github.com/vmware/govmomi/vim25/types"
)

func ExampleVirtualMachine_HostSystem() {
	simulator.Example(func(ctx context.Context, c *vim25.Client) error {
		vm, err := find.NewFinder(c).VirtualMachine(ctx, "DC0_H0_VM0")
		if err != nil {
			return err
		}

		host, err := vm.HostSystem(ctx)
		if err != nil {
			return err
		}

		name, err := host.ObjectName(ctx)
		if err != nil {
			return err
		}

		fmt.Println(name)

		return nil
	})
	// Output: DC0_H0
}

func ExampleVirtualMachine_Clone() {
	simulator.Example(func(ctx context.Context, c *vim25.Client) error {
		finder := find.NewFinder(c)
		dc, err := finder.Datacenter(ctx, "DC0")
		if err != nil {
			return err
		}

		finder.SetDatacenter(dc)

		vm, err := finder.VirtualMachine(ctx, "DC0_H0_VM0")
		if err != nil {
			return err
		}

		folders, err := dc.Folders(ctx)
		if err != nil {
			return err
		}

		spec := types.VirtualMachineCloneSpec{
			PowerOn: false,
		}

		task, err := vm.Clone(ctx, folders.VmFolder, "example-clone", spec)
		if err != nil {
			return err
		}

		info, err := task.WaitForResult(ctx)
		if err != nil {
			return err
		}

		clone := object.NewVirtualMachine(c, info.Result.(types.ManagedObjectReference))
		name, err := clone.ObjectName(ctx)
		if err != nil {
			return err
		}

		fmt.Println(name)

		return nil
	})
	// Output: example-clone
}

func ExampleCommon_Destroy() {
	model := simulator.VPX()
	model.Datastore = 2

	simulator.Example(func(ctx context.Context, c *vim25.Client) error {
		// Change to "LocalDS_0" will cause ResourceInUse error,
		// as simulator VMs created by the VPX model use "LocalDS_0".
		ds, err := find.NewFinder(c).Datastore(ctx, "LocalDS_1")
		if err != nil {
			return err
		}

		task, err := ds.Destroy(ctx)
		if err != nil {
			return err
		}

		if err = task.Wait(ctx); err != nil {
			return err
		}

		fmt.Println("destroyed", ds.InventoryPath)
		return nil
	}, model)
	// Output: destroyed /DC0/datastore/LocalDS_1
}
<commit_msg>examples: add CustomFieldManager.Set<commit_after>/*
Copyright (c) 2019 VMware, Inc. All Rights Reserved.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package object_test

import (
	"context"
	"fmt"
	"log"

	"github.com/vmware/govmomi/find"
	"github.com/vmware/govmomi/object"
	"github.com/vmware/govmomi/property"
	"github.com/vmware/govmomi/simulator"
	"github.com/vmware/govmomi/view"
	"github.com/vmware/govmomi/vim25"
	"github.com/vmware/govmomi/vim25/mo"
	"github.com/vmware/govmomi/vim25/types"
)

func ExampleVirtualMachine_HostSystem() {
	simulator.Example(func(ctx context.Context, c *vim25.Client) error {
		vm, err := find.NewFinder(c).VirtualMachine(ctx, "DC0_H0_VM0")
		if err != nil {
			return err
		}

		host, err := vm.HostSystem(ctx)
		if err != nil {
			return err
		}

		name, err := host.ObjectName(ctx)
		if err != nil {
			return err
		}

		fmt.Println(name)

		return nil
	})
	// Output: DC0_H0
}

func ExampleVirtualMachine_Clone() {
	simulator.Example(func(ctx context.Context, c *vim25.Client) error {
		finder := find.NewFinder(c)
		dc, err := finder.Datacenter(ctx, "DC0")
		if err != nil {
			return err
		}

		finder.SetDatacenter(dc)

		vm, err := finder.VirtualMachine(ctx, "DC0_H0_VM0")
		if err != nil {
			return err
		}

		folders, err := dc.Folders(ctx)
		if err != nil {
			return err
		}

		spec := types.VirtualMachineCloneSpec{
			PowerOn: false,
		}

		task, err := vm.Clone(ctx, folders.VmFolder, "example-clone", spec)
		if err != nil {
			return err
		}

		info, err := task.WaitForResult(ctx)
		if err != nil {
			return err
		}

		clone := object.NewVirtualMachine(c, info.Result.(types.ManagedObjectReference))
		name, err := clone.ObjectName(ctx)
		if err != nil {
			return err
		}

		fmt.Println(name)

		return nil
	})
	// Output: example-clone
}

func ExampleCommon_Destroy() {
	model := simulator.VPX()
	model.Datastore = 2

	simulator.Example(func(ctx context.Context, c *vim25.Client) error {
		// Change to "LocalDS_0" will cause ResourceInUse error,
		// as simulator VMs created by the VPX model use "LocalDS_0".
		ds, err := find.NewFinder(c).Datastore(ctx, "LocalDS_1")
		if err != nil {
			return err
		}

		task, err := ds.Destroy(ctx)
		if err != nil {
			return err
		}

		if err = task.Wait(ctx); err != nil {
			return err
		}

		fmt.Println("destroyed", ds.InventoryPath)
		return nil
	}, model)
	// Output: destroyed /DC0/datastore/LocalDS_1
}

func ExampleCustomFieldsManager_Set() {
	simulator.Example(func(ctx context.Context, c *vim25.Client) error {
		m, err := object.GetCustomFieldsManager(c)
		if err != nil {
			return err
		}

		any := []string{"ManagedEntity"}
		field, err := m.Add(ctx, "backup", any[0], nil, nil) // adds the custom field "backup" to all types
		if err != nil {
			return err
		}

		v, err := view.NewManager(c).CreateContainerView(ctx, c.ServiceContent.RootFolder, any, true)
		if err != nil {
			log.Fatal(err)
		}

		all, err := v.Find(ctx, any, nil) // gives us the count of all objects in the inventory
		if err != nil {
			return err
		}

		refs, err := v.Find(ctx, []string{"VirtualMachine", "Datastore"}, nil)
		if err != nil {
			return err
		}

		for _, ref := range refs {
			err = m.Set(ctx, ref, field.Key, "true") // sets the custom value "backup=true" on specific types
			if err != nil {
				return err
			}
		}

		// filter used to find objects with "backup=true"
		filter := property.Filter{"customValue": &types.CustomFieldStringValue{
			CustomFieldValue: types.CustomFieldValue{Key: field.Key},
			Value:            "true",
		}}

		var objs []mo.ManagedEntity
		err = v.RetrieveWithFilter(ctx, any, []string{"name", "customValue"}, &objs, filter)
		if err != nil {
			return err
		}

		fmt.Printf("backup %d of %d objects", len(objs), len(all))
		return v.Destroy(ctx)
	})
	// Output: backup 5 of 22 objects
}
<|endoftext|>


<commit_before>// Copyright 2013 Alex Browne.  All rights reserved.
// Use of this source code is governed by the MIT
// license, which can be found in the LICENSE file.

package zoom

import (
	"testing"
)

// just get a connection and close it
func BenchmarkConnection(b *testing.B) {

	testingSetUp()
	defer testingTearDown()

	b.ResetTimer()

	for i := 0; i < b.N; i++ {
		conn := GetConn()
		conn.Close()
	}
}

// get a connection, call PING, wait for response, and close it
func BenchmarkPing(b *testing.B) {

	checkReply := func(reply interface{}, err error) {
		if err != nil {
			b.Fatal(err)
		}
		str, ok := reply.(string)
		if !ok {
			b.Fatal("couldn't convert reply to string")
		}
		if str != "PONG" {
			b.Fatal("reply was not PONG: ", str)
		}
	}

	benchmarkCommand(b, nil, checkReply, "PING")
}

// get a connection, call SET, wait for response, and close it
func BenchmarkSet(b *testing.B) {

	checkReply := func(reply interface{}, err error) {
		if err != nil {
			b.Fatal(err)
		}
	}

	benchmarkCommand(b, nil, checkReply, "SET", "foo", "bar")
}

// get a connection, call GET, wait for response, and close it
func BenchmarkGet(b *testing.B) {

	setup := func() {
		conn := GetConn()
		_, err := conn.Do("SET", "foo", "bar")
		if err != nil {
			b.Fatal(err)
		}
		conn.Close()
	}

	checkReply := func(reply interface{}, err error) {
		if err != nil {
			b.Fatal(err)
		}
		byt, ok := reply.([]byte)
		if !ok {
			b.Fatal("couldn't convert reply to []byte: ", reply)
		}
		str := string(byt)
		if str != "bar" {
			b.Fatal("reply was not bar: ", str)
		}
	}

	benchmarkCommand(b, setup, checkReply, "GET", "foo")
}

// saves the same record repeatedly
// (after the first save, nothing changes)
func BenchmarkSave(b *testing.B) {
	singleModelSelect := func(i int, models []*basicModel) *basicModel {
		return models[0]
	}
	benchmarkSave(b, 1, singleModelSelect)
}

// finds the same record over and over
func BenchmarkFindById(b *testing.B) {
	testingSetUp()
	defer testingTearDown()

	// create some models
	ms, err := newBasicModels(1000)
	if err != nil {
		b.Error(err)
	}
	MSave(Models(ms))
	ids := make([]string, len(ms))
	for i, p := range ms {
		ids[i] = p.Id
	}

	// reset the timer
	b.ResetTimer()

	// run the actual test
	for i := 0; i < b.N; i++ {
		b.StopTimer()
		id := ids[i%len(ids)]
		b.StartTimer()
		FindById("basicModel", id)
	}
}

// scans the same record over and over
func BenchmarkScanById(b *testing.B) {
	testingSetUp()
	defer testingTearDown()

	// create some models
	ms, err := newBasicModels(1000)
	if err != nil {
		b.Error(err)
	}
	MSave(Models(ms))
	ids := make([]string, len(ms))
	for i, p := range ms {
		ids[i] = p.Id
	}

	// reset the timer
	b.ResetTimer()

	// run the actual test
	for i := 0; i < b.N; i++ {
		b.StopTimer()
		id := ids[i%len(ids)]
		mCopy := new(basicModel)
		b.StartTimer()
		ScanById(id, mCopy)
	}
}

// repeatedly calls delete on a record
// (after the first, the record will have already been deleted)
func BenchmarkRepeatDeleteById(b *testing.B) {
	benchmarkDeleteById(b, 1, singleIdSelect)
}

// randomly calls delete on a list of records
func BenchmarkRandomDeleteById(b *testing.B) {
	benchmarkDeleteById(b, 1000, randomIdSelect)
}

func benchmarkCommand(b *testing.B, setup func(), checkReply func(interface{}, error), cmd string, args ...interface{}) {
	testingSetUp()
	defer testingTearDown()

	if setup != nil {
		setup()
	}

	b.ResetTimer()

	for i := 0; i < b.N; i++ {
		conn := GetConn()
		reply, err := conn.Do(cmd, args...)
		b.StopTimer()
		if checkReply != nil {
			checkReply(reply, err)
		}
		b.StartTimer()
		conn.Close()
	}
}

func benchmarkSave(b *testing.B, num int, modelSelect func(int, []*basicModel) *basicModel) {
	testingSetUp()
	defer testingTearDown()

	// create a sequence of models to be saved
	ms, err := newBasicModels(num)
	if err != nil {
		b.Error(err)
	}

	// reset the timer
	b.ResetTimer()

	// run the actual test
	for i := 0; i < b.N; i++ {
		b.StopTimer()
		m := modelSelect(i, ms)
		b.StartTimer()
		err := Save(m)
		b.StopTimer()
		if err != nil {
			b.Fatal(err)
		}
		b.StartTimer()
	}
}

func benchmarkDeleteById(b *testing.B, num int, idSelect func(int, []string) string) {
	testingSetUp()
	defer testingTearDown()

	ms, err := newBasicModels(num)
	if err != nil {
		b.Error(err)
	}
	ids := make([]string, len(ms))
	for i, m := range ms {
		ids[i] = m.Id
	}

	b.ResetTimer()

	// run the actual test
	for i := 0; i < b.N; i++ {
		b.StopTimer()
		id := idSelect(i, ids)
		b.StartTimer()
		DeleteById("basicModel", id)
	}
}

func benchmarkDelete(b *testing.B, num int, modelSelect func(int, []*basicModel) *basicModel) {
	testingSetUp()
	defer testingTearDown()

	ms, err := newBasicModels(num)
	if err != nil {
		b.Error(err)
	}

	b.ResetTimer()

	// run the actual test
	for i := 0; i < b.N; i++ {
		b.StopTimer()
		m := modelSelect(i, ms)
		b.StartTimer()
		Delete(m)
	}
}

func singleIdSelect(i int, ids []string) string {
	return ids[0]
}

func sequentialIdSelect(i int, ids []string) string {
	return ids[i%len(ids)]
}

func randomIdSelect(i int, ids []string) string {
	return ids[randInt(0, len(ids)-1)]
}
<commit_msg>Add benchmarks<commit_after>// Copyright 2013 Alex Browne.  All rights reserved.
// Use of this source code is governed by the MIT
// license, which can be found in the LICENSE file.

package zoom

import (
	"testing"
)

// Just gets a connection and closes it
func BenchmarkConnection(b *testing.B) {

	testingSetUp()
	defer testingTearDown()

	b.ResetTimer()

	for i := 0; i < b.N; i++ {
		conn := GetConn()
		conn.Close()
	}
}

// Gets a connection, calls PING, waits for response, and closes it
func BenchmarkPing(b *testing.B) {

	checkReply := func(reply interface{}, err error) {
		if err != nil {
			b.Fatal(err)
		}
		str, ok := reply.(string)
		if !ok {
			b.Fatal("couldn't convert reply to string")
		}
		if str != "PONG" {
			b.Fatal("reply was not PONG: ", str)
		}
	}

	benchmarkCommand(b, nil, checkReply, "PING")
}

// Gets a connection, calls SET, waits for response, and closes it
func BenchmarkSet(b *testing.B) {

	checkReply := func(reply interface{}, err error) {
		if err != nil {
			b.Fatal(err)
		}
	}

	benchmarkCommand(b, nil, checkReply, "SET", "foo", "bar")
}

// Gets a connection, calls GET, waits for response, and closes it
func BenchmarkGet(b *testing.B) {

	setup := func() {
		conn := GetConn()
		_, err := conn.Do("SET", "foo", "bar")
		if err != nil {
			b.Fatal(err)
		}
		conn.Close()
	}

	checkReply := func(reply interface{}, err error) {
		if err != nil {
			b.Fatal(err)
		}
		byt, ok := reply.([]byte)
		if !ok {
			b.Fatal("couldn't convert reply to []byte: ", reply)
		}
		str := string(byt)
		if str != "bar" {
			b.Fatal("reply was not bar: ", str)
		}
	}

	benchmarkCommand(b, setup, checkReply, "GET", "foo")
}

// Saves the same record repeatedly
// (after the first save, nothing changes)
func BenchmarkSave(b *testing.B) {
	singleModelSelect := func(i int, models []*basicModel) *basicModel {
		return models[0]
	}
	benchmarkSave(b, 1, singleModelSelect)
}

// Saves 100 models in a single transaction using MSAVE
// NOTE: divide the reported time/op by 100 to get the time
// to save per model
func BenchmarkMSave100(b *testing.B) {
	testingSetUp()
	defer testingTearDown()

	ms, err := newBasicModels(100)
	if err != nil {
		b.Error(err)
	}
	b.ResetTimer()
	models := Models(ms)

	for i := 0; i < b.N; i++ {
		MSave(models)
	}
}

// Finds one model at a time randomly from a set of 1000 models
func BenchmarkFindById(b *testing.B) {
	testingSetUp()
	defer testingTearDown()

	// create some models
	ms, err := newBasicModels(1000)
	if err != nil {
		b.Error(err)
	}
	MSave(Models(ms))
	ids := make([]string, len(ms))
	for i, p := range ms {
		ids[i] = p.Id
	}

	// reset the timer
	b.ResetTimer()

	// run the actual test
	for i := 0; i < b.N; i++ {
		b.StopTimer()
		index := randInt(0, len(ms)-1)
		id := ids[index]
		b.StartTimer()
		FindById("basicModel", id)
	}
}

// Finds 100 models at a time selected randomly from a set of 10,000 models
func BenchmarkMFindById100(b *testing.B) {
	testingSetUp()
	defer testingTearDown()

	// create some models
	ms, err := newBasicModels(10000)
	if err != nil {
		b.Error(err)
	}
	MSave(Models(ms))
	ids := make([]string, len(ms))
	for i, p := range ms {
		ids[i] = p.Id
	}

	// reset the timer
	b.ResetTimer()

	// run the actual test
	for i := 0; i < b.N; i++ {
		b.StopTimer()
		selectedIds := selectRandomUniqueIds(100, ids)
		modelNames := fillStringSlice(100, "basicModel")
		b.StartTimer()
		_, err := MFindById(modelNames, selectedIds)
		b.StopTimer()
		if err != nil {
			b.Error(err)
		}
	}
}

// Scans a random model selected from a set of 1000 models
func BenchmarkScanById(b *testing.B) {
	testingSetUp()
	defer testingTearDown()

	// create some models
	ms, err := newBasicModels(1000)
	if err != nil {
		b.Error(err)
	}
	MSave(Models(ms))
	ids := make([]string, len(ms))
	for i, p := range ms {
		ids[i] = p.Id
	}

	// reset the timer
	b.ResetTimer()

	// run the actual test
	for i := 0; i < b.N; i++ {
		b.StopTimer()
		index := randInt(0, len(ms)-1)
		id := ids[index]
		mCopy := new(basicModel)
		b.StartTimer()
		ScanById(id, mCopy)
	}
}

// Scans 100 models at a time selected randomly from a set of 10,000 models
func BenchmarkMScanById100(b *testing.B) {
	testingSetUp()
	defer testingTearDown()

	// create some models
	ms, err := newBasicModels(10000)
	if err != nil {
		b.Error(err)
	}
	MSave(Models(ms))
	ids := make([]string, len(ms))
	for i, p := range ms {
		ids[i] = p.Id
	}

	// reset the timer
	b.ResetTimer()

	// run the actual test
	for i := 0; i < b.N; i++ {
		b.StopTimer()
		selectedIds := selectRandomUniqueIds(100, ids)
		models := make([]*basicModel, 100)
		b.StartTimer()
		err := MScanById(selectedIds, &models)
		b.StopTimer()
		if err != nil {
			b.Error(err)
		}
	}
}

// Repeatedly calls delete on a record
// (after the first, the record will have already been deleted)
func BenchmarkRepeatDeleteById(b *testing.B) {
	benchmarkDeleteById(b, 1, singleIdSelect)
}

// Randomly calls delete on a list of records
// repeated deletes have no effect
func BenchmarkRandomDeleteById(b *testing.B) {
	benchmarkDeleteById(b, 1000, randomIdSelect)
}

// Find all models from a set of 10 models using a query
func BenchmarkFindAllQuery1(b *testing.B) {
	benchmarkFindAllQuery(b, 10)
}

// Find all models from a set of 1,000 models using a query
func BenchmarkFindAllQuery1000(b *testing.B) {
	benchmarkFindAllQuery(b, 1000)
}

// Find all models from a set of 100,000 models using a query
func BenchmarkFindAllQuery100000(b *testing.B) {
	benchmarkFindAllQuery(b, 100000)
}

// Count all models from a set of 10 models using a query
func BenchmarkCountAllQuery1(b *testing.B) {
	benchmarkCountAllQuery(b, 10)
}

// Count all models from a set of 1,000 models using a query
func BenchmarkCountAllQuery1000(b *testing.B) {
	benchmarkCountAllQuery(b, 1000)
}

// Count all models from a set of 100,000 models using a query
func BenchmarkCountAllQuery100000(b *testing.B) {
	benchmarkCountAllQuery(b, 100000)
}

// Deletes 100 models at a time randomly selected from a set of 10,000 models
// repeated delets have no effect
func BenchmarkMDeleteById(b *testing.B) {
	testingSetUp()
	defer testingTearDown()

	// create some models
	ms, err := newBasicModels(10000)
	if err != nil {
		b.Error(err)
	}
	MSave(Models(ms))
	ids := make([]string, len(ms))
	for i, p := range ms {
		ids[i] = p.Id
	}

	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		b.StopTimer()
		selectedIds := selectRandomUniqueIds(100, ids)
		modelNames := fillStringSlice(100, "basicModel")
		b.StartTimer()
		MDeleteById(modelNames, selectedIds)
	}
}

func benchmarkCommand(b *testing.B, setup func(), checkReply func(interface{}, error), cmd string, args ...interface{}) {
	testingSetUp()
	defer testingTearDown()

	if setup != nil {
		setup()
	}

	b.ResetTimer()

	for i := 0; i < b.N; i++ {
		conn := GetConn()
		reply, err := conn.Do(cmd, args...)
		b.StopTimer()
		if checkReply != nil {
			checkReply(reply, err)
		}
		b.StartTimer()
		conn.Close()
	}
}

func benchmarkSave(b *testing.B, num int, modelSelect func(int, []*basicModel) *basicModel) {
	testingSetUp()
	defer testingTearDown()

	// create a sequence of models to be saved
	ms, err := newBasicModels(num)
	if err != nil {
		b.Error(err)
	}

	// reset the timer
	b.ResetTimer()

	// run the actual test
	for i := 0; i < b.N; i++ {
		b.StopTimer()
		m := modelSelect(i, ms)
		b.StartTimer()
		err := Save(m)
		b.StopTimer()
		if err != nil {
			b.Fatal(err)
		}
		b.StartTimer()
	}
}

func benchmarkDeleteById(b *testing.B, num int, idSelect func(int, []string) string) {
	testingSetUp()
	defer testingTearDown()

	ms, err := newBasicModels(num)
	if err != nil {
		b.Error(err)
	}
	ids := make([]string, len(ms))
	for i, m := range ms {
		ids[i] = m.Id
	}

	b.ResetTimer()

	// run the actual test
	for i := 0; i < b.N; i++ {
		b.StopTimer()
		id := idSelect(i, ids)
		b.StartTimer()
		DeleteById("basicModel", id)
	}
}

func benchmarkDelete(b *testing.B, num int, modelSelect func(int, []*basicModel) *basicModel) {
	testingSetUp()
	defer testingTearDown()

	ms, err := newBasicModels(num)
	if err != nil {
		b.Error(err)
	}
	if err := MSave(Models(ms)); err != nil {
		b.Error(err)
	}

	b.ResetTimer()

	// run the actual test
	for i := 0; i < b.N; i++ {
		b.StopTimer()
		m := modelSelect(i, ms)
		b.StartTimer()
		Delete(m)
	}
}

func benchmarkFindAllQuery(b *testing.B, num int) {
	testingSetUp()
	defer testingTearDown()

	ms, err := newBasicModels(num)
	if err != nil {
		b.Error(err)
	}
	if err := MSave(Models(ms)); err != nil {
		b.Error(err)
	}

	b.ResetTimer()

	for i := 0; i < b.N; i++ {
		b.StartTimer()
		_, err := NewQuery("basicModel").Run()
		b.StopTimer()
		if err != nil {
			b.Error(err)
		}
	}
}

func benchmarkCountAllQuery(b *testing.B, num int) {
	testingSetUp()
	defer testingTearDown()

	ms, err := newBasicModels(num)
	if err != nil {
		b.Error(err)
	}
	if err := MSave(Models(ms)); err != nil {
		b.Error(err)
	}

	b.ResetTimer()

	for i := 0; i < b.N; i++ {
		b.StartTimer()
		_, err := NewQuery("basicModel").Count()
		b.StopTimer()
		if err != nil {
			b.Error(err)
		}
	}
}

func singleIdSelect(i int, ids []string) string {
	return ids[0]
}

func sequentialIdSelect(i int, ids []string) string {
	return ids[i%len(ids)]
}

func randomIdSelect(i int, ids []string) string {
	return ids[randInt(0, len(ids)-1)]
}

// selects num random ids from the set of ids
func selectRandomUniqueIds(num int, ids []string) []string {
	selected := make(map[string]bool)
	for len(selected) < num {
		index := randInt(0, len(ids)-1)
		id := ids[index]
		if _, found := selected[id]; !found {
			selected[id] = true
		}
	}
	results := make([]string, 0)
	for key, _ := range selected {
		results = append(results, key)
	}
	return results
}

// fills a string slice with the num occurences of str and returns it
func fillStringSlice(num int, str string) []string {
	results := make([]string, num)
	for i := 0; i < num; i++ {
		results[i] = str
	}
	return results
}
<|endoftext|>


<commit_before>// Copyright (c) 2013 by Michael Dvorkin. All Rights Reserved.
// Use of this source code is governed by a MIT-style license that can
// be found in the LICENSE file.

package mop

import (
	`github.com/michaeldv/termbox-go`
	`regexp`
	`strings`
)

type LineEditor struct {
	command  rune		// Keyboard command such as '+' or '-'.
	cursor   int 		// Current cursor position within the input line.
	prompt   string		// Prompt string for the command.
	input    string		// User typed input string.
	screen  *Screen		// Pointer to Screen.
	quotes  *Quotes		// Pointer to Quotes.
	regex   *regexp.Regexp	// Regex to split comma-delimited input string.
}

//-----------------------------------------------------------------------------
func (editor *LineEditor) Initialize(screen *Screen, quotes *Quotes) *LineEditor {
	editor.screen = screen
	editor.quotes = quotes
	editor.regex = regexp.MustCompile(`[,\s]+`)

	return editor
}

//-----------------------------------------------------------------------------
func (editor *LineEditor) Prompt(command rune) *LineEditor {
	prompts := map[rune]string{'+': `Add tickers: `, '-': `Remove tickers: `}
	if prompt, ok := prompts[command]; ok {
		editor.prompt = prompt
		editor.command = command

		editor.screen.DrawLine(0, 3, `<white>` + editor.prompt + `</>`)
		termbox.SetCursor(len(editor.prompt), 3)
		termbox.Flush()
	}

	return editor
}

//-----------------------------------------------------------------------------
func (editor *LineEditor) Handle(ev termbox.Event) bool {
	defer termbox.Flush()

	switch ev.Key {
	case termbox.KeyEsc:
		return editor.done()

	case termbox.KeyEnter:
		return editor.execute().done()

        case termbox.KeyBackspace, termbox.KeyBackspace2:
		editor.deletePreviousCharacter()

	case termbox.KeyCtrlB, termbox.KeyArrowLeft:
		editor.moveLeft()

	case termbox.KeyCtrlF, termbox.KeyArrowRight:
		editor.moveRight()

	case termbox.KeyCtrlA:
		editor.jumpToBeginning()

	case termbox.KeyCtrlE:
		editor.jumpToEnd()

	case termbox.KeySpace:
		editor.insertCharacter(' ')

	default:
		if ev.Ch != 0 {
			editor.insertCharacter(ev.Ch)
		}
	}

	return false
}

//-----------------------------------------------------------------------------
func (editor *LineEditor) deletePreviousCharacter() *LineEditor {
	if editor.cursor > 0 {
		if editor.cursor < len(editor.input) {
			// Remove character in the middle of the input string.
			editor.input = editor.input[0 : editor.cursor-1] + editor.input[editor.cursor : len(editor.input)]
		} else {
			// Remove last input character.
			editor.input = editor.input[ : len(editor.input)-1]
		}
		editor.screen.DrawLine(len(editor.prompt), 3, editor.input + ` `) // Erase last character.
		editor.moveLeft()
	}

	return editor
}

//-----------------------------------------------------------------------------
func (editor *LineEditor) insertCharacter(ch rune) *LineEditor {
	if editor.cursor < len(editor.input) {
		// Insert the character in the middle of the input string.
		editor.input = editor.input[0 : editor.cursor] + string(ch) + editor.input[editor.cursor : len(editor.input)]
	} else {
		// Append the character to the end of the input string.
		editor.input += string(ch)
	}
	editor.screen.DrawLine(len(editor.prompt), 3, editor.input)
	editor.moveRight()

	return editor
}

//-----------------------------------------------------------------------------
func (editor *LineEditor) moveLeft() *LineEditor {
	if editor.cursor > 0 {
		editor.cursor--
		termbox.SetCursor(len(editor.prompt) + editor.cursor, 3)
	}

	return editor
}

//-----------------------------------------------------------------------------
func (editor *LineEditor) moveRight() *LineEditor {
	if editor.cursor < len(editor.input) {
		editor.cursor++
		termbox.SetCursor(len(editor.prompt) + editor.cursor, 3)
	}

	return editor
}

//-----------------------------------------------------------------------------
func (editor *LineEditor) jumpToBeginning() *LineEditor {
	editor.cursor = 0
	termbox.SetCursor(len(editor.prompt) + editor.cursor, 3)

	return editor
}

//-----------------------------------------------------------------------------
func (editor *LineEditor) jumpToEnd() *LineEditor {
	editor.cursor = len(editor.input)
	termbox.SetCursor(len(editor.prompt) + editor.cursor, 3)

	return editor
}

//-----------------------------------------------------------------------------
func (editor *LineEditor) execute() *LineEditor {
	switch editor.command {
	case '+':
		tickers := editor.tokenize()
		if len(tickers) > 0 {
			if added,_ := editor.quotes.AddTickers(tickers); added > 0 {
				editor.screen.Draw(editor.quotes)
			}
		}
	case '-':
		tickers := editor.tokenize()
		if len(tickers) > 0 {
			before := len(editor.quotes.profile.Tickers)
			if removed,_ := editor.quotes.RemoveTickers(tickers); removed > 0 {
				editor.screen.Draw(editor.quotes)
				after := before - removed
				for i := before; i > after; i-- {
					editor.screen.ClearLine(0, i + 4)
				}
			}
		}
	}

	return editor
}

//-----------------------------------------------------------------------------
func (editor *LineEditor) done() bool {
	editor.screen.ClearLine(0, 3)
	termbox.HideCursor()

	return true
}

//
// Split by whitespace/comma to convert a string to array of tickers. Make sure
// the string is trimmed to avoid empty tickers in the array.
//
func (editor *LineEditor) tokenize() []string {
	input := strings.ToUpper(strings.Trim(editor.input, `, `))
	return editor.regex.Split(input, -1)
}
<commit_msg>Documentation for the line editor<commit_after>// Copyright (c) 2013 by Michael Dvorkin. All Rights Reserved.
// Use of this source code is governed by a MIT-style license that can
// be found in the LICENSE file.

package mop

import (
	`github.com/michaeldv/termbox-go`
	`regexp`
	`strings`
)

// LineEditor kicks in when user presses '+' or '-' to add or delete stock
// tickers. The data structure and methods are used to collect the input
// data and keep track of cursor movements (left, right, beginning of the
// line, end of the line, and backspace).
type LineEditor struct {
	command  rune		// Keyboard command such as '+' or '-'.
	cursor   int 		// Current cursor position within the input line.
	prompt   string		// Prompt string for the command.
	input    string		// User typed input string.
	screen  *Screen		// Pointer to Screen.
	quotes  *Quotes		// Pointer to Quotes.
	regex   *regexp.Regexp	// Regex to split comma-delimited input string.
}

// Initialize sets internal pointers and compiles the regular expression.
func (editor *LineEditor) Initialize(screen *Screen, quotes *Quotes) *LineEditor {
	editor.screen = screen
	editor.quotes = quotes
	editor.regex = regexp.MustCompile(`[,\s]+`)

	return editor
}

// Prompt displays a prompt in response to '+' or '-' commands. Unknown commands
// are simply ignored. The prompt is displayed on the 3rd line (between the market
// data and the stock quotes).
func (editor *LineEditor) Prompt(command rune) *LineEditor {
	prompts := map[rune]string{'+': `Add tickers: `, '-': `Remove tickers: `}
	if prompt, ok := prompts[command]; ok {
		editor.prompt = prompt
		editor.command = command

		editor.screen.DrawLine(0, 3, `<white>` + editor.prompt + `</>`)
		termbox.SetCursor(len(editor.prompt), 3)
		termbox.Flush()
	}

	return editor
}

// Handle takes over the keyboard events and dispatches them to appropriate
// line editor handlers. As user types or edits the text cursor movements
// are tracked in `editor.cursor` while the text itself is stored in
// `editor.input`. The method returns true when user presses Esc (discard)
// or Enter (process).
func (editor *LineEditor) Handle(ev termbox.Event) bool {
	defer termbox.Flush()

	switch ev.Key {
	case termbox.KeyEsc:
		return editor.done()

	case termbox.KeyEnter:
		return editor.execute().done()

        case termbox.KeyBackspace, termbox.KeyBackspace2:
		editor.deletePreviousCharacter()

	case termbox.KeyCtrlB, termbox.KeyArrowLeft:
		editor.moveLeft()

	case termbox.KeyCtrlF, termbox.KeyArrowRight:
		editor.moveRight()

	case termbox.KeyCtrlA:
		editor.jumpToBeginning()

	case termbox.KeyCtrlE:
		editor.jumpToEnd()

	case termbox.KeySpace:
		editor.insertCharacter(' ')

	default:
		if ev.Ch != 0 {
			editor.insertCharacter(ev.Ch)
		}
	}

	return false
}

//-----------------------------------------------------------------------------
func (editor *LineEditor) deletePreviousCharacter() *LineEditor {
	if editor.cursor > 0 {
		if editor.cursor < len(editor.input) {
			// Remove character in the middle of the input string.
			editor.input = editor.input[0 : editor.cursor-1] + editor.input[editor.cursor : len(editor.input)]
		} else {
			// Remove last input character.
			editor.input = editor.input[ : len(editor.input)-1]
		}
		editor.screen.DrawLine(len(editor.prompt), 3, editor.input + ` `) // Erase last character.
		editor.moveLeft()
	}

	return editor
}

//-----------------------------------------------------------------------------
func (editor *LineEditor) insertCharacter(ch rune) *LineEditor {
	if editor.cursor < len(editor.input) {
		// Insert the character in the middle of the input string.
		editor.input = editor.input[0 : editor.cursor] + string(ch) + editor.input[editor.cursor : len(editor.input)]
	} else {
		// Append the character to the end of the input string.
		editor.input += string(ch)
	}
	editor.screen.DrawLine(len(editor.prompt), 3, editor.input)
	editor.moveRight()

	return editor
}

//-----------------------------------------------------------------------------
func (editor *LineEditor) moveLeft() *LineEditor {
	if editor.cursor > 0 {
		editor.cursor--
		termbox.SetCursor(len(editor.prompt) + editor.cursor, 3)
	}

	return editor
}

//-----------------------------------------------------------------------------
func (editor *LineEditor) moveRight() *LineEditor {
	if editor.cursor < len(editor.input) {
		editor.cursor++
		termbox.SetCursor(len(editor.prompt) + editor.cursor, 3)
	}

	return editor
}

//-----------------------------------------------------------------------------
func (editor *LineEditor) jumpToBeginning() *LineEditor {
	editor.cursor = 0
	termbox.SetCursor(len(editor.prompt) + editor.cursor, 3)

	return editor
}

//-----------------------------------------------------------------------------
func (editor *LineEditor) jumpToEnd() *LineEditor {
	editor.cursor = len(editor.input)
	termbox.SetCursor(len(editor.prompt) + editor.cursor, 3)

	return editor
}

//-----------------------------------------------------------------------------
func (editor *LineEditor) execute() *LineEditor {
	switch editor.command {
	case '+':
		tickers := editor.tokenize()
		if len(tickers) > 0 {
			if added,_ := editor.quotes.AddTickers(tickers); added > 0 {
				editor.screen.Draw(editor.quotes)
			}
		}
	case '-':
		tickers := editor.tokenize()
		if len(tickers) > 0 {
			before := len(editor.quotes.profile.Tickers)
			if removed,_ := editor.quotes.RemoveTickers(tickers); removed > 0 {
				editor.screen.Draw(editor.quotes)

				// Clear the lines at the bottom of the list, if any.
				after := before - removed
				for i := before; i > after; i-- {
					editor.screen.ClearLine(0, i + 4)
				}
			}
		}
	}

	return editor
}

//-----------------------------------------------------------------------------
func (editor *LineEditor) done() bool {
	editor.screen.ClearLine(0, 3)
	termbox.HideCursor()

	return true
}

// Split by whitespace/comma to convert a string to array of tickers. Make sure
// the string is trimmed to avoid empty tickers in the array.
func (editor *LineEditor) tokenize() []string {
	input := strings.ToUpper(strings.Trim(editor.input, `, `))
	return editor.regex.Split(input, -1)
}
<|endoftext|>


<commit_before>package config

import (
	"fmt"

	"github.com/rliebz/tusk/config/option"
	"github.com/rliebz/tusk/config/task"
	"github.com/rliebz/tusk/interp"
	yaml "gopkg.in/yaml.v2"
)

// Parse loads the contents of a config file into a struct.
func Parse(text []byte) (*Config, error) {
	cfg := new(Config)

	if err := yaml.UnmarshalStrict(text, cfg); err != nil {
		return nil, err
	}

	return cfg, nil
}

// ParseComplete parses the file completely with interpolation.
func ParseComplete(cfgText []byte, passed map[string]string, taskName string) (*Config, error) {

	cfg, err := Parse(cfgText)
	if err != nil {
		return nil, err
	}

	t, isTaskSet := cfg.Tasks[taskName]
	if !isTaskSet {
		return cfg, nil
	}

	if err := passTaskValues(t, cfg, cfgText, passed); err != nil {
		return nil, err
	}

	return cfg, nil
}

func passTaskValues(t *task.Task, cfg *Config, cfgText []byte, passed map[string]string) error {
	values, err := interpolateGlobalOptions(t, cfg, cfgText, passed)
	if err != nil {
		return err
	}

	if err := interpolateTask(t, cfgText, values, passed); err != nil {
		return err
	}

	return addSubTasks(t, cfg, cfgText)
}

func interpolateGlobalOptions(
	t *task.Task, cfg *Config, cfgText []byte, passed map[string]string,
) (map[string]string, error) {

	globalOptions, err := getRequiredGlobalOptions(cfgText, t)
	if err != nil {
		return nil, err
	}

	values := make(map[string]string, len(globalOptions))
	for _, name := range globalOptions {
		o := cfg.Options[name]
		if err := interpolateOption(o, passed, values); err != nil {
			return nil, err
		}
	}

	return values, nil
}

func interpolateTask(t *task.Task, cfgText []byte, values, passed map[string]string) error {
	taskOptions, err := getOrderedTaskOptions(cfgText, t.Name)
	if err != nil {
		return err
	}

	taskValues := make(map[string]string, len(values)+len(taskOptions))
	for k, v := range values {
		taskValues[k] = v
	}

	for _, name := range taskOptions {
		o := t.Options[name]

		if err := interpolateOption(o, passed, taskValues); err != nil {
			return err
		}
	}

	if err := interp.Marshallable(&t.RunList, taskValues); err != nil {
		return err
	}

	t.Vars = taskValues

	return nil
}

func interpolateOption(o *option.Option, passed, values map[string]string) error {
	if err := interp.Marshallable(o, values); err != nil {
		return err
	}

	if valuePassed, ok := passed[o.Name]; ok {
		o.Passed = valuePassed
	}

	o.Vars = values
	value, err := o.Evaluate()
	if err != nil {
		return err
	}

	values[o.Name] = value

	return nil
}

func getRequiredGlobalOptions(cfgText []byte, t *task.Task) ([]string, error) {
	ordered, err := getOrderedGlobalOptions(cfgText)
	if err != nil {
		return nil, err
	}

	cfg, err := Parse(cfgText)
	if err != nil {
		return nil, err
	}

	required, err := FindAllOptions(t, cfg)
	if err != nil {
		return nil, err
	}

	var output []string
	for _, o := range ordered {
		for _, r := range required {
			if r.Name != o {
				continue
			}

			output = append(output, o)
		}
	}

	return output, nil
}

func getOrderedGlobalOptions(cfgText []byte) ([]string, error) {
	cfgMapSlice := new(struct {
		Options yaml.MapSlice
	})

	if err := yaml.Unmarshal(cfgText, cfgMapSlice); err != nil {
		return nil, err
	}

	var ordered []string
	for _, optionMapSlice := range cfgMapSlice.Options {
		ordered = append(ordered, optionMapSlice.Key.(string))
	}

	return ordered, nil
}

func getOrderedTaskOptions(cfgText []byte, taskName string) ([]string, error) {
	cfgMapSlice := new(struct {
		Tasks yaml.MapSlice
	})

	if err := yaml.Unmarshal(cfgText, cfgMapSlice); err != nil {
		return nil, err
	}

	var ordered []string
	for _, taskMapSlice := range cfgMapSlice.Tasks {
		if name := taskMapSlice.Key.(string); name != taskName {
			continue
		}

		for _, mapSlice := range taskMapSlice.Value.(yaml.MapSlice) {
			if name := mapSlice.Key.(string); name != "options" {
				continue
			}

			for _, optionMapSlice := range mapSlice.Value.(yaml.MapSlice) {
				ordered = append(ordered, optionMapSlice.Key.(string))
			}
			break
		}
	}

	return ordered, nil
}

func addSubTasks(t *task.Task, cfg *Config, cfgText []byte) error {

	for _, run := range t.RunList {
		for _, subTaskDesc := range run.SubTaskList {
			st, ok := cfg.Tasks[subTaskDesc.Name]
			if !ok {
				return fmt.Errorf(
					`sub-task "%s" does not exist`,
					subTaskDesc.Name,
				)
			}

			passed := subTaskDesc.Options
			subTask := *st

			for optName := range passed {
				if _, isValidOption := subTask.Options[optName]; !isValidOption {
					return fmt.Errorf(
						`option "%s" cannot be passed to task "%s"`,
						optName, subTask.Name,
					)
				}
			}

			if err := passTaskValues(&subTask, cfg, cfgText, passed); err != nil {
				return err
			}

			run.Tasks = append(run.Tasks, subTask)

		}
	}

	return nil
}
<commit_msg>Disambiguate vars/values<commit_after>package config

import (
	"fmt"

	"github.com/rliebz/tusk/config/option"
	"github.com/rliebz/tusk/config/task"
	"github.com/rliebz/tusk/interp"
	yaml "gopkg.in/yaml.v2"
)

// Parse loads the contents of a config file into a struct.
func Parse(text []byte) (*Config, error) {
	cfg := new(Config)

	if err := yaml.UnmarshalStrict(text, cfg); err != nil {
		return nil, err
	}

	return cfg, nil
}

// ParseComplete parses the file completely with interpolation.
func ParseComplete(cfgText []byte, passed map[string]string, taskName string) (*Config, error) {

	cfg, err := Parse(cfgText)
	if err != nil {
		return nil, err
	}

	t, isTaskSet := cfg.Tasks[taskName]
	if !isTaskSet {
		return cfg, nil
	}

	if err := passTaskValues(t, cfg, cfgText, passed); err != nil {
		return nil, err
	}

	return cfg, nil
}

func passTaskValues(t *task.Task, cfg *Config, cfgText []byte, passed map[string]string) error {
	vars, err := interpolateGlobalOptions(t, cfg, cfgText, passed)
	if err != nil {
		return err
	}

	if err := interpolateTask(t, cfgText, passed, vars); err != nil {
		return err
	}

	return addSubTasks(t, cfg, cfgText)
}

func interpolateGlobalOptions(
	t *task.Task, cfg *Config, cfgText []byte, passed map[string]string,
) (map[string]string, error) {

	globalOptions, err := getRequiredGlobalOptions(cfgText, t)
	if err != nil {
		return nil, err
	}

	vars := make(map[string]string, len(globalOptions))
	for _, name := range globalOptions {
		o := cfg.Options[name]
		if err := interpolateOption(o, passed, vars); err != nil {
			return nil, err
		}
	}

	return vars, nil
}

func interpolateTask(t *task.Task, cfgText []byte, passed, vars map[string]string) error {
	taskOptions, err := getOrderedTaskOptions(cfgText, t.Name)
	if err != nil {
		return err
	}

	taskVars := make(map[string]string, len(vars)+len(taskOptions))
	for k, v := range vars {
		taskVars[k] = v
	}

	for _, name := range taskOptions {
		o := t.Options[name]

		if err := interpolateOption(o, passed, taskVars); err != nil {
			return err
		}
	}

	if err := interp.Marshallable(&t.RunList, taskVars); err != nil {
		return err
	}

	t.Vars = taskVars

	return nil
}

func interpolateOption(o *option.Option, passed, vars map[string]string) error {
	if err := interp.Marshallable(o, vars); err != nil {
		return err
	}

	if valuePassed, ok := passed[o.Name]; ok {
		o.Passed = valuePassed
	}

	o.Vars = vars
	value, err := o.Evaluate()
	if err != nil {
		return err
	}

	vars[o.Name] = value

	return nil
}

func getRequiredGlobalOptions(cfgText []byte, t *task.Task) ([]string, error) {
	ordered, err := getOrderedGlobalOptions(cfgText)
	if err != nil {
		return nil, err
	}

	cfg, err := Parse(cfgText)
	if err != nil {
		return nil, err
	}

	required, err := FindAllOptions(t, cfg)
	if err != nil {
		return nil, err
	}

	var output []string
	for _, o := range ordered {
		for _, r := range required {
			if r.Name != o {
				continue
			}

			output = append(output, o)
		}
	}

	return output, nil
}

func getOrderedGlobalOptions(cfgText []byte) ([]string, error) {
	cfgMapSlice := new(struct {
		Options yaml.MapSlice
	})

	if err := yaml.Unmarshal(cfgText, cfgMapSlice); err != nil {
		return nil, err
	}

	var ordered []string
	for _, optionMapSlice := range cfgMapSlice.Options {
		ordered = append(ordered, optionMapSlice.Key.(string))
	}

	return ordered, nil
}

func getOrderedTaskOptions(cfgText []byte, taskName string) ([]string, error) {
	cfgMapSlice := new(struct {
		Tasks yaml.MapSlice
	})

	if err := yaml.Unmarshal(cfgText, cfgMapSlice); err != nil {
		return nil, err
	}

	var ordered []string
	for _, taskMapSlice := range cfgMapSlice.Tasks {
		if name := taskMapSlice.Key.(string); name != taskName {
			continue
		}

		for _, mapSlice := range taskMapSlice.Value.(yaml.MapSlice) {
			if name := mapSlice.Key.(string); name != "options" {
				continue
			}

			for _, optionMapSlice := range mapSlice.Value.(yaml.MapSlice) {
				ordered = append(ordered, optionMapSlice.Key.(string))
			}
			break
		}
	}

	return ordered, nil
}

func addSubTasks(t *task.Task, cfg *Config, cfgText []byte) error {

	for _, run := range t.RunList {
		for _, subTaskDesc := range run.SubTaskList {
			st, ok := cfg.Tasks[subTaskDesc.Name]
			if !ok {
				return fmt.Errorf(
					`sub-task "%s" does not exist`,
					subTaskDesc.Name,
				)
			}

			passed := subTaskDesc.Options
			subTask := *st

			for optName := range passed {
				if _, isValidOption := subTask.Options[optName]; !isValidOption {
					return fmt.Errorf(
						`option "%s" cannot be passed to task "%s"`,
						optName, subTask.Name,
					)
				}
			}

			if err := passTaskValues(&subTask, cfg, cfgText, passed); err != nil {
				return err
			}

			run.Tasks = append(run.Tasks, subTask)

		}
	}

	return nil
}
<|endoftext|>


<commit_before>/*
Copyright 2021 The TestGrid Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package config

import (
	"container/heap"
	"context"
	"errors"
	"fmt"
	"sync"
	"time"

	"bitbucket.org/creachadair/stringset"
	configpb "github.com/GoogleCloudPlatform/testgrid/pb/config"
	"github.com/sirupsen/logrus"
)

// TestGroupQueue can send test groups to receivers at a specific frequency.
//
// Also contains the ability to modify the next time to send groups.
// First call must be to Init().
// Exported methods are safe to call concurrently.
type TestGroupQueue struct {
	queue  priorityQueue
	items  map[string]*item
	lock   sync.RWMutex
	signal chan struct{}
}

// Init (or reinit) the queue with the specified groups, which should be updated at frequency.
func (q *TestGroupQueue) Init(testGroups []*configpb.TestGroup, when time.Time) {
	n := len(testGroups)
	found := stringset.NewSize(n)

	q.lock.Lock()
	defer q.lock.Unlock()
	defer q.rouse()

	if q.signal == nil {
		q.signal = make(chan struct{})
	}

	if q.items == nil {
		q.items = make(map[string]*item, n)
	}
	if q.queue == nil {
		q.queue = make(priorityQueue, 0, n)
	}
	items := q.items

	for _, tg := range testGroups {
		name := tg.Name
		found.Add(name)
		it, ok := items[name]
		if !ok {
			it = &item{
				tg:    tg,
				when:  when,
				index: len(q.queue),
			}
			heap.Push(&q.queue, it)
			items[name] = it
			logrus.WithFields(logrus.Fields{
				"when":  when,
				"group": name,
			}).Info("Adding group to queue")
		} else {
			it.tg = tg
		}
	}

	for name, it := range items {
		if found.Contains(name) {
			continue
		}
		logrus.WithField("group", name).Info("Removing group from queue")
		heap.Remove(&q.queue, it.index)
		delete(q.items, name)
	}
}

// FixAll will fix multiple groups inside a single critical section.
func (q *TestGroupQueue) FixAll(whens map[string]time.Time) error {
	q.lock.Lock()
	defer q.lock.Unlock()
	var missing []string
	defer q.rouse()

	for name, when := range whens {
		it, ok := q.items[name]
		if !ok {
			missing = append(missing, name)
			continue
		}
		logrus.WithFields(logrus.Fields{
			"group": name,
			"when":  when,
		}).Info("Fixed group")
		it.when = when
	}
	heap.Init(&q.queue)
	if len(missing) > 0 {
		return fmt.Errorf("not found: %v", missing)
	}
	return nil
}

// Fix the next time to send the group to receivers.
func (q *TestGroupQueue) Fix(name string, when time.Time) error {
	q.lock.Lock()
	defer q.lock.Unlock()
	defer q.rouse()

	it, ok := q.items[name]
	if !ok {
		return errors.New("not found")
	}
	it.when = when
	heap.Fix(&q.queue, it.index)
	logrus.WithFields(logrus.Fields{
		"group": name,
		"when":  when,
	}).Info("Fixed group")
	return nil
}

// Status of the queue: depth, next item and when the next item is ready.
func (q *TestGroupQueue) Status() (int, *configpb.TestGroup, time.Time) {
	q.lock.RLock()
	defer q.lock.RUnlock()
	var tg *configpb.TestGroup
	var when time.Time
	if it := q.queue.peek(); it != nil {
		tg = it.tg
		when = it.when
	}
	return len(q.queue), tg, when
}

func (q *TestGroupQueue) rouse() {
	select {
	case q.signal <- struct{}{}: // wake up early
	default: // not sleeping
	}
}

func (q *TestGroupQueue) sleep(d time.Duration) {
	log := logrus.WithFields(logrus.Fields{
		"seconds": d.Round(100 * time.Millisecond).Seconds(),
	})
	log.Info("Sleeping...")
	sleep := time.NewTimer(d)
	select {
	case <-q.signal:
		if !sleep.Stop() {
			<-sleep.C
		}
		log.Info("Roused")
	case <-sleep.C:
	}
}

// Send test groups to receivers until the context expires.
//
// Pops items off the queue when frequency is zero.
// Otherwise reschedules the item after the specified frequency has elapsed.
func (q *TestGroupQueue) Send(ctx context.Context, receivers chan<- *configpb.TestGroup, frequency time.Duration) error {
	var next func() (*configpb.TestGroup, time.Time)
	if frequency == 0 {
		next = func() (*configpb.TestGroup, time.Time) {
			if len(q.queue) == 0 {
				return nil, time.Time{}
			}
			it := heap.Pop(&q.queue).(*item)
			return it.tg, it.when
		}
	} else {
		next = func() (*configpb.TestGroup, time.Time) {
			it := q.queue.peek()
			if it == nil {
				return nil, time.Time{}
			}
			when := it.when
			it.when = time.Now().Add(frequency)
			heap.Fix(&q.queue, it.index)
			return it.tg, when
		}
	}

	for {
		q.lock.Lock()
		select {
		case <-ctx.Done():
			return ctx.Err()
		default:
		}
		tg, when := next()
		q.lock.Unlock()

		if tg == nil {
			if frequency == 0 {
				return nil
			}
			q.sleep(time.Second)
			continue
		}

		if dur := when.Sub(time.Now()); dur > 0 {
			q.sleep(dur)
		}
		select {
		case receivers <- tg:
		case <-ctx.Done():
			return ctx.Err()
		}
	}
}

type priorityQueue []*item

func (pq priorityQueue) Len() int { return len(pq) }
func (pq priorityQueue) Less(i, j int) bool {
	return pq[i].when.Before(pq[j].when)
}
func (pq priorityQueue) Swap(i, j int) {
	pq[i], pq[j] = pq[j], pq[i]
	pq[i].index = i
	pq[j].index = j
}

func (pq *priorityQueue) Push(something interface{}) {
	it := something.(*item)
	it.index = len(*pq)
	*pq = append(*pq, it)
}

func (pq *priorityQueue) Pop() interface{} {
	old := *pq
	n := len(old)
	it := old[n-1]
	it.index = -1
	old[n-1] = nil
	*pq = old[0 : n-1]
	return it
}

func (pq priorityQueue) peek() *item {
	n := len(pq)
	if n == 0 {
		return nil
	}
	return pq[0]
}

type item struct {
	tg    *configpb.TestGroup
	when  time.Time
	index int
}
<commit_msg>Make fixing group update time less noisy<commit_after>/*
Copyright 2021 The TestGrid Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package config

import (
	"container/heap"
	"context"
	"errors"
	"fmt"
	"sync"
	"time"

	"bitbucket.org/creachadair/stringset"
	configpb "github.com/GoogleCloudPlatform/testgrid/pb/config"
	"github.com/sirupsen/logrus"
)

// TestGroupQueue can send test groups to receivers at a specific frequency.
//
// Also contains the ability to modify the next time to send groups.
// First call must be to Init().
// Exported methods are safe to call concurrently.
type TestGroupQueue struct {
	queue  priorityQueue
	items  map[string]*item
	lock   sync.RWMutex
	signal chan struct{}
}

// Init (or reinit) the queue with the specified groups, which should be updated at frequency.
func (q *TestGroupQueue) Init(testGroups []*configpb.TestGroup, when time.Time) {
	n := len(testGroups)
	found := stringset.NewSize(n)

	q.lock.Lock()
	defer q.lock.Unlock()
	defer q.rouse()

	if q.signal == nil {
		q.signal = make(chan struct{})
	}

	if q.items == nil {
		q.items = make(map[string]*item, n)
	}
	if q.queue == nil {
		q.queue = make(priorityQueue, 0, n)
	}
	items := q.items

	for _, tg := range testGroups {
		name := tg.Name
		found.Add(name)
		it, ok := items[name]
		if !ok {
			it = &item{
				tg:    tg,
				when:  when,
				index: len(q.queue),
			}
			heap.Push(&q.queue, it)
			items[name] = it
			logrus.WithFields(logrus.Fields{
				"when":  when,
				"group": name,
			}).Info("Adding group to queue")
		} else {
			it.tg = tg
		}
	}

	for name, it := range items {
		if found.Contains(name) {
			continue
		}
		logrus.WithField("group", name).Info("Removing group from queue")
		heap.Remove(&q.queue, it.index)
		delete(q.items, name)
	}
}

// FixAll will fix multiple groups inside a single critical section.
func (q *TestGroupQueue) FixAll(whens map[string]time.Time) error {
	q.lock.Lock()
	defer q.lock.Unlock()
	var missing []string
	defer q.rouse()

	for name, when := range whens {
		it, ok := q.items[name]
		if !ok {
			missing = append(missing, name)
			continue
		}
		if !when.Equal(it.when) {
			logrus.WithFields(logrus.Fields{
				"group": name,
				"when":  when,
			}).Info("Fixing groups")
		}
		it.when = when
	}
	heap.Init(&q.queue)
	if len(missing) > 0 {
		return fmt.Errorf("not found: %v", missing)
	}
	return nil
}

// Fix the next time to send the group to receivers.
func (q *TestGroupQueue) Fix(name string, when time.Time) error {
	q.lock.Lock()
	defer q.lock.Unlock()
	defer q.rouse()

	it, ok := q.items[name]
	if !ok {
		return errors.New("not found")
	}
	if !when.Equal(it.when) {
		logrus.WithFields(logrus.Fields{
			"group": name,
			"when":  when,
		}).Info("Fixed group")
	}
	it.when = when
	heap.Fix(&q.queue, it.index)
	return nil
}

// Status of the queue: depth, next item and when the next item is ready.
func (q *TestGroupQueue) Status() (int, *configpb.TestGroup, time.Time) {
	q.lock.RLock()
	defer q.lock.RUnlock()
	var tg *configpb.TestGroup
	var when time.Time
	if it := q.queue.peek(); it != nil {
		tg = it.tg
		when = it.when
	}
	return len(q.queue), tg, when
}

func (q *TestGroupQueue) rouse() {
	select {
	case q.signal <- struct{}{}: // wake up early
	default: // not sleeping
	}
}

func (q *TestGroupQueue) sleep(d time.Duration) {
	log := logrus.WithFields(logrus.Fields{
		"seconds": d.Round(100 * time.Millisecond).Seconds(),
	})
	log.Info("Sleeping...")
	sleep := time.NewTimer(d)
	select {
	case <-q.signal:
		if !sleep.Stop() {
			<-sleep.C
		}
		log.Info("Roused")
	case <-sleep.C:
	}
}

// Send test groups to receivers until the context expires.
//
// Pops items off the queue when frequency is zero.
// Otherwise reschedules the item after the specified frequency has elapsed.
func (q *TestGroupQueue) Send(ctx context.Context, receivers chan<- *configpb.TestGroup, frequency time.Duration) error {
	var next func() (*configpb.TestGroup, time.Time)
	if frequency == 0 {
		next = func() (*configpb.TestGroup, time.Time) {
			if len(q.queue) == 0 {
				return nil, time.Time{}
			}
			it := heap.Pop(&q.queue).(*item)
			return it.tg, it.when
		}
	} else {
		next = func() (*configpb.TestGroup, time.Time) {
			it := q.queue.peek()
			if it == nil {
				return nil, time.Time{}
			}
			when := it.when
			it.when = time.Now().Add(frequency)
			heap.Fix(&q.queue, it.index)
			return it.tg, when
		}
	}

	for {
		q.lock.Lock()
		select {
		case <-ctx.Done():
			return ctx.Err()
		default:
		}
		tg, when := next()
		q.lock.Unlock()

		if tg == nil {
			if frequency == 0 {
				return nil
			}
			q.sleep(time.Second)
			continue
		}

		if dur := when.Sub(time.Now()); dur > 0 {
			q.sleep(dur)
		}
		select {
		case receivers <- tg:
		case <-ctx.Done():
			return ctx.Err()
		}
	}
}

type priorityQueue []*item

func (pq priorityQueue) Len() int { return len(pq) }
func (pq priorityQueue) Less(i, j int) bool {
	return pq[i].when.Before(pq[j].when)
}
func (pq priorityQueue) Swap(i, j int) {
	pq[i], pq[j] = pq[j], pq[i]
	pq[i].index = i
	pq[j].index = j
}

func (pq *priorityQueue) Push(something interface{}) {
	it := something.(*item)
	it.index = len(*pq)
	*pq = append(*pq, it)
}

func (pq *priorityQueue) Pop() interface{} {
	old := *pq
	n := len(old)
	it := old[n-1]
	it.index = -1
	old[n-1] = nil
	*pq = old[0 : n-1]
	return it
}

func (pq priorityQueue) peek() *item {
	n := len(pq)
	if n == 0 {
		return nil
	}
	return pq[0]
}

type item struct {
	tg    *configpb.TestGroup
	when  time.Time
	index int
}
<|endoftext|>


<commit_before>// Copyright 2014 Akamai Technologies, Inc.
// Please see the accompanying LICENSE file for licensing information.
//
// Note: this file is a derivative work of
//
// https://github.com/Operational-Transformation/ot.py/blob/3777bee2c2cdb263d4ba09dd8ff0974b48f6b87c/ot/text_operation.py
// https://github.com/Operational-Transformation/ot.py/blob/3777bee2c2cdb263d4ba09dd8ff0974b48f6b87c/ot/client.py
// https://github.com/Operational-Transformation/ot.v/blob/d48f2598142236ee8980247060c98ba3175c464a/ListOperation.v
//
// Copyright © 2012-2013 Tim Baumann, http://timbaumann.info
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the “Software”), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

package ot

import (
	"bytes"
	"encoding/json"
	"fmt"
	"strings"
)

type Tag int

type Op struct {
	// Len is either delete-len (if negative) or retain-len (if positive)
	Size int

	// Body is the text to be inserted
	Body []rune
}

func (o *Op) IsRetain() bool {
	if o == nil {
		return false
	}
	return o.Size > 0
}

func (o *Op) IsDelete() bool {
	if o == nil {
		return false
	}
	return o.Size < 0
}

func (o *Op) IsInsert() bool {
	if o == nil {
		return false
	}
	return o.Size == 0
}

func (o *Op) Len() int {
	switch {
	case o == nil:
		return 0
	case o.Size < 0:
		return -o.Size
	case o.Size > 0:
		return o.Size
	default:
		return len(o.Body)
	}
}

func AsString(rs []rune) string {
	buf := bytes.Buffer{}
	for _, r := range rs {
		buf.WriteRune(r)
	}
	return buf.String()
}

func (o *Op) String() string {
	switch {
	case o == nil:
		return "N "
	case o.IsDelete():
		return fmt.Sprintf("D%d", -o.Size)
	case o.IsRetain():
		return fmt.Sprintf("R%d", o.Size)
	case o.IsInsert():
		return fmt.Sprintf("I%q", AsString(o.Body))
	default:
		return fmt.Sprintf("E%#v", o)
	}
}

type Ops []Op

func (os Ops) String() string {
	if len(os) > 0 {
		strs := []string{}
		for _, o := range os {
			strs = append(strs, o.String())
		}
		//return fmt.Sprintf("[]op{\n\t%s\n\t}", strings.Join(strs, ",\n\t"))
		return fmt.Sprintf("Ops{%s}", strings.Join(strs, " "))
	} else {
		return "Ops{}"
	}
}

func (os Ops) First() *Op {
	return &os[0]
}

func (os Ops) Last() *Op {
	return &os[len(os)-1]
}

func (os Ops) Rest() Ops {
	return os[1:]
}

func (os *Ops) Insert(body []rune) {
	ops := *os
	olen := len(ops)
	switch {
	case len(body) == 0:
		break
	case olen > 0 && os.Last().IsInsert():
		ops.Last().Body = append(ops.Last().Body, body...)
	case olen > 0 && os.Last().IsDelete():
		if olen > 1 && ops[olen-2].IsInsert() {
			ops[olen-2].Body = append(ops[olen-2].Body, body...)
		} else {
			*os = append(ops[:olen-1], Op{Body: body}, ops[olen-1])
		}
	default:
		*os = append(ops, Op{Body: body})
	}
}

func (os *Ops) Retain(size int) {
	switch {
	case size == 0:
		return
	case len(*os) > 0 && os.Last().IsRetain():
		os.Last().Size += size
	default:
		*os = append(*os, Op{Size: size})
	}
}

func (os *Ops) Delete(size int) {
	ops := *os
	olen := len(ops)
	if size == 0 {
		return
	}
	if size > 0 {
		size = -size
	}
	if olen > 0 && ops[olen-1].IsDelete() {
		ops[olen-1].Size += size
	} else {
		*os = append(ops, Op{Size: size})
	}
}

func (o Op) MarshalJSON() ([]byte, error) {
	switch {
	case o.IsDelete() || o.IsRetain():
		return json.Marshal(o.Size)
	default:
		return json.Marshal(AsString(o.Body))
	}
}

func (o *Op) UnmarshalJSON(data []byte) error {
	switch {
	case len(data) == 0:
		return fmt.Errorf("illegal op: %q", data)
	case data[0] == '"':
		var s string
		if err := json.Unmarshal(data, &s); err != nil {
			return err
		}
		o.Body = AsSlice(s)
		return nil
	default:
		return json.Unmarshal(data, &o.Size)
	}
}

func min(a, b int) int {
	if a < b {
		return a
	} else {
		return b
	}
}

func (o Op) Shorten(nl int) Op {
	switch {
	case o.IsRetain():
		o.Size -= nl
	case o.IsDelete():
		o.Size += nl
	case o.IsInsert():
		o.Body = o.Body[nl:]
	}
	return o
}

func shortenOps(a *Op, b *Op) (*Op, *Op) {
	la := a.Len()
	lb := b.Len()
	switch {
	case la == lb:
		return nil, nil
	case a != nil && b != nil && la > lb:
		a2 := a.Shorten(lb)
		return &a2, nil
	case a != nil && b != nil && la <= lb:
		b2 := b.Shorten(la)
		return nil, &b2
	}
	return a, b
}

func Compose(as, bs Ops) Ops {
	if len(as) == 0 {
		return bs
	}

	ops := Ops{}

Fix:
	for {
		switch {
		case len(as) == 0 && len(bs) == 0:
			break Fix
		case len(as) > 0 && as.First().IsDelete():
			ops.Delete(as.First().Size)
			as = as.Rest()
			continue
		case len(bs) > 0 && bs.First().IsInsert():
			ops.Insert(bs.First().Body)
			bs = bs.Rest()
			continue
		case len(as) > 0 && len(bs) > 0:
			a := as.First()
			b := bs.First()
			minlen := min(a.Len(), b.Len())
			switch {
			case a.IsRetain() && b.IsRetain():
				ops.Retain(minlen)
			case a.IsRetain() && b.IsDelete():
				ops.Delete(minlen)
			case a.IsInsert() && b.IsRetain():
				ops.Insert(a.Body[:minlen])
			case a.IsInsert() && b.IsDelete():
				ops.Delete(minlen)
			}
			a, b = shortenOps(a, b)
			if a == nil {
				as = as.Rest()
			} else {
				as = append(Ops{*a}, as.Rest()...)
			}
			if b == nil {
				bs = bs.Rest()
			} else {
				bs = append(Ops{*b}, bs.Rest()...)
			}
			continue
		case len(as) > 0 && len(bs) == 0:
			ops = append(ops, as...)
			as = nil
			continue
		case len(as) == 0 && len(bs) > 0:
			ops = append(ops, bs...)
			bs = nil
			continue
		default:
			panic("impossible")
		}
	}

	return ops
}

func Transform(as, bs Ops) (Ops, Ops) {
	var a, b *Op
	var aos, bos Ops

Fix:
	for {
		switch {
		case len(as) == 0 && len(bs) == 0:
			break Fix
		case len(as) > 0 && as.First().IsInsert():
			a = as.First()
			aos.Insert(a.Body)
			bos.Retain(a.Len())
			as = as.Rest()
			continue
		case len(bs) > 0 && bs.First().IsInsert():
			b = bs.First()
			aos.Retain(b.Len())
			bos.Insert(b.Body)
			bs = bs.Rest()
			continue
		case len(as) > 0 && len(bs) > 0:
			a = as.First()
			b = bs.First()
			minlen := min(a.Len(), b.Len())
			switch {
			case a.IsRetain() && b.IsRetain():
				aos.Retain(minlen)
				bos.Retain(minlen)
			case a.IsDelete() && b.IsRetain():
				aos.Delete(minlen)
			case a.IsRetain() && b.IsDelete():
				bos.Delete(minlen)
			}
			a, b = shortenOps(a, b)
			if a == nil {
				as = as.Rest()
			} else {
				as = append(Ops{*a}, as.Rest()...)
			}
			if b == nil {
				bs = bs.Rest()
			} else {
				bs = append(Ops{*b}, bs.Rest()...)
			}
			continue
		case len(as) > 0 && len(bs) == 0:
			aos = append(aos, as...)
			return aos, bos
		case len(as) == 0 && len(bs) > 0:
			bos = append(bos, bs...)
			return aos, bos
		default:
			panic("oops!")
		}
	}

	return aos, bos
}

type Doc struct {
	// Current text
	body []rune
}

func NewDoc() *Doc {
	return &Doc{
		body: []rune{},
	}
}

func (d *Doc) Len() int {
	return len(d.body)
}

func (d *Doc) String() string {
	buf := bytes.Buffer{}
	for _, r := range d.body {
		buf.WriteRune(r)
	}
	return buf.String()
}

func (d *Doc) Apply(os Ops) {
	pos := 0
	parts := [][]rune{}
	for _, o := range os {
		switch {
		case o.IsDelete():
			pos += o.Len()
		case o.IsRetain() && o.Len() > 0:
			parts = append(parts, d.body[pos:pos+o.Len()])
			pos += o.Len()
		case o.IsInsert():
			parts = append(parts, o.Body)
		}
	}
	size := 0
	for _, p := range parts {
		size += len(p)
	}
	d.body = make([]rune, size)

	idx := 0
	for _, p := range parts {
		copy(d.body[idx:idx+len(p)], p)
		idx += len(p)
	}
}
<commit_msg>Fix deletion bug not covered by tests.<commit_after>// Copyright 2014 Akamai Technologies, Inc.
// Please see the accompanying LICENSE file for licensing information.
//
// Note: this file is a derivative work of
//
// https://github.com/Operational-Transformation/ot.py/blob/3777bee2c2cdb263d4ba09dd8ff0974b48f6b87c/ot/text_operation.py
// https://github.com/Operational-Transformation/ot.py/blob/3777bee2c2cdb263d4ba09dd8ff0974b48f6b87c/ot/client.py
// https://github.com/Operational-Transformation/ot.v/blob/d48f2598142236ee8980247060c98ba3175c464a/ListOperation.v
//
// Copyright © 2012-2013 Tim Baumann, http://timbaumann.info
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the “Software”), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

package ot

import (
	"bytes"
	"encoding/json"
	"fmt"
	"strings"
)

type Tag int

type Op struct {
	// Len is either delete-len (if negative) or retain-len (if positive)
	Size int

	// Body is the text to be inserted
	Body []rune
}

func (o *Op) IsRetain() bool {
	if o == nil {
		return false
	}
	return o.Size > 0
}

func (o *Op) IsDelete() bool {
	if o == nil {
		return false
	}
	return o.Size < 0
}

func (o *Op) IsInsert() bool {
	if o == nil {
		return false
	}
	return o.Size == 0
}

func (o *Op) Len() int {
	switch {
	case o == nil:
		return 0
	case o.Size < 0:
		return -o.Size
	case o.Size > 0:
		return o.Size
	default:
		return len(o.Body)
	}
}

func AsString(rs []rune) string {
	buf := bytes.Buffer{}
	for _, r := range rs {
		buf.WriteRune(r)
	}
	return buf.String()
}

func (o *Op) String() string {
	switch {
	case o == nil:
		return "N "
	case o.IsDelete():
		return fmt.Sprintf("D%d", -o.Size)
	case o.IsRetain():
		return fmt.Sprintf("R%d", o.Size)
	case o.IsInsert():
		return fmt.Sprintf("I%q", AsString(o.Body))
	default:
		return fmt.Sprintf("E%#v", o)
	}
}

type Ops []Op

func (os Ops) String() string {
	if len(os) > 0 {
		strs := []string{}
		for _, o := range os {
			strs = append(strs, o.String())
		}
		//return fmt.Sprintf("[]op{\n\t%s\n\t}", strings.Join(strs, ",\n\t"))
		return fmt.Sprintf("Ops{%s}", strings.Join(strs, " "))
	} else {
		return "Ops{}"
	}
}

func (os Ops) First() *Op {
	return &os[0]
}

func (os Ops) Last() *Op {
	return &os[len(os)-1]
}

func (os Ops) Rest() Ops {
	return os[1:]
}

func (os *Ops) Insert(body []rune) {
	ops := *os
	olen := len(ops)
	switch {
	case len(body) == 0:
		break
	case olen > 0 && os.Last().IsInsert():
		ops.Last().Body = append(ops.Last().Body, body...)
	case olen > 0 && os.Last().IsDelete():
		if olen > 1 && ops[olen-2].IsInsert() {
			ops[olen-2].Body = append(ops[olen-2].Body, body...)
		} else {
			*os = append(ops[:olen-1], Op{Body: body}, ops[olen-1])
		}
	default:
		*os = append(ops, Op{Body: body})
	}
}

func (os *Ops) Retain(size int) {
	switch {
	case size == 0:
		return
	case len(*os) > 0 && os.Last().IsRetain():
		os.Last().Size += size
	default:
		*os = append(*os, Op{Size: size})
	}
}

func (os *Ops) Delete(size int) {
	ops := *os
	olen := len(ops)
	if size == 0 {
		return
	}
	if size > 0 {
		size = -size
	}
	if olen > 0 && ops[olen-1].IsDelete() {
		ops[olen-1].Size += size
	} else {
		*os = append(ops, Op{Size: size})
	}
}

func (o Op) MarshalJSON() ([]byte, error) {
	switch {
	case o.IsDelete() || o.IsRetain():
		return json.Marshal(o.Size)
	default:
		return json.Marshal(AsString(o.Body))
	}
}

func (o *Op) UnmarshalJSON(data []byte) error {
	switch {
	case len(data) == 0:
		return fmt.Errorf("illegal op: %q", data)
	case data[0] == '"':
		var s string
		if err := json.Unmarshal(data, &s); err != nil {
			return err
		}
		o.Body = AsSlice(s)
		return nil
	default:
		return json.Unmarshal(data, &o.Size)
	}
}

func min(a, b int) int {
	if a < b {
		return a
	} else {
		return b
	}
}

func (o Op) Shorten(nl int) Op {
	switch {
	case o.IsRetain():
		o.Size -= nl
	case o.IsDelete():
		o.Size += nl
	case o.IsInsert():
		o.Body = o.Body[nl:]
	}
	return o
}

func shortenOps(a *Op, b *Op) (*Op, *Op) {
	la := a.Len()
	lb := b.Len()
	switch {
	case la == lb:
		return nil, nil
	case a != nil && b != nil && la > lb:
		a2 := a.Shorten(lb)
		return &a2, nil
	case a != nil && b != nil && la <= lb:
		b2 := b.Shorten(la)
		return nil, &b2
	}
	return a, b
}

func Compose(as, bs Ops) Ops {
	if len(as) == 0 {
		return bs
	}

	ops := Ops{}

Fix:
	for {
		switch {
		case len(as) == 0 && len(bs) == 0:
			break Fix
		case len(as) > 0 && as.First().IsDelete():
			ops.Delete(as.First().Size)
			as = as.Rest()
			continue
		case len(bs) > 0 && bs.First().IsInsert():
			ops.Insert(bs.First().Body)
			bs = bs.Rest()
			continue
		case len(as) > 0 && len(bs) > 0:
			a := as.First()
			b := bs.First()
			minlen := min(a.Len(), b.Len())
			switch {
			case a.IsRetain() && b.IsRetain():
				ops.Retain(minlen)
			case a.IsRetain() && b.IsDelete():
				ops.Delete(minlen)
			case a.IsInsert() && b.IsRetain():
				ops.Insert(a.Body[:minlen])
			case a.IsInsert() && b.IsDelete():
				//ops.Delete(minlen)
			}
			a, b = shortenOps(a, b)
			if a == nil {
				as = as.Rest()
			} else {
				as = append(Ops{*a}, as.Rest()...)
			}
			if b == nil {
				bs = bs.Rest()
			} else {
				bs = append(Ops{*b}, bs.Rest()...)
			}
			continue
		case len(as) > 0 && len(bs) == 0:
			ops = append(ops, as...)
			as = nil
			continue
		case len(as) == 0 && len(bs) > 0:
			ops = append(ops, bs...)
			bs = nil
			continue
		default:
			panic("impossible")
		}
	}

	return ops
}

func Transform(as, bs Ops) (Ops, Ops) {
	var a, b *Op
	var aos, bos Ops

Fix:
	for {
		switch {
		case len(as) == 0 && len(bs) == 0:
			break Fix
		case len(as) > 0 && as.First().IsInsert():
			a = as.First()
			aos.Insert(a.Body)
			bos.Retain(a.Len())
			as = as.Rest()
			continue
		case len(bs) > 0 && bs.First().IsInsert():
			b = bs.First()
			aos.Retain(b.Len())
			bos.Insert(b.Body)
			bs = bs.Rest()
			continue
		case len(as) > 0 && len(bs) > 0:
			a = as.First()
			b = bs.First()
			minlen := min(a.Len(), b.Len())
			switch {
			case a.IsRetain() && b.IsRetain():
				aos.Retain(minlen)
				bos.Retain(minlen)
			case a.IsDelete() && b.IsRetain():
				aos.Delete(minlen)
			case a.IsRetain() && b.IsDelete():
				bos.Delete(minlen)
			}
			a, b = shortenOps(a, b)
			if a == nil {
				as = as.Rest()
			} else {
				as = append(Ops{*a}, as.Rest()...)
			}
			if b == nil {
				bs = bs.Rest()
			} else {
				bs = append(Ops{*b}, bs.Rest()...)
			}
			continue
		case len(as) > 0 && len(bs) == 0:
			aos = append(aos, as...)
			return aos, bos
		case len(as) == 0 && len(bs) > 0:
			bos = append(bos, bs...)
			return aos, bos
		default:
			panic("oops!")
		}
	}

	return aos, bos
}

type Doc struct {
	// Current text
	body []rune
}

func NewDoc() *Doc {
	return &Doc{
		body: []rune{},
	}
}

func (d *Doc) Len() int {
	return len(d.body)
}

func (d *Doc) String() string {
	buf := bytes.Buffer{}
	for _, r := range d.body {
		buf.WriteRune(r)
	}
	return buf.String()
}

func (d *Doc) Apply(os Ops) {
	pos := 0
	parts := [][]rune{}
	for _, o := range os {
		switch {
		case o.IsDelete():
			pos += o.Len()
		case o.IsRetain() && o.Len() > 0:
			parts = append(parts, d.body[pos:pos+o.Len()])
			pos += o.Len()
		case o.IsInsert():
			parts = append(parts, o.Body)
		}
	}
	size := 0
	for _, p := range parts {
		size += len(p)
	}
	d.body = make([]rune, size)

	idx := 0
	for _, p := range parts {
		copy(d.body[idx:idx+len(p)], p)
		idx += len(p)
	}
}
<|endoftext|>