<commit_before>package api

import (
	"bytes"
	"encoding/json"
	"net/http"
	"net/http/httptest"
	"testing"

	"github.com/chromium/hstspreload"
	"github.com/chromium/hstspreload.appspot.com/database"
	"github.com/chromium/hstspreload/chromiumpreload"
)

var emptyIssues = hstspreload.Issues{}

var issuesWithWarnings = hstspreload.Issues{
	Warnings: []hstspreload.Issue{{"code", "warning", "message"}},
}

var issuesWithErrors = hstspreload.Issues{
	Errors: []hstspreload.Issue{
		{"code1", "warning1", "message1"},
		{"code2", "warning2", "message2"},
	},
	Warnings: []hstspreload.Issue{{"code", "warning", "message"}},
}

func mockAPI() (api API, ms *database.MockState, h *mockHstspreload, c *mockChromiumpreload) {
	db, ms := database.NewMock()
	h = &mockHstspreload{}
	c = &mockChromiumpreload{}
	api = API{
		database:        db,
		hstspreload:     h,
		chromiumpreload: c,
	}
	return api, ms, h, c
}

func TestCheckConnection(t *testing.T) {
	api, ms, _, _ := mockAPI()

	if err := api.CheckConnection(); err != nil {
		t.Errorf("%s", err)
	}

	ms.FailCalls = true
	if err := api.CheckConnection(); err == nil {
		t.Error("connection should fail")
	}
}

// Any non-zero values are considered wanted.
type wantBody struct {
	text   string
	state  *database.DomainState
	issues *hstspreload.Issues
}

type apiTestCase struct {
	description string
	handlerFunc http.HandlerFunc
	method      string
	url         string
	wantCode    int
	wantBody    wantBody
}

func TestStatusSubmitPendingUpdate(t *testing.T) {
	api, _, h, c := mockAPI()

	h.preloadableResponses = make(map[string]hstspreload.Issues)
	h.preloadableResponses["garron.net"] = emptyIssues
	h.preloadableResponses["badssl.com"] = issuesWithWarnings
	h.preloadableResponses["example.com"] = issuesWithErrors

	c.list.Entries = []chromiumpreload.PreloadEntry{
		{"garron.net", chromiumpreload.ForceHTTPS, true},
		{"chromium.org", chromiumpreload.ForceHTTPS, false},
		{"godoc.og", "", true},
	}

	wantStatus := func(description string, domain string, status database.PreloadStatus) apiTestCase {
		return apiTestCase{description, api.Status, "GET", "?domain=" + domain,
			200, wantBody{state: &database.DomainState{
				Name:   domain,
				Status: status,
			}},
		}
	}

	apiTestSequence := []apiTestCase{
		// wrong HTTP method
		{"status wrong method", api.Status, "POST", "?domain=garron.net",
			405, wantBody{text: "Wrong method. Requires GET.\n"}},
		{"pending wrong method", api.Pending, "POST", "",
			405, wantBody{text: "Wrong method. Requires GET.\n"}},
		{"submit wrong method", api.Submit, "GET", "?domain=garron.net",
			405, wantBody{text: "Wrong method. Requires POST.\n"}},

		// initial
		wantStatus("garron.net initial", "garron.net", database.StatusUnknown),
		wantStatus("example.com initial", "example.com", database.StatusUnknown),
		{"pending 1", api.Pending, "GET", "",
			200, wantBody{text: "[\n]\n"}},

		// submit
		{"bad submit", api.Submit, "POST", "?domain=example.com",
			200, wantBody{issues: &issuesWithErrors}},
		{"good submit", api.Submit, "POST", "?domain=garron.net",
			200, wantBody{issues: &emptyIssues}},
		{"pending 2", api.Pending, "GET", "",
			200, wantBody{text: "[\n    { \"name\": \"garron.net\", \"include_subdomains\": true, \"mode\": \"force-https\" }\n]\n"}},

		// update
		wantStatus("garron.net pending", "garron.net", database.StatusPending),
		{"update", api.Update, "GET", "",
			200, wantBody{text: "The preload list has 3 entries.\n- # of preloaded HSTS entries: 2\n- # to be added in this update: 2\n- # to be removed this update: 0\nSuccess. 2 domain states updated.\n"}},
		{"pending 3", api.Pending, "GET", "",
			200, wantBody{text: "[\n]\n"}},

		// after update
		wantStatus("example.com after update", "example.com", database.StatusUnknown),
		wantStatus("garron.net after update", "garron.net", database.StatusPreloaded),
		wantStatus("chromium.org after update", "chromium.org", database.StatusPreloaded),
		wantStatus("godoc.org after update", "godoc.org", database.StatusUnknown),
	}

	for _, tt := range apiTestSequence {
		w := httptest.NewRecorder()
		w.Body = &bytes.Buffer{}

		r, err := http.NewRequest(tt.method, tt.url, nil)
		if err != nil {
			t.Fatalf("[%s] %s", tt.description, err)
		}

		tt.handlerFunc(w, r)

		if w.Code != tt.wantCode {
			t.Errorf("[%s] Status code does not match wanted: %d", tt.description, w.Code)
		}

		if tt.wantBody.text != "" {
			text := w.Body.String()
			if text != tt.wantBody.text {
				t.Errorf("[%s] Body text does not match wanted: %#v", tt.description, text)
			}
		}

		if tt.wantBody.state != nil {
			var s database.DomainState
			if err := json.Unmarshal(w.Body.Bytes(), &s); err != nil {
				t.Fatalf("[%s] %s", tt.description, err)
			}
			if !s.MatchesWanted(*tt.wantBody.state) {
				t.Errorf("[%s] Domain state does not match wanted: %#v", tt.description, s)
			}
		}

		if tt.wantBody.issues != nil {
			var iss hstspreload.Issues
			if err := json.Unmarshal(w.Body.Bytes(), &iss); err != nil {
				t.Fatalf("[%s] %s", tt.description, err)
			}
			if !iss.Match(*tt.wantBody.issues) {
				t.Errorf("[%s] Issues do not match wanted: %#v", tt.description, iss)
			}
		}
	}
}
<commit_msg>Change the main api.API test to be one large table-driven test.<commit_after>package api

import (
	"bytes"
	"encoding/json"
	"net/http"
	"net/http/httptest"
	"testing"

	"github.com/chromium/hstspreload"
	"github.com/chromium/hstspreload.appspot.com/database"
	"github.com/chromium/hstspreload/chromiumpreload"
)

var emptyIssues = hstspreload.Issues{}

var issuesWithWarnings = hstspreload.Issues{
	Warnings: []hstspreload.Issue{{"code", "warning", "message"}},
}

var issuesWithErrors = hstspreload.Issues{
	Errors: []hstspreload.Issue{
		{"code1", "warning1", "message1"},
		{"code2", "warning2", "message2"},
	},
	Warnings: []hstspreload.Issue{{"code", "warning", "message"}},
}

func mockAPI() (api API, ms *database.MockState, h *mockHstspreload, c *mockChromiumpreload) {
	db, ms := database.NewMock()
	h = &mockHstspreload{}
	c = &mockChromiumpreload{}
	api = API{
		database:        db,
		hstspreload:     h,
		chromiumpreload: c,
	}
	return api, ms, h, c
}

func TestCheckConnection(t *testing.T) {
	api, ms, _, _ := mockAPI()

	if err := api.CheckConnection(); err != nil {
		t.Errorf("%s", err)
	}

	ms.FailCalls = true
	if err := api.CheckConnection(); err == nil {
		t.Error("connection should fail")
	}
}

// Any non-zero values are considered wanted.
type wantBody struct {
	text   string
	state  *database.DomainState
	issues *hstspreload.Issues
}

type apiTestCase struct {
	description string
	handlerFunc http.HandlerFunc
	method      string
	url         string
	wantCode    int
	wantBody    wantBody
}

func TestAPI(t *testing.T) {
	api, _, h, c := mockAPI()

	h.preloadableResponses = make(map[string]hstspreload.Issues)
	h.preloadableResponses["garron.net"] = emptyIssues
	h.preloadableResponses["badssl.com"] = issuesWithWarnings
	h.preloadableResponses["example.com"] = issuesWithErrors

	h.removableResponses = make(map[string]hstspreload.Issues)
	h.removableResponses["removable.test"] = emptyIssues
	h.removableResponses["unremovable.test"] = issuesWithErrors

	c.list.Entries = []chromiumpreload.PreloadEntry{
		{"garron.net", chromiumpreload.ForceHTTPS, true},
		{"chromium.org", chromiumpreload.ForceHTTPS, false},
		{"godoc.og", "", true},
	}

	wantStatus := func(description string, domain string, status database.PreloadStatus) apiTestCase {
		return apiTestCase{description, api.Status, "GET", "?domain=" + domain,
			200, wantBody{state: &database.DomainState{
				Name:   domain,
				Status: status,
			}},
		}
	}

	apiTestSequence := []apiTestCase{
		// wrong HTTP method
		{"submit wrong method", api.Preloadable, "POST", "?domain=garron.net",
			405, wantBody{text: "Wrong method. Requires GET.\n"}},
		{"submit wrong method", api.Removable, "POST", "?domain=garron.net",
			405, wantBody{text: "Wrong method. Requires GET.\n"}},
		{"status wrong method", api.Status, "POST", "?domain=garron.net",
			405, wantBody{text: "Wrong method. Requires GET.\n"}},
		{"pending wrong method", api.Pending, "POST", "",
			405, wantBody{text: "Wrong method. Requires GET.\n"}},
		{"submit wrong method", api.Submit, "GET", "?domain=garron.net",
			405, wantBody{text: "Wrong method. Requires POST.\n"}},

		// misc. issues
		{"status wrong method", api.Status, "GET", "",
			400, wantBody{text: ""}},
		{"status wrong method", api.Status, "GET", "?domain=",
			400, wantBody{text: ""}},

		// preloadable and removable
		{"preloadable good", api.Preloadable, "GET", "?domain=garron.net",
			200, wantBody{issues: &emptyIssues}},
		{"preloadable warning", api.Preloadable, "GET", "?domain=badssl.com",
			200, wantBody{issues: &issuesWithWarnings}},
		{"preloadable error", api.Preloadable, "GET", "?domain=example.com",
			200, wantBody{issues: &issuesWithErrors}},

		// removable
		{"preloadable good", api.Removable, "GET", "?domain=removable.test",
			200, wantBody{issues: &emptyIssues}},
		{"preloadable error", api.Removable, "GET", "?domain=unremovable.test",
			200, wantBody{issues: &issuesWithErrors}},

		// initial
		wantStatus("garron.net initial", "garron.net", database.StatusUnknown),
		wantStatus("example.com initial", "example.com", database.StatusUnknown),
		{"pending 1", api.Pending, "GET", "",
			200, wantBody{text: "[\n]\n"}},

		// submit
		{"bad submit", api.Submit, "POST", "?domain=example.com",
			200, wantBody{issues: &issuesWithErrors}},
		{"good submit", api.Submit, "POST", "?domain=garron.net",
			200, wantBody{issues: &emptyIssues}},

		// pending
		{"pending 2", api.Pending, "GET", "",
			200, wantBody{text: "[\n    { \"name\": \"garron.net\", \"include_subdomains\": true, \"mode\": \"force-https\" }\n]\n"}},
		{"submit while pending", api.Submit, "POST", "?domain=garron.net",
			200, wantBody{issues: &hstspreload.Issues{
				Warnings: []hstspreload.Issue{{Code: "server.preload.already_pending"}},
			}}},

		// update
		wantStatus("garron.net pending", "garron.net", database.StatusPending),
		{"update", api.Update, "GET", "",
			200, wantBody{text: "The preload list has 3 entries.\n- # of preloaded HSTS entries: 2\n- # to be added in this update: 2\n- # to be removed this update: 0\nSuccess. 2 domain states updated.\n"}},
		{"pending 3", api.Pending, "GET", "",
			200, wantBody{text: "[\n]\n"}},

		// after update
		{"submit after preloaded", api.Submit, "POST", "?domain=garron.net",
			200, wantBody{issues: &hstspreload.Issues{
				Errors: []hstspreload.Issue{{Code: "server.preload.already_preloaded"}},
			}}},
		wantStatus("example.com after update", "example.com", database.StatusUnknown),
		wantStatus("garron.net after update", "garron.net", database.StatusPreloaded),
		wantStatus("chromium.org after update", "chromium.org", database.StatusPreloaded),
		wantStatus("godoc.org after update", "godoc.org", database.StatusUnknown),
	}

	for _, tt := range apiTestSequence {
		w := httptest.NewRecorder()
		w.Body = &bytes.Buffer{}

		r, err := http.NewRequest(tt.method, tt.url, nil)
		if err != nil {
			t.Fatalf("[%s] %s", tt.description, err)
		}

		tt.handlerFunc(w, r)

		if w.Code != tt.wantCode {
			t.Errorf("[%s] Status code does not match wanted: %d", tt.description, w.Code)
		}

		if tt.wantBody.text != "" {
			text := w.Body.String()
			if text != tt.wantBody.text {
				t.Errorf("[%s] Body text does not match wanted: %#v", tt.description, text)
			}
		}

		if tt.wantBody.state != nil {
			var s database.DomainState
			if err := json.Unmarshal(w.Body.Bytes(), &s); err != nil {
				t.Fatalf("[%s] %s", tt.description, err)
			}
			if !s.MatchesWanted(*tt.wantBody.state) {
				t.Errorf("[%s] Domain state does not match wanted: %#v", tt.description, s)
			}
		}

		if tt.wantBody.issues != nil {
			var iss hstspreload.Issues
			if err := json.Unmarshal(w.Body.Bytes(), &iss); err != nil {
				t.Fatalf("[%s] %s", tt.description, err)
			}
			if !iss.Match(*tt.wantBody.issues) {
				t.Errorf("[%s] Issues do not match wanted: %#v", tt.description, iss)
			}
		}
	}
}
<|endoftext|>


