<commit_before>package play

import (
	"time"

	"github.com/CyCoreSystems/ari"
	"github.com/pkg/errors"
	"golang.org/x/net/context"
)

// AllDTMF is a string which contains all possible
// DTMF digits.
const AllDTMF = "0123456789ABCD*#"

// playStages executes a staged playback, waiting for its completion
func playStaged(ctx context.Context, h *ari.PlaybackHandle, opts *Options) (Status, error) {
	if opts == nil {
		opts = NewDefaultOptions()
	}

	started := h.Subscribe(ari.Events.PlaybackStarted)
	defer started.Cancel()
	finished := h.Subscribe(ari.Events.PlaybackFinished)
	defer finished.Cancel()

	err := h.Exec()
	if err != nil {
		return Failed, errors.Wrap(err, "failed to start playback")
	}
	defer h.Stop() // nolint: errcheck

	select {
	case <-ctx.Done():
		return Cancelled, nil
	case <-time.After(opts.playbackStartTimeout):
		return Timeout, errors.New("timeout waiting for playback to start")
	case <-finished.Events():
		return Finished, nil
	case <-started.Events():
	}

	// Wait for playback to complete
	select {
	case <-ctx.Done():
		return Cancelled, nil
	case <-finished.Events():
		return Finished, nil
	}
}

// NewPlay creates a new audio Options suitable for general audio playback
func NewPlay(ctx context.Context, p ari.Player, opts ...OptionFunc) (*Options, error) {
	o := NewDefaultOptions()
	err := o.ApplyOptions(opts...)

	return o, err
}

// Play plays the given media URI
func Play(ctx context.Context, p ari.Player, opts ...OptionFunc) *Result {
	o, err := NewPlay(ctx, p, opts...)
	if err != nil && o.result.Error != nil {
		o.result.Error = err
		return o.result
	}

	o.result.Error = o.Play(ctx, p)
	return o.result
}

// PlayAsync executes a playback, returning a channel which will be closed when the playback is ended
func (o *Options) PlayAsync(ctx context.Context, p ari.Player) <-chan error {
	ch := make(chan error)

	go func() {
		ch <- o.Play(ctx, p)
		close(ch)
	}()

	return ch
}

// Play executes a playback with the existing options.
func (o *Options) Play(ctx context.Context, p ari.Player) error {
	ctx, cancel := context.WithCancel(ctx)
	defer cancel()

	if o.result == nil {
		o.result = new(Result)
	}

	if o.uriList == nil {
		return errors.New("empty playback URI list")
	}

	// cancel if we go over the maximum time
	go o.watchMaxTime(ctx)

	// Listen for DTMF
	go o.listenDTMF(ctx, p)

	for i := 0; i < o.maxReplays+1; i++ {
		if ctx.Err() != nil {
			break
		}

		// Reset the digit cache
		o.result.mu.Lock()
		o.result.DTMF = ""
		o.result.mu.Unlock()

		// Play the sequence of audio URIs
		err := o.playSequence(ctx, p)
		if err != nil {
			return err
		}

		// Wait for digits in the silence after the playback sequence completes
		o.waitDigits(ctx)
	}

	return nil

}

// playSequence plays the complete audio sequence
func (o *Options) playSequence(ctx context.Context, p ari.Player) (err error) {
	s := newSequence(o)
	defer s.Stop()

	go s.Play(ctx, p)

	select {
	case <-ctx.Done():
	case <-o.digitChan:
	case err = <-s.Done():
	}

	return err
}

func (o *Options) waitDigits(ctx context.Context) {
	overallTimer := time.NewTimer(o.overallDigitTimeout)
	defer overallTimer.Stop()

	digitTimeout := o.firstDigitTimeout

	for {
		select {
		case <-ctx.Done():
			return
		case <-time.After(digitTimeout):
			return
		case <-overallTimer.C:
			return
		case <-o.digitChan:
			digitTimeout = o.interDigitTimeout

			// Determine if a match was found
			if o.matchFunc != nil {
				o.result.mu.Lock()
				o.result.DTMF, o.result.MatchResult = o.matchFunc(o.result.DTMF)
				o.result.mu.Unlock()

				switch o.result.MatchResult {
				case Complete:
					// If we have a complete response, close the entire playback
					// and return
					o.Stop()
					return
				case Invalid:
					// If invalid, return without waiting
					// for any more digits
					return
				default:
					// Incomplete means we should wait for more
				}
			}
		}
	}

}

// Stop terminates the execution of a playback
func (o *Options) Stop() {
	if o.result == nil {
		o.result = new(Result)
	}

	if o.result.Status == InProgress {
		o.result.Status = Cancelled
	}

	if o.cancel != nil {
		o.cancel()
	}
}

func (o *Options) watchMaxTime(ctx context.Context) {
	select {
	case <-ctx.Done():
		return
	case <-time.After(o.maxPlaybackTime):
		o.Stop()
	}
}
func (o *Options) listenDTMF(ctx context.Context, p ari.Player) {
	sub := p.Subscribe(ari.Events.ChannelDtmfReceived)
	defer sub.Cancel()

	for {
		select {
		case <-ctx.Done():
			return
		case e := <-sub.Events():
			if e == nil {
				return
			}
			v, ok := e.(*ari.ChannelDtmfReceived)
			if !ok {
				continue
			}
			o.result.mu.Lock()
			o.result.DTMF += v.Digit
			o.result.mu.Unlock()

			// Signal receipt of digit, but never block in doing so
			select {
			case o.digitChan <- v.Digit:
			default:
			}

		}
	}
}
<commit_msg>ext/play: add prompt wrappers<commit_after>package play

import (
	"time"

	"github.com/CyCoreSystems/ari"
	"github.com/pkg/errors"
	"golang.org/x/net/context"
)

// AllDTMF is a string which contains all possible
// DTMF digits.
const AllDTMF = "0123456789ABCD*#"

// playStages executes a staged playback, waiting for its completion
func playStaged(ctx context.Context, h *ari.PlaybackHandle, opts *Options) (Status, error) {
	if opts == nil {
		opts = NewDefaultOptions()
	}

	started := h.Subscribe(ari.Events.PlaybackStarted)
	defer started.Cancel()
	finished := h.Subscribe(ari.Events.PlaybackFinished)
	defer finished.Cancel()

	err := h.Exec()
	if err != nil {
		return Failed, errors.Wrap(err, "failed to start playback")
	}
	defer h.Stop() // nolint: errcheck

	select {
	case <-ctx.Done():
		return Cancelled, nil
	case <-time.After(opts.playbackStartTimeout):
		return Timeout, errors.New("timeout waiting for playback to start")
	case <-finished.Events():
		return Finished, nil
	case <-started.Events():
	}

	// Wait for playback to complete
	select {
	case <-ctx.Done():
		return Cancelled, nil
	case <-finished.Events():
		return Finished, nil
	}
}

// NewPlay creates a new audio Options suitable for general audio playback
func NewPlay(ctx context.Context, p ari.Player, opts ...OptionFunc) (*Options, error) {
	o := NewDefaultOptions()
	err := o.ApplyOptions(opts...)

	return o, err
}

// NewPrompt creates a new audio Options suitable for prompt-style playback-and-get-response situations
func NewPrompt(ctx context.Context, p ari.Player, opts ...OptionFunc) (*Options, error) {
	o := NewPromptOptions()
	err := o.ApplyOptions(opts...)

	return o, err
}

// Play plays a set of media URIs.  Pass these URIs in with the `URI` OptionFunc.
func Play(ctx context.Context, p ari.Player, opts ...OptionFunc) *Result {
	o, err := NewPlay(ctx, p, opts...)
	if err != nil && o.result.Error != nil {
		o.result.Error = err
		return o.result
	}

	o.result.Error = o.Play(ctx, p)
	return o.result
}

// Prompt plays the given media URIs and waits for a DTMF response.  The
// received DTMF is available as `DTMF` in the Result object.  Further
// customize the behaviour with Match type OptionFuncs.
func Prompt(ctx context.Context, p ari.Player, opts ...OptionFunc) *Result {
	o, err := NewPrompt(ctx, p, opts...)
	if err != nil && o.result.Error != nil {
		o.result.Error = err
		return o.result
	}

	o.result.Error = o.Play(ctx, p)
	return o.result
}

// PlayAsync executes a playback, returning a channel which will be closed when the playback is ended
func (o *Options) PlayAsync(ctx context.Context, p ari.Player) <-chan error {
	ch := make(chan error)

	go func() {
		ch <- o.Play(ctx, p)
		close(ch)
	}()

	return ch
}

// Play executes a playback with the existing options.
func (o *Options) Play(ctx context.Context, p ari.Player) error {
	ctx, cancel := context.WithCancel(ctx)
	defer cancel()

	if o.result == nil {
		o.result = new(Result)
	}

	if o.uriList == nil {
		return errors.New("empty playback URI list")
	}

	// cancel if we go over the maximum time
	go o.watchMaxTime(ctx)

	// Listen for DTMF
	go o.listenDTMF(ctx, p)

	for i := 0; i < o.maxReplays+1; i++ {
		if ctx.Err() != nil {
			break
		}

		// Reset the digit cache
		o.result.mu.Lock()
		o.result.DTMF = ""
		o.result.mu.Unlock()

		// Play the sequence of audio URIs
		err := o.playSequence(ctx, p)
		if err != nil {
			return err
		}

		// Wait for digits in the silence after the playback sequence completes
		o.waitDigits(ctx)
	}

	return nil

}

// playSequence plays the complete audio sequence
func (o *Options) playSequence(ctx context.Context, p ari.Player) (err error) {
	s := newSequence(o)
	defer s.Stop()

	go s.Play(ctx, p)

	select {
	case <-ctx.Done():
	case <-o.digitChan:
	case err = <-s.Done():
	}

	return err
}

func (o *Options) waitDigits(ctx context.Context) {
	overallTimer := time.NewTimer(o.overallDigitTimeout)
	defer overallTimer.Stop()

	digitTimeout := o.firstDigitTimeout

	for {
		select {
		case <-ctx.Done():
			return
		case <-time.After(digitTimeout):
			return
		case <-overallTimer.C:
			return
		case <-o.digitChan:
			digitTimeout = o.interDigitTimeout

			// Determine if a match was found
			if o.matchFunc != nil {
				o.result.mu.Lock()
				o.result.DTMF, o.result.MatchResult = o.matchFunc(o.result.DTMF)
				o.result.mu.Unlock()

				switch o.result.MatchResult {
				case Complete:
					// If we have a complete response, close the entire playback
					// and return
					o.Stop()
					return
				case Invalid:
					// If invalid, return without waiting
					// for any more digits
					return
				default:
					// Incomplete means we should wait for more
				}
			}
		}
	}

}

// Stop terminates the execution of a playback
func (o *Options) Stop() {
	if o.result == nil {
		o.result = new(Result)
	}

	if o.result.Status == InProgress {
		o.result.Status = Cancelled
	}

	if o.cancel != nil {
		o.cancel()
	}
}

func (o *Options) watchMaxTime(ctx context.Context) {
	select {
	case <-ctx.Done():
		return
	case <-time.After(o.maxPlaybackTime):
		o.Stop()
	}
}
func (o *Options) listenDTMF(ctx context.Context, p ari.Player) {
	sub := p.Subscribe(ari.Events.ChannelDtmfReceived)
	defer sub.Cancel()

	for {
		select {
		case <-ctx.Done():
			return
		case e := <-sub.Events():
			if e == nil {
				return
			}
			v, ok := e.(*ari.ChannelDtmfReceived)
			if !ok {
				continue
			}
			o.result.mu.Lock()
			o.result.DTMF += v.Digit
			o.result.mu.Unlock()

			// Signal receipt of digit, but never block in doing so
			select {
			case o.digitChan <- v.Digit:
			default:
			}

		}
	}
}
<|endoftext|>


<commit_before>// Copyright © 2012, 2013 Lrucache contributors, see AUTHORS file
//
// The license for this file is described in the LICENSE file.

package lrucache

import (
	"errors"
	"math/rand"
	"runtime"
	"strconv"
	"sync"
	"testing"
	"time"
)

type varsize int

func (i varsize) Size() int64 {
	return int64(i)
}

type purgeable struct {
	purged bool
	why    PurgeReason
}

func (x *purgeable) Size() int64 {
	return 1
}

func (x *purgeable) OnPurge(why PurgeReason) {
	x.purged = true
	x.why = why
}

func syncCache(c *Cache) {
	c.Get("imblueifIweregreenIwoulddie")
}

func TestOnPurge_1(t *testing.T) {
	c := New(1)
	defer c.Close()
	var x, y purgeable
	c.Set("x", &x)
	c.Set("y", &y)
	syncCache(c)
	if !x.purged {
		t.Error("Element was not purged from full cache")
	}
	if x.why != CACHEFULL {
		t.Error("Element should have been purged but was deleted")
	}

	verifyIntegrity(t, c)
}

func TestOnPurge_2(t *testing.T) {
	c := New(1)
	defer c.Close()
	var x purgeable
	c.Set("x", &x)
	c.Delete("x")
	syncCache(c)
	if !x.purged {
		t.Error("Element was not deleted from cache")
	}
	if x.why != EXPLICITDELETE {
		t.Error("Element should have been deleted but was purged")
	}

	verifyIntegrity(t, c)
}

// Just test filling a cache with a type that does not implement NotifyPurge
func TestsafeOnPurge(t *testing.T) {
	c := New(1)
	defer c.Close()
	i := varsize(1)
	j := varsize(1)
	c.Set("i", i)
	c.Set("j", j)
	syncCache(c)

	verifyIntegrity(t, c)
}

