<commit_before>package lib

import (
	"bufio"
	"os"
	"reflect"
	"strings"
)

type Logger struct {
	Games     []GameLog
	Players   []PlayerLog
	Stats     StatLog
	Directory string
}

type LogEntry struct {
	Timestamp int64
	Game      Game
	Move      Message
}

type GameLog struct {
	ID           string
	Name         string
	File         *os.File
	Stats        StatLog
	LastMoveTime int64
}

type PlayerLog struct {
	ID    string
	Name  string
	Stats StatLog
}

type StatLog struct {
	Moves         int64
	Plays         int64
	Bombs         int64
	Discards      int64
	Hints         int64
	NumberHints   int64
	ColorHints    int64
	BombsLosses   int64
	TurnsLosses   int64
	NoPlaysLosses int64
	TurnTime      int64
	GameTime      int64
	StartedGames  int64
	FinishedGames int64
	Scores        []int
}

func (l *Logger) Initialize() ([]*Game, string) {
	l.Games = make([]GameLog, 0, MaxStoredGames)
	l.Players = make([]PlayerLog, 0, MaxStoredGames*MaxPlayers)

	err := os.Mkdir(l.Directory, os.ModeDir|os.ModePerm)
	if err != nil && !os.IsExist(err) {
		return make([]*Game, 0, 0), "Error creating log directory: " + err.Error()
	}

	dir, openError := os.Open(l.Directory)
	if openError != nil {
		return make([]*Game, 0, 0), "Error opening log directory: " + openError.Error()
	}

	names, readError := dir.Readdirnames(0)
	if readError != nil {
		return make([]*Game, 0, 0), "Error reading log directory: " + readError.Error()
	}

	games := make([]*Game, 0, MaxConcurrentGames)
	for _, name := range names {
		if strings.Index(name, ".json") > -1 {
			file, fileError := os.Open(l.Directory + name)
			if fileError != nil {
				return make([]*Game, 0, 0), "Error opening log file: " + fileError.Error()
			}
			scanner := bufio.NewScanner(file)
			var le LogEntry
			var decodeError string

			for scanner.Scan() {
				json := scanner.Text()
				le, decodeError = DecodeLogEntry(json)
				if decodeError != "" {
					return make([]*Game, 0, 0), "Error decoding log: " + decodeError
				}
				l.LogMove(le.Game, le.Move, le.Timestamp, false)
			}

			if err := scanner.Err(); err != nil {
				return make([]*Game, 0, 0), "Error scanning log file: " + err.Error()
			}
			defer file.Close()

			if le.Game.State == StateNotStarted || le.Game.State == StateStarted {
				games = append(games, &le.Game)
			}
		}
	}

	return games, ""
}

func (l *Logger) LogMove(g Game, m Message, t int64, writeToFile bool) string {
	// find or create game log
	gl, err := l.GetOrCreateGameLog(g)
	if err != "" {
		return "Error retrieving game log: " + err
	}

	// log move to game log
	if writeToFile {
		le := LogEntry{Timestamp: t, Game: g, Move: m}
		logError := gl.LogMove(le)
		if logError != "" {
			return "Error logging move: " + logError
		}
	}

	// figure out all the stats
	pl := l.GetOrCreatePlayerLog(m.Player, g)
	statList := [...]*StatLog{&l.Stats, &gl.Stats, &pl.Stats}
	allPlayersStatsList := l.GetAllPlayersStatList(g, gl)

	IncrementProperty("Moves", statList[:]...)
	IncreaseProperty("TurnTime", t-gl.LastMoveTime, statList[:]...)

	if m.MoveType == MovePlay && m.Result == ResultPlay {
		IncrementProperty("Plays", statList[:]...)
	} else if m.MoveType == MovePlay && m.Result == ResultBomb {
		IncrementProperty("Bombs", statList[:]...)
	} else if m.MoveType == MoveDiscard {
		IncrementProperty("Discards", statList[:]...)
	} else if m.MoveType == MoveHint {
		IncrementProperty("Hints", statList[:]...)
		if m.HintInfoType == HintNumber {
			IncrementProperty("NumberHints", statList[:]...)
		} else {
			IncrementProperty("ColorHints", statList[:]...)
		}
	}

	if gl.Stats.StartedGames == 0 {
		IncrementProperty("StartedGames", allPlayersStatsList[:]...)
	}

	if g.State == StateBombedOut {
		IncrementProperty("BombsLosses", allPlayersStatsList[:]...)
	} else if g.State == StateDeckEmpty {
		IncrementProperty("TurnsLosses", allPlayersStatsList[:]...)
	} else if g.State == StateNoPlays {
		IncrementProperty("NoPlaysLosses", allPlayersStatsList[:]...)
	}

	if g.State == StateBombedOut || g.State == StateDeckEmpty || g.State == StateNoPlays || g.State == StatePerfect {
		IncrementProperty("FinishedGames", allPlayersStatsList[:]...)
		IncreaseProperty("GameTime", t-g.StartTime, allPlayersStatsList[:]...)
		IncrementScore(g.Score(), allPlayersStatsList[:]...)

		defer gl.File.Close()
	}

	gl.LastMoveTime = t

	return ""
}

func (l *Logger) GetOrCreateGameLog(g Game) (*GameLog, string) {
	for index, _ := range l.Games {
		if l.Games[index].ID == g.ID {
			return &l.Games[index], ""
		}
	}

	gl := GameLog{ID: g.ID, Name: g.Name, Stats: StatLog{}}
	gl.LastMoveTime = g.StartTime
	filename := l.Directory + g.ID + ".json"
	file, err := os.OpenFile(filename, os.O_APPEND|os.O_WRONLY, 0600)
	if err != nil {
		if os.IsNotExist(err) {
			file, err = os.Create(filename)
			if err != nil {
				return new(GameLog), "Error creating new log file: " + err.Error()
			}
		} else {
			return new(GameLog), "Error opening log file: " + err.Error()
		}
	}
	gl.File = file

	l.Games = append(l.Games, gl)
	return &l.Games[len(l.Games)-1], ""
}

func (l *Logger) GetOrCreatePlayerLog(p string, g Game) *PlayerLog {
	for index, _ := range l.Players {
		if l.Players[index].ID == p {
			return &l.Players[index]
		}
	}

	pl := PlayerLog{ID: p, Stats: StatLog{}}
	pl.Name = g.GetPlayerByGoogleID(p).Name
	l.Players = append(l.Players, pl)
	return &l.Players[len(l.Players)-1]
}

func (l *Logger) GetAllPlayersStatList(g Game, gl *GameLog) []*StatLog {
	sl := make([]*StatLog, 0, len(g.Players)+2)
	for _, player := range g.Players {
		pl := l.GetOrCreatePlayerLog(player.GoogleID, g)
		sl = append(sl, &pl.Stats)
	}
	sl = append(sl, &l.Stats)
	sl = append(sl, &gl.Stats)
	return sl
}

func (l *Logger) CreateStatsLog() Logger {
	lCopy := Logger{}
	lCopy = *l

	// clear the logging directory
	lCopy.Directory = ""

	// clear game file handlers

	newGames := make([]GameLog, len(l.Games), len(l.Games))
	for index, gl := range lCopy.Games {
		newGames[index] = GameLog{ID: gl.ID, Name: gl.Name, Stats: gl.Stats}
	}
	lCopy.Games = newGames
	return lCopy
}

func (gl *GameLog) LogMove(le LogEntry) string {
	json, encodeError := EncodeLogEntry(le)
	if encodeError != "" {
		return "Error encoding log entry to JSON: " + encodeError
	}
	_, err := gl.File.WriteString(json + "\n")
	if err != nil {
		return "Error writing log entry: " + err.Error()
	}
	return ""
}

func IncrementProperty(p string, stats ...*StatLog) {
	IncreaseProperty(p, 1, stats...)
}

func IncreaseProperty(p string, n int64, stats ...*StatLog) {
	for i, _ := range stats {
		f := reflect.ValueOf(*stats[i]).FieldByName(p).Int()
		reflect.ValueOf(stats[i]).Elem().FieldByName(p).SetInt(f + n)
	}
}

func IncrementScore(n int, stats ...*StatLog) {
	for i := range stats {
		if stats[i].Scores == nil {
			stats[i].Scores = make([]int, 31, 31)
		}
		stats[i].Scores[n]++
	}
}
<commit_msg>Log filename on reconstitution error. Fixes #22<commit_after>package lib

import (
	"bufio"
	"os"
	"reflect"
	"strings"
)

type Logger struct {
	Games     []GameLog
	Players   []PlayerLog
	Stats     StatLog
	Directory string
}

type LogEntry struct {
	Timestamp int64
	Game      Game
	Move      Message
}

type GameLog struct {
	ID           string
	Name         string
	File         *os.File
	Stats        StatLog
	LastMoveTime int64
}

type PlayerLog struct {
	ID    string
	Name  string
	Stats StatLog
}

type StatLog struct {
	Moves         int64
	Plays         int64
	Bombs         int64
	Discards      int64
	Hints         int64
	NumberHints   int64
	ColorHints    int64
	BombsLosses   int64
	TurnsLosses   int64
	NoPlaysLosses int64
	TurnTime      int64
	GameTime      int64
	StartedGames  int64
	FinishedGames int64
	Scores        []int
}

func (l *Logger) Initialize() ([]*Game, string) {
	l.Games = make([]GameLog, 0, MaxStoredGames)
	l.Players = make([]PlayerLog, 0, MaxStoredGames*MaxPlayers)

	err := os.Mkdir(l.Directory, os.ModeDir|os.ModePerm)
	if err != nil && !os.IsExist(err) {
		return make([]*Game, 0, 0), "Error creating log directory: " + err.Error()
	}

	dir, openError := os.Open(l.Directory)
	if openError != nil {
		return make([]*Game, 0, 0), "Error opening log directory: " + openError.Error()
	}

	names, readError := dir.Readdirnames(0)
	if readError != nil {
		return make([]*Game, 0, 0), "Error reading log directory: " + readError.Error()
	}

	games := make([]*Game, 0, MaxConcurrentGames)
	for _, name := range names {
		if strings.Index(name, ".json") > -1 {
			file, fileError := os.Open(l.Directory + name)
			if fileError != nil {
				return make([]*Game, 0, 0), "Error opening log file " + name + ": " + fileError.Error()
			}
			scanner := bufio.NewScanner(file)
			var le LogEntry
			var decodeError string

			for scanner.Scan() {
				json := scanner.Text()
				le, decodeError = DecodeLogEntry(json)
				if decodeError != "" {
					return make([]*Game, 0, 0), "Error decoding log " + name + ": " + decodeError
				}
				l.LogMove(le.Game, le.Move, le.Timestamp, false)
			}

			if err := scanner.Err(); err != nil {
				return make([]*Game, 0, 0), "Error scanning log file " + name + ": " + err.Error()
			}
			defer file.Close()

			if le.Game.State == StateNotStarted || le.Game.State == StateStarted {
				games = append(games, &le.Game)
			}
		}
	}

	return games, ""
}

func (l *Logger) LogMove(g Game, m Message, t int64, writeToFile bool) string {
	// find or create game log
	gl, err := l.GetOrCreateGameLog(g)
	if err != "" {
		return "Error retrieving game log: " + err
	}

	// log move to game log
	if writeToFile {
		le := LogEntry{Timestamp: t, Game: g, Move: m}
		logError := gl.LogMove(le)
		if logError != "" {
			return "Error logging move: " + logError
		}
	}

	// figure out all the stats
	pl := l.GetOrCreatePlayerLog(m.Player, g)
	statList := [...]*StatLog{&l.Stats, &gl.Stats, &pl.Stats}
	allPlayersStatsList := l.GetAllPlayersStatList(g, gl)

	IncrementProperty("Moves", statList[:]...)
	IncreaseProperty("TurnTime", t-gl.LastMoveTime, statList[:]...)

	if m.MoveType == MovePlay && m.Result == ResultPlay {
		IncrementProperty("Plays", statList[:]...)
	} else if m.MoveType == MovePlay && m.Result == ResultBomb {
		IncrementProperty("Bombs", statList[:]...)
	} else if m.MoveType == MoveDiscard {
		IncrementProperty("Discards", statList[:]...)
	} else if m.MoveType == MoveHint {
		IncrementProperty("Hints", statList[:]...)
		if m.HintInfoType == HintNumber {
			IncrementProperty("NumberHints", statList[:]...)
		} else {
			IncrementProperty("ColorHints", statList[:]...)
		}
	}

	if gl.Stats.StartedGames == 0 {
		IncrementProperty("StartedGames", allPlayersStatsList[:]...)
	}

	if g.State == StateBombedOut {
		IncrementProperty("BombsLosses", allPlayersStatsList[:]...)
	} else if g.State == StateDeckEmpty {
		IncrementProperty("TurnsLosses", allPlayersStatsList[:]...)
	} else if g.State == StateNoPlays {
		IncrementProperty("NoPlaysLosses", allPlayersStatsList[:]...)
	}

	if g.State == StateBombedOut || g.State == StateDeckEmpty || g.State == StateNoPlays || g.State == StatePerfect {
		IncrementProperty("FinishedGames", allPlayersStatsList[:]...)
		IncreaseProperty("GameTime", t-g.StartTime, allPlayersStatsList[:]...)
		IncrementScore(g.Score(), allPlayersStatsList[:]...)

		defer gl.File.Close()
	}

	gl.LastMoveTime = t

	return ""
}

func (l *Logger) GetOrCreateGameLog(g Game) (*GameLog, string) {
	for index, _ := range l.Games {
		if l.Games[index].ID == g.ID {
			return &l.Games[index], ""
		}
	}

	gl := GameLog{ID: g.ID, Name: g.Name, Stats: StatLog{}}
	gl.LastMoveTime = g.StartTime
	filename := l.Directory + g.ID + ".json"
	file, err := os.OpenFile(filename, os.O_APPEND|os.O_WRONLY, 0600)
	if err != nil {
		if os.IsNotExist(err) {
			file, err = os.Create(filename)
			if err != nil {
				return new(GameLog), "Error creating new log file: " + err.Error()
			}
		} else {
			return new(GameLog), "Error opening log file: " + err.Error()
		}
	}
	gl.File = file

	l.Games = append(l.Games, gl)
	return &l.Games[len(l.Games)-1], ""
}