<commit_before>package api

import (
	"testing"

	"github.com/stretchr/testify/require"
)

func TestCSIVolumes_CRUD(t *testing.T) {
	t.Parallel()
	c, s, root := makeACLClient(t, nil, nil)
	defer s.Stop()
	v := c.CSIVolumes()

	// Successful empty result
	vols, qm, err := v.List(nil)
	require.NoError(t, err)
	require.NotEqual(t, 0, qm.LastIndex)
	require.Equal(t, 0, len(vols))

	// Authorized QueryOpts. Use the root token to just bypass ACL details
	opts := &QueryOptions{
		Region:    "global",
		Namespace: "default",
		AuthToken: root.SecretID,
	}

	wpts := &WriteOptions{
		Region:    "global",
		Namespace: "default",
		AuthToken: root.SecretID,
	}

	// Register a volume
	id := "DEADBEEF-31B5-8F78-7986-DD404FDA0CD1"
	_, err = v.Register(&CSIVolume{
		ID:             id,
		Namespace:      "default",
		PluginID:       "adam",
		AccessMode:     CSIVolumeAccessModeMultiNodeSingleWriter,
		AttachmentMode: CSIVolumeAttachmentModeFilesystem,
		Topologies:     []*CSITopology{{Segments: map[string]string{"foo": "bar"}}},
	}, wpts)
	require.NoError(t, err)

	// Successful result with volumes
	vols, qm, err = v.List(opts)
	require.NoError(t, err)
	require.NotEqual(t, 0, qm.LastIndex)
	require.Equal(t, 1, len(vols))

	// Successful info query
	vol, qm, err := v.Info(id, opts)
	require.NoError(t, err)
	require.Equal(t, "bar", vol.Topologies[0].Segments["foo"])

	// Deregister the volume
	err = v.Deregister(id, wpts)
	require.NoError(t, err)

	// Successful empty result
	vols, qm, err = v.List(nil)
	require.NoError(t, err)
	require.NotEqual(t, 0, qm.LastIndex)
	require.Equal(t, 0, len(vols))

	// Failed info query
	vol, qm, err = v.Info(id, opts)
	require.Error(t, err, "missing")
}

func TestCSIPlugins_viaJob(t *testing.T) {
	t.Parallel()
	c, s, root := makeACLClient(t, nil, nil)
	defer s.Stop()
	p := c.CSIPlugins()

	// Successful empty result
	plugs, qm, err := p.List(nil)
	require.NoError(t, err)
	require.NotEqual(t, 0, qm.LastIndex)
	require.Equal(t, 0, len(plugs))

	// Authorized QueryOpts. Use the root token to just bypass ACL details
	opts := &QueryOptions{
		Region:    "global",
		Namespace: "default",
		AuthToken: root.SecretID,
	}

	wpts := &WriteOptions{
		Region:    "global",
		Namespace: "default",
		AuthToken: root.SecretID,
	}

	// Register a plugin job
	j := c.Jobs()
	job := testJob()
	job.Namespace = stringToPtr("default")
	job.TaskGroups[0].Tasks[0].CSIPluginConfig = &TaskCSIPluginConfig{
		ID:       "foo",
		Type:     "monolith",
		MountDir: "/not-empty",
	}
	_, _, err = j.Register(job, wpts)
	require.NoError(t, err)

	// Successful result with the plugin
	plugs, qm, err = p.List(opts)
	require.NoError(t, err)
	require.NotEqual(t, 0, qm.LastIndex)
	require.Equal(t, 1, len(plugs))

	// Successful info query
	plug, qm, err := p.Info("foo", opts)
	require.NoError(t, err)
	require.NotNil(t, plug.Jobs[*job.Namespace][*job.ID])
	require.Equal(t, *job.ID, *plug.Jobs[*job.Namespace][*job.ID].ID)
}
<commit_msg>api: Register CSIPlugin before registering a Volume<commit_after>package api

import (
	"testing"

	"github.com/stretchr/testify/require"
)

func TestCSIVolumes_CRUD(t *testing.T) {
	t.Parallel()
	c, s, root := makeACLClient(t, nil, nil)
	defer s.Stop()
	v := c.CSIVolumes()

	// Successful empty result
	vols, qm, err := v.List(nil)
	require.NoError(t, err)
	require.NotEqual(t, 0, qm.LastIndex)
	require.Equal(t, 0, len(vols))

	// Authorized QueryOpts. Use the root token to just bypass ACL details
	opts := &QueryOptions{
		Region:    "global",
		Namespace: "default",
		AuthToken: root.SecretID,
	}

	wpts := &WriteOptions{
		Region:    "global",
		Namespace: "default",
		AuthToken: root.SecretID,
	}

	// Register a plugin job
	j := c.Jobs()
	job := testJob()
	job.Namespace = stringToPtr("default")
	job.TaskGroups[0].Tasks[0].CSIPluginConfig = &TaskCSIPluginConfig{
		ID:       "foo",
		Type:     "monolith",
		MountDir: "/not-empty",
	}
	_, _, err = j.Register(job, wpts)
	require.NoError(t, err)

	// Register a volume
	id := "DEADBEEF-31B5-8F78-7986-DD404FDA0CD1"
	_, err = v.Register(&CSIVolume{
		ID:             id,
		Namespace:      "default",
		PluginID:       "foo",
		AccessMode:     CSIVolumeAccessModeMultiNodeSingleWriter,
		AttachmentMode: CSIVolumeAttachmentModeFilesystem,
		Topologies:     []*CSITopology{{Segments: map[string]string{"foo": "bar"}}},
	}, wpts)
	require.NoError(t, err)

	// Successful result with volumes
	vols, qm, err = v.List(opts)
	require.NoError(t, err)
	require.NotEqual(t, 0, qm.LastIndex)
	require.Equal(t, 1, len(vols))

	// Successful info query
	vol, qm, err := v.Info(id, opts)
	require.NoError(t, err)
	require.Equal(t, "bar", vol.Topologies[0].Segments["foo"])

	// Deregister the volume
	err = v.Deregister(id, wpts)
	require.NoError(t, err)

	// Successful empty result
	vols, qm, err = v.List(nil)
	require.NoError(t, err)
	require.NotEqual(t, 0, qm.LastIndex)
	require.Equal(t, 0, len(vols))

	// Failed info query
	vol, qm, err = v.Info(id, opts)
	require.Error(t, err, "missing")
}

func TestCSIPlugins_viaJob(t *testing.T) {
	t.Parallel()
	c, s, root := makeACLClient(t, nil, nil)
	defer s.Stop()
	p := c.CSIPlugins()

	// Successful empty result
	plugs, qm, err := p.List(nil)
	require.NoError(t, err)
	require.NotEqual(t, 0, qm.LastIndex)
	require.Equal(t, 0, len(plugs))

	// Authorized QueryOpts. Use the root token to just bypass ACL details
	opts := &QueryOptions{
		Region:    "global",
		Namespace: "default",
		AuthToken: root.SecretID,
	}

	wpts := &WriteOptions{
		Region:    "global",
		Namespace: "default",
		AuthToken: root.SecretID,
	}

	// Register a plugin job
	j := c.Jobs()
	job := testJob()
	job.Namespace = stringToPtr("default")
	job.TaskGroups[0].Tasks[0].CSIPluginConfig = &TaskCSIPluginConfig{
		ID:       "foo",
		Type:     "monolith",
		MountDir: "/not-empty",
	}
	_, _, err = j.Register(job, wpts)
	require.NoError(t, err)

	// Successful result with the plugin
	plugs, qm, err = p.List(opts)
	require.NoError(t, err)
	require.NotEqual(t, 0, qm.LastIndex)
	require.Equal(t, 1, len(plugs))

	// Successful info query
	plug, qm, err := p.Info("foo", opts)
	require.NoError(t, err)
	require.NotNil(t, plug.Jobs[*job.Namespace][*job.ID])
	require.Equal(t, *job.ID, *plug.Jobs[*job.Namespace][*job.ID].ID)
}
<|endoftext|>


<commit_before>// Copyright 2017 Google Inc.
//
// 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 external implements an external probe type for cloudprober.

External probe type executes an external process for actual probing. These probes
can have two modes: "once" and "server". In "once" mode, the external process is
started for each probe run cycle, while in "server" mode, external process is
started only if it's not running already and Cloudprober communicates with it
over stdin/stdout for each probe cycle.

TODO: Add a way to test this program. Write another program that
implements the probe server protocol and use that for testing.
*/
package external

import (
	"bufio"
	"context"
	"fmt"
	"io"
	"os/exec"
	"strings"
	"sync"
	"time"

	"github.com/golang/protobuf/proto"
	"github.com/google/cloudprober/logger"
	"github.com/google/cloudprober/metrics"
	"github.com/google/cloudprober/probes/external/serverutils"
	"github.com/google/cloudprober/probes/options"
)

// Probe holds aggregate information about all probe runs, per-target.
type Probe struct {
	name    string
	mode    string
	cmdName string
	cmdArgs []string
	opts    *options.Options
	c       *ProbeConf
	l       *logger.Logger
	ipVer   int

	// book-keeping params
	requestID  int32
	cmdRunning bool
	cmdStdin   io.Writer
	cmdStdout  io.Reader
	cmdStderr  io.Reader
	replyChan  chan *serverutils.ProbeReply
	success    int64         // toal probe successes
	total      int64         // total number of probes
	latency    time.Duration // cumulative probe latency

	// EventMetrics created from external probe process output
	defaultPayloadMetrics *metrics.EventMetrics
	payloadMetrics        map[string]*metrics.EventMetrics // Per-target metrics
	payloadMetricsMu      sync.Mutex
}

// Init initializes the probe with the given params.
func (p *Probe) Init(name string, opts *options.Options) error {
	c, ok := opts.ProbeConf.(*ProbeConf)
	if !ok {
		return fmt.Errorf("not external probe config")
	}
	p.name = name
	p.opts = opts
	if p.l = opts.Logger; p.l == nil {
		p.l = &logger.Logger{}
	}
	p.c = c
	p.replyChan = make(chan *serverutils.ProbeReply)
	p.ipVer = int(p.c.GetIpVersion())

	switch p.c.GetMode() {
	case ProbeConf_ONCE:
		p.mode = "once"
	case ProbeConf_SERVER:
		p.mode = "server"
	default:
		p.l.Errorf("Invalid mode: %s", p.c.GetMode())
	}

	cmdParts := strings.Split(p.c.GetCommand(), " ")
	p.cmdName = cmdParts[0]
	p.cmdArgs = cmdParts[1:len(cmdParts)]
	p.payloadMetrics = make(map[string]*metrics.EventMetrics)
	return p.initPayloadMetrics()
}

