<commit_before>// Oh the stack trace scanning!
// The density of comments in this file is evidence that
// the code doesn't exactly explain itself. Tread with care...
package convey

import (
	"fmt"
	"strings"

	"github.com/jtolds/gls"
	"github.com/smartystreets/goconvey/convey/reporting"
)

const (
	missingGoTest = `Top-level calls to Convey(...) need a reference to the *testing.T.
		Hint: Convey("description here", t, func() { /* notice that the second argument was the *testing.T (t)! */ }) `
	extraGoTest = `Only the top-level call to Convey(...) needs a reference to the *testing.T.`

	failureHalt = "___FAILURE_HALT___"

	nodeKey = "node"
)

///////////////////////////////// Stack Context /////////////////////////////////

func getCurrentContext() *context {
	ctx, ok := ctxMgr.GetValue(nodeKey)
	if ok {
		return ctx.(*context)
	}
	return nil
}

func mustGetCurrentContext() *context {
	ctx := getCurrentContext()
	if ctx == nil {
		panic("cannot perform operation outside of a convey context")
	}
	return ctx
}

//////////////////////////////////// Context ////////////////////////////////////

// context magically handles all coordination of Convey's and So assertions.
//
// It is tracked on the stack as goroutine-local-storage with the gls package,
// or explicitly if the user decides to call convey like:
//
//   Convey(..., func(c C) {
//     c.So(...)
//   })
//
// This implements the `C` interface.
type context struct {
	reporter reporting.Reporter
	test     t

	children map[string]*context

	resets []func()

	executedOnce   bool
	expectChildRun *bool
	complete       bool

	focus       bool
	failureMode FailureMode
}

// rootConvey is the main entry point to a test suite. This is called when
// there's no context in the stack already, and items must contain a `t` object,
// or this panics.
func rootConvey(items ...interface{}) {
	entry := discover(items)

	if entry.Test == nil {
		panic(missingGoTest)
	}

	expectChildRun := true
	ctx := &context{
		test:     entry.Test,
		reporter: buildReporter(),

		children: make(map[string]*context),

		expectChildRun: &expectChildRun,

		focus:       entry.Focus,
		failureMode: defaultFailureMode.combine(entry.FailMode),
	}
	ctxMgr.SetValues(gls.Values{nodeKey: ctx}, func() {
		ctx.reporter.BeginStory(reporting.NewStoryReport(ctx.test))
		defer ctx.reporter.EndStory()

		for ctx.shouldVisit() {
			ctx.conveyInner(entry.Situation, entry.Func)
			expectChildRun = true
		}
	})
}

//////////////////////////////////// Methods ////////////////////////////////////

func (ctx *context) SkipConvey(items ...interface{}) {
	ctx.Convey(items, skipConvey)
}

func (ctx *context) FocusConvey(items ...interface{}) {
	ctx.Convey(items, focusConvey)
}

func (ctx *context) Convey(items ...interface{}) {
	entry := discover(items)

	// we're a branch, or leaf (on the wind)
	if entry.Test != nil {
		panic(extraGoTest)
	}
	if ctx.focus && !entry.Focus {
		return
	}

	var inner_ctx *context
	if ctx.executedOnce {
		var ok bool
		inner_ctx, ok = ctx.children[entry.Situation]
		if !ok {
			panic("different set of Convey statements on subsequent pass!")
		}
	} else {
		inner_ctx = &context{
			test:     ctx.test,
			reporter: ctx.reporter,

			children: make(map[string]*context),

			expectChildRun: ctx.expectChildRun,

			focus:       entry.Focus,
			failureMode: ctx.failureMode.combine(entry.FailMode),
		}
		ctx.children[entry.Situation] = inner_ctx
	}

	if inner_ctx.shouldVisit() {
		ctxMgr.SetValues(gls.Values{nodeKey: inner_ctx}, func() {
			inner_ctx.conveyInner(entry.Situation, entry.Func)
		})
	}
}

func (ctx *context) SkipSo(stuff ...interface{}) {
	ctx.assertionReport(reporting.NewSkipReport())
}

func (ctx *context) So(actual interface{}, assert assertion, expected ...interface{}) {
	if result := assert(actual, expected...); result == assertionSuccess {
		ctx.assertionReport(reporting.NewSuccessReport())
	} else {
		ctx.assertionReport(reporting.NewFailureReport(result))
	}
}

func (ctx *context) Reset(action func()) {
	/* TODO: Failure mode configuration */
	ctx.resets = append(ctx.resets, action)
}

func (ctx *context) Print(items ...interface{}) (int, error) {
	fmt.Fprint(ctx.reporter, items...)
	return fmt.Print(items...)
}

func (ctx *context) Println(items ...interface{}) (int, error) {
	fmt.Fprintln(ctx.reporter, items...)
	return fmt.Println(items...)
}

func (ctx *context) Printf(format string, items ...interface{}) (int, error) {
	fmt.Fprintf(ctx.reporter, format, items...)
	return fmt.Printf(format, items...)
}

//////////////////////////////////// Private ////////////////////////////////////

// shouldVisit returns true iff we should traverse down into a Convey. Note
// that just because we don't traverse a Convey this time, doesn't mean that
// we may not traverse it on a subsequent pass.
func (c *context) shouldVisit() bool {
	return !c.complete && *c.expectChildRun
}

// conveyInner is the function which actually executes the user's anonymous test
// function body. At this point, Convey or RootConvey has decided that this
// function should actually run.
func (ctx *context) conveyInner(situation string, f func(C)) {
	// Record/Reset state for next time.
	defer func() {
		ctx.executedOnce = true

		// This is only needed at the leaves, but there's no harm in also setting it
		// when returning from branch Convey's
		*ctx.expectChildRun = false
	}()

	// Set up+tear down our scope for the reporter
	ctx.reporter.Enter(reporting.NewScopeReport(situation))
	defer ctx.reporter.Exit()

	// Resets are registered as the `f` function executes, so nil them here.
	// All resets are run in registration order (FIFO).
	// TODO(riannucci): Should these be run in LIFO?
	ctx.resets = []func(){}
	defer func() {
		for _, r := range ctx.resets {
			// TODO(riannucci): handle panics?
			r()
		}
	}()

	// Recover from any panics in f, and assign the `complete` status for this
	// node of the tree.
	defer func() {
		if problem := recover(); problem != nil {
			if strings.HasPrefix(fmt.Sprintf("%v", problem), extraGoTest) {
				panic(problem)
			}
			if problem != failureHalt {
				ctx.reporter.Report(reporting.NewErrorReport(problem))
			}
			ctx.complete = true
		} else {
			ctx.complete = true
			for _, child := range ctx.children {
				if !child.complete {
					ctx.complete = false
					return
				}
			}
		}
	}()

	if f == nil {
		// if f is nil, this was either a Convey(..., nil), or a SkipConvey
		ctx.reporter.Report(reporting.NewSkipReport())
	} else {
		f(ctx)
	}
}

// assertionReport is a helper for So and SkipSo which makes the report and
// then possibly panics, depending on the current context's failureMode.
func (ctx *context) assertionReport(r *reporting.AssertionResult) {
	ctx.reporter.Report(r)
	if r.Failure != "" && ctx.failureMode == FailureHalts {
		panic(failureHalt)
	}
}
<commit_msg>Remove test from context.<commit_after>// Oh the stack trace scanning!
// The density of comments in this file is evidence that
// the code doesn't exactly explain itself. Tread with care...
package convey

import (
	"fmt"
	"strings"

	"github.com/jtolds/gls"
	"github.com/smartystreets/goconvey/convey/reporting"
)

const (
	missingGoTest = `Top-level calls to Convey(...) need a reference to the *testing.T.
		Hint: Convey("description here", t, func() { /* notice that the second argument was the *testing.T (t)! */ }) `
	extraGoTest = `Only the top-level call to Convey(...) needs a reference to the *testing.T.`

	failureHalt = "___FAILURE_HALT___"

	nodeKey = "node"
)

///////////////////////////////// Stack Context /////////////////////////////////

func getCurrentContext() *context {
	ctx, ok := ctxMgr.GetValue(nodeKey)
	if ok {
		return ctx.(*context)
	}
	return nil
}

func mustGetCurrentContext() *context {
	ctx := getCurrentContext()
	if ctx == nil {
		panic("cannot perform operation outside of a convey context")
	}
	return ctx
}

//////////////////////////////////// Context ////////////////////////////////////

// context magically handles all coordination of Convey's and So assertions.
//
// It is tracked on the stack as goroutine-local-storage with the gls package,
// or explicitly if the user decides to call convey like:
//
//   Convey(..., func(c C) {
//     c.So(...)
//   })
//
// This implements the `C` interface.
type context struct {
	reporter reporting.Reporter

	children map[string]*context

	resets []func()

	executedOnce   bool
	expectChildRun *bool
	complete       bool

	focus       bool
	failureMode FailureMode
}

// rootConvey is the main entry point to a test suite. This is called when
// there's no context in the stack already, and items must contain a `t` object,
// or this panics.
func rootConvey(items ...interface{}) {
	entry := discover(items)

	if entry.Test == nil {
		panic(missingGoTest)
	}

	expectChildRun := true
	ctx := &context{
		reporter: buildReporter(),

		children: make(map[string]*context),

		expectChildRun: &expectChildRun,

		focus:       entry.Focus,
		failureMode: defaultFailureMode.combine(entry.FailMode),
	}
	ctxMgr.SetValues(gls.Values{nodeKey: ctx}, func() {
		ctx.reporter.BeginStory(reporting.NewStoryReport(entry.Test))
		defer ctx.reporter.EndStory()

		for ctx.shouldVisit() {
			ctx.conveyInner(entry.Situation, entry.Func)
			expectChildRun = true
		}
	})
}

//////////////////////////////////// Methods ////////////////////////////////////

func (ctx *context) SkipConvey(items ...interface{}) {
	ctx.Convey(items, skipConvey)
}

func (ctx *context) FocusConvey(items ...interface{}) {
	ctx.Convey(items, focusConvey)
}

func (ctx *context) Convey(items ...interface{}) {
	entry := discover(items)

	// we're a branch, or leaf (on the wind)
	if entry.Test != nil {
		panic(extraGoTest)
	}
	if ctx.focus && !entry.Focus {
		return
	}

	var inner_ctx *context
	if ctx.executedOnce {
		var ok bool
		inner_ctx, ok = ctx.children[entry.Situation]
		if !ok {
			panic("different set of Convey statements on subsequent pass!")
		}
	} else {
		inner_ctx = &context{
			reporter: ctx.reporter,

			children: make(map[string]*context),

			expectChildRun: ctx.expectChildRun,

			focus:       entry.Focus,
			failureMode: ctx.failureMode.combine(entry.FailMode),
		}
		ctx.children[entry.Situation] = inner_ctx
	}

	if inner_ctx.shouldVisit() {
		ctxMgr.SetValues(gls.Values{nodeKey: inner_ctx}, func() {
			inner_ctx.conveyInner(entry.Situation, entry.Func)
		})
	}
}

func (ctx *context) SkipSo(stuff ...interface{}) {
	ctx.assertionReport(reporting.NewSkipReport())
}

func (ctx *context) So(actual interface{}, assert assertion, expected ...interface{}) {
	if result := assert(actual, expected...); result == assertionSuccess {
		ctx.assertionReport(reporting.NewSuccessReport())
	} else {
		ctx.assertionReport(reporting.NewFailureReport(result))
	}
}

func (ctx *context) Reset(action func()) {
	/* TODO: Failure mode configuration */
	ctx.resets = append(ctx.resets, action)
}

func (ctx *context) Print(items ...interface{}) (int, error) {
	fmt.Fprint(ctx.reporter, items...)
	return fmt.Print(items...)
}

func (ctx *context) Println(items ...interface{}) (int, error) {
	fmt.Fprintln(ctx.reporter, items...)
	return fmt.Println(items...)
}

func (ctx *context) Printf(format string, items ...interface{}) (int, error) {
	fmt.Fprintf(ctx.reporter, format, items...)
	return fmt.Printf(format, items...)
}

//////////////////////////////////// Private ////////////////////////////////////

// shouldVisit returns true iff we should traverse down into a Convey. Note
// that just because we don't traverse a Convey this time, doesn't mean that
// we may not traverse it on a subsequent pass.
func (c *context) shouldVisit() bool {
	return !c.complete && *c.expectChildRun
}

