<commit_before>package brokerage_server_ib

import (
	"context"
	"errors"
	"fmt"
	"io"
	"sync/atomic"
	"time"

	"github.com/inconshreveable/log15"

	"github.com/dimfeld/brokerage_server/types"
	"github.com/dimfeld/ib"
)

type replyBehavior int

type activeReply struct {
	dataChan chan ib.Reply
	ctx      context.Context
}

var (
	// This should never happen since we don't burst messages.
	ErrWayTooFast = errors.New("Could not send message at this rate")
)

const (
	REPLY_CONTINUE replyBehavior = iota
	REPLY_DONE
)

type callbackFunc func(r ib.Reply) (replyBehavior, error)

func (p *IB) handleMatchedReply(r ib.MatchedReply) {
	id := r.ID()

	p.activeMutex.Lock()
	reply, ok := p.active[id]
	p.activeMutex.Unlock()

	if !ok {
		// Got an unexpected reply
		// This will actually be a fairly normal occurrence so don't warn on it.
		prettyPrint := func() string {
			return fmt.Sprintf("%T:%+v", r, r)
		}
		p.LogDebugVerbose("Unexpected reply", "msg", log15.Lazy{prettyPrint})
		return
	}

	select {
	case reply.dataChan <- r:
	case <-reply.ctx.Done():
	}
}

func (p *IB) handleReply(rep ib.Reply) {
	prettyPrint := func() string {
		return fmt.Sprintf("%T:%+v", rep, rep)
	}
	p.LogDebugTrace("received", "msg", log15.Lazy{prettyPrint})

	switch r := rep.(type) {
	case *ib.ManagedAccounts:
		// TODO Save list of accounts

	case *ib.NextValidID:
		p.LogDebugTrace("NextValidID", "id", r.OrderID)
		atomic.StoreInt64(&p.nextOrderIdValue, r.OrderID)
		p.open = true
		if p.connectChan != nil {
			close(p.connectChan)
			p.connectChan = nil
		}

	case *ib.ErrorMessage:
		id := r.ID()

		if r.SeverityWarning() {
			p.Logger.Warn("info", "err", r)
		} else {
			p.Logger.Error("received error", "err", r)
		}

		// TODO Some errors are not actually replies to the
		// request, and should be handled here instead of passed
		// through.

		if id != -1 {
			p.handleMatchedReply(r)
			// Make sure the request gets closed, since nothing else is coming in.
			p.closeMatchedRequest(id)
		}

	case ib.MatchedReply:
		p.handleMatchedReply(r)
	}

	// TODO Handle unmatched replies.
}

func (p *IB) nextOrderID() int64 {
	return atomic.AddInt64(&p.nextOrderIdValue, 1)
}

func (p *IB) send(r ib.Request) error {
	res := p.rateLimiter.Reserve()
	if !res.OK() {
		// This should never happen since we don't burst messages.
		return ErrWayTooFast
	}

	if waitTime := res.Delay(); waitTime > 0 {
		time.Sleep(waitTime)
	}

	prettyPrint := func() string {
		return fmt.Sprintf("%T:%+v", r, r)
	}
	p.LogDebugTrace("Sending", "msg", log15.Lazy{prettyPrint})
	err := p.engine.Send(r)
	if err == io.EOF {
		err = types.ErrDisconnected
	}
	return err
}

func (p *IB) sendMatchedRequest(ctx context.Context, r ib.MatchedRequest) (nextId int64, dataChan chan ib.Reply, err error) {
	nextId = r.ID()
	if nextId == 0 {
		nextId = p.nextOrderID()
		r.SetID(nextId)
	}

	p.activeMutex.Lock()
	rep, ok := p.active[nextId]
	if !ok {
		dataChan = make(chan ib.Reply, 1)
		p.active[nextId] = activeReply{
			dataChan: dataChan,
			ctx:      ctx,
		}
	} else {
		dataChan = rep.dataChan
	}
	p.activeMutex.Unlock()

	err = p.send(r)
	return
}

func (p *IB) closeMatchedRequest(id int64) {
	p.activeMutex.Lock()
	rep, ok := p.active[id]
	if ok {
	outer:
		for {
			// Drain the channel, if it needs it.
			select {
			case <-rep.dataChan:
			default:
				break outer
			}
		}

		delete(p.active, id)
	}
	p.activeMutex.Unlock()
}

func (p *IB) startStreamingRequest(ctx context.Context, r ib.MatchedRequest) (reqId int64, repChan chan ib.Reply, err error) {
	if err = p.send(r); err != nil {
		return
	}

	var dataChan chan ib.Reply
	reqId, dataChan, err = p.sendMatchedRequest(ctx, r)
	if err != nil {
		return
	}

	repChan = make(chan ib.Reply, 20)

	go func() {
		for {
			select {
			case data := <-dataChan:
				repChan <- data
			case <-ctx.Done():
				p.closeMatchedRequest(reqId)
				close(repChan)
				return

			}
		}
	}()

	return
}

func (p *IB) sendUnmatchedRequest(r ib.Request) error {
	return p.send(r)
}

func (p *IB) syncMatchedRequest(ctx context.Context, r ib.MatchedRequest, cb callbackFunc) error {
	ctx, cancelFunc := context.WithCancel(ctx)
	reqId, dataChan, err := p.sendMatchedRequest(ctx, r)

	defer func() {
		cancelFunc()
		p.closeMatchedRequest(reqId)
	}()

	if err != nil {
		return err
	}

	for {
		select {
		case data := <-dataChan:
			if data == nil {
				// TODO Better error handling
				return errors.New("error occurred")
			}

			behavior, err := cb(data)
			if err != nil || behavior == REPLY_DONE {
				return err
			}
		case <-ctx.Done():
			return ctx.Err()
		}
	}
}
<commit_msg>Don't log on errors that have a handler<commit_after>package brokerage_server_ib

import (
	"context"
	"errors"
	"fmt"
	"io"
	"sync/atomic"
	"time"

	"github.com/inconshreveable/log15"

	"github.com/dimfeld/brokerage_server/types"
	"github.com/dimfeld/ib"
)

type replyBehavior int

type activeReply struct {
	dataChan chan ib.Reply
	ctx      context.Context
}

var (
	// This should never happen since we don't burst messages.
	ErrWayTooFast = errors.New("Could not send message at this rate")
)

const (
	REPLY_CONTINUE replyBehavior = iota
	REPLY_DONE
)

type callbackFunc func(r ib.Reply) (replyBehavior, error)

func (p *IB) handleMatchedReply(r ib.MatchedReply) {
	id := r.ID()

	p.activeMutex.Lock()
	reply, ok := p.active[id]
	p.activeMutex.Unlock()

	if !ok {
		// Got an unexpected reply
		// This will actually be a fairly normal occurrence so don't warn on it.
		prettyPrint := func() string {
			return fmt.Sprintf("%T:%+v", r, r)
		}
		p.LogDebugVerbose("Unexpected reply", "msg", log15.Lazy{prettyPrint})
		return
	}

	select {
	case reply.dataChan <- r:
	case <-reply.ctx.Done():
	}
}

func (p *IB) handleReply(rep ib.Reply) {
	prettyPrint := func() string {
		return fmt.Sprintf("%T:%+v", rep, rep)
	}
	p.LogDebugTrace("received", "msg", log15.Lazy{prettyPrint})

	switch r := rep.(type) {
	case *ib.ManagedAccounts:
		// TODO Save list of accounts

	case *ib.NextValidID:
		p.LogDebugTrace("NextValidID", "id", r.OrderID)
		atomic.StoreInt64(&p.nextOrderIdValue, r.OrderID)
		p.open = true
		if p.connectChan != nil {
			close(p.connectChan)
			p.connectChan = nil
		}

	case *ib.ErrorMessage:
		id := r.ID()

		// TODO Some errors are not actually replies to the
		// request, and should be handled here instead of passed
		// through.

		if id == -1 {
			if r.SeverityWarning() {
				p.Logger.Warn("info", "err", r)
			} else {
				p.Logger.Error("received error", "err", r)
			}
		} else {
			p.handleMatchedReply(r)
			// Make sure the request gets closed, since nothing else is coming in.
			p.closeMatchedRequest(id)
		}

	case ib.MatchedReply:
		p.handleMatchedReply(r)
	}

	// TODO Handle unmatched replies.
}

func (p *IB) nextOrderID() int64 {
	return atomic.AddInt64(&p.nextOrderIdValue, 1)
}

func (p *IB) send(r ib.Request) error {
	res := p.rateLimiter.Reserve()
	if !res.OK() {
		// This should never happen since we don't burst messages.
		return ErrWayTooFast
	}

	if waitTime := res.Delay(); waitTime > 0 {
		time.Sleep(waitTime)
	}

	prettyPrint := func() string {
		return fmt.Sprintf("%T:%+v", r, r)
	}
	p.LogDebugTrace("Sending", "msg", log15.Lazy{prettyPrint})
	err := p.engine.Send(r)
	if err == io.EOF {
		err = types.ErrDisconnected
	}
	return err
}

func (p *IB) sendMatchedRequest(ctx context.Context, r ib.MatchedRequest) (nextId int64, dataChan chan ib.Reply, err error) {
	nextId = r.ID()
	if nextId == 0 {
		nextId = p.nextOrderID()
		r.SetID(nextId)
	}

	p.activeMutex.Lock()
	rep, ok := p.active[nextId]
	if !ok {
		dataChan = make(chan ib.Reply, 1)
		p.active[nextId] = activeReply{
			dataChan: dataChan,
			ctx:      ctx,
		}
	} else {
		dataChan = rep.dataChan
	}
	p.activeMutex.Unlock()

	err = p.send(r)
	return
}

func (p *IB) closeMatchedRequest(id int64) {
	p.activeMutex.Lock()
	rep, ok := p.active[id]
	if ok {
	outer:
		for {
			// Drain the channel, if it needs it.
			select {
			case <-rep.dataChan:
			default:
				break outer
			}
		}

		delete(p.active, id)
	}
	p.activeMutex.Unlock()
}

func (p *IB) startStreamingRequest(ctx context.Context, r ib.MatchedRequest) (reqId int64, repChan chan ib.Reply, err error) {
	if err = p.send(r); err != nil {
		return
	}

	var dataChan chan ib.Reply
	reqId, dataChan, err = p.sendMatchedRequest(ctx, r)
	if err != nil {
		return
	}

	repChan = make(chan ib.Reply, 20)

	go func() {
		for {
			select {
			case data := <-dataChan:
				repChan <- data
			case <-ctx.Done():
				p.closeMatchedRequest(reqId)
				close(repChan)
				return

			}
		}
	}()

	return
}

func (p *IB) sendUnmatchedRequest(r ib.Request) error {
	return p.send(r)
}

func (p *IB) syncMatchedRequest(ctx context.Context, r ib.MatchedRequest, cb callbackFunc) error {
	ctx, cancelFunc := context.WithCancel(ctx)
	reqId, dataChan, err := p.sendMatchedRequest(ctx, r)

	defer func() {
		cancelFunc()
		p.closeMatchedRequest(reqId)
	}()

	if err != nil {
		return err
	}

	for {
		select {
		case data := <-dataChan:
			if data == nil {
				// TODO Better error handling
				return errors.New("error occurred")
			}

			behavior, err := cb(data)
			if err != nil || behavior == REPLY_DONE {
				return err
			}
		case <-ctx.Done():
			return ctx.Err()
		}
	}
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/json"
	"github.com/1lann/beacon/handler"
	"gopkg.in/fsnotify.v1"
	"io/ioutil"
	"os"
	"path/filepath"
)

type ConfigServer struct {
	Name                string   `json:"name"`
	Available           bool     `json:"available"`
	Hostnames           []string `json:"hostnames"`
	MaxPlayers          int      `json:"max_players"`
	ProtocolNumber      int      `json:"protocol_number"`
	AutoShutdownMinutes int      `json:"auto_shutdown_minutes"`
	Droplet             struct {
		Memory         string `json:"memory"`
		Region         string `json:"region"`
		SSHFingerprint string `json:"ssh_fingerprint"`
	} `json:"droplet"`
	Messages struct {
		MessagePrefix    string `json:"message_prefix"`
		Owner            string `json:"owner"`
		ServerInfoPrefix string `json:"server_info_prefix"`
		BootTime         string `json:"boot_time"`
	} `json:"messages"`
	Whitelist []string `json:"start_whitelist"`
}

type Config struct {
	APIToken           string         `json:"api_token"`
	CommunicationsPort string         `json:"communications_port"`
	Servers            []ConfigServer `json:"servers"`
}

func loadConfig() Config {
	dir, err := filepath.Abs(filepath.Dir(os.Args[0]))
	if err != nil {
		Fatal("config", "Could not resolve filepath:", err)
	}

	data, err := ioutil.ReadFile(dir + "/config.json")
	if err != nil {
		Fatal("config", "Failed to read configuration:", err)
	}

	var newConfig Config
	err = json.Unmarshal(data, &newConfig)
	if err != nil {
		Fatal("config", "Failed to decode configuration:", err)
	}

	return newConfig
}