// substituteLabels replaces occurrences of @label@ with the values from
// abels.  It returns the substituted string and a bool indicating if there
// was a @label@ that did not exist in the labels map.
func substituteLabels(in string, labels map[string]string) (string, bool) {
	delimiter := "@"
	output := ""
	words := strings.Split(in, delimiter)
	count := len(words)
	foundAll := true
	for j, kwd := range words {
		// Even number of words => just copy out.
		if j%2 == 0 {
			output += kwd
			continue
		}
		// Special case: If there are an odd number of '@' (unbalanced), the last
		// odd index doesn't actually have a closing '@', so we just append it as it
		// is.
		if j == count-1 {
			output += delimiter
			output += kwd
			continue
		}

		// Special case: "@@" => "@"
		if kwd == "" {
			output += delimiter
			continue
		}

		// Finally, the labels.
		replace, ok := labels[kwd]
		if ok {
			output += replace
			continue
		}

		// Nothing - put the token back in.
		foundAll = false
		output += delimiter
		output += kwd
		output += delimiter
	}
	return output, foundAll
}

func (p *Probe) startCmdIfNotRunning() error {
	// Start external probe command if it's not running already. Note that here we
	// are trusting the cmdRunning to be set correctly. It can be false for 3 reasons:
	// 1) This is the first call and the process has actually never been started.
	// 2) cmd.Start() started the process but still returned an error.
	// 3) cmd.Wait() returned incorrectly, while the process was still running.
	//
	// 2 or 3 should never happen as per design, but managing processes can be tricky.
	// Documenting here to help with debugging if we run into an issue.
	if p.cmdRunning {
		return nil
	}
	p.l.Infof("Starting external command: %s %s", p.cmdName, strings.Join(p.cmdArgs, " "))
	cmd := exec.Command(p.cmdName, p.cmdArgs...)
	var err error
	if p.cmdStdin, err = cmd.StdinPipe(); err != nil {
		return err
	}
	if p.cmdStdout, err = cmd.StdoutPipe(); err != nil {
		return err
	}
	if p.cmdStderr, err = cmd.StderrPipe(); err != nil {
		return err
	}
	go func() {
		scanner := bufio.NewScanner(p.cmdStderr)
		for scanner.Scan() {
			p.l.Infof("Stderr of %s: %s", cmd.Path, scanner.Text())
		}
	}()

	if err = cmd.Start(); err != nil {
		p.l.Errorf("error while starting the cmd: %s %s. Err: %v", cmd.Path, cmd.Args, err)
		return fmt.Errorf("error while starting the cmd: %s %s. Err: %v", cmd.Path, cmd.Args, err)
	}

	done := make(chan struct{})
	// This goroutine waits for the process to terminate and sets cmdRunning to false when that happens.
	go func() {
		err := cmd.Wait()
		close(done)
		p.cmdRunning = false
		if err != nil {
			if exitErr, ok := err.(*exec.ExitError); ok {
				p.l.Errorf("external probe process died with the status: %s. Stderr: %s", exitErr.Error(), string(exitErr.Stderr))
			}
		}
	}()
	go p.readProbeReplies(done)
	p.cmdRunning = true
	return nil
}

func (p *Probe) readProbeReplies(done chan struct{}) {
	// Start a background goroutine to read probe replies from the probe server
	// process's stdout and put them on the probe's replyChan. Note that replyChan
	// is a one element channel. Idea is that we won't need buffering other than the
	// one provided by Unix pipes.
	for {
		select {
		case <-done:
			return
		default:
			rep, err := serverutils.ReadProbeReply(bufio.NewReader(p.cmdStdout))
			if err != nil {
				p.l.Error(err)
			}
			p.replyChan <- rep
		}
	}

}

func (p *Probe) defaultMetrics(target string) *metrics.EventMetrics {
	return metrics.NewEventMetrics(time.Now()).
		AddMetric("success", metrics.NewInt(p.success)).
		AddMetric("total", metrics.NewInt(p.total)).
		AddMetric("latency", metrics.NewFloat(p.latency.Seconds()/p.opts.LatencyUnit.Seconds())).
		AddLabel("ptype", "external").
		AddLabel("probe", p.name).
		AddLabel("dst", target)
}

// replyForProbe looks for a reply on the replyChan, in a context bounded manner.
func (p *Probe) replyForProbe(ctx context.Context, requestID int32) (*serverutils.ProbeReply, error) {
	for {
		select {
		case <-ctx.Done():
			return nil, ctx.Err()
		case rep := <-p.replyChan:
			if rep.GetRequestId() != requestID {
				// Not our reply, could be from the last timedout probe. (it shouldn't happen if probe server
				// is using the standard package).
				p.l.Warningf("Got a reply that doesn't match with the request: Request id as per request: %d, as per reply:%d. Ignoring.", requestID, rep.GetRequestId())
				continue
			}
			return rep, nil
		}
	}
}

func (p *Probe) sendRequest(requestID int32, target string) error {
	labels := map[string]string{
		"probe":  p.name,
		"target": target,
	}
	addr, err := p.opts.Targets.Resolve(target, p.ipVer)
	if err != nil {
		p.l.Warningf("Targets.Resolve(%v, %v) failed: %v ", target, p.ipVer, err)
	} else if !addr.IsUnspecified() {
		labels["address"] = addr.String()
	}

	req := &serverutils.ProbeRequest{
		RequestId: proto.Int32(requestID),
		TimeLimit: proto.Int32(int32(p.opts.Timeout / time.Millisecond)),
		Options:   []*serverutils.ProbeRequest_Option{},
	}
	for _, opt := range p.c.GetOptions() {
		value, found := substituteLabels(opt.GetValue(), labels)
		if !found {
			p.l.Warningf("Missing substitution in option %q", value)
		}
		req.Options = append(req.Options, &serverutils.ProbeRequest_Option{
			Name:  opt.Name,
			Value: proto.String(value),
		})
	}
	return serverutils.WriteMessage(req, p.cmdStdin)
}

func (p *Probe) runServerProbe(ctx context.Context, dataChan chan *metrics.EventMetrics) {
	for _, target := range p.opts.Targets.List() {
		// Run probe for the target. Note that we always return probe
		// results regardless of the values of rep and err.
		rep, err := p.runServerProbeForTarget(ctx, target)
		if err != nil {
			p.l.Error(err)
		}
		em := p.defaultMetrics(target)
		p.l.Info(em.String())
		dataChan <- em

		// If we got a non-nil probe reply and probe is configured to use
		// the reply payload as metrics.
		if rep != nil && p.c.GetOutputAsMetrics() {
			em = p.payloadToMetrics(target, rep.GetPayload())
			p.l.Info(em.String())
			dataChan <- em
		}
	}
}

// runServerProbeForTarget runs a "server" probe for a single target and
// returns the ProbeReply and error if any.
func (p *Probe) runServerProbeForTarget(ctx context.Context, target string) (*serverutils.ProbeReply, error) {
	p.total++
	startTime := time.Now()
	if err := p.startCmdIfNotRunning(); err != nil {
		return nil, err
	}
	p.requestID++
	id := p.requestID

	ctxTimeout, cancelFunc := context.WithTimeout(ctx, p.opts.Timeout)
	defer cancelFunc()

	// TODO: We should use context for sending request as well.
	p.l.Debugf("Sending a probe request to the external probe server for target %v", target)
	if err := p.sendRequest(id, target); err != nil {
		return nil, fmt.Errorf("Error sending request to probe server: %v", err)
	}

	rep, err := p.replyForProbe(ctxTimeout, id)
	if err != nil {
		return nil, fmt.Errorf("Error reading reply from probe server: %v", err)
	}
	if rep.GetErrorMessage() != "" {
		return rep, fmt.Errorf("Probe failed with error message: %s", rep.GetErrorMessage())
	}
	p.success++
	p.latency += time.Since(startTime)
	return rep, nil
}

func (p *Probe) runOnceProbe(ctx context.Context, dataChan chan *metrics.EventMetrics) {
	labels := map[string]string{
		"probe": p.name,
	}
	for _, target := range p.opts.Targets.List() {
		labels["target"] = target
		addr, err := p.opts.Targets.Resolve(target, p.ipVer)
		if err != nil {
			p.l.Warningf("Targets.Resolve(%v, %v) failed: %v ", target, p.ipVer, err)
		} else if !addr.IsUnspecified() {
			labels["address"] = addr.String()
		}
		args := make([]string, len(p.cmdArgs))
		for i, arg := range p.cmdArgs {
			res, found := substituteLabels(arg, labels)
			if !found {
				p.l.Warningf("Substitution not found in %q", arg)
			}
			args[i] = res
		}

		p.l.Infof("Running external command: %s %s", p.cmdName, strings.Join(args, " "))
		p.total++
		startTime := time.Now()
		ctxTimeout, cancelFunc := context.WithTimeout(ctx, p.opts.Timeout)
		defer cancelFunc()
		b, err := exec.CommandContext(ctxTimeout, p.cmdName, args...).Output()
		if err != nil {
			if exitErr, ok := err.(*exec.ExitError); ok {
				p.l.Errorf("external probe process died with the status: %s. Stderr: %s", exitErr.Error(), exitErr.Stderr)
			} else {
				p.l.Errorf("Error executing the external program. Err: %v", err)
			}
		} else {
			p.success++
			p.latency += time.Since(startTime)
		}

		em := p.defaultMetrics(target)
		p.l.Info(em.String())
		dataChan <- em

		if p.c.GetOutputAsMetrics() {
			em = p.payloadToMetrics(target, string(b))
			p.l.Info(em.String())
			dataChan <- em
		}
	}
}

func (p *Probe) runProbe(ctx context.Context, dataChan chan *metrics.EventMetrics) {
	if p.mode == "server" {
		p.runServerProbe(ctx, dataChan)
		return
	}
	p.runOnceProbe(ctx, dataChan)
}

// Start starts and runs the probe indefinitely.
func (p *Probe) Start(ctx context.Context, dataChan chan *metrics.EventMetrics) {
	for _ = range time.Tick(p.opts.Interval) {
		// Don't run another probe if context is canceled already.
		select {
		case <-ctx.Done():
			return
		default:
		}

		p.runProbe(ctx, dataChan)
	}
}
<commit_msg>Improve external probe process death handling.<commit_after>// Copyright 2017 Google Inc.
//
// 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 external implements an external probe type for cloudprober.

External probe type executes an external process for actual probing. These probes
can have two modes: "once" and "server". In "once" mode, the external process is
started for each probe run cycle, while in "server" mode, external process is
started only if it's not running already and Cloudprober communicates with it
over stdin/stdout for each probe cycle.

TODO: Add a way to test this program. Write another program that
implements the probe server protocol and use that for testing.
*/
package external

import (
	"bufio"
	"context"
	"fmt"
	"io"
	"os/exec"
	"strings"
	"sync"
	"time"

	"github.com/golang/protobuf/proto"
	"github.com/google/cloudprober/logger"
	"github.com/google/cloudprober/metrics"
	"github.com/google/cloudprober/probes/external/serverutils"
	"github.com/google/cloudprober/probes/options"
)

// Probe holds aggregate information about all probe runs, per-target.
type Probe struct {
	name    string
	mode    string
	cmdName string
	cmdArgs []string
	opts    *options.Options
	c       *ProbeConf
	l       *logger.Logger
	ipVer   int

	// book-keeping params
	requestID  int32
	cmdRunning bool
	cmdStdin   io.Writer
	cmdStdout  io.ReadCloser
	cmdStderr  io.ReadCloser
	replyChan  chan *serverutils.ProbeReply
	success    int64         // toal probe successes
	total      int64         // total number of probes
	latency    time.Duration // cumulative probe latency

	// EventMetrics created from external probe process output
	defaultPayloadMetrics *metrics.EventMetrics
	payloadMetrics        map[string]*metrics.EventMetrics // Per-target metrics
	payloadMetricsMu      sync.Mutex
}

// Init initializes the probe with the given params.
func (p *Probe) Init(name string, opts *options.Options) error {
	c, ok := opts.ProbeConf.(*ProbeConf)
	if !ok {
		return fmt.Errorf("not external probe config")
	}
	p.name = name
	p.opts = opts
	if p.l = opts.Logger; p.l == nil {
		p.l = &logger.Logger{}
	}
	p.c = c
	p.replyChan = make(chan *serverutils.ProbeReply)
	p.ipVer = int(p.c.GetIpVersion())

	switch p.c.GetMode() {
	case ProbeConf_ONCE:
		p.mode = "once"
	case ProbeConf_SERVER:
		p.mode = "server"
	default:
		p.l.Errorf("Invalid mode: %s", p.c.GetMode())
	}

	cmdParts := strings.Split(p.c.GetCommand(), " ")
	p.cmdName = cmdParts[0]
	p.cmdArgs = cmdParts[1:len(cmdParts)]
	p.payloadMetrics = make(map[string]*metrics.EventMetrics)
	return p.initPayloadMetrics()
}

