<commit_before>package yaml

import (
	"fmt"
	"io/ioutil"
	"strings"

	"github.com/fsnotify/fsnotify"
	"github.com/kelseyhightower/confd/log"
	"gopkg.in/yaml.v2"
)

var replacer = strings.NewReplacer("/", "_")

// Client provides a shell for the yaml client
type Client struct {
	filepath string
}

func NewYamlClient(filepath string) (*Client, error) {
	return &Client{filepath}, nil
}

func (c *Client) GetValues(keys []string) (map[string]string, error) {
	yamlMap := make(map[string]string)
	vars := make(map[string]string)

	data, err := ioutil.ReadFile(c.filepath)
	if err != nil {
		return vars, err
	}
	err = yaml.Unmarshal(data, &yamlMap)
	if err != nil {
		return vars, err
	}

	for _, key := range keys {
		k := transform(key)
		for yamlKey, yamlValue := range yamlMap {
			if strings.HasPrefix(yamlKey, k) {
				vars[clean(yamlKey)] = yamlValue
			}
		}
	}
	log.Debug(fmt.Sprintf("Key Map: %#v", vars))

	return vars, nil
}

func transform(key string) string {
	k := strings.TrimPrefix(key, "/")
	return replacer.Replace(k)
}

var cleanReplacer = strings.NewReplacer("_", "/")

func clean(key string) string {
	newKey := "/" + key
	return cleanReplacer.Replace(newKey)
}

func (c *Client) WatchPrefix(prefix string, keys []string, waitIndex uint64, stopChan chan bool) (uint64, error) {
	if waitIndex == 0 {
		return 1, nil
	}

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

	err = watcher.Add(c.filepath)
	if err != nil {
		return 0, err
	}

	for {
		select {
		case event := <-watcher.Events:
			if event.Op&fsnotify.Write == fsnotify.Write {
				return 1, nil
			}
		case err := <-watcher.Errors:
			return 0, err
		case <-stopChan:
			return 0, nil
		}
	}
	return waitIndex, nil
}
<commit_msg>allow deep structures<commit_after>package yaml

import (
	"fmt"
	"io/ioutil"
	"strings"

	"github.com/fsnotify/fsnotify"
	"github.com/kelseyhightower/confd/log"
	"gopkg.in/yaml.v2"
)

var replacer = strings.NewReplacer("/", "_")

// Client provides a shell for the yaml client
type Client struct {
	filepath string
}

func NewYamlClient(filepath string) (*Client, error) {
	return &Client{filepath}, nil
}

func (c *Client) GetValues(keys []string) (map[string]string, error) {
	yamlMap := make(map[interface{}]interface{})
	vars := make(map[string]string)

	data, err := ioutil.ReadFile(c.filepath)
	if err != nil {
		return vars, err
	}
	err = yaml.Unmarshal(data, &yamlMap)
	if err != nil {
		return vars, err
	}

	nodeWalk(yamlMap, "", vars)

	log.Debug(fmt.Sprintf("YAML Map: %#v", yamlMap))

	log.Debug(fmt.Sprintf("Key Map: %#v", vars))

	return vars, nil
}

// nodeWalk recursively descends nodes, updating vars.
func nodeWalk(node map[interface{}]interface{}, key string, vars map[string]string) error {
	for k, v := range node {
		key := key + "/" + k.(string)

		switch v.(type) {
		case map[interface{}]interface{}:
			nodeWalk(v.(map[interface{}]interface{}), key, vars)
		case []interface{}:
			for _, j := range v.([]interface{}) {
				switch j.(type) {
				case map[interface{}]interface{}:
					nodeWalk(j.(map[interface{}]interface{}), key, vars)
				case string:
					vars[key+"/"+j.(string)] = ""
				}
			}
		case string:
			vars[key] = v.(string)
		}
	}
	return nil
}

func (c *Client) WatchPrefix(prefix string, keys []string, waitIndex uint64, stopChan chan bool) (uint64, error) {
	if waitIndex == 0 {
		return 1, nil
	}

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

	err = watcher.Add(c.filepath)
	if err != nil {
		return 0, err
	}

	for {
		select {
		case event := <-watcher.Events:
			if event.Op&fsnotify.Write == fsnotify.Write {
				return 1, nil
			}
		case err := <-watcher.Errors:
			return 0, err
		case <-stopChan:
			return 0, nil
		}
	}
	return waitIndex, nil
}
<|endoftext|>


<commit_before>package aws

import (
	"fmt"
	"log"
	"net"
	"strings"
	"time"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/awserr"
	"github.com/aws/aws-sdk-go/service/ec2"
	"github.com/hashicorp/terraform/helper/resource"
	"github.com/hashicorp/terraform/helper/schema"
)

func resourceAwsEip() *schema.Resource {
	return &schema.Resource{
		Create: resourceAwsEipCreate,
		Read:   resourceAwsEipRead,
		Update: resourceAwsEipUpdate,
		Delete: resourceAwsEipDelete,
		Importer: &schema.ResourceImporter{
			State: schema.ImportStatePassthrough,
		},

		Schema: map[string]*schema.Schema{
			"vpc": &schema.Schema{
				Type:     schema.TypeBool,
				Optional: true,
				ForceNew: true,
				Computed: true,
			},

			"instance": &schema.Schema{
				Type:     schema.TypeString,
				Optional: true,
				Computed: true,
			},

			"network_interface": &schema.Schema{
				Type:     schema.TypeString,
				Optional: true,
				Computed: true,
			},

			"allocation_id": &schema.Schema{
				Type:     schema.TypeString,
				Computed: true,
			},

			"association_id": &schema.Schema{
				Type:     schema.TypeString,
				Computed: true,
			},

			"domain": &schema.Schema{
				Type:     schema.TypeString,
				Computed: true,
			},

			"public_ip": &schema.Schema{
				Type:     schema.TypeString,
				Computed: true,
			},

			"private_ip": &schema.Schema{
				Type:     schema.TypeString,
				Computed: true,
			},

			"associate_with_private_ip": &schema.Schema{
				Type:     schema.TypeString,
				Optional: true,
			},
		},
	}
}

func resourceAwsEipCreate(d *schema.ResourceData, meta interface{}) error {
	ec2conn := meta.(*AWSClient).ec2conn

	// By default, we're not in a VPC
	domainOpt := ""
	if v := d.Get("vpc"); v != nil && v.(bool) {
		domainOpt = "vpc"
	}

	allocOpts := &ec2.AllocateAddressInput{
		Domain: aws.String(domainOpt),
	}

	log.Printf("[DEBUG] EIP create configuration: %#v", allocOpts)
	allocResp, err := ec2conn.AllocateAddress(allocOpts)
	if err != nil {
		return fmt.Errorf("Error creating EIP: %s", err)
	}

	// The domain tells us if we're in a VPC or not
	d.Set("domain", allocResp.Domain)

	// Assign the eips (unique) allocation id for use later
	// the EIP api has a conditional unique ID (really), so
	// if we're in a VPC we need to save the ID as such, otherwise
	// it defaults to using the public IP
	log.Printf("[DEBUG] EIP Allocate: %#v", allocResp)
	if d.Get("domain").(string) == "vpc" {
		d.SetId(*allocResp.AllocationId)
	} else {
		d.SetId(*allocResp.PublicIp)
	}

	log.Printf("[INFO] EIP ID: %s (domain: %v)", d.Id(), *allocResp.Domain)
	return resourceAwsEipUpdate(d, meta)
}

func resourceAwsEipRead(d *schema.ResourceData, meta interface{}) error {
	ec2conn := meta.(*AWSClient).ec2conn

	domain := resourceAwsEipDomain(d)
	id := d.Id()

	req := &ec2.DescribeAddressesInput{}

	if domain == "vpc" {
		req.AllocationIds = []*string{aws.String(id)}
	} else {
		req.PublicIps = []*string{aws.String(id)}
	}

	log.Printf(
		"[DEBUG] EIP describe configuration: %s (domain: %s)",
		req, domain)

	describeAddresses, err := ec2conn.DescribeAddresses(req)
	if err != nil {
		if ec2err, ok := err.(awserr.Error); ok && (ec2err.Code() == "InvalidAllocationID.NotFound" || ec2err.Code() == "InvalidAddress.NotFound") {
			d.SetId("")
			return nil
		}

		return fmt.Errorf("Error retrieving EIP: %s", err)
	}

	// Verify AWS returned our EIP
	if len(describeAddresses.Addresses) != 1 ||
		domain == "vpc" && *describeAddresses.Addresses[0].AllocationId != id ||
		*describeAddresses.Addresses[0].PublicIp != id {
		if err != nil {
			return fmt.Errorf("Unable to find EIP: %#v", describeAddresses.Addresses)
		}
	}

	address := describeAddresses.Addresses[0]

	d.Set("association_id", address.AssociationId)
	if address.InstanceId != nil {
		d.Set("instance", address.InstanceId)
	} else {
		d.Set("instance", "")
	}
	if address.NetworkInterfaceId != nil {
		d.Set("network_interface", address.NetworkInterfaceId)
	} else {
		d.Set("network_interface", "")
	}
	d.Set("private_ip", address.PrivateIpAddress)
	d.Set("public_ip", address.PublicIp)

	// On import (domain never set, which it must've been if we created),
	// set the 'vpc' attribute depending on if we're in a VPC.
	if address.Domain != nil {
		d.Set("vpc", *address.Domain == "vpc")
	}

	d.Set("domain", address.Domain)

	// Force ID to be an Allocation ID if we're on a VPC
	// This allows users to import the EIP based on the IP if they are in a VPC
	if *address.Domain == "vpc" && net.ParseIP(id) != nil {
		log.Printf("[DEBUG] Re-assigning EIP ID (%s) to it's Allocation ID (%s)", d.Id(), *address.AllocationId)
		d.SetId(*address.AllocationId)
	}

	return nil
}

func resourceAwsEipUpdate(d *schema.ResourceData, meta interface{}) error {
	ec2conn := meta.(*AWSClient).ec2conn

	domain := resourceAwsEipDomain(d)

	// Associate to instance or interface if specified
	v_instance, ok_instance := d.GetOk("instance")
	v_interface, ok_interface := d.GetOk("network_interface")

	if ok_instance || ok_interface {
		instanceId := v_instance.(string)
		networkInterfaceId := v_interface.(string)

		assocOpts := &ec2.AssociateAddressInput{
			InstanceId: aws.String(instanceId),
			PublicIp:   aws.String(d.Id()),
		}

		// more unique ID conditionals
		if domain == "vpc" {
			var privateIpAddress *string
			if v := d.Get("associate_with_private_ip").(string); v != "" {
				privateIpAddress = aws.String(v)
			}
			assocOpts = &ec2.AssociateAddressInput{
				NetworkInterfaceId: aws.String(networkInterfaceId),
				InstanceId:         aws.String(instanceId),
				AllocationId:       aws.String(d.Id()),
				PrivateIpAddress:   privateIpAddress,
			}
		}

		log.Printf("[DEBUG] EIP associate configuration: %s (domain: %s)", assocOpts, domain)

		err := resource.Retry(1*time.Minute, func() *resource.RetryError {
			_, err := ec2conn.AssociateAddress(assocOpts)
			if err != nil {
				if awsErr, ok := err.(awserr.Error); ok {
					if awsErr.Code() == "InvalidAllocationID.NotFound" {
						return resource.RetryableError(awsErr)
					}
				}
				return resource.NonRetryableError(err)
			}
			return nil
		})
		if err != nil {
			// Prevent saving instance if association failed
			// e.g. missing internet gateway in VPC
			d.Set("instance", "")
			d.Set("network_interface", "")
			return fmt.Errorf("Failure associating EIP: %s", err)
		}
	}

	return resourceAwsEipRead(d, meta)
}

func resourceAwsEipDelete(d *schema.ResourceData, meta interface{}) error {
	ec2conn := meta.(*AWSClient).ec2conn

	if err := resourceAwsEipRead(d, meta); err != nil {
		return err
	}
	if d.Id() == "" {
		// This might happen from the read
		return nil
	}

	// If we are attached to an instance or interface, detach first.
	if d.Get("instance").(string) != "" || d.Get("association_id").(string) != "" {
		log.Printf("[DEBUG] Disassociating EIP: %s", d.Id())
		var err error
		switch resourceAwsEipDomain(d) {
		case "vpc":
			_, err = ec2conn.DisassociateAddress(&ec2.DisassociateAddressInput{
				AssociationId: aws.String(d.Get("association_id").(string)),
			})
		case "standard":
			_, err = ec2conn.DisassociateAddress(&ec2.DisassociateAddressInput{
				PublicIp: aws.String(d.Get("public_ip").(string)),
			})
		}

		if err != nil {
			// First check if the association ID is not found. If this
			// is the case, then it was already disassociated somehow,
			// and that is okay. The most commmon reason for this is that
			// the instance or ENI it was attached it was destroyed.
			if ec2err, ok := err.(awserr.Error); ok && ec2err.Code() == "InvalidAssociationID.NotFound" {
				err = nil
			}
		}

		if err != nil {
			return err
		}
	}

	domain := resourceAwsEipDomain(d)
	return resource.Retry(3*time.Minute, func() *resource.RetryError {
		var err error
		switch domain {
		case "vpc":
			log.Printf(
				"[DEBUG] EIP release (destroy) address allocation: %v",
				d.Id())
			_, err = ec2conn.ReleaseAddress(&ec2.ReleaseAddressInput{
				AllocationId: aws.String(d.Id()),
			})
		case "standard":
			log.Printf("[DEBUG] EIP release (destroy) address: %v", d.Id())
			_, err = ec2conn.ReleaseAddress(&ec2.ReleaseAddressInput{
				PublicIp: aws.String(d.Id()),
			})
		}

		if err == nil {
			return nil
		}
		if _, ok := err.(awserr.Error); !ok {
			return resource.NonRetryableError(err)
		}

		return resource.RetryableError(err)
	})
}