// conveyInner is the function which actually executes the user's anonymous test
// function body. At this point, Convey or RootConvey has decided that this
// function should actually run.
func (ctx *context) conveyInner(situation string, f func(C)) {
	// Record/Reset state for next time.
	defer func() {
		ctx.executedOnce = true

		// This is only needed at the leaves, but there's no harm in also setting it
		// when returning from branch Convey's
		*ctx.expectChildRun = false
	}()

	// Set up+tear down our scope for the reporter
	ctx.reporter.Enter(reporting.NewScopeReport(situation))
	defer ctx.reporter.Exit()

	// Resets are registered as the `f` function executes, so nil them here.
	// All resets are run in registration order (FIFO).
	// TODO(riannucci): Should these be run in LIFO?
	ctx.resets = []func(){}
	defer func() {
		for _, r := range ctx.resets {
			// TODO(riannucci): handle panics?
			r()
		}
	}()

	// Recover from any panics in f, and assign the `complete` status for this
	// node of the tree.
	defer func() {
		if problem := recover(); problem != nil {
			if strings.HasPrefix(fmt.Sprintf("%v", problem), extraGoTest) {
				panic(problem)
			}
			if problem != failureHalt {
				ctx.reporter.Report(reporting.NewErrorReport(problem))
			}
			ctx.complete = true
		} else {
			ctx.complete = true
			for _, child := range ctx.children {
				if !child.complete {
					ctx.complete = false
					return
				}
			}
		}
	}()

	if f == nil {
		// if f is nil, this was either a Convey(..., nil), or a SkipConvey
		ctx.reporter.Report(reporting.NewSkipReport())
	} else {
		f(ctx)
	}
}

// assertionReport is a helper for So and SkipSo which makes the report and
// then possibly panics, depending on the current context's failureMode.
func (ctx *context) assertionReport(r *reporting.AssertionResult) {
	ctx.reporter.Report(r)
	if r.Failure != "" && ctx.failureMode == FailureHalts {
		panic(failureHalt)
	}
}
<|endoftext|>


<commit_before>// Copyright (c) 2003-2005 Maxim Sobolev. All rights reserved.
// Copyright (c) 2006-2014 Sippy Software, Inc. All rights reserved.
// Copyright (c) 2016 Andriy Pylypenko. All rights reserved.
//
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification,
// are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this
// list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation and/or
// other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
// ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
package sippy

import (
    "fmt"
    "net"
    "strings"
    "time"

    "sippy/conf"
    "sippy/math"
    "sippy/net"
    "sippy/time"
    "sippy/types"
    "sippy/utils"
)

const (
    _RTPPLWorker_MAX_RETRIES = 3
)

type rtpp_req_stream struct {
    command         string
    result_callback func(string)
}

type _RTPPLWorker struct {
    userv           *Rtp_proxy_client_stream
    shutdown_chan   chan int
}

func newRTPPLWorker(userv *Rtp_proxy_client_stream) *_RTPPLWorker {
    self := &_RTPPLWorker{
        userv           : userv,
        shutdown_chan   : make(chan int, 1),
    }
    go self.run()
    return self
}

func (self *_RTPPLWorker) send_raw(command string, stime *sippy_time.MonoTime) (string, time.Duration, error) {
    //print "%s.send_raw(%s)" % (id(self), command)
    if stime == nil {
        stime, _ = sippy_time.NewMonoTime()
    }
    var err error
    var n int
    retries := 0
    rval := ""
    buf := make([]byte, 1024)
    var s net.Conn
    for {
        if retries > _RTPPLWorker_MAX_RETRIES {
            return "", 0, fmt.Errorf("Error sending to the rtpproxy on " + self.userv._address.String() + ": " + err.Error())
        }
        retries++
        if s != nil {
            s.Close()
        }
        s, err = net.Dial(self.userv._address.Network(), self.userv._address.String())
        if err != nil {
            time.Sleep(100 * time.Millisecond)
            continue
        }
        _, err = s.Write([]byte(command))
        if err != nil {
            time.Sleep(100 * time.Millisecond)
            continue
        }
        n, err = s.Read(buf)
        if err != nil {
            time.Sleep(100 * time.Millisecond)
            continue
        }
        rval = strings.TrimSpace(string(buf[:n]))
        break
    }
    s.Close()
    rtpc_delay, _ := stime.OffsetFromNow()
    return rval, rtpc_delay, nil
}

func (self *_RTPPLWorker) run() {
    for {
        req := <-self.userv.wi
        if req == nil {
            // Shutdown request, relay it further
            self.userv.wi <- nil
            break
        }
        //command, result_callback, callback_parameters = wi
        data, rtpc_delay, err := self.send_raw(req.command, nil)
        if err != nil {
            self.userv.global_config.ErrorLogger().Debug("Error communicating the rtpproxy: " + err.Error())
            data, rtpc_delay = "", -1
        }
        if len(data) == 0 {
            rtpc_delay = -1
        }
        if req.result_callback != nil {
            sippy_utils.SafeCall(func() { req.result_callback(data) }, nil/*lock*/, self.userv.global_config.ErrorLogger())
        }
        if rtpc_delay != -1 {
            self.userv.register_delay(rtpc_delay)
        }
    }
    self.shutdown_chan <- 1
    self.userv = nil
}

type Rtp_proxy_client_stream struct {
    owner       sippy_types.RtpProxyClient
    _address    net.Addr
    nworkers    int
    workers     []*_RTPPLWorker
    delay_flt   sippy_math.RecFilter
    _is_local    bool
    wi          chan *rtpp_req_stream
    global_config sippy_conf.Config
}

func newRtp_proxy_client_stream(owner sippy_types.RtpProxyClient, global_config sippy_conf.Config, address net.Addr, bind_address *sippy_net.HostPort) (rtp_proxy_transport, error) {
    var err error
    if address == nil {
        address, err = net.ResolveUnixAddr("unix", "/var/run/rtpproxy.sock")
        if err != nil {
            return nil, err
        }
    }
    nworkers := 4
    if owner.GetOpts().GetNWorkers() != nil {
        nworkers = *owner.GetOpts().GetNWorkers()
    }
    self := &Rtp_proxy_client_stream{
        owner       : owner,
        _address    : address,
        nworkers    : nworkers,
        workers     : make([]*_RTPPLWorker, nworkers),
        delay_flt   : sippy_math.NewRecFilter(0.95, 0.25),
        wi          : make(chan *rtpp_req_stream, 1000),
        global_config : global_config,
    }
    if strings.HasPrefix(address.Network(), "unix") {
        self._is_local = true
    } else {
        self._is_local = false
    }
    //self.wi_available = Condition()
    //self.wi = []
    for i := 0; i < self.nworkers; i++ {
        self.workers[i] = newRTPPLWorker(self)
    }
    return self, nil
}

func (self *Rtp_proxy_client_stream) is_local() bool {
    return self._is_local
}

func (self *Rtp_proxy_client_stream) address() net.Addr {
    return self._address
}

func (self *Rtp_proxy_client_stream) send_command(command string, result_callback func(string)) {
    if command[len(command)-1] != '\n' {
        command += "\n"
    }
    self.wi <- &rtpp_req_stream{ command, result_callback }
}

func (self *Rtp_proxy_client_stream) reconnect(address net.Addr, bind_addr *sippy_net.HostPort) {
    self.shutdown()
    self._address = address
    self.workers = make([]*_RTPPLWorker, self.nworkers)
    for i := 0; i < self.nworkers; i++ {
        self.workers[i] = newRTPPLWorker(self)
    }
    self.delay_flt = sippy_math.NewRecFilter(0.95, 0.25)
}

func (self *Rtp_proxy_client_stream) shutdown() {
    self.wi <- nil
    for _, rworker := range self.workers {
        <-rworker.shutdown_chan
    }
    self.workers = nil
}

func (self *Rtp_proxy_client_stream) register_delay(rtpc_delay time.Duration) {
    self.delay_flt.Apply(rtpc_delay.Seconds())
}

func (self *Rtp_proxy_client_stream) get_rtpc_delay() float64 {
    return self.delay_flt.GetLastval()
}
/*
if __name__ == "__main__":
    from twisted.internet import reactor
    def display(*args):
        print args
        reactor.crash()
    r = Rtp_proxy_client_stream({"_sip_address":"1.2.3.4"})
    r.send_command("VF 123456", display, "abcd")
    reactor.run(installSignalHandlers = 1)
    r.shutdown()
*/
<commit_msg>Take away the shutdown request from the queue so the reconnect() can start a new set of workers.<commit_after>// Copyright (c) 2003-2005 Maxim Sobolev. All rights reserved.
// Copyright (c) 2006-2014 Sippy Software, Inc. All rights reserved.
// Copyright (c) 2016 Andriy Pylypenko. All rights reserved.
//
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification,
// are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this
// list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation and/or
// other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
// ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
package sippy

import (
    "fmt"
    "net"
    "strings"
    "time"

    "sippy/conf"
    "sippy/math"
    "sippy/net"
    "sippy/time"
    "sippy/types"
    "sippy/utils"
)

const (
    _RTPPLWorker_MAX_RETRIES = 3
)

type rtpp_req_stream struct {
    command         string
    result_callback func(string)
}

type _RTPPLWorker struct {
    userv           *Rtp_proxy_client_stream
    shutdown_chan   chan int
}

func newRTPPLWorker(userv *Rtp_proxy_client_stream) *_RTPPLWorker {
    self := &_RTPPLWorker{
        userv           : userv,
        shutdown_chan   : make(chan int, 1),
    }
    go self.run()
    return self
}

func (self *_RTPPLWorker) send_raw(command string, stime *sippy_time.MonoTime) (string, time.Duration, error) {
    //print "%s.send_raw(%s)" % (id(self), command)
    if stime == nil {
        stime, _ = sippy_time.NewMonoTime()
    }
    var err error
    var n int
    retries := 0
    rval := ""
    buf := make([]byte, 1024)
    var s net.Conn
    for {
        if retries > _RTPPLWorker_MAX_RETRIES {
            return "", 0, fmt.Errorf("Error sending to the rtpproxy on " + self.userv._address.String() + ": " + err.Error())
        }
        retries++
        if s != nil {
            s.Close()
        }
        s, err = net.Dial(self.userv._address.Network(), self.userv._address.String())
        if err != nil {
            time.Sleep(100 * time.Millisecond)
            continue
        }
        _, err = s.Write([]byte(command))
        if err != nil {
            time.Sleep(100 * time.Millisecond)
            continue
        }
        n, err = s.Read(buf)
        if err != nil {
            time.Sleep(100 * time.Millisecond)
            continue
        }
        rval = strings.TrimSpace(string(buf[:n]))
        break
    }
    s.Close()
    rtpc_delay, _ := stime.OffsetFromNow()
    return rval, rtpc_delay, nil
}

func (self *_RTPPLWorker) run() {
    for {
        req := <-self.userv.wi
        if req == nil {
            // Shutdown request, relay it further
            self.userv.wi <- nil
            break
        }
        //command, result_callback, callback_parameters = wi
        data, rtpc_delay, err := self.send_raw(req.command, nil)
        if err != nil {
            self.userv.global_config.ErrorLogger().Debug("Error communicating the rtpproxy: " + err.Error())
            data, rtpc_delay = "", -1
        }
        if len(data) == 0 {
            rtpc_delay = -1
        }
        if req.result_callback != nil {
            sippy_utils.SafeCall(func() { req.result_callback(data) }, nil/*lock*/, self.userv.global_config.ErrorLogger())
        }
        if rtpc_delay != -1 {
            self.userv.register_delay(rtpc_delay)
        }
    }
    self.shutdown_chan <- 1
    self.userv = nil
}

type Rtp_proxy_client_stream struct {
    owner       sippy_types.RtpProxyClient
    _address    net.Addr
    nworkers    int
    workers     []*_RTPPLWorker
    delay_flt   sippy_math.RecFilter
    _is_local    bool
    wi          chan *rtpp_req_stream
    global_config sippy_conf.Config
}

func newRtp_proxy_client_stream(owner sippy_types.RtpProxyClient, global_config sippy_conf.Config, address net.Addr, bind_address *sippy_net.HostPort) (rtp_proxy_transport, error) {
    var err error
    if address == nil {
        address, err = net.ResolveUnixAddr("unix", "/var/run/rtpproxy.sock")
        if err != nil {
            return nil, err
        }
    }
    nworkers := 4
    if owner.GetOpts().GetNWorkers() != nil {
        nworkers = *owner.GetOpts().GetNWorkers()
    }
    self := &Rtp_proxy_client_stream{
        owner       : owner,
        _address    : address,
        nworkers    : nworkers,
        workers     : make([]*_RTPPLWorker, nworkers),
        delay_flt   : sippy_math.NewRecFilter(0.95, 0.25),
        wi          : make(chan *rtpp_req_stream, 1000),
        global_config : global_config,
    }
    if strings.HasPrefix(address.Network(), "unix") {
        self._is_local = true
    } else {
        self._is_local = false
    }
    //self.wi_available = Condition()
    //self.wi = []
    for i := 0; i < self.nworkers; i++ {
        self.workers[i] = newRTPPLWorker(self)
    }
    return self, nil
}

func (self *Rtp_proxy_client_stream) is_local() bool {
    return self._is_local
}

func (self *Rtp_proxy_client_stream) address() net.Addr {
    return self._address
}

func (self *Rtp_proxy_client_stream) send_command(command string, result_callback func(string)) {
    if command[len(command)-1] != '\n' {
        command += "\n"
    }
    self.wi <- &rtpp_req_stream{ command, result_callback }
}