func liveLoadConfig() {
	dir, err := filepath.Abs(filepath.Dir(os.Args[0]))
	if err != nil {
		Log("config", "Could not resolve filepath:", err)
		return
	}

	data, err := ioutil.ReadFile(dir + "/config.json")
	if err != nil {
		Log("config", "Failed to read configuration:", err)
		return
	}

	var newConfig Config
	err = json.Unmarshal(data, &newConfig)
	if err != nil {
		Log("config", "Failed to decode configuration:", err)
		return
	}

	if len(newConfig.Servers) != len(allServers) {
		Log("config", "Number of servers have changed. "+
			"You must restart the reverse proxy for changes to take place.")
		return
	}

	if newConfig.APIToken != globalConfig.APIToken {
		Log("config", "The API key has changed. You must restart "+
			"the server to use the new API key.")
	}

	if newConfig.CommunicationsPort != globalConfig.CommunicationsPort {
		Log("config", "The communications port has changed. You must restart "+
			"the server to use the new communications port.")
	}

	for i, newServer := range newConfig.Servers {
		currentServer := allServers[i]

		if currentServer.Name != newServer.Name {
			Log("config", "The servers have been reordered or renamed."+
				"You must restart the reverse proxy for changes to take place.")
			continue
		}

		currentServer.Messages = newServer.Messages
		currentServer.Whitelist = newServer.Whitelist
		currentServer.Hostnames = newServer.Hostnames
		currentServer.MaxPlayers = newServer.MaxPlayers
		currentServer.ProtocolNumber = newServer.ProtocolNumber
		currentServer.Droplet = newServer.Droplet
		currentServer.AutoShutdownMinutes = newServer.AutoShutdownMinutes

		currentServer.PingStatus.MaxPlayers = currentServer.MaxPlayers
		currentServer.PingStatus.ProtocolNumber = currentServer.ProtocolNumber

		if newServer.Available {
			currentServer.Available = true
			currentServer.setStateRaw(currentServer.State)

			if currentServer.State != stateStarted &&
				currentServer.State != stateOff {
				handler.Handle(currentServer.Hostnames,
					currentServer.ResponseHandler)
			}
		} else {
			currentServer.Available = false
			currentServer.SetState(stateUnavailable)
		}
	}

	Log("config", "Reloaded configuration.")
}

func watchConfig() {
	watcher, err := fsnotify.NewWatcher()
	if err != nil {
		Fatal("config watcher", err)
	}

	go func() {
		for {
			select {
			case event := <-watcher.Events:
				if event.Op&fsnotify.Write == fsnotify.Write ||
					event.Op&fsnotify.Create == fsnotify.Create {
					liveLoadConfig()
				}
			case err := <-watcher.Errors:
				Log("config watcher", err)
			}
		}
	}()

	dir, err := filepath.Abs(filepath.Dir(os.Args[0]))
	if err != nil {
		Log("config watcher", "Could not resolve filepath:", err)
		return
	}

	err = watcher.Add(dir + "/config.json")
	if err != nil {
		Fatal("config watcher", err)
	}
}
<commit_msg>Add wait before config reload<commit_after>package main

import (
	"encoding/json"
	"github.com/1lann/beacon/handler"
	"gopkg.in/fsnotify.v1"
	"io/ioutil"
	"os"
	"path/filepath"
	"time"
)

type ConfigServer struct {
	Name                string   `json:"name"`
	Available           bool     `json:"available"`
	Hostnames           []string `json:"hostnames"`
	MaxPlayers          int      `json:"max_players"`
	ProtocolNumber      int      `json:"protocol_number"`
	AutoShutdownMinutes int      `json:"auto_shutdown_minutes"`
	Droplet             struct {
		Memory         string `json:"memory"`
		Region         string `json:"region"`
		SSHFingerprint string `json:"ssh_fingerprint"`
	} `json:"droplet"`
	Messages struct {
		MessagePrefix    string `json:"message_prefix"`
		Owner            string `json:"owner"`
		ServerInfoPrefix string `json:"server_info_prefix"`
		BootTime         string `json:"boot_time"`
	} `json:"messages"`
	Whitelist []string `json:"start_whitelist"`
}

type Config struct {
	APIToken           string         `json:"api_token"`
	CommunicationsPort string         `json:"communications_port"`
	Servers            []ConfigServer `json:"servers"`
}

func loadConfig() Config {
	dir, err := filepath.Abs(filepath.Dir(os.Args[0]))
	if err != nil {
		Fatal("config", "Could not resolve filepath:", err)
	}

	data, err := ioutil.ReadFile(dir + "/config.json")
	if err != nil {
		Fatal("config", "Failed to read configuration:", err)
	}

	var newConfig Config
	err = json.Unmarshal(data, &newConfig)
	if err != nil {
		Fatal("config", "Failed to decode configuration:", err)
	}

	return newConfig
}

func liveLoadConfig() {
	time.Sleep(time.Second * 3)
	dir, err := filepath.Abs(filepath.Dir(os.Args[0]))
	if err != nil {
		Log("config", "Could not resolve filepath:", err)
		return
	}

	data, err := ioutil.ReadFile(dir + "/config.json")
	if err != nil {
		Log("config", "Failed to read configuration:", err)
		return
	}

	var newConfig Config
	err = json.Unmarshal(data, &newConfig)
	if err != nil {
		Log("config", "Failed to decode configuration:", err)
		return
	}

	if len(newConfig.Servers) != len(allServers) {
		Log("config", "Number of servers have changed. "+
			"You must restart the reverse proxy for changes to take place.")
		return
	}

	if newConfig.APIToken != globalConfig.APIToken {
		Log("config", "The API key has changed. You must restart "+
			"the server to use the new API key.")
	}

	if newConfig.CommunicationsPort != globalConfig.CommunicationsPort {
		Log("config", "The communications port has changed. You must restart "+
			"the server to use the new communications port.")
	}

	for i, newServer := range newConfig.Servers {
		currentServer := allServers[i]

		if currentServer.Name != newServer.Name {
			Log("config", "The servers have been reordered or renamed."+
				"You must restart the reverse proxy for changes to take place.")
			continue
		}

		currentServer.Messages = newServer.Messages
		currentServer.Whitelist = newServer.Whitelist
		currentServer.Hostnames = newServer.Hostnames
		currentServer.MaxPlayers = newServer.MaxPlayers
		currentServer.ProtocolNumber = newServer.ProtocolNumber
		currentServer.Droplet = newServer.Droplet
		currentServer.AutoShutdownMinutes = newServer.AutoShutdownMinutes

		currentServer.PingStatus.MaxPlayers = currentServer.MaxPlayers
		currentServer.PingStatus.ProtocolNumber = currentServer.ProtocolNumber

		if newServer.Available {
			currentServer.Available = true
			currentServer.setStateRaw(currentServer.State)

			if currentServer.State != stateStarted &&
				currentServer.State != stateOff {
				handler.Handle(currentServer.Hostnames,
					currentServer.ResponseHandler)
			}
		} else {
			currentServer.Available = false
			currentServer.SetState(stateUnavailable)
		}
	}

	Log("config", "Reloaded configuration.")
}

func watchConfig() {
	watcher, err := fsnotify.NewWatcher()
	if err != nil {
		Fatal("config watcher", err)
	}

	go func() {
		for {
			select {
			case event := <-watcher.Events:
				if event.Op&fsnotify.Write == fsnotify.Write ||
					event.Op&fsnotify.Create == fsnotify.Create {
					liveLoadConfig()
				}
			case err := <-watcher.Errors:
				Log("config watcher", err)
			}
		}
	}()

	dir, err := filepath.Abs(filepath.Dir(os.Args[0]))
	if err != nil {
		Log("config watcher", "Could not resolve filepath:", err)
		return
	}

	err = watcher.Add(dir + "/config.json")
	if err != nil {
		Fatal("config watcher", err)
	}
}
<|endoftext|>


<commit_before>package node

import (
	"bytes"
	"fmt"
	"sync"
	"time"

	"github.com/jsimonetti/go-artnet/packet"
	"github.com/jsimonetti/go-artnet/packet/code"
)

// controlNode hols a node configuration
type controlNode struct {
	lastSeen time.Time
	node     Config
}

// Controller holds the information for a controller
type Controller struct {
	// Node is the controller itself
	Node

	// Nodes is a slice of nodes that are seen by this controller
	Nodes    []controlNode
	nodeLock sync.Mutex

	shutdownCh chan struct{}
}

// Start will start this controller
func (c *Controller) Start() error {
	go c.pollLoop()
	return c.Node.Start()
}

// Stop will stop this controller
func (c *Controller) Stop() {
	c.Node.Stop()
	close(c.shutdownCh)
}

// pollLoop will routinely poll for new nodes
func (c *Controller) pollLoop() {
	// we poll for new nodes every 3 seconds
	timer := time.NewTicker(3 * time.Second)
	artPoll := &packet.ArtPollPacket{
		TalkToMe: new(code.TalkToMe).WithReplyOnChange(true),
		Priority: code.DpAll,
	}

	// create an ArtPoll packet to send out periodically
	b, err := artPoll.MarshalBinary()
	if err != nil {
		fmt.Printf("error creating ArtPoll packet: %s\n", err)
		return
	}

	// create an ArtPollReply packet to send out with the ArtPoll packet
	me, err := new(packet.ArtPollReplyPacket).MarshalBinary()
	if err != nil {
		fmt.Printf("error creating ArtPollReply packet for self: %s\n", err)
		return
	}

	// loop untill shutdown
	for {
		select {
		case <-timer.C:
			// send ArtPollPacket
			c.Node.sendCh <- &netPayload{data: b}

			// we should always reply to our own polls to let other controllers know we are here
			c.Node.sendCh <- &netPayload{data: me}

			// clean up old nodes
			go c.gcNode()

		case p := <-c.Node.pollReplyCh:
			cfg := ConfigFromArtPollReply(p)
			c.updateNode(cfg)

		case <-c.shutdownCh:
			return
		}
	}
}

// updateNode will add a Node to the list of known nodes
func (c *Controller) updateNode(cfg Config) error {
	c.nodeLock.Lock()
	defer c.nodeLock.Unlock()

	for i, n := range c.Nodes {
		if bytes.Equal(cfg.IP, n.node.IP) {
			fmt.Printf("updated node: %s, %s\n", cfg.Name, cfg.IP.String())
			c.Nodes[i].node = cfg
			c.Nodes[i].lastSeen = time.Now()
			return nil
		}
	}
	fmt.Printf("added node: %s, %s\n", cfg.Name, cfg.IP.String())
	c.Nodes = append(c.Nodes, controlNode{node: cfg, lastSeen: time.Now()})

	return nil
}

// deleteNode will delete a Node from the list of known nodes
func (c *Controller) deleteNode(node Config) error {
	c.nodeLock.Lock()
	defer c.nodeLock.Unlock()

	for i, n := range c.Nodes {
		if bytes.Equal(node.IP, n.node.IP) {
			c.Nodes = append(c.Nodes[:i], c.Nodes[i+1:]...)
		}
	}

	return fmt.Errorf("no known node with this ip known, ip: %s", node.IP)
}

// gcNode will remove stale Nodes from the list of known nodes
func (c *Controller) gcNode() {
	c.nodeLock.Lock()
	defer c.nodeLock.Unlock()

start:
	for i := range c.Nodes {
		if c.Nodes[i].lastSeen.Add(10 * time.Second).Before(time.Now()) {
			// it has been more then 10 seconds since we saw this node. remove it now.
			fmt.Printf("remove stale node: %s, %s\n", c.Nodes[i].node.Name, c.Nodes[i].node.IP.String())
			c.Nodes = append(c.Nodes[:i], c.Nodes[i+1:]...)
			goto start
		}
	}
}
<commit_msg>Move garbage collector to own timer Also add more comments<commit_after>package node

import (
	"bytes"
	"fmt"
	"sync"
	"time"

	"github.com/jsimonetti/go-artnet/packet"
	"github.com/jsimonetti/go-artnet/packet/code"
)

// controlNode hols the configuration of a node we control
type controlNode struct {
	lastSeen time.Time
	node     Config
}

// Controller holds the information for a controller
type Controller struct {
	// Node is the controller itself
	Node

	// Nodes is a slice of nodes that are seen by this controller
	Nodes    []controlNode
	nodeLock sync.Mutex

	shutdownCh chan struct{}
}

// Start will start this controller
func (c *Controller) Start() error {
	go c.pollLoop()
	return c.Node.Start()
}

// Stop will stop this controller
func (c *Controller) Stop() {
	c.Node.Stop()
	close(c.shutdownCh)
}

// pollLoop will routinely poll for new nodes
func (c *Controller) pollLoop() {
	// we poll for new nodes every 3 seconds
	pollTicker := time.NewTicker(3 * time.Second)

	// we garbagecollect every 30 seconds
	gcTicker := time.NewTicker(30 * time.Second)

	artPoll := &packet.ArtPollPacket{
		TalkToMe: new(code.TalkToMe).WithReplyOnChange(true),
		Priority: code.DpAll,
	}

	// create an ArtPoll packet to send out periodically
	b, err := artPoll.MarshalBinary()
	if err != nil {
		fmt.Printf("error creating ArtPoll packet: %s\n", err)
		return
	}

	// create an ArtPollReply packet to send out with the ArtPoll packet
	me, err := new(packet.ArtPollReplyPacket).MarshalBinary()
	if err != nil {
		fmt.Printf("error creating ArtPollReply packet for self: %s\n", err)
		return
	}

	// loop untill shutdown
	for {
		select {
		case <-pollTicker.C:
			// send ArtPollPacket
			c.Node.sendCh <- &netPayload{data: b}

			// we should always reply to our own polls to let other controllers know we are here
			c.Node.sendCh <- &netPayload{data: me}

		case <-gcTicker.C:
			// clean up old nodes
			c.gcNode()

		case p := <-c.Node.pollReplyCh:
			cfg := ConfigFromArtPollReply(p)
			c.updateNode(cfg)

		case <-c.shutdownCh:
			return
		}
	}
}