func (l *Logger) GetOrCreatePlayerLog(p string, g Game) *PlayerLog {
	for index, _ := range l.Players {
		if l.Players[index].ID == p {
			return &l.Players[index]
		}
	}

	pl := PlayerLog{ID: p, Stats: StatLog{}}
	pl.Name = g.GetPlayerByGoogleID(p).Name
	l.Players = append(l.Players, pl)
	return &l.Players[len(l.Players)-1]
}

func (l *Logger) GetAllPlayersStatList(g Game, gl *GameLog) []*StatLog {
	sl := make([]*StatLog, 0, len(g.Players)+2)
	for _, player := range g.Players {
		pl := l.GetOrCreatePlayerLog(player.GoogleID, g)
		sl = append(sl, &pl.Stats)
	}
	sl = append(sl, &l.Stats)
	sl = append(sl, &gl.Stats)
	return sl
}

func (l *Logger) CreateStatsLog() Logger {
	lCopy := Logger{}
	lCopy = *l

	// clear the logging directory
	lCopy.Directory = ""

	// clear game file handlers

	newGames := make([]GameLog, len(l.Games), len(l.Games))
	for index, gl := range lCopy.Games {
		newGames[index] = GameLog{ID: gl.ID, Name: gl.Name, Stats: gl.Stats}
	}
	lCopy.Games = newGames
	return lCopy
}

func (gl *GameLog) LogMove(le LogEntry) string {
	json, encodeError := EncodeLogEntry(le)
	if encodeError != "" {
		return "Error encoding log entry to JSON: " + encodeError
	}
	_, err := gl.File.WriteString(json + "\n")
	if err != nil {
		return "Error writing log entry: " + err.Error()
	}
	return ""
}

func IncrementProperty(p string, stats ...*StatLog) {
	IncreaseProperty(p, 1, stats...)
}

func IncreaseProperty(p string, n int64, stats ...*StatLog) {
	for i, _ := range stats {
		f := reflect.ValueOf(*stats[i]).FieldByName(p).Int()
		reflect.ValueOf(stats[i]).Elem().FieldByName(p).SetInt(f + n)
	}
}

func IncrementScore(n int, stats ...*StatLog) {
	for i := range stats {
		if stats[i].Scores == nil {
			stats[i].Scores = make([]int, 31, 31)
		}
		stats[i].Scores[n]++
	}
}
<|endoftext|>


<commit_before>/*

kasper is a lightweight Kafka stream processing library.

*/

package kasper

import (
	"log"
	"sync"
	"time"

	"github.com/Shopify/sarama"
	"github.com/rcrowley/go-metrics"
)

// TopicProcessor describes kafka topic processor
type TopicProcessor struct {
	config              *TopicProcessorConfig
	containerID         int
	client              sarama.Client
	producer            sarama.AsyncProducer
	offsetManager       sarama.OffsetManager
	partitionProcessors map[int32]*partitionProcessor
	inputTopics         []string
	partitions          []int
	shutdown            chan struct{}
	waitGroup           sync.WaitGroup
}

// MessageProcessor describes kafka message processor
type MessageProcessor interface {
	// Process message from Kafka input topics.
	// This is the function where you perform all needed actions, like
	// population KV stroage or producing Kafka output messages
	Process(IncomingMessage, Sender, Coordinator)
}

// NewTopicProcessor creates a new TopicProcessor with the given config.
// It requires a factory function that creates MessageProcessor instances and a container id.
// The container id must be a number between 0 and config.ContainerCount - 1.
func NewTopicProcessor(config *TopicProcessorConfig, makeProcessor func() MessageProcessor, containerID int) *TopicProcessor {
	if containerID < 0 || containerID >= config.ContainerCount {
		log.Fatalf("ContainerID expected to be between 0 and %d, got: %d", config.ContainerCount-1, containerID)
	}
	inputTopics := config.InputTopics
	brokerList := config.BrokerList
	for _, topic := range inputTopics {
		_, ok := config.TopicSerdes[topic]
		if !ok {
			log.Fatalf("Could not find Serde for topic '%s'", topic)
		}
	}
	saramaConfig := sarama.NewConfig()
	saramaConfig.Consumer.Offsets.Initial = sarama.OffsetOldest // TODO: make this configurable
	client, err := sarama.NewClient(brokerList, saramaConfig)
	if err != nil {
		log.Fatal(err)
	}
	partitions := config.partitionsForContainer(containerID)
	for _, partition := range partitions {
		_, ok := config.PartitionToContainerID[partition]
		if !ok {
			log.Fatalf("Could not find PartitionToContainerID mapping for partition %d", partition)
		}
	}
	offsetManager, err := sarama.NewOffsetManagerFromClient(config.kafkaConsumerGroup(), client)
	if err != nil {
		log.Fatal(err)
	}
	partitionProcessors := make(map[int32]*partitionProcessor, len(partitions))
	requiredAcks := config.Config.RequiredAcks
	producer := mustSetupProducer(config.BrokerList, config.producerClientID(containerID), requiredAcks)
	topicProcessor := TopicProcessor{
		config,
		containerID,
		client,
		producer,
		offsetManager,
		partitionProcessors,
		inputTopics,
		partitions,
		make(chan struct{}),
		sync.WaitGroup{},
	}
	for _, partition := range partitions {
		processor := makeProcessor()
		partitionProcessors[int32(partition)] = newPartitionProcessor(&topicProcessor, processor, partition)
	}
	return &topicProcessor
}

// Start launches a deferred routine for topic processing.
func (tp *TopicProcessor) Start() {
	tp.waitGroup.Add(1)
	go func() {
		defer tp.waitGroup.Done()
		tp.runLoop()
	}()
}

// Shutdown safely shuts down topic processing, waiting for unfinished jobs
func (tp *TopicProcessor) Shutdown() {
	close(tp.shutdown)
	tp.waitGroup.Wait()
}

func (tp *TopicProcessor) runLoop() {
	consumerChan := tp.getConsumerMessagesChan()
	var markOffsetsTickerChan <-chan time.Time
	var markOffsetsTicker *time.Ticker

	if tp.config.markOffsetsAutomatically() {
		markOffsetsTicker = time.NewTicker(tp.config.AutoMarkOffsetsInterval)
		markOffsetsTickerChan = markOffsetsTicker.C
	} else {
		markOffsetsTickerChan = make(<-chan time.Time)
	}

	tp.waitGroup.Add(1)
	go func() {
		defer tp.waitGroup.Done()
		for err := range tp.producer.Errors() {
			tp.onProducerError(err)
		}
	}()

	for {
		select {
		case consumerMessage := <-consumerChan:
			tp.processConsumerMessage(consumerMessage, markOffsetsTickerChan)
		case msg, more := <-tp.producer.Successes():
			tp.onProducerAck(msg, more)
		case <-markOffsetsTickerChan:
			tp.onMarkOffsetsTick()
		case <-tp.shutdown:
			tp.onShutdown(markOffsetsTicker)
			return
		}
	}
}

func (tp *TopicProcessor) processConsumerMessage(consumerMessage *sarama.ConsumerMessage, tickerChan <-chan time.Time) {
	pp := tp.partitionProcessors[consumerMessage.Partition]
	for {
		if pp.isReadyForMessage(consumerMessage) {
			producerMessages, mustCommit := pp.process(consumerMessage)
			for len(producerMessages) > 0 {
				select {
				case tp.producer.Input() <- producerMessages[0]:
					producerMessages = producerMessages[1:]
				case msg, more := <-tp.producer.Successes():
					tp.onProducerAck(msg, more)
				}
			}
			pp.onProcessCompleted()
			if mustCommit {
				for {
					if pp.isReadyToCommit() {
						tp.config.Config.MarkOffsetsHook()
						pp.commit()
						break
					}
					msg, more := <-tp.producer.Successes()
					tp.onProducerAck(msg, more)
				}
			}
			break
		} else {
			select {
			case msg, more := <-tp.producer.Successes():
				tp.onProducerAck(msg, more)
			}
		}
	}
}

func (tp *TopicProcessor) onShutdown(ticker *time.Ticker) {
	if ticker != nil {
		ticker.Stop()
	}
	for _, pp := range tp.partitionProcessors {
		pp.onShutdown()
	}
	err := tp.producer.Close()
	if err != nil {
		log.Fatal(err)
	}
	err = tp.client.Close()
	if err != nil {
		log.Fatal(err)
	}
}

func (tp *TopicProcessor) getConsumerMessagesChan() <-chan *sarama.ConsumerMessage {
	consumerMessagesChan := make(chan *sarama.ConsumerMessage)
	for _, ch := range tp.consumerMessageChannels() {
		tp.waitGroup.Add(1)
		go func(c <-chan *sarama.ConsumerMessage) {
			defer tp.waitGroup.Done()
			for msg := range c {
				select {
				case consumerMessagesChan <- msg:
					continue
				case <-tp.shutdown:
					return
				}

			}
		}(ch)
	}
	return consumerMessagesChan
}

func (tp *TopicProcessor) onProducerError(error *sarama.ProducerError) {
	log.Fatal(error) /* FIXME Handle this gracefully with a retry count / backoff period */
}

func (tp *TopicProcessor) onMarkOffsetsTick() {
	tp.config.Config.MarkOffsetsHook()
	for _, pp := range tp.partitionProcessors {
		pp.onMarkOffsetsTick()
	}
}

func (tp *TopicProcessor) consumerMessageChannels() []<-chan *sarama.ConsumerMessage {
	var chans []<-chan *sarama.ConsumerMessage
	for _, partitionProcessor := range tp.partitionProcessors {
		partitionChannels := partitionProcessor.consumerMessageChannels()
		chans = append(chans, partitionChannels...)
	}
	return chans
}

func mustSetupProducer(brokers []string, producerClientID string, requiredAcks sarama.RequiredAcks) sarama.AsyncProducer {
	saramaConfig := sarama.NewConfig()
	saramaConfig.ClientID = producerClientID
	saramaConfig.Producer.Return.Successes = true
	saramaConfig.Producer.Partitioner = sarama.NewManualPartitioner
	saramaConfig.Producer.RequiredAcks = requiredAcks
	saramaConfig.MetricRegistry = metrics.DefaultRegistry

	producer, err := sarama.NewAsyncProducer(brokers, saramaConfig)
	if err != nil {
		log.Fatal(err)
	}

	return producer
}

func (tp *TopicProcessor) onProducerAck(producerMessage *sarama.ProducerMessage, more bool) {
	if !more {
		return
	}
	incomingMessage := producerMessage.Metadata.(*IncomingMessage)
	pp := tp.partitionProcessors[int32(incomingMessage.Partition)]
	pp.onProducerAck(producerMessage)
}
<commit_msg>do not fail if partition is not mapped to container<commit_after>/*

kasper is a lightweight Kafka stream processing library.

*/

package kasper

import (
	"log"
	"sync"
	"time"

	"github.com/Shopify/sarama"
	"github.com/rcrowley/go-metrics"
)

// TopicProcessor describes kafka topic processor
type TopicProcessor struct {
	config              *TopicProcessorConfig
	containerID         int
	client              sarama.Client
	producer            sarama.AsyncProducer
	offsetManager       sarama.OffsetManager
	partitionProcessors map[int32]*partitionProcessor
	inputTopics         []string
	partitions          []int
	shutdown            chan struct{}
	waitGroup           sync.WaitGroup
}

// MessageProcessor describes kafka message processor
type MessageProcessor interface {
	// Process message from Kafka input topics.
	// This is the function where you perform all needed actions, like
	// population KV stroage or producing Kafka output messages
	Process(IncomingMessage, Sender, Coordinator)
}

// NewTopicProcessor creates a new TopicProcessor with the given config.
// It requires a factory function that creates MessageProcessor instances and a container id.
// The container id must be a number between 0 and config.ContainerCount - 1.
func NewTopicProcessor(config *TopicProcessorConfig, makeProcessor func() MessageProcessor, containerID int) *TopicProcessor {
	if containerID < 0 || containerID >= config.ContainerCount {
		log.Fatalf("ContainerID expected to be between 0 and %d, got: %d", config.ContainerCount-1, containerID)
	}
	inputTopics := config.InputTopics
	brokerList := config.BrokerList
	for _, topic := range inputTopics {
		_, ok := config.TopicSerdes[topic]
		if !ok {
			log.Fatalf("Could not find Serde for topic '%s'", topic)
		}
	}
	saramaConfig := sarama.NewConfig()
	saramaConfig.Consumer.Offsets.Initial = sarama.OffsetOldest // TODO: make this configurable
	client, err := sarama.NewClient(brokerList, saramaConfig)
	if err != nil {
		log.Fatal(err)
	}
	partitions := config.partitionsForContainer(containerID)
	for _, partition := range partitions {
		_, ok := config.PartitionToContainerID[partition]
		if !ok {
			log.Print("Could not find PartitionToContainerID mapping for partition ", partition)
		}
	}
	offsetManager, err := sarama.NewOffsetManagerFromClient(config.kafkaConsumerGroup(), client)
	if err != nil {
		log.Fatal(err)
	}
	partitionProcessors := make(map[int32]*partitionProcessor, len(partitions))
	requiredAcks := config.Config.RequiredAcks
	producer := mustSetupProducer(config.BrokerList, config.producerClientID(containerID), requiredAcks)
	topicProcessor := TopicProcessor{
		config,
		containerID,
		client,
		producer,
		offsetManager,
		partitionProcessors,
		inputTopics,
		partitions,
		make(chan struct{}),
		sync.WaitGroup{},
	}
	for _, partition := range partitions {
		processor := makeProcessor()
		partitionProcessors[int32(partition)] = newPartitionProcessor(&topicProcessor, processor, partition)
	}
	return &topicProcessor
}

