<commit_before>package sync

import (
	"net"
	"os"
	"path/filepath"
	"sync"
	"testing"
	"time"

	"github.com/NebulousLabs/Sia/build"
)

// TestThreadGroupStopEarly tests that a thread group can correctly interrupt
// an ongoing process.
func TestThreadGroupStopEarly(t *testing.T) {
	if testing.Short() {
		t.SkipNow()
	}
	t.Parallel()

	var tg ThreadGroup
	for i := 0; i < 10; i++ {
		err := tg.Add()
		if err != nil {
			t.Fatal(err)
		}

		go func() {
			defer tg.Done()
			select {
			case <-time.After(1 * time.Second):
			case <-tg.StopChan():
			}
		}()
	}
	start := time.Now()
	err := tg.Stop()
	elapsed := time.Since(start)
	if err != nil {
		t.Fatal(err)
	} else if elapsed > 100*time.Millisecond {
		t.Fatal("Stop did not interrupt goroutines")
	}
}

// TestThreadGroupWait tests that a thread group will correctly wait for
// existing processes to halt.
func TestThreadGroupWait(t *testing.T) {
	if testing.Short() {
		t.SkipNow()
	}
	t.Parallel()

	var tg ThreadGroup
	for i := 0; i < 10; i++ {
		err := tg.Add()
		if err != nil {
			t.Fatal(err)
		}

		go func() {
			defer tg.Done()
			time.Sleep(time.Second)
		}()
	}
	start := time.Now()
	err := tg.Stop()
	elapsed := time.Since(start)
	if err != nil {
		t.Fatal(err)
	} else if elapsed < time.Millisecond*950 {
		t.Fatal("Stop did not wait for goroutines:", elapsed)
	}
}

// TestThreadGroupStop tests the behavior of a ThreadGroup after Stop has been
// called.
func TestThreadGroupStop(t *testing.T) {
	// Create a thread group and stop it.
	var tg ThreadGroup
	// Create an array to track the order of execution for OnStop and AfterStop
	// calls.
	var stopCalls []int

	// isStopped should return false
	if tg.isStopped() {
		t.Error("isStopped returns true on unstopped ThreadGroup")
	}
	// The cannel provided by StopChan should be open.
	select {
	case <-tg.StopChan():
		t.Error("stop chan appears to be closed")
	default:
	}

	// OnStop and AfterStop should queue their functions, but not call them.
	// 'Add' and 'Done' are setup around the OnStop functions, to make sure
	// that the OnStop functions are called before waiting for all calls to
	// 'Done' to come through.
	//
	// Note: the practice of calling Add outside of OnStop and Done inside of
	// OnStop is a bad one - any call to tg.Flush() will cause a deadlock
	// because the stop functions will not be called but tg.Flush will be
	// waiting for the thread group counter to reach zero.
	err := tg.Add()
	if err != nil {
		t.Fatal(err)
	}
	err = tg.Add()
	if err != nil {
		t.Fatal(err)
	}
	tg.OnStop(func() {
		tg.Done()
		stopCalls = append(stopCalls, 1)
	})
	tg.OnStop(func() {
		tg.Done()
		stopCalls = append(stopCalls, 2)
	})
	tg.AfterStop(func() {
		stopCalls = append(stopCalls, 10)
	})
	tg.AfterStop(func() {
		stopCalls = append(stopCalls, 20)
	})
	// None of the stop calls should have been called yet.
	if len(stopCalls) != 0 {
		t.Fatal("Stop calls were called too early")
	}

	// Stop the thread group.
	err = tg.Stop()
	if err != nil {
		t.Fatal(err)
	}
	// isStopped should return true.
	if !tg.isStopped() {
		t.Error("isStopped returns false on stopped ThreadGroup")
	}
	// The cannel provided by StopChan should be closed.
	select {
	case <-tg.StopChan():
	default:
		t.Error("stop chan appears to be closed")
	}
	// The OnStop calls should have been called first, in reverse order, and
	// the AfterStop calls should have been called second, in reverse order.
	if len(stopCalls) != 4 {
		t.Fatal("Stop did not call the stopping functions correctly")
	}
	if stopCalls[0] != 2 {
		t.Error("Stop called the stopping functions in the wrong order")
	}
	if stopCalls[1] != 1 {
		t.Error("Stop called the stopping functions in the wrong order")
	}
	if stopCalls[2] != 20 {
		t.Error("Stop called the stopping functions in the wrong order")
	}
	if stopCalls[3] != 10 {
		t.Error("Stop called the stopping functions in the wrong order")
	}

	// Add and Stop should return errors.
	err = tg.Add()
	if err != ErrStopped {
		t.Error("expected ErrStopped, got", err)
	}
	err = tg.Stop()
	if err != ErrStopped {
		t.Error("expected ErrStopped, got", err)
	}

	// OnStop and AfterStop should call their functions immediately now that
	// the thread group has stopped.
	onStopCalled := false
	tg.OnStop(func() {
		onStopCalled = true
	})
	if !onStopCalled {
		t.Error("OnStop function not called immediately despite the thread group being closed already.")
	}
	afterStopCalled := false
	tg.AfterStop(func() {
		afterStopCalled = true
	})
	if !afterStopCalled {
		t.Error("AfterStop function not called immediately despite the thread group being closed already.")
	}
}

// TestThreadGroupConcurrentAdd tests that Add can be called concurrently with Stop.
func TestThreadGroupConcurrentAdd(t *testing.T) {
	if testing.Short() {
		t.SkipNow()
	}
	var tg ThreadGroup
	for i := 0; i < 10; i++ {
		go func() {
			err := tg.Add()
			if err != nil {
				return
			}
			defer tg.Done()

			select {
			case <-time.After(1 * time.Second):
			case <-tg.StopChan():
			}
		}()
	}
	time.Sleep(10 * time.Millisecond) // wait for at least one Add
	err := tg.Stop()
	if err != nil {
		t.Fatal(err)
	}
}

// TestThreadGroupOnce tests that a zero-valued ThreadGroup's stopChan is
// properly initialized.
func TestThreadGroupOnce(t *testing.T) {
	tg := new(ThreadGroup)
	if tg.stopChan != nil {
		t.Error("expected nil stopChan")
	}

	// these methods should cause stopChan to be initialized
	tg.StopChan()
	if tg.stopChan == nil {
		t.Error("stopChan should have been initialized by StopChan")
	}

	tg = new(ThreadGroup)
	tg.isStopped()
	if tg.stopChan == nil {
		t.Error("stopChan should have been initialized by isStopped")
	}

	tg = new(ThreadGroup)
	tg.Add()
	if tg.stopChan == nil {
		t.Error("stopChan should have been initialized by Add")
	}

	tg = new(ThreadGroup)
	tg.Stop()
	if tg.stopChan == nil {
		t.Error("stopChan should have been initialized by Stop")
	}
}

// TestThreadGroupOnStop tests that Stop calls functions registered with
// OnStop.
func TestThreadGroupOnStop(t *testing.T) {
	if testing.Short() {
		t.SkipNow()
	}
	l, err := net.Listen("tcp", "localhost:0")
	if err != nil {
		t.Fatal(err)
	}

	// create ThreadGroup and register the closer
	var tg ThreadGroup
	tg.OnStop(func() { l.Close() })

	// send on channel when listener is closed
	var closed bool
	tg.Add()
	go func() {
		defer tg.Done()
		_, err := l.Accept()
		closed = err != nil
	}()

	tg.Stop()
	if !closed {
		t.Fatal("Stop did not close listener")
	}
}

// TestThreadGroupRace tests that calling ThreadGroup methods concurrently
// does not trigger the race detector.
func TestThreadGroupRace(t *testing.T) {
	var tg ThreadGroup
	go tg.StopChan()
	go func() {
		if tg.Add() == nil {
			tg.Done()
		}
	}()
	err := tg.Stop()
	if err != nil {
		t.Fatal(err)
	}
}

// TestThreadGroupCloseAfterStop checks that an AfterStop function is
// correctly called after the thread is stopped.
func TestThreadGroupClosedAfterStop(t *testing.T) {
	var tg ThreadGroup
	var closed bool
	tg.AfterStop(func() { closed = true })
	if closed {
		t.Fatal("close function should not have been called yet")
	}
	if err := tg.Stop(); err != nil {
		t.Fatal(err)
	}
	if !closed {
		t.Fatal("close function should have been called")
	}

	// Stop has already been called, so the close function should be called
	// immediately
	closed = false
	tg.AfterStop(func() { closed = true })
	if !closed {
		t.Fatal("close function should have been called immediately")
	}
}

// TestThreadGroupSiaExample tries to use a thread group as it might be
// expected to be used by a module of Sia.
func TestThreadGroupSiaExample(t *testing.T) {
	if testing.Short() {
		t.SkipNow()
	}
	t.Parallel()
	testDir := build.TempDir("sync", "TestThreadGroupSiaExample")
	err := os.MkdirAll(testDir, 0700)
	if err != nil {
		t.Fatal(err)
	}
	var tg ThreadGroup

	// Open an example file. The file is expected to be used throughout the
	// lifetime of the module, and should not be closed until 'AfterStop' is
	// called.
	fileClosed := false
	file, err := os.Create(filepath.Join(testDir, "exampleFile.txt"))
	if err != nil {
		t.Fatal(err)
	}
	tg.AfterStop(func() {
		fileClosed = true
		err := file.Close()
		if err != nil {
			t.Fatal(err)
		}
	})

	// Open a listener. The listener and handler thread should be closed before
	// the file is closed.
	listenerCleanedUp := false
	listener, err := net.Listen("tcp", "localhost:0")
	if err != nil {
		t.Fatal(err)
	}
	// Open a thread to accept calls from the listener.
	handlerFinishedChan := make(chan struct{})
	go func() {
		for {
			_, err := listener.Accept()
			if err != nil {
				break
			}
		}
		handlerFinishedChan <- struct{}{}
	}()
	tg.OnStop(func() {
		err := listener.Close()
		if err != nil {
			t.Fatal(err)
		}
		<-handlerFinishedChan

		if fileClosed {
			t.Error("file should be open while the listener is shutting down")
		}
		listenerCleanedUp = true
	})

	// Create a thread that does some stuff which takes time, and then closes.
	// Use Flush to clear out the process without closing the resources.
	threadFinished := false
	err = tg.Add()
	if err != nil {
		t.Fatal(err)
	}
	go func() {
		time.Sleep(time.Second)
		threadFinished = true
		tg.Done()
	}()
	tg.Flush()
	if !threadFinished {
		t.Error("call to Flush should have allowed the working thread to finish")
	}
	if listenerCleanedUp || fileClosed {
		t.Error("call to Flush resulted in permanent resources being closed")
	}

	// Create a thread that does some stuff which takes time, and then closes.
	// Use Stop to wait for the threead to finish and then check that all
	// resources have closed.
	threadFinished2 := false
	err = tg.Add()
	if err != nil {
		t.Fatal(err)
	}
	go func() {
		time.Sleep(time.Second)
		threadFinished2 = true
		tg.Done()
	}()
	tg.Stop()
	if !threadFinished || !listenerCleanedUp || !fileClosed {
		t.Error("stop did not block until all running resources had closed")
	}
}

// TestAddOnStop checks that you can safely call OnStop from under the
// protection of an Add call.
func TestAddOnStop(t *testing.T) {
	if testing.Short() {
		t.SkipNow()
	}
	t.Parallel()

	var tg ThreadGroup
	var data int
	addChan := make(chan struct{})
	stopChan := make(chan struct{})
	tg.OnStop(func() {
		close(stopChan)
	})
	go func() {
		err := tg.Add()
		if err != nil {
			t.Fatal(err)
		}
		close(addChan)

		// Wait for the call to 'Stop' to be called in the parent thread, and
		// then queue a bunch of 'OnStop' and 'AfterStop' functions before
		// calling 'Done'.
		<-stopChan
		for i := 0; i < 10; i++ {
			tg.OnStop(func() {
				data++
			})
			tg.AfterStop(func() {
				data++
			})
		}
		tg.Done()
	}()

	// Wait for 'Add' to be called in the above thread, to guarantee that
	// OnStop and AfterStop will be called after 'Add' and 'Stop' have been
	// called together.
	<-addChan
	err := tg.Stop()
	if err != nil {
		t.Fatal(err)
	}

	if data != 20 {
		t.Error("20 calls were made to increment data, but value is", data)
	}
}