// substituteLabels replaces occurrences of @label@ with the values from
// abels.  It returns the substituted string and a bool indicating if there
// was a @label@ that did not exist in the labels map.
func substituteLabels(in string, labels map[string]string) (string, bool) {
	delimiter := "@"
	output := ""
	words := strings.Split(in, delimiter)
	count := len(words)
	foundAll := true
	for j, kwd := range words {
		// Even number of words => just copy out.
		if j%2 == 0 {
			output += kwd
			continue
		}
		// Special case: If there are an odd number of '@' (unbalanced), the last
		// odd index doesn't actually have a closing '@', so we just append it as it
		// is.
		if j == count-1 {
			output += delimiter
			output += kwd
			continue
		}

		// Special case: "@@" => "@"
		if kwd == "" {
			output += delimiter
			continue
		}

		// Finally, the labels.
		replace, ok := labels[kwd]
		if ok {
			output += replace
			continue
		}

		// Nothing - put the token back in.
		foundAll = false
		output += delimiter
		output += kwd
		output += delimiter
	}
	return output, foundAll
}

func (p *Probe) startCmdIfNotRunning() error {
	// Start external probe command if it's not running already. Note that here we
	// are trusting the cmdRunning to be set correctly. It can be false for 3 reasons:
	// 1) This is the first call and the process has actually never been started.
	// 2) cmd.Start() started the process but still returned an error.
	// 3) cmd.Wait() returned incorrectly, while the process was still running.
	//
	// 2 or 3 should never happen as per design, but managing processes can be tricky.
	// Documenting here to help with debugging if we run into an issue.
	if p.cmdRunning {
		return nil
	}
	p.l.Infof("Starting external command: %s %s", p.cmdName, strings.Join(p.cmdArgs, " "))
	cmd := exec.Command(p.cmdName, p.cmdArgs...)
	var err error
	if p.cmdStdin, err = cmd.StdinPipe(); err != nil {
		return err
	}
	if p.cmdStdout, err = cmd.StdoutPipe(); err != nil {
		return err
	}
	if p.cmdStderr, err = cmd.StderrPipe(); err != nil {
		return err
	}

	go func() {
		scanner := bufio.NewScanner(p.cmdStderr)
		for scanner.Scan() {
			p.l.Warningf("Stderr of %s: %s", cmd.Path, scanner.Text())
		}
	}()

	if err = cmd.Start(); err != nil {
		p.l.Errorf("error while starting the cmd: %s %s. Err: %v", cmd.Path, cmd.Args, err)
		return fmt.Errorf("error while starting the cmd: %s %s. Err: %v", cmd.Path, cmd.Args, err)
	}

	doneChan := make(chan struct{})
	// This goroutine waits for the process to terminate and sets cmdRunning to false when that happens.
	go func() {
		err := cmd.Wait()
		close(doneChan)
		p.cmdRunning = false
		if err != nil {
			if exitErr, ok := err.(*exec.ExitError); ok {
				p.l.Errorf("external probe process died with the status: %s. Stderr: %s", exitErr.Error(), string(exitErr.Stderr))
			}
		}
	}()
	go p.readProbeReplies(doneChan)
	p.cmdRunning = true
	return nil
}

func (p *Probe) readProbeReplies(done chan struct{}) {
	// Start a background goroutine to read probe replies from the probe server
	// process's stdout and put them on the probe's replyChan. Note that replyChan
	// is a one element channel. Idea is that we won't need buffering other than the
	// one provided by Unix pipes.
	for {
		select {
		case <-done:
			return
		default:
		}
		rep, err := serverutils.ReadProbeReply(bufio.NewReader(p.cmdStdout))
		if err != nil {
			p.l.Error(err)
			continue
		}
		p.replyChan <- rep
	}

}

func (p *Probe) defaultMetrics(target string) *metrics.EventMetrics {
	return metrics.NewEventMetrics(time.Now()).
		AddMetric("success", metrics.NewInt(p.success)).
		AddMetric("total", metrics.NewInt(p.total)).
		AddMetric("latency", metrics.NewFloat(p.latency.Seconds()/p.opts.LatencyUnit.Seconds())).
		AddLabel("ptype", "external").
		AddLabel("probe", p.name).
		AddLabel("dst", target)
}

// replyForProbe looks for a reply on the replyChan, in a context bounded manner.
func (p *Probe) replyForProbe(ctx context.Context, requestID int32) (*serverutils.ProbeReply, error) {
	for {
		select {
		case <-ctx.Done():
			return nil, ctx.Err()
		case rep := <-p.replyChan:
			if rep.GetRequestId() != requestID {
				// Not our reply, could be from the last timedout probe. (it shouldn't happen if probe server
				// is using the standard package).
				p.l.Warningf("Got a reply that doesn't match with the request: Request id as per request: %d, as per reply:%d. Ignoring.", requestID, rep.GetRequestId())
				continue
			}
			return rep, nil
		}
	}
}

func (p *Probe) sendRequest(requestID int32, target string) error {
	labels := map[string]string{
		"probe":  p.name,
		"target": target,
	}
	addr, err := p.opts.Targets.Resolve(target, p.ipVer)
	if err != nil {
		p.l.Warningf("Targets.Resolve(%v, %v) failed: %v ", target, p.ipVer, err)
	} else if !addr.IsUnspecified() {
		labels["address"] = addr.String()
	}

	req := &serverutils.ProbeRequest{
		RequestId: proto.Int32(requestID),
		TimeLimit: proto.Int32(int32(p.opts.Timeout / time.Millisecond)),
		Options:   []*serverutils.ProbeRequest_Option{},
	}
	for _, opt := range p.c.GetOptions() {
		value, found := substituteLabels(opt.GetValue(), labels)
		if !found {
			p.l.Warningf("Missing substitution in option %q", value)
		}
		req.Options = append(req.Options, &serverutils.ProbeRequest_Option{
			Name:  opt.Name,
			Value: proto.String(value),
		})
	}
	return serverutils.WriteMessage(req, p.cmdStdin)
}

func (p *Probe) runServerProbe(ctx context.Context, dataChan chan *metrics.EventMetrics) {
	for _, target := range p.opts.Targets.List() {
		// Run probe for the target. Note that we always return probe
		// results regardless of the values of rep and err.
		rep, err := p.runServerProbeForTarget(ctx, target)
		if err != nil {
			p.l.Error(err)
		}
		em := p.defaultMetrics(target)
		p.l.Info(em.String())
		dataChan <- em

		// If we got a non-nil probe reply and probe is configured to use
		// the reply payload as metrics.
		if rep != nil && p.c.GetOutputAsMetrics() {
			em = p.payloadToMetrics(target, rep.GetPayload())
			p.l.Info(em.String())
			dataChan <- em
		}
	}
}

// runServerProbeForTarget runs a "server" probe for a single target and
// returns the ProbeReply and error if any.
func (p *Probe) runServerProbeForTarget(ctx context.Context, target string) (*serverutils.ProbeReply, error) {
	p.total++
	startTime := time.Now()
	if err := p.startCmdIfNotRunning(); err != nil {
		return nil, err
	}
	p.requestID++
	id := p.requestID

	ctxTimeout, cancelFunc := context.WithTimeout(ctx, p.opts.Timeout)
	defer cancelFunc()

	// TODO: We should use context for sending request as well.
	p.l.Debugf("Sending a probe request to the external probe server for target %v", target)
	if err := p.sendRequest(id, target); err != nil {
		return nil, fmt.Errorf("Error sending request to probe server: %v", err)
	}

	rep, err := p.replyForProbe(ctxTimeout, id)
	if err != nil {
		return nil, fmt.Errorf("Error reading reply from probe server: %v", err)
	}
	if rep.GetErrorMessage() != "" {
		return rep, fmt.Errorf("Probe failed with error message: %s", rep.GetErrorMessage())
	}
	p.success++
	p.latency += time.Since(startTime)
	return rep, nil
}

func (p *Probe) runOnceProbe(ctx context.Context, dataChan chan *metrics.EventMetrics) {
	labels := map[string]string{
		"probe": p.name,
	}
	for _, target := range p.opts.Targets.List() {
		labels["target"] = target
		addr, err := p.opts.Targets.Resolve(target, p.ipVer)
		if err != nil {
			p.l.Warningf("Targets.Resolve(%v, %v) failed: %v ", target, p.ipVer, err)
		} else if !addr.IsUnspecified() {
			labels["address"] = addr.String()
		}
		args := make([]string, len(p.cmdArgs))
		for i, arg := range p.cmdArgs {
			res, found := substituteLabels(arg, labels)
			if !found {
				p.l.Warningf("Substitution not found in %q", arg)
			}
			args[i] = res
		}

		p.l.Infof("Running external command: %s %s", p.cmdName, strings.Join(args, " "))
		p.total++
		startTime := time.Now()
		ctxTimeout, cancelFunc := context.WithTimeout(ctx, p.opts.Timeout)
		defer cancelFunc()
		b, err := exec.CommandContext(ctxTimeout, p.cmdName, args...).Output()
		if err != nil {
			if exitErr, ok := err.(*exec.ExitError); ok {
				p.l.Errorf("external probe process died with the status: %s. Stderr: %s", exitErr.Error(), exitErr.Stderr)
			} else {
				p.l.Errorf("Error executing the external program. Err: %v", err)
			}
		} else {
			p.success++
			p.latency += time.Since(startTime)
		}

		em := p.defaultMetrics(target)
		p.l.Info(em.String())
		dataChan <- em

		if p.c.GetOutputAsMetrics() {
			em = p.payloadToMetrics(target, string(b))
			p.l.Info(em.String())
			dataChan <- em
		}
	}
}

func (p *Probe) runProbe(ctx context.Context, dataChan chan *metrics.EventMetrics) {
	if p.mode == "server" {
		p.runServerProbe(ctx, dataChan)
		return
	}
	p.runOnceProbe(ctx, dataChan)
}

// Start starts and runs the probe indefinitely.
func (p *Probe) Start(ctx context.Context, dataChan chan *metrics.EventMetrics) {
	for _ = range time.Tick(p.opts.Interval) {
		// Don't run another probe if context is canceled already.
		select {
		case <-ctx.Done():
			return
		default:
		}

		p.runProbe(ctx, dataChan)
	}
}
<|endoftext|>


<commit_before>package psn

import (
	"bytes"
	"fmt"
	"os"
	"path/filepath"
	"testing"

	humanize "github.com/dustin/go-humanize"
)

func TestGetProcDiskstats(t *testing.T) {
	dss, err := GetProcDiskstats()
	if err != nil {
		t.Error(err)
	}
	for _, ds := range dss {
		if ds.ReadsCompleted == 0 {
			continue
		}
		fmt.Printf("%s %d\n", ds.DeviceName, ds.ReadsCompleted)
	}
}

func getWritten(t *testing.T, targetDevice string) (uint64, uint64) {
	dss, err := GetProcDiskstats()
	if err != nil {
		t.Error(err)
	}
	var ds DiskStat
	for _, elem := range dss {
		if elem.DeviceName == targetDevice {
			ds = elem
			break
		}
	}
	if ds.DeviceName == "" {
		t.Skipf("disk stat is not found for device %q", targetDevice)
	}
	return ds.WritesCompleted, ds.SectorsWritten
}

const minSectorSize = 512