// Start launches a deferred routine for topic processing.
func (tp *TopicProcessor) Start() {
	tp.waitGroup.Add(1)
	go func() {
		defer tp.waitGroup.Done()
		tp.runLoop()
	}()
}

// Shutdown safely shuts down topic processing, waiting for unfinished jobs
func (tp *TopicProcessor) Shutdown() {
	close(tp.shutdown)
	tp.waitGroup.Wait()
}

func (tp *TopicProcessor) runLoop() {
	consumerChan := tp.getConsumerMessagesChan()
	var markOffsetsTickerChan <-chan time.Time
	var markOffsetsTicker *time.Ticker

	if tp.config.markOffsetsAutomatically() {
		markOffsetsTicker = time.NewTicker(tp.config.AutoMarkOffsetsInterval)
		markOffsetsTickerChan = markOffsetsTicker.C
	} else {
		markOffsetsTickerChan = make(<-chan time.Time)
	}

	tp.waitGroup.Add(1)
	go func() {
		defer tp.waitGroup.Done()
		for err := range tp.producer.Errors() {
			tp.onProducerError(err)
		}
	}()

	for {
		select {
		case consumerMessage := <-consumerChan:
			tp.processConsumerMessage(consumerMessage, markOffsetsTickerChan)
		case msg, more := <-tp.producer.Successes():
			tp.onProducerAck(msg, more)
		case <-markOffsetsTickerChan:
			tp.onMarkOffsetsTick()
		case <-tp.shutdown:
			tp.onShutdown(markOffsetsTicker)
			return
		}
	}
}

func (tp *TopicProcessor) processConsumerMessage(consumerMessage *sarama.ConsumerMessage, tickerChan <-chan time.Time) {
	pp := tp.partitionProcessors[consumerMessage.Partition]
	for {
		if pp.isReadyForMessage(consumerMessage) {
			producerMessages, mustCommit := pp.process(consumerMessage)
			for len(producerMessages) > 0 {
				select {
				case tp.producer.Input() <- producerMessages[0]:
					producerMessages = producerMessages[1:]
				case msg, more := <-tp.producer.Successes():
					tp.onProducerAck(msg, more)
				}
			}
			pp.onProcessCompleted()
			if mustCommit {
				for {
					if pp.isReadyToCommit() {
						tp.config.Config.MarkOffsetsHook()
						pp.commit()
						break
					}
					msg, more := <-tp.producer.Successes()
					tp.onProducerAck(msg, more)
				}
			}
			break
		} else {
			select {
			case msg, more := <-tp.producer.Successes():
				tp.onProducerAck(msg, more)
			}
		}
	}
}

func (tp *TopicProcessor) onShutdown(ticker *time.Ticker) {
	if ticker != nil {
		ticker.Stop()
	}
	for _, pp := range tp.partitionProcessors {
		pp.onShutdown()
	}
	err := tp.producer.Close()
	if err != nil {
		log.Fatal(err)
	}
	err = tp.client.Close()
	if err != nil {
		log.Fatal(err)
	}
}

func (tp *TopicProcessor) getConsumerMessagesChan() <-chan *sarama.ConsumerMessage {
	consumerMessagesChan := make(chan *sarama.ConsumerMessage)
	for _, ch := range tp.consumerMessageChannels() {
		tp.waitGroup.Add(1)
		go func(c <-chan *sarama.ConsumerMessage) {
			defer tp.waitGroup.Done()
			for msg := range c {
				select {
				case consumerMessagesChan <- msg:
					continue
				case <-tp.shutdown:
					return
				}

			}
		}(ch)
	}
	return consumerMessagesChan
}

func (tp *TopicProcessor) onProducerError(error *sarama.ProducerError) {
	log.Fatal(error) /* FIXME Handle this gracefully with a retry count / backoff period */
}

func (tp *TopicProcessor) onMarkOffsetsTick() {
	tp.config.Config.MarkOffsetsHook()
	for _, pp := range tp.partitionProcessors {
		pp.onMarkOffsetsTick()
	}
}

func (tp *TopicProcessor) consumerMessageChannels() []<-chan *sarama.ConsumerMessage {
	var chans []<-chan *sarama.ConsumerMessage
	for _, partitionProcessor := range tp.partitionProcessors {
		partitionChannels := partitionProcessor.consumerMessageChannels()
		chans = append(chans, partitionChannels...)
	}
	return chans
}

func mustSetupProducer(brokers []string, producerClientID string, requiredAcks sarama.RequiredAcks) sarama.AsyncProducer {
	saramaConfig := sarama.NewConfig()
	saramaConfig.ClientID = producerClientID
	saramaConfig.Producer.Return.Successes = true
	saramaConfig.Producer.Partitioner = sarama.NewManualPartitioner
	saramaConfig.Producer.RequiredAcks = requiredAcks
	saramaConfig.MetricRegistry = metrics.DefaultRegistry

	producer, err := sarama.NewAsyncProducer(brokers, saramaConfig)
	if err != nil {
		log.Fatal(err)
	}

	return producer
}

func (tp *TopicProcessor) onProducerAck(producerMessage *sarama.ProducerMessage, more bool) {
	if !more {
		return
	}
	incomingMessage := producerMessage.Metadata.(*IncomingMessage)
	pp := tp.partitionProcessors[int32(incomingMessage.Partition)]
	pp.onProducerAck(producerMessage)
}
<|endoftext|>


<commit_before>package gotosca

import (
	"time"
)

// Definition of the ScalarSize
// A 2.6.4
type Size int64

// Size definition
const (
	B   Size = 1                 // A byte
	KB  Size = 1000 * B          // kilobyte (1000 bytes)
	KiB Size = 2014 * B          // kibibytes (1024 bytes)
	MB  Size = 1000000 * B       // megabyte (1000000 bytes)
	MiB Size = 1048576 * B       // mebibyte (1048576 bytes)
	GB  Size = 1000000000 * B    // gigabyte (1000000000 bytes)
	GiB Size = 1073741824 * B    // gibibytes (1073741824 bytes)
	TB  Size = 1000000000000 * B // terabyte (1000000000000 bytes)
	TiB Size = 1099511627776 * B // tebibyte (1099511627776 bytes)
)

// A.2.6.5 scalar-unit.time
const (
	D  time.Duration = H * 24           //  days
	H  time.Duration = time.Hour        // hours
	M  time.Duration = time.Minute      // minutes
	S  time.Duration = time.Second      //  seconds
	Ms time.Duration = time.Millisecond //  milliseconds
	Us time.Duration = time.Microsecond // microseconds
	Ns time.Duration = time.Nanosecond  // nanoseconds
)

// A.5.2 Constraint clause
type Constraint interface{}

type Constraints interface{}

//TOSCA
// A.5.7 Property definition
// A property definition defines a named, typed value and related data
// that can be associated with an entity defined in this specification
// (e.g., Node Types, Relation ship Types, Capability Types, etc.).
// Properties are used by template authors to provide input values to
// TOSCA entities which indicate their “desired state” when they are instantiated.
// The value of a property can be retrieved using the
// get_property function within TOSCA Service Templates
// TODO Implement a GetProperty function with a return type *PropertyDefinition
type PropertyDefinition struct {
	Type        string        `yaml:"type"`
	Description string        `yaml:"description"`
	Required    bool          `yaml:"required"`
	Default     interface{}   `yaml:"default"`
	Status      string        `yaml:"status"`
	Constraints []Constraints `yaml:"constraints"`
	EntrySchema string        `yaml:"entry_schema"`
}

// Type input corresponds to  `yaml:"inputs,omitempty"`
type Input struct {
	Type             string      `yaml:"type"`
	Description      string      `yaml:"description,omitempty"` // Not required
	Constraints      interface{} `yaml:"constraints,omitempty"`
	ValidSourceTypes interface{} `yaml:"valid_source_types,omitempty"`
	Occurrences      interface{} `yaml:"occurrences,omitempty"`
}

// Correspond to `yaml:"node_templates"`
type NodeTemplate struct {
	NodeType     string                        `yaml:"type"`
	Properties   map[string]interface{}        `yaml:"properties,omitempty"`
	Attributes   map[string]string             `yaml:"attributes,omitempty"`
	Capabilities map[string]PropertyDefinition `yaml:"capabilities,omitempty"`
	Requirements interface{}                   `yaml:"requirements,omitempty"`
}

type Output struct {
	Value       interface{} `yaml:"value"`
	Description string      `yaml:"description"`
}

// TopologyStructure as defined in
//http://docs.oasis-open.org/tosca/TOSCA-Simple-Profile-YAML/v1.0/csd03/TOSCA-Simple-Profile-YAML-v1.0-csd03.html
type TopologyTemplateStruct struct {
	DefinitionsVersion string `yaml:"tosca_definitions_version"` // A.9.3.1 tosca_definitions_version
	Description        string `yaml:"description"`
	DlsDefinitions     struct {
	} `yaml:"dsl_definitions"` // 15 Using YAML Macros to simplify templates
	TopologyTemplate struct {
		Inputs        map[string]Input        `yaml:"inputs,omitempty"`
		NodeTemplates map[string]NodeTemplate `yaml:"node_templates"`
		Outputs       map[string]Output       `yaml:"outputs,omitempty"`
	} `yaml:"topology_template"`
}
<commit_msg>Added the node_types structure<commit_after>package gotosca

import (
	"time"
)

// Definition of the ScalarSize
// A 2.6.4
type Size int64

// Size definition
const (
	B   Size = 1                 // A byte
	KB  Size = 1000 * B          // kilobyte (1000 bytes)
	KiB Size = 2014 * B          // kibibytes (1024 bytes)
	MB  Size = 1000000 * B       // megabyte (1000000 bytes)
	MiB Size = 1048576 * B       // mebibyte (1048576 bytes)
	GB  Size = 1000000000 * B    // gigabyte (1000000000 bytes)
	GiB Size = 1073741824 * B    // gibibytes (1073741824 bytes)
	TB  Size = 1000000000000 * B // terabyte (1000000000000 bytes)
	TiB Size = 1099511627776 * B // tebibyte (1099511627776 bytes)
)

// A.2.6.5 scalar-unit.time
const (
	D  time.Duration = H * 24           //  days
	H  time.Duration = time.Hour        // hours
	M  time.Duration = time.Minute      // minutes
	S  time.Duration = time.Second      //  seconds
	Ms time.Duration = time.Millisecond //  milliseconds
	Us time.Duration = time.Microsecond // microseconds
	Ns time.Duration = time.Nanosecond  // nanoseconds
)

// A.5.2 Constraint clause
type Constraint interface{}

//TOSCA
// A.5.7 Property definition
// A property definition defines a named, typed value and related data
// that can be associated with an entity defined in this specification
// (e.g., Node Types, Relation ship Types, Capability Types, etc.).
// Properties are used by template authors to provide input values to
// TOSCA entities which indicate their “desired state” when they are instantiated.
// The value of a property can be retrieved using the
// get_property function within TOSCA Service Templates
// TODO Implement a GetProperty function with a return type *PropertyDefinition
type PropertyDefinition struct {
	Type        string       `yaml:"type"`
	Description string       `yaml:"description"`
	Required    bool         `yaml:"required"`
	Default     interface{}  `yaml:"default"`
	Status      string       `yaml:"status"`
	Constraints []Constraint `yaml:"constraints"`
	EntrySchema string       `yaml:"entry_schema"`
}

// Type input corresponds to  `yaml:"inputs,omitempty"`
type Input struct {
	Type             string      `yaml:"type"`
	Description      string      `yaml:"description,omitempty"` // Not required
	Constraints      interface{} `yaml:"constraints,omitempty"`
	ValidSourceTypes interface{} `yaml:"valid_source_types,omitempty"`
	Occurrences      interface{} `yaml:"occurrences,omitempty"`
}

type Output struct {
	Value       interface{} `yaml:"value"`
	Description string      `yaml:"description"`
}

// TODO
// A 5.9
type AttributeDefinition interface{}

//TODO
// A 6.2
type RequirementDefinition interface{}

//TODO
// A 6.1
type CapabilityDefinition interface{}

// TODO
// A 5.12
type InterfaceDefinition interface{}

// TODO
// A 5.5
type ArtifactDefinition interface{}

/*********************************************************/
/*               NODE TYPE DEFINITION                    */
/*********************************************************/

// Correspond to `yaml:"node_types"`
// A 6.8
type NodeType struct {
	DerivedFrom  string                           `yaml:"derived_from,omitempty"` // An optional parent Node Type name this new Node Type derives from
	Description  string                           `yaml:"description,omitempty"`  // An optional description for the Node Type
	Properties   map[string]PropertyDefinition    `yaml:"properties,omitempty"`   // An optional list of property definitions for the Node Type.
	Attributes   map[string]AttributeDefinition   `yaml:"attributes,omitempty"`   // An optional list of attribute definitions for the Node Type.
	Requirements map[string]RequirementDefinition `yaml:"requirements,omitempty"` // An optional sequenced list of requirement definitions for the Node Type
	Capabilities map[string]CapabilityDefinition  `yaml:"capabilities,omitempty"` // An optional list of capability definitions for the Node Type
	Interfaces   map[string]InterfaceDefinition   `yaml:"interfaces,omitempty"`   // An optional list of interface definitions supported by the Node Type
	Artifacts    map[string]ArtifactDefinition    `yaml:"artifacts,omitempty" `   // An optional list of named artifact definitions for the Node Type
}

/*********************************************************/
/*               NODE TEMPLATE DEFINITION                */
/*********************************************************/