func resourceAwsEipDomain(d *schema.ResourceData) string {
	if v, ok := d.GetOk("domain"); ok {
		return v.(string)
	} else if strings.Contains(d.Id(), "eipalloc") {
		// We have to do this for backwards compatibility since TF 0.1
		// didn't have the "domain" computed attribute.
		return "vpc"
	}

	return "standard"
}
<commit_msg>provider/aws: Increase EIP update timeout (#14381)<commit_after>package aws

import (
	"fmt"
	"log"
	"net"
	"strings"
	"time"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/awserr"
	"github.com/aws/aws-sdk-go/service/ec2"
	"github.com/hashicorp/terraform/helper/resource"
	"github.com/hashicorp/terraform/helper/schema"
)

func resourceAwsEip() *schema.Resource {
	return &schema.Resource{
		Create: resourceAwsEipCreate,
		Read:   resourceAwsEipRead,
		Update: resourceAwsEipUpdate,
		Delete: resourceAwsEipDelete,
		Importer: &schema.ResourceImporter{
			State: schema.ImportStatePassthrough,
		},

		Schema: map[string]*schema.Schema{
			"vpc": &schema.Schema{
				Type:     schema.TypeBool,
				Optional: true,
				ForceNew: true,
				Computed: true,
			},

			"instance": &schema.Schema{
				Type:     schema.TypeString,
				Optional: true,
				Computed: true,
			},

			"network_interface": &schema.Schema{
				Type:     schema.TypeString,
				Optional: true,
				Computed: true,
			},

			"allocation_id": &schema.Schema{
				Type:     schema.TypeString,
				Computed: true,
			},

			"association_id": &schema.Schema{
				Type:     schema.TypeString,
				Computed: true,
			},

			"domain": &schema.Schema{
				Type:     schema.TypeString,
				Computed: true,
			},

			"public_ip": &schema.Schema{
				Type:     schema.TypeString,
				Computed: true,
			},

			"private_ip": &schema.Schema{
				Type:     schema.TypeString,
				Computed: true,
			},

			"associate_with_private_ip": &schema.Schema{
				Type:     schema.TypeString,
				Optional: true,
			},
		},
	}
}

func resourceAwsEipCreate(d *schema.ResourceData, meta interface{}) error {
	ec2conn := meta.(*AWSClient).ec2conn

	// By default, we're not in a VPC
	domainOpt := ""
	if v := d.Get("vpc"); v != nil && v.(bool) {
		domainOpt = "vpc"
	}

	allocOpts := &ec2.AllocateAddressInput{
		Domain: aws.String(domainOpt),
	}

	log.Printf("[DEBUG] EIP create configuration: %#v", allocOpts)
	allocResp, err := ec2conn.AllocateAddress(allocOpts)
	if err != nil {
		return fmt.Errorf("Error creating EIP: %s", err)
	}

	// The domain tells us if we're in a VPC or not
	d.Set("domain", allocResp.Domain)

	// Assign the eips (unique) allocation id for use later
	// the EIP api has a conditional unique ID (really), so
	// if we're in a VPC we need to save the ID as such, otherwise
	// it defaults to using the public IP
	log.Printf("[DEBUG] EIP Allocate: %#v", allocResp)
	if d.Get("domain").(string) == "vpc" {
		d.SetId(*allocResp.AllocationId)
	} else {
		d.SetId(*allocResp.PublicIp)
	}

	log.Printf("[INFO] EIP ID: %s (domain: %v)", d.Id(), *allocResp.Domain)
	return resourceAwsEipUpdate(d, meta)
}

func resourceAwsEipRead(d *schema.ResourceData, meta interface{}) error {
	ec2conn := meta.(*AWSClient).ec2conn

	domain := resourceAwsEipDomain(d)
	id := d.Id()

	req := &ec2.DescribeAddressesInput{}

	if domain == "vpc" {
		req.AllocationIds = []*string{aws.String(id)}
	} else {
		req.PublicIps = []*string{aws.String(id)}
	}

	log.Printf(
		"[DEBUG] EIP describe configuration: %s (domain: %s)",
		req, domain)

	describeAddresses, err := ec2conn.DescribeAddresses(req)
	if err != nil {
		if ec2err, ok := err.(awserr.Error); ok && (ec2err.Code() == "InvalidAllocationID.NotFound" || ec2err.Code() == "InvalidAddress.NotFound") {
			d.SetId("")
			return nil
		}

		return fmt.Errorf("Error retrieving EIP: %s", err)
	}

	// Verify AWS returned our EIP
	if len(describeAddresses.Addresses) != 1 ||
		domain == "vpc" && *describeAddresses.Addresses[0].AllocationId != id ||
		*describeAddresses.Addresses[0].PublicIp != id {
		if err != nil {
			return fmt.Errorf("Unable to find EIP: %#v", describeAddresses.Addresses)
		}
	}

	address := describeAddresses.Addresses[0]

	d.Set("association_id", address.AssociationId)
	if address.InstanceId != nil {
		d.Set("instance", address.InstanceId)
	} else {
		d.Set("instance", "")
	}
	if address.NetworkInterfaceId != nil {
		d.Set("network_interface", address.NetworkInterfaceId)
	} else {
		d.Set("network_interface", "")
	}
	d.Set("private_ip", address.PrivateIpAddress)
	d.Set("public_ip", address.PublicIp)

	// On import (domain never set, which it must've been if we created),
	// set the 'vpc' attribute depending on if we're in a VPC.
	if address.Domain != nil {
		d.Set("vpc", *address.Domain == "vpc")
	}

	d.Set("domain", address.Domain)

	// Force ID to be an Allocation ID if we're on a VPC
	// This allows users to import the EIP based on the IP if they are in a VPC
	if *address.Domain == "vpc" && net.ParseIP(id) != nil {
		log.Printf("[DEBUG] Re-assigning EIP ID (%s) to it's Allocation ID (%s)", d.Id(), *address.AllocationId)
		d.SetId(*address.AllocationId)
	}

	return nil
}

func resourceAwsEipUpdate(d *schema.ResourceData, meta interface{}) error {
	ec2conn := meta.(*AWSClient).ec2conn

	domain := resourceAwsEipDomain(d)

	// Associate to instance or interface if specified
	v_instance, ok_instance := d.GetOk("instance")
	v_interface, ok_interface := d.GetOk("network_interface")

	if ok_instance || ok_interface {
		instanceId := v_instance.(string)
		networkInterfaceId := v_interface.(string)

		assocOpts := &ec2.AssociateAddressInput{
			InstanceId: aws.String(instanceId),
			PublicIp:   aws.String(d.Id()),
		}

		// more unique ID conditionals
		if domain == "vpc" {
			var privateIpAddress *string
			if v := d.Get("associate_with_private_ip").(string); v != "" {
				privateIpAddress = aws.String(v)
			}
			assocOpts = &ec2.AssociateAddressInput{
				NetworkInterfaceId: aws.String(networkInterfaceId),
				InstanceId:         aws.String(instanceId),
				AllocationId:       aws.String(d.Id()),
				PrivateIpAddress:   privateIpAddress,
			}
		}

		log.Printf("[DEBUG] EIP associate configuration: %s (domain: %s)", assocOpts, domain)

		err := resource.Retry(5*time.Minute, func() *resource.RetryError {
			_, err := ec2conn.AssociateAddress(assocOpts)
			if err != nil {
				if awsErr, ok := err.(awserr.Error); ok {
					if awsErr.Code() == "InvalidAllocationID.NotFound" {
						return resource.RetryableError(awsErr)
					}
				}
				return resource.NonRetryableError(err)
			}
			return nil
		})
		if err != nil {
			// Prevent saving instance if association failed
			// e.g. missing internet gateway in VPC
			d.Set("instance", "")
			d.Set("network_interface", "")
			return fmt.Errorf("Failure associating EIP: %s", err)
		}
	}

	return resourceAwsEipRead(d, meta)
}

func resourceAwsEipDelete(d *schema.ResourceData, meta interface{}) error {
	ec2conn := meta.(*AWSClient).ec2conn

	if err := resourceAwsEipRead(d, meta); err != nil {
		return err
	}
	if d.Id() == "" {
		// This might happen from the read
		return nil
	}

	// If we are attached to an instance or interface, detach first.
	if d.Get("instance").(string) != "" || d.Get("association_id").(string) != "" {
		log.Printf("[DEBUG] Disassociating EIP: %s", d.Id())
		var err error
		switch resourceAwsEipDomain(d) {
		case "vpc":
			_, err = ec2conn.DisassociateAddress(&ec2.DisassociateAddressInput{
				AssociationId: aws.String(d.Get("association_id").(string)),
			})
		case "standard":
			_, err = ec2conn.DisassociateAddress(&ec2.DisassociateAddressInput{
				PublicIp: aws.String(d.Get("public_ip").(string)),
			})
		}

		if err != nil {
			// First check if the association ID is not found. If this
			// is the case, then it was already disassociated somehow,
			// and that is okay. The most commmon reason for this is that
			// the instance or ENI it was attached it was destroyed.
			if ec2err, ok := err.(awserr.Error); ok && ec2err.Code() == "InvalidAssociationID.NotFound" {
				err = nil
			}
		}

		if err != nil {
			return err
		}
	}

	domain := resourceAwsEipDomain(d)
	return resource.Retry(3*time.Minute, func() *resource.RetryError {
		var err error
		switch domain {
		case "vpc":
			log.Printf(
				"[DEBUG] EIP release (destroy) address allocation: %v",
				d.Id())
			_, err = ec2conn.ReleaseAddress(&ec2.ReleaseAddressInput{
				AllocationId: aws.String(d.Id()),
			})
		case "standard":
			log.Printf("[DEBUG] EIP release (destroy) address: %v", d.Id())
			_, err = ec2conn.ReleaseAddress(&ec2.ReleaseAddressInput{
				PublicIp: aws.String(d.Id()),
			})
		}

		if err == nil {
			return nil
		}
		if _, ok := err.(awserr.Error); !ok {
			return resource.NonRetryableError(err)
		}

		return resource.RetryableError(err)
	})
}

func resourceAwsEipDomain(d *schema.ResourceData) string {
	if v, ok := d.GetOk("domain"); ok {
		return v.(string)
	} else if strings.Contains(d.Id(), "eipalloc") {
		// We have to do this for backwards compatibility since TF 0.1
		// didn't have the "domain" computed attribute.
		return "vpc"
	}

	return "standard"
}
<|endoftext|>


<commit_before>package horenso

import (
	"encoding/json"
	"io/ioutil"
	"os"
	"reflect"
	"regexp"
	"testing"
	"time"
)

func temp() string {
	f, err := ioutil.TempFile("", "")
	if err != nil {
		panic(err)
	}
	defer f.Close()
	return f.Name()
}

func parseReport(fname string) Report {
	byt, err := ioutil.ReadFile(fname)
	if err != nil {
		panic("failed to read " + fname)
	}
	r := Report{}
	json.Unmarshal(byt, &r)
	return r
}

func TestRun(t *testing.T) {
	noticeReport := temp()
	fname := temp()
	fname2 := temp()
	defer func() {
		for _, f := range []string{noticeReport, fname, fname2} {
			os.RemoveAll(f)
		}
	}()
	_, ho, cmdArgs, err := parseArgs([]string{
		"--noticer",
		"go run testdata/reporter.go " + noticeReport,
		"-n", "invalid",
		"--reporter",
		"go run testdata/reporter.go " + fname,
		"-r",
		"go run testdata/reporter.go " + fname2,
		"--",
		"go", "run", "testdata/run.go",
	})
	if err != nil {
		t.Errorf("err should be nil but: %s", err)
	}
	ho.errStream = ioutil.Discard
	ho.outStream = ioutil.Discard

	r, err := ho.run(cmdArgs)
	if err != nil {
		t.Errorf("err should be nil but: %s", err)
	}

	if r.ExitCode != 0 {
		t.Errorf("exit code should be 0 but: %d", r.ExitCode)
	}

	expect := "1\n2\n3\n"
	if r.Output != expect {
		t.Errorf("output should be %s but: %s", expect, r.Output)
	}
	if r.Stdout != expect {
		t.Errorf("output should be %s but: %s", expect, r.Stdout)
	}
	if r.Stderr != "" {
		t.Errorf("output should be empty but: %s", r.Stderr)
	}
	if r.StartAt == nil {
		t.Errorf("StartAt shouldn't be nil")
	}
	if r.EndAt == nil {
		t.Errorf("EtartAt shouldn't be nil")
	}
	expectedHostname, _ := os.Hostname()
	if r.Hostname != expectedHostname {
		t.Errorf("Hostname should be %s but: %s", expectedHostname, r.Hostname)
	}

	rr := parseReport(fname)
	if !deepEqual(r, rr) {
		t.Errorf("something went wrong. expect: %#v, got: %#v", r, rr)
	}
	rr2 := parseReport(fname2)
	if !deepEqual(r, rr2) {
		t.Errorf("something went wrong. expect: %#v, got: %#v", r, rr2)
	}

	nr := parseReport(noticeReport)
	if nr.Pid != r.Pid {
		t.Errorf("something went wrong")
	}
	if nr.Output != "" {
		t.Errorf("something went wrong")
	}
	if nr.StartAt == nil {
		t.Errorf("StartAt shouldn't be nil")
	}
	if nr.EndAt != nil {
		t.Errorf("EndAt should be nil")
	}
	if nr.ExitCode != -1 {
		t.Errorf("ExitCode should be -1")
	}
	if nr.Hostname != r.Hostname {
		t.Errorf("something went wrong")
	}
}

func TestRun_log(t *testing.T) {
	fname := temp()
	logfile := temp()
	defer func() {
		for _, f := range []string{fname, logfile} {
			os.RemoveAll(f)
		}
	}()
	_, ho, cmdArgs, err := parseArgs([]string{
		"--reporter",
		"go run testdata/reporter.go " + fname,
		"--log", logfile,
		"-vv",
		"-T",
		"--",
		"go", "run", "testdata/run.go",
	})
	if err != nil {
		t.Errorf("err should be nil but: %s", err)
	}
	ho.errStream = ioutil.Discard
	ho.outStream = ioutil.Discard

	r, err := ho.run(cmdArgs)
	if err != nil {
		t.Errorf("err should be nil but: %s", err)
	}
	if r.ExitCode != 0 {
		t.Errorf("exit code should be 0 but: %d", r.ExitCode)
	}

	expect := "1\n2\n3\n"
	if r.Stdout != expect {
		t.Errorf("output should be %s but: %s", expect, r.Stdout)
	}
	if r.Stderr != "" {
		t.Errorf("output should be empty but: %s", r.Stderr)
	}
	if r.StartAt == nil {
		t.Errorf("StartAt shouldn't be nil")
	}
	if r.EndAt == nil {
		t.Errorf("EtartAt shouldn't be nil")
	}
	expectedHostname, _ := os.Hostname()
	if r.Hostname != expectedHostname {
		t.Errorf("Hostname should be %s but: %s", expectedHostname, r.Hostname)
	}

	rr := parseReport(fname)
	if !deepEqual(r, rr) {
		t.Errorf("something went wrong. expect: %#v, got: %#v", r, rr)
	}

	// 2019-02-11T16:39:18.507451+09:00 1
	// 2019-02-11T16:39:18.507666+09:00 2
	// 2019-02-11T16:39:18.507668+09:00 3
	logdata, _ := ioutil.ReadFile(logfile)
	timeRegStr := `[0-9]{4}-[0-9]{2}-[0-9]{2}T` +
		`[0-9]{2}:[0-9]{2}:[0-9]{2}\.[0-9]{6}` +
		`(?:Z|[-+][0-9]{2}:[0-9]{2}) `
	reg := regexp.MustCompile(`\A` +
		timeRegStr + "1\n" +
		timeRegStr + "2\n" +
		timeRegStr + "3\n" + `\z`)
	if !reg.Match(logdata) {
		t.Errorf("something went wrong.\n%s", string(logdata))
	}
}

func TestRun_notfound(t *testing.T) {
	fname := temp()
	defer os.RemoveAll(fname)
	_, ho, cmdArgs, err := parseArgs([]string{
		"--reporter",
		"go run testdata/reporter.go " + fname,
		"--",
		"testdata/notfound",
	})
	if err != nil {
		t.Errorf("err should be nil but: %s", err)
	}
	ho.errStream = ioutil.Discard
	ho.outStream = ioutil.Discard

	r, err := ho.run(cmdArgs)
	if err == nil {
		t.Errorf("err shouldn't be nil")
	}

	if r.ExitCode != -1 {
		t.Errorf("exit code should be -1 but: %d", r.ExitCode)
	}

	if r.StartAt == nil {
		t.Errorf("StartAt shouldn't be nil")
	}
	if r.EndAt != nil {
		t.Errorf("EtartAt should be nil")
	}
	expectedHostname, _ := os.Hostname()
	if r.Hostname != expectedHostname {
		t.Errorf("Hostname should be %s but: %s", expectedHostname, r.Hostname)
	}

	rr := parseReport(fname)
	if !deepEqual(r, rr) {
		t.Errorf("something went wrong. expect: %#v, got: %#v", r, rr)
	}
}

func TestRunHugeOutput(t *testing.T) {
	fname := temp()
	defer os.RemoveAll(fname)
	_, ho, cmdArgs, err := parseArgs([]string{
		"--reporter",
		"go run testdata/reporter.go " + fname,
		"--",
		"go", "run", "testdata/run_hugeoutput.go",
	})
	if err != nil {
		t.Errorf("err should be nil but: %s", err)
	}
	ho.errStream = ioutil.Discard
	ho.outStream = ioutil.Discard

	r, err := ho.run(cmdArgs)
	if err != nil {
		t.Errorf("err should be nil but: %s", err)
	}

	if r.ExitCode != 0 {
		t.Errorf("exit code should be 0 but: %d", r.ExitCode)
	}

	expect := 64*1024 + 1
	if len(r.Output) != expect {
		t.Errorf("output should be %d bytes but: %d bytes", expect, len(r.Output))
	}
	if len(r.Stdout) != expect {
		t.Errorf("output should be %d bytes but: %d bytes", expect, len(r.Stdout))
	}
	if r.Stderr != "" {
		t.Errorf("output should be empty but: %s", r.Stderr)
	}
	if r.StartAt == nil {
		t.Errorf("StartAt shouldn't be nil")
	}
	if r.EndAt == nil {
		t.Errorf("EtartAt shouldn't be nil")
	}
	expectedHostname, _ := os.Hostname()
	if r.Hostname != expectedHostname {
		t.Errorf("Hostname should be %s but: %s", expectedHostname, r.Hostname)
	}

	rr := parseReport(fname)
	if !deepEqual(r, rr) {
		t.Errorf("something went wrong. expect: %#v, got: %#v", r, rr)
	}
}

func equalTimePtr(t1, t2 *time.Time) bool {
	if t1 == nil && t2 == nil {
		return true
	}
	if t1 == nil || t2 == nil {
		return false
	}
	return (*t1).Equal(*t2)
}

func deepEqual(r1, r2 Report) bool {
	return r1.Command == r2.Command &&
		reflect.DeepEqual(r1.CommandArgs, r2.CommandArgs) &&
		r1.Tag == r2.Tag &&
		r1.Output == r2.Output &&
		r1.Stdout == r2.Stdout &&
		r1.Stderr == r2.Stderr &&
		r1.ExitCode == r2.ExitCode &&
		r1.Result == r2.Result &&
		r1.Pid == r2.Pid &&
		r1.Hostname == r2.Hostname &&
		r1.Signaled == r2.Signaled &&
		equalTimePtr(r1.StartAt, r2.StartAt) &&
		equalTimePtr(r1.EndAt, r2.EndAt)
}
<commit_msg>add rough test for horenso_config<commit_after>package horenso

import (
	"encoding/json"
	"io/ioutil"
	"os"
	"reflect"
	"regexp"
	"testing"
	"time"
)

func temp() string {
	f, err := ioutil.TempFile("", "")
	if err != nil {
		panic(err)
	}
	defer f.Close()
	return f.Name()
}

func parseReport(fname string) Report {
	byt, err := ioutil.ReadFile(fname)
	if err != nil {
		panic("failed to read " + fname)
	}
	r := Report{}
	json.Unmarshal(byt, &r)
	return r
}

func TestRun(t *testing.T) {
	noticeReport := temp()
	fname := temp()
	fname2 := temp()
	defer func() {
		for _, f := range []string{noticeReport, fname, fname2} {
			os.RemoveAll(f)
		}
	}()
	_, ho, cmdArgs, err := parseArgs([]string{
		"--noticer",
		"go run testdata/reporter.go " + noticeReport,
		"-n", "invalid",
		"--reporter",
		"go run testdata/reporter.go " + fname,
		"-r",
		"go run testdata/reporter.go " + fname2,
		"--",
		"go", "run", "testdata/run.go",
	})
	if err != nil {
		t.Errorf("err should be nil but: %s", err)
	}
	ho.errStream = ioutil.Discard
	ho.outStream = ioutil.Discard

	r, err := ho.run(cmdArgs)
	if err != nil {
		t.Errorf("err should be nil but: %s", err)
	}

	if r.ExitCode != 0 {
		t.Errorf("exit code should be 0 but: %d", r.ExitCode)
	}

	expect := "1\n2\n3\n"
	if r.Output != expect {
		t.Errorf("output should be %s but: %s", expect, r.Output)
	}
	if r.Stdout != expect {
		t.Errorf("output should be %s but: %s", expect, r.Stdout)
	}
	if r.Stderr != "" {
		t.Errorf("output should be empty but: %s", r.Stderr)
	}
	if r.StartAt == nil {
		t.Errorf("StartAt shouldn't be nil")
	}
	if r.EndAt == nil {
		t.Errorf("EtartAt shouldn't be nil")
	}
	expectedHostname, _ := os.Hostname()
	if r.Hostname != expectedHostname {
		t.Errorf("Hostname should be %s but: %s", expectedHostname, r.Hostname)
	}

	rr := parseReport(fname)
	if !deepEqual(r, rr) {
		t.Errorf("something went wrong. expect: %#v, got: %#v", r, rr)
	}
	rr2 := parseReport(fname2)
	if !deepEqual(r, rr2) {
		t.Errorf("something went wrong. expect: %#v, got: %#v", r, rr2)
	}

	nr := parseReport(noticeReport)
	if nr.Pid != r.Pid {
		t.Errorf("something went wrong")
	}
	if nr.Output != "" {
		t.Errorf("something went wrong")
	}
	if nr.StartAt == nil {
		t.Errorf("StartAt shouldn't be nil")
	}
	if nr.EndAt != nil {
		t.Errorf("EndAt should be nil")
	}
	if nr.ExitCode != -1 {
		t.Errorf("ExitCode should be -1")
	}
	if nr.Hostname != r.Hostname {
		t.Errorf("something went wrong")
	}
}

func TestRun_log(t *testing.T) {
	fname := temp()
	logfile := temp()
	defer func() {
		for _, f := range []string{fname, logfile} {
			os.RemoveAll(f)
		}
	}()
	_, ho, cmdArgs, err := parseArgs([]string{
		"--reporter",
		"go run testdata/reporter.go " + fname,
		"--log", logfile,
		"-vv",
		"-T",
		"--",
		"go", "run", "testdata/run.go",
	})
	if err != nil {
		t.Errorf("err should be nil but: %s", err)
	}
	ho.errStream = ioutil.Discard
	ho.outStream = ioutil.Discard

	r, err := ho.run(cmdArgs)
	if err != nil {
		t.Errorf("err should be nil but: %s", err)
	}
	if r.ExitCode != 0 {
		t.Errorf("exit code should be 0 but: %d", r.ExitCode)
	}

	expect := "1\n2\n3\n"
	if r.Stdout != expect {
		t.Errorf("output should be %s but: %s", expect, r.Stdout)
	}
	if r.Stderr != "" {
		t.Errorf("output should be empty but: %s", r.Stderr)
	}
	if r.StartAt == nil {
		t.Errorf("StartAt shouldn't be nil")
	}
	if r.EndAt == nil {
		t.Errorf("EtartAt shouldn't be nil")
	}
	expectedHostname, _ := os.Hostname()
	if r.Hostname != expectedHostname {
		t.Errorf("Hostname should be %s but: %s", expectedHostname, r.Hostname)
	}

	rr := parseReport(fname)
	if !deepEqual(r, rr) {
		t.Errorf("something went wrong. expect: %#v, got: %#v", r, rr)
	}

	// 2019-02-11T16:39:18.507451+09:00 1
	// 2019-02-11T16:39:18.507666+09:00 2
	// 2019-02-11T16:39:18.507668+09:00 3
	logdata, _ := ioutil.ReadFile(logfile)
	timeRegStr := `[0-9]{4}-[0-9]{2}-[0-9]{2}T` +
		`[0-9]{2}:[0-9]{2}:[0-9]{2}\.[0-9]{6}` +
		`(?:Z|[-+][0-9]{2}:[0-9]{2}) `
	reg := regexp.MustCompile(`\A` +
		timeRegStr + "1\n" +
		timeRegStr + "2\n" +
		timeRegStr + "3\n" + `\z`)
	if !reg.Match(logdata) {
		t.Errorf("something went wrong.\n%s", string(logdata))
	}
}

func TestRun_notfound(t *testing.T) {
	fname := temp()
	defer os.RemoveAll(fname)
	_, ho, cmdArgs, err := parseArgs([]string{
		"--reporter",
		"go run testdata/reporter.go " + fname,
		"--",
		"testdata/notfound",
	})
	if err != nil {
		t.Errorf("err should be nil but: %s", err)
	}
	ho.errStream = ioutil.Discard
	ho.outStream = ioutil.Discard

	r, err := ho.run(cmdArgs)
	if err == nil {
		t.Errorf("err shouldn't be nil")
	}

	if r.ExitCode != -1 {
		t.Errorf("exit code should be -1 but: %d", r.ExitCode)
	}

	if r.StartAt == nil {
		t.Errorf("StartAt shouldn't be nil")
	}
	if r.EndAt != nil {
		t.Errorf("EtartAt should be nil")
	}
	expectedHostname, _ := os.Hostname()
	if r.Hostname != expectedHostname {
		t.Errorf("Hostname should be %s but: %s", expectedHostname, r.Hostname)
	}

	rr := parseReport(fname)
	if !deepEqual(r, rr) {
		t.Errorf("something went wrong. expect: %#v, got: %#v", r, rr)
	}
}

func TestRun_config(t *testing.T) {
	_, ho, cmdArgs, err := parseArgs([]string{
		"--config", "testdata/config.yaml",
		"--",
		"go", "run", "testdata/run.go",
	})
	if err != nil {
		t.Errorf("err should be nil but: %s", err)
	}
	ho.errStream = ioutil.Discard
	ho.outStream = ioutil.Discard

	r, err := ho.run(cmdArgs)
	if err != nil {
		t.Errorf("err should be nil, but: %s", err)
	}

	if r.ExitCode != 0 {
		t.Errorf("exit code should be 0 but: %d", r.ExitCode)
	}

	if r.StartAt == nil {
		t.Errorf("StartAt shouldn't be nil")
	}
	if r.EndAt == nil {
		t.Errorf("EndAt shouldn't be nil")
	}
	expectedHostname, _ := os.Hostname()
	if r.Hostname != expectedHostname {
		t.Errorf("Hostname should be %s but: %s", expectedHostname, r.Hostname)
	}
}

func TestRunHugeOutput(t *testing.T) {
	fname := temp()
	defer os.RemoveAll(fname)
	_, ho, cmdArgs, err := parseArgs([]string{
		"--reporter",
		"go run testdata/reporter.go " + fname,
		"--",
		"go", "run", "testdata/run_hugeoutput.go",
	})
	if err != nil {
		t.Errorf("err should be nil but: %s", err)
	}
	ho.errStream = ioutil.Discard
	ho.outStream = ioutil.Discard

	r, err := ho.run(cmdArgs)
	if err != nil {
		t.Errorf("err should be nil but: %s", err)
	}

	if r.ExitCode != 0 {
		t.Errorf("exit code should be 0 but: %d", r.ExitCode)
	}

	expect := 64*1024 + 1
	if len(r.Output) != expect {
		t.Errorf("output should be %d bytes but: %d bytes", expect, len(r.Output))
	}
	if len(r.Stdout) != expect {
		t.Errorf("output should be %d bytes but: %d bytes", expect, len(r.Stdout))
	}
	if r.Stderr != "" {
		t.Errorf("output should be empty but: %s", r.Stderr)
	}
	if r.StartAt == nil {
		t.Errorf("StartAt shouldn't be nil")
	}
	if r.EndAt == nil {
		t.Errorf("EtartAt shouldn't be nil")
	}
	expectedHostname, _ := os.Hostname()
	if r.Hostname != expectedHostname {
		t.Errorf("Hostname should be %s but: %s", expectedHostname, r.Hostname)
	}

	rr := parseReport(fname)
	if !deepEqual(r, rr) {
		t.Errorf("something went wrong. expect: %#v, got: %#v", r, rr)
	}
}

func equalTimePtr(t1, t2 *time.Time) bool {
	if t1 == nil && t2 == nil {
		return true
	}
	if t1 == nil || t2 == nil {
		return false
	}
	return (*t1).Equal(*t2)
}

func deepEqual(r1, r2 Report) bool {
	return r1.Command == r2.Command &&
		reflect.DeepEqual(r1.CommandArgs, r2.CommandArgs) &&
		r1.Tag == r2.Tag &&
		r1.Output == r2.Output &&
		r1.Stdout == r2.Stdout &&
		r1.Stderr == r2.Stderr &&
		r1.ExitCode == r2.ExitCode &&
		r1.Result == r2.Result &&
		r1.Pid == r2.Pid &&
		r1.Hostname == r2.Hostname &&
		r1.Signaled == r2.Signaled &&
		equalTimePtr(r1.StartAt, r2.StartAt) &&
		equalTimePtr(r1.EndAt, r2.EndAt)
}
<|endoftext|>


<commit_before>package main

import (
	"flag"
	"net/http"

	"os"

	"github.com/bborbe/log"
	"github.com/bborbe/portfolio/handler"
	"github.com/facebookgo/grace/gracehttp"
)

const (
	PARAMETER_LOGLEVEL = "loglevel"
)

var (
	logger          = log.DefaultLogger
	addressPtr      = flag.String("a0", ":48568", "Zero address to bind to.")
	documentRootPtr = flag.String("root", "", "Document root directory")
	logLevelPtr     = flag.String(PARAMETER_LOGLEVEL, log.INFO_STRING, log.FLAG_USAGE)
)

func main() {
	defer logger.Close()
	flag.Parse()

	logger.SetLevelThreshold(log.LogStringToLevel(*logLevelPtr))
	logger.Debugf("set log level to %s", *logLevelPtr)

	server, err := createServer(*addressPtr, *documentRootPtr)
	if err != nil {
		logger.Fatal(err)
		logger.Close()
		os.Exit(1)
	}
	logger.Debugf("start server")
	gracehttp.Serve(server)
}

func createServer(address string, documentRoot string) (*http.Server, error) {
	return &http.Server{Addr: address, Handler: handler.NewHandler(documentRoot)}, nil
}
<commit_msg>set max procs<commit_after>package main

import (
	"flag"
	"net/http"

	"os"

	"github.com/bborbe/log"
	"github.com/bborbe/portfolio/handler"
	"github.com/facebookgo/grace/gracehttp"
	"runtime"
)

const (
	PARAMETER_LOGLEVEL = "loglevel"
)

var (
	logger          = log.DefaultLogger
	addressPtr      = flag.String("a0", ":48568", "Zero address to bind to.")
	documentRootPtr = flag.String("root", "", "Document root directory")
	logLevelPtr     = flag.String(PARAMETER_LOGLEVEL, log.INFO_STRING, log.FLAG_USAGE)
)

func main() {
	defer logger.Close()
	flag.Parse()

	logger.SetLevelThreshold(log.LogStringToLevel(*logLevelPtr))
	logger.Debugf("set log level to %s", *logLevelPtr)

	runtime.GOMAXPROCS(runtime.NumCPU())

	server, err := createServer(*addressPtr, *documentRootPtr)
	if err != nil {
		logger.Fatal(err)
		logger.Close()
		os.Exit(1)
	}
	logger.Debugf("start server")
	gracehttp.Serve(server)
}

func createServer(address string, documentRoot string) (*http.Server, error) {
	return &http.Server{Addr: address, Handler: handler.NewHandler(documentRoot)}, nil
}
<|endoftext|>


<commit_before>package pg

import (
	"testing"

	"github.com/fortytw2/dockertest"
)

const truncateAllTables string = `
CREATE OR REPLACE FUNCTION truncate_tables(_username text)
	RETURNS void AS
$func$
BEGIN
	RAISE NOTICE '%',
	-- EXECUTE  -- dangerous, test before you execute!
	(SELECT 'TRUNCATE TABLE '
		|| string_agg(format('%I.%I', schemaname, tablename), ', ')
		|| ' CASCADE'
	FROM pg_tables
	WHERE tableowner = _username
	AND schemaname = 'public'
	);
END
$func$ LANGUAGE plpgsql;`

func setupTestDB(t *testing.T) (*DB, func()) {
	var db *DB

	container, err := dockertest.RunContainer("postgres:alpine", "5432", func(addr string) error {
		var err error
		db, err = NewDB("postgres://postgres:postgres@"+addr+"?sslmode=disable", false)
		return err
	})
	if err != nil {
		t.Fatalf("could not start postgres, %s", err)
	}

	_, err = db.sql.Exec(truncateAllTables)
	if err != nil {
		t.Fatalf("couldn't create truncate tables script")
	}

	return db, container.Shutdown
}

func truncateTables(t *testing.T, db *DB) {
	_, err := db.sql.Exec(`SELECT truncate_tables('postgres')`)
	if err != nil {
		t.Fatal(err)
	}
}

type testCase struct {
	name string
	do   func(t *testing.T) error
	want func(t *testing.T) error
}

func runCases(t *testing.T, db *DB, cases []testCase) {
	for _, tt := range cases {
		truncateTables(t, db)

		t.Run(tt.name, func(t *testing.T) {
			err := tt.do(t)
			if err != nil {
				t.Fatal(err)
			}

			if tt.want != nil {
				err = tt.want(t)
				if err != nil {
					t.Fatal(err)
				}
			}
		})
	}
}
<commit_msg>t.Helper() thanks cgilmour<commit_after>package pg

import (
	"testing"

	"github.com/fortytw2/dockertest"
)

const truncateAllTables string = `
CREATE OR REPLACE FUNCTION truncate_tables(_username text)
	RETURNS void AS
$func$
BEGIN
	RAISE NOTICE '%',
	-- EXECUTE  -- dangerous, test before you execute!
	(SELECT 'TRUNCATE TABLE '
		|| string_agg(format('%I.%I', schemaname, tablename), ', ')
		|| ' CASCADE'
	FROM pg_tables
	WHERE tableowner = _username
	AND schemaname = 'public'
	);
END
$func$ LANGUAGE plpgsql;`

func setupTestDB(t *testing.T) (*DB, func()) {
	var db *DB

	container, err := dockertest.RunContainer("postgres:alpine", "5432", func(addr string) error {
		var err error
		db, err = NewDB("postgres://postgres:postgres@"+addr+"?sslmode=disable", false)
		return err
	})
	if err != nil {
		t.Fatalf("could not start postgres, %s", err)
	}

	_, err = db.sql.Exec(truncateAllTables)
	if err != nil {
		t.Fatalf("couldn't create truncate tables script")
	}

	return db, container.Shutdown
}

func truncateTables(t *testing.T, db *DB) {
	_, err := db.sql.Exec(`SELECT truncate_tables('postgres')`)
	if err != nil {
		t.Fatal(err)
	}
}

type testCase struct {
	name string
	do   func(t *testing.T) error
	want func(t *testing.T) error
}

func runCases(t *testing.T, db *DB, cases []testCase) {
	t.Helper()

	for _, tt := range cases {
		truncateTables(t, db)

		t.Run(tt.name, func(t *testing.T) {
			err := tt.do(t)
			if err != nil {
				t.Fatal(err)
			}

			if tt.want != nil {
				err = tt.want(t)
				if err != nil {
					t.Fatal(err)
				}
			}
		})
	}
}
<|endoftext|>


<commit_before>// Licensed to the Apache Software Foundation (ASF) under one or more
// contributor license agreements.  See the NOTICE file distributed with
// this work for additional information regarding copyright ownership.
// The ASF licenses this file to You under the Apache License, Version 2.0
// (the "License"); you may not use this file except in compliance with
// the License.  You may obtain a copy of the License at
//
//    http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package grpcx

import (
	"context"
	"fmt"
	"time"

	"google.golang.org/grpc"
)

// Dial is a convenience wrapper over grpc.Dial that specifies an insecure,
// blocking connection with a timeout.
func Dial(ctx context.Context, endpoint string, timeout time.Duration) (*grpc.ClientConn, error) {
	ctx, cancel := context.WithTimeout(ctx, timeout)
	defer cancel()

	cc, err := grpc.DialContext(ctx, endpoint, grpc.WithInsecure(), grpc.WithBlock(),
		grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(50<<20)))
	if err != nil {
		return nil, fmt.Errorf("failed to dial server at %v: %v", endpoint, err)
	}
	return cc, nil
}
<commit_msg>Allow grpcx.Dial to support overrides.<commit_after>// Licensed to the Apache Software Foundation (ASF) under one or more
// contributor license agreements.  See the NOTICE file distributed with
// this work for additional information regarding copyright ownership.
// The ASF licenses this file to You under the Apache License, Version 2.0
// (the "License"); you may not use this file except in compliance with
// the License.  You may obtain a copy of the License at
//
//    http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package grpcx

