<commit_before>// Copyright 2013 tsuru 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 app

import (
	"bytes"
	"github.com/globocom/tsuru/app/bind"
	"github.com/globocom/tsuru/db"
	"github.com/globocom/tsuru/log"
	"github.com/globocom/tsuru/provision"
	"github.com/globocom/tsuru/queue"
	"labix.org/v2/mgo/bson"
	. "launchpad.net/gocheck"
	stdlog "log"
	"strings"
)

func (s *S) TestHandleMessage(c *C) {
	s.provisioner.PrepareOutput([]byte("exported"))
	a := App{
		Name: "nemesis",
		Units: []Unit{
			{
				Name:    "i-00800",
				State:   "started",
				Machine: 19,
			},
		},
		Env: map[string]bind.EnvVar{
			"http_proxy": {
				Name:   "http_proxy",
				Value:  "http://myproxy.com:3128/",
				Public: true,
			},
		},
		State: string(provision.StatusStarted),
	}
	err := db.Session.Apps().Insert(a)
	c.Assert(err, IsNil)
	defer db.Session.Apps().Remove(bson.M{"name": a.Name})
	msg := queue.Message{Action: RegenerateApprc, Args: []string{a.Name}}
	handle(&msg)
	cmds := s.provisioner.GetCmds("", &a)
	c.Assert(cmds, HasLen, 1)
	output := strings.Replace(cmds[0].Cmd, "\n", " ", -1)
	outputRegexp := `^cat > /home/application/apprc <<END # generated by tsuru.*`
	outputRegexp += `export http_proxy="http://myproxy.com:3128/" END $`
	c.Assert(output, Matches, outputRegexp)
}

func (s *S) TestHandleMessageWithSpecificUnit(c *C) {
	s.provisioner.PrepareOutput([]byte("exported"))
	a := App{
		Name: "nemesis",
		Units: []Unit{
			{
				Name:    "nemesis/0",
				State:   "started",
				Machine: 19,
			},
			{
				Name:    "nemesis/1",
				State:   "started",
				Machine: 20,
			},
			{
				Name:    "nemesis/2",
				State:   "started",
				Machine: 23,
			},
		},
		Env: map[string]bind.EnvVar{
			"http_proxy": {
				Name:   "http_proxy",
				Value:  "http://myproxy.com:3128/",
				Public: true,
			},
		},
		State: string(provision.StatusStarted),
	}
	err := db.Session.Apps().Insert(a)
	c.Assert(err, IsNil)
	defer db.Session.Apps().Remove(bson.M{"name": a.Name})
	msg := queue.Message{Action: RegenerateApprc, Args: []string{a.Name, "nemesis/1"}}
	handle(&msg)
	cmds := s.provisioner.GetCmds("", &a)
	c.Assert(cmds, HasLen, 1)
	output := strings.Replace(cmds[0].Cmd, "\n", " ", -1)
	outputRegexp := `^cat > /home/application/apprc <<END # generated by tsuru.*`
	outputRegexp += `export http_proxy="http://myproxy.com:3128/" END $`
	c.Assert(output, Matches, outputRegexp)
}

func (s *S) TestHandleMessageErrors(c *C) {
	var data = []struct {
		action      string
		args        []string
		unitName    string
		expectedLog string
	}{
		{
			action:      "unknown-action",
			args:        []string{"does not matter"},
			expectedLog: `Error handling "unknown-action": invalid action.`,
		},
		{
			action: StartApp,
			args:   []string{"nemesis"},
			expectedLog: `Error handling "start-app" for the app "nemesis":` +
				` The status of the app and all units should be "started" (the app is "pending").`,
		},
		{
			action: StartApp,
			args:   []string{"totem", "totem/0", "totem/1"},
			expectedLog: `Error handling "start-app" for the app "totem":` +
				` The status of the app and all units should be "started" (the app is "started").`,
		},
		{
			action: RegenerateApprc,
			args:   []string{"nemesis"},
			expectedLog: `Error handling "regenerate-apprc" for the app "nemesis":` +
				` The status of the app and all units should be "started" (the app is "pending").`,
		},
		{
			action: RegenerateApprc,
			args:   []string{"totem", "totem/0", "totem/1"},
			expectedLog: `Error handling "regenerate-apprc" for the app "totem":` +
				` The status of the app and all units should be "started" (the app is "started").`,
		},
		{
			action:      RegenerateApprc,
			args:        []string{"unknown-app"},
			expectedLog: `Error handling "regenerate-apprc": app "unknown-app" does not exist.`,
		},
		{
			action:      RegenerateApprc,
			expectedLog: `Error handling "regenerate-apprc": this action requires at least 1 argument.`,
		},
		{
			action: RegenerateApprc,
			args:   []string{"marathon"},
			expectedLog: `Error handling "regenerate-apprc" for the app "marathon":` +
				` the app is in "error" state.`,
		},
		{
			action: RegenerateApprc,
			args:   []string{"territories"},
			expectedLog: `Error handling "regenerate-apprc" for the app "territories":` +
				` the app is down.`,
		},
	}
	var buf bytes.Buffer
	a := App{Name: "nemesis", State: "pending"}
	err := db.Session.Apps().Insert(a)
	c.Assert(err, IsNil)
	defer db.Session.Apps().Remove(bson.M{"name": a.Name})
	a = App{
		Name:  "totem",
		State: "started",
		Units: []Unit{
			{Name: "totem/0", State: "pending"},
			{Name: "totem/1", State: "started"},
		},
	}
	err = db.Session.Apps().Insert(a)
	c.Assert(err, IsNil)
	defer db.Session.Apps().Remove(bson.M{"name": a.Name})
	a = App{Name: "marathon", State: "error"}
	err = db.Session.Apps().Insert(a)
	c.Assert(err, IsNil)
	defer db.Session.Apps().Remove(bson.M{"name": a.Name})
	a = App{Name: "territories", State: "down"}
	err = db.Session.Apps().Insert(a)
	c.Assert(err, IsNil)
	defer db.Session.Apps().Remove(bson.M{"name": a.Name})
	log.SetLogger(stdlog.New(&buf, "", 0))
	for _, d := range data {
		message := queue.Message{Action: d.action}
		if len(d.args) > 0 {
			message.Args = d.args
		}
		handle(&message)
		defer queue.Delete(&message) // Sanity
	}
	content := buf.String()
	lines := strings.Split(content, "\n")
	for i, d := range data {
		var found bool
		for j := i; j < len(lines); j++ {
			if lines[j] == d.expectedLog {
				found = true
				break
			}
		}
		if !found {
			c.Errorf("\nWant: %q.\nGot:\n%s", d.expectedLog, content)
		}
	}
}

func (s *S) TestHandleRestartAppMessage(c *C) {
	s.provisioner.PrepareOutput([]byte("started"))
	a := App{
		Name: "nemesis",
		Units: []Unit{
			{
				Name:    "i-00800",
				State:   "started",
				Machine: 19,
			},
		},
		State: string(provision.StatusStarted),
	}
	err := db.Session.Apps().Insert(a)
	c.Assert(err, IsNil)
	defer db.Session.Apps().Remove(bson.M{"name": a.Name})
	message := queue.Message{Action: StartApp, Args: []string{a.Name}}
	handle(&message)
	cmds := s.provisioner.GetCmds("/var/lib/tsuru/hooks/restart", &a)
	c.Assert(cmds, HasLen, 1)
}

func (s *S) TestUnitListStarted(c *C) {
	var tests = []struct {
		input    []Unit
		expected bool
	}{
		{
			[]Unit{
				{State: "started"},
				{State: "started"},
				{State: "started"},
			},
			true,
		},
		{nil, true},
		{
			[]Unit{
				{State: "started"},
				{State: "blabla"},
			},
			false,
		},
	}
	for _, t := range tests {
		l := unitList(t.input)
		if got := l.Started(); got != t.expected {
			c.Errorf("l.Started(): want %v. Got %v.", t.expected, got)
		}
	}
}
<commit_msg>app: added test for handling of messages directly from the queue<commit_after>// Copyright 2013 tsuru 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 app

import (
	"bytes"
	"github.com/globocom/tsuru/app/bind"
	"github.com/globocom/tsuru/db"
	"github.com/globocom/tsuru/log"
	"github.com/globocom/tsuru/provision"
	"github.com/globocom/tsuru/queue"
	"labix.org/v2/mgo/bson"
	. "launchpad.net/gocheck"
	stdlog "log"
	"strings"
	"sync/atomic"
	"testing"
	"time"
)

func (s *S) TestHandleMessages(c *C) {
	if testing.Short() {
		c.Skip("Slow test.")
	}
	handler.state = stopped
	handler.start()

	s.provisioner.PrepareOutput([]byte("exported"))
	a := App{
		Name: "nemesis",
		Units: []Unit{
			{
				Name:    "i-00800",
				State:   "started",
				Machine: 19,
			},
		},
		State: provision.StatusStarted.String(),
	}
	err := db.Session.Apps().Insert(a)
	c.Check(err, IsNil)
	defer db.Session.Apps().Remove(bson.M{"name": a.Name})
	queue.Put(&queue.Message{Action: StartApp, Args: []string{a.Name}})
	time.Sleep(1e9)
	handler.stop()
	cmds := s.provisioner.GetCmds("/var/lib/tsuru/hooks/restart", &a)
	c.Check(cmds, HasLen, 1)

	// spin
	for atomic.LoadInt32(&handler.state) != stopped {
		time.Sleep(1e3)
	}
}

func (s *S) TestHandleMessage(c *C) {
	s.provisioner.PrepareOutput([]byte("exported"))
	a := App{
		Name: "nemesis",
		Units: []Unit{
			{
				Name:    "i-00800",
				State:   "started",
				Machine: 19,
			},
		},
		Env: map[string]bind.EnvVar{
			"http_proxy": {
				Name:   "http_proxy",
				Value:  "http://myproxy.com:3128/",
				Public: true,
			},
		},
		State: string(provision.StatusStarted),
	}
	err := db.Session.Apps().Insert(a)
	c.Assert(err, IsNil)
	defer db.Session.Apps().Remove(bson.M{"name": a.Name})
	msg := queue.Message{Action: RegenerateApprc, Args: []string{a.Name}}
	handle(&msg)
	cmds := s.provisioner.GetCmds("", &a)
	c.Assert(cmds, HasLen, 1)
	output := strings.Replace(cmds[0].Cmd, "\n", " ", -1)
	outputRegexp := `^cat > /home/application/apprc <<END # generated by tsuru.*`
	outputRegexp += `export http_proxy="http://myproxy.com:3128/" END $`
	c.Assert(output, Matches, outputRegexp)
}

func (s *S) TestHandleMessageWithSpecificUnit(c *C) {
	s.provisioner.PrepareOutput([]byte("exported"))
	a := App{
		Name: "nemesis",
		Units: []Unit{
			{
				Name:    "nemesis/0",
				State:   "started",
				Machine: 19,
			},
			{
				Name:    "nemesis/1",
				State:   "started",
				Machine: 20,
			},
			{
				Name:    "nemesis/2",
				State:   "started",
				Machine: 23,
			},
		},
		Env: map[string]bind.EnvVar{
			"http_proxy": {
				Name:   "http_proxy",
				Value:  "http://myproxy.com:3128/",
				Public: true,
			},
		},
		State: string(provision.StatusStarted),
	}
	err := db.Session.Apps().Insert(a)
	c.Assert(err, IsNil)
	defer db.Session.Apps().Remove(bson.M{"name": a.Name})
	msg := queue.Message{Action: RegenerateApprc, Args: []string{a.Name, "nemesis/1"}}
	handle(&msg)
	cmds := s.provisioner.GetCmds("", &a)
	c.Assert(cmds, HasLen, 1)
	output := strings.Replace(cmds[0].Cmd, "\n", " ", -1)
	outputRegexp := `^cat > /home/application/apprc <<END # generated by tsuru.*`
	outputRegexp += `export http_proxy="http://myproxy.com:3128/" END $`
	c.Assert(output, Matches, outputRegexp)
}