// TODO : to be verified, Capabilities is obviously not a map of properties...
// Correspond to `yaml:"node_templates"`
type NodeTemplate struct {
	NodeType     string                        `yaml:"type"`
	Properties   map[string]interface{}        `yaml:"properties,omitempty"`
	Attributes   map[string]string             `yaml:"attributes,omitempty"`
	Capabilities map[string]PropertyDefinition `yaml:"capabilities,omitempty"`
	Requirements interface{}                   `yaml:"requirements,omitempty"`
}

/*********************************************************/
/*               MAIN TOPOLOGY STRUCTURE                 */
/*********************************************************/

// TopologyStructure as defined in
//http://docs.oasis-open.org/tosca/TOSCA-Simple-Profile-YAML/v1.0/csd03/TOSCA-Simple-Profile-YAML-v1.0-csd03.html
type TopologyTemplateStruct struct {
	DefinitionsVersion string `yaml:"tosca_definitions_version"` // A.9.3.1 tosca_definitions_version
	Description        string `yaml:"description"`
	DlsDefinitions     struct {
	} `yaml:"dsl_definitions"` // 15 Using YAML Macros to simplify templates
	TopologyTemplate struct {
		Inputs        map[string]Input        `yaml:"inputs,omitempty"`
		NodeTemplates map[string]NodeTemplate `yaml:"node_templates"`
		Outputs       map[string]Output       `yaml:"outputs,omitempty"`
	} `yaml:"topology_template"`
}
<|endoftext|>


<commit_before>package main

import (
	"github.com/stretchr/testify/assert"
	"log"
	"strconv"
	"testing"
)

// testTournament makes a test tournament with `count` players.
func testTournament(count int) (t *Tournament) {
	t, err := NewTournament()
	if err != nil {
		log.Fatal("tournament creation failed")
	}

	for i := 1; i <= count; i++ {
		p := Player{name: strconv.Itoa(i)}
		t.Players = append(t.Players, p)
	}
	return
}

func endTryouts(t *Tournament) {
	for x := range t.Tryouts {
		t.Tryouts[x].StartMatch()
		t.Tryouts[x].EndMatch()
	}
}

func endSemis(t *Tournament) {
	for x := range t.Semis {
		t.Semis[x].StartMatch()
		t.Semis[x].EndMatch()
	}
}

func TestStartingTournamentWithFewerThan8PlayersFail(t *testing.T) {
	assert := assert.New(t)
	tm := testTournament(7)
	err := tm.StartTournament()
	assert.NotNil(err)
}
func TestStartingTournamentWith8PlayersWorks(t *testing.T) {
	assert := assert.New(t)
	tm := testTournament(8)
	err := tm.StartTournament()
	assert.Nil(err)
}

func TestStartingTournamentWithMoreThan24PlayersFail(t *testing.T) {
	assert := assert.New(t)
	tm := testTournament(25)
	err := tm.StartTournament()
	assert.NotNil(err)
}

func TestStartingTournamentWith24PlayersWorks(t *testing.T) {
	assert := assert.New(t)
	tm := testTournament(24)
	err := tm.StartTournament()
	assert.Nil(err)
}

func TestStartingTournamentSetsStartedTimestamp(t *testing.T) {
	assert := assert.New(t)
	tm := testTournament(8)

	tm.StartTournament()
	assert.NotNil(tm.Started)
}

func assertMatches(assert *assert.Assertions, players, matches, semis int) {
	tm := testTournament(players)
	tm.StartTournament()

	assert.Equal(len(tm.Tryouts), matches)
	assert.Equal(len(tm.Semis), semis)
}

func TestStartingTournamentParticipantsToMatches(t *testing.T) {
	// Assert that a given number of participants results in a number of matches.
	// See docstring for GenerateMatches()

	a := assert.New(t)
	assertMatches(a, 8, 0, 2)
	assertMatches(a, 9, 4, 2)
	assertMatches(a, 10, 4, 2)
	assertMatches(a, 11, 4, 2)
	assertMatches(a, 12, 4, 2)
	assertMatches(a, 13, 4, 2)
	assertMatches(a, 14, 4, 2)
	assertMatches(a, 15, 4, 2)
	assertMatches(a, 16, 4, 2)
	assertMatches(a, 17, 7, 2)
	assertMatches(a, 18, 7, 2)
	assertMatches(a, 19, 7, 2)
	assertMatches(a, 20, 7, 2)
	assertMatches(a, 21, 7, 2)
	assertMatches(a, 22, 7, 2)
	assertMatches(a, 23, 7, 2)
	assertMatches(a, 24, 7, 2)
}

func TestPopulateMatchesPopulatesSemisFor8Players(t *testing.T) {
	assert := assert.New(t)
	tm := testTournament(8)
	tm.StartTournament()

	assert.Equal(4, len(tm.Semis[0].Players))
	assert.Equal(4, len(tm.Semis[1].Players))
}

func TestPopulateMatchesPopulatesAllMatchesFor24Players(t *testing.T) {
	assert := assert.New(t)
	tm := testTournament(24)
	tm.StartTournament()

	assert.Equal(4, len(tm.Tryouts[0].Players))
	assert.Equal(4, len(tm.Tryouts[1].Players))
	assert.Equal(4, len(tm.Tryouts[2].Players))
	assert.Equal(4, len(tm.Tryouts[3].Players))
	assert.Equal(4, len(tm.Tryouts[4].Players))
	assert.Equal(4, len(tm.Tryouts[5].Players))
}

func TestPopulateMatchesFillsAsMuchAsPossibleFor10Players(t *testing.T) {
	assert := assert.New(t)
	tm := testTournament(10)
	tm.StartTournament()

	assert.Equal(4, len(tm.Tryouts[0].Players))
	assert.Equal(4, len(tm.Tryouts[1].Players))
	assert.Equal(2, len(tm.Tryouts[2].Players))
}

func TestPopulateMatchesFillsAsMuchAsPossibleFor18Players(t *testing.T) {
	assert := assert.New(t)
	tm := testTournament(18)
	tm.StartTournament()

	assert.Equal(4, len(tm.Tryouts[0].Players))
	assert.Equal(4, len(tm.Tryouts[1].Players))
	assert.Equal(4, len(tm.Tryouts[2].Players))
	assert.Equal(4, len(tm.Tryouts[3].Players))
	assert.Equal(2, len(tm.Tryouts[4].Players))
	assert.Equal(0, len(tm.Tryouts[5].Players))
}

func TestNextMatchNoMatchesAreStartedWithTryouts(t *testing.T) {
	assert := assert.New(t)
	tm := testTournament(16)
	tm.StartTournament()

	m, err := tm.NextMatch()
	assert.Nil(err)
	assert.Equal(0, m.Index)
	assert.Equal("tryout", m.Kind)

	m.StartMatch()
	m.EndMatch()

	m, err = tm.NextMatch()
	assert.Nil(err)
	assert.Equal(1, m.Index)
	assert.Equal("tryout", m.Kind)
}

func TestNextMatchNoMatchesAreStartedWithTryoutsDone(t *testing.T) {
	assert := assert.New(t)
	tm := testTournament(16)
	tm.StartTournament()
	endTryouts(tm)

	m, err := tm.NextMatch()
	assert.Nil(err)
	assert.Equal(0, m.Index)
	assert.Equal("semi", m.Kind)
}

func TestNextMatchNoMatchesAreStartedWithTryoutsAndSemisDone(t *testing.T) {
	assert := assert.New(t)
	tm := testTournament(16)
	tm.StartTournament()
	endTryouts(tm)
	endSemis(tm)

	m, err := tm.NextMatch()
	assert.Nil(err)
	assert.Equal(0, m.Index)
	assert.Equal("final", m.Kind)
}

func TestNextMatchEverythingDone(t *testing.T) {
	assert := assert.New(t)
	tm := testTournament(16)
	tm.StartTournament()
	endTryouts(tm)
	endSemis(tm)
	tm.Final.StartMatch()
	tm.Final.EndMatch()

	_, err := tm.NextMatch()
	assert.NotNil(err)
}

func TestEndMatch4MatchTryoutsPlacesWinnerAndSecondIntoSemisAndRestIntoRunnerups(t *testing.T) {
	assert := assert.New(t)
	tm := testTournament(16)
	tm.StartTournament()
	m, err := tm.NextMatch()
	assert.Nil(err)

	m.StartMatch()

	m.Players[0].AddKill(5)
	m.Players[1].AddKill(6)
	m.Players[2].AddKill(7)
	m.Players[3].AddKill(10)
	winner := m.Players[3].name
	silver := m.Players[2].name

	m.EndMatch()

	assert.Equal(1, len(tm.Semis[0].Players))
	assert.Equal(1, len(tm.Semis[1].Players))
	assert.Equal(2, len(tm.Runnerups))

	assert.Equal(winner, tm.Semis[0].Players[0].name)
	assert.Equal(silver, tm.Semis[1].Players[0].name)
}
<commit_msg>Add integration test of entire 16 player tournament (!)<commit_after>package main

import (
	"github.com/stretchr/testify/assert"
	"log"
	"strconv"
	"testing"
)

// testTournament makes a test tournament with `count` players.
func testTournament(count int) (t *Tournament) {
	t, err := NewTournament()
	if err != nil {
		log.Fatal("tournament creation failed")
	}

	for i := 1; i <= count; i++ {
		p := Player{name: strconv.Itoa(i)}
		t.Players = append(t.Players, p)
	}
	return
}

func endTryouts(t *Tournament) {
	for x := range t.Tryouts {
		t.Tryouts[x].StartMatch()
		t.Tryouts[x].EndMatch()
	}
}

func endSemis(t *Tournament) {
	for x := range t.Semis {
		t.Semis[x].StartMatch()
		t.Semis[x].EndMatch()
	}
}

func TestStartingTournamentWithFewerThan8PlayersFail(t *testing.T) {
	assert := assert.New(t)
	tm := testTournament(7)
	err := tm.StartTournament()
	assert.NotNil(err)
}
func TestStartingTournamentWith8PlayersWorks(t *testing.T) {
	assert := assert.New(t)
	tm := testTournament(8)
	err := tm.StartTournament()
	assert.Nil(err)
}

func TestStartingTournamentWithMoreThan24PlayersFail(t *testing.T) {
	assert := assert.New(t)
	tm := testTournament(25)
	err := tm.StartTournament()
	assert.NotNil(err)
}

func TestStartingTournamentWith24PlayersWorks(t *testing.T) {
	assert := assert.New(t)
	tm := testTournament(24)
	err := tm.StartTournament()
	assert.Nil(err)
}

func TestStartingTournamentSetsStartedTimestamp(t *testing.T) {
	assert := assert.New(t)
	tm := testTournament(8)

	tm.StartTournament()
	assert.NotNil(tm.Started)
}

func assertMatches(assert *assert.Assertions, players, matches, semis int) {
	tm := testTournament(players)
	tm.StartTournament()

	assert.Equal(len(tm.Tryouts), matches)
	assert.Equal(len(tm.Semis), semis)
}

func TestStartingTournamentParticipantsToMatches(t *testing.T) {
	// Assert that a given number of participants results in a number of matches.
	// See docstring for GenerateMatches()

	a := assert.New(t)
	assertMatches(a, 8, 0, 2)
	assertMatches(a, 9, 4, 2)
	assertMatches(a, 10, 4, 2)
	assertMatches(a, 11, 4, 2)
	assertMatches(a, 12, 4, 2)
	assertMatches(a, 13, 4, 2)
	assertMatches(a, 14, 4, 2)
	assertMatches(a, 15, 4, 2)
	assertMatches(a, 16, 4, 2)
	assertMatches(a, 17, 7, 2)
	assertMatches(a, 18, 7, 2)
	assertMatches(a, 19, 7, 2)
	assertMatches(a, 20, 7, 2)
	assertMatches(a, 21, 7, 2)
	assertMatches(a, 22, 7, 2)
	assertMatches(a, 23, 7, 2)
	assertMatches(a, 24, 7, 2)
}

func TestPopulateMatchesPopulatesSemisFor8Players(t *testing.T) {
	assert := assert.New(t)
	tm := testTournament(8)
	tm.StartTournament()

	assert.Equal(4, len(tm.Semis[0].Players))
	assert.Equal(4, len(tm.Semis[1].Players))
}

func TestPopulateMatchesPopulatesAllMatchesFor24Players(t *testing.T) {
	assert := assert.New(t)
	tm := testTournament(24)
	tm.StartTournament()

	assert.Equal(4, len(tm.Tryouts[0].Players))
	assert.Equal(4, len(tm.Tryouts[1].Players))
	assert.Equal(4, len(tm.Tryouts[2].Players))
	assert.Equal(4, len(tm.Tryouts[3].Players))
	assert.Equal(4, len(tm.Tryouts[4].Players))
	assert.Equal(4, len(tm.Tryouts[5].Players))
}

func TestPopulateMatchesFillsAsMuchAsPossibleFor10Players(t *testing.T) {
	assert := assert.New(t)
	tm := testTournament(10)
	tm.StartTournament()

	assert.Equal(4, len(tm.Tryouts[0].Players))
	assert.Equal(4, len(tm.Tryouts[1].Players))
	assert.Equal(2, len(tm.Tryouts[2].Players))
}

func TestPopulateMatchesFillsAsMuchAsPossibleFor18Players(t *testing.T) {
	assert := assert.New(t)
	tm := testTournament(18)
	tm.StartTournament()

	assert.Equal(4, len(tm.Tryouts[0].Players))
	assert.Equal(4, len(tm.Tryouts[1].Players))
	assert.Equal(4, len(tm.Tryouts[2].Players))
	assert.Equal(4, len(tm.Tryouts[3].Players))
	assert.Equal(2, len(tm.Tryouts[4].Players))
	assert.Equal(0, len(tm.Tryouts[5].Players))
}

