<commit_before>package consumergroup

import (
	"errors"
	"fmt"
	"reflect"
	"runtime/debug"
	"sync"
	"time"

	"github.com/Shopify/sarama"
	"github.com/meitu/go-zookeeper/zk"
	"github.com/sirupsen/logrus"
)

const (
	cgInit = iota
	cgStart
	cgStopped
)

const (
	restartEvent = iota
	quitEvent
)

// ConsumerGroup consume message from Kafka with rebalancing supports
type ConsumerGroup struct {
	name           string
	storage        groupStorage
	topicConsumers map[string]*topicConsumer
	saramaClient   sarama.Client
	saramaConsumer sarama.Consumer

	id          string
	state       int
	wg          sync.WaitGroup
	stopCh      chan struct{}
	triggerCh   chan int
	triggerOnce *sync.Once
	owners      map[string]map[int32]string

	config *Config
	logger *logrus.Logger

	onLoad, onClose []func()
}

// NewConsumerGroup create the ConsumerGroup instance with config
func NewConsumerGroup(config *Config) (*ConsumerGroup, error) {
	if config == nil {
		return nil, errors.New("config can't be empty")
	}
	err := config.validate()
	if err != nil {
		return nil, fmt.Errorf("vaildate config failed, as %s", err)
	}

	cg := new(ConsumerGroup)
	cg.state = cgInit
	cg.config = config
	cg.id = config.ConsumerID
	if cg.id == "" {
		cg.id = genConsumerID()
	}
	cg.name = config.GroupID
	cg.triggerCh = make(chan int)
	cg.topicConsumers = make(map[string]*topicConsumer)
	cg.onLoad = make([]func(), 0)
	cg.onClose = make([]func(), 0)
	cg.storage = newZKGroupStorage(config.ZkList, config.ZkSessionTimeout)
	cg.logger = logrus.New()
	if _, ok := cg.storage.(*zkGroupStorage); ok {
		cg.storage.(*zkGroupStorage).Chroot(config.Chroot)
	}

	err = cg.initSaramaConsumer()
	if err != nil {
		return nil, fmt.Errorf("init sarama consumer, as %s", err)
	}
	cg.owners = make(map[string]map[int32]string)
	for _, topic := range config.TopicList {
		cg.topicConsumers[topic] = newTopicConsumer(cg, topic)
		cg.owners[topic] = make(map[int32]string)
	}
	return cg, nil
}

func (cg *ConsumerGroup) initSaramaConsumer() error {
	brokerList, err := cg.storage.getBrokerList()
	if err != nil {
		return err
	}
	if len(brokerList) == 0 {
		return errors.New("no broker alive")
	}
	cg.saramaClient, err = sarama.NewClient(brokerList, cg.config.SaramaConfig)
	if err != nil {
		return err
	}
	cg.saramaConsumer, err = sarama.NewConsumerFromClient(cg.saramaClient)
	return err
}

// Start would register ConsumerGroup, and rebalance would be triggered.
// ConsumerGroup computes the partitions which should be consumed by consumer's num, and start fetching message.
func (cg *ConsumerGroup) Start() error {
	// exit when failed to register the consumer
	err := cg.storage.registerConsumer(cg.name, cg.id, nil)
	if err != nil && err != zk.ErrNodeExists {
		return err
	}
	cg.wg.Add(1)
	go cg.start()
	return nil
}

// Stop would unregister ConsumerGroup, and rebalance would be triggered.
// The partitions which consumed by this ConsumerGroup would be assigned to others.
func (cg *ConsumerGroup) Stop() {
	cg.stop()
	cg.wg.Wait()
}

// SetLogger use to set the user's logger the consumer group
func (cg *ConsumerGroup) SetLogger(l *logrus.Logger) {
	if l != nil {
		cg.logger = l
	}
}

// IsStopped return whether the ConsumerGroup was stopped or not.
func (cg *ConsumerGroup) IsStopped() bool {
	return cg.state == cgStopped
}

func (cg *ConsumerGroup) callRecover() {
	if err := recover(); err != nil {
		cg.logger.WithFields(logrus.Fields{
			"group": cg.name,
			"err":   err,
			"stack": string(debug.Stack()),
		}).Error("Recover panic")
		cg.stop()
	}
}

func (cg *ConsumerGroup) start() {
	var wg sync.WaitGroup

	defer cg.callRecover()
	defer func() {
		cg.state = cgStopped
		err := cg.storage.deleteConsumer(cg.name, cg.id)
		if err != nil {
			cg.logger.WithFields(logrus.Fields{
				"group": cg.name,
				"err":   err,
			}).Error("Failed to delete consumer from zk")
		}
		for _, tc := range cg.topicConsumers {
			close(tc.messages)
			close(tc.errors)
		}
		cg.wg.Done()
	}()

CONSUME_TOPIC_LOOP:
	for {
		cg.logger.WithField("group", cg.name).Info("Consumer group started")
		cg.triggerOnce = new(sync.Once)
		cg.stopCh = make(chan struct{})

		err := cg.watchRebalance()
		if err != nil {
			cg.logger.WithFields(logrus.Fields{
				"group": cg.name,
				"err":   err,
			}).Error("Failed to watch rebalance")
			cg.stop()
			return
		}
		wg.Add(1)
		go func() {
			defer cg.callRecover()
			defer wg.Done()
			cg.autoReconnect(cg.storage.(*zkGroupStorage).sessionTimeout / 3)
		}()
		for _, consumer := range cg.topicConsumers {
			wg.Add(1)
			consumer.start()
			go func(tc *topicConsumer) {
				defer cg.callRecover()
				defer wg.Done()
				tc.wg.Wait()
				cg.logger.WithFields(logrus.Fields{
					"group": tc.group,
					"topic": tc.name,
				}).Info("Stop the topic consumer")
			}(consumer)
		}
		cg.state = cgStart
		for _, onLoadFunc := range cg.onLoad {
			onLoadFunc()
		}
		msg := <-cg.triggerCh
		for _, onCloseFunc := range cg.onClose {
			onCloseFunc()
		}
		switch msg {
		case restartEvent:
			close(cg.stopCh)
			// The stop channel was used to notify partition's consumer to stop consuming when rebalance is triggered.
			// So we should reinit when rebalance was triggered, as it would be closed.
			wg.Wait()
			continue CONSUME_TOPIC_LOOP
		case quitEvent:
			close(cg.stopCh)
			cg.logger.WithField("group", cg.name).Info("ConsumerGroup is stopping")
			wg.Wait()
			cg.logger.WithField("group", cg.name).Info("ConsumerGroup was stopped")
			return
		}
	}
}

func (cg *ConsumerGroup) stop() {
	cg.triggerOnce.Do(func() { cg.triggerCh <- quitEvent })
}

func (cg *ConsumerGroup) triggerRebalance() {
	cg.triggerOnce.Do(func() { cg.triggerCh <- restartEvent })
}

func (cg *ConsumerGroup) getPartitionConsumer(topic string, partition int32, nextOffset int64) (sarama.PartitionConsumer, error) {
	consumer, err := cg.saramaConsumer.ConsumePartition(topic, partition, nextOffset)
	if err == sarama.ErrOffsetOutOfRange {
		nextOffset = cg.config.OffsetAutoReset
		consumer, err = cg.saramaConsumer.ConsumePartition(topic, partition, nextOffset)
	}
	return consumer, err
}

// GetMessages was used to get a unbuffered message's channel from specified topic
func (cg *ConsumerGroup) GetMessages(topic string) (<-chan *sarama.ConsumerMessage, bool) {
	if topicConsumer, ok := cg.topicConsumers[topic]; ok {
		return topicConsumer.messages, true
	}
	return nil, false
}

// GetErrors was used to get a unbuffered error's channel from specified topic
func (cg *ConsumerGroup) GetErrors(topic string) (<-chan *sarama.ConsumerError, bool) {
	if topicConsumer, ok := cg.topicConsumers[topic]; ok {
		return topicConsumer.errors, true
	}
	return nil, false
}

// OnLoad load callback function that runs after startup
func (cg *ConsumerGroup) OnLoad(cb func()) {
	cg.onLoad = append(cg.onLoad, cb)
}

// OnClose load callback function that runs before the end
func (cg *ConsumerGroup) OnClose(cb func()) {
	cg.onClose = append(cg.onClose, cb)
}

func (cg *ConsumerGroup) autoReconnect(interval time.Duration) {
	timer := time.NewTimer(interval)
	cg.logger.WithField("group", cg.name).Info("The auto-reconnect consumer thread was started")
	defer cg.logger.WithField("group", cg.name).Info("The auto-reconnect consumer thread was stopped")
	for {
		select {
		case <-cg.stopCh:
			return
		case <-timer.C:
			timer.Reset(interval)
			exist, err := cg.storage.existsConsumer(cg.name, cg.id)
			if err != nil {
				cg.logger.WithFields(logrus.Fields{
					"group": cg.name,
					"err":   err,
				}).Error("Failed to check consumer existence")
				break
			}
			if exist {
				break
			}
			err = cg.storage.registerConsumer(cg.name, cg.id, nil)
			if err != nil {
				cg.logger.WithFields(logrus.Fields{
					"group": cg.name,
					"err":   err,
				}).Error("Failed to re-register consumer")
			}
		}
	}
}

func (cg *ConsumerGroup) watchRebalance() error {
	consumersWatcher, err := cg.storage.watchConsumerList(cg.name)
	if err != nil {
		return err
	}
	topicsChange, topicWatchers, err := cg.watchTopics(cg.config.TopicList)
	if err != nil {
		return err
	}
	go func() {
		defer cg.callRecover()
		cg.logger.WithField("group", cg.name).Info("Rebalance watcher thread was started")
		select {
		case <-consumersWatcher.EvCh:
			cg.triggerRebalance()
			cg.logger.WithField("group", cg.name).Info("Trigger rebalance while consumers was changed")
			for _, tw := range topicWatchers {
				cg.storage.removeWatcher(tw)
			}
		case topic := <-topicsChange:
			cg.triggerRebalance()
			cg.logger.WithFields(logrus.Fields{
				"group": cg.name,
				"topic": topic,
			}).Info("Trigger rebalance while partitions was changed")
			cg.storage.removeWatcher(consumersWatcher)
		case <-cg.stopCh:
		}
		cg.logger.WithField("group", cg.name).Info("Rebalance watcher thread was exited")
	}()
	return nil
}

// CommitOffset is used to commit offset when auto commit was disabled.
func (cg *ConsumerGroup) CommitOffset(topic string, partition int32, offset int64) error {
	if cg.config.OffsetAutoCommitEnable {
		return errors.New("commit offset take effect when offset auto commit was disabled")
	}
	return cg.storage.commitOffset(cg.name, topic, partition, offset)
}

// GetOffsets return the offset in memory for debug
func (cg *ConsumerGroup) GetOffsets() map[string]interface{} {
	topics := make(map[string]interface{})
	for topic, tc := range cg.topicConsumers {
		topics[topic] = tc.getOffsets()
	}
	return topics
}

// Owners return owners of all partitions
func (cg *ConsumerGroup) Owners() map[string]map[int32]string {
	return cg.owners
}

func (cg *ConsumerGroup) watchTopics(topics []string) (<-chan string, []*zk.Watcher, error) {
	ch := make(chan string)
	cases := make([]reflect.SelectCase, len(topics))
	watchers := make([]*zk.Watcher, len(topics))
	for i, topic := range topics {
		w, err := cg.storage.watchTopic(topic)
		if err != nil {
			return nil, nil, err
		}
		watchers[i] = w
		cases[i] = reflect.SelectCase{Dir: reflect.SelectRecv, Chan: reflect.ValueOf(w.EvCh)}
	}
	go func(cases []reflect.SelectCase, ch chan string, topics []string) {
		chosen, _, ok := reflect.Select(cases)
		if !ok {
			//the chosen channel has been closed.
			return
		}
		topic := topics[chosen]
		num, err := cg.storage.getPartitionsNum(topic)
		if err != nil {
			cg.logger.WithFields(logrus.Fields{
				"topic": topic,
				"err":   err,
			}).Error("Failed to get partitions in zookeeper after topic metadata change")
			return
		}
		for {
			cg.saramaClient.RefreshMetadata(topic)
			partitions, err := cg.saramaClient.Partitions(topic)
			if err != nil {
				cg.logger.WithFields(logrus.Fields{
					"topic": topic,
					"err":   err,
				}).Error("Failed to get partitions in broker after topic metadata change")
				return
			}
			if len(partitions) == num {
				break
			}
			time.Sleep(100 * time.Millisecond)
		}

		ch <- topics[chosen]
	}(cases, ch, topics)
	return ch, watchers, nil
}
<commit_msg>MOD: log error msg when start consume with out-of-range<commit_after>package consumergroup