// BenchmarkThreadGroup times how long it takes to add a ton of threads and
// trigger goroutines that call Done.
func BenchmarkThreadGroup(b *testing.B) {
	var tg ThreadGroup
	for i := 0; i < b.N; i++ {
		tg.Add()
		go tg.Done()
	}
	tg.Stop()
}

// BenchmarkWaitGroup times how long it takes to add a ton of threads to a wait
// group and trigger goroutines that call Done.
func BenchmarkWaitGroup(b *testing.B) {
	var wg sync.WaitGroup
	for i := 0; i < b.N; i++ {
		wg.Add(1)
		go wg.Done()
	}
	wg.Wait()
}
<commit_msg>fix threadgroup test typo<commit_after>package sync

import (
	"net"
	"os"
	"path/filepath"
	"sync"
	"testing"
	"time"

	"github.com/NebulousLabs/Sia/build"
)

// TestThreadGroupStopEarly tests that a thread group can correctly interrupt
// an ongoing process.
func TestThreadGroupStopEarly(t *testing.T) {
	if testing.Short() {
		t.SkipNow()
	}
	t.Parallel()

	var tg ThreadGroup
	for i := 0; i < 10; i++ {
		err := tg.Add()
		if err != nil {
			t.Fatal(err)
		}

		go func() {
			defer tg.Done()
			select {
			case <-time.After(1 * time.Second):
			case <-tg.StopChan():
			}
		}()
	}
	start := time.Now()
	err := tg.Stop()
	elapsed := time.Since(start)
	if err != nil {
		t.Fatal(err)
	} else if elapsed > 100*time.Millisecond {
		t.Fatal("Stop did not interrupt goroutines")
	}
}

// TestThreadGroupWait tests that a thread group will correctly wait for
// existing processes to halt.
func TestThreadGroupWait(t *testing.T) {
	if testing.Short() {
		t.SkipNow()
	}
	t.Parallel()

	var tg ThreadGroup
	for i := 0; i < 10; i++ {
		err := tg.Add()
		if err != nil {
			t.Fatal(err)
		}

		go func() {
			defer tg.Done()
			time.Sleep(time.Second)
		}()
	}
	start := time.Now()
	err := tg.Stop()
	elapsed := time.Since(start)
	if err != nil {
		t.Fatal(err)
	} else if elapsed < time.Millisecond*950 {
		t.Fatal("Stop did not wait for goroutines:", elapsed)
	}
}

// TestThreadGroupStop tests the behavior of a ThreadGroup after Stop has been
// called.
func TestThreadGroupStop(t *testing.T) {
	// Create a thread group and stop it.
	var tg ThreadGroup
	// Create an array to track the order of execution for OnStop and AfterStop
	// calls.
	var stopCalls []int

	// isStopped should return false
	if tg.isStopped() {
		t.Error("isStopped returns true on unstopped ThreadGroup")
	}
	// The cannel provided by StopChan should be open.
	select {
	case <-tg.StopChan():
		t.Error("stop chan appears to be closed")
	default:
	}

	// OnStop and AfterStop should queue their functions, but not call them.
	// 'Add' and 'Done' are setup around the OnStop functions, to make sure
	// that the OnStop functions are called before waiting for all calls to
	// 'Done' to come through.
	//
	// Note: the practice of calling Add outside of OnStop and Done inside of
	// OnStop is a bad one - any call to tg.Flush() will cause a deadlock
	// because the stop functions will not be called but tg.Flush will be
	// waiting for the thread group counter to reach zero.
	err := tg.Add()
	if err != nil {
		t.Fatal(err)
	}
	err = tg.Add()
	if err != nil {
		t.Fatal(err)
	}
	tg.OnStop(func() {
		tg.Done()
		stopCalls = append(stopCalls, 1)
	})
	tg.OnStop(func() {
		tg.Done()
		stopCalls = append(stopCalls, 2)
	})
	tg.AfterStop(func() {
		stopCalls = append(stopCalls, 10)
	})
	tg.AfterStop(func() {
		stopCalls = append(stopCalls, 20)
	})
	// None of the stop calls should have been called yet.
	if len(stopCalls) != 0 {
		t.Fatal("Stop calls were called too early")
	}

	// Stop the thread group.
	err = tg.Stop()
	if err != nil {
		t.Fatal(err)
	}
	// isStopped should return true.
	if !tg.isStopped() {
		t.Error("isStopped returns false on stopped ThreadGroup")
	}
	// The cannel provided by StopChan should be closed.
	select {
	case <-tg.StopChan():
	default:
		t.Error("stop chan appears to be closed")
	}
	// The OnStop calls should have been called first, in reverse order, and
	// the AfterStop calls should have been called second, in reverse order.
	if len(stopCalls) != 4 {
		t.Fatal("Stop did not call the stopping functions correctly")
	}
	if stopCalls[0] != 2 {
		t.Error("Stop called the stopping functions in the wrong order")
	}
	if stopCalls[1] != 1 {
		t.Error("Stop called the stopping functions in the wrong order")
	}
	if stopCalls[2] != 20 {
		t.Error("Stop called the stopping functions in the wrong order")
	}
	if stopCalls[3] != 10 {
		t.Error("Stop called the stopping functions in the wrong order")
	}

	// Add and Stop should return errors.
	err = tg.Add()
	if err != ErrStopped {
		t.Error("expected ErrStopped, got", err)
	}
	err = tg.Stop()
	if err != ErrStopped {
		t.Error("expected ErrStopped, got", err)
	}

	// OnStop and AfterStop should call their functions immediately now that
	// the thread group has stopped.
	onStopCalled := false
	tg.OnStop(func() {
		onStopCalled = true
	})
	if !onStopCalled {
		t.Error("OnStop function not called immediately despite the thread group being closed already.")
	}
	afterStopCalled := false
	tg.AfterStop(func() {
		afterStopCalled = true
	})
	if !afterStopCalled {
		t.Error("AfterStop function not called immediately despite the thread group being closed already.")
	}
}

// TestThreadGroupConcurrentAdd tests that Add can be called concurrently with Stop.
func TestThreadGroupConcurrentAdd(t *testing.T) {
	if testing.Short() {
		t.SkipNow()
	}
	var tg ThreadGroup
	for i := 0; i < 10; i++ {
		go func() {
			err := tg.Add()
			if err != nil {
				return
			}
			defer tg.Done()

			select {
			case <-time.After(1 * time.Second):
			case <-tg.StopChan():
			}
		}()
	}
	time.Sleep(10 * time.Millisecond) // wait for at least one Add
	err := tg.Stop()
	if err != nil {
		t.Fatal(err)
	}
}

// TestThreadGroupOnce tests that a zero-valued ThreadGroup's stopChan is
// properly initialized.
func TestThreadGroupOnce(t *testing.T) {
	tg := new(ThreadGroup)
	if tg.stopChan != nil {
		t.Error("expected nil stopChan")
	}

	// these methods should cause stopChan to be initialized
	tg.StopChan()
	if tg.stopChan == nil {
		t.Error("stopChan should have been initialized by StopChan")
	}

	tg = new(ThreadGroup)
	tg.isStopped()
	if tg.stopChan == nil {
		t.Error("stopChan should have been initialized by isStopped")
	}

	tg = new(ThreadGroup)
	tg.Add()
	if tg.stopChan == nil {
		t.Error("stopChan should have been initialized by Add")
	}

	tg = new(ThreadGroup)
	tg.Stop()
	if tg.stopChan == nil {
		t.Error("stopChan should have been initialized by Stop")
	}
}

// TestThreadGroupOnStop tests that Stop calls functions registered with
// OnStop.
func TestThreadGroupOnStop(t *testing.T) {
	if testing.Short() {
		t.SkipNow()
	}
	l, err := net.Listen("tcp", "localhost:0")
	if err != nil {
		t.Fatal(err)
	}

	// create ThreadGroup and register the closer
	var tg ThreadGroup
	tg.OnStop(func() { l.Close() })

	// send on channel when listener is closed
	var closed bool
	tg.Add()
	go func() {
		defer tg.Done()
		_, err := l.Accept()
		closed = err != nil
	}()

	tg.Stop()
	if !closed {
		t.Fatal("Stop did not close listener")
	}
}

// TestThreadGroupRace tests that calling ThreadGroup methods concurrently
// does not trigger the race detector.
func TestThreadGroupRace(t *testing.T) {
	var tg ThreadGroup
	go tg.StopChan()
	go func() {
		if tg.Add() == nil {
			tg.Done()
		}
	}()
	err := tg.Stop()
	if err != nil {
		t.Fatal(err)
	}
}

// TestThreadGroupCloseAfterStop checks that an AfterStop function is
// correctly called after the thread is stopped.
func TestThreadGroupClosedAfterStop(t *testing.T) {
	var tg ThreadGroup
	var closed bool
	tg.AfterStop(func() { closed = true })
	if closed {
		t.Fatal("close function should not have been called yet")
	}
	if err := tg.Stop(); err != nil {
		t.Fatal(err)
	}
	if !closed {
		t.Fatal("close function should have been called")
	}

	// Stop has already been called, so the close function should be called
	// immediately
	closed = false
	tg.AfterStop(func() { closed = true })
	if !closed {
		t.Fatal("close function should have been called immediately")
	}
}

// TestThreadGroupSiaExample tries to use a thread group as it might be
// expected to be used by a module of Sia.
func TestThreadGroupSiaExample(t *testing.T) {
	if testing.Short() {
		t.SkipNow()
	}
	t.Parallel()
	testDir := build.TempDir("sync", "TestThreadGroupSiaExample")
	err := os.MkdirAll(testDir, 0700)
	if err != nil {
		t.Fatal(err)
	}
	var tg ThreadGroup

	// Open an example file. The file is expected to be used throughout the
	// lifetime of the module, and should not be closed until 'AfterStop' is
	// called.
	fileClosed := false
	file, err := os.Create(filepath.Join(testDir, "exampleFile.txt"))
	if err != nil {
		t.Fatal(err)
	}
	tg.AfterStop(func() {
		fileClosed = true
		err := file.Close()
		if err != nil {
			t.Fatal(err)
		}
	})

	// Open a listener. The listener and handler thread should be closed before
	// the file is closed.
	listenerCleanedUp := false
	listener, err := net.Listen("tcp", "localhost:0")
	if err != nil {
		t.Fatal(err)
	}
	// Open a thread to accept calls from the listener.
	handlerFinishedChan := make(chan struct{})
	go func() {
		for {
			_, err := listener.Accept()
			if err != nil {
				break
			}
		}
		handlerFinishedChan <- struct{}{}
	}()
	tg.OnStop(func() {
		err := listener.Close()
		if err != nil {
			t.Fatal(err)
		}
		<-handlerFinishedChan

		if fileClosed {
			t.Error("file should be open while the listener is shutting down")
		}
		listenerCleanedUp = true
	})

	// Create a thread that does some stuff which takes time, and then closes.
	// Use Flush to clear out the process without closing the resources.
	threadFinished := false
	err = tg.Add()
	if err != nil {
		t.Fatal(err)
	}
	go func() {
		time.Sleep(time.Second)
		threadFinished = true
		tg.Done()
	}()
	tg.Flush()
	if !threadFinished {
		t.Error("call to Flush should have allowed the working thread to finish")
	}
	if listenerCleanedUp || fileClosed {
		t.Error("call to Flush resulted in permanent resources being closed")
	}

	// Create a thread that does some stuff which takes time, and then closes.
	// Use Stop to wait for the threead to finish and then check that all
	// resources have closed.
	threadFinished2 := false
	err = tg.Add()
	if err != nil {
		t.Fatal(err)
	}
	go func() {
		time.Sleep(time.Second)
		threadFinished2 = true
		tg.Done()
	}()
	tg.Stop()
	if !threadFinished2 || !listenerCleanedUp || !fileClosed {
		t.Error("stop did not block until all running resources had closed")
	}
}

// TestAddOnStop checks that you can safely call OnStop from under the
// protection of an Add call.
func TestAddOnStop(t *testing.T) {
	if testing.Short() {
		t.SkipNow()
	}
	t.Parallel()

	var tg ThreadGroup
	var data int
	addChan := make(chan struct{})
	stopChan := make(chan struct{})
	tg.OnStop(func() {
		close(stopChan)
	})
	go func() {
		err := tg.Add()
		if err != nil {
			t.Fatal(err)
		}
		close(addChan)

		// Wait for the call to 'Stop' to be called in the parent thread, and
		// then queue a bunch of 'OnStop' and 'AfterStop' functions before
		// calling 'Done'.
		<-stopChan
		for i := 0; i < 10; i++ {
			tg.OnStop(func() {
				data++
			})
			tg.AfterStop(func() {
				data++
			})
		}
		tg.Done()
	}()

	// Wait for 'Add' to be called in the above thread, to guarantee that
	// OnStop and AfterStop will be called after 'Add' and 'Stop' have been
	// called together.
	<-addChan
	err := tg.Stop()
	if err != nil {
		t.Fatal(err)
	}

	if data != 20 {
		t.Error("20 calls were made to increment data, but value is", data)
	}
}