import (
	"context"
	"fmt"
	"time"

	"google.golang.org/grpc"
)

// Dial is a convenience wrapper over grpc.Dial. It can be overridden
// to provide a customized dialing behavior.
var Dial = DefaultDial

// DefaultDial is a dialer that specifies an insecure blocking connection with a timeout.
func DefaultDial(ctx context.Context, endpoint string, timeout time.Duration) (*grpc.ClientConn, error) {
	ctx, cancel := context.WithTimeout(ctx, timeout)
	defer cancel()

	cc, err := grpc.DialContext(ctx, endpoint, grpc.WithInsecure(), grpc.WithBlock(),
		grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(50<<20)))
	if err != nil {
		return nil, fmt.Errorf("failed to dial server at %v: %v", endpoint, err)
	}
	return cc, nil
}
<|endoftext|>


<commit_before>package main

import (
	"github.com/GeertJohan/go.rice"
	"github.com/gorilla/mux"
	"log"
	"net"
	"net/http"
)

const headerKeySessionKey = `X-Nulpunt-SessionKey`

// initHTTPServer sets up the http.FileServer and other http services.
func initHTTPServer() {
	// create fileServer that servces that static files (html,css,js,etc.)
	boxHTTPFiles, err := rice.FindBox("../../../http-files")
	if err != nil {
		log.Fatalf("cannot find rice box. error: %s\n", err)
	}
	fileServer := http.FileServer(boxHTTPFiles.HTTPBox())

	// rootRouter is directly linked to the http server.
	rootRouter := mux.NewRouter()

	// serve static files on / and several subdirs
	// NOTE: only the exact path "/" will match. http.FileService will resolve this to index.html
	// NOTE: "index.html" itself wont match
	rootRouter.Methods("GET").Path("/").Handler(fileServer)
	rootRouter.Methods("GET").PathPrefix("/css/").Handler(fileServer)
	rootRouter.Methods("GET").PathPrefix("/fonts/").Handler(fileServer)
	rootRouter.Methods("GET").PathPrefix("/html/").Handler(fileServer)
	rootRouter.Methods("GET").PathPrefix("/js/").Handler(fileServer)
	rootRouter.Methods("GET").PathPrefix("/img/").Handler(fileServer)
	rootRouter.Methods("GET").Path("/download-original/{documentIDHex}/{filename:.*}").HandlerFunc(downloadOriginalHandlerFunc)

	// serve document files on /docfiles/
	docfilesRouter := rootRouter.PathPrefix("/docfiles/").Subrouter()
	docfilesRouter.Methods("GET").Path("/pages/{documentIDHex}/{pageNumber}.png").HandlerFunc(pageImageHandlerFunc)
	docfilesRouter.Methods("GET").Path("/thumbnails/{documentIDHex}.png").HandlerFunc(thumbnailImageHandlerFunc)

	// create serviceRouter for /service/*
	serviceRouter := rootRouter.PathPrefix("/service/").Subrouter()
	serviceRouter.Path("/sessionInit").HandlerFunc(sessionInitHandlerFunc)
	serviceRouter.Path("/sessionCheck").HandlerFunc(sessionCheckHandlerFunc)

	// Document handlers
	serviceRouter.Methods("POST").Path("/getDocument").HandlerFunc(getDocumentHandler) // TODO: why is this a POST?
	serviceRouter.Methods("POST").Path("/getPage").HandlerFunc(getPageHandlerFunc)
	serviceRouter.Methods("GET").Path("/getDocuments").HandlerFunc(getDocumentsHandler)
	serviceRouter.Methods("GET").Path("/getDocumentList").HandlerFunc(getDocumentListHandler)
	// create sessionPathRouter for /service/session/*
	sessionPathRouter := rootRouter.PathPrefix("/service/session/").Subrouter()

	// sessionRouter handles valid authenticated requests for /service/session
	sessionRouter := sessionPathRouter.MatcherFunc(func(r *http.Request, rm *mux.RouteMatch) bool {
		sessionKey := r.Header.Get(headerKeySessionKey)
		return isValidClientSession(sessionKey)
	}).Subrouter()

	// register /service/session/* handlers
	sessionRouter.Path("/ping").HandlerFunc(sessionPingHandlerFunc)
	sessionRouter.Path("/destroy").HandlerFunc(sessionDestroyHandlerFunc)
	sessionRouter.Path("/registerAccount").HandlerFunc(registerAccountHandlerFunc)
	sessionRouter.Path("/authenticateAccount").HandlerFunc(sessionAuthenticateAccountHandlerFunc)
	sessionRouter.Path("/resume").HandlerFunc(sessionResumeHandlerFunc)
	sessionRouter.Path("/dataBlobSave").HandlerFunc(sessionDataBlobSave)
	sessionRouter.Path("/dataBlobLoad").HandlerFunc(sessionDataBlobLoad)

	sessionRouter.Methods("GET").Path("/get-tags").HandlerFunc(adminGetTags)

	sessionRouter.Methods("POST").Path("/add-annotation").HandlerFunc(addAnnotationHandler)
	sessionRouter.Methods("POST").Path("/add-comment").HandlerFunc(addCommentHandler)

	sessionRouter.Methods("GET").Path("/get-profile").HandlerFunc(getProfileHandler)
	// Users can't make profiles yet. BLOCK em.
	// sessionRouter.Methods("POST").Path("/update-profile").HandlerFunc(updateProfileHandler)

	sessionRouter.Path("/get-documents-by-tags").HandlerFunc(getDocumentsByTagsHandler)

	// register /service/session/admin/* handlers
	adminRouter := sessionRouter.PathPrefix("/admin/").Subrouter()
	adminRouter.Path("/upload").HandlerFunc(adminUpload)
	adminRouter.Methods("GET").Path("/getRawUploads").HandlerFunc(adminGetRawUploads)

	adminRouter.Methods("POST").Path("/add-tag").HandlerFunc(adminAddTag) //  /service/add-tags, ie only for admins
	adminRouter.Methods("POST").Path("/delete-tag").HandlerFunc(adminDeleteTag)

	adminRouter.Methods("POST").Path("/updateDocument").HandlerFunc(updateDocumentHandler)
	adminRouter.Methods("POST").Path("/insertDocument").HandlerFunc(insertDocumentHandler)
	adminRouter.Methods("POST").Path("/deleteDocument").HandlerFunc(deleteDocumentHandler)
	// Trending:
	// get-trending delivers the data structures to build a page with the current contents of the
	// trending-collection, available to everyone without login.
	serviceRouter.Methods("GET").Path("/get-trending").HandlerFunc(getTrendingHandlerFunc)

	// update-trending updates the trending collection.
	// TODO: take out after one run, it's not needed anymore
	rootRouter.Methods("GET").Path("/update-trending").HandlerFunc(updateTrendingHandlerFunc)

	// 404 when /service/session/admin/* was not found
	adminRouter.PathPrefix("/").Handler(http.NotFoundHandler())

	// 404 when /service/session/* was not found
	sessionRouter.PathPrefix("/").Handler(http.NotFoundHandler())

	// when session auth failed, return 403 forbidden for /service/session/*
	sessionPathRouter.PathPrefix("/").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		http.Error(w, "forbidden, invalid session key", http.StatusForbidden)
	})

	// run http server in goroutine
	go func() {
		// inform user of startup
		log.Printf("starting http server on http://localhost:%s\n", flags.HTTPPort)

		// listen and serve on given port
		// error is fatal
		err := http.ListenAndServe(":"+flags.HTTPPort, rootRouter)
		if err != nil {
			log.Fatalf("fatal error listening/serving http on tcp: %s\n", err)
		}
	}()

	if len(flags.UnixSocket) > 0 {
		go func() {
			// socketClosing is used to omit error on socket read when closing down.
			var socketClosing bool

			// inform user of startup
			log.Printf("Starting http server on unix socket %s\n", flags.UnixSocket)

			// create and listen on this unix socket
			socket, err := net.ListenUnix("unix", &net.UnixAddr{
				Name: flags.UnixSocket,
				Net:  "unix",
			})
			if err != nil {
				log.Fatalf("fatal error on listening on unix socket: %s\n", err)
			}

			// append a function on graceful shutdown to close the unix socket
			processEndFuncs = append(processEndFuncs, func() {
				socketClosing = true
				socket.Close()
			})

			// serve on the opened unix socket
			// an error (when not closing down) is fatal
			err = http.Serve(socket, rootRouter)
			if !socketClosing && err != nil {
				log.Fatalf("fatal error serving http on the unix socket: %s\n", err)
			}
		}()
	}
}
<commit_msg>go fmt complains (in a comment).<commit_after>package main

