<commit_before>package main_test

import (
	"bytes"
	"reflect"
	"strings"
	"testing"
	"time"

	"github.com/BurntSushi/toml"
	main "github.com/influxdb/influxdb/cmd/influxd"
)

// Ensure that megabyte sizes can be parsed.
func TestSize_UnmarshalText_MB(t *testing.T) {
	var s main.Size
	if err := s.UnmarshalText([]byte("200m")); err != nil {
		t.Fatalf("unexpected error: %s", err)
	} else if s != 200*(1<<20) {
		t.Fatalf("unexpected size: %d", s)
	}
}

// Ensure that gigabyte sizes can be parsed.
func TestSize_UnmarshalText_GB(t *testing.T) {
	if typ := reflect.TypeOf(0); typ.Size() != 8 {
		t.Skip("large gigabyte parsing on 64-bit arch only")
	}

	var s main.Size
	if err := s.UnmarshalText([]byte("10g")); err != nil {
		t.Fatalf("unexpected error: %s", err)
	} else if s != 10*(1<<30) {
		t.Fatalf("unexpected size: %d", s)
	}
}

// Ensure that a TOML configuration file can be parsed into a Config.
func TestParseConfig(t *testing.T) {
	c, err := main.ParseConfig(testFile)
	if err != nil {
		t.Fatalf("unexpected error: %s", err)
	} else if c.Hostname != "myserver.com" {
		t.Fatalf("hostname mismatch: %v", c.Hostname)
	}

	if c.JoinURLs() != "http://127.0.0.1:8086" {
		t.Fatalf("JoinURLs mistmatch: %v", c.JoinURLs())
	}

	if !c.Authentication.Enabled {
		t.Fatalf("authentication enabled mismatch: %v", c.Authentication.Enabled)
	}

	if c.UDP.Enabled {
		t.Fatalf("udp enabled mismatch: %v", c.UDP.Enabled)
	}

	if c.Admin.Enabled != true {
		t.Fatalf("admin enabled mismatch: %v", c.Admin.Enabled)
	}

	if c.Admin.Port != 8083 {
		t.Fatalf("admin port mismatch: %v", c.Admin.Port)
	}

	if c.ContinuousQuery.Disabled != true {
		t.Fatalf("continuous query disable mismatch: %v", c.ContinuousQuery.Disabled)
	}

	if c.Data.Port != main.DefaultBrokerPort {
		t.Fatalf("data port mismatch: %v", c.Data.Port)
	}

	if len(c.Graphites) != 2 {
		t.Fatalf("graphites  mismatch.  expected %v, got: %v", 2, len(c.Graphites))
	}

	tcpGraphite := c.Graphites[0]
	switch {
	case tcpGraphite.Enabled != true:
		t.Fatalf("graphite tcp enabled mismatch: expected: %v, got %v", true, tcpGraphite.Enabled)
	case tcpGraphite.Addr != "192.168.0.1":
		t.Fatalf("graphite tcp address mismatch: expected %v, got  %v", "192.168.0.1", tcpGraphite.Addr)
	case tcpGraphite.Port != 2003:
		t.Fatalf("graphite tcp port mismatch: expected %v, got %v", 2003, tcpGraphite.Port)
	case tcpGraphite.Database != "graphite_tcp":
		t.Fatalf("graphite tcp database mismatch: expected %v, got %v", "graphite_tcp", tcpGraphite.Database)
	case strings.ToLower(tcpGraphite.Protocol) != "tcp":
		t.Fatalf("graphite tcp protocol mismatch: expected %v, got %v", "tcp", strings.ToLower(tcpGraphite.Protocol))
	case tcpGraphite.LastEnabled() != true:
		t.Fatalf("graphite tcp name-position mismatch: expected %v, got %v", "last", tcpGraphite.NamePosition)
	case tcpGraphite.NameSeparatorString() != "-":
		t.Fatalf("graphite tcp name-separator mismatch: expected %v, got %v", "-", tcpGraphite.NameSeparatorString())
	}

	udpGraphite := c.Graphites[1]
	switch {
	case udpGraphite.Enabled != true:
		t.Fatalf("graphite udp enabled mismatch: expected: %v, got %v", true, udpGraphite.Enabled)
	case udpGraphite.Addr != "192.168.0.2":
		t.Fatalf("graphite udp address mismatch: expected %v, got  %v", "192.168.0.2", udpGraphite.Addr)
	case udpGraphite.Port != 2005:
		t.Fatalf("graphite udp port mismatch: expected %v, got %v", 2005, udpGraphite.Port)
	case udpGraphite.DatabaseString() != "graphite":
		t.Fatalf("graphite database mismatch: expected %v, got %v", "graphite", udpGraphite.Database)
	case strings.ToLower(udpGraphite.Protocol) != "udp":
		t.Fatalf("graphite udp protocol mismatch: expected %v, got %v", "udp", strings.ToLower(udpGraphite.Protocol))
	}

	switch {
	case c.Collectd.Enabled != true:
		t.Errorf("collectd enabled mismatch: expected: %v, got %v", true, c.Collectd.Enabled)
	case c.Collectd.Addr != "192.168.0.3":
		t.Errorf("collectd address mismatch: expected %v, got  %v", "192.168.0.3", c.Collectd.Addr)
	case c.Collectd.Port != 25827:
		t.Errorf("collectd port mismatch: expected %v, got %v", 2005, c.Collectd.Port)
	case c.Collectd.Database != "collectd_database":
		t.Errorf("collectdabase mismatch: expected %v, got %v", "collectd_database", c.Collectd.Database)
	case c.Collectd.TypesDB != "foo-db-type":
		t.Errorf("collectd typesdb mismatch: expected %v, got %v", "foo-db-type", c.Collectd.TypesDB)
	}

	switch {
	case c.OpenTSDB.Enabled != true:
		t.Errorf("opentsdb enabled mismatch: expected: %v, got %v", true, c.OpenTSDB.Enabled)
	case c.OpenTSDB.ListenAddress(c.BindAddress) != "192.168.0.3:4242":
		t.Errorf("opentsdb listen address mismatch: expected %v, got  %v", "192.168.0.3:4242", c.OpenTSDB.ListenAddress(c.BindAddress))
	case c.OpenTSDB.DatabaseString() != "opentsdb_database":
		t.Errorf("opentsdb database mismatch: expected %v, got %v", "opentsdb_database", c.OpenTSDB.DatabaseString())
	case c.OpenTSDB.RetentionPolicy != "raw":
		t.Errorf("collectd retention-policy mismatch: expected %v, got %v", "foo-db-type", c.OpenTSDB.RetentionPolicy)
	}

	if c.Broker.Port != 8086 {
		t.Fatalf("broker port mismatch: %v", c.Broker.Port)
	} else if c.Broker.Dir != "/tmp/influxdb/development/broker" {
		t.Fatalf("broker dir mismatch: %v", c.Broker.Dir)
	} else if time.Duration(c.Broker.Timeout) != time.Second {
		t.Fatalf("broker duration mismatch: %v", c.Broker.Timeout)
	}

	if c.Broker.Enabled != false {
		t.Fatalf("broker disabled mismatch: %v, got: %v", false, c.Broker.Enabled)
	}

	if c.Data.Dir != "/tmp/influxdb/development/db" {
		t.Fatalf("data dir mismatch: %v", c.Data.Dir)
	}
	if c.Data.RetentionCheckEnabled != true {
		t.Fatalf("Retention check enabled mismatch: %v", c.Data.RetentionCheckEnabled)
	}
	if c.Data.RetentionCheckPeriod != main.Duration(5*time.Minute) {
		t.Fatalf("Retention check period mismatch: %v", c.Data.RetentionCheckPeriod)
	}

	if c.Data.Enabled != false {
		t.Fatalf("data disabled mismatch: %v, got: %v", false, c.Data.Enabled)
	}

	if c.Cluster.Dir != "/tmp/influxdb/development/cluster" {
		t.Fatalf("cluster dir mismatch: %v", c.Cluster.Dir)
	}

	if c.Monitoring.WriteInterval.String() != "1m0s" {
		t.Fatalf("Monitoring.WriteInterval mismatch: %v", c.Monitoring.WriteInterval)
	}

	// TODO: UDP Servers testing.
	/*
		c.Assert(config.UdpServers, HasLen, 1)
		c.Assert(config.UdpServers[0].Enabled, Equals, true)
		c.Assert(config.UdpServers[0].Port, Equals, 4444)
		c.Assert(config.UdpServers[0].Database, Equals, "test")
	*/
}

func TestEncodeConfig(t *testing.T) {
	c := main.Config{}
	c.Monitoring.WriteInterval = main.Duration(time.Minute)
	buf := new(bytes.Buffer)
	if err := toml.NewEncoder(buf).Encode(&c); err != nil {
		t.Fatal("Failed to encode: ", err)
	}
	got, search := buf.String(), `write-interval = "1m0s"`
	if !strings.Contains(got, search) {
		t.Fatalf("Encoding config failed.\nfailed to find %s in:\n%s\n", search, got)
	}
}

// Testing configuration file.
const testFile = `
# Welcome to the InfluxDB configuration file.

# If hostname (on the OS) doesn't return a name that can be resolved by the other
# systems in the cluster, you'll have to set the hostname to an IP or something
# that can be resolved here.
hostname = "myserver.com"

# Controls certain parameters that only take effect until an initial successful
# start-up has occurred.
[initialization]
join-urls = "http://127.0.0.1:8086"

# Control authentication
[authentication]
enabled = true

[logging]
write-tracing = true
raft-tracing = true

[monitoring]
enabled = true
write-interval = "1m"

# Configure the admin server
[admin]
enabled = true
port = 8083

# Configure the http api
[api]
ssl-port = 8087    # Ssl support is enabled if you set a port and cert
ssl-cert = "../cert.pem"

# connections will timeout after this amount of time. Ensures that clients that misbehave
# and keep alive connections they don't use won't end up connection a million times.
# However, if a request is taking longer than this to complete, could be a problem.
read-timeout = "5s"

[input_plugins]

  [input_plugins.udp]
  enabled = true
  port = 4444
  database = "test"

# Configure the Graphite servers
[[graphite]]
protocol = "TCP"
enabled = true
address = "192.168.0.1"
port = 2003
database = "graphite_tcp"  # store graphite data in this database
name-position = "last"
name-separator = "-"

[[graphite]]
protocol = "udP"
enabled = true
address = "192.168.0.2"
port = 2005

# Configure collectd server
[collectd]
enabled = true
address = "192.168.0.3"
port = 25827
database = "collectd_database"
typesdb = "foo-db-type"

# Configure OpenTSDB server
[opentsdb]
enabled = true
address = "192.168.0.3"
port = 4242
database = "opentsdb_database"
retention-policy = "raw"

# Broker configuration
[broker]
# The broker port should be open between all servers in a cluster.
# However, this port shouldn't be accessible from the internet.
port = 8086
enabled = false

# Where the broker logs are stored. The user running InfluxDB will need read/write access.
dir  = "/tmp/influxdb/development/broker"

# election-timeout = "2s"

[data]
dir = "/tmp/influxdb/development/db"
retention-auto-create = false
retention-check-enabled = true
retention-check-period = "5m"
enabled = false

[continuous_queries]
disabled = true

[cluster]
dir = "/tmp/influxdb/development/cluster"
`

func TestCollectd_ConnectionString(t *testing.T) {
	var tests = []struct {
		name             string
		defaultBindAddr  string
		connectionString string
		config           main.Collectd
	}{
		{
			name:             "No address or port provided from config",
			defaultBindAddr:  "192.168.0.1",
			connectionString: "192.168.0.1:25826",
			config:           main.Collectd{},
		},
		{
			name:             "address provided, no port provided from config",
			defaultBindAddr:  "192.168.0.1",
			connectionString: "192.168.0.2:25826",
			config:           main.Collectd{Addr: "192.168.0.2"},
		},
		{
			name:             "no address provided, port provided from config",
			defaultBindAddr:  "192.168.0.1",
			connectionString: "192.168.0.1:25827",
			config:           main.Collectd{Port: 25827},
		},
		{
			name:             "both address and port provided from config",
			defaultBindAddr:  "192.168.0.1",
			connectionString: "192.168.0.2:25827",
			config:           main.Collectd{Addr: "192.168.0.2", Port: 25827},
		},
	}

	for _, test := range tests {
		t.Logf("test: %q", test.name)
		s := test.config.ConnectionString(test.defaultBindAddr)
		if s != test.connectionString {
			t.Errorf("connection string mismatch, expected: %q, got: %q", test.connectionString, s)
		}
	}
}
<commit_msg>Add missing tests for snaphost config<commit_after>package main_test

import (
	"bytes"
	"reflect"
	"strings"
	"testing"
	"time"

	"github.com/BurntSushi/toml"
	main "github.com/influxdb/influxdb/cmd/influxd"
)

