func newReadSeeker(input []string, initialLeg int) (*readSeeker, chan struct{}) {
	sigc := make(chan struct{})
	rs := &readSeeker{}
	i := 0
	for ; i < initialLeg; i++ {
		rs.write(input[i])
	}
	go func() {
		<-sigc
		for ; i < len(input); i++ {
			time.Sleep(5 * time.Millisecond)
			rs.write(input[i])
		}
	}()
	return rs, sigc
}
newReadSeeker creates the ReadSeeker with the passed input. The data
is written with an initial number of lines and then waits for a signal
to continue.
package scroller_test
import (
	"bufio"
	"bytes"
	"errors"
	"fmt"
	"io"
	"sync"
	"testing"
	"time"
	"github.com/tideland/golib/audit"
	"github.com/tideland/golib/loop"
	"github.com/tideland/golib/scroller"
)
var tests = []struct {
	description      string
	initialLeg       int
	options          func() []scroller.Option
	initialExpected  []int
	appendedExpected []int
	injector         func(*scroller.Scroller, *readSeeker) func([]string)
	err              string
}{{
	description: "no lines existing; initially no lines scrolled",
	options: func() []scroller.Option {
		return []scroller.Option{
			scroller.PollTime(2 * time.Millisecond),
		}
	},
	initialExpected:  []int{},
	appendedExpected: intRange(0, 99),
}, {
	description: "no lines existing; initially five lines scrolled",
	options: func() []scroller.Option {
		return []scroller.Option{
			scroller.Lines(5),
			scroller.PollTime(2 * time.Millisecond),
		}
	},
	initialExpected:  []int{},
	appendedExpected: intRange(0, 99),
}, {
	description: "ten lines existing; initially no lines scrolled",
	initialLeg:  10,
	options: func() []scroller.Option {
		return []scroller.Option{
			scroller.PollTime(2 * time.Millisecond),
		}
	},
	initialExpected:  []int{},
	appendedExpected: intRange(10, 99),
}, {
	description: "ten lines existing; initially five lines scrolled",
	initialLeg:  10,
	options: func() []scroller.Option {
		return []scroller.Option{
			scroller.Lines(5),
			scroller.PollTime(2 * time.Millisecond),
		}
	},
	initialExpected:  intRange(5, 9),
	appendedExpected: intRange(10, 99),
}, {
	description: "ten lines existing; initially twenty lines scrolled",
	initialLeg:  10,
	options: func() []scroller.Option {
		return []scroller.Option{
			scroller.Lines(20),
			scroller.PollTime(2 * time.Millisecond),
		}
	},
	initialExpected:  intRange(0, 9),
	appendedExpected: intRange(10, 99),
}, {
	description: "ten lines existing; initially twenty lines scrolled; buffer smaller than lines",
	initialLeg:  10,
	options: func() []scroller.Option {
		return []scroller.Option{
			scroller.Lines(20),
			scroller.PollTime(2 * time.Millisecond),
			scroller.BufferSize(10),
		}
	},
	initialExpected:  intRange(0, 9),
	appendedExpected: intRange(10, 99),
}, {
	description: "ten lines existing; initially three lines scrolled; filter lines with special prefix",
	initialLeg:  10,
	options: func() []scroller.Option {
		return []scroller.Option{
			scroller.Lines(3),
			scroller.Filter(func(line []byte) bool { return bytes.HasPrefix(line, specialPrefix) }),
			scroller.PollTime(2 * time.Millisecond),
		}
	},
	initialExpected:  []int{3, 5, 8},
	appendedExpected: []int{13, 21, 44, 65},
}, {
	description: "ten lines existing; initially five lines scrolled; error after further 25 lines",
	initialLeg:  10,
	options: func() []scroller.Option {
		return []scroller.Option{
			scroller.Lines(5),
			scroller.PollTime(2 * time.Millisecond),
		}
	},
	initialExpected:  intRange(5, 9),
	appendedExpected: intRange(10, 99),
	injector: func(s *scroller.Scroller, rs *readSeeker) func([]string) {
		return func(lines []string) {
			if len(lines) == 25 {
				rs.setError("ouch")
			}
		}
	},
	err: "ouch",
}, {
	description: "ten lines existing; initially five lines scrolled; simply stop after 25 lines",
	initialLeg:  10,
	options: func() []scroller.Option {
		return []scroller.Option{
			scroller.Lines(5),
			scroller.PollTime(2 * time.Millisecond),
		}
	},
	initialExpected:  intRange(5, 9),
	appendedExpected: intRange(10, 99),
	injector: func(s *scroller.Scroller, rs *readSeeker) func([]string) {
		return func(lines []string) {
			if len(lines) == 25 {
				s.Stop()
			}
		}
	},
}, {
	description: "unterminated last line is not scrolled",
	initialLeg:  103,
	options: func() []scroller.Option {
		return []scroller.Option{
			scroller.Lines(5),
			scroller.PollTime(2 * time.Millisecond),
		}
	},
	initialExpected:  intRange(95, 97),
	appendedExpected: intRange(98, 99),
}}
func TestScroller(t *testing.T) {
	assert := audit.NewTestingAssertion(t, true)
	input, output := generateTestData()
	for i, test := range tests {
		assert.Logf("test #%d/%d: %s", i+1, len(tests), test.description)
		rs, sigc := newReadSeeker(input, test.initialLeg)
		receiver := newReceiver(assert, output)
		options := []scroller.Option{}
		if test.options != nil {
			options = test.options()
		}
		s, err := scroller.NewScroller(rs, receiver.writer, options...)
		assert.Nil(err)
		receiver.autoClose(s)
		var injection func([]string)
		if test.injector != nil {
			injection = test.injector(s, rs)
		}
		receiver.assertCollected(test.initialExpected, nil)
		sigc <- struct{}{}
		receiver.assertCollected(test.appendedExpected, injection)
		if test.err == "" {
			assert.Nil(s.Stop())
		} else {
			st, err := s.Error()
			assert.Equal(st, loop.Stopped)
			assert.ErrorMatch(err, test.err)
		}
	}
}
func intRange(lo, hi int) []int {
	is := []int{}
	for i := lo; i <= hi; i++ {
		is = append(is, i)
	}
	return is
}
var (
	regularPrefix = []byte("[REGULAR]")
	specialPrefix = []byte("[SPECIAL]")
)
func generateTestData() (input, output []string) {
	tagged := []int{1, 2, 3, 5, 8, 13, 21, 44, 65}
	rand := audit.FixedRand()
	gen := audit.NewGenerator(rand)
	line := ""
	for i := 0; i < 98; i++ {
		switch {
		case i%10 == 0:
			line = "\n"
		case len(tagged) > 0 && i == tagged[0]:
			line = fmt.Sprintf("%s #%d ", specialPrefix, i) + gen.Sentence() + "\n"
			tagged = tagged[1:]
		default:
			line = fmt.Sprintf("%s #%d ", regularPrefix, i) + gen.Sentence() + "\n"
		}
		input = append(input, line)
		output = append(output, line)
	}
	tmp := ""
	for i := 0; i < 4; i++ {
		if i%2 == 0 {
			line = fmt.Sprintf("%s #%d ", regularPrefix, i) +
				gen.Sentence() + " " +
				gen.Sentence() + " "
			tmp = line
		} else {
			line = gen.Sentence() + " " + gen.Sentence() + "\n"
			tmp += line
		}
		input = append(input, line)
		if i%2 != 0 {
			output = append(output, tmp)
			tmp = ""
		}
	}
	line = fmt.Sprintf("%s #%d ", specialPrefix, 100) + gen.Sentence()
	input = append(input, line)
	return input, output
}
type readSeeker struct {
	mux    sync.Mutex
	buffer []byte
	pos    int
	err    error
}