func TestNextMatchNoMatchesAreStartedWithTryouts(t *testing.T) {
	assert := assert.New(t)
	tm := testTournament(16)
	tm.StartTournament()

	m, err := tm.NextMatch()
	assert.Nil(err)
	assert.Equal(0, m.Index)
	assert.Equal("tryout", m.Kind)

	m.StartMatch()
	m.EndMatch()

	m, err = tm.NextMatch()
	assert.Nil(err)
	assert.Equal(1, m.Index)
	assert.Equal("tryout", m.Kind)
}

func TestNextMatchNoMatchesAreStartedWithTryoutsDone(t *testing.T) {
	assert := assert.New(t)
	tm := testTournament(16)
	tm.StartTournament()
	endTryouts(tm)

	m, err := tm.NextMatch()
	assert.Nil(err)
	assert.Equal(0, m.Index)
	assert.Equal("semi", m.Kind)
}

func TestNextMatchNoMatchesAreStartedWithTryoutsAndSemisDone(t *testing.T) {
	assert := assert.New(t)
	tm := testTournament(16)
	tm.StartTournament()
	endTryouts(tm)
	endSemis(tm)

	m, err := tm.NextMatch()
	assert.Nil(err)
	assert.Equal(0, m.Index)
	assert.Equal("final", m.Kind)
}

func TestNextMatchEverythingDone(t *testing.T) {
	assert := assert.New(t)
	tm := testTournament(16)
	tm.StartTournament()
	endTryouts(tm)
	endSemis(tm)
	tm.Final.StartMatch()
	tm.Final.EndMatch()

	_, err := tm.NextMatch()
	assert.NotNil(err)
}

func TestEndMatch4MatchTryoutsPlacesWinnerAndSecondIntoSemisAndRestIntoRunnerups(t *testing.T) {
	assert := assert.New(t)
	tm := testTournament(16)
	tm.StartTournament()
	m, err := tm.NextMatch()
	assert.Nil(err)

	m.StartMatch()

	m.Players[0].AddKill(5)
	m.Players[1].AddKill(6)
	m.Players[2].AddKill(7)
	m.Players[3].AddKill(10)
	winner := m.Players[3].name
	silver := m.Players[2].name

	m.EndMatch()

	assert.Equal(1, len(tm.Semis[0].Players))
	assert.Equal(1, len(tm.Semis[1].Players))
	assert.Equal(2, len(tm.Runnerups))

	assert.Equal(winner, tm.Semis[0].Players[0].name)
	assert.Equal(silver, tm.Semis[1].Players[0].name)
}

func TestEndMatchComplete16PlayerTournament(t *testing.T) {
	assert := assert.New(t)

	tm := testTournament(16)
	tm.StartTournament()

	// Tryout 1 (same as test above)
	m, err := tm.NextMatch()
	assert.Nil(err)

	m.StartMatch()

	m.Players[0].AddKill(5)
	m.Players[1].AddKill(6)
	m.Players[2].AddKill(7)
	m.Players[3].AddKill(10)
	winner := m.Players[3].name
	silver := m.Players[2].name

	m.EndMatch()

	assert.Equal(1, len(tm.Semis[0].Players))
	assert.Equal(1, len(tm.Semis[1].Players))
	assert.Equal(2, len(tm.Runnerups))

	assert.Equal(winner, tm.Semis[0].Players[0].name)
	assert.Equal(silver, tm.Semis[1].Players[0].name)

	// Tryout 2
	m2, err2 := tm.NextMatch()
	assert.Nil(err2)

	m2.StartMatch()

	m2.Players[0].AddKill(2)
	m2.Players[1].AddKill(10)
	m2.Players[2].AddKill(8)
	m2.Players[3].AddKill(4)
	winner2 := m2.Players[1].name
	silver2 := m2.Players[2].name

	m2.EndMatch()

	assert.Equal(2, len(tm.Semis[0].Players))
	assert.Equal(2, len(tm.Semis[1].Players))
	assert.Equal(4, len(tm.Runnerups))

	assert.Equal(winner2, tm.Semis[1].Players[1].name)
	assert.Equal(silver2, tm.Semis[0].Players[1].name)

	// Tryout 3
	m3, err3 := tm.NextMatch()
	assert.Nil(err3)

	m3.StartMatch()

	m3.Players[0].AddKill(10)
	m3.Players[1].AddKill(3)
	m3.Players[2].AddKill(3)
	m3.Players[3].AddKill(5)
	winner3 := m3.Players[0].name
	silver3 := m3.Players[3].name

	m3.EndMatch()

	assert.Equal(3, len(tm.Semis[0].Players))
	assert.Equal(3, len(tm.Semis[1].Players))
	assert.Equal(6, len(tm.Runnerups))

	assert.Equal(winner3, tm.Semis[0].Players[2].name)
	assert.Equal(silver3, tm.Semis[1].Players[2].name)

	// Tryout 4
	m4, err4 := tm.NextMatch()
	assert.Nil(err4)

	m4.StartMatch()

	m4.Players[0].AddKill(9)
	m4.Players[1].AddKill(10)
	m4.Players[2].AddKill(5)
	m4.Players[3].AddKill(5)
	winner4 := m4.Players[1].name
	silver4 := m4.Players[0].name

	m4.EndMatch()

	assert.Equal(4, len(tm.Semis[0].Players))
	assert.Equal(4, len(tm.Semis[1].Players))
	assert.Equal(8, len(tm.Runnerups))

	assert.Equal(winner4, tm.Semis[1].Players[3].name)
	assert.Equal(silver4, tm.Semis[0].Players[3].name)

	// Semi 1
	s1, serr1 := tm.NextMatch()
	assert.Nil(serr1)

	assert.Equal("semi", s1.Kind)

	s1.StartMatch()

	s1.Players[0].AddKill(10)
	s1.Players[1].AddKill(7)
	s1.Players[2].AddKill(9)
	s1.Players[3].AddKill(8)
	winners1 := s1.Players[0].name
	silvers1 := s1.Players[2].name

	s1.EndMatch()

	assert.Equal(2, len(tm.Final.Players))

	assert.Equal(winners1, tm.Final.Players[0].name)
	assert.Equal(silvers1, tm.Final.Players[1].name)

	// Semi 2
	s2, serr2 := tm.NextMatch()
	assert.Nil(serr2)

	assert.Equal("semi", s2.Kind)

	s2.StartMatch()

	s2.Players[0].AddKill(8)
	s2.Players[1].AddKill(10)
	s2.Players[2].AddKill(8)
	s2.Players[3].AddKill(9)
	winners2 := s2.Players[1].name
	silvers2 := s2.Players[3].name

	s2.EndMatch()

	assert.Equal(4, len(tm.Final.Players))

	assert.Equal(winners2, tm.Final.Players[2].name)
	assert.Equal(silvers2, tm.Final.Players[3].name)

	// Final!
	f, ferr := tm.NextMatch()
	assert.Nil(ferr)

	assert.Equal("final", f.Kind)

	f.StartMatch()

	f.Players[0].AddKill(7)
	f.Players[1].AddKill(2)
	f.Players[2].AddKill(9)
	f.Players[3].AddKill(10)
	gold := f.Players[3].name
	lowe := f.Players[2].name
	bronze := f.Players[0].name

	f.EndMatch()

	t.Log(tm.Winners)
	assert.Equal(gold, tm.Winners[0].name)
	assert.Equal(lowe, tm.Winners[1].name)
	assert.Equal(bronze, tm.Winners[2].name)
}
<|endoftext|>


<commit_before>// +build go1.8

package main

import (
	"fmt"
	"os"
	"strings"

	"github.com/docopt/docopt-go"
	"github.com/tisp-lang/tisp/src/lib/compile"
	"github.com/tisp-lang/tisp/src/lib/run"
)

func main() {
	args := getArgs()

	if !args["--debug"].(bool) {
		defer func() {
			if r := recover(); r != nil {
				switch x := r.(type) {
				case error:
					printToStderr(x.Error())
				case string:
					printToStderr(x)
				default:
					if x != nil {
						panic(x)
					}
				}

				os.Exit(1)
			}
		}()
	}

	run.Run(compile.MainModule(args["<filename>"].(string)))
}

func getArgs() map[string]interface{} {
	usage := `Tisp interpreter

Usage:
  tisp [-d] [<filename>]

Options:
  -d, --debug  Turn on debug mode.
  -h, --help  Show this help.`

	args, err := docopt.Parse(usage, nil, true, "Tisp 0.0.0", false)

	if err != nil {
		printToStderr(err.Error())
		os.Exit(1)
	} else if args["<filename>"] == nil {
		args["<filename>"] = ""
	}

	return args
}

func printToStderr(s string) {
	fmt.Fprintf(os.Stderr, strings.TrimSpace(s)+"\n")
}
<commit_msg>Remove redundant if statement<commit_after>// +build go1.8

package main

import (
	"fmt"
	"os"
	"strings"

	"github.com/docopt/docopt-go"
	"github.com/tisp-lang/tisp/src/lib/compile"
	"github.com/tisp-lang/tisp/src/lib/run"
)

func main() {
	args := getArgs()

	if !args["--debug"].(bool) {
		defer func() {
			if r := recover(); r != nil {
				switch x := r.(type) {
				case error:
					printToStderr(x.Error())
				case string:
					printToStderr(x)
				default:
					panic(x)
				}

				os.Exit(1)
			}
		}()
	}

	run.Run(compile.MainModule(args["<filename>"].(string)))
}

func getArgs() map[string]interface{} {
	usage := `Tisp interpreter

Usage:
  tisp [-d] [<filename>]

Options:
  -d, --debug  Turn on debug mode.
  -h, --help  Show this help.`

	args, err := docopt.Parse(usage, nil, true, "Tisp 0.0.0", false)

	if err != nil {
		printToStderr(err.Error())
		os.Exit(1)
	} else if args["<filename>"] == nil {
		args["<filename>"] = ""
	}

	return args
}

func printToStderr(s string) {
	fmt.Fprintf(os.Stderr, strings.TrimSpace(s)+"\n")
}
<|endoftext|>


<commit_before>// Copyright Roger Booth (roger.booth@gmail.com)
// In the event that you find this code useful, feel free to provide attribution :)

package main

import (
	"container/ring"
	"fmt"
	"time"
)

type Color string

var colors = [...]Color{"white", "blue", "red", "yellow", "orange", "green"}
var intForColor = map[Color]int {
	"white": 0,
	"blue": 1,
	"red": 2,
	"yellow": 3,
	"orange": 4,
	"green": 5
}
var edgesForFace = map[Color][]Color{
	"white":  {"red", "green", "orange", "blue"},
	"blue":   {"white", "orange", "yellow", "red"},
	"red":    {"blue", "yellow", "green", "white"},
	"yellow": {"green", "red", "blue", "orange"},
	"orange": {"yellow", "blue", "white", "green"},
	"green":  {"orange", "white", "red", "yellow"},
}

// Based on the coordinate system I discovered in 1984
var edgePos = [...]int{0, 7, 6, 4, 3, 2, 6, 5, 4, 2, 1, 0}

// Pair cube faces by front-to-front projection
// Eight cubes X six faces
var straightProjection = [...][...]int {
	{1,3,4,1,4,3},
	{0,2,5,0,5,2},
	{3,1,6,3,6,1},
	{2,0,7,2,7,0},
	{5,7,0,5,0,7},
	{4,6,1,4,1,6},
	{7,5,2,7,2,5},
	{6,4,3,6,3,4}
}

type Face [8]Color

type Edge [12]*Color

type Cube struct {
	faceMap map[Color]*Face
	edgeMap map[Color]Edge
}

type Entanglement [8]*Cube

func NewCube() (*Cube, error) {
	newFaceMap := make(map[Color]*Face)
	newEdgeMap := make(map[Color]Edge)
	for _, color := range colors {
		newFaceMap[color] = &Face{color, color, color, color, color, color, color, color}
	}
	i := 0
	for _, faceColor := range colors {
		var newEdge Edge
		for _, edgeColor := range edgesForFace[faceColor] {
		        //fmt.Println(faceColor)
		        //fmt.Println(i)
			newEdge[i] = &newFaceMap[edgeColor][edgePos[i]]
			newEdge[i+1] = &newFaceMap[edgeColor][edgePos[i+1]]
			newEdge[i+2] = &newFaceMap[edgeColor][edgePos[i+2]]
			i += 3
			if i == 12 {
			    i = 0
			}
		}
		newEdgeMap[faceColor] = newEdge
	}
	return &Cube{newFaceMap, newEdgeMap}, nil
}

func NewEntanglement() (*Entanglement, error) {
        var newEntanglement Entanglement
	for i:=0; i<8; i++{
		newEntanglement[i], _ = NewCube();
	}
	return &newEntanglement, nil
}

type ThreeDTransformer struct {
	faceRing *ring.Ring
	edgeRing *ring.Ring
}

func ThreeDRotate(op ThreeDOperation) error {

        newFaceRing := ring.New(8)
        newEdgeRing := ring.New(12)
        trx := ThreeDTransformer{
		newFaceRing,newEdgeRing}
	for _, faceColor := range op.ent[op.cubeId].faceMap[op.face] {
		trx.faceRing.Value = faceColor
		trx.faceRing = trx.faceRing.Next()
	}
	for _, edgeColorPtr := range op.ent[op.cubeId].edgeMap[op.face] {
		trx.edgeRing.Value = *edgeColorPtr
		trx.edgeRing = trx.edgeRing.Next()
	}

	trx.faceRing = trx.faceRing.Move(2*op.direction)
	trx.edgeRing = trx.edgeRing.Move(3*op.direction)

	for i, _ := range ent[op.cubeId].faceMap[op.face] {
	        if v,ok := trx.faceRing.Value.(Color); ok {
		    op.ent[op.cubeId].faceMap[op.face][i] = v
		}
		trx.faceRing = trx.faceRing.Next()
	}
	for i, _ := range ent[op.cubeId].edgeMap[op.face] {
	        if v,ok := trx.edgeRing.Value.(Color); ok {
		    *op.ent[cubeId].edgeMap[face][i] = v
		}
		trx.edgeRing = trx.edgeRing.Next()
	}

    return nil
}