func TestSize(t *testing.T) {
	c := New(100)
	defer c.Close()
	// sum(0..14) = 105
	for i := 1; i < 15; i++ {
		c.Set(strconv.Itoa(i), varsize(i))
	}
	syncCache(c)
	// At this point, expect {0, 1, 2, 3} to be purged
	if c.Size() != 99 {
		t.Errorf("Unexpected size: %d", c.Size())
	}
	for i := 0; i < 4; i++ {
		if _, err := c.Get(strconv.Itoa(i)); err != ErrNotFound {
			t.Errorf("Expected %d to be purged", i)
		}
	}
	for i := 4; i < 15; i++ {
		if _, err := c.Get(strconv.Itoa(i)); err != nil {
			t.Errorf("Expected %d to be cached", i)
		}
	}

	verifyIntegrity(t, c)
}

func TestOnMiss(t *testing.T) {
	c := New(10)
	defer c.Close()
	// Expected cache misses (arbitrary value)
	misses := map[string]int{}
	for i := 5; i < 10; i++ {
		misses[strconv.Itoa(i)] = 0
	}
	c.OnMiss(func(id string) (Cacheable, error) {
		if _, ok := misses[id]; !ok {
			return nil, nil
		}
		delete(misses, id)
		i, err := strconv.Atoi(id)
		if err != nil {
			return nil, errors.New("Illegal id: " + id)
		}
		return i, nil
	})
	for i := 0; i < 5; i++ {
		c.Set(strconv.Itoa(i), i)
	}
	for i := 0; i < 10; i++ {
		x, err := c.Get(strconv.Itoa(i))
		switch err {
		case nil:
			break
		case ErrNotFound:
			t.Errorf("Unexpected cache miss for %d", i)
			continue
		default:
			t.Fatal(err)
		}
		if j := x.(int); j != i {
			t.Errorf("Illegal cache value: expected %d, got %d", i, j)
		}
	}
	for k := range misses {
		t.Errorf("Expected %s to miss", k)
	}

	verifyIntegrity(t, c)
}

func TestOnMissDuplicate(t *testing.T) {
	c := New(10)
	defer c.Close()
	onmisscount := 0
	c.OnMiss(func(id string) (Cacheable, error) {
		onmisscount += 1
		return onmisscount, nil
	})
	c.Get("key")
	c.Get("key")
	c.Get("key")
	c.Get("key")
	c.Get("key")
	o, _ := c.Get("key")
	count := o.(int)
	if count != 1 {
		t.Error("OnMiss handler was not called exactly once:", count)
	}
}

func TestOnMissError(t *testing.T) {
	c := New(10)
	defer c.Close()
	myerr := errors.New("some error")
	onmisscount := 0
	c.OnMiss(func(id string) (Cacheable, error) {
		onmisscount += 1
		return onmisscount, myerr
	})
	c.Get("key")
	c.Get("key")
	c.Get("key")
	o, err := c.Get("key")
	if err != myerr {
		t.Error("Unexpected error from OnMiss through Get:", err)
	}
	count, ok := o.(int)
	if !ok {
		t.Fatal("Value not returned from Get when OnMiss returns error")
	}
	if count != 4 {
		t.Error("OnMiss handler was not called repeatedly on error:", count)
	}
}

func TestOnMissConcurrent(t *testing.T) {
	c := New(10)
	defer c.Close()
	ch := make(chan int)
	// If key foo is requested but not cached, read it from the channel
	c.OnMiss(func(id string) (Cacheable, error) {
		if id == "foo" {
			// Indicate that we want a value
			ch <- 0
			return <-ch, nil
		}
		return nil, nil
	})
	go func() {
		c.Get("foo")
	}()
	<-ch
	// Now we know for sure: a goroutine is blocking on c.Get("foo").
	// But other cache operations should be unaffected:
	c.Set("bar", 10)
	// Unlock that poor blocked goroutine
	ch <- 3
	go func() { <-ch; ch <- 10 }()
	result, err := c.Get("foo")
	switch {
	case err != nil:
		t.Error(`Error while fetching "foo":`, err)
	case result != 10:
		t.Error("Expected 10, got:", result)
	}

	verifyIntegrity(t, c)
}

func TestZeroSize(t *testing.T) {
	c := New(2)
	defer c.Close()
	c.Set("a", varsize(0))
	c.Set("b", varsize(1))
	c.Set("c", varsize(2))
	if _, err := c.Get("a"); err != nil {
		t.Error("Purged element with size=0; should have left in cache")
	}
	c.Delete("a")
	c.Set("d", varsize(2))
	if _, err := c.Get("c"); err != ErrNotFound {
		t.Error("Kept `c' around for too long after removing empty element")
	}
	if _, err := c.Get("d"); err != nil {
		t.Error("Failed to cache `d' after removing empty element")
	}

	verifyIntegrity(t, c)
}

func verifyIntegrity(t *testing.T, c *Cache) {
	hKeys := make([]string, 0)
	for h := c.mostRU; h != nil; h = h.older {
		hKeys = append(hKeys, h.id)
	}

	tKeys := make([]string, 0)
	for t := c.leastRU; t != nil; t = t.younger {
		tKeys = append(tKeys, t.id)
	}

	if len(hKeys) != len(tKeys) {
		t.Error("Doubly linked list inconsistent. head has " + strconv.Itoa(len(hKeys)) + " entries and tail has " + strconv.Itoa(len(tKeys)) + " entries")
	}

	for i := 0; i < len(hKeys); i++ {
		j := (len(hKeys) - i) - 1

		if hKeys[i] != tKeys[j] {
			t.Error("Key mismatch; head has " + hKeys[i] + " and tail has " + tKeys[j])
		}
	}
}

func leakingGoroutinesHelper() {
	c := New(100)
	c.Set("foo", 123)
}

func TestLeakingGoroutines(t *testing.T) {
	n := runtime.NumGoroutine()
	for i := 0; i < 100; i++ {
		leakingGoroutinesHelper()
	}
	// seduce the garbage collector
	time.Sleep(time.Second)
	runtime.GC()
	runtime.Gosched()
	time.Sleep(time.Second)
	n2 := runtime.NumGoroutine()
	leak := n2 - n
	// TODO: Why is this 1 no matter how many caches are created and cleaned up?
	// To see what I mean run this test in isolation (go test -run TestLeak);
	// leak will always be exactly 1.  When run alongside other tests their
	// garbage is also cleaned up here so leak can be less than 0---that's okay.
	if leak > 1 { // I'd like to test >0 here :(
		// Not .Error because garbage collection is not spec'ed yet (see doc for
		// New()).
		t.Log("leaking goroutines:", leak)
		//panic("dumping goroutine stacks")
	}
}

func benchmarkGet(b *testing.B, conc int) {
	b.StopTimer()
	// Size doesn't matter (that's what she said)
	c := New(1000)
	defer c.Close()
	c.Set("x", 1)
	syncCache(c)
	var wg sync.WaitGroup
	wg.Add(conc)
	b.StartTimer()
	for i := 0; i < conc; i++ {
		go func() {
			for i := 0; i < b.N/conc; i++ {
				c.Get("x")
			}
			syncCache(c)
			wg.Done()
		}()
	}
	wg.Wait()
}

func benchmarkSet(b *testing.B, conc int) {
	b.StopTimer()
	// Size matters.
	c := New(int64(b.N) / 4)
	defer c.Close()
	syncCache(c)
	var wg sync.WaitGroup
	wg.Add(conc)
	b.StartTimer()
	for i := 0; i < conc; i++ {
		go func() {
			for i := 0; i < b.N/conc; i++ {
				c.Set(strconv.Itoa(i), i)
			}
			wg.Done()
		}()
	}
	wg.Wait()
	syncCache(c)
}

func benchmarkAll(b *testing.B, conc int) {
	b.StopTimer()
	// Size is definitely important, but what is the right size?
	c := New(int64(b.N) / 4)
	defer c.Close()
	syncCache(c)
	var wg sync.WaitGroup
	wg.Add(conc)
	b.StartTimer()
	for i := 0; i < conc; i++ {
		go func() {
			for i := 0; i < b.N/3/conc; i++ {
				c.Set(strconv.Itoa(rand.Int()), 1)
				c.Get(strconv.Itoa(rand.Int()))
				c.Delete(strconv.Itoa(rand.Int()))
			}
			wg.Done()
		}()
	}
	wg.Wait()
	syncCache(c)
}

func BenchmarkGet(b *testing.B) {
	benchmarkGet(b, 1)
}

func Benchmark10ConcurrentGet(b *testing.B) {
	benchmarkGet(b, 10)
}

func Benchmark100ConcurrentGet(b *testing.B) {
	benchmarkGet(b, 100)
}

func Benchmark1KConcurrentGet(b *testing.B) {
	benchmarkGet(b, 1000)
}

func Benchmark10KConcurrentGet(b *testing.B) {
	benchmarkGet(b, 10000)
}

func BenchmarkSet(b *testing.B) {
	benchmarkSet(b, 1)
}

func Benchmark10ConcurrentSet(b *testing.B) {
	benchmarkSet(b, 10)
}

func Benchmark100ConcurrentSet(b *testing.B) {
	benchmarkSet(b, 100)
}

func Benchmark1KConcurrentSet(b *testing.B) {
	benchmarkSet(b, 10000)
}

func Benchmark10KConcurrentSet(b *testing.B) {
	benchmarkSet(b, 10000)
}

func BenchmarkAll(b *testing.B) {
	benchmarkAll(b, 1)
}

func Benchmark10ConcurrentAll(b *testing.B) {
	benchmarkAll(b, 10)
}

func Benchmark100ConcurrentAll(b *testing.B) {
	benchmarkAll(b, 100)
}

func Benchmark1KConcurrentAll(b *testing.B) {
	benchmarkAll(b, 1000)
}

func Benchmark10KConcurrentAll(b *testing.B) {
	benchmarkAll(b, 10000)
}
<commit_msg>remove code by David Kirchner<commit_after>// Copyright © 2012, 2013 Lrucache contributors, see AUTHORS file
//
// The license for this file is described in the LICENSE file.

package lrucache

import (
	"errors"
	"math/rand"
	"runtime"
	"strconv"
	"sync"
	"testing"
	"time"
)

type varsize int

func (i varsize) Size() int64 {
	return int64(i)
}

type purgeable struct {
	purged bool
	why    PurgeReason
}

func (x *purgeable) Size() int64 {
	return 1
}

func (x *purgeable) OnPurge(why PurgeReason) {
	x.purged = true
	x.why = why
}

func syncCache(c *Cache) {
	c.Get("imblueifIweregreenIwoulddie")
}

func TestOnPurge_1(t *testing.T) {
	c := New(1)
	defer c.Close()
	var x, y purgeable
	c.Set("x", &x)
	c.Set("y", &y)
	syncCache(c)
	if !x.purged {
		t.Error("Element was not purged from full cache")
	}
	if x.why != CACHEFULL {
		t.Error("Element should have been purged but was deleted")
	}

	checkDLL(t, c)
}

func TestOnPurge_2(t *testing.T) {
	c := New(1)
	defer c.Close()
	var x purgeable
	c.Set("x", &x)
	c.Delete("x")
	syncCache(c)
	if !x.purged {
		t.Error("Element was not deleted from cache")
	}
	if x.why != EXPLICITDELETE {
		t.Error("Element should have been deleted but was purged")
	}

	checkDLL(t, c)
}

// Just test filling a cache with a type that does not implement NotifyPurge
func TestsafeOnPurge(t *testing.T) {
	c := New(1)
	defer c.Close()
	i := varsize(1)
	j := varsize(1)
	c.Set("i", i)
	c.Set("j", j)
	syncCache(c)

	checkDLL(t, c)
}

func TestSize(t *testing.T) {
	c := New(100)
	defer c.Close()
	// sum(0..14) = 105
	for i := 1; i < 15; i++ {
		c.Set(strconv.Itoa(i), varsize(i))
	}
	syncCache(c)
	// At this point, expect {0, 1, 2, 3} to be purged
	if c.Size() != 99 {
		t.Errorf("Unexpected size: %d", c.Size())
	}
	for i := 0; i < 4; i++ {
		if _, err := c.Get(strconv.Itoa(i)); err != ErrNotFound {
			t.Errorf("Expected %d to be purged", i)
		}
	}
	for i := 4; i < 15; i++ {
		if _, err := c.Get(strconv.Itoa(i)); err != nil {
			t.Errorf("Expected %d to be cached", i)
		}
	}

	checkDLL(t, c)
}

func TestOnMiss(t *testing.T) {
	c := New(10)
	defer c.Close()
	// Expected cache misses (arbitrary value)
	misses := map[string]int{}
	for i := 5; i < 10; i++ {
		misses[strconv.Itoa(i)] = 0
	}
	c.OnMiss(func(id string) (Cacheable, error) {
		if _, ok := misses[id]; !ok {
			return nil, nil
		}
		delete(misses, id)
		i, err := strconv.Atoi(id)
		if err != nil {
			return nil, errors.New("Illegal id: " + id)
		}
		return i, nil
	})
	for i := 0; i < 5; i++ {
		c.Set(strconv.Itoa(i), i)
	}
	for i := 0; i < 10; i++ {
		x, err := c.Get(strconv.Itoa(i))
		switch err {
		case nil:
			break
		case ErrNotFound:
			t.Errorf("Unexpected cache miss for %d", i)
			continue
		default:
			t.Fatal(err)
		}
		if j := x.(int); j != i {
			t.Errorf("Illegal cache value: expected %d, got %d", i, j)
		}
	}
	for k := range misses {
		t.Errorf("Expected %s to miss", k)
	}

	checkDLL(t, c)
}