// updateNode will add a Node to the list of known nodes
// this assumes that there are no universe address collisions
// in the future we should probably be prepared to handle that too
func (c *Controller) updateNode(cfg Config) error {
	c.nodeLock.Lock()
	defer c.nodeLock.Unlock()

	for i, n := range c.Nodes {
		if bytes.Equal(cfg.IP, n.node.IP) {
			// update this node, since we allready know about it
			fmt.Printf("updated node: %s, %s\n", cfg.Name, cfg.IP.String())
			c.Nodes[i].node = cfg
			c.Nodes[i].lastSeen = time.Now()
			return nil
		}
	}
	// new node, add it to our known nodes
	fmt.Printf("added node: %s, %s\n", cfg.Name, cfg.IP.String())
	c.Nodes = append(c.Nodes, controlNode{node: cfg, lastSeen: time.Now()})

	return nil
}

// deleteNode will delete a Node from the list of known nodes
func (c *Controller) deleteNode(node Config) error {
	c.nodeLock.Lock()
	defer c.nodeLock.Unlock()

	for i, n := range c.Nodes {
		if bytes.Equal(node.IP, n.node.IP) {
			// node found, remove it from the list
			c.Nodes = append(c.Nodes[:i], c.Nodes[i+1:]...)
		}
	}

	return fmt.Errorf("no known node with this ip known, ip: %s", node.IP)
}

// gcNode will remove stale Nodes from the list of known nodes
// it will loop through the list of nodes and remove nodes older then X seconds
func (c *Controller) gcNode() {
	c.nodeLock.Lock()
	defer c.nodeLock.Unlock()

	// we use X = 10 here, configurable in the future
	staleAfter := 10 * time.Second

start:
	for i := range c.Nodes {
		if c.Nodes[i].lastSeen.Add(staleAfter).Before(time.Now()) {
			// it has been more then X seconds since we saw this node. remove it now.
			fmt.Printf("remove stale node: %s, %s\n", c.Nodes[i].node.Name, c.Nodes[i].node.IP.String())
			c.Nodes = append(c.Nodes[:i], c.Nodes[i+1:]...)
			goto start
		}
	}
}
<|endoftext|>


<commit_before>package robots

import (
	"errors"
	"fmt"
	"net/url"
	"os"
	"strings"

	"github.com/gistia/slackbot/db"
	"github.com/gistia/slackbot/mavenlink"
	"github.com/gistia/slackbot/robots"
	"github.com/gistia/slackbot/utils"
)

type bot struct {
	handler utils.SlackHandler
}

func init() {
	handler := utils.NewSlackHandler("Mavenlink", ":chart_with_upwards_trend:")
	s := &bot{handler: handler}
	robots.RegisterRobot("mvn", s)
}

func (r bot) Run(p *robots.Payload) (slashCommandImmediateReturn string) {
	go r.DeferredAction(p)
	return ""
}

func (r bot) DeferredAction(p *robots.Payload) {
	cmd := utils.NewCommand(p.Text)

	if cmd.Is("auth", "authorize", "connect") {
		r.sendAuth(p)
		return
	}

	if cmd.Command == "projects" {
		r.sendProjects(p, cmd.Arg(0))
		return
	}

	if cmd.Command == "stories" {
		r.sendStories(p, cmd.Arg(0), cmd.Param("parent"))
	}
}

func conn(user string) (*mavenlink.Mavenlink, error) {
	token, err := db.GetSetting(user, "MAVENLINK_TOKEN")
	if err != nil {
		return nil, err
	}
	if token == nil {
		return nil, errors.New("No MAVENLINK_TOKEN set for @" + user)
	}
	con := mavenlink.NewMavenlink(token.Value, false)
	return con, nil
}

func (r bot) sendProjects(payload *robots.Payload, term string) {
	var ps []mavenlink.Project
	var err error

	mvn, err := conn(payload.UserName)
	if err != nil {
		r.handler.SendError(payload, err)
		return
	}
	s := "Projects"

	if len(term) > 0 {
		fmt.Printf("Retrieving projects with term \"%s\"...\n\n", term)
		s += fmt.Sprintf(" matching '%s':\n", term)
		ps, err = mvn.SearchProject(term)
	} else {
		s += ":\n"
		fmt.Println("Retrieving projects...\n")
		ps, err = mvn.Projects()
	}

	if err != nil {
		fmt.Println(err.Error())
		return
	}

	r.handler.Send(payload, s+projectTable(ps))
}

func (r bot) sendStories(payload *robots.Payload, term string, parent string) {
	mvn, err := conn(payload.UserName)
	if err != nil {
		r.handler.SendError(payload, err)
		return
	}

	var p mavenlink.Project
	var stories []mavenlink.Story

	if term != "" {
		ps, err := r.getProject(payload, term)

		if err != nil {
			msg := fmt.Sprintf("Error retrieving project for \"%s\": %s\n", term, err.Error())
			r.handler.Send(payload, msg)
			return
		}

		if len(ps) < 1 {
			msg := fmt.Sprintf("No projects matched \"%s\"\n", term)
			r.handler.Send(payload, msg)
			return
		} else if len(ps) > 1 {
			s := fmt.Sprintf("More than one project matched \"%s\":\n\n", term)
			r.handler.Send(payload, s+projectTable(ps))
			return
		} else {
			p = ps[0]
		}
	}

	if parent == "" {
		stories, err = mvn.Stories(p.Id)
		if err != nil {
			msg := fmt.Sprintf("Error retrieving stories for project \"%s - %s\": %s\n",
				p.Id, p.Title, err.Error())
			r.handler.Send(payload, msg)
			return
		}
		r.storyTable(payload, stories)
		return
	}

	if utils.IsNumber(parent) {
		stories, err = mvn.ChildStories(parent)
		if err != nil {
			fmt.Printf("Error child stories for \"%s\": %s\n", parent, err.Error())
			return
		}
		r.storyTable(payload, stories)
		return
	}

	r.handler.Send(payload, "Not implemented")
}

func (r bot) getProject(payload *robots.Payload, term string) ([]mavenlink.Project, error) {
	mvn, err := conn(payload.UserName)
	if err != nil {
		return nil, err
	}

	if utils.IsNumber(term) {
		p, err := mvn.GetProject(term)
		if err != nil {
			return nil, err
		}

		return []mavenlink.Project{*p}, nil
	}

	ps, err := mvn.SearchProject(term)
	if err != nil {
		return nil, err
	}

	return ps, nil
}

func (r bot) Description() (description string) {
	return "Mavenlink bot\n\tUsage: ! mvn <command>\n"
}

func projectTable(ps []mavenlink.Project) string {
	s := ""

	for _, p := range ps {
		s += fmt.Sprintf("%s - %s\n", p.Id, p.Title)
	}

	return s
}

func formatHour(h int64) string {
	if h == 0 {
		return ""
	}

	v := float64(h) / 60
	return fmt.Sprintf("%.2f", v)
}

func (r bot) sendAuth(p *robots.Payload) {
	appId := os.Getenv("MAVENLINK_APP_ID")
	callback := os.Getenv("MAVENLINK_CALLBACK")

	link, err := url.Parse("https://app.mavenlink.com/oauth/authorize")
	if err != nil {
		r.handler.SendError(p, err)
	}

	params := url.Values{}
	params.Add("response_type", "code")
	params.Add("client_id", appId)
	params.Add("redirect_uri",
		fmt.Sprintf("%s?domain=%s&user=%s&channel=%s",
			callback, p.TeamDomain, p.UserName, p.ChannelID))

	link.RawQuery = params.Encode()

	fmt.Println("url", link.String())

	a := robots.Attachment{
		Color:     "#7CD197",
		Title:     "Authorize with Mavenlink",
		TitleLink: link.String(),
		Text:      "Authorize your mavenlink user",
	}

	r.handler.SendWithAttachments(p, "", []robots.Attachment{a})
}

func (r bot) storyTable(payload *robots.Payload, stories []mavenlink.Story) {
	for _, s := range stories {
		atts := []robots.Attachment{}
		a := robots.Attachment{}
		a.Color = "#7CD197"
		a.Fallback = fmt.Sprintf("%s - *%s* %s (%s)\n",
			strings.Title(s.StoryType), s.Id, s.Title, s.State)
		a.Title = fmt.Sprintf("Task #%s - %s\n", s.Id, s.Title)
		a.TitleLink = fmt.Sprintf(
			"https://app.mavenlink.com/workspaces/%s/#tracker/%s",
			s.WorkspaceId, s.Id)
		a.Text = strings.Title(s.State)

		if s.TimeEstimateInMinutes > 0 {
			a.Text += fmt.Sprintf(" - Estimated hours: %s",
				formatHour(s.TimeEstimateInMinutes))
		}

		if s.LoggedBillableTimeInMinutes > 0 {
			a.Text += fmt.Sprintf(" - Logged hours: %s",
				formatHour(s.LoggedBillableTimeInMinutes))
		}

		atts = append(atts, a)

		r.handler.SendWithAttachments(payload, "", atts)
	}
}
<commit_msg>Improved command and error detection for mvn command<commit_after>package robots

import (
	"errors"
	"fmt"
	"net/url"
	"os"
	"strings"

	"github.com/gistia/slackbot/db"
	"github.com/gistia/slackbot/mavenlink"
	"github.com/gistia/slackbot/robots"
	"github.com/gistia/slackbot/utils"
)

type bot struct {
	handler utils.SlackHandler
}

func init() {
	handler := utils.NewSlackHandler("Mavenlink", ":chart_with_upwards_trend:")
	s := &bot{handler: handler}
	robots.RegisterRobot("mvn", s)
}

func (r bot) Run(p *robots.Payload) (slashCommandImmediateReturn string) {
	go r.DeferredAction(p)
	return ""
}

func (r bot) DeferredAction(p *robots.Payload) {
	cmd := utils.NewCommand(p.Text)

	if cmd.Is("auth", "authorize", "connect") {
		r.sendAuth(p)
		return
	}

	if cmd.Is("projects") {
		r.sendProjects(p, cmd.Arg(0))
		return
	}

	if cmd.Is("stories") {
		r.sendStories(p, cmd.Arg(0), cmd.Param("parent"))
		return
	}

	r.handler.Send(p, "Invalid command *"+cmd.Command+"*")
}

func conn(user string) (*mavenlink.Mavenlink, error) {
	token, err := db.GetSetting(user, "MAVENLINK_TOKEN")
	if err != nil {
		return nil, err
	}
	if token == nil {
		return nil, errors.New("No MAVENLINK_TOKEN set for @" + user)
	}
	con := mavenlink.NewMavenlink(token.Value, false)
	return con, nil
}

func (r bot) sendProjects(payload *robots.Payload, term string) {
	var ps []mavenlink.Project
	var err error

	mvn, err := conn(payload.UserName)
	if err != nil {
		r.handler.SendError(payload, err)
		return
	}
	s := "Projects"

	if len(term) > 0 {
		fmt.Printf("Retrieving projects with term \"%s\"...\n\n", term)
		s += fmt.Sprintf(" matching '%s':\n", term)
		ps, err = mvn.SearchProject(term)
	} else {
		s += ":\n"
		fmt.Println("Retrieving projects...\n")
		ps, err = mvn.Projects()
	}

	if err != nil {
		fmt.Println(err.Error())
		return
	}

	r.handler.Send(payload, s+projectTable(ps))
}

func (r bot) sendStories(payload *robots.Payload, term string, parent string) {
	mvn, err := conn(payload.UserName)
	if err != nil {
		r.handler.SendError(payload, err)
		return
	}

	var p mavenlink.Project
	var stories []mavenlink.Story

	if term != "" {
		ps, err := r.getProject(payload, term)

		if err != nil {
			msg := fmt.Sprintf("Error retrieving project for \"%s\": %s\n", term, err.Error())
			r.handler.Send(payload, msg)
			return
		}

		if len(ps) < 1 {
			msg := fmt.Sprintf("No projects matched \"%s\"\n", term)
			r.handler.Send(payload, msg)
			return
		} else if len(ps) > 1 {
			s := fmt.Sprintf("More than one project matched \"%s\":\n\n", term)
			r.handler.Send(payload, s+projectTable(ps))
			return
		} else {
			p = ps[0]
		}
	}

	if parent == "" {
		stories, err = mvn.Stories(p.Id)
		if err != nil {
			msg := fmt.Sprintf("Error retrieving stories for project \"%s - %s\": %s\n",
				p.Id, p.Title, err.Error())
			r.handler.Send(payload, msg)
			return
		}
		r.storyTable(payload, stories)
		return
	}

	if utils.IsNumber(parent) {
		stories, err = mvn.ChildStories(parent)
		if err != nil {
			fmt.Printf("Error child stories for \"%s\": %s\n", parent, err.Error())
			return
		}
		r.storyTable(payload, stories)
		return
	}

	r.handler.Send(payload, "Not implemented")
}