import (
	"errors"
	"fmt"
	"reflect"
	"runtime/debug"
	"sync"
	"time"

	"github.com/Shopify/sarama"
	"github.com/meitu/go-zookeeper/zk"
	"github.com/sirupsen/logrus"
)

const (
	cgInit = iota
	cgStart
	cgStopped
)

const (
	restartEvent = iota
	quitEvent
)

// ConsumerGroup consume message from Kafka with rebalancing supports
type ConsumerGroup struct {
	name           string
	storage        groupStorage
	topicConsumers map[string]*topicConsumer
	saramaClient   sarama.Client
	saramaConsumer sarama.Consumer

	id          string
	state       int
	wg          sync.WaitGroup
	stopCh      chan struct{}
	triggerCh   chan int
	triggerOnce *sync.Once
	owners      map[string]map[int32]string

	config *Config
	logger *logrus.Logger

	onLoad, onClose []func()
}

// NewConsumerGroup create the ConsumerGroup instance with config
func NewConsumerGroup(config *Config) (*ConsumerGroup, error) {
	if config == nil {
		return nil, errors.New("config can't be empty")
	}
	err := config.validate()
	if err != nil {
		return nil, fmt.Errorf("vaildate config failed, as %s", err)
	}

	cg := new(ConsumerGroup)
	cg.state = cgInit
	cg.config = config
	cg.id = config.ConsumerID
	if cg.id == "" {
		cg.id = genConsumerID()
	}
	cg.name = config.GroupID
	cg.triggerCh = make(chan int)
	cg.topicConsumers = make(map[string]*topicConsumer)
	cg.onLoad = make([]func(), 0)
	cg.onClose = make([]func(), 0)
	cg.storage = newZKGroupStorage(config.ZkList, config.ZkSessionTimeout)
	cg.logger = logrus.New()
	if _, ok := cg.storage.(*zkGroupStorage); ok {
		cg.storage.(*zkGroupStorage).Chroot(config.Chroot)
	}

	err = cg.initSaramaConsumer()
	if err != nil {
		return nil, fmt.Errorf("init sarama consumer, as %s", err)
	}
	cg.owners = make(map[string]map[int32]string)
	for _, topic := range config.TopicList {
		cg.topicConsumers[topic] = newTopicConsumer(cg, topic)
		cg.owners[topic] = make(map[int32]string)
	}
	return cg, nil
}

func (cg *ConsumerGroup) initSaramaConsumer() error {
	brokerList, err := cg.storage.getBrokerList()
	if err != nil {
		return err
	}
	if len(brokerList) == 0 {
		return errors.New("no broker alive")
	}
	cg.saramaClient, err = sarama.NewClient(brokerList, cg.config.SaramaConfig)
	if err != nil {
		return err
	}
	cg.saramaConsumer, err = sarama.NewConsumerFromClient(cg.saramaClient)
	return err
}

// Start would register ConsumerGroup, and rebalance would be triggered.
// ConsumerGroup computes the partitions which should be consumed by consumer's num, and start fetching message.
func (cg *ConsumerGroup) Start() error {
	// exit when failed to register the consumer
	err := cg.storage.registerConsumer(cg.name, cg.id, nil)
	if err != nil && err != zk.ErrNodeExists {
		return err
	}
	cg.wg.Add(1)
	go cg.start()
	return nil
}

// Stop would unregister ConsumerGroup, and rebalance would be triggered.
// The partitions which consumed by this ConsumerGroup would be assigned to others.
func (cg *ConsumerGroup) Stop() {
	cg.stop()
	cg.wg.Wait()
}

// SetLogger use to set the user's logger the consumer group
func (cg *ConsumerGroup) SetLogger(l *logrus.Logger) {
	if l != nil {
		cg.logger = l
	}
}

// IsStopped return whether the ConsumerGroup was stopped or not.
func (cg *ConsumerGroup) IsStopped() bool {
	return cg.state == cgStopped
}

func (cg *ConsumerGroup) callRecover() {
	if err := recover(); err != nil {
		cg.logger.WithFields(logrus.Fields{
			"group": cg.name,
			"err":   err,
			"stack": string(debug.Stack()),
		}).Error("Recover panic")
		cg.stop()
	}
}

func (cg *ConsumerGroup) start() {
	var wg sync.WaitGroup

	defer cg.callRecover()
	defer func() {
		cg.state = cgStopped
		err := cg.storage.deleteConsumer(cg.name, cg.id)
		if err != nil {
			cg.logger.WithFields(logrus.Fields{
				"group": cg.name,
				"err":   err,
			}).Error("Failed to delete consumer from zk")
		}
		for _, tc := range cg.topicConsumers {
			close(tc.messages)
			close(tc.errors)
		}
		cg.wg.Done()
	}()

CONSUME_TOPIC_LOOP:
	for {
		cg.logger.WithField("group", cg.name).Info("Consumer group started")
		cg.triggerOnce = new(sync.Once)
		cg.stopCh = make(chan struct{})

		err := cg.watchRebalance()
		if err != nil {
			cg.logger.WithFields(logrus.Fields{
				"group": cg.name,
				"err":   err,
			}).Error("Failed to watch rebalance")
			cg.stop()
			return
		}
		wg.Add(1)
		go func() {
			defer cg.callRecover()
			defer wg.Done()
			cg.autoReconnect(cg.storage.(*zkGroupStorage).sessionTimeout / 3)
		}()
		for _, consumer := range cg.topicConsumers {
			wg.Add(1)
			consumer.start()
			go func(tc *topicConsumer) {
				defer cg.callRecover()
				defer wg.Done()
				tc.wg.Wait()
				cg.logger.WithFields(logrus.Fields{
					"group": tc.group,
					"topic": tc.name,
				}).Info("Stop the topic consumer")
			}(consumer)
		}
		cg.state = cgStart
		for _, onLoadFunc := range cg.onLoad {
			onLoadFunc()
		}
		msg := <-cg.triggerCh
		for _, onCloseFunc := range cg.onClose {
			onCloseFunc()
		}
		switch msg {
		case restartEvent:
			close(cg.stopCh)
			// The stop channel was used to notify partition's consumer to stop consuming when rebalance is triggered.
			// So we should reinit when rebalance was triggered, as it would be closed.
			wg.Wait()
			continue CONSUME_TOPIC_LOOP
		case quitEvent:
			close(cg.stopCh)
			cg.logger.WithField("group", cg.name).Info("ConsumerGroup is stopping")
			wg.Wait()
			cg.logger.WithField("group", cg.name).Info("ConsumerGroup was stopped")
			return
		}
	}
}

func (cg *ConsumerGroup) stop() {
	cg.triggerOnce.Do(func() { cg.triggerCh <- quitEvent })
}

func (cg *ConsumerGroup) triggerRebalance() {
	cg.triggerOnce.Do(func() { cg.triggerCh <- restartEvent })
}

func (cg *ConsumerGroup) getPartitionConsumer(topic string, partition int32, nextOffset int64) (sarama.PartitionConsumer, error) {
	consumer, err := cg.saramaConsumer.ConsumePartition(topic, partition, nextOffset)
	if err == sarama.ErrOffsetOutOfRange {
		nextOffset = cg.config.OffsetAutoReset
		cg.logger.WithFields(logrus.Fields{
			"group":     cg.name,
			"topic":     topic,
			"partition": partition,
			"offset":    nextOffset,
		}).Error("Partition's offset was out of range, use auto-reset")
		consumer, err = cg.saramaConsumer.ConsumePartition(topic, partition, nextOffset)
	}
	return consumer, err
}

// GetMessages was used to get a unbuffered message's channel from specified topic
func (cg *ConsumerGroup) GetMessages(topic string) (<-chan *sarama.ConsumerMessage, bool) {
	if topicConsumer, ok := cg.topicConsumers[topic]; ok {
		return topicConsumer.messages, true
	}
	return nil, false
}

// GetErrors was used to get a unbuffered error's channel from specified topic
func (cg *ConsumerGroup) GetErrors(topic string) (<-chan *sarama.ConsumerError, bool) {
	if topicConsumer, ok := cg.topicConsumers[topic]; ok {
		return topicConsumer.errors, true
	}
	return nil, false
}

// OnLoad load callback function that runs after startup
func (cg *ConsumerGroup) OnLoad(cb func()) {
	cg.onLoad = append(cg.onLoad, cb)
}

// OnClose load callback function that runs before the end
func (cg *ConsumerGroup) OnClose(cb func()) {
	cg.onClose = append(cg.onClose, cb)
}

func (cg *ConsumerGroup) autoReconnect(interval time.Duration) {
	timer := time.NewTimer(interval)
	cg.logger.WithField("group", cg.name).Info("The auto-reconnect consumer thread was started")
	defer cg.logger.WithField("group", cg.name).Info("The auto-reconnect consumer thread was stopped")
	for {
		select {
		case <-cg.stopCh:
			return
		case <-timer.C:
			timer.Reset(interval)
			exist, err := cg.storage.existsConsumer(cg.name, cg.id)
			if err != nil {
				cg.logger.WithFields(logrus.Fields{
					"group": cg.name,
					"err":   err,
				}).Error("Failed to check consumer existence")
				break
			}
			if exist {
				break
			}
			err = cg.storage.registerConsumer(cg.name, cg.id, nil)
			if err != nil {
				cg.logger.WithFields(logrus.Fields{
					"group": cg.name,
					"err":   err,
				}).Error("Failed to re-register consumer")
			}
		}
	}
}

func (cg *ConsumerGroup) watchRebalance() error {
	consumersWatcher, err := cg.storage.watchConsumerList(cg.name)
	if err != nil {
		return err
	}
	topicsChange, topicWatchers, err := cg.watchTopics(cg.config.TopicList)
	if err != nil {
		return err
	}
	go func() {
		defer cg.callRecover()
		cg.logger.WithField("group", cg.name).Info("Rebalance watcher thread was started")
		select {
		case <-consumersWatcher.EvCh:
			cg.triggerRebalance()
			cg.logger.WithField("group", cg.name).Info("Trigger rebalance while consumers was changed")
			for _, tw := range topicWatchers {
				cg.storage.removeWatcher(tw)
			}
		case topic := <-topicsChange:
			cg.triggerRebalance()
			cg.logger.WithFields(logrus.Fields{
				"group": cg.name,
				"topic": topic,
			}).Info("Trigger rebalance while partitions was changed")
			cg.storage.removeWatcher(consumersWatcher)
		case <-cg.stopCh:
		}
		cg.logger.WithField("group", cg.name).Info("Rebalance watcher thread was exited")
	}()
	return nil
}

// CommitOffset is used to commit offset when auto commit was disabled.
func (cg *ConsumerGroup) CommitOffset(topic string, partition int32, offset int64) error {
	if cg.config.OffsetAutoCommitEnable {
		return errors.New("commit offset take effect when offset auto commit was disabled")
	}
	return cg.storage.commitOffset(cg.name, topic, partition, offset)
}

// GetOffsets return the offset in memory for debug
func (cg *ConsumerGroup) GetOffsets() map[string]interface{} {
	topics := make(map[string]interface{})
	for topic, tc := range cg.topicConsumers {
		topics[topic] = tc.getOffsets()
	}
	return topics
}