func Sister(cubeId int, face Color) (sisterCubeId int, sisterFace Color){
	sisterCubeId = straightProjection[cubeId][intForColor[Color]]
	sisterFace = face
}

type ThreeDOperation struct {
	ent *Entanglement
	cubeId int
	face Color
	direction int
	primary bool
}

func SplitMessage(op ThreeDOperation, opchan channel ThreeDOperation) error {
	sisterCubeId, sisterFace = Sister(op.CubeId, op.face)
	op.primary = false
	opchan <- op
	var sisterOp = ThreeDOperation {op.ent, sisterCubeId, sisterFace,op.direction, false}
	opchan <- sisterOp
}

func countDown(count chan int) {
	for i := 20 ; i >= 0 ; i-- {
		count <- i
		time.Sleep(1000000000)
	}
}

func takeSample(ent *Entanglement) {
	fmt.Println(ent[0].faceMap["red"][1])
	fmt.Println(ent[0].faceMap["red"][2])
	fmt.Println(*ent[0].edgeMap["red"][2])
	fmt.Println(*ent[0].edgeMap["red"][3])
	fmt.Println(*ent[0].edgeMap["red"][8])
	fmt.Println(*ent[0].edgeMap["red"][11])
}

func player1(blab chan ThreeDOperation) {
	for i := range sim1 {
		blab <- sim1[i]
		time.Sleep(1000000)
	}
}

func player2(blab chan ThreeDOperation) {
	for i := range sim2 {
		blab <- sim2[i]
		time.Sleep(1000000)
	}
}

func main() {
	entanglement1,_ := NewEntanglement()
	operations := make(chan ThreeDOperation)
	count := make(chan int)
        go player1(operations)
	go player2(operations)
	go countDown(count)
	for {
		select {
			case o := <- operations:
			    if o.primary {
					SplitMessage(o, operations)
				} else {
					ThreeDRotate(o)
				}

			case i := <- count:
				if 0 == i {
					takeSample(entanglement1)
					return
				}
				fmt.Printf("%d seconds remaining\n", i)
		}
	}
}
<commit_msg>Minimal set of test operations.<commit_after>// Copyright Roger Booth (roger.booth@gmail.com)
// In the event that you find this code useful, feel free to provide attribution :)

package main

import (
	"container/ring"
	"fmt"
	"time"
)

type Color string

var colors = [...]Color{"white", "blue", "red", "yellow", "orange", "green"}
var intForColor = map[Color]int {
	"white": 0,
	"blue": 1,
	"red": 2,
	"yellow": 3,
	"orange": 4,
	"green": 5
}
var edgesForFace = map[Color][]Color{
	"white":  {"red", "green", "orange", "blue"},
	"blue":   {"white", "orange", "yellow", "red"},
	"red":    {"blue", "yellow", "green", "white"},
	"yellow": {"green", "red", "blue", "orange"},
	"orange": {"yellow", "blue", "white", "green"},
	"green":  {"orange", "white", "red", "yellow"},
}

// Based on the coordinate system I discovered in 1984
var edgePos = [...]int{0, 7, 6, 4, 3, 2, 6, 5, 4, 2, 1, 0}

// Pair cube faces by front-to-front projection
// Eight cubes X six faces
var straightProjection = [...][...]int {
	{1,3,4,1,4,3},
	{0,2,5,0,5,2},
	{3,1,6,3,6,1},
	{2,0,7,2,7,0},
	{5,7,0,5,0,7},
	{4,6,1,4,1,6},
	{7,5,2,7,2,5},
	{6,4,3,6,3,4}
}

type Face [8]Color

type Edge [12]*Color

type Cube struct {
	faceMap map[Color]*Face
	edgeMap map[Color]Edge
}

type Entanglement [8]*Cube

func NewCube() (*Cube, error) {
	newFaceMap := make(map[Color]*Face)
	newEdgeMap := make(map[Color]Edge)
	for _, color := range colors {
		newFaceMap[color] = &Face{color, color, color, color, color, color, color, color}
	}
	i := 0
	for _, faceColor := range colors {
		var newEdge Edge
		for _, edgeColor := range edgesForFace[faceColor] {
		        //fmt.Println(faceColor)
		        //fmt.Println(i)
			newEdge[i] = &newFaceMap[edgeColor][edgePos[i]]
			newEdge[i+1] = &newFaceMap[edgeColor][edgePos[i+1]]
			newEdge[i+2] = &newFaceMap[edgeColor][edgePos[i+2]]
			i += 3
			if i == 12 {
			    i = 0
			}
		}
		newEdgeMap[faceColor] = newEdge
	}
	return &Cube{newFaceMap, newEdgeMap}, nil
}

func NewEntanglement() (*Entanglement, error) {
        var newEntanglement Entanglement
	for i:=0; i<8; i++{
		newEntanglement[i], _ = NewCube();
	}
	return &newEntanglement, nil
}

type ThreeDTransformer struct {
	faceRing *ring.Ring
	edgeRing *ring.Ring
}

func ThreeDRotate(op ThreeDOperation) error {

        newFaceRing := ring.New(8)
        newEdgeRing := ring.New(12)
        trx := ThreeDTransformer{
		newFaceRing,newEdgeRing}
	for _, faceColor := range op.ent[op.cubeId].faceMap[op.face] {
		trx.faceRing.Value = faceColor
		trx.faceRing = trx.faceRing.Next()
	}
	for _, edgeColorPtr := range op.ent[op.cubeId].edgeMap[op.face] {
		trx.edgeRing.Value = *edgeColorPtr
		trx.edgeRing = trx.edgeRing.Next()
	}

	trx.faceRing = trx.faceRing.Move(2*op.direction)
	trx.edgeRing = trx.edgeRing.Move(3*op.direction)

	for i, _ := range ent[op.cubeId].faceMap[op.face] {
	        if v,ok := trx.faceRing.Value.(Color); ok {
		    op.ent[op.cubeId].faceMap[op.face][i] = v
		}
		trx.faceRing = trx.faceRing.Next()
	}
	for i, _ := range ent[op.cubeId].edgeMap[op.face] {
	        if v,ok := trx.edgeRing.Value.(Color); ok {
		    *op.ent[cubeId].edgeMap[face][i] = v
		}
		trx.edgeRing = trx.edgeRing.Next()
	}

    return nil
}

func Sister(cubeId int, face Color) (sisterCubeId int, sisterFace Color){
	sisterCubeId = straightProjection[cubeId][intForColor[Color]]
	sisterFace = face
}

type ThreeDOperation struct {
	ent *Entanglement
	cubeId int
	face Color
	direction int
	primary bool
}

func SplitMessage(op ThreeDOperation, opchan channel ThreeDOperation) error {
	sisterCubeId, sisterFace = Sister(op.CubeId, op.face)
	op.primary = false
	opchan <- op
	var sisterOp = ThreeDOperation {op.ent, sisterCubeId, sisterFace,op.direction, false}
	opchan <- sisterOp
}

func countDown(count chan int) {
	for i := 20 ; i >= 0 ; i-- {
		count <- i
		time.Sleep(1000000000)
	}
}

func takeSample(ent *Entanglement) {
	fmt.Println(ent[0].faceMap["red"][1])
	fmt.Println(ent[0].faceMap["red"][2])
	fmt.Println(*ent[0].edgeMap["red"][2])
	fmt.Println(*ent[0].edgeMap["red"][3])
	fmt.Println(*ent[0].edgeMap["red"][8])
	fmt.Println(*ent[0].edgeMap["red"][11])
}

func player1(blab chan ThreeDOperation, ent *Entanglement) {
	var sim1 = [...]ThreeDOperation {
	    {ent, 0, "red", 1, true}
        }
	for i := range sim1 {
		blab <- sim1[i]
		time.Sleep(1000000)
	}
}

func player2(blab chan ThreeDOperation, ent *Entanglement) {
	var sim2 = [...]ThreeDOperation {
	    {ent, 2, "blue", 1, true}
        }
	for i := range sim2 {
		blab <- sim2[i]
		time.Sleep(1000000)
	}
}

func main() {
	entanglement1,_ := NewEntanglement()
	operations := make(chan ThreeDOperation)
	count := make(chan int)
        go player1(operations, entanglement1)
	go player2(operations, entanglement1)
	go countDown(count)
	for {
		select {
			case o := <- operations:
			    if o.primary {
					SplitMessage(o, operations)
				} else {
					ThreeDRotate(o)
				}

			case i := <- count:
				if 0 == i {
					takeSample(entanglement1)
					return
				}
				fmt.Printf("%d seconds remaining\n", i)
		}
	}
}
<|endoftext|>


<commit_before>// Copyright 2015-2018 the u-root Authors. All rights reserved
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package main

import (
	"bytes"
	"crypto/sha256"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"os/exec"
	"path/filepath"
	"testing"

	"github.com/u-root/u-root/pkg/cpio"
	"github.com/u-root/u-root/pkg/testutil"
	itest "github.com/u-root/u-root/pkg/uroot/initramfs/test"
)

var twocmds = []string{
	"github.com/u-root/u-root/cmds/core/ls",
	"github.com/u-root/u-root/cmds/core/init",
}

var srcmds = []string{
	"github.com/u-root/u-root/cmds/core/ls",
	"github.com/u-root/u-root/cmds/core/init",
	"github.com/u-root/u-root/cmds/core/installcommand",
}

type buildSourceValidator struct {
	gopath string
	goroot string
	env    []string
}

func (b buildSourceValidator) Validate(a *cpio.Archive) error {
	dir, err := ioutil.TempDir("", "u-root-source-")
	if err != nil {
		return err
	}
	defer os.RemoveAll(dir)

	if err := os.Mkdir(filepath.Join(dir, "tmp"), 0755); err != nil {
		return err
	}

	// Unpack into dir.
	err = cpio.ForEachRecord(a.Reader(), func(r cpio.Record) error {
		return cpio.CreateFileInRoot(r, dir, false)
	})
	if err != nil {
		return err
	}

	goroot := filepath.Join(dir, b.goroot)
	gopath := filepath.Join(dir, b.gopath)
	// go build ./src/...
	c := exec.Command(filepath.Join(goroot, "bin/go"), "build", filepath.Join(gopath, "src/..."))
	c.Env = append(b.env,
		fmt.Sprintf("GOPATH=%s", gopath),
		fmt.Sprintf("GOCACHE=%s", filepath.Join(dir, "tmp")),
		fmt.Sprintf("GOROOT=%s", goroot),
		"CGO_ENABLED=0")
	out, err := c.CombinedOutput()
	if err != nil {
		return fmt.Errorf("could not build go source %v; output\n%s", err, out)
	}
	return nil
}

func TestUrootCmdline(t *testing.T) {
	samplef, err := ioutil.TempFile("", "u-root-sample-")
	if err != nil {
		t.Fatal(err)
	}
	samplef.Close()
	defer os.RemoveAll(samplef.Name())

	for _, tt := range []struct {
		name       string
		env        []string
		args       []string
		err        error
		validators []itest.ArchiveValidator
	}{
		{
			name: "include one extra file",
			args: []string{"-nocmd", "-files=/bin/bash"},
			err:  nil,
			validators: []itest.ArchiveValidator{
				itest.HasFile{"bin/bash"},
			},
		},
		{
			name: "uinitcmd",
			args: []string{"-build=bb", "-uinitcmd=echo foobar fuzz", "-defaultsh=", "./cmds/core/init", "./cmds/core/echo"},
			err:  nil,
			validators: []itest.ArchiveValidator{
				itest.HasRecord{cpio.Symlink("bin/uinit", "../bbin/echo")},
				itest.HasContent{
					Path:    "etc/uinit.flags",
					Content: "\"foobar\"\n\"fuzz\"",
				},
			},
		},
		{
			name: "fix usage of an absolute path",
			args: []string{"-nocmd", "-files=/bin:/bin"},
			err:  nil,
			validators: []itest.ArchiveValidator{
				itest.HasFile{"bin/bash"},
			},
		},
		{
			name: "include multiple extra files",
			args: []string{"-nocmd", "-files=/bin/bash", "-files=/bin/ls", fmt.Sprintf("-files=%s", samplef.Name())},
			validators: []itest.ArchiveValidator{
				itest.HasFile{"bin/bash"},
				itest.HasFile{"bin/ls"},
				itest.HasFile{samplef.Name()},
			},
		},
		{
			name: "include one extra file with rename",
			args: []string{"-nocmd", "-files=/bin/bash:bin/bush"},
			validators: []itest.ArchiveValidator{
				itest.HasFile{"bin/bush"},
			},
		},
		{
			name: "hosted source mode",
			args: append([]string{"-build=source", "-base=/dev/null", "-defaultsh=", "-initcmd="}, srcmds...),
		},
		{
			name: "hosted bb mode",
			args: append([]string{"-build=bb", "-base=/dev/null", "-defaultsh=", "-initcmd="}, twocmds...),
		},
		{
			name: "AMD64 bb build",
			env:  []string{"GOARCH=amd64"},
			args: []string{"-build=bb", "all"},
		},
		{
			name: "AMD64 source build",
			env:  []string{"GOARCH=amd64"},
			args: []string{"-build=source", "all"},
			validators: []itest.ArchiveValidator{
				buildSourceValidator{
					goroot: "/go",
					gopath: ".",
					env:    []string{"GOARCH=amd64"},
				},
			},
		},
		{
			name: "386 source build",
			env:  []string{"GOARCH=386"},
			args: []string{"-build=source", "all"},
			validators: []itest.ArchiveValidator{
				buildSourceValidator{
					goroot: "/go",
					gopath: ".",
					env:    []string{"GOARCH=386"},
				},
			},
		},
		{
			name: "MIPS bb build",
			env:  []string{"GOARCH=mips"},
			args: []string{"-build=bb", "all"},
		},
		{
			name: "MIPSLE bb build",
			env:  []string{"GOARCH=mipsle"},
			args: []string{"-build=bb", "all"},
		},
		{
			name: "MIPS64 bb build",
			env:  []string{"GOARCH=mips64"},
			args: []string{"-build=bb", "all"},
		},
		{
			name: "MIPS64LE bb build",
			env:  []string{"GOARCH=mips64le"},
			args: []string{"-build=bb", "all"},
		},
		{
			name: "ARM7 bb build",
			env:  []string{"GOARCH=arm", "GOARM=7"},
			args: []string{"-build=bb", "all"},
		},
		{
			name: "ARM64 bb build",
			env:  []string{"GOARCH=arm64"},
			args: []string{"-build=bb", "all"},
		},
		{
			name: "386 (32 bit) bb build",
			env:  []string{"GOARCH=386"},
			args: []string{"-build=bb", "all"},
		},
		{
			name: "Power 64bit bb build",
			env:  []string{"GOARCH=ppc64le"},
			args: []string{"-build=bb", "all"},
		},
	} {
		t.Run(tt.name, func(t *testing.T) {
			delFiles := true
			f, sum1 := buildIt(t, tt.args, tt.env, tt.err)
			defer func() {
				if delFiles {
					os.RemoveAll(f.Name())
				}
			}()

			a, err := itest.ReadArchive(f.Name())
			if err != nil {
				t.Fatal(err)
			}

			for _, v := range tt.validators {
				if err := v.Validate(a); err != nil {
					t.Errorf("validator failed: %v / archive:\n%s", err, a)
				}
			}

			f2, sum2 := buildIt(t, tt.args, tt.env, tt.err)
			defer func() {
				if delFiles {
					os.RemoveAll(f2.Name())
				}
			}()
			if !bytes.Equal(sum1, sum2) {
				delFiles = false
				t.Errorf("not reproducible, hashes don't match")
				t.Errorf("env: %v args: %v", tt.env, tt.args)
				t.Errorf("file1: %v file2: %v", f.Name(), f2.Name())
			}
		})
	}
}