// BenchmarkThreadGroup times how long it takes to add a ton of threads and
// trigger goroutines that call Done.
func BenchmarkThreadGroup(b *testing.B) {
	var tg ThreadGroup
	for i := 0; i < b.N; i++ {
		tg.Add()
		go tg.Done()
	}
	tg.Stop()
}

// BenchmarkWaitGroup times how long it takes to add a ton of threads to a wait
// group and trigger goroutines that call Done.
func BenchmarkWaitGroup(b *testing.B) {
	var wg sync.WaitGroup
	for i := 0; i < b.N; i++ {
		wg.Add(1)
		go wg.Done()
	}
	wg.Wait()
}
<|endoftext|>


<commit_before>package main

import (
	"errors"
	"fmt"
	"math/rand"
	"net/http"
	"reflect"
	"time"

	"golang.org/x/net/context"

	"github.com/pressly/chi"
	"github.com/pressly/chi/middleware"
	"github.com/pressly/chi/render"
)

type RuntimeObject struct {
	ID       int      `json:"id"`
	AuthData string   `json:"auth_data"`
	Data     []string `json:"data"`
}

type PresenterObject struct {
	*RuntimeObject `json:",inline"`

	URL   string `json:"url"`
	False bool   `json:"false"`

	// Omit by default. Show explicitly for auth'd users only.
	AuthData interface{} `json:"auth_data,omitempty"`
}

type PresenterObjectV2 struct {
	*PresenterObject `json:",inline"`

	ResourceURL string `json:"resource_url"`

	// Omit.
	URL interface{} `json:"url,omitempty"`
}

type PresenterObjectV1 struct {
	*PresenterObjectV2 `json:",inline"`

	Data map[string]bool `json:"data"`
}

var (
	ErrUnauthorized = errors.New("Unauthorized")
	ErrForbidden    = errors.New("Forbidden")
	ErrNotFound     = errors.New("Resource not found")

	v2 = render.NewPresenter()
	v1 = render.NewPresenter()
)

func init() {
	render.Respond = customRespond

	render.DefaultPresenter.Register(func(ctx context.Context, from *RuntimeObject) (*PresenterObject, error) {
		to := &PresenterObject{
			RuntimeObject: from,
			URL:           fmt.Sprintf("https://api.example.com/objects/%v", from.ID),
		}
		// Only show to auth'd user.
		if _, ok := ctx.Value("auth").(bool); ok {
			to.AuthData = from.AuthData
		}
		return to, nil
	})

	v2.RegisterFrom(render.DefaultPresenter)
	v2.Register(func(ctx context.Context, from *PresenterObject) (*PresenterObjectV2, error) {
		return &PresenterObjectV2{PresenterObject: from, ResourceURL: from.URL}, nil
	})

	v1.RegisterFrom(v2)
	v1.Register(func(ctx context.Context, from *PresenterObjectV2) (*PresenterObjectV1, error) {
		to := &PresenterObjectV1{
			PresenterObjectV2: from,
			Data:              map[string]bool{},
		}
		for _, item := range from.Data {
			to.Data[item] = true
		}
		return to, nil
	})
}

// customRespond sets response status code based on Error value/type.
func customRespond(ctx context.Context, w http.ResponseWriter, v interface{}) {
	val := reflect.ValueOf(v)
	if err, ok := val.Interface().(error); ok {
		switch err {
		case ErrUnauthorized:
			ctx = render.Status(ctx, 401)
		case ErrForbidden:
			ctx = render.Status(ctx, 403)
		case ErrNotFound:
			ctx = render.Status(ctx, 404)
		default:
			ctx = render.Status(ctx, 500)
		}
		render.DefaultRespond(ctx, w, map[string]string{"error": err.Error()})
		return
	}

	render.DefaultRespond(ctx, w, v)
}

func main() {
	r := chi.NewRouter()

	r.Use(middleware.RequestID)
	r.Use(middleware.Logger)
	r.Use(middleware.Recoverer)
	r.Use(render.ParseContentType)

	r.Get("/", objectHandler)
	r.Get("/v2", render.UsePresenter(v2), objectHandler)
	r.Get("/v1", render.UsePresenter(v1), objectHandler)

	r.Get("/error", randomErrorHandler)

	http.ListenAndServe(":3333", r)
}

func objectHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) {
	obj := &RuntimeObject{
		ID:       1,
		AuthData: "secret data for auth'd users only",
		Data:     []string{"one", "two", "three", "four"},
	}

	// Simulate some context values (copy over from query params).
	if r.URL.Query().Get("auth") != "" {
		ctx = context.WithValue(ctx, "auth", true)
	}
	if r.URL.Query().Get("error") != "" {
		render.Respond(ctx, w, errors.New("error"))
		return
	}

	render.Respond(ctx, w, obj)
}

func randomErrorHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) {
	errors := []error{ErrUnauthorized, ErrForbidden, ErrNotFound}

	rand.Seed(time.Now().Unix())
	render.Respond(ctx, w, errors[rand.Intn(len(errors))])
}
<commit_msg>PR feedback: Improve render example<commit_after>package main

import (
	"errors"
	"fmt"
	"math/rand"
	"net/http"
	"reflect"
	"time"

	"golang.org/x/net/context"

	"github.com/pressly/chi"
	"github.com/pressly/chi/middleware"
	"github.com/pressly/chi/render"
)

// Article is runtime object, not meant to be sent via REST.
type Article struct {
	ID                     int      `db:"id" json:"id" xml:"id"`
	Title                  string   `db:"title" json:"title" xml:"title"`
	Data                   []string `db:"data,stringarray" json:"data" xml:"data"`
	CustomDataForAuthUsers string   `db:"custom_data" json:"-" xml:"-"`
}

// ArticleAPI is Article object presented in latest API version for REST response.
type ArticleAPI struct {
	*Article `json:",inline" xml:",inline"`

	// Additional fields.
	URL        string `json:"url" xml:"url"`
	ViewsCount int64  `json:"views_count" xml:"views_count"`

	// Omitted fields.
	// Show custom_data explicitly for auth'd users only.
	CustomDataForAuthUsers interface{} `json:"custom_data,omitempty" xml:"custom_data,omitempty"`
}

// ArticleAPIv2 is Article presented in API version 2 for REST response.
type ArticleAPIv2 struct {
	*ArticleAPI `json:",inline" xml:",inline"`

	// Additional fields.
	SelfURL string `json:"self_url" xml:"self_url"`

	// Omitted fields.
	URL interface{} `json:"url,omitempty" xml:"url,omitempty"`
}

// ArticleAPIv1 is Article presented in API version 1 for REST response.
type ArticleAPIv1 struct {
	*ArticleAPIv2 `json:",inline" xml:",inline"`

	Data map[string]bool `json:"data" xml:"data"`
}

var (
	ErrUnauthorized = errors.New("Unauthorized")
	ErrForbidden    = errors.New("Forbidden")
	ErrNotFound     = errors.New("Resource not found")

	API   = render.NewPresenter()
	APIv2 = render.NewPresenter()
	APIv1 = render.NewPresenter()
)

func init() {
	render.Respond = customRespond

	API = render.DefaultPresenter
	API.Register(func(ctx context.Context, from *Article) (*ArticleAPI, error) {
		rand.Seed(time.Now().Unix())
		to := &ArticleAPI{
			Article:    from,
			ViewsCount: rand.Int63n(100000),
			URL:        fmt.Sprintf("http://localhost:3333/?id=%v", from.ID),
		}
		// Only show to auth'd user.
		if _, ok := ctx.Value("auth").(bool); ok {
			to.CustomDataForAuthUsers = from.CustomDataForAuthUsers
		}
		return to, nil
	})

	APIv2.RegisterFrom(API)
	APIv2.Register(func(ctx context.Context, from *ArticleAPI) (*ArticleAPIv2, error) {
		return &ArticleAPIv2{
			ArticleAPI: from,
			SelfURL:    fmt.Sprintf("http://localhost:3333/v2?id=%v", from.ID),
		}, nil
	})

	APIv1.RegisterFrom(APIv2)
	APIv1.Register(func(ctx context.Context, from *ArticleAPIv2) (*ArticleAPIv1, error) {
		to := &ArticleAPIv1{
			ArticleAPIv2: from,
			Data:         map[string]bool{},
		}
		to.SelfURL = fmt.Sprintf("http://localhost:3333/v1?id=%v", from.ID)
		for _, item := range from.Data {
			to.Data[item] = true
		}
		return to, nil
	})
}

func main() {
	r := chi.NewRouter()

	r.Use(middleware.RequestID)
	r.Use(middleware.Logger)
	r.Use(middleware.Recoverer)
	r.Use(render.ParseContentType)

	r.Get("/", getArticle)                               // API latest version by default.
	r.Get("/v2", render.UsePresenter(APIv2), getArticle) // API version 2.
	r.Get("/v1", render.UsePresenter(APIv1), getArticle) // API version 1.

	r.Get("/error", randomErrorHandler)

	http.ListenAndServe(":3333", r)
}

func getArticle(ctx context.Context, w http.ResponseWriter, r *http.Request) {
	article := &Article{
		ID:    1,
		Title: "Article #1",
		Data:  []string{"one", "two", "three", "four"},
		CustomDataForAuthUsers: "secret data for auth'd users only",
	}

	// Simulate some context values:
	// 1. ?auth=true simluates authenticated session/user.
	// 2. ?error=true simulates random error.
	if r.URL.Query().Get("auth") != "" {
		ctx = context.WithValue(ctx, "auth", true)
	}
	if r.URL.Query().Get("error") != "" {
		render.Respond(ctx, w, errors.New("error"))
		return
	}

	render.Respond(ctx, w, article)
}

func randomErrorHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) {
	errors := []error{ErrUnauthorized, ErrForbidden, ErrNotFound}

	rand.Seed(time.Now().Unix())
	render.Respond(ctx, w, errors[rand.Intn(len(errors))])
}

// customRespond sets response status code based on Error value/type.
func customRespond(ctx context.Context, w http.ResponseWriter, v interface{}) {
	val := reflect.ValueOf(v)
	if err, ok := val.Interface().(error); ok {
		switch err {
		case ErrUnauthorized:
			ctx = render.Status(ctx, 401)
		case ErrForbidden:
			ctx = render.Status(ctx, 403)
		case ErrNotFound:
			ctx = render.Status(ctx, 404)
		default:
			ctx = render.Status(ctx, 500)
		}
		render.DefaultRespond(ctx, w, map[string]string{"error": err.Error()})
		return
	}

	render.DefaultRespond(ctx, w, v)
}
<|endoftext|>


<commit_before>package birc

import (
	"crypto/tls"
	"fmt"
	"hash/crc32"
	"net"
	"sort"
	"strconv"
	"strings"
	"time"

	"github.com/42wim/matterbridge/bridge"
	"github.com/42wim/matterbridge/bridge/config"
	"github.com/42wim/matterbridge/bridge/helper"
	"github.com/lrstanley/girc"
	stripmd "github.com/writeas/go-strip-markdown"

	// We need to import the 'data' package as an implicit dependency.
	// See: https://godoc.org/github.com/paulrosania/go-charset/charset
	_ "github.com/paulrosania/go-charset/data"
)

type Birc struct {
	i                                         *girc.Client
	Nick                                      string
	names                                     map[string][]string
	connected                                 chan error
	Local                                     chan config.Message // local queue for flood control
	FirstConnection, authDone                 bool
	MessageDelay, MessageQueue, MessageLength int

	*bridge.Config
}

func New(cfg *bridge.Config) bridge.Bridger {
	b := &Birc{}
	b.Config = cfg
	b.Nick = b.GetString("Nick")
	b.names = make(map[string][]string)
	b.connected = make(chan error)
	if b.GetInt("MessageDelay") == 0 {
		b.MessageDelay = 1300
	} else {
		b.MessageDelay = b.GetInt("MessageDelay")
	}
	if b.GetInt("MessageQueue") == 0 {
		b.MessageQueue = 30
	} else {
		b.MessageQueue = b.GetInt("MessageQueue")
	}
	if b.GetInt("MessageLength") == 0 {
		b.MessageLength = 400
	} else {
		b.MessageLength = b.GetInt("MessageLength")
	}
	b.FirstConnection = true
	return b
}