func (r bot) getProject(payload *robots.Payload, term string) ([]mavenlink.Project, error) {
	mvn, err := conn(payload.UserName)
	if err != nil {
		return nil, err
	}

	if utils.IsNumber(term) {
		p, err := mvn.GetProject(term)
		if err != nil {
			return nil, err
		}

		return []mavenlink.Project{*p}, nil
	}

	ps, err := mvn.SearchProject(term)
	if err != nil {
		return nil, err
	}

	return ps, nil
}

func (r bot) Description() (description string) {
	return "Mavenlink bot\n\tUsage: ! mvn <command>\n"
}

func projectTable(ps []mavenlink.Project) string {
	s := ""

	for _, p := range ps {
		s += fmt.Sprintf("%s - %s\n", p.Id, p.Title)
	}

	return s
}

func formatHour(h int64) string {
	if h == 0 {
		return ""
	}

	v := float64(h) / 60
	return fmt.Sprintf("%.2f", v)
}

func (r bot) sendAuth(p *robots.Payload) {
	appId := os.Getenv("MAVENLINK_APP_ID")
	callback := os.Getenv("MAVENLINK_CALLBACK")

	link, err := url.Parse("https://app.mavenlink.com/oauth/authorize")
	if err != nil {
		r.handler.SendError(p, err)
	}

	params := url.Values{}
	params.Add("response_type", "code")
	params.Add("client_id", appId)
	params.Add("redirect_uri",
		fmt.Sprintf("%s?domain=%s&user=%s&channel=%s",
			callback, p.TeamDomain, p.UserName, p.ChannelID))

	link.RawQuery = params.Encode()

	fmt.Println("url", link.String())

	a := robots.Attachment{
		Color:     "#7CD197",
		Title:     "Authorize with Mavenlink",
		TitleLink: link.String(),
		Text:      "Authorize your mavenlink user",
	}

	r.handler.SendWithAttachments(p, "", []robots.Attachment{a})
}

func (r bot) storyTable(payload *robots.Payload, stories []mavenlink.Story) {
	for _, s := range stories {
		atts := []robots.Attachment{}
		a := robots.Attachment{}
		a.Color = "#7CD197"
		a.Fallback = fmt.Sprintf("%s - *%s* %s (%s)\n",
			strings.Title(s.StoryType), s.Id, s.Title, s.State)
		a.Title = fmt.Sprintf("Task #%s - %s\n", s.Id, s.Title)
		a.TitleLink = fmt.Sprintf(
			"https://app.mavenlink.com/workspaces/%s/#tracker/%s",
			s.WorkspaceId, s.Id)
		a.Text = strings.Title(s.State)

		if s.TimeEstimateInMinutes > 0 {
			a.Text += fmt.Sprintf(" - Estimated hours: %s",
				formatHour(s.TimeEstimateInMinutes))
		}

		if s.LoggedBillableTimeInMinutes > 0 {
			a.Text += fmt.Sprintf(" - Logged hours: %s",
				formatHour(s.LoggedBillableTimeInMinutes))
		}

		atts = append(atts, a)

		r.handler.SendWithAttachments(payload, "", atts)
	}
}
<|endoftext|>


<commit_before>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

package output

// Firewall : Mapping for a firewall component
type Firewall struct {
	ProviderType       string `json:"_type"`
	SecurityGroupAWSID string `json:"security_group_aws_id"`
	Name               string `json:"name"`
	Rules              struct {
		Ingress []FirewallRule `json:"ingress"`
		Egress  []FirewallRule `json:"egress"`
	} `json:"rules"`
	Tags             map[string]string `json:"tags"`
	DatacenterType   string            `json:"datacenter_type,omitempty"`
	DatacenterName   string            `json:"datacenter_name,omitempty"`
	DatacenterRegion string            `json:"datacenter_region"`
	AccessKeyID      string            `json:"aws_access_key_id"`
	SecretAccessKey  string            `json:"aws_secret_access_key"`
	VpcID            string            `json:"vpc_id"`
	Service          string            `json:"service"`
	Status           string            `json:"status"`
	Exists           bool
}

// HasChanged diff's the two items and returns true if there have been any changes
func (f *Firewall) HasChanged(of *Firewall) bool {
	if len(f.Rules.Ingress) != len(of.Rules.Ingress) ||
		len(f.Rules.Egress) != len(of.Rules.Egress) {
		return true
	}

	/*

		for i := 0; i < len(f.Rules.Ingress); i++ {
			if ruleChanged(f.Rules.Ingress[i].To, of.Rules.Ingress[i].To) ||
				f.Rules.Ingress[i].Protocol != of.Rules.Ingress[i].Protocol ||
				f.Rules.Ingress[i].IP != of.Rules.Ingress[i].IP ||
				ruleChanged(f.Rules.Ingress[i].From, of.Rules.Ingress[i].From) {
				return true
			}
		}

		for i := 0; i < len(f.Rules.Egress); i++ {
			if ruleChanged(f.Rules.Egress[i].To, of.Rules.Egress[i].To) ||
				f.Rules.Egress[i].Protocol != of.Rules.Egress[i].Protocol ||
				f.Rules.Egress[i].IP != of.Rules.Egress[i].IP ||
				ruleChanged(f.Rules.Egress[i].From, of.Rules.Egress[i].From) {
				return true
			}
		}

	*/

	for _, rule := range f.Rules.Ingress {
		if hasRule(of.Rules.Ingress, rule) != true {
			return true
		}
	}

	for _, rule := range f.Rules.Egress {
		if hasRule(of.Rules.Egress, rule) != true {
			return true
		}
	}

	return false
}

func hasRule(rules []FirewallRule, rule FirewallRule) bool {
	for _, r := range rules {
		if ruleMatches(r.To, rule.To) &&
			r.Protocol == rule.Protocol &&
			r.IP == rule.IP &&
			ruleMatches(r.From, rule.From) {
			return true
		}
	}

	return false
}

func ruleMatches(nv, ov int) bool {
	if nv == 65535 {
		nv = 0
	}
	if ov == 65535 {
		ov = 0
	}

	return nv == ov
}

// GetTags returns a components tags
func (f Firewall) GetTags() map[string]string {
	return f.Tags
}

// ProviderID returns a components provider id
func (f Firewall) ProviderID() string {
	return f.SecurityGroupAWSID
}

// ComponentName returns a components name
func (f Firewall) ComponentName() string {
	return f.Name
}
<commit_msg>matching values if both protocols are any<commit_after>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

package output

// Firewall : Mapping for a firewall component
type Firewall struct {
	ProviderType       string `json:"_type"`
	SecurityGroupAWSID string `json:"security_group_aws_id"`
	Name               string `json:"name"`
	Rules              struct {
		Ingress []FirewallRule `json:"ingress"`
		Egress  []FirewallRule `json:"egress"`
	} `json:"rules"`
	Tags             map[string]string `json:"tags"`
	DatacenterType   string            `json:"datacenter_type,omitempty"`
	DatacenterName   string            `json:"datacenter_name,omitempty"`
	DatacenterRegion string            `json:"datacenter_region"`
	AccessKeyID      string            `json:"aws_access_key_id"`
	SecretAccessKey  string            `json:"aws_secret_access_key"`
	VpcID            string            `json:"vpc_id"`
	Service          string            `json:"service"`
	Status           string            `json:"status"`
	Exists           bool
}

// HasChanged diff's the two items and returns true if there have been any changes
func (f *Firewall) HasChanged(of *Firewall) bool {
	if len(f.Rules.Ingress) != len(of.Rules.Ingress) ||
		len(f.Rules.Egress) != len(of.Rules.Egress) {
		return true
	}

	for _, rule := range f.Rules.Ingress {
		if hasRule(of.Rules.Ingress, rule) != true {
			return true
		}
	}

	for _, rule := range f.Rules.Egress {
		if hasRule(of.Rules.Egress, rule) != true {
			return true
		}
	}

	return false
}

func hasRule(rules []FirewallRule, rule FirewallRule) bool {
	for _, r := range rules {
		if ruleMatches(r.To, rule.To, r.Protocol, rule.Protocol) &&
			r.Protocol == rule.Protocol &&
			r.IP == rule.IP &&
			ruleMatches(r.From, rule.From, r.Protocol, rule.Protocol) {
			return true
		}
	}

	return false
}

func ruleMatches(nv, ov int, np, op string) bool {
	if np == "-1" && op == "-1" {
		return true
	}

	return nv == ov
}

// GetTags returns a components tags
func (f Firewall) GetTags() map[string]string {
	return f.Tags
}

// ProviderID returns a components provider id
func (f Firewall) ProviderID() string {
	return f.SecurityGroupAWSID
}

// ComponentName returns a components name
func (f Firewall) ComponentName() string {
	return f.Name
}
<|endoftext|>


<commit_before>package manifest_test

import (
	"io/ioutil"
	"os"
	"strings"

	. "github.com/cloudfoundry/cli/cf/manifest"
	. "github.com/cloudfoundry/cli/testhelpers/matchers"
	"github.com/nu7hatch/gouuid"
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
)

type outputs struct {
	contents []string
	cursor   int
}

var _ = Describe("generate_manifest", func() {
	var (
		m              AppManifest
		err            error
		uniqueFilename string
	)

	BeforeEach(func() {
		guid, err := uuid.NewV4()
		Expect(err).NotTo(HaveOccurred())

		uniqueFilename = guid.String()

		m = NewGenerator()
		m.FileSavePath(uniqueFilename)
	})

	AfterEach(func() {
		err = os.Remove(uniqueFilename)
		Ω(err).ToNot(HaveOccurred())
	})

	It("creates a new file at a given path", func() {
		m.Save()

		_, err = os.Stat(uniqueFilename)
		Ω(err).ToNot(HaveOccurred())
	})

	It("starts the manifest with 3 dashes (---), followed by 'applications'", func() {
		m.Save()

		contents := getYamlContent(uniqueFilename)

		Ω(contents[0]).To(Equal("---"))
		Ω(contents[1]).To(Equal("applications:"))
	})

	It("creates entry under the given app name", func() {
		m.Memory("app1", 128)
		m.Memory("app2", 64)
		m.Save()

		//outputs.ContainSubstring assert orders
		cmdOutput := &outputs{
			contents: getYamlContent(uniqueFilename),
			cursor:   0,
		}

		Ω(cmdOutput.ContainsSubstring("- name: app1")).To(BeTrue())
		Ω(cmdOutput.ContainsSubstring("  memory: 128M")).To(BeTrue())

		Ω(cmdOutput.ContainsSubstring("- name: app2")).To(BeTrue())
		Ω(cmdOutput.ContainsSubstring("  memory: 64M")).To(BeTrue())
	})

	It("prefixes each service with '-'", func() {
		m.Service("app1", "service1")
		m.Service("app1", "service2")
		m.Service("app1", "service3")
		m.Save()

		contents := getYamlContent(uniqueFilename)

		Ω(contents).To(ContainSubstrings(
			[]string{"  services:"},
			[]string{"- service1"},
			[]string{"- service2"},
			[]string{"- service3"},
		))
	})

	It("generates a manifest containing all the attributes", func() {
		m.Memory("app1", 128)
		m.StartCommand("app1", "run main.go")
		m.Service("app1", "service1")
		m.EnvironmentVars("app1", "foo", "boo")
		m.HealthCheckTimeout("app1", 100)
		m.Instances("app1", 3)
		m.Domain("app1", "foo", "blahblahblah.com")
		m.BuildpackUrl("app1", "ruby-buildpack")
		err := m.Save()
		Ω(err).NotTo(HaveOccurred())

		Ω(getYamlContent(uniqueFilename)).To(ContainSubstrings(
			[]string{"- name: app1"},
			[]string{"  memory: 128M"},
			[]string{"  command: run main.go"},
			[]string{"  services:"},
			[]string{"  - service1"},
			[]string{"  env:"},
			[]string{"    foo: boo"},
			[]string{"  timeout: 100"},
			[]string{"  instances: 3"},
			[]string{"  host: foo"},
			[]string{"  domain: blahblahblah.com"},
			[]string{"  buildpack: ruby-buildpack"},
		))
	})
	Context("When there are multiple hosts and domains", func() {

		It("generates a manifest containing two hosts two domains", func() {
			m.Memory("app1", 128)
			m.StartCommand("app1", "run main.go")
			m.Service("app1", "service1")
			m.EnvironmentVars("app1", "foo", "boo")
			m.HealthCheckTimeout("app1", 100)
			m.Instances("app1", 3)
			m.Domain("app1", "foo1", "test1.com")
			m.Domain("app1", "foo1", "test2.com")
			m.Domain("app1", "foo2", "test1.com")
			m.Domain("app1", "foo2", "test2.com")
			m.BuildpackUrl("app1", "ruby-buildpack")
			err := m.Save()
			Ω(err).NotTo(HaveOccurred())

			Ω(getYamlContent(uniqueFilename)).To(ContainSubstrings(
				[]string{"- name: app1"},
				[]string{"  memory: 128M"},
				[]string{"  command: run main.go"},
				[]string{"  services:"},
				[]string{"  - service1"},
				[]string{"  env:"},
				[]string{"    foo: boo"},
				[]string{"  timeout: 100"},
				[]string{"  instances: 3"},
				[]string{"  hosts:"},
				[]string{"  - foo1"},
				[]string{"  - foo2"},
				[]string{"  domains:"},
				[]string{"  - test1.com"},
				[]string{"  - test2.com"},
				[]string{"  buildpack: ruby-buildpack"},
			))
		})
	})

	Context("When there are multiple hosts and single domain", func() {

		It("generates a manifest containing two hosts one domain", func() {
			m.Memory("app1", 128)
			m.StartCommand("app1", "run main.go")
			m.Service("app1", "service1")
			m.EnvironmentVars("app1", "foo", "boo")
			m.HealthCheckTimeout("app1", 100)
			m.Instances("app1", 3)
			m.Domain("app1", "foo1", "test.com")
			m.Domain("app1", "foo2", "test.com")
			m.BuildpackUrl("app1", "ruby-buildpack")
			err := m.Save()
			Ω(err).NotTo(HaveOccurred())

			Ω(getYamlContent(uniqueFilename)).To(ContainSubstrings(
				[]string{"- name: app1"},
				[]string{"  memory: 128M"},
				[]string{"  command: run main.go"},
				[]string{"  services:"},
				[]string{"  - service1"},
				[]string{"  env:"},
				[]string{"    foo: boo"},
				[]string{"  timeout: 100"},
				[]string{"  instances: 3"},
				[]string{"  hosts:"},
				[]string{"  - foo1"},
				[]string{"  - foo2"},
				[]string{"  domain: test.com"},
				[]string{"  buildpack: ruby-buildpack"},
			))
		})
	})

	Context("When there is single host and multiple domains", func() {

		It("generates a manifest containing one host two domains", func() {
			m.Memory("app1", 128)
			m.StartCommand("app1", "run main.go")
			m.Service("app1", "service1")
			m.EnvironmentVars("app1", "foo", "boo")
			m.HealthCheckTimeout("app1", 100)
			m.Instances("app1", 3)
			m.Domain("app1", "foo", "test1.com")
			m.Domain("app1", "foo", "test2.com")
			m.BuildpackUrl("app1", "ruby-buildpack")
			err := m.Save()
			Ω(err).NotTo(HaveOccurred())

			Ω(getYamlContent(uniqueFilename)).To(ContainSubstrings(
				[]string{"- name: app1"},
				[]string{"  memory: 128M"},
				[]string{"  command: run main.go"},
				[]string{"  services:"},
				[]string{"  - service1"},
				[]string{"  env:"},
				[]string{"    foo: boo"},
				[]string{"  timeout: 100"},
				[]string{"  instances: 3"},
				[]string{"  host: foo"},
				[]string{"  domains:"},
				[]string{"  - test1.com"},
				[]string{"  - test2.com"},
				[]string{"  buildpack: ruby-buildpack"},
			))
		})
	})

})