// Testing configuration file.
const testFile = `
# Welcome to the InfluxDB configuration file.

# If hostname (on the OS) doesn't return a name that can be resolved by the other
# systems in the cluster, you'll have to set the hostname to an IP or something
# that can be resolved here.
hostname = "myserver.com"

# Controls certain parameters that only take effect until an initial successful
# start-up has occurred.
[initialization]
join-urls = "http://127.0.0.1:8086"

# Control authentication
[authentication]
enabled = true

[logging]
write-tracing = true
raft-tracing = true

[monitoring]
enabled = true
write-interval = "1m"

# Configure the admin server
[admin]
enabled = true
port = 8083

# Configure the http api
[api]
ssl-port = 8087    # Ssl support is enabled if you set a port and cert
ssl-cert = "../cert.pem"

# connections will timeout after this amount of time. Ensures that clients that misbehave
# and keep alive connections they don't use won't end up connection a million times.
# However, if a request is taking longer than this to complete, could be a problem.
read-timeout = "5s"

[input_plugins]

  [input_plugins.udp]
  enabled = true
  port = 4444
  database = "test"

# Configure the Graphite servers
[[graphite]]
protocol = "TCP"
enabled = true
address = "192.168.0.1"
port = 2003
database = "graphite_tcp"  # store graphite data in this database
name-position = "last"
name-separator = "-"

[[graphite]]
protocol = "udP"
enabled = true
address = "192.168.0.2"
port = 2005

# Configure collectd server
[collectd]
enabled = true
address = "192.168.0.3"
port = 25827
database = "collectd_database"
typesdb = "foo-db-type"

# Configure OpenTSDB server
[opentsdb]
enabled = true
address = "192.168.0.3"
port = 4242
database = "opentsdb_database"
retention-policy = "raw"

# Broker configuration
[broker]
# The broker port should be open between all servers in a cluster.
# However, this port shouldn't be accessible from the internet.
port = 8086
enabled = false

# Where the broker logs are stored. The user running InfluxDB will need read/write access.
dir  = "/tmp/influxdb/development/broker"

# election-timeout = "2s"

[data]
dir = "/tmp/influxdb/development/db"
retention-auto-create = false
retention-check-enabled = true
retention-check-period = "5m"
enabled = false

[continuous_queries]
disabled = true

[cluster]
dir = "/tmp/influxdb/development/cluster"

[snapshot]
bind-address = "1.2.3.4"
port = 9999
`

// Ensure that megabyte sizes can be parsed.
func TestSize_UnmarshalText_MB(t *testing.T) {
	var s main.Size
	if err := s.UnmarshalText([]byte("200m")); err != nil {
		t.Fatalf("unexpected error: %s", err)
	} else if s != 200*(1<<20) {
		t.Fatalf("unexpected size: %d", s)
	}
}

// Ensure that gigabyte sizes can be parsed.
func TestSize_UnmarshalText_GB(t *testing.T) {
	if typ := reflect.TypeOf(0); typ.Size() != 8 {
		t.Skip("large gigabyte parsing on 64-bit arch only")
	}

	var s main.Size
	if err := s.UnmarshalText([]byte("10g")); err != nil {
		t.Fatalf("unexpected error: %s", err)
	} else if s != 10*(1<<30) {
		t.Fatalf("unexpected size: %d", s)
	}
}

// Ensure that a TOML configuration file can be parsed into a Config.
func TestParseConfig(t *testing.T) {
	c, err := main.ParseConfig(testFile)
	if err != nil {
		t.Fatalf("unexpected error: %s", err)
	} else if c.Hostname != "myserver.com" {
		t.Fatalf("hostname mismatch: %v", c.Hostname)
	}

	if c.JoinURLs() != "http://127.0.0.1:8086" {
		t.Fatalf("JoinURLs mistmatch: %v", c.JoinURLs())
	}

	if !c.Authentication.Enabled {
		t.Fatalf("authentication enabled mismatch: %v", c.Authentication.Enabled)
	}

	if c.UDP.Enabled {
		t.Fatalf("udp enabled mismatch: %v", c.UDP.Enabled)
	}

	if c.Admin.Enabled != true {
		t.Fatalf("admin enabled mismatch: %v", c.Admin.Enabled)
	}

	if c.Admin.Port != 8083 {
		t.Fatalf("admin port mismatch: %v", c.Admin.Port)
	}

	if c.ContinuousQuery.Disabled != true {
		t.Fatalf("continuous query disable mismatch: %v", c.ContinuousQuery.Disabled)
	}

	if c.Data.Port != main.DefaultBrokerPort {
		t.Fatalf("data port mismatch: %v", c.Data.Port)
	}

	if len(c.Graphites) != 2 {
		t.Fatalf("graphites  mismatch.  expected %v, got: %v", 2, len(c.Graphites))
	}

	tcpGraphite := c.Graphites[0]
	switch {
	case tcpGraphite.Enabled != true:
		t.Fatalf("graphite tcp enabled mismatch: expected: %v, got %v", true, tcpGraphite.Enabled)
	case tcpGraphite.Addr != "192.168.0.1":
		t.Fatalf("graphite tcp address mismatch: expected %v, got  %v", "192.168.0.1", tcpGraphite.Addr)
	case tcpGraphite.Port != 2003:
		t.Fatalf("graphite tcp port mismatch: expected %v, got %v", 2003, tcpGraphite.Port)
	case tcpGraphite.Database != "graphite_tcp":
		t.Fatalf("graphite tcp database mismatch: expected %v, got %v", "graphite_tcp", tcpGraphite.Database)
	case strings.ToLower(tcpGraphite.Protocol) != "tcp":
		t.Fatalf("graphite tcp protocol mismatch: expected %v, got %v", "tcp", strings.ToLower(tcpGraphite.Protocol))
	case tcpGraphite.LastEnabled() != true:
		t.Fatalf("graphite tcp name-position mismatch: expected %v, got %v", "last", tcpGraphite.NamePosition)
	case tcpGraphite.NameSeparatorString() != "-":
		t.Fatalf("graphite tcp name-separator mismatch: expected %v, got %v", "-", tcpGraphite.NameSeparatorString())
	}

	udpGraphite := c.Graphites[1]
	switch {
	case udpGraphite.Enabled != true:
		t.Fatalf("graphite udp enabled mismatch: expected: %v, got %v", true, udpGraphite.Enabled)
	case udpGraphite.Addr != "192.168.0.2":
		t.Fatalf("graphite udp address mismatch: expected %v, got  %v", "192.168.0.2", udpGraphite.Addr)
	case udpGraphite.Port != 2005:
		t.Fatalf("graphite udp port mismatch: expected %v, got %v", 2005, udpGraphite.Port)
	case udpGraphite.DatabaseString() != "graphite":
		t.Fatalf("graphite database mismatch: expected %v, got %v", "graphite", udpGraphite.Database)
	case strings.ToLower(udpGraphite.Protocol) != "udp":
		t.Fatalf("graphite udp protocol mismatch: expected %v, got %v", "udp", strings.ToLower(udpGraphite.Protocol))
	}

	switch {
	case c.Collectd.Enabled != true:
		t.Errorf("collectd enabled mismatch: expected: %v, got %v", true, c.Collectd.Enabled)
	case c.Collectd.Addr != "192.168.0.3":
		t.Errorf("collectd address mismatch: expected %v, got  %v", "192.168.0.3", c.Collectd.Addr)
	case c.Collectd.Port != 25827:
		t.Errorf("collectd port mismatch: expected %v, got %v", 2005, c.Collectd.Port)
	case c.Collectd.Database != "collectd_database":
		t.Errorf("collectdabase mismatch: expected %v, got %v", "collectd_database", c.Collectd.Database)
	case c.Collectd.TypesDB != "foo-db-type":
		t.Errorf("collectd typesdb mismatch: expected %v, got %v", "foo-db-type", c.Collectd.TypesDB)
	}

	switch {
	case c.OpenTSDB.Enabled != true:
		t.Errorf("opentsdb enabled mismatch: expected: %v, got %v", true, c.OpenTSDB.Enabled)
	case c.OpenTSDB.ListenAddress(c.BindAddress) != "192.168.0.3:4242":
		t.Errorf("opentsdb listen address mismatch: expected %v, got  %v", "192.168.0.3:4242", c.OpenTSDB.ListenAddress(c.BindAddress))
	case c.OpenTSDB.DatabaseString() != "opentsdb_database":
		t.Errorf("opentsdb database mismatch: expected %v, got %v", "opentsdb_database", c.OpenTSDB.DatabaseString())
	case c.OpenTSDB.RetentionPolicy != "raw":
		t.Errorf("collectd retention-policy mismatch: expected %v, got %v", "foo-db-type", c.OpenTSDB.RetentionPolicy)
	}

	if c.Broker.Port != 8086 {
		t.Fatalf("broker port mismatch: %v", c.Broker.Port)
	} else if c.Broker.Dir != "/tmp/influxdb/development/broker" {
		t.Fatalf("broker dir mismatch: %v", c.Broker.Dir)
	} else if time.Duration(c.Broker.Timeout) != time.Second {
		t.Fatalf("broker duration mismatch: %v", c.Broker.Timeout)
	}

	if c.Broker.Enabled != false {
		t.Fatalf("broker disabled mismatch: %v, got: %v", false, c.Broker.Enabled)
	}

	if c.Data.Dir != "/tmp/influxdb/development/db" {
		t.Fatalf("data dir mismatch: %v", c.Data.Dir)
	}
	if c.Data.RetentionCheckEnabled != true {
		t.Fatalf("Retention check enabled mismatch: %v", c.Data.RetentionCheckEnabled)
	}
	if c.Data.RetentionCheckPeriod != main.Duration(5*time.Minute) {
		t.Fatalf("Retention check period mismatch: %v", c.Data.RetentionCheckPeriod)
	}

	if c.Data.Enabled != false {
		t.Fatalf("data disabled mismatch: %v, got: %v", false, c.Data.Enabled)
	}

	if c.Cluster.Dir != "/tmp/influxdb/development/cluster" {
		t.Fatalf("cluster dir mismatch: %v", c.Cluster.Dir)
	}

	if c.Monitoring.WriteInterval.String() != "1m0s" {
		t.Fatalf("Monitoring.WriteInterval mismatch: %v", c.Monitoring.WriteInterval)
	}

	if exp := "1.2.3.4"; c.Snapshot.BindAddress != exp {
		t.Fatalf("snapshot bind-address mismatch: %v, got %v", exp, c.Snapshot.BindAddress)
	}

	if exp := 9999; c.Snapshot.Port != exp {
		t.Fatalf("snapshot port mismatch: %v, got %v", exp, c.Snapshot.Port)
	}
	// TODO: UDP Servers testing.
	/*
		c.Assert(config.UdpServers, HasLen, 1)
		c.Assert(config.UdpServers[0].Enabled, Equals, true)
		c.Assert(config.UdpServers[0].Port, Equals, 4444)
		c.Assert(config.UdpServers[0].Database, Equals, "test")
	*/
}

func TestEncodeConfig(t *testing.T) {
	c := main.Config{}
	c.Monitoring.WriteInterval = main.Duration(time.Minute)
	buf := new(bytes.Buffer)
	if err := toml.NewEncoder(buf).Encode(&c); err != nil {
		t.Fatal("Failed to encode: ", err)
	}
	got, search := buf.String(), `write-interval = "1m0s"`
	if !strings.Contains(got, search) {
		t.Fatalf("Encoding config failed.\nfailed to find %s in:\n%s\n", search, got)
	}
}

func TestCollectd_ConnectionString(t *testing.T) {
	var tests = []struct {
		name             string
		defaultBindAddr  string
		connectionString string
		config           main.Collectd
	}{
		{
			name:             "No address or port provided from config",
			defaultBindAddr:  "192.168.0.1",
			connectionString: "192.168.0.1:25826",
			config:           main.Collectd{},
		},
		{
			name:             "address provided, no port provided from config",
			defaultBindAddr:  "192.168.0.1",
			connectionString: "192.168.0.2:25826",
			config:           main.Collectd{Addr: "192.168.0.2"},
		},
		{
			name:             "no address provided, port provided from config",
			defaultBindAddr:  "192.168.0.1",
			connectionString: "192.168.0.1:25827",
			config:           main.Collectd{Port: 25827},
		},
		{
			name:             "both address and port provided from config",
			defaultBindAddr:  "192.168.0.1",
			connectionString: "192.168.0.2:25827",
			config:           main.Collectd{Addr: "192.168.0.2", Port: 25827},
		},
	}

	for _, test := range tests {
		t.Logf("test: %q", test.name)
		s := test.config.ConnectionString(test.defaultBindAddr)
		if s != test.connectionString {
			t.Errorf("connection string mismatch, expected: %q, got: %q", test.connectionString, s)
		}
	}
}
<|endoftext|>


<commit_before>package main_test

import (
	"reflect"
	"strings"
	"testing"
	"time"

	main "github.com/influxdb/influxdb/cmd/influxd"
)

// Ensure that megabyte sizes can be parsed.
func TestSize_UnmarshalText_MB(t *testing.T) {
	var s main.Size
	if err := s.UnmarshalText([]byte("200m")); err != nil {
		t.Fatalf("unexpected error: %s", err)
	} else if s != 200*(1<<20) {
		t.Fatalf("unexpected size: %d", s)
	}
}

// Ensure that gigabyte sizes can be parsed.
func TestSize_UnmarshalText_GB(t *testing.T) {
	if typ := reflect.TypeOf(0); typ.Size() != 8 {
		t.Skip("large gigabyte parsing on 64-bit arch only")
	}

	var s main.Size
	if err := s.UnmarshalText([]byte("10g")); err != nil {
		t.Fatalf("unexpected error: %s", err)
	} else if s != 10*(1<<30) {
		t.Fatalf("unexpected size: %d", s)
	}
}