func (self *Rtp_proxy_client_stream) reconnect(address net.Addr, bind_addr *sippy_net.HostPort) {
    self.shutdown()
    self._address = address
    self.workers = make([]*_RTPPLWorker, self.nworkers)
    for i := 0; i < self.nworkers; i++ {
        self.workers[i] = newRTPPLWorker(self)
    }
    self.delay_flt = sippy_math.NewRecFilter(0.95, 0.25)
}

func (self *Rtp_proxy_client_stream) shutdown() {
    self.wi <- nil
    for _, rworker := range self.workers {
        <-rworker.shutdown_chan
    }
    self.workers = nil
    <-self.wi // take away the shutdown request
}

func (self *Rtp_proxy_client_stream) register_delay(rtpc_delay time.Duration) {
    self.delay_flt.Apply(rtpc_delay.Seconds())
}

func (self *Rtp_proxy_client_stream) get_rtpc_delay() float64 {
    return self.delay_flt.GetLastval()
}
/*
if __name__ == "__main__":
    from twisted.internet import reactor
    def display(*args):
        print args
        reactor.crash()
    r = Rtp_proxy_client_stream({"_sip_address":"1.2.3.4"})
    r.send_command("VF 123456", display, "abcd")
    reactor.run(installSignalHandlers = 1)
    r.shutdown()
*/
<|endoftext|>


<commit_before>package main

import (
	"bufio"
	"bytes"
	"fmt"
	"html/template"
	"io"
	"net/http"
	"os"
	"path"
	"regexp"
	"strconv"
	"strings"
	"time"

	"github.com/zaunerc/cntrinfod/consul"
	"github.com/zaunerc/cntrinfod/docker"
	"github.com/zaunerc/cntrinfod/system"

	auth "github.com/abbot/go-http-auth"
	"github.com/shurcooL/github_flavored_markdown"
	"github.com/shurcooL/github_flavored_markdown/gfmstyle"
	"github.com/urfave/cli"
)

type Page struct {
	Path     string
	Markdown []byte
	Html     []byte
}

type Log struct {
	Path       string
	ParentPath string
	Text       string
}

func convertMdToHtml(readme []byte) (*Page, error) {

	location := locateReadme()
	enrichedReadme := enrichMd(getReadmeAsMarkdown(location))

	readmeAsHtml := github_flavored_markdown.Markdown(enrichedReadme)
	return &Page{Path: location, Markdown: enrichedReadme, Html: readmeAsHtml}, nil
}

/**
 * Adds various informations to the README.md of the container.
 */
func enrichMd(readme []byte) []byte {

	appendixTemplate, error := template.ParseFiles(path.Join(staticDataDir, "appendix_template.md"))
	var appendixBuffer bytes.Buffer

	vars := map[string]interface{}{
		"ContainerHostname": system.FetchContainerHostname(),
		"HostHostname":      docker.FetchHostHostname(),
		"TcpSocketInfo":     system.FetchTcp46SocketInfo(),
		"UdpSocketInfo":     system.FetchUdp46SocketInfo(),
		"ProcessInfo":       system.FetchProcessInfo(),
		"ProcessTree":       system.FetchProcessTree(),
	}

	error = appendixTemplate.Execute(&appendixBuffer, vars)

	if error != nil {
		fmt.Printf("Error while processing template: >%s<.", error)
		return readme
	}

	// Make sure that there is at least one newline before our heading
	readme = append(readme, "\n"...)
	return append(readme, appendixBuffer.Bytes()...)
}

// Use getReadmeAsMarkdown
// `/var/log/AdminServer` - sshd log file
func parseLogFilePathsFromMd(pathToFile string) (*[]string, error) {

	var foundPath []string

	pageAsMarkdown, error := os.Open(pathToFile)

	if error != nil {
		return nil, error
	}

	matcher := regexp.MustCompile("`(.*)`.*-.*log.*")
	scanner := bufio.NewScanner(pageAsMarkdown)
	for scanner.Scan() {
		line := scanner.Text()
		match := matcher.FindStringSubmatch(line)
		if len(match) > 0 {
			foundPath = append(foundPath, match[1])
			fmt.Printf("Found path >%s< in file >%s<.\n", match[1], pathToFile)
		}
	}

	fmt.Printf("Found %d paths in file >%s<\n", len(foundPath), pathToFile)
	return &foundPath, nil
}

func readLogFiles(pathToLogFiles *[]string) (*[]Log, error) {

	var logs []Log

	for _, pathToLogFile := range *pathToLogFiles {
		file, err := os.Open(pathToLogFile)

		if err != nil {
			fmt.Printf("Error while reading log file >%s<: >%s<\n", pathToLogFile, err)
		} else {
			fmt.Printf("Successfully read log file >%s<.\n", pathToLogFile)
		}

		defer file.Close()

		var lines []string
		scanner := bufio.NewScanner(file)
		for scanner.Scan() {
			lines = append(lines, scanner.Text())
		}

		log := Log{Path: pathToLogFile, ParentPath: pathToLogFile, Text: strings.Join(lines, "\n")}
		logs = append(logs, log)
	}

	return &logs, nil
}

func init() {
	// By default logger is set to write to stderr device.
	//log.SetOutput(os.Stdout)
}

var staticDataDir string

func main() {

	var httpPort int
	var htpasswd string
	var consulUrl string

	app := cli.NewApp()

	app.Flags = []cli.Flag{
		cli.IntFlag{
			Name:        "httpPort, p",
			Value:       2020,
			Usage:       "Listen on port `PORT` for HTTP connections",
			Destination: &httpPort,
		},
		cli.StringFlag{
			Name: "htpasswd, s",
			//Value:       "/usr/local/etc/cntrinfod/htpasswd",
			Usage:       "Use htpasswd `FILE`. Enables user authentication.",
			Destination: &htpasswd,
		},
		cli.StringFlag{
			Name:        "consulUrl, c",
			Usage:       "Register container with consul at `URL`. Enables consul registration. E.g. \"registry.mynet.root.local:8500\".",
			Destination: &consulUrl,
		},
		cli.StringFlag{
			Name:        "staticDataDir, d",
			Value:       "/usr/local/share/cntrinfod/",
			Usage:       "Directory containing static web site files.",
			Destination: &staticDataDir,
		},
	}

	app.Email = "christoph.zauner@NLLK.net"
	app.Author = "Christoph Zauner"
	app.Version = "0.2.3"
	// cntrinfod, cntinfod
	app.Usage = "Container Info Daemon: HTTP daemon which exposes and augments the containers REAMDE.md"

	app.Action = func(c *cli.Context) error {

		if consulUrl != "" {
			consul.ScheduleRegistration(consulUrl, httpPort)
		}

		fmt.Printf("Serving static files from >%s<\n", getAbsolutePath(staticDataDir))
		fmt.Printf("Starting HTTP daemon on port %d...\n", httpPort)

		http.HandleFunc("/", protect(handler, htpasswd))
		http.HandleFunc("/log", protect(logHandler, htpasswd))
		http.HandleFunc("/hostinfo", protect(hostInfoHandler, htpasswd))
		http.HandleFunc("/markdown", protect(markdownHandler, htpasswd))

		// Serve the "/assets/gfm.css" file.
		http.Handle("/assets/", http.StripPrefix("/assets", http.FileServer(gfmstyle.Assets)))

		http.ListenAndServe(":"+strconv.Itoa(httpPort), nil)

		return nil
	}

	app.Run(os.Args)
}

func protect(handlerFunc http.HandlerFunc, htpasswdPath string) http.HandlerFunc {

	if htpasswdPath != "" {
		// read from htpasswd file
		htpasswd := auth.HtpasswdFileProvider(htpasswdPath)
		authenticator := auth.NewBasicAuthenticator("cntrinfod htpasswd realm", htpasswd)
		return auth.JustCheck(authenticator, handlerFunc)
	} else {
		return func(w http.ResponseWriter, r *http.Request) {
			handlerFunc(w, r)
		}
	}
}

func locateReadme() string {

	var possibleLocations [2]string = [2]string{"/README.md",
		"testdata/README.md"}

	var fd *os.File
	defer fd.Close()
	var err error

	var location string
	for _, possibleLocation := range possibleLocations {
		fd, err = os.Open(possibleLocation)
		if err == nil {
			fmt.Printf("Found README.md file: >%s<\n", possibleLocation)
			location = possibleLocation
			break
		} else {
			fmt.Printf("Error when looking for README.md at >%s<: >%s<\n", possibleLocation, err)
		}
	}

	if err != nil {
		fmt.Printf("Could not find README.md file.\n")
		os.Exit(-1)
	}

	return location
}

func getReadmeAsMarkdown(path string) []byte {

	var fd *os.File
	defer fd.Close()
	var err error

	fd, err = os.Open(path)
	if err != nil {
		fmt.Printf("Error while trying to open REAMDE.md file: %s\n", err)
		os.Exit(-1)
	}

	var readme []byte
	scanner := bufio.NewScanner(fd)
	for scanner.Scan() {
		readme = append(readme, scanner.Bytes()...)
		readme = append(readme, "\n"...)
	}

	return readme
}

func handler(w http.ResponseWriter, r *http.Request) {
	page, _ := convertMdToHtml(getReadmeAsMarkdown(locateReadme()))

	io.WriteString(w, `<meta charset="utf-8"><link href="/assets/gfm.css" media="all" rel="stylesheet" type="text/css" /><link href="//cdnjs.cloudflare.com/ajax/libs/octicons/2.1.2/octicons.css" media="all" rel="stylesheet" type="text/css" />
`)
	w.Write(page.Html)
	io.WriteString(w, `</article></body></html>`)
}

func logHandler(w http.ResponseWriter, r *http.Request) {

	pathToLogFiles, _ := parseLogFilePathsFromMd(locateReadme())
	logs, _ := readLogFiles(pathToLogFiles)

	t, error := template.ParseFiles(path.Join(staticDataDir, "log.html"))

	currentDateAndTime := time.Now().Format("2006-01-02 15:04:05")

	vars := map[string]interface{}{
		"Logs":               logs,
		"CurrentDateAndTime": currentDateAndTime,
	}

	error = t.Execute(w, vars)

	if error != nil {
		fmt.Printf("Error while processing template: >%s<.", error)
	}
}

func hostInfoHandler(w http.ResponseWriter, r *http.Request) {
	t, error := template.ParseFiles(path.Join(staticDataDir, "hostinfo.html"))

	hostinfo := docker.FetchHostInfo()
	error = t.Execute(w, hostinfo)

	if error != nil {
		fmt.Printf("Error while processing template: >%s<.", error)
	}
}

func markdownHandler(w http.ResponseWriter, r *http.Request) {

	readmeAsMarkdown := getReadmeAsMarkdown(locateReadme())
	enrichedReadme := enrichMd(readmeAsMarkdown)

	t, error := template.ParseFiles(path.Join(staticDataDir, "markdown.html"))
	error = t.Execute(w, string(enrichedReadme))
	if error != nil {
		fmt.Printf("Error while processing template: >%s<.", error)
	}
}

func getAbsolutePath(name string) string {
	if path.IsAbs(name) {
		return name
	}
	wd, err := os.Getwd()

	if err != nil {
		panic(fmt.Sprintf("Error while trying to get current working dir: %s", err))
	}

	return path.Join(wd, name)
}
<commit_msg>Starting development iteration on 0.2.4-SNAPSHOT.<commit_after>package main

import (
	"bufio"
	"bytes"
	"fmt"
	"html/template"
	"io"
	"net/http"
	"os"
	"path"
	"regexp"
	"strconv"
	"strings"
	"time"

	"github.com/zaunerc/cntrinfod/consul"
	"github.com/zaunerc/cntrinfod/docker"
	"github.com/zaunerc/cntrinfod/system"

	auth "github.com/abbot/go-http-auth"
	"github.com/shurcooL/github_flavored_markdown"
	"github.com/shurcooL/github_flavored_markdown/gfmstyle"
	"github.com/urfave/cli"
)

type Page struct {
	Path     string
	Markdown []byte
	Html     []byte
}

type Log struct {
	Path       string
	ParentPath string
	Text       string
}

func convertMdToHtml(readme []byte) (*Page, error) {

	location := locateReadme()
	enrichedReadme := enrichMd(getReadmeAsMarkdown(location))

	readmeAsHtml := github_flavored_markdown.Markdown(enrichedReadme)
	return &Page{Path: location, Markdown: enrichedReadme, Html: readmeAsHtml}, nil
}

/**
 * Adds various informations to the README.md of the container.
 */
func enrichMd(readme []byte) []byte {

	appendixTemplate, error := template.ParseFiles(path.Join(staticDataDir, "appendix_template.md"))
	var appendixBuffer bytes.Buffer

	vars := map[string]interface{}{
		"ContainerHostname": system.FetchContainerHostname(),
		"HostHostname":      docker.FetchHostHostname(),
		"TcpSocketInfo":     system.FetchTcp46SocketInfo(),
		"UdpSocketInfo":     system.FetchUdp46SocketInfo(),
		"ProcessInfo":       system.FetchProcessInfo(),
		"ProcessTree":       system.FetchProcessTree(),
	}

	error = appendixTemplate.Execute(&appendixBuffer, vars)

	if error != nil {
		fmt.Printf("Error while processing template: >%s<.", error)
		return readme
	}

	// Make sure that there is at least one newline before our heading
	readme = append(readme, "\n"...)
	return append(readme, appendixBuffer.Bytes()...)
}