func (b *Birc) Command(msg *config.Message) string {
	if msg.Text == "!users" {
		b.i.Handlers.Add(girc.RPL_NAMREPLY, b.storeNames)
		b.i.Handlers.Add(girc.RPL_ENDOFNAMES, b.endNames)
		b.i.Cmd.SendRaw("NAMES " + msg.Channel) //nolint:errcheck
	}
	return ""
}

func (b *Birc) Connect() error {
	b.Local = make(chan config.Message, b.MessageQueue+10)
	b.Log.Infof("Connecting %s", b.GetString("Server"))

	i, err := b.getClient()
	if err != nil {
		return err
	}

	if b.GetBool("UseSASL") {
		i.Config.SASL = &girc.SASLPlain{
			User: b.GetString("NickServNick"),
			Pass: b.GetString("NickServPassword"),
		}
	}

	i.Handlers.Add(girc.RPL_WELCOME, b.handleNewConnection)
	i.Handlers.Add(girc.RPL_ENDOFMOTD, b.handleOtherAuth)
	i.Handlers.Add(girc.ERR_NOMOTD, b.handleOtherAuth)
	i.Handlers.Add(girc.ALL_EVENTS, b.handleOther)
	b.i = i

	go b.doConnect()

	err = <-b.connected
	if err != nil {
		return fmt.Errorf("connection failed %s", err)
	}
	b.Log.Info("Connection succeeded")
	b.FirstConnection = false
	if b.GetInt("DebugLevel") == 0 {
		i.Handlers.Clear(girc.ALL_EVENTS)
	}
	go b.doSend()
	return nil
}

func (b *Birc) Disconnect() error {
	b.i.Close()
	close(b.Local)
	return nil
}

func (b *Birc) JoinChannel(channel config.ChannelInfo) error {
	// need to check if we have nickserv auth done before joining channels
	for {
		if b.authDone {
			break
		}
		time.Sleep(time.Second)
	}
	if channel.Options.Key != "" {
		b.Log.Debugf("using key %s for channel %s", channel.Options.Key, channel.Name)
		b.i.Cmd.JoinKey(channel.Name, channel.Options.Key)
	} else {
		b.i.Cmd.Join(channel.Name)
	}
	return nil
}

func (b *Birc) Send(msg config.Message) (string, error) {
	// ignore delete messages
	if msg.Event == config.EventMsgDelete {
		return "", nil
	}

	b.Log.Debugf("=> Receiving %#v", msg)

	// we can be in between reconnects #385
	if !b.i.IsConnected() {
		b.Log.Error("Not connected to server, dropping message")
		return "", nil
	}

	// Execute a command
	if strings.HasPrefix(msg.Text, "!") {
		b.Command(&msg)
	}

	// convert to specified charset
	if err := b.handleCharset(&msg); err != nil {
		return "", err
	}

	// handle files, return if we're done here
	if ok := b.handleFiles(&msg); ok {
		return "", nil
	}

	var msgLines []string
	if b.GetBool("StripMarkdown") {
		msg.Text = stripmd.Strip(msg.Text)
	}

	if b.GetBool("MessageSplit") {
		msgLines = helper.GetSubLines(msg.Text, b.MessageLength)
	} else {
		msgLines = helper.GetSubLines(msg.Text, 0)
	}
	for i := range msgLines {
		if len(b.Local) >= b.MessageQueue {
			b.Log.Debugf("flooding, dropping message (queue at %d)", len(b.Local))
			return "", nil
		}

		msg.Text = msgLines[i]
		b.Local <- msg
	}
	return "", nil
}

func (b *Birc) doConnect() {
	for {
		if err := b.i.Connect(); err != nil {
			b.Log.Errorf("disconnect: error: %s", err)
			if b.FirstConnection {
				b.connected <- err
				return
			}
		} else {
			b.Log.Info("disconnect: client requested quit")
		}
		b.Log.Info("reconnecting in 30 seconds...")
		time.Sleep(30 * time.Second)
		b.i.Handlers.Clear(girc.RPL_WELCOME)
		b.i.Handlers.Add(girc.RPL_WELCOME, func(client *girc.Client, event girc.Event) {
			b.Remote <- config.Message{Username: "system", Text: "rejoin", Channel: "", Account: b.Account, Event: config.EventRejoinChannels}
			// set our correct nick on reconnect if necessary
			b.Nick = event.Source.Name
		})
	}
}

func (b *Birc) doSend() {
	rate := time.Millisecond * time.Duration(b.MessageDelay)
	throttle := time.NewTicker(rate)
	for msg := range b.Local {
		<-throttle.C
		username := msg.Username
		if b.GetBool("Colornicks") {
			checksum := crc32.ChecksumIEEE([]byte(msg.Username))
			colorCode := checksum%14 + 2 // quick fix - prevent white or black color codes
			username = fmt.Sprintf("\x03%02d%s\x0F", colorCode, msg.Username)
		}
		if msg.Event == config.EventUserAction {
			b.i.Cmd.Action(msg.Channel, username+msg.Text)
		} else {
			b.Log.Debugf("Sending to channel %s", msg.Channel)
			b.i.Cmd.Message(msg.Channel, username+msg.Text)
		}
	}
}

// validateInput validates the server/port/nick configuration. Returns a *girc.Client if successful
func (b *Birc) getClient() (*girc.Client, error) {
	server, portstr, err := net.SplitHostPort(b.GetString("Server"))
	if err != nil {
		return nil, err
	}
	port, err := strconv.Atoi(portstr)
	if err != nil {
		return nil, err
	}
	// fix strict user handling of girc
	user := b.GetString("Nick")
	for !girc.IsValidUser(user) {
		if len(user) == 1 || len(user) == 0 {
			user = "matterbridge"
			break
		}
		user = user[1:]
	}

	i := girc.New(girc.Config{
		Server:     server,
		ServerPass: b.GetString("Password"),
		Port:       port,
		Nick:       b.GetString("Nick"),
		User:       user,
		Name:       b.GetString("Nick"),
		SSL:        b.GetBool("UseTLS"),
		TLSConfig:  &tls.Config{InsecureSkipVerify: b.GetBool("SkipTLSVerify"), ServerName: server}, //nolint:gosec
		PingDelay:  time.Minute,
	})
	return i, nil
}

func (b *Birc) endNames(client *girc.Client, event girc.Event) {
	channel := event.Params[1]
	sort.Strings(b.names[channel])
	maxNamesPerPost := (300 / b.nicksPerRow()) * b.nicksPerRow()
	for len(b.names[channel]) > maxNamesPerPost {
		b.Remote <- config.Message{Username: b.Nick, Text: b.formatnicks(b.names[channel][0:maxNamesPerPost]),
			Channel: channel, Account: b.Account}
		b.names[channel] = b.names[channel][maxNamesPerPost:]
	}
	b.Remote <- config.Message{Username: b.Nick, Text: b.formatnicks(b.names[channel]),
		Channel: channel, Account: b.Account}
	b.names[channel] = nil
	b.i.Handlers.Clear(girc.RPL_NAMREPLY)
	b.i.Handlers.Clear(girc.RPL_ENDOFNAMES)
}

func (b *Birc) skipPrivMsg(event girc.Event) bool {
	// Our nick can be changed
	b.Nick = b.i.GetNick()

	// freenode doesn't send 001 as first reply
	if event.Command == "NOTICE" {
		return true
	}
	// don't forward queries to the bot
	if event.Params[0] == b.Nick {
		return true
	}
	// don't forward message from ourself
	if event.Source.Name == b.Nick {
		return true
	}
	return false
}

func (b *Birc) nicksPerRow() int {
	return 4
}

func (b *Birc) storeNames(client *girc.Client, event girc.Event) {
	channel := event.Params[2]
	b.names[channel] = append(
		b.names[channel],
		strings.Split(strings.TrimSpace(event.Last()), " ")...)
}