// Owners return owners of all partitions
func (cg *ConsumerGroup) Owners() map[string]map[int32]string {
	return cg.owners
}

func (cg *ConsumerGroup) watchTopics(topics []string) (<-chan string, []*zk.Watcher, error) {
	ch := make(chan string)
	cases := make([]reflect.SelectCase, len(topics))
	watchers := make([]*zk.Watcher, len(topics))
	for i, topic := range topics {
		w, err := cg.storage.watchTopic(topic)
		if err != nil {
			return nil, nil, err
		}
		watchers[i] = w
		cases[i] = reflect.SelectCase{Dir: reflect.SelectRecv, Chan: reflect.ValueOf(w.EvCh)}
	}
	go func(cases []reflect.SelectCase, ch chan string, topics []string) {
		chosen, _, ok := reflect.Select(cases)
		if !ok {
			//the chosen channel has been closed.
			return
		}
		topic := topics[chosen]
		num, err := cg.storage.getPartitionsNum(topic)
		if err != nil {
			cg.logger.WithFields(logrus.Fields{
				"topic": topic,
				"err":   err,
			}).Error("Failed to get partitions in zookeeper after topic metadata change")
			return
		}
		for {
			cg.saramaClient.RefreshMetadata(topic)
			partitions, err := cg.saramaClient.Partitions(topic)
			if err != nil {
				cg.logger.WithFields(logrus.Fields{
					"topic": topic,
					"err":   err,
				}).Error("Failed to get partitions in broker after topic metadata change")
				return
			}
			if len(partitions) == num {
				break
			}
			time.Sleep(100 * time.Millisecond)
		}

		ch <- topics[chosen]
	}(cases, ch, topics)
	return ch, watchers, nil
}
<|endoftext|>


<commit_before>package functions

import (
	"encoding/json"

	"testing"

	"github.com/cihangir/gene/generators/common"
	"github.com/cihangir/gene/testdata"
	"github.com/cihangir/schema"
)

func TestFunctions(t *testing.T) {
	s := &schema.Schema{}
	if err := json.Unmarshal([]byte(testdata.JSON1), s); err != nil {
		t.Fatal(err.Error())
	}

	s = s.Resolve(nil)

	a, err := generate("test", s.Definitions["Account"])
	common.TestEquals(t, nil, err)
	common.TestEquals(t, expected, string(a))
}

const expected = `package testapi

// New creates a new local Account handler
func NewAccount() *Account { return &Account{} }

// Account is for holding the api functions
type Account struct{}

func (a *Account) Create(ctx context.Context, req *models.Account, res *models.Account) error {
	return db.MustGetDB(ctx).Create(models.NewAccount(), req, res)
}

func (a *Account) Delete(ctx context.Context, req *models.Account, res *models.Account) error {
	return db.MustGetDB(ctx).Delete(models.NewAccount(), req, res)
}

func (a *Account) One(ctx context.Context, req *models.Account, res *models.Account) error {
	return db.MustGetDB(ctx).One(models.NewAccount(), req, res)
}

func (a *Account) Some(ctx context.Context, req *models.Account, res *[]*models.Account) error {
	return db.MustGetDB(ctx).Some(models.NewAccount(), req, res)
}

func (a *Account) Update(ctx context.Context, req *models.Account, res *models.Account) error {
	return db.MustGetDB(ctx).Update(models.NewAccount(), req, res)
}
`
<commit_msg>Gene: provide context while calling test function<commit_after>package functions

import (
	"encoding/json"

	"testing"

	"github.com/cihangir/gene/generators/common"
	"github.com/cihangir/gene/testdata"
	"github.com/cihangir/schema"
)

func TestFunctions(t *testing.T) {
	s := &schema.Schema{}
	err := json.Unmarshal([]byte(testdata.TestDataFull), s)
	common.TestEquals(t, nil, err)

	s = s.Resolve(s)
	context := common.NewContext()

	a, err := generate(context, "test", s.Definitions["Account"])
	common.TestEquals(t, nil, err)
	common.TestEquals(t, expected, string(a))
}

const expected = `package testapi

// New creates a new local Account handler
func NewAccount() *Account { return &Account{} }

// Account is for holding the api functions
type Account struct{}

func (a *Account) Create(ctx context.Context, req *models.Account, res *models.Account) error {
	return db.MustGetDB(ctx).Create(models.NewAccount(), req, res)
}

func (a *Account) Delete(ctx context.Context, req *models.Account, res *models.Account) error {
	return db.MustGetDB(ctx).Delete(models.NewAccount(), req, res)
}

func (a *Account) One(ctx context.Context, req *models.Account, res *models.Account) error {
	return db.MustGetDB(ctx).One(models.NewAccount(), req, res)
}

func (a *Account) Some(ctx context.Context, req *models.Account, res *[]*models.Account) error {
	return db.MustGetDB(ctx).Some(models.NewAccount(), req, res)
}

func (a *Account) Update(ctx context.Context, req *models.Account, res *models.Account) error {
	return db.MustGetDB(ctx).Update(models.NewAccount(), req, res)
}
`
<|endoftext|>


<commit_before><commit_msg>add timers<commit_after><|endoftext|>


<commit_before><commit_msg>spec changes<commit_after><|endoftext|>


<commit_before>package commands

import (
	"path/filepath"
	"strings"

	"github.com/git-lfs/git-lfs/errors"
	"github.com/git-lfs/git-lfs/git"
	"github.com/git-lfs/git-lfs/git/odb"
	"github.com/spf13/cobra"
)

var (
	// migrateIncludeRefs is a set of Git references to explicitly include
	// in the migration.
	migrateIncludeRefs []string
	// migrateExcludeRefs is a set of Git references to explicitly exclude
	// in the migration.
	migrateExcludeRefs []string
)

// getObjectDatabase creates a *git.ObjectDatabase from the filesystem pointed
// at the .git directory of the currently checked-out repository.
func getObjectDatabase() (*odb.ObjectDatabase, error) {
	dir, err := git.GitDir()
	if err != nil {
		return nil, errors.Wrap(err, "cannot open root")
	}
	return odb.FromFilesystem(filepath.Join(dir, "objects"))
}

// formatRefName returns the fully-qualified name for the given Git reference
// "ref".
func formatRefName(ref *git.Ref, remote string) string {
	var name []string

	switch ref.Type {
	case git.RefTypeRemoteBranch:
		name = []string{"refs", "remotes", remote, ref.Name}
	case git.RefTypeRemoteTag:
		name = []string{"refs", "tags", ref.Name}
	default:
		return ref.Name
	}
	return strings.Join(name, "/")

}

// currentRefToMigrate returns the fully-qualified name of the currently
// checked-out reference, or an error if the reference's type was not a local
// branch.
func currentRefToMigrate() (*git.Ref, error) {
	current, err := git.CurrentRef()
	if err != nil {
		return nil, err
	}

	if current.Type == git.RefTypeOther ||
		current.Type == git.RefTypeRemoteBranch ||
		current.Type == git.RefTypeRemoteTag {

		return nil, errors.Errorf("fatal: cannot migrate non-local ref: %s", current.Name)
	}
	return current, nil
}

func init() {
	RegisterCommand("migrate", nil, func(cmd *cobra.Command) {
		// Adding flags directly to cmd.Flags() doesn't apply those
		// flags to any subcommands of the root. Therefore, loop through
		// each subcommand specifically, and include common arguments to
		// each.
		//
		// Once done, link each orphaned command to the
		// `git-lfs-migrate(1)` command as a subcommand (child).

		for _, subcommand := range []*cobra.Command{} {
			subcommand.Flags().StringVarP(&includeArg, "include", "I", "", "Include a list of paths")
			subcommand.Flags().StringVarP(&excludeArg, "exclude", "X", "", "Exclude a list of paths")

			subcommand.Flags().StringSliceVar(&migrateIncludeRefs, "include-ref", nil, "An explicit list of refs to include")
			subcommand.Flags().StringSliceVar(&migrateExcludeRefs, "exclude-ref", nil, "An explicit list of refs to exclude")

			cmd.AddCommand(subcommand)
		}
	})
}
<commit_msg>commands/command_migrate: teach how to find all remote refs<commit_after>package commands

import (
	"path/filepath"
	"strings"

	"github.com/git-lfs/git-lfs/errors"
	"github.com/git-lfs/git-lfs/git"
	"github.com/git-lfs/git-lfs/git/odb"
	"github.com/spf13/cobra"
)

var (
	// migrateIncludeRefs is a set of Git references to explicitly include
	// in the migration.
	migrateIncludeRefs []string
	// migrateExcludeRefs is a set of Git references to explicitly exclude
	// in the migration.
	migrateExcludeRefs []string
)

// getObjectDatabase creates a *git.ObjectDatabase from the filesystem pointed
// at the .git directory of the currently checked-out repository.
func getObjectDatabase() (*odb.ObjectDatabase, error) {
	dir, err := git.GitDir()
	if err != nil {
		return nil, errors.Wrap(err, "cannot open root")
	}
	return odb.FromFilesystem(filepath.Join(dir, "objects"))
}

// getRemoteRefs returns a fully qualified set of references belonging to all
// remotes known by the currently checked-out repository, or an error if those
// references could not be determined.
func getRemoteRefs() ([]string, error) {
	var refs []string

	remotes, err := git.RemoteList()
	if err != nil {
		return nil, err
	}

	for _, remote := range remotes {
		refsForRemote, err := git.RemoteRefs(remote)
		if err != nil {
			return nil, err
		}

		for _, ref := range refsForRemote {
			refs = append(refs, formatRefName(ref, remote))
		}
	}

	return refs, nil
}

// formatRefName returns the fully-qualified name for the given Git reference
// "ref".
func formatRefName(ref *git.Ref, remote string) string {
	var name []string

	switch ref.Type {
	case git.RefTypeRemoteBranch:
		name = []string{"refs", "remotes", remote, ref.Name}
	case git.RefTypeRemoteTag:
		name = []string{"refs", "tags", ref.Name}
	default:
		return ref.Name
	}
	return strings.Join(name, "/")

}

// currentRefToMigrate returns the fully-qualified name of the currently
// checked-out reference, or an error if the reference's type was not a local
// branch.
func currentRefToMigrate() (*git.Ref, error) {
	current, err := git.CurrentRef()
	if err != nil {
		return nil, err
	}

	if current.Type == git.RefTypeOther ||
		current.Type == git.RefTypeRemoteBranch ||
		current.Type == git.RefTypeRemoteTag {

		return nil, errors.Errorf("fatal: cannot migrate non-local ref: %s", current.Name)
	}
	return current, nil
}

func init() {
	RegisterCommand("migrate", nil, func(cmd *cobra.Command) {
		// Adding flags directly to cmd.Flags() doesn't apply those
		// flags to any subcommands of the root. Therefore, loop through
		// each subcommand specifically, and include common arguments to
		// each.
		//
		// Once done, link each orphaned command to the
		// `git-lfs-migrate(1)` command as a subcommand (child).

		for _, subcommand := range []*cobra.Command{} {
			subcommand.Flags().StringVarP(&includeArg, "include", "I", "", "Include a list of paths")
			subcommand.Flags().StringVarP(&excludeArg, "exclude", "X", "", "Exclude a list of paths")

			subcommand.Flags().StringSliceVar(&migrateIncludeRefs, "include-ref", nil, "An explicit list of refs to include")
			subcommand.Flags().StringSliceVar(&migrateExcludeRefs, "exclude-ref", nil, "An explicit list of refs to exclude")

			cmd.AddCommand(subcommand)
		}
	})
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/hex"
	"flag"
	"io/ioutil"
	"log"
	"runtime"
	"sync"

	"github.com/BurntSushi/toml"
	"github.com/mitchellh/go-homedir"
)

const domainStr = "PK"

type tomlConfig struct {
	Listen    string
	Connect   string
	Psk       string
	EncryptSk string
	SignSk    string
	SignPk    string
}

