star/bed/window/manager_test.go
2025-04-26 19:33:14 +08:00

709 lines
21 KiB
Go

package window
import (
"os"
"path/filepath"
"reflect"
"runtime"
"strings"
"testing"
"b612.me/apps/b612/bed/buffer"
"b612.me/apps/b612/bed/event"
"b612.me/apps/b612/bed/layout"
"b612.me/apps/b612/bed/mode"
)
func createTemp(dir, contents string) (*os.File, error) {
f, err := os.CreateTemp(dir, "")
if err != nil {
return nil, err
}
if _, err = f.WriteString(contents); err != nil {
return nil, err
}
if err = f.Close(); err != nil {
return nil, err
}
return f, nil
}
func TestManagerOpenEmpty(t *testing.T) {
wm := NewManager()
eventCh, redrawCh, waitCh := make(chan event.Event), make(chan struct{}), make(chan struct{})
wm.Init(eventCh, redrawCh)
go func() {
defer func() {
close(eventCh)
close(redrawCh)
close(waitCh)
}()
ev := <-eventCh
if ev.Type != event.Error {
t.Errorf("event type should be %d but got: %d", event.Error, ev.Type)
}
if expected := "no file name"; ev.Error.Error() != expected {
t.Errorf("err should be %q but got: %v", expected, ev.Error)
}
}()
wm.SetSize(110, 20)
if err := wm.Open(""); err != nil {
t.Fatalf("err should be nil but got: %v", err)
}
windowStates, _, windowIndex, err := wm.State()
if expected := 0; windowIndex != expected {
t.Errorf("windowIndex should be %d but got %d", expected, windowIndex)
}
ws, ok := windowStates[windowIndex]
if !ok {
t.Fatalf("windowStates should contain %d but got: %v", windowIndex, windowStates)
}
if expected := ""; ws.Name != expected {
t.Errorf("name should be %q but got %q", expected, ws.Name)
}
if ws.Width != 16 {
t.Errorf("width should be %d but got %d", 16, ws.Width)
}
if ws.Size != 0 {
t.Errorf("size should be %d but got %d", 0, ws.Size)
}
if ws.Length != int64(0) {
t.Errorf("Length should be %d but got %d", int64(0), ws.Length)
}
if expected := "\x00"; !strings.HasPrefix(string(ws.Bytes), expected) {
t.Errorf("Bytes should start with %q but got %q", expected, string(ws.Bytes))
}
if err != nil {
t.Errorf("err should be nil but got: %v", err)
}
wm.Emit(event.Event{Type: event.Write})
<-waitCh
wm.Close()
}
func TestManagerOpenStates(t *testing.T) {
wm := NewManager()
wm.Init(nil, nil)
wm.SetSize(110, 20)
str := "Hello, world! こんにちは、世界!"
f, err := createTemp(t.TempDir(), str)
if err != nil {
t.Fatalf("err should be nil but got: %v", err)
}
if err := wm.Open(f.Name()); err != nil {
t.Fatalf("err should be nil but got: %v", err)
}
windowStates, _, windowIndex, err := wm.State()
if expected := 0; windowIndex != expected {
t.Errorf("windowIndex should be %d but got %d", expected, windowIndex)
}
ws, ok := windowStates[windowIndex]
if !ok {
t.Fatalf("windowStates should contain %d but got: %v", windowIndex, windowStates)
}
if expected := filepath.Base(f.Name()); ws.Name != expected {
t.Errorf("name should be %q but got %q", expected, ws.Name)
}
if ws.Width != 16 {
t.Errorf("width should be %d but got %d", 16, ws.Width)
}
if ws.Size != 41 {
t.Errorf("size should be %d but got %d", 41, ws.Size)
}
if ws.Length != int64(41) {
t.Errorf("Length should be %d but got %d", int64(41), ws.Length)
}
if !strings.HasPrefix(string(ws.Bytes), str) {
t.Errorf("Bytes should start with %q but got %q", str, string(ws.Bytes))
}
if err != nil {
t.Errorf("err should be nil but got: %v", err)
}
wm.Close()
}
func TestManagerOpenNonExistsWrite(t *testing.T) {
wm := NewManager()
eventCh, redrawCh, waitCh := make(chan event.Event), make(chan struct{}), make(chan struct{})
wm.Init(eventCh, redrawCh)
go func() {
defer func() {
close(eventCh)
close(redrawCh)
close(waitCh)
}()
for range 16 {
<-redrawCh
}
if ev := <-eventCh; ev.Type != event.QuitAll {
t.Errorf("event type should be %d but got: %d", event.QuitAll, ev.Type)
}
}()
wm.SetSize(110, 20)
fname := filepath.Join(t.TempDir(), "test")
if err := wm.Open(fname); err != nil {
t.Fatalf("err should be nil but got: %v", err)
}
_, _, _, _ = wm.State()
str := "Hello, world!"
wm.Emit(event.Event{Type: event.StartInsert})
wm.Emit(event.Event{Type: event.SwitchFocus})
for _, c := range str {
wm.Emit(event.Event{Type: event.Rune, Rune: c, Mode: mode.Insert})
}
wm.Emit(event.Event{Type: event.ExitInsert})
wm.Emit(event.Event{Type: event.WriteQuit})
windowStates, _, windowIndex, err := wm.State()
if expected := 0; windowIndex != expected {
t.Errorf("windowIndex should be %d but got %d", expected, windowIndex)
}
ws, ok := windowStates[windowIndex]
if !ok {
t.Fatalf("windowStates should contain %d but got: %v", windowIndex, windowStates)
}
if expected := filepath.Base(fname); ws.Name != expected {
t.Errorf("name should be %q but got %q", expected, ws.Name)
}
if ws.Width != 16 {
t.Errorf("width should be %d but got %d", 16, ws.Width)
}
if ws.Size != 13 {
t.Errorf("size should be %d but got %d", 13, ws.Size)
}
if ws.Length != int64(13) {
t.Errorf("Length should be %d but got %d", int64(13), ws.Length)
}
if !strings.HasPrefix(string(ws.Bytes), str) {
t.Errorf("Bytes should start with %q but got %q", str, string(ws.Bytes))
}
if err != nil {
t.Errorf("err should be nil but got: %v", err)
}
bs, err := os.ReadFile(fname)
if err != nil {
t.Errorf("err should be nil but got: %v", err)
}
if string(bs) != str {
t.Errorf("file contents should be %q but got %q", str, string(bs))
}
<-waitCh
wm.Close()
}
func TestManagerOpenExpandBacktick(t *testing.T) {
wm := NewManager()
wm.Init(nil, nil)
wm.SetSize(110, 20)
cmd, name := "`which ls`", "ls"
if runtime.GOOS == "windows" {
cmd, name = "`where ping`", "PING.EXE"
}
if err := wm.Open(cmd); err != nil {
t.Fatalf("err should be nil but got: %v", err)
}
windowStates, _, windowIndex, err := wm.State()
ws, ok := windowStates[windowIndex]
if !ok {
t.Fatalf("windowStates should contain %d but got: %v", windowIndex, windowStates)
}
if ws.Name != name {
t.Errorf("name should be %q but got %q", name, ws.Name)
}
if ws.Width != 16 {
t.Errorf("width should be %d but got %d", 16, ws.Width)
}
if ws.Size == 0 {
t.Errorf("size should not be %d but got %d", 0, ws.Size)
}
if ws.Length == 0 {
t.Errorf("length should not be %d but got %d", 0, ws.Length)
}
if err != nil {
t.Errorf("err should be nil but got: %v", err)
}
wm.Close()
}
func TestManagerOpenExpandHomedir(t *testing.T) {
if runtime.GOOS == "windows" {
t.Skip("skip on Windows")
}
wm := NewManager()
wm.Init(nil, nil)
wm.SetSize(110, 20)
str := "Hello, world!"
f, err := createTemp(t.TempDir(), str)
if err != nil {
t.Fatalf("err should be nil but got: %v", err)
}
home := os.Getenv("HOME")
t.Cleanup(func() {
if err := os.Setenv("HOME", home); err != nil {
t.Errorf("err should be nil but got: %v", err)
}
})
if err := os.Setenv("HOME", filepath.Dir(f.Name())); err != nil {
t.Fatalf("err should be nil but got: %v", err)
}
for i, prefix := range []string{"~/", "$HOME/"} {
if err := wm.Open(prefix + filepath.Base(f.Name())); err != nil {
t.Fatalf("err should be nil but got: %v", err)
}
windowStates, _, windowIndex, err := wm.State()
if windowIndex != i {
t.Errorf("windowIndex should be %d but got %d", i, windowIndex)
}
ws, ok := windowStates[windowIndex]
if !ok {
t.Fatalf("windowStates should contain %d but got: %v", windowIndex, windowStates)
}
if expected := filepath.Base(f.Name()); ws.Name != expected {
t.Errorf("name should be %q but got %q", expected, ws.Name)
}
if !strings.HasPrefix(string(ws.Bytes), str) {
t.Errorf("Bytes should start with %q but got %q", str, string(ws.Bytes))
}
if err != nil {
t.Errorf("err should be nil but got: %v", err)
}
}
wm.Close()
}
func TestManagerOpenChdirWrite(t *testing.T) {
if runtime.GOOS == "windows" {
t.Skip("skip on Windows")
}
wm := NewManager()
eventCh, redrawCh, waitCh := make(chan event.Event), make(chan struct{}), make(chan struct{})
wm.Init(eventCh, redrawCh)
f, err := createTemp(t.TempDir(), "Hello")
if err != nil {
t.Fatalf("err should be nil but got: %v", err)
}
go func() {
defer func() {
close(eventCh)
close(redrawCh)
close(waitCh)
}()
ev := <-eventCh
if ev.Type != event.Info {
t.Errorf("event type should be %d but got: %d", event.Info, ev.Type)
}
dir, err := filepath.EvalSymlinks(filepath.Dir(f.Name()))
if err != nil {
t.Errorf("err should be nil but got: %v", err)
}
if expected := dir; ev.Error.Error() != expected {
t.Errorf("err should be %q but got: %v", expected, ev.Error)
}
ev = <-eventCh
if ev.Type != event.Info {
t.Errorf("event type should be %d but got: %d", event.Info, ev.Type)
}
if expected := filepath.Dir(dir); ev.Error.Error() != expected {
t.Errorf("err should be %q but got: %v", expected, ev.Error)
}
for range 11 {
<-redrawCh
}
ev = <-eventCh
if ev.Type != event.Info {
t.Errorf("event type should be %d but got: %d", event.Info, ev.Type)
}
if expected := "13 (0xd) bytes written"; !strings.HasSuffix(ev.Error.Error(), expected) {
t.Errorf("err should be %q but got: %v", expected, ev.Error)
}
}()
wm.SetSize(110, 20)
wm.Emit(event.Event{Type: event.Chdir, Arg: filepath.Dir(f.Name())})
if err := wm.Open(filepath.Base(f.Name())); err != nil {
t.Fatalf("err should be nil but got: %v", err)
}
_, _, windowIndex, _ := wm.State()
if expected := 0; windowIndex != expected {
t.Errorf("windowIndex should be %d but got %d", expected, windowIndex)
}
wm.Emit(event.Event{Type: event.Chdir, Arg: "../"})
wm.Emit(event.Event{Type: event.StartAppendEnd})
wm.Emit(event.Event{Type: event.SwitchFocus})
for _, c := range ", world!" {
wm.Emit(event.Event{Type: event.Rune, Rune: c, Mode: mode.Insert})
}
wm.Emit(event.Event{Type: event.ExitInsert})
wm.Emit(event.Event{Type: event.Write})
bs, err := os.ReadFile(f.Name())
if err != nil {
t.Errorf("err should be nil but got: %v", err)
}
if expected := "Hello, world!"; string(bs) != expected {
t.Errorf("file contents should be %q but got %q", expected, string(bs))
}
<-waitCh
wm.Close()
}
func TestManagerOpenDirectory(t *testing.T) {
wm := NewManager()
wm.Init(nil, nil)
wm.SetSize(110, 20)
dir := t.TempDir()
if err := wm.Open(dir); err != nil {
if expected := dir + " is a directory"; err.Error() != expected {
t.Errorf("err should be %q but got: %v", expected, err)
}
} else {
t.Errorf("err should not be nil but got: %v", err)
}
wm.Close()
}
func TestManagerRead(t *testing.T) {
wm := NewManager()
wm.Init(nil, nil)
wm.SetSize(110, 20)
r := strings.NewReader("Hello, world!")
if err := wm.Read(r); err != nil {
t.Fatalf("err should be nil but got: %v", err)
}
windowStates, _, windowIndex, err := wm.State()
if expected := 0; windowIndex != expected {
t.Errorf("windowIndex should be %d but got %d", expected, windowIndex)
}
ws, ok := windowStates[windowIndex]
if !ok {
t.Fatalf("windowStates should contain %d but got: %v", windowIndex, windowStates)
}
if ws.Name != "" {
t.Errorf("name should be %q but got %q", "", ws.Name)
}
if ws.Width != 16 {
t.Errorf("width should be %d but got %d", 16, ws.Width)
}
if ws.Size != 13 {
t.Errorf("size should be %d but got %d", 13, ws.Size)
}
if ws.Length != int64(13) {
t.Errorf("Length should be %d but got %d", int64(13), ws.Length)
}
if err != nil {
t.Errorf("err should be nil but got: %v", err)
}
wm.Close()
}
func TestManagerOnly(t *testing.T) {
wm := NewManager()
eventCh, redrawCh, waitCh := make(chan event.Event), make(chan struct{}), make(chan struct{})
wm.Init(eventCh, redrawCh)
go func() {
defer func() {
close(eventCh)
close(redrawCh)
close(waitCh)
}()
for range 4 {
<-eventCh
}
}()
wm.SetSize(110, 20)
if err := wm.Open(""); err != nil {
t.Fatalf("err should be nil but got: %v", err)
}
wm.Emit(event.Event{Type: event.Vnew})
wm.Emit(event.Event{Type: event.Vnew})
wm.Emit(event.Event{Type: event.FocusWindowRight})
wm.Resize(110, 20)
_, got, _, _ := wm.State()
expected := layout.NewLayout(0).SplitLeft(1).SplitLeft(2).
Activate(1).Resize(0, 0, 110, 20)
if !reflect.DeepEqual(got, expected) {
t.Errorf("layout should be %#v but got %#v", expected, got)
}
wm.Emit(event.Event{Type: event.Only})
wm.Resize(110, 20)
_, got, _, _ = wm.State()
expected = layout.NewLayout(1).Resize(0, 0, 110, 20)
if !reflect.DeepEqual(got, expected) {
t.Errorf("layout should be %#v but got %#v", expected, got)
}
<-waitCh
wm.Close()
}
func TestManagerAlternative(t *testing.T) {
wm := NewManager()
eventCh, redrawCh, waitCh := make(chan event.Event), make(chan struct{}), make(chan struct{})
wm.Init(eventCh, redrawCh)
go func() {
defer func() {
close(eventCh)
close(redrawCh)
close(waitCh)
}()
for range 9 {
<-eventCh
}
}()
wm.SetSize(110, 20)
if err := os.Chdir(os.TempDir()); err != nil {
t.Fatalf("err should be nil but got: %v", err)
}
if err := wm.Open("bed-test-manager-alternative-1"); err != nil {
t.Fatalf("err should be nil but got: %v", err)
}
if err := wm.Open("bed-test-manager-alternative-2"); err != nil {
t.Fatalf("err should be nil but got: %v", err)
}
wm.Emit(event.Event{Type: event.Alternative})
_, _, windowIndex, _ := wm.State()
if expected := 0; windowIndex != expected {
t.Errorf("windowIndex should be %d but got %d", expected, windowIndex)
}
if err := wm.Open("bed-test-manager-alternative-3"); err != nil {
t.Errorf("err should be nil but got: %v", err)
}
_, _, windowIndex, _ = wm.State()
if expected := 2; windowIndex != expected {
t.Errorf("windowIndex should be %d but got %d", expected, windowIndex)
}
wm.Emit(event.Event{Type: event.Alternative})
_, _, windowIndex, _ = wm.State()
if expected := 0; windowIndex != expected {
t.Errorf("windowIndex should be %d but got %d", expected, windowIndex)
}
wm.Emit(event.Event{Type: event.Alternative})
_, _, windowIndex, _ = wm.State()
if expected := 2; windowIndex != expected {
t.Errorf("windowIndex should be %d but got %d", expected, windowIndex)
}
if err := wm.Open("bed-test-manager-alternative-4"); err != nil {
t.Errorf("err should be nil but got: %v", err)
}
_, _, windowIndex, _ = wm.State()
if expected := 3; windowIndex != expected {
t.Errorf("windowIndex should be %d but got %d", expected, windowIndex)
}
wm.Emit(event.Event{Type: event.Alternative, Count: 2})
_, _, windowIndex, _ = wm.State()
if expected := 1; windowIndex != expected {
t.Errorf("windowIndex should be %d but got %d", expected, windowIndex)
}
wm.Emit(event.Event{Type: event.Alternative, Count: 4})
_, _, windowIndex, _ = wm.State()
if expected := 3; windowIndex != expected {
t.Errorf("windowIndex should be %d but got %d", expected, windowIndex)
}
wm.Emit(event.Event{Type: event.Alternative})
_, _, windowIndex, _ = wm.State()
if expected := 1; windowIndex != expected {
t.Errorf("windowIndex should be %d but got %d", expected, windowIndex)
}
wm.Emit(event.Event{Type: event.Edit, Arg: "#2"})
_, _, windowIndex, _ = wm.State()
if expected := 1; windowIndex != expected {
t.Errorf("windowIndex should be %d but got %d", expected, windowIndex)
}
wm.Emit(event.Event{Type: event.Edit, Arg: "#4"})
_, _, windowIndex, _ = wm.State()
if expected := 3; windowIndex != expected {
t.Errorf("windowIndex should be %d but got %d", expected, windowIndex)
}
wm.Emit(event.Event{Type: event.Edit, Arg: "#"})
_, _, windowIndex, _ = wm.State()
if expected := 1; windowIndex != expected {
t.Errorf("windowIndex should be %d but got %d", expected, windowIndex)
}
<-waitCh
wm.Close()
}
func TestManagerWincmd(t *testing.T) {
wm := NewManager()
eventCh, redrawCh, waitCh := make(chan event.Event), make(chan struct{}), make(chan struct{})
wm.Init(eventCh, redrawCh)
go func() {
defer func() {
close(eventCh)
close(redrawCh)
close(waitCh)
}()
for range 17 {
<-eventCh
}
}()
wm.SetSize(110, 20)
if err := wm.Open(""); err != nil {
t.Fatalf("err should be nil but got: %v", err)
}
wm.Emit(event.Event{Type: event.Wincmd, Arg: "n"})
wm.Emit(event.Event{Type: event.Wincmd, Arg: "n"})
wm.Emit(event.Event{Type: event.Wincmd, Arg: "n"})
wm.Emit(event.Event{Type: event.MoveWindowLeft})
wm.Emit(event.Event{Type: event.FocusWindowRight})
wm.Emit(event.Event{Type: event.FocusWindowBottomRight})
wm.Emit(event.Event{Type: event.MoveWindowRight})
wm.Emit(event.Event{Type: event.FocusWindowLeft})
wm.Emit(event.Event{Type: event.MoveWindowTop})
wm.Resize(110, 20)
_, got, _, _ := wm.State()
expected := layout.NewLayout(2).SplitBottom(0).SplitLeft(1).
SplitLeft(3).Activate(2).Resize(0, 0, 110, 20)
if !reflect.DeepEqual(got, expected) {
t.Errorf("layout should be %#v but got %#v", expected, got)
}
wm.Emit(event.Event{Type: event.FocusWindowDown})
wm.Emit(event.Event{Type: event.FocusWindowRight})
wm.Emit(event.Event{Type: event.Quit})
_, got, _, _ = wm.State()
expected = layout.NewLayout(2).SplitBottom(0).SplitLeft(3).Resize(0, 0, 110, 20)
if !reflect.DeepEqual(got, expected) {
t.Errorf("layout should be %#v but got %#v", expected, got)
}
wm.Emit(event.Event{Type: event.Wincmd, Arg: "o"})
_, got, _, _ = wm.State()
expected = layout.NewLayout(3).Resize(0, 0, 110, 20)
if !reflect.DeepEqual(got, expected) {
t.Errorf("layout should be %#v but got %#v", expected, got)
}
wm.Emit(event.Event{Type: event.MoveWindowLeft})
wm.Emit(event.Event{Type: event.MoveWindowRight})
wm.Emit(event.Event{Type: event.MoveWindowTop})
wm.Emit(event.Event{Type: event.MoveWindowBottom})
wm.Resize(110, 20)
_, got, _, _ = wm.State()
if !reflect.DeepEqual(got, expected) {
t.Errorf("layout should be %#v but got %#v", expected, got)
}
<-waitCh
wm.Close()
}
func TestManagerCopyCutPaste(t *testing.T) {
wm := NewManager()
eventCh, redrawCh, waitCh := make(chan event.Event), make(chan struct{}), make(chan struct{})
wm.Init(eventCh, redrawCh)
str := "Hello, world!"
f, err := createTemp(t.TempDir(), str)
if err != nil {
t.Fatalf("err should be nil but got: %v", err)
}
wm.SetSize(110, 20)
if err := wm.Open(f.Name()); err != nil {
t.Fatalf("err should be nil but got: %v", err)
}
_, _, _, _ = wm.State()
go func() {
defer func() {
close(eventCh)
close(redrawCh)
close(waitCh)
}()
<-redrawCh
<-redrawCh
<-redrawCh
waitCh <- struct{}{}
ev := <-eventCh
if ev.Type != event.Copied {
t.Errorf("event type should be %d but got: %d", event.Copied, ev.Type)
}
if ev.Buffer == nil {
t.Errorf("Buffer should not be nil but got: %#v", ev)
}
if expected := "yanked"; ev.Arg != expected {
t.Errorf("Arg should be %q but got: %q", expected, ev.Arg)
}
p := make([]byte, 20)
_, _ = ev.Buffer.ReadAt(p, 0)
if !strings.HasPrefix(string(p), "lo, worl") {
t.Errorf("buffer string should be %q but got: %q", "", string(p))
}
waitCh <- struct{}{}
<-redrawCh
<-redrawCh
waitCh <- struct{}{}
ev = <-eventCh
if ev.Type != event.Copied {
t.Errorf("event type should be %d but got: %d", event.Copied, ev.Type)
}
if ev.Buffer == nil {
t.Errorf("Buffer should not be nil but got: %#v", ev)
}
if expected := "deleted"; ev.Arg != expected {
t.Errorf("Arg should be %q but got: %q", expected, ev.Arg)
}
p = make([]byte, 20)
_, _ = ev.Buffer.ReadAt(p, 0)
if !strings.HasPrefix(string(p), "lo, wo") {
t.Errorf("buffer string should be %q but got: %q", "", string(p))
}
windowStates, _, windowIndex, _ := wm.State()
ws, ok := windowStates[windowIndex]
if !ok {
t.Errorf("windowStates should contain %d but got: %v", windowIndex, windowStates)
return
}
if ws.Length != int64(7) {
t.Errorf("Length should be %d but got %d", int64(7), ws.Length)
}
if expected := "Helrld!"; !strings.HasPrefix(string(ws.Bytes), expected) {
t.Errorf("Bytes should start with %q but got %q", expected, string(ws.Bytes))
}
waitCh <- struct{}{}
<-redrawCh
waitCh <- struct{}{}
ev = <-eventCh
if ev.Type != event.Pasted {
t.Errorf("event type should be %d but got: %d", event.Pasted, ev.Type)
}
if ev.Count != 18 {
t.Errorf("Count should be %d but got: %d", 18, ev.Count)
}
windowStates, _, _, _ = wm.State()
ws = windowStates[0]
if ws.Length != int64(25) {
t.Errorf("Length should be %d but got %d", int64(25), ws.Length)
}
if expected := "Hefoobarfoobarfoobarlrld!"; !strings.HasPrefix(string(ws.Bytes), expected) {
t.Errorf("Bytes should start with %q but got %q", expected, string(ws.Bytes))
}
}()
wm.Emit(event.Event{Type: event.CursorNext, Mode: mode.Normal, Count: 3})
wm.Emit(event.Event{Type: event.StartVisual})
wm.Emit(event.Event{Type: event.CursorNext, Mode: mode.Visual, Count: 7})
<-waitCh
wm.Emit(event.Event{Type: event.Copy})
<-waitCh
wm.Emit(event.Event{Type: event.StartVisual})
wm.Emit(event.Event{Type: event.CursorNext, Mode: mode.Visual, Count: 5})
<-waitCh
wm.Emit(event.Event{Type: event.Cut})
<-waitCh
wm.Emit(event.Event{Type: event.CursorPrev, Mode: mode.Normal, Count: 2})
<-waitCh
wm.Emit(event.Event{Type: event.Paste, Buffer: buffer.NewBuffer(strings.NewReader("foobar")), Count: 3})
<-waitCh
wm.Close()
}