func (s *S) TestHandleMessageErrors(c *C) {
	var data = []struct {
		action      string
		args        []string
		unitName    string
		expectedLog string
	}{
		{
			action:      "unknown-action",
			args:        []string{"does not matter"},
			expectedLog: `Error handling "unknown-action": invalid action.`,
		},
		{
			action: StartApp,
			args:   []string{"nemesis"},
			expectedLog: `Error handling "start-app" for the app "nemesis":` +
				` The status of the app and all units should be "started" (the app is "pending").`,
		},
		{
			action: StartApp,
			args:   []string{"totem", "totem/0", "totem/1"},
			expectedLog: `Error handling "start-app" for the app "totem":` +
				` The status of the app and all units should be "started" (the app is "started").`,
		},
		{
			action: RegenerateApprc,
			args:   []string{"nemesis"},
			expectedLog: `Error handling "regenerate-apprc" for the app "nemesis":` +
				` The status of the app and all units should be "started" (the app is "pending").`,
		},
		{
			action: RegenerateApprc,
			args:   []string{"totem", "totem/0", "totem/1"},
			expectedLog: `Error handling "regenerate-apprc" for the app "totem":` +
				` The status of the app and all units should be "started" (the app is "started").`,
		},
		{
			action:      RegenerateApprc,
			args:        []string{"unknown-app"},
			expectedLog: `Error handling "regenerate-apprc": app "unknown-app" does not exist.`,
		},
		{
			action:      RegenerateApprc,
			expectedLog: `Error handling "regenerate-apprc": this action requires at least 1 argument.`,
		},
		{
			action: RegenerateApprc,
			args:   []string{"marathon"},
			expectedLog: `Error handling "regenerate-apprc" for the app "marathon":` +
				` the app is in "error" state.`,
		},
		{
			action: RegenerateApprc,
			args:   []string{"territories"},
			expectedLog: `Error handling "regenerate-apprc" for the app "territories":` +
				` the app is down.`,
		},
	}
	var buf bytes.Buffer
	a := App{Name: "nemesis", State: "pending"}
	err := db.Session.Apps().Insert(a)
	c.Assert(err, IsNil)
	defer db.Session.Apps().Remove(bson.M{"name": a.Name})
	a = App{
		Name:  "totem",
		State: "started",
		Units: []Unit{
			{Name: "totem/0", State: "pending"},
			{Name: "totem/1", State: "started"},
		},
	}
	err = db.Session.Apps().Insert(a)
	c.Assert(err, IsNil)
	defer db.Session.Apps().Remove(bson.M{"name": a.Name})
	a = App{Name: "marathon", State: "error"}
	err = db.Session.Apps().Insert(a)
	c.Assert(err, IsNil)
	defer db.Session.Apps().Remove(bson.M{"name": a.Name})
	a = App{Name: "territories", State: "down"}
	err = db.Session.Apps().Insert(a)
	c.Assert(err, IsNil)
	defer db.Session.Apps().Remove(bson.M{"name": a.Name})
	log.SetLogger(stdlog.New(&buf, "", 0))
	for _, d := range data {
		message := queue.Message{Action: d.action}
		if len(d.args) > 0 {
			message.Args = d.args
		}
		handle(&message)
		defer queue.Delete(&message) // Sanity
	}
	content := buf.String()
	lines := strings.Split(content, "\n")
	for i, d := range data {
		var found bool
		for j := i; j < len(lines); j++ {
			if lines[j] == d.expectedLog {
				found = true
				break
			}
		}
		if !found {
			c.Errorf("\nWant: %q.\nGot:\n%s", d.expectedLog, content)
		}
	}
}

func (s *S) TestHandleRestartAppMessage(c *C) {
	s.provisioner.PrepareOutput([]byte("started"))
	a := App{
		Name: "nemesis",
		Units: []Unit{
			{
				Name:    "i-00800",
				State:   "started",
				Machine: 19,
			},
		},
		State: string(provision.StatusStarted),
	}
	err := db.Session.Apps().Insert(a)
	c.Assert(err, IsNil)
	defer db.Session.Apps().Remove(bson.M{"name": a.Name})
	message := queue.Message{Action: StartApp, Args: []string{a.Name}}
	handle(&message)
	cmds := s.provisioner.GetCmds("/var/lib/tsuru/hooks/restart", &a)
	c.Assert(cmds, HasLen, 1)
}

func (s *S) TestUnitListStarted(c *C) {
	var tests = []struct {
		input    []Unit
		expected bool
	}{
		{
			[]Unit{
				{State: "started"},
				{State: "started"},
				{State: "started"},
			},
			true,
		},
		{nil, true},
		{
			[]Unit{
				{State: "started"},
				{State: "blabla"},
			},
			false,
		},
	}
	for _, t := range tests {
		l := unitList(t.input)
		if got := l.Started(); got != t.expected {
			c.Errorf("l.Started(): want %v. Got %v.", t.expected, got)
		}
	}
}
<|endoftext|>


<commit_before>package main

import (
	"crypto/tls"
	"flag"
	"fmt"
	"log"
	"math/rand"
	"os"
	"os/signal"
	"strconv"
	"strings"
	"syscall"
	"time"

	"github.com/BurntSushi/toml"
	"github.com/bitly/nsq/internal/app"
	"github.com/bitly/nsq/internal/version"
	"github.com/bitly/nsq/nsqd"
	"github.com/mreiferson/go-options"
)

type tlsRequiredOption int

func (t *tlsRequiredOption) Set(s string) error {
	s = strings.ToLower(s)
	if s == "tcp-https" {
		*t = nsqd.TLSRequiredExceptHTTP
		return nil
	}
	required, err := strconv.ParseBool(s)
	if required {
		*t = nsqd.TLSRequired
	} else {
		*t = nsqd.TLSNotRequired
	}
	return err
}

func (t *tlsRequiredOption) Get() interface{} { return *t }

func (t *tlsRequiredOption) String() string {
	return strconv.FormatInt(int64(*t), 10)
}

func (t *tlsRequiredOption) IsBoolFlag() bool { return true }

type tlsVersionOption uint16

func (t *tlsVersionOption) Set(s string) error {
	s = strings.ToLower(s)
	switch s {
	case "":
		return nil
	case "ssl3.0":
		*t = tls.VersionSSL30
	case "tls1.0":
		*t = tls.VersionTLS10
	case "tls1.1":
		*t = tls.VersionTLS11
	case "tls1.2":
		*t = tls.VersionTLS12
	default:
		return fmt.Errorf("unknown tlsVersionOption %q", s)
	}
	return nil
}

func (t *tlsVersionOption) Get() interface{} {
	return *t
}

func (t *tlsVersionOption) String() string {
	return strconv.FormatInt(int64(*t), 10)
}

func nsqFlagset() *flag.FlagSet {
	flagSet := flag.NewFlagSet("nsqd", flag.ExitOnError)

	// basic options
	flagSet.String("config", "", "path to config file")
	flagSet.Bool("version", false, "print version string")
	flagSet.Bool("verbose", false, "enable verbose logging")
	flagSet.Int64("worker-id", 0, "unique seed for message ID generation (int) in range [0,4096) (will default to a hash of hostname)")
	flagSet.String("https-address", "", "<addr>:<port> to listen on for HTTPS clients")
	flagSet.String("http-address", "0.0.0.0:4151", "<addr>:<port> to listen on for HTTP clients")
	flagSet.String("tcp-address", "0.0.0.0:4150", "<addr>:<port> to listen on for TCP clients")

	authHTTPAddresses := app.StringArray{}
	flagSet.Var(&authHTTPAddresses, "auth-http-address", "<addr>:<port> to query auth server (may be given multiple times)")

	flagSet.String("broadcast-address", "", "address that will be registered with lookupd (defaults to the OS hostname)")
	lookupdTCPAddrs := app.StringArray{}
	flagSet.Var(&lookupdTCPAddrs, "lookupd-tcp-address", "lookupd TCP address (may be given multiple times)")

	// diskqueue options
	flagSet.String("data-path", "", "path to store disk-backed messages")
	flagSet.Int64("mem-queue-size", 10000, "number of messages to keep in memory (per topic/channel)")
	flagSet.Int64("max-bytes-per-file", 104857600, "number of bytes per diskqueue file before rolling")
	flagSet.Int64("sync-every", 2500, "number of messages per diskqueue fsync")
	flagSet.Duration("sync-timeout", 2*time.Second, "duration of time per diskqueue fsync")

	// msg and command options
	flagSet.String("msg-timeout", "60s", "duration to wait before auto-requeing a message")
	flagSet.Duration("max-msg-timeout", 15*time.Minute, "maximum duration before a message will timeout")
	flagSet.Int64("max-msg-size", 1024768, "maximum size of a single message in bytes")
	flagSet.Duration("max-req-timeout", 1*time.Hour, "maximum requeuing timeout for a message")
	// remove, deprecated
	flagSet.Int64("max-message-size", 1024768, "(deprecated use --max-msg-size) maximum size of a single message in bytes")
	flagSet.Int64("max-body-size", 5*1024768, "maximum size of a single command body")

	// client overridable configuration options
	flagSet.Duration("max-heartbeat-interval", 60*time.Second, "maximum client configurable duration of time between client heartbeats")
	flagSet.Int64("max-rdy-count", 2500, "maximum RDY count for a client")
	flagSet.Int64("max-output-buffer-size", 64*1024, "maximum client configurable size (in bytes) for a client output buffer")
	flagSet.Duration("max-output-buffer-timeout", 1*time.Second, "maximum client configurable duration of time between flushing to a client")

	// statsd integration options
	flagSet.String("statsd-address", "", "UDP <addr>:<port> of a statsd daemon for pushing stats")
	flagSet.String("statsd-interval", "60s", "duration between pushing to statsd")
	flagSet.Bool("statsd-mem-stats", true, "toggle sending memory and GC stats to statsd")
	flagSet.String("statsd-prefix", "nsq.%s", "prefix used for keys sent to statsd (%s for host replacement)")

	// End to end percentile flags
	e2eProcessingLatencyPercentiles := app.FloatArray{}
	flagSet.Var(&e2eProcessingLatencyPercentiles, "e2e-processing-latency-percentile", "message processing time percentiles to keep track of (can be specified multiple times or comma separated, default none)")
	flagSet.Duration("e2e-processing-latency-window-time", 10*time.Minute, "calculate end to end latency quantiles for this duration of time (ie: 60s would only show quantile calculations from the past 60 seconds)")

	// TLS config
	flagSet.String("tls-cert", "", "path to certificate file")
	flagSet.String("tls-key", "", "path to private key file")
	flagSet.String("tls-client-auth-policy", "", "client certificate auth policy ('require' or 'require-verify')")
	flagSet.String("tls-root-ca-file", "", "path to private certificate authority pem")
	var tlsRequired tlsRequiredOption
	var tlsMinVersion tlsVersionOption
	flagSet.Var(&tlsRequired, "tls-required", "require TLS for client connections (true, false, tcp-https)")
	flagSet.Var(&tlsMinVersion, "tls-min-version", "minimum SSL/TLS version acceptable ('ssl3.0', 'tls1.0', 'tls1.1', or 'tls1.2')")

	// compression
	flagSet.Bool("deflate", true, "enable deflate feature negotiation (client compression)")
	flagSet.Int("max-deflate-level", 6, "max deflate compression level a client can negotiate (> values == > nsqd CPU usage)")
	flagSet.Bool("snappy", true, "enable snappy feature negotiation (client compression)")

	return flagSet
}

type config map[string]interface{}

// Validate settings in the config file, and fatal on errors
func (cfg config) Validate() {
	// special validation/translation
	if v, exists := cfg["tls_required"]; exists {
		var t tlsRequiredOption
		err := t.Set(fmt.Sprintf("%v", v))
		if err == nil {
			cfg["tls_required"] = t.String()
		} else {
			log.Fatalf("ERROR: failed parsing tls required %v", v)
		}
	}
	if v, exists := cfg["tls_min_version"]; exists {
		var t tlsVersionOption
		err := t.Set(fmt.Sprintf("%v", v))
		if err == nil {
			newVal := fmt.Sprintf("%v", t.Get())
			if newVal != "0" {
				cfg["tls_min_version"] = newVal
			} else {
				delete(cfg, "tls_min_version")
			}
		} else {
			log.Fatalf("ERROR: failed parsing tls min version %v", v)
		}
	}
}

