<commit_before>package jolokia2

import (
	"fmt"
	"sort"
	"strings"

	"github.com/influxdata/telegraf"
)

const defaultFieldName = "value"

type Gatherer struct {
	metrics  []Metric
	requests []ReadRequest
}

func NewGatherer(metrics []Metric) *Gatherer {
	return &Gatherer{
		metrics:  metrics,
		requests: makeReadRequests(metrics),
	}
}

// Gather adds points to an accumulator from responses returned
// by a Jolokia agent.
func (g *Gatherer) Gather(client *Client, acc telegraf.Accumulator) error {
	var tags map[string]string

	if client.config.ProxyConfig != nil {
		tags = map[string]string{"jolokia_proxy_url": client.URL}
	} else {
		tags = map[string]string{"jolokia_agent_url": client.URL}
	}

	requests := makeReadRequests(g.metrics)
	responses, err := client.read(requests)
	if err != nil {
		return err
	}

	g.gatherResponses(responses, tags, acc)
	return nil
}

// gatherResponses adds points to an accumulator from the ReadResponse objects
// returned by a Jolokia agent.
func (g *Gatherer) gatherResponses(responses []ReadResponse, tags map[string]string, acc telegraf.Accumulator) {
	series := make(map[string][]point, 0)

	for _, metric := range g.metrics {
		points, ok := series[metric.Name]
		if !ok {
			points = make([]point, 0)
		}

		responsePoints, responseErrors := g.generatePoints(metric, responses)

		for _, responsePoint := range responsePoints {
			points = append(points, responsePoint)
		}

		for _, err := range responseErrors {
			acc.AddError(err)
		}

		series[metric.Name] = points
	}

	for measurement, points := range series {
		for _, point := range compactPoints(points) {
			acc.AddFields(measurement,
				point.Fields, mergeTags(point.Tags, tags))
		}
	}
}

// generatePoints creates points for the supplied metric from the ReadResponse
// objects returned by the Jolokia client.
func (g *Gatherer) generatePoints(metric Metric, responses []ReadResponse) ([]point, []error) {
	points := make([]point, 0)
	errors := make([]error, 0)

	for _, response := range responses {
		switch response.Status {
		case 200:
			break
		case 404:
			continue
		default:
			errors = append(errors, fmt.Errorf("Unexpected status in response from target %s: %d",
				response.RequestTarget, response.Status))
			continue
		}

		if !metricMatchesResponse(metric, response) {
			continue
		}

		pb := newPointBuilder(metric, response.RequestAttributes, response.RequestPath)
		for _, point := range pb.Build(metric.Mbean, response.Value) {
			if response.RequestTarget != "" {
				point.Tags["jolokia_agent_url"] = response.RequestTarget
			}

			points = append(points, point)
		}
	}

	return points, errors
}

// mergeTags combines two tag sets into a single tag set.
func mergeTags(metricTags, outerTags map[string]string) map[string]string {
	tags := make(map[string]string)
	for k, v := range outerTags {
		tags[k] = v
	}
	for k, v := range metricTags {
		tags[k] = v
	}

	return tags
}

// metricMatchesResponse returns true when the name, attributes, and path
// of a Metric match the corresponding elements in a ReadResponse object
// returned by a Jolokia agent.
func metricMatchesResponse(metric Metric, response ReadResponse) bool {
	if !metric.MatchObjectName(response.RequestMbean) {
		return false
	}

	if len(metric.Paths) == 0 {
		return len(response.RequestAttributes) == 0
	}

	for _, attribute := range response.RequestAttributes {
		if metric.MatchAttributeAndPath(attribute, response.RequestPath) {
			return true
		}
	}

	return false
}

// compactPoints attempts to remove points by compacting points
// with matching tag sets. When a match is found, the fields from
// one point are moved to another, and the empty point is removed.
func compactPoints(points []point) []point {
	compactedPoints := make([]point, 0)

	for _, sourcePoint := range points {
		keepPoint := true

		for _, compactPoint := range compactedPoints {
			if !tagSetsMatch(sourcePoint.Tags, compactPoint.Tags) {
				continue
			}

			keepPoint = false
			for key, val := range sourcePoint.Fields {
				compactPoint.Fields[key] = val
			}
		}

		if keepPoint {
			compactedPoints = append(compactedPoints, sourcePoint)
		}
	}

	return compactedPoints
}

// tagSetsMatch returns true if two maps are equivalent.
func tagSetsMatch(a, b map[string]string) bool {
	if len(a) != len(b) {
		return false
	}

	for ak, av := range a {
		bv, ok := b[ak]
		if !ok {
			return false
		}
		if av != bv {
			return false
		}
	}

	return true
}

// makeReadRequests creates ReadRequest objects from metrics definitions.
func makeReadRequests(metrics []Metric) []ReadRequest {
	var requests []ReadRequest
	for _, metric := range metrics {

		if len(metric.Paths) == 0 {
			requests = append(requests, ReadRequest{
				Mbean:      metric.Mbean,
				Attributes: []string{},
			})
		} else {
			attributes := make(map[string][]string)

			for _, path := range metric.Paths {
				segments := strings.Split(path, "/")
				attribute := segments[0]

				if _, ok := attributes[attribute]; !ok {
					attributes[attribute] = make([]string, 0)
				}

				if len(segments) > 1 {
					paths := attributes[attribute]
					attributes[attribute] = append(paths, strings.Join(segments[1:], "/"))
				}
			}

			rootAttributes := findRequestAttributesWithoutPaths(attributes)
			if len(rootAttributes) > 0 {
				requests = append(requests, ReadRequest{
					Mbean:      metric.Mbean,
					Attributes: rootAttributes,
				})
			}

			for _, deepAttribute := range findRequestAttributesWithPaths(attributes) {
				for _, path := range attributes[deepAttribute] {
					requests = append(requests, ReadRequest{
						Mbean:      metric.Mbean,
						Attributes: []string{deepAttribute},
						Path:       path,
					})
				}
			}
		}
	}

	return requests
}

func findRequestAttributesWithoutPaths(attributes map[string][]string) []string {
	results := make([]string, 0)
	for attr, paths := range attributes {
		if len(paths) == 0 {
			results = append(results, attr)
		}
	}

	sort.Strings(results)
	return results
}