// Conf - Shared config
type Conf struct {
	Listen    string
	Connect   string
	Psk       []byte
	EncryptSk []byte
	SignSk    []byte
	SignPk    []byte
}

// StoredContent - Paste buffer
type StoredContent struct {
	signature           []byte
	ciphertextWithNonce []byte
}

var storedContent StoredContent
var storedContentRWMutex sync.RWMutex

func expandConfigFile(path string) string {
	file, err := homedir.Expand(path)
	if err != nil {
		log.Fatal(err)
	}
	return file
}

func main() {
	isCopy := flag.Bool("copy", false, "store content (copy) - default is to retrieve the clipboard content (paste)")
	_ = flag.Bool("paste", false, "retrieve content (paste) - ignored")
	isServer := flag.Bool("server", false, "start a server")
	isGenKeys := flag.Bool("genkeys", false, "generate keys")
	defaultConfigFile := "~/.piknik.toml"
	if runtime.GOOS == "windows" {
		defaultConfigFile = "~/piknik.toml"
	}
	configFile := flag.String("config", defaultConfigFile, "configuration file")
	flag.Parse()
	tomlData, err := ioutil.ReadFile(expandConfigFile(*configFile))
	if err != nil && *isGenKeys == false {
		log.Fatal(err)
	}
	var tomlConf tomlConfig
	if _, err = toml.Decode(string(tomlData), &tomlConf); err != nil {
		log.Fatal(err)
	}
	var conf Conf
	if tomlConf.Listen == "" {
		conf.Listen = "0.0.0.0:8075"
	} else {
		conf.Listen = tomlConf.Listen
	}
	if tomlConf.Connect == "" {
		conf.Connect = "127.0.0.1:8075"
	} else {
		conf.Connect = tomlConf.Connect
	}
	if *isGenKeys {
		genKeys(conf, *configFile)
		return
	}
	pskHex := tomlConf.Psk
	psk, err := hex.DecodeString(pskHex)
	if err != nil {
		log.Fatal(err)
	}
	conf.Psk = psk
	if encryptSkHex := tomlConf.EncryptSk; encryptSkHex != "" {
		encryptSk, err := hex.DecodeString(encryptSkHex)
		if err != nil {
			log.Fatal(err)
		}
		conf.EncryptSk = encryptSk
	}
	if signSkHex := tomlConf.SignSk; signSkHex != "" {
		signSk, err := hex.DecodeString(signSkHex)
		if err != nil {
			log.Fatal(err)
		}
		conf.SignSk = signSk
	}
	if signPkHex := tomlConf.SignPk; signPkHex != "" {
		signPk, err := hex.DecodeString(signPkHex)
		if err != nil {
			log.Fatal(err)
		}
		conf.SignPk = signPk
	}
	if *isServer {
		ServerMain(conf)
	} else {
		ClientMain(conf, *isCopy)
	}
}
<commit_msg>Reorder<commit_after>package main

import (
	"encoding/hex"
	"flag"
	"io/ioutil"
	"log"
	"runtime"
	"sync"

	"github.com/BurntSushi/toml"
	"github.com/mitchellh/go-homedir"
)

const domainStr = "PK"

type tomlConfig struct {
	Connect   string
	Listen    string
	EncryptSk string
	Psk       string
	SignPk    string
	SignSk    string
}

// Conf - Shared config
type Conf struct {
	Connect   string
	Listen    string
	EncryptSk []byte
	Psk       []byte
	SignPk    []byte
	SignSk    []byte
}

// StoredContent - Paste buffer
type StoredContent struct {
	signature           []byte
	ciphertextWithNonce []byte
}

var storedContent StoredContent
var storedContentRWMutex sync.RWMutex

func expandConfigFile(path string) string {
	file, err := homedir.Expand(path)
	if err != nil {
		log.Fatal(err)
	}
	return file
}

func main() {
	isCopy := flag.Bool("copy", false, "store content (copy) - default is to retrieve the clipboard content (paste)")
	_ = flag.Bool("paste", false, "retrieve content (paste) - ignored")
	isServer := flag.Bool("server", false, "start a server")
	isGenKeys := flag.Bool("genkeys", false, "generate keys")
	defaultConfigFile := "~/.piknik.toml"
	if runtime.GOOS == "windows" {
		defaultConfigFile = "~/piknik.toml"
	}
	configFile := flag.String("config", defaultConfigFile, "configuration file")
	flag.Parse()
	tomlData, err := ioutil.ReadFile(expandConfigFile(*configFile))
	if err != nil && *isGenKeys == false {
		log.Fatal(err)
	}
	var tomlConf tomlConfig
	if _, err = toml.Decode(string(tomlData), &tomlConf); err != nil {
		log.Fatal(err)
	}
	var conf Conf
	if tomlConf.Listen == "" {
		conf.Listen = "0.0.0.0:8075"
	} else {
		conf.Listen = tomlConf.Listen
	}
	if tomlConf.Connect == "" {
		conf.Connect = "127.0.0.1:8075"
	} else {
		conf.Connect = tomlConf.Connect
	}
	if *isGenKeys {
		genKeys(conf, *configFile)
		return
	}
	pskHex := tomlConf.Psk
	psk, err := hex.DecodeString(pskHex)
	if err != nil {
		log.Fatal(err)
	}
	conf.Psk = psk
	if encryptSkHex := tomlConf.EncryptSk; encryptSkHex != "" {
		encryptSk, err := hex.DecodeString(encryptSkHex)
		if err != nil {
			log.Fatal(err)
		}
		conf.EncryptSk = encryptSk
	}
	if signSkHex := tomlConf.SignSk; signSkHex != "" {
		signSk, err := hex.DecodeString(signSkHex)
		if err != nil {
			log.Fatal(err)
		}
		conf.SignSk = signSk
	}
	if signPkHex := tomlConf.SignPk; signPkHex != "" {
		signPk, err := hex.DecodeString(signPkHex)
		if err != nil {
			log.Fatal(err)
		}
		conf.SignPk = signPk
	}
	if *isServer {
		ServerMain(conf)
	} else {
		ClientMain(conf, *isCopy)
	}
}
<|endoftext|>


<commit_before>// Package pinata is a utility to beat data out of interface{}, []interface{}
// and map[string]interface{}.
package pinata

import (
	"bytes"
	"fmt"
	"strings"
)

// Pinata holds a value and offers methods for extracting data from it.
type Pinata interface {
	Contents() interface{}
	Error() error
	ClearError()
	StringAtPath(...string) string
	String() string
	StringAtIndex(int) string
	PinataAtPath(...string) Pinata
	PinataAtIndex(int) Pinata
}

// New creates a new Pinata. Instances returned are not thread safe.
func New(contents interface{}) Pinata {
	switch t := contents.(type) {
	default:
		return &otherPinata{contents: t}
	case map[string]interface{}:
		return &mapPinata{contents: t}
	case []interface{}:
		return &slicePinata{}
	}
}

var _ = error(PinataError{})

type ErrorReason string

const (
	ErrorReasonIncompatibleType ErrorReason = "incompatible type"
	ErrorReasonNotFound                     = "not found"
	ErrorReasonInvalidInput                 = "invalid input"
)

type PinataError struct {
	Reason ErrorReason
	Method string
	Input  []interface{}
	Advice string
}

func (p PinataError) Error() string {
	var input string
	if len(p.Input) > 0 {
		var buf bytes.Buffer
		for i := range p.Input {
			buf.WriteString("%#v")
			if i < len(p.Input)-1 {
				buf.WriteString(", ")
			}
		}
		input = fmt.Sprintf(buf.String(), p.Input...)
	}
	return fmt.Sprintf("pinata: %s(%s) - %s (%s)", p.Method, input, p.Reason, p.Advice)
}

type basePinata struct {
	err error
}

func (p *basePinata) Error() error {
	return p.err
}

func (p *basePinata) ClearError() {
	p.err = nil
}

func (p *basePinata) String() string {
	if p.err != nil {
		return ""
	}
	p.err = &PinataError{
		Method: "String",
		Reason: ErrorReasonIncompatibleType,
		Input:  nil,
		Advice: "call this method on a string pinata",
	}
	return ""
}

func (p *basePinata) PinataAtIndex(index int) Pinata {
	p.indexUnsupported("PinataAtIndex", index)
	return nil
}

func (p *basePinata) PinataAtPath(path ...string) Pinata {
	p.pathUnsupported("PinataAtPath", path)
	return nil
}

func (p *basePinata) StringAtPath(path ...string) string {
	p.pathUnsupported("StringAtPath", path)
	return ""
}

func (p *basePinata) StringAtIndex(index int) string {
	p.indexUnsupported("StringAtIndex", index)
	return ""
}

func (p *basePinata) Contents() interface{} {
	return nil
}

func (p *basePinata) indexUnsupported(method string, index int) {
	if p.err != nil {
		return
	}
	p.err = &PinataError{
		Method: method,
		Reason: ErrorReasonIncompatibleType,
		Input:  []interface{}{index},
		Advice: "call this method on a slice pinata",
	}
}

func (p *basePinata) setIndexOutOfRange(method string, index int, contents []interface{}) bool {
	if index < 0 || index >= len(contents) {
		p.err = &PinataError{
			Method: method,
			Reason: ErrorReasonInvalidInput,
			Input:  []interface{}{index},
			Advice: fmt.Sprintf("specify an index from 0 to %d", len(contents)-1),
		}
		return true
	}
	return false
}

func (p *basePinata) pathUnsupported(method string, path []string) {
	if p.err != nil {
		return
	}
	p.err = &PinataError{
		Method: method,
		Reason: ErrorReasonIncompatibleType,
		Input:  toInterfaceSlice(path),
		Advice: "call this method on a map pinata",
	}
}

type otherPinata struct {
	basePinata
	contents interface{}
}

func (p *otherPinata) String() string {
	if p.err != nil {
		return ""
	}
	if v, ok := p.contents.(string); ok {
		return v
	}
	return p.basePinata.String()
}

func (p *otherPinata) Contents() interface{} {
	return p.contents
}

type slicePinata struct {
	basePinata
	contents []interface{}
}

func (p *slicePinata) pinataAtIndex(method string, index int) Pinata {
	if p.err != nil {
		return nil
	}
	if p.setIndexOutOfRange(method, index, p.contents) {
		return nil
	}
	return New(p.contents[index])
}

func (p *slicePinata) PinataAtIndex(index int) Pinata {
	return p.pinataAtIndex("PinataAtIndex", index)
}

func (p *slicePinata) StringAtIndex(index int) string {
	const method = "StringAtIndex"
	pinata := p.pinataAtIndex(method, index)
	if p.err != nil {
		return ""
	}
	s := pinata.String()
	if pinata.Error() != nil {
		p.err = &PinataError{
			Method: method,
			Reason: ErrorReasonIncompatibleType,
			Input:  []interface{}{index},
			Advice: "not a string, try another type",
		}
	}
	return s
}

func (p *slicePinata) Contents() interface{} {
	return p.contents
}

type mapPinata struct {
	basePinata
	contents map[string]interface{}
}

func (p *mapPinata) pinataAtPath(method string, path ...string) Pinata {
	if p.err != nil {
		return nil
	}
	if len(path) == 0 {
		p.err = &PinataError{
			Method: method,
			Reason: ErrorReasonInvalidInput,
			Input:  toInterfaceSlice(path),
			Advice: "specify a path",
		}
		return nil
	}

	value := p.contents
	for i := 0; i < len(path)-1; i++ {
		current := path[i]
		if v, ok := value[current]; ok {
			if v, ok := v.(map[string]interface{}); ok {
				value = v
			} else {
				p.err = &PinataError{
					Method: method,
					Reason: ErrorReasonIncompatibleType,
					Input:  toInterfaceSlice(path),
					Advice: fmt.Sprintf(`path "%s" does not hold a pinata`, strings.Join(path[:i+1], `", "`)),
				}
				return nil
			}
		} else {
			p.err = &PinataError{
				Method: method,
				Reason: ErrorReasonNotFound,
				Input:  toInterfaceSlice(path),
				Advice: fmt.Sprintf(`path "%s" does not exist`, strings.Join(path[:i+1], `", "`)),
			}
			return nil
		}
	}

	if v, ok := value[path[len(path)-1]]; ok {
		return New(v)
	}

	p.err = &PinataError{
		Method: method,
		Reason: ErrorReasonNotFound,
		Input:  toInterfaceSlice(path),
		Advice: fmt.Sprintf(`path "%s" does not exist`, strings.Join(path, `", "`)),
	}
	return nil
}