func getYamlContent(path string) []string {
	b, err := ioutil.ReadFile(path)
	Ω(err).ToNot(HaveOccurred())

	return strings.Split(string(b), "\n")
}

func (o *outputs) ContainsSubstring(str string) bool {
	for i := o.cursor; i < len(o.contents)-1; i++ {
		if strings.Contains(o.contents[i], str) {
			o.cursor = i
			return true
		}
	}
	return false
}
<commit_msg>Replace Ω with Expect, ToNot with NotTo<commit_after>package manifest_test

import (
	"io/ioutil"
	"os"
	"strings"

	. "github.com/cloudfoundry/cli/cf/manifest"
	. "github.com/cloudfoundry/cli/testhelpers/matchers"
	"github.com/nu7hatch/gouuid"
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
)

type outputs struct {
	contents []string
	cursor   int
}

var _ = Describe("generate_manifest", func() {
	var (
		m              AppManifest
		err            error
		uniqueFilename string
	)

	BeforeEach(func() {
		guid, err := uuid.NewV4()
		Expect(err).NotTo(HaveOccurred())

		uniqueFilename = guid.String()

		m = NewGenerator()
		m.FileSavePath(uniqueFilename)
	})

	AfterEach(func() {
		err = os.Remove(uniqueFilename)
		Expect(err).NotTo(HaveOccurred())
	})

	It("creates a new file at a given path", func() {
		m.Save()

		_, err = os.Stat(uniqueFilename)
		Expect(err).NotTo(HaveOccurred())
	})

	It("starts the manifest with 3 dashes (---), followed by 'applications'", func() {
		m.Save()

		contents := getYamlContent(uniqueFilename)

		Expect(contents[0]).To(Equal("---"))
		Expect(contents[1]).To(Equal("applications:"))
	})

	It("creates entry under the given app name", func() {
		m.Memory("app1", 128)
		m.Memory("app2", 64)
		m.Save()

		//outputs.ContainSubstring assert orders
		cmdOutput := &outputs{
			contents: getYamlContent(uniqueFilename),
			cursor:   0,
		}

		Expect(cmdOutput.ContainsSubstring("- name: app1")).To(BeTrue())
		Expect(cmdOutput.ContainsSubstring("  memory: 128M")).To(BeTrue())

		Expect(cmdOutput.ContainsSubstring("- name: app2")).To(BeTrue())
		Expect(cmdOutput.ContainsSubstring("  memory: 64M")).To(BeTrue())
	})

	It("prefixes each service with '-'", func() {
		m.Service("app1", "service1")
		m.Service("app1", "service2")
		m.Service("app1", "service3")
		m.Save()

		contents := getYamlContent(uniqueFilename)

		Expect(contents).To(ContainSubstrings(
			[]string{"  services:"},
			[]string{"- service1"},
			[]string{"- service2"},
			[]string{"- service3"},
		))
	})

	It("generates a manifest containing all the attributes", func() {
		m.Memory("app1", 128)
		m.StartCommand("app1", "run main.go")
		m.Service("app1", "service1")
		m.EnvironmentVars("app1", "foo", "boo")
		m.HealthCheckTimeout("app1", 100)
		m.Instances("app1", 3)
		m.Domain("app1", "foo", "blahblahblah.com")
		m.BuildpackUrl("app1", "ruby-buildpack")
		err := m.Save()
		Expect(err).NotTo(HaveOccurred())

		Expect(getYamlContent(uniqueFilename)).To(ContainSubstrings(
			[]string{"- name: app1"},
			[]string{"  memory: 128M"},
			[]string{"  command: run main.go"},
			[]string{"  services:"},
			[]string{"  - service1"},
			[]string{"  env:"},
			[]string{"    foo: boo"},
			[]string{"  timeout: 100"},
			[]string{"  instances: 3"},
			[]string{"  host: foo"},
			[]string{"  domain: blahblahblah.com"},
			[]string{"  buildpack: ruby-buildpack"},
		))
	})
	Context("When there are multiple hosts and domains", func() {

		It("generates a manifest containing two hosts two domains", func() {
			m.Memory("app1", 128)
			m.StartCommand("app1", "run main.go")
			m.Service("app1", "service1")
			m.EnvironmentVars("app1", "foo", "boo")
			m.HealthCheckTimeout("app1", 100)
			m.Instances("app1", 3)
			m.Domain("app1", "foo1", "test1.com")
			m.Domain("app1", "foo1", "test2.com")
			m.Domain("app1", "foo2", "test1.com")
			m.Domain("app1", "foo2", "test2.com")
			m.BuildpackUrl("app1", "ruby-buildpack")
			err := m.Save()
			Expect(err).NotTo(HaveOccurred())

			Expect(getYamlContent(uniqueFilename)).To(ContainSubstrings(
				[]string{"- name: app1"},
				[]string{"  memory: 128M"},
				[]string{"  command: run main.go"},
				[]string{"  services:"},
				[]string{"  - service1"},
				[]string{"  env:"},
				[]string{"    foo: boo"},
				[]string{"  timeout: 100"},
				[]string{"  instances: 3"},
				[]string{"  hosts:"},
				[]string{"  - foo1"},
				[]string{"  - foo2"},
				[]string{"  domains:"},
				[]string{"  - test1.com"},
				[]string{"  - test2.com"},
				[]string{"  buildpack: ruby-buildpack"},
			))
		})
	})

	Context("When there are multiple hosts and single domain", func() {

		It("generates a manifest containing two hosts one domain", func() {
			m.Memory("app1", 128)
			m.StartCommand("app1", "run main.go")
			m.Service("app1", "service1")
			m.EnvironmentVars("app1", "foo", "boo")
			m.HealthCheckTimeout("app1", 100)
			m.Instances("app1", 3)
			m.Domain("app1", "foo1", "test.com")
			m.Domain("app1", "foo2", "test.com")
			m.BuildpackUrl("app1", "ruby-buildpack")
			err := m.Save()
			Expect(err).NotTo(HaveOccurred())

			Expect(getYamlContent(uniqueFilename)).To(ContainSubstrings(
				[]string{"- name: app1"},
				[]string{"  memory: 128M"},
				[]string{"  command: run main.go"},
				[]string{"  services:"},
				[]string{"  - service1"},
				[]string{"  env:"},
				[]string{"    foo: boo"},
				[]string{"  timeout: 100"},
				[]string{"  instances: 3"},
				[]string{"  hosts:"},
				[]string{"  - foo1"},
				[]string{"  - foo2"},
				[]string{"  domain: test.com"},
				[]string{"  buildpack: ruby-buildpack"},
			))
		})
	})

	Context("When there is single host and multiple domains", func() {

		It("generates a manifest containing one host two domains", func() {
			m.Memory("app1", 128)
			m.StartCommand("app1", "run main.go")
			m.Service("app1", "service1")
			m.EnvironmentVars("app1", "foo", "boo")
			m.HealthCheckTimeout("app1", 100)
			m.Instances("app1", 3)
			m.Domain("app1", "foo", "test1.com")
			m.Domain("app1", "foo", "test2.com")
			m.BuildpackUrl("app1", "ruby-buildpack")
			err := m.Save()
			Expect(err).NotTo(HaveOccurred())

			Expect(getYamlContent(uniqueFilename)).To(ContainSubstrings(
				[]string{"- name: app1"},
				[]string{"  memory: 128M"},
				[]string{"  command: run main.go"},
				[]string{"  services:"},
				[]string{"  - service1"},
				[]string{"  env:"},
				[]string{"    foo: boo"},
				[]string{"  timeout: 100"},
				[]string{"  instances: 3"},
				[]string{"  host: foo"},
				[]string{"  domains:"},
				[]string{"  - test1.com"},
				[]string{"  - test2.com"},
				[]string{"  buildpack: ruby-buildpack"},
			))
		})
	})

})

func getYamlContent(path string) []string {
	b, err := ioutil.ReadFile(path)
	Expect(err).NotTo(HaveOccurred())

	return strings.Split(string(b), "\n")
}

func (o *outputs) ContainsSubstring(str string) bool {
	for i := o.cursor; i < len(o.contents)-1; i++ {
		if strings.Contains(o.contents[i], str) {
			o.cursor = i
			return true
		}
	}
	return false
}
<|endoftext|>


<commit_before>package event_convert

import (
	"reflect"
	"testing"

	"github.com/karimra/gnmic/formatters"
)

type item struct {
	input  *formatters.EventMsg
	output *formatters.EventMsg
}