// Use getReadmeAsMarkdown
// `/var/log/AdminServer` - sshd log file
func parseLogFilePathsFromMd(pathToFile string) (*[]string, error) {

	var foundPath []string

	pageAsMarkdown, error := os.Open(pathToFile)

	if error != nil {
		return nil, error
	}

	matcher := regexp.MustCompile("`(.*)`.*-.*log.*")
	scanner := bufio.NewScanner(pageAsMarkdown)
	for scanner.Scan() {
		line := scanner.Text()
		match := matcher.FindStringSubmatch(line)
		if len(match) > 0 {
			foundPath = append(foundPath, match[1])
			fmt.Printf("Found path >%s< in file >%s<.\n", match[1], pathToFile)
		}
	}

	fmt.Printf("Found %d paths in file >%s<\n", len(foundPath), pathToFile)
	return &foundPath, nil
}

func readLogFiles(pathToLogFiles *[]string) (*[]Log, error) {

	var logs []Log

	for _, pathToLogFile := range *pathToLogFiles {
		file, err := os.Open(pathToLogFile)

		if err != nil {
			fmt.Printf("Error while reading log file >%s<: >%s<\n", pathToLogFile, err)
		} else {
			fmt.Printf("Successfully read log file >%s<.\n", pathToLogFile)
		}

		defer file.Close()

		var lines []string
		scanner := bufio.NewScanner(file)
		for scanner.Scan() {
			lines = append(lines, scanner.Text())
		}

		log := Log{Path: pathToLogFile, ParentPath: pathToLogFile, Text: strings.Join(lines, "\n")}
		logs = append(logs, log)
	}

	return &logs, nil
}

func init() {
	// By default logger is set to write to stderr device.
	//log.SetOutput(os.Stdout)
}

var staticDataDir string

func main() {

	var httpPort int
	var htpasswd string
	var consulUrl string

	app := cli.NewApp()

	app.Flags = []cli.Flag{
		cli.IntFlag{
			Name:        "httpPort, p",
			Value:       2020,
			Usage:       "Listen on port `PORT` for HTTP connections",
			Destination: &httpPort,
		},
		cli.StringFlag{
			Name: "htpasswd, s",
			//Value:       "/usr/local/etc/cntrinfod/htpasswd",
			Usage:       "Use htpasswd `FILE`. Enables user authentication.",
			Destination: &htpasswd,
		},
		cli.StringFlag{
			Name:        "consulUrl, c",
			Usage:       "Register container with consul at `URL`. Enables consul registration. E.g. \"registry.mynet.root.local:8500\".",
			Destination: &consulUrl,
		},
		cli.StringFlag{
			Name:        "staticDataDir, d",
			Value:       "/usr/local/share/cntrinfod/",
			Usage:       "Directory containing static web site files.",
			Destination: &staticDataDir,
		},
	}

	app.Email = "christoph.zauner@NLLK.net"
	app.Author = "Christoph Zauner"
	app.Version = "0.2.4-SNAPSHOT"
	// cntrinfod, cntinfod
	app.Usage = "Container Info Daemon: HTTP daemon which exposes and augments the containers REAMDE.md"

	app.Action = func(c *cli.Context) error {

		if consulUrl != "" {
			consul.ScheduleRegistration(consulUrl, httpPort)
		}

		fmt.Printf("Serving static files from >%s<\n", getAbsolutePath(staticDataDir))
		fmt.Printf("Starting HTTP daemon on port %d...\n", httpPort)

		http.HandleFunc("/", protect(handler, htpasswd))
		http.HandleFunc("/log", protect(logHandler, htpasswd))
		http.HandleFunc("/hostinfo", protect(hostInfoHandler, htpasswd))
		http.HandleFunc("/markdown", protect(markdownHandler, htpasswd))

		// Serve the "/assets/gfm.css" file.
		http.Handle("/assets/", http.StripPrefix("/assets", http.FileServer(gfmstyle.Assets)))

		http.ListenAndServe(":"+strconv.Itoa(httpPort), nil)

		return nil
	}

	app.Run(os.Args)
}

func protect(handlerFunc http.HandlerFunc, htpasswdPath string) http.HandlerFunc {

	if htpasswdPath != "" {
		// read from htpasswd file
		htpasswd := auth.HtpasswdFileProvider(htpasswdPath)
		authenticator := auth.NewBasicAuthenticator("cntrinfod htpasswd realm", htpasswd)
		return auth.JustCheck(authenticator, handlerFunc)
	} else {
		return func(w http.ResponseWriter, r *http.Request) {
			handlerFunc(w, r)
		}
	}
}

func locateReadme() string {

	var possibleLocations [2]string = [2]string{"/README.md",
		"testdata/README.md"}

	var fd *os.File
	defer fd.Close()
	var err error

	var location string
	for _, possibleLocation := range possibleLocations {
		fd, err = os.Open(possibleLocation)
		if err == nil {
			fmt.Printf("Found README.md file: >%s<\n", possibleLocation)
			location = possibleLocation
			break
		} else {
			fmt.Printf("Error when looking for README.md at >%s<: >%s<\n", possibleLocation, err)
		}
	}

	if err != nil {
		fmt.Printf("Could not find README.md file.\n")
		os.Exit(-1)
	}

	return location
}

func getReadmeAsMarkdown(path string) []byte {

	var fd *os.File
	defer fd.Close()
	var err error

	fd, err = os.Open(path)
	if err != nil {
		fmt.Printf("Error while trying to open REAMDE.md file: %s\n", err)
		os.Exit(-1)
	}

	var readme []byte
	scanner := bufio.NewScanner(fd)
	for scanner.Scan() {
		readme = append(readme, scanner.Bytes()...)
		readme = append(readme, "\n"...)
	}

	return readme
}

func handler(w http.ResponseWriter, r *http.Request) {
	page, _ := convertMdToHtml(getReadmeAsMarkdown(locateReadme()))

	io.WriteString(w, `<meta charset="utf-8"><link href="/assets/gfm.css" media="all" rel="stylesheet" type="text/css" /><link href="//cdnjs.cloudflare.com/ajax/libs/octicons/2.1.2/octicons.css" media="all" rel="stylesheet" type="text/css" />
`)
	w.Write(page.Html)
	io.WriteString(w, `</article></body></html>`)
}

func logHandler(w http.ResponseWriter, r *http.Request) {

	pathToLogFiles, _ := parseLogFilePathsFromMd(locateReadme())
	logs, _ := readLogFiles(pathToLogFiles)

	t, error := template.ParseFiles(path.Join(staticDataDir, "log.html"))

	currentDateAndTime := time.Now().Format("2006-01-02 15:04:05")

	vars := map[string]interface{}{
		"Logs":               logs,
		"CurrentDateAndTime": currentDateAndTime,
	}

	error = t.Execute(w, vars)

	if error != nil {
		fmt.Printf("Error while processing template: >%s<.", error)
	}
}

func hostInfoHandler(w http.ResponseWriter, r *http.Request) {
	t, error := template.ParseFiles(path.Join(staticDataDir, "hostinfo.html"))

	hostinfo := docker.FetchHostInfo()
	error = t.Execute(w, hostinfo)

	if error != nil {
		fmt.Printf("Error while processing template: >%s<.", error)
	}
}

func markdownHandler(w http.ResponseWriter, r *http.Request) {

	readmeAsMarkdown := getReadmeAsMarkdown(locateReadme())
	enrichedReadme := enrichMd(readmeAsMarkdown)

	t, error := template.ParseFiles(path.Join(staticDataDir, "markdown.html"))
	error = t.Execute(w, string(enrichedReadme))
	if error != nil {
		fmt.Printf("Error while processing template: >%s<.", error)
	}
}

func getAbsolutePath(name string) string {
	if path.IsAbs(name) {
		return name
	}
	wd, err := os.Getwd()

	if err != nil {
		panic(fmt.Sprintf("Error while trying to get current working dir: %s", err))
	}

	return path.Join(wd, name)
}
<|endoftext|>


<commit_before>package ring

import (
	"sort"

	"github.com/weaveworks/weave/common"
	"github.com/weaveworks/weave/net/address"
	"github.com/weaveworks/weave/router"
)

// Entry represents entries around the ring
type entry struct {
	Token   address.Address // The start of this range
	Peer    router.PeerName // Who owns this range
	Version uint32          // Version of this range
	Free    address.Offset  // Number of free IPs in this range
}

func (e *entry) Equal(e2 *entry) bool {
	return e.Token == e2.Token && e.Peer == e2.Peer &&
		e.Version == e2.Version
}

func (e *entry) update(peername router.PeerName, free address.Offset) {
	e.Peer = peername
	e.Version++
	e.Free = free
}

// For compatibility with sort.Interface
type entries []*entry

func (es entries) Len() int           { return len(es) }
func (es entries) Less(i, j int) bool { return es[i].Token < es[j].Token }
func (es entries) Swap(i, j int)      { panic("Should never be swapping entries!") }

func (es entries) entry(i int) *entry {
	i = i % len(es)
	if i < 0 {
		i += len(es)
	}
	return es[i]
}

func (es *entries) insert(e entry) {
	i := sort.Search(len(*es), func(j int) bool {
		return (*es)[j].Token >= e.Token
	})

	if i < len(*es) && (*es)[i].Token == e.Token {
		panic("Trying to insert an existing token!")
	}

	*es = append(*es, &entry{})
	copy((*es)[i+1:], (*es)[i:])
	(*es)[i] = &e
}

func (es entries) get(token address.Address) (*entry, bool) {
	i := sort.Search(len(es), func(j int) bool {
		return es[j].Token >= token
	})

	if i < len(es) && es[i].Token == token {
		return es[i], true
	}

	return nil, false
}

func (es *entries) remove(i int) {
	*es = (*es)[:i+copy((*es)[i:], (*es)[i+1:])]
}

// Is token between entries at i and j?
// NB i and j can overflow and will wrap
// NBB this function does not work very well if there is only one
//     token on the ring; luckily an accurate answer is not needed
//     by the call sites in this case.
func (es entries) between(token address.Address, i, j int) bool {
	common.Assert(i < j)

	first := es.entry(i)
	second := es.entry(j)

	switch {
	case first.Token == second.Token:
		// This implies there is only one token
		// on the ring (i < j and i.token == j.token)
		// In which case everything is between, expect
		// this one token
		return token != first.Token

	case first.Token < second.Token:
		return first.Token <= token && token < second.Token

	case second.Token < first.Token:
		return first.Token <= token || token < second.Token
	}

	panic("Should never get here - switch covers all possibilities.")
}
<commit_msg>remove dead code<commit_after>package ring

import (
	"sort"

	"github.com/weaveworks/weave/common"
	"github.com/weaveworks/weave/net/address"
	"github.com/weaveworks/weave/router"
)

// Entry represents entries around the ring
type entry struct {
	Token   address.Address // The start of this range
	Peer    router.PeerName // Who owns this range
	Version uint32          // Version of this range
	Free    address.Offset  // Number of free IPs in this range
}

func (e *entry) Equal(e2 *entry) bool {
	return e.Token == e2.Token && e.Peer == e2.Peer &&
		e.Version == e2.Version
}

func (e *entry) update(peername router.PeerName, free address.Offset) {
	e.Peer = peername
	e.Version++
	e.Free = free
}

// For compatibility with sort.Interface
type entries []*entry

func (es entries) Len() int           { return len(es) }
func (es entries) Less(i, j int) bool { return es[i].Token < es[j].Token }
func (es entries) Swap(i, j int)      { panic("Should never be swapping entries!") }

func (es entries) entry(i int) *entry {
	i = i % len(es)
	if i < 0 {
		i += len(es)
	}
	return es[i]
}

func (es *entries) insert(e entry) {
	i := sort.Search(len(*es), func(j int) bool {
		return (*es)[j].Token >= e.Token
	})

	if i < len(*es) && (*es)[i].Token == e.Token {
		panic("Trying to insert an existing token!")
	}

	*es = append(*es, &entry{})
	copy((*es)[i+1:], (*es)[i:])
	(*es)[i] = &e
}

func (es entries) get(token address.Address) (*entry, bool) {
	i := sort.Search(len(es), func(j int) bool {
		return es[j].Token >= token
	})

	if i < len(es) && es[i].Token == token {
		return es[i], true
	}

	return nil, false
}