func findRequestAttributesWithPaths(attributes map[string][]string) []string {
	results := make([]string, 0)
	for attr, paths := range attributes {
		if len(paths) != 0 {
			results = append(results, attr)
		}
	}

	sort.Strings(results)
	return results
}
<commit_msg>jolokia: add some error message context (#7906)<commit_after>package jolokia2

import (
	"fmt"
	"sort"
	"strings"

	"github.com/influxdata/telegraf"
)

const defaultFieldName = "value"

type Gatherer struct {
	metrics  []Metric
	requests []ReadRequest
}

func NewGatherer(metrics []Metric) *Gatherer {
	return &Gatherer{
		metrics:  metrics,
		requests: makeReadRequests(metrics),
	}
}

// Gather adds points to an accumulator from responses returned
// by a Jolokia agent.
func (g *Gatherer) Gather(client *Client, acc telegraf.Accumulator) error {
	var tags map[string]string

	if client.config.ProxyConfig != nil {
		tags = map[string]string{"jolokia_proxy_url": client.URL}
	} else {
		tags = map[string]string{"jolokia_agent_url": client.URL}
	}

	requests := makeReadRequests(g.metrics)
	responses, err := client.read(requests)
	if err != nil {
		return err
	}

	g.gatherResponses(responses, tags, acc)
	return nil
}

// gatherResponses adds points to an accumulator from the ReadResponse objects
// returned by a Jolokia agent.
func (g *Gatherer) gatherResponses(responses []ReadResponse, tags map[string]string, acc telegraf.Accumulator) {
	series := make(map[string][]point, 0)

	for _, metric := range g.metrics {
		points, ok := series[metric.Name]
		if !ok {
			points = make([]point, 0)
		}

		responsePoints, responseErrors := g.generatePoints(metric, responses)

		for _, responsePoint := range responsePoints {
			points = append(points, responsePoint)
		}

		for _, err := range responseErrors {
			acc.AddError(err)
		}

		series[metric.Name] = points
	}

	for measurement, points := range series {
		for _, point := range compactPoints(points) {
			acc.AddFields(measurement,
				point.Fields, mergeTags(point.Tags, tags))
		}
	}
}

// generatePoints creates points for the supplied metric from the ReadResponse
// objects returned by the Jolokia client.
func (g *Gatherer) generatePoints(metric Metric, responses []ReadResponse) ([]point, []error) {
	points := make([]point, 0)
	errors := make([]error, 0)

	for _, response := range responses {
		switch response.Status {
		case 200:
			break
		case 404:
			continue
		default:
			errors = append(errors, fmt.Errorf("Unexpected status in response from target %s (%q): %d",
				response.RequestTarget, response.RequestMbean, response.Status))
			continue
		}

		if !metricMatchesResponse(metric, response) {
			continue
		}

		pb := newPointBuilder(metric, response.RequestAttributes, response.RequestPath)
		for _, point := range pb.Build(metric.Mbean, response.Value) {
			if response.RequestTarget != "" {
				point.Tags["jolokia_agent_url"] = response.RequestTarget
			}

			points = append(points, point)
		}
	}

	return points, errors
}

// mergeTags combines two tag sets into a single tag set.
func mergeTags(metricTags, outerTags map[string]string) map[string]string {
	tags := make(map[string]string)
	for k, v := range outerTags {
		tags[k] = v
	}
	for k, v := range metricTags {
		tags[k] = v
	}

	return tags
}

// metricMatchesResponse returns true when the name, attributes, and path
// of a Metric match the corresponding elements in a ReadResponse object
// returned by a Jolokia agent.
func metricMatchesResponse(metric Metric, response ReadResponse) bool {
	if !metric.MatchObjectName(response.RequestMbean) {
		return false
	}

	if len(metric.Paths) == 0 {
		return len(response.RequestAttributes) == 0
	}

	for _, attribute := range response.RequestAttributes {
		if metric.MatchAttributeAndPath(attribute, response.RequestPath) {
			return true
		}
	}

	return false
}

// compactPoints attempts to remove points by compacting points
// with matching tag sets. When a match is found, the fields from
// one point are moved to another, and the empty point is removed.
func compactPoints(points []point) []point {
	compactedPoints := make([]point, 0)

	for _, sourcePoint := range points {
		keepPoint := true

		for _, compactPoint := range compactedPoints {
			if !tagSetsMatch(sourcePoint.Tags, compactPoint.Tags) {
				continue
			}

			keepPoint = false
			for key, val := range sourcePoint.Fields {
				compactPoint.Fields[key] = val
			}
		}

		if keepPoint {
			compactedPoints = append(compactedPoints, sourcePoint)
		}
	}

	return compactedPoints
}

// tagSetsMatch returns true if two maps are equivalent.
func tagSetsMatch(a, b map[string]string) bool {
	if len(a) != len(b) {
		return false
	}

	for ak, av := range a {
		bv, ok := b[ak]
		if !ok {
			return false
		}
		if av != bv {
			return false
		}
	}

	return true
}

// makeReadRequests creates ReadRequest objects from metrics definitions.
func makeReadRequests(metrics []Metric) []ReadRequest {
	var requests []ReadRequest
	for _, metric := range metrics {

		if len(metric.Paths) == 0 {
			requests = append(requests, ReadRequest{
				Mbean:      metric.Mbean,
				Attributes: []string{},
			})
		} else {
			attributes := make(map[string][]string)

			for _, path := range metric.Paths {
				segments := strings.Split(path, "/")
				attribute := segments[0]

				if _, ok := attributes[attribute]; !ok {
					attributes[attribute] = make([]string, 0)
				}

				if len(segments) > 1 {
					paths := attributes[attribute]
					attributes[attribute] = append(paths, strings.Join(segments[1:], "/"))
				}
			}

			rootAttributes := findRequestAttributesWithoutPaths(attributes)
			if len(rootAttributes) > 0 {
				requests = append(requests, ReadRequest{
					Mbean:      metric.Mbean,
					Attributes: rootAttributes,
				})
			}

			for _, deepAttribute := range findRequestAttributesWithPaths(attributes) {
				for _, path := range attributes[deepAttribute] {
					requests = append(requests, ReadRequest{
						Mbean:      metric.Mbean,
						Attributes: []string{deepAttribute},
						Path:       path,
					})
				}
			}
		}
	}

	return requests
}

func findRequestAttributesWithoutPaths(attributes map[string][]string) []string {
	results := make([]string, 0)
	for attr, paths := range attributes {
		if len(paths) == 0 {
			results = append(results, attr)
		}
	}

	sort.Strings(results)
	return results
}

func findRequestAttributesWithPaths(attributes map[string][]string) []string {
	results := make([]string, 0)
	for attr, paths := range attributes {
		if len(paths) != 0 {
			results = append(results, attr)
		}
	}

	sort.Strings(results)
	return results
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"github.com/codegangsta/cli"
	"io/ioutil"
	"os/exec"
	"path"
	"strconv"
	"strings"
	"text/template"
	"time"
)

var informationTemplate = `{{.Artist}}
{{.Date}}
{{.Album}}
{{.Tour}}

Lineage:

Notes:

This source is considered Source 1 for this date:
https://www.depechemode-live.com/wiki/{{wikiescape .Album}}/Source_1

Track list:

{{range .Tracks}}{{printf "%02d" .Index}}. [{{.Duration}}] {{.Title}}
{{end}}Total time: {{.Duration}}

Torrent downloaded from https://www.depechemode-live.com
`

type AlbumData struct {
	Artist   string
	Date     string
	Album    string
	Tour     string
	Tracks   []TrackData
	Duration string
}

type TrackData struct {
	Index    int
	Title    string
	Duration string
}

func generateInformation(c *cli.Context) {
	if c.GlobalBool("delete") {
		fmt.Println("Delete not implemented for this subcommand (yet)")
		return
	}

	filepath, fileInfo := checkFilepathArgument(c)
	if filepath == "" {
		return
	}

	tour := c.String("tour")
	if tour == "" {
		cli.ShowSubcommandHelp(c)
		return
	}

	mode := "batch"
	if c.GlobalBool("single") {
		mode = "single"
	}

	fmt.Println("The current tour is:", tour)
	fmt.Printf("The following filepath (%s mode) will be processed: %s\n", mode, filepath)

	if !shouldContinue(c) {
		return
	}

	if mode == "single" {
		generateFile(filepath, fileInfo.Name(), tour, c.GlobalBool("delete"))
		return
	}

	files, _ := ioutil.ReadDir(filepath)
	for _, file := range files {
		if file.IsDir() {
			name := file.Name()
			generateFile(path.Join(filepath, name), name, tour, c.GlobalBool("delete"))
		}
	}

	///////////////////////////////////////////////////////////////////////
}

func generateFile(filepath string, name string, tour string, deleteMode bool) {
	infoFile := createFile(path.Join(filepath, name+".txt"))
	defer infoFile.Close()

	album := new(AlbumData)
	album.Tour = tour

	var duration int64 = 0 // duration incrementer for the album

	files, _ := ioutil.ReadDir(filepath)
	for _, file := range files {
		if name := file.Name(); (path.Ext(name) == ".flac") && !file.IsDir() {
			getTagsFromFile(path.Join(filepath, name), album, &duration)
		}
	}

	format := "4:05" // minute:0second
	if duration >= 3600 {
		format = "15:04:05" // duration is longer than an hour
	}
	album.Duration = time.Unix(duration, 0).Format(format)

	funcMap := template.FuncMap{"wikiescape": wikiescape}
	t := template.Must(template.New("generate").Funcs(funcMap).Parse(informationTemplate))
	err := t.Execute(infoFile, album)
	if err != nil {
		panic(err)
	}
}

func getTagsFromFile(filepath string, album *AlbumData, albumDuration *int64) {
	args := []string{
		"--show-total-samples",
		"--show-sample-rate",
	}

	nonTagArgs := len(args)
	tags := []string{"TITLE"}

	getAlbumData := album.Artist == ""
	if getAlbumData {
		tags = append(tags,
			"ARTIST",
			"DATE",
			"ALBUM",
		)
	}

	args = append(args, filepath)
	for _, tag := range tags {
		args = append(args, "--show-tag="+tag)
	}

	data, err := exec.Command(
		"metaflac",
		args[:]...,
	).Output()

	if err != nil {
		panic(err)
	}

	var track TrackData
	track.Index = len(album.Tracks) + 1

	lines := strings.Split(string(data), "\r\n")
	if len(lines) != len(args) {
		panic(fmt.Sprintf("[invalid metaflac output] Expected %d lines, got %d", len(args), len(lines)-1))
		// todo, return a bool to delete this file
		// and say that the current file is being skipped
		// perhaps an --ignore flag to enable this feature
		// false by default, to make it cancel the whole procedure?
	}

	var samples, sampleRate int64
	for i, line := range lines {
		line = strings.TrimSpace(line)

		switch {
		case i <= 1:
			value, err := strconv.Atoi(line)
			if err != nil {
				panic(err)
			}

			if i == 0 {
				samples = int64(value)
			} else {
				sampleRate = int64(value)
			}
		case i < len(args)-1:
			tagName := tags[i-nonTagArgs]
			prefix := tagName + "="
			tagValue := ifTrimPrefix(line, prefix)

			switch tagName {
			case "TITLE":
				track.Title = tagValue
			case "ARTIST":
				album.Artist = tagValue
			case "DATE":
				album.Date = tagValue
			case "ALBUM":
				album.Album = ifTrimPrefix(tagValue, album.Date+" ")
			}
		}
	}
	duration := samples / sampleRate
	*albumDuration += duration
	track.Duration = time.Unix(duration, 0).Format("4:05")

	// Finally, add the new track to the album
	album.Tracks = append(album.Tracks, track)
}
<commit_msg>Add '--delete' to 'generate'<commit_after>package main

import (
	"fmt"
	"github.com/codegangsta/cli"
	"io/ioutil"
	"os"
	"os/exec"
	"path"
	"strconv"
	"strings"
	"text/template"
	"time"
)

var informationTemplate = `{{.Artist}}
{{.Date}}
{{.Album}}
{{.Tour}}

Lineage:

Notes:

This source is considered Source 1 for this date:
https://www.depechemode-live.com/wiki/{{wikiescape .Album}}/Source_1

Track list:

{{range .Tracks}}{{printf "%02d" .Index}}. [{{.Duration}}] {{.Title}}
{{end}}Total time: {{.Duration}}

Torrent downloaded from https://www.depechemode-live.com
`

type AlbumData struct {
	Artist   string
	Date     string
	Album    string
	Tour     string
	Tracks   []TrackData
	Duration string
}

type TrackData struct {
	Index    int
	Title    string
	Duration string
}

func generateInformation(c *cli.Context) {
	filepath, fileInfo := checkFilepathArgument(c)
	if filepath == "" {
		return
	}

	tour := c.String("tour")
	if tour == "" {
		cli.ShowSubcommandHelp(c)
		return
	}

	mode := "batch"
	if c.GlobalBool("single") {
		mode = "single"
	}

	fmt.Println("The current tour is:", tour)
	fmt.Printf("The following filepath (%s mode) will be processed: %s\n", mode, filepath)

	if !shouldContinue(c) {
		return
	}

	if mode == "single" {
		generateFile(filepath, fileInfo.Name(), tour, c.GlobalBool("delete"))
		return
	}

	files, _ := ioutil.ReadDir(filepath)
	for _, file := range files {
		if file.IsDir() {
			name := file.Name()
			generateFile(path.Join(filepath, name), name, tour, c.GlobalBool("delete"))
		}
	}

	///////////////////////////////////////////////////////////////////////
}

func generateFile(filepath string, name string, tour string, deleteMode bool) {
	var infoFile *os.File
	if filename := path.Join(filepath, name+".txt"); deleteMode {
		removeFile(filename)
		return
	} else {
		infoFile = createFile(filename)
		defer infoFile.Close()
	}

	album := new(AlbumData)
	album.Tour = tour

	var duration int64 = 0 // duration incrementer for the album

	files, _ := ioutil.ReadDir(filepath)
	for _, file := range files {
		if name := file.Name(); (path.Ext(name) == ".flac") && !file.IsDir() {
			getTagsFromFile(path.Join(filepath, name), album, &duration)
		}
	}

	format := "4:05" // minute:0second
	if duration >= 3600 {
		format = "15:04:05" // duration is longer than an hour
	}
	album.Duration = time.Unix(duration, 0).Format(format)

	funcMap := template.FuncMap{"wikiescape": wikiescape}
	t := template.Must(template.New("generate").Funcs(funcMap).Parse(informationTemplate))
	err := t.Execute(infoFile, album)
	if err != nil {
		panic(err)
	}
}

func getTagsFromFile(filepath string, album *AlbumData, albumDuration *int64) {
	args := []string{
		"--show-total-samples",
		"--show-sample-rate",
	}

	nonTagArgs := len(args)
	tags := []string{"TITLE"}

	getAlbumData := album.Artist == ""
	if getAlbumData {
		tags = append(tags,
			"ARTIST",
			"DATE",
			"ALBUM",
		)
	}

	args = append(args, filepath)
	for _, tag := range tags {
		args = append(args, "--show-tag="+tag)
	}

	data, err := exec.Command(
		"metaflac",
		args[:]...,
	).Output()

	if err != nil {
		panic(err)
	}

	var track TrackData
	track.Index = len(album.Tracks) + 1

	lines := strings.Split(string(data), "\r\n")
	if len(lines) != len(args) {
		panic(fmt.Sprintf("[invalid metaflac output] Expected %d lines, got %d", len(args), len(lines)-1))
		// todo, return a bool to delete this file
		// and say that the current file is being skipped
		// perhaps an --ignore flag to enable this feature
		// false by default, to make it cancel the whole procedure?
	}

	var samples, sampleRate int64
	for i, line := range lines {
		line = strings.TrimSpace(line)

		switch {
		case i <= 1:
			value, err := strconv.Atoi(line)
			if err != nil {
				panic(err)
			}

			if i == 0 {
				samples = int64(value)
			} else {
				sampleRate = int64(value)
			}
		case i < len(args)-1:
			tagName := tags[i-nonTagArgs]
			prefix := tagName + "="
			tagValue := ifTrimPrefix(line, prefix)

			switch tagName {
			case "TITLE":
				track.Title = tagValue
			case "ARTIST":
				album.Artist = tagValue
			case "DATE":
				album.Date = tagValue
			case "ALBUM":
				album.Album = ifTrimPrefix(tagValue, album.Date+" ")
			}
		}
	}
	duration := samples / sampleRate
	*albumDuration += duration
	track.Duration = time.Unix(duration, 0).Format("4:05")

	// Finally, add the new track to the album
	album.Tracks = append(album.Tracks, track)
}
<|endoftext|>


<commit_before>// Copyright 2021 The Prometheus 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 collector

import (
	"os"
	"reflect"
	"strings"
	"testing"

	"github.com/go-kit/log"
)

const debianBullseye string = `PRETTY_NAME="Debian GNU/Linux 11 (bullseye)"
NAME="Debian GNU/Linux"
VERSION_ID="11"
VERSION="11 (bullseye)"
VERSION_CODENAME=bullseye
ID=debian
HOME_URL="https://www.debian.org/"
SUPPORT_URL="https://www.debian.org/support"
BUG_REPORT_URL="https://bugs.debian.org/"
`

func TestParseOSRelease(t *testing.T) {
	want := &osRelease{
		Name:            "Ubuntu",
		ID:              "ubuntu",
		IDLike:          "debian",
		PrettyName:      "Ubuntu 20.04.2 LTS",
		Version:         "20.04.2 LTS (Focal Fossa)",
		VersionID:       "20.04",
		VersionCodename: "focal",
	}

	osReleaseFile, err := os.Open("fixtures" + usrLibOSRelease)
	if err != nil {
		t.Fatal(err)
	}
	got, err := parseOSRelease(osReleaseFile)
	if err != nil {
		t.Fatal(err)
	}
	if !reflect.DeepEqual(want, got) {
		t.Fatalf("should have %+v osRelease: got %+v", want, got)
	}

	want = &osRelease{
		Name:            "Debian GNU/Linux",
		ID:              "debian",
		PrettyName:      "Debian GNU/Linux 11 (bullseye)",
		Version:         "11 (bullseye)",
		VersionID:       "11",
		VersionCodename: "bullseye",
	}
	got, err = parseOSRelease(strings.NewReader(debianBullseye))
	if err != nil {
		t.Fatal(err)
	}
	if !reflect.DeepEqual(want, got) {
		t.Fatalf("should have %+v osRelease: got %+v", want, got)
	}
}

func TestUpdateStruct(t *testing.T) {
	wantedOS := &osRelease{
		Name:            "Ubuntu",
		ID:              "ubuntu",
		IDLike:          "debian",
		PrettyName:      "Ubuntu 20.04.2 LTS",
		Version:         "20.04.2 LTS (Focal Fossa)",
		VersionID:       "20.04",
		VersionCodename: "focal",
	}
	wantedVersion := 20.04

	collector, err := NewOSCollector(log.NewNopLogger())
	if err != nil {
		t.Fatal(err)
	}
	c := collector.(*osReleaseCollector)

	err = c.UpdateStruct("fixtures" + usrLibOSRelease)
	if err != nil {
		t.Fatal(err)
	}

	if !reflect.DeepEqual(wantedOS, c.os) {
		t.Fatalf("should have %+v osRelease: got %+v", wantedOS, c.os)
	}
	if wantedVersion != c.version {
		t.Errorf("Expected '%v' but got '%v'", wantedVersion, c.version)
	}
}
<commit_msg>add file descriptor close safely in test<commit_after>// Copyright 2021 The Prometheus 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 collector

import (
	"os"
	"reflect"
	"strings"
	"testing"

	"github.com/go-kit/log"
)

const debianBullseye string = `PRETTY_NAME="Debian GNU/Linux 11 (bullseye)"
NAME="Debian GNU/Linux"
VERSION_ID="11"
VERSION="11 (bullseye)"
VERSION_CODENAME=bullseye
ID=debian
HOME_URL="https://www.debian.org/"
SUPPORT_URL="https://www.debian.org/support"
BUG_REPORT_URL="https://bugs.debian.org/"
`

func TestParseOSRelease(t *testing.T) {
	want := &osRelease{
		Name:            "Ubuntu",
		ID:              "ubuntu",
		IDLike:          "debian",
		PrettyName:      "Ubuntu 20.04.2 LTS",
		Version:         "20.04.2 LTS (Focal Fossa)",
		VersionID:       "20.04",
		VersionCodename: "focal",
	}

	osReleaseFile, err := os.Open("fixtures" + usrLibOSRelease)
	if err != nil {
		t.Fatal(err)
	}
	defer osReleaseFile.Close()

	got, err := parseOSRelease(osReleaseFile)
	if err != nil {
		t.Fatal(err)
	}
	if !reflect.DeepEqual(want, got) {
		t.Fatalf("should have %+v osRelease: got %+v", want, got)
	}

	want = &osRelease{
		Name:            "Debian GNU/Linux",
		ID:              "debian",
		PrettyName:      "Debian GNU/Linux 11 (bullseye)",
		Version:         "11 (bullseye)",
		VersionID:       "11",
		VersionCodename: "bullseye",
	}
	got, err = parseOSRelease(strings.NewReader(debianBullseye))
	if err != nil {
		t.Fatal(err)
	}
	if !reflect.DeepEqual(want, got) {
		t.Fatalf("should have %+v osRelease: got %+v", want, got)
	}
}

func TestUpdateStruct(t *testing.T) {
	wantedOS := &osRelease{
		Name:            "Ubuntu",
		ID:              "ubuntu",
		IDLike:          "debian",
		PrettyName:      "Ubuntu 20.04.2 LTS",
		Version:         "20.04.2 LTS (Focal Fossa)",
		VersionID:       "20.04",
		VersionCodename: "focal",
	}
	wantedVersion := 20.04

	collector, err := NewOSCollector(log.NewNopLogger())
	if err != nil {
		t.Fatal(err)
	}
	c := collector.(*osReleaseCollector)

	err = c.UpdateStruct("fixtures" + usrLibOSRelease)
	if err != nil {
		t.Fatal(err)
	}

	if !reflect.DeepEqual(wantedOS, c.os) {
		t.Fatalf("should have %+v osRelease: got %+v", wantedOS, c.os)
	}
	if wantedVersion != c.version {
		t.Errorf("Expected '%v' but got '%v'", wantedVersion, c.version)
	}
}
<|endoftext|>


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

package log

// Calls Output to print to the logging with the Trace priority.
func (me *Logging) Trace(v ...interface{}) {
	if me.outputs&Oconsole != 0 {
		me.resetConsoleLogger()
		me.console.Trace(v...)
	}
	if me.outputs&Odailyfile != 0 {
		me.resetDailyFileLogger()
		me.dailyfile.Trace(v...)
	}
}

// Calls Output to printf to the logging with the Trace priority.
func (me *Logging) Tracef(format string, v ...interface{}) {
	if me.outputs&Oconsole != 0 {
		me.resetConsoleLogger()
		me.console.Tracef(format, v...)
	}
	if me.outputs&Odailyfile != 0 {
		me.resetDailyFileLogger()
		me.dailyfile.Tracef(format, v...)
	}
}

// Calls Output to println to the logging with the Trace priority.
func (me *Logging) Traceln(v ...interface{}) {
	if me.outputs&Oconsole != 0 {
		me.resetConsoleLogger()
		me.console.Traceln(v...)
	}
	if me.outputs&Odailyfile != 0 {
		me.resetDailyFileLogger()
		me.dailyfile.Traceln(v...)
	}
}

// Calls Output to print to the logging with the Debug priority.
func (me *Logging) Debug(v ...interface{}) {
	if me.outputs&Oconsole != 0 {
		me.resetConsoleLogger()
		me.console.Debug(v...)
	}
	if me.outputs&Odailyfile != 0 {
		me.resetDailyFileLogger()
		me.dailyfile.Debug(v...)
	}
}

// Calls Output to printf to the logging with the Debug priority.
func (me *Logging) Debugf(format string, v ...interface{}) {
	if me.outputs&Oconsole != 0 {
		me.resetConsoleLogger()
		me.console.Debugf(format, v...)
	}
	if me.outputs&Odailyfile != 0 {
		me.resetDailyFileLogger()
		me.dailyfile.Debugf(format, v...)
	}
}

// Calls Output to println to the logging with the Debug priority.
func (me *Logging) Debugln(v ...interface{}) {
	if me.outputs&Oconsole != 0 {
		me.resetConsoleLogger()
		me.console.Debugln(v...)
	}
	if me.outputs&Odailyfile != 0 {
		me.resetDailyFileLogger()
		me.dailyfile.Debugln(v...)
	}
}

// Calls Output to print to the logging with the Info priority.
func (me *Logging) Info(v ...interface{}) {
	if me.outputs&Oconsole != 0 {
		me.resetConsoleLogger()
		me.console.Info(v...)
	}
	if me.outputs&Odailyfile != 0 {
		me.resetDailyFileLogger()
		me.dailyfile.Info(v...)
	}
}

// Calls Output to printf to the logging with the Info priority.
func (me *Logging) Infof(format string, v ...interface{}) {
	if me.outputs&Oconsole != 0 {
		me.resetConsoleLogger()
		me.console.Infof(format, v...)
	}
	if me.outputs&Odailyfile != 0 {
		me.resetDailyFileLogger()
		me.dailyfile.Infof(format, v...)
	}
}

// Calls Output to println to the logging with the Info priority.
func (me *Logging) Infoln(v ...interface{}) {
	if me.outputs&Oconsole != 0 {
		me.resetConsoleLogger()
		me.console.Infoln(v...)
	}
	if me.outputs&Odailyfile != 0 {
		me.resetDailyFileLogger()
		me.dailyfile.Infoln(v...)
	}
}

// Calls Output to print to the logging with the Warn priority.
func (me *Logging) Warn(v ...interface{}) {
	if me.outputs&Oconsole != 0 {
		me.resetConsoleLogger()
		me.console.Warn(v...)
	}
	if me.outputs&Odailyfile != 0 {
		me.resetDailyFileLogger()
		me.dailyfile.Warn(v...)
	}
}

// Calls Output to printf to the logging with the Warn priority.
func (me *Logging) Warnf(format string, v ...interface{}) {
	if me.outputs&Oconsole != 0 {
		me.resetConsoleLogger()
		me.console.Warnf(format, v...)
	}
	if me.outputs&Odailyfile != 0 {
		me.resetDailyFileLogger()
		me.dailyfile.Warnf(format, v...)
	}
}

// Calls Output to println to the logging with the Warn priority.
func (me *Logging) Warnln(v ...interface{}) {
	if me.outputs&Oconsole != 0 {
		me.resetConsoleLogger()
		me.console.Warnln(v...)
	}
	if me.outputs&Odailyfile != 0 {
		me.resetDailyFileLogger()
		me.dailyfile.Warnln(v...)
	}
}

// Calls Output to print to the logging with the Error priority.
func (me *Logging) Error(v ...interface{}) {
	if me.outputs&Oconsole != 0 {
		me.resetConsoleLogger()
		me.console.Error(v...)
	}
	if me.outputs&Odailyfile != 0 {
		me.resetDailyFileLogger()
		me.dailyfile.Error(v...)
	}
}

// Calls Output to printf to the logging with the Error priority.
func (me *Logging) Errorf(format string, v ...interface{}) {
	if me.outputs&Oconsole != 0 {
		me.resetConsoleLogger()
		me.console.Errorf(format, v...)
	}
	if me.outputs&Odailyfile != 0 {
		me.resetDailyFileLogger()
		me.dailyfile.Errorf(format, v...)
	}
}

// Calls Output to println to the logging with the Error priority.
func (me *Logging) Errorln(v ...interface{}) {
	if me.outputs&Oconsole != 0 {
		me.resetConsoleLogger()
		me.console.Errorln(v...)
	}
	if me.outputs&Odailyfile != 0 {
		me.resetDailyFileLogger()
		me.dailyfile.Errorln(v...)
	}
}

// Calls Output to print to the logging with the Critical priority.
func (me *Logging) Critical(v ...interface{}) {
	if me.outputs&Oconsole != 0 {
		me.resetConsoleLogger()
		me.console.Critical(v...)
	}
	if me.outputs&Odailyfile != 0 {
		me.resetDailyFileLogger()
		me.dailyfile.Critical(v...)
	}
}

// Calls Output to printf to the logging with the Critical priority.
func (me *Logging) Criticalf(format string, v ...interface{}) {
	if me.outputs&Oconsole != 0 {
		me.resetConsoleLogger()
		me.console.Criticalf(format, v...)
	}
	if me.outputs&Odailyfile != 0 {
		me.resetDailyFileLogger()
		me.dailyfile.Criticalf(format, v...)
	}
}

// Calls Output to println to the logging with the Critical priority.
func (me *Logging) Criticalln(v ...interface{}) {
	if me.outputs&Oconsole != 0 {
		me.resetConsoleLogger()
		me.console.Criticalln(v...)
	}
	if me.outputs&Odailyfile != 0 {
		me.resetDailyFileLogger()
		me.dailyfile.Criticalln(v...)
	}
}

// Calls Output to print to the logging with the Print priority.
func (me *Logging) Print(v ...interface{}) {
	if me.outputs&Oconsole != 0 {
		me.resetConsoleLogger()
		me.console.Print(v...)
	}
	if me.outputs&Odailyfile != 0 {
		me.resetDailyFileLogger()
		me.dailyfile.Print(v...)
	}
}

// Calls Output to printf to the logging with the Print priority.
func (me *Logging) Printf(format string, v ...interface{}) {
	if me.outputs&Oconsole != 0 {
		me.resetConsoleLogger()
		me.console.Printf(format, v...)
	}
	if me.outputs&Odailyfile != 0 {
		me.resetDailyFileLogger()
		me.dailyfile.Printf(format, v...)
	}
}

// Calls Output to println to the logging with the Print priority.
func (me *Logging) Println(v ...interface{}) {
	if me.outputs&Oconsole != 0 {
		me.resetConsoleLogger()
		me.console.Println(v...)
	}
	if me.outputs&Odailyfile != 0 {
		me.resetDailyFileLogger()
		me.dailyfile.Println(v...)
	}
}
<commit_msg>Add as an empty judgment<commit_after>// Copyright 2014 The goyy Authors.  All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.

package log

// Calls Output to print to the logging with the Trace priority.
func (me *Logging) Trace(v ...interface{}) {
	if me.outputs&Oconsole != 0 {
		me.resetConsoleLogger()
		if me.console != nil {
			me.console.Trace(v...)
		}
	}
	if me.outputs&Odailyfile != 0 {
		me.resetDailyFileLogger()
		if me.dailyfile != nil {
			me.dailyfile.Trace(v...)
		}
	}
}

// Calls Output to printf to the logging with the Trace priority.
func (me *Logging) Tracef(format string, v ...interface{}) {
	if me.outputs&Oconsole != 0 {
		me.resetConsoleLogger()
		if me.console != nil {
			me.console.Tracef(format, v...)
		}
	}
	if me.outputs&Odailyfile != 0 {
		me.resetDailyFileLogger()
		if me.dailyfile != nil {
			me.dailyfile.Tracef(format, v...)
		}
	}
}

// Calls Output to println to the logging with the Trace priority.
func (me *Logging) Traceln(v ...interface{}) {
	if me.outputs&Oconsole != 0 {
		me.resetConsoleLogger()
		if me.console != nil {
			me.console.Traceln(v...)
		}
	}
	if me.outputs&Odailyfile != 0 {
		me.resetDailyFileLogger()
		if me.dailyfile != nil {
			me.dailyfile.Traceln(v...)
		}
	}
}

// Calls Output to print to the logging with the Debug priority.
func (me *Logging) Debug(v ...interface{}) {
	if me.outputs&Oconsole != 0 {
		me.resetConsoleLogger()
		if me.console != nil {
			me.console.Debug(v...)
		}
	}
	if me.outputs&Odailyfile != 0 {
		me.resetDailyFileLogger()
		if me.dailyfile != nil {
			me.dailyfile.Debug(v...)
		}
	}
}

// Calls Output to printf to the logging with the Debug priority.
func (me *Logging) Debugf(format string, v ...interface{}) {
	if me.outputs&Oconsole != 0 {
		me.resetConsoleLogger()
		if me.console != nil {
			me.console.Debugf(format, v...)
		}
	}
	if me.outputs&Odailyfile != 0 {
		me.resetDailyFileLogger()
		if me.dailyfile != nil {
			me.dailyfile.Debugf(format, v...)
		}
	}
}

// Calls Output to println to the logging with the Debug priority.
func (me *Logging) Debugln(v ...interface{}) {
	if me.outputs&Oconsole != 0 {
		me.resetConsoleLogger()
		if me.console != nil {
			me.console.Debugln(v...)
		}
	}
	if me.outputs&Odailyfile != 0 {
		me.resetDailyFileLogger()
		if me.dailyfile != nil {
			me.dailyfile.Debugln(v...)
		}
	}
}

// Calls Output to print to the logging with the Info priority.
func (me *Logging) Info(v ...interface{}) {
	if me.outputs&Oconsole != 0 {
		me.resetConsoleLogger()
		if me.console != nil {
			me.console.Info(v...)
		}
	}
	if me.outputs&Odailyfile != 0 {
		me.resetDailyFileLogger()
		if me.dailyfile != nil {
			me.dailyfile.Info(v...)
		}
	}
}

// Calls Output to printf to the logging with the Info priority.
func (me *Logging) Infof(format string, v ...interface{}) {
	if me.outputs&Oconsole != 0 {
		me.resetConsoleLogger()
		if me.console != nil {
			me.console.Infof(format, v...)
		}
	}
	if me.outputs&Odailyfile != 0 {
		me.resetDailyFileLogger()
		if me.dailyfile != nil {
			me.dailyfile.Infof(format, v...)
		}
	}
}

// Calls Output to println to the logging with the Info priority.
func (me *Logging) Infoln(v ...interface{}) {
	if me.outputs&Oconsole != 0 {
		me.resetConsoleLogger()
		if me.console != nil {
			me.console.Infoln(v...)
		}
	}
	if me.outputs&Odailyfile != 0 {
		me.resetDailyFileLogger()
		if me.dailyfile != nil {
			me.dailyfile.Infoln(v...)
		}
	}
}

// Calls Output to print to the logging with the Warn priority.
func (me *Logging) Warn(v ...interface{}) {
	if me.outputs&Oconsole != 0 {
		me.resetConsoleLogger()
		if me.console != nil {
			me.console.Warn(v...)
		}
	}
	if me.outputs&Odailyfile != 0 {
		me.resetDailyFileLogger()
		if me.dailyfile != nil {
			me.dailyfile.Warn(v...)
		}
	}
}

// Calls Output to printf to the logging with the Warn priority.
func (me *Logging) Warnf(format string, v ...interface{}) {
	if me.outputs&Oconsole != 0 {
		me.resetConsoleLogger()
		if me.console != nil {
			me.console.Warnf(format, v...)
		}
	}
	if me.outputs&Odailyfile != 0 {
		me.resetDailyFileLogger()
		if me.dailyfile != nil {
			me.dailyfile.Warnf(format, v...)
		}
	}
}

// Calls Output to println to the logging with the Warn priority.
func (me *Logging) Warnln(v ...interface{}) {
	if me.outputs&Oconsole != 0 {
		me.resetConsoleLogger()
		if me.console != nil {
			me.console.Warnln(v...)
		}
	}
	if me.outputs&Odailyfile != 0 {
		me.resetDailyFileLogger()
		if me.dailyfile != nil {
			me.dailyfile.Warnln(v...)
		}
	}
}

// Calls Output to print to the logging with the Error priority.
func (me *Logging) Error(v ...interface{}) {
	if me.outputs&Oconsole != 0 {
		me.resetConsoleLogger()
		if me.console != nil {
			me.console.Error(v...)
		}
	}
	if me.outputs&Odailyfile != 0 {
		me.resetDailyFileLogger()
		if me.dailyfile != nil {
			me.dailyfile.Error(v...)
		}
	}
}

// Calls Output to printf to the logging with the Error priority.
func (me *Logging) Errorf(format string, v ...interface{}) {
	if me.outputs&Oconsole != 0 {
		me.resetConsoleLogger()
		if me.console != nil {
			me.console.Errorf(format, v...)
		}
	}
	if me.outputs&Odailyfile != 0 {
		me.resetDailyFileLogger()
		if me.dailyfile != nil {
			me.dailyfile.Errorf(format, v...)
		}
	}
}

// Calls Output to println to the logging with the Error priority.
func (me *Logging) Errorln(v ...interface{}) {
	if me.outputs&Oconsole != 0 {
		me.resetConsoleLogger()
		if me.console != nil {
			me.console.Errorln(v...)
		}
	}
	if me.outputs&Odailyfile != 0 {
		me.resetDailyFileLogger()
		if me.dailyfile != nil {
			me.dailyfile.Errorln(v...)
		}
	}
}

// Calls Output to print to the logging with the Critical priority.
func (me *Logging) Critical(v ...interface{}) {
	if me.outputs&Oconsole != 0 {
		me.resetConsoleLogger()
		if me.console != nil {
			me.console.Critical(v...)
		}
	}
	if me.outputs&Odailyfile != 0 {
		me.resetDailyFileLogger()
		if me.dailyfile != nil {
			me.dailyfile.Critical(v...)
		}
	}
}

// Calls Output to printf to the logging with the Critical priority.
func (me *Logging) Criticalf(format string, v ...interface{}) {
	if me.outputs&Oconsole != 0 {
		me.resetConsoleLogger()
		if me.console != nil {
			me.console.Criticalf(format, v...)
		}
	}
	if me.outputs&Odailyfile != 0 {
		me.resetDailyFileLogger()
		if me.dailyfile != nil {
			me.dailyfile.Criticalf(format, v...)
		}
	}
}

// Calls Output to println to the logging with the Critical priority.
func (me *Logging) Criticalln(v ...interface{}) {
	if me.outputs&Oconsole != 0 {
		me.resetConsoleLogger()
		if me.console != nil {
			me.console.Criticalln(v...)
		}
	}
	if me.outputs&Odailyfile != 0 {
		me.resetDailyFileLogger()
		if me.dailyfile != nil {
			me.dailyfile.Criticalln(v...)
		}
	}
}

// Calls Output to print to the logging with the Print priority.
func (me *Logging) Print(v ...interface{}) {
	if me.outputs&Oconsole != 0 {
		me.resetConsoleLogger()
		if me.console != nil {
			me.console.Print(v...)
		}
	}
	if me.outputs&Odailyfile != 0 {
		me.resetDailyFileLogger()
		if me.dailyfile != nil {
			me.dailyfile.Print(v...)
		}
	}
}

// Calls Output to printf to the logging with the Print priority.
func (me *Logging) Printf(format string, v ...interface{}) {
	if me.outputs&Oconsole != 0 {
		me.resetConsoleLogger()
		if me.console != nil {
			me.console.Printf(format, v...)
		}
	}
	if me.outputs&Odailyfile != 0 {
		me.resetDailyFileLogger()
		if me.dailyfile != nil {
			me.dailyfile.Printf(format, v...)
		}
	}
}

// Calls Output to println to the logging with the Print priority.
func (me *Logging) Println(v ...interface{}) {
	if me.outputs&Oconsole != 0 {
		me.resetConsoleLogger()
		if me.console != nil {
			me.console.Println(v...)
		}
	}
	if me.outputs&Odailyfile != 0 {
		me.resetDailyFileLogger()
		if me.dailyfile != nil {
			me.dailyfile.Println(v...)
		}
	}
}
<|endoftext|>


<commit_before>package agent

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"os"
	"os/exec"
	"path"
	"strconv"
	"sync"
	"syscall"
	"time"

	"github.com/hashicorp/consul/consul/structs"
)