func (b *Birc) formatnicks(nicks []string) string {
	return strings.Join(nicks, ", ") + " currently on IRC"
}
<commit_msg>Only colour IRC nicks if there is one. (#1161)<commit_after>package birc

import (
	"crypto/tls"
	"fmt"
	"hash/crc32"
	"net"
	"sort"
	"strconv"
	"strings"
	"time"

	"github.com/42wim/matterbridge/bridge"
	"github.com/42wim/matterbridge/bridge/config"
	"github.com/42wim/matterbridge/bridge/helper"
	"github.com/lrstanley/girc"
	stripmd "github.com/writeas/go-strip-markdown"

	// We need to import the 'data' package as an implicit dependency.
	// See: https://godoc.org/github.com/paulrosania/go-charset/charset
	_ "github.com/paulrosania/go-charset/data"
)

type Birc struct {
	i                                         *girc.Client
	Nick                                      string
	names                                     map[string][]string
	connected                                 chan error
	Local                                     chan config.Message // local queue for flood control
	FirstConnection, authDone                 bool
	MessageDelay, MessageQueue, MessageLength int

	*bridge.Config
}

func New(cfg *bridge.Config) bridge.Bridger {
	b := &Birc{}
	b.Config = cfg
	b.Nick = b.GetString("Nick")
	b.names = make(map[string][]string)
	b.connected = make(chan error)
	if b.GetInt("MessageDelay") == 0 {
		b.MessageDelay = 1300
	} else {
		b.MessageDelay = b.GetInt("MessageDelay")
	}
	if b.GetInt("MessageQueue") == 0 {
		b.MessageQueue = 30
	} else {
		b.MessageQueue = b.GetInt("MessageQueue")
	}
	if b.GetInt("MessageLength") == 0 {
		b.MessageLength = 400
	} else {
		b.MessageLength = b.GetInt("MessageLength")
	}
	b.FirstConnection = true
	return b
}

func (b *Birc) Command(msg *config.Message) string {
	if msg.Text == "!users" {
		b.i.Handlers.Add(girc.RPL_NAMREPLY, b.storeNames)
		b.i.Handlers.Add(girc.RPL_ENDOFNAMES, b.endNames)
		b.i.Cmd.SendRaw("NAMES " + msg.Channel) //nolint:errcheck
	}
	return ""
}

func (b *Birc) Connect() error {
	b.Local = make(chan config.Message, b.MessageQueue+10)
	b.Log.Infof("Connecting %s", b.GetString("Server"))

	i, err := b.getClient()
	if err != nil {
		return err
	}

	if b.GetBool("UseSASL") {
		i.Config.SASL = &girc.SASLPlain{
			User: b.GetString("NickServNick"),
			Pass: b.GetString("NickServPassword"),
		}
	}

	i.Handlers.Add(girc.RPL_WELCOME, b.handleNewConnection)
	i.Handlers.Add(girc.RPL_ENDOFMOTD, b.handleOtherAuth)
	i.Handlers.Add(girc.ERR_NOMOTD, b.handleOtherAuth)
	i.Handlers.Add(girc.ALL_EVENTS, b.handleOther)
	b.i = i

	go b.doConnect()

	err = <-b.connected
	if err != nil {
		return fmt.Errorf("connection failed %s", err)
	}
	b.Log.Info("Connection succeeded")
	b.FirstConnection = false
	if b.GetInt("DebugLevel") == 0 {
		i.Handlers.Clear(girc.ALL_EVENTS)
	}
	go b.doSend()
	return nil
}

func (b *Birc) Disconnect() error {
	b.i.Close()
	close(b.Local)
	return nil
}

func (b *Birc) JoinChannel(channel config.ChannelInfo) error {
	// need to check if we have nickserv auth done before joining channels
	for {
		if b.authDone {
			break
		}
		time.Sleep(time.Second)
	}
	if channel.Options.Key != "" {
		b.Log.Debugf("using key %s for channel %s", channel.Options.Key, channel.Name)
		b.i.Cmd.JoinKey(channel.Name, channel.Options.Key)
	} else {
		b.i.Cmd.Join(channel.Name)
	}
	return nil
}

func (b *Birc) Send(msg config.Message) (string, error) {
	// ignore delete messages
	if msg.Event == config.EventMsgDelete {
		return "", nil
	}

	b.Log.Debugf("=> Receiving %#v", msg)

	// we can be in between reconnects #385
	if !b.i.IsConnected() {
		b.Log.Error("Not connected to server, dropping message")
		return "", nil
	}

	// Execute a command
	if strings.HasPrefix(msg.Text, "!") {
		b.Command(&msg)
	}

	// convert to specified charset
	if err := b.handleCharset(&msg); err != nil {
		return "", err
	}

	// handle files, return if we're done here
	if ok := b.handleFiles(&msg); ok {
		return "", nil
	}

	var msgLines []string
	if b.GetBool("StripMarkdown") {
		msg.Text = stripmd.Strip(msg.Text)
	}

	if b.GetBool("MessageSplit") {
		msgLines = helper.GetSubLines(msg.Text, b.MessageLength)
	} else {
		msgLines = helper.GetSubLines(msg.Text, 0)
	}
	for i := range msgLines {
		if len(b.Local) >= b.MessageQueue {
			b.Log.Debugf("flooding, dropping message (queue at %d)", len(b.Local))
			return "", nil
		}

		msg.Text = msgLines[i]
		b.Local <- msg
	}
	return "", nil
}

func (b *Birc) doConnect() {
	for {
		if err := b.i.Connect(); err != nil {
			b.Log.Errorf("disconnect: error: %s", err)
			if b.FirstConnection {
				b.connected <- err
				return
			}
		} else {
			b.Log.Info("disconnect: client requested quit")
		}
		b.Log.Info("reconnecting in 30 seconds...")
		time.Sleep(30 * time.Second)
		b.i.Handlers.Clear(girc.RPL_WELCOME)
		b.i.Handlers.Add(girc.RPL_WELCOME, func(client *girc.Client, event girc.Event) {
			b.Remote <- config.Message{Username: "system", Text: "rejoin", Channel: "", Account: b.Account, Event: config.EventRejoinChannels}
			// set our correct nick on reconnect if necessary
			b.Nick = event.Source.Name
		})
	}
}

func (b *Birc) doSend() {
	rate := time.Millisecond * time.Duration(b.MessageDelay)
	throttle := time.NewTicker(rate)
	for msg := range b.Local {
		<-throttle.C
		username := msg.Username
		if b.GetBool("Colornicks") && len(username) > 1 {
			checksum := crc32.ChecksumIEEE([]byte(msg.Username))
			colorCode := checksum%14 + 2 // quick fix - prevent white or black color codes
			username = fmt.Sprintf("\x03%02d%s\x0F", colorCode, msg.Username)
		}
		if msg.Event == config.EventUserAction {
			b.i.Cmd.Action(msg.Channel, username+msg.Text)
		} else {
			b.Log.Debugf("Sending to channel %s", msg.Channel)
			b.i.Cmd.Message(msg.Channel, username+msg.Text)
		}
	}
}

// validateInput validates the server/port/nick configuration. Returns a *girc.Client if successful
func (b *Birc) getClient() (*girc.Client, error) {
	server, portstr, err := net.SplitHostPort(b.GetString("Server"))
	if err != nil {
		return nil, err
	}
	port, err := strconv.Atoi(portstr)
	if err != nil {
		return nil, err
	}
	// fix strict user handling of girc
	user := b.GetString("Nick")
	for !girc.IsValidUser(user) {
		if len(user) == 1 || len(user) == 0 {
			user = "matterbridge"
			break
		}
		user = user[1:]
	}

	i := girc.New(girc.Config{
		Server:     server,
		ServerPass: b.GetString("Password"),
		Port:       port,
		Nick:       b.GetString("Nick"),
		User:       user,
		Name:       b.GetString("Nick"),
		SSL:        b.GetBool("UseTLS"),
		TLSConfig:  &tls.Config{InsecureSkipVerify: b.GetBool("SkipTLSVerify"), ServerName: server}, //nolint:gosec
		PingDelay:  time.Minute,
	})
	return i, nil
}

func (b *Birc) endNames(client *girc.Client, event girc.Event) {
	channel := event.Params[1]
	sort.Strings(b.names[channel])
	maxNamesPerPost := (300 / b.nicksPerRow()) * b.nicksPerRow()
	for len(b.names[channel]) > maxNamesPerPost {
		b.Remote <- config.Message{Username: b.Nick, Text: b.formatnicks(b.names[channel][0:maxNamesPerPost]),
			Channel: channel, Account: b.Account}
		b.names[channel] = b.names[channel][maxNamesPerPost:]
	}
	b.Remote <- config.Message{Username: b.Nick, Text: b.formatnicks(b.names[channel]),
		Channel: channel, Account: b.Account}
	b.names[channel] = nil
	b.i.Handlers.Clear(girc.RPL_NAMREPLY)
	b.i.Handlers.Clear(girc.RPL_ENDOFNAMES)
}

func (b *Birc) skipPrivMsg(event girc.Event) bool {
	// Our nick can be changed
	b.Nick = b.i.GetNick()

	// freenode doesn't send 001 as first reply
	if event.Command == "NOTICE" {
		return true
	}
	// don't forward queries to the bot
	if event.Params[0] == b.Nick {
		return true
	}
	// don't forward message from ourself
	if event.Source.Name == b.Nick {
		return true
	}
	return false
}

func (b *Birc) nicksPerRow() int {
	return 4
}

func (b *Birc) storeNames(client *girc.Client, event girc.Event) {
	channel := event.Params[2]
	b.names[channel] = append(
		b.names[channel],
		strings.Split(strings.TrimSpace(event.Last()), " ")...)
}

func (b *Birc) formatnicks(nicks []string) string {
	return strings.Join(nicks, ", ") + " currently on IRC"
}
<|endoftext|>


<commit_before>package socketio

import (
	"http"
	"os"
	"io"
	"bytes"
	"json"
	"net"
	"fmt"
)

// The xhr-multipart transport.
type htmlfileTransport struct {
	rtimeout int64 // The period during which the client must send a message.
	wtimeout int64 // The period during which a write must succeed.
}

// Creates a new xhr-multipart transport with the given read and write timeouts.
func NewHTMLFileTransport(rtimeout, wtimeout int64) Transport {
	return &htmlfileTransport{rtimeout, wtimeout}
}

// Returns the resource name.
func (t *htmlfileTransport) Resource() string {
	return "htmlfile"
}

// Creates a new socket that can be used with a connection.
func (t *htmlfileTransport) newSocket() socket {
	return &htmlfileSocket{t: t}
}

// Implements the socket interface for xhr-multipart transports.
type htmlfileSocket struct {
	t         *htmlfileTransport
	rwc       io.ReadWriteCloser
	connected bool
}

// String returns a verbose representation of the socket.
func (s *htmlfileSocket) String() string {
	return s.t.Resource()
}

// Transport returns the transport the socket is based on.
func (s *htmlfileSocket) Transport() Transport {
	return s.t
}

// Accepts a http connection & request pair. It hijacks the connection, sends headers and calls
// proceed if succesfull.
func (s *htmlfileSocket) accept(w http.ResponseWriter, req *http.Request, proceed func()) (err os.Error) {
	if s.connected {
		return ErrConnected
	}

	rwc, _, err := w.Hijack()

	if err == nil {
		rwc.(*net.TCPConn).SetReadTimeout(s.t.rtimeout)
		rwc.(*net.TCPConn).SetWriteTimeout(s.t.wtimeout)

		buf := new(bytes.Buffer)
		buf.WriteString("HTTP/1.0 200 OK\r\n")
		buf.WriteString("Content-Type: text/html\r\n")
		buf.WriteString("Connection: keep-alive\r\n")
		buf.WriteString("Transfer-Encoding: chunked\r\n")
		if _, err = buf.WriteTo(rwc); err != nil {
			rwc.Close()
			return
		}

		s.rwc = rwc
		s.connected = true
		proceed()
	}

	return
}

func (s *htmlfileSocket) Read(p []byte) (n int, err os.Error) {
	if !s.connected {
		return 0, ErrNotConnected
	}

	return s.rwc.Read(p)
}

// Write sends a single multipart message to the wire.
func (s *htmlfileSocket) Write(p []byte) (n int, err os.Error) {
	if !s.connected {
		return 0, ErrNotConnected
	}

	var jp []byte
	if jp, err = json.Marshal(string(p)); err != nil {
		return
	}

    return fmt.Fprintf(s.rwc, "<script>parent.s._(%s, document);</script>", jp);
}

func (s *htmlfileSocket) Close() os.Error {
	if !s.connected {
		return ErrNotConnected
	}

	s.connected = false
	return s.rwc.Close()
}
<commit_msg>finalize<commit_after>package socketio

import (
	"http"
	"os"
	"io"
	"bytes"
	"strings"
	"json"
	"net"
	"fmt"
)

var htmlfileHeader = "<html><body>" + strings.Repeat(" ", 244)

// The xhr-multipart transport.
type htmlfileTransport struct {
	rtimeout int64 // The period during which the client must send a message.
	wtimeout int64 // The period during which a write must succeed.
}

// Creates a new xhr-multipart transport with the given read and write timeouts.
func NewHTMLFileTransport(rtimeout, wtimeout int64) Transport {
	return &htmlfileTransport{rtimeout, wtimeout}
}

// Returns the resource name.
func (t *htmlfileTransport) Resource() string {
	return "htmlfile"
}

// Creates a new socket that can be used with a connection.
func (t *htmlfileTransport) newSocket() socket {
	return &htmlfileSocket{t: t}
}

// Implements the socket interface for xhr-multipart transports.
type htmlfileSocket struct {
	t         *htmlfileTransport
	rwc       io.ReadWriteCloser
	connected bool
}

// String returns a verbose representation of the socket.
func (s *htmlfileSocket) String() string {
	return s.t.Resource()
}

// Transport returns the transport the socket is based on.
func (s *htmlfileSocket) Transport() Transport {
	return s.t
}

// Accepts a http connection & request pair. It hijacks the connection, sends headers and calls
// proceed if succesfull.
func (s *htmlfileSocket) accept(w http.ResponseWriter, req *http.Request, proceed func()) (err os.Error) {
	if s.connected {
		return ErrConnected
	}

	rwc, _, err := w.Hijack()

	if err == nil {
		rwc.(*net.TCPConn).SetReadTimeout(s.t.rtimeout)
		rwc.(*net.TCPConn).SetWriteTimeout(s.t.wtimeout)

		buf := new(bytes.Buffer)
		buf.WriteString("HTTP/1.1 200 OK\r\n")
		buf.WriteString("Content-Type: text/html\r\n")
		buf.WriteString("Connection: keep-alive\r\n")
		buf.WriteString("Transfer-Encoding: chunked\r\n\r\n")
		if _, err = buf.WriteTo(rwc); err != nil {
			rwc.Close()
			return
		}
		if _, err = fmt.Fprintf(rwc, "%x\r\n%s\r\n", len(htmlfileHeader), htmlfileHeader); err != nil {
			rwc.Close()
			return
		}

		s.rwc = rwc
		s.connected = true
		proceed()
	}

	return
}

func (s *htmlfileSocket) Read(p []byte) (n int, err os.Error) {
	if !s.connected {
		return 0, ErrNotConnected
	}

	return s.rwc.Read(p)
}

// Write sends a single multipart message to the wire.
func (s *htmlfileSocket) Write(p []byte) (n int, err os.Error) {
	if !s.connected {
		return 0, ErrNotConnected
	}

	var jp []byte
	var buf bytes.Buffer
	if jp, err = json.Marshal(string(p)); err != nil {
		return
	}

	fmt.Fprintf(&buf, "<script>parent.s._(%s, document);</script>", jp)
	return fmt.Fprintf(s.rwc, "%x\r\n%s\r\n", buf.Len(), buf.String())
}

func (s *htmlfileSocket) Close() os.Error {
	if !s.connected {
		return ErrNotConnected
	}

	s.connected = false
	return s.rwc.Close()
}
<|endoftext|>


<commit_before>package system

import (
	"regexp"
	"sort"
	"sync"
)

// HandlerFunc ...
type HandlerFunc func(*Context)

//////////////////////////////////
// 		COMMAND ROUTER
/////////////////////////////////

// CommandRouter ...
type CommandRouter struct {
	sync.Mutex
	CurrentCategory string
	Prefix          string
	Routes          []*CommandRoute
	Subrouters      []*SubCommandRouter
}

// NewCommandRouter ..,
func NewCommandRouter() *CommandRouter {
	return &CommandRouter{
		Prefix:     "",
		Routes:     []*CommandRoute{},
		Subrouters: []*SubCommandRouter{},
	}
}

// On adds a command router to the list of routes.
//		matcher: The regular expression to use when searching for this route.
//		handler: The handler function for this command route.
func (c *CommandRouter) On(matcher string, handler HandlerFunc) *CommandRoute {

	// Specify that the matched text must be at the beginning of the command
	// And include the router prefix
	matcher = c.Prefix + matcher + `(\s|$)`

	route := &CommandRoute{
		Matcher:  regexp.MustCompile(matcher),
		Handler:  handler,
		Name:     matcher,
		Category: c.CurrentCategory,
	}

	c.Lock()
	c.Routes = append(c.Routes, route)
	c.Unlock()

	return route
}

// SetCategory sets the routers current category
//		name: the name of the category to add new routes to by default
func (c *CommandRouter) SetCategory(name string) {
	c.Lock()
	c.CurrentCategory = name
	c.Unlock()
}

// OnReg allows you to supply a custom regular expression as the route matcher.
//		matcher: The regular expression to use when searching for this route
//		handler: The handler function for this command route.
func (c *CommandRouter) OnReg(matcher string, handler HandlerFunc) *CommandRoute {
	route := &CommandRoute{
		Matcher: regexp.MustCompile(matcher),
		Handler: handler,
		Name:    matcher,
	}

	c.Lock()
	c.Routes = append(c.Routes, route)
	c.Unlock()

	return route
}

// Off removes a CommandRoute from the list of routes and returns a pointer
// To the removed value.
//		name:	The regular expression to match against
func (c *CommandRouter) Off(name string) *CommandRoute {
	c.Lock()
	defer c.Unlock()

	for i, v := range c.Routes {
		if v.Matcher.MatchString(name) {
			c.Routes = append(c.Routes[:i], c.Routes[i+1:]...)
			return v
		}
	}

	return nil
}

// AddSubrouter adds a subrouter to the list of subrouters.
func (c *CommandRouter) AddSubrouter(subrouter *SubCommandRouter) *SubCommandRouter {
	c.Lock()
	c.Subrouters = append(c.Subrouters, subrouter)
	c.Unlock()

	return subrouter
}

// FindMatch returns the first match found
//		name: The name of the route to find
func (c *CommandRouter) FindMatch(name string) (*CommandRoute, []int) {

	for _, route := range c.Routes {
		if route.Matcher.MatchString(name) {
			return route, nil
		}
	}

	for _, v := range c.Subrouters {
		if loc := v.Matcher.FindStringIndex(name); loc != nil {
			if match, loc2 := v.Router.FindMatch(name[loc[1]:]); match != nil {
				return match, []int{loc[0], loc[1] + loc2[1]}
			}

			// Return the subrouters command route if nothing is found
			return v.CommandRoute, nil
		}
	}

	return nil, nil
}

// FindMatches will return all commands matching the given string
//		name: The name of the route to find
func (c *CommandRouter) FindMatches(name string) []*CommandRoute {
	matches := []*CommandRoute{}

	// Search routes
	for _, route := range c.Routes {
		if route.Matcher.MatchString(name) {
			matches = append(matches, route)
		}
	}

	// Search subrouters
	for _, v := range c.Subrouters {
		if v.Matcher.MatchString(name) {
			if route := v.Router.FindMatch(name); route != nil {
				matches = append(matches, route)
			} else if v.CommandRoute != nil {
				matches = append(matches, v.CommandRoute)
			}
		}
	}

	return matches
}

// GetAllRoutes returns all routes including the routes
// of this routers subrouters.
func (c *CommandRouter) GetAllRoutes() []*CommandRoute {

	var find func(router *CommandRouter) []*CommandRoute
	find = func(router *CommandRouter) []*CommandRoute {

		routes := []*CommandRoute{}

		for _, v := range router.Routes {
			routes = append(routes, v)
		}

		for _, v := range router.Subrouters {
			if v.CommandRoute != nil {
				routes = append(routes, v.CommandRoute)
			}
		}

		for _, v := range router.Subrouters {
			routes = append(routes, find(v.Router)...)
		}

		return routes
	}

	return find(c)
}

//////////////////////////////////
// 		SUB COMMAND ROUTER
/////////////////////////////////

// SubCommandRouter is a subrouter for commands
type SubCommandRouter struct {
	Matcher *regexp.Regexp
	Router  *CommandRouter
	Name    string

	// CommandRoute is retrieved when there are no matching routes found under the subrouter,
	// But the subrouter was found.s
	CommandRoute *CommandRoute
}

// NewSubCommandRouter returns a pointer to a new SubCommandRouter
//		matcher: The regular expression to use when matching for commands.
//				 Use the expression '.' and set the prefix to an empty string.
//				 to match everything..
func NewSubCommandRouter(matcher string) (*SubCommandRouter, error) {
	reg, err := regexp.Compile(matcher)
	if err != nil {
		return nil, err
	}

	return &SubCommandRouter{
		Matcher: reg,
		Router: &CommandRouter{
			Prefix: " ",
		},
		Name:         matcher,
		CommandRoute: nil,
	}, nil
}

//////////////////////////////////
// 		COMMAND ROUTE
/////////////////////////////////

// CommandRoute ...
type CommandRoute struct {
	Matcher  *regexp.Regexp
	Handler  HandlerFunc
	Name     string
	Desc     string
	Category string
}

// Set sets the field values of the CommandRoute
// Accepts three fields:
//		1:	Name
//		2:  Description
//		3:  Category
func (c *CommandRoute) Set(values ...string) {
	switch {

	case len(values) > 2:
		if values[2] != "" {
			c.Category = values[2]
		}
		fallthrough

	case len(values) > 1:
		if values[1] != "" {
			c.Desc = values[1]
		}
		fallthrough

	case len(values) > 0:
		if values[0] != "" {
			c.Name = values[0]
		}
	}

}

//////////////////////////////////
// 		SORTING BY CATEGORY
/////////////////////////////////

// CommandRoutesByCategory implements the sort.Sortable interface
// To allow CommandRouters to be sorted in alphabetical order based on their
// Category.
type CommandRoutesByCategory []*CommandRoute

func (c CommandRoutesByCategory) Swap(a, b int) {
	c[a], c[b] = c[b], c[a]
}

// Len implements the sorter.Sortable interface
func (c CommandRoutesByCategory) Len() int {
	return len(c)
}

// Less implements the sorter.Sortable interface
func (c CommandRoutesByCategory) Less(a, b int) bool {
	return c[a].Category < c[b].Category
}

// Group splits the CommandRouters into separate slices according to group
func (c CommandRoutesByCategory) Group() [][]*CommandRoute {
	var (
		groups       = [][]*CommandRoute{}
		lastCategory = "__undefined__"
		currentGroup = []*CommandRoute{}
	)

	sort.Sort(c)

	for _, v := range c {

		if v.Category != lastCategory {
			if len(currentGroup) > 0 {
				groups = append(groups, currentGroup)
				currentGroup = []*CommandRoute{}
			}
		}

		currentGroup = append(currentGroup, v)
	}

	if len(currentGroup) > 0 {
		groups = append(groups, currentGroup)
	}

	return groups
}
<commit_msg>Fix FindMatch<commit_after>package system

import (
	"regexp"
	"sort"
	"sync"
)

// HandlerFunc ...
type HandlerFunc func(*Context)

//////////////////////////////////
// 		COMMAND ROUTER
/////////////////////////////////

// CommandRouter ...
type CommandRouter struct {
	sync.Mutex
	CurrentCategory string
	Prefix          string
	Routes          []*CommandRoute
	Subrouters      []*SubCommandRouter
}

// NewCommandRouter ..,
func NewCommandRouter() *CommandRouter {
	return &CommandRouter{
		Prefix:     "",
		Routes:     []*CommandRoute{},
		Subrouters: []*SubCommandRouter{},
	}
}

// On adds a command router to the list of routes.
//		matcher: The regular expression to use when searching for this route.
//		handler: The handler function for this command route.
func (c *CommandRouter) On(matcher string, handler HandlerFunc) *CommandRoute {

	// Specify that the matched text must be at the beginning of the command
	// And include the router prefix
	matcher = c.Prefix + matcher + `(\s|$)`

	route := &CommandRoute{
		Matcher:  regexp.MustCompile(matcher),
		Handler:  handler,
		Name:     matcher,
		Category: c.CurrentCategory,
	}

	c.Lock()
	c.Routes = append(c.Routes, route)
	c.Unlock()

	return route
}

// SetCategory sets the routers current category
//		name: the name of the category to add new routes to by default
func (c *CommandRouter) SetCategory(name string) {
	c.Lock()
	c.CurrentCategory = name
	c.Unlock()
}

// OnReg allows you to supply a custom regular expression as the route matcher.
//		matcher: The regular expression to use when searching for this route
//		handler: The handler function for this command route.
func (c *CommandRouter) OnReg(matcher string, handler HandlerFunc) *CommandRoute {
	route := &CommandRoute{
		Matcher: regexp.MustCompile(matcher),
		Handler: handler,
		Name:    matcher,
	}

	c.Lock()
	c.Routes = append(c.Routes, route)
	c.Unlock()

	return route
}

// Off removes a CommandRoute from the list of routes and returns a pointer
// To the removed value.
//		name:	The regular expression to match against
func (c *CommandRouter) Off(name string) *CommandRoute {
	c.Lock()
	defer c.Unlock()

	for i, v := range c.Routes {
		if v.Matcher.MatchString(name) {
			c.Routes = append(c.Routes[:i], c.Routes[i+1:]...)
			return v
		}
	}

	return nil
}

// AddSubrouter adds a subrouter to the list of subrouters.
func (c *CommandRouter) AddSubrouter(subrouter *SubCommandRouter) *SubCommandRouter {
	c.Lock()
	c.Subrouters = append(c.Subrouters, subrouter)
	c.Unlock()

	return subrouter
}

// FindMatch returns the first match found
//		name: The name of the route to find
func (c *CommandRouter) FindMatch(name string) (*CommandRoute, []int) {

	for _, route := range c.Routes {
		if loc := route.Matcher.FindStringIndex(name); loc != nil {
			return route, loc
		}
	}

	for _, v := range c.Subrouters {
		if loc := v.Matcher.FindStringIndex(name); loc != nil {
			if match, loc2 := v.Router.FindMatch(name[loc[1]:]); match != nil {
				return match, []int{loc[0], loc[1] + loc2[1]}
			}

			// Return the subrouters command route if nothing is found
			return v.CommandRoute, nil
		}
	}

	return nil, nil
}

// FindMatches will return all commands matching the given string
//		name: The name of the route to find
func (c *CommandRouter) FindMatches(name string) []*CommandRoute {
	matches := []*CommandRoute{}

	// Search routes
	for _, route := range c.Routes {
		if route.Matcher.MatchString(name) {
			matches = append(matches, route)
		}
	}

	// Search subrouters
	for _, v := range c.Subrouters {
		if v.Matcher.MatchString(name) {
			if route, _ := v.Router.FindMatch(name); route != nil {
				matches = append(matches, route)
			} else if v.CommandRoute != nil {
				matches = append(matches, v.CommandRoute)
			}
		}
	}

	return matches
}

// GetAllRoutes returns all routes including the routes
// of this routers subrouters.
func (c *CommandRouter) GetAllRoutes() []*CommandRoute {

	var find func(router *CommandRouter) []*CommandRoute
	find = func(router *CommandRouter) []*CommandRoute {

		routes := []*CommandRoute{}

		for _, v := range router.Routes {
			routes = append(routes, v)
		}

		for _, v := range router.Subrouters {
			if v.CommandRoute != nil {
				routes = append(routes, v.CommandRoute)
			}
		}

		for _, v := range router.Subrouters {
			routes = append(routes, find(v.Router)...)
		}

		return routes
	}

	return find(c)
}

//////////////////////////////////
// 		SUB COMMAND ROUTER
/////////////////////////////////

// SubCommandRouter is a subrouter for commands
type SubCommandRouter struct {
	Matcher *regexp.Regexp
	Router  *CommandRouter
	Name    string

	// CommandRoute is retrieved when there are no matching routes found under the subrouter,
	// But the subrouter was found.s
	CommandRoute *CommandRoute
}

// NewSubCommandRouter returns a pointer to a new SubCommandRouter
//		matcher: The regular expression to use when matching for commands.
//				 Use the expression '.' and set the prefix to an empty string.
//				 to match everything..
func NewSubCommandRouter(matcher string) (*SubCommandRouter, error) {
	reg, err := regexp.Compile(matcher)
	if err != nil {
		return nil, err
	}

	return &SubCommandRouter{
		Matcher: reg,
		Router: &CommandRouter{
			Prefix: " ",
		},
		Name:         matcher,
		CommandRoute: nil,
	}, nil
}

//////////////////////////////////
// 		COMMAND ROUTE
/////////////////////////////////

// CommandRoute ...
type CommandRoute struct {
	Matcher  *regexp.Regexp
	Handler  HandlerFunc
	Name     string
	Desc     string
	Category string
}

// Set sets the field values of the CommandRoute
// Accepts three fields:
//		1:	Name
//		2:  Description
//		3:  Category
func (c *CommandRoute) Set(values ...string) {
	switch {

	case len(values) > 2:
		if values[2] != "" {
			c.Category = values[2]
		}
		fallthrough

	case len(values) > 1:
		if values[1] != "" {
			c.Desc = values[1]
		}
		fallthrough

	case len(values) > 0:
		if values[0] != "" {
			c.Name = values[0]
		}
	}

}

//////////////////////////////////
// 		SORTING BY CATEGORY
/////////////////////////////////

// CommandRoutesByCategory implements the sort.Sortable interface
// To allow CommandRouters to be sorted in alphabetical order based on their
// Category.
type CommandRoutesByCategory []*CommandRoute

func (c CommandRoutesByCategory) Swap(a, b int) {
	c[a], c[b] = c[b], c[a]
}

// Len implements the sorter.Sortable interface
func (c CommandRoutesByCategory) Len() int {
	return len(c)
}

// Less implements the sorter.Sortable interface
func (c CommandRoutesByCategory) Less(a, b int) bool {
	return c[a].Category < c[b].Category
}

// Group splits the CommandRouters into separate slices according to group
func (c CommandRoutesByCategory) Group() [][]*CommandRoute {
	var (
		groups       = [][]*CommandRoute{}
		lastCategory = "__undefined__"
		currentGroup = []*CommandRoute{}
	)

	sort.Sort(c)

	for _, v := range c {

		if v.Category != lastCategory {
			if len(currentGroup) > 0 {
				groups = append(groups, currentGroup)
				currentGroup = []*CommandRoute{}
			}
		}

		currentGroup = append(currentGroup, v)
	}

	if len(currentGroup) > 0 {
		groups = append(groups, currentGroup)
	}

	return groups
}
<|endoftext|>


<commit_before>package sm_yamux

import (
	"io/ioutil"
	"net"
	"time"

	smux "github.com/libp2p/go-stream-muxer"
	yamux "github.com/whyrusleeping/yamux"
)

// Conn is a connection to a remote peer.
type conn yamux.Session

func (c *conn) yamuxSession() *yamux.Session {
	return (*yamux.Session)(c)
}

func (c *conn) Close() error {
	return c.yamuxSession().Close()
}

func (c *conn) IsClosed() bool {
	return c.yamuxSession().IsClosed()
}

// OpenStream creates a new stream.
func (c *conn) OpenStream() (smux.Stream, error) {
	s, err := c.yamuxSession().OpenStream()
	if err != nil {
		return nil, err
	}

	return s, nil
}

// AcceptStream accepts a stream opened by the other side.
func (c *conn) AcceptStream() (smux.Stream, error) {
	s, err := c.yamuxSession().AcceptStream()
	return s, err
}

// Serve starts listening for incoming requests and handles them
// using given StreamHandler
func (c *conn) Serve(handler smux.StreamHandler) {
	for { // accept loop
		s, err := c.AcceptStream()
		if err != nil {
			return // err always means closed.
		}
		go handler(s)
	}
}

// Transport is a go-peerstream transport that constructs
// yamux-backed connections.
type Transport yamux.Config

// DefaultTransport has default settings for yamux
var DefaultTransport = (*Transport)(&yamux.Config{
	AcceptBacklog:          256,                // from yamux.DefaultConfig
	EnableKeepAlive:        true,               // from yamux.DefaultConfig
	KeepAliveInterval:      30 * time.Second,   // from yamux.DefaultConfig
	ConnectionWriteTimeout: 10 * time.Second,   // from yamux.DefaultConfig
	MaxStreamWindowSize:    uint32(256 * 1024), // from yamux.DefaultConfig
	LogOutput:              ioutil.Discard,
})

func (t *Transport) NewConn(nc net.Conn, isServer bool) (smux.Conn, error) {
	var s *yamux.Session
	var err error
	if isServer {
		s, err = yamux.Server(nc, t.Config())
	} else {
		s, err = yamux.Client(nc, t.Config())
	}
	return (*conn)(s), err
}

func (t *Transport) Config() *yamux.Config {
	return (*yamux.Config)(t)
}
<commit_msg>remove conn.Serve<commit_after>package sm_yamux

import (
	"io/ioutil"
	"net"
	"time"

	smux "github.com/libp2p/go-stream-muxer"
	yamux "github.com/whyrusleeping/yamux"
)

// Conn is a connection to a remote peer.
type conn yamux.Session

func (c *conn) yamuxSession() *yamux.Session {
	return (*yamux.Session)(c)
}

func (c *conn) Close() error {
	return c.yamuxSession().Close()
}

func (c *conn) IsClosed() bool {
	return c.yamuxSession().IsClosed()
}

// OpenStream creates a new stream.
func (c *conn) OpenStream() (smux.Stream, error) {
	s, err := c.yamuxSession().OpenStream()
	if err != nil {
		return nil, err
	}

	return s, nil
}

// AcceptStream accepts a stream opened by the other side.
func (c *conn) AcceptStream() (smux.Stream, error) {
	s, err := c.yamuxSession().AcceptStream()
	return s, err
}

// Transport is a go-peerstream transport that constructs
// yamux-backed connections.
type Transport yamux.Config

// DefaultTransport has default settings for yamux
var DefaultTransport = (*Transport)(&yamux.Config{
	AcceptBacklog:          256,                // from yamux.DefaultConfig
	EnableKeepAlive:        true,               // from yamux.DefaultConfig
	KeepAliveInterval:      30 * time.Second,   // from yamux.DefaultConfig
	ConnectionWriteTimeout: 10 * time.Second,   // from yamux.DefaultConfig
	MaxStreamWindowSize:    uint32(256 * 1024), // from yamux.DefaultConfig
	LogOutput:              ioutil.Discard,
})

func (t *Transport) NewConn(nc net.Conn, isServer bool) (smux.Conn, error) {
	var s *yamux.Session
	var err error
	if isServer {
		s, err = yamux.Server(nc, t.Config())
	} else {
		s, err = yamux.Client(nc, t.Config())
	}
	return (*conn)(s), err
}

func (t *Transport) Config() *yamux.Config {
	return (*yamux.Config)(t)
}
<|endoftext|>


<commit_before>package chromedp

import (
	"context"
	"net"
	"strconv"
	"strings"
	"testing"
)

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

	// take a random available port
	l, err := net.Listen("tcp4", "localhost:0")
	if err != nil {
		t.Fatal(err)
	}
	defer l.Close()

	ctxt, cancel := context.WithCancel(context.Background())
	defer cancel()

	// make the pool use the port already in use via a port range
	_, portStr, _ := net.SplitHostPort(l.Addr().String())
	port, _ := strconv.Atoi(portStr)
	pool, err := NewPool(PortRange(port, port+1))
	if err != nil {
		t.Fatal(err)
	}

	c, err := pool.Allocate(ctxt)
	if err != nil {
		want := "address already in use"
		got := err.Error()
		if !strings.Contains(got, want) {
			t.Fatalf("wanted error to contain %q, but got %q", want, got)
		}
	} else {
		t.Fatal("wanted Allocate to error if port is in use")
		c.Release()
	}
}
<commit_msg>skip the error log in TestAllocatePortInUse<commit_after>package chromedp