import (
	"github.com/GeertJohan/go.rice"
	"github.com/gorilla/mux"
	"log"
	"net"
	"net/http"
)

const headerKeySessionKey = `X-Nulpunt-SessionKey`

// initHTTPServer sets up the http.FileServer and other http services.
func initHTTPServer() {
	// create fileServer that servces that static files (html,css,js,etc.)
	boxHTTPFiles, err := rice.FindBox("../../../http-files")
	if err != nil {
		log.Fatalf("cannot find rice box. error: %s\n", err)
	}
	fileServer := http.FileServer(boxHTTPFiles.HTTPBox())

	// rootRouter is directly linked to the http server.
	rootRouter := mux.NewRouter()

	// serve static files on / and several subdirs
	// NOTE: only the exact path "/" will match. http.FileService will resolve this to index.html
	// NOTE: "index.html" itself wont match
	rootRouter.Methods("GET").Path("/").Handler(fileServer)
	rootRouter.Methods("GET").PathPrefix("/css/").Handler(fileServer)
	rootRouter.Methods("GET").PathPrefix("/fonts/").Handler(fileServer)
	rootRouter.Methods("GET").PathPrefix("/html/").Handler(fileServer)
	rootRouter.Methods("GET").PathPrefix("/js/").Handler(fileServer)
	rootRouter.Methods("GET").PathPrefix("/img/").Handler(fileServer)
	rootRouter.Methods("GET").Path("/download-original/{documentIDHex}/{filename:.*}").HandlerFunc(downloadOriginalHandlerFunc)

	// serve document files on /docfiles/
	docfilesRouter := rootRouter.PathPrefix("/docfiles/").Subrouter()
	docfilesRouter.Methods("GET").Path("/pages/{documentIDHex}/{pageNumber}.png").HandlerFunc(pageImageHandlerFunc)
	docfilesRouter.Methods("GET").Path("/thumbnails/{documentIDHex}.png").HandlerFunc(thumbnailImageHandlerFunc)

	// create serviceRouter for /service/*
	serviceRouter := rootRouter.PathPrefix("/service/").Subrouter()
	serviceRouter.Path("/sessionInit").HandlerFunc(sessionInitHandlerFunc)
	serviceRouter.Path("/sessionCheck").HandlerFunc(sessionCheckHandlerFunc)

	// Document handlers
	serviceRouter.Methods("POST").Path("/getDocument").HandlerFunc(getDocumentHandler) // TODO: why is this a POST?
	serviceRouter.Methods("POST").Path("/getPage").HandlerFunc(getPageHandlerFunc)
	serviceRouter.Methods("GET").Path("/getDocuments").HandlerFunc(getDocumentsHandler)
	serviceRouter.Methods("GET").Path("/getDocumentList").HandlerFunc(getDocumentListHandler)
	// create sessionPathRouter for /service/session/*
	sessionPathRouter := rootRouter.PathPrefix("/service/session/").Subrouter()

	// sessionRouter handles valid authenticated requests for /service/session
	sessionRouter := sessionPathRouter.MatcherFunc(func(r *http.Request, rm *mux.RouteMatch) bool {
		sessionKey := r.Header.Get(headerKeySessionKey)
		return isValidClientSession(sessionKey)
	}).Subrouter()

	// register /service/session/* handlers
	sessionRouter.Path("/ping").HandlerFunc(sessionPingHandlerFunc)
	sessionRouter.Path("/destroy").HandlerFunc(sessionDestroyHandlerFunc)
	sessionRouter.Path("/registerAccount").HandlerFunc(registerAccountHandlerFunc)
	sessionRouter.Path("/authenticateAccount").HandlerFunc(sessionAuthenticateAccountHandlerFunc)
	sessionRouter.Path("/resume").HandlerFunc(sessionResumeHandlerFunc)
	sessionRouter.Path("/dataBlobSave").HandlerFunc(sessionDataBlobSave)
	sessionRouter.Path("/dataBlobLoad").HandlerFunc(sessionDataBlobLoad)

	sessionRouter.Methods("GET").Path("/get-tags").HandlerFunc(adminGetTags)

	sessionRouter.Methods("POST").Path("/add-annotation").HandlerFunc(addAnnotationHandler)
	sessionRouter.Methods("POST").Path("/add-comment").HandlerFunc(addCommentHandler)

	sessionRouter.Methods("GET").Path("/get-profile").HandlerFunc(getProfileHandler)
	// Users can't make profiles yet. BLOCK em.
	// sessionRouter.Methods("POST").Path("/update-profile").HandlerFunc(updateProfileHandler)

	sessionRouter.Path("/get-documents-by-tags").HandlerFunc(getDocumentsByTagsHandler)

	// register /service/session/admin/* handlers
	adminRouter := sessionRouter.PathPrefix("/admin/").Subrouter()
	adminRouter.Path("/upload").HandlerFunc(adminUpload)
	adminRouter.Methods("GET").Path("/getRawUploads").HandlerFunc(adminGetRawUploads)

	adminRouter.Methods("POST").Path("/add-tag").HandlerFunc(adminAddTag) //  /service/add-tags, ie only for admins
	adminRouter.Methods("POST").Path("/delete-tag").HandlerFunc(adminDeleteTag)

	adminRouter.Methods("POST").Path("/updateDocument").HandlerFunc(updateDocumentHandler)
	adminRouter.Methods("POST").Path("/insertDocument").HandlerFunc(insertDocumentHandler)
	adminRouter.Methods("POST").Path("/deleteDocument").HandlerFunc(deleteDocumentHandler)
	// Trending:
	// get-trending delivers the data structures to build a page with the current contents of the
	// trending-collection, available to everyone without login.
	serviceRouter.Methods("GET").Path("/get-trending").HandlerFunc(getTrendingHandlerFunc)

	// update-trending updates the trending collection.
	// TODO: take out after one run, it's not needed anymore
	rootRouter.Methods("GET").Path("/update-trending").HandlerFunc(updateTrendingHandlerFunc)

	// 404 when /service/session/admin/* was not found
	adminRouter.PathPrefix("/").Handler(http.NotFoundHandler())

	// 404 when /service/session/* was not found
	sessionRouter.PathPrefix("/").Handler(http.NotFoundHandler())

	// when session auth failed, return 403 forbidden for /service/session/*
	sessionPathRouter.PathPrefix("/").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		http.Error(w, "forbidden, invalid session key", http.StatusForbidden)
	})

	// run http server in goroutine
	go func() {
		// inform user of startup
		log.Printf("starting http server on http://localhost:%s\n", flags.HTTPPort)

		// listen and serve on given port
		// error is fatal
		err := http.ListenAndServe(":"+flags.HTTPPort, rootRouter)
		if err != nil {
			log.Fatalf("fatal error listening/serving http on tcp: %s\n", err)
		}
	}()

	if len(flags.UnixSocket) > 0 {
		go func() {
			// socketClosing is used to omit error on socket read when closing down.
			var socketClosing bool

			// inform user of startup
			log.Printf("Starting http server on unix socket %s\n", flags.UnixSocket)

			// create and listen on this unix socket
			socket, err := net.ListenUnix("unix", &net.UnixAddr{
				Name: flags.UnixSocket,
				Net:  "unix",
			})
			if err != nil {
				log.Fatalf("fatal error on listening on unix socket: %s\n", err)
			}

			// append a function on graceful shutdown to close the unix socket
			processEndFuncs = append(processEndFuncs, func() {
				socketClosing = true
				socket.Close()
			})

			// serve on the opened unix socket
			// an error (when not closing down) is fatal
			err = http.Serve(socket, rootRouter)
			if !socketClosing && err != nil {
				log.Fatalf("fatal error serving http on the unix socket: %s\n", err)
			}
		}()
	}
}
<|endoftext|>