func TestOnMissDuplicate(t *testing.T) {
	c := New(10)
	defer c.Close()
	onmisscount := 0
	c.OnMiss(func(id string) (Cacheable, error) {
		onmisscount += 1
		return onmisscount, nil
	})
	c.Get("key")
	c.Get("key")
	c.Get("key")
	c.Get("key")
	c.Get("key")
	o, _ := c.Get("key")
	count := o.(int)
	if count != 1 {
		t.Error("OnMiss handler was not called exactly once:", count)
	}
}

func TestOnMissError(t *testing.T) {
	c := New(10)
	defer c.Close()
	myerr := errors.New("some error")
	onmisscount := 0
	c.OnMiss(func(id string) (Cacheable, error) {
		onmisscount += 1
		return onmisscount, myerr
	})
	c.Get("key")
	c.Get("key")
	c.Get("key")
	o, err := c.Get("key")
	if err != myerr {
		t.Error("Unexpected error from OnMiss through Get:", err)
	}
	count, ok := o.(int)
	if !ok {
		t.Fatal("Value not returned from Get when OnMiss returns error")
	}
	if count != 4 {
		t.Error("OnMiss handler was not called repeatedly on error:", count)
	}
}

func TestOnMissConcurrent(t *testing.T) {
	c := New(10)
	defer c.Close()
	ch := make(chan int)
	// If key foo is requested but not cached, read it from the channel
	c.OnMiss(func(id string) (Cacheable, error) {
		if id == "foo" {
			// Indicate that we want a value
			ch <- 0
			return <-ch, nil
		}
		return nil, nil
	})
	go func() {
		c.Get("foo")
	}()
	<-ch
	// Now we know for sure: a goroutine is blocking on c.Get("foo").
	// But other cache operations should be unaffected:
	c.Set("bar", 10)
	// Unlock that poor blocked goroutine
	ch <- 3
	go func() { <-ch; ch <- 10 }()
	result, err := c.Get("foo")
	switch {
	case err != nil:
		t.Error(`Error while fetching "foo":`, err)
	case result != 10:
		t.Error("Expected 10, got:", result)
	}

	checkDLL(t, c)
}

func TestZeroSize(t *testing.T) {
	c := New(2)
	defer c.Close()
	c.Set("a", varsize(0))
	c.Set("b", varsize(1))
	c.Set("c", varsize(2))
	if _, err := c.Get("a"); err != nil {
		t.Error("Purged element with size=0; should have left in cache")
	}
	c.Delete("a")
	c.Set("d", varsize(2))
	if _, err := c.Get("c"); err != ErrNotFound {
		t.Error("Kept `c' around for too long after removing empty element")
	}
	if _, err := c.Get("d"); err != nil {
		t.Error("Failed to cache `d' after removing empty element")
	}

	checkDLL(t, c)
}

func checkDLL(t *testing.T, c *Cache) {
	if c.mostRU == nil && c.leastRU == nil {
		return
	}
	if c.mostRU.younger != nil {
		t.Fatal("cache inconsistent: most recently used element has a younger sibling")
	}
	if c.leastRU.older != nil {
		t.Fatal("cache inconsistent: least recently used element has an older sibling")
	}
	for p := c.mostRU; p != c.leastRU; p = p.older {
		if p.older.younger != p {
			t.Fatalf("cache inconsistent: older-younger sibling relation violated")
		}
	}
}

func leakingGoroutinesHelper() {
	c := New(100)
	c.Set("foo", 123)
}

func TestLeakingGoroutines(t *testing.T) {
	n := runtime.NumGoroutine()
	for i := 0; i < 100; i++ {
		leakingGoroutinesHelper()
	}
	// seduce the garbage collector
	time.Sleep(time.Second)
	runtime.GC()
	runtime.Gosched()
	time.Sleep(time.Second)
	n2 := runtime.NumGoroutine()
	leak := n2 - n
	// TODO: Why is this 1 no matter how many caches are created and cleaned up?
	// To see what I mean run this test in isolation (go test -run TestLeak);
	// leak will always be exactly 1.  When run alongside other tests their
	// garbage is also cleaned up here so leak can be less than 0---that's okay.
	if leak > 1 { // I'd like to test >0 here :(
		// Not .Error because garbage collection is not spec'ed yet (see doc for
		// New()).
		t.Log("leaking goroutines:", leak)
		//panic("dumping goroutine stacks")
	}
}

func benchmarkGet(b *testing.B, conc int) {
	b.StopTimer()
	// Size doesn't matter (that's what she said)
	c := New(1000)
	defer c.Close()
	c.Set("x", 1)
	syncCache(c)
	var wg sync.WaitGroup
	wg.Add(conc)
	b.StartTimer()
	for i := 0; i < conc; i++ {
		go func() {
			for i := 0; i < b.N/conc; i++ {
				c.Get("x")
			}
			syncCache(c)
			wg.Done()
		}()
	}
	wg.Wait()
}

func benchmarkSet(b *testing.B, conc int) {
	b.StopTimer()
	// Size matters.
	c := New(int64(b.N) / 4)
	defer c.Close()
	syncCache(c)
	var wg sync.WaitGroup
	wg.Add(conc)
	b.StartTimer()
	for i := 0; i < conc; i++ {
		go func() {
			for i := 0; i < b.N/conc; i++ {
				c.Set(strconv.Itoa(i), i)
			}
			wg.Done()
		}()
	}
	wg.Wait()
	syncCache(c)
}

func benchmarkAll(b *testing.B, conc int) {
	b.StopTimer()
	// Size is definitely important, but what is the right size?
	c := New(int64(b.N) / 4)
	defer c.Close()
	syncCache(c)
	var wg sync.WaitGroup
	wg.Add(conc)
	b.StartTimer()
	for i := 0; i < conc; i++ {
		go func() {
			for i := 0; i < b.N/3/conc; i++ {
				c.Set(strconv.Itoa(rand.Int()), 1)
				c.Get(strconv.Itoa(rand.Int()))
				c.Delete(strconv.Itoa(rand.Int()))
			}
			wg.Done()
		}()
	}
	wg.Wait()
	syncCache(c)
}

func BenchmarkGet(b *testing.B) {
	benchmarkGet(b, 1)
}

func Benchmark10ConcurrentGet(b *testing.B) {
	benchmarkGet(b, 10)
}

func Benchmark100ConcurrentGet(b *testing.B) {
	benchmarkGet(b, 100)
}

func Benchmark1KConcurrentGet(b *testing.B) {
	benchmarkGet(b, 1000)
}

func Benchmark10KConcurrentGet(b *testing.B) {
	benchmarkGet(b, 10000)
}

func BenchmarkSet(b *testing.B) {
	benchmarkSet(b, 1)
}

func Benchmark10ConcurrentSet(b *testing.B) {
	benchmarkSet(b, 10)
}

func Benchmark100ConcurrentSet(b *testing.B) {
	benchmarkSet(b, 100)
}

func Benchmark1KConcurrentSet(b *testing.B) {
	benchmarkSet(b, 10000)
}

func Benchmark10KConcurrentSet(b *testing.B) {
	benchmarkSet(b, 10000)
}

func BenchmarkAll(b *testing.B) {
	benchmarkAll(b, 1)
}

func Benchmark10ConcurrentAll(b *testing.B) {
	benchmarkAll(b, 10)
}

func Benchmark100ConcurrentAll(b *testing.B) {
	benchmarkAll(b, 100)
}

func Benchmark1KConcurrentAll(b *testing.B) {
	benchmarkAll(b, 1000)
}

func Benchmark10KConcurrentAll(b *testing.B) {
	benchmarkAll(b, 10000)
}
<|endoftext|>


<commit_before>// Package pure provides Lua functions for running commands and listing files
package pure

import (
	log "github.com/sirupsen/logrus"
	"github.com/yuin/gopher-lua"
)

// Extra Lua functions

const luacode = `
-- Given the name of a python script in the same directory,
-- return the outputted lines as a table
function py(filename)
  if filename == nil then
    return {}
  end
  local cmd = "python " .. scriptdir() .. "/" .. filename
  local f = assert(io.popen(cmd, 'r'))
  local a = {}
  for line in f:lines() do
    table.insert(a, line)
  end
  f:close()
  return a
end

-- Given the name of an executable (or executable script) in the same directory,
-- return the outputted lines as a table
function run(given_command)
  if given_command == nil then
    return {}
  end
  local cmd = "cd " .. scriptdir() .. "; " .. given_command
  local f = assert(io.popen(cmd, 'r'))
  local a = {}
  for line in f:lines() do
    table.insert(a, line)
  end
  f:close()
  return a
end

-- List a table
function dir(t)
  if t == nil then
    t = _G
  end
  local output = {}
  for k, v in pairs(t) do
	table.insert(output, string.format("%-16s\t->\t%s", tostring(k), tostring(v)))
  end
  return table.concat(output, "\n")
end
`

// Load makes functions for running commands, python code or listing files to
// the given Lua state struct: py, run and dir
func Load(L *lua.LState) {
	if err := L.DoString(luacode); err != nil {
		log.Error("Could not load Lua extras!")
		log.Error(err)
	}
}
<commit_msg>Use Errorf instead of Error when logging<commit_after>// Package pure provides Lua functions for running commands and listing files
package pure

import (
	log "github.com/sirupsen/logrus"
	"github.com/yuin/gopher-lua"
)

// Extra Lua functions
const luacode = `
-- Given the name of a python script in the same directory,
-- return the outputted lines as a table
function py(filename)
  if filename == nil then
    return {}
  end
  local cmd = "python " .. scriptdir() .. "/" .. filename
  local f = assert(io.popen(cmd, 'r'))
  local a = {}
  for line in f:lines() do
    table.insert(a, line)
  end
  f:close()
  return a
end

-- Given the name of an executable (or executable script) in the same directory,
-- return the outputted lines as a table
function run(given_command)
  if given_command == nil then
    return {}
  end
  local cmd = "cd " .. scriptdir() .. "; " .. given_command
  local f = assert(io.popen(cmd, 'r'))
  local a = {}
  for line in f:lines() do
    table.insert(a, line)
  end
  f:close()
  return a
end

-- List a table
function dir(t)
  if t == nil then
    t = _G
  end
  local output = {}
  for k, v in pairs(t) do
	table.insert(output, string.format("%-16s\t->\t%s", tostring(k), tostring(v)))
  end
  return table.concat(output, "\n")
end
`

// Load makes functions for running commands, python code or listing files to
// the given Lua state struct: py, run and dir
func Load(L *lua.LState) {
	if err := L.DoString(luacode); err != nil {
		log.Errorf("Could not load extra Lua functions: %s", err)
	}
}
<|endoftext|>


<commit_before>package cmd

import (
	"github.com/qiniu/qshell/v2/docs"
	"github.com/qiniu/qshell/v2/iqshell"
	"github.com/qiniu/qshell/v2/iqshell/aws"
	"github.com/spf13/cobra"
)

// NewCmdAwsFetch 返回一个cobra.Command指针
func awsFetchCmdBuilder(cfg *iqshell.Config) *cobra.Command {
	info := aws.FetchInfo{}
	cmd := &cobra.Command{
		Use:   "awsfetch [-p <Prefix>] [-n <maxKeys>] [-m <ContinuationToken>] [-c <threadCount>][-u <Qiniu UpHost>] -S <AwsSecretKey> -A <AwsID> <awsBucket> <awsRegion> <qiniuBucket>",
		Short: "Copy data from AWS bucket to qiniu bucket",
		Run: func(cmd *cobra.Command, args []string) {
			cfg.CmdCfg.CmdId = docs.AwsFetch
			info.BatchInfo.Force = true
			if len(args) > 0 {
				info.AwsBucketInfo.Bucket = args[0]
			}
			if len(args) > 1 {
				info.AwsBucketInfo.Region = args[1]
			}
			if len(args) > 2 {
				info.QiniuBucket = args[2]
			}
			aws.Fetch(cfg, info)
		},
	}

	cmd.Flags().StringVarP(&info.AwsBucketInfo.Prefix, "prefix", "p", "", "list AWS bucket with this prefix if set")
	cmd.Flags().Int64VarP(&info.AwsBucketInfo.MaxKeys, "max-keys", "n", 1000, "list AWS bucket with numbers of keys returned each time limited by this number if set")
	cmd.Flags().StringVarP(&info.AwsBucketInfo.CToken, "continuation-token", "m", "", "AWS list continuation token")
	cmd.Flags().IntVarP(&info.BatchInfo.WorkerCount, "thread-count", "c", 20, "maximum of fetch thread")
	cmd.Flags().StringVarP(&info.Host, "up-host", "u", "", "Qiniu fetch up host")
	cmd.Flags().StringVarP(&info.AwsBucketInfo.SecretKey, "aws-secret-key", "S", "", "AWS secret key")
	cmd.Flags().StringVarP(&info.AwsBucketInfo.Id, "aws-id", "A", "", "AWS ID")
	cmd.Flags().StringVarP(&info.BatchInfo.SuccessExportFilePath, "success-list", "s", "", "success fetch key list")
	cmd.Flags().StringVarP(&info.BatchInfo.FailExportFilePath, "failure-list", "e", "", "error fetch key list")
	cmd.Flags().BoolVarP(&info.BatchInfo.EnableRecord, "enable-record", "", false, "record work progress, and do from last progress while retry")
	cmd.Flags().BoolVarP(&info.BatchInfo.RecordRedoWhileError, "record-redo-while-error", "", false, "when re-executing the command and checking the command task progress record, if a task has already been done and failed, the task will be re-executed. The default is false, and the task will not be re-executed when it detects that the task fails")

	return cmd
}

// NewCmdAwsList 返回一个cobra.Command指针
// 该命令列举亚马逊存储空间中的文件, 会忽略目录