func buildIt(t *testing.T, args, env []string, want error) (*os.File, []byte) {
	f, err := ioutil.TempFile("", "u-root-")
	if err != nil {
		t.Fatal(err)
	}

	arg := append([]string{"-o", f.Name()}, args...)
	c := testutil.Command(t, arg...)
	t.Logf("Commandline: %v", arg)
	c.Env = append(c.Env, env...)
	if out, err := c.CombinedOutput(); err != want {
		t.Fatalf("Error: %v\nOutput:\n%s", err, out)
	} else if err != nil {
		h1 := sha256.New()
		if _, err := io.Copy(h1, f); err != nil {
			t.Fatal()
		}
		return f, h1.Sum(nil)
	}
	return f, nil
}

func TestMain(m *testing.M) {
	testutil.Run(m, main)
}
<commit_msg>uroot_test: disable 386 source build<commit_after>// Copyright 2015-2018 the u-root Authors. All rights reserved
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package main

import (
	"bytes"
	"crypto/sha256"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"os/exec"
	"path/filepath"
	"testing"

	"github.com/u-root/u-root/pkg/cpio"
	"github.com/u-root/u-root/pkg/testutil"
	itest "github.com/u-root/u-root/pkg/uroot/initramfs/test"
)

var twocmds = []string{
	"github.com/u-root/u-root/cmds/core/ls",
	"github.com/u-root/u-root/cmds/core/init",
}

var srcmds = []string{
	"github.com/u-root/u-root/cmds/core/ls",
	"github.com/u-root/u-root/cmds/core/init",
	"github.com/u-root/u-root/cmds/core/installcommand",
}

type buildSourceValidator struct {
	gopath string
	goroot string
	env    []string
}

func (b buildSourceValidator) Validate(a *cpio.Archive) error {
	dir, err := ioutil.TempDir("", "u-root-source-")
	if err != nil {
		return err
	}
	defer os.RemoveAll(dir)

	if err := os.Mkdir(filepath.Join(dir, "tmp"), 0755); err != nil {
		return err
	}

	// Unpack into dir.
	err = cpio.ForEachRecord(a.Reader(), func(r cpio.Record) error {
		return cpio.CreateFileInRoot(r, dir, false)
	})
	if err != nil {
		return err
	}

	goroot := filepath.Join(dir, b.goroot)
	gopath := filepath.Join(dir, b.gopath)
	// go build ./src/...
	c := exec.Command(filepath.Join(goroot, "bin/go"), "build", filepath.Join(gopath, "src/..."))
	c.Env = append(b.env,
		fmt.Sprintf("GOPATH=%s", gopath),
		fmt.Sprintf("GOCACHE=%s", filepath.Join(dir, "tmp")),
		fmt.Sprintf("GOROOT=%s", goroot),
		"CGO_ENABLED=0")
	out, err := c.CombinedOutput()
	if err != nil {
		return fmt.Errorf("could not build go source %v; output\n%s", err, out)
	}
	return nil
}

func TestUrootCmdline(t *testing.T) {
	samplef, err := ioutil.TempFile("", "u-root-sample-")
	if err != nil {
		t.Fatal(err)
	}
	samplef.Close()
	defer os.RemoveAll(samplef.Name())

	for _, tt := range []struct {
		name       string
		env        []string
		args       []string
		err        error
		validators []itest.ArchiveValidator
	}{
		{
			name: "include one extra file",
			args: []string{"-nocmd", "-files=/bin/bash"},
			err:  nil,
			validators: []itest.ArchiveValidator{
				itest.HasFile{"bin/bash"},
			},
		},
		{
			name: "uinitcmd",
			args: []string{"-build=bb", "-uinitcmd=echo foobar fuzz", "-defaultsh=", "./cmds/core/init", "./cmds/core/echo"},
			err:  nil,
			validators: []itest.ArchiveValidator{
				itest.HasRecord{cpio.Symlink("bin/uinit", "../bbin/echo")},
				itest.HasContent{
					Path:    "etc/uinit.flags",
					Content: "\"foobar\"\n\"fuzz\"",
				},
			},
		},
		{
			name: "fix usage of an absolute path",
			args: []string{"-nocmd", "-files=/bin:/bin"},
			err:  nil,
			validators: []itest.ArchiveValidator{
				itest.HasFile{"bin/bash"},
			},
		},
		{
			name: "include multiple extra files",
			args: []string{"-nocmd", "-files=/bin/bash", "-files=/bin/ls", fmt.Sprintf("-files=%s", samplef.Name())},
			validators: []itest.ArchiveValidator{
				itest.HasFile{"bin/bash"},
				itest.HasFile{"bin/ls"},
				itest.HasFile{samplef.Name()},
			},
		},
		{
			name: "include one extra file with rename",
			args: []string{"-nocmd", "-files=/bin/bash:bin/bush"},
			validators: []itest.ArchiveValidator{
				itest.HasFile{"bin/bush"},
			},
		},
		{
			name: "hosted source mode",
			args: append([]string{"-build=source", "-base=/dev/null", "-defaultsh=", "-initcmd="}, srcmds...),
		},
		{
			name: "hosted bb mode",
			args: append([]string{"-build=bb", "-base=/dev/null", "-defaultsh=", "-initcmd="}, twocmds...),
		},
		{
			name: "AMD64 bb build",
			env:  []string{"GOARCH=amd64"},
			args: []string{"-build=bb", "all"},
		},
		{
			name: "AMD64 source build",
			env:  []string{"GOARCH=amd64"},
			args: []string{"-build=source", "all"},
			validators: []itest.ArchiveValidator{
				buildSourceValidator{
					goroot: "/go",
					gopath: ".",
					env:    []string{"GOARCH=amd64"},
				},
			},
		},
		{
			name: "MIPS bb build",
			env:  []string{"GOARCH=mips"},
			args: []string{"-build=bb", "all"},
		},
		{
			name: "MIPSLE bb build",
			env:  []string{"GOARCH=mipsle"},
			args: []string{"-build=bb", "all"},
		},
		{
			name: "MIPS64 bb build",
			env:  []string{"GOARCH=mips64"},
			args: []string{"-build=bb", "all"},
		},
		{
			name: "MIPS64LE bb build",
			env:  []string{"GOARCH=mips64le"},
			args: []string{"-build=bb", "all"},
		},
		{
			name: "ARM7 bb build",
			env:  []string{"GOARCH=arm", "GOARM=7"},
			args: []string{"-build=bb", "all"},
		},
		{
			name: "ARM64 bb build",
			env:  []string{"GOARCH=arm64"},
			args: []string{"-build=bb", "all"},
		},
		{
			name: "386 (32 bit) bb build",
			env:  []string{"GOARCH=386"},
			args: []string{"-build=bb", "all"},
		},
		{
			name: "Power 64bit bb build",
			env:  []string{"GOARCH=ppc64le"},
			args: []string{"-build=bb", "all"},
		},
	} {
		t.Run(tt.name, func(t *testing.T) {
			delFiles := true
			f, sum1 := buildIt(t, tt.args, tt.env, tt.err)
			defer func() {
				if delFiles {
					os.RemoveAll(f.Name())
				}
			}()

			a, err := itest.ReadArchive(f.Name())
			if err != nil {
				t.Fatal(err)
			}

			for _, v := range tt.validators {
				if err := v.Validate(a); err != nil {
					t.Errorf("validator failed: %v / archive:\n%s", err, a)
				}
			}

			f2, sum2 := buildIt(t, tt.args, tt.env, tt.err)
			defer func() {
				if delFiles {
					os.RemoveAll(f2.Name())
				}
			}()
			if !bytes.Equal(sum1, sum2) {
				delFiles = false
				t.Errorf("not reproducible, hashes don't match")
				t.Errorf("env: %v args: %v", tt.env, tt.args)
				t.Errorf("file1: %v file2: %v", f.Name(), f2.Name())
			}
		})
	}
}

func buildIt(t *testing.T, args, env []string, want error) (*os.File, []byte) {
	f, err := ioutil.TempFile("", "u-root-")
	if err != nil {
		t.Fatal(err)
	}

	arg := append([]string{"-o", f.Name()}, args...)
	c := testutil.Command(t, arg...)
	t.Logf("Commandline: %v", arg)
	c.Env = append(c.Env, env...)
	if out, err := c.CombinedOutput(); err != want {
		t.Fatalf("Error: %v\nOutput:\n%s", err, out)
	} else if err != nil {
		h1 := sha256.New()
		if _, err := io.Copy(h1, f); err != nil {
			t.Fatal()
		}
		return f, h1.Sum(nil)
	}
	return f, nil
}

func TestMain(m *testing.M) {
	testutil.Run(m, main)
}
<|endoftext|>


<commit_before>// Copyright 2010 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package main

import (
	"http"
	"log"
	"time"
)

const (
	numPollers     = 2           // number of Poller goroutines to launch
	second         = 1e9         // one second is 1e9 nanoseconds
	pollInterval   = 60 * second // how often to poll each URL
	statusInterval = 10 * second // how often to log status to stdout
	errTimeout     = 10 * second // back-off timeout on error
)

var urls = []string{
	"http://www.google.com/",
	"http://golang.org/",
	"http://blog.golang.org/",
}

// State represents the last-known state of a URL.
type State struct {
	url    string
	status string
}

// StateMonitor maintains a map that stores the state of the URLs being
// polled, and prints the current state every updateInterval nanoseconds.
// It returns a chan State to which resource state should be sent.
func StateMonitor(updateInterval int64) chan<- State {
	updates := make(chan State)
	urlStatus := make(map[string]string)
	ticker := time.NewTicker(updateInterval)
	go func() {
		for {
			select {
			case <-ticker.C:
				logState(urlStatus)
			case s := <-updates:
				urlStatus[s.url] = s.status
			}
		}
	}()
	return updates
}

// logState prints a state map.
func logState(s map[string]string) {
	log.Stdout("Current state:")
	for k, v := range s {
		log.Stdoutf(" %s %s", k, v)
	}
}

// Resource represents an HTTP URL to be polled by this program.
type Resource struct {
	url      string
	errCount int64
}

// Poll executes an HTTP HEAD request for url
// and returns the HTTP status string or an error string.
func (r *Resource) Poll() string {
	resp, err := http.Head(r.url)
	if err != nil {
		log.Stderr("Error", r.url, err)
		r.errCount++
		return err.String()
	}
	r.errCount = 0
	return resp.Status
}

// Sleep sleeps for an appropriate interval (dependant on error state)
// before sending the Resource to done.
func (r *Resource) Sleep(done chan *Resource) {
	time.Sleep(pollInterval + errTimeout*r.errCount)
	done <- r
}

func Poller(in <-chan *Resource, out chan<- *Resource, status chan<- State) {
	for r := range in {
		s := r.Poll()
		status <- State{r.url, s}
		out <- r
	}
}

func main() {
	// create our input and output channels
	pending, complete := make(chan *Resource), make(chan *Resource)

	// launch the StateMonitor
	status := StateMonitor(statusInterval)

	// launch some Poller goroutines
	for i := 0; i < numPollers; i++ {
		go Poller(pending, complete, status)
	}

	// send some Resources to the pending queue
	go func() {
		for _, url := range urls {
			pending <- &Resource{url: url}
		}
	}()

	for r := range complete {
		go r.Sleep(pending)
	}
}
<commit_msg>doc/codewalk: update for changes in log package<commit_after>// Copyright 2010 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package main

import (
	"http"
	"log"
	"time"
)

const (
	numPollers     = 2           // number of Poller goroutines to launch
	second         = 1e9         // one second is 1e9 nanoseconds
	pollInterval   = 60 * second // how often to poll each URL
	statusInterval = 10 * second // how often to log status to stdout
	errTimeout     = 10 * second // back-off timeout on error
)

var urls = []string{
	"http://www.google.com/",
	"http://golang.org/",
	"http://blog.golang.org/",
}