// TODO: use tmpfs
// sudo /usr/local/go/bin/go test -v -run TestGetProcDiskstatsSectorWrite
func TestGetProcDiskstatsSectorWrite(t *testing.T) {
	dn, err := GetDevice("/boot")
	if err != nil {
		fmt.Println(err)
		t.Skip()
	}
	fpath := filepath.Join("/boot", "test-temp-file")
	fmt.Println("writing to", fpath, "with device", dn)
	defer os.RemoveAll(fpath)

	oldWritesCompleted, oldSectorWritten := getWritten(t, dn)

	f, err := openToOverwrite(fpath)
	if err != nil {
		fmt.Println(err)
		t.Skip()
	}

	var sum uint64
	for i := 0; i < 1000; i++ {
		n, err := f.Write(bytes.Repeat([]byte{50}, 100*minSectorSize))
		if err != nil {
			t.Fatal(err)
		}
		sum += uint64(n)
	}
	fmt.Println("written", humanize.Bytes(sum))

	if err = f.Sync(); err != nil {
		t.Fatal(err)
	}
	if err = f.Close(); err != nil {
		t.Fatal(err)
	}

	newWritesCompleted, newSectorWritten := getWritten(t, dn)

	// sector delta >= write delta
	// because
	deltaWrites := newWritesCompleted - oldWritesCompleted
	deltaSector := newSectorWritten - oldSectorWritten
	if deltaSector < deltaWrites {
		t.Fatalf("expected sector delta %d >= writes delta %d", deltaSector, deltaWrites)
	}
	fmt.Printf("writes completed: %d\n", deltaWrites)
	fmt.Printf("sector written: %d\n", deltaSector)
}
<commit_msg>psn: comment<commit_after>package psn

import (
	"bytes"
	"fmt"
	"os"
	"path/filepath"
	"testing"

	humanize "github.com/dustin/go-humanize"
)

func TestGetProcDiskstats(t *testing.T) {
	dss, err := GetProcDiskstats()
	if err != nil {
		t.Error(err)
	}
	for _, ds := range dss {
		if ds.ReadsCompleted == 0 {
			continue
		}
		fmt.Printf("%s %d\n", ds.DeviceName, ds.ReadsCompleted)
	}
}

func getWritten(t *testing.T, targetDevice string) (uint64, uint64) {
	dss, err := GetProcDiskstats()
	if err != nil {
		t.Error(err)
	}
	var ds DiskStat
	for _, elem := range dss {
		if elem.DeviceName == targetDevice {
			ds = elem
			break
		}
	}
	if ds.DeviceName == "" {
		t.Skipf("disk stat is not found for device %q", targetDevice)
	}
	return ds.WritesCompleted, ds.SectorsWritten
}

const minSectorSize = 512

// TODO: use tmpfs
// sudo /usr/local/go/bin/go test -v -run TestGetProcDiskstatsSectorWrite
func TestGetProcDiskstatsSectorWrite(t *testing.T) {
	dn, err := GetDevice("/boot")
	if err != nil {
		fmt.Println(err)
		t.Skip()
	}
	fpath := filepath.Join("/boot", "test-temp-file")
	fmt.Println("writing to", fpath, "with device", dn)
	defer os.RemoveAll(fpath)

	oldWritesCompleted, oldSectorWritten := getWritten(t, dn)

	f, err := openToOverwrite(fpath)
	if err != nil {
		fmt.Println(err)
		t.Skip()
	}

	var sum uint64
	for i := 0; i < 1000; i++ {
		n, err := f.Write(bytes.Repeat([]byte{50}, 100*minSectorSize))
		if err != nil {
			t.Fatal(err)
		}
		sum += uint64(n)
	}
	fmt.Println("written", humanize.Bytes(sum))

	if err = f.Sync(); err != nil {
		t.Fatal(err)
	}
	if err = f.Close(); err != nil {
		t.Fatal(err)
	}

	newWritesCompleted, newSectorWritten := getWritten(t, dn)

	// sector delta >= write delta
	// because one write size can have 100*minSectorSize
	// e.g. if data 100-byte is written and the sector size is 10-byte
	// then writes completed increases 1,but sector written increases 10
	deltaWrites := newWritesCompleted - oldWritesCompleted
	deltaSector := newSectorWritten - oldSectorWritten
	if deltaSector < deltaWrites {
		t.Fatalf("expected sector delta %d >= writes delta %d", deltaSector, deltaWrites)
	}
	fmt.Printf("writes completed: %d\n", deltaWrites)
	fmt.Printf("sector written: %d\n", deltaSector)
}
<|endoftext|>


<commit_before>package torrent

import torrentapi "github.com/qopher/go-torrentapi"

func Search(name string) (string, error) {
	searchName := name + " 720p"
	api, err := torrentapi.Init()
	if err != nil {
		return "", err
	}
	api.Format("json_extended")
	api.SearchString(searchName)
	results, err := api.Search()
	if err != nil {
		return "", nil
	}
	if len(results) == 0 {
		return "", nil
	}
	return results[0].Download, nil
}
<commit_msg>Trim the torrent that have 0 seed and 0 leech<commit_after>package torrent

import torrentapi "github.com/qopher/go-torrentapi"

func goodEnoughTorrent(results torrentapi.TorrentResults) string {
	for _, t := range results {
		if t.Seeders > 0 || t.Leechers > 0 {
			return t.Download
		}
	}
	return ""
}

func Search(name string) (string, error) {
	searchName := name + " 720p"
	api, err := torrentapi.Init()
	if err != nil {
		return "", err
	}
	api.Format("json_extended")
	api.SearchString(searchName)
	results, err := api.Search()
	if err != nil {
		return "", nil
	}

	if len(results) == 0 {
		return "", nil
	}
	return goodEnoughTorrent(results), nil
}
<|endoftext|>


<commit_before>package appfile

import (
	"path/filepath"
	"reflect"
	"testing"
)

func TestFileActiveInfrastructure(t *testing.T) {
	cases := []struct {
		File   string
		Result string
	}{
		{
			"file-active-infra-basic.hcl",
			"aws",
		},
	}

	for _, tc := range cases {
		path := filepath.Join("./test-fixtures", tc.File)
		actual, err := ParseFile(path)
		if err != nil {
			t.Fatalf("file: %s\n\n%s", tc.File, err)
			continue
		}

		infra := actual.ActiveInfrastructure()
		if infra.Name != tc.Result {
			t.Fatalf("file: %s\n\n%s", tc.File, infra.Name)
		}
	}
}

func TestFileMerge(t *testing.T) {
	cases := map[string]struct {
		One, Two, Three *File
	}{
		"ID": {
			One: &File{
				ID: "foo",
			},
			Two: &File{
				ID: "bar",
			},
			Three: &File{
				ID: "bar",
			},
		},

		"Path": {
			One: &File{
				Path: "foo",
			},
			Two: &File{
				Path: "bar",
			},
			Three: &File{
				Path: "bar",
			},
		},

		"Application": {
			One: &File{
				Application: &Application{
					Name: "foo",
				},
			},
			Two: &File{
				Application: &Application{
					Type: "foo",
				},
			},
			Three: &File{
				Application: &Application{
					Name: "foo",
					Type: "foo",
				},
			},
		},

		"Application (no merge)": {
			One: &File{
				Application: &Application{
					Name: "foo",
				},
			},
			Two: &File{},
			Three: &File{
				Application: &Application{
					Name: "foo",
				},
			},
		},

		"Application (detect)": {
			One: &File{
				Application: &Application{
					Name:   "foo",
					Detect: true,
				},
			},
			Two: &File{
				Application: &Application{
					Type:   "foo",
					Detect: false,
				},
			},
			Three: &File{
				Application: &Application{
					Name:   "foo",
					Type:   "foo",
					Detect: false,
				},
			},
		},

		"Infra (no merge)": {
			One: &File{
				Infrastructure: []*Infrastructure{
					&Infrastructure{
						Name: "aws",
					},
				},
			},
			Two: &File{},
			Three: &File{
				Infrastructure: []*Infrastructure{
					&Infrastructure{
						Name: "aws",
					},
				},
			},
		},

		"Infra (add)": {
			One: &File{
				Infrastructure: []*Infrastructure{
					&Infrastructure{
						Name: "aws",
					},
				},
			},
			Two: &File{
				Infrastructure: []*Infrastructure{
					&Infrastructure{
						Name: "google",
					},
				},
			},
			Three: &File{
				Infrastructure: []*Infrastructure{
					&Infrastructure{
						Name: "aws",
					},
					&Infrastructure{
						Name: "google",
					},
				},
			},
		},

		"Infra (override)": {
			One: &File{
				Infrastructure: []*Infrastructure{
					&Infrastructure{
						Name: "aws",
					},
				},
			},
			Two: &File{
				Infrastructure: []*Infrastructure{
					&Infrastructure{
						Name: "aws",
					},
				},
			},
			Three: &File{
				Infrastructure: []*Infrastructure{
					&Infrastructure{
						Name: "aws",
					},
				},
			},
		},

		"Foundations (none)": {
			One: &File{
				Infrastructure: []*Infrastructure{
					&Infrastructure{
						Name: "aws",
						Foundations: []*Foundation{
							&Foundation{
								Name: "consul",
							},
						},
					},
				},
			},
			Two: &File{
				Infrastructure: []*Infrastructure{
					&Infrastructure{
						Name: "aws",
					},
				},
			},
			Three: &File{
				Infrastructure: []*Infrastructure{
					&Infrastructure{
						Name: "aws",
						Foundations: []*Foundation{
							&Foundation{
								Name: "consul",
							},
						},
					},
				},
			},
		},

		"Foundations (override)": {
			One: &File{
				Infrastructure: []*Infrastructure{
					&Infrastructure{
						Name: "aws",
						Foundations: []*Foundation{
							&Foundation{
								Name: "consul",
							},
						},
					},
				},
			},
			Two: &File{
				Infrastructure: []*Infrastructure{
					&Infrastructure{
						Name: "aws",
						Foundations: []*Foundation{
							&Foundation{
								Name: "tubes",
							},
						},
					},
				},
			},
			Three: &File{
				Infrastructure: []*Infrastructure{
					&Infrastructure{
						Name: "aws",
						Foundations: []*Foundation{
							&Foundation{
								Name: "tubes",
							},
						},
					},
				},
			},
		},
	}

	for name, tc := range cases {
		if err := tc.One.Merge(tc.Two); err != nil {
			t.Fatalf("%s: %s", name, err)
		}

		if !reflect.DeepEqual(tc.One, tc.Three) {
			t.Fatalf("%s:\n\n%#v\n\n%#v", name, tc.One, tc.Three)
		}
	}
}
<commit_msg>appfile: add test to ensure copy-ability<commit_after>package appfile

import (
	"path/filepath"
	"reflect"
	"testing"

	"github.com/mitchellh/copystructure"
)

func TestFileActiveInfrastructure(t *testing.T) {
	cases := []struct {
		File   string
		Result string
	}{
		{
			"file-active-infra-basic.hcl",
			"aws",
		},
	}

	for _, tc := range cases {
		path := filepath.Join("./test-fixtures", tc.File)
		actual, err := ParseFile(path)
		if err != nil {
			t.Fatalf("file: %s\n\n%s", tc.File, err)
			continue
		}

		infra := actual.ActiveInfrastructure()
		if infra.Name != tc.Result {
			t.Fatalf("file: %s\n\n%s", tc.File, infra.Name)
		}
	}
}