func main() {

	flagSet := nsqFlagset()
	flagSet.Parse(os.Args[1:])

	rand.Seed(time.Now().UTC().UnixNano())

	if flagSet.Lookup("version").Value.(flag.Getter).Get().(bool) {
		fmt.Println(version.String("nsqd"))
		return
	}

	signalChan := make(chan os.Signal, 1)
	signal.Notify(signalChan, syscall.SIGINT, syscall.SIGTERM)

	var cfg config
	configFile := flagSet.Lookup("config").Value.String()
	if configFile != "" {
		_, err := toml.DecodeFile(configFile, &cfg)
		if err != nil {
			log.Fatalf("ERROR: failed to load config file %s - %s", configFile, err.Error())
		}
	}
	cfg.Validate()

	opts := nsqd.NewOptions()
	options.Resolve(opts, flagSet, cfg)
	nsqd := nsqd.New(opts)

	nsqd.LoadMetadata()
	err := nsqd.PersistMetadata()
	if err != nil {
		log.Fatalf("ERROR: failed to persist metadata - %s", err.Error())
	}
	nsqd.Main()
	<-signalChan
	nsqd.Exit()
}
<commit_msg>nsqd: clarify help text for --e2e-processing-latency-percentile<commit_after>package main

import (
	"crypto/tls"
	"flag"
	"fmt"
	"log"
	"math/rand"
	"os"
	"os/signal"
	"strconv"
	"strings"
	"syscall"
	"time"

	"github.com/BurntSushi/toml"
	"github.com/bitly/nsq/internal/app"
	"github.com/bitly/nsq/internal/version"
	"github.com/bitly/nsq/nsqd"
	"github.com/mreiferson/go-options"
)

type tlsRequiredOption int

func (t *tlsRequiredOption) Set(s string) error {
	s = strings.ToLower(s)
	if s == "tcp-https" {
		*t = nsqd.TLSRequiredExceptHTTP
		return nil
	}
	required, err := strconv.ParseBool(s)
	if required {
		*t = nsqd.TLSRequired
	} else {
		*t = nsqd.TLSNotRequired
	}
	return err
}

func (t *tlsRequiredOption) Get() interface{} { return *t }

func (t *tlsRequiredOption) String() string {
	return strconv.FormatInt(int64(*t), 10)
}

func (t *tlsRequiredOption) IsBoolFlag() bool { return true }

type tlsVersionOption uint16

func (t *tlsVersionOption) Set(s string) error {
	s = strings.ToLower(s)
	switch s {
	case "":
		return nil
	case "ssl3.0":
		*t = tls.VersionSSL30
	case "tls1.0":
		*t = tls.VersionTLS10
	case "tls1.1":
		*t = tls.VersionTLS11
	case "tls1.2":
		*t = tls.VersionTLS12
	default:
		return fmt.Errorf("unknown tlsVersionOption %q", s)
	}
	return nil
}

func (t *tlsVersionOption) Get() interface{} {
	return *t
}

func (t *tlsVersionOption) String() string {
	return strconv.FormatInt(int64(*t), 10)
}

func nsqFlagset() *flag.FlagSet {
	flagSet := flag.NewFlagSet("nsqd", flag.ExitOnError)

	// basic options
	flagSet.String("config", "", "path to config file")
	flagSet.Bool("version", false, "print version string")
	flagSet.Bool("verbose", false, "enable verbose logging")
	flagSet.Int64("worker-id", 0, "unique seed for message ID generation (int) in range [0,4096) (will default to a hash of hostname)")
	flagSet.String("https-address", "", "<addr>:<port> to listen on for HTTPS clients")
	flagSet.String("http-address", "0.0.0.0:4151", "<addr>:<port> to listen on for HTTP clients")
	flagSet.String("tcp-address", "0.0.0.0:4150", "<addr>:<port> to listen on for TCP clients")

	authHTTPAddresses := app.StringArray{}
	flagSet.Var(&authHTTPAddresses, "auth-http-address", "<addr>:<port> to query auth server (may be given multiple times)")

	flagSet.String("broadcast-address", "", "address that will be registered with lookupd (defaults to the OS hostname)")
	lookupdTCPAddrs := app.StringArray{}
	flagSet.Var(&lookupdTCPAddrs, "lookupd-tcp-address", "lookupd TCP address (may be given multiple times)")

	// diskqueue options
	flagSet.String("data-path", "", "path to store disk-backed messages")
	flagSet.Int64("mem-queue-size", 10000, "number of messages to keep in memory (per topic/channel)")
	flagSet.Int64("max-bytes-per-file", 104857600, "number of bytes per diskqueue file before rolling")
	flagSet.Int64("sync-every", 2500, "number of messages per diskqueue fsync")
	flagSet.Duration("sync-timeout", 2*time.Second, "duration of time per diskqueue fsync")

	// msg and command options
	flagSet.String("msg-timeout", "60s", "duration to wait before auto-requeing a message")
	flagSet.Duration("max-msg-timeout", 15*time.Minute, "maximum duration before a message will timeout")
	flagSet.Int64("max-msg-size", 1024768, "maximum size of a single message in bytes")
	flagSet.Duration("max-req-timeout", 1*time.Hour, "maximum requeuing timeout for a message")
	// remove, deprecated
	flagSet.Int64("max-message-size", 1024768, "(deprecated use --max-msg-size) maximum size of a single message in bytes")
	flagSet.Int64("max-body-size", 5*1024768, "maximum size of a single command body")

	// client overridable configuration options
	flagSet.Duration("max-heartbeat-interval", 60*time.Second, "maximum client configurable duration of time between client heartbeats")
	flagSet.Int64("max-rdy-count", 2500, "maximum RDY count for a client")
	flagSet.Int64("max-output-buffer-size", 64*1024, "maximum client configurable size (in bytes) for a client output buffer")
	flagSet.Duration("max-output-buffer-timeout", 1*time.Second, "maximum client configurable duration of time between flushing to a client")

	// statsd integration options
	flagSet.String("statsd-address", "", "UDP <addr>:<port> of a statsd daemon for pushing stats")
	flagSet.String("statsd-interval", "60s", "duration between pushing to statsd")
	flagSet.Bool("statsd-mem-stats", true, "toggle sending memory and GC stats to statsd")
	flagSet.String("statsd-prefix", "nsq.%s", "prefix used for keys sent to statsd (%s for host replacement)")

	// End to end percentile flags
	e2eProcessingLatencyPercentiles := app.FloatArray{}
	flagSet.Var(&e2eProcessingLatencyPercentiles, "e2e-processing-latency-percentile", "message processing time percentiles (as float (0, 1.0]) to track (can be specified multiple times or comma separated '1.0,0.99,0.95', default none)")
	flagSet.Duration("e2e-processing-latency-window-time", 10*time.Minute, "calculate end to end latency quantiles for this duration of time (ie: 60s would only show quantile calculations from the past 60 seconds)")

	// TLS config
	flagSet.String("tls-cert", "", "path to certificate file")
	flagSet.String("tls-key", "", "path to private key file")
	flagSet.String("tls-client-auth-policy", "", "client certificate auth policy ('require' or 'require-verify')")
	flagSet.String("tls-root-ca-file", "", "path to private certificate authority pem")
	var tlsRequired tlsRequiredOption
	var tlsMinVersion tlsVersionOption
	flagSet.Var(&tlsRequired, "tls-required", "require TLS for client connections (true, false, tcp-https)")
	flagSet.Var(&tlsMinVersion, "tls-min-version", "minimum SSL/TLS version acceptable ('ssl3.0', 'tls1.0', 'tls1.1', or 'tls1.2')")

	// compression
	flagSet.Bool("deflate", true, "enable deflate feature negotiation (client compression)")
	flagSet.Int("max-deflate-level", 6, "max deflate compression level a client can negotiate (> values == > nsqd CPU usage)")
	flagSet.Bool("snappy", true, "enable snappy feature negotiation (client compression)")

	return flagSet
}

type config map[string]interface{}

// Validate settings in the config file, and fatal on errors
func (cfg config) Validate() {
	// special validation/translation
	if v, exists := cfg["tls_required"]; exists {
		var t tlsRequiredOption
		err := t.Set(fmt.Sprintf("%v", v))
		if err == nil {
			cfg["tls_required"] = t.String()
		} else {
			log.Fatalf("ERROR: failed parsing tls required %v", v)
		}
	}
	if v, exists := cfg["tls_min_version"]; exists {
		var t tlsVersionOption
		err := t.Set(fmt.Sprintf("%v", v))
		if err == nil {
			newVal := fmt.Sprintf("%v", t.Get())
			if newVal != "0" {
				cfg["tls_min_version"] = newVal
			} else {
				delete(cfg, "tls_min_version")
			}
		} else {
			log.Fatalf("ERROR: failed parsing tls min version %v", v)
		}
	}
}

func main() {

	flagSet := nsqFlagset()
	flagSet.Parse(os.Args[1:])

	rand.Seed(time.Now().UTC().UnixNano())

	if flagSet.Lookup("version").Value.(flag.Getter).Get().(bool) {
		fmt.Println(version.String("nsqd"))
		return
	}

	signalChan := make(chan os.Signal, 1)
	signal.Notify(signalChan, syscall.SIGINT, syscall.SIGTERM)

	var cfg config
	configFile := flagSet.Lookup("config").Value.String()
	if configFile != "" {
		_, err := toml.DecodeFile(configFile, &cfg)
		if err != nil {
			log.Fatalf("ERROR: failed to load config file %s - %s", configFile, err.Error())
		}
	}
	cfg.Validate()

	opts := nsqd.NewOptions()
	options.Resolve(opts, flagSet, cfg)
	nsqd := nsqd.New(opts)

	nsqd.LoadMetadata()
	err := nsqd.PersistMetadata()
	if err != nil {
		log.Fatalf("ERROR: failed to persist metadata - %s", err.Error())
	}
	nsqd.Main()
	<-signalChan
	nsqd.Exit()
}
<|endoftext|>


<commit_before>/*
 * Implements a recursive file system watcher. This code is based on code from
 * https://github.com/gophertown/looper/blob/master/watch.go.
 */
package fs

import (
	"errors"
	"github.com/howeyc/fsnotify"
	"log"
	"os"
	"path/filepath"
)

// The event that occurred. This is a structure that wraps the
// fsnotify.FileEvent. The reason we don't expose fsnotify.FileEvent
// directly is to allow for future expansion of state information in
// the Event struct.
type Event struct {
	*fsnotify.FileEvent
}

// RecursiveWatcher represents the file system watcher and
// communication channels.
type RecursiveWatcher struct {
	*fsnotify.Watcher            // File system watcher
	Events            chan Event // The channel to send events on
}

// NewRecursiveWatcher creates a new file system watcher for path. It walks the directory
// tree at path adding each directory to the watcher. When the user creates a new directory
// it is also added to the list of directories to watch.
func NewRecursiveWatcher(path string) (*RecursiveWatcher, error) {
	directories := subdirs(path)
	if len(directories) == 0 {
		return nil, errors.New("No directories to watch")
	}

	watcher, err := fsnotify.NewWatcher()
	if err != nil {
		return nil, err
	}

	recursiveWatcher := &RecursiveWatcher{Watcher: watcher}
	recursiveWatcher.Events = make(chan Event, 10)

	for _, dir := range directories {
		recursiveWatcher.addDirectory(dir)
	}

	return recursiveWatcher, nil
}

// addDirectory adds a new directory to the file system watcher. It is automatically
// called when the watcher detects a directory create event.
func (watcher *RecursiveWatcher) addDirectory(dir string) {
	err := watcher.WatchFlags(dir, fsnotify.FSN_ALL)
	if err != nil {
		log.Println("Error watching directory: ", dir, err)
	}
}

// Start starts monitoring for file system events and sends the
// events on the Events channel. It also handles directory create
// events by adding the newly created directory to the list of
// directories to monitor.
func (watcher *RecursiveWatcher) Start() {
	go func() {
		for {
			select {
			case event := <-watcher.Event:
				watcher.handleEvent(event)
			case err := <-watcher.Error:
				log.Println("error:", err)
			}
		}
	}()
}

// handleEvent performs the actual task of adding directories newly created
// directories to be monitored and sending events on the Events channel.
func (watcher *RecursiveWatcher) handleEvent(event *fsnotify.FileEvent) {
	if event.IsCreate() {
		watcher.handleCreate(event)
	}

	e := Event{
		FileEvent: event,
	}

	watcher.Events <- e
}

// handleCreate checks if the created item is a directory and if so
// sets that directory up for monitoring.
func (watcher *RecursiveWatcher) handleCreate(event *fsnotify.FileEvent) {
	finfo, err := os.Stat(event.Name)
	if err != nil {
		log.Printf("Error on stat for %s: %s\n", event.Name, err.Error())
	} else if finfo.IsDir() {
		watcher.addDirectory(event.Name)
	}
}