const (
	// remoteExecFileName is the name of the file we append to
	// the path, e.g. _rexec/session_id/job
	remoteExecFileName = "job"

	// rExecAck is the suffix added to an ack path
	remoteExecAckSuffix = "ack"

	// remoteExecAck is the suffix added to an exit code
	remoteExecExitSuffix = "exit"

	// remoteExecOutputDivider is used to namespace the output
	remoteExecOutputDivider = "out"

	// remoteExecOutputSize is the size we chunk output too
	remoteExecOutputSize = 4 * 1024

	// remoteExecOutputDeadline is how long we wait before uploading
	// less than the chunk size
	remoteExecOutputDeadline = 500 * time.Millisecond
)

// remoteExecEvent is used as the payload of the user event to transmit
// what we need to know about the event
type remoteExecEvent struct {
	Prefix  string
	Session string
}

// remoteExecSpec is used as the specification of the remote exec.
// It is stored in the KV store
type remoteExecSpec struct {
	Command string
	Script  []byte
	Wait    time.Duration
}

type rexecWriter struct {
	BufCh    chan []byte
	BufSize  int
	BufIdle  time.Duration
	CancelCh chan struct{}

	buf     []byte
	bufLen  int
	bufLock sync.Mutex
	flush   *time.Timer
}

func (r *rexecWriter) Write(b []byte) (int, error) {
	r.bufLock.Lock()
	defer r.bufLock.Unlock()
	if r.flush != nil {
		r.flush.Stop()
		r.flush = nil
	}
	inpLen := len(b)
	if r.buf == nil {
		r.buf = make([]byte, r.BufSize)
	}

COPY:
	remain := len(r.buf) - r.bufLen
	if remain > len(b) {
		copy(r.buf[r.bufLen:], b)
		r.bufLen += len(b)
	} else {
		copy(r.buf[r.bufLen:], b[:remain])
		b = b[remain:]
		r.bufLen += remain
		r.bufLock.Unlock()
		r.Flush()
		r.bufLock.Lock()
		goto COPY
	}

	r.flush = time.AfterFunc(r.BufIdle, r.Flush)
	return inpLen, nil
}