import (
	"context"
	"net"
	"strconv"
	"strings"
	"testing"
)

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

	// take a random available port
	l, err := net.Listen("tcp4", "localhost:0")
	if err != nil {
		t.Fatal(err)
	}
	defer l.Close()

	ctxt, cancel := context.WithCancel(context.Background())
	defer cancel()

	// make the pool use the port already in use via a port range
	_, portStr, _ := net.SplitHostPort(l.Addr().String())
	port, _ := strconv.Atoi(portStr)
	pool, err := NewPool(
		PortRange(port, port+1),
		// skip the error log from the used port
		PoolLog(nil, nil, func(string, ...interface{}) {}),
	)
	if err != nil {
		t.Fatal(err)
	}

	c, err := pool.Allocate(ctxt)
	if err != nil {
		want := "address already in use"
		got := err.Error()
		if !strings.Contains(got, want) {
			t.Fatalf("wanted error to contain %q, but got %q", want, got)
		}
	} else {
		t.Fatal("wanted Allocate to error if port is in use")
		c.Release()
	}
}
<|endoftext|>


<commit_before>// Copyright (C) 2017 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package gapidapk

import (
	"bufio"
	"bytes"
	"context"
	"fmt"
	"io"
	"io/ioutil"
	"strings"
	"time"

	"github.com/golang/protobuf/proto"
	"github.com/google/gapid/core/app"
	"github.com/google/gapid/core/app/crash"
	"github.com/google/gapid/core/event/task"
	"github.com/google/gapid/core/log"
	"github.com/google/gapid/core/os/android"
	"github.com/google/gapid/core/os/android/adb"
	"github.com/google/gapid/core/os/device"
	"github.com/google/gapid/core/os/flock"
)

