<commit_before>/*
 *
 * k6 - a next-generation load testing tool
 * Copyright (C) 2016 Load Impact
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

package httpext

import (
	"context"
	"fmt"
	"io"
	"io/ioutil"
	"math/rand"
	"net"
	"net/http"
	"net/http/httptest"
	"net/http/httptrace"
	"runtime"
	"strings"
	"testing"
	"time"

	"github.com/mccutchen/go-httpbin/httpbin"
	"github.com/pkg/errors"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"

	"github.com/loadimpact/k6/lib/metrics"
	"github.com/loadimpact/k6/lib/netext"
	"github.com/loadimpact/k6/stats"
)

func TestTracer(t *testing.T) {
	if runtime.GOOS == "windows" {
		t.Skip()
	}
	t.Parallel()
	srv := httptest.NewTLSServer(httpbin.New().Handler())
	defer srv.Close()

	transport, ok := srv.Client().Transport.(*http.Transport)
	assert.True(t, ok)
	transport.DialContext = netext.NewDialer(net.Dialer{}).DialContext

	var prev int64
	assertLaterOrZero := func(t *testing.T, val int64, canBeZero bool) {
		if canBeZero && val == 0 {
			return
		}
		if prev > val {
			_, file, line, _ := runtime.Caller(1)
			t.Errorf("Expected %d to be greater or equal to %d (from %s:%d)", val, prev, file, line)
			return
		}
		prev = val
	}

	for tnum, isReuse := range []bool{false, true, true} {
		t.Run(fmt.Sprintf("Test #%d", tnum), func(t *testing.T) {
			// Do not enable parallel testing, test relies on sequential execution
			tracer := &Tracer{}
			req, err := http.NewRequest("GET", srv.URL+"/get", nil)
			require.NoError(t, err)

			res, err := transport.RoundTrip(req.WithContext(httptrace.WithClientTrace(context.Background(), tracer.Trace())))
			require.NoError(t, err)

			_, err = io.Copy(ioutil.Discard, res.Body)
			assert.NoError(t, err)
			assert.NoError(t, res.Body.Close())
			trail := tracer.Done()
			trail.SaveSamples(stats.IntoSampleTags(&map[string]string{"tag": "value"}))
			samples := trail.GetSamples()

			assertLaterOrZero(t, tracer.getConn, isReuse)
			assertLaterOrZero(t, tracer.connectStart, isReuse)
			assertLaterOrZero(t, tracer.connectDone, isReuse)
			assertLaterOrZero(t, tracer.tlsHandshakeStart, isReuse)
			assertLaterOrZero(t, tracer.tlsHandshakeDone, isReuse)
			assertLaterOrZero(t, tracer.gotConn, false)
			assertLaterOrZero(t, tracer.wroteRequest, false)
			assertLaterOrZero(t, tracer.gotFirstResponseByte, false)
			assertLaterOrZero(t, now(), false)

			assert.Equal(t, strings.TrimPrefix(srv.URL, "https://"), trail.ConnRemoteAddr.String())

			assert.Len(t, samples, 8)
			seenMetrics := map[*stats.Metric]bool{}
			for i, s := range samples {
				assert.NotContains(t, seenMetrics, s.Metric)
				seenMetrics[s.Metric] = true

				assert.False(t, s.Time.IsZero())
				assert.Equal(t, map[string]string{"tag": "value"}, s.Tags.CloneTags())

				switch s.Metric {
				case metrics.HTTPReqs:
					assert.Equal(t, 1.0, s.Value)
					assert.Equal(t, 0, i, "`HTTPReqs` is reported before the other HTTP metrics")
				case metrics.HTTPReqConnecting, metrics.HTTPReqTLSHandshaking:
					if isReuse {
						assert.Equal(t, 0.0, s.Value)
						break
					}
					fallthrough
				case metrics.HTTPReqDuration, metrics.HTTPReqBlocked, metrics.HTTPReqSending, metrics.HTTPReqWaiting, metrics.HTTPReqReceiving:
					assert.True(t, s.Value > 0.0, "%s is <= 0", s.Metric.Name)
				default:
					t.Errorf("unexpected metric: %s", s.Metric.Name)
				}
			}
		})
	}
}

type failingConn struct {
	net.Conn
}

var failOnConnWrite = false

func (c failingConn) Write(b []byte) (int, error) {
	if failOnConnWrite {
		failOnConnWrite = false
		return 0, errors.New("write error")
	}

	return c.Conn.Write(b)
}

func TestTracerNegativeHttpSendingValues(t *testing.T) {
	if runtime.GOOS == "windows" {
		t.Skip()
	}
	t.Parallel()
	srv := httptest.NewTLSServer(httpbin.New().Handler())
	defer srv.Close()

	transport, ok := srv.Client().Transport.(*http.Transport)
	assert.True(t, ok)

	dialer := &net.Dialer{}
	transport.DialContext = func(ctx context.Context, proto, addr string) (net.Conn, error) {
		conn, err := dialer.DialContext(ctx, proto, addr)
		return failingConn{conn}, err
	}

	req, err := http.NewRequest("GET", srv.URL+"/get", nil)
	require.NoError(t, err)

	{
		tracer := &Tracer{}
		res, err := transport.RoundTrip(req.WithContext(httptrace.WithClientTrace(context.Background(), tracer.Trace())))
		require.NoError(t, err)
		_, err = io.Copy(ioutil.Discard, res.Body)
		assert.NoError(t, err)
		assert.NoError(t, res.Body.Close())
		tracer.Done()
	}

	// make the next connection write fail
	failOnConnWrite = true

	{
		tracer := &Tracer{}
		res, err := transport.RoundTrip(req.WithContext(httptrace.WithClientTrace(context.Background(), tracer.Trace())))
		require.NoError(t, err)
		_, err = io.Copy(ioutil.Discard, res.Body)
		assert.NoError(t, err)
		assert.NoError(t, res.Body.Close())
		trail := tracer.Done()
		trail.SaveSamples(nil)

		require.True(t, trail.Sending > 0)
	}
}

func TestTracerError(t *testing.T) {
	t.Parallel()
	srv := httptest.NewTLSServer(httpbin.New().Handler())
	defer srv.Close()

	tracer := &Tracer{}
	req, err := http.NewRequest("GET", srv.URL+"/get", nil)
	require.NoError(t, err)

	_, err = http.DefaultTransport.RoundTrip(
		req.WithContext(
			httptrace.WithClientTrace(
				context.Background(),
				tracer.Trace())))

	assert.Error(t, err)
}

func TestCancelledRequest(t *testing.T) {
	t.Parallel()
	srv := httptest.NewTLSServer(httpbin.New().Handler())
	defer srv.Close()

	cancelTest := func(t *testing.T) {
		t.Parallel()
		tracer := &Tracer{}
		req, err := http.NewRequest("GET", srv.URL+"/delay/1", nil)
		require.NoError(t, err)

		ctx, cancel := context.WithCancel(httptrace.WithClientTrace(req.Context(), tracer.Trace()))
		req = req.WithContext(ctx)
		go func() {
			time.Sleep(time.Duration(rand.Int31n(50)) * time.Millisecond)
			cancel()
		}()

		resp, err := srv.Client().Transport.RoundTrip(req)
		_ = tracer.Done()
		assert.Nil(t, resp)
		assert.Error(t, err)
	}

	// This Run will not return until the parallel subtests complete.
	t.Run("group", func(t *testing.T) {
		for i := 0; i < 200; i++ {
			t.Run(fmt.Sprintf("TestCancelledRequest_%d", i), cancelTest)
		}
	})
}
<commit_msg>Revert the check in TestCancelledRequest<commit_after>/*
 *
 * k6 - a next-generation load testing tool
 * Copyright (C) 2016 Load Impact
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

package httpext

import (
	"context"
	"fmt"
	"io"
	"io/ioutil"
	"math/rand"
	"net"
	"net/http"
	"net/http/httptest"
	"net/http/httptrace"
	"runtime"
	"strings"
	"testing"
	"time"

	"github.com/mccutchen/go-httpbin/httpbin"
	"github.com/pkg/errors"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"

	"github.com/loadimpact/k6/lib/metrics"
	"github.com/loadimpact/k6/lib/netext"
	"github.com/loadimpact/k6/stats"
)

func TestTracer(t *testing.T) {
	if runtime.GOOS == "windows" {
		t.Skip()
	}
	t.Parallel()
	srv := httptest.NewTLSServer(httpbin.New().Handler())
	defer srv.Close()

	transport, ok := srv.Client().Transport.(*http.Transport)
	assert.True(t, ok)
	transport.DialContext = netext.NewDialer(net.Dialer{}).DialContext

	var prev int64
	assertLaterOrZero := func(t *testing.T, val int64, canBeZero bool) {
		if canBeZero && val == 0 {
			return
		}
		if prev > val {
			_, file, line, _ := runtime.Caller(1)
			t.Errorf("Expected %d to be greater or equal to %d (from %s:%d)", val, prev, file, line)
			return
		}
		prev = val
	}

	for tnum, isReuse := range []bool{false, true, true} {
		t.Run(fmt.Sprintf("Test #%d", tnum), func(t *testing.T) {
			// Do not enable parallel testing, test relies on sequential execution
			tracer := &Tracer{}
			req, err := http.NewRequest("GET", srv.URL+"/get", nil)
			require.NoError(t, err)

			res, err := transport.RoundTrip(req.WithContext(httptrace.WithClientTrace(context.Background(), tracer.Trace())))
			require.NoError(t, err)

			_, err = io.Copy(ioutil.Discard, res.Body)
			assert.NoError(t, err)
			assert.NoError(t, res.Body.Close())
			trail := tracer.Done()
			trail.SaveSamples(stats.IntoSampleTags(&map[string]string{"tag": "value"}))
			samples := trail.GetSamples()

			assertLaterOrZero(t, tracer.getConn, isReuse)
			assertLaterOrZero(t, tracer.connectStart, isReuse)
			assertLaterOrZero(t, tracer.connectDone, isReuse)
			assertLaterOrZero(t, tracer.tlsHandshakeStart, isReuse)
			assertLaterOrZero(t, tracer.tlsHandshakeDone, isReuse)
			assertLaterOrZero(t, tracer.gotConn, false)
			assertLaterOrZero(t, tracer.wroteRequest, false)
			assertLaterOrZero(t, tracer.gotFirstResponseByte, false)
			assertLaterOrZero(t, now(), false)

			assert.Equal(t, strings.TrimPrefix(srv.URL, "https://"), trail.ConnRemoteAddr.String())

			assert.Len(t, samples, 8)
			seenMetrics := map[*stats.Metric]bool{}
			for i, s := range samples {
				assert.NotContains(t, seenMetrics, s.Metric)
				seenMetrics[s.Metric] = true

				assert.False(t, s.Time.IsZero())
				assert.Equal(t, map[string]string{"tag": "value"}, s.Tags.CloneTags())

				switch s.Metric {
				case metrics.HTTPReqs:
					assert.Equal(t, 1.0, s.Value)
					assert.Equal(t, 0, i, "`HTTPReqs` is reported before the other HTTP metrics")
				case metrics.HTTPReqConnecting, metrics.HTTPReqTLSHandshaking:
					if isReuse {
						assert.Equal(t, 0.0, s.Value)
						break
					}
					fallthrough
				case metrics.HTTPReqDuration, metrics.HTTPReqBlocked, metrics.HTTPReqSending, metrics.HTTPReqWaiting, metrics.HTTPReqReceiving:
					assert.True(t, s.Value > 0.0, "%s is <= 0", s.Metric.Name)
				default:
					t.Errorf("unexpected metric: %s", s.Metric.Name)
				}
			}
		})
	}
}

type failingConn struct {
	net.Conn
}

var failOnConnWrite = false

func (c failingConn) Write(b []byte) (int, error) {
	if failOnConnWrite {
		failOnConnWrite = false
		return 0, errors.New("write error")
	}

	return c.Conn.Write(b)
}

func TestTracerNegativeHttpSendingValues(t *testing.T) {
	if runtime.GOOS == "windows" {
		t.Skip()
	}
	t.Parallel()
	srv := httptest.NewTLSServer(httpbin.New().Handler())
	defer srv.Close()

	transport, ok := srv.Client().Transport.(*http.Transport)
	assert.True(t, ok)

	dialer := &net.Dialer{}
	transport.DialContext = func(ctx context.Context, proto, addr string) (net.Conn, error) {
		conn, err := dialer.DialContext(ctx, proto, addr)
		return failingConn{conn}, err
	}

	req, err := http.NewRequest("GET", srv.URL+"/get", nil)
	require.NoError(t, err)

	{
		tracer := &Tracer{}
		res, err := transport.RoundTrip(req.WithContext(httptrace.WithClientTrace(context.Background(), tracer.Trace())))
		require.NoError(t, err)
		_, err = io.Copy(ioutil.Discard, res.Body)
		assert.NoError(t, err)
		assert.NoError(t, res.Body.Close())
		tracer.Done()
	}

	// make the next connection write fail
	failOnConnWrite = true

	{
		tracer := &Tracer{}
		res, err := transport.RoundTrip(req.WithContext(httptrace.WithClientTrace(context.Background(), tracer.Trace())))
		require.NoError(t, err)
		_, err = io.Copy(ioutil.Discard, res.Body)
		assert.NoError(t, err)
		assert.NoError(t, res.Body.Close())
		trail := tracer.Done()
		trail.SaveSamples(nil)

		require.True(t, trail.Sending > 0)
	}
}

func TestTracerError(t *testing.T) {
	t.Parallel()
	srv := httptest.NewTLSServer(httpbin.New().Handler())
	defer srv.Close()

	tracer := &Tracer{}
	req, err := http.NewRequest("GET", srv.URL+"/get", nil)
	require.NoError(t, err)

	_, err = http.DefaultTransport.RoundTrip(
		req.WithContext(
			httptrace.WithClientTrace(
				context.Background(),
				tracer.Trace())))

	assert.Error(t, err)
}

func TestCancelledRequest(t *testing.T) {
	t.Parallel()
	srv := httptest.NewTLSServer(httpbin.New().Handler())
	defer srv.Close()

	cancelTest := func(t *testing.T) {
		t.Parallel()
		tracer := &Tracer{}
		req, err := http.NewRequestWithContext(context.Background(), "GET", srv.URL+"/delay/1", nil)
		require.NoError(t, err)

		ctx, cancel := context.WithCancel(httptrace.WithClientTrace(req.Context(), tracer.Trace()))
		req = req.WithContext(ctx)
		go func() {
			time.Sleep(time.Duration(rand.Int31n(50)) * time.Millisecond) //nolint:gosec
			cancel()
		}()

		resp, err := srv.Client().Transport.RoundTrip(req) //nolint:bodyclose
		_ = tracer.Done()
		if resp == nil && err == nil {
			t.Errorf("Expected either a RoundTrip response or error but got %#v and %#v", resp, err)
		}
	}

	// This Run will not return until the parallel subtests complete.
	t.Run("group", func(t *testing.T) {
		for i := 0; i < 200; i++ {
			t.Run(fmt.Sprintf("TestCancelledRequest_%d", i), cancelTest)
		}
	})
}
<|endoftext|>


<commit_before>package edit

import (
	"bufio"
	"errors"
	"os"
	"sync"

	"github.com/elves/elvish/eval"
)

var defaultBindings = map[ModeType]map[Key]string{
	modeInsert: map[Key]string{
		// Moving.
		Key{Left, 0}:     "move-dot-left",
		Key{Right, 0}:    "move-dot-right",
		Key{Up, Alt}:     "move-dot-up",
		Key{Down, Alt}:   "move-dot-down",
		Key{Left, Ctrl}:  "move-dot-left-word",
		Key{Right, Ctrl}: "move-dot-right-word",
		Key{Home, 0}:     "move-dot-sol",
		Key{End, 0}:      "move-dot-eol",
		// Killing.
		Key{'U', Ctrl}:    "kill-line-left",
		Key{'K', Ctrl}:    "kill-line-right",
		Key{'W', Ctrl}:    "kill-word-left",
		Key{Backspace, 0}: "kill-rune-left",
		// Some terminal send ^H on backspace
		// Key{'H', Ctrl}: "kill-rune-left",
		Key{Delete, 0}: "kill-rune-right",
		// Inserting.
		Key{'.', Alt}:   "insert-last-word",
		Key{Enter, Alt}: "insert-key",
		// Controls.
		Key{Enter, 0}:  "smart-enter",
		Key{'D', Ctrl}: "return-eof",
		// Key{'[', Ctrl}: "startCommand",
		Key{Tab, 0}:    "complete-prefix-or-start-completion",
		Key{Up, 0}:     "start-history",
		Key{'N', Ctrl}: "start-navigation",
		Key{'H', Ctrl}: "start-history-listing",
		Key{'L', Ctrl}: "start-location",
		Default:        "default-insert",
	},
	modeCommand: map[Key]string{
		// Moving.
		Key{'h', 0}: "move-dot-left",
		Key{'l', 0}: "move-dot-right",
		Key{'k', 0}: "move-dot-up",
		Key{'j', 0}: "move-dot-down",
		Key{'b', 0}: "move-dot-left-word",
		Key{'w', 0}: "move-dot-right-word",
		Key{'0', 0}: "move-dot-sol",
		Key{'$', 0}: "move-dot-eol",
		// Killing.
		Key{'x', 0}: "kill-rune-right",
		Key{'D', 0}: "kill-line-right",
		// Controls.
		Key{'i', 0}: "start-insert",
		Default:     "default-command",
	},
	modeCompletion: map[Key]string{
		Key{Up, 0}:     "select-cand-up",
		Key{Down, 0}:   "select-cand-down",
		Key{Left, 0}:   "select-cand-left",
		Key{Right, 0}:  "select-cand-right",
		Key{Tab, 0}:    "cycle-cand-right",
		Key{Enter, 0}:  "accept-completion",
		Key{'[', Ctrl}: "start-insert",
		Default:        "default-completion",
	},
	modeNavigation: map[Key]string{
		Key{Up, 0}:     "select-nav-up",
		Key{Down, 0}:   "select-nav-down",
		Key{Left, 0}:   "ascend-nav",
		Key{Right, 0}:  "descend-nav",
		Key{Tab, 0}:    "nav-insert-selected",
		Key{'H', Ctrl}: "trigger-nav-show-hidden",
		Key{'[', Ctrl}: "start-insert",
		Default:        "default-navigation",
	},
	modeHistory: map[Key]string{
		Key{Up, 0}:     "select-history-prev",
		Key{Down, 0}:   "select-history-next-or-quit",
		Key{'[', Ctrl}: "start-insert",
		Default:        "default-history",
	},
	modeHistoryListing: map[Key]string{
		Key{Up, 0}:     "histlist-prev",
		Key{Down, 0}:   "histlist-next",
		Key{Enter, 0}:  "histlist-append",
		Key{'[', Ctrl}: "start-insert",
		Default:        "default-history-listing",
	},
	modeLocation: map[Key]string{
		Key{Up, 0}:        "location-prev",
		Key{Down, 0}:      "location-next",
		Key{Tab, 0}:       "location-next",
		Key{Backspace, 0}: "location-backspace",
		Key{Enter, 0}:     "accept-location",
		Key{'[', Ctrl}:    "start-insert",
		Default:           "location-default",
	},
}

var keyBindings = map[ModeType]map[Key]Caller{}

var (
	errKeyMustBeString = errors.New("key must be string")
	errInvalidKey      = errors.New("invalid key to bind to")
	errInvalidFunction = errors.New("invalid function to bind")
)

// Caller is a function operating on an Editor. It is either a Builtin or an
// EvalCaller.
type Caller interface {
	eval.Reprer
	Call(ed *Editor)
}

func (b Builtin) Repr(int) string {
	return b.name
}

func (b Builtin) Call(ed *Editor) {
	b.impl(ed)
}

// EvalCaller adapts an eval.Caller to a Caller.
type EvalCaller struct {
	Caller eval.CallerValue
}

func (c EvalCaller) Repr(indent int) string {
	return c.Caller.Repr(indent)
}

func (c EvalCaller) Call(ed *Editor) {
	rout, chanOut, ports, err := makePorts()
	if err != nil {
		return
	}

	// Goroutines to collect output.
	var wg sync.WaitGroup
	wg.Add(2)
	go func() {
		rd := bufio.NewReader(rout)
		for {
			line, err := rd.ReadString('\n')
			if err != nil {
				break
			}
			// XXX notify is not concurrency-safe.
			ed.notify("[bound fn bytes] %s", line[:len(line)-1])
		}
		rout.Close()
		wg.Done()
	}()
	go func() {
		for v := range chanOut {
			ed.notify("[bound fn value] %s", v.Repr(eval.NoPretty))
		}
		wg.Done()
	}()

	// XXX There is no source to pass to NewTopEvalCtx.
	ec := eval.NewTopEvalCtx(ed.evaler, "[editor]", "", ports)
	ex := ec.PCall(c.Caller, []eval.Value{})
	if ex != nil {
		ed.notify("function error: %s", ex.Error())
	}

	eval.ClosePorts(ports)
	wg.Wait()
	ed.refresh(true, true)
}

// makePorts connects stdin to /dev/null and a closed channel, identifies
// stdout and stderr and connects them to a pipe and channel. It returns the
// other end of stdout and the resulting []*eval.Port. The caller is
// responsible for closing the returned file and calling eval.ClosePorts on the
// ports.
func makePorts() (*os.File, chan eval.Value, []*eval.Port, error) {
	in, err := makeClosedStdin()
	if err != nil {
		return nil, nil, nil, err
	}

	// Output
	rout, out, err := os.Pipe()
	if err != nil {
		Logger.Println(err)
		return nil, nil, nil, err
	}
	chanOut := make(chan eval.Value)

	return rout, chanOut, []*eval.Port{
		in,
		{File: out, CloseFile: true, Chan: chanOut, CloseChan: true},
		{File: out, Chan: chanOut},
	}, nil
}

func makeClosedStdin() (*eval.Port, error) {
	// Input
	devnull, err := os.Open("/dev/null")
	if err != nil {
		Logger.Println(err)
		return nil, err
	}
	in := make(chan eval.Value)
	close(in)
	return &eval.Port{File: devnull, CloseFile: true, Chan: in}, nil
}
<commit_msg>edit: Bind Enter to insert filename in navigation mode.<commit_after>package edit

import (
	"bufio"
	"errors"
	"os"
	"sync"

	"github.com/elves/elvish/eval"
)

var defaultBindings = map[ModeType]map[Key]string{
	modeInsert: map[Key]string{
		// Moving.
		Key{Left, 0}:     "move-dot-left",
		Key{Right, 0}:    "move-dot-right",
		Key{Up, Alt}:     "move-dot-up",
		Key{Down, Alt}:   "move-dot-down",
		Key{Left, Ctrl}:  "move-dot-left-word",
		Key{Right, Ctrl}: "move-dot-right-word",
		Key{Home, 0}:     "move-dot-sol",
		Key{End, 0}:      "move-dot-eol",
		// Killing.
		Key{'U', Ctrl}:    "kill-line-left",
		Key{'K', Ctrl}:    "kill-line-right",
		Key{'W', Ctrl}:    "kill-word-left",
		Key{Backspace, 0}: "kill-rune-left",
		// Some terminal send ^H on backspace
		// Key{'H', Ctrl}: "kill-rune-left",
		Key{Delete, 0}: "kill-rune-right",
		// Inserting.
		Key{'.', Alt}:   "insert-last-word",
		Key{Enter, Alt}: "insert-key",
		// Controls.
		Key{Enter, 0}:  "smart-enter",
		Key{'D', Ctrl}: "return-eof",
		// Key{'[', Ctrl}: "startCommand",
		Key{Tab, 0}:    "complete-prefix-or-start-completion",
		Key{Up, 0}:     "start-history",
		Key{'N', Ctrl}: "start-navigation",
		Key{'H', Ctrl}: "start-history-listing",
		Key{'L', Ctrl}: "start-location",
		Default:        "default-insert",
	},
	modeCommand: map[Key]string{
		// Moving.
		Key{'h', 0}: "move-dot-left",
		Key{'l', 0}: "move-dot-right",
		Key{'k', 0}: "move-dot-up",
		Key{'j', 0}: "move-dot-down",
		Key{'b', 0}: "move-dot-left-word",
		Key{'w', 0}: "move-dot-right-word",
		Key{'0', 0}: "move-dot-sol",
		Key{'$', 0}: "move-dot-eol",
		// Killing.
		Key{'x', 0}: "kill-rune-right",
		Key{'D', 0}: "kill-line-right",
		// Controls.
		Key{'i', 0}: "start-insert",
		Default:     "default-command",
	},
	modeCompletion: map[Key]string{
		Key{Up, 0}:     "select-cand-up",
		Key{Down, 0}:   "select-cand-down",
		Key{Left, 0}:   "select-cand-left",
		Key{Right, 0}:  "select-cand-right",
		Key{Tab, 0}:    "cycle-cand-right",
		Key{Enter, 0}:  "accept-completion",
		Key{'[', Ctrl}: "start-insert",
		Default:        "default-completion",
	},
	modeNavigation: map[Key]string{
		Key{Up, 0}:     "select-nav-up",
		Key{Down, 0}:   "select-nav-down",
		Key{Left, 0}:   "ascend-nav",
		Key{Right, 0}:  "descend-nav",
		Key{Enter, 0}:  "nav-insert-selected",
		Key{'H', Ctrl}: "trigger-nav-show-hidden",
		Key{'[', Ctrl}: "start-insert",
		Default:        "default-navigation",
	},
	modeHistory: map[Key]string{
		Key{Up, 0}:     "select-history-prev",
		Key{Down, 0}:   "select-history-next-or-quit",
		Key{'[', Ctrl}: "start-insert",
		Default:        "default-history",
	},
	modeHistoryListing: map[Key]string{
		Key{Up, 0}:     "histlist-prev",
		Key{Down, 0}:   "histlist-next",
		Key{Enter, 0}:  "histlist-append",
		Key{'[', Ctrl}: "start-insert",
		Default:        "default-history-listing",
	},
	modeLocation: map[Key]string{
		Key{Up, 0}:        "location-prev",
		Key{Down, 0}:      "location-next",
		Key{Tab, 0}:       "location-next",
		Key{Backspace, 0}: "location-backspace",
		Key{Enter, 0}:     "accept-location",
		Key{'[', Ctrl}:    "start-insert",
		Default:           "location-default",
	},
}

var keyBindings = map[ModeType]map[Key]Caller{}

var (
	errKeyMustBeString = errors.New("key must be string")
	errInvalidKey      = errors.New("invalid key to bind to")
	errInvalidFunction = errors.New("invalid function to bind")
)

// Caller is a function operating on an Editor. It is either a Builtin or an
// EvalCaller.
type Caller interface {
	eval.Reprer
	Call(ed *Editor)
}

func (b Builtin) Repr(int) string {
	return b.name
}

func (b Builtin) Call(ed *Editor) {
	b.impl(ed)
}

// EvalCaller adapts an eval.Caller to a Caller.
type EvalCaller struct {
	Caller eval.CallerValue
}

func (c EvalCaller) Repr(indent int) string {
	return c.Caller.Repr(indent)
}

func (c EvalCaller) Call(ed *Editor) {
	rout, chanOut, ports, err := makePorts()
	if err != nil {
		return
	}

	// Goroutines to collect output.
	var wg sync.WaitGroup
	wg.Add(2)
	go func() {
		rd := bufio.NewReader(rout)
		for {
			line, err := rd.ReadString('\n')
			if err != nil {
				break
			}
			// XXX notify is not concurrency-safe.
			ed.notify("[bound fn bytes] %s", line[:len(line)-1])
		}
		rout.Close()
		wg.Done()
	}()
	go func() {
		for v := range chanOut {
			ed.notify("[bound fn value] %s", v.Repr(eval.NoPretty))
		}
		wg.Done()
	}()

	// XXX There is no source to pass to NewTopEvalCtx.
	ec := eval.NewTopEvalCtx(ed.evaler, "[editor]", "", ports)
	ex := ec.PCall(c.Caller, []eval.Value{})
	if ex != nil {
		ed.notify("function error: %s", ex.Error())
	}

	eval.ClosePorts(ports)
	wg.Wait()
	ed.refresh(true, true)
}

// makePorts connects stdin to /dev/null and a closed channel, identifies
// stdout and stderr and connects them to a pipe and channel. It returns the
// other end of stdout and the resulting []*eval.Port. The caller is
// responsible for closing the returned file and calling eval.ClosePorts on the
// ports.
func makePorts() (*os.File, chan eval.Value, []*eval.Port, error) {
	in, err := makeClosedStdin()
	if err != nil {
		return nil, nil, nil, err
	}

	// Output
	rout, out, err := os.Pipe()
	if err != nil {
		Logger.Println(err)
		return nil, nil, nil, err
	}
	chanOut := make(chan eval.Value)

	return rout, chanOut, []*eval.Port{
		in,
		{File: out, CloseFile: true, Chan: chanOut, CloseChan: true},
		{File: out, Chan: chanOut},
	}, nil
}

func makeClosedStdin() (*eval.Port, error) {
	// Input
	devnull, err := os.Open("/dev/null")
	if err != nil {
		Logger.Println(err)
		return nil, err
	}
	in := make(chan eval.Value)
	close(in)
	return &eval.Port{File: devnull, CloseFile: true, Chan: in}, nil
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"log"
	"net/http"
	"strconv"
	"sync"
	"html/template"

	_ "github.com/mattn/go-sqlite3"
	"database/sql"
)

var templFuncs = template.FuncMap{
	"prettyDisplay": prettyDisplay,
	"delta": computeDelta,
}
var templ = template.Must(template.New("").Funcs(templFuncs).ParseGlob("templates/*.html"))

type Envelope struct {
	// Values in Euro-cents
	Id int
	Balance int
	Target  int
	Name    string
	m       sync.Mutex
}

func (e *Envelope) String() string {
	return fmt.Sprintf("<Envelope '%s', Balance: %d, Target: %d>", e.Name, e.Balance, e.Target)
}

func EnvelopeFromDB(tx *sql.Tx, name string) *Envelope {
	e := Envelope{Name: name}

	err := tx.QueryRow("SELECT id, balance, target FROM envelopes WHERE name = $1", name).Scan(&e.Id, &e.Balance, &e.Target)
	if err == nil {
		return &e
	}
	if err, _ := tx.Exec("INSERT INTO envelopes VALUES (NULL, $1, 0, 0)", name); err != nil {
		log.Printf(`db insert failed: %v`, err)
	}
	return &e
}

func (e *Envelope) IncBalance(delta int) {
	e.m.Lock()
	defer e.m.Unlock()

	e.Balance += delta
}

func AllEnvelopes(db *sql.DB) []*Envelope {
	rv := []*Envelope{}

	rows, err := db.Query("SELECT id, name, balance, target FROM envelopes")
	if err != nil {
		log.Printf(`error querying DB: %v`, err)
		return nil
	}
	defer rows.Close()

	for rows.Next() {
		var e Envelope
		if err := rows.Scan(&e.Id, &e.Name, &e.Balance, &e.Target); err != nil {
			log.Printf(`error querying DB: %v`, err)
			return nil
		}
		rv = append(rv, &e)
	}

	return rv
}

func prettyDisplay(cents int) string {
	return fmt.Sprintf("%.02f", float64(cents) / 100)
}

func computeDelta(balance, target int) []string {
	delta := balance - target
	cls := "delta-ok"
	if delta < 0 {
		cls = "delta-warn"
	}
	return []string{cls, fmt.Sprintf(`%.02f`, float64(delta) / 100)}
}

func handleDeleteRequest(db *sql.DB, w http.ResponseWriter, r *http.Request) {
	log.Printf(`delete: %v`, r.URL)
	log.Printf(`name: %s`, r.FormValue("name"))

	_, err := db.Exec("DELETE FROM envelopes WHERE name = $1", r.FormValue("name"))
	if err != nil {
		log.Printf(`err: %s`, err)
	}
	http.Redirect(w, r, "/", http.StatusSeeOther)
}

func handleUpdateRequest(db *sql.DB, w http.ResponseWriter, r *http.Request) {
	log.Printf(`update: %v`, r.URL)

	log.Printf(`name: %s`, r.FormValue("env-name"))
	log.Printf(`newname: %s`, r.FormValue("env-new-name"))
	log.Printf(`target: %s`, r.FormValue("env-target"))
	log.Printf(`balance: %s`, r.FormValue("env-balance"))

	name := r.FormValue("env-name")
	newname := r.FormValue("env-new-name")

	tx, err := db.Begin()
	env := EnvelopeFromDB(tx, name)

	if newname != "" {
		env.Name = newname
	}

	deltaBalance := 0
	bal, err := strconv.ParseFloat(r.FormValue("env-balance"), 64)
	if err != nil {
		log.Printf(`err: %s`, err)
	} else {
		deltaBalance = int(bal * 100) - env.Balance
		env.Balance += deltaBalance
	}

	deltaTarget := 0
	tgt, err := strconv.ParseFloat(r.FormValue("env-target"), 64)
	if err != nil {
		log.Printf(`err: %s`, err)
	} else {
		deltaTarget = int(tgt * 100) - env.Target
		env.Target += deltaTarget
	}

	_, err = tx.Exec("INSERT INTO history VALUES (NULL, $1, $2, $3, $4)", env.Id, env.Name, deltaBalance, deltaTarget)
	if err != nil {
		log.Printf(`can't create history entry for change to envelope %d`, env.Id)
	}
	_, err = tx.Exec("UPDATE envelopes SET name = $1, balance = $2, target = $3 WHERE id = $4", env.Name, env.Balance, env.Target, env.Id)
	if err != nil {
		log.Printf(`can't update envelope: %v`, err)
	}
	if err = tx.Commit(); err != nil {
		log.Printf(`can't commit transaction: %v`, err)
	}

	http.Redirect(w, r, "/", http.StatusSeeOther)
}

func handleHistory(db *sql.DB, w http.ResponseWriter, r *http.Request) {
	id, err := strconv.Atoi(r.FormValue("id"))
	if err != nil {
		log.Printf(`can't parse ID %s: %s`, r.FormValue("id"), err)
	}

	type Event struct {
		Date string
		Name string
		Balance int
		Target int
	}

	param := struct {
		Id int
		Name string
		Events []Event
	}{
		Id: id,
	}

	tx, err := db.Begin()
	if err != nil {
		log.Printf(`tx: %s`, err)
		http.Redirect(w, r, "/", http.StatusSeeOther)
		return
	}
	defer tx.Rollback()

	if err := tx.QueryRow("SELECT name FROM envelopes WHERE id = $1", id).Scan(&param.Name); err != nil {
		http.Redirect(w, r, "/", http.StatusSeeOther)
		return
	}

	rows, err := tx.Query("SELECT id, name, balance, target FROM history WHERE envelope = $1", id)
	if err != nil {
		log.Printf(`can't query history for envelope %d: %s`, id, err)
		http.Redirect(w, r, "/", http.StatusSeeOther)
		return
	}
	defer rows.Close()

	for rows.Next() {
		var e Event
		var eventId int
		if err := rows.Scan(&eventId, &e.Name, &e.Balance, &e.Target); err != nil {
			log.Printf(`can't scan event %d: %s`, eventId, err)
		}
		param.Events = append(param.Events, e)
	}

	templ.ExecuteTemplate(w, "history.html", param)
}

func handleRequest(db *sql.DB, w http.ResponseWriter, r *http.Request) {
	log.Printf(`request: %v`, r.URL)

	w.Header().Add("Content-Type", "text/html")
	es := AllEnvelopes(db)
	delta := int(0)
	balance := int(0)
	for i := range es {
		delta += es[i].Balance - es[i].Target
		balance += es[i].Balance
	}
	dcls := "delta-ok"
	if delta < 0 {
		dcls = "delta-warn"
	}
	param := struct {
		Envelopes []*Envelope
		TotalDelta struct{
			Cls string
			Val int
		}
		TotalBalance int
	}{
		Envelopes: es,
		TotalDelta: struct{Cls string; Val int}{ dcls, delta },
		TotalBalance: balance,
	}
	templ.ExecuteTemplate(w, "index.html", param)
}

func setupDB(db *sql.DB) error {
	tx, err := db.Begin()
	if err != nil {
		return err
	}
	if _, err := tx.Exec("CREATE TABLE IF NOT EXISTS envelopes (id INTEGER PRIMARY KEY AUTOINCREMENT, name STRING, balance INTEGER, target INTEGER)"); err != nil {
		return err
	}
	if _, err := tx.Exec("CREATE TABLE IF NOT EXISTS history (id INTEGER PRIMARY KEY AUTOINCREMENT, envelope INTEGER, name STRING, balance INTEGER, target INTEGER, FOREIGN KEY(envelope) REFERENCES envelopes(id))"); err != nil {
		return err
	}

	return tx.Commit()
}

func main() {
	log.Printf("Here we go")

	db, err := sql.Open("sqlite3", "envelopes.sqlite")
	if err != nil {
		log.Fatal(err)
	}
	defer func() {
		log.Printf("db stats: %v", db.Stats())
		if err := db.Close(); err != nil {
			log.Printf(`error while saving DB: %s`, err)
		}
	}()

	if err := setupDB(db); err != nil {
		log.Fatalf(`can't setup DB: %v`, err)
	}

	var count int64
	if err := db.QueryRow("SELECT count(*) FROM envelopes").Scan(&count); err != nil {
		log.Fatal(err)
	}
	log.Printf(`DB contains %d envelopes`, count)

	http.Handle("/static/", http.FileServer(http.Dir(".")))
	http.HandleFunc("/", func (w http.ResponseWriter, r *http.Request) {
		handleRequest(db, w, r)
	})
	http.HandleFunc("/update", func (w http.ResponseWriter, r *http.Request) {
		handleUpdateRequest(db, w, r)
	})
	http.HandleFunc("/delete", func (w http.ResponseWriter, r *http.Request) {
		handleDeleteRequest(db, w, r)
	})
	http.HandleFunc("/history", func (w http.ResponseWriter, r *http.Request) {
		handleHistory(db, w, r)
	})
	err = http.ListenAndServe("127.0.0.1:8081", nil)
	if err != nil {
		log.Printf(`HTTP died: %s`, err)
	}
}
<commit_msg>Unexport allEnvelopes<commit_after>package main

import (
	"fmt"
	"log"
	"net/http"
	"strconv"
	"sync"
	"html/template"

	_ "github.com/mattn/go-sqlite3"
	"database/sql"
)

var templFuncs = template.FuncMap{
	"prettyDisplay": prettyDisplay,
	"delta": computeDelta,
}
var templ = template.Must(template.New("").Funcs(templFuncs).ParseGlob("templates/*.html"))

type Envelope struct {
	// Values in Euro-cents
	Id int
	Balance int
	Target  int
	Name    string
	m       sync.Mutex
}

func (e *Envelope) String() string {
	return fmt.Sprintf("<Envelope '%s', Balance: %d, Target: %d>", e.Name, e.Balance, e.Target)
}

func EnvelopeFromDB(tx *sql.Tx, name string) *Envelope {
	e := Envelope{Name: name}

	err := tx.QueryRow("SELECT id, balance, target FROM envelopes WHERE name = $1", name).Scan(&e.Id, &e.Balance, &e.Target)
	if err == nil {
		return &e
	}
	if err, _ := tx.Exec("INSERT INTO envelopes VALUES (NULL, $1, 0, 0)", name); err != nil {
		log.Printf(`db insert failed: %v`, err)
	}
	return &e
}

func (e *Envelope) IncBalance(delta int) {
	e.m.Lock()
	defer e.m.Unlock()

	e.Balance += delta
}

func allEnvelopes(db *sql.DB) []*Envelope {
	rv := []*Envelope{}

	rows, err := db.Query("SELECT id, name, balance, target FROM envelopes")
	if err != nil {
		log.Printf(`error querying DB: %v`, err)
		return nil
	}
	defer rows.Close()

	for rows.Next() {
		var e Envelope
		if err := rows.Scan(&e.Id, &e.Name, &e.Balance, &e.Target); err != nil {
			log.Printf(`error querying DB: %v`, err)
			return nil
		}
		rv = append(rv, &e)
	}

	return rv
}

func prettyDisplay(cents int) string {
	return fmt.Sprintf("%.02f", float64(cents) / 100)
}

func computeDelta(balance, target int) []string {
	delta := balance - target
	cls := "delta-ok"
	if delta < 0 {
		cls = "delta-warn"
	}
	return []string{cls, fmt.Sprintf(`%.02f`, float64(delta) / 100)}
}

func handleDeleteRequest(db *sql.DB, w http.ResponseWriter, r *http.Request) {
	log.Printf(`delete: %v`, r.URL)
	log.Printf(`name: %s`, r.FormValue("name"))

	_, err := db.Exec("DELETE FROM envelopes WHERE name = $1", r.FormValue("name"))
	if err != nil {
		log.Printf(`err: %s`, err)
	}
	http.Redirect(w, r, "/", http.StatusSeeOther)
}

func handleUpdateRequest(db *sql.DB, w http.ResponseWriter, r *http.Request) {
	log.Printf(`update: %v`, r.URL)

	log.Printf(`name: %s`, r.FormValue("env-name"))
	log.Printf(`newname: %s`, r.FormValue("env-new-name"))
	log.Printf(`target: %s`, r.FormValue("env-target"))
	log.Printf(`balance: %s`, r.FormValue("env-balance"))

	name := r.FormValue("env-name")
	newname := r.FormValue("env-new-name")

	tx, err := db.Begin()
	env := EnvelopeFromDB(tx, name)

	if newname != "" {
		env.Name = newname
	}

	deltaBalance := 0
	bal, err := strconv.ParseFloat(r.FormValue("env-balance"), 64)
	if err != nil {
		log.Printf(`err: %s`, err)
	} else {
		deltaBalance = int(bal * 100) - env.Balance
		env.Balance += deltaBalance
	}

	deltaTarget := 0
	tgt, err := strconv.ParseFloat(r.FormValue("env-target"), 64)
	if err != nil {
		log.Printf(`err: %s`, err)
	} else {
		deltaTarget = int(tgt * 100) - env.Target
		env.Target += deltaTarget
	}

	_, err = tx.Exec("INSERT INTO history VALUES (NULL, $1, $2, $3, $4)", env.Id, env.Name, deltaBalance, deltaTarget)
	if err != nil {
		log.Printf(`can't create history entry for change to envelope %d`, env.Id)
	}
	_, err = tx.Exec("UPDATE envelopes SET name = $1, balance = $2, target = $3 WHERE id = $4", env.Name, env.Balance, env.Target, env.Id)
	if err != nil {
		log.Printf(`can't update envelope: %v`, err)
	}
	if err = tx.Commit(); err != nil {
		log.Printf(`can't commit transaction: %v`, err)
	}

	http.Redirect(w, r, "/", http.StatusSeeOther)
}

func handleHistory(db *sql.DB, w http.ResponseWriter, r *http.Request) {
	id, err := strconv.Atoi(r.FormValue("id"))
	if err != nil {
		log.Printf(`can't parse ID %s: %s`, r.FormValue("id"), err)
	}

	type Event struct {
		Date string
		Name string
		Balance int
		Target int
	}

	param := struct {
		Id int
		Name string
		Events []Event
	}{
		Id: id,
	}

	tx, err := db.Begin()
	if err != nil {
		log.Printf(`tx: %s`, err)
		http.Redirect(w, r, "/", http.StatusSeeOther)
		return
	}
	defer tx.Rollback()

	if err := tx.QueryRow("SELECT name FROM envelopes WHERE id = $1", id).Scan(&param.Name); err != nil {
		http.Redirect(w, r, "/", http.StatusSeeOther)
		return
	}

	rows, err := tx.Query("SELECT id, name, balance, target FROM history WHERE envelope = $1", id)
	if err != nil {
		log.Printf(`can't query history for envelope %d: %s`, id, err)
		http.Redirect(w, r, "/", http.StatusSeeOther)
		return
	}
	defer rows.Close()

	for rows.Next() {
		var e Event
		var eventId int
		if err := rows.Scan(&eventId, &e.Name, &e.Balance, &e.Target); err != nil {
			log.Printf(`can't scan event %d: %s`, eventId, err)
		}
		param.Events = append(param.Events, e)
	}

	templ.ExecuteTemplate(w, "history.html", param)
}

func handleRequest(db *sql.DB, w http.ResponseWriter, r *http.Request) {
	log.Printf(`request: %v`, r.URL)

	w.Header().Add("Content-Type", "text/html")
	es := allEnvelopes(db)
	delta := int(0)
	balance := int(0)
	for i := range es {
		delta += es[i].Balance - es[i].Target
		balance += es[i].Balance
	}
	dcls := "delta-ok"
	if delta < 0 {
		dcls = "delta-warn"
	}
	param := struct {
		Envelopes []*Envelope
		TotalDelta struct{
			Cls string
			Val int
		}
		TotalBalance int
	}{
		Envelopes: es,
		TotalDelta: struct{Cls string; Val int}{ dcls, delta },
		TotalBalance: balance,
	}
	templ.ExecuteTemplate(w, "index.html", param)
}

func setupDB(db *sql.DB) error {
	tx, err := db.Begin()
	if err != nil {
		return err
	}
	if _, err := tx.Exec("CREATE TABLE IF NOT EXISTS envelopes (id INTEGER PRIMARY KEY AUTOINCREMENT, name STRING, balance INTEGER, target INTEGER)"); err != nil {
		return err
	}
	if _, err := tx.Exec("CREATE TABLE IF NOT EXISTS history (id INTEGER PRIMARY KEY AUTOINCREMENT, envelope INTEGER, name STRING, balance INTEGER, target INTEGER, FOREIGN KEY(envelope) REFERENCES envelopes(id))"); err != nil {
		return err
	}

	return tx.Commit()
}

func main() {
	log.Printf("Here we go")

	db, err := sql.Open("sqlite3", "envelopes.sqlite")
	if err != nil {
		log.Fatal(err)
	}
	defer func() {
		log.Printf("db stats: %v", db.Stats())
		if err := db.Close(); err != nil {
			log.Printf(`error while saving DB: %s`, err)
		}
	}()

	if err := setupDB(db); err != nil {
		log.Fatalf(`can't setup DB: %v`, err)
	}

	var count int64
	if err := db.QueryRow("SELECT count(*) FROM envelopes").Scan(&count); err != nil {
		log.Fatal(err)
	}
	log.Printf(`DB contains %d envelopes`, count)

	http.Handle("/static/", http.FileServer(http.Dir(".")))
	http.HandleFunc("/", func (w http.ResponseWriter, r *http.Request) {
		handleRequest(db, w, r)
	})
	http.HandleFunc("/update", func (w http.ResponseWriter, r *http.Request) {
		handleUpdateRequest(db, w, r)
	})
	http.HandleFunc("/delete", func (w http.ResponseWriter, r *http.Request) {
		handleDeleteRequest(db, w, r)
	})
	http.HandleFunc("/history", func (w http.ResponseWriter, r *http.Request) {
		handleHistory(db, w, r)
	})
	err = http.ListenAndServe("127.0.0.1:8081", nil)
	if err != nil {
		log.Printf(`HTTP died: %s`, err)
	}
}
<|endoftext|>


<commit_before>package main

import (
	"testing"
)

// Should cache first response and return it on second request without
// hitting origin again.
func TestCacheFirstResponse(t *testing.T) {
	testThreeRequestsAreCached(t, nil)
}

// Should cache responses with default TTL if the response doesn't specify
// a period itself.
func TestCacheDefaultTTL(t *testing.T) {
	t.Error("Not implemented")
}

// Should cache responses for the period defined in a `Expires: n` response
// header.
func TestCacheExpires(t *testing.T) {
	t.Error("Not implemented")
}

// Should cache responses for the period defined in a `Cache-Control:
// max-age=n` response header.
func TestCacheCacheControlMaxAge(t *testing.T) {
	t.Error("Not implemented")
}

// Should cache responses with a `Cache-Control: no-cache` header. Varnish
// doesn't respect this by default.
func TestCacheCacheControlNoCache(t *testing.T) {
	t.Error("Not implemented")
}

// Should cache responses with a status code of 404. It's a common
// misconception that 404 responses shouldn't be cached; they should because
// they can be expensive to generate.
func TestCache404Response(t *testing.T) {
	t.Error("Not implemented")
}
<commit_msg>Implement TestCacheCacheControlNoCache<commit_after>package main

import (
	"net/http"
	"testing"
)

// Should cache first response and return it on second request without
// hitting origin again.
func TestCacheFirstResponse(t *testing.T) {
	testThreeRequestsAreCached(t, nil)
}

// Should cache responses with default TTL if the response doesn't specify
// a period itself.
func TestCacheDefaultTTL(t *testing.T) {
	t.Error("Not implemented")
}

// Should cache responses for the period defined in a `Expires: n` response
// header.
func TestCacheExpires(t *testing.T) {
	t.Error("Not implemented")
}

// Should cache responses for the period defined in a `Cache-Control:
// max-age=n` response header.
func TestCacheCacheControlMaxAge(t *testing.T) {
	t.Error("Not implemented")
}

// Should cache responses with a `Cache-Control: no-cache` header. Varnish
// doesn't respect this by default.
func TestCacheCacheControlNoCache(t *testing.T) {
	handler := func(h http.Header) {
		h.Set("Cache-Control", "no-cache")
	}

	testThreeRequestsAreCached(t, handler)
}

// Should cache responses with a status code of 404. It's a common
// misconception that 404 responses shouldn't be cached; they should because
// they can be expensive to generate.
func TestCache404Response(t *testing.T) {
	t.Error("Not implemented")
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"path/filepath"
	"strconv"
	"strings"
)

func getPids(pattern string, filter bool) (pids []int) {
	pid := os.Getpid()

	fis, err := ioutil.ReadDir("/proc")
	if err != nil {
		log.Printf("err %s\n", err.Error())
		return pids
	}
Check:
	for _, fi := range fis {
		chpid, err := strconv.Atoi(fi.Name())
		if err != nil {
			continue Check
		}

		buf, err := ioutil.ReadFile(filepath.Join("/proc", fi.Name(), "comm"))

		if err == nil {
			if strings.TrimSpace(string(buf)) == pattern {
				if filter {
					ffis, err := ioutil.ReadDir(filepath.Join("/proc", fi.Name(), "task"))
					if err == nil {
						for _, ffi := range ffis {
							if ffi.Name() == fmt.Sprintf("%d", pid) {
								continue Check
							}
						}
						for _, ffi := range ffis {
							if ffi.Name() != fi.Name() {
								if cchpid, err := strconv.Atoi(ffi.Name()); err == nil {
									pids = append(pids, cchpid)
								}
							}
						}
					}
				}
				pids = append(pids, chpid)
			}
		}
	}
	return pids
}
<commit_msg>kill all pids<commit_after>package main

import (
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"path/filepath"
	"strconv"
	"strings"
)

func getPids(pattern string, filter bool) (pids []int) {
	pid := os.Getpid()

	fis, err := ioutil.ReadDir("/proc")
	if err != nil {
		log.Printf("err %s\n", err.Error())
		return pids
	}
Check:
	for _, fi := range fis {
		chpid, err := strconv.Atoi(fi.Name())
		if err != nil {
			continue Check
		}

		buf, err := ioutil.ReadFile(filepath.Join("/proc", fi.Name(), "comm"))

		if err == nil {
			progname := strings.TrimSpace(string(buf))
			switch pattern {
			case progname, filepath.Base(progname):
				if filter {
					ffis, err := ioutil.ReadDir(filepath.Join("/proc", fi.Name(), "task"))
					if err == nil {
						for _, ffi := range ffis {
							if ffi.Name() == fmt.Sprintf("%d", pid) {
								continue Check
							}
						}
						for _, ffi := range ffis {
							if ffi.Name() != fi.Name() {
								if cchpid, err := strconv.Atoi(ffi.Name()); err == nil {
									pids = append(pids, cchpid)
								}
							}
						}
					}
				}
				pids = append(pids, chpid)
			}

		}
	}
	return pids
}
<|endoftext|>


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

package proto_test

import (
	"flag"
	"fmt"
	"reflect"
	"testing"

	protoV1 "github.com/golang/protobuf/proto"
	"google.golang.org/protobuf/proto"
)

// The results of these microbenchmarks are unlikely to correspond well
// to real world peformance. They are mainly useful as a quick check to
// detect unexpected regressions and for profiling specific cases.

var (
	benchV1      = flag.Bool("v1", false, "benchmark the v1 implementation")
	allowPartial = flag.Bool("allow_partial", false, "set AllowPartial")
)

// BenchmarkEncode benchmarks encoding all the test messages.
func BenchmarkEncode(b *testing.B) {
	for _, test := range testProtos {
		for _, want := range test.decodeTo {
			v1 := want.(protoV1.Message)
			opts := proto.MarshalOptions{AllowPartial: *allowPartial}
			b.Run(fmt.Sprintf("%s (%T)", test.desc, want), func(b *testing.B) {
				b.RunParallel(func(pb *testing.PB) {
					for pb.Next() {
						var err error
						if *benchV1 {
							_, err = protoV1.Marshal(v1)
						} else {
							_, err = opts.Marshal(want)
						}
						if err != nil {
							b.Fatal(err)
						}
					}
				})
			})
		}
	}
}

// BenchmarkDecode benchmarks decoding all the test messages.
func BenchmarkDecode(b *testing.B) {
	for _, test := range testProtos {
		for _, want := range test.decodeTo {
			opts := proto.UnmarshalOptions{AllowPartial: *allowPartial}
			b.Run(fmt.Sprintf("%s (%T)", test.desc, want), func(b *testing.B) {
				b.RunParallel(func(pb *testing.PB) {
					m := reflect.New(reflect.TypeOf(want).Elem()).Interface().(proto.Message)
					v1 := m.(protoV1.Message)
					for pb.Next() {
						var err error
						if *benchV1 {
							err = protoV1.Unmarshal(test.wire, v1)
						} else {
							err = opts.Unmarshal(test.wire, m)
						}
						if err != nil {
							b.Fatal(err)
						}
					}
				})
			})
		}
	}
}
<commit_msg>proto: bench_test fixes<commit_after>// Copyright 2019 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package proto_test

import (
	"flag"
	"fmt"
	"reflect"
	"testing"

	protoV1 "github.com/golang/protobuf/proto"
	"google.golang.org/protobuf/proto"
)

// The results of these microbenchmarks are unlikely to correspond well
// to real world peformance. They are mainly useful as a quick check to
// detect unexpected regressions and for profiling specific cases.

var (
	benchV1      = flag.Bool("v1", false, "benchmark the v1 implementation")
	allowPartial = flag.Bool("allow_partial", false, "set AllowPartial")
)

// BenchmarkEncode benchmarks encoding all the test messages.
func BenchmarkEncode(b *testing.B) {
	for _, test := range testProtos {
		for _, want := range test.decodeTo {
			v1 := want.(protoV1.Message)
			opts := proto.MarshalOptions{AllowPartial: *allowPartial}
			b.Run(fmt.Sprintf("%s (%T)", test.desc, want), func(b *testing.B) {
				b.RunParallel(func(pb *testing.PB) {
					for pb.Next() {
						var err error
						if *benchV1 {
							_, err = protoV1.Marshal(v1)
						} else {
							_, err = opts.Marshal(want)
						}
						if err != nil && !test.partial {
							b.Fatal(err)
						}
					}
				})
			})
		}
	}
}

// BenchmarkDecode benchmarks decoding all the test messages.
func BenchmarkDecode(b *testing.B) {
	for _, test := range testProtos {
		for _, want := range test.decodeTo {
			opts := proto.UnmarshalOptions{AllowPartial: *allowPartial}
			b.Run(fmt.Sprintf("%s (%T)", test.desc, want), func(b *testing.B) {
				b.RunParallel(func(pb *testing.PB) {
					for pb.Next() {
						m := reflect.New(reflect.TypeOf(want).Elem()).Interface().(proto.Message)
						v1 := m.(protoV1.Message)
						var err error
						if *benchV1 {
							err = protoV1.Unmarshal(test.wire, v1)
						} else {
							err = opts.Unmarshal(test.wire, m)
						}
						if err != nil && !test.partial {
							b.Fatal(err)
						}
					}
				})
			})
		}
	}
}
<|endoftext|>


<commit_before>// Copyright (C) 2017 Michał Matczuk
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package proto

import (
	"fmt"
	"net/http"
)

// Protocol HTTP headers.
const (
	HeaderAction       = "T-Action"
	HeaderError        = "T-Error"
	HeaderForwardedBy  = "T-Forwarded-By"
	HeaderForwardedFor = "T-Forwarded-For"
	HeaderPath         = "T-Path"
	HeaderProtocol     = "T-Proto"
)

// Known actions.
const (
	ActionProxy string = "proxy"
)

// Known protocol types.
const (
	HTTP = "http"
	TCP  = "tcp"
	TCP4 = "tcp4"
	TCP6 = "tcp6"
	UNIX = "unix"
	WS   = "ws"
)

// ControlMessage is sent from server to client before streaming data. It's
// used to inform client about the data and action to take. Based on that client
// routes requests to backend services.
type ControlMessage struct {
	Action       string
	Protocol     string
	ForwardedFor string
	ForwardedBy  string
	Path         string
}

// ReadControlMessage reads ControlMessage from HTTP headers.
func ReadControlMessage(h http.Header) (*ControlMessage, error) {
	msg := ControlMessage{
		Action:       h.Get(HeaderAction),
		Protocol:     h.Get(HeaderProtocol),
		ForwardedFor: h.Get(HeaderForwardedFor),
		ForwardedBy:  h.Get(HeaderForwardedBy),
		Path:         h.Get(HeaderPath),
	}

	var missing []string

	if msg.Action == "" {
		missing = append(missing, HeaderAction)
	}
	if msg.Protocol == "" {
		missing = append(missing, HeaderProtocol)
	}
	if msg.ForwardedFor == "" {
		missing = append(missing, HeaderForwardedFor)
	}
	if msg.ForwardedBy == "" {
		missing = append(missing, HeaderForwardedBy)
	}

	if len(missing) != 0 {
		return nil, fmt.Errorf("missing headers: %s", missing)
	}

	return &msg, nil
}

// Update writes ControlMessage to HTTP header.
func (c *ControlMessage) Update(h http.Header) {
	h.Set(HeaderAction, string(c.Action))
	h.Set(HeaderProtocol, c.Protocol)
	h.Set(HeaderForwardedFor, c.ForwardedFor)
	h.Set(HeaderForwardedBy, c.ForwardedBy)
	h.Set(HeaderPath, c.Path)
}
<commit_msg>proto: adjusted const order<commit_after>// Copyright (C) 2017 Michał Matczuk
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package proto

import (
	"fmt"
	"net/http"
)

// Protocol HTTP headers.
const (
	HeaderError        = "T-Error"

	HeaderAction       = "T-Action"
	HeaderProtocol     = "T-Proto"
	HeaderForwardedFor = "T-Forwarded-For"
	HeaderForwardedBy  = "T-Forwarded-By"
	HeaderPath         = "T-Path"
)

// Known actions.
const (
	ActionProxy string = "proxy"
)

// Known protocol types.
const (
	HTTP = "http"
	TCP  = "tcp"
	TCP4 = "tcp4"
	TCP6 = "tcp6"
	UNIX = "unix"
	WS   = "ws"
)

// ControlMessage is sent from server to client before streaming data. It's
// used to inform client about the data and action to take. Based on that client
// routes requests to backend services.
type ControlMessage struct {
	Action       string
	Protocol     string
	ForwardedFor string
	ForwardedBy  string
	Path         string
}

// ReadControlMessage reads ControlMessage from HTTP headers.
func ReadControlMessage(h http.Header) (*ControlMessage, error) {
	msg := ControlMessage{
		Action:       h.Get(HeaderAction),
		Protocol:     h.Get(HeaderProtocol),
		ForwardedFor: h.Get(HeaderForwardedFor),
		ForwardedBy:  h.Get(HeaderForwardedBy),
		Path:         h.Get(HeaderPath),
	}

	var missing []string

	if msg.Action == "" {
		missing = append(missing, HeaderAction)
	}
	if msg.Protocol == "" {
		missing = append(missing, HeaderProtocol)
	}
	if msg.ForwardedFor == "" {
		missing = append(missing, HeaderForwardedFor)
	}
	if msg.ForwardedBy == "" {
		missing = append(missing, HeaderForwardedBy)
	}

	if len(missing) != 0 {
		return nil, fmt.Errorf("missing headers: %s", missing)
	}

	return &msg, nil
}

// Update writes ControlMessage to HTTP header.
func (c *ControlMessage) Update(h http.Header) {
	h.Set(HeaderAction, string(c.Action))
	h.Set(HeaderProtocol, c.Protocol)
	h.Set(HeaderForwardedFor, c.ForwardedFor)
	h.Set(HeaderForwardedBy, c.ForwardedBy)
	h.Set(HeaderPath, c.Path)
}
<|endoftext|>


<commit_before>// Copyright 2015 Google Inc. All Rights Reserved.
//
// 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 fuseutil

import (
	"io"

	"github.com/jacobsa/fuse"
	"github.com/jacobsa/fuse/fuseops"
)

// An interface with a method for each op type in the fuseops package. This can
// be used in conjunction with NewFileSystemServer to avoid writing a "dispatch
// loop" that switches on op types, instead receiving typed method calls
// directly.
//
// Each method is responsible for calling Respond on the supplied op.
//
// See NotImplementedFileSystem for a convenient way to embed default
// implementations for methods you don't care about.
type FileSystem interface {
	Init(*fuseops.InitOp)
	LookUpInode(*fuseops.LookUpInodeOp)
	GetInodeAttributes(*fuseops.GetInodeAttributesOp)
	SetInodeAttributes(*fuseops.SetInodeAttributesOp)
	ForgetInode(*fuseops.ForgetInodeOp)
	MkDir(*fuseops.MkDirOp)
	CreateFile(*fuseops.CreateFileOp)
	RmDir(*fuseops.RmDirOp)
	Unlink(*fuseops.UnlinkOp)
	OpenDir(*fuseops.OpenDirOp)
	ReadDir(*fuseops.ReadDirOp)
	ReleaseDirHandle(*fuseops.ReleaseDirHandleOp)
	OpenFile(*fuseops.OpenFileOp)
	ReadFile(*fuseops.ReadFileOp)
	WriteFile(*fuseops.WriteFileOp)
	SyncFile(*fuseops.SyncFileOp)
	FlushFile(*fuseops.FlushFileOp)
	ReleaseFileHandle(*fuseops.ReleaseFileHandleOp)
}

// Create a fuse.Server that handles ops by calling the associated FileSystem
// method.Respond with the resulting error. Unsupported ops are responded to
// directly with ENOSYS.
//
// Each call to a FileSystem method is made on its own goroutine, and is free
// to block.
//
// (It is safe to naively process ops concurrently because the kernel
// guarantees to serialize operations that the user expects to happen in order,
// cf. http://goo.gl/jnkHPO, fuse-devel thread "Fuse guarantees on concurrent
// requests").
func NewFileSystemServer(fs FileSystem) fuse.Server {
	return fileSystemServer{fs}
}

// A convenience function that makes it easy to ensure you respond to an
// operation when a FileSystem method returns. Responds to op with the current
// value of *err.
//
// For example:
//
//     func (fs *myFS) ReadFile(op *fuseops.ReadFileOp) {
//       var err error
//       defer fuseutil.RespondToOp(op, &err)
//
//       if err = fs.frobnicate(); err != nil {
//         err = fmt.Errorf("frobnicate: %v", err)
//         return
//       }
//
//       // Lots more manipulation of err, and return paths.
//       // [...]
//     }
//
func RespondToOp(op fuseops.Op, err *error) {
	op.Respond(*err)
}

type fileSystemServer struct {
	fs FileSystem
}

func (s fileSystemServer) ServeOps(c *fuse.Connection) {
	for {
		op, err := c.ReadOp()
		if err == io.EOF {
			break
		}

		if err != nil {
			panic(err)
		}

		go s.handleOp(op)
	}
}

func (s fileSystemServer) handleOp(op fuseops.Op) {
	switch typed := op.(type) {
	default:
		op.Respond(fuse.ENOSYS)

	case *fuseops.InitOp:
		s.fs.Init(typed)

	case *fuseops.LookUpInodeOp:
		s.fs.LookUpInode(typed)

	case *fuseops.GetInodeAttributesOp:
		s.fs.GetInodeAttributes(typed)

	case *fuseops.SetInodeAttributesOp:
		s.fs.SetInodeAttributes(typed)

	case *fuseops.ForgetInodeOp:
		s.fs.ForgetInode(typed)

	case *fuseops.MkDirOp:
		s.fs.MkDir(typed)

	case *fuseops.CreateFileOp:
		s.fs.CreateFile(typed)

	case *fuseops.RmDirOp:
		s.fs.RmDir(typed)

	case *fuseops.UnlinkOp:
		s.fs.Unlink(typed)

	case *fuseops.OpenDirOp:
		s.fs.OpenDir(typed)

	case *fuseops.ReadDirOp:
		s.fs.ReadDir(typed)

	case *fuseops.ReleaseDirHandleOp:
		s.fs.ReleaseDirHandle(typed)

	case *fuseops.OpenFileOp:
		s.fs.OpenFile(typed)

	case *fuseops.ReadFileOp:
		s.fs.ReadFile(typed)

	case *fuseops.WriteFileOp:
		s.fs.WriteFile(typed)

	case *fuseops.SyncFileOp:
		s.fs.SyncFile(typed)

	case *fuseops.FlushFileOp:
		s.fs.FlushFile(typed)

	case *fuseops.ReleaseFileHandleOp:
		s.fs.ReleaseFileHandle(typed)
	}
}
<commit_msg>Added a random delays feature for #8.<commit_after>// Copyright 2015 Google Inc. All Rights Reserved.
//
// 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 fuseutil

import (
	"flag"
	"io"
	"math/rand"
	"time"

	"github.com/jacobsa/fuse"
	"github.com/jacobsa/fuse/fuseops"
)

var fRandDelays = flag.Bool(
	"fuseutil.rand_delays", false,
	"If set, randomly delay each op received, to help expose concurrency issues.")

// An interface with a method for each op type in the fuseops package. This can
// be used in conjunction with NewFileSystemServer to avoid writing a "dispatch
// loop" that switches on op types, instead receiving typed method calls
// directly.
//
// Each method is responsible for calling Respond on the supplied op.
//
// See NotImplementedFileSystem for a convenient way to embed default
// implementations for methods you don't care about.
type FileSystem interface {
	Init(*fuseops.InitOp)
	LookUpInode(*fuseops.LookUpInodeOp)
	GetInodeAttributes(*fuseops.GetInodeAttributesOp)
	SetInodeAttributes(*fuseops.SetInodeAttributesOp)
	ForgetInode(*fuseops.ForgetInodeOp)
	MkDir(*fuseops.MkDirOp)
	CreateFile(*fuseops.CreateFileOp)
	RmDir(*fuseops.RmDirOp)
	Unlink(*fuseops.UnlinkOp)
	OpenDir(*fuseops.OpenDirOp)
	ReadDir(*fuseops.ReadDirOp)
	ReleaseDirHandle(*fuseops.ReleaseDirHandleOp)
	OpenFile(*fuseops.OpenFileOp)
	ReadFile(*fuseops.ReadFileOp)
	WriteFile(*fuseops.WriteFileOp)
	SyncFile(*fuseops.SyncFileOp)
	FlushFile(*fuseops.FlushFileOp)
	ReleaseFileHandle(*fuseops.ReleaseFileHandleOp)
}

// Create a fuse.Server that handles ops by calling the associated FileSystem
// method.Respond with the resulting error. Unsupported ops are responded to
// directly with ENOSYS.
//
// Each call to a FileSystem method is made on its own goroutine, and is free
// to block.
//
// (It is safe to naively process ops concurrently because the kernel
// guarantees to serialize operations that the user expects to happen in order,
// cf. http://goo.gl/jnkHPO, fuse-devel thread "Fuse guarantees on concurrent
// requests").
func NewFileSystemServer(fs FileSystem) fuse.Server {
	return fileSystemServer{fs}
}

// A convenience function that makes it easy to ensure you respond to an
// operation when a FileSystem method returns. Responds to op with the current
// value of *err.
//
// For example:
//
//     func (fs *myFS) ReadFile(op *fuseops.ReadFileOp) {
//       var err error
//       defer fuseutil.RespondToOp(op, &err)
//
//       if err = fs.frobnicate(); err != nil {
//         err = fmt.Errorf("frobnicate: %v", err)
//         return
//       }
//
//       // Lots more manipulation of err, and return paths.
//       // [...]
//     }
//
func RespondToOp(op fuseops.Op, err *error) {
	op.Respond(*err)
}

type fileSystemServer struct {
	fs FileSystem
}

func (s fileSystemServer) ServeOps(c *fuse.Connection) {
	for {
		op, err := c.ReadOp()
		if err == io.EOF {
			break
		}

		if err != nil {
			panic(err)
		}

		go s.handleOp(op)
	}
}

func (s fileSystemServer) handleOp(op fuseops.Op) {
	// Delay if requested.
	if *fRandDelays {
		const delayLimit = 100 * time.Microsecond
		delay := time.Duration(rand.Int63n(int64(delayLimit)))
		time.Sleep(delay)
	}

	// Dispatch to the appropriate method.
	switch typed := op.(type) {
	default:
		op.Respond(fuse.ENOSYS)

	case *fuseops.InitOp:
		s.fs.Init(typed)

	case *fuseops.LookUpInodeOp:
		s.fs.LookUpInode(typed)

	case *fuseops.GetInodeAttributesOp:
		s.fs.GetInodeAttributes(typed)

	case *fuseops.SetInodeAttributesOp:
		s.fs.SetInodeAttributes(typed)

	case *fuseops.ForgetInodeOp:
		s.fs.ForgetInode(typed)

	case *fuseops.MkDirOp:
		s.fs.MkDir(typed)

	case *fuseops.CreateFileOp:
		s.fs.CreateFile(typed)

	case *fuseops.RmDirOp:
		s.fs.RmDir(typed)

	case *fuseops.UnlinkOp:
		s.fs.Unlink(typed)

	case *fuseops.OpenDirOp:
		s.fs.OpenDir(typed)

	case *fuseops.ReadDirOp:
		s.fs.ReadDir(typed)

	case *fuseops.ReleaseDirHandleOp:
		s.fs.ReleaseDirHandle(typed)

	case *fuseops.OpenFileOp:
		s.fs.OpenFile(typed)

	case *fuseops.ReadFileOp:
		s.fs.ReadFile(typed)

	case *fuseops.WriteFileOp:
		s.fs.WriteFile(typed)

	case *fuseops.SyncFileOp:
		s.fs.SyncFile(typed)

	case *fuseops.FlushFileOp:
		s.fs.FlushFile(typed)

	case *fuseops.ReleaseFileHandleOp:
		s.fs.ReleaseFileHandle(typed)
	}
}
<|endoftext|>


<commit_before><commit_msg>Start writing tests for  hash.go<commit_after><|endoftext|>


<commit_before>// Copyright (C) 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 client

import (
	"context"
	"fmt"
	"io"
	"os"
	"strconv"
	"strings"
	"time"

	"github.com/google/gapid/core/app/auth"
	"github.com/google/gapid/core/app/crash"
	"github.com/google/gapid/core/app/layout"
	"github.com/google/gapid/core/event/task"
	"github.com/google/gapid/core/log"
	"github.com/google/gapid/core/os/android"
	"github.com/google/gapid/core/os/android/adb"
	"github.com/google/gapid/core/os/device"
	"github.com/google/gapid/core/os/device/bind"
	"github.com/google/gapid/core/os/device/host"
	"github.com/google/gapid/core/os/device/remotessh"
	"github.com/google/gapid/core/os/process"
	"github.com/google/gapid/core/os/shell"
	"github.com/google/gapid/core/text"
	"github.com/google/gapid/core/vulkan/loader"
	"github.com/google/gapid/gapidapk"
)

const (
	sessionTimeout             = time.Second * 30
	maxCheckSocketFileAttempts = 10
	checkSocketFileRetryDelay  = time.Second
	connectTimeout             = time.Second * 10
	heartbeatInterval          = time.Millisecond * 500
)

type session struct {
	device   bind.Device
	port     int
	auth     auth.Token
	closeCBs []func()
	inited   chan struct{}
	// The connection for heartbeat
	conn *Connection
}

func newSession(d bind.Device) *session {
	return &session{device: d, inited: make(chan struct{})}
}

func (s *session) init(ctx context.Context, d bind.Device, abi *device.ABI, launchArgs []string) error {
	defer close(s.inited)
	var err error

	if host.Instance(ctx).SameAs(d.Instance()) {
		err = s.newHost(ctx, d, abi, launchArgs)
	} else if adbd, ok := d.(adb.Device); ok {
		err = s.newADB(ctx, adbd, abi)
	} else if remoted, ok := d.(remotessh.Device); ok {
		err = s.newRemote(ctx, remoted, abi, launchArgs)
	} else {
		err = log.Errf(ctx, nil, "Cannot connect to device type %+v", d)
	}
	if err != nil {
		s.close(ctx)
		return err
	}

	crash.Go(func() { s.heartbeat(ctx, heartbeatInterval) })
	return nil
}

func (s *session) newRemote(ctx context.Context, d remotessh.Device, abi *device.ABI, launchArgs []string) error {
	authTokenFile, authToken := auth.GenTokenFile()
	defer os.Remove(authTokenFile)

	otherdir, cleanup, err := d.MakeTempDir(ctx)
	if err != nil {
		return err
	}
	defer cleanup(ctx)

	pf := otherdir + "/auth"
	if err = d.PushFile(ctx, authTokenFile, pf); err != nil {
		return err
	}

	forceEnableDiskCache := true

	// If the user has not specified anything disk-cache related
	// we should force a disk cache for remote devices.
	for _, a := range launchArgs {
		if a == "--enable-disk-cache" ||
			a == "--disk-cache-path" ||
			a == "--cleanup-on-disk-cache" {
			forceEnableDiskCache = false
		}
	}

	args := []string{
		"--idle-timeout-sec", strconv.Itoa(int(sessionTimeout / time.Second)),
		"--auth-token-file", pf,
	}
	args = append(args, launchArgs...)
	if forceEnableDiskCache {
		args = append(args, "--enable-disk-cache")
		if len(d.DefaultReplayCacheDir()) > 0 {
			args = append(args, "--disk-cache-path", d.DefaultReplayCacheDir())
		}
		args = append(args, "--cleanup-on-disk-cache")
	}

	gapir, err := layout.Gapir(ctx, abi)
	if err = d.PushFile(ctx, gapir.System(), otherdir+"/gapir"); err != nil {
		return err
	}

	remoteGapir := otherdir + "/gapir"

	env := shell.NewEnv()
	sessionCleanup, err := loader.SetupReplay(ctx, d, abi, env)
	if err != nil {
		return err
	}
	s.onClose(func() { sessionCleanup.Invoke(ctx) })

	parser := func(severity log.Severity) io.WriteCloser {
		h := log.GetHandler(ctx)
		if h == nil {
			return nil
		}
		ctx := log.PutProcess(ctx, "gapir")
		ctx = log.PutFilter(ctx, nil)
		return text.Writer(func(line string) error {
			if m := parseHostLogMsg(line); m != nil {
				h.Handle(m)
				return nil
			}
			log.From(ctx).Log(severity, false, line)
			return nil
		})
	}

	stdout := parser(log.Info)
	if stdout != nil {
		defer stdout.Close()
	}

	stderr := parser(log.Error)
	if stderr != nil {
		defer stderr.Close()
	}

	log.I(ctx, "Starting gapir on remote: %v %v", remoteGapir, args)

	port, err := process.StartOnDevice(ctx, remoteGapir, process.StartOptions{
		Env:    env,
		Args:   args,
		Stdout: stdout,
		Stderr: stderr,
		Device: d,
	})

	if err != nil {
		log.E(ctx, "Starting gapir. Error: %v", err)
		return nil
	}

	s.conn, err = newConnection(fmt.Sprintf("localhost:%d", port), authToken, connectTimeout)
	if err != nil {
		return log.Err(ctx, err, "Timeout waiting for connection")
	}

	s.onClose(func() {
		if s.conn != nil {
			s.conn.Close()
		}
	})
	log.I(ctx, "Heartbeat connection setup done")

	s.port = port
	s.auth = authToken
	return nil
}

// newHost spawns and returns a new GAPIR instance on the host machine.
func (s *session) newHost(ctx context.Context, d bind.Device, abi *device.ABI, launchArgs []string) error {
	authTokenFile, authToken := auth.GenTokenFile()
	defer os.Remove(authTokenFile)

	args := []string{
		"--idle-timeout-sec", strconv.Itoa(int(sessionTimeout / time.Second)),
		"--auth-token-file", authTokenFile,
	}
	args = append(args, launchArgs...)

	gapir, err := layout.Gapir(ctx, abi)
	if err != nil {
		log.F(ctx, true, "Couldn't locate gapir executable: %v", err)
		return nil
	}

	env := shell.CloneEnv()
	cleanup, err := loader.SetupReplay(ctx, d, abi, env)
	if err != nil {
		return err
	}
	s.onClose(func() { cleanup.Invoke(ctx) })

	parser := func(severity log.Severity) io.WriteCloser {
		h := log.GetHandler(ctx)
		if h == nil {
			return nil
		}
		ctx := log.PutProcess(ctx, "gapir")
		ctx = log.PutFilter(ctx, nil)
		return text.Writer(func(line string) error {
			if m := parseHostLogMsg(line); m != nil {
				h.Handle(m)
				return nil
			}
			log.From(ctx).Log(severity, false, line)
			return nil
		})
	}

	stdout := parser(log.Info)
	if stdout != nil {
		defer stdout.Close()
	}

	stderr := parser(log.Error)
	if stderr != nil {
		defer stderr.Close()
	}

	log.I(ctx, "Starting gapir on host: %v %v", gapir.System(), args)
	port, err := process.StartOnDevice(ctx, gapir.System(), process.StartOptions{
		Env:    env,
		Args:   args,
		Stdout: stdout,
		Stderr: stderr,
		Device: d,
	})
	if err != nil {
		log.E(ctx, "Starting gapir. Error: %v", err)
		return nil
	}

	s.conn, err = newConnection(fmt.Sprintf("localhost:%d", port), authToken, connectTimeout)
	if err != nil {
		return log.Err(ctx, err, "Timeout waiting for connection")
	}
	s.onClose(func() {
		if s.conn != nil {
			s.conn.Close()
		}
	})
	log.I(ctx, "Heartbeat connection setup done")

	s.port = port
	s.auth = authToken
	return nil
}

var socketNames = map[device.Architecture]string{
	device.ARMv7a: "gapir-arm",
	device.ARMv8a: "gapir-arm64",
	device.X86:    "gapir-x86",
	device.X86_64: "gapir-x86-64",
}

func (s *session) newADB(ctx context.Context, d adb.Device, abi *device.ABI) error {
	ctx = log.V{"abi": abi}.Bind(ctx)

	log.I(ctx, "Checking gapid.apk is installed...")
	apk, err := gapidapk.EnsureInstalled(ctx, d, abi)
	if err != nil {
		return err
	}

	log.I(ctx, "Launching GAPIR...")
	if err := d.StartActivity(ctx, *apk.ActivityActions[0],
		android.IntExtra{"idle_timeout", int(sessionTimeout / time.Second)},
		android.StringExtra{"auth_token", string(s.auth)},
	); err != nil {
		return err
	}

	log.I(ctx, "Setting up port forwarding...")
	localPort, err := adb.LocalFreeTCPPort()
	if err != nil {
		return log.Err(ctx, err, "Finding free port")
	}
	s.port = int(localPort)
	socket, ok := socketNames[abi.Architecture]
	ctx = log.V{"socket": socket}.Bind(ctx)
	if !ok {
		return log.Errf(ctx, nil, "Unsupported architecture: %v", abi.Architecture)
	}
	apkDir, err := apk.FileDir(ctx)
	if err != nil {
		return log.Errf(ctx, err, "Getting gapid.apk files directory")
	}
	appDir, err := apk.AppDir(ctx)
	if err != nil {
		return log.Errf(ctx, err, "Getting gapid.apk directory")
	}

	// Ignore the error returned from this. This is best-effort.
	// See: https://android.googlesource.com/platform/ndk.git/+/ndk-release-r18/ndk-gdb.py#386
	// for more information.
	_, _ = d.Shell("run-as", apk.Name, "chmod", "+x", appDir).Call(ctx)

	// Wait for the socket file to be created
	socketPath := strings.Join([]string{apkDir, socket}, "/")
	err = task.Retry(ctx, maxCheckSocketFileAttempts, checkSocketFileRetryDelay,
		func(ctx context.Context) (bool, error) {
			str, err := d.Shell("run-as", apk.Name, "ls", socketPath).Call(ctx)
			if err != nil {
				return false, err
			}
			if strings.HasSuffix(str, "No such file or directory") {
				return false, log.Errf(ctx, nil, "Gapir socket '%v' not created yet", socketPath)
			}
			return true, nil
		})
	if err != nil {
		return log.Errf(ctx, err, "Checking socket: %v", socketPath)
	}
	log.I(ctx, "Gapir socket: '%v' is opened now", socketPath)

	if err := d.Forward(ctx, localPort, adb.NamedFileSystemSocket(socketPath)); err != nil {
		return log.Err(ctx, err, "Forwarding port")
	}
	s.onClose(func() { d.RemoveForward(ctx, localPort) })

	log.I(ctx, "Waiting for connection to GAPIR...")
	s.conn, err = newConnection(fmt.Sprintf("localhost:%d", localPort), s.auth, connectTimeout)
	if err != nil {
		return log.Err(ctx, err, "Timeout waiting for connection")
	}
	s.onClose(func() {
		if s.conn != nil {
			s.conn.Close()
		}
	})
	log.I(ctx, "Heartbeat connection setup done")
	return nil
}

func (s *session) connect(ctx context.Context) (*Connection, error) {
	<-s.inited
	return newConnection(fmt.Sprintf("localhost:%d", s.port), s.auth, connectTimeout)
}

func (s *session) onClose(f func()) {
	s.closeCBs = append(s.closeCBs, f)
}

func (s *session) close(ctx context.Context) {
	s.conn.Shutdown(ctx)
	for _, f := range s.closeCBs {
		f()
	}
	s.closeCBs = nil
}

func (s *session) ping(ctx context.Context) (time.Duration, error) {
	if s.conn == nil {
		return time.Duration(0), log.Errf(ctx, nil, "cannot ping without gapir connection")
	}
	start := time.Now()
	err := s.conn.Ping(ctx)
	if err != nil {
		return 0, err
	}
	return time.Since(start), nil
}

func (s *session) heartbeat(ctx context.Context, pingInterval time.Duration) {
	for {
		select {
		case <-task.ShouldStop(ctx):
			return
		case <-time.After(pingInterval):
			_, err := s.ping(ctx)
			if err != nil {
				log.E(ctx, "Error sending keep-alive ping. Error: %v", err)
				s.close(ctx)
				return
			}
		}
	}
}
<commit_msg>Check if the connection exists before shutting down.<commit_after>// Copyright (C) 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 client

import (
	"context"
	"fmt"
	"io"
	"os"
	"strconv"
	"strings"
	"time"

	"github.com/google/gapid/core/app/auth"
	"github.com/google/gapid/core/app/crash"
	"github.com/google/gapid/core/app/layout"
	"github.com/google/gapid/core/event/task"
	"github.com/google/gapid/core/log"
	"github.com/google/gapid/core/os/android"
	"github.com/google/gapid/core/os/android/adb"
	"github.com/google/gapid/core/os/device"
	"github.com/google/gapid/core/os/device/bind"
	"github.com/google/gapid/core/os/device/host"
	"github.com/google/gapid/core/os/device/remotessh"
	"github.com/google/gapid/core/os/process"
	"github.com/google/gapid/core/os/shell"
	"github.com/google/gapid/core/text"
	"github.com/google/gapid/core/vulkan/loader"
	"github.com/google/gapid/gapidapk"
)

const (
	sessionTimeout             = time.Second * 30
	maxCheckSocketFileAttempts = 10
	checkSocketFileRetryDelay  = time.Second
	connectTimeout             = time.Second * 10
	heartbeatInterval          = time.Millisecond * 500
)

type session struct {
	device   bind.Device
	port     int
	auth     auth.Token
	closeCBs []func()
	inited   chan struct{}
	// The connection for heartbeat
	conn *Connection
}

func newSession(d bind.Device) *session {
	return &session{device: d, inited: make(chan struct{})}
}

func (s *session) init(ctx context.Context, d bind.Device, abi *device.ABI, launchArgs []string) error {
	defer close(s.inited)
	var err error

	if host.Instance(ctx).SameAs(d.Instance()) {
		err = s.newHost(ctx, d, abi, launchArgs)
	} else if adbd, ok := d.(adb.Device); ok {
		err = s.newADB(ctx, adbd, abi)
	} else if remoted, ok := d.(remotessh.Device); ok {
		err = s.newRemote(ctx, remoted, abi, launchArgs)
	} else {
		err = log.Errf(ctx, nil, "Cannot connect to device type %+v", d)
	}
	if err != nil {
		s.close(ctx)
		return err
	}

	crash.Go(func() { s.heartbeat(ctx, heartbeatInterval) })
	return nil
}

func (s *session) newRemote(ctx context.Context, d remotessh.Device, abi *device.ABI, launchArgs []string) error {
	authTokenFile, authToken := auth.GenTokenFile()
	defer os.Remove(authTokenFile)

	otherdir, cleanup, err := d.MakeTempDir(ctx)
	if err != nil {
		return err
	}
	defer cleanup(ctx)

	pf := otherdir + "/auth"
	if err = d.PushFile(ctx, authTokenFile, pf); err != nil {
		return err
	}

	forceEnableDiskCache := true

	// If the user has not specified anything disk-cache related
	// we should force a disk cache for remote devices.
	for _, a := range launchArgs {
		if a == "--enable-disk-cache" ||
			a == "--disk-cache-path" ||
			a == "--cleanup-on-disk-cache" {
			forceEnableDiskCache = false
		}
	}

	args := []string{
		"--idle-timeout-sec", strconv.Itoa(int(sessionTimeout / time.Second)),
		"--auth-token-file", pf,
	}
	args = append(args, launchArgs...)
	if forceEnableDiskCache {
		args = append(args, "--enable-disk-cache")
		if len(d.DefaultReplayCacheDir()) > 0 {
			args = append(args, "--disk-cache-path", d.DefaultReplayCacheDir())
		}
		args = append(args, "--cleanup-on-disk-cache")
	}

	gapir, err := layout.Gapir(ctx, abi)
	if err = d.PushFile(ctx, gapir.System(), otherdir+"/gapir"); err != nil {
		return err
	}

	remoteGapir := otherdir + "/gapir"

	env := shell.NewEnv()
	sessionCleanup, err := loader.SetupReplay(ctx, d, abi, env)
	if err != nil {
		return err
	}
	s.onClose(func() { sessionCleanup.Invoke(ctx) })

	parser := func(severity log.Severity) io.WriteCloser {
		h := log.GetHandler(ctx)
		if h == nil {
			return nil
		}
		ctx := log.PutProcess(ctx, "gapir")
		ctx = log.PutFilter(ctx, nil)
		return text.Writer(func(line string) error {
			if m := parseHostLogMsg(line); m != nil {
				h.Handle(m)
				return nil
			}
			log.From(ctx).Log(severity, false, line)
			return nil
		})
	}

	stdout := parser(log.Info)
	if stdout != nil {
		defer stdout.Close()
	}

	stderr := parser(log.Error)
	if stderr != nil {
		defer stderr.Close()
	}

	log.I(ctx, "Starting gapir on remote: %v %v", remoteGapir, args)

	port, err := process.StartOnDevice(ctx, remoteGapir, process.StartOptions{
		Env:    env,
		Args:   args,
		Stdout: stdout,
		Stderr: stderr,
		Device: d,
	})

	if err != nil {
		log.E(ctx, "Starting gapir. Error: %v", err)
		return nil
	}

	s.conn, err = newConnection(fmt.Sprintf("localhost:%d", port), authToken, connectTimeout)
	if err != nil {
		return log.Err(ctx, err, "Timeout waiting for connection")
	}

	s.onClose(func() {
		if s.conn != nil {
			s.conn.Close()
		}
	})
	log.I(ctx, "Heartbeat connection setup done")

	s.port = port
	s.auth = authToken
	return nil
}

// newHost spawns and returns a new GAPIR instance on the host machine.
func (s *session) newHost(ctx context.Context, d bind.Device, abi *device.ABI, launchArgs []string) error {
	authTokenFile, authToken := auth.GenTokenFile()
	defer os.Remove(authTokenFile)

	args := []string{
		"--idle-timeout-sec", strconv.Itoa(int(sessionTimeout / time.Second)),
		"--auth-token-file", authTokenFile,
	}
	args = append(args, launchArgs...)

	gapir, err := layout.Gapir(ctx, abi)
	if err != nil {
		log.F(ctx, true, "Couldn't locate gapir executable: %v", err)
		return nil
	}

	env := shell.CloneEnv()
	cleanup, err := loader.SetupReplay(ctx, d, abi, env)
	if err != nil {
		return err
	}
	s.onClose(func() { cleanup.Invoke(ctx) })

	parser := func(severity log.Severity) io.WriteCloser {
		h := log.GetHandler(ctx)
		if h == nil {
			return nil
		}
		ctx := log.PutProcess(ctx, "gapir")
		ctx = log.PutFilter(ctx, nil)
		return text.Writer(func(line string) error {
			if m := parseHostLogMsg(line); m != nil {
				h.Handle(m)
				return nil
			}
			log.From(ctx).Log(severity, false, line)
			return nil
		})
	}

	stdout := parser(log.Info)
	if stdout != nil {
		defer stdout.Close()
	}

	stderr := parser(log.Error)
	if stderr != nil {
		defer stderr.Close()
	}

	log.I(ctx, "Starting gapir on host: %v %v", gapir.System(), args)
	port, err := process.StartOnDevice(ctx, gapir.System(), process.StartOptions{
		Env:    env,
		Args:   args,
		Stdout: stdout,
		Stderr: stderr,
		Device: d,
	})
	if err != nil {
		log.E(ctx, "Starting gapir. Error: %v", err)
		return nil
	}

	s.conn, err = newConnection(fmt.Sprintf("localhost:%d", port), authToken, connectTimeout)
	if err != nil {
		return log.Err(ctx, err, "Timeout waiting for connection")
	}
	s.onClose(func() {
		if s.conn != nil {
			s.conn.Close()
		}
	})
	log.I(ctx, "Heartbeat connection setup done")

	s.port = port
	s.auth = authToken
	return nil
}

var socketNames = map[device.Architecture]string{
	device.ARMv7a: "gapir-arm",
	device.ARMv8a: "gapir-arm64",
	device.X86:    "gapir-x86",
	device.X86_64: "gapir-x86-64",
}

func (s *session) newADB(ctx context.Context, d adb.Device, abi *device.ABI) error {
	ctx = log.V{"abi": abi}.Bind(ctx)

	log.I(ctx, "Checking gapid.apk is installed...")
	apk, err := gapidapk.EnsureInstalled(ctx, d, abi)
	if err != nil {
		return err
	}

	log.I(ctx, "Launching GAPIR...")
	if err := d.StartActivity(ctx, *apk.ActivityActions[0],
		android.IntExtra{"idle_timeout", int(sessionTimeout / time.Second)},
		android.StringExtra{"auth_token", string(s.auth)},
	); err != nil {
		return err
	}

	log.I(ctx, "Setting up port forwarding...")
	localPort, err := adb.LocalFreeTCPPort()
	if err != nil {
		return log.Err(ctx, err, "Finding free port")
	}
	s.port = int(localPort)
	socket, ok := socketNames[abi.Architecture]
	ctx = log.V{"socket": socket}.Bind(ctx)
	if !ok {
		return log.Errf(ctx, nil, "Unsupported architecture: %v", abi.Architecture)
	}
	apkDir, err := apk.FileDir(ctx)
	if err != nil {
		return log.Errf(ctx, err, "Getting gapid.apk files directory")
	}
	appDir, err := apk.AppDir(ctx)
	if err != nil {
		return log.Errf(ctx, err, "Getting gapid.apk directory")
	}

	// Ignore the error returned from this. This is best-effort.
	// See: https://android.googlesource.com/platform/ndk.git/+/ndk-release-r18/ndk-gdb.py#386
	// for more information.
	_, _ = d.Shell("run-as", apk.Name, "chmod", "+x", appDir).Call(ctx)

	// Wait for the socket file to be created
	socketPath := strings.Join([]string{apkDir, socket}, "/")
	err = task.Retry(ctx, maxCheckSocketFileAttempts, checkSocketFileRetryDelay,
		func(ctx context.Context) (bool, error) {
			str, err := d.Shell("run-as", apk.Name, "ls", socketPath).Call(ctx)
			if err != nil {
				return false, err
			}
			if strings.HasSuffix(str, "No such file or directory") {
				return false, log.Errf(ctx, nil, "Gapir socket '%v' not created yet", socketPath)
			}
			return true, nil
		})
	if err != nil {
		return log.Errf(ctx, err, "Checking socket: %v", socketPath)
	}
	log.I(ctx, "Gapir socket: '%v' is opened now", socketPath)

	if err := d.Forward(ctx, localPort, adb.NamedFileSystemSocket(socketPath)); err != nil {
		return log.Err(ctx, err, "Forwarding port")
	}
	s.onClose(func() { d.RemoveForward(ctx, localPort) })

	log.I(ctx, "Waiting for connection to GAPIR...")
	s.conn, err = newConnection(fmt.Sprintf("localhost:%d", localPort), s.auth, connectTimeout)
	if err != nil {
		return log.Err(ctx, err, "Timeout waiting for connection")
	}
	s.onClose(func() {
		if s.conn != nil {
			s.conn.Close()
		}
	})
	log.I(ctx, "Heartbeat connection setup done")
	return nil
}

func (s *session) connect(ctx context.Context) (*Connection, error) {
	<-s.inited
	return newConnection(fmt.Sprintf("localhost:%d", s.port), s.auth, connectTimeout)
}

func (s *session) onClose(f func()) {
	s.closeCBs = append(s.closeCBs, f)
}

func (s *session) close(ctx context.Context) {
	if s.conn != nil {
		s.conn.Shutdown(ctx)
	}
	for _, f := range s.closeCBs {
		f()
	}
	s.closeCBs = nil
}

func (s *session) ping(ctx context.Context) (time.Duration, error) {
	if s.conn == nil {
		return time.Duration(0), log.Errf(ctx, nil, "cannot ping without gapir connection")
	}
	start := time.Now()
	err := s.conn.Ping(ctx)
	if err != nil {
		return 0, err
	}
	return time.Since(start), nil
}

func (s *session) heartbeat(ctx context.Context, pingInterval time.Duration) {
	for {
		select {
		case <-task.ShouldStop(ctx):
			return
		case <-time.After(pingInterval):
			_, err := s.ping(ctx)
			if err != nil {
				log.E(ctx, "Error sending keep-alive ping. Error: %v", err)
				s.close(ctx)
				return
			}
		}
	}
}
<|endoftext|>


<commit_before>package logn

import (
	"io"
	"log"
	"os"
	"runtime"
)

type Logger struct {
	t *log.Logger
	i *log.Logger
	w *log.Logger
	e *log.Logger
	f *log.Logger
	p *log.Logger
}

// New creates a new Logger. The out variable sets the destination to which
// log data will be written.
func New(out io.Writer) *Logger {
	return &Logger{
		t: log.New(out, "TRACE ", log.LstdFlags|log.Lshortfile),
		i: log.New(out, "INFO  ", log.LstdFlags),
		w: log.New(out, "WARN  ", log.LstdFlags),
		e: log.New(out, "ERROR ", log.LstdFlags),
		f: log.New(out, "FATAL ", log.LstdFlags),
		p: log.New(out, "PANIC ", log.LstdFlags),
	}
}

var std = New(os.Stderr)

// Trace prints msg and then the current stack trace
func (l *Logger) Trace(msg string) {
	buf := make([]byte, 1024)
	runtime.Stack(buf, false)
	l.t.Printf("%s\n%s", msg, buf)
}

// Info prints informational message
func (l *Logger) Info(v ...interface{}) {
	l.i.Print(v...)
}

// Infof prints informational message as in fmt.Printf
func (l *Logger) Infof(fmt string, v ...interface{}) {
	l.i.Printf(fmt, v...)
}

// Infoln prints informational message as in fmt.Println
func (l *Logger) Infoln(v ...interface{}) {
	l.i.Println(v...)
}

// Warning prints warning message
func (l *Logger) Warning(v ...interface{}) {
	l.w.Print(v...)
}

// Warningf prints warning message as in fmt.Printf
func (l *Logger) Warningf(fmt string, v ...interface{}) {
	l.w.Printf(fmt, v...)
}

// Warningln prints warning message as in fmt.Println
func (l *Logger) Warningln(v ...interface{}) {
	l.w.Println(v...)
}

// Error prints error message
func (l *Logger) Error(v ...interface{}) {
	l.e.Print(v...)
}

// Errorf prints error message as in fmt.Printf
func (l *Logger) Errorf(fmt string, v ...interface{}) {
	l.e.Printf(fmt, v...)
}

// Errorln prints error message as in fmt.Println
func (l *Logger) Errorln(v ...interface{}) {
	l.e.Println(v...)
}

// Fatal prints warning message using log.Fatal
func (l *Logger) Fatal(v ...interface{}) {
	l.f.Fatal(v...)
}

// Fatalf prints warning message using log.Fatalf
func (l *Logger) Fatalf(fmt string, v ...interface{}) {
	l.f.Fatalf(fmt, v...)
}

// Fatalln prints warning message using log.Fatalln
func (l *Logger) Fatalln(v ...interface{}) {
	l.f.Fatalln(v...)
}

// Panic prints warning message using log.Panic
func (l *Logger) Panic(v ...interface{}) {
	l.p.Panic(v...)
}

// Panicf prints warning message using log.Panicf
func (l *Logger) Panicf(fmt string, v ...interface{}) {
	l.p.Panicf(fmt, v...)
}

// Panicln prints warning message using log.Panicln
func (l *Logger) Panicln(v ...interface{}) {
	l.p.Panicln(v...)
}

func Trace(msg string) {
	std.Trace(msg)
}

func Info(v ...interface{}) {
	std.Info(v...)
}

func Infof(fmt string, v ...interface{}) {
	std.Infof(fmt, v...)
}

func Infoln(v ...interface{}) {
	std.Infoln(v...)
}

func Warning(v ...interface{}) {
	std.Warning(v...)
}

func Warningf(fmt string, v ...interface{}) {
	std.Warningf(fmt, v...)
}

func Warningln(v ...interface{}) {
	std.Warningln(v...)
}

func Error(v ...interface{}) {
	std.Error(v...)
}

func Errorf(fmt string, v ...interface{}) {
	std.Errorf(fmt, v...)
}

func Errorln(v ...interface{}) {
	std.Errorln(v...)
}

func Fatal(v ...interface{}) {
	std.Fatal(v...)
}

func Fatalf(fmt string, v ...interface{}) {
	std.Fatalf(fmt, v...)
}

func Fatalln(v ...interface{}) {
	std.Fatalln(v...)
}

func Panic(v ...interface{}) {
	std.Panic(v...)
}

func Panicf(fmt string, v ...interface{}) {
	std.Panicf(fmt, v...)
}

func Panicln(v ...interface{}) {
	std.Panicln(v...)
}
<commit_msg>Added godoc comments<commit_after>package logn

import (
	"io"
	"log"
	"os"
	"runtime"
)

// Logger represents active logging object which iteself contains standard
// library logger objects for each log level.
type Logger struct {
	t *log.Logger
	i *log.Logger
	w *log.Logger
	e *log.Logger
	f *log.Logger
	p *log.Logger
}

// New creates a new Logger. The out variable sets the destination to which
// log data will be written.
func New(out io.Writer) *Logger {
	return &Logger{
		t: log.New(out, "TRACE ", log.LstdFlags|log.Lshortfile),
		i: log.New(out, "INFO  ", log.LstdFlags),
		w: log.New(out, "WARN  ", log.LstdFlags),
		e: log.New(out, "ERROR ", log.LstdFlags),
		f: log.New(out, "FATAL ", log.LstdFlags),
		p: log.New(out, "PANIC ", log.LstdFlags),
	}
}

var std = New(os.Stderr)

// Trace prints msg and then the current stack trace
func (l *Logger) Trace(msg string) {
	buf := make([]byte, 1024)
	runtime.Stack(buf, false)
	l.t.Printf("%s\n%s", msg, buf)
}

// Info prints informational message
func (l *Logger) Info(v ...interface{}) {
	l.i.Print(v...)
}

// Infof prints informational message as in fmt.Printf
func (l *Logger) Infof(fmt string, v ...interface{}) {
	l.i.Printf(fmt, v...)
}

// Infoln prints informational message as in fmt.Println
func (l *Logger) Infoln(v ...interface{}) {
	l.i.Println(v...)
}

// Warning prints warning message
func (l *Logger) Warning(v ...interface{}) {
	l.w.Print(v...)
}

// Warningf prints warning message as in fmt.Printf
func (l *Logger) Warningf(fmt string, v ...interface{}) {
	l.w.Printf(fmt, v...)
}

// Warningln prints warning message as in fmt.Println
func (l *Logger) Warningln(v ...interface{}) {
	l.w.Println(v...)
}

// Error prints error message
func (l *Logger) Error(v ...interface{}) {
	l.e.Print(v...)
}

// Errorf prints error message as in fmt.Printf
func (l *Logger) Errorf(fmt string, v ...interface{}) {
	l.e.Printf(fmt, v...)
}

// Errorln prints error message as in fmt.Println
func (l *Logger) Errorln(v ...interface{}) {
	l.e.Println(v...)
}

// Fatal prints warning message using log.Fatal
func (l *Logger) Fatal(v ...interface{}) {
	l.f.Fatal(v...)
}

// Fatalf prints warning message using log.Fatalf
func (l *Logger) Fatalf(fmt string, v ...interface{}) {
	l.f.Fatalf(fmt, v...)
}

// Fatalln prints warning message using log.Fatalln
func (l *Logger) Fatalln(v ...interface{}) {
	l.f.Fatalln(v...)
}

// Panic prints warning message using log.Panic
func (l *Logger) Panic(v ...interface{}) {
	l.p.Panic(v...)
}

// Panicf prints warning message using log.Panicf
func (l *Logger) Panicf(fmt string, v ...interface{}) {
	l.p.Panicf(fmt, v...)
}

// Panicln prints warning message using log.Panicln
func (l *Logger) Panicln(v ...interface{}) {
	l.p.Panicln(v...)
}

// Trace prints msg and then the current stack trace using default logger
func Trace(msg string) {
	std.Trace(msg)
}

// Info prints informational message using default logger
func Info(v ...interface{}) {
	std.Info(v...)
}

// Infof prints informational message as in fmt.Printf using default logger
func Infof(fmt string, v ...interface{}) {
	std.Infof(fmt, v...)
}

// Infoln prints informational message as in fmt.Println using default logger
func Infoln(v ...interface{}) {
	std.Infoln(v...)
}

// Warning prints warning message using default logger
func Warning(v ...interface{}) {
	std.Warning(v...)
}

// Warningf prints warning message as in fmt.Printf using default logger
func Warningf(fmt string, v ...interface{}) {
	std.Warningf(fmt, v...)
}

// Warningln prints warning message as in fmt.Println using default logger
func Warningln(v ...interface{}) {
	std.Warningln(v...)
}

// Error prints error message using default logger
func Error(v ...interface{}) {
	std.Error(v...)
}

// Errorf prints error message as in fmt.Printf using default logger
func Errorf(fmt string, v ...interface{}) {
	std.Errorf(fmt, v...)
}

// Errorln prints error message as in fmt.Println using default logger
func Errorln(v ...interface{}) {
	std.Errorln(v...)
}

// Fatal prints warning message using log.Fatal using default logger
func Fatal(v ...interface{}) {
	std.Fatal(v...)
}

// Fatalf prints warning message using log.Fatalf using default logger
func Fatalf(fmt string, v ...interface{}) {
	std.Fatalf(fmt, v...)
}

// Fatalln prints warning message using log.Fatalln using default logger
func Fatalln(v ...interface{}) {
	std.Fatalln(v...)
}

// Panic prints warning message using log.Panic using default logger
func Panic(v ...interface{}) {
	std.Panic(v...)
}

// Panicf prints warning message using log.Panicf using default logger
func Panicf(fmt string, v ...interface{}) {
	std.Panicf(fmt, v...)
}

// Panicln prints warning message using log.Panicln using default logger
func Panicln(v ...interface{}) {
	std.Panicln(v...)
}
<|endoftext|>


<commit_before>/*
 * Copyright 2015 1&1 Internet AG, http://1und1.de . All rights reserved. Licensed under the Apache v2 License.
 */