func (p *mapPinata) PinataAtPath(path ...string) Pinata {
	return p.pinataAtPath("PinataAtPath", path...)
}

func (p *mapPinata) StringAtPath(path ...string) string {
	const method = "StringAtPath"
	pinata := p.pinataAtPath(method, path...)
	if p.err != nil {
		return ""
	}
	s := pinata.String()
	if pinata.Error() != nil {
		p.err = &PinataError{
			Method: method,
			Reason: ErrorReasonIncompatibleType,
			Input:  toInterfaceSlice(path),
			Advice: "not a string, try another type",
		}
	}
	return s
}

func (p *mapPinata) Contents() interface{} {
	return p.contents
}

func toInterfaceSlice(c []string) []interface{} {
	ifaces := make([]interface{}, len(c))
	for i := range c {
		ifaces[i] = c[i]
	}
	return ifaces
}

func toSlice(first string, rest []string) []interface{} {
	slice := make([]interface{}, len(rest)+1)
	i := 0
	slice[i] = first
	for _, v := range rest {
		i++
		slice[i] = v
	}
	return slice
}
<commit_msg>clarify the internal method error checking policy<commit_after>// Package pinata is a utility to beat data out of interface{}, []interface{}
// and map[string]interface{}.
package pinata

import (
	"bytes"
	"fmt"
	"strings"
)

// Pinata holds a value and offers methods for extracting data from it.
type Pinata interface {
	Contents() interface{}
	Error() error
	ClearError()
	StringAtPath(...string) string
	String() string
	StringAtIndex(int) string
	PinataAtPath(...string) Pinata
	PinataAtIndex(int) Pinata
}

// New creates a new Pinata. Instances returned are not thread safe.
func New(contents interface{}) Pinata {
	switch t := contents.(type) {
	default:
		return &otherPinata{contents: t}
	case map[string]interface{}:
		return &mapPinata{contents: t}
	case []interface{}:
		return &slicePinata{}
	}
}

var _ = error(PinataError{})

type ErrorReason string

const (
	ErrorReasonIncompatibleType ErrorReason = "incompatible type"
	ErrorReasonNotFound                     = "not found"
	ErrorReasonInvalidInput                 = "invalid input"
)

type PinataError struct {
	Reason ErrorReason
	Method string
	Input  []interface{}
	Advice string
}

func (p PinataError) Error() string {
	var input string
	if len(p.Input) > 0 {
		var buf bytes.Buffer
		for i := range p.Input {
			buf.WriteString("%#v")
			if i < len(p.Input)-1 {
				buf.WriteString(", ")
			}
		}
		input = fmt.Sprintf(buf.String(), p.Input...)
	}
	return fmt.Sprintf("pinata: %s(%s) - %s (%s)", p.Method, input, p.Reason, p.Advice)
}

type basePinata struct {
	err error
}

func (p *basePinata) Error() error {
	return p.err
}

func (p *basePinata) ClearError() {
	p.err = nil
}

func (p *basePinata) String() string {
	if p.err != nil {
		return ""
	}
	p.err = &PinataError{
		Method: "String",
		Reason: ErrorReasonIncompatibleType,
		Input:  nil,
		Advice: "call this method on a string pinata",
	}
	return ""
}

func (p *basePinata) PinataAtIndex(index int) Pinata {
	if p.err != nil {
		return nil
	}
	p.indexUnsupported("PinataAtIndex", index)
	return nil
}

func (p *basePinata) PinataAtPath(path ...string) Pinata {
	if p.err != nil {
		return nil
	}
	p.pathUnsupported("PinataAtPath", path)
	return nil
}

func (p *basePinata) StringAtPath(path ...string) string {
	if p.err != nil {
		return ""
	}
	p.pathUnsupported("StringAtPath", path)
	return ""
}

func (p *basePinata) StringAtIndex(index int) string {
	if p.err != nil {
		return ""
	}
	p.indexUnsupported("StringAtIndex", index)
	return ""
}

func (p *basePinata) Contents() interface{} {
	return nil // should always override this method
}

// this method assumes p.err != nil
func (p *basePinata) indexUnsupported(method string, index int) {
	p.err = &PinataError{
		Method: method,
		Reason: ErrorReasonIncompatibleType,
		Input:  []interface{}{index},
		Advice: "call this method on a slice pinata",
	}
}

// this method assumes p.err != nil
func (p *basePinata) setIndexOutOfRange(method string, index int, contents []interface{}) bool {
	if index < 0 || index >= len(contents) {
		p.err = &PinataError{
			Method: method,
			Reason: ErrorReasonInvalidInput,
			Input:  []interface{}{index},
			Advice: fmt.Sprintf("specify an index from 0 to %d", len(contents)-1),
		}
		return true
	}
	return false
}

// this method assumes p.err != nil
func (p *basePinata) pathUnsupported(method string, path []string) {
	p.err = &PinataError{
		Method: method,
		Reason: ErrorReasonIncompatibleType,
		Input:  toInterfaceSlice(path),
		Advice: "call this method on a map pinata",
	}
}

type otherPinata struct {
	basePinata
	contents interface{}
}

func (p *otherPinata) String() string {
	if p.err != nil {
		return ""
	}
	if v, ok := p.contents.(string); ok {
		return v
	}
	return p.basePinata.String()
}

func (p *otherPinata) Contents() interface{} {
	return p.contents
}

type slicePinata struct {
	basePinata
	contents []interface{}
}

// this method assumes p.err != nil
func (p *slicePinata) pinataAtIndex(method string, index int) Pinata {
	if p.setIndexOutOfRange(method, index, p.contents) {
		return nil
	}
	return New(p.contents[index])
}

func (p *slicePinata) PinataAtIndex(index int) Pinata {
	if p.err != nil {
		return nil
	}
	return p.pinataAtIndex("PinataAtIndex", index)
}

func (p *slicePinata) StringAtIndex(index int) string {
	if p.err != nil {
		return ""
	}
	const method = "StringAtIndex"
	pinata := p.pinataAtIndex(method, index)
	if p.err != nil {
		return ""
	}
	s := pinata.String()
	if pinata.Error() != nil {
		p.err = &PinataError{
			Method: method,
			Reason: ErrorReasonIncompatibleType,
			Input:  []interface{}{index},
			Advice: "not a string, try another type",
		}
	}
	return s
}

func (p *slicePinata) Contents() interface{} {
	return p.contents
}

type mapPinata struct {
	basePinata
	contents map[string]interface{}
}

// this method assumes p.err != nil
func (p *mapPinata) pinataAtPath(method string, path ...string) Pinata {
	if len(path) == 0 {
		p.err = &PinataError{
			Method: method,
			Reason: ErrorReasonInvalidInput,
			Input:  toInterfaceSlice(path),
			Advice: "specify a path",
		}
		return nil
	}

	value := p.contents
	for i := 0; i < len(path)-1; i++ {
		current := path[i]
		if v, ok := value[current]; ok {
			if v, ok := v.(map[string]interface{}); ok {
				value = v
			} else {
				p.err = &PinataError{
					Method: method,
					Reason: ErrorReasonIncompatibleType,
					Input:  toInterfaceSlice(path),
					Advice: fmt.Sprintf(`path "%s" does not hold a pinata`, strings.Join(path[:i+1], `", "`)),
				}
				return nil
			}
		} else {
			p.err = &PinataError{
				Method: method,
				Reason: ErrorReasonNotFound,
				Input:  toInterfaceSlice(path),
				Advice: fmt.Sprintf(`path "%s" does not exist`, strings.Join(path[:i+1], `", "`)),
			}
			return nil
		}
	}

	if v, ok := value[path[len(path)-1]]; ok {
		return New(v)
	}

	p.err = &PinataError{
		Method: method,
		Reason: ErrorReasonNotFound,
		Input:  toInterfaceSlice(path),
		Advice: fmt.Sprintf(`path "%s" does not exist`, strings.Join(path, `", "`)),
	}
	return nil
}

func (p *mapPinata) PinataAtPath(path ...string) Pinata {
	if p.err != nil {
		return nil
	}
	return p.pinataAtPath("PinataAtPath", path...)
}

func (p *mapPinata) StringAtPath(path ...string) string {
	if p.err != nil {
		return ""
	}
	const method = "StringAtPath"
	pinata := p.pinataAtPath(method, path...)
	if p.err != nil {
		return ""
	}
	s := pinata.String()
	if pinata.Error() != nil {
		p.err = &PinataError{
			Method: method,
			Reason: ErrorReasonIncompatibleType,
			Input:  toInterfaceSlice(path),
			Advice: "not a string, try another type",
		}
	}
	return s
}

func (p *mapPinata) Contents() interface{} {
	return p.contents
}

func toInterfaceSlice(c []string) []interface{} {
	ifaces := make([]interface{}, len(c))
	for i := range c {
		ifaces[i] = c[i]
	}
	return ifaces
}

func toSlice(first string, rest []string) []interface{} {
	slice := make([]interface{}, len(rest)+1)
	i := 0
	slice[i] = first
	for _, v := range rest {
		i++
		slice[i] = v
	}
	return slice
}
<|endoftext|>


<commit_before>// Copyright 2016 The Ebiten 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 ebiten

import (
	"image"
	"image/color"

	"github.com/hajimehoshi/ebiten/internal/graphics"
	"github.com/hajimehoshi/ebiten/internal/graphics/opengl"
)

type drawImageHistoryItem struct {
	image    *Image
	vertices []int16
	geom     GeoM
	colorm   ColorM
	mode     opengl.CompositeMode
}

type imageForPixels interface {
	Pixels(context *opengl.Context) ([]uint8, error)
}

// basePixels and baseColor are exclusive.

type pixels struct {
	image            imageForPixels
	inconsistent     bool
	basePixels       []uint8
	baseColor        color.Color
	drawImageHistory []*drawImageHistoryItem
}

func (p *pixels) resetWithPixels(pixels []uint8) {
	if p.basePixels == nil {
		p.basePixels = make([]uint8, len(pixels))
	}
	copy(p.basePixels, pixels)
	p.inconsistent = false
	p.baseColor = nil
	p.drawImageHistory = nil
}

func (p *pixels) clear() {
	p.inconsistent = false
	p.basePixels = nil
	p.baseColor = nil
	p.drawImageHistory = nil
}

func (p *pixels) isCleared() bool {
	if p.inconsistent {
		return false
	}
	return p.basePixels == nil && p.baseColor == nil && p.drawImageHistory == nil
}

func (p *pixels) fill(clr color.Color) {
	p.inconsistent = false
	p.basePixels = nil
	p.baseColor = clr
	p.drawImageHistory = nil
}

func (p *pixels) appendDrawImageHistory(item *drawImageHistoryItem) {
	if item.image.impl.pixels.inconsistent {
		p.inconsistent = true
	}
	if p.inconsistent {
		return
	}
	p.drawImageHistory = append(p.drawImageHistory, item)
}

func (p *pixels) at(idx int, context *opengl.Context) (color.Color, error) {
	if p.inconsistent || p.basePixels == nil || p.drawImageHistory != nil {
		p.inconsistent = false
		var err error
		p.basePixels, err = p.image.Pixels(context)
		if err != nil {
			return nil, err
		}
		p.baseColor = nil
		p.drawImageHistory = nil
	}
	r, g, b, a := p.basePixels[idx], p.basePixels[idx+1], p.basePixels[idx+2], p.basePixels[idx+3]
	return color.RGBA{r, g, b, a}, nil
}