const (
	sendDevInfoAction        = "com.google.android.gapid.action.SEND_DEV_INFO"
	sendDevInfoService       = "com.google.android.gapid.DeviceInfoService"
	sendDevInfoPort          = "gapid-devinfo"
	startServiceAttempts     = 3
	portListeningAttempts    = 5
	perfettoProducerLauncher = "launch_producer"
	launcherPath             = "/data/local/tmp/gapid_launch_producer"
	launcherScript           = "nohup %[1]s &"
)

func init() {
	adb.RegisterDeviceInfoProvider(fetchDeviceInfo)
}

// Returns true if the device is listening to sendDevInfoPort, false if not.
// Error if failed at getting the port info.
func devInfoPortListening(ctx context.Context, d adb.Device) (bool, error) {
	var stdout bytes.Buffer
	if err := d.Shell("cat", "/proc/net/unix").Capture(&stdout, nil).Run(ctx); err != nil {
		return false, log.Errf(ctx, err, "Getting unix abstract port info...")
	}
	if strings.Contains(stdout.String(), sendDevInfoPort) {
		return true, nil
	}
	return false, nil
}

// startDevInfoService tries to start the fresh run of the package and start
// the service to send device info.
func startDevInfoService(ctx context.Context, d adb.Device, apk *APK) error {
	ctx = log.Enter(ctx, "startDevInfoService")
	var listening bool

	action := apk.ServiceActions.FindByName(sendDevInfoAction, sendDevInfoService)
	if action == nil {
		return log.Err(ctx, nil, "Service intent was not found")
	}

	// Try to start service.
	err := task.Retry(ctx, startServiceAttempts, 100*time.Millisecond,
		func(ctx context.Context) (bool, error) {
			log.I(ctx, "Attempt to start service: %s", sendDevInfoService)
			if err := d.StartService(ctx, *action); err != nil {
				return false, err
			}
			err := task.Retry(ctx, portListeningAttempts, time.Second, func(
				ctx context.Context) (bool, error) {
				var err error
				listening, err = devInfoPortListening(ctx, d)
				return listening, err
			})
			return listening, err
		})
	if listening {
		return nil
	}
	return log.Errf(ctx, err, "Start DevInfo service: Run out of attempts: %v",
		startServiceAttempts)
}