func (r *rexecWriter) Flush() {
	r.bufLock.Lock()
	defer r.bufLock.Unlock()
	if r.flush != nil {
		r.flush.Stop()
		r.flush = nil
	}
	if r.bufLen == 0 {
		return
	}
	select {
	case r.BufCh <- r.buf[:r.bufLen]:
		r.buf = make([]byte, r.BufSize)
		r.bufLen = 0
	case <-r.CancelCh:
		r.bufLen = 0
	}
}

// handleRemoteExec is invoked when a new remote exec request is received
func (a *Agent) handleRemoteExec(msg *UserEvent) {
	a.logger.Printf("[DEBUG] agent: received remote exec event (ID: %s)", msg.ID)
	// Decode the event paylaod
	var event remoteExecEvent
	if err := json.Unmarshal(msg.Payload, &event); err != nil {
		a.logger.Printf("[ERR] agent: failed to decode remote exec event: %v", err)
		return
	}

	// Read the job specification
	var spec remoteExecSpec
	if !a.remoteExecGetSpec(&event, &spec) {
		return
	}

	// Write the acknowledgement
	if !a.remoteExecWriteAck(&event) {
		return
	}

	// Ensure we write out an exit code
	exitCode := 0
	defer a.remoteExecWriteExitCode(&event, exitCode)

	// Check if this is a script, we may need to spill to disk
	var script string
	if len(spec.Script) != 0 {
		tmpFile, err := ioutil.TempFile("", "rexec")
		if err != nil {
			a.logger.Printf("[DEBUG] agent: failed to make tmp file: %v", err)
			exitCode = 255
			return
		}
		defer os.Remove(tmpFile.Name())
		os.Chmod(tmpFile.Name(), 0750)
		tmpFile.Write(spec.Script)
		tmpFile.Close()
		script = tmpFile.Name()
	} else {
		script = spec.Command
	}

	// Create the exec.Cmd
	cmd, err := ExecScript(script)
	if err != nil {
		a.logger.Printf("[DEBUG] agent: failed to start remote exec: %v", err)
		exitCode = 255
		return
	}

	// Setup the output streaming
	writer := &rexecWriter{
		BufCh:    make(chan []byte, 16),
		BufSize:  remoteExecOutputSize,
		BufIdle:  remoteExecOutputDeadline,
		CancelCh: make(chan struct{}),
	}
	cmd.Stdout = writer
	cmd.Stderr = writer

	// Start execution
	err = cmd.Start()
	if err != nil {
		a.logger.Printf("[DEBUG] agent: failed to start remote exec: %v", err)
		exitCode = 255
		return
	}

	// Wait for the process to exit
	exitCh := make(chan int, 1)
	go func() {
		err := cmd.Wait()
		writer.Flush()
		close(writer.BufCh)
		if err != nil {
			exitCh <- 0
			return
		}

		// Try to determine the exit code
		if exitErr, ok := err.(*exec.ExitError); ok {
			if status, ok := exitErr.Sys().(syscall.WaitStatus); ok {
				exitCh <- status.ExitStatus()
				return
			}
		}
		exitCh <- 1
	}()

	// Wait until we are complete, uploading as we go
WAIT:
	for num := 0; ; num++ {
		select {
		case out := <-writer.BufCh:
			if out == nil {
				break WAIT
			}
			if !a.remoteExecWriteOutput(&event, num, out) {
				close(writer.CancelCh)
				exitCode = 255
				return
			}
		case <-time.After(spec.Wait):
			// Acts like a heartbeat, since there is no output
			a.remoteExecWriteOutput(&event, num, nil)
		}
	}

	// Get the exit code
	exitCode = <-exitCh
}

// remoteExecGetSpec is used to get the exec specification.
// Returns if execution should continue
func (a *Agent) remoteExecGetSpec(event *remoteExecEvent, spec *remoteExecSpec) bool {
	get := structs.KeyRequest{
		Datacenter: a.config.Datacenter,
		Key:        path.Join(event.Prefix, event.Session, remoteExecFileName),
		QueryOptions: structs.QueryOptions{
			AllowStale: true, // Stale read for scale! Retry on failure.
		},
	}
	var out structs.IndexedDirEntries
QUERY:
	if err := a.RPC("KVS.Get", &get, &out); err != nil {
		a.logger.Printf("[ERR] agent: failed to get remote exec job: %v", err)
		return false
	}
	if len(out.Entries) == 0 {
		// If the initial read was stale and had no data, retry as a consistent read
		if get.QueryOptions.AllowStale {
			a.logger.Printf("[DEBUG] agent: trying consistent fetch of remote exec job spec")
			get.QueryOptions.AllowStale = false
			goto QUERY
		} else {
			a.logger.Printf("[DEBUG] agent: remote exec aborted, job spec missing")
			return false
		}
	}
	if err := json.Unmarshal(out.Entries[0].Value, &spec); err != nil {
		a.logger.Printf("[ERR] agent: failed to decode remote exec spec: %v", err)
		return false
	}
	return true
}

// remoteExecWriteAck is used to write an ack. Returns if execution should
// continue.
func (a *Agent) remoteExecWriteAck(event *remoteExecEvent) bool {
	write := structs.KVSRequest{
		Datacenter: a.config.Datacenter,
		Op:         structs.KVSLock,
		DirEnt: structs.DirEntry{
			Key: path.Join(event.Prefix, event.Session,
				a.config.NodeName, remoteExecAckSuffix),
			Session: event.Session,
		},
	}
	var success bool
	if err := a.RPC("KVS.Apply", &write, &success); err != nil {
		a.logger.Printf("[ERR] agent: failed to ack remote exec job: %v", err)
		return false
	}
	if !success {
		a.logger.Printf("[DEBUG] agent: remote exec aborted, ack failed")
		return false
	}
	return true
}

