func TestExceedBudget(t *testing.T) {
	ctx, cancel := context.WithCancel(context.Background())
	retry := 0
	start := func(config interface{}, epoch int, _ <-chan error) error {
		if epoch == 0 && retry == 0 {
			retry++
			return fmt.Errorf("error on try %d", retry)
		} else if epoch == 0 && retry == 1 {
			retry++
			return fmt.Errorf("error on try %d", retry)
		} else {
			t.Errorf("Unexpected epoch %d and retry %d", epoch, retry)
			cancel()
		}
		return nil
	}
	cleanup := func(epoch int) {
		if epoch == 0 && (retry == 0 || retry == 1 || retry == 2) {
		} else {
			t.Errorf("Unexpected epoch %d and retry %d", epoch, retry)
			cancel()
		}
	}
	retryDelay := testRetry
	retryDelay.MaxRetries = 1
	a := NewAgent(TestProxy{start, cleanup, func(_ interface{}) { cancel() }}, retryDelay)
	go a.Run(ctx)
	a.ScheduleConfigUpdate("test")
	<-ctx.Done()
}
TestExceedBudget drives to permanent failure
package proxy
import (
	"context"
	"errors"
	"fmt"
	"testing"
	"time"
)
var (
	testRetry = Retry{
		InitialInterval: time.Millisecond,
		MaxRetries:      10,
	}
)
type TestProxy struct {
	run     func(interface{}, int, <-chan error) error
	cleanup func(int)
	panic   func(interface{})
}
func (tp TestProxy) Run(config interface{}, epoch int, stop <-chan error) error {
	return tp.run(config, epoch, stop)
}
func (tp TestProxy) Cleanup(epoch int) {
	if tp.cleanup != nil {
		tp.cleanup(epoch)
	}
}
func (tp TestProxy) Panic(config interface{}) {
	if tp.panic != nil {
		tp.panic(config)
	}
}
func TestStartStop(t *testing.T) {
	current := -1
	ctx, cancel := context.WithCancel(context.Background())
	desired := "config"
	start := func(config interface{}, epoch int, _ <-chan error) error {
		if current != -1 {
			t.Error("Expected epoch not to be set")
		}
		if epoch != 0 {
			t.Error("Expected initial epoch to be 0")
		}
		if config != desired {
			t.Errorf("Start got config %v, want %v", config, desired)
		}
		current = epoch
		return nil
	}
	cleanup := func(epoch int) {
		if current != 0 {
			t.Error("Expected epoch to be set")
		}
		if epoch != 0 {
			t.Error("Expected initial epoch in cleanup to be 0")
		}
		cancel()
	}
	a := NewAgent(TestProxy{start, cleanup, nil}, testRetry)
	go a.Run(ctx)
	a.ScheduleConfigUpdate(desired)
	<-ctx.Done()
}
func TestApplyTwice(t *testing.T) {
	ctx, cancel := context.WithCancel(context.Background())
	desired := "config"
	start := func(config interface{}, epoch int, _ <-chan error) error {
		if epoch == 1 {
			t.Error("Should start only once for same config")
		}
		<-ctx.Done()
		return nil
	}
	cleanup := func(epoch int) {}
	a := NewAgent(TestProxy{start, cleanup, nil}, testRetry)
	go a.Run(ctx)
	a.ScheduleConfigUpdate(desired)
	a.ScheduleConfigUpdate(desired)
	cancel()
}
func TestApplyThrice(t *testing.T) {
	ctx, cancel := context.WithCancel(context.Background())
	good := "good"
	bad := "bad"
	applied := false
	var a Agent
	start := func(config interface{}, epoch int, _ <-chan error) error {
		if config == bad {
			return nil
		}
		if config == good && applied {
			t.Errorf("Config has already been applied")
		}
		applied = true
		<-ctx.Done()
		return nil
	}
	cleanup := func(epoch int) {
		if epoch == 1 {
			go func() {
				a.ScheduleConfigUpdate(good)
				cancel()
			}()
		} else if epoch != 0 {
			t.Errorf("Unexpected epoch %d", epoch)
		}
	}
	retry := testRetry
	retry.MaxRetries = 0
	a = NewAgent(TestProxy{start, cleanup, nil}, retry)
	go a.Run(ctx)
	a.ScheduleConfigUpdate(good)
	a.ScheduleConfigUpdate(bad)
	<-ctx.Done()
}
func TestAbort(t *testing.T) {
	ctx, cancel := context.WithCancel(context.Background())
	good1 := "good1"
	aborted1 := false
	good2 := "good2"
	aborted2 := false
	bad := "bad"
	active := 3
	start := func(config interface{}, epoch int, abort <-chan error) error {
		if config == bad {
			return errors.New(bad)
		}
		select {
		case err := <-abort:
			if config == good1 {
				aborted1 = true
			} else if config == good2 {
				aborted2 = true
			}
			return err
		case <-ctx.Done():
		}
		return nil
	}
	cleanup := func(epoch int) {
		active = active - 1
		if active == 0 {
			if !aborted1 {
				t.Error("Expected first epoch to be aborted")
			}
			if !aborted2 {
				t.Error("Expected second epoch to be aborted")
			}
			cancel()
		}
	}
	retry := testRetry
	retry.InitialInterval = 10 * time.Second
	a := NewAgent(TestProxy{start, cleanup, nil}, retry)
	go a.Run(ctx)
	a.ScheduleConfigUpdate(good1)
	a.ScheduleConfigUpdate(good2)
	a.ScheduleConfigUpdate(bad)
	<-ctx.Done()
}
func TestStartFail(t *testing.T) {
	ctx, cancel := context.WithCancel(context.Background())
	retry := 0
	start := func(config interface{}, epoch int, _ <-chan error) error {
		if epoch == 0 && retry == 0 {
			retry++
			return fmt.Errorf("error on try %d", retry)
		} else if epoch == 0 && retry == 1 {
			retry++
			return fmt.Errorf("error on try %d", retry)
		} else if epoch == 0 && retry == 2 {
			retry++
			cancel()
		} else {
			t.Errorf("Unexpected epoch %d and retry %d", epoch, retry)
			cancel()
		}
		return nil
	}
	cleanup := func(epoch int) {}
	a := NewAgent(TestProxy{start, cleanup, nil}, testRetry)
	go a.Run(ctx)
	a.ScheduleConfigUpdate("test")
	<-ctx.Done()
}