func TestFileMerge(t *testing.T) {
	cases := map[string]struct {
		One, Two, Three *File
	}{
		"ID": {
			One: &File{
				ID: "foo",
			},
			Two: &File{
				ID: "bar",
			},
			Three: &File{
				ID: "bar",
			},
		},

		"Path": {
			One: &File{
				Path: "foo",
			},
			Two: &File{
				Path: "bar",
			},
			Three: &File{
				Path: "bar",
			},
		},

		"Application": {
			One: &File{
				Application: &Application{
					Name: "foo",
				},
			},
			Two: &File{
				Application: &Application{
					Type: "foo",
				},
			},
			Three: &File{
				Application: &Application{
					Name: "foo",
					Type: "foo",
				},
			},
		},

		"Application (no merge)": {
			One: &File{
				Application: &Application{
					Name: "foo",
				},
			},
			Two: &File{},
			Three: &File{
				Application: &Application{
					Name: "foo",
				},
			},
		},

		"Application (detect)": {
			One: &File{
				Application: &Application{
					Name:   "foo",
					Detect: true,
				},
			},
			Two: &File{
				Application: &Application{
					Type:   "foo",
					Detect: false,
				},
			},
			Three: &File{
				Application: &Application{
					Name:   "foo",
					Type:   "foo",
					Detect: false,
				},
			},
		},

		"Infra (no merge)": {
			One: &File{
				Infrastructure: []*Infrastructure{
					&Infrastructure{
						Name: "aws",
					},
				},
			},
			Two: &File{},
			Three: &File{
				Infrastructure: []*Infrastructure{
					&Infrastructure{
						Name: "aws",
					},
				},
			},
		},

		"Infra (add)": {
			One: &File{
				Infrastructure: []*Infrastructure{
					&Infrastructure{
						Name: "aws",
					},
				},
			},
			Two: &File{
				Infrastructure: []*Infrastructure{
					&Infrastructure{
						Name: "google",
					},
				},
			},
			Three: &File{
				Infrastructure: []*Infrastructure{
					&Infrastructure{
						Name: "aws",
					},
					&Infrastructure{
						Name: "google",
					},
				},
			},
		},

		"Infra (override)": {
			One: &File{
				Infrastructure: []*Infrastructure{
					&Infrastructure{
						Name: "aws",
					},
				},
			},
			Two: &File{
				Infrastructure: []*Infrastructure{
					&Infrastructure{
						Name: "aws",
					},
				},
			},
			Three: &File{
				Infrastructure: []*Infrastructure{
					&Infrastructure{
						Name: "aws",
					},
				},
			},
		},

		"Foundations (none)": {
			One: &File{
				Infrastructure: []*Infrastructure{
					&Infrastructure{
						Name: "aws",
						Foundations: []*Foundation{
							&Foundation{
								Name: "consul",
							},
						},
					},
				},
			},
			Two: &File{
				Infrastructure: []*Infrastructure{
					&Infrastructure{
						Name: "aws",
					},
				},
			},
			Three: &File{
				Infrastructure: []*Infrastructure{
					&Infrastructure{
						Name: "aws",
						Foundations: []*Foundation{
							&Foundation{
								Name: "consul",
							},
						},
					},
				},
			},
		},

		"Foundations (override)": {
			One: &File{
				Infrastructure: []*Infrastructure{
					&Infrastructure{
						Name: "aws",
						Foundations: []*Foundation{
							&Foundation{
								Name: "consul",
							},
						},
					},
				},
			},
			Two: &File{
				Infrastructure: []*Infrastructure{
					&Infrastructure{
						Name: "aws",
						Foundations: []*Foundation{
							&Foundation{
								Name: "tubes",
							},
						},
					},
				},
			},
			Three: &File{
				Infrastructure: []*Infrastructure{
					&Infrastructure{
						Name: "aws",
						Foundations: []*Foundation{
							&Foundation{
								Name: "tubes",
							},
						},
					},
				},
			},
		},
	}

	for name, tc := range cases {
		if err := tc.One.Merge(tc.Two); err != nil {
			t.Fatalf("%s: %s", name, err)
		}

		if !reflect.DeepEqual(tc.One, tc.Three) {
			t.Fatalf("%s:\n\n%#v\n\n%#v", name, tc.One, tc.Three)
		}
	}
}

func TestFileDeepCopy(t *testing.T) {
	f, err := ParseFile(filepath.Join("./test-fixtures", "basic.hcl"))
	if err != nil {
		t.Fatalf("err: %s", err)
	}

	f2, err := copystructure.Copy(f)
	if err != nil {
		t.Fatalf("err: %s", err)
	}

	if !reflect.DeepEqual(f, f2) {
		t.Fatalf("bad:\n\n%#v\n\n%#v", f, f2)
	}
}
<|endoftext|>


<commit_before>// Copyright 2014 Igor Dolzhikov. All rights reserved.
// Use of this source code is governed by a license
// that can be found in the LICENSE file.

/*
Package ecbrates 0.1.7
This package helps parse the ECB exchange rates and use it for an applications

Example 1:

	package main

	import (
		"fmt"
		"log"

		"github.com/takama/ecbrates"
	)

	func main() {
		r, err := ecbrates.New()
		if err != nil {
			log.Fatal("Error: ", err)
		}

		// Case 1: get dollar rate relative to euro
		if value, ok := r.Rate[ecbrates.USD].(string); ok {
			fmt.Println("Exchange rate", r.Date, ": EUR 1 -> USD", value)
		}

		// Case 2: convert of 100 euros to dollars
		if value, err := r.Convert(100, ecbrates.EUR, ecbrates.USD); err == nil {
			fmt.Println("Exchange rate", r.Date, ": EUR 100.0 -> USD", value)
		}
	}

Example 2:

	package main

	import (
		"fmt"
		"log"

		"github.com/takama/ecbrates"
	)

	func main() {
		rates, err := ecbrates.Load()
		if err != nil {
			log.Fatal("Error: ", err)
		}

		// Show history of exchange rates for EUR -> USD
		for _, r := range rates {
			if value, ok := r.Rate[ecbrates.USD].(string); ok {
				fmt.Println("Exchange rate", r.Date, ": EUR 1 -> USD", value)
			}
		}
	}

The European Central Bank exchange rates
*/
package ecbrates

import (
	"encoding/xml"
	"errors"
	"math"
	"net/http"
	"strconv"
)

// List of all supported currencies
const (
	AUD Currency = "AUD" // Australian Dollar (A$)
	BGN Currency = "BGN" // Bulgarian Lev (BGN)
	BRL Currency = "BRL" // Brazilian Real (R$)
	CAD Currency = "CAD" // Canadian Dollar (CA$)
	CHF Currency = "CHF" // Swiss Franc (CHF)
	CNY Currency = "CNY" // Chinese Yuan (CN¥)
	CZK Currency = "CZK" // Czech Republic Koruna (CZK)
	DKK Currency = "DKK" // Danish Krone (DKK)
	EUR Currency = "EUR" // Euro (€)
	GBP Currency = "GBP" // British Pound Sterling (£)
	HKD Currency = "HKD" // Hong Kong Dollar (HK$)
	HRK Currency = "HRK" // Croatian Kuna (HRK)
	HUF Currency = "HUF" // Hungarian Forint (HUF)
	IDR Currency = "IDR" // Indonesian Rupiah (IDR)
	ILS Currency = "ILS" // Israeli New Sheqel (₪)
	INR Currency = "INR" // Indian Rupee (Rs.)
	JPY Currency = "JPY" // Japanese Yen (¥)
	KRW Currency = "KRW" // South Korean Won (₩)
	LTL Currency = "LTL" // Lithuanian Litas (LTL)
	MXN Currency = "MXN" // Mexican Peso (MX$)
	MYR Currency = "MYR" // Malaysian Ringgit (MYR)
	NOK Currency = "NOK" // Norwegian Krone (NOK)
	NZD Currency = "NZD" // New Zealand Dollar (NZ$)
	PHP Currency = "PHP" // Philippine Peso (Php)
	PLN Currency = "PLN" // Polish Zloty (PLN)
	RON Currency = "RON" // Romanian Leu (RON)
	RUB Currency = "RUB" // Russian Ruble (RUB)
	SEK Currency = "SEK" // Swedish Krona (SEK)
	SGD Currency = "SGD" // Singapore Dollar (SGD)
	THB Currency = "THB" // Thai Baht (฿)
	TRY Currency = "TRY" // Turkish Lira (TRY)
	USD Currency = "USD" // US Dollar ($)
	ZAR Currency = "ZAR" // South African Rand (ZAR)

	ratesLastURL   = "http://www.ecb.europa.eu/stats/eurofxref/eurofxref-daily.xml"
	rates90daysUrl = "http://www.ecb.europa.eu/stats/eurofxref/eurofxref-hist-90d.xml"
)

// Currency type as a link to string
type Currency string

// Rates represent date and currency exchange rates
type Rates struct {
	Date string
	Rate map[Currency]interface{}
}

var Currencies = []Currency{
	AUD, BGN, BRL, CAD, CHF, CNY, CZK, DKK, EUR, GBP, HKD,
	HRK, HUF, IDR, ILS, INR, JPY, KRW, LTL, MXN, MYR, NOK,
	NZD, PHP, PLN, RON, RUB, SEK, SGD, THB, TRY, USD, ZAR,
}

// New - create a new instance of the rates and fetch a data from ECB
func New() (*Rates, error) {
	r := new(Rates)
	err := r.fetchDay()
	return r, err
}

// Load - create a new instances of the rates and fetch a historical data from ECB
func Load() ([]Rates, error) {
	return fetch90days()
}

// Convert a value "from" one Currency -> "to" other Currency
func (r *Rates) Convert(value float64, from, to Currency) (float64, error) {
	if r.Rate[to] == nil || r.Rate[from] == nil {
		return 0, errors.New("Perhaps one of the values of currencies does not exist")
	}
	errorMessage := "Perhaps one of the values of currencies could not parsed correctly"
	strFrom, okFrom := r.Rate[from].(string)
	strTo, okTo := r.Rate[to].(string)
	if !okFrom || !okTo {
		return 0, errors.New(errorMessage)
	}
	vFrom, err := strconv.ParseFloat(strFrom, 32)
	if err != nil {
		return 0, errors.New(errorMessage)
	}
	vTo, err := strconv.ParseFloat(strTo, 32)
	if err != nil {
		return 0, errors.New(errorMessage)
	}
	return round64(value*round64(vTo, 4)/round64(vFrom, 4), 4), nil

}

// ECB XML envelope
type envelope struct {
	Data []struct {
		Date  string `xml:"time,attr"`
		Rates []struct {
			Currency string `xml:"currency,attr"`
			Rate     string `xml:"rate,attr"`
		} `xml:"Cube"`
	} `xml:"Cube>Cube"`
}

// Fetch an exchange rates
func (r *Rates) fetchDay() error {

	response, err := http.Get(ratesLastURL)
	if err != nil {
		return err
	}
	defer response.Body.Close()

	var raw envelope

	if err := xml.NewDecoder(response.Body).Decode(&raw); err != nil {
		return err
	}

	for _, day := range raw.Data {
		r.Rate = make(map[Currency]interface{})

		// an exchange rates fetched relatively the EUR currency
		r.Rate[EUR] = "1"

		r.Date = day.Date

		for _, item := range day.Rates {
			r.Rate[Currency(item.Currency)] = item.Rate
		}
		break
	}

	return nil
}

// Fetch a lot of exchange rates
func fetch90days() ([]Rates, error) {

	var rates []Rates

	response, err := http.Get(rates90daysUrl)
	if err != nil {
		return rates, err
	}
	defer response.Body.Close()

	var raw envelope

	if err := xml.NewDecoder(response.Body).Decode(&raw); err != nil {
		return rates, err
	}

	for _, day := range raw.Data {

		var r Rates
		r.Rate = make(map[Currency]interface{})

		// an exchange rates fetched relatively the EUR currency
		r.Rate[EUR] = "1"

		r.Date = day.Date
		for _, item := range day.Rates {
			r.Rate[Currency(item.Currency)] = item.Rate
		}
		rates = append(rates, r)
	}
	return rates, nil
}

func round64(x float64, prec int) float64 {
	if math.IsNaN(x) || math.IsInf(x, 0) {
		return x
	}

	sign := 1.0
	if x < 0 {
		sign = -1
		x *= -1
	}

	var rounder float64
	pow := math.Pow(10, float64(prec))
	intermed := x * pow
	_, frac := math.Modf(intermed)

	if frac >= 0.5 {
		rounder = math.Ceil(intermed)
	} else {
		rounder = math.Floor(intermed)
	}

	return rounder / pow * sign
}
<commit_msg>Bumped version number to 0.1.8<commit_after>// Copyright 2014 Igor Dolzhikov. All rights reserved.
// Use of this source code is governed by a license
// that can be found in the LICENSE file.

/*
Package ecbrates 0.1.8
This package helps parse the ECB exchange rates and use it for an applications

Example 1:

	package main

	import (
		"fmt"
		"log"

		"github.com/takama/ecbrates"
	)

	func main() {
		r, err := ecbrates.New()
		if err != nil {
			log.Fatal("Error: ", err)
		}

		// Case 1: get dollar rate relative to euro
		if value, ok := r.Rate[ecbrates.USD].(string); ok {
			fmt.Println("Exchange rate", r.Date, ": EUR 1 -> USD", value)
		}

		// Case 2: convert of 100 euros to dollars
		if value, err := r.Convert(100, ecbrates.EUR, ecbrates.USD); err == nil {
			fmt.Println("Exchange rate", r.Date, ": EUR 100.0 -> USD", value)
		}
	}

Example 2:

	package main

	import (
		"fmt"
		"log"

		"github.com/takama/ecbrates"
	)

	func main() {
		rates, err := ecbrates.Load()
		if err != nil {
			log.Fatal("Error: ", err)
		}

		// Show history of exchange rates for EUR -> USD
		for _, r := range rates {
			if value, ok := r.Rate[ecbrates.USD].(string); ok {
				fmt.Println("Exchange rate", r.Date, ": EUR 1 -> USD", value)
			}
		}
	}

The European Central Bank exchange rates
*/
package ecbrates