// remoteExecWriteOutput is used to write output
func (a *Agent) remoteExecWriteOutput(event *remoteExecEvent, num int, output []byte) bool {
	outputNum := fmt.Sprintf("%05x", num)
	key := path.Join(event.Prefix, event.Session,
		a.config.NodeName, remoteExecOutputDivider, outputNum)
	write := structs.KVSRequest{
		Datacenter: a.config.Datacenter,
		Op:         structs.KVSLock,
		DirEnt: structs.DirEntry{
			Key:     key,
			Value:   output,
			Session: event.Session,
		},
	}
	var success bool
	if err := a.RPC("KVS.Apply", &write, &success); err != nil {
		a.logger.Printf("[ERR] agent: failed to write output for remote exec job: %v", err)
		return false
	}
	if !success {
		a.logger.Printf("[DEBUG] agent: remote exec aborted, output write failed")
		return false
	}
	return true
}

// remoteExecWriteExitCode is used to write an exit code
func (a *Agent) remoteExecWriteExitCode(event *remoteExecEvent, exitCode int) {
	write := structs.KVSRequest{
		Datacenter: a.config.Datacenter,
		Op:         structs.KVSLock,
		DirEnt: structs.DirEntry{
			Key: path.Join(event.Prefix, event.Session,
				a.config.NodeName, remoteExecExitSuffix),
			Value:   []byte(strconv.FormatInt(int64(exitCode), 10)),
			Session: event.Session,
		},
	}
	var success bool
	if err := a.RPC("KVS.Apply", &write, &success); err != nil {
		a.logger.Printf("[ERR] agent: failed to write exit code for remote exec job: %v", err)
	}
	if !success {
		a.logger.Printf("[DEBUG] agent: remote exec aborted, exit code write failed")
	}
}
<commit_msg>agent: Refactor remote exec write code<commit_after>package agent

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"os"
	"os/exec"
	"path"
	"strconv"
	"sync"
	"syscall"
	"time"

	"github.com/hashicorp/consul/consul/structs"
)

const (
	// remoteExecFileName is the name of the file we append to
	// the path, e.g. _rexec/session_id/job
	remoteExecFileName = "job"

	// rExecAck is the suffix added to an ack path
	remoteExecAckSuffix = "ack"

	// remoteExecAck is the suffix added to an exit code
	remoteExecExitSuffix = "exit"

	// remoteExecOutputDivider is used to namespace the output
	remoteExecOutputDivider = "out"

	// remoteExecOutputSize is the size we chunk output too
	remoteExecOutputSize = 4 * 1024

	// remoteExecOutputDeadline is how long we wait before uploading
	// less than the chunk size
	remoteExecOutputDeadline = 500 * time.Millisecond
)

// remoteExecEvent is used as the payload of the user event to transmit
// what we need to know about the event
type remoteExecEvent struct {
	Prefix  string
	Session string
}

// remoteExecSpec is used as the specification of the remote exec.
// It is stored in the KV store
type remoteExecSpec struct {
	Command string
	Script  []byte
	Wait    time.Duration
}

type rexecWriter struct {
	BufCh    chan []byte
	BufSize  int
	BufIdle  time.Duration
	CancelCh chan struct{}

	buf     []byte
	bufLen  int
	bufLock sync.Mutex
	flush   *time.Timer
}

func (r *rexecWriter) Write(b []byte) (int, error) {
	r.bufLock.Lock()
	defer r.bufLock.Unlock()
	if r.flush != nil {
		r.flush.Stop()
		r.flush = nil
	}
	inpLen := len(b)
	if r.buf == nil {
		r.buf = make([]byte, r.BufSize)
	}

COPY:
	remain := len(r.buf) - r.bufLen
	if remain > len(b) {
		copy(r.buf[r.bufLen:], b)
		r.bufLen += len(b)
	} else {
		copy(r.buf[r.bufLen:], b[:remain])
		b = b[remain:]
		r.bufLen += remain
		r.bufLock.Unlock()
		r.Flush()
		r.bufLock.Lock()
		goto COPY
	}

	r.flush = time.AfterFunc(r.BufIdle, r.Flush)
	return inpLen, nil
}

func (r *rexecWriter) Flush() {
	r.bufLock.Lock()
	defer r.bufLock.Unlock()
	if r.flush != nil {
		r.flush.Stop()
		r.flush = nil
	}
	if r.bufLen == 0 {
		return
	}
	select {
	case r.BufCh <- r.buf[:r.bufLen]:
		r.buf = make([]byte, r.BufSize)
		r.bufLen = 0
	case <-r.CancelCh:
		r.bufLen = 0
	}
}

// handleRemoteExec is invoked when a new remote exec request is received
func (a *Agent) handleRemoteExec(msg *UserEvent) {
	a.logger.Printf("[DEBUG] agent: received remote exec event (ID: %s)", msg.ID)
	// Decode the event paylaod
	var event remoteExecEvent
	if err := json.Unmarshal(msg.Payload, &event); err != nil {
		a.logger.Printf("[ERR] agent: failed to decode remote exec event: %v", err)
		return
	}

	// Read the job specification
	var spec remoteExecSpec
	if !a.remoteExecGetSpec(&event, &spec) {
		return
	}

	// Write the acknowledgement
	if !a.remoteExecWriteAck(&event) {
		return
	}

	// Ensure we write out an exit code
	exitCode := 0
	defer a.remoteExecWriteExitCode(&event, exitCode)

	// Check if this is a script, we may need to spill to disk
	var script string
	if len(spec.Script) != 0 {
		tmpFile, err := ioutil.TempFile("", "rexec")
		if err != nil {
			a.logger.Printf("[DEBUG] agent: failed to make tmp file: %v", err)
			exitCode = 255
			return
		}
		defer os.Remove(tmpFile.Name())
		os.Chmod(tmpFile.Name(), 0750)
		tmpFile.Write(spec.Script)
		tmpFile.Close()
		script = tmpFile.Name()
	} else {
		script = spec.Command
	}

	// Create the exec.Cmd
	cmd, err := ExecScript(script)
	if err != nil {
		a.logger.Printf("[DEBUG] agent: failed to start remote exec: %v", err)
		exitCode = 255
		return
	}

	// Setup the output streaming
	writer := &rexecWriter{
		BufCh:    make(chan []byte, 16),
		BufSize:  remoteExecOutputSize,
		BufIdle:  remoteExecOutputDeadline,
		CancelCh: make(chan struct{}),
	}
	cmd.Stdout = writer
	cmd.Stderr = writer

	// Start execution
	err = cmd.Start()
	if err != nil {
		a.logger.Printf("[DEBUG] agent: failed to start remote exec: %v", err)
		exitCode = 255
		return
	}

	// Wait for the process to exit
	exitCh := make(chan int, 1)
	go func() {
		err := cmd.Wait()
		writer.Flush()
		close(writer.BufCh)
		if err != nil {
			exitCh <- 0
			return
		}

		// Try to determine the exit code
		if exitErr, ok := err.(*exec.ExitError); ok {
			if status, ok := exitErr.Sys().(syscall.WaitStatus); ok {
				exitCh <- status.ExitStatus()
				return
			}
		}
		exitCh <- 1
	}()

	// Wait until we are complete, uploading as we go
WAIT:
	for num := 0; ; num++ {
		select {
		case out := <-writer.BufCh:
			if out == nil {
				break WAIT
			}
			if !a.remoteExecWriteOutput(&event, num, out) {
				close(writer.CancelCh)
				exitCode = 255
				return
			}
		case <-time.After(spec.Wait):
			// Acts like a heartbeat, since there is no output
			a.remoteExecWriteOutput(&event, num, nil)
		}
	}

	// Get the exit code
	exitCode = <-exitCh
}

// remoteExecGetSpec is used to get the exec specification.
// Returns if execution should continue
func (a *Agent) remoteExecGetSpec(event *remoteExecEvent, spec *remoteExecSpec) bool {
	get := structs.KeyRequest{
		Datacenter: a.config.Datacenter,
		Key:        path.Join(event.Prefix, event.Session, remoteExecFileName),
		QueryOptions: structs.QueryOptions{
			AllowStale: true, // Stale read for scale! Retry on failure.
		},
	}
	var out structs.IndexedDirEntries
QUERY:
	if err := a.RPC("KVS.Get", &get, &out); err != nil {
		a.logger.Printf("[ERR] agent: failed to get remote exec job: %v", err)
		return false
	}
	if len(out.Entries) == 0 {
		// If the initial read was stale and had no data, retry as a consistent read
		if get.QueryOptions.AllowStale {
			a.logger.Printf("[DEBUG] agent: trying consistent fetch of remote exec job spec")
			get.QueryOptions.AllowStale = false
			goto QUERY
		} else {
			a.logger.Printf("[DEBUG] agent: remote exec aborted, job spec missing")
			return false
		}
	}
	if err := json.Unmarshal(out.Entries[0].Value, &spec); err != nil {
		a.logger.Printf("[ERR] agent: failed to decode remote exec spec: %v", err)
		return false
	}
	return true
}

// remoteExecWriteAck is used to write an ack. Returns if execution should
// continue.
func (a *Agent) remoteExecWriteAck(event *remoteExecEvent) bool {
	if err := a.remoteExecWriteKey(event, remoteExecAckSuffix, nil); err != nil {
		a.logger.Printf("[ERR] agent: failed to ack remote exec job: %v", err)
		return false
	}
	return true
}

// remoteExecWriteOutput is used to write output
func (a *Agent) remoteExecWriteOutput(event *remoteExecEvent, num int, output []byte) bool {
	suffix := path.Join(remoteExecOutputDivider, fmt.Sprintf("%05x", num))
	if err := a.remoteExecWriteKey(event, suffix, output); err != nil {
		a.logger.Printf("[ERR] agent: failed to write output for remote exec job: %v", err)
		return false
	}
	return true
}

// remoteExecWriteExitCode is used to write an exit code
func (a *Agent) remoteExecWriteExitCode(event *remoteExecEvent, exitCode int) {
	val := []byte(strconv.FormatInt(int64(exitCode), 10))
	if err := a.remoteExecWriteKey(event, remoteExecExitSuffix, val); err != nil {
		a.logger.Printf("[ERR] agent: failed to write exit code for remote exec job: %v", err)
	}
}

// remoteExecWriteKey is used to write an output key for a remote exec job
func (a *Agent) remoteExecWriteKey(event *remoteExecEvent, suffix string, val []byte) error {
	key := path.Join(event.Prefix, event.Session, a.config.NodeName, suffix)
	write := structs.KVSRequest{
		Datacenter: a.config.Datacenter,
		Op:         structs.KVSLock,
		DirEnt: structs.DirEntry{
			Key:     key,
			Value:   val,
			Session: event.Session,
		},
	}
	var success bool
	if err := a.RPC("KVS.Apply", &write, &success); err != nil {
		return err
	}
	if !success {
		return fmt.Errorf("write failed")
	}
	return nil
}
<|endoftext|>


<commit_before>package commandrunner_test

import (
	"bytes"
	"strings"
	"time"

	. "github.com/cloudfoundry-incubator/route-registrar/Godeps/_workspace/src/github.com/onsi/ginkgo"
	. "github.com/cloudfoundry-incubator/route-registrar/Godeps/_workspace/src/github.com/onsi/gomega"
	"github.com/cloudfoundry-incubator/route-registrar/commandrunner"
	"github.com/onsi/gomega/gexec"

	"io/ioutil"
	"os"
	"path/filepath"
)

const (
	golangExecutable = `
package main

import "fmt"

func main() {
	fmt.Println("Hello from a binary")
}`
)

