github.com/qubitproducts/logspray@v0.2.14/sources/filesystem/filewatcher_test.go (about)

     1  // Copyright 2016 Qubit Digital Ltd.
     2  // Licensed under the Apache License, Version 2.0 (the "License");
     3  // you may not use this file except in compliance with the License.
     4  // You may obtain a copy of the License at
     5  //
     6  // http://www.apache.org/licenses/LICENSE-2.0
     7  //
     8  // Unless required by applicable law or agreed to in writing, software
     9  // distributed under the License is distributed on an "AS IS" BASIS,
    10  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    11  // See the License for the specific language governing permissions and
    12  // limitations under the License.
    13  // Package logspray is a collection of tools for streaming and indexing
    14  // large volumes of dynamic logs.
    15  
    16  package filesystem
    17  
    18  import (
    19  	"context"
    20  	"fmt"
    21  	"io/ioutil"
    22  	"os"
    23  	"path/filepath"
    24  	"regexp"
    25  	"strings"
    26  	"testing"
    27  	"time"
    28  
    29  	"github.com/QubitProducts/logspray/sources"
    30  )
    31  
    32  func TestFileSystemWatcher_Next1(t *testing.T) {
    33  	fsts := []struct {
    34  		fstest
    35  		watcher *Watcher
    36  		expect  []map[string]sources.Update
    37  	}{
    38  		{
    39  			fstest: fstest{
    40  				preactions: []taction{},
    41  			},
    42  			watcher: &Watcher{},
    43  			expect:  []map[string]sources.Update{{}},
    44  		},
    45  		{
    46  			fstest: fstest{
    47  				preactions: []taction{
    48  					createFile("file1"),
    49  				},
    50  			},
    51  			watcher: &Watcher{},
    52  			expect: []map[string]sources.Update{
    53  				{
    54  					"file1": sources.Update{Action: sources.Add},
    55  				},
    56  			},
    57  		},
    58  		{
    59  			fstest: fstest{
    60  				subdirs: []string{"dir1"},
    61  				preactions: []taction{
    62  					createFile("file1"),
    63  					createFile("dir1/ignored"),
    64  				},
    65  			},
    66  			watcher: &Watcher{},
    67  			expect: []map[string]sources.Update{
    68  				{
    69  					"file1": sources.Update{Action: sources.Add},
    70  				},
    71  			},
    72  		},
    73  		{
    74  			fstest: fstest{
    75  				subdirs: []string{"dir1/dir2"},
    76  				preactions: []taction{
    77  					createFile("file1"),
    78  					createFile("dir1/dir2/file1"),
    79  				},
    80  			},
    81  			watcher: &Watcher{
    82  				Recur: true,
    83  			},
    84  			expect: []map[string]sources.Update{
    85  				{
    86  					"file1":           sources.Update{Action: sources.Add},
    87  					"dir1/dir2/file1": sources.Update{Action: sources.Add},
    88  				},
    89  			},
    90  		},
    91  		{
    92  			fstest: fstest{
    93  				actions: []taction{
    94  					createFile("file1"),
    95  				},
    96  			},
    97  			watcher: &Watcher{},
    98  			expect: []map[string]sources.Update{
    99  				{},
   100  				{
   101  					"file1": sources.Update{Action: sources.Add},
   102  				},
   103  			},
   104  		},
   105  		{
   106  			fstest: fstest{
   107  				actions: []taction{
   108  					createFile("file1"),
   109  				},
   110  			},
   111  			watcher: &Watcher{},
   112  			expect: []map[string]sources.Update{
   113  				{},
   114  				{
   115  					"file1": sources.Update{Action: sources.Add},
   116  				},
   117  			},
   118  		},
   119  		{
   120  			fstest: fstest{
   121  				actions: []taction{
   122  					createFile("file1"),
   123  					createFile("file2"),
   124  					createFile("file3"),
   125  					createFile("file4"),
   126  				},
   127  			},
   128  			watcher: &Watcher{
   129  				Recur: true,
   130  			},
   131  			expect: []map[string]sources.Update{
   132  				{},
   133  				{"file1": sources.Update{Action: sources.Add}},
   134  				{"file2": sources.Update{Action: sources.Add}},
   135  				{"file3": sources.Update{Action: sources.Add}},
   136  				{"file4": sources.Update{Action: sources.Add}},
   137  			},
   138  		},
   139  		{
   140  			fstest: fstest{
   141  				actions: []taction{
   142  					createDir("dir1/dir2/dir3"),
   143  					createFile("dir1/dir2/dir3/file1"),
   144  				},
   145  			},
   146  			watcher: &Watcher{
   147  				Recur: true,
   148  			},
   149  			expect: []map[string]sources.Update{
   150  				{},
   151  				{"dir1": sources.Update{Action: sources.Add}},
   152  				{
   153  					"dir1/dir2/dir3/file1": sources.Update{Action: sources.Add},
   154  				},
   155  			},
   156  		},
   157  		{
   158  			fstest: fstest{
   159  				actions: []taction{
   160  					createDir("dir1/dir2/dir3"),
   161  					createFile("dir1/dir2/dir3/file1"),
   162  				},
   163  			},
   164  			watcher: &Watcher{
   165  				Recur:      true,
   166  				NameRegexp: regexp.MustCompile("file1"),
   167  			},
   168  			expect: []map[string]sources.Update{
   169  				{},
   170  				{
   171  					"dir1/dir2/dir3/file1": sources.Update{Action: sources.Add},
   172  				},
   173  			},
   174  		},
   175  	}
   176  
   177  	for i, fst := range fsts {
   178  		t.Run(fmt.Sprintf("%s/%d", t.Name(), i), func(t *testing.T) {
   179  			fst.run(t, func(ctx context.Context, t *testing.T, start func(context.Context, *testing.T)) {
   180  				var err error
   181  				w := fst.watcher
   182  				w.Path, err = os.Getwd()
   183  				if err != nil {
   184  					t.Skip(err)
   185  					return
   186  				}
   187  
   188  				ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
   189  				//defer cancel()
   190  				ius, err := w.Next(ctx)
   191  				if err != nil {
   192  					t.Fatalf("Initial Next() got err = %v", err)
   193  					return
   194  				}
   195  
   196  				if len(ius) != len(fst.expect[0]) {
   197  					t.Fatalf("wrong initial file count expected = %d, got = %d", len(fst.expect[0]), len(ius))
   198  				}
   199  				for _, u := range ius {
   200  					t.Logf("got update %v %s", u.Action, u.Target)
   201  					if u.Action != sources.Add {
   202  						t.Fatalf("Initial events should only be sources.Add")
   203  					}
   204  					if !strings.HasPrefix(u.Target, w.Path) {
   205  						t.Fatalf("File update for file not in path path = %s, got = %s", w.Path, u.Target)
   206  					}
   207  					p := u.Target[len(w.Path)+1:]
   208  					if _, ok := fst.expect[0][p]; !ok {
   209  						t.Fatalf("Got unexpected event path = %s", p)
   210  					}
   211  				}
   212  
   213  				start(ctx, t)
   214  
   215  				for i := range fst.expect[1:] {
   216  					ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
   217  					us, err := w.Next(ctx)
   218  					if err != nil {
   219  						t.Fatalf("intermediate Next() got err = %v", err)
   220  						return
   221  					}
   222  					cancel()
   223  
   224  					for _, u := range us {
   225  						t.Logf("got update %v %s", u.Action, u.Target)
   226  						if !strings.HasPrefix(u.Target, w.Path) {
   227  							t.Fatalf("Intermediate update for file not in path path = %s, got = %s", w.Path, u.Target)
   228  						}
   229  						p := u.Target[len(w.Path)+1:]
   230  						if _, ok := fst.expect[i+1][p]; !ok {
   231  							t.Fatalf("Got intermediate unexpected event path = %s; expecting = %v", p, fst.expect[i+1])
   232  						}
   233  						if u.Action != fst.expect[i+1][p].Action {
   234  							t.Fatalf("Got wrong action want = %v , got = %v", fst.expect[i+1][p].Action, u.Action)
   235  						}
   236  					}
   237  				}
   238  
   239  				// We'll wait a bit and see if we get any extra events
   240  				ctx, cancel = context.WithTimeout(context.Background(), 100*time.Millisecond)
   241  				fus, err := w.Next(ctx)
   242  				defer cancel()
   243  				if err == nil {
   244  					for _, u := range fus {
   245  						t.Logf("unexpected u = %v, f = %s", u.Action, u.Target)
   246  					}
   247  					t.Fatal("Got unexpected additional updates")
   248  					return
   249  				}
   250  			})
   251  		})
   252  	}
   253  }
   254  
   255  type taction func(context.Context, *testing.T)
   256  
   257  type tactions []taction
   258  
   259  func (as tactions) run(ctx context.Context, t *testing.T) {
   260  	for _, a := range as {
   261  		a(ctx, t)
   262  		// we lose notification events if they happen too quickly
   263  		time.Sleep(1 * time.Millisecond)
   264  	}
   265  	return
   266  }
   267  
   268  type fstest struct {
   269  	subdirs    []string
   270  	preactions tactions
   271  	actions    tactions
   272  }
   273  
   274  func (fst fstest) run(t *testing.T, tf func(context.Context, *testing.T, func(context.Context, *testing.T))) {
   275  	dir, err := ioutil.TempDir("", "fstest")
   276  	if err != nil {
   277  		t.Skip(err)
   278  		return
   279  	}
   280  	//defer os.RemoveAll(dir) // clean up
   281  
   282  	for _, d := range fst.subdirs {
   283  		path := filepath.Join(dir, d)
   284  		err = os.MkdirAll(path, 0777)
   285  		if err != nil {
   286  			t.Skip(err)
   287  			return
   288  		}
   289  	}
   290  
   291  	ctx, cancel := context.WithCancel(context.Background())
   292  	defer cancel()
   293  
   294  	if err := os.Chdir(dir); err != nil {
   295  		t.Skip(err)
   296  		return
   297  	}
   298  
   299  	fst.preactions.run(ctx, t)
   300  	tf(ctx, t, fst.actions.run)
   301  }
   302  
   303  func createFile(fn string) taction {
   304  	return func(ctx context.Context, t *testing.T) {
   305  		t.Logf("create file %v", fn)
   306  		if _, err := os.Create(fn); err != nil {
   307  			t.Skip(err)
   308  		}
   309  	}
   310  }
   311  
   312  func removeFile(fn string) taction {
   313  	return func(ctx context.Context, t *testing.T) {
   314  		t.Logf("removing file %v", fn)
   315  		if err := os.Remove(fn); err != nil {
   316  			t.Skip(err)
   317  		}
   318  	}
   319  }
   320  
   321  func createDir(fn string) taction {
   322  	return func(ctx context.Context, t *testing.T) {
   323  		t.Logf("creating dir %v", fn)
   324  		if err := os.MkdirAll(fn, 0777); err != nil {
   325  			t.Skip(err)
   326  		}
   327  	}
   328  }
   329  
   330  func removeDir(fn string) taction {
   331  	return func(ctx context.Context, t *testing.T) {
   332  		t.Logf("removing dir %v", fn)
   333  		if err := os.RemoveAll(fn); err != nil {
   334  			t.Skip(err)
   335  		}
   336  	}
   337  }
   338  
   339  func pause(d time.Duration) taction {
   340  	return func(ctx context.Context, t *testing.T) {
   341  		t.Logf("pausing for %v", d)
   342  		select {
   343  		case <-ctx.Done():
   344  			t.Skip(ctx.Err())
   345  		case <-time.After(d):
   346  		}
   347  	}
   348  }
   349  
   350  func noop() taction {
   351  	return func(ctx context.Context, t *testing.T) {
   352  		t.Logf("noop")
   353  		return
   354  	}
   355  }