func awsListCmdBuilder(cfg *iqshell.Config) *cobra.Command {
	info := aws.ListBucketInfo{}
	cmd := &cobra.Command{
		Use:   "awslist [-p <Prefix>] [-n <maxKeys>] [-m <ContinuationToken>] -S <AwsSecretKey> -A <AwsID> <awsBucket> <awsRegion>",
		Short: "List Objects in AWS bucket",
		Run: func(cmd *cobra.Command, args []string) {
			cfg.CmdCfg.CmdId = docs.AwsList
			if len(args) > 0 {
				info.Bucket = args[0]
			}
			if len(args) > 1 {
				info.Region = args[1]
			}
			aws.ListBucket(cfg, info)
		},
	}
	cmd.Flags().StringVarP(&info.Prefix, "prefix", "p", "", "list AWS bucket with this prefix if set")
	cmd.Flags().Int64VarP(&info.MaxKeys, "max-keys", "n", 1000, "list AWS bucket with numbers of keys returned each time limited by this number if set")
	cmd.Flags().StringVarP(&info.CToken, "continuation-token", "m", "", "AWS list continuation token")
	cmd.Flags().StringVarP(&info.SecretKey, "aws-secret-key", "S", "", "AWS secret key")
	cmd.Flags().StringVarP(&info.Id, "aws-id", "A", "", "AWS ID")
	return cmd
}

func init() {
	registerLoader(awsCmdLoader)
}

func awsCmdLoader(superCmd *cobra.Command, cfg *iqshell.Config) {
	superCmd.AddCommand(
		awsFetchCmdBuilder(cfg),
		awsListCmdBuilder(cfg),
	)
}
<commit_msg>optimize aws code style<commit_after>package cmd

import (
	"github.com/qiniu/qshell/v2/docs"
	"github.com/qiniu/qshell/v2/iqshell"
	"github.com/qiniu/qshell/v2/iqshell/aws"
	"github.com/spf13/cobra"
)

// NewCmdAwsFetch 返回一个cobra.Command指针
func awsFetchCmdBuilder(cfg *iqshell.Config) *cobra.Command {
	info := aws.FetchInfo{}
	cmd := &cobra.Command{
		Use:   "awsfetch [-p <Prefix>] [-n <maxKeys>] [-m <ContinuationToken>] [-c <threadCount>][-u <Qiniu UpHost>] -S <AwsSecretKey> -A <AwsID> <awsBucket> <awsRegion> <qiniuBucket>",
		Short: "Copy data from AWS bucket to qiniu bucket",
		Run: func(cmd *cobra.Command, args []string) {
			cfg.CmdCfg.CmdId = docs.AwsFetch
			info.BatchInfo.Force = true
			if len(args) > 0 {
				info.AwsBucketInfo.Bucket = args[0]
			}
			if len(args) > 1 {
				info.AwsBucketInfo.Region = args[1]
			}
			if len(args) > 2 {
				info.QiniuBucket = args[2]
			}
			aws.Fetch(cfg, info)
		},
	}

	cmd.Flags().StringVarP(&info.AwsBucketInfo.Prefix, "prefix", "p", "", "list AWS bucket with this prefix if set")
	cmd.Flags().Int64VarP(&info.AwsBucketInfo.MaxKeys, "max-keys", "n", 1000, "list AWS bucket with numbers of keys returned each time limited by this number if set")
	cmd.Flags().StringVarP(&info.AwsBucketInfo.CToken, "continuation-token", "m", "", "AWS list continuation token")
	cmd.Flags().IntVarP(&info.BatchInfo.WorkerCount, "thread-count", "c", 20, "maximum of fetch thread")
	cmd.Flags().StringVarP(&info.Host, "up-host", "u", "", "Qiniu fetch up host")
	cmd.Flags().StringVarP(&info.AwsBucketInfo.SecretKey, "aws-secret-key", "S", "", "AWS secret key")
	cmd.Flags().StringVarP(&info.AwsBucketInfo.Id, "aws-id", "A", "", "AWS ID")
	cmd.Flags().StringVarP(&info.BatchInfo.SuccessExportFilePath, "success-list", "s", "", "success fetch key list")
	cmd.Flags().StringVarP(&info.BatchInfo.FailExportFilePath, "failure-list", "e", "", "error fetch key list")
	cmd.Flags().BoolVarP(&info.BatchInfo.EnableRecord, "enable-record", "", false, "record work progress, and do from last progress while retry")
	cmd.Flags().BoolVarP(&info.BatchInfo.RecordRedoWhileError, "record-redo-while-error", "", false, "when re-executing the command and checking the command task progress record, if a task has already been done and failed, the task will be re-executed. The default is false, and the task will not be re-executed when it detects that the task fails")

	return cmd
}

// NewCmdAwsList 返回一个cobra.Command指针
// 该命令列举亚马逊存储空间中的文件, 会忽略目录
func awsListCmdBuilder(cfg *iqshell.Config) *cobra.Command {
	info := aws.ListBucketInfo{}
	cmd := &cobra.Command{
		Use:   "awslist [-p <Prefix>] [-n <maxKeys>] [-m <ContinuationToken>] -S <AwsSecretKey> -A <AwsID> <awsBucket> <awsRegion>",
		Short: "List Objects in AWS bucket",
		Run: func(cmd *cobra.Command, args []string) {
			cfg.CmdCfg.CmdId = docs.AwsList
			if len(args) > 0 {
				info.Bucket = args[0]
			}
			if len(args) > 1 {
				info.Region = args[1]
			}
			aws.ListBucket(cfg, info)
		},
	}
	cmd.Flags().StringVarP(&info.Prefix, "prefix", "p", "", "list AWS bucket with this prefix if set")
	cmd.Flags().Int64VarP(&info.MaxKeys, "max-keys", "n", 1000, "list AWS bucket with numbers of keys returned each time limited by this number if set")
	cmd.Flags().StringVarP(&info.CToken, "continuation-token", "m", "", "AWS list continuation token")
	cmd.Flags().StringVarP(&info.SecretKey, "aws-secret-key", "S", "", "AWS secret key")
	cmd.Flags().StringVarP(&info.Id, "aws-id", "A", "", "AWS ID")
	return cmd
}

func init() {
	registerLoader(awsCmdLoader)
}

func awsCmdLoader(superCmd *cobra.Command, cfg *iqshell.Config) {
	superCmd.AddCommand(
		awsFetchCmdBuilder(cfg),
		awsListCmdBuilder(cfg),
	)
}
<|endoftext|>


<commit_before>package cmd

import (
	"fmt"
	"os"
	"os/exec"
	"runtime"
	"strings"
	"syscall"

	"github.com/github/hub/ui"
	"github.com/github/hub/utils"
	"github.com/kballard/go-shellquote"
)

type Cmd struct {
	Name   string
	Args   []string
	Stdin  *os.File
	Stdout *os.File
	Stderr *os.File
}

func (cmd Cmd) String() string {
	return fmt.Sprintf("%s %s", cmd.Name, strings.Join(cmd.Args, " "))
}

func (cmd *Cmd) WithArg(arg string) *Cmd {
	cmd.Args = append(cmd.Args, arg)

	return cmd
}

func (cmd *Cmd) WithArgs(args ...string) *Cmd {
	for _, arg := range args {
		cmd.WithArg(arg)
	}

	return cmd
}

func (cmd *Cmd) CombinedOutput() (string, error) {
	verboseLog(cmd)
	output, err := exec.Command(cmd.Name, cmd.Args...).CombinedOutput()

	return string(output), err
}

func (cmd *Cmd) Success() bool {
	verboseLog(cmd)
	err := exec.Command(cmd.Name, cmd.Args...).Run()
	return err == nil
}

// Run runs command with `Exec` on platforms except Windows
// which only supports `Spawn`
func (cmd *Cmd) Run() error {
	if runtime.GOOS == "windows" {
		return cmd.Spawn()
	} else {
		return cmd.Exec()
	}
}

// Spawn runs command with spawn(3)
func (cmd *Cmd) Spawn() error {
	verboseLog(cmd)
	c := exec.Command(cmd.Name, cmd.Args...)
	c.Stdin = cmd.Stdin
	c.Stdout = cmd.Stdout
	c.Stderr = cmd.Stderr

	return c.Run()
}

// Exec runs command with exec(3)
// Note that Windows doesn't support exec(3): http://golang.org/src/pkg/syscall/exec_windows.go#L339
func (cmd *Cmd) Exec() error {
	verboseLog(cmd)

	binary, err := exec.LookPath(cmd.Name)
	if err != nil {
		return &exec.Error{
			Name: cmd.Name,
			Err:  fmt.Errorf("command not found"),
		}
	}

	args := []string{binary}
	args = append(args, cmd.Args...)

	return syscall.Exec(binary, args, os.Environ())
}

func New(cmd string) *Cmd {
	cmds, err := shellquote.Split(cmd)
	utils.Check(err)

	name := cmds[0]
	args := cmds[1:]

	return &Cmd{Name: name, Args: args, Stdin: os.Stdin, Stdout: os.Stdout, Stderr: os.Stderr}
}

func NewWithArray(cmd []string) *Cmd {
	return &Cmd{Name: cmd[0], Args: cmd[1:], Stdin: os.Stdin, Stdout: os.Stdout, Stderr: os.Stderr}
}

func verboseLog(cmd *Cmd) {
	if os.Getenv("HUB_VERBOSE") != "" {
		msg := fmt.Sprintf("$ %s %s", cmd.Name, strings.Join(cmd.Args, " "))
		if ui.IsTerminal(os.Stderr) {
			msg = fmt.Sprintf("\033[35m%s\033[0m", msg)
		}
		ui.Errorln(msg)
	}
}
<commit_msg>Fix subcommand execution under WSL<commit_after>package cmd

import (
	"fmt"
	"os"
	"os/exec"
	"runtime"
	"strings"
	"syscall"

	"github.com/github/hub/ui"
	"github.com/github/hub/utils"
	"github.com/kballard/go-shellquote"
)

type Cmd struct {
	Name   string
	Args   []string
	Stdin  *os.File
	Stdout *os.File
	Stderr *os.File
}

func (cmd Cmd) String() string {
	return fmt.Sprintf("%s %s", cmd.Name, strings.Join(cmd.Args, " "))
}

func (cmd *Cmd) WithArg(arg string) *Cmd {
	cmd.Args = append(cmd.Args, arg)

	return cmd
}

func (cmd *Cmd) WithArgs(args ...string) *Cmd {
	for _, arg := range args {
		cmd.WithArg(arg)
	}

	return cmd
}

func (cmd *Cmd) CombinedOutput() (string, error) {
	verboseLog(cmd)
	output, err := exec.Command(cmd.Name, cmd.Args...).CombinedOutput()

	return string(output), err
}

func (cmd *Cmd) Success() bool {
	verboseLog(cmd)
	err := exec.Command(cmd.Name, cmd.Args...).Run()
	return err == nil
}

// Run runs command with `Exec` on platforms except Windows
// which only supports `Spawn`
func (cmd *Cmd) Run() error {
	if isWindows() {
		return cmd.Spawn()
	} else {
		return cmd.Exec()
	}
}

func isWindows() bool {
	return runtime.GOOS == "windows" || detectWSL()
}

var detectedWSL bool
var detectedWSLContents string

// https://github.com/Microsoft/WSL/issues/423#issuecomment-221627364
func detectWSL() bool {
	if !detectedWSL {
		b := make([]byte, 1024)
		f, err := os.Open("/proc/version")
		if err == nil {
			f.Read(b)
			f.Close()
			detectedWSLContents = string(b)
		}
		detectedWSL = true
	}
	return strings.Contains(detectedWSLContents, "Microsoft")
}

// Spawn runs command with spawn(3)
func (cmd *Cmd) Spawn() error {
	verboseLog(cmd)
	c := exec.Command(cmd.Name, cmd.Args...)
	c.Stdin = cmd.Stdin
	c.Stdout = cmd.Stdout
	c.Stderr = cmd.Stderr

	return c.Run()
}

// Exec runs command with exec(3)
// Note that Windows doesn't support exec(3): http://golang.org/src/pkg/syscall/exec_windows.go#L339
func (cmd *Cmd) Exec() error {
	verboseLog(cmd)

	binary, err := exec.LookPath(cmd.Name)
	if err != nil {
		return &exec.Error{
			Name: cmd.Name,
			Err:  fmt.Errorf("command not found"),
		}
	}

	args := []string{binary}
	args = append(args, cmd.Args...)

	return syscall.Exec(binary, args, os.Environ())
}

func New(cmd string) *Cmd {
	cmds, err := shellquote.Split(cmd)
	utils.Check(err)

	name := cmds[0]
	args := cmds[1:]

	return &Cmd{Name: name, Args: args, Stdin: os.Stdin, Stdout: os.Stdout, Stderr: os.Stderr}
}

func NewWithArray(cmd []string) *Cmd {
	return &Cmd{Name: cmd[0], Args: cmd[1:], Stdin: os.Stdin, Stdout: os.Stdout, Stderr: os.Stderr}
}

func verboseLog(cmd *Cmd) {
	if os.Getenv("HUB_VERBOSE") != "" {
		msg := fmt.Sprintf("$ %s %s", cmd.Name, strings.Join(cmd.Args, " "))
		if ui.IsTerminal(os.Stderr) {
			msg = fmt.Sprintf("\033[35m%s\033[0m", msg)
		}
		ui.Errorln(msg)
	}
}
<|endoftext|>


<commit_before>package cmd

import (
	"fmt"

	"github.com/spf13/cobra"
	"go/build"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
)