var testset = map[string]struct {
	processorType string
	processor     map[string]interface{}
	tests         []item
}{
	"int_convert": {
		processorType: processorType,
		processor: map[string]interface{}{
			"value-names": []string{"^number*"},
			"type":        "int",
		},
		tests: []item{
			{
				input:  nil,
				output: nil,
			},
			{
				input: &formatters.EventMsg{
					Values: map[string]interface{}{}},
				output: &formatters.EventMsg{
					Values: map[string]interface{}{}},
			},
			{
				input: &formatters.EventMsg{
					Values: map[string]interface{}{"name": 1}},
				output: &formatters.EventMsg{
					Values: map[string]interface{}{"name": 1}},
			},
			{
				input: &formatters.EventMsg{
					Values: map[string]interface{}{"number": "100"},
					Tags:   map[string]string{"number": "name_tag"},
				},
				output: &formatters.EventMsg{
					Values: map[string]interface{}{"number": int(100)},
					Tags:   map[string]string{"number": "name_tag"},
				},
			},
			{
				input: &formatters.EventMsg{
					Values: map[string]interface{}{"number": int(100)},
					Tags:   map[string]string{"number": "name_tag"},
				},
				output: &formatters.EventMsg{
					Values: map[string]interface{}{"number": int(100)},
					Tags:   map[string]string{"number": "name_tag"},
				},
			},
			{
				input: &formatters.EventMsg{
					Values: map[string]interface{}{"number": uint(100)},
					Tags:   map[string]string{"number": "name_tag"},
				},
				output: &formatters.EventMsg{
					Values: map[string]interface{}{"number": int(100)},
					Tags:   map[string]string{"number": "name_tag"},
				},
			},
			{
				input: &formatters.EventMsg{
					Values: map[string]interface{}{"number": float64(100)},
					Tags:   map[string]string{"number": "name_tag"},
				},
				output: &formatters.EventMsg{
					Values: map[string]interface{}{"number": int(100)},
					Tags:   map[string]string{"number": "name_tag"},
				},
			},
			{
				input: &formatters.EventMsg{
					Values: map[string]interface{}{"number": true},
					Tags:   map[string]string{"number": "name_tag"},
				},
				output: &formatters.EventMsg{
					Values: map[string]interface{}{"number": true},
					Tags:   map[string]string{"number": "name_tag"},
				},
			},
		},
	},
	"uint_convert": {
		processorType: processorType,
		processor: map[string]interface{}{
			"value-names": []string{"^name.*"},
			"type":        "uint",
		},
		tests: []item{
			{
				input:  nil,
				output: nil,
			},
			{
				input: &formatters.EventMsg{
					Values: map[string]interface{}{}},
				output: &formatters.EventMsg{
					Values: map[string]interface{}{}},
			},
			{
				input: &formatters.EventMsg{
					Values: map[string]interface{}{"name_value_bytes": "42"}},
				output: &formatters.EventMsg{
					Values: map[string]interface{}{"name_value_bytes": uint(42)}},
			},
			{
				input: &formatters.EventMsg{
					Values: map[string]interface{}{"name_value_bytes": uint(42)}},
				output: &formatters.EventMsg{
					Values: map[string]interface{}{"name_value_bytes": uint(42)}},
			},
			{
				input: &formatters.EventMsg{
					Values: map[string]interface{}{"name_value_bytes": -42}},
				output: &formatters.EventMsg{
					Values: map[string]interface{}{"name_value_bytes": uint(0)}},
			},
			{
				input: &formatters.EventMsg{
					Values: map[string]interface{}{"name_value_bytes": true}},
				output: &formatters.EventMsg{
					Values: map[string]interface{}{"name_value_bytes": true}},
			},
		},
	},
	"float_convert": {
		processorType: processorType,
		processor: map[string]interface{}{
			"value-names": []string{"^number*"},
			"type":        "float",
		},
		tests: []item{
			{
				input:  nil,
				output: nil,
			},
			{
				input: &formatters.EventMsg{
					Values: map[string]interface{}{}},
				output: &formatters.EventMsg{
					Values: map[string]interface{}{}},
			},
			{
				input: &formatters.EventMsg{
					Values: map[string]interface{}{"number": "1.1"}},
				output: &formatters.EventMsg{
					Values: map[string]interface{}{"number": float64(1.1)}},
			},
			{
				input: &formatters.EventMsg{
					Values: map[string]interface{}{"number": uint(42)}},
				output: &formatters.EventMsg{
					Values: map[string]interface{}{"number": float64(42)}},
			},
			{
				input: &formatters.EventMsg{
					Values: map[string]interface{}{"number": int(42)}},
				output: &formatters.EventMsg{
					Values: map[string]interface{}{"number": float64(42)}},
			},
			{
				input: &formatters.EventMsg{
					Values: map[string]interface{}{"number": true}},
				output: &formatters.EventMsg{
					Values: map[string]interface{}{"number": true}},
			},
		},
	},
	"string_convert": {
		processorType: processorType,
		processor: map[string]interface{}{
			"value-names": []string{"id"},
			"type":        "string",
		},
		tests: []item{
			{
				input:  nil,
				output: nil,
			},
			{
				input: &formatters.EventMsg{
					Values: map[string]interface{}{}},
				output: &formatters.EventMsg{
					Values: map[string]interface{}{}},
			},
			{
				input: &formatters.EventMsg{
					Values: map[string]interface{}{"id": 1}},
				output: &formatters.EventMsg{
					Values: map[string]interface{}{"id": string("1")}},
			},
			{
				input: &formatters.EventMsg{
					Values: map[string]interface{}{"id": -1}},
				output: &formatters.EventMsg{
					Values: map[string]interface{}{"id": string("-1")}},
			},
		},
	},
}

func TestEventConvertToUint(t *testing.T) {
	ts := testset["uint_convert"]
	if pi, ok := formatters.EventProcessors[ts.processorType]; ok {
		t.Log("found processor")
		p := pi()
		err := p.Init(ts.processor, nil)
		if err != nil {
			t.Errorf("failed to initialize processors: %v", err)
			return
		}
		t.Logf("processor: %+v", p)
		for i, item := range ts.tests {
			t.Run("uint_convert", func(t *testing.T) {
				t.Logf("running test item %d", i)
				var inputMsg *formatters.EventMsg
				if item.input != nil {
					inputMsg = &formatters.EventMsg{
						Name:      item.input.Name,
						Timestamp: item.input.Timestamp,
						Tags:      make(map[string]string),
						Values:    make(map[string]interface{}),
						Deletes:   item.input.Deletes,
					}
					for k, v := range item.input.Tags {
						inputMsg.Tags[k] = v
					}
					for k, v := range item.input.Values {
						inputMsg.Values[k] = v
					}
				}
				p.Apply(item.input)
				t.Logf("input: %+v, changed: %+v", inputMsg, item.input)
				if !reflect.DeepEqual(item.input, item.output) {
					t.Logf("failed at uint_convert item %d", i)
					t.Logf("expected: %#v", item.output)
					t.Logf("     got: %#v", item.input)
					t.Fail()
				}
			})
		}
	}
}

func TestEventConvertToInt(t *testing.T) {
	ts := testset["int_convert"]
	if pi, ok := formatters.EventProcessors[ts.processorType]; ok {
		t.Log("found processor")
		p := pi()
		err := p.Init(ts.processor, nil)
		if err != nil {
			t.Errorf("failed to initialize processors: %v", err)
			return
		}
		for i, item := range ts.tests {
			t.Run("int_convert", func(t *testing.T) {
				t.Logf("running test item %d", i)
				var inputMsg *formatters.EventMsg
				if item.input != nil {
					inputMsg = &formatters.EventMsg{
						Name:      item.input.Name,
						Timestamp: item.input.Timestamp,
						Tags:      make(map[string]string),
						Values:    make(map[string]interface{}),
						Deletes:   item.input.Deletes,
					}
					for k, v := range item.input.Tags {
						inputMsg.Tags[k] = v
					}
					for k, v := range item.input.Values {
						inputMsg.Values[k] = v
					}
				}
				p.Apply(item.input)
				t.Logf("input: %+v, changed: %+v", inputMsg, item.input)
				if !reflect.DeepEqual(item.input, item.output) {
					t.Logf("failed at int_convert item %d", i)
					t.Logf("expected: %#v", item.output)
					t.Logf("     got: %#v", item.input)
					t.Fail()
				}
			})
		}
	}
}

func TestEventConvertToString(t *testing.T) {
	ts := testset["string_convert"]
	if pi, ok := formatters.EventProcessors[ts.processorType]; ok {
		t.Log("found processor")
		p := pi()
		err := p.Init(ts.processor, nil)
		if err != nil {
			t.Errorf("failed to initialize processors: %v", err)
			return
		}
		for i, item := range ts.tests {
			t.Run("string_convert", func(t *testing.T) {
				t.Logf("running test item %d", i)
				var inputMsg *formatters.EventMsg
				if item.input != nil {
					inputMsg = &formatters.EventMsg{
						Name:      item.input.Name,
						Timestamp: item.input.Timestamp,
						Tags:      make(map[string]string),
						Values:    make(map[string]interface{}),
						Deletes:   item.input.Deletes,
					}
					for k, v := range item.input.Tags {
						inputMsg.Tags[k] = v
					}
					for k, v := range item.input.Values {
						inputMsg.Values[k] = v
					}
				}
				p.Apply(item.input)
				t.Logf("input: %+v, changed: %+v", inputMsg, item.input)
				if !reflect.DeepEqual(item.input, item.output) {
					t.Logf("failed at string_convert item %d", i)
					t.Logf("expected: %#v", item.output)
					t.Logf("     got: %#v", item.input)
					t.Fail()
				}
			})
		}
	}
}

func TestEventConvertToFloat(t *testing.T) {
	ts := testset["float_convert"]
	if pi, ok := formatters.EventProcessors[ts.processorType]; ok {
		t.Log("found processor")
		p := pi()
		err := p.Init(ts.processor, nil)
		if err != nil {
			t.Errorf("failed to initialize processors: %v", err)
			return
		}
		for i, item := range ts.tests {
			t.Run("float_convert", func(t *testing.T) {
				t.Logf("running test item %d", i)
				var inputMsg *formatters.EventMsg
				if item.input != nil {
					inputMsg = &formatters.EventMsg{
						Name:      item.input.Name,
						Timestamp: item.input.Timestamp,
						Tags:      make(map[string]string),
						Values:    make(map[string]interface{}),
						Deletes:   item.input.Deletes,
					}
					for k, v := range item.input.Tags {
						inputMsg.Tags[k] = v
					}
					for k, v := range item.input.Values {
						inputMsg.Values[k] = v
					}
				}
				p.Apply(item.input)
				t.Logf("input: %+v, changed: %+v", inputMsg, item.input)
				if !reflect.DeepEqual(item.input, item.output) {
					t.Logf("failed at float_convert item %d", i)
					t.Logf("expected: %#v", item.output)
					t.Logf("     got: %#v", item.input)
					t.Fail()
				}
			})
		}
	}
}
<commit_msg>update convert tests<commit_after>package event_convert

import (
	"reflect"
	"testing"

	"github.com/karimra/gnmic/formatters"
)

type item struct {
	input  []*formatters.EventMsg
	output []*formatters.EventMsg
}

var testset = map[string]struct {
	processorType string
	processor     map[string]interface{}
	tests         []item
}{
	"int_convert": {
		processorType: processorType,
		processor: map[string]interface{}{
			"value-names": []string{"^number*"},
			"type":        "int",
		},
		tests: []item{
			{
				input:  nil,
				output: nil,
			},
			{
				input:  make([]*formatters.EventMsg, 0),
				output: make([]*formatters.EventMsg, 0),
			},
			{
				input: []*formatters.EventMsg{
					{
						Values: map[string]interface{}{"name": 1},
					},
				},
				output: []*formatters.EventMsg{
					{
						Values: map[string]interface{}{"name": 1},
					},
				},
			},
			{
				input: []*formatters.EventMsg{
					{
						Values: map[string]interface{}{"number": "100"},
						Tags:   map[string]string{"number": "name_tag"},
					},
				},
				output: []*formatters.EventMsg{
					{
						Values: map[string]interface{}{"number": int(100)},
						Tags:   map[string]string{"number": "name_tag"},
					},
				},
			},
			{
				input: []*formatters.EventMsg{
					{
						Values: map[string]interface{}{"number": "100"},
						Tags:   map[string]string{"number": "name_tag"},
					},
					{
						Values: map[string]interface{}{"number": "200"},
						Tags:   map[string]string{"number": "name_tag"},
					},
				},
				output: []*formatters.EventMsg{
					{
						Values: map[string]interface{}{"number": int(100)},
						Tags:   map[string]string{"number": "name_tag"},
					},
					{
						Values: map[string]interface{}{"number": int(200)},
						Tags:   map[string]string{"number": "name_tag"},
					},
				},
			},
			{
				input: []*formatters.EventMsg{
					{
						Tags:   map[string]string{"number": "name_tag"},
					},
					{
						Values: map[string]interface{}{"number": "200"},
						Tags:   map[string]string{"number": "name_tag"},
					},
				},
				output: []*formatters.EventMsg{
					{
						Tags:   map[string]string{"number": "name_tag"},
					},
					{
						Values: map[string]interface{}{"number": int(200)},
						Tags:   map[string]string{"number": "name_tag"},
					},
				},
			},
			{
				input: []*formatters.EventMsg{
					{
						Values: map[string]interface{}{"number": int(100)},
						Tags:   map[string]string{"number": "name_tag"},
					},
				},
				output: []*formatters.EventMsg{
					{
						Values: map[string]interface{}{"number": int(100)},
						Tags:   map[string]string{"number": "name_tag"},
					},
				},
			},
			{
				input: []*formatters.EventMsg{
					{
						Values: map[string]interface{}{"number": uint(100)},
						Tags:   map[string]string{"number": "name_tag"},
					},
				},
				output: []*formatters.EventMsg{
					{
						Values: map[string]interface{}{"number": int(100)},
						Tags:   map[string]string{"number": "name_tag"},
					},
				},
			},
			{
				input: []*formatters.EventMsg{
					{
						Values: map[string]interface{}{"number": float64(100)},
						Tags:   map[string]string{"number": "name_tag"},
					},
				},
				output: []*formatters.EventMsg{
					{
						Values: map[string]interface{}{"number": int(100)},
						Tags:   map[string]string{"number": "name_tag"},
					},
				},
			},
			{
				input: []*formatters.EventMsg{
					{
						Values: map[string]interface{}{"number": true},
						Tags:   map[string]string{"number": "name_tag"},
					},
				},
				output: []*formatters.EventMsg{
					{
						Values: map[string]interface{}{"number": true},
						Tags:   map[string]string{"number": "name_tag"},
					},
				},
			},
		},
	},
	"uint_convert": {
		processorType: processorType,
		processor: map[string]interface{}{
			"value-names": []string{"^name.*"},
			"type":        "uint",
		},
		tests: []item{
			{
				input:  nil,
				output: nil,
			},
			{
				input:  []*formatters.EventMsg{{Values: map[string]interface{}{}}},
				output: []*formatters.EventMsg{{Values: map[string]interface{}{}}},
			},
			{
				input: []*formatters.EventMsg{{
					Values: map[string]interface{}{"name_value_bytes": "42"}}},
				output: []*formatters.EventMsg{{
					Values: map[string]interface{}{"name_value_bytes": uint(42)}}},
			},
			{
				input: []*formatters.EventMsg{{
					Values: map[string]interface{}{"name_value_bytes": uint(42)}}},
				output: []*formatters.EventMsg{{
					Values: map[string]interface{}{"name_value_bytes": uint(42)}}},
			},
			{
				input: []*formatters.EventMsg{{
					Values: map[string]interface{}{"name_value_bytes": -42}}},
				output: []*formatters.EventMsg{{
					Values: map[string]interface{}{"name_value_bytes": uint(0)}}},
			},
			{
				input: []*formatters.EventMsg{{
					Values: map[string]interface{}{"name_value_bytes": true}}},
				output: []*formatters.EventMsg{{
					Values: map[string]interface{}{"name_value_bytes": true}}},
			},
		},
	},
	"float_convert": {
		processorType: processorType,
		processor:     map[string]interface{}{"value-names": []string{"^number*"}, "type": "float"},
		tests: []item{
			{
				input:  nil,
				output: nil,
			},
			{
				input: []*formatters.EventMsg{
					{
						Values: map[string]interface{}{},
					},
				},
				output: []*formatters.EventMsg{
					{
						Values: map[string]interface{}{},
					},
				},
			},
			{
				input: []*formatters.EventMsg{
					{
						Values: map[string]interface{}{"number": "1.1"},
					},
				},
				output: []*formatters.EventMsg{
					{
						Values: map[string]interface{}{"number": float64(1.1)},
					},
				},
			},
			{
				input: []*formatters.EventMsg{
					{
						Values: map[string]interface{}{"number": uint(42)},
					},
				},
				output: []*formatters.EventMsg{
					{
						Values: map[string]interface{}{"number": float64(42)},
					},
				},
			},
			{
				input: []*formatters.EventMsg{
					{
						Values: map[string]interface{}{"number": int(42)},
					},
				},
				output: []*formatters.EventMsg{
					{
						Values: map[string]interface{}{"number": float64(42)},
					},
				},
			},
			{
				input: []*formatters.EventMsg{
					{
						Values: map[string]interface{}{"number": true},
					},
				},
				output: []*formatters.EventMsg{
					{
						Values: map[string]interface{}{"number": true},
					},
				},
			},
		},
	},
}