func fetchDeviceInfo(ctx context.Context, d adb.Device) error {
	apk, err := EnsureInstalled(ctx, d, device.UnknownABI)
	if err != nil {
		// The gapid.apk was not found. This can happen with partial builds used
		// for testing.
		// Don't return an error as this will prevent the device from being
		// registered and the device already comes with basic usable
		// information.
		log.W(ctx, "Couldn't find gapid.apk for device. Error: %v", err)
		return nil
	}

	driver, err := d.GraphicsDriver(ctx)
	if err != nil {
		return err
	}

	var cleanup app.Cleanup

	// Set up device info service to use prerelease driver.
	nextCleanup, err := adb.SetupPrereleaseDriver(ctx, d, apk.InstalledPackage)
	cleanup = cleanup.Then(nextCleanup)
	if err != nil {
		cleanup.Invoke(ctx)
		return err
	}

	// Set driver package
	nextCleanup, err = android.SetupLayers(ctx, d, apk.Name, []string{driver.Package}, []string{}, true)
	cleanup = cleanup.Then(nextCleanup)
	if err != nil {
		cleanup.Invoke(ctx)
		return err
	}
	defer cleanup.Invoke(ctx)

	if d.Instance().GetConfiguration().GetOS().GetAPIVersion() >= 29 {
		startSignal, startFunc := task.NewSignal()
		startFunc = task.Once(startFunc)
		crash.Go(func() {
			err := launchPerfettoProducerFromApk(ctx, d, startFunc)
			if err != nil {
				log.E(ctx, "[launchPerfettoProducerFromApk] error: %v", err)
			}

			// Ensure the start signal is fired on failure/immediate return.
			startFunc(ctx)
		})
		startSignal.Wait(ctx)
	}

	// Make sure the device is available to query device info, this is to prevent
	// Vulkan trace from happening at the same time than device info query.
	m := flock.Lock(d.Instance().GetSerial())
	defer m.Unlock()

	// Tries to start the device info service.
	if err := startDevInfoService(ctx, d, apk); err != nil {
		return log.Err(ctx, err, "Starting service")
	}

	sock, err := adb.ForwardAndConnect(ctx, d, sendDevInfoPort)
	if err != nil {
		return log.Err(ctx, err, "Connecting to service port")
	}

	defer sock.Close()

	data, err := ioutil.ReadAll(sock)
	if err != nil {
		return log.Err(ctx, err, "Reading data")
	}

	if err := proto.UnmarshalMerge(data, d.Instance()); err != nil {
		return log.Err(ctx, err, "Unmarshalling device Instance")
	}

	return nil
}

func preparePerfettoProducerLauncherFromApk(ctx context.Context, d adb.Device) error {
	packageName := PackageName(d.Instance().GetConfiguration().PreferredABI(nil))
	res, err := d.Shell("pm", "path", packageName).Call(ctx)
	if err != nil {
		return log.Errf(ctx, err, "Failed to query path to apk %v", packageName)
	}
	packagePath := strings.Split(res, ":")[1]
	d.Shell("rm", "-f", launcherPath).Call(ctx)
	if _, err := d.Shell("unzip", "-o", packagePath, "assets/"+perfettoProducerLauncher, "-p", ">", launcherPath).Call(ctx); err != nil {
		return log.Errf(ctx, err, "Failed to unzip %v from %v", perfettoProducerLauncher, packageName)
	}

	// Finally, make sure the binary is executable
	d.Shell("chmod", "a+x", launcherPath).Call(ctx)
	return nil
}

func launchPerfettoProducerFromApk(ctx context.Context, d adb.Device, startFunc task.Task) error {
	driver, err := d.GraphicsDriver(ctx)
	if err != nil {
		return err
	}

	// Extract the producer launcher from the APK.
	if err := preparePerfettoProducerLauncherFromApk(ctx, d); err != nil {
		return err
	}

	// Construct IO pipe, shell command outputs to stdout, GAPID reads from
	// reader for logging purpose.
	reader, stdout := io.Pipe()
	fail := make(chan error, 1)
	crash.Go(func() {
		buf := bufio.NewReader(reader)
		for {
			line, e := buf.ReadString('\n')
			// As long as there's output, consider the binary starting running.
			startFunc(ctx)
			switch e {
			default:
				log.E(ctx, "[launch producer] Read error %v", e)
				fail <- e
				return
			case io.EOF:
				fail <- nil
				return
			case nil:
				log.E(ctx, "[launch producer] %s", strings.TrimSuffix(adb.AnsiRegex.ReplaceAllString(line, ""), "\n"))
			}
		}
	})

	// Start the shell command to launch producer
	script := fmt.Sprintf(launcherScript, launcherPath)
	if driver.Package != "" {
		abi := d.Instance().GetConfiguration().PreferredABI(nil)
		script = "export LD_LIBRARY_PATH=\"" + driver.Path + "!/lib/" + abi.Name + "/\";" + script
	}
	process, err := d.Shell(script).
		Capture(stdout, stdout).
		Start(ctx)
	if err != nil {
		stdout.Close()
		return err
	}

	wait := make(chan error, 1)
	crash.Go(func() {
		wait <- process.Wait(ctx)
	})

	// Wait until either an error or EOF is read, or shell command exits.
	select {
	case err = <-fail:
		return err
	case err = <-wait:
		// Do nothing.
	}
	stdout.Close()
	if err != nil {
		return err
	}
	return <-fail
}
<commit_msg>Force stop already running gapidapk before starting (#28)<commit_after>// Copyright (C) 2017 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package gapidapk

import (
	"bufio"
	"bytes"
	"context"
	"fmt"
	"io"
	"io/ioutil"
	"strings"
	"time"

	"github.com/golang/protobuf/proto"
	"github.com/google/gapid/core/app"
	"github.com/google/gapid/core/app/crash"
	"github.com/google/gapid/core/event/task"
	"github.com/google/gapid/core/log"
	"github.com/google/gapid/core/os/android"
	"github.com/google/gapid/core/os/android/adb"
	"github.com/google/gapid/core/os/device"
	"github.com/google/gapid/core/os/flock"
)

const (
	sendDevInfoAction        = "com.google.android.gapid.action.SEND_DEV_INFO"
	sendDevInfoService       = "com.google.android.gapid.DeviceInfoService"
	sendDevInfoPort          = "gapid-devinfo"
	startServiceAttempts     = 3
	portListeningAttempts    = 5
	perfettoProducerLauncher = "launch_producer"
	launcherPath             = "/data/local/tmp/gapid_launch_producer"
	launcherScript           = "nohup %[1]s &"
)

func init() {
	adb.RegisterDeviceInfoProvider(fetchDeviceInfo)
}

// Returns true if the device is listening to sendDevInfoPort, false if not.
// Error if failed at getting the port info.
func devInfoPortListening(ctx context.Context, d adb.Device) (bool, error) {
	var stdout bytes.Buffer
	if err := d.Shell("cat", "/proc/net/unix").Capture(&stdout, nil).Run(ctx); err != nil {
		return false, log.Errf(ctx, err, "Getting unix abstract port info...")
	}
	if strings.Contains(stdout.String(), sendDevInfoPort) {
		return true, nil
	}
	return false, nil
}

// startDevInfoService tries to start the fresh run of the package and start
// the service to send device info.
func startDevInfoService(ctx context.Context, d adb.Device, apk *APK) error {
	ctx = log.Enter(ctx, "startDevInfoService")
	var listening bool

	action := apk.ServiceActions.FindByName(sendDevInfoAction, sendDevInfoService)
	if action == nil {
		return log.Err(ctx, nil, "Service intent was not found")
	}

	// Try to start service.
	err := task.Retry(ctx, startServiceAttempts, 100*time.Millisecond,
		func(ctx context.Context) (bool, error) {
			log.I(ctx, "Attempt to start service: %s", sendDevInfoService)
			if err := d.StartService(ctx, *action); err != nil {
				return false, err
			}
			err := task.Retry(ctx, portListeningAttempts, time.Second, func(
				ctx context.Context) (bool, error) {
				var err error
				listening, err = devInfoPortListening(ctx, d)
				return listening, err
			})
			return listening, err
		})
	if listening {
		return nil
	}
	return log.Errf(ctx, err, "Start DevInfo service: Run out of attempts: %v",
		startServiceAttempts)
}

func fetchDeviceInfo(ctx context.Context, d adb.Device) error {
	apk, err := EnsureInstalled(ctx, d, device.UnknownABI)
	if err != nil {
		// The gapid.apk was not found. This can happen with partial builds used
		// for testing.
		// Don't return an error as this will prevent the device from being
		// registered and the device already comes with basic usable
		// information.
		log.W(ctx, "Couldn't find gapid.apk for device. Error: %v", err)
		return nil
	}

	// Close any previous runs of the apk
	apk.Stop(ctx)

	driver, err := d.GraphicsDriver(ctx)
	if err != nil {
		return err
	}

	var cleanup app.Cleanup

	// Set up device info service to use prerelease driver.
	nextCleanup, err := adb.SetupPrereleaseDriver(ctx, d, apk.InstalledPackage)
	cleanup = cleanup.Then(nextCleanup)
	if err != nil {
		cleanup.Invoke(ctx)
		return err
	}

	// Set driver package
	nextCleanup, err = android.SetupLayers(ctx, d, apk.Name, []string{driver.Package}, []string{}, true)
	cleanup = cleanup.Then(nextCleanup)
	if err != nil {
		cleanup.Invoke(ctx)
		return err
	}
	defer cleanup.Invoke(ctx)

	if d.Instance().GetConfiguration().GetOS().GetAPIVersion() >= 29 {
		startSignal, startFunc := task.NewSignal()
		startFunc = task.Once(startFunc)
		crash.Go(func() {
			err := launchPerfettoProducerFromApk(ctx, d, startFunc)
			if err != nil {
				log.E(ctx, "[launchPerfettoProducerFromApk] error: %v", err)
			}

			// Ensure the start signal is fired on failure/immediate return.
			startFunc(ctx)
		})
		startSignal.Wait(ctx)
	}

	// Make sure the device is available to query device info, this is to prevent
	// Vulkan trace from happening at the same time than device info query.
	m := flock.Lock(d.Instance().GetSerial())
	defer m.Unlock()

	// Tries to start the device info service.
	if err := startDevInfoService(ctx, d, apk); err != nil {
		return log.Err(ctx, err, "Starting service")
	}

	sock, err := adb.ForwardAndConnect(ctx, d, sendDevInfoPort)
	if err != nil {
		return log.Err(ctx, err, "Connecting to service port")
	}

	defer sock.Close()

	data, err := ioutil.ReadAll(sock)
	if err != nil {
		return log.Err(ctx, err, "Reading data")
	}

	if err := proto.UnmarshalMerge(data, d.Instance()); err != nil {
		return log.Err(ctx, err, "Unmarshalling device Instance")
	}

	return nil
}

func preparePerfettoProducerLauncherFromApk(ctx context.Context, d adb.Device) error {
	packageName := PackageName(d.Instance().GetConfiguration().PreferredABI(nil))
	res, err := d.Shell("pm", "path", packageName).Call(ctx)
	if err != nil {
		return log.Errf(ctx, err, "Failed to query path to apk %v", packageName)
	}
	packagePath := strings.Split(res, ":")[1]
	d.Shell("rm", "-f", launcherPath).Call(ctx)
	if _, err := d.Shell("unzip", "-o", packagePath, "assets/"+perfettoProducerLauncher, "-p", ">", launcherPath).Call(ctx); err != nil {
		return log.Errf(ctx, err, "Failed to unzip %v from %v", perfettoProducerLauncher, packageName)
	}

	// Finally, make sure the binary is executable
	d.Shell("chmod", "a+x", launcherPath).Call(ctx)
	return nil
}

func launchPerfettoProducerFromApk(ctx context.Context, d adb.Device, startFunc task.Task) error {
	driver, err := d.GraphicsDriver(ctx)
	if err != nil {
		return err
	}

	// Extract the producer launcher from the APK.
	if err := preparePerfettoProducerLauncherFromApk(ctx, d); err != nil {
		return err
	}

	// Construct IO pipe, shell command outputs to stdout, GAPID reads from
	// reader for logging purpose.
	reader, stdout := io.Pipe()
	fail := make(chan error, 1)
	crash.Go(func() {
		buf := bufio.NewReader(reader)
		for {
			line, e := buf.ReadString('\n')
			// As long as there's output, consider the binary starting running.
			startFunc(ctx)
			switch e {
			default:
				log.E(ctx, "[launch producer] Read error %v", e)
				fail <- e
				return
			case io.EOF:
				fail <- nil
				return
			case nil:
				log.E(ctx, "[launch producer] %s", strings.TrimSuffix(adb.AnsiRegex.ReplaceAllString(line, ""), "\n"))
			}
		}
	})

	// Start the shell command to launch producer
	script := fmt.Sprintf(launcherScript, launcherPath)
	if driver.Package != "" {
		abi := d.Instance().GetConfiguration().PreferredABI(nil)
		script = "export LD_LIBRARY_PATH=\"" + driver.Path + "!/lib/" + abi.Name + "/\";" + script
	}
	process, err := d.Shell(script).
		Capture(stdout, stdout).
		Start(ctx)
	if err != nil {
		stdout.Close()
		return err
	}

	wait := make(chan error, 1)
	crash.Go(func() {
		wait <- process.Wait(ctx)
	})

	// Wait until either an error or EOF is read, or shell command exits.
	select {
	case err = <-fail:
		return err
	case err = <-wait:
		// Do nothing.
	}
	stdout.Close()
	if err != nil {
		return err
	}
	return <-fail
}
<|endoftext|>