// subdirs walks a directory creating a list of all subdirectories. It ignores
// hidden directories (directories starting with a '.' "dot")
func subdirs(path string) (paths []string) {
	filepath.Walk(path, func(subpath string, info os.FileInfo, err error) error {
		if err != nil {
			return err
		}

		if info.IsDir() {
			name := info.Name()
			hidden := filepath.HasPrefix(name, ".") && name != "." && name != ".."
			if hidden {
				return filepath.SkipDir
			} else {
				paths = append(paths, subpath)
			}
		}
		return nil
	})
	return paths
}
<commit_msg>Add back in communicating errors along a channel.<commit_after>/*
 * Implements a recursive file system watcher. This code is based on code from
 * https://github.com/gophertown/looper/blob/master/watch.go.
 */
package fs

import (
	"errors"
	"github.com/howeyc/fsnotify"
	"log"
	"os"
	"path/filepath"
)

// The event that occurred. This is a structure that wraps the
// fsnotify.FileEvent. The reason we don't expose fsnotify.FileEvent
// directly is to allow for future expansion of state information in
// the Event struct.
type Event struct {
	*fsnotify.FileEvent
}

// RecursiveWatcher represents the file system watcher and
// communication channels.
type RecursiveWatcher struct {
	*fsnotify.Watcher            // File system watcher
	Events            chan Event // The channel to send events on
	ErrorEvents       chan error
}

// NewRecursiveWatcher creates a new file system watcher for path. It walks the directory
// tree at path adding each directory to the watcher. When the user creates a new directory
// it is also added to the list of directories to watch.
func NewRecursiveWatcher(path string) (*RecursiveWatcher, error) {
	directories := subdirs(path)
	if len(directories) == 0 {
		return nil, errors.New("No directories to watch")
	}

	watcher, err := fsnotify.NewWatcher()
	if err != nil {
		return nil, err
	}

	recursiveWatcher := &RecursiveWatcher{Watcher: watcher}
	recursiveWatcher.Events = make(chan Event, 10)

	for _, dir := range directories {
		recursiveWatcher.addDirectory(dir)
	}

	return recursiveWatcher, nil
}

// addDirectory adds a new directory to the file system watcher. It is automatically
// called when the watcher detects a directory create event.
func (watcher *RecursiveWatcher) addDirectory(dir string) {
	err := watcher.WatchFlags(dir, fsnotify.FSN_ALL)
	if err != nil {
		log.Println("Error watching directory: ", dir, err)
	}
}

// Start starts monitoring for file system events and sends the
// events on the Events channel. It also handles directory create
// events by adding the newly created directory to the list of
// directories to monitor.
func (watcher *RecursiveWatcher) Start() {
	go func() {
		for {
			select {
			case event := <-watcher.Event:
				watcher.handleEvent(event)
			case err := <-watcher.Error:
				watcher.ErrorEvents <- err
				log.Println("error:", err)
			}
		}
	}()
}

// handleEvent performs the actual task of adding directories newly created
// directories to be monitored and sending events on the Events channel.
func (watcher *RecursiveWatcher) handleEvent(event *fsnotify.FileEvent) {
	if event.IsCreate() {
		watcher.handleCreate(event)
	}

	e := Event{
		FileEvent: event,
	}

	watcher.Events <- e
}

// handleCreate checks if the created item is a directory and if so
// sets that directory up for monitoring.
func (watcher *RecursiveWatcher) handleCreate(event *fsnotify.FileEvent) {
	finfo, err := os.Stat(event.Name)
	if err != nil {
		log.Printf("Error on stat for %s: %s\n", event.Name, err.Error())
	} else if finfo.IsDir() {
		watcher.addDirectory(event.Name)
	}
}

// subdirs walks a directory creating a list of all subdirectories. It ignores
// hidden directories (directories starting with a '.' "dot")
func subdirs(path string) (paths []string) {
	filepath.Walk(path, func(subpath string, info os.FileInfo, err error) error {
		if err != nil {
			return err
		}

		if info.IsDir() {
			name := info.Name()
			hidden := filepath.HasPrefix(name, ".") && name != "." && name != ".."
			if hidden {
				return filepath.SkipDir
			} else {
				paths = append(paths, subpath)
			}
		}
		return nil
	})
	return paths
}
<|endoftext|>


<commit_before>package game

import (
	"errors"
	"strconv"
	"strings"

	"github.com/ghthor/engine/net/protocol"
	"github.com/ghthor/engine/rpg2d"
	"github.com/ghthor/engine/rpg2d/coord"
	"github.com/ghthor/engine/rpg2d/entity"
	"github.com/ghthor/engine/sim/stime"
)

type actorCmd struct {
	timeIssued stime.Time
	cmd        string
	params     string
}

type moveRequest struct {
	stime.Time
	coord.Direction
}

type actorCmdRequest struct {
	moveRequest
}

type actorConn struct {
	// Comm interface to muxer used by WriteInput() method
	submitCmd chan<- actorCmd

	// Comm interface to muxer used by getEntity() method
	readCmdReq <-chan actorCmdRequest

	// Comm interface to muxer used by SendState() method
	sendState chan<- interface{}

	// Comm interface to muxer used by stopIO() method
	stop chan<- chan<- struct{}

	// External connection used to publish the world state
	protocol.Conn
}

func newActorConn(conn protocol.Conn) actorConn {
	return actorConn{Conn: conn}
}

// Object stored in the quad tree
type actorEntity struct {
	id int64

	name string

	cell   coord.Cell
	facing coord.Direction

	pathAction coord.PathAction
}

type actor struct {
	actorEntity
	actorConn
}

func (a actor) Entity() entity.Entity       { return a.actorEntity }
func (a actor) WriteState(rpg2d.WorldState) {}

func (e actorEntity) Id() int64        { return e.id }
func (e actorEntity) Cell() coord.Cell { return e.cell }
func (e actorEntity) Bounds() coord.Bounds {
	return coord.JoinBounds(coord.Bounds{
		e.cell,
		e.cell,
	}, e.pathAction.Bounds())
}

func (e actorEntity) ToState() entity.State { return e }
func (e actorEntity) IsDifferentFrom(entity.State) bool {
	return true
}

func (a *actorConn) startIO() {
	// Setup communication channels
	cmdCh := make(chan actorCmd)
	cmdReqCh := make(chan actorCmdRequest)
	outputCh := make(chan interface{})
	stopCh := make(chan chan<- struct{})

	// Set the channels accessible to the outside world
	a.submitCmd = cmdCh
	a.readCmdReq = cmdReqCh
	a.sendState = outputCh
	a.stop = stopCh

	// Establish the channel endpoints used inside the go routine
	var newCmd <-chan actorCmd
	var sendCmdReq chan<- actorCmdRequest
	var newState <-chan interface{}
	var stopReq <-chan chan<- struct{}

	newCmd = cmdCh
	sendCmdReq = cmdReqCh
	newState = outputCh
	stopReq = stopCh

	go func() {
		var hasStopped chan<- struct{}

		// Buffer of 1 used to store the most recently
		// received actor cmd from the network.
		var cmdReq actorCmdRequest

		updateCmdReqWith := func(c actorCmd) {
			switch c.cmd {
			case "move":
				// TODO This is a shit place to be having an error to deal with
				// TODO It needs to be dealt with at the packet handler level
				d, _ := coord.NewDirectionWithString(c.params)

				cmdReq.moveRequest = moveRequest{
					Time:      stime.Time(c.timeIssued),
					Direction: d,
				}
			}
		}

	unlocked:
		// # This select prioritizes the following events.
		// ## 2 potential events to respond to
		// 1. ReadCmdRequest() method requests the actor command request
		// 2. stopIO() method has been called
		select {
		case sendCmdReq <- cmdReq:
			// Transition: unlocked -> locked
			goto locked

		case hasStopped = <-stopReq:
			// Transition: unlocked -> exit
			goto exit
		default:
		}

		// ## 3 potential events to respond to
		// 1. SubmitInput() method has been called with a new command
		// 2. ReadCmdRequest() method requests the actor command request
		// 3. stopIO() method has been called
		select {
		case c := <-newCmd:
			updateCmdReqWith(c)

			// Transition: unlocked -> unlocked
			goto unlocked

		case sendCmdReq <- cmdReq:
			// Transition: unlocked -> locked
			goto locked

		case hasStopped = <-stopReq:
			// Transition: unlocked -> exit
			goto exit
		}

		panic("unclosed case in unlocked state select")

	locked:
		// Accepting and processing input commands is now on hold

		// ## 3 potential events to respond to
		// 1. WriteState() method has been called with a new world state
		// 2. ReadCmdRequest() method requests the actor command request.. again!
		// 3. stopIO() method has been called
		select {
		case _ = <-newState:
			// TODO send state out over connection

			// Reset the cmdReq object
			cmdReq = actorCmdRequest{}

			// Transition: locked -> unlocked
			goto unlocked

		case sendCmdReq <- cmdReq:
			// Transition: locked -> locked
			goto locked

		case hasStopped = <-stopReq:
			// Transition: locked -> exit
			goto exit
		}

		panic("unclosed case in locked state select")
	exit:
		hasStopped <- struct{}{}
	}()
}

func (c actorConn) SubmitCmd(cmd, params string) error {
	parts := strings.Split(cmd, "=")

	if len(parts) != 2 {
		return errors.New("invalid command")
	}

	timeIssued, err := strconv.ParseInt(parts[1], 10, 64)
	if err != nil {
		return err
	}

	c.submitCmd <- actorCmd{
		timeIssued: stime.Time(timeIssued),
		cmd:        parts[0],
		params:     params,
	}

	return nil
}

func (c actorConn) WriteState(s interface{}) error {
	c.sendState <- s
	return nil
}

func (a actorConn) stopIO() {
	hasStopped := make(chan struct{})

	a.stop <- hasStopped
	<-hasStopped
}
<commit_msg>Cull and Diff the world state then ship is down the tubes<commit_after>package game

import (
	"errors"
	"strconv"
	"strings"

	"github.com/ghthor/engine/net/protocol"
	"github.com/ghthor/engine/rpg2d"
	"github.com/ghthor/engine/rpg2d/coord"
	"github.com/ghthor/engine/rpg2d/entity"
	"github.com/ghthor/engine/sim/stime"
)

type actorCmd struct {
	timeIssued stime.Time
	cmd        string
	params     string
}

type moveRequest struct {
	stime.Time
	coord.Direction
}

type actorCmdRequest struct {
	moveRequest
}

type actorConn struct {
	// Comm interface to muxer used by WriteInput() method
	submitCmd chan<- actorCmd

	// Comm interface to muxer used by getEntity() method
	readCmdReq <-chan actorCmdRequest

	// Comm interface to muxer used by SendState() method
	sendState chan<- *rpg2d.WorldState

	// Comm interface to muxer used by stopIO() method
	stop chan<- chan<- struct{}

	// External connection used to publish the world state
	protocol.Conn

	lastState rpg2d.WorldState
}

func newActorConn(conn protocol.Conn) actorConn {
	return actorConn{Conn: conn}
}

// Object stored in the quad tree
type actorEntity struct {
	id int64

	name string

	cell   coord.Cell
	facing coord.Direction

	pathAction *coord.PathAction
}

type actorEntityState struct {
	EntityId int64 `json:"id"`

	Name string `json:"name"`

	Facing string     `json:"facing"`
	Cell   coord.Cell `json:"cell"`
	bounds coord.Bounds

	PathAction *coord.PathAction `json:"pathAction"`
}

type actor struct {
	actorEntity
	actorConn
}

func (a actor) Entity() entity.Entity  { return a.actorEntity }
func (e actorEntity) Id() int64        { return e.id }
func (e actorEntity) Cell() coord.Cell { return e.cell }
func (e actorEntity) Bounds() coord.Bounds {
	bounds := coord.Bounds{
		e.cell,
		e.cell,
	}

	if e.pathAction != nil {
		bounds = coord.JoinBounds(bounds, e.pathAction.Bounds())
	}

	return bounds
}

func (e actorEntity) ToState() entity.State {
	return actorEntityState{
		EntityId: e.id,

		Cell:   e.cell,
		Facing: e.facing.String(),

		bounds: e.Bounds(),

		PathAction: e.pathAction,
	}
}

func (e actorEntityState) Id() int64            { return e.EntityId }
func (e actorEntityState) Bounds() coord.Bounds { return e.bounds }
func (e actorEntityState) IsDifferentFrom(other entity.State) bool {
	o := other.(actorEntityState)

	switch {
	case e.Facing != o.Facing:
		return true

	case e.PathAction != nil && o.PathAction != nil:
		if *e.PathAction != *o.PathAction {
			return true
		}

	case e.PathAction == nil && o.PathAction != nil:
		return true
	case e.PathAction != nil && o.PathAction == nil:
		return true
	}

	return false
}