func (p *pixels) hasHistoryWith(target *Image) bool {
	for _, c := range p.drawImageHistory {
		if c.image == target {
			return true
		}
	}
	return false
}

func (p *pixels) flushIfInconsistent(context *opengl.Context) error {
	if !p.inconsistent {
		return nil
	}
	p.inconsistent = false
	var err error
	p.basePixels, err = p.image.Pixels(context)
	if err != nil {
		return err
	}
	p.baseColor = nil
	p.drawImageHistory = nil
	return nil
}

func (p *pixels) flushIfNeeded(target *Image, context *opengl.Context) error {
	if p.drawImageHistory == nil {
		return nil
	}
	if !p.hasHistoryWith(target) {
		return nil
	}
	if context == nil {
		p.inconsistent = true
		return nil
	}
	p.inconsistent = false
	var err error
	p.basePixels, err = p.image.Pixels(context)
	if err != nil {
		return err
	}
	p.baseColor = nil
	p.drawImageHistory = nil
	return nil
}

func (p *pixels) hasHistory() bool {
	return p.drawImageHistory != nil
}

// restore restores the pixels using its history.
//
// restore is the only function that the pixel data is not present on GPU when this is called.
func (p *pixels) restore(context *opengl.Context, width, height int, filter Filter) (*graphics.Image, error) {
	img := image.NewRGBA(image.Rect(0, 0, width, height))
	if p.basePixels != nil {
		for j := 0; j < height; j++ {
			copy(img.Pix[j*img.Stride:], p.basePixels[j*width*4:(j+1)*width*4])
		}
	}
	gimg, err := graphics.NewImageFromImage(img, glFilter(filter))
	if err != nil {
		return nil, err
	}
	if p.baseColor != nil {
		if p.basePixels != nil {
			panic("not reach")
		}
		if err := gimg.Fill(p.baseColor); err != nil {
			return nil, err
		}
	}
	for _, c := range p.drawImageHistory {
		// c.image.impl must be already restored.
		if c.image.impl.hasHistory() {
			panic("not reach")
		}
		if err := gimg.DrawImage(c.image.impl.image, c.vertices, &c.geom, &c.colorm, c.mode); err != nil {
			return nil, err
		}
	}
	p.inconsistent = false
	p.basePixels, err = gimg.Pixels(context)
	if err != nil {
		return nil, err
	}
	p.baseColor = nil
	p.drawImageHistory = nil
	return gimg, nil
}
<commit_msg>graphics: Add pixels.makeInconsistent<commit_after>// Copyright 2016 The Ebiten 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 ebiten

import (
	"image"
	"image/color"

	"github.com/hajimehoshi/ebiten/internal/graphics"
	"github.com/hajimehoshi/ebiten/internal/graphics/opengl"
)

type drawImageHistoryItem struct {
	image    *Image
	vertices []int16
	geom     GeoM
	colorm   ColorM
	mode     opengl.CompositeMode
}

type imageForPixels interface {
	Pixels(context *opengl.Context) ([]uint8, error)
}

// basePixels and baseColor are exclusive.

type pixels struct {
	image            imageForPixels
	inconsistent     bool
	basePixels       []uint8
	baseColor        color.Color
	drawImageHistory []*drawImageHistoryItem
}

func (p *pixels) resetWithPixels(pixels []uint8) {
	if p.basePixels == nil {
		p.basePixels = make([]uint8, len(pixels))
	}
	copy(p.basePixels, pixels)
	p.inconsistent = false
	p.baseColor = nil
	p.drawImageHistory = nil
}

func (p *pixels) clear() {
	p.inconsistent = false
	p.basePixels = nil
	p.baseColor = nil
	p.drawImageHistory = nil
}

func (p *pixels) isCleared() bool {
	if p.inconsistent {
		return false
	}
	return p.basePixels == nil && p.baseColor == nil && p.drawImageHistory == nil
}

func (p *pixels) fill(clr color.Color) {
	p.inconsistent = false
	p.basePixels = nil
	p.baseColor = clr
	p.drawImageHistory = nil
}

func (p *pixels) makeInconsistent() {
	p.inconsistent = true
	p.basePixels = nil
	p.baseColor = nil
	p.drawImageHistory = nil
}

func (p *pixels) appendDrawImageHistory(item *drawImageHistoryItem) {
	if item.image.impl.pixels.inconsistent {
		p.makeInconsistent()
	}
	if p.inconsistent {
		return
	}
	p.drawImageHistory = append(p.drawImageHistory, item)
}

func (p *pixels) at(idx int, context *opengl.Context) (color.Color, error) {
	if p.inconsistent || p.basePixels == nil || p.drawImageHistory != nil {
		p.inconsistent = false
		var err error
		p.basePixels, err = p.image.Pixels(context)
		if err != nil {
			return nil, err
		}
		p.baseColor = nil
		p.drawImageHistory = nil
	}
	r, g, b, a := p.basePixels[idx], p.basePixels[idx+1], p.basePixels[idx+2], p.basePixels[idx+3]
	return color.RGBA{r, g, b, a}, nil
}

func (p *pixels) hasHistoryWith(target *Image) bool {
	for _, c := range p.drawImageHistory {
		if c.image == target {
			return true
		}
	}
	return false
}

func (p *pixels) flushIfInconsistent(context *opengl.Context) error {
	if !p.inconsistent {
		return nil
	}
	p.inconsistent = false
	var err error
	p.basePixels, err = p.image.Pixels(context)
	if err != nil {
		return err
	}
	p.baseColor = nil
	p.drawImageHistory = nil
	return nil
}

func (p *pixels) flushIfNeeded(target *Image, context *opengl.Context) error {
	if p.drawImageHistory == nil {
		return nil
	}
	if !p.hasHistoryWith(target) {
		return nil
	}
	if context == nil {
		p.makeInconsistent()
		return nil
	}
	p.inconsistent = false
	var err error
	p.basePixels, err = p.image.Pixels(context)
	if err != nil {
		return err
	}
	p.baseColor = nil
	p.drawImageHistory = nil
	return nil
}

func (p *pixels) hasHistory() bool {
	return p.drawImageHistory != nil
}

// restore restores the pixels using its history.
//
// restore is the only function that the pixel data is not present on GPU when this is called.
func (p *pixels) restore(context *opengl.Context, width, height int, filter Filter) (*graphics.Image, error) {
	img := image.NewRGBA(image.Rect(0, 0, width, height))
	if p.basePixels != nil {
		for j := 0; j < height; j++ {
			copy(img.Pix[j*img.Stride:], p.basePixels[j*width*4:(j+1)*width*4])
		}
	}
	gimg, err := graphics.NewImageFromImage(img, glFilter(filter))
	if err != nil {
		return nil, err
	}
	if p.baseColor != nil {
		if p.basePixels != nil {
			panic("not reach")
		}
		if err := gimg.Fill(p.baseColor); err != nil {
			return nil, err
		}
	}
	for _, c := range p.drawImageHistory {
		// c.image.impl must be already restored.
		if c.image.impl.hasHistory() {
			panic("not reach")
		}
		if err := gimg.DrawImage(c.image.impl.image, c.vertices, &c.geom, &c.colorm, c.mode); err != nil {
			return nil, err
		}
	}
	p.inconsistent = false
	p.basePixels, err = gimg.Pixels(context)
	if err != nil {
		return nil, err
	}
	p.baseColor = nil
	p.drawImageHistory = nil
	return gimg, nil
}
<|endoftext|>


<commit_before>package appdash

import (
	"encoding/json"
	"reflect"
	"sort"
	"testing"
	"time"
)

func TestMarshalEvent(t *testing.T) {
	e := dummyEvent{
		A: "a",
		B: "b",
		C: 1,
		D: map[string]string{"k1": "v1", "k2": "v2"},
		E: "e",
		F: dummyEventF{
			G: "g",
			H: map[string]string{"k3": "v3", "k4": "v4"},
		},
	}
	as, err := MarshalEvent(e)
	if err != nil {
		t.Fatal(err)
	}

	want := Annotations{
		{Key: "A", Value: []byte("a")},
		{Key: "B", Value: []byte("b")},
		{Key: "C", Value: []byte("1")},
		{Key: "D.k1", Value: []byte("v1")},
		{Key: "D.k2", Value: []byte("v2")},
		{Key: "e", Value: []byte("e")},
		{Key: "F.G", Value: []byte("g")},
		{Key: "F.H.k3", Value: []byte("v3")},
		{Key: "F.H.k4", Value: []byte("v4")},
		{Key: "_schema:dummy"},
	}

	sort.Sort(annotations(as))
	sort.Sort(annotations(want))

	if !reflect.DeepEqual(as, want) {
		t.Errorf("got annotations\n%s\n\nwant\n%s", as, want)
	}
}

func TestUnmarshalEvent(t *testing.T) {
	as := Annotations{
		{Key: "A", Value: []byte("a")},
		{Key: "B", Value: []byte("b")},
		{Key: "C", Value: []byte("1")},
		{Key: "D.k1", Value: []byte("v1")},
		{Key: "D.k2", Value: []byte("v2")},
		{Key: "e", Value: []byte("e")},
		{Key: "F.G", Value: []byte("g")},
		{Key: "F.H.k3", Value: []byte("v3")},
		{Key: "F.H.k4", Value: []byte("v4")},
		{Key: "_schema:dummy"},
	}

	var e dummyEvent
	if err := UnmarshalEvent(as, &e); err != nil {
		t.Fatal(err)
	}

	want := dummyEvent{
		A: "a",
		B: "b",
		C: 1,
		D: map[string]string{"k1": "v1", "k2": "v2"},
		E: "e",
		F: dummyEventF{
			G: "g",
			H: map[string]string{"k3": "v3", "k4": "v4"},
		},
	}

	if !reflect.DeepEqual(e, want) {
		t.Errorf("got event\n%+v\n\nwant\n%+v", e, want)
	}
}

func TestUnmarshalEvents(t *testing.T) {
	origRegisteredEvents := registeredEvents
	defer func() {
		registeredEvents = origRegisteredEvents
	}()

	for k := range registeredEvents {
		delete(registeredEvents, k)
	}
	RegisterEvent(dummyEvent{})
	RegisterEvent(dummyEvent2{})

	anns := Annotations{
		{Key: "A", Value: []byte("a")},
		{Key: "X", Value: []byte("x")},
		{Key: "_schema:dummy"},
		{Key: "_schema:dummy2"},
	}
	var events []Event
	if err := UnmarshalEvents(anns, &events); err != nil {
		t.Fatal(err)
	}

	want := []Event{
		dummyEvent{A: "a"},
		dummyEvent2{A: "a", X: "x"},
	}
	if !reflect.DeepEqual(events, want) {
		t.Errorf("got events %#v, want %#v", events, want)
	}
}

func TestSpanName(t *testing.T) {
	e := spanName{"foo"}

	anns, err := MarshalEvent(e)
	if err != nil {
		t.Fatal(err)
	}

	span := Span{Annotations: anns}
	if want := "foo"; span.Name() != want {
		t.Errorf("got span name == %q, want %q", span.Name(), want)
	}
}

func TestMsg(t *testing.T) {
	e := Msg("foo")

	j, err := json.Marshal(e)
	if err != nil {
		t.Error(err)
	}

	got := string(j)
	want := `{"Msg":"foo"}`
	if got != want {
		t.Errorf("got %#v, want %#v", got, want)
	}
}

func TestMsg_Schema(t *testing.T) {
	e := Msg("foo")

	if e.Schema() != "msg" {
		t.Errorf("Unwant schema: %v", e.Schema())
	}
}

func TestLog(t *testing.T) {
	e := Log("foo").(logEvent)

	e.Time = time.Unix(123456789, 0).In(time.UTC)

	j, err := json.Marshal(e)
	if err != nil {
		t.Error(err)
	}

	got := string(j)
	want := `{"Msg":"foo","Time":"1973-11-29T21:33:09Z"}`
	if got != want {
		t.Errorf("got %#v, want %#v", got, want)
	}
}