func TestEventConvertToUint(t *testing.T) {
	ts := testset["uint_convert"]
	if pi, ok := formatters.EventProcessors[ts.processorType]; ok {
		t.Log("found processor")
		p := pi()
		err := p.Init(ts.processor, nil)
		if err != nil {
			t.Errorf("failed to initialize processors: %v", err)
			return
		}
		t.Logf("processor: %+v", p)
		for i, item := range ts.tests {
			t.Run("uint_convert", func(t *testing.T) {
				t.Logf("running test item %d", i)
				outs := p.Apply(item.input...)
				for j := range outs {
					if !reflect.DeepEqual(outs[j], item.output[j]) {
						t.Logf("failed at uint_convert item %d, index %d", i, j)
						t.Logf("expected: %#v", item.output[j])
						t.Logf("     got: %#v", outs[j])
						t.Fail()
					}
				}
			})
		}
	}
}

func TestEventConvertToInt(t *testing.T) {
	ts := testset["int_convert"]
	if pi, ok := formatters.EventProcessors[ts.processorType]; ok {
		t.Log("found processor")
		p := pi()
		err := p.Init(ts.processor, nil)
		if err != nil {
			t.Errorf("failed to initialize processors: %v", err)
			return
		}
		for i, item := range ts.tests {
			t.Run("int_convert", func(t *testing.T) {
				t.Logf("running test item %d", i)
				outs := p.Apply(item.input...)
				for j := range outs {
					if !reflect.DeepEqual(outs[j], item.output[j]) {
						t.Logf("failed at int_convert item %d, index %d", i, j)
						t.Logf("expected: %#v", item.output[j])
						t.Logf("     got: %#v", outs[j])
						t.Fail()
					}
				}
			})
		}
	}
}

func TestEventConvertToString(t *testing.T) {
	ts := testset["string_convert"]
	if pi, ok := formatters.EventProcessors[ts.processorType]; ok {
		t.Log("found processor")
		p := pi()
		err := p.Init(ts.processor, nil)
		if err != nil {
			t.Errorf("failed to initialize processors: %v", err)
			return
		}
		for i, item := range ts.tests {
			t.Run("string_convert", func(t *testing.T) {
				t.Logf("running test item %d", i)
				outs := p.Apply(item.input...)
				for j := range outs {
					if !reflect.DeepEqual(outs[j], item.output[j]) {
						t.Logf("failed at string_convert item %d, index %d", i, j)
						t.Logf("expected: %#v", item.output[j])
						t.Logf("     got: %#v", outs[j])
						t.Fail()
					}
				}
			})
		}
	}
}

func TestEventConvertToFloat(t *testing.T) {
	ts := testset["float_convert"]
	if pi, ok := formatters.EventProcessors[ts.processorType]; ok {
		t.Log("found processor")
		p := pi()
		err := p.Init(ts.processor, nil)
		if err != nil {
			t.Errorf("failed to initialize processors: %v", err)
			return
		}
		for i, item := range ts.tests {
			t.Run("float_convert", func(t *testing.T) {
				t.Logf("running test item %d", i)
				outs := p.Apply(item.input...)
				for j := range outs {
					if !reflect.DeepEqual(outs[j], item.output[j]) {
						t.Logf("failed at float_convert item %d, index %d", i, j)
						t.Logf("expected: %#v", item.output[j])
						t.Logf("     got: %#v", outs[j])
						t.Fail()
					}
				}
			})
		}
	}
}
<|endoftext|>


<commit_before>package main

import (
	"bytes"
	"crypto/rand"
	"fmt"
	"image"
	"image/gif"
	"io"
	"log"
	"net/http"
	"strconv"
	"time"

	"github.com/avct/uasurfer"

	"./database"

	"encoding/hex"

	influx "github.com/influxdata/influxdb/client/v2"
)

// VersionString converts a uasurfer.Version into a string
func VersionString(v uasurfer.Version) string {
	if v.Major == 0 {
		return ""
	}

	return strconv.Itoa(v.Major) + "." + strconv.Itoa(v.Minor) + "." + strconv.Itoa(v.Patch)
}

var b bytes.Buffer
var err = gif.Encode(&b, image.NewAlpha(image.Rect(0, 0, 1, 1)), nil)

// OnePixelGIF - The data for a one pixel transparent GIF
var OnePixelGIF = b.Bytes()

func handler(w http.ResponseWriter, r *http.Request) {

	tags := make(map[string]string)

	// TODO: set a limit on arguments
	for key, vals := range r.URL.Query() {
		log.Printf("%s: %s\n", key, vals[0])
		tags[key] = vals[0]
	}

	ua := r.Header.Get("User-Agent")

	if ua != "" {
		parsedUa := uasurfer.Parse(ua)

		tags["browser"] = parsedUa.Browser.Name.String()
		tags["browser_ver"] = VersionString(parsedUa.Browser.Version)

		if parsedUa.Browser.Version.Major != 0 {
			tags["browser_major"] = strconv.Itoa(parsedUa.Browser.Version.Major)
		}

		tags["os"] = parsedUa.OS.Name.String()

		tags["os_ver"] = VersionString(parsedUa.OS.Version)

		if parsedUa.OS.Version.Major != 0 {
			// OS X versions are weird
			if parsedUa.OS.Name == uasurfer.OSMacOSX {
				tags["os_major"] = strconv.Itoa(parsedUa.OS.Version.Minor)
			} else {
				tags["os_major"] = strconv.Itoa(parsedUa.OS.Version.Major)
			}
		}

		tags["device_type"] = parsedUa.DeviceType.String()

		log.Printf("%v\n", parsedUa.Browser.Name)
	}

	connection := database.Connect("test", "test", "http://xenial.dev:8086")
	points, err := influx.NewBatchPoints(influx.BatchPointsConfig{
		Database: "test",
	})

	if err != nil {
		log.Fatal(err)
	}

	pt, _ := influx.NewPoint("hello", tags, map[string]interface{}{
		"fpt": "",
	})

	points.AddPoint(pt)

	log.Println(connection.Ping(time.Second))
	err = connection.Write(points)

	if err != nil {
		log.Fatal(err)
	}

	w.Header().Add("Content-Type", "image/gif")
	io.Copy(w, bytes.NewReader(OnePixelGIF))
}

func randomFingerprint(w http.ResponseWriter, r *http.Request) {
	bytes := make([]byte, 8)

	_, err := rand.Read(bytes)

	if err != nil {
		r.Response.StatusCode = 500
	} else {
		fmt.Fprintf(w, "%s", hex.EncodeToString(bytes))
	}
}

func main() {
	http.HandleFunc("/aurora", handler)
	http.HandleFunc("/fp", randomFingerprint)
	http.ListenAndServe(":3030", nil)
}
<commit_msg>added fingerprints from aurora<commit_after>package main

import (
	"bytes"
	"crypto/rand"
	"fmt"
	"image"
	"image/gif"
	"io"
	"log"
	"net/http"
	"strconv"
	"time"

	"github.com/avct/uasurfer"

	"./database"

	"encoding/hex"

	influx "github.com/influxdata/influxdb/client/v2"
)

// VersionString converts a uasurfer.Version into a string
func VersionString(v uasurfer.Version) string {
	if v.Major == 0 {
		return ""
	}

	return strconv.Itoa(v.Major) + "." + strconv.Itoa(v.Minor) + "." + strconv.Itoa(v.Patch)
}

var b bytes.Buffer
var err = gif.Encode(&b, image.NewAlpha(image.Rect(0, 0, 1, 1)), nil)

// OnePixelGIF - The data for a one pixel transparent GIF
var OnePixelGIF = b.Bytes()

func handler(w http.ResponseWriter, r *http.Request) {

	tags := make(map[string]string)

	var fpt string

	// TODO: set a limit on arguments
	for key, vals := range r.URL.Query() {
		log.Printf("%s: %s\n", key, vals[0])

		if key != "fpt" {
			tags[key] = vals[0]
		} else {
			fpt = vals[0]
		}
	}

	ua := r.Header.Get("User-Agent")

	if ua != "" {
		parsedUa := uasurfer.Parse(ua)

		tags["browser"] = parsedUa.Browser.Name.String()
		tags["browser_ver"] = VersionString(parsedUa.Browser.Version)

		if parsedUa.Browser.Version.Major != 0 {
			tags["browser_major"] = strconv.Itoa(parsedUa.Browser.Version.Major)
		}

		tags["os"] = parsedUa.OS.Name.String()

		tags["os_ver"] = VersionString(parsedUa.OS.Version)

		if parsedUa.OS.Version.Major != 0 {
			// OS X versions are weird
			if parsedUa.OS.Name == uasurfer.OSMacOSX {
				tags["os_major"] = strconv.Itoa(parsedUa.OS.Version.Minor)
			} else {
				tags["os_major"] = strconv.Itoa(parsedUa.OS.Version.Major)
			}
		}

		tags["device_type"] = parsedUa.DeviceType.String()

		log.Printf("%v\n", parsedUa.Browser.Name)
	}

	connection := database.Connect("test", "test", "http://xenial.dev:8086")
	points, err := influx.NewBatchPoints(influx.BatchPointsConfig{
		Database: "test",
	})

	if err != nil {
		log.Fatal(err)
	}

	pt, _ := influx.NewPoint("hello", tags, map[string]interface{}{
		"fpt": fpt,
	})

	points.AddPoint(pt)

	log.Println(connection.Ping(time.Second))
	err = connection.Write(points)

	if err != nil {
		log.Fatal(err)
	}

	w.Header().Add("Content-Type", "image/gif")
	io.Copy(w, bytes.NewReader(OnePixelGIF))
}

func randomFingerprint(w http.ResponseWriter, r *http.Request) {
	bytes := make([]byte, 8)

	_, err := rand.Read(bytes)

	if err != nil {
		r.Response.StatusCode = 500
	} else {
		fmt.Fprintf(w, "%s", hex.EncodeToString(bytes))
	}
}

func main() {
	http.HandleFunc("/aurora", handler)
	http.HandleFunc("/fp", randomFingerprint)
	http.ListenAndServe(":3030", nil)
}
<|endoftext|>


<commit_before>package fetch

import (
	"os"
	"testing"

	"github.com/stretchr/testify/assert"
)

func TestEnvAdapter_CanReadFromEnv(t *testing.T) {
	// given
	os.Setenv("SVC_TEST_SVC_ONE_ADDR", "test123:8080")

	// when
	svc, _ := NewEnvAdapter().GetService("test-svc-one")

	// then
	assert.Equal(t, "test-svc-one", svc.Name)
	assert.Equal(t, []Address{Address{Host: "test123", Port: "8080"}}, svc.Addresses)

	os.Unsetenv("SVC_TEST_SVC_ONE_ADDR")
}