// Ensure that a TOML configuration file can be parsed into a Config.
func TestParseConfig(t *testing.T) {
	c, err := main.ParseConfig(testFile)
	if err != nil {
		t.Fatalf("unexpected error: %s", err)
	} else if c.Hostname != "myserver.com" {
		t.Fatalf("hostname mismatch: %v", c.Hostname)
	}

	if c.JoinURLs() != "http://127.0.0.1:8086" {
		t.Fatalf("JoinURLs mistmatch: %v", c.JoinURLs())
	}

	if c.Logging.File != "influxdb.log" {
		t.Fatalf("logging file mismatch: %v", c.Logging.File)
	}

	if !c.Authentication.Enabled {
		t.Fatalf("authentication enabled mismatch: %v", c.Authentication.Enabled)
	}

	if c.UDP.Enabled {
		t.Fatalf("udp enabled mismatch: %v", c.UDP.Enabled)
	}

	if c.Admin.Enabled != true {
		t.Fatalf("admin enabled mismatch: %v", c.Admin.Enabled)
	}

	if c.Admin.Port != 8083 {
		t.Fatalf("admin port mismatch: %v", c.Admin.Port)
	}

	if c.ContinuousQuery.Disable == true {
		t.Fatalf("continuous query disable mismatch: %v", c.ContinuousQuery.Disable)
	}

	if c.Data.Port != main.DefaultBrokerPort {
		t.Fatalf("data port mismatch: %v", c.Data.Port)
	}

	if len(c.Graphites) != 2 {
		t.Fatalf("graphites  mismatch.  expected %v, got: %v", 2, len(c.Graphites))
	}

	tcpGraphite := c.Graphites[0]
	switch {
	case tcpGraphite.Enabled != true:
		t.Fatalf("graphite tcp enabled mismatch: expected: %v, got %v", true, tcpGraphite.Enabled)
	case tcpGraphite.Addr != "192.168.0.1":
		t.Fatalf("graphite tcp address mismatch: expected %v, got  %v", "192.168.0.1", tcpGraphite.Addr)
	case tcpGraphite.Port != 2003:
		t.Fatalf("graphite tcp port mismatch: expected %v, got %v", 2003, tcpGraphite.Port)
	case tcpGraphite.Database != "graphite_tcp":
		t.Fatalf("graphite tcp database mismatch: expected %v, got %v", "graphite_tcp", tcpGraphite.Database)
	case strings.ToLower(tcpGraphite.Protocol) != "tcp":
		t.Fatalf("graphite tcp protocol mismatch: expected %v, got %v", "tcp", strings.ToLower(tcpGraphite.Protocol))
	case tcpGraphite.LastEnabled() != true:
		t.Fatalf("graphite tcp name-position mismatch: expected %v, got %v", "last", tcpGraphite.NamePosition)
	case tcpGraphite.NameSeparatorString() != "-":
		t.Fatalf("graphite tcp name-separator mismatch: expected %v, got %v", "-", tcpGraphite.NameSeparatorString())
	}

	udpGraphite := c.Graphites[1]
	switch {
	case udpGraphite.Enabled != true:
		t.Fatalf("graphite udp enabled mismatch: expected: %v, got %v", true, udpGraphite.Enabled)
	case udpGraphite.Addr != "192.168.0.2":
		t.Fatalf("graphite udp address mismatch: expected %v, got  %v", "192.168.0.2", udpGraphite.Addr)
	case udpGraphite.Port != 2005:
		t.Fatalf("graphite udp port mismatch: expected %v, got %v", 2005, udpGraphite.Port)
	case udpGraphite.DatabaseString() != "graphite":
		t.Fatalf("graphite database mismatch: expected %v, got %v", "graphite", udpGraphite.Database)
	case strings.ToLower(udpGraphite.Protocol) != "udp":
		t.Fatalf("graphite udp protocol mismatch: expected %v, got %v", "udp", strings.ToLower(udpGraphite.Protocol))
	}

	switch {
	case c.Collectd.Enabled != true:
		t.Errorf("collectd enabled mismatch: expected: %v, got %v", true, c.Collectd.Enabled)
	case c.Collectd.Addr != "192.168.0.3":
		t.Errorf("collectd address mismatch: expected %v, got  %v", "192.168.0.3", c.Collectd.Addr)
	case c.Collectd.Port != 25827:
		t.Errorf("collectd port mismatch: expected %v, got %v", 2005, c.Collectd.Port)
	case c.Collectd.Database != "collectd_database":
		t.Errorf("collectdabase mismatch: expected %v, got %v", "collectd_database", c.Collectd.Database)
	case c.Collectd.TypesDB != "foo-db-type":
		t.Errorf("collectd typesdb mismatch: expected %v, got %v", "foo-db-type", c.Collectd.TypesDB)
	}

	if c.Broker.Port != 8086 {
		t.Fatalf("broker port mismatch: %v", c.Broker.Port)
	} else if c.Broker.Dir != "/tmp/influxdb/development/broker" {
		t.Fatalf("broker dir mismatch: %v", c.Broker.Dir)
	} else if time.Duration(c.Broker.Timeout) != time.Second {
		t.Fatalf("broker duration mismatch: %v", c.Broker.Timeout)
	}

	if c.Data.Dir != "/tmp/influxdb/development/db" {
		t.Fatalf("data dir mismatch: %v", c.Data.Dir)
	}
	if c.Data.RetentionCheckEnabled != true {
		t.Fatalf("Retention check enabled mismatch: %v", c.Data.RetentionCheckEnabled)
	}
	if c.Data.RetentionCheckPeriod != main.Duration(5*time.Minute) {
		t.Fatalf("Retention check period mismatch: %v", c.Data.RetentionCheckPeriod)
	}

	if c.Cluster.Dir != "/tmp/influxdb/development/cluster" {
		t.Fatalf("cluster dir mismatch: %v", c.Cluster.Dir)
	}

	// TODO: UDP Servers testing.
	/*
		c.Assert(config.UdpServers, HasLen, 1)
		c.Assert(config.UdpServers[0].Enabled, Equals, true)
		c.Assert(config.UdpServers[0].Port, Equals, 4444)
		c.Assert(config.UdpServers[0].Database, Equals, "test")
	*/
}

// Testing configuration file.
const testFile = `
# Welcome to the InfluxDB configuration file.

# If hostname (on the OS) doesn't return a name that can be resolved by the other
# systems in the cluster, you'll have to set the hostname to an IP or something
# that can be resolved here.
hostname = "myserver.com"

# Controls certain parameters that only take effect until an initial successful
# start-up has occurred.
[initialization]
join-urls = "http://127.0.0.1:8086"

# Control authentication
[authentication]
enabled = true

[logging]
file   = "influxdb.log"
write-tracing = true
raft-tracing = true

[statistics]
enabled = true
database = "_internal"
retention = "default"
duration = "7d"
check-interval = "1m"

# Configure the admin server
[admin]
enabled = true
port = 8083

# Configure the http api
[api]
ssl-port = 8087    # Ssl support is enabled if you set a port and cert
ssl-cert = "../cert.pem"

# connections will timeout after this amount of time. Ensures that clients that misbehave
# and keep alive connections they don't use won't end up connection a million times.
# However, if a request is taking longer than this to complete, could be a problem.
read-timeout = "5s"

[input_plugins]

  [input_plugins.udp]
  enabled = true
  port = 4444
  database = "test"

# Configure the Graphite servers
[[graphite]]
protocol = "TCP"
enabled = true
address = "192.168.0.1"
port = 2003
database = "graphite_tcp"  # store graphite data in this database
name-position = "last"
name-separator = "-"

[[graphite]]
protocol = "udP"
enabled = true
address = "192.168.0.2"
port = 2005

# Configure collectd server
[collectd]
enabled = true
address = "192.168.0.3"
port = 25827
database = "collectd_database"
typesdb = "foo-db-type"

# Broker configuration
[broker]
# The broker port should be open between all servers in a cluster.
# However, this port shouldn't be accessible from the internet.
port = 8086

# Where the broker logs are stored. The user running InfluxDB will need read/write access.
dir  = "/tmp/influxdb/development/broker"

# election-timeout = "2s"

[data]
dir = "/tmp/influxdb/development/db"
retention-auto-create = false
retention-check-enabled = true
retention-check-period = "5m"

[continuous_queries]
disable = false

[cluster]
dir = "/tmp/influxdb/development/cluster"
`

func TestCollectd_ConnectionString(t *testing.T) {
	var tests = []struct {
		name             string
		defaultBindAddr  string
		connectionString string
		config           main.Collectd
	}{
		{
			name:             "No address or port provided from config",
			defaultBindAddr:  "192.168.0.1",
			connectionString: "192.168.0.1:25826",
			config:           main.Collectd{},
		},
		{
			name:             "address provided, no port provided from config",
			defaultBindAddr:  "192.168.0.1",
			connectionString: "192.168.0.2:25826",
			config:           main.Collectd{Addr: "192.168.0.2"},
		},
		{
			name:             "no address provided, port provided from config",
			defaultBindAddr:  "192.168.0.1",
			connectionString: "192.168.0.1:25827",
			config:           main.Collectd{Port: 25827},
		},
		{
			name:             "both address and port provided from config",
			defaultBindAddr:  "192.168.0.1",
			connectionString: "192.168.0.2:25827",
			config:           main.Collectd{Addr: "192.168.0.2", Port: 25827},
		},
	}

	for _, test := range tests {
		t.Logf("test: %q", test.name)
		s := test.config.ConnectionString(test.defaultBindAddr)
		if s != test.connectionString {
			t.Errorf("connection string mismatch, expected: %q, got: %q", test.connectionString, s)
		}
	}
}
<commit_msg>Fix test config for _internal database<commit_after>package main_test

import (
	"reflect"
	"strings"
	"testing"
	"time"

	main "github.com/influxdb/influxdb/cmd/influxd"
)

// Ensure that megabyte sizes can be parsed.
func TestSize_UnmarshalText_MB(t *testing.T) {
	var s main.Size
	if err := s.UnmarshalText([]byte("200m")); err != nil {
		t.Fatalf("unexpected error: %s", err)
	} else if s != 200*(1<<20) {
		t.Fatalf("unexpected size: %d", s)
	}
}

// Ensure that gigabyte sizes can be parsed.
func TestSize_UnmarshalText_GB(t *testing.T) {
	if typ := reflect.TypeOf(0); typ.Size() != 8 {
		t.Skip("large gigabyte parsing on 64-bit arch only")
	}

	var s main.Size
	if err := s.UnmarshalText([]byte("10g")); err != nil {
		t.Fatalf("unexpected error: %s", err)
	} else if s != 10*(1<<30) {
		t.Fatalf("unexpected size: %d", s)
	}
}

// Ensure that a TOML configuration file can be parsed into a Config.
func TestParseConfig(t *testing.T) {
	c, err := main.ParseConfig(testFile)
	if err != nil {
		t.Fatalf("unexpected error: %s", err)
	} else if c.Hostname != "myserver.com" {
		t.Fatalf("hostname mismatch: %v", c.Hostname)
	}

	if c.JoinURLs() != "http://127.0.0.1:8086" {
		t.Fatalf("JoinURLs mistmatch: %v", c.JoinURLs())
	}

	if c.Logging.File != "influxdb.log" {
		t.Fatalf("logging file mismatch: %v", c.Logging.File)
	}

	if !c.Authentication.Enabled {
		t.Fatalf("authentication enabled mismatch: %v", c.Authentication.Enabled)
	}

	if c.UDP.Enabled {
		t.Fatalf("udp enabled mismatch: %v", c.UDP.Enabled)
	}

	if c.Admin.Enabled != true {
		t.Fatalf("admin enabled mismatch: %v", c.Admin.Enabled)
	}

	if c.Admin.Port != 8083 {
		t.Fatalf("admin port mismatch: %v", c.Admin.Port)
	}

	if c.ContinuousQuery.Disable == true {
		t.Fatalf("continuous query disable mismatch: %v", c.ContinuousQuery.Disable)
	}

	if c.Data.Port != main.DefaultBrokerPort {
		t.Fatalf("data port mismatch: %v", c.Data.Port)
	}

	if len(c.Graphites) != 2 {
		t.Fatalf("graphites  mismatch.  expected %v, got: %v", 2, len(c.Graphites))
	}

	tcpGraphite := c.Graphites[0]
	switch {
	case tcpGraphite.Enabled != true:
		t.Fatalf("graphite tcp enabled mismatch: expected: %v, got %v", true, tcpGraphite.Enabled)
	case tcpGraphite.Addr != "192.168.0.1":
		t.Fatalf("graphite tcp address mismatch: expected %v, got  %v", "192.168.0.1", tcpGraphite.Addr)
	case tcpGraphite.Port != 2003:
		t.Fatalf("graphite tcp port mismatch: expected %v, got %v", 2003, tcpGraphite.Port)
	case tcpGraphite.Database != "graphite_tcp":
		t.Fatalf("graphite tcp database mismatch: expected %v, got %v", "graphite_tcp", tcpGraphite.Database)
	case strings.ToLower(tcpGraphite.Protocol) != "tcp":
		t.Fatalf("graphite tcp protocol mismatch: expected %v, got %v", "tcp", strings.ToLower(tcpGraphite.Protocol))
	case tcpGraphite.LastEnabled() != true:
		t.Fatalf("graphite tcp name-position mismatch: expected %v, got %v", "last", tcpGraphite.NamePosition)
	case tcpGraphite.NameSeparatorString() != "-":
		t.Fatalf("graphite tcp name-separator mismatch: expected %v, got %v", "-", tcpGraphite.NameSeparatorString())
	}

	udpGraphite := c.Graphites[1]
	switch {
	case udpGraphite.Enabled != true:
		t.Fatalf("graphite udp enabled mismatch: expected: %v, got %v", true, udpGraphite.Enabled)
	case udpGraphite.Addr != "192.168.0.2":
		t.Fatalf("graphite udp address mismatch: expected %v, got  %v", "192.168.0.2", udpGraphite.Addr)
	case udpGraphite.Port != 2005:
		t.Fatalf("graphite udp port mismatch: expected %v, got %v", 2005, udpGraphite.Port)
	case udpGraphite.DatabaseString() != "graphite":
		t.Fatalf("graphite database mismatch: expected %v, got %v", "graphite", udpGraphite.Database)
	case strings.ToLower(udpGraphite.Protocol) != "udp":
		t.Fatalf("graphite udp protocol mismatch: expected %v, got %v", "udp", strings.ToLower(udpGraphite.Protocol))
	}

	switch {
	case c.Collectd.Enabled != true:
		t.Errorf("collectd enabled mismatch: expected: %v, got %v", true, c.Collectd.Enabled)
	case c.Collectd.Addr != "192.168.0.3":
		t.Errorf("collectd address mismatch: expected %v, got  %v", "192.168.0.3", c.Collectd.Addr)
	case c.Collectd.Port != 25827:
		t.Errorf("collectd port mismatch: expected %v, got %v", 2005, c.Collectd.Port)
	case c.Collectd.Database != "collectd_database":
		t.Errorf("collectdabase mismatch: expected %v, got %v", "collectd_database", c.Collectd.Database)
	case c.Collectd.TypesDB != "foo-db-type":
		t.Errorf("collectd typesdb mismatch: expected %v, got %v", "foo-db-type", c.Collectd.TypesDB)
	}

	if c.Broker.Port != 8086 {
		t.Fatalf("broker port mismatch: %v", c.Broker.Port)
	} else if c.Broker.Dir != "/tmp/influxdb/development/broker" {
		t.Fatalf("broker dir mismatch: %v", c.Broker.Dir)
	} else if time.Duration(c.Broker.Timeout) != time.Second {
		t.Fatalf("broker duration mismatch: %v", c.Broker.Timeout)
	}

	if c.Data.Dir != "/tmp/influxdb/development/db" {
		t.Fatalf("data dir mismatch: %v", c.Data.Dir)
	}
	if c.Data.RetentionCheckEnabled != true {
		t.Fatalf("Retention check enabled mismatch: %v", c.Data.RetentionCheckEnabled)
	}
	if c.Data.RetentionCheckPeriod != main.Duration(5*time.Minute) {
		t.Fatalf("Retention check period mismatch: %v", c.Data.RetentionCheckPeriod)
	}

	if c.Cluster.Dir != "/tmp/influxdb/development/cluster" {
		t.Fatalf("cluster dir mismatch: %v", c.Cluster.Dir)
	}

	// TODO: UDP Servers testing.
	/*
		c.Assert(config.UdpServers, HasLen, 1)
		c.Assert(config.UdpServers[0].Enabled, Equals, true)
		c.Assert(config.UdpServers[0].Port, Equals, 4444)
		c.Assert(config.UdpServers[0].Database, Equals, "test")
	*/
}

