<commit_before>package cli

import (
	"testing"

	"command"
)

type testApp struct {
	cmdRoot           *command.CmdNode
	confRootCandidate *command.ConfNode
	confRootActive    *command.ConfNode
}

func (a testApp) CmdRoot() *command.CmdNode {
	return a.cmdRoot
}
func (a testApp) ConfRootCandidate() *command.ConfNode {
	return a.confRootCandidate
}
func (a testApp) ConfRootActive() *command.ConfNode {
	return a.confRootActive
}

type testClient struct {
	outputChannel chan string
}

func (c testClient) ConfigPath() string {
	return ""
}
func (c testClient) Status() int {
	return command.CONF
}
func (c testClient) ConfigPathSet(path string) {}
func (c testClient) Send(msg string)           {}
func (c testClient) Sendln(msg string)         {}
func (c testClient) SendlnNow(msg string)      {}
func (c testClient) InputQuit()                {}
func (c testClient) Output() chan<- string {
	return c.outputChannel
}

func TestConf(t *testing.T) {

	app := &testApp{
		cmdRoot:           &command.CmdNode{MinLevel: command.EXEC},
		confRootCandidate: &command.ConfNode{},
		confRootActive:    &command.ConfNode{},
	}

	root := app.cmdRoot
	cmdNone := command.CMD_NONE
	cmdConf := command.CMD_CONF

	cmdBogus := func(ctx command.ConfContext, node *command.CmdNode, line string, c command.CmdClient) {
	}

	command.CmdInstall(root, cmdConf, "interface {IFNAME} description {ANY}", command.CONF, command.HelperDescription, "Set interface description")
	command.CmdInstall(root, cmdConf, "interface {IFNAME} ipv4 address {IPADDR}", command.CONF, command.HelperIfaceAddr, "Assign IPv4 address to interface")
	command.CmdInstall(root, cmdConf, "interface {IFNAME} ipv6 address {IPADDR6}", command.CONF, cmdBogus, "Assign IPv6 address to interface")
	command.CmdInstall(root, cmdConf, "interface {IFNAME} shutdown", command.CONF, cmdBogus, "Disable interface")
	command.CmdInstall(root, cmdConf, "ip routing", command.CONF, cmdBogus, "Enable IP routing")
	command.CmdInstall(root, cmdConf, "hostname {HOSTNAME}", command.CONF, command.HelperHostname, "Assign hostname")
	command.CmdInstall(root, cmdNone, "no {ANY}", command.CONF, command.HelperNo, "Remove a configuration item")

	c := &testClient{outputChannel: make(chan string)}

	go func() {
		t.Logf("TestConf: starting output channel goroutine")
		for {
			line, ok := <-c.outputChannel
			if !ok {
				break
			}
			t.Logf("TestConf: read bogus client output channel: [%s]", line)
		}
		t.Logf("TestConf: exiting output channel goroutine")
	}()

	if err := dispatchCommand(app, "", c, command.CONF); err != nil {
		t.Errorf("empty command rejected: %v", err)
	}

	if err := dispatchCommand(app, "      ", c, command.CONF); err != nil {
		t.Errorf("blank command rejected: %v", err)
	}

	if err := dispatchCommand(app, "xxxxxx", c, command.CONF); err == nil {
		t.Errorf("bad command accepted")
	}

	dispatchCommand(app, "hostname nexthop-router", c, command.CONF)
	if host := hostname(app); host != "nexthop-router" {
		t.Errorf("bad hostname: %s", host)
	}

	dispatchCommand(app, "int eth0 desc  aa  bb   ccc", c, command.CONF)
	node, err := app.confRootCandidate.Get("interface eth0 description")
	if err != nil {
		t.Errorf("bad description: %v", err)
	}
	if node.Path != "interface eth0 description" {
		t.Errorf("bad description path: [%s]", node.Path)
	}
	if len(node.Value) != 1 {
		t.Errorf("bad description value count: %d", len(node.Value))
	}
	if node.Value[0] != " aa  bb   ccc" {
		t.Errorf("bad description value: [%s]", node.Value[0])
	}

	dispatchCommand(app, "no int eth0 desc xxxxxxx", c, command.CONF)
	node, err = app.confRootCandidate.Get("interface eth0 description")
	if node != nil || err == nil {
		t.Errorf("eth0 description should not be present: node=[%v] error=[%v]", node, err)
	}

	dispatchCommand(app, "int eth1 desc ddd   eee   fff ", c, command.CONF)
	node, err = app.confRootCandidate.Get("interface eth1 description")
	if err != nil {
		t.Errorf("bad description: %v", err)
	}
	if node.Path != "interface eth1 description" {
		t.Errorf("bad description path: [%s]", node.Path)
	}
	if len(node.Value) != 1 {
		t.Errorf("bad description value count: %d", len(node.Value))
	}
	if node.Value[0] != "ddd   eee   fff " {
		t.Errorf("bad description value: [%s]", node.Value[0])
	}

	dispatchCommand(app, "no int eth1 desc", c, command.CONF)
	node, err = app.confRootCandidate.Get("interface eth1 description")
	if node != nil || err == nil {
		t.Errorf("eth1 description should not be present: node=[%v] error=[%v]", node, err)
	}

	dispatchCommand(app, "int eth2 ipv4 addr 1", c, command.CONF)
	node, err = app.confRootCandidate.Get("interface eth2 ipv4 address")
	if err != nil {
		t.Errorf("bad eth2 address 1: %v", err)
		return
	}
	if len(node.Value) != 1 {
		t.Errorf("wrong number of eth2 addresses (expected=1): %d", len(node.Value))
	}
	dispatchCommand(app, "int eth2 ipv4 addr 2", c, command.CONF)
	if err != nil {
		t.Errorf("bad eth2 address 2: %v", err)
		return
	}
	if len(node.Value) != 2 {
		t.Errorf("wrong number of eth2 addresses (expected=2): %d", len(node.Value))
	}
	dispatchCommand(app, "int eth2 ipv4 addr 3", c, command.CONF)
	node, err = app.confRootCandidate.Get("interface eth2 ipv4 address")
	if err != nil {
		t.Errorf("bad eth2 address 3: %v", err)
		return
	}
	if len(node.Value) != 3 {
		t.Errorf("wrong number of eth2 addresses (expected=3): %d", len(node.Value))
	}
	dispatchCommand(app, "no int eth2 ipv4 addr 3", c, command.CONF)
	if len(node.Value) != 2 {
		t.Errorf("wrong number of eth2 addresses (expected=2): %d", len(node.Value))
	}
	dispatchCommand(app, "no int eth2 ipv4 addr", c, command.CONF)
	node, err = app.confRootCandidate.Get("interface eth2 ipv4 address")
	if node != nil || err == nil {
		t.Errorf("eth2 should not have address: node=[%v] error=[%v]", node, err)
	}

	dispatchCommand(app, "int eth3 ipv4 addr 1", c, command.CONF)
	dispatchCommand(app, "int eth3 ipv4 addr 2", c, command.CONF)
	dispatchCommand(app, "int eth3 ipv4 addr 3", c, command.CONF)
	dispatchCommand(app, "int eth3 ipv4 addr 4", c, command.CONF)
	node, err = app.confRootCandidate.Get("interface eth3 ipv4 address")
	if err != nil {
		t.Errorf("bad eth3 address: %v", err)
		return
	}
	if len(node.Value) != 4 {
		t.Errorf("wrong number of eth3 addresses (expected=4): %d", len(node.Value))
	}
	node, err = app.confRootCandidate.Get("interface eth3 ipv4")
	if err != nil {
		t.Errorf("eth3 should have ipv4: node=[%v] error=[%v]", node, err)
	}
	dispatchCommand(app, "no int eth3 ipv4", c, command.CONF)
	node, err = app.confRootCandidate.Get("interface eth3")
	if node != nil || err == nil {
		t.Errorf("eth3 should not have ipv4: node=[%v] error=[%v]", node, err)
	}

	dispatchCommand(app, "int eth4 ipv4 addr 1", c, command.CONF)
	dispatchCommand(app, "int eth4 desc abc", c, command.CONF)
	node, err = app.confRootCandidate.Get("interface eth4 ipv4 address")
	if err != nil {
		t.Errorf("bad eth4 address: %v", err)
		return
	}
	if len(node.Value) != 1 {
		t.Errorf("wrong number of eth4 addresses (expected=1): %d", len(node.Value))
	}
	node, err = app.confRootCandidate.Get("interface eth4 description")
	if err != nil {
		t.Errorf("bad eth4 description: %v", err)
		return
	}
	dispatchCommand(app, "no int eth4 ipv4 addr", c, command.CONF)
	node, err = app.confRootCandidate.Get("interface eth4 ipv4")
	if node != nil || err == nil {
		t.Errorf("eth4 should not have ipv4: node=[%v] error=[%v]", node, err)
		return
	}
	node, err = app.confRootCandidate.Get("interface eth4")
	if node == nil || err != nil {
		t.Errorf("eth4 should have ipv4: node=[%v] error=[%v]", node, err)
		return
	}

	close(c.outputChannel)
}
<commit_msg>Fix test messages.<commit_after>package cli

import (
	"testing"

	"command"
)

type testApp struct {
	cmdRoot           *command.CmdNode
	confRootCandidate *command.ConfNode
	confRootActive    *command.ConfNode
}

func (a testApp) CmdRoot() *command.CmdNode {
	return a.cmdRoot
}
func (a testApp) ConfRootCandidate() *command.ConfNode {
	return a.confRootCandidate
}
func (a testApp) ConfRootActive() *command.ConfNode {
	return a.confRootActive
}

type testClient struct {
	outputChannel chan string
}

func (c testClient) ConfigPath() string {
	return ""
}
func (c testClient) Status() int {
	return command.CONF
}
func (c testClient) ConfigPathSet(path string) {}
func (c testClient) Send(msg string)           {}
func (c testClient) Sendln(msg string)         {}
func (c testClient) SendlnNow(msg string)      {}
func (c testClient) InputQuit()                {}
func (c testClient) Output() chan<- string {
	return c.outputChannel
}