<commit_before>package phraseapp

import (
	"encoding/json"
	"fmt"
	"net/http"
)

func handleResponseStatus(resp *http.Response, expectedStatus int) error {
	switch resp.StatusCode {
	case expectedStatus:
		return nil
	case 400:
		e := new(ErrorResponse)
		err := json.NewDecoder(resp.Body).Decode(&e)
		if err != nil {
			return err
		}
		return e
	case 404:
		return fmt.Errorf("not found")
	case 422:
		e := new(ValidationErrorResponse)
		err := json.NewDecoder(resp.Body).Decode(&e)
		if err != nil {
			return err
		}
		return e
	case 429:
		e, err := NewRateLimitError(resp)
		if err != nil {
			return err
		}
		return e
	default:
		return fmt.Errorf("unexpected status code (%d) received; expected %d", resp.StatusCode, expectedStatus)
	}
}
<commit_msg>adds nicer error messages<commit_after>package phraseapp

import (
	"encoding/json"
	"fmt"
	"net/http"
)

const docsURL = `http://docs.phraseapp.com/api/v2/`

func further() string {
	return fmt.Sprintf("\nFor further information see:\n%s", docsURL)
}

func handleResponseStatus(resp *http.Response, expectedStatus int) error {
	switch resp.StatusCode {
	case expectedStatus:
		return nil
	case 400:
		e := new(ErrorResponse)
		err := json.NewDecoder(resp.Body).Decode(&e)
		if err != nil {
			return err
		}
		return e
	case 401:
		return fmt.Errorf("401 - %s\nThe credentials you provided are invalid.%s", http.StatusText(resp.StatusCode), further())
	case 403:
		return fmt.Errorf("403 - %s\nYou are not authorized to perform the requested action on the requested resource. Check if your provided access_token has the correct scope.%s", http.StatusText(resp.StatusCode), further())
	case 404:
		return fmt.Errorf("404 - Resource Not Found\nThe resource you requested or referenced resources you required do either not exist or you do not have the authorization to request this resource.")
	case 422:
		e := new(ValidationErrorResponse)
		err := json.NewDecoder(resp.Body).Decode(&e)
		if err != nil {
			return err
		}
		return e
	case 429:
		e, err := NewRateLimitError(resp)
		if err != nil {
			return err
		}
		return e
	default:
		return fmt.Errorf("Unexpected HTTP Status Code (%d %s) received; expected %d %s.%s", resp.StatusCode, http.StatusText(resp.StatusCode), expectedStatus, http.StatusText(expectedStatus), further())
	}
}
<|endoftext|>


