syncthing/lib/fs/basicfs_watch_test.go
Jakob Borg bea3c01772 vendor: github.com/Zillode/notify is now github.com/syncthing/notify (#4813)
Given that we've taken on the resposibility of maintaining this forked
package I've added it to the Syncthing organization. We still vendor it
like an external package, because it's convenient to keep it as a fork
of upstream to easier merge and file pull requests towards them.
2018-03-14 14:48:22 +01:00

296 lines
6.7 KiB
Go

// Copyright (C) 2016 The Syncthing Authors.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at http://mozilla.org/MPL/2.0/.
// +build !solaris,!darwin solaris,cgo darwin,cgo
package fs
import (
"context"
"fmt"
"os"
"path/filepath"
"runtime"
"strconv"
"testing"
"time"
"github.com/syncthing/notify"
)
func TestMain(m *testing.M) {
if err := os.RemoveAll(testDir); err != nil {
panic(err)
}
dir, err := filepath.Abs(".")
if err != nil {
panic("Cannot get absolute path to working dir")
}
dir, err = filepath.EvalSymlinks(dir)
if err != nil {
panic("Cannot get real path to working dir")
}
testDirAbs = filepath.Join(dir, testDir)
testFs = NewFilesystem(FilesystemTypeBasic, testDirAbs)
backendBuffer = 10
defer func() {
backendBuffer = 500
os.RemoveAll(testDir)
}()
os.Exit(m.Run())
}
const (
testDir = "temporary_test_root"
)
var (
testDirAbs string
testFs Filesystem
)
func TestWatchIgnore(t *testing.T) {
name := "ignore"
file := "file"
ignored := "ignored"
testCase := func() {
createTestFile(name, file)
createTestFile(name, ignored)
}
expectedEvents := []Event{
{file, NonRemove},
}
allowedEvents := []Event{
{name, NonRemove},
}
testScenario(t, name, testCase, expectedEvents, allowedEvents, ignored)
}
func TestWatchRename(t *testing.T) {
name := "rename"
old := createTestFile(name, "oldfile")
new := "newfile"
testCase := func() {
renameTestFile(name, old, new)
}
destEvent := Event{new, Remove}
// Only on these platforms the removed file can be differentiated from
// the created file during renaming
if runtime.GOOS == "windows" || runtime.GOOS == "linux" || runtime.GOOS == "solaris" {
destEvent = Event{new, NonRemove}
}
expectedEvents := []Event{
{old, Remove},
destEvent,
}
allowedEvents := []Event{
{name, NonRemove},
}
// set the "allow others" flag because we might get the create of
// "oldfile" initially
testScenario(t, name, testCase, expectedEvents, allowedEvents, "")
}
// TestWatchOutside checks that no changes from outside the folder make it in
func TestWatchOutside(t *testing.T) {
outChan := make(chan Event)
backendChan := make(chan notify.EventInfo, backendBuffer)
ctx, cancel := context.WithCancel(context.Background())
// testFs is Filesystem, but we need BasicFilesystem here
fs := newBasicFilesystem(testDirAbs)
go func() {
defer func() {
if recover() == nil {
t.Fatalf("Watch did not panic on receiving event outside of folder")
}
cancel()
}()
fs.watchLoop(".", testDirAbs, backendChan, outChan, fakeMatcher{}, ctx)
}()
backendChan <- fakeEventInfo(filepath.Join(filepath.Dir(testDirAbs), "outside"))
}
func TestWatchSubpath(t *testing.T) {
outChan := make(chan Event)
backendChan := make(chan notify.EventInfo, backendBuffer)
ctx, cancel := context.WithCancel(context.Background())
// testFs is Filesystem, but we need BasicFilesystem here
fs := newBasicFilesystem(testDirAbs)
abs, _ := fs.rooted("sub")
go fs.watchLoop("sub", abs, backendChan, outChan, fakeMatcher{}, ctx)
backendChan <- fakeEventInfo(filepath.Join(abs, "file"))
timeout := time.NewTimer(2 * time.Second)
select {
case <-timeout.C:
t.Errorf("Timed out before receiving an event")
cancel()
case ev := <-outChan:
if ev.Name != filepath.Join("sub", "file") {
t.Errorf("While watching a subfolder, received an event with unexpected path %v", ev.Name)
}
}
cancel()
}
// TestWatchOverflow checks that an event at the root is sent when maxFiles is reached
func TestWatchOverflow(t *testing.T) {
name := "overflow"
expectedEvents := []Event{
{".", NonRemove},
}
allowedEvents := []Event{
{name, NonRemove},
}
testCase := func() {
for i := 0; i < 5*backendBuffer; i++ {
file := "file" + strconv.Itoa(i)
createTestFile(name, file)
allowedEvents = append(allowedEvents, Event{file, NonRemove})
}
}
testScenario(t, name, testCase, expectedEvents, allowedEvents, "")
}
// path relative to folder root, also creates parent dirs if necessary
func createTestFile(name string, file string) string {
joined := filepath.Join(name, file)
if err := testFs.MkdirAll(filepath.Dir(joined), 0755); err != nil {
panic(fmt.Sprintf("Failed to create parent directory for %s: %s", joined, err))
}
handle, err := testFs.Create(joined)
if err != nil {
panic(fmt.Sprintf("Failed to create test file %s: %s", joined, err))
}
handle.Close()
return file
}
func renameTestFile(name string, old string, new string) {
old = filepath.Join(name, old)
new = filepath.Join(name, new)
if err := testFs.Rename(old, new); err != nil {
panic(fmt.Sprintf("Failed to rename %s to %s: %s", old, new, err))
}
}
func sleepMs(ms int) {
time.Sleep(time.Duration(ms) * time.Millisecond)
}
func testScenario(t *testing.T, name string, testCase func(), expectedEvents, allowedEvents []Event, ignored string) {
if err := testFs.MkdirAll(name, 0755); err != nil {
panic(fmt.Sprintf("Failed to create directory %s: %s", name, err))
}
defer testFs.RemoveAll(name)
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
if ignored != "" {
ignored = filepath.Join(name, ignored)
}
eventChan, err := testFs.Watch(name, fakeMatcher{ignored}, ctx, false)
if err != nil {
panic(err)
}
go testWatchOutput(t, name, eventChan, expectedEvents, allowedEvents, ctx, cancel)
testCase()
select {
case <-time.NewTimer(time.Minute).C:
t.Errorf("Timed out before receiving all expected events")
case <-ctx.Done():
}
}
func testWatchOutput(t *testing.T, name string, in <-chan Event, expectedEvents, allowedEvents []Event, ctx context.Context, cancel context.CancelFunc) {
var expected = make(map[Event]struct{})
for _, ev := range expectedEvents {
ev.Name = filepath.Join(name, ev.Name)
expected[ev] = struct{}{}
}
var received Event
var last Event
for {
if len(expected) == 0 {
cancel()
return
}
select {
case <-ctx.Done():
return
case received = <-in:
}
// apparently the backend sometimes sends repeat events
if last == received {
continue
}
if _, ok := expected[received]; !ok {
if len(allowedEvents) > 0 {
sleepMs(100) // To facilitate overflow
continue
}
t.Errorf("Received unexpected event %v expected one of %v", received, expected)
cancel()
return
}
delete(expected, received)
last = received
}
}
type fakeMatcher struct{ match string }
func (fm fakeMatcher) ShouldIgnore(name string) bool {
return name == fm.match
}
type fakeEventInfo string
func (e fakeEventInfo) Path() string {
return string(e)
}
func (e fakeEventInfo) Event() notify.Event {
return notify.Write
}
func (e fakeEventInfo) Sys() interface{} {
return nil
}