import (
	"encoding/xml"
	"errors"
	"math"
	"net/http"
	"strconv"
)

// List of all supported currencies
const (
	AUD Currency = "AUD" // Australian Dollar (A$)
	BGN Currency = "BGN" // Bulgarian Lev (BGN)
	BRL Currency = "BRL" // Brazilian Real (R$)
	CAD Currency = "CAD" // Canadian Dollar (CA$)
	CHF Currency = "CHF" // Swiss Franc (CHF)
	CNY Currency = "CNY" // Chinese Yuan (CN¥)
	CZK Currency = "CZK" // Czech Republic Koruna (CZK)
	DKK Currency = "DKK" // Danish Krone (DKK)
	EUR Currency = "EUR" // Euro (€)
	GBP Currency = "GBP" // British Pound Sterling (£)
	HKD Currency = "HKD" // Hong Kong Dollar (HK$)
	HRK Currency = "HRK" // Croatian Kuna (HRK)
	HUF Currency = "HUF" // Hungarian Forint (HUF)
	IDR Currency = "IDR" // Indonesian Rupiah (IDR)
	ILS Currency = "ILS" // Israeli New Sheqel (₪)
	INR Currency = "INR" // Indian Rupee (Rs.)
	JPY Currency = "JPY" // Japanese Yen (¥)
	KRW Currency = "KRW" // South Korean Won (₩)
	LTL Currency = "LTL" // Lithuanian Litas (LTL)
	MXN Currency = "MXN" // Mexican Peso (MX$)
	MYR Currency = "MYR" // Malaysian Ringgit (MYR)
	NOK Currency = "NOK" // Norwegian Krone (NOK)
	NZD Currency = "NZD" // New Zealand Dollar (NZ$)
	PHP Currency = "PHP" // Philippine Peso (Php)
	PLN Currency = "PLN" // Polish Zloty (PLN)
	RON Currency = "RON" // Romanian Leu (RON)
	RUB Currency = "RUB" // Russian Ruble (RUB)
	SEK Currency = "SEK" // Swedish Krona (SEK)
	SGD Currency = "SGD" // Singapore Dollar (SGD)
	THB Currency = "THB" // Thai Baht (฿)
	TRY Currency = "TRY" // Turkish Lira (TRY)
	USD Currency = "USD" // US Dollar ($)
	ZAR Currency = "ZAR" // South African Rand (ZAR)

	ratesLastURL   = "http://www.ecb.europa.eu/stats/eurofxref/eurofxref-daily.xml"
	rates90daysUrl = "http://www.ecb.europa.eu/stats/eurofxref/eurofxref-hist-90d.xml"
)

// Currency type as a link to string
type Currency string

// Rates represent date and currency exchange rates
type Rates struct {
	Date string
	Rate map[Currency]interface{}
}

var Currencies = []Currency{
	AUD, BGN, BRL, CAD, CHF, CNY, CZK, DKK, EUR, GBP, HKD,
	HRK, HUF, IDR, ILS, INR, JPY, KRW, LTL, MXN, MYR, NOK,
	NZD, PHP, PLN, RON, RUB, SEK, SGD, THB, TRY, USD, ZAR,
}

// New - create a new instance of the rates and fetch a data from ECB
func New() (*Rates, error) {
	r := new(Rates)
	err := r.fetchDay()
	return r, err
}

// Load - create a new instances of the rates and fetch a historical data from ECB
func Load() ([]Rates, error) {
	return fetch90days()
}

// Convert a value "from" one Currency -> "to" other Currency
func (r *Rates) Convert(value float64, from, to Currency) (float64, error) {
	if r.Rate[to] == nil || r.Rate[from] == nil {
		return 0, errors.New("Perhaps one of the values of currencies does not exist")
	}
	errorMessage := "Perhaps one of the values of currencies could not parsed correctly"
	strFrom, okFrom := r.Rate[from].(string)
	strTo, okTo := r.Rate[to].(string)
	if !okFrom || !okTo {
		return 0, errors.New(errorMessage)
	}
	vFrom, err := strconv.ParseFloat(strFrom, 32)
	if err != nil {
		return 0, errors.New(errorMessage)
	}
	vTo, err := strconv.ParseFloat(strTo, 32)
	if err != nil {
		return 0, errors.New(errorMessage)
	}
	return round64(value*round64(vTo, 4)/round64(vFrom, 4), 4), nil

}

// ECB XML envelope
type envelope struct {
	Data []struct {
		Date  string `xml:"time,attr"`
		Rates []struct {
			Currency string `xml:"currency,attr"`
			Rate     string `xml:"rate,attr"`
		} `xml:"Cube"`
	} `xml:"Cube>Cube"`
}

// Fetch an exchange rates
func (r *Rates) fetchDay() error {

	response, err := http.Get(ratesLastURL)
	if err != nil {
		return err
	}
	defer response.Body.Close()

	var raw envelope

	if err := xml.NewDecoder(response.Body).Decode(&raw); err != nil {
		return err
	}

	for _, day := range raw.Data {
		r.Rate = make(map[Currency]interface{})

		// an exchange rates fetched relatively the EUR currency
		r.Rate[EUR] = "1"

		r.Date = day.Date

		for _, item := range day.Rates {
			r.Rate[Currency(item.Currency)] = item.Rate
		}
		break
	}

	return nil
}

// Fetch a lot of exchange rates
func fetch90days() ([]Rates, error) {

	var rates []Rates

	response, err := http.Get(rates90daysUrl)
	if err != nil {
		return rates, err
	}
	defer response.Body.Close()

	var raw envelope

	if err := xml.NewDecoder(response.Body).Decode(&raw); err != nil {
		return rates, err
	}

	for _, day := range raw.Data {

		var r Rates
		r.Rate = make(map[Currency]interface{})

		// an exchange rates fetched relatively the EUR currency
		r.Rate[EUR] = "1"

		r.Date = day.Date
		for _, item := range day.Rates {
			r.Rate[Currency(item.Currency)] = item.Rate
		}
		rates = append(rates, r)
	}
	return rates, nil
}

func round64(x float64, prec int) float64 {
	if math.IsNaN(x) || math.IsInf(x, 0) {
		return x
	}

	sign := 1.0
	if x < 0 {
		sign = -1
		x *= -1
	}

	var rounder float64
	pow := math.Pow(10, float64(prec))
	intermed := x * pow
	_, frac := math.Modf(intermed)

	if frac >= 0.5 {
		rounder = math.Ceil(intermed)
	} else {
		rounder = math.Floor(intermed)
	}

	return rounder / pow * sign
}
<|endoftext|>


<commit_before>package autonat

import (
	"net"

	ma "github.com/multiformats/go-multiaddr"
)

var Private4, Private6 []*net.IPNet
var privateCIDR4 = []string{
	// localhost
	"127.0.0.0/8",
	// private networks
	"10.0.0.0/8",
	"100.64.0.0/10",
	"172.16.0.0/12",
	"192.168.0.0/16",
	// link local
	"169.254.0.0/16",
}
var privateCIDR6 = []string{
	// localhost
	"::1/128",
	// ULA reserved
	"fc00::/7",
	// link local
	"fe80::/10",
}

func init() {
	Private4 = parsePrivateCIDR(privateCIDR4)
	Private6 = parsePrivateCIDR(privateCIDR6)
}

func parsePrivateCIDR(cidrs []string) []*net.IPNet {
	ipnets := make([]*net.IPNet, len(cidrs))
	for i, cidr := range cidrs {
		_, ipnet, err := net.ParseCIDR(cidr)
		if err != nil {
			panic(err)
		}
		ipnets[i] = ipnet
	}
	return ipnets
}

// IsPublicAddr retruns true if the IP part of the multiaddr is not in a private network
func IsPublicAddr(a ma.Multiaddr) bool {
	ip, err := a.ValueForProtocol(ma.P_IP4)
	if err == nil {
		return !inAddrRange(ip, Private4)
	}

	ip, err = a.ValueForProtocol(ma.P_IP6)
	if err == nil {
		return !inAddrRange(ip, Private6)
	}

	return false
}

func inAddrRange(s string, ipnets []*net.IPNet) bool {
	ip := net.ParseIP(s)
	for _, ipnet := range ipnets {
		if ipnet.Contains(ip) {
			return true
		}
	}

	return false
}
<commit_msg>move addr.go to go-multiaddr-net<commit_after><|endoftext|>


<commit_before><commit_msg>switch to longer interval<commit_after><|endoftext|>


<commit_before>package untappd

import (
	"net/http"
	"net/url"
	"strconv"
)

type CheckinRequest struct {
	// Mandatory parameters
	BeerID    int
	GMTOffset int
	TimeZone  string

	// Optional parameters

	// Checkin location
	FoursquareID string
	Latitude     float64
	Longitude    float64

	// User comment and rating
	Comment string
	Rating  float64

	// Send to social media?
	Facebook bool
	Twitter  bool
	// FoursquareID is required if this is true
	Foursquare bool
}

func (a *AuthService) Checkin(r CheckinRequest) (*Checkin, *http.Response, error) {
	// Add required parameters
	q := url.Values{
		"bid":        []string{strconv.Itoa(r.BeerID)},
		"gmt_offset": []string{strconv.Itoa(r.GMTOffset)},
		"timezone":   []string{r.TimeZone},
	}

	// Add optional parameters, if not empty
	if r.FoursquareID != "" {
		q.Set("foursquare_id", r.FoursquareID)
	}
	if r.Latitude != 0 {
		q.Set("geolat", strconv.FormatFloat(r.Latitude, 'f', -1, 64))
	}
	if r.Longitude != 0 {
		q.Set("geolng", strconv.FormatFloat(r.Longitude, 'f', -1, 64))
	}

	if r.Comment != "" {
		q.Set("shout", r.Comment)
	}
	if r.Rating != 0 {
		q.Set("rating", strconv.FormatFloat(r.Rating, 'f', -1, 64))
	}

	if r.Facebook {
		q.Set("facebook", "on")
	}
	if r.Twitter {
		q.Set("twitter", "on")
	}
	if r.Foursquare {
		q.Set("foursquare", "on")
	}

	// Temporary struct to unmarshal checkin JSON
	var v struct {
		Response rawCheckin `json:"response"`
	}

	// Perform request to check in a beer
	res, err := a.client.request("POST", "checkin/add", q, nil, &v)
	if err != nil {
		return nil, res, err
	}

	return v.Response.export(), res, nil
}
<commit_msg>auth_checkin: add documentation<commit_after>package untappd

import (
	"net/http"
	"net/url"
	"strconv"
)

// CheckinRequest represents a request to check-in a beer to Untappd.
// To perform a successful checkin, the BeerID, GMTOffset, and TimeZone
// members must be filled in.  The easiest way to obtain the GMTOffset
// and TimeZone for your current system is to use the time package from
// the standard library:
//   beerID := 1
//   timezone, offset := time.Now().Zone()
//   offset = offset / 60 / 60
//
//   request := CheckinRequest{
//       BeerID:    beerID,
//       GMTOffset: offset,
//       TimeZone:  timezone,
//   }
type CheckinRequest struct {
	// Mandatory parameters
	BeerID    int
	GMTOffset int
	TimeZone  string

	// Optional parameters

	// Checkin location
	FoursquareID string
	Latitude     float64
	Longitude    float64

	// User comment and rating
	Comment string
	Rating  float64

	// Send to social media?
	Facebook bool
	Twitter  bool
	// FoursquareID is required if this is true
	Foursquare bool
}

