Skip to content

Commit 45f8cd2

Browse files
committed
#5 debugging failed test on appveyor.com (Windows)
1 parent c329d11 commit 45f8cd2

File tree

2 files changed

+81
-19
lines changed

2 files changed

+81
-19
lines changed

tailer/fswatcher/fswatcher.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,9 @@ func (t *fileTailer) Errors() chan Error {
8686
return t.errors
8787
}
8888

89+
// Close() triggers the shutdown of the file tailer.
90+
// The file tailer will eventually terminate,
91+
// but after Close() returns it might still be running in the background for a few milliseconds.
8992
func (t *fileTailer) Close() {
9093
// Closing the done channel will stop the consumer loop.
9194
// Deferred functions within the consumer loop will close the producer loop.

tailer/fswatcher_test.go

Lines changed: 78 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -95,26 +95,30 @@ func TestAll(t *testing.T) {
9595
// * directories with the xattr com.apple.FinderInfo (like everything in /tmp) are hidden
9696
// In order to test this, we must create a log file somewhere outside of /tmp, so we use $HOME.
9797
func TestVisibleInOSXFinder(t *testing.T) {
98+
ctx := setUp(t, "visible in macOS finder", closeFileAfterEachLine, fseventTailer, _nocreate, mv)
99+
100+
// replace ctx.basedir with a directory in $HOME
98101
currentUser, err := user.Current()
99102
if err != nil {
100-
t.Fatalf("failed to get current user: %v", err)
103+
fatalf(t, ctx, "failed to get current user: %v", err)
101104
}
102105
testDir, err := ioutil.TempDir(currentUser.HomeDir, "grok_exporter_test_dir_")
103106
if err != nil {
104-
t.Fatalf("failed to create test directory: %v", err.Error())
107+
fatalf(t, ctx, "failed to create test directory: %v", err.Error())
105108
}
106109
defer func() {
107110
err := os.RemoveAll(testDir)
108111
if err != nil {
109-
t.Fatalf("%v: Failed to remove test directory after running the tests: %v", testDir, err.Error())
112+
fatalf(t, ctx, "%v: Failed to remove test directory after running the tests: %v", testDir, err.Error())
110113
}
111114
}()
112-
ctx := setUp(t, "visible in macOS finder", closeFileAfterEachLine, fseventTailer, _nocreate, mv)
113115
err = os.RemoveAll(ctx.basedir)
114116
if err != nil {
115-
t.Fatalf("%v: failed to remove temp dir: %v", ctx.basedir, err)
117+
fatalf(t, ctx, "%v: failed to remove temp dir: %v", ctx.basedir, err)
116118
}
117119
ctx.basedir = testDir
120+
121+
// run simple test in the new directory
118122
test := [][]string{
119123
{"log", "line 1", "test.log"},
120124
{"start file tailer", "test.log"},
@@ -151,31 +155,39 @@ func runTest(t *testing.T, ctx *context, cmds [][]string) {
151155
}
152156
closeTailer(t, ctx)
153157
assertGoroutinesTerminated(t, ctx, nGoroutinesBefore)
158+
for _, writer := range ctx.logFileWriters {
159+
writer.close(t, ctx)
160+
}
154161
fmt.Println()
155162
})
156163
}
157164

158165
func closeTailer(t *testing.T, ctx *context) {
166+
// Note: This function checks if the Lines() channel gets closed.
167+
// While it's good to check this, it doesn't guarantee that the tailer is
168+
// fully shut down. There might be an fseventProducerLoop running in the
169+
// background, or a hanging system call keeping the log directory open.
170+
// There are tests for that like counting the number of goroutines
171+
// in assertGoroutinesTerminated() or making sure the log directory
172+
// can be removed in tearDown().
159173
timeout := 5 * time.Second
160174
if ctx.tailer != nil {
161175
ctx.tailer.Close()
162-
// wait until closed
176+
// check if the lines channel gets closed
163177
select {
164178
case line, open := <-ctx.tailer.Lines():
165179
if open {
166-
msg := fmt.Sprintf("read unexpected line line from file %q: %q", line.File, line.Line)
167-
ctx.log.Error(msg)
168-
t.Fatal(msg)
180+
fatalf(t, ctx, "read unexpected line line from file %q: %q", line.File, line.Line)
169181
}
170182
case <-time.After(timeout):
171-
msg := fmt.Sprintf("failed to shut down the tailer. timeout after %v seconds", timeout)
172-
ctx.log.Error(msg)
173-
fatalf(t, ctx, msg)
183+
fatalf(t, ctx, "failed to shut down the tailer. timeout after %v seconds", timeout)
174184
}
175185
}
176186
}
177187

178188
func assertGoroutinesTerminated(t *testing.T, ctx *context, nGoroutinesBefore int) {
189+
// Timeout of 2 seconds, because after FileTailer.Close() returns the tailer is still
190+
// shutting down in the background.
179191
timeout := 2 * time.Second
180192
for nGoroutinesBefore < runtime.NumGoroutine() && timeout > 0 {
181193
timeout = timeout - 50*time.Millisecond
@@ -468,7 +480,7 @@ func startFileTailer(t *testing.T, ctx *context, params []string) {
468480
for _, g := range globs {
469481
parsedGlob, err := glob.Parse(filepath.Join(ctx.basedir, g))
470482
if err != nil {
471-
t.Fatal(err)
483+
fatalf(t, ctx, "%v", err)
472484
}
473485
parsedGlobs = append(parsedGlobs, parsedGlob)
474486
}
@@ -723,9 +735,56 @@ func (l *keepOpenLogFileWriter) close(t *testing.T, ctx *context) {
723735
}
724736

725737
func tearDown(t *testing.T, ctx *context) {
726-
err := os.RemoveAll(ctx.basedir)
738+
deleteRecursively(t, ctx, ctx.basedir)
739+
}
740+
741+
// Verbose implementation of os.RemoveAll() to debug a Windows "Access is denied" issue.
742+
func deleteRecursively(t *testing.T, ctx *context, file string) {
743+
fileInfo, err := os.Stat(file)
727744
if err != nil {
728-
fatalf(t, ctx, "%v: failed to remove the test directory after running the tests: %v", ctx.basedir, err)
745+
fatalf(t, ctx, "tearDown: stat(%q) failed: %v", file, err)
746+
}
747+
if fileInfo.IsDir() {
748+
for _, childInfo := range ls(t, ctx, file) {
749+
deleteRecursively(t, ctx, path.Join(file, childInfo.Name()))
750+
}
751+
}
752+
ctx.log.Debugf("tearDown: removing %q", file)
753+
delete(t, ctx, file)
754+
}
755+
756+
// Verbose implementation of os.Remove() to debug a Windows "Access is denied" issue.
757+
func delete(t *testing.T, ctx *context, file string) {
758+
var (
759+
err, statErr error
760+
timeout = 5 * time.Second
761+
timePassed = 0 * time.Second
762+
)
763+
// Repeat a few times to ensure the Windows issue is not caused by a slow file tailer shutdown.
764+
// It's unlikely though, as assertGoroutinesTerminated() should make sure that the tailer is really terminated.
765+
for timePassed < timeout {
766+
err = os.Remove(file) // removes files and empty directories
767+
if err == nil {
768+
// Check if the file or directory is really removed. It seems that on Windows, os.Remove() sometimes
769+
// returns no error while the file or directory is still there.
770+
_, statErr = os.Stat(file)
771+
if statErr != nil {
772+
if os.IsNotExist(statErr) {
773+
// os.Remove(file) was successful, the file or directory is gone.
774+
return
775+
} else {
776+
fatalf(t, ctx, "tearDown: %q: stat failed: %v", file, statErr)
777+
}
778+
}
779+
}
780+
// os.Stat() successful. The file or directory is still there. Try again.
781+
time.Sleep(200 * time.Millisecond)
782+
timePassed += 200 * time.Millisecond
783+
}
784+
if err != nil {
785+
fatalf(t, ctx, "tearDown: %q: failed to remove file or directory: %v", file, err)
786+
} else {
787+
fatalf(t, ctx, "tearDown: %q: failed to remove file or directory", file)
729788
}
730789
}
731790

@@ -803,18 +862,18 @@ func runTestShutdown(t *testing.T, mode string) {
803862
select {
804863
case _, open := <-tailer.Errors():
805864
if open {
806-
t.Fatalf("error channel not closed")
865+
fatalf(t, ctx, "error channel not closed")
807866
}
808867
case <-time.After(5 * time.Second):
809-
t.Fatalf("timeout while waiting for errors channel to be closed.")
868+
fatalf(t, ctx, "timeout while waiting for errors channel to be closed.")
810869
}
811870
select {
812871
case _, open := <-tailer.Lines():
813872
if open {
814-
t.Fatalf("lines channel not closed")
873+
fatalf(t, ctx, "lines channel not closed")
815874
}
816875
case <-time.After(5 * time.Second):
817-
t.Fatalf("timeout while waiting for errors channel to be closed.")
876+
fatalf(t, ctx, "timeout while waiting for errors channel to be closed.")
818877
}
819878
assertGoroutinesTerminated(t, ctx, nGoroutinesBefore)
820879
}

0 commit comments

Comments
 (0)