// Testing configuration file.
const testFile = `
# Welcome to the InfluxDB configuration file.

# If hostname (on the OS) doesn't return a name that can be resolved by the other
# systems in the cluster, you'll have to set the hostname to an IP or something
# that can be resolved here.
hostname = "myserver.com"

# Controls certain parameters that only take effect until an initial successful
# start-up has occurred.
[initialization]
join-urls = "http://127.0.0.1:8086"

# Control authentication
[authentication]
enabled = true

[logging]
file   = "influxdb.log"
write-tracing = true
raft-tracing = true

[statistics]
enabled = true
database = "_internal"
retention = "default"
duration = "168h"
check-interval = "1m"

# Configure the admin server
[admin]
enabled = true
port = 8083

# Configure the http api
[api]
ssl-port = 8087    # Ssl support is enabled if you set a port and cert
ssl-cert = "../cert.pem"

# connections will timeout after this amount of time. Ensures that clients that misbehave
# and keep alive connections they don't use won't end up connection a million times.
# However, if a request is taking longer than this to complete, could be a problem.
read-timeout = "5s"

[input_plugins]

  [input_plugins.udp]
  enabled = true
  port = 4444
  database = "test"

# Configure the Graphite servers
[[graphite]]
protocol = "TCP"
enabled = true
address = "192.168.0.1"
port = 2003
database = "graphite_tcp"  # store graphite data in this database
name-position = "last"
name-separator = "-"

[[graphite]]
protocol = "udP"
enabled = true
address = "192.168.0.2"
port = 2005

# Configure collectd server
[collectd]
enabled = true
address = "192.168.0.3"
port = 25827
database = "collectd_database"
typesdb = "foo-db-type"

# Broker configuration
[broker]
# The broker port should be open between all servers in a cluster.
# However, this port shouldn't be accessible from the internet.
port = 8086

# Where the broker logs are stored. The user running InfluxDB will need read/write access.
dir  = "/tmp/influxdb/development/broker"

# election-timeout = "2s"

[data]
dir = "/tmp/influxdb/development/db"
retention-auto-create = false
retention-check-enabled = true
retention-check-period = "5m"

[continuous_queries]
disable = false

[cluster]
dir = "/tmp/influxdb/development/cluster"
`

func TestCollectd_ConnectionString(t *testing.T) {
	var tests = []struct {
		name             string
		defaultBindAddr  string
		connectionString string
		config           main.Collectd
	}{
		{
			name:             "No address or port provided from config",
			defaultBindAddr:  "192.168.0.1",
			connectionString: "192.168.0.1:25826",
			config:           main.Collectd{},
		},
		{
			name:             "address provided, no port provided from config",
			defaultBindAddr:  "192.168.0.1",
			connectionString: "192.168.0.2:25826",
			config:           main.Collectd{Addr: "192.168.0.2"},
		},
		{
			name:             "no address provided, port provided from config",
			defaultBindAddr:  "192.168.0.1",
			connectionString: "192.168.0.1:25827",
			config:           main.Collectd{Port: 25827},
		},
		{
			name:             "both address and port provided from config",
			defaultBindAddr:  "192.168.0.1",
			connectionString: "192.168.0.2:25827",
			config:           main.Collectd{Addr: "192.168.0.2", Port: 25827},
		},
	}

	for _, test := range tests {
		t.Logf("test: %q", test.name)
		s := test.config.ConnectionString(test.defaultBindAddr)
		if s != test.connectionString {
			t.Errorf("connection string mismatch, expected: %q, got: %q", test.connectionString, s)
		}
	}
}
<|endoftext|>


<commit_before>package cli

import (
	"errors"
	"fmt"
	"os"
	"time"

	log "github.com/Sirupsen/logrus"
	"github.com/bitrise-io/bitrise-cli/bitrise"
	models "github.com/bitrise-io/bitrise-cli/models/models_1_0_0"
	"github.com/bitrise-io/go-pathutil/pathutil"
	stepmanModels "github.com/bitrise-io/stepman/models"
	"github.com/codegangsta/cli"
)

const (
	// DefaultBitriseConfigFileName ...
	DefaultBitriseConfigFileName = "bitrise.yml"
	// DefaultSecretsFileName ...
	DefaultSecretsFileName = ".bitrise.secrets.yml"
)

var (
	failedSteps   []FailedStepModel
	inventoryPath string
	startTime     time.Time
)

// FailedStepModel ...
type FailedStepModel struct {
	StepName string
	Error    error
}

func isBuildFailed() bool {
	if len(failedSteps) > 0 {
		return true
	}
	return false
}

func registerFailedStepListItem(stepListItem models.StepListItemModel, err error) {
	name := ""
	for key := range stepListItem {
		name = key
		break
	}

	failedStep := FailedStepModel{
		StepName: name,
		Error:    err,
	}
	failedSteps = append(failedSteps, failedStep)
	log.Errorf("Failed to execute step: (%v) error: (%v)", name, err)
}

func registerFailedStep(step stepmanModels.StepModel, err error) {
	if *step.IsNotImportant {
		log.Errorf("Failed to execute step: (%v) error: (%v), but it's marked as not important", *step.Title, err)
		fmt.Println()
	} else {
		failedStep := FailedStepModel{
			StepName: *step.Title,
			Error:    err,
		}
		failedSteps = append(failedSteps, failedStep)
		log.Errorf("Failed to execute step: (%v) error: (%v)", *step.Title, err)
		fmt.Println()
	}
}

func registerRunFailed(err error) {
	runTime := time.Now().Sub(startTime)
	log.Fatal("Build failed error: " + err.Error() + " total run time: " + runTime.String())
}

func registerRunSuccess() {
	runTime := time.Now().Sub(startTime)
	log.Info("DONE - Congrats!!")
	log.Info("Total run time: " + runTime.String())
}

func registerRunSuccessWithFailedSteps() {
	runTime := time.Now().Sub(startTime)
	log.Info("Failed steps:", failedSteps)
	log.Info("FINISHED but a couple of steps failed - Ouch")
	log.Info("Total run time: " + runTime.String())
	os.Exit(1)
}

func exportEnvironmentsList(envsList []stepmanModels.EnvironmentItemModel) error {
	log.Debugln("[BITRISE_CLI] - Exporting environments:", envsList)

	for _, env := range envsList {
		key, value, err := env.GetKeyValuePair()
		if err != nil {
			return err
		}

		opts, err := env.GetOptions()
		if err != nil {
			return err
		}

		if value != "" {
			if err := bitrise.RunEnvmanAdd(key, value, *opts.IsExpand); err != nil {
				log.Errorln("[BITRISE_CLI] - Failed to run envman add")
				return err
			}
		}
	}
	return nil
}

func cleanupStepWorkDir() error {
	stepYMLPth := bitrise.BitriseWorkDirPath + "/current_step.yml"
	if err := bitrise.RemoveFile(stepYMLPth); err != nil {
		return errors.New(fmt.Sprint("Failed to remove step yml: ", err))
	}

	stepDir := bitrise.BitriseWorkStepsDirPath
	if err := bitrise.RemoveDir(stepDir); err != nil {
		return errors.New(fmt.Sprint("Failed to remove step work dir: ", err))
	}
	return nil
}

func activateAndRunSteps(workflow models.WorkflowModel, defaultStepLibSource string) error {
	log.Debugln("[BITRISE_CLI] - Activating and running steps")

	for idx, stepListItm := range workflow.Steps {
		compositeStepIDStr, workflowStep, err := models.GetStepIDStepDataPair(stepListItm)
		if err != nil {
			registerFailedStepListItem(stepListItm, err)
			continue
		}
		stepIDData, err := models.CreateStepIDDataFromString(compositeStepIDStr, defaultStepLibSource)
		if err != nil {
			registerFailedStepListItem(stepListItm, err)
			continue
		}
		log.Debugf("[BITRISE_CLI] - Running Step: %#v", workflowStep)

		stepDir := bitrise.BitriseWorkStepsDirPath

		if err := bitrise.RunStepmanSetup(stepIDData.SteplibSource); err != nil {
			registerFailedStepListItem(stepListItm, err)
			continue
		}

		if err := cleanupStepWorkDir(); err != nil {
			registerFailedStepListItem(stepListItm, err)
			continue
		}

		stepYMLPth := bitrise.BitriseWorkDirPath + "/current_step.yml"
		if err := bitrise.RunStepmanActivate(stepIDData.SteplibSource, stepIDData.ID, stepIDData.Version, stepDir, stepYMLPth); err != nil {
			registerFailedStepListItem(stepListItm, err)
			continue
		} else {
			log.Debugf("[BITRISE_CLI] - Step activated: %s (%s)", stepIDData.ID, stepIDData.Version)

			specStep, err := bitrise.ReadSpecStep(stepYMLPth)
			log.Debugf("Spec read from YML: %#v\n", specStep)
			if err != nil {
				registerFailedStepListItem(stepListItm, err)
				continue
			}

			if err := models.MergeStepWith(specStep, workflowStep); err != nil {
				registerFailedStepListItem(stepListItm, err)
				continue
			}

			fmt.Println()
			log.Infof("========== (%d) %s ==========", idx, *specStep.Title)
			fmt.Println()

			if isBuildFailed() && !*specStep.IsAlwaysRun {
				log.Infof("A previous step failed and this step was not marked to IsAlwaysRun - skipping step (id:%s) (version:%s)", stepIDData.ID, stepIDData.Version)
			} else {
				if err := runStep(specStep, stepIDData); err != nil {
					registerFailedStep(specStep, err)
					continue

				}
			}

			fmt.Println()
		}
	}
	return nil
}

func runStep(step stepmanModels.StepModel, stepIDData models.StepIDData) error {
	log.Debugf("[BITRISE_CLI] - Try running step: %s (%s)", stepIDData.ID, stepIDData.Version)

	// Add step envs
	for _, input := range step.Inputs {
		key, value, err := input.GetKeyValuePair()
		if err != nil {
			return err
		}

		opts, err := input.GetOptions()
		if err != nil {
			return err
		}

		if value != "" {
			log.Debugf("Input: %#v\n", input)
			if err := bitrise.RunEnvmanAdd(key, value, *opts.IsExpand); err != nil {
				log.Errorln("[BITRISE_CLI] - Failed to run envman add")
				return err
			}
		}
	}

	stepDir := bitrise.BitriseWorkStepsDirPath
	stepCmd := stepDir + "/" + "step.sh"
	cmd := []string{"bash", stepCmd}
	if err := bitrise.RunEnvmanRunInDir(bitrise.CurrentDir, cmd); err != nil {
		log.Errorln("[BITRISE_CLI] - Failed to run envman run")
		return err
	}

	log.Debugf("[BITRISE_CLI] - Step executed: %s (%s)", stepIDData.ID, stepIDData.Version)
	return nil
}