func (a *actorConn) startIO() {
	// Setup communication channels
	cmdCh := make(chan actorCmd)
	cmdReqCh := make(chan actorCmdRequest)
	outputCh := make(chan *rpg2d.WorldState)
	stopCh := make(chan chan<- struct{})

	// Set the channels accessible to the outside world
	a.submitCmd = cmdCh
	a.readCmdReq = cmdReqCh
	a.sendState = outputCh
	a.stop = stopCh

	// Establish the channel endpoints used inside the go routine
	var newCmd <-chan actorCmd
	var sendCmdReq chan<- actorCmdRequest
	var newState <-chan *rpg2d.WorldState
	var stopReq <-chan chan<- struct{}

	newCmd = cmdCh
	sendCmdReq = cmdReqCh
	newState = outputCh
	stopReq = stopCh

	go func() {
		var hasStopped chan<- struct{}

		// Buffer of 1 used to store the most recently
		// received actor cmd from the network.
		var cmdReq actorCmdRequest

		updateCmdReqWith := func(c actorCmd) {
			switch c.cmd {
			case "move":
				// TODO This is a shit place to be having an error to deal with
				// TODO It needs to be dealt with at the packet handler level
				d, _ := coord.NewDirectionWithString(c.params)

				cmdReq.moveRequest = moveRequest{
					Time:      stime.Time(c.timeIssued),
					Direction: d,
				}
			}
		}

	unlocked:
		// # This select prioritizes the following events.
		// ## 2 potential events to respond to
		// 1. ReadCmdRequest() method requests the actor command request
		// 2. stopIO() method has been called
		select {
		case sendCmdReq <- cmdReq:
			// Transition: unlocked -> locked
			goto locked

		case hasStopped = <-stopReq:
			// Transition: unlocked -> exit
			goto exit
		default:
		}

		// ## 3 potential events to respond to
		// 1. SubmitInput() method has been called with a new command
		// 2. ReadCmdRequest() method requests the actor command request
		// 3. stopIO() method has been called
		select {
		case c := <-newCmd:
			updateCmdReqWith(c)

			// Transition: unlocked -> unlocked
			goto unlocked

		case sendCmdReq <- cmdReq:
			// Transition: unlocked -> locked
			goto locked

		case hasStopped = <-stopReq:
			// Transition: unlocked -> exit
			goto exit
		}

		panic("unclosed case in unlocked state select")

	locked:
		// Accepting and processing input commands is now on hold

		// ## 3 potential events to respond to
		// 1. WriteState() method has been called with a new world state
		// 2. ReadCmdRequest() method requests the actor command request.. again!
		// 3. stopIO() method has been called
		select {
		case state := <-newState:
			if state != nil {
				a.SendJson("update", state)
			}

			// Reset the cmdReq object
			cmdReq = actorCmdRequest{}

			// Transition: locked -> unlocked
			goto unlocked

		case sendCmdReq <- cmdReq:
			// Transition: locked -> locked
			goto locked

		case hasStopped = <-stopReq:
			// Transition: locked -> exit
			goto exit
		}

		panic("unclosed case in locked state select")
	exit:
		hasStopped <- struct{}{}
	}()
}

func (c actorConn) SubmitCmd(cmd, params string) error {
	parts := strings.Split(cmd, "=")

	if len(parts) != 2 {
		return errors.New("invalid command")
	}

	timeIssued, err := strconv.ParseInt(parts[1], 10, 64)
	if err != nil {
		return err
	}

	c.submitCmd <- actorCmd{
		timeIssued: stime.Time(timeIssued),
		cmd:        parts[0],
		params:     params,
	}

	return nil
}

// Culls the world state to the actor's viewport
func (a *actor) WriteState(state rpg2d.WorldState) {
	c := a.Cell()

	state = state.Cull(coord.Bounds{
		c.Add(-26, 26),
		c.Add(26, -26),
	})

	a.actorConn.WriteState(state)
}

// Diffs the world state so only the changes are sent
func (a *actorConn) WriteState(state rpg2d.WorldState) {
	diff := a.lastState.Diff(state)
	a.lastState = state

	// Will need this when I start comparing for terrain type changes
	// a.lastState.Prepare()

	if len(diff.Entities) > 0 || len(diff.Removed) > 0 || diff.TerrainMap != nil {
		diff.Prepare()
		a.sendState <- &diff
	} else {
		a.sendState <- nil
	}
}

func (a actorConn) stopIO() {
	hasStopped := make(chan struct{})

	a.stop <- hasStopped
	<-hasStopped
}
<|endoftext|>


<commit_before>package tchannel_test

// Copyright (c) 2015 Uber Technologies, Inc.

// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

import (
	"os"
	"testing"
	"time"

	. "github.com/uber/tchannel/golang"

	"github.com/stretchr/testify/require"
	"github.com/uber/tchannel/golang/raw"
	"github.com/uber/tchannel/golang/testutils"
)

func tagsForOutboundCall(serverCh *Channel, clientCh *Channel, operation string) map[string]string {
	host, _ := os.Hostname()
	return map[string]string{
		"app":             clientCh.PeerInfo().ProcessName,
		"host":            host,
		"service":         clientCh.PeerInfo().ServiceName,
		"target-service":  serverCh.PeerInfo().ServiceName,
		"target-endpoint": operation,
	}
}

func tagsForInboundCall(serverCh *Channel, clientCh *Channel, operation string) map[string]string {
	host, _ := os.Hostname()
	return map[string]string{
		"app":             serverCh.PeerInfo().ProcessName,
		"host":            host,
		"service":         serverCh.PeerInfo().ServiceName,
		"calling-service": clientCh.PeerInfo().ServiceName,
		"endpoint":        operation,
	}
}

func TestStatsCalls(t *testing.T) {
	testutils.SetTimeout(t, time.Second)

	initialTime := time.Date(2015, 2, 1, 10, 10, 0, 0, time.UTC)
	nowFn := testutils.NowStub(GetTimeNow(), initialTime)
	defer testutils.ResetNowStub(GetTimeNow())

	// time.Now will be called in this order for each call:
	// sender records time they started sending
	// receiver records time the request is sent to application
	// receiver calculates application handler latency
	// sender records call latency
	// So expected times are going to be:
	// Inbound latency: 50
	// Outbound latency: 150
	nowFn(50 * time.Millisecond)

	serverStats := newRecordingStatsReporter()
	serverOpts := &testutils.ChannelOpts{
		StatsReporter: serverStats,
	}
	require.NoError(t, testutils.WithServer(serverOpts, func(serverCh *Channel, hostPort string) {
		handler := raw.Wrap(newTestHandler(t))
		serverCh.Register(handler, "echo")
		serverCh.Register(handler, "app-error")

		clientStats := newRecordingStatsReporter()
		ch, err := testutils.NewClient(&testutils.ChannelOpts{StatsReporter: clientStats})
		require.NoError(t, err)

		ctx, cancel := NewContext(time.Second * 5)
		defer cancel()

		_, _, _, err = raw.Call(ctx, ch, hostPort, testServiceName, "echo", []byte("Headers"), []byte("Body"))
		require.NoError(t, err)

		_, _, resp, err := raw.Call(ctx, ch, hostPort, testServiceName, "app-error", nil, nil)
		require.NoError(t, err)
		require.True(t, resp.ApplicationError(), "expected application error")

		outboundTags := tagsForOutboundCall(serverCh, ch, "echo")
		clientStats.Expected.IncCounter("outbound.calls.send", outboundTags, 1)
		clientStats.Expected.IncCounter("outbound.calls.success", outboundTags, 1)
		clientStats.Expected.RecordTimer("outbound.calls.latency", outboundTags, 150*time.Millisecond)
		outboundTags["target-endpoint"] = "app-error"
		clientStats.Expected.IncCounter("outbound.calls.send", outboundTags, 1)
		clientStats.Expected.IncCounter("outbound.calls.app-errors", outboundTags, 1)
		clientStats.Expected.RecordTimer("outbound.calls.latency", outboundTags, 150*time.Millisecond)

		inboundTags := tagsForInboundCall(serverCh, ch, "echo")
		serverStats.Expected.IncCounter("inbound.calls.recvd", inboundTags, 1)
		serverStats.Expected.IncCounter("inbound.calls.success", inboundTags, 1)
		serverStats.Expected.RecordTimer("inbound.calls.latency", inboundTags, 50*time.Millisecond)
		inboundTags["endpoint"] = "app-error"
		serverStats.Expected.IncCounter("inbound.calls.recvd", inboundTags, 1)
		serverStats.Expected.IncCounter("inbound.calls.app-errors", inboundTags, 1)
		serverStats.Expected.RecordTimer("inbound.calls.latency", inboundTags, 50*time.Millisecond)

		clientStats.Validate(t)
		serverStats.Validate(t)
	}))
}
<commit_msg>Use different latencies for "app-error" call.<commit_after>package tchannel_test

// Copyright (c) 2015 Uber Technologies, Inc.

// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

import (
	"os"
	"testing"
	"time"

	. "github.com/uber/tchannel/golang"

	"github.com/stretchr/testify/require"
	"github.com/uber/tchannel/golang/raw"
	"github.com/uber/tchannel/golang/testutils"
)

func tagsForOutboundCall(serverCh *Channel, clientCh *Channel, operation string) map[string]string {
	host, _ := os.Hostname()
	return map[string]string{
		"app":             clientCh.PeerInfo().ProcessName,
		"host":            host,
		"service":         clientCh.PeerInfo().ServiceName,
		"target-service":  serverCh.PeerInfo().ServiceName,
		"target-endpoint": operation,
	}
}

func tagsForInboundCall(serverCh *Channel, clientCh *Channel, operation string) map[string]string {
	host, _ := os.Hostname()
	return map[string]string{
		"app":             serverCh.PeerInfo().ProcessName,
		"host":            host,
		"service":         serverCh.PeerInfo().ServiceName,
		"calling-service": clientCh.PeerInfo().ServiceName,
		"endpoint":        operation,
	}
}

func TestStatsCalls(t *testing.T) {
	defer testutils.SetTimeout(t, time.Second)()

	initialTime := time.Date(2015, 2, 1, 10, 10, 0, 0, time.UTC)
	nowFn := testutils.NowStub(GetTimeNow(), initialTime)
	defer testutils.ResetNowStub(GetTimeNow())
	// time.Now will be called in this order for each call:
	// sender records time they started sending
	// receiver records time the request is sent to application
	// receiver calculates application handler latency
	// sender records call latency
	// so expected inbound latency = incrementor, outbound = 3 * incrementor

	serverStats := newRecordingStatsReporter()
	serverOpts := &testutils.ChannelOpts{
		StatsReporter: serverStats,
	}
	require.NoError(t, testutils.WithServer(serverOpts, func(serverCh *Channel, hostPort string) {
		handler := raw.Wrap(newTestHandler(t))
		serverCh.Register(handler, "echo")
		serverCh.Register(handler, "app-error")

		clientStats := newRecordingStatsReporter()
		ch, err := testutils.NewClient(&testutils.ChannelOpts{StatsReporter: clientStats})
		require.NoError(t, err)

		ctx, cancel := NewContext(time.Second * 5)
		defer cancel()

		// Set now incrementor to 50ms, so expected Inbound latency is 50ms, outbound is 150ms.
		nowFn(50 * time.Millisecond)
		_, _, _, err = raw.Call(ctx, ch, hostPort, testServiceName, "echo", []byte("Headers"), []byte("Body"))
		require.NoError(t, err)

		outboundTags := tagsForOutboundCall(serverCh, ch, "echo")
		clientStats.Expected.IncCounter("outbound.calls.send", outboundTags, 1)
		clientStats.Expected.IncCounter("outbound.calls.success", outboundTags, 1)
		clientStats.Expected.RecordTimer("outbound.calls.latency", outboundTags, 150*time.Millisecond)
		inboundTags := tagsForInboundCall(serverCh, ch, "echo")
		serverStats.Expected.IncCounter("inbound.calls.recvd", inboundTags, 1)
		serverStats.Expected.IncCounter("inbound.calls.success", inboundTags, 1)
		serverStats.Expected.RecordTimer("inbound.calls.latency", inboundTags, 50*time.Millisecond)

		// Expected inbound latency = 70ms, outbound = 210ms.
		nowFn(70 * time.Millisecond)
		_, _, resp, err := raw.Call(ctx, ch, hostPort, testServiceName, "app-error", nil, nil)
		require.NoError(t, err)
		require.True(t, resp.ApplicationError(), "expected application error")

		outboundTags = tagsForOutboundCall(serverCh, ch, "app-error")
		clientStats.Expected.IncCounter("outbound.calls.send", outboundTags, 1)
		clientStats.Expected.IncCounter("outbound.calls.app-errors", outboundTags, 1)
		clientStats.Expected.RecordTimer("outbound.calls.latency", outboundTags, 210*time.Millisecond)
		inboundTags = tagsForInboundCall(serverCh, ch, "app-error")
		serverStats.Expected.IncCounter("inbound.calls.recvd", inboundTags, 1)
		serverStats.Expected.IncCounter("inbound.calls.app-errors", inboundTags, 1)
		serverStats.Expected.RecordTimer("inbound.calls.latency", inboundTags, 70*time.Millisecond)

		clientStats.Validate(t)
		serverStats.Validate(t)
	}))
}
<|endoftext|>


