@@ -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.
9797func 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
158165func 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
178188func 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
725737func 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