var _ = Describe("CommandRunner", func() {
	var (
		executable   string
		tmpDir       string
		tmpGoPkg     string
		tmpGoPkgPath string
		outbuf       bytes.Buffer
		errbuf       bytes.Buffer
		r            commandrunner.Runner
	)

	BeforeEach(func() {
		var err error
		tmpDir, err = ioutil.TempDir("", "healthchecker-test")
		Expect(err).NotTo(HaveOccurred())

		gopathEnv := os.Getenv("GOPATH")
		gopathArray := strings.SplitN(gopathEnv, ":", 1)
		gopath := gopathArray[0]
		Expect(gopath).NotTo(BeEmpty())

		tmpGoPkg = "tmp-foo"
		tmpGoPkgPath = filepath.Join(gopath, "src", tmpGoPkg)
		err = os.MkdirAll(tmpGoPkgPath, os.ModePerm)
		Expect(err).NotTo(HaveOccurred())

		executable = filepath.Join(tmpDir, "healthchecker.sh")
		scriptText := "echo 'my-stdout'; >&2 echo 'my-stderr'; exit 0\n"
		ioutil.WriteFile(executable, []byte(scriptText), os.ModePerm)

		outbuf = bytes.Buffer{}
		errbuf = bytes.Buffer{}
	})

	AfterEach(func() {
		err := os.RemoveAll(tmpDir)
		Expect(err).ShouldNot(HaveOccurred())

		err = os.RemoveAll(tmpGoPkg)
		Expect(err).ShouldNot(HaveOccurred())
	})

	Describe("Run", func() {
		JustBeforeEach(func() {
			r = commandrunner.NewRunner(executable)
		})

		It("captures stdout and stderr", func() {
			err := r.Run(&outbuf, &errbuf)
			Expect(err).NotTo(HaveOccurred())

			Eventually(outbuf.String).Should(ContainSubstring("my-stdout"))
			Eventually(errbuf.String).Should(ContainSubstring("my-stderr"))
		})

		It("runs the command in the background", func() {
			err := r.Run(&outbuf, &errbuf)
			Expect(err).NotTo(HaveOccurred())

			err = r.Wait()
			Expect(err).NotTo(HaveOccurred())
		})

		Context("when the script exits with a non-zero code", func() {
			BeforeEach(func() {
				scriptText := "exit 1\n"
				ioutil.WriteFile(executable, []byte(scriptText), os.ModePerm)
			})

			It("places the error on the error chan", func() {
				err := r.Run(&outbuf, &errbuf)
				Expect(err).NotTo(HaveOccurred())

				err = r.Wait()
				Expect(err).To(HaveOccurred())
			})
		})

		Describe("running a binary", func() {
			BeforeEach(func() {
				executableFilepath := filepath.Join(tmpGoPkgPath, "main.go")
				err := ioutil.WriteFile(executableFilepath, []byte(golangExecutable), os.ModePerm)
				Expect(err).NotTo(HaveOccurred())

				executable, err = gexec.Build(tmpGoPkg)
				Expect(err).ShouldNot(HaveOccurred())
			})

			It("runs a binary without error", func() {
				err := r.Run(&outbuf, &errbuf)
				Expect(err).NotTo(HaveOccurred())

				err = r.Wait()
				Expect(err).NotTo(HaveOccurred())
			})
		})
	})

	Describe("Kill", func() {
		BeforeEach(func() {
			r = commandrunner.NewRunner(executable)
		})
		Context("when the kill succeeds", func() {
			BeforeEach(func() {
				scriptText := "sleep 10; exit 0\n"
				ioutil.WriteFile(executable, []byte(scriptText), os.ModePerm)

				var outbuf, errbuf bytes.Buffer
				r.Run(&outbuf, &errbuf)
			})

			It("returns no error", func() {
				err := r.Kill()
				Expect(err).NotTo(HaveOccurred())
			})
		})

		Context("when the kill does not succeed", func() {
			BeforeEach(func() {
				scriptText := "exit 0\n"
				ioutil.WriteFile(executable, []byte(scriptText), os.ModePerm)

				var outbuf, errbuf bytes.Buffer
				r.Run(&outbuf, &errbuf)
			})

			It("places an error on the errChan", func() {
				time.Sleep(10 * time.Millisecond) // Give the script time to complete so `kill` returns an error
				err := r.Kill()
				Expect(err).To(HaveOccurred())
			})
		})
	})
})
<commit_msg>Add test for running scripts with shebang.<commit_after>package commandrunner_test

import (
	"bytes"
	"strings"
	"time"

	. "github.com/cloudfoundry-incubator/route-registrar/Godeps/_workspace/src/github.com/onsi/ginkgo"
	. "github.com/cloudfoundry-incubator/route-registrar/Godeps/_workspace/src/github.com/onsi/gomega"
	"github.com/cloudfoundry-incubator/route-registrar/commandrunner"
	"github.com/onsi/gomega/gexec"

	"io/ioutil"
	"os"
	"path/filepath"
)

const (
	golangExecutable = `
package main

import "fmt"

func main() {
	fmt.Println("Hello from a binary")
}`
)

var _ = Describe("CommandRunner", func() {
	var (
		executable   string
		tmpDir       string
		tmpGoPkg     string
		tmpGoPkgPath string
		outbuf       bytes.Buffer
		errbuf       bytes.Buffer
		r            commandrunner.Runner
	)

	BeforeEach(func() {
		var err error
		tmpDir, err = ioutil.TempDir("", "healthchecker-test")
		Expect(err).NotTo(HaveOccurred())

		gopathEnv := os.Getenv("GOPATH")
		gopathArray := strings.SplitN(gopathEnv, ":", 1)
		gopath := gopathArray[0]
		Expect(gopath).NotTo(BeEmpty())

		tmpGoPkg = "tmp-foo"
		tmpGoPkgPath = filepath.Join(gopath, "src", tmpGoPkg)
		err = os.MkdirAll(tmpGoPkgPath, os.ModePerm)
		Expect(err).NotTo(HaveOccurred())

		executable = filepath.Join(tmpDir, "healthchecker.sh")
		scriptText := "echo 'my-stdout'; >&2 echo 'my-stderr'; exit 0\n"

		err = ioutil.WriteFile(executable, []byte(scriptText), os.ModePerm)
		Expect(err).NotTo(HaveOccurred())

		outbuf = bytes.Buffer{}
		errbuf = bytes.Buffer{}
	})

	AfterEach(func() {
		err := os.RemoveAll(tmpDir)
		Expect(err).ShouldNot(HaveOccurred())

		err = os.RemoveAll(tmpGoPkg)
		Expect(err).ShouldNot(HaveOccurred())
	})

	Describe("Run", func() {
		JustBeforeEach(func() {
			r = commandrunner.NewRunner(executable)
		})

		It("captures stdout and stderr", func() {
			err := r.Run(&outbuf, &errbuf)
			Expect(err).NotTo(HaveOccurred())

			Eventually(outbuf.String).Should(ContainSubstring("my-stdout"))
			Eventually(errbuf.String).Should(ContainSubstring("my-stderr"))
		})

		It("runs the command in the background", func() {
			err := r.Run(&outbuf, &errbuf)
			Expect(err).NotTo(HaveOccurred())

			err = r.Wait()
			Expect(err).NotTo(HaveOccurred())
		})

		Context("when the script exits with a non-zero code", func() {
			BeforeEach(func() {
				scriptText := "exit 1\n"
				ioutil.WriteFile(executable, []byte(scriptText), os.ModePerm)
			})

			It("places the error on the error chan", func() {
				err := r.Run(&outbuf, &errbuf)
				Expect(err).NotTo(HaveOccurred())

				err = r.Wait()
				Expect(err).To(HaveOccurred())
			})
		})

		Describe("running a binary", func() {
			BeforeEach(func() {
				executableFilepath := filepath.Join(tmpGoPkgPath, "main.go")
				err := ioutil.WriteFile(executableFilepath, []byte(golangExecutable), os.ModePerm)
				Expect(err).NotTo(HaveOccurred())

				executable, err = gexec.Build(tmpGoPkg)
				Expect(err).ShouldNot(HaveOccurred())
			})

			It("runs a binary without error", func() {
				err := r.Run(&outbuf, &errbuf)
				Expect(err).NotTo(HaveOccurred())

				err = r.Wait()
				Expect(err).NotTo(HaveOccurred())

				Expect(outbuf.String()).To(Equal("Hello from a binary\n"))
			})
		})

		Describe("running a script with a shebang", func() {
			BeforeEach(func() {
				executable = filepath.Join(tmpDir, "healthchecker.sh")
				scriptText := "#!/bin/sh\necho 'my-stdout'; >&2 echo 'my-stderr'; exit 0\n"

				err := ioutil.WriteFile(executable, []byte(scriptText), os.ModePerm)
				Expect(err).NotTo(HaveOccurred())
			})

			It("runs the script without error", func() {
				err := r.Run(&outbuf, &errbuf)
				Expect(err).NotTo(HaveOccurred())

				err = r.Wait()
				Expect(err).NotTo(HaveOccurred())

				Expect(outbuf.String()).To(Equal("my-stdout\n"))
			})
		})
	})

	Describe("Kill", func() {
		BeforeEach(func() {
			r = commandrunner.NewRunner(executable)
		})
		Context("when the kill succeeds", func() {
			BeforeEach(func() {
				scriptText := "sleep 10; exit 0\n"
				ioutil.WriteFile(executable, []byte(scriptText), os.ModePerm)

				var outbuf, errbuf bytes.Buffer
				r.Run(&outbuf, &errbuf)
			})

			It("returns no error", func() {
				err := r.Kill()
				Expect(err).NotTo(HaveOccurred())
			})
		})

		Context("when the kill does not succeed", func() {
			BeforeEach(func() {
				scriptText := "exit 0\n"
				ioutil.WriteFile(executable, []byte(scriptText), os.ModePerm)

				var outbuf, errbuf bytes.Buffer
				r.Run(&outbuf, &errbuf)
			})

			It("places an error on the errChan", func() {
				time.Sleep(10 * time.Millisecond) // Give the script time to complete so `kill` returns an error
				err := r.Kill()
				Expect(err).To(HaveOccurred())
			})
		})
	})
})
<|endoftext|>


<commit_before>package main

import (
	log "github.com/Sirupsen/logrus"
	"github.com/elazarl/goproxy"

	"flag"
	"fmt"
	"net/http"
	"os"
	"regexp"
)

const DefaultPort = ":8500"

func main() {
	// getting proxy configuration
	verbose := flag.Bool("v", false, "should every proxy request be logged to stdout")
	record := flag.Bool("record", false, "should proxy record")
	destination := flag.String("source", "^.*:80$", "destination URI to catch")
	flag.Parse()

	// getting settings
	initSettings()

	// getting default database
	port := os.Getenv("ProxyPort")
	if port == "" {
		port = DefaultPort
	} else {
		port = fmt.Sprintf(":%s", port)
	}

	// Output to stderr instead of stdout, could also be a file.
	log.SetOutput(os.Stderr)
	log.SetFormatter(&log.TextFormatter{})

	// app starting
	log.WithFields(log.Fields{
		"RedisAddress": AppConfig.redisAddress,
		"Destination":  *destination,
		"ProxyPort":    port,
	}).Info("app is starting")

	redisPool := getRedisPool()
	defer redisPool.Close()

	cache := Cache{pool: redisPool}

	// getting connections
	d := DBClient{
		cache: cache,
		http:  &http.Client{},
	}

	// creating proxy
	proxy := goproxy.NewProxyHttpServer()

	proxy.OnRequest(goproxy.ReqHostMatches(regexp.MustCompile("^.*$"))).
		HandleConnect(goproxy.AlwaysMitm)

	proxy.OnRequest().DoFunc(
		func(r *http.Request, ctx *goproxy.ProxyCtx) (*http.Request, *http.Response) {
			log.WithFields(log.Fields{
				"SourceIP":    r.RemoteAddr,
				"Destination": r.RemoteAddr,
			}).Info("Got request")
			return r, nil
		})
	// hijacking plain connections
	proxy.OnRequest(goproxy.ReqHostMatches(regexp.MustCompile(*destination))).DoFunc(
		func(r *http.Request, ctx *goproxy.ProxyCtx) (*http.Request, *http.Response) {

			log.Info("connection found......")
			log.Info(fmt.Sprintf("Url path:  %s", r.URL.Path))

			if *record {
				log.Info("*** RECORD ***")
				newResponse, err := d.recordRequest(r)
				if err != nil {
					// something bad happened, passing through
					return r, nil
				} else {
					// discarding original requests and returns supplied response
					return r, newResponse
				}

			} else {
				log.Info("*** PLAYBACK ***")
				newResponse := d.getResponse(r)
				return r, newResponse
			}
		})

	proxy.Verbose = *verbose
	log.Fatal(http.ListenAndServe(port, proxy))
}
<commit_msg>added admin webserver<commit_after>package main

import (
	log "github.com/Sirupsen/logrus"
	"github.com/codegangsta/negroni"
	"github.com/elazarl/goproxy"
	"github.com/meatballhat/negroni-logrus"

	"flag"
	"fmt"
	"net/http"
	"os"
	"regexp"
)

const DefaultPort = ":8500"

func main() {
	// getting proxy configuration
	verbose := flag.Bool("v", false, "should every proxy request be logged to stdout")
	record := flag.Bool("record", false, "should proxy record")
	destination := flag.String("source", "^.*:80$", "destination URI to catch")
	flag.Parse()

	// getting settings
	initSettings()

	// getting default database
	port := os.Getenv("ProxyPort")
	if port == "" {
		port = DefaultPort
	} else {
		port = fmt.Sprintf(":%s", port)
	}

	// Output to stderr instead of stdout, could also be a file.
	log.SetOutput(os.Stderr)
	log.SetFormatter(&log.TextFormatter{})

	redisPool := getRedisPool()
	defer redisPool.Close()

	cache := Cache{pool: redisPool,
		prefix: AppConfig.cachePrefix}

	// getting connections
	d := DBClient{
		cache: cache,
		http:  &http.Client{},
	}

	// creating proxy
	proxy := goproxy.NewProxyHttpServer()

	proxy.OnRequest(goproxy.ReqHostMatches(regexp.MustCompile("^.*$"))).
		HandleConnect(goproxy.AlwaysMitm)

	proxy.OnRequest().DoFunc(
		func(r *http.Request, ctx *goproxy.ProxyCtx) (*http.Request, *http.Response) {
			log.WithFields(log.Fields{
				"SourceIP":    r.RemoteAddr,
				"Destination": r.RemoteAddr,
			}).Info("Got request")
			return r, nil
		})

	// hijacking plain connections
	proxy.OnRequest(goproxy.ReqHostMatches(regexp.MustCompile(*destination))).DoFunc(
		func(r *http.Request, ctx *goproxy.ProxyCtx) (*http.Request, *http.Response) {

			log.Info("connection found......")
			log.Info(fmt.Sprintf("Url path:  %s", r.URL.Path))

			if *record {
				log.Info("*** RECORD ***")
				newResponse, err := d.recordRequest(r)
				if err != nil {
					// something bad happened, passing through
					return r, nil
				} else {
					// discarding original requests and returns supplied response
					return r, newResponse
				}

			} else {
				log.Info("*** PLAYBACK ***")
				newResponse := d.getResponse(r)
				return r, newResponse
			}
		})

	go d.startAdminInterface()

	proxy.Verbose = *verbose

	// proxy starting message
	log.WithFields(log.Fields{
		"RedisAddress": AppConfig.redisAddress,
		"Destination":  *destination,
		"ProxyPort":    port,
	}).Info("Proxy is starting...")

	log.Error(http.ListenAndServe(port, proxy))
}