// Is token between entries at i and j?
// NB i and j can overflow and will wrap
// NBB this function does not work very well if there is only one
//     token on the ring; luckily an accurate answer is not needed
//     by the call sites in this case.
func (es entries) between(token address.Address, i, j int) bool {
	common.Assert(i < j)

	first := es.entry(i)
	second := es.entry(j)

	switch {
	case first.Token == second.Token:
		// This implies there is only one token
		// on the ring (i < j and i.token == j.token)
		// In which case everything is between, expect
		// this one token
		return token != first.Token

	case first.Token < second.Token:
		return first.Token <= token && token < second.Token

	case second.Token < first.Token:
		return first.Token <= token || token < second.Token
	}

	panic("Should never get here - switch covers all possibilities.")
}
<|endoftext|>


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

package http

import (
	"bytes"
	"encoding/binary"
)

// The algorithm uses at most sniffLen bytes to make its decision.
const sniffLen = 512

// DetectContentType implements the algorithm described
// at http://mimesniff.spec.whatwg.org/ to determine the
// Content-Type of the given data.  It considers at most the
// first 512 bytes of data.  DetectContentType always returns
// a valid MIME type: if it cannot determine a more specific one, it
// returns "application/octet-stream".
func DetectContentType(data []byte) string {
	if len(data) > sniffLen {
		data = data[:sniffLen]
	}

	// Index of the first non-whitespace byte in data.
	firstNonWS := 0
	for ; firstNonWS < len(data) && isWS(data[firstNonWS]); firstNonWS++ {
	}

	for _, sig := range sniffSignatures {
		if ct := sig.match(data, firstNonWS); ct != "" {
			return ct
		}
	}

	return "application/octet-stream" // fallback
}

func isWS(b byte) bool {
	switch b {
	case '\t', '\n', '\x0c', '\r', ' ':
		return true
	}
	return false
}

type sniffSig interface {
	// match returns the MIME type of the data, or "" if unknown.
	match(data []byte, firstNonWS int) string
}

// Data matching the table in section 6.
var sniffSignatures = []sniffSig{
	htmlSig("<!DOCTYPE HTML"),
	htmlSig("<HTML"),
	htmlSig("<HEAD"),
	htmlSig("<SCRIPT"),
	htmlSig("<IFRAME"),
	htmlSig("<H1"),
	htmlSig("<DIV"),
	htmlSig("<FONT"),
	htmlSig("<TABLE"),
	htmlSig("<A"),
	htmlSig("<STYLE"),
	htmlSig("<TITLE"),
	htmlSig("<B"),
	htmlSig("<BODY"),
	htmlSig("<BR"),
	htmlSig("<P"),
	htmlSig("<!--"),

	&maskedSig{mask: []byte("\xFF\xFF\xFF\xFF\xFF"), pat: []byte("<?xml"), skipWS: true, ct: "text/xml; charset=utf-8"},

	&exactSig{[]byte("%PDF-"), "application/pdf"},
	&exactSig{[]byte("%!PS-Adobe-"), "application/postscript"},

	// UTF BOMs.
	&maskedSig{mask: []byte("\xFF\xFF\x00\x00"), pat: []byte("\xFE\xFF\x00\x00"), ct: "text/plain; charset=utf-16be"},
	&maskedSig{mask: []byte("\xFF\xFF\x00\x00"), pat: []byte("\xFF\xFE\x00\x00"), ct: "text/plain; charset=utf-16le"},
	&maskedSig{mask: []byte("\xFF\xFF\xFF\x00"), pat: []byte("\xEF\xBB\xBF\x00"), ct: "text/plain; charset=utf-8"},

	&exactSig{[]byte("GIF87a"), "image/gif"},
	&exactSig{[]byte("GIF89a"), "image/gif"},
	&exactSig{[]byte("\x89\x50\x4E\x47\x0D\x0A\x1A\x0A"), "image/png"},
	&exactSig{[]byte("\xFF\xD8\xFF"), "image/jpeg"},
	&exactSig{[]byte("BM"), "image/bmp"},
	&maskedSig{
		mask: []byte("\xFF\xFF\xFF\xFF\x00\x00\x00\x00\xFF\xFF\xFF\xFF\xFF\xFF"),
		pat:  []byte("RIFF\x00\x00\x00\x00WEBPVP"),
		ct:   "image/webp",
	},
	&exactSig{[]byte("\x00\x00\x01\x00"), "image/vnd.microsoft.icon"},
	&exactSig{[]byte("\x4F\x67\x67\x53\x00"), "application/ogg"},
	&maskedSig{
		mask: []byte("\xFF\xFF\xFF\xFF\x00\x00\x00\x00\xFF\xFF\xFF\xFF"),
		pat:  []byte("RIFF\x00\x00\x00\x00WAVE"),
		ct:   "audio/wave",
	},
	&exactSig{[]byte("\x1A\x45\xDF\xA3"), "video/webm"},
	&exactSig{[]byte("\x52\x61\x72\x20\x1A\x07\x00"), "application/x-rar-compressed"},
	&exactSig{[]byte("\x50\x4B\x03\x04"), "application/zip"},
	&exactSig{[]byte("\x1F\x8B\x08"), "application/x-gzip"},

	// TODO(dsymonds): Re-enable this when the spec is sorted w.r.t. MP4.
	//mp4Sig(0),

	textSig(0), // should be last
}

type exactSig struct {
	sig []byte
	ct  string
}

func (e *exactSig) match(data []byte, firstNonWS int) string {
	if bytes.HasPrefix(data, e.sig) {
		return e.ct
	}
	return ""
}

type maskedSig struct {
	mask, pat []byte
	skipWS    bool
	ct        string
}

func (m *maskedSig) match(data []byte, firstNonWS int) string {
	if m.skipWS {
		data = data[firstNonWS:]
	}
	if len(data) < len(m.mask) {
		return ""
	}
	for i, mask := range m.mask {
		db := data[i] & mask
		if db != m.pat[i] {
			return ""
		}
	}
	return m.ct
}

type htmlSig []byte

func (h htmlSig) match(data []byte, firstNonWS int) string {
	data = data[firstNonWS:]
	if len(data) < len(h)+1 {
		return ""
	}
	for i, b := range h {
		db := data[i]
		if 'A' <= b && b <= 'Z' {
			db &= 0xDF
		}
		if b != db {
			return ""
		}
	}
	// Next byte must be space or right angle bracket.
	if db := data[len(h)]; db != ' ' && db != '>' {
		return ""
	}
	return "text/html; charset=utf-8"
}

var mp4ftype = []byte("ftyp")

type mp4Sig int

func (mp4Sig) match(data []byte, firstNonWS int) string {
	// c.f. section 6.1.
	if len(data) < 8 {
		return ""
	}
	boxSize := int(binary.BigEndian.Uint32(data[:4]))
	if boxSize%4 != 0 || len(data) < boxSize {
		return ""
	}
	if !bytes.Equal(data[4:8], mp4ftype) {
		return ""
	}
	for st := 8; st < boxSize; st += 4 {
		if st == 12 {
			// minor version number
			continue
		}
		seg := string(data[st : st+3])
		switch seg {
		case "mp4", "iso", "M4V", "M4P", "M4B":
			return "video/mp4"
			/* The remainder are not in the spec.
			case "M4A":
				return "audio/mp4"
			case "3gp":
				return "video/3gpp"
			case "jp2":
				return "image/jp2" // JPEG 2000
			*/
		}
	}
	return ""
}

type textSig int

func (textSig) match(data []byte, firstNonWS int) string {
	// c.f. section 5, step 4.
	for _, b := range data[firstNonWS:] {
		switch {
		case 0x00 <= b && b <= 0x08,
			b == 0x0B,
			0x0E <= b && b <= 0x1A,
			0x1C <= b && b <= 0x1F:
			return ""
		}
	}
	return "text/plain; charset=utf-8"
}
<commit_msg>net/http: remove always true comparison<commit_after>// Copyright 2011 The Go Authors.  All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package http

import (
	"bytes"
	"encoding/binary"
)

// The algorithm uses at most sniffLen bytes to make its decision.
const sniffLen = 512

// DetectContentType implements the algorithm described
// at http://mimesniff.spec.whatwg.org/ to determine the
// Content-Type of the given data.  It considers at most the
// first 512 bytes of data.  DetectContentType always returns
// a valid MIME type: if it cannot determine a more specific one, it
// returns "application/octet-stream".
func DetectContentType(data []byte) string {
	if len(data) > sniffLen {
		data = data[:sniffLen]
	}

	// Index of the first non-whitespace byte in data.
	firstNonWS := 0
	for ; firstNonWS < len(data) && isWS(data[firstNonWS]); firstNonWS++ {
	}

	for _, sig := range sniffSignatures {
		if ct := sig.match(data, firstNonWS); ct != "" {
			return ct
		}
	}

	return "application/octet-stream" // fallback
}

func isWS(b byte) bool {
	switch b {
	case '\t', '\n', '\x0c', '\r', ' ':
		return true
	}
	return false
}

type sniffSig interface {
	// match returns the MIME type of the data, or "" if unknown.
	match(data []byte, firstNonWS int) string
}

// Data matching the table in section 6.
var sniffSignatures = []sniffSig{
	htmlSig("<!DOCTYPE HTML"),
	htmlSig("<HTML"),
	htmlSig("<HEAD"),
	htmlSig("<SCRIPT"),
	htmlSig("<IFRAME"),
	htmlSig("<H1"),
	htmlSig("<DIV"),
	htmlSig("<FONT"),
	htmlSig("<TABLE"),
	htmlSig("<A"),
	htmlSig("<STYLE"),
	htmlSig("<TITLE"),
	htmlSig("<B"),
	htmlSig("<BODY"),
	htmlSig("<BR"),
	htmlSig("<P"),
	htmlSig("<!--"),

	&maskedSig{mask: []byte("\xFF\xFF\xFF\xFF\xFF"), pat: []byte("<?xml"), skipWS: true, ct: "text/xml; charset=utf-8"},

	&exactSig{[]byte("%PDF-"), "application/pdf"},
	&exactSig{[]byte("%!PS-Adobe-"), "application/postscript"},

	// UTF BOMs.
	&maskedSig{mask: []byte("\xFF\xFF\x00\x00"), pat: []byte("\xFE\xFF\x00\x00"), ct: "text/plain; charset=utf-16be"},
	&maskedSig{mask: []byte("\xFF\xFF\x00\x00"), pat: []byte("\xFF\xFE\x00\x00"), ct: "text/plain; charset=utf-16le"},
	&maskedSig{mask: []byte("\xFF\xFF\xFF\x00"), pat: []byte("\xEF\xBB\xBF\x00"), ct: "text/plain; charset=utf-8"},

	&exactSig{[]byte("GIF87a"), "image/gif"},
	&exactSig{[]byte("GIF89a"), "image/gif"},
	&exactSig{[]byte("\x89\x50\x4E\x47\x0D\x0A\x1A\x0A"), "image/png"},
	&exactSig{[]byte("\xFF\xD8\xFF"), "image/jpeg"},
	&exactSig{[]byte("BM"), "image/bmp"},
	&maskedSig{
		mask: []byte("\xFF\xFF\xFF\xFF\x00\x00\x00\x00\xFF\xFF\xFF\xFF\xFF\xFF"),
		pat:  []byte("RIFF\x00\x00\x00\x00WEBPVP"),
		ct:   "image/webp",
	},
	&exactSig{[]byte("\x00\x00\x01\x00"), "image/vnd.microsoft.icon"},
	&exactSig{[]byte("\x4F\x67\x67\x53\x00"), "application/ogg"},
	&maskedSig{
		mask: []byte("\xFF\xFF\xFF\xFF\x00\x00\x00\x00\xFF\xFF\xFF\xFF"),
		pat:  []byte("RIFF\x00\x00\x00\x00WAVE"),
		ct:   "audio/wave",
	},
	&exactSig{[]byte("\x1A\x45\xDF\xA3"), "video/webm"},
	&exactSig{[]byte("\x52\x61\x72\x20\x1A\x07\x00"), "application/x-rar-compressed"},
	&exactSig{[]byte("\x50\x4B\x03\x04"), "application/zip"},
	&exactSig{[]byte("\x1F\x8B\x08"), "application/x-gzip"},

	// TODO(dsymonds): Re-enable this when the spec is sorted w.r.t. MP4.
	//mp4Sig(0),

	textSig(0), // should be last
}

type exactSig struct {
	sig []byte
	ct  string
}

func (e *exactSig) match(data []byte, firstNonWS int) string {
	if bytes.HasPrefix(data, e.sig) {
		return e.ct
	}
	return ""
}

type maskedSig struct {
	mask, pat []byte
	skipWS    bool
	ct        string
}

func (m *maskedSig) match(data []byte, firstNonWS int) string {
	if m.skipWS {
		data = data[firstNonWS:]
	}
	if len(data) < len(m.mask) {
		return ""
	}
	for i, mask := range m.mask {
		db := data[i] & mask
		if db != m.pat[i] {
			return ""
		}
	}
	return m.ct
}

type htmlSig []byte