func doRun(c *cli.Context) {
	log.Debugln("[BITRISE_CLI] - Run")

	startTime = time.Now()
	failedSteps = []FailedStepModel{}

	// Cleanup
	if err := bitrise.CleanupBitriseWorkPath(); err != nil {
		registerRunFailed(errors.New("[BITRISE_CLI] - Failed to cleanup bitrise work dir: " + err.Error()))
	}

	// Input validation
	bitriseConfigPath := c.String(PathKey)
	if bitriseConfigPath == "" {
		log.Debugln("[BITRISE_CLI] - Workflow path not defined, searching for " + DefaultBitriseConfigFileName + " in current folder...")

		if exist, err := pathutil.IsPathExists("./" + DefaultBitriseConfigFileName); err != nil {
			registerRunFailed(errors.New("[BITRISE_CLI] - Failed to check path:" + err.Error()))
		} else if !exist {
			log.Fatalln("[BITRISE_CLI] - No workflow yml found")
			registerRunFailed(errors.New("[BITRISE_CLI] - No workflow yml found"))
		}
		bitriseConfigPath = "./" + DefaultBitriseConfigFileName
	}

	inventoryPath = c.String(InventoryKey)
	if inventoryPath == "" {
		log.Debugln("[BITRISE_CLI] - Inventory path not defined, searching for " + DefaultSecretsFileName + " in current folder...")
		inventoryPath = bitrise.CurrentDir + "/" + DefaultSecretsFileName

		if exist, err := pathutil.IsPathExists(inventoryPath); err != nil {
			registerRunFailed(errors.New("[BITRISE_CLI] - Failed to check path: " + err.Error()))
		} else if !exist {
			log.Debugln("[BITRISE_CLI] - No inventory yml found")
			inventoryPath = ""
		}
	} else {
		if exist, err := pathutil.IsPathExists(inventoryPath); err != nil {
			registerRunFailed(errors.New("[BITRISE_CLI] - Failed to check path: " + err.Error()))
		} else if !exist {
			registerRunFailed(errors.New("[BITRISE_CLI] - No inventory yml found"))
		}
	}
	if inventoryPath != "" {
		if err := bitrise.RunEnvmanEnvstoreTest(inventoryPath); err != nil {
			registerRunFailed(errors.New("Invalid invetory format: " + err.Error()))
		}

		if err := bitrise.RunCopy(inventoryPath, bitrise.EnvstorePath); err != nil {
			registerRunFailed(errors.New("Failed to copy inventory: " + err.Error()))
		}
	}

	// Workflow selection
	workflowToRunName := ""
	if len(c.Args()) < 1 {
		log.Infoln("No workfow specified!")
	} else {
		workflowToRunName = c.Args()[0]
	}

	// Envman setup
	if err := os.Setenv(bitrise.EnvstorePathEnvKey, bitrise.EnvstorePath); err != nil {
		registerRunFailed(errors.New("[BITRISE_CLI] - Failed to add env: " + err.Error()))
	}

	if err := os.Setenv(bitrise.FormattedOutputPathEnvKey, bitrise.FormattedOutputPath); err != nil {
		registerRunFailed(errors.New("[BITRISE_CLI] - Failed to add env: " + err.Error()))
	}

	if inventoryPath == "" {
		if err := bitrise.RunEnvmanInit(); err != nil {
			registerRunFailed(errors.New("[BITRISE_CLI] - Failed to run envman init"))
		}
	}

	// Run work flow
	bitriseConfig, err := bitrise.ReadBitriseConfig(bitriseConfigPath)
	if err != nil {
		registerRunFailed(errors.New("[BITRISE_CLI] - Failed to read Workflow: " + err.Error()))
	}

	// check workflow
	if workflowToRunName == "" {
		// no workflow specified
		//  list all the available ones and then exit
		log.Infoln("The following workflows are available:")
		for wfName := range bitriseConfig.Workflows {
			log.Infoln(" * " + wfName)
		}
		os.Exit(1)
	}

	workflowToRun, exist := bitriseConfig.Workflows[workflowToRunName]
	if !exist {
		registerRunFailed(errors.New("[BITRISE_CLI] - Specified Workflow (" + workflowToRunName + ") does not exist!"))
	}
	log.Infoln("[BITRISE_CLI] - Running Workflow:", workflowToRunName)

	// App level environment
	if err := exportEnvironmentsList(bitriseConfig.App.Environments); err != nil {
		registerRunFailed(errors.New("[BITRISE_CLI] - Failed to export App environments: " + err.Error()))
	}

	// Workflow level environments
	if err := exportEnvironmentsList(workflowToRun.Environments); err != nil {
		registerRunFailed(errors.New("[BITRISE_CLI] - Failed to export Workflow environments: " + err.Error()))
	}

	// Run the Workflow
	if err := activateAndRunSteps(workflowToRun, bitriseConfig.DefaultStepLibSource); err != nil {
		log.Fatalln("[BITRISE_CLI] - Failed to activate steps:", err)
		registerRunFailed(errors.New("[BITRISE_CLI] - Failed to activate steps: " + err.Error()))
	}

	log.Infoln("")
	if len(failedSteps) > 0 {
		registerRunSuccessWithFailedSteps()
	} else {
		registerRunSuccess()
	}
}
<commit_msg>failed step fix<commit_after>package cli

import (
	"errors"
	"fmt"
	"os"
	"time"

	log "github.com/Sirupsen/logrus"
	"github.com/bitrise-io/bitrise-cli/bitrise"
	models "github.com/bitrise-io/bitrise-cli/models/models_1_0_0"
	"github.com/bitrise-io/go-pathutil/pathutil"
	stepmanModels "github.com/bitrise-io/stepman/models"
	"github.com/codegangsta/cli"
)

const (
	// DefaultBitriseConfigFileName ...
	DefaultBitriseConfigFileName = "bitrise.yml"
	// DefaultSecretsFileName ...
	DefaultSecretsFileName = ".bitrise.secrets.yml"
)

var (
	failedSteps   []FailedStepModel
	inventoryPath string
	startTime     time.Time
)

// FailedStepModel ...
type FailedStepModel struct {
	StepName       string
	Error          error
	IsNotImportant bool
}

func isBuildFailed() bool {
	for _, failedStep := range failedSteps {
		if !failedStep.IsNotImportant {
			return true
		}
	}
	return false
}

func registerFailedStepListItem(stepListItem models.StepListItemModel, err error) {
	name := ""
	for key := range stepListItem {
		name = key
		break
	}

	failedStep := FailedStepModel{
		StepName:       name,
		Error:          err,
		IsNotImportant: false,
	}
	failedSteps = append(failedSteps, failedStep)
	log.Errorf("Failed to execute step: (%v) error: (%v)", name, err)
}

func registerFailedStep(step stepmanModels.StepModel, err error) {
	if *step.IsNotImportant {
		log.Errorf("Failed to execute step: (%v) error: (%v), but it's marked as not important", *step.Title, err)
		fmt.Println()
	} else {
		log.Errorf("Failed to execute step: (%v) error: (%v)", *step.Title, err)
		fmt.Println()
	}

	failedStep := FailedStepModel{
		StepName:       *step.Title,
		Error:          err,
		IsNotImportant: *step.IsNotImportant,
	}
	failedSteps = append(failedSteps, failedStep)
}

func registerRunFailed(err error) {
	runTime := time.Now().Sub(startTime)
	log.Fatal("Build failed error: " + err.Error() + " total run time: " + runTime.String())
}

func registerRunSuccess() {
	runTime := time.Now().Sub(startTime)
	log.Info("Total run time: " + runTime.String())
	log.Info("DONE - Congrats!!")
}

func registerRunSuccessWithFailedSteps() {
	runTime := time.Now().Sub(startTime)
	printFailedSteps()
	log.Info("Total run time: " + runTime.String())
	log.Info("FINISHED but a couple of steps failed - Ouch")
	os.Exit(1)
}

func printFailedSteps() {
	log.Infof("%d step(s) failed:", len(failedSteps))
	for _, failedStep := range failedSteps {
		if failedStep.IsNotImportant {
			log.Infof("Step: (%s) | marked as not important | error: (%v)", failedStep.StepName, failedStep.Error)
		} else {
			log.Infof("Step: (%s) | error: (%v)", failedStep.StepName, failedStep.Error)
		}
	}
}

func exportEnvironmentsList(envsList []stepmanModels.EnvironmentItemModel) error {
	log.Debugln("[BITRISE_CLI] - Exporting environments:", envsList)

	for _, env := range envsList {
		key, value, err := env.GetKeyValuePair()
		if err != nil {
			return err
		}

		opts, err := env.GetOptions()
		if err != nil {
			return err
		}

		if value != "" {
			if err := bitrise.RunEnvmanAdd(key, value, *opts.IsExpand); err != nil {
				log.Errorln("[BITRISE_CLI] - Failed to run envman add")
				return err
			}
		}
	}
	return nil
}

func cleanupStepWorkDir() error {
	stepYMLPth := bitrise.BitriseWorkDirPath + "/current_step.yml"
	if err := bitrise.RemoveFile(stepYMLPth); err != nil {
		return errors.New(fmt.Sprint("Failed to remove step yml: ", err))
	}

	stepDir := bitrise.BitriseWorkStepsDirPath
	if err := bitrise.RemoveDir(stepDir); err != nil {
		return errors.New(fmt.Sprint("Failed to remove step work dir: ", err))
	}
	return nil
}

func activateAndRunSteps(workflow models.WorkflowModel, defaultStepLibSource string) error {
	log.Debugln("[BITRISE_CLI] - Activating and running steps")

	for idx, stepListItm := range workflow.Steps {
		compositeStepIDStr, workflowStep, err := models.GetStepIDStepDataPair(stepListItm)
		if err != nil {
			registerFailedStepListItem(stepListItm, err)
			continue
		}
		stepIDData, err := models.CreateStepIDDataFromString(compositeStepIDStr, defaultStepLibSource)
		if err != nil {
			registerFailedStepListItem(stepListItm, err)
			continue
		}
		log.Debugf("[BITRISE_CLI] - Running Step: %#v", workflowStep)

		stepDir := bitrise.BitriseWorkStepsDirPath

		if err := bitrise.RunStepmanSetup(stepIDData.SteplibSource); err != nil {
			registerFailedStepListItem(stepListItm, err)
			continue
		}

		if err := cleanupStepWorkDir(); err != nil {
			registerFailedStepListItem(stepListItm, err)
			continue
		}

		stepYMLPth := bitrise.BitriseWorkDirPath + "/current_step.yml"
		if err := bitrise.RunStepmanActivate(stepIDData.SteplibSource, stepIDData.ID, stepIDData.Version, stepDir, stepYMLPth); err != nil {
			registerFailedStepListItem(stepListItm, err)
			continue
		} else {
			log.Debugf("[BITRISE_CLI] - Step activated: %s (%s)", stepIDData.ID, stepIDData.Version)

			specStep, err := bitrise.ReadSpecStep(stepYMLPth)
			log.Debugf("Spec read from YML: %#v\n", specStep)
			if err != nil {
				registerFailedStepListItem(stepListItm, err)
				continue
			}

			if err := models.MergeStepWith(specStep, workflowStep); err != nil {
				registerFailedStepListItem(stepListItm, err)
				continue
			}

			fmt.Println()
			log.Infof("========== (%d) %s ==========", idx, *specStep.Title)
			fmt.Println()

			if isBuildFailed() && !*specStep.IsAlwaysRun {
				log.Infof("A previous step failed and this step was not marked to IsAlwaysRun - skipping step (id:%s) (version:%s)", stepIDData.ID, stepIDData.Version)
			} else {
				if err := runStep(specStep, stepIDData); err != nil {
					registerFailedStep(specStep, err)
					continue

				}
			}

			fmt.Println()
		}
	}
	return nil
}

func runStep(step stepmanModels.StepModel, stepIDData models.StepIDData) error {
	log.Debugf("[BITRISE_CLI] - Try running step: %s (%s)", stepIDData.ID, stepIDData.Version)

	// Add step envs
	for _, input := range step.Inputs {
		key, value, err := input.GetKeyValuePair()
		if err != nil {
			return err
		}

		opts, err := input.GetOptions()
		if err != nil {
			return err
		}

		if value != "" {
			log.Debugf("Input: %#v\n", input)
			if err := bitrise.RunEnvmanAdd(key, value, *opts.IsExpand); err != nil {
				log.Errorln("[BITRISE_CLI] - Failed to run envman add")
				return err
			}
		}
	}

	stepDir := bitrise.BitriseWorkStepsDirPath
	stepCmd := stepDir + "/" + "step.sh"
	cmd := []string{"bash", stepCmd}
	if err := bitrise.RunEnvmanRunInDir(bitrise.CurrentDir, cmd); err != nil {
		log.Errorln("[BITRISE_CLI] - Failed to run envman run")
		return err
	}

	log.Debugf("[BITRISE_CLI] - Step executed: %s (%s)", stepIDData.ID, stepIDData.Version)
	return nil
}