func (d *DBClient) startAdminInterface() {
	// starting admin interface
	mux := getBoneRouter(*d)
	n := negroni.Classic()
	n.Use(negronilogrus.NewMiddleware())
	n.UseHandler(mux)

	// admin interface starting message
	log.WithFields(log.Fields{
		"RedisAddress": AppConfig.redisAddress,
		"AdminPort":    AppConfig.adminInterface,
	}).Info("Admin interface is starting...")

	n.Run(AppConfig.adminInterface)
}
<|endoftext|>


<commit_before>package dht

import (
	"bitbucket.org/anacrolix/go.torrent/util"
	"github.com/willf/bloom"
	"log"
	"net"
	"sync"
	"time"
)

type peerDiscovery struct {
	*peerStream
	triedAddrs *bloom.BloomFilter
	pending    int
	server     *Server
	infoHash   string
}

func (me *peerDiscovery) Close() {
	me.peerStream.Close()
}

func (s *Server) GetPeers(infoHash string) (*peerStream, error) {
	s.mu.Lock()
	startAddrs := func() (ret []net.Addr) {
		for _, n := range s.closestGoodNodes(160, infoHash) {
			ret = append(ret, n.addr)
		}
		return
	}()
	s.mu.Unlock()
	if len(startAddrs) == 0 {
		addrs, err := bootstrapAddrs()
		if err != nil {
			return nil, err
		}
		for _, addr := range addrs {
			startAddrs = append(startAddrs, addr)
		}
	}
	disc := &peerDiscovery{
		peerStream: &peerStream{
			Values: make(chan peerStreamValue),
			stop:   make(chan struct{}),
			values: make(chan peerStreamValue),
		},
		triedAddrs: bloom.NewWithEstimates(500000, 0.01),
		server:     s,
		infoHash:   infoHash,
	}
	// Function ferries from values to Values until discovery is halted.
	go func() {
		defer close(disc.Values)
		for {
			select {
			case psv := <-disc.values:
				select {
				case disc.Values <- psv:
				case <-disc.stop:
					return
				}
			case <-disc.stop:
				return
			}
		}
	}()
	disc.mu.Lock()
	for i, addr := range startAddrs {
		if i != 0 {
			time.Sleep(time.Millisecond)
		}
		disc.contact(addr)
	}
	disc.mu.Unlock()
	return disc.peerStream, nil
}

func (me *peerDiscovery) gotNodeAddr(addr net.Addr) {
	if util.AddrPort(addr) == 0 {
		// Not a contactable address.
		return
	}
	if me.triedAddrs.Test([]byte(addr.String())) {
		return
	}
	if me.server.ipBlocked(util.AddrIP(addr)) {
		return
	}
	me.contact(addr)
}

func (me *peerDiscovery) contact(addr net.Addr) {
	me.triedAddrs.Add([]byte(addr.String()))
	if err := me.getPeers(addr); err != nil {
		log.Printf("error sending get_peers request to %s: %s", addr, err)
		return
	}
	me.pending++
}

func (me *peerDiscovery) transactionClosed() {
	me.pending--
	if me.pending == 0 {
		me.Close()
		return
	}
}

func (me *peerDiscovery) responseNode(node NodeInfo) {
	me.gotNodeAddr(node.Addr)
}

func (me *peerDiscovery) closingCh() chan struct{} {
	return me.peerStream.stop
}

func (me *peerDiscovery) getPeers(addr net.Addr) error {
	me.server.mu.Lock()
	defer me.server.mu.Unlock()
	t, err := me.server.getPeers(addr, me.infoHash)
	if err != nil {
		return err
	}
	go func() {
		select {
		case m := <-t.Response:
			me.mu.Lock()
			if nodes := m.Nodes(); len(nodes) != 0 {
				for _, n := range nodes {
					me.responseNode(n)
				}
			}
			me.mu.Unlock()
			if vs := extractValues(m); vs != nil {
				nodeInfo := NodeInfo{
					Addr: t.remoteAddr,
				}
				id := func() string {
					defer func() {
						recover()
					}()
					return m["r"].(map[string]interface{})["id"].(string)
				}()
				copy(nodeInfo.ID[:], id)
				select {
				case me.peerStream.values <- peerStreamValue{
					Peers:    vs,
					NodeInfo: nodeInfo,
				}:
				case <-me.peerStream.stop:
				}
			}
		case <-me.closingCh():
		}
		t.Close()
		me.mu.Lock()
		me.transactionClosed()
		me.mu.Unlock()
	}()
	return nil
}

type peerStreamValue struct {
	Peers    []util.CompactPeer // Peers given in get_peers response.
	NodeInfo                    // The node that gave the response.
}

type peerStream struct {
	mu     sync.Mutex
	Values chan peerStreamValue
	// Inner chan is set to nil when on close.
	values chan peerStreamValue
	stop   chan struct{}
}

func (ps *peerStream) Close() {
	ps.mu.Lock()
	defer ps.mu.Unlock()
	select {
	case <-ps.stop:
	default:
		close(ps.stop)
	}
}
<commit_msg>dht: Fix partial deadlock closing peer discovery<commit_after>package dht

import (
	"bitbucket.org/anacrolix/go.torrent/util"
	"bitbucket.org/anacrolix/sync"
	"github.com/willf/bloom"
	"log"
	"net"
	"time"
)

type peerDiscovery struct {
	*peerStream
	triedAddrs *bloom.BloomFilter
	pending    int
	server     *Server
	infoHash   string
}

func (s *Server) GetPeers(infoHash string) (*peerStream, error) {
	s.mu.Lock()
	startAddrs := func() (ret []net.Addr) {
		for _, n := range s.closestGoodNodes(160, infoHash) {
			ret = append(ret, n.addr)
		}
		return
	}()
	s.mu.Unlock()
	if len(startAddrs) == 0 {
		addrs, err := bootstrapAddrs()
		if err != nil {
			return nil, err
		}
		for _, addr := range addrs {
			startAddrs = append(startAddrs, addr)
		}
	}
	disc := &peerDiscovery{
		peerStream: &peerStream{
			Values: make(chan peerStreamValue),
			stop:   make(chan struct{}),
			values: make(chan peerStreamValue),
		},
		triedAddrs: bloom.NewWithEstimates(500000, 0.01),
		server:     s,
		infoHash:   infoHash,
	}
	// Function ferries from values to Values until discovery is halted.
	go func() {
		defer close(disc.Values)
		for {
			select {
			case psv := <-disc.values:
				select {
				case disc.Values <- psv:
				case <-disc.stop:
					return
				}
			case <-disc.stop:
				return
			}
		}
	}()
	for i, addr := range startAddrs {
		if i != 0 {
			time.Sleep(time.Millisecond)
		}
		disc.mu.Lock()
		disc.contact(addr)
		disc.mu.Unlock()
	}
	return disc.peerStream, nil
}

func (me *peerDiscovery) gotNodeAddr(addr net.Addr) {
	if util.AddrPort(addr) == 0 {
		// Not a contactable address.
		return
	}
	if me.triedAddrs.Test([]byte(addr.String())) {
		return
	}
	if me.server.ipBlocked(util.AddrIP(addr)) {
		return
	}
	me.contact(addr)
}

func (me *peerDiscovery) contact(addr net.Addr) {
	me.triedAddrs.Add([]byte(addr.String()))
	if err := me.getPeers(addr); err != nil {
		log.Printf("error sending get_peers request to %s: %s", addr, err)
		return
	}
	me.pending++
}

func (me *peerDiscovery) transactionClosed() {
	me.pending--
	if me.pending == 0 {
		me.close()
		return
	}
}

func (me *peerDiscovery) responseNode(node NodeInfo) {
	me.gotNodeAddr(node.Addr)
}

func (me *peerDiscovery) closingCh() chan struct{} {
	return me.peerStream.stop
}

func (me *peerDiscovery) getPeers(addr net.Addr) error {
	me.server.mu.Lock()
	defer me.server.mu.Unlock()
	t, err := me.server.getPeers(addr, me.infoHash)
	if err != nil {
		return err
	}
	go func() {
		select {
		case m := <-t.Response:
			me.mu.Lock()
			for _, n := range m.Nodes() {
				me.responseNode(n)
			}
			me.mu.Unlock()
			if vs := extractValues(m); vs != nil {
				nodeInfo := NodeInfo{
					Addr: t.remoteAddr,
				}
				id := func() string {
					defer func() {
						recover()
					}()
					return m["r"].(map[string]interface{})["id"].(string)
				}()
				copy(nodeInfo.ID[:], id)
				select {
				case me.peerStream.values <- peerStreamValue{
					Peers:    vs,
					NodeInfo: nodeInfo,
				}:
				case <-me.peerStream.stop:
				}
			}
		case <-me.closingCh():
		}
		t.Close()
		me.mu.Lock()
		me.transactionClosed()
		me.mu.Unlock()
	}()
	return nil
}

type peerStreamValue struct {
	Peers    []util.CompactPeer // Peers given in get_peers response.
	NodeInfo                    // The node that gave the response.
}

type peerStream struct {
	mu     sync.Mutex
	Values chan peerStreamValue
	// Inner chan is set to nil when on close.
	values chan peerStreamValue
	stop   chan struct{}
}

func (ps *peerStream) Close() {
	ps.mu.Lock()
	defer ps.mu.Unlock()
	ps.close()
}

func (ps *peerStream) close() {
	select {
	case <-ps.stop:
	default:
		close(ps.stop)
	}
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"golang.org/x/oauth2"
	"golang.org/x/oauth2/google"
	"golang.org/x/oauth2/jwt"
)

func main() {

	conf := &jwt.Config{
		Email:      "email",
		PrivateKey: []byte("privatekey"),
		Scopes: []string{
			"https://www.googleapis.com/auth/projecthosting",
			"https://www.googleapis.com/auth/appengine.admin",
		},
		TokenURL: google.JWTTokenURL,
		// If you would like to impersonate a user, you can
		// create a transport with a subject. The following GET
		// request will be made on the behalf of user@example.com.
		// Optional.
		//Subject: "email@example.com",
	}

	tokensource := conf.TokenSource(oauth2.NoContext)
	fmt.Printf("tokenSrc=%v\n", tokensource)
	token, err := tokensource.Token()
	if err != nil {
		fmt.Printf("error getting token %v\n", err)
		return
	}
	fmt.Printf("access_token=%v\nexpiry=%v\ntoken_type=%v\n", token.AccessToken, token.Expiry,
		token.TokenType,
	)
}
<commit_msg>Removed the extra line to make parsing the access token easier<commit_after>package main

import (
	"fmt"
	"golang.org/x/oauth2"
	"golang.org/x/oauth2/google"
	"golang.org/x/oauth2/jwt"
)

func main() {

	conf := &jwt.Config{
		Email:      "email",
		PrivateKey: []byte("privatekey"),
		Scopes: []string{
			"https://www.googleapis.com/auth/projecthosting",
			"https://www.googleapis.com/auth/appengine.admin",
		},
		TokenURL: google.JWTTokenURL,
		// If you would like to impersonate a user, you can
		// create a transport with a subject. The following GET
		// request will be made on the behalf of user@example.com.
		// Optional.
		//Subject: "email@example.com",
	}

	tokensource := conf.TokenSource(oauth2.NoContext)
	token, err := tokensource.Token()
	if err != nil {
		fmt.Printf("error getting token %v\n", err)
		return
	}
	fmt.Printf("access_token=%v\nexpiry=%v\ntoken_type=%v\n",
		token.AccessToken,
		token.Expiry,
		token.TokenType,
	)
}
<|endoftext|>


<commit_before>package geojson

import (
	"encoding/json"
)

// A Feature corresponds to GeoJSON feature object
type Feature struct {
	ID          string                 `json:"id,omitempty"`
	Type        string                 `json:"type"`
	BoundingBox []float64              `json:"bbox,omitempty"`
	Geometry    *Geometry              `json:"geometry"`
	Properties  map[string]interface{} `json:"properties"`
	CRS         map[string]interface{} `json:"crs,omitempty"` // Coordinate Reference System Objects are not currently supported
}

// NewFeature creates and initializes a GeoJSON feature given the required attributes.
func NewFeature(geometry *Geometry) *Feature {
	return &Feature{
		Type:       "Feature",
		Geometry:   geometry,
		Properties: make(map[string]interface{}),
	}
}

// NewPointFeature creates and initializes a GeoJSON feature with a point geometry using the given coordinate.
func NewPointFeature(coordinate []float64) *Feature {
	return NewFeature(NewPointGeometry(coordinate))
}

// NewMultiPointFeature creates and initializes a GeoJSON feature with a multi-point geometry using the given coordinates.
func NewMultiPointFeature(coordinates ...[]float64) *Feature {
	return NewFeature(NewMultiPointGeometry(coordinates...))
}

// NewLineStringFeature creates and initializes a GeoJSON feature with a line string geometry using the given coordinates.
func NewLineStringFeature(coordinates [][]float64) *Feature {
	return NewFeature(NewLineStringGeometry(coordinates))
}