// newCmd represents the new command
var newCmd = &cobra.Command{
	Use:   "new",
	Short: "Create a new mercurius project",
	Long:  `Create a new mercurius project`,
	Run: func(cmd *cobra.Command, args []string) {
		// checking number of arguments
		if len(args) > 0 {
			fmt.Println("Too many arguments")
			os.Exit(-1)
		}
		initGoPaths()
		setApplicationPath()
		copyNewAppFiles(confValues())
	},
}

const mercuriusPath = "github.com/novatrixtech/mercurius"

var (
	// go related paths
	gopath  string
	srcRoot string

	// mercurius related paths
	mercuriusPkg *build.Package
	appPath      string
	appName      string
	basePath     string
	importPath   string
	skeletonPath string
)

// lookup and set Go related variables
func initGoPaths() {
	// lookup go path
	gopath = build.Default.GOPATH
	if gopath == "" {
		fmt.Println("Abort: GOPATH environment variable is not set. " +
			"Please refer to http://golang.org/doc/code.html to configure your Go environment.")
		os.Exit(-1)
	}

	// check for go executable
	var err error
	_, err = exec.LookPath("go")
	if err != nil {
		fmt.Println("Go executable not found in PATH.")
		os.Exit(-1)
	}

	//support relative path
	workingDir, _ := os.Getwd()
	goPathList := filepath.SplitList(gopath)
	for _, path := range goPathList {
		if strings.HasPrefix(strings.ToLower(workingDir), strings.ToLower(path)) {
			srcRoot = path
			break
		}

		path, _ = filepath.EvalSymlinks(path)
		if len(path) > 0 && strings.HasPrefix(strings.ToLower(workingDir), strings.ToLower(path)) {
			srcRoot = path
			break
		}
	}

	if len(srcRoot) == 0 {
		fmt.Println("Abort: could not create a Mercurius application outside of GOPATH.")
		os.Exit(-1)
	}

	// set go src path
	srcRoot = filepath.Join(srcRoot, "src")
}

func setApplicationPath() {
	var err error
	appName = terminal("What is your application name?", "")
	gitPath := terminal("What is your git or mercurial host?", "github.com")
	gitUser := terminal("What is your git or mercurial username?", "")

	//check if gitUser is not empty to put gitUser between slashes
	if gitUser != "" {
		gitUser = fmt.Sprintf("/%s", gitUser)
	}

	//build import path
	importPath = fmt.Sprintf("%s%s/%s", gitPath, gitUser, appName)

	//check if import path is valid
	if importPath == "" {
		fmt.Println("Abort: could not create a Mercurius application with empty application path.")
		os.Exit(-1)
	}

	// validate relative path, we cannot use built-in functions
	// since Go import path is valid relative path too.
	// so check basic part of the path, which is "."
	if filepath.IsAbs(importPath) || strings.HasPrefix(importPath, ".") {
		fmt.Printf("Abort: '%s' looks like a directory.  Please provide a Go import path instead.\n", importPath)
		os.Exit(-1)
	}

	_, err = build.Import(importPath, "", build.FindOnly)
	if err == nil {
		fmt.Printf("Alert: Import path %s already exists.\n", importPath)
	}

	mercuriusPkg, err = build.Import(mercuriusPath, "", build.FindOnly)
	if err != nil {
		fmt.Printf("Abort: Could not find Mercurius source code: %s\n", err)
		os.Exit(-1)
	}

	appPath = filepath.Join(srcRoot, filepath.FromSlash(importPath))
	basePath = filepath.ToSlash(filepath.Dir(importPath))

	if basePath == "." {
		// we need to remove the a single '.' when
		// the app is in the $GOROOT/src directory
		basePath = ""
	} else {
		// we need to append a '/' when the app is
		// is a subdirectory such as $GOROOT/src/path/to/mercurius
		basePath += "/"
	}
	// set base project path
	skeletonPath = filepath.Join(mercuriusPkg.Dir, "skeleton")
}

func copyNewAppFiles(cfgs map[string]interface{}) {
	var err error
	err = os.MkdirAll(appPath, 0777)
	if err != nil {
		panic(err)
	}

	mustCopyDir(appPath, skeletonPath, cfgs)

	// Dotfiles are skipped by mustCopyDir, so we have to explicitly copy the .gitignore.
	gitignore := ".gitignore"
	mustCopyFile(filepath.Join(appPath, gitignore), filepath.Join(skeletonPath, gitignore))

}

func confValues() map[string]interface{} {
	// Define default value for cache types
	// See https://go-macaron.com/docs/middlewares/cache for details
	cacheMap := map[string]string{
		"memory":   "",
		"file":     "data/caches",
		"redis":    "addr=127.0.0.1:6379",
		"memcache": "127.0.0.1:11211",
	}

	// define configs
	cfgs := make(map[string]interface{})
	cfgs["AppPath"] = importPath
	cfgs["AppName"] = appName
	cfgs["DBType"] = terminal("What database do you want to use?", "mysql")
	cfgs["DBUser"] = terminal("What is your database user?", "root")
	cfgs["DBPw"] = terminal("What is your database password?", "")
	cfgs["DBName"] = terminal("What is your database name?", "")
	cfgs["DBHost"] = terminal("What is your database host?", "localhost")
	cfgs["DBPort"] = terminal("What is your database port?", "3306")
	cfgs["MaxConn"] = terminal("What is your database max connection pool?", "10")
	cfgs["IdleConn"] = terminal("What is your database idle connection pool?", "10")
	cache := terminal("What cache do you want to use?", "memory")
	cfgs["CacheType"] = cache
	cfgs["CacheCfgs"] = terminal("What is your cache server address?", cacheMap[cache])
	cfgs["Key"] = terminal("What is your oauth key (key size must be 16 or 32)?", "")
	cfgs["HttpPort"] = terminal("What is your HTTP port?", "8080")
	return cfgs
}

func init() {
	RootCmd.AddCommand(newCmd)
}
<commit_msg>implementing checker for generated project<commit_after>package cmd

import (
	"fmt"

	"github.com/spf13/cobra"
	"go/build"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	"runtime"
)

// newCmd represents the new command
var newCmd = &cobra.Command{
	Use:   "new",
	Short: "Create a new mercurius project",
	Long:  `Create a new mercurius project`,
	Run: func(cmd *cobra.Command, args []string) {
		// checking number of arguments
		if len(args) > 0 {
			fmt.Println("Too many arguments")
			os.Exit(-1)
		}
		initGoPaths()
		setApplicationPath()
		copyNewAppFiles(confValues())
		packageStateCheck()
	},
}

const mercuriusPath = "github.com/novatrixtech/mercurius"

var (
	// go related paths
	gopath  string
	srcRoot string

	// mercurius related paths
	mercuriusPkg *build.Package
	appPath      string
	appName      string
	basePath     string
	importPath   string
	skeletonPath string
)

// lookup and set Go related variables
func initGoPaths() {
	// lookup go path
	gopath = build.Default.GOPATH
	if gopath == "" {
		fmt.Println("Abort: GOPATH environment variable is not set. " +
			"Please refer to http://golang.org/doc/code.html to configure your Go environment.")
		os.Exit(-1)
	}

	// check for go executable
	var err error
	_, err = exec.LookPath("go")
	if err != nil {
		fmt.Println("Go executable not found in PATH.")
		os.Exit(-1)
	}

	//support relative path
	workingDir, _ := os.Getwd()
	goPathList := filepath.SplitList(gopath)
	for _, path := range goPathList {
		if strings.HasPrefix(strings.ToLower(workingDir), strings.ToLower(path)) {
			srcRoot = path
			break
		}

		path, _ = filepath.EvalSymlinks(path)
		if len(path) > 0 && strings.HasPrefix(strings.ToLower(workingDir), strings.ToLower(path)) {
			srcRoot = path
			break
		}
	}

	if len(srcRoot) == 0 {
		fmt.Println("Abort: could not create a Mercurius application outside of GOPATH.")
		os.Exit(-1)
	}

	// set go src path
	srcRoot = filepath.Join(srcRoot, "src")
}

func setApplicationPath() {
	var err error
	appName = terminal("What is your application name?", "")
	gitPath := terminal("What is your git or mercurial host?", "github.com")
	gitUser := terminal("What is your git or mercurial username?", "")

	//check if gitUser is not empty to put gitUser between slashes
	if gitUser != "" {
		gitUser = fmt.Sprintf("/%s", gitUser)
	}

	//build import path
	importPath = fmt.Sprintf("%s%s/%s", gitPath, gitUser, appName)

	//check if import path is valid
	if importPath == "" {
		fmt.Println("Abort: could not create a Mercurius application with empty application path.")
		os.Exit(-1)
	}

	// validate relative path, we cannot use built-in functions
	// since Go import path is valid relative path too.
	// so check basic part of the path, which is "."
	if filepath.IsAbs(importPath) || strings.HasPrefix(importPath, ".") {
		fmt.Printf("Abort: '%s' looks like a directory.  Please provide a Go import path instead.\n", importPath)
		os.Exit(-1)
	}

	_, err = build.Import(importPath, "", build.FindOnly)
	if err == nil {
		fmt.Printf("Alert: Import path %s already exists.\n", importPath)
	}

	mercuriusPkg, err = build.Import(mercuriusPath, "", build.FindOnly)
	if err != nil {
		fmt.Printf("Abort: Could not find Mercurius source code: %s\n", err)
		os.Exit(-1)
	}

	appPath = filepath.Join(srcRoot, filepath.FromSlash(importPath))
	basePath = filepath.ToSlash(filepath.Dir(importPath))

	if basePath == "." {
		// we need to remove the a single '.' when
		// the app is in the $GOROOT/src directory
		basePath = ""
	} else {
		// we need to append a '/' when the app is
		// is a subdirectory such as $GOROOT/src/path/to/mercurius
		basePath += "/"
	}
	// set base project path
	skeletonPath = filepath.Join(mercuriusPkg.Dir, "skeleton")
}

func copyNewAppFiles(cfgs map[string]interface{}) {
	var err error
	err = os.MkdirAll(appPath, 0777)
	if err != nil {
		fmt.Printf("Abort: Could not generate app %s\n", err)
		os.Exit(-1)
	}

	mustCopyDir(appPath, skeletonPath, cfgs)

	// Dotfiles are skipped by mustCopyDir, so we have to explicitly copy the .gitignore.
	gitignore := ".gitignore"
	mustCopyFile(filepath.Join(appPath, gitignore), filepath.Join(skeletonPath, gitignore))

}

func confValues() map[string]interface{} {
	// Define default value for cache types
	// See https://go-macaron.com/docs/middlewares/cache for details
	cacheMap := map[string]string{
		"memory":   "",
		"file":     "data/caches",
		"redis":    "addr=127.0.0.1:6379",
		"memcache": "127.0.0.1:11211",
	}

	// define configs
	cfgs := make(map[string]interface{})
	cfgs["AppPath"] = importPath
	cfgs["AppName"] = appName
	cfgs["DBType"] = terminal("What database do you want to use?", "mysql")
	cfgs["DBUser"] = terminal("What is your database user?", "root")
	cfgs["DBPw"] = terminal("What is your database password?", "")
	cfgs["DBName"] = terminal("What is your database name?", "")
	cfgs["DBHost"] = terminal("What is your database host?", "localhost")
	cfgs["DBPort"] = terminal("What is your database port?", "3306")
	cfgs["MaxConn"] = terminal("What is your database max connection pool?", "10")
	cfgs["IdleConn"] = terminal("What is your database idle connection pool?", "10")
	cache := terminal("What cache do you want to use?", "memory")
	cfgs["CacheType"] = cache
	cfgs["CacheCfgs"] = terminal("What is your cache server address?", cacheMap[cache])
	cfgs["Key"] = terminal("What is your oauth key (key size must be 16 or 32)?", "")
	cfgs["HttpPort"] = terminal("What is your HTTP port?", "8080")
	return cfgs
}

func packageStateCheck() {
	pkg, err := build.Import(importPath, "", build.FindOnly)
	if err != nil {
		fmt.Printf("Abort: Could not find generated app: %s\n", err)
		os.Exit(-1)
	}
	cmd := exec.Command("go", "build", pkg.ImportPath)
	out, _ := cmd.CombinedOutput()
	msg := string(out)
	fmt.Println(msg)
	if msg != "" {
		if runtime.GOOS == "windows" {
			cmd = exec.Command("rd", "/s", "/q", pkg.Dir)
		} else {
			cmd = exec.Command("rm", "-rf", pkg.Dir)
		}
		err := cmd.Run()
		if err != nil {
			fmt.Printf("Abort: %s\n", err)
			os.Exit(-1)
		}
	}

}

func init() {
	RootCmd.AddCommand(newCmd)
}
<|endoftext|>


<commit_before>//
// Copyright 2015, Sander van Harmelen
//
// 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 gitlab

import (
	"fmt"
	"net/url"
	"time"
)

// CommitsService handles communication with the commit related methods
// of the GitLab API.
//
// GitLab API docs: https://docs.gitlab.com/ce/api/commits.html
type CommitsService struct {
	client *Client
}

// Commit represents a GitLab commit.
//
// GitLab API docs: https://docs.gitlab.com/ce/api/commits.html
type Commit struct {
	ID            string     `json:"id"`
	ShortID       string     `json:"short_id"`
	Title         string     `json:"title"`
	AuthorName    string     `json:"author_name"`
	AuthorEmail   string     `json:"author_email"`
	AuthoredDate  *time.Time `json:"authored_date"`
	CommittedDate *time.Time `json:"committed_date"`
	CreatedAt     *time.Time `json:"created_at"`
	Message       string     `json:"message"`
	ParentIDs     []string   `json:"parent_ids"`
}

func (c Commit) String() string {
	return Stringify(c)
}