func TestConf(t *testing.T) {

	app := &testApp{
		cmdRoot:           &command.CmdNode{MinLevel: command.EXEC},
		confRootCandidate: &command.ConfNode{},
		confRootActive:    &command.ConfNode{},
	}

	root := app.cmdRoot
	cmdNone := command.CMD_NONE
	cmdConf := command.CMD_CONF

	cmdBogus := func(ctx command.ConfContext, node *command.CmdNode, line string, c command.CmdClient) {
	}

	command.CmdInstall(root, cmdConf, "interface {IFNAME} description {ANY}", command.CONF, command.HelperDescription, "Set interface description")
	command.CmdInstall(root, cmdConf, "interface {IFNAME} ipv4 address {IPADDR}", command.CONF, command.HelperIfaceAddr, "Assign IPv4 address to interface")
	command.CmdInstall(root, cmdConf, "interface {IFNAME} ipv6 address {IPADDR6}", command.CONF, cmdBogus, "Assign IPv6 address to interface")
	command.CmdInstall(root, cmdConf, "interface {IFNAME} shutdown", command.CONF, cmdBogus, "Disable interface")
	command.CmdInstall(root, cmdConf, "ip routing", command.CONF, cmdBogus, "Enable IP routing")
	command.CmdInstall(root, cmdConf, "hostname {HOSTNAME}", command.CONF, command.HelperHostname, "Assign hostname")
	command.CmdInstall(root, cmdNone, "no {ANY}", command.CONF, command.HelperNo, "Remove a configuration item")

	c := &testClient{outputChannel: make(chan string)}

	go func() {
		t.Logf("TestConf: starting output channel goroutine")
		for {
			line, ok := <-c.outputChannel
			if !ok {
				break
			}
			t.Logf("TestConf: read bogus client output channel: [%s]", line)
		}
		t.Logf("TestConf: exiting output channel goroutine")
	}()

	if err := dispatchCommand(app, "", c, command.CONF); err != nil {
		t.Errorf("empty command rejected: %v", err)
	}

	if err := dispatchCommand(app, "      ", c, command.CONF); err != nil {
		t.Errorf("blank command rejected: %v", err)
	}

	if err := dispatchCommand(app, "xxxxxx", c, command.CONF); err == nil {
		t.Errorf("bad command accepted")
	}

	dispatchCommand(app, "hostname nexthop-router", c, command.CONF)
	if host := hostname(app); host != "nexthop-router" {
		t.Errorf("bad hostname: %s", host)
	}

	dispatchCommand(app, "int eth0 desc  aa  bb   ccc", c, command.CONF)
	node, err := app.confRootCandidate.Get("interface eth0 description")
	if err != nil {
		t.Errorf("bad description: %v", err)
	}
	if node.Path != "interface eth0 description" {
		t.Errorf("bad description path: [%s]", node.Path)
	}
	if len(node.Value) != 1 {
		t.Errorf("bad description value count: %d", len(node.Value))
	}
	if node.Value[0] != " aa  bb   ccc" {
		t.Errorf("bad description value: [%s]", node.Value[0])
	}

	dispatchCommand(app, "no int eth0 desc xxxxxxx", c, command.CONF)
	node, err = app.confRootCandidate.Get("interface eth0 description")
	if node != nil || err == nil {
		t.Errorf("eth0 description should not be present: node=[%v] error=[%v]", node, err)
	}

	dispatchCommand(app, "int eth1 desc ddd   eee   fff ", c, command.CONF)
	node, err = app.confRootCandidate.Get("interface eth1 description")
	if err != nil {
		t.Errorf("bad description: %v", err)
	}
	if node.Path != "interface eth1 description" {
		t.Errorf("bad description path: [%s]", node.Path)
	}
	if len(node.Value) != 1 {
		t.Errorf("bad description value count: %d", len(node.Value))
	}
	if node.Value[0] != "ddd   eee   fff " {
		t.Errorf("bad description value: [%s]", node.Value[0])
	}

	dispatchCommand(app, "no int eth1 desc", c, command.CONF)
	node, err = app.confRootCandidate.Get("interface eth1 description")
	if node != nil || err == nil {
		t.Errorf("eth1 description should not be present: node=[%v] error=[%v]", node, err)
	}

	dispatchCommand(app, "int eth2 ipv4 addr 1", c, command.CONF)
	node, err = app.confRootCandidate.Get("interface eth2 ipv4 address")
	if err != nil {
		t.Errorf("bad eth2 address 1: %v", err)
		return
	}
	if len(node.Value) != 1 {
		t.Errorf("wrong number of eth2 addresses (expected=1): %d", len(node.Value))
	}
	dispatchCommand(app, "int eth2 ipv4 addr 2", c, command.CONF)
	if err != nil {
		t.Errorf("bad eth2 address 2: %v", err)
		return
	}
	if len(node.Value) != 2 {
		t.Errorf("wrong number of eth2 addresses (expected=2): %d", len(node.Value))
	}
	dispatchCommand(app, "int eth2 ipv4 addr 3", c, command.CONF)
	node, err = app.confRootCandidate.Get("interface eth2 ipv4 address")
	if err != nil {
		t.Errorf("bad eth2 address 3: %v", err)
		return
	}
	if len(node.Value) != 3 {
		t.Errorf("wrong number of eth2 addresses (expected=3): %d", len(node.Value))
	}
	dispatchCommand(app, "no int eth2 ipv4 addr 3", c, command.CONF)
	if len(node.Value) != 2 {
		t.Errorf("wrong number of eth2 addresses (expected=2): %d", len(node.Value))
	}
	dispatchCommand(app, "no int eth2 ipv4 addr", c, command.CONF)
	node, err = app.confRootCandidate.Get("interface eth2 ipv4 address")
	if node != nil || err == nil {
		t.Errorf("eth2 should not have address: node=[%v] error=[%v]", node, err)
	}

	dispatchCommand(app, "int eth3 ipv4 addr 1", c, command.CONF)
	dispatchCommand(app, "int eth3 ipv4 addr 2", c, command.CONF)
	dispatchCommand(app, "int eth3 ipv4 addr 3", c, command.CONF)
	dispatchCommand(app, "int eth3 ipv4 addr 4", c, command.CONF)
	node, err = app.confRootCandidate.Get("interface eth3 ipv4 address")
	if err != nil {
		t.Errorf("bad eth3 address: %v", err)
		return
	}
	if len(node.Value) != 4 {
		t.Errorf("wrong number of eth3 addresses (expected=4): %d", len(node.Value))
	}
	node, err = app.confRootCandidate.Get("interface eth3 ipv4")
	if err != nil {
		t.Errorf("eth3 should have ipv4: node=[%v] error=[%v]", node, err)
	}
	dispatchCommand(app, "no int eth3 ipv4", c, command.CONF)
	node, err = app.confRootCandidate.Get("interface eth3")
	if node != nil || err == nil {
		t.Errorf("eth3 should not exist: node=[%v] error=[%v]", node, err)
	}

	dispatchCommand(app, "int eth4 ipv4 addr 1", c, command.CONF)
	dispatchCommand(app, "int eth4 desc abc", c, command.CONF)
	node, err = app.confRootCandidate.Get("interface eth4 ipv4 address")
	if err != nil {
		t.Errorf("bad eth4 address: %v", err)
		return
	}
	if len(node.Value) != 1 {
		t.Errorf("wrong number of eth4 addresses (expected=1): %d", len(node.Value))
	}
	node, err = app.confRootCandidate.Get("interface eth4 description")
	if err != nil {
		t.Errorf("bad eth4 description: %v", err)
		return
	}
	dispatchCommand(app, "no int eth4 ipv4 addr", c, command.CONF)
	node, err = app.confRootCandidate.Get("interface eth4 ipv4")
	if node != nil || err == nil {
		t.Errorf("eth4 should not have ipv4: node=[%v] error=[%v]", node, err)
		return
	}
	node, err = app.confRootCandidate.Get("interface eth4")
	if node == nil || err != nil {
		t.Errorf("eth4 should exist: node=[%v] error=[%v]", node, err)
		return
	}

	close(c.outputChannel)
}
<|endoftext|>


<commit_before>package options

import (
	"fmt"
	"io"
	"log"
	"net/http"
	"os"
	"path/filepath"
	"strings"

	"path"

	"github.com/ingvagabund/cluster-capacity/pkg/apiserver/cache"
	"github.com/ingvagabund/cluster-capacity/pkg/framework"
	"github.com/ingvagabund/cluster-capacity/pkg/framework/store"
	"github.com/spf13/pflag"
	"k8s.io/kubernetes/pkg/api"
	"k8s.io/kubernetes/pkg/api/validation"
	clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
	"k8s.io/kubernetes/pkg/util/yaml"
	schedopt "k8s.io/kubernetes/plugin/cmd/kube-scheduler/app/options"
)

type ClusterCapacityConfig struct {
	Schedulers       []*schedopt.SchedulerServer
	Pod              *api.Pod
	KubeClient       clientset.Interface
	Options          *ClusterCapacityOptions
	DefaultScheduler *schedopt.SchedulerServer
	Reports          *cache.Cache
	ApiServerOptions *framework.ApiServerOptions
	ResourceStore    store.ResourceStore
}

type ClusterCapacityOptions struct {
	Master                     string
	Kubeconfig                 string
	SchedulerConfigFile        []string
	DefaultSchedulerConfigFile string
	MaxLimit                   int
	Verbose                    bool
	PodSpecFile                string
	Period                     int
	OutputFormat               string
	ApiserverConfigFile        string
	ResourceSpaceMode          string
}

func NewClusterCapacityConfig(opt *ClusterCapacityOptions) *ClusterCapacityConfig {
	return &ClusterCapacityConfig{
		Schedulers:       make([]*schedopt.SchedulerServer, 0),
		Options:          opt,
		DefaultScheduler: schedopt.NewSchedulerServer(),
	}
}

func NewClusterCapacityOptions() *ClusterCapacityOptions {
	return &ClusterCapacityOptions{}
}

func (s *ClusterCapacityOptions) AddFlags(fs *pflag.FlagSet) {
	fs.StringVar(&s.Master, "master", s.Master, "The address of the Kubernetes API server (overrides any value in kubeconfig)")
	fs.StringVar(&s.Kubeconfig, "kubeconfig", s.Kubeconfig, "Path to kubeconfig file with authorization and master location information.")
	fs.StringVar(&s.PodSpecFile, "podspec", s.PodSpecFile, "Path to JSON or YAML file containing pod definition.")
	fs.IntVar(&s.MaxLimit, "max-limit", 0, "Number of instances of pod to be scheduled after which analysis stops. By default unlimited.")

	//TODO(jchaloup): uncomment this line once the multi-schedulers are fully implemented
	//fs.StringArrayVar(&s.SchedulerConfigFile, "config", s.SchedulerConfigFile, "Paths to files containing scheduler configuration in JSON or YAML format")

	dir, err := filepath.Abs(filepath.Dir(os.Args[0]))
	if err != nil {
		log.Fatalf("Unable to get current directory: %v", err)
	}

	filepath := path.Join(dir, "config/default-scheduler.yaml")

	fs.StringVar(&s.DefaultSchedulerConfigFile, "default-config", filepath, "Path to JSON or YAML file containing scheduler configuration.")
	fs.StringVar(&s.ApiserverConfigFile, "apiserver-config", s.ApiserverConfigFile, "Path to JSON or YAML file containing apiserver configuration.")
	fs.StringVar(&s.ResourceSpaceMode, "resource-space-mode", "ResourceSpaceFull", "Resource space limitation. Defaults to ResourceSpaceFull. If set to ResourceSpacePartial, ResourceQuota admission is applied.")

	fs.BoolVar(&s.Verbose, "verbose", s.Verbose, "Verbose mode")
	fs.IntVar(&s.Period, "period", 0, "Number of seconds between cluster capacity checks, if period=0 cluster-capacity will be checked just once")
	fs.StringVarP(&s.OutputFormat, "output", "o", s.OutputFormat, "Output format. One of: json|yaml")
}

func parseApiServerOptions(path string) (*framework.ApiServerOptions, error) {
	filename, _ := filepath.Abs(path)
	config, err := os.Open(filename)
	if err != nil {
		return nil, fmt.Errorf("Failed to open config file: %v", err)
	}

	apiServerRunOptions := &framework.ApiServerOptions{}
	decoder := yaml.NewYAMLOrJSONDecoder(config, 4096)
	err = decoder.Decode(apiServerRunOptions)
	if err != nil {
		fmt.Printf("Error: %v\n", err)
	}

	return apiServerRunOptions, nil
}

func parseSchedulerConfig(path string) (*schedopt.SchedulerServer, error) {
	filename, _ := filepath.Abs(path)
	config, err := os.Open(filename)
	if err != nil {
		return nil, fmt.Errorf("Failed to open config file: %v", err)
	}

	newScheduler := schedopt.NewSchedulerServer()
	decoder := yaml.NewYAMLOrJSONDecoder(config, 4096)
	decoder.Decode(&(newScheduler.KubeSchedulerConfiguration))
	return newScheduler, nil
}

func (s *ClusterCapacityConfig) ParseAdditionalSchedulerConfigs() error {
	for _, config := range s.Options.SchedulerConfigFile {
		if config == "default-scheduler.yaml" {
			continue
		}
		newScheduler, err := parseSchedulerConfig(config)
		if err != nil {
			return err
		}
		newScheduler.Master = s.Options.Master
		newScheduler.Kubeconfig = s.Options.Kubeconfig
		s.Schedulers = append(s.Schedulers, newScheduler)
	}
	return nil
}

func (s *ClusterCapacityConfig) ParseAPISpec() error {
	var spec io.Reader
	var err error
	if strings.HasPrefix(s.Options.PodSpecFile, "http://") || strings.HasPrefix(s.Options.PodSpecFile, "https://") {
		response, err := http.Get(s.Options.PodSpecFile)
		if err != nil {
			return err
		}
		defer response.Body.Close()
		spec = response.Body
	} else {
		filename, _ := filepath.Abs(s.Options.PodSpecFile)
		spec, err = os.Open(filename)
		if err != nil {
			return fmt.Errorf("Failed to open config file: %v", err)
		}
	}

	decoder := yaml.NewYAMLOrJSONDecoder(spec, 4096)
	err = decoder.Decode(&(s.Pod))
	if err != nil {
		return fmt.Errorf("Failed to decode config file: %v", err)
	}

	if s.Pod.ObjectMeta.Namespace == "" {
		s.Pod.ObjectMeta.Namespace = "default"
	}

	if errs := validation.ValidatePod(s.Pod); len(errs) > 0 {
		var errStrs []string
		for _, err := range errs {
			errStrs = append(errStrs, fmt.Sprintf("%v: %v", err.Type, err.Field))
		}
		return fmt.Errorf("Invalid pod: %#v", strings.Join(errStrs, ", "))
	}
	return nil
}

func (s *ClusterCapacityConfig) SetDefaultScheduler() error {
	var err error
	s.DefaultScheduler, err = parseSchedulerConfig(s.Options.DefaultSchedulerConfigFile)
	if err != nil {
		return err
	}

	s.DefaultScheduler.Master = s.Options.Master
	s.DefaultScheduler.Kubeconfig = s.Options.Kubeconfig
	return nil
}

func (s *ClusterCapacityConfig) ParseApiServerConfig() error {
	if len(s.Options.ApiserverConfigFile) == 0 {
		s.ApiServerOptions = &framework.ApiServerOptions{}
		return nil
	}
	options, err := parseApiServerOptions(s.Options.ApiserverConfigFile)
	if err != nil {
		return err
	}
	s.ApiServerOptions = options
	return nil
}
<commit_msg>check if pod specification url based path exists<commit_after>package options

