<commit_before>/*
Copyright 2014 The Kubernetes Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package prober

import (
	"fmt"
	"io"
	"net"
	"net/http"
	"net/url"
	"strconv"
	"strings"
	"time"

	v1 "k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/util/intstr"
	"k8s.io/client-go/tools/record"
	kubecontainer "k8s.io/kubernetes/pkg/kubelet/container"
	"k8s.io/kubernetes/pkg/kubelet/events"
	"k8s.io/kubernetes/pkg/kubelet/prober/results"
	"k8s.io/kubernetes/pkg/kubelet/util/format"
	"k8s.io/kubernetes/pkg/probe"
	execprobe "k8s.io/kubernetes/pkg/probe/exec"
	httpprobe "k8s.io/kubernetes/pkg/probe/http"
	tcpprobe "k8s.io/kubernetes/pkg/probe/tcp"
	"k8s.io/utils/exec"

	"k8s.io/klog"
)

const maxProbeRetries = 3

// Prober helps to check the liveness/readiness/startup of a container.
type prober struct {
	exec execprobe.Prober
	// probe types needs different httprobe instances so they don't
	// share a connection pool which can cause collsions to the
	// same host:port and transient failures. See #49740.
	readinessHTTP httpprobe.Prober
	livenessHTTP  httpprobe.Prober
	startupHTTP   httpprobe.Prober
	tcp           tcpprobe.Prober
	runner        kubecontainer.ContainerCommandRunner

	refManager *kubecontainer.RefManager
	recorder   record.EventRecorder
}

// NewProber creates a Prober, it takes a command runner and
// several container info managers.
func newProber(
	runner kubecontainer.ContainerCommandRunner,
	refManager *kubecontainer.RefManager,
	recorder record.EventRecorder) *prober {

	const followNonLocalRedirects = false
	return &prober{
		exec:          execprobe.New(),
		readinessHTTP: httpprobe.New(followNonLocalRedirects),
		livenessHTTP:  httpprobe.New(followNonLocalRedirects),
		startupHTTP:   httpprobe.New(followNonLocalRedirects),
		tcp:           tcpprobe.New(),
		runner:        runner,
		refManager:    refManager,
		recorder:      recorder,
	}
}

// probe probes the container.
func (pb *prober) probe(probeType probeType, pod *v1.Pod, status v1.PodStatus, container v1.Container, containerID kubecontainer.ContainerID) (results.Result, error) {
	var probeSpec *v1.Probe
	switch probeType {
	case readiness:
		probeSpec = container.ReadinessProbe
	case liveness:
		probeSpec = container.LivenessProbe
	case startup:
		probeSpec = container.StartupProbe
	default:
		return results.Failure, fmt.Errorf("unknown probe type: %q", probeType)
	}

	ctrName := fmt.Sprintf("%s:%s", format.Pod(pod), container.Name)
	if probeSpec == nil {
		klog.Warningf("%s probe for %s is nil", probeType, ctrName)
		return results.Success, nil
	}

	result, output, err := pb.runProbeWithRetries(probeType, probeSpec, pod, status, container, containerID, maxProbeRetries)
	if err != nil || (result != probe.Success && result != probe.Warning) {
		// Probe failed in one way or another.
		ref, hasRef := pb.refManager.GetRef(containerID)
		if !hasRef {
			klog.Warningf("No ref for container %q (%s)", containerID.String(), ctrName)
		}
		if err != nil {
			klog.V(1).Infof("%s probe for %q errored: %v", probeType, ctrName, err)
			if hasRef {
				pb.recorder.Eventf(ref, v1.EventTypeWarning, events.ContainerUnhealthy, "%s probe errored: %v", probeType, err)
			}
		} else { // result != probe.Success
			klog.V(1).Infof("%s probe for %q failed (%v): %s", probeType, ctrName, result, output)
			if hasRef {
				pb.recorder.Eventf(ref, v1.EventTypeWarning, events.ContainerUnhealthy, "%s probe failed: %s", probeType, output)
			}
		}
		return results.Failure, err
	}
	if result == probe.Warning {
		if ref, hasRef := pb.refManager.GetRef(containerID); hasRef {
			pb.recorder.Eventf(ref, v1.EventTypeWarning, events.ContainerProbeWarning, "%s probe warning: %s", probeType, output)
		}
		klog.V(3).Infof("%s probe for %q succeeded with a warning: %s", probeType, ctrName, output)
	} else {
		klog.V(3).Infof("%s probe for %q succeeded", probeType, ctrName)
	}
	return results.Success, nil
}

// runProbeWithRetries tries to probe the container in a finite loop, it returns the last result
// if it never succeeds.
func (pb *prober) runProbeWithRetries(probeType probeType, p *v1.Probe, pod *v1.Pod, status v1.PodStatus, container v1.Container, containerID kubecontainer.ContainerID, retries int) (probe.Result, string, error) {
	var err error
	var result probe.Result
	var output string
	for i := 0; i < retries; i++ {
		result, output, err = pb.runProbe(probeType, p, pod, status, container, containerID)
		if err == nil {
			return result, output, nil
		}
	}
	return result, output, err
}

// buildHeaderMap takes a list of HTTPHeader <name, value> string
// pairs and returns a populated string->[]string http.Header map.
func buildHeader(headerList []v1.HTTPHeader) http.Header {
	headers := make(http.Header)
	for _, header := range headerList {
		headers[header.Name] = append(headers[header.Name], header.Value)
	}
	return headers
}

func (pb *prober) runProbe(probeType probeType, p *v1.Probe, pod *v1.Pod, status v1.PodStatus, container v1.Container, containerID kubecontainer.ContainerID) (probe.Result, string, error) {
	timeout := time.Duration(p.TimeoutSeconds) * time.Second
	if p.Exec != nil {
		klog.V(4).Infof("Exec-Probe Pod: %v, Container: %v, Command: %v", pod, container, p.Exec.Command)
		command := kubecontainer.ExpandContainerCommandOnlyStatic(p.Exec.Command, container.Env)
		return pb.exec.Probe(pb.newExecInContainer(container, containerID, command, timeout))
	}
	if p.HTTPGet != nil {
		scheme := strings.ToLower(string(p.HTTPGet.Scheme))
		host := p.HTTPGet.Host
		if host == "" {
			host = status.PodIP
		}
		port, err := extractPort(p.HTTPGet.Port, container)
		if err != nil {
			return probe.Unknown, "", err
		}
		path := p.HTTPGet.Path
		klog.V(4).Infof("HTTP-Probe Host: %v://%v, Port: %v, Path: %v", scheme, host, port, path)
		url := formatURL(scheme, host, port, path)
		headers := buildHeader(p.HTTPGet.HTTPHeaders)
		klog.V(4).Infof("HTTP-Probe Headers: %v", headers)
		switch probeType {
		case liveness:
			return pb.livenessHTTP.Probe(url, headers, timeout)
		case startup:
			return pb.startupHTTP.Probe(url, headers, timeout)
		default:
			return pb.readinessHTTP.Probe(url, headers, timeout)
		}
	}
	if p.TCPSocket != nil {
		port, err := extractPort(p.TCPSocket.Port, container)
		if err != nil {
			return probe.Unknown, "", err
		}
		host := p.TCPSocket.Host
		if host == "" {
			host = status.PodIP
		}
		klog.V(4).Infof("TCP-Probe Host: %v, Port: %v, Timeout: %v", host, port, timeout)
		return pb.tcp.Probe(host, port, timeout)
	}
	klog.Warningf("Failed to find probe builder for container: %v", container)
	return probe.Unknown, "", fmt.Errorf("missing probe handler for %s:%s", format.Pod(pod), container.Name)
}

func extractPort(param intstr.IntOrString, container v1.Container) (int, error) {
	port := -1
	var err error
	switch param.Type {
	case intstr.Int:
		port = param.IntValue()
	case intstr.String:
		if port, err = findPortByName(container, param.StrVal); err != nil {
			// Last ditch effort - maybe it was an int stored as string?
			if port, err = strconv.Atoi(param.StrVal); err != nil {
				return port, err
			}
		}
	default:
		return port, fmt.Errorf("intOrString had no kind: %+v", param)
	}
	if port > 0 && port < 65536 {
		return port, nil
	}
	return port, fmt.Errorf("invalid port number: %v", port)
}

// findPortByName is a helper function to look up a port in a container by name.
func findPortByName(container v1.Container, portName string) (int, error) {
	for _, port := range container.Ports {
		if port.Name == portName {
			return int(port.ContainerPort), nil
		}
	}
	return 0, fmt.Errorf("port %s not found", portName)
}

// formatURL formats a URL from args.  For testability.
func formatURL(scheme string, host string, port int, path string) *url.URL {
	u, err := url.Parse(path)
	// Something is busted with the path, but it's too late to reject it. Pass it along as is.
	if err != nil {
		u = &url.URL{
			Path: path,
		}
	}
	u.Scheme = scheme
	u.Host = net.JoinHostPort(host, strconv.Itoa(port))
	return u
}

type execInContainer struct {
	// run executes a command in a container. Combined stdout and stderr output is always returned. An
	// error is returned if one occurred.
	run    func() ([]byte, error)
	writer io.Writer
}

func (pb *prober) newExecInContainer(container v1.Container, containerID kubecontainer.ContainerID, cmd []string, timeout time.Duration) exec.Cmd {
	return &execInContainer{run: func() ([]byte, error) {
		return pb.runner.RunInContainer(containerID, cmd, timeout)
	}}
}

func (eic *execInContainer) Run() error {
	return nil
}

func (eic *execInContainer) CombinedOutput() ([]byte, error) {
	return eic.run()
}

func (eic *execInContainer) Output() ([]byte, error) {
	return nil, fmt.Errorf("unimplemented")
}

func (eic *execInContainer) SetDir(dir string) {
	//unimplemented
}

func (eic *execInContainer) SetStdin(in io.Reader) {
	//unimplemented
}

func (eic *execInContainer) SetStdout(out io.Writer) {
	eic.writer = out
}

func (eic *execInContainer) SetStderr(out io.Writer) {
	eic.writer = out
}

func (eic *execInContainer) SetEnv(env []string) {
	//unimplemented
}

func (eic *execInContainer) Stop() {
	//unimplemented
}

func (eic *execInContainer) Start() error {
	data, err := eic.run()
	if eic.writer != nil {
		eic.writer.Write(data)
	}
	return err
}

func (eic *execInContainer) Wait() error {
	return nil
}

func (eic *execInContainer) StdoutPipe() (io.ReadCloser, error) {
	return nil, fmt.Errorf("unimplemented")
}

func (eic *execInContainer) StderrPipe() (io.ReadCloser, error) {
	return nil, fmt.Errorf("unimplemented")
}
<commit_msg>Fixes for the `No ref for container` in probes after kubelet restart<commit_after>/*
Copyright 2014 The Kubernetes Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package prober

import (
	"fmt"
	"io"
	"net"
	"net/http"
	"net/url"
	"strconv"
	"strings"
	"time"

	v1 "k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/util/intstr"
	"k8s.io/client-go/tools/record"
	kubecontainer "k8s.io/kubernetes/pkg/kubelet/container"
	"k8s.io/kubernetes/pkg/kubelet/events"
	"k8s.io/kubernetes/pkg/kubelet/prober/results"
	"k8s.io/kubernetes/pkg/kubelet/util/format"
	"k8s.io/kubernetes/pkg/probe"
	execprobe "k8s.io/kubernetes/pkg/probe/exec"
	httpprobe "k8s.io/kubernetes/pkg/probe/http"
	tcpprobe "k8s.io/kubernetes/pkg/probe/tcp"
	"k8s.io/utils/exec"

	"k8s.io/klog"
)

const maxProbeRetries = 3

// Prober helps to check the liveness/readiness/startup of a container.
type prober struct {
	exec execprobe.Prober
	// probe types needs different httprobe instances so they don't
	// share a connection pool which can cause collsions to the
	// same host:port and transient failures. See #49740.
	readinessHTTP httpprobe.Prober
	livenessHTTP  httpprobe.Prober
	startupHTTP   httpprobe.Prober
	tcp           tcpprobe.Prober
	runner        kubecontainer.ContainerCommandRunner

	refManager *kubecontainer.RefManager
	recorder   record.EventRecorder
}

// NewProber creates a Prober, it takes a command runner and
// several container info managers.
func newProber(
	runner kubecontainer.ContainerCommandRunner,
	refManager *kubecontainer.RefManager,
	recorder record.EventRecorder) *prober {

	const followNonLocalRedirects = false
	return &prober{
		exec:          execprobe.New(),
		readinessHTTP: httpprobe.New(followNonLocalRedirects),
		livenessHTTP:  httpprobe.New(followNonLocalRedirects),
		startupHTTP:   httpprobe.New(followNonLocalRedirects),
		tcp:           tcpprobe.New(),
		runner:        runner,
		refManager:    refManager,
		recorder:      recorder,
	}
}

// recordContainerEvent should be used by the prober for all container related events.
func (pb *prober) recordContainerEvent(pod *v1.Pod, container *v1.Container, containerID kubecontainer.ContainerID, eventType, reason, message string, args ...interface{}) {
	var err error
	ref, hasRef := pb.refManager.GetRef(containerID)
	if !hasRef {
		ref, err = kubecontainer.GenerateContainerRef(pod, container)
		if err != nil {
			klog.Errorf("Can't make a ref to pod %q, container %v: %v", format.Pod(pod), container.Name, err)
			return
		}
	}
	pb.recorder.Eventf(ref, eventType, reason, message, args...)
}

// probe probes the container.
func (pb *prober) probe(probeType probeType, pod *v1.Pod, status v1.PodStatus, container v1.Container, containerID kubecontainer.ContainerID) (results.Result, error) {
	var probeSpec *v1.Probe
	switch probeType {
	case readiness:
		probeSpec = container.ReadinessProbe
	case liveness:
		probeSpec = container.LivenessProbe
	case startup:
		probeSpec = container.StartupProbe
	default:
		return results.Failure, fmt.Errorf("unknown probe type: %q", probeType)
	}

	ctrName := fmt.Sprintf("%s:%s", format.Pod(pod), container.Name)
	if probeSpec == nil {
		klog.Warningf("%s probe for %s is nil", probeType, ctrName)
		return results.Success, nil
	}

	result, output, err := pb.runProbeWithRetries(probeType, probeSpec, pod, status, container, containerID, maxProbeRetries)
	if err != nil || (result != probe.Success && result != probe.Warning) {
		// Probe failed in one way or another.
		if err != nil {
			klog.V(1).Infof("%s probe for %q errored: %v", probeType, ctrName, err)
			pb.recordContainerEvent(pod, &container, containerID, v1.EventTypeWarning, events.ContainerUnhealthy, "%s probe errored: %v", probeType, err)
		} else { // result != probe.Success
			klog.V(1).Infof("%s probe for %q failed (%v): %s", probeType, ctrName, result, output)
			pb.recordContainerEvent(pod, &container, containerID, v1.EventTypeWarning, events.ContainerUnhealthy, "%s probe failed: %v", probeType, output)
		}
		return results.Failure, err
	}
	if result == probe.Warning {
		pb.recordContainerEvent(pod, &container, containerID, v1.EventTypeWarning, events.ContainerProbeWarning, "%s probe warning: %v", probeType, output)
		klog.V(3).Infof("%s probe for %q succeeded with a warning: %s", probeType, ctrName, output)
	} else {
		klog.V(3).Infof("%s probe for %q succeeded", probeType, ctrName)
	}
	return results.Success, nil
}

// runProbeWithRetries tries to probe the container in a finite loop, it returns the last result
// if it never succeeds.
func (pb *prober) runProbeWithRetries(probeType probeType, p *v1.Probe, pod *v1.Pod, status v1.PodStatus, container v1.Container, containerID kubecontainer.ContainerID, retries int) (probe.Result, string, error) {
	var err error
	var result probe.Result
	var output string
	for i := 0; i < retries; i++ {
		result, output, err = pb.runProbe(probeType, p, pod, status, container, containerID)
		if err == nil {
			return result, output, nil
		}
	}
	return result, output, err
}

// buildHeaderMap takes a list of HTTPHeader <name, value> string
// pairs and returns a populated string->[]string http.Header map.
func buildHeader(headerList []v1.HTTPHeader) http.Header {
	headers := make(http.Header)
	for _, header := range headerList {
		headers[header.Name] = append(headers[header.Name], header.Value)
	}
	return headers
}

func (pb *prober) runProbe(probeType probeType, p *v1.Probe, pod *v1.Pod, status v1.PodStatus, container v1.Container, containerID kubecontainer.ContainerID) (probe.Result, string, error) {
	timeout := time.Duration(p.TimeoutSeconds) * time.Second
	if p.Exec != nil {
		klog.V(4).Infof("Exec-Probe Pod: %v, Container: %v, Command: %v", pod, container, p.Exec.Command)
		command := kubecontainer.ExpandContainerCommandOnlyStatic(p.Exec.Command, container.Env)
		return pb.exec.Probe(pb.newExecInContainer(container, containerID, command, timeout))
	}
	if p.HTTPGet != nil {
		scheme := strings.ToLower(string(p.HTTPGet.Scheme))
		host := p.HTTPGet.Host
		if host == "" {
			host = status.PodIP
		}
		port, err := extractPort(p.HTTPGet.Port, container)
		if err != nil {
			return probe.Unknown, "", err
		}
		path := p.HTTPGet.Path
		klog.V(4).Infof("HTTP-Probe Host: %v://%v, Port: %v, Path: %v", scheme, host, port, path)
		url := formatURL(scheme, host, port, path)
		headers := buildHeader(p.HTTPGet.HTTPHeaders)
		klog.V(4).Infof("HTTP-Probe Headers: %v", headers)
		switch probeType {
		case liveness:
			return pb.livenessHTTP.Probe(url, headers, timeout)
		case startup:
			return pb.startupHTTP.Probe(url, headers, timeout)
		default:
			return pb.readinessHTTP.Probe(url, headers, timeout)
		}
	}
	if p.TCPSocket != nil {
		port, err := extractPort(p.TCPSocket.Port, container)
		if err != nil {
			return probe.Unknown, "", err
		}
		host := p.TCPSocket.Host
		if host == "" {
			host = status.PodIP
		}
		klog.V(4).Infof("TCP-Probe Host: %v, Port: %v, Timeout: %v", host, port, timeout)
		return pb.tcp.Probe(host, port, timeout)
	}
	klog.Warningf("Failed to find probe builder for container: %v", container)
	return probe.Unknown, "", fmt.Errorf("missing probe handler for %s:%s", format.Pod(pod), container.Name)
}

func extractPort(param intstr.IntOrString, container v1.Container) (int, error) {
	port := -1
	var err error
	switch param.Type {
	case intstr.Int:
		port = param.IntValue()
	case intstr.String:
		if port, err = findPortByName(container, param.StrVal); err != nil {
			// Last ditch effort - maybe it was an int stored as string?
			if port, err = strconv.Atoi(param.StrVal); err != nil {
				return port, err
			}
		}
	default:
		return port, fmt.Errorf("intOrString had no kind: %+v", param)
	}
	if port > 0 && port < 65536 {
		return port, nil
	}
	return port, fmt.Errorf("invalid port number: %v", port)
}

// findPortByName is a helper function to look up a port in a container by name.
func findPortByName(container v1.Container, portName string) (int, error) {
	for _, port := range container.Ports {
		if port.Name == portName {
			return int(port.ContainerPort), nil
		}
	}
	return 0, fmt.Errorf("port %s not found", portName)
}

// formatURL formats a URL from args.  For testability.
func formatURL(scheme string, host string, port int, path string) *url.URL {
	u, err := url.Parse(path)
	// Something is busted with the path, but it's too late to reject it. Pass it along as is.
	if err != nil {
		u = &url.URL{
			Path: path,
		}
	}
	u.Scheme = scheme
	u.Host = net.JoinHostPort(host, strconv.Itoa(port))
	return u
}

type execInContainer struct {
	// run executes a command in a container. Combined stdout and stderr output is always returned. An
	// error is returned if one occurred.
	run    func() ([]byte, error)
	writer io.Writer
}

func (pb *prober) newExecInContainer(container v1.Container, containerID kubecontainer.ContainerID, cmd []string, timeout time.Duration) exec.Cmd {
	return &execInContainer{run: func() ([]byte, error) {
		return pb.runner.RunInContainer(containerID, cmd, timeout)
	}}
}

func (eic *execInContainer) Run() error {
	return nil
}

func (eic *execInContainer) CombinedOutput() ([]byte, error) {
	return eic.run()
}

func (eic *execInContainer) Output() ([]byte, error) {
	return nil, fmt.Errorf("unimplemented")
}

func (eic *execInContainer) SetDir(dir string) {
	//unimplemented
}

func (eic *execInContainer) SetStdin(in io.Reader) {
	//unimplemented
}

func (eic *execInContainer) SetStdout(out io.Writer) {
	eic.writer = out
}

func (eic *execInContainer) SetStderr(out io.Writer) {
	eic.writer = out
}

func (eic *execInContainer) SetEnv(env []string) {
	//unimplemented
}

func (eic *execInContainer) Stop() {
	//unimplemented
}

func (eic *execInContainer) Start() error {
	data, err := eic.run()
	if eic.writer != nil {
		eic.writer.Write(data)
	}
	return err
}

func (eic *execInContainer) Wait() error {
	return nil
}

func (eic *execInContainer) StdoutPipe() (io.ReadCloser, error) {
	return nil, fmt.Errorf("unimplemented")
}

func (eic *execInContainer) StderrPipe() (io.ReadCloser, error) {
	return nil, fmt.Errorf("unimplemented")
}
<|endoftext|>


<commit_before>// errchk $G $D/$F.go

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

package main

func main() {
	if {  // ERROR "missing condition"
	}

	if x(); {  // ERROR "missing condition"
	}
}
<commit_msg>test: avoid undefined error in syntax/if.go.<commit_after>// errchk $G $D/$F.go

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

package main

func x() {
}

func main() {
	if {  // ERROR "missing condition"
	}

	if x(); {  // ERROR "missing condition"
	}
}
<|endoftext|>


<commit_before>package playedplugin

import (
	"encoding/json"
	"fmt"
	"log"
	"regexp"
	"sort"
	"strings"
	"sync"
	"time"

	"github.com/dustin/go-humanize"
	"github.com/iopred/bruxism"
	"github.com/iopred/discordgo"
)

type playedEntry struct {
	Name     string
	Duration time.Duration
}

type byDuration []*playedEntry

func (a byDuration) Len() int           { return len(a) }
func (a byDuration) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
func (a byDuration) Less(i, j int) bool { return a[i].Duration < a[j].Duration }

type playedUser struct {
	Entries     map[string]*playedEntry
	Current     string
	LastChanged time.Time
	FirstSeen   time.Time
}

func (p *playedUser) Update(name string, now time.Time) {
	if p.Current != "" {
		pe := p.Entries[p.Current]
		if pe == nil {
			pe = &playedEntry{
				Name: p.Current,
			}
			p.Entries[p.Current] = pe
		}
		pe.Duration += now.Sub(p.LastChanged)
	}

	p.Current = name
	p.LastChanged = now
}

type playedPlugin struct {
	sync.RWMutex
	Users map[string]*playedUser
}

// Load will load plugin state from a byte array.
func (p *playedPlugin) Load(bot *bruxism.Bot, service bruxism.Service, data []byte) error {
	if service.Name() != bruxism.DiscordServiceName {
		panic("Carbonitex Plugin only supports Discord.")
	}

	if data != nil {
		if err := json.Unmarshal(data, p); err != nil {
			log.Println("Error loading data", err)
		}
	}

	go p.Run(bot, service)
	return nil
}

// Save will save plugin state to a byte array.
func (p *playedPlugin) Save() ([]byte, error) {
	return json.Marshal(p)
}

func (p *playedPlugin) Update(user string, entry string) {
	fmt.Println(user, entry)
	p.Lock()
	defer p.Unlock()

	t := time.Now()

	u := p.Users[user]
	if u == nil {
		u = &playedUser{
			Entries:     map[string]*playedEntry{},
			Current:     entry,
			LastChanged: t,
			FirstSeen:   t,
		}
		p.Users[user] = u
	}
	u.Update(entry, t)
}

// Run is the background go routine that executes for the life of the plugin.
func (p *playedPlugin) Run(bot *bruxism.Bot, service bruxism.Service) {
	discord := service.(*bruxism.Discord)

	discord.Session.AddHandler(func(s *discordgo.Session, r *discordgo.Ready) {
		for _, g := range r.Guilds {
			for _, pu := range g.Presences {
				e := ""
				if pu.Game != nil {
					e = pu.Game.Name
				}
				p.Update(pu.User.ID, e)
			}
		}
	})

	discord.Session.AddHandler(func(s *discordgo.Session, g *discordgo.GuildCreate) {
		if g.Unavailable == nil || *g.Unavailable {
			return
		}

		for _, pu := range g.Presences {
			e := ""
			if pu.Game != nil {
				e = pu.Game.Name
			}
			p.Update(pu.User.ID, e)
		}
	})

	discord.Session.AddHandler(func(s *discordgo.Session, pr *discordgo.PresencesReplace) {
		for _, pu := range *pr {
			e := ""
			if pu.Game != nil {
				e = pu.Game.Name
			}
			p.Update(pu.User.ID, e)
		}
	})

	discord.Session.AddHandler(func(s *discordgo.Session, pu *discordgo.PresenceUpdate) {
		e := ""
		if pu.Game != nil {
			e = pu.Game.Name
		}
		p.Update(pu.User.ID, e)
	})
}

// Help returns a list of help strings that are printed when the user requests them.
func (p *playedPlugin) Help(bot *bruxism.Bot, service bruxism.Service, message bruxism.Message, detailed bool) []string {
	if detailed {
		return nil
	}

	return bruxism.CommandHelp(service, "played", "[@username]", "Returns your most played games, or a users most played games if provided.")
}

var userIDRegex = regexp.MustCompile("<@!?([0-9]*)>")

func (p *playedPlugin) Message(bot *bruxism.Bot, service bruxism.Service, message bruxism.Message) {
	defer bruxism.MessageRecover()
	if service.Name() == bruxism.DiscordServiceName && !service.IsMe(message) {
		if bruxism.MatchesCommand(service, "played", message) {
			query := strings.Join(strings.Split(message.RawMessage(), " ")[1:], " ")

			id := message.UserID()
			match := userIDRegex.FindStringSubmatch(query)
			if match != nil {
				id = match[1]
			}

			p.Lock()
			defer p.Unlock()

			u := p.Users[id]
			if u == nil {
				service.SendMessage(message.Channel(), "I haven't seen that user.")
				return
			}

			u.Update(u.Current, time.Now())

			pes := make(byDuration, len(u.Entries))
			i := 0
			for _, pe := range u.Entries {
				pes[i] = pe
				i++
			}

			sort.Sort(pes)

			messageText := fmt.Sprintf("First seen %s.\n", humanize.Time(u.FirstSeen))
			for i = 0; i < len(pes) && i < 5; i++ {
				pe := pes[i]
				messageText += fmt.Sprintf("%s: %.0f hours, %.0f minutes, %.0f seconds\n", pe.Name, pe.Duration.Hours(), pe.Duration.Minutes(), pe.Duration.Seconds())
			}
			service.SendMessage(message.Channel(), messageText)
		}
	}
}

// Name returns the name of the plugin.
func (p *playedPlugin) Name() string {
	return "Played"
}

// New will create a played plugin.
func New() bruxism.Plugin {
	return &playedPlugin{
		Users: map[string]*playedUser{},
	}
}
<commit_msg>Remove trace.<commit_after>package playedplugin

import (
	"encoding/json"
	"fmt"
	"log"
	"regexp"
	"sort"
	"strings"
	"sync"
	"time"

	"github.com/dustin/go-humanize"
	"github.com/iopred/bruxism"
	"github.com/iopred/discordgo"
)

type playedEntry struct {
	Name     string
	Duration time.Duration
}

type byDuration []*playedEntry

func (a byDuration) Len() int           { return len(a) }
func (a byDuration) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
func (a byDuration) Less(i, j int) bool { return a[i].Duration < a[j].Duration }

type playedUser struct {
	Entries     map[string]*playedEntry
	Current     string
	LastChanged time.Time
	FirstSeen   time.Time
}

func (p *playedUser) Update(name string, now time.Time) {
	if p.Current != "" {
		pe := p.Entries[p.Current]
		if pe == nil {
			pe = &playedEntry{
				Name: p.Current,
			}
			p.Entries[p.Current] = pe
		}
		pe.Duration += now.Sub(p.LastChanged)
	}

	p.Current = name
	p.LastChanged = now
}

type playedPlugin struct {
	sync.RWMutex
	Users map[string]*playedUser
}

// Load will load plugin state from a byte array.
func (p *playedPlugin) Load(bot *bruxism.Bot, service bruxism.Service, data []byte) error {
	if service.Name() != bruxism.DiscordServiceName {
		panic("Carbonitex Plugin only supports Discord.")
	}

	if data != nil {
		if err := json.Unmarshal(data, p); err != nil {
			log.Println("Error loading data", err)
		}
	}

	go p.Run(bot, service)
	return nil
}

// Save will save plugin state to a byte array.
func (p *playedPlugin) Save() ([]byte, error) {
	return json.Marshal(p)
}

func (p *playedPlugin) Update(user string, entry string) {
	p.Lock()
	defer p.Unlock()

	t := time.Now()

	u := p.Users[user]
	if u == nil {
		u = &playedUser{
			Entries:     map[string]*playedEntry{},
			Current:     entry,
			LastChanged: t,
			FirstSeen:   t,
		}
		p.Users[user] = u
	}
	u.Update(entry, t)
}

// Run is the background go routine that executes for the life of the plugin.
func (p *playedPlugin) Run(bot *bruxism.Bot, service bruxism.Service) {
	discord := service.(*bruxism.Discord)

	discord.Session.AddHandler(func(s *discordgo.Session, r *discordgo.Ready) {
		for _, g := range r.Guilds {
			for _, pu := range g.Presences {
				e := ""
				if pu.Game != nil {
					e = pu.Game.Name
				}
				p.Update(pu.User.ID, e)
			}
		}
	})

	discord.Session.AddHandler(func(s *discordgo.Session, g *discordgo.GuildCreate) {
		if g.Unavailable == nil || *g.Unavailable {
			return
		}

		for _, pu := range g.Presences {
			e := ""
			if pu.Game != nil {
				e = pu.Game.Name
			}
			p.Update(pu.User.ID, e)
		}
	})

	discord.Session.AddHandler(func(s *discordgo.Session, pr *discordgo.PresencesReplace) {
		for _, pu := range *pr {
			e := ""
			if pu.Game != nil {
				e = pu.Game.Name
			}
			p.Update(pu.User.ID, e)
		}
	})

	discord.Session.AddHandler(func(s *discordgo.Session, pu *discordgo.PresenceUpdate) {
		e := ""
		if pu.Game != nil {
			e = pu.Game.Name
		}
		p.Update(pu.User.ID, e)
	})
}

// Help returns a list of help strings that are printed when the user requests them.
func (p *playedPlugin) Help(bot *bruxism.Bot, service bruxism.Service, message bruxism.Message, detailed bool) []string {
	if detailed {
		return nil
	}

	return bruxism.CommandHelp(service, "played", "[@username]", "Returns your most played games, or a users most played games if provided.")
}

var userIDRegex = regexp.MustCompile("<@!?([0-9]*)>")

func (p *playedPlugin) Message(bot *bruxism.Bot, service bruxism.Service, message bruxism.Message) {
	defer bruxism.MessageRecover()
	if service.Name() == bruxism.DiscordServiceName && !service.IsMe(message) {
		if bruxism.MatchesCommand(service, "played", message) {
			query := strings.Join(strings.Split(message.RawMessage(), " ")[1:], " ")

			id := message.UserID()
			match := userIDRegex.FindStringSubmatch(query)
			if match != nil {
				id = match[1]
			}

			p.Lock()
			defer p.Unlock()

			u := p.Users[id]
			if u == nil {
				service.SendMessage(message.Channel(), "I haven't seen that user.")
				return
			}

			u.Update(u.Current, time.Now())

			pes := make(byDuration, len(u.Entries))
			i := 0
			for _, pe := range u.Entries {
				pes[i] = pe
				i++
			}

			sort.Sort(pes)

			messageText := fmt.Sprintf("First seen %s.\n", humanize.Time(u.FirstSeen))
			for i = 0; i < len(pes) && i < 5; i++ {
				pe := pes[i]
				messageText += fmt.Sprintf("%s: %.0f hours, %.0f minutes, %.0f seconds\n", pe.Name, pe.Duration.Hours(), pe.Duration.Minutes(), pe.Duration.Seconds())
			}
			service.SendMessage(message.Channel(), messageText)
		}
	}
}

// Name returns the name of the plugin.
func (p *playedPlugin) Name() string {
	return "Played"
}

// New will create a played plugin.
func New() bruxism.Plugin {
	return &playedPlugin{
		Users: map[string]*playedUser{},
	}
}
<|endoftext|>


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

// +build !windows,!nacl,!plan9,!js

package syslog

import (
	"bufio"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"net"
	"os"
	"runtime"
	"sync"
	"testing"
	"time"
)

func runPktSyslog(c net.PacketConn, done chan<- string) {
	var buf [4096]byte
	var rcvd string
	ct := 0
	for {
		var n int
		var err error

		c.SetReadDeadline(time.Now().Add(100 * time.Millisecond))
		n, _, err = c.ReadFrom(buf[:])
		rcvd += string(buf[:n])
		if err != nil {
			if oe, ok := err.(*net.OpError); ok {
				if ct < 3 && oe.Temporary() {
					ct++
					continue
				}
			}
			break
		}
	}
	c.Close()
	done <- rcvd
}

var crashy = false

func testableNetwork(network string) bool {
	switch network {
	case "unix", "unixgram":
		switch runtime.GOOS {
		case "darwin":
			switch runtime.GOARCH {
			case "arm", "arm64":
				return false
			}
		case "android":
			return false
		}
	}
	return true
}

func runStreamSyslog(l net.Listener, done chan<- string, wg *sync.WaitGroup) {
	for {
		var c net.Conn
		var err error
		if c, err = l.Accept(); err != nil {
			return
		}
		wg.Add(1)
		go func(c net.Conn) {
			defer wg.Done()
			c.SetReadDeadline(time.Now().Add(5 * time.Second))
			b := bufio.NewReader(c)
			for ct := 1; !crashy || ct&7 != 0; ct++ {
				s, err := b.ReadString('\n')
				if err != nil {
					break
				}
				done <- s
			}
			c.Close()
		}(c)
	}
}

func startServer(n, la string, done chan<- string) (addr string, sock io.Closer, wg *sync.WaitGroup) {
	if n == "udp" || n == "tcp" {
		la = "127.0.0.1:0"
	} else {
		// unix and unixgram: choose an address if none given
		if la == "" {
			// use ioutil.TempFile to get a name that is unique
			f, err := ioutil.TempFile("", "syslogtest")
			if err != nil {
				log.Fatal("TempFile: ", err)
			}
			f.Close()
			la = f.Name()
		}
		os.Remove(la)
	}

	wg = new(sync.WaitGroup)
	if n == "udp" || n == "unixgram" {
		l, e := net.ListenPacket(n, la)
		if e != nil {
			log.Fatalf("startServer failed: %v", e)
		}
		addr = l.LocalAddr().String()
		sock = l
		wg.Add(1)
		go func() {
			defer wg.Done()
			runPktSyslog(l, done)
		}()
	} else {
		l, e := net.Listen(n, la)
		if e != nil {
			log.Fatalf("startServer failed: %v", e)
		}
		addr = l.Addr().String()
		sock = l
		wg.Add(1)
		go func() {
			defer wg.Done()
			runStreamSyslog(l, done, wg)
		}()
	}
	return
}

func TestWithSimulated(t *testing.T) {
	t.Parallel()
	msg := "Test 123"
	var transport []string
	for _, n := range []string{"unix", "unixgram", "udp", "tcp"} {
		if testableNetwork(n) {
			transport = append(transport, n)
		}
	}

	for _, tr := range transport {
		done := make(chan string)
		addr, sock, srvWG := startServer(tr, "", done)
		defer srvWG.Wait()
		defer sock.Close()
		if tr == "unix" || tr == "unixgram" {
			defer os.Remove(addr)
		}
		s, err := Dial(tr, addr, LOG_INFO|LOG_USER, "syslog_test")
		if err != nil {
			t.Fatalf("Dial() failed: %v", err)
		}
		err = s.Info(msg)
		if err != nil {
			t.Fatalf("log failed: %v", err)
		}
		check(t, msg, <-done)
		s.Close()
	}
}

func TestFlap(t *testing.T) {
	net := "unix"
	if !testableNetwork(net) {
		t.Skipf("skipping on %s/%s; 'unix' is not supported", runtime.GOOS, runtime.GOARCH)
	}

	done := make(chan string)
	addr, sock, srvWG := startServer(net, "", done)
	defer srvWG.Wait()
	defer os.Remove(addr)
	defer sock.Close()

	s, err := Dial(net, addr, LOG_INFO|LOG_USER, "syslog_test")
	if err != nil {
		t.Fatalf("Dial() failed: %v", err)
	}
	msg := "Moo 2"
	err = s.Info(msg)
	if err != nil {
		t.Fatalf("log failed: %v", err)
	}
	check(t, msg, <-done)

	// restart the server
	_, sock2, srvWG2 := startServer(net, addr, done)
	defer srvWG2.Wait()
	defer sock2.Close()

	// and try retransmitting
	msg = "Moo 3"
	err = s.Info(msg)
	if err != nil {
		t.Fatalf("log failed: %v", err)
	}
	check(t, msg, <-done)

	s.Close()
}

func TestNew(t *testing.T) {
	if LOG_LOCAL7 != 23<<3 {
		t.Fatalf("LOG_LOCAL7 has wrong value")
	}
	if testing.Short() {
		// Depends on syslog daemon running, and sometimes it's not.
		t.Skip("skipping syslog test during -short")
	}

	s, err := New(LOG_INFO|LOG_USER, "the_tag")
	if err != nil {
		if err.Error() == "Unix syslog delivery error" {
			t.Skip("skipping: syslogd not running")
		}
		t.Fatalf("New() failed: %s", err)
	}
	// Don't send any messages.
	s.Close()
}

func TestNewLogger(t *testing.T) {
	if testing.Short() {
		t.Skip("skipping syslog test during -short")
	}
	f, err := NewLogger(LOG_USER|LOG_INFO, 0)
	if f == nil {
		if err.Error() == "Unix syslog delivery error" {
			t.Skip("skipping: syslogd not running")
		}
		t.Error(err)
	}
}

func TestDial(t *testing.T) {
	if testing.Short() {
		t.Skip("skipping syslog test during -short")
	}
	f, err := Dial("", "", (LOG_LOCAL7|LOG_DEBUG)+1, "syslog_test")
	if f != nil {
		t.Fatalf("Should have trapped bad priority")
	}
	f, err = Dial("", "", -1, "syslog_test")
	if f != nil {
		t.Fatalf("Should have trapped bad priority")
	}
	l, err := Dial("", "", LOG_USER|LOG_ERR, "syslog_test")
	if err != nil {
		if err.Error() == "Unix syslog delivery error" {
			t.Skip("skipping: syslogd not running")
		}
		t.Fatalf("Dial() failed: %s", err)
	}
	l.Close()
}

func check(t *testing.T, in, out string) {
	tmpl := fmt.Sprintf("<%d>%%s %%s syslog_test[%%d]: %s\n", LOG_USER+LOG_INFO, in)
	if hostname, err := os.Hostname(); err != nil {
		t.Error("Error retrieving hostname")
	} else {
		var parsedHostname, timestamp string
		var pid int
		if n, err := fmt.Sscanf(out, tmpl, &timestamp, &parsedHostname, &pid); n != 3 || err != nil || hostname != parsedHostname {
			t.Errorf("Got %q, does not match template %q (%d %s)", out, tmpl, n, err)
		}
	}
}

func TestWrite(t *testing.T) {
	t.Parallel()
	tests := []struct {
		pri Priority
		pre string
		msg string
		exp string
	}{
		{LOG_USER | LOG_ERR, "syslog_test", "", "%s %s syslog_test[%d]: \n"},
		{LOG_USER | LOG_ERR, "syslog_test", "write test", "%s %s syslog_test[%d]: write test\n"},
		// Write should not add \n if there already is one
		{LOG_USER | LOG_ERR, "syslog_test", "write test 2\n", "%s %s syslog_test[%d]: write test 2\n"},
	}

	if hostname, err := os.Hostname(); err != nil {
		t.Fatalf("Error retrieving hostname")
	} else {
		for _, test := range tests {
			done := make(chan string)
			addr, sock, srvWG := startServer("udp", "", done)
			defer srvWG.Wait()
			defer sock.Close()
			l, err := Dial("udp", addr, test.pri, test.pre)
			if err != nil {
				t.Fatalf("syslog.Dial() failed: %v", err)
			}
			defer l.Close()
			_, err = io.WriteString(l, test.msg)
			if err != nil {
				t.Fatalf("WriteString() failed: %v", err)
			}
			rcvd := <-done
			test.exp = fmt.Sprintf("<%d>", test.pri) + test.exp
			var parsedHostname, timestamp string
			var pid int
			if n, err := fmt.Sscanf(rcvd, test.exp, &timestamp, &parsedHostname, &pid); n != 3 || err != nil || hostname != parsedHostname {
				t.Errorf("s.Info() = '%q', didn't match '%q' (%d %s)", rcvd, test.exp, n, err)
			}
		}
	}
}

func TestConcurrentWrite(t *testing.T) {
	addr, sock, srvWG := startServer("udp", "", make(chan string, 1))
	defer srvWG.Wait()
	defer sock.Close()
	w, err := Dial("udp", addr, LOG_USER|LOG_ERR, "how's it going?")
	if err != nil {
		t.Fatalf("syslog.Dial() failed: %v", err)
	}
	var wg sync.WaitGroup
	for i := 0; i < 10; i++ {
		wg.Add(1)
		go func() {
			defer wg.Done()
			err := w.Info("test")
			if err != nil {
				t.Errorf("Info() failed: %v", err)
				return
			}
		}()
	}
	wg.Wait()
}

func TestConcurrentReconnect(t *testing.T) {
	crashy = true
	defer func() { crashy = false }()

	const N = 10
	const M = 100
	net := "unix"
	if !testableNetwork(net) {
		net = "tcp"
		if !testableNetwork(net) {
			t.Skipf("skipping on %s/%s; neither 'unix' or 'tcp' is supported", runtime.GOOS, runtime.GOARCH)
		}
	}
	done := make(chan string, N*M)
	addr, sock, srvWG := startServer(net, "", done)
	if net == "unix" {
		defer os.Remove(addr)
	}

	// count all the messages arriving
	count := make(chan int)
	go func() {
		ct := 0
		for range done {
			ct++
			// we are looking for 500 out of 1000 events
			// here because lots of log messages are lost
			// in buffers (kernel and/or bufio)
			if ct > N*M/2 {
				break
			}
		}
		count <- ct
	}()

	var wg sync.WaitGroup
	wg.Add(N)
	for i := 0; i < N; i++ {
		go func() {
			defer wg.Done()
			w, err := Dial(net, addr, LOG_USER|LOG_ERR, "tag")
			if err != nil {
				t.Errorf("syslog.Dial() failed: %v", err)
				return
			}
			defer w.Close()
			for i := 0; i < M; i++ {
				err := w.Info("test")
				if err != nil {
					t.Errorf("Info() failed: %v", err)
					return
				}
			}
		}()
	}
	wg.Wait()
	sock.Close()
	srvWG.Wait()
	close(done)

	select {
	case <-count:
	case <-time.After(100 * time.Millisecond):
		t.Error("timeout in concurrent reconnect")
	}
}
<commit_msg>log/syslog: skip unsupported tests on iOS<commit_after>// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// +build !windows,!nacl,!plan9,!js

package syslog

import (
	"bufio"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"net"
	"os"
	"runtime"
	"sync"
	"testing"
	"time"
)

func runPktSyslog(c net.PacketConn, done chan<- string) {
	var buf [4096]byte
	var rcvd string
	ct := 0
	for {
		var n int
		var err error

		c.SetReadDeadline(time.Now().Add(100 * time.Millisecond))
		n, _, err = c.ReadFrom(buf[:])
		rcvd += string(buf[:n])
		if err != nil {
			if oe, ok := err.(*net.OpError); ok {
				if ct < 3 && oe.Temporary() {
					ct++
					continue
				}
			}
			break
		}
	}
	c.Close()
	done <- rcvd
}

var crashy = false

func testableNetwork(network string) bool {
	switch network {
	case "unix", "unixgram":
		switch runtime.GOOS {
		case "darwin":
			switch runtime.GOARCH {
			case "arm", "arm64":
				return false
			}
		case "android":
			return false
		}
	}
	return true
}

func runStreamSyslog(l net.Listener, done chan<- string, wg *sync.WaitGroup) {
	for {
		var c net.Conn
		var err error
		if c, err = l.Accept(); err != nil {
			return
		}
		wg.Add(1)
		go func(c net.Conn) {
			defer wg.Done()
			c.SetReadDeadline(time.Now().Add(5 * time.Second))
			b := bufio.NewReader(c)
			for ct := 1; !crashy || ct&7 != 0; ct++ {
				s, err := b.ReadString('\n')
				if err != nil {
					break
				}
				done <- s
			}
			c.Close()
		}(c)
	}
}

func startServer(n, la string, done chan<- string) (addr string, sock io.Closer, wg *sync.WaitGroup) {
	if n == "udp" || n == "tcp" {
		la = "127.0.0.1:0"
	} else {
		// unix and unixgram: choose an address if none given
		if la == "" {
			// use ioutil.TempFile to get a name that is unique
			f, err := ioutil.TempFile("", "syslogtest")
			if err != nil {
				log.Fatal("TempFile: ", err)
			}
			f.Close()
			la = f.Name()
		}
		os.Remove(la)
	}

	wg = new(sync.WaitGroup)
	if n == "udp" || n == "unixgram" {
		l, e := net.ListenPacket(n, la)
		if e != nil {
			log.Fatalf("startServer failed: %v", e)
		}
		addr = l.LocalAddr().String()
		sock = l
		wg.Add(1)
		go func() {
			defer wg.Done()
			runPktSyslog(l, done)
		}()
	} else {
		l, e := net.Listen(n, la)
		if e != nil {
			log.Fatalf("startServer failed: %v", e)
		}
		addr = l.Addr().String()
		sock = l
		wg.Add(1)
		go func() {
			defer wg.Done()
			runStreamSyslog(l, done, wg)
		}()
	}
	return
}

func TestWithSimulated(t *testing.T) {
	if runtime.GOOS == "darwin" && (runtime.GOARCH == "arm" || runtime.GOARCH == "arm64") {
		t.Skipf("sysctl is not supported on iOS")
	}
	t.Parallel()
	msg := "Test 123"
	var transport []string
	for _, n := range []string{"unix", "unixgram", "udp", "tcp"} {
		if testableNetwork(n) {
			transport = append(transport, n)
		}
	}

	for _, tr := range transport {
		done := make(chan string)
		addr, sock, srvWG := startServer(tr, "", done)
		defer srvWG.Wait()
		defer sock.Close()
		if tr == "unix" || tr == "unixgram" {
			defer os.Remove(addr)
		}
		s, err := Dial(tr, addr, LOG_INFO|LOG_USER, "syslog_test")
		if err != nil {
			t.Fatalf("Dial() failed: %v", err)
		}
		err = s.Info(msg)
		if err != nil {
			t.Fatalf("log failed: %v", err)
		}
		check(t, msg, <-done)
		s.Close()
	}
}

func TestFlap(t *testing.T) {
	net := "unix"
	if !testableNetwork(net) {
		t.Skipf("skipping on %s/%s; 'unix' is not supported", runtime.GOOS, runtime.GOARCH)
	}

	done := make(chan string)
	addr, sock, srvWG := startServer(net, "", done)
	defer srvWG.Wait()
	defer os.Remove(addr)
	defer sock.Close()

	s, err := Dial(net, addr, LOG_INFO|LOG_USER, "syslog_test")
	if err != nil {
		t.Fatalf("Dial() failed: %v", err)
	}
	msg := "Moo 2"
	err = s.Info(msg)
	if err != nil {
		t.Fatalf("log failed: %v", err)
	}
	check(t, msg, <-done)

	// restart the server
	_, sock2, srvWG2 := startServer(net, addr, done)
	defer srvWG2.Wait()
	defer sock2.Close()

	// and try retransmitting
	msg = "Moo 3"
	err = s.Info(msg)
	if err != nil {
		t.Fatalf("log failed: %v", err)
	}
	check(t, msg, <-done)

	s.Close()
}

func TestNew(t *testing.T) {
	if LOG_LOCAL7 != 23<<3 {
		t.Fatalf("LOG_LOCAL7 has wrong value")
	}
	if testing.Short() {
		// Depends on syslog daemon running, and sometimes it's not.
		t.Skip("skipping syslog test during -short")
	}

	s, err := New(LOG_INFO|LOG_USER, "the_tag")
	if err != nil {
		if err.Error() == "Unix syslog delivery error" {
			t.Skip("skipping: syslogd not running")
		}
		t.Fatalf("New() failed: %s", err)
	}
	// Don't send any messages.
	s.Close()
}

func TestNewLogger(t *testing.T) {
	if testing.Short() {
		t.Skip("skipping syslog test during -short")
	}
	f, err := NewLogger(LOG_USER|LOG_INFO, 0)
	if f == nil {
		if err.Error() == "Unix syslog delivery error" {
			t.Skip("skipping: syslogd not running")
		}
		t.Error(err)
	}
}

func TestDial(t *testing.T) {
	if testing.Short() {
		t.Skip("skipping syslog test during -short")
	}
	f, err := Dial("", "", (LOG_LOCAL7|LOG_DEBUG)+1, "syslog_test")
	if f != nil {
		t.Fatalf("Should have trapped bad priority")
	}
	f, err = Dial("", "", -1, "syslog_test")
	if f != nil {
		t.Fatalf("Should have trapped bad priority")
	}
	l, err := Dial("", "", LOG_USER|LOG_ERR, "syslog_test")
	if err != nil {
		if err.Error() == "Unix syslog delivery error" {
			t.Skip("skipping: syslogd not running")
		}
		t.Fatalf("Dial() failed: %s", err)
	}
	l.Close()
}

func check(t *testing.T, in, out string) {
	tmpl := fmt.Sprintf("<%d>%%s %%s syslog_test[%%d]: %s\n", LOG_USER+LOG_INFO, in)
	if hostname, err := os.Hostname(); err != nil {
		t.Error("Error retrieving hostname")
	} else {
		var parsedHostname, timestamp string
		var pid int
		if n, err := fmt.Sscanf(out, tmpl, &timestamp, &parsedHostname, &pid); n != 3 || err != nil || hostname != parsedHostname {
			t.Errorf("Got %q, does not match template %q (%d %s)", out, tmpl, n, err)
		}
	}
}

func TestWrite(t *testing.T) {
	if runtime.GOOS == "darwin" && (runtime.GOARCH == "arm" || runtime.GOARCH == "arm64") {
		t.Skipf("sysctl is not supported on iOS")
	}
	t.Parallel()
	tests := []struct {
		pri Priority
		pre string
		msg string
		exp string
	}{
		{LOG_USER | LOG_ERR, "syslog_test", "", "%s %s syslog_test[%d]: \n"},
		{LOG_USER | LOG_ERR, "syslog_test", "write test", "%s %s syslog_test[%d]: write test\n"},
		// Write should not add \n if there already is one
		{LOG_USER | LOG_ERR, "syslog_test", "write test 2\n", "%s %s syslog_test[%d]: write test 2\n"},
	}

	if hostname, err := os.Hostname(); err != nil {
		t.Fatalf("Error retrieving hostname")
	} else {
		for _, test := range tests {
			done := make(chan string)
			addr, sock, srvWG := startServer("udp", "", done)
			defer srvWG.Wait()
			defer sock.Close()
			l, err := Dial("udp", addr, test.pri, test.pre)
			if err != nil {
				t.Fatalf("syslog.Dial() failed: %v", err)
			}
			defer l.Close()
			_, err = io.WriteString(l, test.msg)
			if err != nil {
				t.Fatalf("WriteString() failed: %v", err)
			}
			rcvd := <-done
			test.exp = fmt.Sprintf("<%d>", test.pri) + test.exp
			var parsedHostname, timestamp string
			var pid int
			if n, err := fmt.Sscanf(rcvd, test.exp, &timestamp, &parsedHostname, &pid); n != 3 || err != nil || hostname != parsedHostname {
				t.Errorf("s.Info() = '%q', didn't match '%q' (%d %s)", rcvd, test.exp, n, err)
			}
		}
	}
}

func TestConcurrentWrite(t *testing.T) {
	addr, sock, srvWG := startServer("udp", "", make(chan string, 1))
	defer srvWG.Wait()
	defer sock.Close()
	w, err := Dial("udp", addr, LOG_USER|LOG_ERR, "how's it going?")
	if err != nil {
		t.Fatalf("syslog.Dial() failed: %v", err)
	}
	var wg sync.WaitGroup
	for i := 0; i < 10; i++ {
		wg.Add(1)
		go func() {
			defer wg.Done()
			err := w.Info("test")
			if err != nil {
				t.Errorf("Info() failed: %v", err)
				return
			}
		}()
	}
	wg.Wait()
}

func TestConcurrentReconnect(t *testing.T) {
	crashy = true
	defer func() { crashy = false }()

	const N = 10
	const M = 100
	net := "unix"
	if !testableNetwork(net) {
		net = "tcp"
		if !testableNetwork(net) {
			t.Skipf("skipping on %s/%s; neither 'unix' or 'tcp' is supported", runtime.GOOS, runtime.GOARCH)
		}
	}
	done := make(chan string, N*M)
	addr, sock, srvWG := startServer(net, "", done)
	if net == "unix" {
		defer os.Remove(addr)
	}

	// count all the messages arriving
	count := make(chan int)
	go func() {
		ct := 0
		for range done {
			ct++
			// we are looking for 500 out of 1000 events
			// here because lots of log messages are lost
			// in buffers (kernel and/or bufio)
			if ct > N*M/2 {
				break
			}
		}
		count <- ct
	}()

	var wg sync.WaitGroup
	wg.Add(N)
	for i := 0; i < N; i++ {
		go func() {
			defer wg.Done()
			w, err := Dial(net, addr, LOG_USER|LOG_ERR, "tag")
			if err != nil {
				t.Errorf("syslog.Dial() failed: %v", err)
				return
			}
			defer w.Close()
			for i := 0; i < M; i++ {
				err := w.Info("test")
				if err != nil {
					t.Errorf("Info() failed: %v", err)
					return
				}
			}
		}()
	}
	wg.Wait()
	sock.Close()
	srvWG.Wait()
	close(done)

	select {
	case <-count:
	case <-time.After(100 * time.Millisecond):
		t.Error("timeout in concurrent reconnect")
	}
}
<|endoftext|>


<commit_before>// Package homebrew implements the Home Brew DMR IPSC protocol
package homebrew

import (
	"bytes"
	"crypto/sha256"
	"encoding/hex"
	"errors"
	"fmt"
	"log"
	"net"
	"runtime"
	"strings"
	"sync/atomic"
	"time"

	"github.com/tehmaze/go-dmr/ipsc"
)

var (
	Version    = "20151208"
	SoftwareID = fmt.Sprintf("%s:go-dmr:%s", runtime.GOOS, Version)
	PackageID  = fmt.Sprintf("%s:go-dmr:%s-%s", runtime.GOOS, Version, runtime.GOARCH)
)

// RepeaterConfiguration holds information about the current repeater. It
// should be returned by a callback in the implementation, returning actual
// information about the current repeater status.
type RepeaterConfiguration struct {
	Callsign    string
	RepeaterID  uint32
	RXFreq      uint32
	TXFreq      uint32
	TXPower     uint8
	ColorCode   uint8
	Latitude    float32
	Longitude   float32
	Height      uint16
	Location    string
	Description string
	URL         string
}

// Bytes returns the configuration as bytes.
func (r *RepeaterConfiguration) Bytes() []byte {
	return []byte(r.String())
}

// String returns the configuration as string.
func (r *RepeaterConfiguration) String() string {
	if r.ColorCode < 1 {
		r.ColorCode = 1
	}
	if r.ColorCode > 15 {
		r.ColorCode = 15
	}
	if r.TXPower > 99 {
		r.TXPower = 99
	}

	var lat = fmt.Sprintf("%-08f", r.Latitude)
	if len(lat) > 8 {
		lat = lat[:8]
	}
	var lon = fmt.Sprintf("%-09f", r.Longitude)
	if len(lon) > 9 {
		lon = lon[:9]
	}

	var b = "RPTC"
	b += fmt.Sprintf("%-8s", r.Callsign)
	b += fmt.Sprintf("%08x", r.RepeaterID)
	b += fmt.Sprintf("%09d", r.RXFreq)
	b += fmt.Sprintf("%09d", r.TXFreq)
	b += fmt.Sprintf("%02d", r.TXPower)
	b += fmt.Sprintf("%02d", r.ColorCode)
	b += lat
	b += lon
	b += fmt.Sprintf("%03d", r.Height)
	b += fmt.Sprintf("%-20s", r.Location)
	b += fmt.Sprintf("%-20s", r.Description)
	b += fmt.Sprintf("%-124s", r.URL)
	b += fmt.Sprintf("%-40s", SoftwareID)
	b += fmt.Sprintf("%-40s", PackageID)
	return b
}

// ConfigFunc returns an actual RepeaterConfiguration instance when called.
// This is used by the DMR repeater to poll for current configuration,
// statistics and metrics.
type ConfigFunc func() *RepeaterConfiguration

// CallType reflects the DMR data frame call type.
type CallType byte

const (
	GroupCall CallType = iota
	UnitCall
)

// StreamFunc is called by the DMR repeater when a DMR data frame is received.
type StreamFunc func(*ipsc.Packet)

type authStatus byte

const (
	authNone authStatus = iota
	authBegin
	authDone
	authFail
)

type Network struct {
	AuthKey  string
	Local    string
	LocalID  uint32
	Master   string
	MasterID uint32
}

type packet struct {
	addr *net.UDPAddr
	data []byte
}

type Link struct {
	Dump    bool
	config  ConfigFunc
	stream  StreamFunc
	network *Network
	conn    *net.UDPConn
	authKey []byte
	local   struct {
		addr *net.UDPAddr
		id   []byte
	}
	master struct {
		addr      *net.UDPAddr
		id        []byte
		status    authStatus
		secret    []byte
		keepalive struct {
			outstanding uint32
			sent        uint64
		}
	}
}

// New starts a new DMR repeater using the Home Brew protocol.
func New(network *Network, cf ConfigFunc, sf StreamFunc) (*Link, error) {
	if cf == nil {
		return nil, errors.New("config func can't be nil")
	}

	link := &Link{
		network: network,
		config:  cf,
		stream:  sf,
	}

	var err error
	if strings.HasPrefix(network.AuthKey, "0x") {
		if link.authKey, err = hex.DecodeString(network.AuthKey[2:]); err != nil {
			return nil, err
		}
	} else {
		link.authKey = []byte(network.AuthKey)
	}
	if network.Local == "" {
		network.Local = "0.0.0.0:62030"
	}
	if network.LocalID == 0 {
		return nil, errors.New("missing localid")
	}
	link.local.id = []byte(fmt.Sprintf("%08x", network.LocalID))
	if link.local.addr, err = net.ResolveUDPAddr("udp", network.Local); err != nil {
		return nil, err
	}
	if network.Master == "" {
		return nil, errors.New("no master address configured")
	}
	if link.master.addr, err = net.ResolveUDPAddr("udp", network.Master); err != nil {
		return nil, err
	}

	return link, nil
}

// Run starts the datagram receiver and logs the repeater in with the master.
func (l *Link) Run() error {
	var err error

	if l.conn, err = net.ListenUDP("udp", l.local.addr); err != nil {
		return err
	}

	queue := make(chan packet)
	go l.login()
	go l.parse(queue)

	for {
		var (
			n    int
			peer *net.UDPAddr
			data = make([]byte, 512)
		)
		if n, peer, err = l.conn.ReadFromUDP(data); err != nil {
			log.Printf("dmr/homebrew: error reading from %s: %v\n", peer, err)
			continue
		}

		queue <- packet{peer, data[:n]}
	}

	return nil
}

// Send data to an UDP address using the repeater datagram socket.
func (l *Link) Send(addr *net.UDPAddr, data []byte) error {
	for len(data) > 0 {
		n, err := l.conn.WriteToUDP(data, addr)
		if err != nil {
			return err
		}
		data = data[n:]
	}
	return nil
}

func (l *Link) login() {
	var previous = authDone
	for l.master.status != authFail {
		var p []byte

		if l.master.status != previous {
			switch l.master.status {
			case authNone:
				log.Printf("dmr/homebrew: logging in as %d\n", l.network.LocalID)
				p = append(RepeaterLogin, l.local.id...)

			case authBegin:
				log.Printf("dmr/homebrew: authenticating as %d\n", l.network.LocalID)
				p = append(RepeaterKey, l.local.id...)

				hash := sha256.New()
				hash.Write(l.master.secret)
				hash.Write(l.authKey)

				p = append(p, []byte(hex.EncodeToString(hash.Sum(nil)))...)

			case authDone:
				config := l.config().Bytes()
				if l.Dump {
					fmt.Printf(hex.Dump(config))
				}
				log.Printf("dmr/homebrew: logged in, sending %d bytes of repeater configuration\n", len(config))

				if err := l.Send(l.master.addr, config); err != nil {
					log.Printf("dmr/homebrew: send(%s) failed: %v\n", l.master.addr, err)
					return
				}
				l.keepAlive()
				return

			case authFail:
				log.Println("dmr/homebrew: login failed")
				return
			}
			if p != nil {
				l.Send(l.master.addr, p)
			}
			previous = l.master.status
		} else {
			log.Println("dmr/homebrew: waiting for master to respond in login sequence...")
			time.Sleep(time.Second)
		}
	}
}

func (l *Link) keepAlive() {
	for {
		atomic.AddUint32(&l.master.keepalive.outstanding, 1)
		atomic.AddUint64(&l.master.keepalive.sent, 1)
		var p = append(MasterPing, l.local.id...)
		if err := l.Send(l.master.addr, p); err != nil {
			log.Printf("dmr/homebrew: send(%s) failed: %v\n", l.master.addr, err)
			return
		}
		time.Sleep(time.Minute)
	}
}

func (l *Link) parse(queue <-chan packet) {
	for {
		select {
		case p := <-queue:
			size := len(p.data)
			if size < 4 {
				continue
			}

			switch l.master.status {
			case authNone:
				if bytes.Equal(p.data[:4], DMRData) {
					return
				}
				if size < 14 {
					return
				}
				packet := p.data[:6]
				repeater, err := hex.DecodeString(string(p.data[6:14]))
				if err != nil {
					log.Println("dmr/homebrew: unexpected login reply from master")
					l.master.status = authFail
					break
				}

				switch {
				case bytes.Equal(packet, MasterNAK):
					log.Printf("dmr/homebrew: login refused by master %d\n", repeater)
					l.master.status = authFail
					break
				case bytes.Equal(packet, MasterACK):
					log.Printf("dmr/homebrew: login accepted by master %d\n", repeater)
					l.master.secret = p.data[14:]
					l.master.status = authBegin
					break
				default:
					log.Printf("dmr/homebrew: unexpected login reply from master %d\n", repeater)
					l.master.status = authFail
					break
				}

			case authBegin:
				if bytes.Equal(p.data[:4], DMRData) {
					return
				}
				if size < 14 {
					log.Println("dmr/homebrew: unexpected login reply from master")
					l.master.status = authFail
					break
				}
				packet := p.data[:6]
				repeater, err := hex.DecodeString(string(p.data[6:14]))
				if err != nil {
					log.Println("dmr/homebrew: unexpected login reply from master")
					l.master.status = authFail
					break
				}

				switch {
				case bytes.Equal(packet, MasterNAK):
					log.Printf("dmr/homebrew: authentication refused by master %d\n", repeater)
					l.master.status = authFail
					break
				case bytes.Equal(packet, MasterACK):
					log.Printf("dmr/homebrew: authentication accepted by master %d\n", repeater)
					l.master.status = authDone
					break
				default:
					log.Printf("dmr/homebrew: unexpected authentication reply from master %d\n", repeater)
					l.master.status = authFail
					break
				}

			case authDone:
				if l.Dump {
					fmt.Printf("received from %s:\n", p.addr)
					fmt.Print(hex.Dump(p.data))
				}
				switch {
				case bytes.Equal(p.data[:4], DMRData):
					l.parseDMR(p.data)
				}
			}
		}
	}
}

func (l *Link) parseDMR(d []byte) {
	// If we have no stream callback, don't even bother to decode the DMR data frame.
	if l.stream == nil {
		return
	}

	var (
		p   *ipsc.Packet
		err error
	)
	if p, err = ParseData(d); err != nil {
		log.Printf("dmr/homebrew: error parsing DMRD frame: %v\n", err)
		return
	}
	l.stream(p)
}
<commit_msg>homebrew: allow Close()<commit_after>// Package homebrew implements the Home Brew DMR IPSC protocol
package homebrew

import (
	"bytes"
	"crypto/sha256"
	"encoding/hex"
	"errors"
	"fmt"
	"log"
	"net"
	"runtime"
	"strings"
	"sync/atomic"
	"time"

	"github.com/tehmaze/go-dmr/ipsc"
)

var (
	Version    = "20151208"
	SoftwareID = fmt.Sprintf("%s:go-dmr:%s", runtime.GOOS, Version)
	PackageID  = fmt.Sprintf("%s:go-dmr:%s-%s", runtime.GOOS, Version, runtime.GOARCH)
)

// RepeaterConfiguration holds information about the current repeater. It
// should be returned by a callback in the implementation, returning actual
// information about the current repeater status.
type RepeaterConfiguration struct {
	Callsign    string
	RepeaterID  uint32
	RXFreq      uint32
	TXFreq      uint32
	TXPower     uint8
	ColorCode   uint8
	Latitude    float32
	Longitude   float32
	Height      uint16
	Location    string
	Description string
	URL         string
}

// Bytes returns the configuration as bytes.
func (r *RepeaterConfiguration) Bytes() []byte {
	return []byte(r.String())
}

// String returns the configuration as string.
func (r *RepeaterConfiguration) String() string {
	if r.ColorCode < 1 {
		r.ColorCode = 1
	}
	if r.ColorCode > 15 {
		r.ColorCode = 15
	}
	if r.TXPower > 99 {
		r.TXPower = 99
	}

	var lat = fmt.Sprintf("%-08f", r.Latitude)
	if len(lat) > 8 {
		lat = lat[:8]
	}
	var lon = fmt.Sprintf("%-09f", r.Longitude)
	if len(lon) > 9 {
		lon = lon[:9]
	}

	var b = "RPTC"
	b += fmt.Sprintf("%-8s", r.Callsign)
	b += fmt.Sprintf("%08x", r.RepeaterID)
	b += fmt.Sprintf("%09d", r.RXFreq)
	b += fmt.Sprintf("%09d", r.TXFreq)
	b += fmt.Sprintf("%02d", r.TXPower)
	b += fmt.Sprintf("%02d", r.ColorCode)
	b += lat
	b += lon
	b += fmt.Sprintf("%03d", r.Height)
	b += fmt.Sprintf("%-20s", r.Location)
	b += fmt.Sprintf("%-20s", r.Description)
	b += fmt.Sprintf("%-124s", r.URL)
	b += fmt.Sprintf("%-40s", SoftwareID)
	b += fmt.Sprintf("%-40s", PackageID)
	return b
}

// ConfigFunc returns an actual RepeaterConfiguration instance when called.
// This is used by the DMR repeater to poll for current configuration,
// statistics and metrics.
type ConfigFunc func() *RepeaterConfiguration

// CallType reflects the DMR data frame call type.
type CallType byte

const (
	GroupCall CallType = iota
	UnitCall
)

// StreamFunc is called by the DMR repeater when a DMR data frame is received.
type StreamFunc func(*ipsc.Packet)

type authStatus byte

const (
	authNone authStatus = iota
	authBegin
	authDone
	authFail
)

type Network struct {
	AuthKey  string
	Local    string
	LocalID  uint32
	Master   string
	MasterID uint32
}

type packet struct {
	addr *net.UDPAddr
	data []byte
}

type Link struct {
	Dump    bool
	config  ConfigFunc
	stream  StreamFunc
	network *Network
	conn    *net.UDPConn
	authKey []byte
	local   struct {
		addr *net.UDPAddr
		id   []byte
	}
	master struct {
		addr      *net.UDPAddr
		id        []byte
		status    authStatus
		secret    []byte
		keepalive struct {
			outstanding uint32
			sent        uint64
		}
	}
}

// New starts a new DMR repeater using the Home Brew protocol.
func New(network *Network, cf ConfigFunc, sf StreamFunc) (*Link, error) {
	if cf == nil {
		return nil, errors.New("config func can't be nil")
	}

	link := &Link{
		network: network,
		config:  cf,
		stream:  sf,
	}

	var err error
	if strings.HasPrefix(network.AuthKey, "0x") {
		if link.authKey, err = hex.DecodeString(network.AuthKey[2:]); err != nil {
			return nil, err
		}
	} else {
		link.authKey = []byte(network.AuthKey)
	}
	if network.Local == "" {
		network.Local = "0.0.0.0:62030"
	}
	if network.LocalID == 0 {
		return nil, errors.New("missing localid")
	}
	link.local.id = []byte(fmt.Sprintf("%08x", network.LocalID))
	if link.local.addr, err = net.ResolveUDPAddr("udp", network.Local); err != nil {
		return nil, err
	}
	if network.Master == "" {
		return nil, errors.New("no master address configured")
	}
	if link.master.addr, err = net.ResolveUDPAddr("udp", network.Master); err != nil {
		return nil, err
	}

	return link, nil
}

// Close stops the socket and stops the runner
func (l *Link) Close() error {
	if l.conn == nil {
		return errors.New("dmr/homebrew: link not open")
	}
	if l.master.addr != nil {
		l.Send(l.master.addr, append(RepeaterClosing, l.local.id...))
	}

	return l.conn.Close()
}

// Run starts the datagram receiver and logs the repeater in with the master.
func (l *Link) Run() error {
	var err error

	if l.conn, err = net.ListenUDP("udp", l.local.addr); err != nil {
		return err
	}

	queue := make(chan packet)
	go l.login()
	go l.parse(queue)

receiving:
	for {
		var (
			n    int
			peer *net.UDPAddr
			data = make([]byte, 512)
		)
		if n, peer, err = l.conn.ReadFromUDP(data); err != nil {
			if peer == nil {
				break receiving
			}
			log.Printf("dmr/homebrew: error reading from %s: %v\n", peer, err)
			continue
		}

		queue <- packet{peer, data[:n]}
	}

	// Because we close it in .Close()
	if strings.HasSuffix(err.Error(), ": use of closed network connection") {
		return nil
	}
	return err
}

// Send data to an UDP address using the repeater datagram socket.
func (l *Link) Send(addr *net.UDPAddr, data []byte) error {
	for len(data) > 0 {
		n, err := l.conn.WriteToUDP(data, addr)
		if err != nil {
			return err
		}
		data = data[n:]
	}
	return nil
}

func (l *Link) login() {
	var previous = authDone
	for l.master.status != authFail {
		var p []byte

		if l.master.status != previous {
			switch l.master.status {
			case authNone:
				log.Printf("dmr/homebrew: logging in as %d\n", l.network.LocalID)
				p = append(RepeaterLogin, l.local.id...)

			case authBegin:
				log.Printf("dmr/homebrew: authenticating as %d\n", l.network.LocalID)
				p = append(RepeaterKey, l.local.id...)

				hash := sha256.New()
				hash.Write(l.master.secret)
				hash.Write(l.authKey)

				p = append(p, []byte(hex.EncodeToString(hash.Sum(nil)))...)

			case authDone:
				config := l.config().Bytes()
				if l.Dump {
					fmt.Printf(hex.Dump(config))
				}
				log.Printf("dmr/homebrew: logged in, sending %d bytes of repeater configuration\n", len(config))

				if err := l.Send(l.master.addr, config); err != nil {
					log.Printf("dmr/homebrew: send(%s) failed: %v\n", l.master.addr, err)
					return
				}
				l.keepAlive()
				return

			case authFail:
				log.Println("dmr/homebrew: login failed")
				return
			}
			if p != nil {
				l.Send(l.master.addr, p)
			}
			previous = l.master.status
		} else {
			log.Println("dmr/homebrew: waiting for master to respond in login sequence...")
			time.Sleep(time.Second)
		}
	}
}

func (l *Link) keepAlive() {
	for {
		atomic.AddUint32(&l.master.keepalive.outstanding, 1)
		atomic.AddUint64(&l.master.keepalive.sent, 1)
		var p = append(MasterPing, l.local.id...)
		if err := l.Send(l.master.addr, p); err != nil {
			log.Printf("dmr/homebrew: send(%s) failed: %v\n", l.master.addr, err)
			return
		}
		time.Sleep(time.Minute)
	}
}

func (l *Link) parse(queue <-chan packet) {
	for {
		select {
		case p := <-queue:
			size := len(p.data)
			if size < 4 {
				continue
			}

			switch l.master.status {
			case authNone:
				if bytes.Equal(p.data[:4], DMRData) {
					return
				}
				if size < 14 {
					return
				}
				packet := p.data[:6]
				repeater, err := hex.DecodeString(string(p.data[6:14]))
				if err != nil {
					log.Println("dmr/homebrew: unexpected login reply from master")
					l.master.status = authFail
					break
				}

				switch {
				case bytes.Equal(packet, MasterNAK):
					log.Printf("dmr/homebrew: login refused by master %d\n", repeater)
					l.master.status = authFail
					break
				case bytes.Equal(packet, MasterACK):
					log.Printf("dmr/homebrew: login accepted by master %d\n", repeater)
					l.master.secret = p.data[14:]
					l.master.status = authBegin
					break
				default:
					log.Printf("dmr/homebrew: unexpected login reply from master %d\n", repeater)
					l.master.status = authFail
					break
				}

			case authBegin:
				if bytes.Equal(p.data[:4], DMRData) {
					return
				}
				if size < 14 {
					log.Println("dmr/homebrew: unexpected login reply from master")
					l.master.status = authFail
					break
				}
				packet := p.data[:6]
				repeater, err := hex.DecodeString(string(p.data[6:14]))
				if err != nil {
					log.Println("dmr/homebrew: unexpected login reply from master")
					l.master.status = authFail
					break
				}

				switch {
				case bytes.Equal(packet, MasterNAK):
					log.Printf("dmr/homebrew: authentication refused by master %d\n", repeater)
					l.master.status = authFail
					break
				case bytes.Equal(packet, MasterACK):
					log.Printf("dmr/homebrew: authentication accepted by master %d\n", repeater)
					l.master.status = authDone
					break
				default:
					log.Printf("dmr/homebrew: unexpected authentication reply from master %d\n", repeater)
					l.master.status = authFail
					break
				}

			case authDone:
				if l.Dump {
					fmt.Printf("received from %s:\n", p.addr)
					fmt.Print(hex.Dump(p.data))
				}
				switch {
				case bytes.Equal(p.data[:4], DMRData):
					l.parseDMR(p.data)
				}
			}
		}
	}
}

func (l *Link) parseDMR(d []byte) {
	// If we have no stream callback, don't even bother to decode the DMR data frame.
	if l.stream == nil {
		return
	}

	var (
		p   *ipsc.Packet
		err error
	)
	if p, err = ParseData(d); err != nil {
		log.Printf("dmr/homebrew: error parsing DMRD frame: %v\n", err)
		return
	}
	l.stream(p)
}
<|endoftext|>


<commit_before>// Copyright (c) 2015 Arista Networks, Inc.
// Use of this source code is governed by the Apache License 2.0
// that can be found in the COPYING file.

package key

import (
	"encoding/json"
	"fmt"
	"math"
	"strconv"

	"github.com/aristanetworks/goarista/value"
)

// Key represents the Key in the updates and deletes of the Notification
// objects.  The only reason this exists is that Go won't let us define
// our own hash function for non-hashable types, and unfortunately we
// need to be able to index maps by map[string]interface{} objects.
type Key interface {
	Key() interface{}
	String() string
	Equal(other interface{}) bool
}

type keyImpl struct {
	key interface{}
}

type strKey string

type int8Key int8
type int16Key int16
type int32Key int32
type int64Key int64

type uint8Key int8
type uint16Key int16
type uint32Key int32
type uint64Key int64

type float32Key float32
type float64Key float64

type boolKey bool

// New wraps the given value in a Key.
// This function panics if the value passed in isn't allowed in a Key or
// doesn't implement value.Value.
func New(intf interface{}) Key {
	switch t := intf.(type) {
	case map[string]interface{}:
		return composite{sentinel, t}
	case string:
		return strKey(t)
	case int8:
		return int8Key(t)
	case int16:
		return int16Key(t)
	case int32:
		return int32Key(t)
	case int64:
		return int64Key(t)
	case uint8:
		return uint8Key(t)
	case uint16:
		return uint16Key(t)
	case uint32:
		return uint32Key(t)
	case uint64:
		return uint64Key(t)
	case float32:
		return float32Key(t)
	case float64:
		return float64Key(t)
	case bool:
		return boolKey(t)
	case value.Value:
		return keyImpl{key: intf}
	default:
		panic(fmt.Sprintf("Invalid type for key: %T", intf))
	}
}

func (k keyImpl) Key() interface{} {
	return k.key
}

func (k keyImpl) String() string {
	return stringify(k.key)
}

func (k keyImpl) GoString() string {
	return fmt.Sprintf("key.New(%#v)", k.Key())
}

func (k keyImpl) MarshalJSON() ([]byte, error) {
	return json.Marshal(k.Key())
}

func (k keyImpl) Equal(other interface{}) bool {
	o, ok := other.(Key)
	return ok && keyEqual(k.key, o.Key())
}

// Comparable types have an equality-testing method.
type Comparable interface {
	// Equal returns true if this object is equal to the other one.
	Equal(other interface{}) bool
}

func keyEqual(a, b interface{}) bool {
	switch a := a.(type) {
	case map[string]interface{}:
		b, ok := b.(map[string]interface{})
		if !ok || len(a) != len(b) {
			return false
		}
		for k, av := range a {
			if bv, ok := b[k]; !ok || !keyEqual(av, bv) {
				return false
			}
		}
		return true
	case map[Key]interface{}:
		b, ok := b.(map[Key]interface{})
		if !ok || len(a) != len(b) {
			return false
		}
		for k, av := range a {
			if bv, ok := b[k]; !ok || !keyEqual(av, bv) {
				return false
			}
		}
		return true
	case Comparable:
		return a.Equal(b)
	}

	return a == b
}

func (k strKey) Key() interface{} {
	return string(k)
}

func (k strKey) String() string {
	return escape(string(k))
}

func (k strKey) GoString() string {
	return fmt.Sprintf("key.New(%q)", string(k))
}

func (k strKey) MarshalJSON() ([]byte, error) {
	return json.Marshal(string(k))
}

func (k strKey) Equal(other interface{}) bool {
	o, ok := other.(Key)
	return ok && string(k) == o.Key()
}

// Key interface implementation for int8
func (k int8Key) Key() interface{} {
	return int8(k)
}

func (k int8Key) String() string {
	return strconv.FormatInt(int64(k), 10)
}

func (k int8Key) GoString() string {
	return fmt.Sprintf("key.New(%d)", int8(k))
}

func (k int8Key) MarshalJSON() ([]byte, error) {
	return []byte(strconv.FormatInt(int64(k), 10)), nil
}

func (k int8Key) Equal(other interface{}) bool {
	o, ok := other.(Key)
	return ok && int8(k) == o.Key()
}

// Key interface implementation for int16
func (k int16Key) Key() interface{} {
	return int16(k)
}

func (k int16Key) String() string {
	return strconv.FormatInt(int64(k), 10)
}

func (k int16Key) GoString() string {
	return fmt.Sprintf("key.New(%d)", int16(k))
}

func (k int16Key) MarshalJSON() ([]byte, error) {
	return []byte(strconv.FormatInt(int64(k), 10)), nil
}

func (k int16Key) Equal(other interface{}) bool {
	o, ok := other.(Key)
	return ok && int16(k) == o.Key()
}

// Key interface implementation for int32
func (k int32Key) Key() interface{} {
	return int32(k)
}

func (k int32Key) String() string {
	return strconv.FormatInt(int64(k), 10)
}

func (k int32Key) GoString() string {
	return fmt.Sprintf("key.New(%d)", int32(k))
}

func (k int32Key) MarshalJSON() ([]byte, error) {
	return []byte(strconv.FormatInt(int64(k), 10)), nil
}

func (k int32Key) Equal(other interface{}) bool {
	o, ok := other.(Key)
	return ok && int32(k) == o.Key()
}

// Key interface implementation for int64
func (k int64Key) Key() interface{} {
	return int64(k)
}

func (k int64Key) String() string {
	return strconv.FormatInt(int64(k), 10)
}

func (k int64Key) GoString() string {
	return fmt.Sprintf("key.New(%d)", int64(k))
}

func (k int64Key) MarshalJSON() ([]byte, error) {
	return []byte(strconv.FormatInt(int64(k), 10)), nil
}

func (k int64Key) Equal(other interface{}) bool {
	o, ok := other.(Key)
	return ok && int64(k) == o.Key()
}

// Key interface implementation for uint8
func (k uint8Key) Key() interface{} {
	return uint8(k)
}

func (k uint8Key) String() string {
	return strconv.FormatUint(uint64(k), 10)
}

func (k uint8Key) GoString() string {
	return fmt.Sprintf("key.New(%d)", uint8(k))
}

func (k uint8Key) MarshalJSON() ([]byte, error) {
	return []byte(strconv.FormatUint(uint64(k), 10)), nil
}

func (k uint8Key) Equal(other interface{}) bool {
	o, ok := other.(Key)
	return ok && uint8(k) == o.Key()
}

// Key interface implementation for uint16
func (k uint16Key) Key() interface{} {
	return uint16(k)
}

func (k uint16Key) String() string {
	return strconv.FormatUint(uint64(k), 10)
}

func (k uint16Key) GoString() string {
	return fmt.Sprintf("key.New(%d)", uint16(k))
}

func (k uint16Key) MarshalJSON() ([]byte, error) {
	return []byte(strconv.FormatUint(uint64(k), 10)), nil
}

func (k uint16Key) Equal(other interface{}) bool {
	o, ok := other.(Key)
	return ok && uint16(k) == o.Key()
}

// Key interface implementation for uint32
func (k uint32Key) Key() interface{} {
	return uint32(k)
}

func (k uint32Key) String() string {
	return strconv.FormatUint(uint64(k), 10)
}

func (k uint32Key) GoString() string {
	return fmt.Sprintf("key.New(%d)", uint32(k))
}

func (k uint32Key) MarshalJSON() ([]byte, error) {
	return []byte(strconv.FormatUint(uint64(k), 10)), nil
}

func (k uint32Key) Equal(other interface{}) bool {
	o, ok := other.(Key)
	return ok && uint32(k) == o.Key()
}

// Key interface implementation for uint64
func (k uint64Key) Key() interface{} {
	return uint64(k)
}

func (k uint64Key) String() string {
	return strconv.FormatUint(uint64(k), 10)
}

func (k uint64Key) GoString() string {
	return fmt.Sprintf("key.New(%d)", uint64(k))
}

func (k uint64Key) MarshalJSON() ([]byte, error) {
	return []byte(strconv.FormatUint(uint64(k), 10)), nil
}

func (k uint64Key) Equal(other interface{}) bool {
	o, ok := other.(Key)
	return ok && uint64(k) == o.Key()
}

// Key interface implementation for float32
func (k float32Key) Key() interface{} {
	return float32(k)
}

func (k float32Key) String() string {
	return "f" + strconv.FormatInt(int64(math.Float32bits(float32(k))), 10)
}

func (k float32Key) GoString() string {
	return fmt.Sprintf("key.New(%v)", float32(k))
}

func (k float32Key) MarshalJSON() ([]byte, error) {
	return []byte(strconv.FormatFloat(float64(k), 'g', -1, 32)), nil
}

func (k float32Key) Equal(other interface{}) bool {
	o, ok := other.(Key)
	return ok && float32(k) == o.Key()
}

// Key interface implementation for float64
func (k float64Key) Key() interface{} {
	return float64(k)
}

func (k float64Key) String() string {
	return "f" + strconv.FormatInt(int64(math.Float64bits(float64(k))), 10)
}

func (k float64Key) GoString() string {
	return fmt.Sprintf("key.New(%v)", float64(k))
}

func (k float64Key) MarshalJSON() ([]byte, error) {
	return []byte(strconv.FormatFloat(float64(k), 'g', -1, 64)), nil
}

func (k float64Key) Equal(other interface{}) bool {
	o, ok := other.(Key)
	return ok && float64(k) == o.Key()
}

// Key interface implementation for bool
func (k boolKey) Key() interface{} {
	return bool(k)
}

func (k boolKey) String() string {
	return strconv.FormatBool(bool(k))
}

func (k boolKey) GoString() string {
	return fmt.Sprintf("key.New(%v)", bool(k))
}

func (k boolKey) MarshalJSON() ([]byte, error) {
	return []byte(strconv.FormatBool(bool(k))), nil
}

func (k boolKey) Equal(other interface{}) bool {
	o, ok := other.(Key)
	return ok && bool(k) == o.Key()
}
<commit_msg>key: Optimize Equal by type asserting to concrete type<commit_after>// Copyright (c) 2015 Arista Networks, Inc.
// Use of this source code is governed by the Apache License 2.0
// that can be found in the COPYING file.

package key

import (
	"encoding/json"
	"fmt"
	"math"
	"strconv"

	"github.com/aristanetworks/goarista/value"
)

// Key represents the Key in the updates and deletes of the Notification
// objects.  The only reason this exists is that Go won't let us define
// our own hash function for non-hashable types, and unfortunately we
// need to be able to index maps by map[string]interface{} objects.
type Key interface {
	Key() interface{}
	String() string
	Equal(other interface{}) bool
}

type keyImpl struct {
	key interface{}
}

type strKey string

type int8Key int8
type int16Key int16
type int32Key int32
type int64Key int64

type uint8Key int8
type uint16Key int16
type uint32Key int32
type uint64Key int64

type float32Key float32
type float64Key float64

type boolKey bool

// New wraps the given value in a Key.
// This function panics if the value passed in isn't allowed in a Key or
// doesn't implement value.Value.
func New(intf interface{}) Key {
	switch t := intf.(type) {
	case map[string]interface{}:
		return composite{sentinel, t}
	case string:
		return strKey(t)
	case int8:
		return int8Key(t)
	case int16:
		return int16Key(t)
	case int32:
		return int32Key(t)
	case int64:
		return int64Key(t)
	case uint8:
		return uint8Key(t)
	case uint16:
		return uint16Key(t)
	case uint32:
		return uint32Key(t)
	case uint64:
		return uint64Key(t)
	case float32:
		return float32Key(t)
	case float64:
		return float64Key(t)
	case bool:
		return boolKey(t)
	case value.Value:
		return keyImpl{key: intf}
	default:
		panic(fmt.Sprintf("Invalid type for key: %T", intf))
	}
}

func (k keyImpl) Key() interface{} {
	return k.key
}

func (k keyImpl) String() string {
	return stringify(k.key)
}

func (k keyImpl) GoString() string {
	return fmt.Sprintf("key.New(%#v)", k.Key())
}

func (k keyImpl) MarshalJSON() ([]byte, error) {
	return json.Marshal(k.Key())
}

func (k keyImpl) Equal(other interface{}) bool {
	o, ok := other.(keyImpl)
	return ok && keyEqual(k.key, o.key)
}

// Comparable types have an equality-testing method.
type Comparable interface {
	// Equal returns true if this object is equal to the other one.
	Equal(other interface{}) bool
}

func keyEqual(a, b interface{}) bool {
	switch a := a.(type) {
	case map[string]interface{}:
		b, ok := b.(map[string]interface{})
		if !ok || len(a) != len(b) {
			return false
		}
		for k, av := range a {
			if bv, ok := b[k]; !ok || !keyEqual(av, bv) {
				return false
			}
		}
		return true
	case map[Key]interface{}:
		b, ok := b.(map[Key]interface{})
		if !ok || len(a) != len(b) {
			return false
		}
		for k, av := range a {
			if bv, ok := b[k]; !ok || !keyEqual(av, bv) {
				return false
			}
		}
		return true
	case Comparable:
		return a.Equal(b)
	}

	return a == b
}

func (k strKey) Key() interface{} {
	return string(k)
}

func (k strKey) String() string {
	return escape(string(k))
}

func (k strKey) GoString() string {
	return fmt.Sprintf("key.New(%q)", string(k))
}

func (k strKey) MarshalJSON() ([]byte, error) {
	return json.Marshal(string(k))
}

func (k strKey) Equal(other interface{}) bool {
	o, ok := other.(strKey)
	return ok && k == o
}

// Key interface implementation for int8
func (k int8Key) Key() interface{} {
	return int8(k)
}

func (k int8Key) String() string {
	return strconv.FormatInt(int64(k), 10)
}

func (k int8Key) GoString() string {
	return fmt.Sprintf("key.New(%d)", int8(k))
}

func (k int8Key) MarshalJSON() ([]byte, error) {
	return []byte(strconv.FormatInt(int64(k), 10)), nil
}

func (k int8Key) Equal(other interface{}) bool {
	o, ok := other.(int8Key)
	return ok && k == o
}

// Key interface implementation for int16
func (k int16Key) Key() interface{} {
	return int16(k)
}

func (k int16Key) String() string {
	return strconv.FormatInt(int64(k), 10)
}

func (k int16Key) GoString() string {
	return fmt.Sprintf("key.New(%d)", int16(k))
}

func (k int16Key) MarshalJSON() ([]byte, error) {
	return []byte(strconv.FormatInt(int64(k), 10)), nil
}

func (k int16Key) Equal(other interface{}) bool {
	o, ok := other.(int16Key)
	return ok && k == o
}

// Key interface implementation for int32
func (k int32Key) Key() interface{} {
	return int32(k)
}

func (k int32Key) String() string {
	return strconv.FormatInt(int64(k), 10)
}

func (k int32Key) GoString() string {
	return fmt.Sprintf("key.New(%d)", int32(k))
}

func (k int32Key) MarshalJSON() ([]byte, error) {
	return []byte(strconv.FormatInt(int64(k), 10)), nil
}

func (k int32Key) Equal(other interface{}) bool {
	o, ok := other.(int32Key)
	return ok && k == o
}

// Key interface implementation for int64
func (k int64Key) Key() interface{} {
	return int64(k)
}

func (k int64Key) String() string {
	return strconv.FormatInt(int64(k), 10)
}

func (k int64Key) GoString() string {
	return fmt.Sprintf("key.New(%d)", int64(k))
}

func (k int64Key) MarshalJSON() ([]byte, error) {
	return []byte(strconv.FormatInt(int64(k), 10)), nil
}

func (k int64Key) Equal(other interface{}) bool {
	o, ok := other.(int64Key)
	return ok && k == o
}

// Key interface implementation for uint8
func (k uint8Key) Key() interface{} {
	return uint8(k)
}

func (k uint8Key) String() string {
	return strconv.FormatUint(uint64(k), 10)
}

func (k uint8Key) GoString() string {
	return fmt.Sprintf("key.New(%d)", uint8(k))
}

func (k uint8Key) MarshalJSON() ([]byte, error) {
	return []byte(strconv.FormatUint(uint64(k), 10)), nil
}

func (k uint8Key) Equal(other interface{}) bool {
	o, ok := other.(uint8Key)
	return ok && k == o
}

// Key interface implementation for uint16
func (k uint16Key) Key() interface{} {
	return uint16(k)
}

func (k uint16Key) String() string {
	return strconv.FormatUint(uint64(k), 10)
}

func (k uint16Key) GoString() string {
	return fmt.Sprintf("key.New(%d)", uint16(k))
}

func (k uint16Key) MarshalJSON() ([]byte, error) {
	return []byte(strconv.FormatUint(uint64(k), 10)), nil
}

func (k uint16Key) Equal(other interface{}) bool {
	o, ok := other.(uint16Key)
	return ok && k == o
}

// Key interface implementation for uint32
func (k uint32Key) Key() interface{} {
	return uint32(k)
}

func (k uint32Key) String() string {
	return strconv.FormatUint(uint64(k), 10)
}

func (k uint32Key) GoString() string {
	return fmt.Sprintf("key.New(%d)", uint32(k))
}

func (k uint32Key) MarshalJSON() ([]byte, error) {
	return []byte(strconv.FormatUint(uint64(k), 10)), nil
}

func (k uint32Key) Equal(other interface{}) bool {
	o, ok := other.(uint32Key)
	return ok && k == o
}

// Key interface implementation for uint64
func (k uint64Key) Key() interface{} {
	return uint64(k)
}

func (k uint64Key) String() string {
	return strconv.FormatUint(uint64(k), 10)
}

func (k uint64Key) GoString() string {
	return fmt.Sprintf("key.New(%d)", uint64(k))
}

func (k uint64Key) MarshalJSON() ([]byte, error) {
	return []byte(strconv.FormatUint(uint64(k), 10)), nil
}

func (k uint64Key) Equal(other interface{}) bool {
	o, ok := other.(uint64Key)
	return ok && k == o
}

// Key interface implementation for float32
func (k float32Key) Key() interface{} {
	return float32(k)
}

func (k float32Key) String() string {
	return "f" + strconv.FormatInt(int64(math.Float32bits(float32(k))), 10)
}

func (k float32Key) GoString() string {
	return fmt.Sprintf("key.New(%v)", float32(k))
}

func (k float32Key) MarshalJSON() ([]byte, error) {
	return []byte(strconv.FormatFloat(float64(k), 'g', -1, 32)), nil
}

func (k float32Key) Equal(other interface{}) bool {
	o, ok := other.(float32Key)
	return ok && k == o
}

// Key interface implementation for float64
func (k float64Key) Key() interface{} {
	return float64(k)
}

func (k float64Key) String() string {
	return "f" + strconv.FormatInt(int64(math.Float64bits(float64(k))), 10)
}

func (k float64Key) GoString() string {
	return fmt.Sprintf("key.New(%v)", float64(k))
}

func (k float64Key) MarshalJSON() ([]byte, error) {
	return []byte(strconv.FormatFloat(float64(k), 'g', -1, 64)), nil
}

func (k float64Key) Equal(other interface{}) bool {
	o, ok := other.(float64Key)
	return ok && k == o
}

// Key interface implementation for bool
func (k boolKey) Key() interface{} {
	return bool(k)
}

func (k boolKey) String() string {
	return strconv.FormatBool(bool(k))
}

func (k boolKey) GoString() string {
	return fmt.Sprintf("key.New(%v)", bool(k))
}

func (k boolKey) MarshalJSON() ([]byte, error) {
	return []byte(strconv.FormatBool(bool(k))), nil
}

func (k boolKey) Equal(other interface{}) bool {
	o, ok := other.(boolKey)
	return ok && k == o
}
<|endoftext|>


<commit_before>// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0

package fix

import (
	"os"
	"testing"

	"github.com/google/go-cmp/cmp"
	"github.com/stretchr/testify/assert"
	testutils_test "sigs.k8s.io/kustomize/kustomize/v4/commands/internal/testutils"
	"sigs.k8s.io/kustomize/kyaml/filesys"
)

func TestFix(t *testing.T) {
	fSys := filesys.MakeFsInMemory()
	testutils_test.WriteTestKustomizationWith(fSys, []byte(`nameprefix: some-prefix-`))

	cmd := NewCmdFix(fSys, os.Stdout)
	assert.NoError(t, cmd.RunE(cmd, nil))

	content, err := testutils_test.ReadTestKustomization(fSys)
	assert.NoError(t, err)

	assert.Contains(t, string(content), "apiVersion: ")
	assert.Contains(t, string(content), "kind: Kustomization")
}

func TestFixCommand(t *testing.T) {
	tests := []struct {
		name     string
		input    string
		expected string
	}{
		{
			name: "FixOutdatedPatchesFieldTitle",
			input: `
patchesJson6902:
- path: patch1.yaml
  target:
    kind: Service
- path: patch2.yaml
  target:
    group: apps
    kind: Deployment
    version: v1
`,
			expected: `
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
patches:
- path: patch1.yaml
  target:
    kind: Service
- path: patch2.yaml
  target:
    group: apps
    kind: Deployment
    version: v1
`,
		},
		{
			name: "TestRenameAndKeepOutdatedPatchesField",
			input: `
patchesJson6902:
- path: patch1.yaml
  target:
    kind: Deployment
patches:
- path: patch2.yaml
  target:
    kind: Deployment
- path: patch3.yaml
  target:
    kind: Service
`,
			expected: `
patches:
- path: patch2.yaml
  target:
    kind: Deployment
- path: patch3.yaml
  target:
    kind: Service
- path: patch1.yaml
  target:
    kind: Deployment
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
`,
		},
		{
			name: "TestFixOutdatedPatchesStrategicMergeFieldTitle",
			input: `
patchesStrategicMerge:
- |-
  apiVersion: apps/v1
  kind: Deployment
  metadata:
    name: nginx
  spec:
    template:
      spec:
        containers:
          - name: nginx
            image: nignx:latest
`,
			expected: `
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
patches:
- patch: |-
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: nginx
    spec:
      template:
        spec:
          containers:
            - name: nginx
              image: nignx:latest
`,
		},
		{
			name: "TestFixAndMergeOutdatedPatchesStrategicMergeFieldTitle",
			input: `
patchesStrategicMerge:
- |-
  apiVersion: apps/v1
  kind: Deployment
  metadata:
    name: nginx
  spec:
    template:
      spec:
        containers:
          - name: nginx
            image: nignx:latest
patches:
- path: patch2.yaml
  target:
    kind: Deployment
`,
			expected: `
patches:
- path: patch2.yaml
  target:
    kind: Deployment
- patch: |-
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: nginx
    spec:
      template:
        spec:
          containers:
            - name: nginx
              image: nignx:latest
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
`,
		},
		{
			name: "TestFixOutdatedPatchesStrategicMergeToPathFieldTitle",
			input: `
patchesStrategicMerge:
- deploy.yaml
`,
			expected: `
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
patches:
- path: deploy.yaml
`,
		},
		{
			name: "TestFixOutdatedPatchesStrategicMergeToPathFieldYMLTitle",
			input: `
patchesStrategicMerge:
- deploy.yml
`,
			expected: `
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
patches:
- path: deploy.yml
`,
		},
		{
			name: "TestFixOutdatedPatchesStrategicMergeFieldPatchEndOfYamlTitle",
			input: `
patchesStrategicMerge:
- |-
  apiVersion: apps/v1
  kind: Deployment
  metadata:
    name: nginx
  spec:
    template:
      spec:
        containers:
          - name: nginx
            env:
              - name: CONFIG_FILE_PATH
                value: home.yaml
`,
			expected: `
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
patches:
- patch: |-
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: nginx
    spec:
      template:
        spec:
          containers:
            - name: nginx
              env:
                - name: CONFIG_FILE_PATH
                  value: home.yaml
`,
		},
		{
			name: "TestFixOutdatedCommonLabels",
			input: `
commonLabels:
  foo: bar
labels:
- pairs:
    a: b
`,
			expected: `
labels:
- pairs:
    a: b
- includeSelectors: true
  pairs:
    foo: bar
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
`,
		},
	}
	for _, test := range tests {
		fSys := filesys.MakeFsInMemory()
		testutils_test.WriteTestKustomizationWith(fSys, []byte(test.input))
		cmd := NewCmdFix(fSys, os.Stdout)
		assert.NoError(t, cmd.RunE(cmd, nil))

		content, err := testutils_test.ReadTestKustomization(fSys)
		assert.NoError(t, err)
		assert.Contains(t, string(content), "apiVersion: ")
		assert.Contains(t, string(content), "kind: Kustomization")

		if diff := cmp.Diff([]byte(test.expected), content); diff != "" {
			t.Errorf("%s: Mismatch (-expected, +actual):\n%s", test.name, diff)
		}
	}
}

func TestFixOutdatedCommonLabelsDuplicate(t *testing.T) {
	kustomizationContentWithOutdatedCommonLabels := []byte(`
commonLabels:
  foo: bar
labels:
- pairs:
    foo: baz
    a: b
`)

	fSys := filesys.MakeFsInMemory()
	testutils_test.WriteTestKustomizationWith(fSys, kustomizationContentWithOutdatedCommonLabels)
	cmd := NewCmdFix(fSys, os.Stdout)
	err := cmd.RunE(cmd, nil)
	assert.Error(t, err)
	assert.Equal(t, err.Error(), "label name 'foo' exists in both commonLabels and labels")
}
<commit_msg>add testcases<commit_after>// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0

package fix

import (
	"os"
	"testing"

	"github.com/google/go-cmp/cmp"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	testutils_test "sigs.k8s.io/kustomize/kustomize/v4/commands/internal/testutils"
	"sigs.k8s.io/kustomize/kyaml/filesys"
)

func TestFix(t *testing.T) {
	fSys := filesys.MakeFsInMemory()
	testutils_test.WriteTestKustomizationWith(fSys, []byte(`nameprefix: some-prefix-`))

	cmd := NewCmdFix(fSys, os.Stdout)
	assert.NoError(t, cmd.RunE(cmd, nil))

	content, err := testutils_test.ReadTestKustomization(fSys)
	assert.NoError(t, err)

	assert.Contains(t, string(content), "apiVersion: ")
	assert.Contains(t, string(content), "kind: Kustomization")
}

func TestFixCommand(t *testing.T) {
	tests := []struct {
		name     string
		input    string
		expected string
	}{
		{
			name: "FixOutdatedPatchesFieldTitle",
			input: `
patchesJson6902:
- path: patch1.yaml
  target:
    kind: Service
- path: patch2.yaml
  target:
    group: apps
    kind: Deployment
    version: v1
`,
			expected: `
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
patches:
- path: patch1.yaml
  target:
    kind: Service
- path: patch2.yaml
  target:
    group: apps
    kind: Deployment
    version: v1
`,
		},
		{
			name: "TestRenameAndKeepOutdatedPatchesField",
			input: `
patchesJson6902:
- path: patch1.yaml
  target:
    kind: Deployment
patches:
- path: patch2.yaml
  target:
    kind: Deployment
- path: patch3.yaml
  target:
    kind: Service
`,
			expected: `
patches:
- path: patch2.yaml
  target:
    kind: Deployment
- path: patch3.yaml
  target:
    kind: Service
- path: patch1.yaml
  target:
    kind: Deployment
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
`,
		},
		{
			name: "TestFixOutdatedPatchesStrategicMergeFieldTitle",
			input: `
patchesStrategicMerge:
- |-
  apiVersion: apps/v1
  kind: Deployment
  metadata:
    name: nginx
  spec:
    template:
      spec:
        containers:
          - name: nginx
            image: nignx:latest
`,
			expected: `
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
patches:
- patch: |-
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: nginx
    spec:
      template:
        spec:
          containers:
            - name: nginx
              image: nignx:latest
`,
		},
		{
			name: "TestFixAndMergeOutdatedPatchesStrategicMergeFieldTitle",
			input: `
patchesStrategicMerge:
- |-
  apiVersion: apps/v1
  kind: Deployment
  metadata:
    name: nginx
  spec:
    template:
      spec:
        containers:
          - name: nginx
            image: nignx:latest
patches:
- path: patch2.yaml
  target:
    kind: Deployment
`,
			expected: `
patches:
- path: patch2.yaml
  target:
    kind: Deployment
- patch: |-
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: nginx
    spec:
      template:
        spec:
          containers:
            - name: nginx
              image: nignx:latest
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
`,
		},
		{
			name: "TestFixOutdatedPatchesStrategicMergeToPathFieldTitle",
			input: `
patchesStrategicMerge:
- deploy.yaml
`,
			expected: `
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
patches:
- path: deploy.yaml
`,
		},
		{
			name: "TestFixOutdatedPatchesStrategicMergeToPathFieldYMLTitle",
			input: `
patchesStrategicMerge:
- deploy.yml
`,
			expected: `
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
patches:
- path: deploy.yml
`,
		},
		{
			name: "Test fix outdated patchesStrategicMerge from a file and one string literal",
			input: `
patchesStrategicMerge:
- deploy.yaml
- |-
  apiVersion: apps/v1
  kind: Deployment
  metadata:
    name: nginx
  spec:
    template:
      spec:
        containers:
          - name: nginx
            env:
              - name: CONFIG_FILE_PATH
                value: home.yaml
`,
			expected: `
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
patches:
- path: deploy.yaml
- patch: |-
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: nginx
    spec:
      template:
        spec:
          containers:
            - name: nginx
              env:
                - name: CONFIG_FILE_PATH
                  value: home.yaml
`,
		},
		{
			name: "Test fix outdated patchesStrategicMerge and patchesJson6902",
			input: `
patchesStrategicMerge:
- deploy.yaml
patchesJson6902:
- path: patch1.yaml
  target:
    kind: Deployment
`,
			expected: `
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
patches:
- path: patch1.yaml
  target:
    kind: Deployment
- path: deploy.yaml
`,
		},
		{
			name: "TestFixOutdatedPatchesStrategicMergeFieldPatchEndOfYamlTitle",
			input: `
patchesStrategicMerge:
- |-
  apiVersion: apps/v1
  kind: Deployment
  metadata:
    name: nginx
  spec:
    template:
      spec:
        containers:
          - name: nginx
            env:
              - name: CONFIG_FILE_PATH
                value: home.yaml
`,
			expected: `
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
patches:
- patch: |-
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: nginx
    spec:
      template:
        spec:
          containers:
            - name: nginx
              env:
                - name: CONFIG_FILE_PATH
                  value: home.yaml
`,
		},
		{
			name: "TestFixOutdatedCommonLabels",
			input: `
commonLabels:
  foo: bar
labels:
- pairs:
    a: b
`,
			expected: `
labels:
- pairs:
    a: b
- includeSelectors: true
  pairs:
    foo: bar
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
`,
		},
	}
	for _, test := range tests {
		fSys := filesys.MakeFsInMemory()
		testutils_test.WriteTestKustomizationWith(fSys, []byte(test.input))
		cmd := NewCmdFix(fSys, os.Stdout)
		require.NoError(t, cmd.RunE(cmd, nil))

		content, err := testutils_test.ReadTestKustomization(fSys)
		require.NoError(t, err)
		require.Contains(t, string(content), "apiVersion: ")
		require.Contains(t, string(content), "kind: Kustomization")

		if diff := cmp.Diff([]byte(test.expected), content); diff != "" {
			t.Errorf("%s: Mismatch (-expected, +actual):\n%s", test.name, diff)
		}
	}
}

func TestFixOutdatedCommonLabelsDuplicate(t *testing.T) {
	kustomizationContentWithOutdatedCommonLabels := []byte(`
commonLabels:
  foo: bar
labels:
- pairs:
    foo: baz
    a: b
`)

	fSys := filesys.MakeFsInMemory()
	testutils_test.WriteTestKustomizationWith(fSys, kustomizationContentWithOutdatedCommonLabels)
	cmd := NewCmdFix(fSys, os.Stdout)
	err := cmd.RunE(cmd, nil)
	assert.Error(t, err)
	assert.Equal(t, err.Error(), "label name 'foo' exists in both commonLabels and labels")
}
<|endoftext|>