<commit_before>package app

import (
	"fmt"
	"os"
	"strings"

	"k8s.io/kubernetes/pkg/version"

	"github.com/golang/glog"
)

// ImageTemplate is a class to assist in expanding parameterized Docker image references
// from configuration or a file
type ImageTemplate struct {
	// Format is required, set to the image template to pull
	Format string
	Latest bool
	// EnvFormat is optional, if set will substitute the value of ${component} with any env
	// var that matches this format. Is a printf format string accepting a single
	// string parameter.
	EnvFormat string
}

var (
	// defaultImagePrefix is the default prefix for any container image names.
	// This value should be set duing build via -ldflags.
	DefaultImagePrefix string

	// defaultImageFormat is the default format for container image names used
	// to run containerized components of the platform
	defaultImageFormat = DefaultImagePrefix + "-${component}:${version}"
)

const defaultImageEnvFormat = "OPENSHIFT_%s_IMAGE"

// NewDefaultImageTemplate returns the default image template
func NewDefaultImageTemplate() ImageTemplate {
	return ImageTemplate{
		Format:    defaultImageFormat,
		Latest:    false,
		EnvFormat: defaultImageEnvFormat,
	}
}

// ExpandOrDie will either expand a string or exit in case of failure
func (t *ImageTemplate) ExpandOrDie(component string) string {
	value, err := t.Expand(component)
	if err != nil {
		glog.Fatalf("Unable to find an image for %q due to an error processing the format: %v", component, err)
	}
	return value
}

// Expand expands a string using a series of common format functions
func (t *ImageTemplate) Expand(component string) (string, error) {
	template := t.Format
	if len(t.EnvFormat) > 0 {
		if s, ok := t.imageComponentEnvExpander(component); ok {
			template = s
		}
	}
	value, err := ExpandStrict(template, func(key string) (string, bool) {
		switch key {
		case "component":
			return component, true
		case "version":
			if t.Latest {
				return "latest", true
			}
		}
		return "", false
	}, Versions)
	return value, err
}

func (t *ImageTemplate) imageComponentEnvExpander(key string) (string, bool) {
	s := strings.Replace(strings.ToUpper(key), "-", "_", -1)
	val := os.Getenv(fmt.Sprintf(t.EnvFormat, s))
	if len(val) == 0 {
		return "", false
	}
	return val, true
}

// ExpandStrict expands a string using a series of common format functions
func ExpandStrict(s string, fns ...KeyFunc) (string, error) {
	unmatched := []string{}
	result := os.Expand(s, func(key string) string {
		for _, fn := range fns {
			val, ok := fn(key)
			if !ok {
				continue
			}
			return val
		}
		unmatched = append(unmatched, key)
		return ""
	})

	switch len(unmatched) {
	case 0:
		return result, nil
	case 1:
		return "", fmt.Errorf("the key %q in %q is not recognized", unmatched[0], s)
	default:
		return "", fmt.Errorf("multiple keys in %q were not recognized: %s", s, strings.Join(unmatched, ", "))
	}
}

// KeyFunc returns the value associated with the provided key or false if no
// such key exists.
type KeyFunc func(key string) (string, bool)

// Versions is a KeyFunc for retrieving information about the current version.
func Versions(key string) (string, bool) {
	switch key {
	case "shortcommit":
		s := OverrideVersion.GitCommit
		if len(s) > 7 {
			s = s[:7]
		}
		return s, true
	case "version":
		s := lastSemanticVersion(OverrideVersion.GitVersion)
		return s, true
	default:
		return "", false
	}
}

// OverrideVersion is the latest version, exposed for testing.
var OverrideVersion = version.Get()

// lastSemanticVersion attempts to return a semantic version from the GitVersion - which
// is either <semver>+<commit> or <semver> on release boundaries.
func lastSemanticVersion(gitVersion string) string {
	parts := strings.Split(gitVersion, "+")
	return parts[0]
}
<commit_msg>UPSTREAM: <carry>: remove dead controller-manager carry<commit_after><|endoftext|>


<commit_before>package main

import (
	log "code.google.com/p/log4go"
	"sync"
	"time"
)

type RoomOptions struct {
	ChannelSize int
	ProtoSize   int
	BatchNum    int
	SignalTime  time.Duration
}

type Room struct {
	id      int32
	rLock   sync.RWMutex
	proto   Ring
	signal  chan int
	chs     map[*Channel]struct{} // map room id with channels
	sigTime time.Duration
	options RoomOptions
}

// NewRoom new a room struct, store channel room info.
func NewRoom(id int32, options RoomOptions) (r *Room) {
	r = new(Room)
	r.id = id
	r.options = options
	r.proto.Init(options.ProtoSize)
	r.signal = make(chan int, signalNum)
	r.chs = make(map[*Channel]struct{}, options.ChannelSize)
	go r.push()
	return
}

// Put put channel into the room.
func (r *Room) Put(ch *Channel) {
	r.rLock.Lock()
	r.chs[ch] = struct{}{}
	r.rLock.Unlock()
	return
}

// Del delete channel from the room.
func (r *Room) Del(ch *Channel) {
	r.rLock.Lock()
	delete(r.chs, ch)
	r.rLock.Unlock()
}

// push merge proto and push msgs in batch.
func (r *Room) push() {
	var (
		s, n       int
		exit, done bool
		err        error
		p          *Proto
		ch         *Channel
		least      time.Duration
		last       = time.Now()
		vers       = make([]int32, r.options.BatchNum)
		operations = make([]int32, r.options.BatchNum)
		msgs       = make([][]byte, r.options.BatchNum)
	)
	if Debug {
		log.Debug("start room: %d goroutine", r.id)
	}
	for {
		if least = r.options.SignalTime - time.Now().Sub(last); least > 0 {
			select {
			case s = <-r.signal:
				if exit = (s != protoReady); exit {
					done = true
					break
				}
				// merge msgs
				if n == 0 {
					last = time.Now()
				}
				for {
					if n >= r.options.BatchNum {
						// msgs full
						done = true
						break
					}
					if p, err = r.proto.Get(); err != nil {
						// must be empty error
						break
					}
					vers[n] = int32(p.Ver)
					operations[n] = p.Operation
					msgs[n] = p.Body
					n++
					r.proto.GetAdv()
				}
			case <-time.After(least):
				done = true
				exit = false
			}
		} else {
			done = true
		}
		if done {
			if n > 0 {
				r.rLock.RLock()
				for ch, _ = range r.chs {
					// ignore error
					ch.PushMsgs(vers[:n], operations[:n], msgs[:n])
				}
				r.rLock.RUnlock()
				n = 0
			}
			done = false
			last = time.Now()
		}
		if exit {
			break
		}
	}
	if Debug {
		log.Debug("room: %d goroutine exit", r.id)
	}
}

// PushMsg push msg to the room.
func (r *Room) PushMsg(ver int16, operation int32, msg []byte) (err error) {
	var proto *Proto
	r.rLock.Lock()
	if proto, err = r.proto.Set(); err == nil {
		proto.Ver = ver
		proto.Operation = operation
		proto.Body = msg
		r.proto.SetAdv()
	}
	r.rLock.Unlock()
	select {
	case r.signal <- protoReady:
	default:
	}
	return
}

// Online get online number.
func (r *Room) Online() (o int) {
	r.rLock.RLock()
	o = len(r.chs)
	r.rLock.RUnlock()
	return
}

// Close close the room.
func (r *Room) Close() {
	var ch *Channel
	// don't use close chan, Signal can be reused
	// if chan full, writer goroutine next fetch from chan will exit
	// if chan empty, send a 0(close) let the writer exit
	select {
	case r.signal <- protoFinish:
	default:
	}
	r.rLock.RLock()
	for ch, _ = range r.chs {
		ch.Close()
	}
	r.rLock.RUnlock()
}
<commit_msg>fix room close bug<commit_after>package main