func TestEnvAdapter_CanReadFromEnv_MultipleAddresses(t *testing.T) {
	// given
	os.Setenv("SVC_TEST_SVC_ONE_ADDR", "test123:8080, test123:8081, test321:9090")

	// when
	svc, _ := NewEnvAdapter().GetService("test-svc-one")

	// then
	assert.Equal(t, "test-svc-one", svc.Name)
	assert.Equal(t, []Address{
		Address{Host: "test123", Port: "8080"},
		Address{Host: "test123", Port: "8081"},
		Address{Host: "test321", Port: "9090"}},
		svc.Addresses,
	)

	os.Unsetenv("SVC_TEST_SVC_ONE_ADDR")
}

func TestEnvAdapter_CanReadFromEnv_TrailingComma(t *testing.T) {
	// given
	os.Setenv("SVC_TEST_SVC_ONE_ADDR", "test123:")

	// when
	svc, _ := NewEnvAdapter().GetService("test-svc-one")

	// then
	assert.Equal(t, "test-svc-one", svc.Name)
	assert.Equal(t, []Address{Address{Host: "test123", Port: "80"}}, svc.Addresses)

	os.Unsetenv("SVC_TEST_SVC_ONE_ADDR")
}

func TestEnvAdapter_CanReadFromEnv_LeadingComma(t *testing.T) {
	// given
	os.Setenv("SVC_TEST_SVC_ONE_ADDR", ":8080")

	// when
	svc, _ := NewEnvAdapter().GetService("test-svc-one")

	// then
	assert.Equal(t, "test-svc-one", svc.Name)
	assert.Equal(t, []Address{Address{Host: "localhost", Port: "8080"}}, svc.Addresses)

	os.Unsetenv("SVC_TEST_SVC_ONE_ADDR")
}

func TestEnvAdapter_CanReadFromEnv_OnlyComma(t *testing.T) {
	// given
	os.Setenv("SVC_TEST_SVC_ONE_ADDR", ":")

	// when
	svc, _ := NewEnvAdapter().GetService("test-svc-one")

	// then
	assert.Equal(t, "test-svc-one", svc.Name)
	assert.Equal(t, []Address{Address{Host: "localhost", Port: "80"}}, svc.Addresses)

	os.Unsetenv("SVC_TEST_SVC_ONE_ADDR")
}
<commit_msg>making stuff better<commit_after>package fetch

import (
	"os"
	"testing"

	"github.com/stretchr/testify/assert"
)

func TestEnvAdapter_CanReadFromEnv(t *testing.T) {
	// given
	os.Setenv("SVC_TEST_SVC_ONE_ADDR", "test123:8080")

	// when
	svc, _ := NewEnvAdapter().GetService("test-svc-one")

	// then
	assert.Equal(t, "test-svc-one", svc.Name)
	assert.Equal(t, []Address{Address{Host: "test123", Port: "8080"}}, svc.Addresses)

	os.Unsetenv("SVC_TEST_SVC_ONE_ADDR")
}

func TestEnvAdapter_CanReadFromEnv_MultipleAddresses(t *testing.T) {
	// given
	os.Setenv("SVC_TEST_SVC_ONE_ADDR", "test123:8080, test123:8081, test321:9090")

	// when
	svc, _ := NewEnvAdapter().GetService("test-svc-one")

	// then
	assert.Equal(t, "test-svc-one", svc.Name)
	assert.Equal(t, []Address{
		Address{Host: "test123", Port: "8080"},
		Address{Host: "test123", Port: "8081"},
		Address{Host: "test321", Port: "9090"}},
		svc.Addresses,
	)

	os.Unsetenv("SVC_TEST_SVC_ONE_ADDR")
}

func TestEnvAdapter_CanReadFromEnv_TrailingComma(t *testing.T) {
	// given
	os.Setenv("SVC_TEST_SVC_ONE_ADDR", "test123:")

	// when
	svc, _ := NewEnvAdapter().GetService("test-svc-one")

	// then
	assert.Equal(t, "test-svc-one", svc.Name)
	assert.Equal(t, []Address{Address{Host: "test123", Port: "80"}}, svc.Addresses)

	os.Unsetenv("SVC_TEST_SVC_ONE_ADDR")
}

func TestEnvAdapter_CanReadFromEnv_LeadingComma(t *testing.T) {
	// given
	os.Setenv("SVC_TEST_SVC_ONE_ADDR", ":8080")

	// when
	svc, _ := NewEnvAdapter().GetService("test-svc-one")

	// then
	assert.Equal(t, "test-svc-one", svc.Name)
	assert.Equal(t, []Address{Address{Host: "localhost", Port: "8080"}}, svc.Addresses)

	os.Unsetenv("SVC_TEST_SVC_ONE_ADDR")
}

func TestEnvAdapter_CanReadFromEnv_OnlyComma(t *testing.T) {
	// given
	os.Setenv("SVC_TEST_SVC_ONE_ADDR", ":")

	// when
	svc, _ := NewEnvAdapter().GetService("test-svc-one")

	// then
	assert.Equal(t, "test-svc-one", svc.Name)
	assert.Equal(t, []Address{Address{Host: "localhost", Port: "80"}}, svc.Addresses)

	os.Unsetenv("SVC_TEST_SVC_ONE_ADDR")
}

func TestEnvAdapter_CanReadFromEnv_MultipleSvcEnvs(t *testing.T) {
	// given
	os.Setenv("SVC_TEST_SVC_ONE_ADDR", "test567:8080")
	os.Setenv("SVC_TEST_SVC_TWO_ADDR", "test890:9090")

	// when
	ea := NewEnvAdapter()

	svc1, _ := ea.GetService("test-svc-one")
	svc2, _ := ea.GetService("test-svc-two")

	// then
	assert.Equal(t, "test-svc-one", svc1.Name)
	assert.Equal(t, []Address{Address{Host: "test567", Port: "8080"}}, svc1.Addresses)

	assert.Equal(t, "test-svc-two", svc2.Name)
	assert.Equal(t, []Address{Address{Host: "test890", Port: "9090"}}, svc2.Addresses)

	os.Unsetenv("SVC_TEST_SVC_ONE_ADDR")
	os.Unsetenv("SVC_TEST_SVC_TWO_ADDR")
}
<|endoftext|>


<commit_before>package accnt

// represents an instance of cache accounting. currently there is
// only one implementation called `FlatAccnt`, but it could be
// replaced with alternative eviction algorithms in the future if
// they just implement this interface
type Accnt interface {
	GetEvictQ() chan *EvictTarget
	AddChunk(string, uint32, uint64)
	HitChunk(string, uint32)

	// these methods are for stats only
	MissMetric()
	PartialMetric()
	CompleteMetric()
}

// used by accounting to tell the chunk cache what to evict
type EvictTarget struct {
	Metric string
	Ts     uint32
}

func NewAccnt(maxSize uint64) Accnt {
	// currently we only know NewFlatAccnt
	return NewFlatAccnt(maxSize)
}
<commit_msg>comment fix<commit_after>package accnt

// Accnt represents an instance of cache accounting.
// Currently there is only one implementation called `FlatAccnt`,
// but it could be replaced with alternative eviction algorithms
// in the future if they just implement this interface.
type Accnt interface {
	GetEvictQ() chan *EvictTarget
	AddChunk(string, uint32, uint64)
	HitChunk(string, uint32)

	// these methods are for stats only
	MissMetric()
	PartialMetric()
	CompleteMetric()
}

// used by accounting to tell the chunk cache what to evict
type EvictTarget struct {
	Metric string
	Ts     uint32
}

func NewAccnt(maxSize uint64) Accnt {
	// currently we only know NewFlatAccnt
	return NewFlatAccnt(maxSize)
}
<|endoftext|>


<commit_before>package scene

import (
	"context"
	"image/color"
	"testing"
	"time"

	"github.com/oakmound/oak/v3/render"
)

func TestDoAfterCancels(t *testing.T) {
	baseCtx, cancel := context.WithCancel(context.Background())
	ctx := &Context{
		Context: baseCtx,
	}
	triggered := false
	go ctx.DoAfter(3*time.Second, func() {
		triggered = true
	})
	// Wait to make sure the routine started
	time.Sleep(1 * time.Second)
	cancel()
	time.Sleep(3 * time.Second)
	if triggered {
		t.Fatal("doAfter should not have triggered")
	}
}

func TestDoAfterHappens(t *testing.T) {
	baseCtx, cancel := context.WithCancel(context.Background())
	defer cancel()
	ctx := &Context{
		Context: baseCtx,
	}
	triggered := false
	go ctx.DoAfter(1*time.Second, func() {
		triggered = true
	})
	time.Sleep(2 * time.Second)
	if !triggered {
		t.Fatal("doAfter did not trigger")
	}
}

func TestDoAfterContextCancels(t *testing.T) {
	baseCtx, baseCancel := context.WithCancel(context.Background())
	ctx := &Context{
		Context: baseCtx,
	}
	triggered := false
	cancelCtx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
	defer cancel()
	go ctx.DoAfterContext(cancelCtx, func() {
		triggered = true
	})
	// Wait to make sure the routine started
	time.Sleep(1 * time.Second)
	baseCancel()
	time.Sleep(3 * time.Second)
	if triggered {
		t.Fatal("doAfterContext should not have triggered")
	}
}

func TestDoAfterContextHappens(t *testing.T) {
	baseCtx, baseCancel := context.WithCancel(context.Background())
	defer baseCancel()
	ctx := &Context{
		Context: baseCtx,
	}
	cancelCtx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
	defer cancel()
	triggered := false
	go ctx.DoAfterContext(cancelCtx, func() {
		triggered = true
	})
	time.Sleep(2 * time.Second)
	if !triggered {
		t.Fatal("doAfterContext did not trigger")
	}
}

func TestDrawForTime(t *testing.T) {
	baseCtx, baseCancel := context.WithCancel(context.Background())
	defer baseCancel()
	ctx := &Context{
		Context:   baseCtx,
		DrawStack: render.GlobalDrawStack,
	}
	err := ctx.DrawForTime(render.NewColorBox(5, 5, color.RGBA{255, 255, 255, 255}), 0, 4)
	if err == nil {
		t.Fatalf("draw time to invalid layer should fail")
	}

	err = ctx.DrawForTime(render.NewColorBox(5, 5, color.RGBA{255, 255, 255, 255}), 0, 0)
	if err != nil {
		t.Fatalf("draw time should not have failed")
	}
}
<commit_msg>scene: fix draw for time test<commit_after>package scene

import (
	"context"
	"image/color"
	"testing"
	"time"

	"github.com/oakmound/oak/v3/render"
)

func TestDoAfterCancels(t *testing.T) {
	baseCtx, cancel := context.WithCancel(context.Background())
	ctx := &Context{
		Context: baseCtx,
	}
	triggered := false
	go ctx.DoAfter(3*time.Second, func() {
		triggered = true
	})
	// Wait to make sure the routine started
	time.Sleep(1 * time.Second)
	cancel()
	time.Sleep(3 * time.Second)
	if triggered {
		t.Fatal("doAfter should not have triggered")
	}
}

func TestDoAfterHappens(t *testing.T) {
	baseCtx, cancel := context.WithCancel(context.Background())
	defer cancel()
	ctx := &Context{
		Context: baseCtx,
	}
	triggered := false
	go ctx.DoAfter(1*time.Second, func() {
		triggered = true
	})
	time.Sleep(2 * time.Second)
	if !triggered {
		t.Fatal("doAfter did not trigger")
	}
}

func TestDoAfterContextCancels(t *testing.T) {
	baseCtx, baseCancel := context.WithCancel(context.Background())
	ctx := &Context{
		Context: baseCtx,
	}
	triggered := false
	cancelCtx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
	defer cancel()
	go ctx.DoAfterContext(cancelCtx, func() {
		triggered = true
	})
	// Wait to make sure the routine started
	time.Sleep(1 * time.Second)
	baseCancel()
	time.Sleep(3 * time.Second)
	if triggered {
		t.Fatal("doAfterContext should not have triggered")
	}
}

func TestDoAfterContextHappens(t *testing.T) {
	baseCtx, baseCancel := context.WithCancel(context.Background())
	defer baseCancel()
	ctx := &Context{
		Context: baseCtx,
	}
	cancelCtx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
	defer cancel()
	triggered := false
	go ctx.DoAfterContext(cancelCtx, func() {
		triggered = true
	})
	time.Sleep(2 * time.Second)
	if !triggered {
		t.Fatal("doAfterContext did not trigger")
	}
}

func TestDrawForTime(t *testing.T) {
	baseCtx, baseCancel := context.WithCancel(context.Background())
	defer baseCancel()
	ctx := &Context{
		Context:   baseCtx,
		DrawStack: render.NewDrawStack(render.NewDynamicHeap(), render.NewDynamicHeap()),
	}
	err := ctx.DrawForTime(render.NewColorBox(5, 5, color.RGBA{255, 255, 255, 255}), 0, 4)
	if err == nil {
		t.Fatalf("draw time to invalid layer should fail")
	}

	err = ctx.DrawForTime(render.NewColorBox(5, 5, color.RGBA{255, 255, 255, 255}), 0, 0)
	if err != nil {
		t.Fatalf("draw time should not have failed")
	}
}
<|endoftext|>