<commit_before>package gerrit

import (
	"crypto/md5"
	"crypto/rand"
	"encoding/base64"
	"fmt"
	"io"
	"net/http"
	"strings"
)

const (
	// HTTP Basic Authentication
	authTypeBasic = 1
	// HTTP Digest Authentication
	authTypeDigest = 2
	// HTTP Cookie Authentication
	authTypeCookie = 3
)

// AuthenticationService contains Authentication related functions.
//
// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api.html#authentication
type AuthenticationService struct {
	client *Client

	// Storage for authentication
	// Username or name of cookie
	name string
	// Password or value of cookie
	secret   string
	authType int
}

// SetBasicAuth sets basic parameters for HTTP Basic auth
func (s *AuthenticationService) SetBasicAuth(username, password string) {
	s.name = username
	s.secret = password
	s.authType = authTypeBasic
}

// SetDigestAuth sets digest parameters for HTTP Digest auth.
func (s *AuthenticationService) SetDigestAuth(username, password string) {
	s.name = username
	s.secret = password
	s.authType = authTypeDigest
}

// digestAuthHeader is called by gerrit.Client.Do in the event the server
// returns 401 Unauthorized and authType was set to authTypeDigest. The
// resulting string is used to set the Authorization header before retrying
// the request.
func (s *AuthenticationService) digestAuthHeader(response *http.Response) (string, error) {
	authenticateHeader := response.Header.Get("WWW-Authenticate")
	if authenticateHeader == "" {
		return "", fmt.Errorf("WWW-Authenticate header is missing")
	}

	split := strings.SplitN(authenticateHeader, " ", 2)
	if len(split) != 2 {
		return "", fmt.Errorf("WWW-Authenticate header is invalid")
	}

	if split[0] != "Digest" {
		return "", fmt.Errorf("WWW-Authenticate header type is not Digest")
	}

	// Iterate over all the fields from the WWW-Authenticate header
	// and create a map of keys and values.
	authenticate := map[string]string{}
	for _, value := range strings.Split(split[1], ",") {
		kv := strings.SplitN(value, "=", 2)
		if len(kv) != 2 {
			continue
		}

		key := strings.Trim(strings.Trim(kv[0], " "), "\"")
		value := strings.Trim(strings.Trim(kv[1], " "), "\"")
		authenticate[key] = value
	}

	// Gerrit usually responds without providing the algorithm.  According
	// to RFC2617 if no algorithm is provided then the default is to use
	// MD5.  At the time this code was implemented Gerrit did no appear
	// to support other algorithms or provide a means of changing the
	// algorithm.
	if value, ok := authenticate["algorithm"]; ok {
		if value != "MD5" {
			return "", fmt.Errorf(
				"algorithm not implemented: %s", value)
		}
	}

	realmHeader := authenticate["realm"]
	qopHeader := authenticate["qop"]
	nonceHeader := authenticate["nonce"]

	// If the server does not inform us what the uri is supposed
	// to be then use the last requests's uri instead.
	if _, ok := authenticate["uri"]; !ok {
		authenticate["uri"] = response.Request.URL.Path
	}

	uriHeader := authenticate["uri"]

	// A1
	h := md5.New()
	A1 := fmt.Sprintf("%s:%s:%s", s.name, realmHeader, s.secret)
	io.WriteString(h, A1)
	HA1 := fmt.Sprintf("%x", h.Sum(nil))

	// A2
	h = md5.New()
	A2 := fmt.Sprintf("GET:%s", uriHeader)
	io.WriteString(h, A2)
	HA2 := fmt.Sprintf("%x", h.Sum(nil))

	k := make([]byte, 12)
	for bytes := 0; bytes < len(k); {
		n, err := rand.Read(k[bytes:])
		if err != nil {
			return "", fmt.Errorf("cnonce generation failed: %s", err)
		}
		bytes += n
	}
	cnonce := base64.StdEncoding.EncodeToString(k)
	digest := md5.New()
	digest.Write([]byte(strings.Join([]string{HA1, nonceHeader, "00000001", cnonce, qopHeader, HA2}, ":")))
	responseField := fmt.Sprintf("%x", digest.Sum(nil))

	return fmt.Sprintf(
		`Digest username="%s", realm="%s", nonce="%s", uri="%s", cnonce="%s", nc=00000001, qop=%s, response="%s"`,
		s.name, realmHeader, nonceHeader, uriHeader, cnonce, qopHeader, responseField), nil
}

// SetCookieAuth sets basic parameters for HTTP Cookie
func (s *AuthenticationService) SetCookieAuth(name, value string) {
	s.name = name
	s.secret = value
	s.authType = authTypeCookie
}

// HasBasicAuth checks if the auth type is HTTP Basic auth
func (s *AuthenticationService) HasBasicAuth() bool {
	return s.authType == authTypeBasic
}

// HasDigestAuth checks if the auth type is HTTP Digest based
func (s *AuthenticationService) HasDigestAuth() bool {
	return s.authType == authTypeDigest
}

// HasCookieAuth checks if the auth type is HTTP Cookie based
func (s *AuthenticationService) HasCookieAuth() bool {
	return s.authType == authTypeCookie
}

// HasAuth checks if an auth type is used
func (s *AuthenticationService) HasAuth() bool {
	return s.authType > 0
}

// ResetAuth resets all former authentification settings
func (s *AuthenticationService) ResetAuth() {
	s.name = ""
	s.secret = ""
	s.authType = 0
}
<commit_msg>Removed obsolete whitespace<commit_after>package gerrit

import (
	"crypto/md5"
	"crypto/rand"
	"encoding/base64"
	"fmt"
	"io"
	"net/http"
	"strings"
)

const (
	// HTTP Basic Authentication
	authTypeBasic = 1
	// HTTP Digest Authentication
	authTypeDigest = 2
	// HTTP Cookie Authentication
	authTypeCookie = 3
)

// AuthenticationService contains Authentication related functions.
//
// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api.html#authentication
type AuthenticationService struct {
	client *Client

	// Storage for authentication
	// Username or name of cookie
	name string
	// Password or value of cookie
	secret   string
	authType int
}

// SetBasicAuth sets basic parameters for HTTP Basic auth
func (s *AuthenticationService) SetBasicAuth(username, password string) {
	s.name = username
	s.secret = password
	s.authType = authTypeBasic
}

// SetDigestAuth sets digest parameters for HTTP Digest auth.
func (s *AuthenticationService) SetDigestAuth(username, password string) {
	s.name = username
	s.secret = password
	s.authType = authTypeDigest
}

// digestAuthHeader is called by gerrit.Client.Do in the event the server
// returns 401 Unauthorized and authType was set to authTypeDigest. The
// resulting string is used to set the Authorization header before retrying
// the request.
func (s *AuthenticationService) digestAuthHeader(response *http.Response) (string, error) {
	authenticateHeader := response.Header.Get("WWW-Authenticate")
	if authenticateHeader == "" {
		return "", fmt.Errorf("WWW-Authenticate header is missing")
	}

	split := strings.SplitN(authenticateHeader, " ", 2)
	if len(split) != 2 {
		return "", fmt.Errorf("WWW-Authenticate header is invalid")
	}

	if split[0] != "Digest" {
		return "", fmt.Errorf("WWW-Authenticate header type is not Digest")
	}

	// Iterate over all the fields from the WWW-Authenticate header
	// and create a map of keys and values.
	authenticate := map[string]string{}
	for _, value := range strings.Split(split[1], ",") {
		kv := strings.SplitN(value, "=", 2)
		if len(kv) != 2 {
			continue
		}

		key := strings.Trim(strings.Trim(kv[0], " "), "\"")
		value := strings.Trim(strings.Trim(kv[1], " "), "\"")
		authenticate[key] = value
	}

	// Gerrit usually responds without providing the algorithm.  According
	// to RFC2617 if no algorithm is provided then the default is to use
	// MD5. At the time this code was implemented Gerrit did not appear
	// to support other algorithms or provide a means of changing the
	// algorithm.
	if value, ok := authenticate["algorithm"]; ok {
		if value != "MD5" {
			return "", fmt.Errorf(
				"algorithm not implemented: %s", value)
		}
	}

	realmHeader := authenticate["realm"]
	qopHeader := authenticate["qop"]
	nonceHeader := authenticate["nonce"]

	// If the server does not inform us what the uri is supposed
	// to be then use the last requests's uri instead.
	if _, ok := authenticate["uri"]; !ok {
		authenticate["uri"] = response.Request.URL.Path
	}

	uriHeader := authenticate["uri"]

	// A1
	h := md5.New()
	A1 := fmt.Sprintf("%s:%s:%s", s.name, realmHeader, s.secret)
	io.WriteString(h, A1)
	HA1 := fmt.Sprintf("%x", h.Sum(nil))

	// A2
	h = md5.New()
	A2 := fmt.Sprintf("GET:%s", uriHeader)
	io.WriteString(h, A2)
	HA2 := fmt.Sprintf("%x", h.Sum(nil))

	k := make([]byte, 12)
	for bytes := 0; bytes < len(k); {
		n, err := rand.Read(k[bytes:])
		if err != nil {
			return "", fmt.Errorf("cnonce generation failed: %s", err)
		}
		bytes += n
	}
	cnonce := base64.StdEncoding.EncodeToString(k)
	digest := md5.New()
	digest.Write([]byte(strings.Join([]string{HA1, nonceHeader, "00000001", cnonce, qopHeader, HA2}, ":")))
	responseField := fmt.Sprintf("%x", digest.Sum(nil))

	return fmt.Sprintf(
		`Digest username="%s", realm="%s", nonce="%s", uri="%s", cnonce="%s", nc=00000001, qop=%s, response="%s"`,
		s.name, realmHeader, nonceHeader, uriHeader, cnonce, qopHeader, responseField), nil
}

// SetCookieAuth sets basic parameters for HTTP Cookie
func (s *AuthenticationService) SetCookieAuth(name, value string) {
	s.name = name
	s.secret = value
	s.authType = authTypeCookie
}

// HasBasicAuth checks if the auth type is HTTP Basic auth
func (s *AuthenticationService) HasBasicAuth() bool {
	return s.authType == authTypeBasic
}

// HasDigestAuth checks if the auth type is HTTP Digest based
func (s *AuthenticationService) HasDigestAuth() bool {
	return s.authType == authTypeDigest
}

// HasCookieAuth checks if the auth type is HTTP Cookie based
func (s *AuthenticationService) HasCookieAuth() bool {
	return s.authType == authTypeCookie
}

// HasAuth checks if an auth type is used
func (s *AuthenticationService) HasAuth() bool {
	return s.authType > 0
}

// ResetAuth resets all former authentification settings
func (s *AuthenticationService) ResetAuth() {
	s.name = ""
	s.secret = ""
	s.authType = 0
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"image"
	"image/color"
	"image/draw"
	"os"
	"time"

	. "github.com/eaburns/quart/geom"
	"github.com/eaburns/quart/phys"

	"github.com/skelterjohn/go.wde"
)

const (
	width  = 640
	height = 480

	// Jerk is the amout by which the acceleration changes when an
	// arrow key is pressed.
	jerk = 5
)

var (
	black  = color.RGBA{A: 255}
	white  = color.RGBA{R: 255, G: 255, B: 255, A: 255}
	red    = color.RGBA{R: 255, A: 255}
	green  = color.RGBA{G: 255, A: 255}
	blue   = color.RGBA{B: 255, A: 255}
	purple = color.RGBA{R: 255, B: 255, A: 255}
	teal   = color.RGBA{G: 255, B: 255, A: 255}
)

