From edbdf2785edd1f05c9dc9e5dcb0f654b7c3b2bc7 Mon Sep 17 00:00:00 2001 From: itchyny Date: Fri, 18 Oct 2024 05:42:49 +0900 Subject: [PATCH] fix goroutine leaks and close channels in window tests --- window/manager_test.go | 165 +++++++++++++++++++++++++---------------- window/window_test.go | 75 +++++++++---------- 2 files changed, 135 insertions(+), 105 deletions(-) diff --git a/window/manager_test.go b/window/manager_test.go index 4d28d64..6714864 100644 --- a/window/manager_test.go +++ b/window/manager_test.go @@ -30,10 +30,21 @@ func createTemp(dir, contents string) (*os.File, error) { func TestManagerOpenEmpty(t *testing.T) { wm := NewManager() - eventCh, redrawCh := make(chan event.Event), make(chan struct{}) + eventCh, redrawCh, waitCh := make(chan event.Event), make(chan struct{}), make(chan struct{}) wm.Init(eventCh, redrawCh) go func() { - <-redrawCh + 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 { @@ -62,21 +73,14 @@ func TestManagerOpenEmpty(t *testing.T) { if err != nil { t.Errorf("err should be nil but got: %v", err) } - go wm.Emit(event.Event{Type: event.Write}) - 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.Emit(event.Event{Type: event.Write}) + <-waitCh wm.Close() } func TestManagerOpenStates(t *testing.T) { wm := NewManager() - eventCh, redrawCh := make(chan event.Event), make(chan struct{}) - wm.Init(eventCh, redrawCh) + wm.Init(nil, nil) wm.SetSize(110, 20) str := "Hello, world! こんにちは、世界!" f, err := createTemp(t.TempDir(), str) @@ -114,14 +118,19 @@ func TestManagerOpenStates(t *testing.T) { func TestManagerOpenNonExistsWrite(t *testing.T) { wm := NewManager() - eventCh, redrawCh := make(chan event.Event), make(chan struct{}) + eventCh, redrawCh, waitCh := make(chan event.Event), make(chan struct{}), make(chan struct{}) wm.Init(eventCh, redrawCh) go func() { - for { - select { - case <-eventCh: - case <-redrawCh: - } + 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) @@ -168,13 +177,13 @@ func TestManagerOpenNonExistsWrite(t *testing.T) { 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() - eventCh, redrawCh := make(chan event.Event), make(chan struct{}) - wm.Init(eventCh, redrawCh) + wm.Init(nil, nil) wm.SetSize(110, 20) cmd, name := "`which ls`", "ls" if runtime.GOOS == "windows" { @@ -208,8 +217,7 @@ func TestEditorOpenExpandHomedir(t *testing.T) { t.Skip("skip on Windows") } wm := NewManager() - eventCh, redrawCh := make(chan event.Event), make(chan struct{}) - wm.Init(eventCh, redrawCh) + wm.Init(nil, nil) wm.SetSize(110, 20) str := "Hello, world!" f, err := createTemp(t.TempDir(), str) @@ -246,21 +254,48 @@ func TestManagerOpenChdirWrite(t *testing.T) { t.Skip("skip on Windows") } wm := NewManager() - eventCh, redrawCh := make(chan event.Event), make(chan struct{}) + eventCh, redrawCh, waitCh := make(chan event.Event), make(chan struct{}), make(chan struct{}) wm.Init(eventCh, redrawCh) - go func() { - for { - select { - case <-eventCh: - case <-redrawCh: - } - } - }() - wm.SetSize(110, 20) 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) @@ -284,21 +319,13 @@ func TestManagerOpenChdirWrite(t *testing.T) { 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() - eventCh, redrawCh := make(chan event.Event), make(chan struct{}) - wm.Init(eventCh, redrawCh) - go func() { - for { - select { - case <-eventCh: - case <-redrawCh: - } - } - }() + wm.Init(nil, nil) wm.SetSize(110, 20) dir := t.TempDir() if err := wm.Open(dir); err != nil { @@ -313,8 +340,7 @@ func TestManagerOpenDirectory(t *testing.T) { func TestManagerRead(t *testing.T) { wm := NewManager() - eventCh, redrawCh := make(chan event.Event), make(chan struct{}) - wm.Init(eventCh, redrawCh) + wm.Init(nil, nil) wm.SetSize(110, 20) r := strings.NewReader("Hello, world!") if err := wm.Read(r); err != nil { @@ -345,14 +371,16 @@ func TestManagerRead(t *testing.T) { func TestManagerOnly(t *testing.T) { wm := NewManager() - eventCh, redrawCh := make(chan event.Event), make(chan struct{}) + eventCh, redrawCh, waitCh := make(chan event.Event), make(chan struct{}), make(chan struct{}) wm.Init(eventCh, redrawCh) go func() { - for { - select { - case <-eventCh: - case <-redrawCh: - } + defer func() { + close(eventCh) + close(redrawCh) + close(waitCh) + }() + for range 4 { + <-eventCh } }() wm.SetSize(110, 20) @@ -379,19 +407,22 @@ func TestManagerOnly(t *testing.T) { t.Errorf("layout should be %#v but got %#v", expected, got) } + <-waitCh wm.Close() } func TestManagerAlternative(t *testing.T) { wm := NewManager() - eventCh, redrawCh := make(chan event.Event), make(chan struct{}) + eventCh, redrawCh, waitCh := make(chan event.Event), make(chan struct{}), make(chan struct{}) wm.Init(eventCh, redrawCh) go func() { - for { - select { - case <-eventCh: - case <-redrawCh: - } + defer func() { + close(eventCh) + close(redrawCh) + close(waitCh) + }() + for range 9 { + <-eventCh } }() wm.SetSize(110, 20) @@ -475,19 +506,22 @@ func TestManagerAlternative(t *testing.T) { t.Errorf("windowIndex should be %d but got %d", expected, windowIndex) } + <-waitCh wm.Close() } func TestManagerWincmd(t *testing.T) { wm := NewManager() - eventCh, redrawCh := make(chan event.Event), make(chan struct{}) + eventCh, redrawCh, waitCh := make(chan event.Event), make(chan struct{}), make(chan struct{}) wm.Init(eventCh, redrawCh) go func() { - for { - select { - case <-eventCh: - case <-redrawCh: - } + defer func() { + close(eventCh) + close(redrawCh) + close(waitCh) + }() + for range 17 { + <-eventCh } }() wm.SetSize(110, 20) @@ -538,6 +572,7 @@ func TestManagerWincmd(t *testing.T) { t.Errorf("layout should be %#v but got %#v", expected, got) } + <-waitCh wm.Close() } @@ -556,6 +591,11 @@ func TestManagerCopyCutPaste(t *testing.T) { } _, _, _, _ = wm.State() go func() { + defer func() { + close(eventCh) + close(redrawCh) + close(waitCh) + }() <-redrawCh <-redrawCh <-redrawCh @@ -620,7 +660,6 @@ func TestManagerCopyCutPaste(t *testing.T) { if expected := "Hefoobarfoobarfoobarlrld!"; !strings.HasPrefix(string(ws.Bytes), expected) { t.Errorf("Bytes should start with %q but got %q", expected, string(ws.Bytes)) } - close(waitCh) }() wm.Emit(event.Event{Type: event.CursorNext, Mode: mode.Normal, Count: 3}) wm.Emit(event.Event{Type: event.StartVisual}) diff --git a/window/window_test.go b/window/window_test.go index 1f69ccf..d1569a2 100644 --- a/window/window_test.go +++ b/window/window_test.go @@ -14,7 +14,7 @@ import ( func TestWindowState(t *testing.T) { r := strings.NewReader("Hello, world!") width, height := 16, 10 - window, err := newWindow(r, "test", "test", make(chan event.Event), make(chan struct{})) + window, err := newWindow(r, "test", "test", nil, nil) if err != nil { t.Fatal(err) } @@ -70,7 +70,7 @@ func TestWindowState(t *testing.T) { func TestWindowEmptyState(t *testing.T) { r := strings.NewReader("") width, height := 16, 10 - window, err := newWindow(r, "test", "test", make(chan event.Event), make(chan struct{})) + window, err := newWindow(r, "test", "test", nil, nil) if err != nil { t.Fatal(err) } @@ -136,7 +136,7 @@ func TestWindowEmptyState(t *testing.T) { func TestWindowCursorMotions(t *testing.T) { r := strings.NewReader(strings.Repeat("Hello, world!", 100)) width, height := 16, 10 - window, err := newWindow(r, "test", "test", make(chan event.Event), make(chan struct{})) + window, err := newWindow(r, "test", "test", nil, nil) if err != nil { t.Fatal(err) } @@ -437,7 +437,7 @@ func TestWindowCursorMotions(t *testing.T) { func TestWindowScreenMotions(t *testing.T) { r := strings.NewReader(strings.Repeat("Hello, world!", 100)) width, height := 16, 10 - window, err := newWindow(r, "test", "test", make(chan event.Event), make(chan struct{})) + window, err := newWindow(r, "test", "test", nil, nil) if err != nil { t.Fatal(err) } @@ -619,13 +619,7 @@ func TestWindowScreenMotions(t *testing.T) { func TestWindowDeleteBytes(t *testing.T) { r := strings.NewReader("Hello, world!") width, height := 16, 10 - eventCh := make(chan event.Event) - go func() { - for { - <-eventCh - } - }() - window, _ := newWindow(r, "test", "test", eventCh, make(chan struct{})) + window, _ := newWindow(r, "test", "test", nil, nil) window.setSize(width, height) window.cursorNext(mode.Normal, 7) @@ -687,13 +681,7 @@ func TestWindowDeleteBytes(t *testing.T) { func TestWindowDeletePrevBytes(t *testing.T) { r := strings.NewReader("Hello, world!") width, height := 16, 10 - eventCh := make(chan event.Event) - go func() { - for { - <-eventCh - } - }() - window, _ := newWindow(r, "test", "test", eventCh, make(chan struct{})) + window, _ := newWindow(r, "test", "test", nil, nil) window.setSize(width, height) window.cursorNext(mode.Normal, 5) @@ -728,7 +716,7 @@ func TestWindowDeletePrevBytes(t *testing.T) { func TestWindowIncrementDecrement(t *testing.T) { r := strings.NewReader("Hello, world!") width, height := 16, 10 - window, _ := newWindow(r, "test", "test", make(chan event.Event), make(chan struct{})) + window, _ := newWindow(r, "test", "test", nil, nil) window.setSize(width, height) window.increment(0) @@ -778,7 +766,7 @@ func TestWindowIncrementDecrement(t *testing.T) { func TestWindowIncrementDecrementEmpty(t *testing.T) { r := strings.NewReader("") width, height := 16, 10 - window, _ := newWindow(r, "test", "test", make(chan event.Event), make(chan struct{})) + window, _ := newWindow(r, "test", "test", nil, nil) window.setSize(width, height) s, _ := window.state(width, height) @@ -801,7 +789,7 @@ func TestWindowIncrementDecrementEmpty(t *testing.T) { t.Errorf("s.Length should be %d but got %d", expected, s.Length) } - window, _ = newWindow(r, "test", "test", make(chan event.Event), make(chan struct{})) + window, _ = newWindow(r, "test", "test", nil, nil) window.setSize(width, height) window.decrement(0) @@ -820,7 +808,7 @@ func TestWindowIncrementDecrementEmpty(t *testing.T) { func TestWindowInsertByte(t *testing.T) { r := strings.NewReader("Hello, world!") width, height := 16, 1 - window, _ := newWindow(r, "test", "test", make(chan event.Event), make(chan struct{})) + window, _ := newWindow(r, "test", "test", nil, nil) window.setSize(width, height) window.cursorNext(mode.Normal, 7) @@ -879,7 +867,7 @@ func TestWindowInsertByte(t *testing.T) { func TestWindowInsertEmpty(t *testing.T) { r := strings.NewReader("") width, height := 16, 10 - window, _ := newWindow(r, "test", "test", make(chan event.Event), make(chan struct{})) + window, _ := newWindow(r, "test", "test", nil, nil) window.setSize(width, height) window.startInsert() @@ -915,7 +903,7 @@ func TestWindowInsertEmpty(t *testing.T) { func TestWindowInsertHead(t *testing.T) { r := strings.NewReader(strings.Repeat("Hello, world!", 2)) width, height := 16, 10 - window, _ := newWindow(r, "test", "test", make(chan event.Event), make(chan struct{})) + window, _ := newWindow(r, "test", "test", nil, nil) window.setSize(width, height) window.pageEnd() @@ -948,7 +936,7 @@ func TestWindowInsertHead(t *testing.T) { func TestWindowInsertHeadEmpty(t *testing.T) { r := strings.NewReader("") width, height := 16, 10 - window, _ := newWindow(r, "test", "test", make(chan event.Event), make(chan struct{})) + window, _ := newWindow(r, "test", "test", nil, nil) window.setSize(width, height) window.startInsertHead() @@ -984,7 +972,7 @@ func TestWindowInsertHeadEmpty(t *testing.T) { func TestWindowAppend(t *testing.T) { r := strings.NewReader("Hello, world!") width, height := 16, 10 - window, _ := newWindow(r, "test", "test", make(chan event.Event), make(chan struct{})) + window, _ := newWindow(r, "test", "test", nil, nil) window.setSize(width, height) window.cursorNext(mode.Normal, 7) @@ -1028,7 +1016,7 @@ func TestWindowAppend(t *testing.T) { func TestWindowAppendEmpty(t *testing.T) { r := strings.NewReader("") width, height := 16, 10 - window, _ := newWindow(r, "test", "test", make(chan event.Event), make(chan struct{})) + window, _ := newWindow(r, "test", "test", nil, nil) window.setSize(width, height) window.startAppend() @@ -1075,7 +1063,7 @@ func TestWindowAppendEmpty(t *testing.T) { func TestWindowReplaceByte(t *testing.T) { r := strings.NewReader("Hello, world!") width, height := 16, 10 - window, _ := newWindow(r, "test", "test", make(chan event.Event), make(chan struct{})) + window, _ := newWindow(r, "test", "test", nil, nil) window.setSize(width, height) window.cursorNext(mode.Normal, 7) @@ -1102,7 +1090,7 @@ func TestWindowReplaceByte(t *testing.T) { func TestWindowReplaceByteEmpty(t *testing.T) { r := strings.NewReader("") width, height := 16, 10 - window, _ := newWindow(r, "test", "test", make(chan event.Event), make(chan struct{})) + window, _ := newWindow(r, "test", "test", nil, nil) window.setSize(width, height) window.startReplaceByte() @@ -1128,7 +1116,7 @@ func TestWindowReplaceByteEmpty(t *testing.T) { func TestWindowReplace(t *testing.T) { r := strings.NewReader("Hello, world!") width, height := 16, 10 - window, _ := newWindow(r, "test", "test", make(chan event.Event), make(chan struct{})) + window, _ := newWindow(r, "test", "test", nil, nil) window.setSize(width, height) window.cursorNext(mode.Normal, 10) @@ -1175,7 +1163,7 @@ func TestWindowReplace(t *testing.T) { func TestWindowReplaceEmpty(t *testing.T) { r := strings.NewReader("") width, height := 16, 10 - window, _ := newWindow(r, "test", "test", make(chan event.Event), make(chan struct{})) + window, _ := newWindow(r, "test", "test", nil, nil) window.setSize(width, height) window.startReplace() @@ -1204,7 +1192,7 @@ func TestWindowReplaceEmpty(t *testing.T) { func TestWindowInsertByte2(t *testing.T) { r := strings.NewReader("") width, height := 16, 10 - window, _ := newWindow(r, "test", "test", make(chan event.Event), make(chan struct{})) + window, _ := newWindow(r, "test", "test", nil, nil) window.setSize(width, height) window.startInsert() @@ -1234,7 +1222,7 @@ func TestWindowInsertByte2(t *testing.T) { func TestWindowBackspace(t *testing.T) { r := strings.NewReader("Hello, world!") width, height := 16, 10 - window, _ := newWindow(r, "test", "test", make(chan event.Event), make(chan struct{})) + window, _ := newWindow(r, "test", "test", nil, nil) window.setSize(width, height) window.cursorNext(mode.Normal, 5) @@ -1258,7 +1246,7 @@ func TestWindowBackspace(t *testing.T) { func TestWindowBackspacePending(t *testing.T) { r := strings.NewReader("Hello, world!") width, height := 16, 10 - window, _ := newWindow(r, "test", "test", make(chan event.Event), make(chan struct{})) + window, _ := newWindow(r, "test", "test", nil, nil) window.setSize(width, height) window.cursorNext(mode.Normal, 5) @@ -1288,11 +1276,12 @@ func TestWindowBackspacePending(t *testing.T) { func TestWindowEventRune(t *testing.T) { width, height := 16, 10 redrawCh := make(chan struct{}) - window, _ := newWindow(strings.NewReader(""), "test", "test", make(chan event.Event), redrawCh) + window, _ := newWindow(strings.NewReader(""), "test", "test", nil, redrawCh) window.setSize(width, height) str := "48723fffab" go func() { + defer close(redrawCh) window.emit(event.Event{Type: event.StartInsert}) for _, r := range str { window.emit(event.Event{Type: event.Rune, Rune: r, Mode: mode.Insert}) @@ -1306,16 +1295,18 @@ func TestWindowEventRune(t *testing.T) { if expected := "\x48\x72\x3f\xff\xab\x00"; !strings.HasPrefix(string(s.Bytes), expected) { t.Errorf("s.Bytes should start with %q but got %q", expected, string(s.Bytes)) } + <-redrawCh } func TestWindowEventRuneText(t *testing.T) { width, height := 16, 10 redrawCh := make(chan struct{}) - window, _ := newWindow(strings.NewReader(""), "test", "test", make(chan event.Event), redrawCh) + window, _ := newWindow(strings.NewReader(""), "test", "test", nil, redrawCh) window.setSize(width, height) str := "Hello, World!\nこんにちは、世界!\n鰰は魚の一種" go func() { + defer close(redrawCh) window.emit(event.Event{Type: event.SwitchFocus}) window.emit(event.Event{Type: event.StartInsert}) for _, r := range str { @@ -1331,18 +1322,15 @@ func TestWindowEventRuneText(t *testing.T) { if expected := str + "\x00"; !strings.HasPrefix(string(s.Bytes), expected) { t.Errorf("s.Bytes should start with %q but got %q", expected, string(s.Bytes)) } + <-redrawCh } func TestWindowEventUndoRedo(t *testing.T) { width, height := 16, 10 redrawCh := make(chan struct{}) - window, _ := newWindow(strings.NewReader("Hello, world!"), "test", "test", make(chan event.Event), redrawCh) + window, _ := newWindow(strings.NewReader("Hello, world!"), "test", "test", nil, redrawCh) window.setSize(width, height) waitCh := make(chan struct{}) - defer func() { - close(waitCh) - close(redrawCh) - }() waitRedraw := func(count int) { for range count { @@ -1350,6 +1338,8 @@ func TestWindowEventUndoRedo(t *testing.T) { } } go func() { + defer close(redrawCh) + defer close(waitCh) window.emit(event.Event{Type: event.Undo}) window.emit(event.Event{Type: event.SwitchFocus}) window.emit(event.Event{Type: event.StartAppend, Mode: mode.Insert}) @@ -1439,11 +1429,12 @@ func TestWindowEventUndoRedo(t *testing.T) { if expected := int64(4); s.Cursor != expected { t.Errorf("s.Cursor should be %d but got %d", expected, s.Cursor) } + <-redrawCh } func TestWindowWriteTo(t *testing.T) { r := strings.NewReader("Hello, world!") - window, err := newWindow(r, "test", "test", make(chan event.Event), make(chan struct{})) + window, err := newWindow(r, "test", "test", nil, nil) if err != nil { t.Fatal(err) }