func TestLog_Schema(t *testing.T) {
	e := Log("foo")

	if e.Schema() != "log" {
		t.Errorf("Unwant schema: %v", e.Schema())
	}
}

type dummyEvent struct {
	A, B string
	C    int
	D    map[string]string
	E    string `trace:"e"`
	F    dummyEventF
}

type dummyEventF struct {
	G string
	H map[string]string
}

func (dummyEvent) Schema() string { return "dummy" }

type dummyEvent2 struct{ A, X string }

func (dummyEvent2) Schema() string { return "dummy2" }

func BenchmarkMarshalEvent(b *testing.B) {
	e := dummyEvent{
		A: "a",
		B: "b",
		C: 1,
		D: map[string]string{"k1": "v1", "k2": "v2"},
		E: "e",
		F: dummyEventF{
			G: "g",
			H: map[string]string{"k3": "v3", "k4": "v4"},
		},
	}
	for i := 0; i < b.N; i++ {
		_, err := MarshalEvent(e)
		if err != nil {
			b.Fatal(err)
		}
	}
}

func BenchmarkUnmarshalEvents(b *testing.B) {
	anns := Annotations{
		{Key: "A", Value: []byte("a")},
		{Key: "X", Value: []byte("x")},
		{Key: "_schema:dummy"},
		{Key: "_schema:dummy2"},
	}
	for i := 0; i < b.N; i++ {
		var events []Event
		if err := UnmarshalEvents(anns, &events); err != nil {
			b.Fatal(err)
		}
	}
}
<commit_msg>fixes set-back `registeredEvents` to it's initial value<commit_after>package appdash

import (
	"encoding/json"
	"reflect"
	"sort"
	"testing"
	"time"
)

func TestMarshalEvent(t *testing.T) {
	e := dummyEvent{
		A: "a",
		B: "b",
		C: 1,
		D: map[string]string{"k1": "v1", "k2": "v2"},
		E: "e",
		F: dummyEventF{
			G: "g",
			H: map[string]string{"k3": "v3", "k4": "v4"},
		},
	}
	as, err := MarshalEvent(e)
	if err != nil {
		t.Fatal(err)
	}

	want := Annotations{
		{Key: "A", Value: []byte("a")},
		{Key: "B", Value: []byte("b")},
		{Key: "C", Value: []byte("1")},
		{Key: "D.k1", Value: []byte("v1")},
		{Key: "D.k2", Value: []byte("v2")},
		{Key: "e", Value: []byte("e")},
		{Key: "F.G", Value: []byte("g")},
		{Key: "F.H.k3", Value: []byte("v3")},
		{Key: "F.H.k4", Value: []byte("v4")},
		{Key: "_schema:dummy"},
	}

	sort.Sort(annotations(as))
	sort.Sort(annotations(want))

	if !reflect.DeepEqual(as, want) {
		t.Errorf("got annotations\n%s\n\nwant\n%s", as, want)
	}
}

func TestUnmarshalEvent(t *testing.T) {
	as := Annotations{
		{Key: "A", Value: []byte("a")},
		{Key: "B", Value: []byte("b")},
		{Key: "C", Value: []byte("1")},
		{Key: "D.k1", Value: []byte("v1")},
		{Key: "D.k2", Value: []byte("v2")},
		{Key: "e", Value: []byte("e")},
		{Key: "F.G", Value: []byte("g")},
		{Key: "F.H.k3", Value: []byte("v3")},
		{Key: "F.H.k4", Value: []byte("v4")},
		{Key: "_schema:dummy"},
	}

	var e dummyEvent
	if err := UnmarshalEvent(as, &e); err != nil {
		t.Fatal(err)
	}

	want := dummyEvent{
		A: "a",
		B: "b",
		C: 1,
		D: map[string]string{"k1": "v1", "k2": "v2"},
		E: "e",
		F: dummyEventF{
			G: "g",
			H: map[string]string{"k3": "v3", "k4": "v4"},
		},
	}

	if !reflect.DeepEqual(e, want) {
		t.Errorf("got event\n%+v\n\nwant\n%+v", e, want)
	}
}

func TestUnmarshalEvents(t *testing.T) {
	origRegisteredEvents := registeredEvents
	defer func() {
		registeredEvents = origRegisteredEvents
	}()
	registeredEvents = make(map[string]Event)

	RegisterEvent(dummyEvent{})
	RegisterEvent(dummyEvent2{})

	anns := Annotations{
		{Key: "A", Value: []byte("a")},
		{Key: "X", Value: []byte("x")},
		{Key: "_schema:dummy"},
		{Key: "_schema:dummy2"},
	}
	var events []Event
	if err := UnmarshalEvents(anns, &events); err != nil {
		t.Fatal(err)
	}

	want := []Event{
		dummyEvent{A: "a"},
		dummyEvent2{A: "a", X: "x"},
	}
	if !reflect.DeepEqual(events, want) {
		t.Errorf("got events %#v, want %#v", events, want)
	}
}

func TestSpanName(t *testing.T) {
	e := spanName{"foo"}

	anns, err := MarshalEvent(e)
	if err != nil {
		t.Fatal(err)
	}

	span := Span{Annotations: anns}
	if want := "foo"; span.Name() != want {
		t.Errorf("got span name == %q, want %q", span.Name(), want)
	}
}

func TestMsg(t *testing.T) {
	e := Msg("foo")

	j, err := json.Marshal(e)
	if err != nil {
		t.Error(err)
	}

	got := string(j)
	want := `{"Msg":"foo"}`
	if got != want {
		t.Errorf("got %#v, want %#v", got, want)
	}
}

func TestMsg_Schema(t *testing.T) {
	e := Msg("foo")

	if e.Schema() != "msg" {
		t.Errorf("Unwant schema: %v", e.Schema())
	}
}

func TestLog(t *testing.T) {
	e := Log("foo").(logEvent)

	e.Time = time.Unix(123456789, 0).In(time.UTC)

	j, err := json.Marshal(e)
	if err != nil {
		t.Error(err)
	}

	got := string(j)
	want := `{"Msg":"foo","Time":"1973-11-29T21:33:09Z"}`
	if got != want {
		t.Errorf("got %#v, want %#v", got, want)
	}
}

func TestLog_Schema(t *testing.T) {
	e := Log("foo")

	if e.Schema() != "log" {
		t.Errorf("Unwant schema: %v", e.Schema())
	}
}

type dummyEvent struct {
	A, B string
	C    int
	D    map[string]string
	E    string `trace:"e"`
	F    dummyEventF
}

type dummyEventF struct {
	G string
	H map[string]string
}

func (dummyEvent) Schema() string { return "dummy" }

type dummyEvent2 struct{ A, X string }

func (dummyEvent2) Schema() string { return "dummy2" }

func BenchmarkMarshalEvent(b *testing.B) {
	e := dummyEvent{
		A: "a",
		B: "b",
		C: 1,
		D: map[string]string{"k1": "v1", "k2": "v2"},
		E: "e",
		F: dummyEventF{
			G: "g",
			H: map[string]string{"k3": "v3", "k4": "v4"},
		},
	}
	for i := 0; i < b.N; i++ {
		_, err := MarshalEvent(e)
		if err != nil {
			b.Fatal(err)
		}
	}
}

func BenchmarkUnmarshalEvents(b *testing.B) {
	anns := Annotations{
		{Key: "A", Value: []byte("a")},
		{Key: "X", Value: []byte("x")},
		{Key: "_schema:dummy"},
		{Key: "_schema:dummy2"},
	}
	for i := 0; i < b.N; i++ {
		var events []Event
		if err := UnmarshalEvents(anns, &events); err != nil {
			b.Fatal(err)
		}
	}
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"strings"

	maps "github.com/ashwanthkumar/golang-utils/maps"
	"github.com/ashwanthkumar/slack-go-webhook"
)

type Slack struct {
	Webhook string
	Channel string
	Owners  string
}

func (s *Slack) Name() string {
	return "slack"
}

func (s *Slack) Notify(check AppCheck) {
	attachment := slack.Attachment{
		Text:  &check.Message,
		Color: s.resultToColor(check.Result),
	}
	attachment.
		AddField(slack.Field{Title: "App", Value: check.App, Short: true}).
		AddField(slack.Field{Title: "Check", Value: check.CheckName, Short: true}).
		AddField(slack.Field{Title: "Result", Value: s.resultToString(check.Result), Short: true}).
		AddField(slack.Field{Title: "Times", Value: fmt.Sprintf("%d", check.Times), Short: true})

	destination := maps.GetString(check.Labels, "alerts.slack.channel", s.Channel)

	appSpecificOwners := maps.GetString(check.Labels, "alerts.slack.owners", s.Owners)
	var owners []string
	if appSpecificOwners != "" {
		owners = strings.Split(appSpecificOwners, ",")
	} else {
		owners = []string{"@here"}
	}

	alertSuffix := "Please check!"
	if check.Result == Pass {
		alertSuffix = "Check Resolved, thanks!"
	}
	mainText := fmt.Sprintf("%s, %s", s.parseOwners(owners), alertSuffix)

	payload := slack.Payload(mainText,
		"marathon-alerts",
		"",
		destination,
		[]slack.Attachment{attachment})

	webhooks := strings.Split(maps.GetString(check.Labels, "alerts.slack.webhook", s.Webhook), ",")

	for _, webhook := range webhooks {
		err := slack.Send(webhook, "", payload)
		if err != nil {
			fmt.Printf("Unexpected Error - %v", err)
		}
	}
}

func (s *Slack) resultToColor(result CheckStatus) *string {
	color := "black"
	switch {
	case Pass == result || Resolved == result:
		color = "good"
	case Warning == result:
		color = "warning"
	case Critical == result:
		color = "danger"
	}

	return &color
}

func (s *Slack) resultToString(result CheckStatus) string {
	value := "Unknown"
	switch result {
	case Pass:
		value = "Passed"
	case Resolved:
		value = "Resolved"
	case Warning:
		value = "Warning"
	case Critical:
		value = "Critical"
	}

	return value
}

func (s *Slack) parseOwners(owners []string) string {
	parsedOwners := []string{}
	for _, owner := range owners {
		if owner != "@here" {
			owner = fmt.Sprintf("@%s", owner)
		}
		parsedOwners = append(parsedOwners, owner)
	}

	return strings.Join(parsedOwners, ", ")
}
<commit_msg>Sending in right slack alert suffix on Resolved vs Pass<commit_after>package main

import (
	"fmt"
	"strings"

	maps "github.com/ashwanthkumar/golang-utils/maps"
	"github.com/ashwanthkumar/slack-go-webhook"
)

type Slack struct {
	Webhook string
	Channel string
	Owners  string
}

func (s *Slack) Name() string {
	return "slack"
}

func (s *Slack) Notify(check AppCheck) {
	attachment := slack.Attachment{
		Text:  &check.Message,
		Color: s.resultToColor(check.Result),
	}
	attachment.
		AddField(slack.Field{Title: "App", Value: check.App, Short: true}).
		AddField(slack.Field{Title: "Check", Value: check.CheckName, Short: true}).
		AddField(slack.Field{Title: "Result", Value: s.resultToString(check.Result), Short: true}).
		AddField(slack.Field{Title: "Times", Value: fmt.Sprintf("%d", check.Times), Short: true})

	destination := maps.GetString(check.Labels, "alerts.slack.channel", s.Channel)

	appSpecificOwners := maps.GetString(check.Labels, "alerts.slack.owners", s.Owners)
	var owners []string
	if appSpecificOwners != "" {
		owners = strings.Split(appSpecificOwners, ",")
	} else {
		owners = []string{"@here"}
	}

	alertSuffix := "Please check!"
	if check.Result == Resolved {
		alertSuffix = "Check Resolved, thanks!"
	} else if check.Result == Pass {
		alertSuffix = "Check Passed"
	}
	mainText := fmt.Sprintf("%s, %s", s.parseOwners(owners), alertSuffix)

	payload := slack.Payload(mainText,
		"marathon-alerts",
		"",
		destination,
		[]slack.Attachment{attachment})

	webhooks := strings.Split(maps.GetString(check.Labels, "alerts.slack.webhook", s.Webhook), ",")

	for _, webhook := range webhooks {
		err := slack.Send(webhook, "", payload)
		if err != nil {
			fmt.Printf("Unexpected Error - %v", err)
		}
	}
}