func doRun(c *cli.Context) {
	log.Debugln("[BITRISE_CLI] - Run")

	startTime = time.Now()
	failedSteps = []FailedStepModel{}

	// Cleanup
	if err := bitrise.CleanupBitriseWorkPath(); err != nil {
		registerRunFailed(errors.New("[BITRISE_CLI] - Failed to cleanup bitrise work dir: " + err.Error()))
	}

	// Input validation
	bitriseConfigPath := c.String(PathKey)
	if bitriseConfigPath == "" {
		log.Debugln("[BITRISE_CLI] - Workflow path not defined, searching for " + DefaultBitriseConfigFileName + " in current folder...")

		if exist, err := pathutil.IsPathExists("./" + DefaultBitriseConfigFileName); err != nil {
			registerRunFailed(errors.New("[BITRISE_CLI] - Failed to check path:" + err.Error()))
		} else if !exist {
			log.Fatalln("[BITRISE_CLI] - No workflow yml found")
			registerRunFailed(errors.New("[BITRISE_CLI] - No workflow yml found"))
		}
		bitriseConfigPath = "./" + DefaultBitriseConfigFileName
	}

	inventoryPath = c.String(InventoryKey)
	if inventoryPath == "" {
		log.Debugln("[BITRISE_CLI] - Inventory path not defined, searching for " + DefaultSecretsFileName + " in current folder...")
		inventoryPath = bitrise.CurrentDir + "/" + DefaultSecretsFileName

		if exist, err := pathutil.IsPathExists(inventoryPath); err != nil {
			registerRunFailed(errors.New("[BITRISE_CLI] - Failed to check path: " + err.Error()))
		} else if !exist {
			log.Debugln("[BITRISE_CLI] - No inventory yml found")
			inventoryPath = ""
		}
	} else {
		if exist, err := pathutil.IsPathExists(inventoryPath); err != nil {
			registerRunFailed(errors.New("[BITRISE_CLI] - Failed to check path: " + err.Error()))
		} else if !exist {
			registerRunFailed(errors.New("[BITRISE_CLI] - No inventory yml found"))
		}
	}
	if inventoryPath != "" {
		if err := bitrise.RunEnvmanEnvstoreTest(inventoryPath); err != nil {
			registerRunFailed(errors.New("Invalid invetory format: " + err.Error()))
		}

		if err := bitrise.RunCopy(inventoryPath, bitrise.EnvstorePath); err != nil {
			registerRunFailed(errors.New("Failed to copy inventory: " + err.Error()))
		}
	}

	// Workflow selection
	workflowToRunName := ""
	if len(c.Args()) < 1 {
		log.Infoln("No workfow specified!")
	} else {
		workflowToRunName = c.Args()[0]
	}

	// Envman setup
	if err := os.Setenv(bitrise.EnvstorePathEnvKey, bitrise.EnvstorePath); err != nil {
		registerRunFailed(errors.New("[BITRISE_CLI] - Failed to add env: " + err.Error()))
	}

	if err := os.Setenv(bitrise.FormattedOutputPathEnvKey, bitrise.FormattedOutputPath); err != nil {
		registerRunFailed(errors.New("[BITRISE_CLI] - Failed to add env: " + err.Error()))
	}

	if inventoryPath == "" {
		if err := bitrise.RunEnvmanInit(); err != nil {
			registerRunFailed(errors.New("[BITRISE_CLI] - Failed to run envman init"))
		}
	}

	// Run work flow
	bitriseConfig, err := bitrise.ReadBitriseConfig(bitriseConfigPath)
	if err != nil {
		registerRunFailed(errors.New("[BITRISE_CLI] - Failed to read Workflow: " + err.Error()))
	}

	// check workflow
	if workflowToRunName == "" {
		// no workflow specified
		//  list all the available ones and then exit
		log.Infoln("The following workflows are available:")
		for wfName := range bitriseConfig.Workflows {
			log.Infoln(" * " + wfName)
		}
		os.Exit(1)
	}

	workflowToRun, exist := bitriseConfig.Workflows[workflowToRunName]
	if !exist {
		registerRunFailed(errors.New("[BITRISE_CLI] - Specified Workflow (" + workflowToRunName + ") does not exist!"))
	}
	log.Infoln("[BITRISE_CLI] - Running Workflow:", workflowToRunName)

	// App level environment
	if err := exportEnvironmentsList(bitriseConfig.App.Environments); err != nil {
		registerRunFailed(errors.New("[BITRISE_CLI] - Failed to export App environments: " + err.Error()))
	}

	// Workflow level environments
	if err := exportEnvironmentsList(workflowToRun.Environments); err != nil {
		registerRunFailed(errors.New("[BITRISE_CLI] - Failed to export Workflow environments: " + err.Error()))
	}

	// Run the Workflow
	if err := activateAndRunSteps(workflowToRun, bitriseConfig.DefaultStepLibSource); err != nil {
		log.Fatalln("[BITRISE_CLI] - Failed to activate steps:", err)
		registerRunFailed(errors.New("[BITRISE_CLI] - Failed to activate steps: " + err.Error()))
	}

	log.Infoln("")
	if len(failedSteps) > 0 {
		registerRunSuccessWithFailedSteps()
	} else {
		registerRunSuccess()
	}
}
<|endoftext|>


<commit_before>package radix

import "time"

// PoolConnCmder is a ConnCmder which came from a Pool, and which has the
// special property of being able to be returned to the Pool it came from
type PoolConnCmder interface {
	ConnCmder

	// Done, when called, indicates that the PoolConnCmder will no longer be
	// used by its current borrower and should be returned to the Pool it came
	// from. This _must_ be called by all borrowers, or their PoolConnCmders
	// will never be put back in their Pools.
	//
	// May not be called after Close is called on the PoolConnCmder
	Done()
}

// Pool is an entity which can be used to manage a set of open ConnCmders which
// can be used by multiple go-routines.
type Pool interface {
	// Get retrieves an available PoolConnCmder for use by a single go-routine
	// (until a subsequent call to Done on it), or returns an error if that's
	// not possible.
	//
	// The key passed in should be one of the keys involved in the
	// command/commands being performed. This is helps to support cluster, for
	// normal pool implementations this key may be ignored.
	Get(forKey string) (PoolConnCmder, error)

	// Put takes in a ConnCmder previously returned by Get and returns it to the
	// Pool. A defunct ConnCmder (i.e. one which has been closed or encountered
	// an IOErr) may be passed in, so Put needs to handle that case (probably by
	// discarding it).
	//
	// Only ConnCmders obtained through Get should be passed into the same
	// Pool's Put.
	//Put(ConnCmder)

	// Close closes all PoolConnCmders in the Pool and cleans up the Pool's
	// resources. Once Close is called no other methods should be called on the
	// Pool, though Done may still be called on PoolConnCmders which haven't
	// been returned yet (these will be closed at that point as well).
	Close()

	// Stats returns any runtime stats that the implementation of Pool wishes to
	// return, or nil if it doesn't want to return any. This method aims to help
	// support logging and debugging, not necessarily to give any actionable
	// information to the program during runtime.
	//
	// Examples of useful runtime stats might be: number of connections
	// currently available, number of connections currently lent out, number of
	// connections ever created, number of connections ever closed, average time
	// to create a new connection, and so on.
	//
	// TODO I'm not sure if I actually like this
	//
	//Stats() map[string]interface{}
}

// PoolFunc is a function which can be used to create a Pool of connections to
// the redis instance on the given network/address.
type PoolFunc func(network, addr string) (Pool, error)

// TODO expose Avail for the pool

type staticPoolConn struct {
	ConnCmder
	sp *staticPool

	// The most recent network error which occurred when either reading
	// or writing. A critical network error is basically any non-application
	// level error, e.g. a timeout, disconnect, etc... Close is automatically
	// called on the client when it encounters a critical network error
	lastIOErr error
}

func (spc *staticPoolConn) Done() {
	if spc.sp == nil {
		panic("Done called on Closed PoolConnCmder")
	}
	spc.sp.put(spc)
}

func (spc *staticPoolConn) Write(m interface{}) error {
	if spc.lastIOErr != nil {
		return spc.lastIOErr
	}

	err := spc.ConnCmder.Write(m)
	if err != nil {
		spc.lastIOErr = err
	}
	return err
}

func (spc *staticPoolConn) Read() Resp {
	if spc.lastIOErr != nil {
		return ioErrResp(spc.lastIOErr)
	}

	r := spc.ConnCmder.Read()
	if _, ok := r.Err.(IOErr); ok {
		spc.lastIOErr = r.Err
	}
	return r
}

func (spc *staticPoolConn) Close() error {
	// in case there's some kind of problem with circular reference and gc, also
	// prevents Done from being called
	spc.sp = nil
	return spc.ConnCmder.Close()
}

type staticPool struct {
	pool          chan *staticPoolConn
	df            DialFunc
	network, addr string
	closed        chan struct{}
}

// NewPool creates a new Pool whose connections are all created using the given
// DialFunc (or Dial, if the given DialFunc is nil).  The size indicates the
// maximum number of idle connections to have waiting to be used at any given
// moment. If an error is encountered an empty (but still usable) Pool is
// returned alongside that error
//
// The implementation of Pool returned here is a semi-dynamic pool. It holds a
// fixed number of connections open. If Get is called and there are no available
// Conns it will create a new one on the spot (using the DialFunc). If Put is
// called and the Pool is full that Conn will be closed and discarded. In this
// way spikes are handled rather well, but sustained over-use will cause
// connection churn and will need the size to be increased.
// TODO make this return a PoolCmder
func NewPool(network, addr string, size int, df DialFunc) (Pool, error) {
	sp := &staticPool{
		network: network,
		addr:    addr,
		df:      df,
		pool:    make(chan *staticPoolConn, size),
		closed:  make(chan struct{}),
	}
	if sp.df == nil {
		sp.df = Dial
	}

	// First make as many Conns as we can to initialize the pool. If we hit an
	// error bail entirely, we'll return an empty pool
	var spc *staticPoolConn
	var err error
	pool := make([]*staticPoolConn, 0, size)
	for i := 0; i < size; i++ {
		if spc, err = sp.newConn(); err != nil {
			for _, spc := range pool {
				spc.Close()
			}
			pool = pool[0:]
			break
		}
		pool = append(pool, spc)
	}

	for i := range pool {
		sp.pool <- pool[i]
	}
	return sp, err
}

func (sp *staticPool) newConn() (*staticPoolConn, error) {
	c, err := sp.df(sp.network, sp.addr)
	if err != nil {
		return nil, err
	}

	return &staticPoolConn{
		ConnCmder: NewConnCmder(c),
		sp:        sp,
	}, nil
}

func (sp *staticPool) Get(forkey string) (PoolConnCmder, error) {
	select {
	case spc := <-sp.pool:
		return spc, nil
	default:
		return sp.newConn()
	}
}

func (sp *staticPool) put(spc *staticPoolConn) {
	if spc.lastIOErr != nil {
		spc.Close()
		return
	}

	select {
	case <-sp.closed:
		spc.Close()
		return
	default:
		select {
		case sp.pool <- spc:
		default:
			spc.Close()
		}
	}
}

func (sp *staticPool) Close() {
	close(sp.closed)
	for {
		select {
		case spc := <-sp.pool:
			spc.Close()
		default:
			close(sp.pool)
			return
		}
	}
}

type poolCmder struct {
	p Pool
}

// NewPoolCmder takes a Pool and wraps it to support the Cmd method. When Cmd is
// called the PoolCmder will call Get, then run the command on the returned
// Conn, then call Put on the Conn.
func NewPoolCmder(p Pool) Cmder {
	return poolCmder{p: p}
}

func (pc poolCmder) Cmd(cmd string, args ...interface{}) Resp {
	k := NewCmd(cmd, args...).FirstArg()
	c, err := pc.p.Get(k)
	if err != nil {
		return ioErrResp(err)
	}
	defer c.Done()

	return c.Cmd(cmd, args...)
}

type poolPinger struct {
	Pool
	closeCh chan struct{}
	doneCh  chan struct{}
}

// NewPoolPinger will periodically call Get on the given Pool, do a PING
// command, then return the connection to the Pool. This effectively tests the
// connections and cleans our the dead ones.
//
// Close must be called on the returned Pool in order to properly clean up.
func NewPoolPinger(p Pool, period time.Duration) Pool {
	closeCh := make(chan struct{})
	doneCh := make(chan struct{})
	go func() {
		pc := NewPoolCmder(p)
		t := time.NewTicker(period)
		for {
			select {
			case <-t.C:
				pc.Cmd("PING")
			case <-closeCh:
				t.Stop()
				close(doneCh)
				return
			}
		}
	}()

	return poolPinger{Pool: p, closeCh: closeCh, doneCh: doneCh}
}

func (pp poolPinger) Close() {
	close(pp.closeCh)
	<-pp.doneCh
	pp.Pool.Close()
}
<commit_msg>make Pool just return a Cmder, not a ConnCmder<commit_after>package radix

import "time"

// PoolCmder is a ConnCmder which came from a Pool, and which has the
// special property of being able to be returned to the Pool it came from
type PoolCmder interface {
	Cmder

	// Done, when called, indicates that the PoolCmder will no longer be
	// used by its current borrower and should be returned to the Pool it came
	// from. This _must_ be called by all borrowers, or their PoolCmders
	// will never be put back in their Pools.
	//
	// May not be called after Close is called on the PoolCmder
	Done()
}

// Pool is an entity which can be used to manage a set of open ConnCmders which
// can be used by multiple go-routines.
type Pool interface {
	// Get retrieves an available PoolCmder for use by a single go-routine
	// (until a subsequent call to Done on it), or returns an error if that's
	// not possible.
	//
	// The key passed in should be one of the keys involved in the
	// command/commands being performed. This is helps to support cluster, for
	// normal pool implementations this key may be ignored.
	Get(forKey string) (PoolCmder, error)

	// Put takes in a ConnCmder previously returned by Get and returns it to the
	// Pool. A defunct ConnCmder (i.e. one which has been closed or encountered
	// an IOErr) may be passed in, so Put needs to handle that case (probably by
	// discarding it).
	//
	// Only ConnCmders obtained through Get should be passed into the same
	// Pool's Put.
	//Put(ConnCmder)

	// Close closes all PoolCmders in the Pool and cleans up the Pool's
	// resources. Once Close is called no other methods should be called on the
	// Pool, though Done may still be called on PoolCmders which haven't
	// been returned yet (these will be closed at that point as well).
	Close()

	// Stats returns any runtime stats that the implementation of Pool wishes to
	// return, or nil if it doesn't want to return any. This method aims to help
	// support logging and debugging, not necessarily to give any actionable
	// information to the program during runtime.
	//
	// Examples of useful runtime stats might be: number of connections
	// currently available, number of connections currently lent out, number of
	// connections ever created, number of connections ever closed, average time
	// to create a new connection, and so on.
	//
	// TODO I'm not sure if I actually like this
	//
	//Stats() map[string]interface{}
}

// PoolFunc is a function which can be used to create a Pool of connections to
// the redis instance on the given network/address.
type PoolFunc func(network, addr string) (Pool, error)

// TODO expose Avail for the pool

type staticPoolConn struct {
	ConnCmder
	sp *staticPool

	// The most recent network error which occurred when either reading
	// or writing. A critical network error is basically any non-application
	// level error, e.g. a timeout, disconnect, etc... Close is automatically
	// called on the client when it encounters a critical network error
	lastIOErr error
}

func (spc *staticPoolConn) Done() {
	if spc.sp == nil {
		panic("Done called on Closed PoolCmder")
	}
	spc.sp.put(spc)
}

func (spc *staticPoolConn) Write(m interface{}) error {
	if spc.lastIOErr != nil {
		return spc.lastIOErr
	}

	err := spc.ConnCmder.Write(m)
	if err != nil {
		spc.lastIOErr = err
	}
	return err
}