func (h htmlSig) match(data []byte, firstNonWS int) string {
	data = data[firstNonWS:]
	if len(data) < len(h)+1 {
		return ""
	}
	for i, b := range h {
		db := data[i]
		if 'A' <= b && b <= 'Z' {
			db &= 0xDF
		}
		if b != db {
			return ""
		}
	}
	// Next byte must be space or right angle bracket.
	if db := data[len(h)]; db != ' ' && db != '>' {
		return ""
	}
	return "text/html; charset=utf-8"
}

var mp4ftype = []byte("ftyp")

type mp4Sig int

func (mp4Sig) match(data []byte, firstNonWS int) string {
	// c.f. section 6.1.
	if len(data) < 8 {
		return ""
	}
	boxSize := int(binary.BigEndian.Uint32(data[:4]))
	if boxSize%4 != 0 || len(data) < boxSize {
		return ""
	}
	if !bytes.Equal(data[4:8], mp4ftype) {
		return ""
	}
	for st := 8; st < boxSize; st += 4 {
		if st == 12 {
			// minor version number
			continue
		}
		seg := string(data[st : st+3])
		switch seg {
		case "mp4", "iso", "M4V", "M4P", "M4B":
			return "video/mp4"
			/* The remainder are not in the spec.
			case "M4A":
				return "audio/mp4"
			case "3gp":
				return "video/3gpp"
			case "jp2":
				return "image/jp2" // JPEG 2000
			*/
		}
	}
	return ""
}

type textSig int

func (textSig) match(data []byte, firstNonWS int) string {
	// c.f. section 5, step 4.
	for _, b := range data[firstNonWS:] {
		switch {
		case b <= 0x08,
			b == 0x0B,
			0x0E <= b && b <= 0x1A,
			0x1C <= b && b <= 0x1F:
			return ""
		}
	}
	return "text/plain; charset=utf-8"
}
<|endoftext|>


<commit_before>package velocity

import (
	"fmt"
	"io"
	"net"
	"os"
	"strings"
	"sync"
	"time"

	"go.uber.org/zap"

	"golang.org/x/crypto/ssh/agent"

	"github.com/go-cmd/cmd"
	"github.com/velocity-ci/velocity/backend/pkg/auth"
	"golang.org/x/crypto/ssh"
)

func runCmd(shCmd []string, writer io.Writer) error {
	// c := cmd.NewCmd(shCmd[0], shCmd[1:len(shCmd)]...)

	opts := cmd.Options{Buffered: false, Streaming: true}
	c := cmd.NewCmdOptions(opts, shCmd[0], shCmd[1:len(shCmd)]...)
	stdout := []string{}
	stderr := []string{}
	go func() {
		for line := range c.Stdout {
			writer.Write([]byte(line))
			stdout = append(stdout, line)
		}
	}()
	go func() {
		for line := range c.Stderr {
			writer.Write([]byte(line))
			stderr = append(stderr, line)
		}
	}()

	GetLogger().Debug("running command", zap.Strings("cmd", shCmd))
	go func() {
		<-time.After(3 * time.Second)
		GetLogger().Debug("3s", zap.Strings("cmd", shCmd), zap.Strings("stdout", stdout), zap.Strings("stderr", stderr), zap.Int("status", c.Status().Exit))
		if len(stdout) < 1 {
			c.Stop()
		}
	}()
	s := c.Start()

	finalStatus := <-s

	fmt.Printf("%+v\n", finalStatus)

	return nil
}

type SSHKeyError string

func (s SSHKeyError) Error() string {
	return string(s)
}

type HostKeyError string

func (s HostKeyError) Error() string {
	return string(s)
}

type GitRepository struct {
	Address    string        `json:"address"`
	PrivateKey string        `json:"privateKey"`
	PublicKey  ssh.PublicKey `json:"-"`
	Agent      agent.Agent   `json:"-"`
}

const WorkspaceDir = "/opt/velocityci/workspaces"

func getUniqueWorkspace(r *GitRepository) (string, error) {
	dir := fmt.Sprintf("%s/_%s-%s",
		WorkspaceDir,
		// slug.Make(r.Address),
		auth.RandomString(8),
		auth.RandomString(8),
	)

	GetLogger().Debug(dir)

	err := os.RemoveAll(dir)
	if err != nil {
		GetLogger().Fatal("could not create unique workspace", zap.Error(err))
		return "", err
	}
	GetLogger().Debug("Removed dir", zap.String("dir", dir))
	err = os.MkdirAll(dir, os.ModePerm)
	if err != nil {
		GetLogger().Fatal("could not create unique workspace", zap.Error(err))
		return "", err
	}
	GetLogger().Debug("Created dir", zap.String("dir", dir))

	return dir, nil
}

func handleGitSSH(r *GitRepository) (d func(*GitRepository), err error) {
	d = func(*GitRepository) {}
	if r.Address[:3] == "git" {
		key, err := ssh.ParseRawPrivateKey([]byte(r.PrivateKey))
		if err != nil {
			return d, err.(SSHKeyError)
		}

		if sshAgent, err := net.Dial("unix", os.Getenv("SSH_AUTH_SOCK")); err == nil {
			a := agent.NewClient(sshAgent)
			a.Add(agent.AddedKey{PrivateKey: key})
			signer, _ := ssh.NewSignerFromKey(key)
			GetLogger().Debug("added ssh key to ssh-agent", zap.String("address", r.Address))
			r.Agent = a
			r.PublicKey = signer.PublicKey()
			return cleanSSHAgent, nil
		}

		return d, fmt.Errorf("ssh-agent not found")
	}

	return d, nil
}

func cleanSSHAgent(r *GitRepository) {
	if r.Agent != nil {
		r.Agent.Remove(r.PublicKey)
		GetLogger().Debug("removed ssh key from ssh-agent", zap.String("address", r.Address))
	}
}

func initWorkspace(r *GitRepository, writer io.Writer) (string, error) {
	dir, err := getUniqueWorkspace(r)
	if err != nil {
		return "", err
	}
	GetLogger().Debug("build workspace", zap.String("directory", dir))

	os.Chdir(dir)
	shCmd := []string{"git", "init"}
	runCmd(shCmd, writer)

	shCmd = []string{"git", "remote", "add", "origin", r.Address}
	runCmd(shCmd, writer)

	return dir, nil
}

func Validate(r *GitRepository) (bool, error) {
	wd, _ := os.Getwd()
	defer os.Chdir(wd)

	writer := &BlankWriter{}

	dir, err := initWorkspace(r, writer)
	if err != nil {
		return false, err
	}
	deferFunc, err := handleGitSSH(r)
	if err != nil {
		return false, err
	}
	defer deferFunc(r)
	os.Chdir(dir)

	shCmd := []string{"git", "ls-remote"}
	c := cmd.NewCmd(shCmd[0], shCmd[1:len(shCmd)]...)
	s := <-c.Start()

	if s.Exit != 0 {
		err := fmt.Errorf(strings.Join(s.Stderr, " "))
		if strings.Contains(err.Error(), "Host key verification failed") {
			err = HostKeyError(err.Error())
		}
		return false, err
	}

	return true, nil
}

type CloneOptions struct {
	Bare      bool
	Full      bool
	Submodule bool
	Commit    string
}

func Clone(
	r *GitRepository,
	writer io.Writer,
	cloneOpts *CloneOptions,
) (*RawRepository, error) {
	wd, _ := os.Getwd()
	defer os.Chdir(wd)

	dir, err := initWorkspace(r, writer)
	if err != nil {
		return nil, err
	}
	os.Chdir(dir)

	shCmd := []string{"git", "fetch", "--progress"}

	if cloneOpts.Bare {
		shCmd = append(shCmd, "--bare")
	}

	if !cloneOpts.Full {
		shCmd = append(shCmd, "--depth=1")
	}

	if cloneOpts.Submodule {
		shCmd = append(shCmd, "--recurse-submodules")
	}

	if len(cloneOpts.Commit) > 0 {
		// shCmd = append(shCmd, "origin", cloneOpts.Commit)
	}

	GetLogger().Info("fetching repository", zap.String("cmd", strings.Join(shCmd, " ")))

	deferFunc, err := handleGitSSH(r)
	if err != nil {
		return nil, err
	}
	defer deferFunc(r)

	opts := cmd.Options{Buffered: false, Streaming: true}
	c := cmd.NewCmdOptions(opts, shCmd[0], shCmd[1:len(shCmd)]...)
	stdOut := []string{}
	stdErr := []string{}
	go func() {
		for {
			select {
			case line := <-c.Stdout:
				writer.Write([]byte(line))
				stdOut = append(stdOut, line)
			case line := <-c.Stderr:
				writer.Write([]byte(line))
				stdErr = append(stdErr, line)
			}
		}
	}()
	s := <-c.Start()
	for len(c.Stdout) > 0 || len(c.Stderr) > 0 {
		time.Sleep(10 * time.Millisecond)
	}
	s.Stdout = stdOut
	s.Stderr = stdErr

	if err := handleStatusError(s); err != nil {
		os.RemoveAll(dir)
		return nil, err
	}

	GetLogger().Info("fetched repository", zap.String("address", r.Address), zap.String("dir", dir))

	return &RawRepository{Directory: dir, GitRepository: r}, nil
}

func (r *RawRepository) GetBranches() (b []string) {
	r.init()
	defer r.done()

	shCmd := []string{"git", "branch", "--remote"}
	c := cmd.NewCmd(shCmd[0], shCmd[1:len(shCmd)]...)
	s := <-c.Start()
	for _, line := range s.Stdout {
		line = strings.TrimSpace(line)
		line = strings.TrimPrefix(line, "origin/")
		if strings.HasPrefix(line, "HEAD") {
			continue
		}
		b = append(b, line)
	}

	return b
}

func (r *RawRepository) GetCommitAtHeadOfBranch(branch string) (*RawCommit, error) {
	r.init()
	defer r.done()
	commitSha := r.RevParse(fmt.Sprintf("origin/%s", branch))

	return r.GetCommitInfo(commitSha)
}

func (r *RawRepository) RevParse(obj string) string {
	r.init()
	defer r.done()
	shCmd := []string{"git", "rev-parse", obj}
	c := cmd.NewCmd(shCmd[0], shCmd[1:len(shCmd)]...)
	s := <-c.Start()

	return strings.TrimSpace(s.Stdout[0])
}

type RawCommit struct {
	SHA         string
	AuthorDate  time.Time
	AuthorEmail string
	AuthorName  string
	Signed      string
	Message     string
}

type RawRepository struct {
	GitRepository *GitRepository
	Directory     string
	pwd           string
	sync.RWMutex
}

func (r *RawRepository) init() {
	r.RLock()
	cwd, err := os.Getwd()
	if err != nil {
		GetLogger().Fatal("could not get working directory", zap.Error(err))
	}
	r.pwd = cwd
	os.Chdir(r.Directory)
}

func (r *RawRepository) done() {
	os.Chdir(r.pwd)
	r.pwd = ""
	r.RUnlock()
}

func (r *RawRepository) GetCommitInfo(sha string) (*RawCommit, error) {
	r.init()
	defer r.done()
	shCmd := []string{"git", "show", "-s", `--format=%H%n%aI%n%aE%n%aN%n%GK%n%s`, sha}
	c := cmd.NewCmd(shCmd[0], shCmd[1:len(shCmd)]...)
	s := <-c.Start()

	if len(s.Stdout) < 6 {
		GetLogger().Error("unexpected commit info output", zap.Strings("stdout", s.Stdout), zap.Strings("stderr", s.Stderr))
		return nil, fmt.Errorf("unexpected commit info output")
	}

	authorDate, _ := time.Parse(time.RFC3339, strings.TrimSpace(s.Stdout[1]))

	return &RawCommit{
		SHA:         strings.TrimSpace(s.Stdout[0]),
		AuthorDate:  authorDate,
		AuthorEmail: strings.TrimSpace(s.Stdout[2]),
		AuthorName:  strings.TrimSpace(s.Stdout[3]),
		Signed:      strings.TrimSpace(s.Stdout[4]),
		Message:     strings.TrimSpace(s.Stdout[5]),
	}, nil
}

func (r *RawRepository) GetCurrentCommitInfo() (*RawCommit, error) {
	r.init()
	defer r.done()
	shCmd := []string{"git", "rev-parse", "HEAD"}
	c := cmd.NewCmd(shCmd[0], shCmd[1:len(shCmd)]...)
	s := <-c.Start()

	GetLogger().Debug("git rev-parse HEAD", zap.Strings("stdout", s.Stdout), zap.Strings("stderr", s.Stderr))

	return r.GetCommitInfo(strings.TrimSpace(s.Stdout[0]))
}

func (r *RawRepository) GetDescribe() string {
	r.init()
	defer r.done()
	shCmd := []string{"git", "describe", "--always"}
	c := cmd.NewCmd(shCmd[0], shCmd[1:len(shCmd)]...)
	s := <-c.Start()

	return strings.TrimSpace(s.Stdout[0])
}

func (r *RawRepository) Clean() error {
	r.init()
	defer r.done()

	shCmd := []string{"git", "clean", "-fd"}
	c := cmd.NewCmd(shCmd[0], shCmd[1:len(shCmd)]...)
	s := <-c.Start()

	return handleStatusError(s)
}