package oneandone_cloudserver_api

import (
	"github.com/docker/machine/log"
	"net/http"
)

type Log struct {
	withId,
	StartDate string `json:"start_date"`
	EndDate  string `json:"end_date"`
	Duration int    `json:"duration"`
	Status   Status
	Action   string `json:"action"`
	withType
	Resource     LogDetails
	User         LogDetails
	CloudPanelId string `json:"cloudpanel_id"`
}

type LogDetails struct {
	Id   string `json:"id"`
	Name string `json:"name"`
}

// GET /logs
func (api *API) GetLogs(period string) ([]Log, error) {
	log.Debug("requesting information about logs")
	result := []Log{}
	err := api.Client.Get(createUrl(api, "logs")+"?period="+period, &result, http.StatusOK)
	if err != nil {
		return nil, err
	}
	return result, nil
}

// GET /logs/{id}
func (api *API) GetLog(id string) (*Log, error) {
	log.Debugf("requesting information about log: '%s'", id)
	result := new(Log)
	err := api.Client.Get(createUrl(api, "logs", id), &result, http.StatusOK)
	if err != nil {
		return nil, err
	}
	return result, nil
}
<commit_msg>Remove incorrect comma in the logs struct<commit_after>/*
 * Copyright 2015 1&1 Internet AG, http://1und1.de . All rights reserved. Licensed under the Apache v2 License.
 */

package oneandone_cloudserver_api

import (
	"github.com/docker/machine/log"
	"net/http"
)

type Log struct {
	withId
	StartDate string `json:"start_date"`
	EndDate  string `json:"end_date"`
	Duration int    `json:"duration"`
	Status   Status
	Action   string `json:"action"`
	withType
	Resource     LogDetails
	User         LogDetails
	CloudPanelId string `json:"cloudpanel_id"`
}

type LogDetails struct {
	Id   string `json:"id"`
	Name string `json:"name"`
}

// GET /logs
func (api *API) GetLogs(period string) ([]Log, error) {
	log.Debug("requesting information about logs")
	result := []Log{}
	err := api.Client.Get(createUrl(api, "logs")+"?period="+period, &result, http.StatusOK)
	if err != nil {
		return nil, err
	}
	return result, nil
}

// GET /logs/{id}
func (api *API) GetLog(id string) (*Log, error) {
	log.Debugf("requesting information about log: '%s'", id)
	result := new(Log)
	err := api.Client.Get(createUrl(api, "logs", id), &result, http.StatusOK)
	if err != nil {
		return nil, err
	}
	return result, nil
}
<|endoftext|>