func (spc *staticPoolConn) Read() Resp {
	if spc.lastIOErr != nil {
		return ioErrResp(spc.lastIOErr)
	}

	r := spc.ConnCmder.Read()
	if _, ok := r.Err.(IOErr); ok {
		spc.lastIOErr = r.Err
	}
	return r
}

func (spc *staticPoolConn) Close() error {
	// in case there's some kind of problem with circular reference and gc, also
	// prevents Done from being called
	spc.sp = nil
	return spc.ConnCmder.Close()
}

type staticPool struct {
	pool          chan *staticPoolConn
	df            DialFunc
	network, addr string
	closed        chan struct{}
}

// NewPool creates a new Pool whose connections are all created using the given
// DialFunc (or Dial, if the given DialFunc is nil).  The size indicates the
// maximum number of idle connections to have waiting to be used at any given
// moment. If an error is encountered an empty (but still usable) Pool is
// returned alongside that error
//
// The implementation of Pool returned here is a semi-dynamic pool. It holds a
// fixed number of connections open. If Get is called and there are no available
// Conns it will create a new one on the spot (using the DialFunc). If Put is
// called and the Pool is full that Conn will be closed and discarded. In this
// way spikes are handled rather well, but sustained over-use will cause
// connection churn and will need the size to be increased.
// TODO make this return a PoolCmder
func NewPool(network, addr string, size int, df DialFunc) (Pool, error) {
	sp := &staticPool{
		network: network,
		addr:    addr,
		df:      df,
		pool:    make(chan *staticPoolConn, size),
		closed:  make(chan struct{}),
	}
	if sp.df == nil {
		sp.df = Dial
	}

	// First make as many Conns as we can to initialize the pool. If we hit an
	// error bail entirely, we'll return an empty pool
	var spc *staticPoolConn
	var err error
	pool := make([]*staticPoolConn, 0, size)
	for i := 0; i < size; i++ {
		if spc, err = sp.newConn(); err != nil {
			for _, spc := range pool {
				spc.Close()
			}
			pool = pool[0:]
			break
		}
		pool = append(pool, spc)
	}

	for i := range pool {
		sp.pool <- pool[i]
	}
	return sp, err
}

func (sp *staticPool) newConn() (*staticPoolConn, error) {
	c, err := sp.df(sp.network, sp.addr)
	if err != nil {
		return nil, err
	}

	return &staticPoolConn{
		ConnCmder: NewConnCmder(c),
		sp:        sp,
	}, nil
}

func (sp *staticPool) Get(forkey string) (PoolCmder, error) {
	select {
	case spc := <-sp.pool:
		return spc, nil
	default:
		return sp.newConn()
	}
}

func (sp *staticPool) put(spc *staticPoolConn) {
	if spc.lastIOErr != nil {
		spc.Close()
		return
	}

	select {
	case <-sp.closed:
		spc.Close()
		return
	default:
		select {
		case sp.pool <- spc:
		default:
			spc.Close()
		}
	}
}

func (sp *staticPool) Close() {
	close(sp.closed)
	for {
		select {
		case spc := <-sp.pool:
			spc.Close()
		default:
			close(sp.pool)
			return
		}
	}
}

type poolCmder struct {
	p Pool
}

// NewPoolCmder takes a Pool and wraps it to support the Cmd method. When Cmd is
// called the PoolCmder will call Get, then run the command on the returned
// Conn, then call Put on the Conn.
func NewPoolCmder(p Pool) Cmder {
	return poolCmder{p: p}
}

func (pc poolCmder) Cmd(cmd string, args ...interface{}) Resp {
	k := NewCmd(cmd, args...).FirstArg()
	c, err := pc.p.Get(k)
	if err != nil {
		return ioErrResp(err)
	}
	defer c.Done()

	return c.Cmd(cmd, args...)
}

type poolPinger struct {
	Pool
	closeCh chan struct{}
	doneCh  chan struct{}
}

// NewPoolPinger will periodically call Get on the given Pool, do a PING
// command, then return the connection to the Pool. This effectively tests the
// connections and cleans our the dead ones.
//
// Close must be called on the returned Pool in order to properly clean up.
func NewPoolPinger(p Pool, period time.Duration) Pool {
	closeCh := make(chan struct{})
	doneCh := make(chan struct{})
	go func() {
		pc := NewPoolCmder(p)
		t := time.NewTicker(period)
		for {
			select {
			case <-t.C:
				pc.Cmd("PING")
			case <-closeCh:
				t.Stop()
				close(doneCh)
				return
			}
		}
	}()

	return poolPinger{Pool: p, closeCh: closeCh, doneCh: doneCh}
}

func (pp poolPinger) Close() {
	close(pp.closeCh)
	<-pp.doneCh
	pp.Pool.Close()
}
<|endoftext|>


<commit_before>//       ___  _____  ____
//      / _ \/  _/ |/_/ /____ ______ _
//     / ___// /_>  </ __/ -_) __/  ' \
//    /_/  /___/_/|_|\__/\__/_/ /_/_/_/
//
//    Copyright 2017 Eliuk Blau
//
//    This Source Code Form is subject to the terms of the Mozilla Public
//    License, v. 2.0. If a copy of the MPL was not distributed with this
//    file, You can obtain one at http://mozilla.org/MPL/2.0/.

package ansimage

import (
	"errors"
	"fmt"
	"image"
	"image/color"
	"image/draw"
	_ "image/gif"  // initialize decoder
	_ "image/jpeg" // initialize decoder
	_ "image/png"  // initialize decoder
	"io"
	"os"
	"strings"

	"github.com/disintegration/imaging"
	_ "golang.org/x/image/bmp"  // initialize decoder
	_ "golang.org/x/image/tiff" // initialize decoder
	_ "golang.org/x/image/webp" // initialize decoder
)

// Unicode Block Element character used to represent lower pixel in terminal row.
// INFO: http://en.wikipedia.org/wiki/Block_Elements
const lowerHalfBlock = "\u2584"

// ANSImage scale modes:
// resize (full scaled to area),
// fill (resize and crop the image with a center anchor point to fill area),
// fit (resize the image to fit area, preserving the aspect ratio).
const (
	ScaleModeResize = scaleMode(iota)
	ScaleModeFill
	ScaleModeFit
)

var (
	// ErrOddHeight happens when ANSImage height is not even value.
	ErrOddHeight = errors.New("ANSImage: height must be even value")

	// ErrInvalidBounds happens when ANSImage height or width are invalid values.
	ErrInvalidBounds = errors.New("ANSImage: height or width must be >=2")

	// ErrOutOfBounds happens when ANSI-pixel coordinates are out of ANSImage bounds.
	ErrOutOfBounds = errors.New("ANSImage: out of bounds")
)

// scaleMode type is used for image scale mode constants.
type scaleMode uint8

// ANSIpixel represents a pixel of an ANSImage.
type ANSIpixel struct {
	R, G, B uint32
	upper   bool
}

// ANSImage represents an image encoded in ANSI escape codes.
type ANSImage struct {
	h, w     int
	maxprocs int
	pixmap   [][]*ANSIpixel
}

// Render returns the ANSI-compatible string form of ANSI-pixel.
func (ap *ANSIpixel) Render() string {
	if ap.upper {
		return fmt.Sprintf(
			"\033[48;2;%d;%d;%dm",
			ap.R, ap.G, ap.B,
		)
	}
	return fmt.Sprintf(
		"\033[38;2;%d;%d;%dm%s",
		ap.R, ap.G, ap.B,
		lowerHalfBlock,
	)
}

// Height gets total rows of ANSImage.
func (ai *ANSImage) Height() int {
	return ai.h
}

// Width gets total columns of ANSImage.
func (ai *ANSImage) Width() int {
	return ai.w
}

// SetMaxProcs sets the maximum number of parallel goroutines to render the ANSImage
// (user should manually sets `runtime.GOMAXPROCS(max)` before to this change takes effect).
func (ai *ANSImage) SetMaxProcs(max int) {
	ai.maxprocs = max
}

// GetMaxProcs gets the maximum number of parallels goroutines to render the ANSImage.
func (ai *ANSImage) GetMaxProcs() int {
	return ai.maxprocs
}

// SetAt sets ANSI-pixel color (RBG) in coordinates (y,x).
func (ai *ANSImage) SetAt(y, x int, r, g, b uint32) error {
	if y >= 0 && y < ai.h && x >= 0 && x < ai.w {
		ai.pixmap[y][x].R = r
		ai.pixmap[y][x].G = g
		ai.pixmap[y][x].B = b
		ai.pixmap[y][x].upper = (y%2 == 0)
		return nil
	}
	return ErrOutOfBounds
}

// GetAt gets ANSI-pixel in coordinates (y,x).
func (ai *ANSImage) GetAt(y, x int) (*ANSIpixel, error) {
	if y >= 0 && y < ai.h && x >= 0 && x < ai.w {
		return &ANSIpixel{
				R:     ai.pixmap[y][x].R,
				G:     ai.pixmap[y][x].G,
				B:     ai.pixmap[y][x].B,
				upper: ai.pixmap[y][x].upper,
			},
			nil
	}
	return nil, ErrOutOfBounds
}

// Render returns the ANSI-compatible string form of ANSImage.
// (Nice info for ANSI True Colour - https://gist.github.com/XVilka/8346728)
func (ai *ANSImage) Render() string {
	type renderData struct {
		row    int
		render string
	}

	rows := make([]string, ai.h/2)

	for y := 0; y < ai.h; y += ai.maxprocs {
		ch := make(chan renderData, ai.maxprocs)

		for n, r := 1, y+1; (n <= ai.maxprocs) && (2*r+1 < ai.h); n, r = n+1, y+n+1 {
			go func(r, y int) {
				var str string
				for x := 0; x < ai.w; x++ {
					str += ai.pixmap[y][x].Render()   // upper pixel
					str += ai.pixmap[y+1][x].Render() // lower pixel
				}
				str += "\033[0m\n" // reset ansi style
				ch <- renderData{row: r, render: str}
			}(r, 2*r)

			// DEBUG:
			// fmt.Printf("y:%d | n:%d | r:%d | 2*r:%d\n", y, n, r, 2*r)
			// time.Sleep(time.Millisecond * 100)
		}

		for n, r := 1, y+1; (n <= ai.maxprocs) && (2*r+1 < ai.h); n, r = n+1, y+n+1 {
			data := <-ch
			rows[data.row] = data.render

			// DEBUG:
			// fmt.Printf("data.row:%d\n", data.row)
			// time.Sleep(time.Millisecond * 100)
		}
	}

	return strings.Join(rows, "")
}

// RenderOLD returns the ANSI-compatible string form of ANSImage
// func (ai *ANSImage) RenderOLD() string {
// 	var str string
// 	for y := 0; y < ai.h; y += 2 {
// 		for x := 0; x < ai.w; x++ {
// 			str += ai.pixmap[y][x].Render()   // upper pixel
// 			str += ai.pixmap[y+1][x].Render() // lower pixel
// 			//fmt.Printf("%d:%d\n", x, y)
// 		}
// 		str += "\x1b[m\n" // reset ansi style
// 	}
// 	return str
// }

// Draw writes the ANSImage to standard output (terminal).
func (ai *ANSImage) Draw() {
	fmt.Print(ai.Render())
}

// New creates a new empty ANSImage ready to draw on it.
func New(h, w int) (*ANSImage, error) {
	if h%2 != 0 {
		return nil, ErrOddHeight
	}
	if h < 2 || w < 2 {
		return nil, ErrInvalidBounds
	}

	ansimage := &ANSImage{
		h: h, w: w,
		maxprocs: 1,
		pixmap: func() [][]*ANSIpixel {
			aux := make([][]*ANSIpixel, h)
			for y := 0; y < h; y++ {
				aux[y] = make([]*ANSIpixel, w)
				for x := 0; x < w; x++ {
					aux[y][x] = &ANSIpixel{
						R: 0, G: 0, B: 0,
						upper: (y%2 == 0),
					}
				}
			}
			return aux
		}(),
	}

	return ansimage, nil
}

// NewFromReader creates a new ANSImage from an io.Reader.
// Background color is used to fill when image has transparency.
func NewFromReader(bg color.Color, reader io.Reader) (*ANSImage, error) {
	image, _, err := image.Decode(reader)
	if err != nil {
		return nil, err
	}

	return createANSImage(bg, image)
}

// NewScaledFromReader creates a new scaled ANSImage from an io.Reader.
// Background color is used to fill when image has transparency.
func NewScaledFromReader(y, x int, sm scaleMode, bg color.Color, reader io.Reader) (*ANSImage, error) {
	image, _, err := image.Decode(reader)
	if err != nil {
		return nil, err
	}

	switch sm {
	case ScaleModeResize:
		image = imaging.Resize(image, x, y, imaging.Lanczos)
	case ScaleModeFill:
		image = imaging.Fill(image, x, y, imaging.Center, imaging.Lanczos)
	case ScaleModeFit:
		image = imaging.Fit(image, x, y, imaging.Lanczos)
	}

	return createANSImage(bg, image)
}

// NewFromFile creates a new ANSImage from a file.
// Background color is used to fill when image has transparency.
func NewFromFile(bg color.Color, name string) (*ANSImage, error) {
	reader, err := os.Open(name)
	if err != nil {
		return nil, err
	}
	defer reader.Close()
	return NewFromReader(bg, reader)
}

// NewScaledFromFile creates a new scaled ANSImage from a file.
// Background color is used to fill when image has transparency.
func NewScaledFromFile(y, x int, sm scaleMode, bg color.Color, name string) (*ANSImage, error) {
	reader, err := os.Open(name)
	if err != nil {
		return nil, err
	}
	defer reader.Close()
	return NewScaledFromReader(y, x, sm, bg, reader)
}

