hugo

Fork of github.com/gohugoio/hugo with reverse pagination support

git clone git://git.shimmy1996.com/hugo.git

poller_test.go (8206B)

    1 // Package filenotify is adapted from https://github.com/moby/moby/tree/master/pkg/filenotify, Apache-2.0 License.
    2 // Hopefully this can be replaced with an external package sometime in the future, see https://github.com/fsnotify/fsnotify/issues/9
    3 package filenotify
    4 
    5 import (
    6 	"fmt"
    7 	"io/ioutil"
    8 	"os"
    9 	"path/filepath"
   10 	"runtime"
   11 	"testing"
   12 	"time"
   13 
   14 	qt "github.com/frankban/quicktest"
   15 	"github.com/fsnotify/fsnotify"
   16 	"github.com/gohugoio/hugo/htesting"
   17 )
   18 
   19 const (
   20 	subdir1       = "subdir1"
   21 	subdir2       = "subdir2"
   22 	watchWaitTime = 200 * time.Millisecond
   23 )
   24 
   25 var (
   26 	isMacOs   = runtime.GOOS == "darwin"
   27 	isWindows = runtime.GOOS == "windows"
   28 	isCI      = htesting.IsCI()
   29 )
   30 
   31 func TestPollerAddRemove(t *testing.T) {
   32 	c := qt.New(t)
   33 	w := NewPollingWatcher(watchWaitTime)
   34 
   35 	c.Assert(w.Add("foo"), qt.Not(qt.IsNil))
   36 	c.Assert(w.Remove("foo"), qt.Not(qt.IsNil))
   37 
   38 	f, err := ioutil.TempFile("", "asdf")
   39 	if err != nil {
   40 		t.Fatal(err)
   41 	}
   42 	defer os.RemoveAll(f.Name())
   43 	c.Assert(w.Add(f.Name()), qt.IsNil)
   44 	c.Assert(w.Remove(f.Name()), qt.IsNil)
   45 
   46 }
   47 
   48 func TestPollerEvent(t *testing.T) {
   49 	c := qt.New(t)
   50 
   51 	for _, poll := range []bool{true, false} {
   52 		if !(poll || isMacOs) || isCI {
   53 			// Only run the fsnotify tests on MacOS locally.
   54 			continue
   55 		}
   56 		method := "fsnotify"
   57 		if poll {
   58 			method = "poll"
   59 		}
   60 
   61 		c.Run(fmt.Sprintf("%s, Watch dir", method), func(c *qt.C) {
   62 			dir, w := preparePollTest(c, poll)
   63 			subdir := filepath.Join(dir, subdir1)
   64 			c.Assert(w.Add(subdir), qt.IsNil)
   65 
   66 			filename := filepath.Join(subdir, "file1")
   67 
   68 			// Write to one file.
   69 			c.Assert(ioutil.WriteFile(filename, []byte("changed"), 0600), qt.IsNil)
   70 
   71 			var expected []fsnotify.Event
   72 
   73 			if poll {
   74 				expected = append(expected, fsnotify.Event{Name: filename, Op: fsnotify.Write})
   75 				assertEvents(c, w, expected...)
   76 			} else {
   77 				// fsnotify sometimes emits Chmod before Write,
   78 				// which is hard to test, so skip it here.
   79 				drainEvents(c, w)
   80 			}
   81 
   82 			// Remove one file.
   83 			filename = filepath.Join(subdir, "file2")
   84 			c.Assert(os.Remove(filename), qt.IsNil)
   85 			assertEvents(c, w, fsnotify.Event{Name: filename, Op: fsnotify.Remove})
   86 
   87 			// Add one file.
   88 			filename = filepath.Join(subdir, "file3")
   89 			c.Assert(ioutil.WriteFile(filename, []byte("new"), 0600), qt.IsNil)
   90 			assertEvents(c, w, fsnotify.Event{Name: filename, Op: fsnotify.Create})
   91 
   92 			// Remove entire directory.
   93 			subdir = filepath.Join(dir, subdir2)
   94 			c.Assert(w.Add(subdir), qt.IsNil)
   95 
   96 			c.Assert(os.RemoveAll(subdir), qt.IsNil)
   97 
   98 			expected = expected[:0]
   99 
  100 			// This looks like a bug in fsnotify on MacOS. There are
  101 			// 3 files in this directory, yet we get Remove events
  102 			// for one of them + the directory.
  103 			if !poll {
  104 				expected = append(expected, fsnotify.Event{Name: filepath.Join(subdir, "file2"), Op: fsnotify.Remove})
  105 			}
  106 			expected = append(expected, fsnotify.Event{Name: subdir, Op: fsnotify.Remove})
  107 			assertEvents(c, w, expected...)
  108 
  109 		})
  110 
  111 		c.Run(fmt.Sprintf("%s, Add should not trigger event", method), func(c *qt.C) {
  112 			dir, w := preparePollTest(c, poll)
  113 			subdir := filepath.Join(dir, subdir1)
  114 			w.Add(subdir)
  115 			assertEvents(c, w)
  116 			// Create a new sub directory and add it to the watcher.
  117 			subdir = filepath.Join(dir, subdir1, subdir2)
  118 			c.Assert(os.Mkdir(subdir, 0777), qt.IsNil)
  119 			w.Add(subdir)
  120 			// This should create only one event.
  121 			assertEvents(c, w, fsnotify.Event{Name: subdir, Op: fsnotify.Create})
  122 		})
  123 
  124 	}
  125 }
  126 
  127 func TestPollerClose(t *testing.T) {
  128 	c := qt.New(t)
  129 	w := NewPollingWatcher(watchWaitTime)
  130 	f1, err := ioutil.TempFile("", "f1")
  131 	c.Assert(err, qt.IsNil)
  132 	f2, err := ioutil.TempFile("", "f2")
  133 	c.Assert(err, qt.IsNil)
  134 	filename1 := f1.Name()
  135 	filename2 := f2.Name()
  136 	f1.Close()
  137 	f2.Close()
  138 
  139 	c.Assert(w.Add(filename1), qt.IsNil)
  140 	c.Assert(w.Add(filename2), qt.IsNil)
  141 	c.Assert(w.Close(), qt.IsNil)
  142 	c.Assert(w.Close(), qt.IsNil)
  143 	c.Assert(ioutil.WriteFile(filename1, []byte("new"), 0600), qt.IsNil)
  144 	c.Assert(ioutil.WriteFile(filename2, []byte("new"), 0600), qt.IsNil)
  145 	// No more event as the watchers are closed.
  146 	assertEvents(c, w)
  147 
  148 	f2, err = ioutil.TempFile("", "f2")
  149 	c.Assert(err, qt.IsNil)
  150 
  151 	defer os.Remove(f2.Name())
  152 
  153 	c.Assert(w.Add(f2.Name()), qt.Not(qt.IsNil))
  154 
  155 }
  156 
  157 func TestCheckChange(t *testing.T) {
  158 	c := qt.New(t)
  159 
  160 	dir := prepareTestDirWithSomeFiles(c, "check-change")
  161 
  162 	stat := func(s ...string) os.FileInfo {
  163 		fi, err := os.Stat(filepath.Join(append([]string{dir}, s...)...))
  164 		c.Assert(err, qt.IsNil)
  165 		return fi
  166 	}
  167 
  168 	f0, f1, f2 := stat(subdir2, "file0"), stat(subdir2, "file1"), stat(subdir2, "file2")
  169 	d1 := stat(subdir1)
  170 
  171 	// Note that on Windows, only the 0200 bit (owner writable) of mode is used.
  172 	c.Assert(os.Chmod(filepath.Join(filepath.Join(dir, subdir2, "file1")), 0400), qt.IsNil)
  173 	f1_2 := stat(subdir2, "file1")
  174 
  175 	c.Assert(ioutil.WriteFile(filepath.Join(filepath.Join(dir, subdir2, "file2")), []byte("changed"), 0600), qt.IsNil)
  176 	f2_2 := stat(subdir2, "file2")
  177 
  178 	c.Assert(checkChange(f0, nil), qt.Equals, fsnotify.Remove)
  179 	c.Assert(checkChange(nil, f0), qt.Equals, fsnotify.Create)
  180 	c.Assert(checkChange(f1, f1_2), qt.Equals, fsnotify.Chmod)
  181 	c.Assert(checkChange(f2, f2_2), qt.Equals, fsnotify.Write)
  182 	c.Assert(checkChange(nil, nil), qt.Equals, fsnotify.Op(0))
  183 	c.Assert(checkChange(d1, f1), qt.Equals, fsnotify.Op(0))
  184 	c.Assert(checkChange(f1, d1), qt.Equals, fsnotify.Op(0))
  185 }
  186 
  187 func BenchmarkPoller(b *testing.B) {
  188 	runBench := func(b *testing.B, item *itemToWatch) {
  189 		b.ResetTimer()
  190 		for i := 0; i < b.N; i++ {
  191 			evs, err := item.checkForChanges()
  192 			if err != nil {
  193 				b.Fatal(err)
  194 			}
  195 			if len(evs) != 0 {
  196 				b.Fatal("got events")
  197 			}
  198 
  199 		}
  200 
  201 	}
  202 
  203 	b.Run("Check for changes in dir", func(b *testing.B) {
  204 		c := qt.New(b)
  205 		dir := prepareTestDirWithSomeFiles(c, "bench-check")
  206 		item, err := newItemToWatch(dir)
  207 		c.Assert(err, qt.IsNil)
  208 		runBench(b, item)
  209 
  210 	})
  211 
  212 	b.Run("Check for changes in file", func(b *testing.B) {
  213 		c := qt.New(b)
  214 		dir := prepareTestDirWithSomeFiles(c, "bench-check-file")
  215 		filename := filepath.Join(dir, subdir1, "file1")
  216 		item, err := newItemToWatch(filename)
  217 		c.Assert(err, qt.IsNil)
  218 		runBench(b, item)
  219 	})
  220 
  221 }
  222 
  223 func prepareTestDirWithSomeFiles(c *qt.C, id string) string {
  224 	dir, err := ioutil.TempDir("", fmt.Sprintf("test-poller-dir-%s", id))
  225 	c.Assert(err, qt.IsNil)
  226 	c.Assert(os.MkdirAll(filepath.Join(dir, subdir1), 0777), qt.IsNil)
  227 	c.Assert(os.MkdirAll(filepath.Join(dir, subdir2), 0777), qt.IsNil)
  228 
  229 	for i := 0; i < 3; i++ {
  230 		c.Assert(ioutil.WriteFile(filepath.Join(dir, subdir1, fmt.Sprintf("file%d", i)), []byte("hello1"), 0600), qt.IsNil)
  231 	}
  232 
  233 	for i := 0; i < 3; i++ {
  234 		c.Assert(ioutil.WriteFile(filepath.Join(dir, subdir2, fmt.Sprintf("file%d", i)), []byte("hello2"), 0600), qt.IsNil)
  235 	}
  236 
  237 	c.Cleanup(func() {
  238 		os.RemoveAll(dir)
  239 	})
  240 
  241 	return dir
  242 }
  243 
  244 func preparePollTest(c *qt.C, poll bool) (string, FileWatcher) {
  245 	var w FileWatcher
  246 	if poll {
  247 		w = NewPollingWatcher(watchWaitTime)
  248 	} else {
  249 		var err error
  250 		w, err = NewEventWatcher()
  251 		c.Assert(err, qt.IsNil)
  252 	}
  253 
  254 	dir := prepareTestDirWithSomeFiles(c, fmt.Sprint(poll))
  255 
  256 	c.Cleanup(func() {
  257 		w.Close()
  258 	})
  259 	return dir, w
  260 }
  261 
  262 func assertEvents(c *qt.C, w FileWatcher, evs ...fsnotify.Event) {
  263 	c.Helper()
  264 	i := 0
  265 	check := func() error {
  266 		for {
  267 			select {
  268 			case got := <-w.Events():
  269 				if i > len(evs)-1 {
  270 					return fmt.Errorf("got too many event(s): %q", got)
  271 				}
  272 				expected := evs[i]
  273 				i++
  274 				if expected.Name != got.Name {
  275 					return fmt.Errorf("got wrong filename, expected %q: %v", expected.Name, got.Name)
  276 				} else if got.Op&expected.Op != expected.Op {
  277 					return fmt.Errorf("got wrong event type, expected %q: %v", expected.Op, got.Op)
  278 				}
  279 			case e := <-w.Errors():
  280 				return fmt.Errorf("got unexpected error waiting for events %v", e)
  281 			case <-time.After(watchWaitTime + (watchWaitTime / 2)):
  282 				return nil
  283 			}
  284 		}
  285 	}
  286 	c.Assert(check(), qt.IsNil)
  287 	c.Assert(i, qt.Equals, len(evs))
  288 }
  289 
  290 func drainEvents(c *qt.C, w FileWatcher) {
  291 	c.Helper()
  292 	check := func() error {
  293 		for {
  294 			select {
  295 			case <-w.Events():
  296 			case e := <-w.Errors():
  297 				return fmt.Errorf("got unexpected error waiting for events %v", e)
  298 			case <-time.After(watchWaitTime * 2):
  299 				return nil
  300 			}
  301 		}
  302 	}
  303 	c.Assert(check(), qt.IsNil)
  304 }