// State represents the last-known state of a URL.
type State struct {
	url    string
	status string
}

// StateMonitor maintains a map that stores the state of the URLs being
// polled, and prints the current state every updateInterval nanoseconds.
// It returns a chan State to which resource state should be sent.
func StateMonitor(updateInterval int64) chan<- State {
	updates := make(chan State)
	urlStatus := make(map[string]string)
	ticker := time.NewTicker(updateInterval)
	go func() {
		for {
			select {
			case <-ticker.C:
				logState(urlStatus)
			case s := <-updates:
				urlStatus[s.url] = s.status
			}
		}
	}()
	return updates
}

// logState prints a state map.
func logState(s map[string]string) {
	log.Println("Current state:")
	for k, v := range s {
		log.Printf(" %s %s", k, v)
	}
}

// Resource represents an HTTP URL to be polled by this program.
type Resource struct {
	url      string
	errCount int64
}

// Poll executes an HTTP HEAD request for url
// and returns the HTTP status string or an error string.
func (r *Resource) Poll() string {
	resp, err := http.Head(r.url)
	if err != nil {
		log.Println("Error", r.url, err)
		r.errCount++
		return err.String()
	}
	r.errCount = 0
	return resp.Status
}

// Sleep sleeps for an appropriate interval (dependant on error state)
// before sending the Resource to done.
func (r *Resource) Sleep(done chan *Resource) {
	time.Sleep(pollInterval + errTimeout*r.errCount)
	done <- r
}

func Poller(in <-chan *Resource, out chan<- *Resource, status chan<- State) {
	for r := range in {
		s := r.Poll()
		status <- State{r.url, s}
		out <- r
	}
}

func main() {
	// create our input and output channels
	pending, complete := make(chan *Resource), make(chan *Resource)

	// launch the StateMonitor
	status := StateMonitor(statusInterval)

	// launch some Poller goroutines
	for i := 0; i < numPollers; i++ {
		go Poller(pending, complete, status)
	}

	// send some Resources to the pending queue
	go func() {
		for _, url := range urls {
			pending <- &Resource{url: url}
		}
	}()

	for r := range complete {
		go r.Sleep(pending)
	}
}
<|endoftext|>


<commit_before>/*
 *
 * k6 - a next-generation load testing tool
 * Copyright (C) 2016 Load Impact
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

package lib

import (
	"context"

	"github.com/loadimpact/k6/stats"
)

// Ensure RunnerFunc conforms to Runner.
var _ Runner = RunnerFunc(nil)

// A Runner is a factory for VUs. It should precompute as much as possible upon creation (parse
// ASTs, load files into memory, etc.), so that spawning VUs becomes as fast as possible.
// The Runner doesn't actually *do* anything in itself, the Executor is responsible for wrapping
// and scheduling these VUs for execution.
//
// TODO: Rename this to something more obvious? This name made sense a very long time ago.
type Runner interface {
	// Creates an Archive of the runner. There should be a corresponding NewFromArchive() function
	// that will restore the runner from the archive.
	MakeArchive() *Archive

	// Spawns a new VU. It's fine to make this function rather heavy, if it means a performance
	// improvement at runtime. Remember, this is called once per VU and normally only at the start
	// of a test - RunOnce() may be called hundreds of thousands of times, and must be fast.
	NewVU() (VU, error)

	// Returns the default (root) Group.
	GetDefaultGroup() *Group

	// Get and set options. The initial value will be whatever the script specifies (for JS,
	// `export let options = {}`); cmd/run.go will mix this in with CLI-, config- and env-provided
	// values and write it back to the runner.
	GetOptions() Options
	SetOptions(opts Options)
}

// A VU is a Virtual User, that can be scheduled by an Executor.
type VU interface {
	// Runs the VU once. The VU is responsible for handling the Halting Problem, eg. making sure
	// that execution actually stops when the context is cancelled.
	RunOnce(ctx context.Context) ([]stats.Sample, error)

	// Assign the VU a new ID. Called by the Executor upon creation, but may be called multiple
	// times if the VU is recycled because the test was scaled down and then back up.
	Reconfigure(id int64) error
}

// RunnerFunc wraps a function in a runner whose VUs will simply call that function.
type RunnerFunc func(ctx context.Context) ([]stats.Sample, error)

func (fn RunnerFunc) VU() *runnerFuncVU {
	return &runnerFuncVU{Fn: fn}
}

func (fn RunnerFunc) MakeArchive() *Archive {
	return nil
}

func (fn RunnerFunc) NewVU() (VU, error) {
	return fn.VU(), nil
}

func (fn RunnerFunc) GetDefaultGroup() *Group {
	return &Group{}
}

func (fn RunnerFunc) GetOptions() Options {
	return Options{}
}

func (fn RunnerFunc) SetOptions(opts Options) {
}

type runnerFuncVU struct {
	Fn RunnerFunc
	ID int64
}

func (fn runnerFuncVU) RunOnce(ctx context.Context) ([]stats.Sample, error) {
	if fn.Fn == nil {
		return []stats.Sample{}, nil
	}
	return fn.Fn(ctx)
}

func (fn *runnerFuncVU) Reconfigure(id int64) error {
	fn.ID = id
	return nil
}
<commit_msg>Re-exposed RunnerFuncVU<commit_after>/*
 *
 * k6 - a next-generation load testing tool
 * Copyright (C) 2016 Load Impact
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

package lib

import (
	"context"

	"github.com/loadimpact/k6/stats"
)

// Ensure RunnerFunc conforms to Runner.
var _ Runner = RunnerFunc(nil)

// A Runner is a factory for VUs. It should precompute as much as possible upon creation (parse
// ASTs, load files into memory, etc.), so that spawning VUs becomes as fast as possible.
// The Runner doesn't actually *do* anything in itself, the Executor is responsible for wrapping
// and scheduling these VUs for execution.
//
// TODO: Rename this to something more obvious? This name made sense a very long time ago.
type Runner interface {
	// Creates an Archive of the runner. There should be a corresponding NewFromArchive() function
	// that will restore the runner from the archive.
	MakeArchive() *Archive

	// Spawns a new VU. It's fine to make this function rather heavy, if it means a performance
	// improvement at runtime. Remember, this is called once per VU and normally only at the start
	// of a test - RunOnce() may be called hundreds of thousands of times, and must be fast.
	NewVU() (VU, error)

	// Returns the default (root) Group.
	GetDefaultGroup() *Group

	// Get and set options. The initial value will be whatever the script specifies (for JS,
	// `export let options = {}`); cmd/run.go will mix this in with CLI-, config- and env-provided
	// values and write it back to the runner.
	GetOptions() Options
	SetOptions(opts Options)
}

// A VU is a Virtual User, that can be scheduled by an Executor.
type VU interface {
	// Runs the VU once. The VU is responsible for handling the Halting Problem, eg. making sure
	// that execution actually stops when the context is cancelled.
	RunOnce(ctx context.Context) ([]stats.Sample, error)

	// Assign the VU a new ID. Called by the Executor upon creation, but may be called multiple
	// times if the VU is recycled because the test was scaled down and then back up.
	Reconfigure(id int64) error
}

// RunnerFunc wraps a function in a runner whose VUs will simply call that function.
type RunnerFunc func(ctx context.Context) ([]stats.Sample, error)

func (fn RunnerFunc) VU() *RunnerFuncVU {
	return &RunnerFuncVU{Fn: fn}
}

func (fn RunnerFunc) MakeArchive() *Archive {
	return nil
}

func (fn RunnerFunc) NewVU() (VU, error) {
	return fn.VU(), nil
}

func (fn RunnerFunc) GetDefaultGroup() *Group {
	return &Group{}
}

func (fn RunnerFunc) GetOptions() Options {
	return Options{}
}

func (fn RunnerFunc) SetOptions(opts Options) {
}

// A VU spawned by a RunnerFunc.
type RunnerFuncVU struct {
	Fn RunnerFunc
	ID int64
}

func (fn RunnerFuncVU) RunOnce(ctx context.Context) ([]stats.Sample, error) {
	if fn.Fn == nil {
		return []stats.Sample{}, nil
	}
	return fn.Fn(ctx)
}

func (fn *RunnerFuncVU) Reconfigure(id int64) error {
	fn.ID = id
	return nil
}
<|endoftext|>


<commit_before>package util

import "fmt"

type SdkError struct {
	Resp ErrorResponse
	Url  string
}

func (err *SdkError) Error() string {
	return fmt.Sprintf("Aliyun SDK Request Error:\nURL: %s\nCode: %s\nMessage: %s", err.Url, err.Resp.Code, err.Resp.Message)
}
<commit_msg>add RequestId & HostId to error string<commit_after>package util

import "fmt"

type SdkError struct {
	Resp ErrorResponse
	Url  string
}

func (err *SdkError) Error() string {
	return fmt.Sprintf("Aliyun SDK Request Error:\nURL: %s\nRequestId: %s\nHostId: %s\nCode: %s\nMessage: %s",
		err.Url, err.Resp.RequestId, err.Resp.HostId, err.Resp.Code, err.Resp.Message)
}
<|endoftext|>


<commit_before>// Copyright 2015 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package lease

import (
	"errors"
	"os"
	"sync"
)

// A sentinel error used when a lease has been revoked. See notes on particular
// methods.
type RevokedError struct {
}

func (re *RevokedError) Error() string {
	return "Lease revoked"
}

// A read-only wrapper around a file that may be revoked, when e.g. there is
// temporary disk space pressure.
//
// All methods are safe for concurrent access. Must be created with
// NewReadLease.
type ReadLease struct {
	// Functions injected by the user.
	revoked func()
	upgrade func(*os.File) *WriteLease

	mu sync.Mutex

	// The wrapped file. If the lease has been revoked, this will be nil.
	//
	// GUARDED_BY(mu)
	f *os.File
}

// Create a read lease wrapping the supplied file. The lease "owns" this file
// until it is revoked or upgraded, if ever.
//
// If the lease is revoked, the file will be closed and the supplied function
// will be notified (at most once).
//
// If the lease is upgraded, the supplied function will be used to create an
// appropriate write lease and then this lease will forget the file. The
// function must not return nil.
func NewReadLease(
	f *os.File,
	revoked func(),
	upgrade func(f *os.File) *WriteLease) (rl *ReadLease) {
	rl = &ReadLease{
		f:       f,
		revoked: revoked,
		upgrade: upgrade,
	}

	return
}

// Attempt to read within the wrapped file, returning an error of type
// *RevokedError if the lease has been revoked.
func (rl *ReadLease) ReadAt(p []byte, off int64) (n int, err error) {
	rl.mu.Lock()
	defer rl.mu.Unlock()

	// Do we still have a file?
	if rl.f == nil {
		err = &RevokedError{}
		return
	}

	// Read via the file.
	n, err = rl.f.ReadAt(p, off)

	return
}

// Attempt to revoke the lease, freeing any resources associated with it. It is
// an error to revoke more than once.
func (rl *ReadLease) Revoke() (err error) {
	err = errors.New("TODO")
	return
}

// Attempt to upgrade the lease to a write lease, returning an error of type
// *RevokedError if the lease has been revoked. After upgrading, it is as if
// the lease has been revoked.
func (rl *ReadLease) Upgrade() (wl *WriteLease, err error) {
	err = errors.New("TODO")
	return
}
<commit_msg>ReadLease.Revoke<commit_after>// Copyright 2015 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package lease

import (
	"errors"
	"os"
	"sync"
)

// A sentinel error used when a lease has been revoked. See notes on particular
// methods.
type RevokedError struct {
}

func (re *RevokedError) Error() string {
	return "Lease revoked"
}

// A read-only wrapper around a file that may be revoked, when e.g. there is
// temporary disk space pressure.
//
// All methods are safe for concurrent access. Must be created with
// NewReadLease.
type ReadLease struct {
	// Functions injected by the user.
	revoked func()
	upgrade func(*os.File) *WriteLease

	mu sync.Mutex

	// The wrapped file. If the lease has been revoked, this will be nil.
	//
	// GUARDED_BY(mu)
	f *os.File
}

// Create a read lease wrapping the supplied file. The lease "owns" this file
// until it is revoked or upgraded, if ever.
//
// If the lease is revoked, the file will be closed and the supplied function
// will be notified (at most once).
//
// If the lease is upgraded, the supplied function will be used to create an
// appropriate write lease and then this lease will forget the file. The
// function must not return nil.
func NewReadLease(
	f *os.File,
	revoked func(),
	upgrade func(f *os.File) *WriteLease) (rl *ReadLease) {
	rl = &ReadLease{
		f:       f,
		revoked: revoked,
		upgrade: upgrade,
	}

	return
}

// Attempt to read within the wrapped file, returning an error of type
// *RevokedError if the lease has been revoked.
func (rl *ReadLease) ReadAt(p []byte, off int64) (n int, err error) {
	rl.mu.Lock()
	defer rl.mu.Unlock()

	// Do we still have a file?
	if rl.f == nil {
		err = &RevokedError{}
		return
	}

	// Read via the file.
	n, err = rl.f.ReadAt(p, off)

	return
}

// Attempt to revoke the lease, freeing any resources associated with it. It is
// an error to revoke more than once.
func (rl *ReadLease) Revoke() (err error) {
	// Notify the user outside of the lock, if appropriate.
	shouldNotify := false
	defer func() {
		if shouldNotify {
			rl.revoked()
		}
	}()

	// Already revoked?
	rl.mu.Lock()
	defer rl.mu.Unlock()

	if rl.f == nil {
		err = &RevokedError{}
		return
	}

	// Revoke and notify.
	rl.f = nil
	shouldNotify = true

	return
}

// Attempt to upgrade the lease to a write lease, returning an error of type
// *RevokedError if the lease has been revoked. After upgrading, it is as if
// the lease has been revoked.
func (rl *ReadLease) Upgrade() (wl *WriteLease, err error) {
	err = errors.New("TODO")
	return
}
<|endoftext|>