// ListCommitsOptions represents the available ListCommits() options.
//
// GitLab API docs: https://docs.gitlab.com/ce/api/commits.html#list-repository-commits
type ListCommitsOptions struct {
	ListOptions
	RefName *string   `url:"ref_name,omitempty" json:"ref_name,omitempty"`
	Since   time.Time `url:"since,omitempty" json:"since,omitempty"`
	Until   time.Time `url:"until,omitempty" json:"until,omitempty"`
}

// ListCommits gets a list of repository commits in a project.
//
// GitLab API docs: https://docs.gitlab.com/ce/api/commits.html#list-commits
func (s *CommitsService) ListCommits(pid interface{}, opt *ListCommitsOptions, options ...OptionFunc) ([]*Commit, *Response, error) {
	project, err := parseID(pid)
	if err != nil {
		return nil, nil, err
	}
	u := fmt.Sprintf("projects/%s/repository/commits", url.QueryEscape(project))

	req, err := s.client.NewRequest("GET", u, opt, options)
	if err != nil {
		return nil, nil, err
	}

	var c []*Commit
	resp, err := s.client.Do(req, &c)
	if err != nil {
		return nil, resp, err
	}

	return c, resp, err
}

// GetCommit gets a specific commit identified by the commit hash or name of a
// branch or tag.
//
// GitLab API docs: https://docs.gitlab.com/ce/api/commits.html#get-a-single-commit
func (s *CommitsService) GetCommit(pid interface{}, sha string, options ...OptionFunc) (*Commit, *Response, error) {
	project, err := parseID(pid)
	if err != nil {
		return nil, nil, err
	}
	u := fmt.Sprintf("projects/%s/repository/commits/%s", url.QueryEscape(project), sha)

	req, err := s.client.NewRequest("GET", u, nil, options)
	if err != nil {
		return nil, nil, err
	}

	c := new(Commit)
	resp, err := s.client.Do(req, c)
	if err != nil {
		return nil, resp, err
	}

	return c, resp, err
}

// Diff represents a GitLab diff.
//
// GitLab API docs: https://docs.gitlab.com/ce/api/commits.html
type Diff struct {
	Diff        string `json:"diff"`
	NewPath     string `json:"new_path"`
	OldPath     string `json:"old_path"`
	AMode       string `json:"a_mode"`
	BMode       string `json:"b_mode"`
	NewFile     bool   `json:"new_file"`
	RenamedFile bool   `json:"renamed_file"`
	DeletedFile bool   `json:"deleted_file"`
}

func (d Diff) String() string {
	return Stringify(d)
}

// GetCommitDiff gets the diff of a commit in a project..
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/commits.html#get-the-diff-of-a-commit
func (s *CommitsService) GetCommitDiff(pid interface{}, sha string, options ...OptionFunc) ([]*Diff, *Response, error) {
	project, err := parseID(pid)
	if err != nil {
		return nil, nil, err
	}
	u := fmt.Sprintf("projects/%s/repository/commits/%s/diff", url.QueryEscape(project), sha)

	req, err := s.client.NewRequest("GET", u, nil, options)
	if err != nil {
		return nil, nil, err
	}

	var d []*Diff
	resp, err := s.client.Do(req, &d)
	if err != nil {
		return nil, resp, err
	}

	return d, resp, err
}

// CommitComment represents a GitLab commit comment.
//
// GitLab API docs: https://docs.gitlab.com/ce/api/commits.html
type CommitComment struct {
	Note     string `json:"note"`
	Path     string `json:"path"`
	Line     int    `json:"line"`
	LineType string `json:"line_type"`
	Author   Author `json:"author"`
}

// Author represents a GitLab commit author
type Author struct {
	ID        int        `json:"id"`
	Username  string     `json:"username"`
	Email     string     `json:"email"`
	Name      string     `json:"name"`
	State     string     `json:"state"`
	Blocked   bool       `json:"blocked"`
	CreatedAt *time.Time `json:"created_at"`
}

func (c CommitComment) String() string {
	return Stringify(c)
}

// GetCommitComments gets the comments of a commit in a project.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/commits.html#get-the-comments-of-a-commit
func (s *CommitsService) GetCommitComments(pid interface{}, sha string, options ...OptionFunc) ([]*CommitComment, *Response, error) {
	project, err := parseID(pid)
	if err != nil {
		return nil, nil, err
	}
	u := fmt.Sprintf("projects/%s/repository/commits/%s/comments", url.QueryEscape(project), sha)

	req, err := s.client.NewRequest("GET", u, nil, options)
	if err != nil {
		return nil, nil, err
	}

	var c []*CommitComment
	resp, err := s.client.Do(req, &c)
	if err != nil {
		return nil, resp, err
	}

	return c, resp, err
}

// PostCommitCommentOptions represents the available PostCommitComment()
// options.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/commits.html#post-comment-to-commit
type PostCommitCommentOptions struct {
	Note     *string `url:"note,omitempty" json:"note,omitempty"`
	Path     *string `url:"path" json:"path"`
	Line     *int    `url:"line" json:"line"`
	LineType *string `url:"line_type" json:"line_type"`
}

// PostCommitComment adds a comment to a commit. Optionally you can post
// comments on a specific line of a commit. Therefor both path, line_new and
// line_old are required.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/commits.html#post-comment-to-commit
func (s *CommitsService) PostCommitComment(pid interface{}, sha string, opt *PostCommitCommentOptions, options ...OptionFunc) (*CommitComment, *Response, error) {
	project, err := parseID(pid)
	if err != nil {
		return nil, nil, err
	}
	u := fmt.Sprintf("projects/%s/repository/commits/%s/comments", url.QueryEscape(project), sha)

	req, err := s.client.NewRequest("POST", u, opt, options)
	if err != nil {
		return nil, nil, err
	}

	c := new(CommitComment)
	resp, err := s.client.Do(req, c)
	if err != nil {
		return nil, resp, err
	}

	return c, resp, err
}

// GetCommitStatusesOptions represents the available GetCommitStatuses() options.
//
// GitLab API docs: https://docs.gitlab.com/ce/api/commits.html#get-the-status-of-a-commit
type GetCommitStatusesOptions struct {
	Ref   *string `url:"ref,omitempty" json:"ref,omitempty"`
	Stage *string `url:"stage,omitempty" json:"stage,omitempty"`
	Name  *string `url:"name,omitempty" json:"name,omitempty"`
	All   *bool   `url:"all,omitempty" json:"all,omitempty"`
}

// CommitStatus represents a GitLab commit status.
//
// GitLab API docs: https://docs.gitlab.com/ce/api/commits.html#get-the-status-of-a-commit
type CommitStatus struct {
	ID          int        `json:"id"`
	SHA         string     `json:"sha"`
	Ref         string     `json:"ref"`
	Status      string     `json:"status"`
	Name        string     `json:"name"`
	TargetURL   string     `json:"target_url"`
	Description string     `json:"description"`
	CreatedAt   *time.Time `json:"created_at"`
	StartedAt   *time.Time `json:"started_at"`
	FinishedAt  *time.Time `json:"finished_at"`
	Author      Author     `json:"author"`
}

// GetCommitStatuses gets the statuses of a commit in a project.
//
// GitLab API docs: https://docs.gitlab.com/ce/api/commits.html#get-the-status-of-a-commit
func (s *CommitsService) GetCommitStatuses(pid interface{}, sha string, opt *GetCommitStatusesOptions, options ...OptionFunc) ([]*CommitStatus, *Response, error) {
	project, err := parseID(pid)
	if err != nil {
		return nil, nil, err
	}
	u := fmt.Sprintf("projects/%s/repository/commits/%s/statuses", url.QueryEscape(project), sha)

	req, err := s.client.NewRequest("GET", u, opt, options)
	if err != nil {
		return nil, nil, err
	}

	var cs []*CommitStatus
	resp, err := s.client.Do(req, &cs)
	if err != nil {
		return nil, resp, err
	}

	return cs, resp, err
}

// SetCommitStatusOptions represents the available SetCommitStatus() options.
//
// GitLab API docs: https://docs.gitlab.com/ce/api/commits.html#post-the-status-to-commit
type SetCommitStatusOptions struct {
	State       BuildState `url:"state" json:"state"`
	Ref         *string    `url:"ref,omitempty" json:"ref,omitempty"`
	Name        *string    `url:"name,omitempty" json:"name,omitempty"`
	Context     *string    `url:"context,omitempty" json:"context,omitempty"`
	TargetURL   *string    `url:"target_url,omitempty" json:"target_url,omitempty"`
	Description *string    `url:"description,omitempty" json:"description,omitempty"`
}

// BuildState represents a GitLab build state
type BuildState string

// These constants represent all valid build states
const (
	Pending  BuildState = "pending"
	Running  BuildState = "running"
	Success  BuildState = "success"
	Failed   BuildState = "failed"
	Canceled BuildState = "canceled"
)

// SetCommitStatus sets the status of a commit in a project.
//
// GitLab API docs: https://docs.gitlab.com/ce/api/commits.html#post-the-status-to-commit
func (s *CommitsService) SetCommitStatus(pid interface{}, sha string, opt *SetCommitStatusOptions, options ...OptionFunc) (*CommitStatus, *Response, error) {
	project, err := parseID(pid)
	if err != nil {
		return nil, nil, err
	}
	u := fmt.Sprintf("projects/%s/statuses/%s", url.QueryEscape(project), sha)

	req, err := s.client.NewRequest("POST", u, opt, options)
	if err != nil {
		return nil, nil, err
	}

	var cs *CommitStatus
	resp, err := s.client.Do(req, &cs)
	if err != nil {
		return nil, resp, err
	}

	return cs, resp, err
}
<commit_msg>Update Commit struct<commit_after>//
// Copyright 2015, Sander van Harmelen
//
// 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 gitlab

import (
	"fmt"
	"net/url"
	"time"
)

// CommitsService handles communication with the commit related methods
// of the GitLab API.
//
// GitLab API docs: https://docs.gitlab.com/ce/api/commits.html
type CommitsService struct {
	client *Client
}

// Commit represents a GitLab commit.
//
// GitLab API docs: https://docs.gitlab.com/ce/api/commits.html
type Commit struct {
	ID             string     `json:"id"`
	ShortID        string     `json:"short_id"`
	Title          string     `json:"title"`
	AuthorName     string     `json:"author_name"`
	AuthorEmail    string     `json:"author_email"`
	AuthoredDate   *time.Time `json:"authored_date"`
	CommitterName  string     `json:"committer_name"`
	CommitterEmail string     `json:"committer_email"`
	CommittedDate  *time.Time `json:"committed_date"`
	CreatedAt      *time.Time `json:"created_at"`
	Message        string     `json:"message"`
	ParentIDs      []string   `json:"parent_ids"`
}

func (c Commit) String() string {
	return Stringify(c)
}

// ListCommitsOptions represents the available ListCommits() options.
//
// GitLab API docs: https://docs.gitlab.com/ce/api/commits.html#list-repository-commits
type ListCommitsOptions struct {
	ListOptions
	RefName *string   `url:"ref_name,omitempty" json:"ref_name,omitempty"`
	Since   time.Time `url:"since,omitempty" json:"since,omitempty"`
	Until   time.Time `url:"until,omitempty" json:"until,omitempty"`
}

// ListCommits gets a list of repository commits in a project.
//
// GitLab API docs: https://docs.gitlab.com/ce/api/commits.html#list-commits
func (s *CommitsService) ListCommits(pid interface{}, opt *ListCommitsOptions, options ...OptionFunc) ([]*Commit, *Response, error) {
	project, err := parseID(pid)
	if err != nil {
		return nil, nil, err
	}
	u := fmt.Sprintf("projects/%s/repository/commits", url.QueryEscape(project))

	req, err := s.client.NewRequest("GET", u, opt, options)
	if err != nil {
		return nil, nil, err
	}

	var c []*Commit
	resp, err := s.client.Do(req, &c)
	if err != nil {
		return nil, resp, err
	}

	return c, resp, err
}

// GetCommit gets a specific commit identified by the commit hash or name of a
// branch or tag.
//
// GitLab API docs: https://docs.gitlab.com/ce/api/commits.html#get-a-single-commit
func (s *CommitsService) GetCommit(pid interface{}, sha string, options ...OptionFunc) (*Commit, *Response, error) {
	project, err := parseID(pid)
	if err != nil {
		return nil, nil, err
	}
	u := fmt.Sprintf("projects/%s/repository/commits/%s", url.QueryEscape(project), sha)

	req, err := s.client.NewRequest("GET", u, nil, options)
	if err != nil {
		return nil, nil, err
	}

	c := new(Commit)
	resp, err := s.client.Do(req, c)
	if err != nil {
		return nil, resp, err
	}

	return c, resp, err
}

// Diff represents a GitLab diff.
//
// GitLab API docs: https://docs.gitlab.com/ce/api/commits.html
type Diff struct {
	Diff        string `json:"diff"`
	NewPath     string `json:"new_path"`
	OldPath     string `json:"old_path"`
	AMode       string `json:"a_mode"`
	BMode       string `json:"b_mode"`
	NewFile     bool   `json:"new_file"`
	RenamedFile bool   `json:"renamed_file"`
	DeletedFile bool   `json:"deleted_file"`
}

func (d Diff) String() string {
	return Stringify(d)
}

// GetCommitDiff gets the diff of a commit in a project..
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/commits.html#get-the-diff-of-a-commit
func (s *CommitsService) GetCommitDiff(pid interface{}, sha string, options ...OptionFunc) ([]*Diff, *Response, error) {
	project, err := parseID(pid)
	if err != nil {
		return nil, nil, err
	}
	u := fmt.Sprintf("projects/%s/repository/commits/%s/diff", url.QueryEscape(project), sha)

	req, err := s.client.NewRequest("GET", u, nil, options)
	if err != nil {
		return nil, nil, err
	}

	var d []*Diff
	resp, err := s.client.Do(req, &d)
	if err != nil {
		return nil, resp, err
	}

	return d, resp, err
}

