123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299 |
- // 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/zillode/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 = newBasicFilesystem(testDirAbs)
- if l.ShouldDebug("filesystem") {
- testFs = &logFilesystem{testFs}
- }
- backendBuffer = 10
- defer func() {
- backendBuffer = 500
- }()
- 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},
- }
- testScenario(t, name, testCase, expectedEvents, false, 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,
- }
- testScenario(t, name, testCase, expectedEvents, false, "")
- }
- // 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"
- testCase := func() {
- for i := 0; i < 5*backendBuffer; i++ {
- createTestFile(name, "file"+strconv.Itoa(i))
- }
- }
- expectedEvents := []Event{
- {".", NonRemove},
- }
- testScenario(t, name, testCase, expectedEvents, true, "")
- }
- // 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 []Event, allowOthers bool, ignored string) {
- if err := testFs.MkdirAll(name, 0755); err != nil {
- panic(fmt.Sprintf("Failed to create directory %s: %s", name, err))
- }
- // Tests pick up the previously created files/dirs, probably because
- // they get flushed to disk with a delay.
- initDelayMs := 500
- if runtime.GOOS == "darwin" {
- initDelayMs = 2000
- }
- sleepMs(initDelayMs)
- ctx, cancel := context.WithCancel(context.Background())
- 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, allowOthers, ctx, cancel)
- timeoutDuration := 2 * time.Second
- if runtime.GOOS == "darwin" {
- timeoutDuration *= 2
- }
- timeout := time.NewTimer(timeoutDuration)
- testCase()
- select {
- case <-timeout.C:
- t.Errorf("Timed out before receiving all expected events")
- cancel()
- case <-ctx.Done():
- }
- if err := testFs.RemoveAll(name); err != nil {
- panic(fmt.Sprintf("Failed to remove directory %s: %s", name, err))
- }
- }
- func testWatchOutput(t *testing.T, name string, in <-chan Event, expectedEvents []Event, allowOthers bool, 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 allowOthers {
- 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
- }
|