// ClearTerminal clears current terminal buffer using ANSI escape code.
// (Nice info for ANSI escape codes - http://unix.stackexchange.com/questions/124762/how-does-clear-command-work)
func ClearTerminal() {
	fmt.Print("\033[H\033[2J")
}

// createANSImage loads data from an image and returns an ANSImage.
// Background color is used to fill when image has transparency.
func createANSImage(bg color.Color, img image.Image) (*ANSImage, error) {
	bounds := img.Bounds()

	// do compositing only if background color has no transparency (thank you disq for the idea!)
	// (info - http://stackoverflow.com/questions/36595687/transparent-pixel-color-go-lang-image)
	if _, _, _, a := bg.RGBA(); a >= 0xffff {
		canvas := image.NewRGBA(bounds)
		draw.Draw(canvas, bounds, image.NewUniform(bg), image.ZP, draw.Src)
		draw.Draw(canvas, bounds, img, image.ZP, draw.Over)
		img = canvas // swap original image with the new one
	}

	yMin, xMin := bounds.Min.Y, bounds.Min.X
	yMax, xMax := bounds.Max.Y, bounds.Max.X

	if yMax%2 != 0 {
		yMax-- // always even value!
	}

	ansimage, err := New(yMax, xMax)
	if err != nil {
		return nil, err
	}

	for y := yMin; y < yMax; y++ {
		for x := xMin; x < xMax; x++ {
			r, g, b, _ := img.At(x, y).RGBA()
			if err := ansimage.SetAt(y, x, r, g, b); err != nil {
				return nil, err
			}
		}
	}

	return ansimage, nil
}
<commit_msg>fix output, each color should 8bit in escape sequence<commit_after>//       ___  _____  ____
//      / _ \/  _/ |/_/ /____ ______ _
//     / ___// /_>  </ __/ -_) __/  ' \
//    /_/  /___/_/|_|\__/\__/_/ /_/_/_/
//
//    Copyright 2017 Eliuk Blau
//
//    This Source Code Form is subject to the terms of the Mozilla Public
//    License, v. 2.0. If a copy of the MPL was not distributed with this
//    file, You can obtain one at http://mozilla.org/MPL/2.0/.

package ansimage

import (
	"errors"
	"fmt"
	"image"
	"image/color"
	"image/draw"
	_ "image/gif"  // initialize decoder
	_ "image/jpeg" // initialize decoder
	_ "image/png"  // initialize decoder
	"io"
	"os"
	"strings"

	"github.com/disintegration/imaging"
	_ "golang.org/x/image/bmp"  // initialize decoder
	_ "golang.org/x/image/tiff" // initialize decoder
	_ "golang.org/x/image/webp" // initialize decoder
)

// Unicode Block Element character used to represent lower pixel in terminal row.
// INFO: http://en.wikipedia.org/wiki/Block_Elements
const lowerHalfBlock = "\u2584"

// ANSImage scale modes:
// resize (full scaled to area),
// fill (resize and crop the image with a center anchor point to fill area),
// fit (resize the image to fit area, preserving the aspect ratio).
const (
	ScaleModeResize = scaleMode(iota)
	ScaleModeFill
	ScaleModeFit
)

var (
	// ErrOddHeight happens when ANSImage height is not even value.
	ErrOddHeight = errors.New("ANSImage: height must be even value")

	// ErrInvalidBounds happens when ANSImage height or width are invalid values.
	ErrInvalidBounds = errors.New("ANSImage: height or width must be >=2")

	// ErrOutOfBounds happens when ANSI-pixel coordinates are out of ANSImage bounds.
	ErrOutOfBounds = errors.New("ANSImage: out of bounds")
)

// scaleMode type is used for image scale mode constants.
type scaleMode uint8

// ANSIpixel represents a pixel of an ANSImage.
type ANSIpixel struct {
	R, G, B uint8
	upper   bool
}

// ANSImage represents an image encoded in ANSI escape codes.
type ANSImage struct {
	h, w     int
	maxprocs int
	pixmap   [][]*ANSIpixel
}

// Render returns the ANSI-compatible string form of ANSI-pixel.
func (ap *ANSIpixel) Render() string {
	if ap.upper {
		return fmt.Sprintf(
			"\033[48;2;%d;%d;%dm",
			ap.R, ap.G, ap.B,
		)
	}
	return fmt.Sprintf(
		"\033[38;2;%d;%d;%dm%s",
		ap.R, ap.G, ap.B,
		lowerHalfBlock,
	)
}

// Height gets total rows of ANSImage.
func (ai *ANSImage) Height() int {
	return ai.h
}

// Width gets total columns of ANSImage.
func (ai *ANSImage) Width() int {
	return ai.w
}

// SetMaxProcs sets the maximum number of parallel goroutines to render the ANSImage
// (user should manually sets `runtime.GOMAXPROCS(max)` before to this change takes effect).
func (ai *ANSImage) SetMaxProcs(max int) {
	ai.maxprocs = max
}

// GetMaxProcs gets the maximum number of parallels goroutines to render the ANSImage.
func (ai *ANSImage) GetMaxProcs() int {
	return ai.maxprocs
}

// SetAt sets ANSI-pixel color (RBG) in coordinates (y,x).
func (ai *ANSImage) SetAt(y, x int, r, g, b uint8) error {
	if y >= 0 && y < ai.h && x >= 0 && x < ai.w {
		ai.pixmap[y][x].R = r
		ai.pixmap[y][x].G = g
		ai.pixmap[y][x].B = b
		ai.pixmap[y][x].upper = (y%2 == 0)
		return nil
	}
	return ErrOutOfBounds
}

// GetAt gets ANSI-pixel in coordinates (y,x).
func (ai *ANSImage) GetAt(y, x int) (*ANSIpixel, error) {
	if y >= 0 && y < ai.h && x >= 0 && x < ai.w {
		return &ANSIpixel{
				R:     ai.pixmap[y][x].R,
				G:     ai.pixmap[y][x].G,
				B:     ai.pixmap[y][x].B,
				upper: ai.pixmap[y][x].upper,
			},
			nil
	}
	return nil, ErrOutOfBounds
}

// Render returns the ANSI-compatible string form of ANSImage.
// (Nice info for ANSI True Colour - https://gist.github.com/XVilka/8346728)
func (ai *ANSImage) Render() string {
	type renderData struct {
		row    int
		render string
	}

	rows := make([]string, ai.h/2)

	for y := 0; y < ai.h; y += ai.maxprocs {
		ch := make(chan renderData, ai.maxprocs)

		for n, r := 1, y+1; (n <= ai.maxprocs) && (2*r+1 < ai.h); n, r = n+1, y+n+1 {
			go func(r, y int) {
				var str string
				for x := 0; x < ai.w; x++ {
					str += ai.pixmap[y][x].Render()   // upper pixel
					str += ai.pixmap[y+1][x].Render() // lower pixel
				}
				str += "\033[0m\n" // reset ansi style
				ch <- renderData{row: r, render: str}
			}(r, 2*r)

			// DEBUG:
			// fmt.Printf("y:%d | n:%d | r:%d | 2*r:%d\n", y, n, r, 2*r)
			// time.Sleep(time.Millisecond * 100)
		}

		for n, r := 1, y+1; (n <= ai.maxprocs) && (2*r+1 < ai.h); n, r = n+1, y+n+1 {
			data := <-ch
			rows[data.row] = data.render

			// DEBUG:
			// fmt.Printf("data.row:%d\n", data.row)
			// time.Sleep(time.Millisecond * 100)
		}
	}

	return strings.Join(rows, "")
}

// RenderOLD returns the ANSI-compatible string form of ANSImage
// func (ai *ANSImage) RenderOLD() string {
// 	var str string
// 	for y := 0; y < ai.h; y += 2 {
// 		for x := 0; x < ai.w; x++ {
// 			str += ai.pixmap[y][x].Render()   // upper pixel
// 			str += ai.pixmap[y+1][x].Render() // lower pixel
// 			//fmt.Printf("%d:%d\n", x, y)
// 		}
// 		str += "\x1b[m\n" // reset ansi style
// 	}
// 	return str
// }

// Draw writes the ANSImage to standard output (terminal).
func (ai *ANSImage) Draw() {
	fmt.Print(ai.Render())
}

// New creates a new empty ANSImage ready to draw on it.
func New(h, w int) (*ANSImage, error) {
	if h%2 != 0 {
		return nil, ErrOddHeight
	}
	if h < 2 || w < 2 {
		return nil, ErrInvalidBounds
	}

	ansimage := &ANSImage{
		h: h, w: w,
		maxprocs: 1,
		pixmap: func() [][]*ANSIpixel {
			aux := make([][]*ANSIpixel, h)
			for y := 0; y < h; y++ {
				aux[y] = make([]*ANSIpixel, w)
				for x := 0; x < w; x++ {
					aux[y][x] = &ANSIpixel{
						R: 0, G: 0, B: 0,
						upper: (y%2 == 0),
					}
				}
			}
			return aux
		}(),
	}

	return ansimage, nil
}

// NewFromReader creates a new ANSImage from an io.Reader.
// Background color is used to fill when image has transparency.
func NewFromReader(bg color.Color, reader io.Reader) (*ANSImage, error) {
	image, _, err := image.Decode(reader)
	if err != nil {
		return nil, err
	}

	return createANSImage(bg, image)
}

// NewScaledFromReader creates a new scaled ANSImage from an io.Reader.
// Background color is used to fill when image has transparency.
func NewScaledFromReader(y, x int, sm scaleMode, bg color.Color, reader io.Reader) (*ANSImage, error) {
	image, _, err := image.Decode(reader)
	if err != nil {
		return nil, err
	}

	switch sm {
	case ScaleModeResize:
		image = imaging.Resize(image, x, y, imaging.Lanczos)
	case ScaleModeFill:
		image = imaging.Fill(image, x, y, imaging.Center, imaging.Lanczos)
	case ScaleModeFit:
		image = imaging.Fit(image, x, y, imaging.Lanczos)
	}

	return createANSImage(bg, image)
}

// NewFromFile creates a new ANSImage from a file.
// Background color is used to fill when image has transparency.
func NewFromFile(bg color.Color, name string) (*ANSImage, error) {
	reader, err := os.Open(name)
	if err != nil {
		return nil, err
	}
	defer reader.Close()
	return NewFromReader(bg, reader)
}

// NewScaledFromFile creates a new scaled ANSImage from a file.
// Background color is used to fill when image has transparency.
func NewScaledFromFile(y, x int, sm scaleMode, bg color.Color, name string) (*ANSImage, error) {
	reader, err := os.Open(name)
	if err != nil {
		return nil, err
	}
	defer reader.Close()
	return NewScaledFromReader(y, x, sm, bg, reader)
}

// ClearTerminal clears current terminal buffer using ANSI escape code.
// (Nice info for ANSI escape codes - http://unix.stackexchange.com/questions/124762/how-does-clear-command-work)
func ClearTerminal() {
	fmt.Print("\033[H\033[2J")
}

// createANSImage loads data from an image and returns an ANSImage.
// Background color is used to fill when image has transparency.
func createANSImage(bg color.Color, img image.Image) (*ANSImage, error) {
	bounds := img.Bounds()

	var rgbaOut *image.RGBA

	// do compositing only if background color has no transparency (thank you disq for the idea!)
	// (info - http://stackoverflow.com/questions/36595687/transparent-pixel-color-go-lang-image)
	if _, _, _, a := bg.RGBA(); a >= 0xffff {
		rgbaOut = image.NewRGBA(bounds)
		draw.Draw(rgbaOut, bounds, image.NewUniform(bg), image.ZP, draw.Src)
		draw.Draw(rgbaOut, bounds, img, image.ZP, draw.Over)
	} else {
		if v, ok := img.(*image.RGBA); ok {
			rgbaOut = v
		} else {
			rgbaOut = image.NewRGBA(bounds)
			draw.Draw(rgbaOut, bounds, img, image.ZP, draw.Src)
		}
	}

	yMin, xMin := bounds.Min.Y, bounds.Min.X
	yMax, xMax := bounds.Max.Y, bounds.Max.X

	if yMax%2 != 0 {
		yMax-- // always even value!
	}

	ansimage, err := New(yMax, xMax)
	if err != nil {
		return nil, err
	}

	for y := yMin; y < yMax; y++ {
		for x := xMin; x < xMax; x++ {
			v := rgbaOut.RGBAAt(x,y)
			if err := ansimage.SetAt(y, x, v.R, v.G, v.B); err != nil {
				return nil, err
			}
		}
	}

	return ansimage, nil
}
<|endoftext|>


<commit_before>package eventinterp

import "github.com/crawsible/crawsibot/irc"

type Forwarder interface {
	EnrollForMsgs(mrc irc.MsgRcvr, cmd string)
}

type Interp interface {
	BeginInterpreting(fwdr Forwarder)
}

type EventInterp struct {
	LoginInterp Interp
}

func New() *EventInterp {
	return &EventInterp{
		LoginInterp: &LoginInterp{},
	}
}

func (e *EventInterp) BeginInterpreting(fwdr Forwarder) {
	e.LoginInterp.BeginInterpreting(fwdr)
}

func (e *EventInterp) RcvMsg(nick, fprms, prms string) {}
<commit_msg>Remove unnecessary test stub<commit_after>package eventinterp

import "github.com/crawsible/crawsibot/irc"

type Forwarder interface {
	EnrollForMsgs(mrc irc.MsgRcvr, cmd string)
}

type Interp interface {
	BeginInterpreting(fwdr Forwarder)
}

type EventInterp struct {
	LoginInterp Interp
}

func New() *EventInterp {
	return &EventInterp{
		LoginInterp: &LoginInterp{},
	}
}

func (e *EventInterp) BeginInterpreting(fwdr Forwarder) {
	e.LoginInterp.BeginInterpreting(fwdr)
}
<|endoftext|>