import (
	log "code.google.com/p/log4go"
	"sync"
	"time"
)

type RoomOptions struct {
	ChannelSize int
	ProtoSize   int
	BatchNum    int
	SignalTime  time.Duration
}

type Room struct {
	id      int32
	rLock   sync.RWMutex
	proto   Ring
	signal  chan int
	chs     map[*Channel]struct{} // map room id with channels
	sigTime time.Duration
	options RoomOptions
}

// NewRoom new a room struct, store channel room info.
func NewRoom(id int32, options RoomOptions) (r *Room) {
	r = new(Room)
	r.id = id
	r.options = options
	r.proto.Init(options.ProtoSize)
	r.signal = make(chan int, signalNum)
	r.chs = make(map[*Channel]struct{}, options.ChannelSize)
	go r.push()
	return
}

// Put put channel into the room.
func (r *Room) Put(ch *Channel) {
	r.rLock.Lock()
	r.chs[ch] = struct{}{}
	r.rLock.Unlock()
	return
}

// Del delete channel from the room.
func (r *Room) Del(ch *Channel) {
	r.rLock.Lock()
	delete(r.chs, ch)
	r.rLock.Unlock()
}

// push merge proto and push msgs in batch.
func (r *Room) push() {
	var (
		s, n       int
		exit, done bool
		err        error
		p          *Proto
		ch         *Channel
		least      time.Duration
		last       = time.Now()
		vers       = make([]int32, r.options.BatchNum)
		operations = make([]int32, r.options.BatchNum)
		msgs       = make([][]byte, r.options.BatchNum)
	)
	if Debug {
		log.Debug("start room: %d goroutine", r.id)
	}
	for {
		if least = r.options.SignalTime - time.Now().Sub(last); least > 0 {
			select {
			case s = <-r.signal:
				if exit = (s != protoReady); exit {
					done = true
					break
				}
				// merge msgs
				if n == 0 {
					last = time.Now()
				}
				for {
					if n >= r.options.BatchNum {
						// msgs full
						done = true
						break
					}
					if p, err = r.proto.Get(); err != nil {
						// must be empty error
						break
					}
					vers[n] = int32(p.Ver)
					operations[n] = p.Operation
					msgs[n] = p.Body
					n++
					r.proto.GetAdv()
				}
			case <-time.After(least):
				done = true
				exit = false
			}
		} else {
			done = true
		}
		if done {
			if n > 0 {
				r.rLock.RLock()
				for ch, _ = range r.chs {
					// ignore error
					ch.PushMsgs(vers[:n], operations[:n], msgs[:n])
				}
				r.rLock.RUnlock()
				n = 0
			}
			done = false
			last = time.Now()
		}
		if exit {
			break
		}
	}
	if Debug {
		log.Debug("room: %d goroutine exit", r.id)
	}
}

// PushMsg push msg to the room.
func (r *Room) PushMsg(ver int16, operation int32, msg []byte) (err error) {
	var proto *Proto
	r.rLock.Lock()
	if proto, err = r.proto.Set(); err == nil {
		proto.Ver = ver
		proto.Operation = operation
		proto.Body = msg
		r.proto.SetAdv()
	}
	r.rLock.Unlock()
	select {
	case r.signal <- protoReady:
	default:
	}
	return
}

// Online get online number.
func (r *Room) Online() (o int) {
	r.rLock.RLock()
	o = len(r.chs)
	r.rLock.RUnlock()
	return
}

// Close close the room.
func (r *Room) Close() {
	var ch *Channel
	r.signal <- protoFinish
	r.rLock.RLock()
	for ch, _ = range r.chs {
		ch.Close()
	}
	r.rLock.RUnlock()
}
<|endoftext|>


<commit_before>package command

import (
	"fmt"
	"io"
	"math/rand"
	"os"
	"strings"
	"time"

	humanize "github.com/dustin/go-humanize"
	"github.com/hashicorp/nomad/api"
)

type FSCommand struct {
	Meta
}

func (f *FSCommand) Help() string {
	helpText := `
Usage: nomad fs <alloc-id> <path>

  fs displays either the contents of an allocation directory for the passed allocation,
  or displays the file at the given path. The path is relative to the root of the alloc
  dir and defaults to root if unspecified.

General Options:

  ` + generalOptionsUsage() + `

  -H
    Machine friendly output.

  -verbose
    Show full information.

  -job <job-id>
    Use a random allocation from a specified job-id.

  -stat
    Show file stat information instead of displaying the file, or listing the directory.

`
	return strings.TrimSpace(helpText)
}

func (f *FSCommand) Synopsis() string {
	return "Inspect the contents of an allocation directory"
}

func (f *FSCommand) Run(args []string) int {
	var verbose, machine, job, stat bool
	flags := f.Meta.FlagSet("fs-list", FlagSetClient)
	flags.Usage = func() { f.Ui.Output(f.Help()) }
	flags.BoolVar(&verbose, "verbose", false, "")
	flags.BoolVar(&machine, "H", false, "")
	flags.BoolVar(&job, "job", false, "")
	flags.BoolVar(&stat, "stat", false, "")

	if err := flags.Parse(args); err != nil {
		return 1
	}
	args = flags.Args()

	if len(args) < 1 {
		f.Ui.Error("allocation id or -job is required")
		return 1
	}

	path := "/"
	if len(args) == 2 {
		path = args[1]
	}

	client, err := f.Meta.Client()
	if err != nil {
		f.Ui.Error(fmt.Sprintf("Error initializing client: %v", err))
		return 1
	}

	// If -job is specified, use random allocation, otherwise use provided allocation
	allocID := args[0]
	if job {
		allocID, err = getRandomJobAlloc(client, args[0])
		if err != nil {
			f.Ui.Error(fmt.Sprintf("Error fetching allocations: %v", err))
			return 1
		}
	}

	// Truncate the id unless full length is requested
	length := shortId
	if verbose {
		length = fullId
	}
	// Query the allocation info
	if len(allocID) == 1 {
		f.Ui.Error(fmt.Sprintf("Alloc ID must contain at least two characters."))
		return 1
	}
	if len(allocID)%2 == 1 {
		// Identifiers must be of even length, so we strip off the last byte
		// to provide a consistent user experience.
		allocID = allocID[:len(allocID)-1]
	}

	allocs, _, err := client.Allocations().PrefixList(allocID)
	if err != nil {
		f.Ui.Error(fmt.Sprintf("Error querying allocation: %v", err))
		return 1
	}
	if len(allocs) == 0 {
		f.Ui.Error(fmt.Sprintf("No allocation(s) with prefix or id %q found", allocID))
		return 1
	}
	if len(allocs) > 1 {
		// Format the allocs
		out := make([]string, len(allocs)+1)
		out[0] = "ID|Eval ID|Job ID|Task Group|Desired Status|Client Status"
		for i, alloc := range allocs {
			out[i+1] = fmt.Sprintf("%s|%s|%s|%s|%s|%s",
				limit(alloc.ID, length),
				limit(alloc.EvalID, length),
				alloc.JobID,
				alloc.TaskGroup,
				alloc.DesiredStatus,
				alloc.ClientStatus,
			)
		}
		f.Ui.Output(fmt.Sprintf("Prefix matched multiple allocations\n\n%s", formatList(out)))
		return 0
	}
	// Prefix lookup matched a single allocation
	alloc, _, err := client.Allocations().Info(allocs[0].ID, nil)
	if err != nil {
		f.Ui.Error(fmt.Sprintf("Error querying allocation: %s", err))
		return 1
	}

	if alloc.DesiredStatus == "failed" {
		allocID := limit(alloc.ID, length)
		msg := fmt.Sprintf(`The allocation %q failed to be placed. To see the cause, run:
nomad alloc-status %s`, allocID, allocID)
		f.Ui.Error(msg)
		return 0
	}

	// Get file stat info
	file, _, err := client.AllocFS().Stat(alloc, path, nil)
	if err != nil {
		f.Ui.Error(err.Error())
		return 1
	}

	// If we want file stats, print those and exit.
	if stat {
		// Display the file information
		out := make([]string, 2)
		out[0] = "Mode|Size|Modified Time|Name"
		if file != nil {
			fn := file.Name
			if file.IsDir {
				fn = fmt.Sprintf("%s/", fn)
			}
			var size string
			if machine {
				size = fmt.Sprintf("%d", file.Size)
			} else {
				size = humanize.Bytes(uint64(file.Size))
			}
			out[1] = fmt.Sprintf("%s|%s|%s|%s", file.FileMode, size,
				formatTime(file.ModTime), fn)
		}
		f.Ui.Output(formatList(out))
		return 0
	}

	// Determine if the path is a file or a directory.
	if file.IsDir {
		// We have a directory, list it.
		files, _, err := client.AllocFS().List(alloc, path, nil)
		if err != nil {
			f.Ui.Error(fmt.Sprintf("Error listing alloc dir: %s", err))
			return 1
		}
		// Display the file information in a tabular format
		out := make([]string, len(files)+1)
		out[0] = "Mode|Size|Modfied Time|Name"
		for i, file := range files {
			fn := file.Name
			if file.IsDir {
				fn = fmt.Sprintf("%s/", fn)
			}
			var size string
			if machine {
				size = fmt.Sprintf("%d", file.Size)
			} else {
				size = humanize.Bytes(uint64(file.Size))
			}
			out[i+1] = fmt.Sprintf("%s|%s|%s|%s",
				file.FileMode,
				size,
				formatTime(file.ModTime),
				fn,
			)
		}
		f.Ui.Output(formatList(out))
	} else {
		// We have a file, cat it.
		r, _, err := client.AllocFS().Cat(alloc, path, nil)
		if err != nil {
			f.Ui.Error(fmt.Sprintf("Error reading file: %s", err))
			return 1
		}
		io.Copy(os.Stdout, r)
	}

	return 0
}

// Get Random Allocation ID from a known jobID. Prefer to use a running allocation,
// but use a dead allocation if no running allocations are found
func getRandomJobAlloc(client *api.Client, jobID string) (string, error) {
	var runningAllocs []*api.AllocationListStub
	allocs, _, err := client.Jobs().Allocations(jobID, nil)

	// Check that the job actually has allocations
	if len(allocs) == 0 {
		return "", fmt.Errorf("job %q doesn't exist or it has no allocations", jobID)
	}

	for _, v := range allocs {
		if v.ClientStatus == "running" {
			runningAllocs = append(runningAllocs, v)
		}
	}
	// If we don't have any allocations running, use dead allocations
	if len(runningAllocs) < 1 {
		runningAllocs = allocs
	}

	r := rand.New(rand.NewSource(time.Now().UnixNano()))
	allocID := runningAllocs[r.Intn(len(runningAllocs))].ID
	return allocID, err
}
<commit_msg>improve help message for fs missing input<commit_after>package command

import (
	"fmt"
	"io"
	"math/rand"
	"os"
	"strings"
	"time"

	humanize "github.com/dustin/go-humanize"
	"github.com/hashicorp/nomad/api"
)

type FSCommand struct {
	Meta
}

func (f *FSCommand) Help() string {
	helpText := `
Usage: nomad fs <alloc-id> <path>

  fs displays either the contents of an allocation directory for the passed allocation,
  or displays the file at the given path. The path is relative to the root of the alloc
  dir and defaults to root if unspecified.

General Options:

  ` + generalOptionsUsage() + `

  -H
    Machine friendly output.

  -verbose
    Show full information.

  -job <job-id>
    Use a random allocation from a specified job-id.

  -stat
    Show file stat information instead of displaying the file, or listing the directory.

`
	return strings.TrimSpace(helpText)
}

func (f *FSCommand) Synopsis() string {
	return "Inspect the contents of an allocation directory"
}