// NewMultiLineStringFeature creates and initializes a GeoJSON feature with a multi-line string geometry using the given lines.
func NewMultiLineStringFeature(lines ...[][]float64) *Feature {
	return NewFeature(NewMultiLineStringGeometry(lines...))
}

// NewPolygonFeature creates and initializes a GeoJSON feature with a polygon geometry using the given polygon.
func NewPolygonFeature(polygon [][][]float64) *Feature {
	return NewFeature(NewPolygonGeometry(polygon))
}

// NewMultiPolygonFeature creates and initializes a GeoJSON feature with a multi-polygon geometry using the given polygons.
func NewMultiPolygonFeature(polygons ...[][][]float64) *Feature {
	return NewFeature(NewMultiPolygonGeometry(polygons...))
}

// NewCollectionFeature creates and initializes a GeoJSON feature with a geometry collection geometry using the given geometries.
func NewCollectionFeature(geometries ...*Geometry) *Feature {
	return NewFeature(NewCollectionGeometry(geometries...))
}

// MarshalJSON converts the feature object into the proper JSON.
// It will handle the encoding of all the child geometries.
// Alternately one can call json.Marshal(f) directly for the same result.
func (f *Feature) MarshalJSON() ([]byte, error) {
	f.Type = "Feature"
	if len(f.Properties) == 0 {
		f.Properties = nil
	}

	return json.Marshal(*f)
}

// UnmarshalFeature decodes the data into a GeoJSON feature.
// Alternately one can call json.Unmarshal(f) directly for the same result.
func UnmarshalFeature(data []byte) (*Feature, error) {
	f := &Feature{}
	err := json.Unmarshal(data, f)
	if err != nil {
		return nil, err
	}

	return f, nil
}
<commit_msg>Fix JSON int ID<commit_after>package geojson

import (
	"encoding/json"
)

// A Feature corresponds to GeoJSON feature object
type Feature struct {
	ID          json.Number            `json:"id,omitempty"`
	Type        string                 `json:"type"`
	BoundingBox []float64              `json:"bbox,omitempty"`
	Geometry    *Geometry              `json:"geometry"`
	Properties  map[string]interface{} `json:"properties"`
	CRS         map[string]interface{} `json:"crs,omitempty"` // Coordinate Reference System Objects are not currently supported
}

// NewFeature creates and initializes a GeoJSON feature given the required attributes.
func NewFeature(geometry *Geometry) *Feature {
	return &Feature{
		Type:       "Feature",
		Geometry:   geometry,
		Properties: make(map[string]interface{}),
	}
}

// NewPointFeature creates and initializes a GeoJSON feature with a point geometry using the given coordinate.
func NewPointFeature(coordinate []float64) *Feature {
	return NewFeature(NewPointGeometry(coordinate))
}

// NewMultiPointFeature creates and initializes a GeoJSON feature with a multi-point geometry using the given coordinates.
func NewMultiPointFeature(coordinates ...[]float64) *Feature {
	return NewFeature(NewMultiPointGeometry(coordinates...))
}

// NewLineStringFeature creates and initializes a GeoJSON feature with a line string geometry using the given coordinates.
func NewLineStringFeature(coordinates [][]float64) *Feature {
	return NewFeature(NewLineStringGeometry(coordinates))
}

// NewMultiLineStringFeature creates and initializes a GeoJSON feature with a multi-line string geometry using the given lines.
func NewMultiLineStringFeature(lines ...[][]float64) *Feature {
	return NewFeature(NewMultiLineStringGeometry(lines...))
}

// NewPolygonFeature creates and initializes a GeoJSON feature with a polygon geometry using the given polygon.
func NewPolygonFeature(polygon [][][]float64) *Feature {
	return NewFeature(NewPolygonGeometry(polygon))
}

// NewMultiPolygonFeature creates and initializes a GeoJSON feature with a multi-polygon geometry using the given polygons.
func NewMultiPolygonFeature(polygons ...[][][]float64) *Feature {
	return NewFeature(NewMultiPolygonGeometry(polygons...))
}

// NewCollectionFeature creates and initializes a GeoJSON feature with a geometry collection geometry using the given geometries.
func NewCollectionFeature(geometries ...*Geometry) *Feature {
	return NewFeature(NewCollectionGeometry(geometries...))
}

// MarshalJSON converts the feature object into the proper JSON.
// It will handle the encoding of all the child geometries.
// Alternately one can call json.Marshal(f) directly for the same result.
func (f *Feature) MarshalJSON() ([]byte, error) {
	f.Type = "Feature"
	if len(f.Properties) == 0 {
		f.Properties = nil
	}

	return json.Marshal(*f)
}

// UnmarshalFeature decodes the data into a GeoJSON feature.
// Alternately one can call json.Unmarshal(f) directly for the same result.
func UnmarshalFeature(data []byte) (*Feature, error) {
	f := &Feature{}
	err := json.Unmarshal(data, f)
	if err != nil {
		return nil, err
	}

	return f, nil
}
<|endoftext|>


<commit_before>package semaphore

import (
	"testing"
	"math/rand"
	"fmt"
	"time"
)

func TestSemaphore(t *testing.T) {
	s := NewSemaphore(3)

	for{
		s.P()
		go func(){
			t := rand.Intn(5)
			fmt.Println("sub called,sleeping",t)
			time.Sleep(time.Duration(t)*time.Second)
			s.V()
		}()
	}

}<commit_msg>semaphore: update test(s)<commit_after>package semaphore

import (
	"math/rand"
	"testing"
	"time"
)

func TestSemaphore(t *testing.T) {
	s := NewSemaphore(5)

	for index := 0; index < 20; index++ {
		s.P()
		go func() {
			r := rand.Intn(3)
			t.Log("sub called,sleeping", r)
			time.Sleep(time.Duration(r) * time.Second)
			s.V()
		}()
	}
}
<|endoftext|>


<commit_before>package main

import (
	"testing"
)

func TestEscapeSearch(t *testing.T) {
	tests := []struct {
		in  string
		out string
	}{
		{"", ""},
		{"foo", "foo"},
		{"foo bar", "foo+bar"},
		{"foo bar baz", "foo+bar+baz"},
	}

	for _, test := range tests {
		res := escapeSearch(test.in)
		if res != test.out {
			t.Errorf("Expected (%s) for (%s) got (%s)", test.out, test.in, res)
		}
	}
}

func TestSearchString(t *testing.T) {
	tests := []struct {
		q   Query
		out string
		err bool
	}{
		{Query{"", "", 0}, "", true}, // Test empty error
		{Query{"foo", "", 0}, baseURL + "?q=foo" + helpers + "10", false}, // Test single query
		{Query{"foo bar", "", 0}, baseURL + "?q=foo+bar" + helpers + "10", false}, // Test spaced query
		{Query{"bar baz", "go", 0}, baseURL + "?q=bar+baz+language:go" + helpers + "10", false}, // Test spaced and language
		{Query{"baz qux", "objc", 5}, baseURL + "?q=baz+qux+language:objc" + helpers + "5", false}, // Test custom number
	}

	for _, test := range tests {
		res, err := searchString(test.q)
		if test.err != (err != nil) {
			t.Errorf("Expected error to be (%t) got text (%s) for (%+v)", test.err, err.Error(), test.q)
		}

		if res != test.out {
			t.Errorf("Expected (%s) for (%+v) got (%s)", test.out, test.q, res)
		}
	}
}
<commit_msg>Test for repo string<commit_after>package main

import (
	"testing"
	"strings"
	"strconv"
)

func TestEscapeSearch(t *testing.T) {
	tests := []struct {
		in  string
		out string
	}{
		{"", ""},
		{"foo", "foo"},
		{"foo bar", "foo+bar"},
		{"foo bar baz", "foo+bar+baz"},
	}

	for _, test := range tests {
		res := escapeSearch(test.in)
		if res != test.out {
			t.Errorf("Expected (%s) for (%s) got (%s)", test.out, test.in, res)
		}
	}
}

func TestSearchString(t *testing.T) {
	tests := []struct {
		q   Query
		out string
		err bool
	}{
		{Query{"", "", 0}, "", true}, // Test empty error
		{Query{"foo", "", 0}, baseURL + "?q=foo" + helpers + "0", false}, // Test single query
		{Query{"foo bar", "", 1}, baseURL + "?q=foo+bar" + helpers + "1", false}, // Test spaced query
		{Query{"bar baz", "go", 2}, baseURL + "?q=bar+baz+language:go" + helpers + "2", false}, // Test spaced and language
		{Query{"baz qux", "objc", 5}, baseURL + "?q=baz+qux+language:objc" + helpers + "5", false}, // Test custom number
	}

	for _, test := range tests {
		res, err := searchString(test.q)
		if test.err != (err != nil) {
			t.Errorf("Expected error to be (%t) got text (%s) for (%+v)", test.err, err.Error(), test.q)
		}

		if res != test.out {
			t.Errorf("Expected (%s) for (%+v) got (%s)", test.out, test.q, res)
		}
	}
}

func TestRepoString(t *testing.T) {
	tests := []struct {
		url string
		stars int
		lang string
		name string
	}{
		{"https://github.com/foo/bar", 10, "C", "foo/bar"},
		{"othersite.com/foo/bar", 12, "C++", "othersite.com/foo/bar"},
	}

	for _, test := range tests {
		str := repoString(test.url, test.stars, test.lang)
		fields := strings.Fields(str)
		if len(fields) < 1 {
			t.Errorf("Expected fields from (%+v)", test)
		}

		if fields[0] != test.name {
			t.Errorf("Expected (%s) got (%s) for (%+v)", test.name, fields[0], test)
		}

		if fields[1] != strconv.Itoa(test.stars) {
			t.Errorf("Expected (%d) got (%s) for (%+v)", test.stars, fields[1], test)
		}

		if fields[2] != test.lang {
			t.Errorf("Expected (%s) got (%s) for (%+v)", test.lang, fields[2], test)
		}
	}
}
<|endoftext|>


<commit_before>// The ghstatus package allows you to check the system status of GitHub from
// your own applications. The status information is retrieved from GitHub's
// system status API (https://status.github.com/api).

package ghstatus

import (
	"encoding/json"
	"io/ioutil"
	"net/http"
)

// The root URL of GitHub's system status API.
var StatusApiUrl = "https://status.github.com"

// Possible status values.
const (
	StatusGood  = "good"
	StatusMinor = "minor"
	StatusMajor = "major"
)

// Current system status as returned by the /api/status.json endpoint.
type Status struct {
	Status      string `json:"status"`
	LastUpdated string `json:"last_updated"`
}

// A status message as returned by the /api/messages.json and
// /api/last-message.json endpoints.
type Message struct {
	Status    string `json:"status"`
	Body      string `json:"body"`
	CreatedOn string `json:"created_on"`
}

func sendRequest(endpoint string, v interface{}) error {
	resp, err := http.Get(StatusApiUrl + endpoint)
	if err != nil {
		return err
	}
	defer resp.Body.Close()

	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return err
	}

	return json.Unmarshal(body, v)
}

// Get current system status and timestamp.
func GetStatus() (*Status, error) {
	var status *Status
	if err := sendRequest("/api/status.json", &status); err != nil {
		return nil, err
	}
	return status, nil
}

// Get most recent human communications with status and timestamp.
func GetMessages() ([]Message, error) {
	var messages []Message
	if err := sendRequest("/api/messages.json", &messages); err != nil {
		return nil, err
	}
	return messages, nil
}

// Get last human communication, status, and timestamp.
func GetLastMessage() (*Message, error) {
	var message *Message
	if err := sendRequest("/api/last-message.json", &message); err != nil {
		return nil, err
	}
	return message, nil
}
<commit_msg>Check HTTP status code<commit_after>// The ghstatus package allows you to check the system status of GitHub from
// your own applications. The status information is retrieved from GitHub's
// system status API (https://status.github.com/api).

package ghstatus

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

// The root URL of GitHub's system status API.
var StatusApiUrl = "https://status.github.com"

// Possible status values.
const (
	StatusGood  = "good"
	StatusMinor = "minor"
	StatusMajor = "major"
)

// Current system status as returned by the /api/status.json endpoint.
type Status struct {
	Status      string `json:"status"`
	LastUpdated string `json:"last_updated"`
}

// A status message as returned by the /api/messages.json and
// /api/last-message.json endpoints.
type Message struct {
	Status    string `json:"status"`
	Body      string `json:"body"`
	CreatedOn string `json:"created_on"`
}

func sendRequest(endpoint string, v interface{}) error {
	resp, err := http.Get(StatusApiUrl + endpoint)
	if err != nil {
		return err
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		return fmt.Errorf("HTTP error: %s", resp.Status)
	}

	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return err
	}

	return json.Unmarshal(body, v)
}

// Get current system status and timestamp.
func GetStatus() (*Status, error) {
	var status *Status
	if err := sendRequest("/api/status.json", &status); err != nil {
		return nil, err
	}
	return status, nil
}

// Get most recent human communications with status and timestamp.
func GetMessages() ([]Message, error) {
	var messages []Message
	if err := sendRequest("/api/messages.json", &messages); err != nil {
		return nil, err
	}
	return messages, nil
}

// Get last human communication, status, and timestamp.
func GetLastMessage() (*Message, error) {
	var message *Message
	if err := sendRequest("/api/last-message.json", &message); err != nil {
		return nil, err
	}
	return message, nil
}
<|endoftext|>