func (s *Slack) resultToColor(result CheckStatus) *string {
	color := "black"
	switch {
	case Pass == result || Resolved == result:
		color = "good"
	case Warning == result:
		color = "warning"
	case Critical == result:
		color = "danger"
	}

	return &color
}

func (s *Slack) resultToString(result CheckStatus) string {
	value := "Unknown"
	switch result {
	case Pass:
		value = "Passed"
	case Resolved:
		value = "Resolved"
	case Warning:
		value = "Warning"
	case Critical:
		value = "Critical"
	}

	return value
}

func (s *Slack) parseOwners(owners []string) string {
	parsedOwners := []string{}
	for _, owner := range owners {
		if owner != "@here" {
			owner = fmt.Sprintf("@%s", owner)
		}
		parsedOwners = append(parsedOwners, owner)
	}

	return strings.Join(parsedOwners, ", ")
}
<|endoftext|>


<commit_before>package main

import (
	"log"
	"os"
	"sync"

	"bazil.org/fuse"
	"bazil.org/fuse/fs"
	"github.com/keybase/client/protocol/go"
	"github.com/keybase/kbfs/libkbfs"
	"golang.org/x/net/context"
)

type FolderList struct {
	fs *FS
	// only accept public folders
	public bool

	mu      sync.Mutex
	folders map[string]*Dir
}

var _ fs.Node = (*FolderList)(nil)

// Attr implements the fs.Node interface.
func (*FolderList) Attr(ctx context.Context, a *fuse.Attr) error {
	a.Mode = os.ModeDir | 0755
	return nil
}

var _ fs.NodeRequestLookuper = (*FolderList)(nil)

// Lookup implements the fs.NodeRequestLookuper interface.
func (fl *FolderList) Lookup(ctx context.Context, req *fuse.LookupRequest, resp *fuse.LookupResponse) (node fs.Node, err error) {
	defer func() { fl.fs.reportErr(err) }()
	fl.mu.Lock()
	defer fl.mu.Unlock()

	if req.Name == libkbfs.ErrorFile {
		resp.EntryValid = 0
		n := &ErrorFile{
			fs: fl.fs,
		}
		return n, nil
	}

	if child, ok := fl.folders[req.Name]; ok {
		return child, nil
	}

	dh, err := libkbfs.ParseTlfHandle(ctx, fl.fs.config, req.Name)
	if err != nil {
		return nil, err
	}

	if fl.public && !dh.HasPublic() {
		// no public folder exists for this folder
		return nil, fuse.ENOENT
	}

	if canon := dh.ToString(ctx, fl.fs.config); canon != req.Name {
		n := &Alias{
			canon: canon,
		}
		return n, nil
	}

	if fl.public {
		dh = &libkbfs.TlfHandle{
			Writers: dh.Writers,
			Readers: []keybase1.UID{keybase1.PublicUID},
		}
	}

	rootNode, _, err := fl.fs.config.KBFSOps().GetOrCreateRootNodeForHandle(ctx, dh, libkbfs.MasterBranch)
	if err != nil {
		// TODO make errors aware of fuse
		return nil, err
	}

	folderBranch := rootNode.GetFolderBranch()
	folder := &Folder{
		fs:           fl.fs,
		list:         fl,
		name:         req.Name,
		folderBranch: folderBranch,
		nodes:        map[libkbfs.NodeID]fs.Node{},
	}

	// TODO unregister all at unmount
	if err := fl.fs.config.Notifier().RegisterForChanges([]libkbfs.FolderBranch{folderBranch}, folder); err != nil {
		return nil, err
	}

	child := newDir(folder, rootNode)
	folder.nodes[rootNode.GetID()] = child

	fl.folders[req.Name] = child
	return child, nil
}

func (fl *FolderList) forgetFolder(f *Folder) {
	fl.mu.Lock()
	defer fl.mu.Unlock()

	if err := fl.fs.config.Notifier().UnregisterFromChanges([]libkbfs.FolderBranch{f.folderBranch}, f); err != nil {
		log.Printf("cannot unregister change notifier for folder %q: %v", f.name, err)
	}
	delete(fl.folders, f.name)
}

var _ fs.Handle = (*FolderList)(nil)

var _ fs.HandleReadDirAller = (*FolderList)(nil)

func (fl *FolderList) getDirent(ctx context.Context, work <-chan libkbfs.TlfID, results chan<- fuse.Dirent) error {
	for {
		select {
		case tlfID, ok := <-work:
			if !ok {
				return nil
			}
			_, _, dh, err := fl.fs.config.KBFSOps().GetRootNode(ctx,
				libkbfs.FolderBranch{Tlf: tlfID, Branch: libkbfs.MasterBranch})
			if err != nil {
				return err
			}
			name := dh.ToString(ctx, fl.fs.config)
			results <- fuse.Dirent{
				Type: fuse.DT_Dir,
				Name: name,
			}
		case <-ctx.Done():
			return ctx.Err()
		}
	}
}

// ReadDirAll implements the ReadDirAll interface.
func (fl *FolderList) ReadDirAll(ctx context.Context) (res []fuse.Dirent, err error) {
	defer func() { fl.fs.reportErr(err) }()
	favs, err := fl.fs.config.KBFSOps().GetFavorites(ctx)
	if err != nil {
		return nil, err
	}
	work := make(chan libkbfs.TlfID)
	results := make(chan fuse.Dirent)
	errCh := make(chan error, 1)
	const maxWorkers = 10
	var wg sync.WaitGroup
	ctx, cancel := context.WithCancel(ctx)
	defer cancel()
	for i := 0; i < maxWorkers && i < len(favs); i++ {
		wg.Add(1)
		go func() {
			defer wg.Done()
			if err := fl.getDirent(ctx, work, results); err != nil {
				select {
				case errCh <- err:
				default:
				}
			}
		}()
	}

	go func() {
		// feed work
		for _, tlfID := range favs {
			work <- tlfID
		}
		close(work)
		wg.Wait()
		// workers are done
		close(results)
	}()

outer:
	for {
		select {
		case dirent, ok := <-results:
			if !ok {
				break outer
			}
			res = append(res, dirent)
		case err := <-errCh:
			return nil, err
		}
	}
	return res, nil
}
<commit_msg>folderlist: fix lint.<commit_after>package main

import (
	"log"
	"os"
	"sync"

	"bazil.org/fuse"
	"bazil.org/fuse/fs"
	"github.com/keybase/client/protocol/go"
	"github.com/keybase/kbfs/libkbfs"
	"golang.org/x/net/context"
)

// FolderList is a node that can list all of the logged-in user's
// favorite top-level folders, on either a public or private basis.
type FolderList struct {
	fs *FS
	// only accept public folders
	public bool

	mu      sync.Mutex
	folders map[string]*Dir
}

var _ fs.Node = (*FolderList)(nil)

// Attr implements the fs.Node interface.
func (*FolderList) Attr(ctx context.Context, a *fuse.Attr) error {
	a.Mode = os.ModeDir | 0755
	return nil
}

var _ fs.NodeRequestLookuper = (*FolderList)(nil)

// Lookup implements the fs.NodeRequestLookuper interface.
func (fl *FolderList) Lookup(ctx context.Context, req *fuse.LookupRequest, resp *fuse.LookupResponse) (node fs.Node, err error) {
	defer func() { fl.fs.reportErr(err) }()
	fl.mu.Lock()
	defer fl.mu.Unlock()

	if req.Name == libkbfs.ErrorFile {
		resp.EntryValid = 0
		n := &ErrorFile{
			fs: fl.fs,
		}
		return n, nil
	}

	if child, ok := fl.folders[req.Name]; ok {
		return child, nil
	}

	dh, err := libkbfs.ParseTlfHandle(ctx, fl.fs.config, req.Name)
	if err != nil {
		return nil, err
	}

	if fl.public && !dh.HasPublic() {
		// no public folder exists for this folder
		return nil, fuse.ENOENT
	}

	if canon := dh.ToString(ctx, fl.fs.config); canon != req.Name {
		n := &Alias{
			canon: canon,
		}
		return n, nil
	}

	if fl.public {
		dh = &libkbfs.TlfHandle{
			Writers: dh.Writers,
			Readers: []keybase1.UID{keybase1.PublicUID},
		}
	}

	rootNode, _, err := fl.fs.config.KBFSOps().GetOrCreateRootNodeForHandle(ctx, dh, libkbfs.MasterBranch)
	if err != nil {
		// TODO make errors aware of fuse
		return nil, err
	}

	folderBranch := rootNode.GetFolderBranch()
	folder := &Folder{
		fs:           fl.fs,
		list:         fl,
		name:         req.Name,
		folderBranch: folderBranch,
		nodes:        map[libkbfs.NodeID]fs.Node{},
	}

	// TODO unregister all at unmount
	if err := fl.fs.config.Notifier().RegisterForChanges([]libkbfs.FolderBranch{folderBranch}, folder); err != nil {
		return nil, err
	}

	child := newDir(folder, rootNode)
	folder.nodes[rootNode.GetID()] = child

	fl.folders[req.Name] = child
	return child, nil
}

func (fl *FolderList) forgetFolder(f *Folder) {
	fl.mu.Lock()
	defer fl.mu.Unlock()

	if err := fl.fs.config.Notifier().UnregisterFromChanges([]libkbfs.FolderBranch{f.folderBranch}, f); err != nil {
		log.Printf("cannot unregister change notifier for folder %q: %v", f.name, err)
	}
	delete(fl.folders, f.name)
}

var _ fs.Handle = (*FolderList)(nil)

var _ fs.HandleReadDirAller = (*FolderList)(nil)

func (fl *FolderList) getDirent(ctx context.Context, work <-chan libkbfs.TlfID, results chan<- fuse.Dirent) error {
	for {
		select {
		case tlfID, ok := <-work:
			if !ok {
				return nil
			}
			_, _, dh, err := fl.fs.config.KBFSOps().GetRootNode(ctx,
				libkbfs.FolderBranch{Tlf: tlfID, Branch: libkbfs.MasterBranch})
			if err != nil {
				return err
			}
			name := dh.ToString(ctx, fl.fs.config)
			results <- fuse.Dirent{
				Type: fuse.DT_Dir,
				Name: name,
			}
		case <-ctx.Done():
			return ctx.Err()
		}
	}
}

// ReadDirAll implements the ReadDirAll interface.
func (fl *FolderList) ReadDirAll(ctx context.Context) (res []fuse.Dirent, err error) {
	defer func() { fl.fs.reportErr(err) }()
	favs, err := fl.fs.config.KBFSOps().GetFavorites(ctx)
	if err != nil {
		return nil, err
	}
	work := make(chan libkbfs.TlfID)
	results := make(chan fuse.Dirent)
	errCh := make(chan error, 1)
	const maxWorkers = 10
	var wg sync.WaitGroup
	ctx, cancel := context.WithCancel(ctx)
	defer cancel()
	for i := 0; i < maxWorkers && i < len(favs); i++ {
		wg.Add(1)
		go func() {
			defer wg.Done()
			if err := fl.getDirent(ctx, work, results); err != nil {
				select {
				case errCh <- err:
				default:
				}
			}
		}()
	}

	go func() {
		// feed work
		for _, tlfID := range favs {
			work <- tlfID
		}
		close(work)
		wg.Wait()
		// workers are done
		close(results)
	}()

outer:
	for {
		select {
		case dirent, ok := <-results:
			if !ok {
				break outer
			}
			res = append(res, dirent)
		case err := <-errCh:
			return nil, err
		}
	}
	return res, nil
}
<|endoftext|>