// CommitComment represents a GitLab commit comment.
//
// GitLab API docs: https://docs.gitlab.com/ce/api/commits.html
type CommitComment struct {
	Note     string `json:"note"`
	Path     string `json:"path"`
	Line     int    `json:"line"`
	LineType string `json:"line_type"`
	Author   Author `json:"author"`
}

// Author represents a GitLab commit author
type Author struct {
	ID        int        `json:"id"`
	Username  string     `json:"username"`
	Email     string     `json:"email"`
	Name      string     `json:"name"`
	State     string     `json:"state"`
	Blocked   bool       `json:"blocked"`
	CreatedAt *time.Time `json:"created_at"`
}

func (c CommitComment) String() string {
	return Stringify(c)
}

// GetCommitComments gets the comments of a commit in a project.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/commits.html#get-the-comments-of-a-commit
func (s *CommitsService) GetCommitComments(pid interface{}, sha string, options ...OptionFunc) ([]*CommitComment, *Response, error) {
	project, err := parseID(pid)
	if err != nil {
		return nil, nil, err
	}
	u := fmt.Sprintf("projects/%s/repository/commits/%s/comments", url.QueryEscape(project), sha)

	req, err := s.client.NewRequest("GET", u, nil, options)
	if err != nil {
		return nil, nil, err
	}

	var c []*CommitComment
	resp, err := s.client.Do(req, &c)
	if err != nil {
		return nil, resp, err
	}

	return c, resp, err
}

// PostCommitCommentOptions represents the available PostCommitComment()
// options.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/commits.html#post-comment-to-commit
type PostCommitCommentOptions struct {
	Note     *string `url:"note,omitempty" json:"note,omitempty"`
	Path     *string `url:"path" json:"path"`
	Line     *int    `url:"line" json:"line"`
	LineType *string `url:"line_type" json:"line_type"`
}

// PostCommitComment adds a comment to a commit. Optionally you can post
// comments on a specific line of a commit. Therefor both path, line_new and
// line_old are required.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/commits.html#post-comment-to-commit
func (s *CommitsService) PostCommitComment(pid interface{}, sha string, opt *PostCommitCommentOptions, options ...OptionFunc) (*CommitComment, *Response, error) {
	project, err := parseID(pid)
	if err != nil {
		return nil, nil, err
	}
	u := fmt.Sprintf("projects/%s/repository/commits/%s/comments", url.QueryEscape(project), sha)

	req, err := s.client.NewRequest("POST", u, opt, options)
	if err != nil {
		return nil, nil, err
	}

	c := new(CommitComment)
	resp, err := s.client.Do(req, c)
	if err != nil {
		return nil, resp, err
	}

	return c, resp, err
}

// GetCommitStatusesOptions represents the available GetCommitStatuses() options.
//
// GitLab API docs: https://docs.gitlab.com/ce/api/commits.html#get-the-status-of-a-commit
type GetCommitStatusesOptions struct {
	Ref   *string `url:"ref,omitempty" json:"ref,omitempty"`
	Stage *string `url:"stage,omitempty" json:"stage,omitempty"`
	Name  *string `url:"name,omitempty" json:"name,omitempty"`
	All   *bool   `url:"all,omitempty" json:"all,omitempty"`
}

// CommitStatus represents a GitLab commit status.
//
// GitLab API docs: https://docs.gitlab.com/ce/api/commits.html#get-the-status-of-a-commit
type CommitStatus struct {
	ID          int        `json:"id"`
	SHA         string     `json:"sha"`
	Ref         string     `json:"ref"`
	Status      string     `json:"status"`
	Name        string     `json:"name"`
	TargetURL   string     `json:"target_url"`
	Description string     `json:"description"`
	CreatedAt   *time.Time `json:"created_at"`
	StartedAt   *time.Time `json:"started_at"`
	FinishedAt  *time.Time `json:"finished_at"`
	Author      Author     `json:"author"`
}

// GetCommitStatuses gets the statuses of a commit in a project.
//
// GitLab API docs: https://docs.gitlab.com/ce/api/commits.html#get-the-status-of-a-commit
func (s *CommitsService) GetCommitStatuses(pid interface{}, sha string, opt *GetCommitStatusesOptions, options ...OptionFunc) ([]*CommitStatus, *Response, error) {
	project, err := parseID(pid)
	if err != nil {
		return nil, nil, err
	}
	u := fmt.Sprintf("projects/%s/repository/commits/%s/statuses", url.QueryEscape(project), sha)

	req, err := s.client.NewRequest("GET", u, opt, options)
	if err != nil {
		return nil, nil, err
	}

	var cs []*CommitStatus
	resp, err := s.client.Do(req, &cs)
	if err != nil {
		return nil, resp, err
	}

	return cs, resp, err
}

// SetCommitStatusOptions represents the available SetCommitStatus() options.
//
// GitLab API docs: https://docs.gitlab.com/ce/api/commits.html#post-the-status-to-commit
type SetCommitStatusOptions struct {
	State       BuildState `url:"state" json:"state"`
	Ref         *string    `url:"ref,omitempty" json:"ref,omitempty"`
	Name        *string    `url:"name,omitempty" json:"name,omitempty"`
	Context     *string    `url:"context,omitempty" json:"context,omitempty"`
	TargetURL   *string    `url:"target_url,omitempty" json:"target_url,omitempty"`
	Description *string    `url:"description,omitempty" json:"description,omitempty"`
}

// BuildState represents a GitLab build state
type BuildState string

// These constants represent all valid build states
const (
	Pending  BuildState = "pending"
	Running  BuildState = "running"
	Success  BuildState = "success"
	Failed   BuildState = "failed"
	Canceled BuildState = "canceled"
)

// SetCommitStatus sets the status of a commit in a project.
//
// GitLab API docs: https://docs.gitlab.com/ce/api/commits.html#post-the-status-to-commit
func (s *CommitsService) SetCommitStatus(pid interface{}, sha string, opt *SetCommitStatusOptions, options ...OptionFunc) (*CommitStatus, *Response, error) {
	project, err := parseID(pid)
	if err != nil {
		return nil, nil, err
	}
	u := fmt.Sprintf("projects/%s/statuses/%s", url.QueryEscape(project), sha)

	req, err := s.client.NewRequest("POST", u, opt, options)
	if err != nil {
		return nil, nil, err
	}

	var cs *CommitStatus
	resp, err := s.client.Do(req, &cs)
	if err != nil {
		return nil, resp, err
	}

	return cs, resp, err
}
<|endoftext|>


<commit_before>// Copyright (c) 2012, Suryandaru Triandana <syndtr@gmail.com>
// All rights reserved.
//
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

// This LevelDB Go implementation is based on LevelDB C++ implementation.
// Which contains the following header:
//   Copyright (c) 2011 The LevelDB Authors. All rights reserved.
//   Use of this source code is governed by a BSD-style license that can be
//   found in the LEVELDBCPP_LICENSE file. See the LEVELDBCPP_AUTHORS file
//   for names of contributors.

// Package memdb provide in-memory key/value database implementation.
package memdb

import (
	"leveldb/comparer"
	"leveldb/errors"
	"math/rand"
	"sync"
	"unsafe"
)

const tMaxHeight = 12

var (
	mPtrSize  int
	mNodeSize int
)

func init() {
	node := new(mNode)
	mPtrSize = int(unsafe.Sizeof(node))
	mNodeSize = int(unsafe.Sizeof(*node))
}

type mNode struct {
	key   []byte
	value []byte
	next  []*mNode
}

// DB represent an in-memory key/value database.
type DB struct {
	cmp       comparer.BasicComparer
	rnd       *rand.Rand
	head      *mNode
	maxHeight int
	memSize   int
	n         int

	mu   sync.RWMutex
	prev [tMaxHeight]*mNode
}

// New create new initalized in-memory key/value database.
func New(cmp comparer.BasicComparer) *DB {
	p := &DB{
		cmp:       cmp,
		rnd:       rand.New(rand.NewSource(0xdeadbeef)),
		maxHeight: 1,
	}
	p.head = p.newNode(nil, nil, tMaxHeight)
	return p
}

// Put insert given key and value to the database.
func (p *DB) Put(key []byte, value []byte) {
	p.mu.Lock()
	p.findGreaterOrEqual(key, true)

	h := p.randHeight()
	if h > p.maxHeight {
		for i := p.maxHeight; i < h; i++ {
			p.prev[i] = p.head
		}
		p.maxHeight = h
	}

	x := p.newNode(key, value, h)
	for i, n := range p.prev[:h] {
		x.next[i] = n.next[i]
		n.next[i] = x
	}

	p.memSize += mNodeSize + (mPtrSize * h)
	p.memSize += len(key) + len(value)
	p.n++
	p.mu.Unlock()
}

// Remove remove given key from the database.
func (p *DB) Remove(key []byte) {
	p.mu.Lock()
	defer p.mu.Unlock()
	x := p.findGreaterOrEqual(key, true)
	if x == nil || x == p.head || p.cmp.Compare(x.key, key) != 0 {
		return
	}

	h := len(x.next)
	for i, n := range p.prev[:h] {
		n.next[i] = n.next[i].next[i]
	}

	p.memSize -= mNodeSize + (mPtrSize * h)
	p.memSize -= len(x.key) + len(x.value)
	p.n--
}

// Contains return true if given key are in database.
func (p *DB) Contains(key []byte) bool {
	p.mu.RLock()
	x := p.findGreaterOrEqual(key, false)
	p.mu.RUnlock()
	if x != nil && x != p.head && p.cmp.Compare(x.key, key) == 0 {
		return true
	}
	return false
}

// Get return key/value equal or greater than given key.
func (p *DB) Get(key []byte) (rkey, value []byte, err error) {
	p.mu.RLock()
	node := p.findGreaterOrEqual(key, false)
	p.mu.RUnlock()
	if node == nil || node == p.head {
		err = errors.ErrNotFound
		return
	}
	return node.key, node.value, nil
}

// NewIterator create a new iterator over the database content.
func (p *DB) NewIterator() *Iterator {
	return &Iterator{p: p}
}

// Size return approximate size of memory used by the database.
func (p *DB) Size() int {
	p.mu.RLock()
	defer p.mu.RUnlock()
	return p.memSize
}

// Len return the number of entries in the database.
func (p *DB) Len() int {
	p.mu.RLock()
	defer p.mu.RUnlock()
	return p.n
}

func (p *DB) newNode(key, value []byte, height int) *mNode {
	return &mNode{key, value, make([]*mNode, height)}
}

func (p *DB) findGreaterOrEqual(key []byte, prev bool) *mNode {
	x := p.head
	n := p.maxHeight - 1
	for {
		next := x.next[n]
		if next != nil && p.cmp.Compare(next.key, key) < 0 {
			// Keep searching in this list
			x = next
		} else {
			if prev {
				p.prev[n] = x
			}
			if n == 0 {
				return next
			}
			n--
		}
	}
	return nil
}

func (p *DB) findLessThan(key []byte) *mNode {
	x := p.head
	n := p.maxHeight - 1
	for {
		next := x.next[n]
		if next == nil || p.cmp.Compare(next.key, key) >= 0 {
			if n == 0 {
				return x
			}
			n--
		} else {
			x = next
		}
	}
	return nil
}

func (p *DB) findLast() *mNode {
	x := p.head
	n := p.maxHeight - 1
	for {
		next := x.next[n]
		if next == nil {
			if n == 0 {
				return x
			}
			n--
		} else {
			x = next
		}
	}
	return nil
}

func (p *DB) randHeight() int {
	const branching = 4
	n := 1
	for n < tMaxHeight && p.rnd.Int()%branching == 0 {
		n++
	}
	return n
}

type Iterator struct {
	p      *DB
	node   *mNode
	onLast bool
}

func (i *Iterator) Valid() bool {
	return i.node != nil && i.node != i.p.head
}

func (i *Iterator) First() bool {
	i.p.mu.RLock()
	i.node = i.p.head.next[0]
	i.p.mu.RUnlock()
	return i.Valid()
}

func (i *Iterator) Last() bool {
	i.p.mu.RLock()
	i.node = i.p.findLast()
	i.p.mu.RUnlock()
	return i.Valid()
}

func (i *Iterator) Seek(key []byte) (r bool) {
	i.p.mu.RLock()
	i.node = i.p.findGreaterOrEqual(key, false)
	i.p.mu.RUnlock()
	return i.Valid()
}

func (i *Iterator) Next() bool {
	if i.node == nil {
		return i.First()
	}
	i.p.mu.RLock()
	i.node = i.node.next[0]
	i.p.mu.RUnlock()
	res := i.Valid()
	if !res {
		i.onLast = true
	}
	return res
}

func (i *Iterator) Prev() bool {
	if i.node == nil {
		if i.onLast {
			return i.Last()
		}
		return false
	}
	i.p.mu.RLock()
	i.node = i.p.findLessThan(i.node.key)
	i.p.mu.RUnlock()
	if i.node == i.p.head {
		i.node = nil
	}
	return i.Valid()
}

func (i *Iterator) Key() []byte {
	if !i.Valid() {
		return nil
	}
	return i.node.key
}

func (i *Iterator) Value() []byte {
	if !i.Valid() {
		return nil
	}
	return i.node.value
}

func (i *Iterator) Error() error { return nil }
<commit_msg>memdb: replace mutex with atomic variable<commit_after>// Copyright (c) 2012, Suryandaru Triandana <syndtr@gmail.com>
// All rights reserved.
//
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