import (
	"fmt"
	"io"
	"log"
	"net/http"
	"os"
	"path/filepath"
	"strings"

	"path"

	"github.com/ingvagabund/cluster-capacity/pkg/apiserver/cache"
	"github.com/ingvagabund/cluster-capacity/pkg/framework"
	"github.com/ingvagabund/cluster-capacity/pkg/framework/store"
	"github.com/spf13/pflag"
	"k8s.io/kubernetes/pkg/api"
	"k8s.io/kubernetes/pkg/api/validation"
	clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
	"k8s.io/kubernetes/pkg/util/yaml"
	schedopt "k8s.io/kubernetes/plugin/cmd/kube-scheduler/app/options"
)

type ClusterCapacityConfig struct {
	Schedulers       []*schedopt.SchedulerServer
	Pod              *api.Pod
	KubeClient       clientset.Interface
	Options          *ClusterCapacityOptions
	DefaultScheduler *schedopt.SchedulerServer
	Reports          *cache.Cache
	ApiServerOptions *framework.ApiServerOptions
	ResourceStore    store.ResourceStore
}

type ClusterCapacityOptions struct {
	Master                     string
	Kubeconfig                 string
	SchedulerConfigFile        []string
	DefaultSchedulerConfigFile string
	MaxLimit                   int
	Verbose                    bool
	PodSpecFile                string
	Period                     int
	OutputFormat               string
	ApiserverConfigFile        string
	ResourceSpaceMode          string
}

func NewClusterCapacityConfig(opt *ClusterCapacityOptions) *ClusterCapacityConfig {
	return &ClusterCapacityConfig{
		Schedulers:       make([]*schedopt.SchedulerServer, 0),
		Options:          opt,
		DefaultScheduler: schedopt.NewSchedulerServer(),
	}
}

func NewClusterCapacityOptions() *ClusterCapacityOptions {
	return &ClusterCapacityOptions{}
}

func (s *ClusterCapacityOptions) AddFlags(fs *pflag.FlagSet) {
	fs.StringVar(&s.Master, "master", s.Master, "The address of the Kubernetes API server (overrides any value in kubeconfig)")
	fs.StringVar(&s.Kubeconfig, "kubeconfig", s.Kubeconfig, "Path to kubeconfig file with authorization and master location information.")
	fs.StringVar(&s.PodSpecFile, "podspec", s.PodSpecFile, "Path to JSON or YAML file containing pod definition.")
	fs.IntVar(&s.MaxLimit, "max-limit", 0, "Number of instances of pod to be scheduled after which analysis stops. By default unlimited.")

	//TODO(jchaloup): uncomment this line once the multi-schedulers are fully implemented
	//fs.StringArrayVar(&s.SchedulerConfigFile, "config", s.SchedulerConfigFile, "Paths to files containing scheduler configuration in JSON or YAML format")

	dir, err := filepath.Abs(filepath.Dir(os.Args[0]))
	if err != nil {
		log.Fatalf("Unable to get current directory: %v", err)
	}

	filepath := path.Join(dir, "config/default-scheduler.yaml")

	fs.StringVar(&s.DefaultSchedulerConfigFile, "default-config", filepath, "Path to JSON or YAML file containing scheduler configuration.")
	fs.StringVar(&s.ApiserverConfigFile, "apiserver-config", s.ApiserverConfigFile, "Path to JSON or YAML file containing apiserver configuration.")
	fs.StringVar(&s.ResourceSpaceMode, "resource-space-mode", "ResourceSpaceFull", "Resource space limitation. Defaults to ResourceSpaceFull. If set to ResourceSpacePartial, ResourceQuota admission is applied.")

	fs.BoolVar(&s.Verbose, "verbose", s.Verbose, "Verbose mode")
	fs.IntVar(&s.Period, "period", 0, "Number of seconds between cluster capacity checks, if period=0 cluster-capacity will be checked just once")
	fs.StringVarP(&s.OutputFormat, "output", "o", s.OutputFormat, "Output format. One of: json|yaml")
}

func parseApiServerOptions(path string) (*framework.ApiServerOptions, error) {
	filename, _ := filepath.Abs(path)
	config, err := os.Open(filename)
	if err != nil {
		return nil, fmt.Errorf("Failed to open config file: %v", err)
	}

	apiServerRunOptions := &framework.ApiServerOptions{}
	decoder := yaml.NewYAMLOrJSONDecoder(config, 4096)
	err = decoder.Decode(apiServerRunOptions)
	if err != nil {
		fmt.Printf("Error: %v\n", err)
	}

	return apiServerRunOptions, nil
}

func parseSchedulerConfig(path string) (*schedopt.SchedulerServer, error) {
	filename, _ := filepath.Abs(path)
	config, err := os.Open(filename)
	if err != nil {
		return nil, fmt.Errorf("Failed to open config file: %v", err)
	}

	newScheduler := schedopt.NewSchedulerServer()
	decoder := yaml.NewYAMLOrJSONDecoder(config, 4096)
	decoder.Decode(&(newScheduler.KubeSchedulerConfiguration))
	return newScheduler, nil
}

func (s *ClusterCapacityConfig) ParseAdditionalSchedulerConfigs() error {
	for _, config := range s.Options.SchedulerConfigFile {
		if config == "default-scheduler.yaml" {
			continue
		}
		newScheduler, err := parseSchedulerConfig(config)
		if err != nil {
			return err
		}
		newScheduler.Master = s.Options.Master
		newScheduler.Kubeconfig = s.Options.Kubeconfig
		s.Schedulers = append(s.Schedulers, newScheduler)
	}
	return nil
}

func (s *ClusterCapacityConfig) ParseAPISpec() error {
	var spec io.Reader
	var err error
	if strings.HasPrefix(s.Options.PodSpecFile, "http://") || strings.HasPrefix(s.Options.PodSpecFile, "https://") {
		response, err := http.Get(s.Options.PodSpecFile)
		if err != nil {
			return err
		}
		defer response.Body.Close()
		if response.StatusCode != http.StatusOK {
			return fmt.Errorf("unable to read URL %q, server reported %v, status code=%v", s.Options.PodSpecFile, response.Status, response.StatusCode)
		}
		spec = response.Body
	} else {
		filename, _ := filepath.Abs(s.Options.PodSpecFile)
		spec, err = os.Open(filename)
		if err != nil {
			return fmt.Errorf("Failed to open config file: %v", err)
		}
	}

	decoder := yaml.NewYAMLOrJSONDecoder(spec, 4096)
	err = decoder.Decode(&(s.Pod))
	if err != nil {
		return fmt.Errorf("Failed to decode config file: %v", err)
	}

	if s.Pod.ObjectMeta.Namespace == "" {
		s.Pod.ObjectMeta.Namespace = "default"
	}

	if errs := validation.ValidatePod(s.Pod); len(errs) > 0 {
		var errStrs []string
		for _, err := range errs {
			errStrs = append(errStrs, fmt.Sprintf("%v: %v", err.Type, err.Field))
		}
		return fmt.Errorf("Invalid pod: %#v", strings.Join(errStrs, ", "))
	}
	return nil
}

func (s *ClusterCapacityConfig) SetDefaultScheduler() error {
	var err error
	s.DefaultScheduler, err = parseSchedulerConfig(s.Options.DefaultSchedulerConfigFile)
	if err != nil {
		return err
	}

	s.DefaultScheduler.Master = s.Options.Master
	s.DefaultScheduler.Kubeconfig = s.Options.Kubeconfig
	return nil
}

func (s *ClusterCapacityConfig) ParseApiServerConfig() error {
	if len(s.Options.ApiserverConfigFile) == 0 {
		s.ApiServerOptions = &framework.ApiServerOptions{}
		return nil
	}
	options, err := parseApiServerOptions(s.Options.ApiserverConfigFile)
	if err != nil {
		return err
	}
	s.ApiServerOptions = options
	return nil
}
<|endoftext|>


<commit_before>package cli

import (
	"testing"

	"command"
)

type testApp struct {
	cmdRoot           *command.CmdNode
	confRootCandidate *command.ConfNode
	confRootActive    *command.ConfNode
}

func (a testApp) CmdRoot() *command.CmdNode {
	return a.cmdRoot
}
func (a testApp) ConfRootCandidate() *command.ConfNode {
	return a.confRootCandidate
}
func (a testApp) ConfRootActive() *command.ConfNode {
	return a.confRootActive
}

type testClient struct {
	outputChannel chan string
}

func (c testClient) ConfigPath() string {
	return ""
}
func (c testClient) Status() int {
	return command.CONF
}
func (c testClient) ConfigPathSet(path string) {}
func (c testClient) Send(msg string)           {}
func (c testClient) Sendln(msg string)         {}
func (c testClient) SendlnNow(msg string)      {}
func (c testClient) InputQuit()                {}
func (c testClient) Output() chan<- string {
	return c.outputChannel
}

func TestConf(t *testing.T) {

	app := &testApp{
		cmdRoot:           &command.CmdNode{MinLevel: command.EXEC},
		confRootCandidate: &command.ConfNode{},
		confRootActive:    &command.ConfNode{},
	}

	root := app.cmdRoot
	cmdNone := command.CMD_NONE
	cmdConf := command.CMD_CONF

	cmdBogus := func(ctx command.ConfContext, node *command.CmdNode, line string, c command.CmdClient) {
	}

	command.CmdInstall(root, cmdConf, "interface {IFNAME} description {ANY}", command.CONF, command.HelperDescription, "Set interface description")
	command.CmdInstall(root, cmdConf, "interface {IFNAME} ipv4 address {IPADDR}", command.CONF, command.HelperIfaceAddr, "Assign IPv4 address to interface")
	command.CmdInstall(root, cmdConf, "interface {IFNAME} ipv6 address {IPADDR6}", command.CONF, cmdBogus, "Assign IPv6 address to interface")
	command.CmdInstall(root, cmdConf, "interface {IFNAME} shutdown", command.CONF, cmdBogus, "Disable interface")
	command.CmdInstall(root, cmdConf, "ip routing", command.CONF, cmdBogus, "Enable IP routing")
	command.CmdInstall(root, cmdConf, "hostname {HOSTNAME}", command.CONF, command.HelperHostname, "Assign hostname")
	command.CmdInstall(root, cmdNone, "no {ANY}", command.CONF, command.HelperNo, "Remove a configuration item")

	c := &testClient{outputChannel: make(chan string)}

	go func() {
		t.Logf("TestConf: starting output channel goroutine")
		for {
			line, ok := <-c.outputChannel
			if !ok {
				break
			}
			t.Logf("TestConf: read bogus client output channel: [%s]", line)
		}
		t.Logf("TestConf: exiting output channel goroutine")
	}()

	if err := dispatchCommand(app, "", c, command.CONF); err != nil {
		t.Errorf("empty command rejected: %v", err)
	}

	if err := dispatchCommand(app, "      ", c, command.CONF); err != nil {
		t.Errorf("blank command rejected: %v", err)
	}

	if err := dispatchCommand(app, "xxxxxx", c, command.CONF); err == nil {
		t.Errorf("bad command accepted")
	}

	dispatchCommand(app, "hostname nexthop-router", c, command.CONF)
	if host := hostname(app); host != "nexthop-router" {
		t.Errorf("bad hostname: %s", host)
	}

	dispatchCommand(app, "int eth0 desc  aa  bb   ccc", c, command.CONF)
	node, err := app.confRootCandidate.Get("interface eth0 description")
	if err != nil {
		t.Errorf("bad description: %v", err)
	}
	if node.Path != "interface eth0 description" {
		t.Errorf("bad description path: [%s]", node.Path)
	}
	if len(node.Value) != 1 {
		t.Errorf("bad description value count: %d", len(node.Value))
	}
	if node.Value[0] != " aa  bb   ccc" {
		t.Errorf("bad description value: [%s]", node.Value[0])
	}

	dispatchCommand(app, "no int eth0 desc xxxxxxx", c, command.CONF)
	node, err = app.confRootCandidate.Get("interface eth0 description")
	if node != nil || err == nil {
		t.Errorf("eth0 description should not be present: node=[%v] error=[%v]", node, err)
	}

	dispatchCommand(app, "int eth1 desc ddd   eee   fff ", c, command.CONF)
	node, err = app.confRootCandidate.Get("interface eth1 description")
	if err != nil {
		t.Errorf("bad description: %v", err)
	}
	if node.Path != "interface eth1 description" {
		t.Errorf("bad description path: [%s]", node.Path)
	}
	if len(node.Value) != 1 {
		t.Errorf("bad description value count: %d", len(node.Value))
	}
	if node.Value[0] != "ddd   eee   fff " {
		t.Errorf("bad description value: [%s]", node.Value[0])
	}

	dispatchCommand(app, "no int eth1 desc", c, command.CONF)
	node, err = app.confRootCandidate.Get("interface eth1 description")
	if node != nil || err == nil {
		t.Errorf("eth1 description should not be present: node=[%v] error=[%v]", node, err)
	}

	dispatchCommand(app, "int eth2 ipv4 addr 1", c, command.CONF)
	node, err = app.confRootCandidate.Get("interface eth2 ipv4 address")
	if err != nil {
		t.Errorf("bad eth2 address 1: %v", err)
		return
	}
	if len(node.Value) != 1 {
		t.Errorf("wrong number of eth2 addresses (expected=1): %d", len(node.Value))
	}
	dispatchCommand(app, "int eth2 ipv4 addr 2", c, command.CONF)
	if err != nil {
		t.Errorf("bad eth2 address 2: %v", err)
		return
	}
	if len(node.Value) != 2 {
		t.Errorf("wrong number of eth2 addresses (expected=2): %d", len(node.Value))
	}
	dispatchCommand(app, "int eth2 ipv4 addr 3", c, command.CONF)
	node, err = app.confRootCandidate.Get("interface eth2 ipv4 address")
	if err != nil {
		t.Errorf("bad eth2 address 3: %v", err)
		return
	}
	if len(node.Value) != 3 {
		t.Errorf("wrong number of eth2 addresses (expected=3): %d", len(node.Value))
	}
	dispatchCommand(app, "no int eth2 ipv4 addr 3", c, command.CONF)
	if len(node.Value) != 2 {
		t.Errorf("wrong number of eth2 addresses (expected=2): %d", len(node.Value))
	}
	dispatchCommand(app, "no int eth2 ipv4 addr", c, command.CONF)
	node, err = app.confRootCandidate.Get("interface eth2 ipv4 address")
	if node != nil || err == nil {
		t.Errorf("eth2 should not have address: node=[%v] error=[%v]", node, err)
	}

	close(c.outputChannel)
}
<commit_msg>Test removal of ipv4 addresses.<commit_after>package cli