var (
	vel, acc Vector
	circle   = Circle{Center: Point{200, 200}, Radius: 50}

	// Sides is the set of polygon sides.
	sides = []Side{
		{{400, 200}, {300, 300}},
		{{300, 300}, {400, 400}},
		{{400, 400}, {500, 300}},
		{{500, 300}, {400, 200}},
	}

	// Click is the position of the latest mouse clicke
	click = Point{-1, -1}

	// Cursor is the current cursor position.
	cursor Point

	// Changed is true if anything has changeds and needs a redraw
	changed bool
)

func main() {
	go mainLoop()
	wde.Run()
}

func mainLoop() {
	win, err := wde.NewWindow(width, height)
	if err != nil {
		panic(err)
	}
	win.SetTitle("geom test")
	win.Show()

	drawScene(win)

	tick := time.NewTicker(40 * time.Millisecond)
	for {
		select {
		case ev, ok := <-win.EventChan():
			if !ok {
				os.Exit(0)
			}
			switch ev := ev.(type) {
			case wde.CloseEvent:
				os.Exit(0)

			case wde.KeyDownEvent:
				switch ev.Key {
				case "left_arrow":
					acc[0] = -jerk
				case "right_arrow":
					acc[0] = jerk
				case "up_arrow":
					acc[1] = jerk
				case "down_arrow":
					acc[1] = -jerk
				default:
					fmt.Println("Pressed ", ev.Key)
				}

			case wde.KeyUpEvent:
				switch ev.Key {
				case "left_arrow", "right_arrow":
					acc[0] = 0
				case "up_arrow", "down_arrow":
					acc[1] = 0
				}

			case wde.MouseDraggedEvent:
				mouseMove(ev.MouseEvent)
			case wde.MouseMovedEvent:
				mouseMove(ev.MouseEvent)
			case wde.MouseDownEvent:
				mouseDown(wde.MouseButtonEvent(ev))
			case wde.MouseUpEvent:
				mouseUp(wde.MouseButtonEvent(ev))
			}

		case <-tick.C:
			if !changed && acc.Equals(Vector{}) {
				continue
			}
			changed = false
			vel.Add(acc)
			circle = phys.MoveCircle(circle, vel, sides)
			vel = Vector{}
			drawScene(win)
		}
	}
}

func mouseMove(ev wde.MouseEvent) {
	changed = true
	cursor = Point{float64(ev.Where.X), float64(height - ev.Where.Y - 1)}
}

func mouseDown(ev wde.MouseButtonEvent) {
	switch ev.Which {
	case wde.LeftButton:
		click = Point{float64(ev.Where.X), float64(height - ev.Where.Y - 1)}
	}
}

func mouseUp(ev wde.MouseButtonEvent) {
	switch ev.Which {
	case wde.LeftButton:
		sides = append(sides, Side{click, cursor})
		click = Point{-1, -1}
	}
}

func drawScene(win wde.Window) {
	clear(win)
	cv := ImageCanvas{win.Screen()}

	for _, s := range sides {
		s.Draw(cv, black)
	}
	circle.Draw(cv, black)

	if click[0] >= 0 {
		Side{click, cursor}.Draw(cv, blue)
	}

	win.FlushImage()
}

func clear(win wde.Window) {
	img := win.Screen()
	draw.Draw(img, img.Bounds(), image.NewUniform(white), image.ZP, draw.Src)
}
<commit_msg>Mark mouse events as changing the scene.<commit_after>package main

import (
	"fmt"
	"image"
	"image/color"
	"image/draw"
	"os"
	"time"

	. "github.com/eaburns/quart/geom"
	"github.com/eaburns/quart/phys"

	"github.com/skelterjohn/go.wde"
)

const (
	width  = 640
	height = 480

	// Jerk is the amout by which the acceleration changes when an
	// arrow key is pressed.
	jerk = 5
)

var (
	black  = color.RGBA{A: 255}
	white  = color.RGBA{R: 255, G: 255, B: 255, A: 255}
	red    = color.RGBA{R: 255, A: 255}
	green  = color.RGBA{G: 255, A: 255}
	blue   = color.RGBA{B: 255, A: 255}
	purple = color.RGBA{R: 255, B: 255, A: 255}
	teal   = color.RGBA{G: 255, B: 255, A: 255}
)

var (
	vel, acc Vector
	circle   = Circle{Center: Point{200, 200}, Radius: 50}

	// Sides is the set of polygon sides.
	sides = []Side{
		{{400, 200}, {300, 300}},
		{{300, 300}, {400, 400}},
		{{400, 400}, {500, 300}},
		{{500, 300}, {400, 200}},
	}

	// Click is the position of the latest mouse clicke
	click = Point{-1, -1}

	// Cursor is the current cursor position.
	cursor Point

	// Changed is true if anything has changeds and needs a redraw
	changed bool
)

func main() {
	go mainLoop()
	wde.Run()
}

func mainLoop() {
	win, err := wde.NewWindow(width, height)
	if err != nil {
		panic(err)
	}
	win.SetTitle("geom test")
	win.Show()

	drawScene(win)

	tick := time.NewTicker(40 * time.Millisecond)
	for {
		select {
		case ev, ok := <-win.EventChan():
			if !ok {
				os.Exit(0)
			}
			switch ev := ev.(type) {
			case wde.CloseEvent:
				os.Exit(0)

			case wde.KeyDownEvent:
				switch ev.Key {
				case "left_arrow":
					acc[0] = -jerk
				case "right_arrow":
					acc[0] = jerk
				case "up_arrow":
					acc[1] = jerk
				case "down_arrow":
					acc[1] = -jerk
				default:
					fmt.Println("Pressed ", ev.Key)
				}

			case wde.KeyUpEvent:
				switch ev.Key {
				case "left_arrow", "right_arrow":
					acc[0] = 0
				case "up_arrow", "down_arrow":
					acc[1] = 0
				}

			case wde.MouseDraggedEvent:
				mouseMove(ev.MouseEvent)
			case wde.MouseMovedEvent:
				mouseMove(ev.MouseEvent)
			case wde.MouseDownEvent:
				mouseDown(wde.MouseButtonEvent(ev))
			case wde.MouseUpEvent:
				mouseUp(wde.MouseButtonEvent(ev))
			}

		case <-tick.C:
			if !changed && acc.Equals(Vector{}) {
				continue
			}
			changed = false
			vel.Add(acc)
			circle = phys.MoveCircle(circle, vel, sides)
			vel = Vector{}
			drawScene(win)
		}
	}
}

func mouseMove(ev wde.MouseEvent) {
	cursor = Point{float64(ev.Where.X), float64(height - ev.Where.Y - 1)}
	changed = true
}

func mouseDown(ev wde.MouseButtonEvent) {
	switch ev.Which {
	case wde.LeftButton:
		click = Point{float64(ev.Where.X), float64(height - ev.Where.Y - 1)}
		changed = true
	}
}

func mouseUp(ev wde.MouseButtonEvent) {
	switch ev.Which {
	case wde.LeftButton:
		sides = append(sides, Side{click, cursor})
		click = Point{-1, -1}
		changed = true
	}
}

func drawScene(win wde.Window) {
	clear(win)
	cv := ImageCanvas{win.Screen()}

	for _, s := range sides {
		s.Draw(cv, black)
	}
	circle.Draw(cv, black)

	if click[0] >= 0 {
		Side{click, cursor}.Draw(cv, blue)
	}

	win.FlushImage()
}

func clear(win wde.Window) {
	img := win.Screen()
	draw.Draw(img, img.Bounds(), image.NewUniform(white), image.ZP, draw.Src)
}
<|endoftext|>


<commit_before>package awsutil

import (
	"bytes"
	"encoding/json"
	"errors"
	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/credentials"
	"github.com/aws/aws-sdk-go/aws/session"
	"github.com/aws/aws-sdk-go/service/devicefarm"
	"github.com/aws/aws-sdk-go/service/devicefarm/devicefarmiface"
	"github.com/ride/devicefarm/util"
	"net/http"
	"os"
	"strings"
	"time"
)

type DeviceFarm struct {
	Client          devicefarmiface.DeviceFarmAPI
	Log             util.Logger
	allDevicesCache DeviceList
	initialized     bool
}

func NewClient(creds *credentials.Credentials, log util.Logger) (df *DeviceFarm, err error) {
	sess := session.New(&aws.Config{
		Region:      aws.String("us-west-2"),
		Credentials: creds,
	})
	client := devicefarm.New(sess)
	df = &DeviceFarm{client, log, nil, false}
	err = df.Init()
	return
}

func (df *DeviceFarm) Init() (err error) {
	if df.initialized {
		return
	}
	params := &devicefarm.ListDevicesInput{}
	r, err := df.Client.ListDevices(params)
	if err != nil {
		return
	}
	list := DeviceList(r.Devices)
	list.Sort()
	df.allDevicesCache = list
	df.initialized = true
	return
}

func (df *DeviceFarm) DevicesLookup() map[string]*devicefarm.Device {
	allDevices := df.allDevicesCache
	lookup := map[string]*devicefarm.Device{}
	for _, device := range allDevices {
		lookup[*device.Name] = device
		lookup[*device.Arn] = device
	}
	return lookup
}

func (df *DeviceFarm) SearchDevices(search string, androidOnly bool, iosOnly bool) (devices DeviceList) {
	allDevices := df.allDevicesCache
	devices = DeviceList{}
	search = strings.ToLower(search)
	doSearch := len(search) > 0
	for _, device := range allDevices {
		deviceName := *device.Name
		if doSearch && !strings.Contains(strings.ToLower(deviceName), search) {
			continue
		}
		if androidOnly && *device.Platform != devicefarm.DevicePlatformAndroid {
			continue
		}
		if iosOnly && *device.Platform != devicefarm.DevicePlatformIos {
			continue
		}
		devices = append(devices, device)
	}
	return
}

func (df *DeviceFarm) ListDevicePools(arn string) ([]*devicefarm.DevicePool, error) {
	params := &devicefarm.ListDevicePoolsInput{Arn: aws.String(arn)}
	r, err := df.Client.ListDevicePools(params)
	if err != nil {
		return nil, err
	}
	return r.DevicePools, nil
}

func (df *DeviceFarm) NamesToArns(names []string) ([]string, error) {
	lookup := df.DevicesLookup()
	arns := []string{}
	for _, name := range names {
		device, ok := lookup[name]
		if !ok {
			return nil, errors.New("No such device: " + name)
		}
		arns = append(arns, *device.Arn)
	}
	return arns, nil
}

func (df *DeviceFarm) CreateDevicePool(arn string, name string, arns []string) (*devicefarm.DevicePool, error) {
	val, err := json.Marshal(arns)
	if err != nil {
		return nil, err
	}
	params := &devicefarm.CreateDevicePoolInput{
		ProjectArn: aws.String(arn),
		Name:       aws.String(name),
		Rules: []*devicefarm.Rule{
			{
				Attribute: aws.String("ARN"),
				Operator:  aws.String("IN"),
				Value:     aws.String(string(val)),
			},
		},
	}
	r, err := df.Client.CreateDevicePool(params)
	if err != nil {
		return nil, err
	}
	return r.DevicePool, nil
}

func (df *DeviceFarm) UpdateDevicePool(pool *devicefarm.DevicePool, arns []string) (*devicefarm.DevicePool, error) {
	val, err := json.Marshal(arns)
	if err != nil {
		return nil, err
	}
	params := &devicefarm.UpdateDevicePoolInput{
		Arn:  pool.Arn,
		Name: pool.Name,
		Rules: []*devicefarm.Rule{
			{
				Attribute: aws.String("ARN"),
				Operator:  aws.String("IN"),
				Value:     aws.String(string(val)),
			},
		},
	}
	r, err := df.Client.UpdateDevicePool(params)
	if err != nil {
		return nil, err
	}
	return r.DevicePool, nil
}

func (df *DeviceFarm) DevicePoolMatches(pool *devicefarm.DevicePool, arns []string) (bool, error) {
	val, err := json.Marshal(arns)
	if err != nil {
		return false, err
	}
	for _, rule := range pool.Rules {
		if *rule.Attribute != "ARN" || *rule.Operator != "IN" {
			return false, nil
		}
		if *rule.Value != string(val) {
			return false, nil
		}
	}
	return true, nil
}