func (f *FSCommand) Run(args []string) int {
	var verbose, machine, job, stat bool
	flags := f.Meta.FlagSet("fs-list", FlagSetClient)
	flags.Usage = func() { f.Ui.Output(f.Help()) }
	flags.BoolVar(&verbose, "verbose", false, "")
	flags.BoolVar(&machine, "H", false, "")
	flags.BoolVar(&job, "job", false, "")
	flags.BoolVar(&stat, "stat", false, "")

	if err := flags.Parse(args); err != nil {
		return 1
	}
	args = flags.Args()

	if len(args) < 1 {
		if job {
			f.Ui.Error("job ID is required")
		} else {
			f.Ui.Error("allocation ID is required")
		}

		return 1
	}

	path := "/"
	if len(args) == 2 {
		path = args[1]
	}

	client, err := f.Meta.Client()
	if err != nil {
		f.Ui.Error(fmt.Sprintf("Error initializing client: %v", err))
		return 1
	}

	// If -job is specified, use random allocation, otherwise use provided allocation
	allocID := args[0]
	if job {
		allocID, err = getRandomJobAlloc(client, args[0])
		if err != nil {
			f.Ui.Error(fmt.Sprintf("Error fetching allocations: %v", err))
			return 1
		}
	}

	// Truncate the id unless full length is requested
	length := shortId
	if verbose {
		length = fullId
	}
	// Query the allocation info
	if len(allocID) == 1 {
		f.Ui.Error(fmt.Sprintf("Alloc ID must contain at least two characters."))
		return 1
	}
	if len(allocID)%2 == 1 {
		// Identifiers must be of even length, so we strip off the last byte
		// to provide a consistent user experience.
		allocID = allocID[:len(allocID)-1]
	}

	allocs, _, err := client.Allocations().PrefixList(allocID)
	if err != nil {
		f.Ui.Error(fmt.Sprintf("Error querying allocation: %v", err))
		return 1
	}
	if len(allocs) == 0 {
		f.Ui.Error(fmt.Sprintf("No allocation(s) with prefix or id %q found", allocID))
		return 1
	}
	if len(allocs) > 1 {
		// Format the allocs
		out := make([]string, len(allocs)+1)
		out[0] = "ID|Eval ID|Job ID|Task Group|Desired Status|Client Status"
		for i, alloc := range allocs {
			out[i+1] = fmt.Sprintf("%s|%s|%s|%s|%s|%s",
				limit(alloc.ID, length),
				limit(alloc.EvalID, length),
				alloc.JobID,
				alloc.TaskGroup,
				alloc.DesiredStatus,
				alloc.ClientStatus,
			)
		}
		f.Ui.Output(fmt.Sprintf("Prefix matched multiple allocations\n\n%s", formatList(out)))
		return 0
	}
	// Prefix lookup matched a single allocation
	alloc, _, err := client.Allocations().Info(allocs[0].ID, nil)
	if err != nil {
		f.Ui.Error(fmt.Sprintf("Error querying allocation: %s", err))
		return 1
	}

	if alloc.DesiredStatus == "failed" {
		allocID := limit(alloc.ID, length)
		msg := fmt.Sprintf(`The allocation %q failed to be placed. To see the cause, run:
nomad alloc-status %s`, allocID, allocID)
		f.Ui.Error(msg)
		return 0
	}

	// Get file stat info
	file, _, err := client.AllocFS().Stat(alloc, path, nil)
	if err != nil {
		f.Ui.Error(err.Error())
		return 1
	}

	// If we want file stats, print those and exit.
	if stat {
		// Display the file information
		out := make([]string, 2)
		out[0] = "Mode|Size|Modified Time|Name"
		if file != nil {
			fn := file.Name
			if file.IsDir {
				fn = fmt.Sprintf("%s/", fn)
			}
			var size string
			if machine {
				size = fmt.Sprintf("%d", file.Size)
			} else {
				size = humanize.Bytes(uint64(file.Size))
			}
			out[1] = fmt.Sprintf("%s|%s|%s|%s", file.FileMode, size,
				formatTime(file.ModTime), fn)
		}
		f.Ui.Output(formatList(out))
		return 0
	}

	// Determine if the path is a file or a directory.
	if file.IsDir {
		// We have a directory, list it.
		files, _, err := client.AllocFS().List(alloc, path, nil)
		if err != nil {
			f.Ui.Error(fmt.Sprintf("Error listing alloc dir: %s", err))
			return 1
		}
		// Display the file information in a tabular format
		out := make([]string, len(files)+1)
		out[0] = "Mode|Size|Modfied Time|Name"
		for i, file := range files {
			fn := file.Name
			if file.IsDir {
				fn = fmt.Sprintf("%s/", fn)
			}
			var size string
			if machine {
				size = fmt.Sprintf("%d", file.Size)
			} else {
				size = humanize.Bytes(uint64(file.Size))
			}
			out[i+1] = fmt.Sprintf("%s|%s|%s|%s",
				file.FileMode,
				size,
				formatTime(file.ModTime),
				fn,
			)
		}
		f.Ui.Output(formatList(out))
	} else {
		// We have a file, cat it.
		r, _, err := client.AllocFS().Cat(alloc, path, nil)
		if err != nil {
			f.Ui.Error(fmt.Sprintf("Error reading file: %s", err))
			return 1
		}
		io.Copy(os.Stdout, r)
	}

	return 0
}

// Get Random Allocation ID from a known jobID. Prefer to use a running allocation,
// but use a dead allocation if no running allocations are found
func getRandomJobAlloc(client *api.Client, jobID string) (string, error) {
	var runningAllocs []*api.AllocationListStub
	allocs, _, err := client.Jobs().Allocations(jobID, nil)

	// Check that the job actually has allocations
	if len(allocs) == 0 {
		return "", fmt.Errorf("job %q doesn't exist or it has no allocations", jobID)
	}

	for _, v := range allocs {
		if v.ClientStatus == "running" {
			runningAllocs = append(runningAllocs, v)
		}
	}
	// If we don't have any allocations running, use dead allocations
	if len(runningAllocs) < 1 {
		runningAllocs = allocs
	}

	r := rand.New(rand.NewSource(time.Now().UnixNano()))
	allocID := runningAllocs[r.Intn(len(runningAllocs))].ID
	return allocID, err
}
<|endoftext|>


<commit_before>// Copyright 2014 Marc-Antoine Ruel. All rights reserved.
// Use of this source code is governed under the Apache License, Version 2.0
// that can be found in the LICENSE file.

package editor

import (
	"io/ioutil"
	"log"
	"testing"

	"github.com/maruel/ut"
	"github.com/maruel/wi/wicore"
	"github.com/maruel/wi/wicore/colors"
	"github.com/maruel/wi/wicore/raster"
)

func init() {
	// TODO(maruel): This has persistent side-effect. Figure out how to handle
	// "log" properly. Likely by using the same mechanism as used in package
	// "subcommands".
	log.SetOutput(ioutil.Discard)
}

// TODO(maruel): Add a test with small display (10x2) and ensure it's somewhat
// usable.

func keepLog(t *testing.T) func() {
	out := ut.NewWriter(t)
	log.SetOutput(out)
	return func() {
		log.SetOutput(ioutil.Discard)
		_ = out.Close()
	}
}

func compareBuffers(t *testing.T, expected *raster.Buffer, actual *raster.Buffer) {
	ut.AssertEqual(t, expected.Height, actual.Height)
	ut.AssertEqual(t, expected.Width, actual.Width)
	// First compares lines of text, then colors.
	for l := 0; l < expected.Height; l++ {
		e := expected.Line(l)
		a := actual.Line(l)
		ut.AssertEqualIndex(t, l, string(e.Runes()), string(a.Runes()))
		ut.AssertEqualIndex(t, l, e.Formats(), a.Formats())
	}
}

func TestMainImmediateQuit(t *testing.T) {
	defer keepLog(t)()

	terminal := NewTerminalFake(80, 25, []TerminalEvent{})
	editor, err := MakeEditor(terminal, true)
	ut.AssertEqual(t, nil, err)
	closeEditor := func() {
		_ = editor.Close()
	}
	defer closeEditor()

	wicore.PostCommand(editor, nil, "editor_bootstrap_ui")
	wicore.PostCommand(editor, nil, "new")
	// Supporting this command requires using "go test -tags debug"
	// wicore.PostCommand(editor, nil, "log_all")
	wicore.PostCommand(editor, nil, "editor_quit")
	ut.AssertEqual(t, 0, editor.EventLoop())

	expected := raster.NewBuffer(80, 25)
	expected.Fill(raster.MakeCell(' ', colors.BrightYellow, colors.Black))
	expected.DrawString("Dummy content", 0, 0, raster.CellFormat{Fg: colors.BrightYellow, Bg: colors.Black})
	expected.DrawString("Really", 0, 1, raster.CellFormat{Fg: colors.BrightYellow, Bg: colors.Black})
	expected.DrawString("Status Name    Normal                                            0,0            ", 0, 24, raster.CellFormat{Fg: colors.Red, Bg: colors.LightGray})
	expected.Cell(0, 0).F.Bg = colors.White
	expected.Cell(0, 0).F.Fg = colors.Black
	compareBuffers(t, expected, terminal.Buffer)
}

func TestMainInvalidThenQuit(t *testing.T) {
	defer keepLog(t)()

	terminal := NewTerminalFake(80, 25, []TerminalEvent{})
	editor, err := MakeEditor(terminal, true)
	ut.AssertEqual(t, nil, err)
	closeEditor := func() {
		_ = editor.Close()
	}
	defer closeEditor()

	wicore.PostCommand(editor, nil, "editor_bootstrap_ui")
	wicore.PostCommand(editor, nil, "invalid")
	wicore.PostCommand(editor, nil, "editor_quit")
	ut.AssertEqual(t, 0, editor.EventLoop())

	expected := raster.NewBuffer(80, 25)
	expected.Fill(raster.MakeCell(' ', colors.Red, colors.Black))
	expected.DrawString("Root", 0, 0, raster.CellFormat{Fg: colors.Red, Bg: colors.Black})
	expected.DrawString("Status Name    Normal                                            Status Position", 0, 24, raster.CellFormat{Fg: colors.Red, Bg: colors.LightGray})
	compareBuffers(t, expected, terminal.Buffer)
}
<commit_msg>Address maruel's comments.<commit_after>// Copyright 2014 Marc-Antoine Ruel. All rights reserved.
// Use of this source code is governed under the Apache License, Version 2.0
// that can be found in the LICENSE file.

package editor

import (
	"io/ioutil"
	"log"
	"testing"

	"github.com/maruel/ut"
	"github.com/maruel/wi/wicore"
	"github.com/maruel/wi/wicore/colors"
	"github.com/maruel/wi/wicore/raster"
)

func init() {
	// TODO(maruel): This has persistent side-effect. Figure out how to handle
	// "log" properly. Likely by using the same mechanism as used in package
	// "subcommands".
	log.SetOutput(ioutil.Discard)
}

// TODO(maruel): Add a test with small display (10x2) and ensure it's somewhat
// usable.

func keepLog(t *testing.T) func() {
	out := ut.NewWriter(t)
	log.SetOutput(out)
	return func() {
		log.SetOutput(ioutil.Discard)
		_ = out.Close()
	}
}

func compareBuffers(t *testing.T, expected *raster.Buffer, actual *raster.Buffer) {
	ut.AssertEqual(t, expected.Height, actual.Height)
	ut.AssertEqual(t, expected.Width, actual.Width)
	// First compares lines of text, then colors.
	for l := 0; l < expected.Height; l++ {
		e := expected.Line(l)
		a := actual.Line(l)
		ut.AssertEqualIndex(t, l, string(e.Runes()), string(a.Runes()))
		ut.AssertEqualIndex(t, l, e.Formats(), a.Formats())
	}
}

func TestMainImmediateQuit(t *testing.T) {
	defer keepLog(t)()

	terminal := NewTerminalFake(80, 25, []TerminalEvent{})
	editor, err := MakeEditor(terminal, true)
	ut.AssertEqual(t, nil, err)
	defer func() {
		_ = editor.Close()
	}()

	wicore.PostCommand(editor, nil, "editor_bootstrap_ui")
	wicore.PostCommand(editor, nil, "new")
	// Supporting this command requires using "go test -tags debug"
	// wicore.PostCommand(editor, nil, "log_all")
	wicore.PostCommand(editor, nil, "editor_quit")
	ut.AssertEqual(t, 0, editor.EventLoop())

	expected := raster.NewBuffer(80, 25)
	expected.Fill(raster.MakeCell(' ', colors.BrightYellow, colors.Black))
	expected.DrawString("Dummy content", 0, 0, raster.CellFormat{Fg: colors.BrightYellow, Bg: colors.Black})
	expected.DrawString("Really", 0, 1, raster.CellFormat{Fg: colors.BrightYellow, Bg: colors.Black})
	expected.DrawString("Status Name    Normal                                            0,0            ", 0, 24, raster.CellFormat{Fg: colors.Red, Bg: colors.LightGray})
	expected.Cell(0, 0).F.Bg = colors.White
	expected.Cell(0, 0).F.Fg = colors.Black
	compareBuffers(t, expected, terminal.Buffer)
}

func TestMainInvalidThenQuit(t *testing.T) {
	defer keepLog(t)()

	terminal := NewTerminalFake(80, 25, []TerminalEvent{})
	editor, err := MakeEditor(terminal, true)
	ut.AssertEqual(t, nil, err)
	defer func() {
		_ = editor.Close()
	}()

	wicore.PostCommand(editor, nil, "editor_bootstrap_ui")
	wicore.PostCommand(editor, nil, "invalid")
	wicore.PostCommand(editor, nil, "editor_quit")
	ut.AssertEqual(t, 0, editor.EventLoop())

	expected := raster.NewBuffer(80, 25)
	expected.Fill(raster.MakeCell(' ', colors.Red, colors.Black))
	expected.DrawString("Root", 0, 0, raster.CellFormat{Fg: colors.Red, Bg: colors.Black})
	expected.DrawString("Status Name    Normal                                            Status Position", 0, 24, raster.CellFormat{Fg: colors.Red, Bg: colors.LightGray})
	compareBuffers(t, expected, terminal.Buffer)
}
<|endoftext|>