func handleStatusError(s cmd.Status) error {
	if s.Error != nil {
		GetLogger().Error("unknown cmd error", zap.Error(s.Error))
		return s.Error
	}

	if s.Exit != 0 {
		GetLogger().Error("non-zero exit in git", zap.Strings("stdout", s.Stdout), zap.Strings("stderr", s.Stderr))
		return fmt.Errorf(strings.Join(s.Stderr, "\n"))
	}

	return nil
}

func (r *RawRepository) Checkout(ref string) error {
	r.init()
	defer r.done()

	if err := r.Clean(); err != nil {
		return err
	}

	shCmd := []string{"git", "checkout", "--force", ref}
	c := cmd.NewCmd(shCmd[0], shCmd[1:len(shCmd)]...)
	s := <-c.Start()

	if err := handleStatusError(s); err != nil {
		return err
	}

	GetLogger().Debug("checked out", zap.String("reference", ref), zap.String("repository", r.Directory))

	return nil
}

func (r *RawRepository) GetDefaultBranch() (string, error) {
	r.init()
	defer r.done()

	deferFunc, err := handleGitSSH(r.GitRepository)
	if err != nil {
		return "", err
	}
	defer deferFunc(r.GitRepository)

	shCmd := []string{"git", "remote", "show", "origin"}
	c := cmd.NewCmd(shCmd[0], shCmd[1:len(shCmd)]...)
	s := <-c.Start()

	if err := handleStatusError(s); err != nil {
		return "", err
	}

	defaultBranch := strings.TrimSpace(strings.Split(s.Stdout[3], ":")[1])

	return defaultBranch, nil
}
<commit_msg>use runCmd func to clean up goroutines<commit_after>package velocity

import (
	"fmt"
	"io"
	"net"
	"os"
	"strings"
	"sync"
	"time"

	"go.uber.org/zap"

	"golang.org/x/crypto/ssh/agent"

	"github.com/go-cmd/cmd"
	"github.com/velocity-ci/velocity/backend/pkg/auth"
	"golang.org/x/crypto/ssh"
)

func runCmd(shCmd []string, writer io.Writer) cmd.Status {
	opts := cmd.Options{Buffered: false, Streaming: true}
	c := cmd.NewCmdOptions(opts, shCmd[0], shCmd[1:len(shCmd)]...)
	stdout := []string{}
	stderr := []string{}
	go func() {
		for line := range c.Stdout {
			writer.Write([]byte(line))
			stdout = append(stdout, line)
		}
	}()
	go func() {
		for line := range c.Stderr {
			writer.Write([]byte(line))
			stderr = append(stderr, line)
		}
	}()

	GetLogger().Debug("running command", zap.Strings("cmd", shCmd))
	go func() {
		<-time.After(3 * time.Second)
		GetLogger().Debug("3s", zap.Strings("cmd", shCmd), zap.Strings("stdout", stdout), zap.Strings("stderr", stderr), zap.Int("status", c.Status().Exit))
		if len(stdout) < 1 {
			c.Stop()
		}
	}()
	s := c.Start()

	finalStatus := <-s
	close(c.Stdout)
	close(c.Stderr)
	finalStatus.Stdout = stdout
	finalStatus.Stderr = stderr

	fmt.Printf("%+v\n", finalStatus)

	return finalStatus
}

type SSHKeyError string

func (s SSHKeyError) Error() string {
	return string(s)
}

type HostKeyError string

func (s HostKeyError) Error() string {
	return string(s)
}

type GitRepository struct {
	Address    string        `json:"address"`
	PrivateKey string        `json:"privateKey"`
	PublicKey  ssh.PublicKey `json:"-"`
	Agent      agent.Agent   `json:"-"`
}

const WorkspaceDir = "/opt/velocityci/workspaces"

func getUniqueWorkspace(r *GitRepository) (string, error) {
	dir := fmt.Sprintf("%s/_%s-%s",
		WorkspaceDir,
		// slug.Make(r.Address),
		auth.RandomString(8),
		auth.RandomString(8),
	)

	GetLogger().Debug(dir)

	err := os.RemoveAll(dir)
	if err != nil {
		GetLogger().Fatal("could not create unique workspace", zap.Error(err))
		return "", err
	}
	GetLogger().Debug("Removed dir", zap.String("dir", dir))
	err = os.MkdirAll(dir, os.ModePerm)
	if err != nil {
		GetLogger().Fatal("could not create unique workspace", zap.Error(err))
		return "", err
	}
	GetLogger().Debug("Created dir", zap.String("dir", dir))

	return dir, nil
}

func handleGitSSH(r *GitRepository) (d func(*GitRepository), err error) {
	d = func(*GitRepository) {}
	if r.Address[:3] == "git" {
		key, err := ssh.ParseRawPrivateKey([]byte(r.PrivateKey))
		if err != nil {
			return d, err.(SSHKeyError)
		}

		if sshAgent, err := net.Dial("unix", os.Getenv("SSH_AUTH_SOCK")); err == nil {
			a := agent.NewClient(sshAgent)
			a.Add(agent.AddedKey{PrivateKey: key})
			signer, _ := ssh.NewSignerFromKey(key)
			GetLogger().Debug("added ssh key to ssh-agent", zap.String("address", r.Address))
			r.Agent = a
			r.PublicKey = signer.PublicKey()
			return cleanSSHAgent, nil
		}

		return d, fmt.Errorf("ssh-agent not found")
	}

	return d, nil
}

func cleanSSHAgent(r *GitRepository) {
	if r.Agent != nil {
		r.Agent.Remove(r.PublicKey)
		GetLogger().Debug("removed ssh key from ssh-agent", zap.String("address", r.Address))
	}
}

func initWorkspace(r *GitRepository, writer io.Writer) (string, error) {
	dir, err := getUniqueWorkspace(r)
	if err != nil {
		return "", err
	}
	GetLogger().Debug("build workspace", zap.String("directory", dir))

	os.Chdir(dir)
	shCmd := []string{"git", "init"}
	runCmd(shCmd, writer)

	shCmd = []string{"git", "remote", "add", "origin", r.Address}
	runCmd(shCmd, writer)

	return dir, nil
}

func Validate(r *GitRepository) (bool, error) {
	wd, _ := os.Getwd()
	defer os.Chdir(wd)

	writer := &BlankWriter{}

	dir, err := initWorkspace(r, writer)
	if err != nil {
		return false, err
	}
	deferFunc, err := handleGitSSH(r)
	if err != nil {
		return false, err
	}
	defer deferFunc(r)
	os.Chdir(dir)

	shCmd := []string{"git", "ls-remote"}
	s := runCmd(shCmd, writer)

	if s.Exit != 0 {
		err := fmt.Errorf(strings.Join(s.Stderr, " "))
		if strings.Contains(err.Error(), "Host key verification failed") {
			err = HostKeyError(err.Error())
		}
		return false, err
	}

	return true, nil
}

type CloneOptions struct {
	Bare      bool
	Full      bool
	Submodule bool
	Commit    string
}

func Clone(
	r *GitRepository,
	writer io.Writer,
	cloneOpts *CloneOptions,
) (*RawRepository, error) {
	wd, _ := os.Getwd()
	defer os.Chdir(wd)

	dir, err := initWorkspace(r, writer)
	if err != nil {
		return nil, err
	}
	os.Chdir(dir)

	shCmd := []string{"git", "fetch", "--progress"}

	if cloneOpts.Bare {
		shCmd = append(shCmd, "--bare")
	}

	if !cloneOpts.Full {
		shCmd = append(shCmd, "--depth=1")
	}

	if cloneOpts.Submodule {
		shCmd = append(shCmd, "--recurse-submodules")
	}

	if len(cloneOpts.Commit) > 0 {
		// shCmd = append(shCmd, "origin", cloneOpts.Commit)
	}

	GetLogger().Info("fetching repository", zap.String("cmd", strings.Join(shCmd, " ")))

	deferFunc, err := handleGitSSH(r)
	if err != nil {
		return nil, err
	}
	defer deferFunc(r)

	s := runCmd(shCmd, writer)
	if err := handleStatusError(s); err != nil {
		os.RemoveAll(dir)
		return nil, err
	}

	GetLogger().Info("fetched repository", zap.String("address", r.Address), zap.String("dir", dir))

	return &RawRepository{Directory: dir, GitRepository: r}, nil
}

func (r *RawRepository) GetBranches() (b []string) {
	r.init()
	defer r.done()

	shCmd := []string{"git", "branch", "--remote"}
	writer := &BlankWriter{}
	s := runCmd(shCmd, writer)
	for _, line := range s.Stdout {
		line = strings.TrimSpace(line)
		line = strings.TrimPrefix(line, "origin/")
		if strings.HasPrefix(line, "HEAD") {
			continue
		}
		b = append(b, line)
	}

	return b
}

func (r *RawRepository) GetCommitAtHeadOfBranch(branch string) (*RawCommit, error) {
	r.init()
	defer r.done()
	commitSha := r.RevParse(fmt.Sprintf("origin/%s", branch))

	return r.GetCommitInfo(commitSha)
}

func (r *RawRepository) RevParse(obj string) string {
	r.init()
	defer r.done()
	shCmd := []string{"git", "rev-parse", obj}
	writer := &BlankWriter{}
	s := runCmd(shCmd, writer)

	return strings.TrimSpace(s.Stdout[0])
}

type RawCommit struct {
	SHA         string
	AuthorDate  time.Time
	AuthorEmail string
	AuthorName  string
	Signed      string
	Message     string
}

type RawRepository struct {
	GitRepository *GitRepository
	Directory     string
	pwd           string
	sync.RWMutex
}

func (r *RawRepository) init() {
	r.RLock()
	cwd, err := os.Getwd()
	if err != nil {
		GetLogger().Fatal("could not get working directory", zap.Error(err))
	}
	r.pwd = cwd
	os.Chdir(r.Directory)
}

func (r *RawRepository) done() {
	os.Chdir(r.pwd)
	r.pwd = ""
	r.RUnlock()
}

func (r *RawRepository) GetCommitInfo(sha string) (*RawCommit, error) {
	r.init()
	defer r.done()
	shCmd := []string{"git", "show", "-s", `--format=%H%n%aI%n%aE%n%aN%n%GK%n%s`, sha}
	writer := &BlankWriter{}
	s := runCmd(shCmd, writer)

	if len(s.Stdout) < 6 {
		GetLogger().Error("unexpected commit info output", zap.Strings("stdout", s.Stdout), zap.Strings("stderr", s.Stderr))
		return nil, fmt.Errorf("unexpected commit info output")
	}

	authorDate, _ := time.Parse(time.RFC3339, strings.TrimSpace(s.Stdout[1]))

	return &RawCommit{
		SHA:         strings.TrimSpace(s.Stdout[0]),
		AuthorDate:  authorDate,
		AuthorEmail: strings.TrimSpace(s.Stdout[2]),
		AuthorName:  strings.TrimSpace(s.Stdout[3]),
		Signed:      strings.TrimSpace(s.Stdout[4]),
		Message:     strings.TrimSpace(s.Stdout[5]),
	}, nil
}

func (r *RawRepository) GetCurrentCommitInfo() (*RawCommit, error) {
	r.init()
	defer r.done()
	shCmd := []string{"git", "rev-parse", "HEAD"}
	writer := &BlankWriter{}
	s := runCmd(shCmd, writer)

	GetLogger().Debug("git rev-parse HEAD", zap.Strings("stdout", s.Stdout), zap.Strings("stderr", s.Stderr))

	return r.GetCommitInfo(strings.TrimSpace(s.Stdout[0]))
}

func (r *RawRepository) GetDescribe() string {
	r.init()
	defer r.done()
	shCmd := []string{"git", "describe", "--always"}
	writer := &BlankWriter{}
	s := runCmd(shCmd, writer)

	return strings.TrimSpace(s.Stdout[0])
}

func (r *RawRepository) Clean() error {
	r.init()
	defer r.done()

	shCmd := []string{"git", "clean", "-fd"}
	writer := &BlankWriter{}
	s := runCmd(shCmd, writer)

	return handleStatusError(s)
}

func handleStatusError(s cmd.Status) error {
	if s.Error != nil {
		GetLogger().Error("unknown cmd error", zap.Error(s.Error))
		return s.Error
	}

	if s.Exit != 0 {
		GetLogger().Error("non-zero exit in git", zap.Strings("stdout", s.Stdout), zap.Strings("stderr", s.Stderr))
		return fmt.Errorf(strings.Join(s.Stderr, "\n"))
	}

	return nil
}

func (r *RawRepository) Checkout(ref string) error {
	r.init()
	defer r.done()

	if err := r.Clean(); err != nil {
		return err
	}

	shCmd := []string{"git", "checkout", "--force", ref}
	writer := &BlankWriter{}
	s := runCmd(shCmd, writer)

	if err := handleStatusError(s); err != nil {
		return err
	}

	GetLogger().Debug("checked out", zap.String("reference", ref), zap.String("repository", r.Directory))

	return nil
}