import (
	"testing"

	"command"
)

type testApp struct {
	cmdRoot           *command.CmdNode
	confRootCandidate *command.ConfNode
	confRootActive    *command.ConfNode
}

func (a testApp) CmdRoot() *command.CmdNode {
	return a.cmdRoot
}
func (a testApp) ConfRootCandidate() *command.ConfNode {
	return a.confRootCandidate
}
func (a testApp) ConfRootActive() *command.ConfNode {
	return a.confRootActive
}

type testClient struct {
	outputChannel chan string
}

func (c testClient) ConfigPath() string {
	return ""
}
func (c testClient) Status() int {
	return command.CONF
}
func (c testClient) ConfigPathSet(path string) {}
func (c testClient) Send(msg string)           {}
func (c testClient) Sendln(msg string)         {}
func (c testClient) SendlnNow(msg string)      {}
func (c testClient) InputQuit()                {}
func (c testClient) Output() chan<- string {
	return c.outputChannel
}

func TestConf(t *testing.T) {

	app := &testApp{
		cmdRoot:           &command.CmdNode{MinLevel: command.EXEC},
		confRootCandidate: &command.ConfNode{},
		confRootActive:    &command.ConfNode{},
	}

	root := app.cmdRoot
	cmdNone := command.CMD_NONE
	cmdConf := command.CMD_CONF

	cmdBogus := func(ctx command.ConfContext, node *command.CmdNode, line string, c command.CmdClient) {
	}

	command.CmdInstall(root, cmdConf, "interface {IFNAME} description {ANY}", command.CONF, command.HelperDescription, "Set interface description")
	command.CmdInstall(root, cmdConf, "interface {IFNAME} ipv4 address {IPADDR}", command.CONF, command.HelperIfaceAddr, "Assign IPv4 address to interface")
	command.CmdInstall(root, cmdConf, "interface {IFNAME} ipv6 address {IPADDR6}", command.CONF, cmdBogus, "Assign IPv6 address to interface")
	command.CmdInstall(root, cmdConf, "interface {IFNAME} shutdown", command.CONF, cmdBogus, "Disable interface")
	command.CmdInstall(root, cmdConf, "ip routing", command.CONF, cmdBogus, "Enable IP routing")
	command.CmdInstall(root, cmdConf, "hostname {HOSTNAME}", command.CONF, command.HelperHostname, "Assign hostname")
	command.CmdInstall(root, cmdNone, "no {ANY}", command.CONF, command.HelperNo, "Remove a configuration item")

	c := &testClient{outputChannel: make(chan string)}

	go func() {
		t.Logf("TestConf: starting output channel goroutine")
		for {
			line, ok := <-c.outputChannel
			if !ok {
				break
			}
			t.Logf("TestConf: read bogus client output channel: [%s]", line)
		}
		t.Logf("TestConf: exiting output channel goroutine")
	}()

	if err := dispatchCommand(app, "", c, command.CONF); err != nil {
		t.Errorf("empty command rejected: %v", err)
	}

	if err := dispatchCommand(app, "      ", c, command.CONF); err != nil {
		t.Errorf("blank command rejected: %v", err)
	}

	if err := dispatchCommand(app, "xxxxxx", c, command.CONF); err == nil {
		t.Errorf("bad command accepted")
	}

	dispatchCommand(app, "hostname nexthop-router", c, command.CONF)
	if host := hostname(app); host != "nexthop-router" {
		t.Errorf("bad hostname: %s", host)
	}

	dispatchCommand(app, "int eth0 desc  aa  bb   ccc", c, command.CONF)
	node, err := app.confRootCandidate.Get("interface eth0 description")
	if err != nil {
		t.Errorf("bad description: %v", err)
	}
	if node.Path != "interface eth0 description" {
		t.Errorf("bad description path: [%s]", node.Path)
	}
	if len(node.Value) != 1 {
		t.Errorf("bad description value count: %d", len(node.Value))
	}
	if node.Value[0] != " aa  bb   ccc" {
		t.Errorf("bad description value: [%s]", node.Value[0])
	}

	dispatchCommand(app, "no int eth0 desc xxxxxxx", c, command.CONF)
	node, err = app.confRootCandidate.Get("interface eth0 description")
	if node != nil || err == nil {
		t.Errorf("eth0 description should not be present: node=[%v] error=[%v]", node, err)
	}

	dispatchCommand(app, "int eth1 desc ddd   eee   fff ", c, command.CONF)
	node, err = app.confRootCandidate.Get("interface eth1 description")
	if err != nil {
		t.Errorf("bad description: %v", err)
	}
	if node.Path != "interface eth1 description" {
		t.Errorf("bad description path: [%s]", node.Path)
	}
	if len(node.Value) != 1 {
		t.Errorf("bad description value count: %d", len(node.Value))
	}
	if node.Value[0] != "ddd   eee   fff " {
		t.Errorf("bad description value: [%s]", node.Value[0])
	}

	dispatchCommand(app, "no int eth1 desc", c, command.CONF)
	node, err = app.confRootCandidate.Get("interface eth1 description")
	if node != nil || err == nil {
		t.Errorf("eth1 description should not be present: node=[%v] error=[%v]", node, err)
	}

	dispatchCommand(app, "int eth2 ipv4 addr 1", c, command.CONF)
	node, err = app.confRootCandidate.Get("interface eth2 ipv4 address")
	if err != nil {
		t.Errorf("bad eth2 address 1: %v", err)
		return
	}
	if len(node.Value) != 1 {
		t.Errorf("wrong number of eth2 addresses (expected=1): %d", len(node.Value))
	}
	dispatchCommand(app, "int eth2 ipv4 addr 2", c, command.CONF)
	if err != nil {
		t.Errorf("bad eth2 address 2: %v", err)
		return
	}
	if len(node.Value) != 2 {
		t.Errorf("wrong number of eth2 addresses (expected=2): %d", len(node.Value))
	}
	dispatchCommand(app, "int eth2 ipv4 addr 3", c, command.CONF)
	node, err = app.confRootCandidate.Get("interface eth2 ipv4 address")
	if err != nil {
		t.Errorf("bad eth2 address 3: %v", err)
		return
	}
	if len(node.Value) != 3 {
		t.Errorf("wrong number of eth2 addresses (expected=3): %d", len(node.Value))
	}
	dispatchCommand(app, "no int eth2 ipv4 addr 3", c, command.CONF)
	if len(node.Value) != 2 {
		t.Errorf("wrong number of eth2 addresses (expected=2): %d", len(node.Value))
	}
	dispatchCommand(app, "no int eth2 ipv4 addr", c, command.CONF)
	node, err = app.confRootCandidate.Get("interface eth2 ipv4 address")
	if node != nil || err == nil {
		t.Errorf("eth2 should not have address: node=[%v] error=[%v]", node, err)
	}

	dispatchCommand(app, "int eth3 ipv4 addr 1", c, command.CONF)
	dispatchCommand(app, "int eth3 ipv4 addr 2", c, command.CONF)
	dispatchCommand(app, "int eth3 ipv4 addr 3", c, command.CONF)
	dispatchCommand(app, "int eth3 ipv4 addr 4", c, command.CONF)
	node, err = app.confRootCandidate.Get("interface eth3 ipv4 address")
	if err != nil {
		t.Errorf("bad eth3 address: %v", err)
		return
	}
	if len(node.Value) != 4 {
		t.Errorf("wrong number of eth3 addresses (expected=4): %d", len(node.Value))
	}
	node, err = app.confRootCandidate.Get("interface eth3 ipv4")
	if err != nil {
		t.Errorf("eth3 should have ipv4: node=[%v] error=[%v]", node, err)
	}
	dispatchCommand(app, "no int eth3 ipv4", c, command.CONF)
	node, err = app.confRootCandidate.Get("interface eth3 ipv4")
	if node != nil || err == nil {
		t.Errorf("eth3 should not have ipv4: node=[%v] error=[%v]", node, err)
	}

	close(c.outputChannel)
}
<|endoftext|>


<commit_before><commit_msg>Modified file path validation to check that all characters in the path are printable ASCII characters<commit_after><|endoftext|>