// This LevelDB Go implementation is based on LevelDB C++ implementation.
// Which contains the following header:
//   Copyright (c) 2011 The LevelDB Authors. All rights reserved.
//   Use of this source code is governed by a BSD-style license that can be
//   found in the LEVELDBCPP_LICENSE file. See the LEVELDBCPP_AUTHORS file
//   for names of contributors.

// Package memdb provide in-memory key/value database implementation.
package memdb

import (
	"leveldb/comparer"
	"leveldb/errors"
	"math/rand"
	"sync/atomic"
	"unsafe"
)

const tMaxHeight = 12

var (
	mPtrSize  int
	mNodeSize int
)

func init() {
	node := new(mNode)
	mPtrSize = int(unsafe.Sizeof(node))
	mNodeSize = int(unsafe.Sizeof(*node))
}

type mNode struct {
	key   []byte
	value []byte
	next  []unsafe.Pointer
}

func (p *mNode) getNext(n int) *mNode {
	return (*mNode)(atomic.LoadPointer(&p.next[n]))
}

func (p *mNode) setNext(n int, x *mNode) {
	atomic.StorePointer(&p.next[n], unsafe.Pointer(x))
}

func (p *mNode) getNext_NB(n int) *mNode {
	return (*mNode)(p.next[n])
}

func (p *mNode) setNext_NB(n int, x *mNode) {
	p.next[n] = unsafe.Pointer(x)
}

// DB represent an in-memory key/value database.
type DB struct {
	cmp       comparer.BasicComparer
	rnd       *rand.Rand
	head      *mNode
	maxHeight int
	memSize   int64
	n         int32

	prev [tMaxHeight]*mNode
}

// New create new initalized in-memory key/value database.
func New(cmp comparer.BasicComparer) *DB {
	p := &DB{
		cmp:       cmp,
		rnd:       rand.New(rand.NewSource(0xdeadbeef)),
		maxHeight: 1,
	}
	p.head = p.newNode(nil, nil, tMaxHeight)
	return p
}

// Put insert given key and value to the database. Need external synchronization.
func (p *DB) Put(key []byte, value []byte) {
	p.findGE_NB(key, true)

	h := p.randHeight()
	if h > p.maxHeight {
		for i := p.maxHeight; i < h; i++ {
			p.prev[i] = p.head
		}
		p.maxHeight = h
	}

	x := p.newNode(key, value, h)
	for i, n := range p.prev[:h] {
		x.setNext_NB(i, n.getNext_NB(i))
		n.setNext(i, x)
	}

	atomic.AddInt64(&p.memSize, int64(mNodeSize+(mPtrSize*h)+len(key)+len(value)))
	atomic.AddInt32(&p.n, 1)
}

// Remove remove given key from the database. Need external synchronization.
func (p *DB) Remove(key []byte) {
	x := p.findGE_NB(key, true)
	if x == nil || x == p.head || p.cmp.Compare(x.key, key) != 0 {
		return
	}

	h := len(x.next)
	for i, n := range p.prev[:h] {
		n.setNext(i, n.getNext_NB(i).getNext_NB(i))
	}

	atomic.AddInt64(&p.memSize, -int64(mNodeSize+(mPtrSize*h)+len(x.key)+len(x.value)))
	atomic.AddInt32(&p.n, -1)
}

// Contains return true if given key are in database.
func (p *DB) Contains(key []byte) bool {
	x := p.findGE(key, false)
	if x != nil && x != p.head && p.cmp.Compare(x.key, key) == 0 {
		return true
	}
	return false
}

// Get return key/value equal or greater than given key.
func (p *DB) Get(key []byte) (rkey, value []byte, err error) {
	node := p.findGE(key, false)
	if node == nil || node == p.head {
		err = errors.ErrNotFound
		return
	}
	return node.key, node.value, nil
}

// NewIterator create a new iterator over the database content.
func (p *DB) NewIterator() *Iterator {
	return &Iterator{p: p}
}

// Size return approximate size of memory used by the database.
func (p *DB) Size() int {
	return int(atomic.LoadInt64(&p.memSize))
}

// Len return the number of entries in the database.
func (p *DB) Len() int {
	return int(atomic.LoadInt32(&p.n))
}

func (p *DB) newNode(key, value []byte, height int) *mNode {
	return &mNode{key, value, make([]unsafe.Pointer, height)}
}

func (p *DB) findGE(key []byte, prev bool) *mNode {
	x := p.head
	n := p.maxHeight - 1
	for {
		next := x.getNext(n)
		if next != nil && p.cmp.Compare(next.key, key) < 0 {
			// Keep searching in this list
			x = next
		} else {
			if prev {
				p.prev[n] = x
			}
			if n == 0 {
				return next
			}
			n--
		}
	}
	return nil
}

func (p *DB) findGE_NB(key []byte, prev bool) *mNode {
	x := p.head
	n := p.maxHeight - 1
	for {
		next := x.getNext_NB(n)
		if next != nil && p.cmp.Compare(next.key, key) < 0 {
			// Keep searching in this list
			x = next
		} else {
			if prev {
				p.prev[n] = x
			}
			if n == 0 {
				return next
			}
			n--
		}
	}
	return nil
}

func (p *DB) findLT(key []byte) *mNode {
	x := p.head
	n := p.maxHeight - 1
	for {
		next := x.getNext(n)
		if next == nil || p.cmp.Compare(next.key, key) >= 0 {
			if n == 0 {
				return x
			}
			n--
		} else {
			x = next
		}
	}
	return nil
}

func (p *DB) findLast() *mNode {
	x := p.head
	n := p.maxHeight - 1
	for {
		next := x.getNext(n)
		if next == nil {
			if n == 0 {
				return x
			}
			n--
		} else {
			x = next
		}
	}
	return nil
}

func (p *DB) randHeight() int {
	const branching = 4
	n := 1
	for n < tMaxHeight && p.rnd.Int()%branching == 0 {
		n++
	}
	return n
}

type Iterator struct {
	p      *DB
	node   *mNode
	onLast bool
}

func (i *Iterator) Valid() bool {
	return i.node != nil && i.node != i.p.head
}

func (i *Iterator) First() bool {
	i.node = i.p.head.getNext(0)
	return i.Valid()
}

func (i *Iterator) Last() bool {
	i.node = i.p.findLast()
	return i.Valid()
}

func (i *Iterator) Seek(key []byte) (r bool) {
	i.node = i.p.findGE(key, false)
	return i.Valid()
}

func (i *Iterator) Next() bool {
	if i.node == nil {
		return i.First()
	}
	i.node = i.node.getNext(0)
	res := i.Valid()
	if !res {
		i.onLast = true
	}
	return res
}

func (i *Iterator) Prev() bool {
	if i.node == nil {
		if i.onLast {
			return i.Last()
		}
		return false
	}
	i.node = i.p.findLT(i.node.key)
	if i.node == i.p.head {
		i.node = nil
	}
	return i.Valid()
}

func (i *Iterator) Key() []byte {
	if !i.Valid() {
		return nil
	}
	return i.node.key
}

func (i *Iterator) Value() []byte {
	if !i.Valid() {
		return nil
	}
	return i.node.value
}

func (i *Iterator) Error() error { return nil }
<|endoftext|>


<commit_before>package consul

import (
	consulapi "github.com/hashicorp/consul/api"
	"github.com/stretchr/testify/assert"
	"testing"
)

func TestGetAllServices(t *testing.T) {
	t.Parallel()
	// create cluster of 2 consul servers
	server1 := CreateConsulTestServer("dc1", t)
	defer server1.Stop()

	server2 := CreateConsulTestServer("dc2", t)
	defer server2.Stop()

	server1.JoinWAN(server2.LANAddr)

	// create client
	consul := ConsulClientAtServer(server1)

	// given
	// register services in both servers
	server1.AddService("serviceA", "passing", []string{"public", "marathon"})
	server1.AddService("serviceB", "passing", []string{"marathon"})
	server1.AddService("serviceC", "passing", []string{"zookeeper"})

	server2.AddService("serviceA", "passing", []string{"private", "marathon"})
	server2.AddService("serviceB", "passing", []string{"zookeeper"})

	// when
	services, err := consul.GetAllServices()
	if err != nil {
		t.Fatal("Could not get services from consul")
	}

	// then
	assert.Len(t, services, 3)

	serviceNames := make(map[string]struct{})
	for _, s := range services {
		serviceNames[s.ServiceName] = struct{}{}
	}
	assert.Len(t, serviceNames, 2)
	assert.Contains(t, serviceNames, "serviceA")
	assert.Contains(t, serviceNames, "serviceB")
}

func TestRegisterServices(t *testing.T) {
	t.Parallel()
	server := CreateConsulTestServer("dc1", t)
	defer server.Stop()

	consul := ConsulClientAtServer(server)

	// given
	service := &consulapi.AgentServiceRegistration{
		Name:    "serviceA",
		Address: "127.0.0.1",
		Port:    8080,
		Tags:    []string{"test", "marathon"},
		Check: &consulapi.AgentServiceCheck{
			HTTP:     "http://127.0.0.1:8080/status/ping",
			Interval: "60s",
		},
	}

	// when
	consul.Register(service)

	// then
	services, _ := consul.GetAllServices()
	assert.Len(t, services, 1)
	assert.Equal(t, "serviceA", services[0].ServiceName)
	assert.Equal(t, []string{"test", "marathon"}, services[0].ServiceTags)
}

func TestDeregisterServices(t *testing.T) {
	t.Parallel()
	server := CreateConsulTestServer("dc1", t)
	defer server.Stop()

	consul := ConsulClientAtServer(server)

	// given
	server.AddService("serviceA", "passing", []string{"marathon"})
	server.AddService("serviceB", "passing", []string{"marathon"})
	services, _ := consul.GetAllServices()
	assert.Len(t, services, 2)

	// when
	consul.Deregister("serviceA", server.Config.NodeName)

	// then
	services, _ = consul.GetAllServices()
	assert.Len(t, services, 1)
	assert.Equal(t, "serviceB", services[0].ServiceName)
}
<commit_msg>added consul tests for failure-cases<commit_after>package consul

import (
	consulapi "github.com/hashicorp/consul/api"
	"github.com/stretchr/testify/assert"
	"testing"
)

func TestGetAllServices(t *testing.T) {
	t.Parallel()
	// create cluster of 2 consul servers
	server1 := CreateConsulTestServer("dc1", t)
	defer server1.Stop()

	server2 := CreateConsulTestServer("dc2", t)
	defer server2.Stop()

	server1.JoinWAN(server2.LANAddr)

	// create client
	consul := ConsulClientAtServer(server1)

	// given
	// register services in both servers
	server1.AddService("serviceA", "passing", []string{"public", "marathon"})
	server1.AddService("serviceB", "passing", []string{"marathon"})
	server1.AddService("serviceC", "passing", []string{"zookeeper"})

	server2.AddService("serviceA", "passing", []string{"private", "marathon"})
	server2.AddService("serviceB", "passing", []string{"zookeeper"})

	// when
	services, err := consul.GetAllServices()
	if err != nil {
		t.Fatal("Could not get services from consul")
	}

	// then
	assert.Len(t, services, 3)

	serviceNames := make(map[string]struct{})
	for _, s := range services {
		serviceNames[s.ServiceName] = struct{}{}
	}
	assert.Len(t, serviceNames, 2)
	assert.Contains(t, serviceNames, "serviceA")
	assert.Contains(t, serviceNames, "serviceB")
}

func TestRegisterServices(t *testing.T) {
	t.Parallel()
	server := CreateConsulTestServer("dc1", t)
	defer server.Stop()

	consul := ConsulClientAtServer(server)

	// given
	service := serviceRegistration("serviceA", []string{"test", "marathon"})

	// when
	consul.Register(service)

	// then
	services, _ := consul.GetAllServices()
	assert.Len(t, services, 1)
	assert.Equal(t, "serviceA", services[0].ServiceName)
	assert.Equal(t, []string{"test", "marathon"}, services[0].ServiceTags)
}

func TestRegisterServices_shouldReturnErrorOnFailure(t *testing.T) {
	t.Parallel()

	// given
	consul := New(ConsulConfig{Port: "1234"})
	service := serviceRegistration("serviceA", []string{"test", "marathon"})

	// when
	err := consul.Register(service)

	// then
	assert.NotNil(t, err)
}

func TestDeregisterServices(t *testing.T) {
	t.Parallel()
	server := CreateConsulTestServer("dc1", t)
	defer server.Stop()

	consul := ConsulClientAtServer(server)

	// given
	server.AddService("serviceA", "passing", []string{"marathon"})
	server.AddService("serviceB", "passing", []string{"marathon"})
	services, _ := consul.GetAllServices()
	assert.Len(t, services, 2)

	// when
	consul.Deregister("serviceA", server.Config.NodeName)

	// then
	services, _ = consul.GetAllServices()
	assert.Len(t, services, 1)
	assert.Equal(t, "serviceB", services[0].ServiceName)
}

func TestDeregisterServices_shouldReturnErrorOnFailure(t *testing.T) {
	t.Parallel()
	server := CreateConsulTestServer("dc1", t)
	defer server.Stop()

	consul := ConsulClientAtServer(server)

	// given
	server.AddService("serviceA", "passing", []string{"marathon"})

	// when
	server.Stop()
	err := consul.Deregister("serviceA", server.Config.NodeName)

	// then
	assert.NotNil(t, err)
}

func serviceRegistration(name string, tags []string) *consulapi.AgentServiceRegistration {
	return &consulapi.AgentServiceRegistration{
		Name:    name,
		Address: "127.0.0.1",
		Port:    8080,
		Tags:    tags,
		Check: &consulapi.AgentServiceCheck{
			HTTP:     "http://127.0.0.1:8080/status/ping",
			Interval: "60s",
		},
	}
}
<|endoftext|>