// Checkin checks-in a beer specified by the input CheckinRequest struct.
// A variety of struct members can be filled in to specify the rating,
// comment, etc. for a checkin.
func (a *AuthService) Checkin(r CheckinRequest) (*Checkin, *http.Response, error) {
	// Add required parameters
	q := url.Values{
		"bid":        []string{strconv.Itoa(r.BeerID)},
		"gmt_offset": []string{strconv.Itoa(r.GMTOffset)},
		"timezone":   []string{r.TimeZone},
	}

	// Add optional parameters, if not empty
	if r.FoursquareID != "" {
		q.Set("foursquare_id", r.FoursquareID)
	}
	if r.Latitude != 0 {
		q.Set("geolat", strconv.FormatFloat(r.Latitude, 'f', -1, 64))
	}
	if r.Longitude != 0 {
		q.Set("geolng", strconv.FormatFloat(r.Longitude, 'f', -1, 64))
	}

	if r.Comment != "" {
		q.Set("shout", r.Comment)
	}
	if r.Rating != 0 {
		q.Set("rating", strconv.FormatFloat(r.Rating, 'f', -1, 64))
	}

	if r.Facebook {
		q.Set("facebook", "on")
	}
	if r.Twitter {
		q.Set("twitter", "on")
	}
	if r.Foursquare {
		q.Set("foursquare", "on")
	}

	// Temporary struct to unmarshal checkin JSON
	var v struct {
		Response rawCheckin `json:"response"`
	}

	// Perform request to check in a beer
	res, err := a.client.request("POST", "checkin/add", q, nil, &v)
	if err != nil {
		return nil, res, err
	}

	return v.Response.export(), res, nil
}
<|endoftext|>


<commit_before>// Copyright 2018 The Terraformer 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 github

import (
	"context"
	"log"

	"github.com/GoogleCloudPlatform/terraformer/terraform_utils"

	githubAPI "github.com/google/go-github/v25/github"
	"golang.org/x/oauth2"
)

type MembersGenerator struct {
	GithubService
}

// Generate TerraformResources from Github API,
func (g *MembersGenerator) InitResources() error {
	ctx := context.Background()
	ts := oauth2.StaticTokenSource(
		&oauth2.Token{AccessToken: g.Args["token"].(string)},
	)
	tc := oauth2.NewClient(ctx, ts)

	client := githubAPI.NewClient(tc)

	members, _, err := client.Organizations.ListMembers(ctx, g.Args["organization"].(string), nil)
	if err != nil {
		log.Println(err)
		return nil
	}
	for _, member := range members {
		g.Resources = append(g.Resources, terraform_utils.NewSimpleResource(
			g.Args["organization"].(string)+":"+member.GetLogin(),
			member.GetLogin(),
			"github_membership",
			"github",
			[]string{},
		))
	}
	return nil
}
<commit_msg>#260 Add support for pagination on Github members import<commit_after>// Copyright 2018 The Terraformer 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 github

import (
	"context"
	"log"

	"github.com/GoogleCloudPlatform/terraformer/terraform_utils"

	githubAPI "github.com/google/go-github/v25/github"
	"golang.org/x/oauth2"
)

// MembersGenerator holds GithubService struct of Terraform service information
type MembersGenerator struct {
	GithubService
}

// InitResources generates TerraformResources from Github API,
func (g *MembersGenerator) InitResources() error {
	ctx := context.Background()
	ts := oauth2.StaticTokenSource(
		&oauth2.Token{AccessToken: g.Args["token"].(string)},
	)
	tc := oauth2.NewClient(ctx, ts)

	client := githubAPI.NewClient(tc)

	opt := &githubAPI.ListMembersOptions{
		ListOptions: githubAPI.ListOptions{PerPage: 100},
	}

	// List all organization members for the authenticated user
	for {
		members, resp, err := client.Organizations.ListMembers(ctx, g.Args["organization"].(string), opt)
		if err != nil {
			log.Println(err)
			return nil
		}

		for _, member := range members {
			g.Resources = append(g.Resources, terraform_utils.NewSimpleResource(
				g.Args["organization"].(string)+":"+member.GetLogin(),
				member.GetLogin(),
				"github_membership",
				"github",
				[]string{},
			))
		}

		if resp.NextPage == 0 {
			break
		}
		opt.Page = resp.NextPage
	}
	return nil
}
<|endoftext|>


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

package provision

import (
	"errors"
	"launchpad.net/gocheck"
	"reflect"
	"testing"
)

type ProvisionSuite struct{}

var _ = gocheck.Suite(ProvisionSuite{})

func Test(t *testing.T) {
	gocheck.TestingT(t)
}

func (ProvisionSuite) TestRegisterAndGetProvisioner(c *gocheck.C) {
	var p Provisioner
	Register("my-provisioner", p)
	got, err := Get("my-provisioner")
	c.Assert(err, gocheck.IsNil)
	c.Check(got, gocheck.DeepEquals, p)
	_, err = Get("unknown-provisioner")
	c.Check(err, gocheck.NotNil)
	expectedMessage := `Unknown provisioner: "unknown-provisioner".`
	c.Assert(err.Error(), gocheck.Equals, expectedMessage)
}

func (ProvisionSuite) TestRegistry(c *gocheck.C) {
	var p1, p2 Provisioner
	Register("my-provisioner", p1)
	Register("your-provisioner", p2)
	provisioners := Registry()
	alt1 := []Provisioner{p1, p2}
	alt2 := []Provisioner{p2, p1}
	if !reflect.DeepEqual(provisioners, alt1) && !reflect.DeepEqual(provisioners, alt2) {
		c.Errorf("Registry(): Expected %#v. Got %#v.", alt1, provisioners)
	}
}

func (ProvisionSuite) TestError(c *gocheck.C) {
	errs := []*Error{
		{Reason: "something", Err: errors.New("went wrong")},
		{Reason: "something went wrong"},
	}
	expected := []string{"went wrong: something", "something went wrong"}
	for i := range errs {
		c.Check(errs[i].Error(), gocheck.Equals, expected[i])
	}
}

func (ProvisionSuite) TestErrorImplementsError(c *gocheck.C) {
	var _ error = &Error{}
}

func (ProvisionSuite) TestStatusString(c *gocheck.C) {
	var s Status = "pending"
	c.Assert(s.String(), gocheck.Equals, "pending")
}

func (ProvisionSuite) TestStatusUnreachable(c *gocheck.C) {
	c.Assert(StatusUnreachable.String(), gocheck.Equals, "unreachable")
}

func (ProvisionSuite) TestStatusBuilding(c *gocheck.C) {
	c.Assert(StatusBuilding.String(), gocheck.Equals, "building")
}

func (ProvisionSuite) TestUnitAvailable(c *gocheck.C) {
	var tests = []struct {
		input    Status
		expected bool
	}{
		{StatusStarted, true},
		{StatusUnreachable, true},
		{StatusBuilding, false},
		{StatusDown, false},
		{StatusError, false},
	}
	for _, test := range tests {
		u := Unit{Status: test.input}
		c.Check(u.Available(), gocheck.Equals, test.expected)
	}
}

func (ProvisionSuite) TestUnitGetIp(c *gocheck.C) {
	u := Unit{Ip: "10.3.3.1"}
	c.Assert(u.Ip, gocheck.Equals, u.GetIp())
}
<commit_msg>fixed license year.<commit_after>// Copyright 2014 tsuru authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package provision

import (
	"errors"
	"launchpad.net/gocheck"
	"reflect"
	"testing"
)

type ProvisionSuite struct{}

var _ = gocheck.Suite(ProvisionSuite{})

func Test(t *testing.T) {
	gocheck.TestingT(t)
}

func (ProvisionSuite) TestRegisterAndGetProvisioner(c *gocheck.C) {
	var p Provisioner
	Register("my-provisioner", p)
	got, err := Get("my-provisioner")
	c.Assert(err, gocheck.IsNil)
	c.Check(got, gocheck.DeepEquals, p)
	_, err = Get("unknown-provisioner")
	c.Check(err, gocheck.NotNil)
	expectedMessage := `Unknown provisioner: "unknown-provisioner".`
	c.Assert(err.Error(), gocheck.Equals, expectedMessage)
}

func (ProvisionSuite) TestRegistry(c *gocheck.C) {
	var p1, p2 Provisioner
	Register("my-provisioner", p1)
	Register("your-provisioner", p2)
	provisioners := Registry()
	alt1 := []Provisioner{p1, p2}
	alt2 := []Provisioner{p2, p1}
	if !reflect.DeepEqual(provisioners, alt1) && !reflect.DeepEqual(provisioners, alt2) {
		c.Errorf("Registry(): Expected %#v. Got %#v.", alt1, provisioners)
	}
}

func (ProvisionSuite) TestError(c *gocheck.C) {
	errs := []*Error{
		{Reason: "something", Err: errors.New("went wrong")},
		{Reason: "something went wrong"},
	}
	expected := []string{"went wrong: something", "something went wrong"}
	for i := range errs {
		c.Check(errs[i].Error(), gocheck.Equals, expected[i])
	}
}

func (ProvisionSuite) TestErrorImplementsError(c *gocheck.C) {
	var _ error = &Error{}
}

func (ProvisionSuite) TestStatusString(c *gocheck.C) {
	var s Status = "pending"
	c.Assert(s.String(), gocheck.Equals, "pending")
}

func (ProvisionSuite) TestStatusUnreachable(c *gocheck.C) {
	c.Assert(StatusUnreachable.String(), gocheck.Equals, "unreachable")
}

func (ProvisionSuite) TestStatusBuilding(c *gocheck.C) {
	c.Assert(StatusBuilding.String(), gocheck.Equals, "building")
}

func (ProvisionSuite) TestUnitAvailable(c *gocheck.C) {
	var tests = []struct {
		input    Status
		expected bool
	}{
		{StatusStarted, true},
		{StatusUnreachable, true},
		{StatusBuilding, false},
		{StatusDown, false},
		{StatusError, false},
	}
	for _, test := range tests {
		u := Unit{Status: test.input}
		c.Check(u.Available(), gocheck.Equals, test.expected)
	}
}

func (ProvisionSuite) TestUnitGetIp(c *gocheck.C) {
	u := Unit{Ip: "10.3.3.1"}
	c.Assert(u.Ip, gocheck.Equals, u.GetIp())
}
<|endoftext|>


<commit_before>package audio

import (
	"errors"
	"fmt"
	"strings"
	"time"

	"github.com/gordonklaus/portaudio"
)

const (
	INPUT  = 1
	OUTPUT = 2
)

const (
	MONO   = 1
	STEREO = 2
)

type AudioStream struct {
	DeviceName      string
	Direction       int
	Channels        int
	Samplingrate    float64
	Latency         time.Duration
	FramesPerBuffer int
	Device          *portaudio.DeviceInfo
	Stream          *portaudio.Stream
	out             AudioData
	in              AudioData
}

type AudioSamples struct {
	Channels     int32
	Samplingrate int32
	Frames       int32
	Data         []int32
}

// Audiodata is an Interface to handle either incoming or outgoing audio data
type AudioData interface {
	// Process(StereoSamples)
	Process([]int32)
}

// IdentifyDevice checks if the Audio Devices actually exist
func (as *AudioDevice) IdentifyDevice() error {
	devices, _ := portaudio.Devices()
	for _, device := range devices {
		if device.Name == as.DeviceName {
			as.Device = device
			return nil
		}
	}
	return errors.New("unknown audio device")
}

func GetChannel(ch string) int {
	fmt.Println("ch")
	if strings.ToUpper(ch) == "MONO" {
		fmt.Println("ch")
		return MONO
	} else if strings.ToUpper(ch) == "STEREO" {
		fmt.Println("ch")
		return STEREO
	}

	return 0
}
<commit_msg>removed prints<commit_after>package audio

import (
	"errors"
	"strings"
	"time"

	"github.com/gordonklaus/portaudio"
)

const (
	INPUT  = 1
	OUTPUT = 2
)

const (
	MONO   = 1
	STEREO = 2
)

type AudioStream struct {
	DeviceName      string
	Direction       int
	Channels        int
	Samplingrate    float64
	Latency         time.Duration
	FramesPerBuffer int
	Device          *portaudio.DeviceInfo
	Stream          *portaudio.Stream
	out             AudioData
	in              AudioData
}

type AudioSamples struct {
	Channels     int32
	Samplingrate int32
	Frames       int32
	Data         []int32
}

// Audiodata is an Interface to handle either incoming or outgoing audio data
type AudioData interface {
	// Process(StereoSamples)
	Process([]int32)
}

// IdentifyDevice checks if the Audio Devices actually exist
func (as *AudioDevice) IdentifyDevice() error {
	devices, _ := portaudio.Devices()
	for _, device := range devices {
		if device.Name == as.DeviceName {
			as.Device = device
			return nil
		}
	}
	return errors.New("unknown audio device")
}

func GetChannel(ch string) int {
	if strings.ToUpper(ch) == "MONO" {
		return MONO
	} else if strings.ToUpper(ch) == "STEREO" {
		return STEREO
	}
	return 0
}
<|endoftext|>