<commit_before><commit_msg>fix permission denied error for non existing projects (#17586) (#17619)<commit_after><|endoftext|>


<commit_before><commit_msg>Update where the semicolons end<commit_after><|endoftext|>


<commit_before>package main

import (
	"errors"
	"flag"
	"fmt"
	"os"
	"os/exec"
	"strconv"
	"strings"

	mp "github.com/mackerelio/go-mackerel-plugin"
	"github.com/mackerelio/mackerel-agent/logging"
)

var logger = logging.GetLogger("metrics.plugin.jvm")

type JVMPlugin struct {
	Target    string
	Lvmid     string
	JstatPath string
	JavaName  string
	Tempfile  string
}

// # jps
// 26547 NettyServer
// 6438 Jps
func FetchLvmidByAppname(appname, target, jpsPath string) (string, error) {
	out, err := exec.Command(jpsPath, target).Output()
	if err != nil {
		logger.Errorf("Failed to run exec jps. %s", err)
		return "", err
	}
	for _, line := range strings.Split(string(out), "\n") {
		words := strings.Split(line, " ")
		if len(words) != 2 {
			continue
		}
		lvmid, name := words[0], words[1]
		if name == appname {
			return lvmid, nil
		}
	}
	return "", errors.New(fmt.Sprintf("Cannot get lvmid from %s", appname))
}

func fetchJstatMetrics(lvmid, option, jstatPath string) (map[string]float64, error) {
	out, err := exec.Command(jstatPath, option, lvmid).Output()
	if err != nil {
		logger.Errorf("Failed to run exec jstat. %s", err)
		return nil, err
	}

	lines := strings.Split(string(out), "\n")
	keys := strings.Fields(lines[0])
	values := strings.Fields(lines[1])

	stat := make(map[string]float64)
	for i, key := range keys {
		value, err := strconv.ParseFloat(values[i], 64)
		if err != nil {
			logger.Warningf("Failed to parse value. %s", err)
		}
		stat[key] = value
	}

	return stat, nil
}

func mergeStat(dst, src map[string]float64) {
	for k, v := range src {
		dst[k] = v
	}
}

// # jstat -gc <vmid>
//  S0C    S1C    S0U    S1U      EC       EU        OC         OU       PC     PU    YGC     YGCT    FGC    FGCT     GCT
// 3584.0 3584.0 2528.0  0.0   692224.0 19062.4  1398272.0   485450.1  72704.0 72611.3   3152   30.229   0      0.000   30.229

// # jstat -gccapacity  <vmid>
//  NGCMN    NGCMX     NGC     S0C   S1C       EC      OGCMN      OGCMX       OGC         OC      PGCMN    PGCMX     PGC       PC     YGC    FGC
// 699392.0 699392.0 699392.0 4096.0 4096.0 691200.0  1398272.0  1398272.0  1398272.0  1398272.0  21504.0 524288.0  72704.0  72704.0   4212     0

// # jstat -gcnew  <vmid>
//  S0C    S1C    S0U    S1U   TT MTT  DSS      EC       EU     YGC     YGCT
// 3072.0 3072.0    0.0 2848.0  1  15 3072.0 693248.0 626782.2   3463   33.658

func (m JVMPlugin) FetchMetrics() (map[string]float64, error) {
	gcStat, err := fetchJstatMetrics(m.Lvmid, "-gc", m.JstatPath)
	if err != nil {
		return nil, err
	}
	gcCapacityStat, err := fetchJstatMetrics(m.Lvmid, "-gccapacity", m.JstatPath)
	if err != nil {
		return nil, err
	}
	gcNewStat, err := fetchJstatMetrics(m.Lvmid, "-gcnew", m.JstatPath)
	if err != nil {
		return nil, err
	}
	gcOldStat, err := fetchJstatMetrics(m.Lvmid, "-gcold", m.JstatPath)
	if err != nil {
		return nil, err
	}

	stat := make(map[string]float64)
	mergeStat(stat, gcStat)
	mergeStat(stat, gcCapacityStat)
	mergeStat(stat, gcNewStat)
	mergeStat(stat, gcOldStat)

	return stat, nil
}

func (m JVMPlugin) GraphDefinition() map[string](mp.Graphs) {
	rawJavaName := m.JavaName
	lowerJavaName := strings.ToLower(m.JavaName)
	return map[string](mp.Graphs){
		fmt.Sprintf("jvm.%s.gc_events", lowerJavaName): mp.Graphs{
			Label: fmt.Sprintf("JVM %s GC events", rawJavaName),
			Unit:  "integer",
			Metrics: [](mp.Metrics){
				mp.Metrics{Name: "YGC", Label: "Young GC event", Diff: true},
				mp.Metrics{Name: "FGC", Label: "Full GC event", Diff: true},
			},
		},
		fmt.Sprintf("jvm.%s.gc_time", lowerJavaName): mp.Graphs{
			Label: fmt.Sprintf("JVM %s GC time (msec)", rawJavaName),
			Unit:  "float",
			Metrics: [](mp.Metrics){
				mp.Metrics{Name: "YGCT", Label: "Young GC time", Diff: true},
				mp.Metrics{Name: "FGCT", Label: "Full GC time", Diff: true},
			},
		},
		fmt.Sprintf("jvm.%s.new_space", lowerJavaName): mp.Graphs{
			Label: fmt.Sprintf("JVM %s New Space memory (KB)", rawJavaName),
			Unit:  "float",
			Metrics: [](mp.Metrics){
				mp.Metrics{Name: "NGCMX", Label: "New max", Diff: false},
				mp.Metrics{Name: "NGC", Label: "New current", Diff: false},
				mp.Metrics{Name: "EU", Label: "Eden used", Diff: false},
				mp.Metrics{Name: "S0U", Label: "Survivor0 used", Diff: false},
				mp.Metrics{Name: "S1U", Label: "Survivor1 used", Diff: false},
			},
		},
		fmt.Sprintf("jvm.%s.old_space", lowerJavaName): mp.Graphs{
			Label: fmt.Sprintf("JVM %s Old Space memory (KB)", rawJavaName),
			Unit:  "float",
			Metrics: [](mp.Metrics){
				mp.Metrics{Name: "OGCMX", Label: "Old max", Diff: false},
				mp.Metrics{Name: "OGC", Label: "Old current", Diff: false},
				mp.Metrics{Name: "OU", Label: "Old used", Diff: false},
			},
		},
		fmt.Sprintf("jvm.%s.perm_space", lowerJavaName): mp.Graphs{
			Label: fmt.Sprintf("JVM %s Permanent Space (KB)", rawJavaName),
			Unit:  "float",
			Metrics: [](mp.Metrics){
				mp.Metrics{Name: "PGCMX", Label: "Perm max", Diff: false},
				mp.Metrics{Name: "PGC", Label: "Perm current", Diff: false},
				mp.Metrics{Name: "PU", Label: "Perm used", Diff: false},
			},
		},
	}
}

func main() {
	optHost := flag.String("host", "localhost", "Hostname")
	optPort := flag.String("port", "1099", "Port")
	optJstatPath := flag.String("jstatpath", "/usr/bin/jstat", "jstat path")
	optJpsPath := flag.String("jpspath", "/usr/bin/jps", "jps path")
	optJavaName := flag.String("javaname", "", "Java app name")
	optTempfile := flag.String("tempfile", "", "Temp file name")
	flag.Parse()

	if *optJavaName == "" {
		logger.Errorf("javaname is required")
		flag.PrintDefaults()
		os.Exit(1)
	}

	var jvm JVMPlugin
	jvm.Target = fmt.Sprintf("%s:%s", *optHost, *optPort)
	lvmid, err := FetchLvmidByAppname(*optJavaName, jvm.Target, *optJpsPath)
	if err != nil {
		logger.Errorf("Failed to fetch lvmid. %s", err)
		os.Exit(1)
	}
	jvm.Lvmid = lvmid
	jvm.JstatPath = *optJstatPath
	jvm.JavaName = *optJavaName

	helper := mp.NewMackerelPlugin(jvm)
	if *optTempfile != "" {
		helper.Tempfile = *optTempfile
	} else {
		helper.Tempfile = fmt.Sprintf("/tmp/mackerel-plugin-jvm-%s", *optHost)
	}

	if os.Getenv("MACKEREL_AGENT_PLUGIN_META") != "" {
		helper.OutputDefinitions()
	} else {
		helper.OutputValues()
	}
}
<commit_msg>Add -pidfile option<commit_after>package main

import (
	"errors"
	"flag"
	"fmt"
	"io/ioutil"
	"os"
	"os/exec"
	"strconv"
	"strings"

	mp "github.com/mackerelio/go-mackerel-plugin"
	"github.com/mackerelio/mackerel-agent/logging"
)

var logger = logging.GetLogger("metrics.plugin.jvm")

type JVMPlugin struct {
	Target    string
	Lvmid     string
	JstatPath string
	JavaName  string
	Tempfile  string
}

// # jps
// 26547 NettyServer
// 6438 Jps
func FetchLvmidByAppname(appname, target, jpsPath string) (string, error) {
	out, err := exec.Command(jpsPath, target).Output()
	if err != nil {
		logger.Errorf("Failed to run exec jps. %s", err)
		return "", err
	}
	for _, line := range strings.Split(string(out), "\n") {
		words := strings.Split(line, " ")
		if len(words) != 2 {
			continue
		}
		lvmid, name := words[0], words[1]
		if name == appname {
			return lvmid, nil
		}
	}
	return "", errors.New(fmt.Sprintf("Cannot get lvmid from %s", appname))
}

func fetchJstatMetrics(lvmid, option, jstatPath string) (map[string]float64, error) {
	out, err := exec.Command(jstatPath, option, lvmid).Output()
	if err != nil {
		logger.Errorf("Failed to run exec jstat. %s", err)
		return nil, err
	}

	lines := strings.Split(string(out), "\n")
	keys := strings.Fields(lines[0])
	values := strings.Fields(lines[1])

	stat := make(map[string]float64)
	for i, key := range keys {
		value, err := strconv.ParseFloat(values[i], 64)
		if err != nil {
			logger.Warningf("Failed to parse value. %s", err)
		}
		stat[key] = value
	}

	return stat, nil
}

func mergeStat(dst, src map[string]float64) {
	for k, v := range src {
		dst[k] = v
	}
}

// # jstat -gc <vmid>
//  S0C    S1C    S0U    S1U      EC       EU        OC         OU       PC     PU    YGC     YGCT    FGC    FGCT     GCT
// 3584.0 3584.0 2528.0  0.0   692224.0 19062.4  1398272.0   485450.1  72704.0 72611.3   3152   30.229   0      0.000   30.229

// # jstat -gccapacity  <vmid>
//  NGCMN    NGCMX     NGC     S0C   S1C       EC      OGCMN      OGCMX       OGC         OC      PGCMN    PGCMX     PGC       PC     YGC    FGC
// 699392.0 699392.0 699392.0 4096.0 4096.0 691200.0  1398272.0  1398272.0  1398272.0  1398272.0  21504.0 524288.0  72704.0  72704.0   4212     0

// # jstat -gcnew  <vmid>
//  S0C    S1C    S0U    S1U   TT MTT  DSS      EC       EU     YGC     YGCT
// 3072.0 3072.0    0.0 2848.0  1  15 3072.0 693248.0 626782.2   3463   33.658

func (m JVMPlugin) FetchMetrics() (map[string]float64, error) {
	gcStat, err := fetchJstatMetrics(m.Lvmid, "-gc", m.JstatPath)
	if err != nil {
		return nil, err
	}
	gcCapacityStat, err := fetchJstatMetrics(m.Lvmid, "-gccapacity", m.JstatPath)
	if err != nil {
		return nil, err
	}
	gcNewStat, err := fetchJstatMetrics(m.Lvmid, "-gcnew", m.JstatPath)
	if err != nil {
		return nil, err
	}
	gcOldStat, err := fetchJstatMetrics(m.Lvmid, "-gcold", m.JstatPath)
	if err != nil {
		return nil, err
	}

	stat := make(map[string]float64)
	mergeStat(stat, gcStat)
	mergeStat(stat, gcCapacityStat)
	mergeStat(stat, gcNewStat)
	mergeStat(stat, gcOldStat)

	return stat, nil
}

func (m JVMPlugin) GraphDefinition() map[string](mp.Graphs) {
	rawJavaName := m.JavaName
	lowerJavaName := strings.ToLower(m.JavaName)
	return map[string](mp.Graphs){
		fmt.Sprintf("jvm.%s.gc_events", lowerJavaName): mp.Graphs{
			Label: fmt.Sprintf("JVM %s GC events", rawJavaName),
			Unit:  "integer",
			Metrics: [](mp.Metrics){
				mp.Metrics{Name: "YGC", Label: "Young GC event", Diff: true},
				mp.Metrics{Name: "FGC", Label: "Full GC event", Diff: true},
			},
		},
		fmt.Sprintf("jvm.%s.gc_time", lowerJavaName): mp.Graphs{
			Label: fmt.Sprintf("JVM %s GC time (msec)", rawJavaName),
			Unit:  "float",
			Metrics: [](mp.Metrics){
				mp.Metrics{Name: "YGCT", Label: "Young GC time", Diff: true},
				mp.Metrics{Name: "FGCT", Label: "Full GC time", Diff: true},
			},
		},
		fmt.Sprintf("jvm.%s.new_space", lowerJavaName): mp.Graphs{
			Label: fmt.Sprintf("JVM %s New Space memory (KB)", rawJavaName),
			Unit:  "float",
			Metrics: [](mp.Metrics){
				mp.Metrics{Name: "NGCMX", Label: "New max", Diff: false},
				mp.Metrics{Name: "NGC", Label: "New current", Diff: false},
				mp.Metrics{Name: "EU", Label: "Eden used", Diff: false},
				mp.Metrics{Name: "S0U", Label: "Survivor0 used", Diff: false},
				mp.Metrics{Name: "S1U", Label: "Survivor1 used", Diff: false},
			},
		},
		fmt.Sprintf("jvm.%s.old_space", lowerJavaName): mp.Graphs{
			Label: fmt.Sprintf("JVM %s Old Space memory (KB)", rawJavaName),
			Unit:  "float",
			Metrics: [](mp.Metrics){
				mp.Metrics{Name: "OGCMX", Label: "Old max", Diff: false},
				mp.Metrics{Name: "OGC", Label: "Old current", Diff: false},
				mp.Metrics{Name: "OU", Label: "Old used", Diff: false},
			},
		},
		fmt.Sprintf("jvm.%s.perm_space", lowerJavaName): mp.Graphs{
			Label: fmt.Sprintf("JVM %s Permanent Space (KB)", rawJavaName),
			Unit:  "float",
			Metrics: [](mp.Metrics){
				mp.Metrics{Name: "PGCMX", Label: "Perm max", Diff: false},
				mp.Metrics{Name: "PGC", Label: "Perm current", Diff: false},
				mp.Metrics{Name: "PU", Label: "Perm used", Diff: false},
			},
		},
	}
}

func main() {
	optHost := flag.String("host", "localhost", "Hostname")
	optPort := flag.String("port", "1099", "Port")
	optJstatPath := flag.String("jstatpath", "/usr/bin/jstat", "jstat path")
	optJpsPath := flag.String("jpspath", "/usr/bin/jps", "jps path")
	optJavaName := flag.String("javaname", "", "Java app name")
	optPidFile := flag.String("pidfile", "", "pidfile path")
	optTempfile := flag.String("tempfile", "", "Temp file name")
	flag.Parse()

	var jvm JVMPlugin
	jvm.Target = fmt.Sprintf("%s:%s", *optHost, *optPort)
	jvm.JstatPath = *optJstatPath

	if *optJavaName == "" {
		// https://docs.oracle.com/javase/7/docs/technotes/tools/share/jps.html
		// `The lvmid is typically, but not necessarily, the operating system's process identifier for the JVM process.`
		pid, err := ioutil.ReadFile(*optPidFile)
		if err != nil {
			logger.Errorf("Failed to load pid. %s", err)
			os.Exit(1)
		}
		jvm.Lvmid = string(pid)
	} else {
		lvmid, err := FetchLvmidByAppname(*optJavaName, jvm.Target, *optJpsPath)
		if err != nil {
			logger.Errorf("Failed to fetch lvmid. %s", err)
			os.Exit(1)
		}
		jvm.Lvmid = lvmid
	}

	jvm.JavaName = *optJavaName

	helper := mp.NewMackerelPlugin(jvm)
	if *optTempfile != "" {
		helper.Tempfile = *optTempfile
	} else {
		helper.Tempfile = fmt.Sprintf("/tmp/mackerel-plugin-jvm-%s", *optHost)
	}

	if os.Getenv("MACKEREL_AGENT_PLUGIN_META") != "" {
		helper.OutputDefinitions()
	} else {
		helper.OutputValues()
	}
}
<|endoftext|>


<commit_before>// Copyright 2012 Jesse van den Kieboom. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

/*
Package flags provides an extensive command line option parser.
The flags package is similar in functionality to the go built-in flag package
but provides more options and uses reflection to provide a convenient and
succinct way of specifying command line options.

Supported features

The following features are supported in go-flags:

    Options with short names (-v)
    Options with long names (--verbose)
    Options with and without arguments (bool v.s. other type)
    Options with optional arguments and default values
    Option default values from ENVIRONMENT_VARIABLES, including slice and map values
    Multiple option groups each containing a set of options
    Generate and print well-formatted help message
    Passing remaining command line arguments after -- (optional)
    Ignoring unknown command line options (optional)
    Supports -I/usr/include -I=/usr/include -I /usr/include option argument specification
    Supports multiple short options -aux
    Supports all primitive go types (string, int{8..64}, uint{8..64}, float)
    Supports same option multiple times (can store in slice or last option counts)
    Supports maps
    Supports function callbacks
    Supports namespaces for (nested) option groups

Additional features specific to Windows:
    Options with short names (/v)
    Options with long names (/verbose)
    Windows-style options with arguments use a colon as the delimiter
    Modify generated help message with Windows-style / options

Basic usage

The flags package uses structs, reflection and struct field tags
to allow users to specify command line options. This results in very simple
and concise specification of your application options. For example:

    type Options struct {
        Verbose []bool `short:"v" long:"verbose" description:"Show verbose debug information"`
    }

This specifies one option with a short name -v and a long name --verbose.
When either -v or --verbose is found on the command line, a 'true' value
will be appended to the Verbose field. e.g. when specifying -vvv, the
resulting value of Verbose will be {[true, true, true]}.

Slice options work exactly the same as primitive type options, except that
whenever the option is encountered, a value is appended to the slice.

Map options from string to primitive type are also supported. On the command
line, you specify the value for such an option as key:value. For example

    type Options struct {
        AuthorInfo string[string] `short:"a"`
    }

Then, the AuthorInfo map can be filled with something like
-a name:Jesse -a "surname:van den Kieboom".

Finally, for full control over the conversion between command line argument
values and options, user defined types can choose to implement the Marshaler
and Unmarshaler interfaces.

Available field tags

The following is a list of tags for struct fields supported by go-flags:

    short:            the short name of the option (single character)
    long:             the long name of the option
    required:         whether an option is required to appear on the command
                      line. If a required option is not present, the parser will
                      return ErrRequired (optional)
    description:      the description of the option (optional)
    long-description: the long description of the option. Currently only
                      displayed in generated man pages (optional)
    no-flag:          if non-empty this field is ignored as an option (optional)

    optional:       whether an argument of the option is optional. When an
                    argument is optional it can only be specified using
                    --option=argument (optional)
    optional-value: the value of an optional option when the option occurs
                    without an argument. This tag can be specified multiple
                    times in the case of maps or slices (optional)
    default:        the default value of an option. This tag can be specified
                    multiple times in the case of slices or maps (optional)
    default-mask:   when specified, this value will be displayed in the help
                    instead of the actual default value. This is useful
                    mostly for hiding otherwise sensitive information from
                    showing up in the help. If default-mask takes the special
                    value "-", then no default value will be shown at all
                    (optional)
    env:            the default value of the option is overridden from the
                    specified environment variable, if one has been defined.
                    (optional)
    env-delim:      the 'env' default value from environment is split into
                    multiple values with the given delimiter string, use with
                    slices and maps (optional)
    value-name:     the name of the argument value (to be shown in the help)
                    (optional)
    choice:         limits the values for an option to a set of values.
                    This tag can be specified mltiple times (optional)
    hidden:         the option is not visible in the help or man page.

    base: a base (radix) used to convert strings to integer values, the
          default base is 10 (i.e. decimal) (optional)

    ini-name:       the explicit ini option name (optional)
    no-ini:         if non-empty this field is ignored as an ini option
                    (optional)

    group:                when specified on a struct field, makes the struct
                          field a separate group with the given name (optional)
    namespace:            when specified on a group struct field, the namespace
                          gets prepended to every option's long name and
                          subgroup's namespace of this group, separated by
                          the parser's namespace delimiter (optional)
    command:              when specified on a struct field, makes the struct
                          field a (sub)command with the given name (optional)
    subcommands-optional: when specified on a command struct field, makes
                          any subcommands of that command optional (optional)
    alias:                when specified on a command struct field, adds the
                          specified name as an alias for the command. Can be
                          be specified multiple times to add more than one
                          alias (optional)
    positional-args:      when specified on a field with a struct type,
                          uses the fields of that struct to parse remaining
                          positional command line arguments into (in order
                          of the fields). If a field has a slice type,
                          then all remaining arguments will be added to it.
                          Positional arguments are optional by default,
                          unless the "required" tag is specified together
                          with the "positional-args" tag. The "required" tag
                          can also be set on the individual rest argument
                          fields, to require only the first N positional
                          arguments. If the "required" tag is set on the
                          rest arguments slice, then its value determines
                          the minimum amount of rest arguments that needs to
                          be provided (e.g. `required:"2"`) (optional)
    positional-arg-name:  used on a field in a positional argument struct; name
                          of the positional argument placeholder to be shown in
                          the help (optional)

Either the `short:` tag or the `long:` must be specified to make the field eligible as an
option.

Option groups

Option groups are a simple way to semantically separate your options. All
options in a particular group are shown together in the help under the name
of the group. Namespaces can be used to specify option long names more
precisely and emphasize the options affiliation to their group.

There are currently three ways to specify option groups.

    1. Use NewNamedParser specifying the various option groups.
    2. Use AddGroup to add a group to an existing parser.
    3. Add a struct field to the top-level options annotated with the
       group:"group-name" tag.

Commands

The flags package also has basic support for commands. Commands are often
used in monolithic applications that support various commands or actions.
Take git for example, all of the add, commit, checkout, etc. are called
commands. Using commands you can easily separate multiple functions of your
application.

There are currently two ways to specify a command.

    1. Use AddCommand on an existing parser.
    2. Add a struct field to your options struct annotated with the
       command:"command-name" tag.

The most common, idiomatic way to implement commands is to define a global
parser instance and implement each command in a separate file. These
command files should define a go init function which calls AddCommand on
the global parser.

When parsing ends and there is an active command and that command implements
the Commander interface, then its Execute method will be run with the
remaining command line arguments.

Command structs can have options which become valid to parse after the
command has been specified on the command line, in addition to the options
of all the parent commands. I.e. considering a -v flag on the parser and an
add command, the following are equivalent:

    ./app -v add
    ./app add -v

However, if the -v flag is defined on the add command, then the first of
the two examples above would fail since the -v flag is not defined before
the add command.

Completion

go-flags has builtin support to provide bash completion of flags, commands
and argument values. To use completion, the binary which uses go-flags
can be invoked in a special environment to list completion of the current
command line argument. It should be noted that this `executes` your application,
and it is up to the user to make sure there are no negative side effects (for
example from init functions).

Setting the environment variable `GO_FLAGS_COMPLETION=1` enables completion
by replacing the argument parsing routine with the completion routine which
outputs completions for the passed arguments. The basic invocation to
complete a set of arguments is therefore:

    GO_FLAGS_COMPLETION=1 ./completion-example arg1 arg2 arg3

where `completion-example` is the binary, `arg1` and `arg2` are
the current arguments, and `arg3` (the last argument) is the argument
to be completed. If the GO_FLAGS_COMPLETION is set to "verbose", then
descriptions of possible completion items will also be shown, if there
are more than 1 completion items.

To use this with bash completion, a simple file can be written which
calls the binary which supports go-flags completion:

    _completion_example() {
        # All arguments except the first one
        args=("${COMP_WORDS[@]:1:$COMP_CWORD}")

        # Only split on newlines
        local IFS=$'\n'

        # Call completion (note that the first element of COMP_WORDS is
        # the executable itself)
        COMPREPLY=($(GO_FLAGS_COMPLETION=1 ${COMP_WORDS[0]} "${args[@]}"))
        return 0
    }

    complete -F _completion_example completion-example

Completion requires the parser option PassDoubleDash and is therefore enforced if the environment variable GO_FLAGS_COMPLETION is set.

Customized completion for argument values is supported by implementing
the flags.Completer interface for the argument value type. An example
of a type which does so is the flags.Filename type, an alias of string
allowing simple filename completion. A slice or array argument value
whose element type implements flags.Completer will also be completed.
*/
package flags
<commit_msg>Add line to doc for 'forceposix' build tag<commit_after>// Copyright 2012 Jesse van den Kieboom. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

/*
Package flags provides an extensive command line option parser.
The flags package is similar in functionality to the go built-in flag package
but provides more options and uses reflection to provide a convenient and
succinct way of specifying command line options.

Supported features

The following features are supported in go-flags:

    Options with short names (-v)
    Options with long names (--verbose)
    Options with and without arguments (bool v.s. other type)
    Options with optional arguments and default values
    Option default values from ENVIRONMENT_VARIABLES, including slice and map values
    Multiple option groups each containing a set of options
    Generate and print well-formatted help message
    Passing remaining command line arguments after -- (optional)
    Ignoring unknown command line options (optional)
    Supports -I/usr/include -I=/usr/include -I /usr/include option argument specification
    Supports multiple short options -aux
    Supports all primitive go types (string, int{8..64}, uint{8..64}, float)
    Supports same option multiple times (can store in slice or last option counts)
    Supports maps
    Supports function callbacks
    Supports namespaces for (nested) option groups

Additional features specific to Windows:
    Options with short names (/v)
    Options with long names (/verbose)
    Windows-style options with arguments use a colon as the delimiter
    Modify generated help message with Windows-style / options
    Windows style options can be disabled at build time using the "forceposix"
    build tag

Basic usage

The flags package uses structs, reflection and struct field tags
to allow users to specify command line options. This results in very simple
and concise specification of your application options. For example:

    type Options struct {
        Verbose []bool `short:"v" long:"verbose" description:"Show verbose debug information"`
    }

This specifies one option with a short name -v and a long name --verbose.
When either -v or --verbose is found on the command line, a 'true' value
will be appended to the Verbose field. e.g. when specifying -vvv, the
resulting value of Verbose will be {[true, true, true]}.

Slice options work exactly the same as primitive type options, except that
whenever the option is encountered, a value is appended to the slice.

Map options from string to primitive type are also supported. On the command
line, you specify the value for such an option as key:value. For example

    type Options struct {
        AuthorInfo string[string] `short:"a"`
    }

Then, the AuthorInfo map can be filled with something like
-a name:Jesse -a "surname:van den Kieboom".

Finally, for full control over the conversion between command line argument
values and options, user defined types can choose to implement the Marshaler
and Unmarshaler interfaces.

Available field tags

The following is a list of tags for struct fields supported by go-flags:

    short:            the short name of the option (single character)
    long:             the long name of the option
    required:         whether an option is required to appear on the command
                      line. If a required option is not present, the parser will
                      return ErrRequired (optional)
    description:      the description of the option (optional)
    long-description: the long description of the option. Currently only
                      displayed in generated man pages (optional)
    no-flag:          if non-empty this field is ignored as an option (optional)

    optional:       whether an argument of the option is optional. When an
                    argument is optional it can only be specified using
                    --option=argument (optional)
    optional-value: the value of an optional option when the option occurs
                    without an argument. This tag can be specified multiple
                    times in the case of maps or slices (optional)
    default:        the default value of an option. This tag can be specified
                    multiple times in the case of slices or maps (optional)
    default-mask:   when specified, this value will be displayed in the help
                    instead of the actual default value. This is useful
                    mostly for hiding otherwise sensitive information from
                    showing up in the help. If default-mask takes the special
                    value "-", then no default value will be shown at all
                    (optional)
    env:            the default value of the option is overridden from the
                    specified environment variable, if one has been defined.
                    (optional)
    env-delim:      the 'env' default value from environment is split into
                    multiple values with the given delimiter string, use with
                    slices and maps (optional)
    value-name:     the name of the argument value (to be shown in the help)
                    (optional)
    choice:         limits the values for an option to a set of values.
                    This tag can be specified mltiple times (optional)
    hidden:         the option is not visible in the help or man page.

    base: a base (radix) used to convert strings to integer values, the
          default base is 10 (i.e. decimal) (optional)

    ini-name:       the explicit ini option name (optional)
    no-ini:         if non-empty this field is ignored as an ini option
                    (optional)

    group:                when specified on a struct field, makes the struct
                          field a separate group with the given name (optional)
    namespace:            when specified on a group struct field, the namespace
                          gets prepended to every option's long name and
                          subgroup's namespace of this group, separated by
                          the parser's namespace delimiter (optional)
    command:              when specified on a struct field, makes the struct
                          field a (sub)command with the given name (optional)
    subcommands-optional: when specified on a command struct field, makes
                          any subcommands of that command optional (optional)
    alias:                when specified on a command struct field, adds the
                          specified name as an alias for the command. Can be
                          be specified multiple times to add more than one
                          alias (optional)
    positional-args:      when specified on a field with a struct type,
                          uses the fields of that struct to parse remaining
                          positional command line arguments into (in order
                          of the fields). If a field has a slice type,
                          then all remaining arguments will be added to it.
                          Positional arguments are optional by default,
                          unless the "required" tag is specified together
                          with the "positional-args" tag. The "required" tag
                          can also be set on the individual rest argument
                          fields, to require only the first N positional
                          arguments. If the "required" tag is set on the
                          rest arguments slice, then its value determines
                          the minimum amount of rest arguments that needs to
                          be provided (e.g. `required:"2"`) (optional)
    positional-arg-name:  used on a field in a positional argument struct; name
                          of the positional argument placeholder to be shown in
                          the help (optional)

Either the `short:` tag or the `long:` must be specified to make the field eligible as an
option.

Option groups

Option groups are a simple way to semantically separate your options. All
options in a particular group are shown together in the help under the name
of the group. Namespaces can be used to specify option long names more
precisely and emphasize the options affiliation to their group.

There are currently three ways to specify option groups.

    1. Use NewNamedParser specifying the various option groups.
    2. Use AddGroup to add a group to an existing parser.
    3. Add a struct field to the top-level options annotated with the
       group:"group-name" tag.

Commands

The flags package also has basic support for commands. Commands are often
used in monolithic applications that support various commands or actions.
Take git for example, all of the add, commit, checkout, etc. are called
commands. Using commands you can easily separate multiple functions of your
application.

There are currently two ways to specify a command.

    1. Use AddCommand on an existing parser.
    2. Add a struct field to your options struct annotated with the
       command:"command-name" tag.

The most common, idiomatic way to implement commands is to define a global
parser instance and implement each command in a separate file. These
command files should define a go init function which calls AddCommand on
the global parser.

When parsing ends and there is an active command and that command implements
the Commander interface, then its Execute method will be run with the
remaining command line arguments.

Command structs can have options which become valid to parse after the
command has been specified on the command line, in addition to the options
of all the parent commands. I.e. considering a -v flag on the parser and an
add command, the following are equivalent:

    ./app -v add
    ./app add -v

However, if the -v flag is defined on the add command, then the first of
the two examples above would fail since the -v flag is not defined before
the add command.

Completion

go-flags has builtin support to provide bash completion of flags, commands
and argument values. To use completion, the binary which uses go-flags
can be invoked in a special environment to list completion of the current
command line argument. It should be noted that this `executes` your application,
and it is up to the user to make sure there are no negative side effects (for
example from init functions).

Setting the environment variable `GO_FLAGS_COMPLETION=1` enables completion
by replacing the argument parsing routine with the completion routine which
outputs completions for the passed arguments. The basic invocation to
complete a set of arguments is therefore:

    GO_FLAGS_COMPLETION=1 ./completion-example arg1 arg2 arg3

where `completion-example` is the binary, `arg1` and `arg2` are
the current arguments, and `arg3` (the last argument) is the argument
to be completed. If the GO_FLAGS_COMPLETION is set to "verbose", then
descriptions of possible completion items will also be shown, if there
are more than 1 completion items.

To use this with bash completion, a simple file can be written which
calls the binary which supports go-flags completion:

    _completion_example() {
        # All arguments except the first one
        args=("${COMP_WORDS[@]:1:$COMP_CWORD}")

        # Only split on newlines
        local IFS=$'\n'

        # Call completion (note that the first element of COMP_WORDS is
        # the executable itself)
        COMPREPLY=($(GO_FLAGS_COMPLETION=1 ${COMP_WORDS[0]} "${args[@]}"))
        return 0
    }

    complete -F _completion_example completion-example

Completion requires the parser option PassDoubleDash and is therefore enforced if the environment variable GO_FLAGS_COMPLETION is set.

Customized completion for argument values is supported by implementing
the flags.Completer interface for the argument value type. An example
of a type which does so is the flags.Filename type, an alias of string
allowing simple filename completion. A slice or array argument value
whose element type implements flags.Completer will also be completed.
*/
package flags
<|endoftext|>


<commit_before>// Copyright 2013 go-dockerclient authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package docker

import (
	"bytes"
	"encoding/json"
	"errors"
	"github.com/dotcloud/docker"
	"io"
	"io/ioutil"
	"net/http"
	"net/url"
	"os"
)

type APIImages struct {
	Repository string `json:",omitempty"`
	Tag        string `json:",omitempty"`
	ID         string
	Created    int64 `json:",omitempty"`
}

// Error returned when the image does not exist.
var ErrNoSuchImage = errors.New("No such image")

// ListImages returns the list of available images in the server.
//
// See http://goo.gl/dkMrwP for more details.
func (c *Client) ListImages(all bool) ([]APIImages, error) {
	path := "/images/json?all="
	if all {
		path += "1"
	} else {
		path += "0"
	}
	body, _, err := c.do("GET", path, nil)
	if err != nil {
		return nil, err
	}
	var images []APIImages
	err = json.Unmarshal(body, &images)
	if err != nil {
		return nil, err
	}
	return images, nil
}

// RemoveImage removes an image by its name or ID.
//
// See http://goo.gl/7hjHHy for more details.
func (c *Client) RemoveImage(name string) error {
	_, status, err := c.do("DELETE", "/images/"+name, nil)
	if status == http.StatusNotFound {
		return ErrNoSuchImage
	}
	return err
}

// InspectImage returns an image by its name or ID.
//
// See http://goo.gl/pHEbma for more details.
func (c *Client) InspectImage(name string) (*docker.Image, error) {
	body, status, err := c.do("GET", "/images/"+name+"/json", nil)
	if status == http.StatusNotFound {
		return nil, ErrNoSuchImage
	}
	if err != nil {
		return nil, err
	}
	var image docker.Image
	err = json.Unmarshal(body, &image)
	if err != nil {
		return nil, err
	}
	return &image, nil
}

// PushImageOptions represents options to use in the PushImage method.
//
// See http://goo.gl/GBmyhc for more details.
type PushImageOptions struct {
	// Name of the image
	Name string

	// Registry server to push the image
	Registry string
}

// AuthConfiguration represents authentication options to use in the PushImage
// method. It represents the authencation in the Docker index server.
type AuthConfiguration struct {
	Username string `json:"username,omitempty"`
	Password string `json:"password,omitempty"`
	Email    string `json:"email,omitempty"`
}

// PushImage pushes an image to a remote registry, logging progress to w.
//
// An empty instance of AuthConfiguration may be used for unauthenticated
// pushes.
//
// See http://goo.gl/GBmyhc for more details.
func (c *Client) PushImage(opts PushImageOptions, auth AuthConfiguration, w io.Writer) error {
	if opts.Name == "" {
		return ErrNoSuchImage
	}
	name := opts.Name
	opts.Name = ""
	path := "/images/" + name + "/push?" + queryString(&opts)
	var buf bytes.Buffer
	json.NewEncoder(&buf).Encode(auth)
	return c.stream("POST", path, &buf, w)
}

// PullImageOptions present the set of options available for pulling an image
// from a registry.
//
// See http://goo.gl/PhBKnS for more details.
type PullImageOptions struct {
	Repository string `qs:"fromImage"`
	Registry   string
}

// PullImage pulls an image from a remote registry, logging progress to w.
//
// See http://goo.gl/PhBKnS for more details.
func (c *Client) PullImage(opts PullImageOptions, w io.Writer) error {
	if opts.Repository == "" {
		return ErrNoSuchImage
	}
	return c.createImage(queryString(&opts), nil, w)
}

func (c *Client) createImage(qs string, in io.Reader, w io.Writer) error {
	path := "/images/create?" + qs
	return c.stream("POST", path, in, w)
}

// ImportImageOptions present the set of informations available for importing
// an image from a source file or the stdin.
//
// See http://goo.gl/PhBKnS for more details.
type ImportImageOptions struct {
	Repository string `qs:"repo"`
	Source     string `qs:"fromSrc"`
}

// ImportImage imports an image from a url, a file or stdin
//
// See http://goo.gl/PhBKnS for more details.
func (c *Client) ImportImage(opts ImportImageOptions, in io.Reader, out io.Writer) error {
	if opts.Repository == "" {
		return ErrNoSuchImage
	}
	if opts.Source != "-" {
		in = nil
	}
	if opts.Source != "-" && !isUrl(opts.Source) {
		f, err := os.Open(opts.Source)
		if err != nil {
			return err
		}
		b, err := ioutil.ReadAll(f)
		in = bytes.NewBuffer(b)
		opts.Source = "-"
	}
	return c.createImage(queryString(&opts), in, out)
}

func isUrl(u string) bool {
	p, err := url.Parse(u)
	if err != nil {
		return false
	}
	return p.Scheme == "http" || p.Scheme == "https"
}
<commit_msg>add repoTags<commit_after>// Copyright 2013 go-dockerclient authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package docker

import (
	"bytes"
	"encoding/json"
	"errors"
	"github.com/dotcloud/docker"
	"io"
	"io/ioutil"
	"net/http"
	"net/url"
	"os"
)

type APIImages struct {
	Repository string `json:",omitempty"`
	Tag        string `json:",omitempty"`
	RepoTags   []string `json:",omitempty"`
	ID         string
	Created    int64 `json:",omitempty"`
}

// Error returned when the image does not exist.
var ErrNoSuchImage = errors.New("No such image")

// ListImages returns the list of available images in the server.
//
// See http://goo.gl/dkMrwP for more details.
func (c *Client) ListImages(all bool) ([]APIImages, error) {
	path := "/images/json?all="
	if all {
		path += "1"
	} else {
		path += "0"
	}
	body, _, err := c.do("GET", path, nil)
	if err != nil {
		return nil, err
	}
	var images []APIImages
	err = json.Unmarshal(body, &images)
	if err != nil {
		return nil, err
	}
	return images, nil
}

// RemoveImage removes an image by its name or ID.
//
// See http://goo.gl/7hjHHy for more details.
func (c *Client) RemoveImage(name string) error {
	_, status, err := c.do("DELETE", "/images/"+name, nil)
	if status == http.StatusNotFound {
		return ErrNoSuchImage
	}
	return err
}

// InspectImage returns an image by its name or ID.
//
// See http://goo.gl/pHEbma for more details.
func (c *Client) InspectImage(name string) (*docker.Image, error) {
	body, status, err := c.do("GET", "/images/"+name+"/json", nil)
	if status == http.StatusNotFound {
		return nil, ErrNoSuchImage
	}
	if err != nil {
		return nil, err
	}
	var image docker.Image
	err = json.Unmarshal(body, &image)
	if err != nil {
		return nil, err
	}
	return &image, nil
}

// PushImageOptions represents options to use in the PushImage method.
//
// See http://goo.gl/GBmyhc for more details.
type PushImageOptions struct {
	// Name of the image
	Name string

	// Registry server to push the image
	Registry string
}

// AuthConfiguration represents authentication options to use in the PushImage
// method. It represents the authencation in the Docker index server.
type AuthConfiguration struct {
	Username string `json:"username,omitempty"`
	Password string `json:"password,omitempty"`
	Email    string `json:"email,omitempty"`
}

// PushImage pushes an image to a remote registry, logging progress to w.
//
// An empty instance of AuthConfiguration may be used for unauthenticated
// pushes.
//
// See http://goo.gl/GBmyhc for more details.
func (c *Client) PushImage(opts PushImageOptions, auth AuthConfiguration, w io.Writer) error {
	if opts.Name == "" {
		return ErrNoSuchImage
	}
	name := opts.Name
	opts.Name = ""
	path := "/images/" + name + "/push?" + queryString(&opts)
	var buf bytes.Buffer
	json.NewEncoder(&buf).Encode(auth)
	return c.stream("POST", path, &buf, w)
}

// PullImageOptions present the set of options available for pulling an image
// from a registry.
//
// See http://goo.gl/PhBKnS for more details.
type PullImageOptions struct {
	Repository string `qs:"fromImage"`
	Registry   string
}

// PullImage pulls an image from a remote registry, logging progress to w.
//
// See http://goo.gl/PhBKnS for more details.
func (c *Client) PullImage(opts PullImageOptions, w io.Writer) error {
	if opts.Repository == "" {
		return ErrNoSuchImage
	}
	return c.createImage(queryString(&opts), nil, w)
}

func (c *Client) createImage(qs string, in io.Reader, w io.Writer) error {
	path := "/images/create?" + qs
	return c.stream("POST", path, in, w)
}

// ImportImageOptions present the set of informations available for importing
// an image from a source file or the stdin.
//
// See http://goo.gl/PhBKnS for more details.
type ImportImageOptions struct {
	Repository string `qs:"repo"`
	Source     string `qs:"fromSrc"`
}

// ImportImage imports an image from a url, a file or stdin
//
// See http://goo.gl/PhBKnS for more details.
func (c *Client) ImportImage(opts ImportImageOptions, in io.Reader, out io.Writer) error {
	if opts.Repository == "" {
		return ErrNoSuchImage
	}
	if opts.Source != "-" {
		in = nil
	}
	if opts.Source != "-" && !isUrl(opts.Source) {
		f, err := os.Open(opts.Source)
		if err != nil {
			return err
		}
		b, err := ioutil.ReadAll(f)
		in = bytes.NewBuffer(b)
		opts.Source = "-"
	}
	return c.createImage(queryString(&opts), in, out)
}

func isUrl(u string) bool {
	p, err := url.Parse(u)
	if err != nil {
		return false
	}
	return p.Scheme == "http" || p.Scheme == "https"
}
<|endoftext|>


<commit_before>package main

import (
	"io/ioutil"
	"reflect"
	"testing"
)

func testIconsFindHelper(terms []string) icons {
	return newIcons().find(terms)
}

func TestIcons_iconsYamlPath_TestEnv(t *testing.T) {
	actual := iconsYamlPath()
	expected := "workflow/icons.yml"
	if actual != expected {
		t.Errorf("expected %v to eq %v", actual, expected)
	}
}

func TestIcons_iconsYamlPath_ProductionEnv(t *testing.T) {
	resetEnv := setTestEnvHelper("FAW_ICONS_YAML_PATH", "")
	defer resetEnv()

	actual := iconsYamlPath()
	expected := "icons.yml"
	if actual != expected {
		t.Errorf("expected %v to eq %v", actual, expected)
	}
}

func TestIcons_iconsReadYaml(t *testing.T) {
	path := "workflow/icons.yml"
	actual, _ := iconsReadYaml(path)

	expected, _ := ioutil.ReadFile(path)
	if !reflect.DeepEqual(actual, expected) {
		t.Error("failed to read file")
	}
}

func TestIcons_iconsReadYaml_Error(t *testing.T) {
	path := ""
	_, err := iconsReadYaml(path)

	if err == nil {
		t.Error("expected error, but nil")
	}
}

func TestIcons_iconsUnmarshalYaml(t *testing.T) {
	b := []byte(`
icons:
  - name:       Glass
    id:         glass
    unicode:    f000
    created:    1.0
    filter:
      - martini
`)
	actual, _ := iconsUnmarshalYaml(b)

	icon := icon{
		Name:    "Glass",
		ID:      "glass",
		Unicode: "f000",
		Created: "1.0",
		Filter:  []string{"martini"},
	}
	expected := iconsYaml{icons{icon}}
	if !reflect.DeepEqual(actual, expected) {
		t.Errorf("expected %v to eq %v", actual, expected)
	}
}

func TestIcons_find_AllIcons(t *testing.T) {
	fi := testIconsFindHelper([]string{""})

	actual := len(fi)
	expected := 585
	if actual != expected {
		t.Errorf("expected %v to eq %v", actual, expected)
	}
}

func TestIcons_find_ZeroIcon(t *testing.T) {
	fi := testIconsFindHelper([]string{"foo-bar-baz"})

	actual := len(fi)
	expected := 0
	if actual != expected {
		t.Errorf("expected %v to eq %v", actual, expected)
	}
}

func TestIcons_find_OneIcon(t *testing.T) {
	fi := testIconsFindHelper([]string{"github-square"})

	actual := len(fi)
	expected := 1
	if actual != expected {
		t.Errorf("expected %v to eq %v", actual, expected)
	}
}

func TestIcons_find_TwoIcons(t *testing.T) {
	fi := testIconsFindHelper([]string{"github-"})

	actual := len(fi)
	expected := 2
	if actual != expected {
		t.Errorf("expected %v to eq %v", actual, expected)
	}
}

func TestIcons_find_FirstIcon(t *testing.T) {
	fi := testIconsFindHelper([]string{""})

	actual := fi[0].ID
	expected := "500px"
	if actual != expected {
		t.Errorf("expected %v to eq %v", actual, expected)
	}
}

func TestIcons_find_LastIcon(t *testing.T) {
	fi := testIconsFindHelper([]string{""})

	actual := fi[len(fi)-1].ID
	expected := "youtube-square"
	if actual != expected {
		t.Errorf("expected %v to eq %v", actual, expected)
	}
}

func TestIcons_find_TaxiIcon(t *testing.T) {
	fi := testIconsFindHelper([]string{"taxi"})

	actual := fi[0].Name
	expected := "Taxi"
	if actual != expected {
		t.Errorf("expected %v to eq %v", actual, expected)
	}

	actual = fi[0].ID
	expected = "taxi"
	if actual != expected {
		t.Errorf("expected %v to eq %v", actual, expected)
	}

	actual = fi[0].Unicode
	expected = "f1ba"
	if actual != expected {
		t.Errorf("expected %v to eq %v", actual, expected)
	}

	actual = fi[0].Created
	expected = "4.1"
	if actual != expected {
		t.Errorf("expected %v to eq %v", actual, expected)
	}

	actual = fi[0].Aliases[0]
	expected = "cab"
	if actual != expected {
		t.Errorf("expected %v to eq %v", actual, expected)
	}

	actual = fi[0].Filter[0]
	expected = "vehicle"
	if actual != expected {
		t.Errorf("expected %v to eq %v", actual, expected)
	}

	actual = fi[0].Categories[0]
	expected = "Web Application Icons"
	if actual != expected {
		t.Errorf("expected %v to eq %v", actual, expected)
	}
}

func TestIcons_find_Aliases(t *testing.T) {
	fi := testIconsFindHelper([]string{"navicon"})

	actual := fi[0].ID
	expected := "bars"
	if actual != expected {
		t.Errorf("expected %v to eq %v", actual, expected)
	}

	if len(fi) != 1 {
		t.Errorf("expected %v to eq %v", len(fi), 1)
	}
}

func TestIcons_findByUnicode(t *testing.T) {
	fi := newIcons().findByUnicode("f067")

	actual := fi[0].ID
	expected := "plus"
	if actual != expected {
		t.Errorf("expected %v to eq %v", actual, expected)
	}

	if len(fi) != 1 {
		t.Errorf("expected %v to eq %v", len(fi), 1)
	}
}
<commit_msg>Fix test (Font Awesome 4.5.0)<commit_after>package main

import (
	"io/ioutil"
	"reflect"
	"testing"
)

func testIconsFindHelper(terms []string) icons {
	return newIcons().find(terms)
}

func TestIcons_iconsYamlPath_TestEnv(t *testing.T) {
	actual := iconsYamlPath()
	expected := "workflow/icons.yml"
	if actual != expected {
		t.Errorf("expected %v to eq %v", actual, expected)
	}
}

func TestIcons_iconsYamlPath_ProductionEnv(t *testing.T) {
	resetEnv := setTestEnvHelper("FAW_ICONS_YAML_PATH", "")
	defer resetEnv()

	actual := iconsYamlPath()
	expected := "icons.yml"
	if actual != expected {
		t.Errorf("expected %v to eq %v", actual, expected)
	}
}

func TestIcons_iconsReadYaml(t *testing.T) {
	path := "workflow/icons.yml"
	actual, _ := iconsReadYaml(path)

	expected, _ := ioutil.ReadFile(path)
	if !reflect.DeepEqual(actual, expected) {
		t.Error("failed to read file")
	}
}

func TestIcons_iconsReadYaml_Error(t *testing.T) {
	path := ""
	_, err := iconsReadYaml(path)

	if err == nil {
		t.Error("expected error, but nil")
	}
}

func TestIcons_iconsUnmarshalYaml(t *testing.T) {
	b := []byte(`
icons:
  - name:       Glass
    id:         glass
    unicode:    f000
    created:    1.0
    filter:
      - martini
`)
	actual, _ := iconsUnmarshalYaml(b)

	icon := icon{
		Name:    "Glass",
		ID:      "glass",
		Unicode: "f000",
		Created: "1.0",
		Filter:  []string{"martini"},
	}
	expected := iconsYaml{icons{icon}}
	if !reflect.DeepEqual(actual, expected) {
		t.Errorf("expected %v to eq %v", actual, expected)
	}
}

func TestIcons_find_AllIcons(t *testing.T) {
	fi := testIconsFindHelper([]string{""})

	actual := len(fi)
	expected := 605
	if actual != expected {
		t.Errorf("expected %v to eq %v", actual, expected)
	}
}

func TestIcons_find_ZeroIcon(t *testing.T) {
	fi := testIconsFindHelper([]string{"foo-bar-baz"})

	actual := len(fi)
	expected := 0
	if actual != expected {
		t.Errorf("expected %v to eq %v", actual, expected)
	}
}

func TestIcons_find_OneIcon(t *testing.T) {
	fi := testIconsFindHelper([]string{"github-square"})

	actual := len(fi)
	expected := 1
	if actual != expected {
		t.Errorf("expected %v to eq %v", actual, expected)
	}
}

func TestIcons_find_TwoIcons(t *testing.T) {
	fi := testIconsFindHelper([]string{"github-"})

	actual := len(fi)
	expected := 2
	if actual != expected {
		t.Errorf("expected %v to eq %v", actual, expected)
	}
}

func TestIcons_find_FirstIcon(t *testing.T) {
	fi := testIconsFindHelper([]string{""})

	actual := fi[0].ID
	expected := "500px"
	if actual != expected {
		t.Errorf("expected %v to eq %v", actual, expected)
	}
}

func TestIcons_find_LastIcon(t *testing.T) {
	fi := testIconsFindHelper([]string{""})

	actual := fi[len(fi)-1].ID
	expected := "youtube-square"
	if actual != expected {
		t.Errorf("expected %v to eq %v", actual, expected)
	}
}

func TestIcons_find_TaxiIcon(t *testing.T) {
	fi := testIconsFindHelper([]string{"taxi"})

	actual := fi[0].Name
	expected := "Taxi"
	if actual != expected {
		t.Errorf("expected %v to eq %v", actual, expected)
	}

	actual = fi[0].ID
	expected = "taxi"
	if actual != expected {
		t.Errorf("expected %v to eq %v", actual, expected)
	}

	actual = fi[0].Unicode
	expected = "f1ba"
	if actual != expected {
		t.Errorf("expected %v to eq %v", actual, expected)
	}

	actual = fi[0].Created
	expected = "4.1"
	if actual != expected {
		t.Errorf("expected %v to eq %v", actual, expected)
	}

	actual = fi[0].Aliases[0]
	expected = "cab"
	if actual != expected {
		t.Errorf("expected %v to eq %v", actual, expected)
	}

	actual = fi[0].Filter[0]
	expected = "vehicle"
	if actual != expected {
		t.Errorf("expected %v to eq %v", actual, expected)
	}

	actual = fi[0].Categories[0]
	expected = "Web Application Icons"
	if actual != expected {
		t.Errorf("expected %v to eq %v", actual, expected)
	}
}

func TestIcons_find_Aliases(t *testing.T) {
	fi := testIconsFindHelper([]string{"navicon"})

	actual := fi[0].ID
	expected := "bars"
	if actual != expected {
		t.Errorf("expected %v to eq %v", actual, expected)
	}

	if len(fi) != 1 {
		t.Errorf("expected %v to eq %v", len(fi), 1)
	}
}

func TestIcons_findByUnicode(t *testing.T) {
	fi := newIcons().findByUnicode("f067")

	actual := fi[0].ID
	expected := "plus"
	if actual != expected {
		t.Errorf("expected %v to eq %v", actual, expected)
	}

	if len(fi) != 1 {
		t.Errorf("expected %v to eq %v", len(fi), 1)
	}
}
<|endoftext|>