func (df *DeviceFarm) CreateUpload(projectArn, filename, uploadType, name string) (string, error) {
	// open file and read into io.ReaderSeeker
	// to avoid 501 Not Implemented Transfer-Encoding
	file, err := os.Open(filename)
	if err != nil {
		return "", err
	}
	defer file.Close()
	fileInfo, err := file.Stat()
	if err != nil {
		return "", err
	}
	fileSize := fileInfo.Size()
	buffer := make([]byte, fileSize)
	file.Read(buffer)
	fileBytes := bytes.NewReader(buffer)

	// create upload object, get signed S3 URL
	params := &devicefarm.CreateUploadInput{
		Name:        aws.String(name),
		ProjectArn:  aws.String(projectArn),
		Type:        aws.String(uploadType),
		ContentType: aws.String("application/octet-stream"),
	}
	rApp, err := df.Client.CreateUpload(params)
	if err != nil {
		return "", err
	}
	uploadUrl := *rApp.Upload.Url

	// upload to S3
	req, err := http.NewRequest("PUT", uploadUrl, fileBytes)
	if err != nil {
		return "", err
	}
	req.Header.Set("Content-Type", "application/octet-stream")
	client := &http.Client{}
	res, err := client.Do(req)
	if err != nil {
		return "", err
	}
	defer res.Body.Close()

	return *rApp.Upload.Arn, nil
}

func (df *DeviceFarm) UploadSucceeded(arn string) (bool, error) {
	params := &devicefarm.GetUploadInput{Arn: aws.String(arn)}
	r, err := df.Client.GetUpload(params)
	if err != nil {
		return false, err
	}
	if *r.Upload.Status == devicefarm.UploadStatusFailed {
		return false, errors.New("Upload failed: " + arn)
	}
	if *r.Upload.Status == devicefarm.UploadStatusSucceeded {
		return true, nil
	}
	return false, nil
}

func (df *DeviceFarm) WaitForUploadsToSucceed(timeoutMs, delayMs int, arns ...string) error {
	errchan := make(chan error, 1)
	quitchan := make(chan bool, 1)
	go func() {
		var err error
		var succeeded bool
	mainloop:
		for len(arns) > 0 {
			select {
			case <-quitchan:
				break mainloop
			default:
			}
			nextArns := []string{}
			for _, arn := range arns {
				succeeded, err = df.UploadSucceeded(arn)
				if err != nil {
					break mainloop
				}
				if succeeded {
					continue
				}
				nextArns = append(nextArns, arn)
			}
			arns = nextArns
			if len(arns) > 0 && delayMs > 0 {
				time.Sleep(time.Duration(delayMs) * time.Millisecond)
			}
		}
		errchan <- err
	}()
	select {
	case <-time.After(time.Duration(timeoutMs) * time.Millisecond):
		quitchan <- true
		return errors.New("Timed out")
	case err := <-errchan:
		return err
	}
}

func (df *DeviceFarm) CreateRun(projectArn, poolArn, apk, apkInstrumentation string) (string, error) {
	log := df.Log
	log.Println(">> Uploading files...")
	log.Println(apk)
	appArn, err := df.CreateUpload(projectArn, apk, "ANDROID_APP", "app.apk")
	if err != nil {
		return "", err
	}
	log.Println(apkInstrumentation)
	instArn, err := df.CreateUpload(projectArn, apkInstrumentation, "INSTRUMENTATION_TEST_PACKAGE", "instrumentation.apk")
	if err != nil {
		return "", err
	}

	log.Println(">> Waiting for files to be processed...")
	err = df.WaitForUploadsToSucceed(60000, 5000, appArn, instArn)
	if err != nil {
		return "", err
	}

	log.Println(">> Creating test run...")
	params := &devicefarm.ScheduleRunInput{
		DevicePoolArn: aws.String(poolArn),
		ProjectArn:    aws.String(projectArn),
		Test: &devicefarm.ScheduleRunTest{
			Type:           aws.String("INSTRUMENTATION"),
			TestPackageArn: aws.String(instArn),
		},
		AppArn: aws.String(appArn),
	}
	r, err := df.Client.ScheduleRun(params)
	if err != nil {
		return "", err
	}
	return *r.Run.Arn, nil
}
<commit_msg>Remove unused NamestoArns<commit_after>package awsutil

import (
	"bytes"
	"encoding/json"
	"errors"
	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/credentials"
	"github.com/aws/aws-sdk-go/aws/session"
	"github.com/aws/aws-sdk-go/service/devicefarm"
	"github.com/aws/aws-sdk-go/service/devicefarm/devicefarmiface"
	"github.com/ride/devicefarm/util"
	"net/http"
	"os"
	"strings"
	"time"
)

type DeviceFarm struct {
	Client          devicefarmiface.DeviceFarmAPI
	Log             util.Logger
	allDevicesCache DeviceList
	initialized     bool
}

func NewClient(creds *credentials.Credentials, log util.Logger) (df *DeviceFarm, err error) {
	sess := session.New(&aws.Config{
		Region:      aws.String("us-west-2"),
		Credentials: creds,
	})
	client := devicefarm.New(sess)
	df = &DeviceFarm{client, log, nil, false}
	err = df.Init()
	return
}

func (df *DeviceFarm) Init() (err error) {
	if df.initialized {
		return
	}
	params := &devicefarm.ListDevicesInput{}
	r, err := df.Client.ListDevices(params)
	if err != nil {
		return
	}
	list := DeviceList(r.Devices)
	list.Sort()
	df.allDevicesCache = list
	df.initialized = true
	return
}

func (df *DeviceFarm) DevicesLookup() map[string]*devicefarm.Device {
	allDevices := df.allDevicesCache
	lookup := map[string]*devicefarm.Device{}
	for _, device := range allDevices {
		lookup[*device.Name] = device
		lookup[*device.Arn] = device
	}
	return lookup
}

func (df *DeviceFarm) SearchDevices(search string, androidOnly bool, iosOnly bool) (devices DeviceList) {
	allDevices := df.allDevicesCache
	devices = DeviceList{}
	search = strings.ToLower(search)
	doSearch := len(search) > 0
	for _, device := range allDevices {
		deviceName := *device.Name
		if doSearch && !strings.Contains(strings.ToLower(deviceName), search) {
			continue
		}
		if androidOnly && *device.Platform != devicefarm.DevicePlatformAndroid {
			continue
		}
		if iosOnly && *device.Platform != devicefarm.DevicePlatformIos {
			continue
		}
		devices = append(devices, device)
	}
	return
}

func (df *DeviceFarm) ListDevicePools(arn string) ([]*devicefarm.DevicePool, error) {
	params := &devicefarm.ListDevicePoolsInput{Arn: aws.String(arn)}
	r, err := df.Client.ListDevicePools(params)
	if err != nil {
		return nil, err
	}
	return r.DevicePools, nil
}

func (df *DeviceFarm) CreateDevicePool(arn string, name string, arns []string) (*devicefarm.DevicePool, error) {
	val, err := json.Marshal(arns)
	if err != nil {
		return nil, err
	}
	params := &devicefarm.CreateDevicePoolInput{
		ProjectArn: aws.String(arn),
		Name:       aws.String(name),
		Rules: []*devicefarm.Rule{
			{
				Attribute: aws.String("ARN"),
				Operator:  aws.String("IN"),
				Value:     aws.String(string(val)),
			},
		},
	}
	r, err := df.Client.CreateDevicePool(params)
	if err != nil {
		return nil, err
	}
	return r.DevicePool, nil
}

func (df *DeviceFarm) UpdateDevicePool(pool *devicefarm.DevicePool, arns []string) (*devicefarm.DevicePool, error) {
	val, err := json.Marshal(arns)
	if err != nil {
		return nil, err
	}
	params := &devicefarm.UpdateDevicePoolInput{
		Arn:  pool.Arn,
		Name: pool.Name,
		Rules: []*devicefarm.Rule{
			{
				Attribute: aws.String("ARN"),
				Operator:  aws.String("IN"),
				Value:     aws.String(string(val)),
			},
		},
	}
	r, err := df.Client.UpdateDevicePool(params)
	if err != nil {
		return nil, err
	}
	return r.DevicePool, nil
}

func (df *DeviceFarm) DevicePoolMatches(pool *devicefarm.DevicePool, arns []string) (bool, error) {
	val, err := json.Marshal(arns)
	if err != nil {
		return false, err
	}
	for _, rule := range pool.Rules {
		if *rule.Attribute != "ARN" || *rule.Operator != "IN" {
			return false, nil
		}
		if *rule.Value != string(val) {
			return false, nil
		}
	}
	return true, nil
}

func (df *DeviceFarm) CreateUpload(projectArn, filename, uploadType, name string) (string, error) {
	// open file and read into io.ReaderSeeker
	// to avoid 501 Not Implemented Transfer-Encoding
	file, err := os.Open(filename)
	if err != nil {
		return "", err
	}
	defer file.Close()
	fileInfo, err := file.Stat()
	if err != nil {
		return "", err
	}
	fileSize := fileInfo.Size()
	buffer := make([]byte, fileSize)
	file.Read(buffer)
	fileBytes := bytes.NewReader(buffer)

	// create upload object, get signed S3 URL
	params := &devicefarm.CreateUploadInput{
		Name:        aws.String(name),
		ProjectArn:  aws.String(projectArn),
		Type:        aws.String(uploadType),
		ContentType: aws.String("application/octet-stream"),
	}
	rApp, err := df.Client.CreateUpload(params)
	if err != nil {
		return "", err
	}
	uploadUrl := *rApp.Upload.Url

	// upload to S3
	req, err := http.NewRequest("PUT", uploadUrl, fileBytes)
	if err != nil {
		return "", err
	}
	req.Header.Set("Content-Type", "application/octet-stream")
	client := &http.Client{}
	res, err := client.Do(req)
	if err != nil {
		return "", err
	}
	defer res.Body.Close()

	return *rApp.Upload.Arn, nil
}

func (df *DeviceFarm) UploadSucceeded(arn string) (bool, error) {
	params := &devicefarm.GetUploadInput{Arn: aws.String(arn)}
	r, err := df.Client.GetUpload(params)
	if err != nil {
		return false, err
	}
	if *r.Upload.Status == devicefarm.UploadStatusFailed {
		return false, errors.New("Upload failed: " + arn)
	}
	if *r.Upload.Status == devicefarm.UploadStatusSucceeded {
		return true, nil
	}
	return false, nil
}

func (df *DeviceFarm) WaitForUploadsToSucceed(timeoutMs, delayMs int, arns ...string) error {
	errchan := make(chan error, 1)
	quitchan := make(chan bool, 1)
	go func() {
		var err error
		var succeeded bool
	mainloop:
		for len(arns) > 0 {
			select {
			case <-quitchan:
				break mainloop
			default:
			}
			nextArns := []string{}
			for _, arn := range arns {
				succeeded, err = df.UploadSucceeded(arn)
				if err != nil {
					break mainloop
				}
				if succeeded {
					continue
				}
				nextArns = append(nextArns, arn)
			}
			arns = nextArns
			if len(arns) > 0 && delayMs > 0 {
				time.Sleep(time.Duration(delayMs) * time.Millisecond)
			}
		}
		errchan <- err
	}()
	select {
	case <-time.After(time.Duration(timeoutMs) * time.Millisecond):
		quitchan <- true
		return errors.New("Timed out")
	case err := <-errchan:
		return err
	}
}

func (df *DeviceFarm) CreateRun(projectArn, poolArn, apk, apkInstrumentation string) (string, error) {
	log := df.Log
	log.Println(">> Uploading files...")
	log.Println(apk)
	appArn, err := df.CreateUpload(projectArn, apk, "ANDROID_APP", "app.apk")
	if err != nil {
		return "", err
	}
	log.Println(apkInstrumentation)
	instArn, err := df.CreateUpload(projectArn, apkInstrumentation, "INSTRUMENTATION_TEST_PACKAGE", "instrumentation.apk")
	if err != nil {
		return "", err
	}

	log.Println(">> Waiting for files to be processed...")
	err = df.WaitForUploadsToSucceed(60000, 5000, appArn, instArn)
	if err != nil {
		return "", err
	}

	log.Println(">> Creating test run...")
	params := &devicefarm.ScheduleRunInput{
		DevicePoolArn: aws.String(poolArn),
		ProjectArn:    aws.String(projectArn),
		Test: &devicefarm.ScheduleRunTest{
			Type:           aws.String("INSTRUMENTATION"),
			TestPackageArn: aws.String(instArn),
		},
		AppArn: aws.String(appArn),
	}
	r, err := df.Client.ScheduleRun(params)
	if err != nil {
		return "", err
	}
	return *r.Run.Arn, nil
}
<|endoftext|>