func (r *RawRepository) GetDefaultBranch() (string, error) {
	r.init()
	defer r.done()

	deferFunc, err := handleGitSSH(r.GitRepository)
	if err != nil {
		return "", err
	}
	defer deferFunc(r.GitRepository)

	shCmd := []string{"git", "remote", "show", "origin"}
	writer := &BlankWriter{}
	s := runCmd(shCmd, writer)

	if err := handleStatusError(s); err != nil {
		return "", err
	}

	defaultBranch := strings.TrimSpace(strings.Split(s.Stdout[3], ":")[1])

	return defaultBranch, nil
}
<|endoftext|>


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

package gob

import (
	"reflect"
	"testing"
)

type typeT struct {
	id  typeId
	str string
}

var basicTypes = []typeT{
	{tBool, "bool"},
	{tInt, "int"},
	{tUint, "uint"},
	{tFloat, "float"},
	{tBytes, "bytes"},
	{tString, "string"},
}

func getTypeUnlocked(name string, rt reflect.Type) gobType {
	typeLock.Lock()
	defer typeLock.Unlock()
	t, err := getBaseType(name, rt)
	if err != nil {
		panic("getTypeUnlocked: " + err.Error())
	}
	return t
}

// Sanity checks
func TestBasic(t *testing.T) {
	for _, tt := range basicTypes {
		if tt.id.string() != tt.str {
			t.Errorf("checkType: expected %q got %s", tt.str, tt.id.string())
		}
		if tt.id == 0 {
			t.Errorf("id for %q is zero", tt.str)
		}
	}
}

// Reregister some basic types to check registration is idempotent.
func TestReregistration(t *testing.T) {
	newtyp := getTypeUnlocked("int", reflect.TypeOf(int(0)))
	if newtyp != tInt.gobType() {
		t.Errorf("reregistration of %s got new type", newtyp.string())
	}
	newtyp = getTypeUnlocked("uint", reflect.TypeOf(uint(0)))
	if newtyp != tUint.gobType() {
		t.Errorf("reregistration of %s got new type", newtyp.string())
	}
	newtyp = getTypeUnlocked("string", reflect.TypeOf("hello"))
	if newtyp != tString.gobType() {
		t.Errorf("reregistration of %s got new type", newtyp.string())
	}
}

func TestArrayType(t *testing.T) {
	var a3 [3]int
	a3int := getTypeUnlocked("foo", reflect.TypeOf(a3))
	newa3int := getTypeUnlocked("bar", reflect.TypeOf(a3))
	if a3int != newa3int {
		t.Errorf("second registration of [3]int creates new type")
	}
	var a4 [4]int
	a4int := getTypeUnlocked("goo", reflect.TypeOf(a4))
	if a3int == a4int {
		t.Errorf("registration of [3]int creates same type as [4]int")
	}
	var b3 [3]bool
	a3bool := getTypeUnlocked("", reflect.TypeOf(b3))
	if a3int == a3bool {
		t.Errorf("registration of [3]bool creates same type as [3]int")
	}
	str := a3bool.string()
	expected := "[3]bool"
	if str != expected {
		t.Errorf("array printed as %q; expected %q", str, expected)
	}
}

func TestSliceType(t *testing.T) {
	var s []int
	sint := getTypeUnlocked("slice", reflect.TypeOf(s))
	var news []int
	newsint := getTypeUnlocked("slice1", reflect.TypeOf(news))
	if sint != newsint {
		t.Errorf("second registration of []int creates new type")
	}
	var b []bool
	sbool := getTypeUnlocked("", reflect.TypeOf(b))
	if sbool == sint {
		t.Errorf("registration of []bool creates same type as []int")
	}
	str := sbool.string()
	expected := "[]bool"
	if str != expected {
		t.Errorf("slice printed as %q; expected %q", str, expected)
	}
}

func TestMapType(t *testing.T) {
	var m map[string]int
	mapStringInt := getTypeUnlocked("map", reflect.TypeOf(m))
	var newm map[string]int
	newMapStringInt := getTypeUnlocked("map1", reflect.TypeOf(newm))
	if mapStringInt != newMapStringInt {
		t.Errorf("second registration of map[string]int creates new type")
	}
	var b map[string]bool
	mapStringBool := getTypeUnlocked("", reflect.TypeOf(b))
	if mapStringBool == mapStringInt {
		t.Errorf("registration of map[string]bool creates same type as map[string]int")
	}
	str := mapStringBool.string()
	expected := "map[string]bool"
	if str != expected {
		t.Errorf("map printed as %q; expected %q", str, expected)
	}
}

type Bar struct {
	X string
}

// This structure has pointers and refers to itself, making it a good test case.
type Foo struct {
	A int
	B int32 // will become int
	C string
	D []byte
	E *float64    // will become float64
	F ****float64 // will become float64
	G *Bar
	H *Bar // should not interpolate the definition of Bar again
	I *Foo // will not explode
}

func TestStructType(t *testing.T) {
	sstruct := getTypeUnlocked("Foo", reflect.TypeOf(Foo{}))
	str := sstruct.string()
	// If we can print it correctly, we built it correctly.
	expected := "Foo = struct { A int; B int; C string; D bytes; E float; F float; G Bar = struct { X string; }; H Bar; I Foo; }"
	if str != expected {
		t.Errorf("struct printed as %q; expected %q", str, expected)
	}
}

// Should be OK to register the same type multiple times, as long as they're
// at the same level of indirection.
func TestRegistration(t *testing.T) {
	type T struct{ a int }
	Register(new(T))
	Register(new(T))
}

type N1 struct{}
type N2 struct{}

// See comment in type.go/Register.
func TestRegistrationNaming(t *testing.T) {
	testCases := []struct {
		t    interface{}
		name string
	}{
		{&N1{}, "*gob.N1"},
		{N2{}, "encoding/gob.N2"},
	}

	for _, tc := range testCases {
		Register(tc.t)

		tct := reflect.TypeOf(tc.t)
		registerLock.RLock()
		ct := nameToConcreteType[tc.name]
		registerLock.RUnlock()
		if ct != tct {
			t.Errorf("nameToConcreteType[%q] = %v, want %v", tc.name, ct, tct)
		}
		// concreteTypeToName is keyed off the base type.
		if tct.Kind() == reflect.Ptr {
			tct = tct.Elem()
		}
		if n := concreteTypeToName[tct]; n != tc.name {
			t.Errorf("concreteTypeToName[%v] got %v, want %v", tct, n, tc.name)
		}
	}
}
<commit_msg>encoding/gob: add test case for issue 4214. See http://code.google.com/p/go/issues/detail?id=4214<commit_after>// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package gob

import (
	"bytes"
	"reflect"
	"testing"
)

type typeT struct {
	id  typeId
	str string
}

var basicTypes = []typeT{
	{tBool, "bool"},
	{tInt, "int"},
	{tUint, "uint"},
	{tFloat, "float"},
	{tBytes, "bytes"},
	{tString, "string"},
}

func getTypeUnlocked(name string, rt reflect.Type) gobType {
	typeLock.Lock()
	defer typeLock.Unlock()
	t, err := getBaseType(name, rt)
	if err != nil {
		panic("getTypeUnlocked: " + err.Error())
	}
	return t
}

// Sanity checks
func TestBasic(t *testing.T) {
	for _, tt := range basicTypes {
		if tt.id.string() != tt.str {
			t.Errorf("checkType: expected %q got %s", tt.str, tt.id.string())
		}
		if tt.id == 0 {
			t.Errorf("id for %q is zero", tt.str)
		}
	}
}

// Reregister some basic types to check registration is idempotent.
func TestReregistration(t *testing.T) {
	newtyp := getTypeUnlocked("int", reflect.TypeOf(int(0)))
	if newtyp != tInt.gobType() {
		t.Errorf("reregistration of %s got new type", newtyp.string())
	}
	newtyp = getTypeUnlocked("uint", reflect.TypeOf(uint(0)))
	if newtyp != tUint.gobType() {
		t.Errorf("reregistration of %s got new type", newtyp.string())
	}
	newtyp = getTypeUnlocked("string", reflect.TypeOf("hello"))
	if newtyp != tString.gobType() {
		t.Errorf("reregistration of %s got new type", newtyp.string())
	}
}

func TestArrayType(t *testing.T) {
	var a3 [3]int
	a3int := getTypeUnlocked("foo", reflect.TypeOf(a3))
	newa3int := getTypeUnlocked("bar", reflect.TypeOf(a3))
	if a3int != newa3int {
		t.Errorf("second registration of [3]int creates new type")
	}
	var a4 [4]int
	a4int := getTypeUnlocked("goo", reflect.TypeOf(a4))
	if a3int == a4int {
		t.Errorf("registration of [3]int creates same type as [4]int")
	}
	var b3 [3]bool
	a3bool := getTypeUnlocked("", reflect.TypeOf(b3))
	if a3int == a3bool {
		t.Errorf("registration of [3]bool creates same type as [3]int")
	}
	str := a3bool.string()
	expected := "[3]bool"
	if str != expected {
		t.Errorf("array printed as %q; expected %q", str, expected)
	}
}

func TestSliceType(t *testing.T) {
	var s []int
	sint := getTypeUnlocked("slice", reflect.TypeOf(s))
	var news []int
	newsint := getTypeUnlocked("slice1", reflect.TypeOf(news))
	if sint != newsint {
		t.Errorf("second registration of []int creates new type")
	}
	var b []bool
	sbool := getTypeUnlocked("", reflect.TypeOf(b))
	if sbool == sint {
		t.Errorf("registration of []bool creates same type as []int")
	}
	str := sbool.string()
	expected := "[]bool"
	if str != expected {
		t.Errorf("slice printed as %q; expected %q", str, expected)
	}
}

func TestMapType(t *testing.T) {
	var m map[string]int
	mapStringInt := getTypeUnlocked("map", reflect.TypeOf(m))
	var newm map[string]int
	newMapStringInt := getTypeUnlocked("map1", reflect.TypeOf(newm))
	if mapStringInt != newMapStringInt {
		t.Errorf("second registration of map[string]int creates new type")
	}
	var b map[string]bool
	mapStringBool := getTypeUnlocked("", reflect.TypeOf(b))
	if mapStringBool == mapStringInt {
		t.Errorf("registration of map[string]bool creates same type as map[string]int")
	}
	str := mapStringBool.string()
	expected := "map[string]bool"
	if str != expected {
		t.Errorf("map printed as %q; expected %q", str, expected)
	}
}

type Bar struct {
	X string
}

// This structure has pointers and refers to itself, making it a good test case.
type Foo struct {
	A int
	B int32 // will become int
	C string
	D []byte
	E *float64    // will become float64
	F ****float64 // will become float64
	G *Bar
	H *Bar // should not interpolate the definition of Bar again
	I *Foo // will not explode
}

func TestStructType(t *testing.T) {
	sstruct := getTypeUnlocked("Foo", reflect.TypeOf(Foo{}))
	str := sstruct.string()
	// If we can print it correctly, we built it correctly.
	expected := "Foo = struct { A int; B int; C string; D bytes; E float; F float; G Bar = struct { X string; }; H Bar; I Foo; }"
	if str != expected {
		t.Errorf("struct printed as %q; expected %q", str, expected)
	}
}

// Should be OK to register the same type multiple times, as long as they're
// at the same level of indirection.
func TestRegistration(t *testing.T) {
	type T struct{ a int }
	Register(new(T))
	Register(new(T))
}

type N1 struct{}
type N2 struct{}

// See comment in type.go/Register.
func TestRegistrationNaming(t *testing.T) {
	testCases := []struct {
		t    interface{}
		name string
	}{
		{&N1{}, "*gob.N1"},
		{N2{}, "encoding/gob.N2"},
	}

	for _, tc := range testCases {
		Register(tc.t)

		tct := reflect.TypeOf(tc.t)
		registerLock.RLock()
		ct := nameToConcreteType[tc.name]
		registerLock.RUnlock()
		if ct != tct {
			t.Errorf("nameToConcreteType[%q] = %v, want %v", tc.name, ct, tct)
		}
		// concreteTypeToName is keyed off the base type.
		if tct.Kind() == reflect.Ptr {
			tct = tct.Elem()
		}
		if n := concreteTypeToName[tct]; n != tc.name {
			t.Errorf("concreteTypeToName[%v] got %v, want %v", tct, n, tc.name)
		}
	}
}

func TestStressParallel(t *testing.T) {
	type T2 struct{ A int }
	c := make(chan bool)
	const N = 10
	for i := 0; i < N; i++ {
		go func() {
			p := new(T2)
			Register(p)
			b := new(bytes.Buffer)
			enc := NewEncoder(b)
			err := enc.Encode(p)
			if err != nil {
				t.Error("encoder fail:", err)
			}
			dec := NewDecoder(b)
			err = dec.Decode(p)
			if err != nil {
				t.Error("decoder fail:", err)
			}
			c <- true
		}()
	}
	for i := 0; i < N; i++ {
		<-c
	}
}
<|endoftext|>