1// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
2// Use of this source code is governed by the MIT license that can be
3// found in the LICENSE file.
4
5package notify
6
7import (
8	"bufio"
9	"fmt"
10	"io/ioutil"
11	"os"
12	"path/filepath"
13	"reflect"
14	"runtime"
15	"sort"
16	"strconv"
17	"strings"
18	"testing"
19	"time"
20)
21
22// NOTE(rjeczalik): some useful environment variables:
23//
24//   - NOTIFY_DEBUG gives some extra information about generated events
25//   - NOTIFY_TIMEOUT allows for changing default wait time for watcher's
26//     events
27//   - NOTIFY_TMP allows for changing location of temporary directory trees
28//     created for test purpose
29
30var wd string
31
32func init() {
33	var err error
34	if wd, err = os.Getwd(); err != nil {
35		panic("Getwd()=" + err.Error())
36	}
37}
38
39func timeout() time.Duration {
40	if s := os.Getenv("NOTIFY_TIMEOUT"); s != "" {
41		if t, err := time.ParseDuration(s); err == nil {
42			return t
43		}
44	}
45	return 2 * time.Second
46}
47
48func vfs() (string, string) {
49	if s := os.Getenv("NOTIFY_TMP"); s != "" {
50		return filepath.Split(s)
51	}
52	return "testdata", ""
53}
54
55func isDir(path string) bool {
56	r := path[len(path)-1]
57	return r == '\\' || r == '/'
58}
59
60func tmpcreateall(tmp string, path string) error {
61	isdir := isDir(path)
62	path = filepath.Join(tmp, filepath.FromSlash(path))
63	if isdir {
64		if err := os.MkdirAll(path, 0755); err != nil {
65			return err
66		}
67	} else {
68		if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil {
69			return err
70		}
71		f, err := os.Create(path)
72		if err != nil {
73			return err
74		}
75		if err := nonil(f.Sync(), f.Close()); err != nil {
76			return err
77		}
78	}
79	return nil
80}
81
82func tmpcreate(root, path string) (bool, error) {
83	isdir := isDir(path)
84	path = filepath.Join(root, filepath.FromSlash(path))
85	if isdir {
86		if err := os.Mkdir(path, 0755); err != nil {
87			return false, err
88		}
89	} else {
90		f, err := os.Create(path)
91		if err != nil {
92			return false, err
93		}
94		if err := nonil(f.Sync(), f.Close()); err != nil {
95			return false, err
96		}
97	}
98	return isdir, nil
99}
100
101func tmptree(root, list string) (string, error) {
102	f, err := os.Open(list)
103	if err != nil {
104		return "", err
105	}
106	defer f.Close()
107	if root == "" {
108		if root, err = ioutil.TempDir(vfs()); err != nil {
109			return "", err
110		}
111	}
112	scanner := bufio.NewScanner(f)
113	for scanner.Scan() {
114		if err := tmpcreateall(root, scanner.Text()); err != nil {
115			return "", err
116		}
117	}
118	if err := scanner.Err(); err != nil {
119		return "", err
120	}
121	return root, nil
122}
123
124func callern(n int) string {
125	_, file, line, ok := runtime.Caller(n)
126	if !ok {
127		return "<unknown>"
128	}
129	return filepath.Base(file) + ":" + strconv.Itoa(line)
130}
131
132func caller() string {
133	return callern(3)
134}
135
136type WCase struct {
137	Action func()
138	Events []EventInfo
139}
140
141func (cas WCase) String() string {
142	s := make([]string, 0, len(cas.Events))
143	for _, ei := range cas.Events {
144		s = append(s, "Event("+ei.Event().String()+")@"+filepath.FromSlash(ei.Path()))
145	}
146	return strings.Join(s, ", ")
147}
148
149type W struct {
150	Watcher watcher
151	C       chan EventInfo
152	Timeout time.Duration
153
154	t    *testing.T
155	root string
156}
157
158func newWatcherTest(t *testing.T, tree string) *W {
159	root, err := tmptree("", filepath.FromSlash(tree))
160	if err != nil {
161		t.Fatalf(`tmptree("", %q)=%v`, tree, err)
162	}
163	root, _, err = cleanpath(root)
164	if err != nil {
165		t.Fatalf(`cleanpath(%q)=%v`, root, err)
166	}
167	Sync()
168	return &W{
169		t:    t,
170		root: root,
171	}
172}
173
174func NewWatcherTest(t *testing.T, tree string, events ...Event) *W {
175	w := newWatcherTest(t, tree)
176	if len(events) == 0 {
177		events = []Event{Create, Remove, Write, Rename}
178	}
179	if rw, ok := w.watcher().(recursiveWatcher); ok {
180		if err := rw.RecursiveWatch(w.root, joinevents(events)); err != nil {
181			t.Fatalf("RecursiveWatch(%q, All)=%v", w.root, err)
182		}
183	} else {
184		fn := func(path string, fi os.FileInfo, err error) error {
185			if err != nil {
186				return err
187			}
188			if fi.IsDir() {
189				if err := w.watcher().Watch(path, joinevents(events)); err != nil {
190					return err
191				}
192			}
193			return nil
194		}
195		if err := filepath.Walk(w.root, fn); err != nil {
196			t.Fatalf("Walk(%q, fn)=%v", w.root, err)
197		}
198	}
199	drainall(w.C)
200	return w
201}
202
203func (w *W) clean(path string) string {
204	path, isrec, err := cleanpath(filepath.Join(w.root, path))
205	if err != nil {
206		w.Fatalf("cleanpath(%q)=%v", path, err)
207	}
208	if isrec {
209		path = path + "..."
210	}
211	return path
212}
213
214func (w *W) Fatal(v interface{}) {
215	w.t.Fatalf("%s: %v", caller(), v)
216}
217
218func (w *W) Fatalf(format string, v ...interface{}) {
219	w.t.Fatalf("%s: %s", caller(), fmt.Sprintf(format, v...))
220}
221
222func (w *W) Watch(path string, e Event) {
223	if err := w.watcher().Watch(w.clean(path), e); err != nil {
224		w.Fatalf("Watch(%s, %v)=%v", path, e, err)
225	}
226}
227
228func (w *W) Rewatch(path string, olde, newe Event) {
229	if err := w.watcher().Rewatch(w.clean(path), olde, newe); err != nil {
230		w.Fatalf("Rewatch(%s, %v, %v)=%v", path, olde, newe, err)
231	}
232}
233
234func (w *W) Unwatch(path string) {
235	if err := w.watcher().Unwatch(w.clean(path)); err != nil {
236		w.Fatalf("Unwatch(%s)=%v", path, err)
237	}
238}
239
240func (w *W) RecursiveWatch(path string, e Event) {
241	rw, ok := w.watcher().(recursiveWatcher)
242	if !ok {
243		w.Fatal("watcher does not implement recursive watching on this platform")
244	}
245	if err := rw.RecursiveWatch(w.clean(path), e); err != nil {
246		w.Fatalf("RecursiveWatch(%s, %v)=%v", path, e, err)
247	}
248}
249
250func (w *W) RecursiveRewatch(oldp, newp string, olde, newe Event) {
251	rw, ok := w.watcher().(recursiveWatcher)
252	if !ok {
253		w.Fatal("watcher does not implement recursive watching on this platform")
254	}
255	if err := rw.RecursiveRewatch(w.clean(oldp), w.clean(newp), olde, newe); err != nil {
256		w.Fatalf("RecursiveRewatch(%s, %s, %v, %v)=%v", oldp, newp, olde, newe, err)
257	}
258}
259
260func (w *W) RecursiveUnwatch(path string) {
261	rw, ok := w.watcher().(recursiveWatcher)
262	if !ok {
263		w.Fatal("watcher does not implement recursive watching on this platform")
264	}
265	if err := rw.RecursiveUnwatch(w.clean(path)); err != nil {
266		w.Fatalf("RecursiveUnwatch(%s)=%v", path, err)
267	}
268}
269
270func (w *W) initwatcher(buffer int) {
271	c := make(chan EventInfo, buffer)
272	w.Watcher = newWatcher(c)
273	w.C = c
274}
275
276func (w *W) watcher() watcher {
277	if w.Watcher == nil {
278		w.initwatcher(512)
279	}
280	return w.Watcher
281}
282
283func (w *W) c() chan EventInfo {
284	if w.C == nil {
285		w.initwatcher(512)
286	}
287	return w.C
288}
289
290func (w *W) timeout() time.Duration {
291	if w.Timeout != 0 {
292		return w.Timeout
293	}
294	return timeout()
295}
296
297func (w *W) Close() error {
298	defer os.RemoveAll(w.root)
299	if err := w.watcher().Close(); err != nil {
300		w.Fatalf("w.Watcher.Close()=%v", err)
301	}
302	return nil
303}
304
305func EqualEventInfo(want, got EventInfo) error {
306	if got.Event() != want.Event() {
307		return fmt.Errorf("want Event()=%v; got %v (path=%s)", want.Event(),
308			got.Event(), want.Path())
309	}
310	path := strings.TrimRight(filepath.FromSlash(want.Path()), `/\`)
311	if !strings.HasSuffix(got.Path(), path) {
312		return fmt.Errorf("want Path()=%s; got %s (event=%v)", path, got.Path(),
313			want.Event())
314	}
315	return nil
316}
317
318func HasEventInfo(want, got Event, p string) error {
319	if got&want != want {
320		return fmt.Errorf("want Event=%v; got %v (path=%s)", want,
321			got, p)
322	}
323	return nil
324}
325
326func EqualCall(want, got Call) error {
327	if want.F != got.F {
328		return fmt.Errorf("want F=%v; got %v (want.P=%q, got.P=%q)", want.F, got.F, want.P, got.P)
329	}
330	if got.E != want.E {
331		return fmt.Errorf("want E=%v; got %v (want.P=%q, got.P=%q)", want.E, got.E, want.P, got.P)
332	}
333	if got.NE != want.NE {
334		return fmt.Errorf("want NE=%v; got %v (want.P=%q, got.P=%q)", want.NE, got.NE, want.P, got.P)
335	}
336	if want.C != got.C {
337		return fmt.Errorf("want C=%p; got %p (want.P=%q, got.P=%q)", want.C, got.C, want.P, got.P)
338	}
339	if want := filepath.FromSlash(want.P); !strings.HasSuffix(got.P, want) {
340		return fmt.Errorf("want P=%s; got %s", want, got.P)
341	}
342	if want := filepath.FromSlash(want.NP); !strings.HasSuffix(got.NP, want) {
343		return fmt.Errorf("want NP=%s; got %s", want, got.NP)
344	}
345	return nil
346}
347
348func create(w *W, path string) WCase {
349	return WCase{
350		Action: func() {
351			isdir, err := tmpcreate(w.root, filepath.FromSlash(path))
352			if err != nil {
353				w.Fatalf("tmpcreate(%q, %q)=%v", w.root, path, err)
354			}
355			if isdir {
356				dbgprintf("[FS] os.Mkdir(%q)\n", path)
357			} else {
358				dbgprintf("[FS] os.Create(%q)\n", path)
359			}
360		},
361		Events: []EventInfo{
362			&Call{P: path, E: Create},
363		},
364	}
365}
366
367func remove(w *W, path string) WCase {
368	return WCase{
369		Action: func() {
370			if err := os.RemoveAll(filepath.Join(w.root, filepath.FromSlash(path))); err != nil {
371				w.Fatal(err)
372			}
373			dbgprintf("[FS] os.Remove(%q)\n", path)
374		},
375		Events: []EventInfo{
376			&Call{P: path, E: Remove},
377		},
378	}
379}
380
381func rename(w *W, oldpath, newpath string) WCase {
382	return WCase{
383		Action: func() {
384			err := os.Rename(filepath.Join(w.root, filepath.FromSlash(oldpath)),
385				filepath.Join(w.root, filepath.FromSlash(newpath)))
386			if err != nil {
387				w.Fatal(err)
388			}
389			dbgprintf("[FS] os.Rename(%q, %q)\n", oldpath, newpath)
390		},
391		Events: []EventInfo{
392			&Call{P: newpath, E: Rename},
393		},
394	}
395}
396
397func write(w *W, path string, p []byte) WCase {
398	return WCase{
399		Action: func() {
400			f, err := os.OpenFile(filepath.Join(w.root, filepath.FromSlash(path)),
401				os.O_WRONLY, 0644)
402			if err != nil {
403				w.Fatalf("OpenFile(%q)=%v", path, err)
404			}
405			if _, err := f.Write(p); err != nil {
406				w.Fatalf("Write(%q)=%v", path, err)
407			}
408			if err := nonil(f.Sync(), f.Close()); err != nil {
409				w.Fatalf("Sync(%q)/Close(%q)=%v", path, path, err)
410			}
411			dbgprintf("[FS] Write(%q)\n", path)
412		},
413		Events: []EventInfo{
414			&Call{P: path, E: Write},
415		},
416	}
417}
418
419func drainall(c chan EventInfo) (ei []EventInfo) {
420	time.Sleep(50 * time.Millisecond)
421	for {
422		select {
423		case e := <-c:
424			ei = append(ei, e)
425			runtime.Gosched()
426		default:
427			return
428		}
429	}
430}
431
432type WCaseFunc func(i int, cas WCase, ei EventInfo) error
433
434func (w *W) ExpectAnyFunc(cases []WCase, fn WCaseFunc) {
435	UpdateWait() // Wait some time before starting the test.
436Test:
437	for i, cas := range cases {
438		dbgprintf("ExpectAny: i=%d\n", i)
439		cas.Action()
440		Sync()
441		switch cas.Events {
442		case nil:
443			if ei := drainall(w.C); len(ei) != 0 {
444				w.Fatalf("unexpected dangling events: %v (i=%d)", ei, i)
445			}
446		default:
447			select {
448			case ei := <-w.C:
449				dbgprintf("received: path=%q, event=%v, sys=%v (i=%d)", ei.Path(),
450					ei.Event(), ei.Sys(), i)
451				for j, want := range cas.Events {
452					if err := EqualEventInfo(want, ei); err != nil {
453						dbgprint(err, j)
454						continue
455					}
456					if fn != nil {
457						if err := fn(i, cas, ei); err != nil {
458							w.Fatalf("ExpectAnyFunc(%d, %v)=%v", i, ei, err)
459						}
460					}
461					drainall(w.C) // TODO(rjeczalik): revisit
462					continue Test
463				}
464				w.Fatalf("ExpectAny received an event which does not match any of "+
465					"the expected ones (i=%d): want one of %v; got %v", i, cas.Events, ei)
466			case <-time.After(w.timeout()):
467				w.Fatalf("timed out after %v waiting for one of %v (i=%d)", w.timeout(),
468					cas.Events, i)
469			}
470			drainall(w.C) // TODO(rjeczalik): revisit
471		}
472	}
473}
474
475func (w *W) ExpectAny(cases []WCase) {
476	w.ExpectAnyFunc(cases, nil)
477}
478
479func (w *W) aggregate(ei []EventInfo, pf string) (evs map[string]Event) {
480	evs = make(map[string]Event)
481	for _, cas := range ei {
482		p := cas.Path()
483		if pf != "" {
484			p = filepath.Join(pf, p)
485		}
486		evs[p] |= cas.Event()
487	}
488	return
489}
490
491func (w *W) ExpectAllFunc(cases []WCase) {
492	UpdateWait() // Wait some time before starting the test.
493	for i, cas := range cases {
494		exp := w.aggregate(cas.Events, w.root)
495		dbgprintf("ExpectAll: i=%d\n", i)
496		cas.Action()
497		Sync()
498		got := w.aggregate(drainall(w.C), "")
499		for ep, ee := range exp {
500			ge, ok := got[ep]
501			if !ok {
502				w.Fatalf("missing events for %q (%v)", ep, ee)
503				continue
504			}
505			delete(got, ep)
506			if err := HasEventInfo(ee, ge, ep); err != nil {
507				w.Fatalf("ExpectAll received an event which does not match "+
508					"the expected ones for %q: want %v; got %v", ep, ee, ge)
509				continue
510			}
511		}
512		if len(got) != 0 {
513			w.Fatalf("ExpectAll received unexpected events: %v", got)
514		}
515	}
516}
517
518// ExpectAll requires all requested events to be send.
519// It does not require events to be send in the same order or in the same
520// chunks (e.g. NoteWrite and NoteExtend reported as independent events are
521// treated the same as one NoteWrite|NoteExtend event).
522func (w *W) ExpectAll(cases []WCase) {
523	w.ExpectAllFunc(cases)
524}
525
526// FuncType represents enums for Watcher interface.
527type FuncType string
528
529const (
530	FuncWatch            = FuncType("Watch")
531	FuncUnwatch          = FuncType("Unwatch")
532	FuncRewatch          = FuncType("Rewatch")
533	FuncRecursiveWatch   = FuncType("RecursiveWatch")
534	FuncRecursiveUnwatch = FuncType("RecursiveUnwatch")
535	FuncRecursiveRewatch = FuncType("RecursiveRewatch")
536	FuncStop             = FuncType("Stop")
537)
538
539type Chans []chan EventInfo
540
541func NewChans(n int) Chans {
542	ch := make([]chan EventInfo, n)
543	for i := range ch {
544		ch[i] = make(chan EventInfo, buffer)
545	}
546	return ch
547}
548
549func (c Chans) Foreach(fn func(chan<- EventInfo, node)) {
550	for i, ch := range c {
551		fn(ch, node{Name: strconv.Itoa(i)})
552	}
553}
554
555func (c Chans) Drain() (ei []EventInfo) {
556	n := len(c)
557	stop := make(chan struct{})
558	eich := make(chan EventInfo, n*buffer)
559	go func() {
560		defer close(eich)
561		cases := make([]reflect.SelectCase, n+1)
562		for i := range c {
563			cases[i].Chan = reflect.ValueOf(c[i])
564			cases[i].Dir = reflect.SelectRecv
565		}
566		cases[n].Chan = reflect.ValueOf(stop)
567		cases[n].Dir = reflect.SelectRecv
568		for {
569			i, v, ok := reflect.Select(cases)
570			if i == n {
571				return
572			}
573			if !ok {
574				panic("(Chans).Drain(): unexpected chan close")
575			}
576			eich <- v.Interface().(EventInfo)
577		}
578	}()
579	<-time.After(50 * time.Duration(n) * time.Millisecond)
580	close(stop)
581	for e := range eich {
582		ei = append(ei, e)
583	}
584	return
585}
586
587// Call represents single call to Watcher issued by the Tree
588// and recorded by a spy Watcher mock.
589type Call struct {
590	F   FuncType       // denotes type of function to call, for both watcher and notifier interface
591	C   chan EventInfo // user channel being an argument to either Watch or Stop function
592	P   string         // regular Path argument and old path from RecursiveRewatch call
593	NP  string         // new Path argument from RecursiveRewatch call
594	E   Event          // regular Event argument and old Event from a Rewatch call
595	NE  Event          // new Event argument from Rewatch call
596	S   interface{}    // when Call is used as EventInfo, S is a value of Sys()
597	Dir bool           // when Call is used as EventInfo, Dir is a value of isDir()
598}
599
600// Call implements the EventInfo interface.
601func (c *Call) Event() Event         { return c.E }
602func (c *Call) Path() string         { return c.P }
603func (c *Call) String() string       { return fmt.Sprintf("%#v", c) }
604func (c *Call) Sys() interface{}     { return c.S }
605func (c *Call) isDir() (bool, error) { return c.Dir, nil }
606
607// CallSlice is a convenient wrapper for a slice of Call values, which allows
608// to sort them in ascending order.
609type CallSlice []Call
610
611// CallSlice implements sort.Interface inteface.
612func (cs CallSlice) Len() int           { return len(cs) }
613func (cs CallSlice) Less(i, j int) bool { return cs[i].P < cs[j].P }
614func (cs CallSlice) Swap(i, j int)      { cs[i], cs[j] = cs[j], cs[i] }
615func (cs CallSlice) Sort()              { sort.Sort(cs) }
616
617// Spy is a mock for Watcher interface, which records every call.
618type Spy []Call
619
620func (s *Spy) Close() (_ error) { return }
621
622func (s *Spy) Watch(p string, e Event) (_ error) {
623	dbgprintf("%s: (*Spy).Watch(%q, %v)", caller(), p, e)
624	*s = append(*s, Call{F: FuncWatch, P: p, E: e})
625	return
626}
627
628func (s *Spy) Unwatch(p string) (_ error) {
629	dbgprintf("%s: (*Spy).Unwatch(%q)", caller(), p)
630	*s = append(*s, Call{F: FuncUnwatch, P: p})
631	return
632}
633
634func (s *Spy) Rewatch(p string, olde, newe Event) (_ error) {
635	dbgprintf("%s: (*Spy).Rewatch(%q, %v, %v)", caller(), p, olde, newe)
636	*s = append(*s, Call{F: FuncRewatch, P: p, E: olde, NE: newe})
637	return
638}
639
640func (s *Spy) RecursiveWatch(p string, e Event) (_ error) {
641	dbgprintf("%s: (*Spy).RecursiveWatch(%q, %v)", caller(), p, e)
642	*s = append(*s, Call{F: FuncRecursiveWatch, P: p, E: e})
643	return
644}
645
646func (s *Spy) RecursiveUnwatch(p string) (_ error) {
647	dbgprintf("%s: (*Spy).RecursiveUnwatch(%q)", caller(), p)
648	*s = append(*s, Call{F: FuncRecursiveUnwatch, P: p})
649	return
650}
651
652func (s *Spy) RecursiveRewatch(oldp, newp string, olde, newe Event) (_ error) {
653	dbgprintf("%s: (*Spy).RecursiveRewatch(%q, %q, %v, %v)", caller(), oldp, newp, olde, newe)
654	*s = append(*s, Call{F: FuncRecursiveRewatch, P: oldp, NP: newp, E: olde, NE: newe})
655	return
656}
657
658type RCase struct {
659	Call   Call
660	Record []Call
661}
662
663type TCase struct {
664	Event    Call
665	Receiver Chans
666}
667
668type NCase struct {
669	Event    WCase
670	Receiver Chans
671}
672
673type N struct {
674	Timeout time.Duration
675
676	t    *testing.T
677	tree tree
678	w    *W
679	spy  *Spy
680	c    chan EventInfo
681	j    int // spy offset
682
683	realroot string
684}
685
686func newN(t *testing.T, tree string) *N {
687	n := &N{
688		t: t,
689		w: newWatcherTest(t, tree),
690	}
691	realroot, err := canonical(n.w.root)
692	if err != nil {
693		t.Fatalf("%s: unexpected fixture failure: %v", caller(), err)
694	}
695	n.realroot = realroot
696	return n
697}
698
699func newTreeN(t *testing.T, tree string) *N {
700	c := make(chan EventInfo, buffer)
701	n := newN(t, tree)
702	n.spy = &Spy{}
703	n.w.Watcher = n.spy
704	n.w.C = c
705	n.c = c
706	return n
707}
708
709func NewNotifyTest(t *testing.T, tree string) *N {
710	n := newN(t, tree)
711	if rw, ok := n.w.watcher().(recursiveWatcher); ok {
712		n.tree = newRecursiveTree(rw, n.w.c())
713	} else {
714		n.tree = newNonrecursiveTree(n.w.watcher(), n.w.c(), nil)
715	}
716	return n
717}
718
719func NewRecursiveTreeTest(t *testing.T, tree string) *N {
720	n := newTreeN(t, tree)
721	n.tree = newRecursiveTree(n.spy, n.c)
722	return n
723}
724
725func NewNonrecursiveTreeTest(t *testing.T, tree string) *N {
726	n := newTreeN(t, tree)
727	n.tree = newNonrecursiveTree(n.spy, n.c, nil)
728	return n
729}
730
731func NewNonrecursiveTreeTestC(t *testing.T, tree string) (*N, chan EventInfo) {
732	rec := make(chan EventInfo, buffer)
733	recinternal := make(chan EventInfo, buffer)
734	recuser := make(chan EventInfo, buffer)
735	go func() {
736		for ei := range rec {
737			select {
738			case recinternal <- ei:
739			default:
740				t.Fatalf("failed to send ei to recinternal: not ready")
741			}
742			select {
743			case recuser <- ei:
744			default:
745				t.Fatalf("failed to send ei to recuser: not ready")
746			}
747		}
748	}()
749	n := newTreeN(t, tree)
750	tr := newNonrecursiveTree(n.spy, n.c, recinternal)
751	tr.rec = rec
752	n.tree = tr
753	return n, recuser
754}
755
756func (n *N) timeout() time.Duration {
757	if n.Timeout != 0 {
758		return n.Timeout
759	}
760	return n.w.timeout()
761}
762
763func (n *N) W() *W {
764	return n.w
765}
766
767func (n *N) Close() error {
768	defer os.RemoveAll(n.w.root)
769	if err := n.tree.Close(); err != nil {
770		n.w.Fatalf("(notifier).Close()=%v", err)
771	}
772	return nil
773}
774
775func (n *N) Watch(path string, c chan<- EventInfo, events ...Event) {
776	UpdateWait() // we need to wait on Windows because of its asynchronous watcher.
777	path = filepath.Join(n.w.root, path)
778	if err := n.tree.Watch(path, c, events...); err != nil {
779		n.t.Errorf("Watch(%s, %p, %v)=%v", path, c, events, err)
780	}
781}
782
783func (n *N) WatchErr(path string, c chan<- EventInfo, err error, events ...Event) {
784	path = filepath.Join(n.w.root, path)
785	switch e := n.tree.Watch(path, c, events...); {
786	case err == nil && e == nil:
787		n.t.Errorf("Watch(%s, %p, %v)=nil", path, c, events)
788	case err != nil && e != err:
789		n.t.Errorf("Watch(%s, %p, %v)=%v != %v", path, c, events, e, err)
790	}
791}
792
793func (n *N) Stop(c chan<- EventInfo) {
794	n.tree.Stop(c)
795}
796
797func (n *N) Call(calls ...Call) {
798	for i := range calls {
799		switch calls[i].F {
800		case FuncWatch:
801			n.Watch(calls[i].P, calls[i].C, calls[i].E)
802		case FuncStop:
803			n.Stop(calls[i].C)
804		default:
805			panic("unsupported call type: " + string(calls[i].F))
806		}
807	}
808}
809
810func (n *N) expectDry(ch Chans, i int) {
811	if ei := ch.Drain(); len(ei) != 0 {
812		n.w.Fatalf("unexpected dangling events: %v (i=%d)", ei, i)
813	}
814}
815
816func (n *N) ExpectRecordedCalls(cases []RCase) {
817	for i, cas := range cases {
818		dbgprintf("ExpectRecordedCalls: i=%d\n", i)
819		n.Call(cas.Call)
820		record := (*n.spy)[n.j:]
821		if len(cas.Record) == 0 && len(record) == 0 {
822			continue
823		}
824		n.j = len(*n.spy)
825		if len(record) != len(cas.Record) {
826			n.t.Fatalf("%s: want len(record)=%d; got %d [%+v] (i=%d)", caller(),
827				len(cas.Record), len(record), record, i)
828		}
829		CallSlice(record).Sort()
830		for j := range cas.Record {
831			if err := EqualCall(cas.Record[j], record[j]); err != nil {
832				n.t.Fatalf("%s: %v (i=%d, j=%d)", caller(), err, i, j)
833			}
834		}
835	}
836}
837
838func (n *N) collect(ch Chans) <-chan []EventInfo {
839	done := make(chan []EventInfo)
840	go func() {
841		cases := make([]reflect.SelectCase, len(ch))
842		unique := make(map[<-chan EventInfo]EventInfo, len(ch))
843		for i := range ch {
844			cases[i].Chan = reflect.ValueOf(ch[i])
845			cases[i].Dir = reflect.SelectRecv
846		}
847		for i := len(cases); i != 0; i = len(cases) {
848			j, v, ok := reflect.Select(cases)
849			if !ok {
850				n.t.Fatal("unexpected chan close")
851			}
852			ch := cases[j].Chan.Interface().(chan EventInfo)
853			got := v.Interface().(EventInfo)
854			if ei, ok := unique[ch]; ok {
855				n.t.Fatalf("duplicated event %v (previous=%v) received on collect", got, ei)
856			}
857			unique[ch] = got
858			cases[j], cases = cases[i-1], cases[:i-1]
859		}
860		collected := make([]EventInfo, 0, len(ch))
861		for _, ch := range unique {
862			collected = append(collected, ch)
863		}
864		done <- collected
865	}()
866	return done
867}
868
869func (n *N) abs(rel Call) *Call {
870	rel.P = filepath.Join(n.realroot, filepath.FromSlash(rel.P))
871	if !filepath.IsAbs(rel.P) {
872		rel.P = filepath.Join(wd, rel.P)
873	}
874	return &rel
875}
876
877func (n *N) ExpectTreeEvents(cases []TCase, all Chans) {
878	for i, cas := range cases {
879		dbgprintf("ExpectTreeEvents: i=%d\n", i)
880		// Ensure there're no dangling event left by previous test-case.
881		n.expectDry(all, i)
882		n.c <- n.abs(cas.Event)
883		switch cas.Receiver {
884		case nil:
885			n.expectDry(all, i)
886		default:
887			ch := n.collect(cas.Receiver)
888			select {
889			case collected := <-ch:
890				for _, got := range collected {
891					if err := EqualEventInfo(&cas.Event, got); err != nil {
892						n.w.Fatalf("%s: %s (i=%d)", caller(), err, i)
893					}
894				}
895			case <-time.After(n.timeout()):
896				n.w.Fatalf("ExpectTreeEvents has timed out after %v waiting for"+
897					" %v on %s (i=%d)", n.timeout(), cas.Event.E, cas.Event.P, i)
898			}
899
900		}
901	}
902	n.expectDry(all, -1)
903}
904
905func (n *N) ExpectNotifyEvents(cases []NCase, all Chans) {
906	UpdateWait() // Wait some time before starting the test.
907	for i, cas := range cases {
908		dbgprintf("ExpectNotifyEvents: i=%d\n", i)
909		cas.Event.Action()
910		Sync()
911		switch cas.Receiver {
912		case nil:
913			n.expectDry(all, i)
914		default:
915			ch := n.collect(cas.Receiver)
916			select {
917			case collected := <-ch:
918			Compare:
919				for j, ei := range collected {
920					dbgprintf("received: path=%q, event=%v, sys=%v (i=%d, j=%d)", ei.Path(),
921						ei.Event(), ei.Sys(), i, j)
922					for _, want := range cas.Event.Events {
923						if err := EqualEventInfo(want, ei); err != nil {
924							dbgprint(err, j)
925							continue
926						}
927						continue Compare
928					}
929					n.w.Fatalf("ExpectNotifyEvents received an event which does not"+
930						" match any of the expected ones (i=%d): want one of %v; got %v", i,
931						cas.Event.Events, ei)
932				}
933			case <-time.After(n.timeout()):
934				n.w.Fatalf("ExpectNotifyEvents did not receive any of the expected events [%v] "+
935					"after %v (i=%d)", cas.Event, n.timeout(), i)
936			}
937		}
938	}
939	n.expectDry(all, -1)
940}
941
942func (n *N) Walk(fn walkFunc) {
943	switch t := n.tree.(type) {
944	case *recursiveTree:
945		if err := t.root.Walk("", fn); err != nil {
946			n.w.Fatal(err)
947		}
948	case *nonrecursiveTree:
949		if err := t.root.Walk("", fn); err != nil {
950			n.w.Fatal(err)
951		}
952	default:
953		n.t.Fatal("unknown tree type")
954	}
955}
956