|
0 |
package watch
|
|
1 |
|
|
2 |
import (
|
|
3 |
"errors"
|
|
4 |
"fmt"
|
|
5 |
"gopkg.in/tomb.v1"
|
|
6 |
"io/ioutil"
|
|
7 |
"os"
|
|
8 |
"os/exec"
|
|
9 |
"path/filepath"
|
|
10 |
"runtime"
|
|
11 |
"sync"
|
|
12 |
"testing"
|
|
13 |
"time"
|
|
14 |
)
|
|
15 |
|
|
16 |
func TestWatchNotify(t *testing.T) {
|
|
17 |
testCases := []struct {
|
|
18 |
name string
|
|
19 |
poll bool
|
|
20 |
}{
|
|
21 |
{"Test watch inotify", false},
|
|
22 |
{"Test watch poll", true},
|
|
23 |
}
|
|
24 |
for _, test := range testCases {
|
|
25 |
t.Run(test.name, func(t *testing.T) {
|
|
26 |
tmpDir, err := ioutil.TempDir("", "watch-")
|
|
27 |
if err != nil {
|
|
28 |
t.Fatal(err)
|
|
29 |
}
|
|
30 |
defer os.RemoveAll(tmpDir)
|
|
31 |
filePath := filepath.Join(tmpDir, "a")
|
|
32 |
// create file
|
|
33 |
file, err := os.OpenFile(filePath, os.O_WRONLY|os.O_CREATE, 0777)
|
|
34 |
if err != nil {
|
|
35 |
t.Fatal(err)
|
|
36 |
}
|
|
37 |
err = file.Close()
|
|
38 |
if err != nil {
|
|
39 |
t.Fatal(err)
|
|
40 |
}
|
|
41 |
|
|
42 |
var wg sync.WaitGroup
|
|
43 |
var werr error
|
|
44 |
changes := 0
|
|
45 |
chanClose := make(chan struct{})
|
|
46 |
go func() {
|
|
47 |
wg.Add(1)
|
|
48 |
changes, werr = watchFile(filePath, test.poll, chanClose)
|
|
49 |
wg.Done()
|
|
50 |
}()
|
|
51 |
|
|
52 |
writeToFile(t, filePath, "hello", true)
|
|
53 |
<-time.After(time.Second)
|
|
54 |
writeToFile(t, filePath, "world", true)
|
|
55 |
<-time.After(time.Second)
|
|
56 |
writeToFile(t, filePath, "end", false)
|
|
57 |
<-time.After(time.Second)
|
|
58 |
//err = os.Remove(filePath)
|
|
59 |
//if err != nil {
|
|
60 |
// t.Fatal(err)
|
|
61 |
//}
|
|
62 |
rmFile(t, filePath)
|
|
63 |
chanClose <- struct{}{}
|
|
64 |
wg.Wait()
|
|
65 |
close(chanClose)
|
|
66 |
|
|
67 |
if werr != nil {
|
|
68 |
t.Fatal(werr)
|
|
69 |
}
|
|
70 |
// ideally, there should be 4 changes (2xmodified,1xtruncaed and 1xdeleted)
|
|
71 |
// but, notifications from fsnotify are usually 2 (2xmodify) and 3x from poll (2xmodify, 1xtruncated)
|
|
72 |
if changes < 1 || changes > 4 {
|
|
73 |
t.Errorf("Invalid changes count: %d\n", changes)
|
|
74 |
}
|
|
75 |
})
|
|
76 |
}
|
|
77 |
}
|
|
78 |
|
|
79 |
func writeToFile(t *testing.T, path, content string, append bool) {
|
|
80 |
t.Helper()
|
|
81 |
redir := ">"
|
|
82 |
if append {
|
|
83 |
redir = ">>"
|
|
84 |
}
|
|
85 |
//var cmd *exec.Cmd
|
|
86 |
var out []byte
|
|
87 |
var err error
|
|
88 |
line := `echo ` + content + " " + redir + path + ``
|
|
89 |
if runtime.GOOS == "windows" {
|
|
90 |
out, err = exec.Command("cmd", "/c", line).Output()
|
|
91 |
//cmd = exec.Command("cmd", "/c", line)
|
|
92 |
} else {
|
|
93 |
//cmd = exec.Command("sh", "-c", line)
|
|
94 |
out, err = exec.Command("sh", "-c", line).Output()
|
|
95 |
}
|
|
96 |
//fmt.Println(cmd.String())
|
|
97 |
//err := cmd.Run()
|
|
98 |
if len(out) > 2 {
|
|
99 |
fmt.Println("output:", string(out))
|
|
100 |
}
|
|
101 |
if err != nil {
|
|
102 |
if ee, ok := err.(*exec.ExitError); ok {
|
|
103 |
fmt.Println("Stderr:", string(ee.Stderr))
|
|
104 |
}
|
|
105 |
t.Fatal(err)
|
|
106 |
}
|
|
107 |
}
|
|
108 |
|
|
109 |
func rmFile(t *testing.T, path string) {
|
|
110 |
t.Helper()
|
|
111 |
var cmd *exec.Cmd
|
|
112 |
if runtime.GOOS == "windows" {
|
|
113 |
cmd = exec.Command("cmd", "/c", "del", path)
|
|
114 |
} else {
|
|
115 |
cmd = exec.Command("rm", path)
|
|
116 |
}
|
|
117 |
|
|
118 |
err := cmd.Run()
|
|
119 |
if err != nil {
|
|
120 |
t.Fatal(err)
|
|
121 |
}
|
|
122 |
}
|
|
123 |
|
|
124 |
func watchFile(path string, poll bool, close <-chan struct{}) (int, error) {
|
|
125 |
changesCount := 0
|
|
126 |
var mytomb tomb.Tomb
|
|
127 |
var watcher FileWatcher
|
|
128 |
if poll {
|
|
129 |
watcher = NewPollingFileWatcher(path)
|
|
130 |
} else {
|
|
131 |
watcher = NewInotifyFileWatcher(path)
|
|
132 |
}
|
|
133 |
|
|
134 |
for {
|
|
135 |
changes, err := watcher.ChangeEvents(&mytomb, 0)
|
|
136 |
if err != nil {
|
|
137 |
return -1, err
|
|
138 |
}
|
|
139 |
select {
|
|
140 |
case <-changes.Modified:
|
|
141 |
fmt.Println("Modified")
|
|
142 |
changesCount++
|
|
143 |
case <-changes.Deleted:
|
|
144 |
fmt.Println("Deleted")
|
|
145 |
<-time.After(time.Second)
|
|
146 |
if _, err := os.Stat(path); err == nil {
|
|
147 |
changesCount++
|
|
148 |
}
|
|
149 |
case <-changes.Truncated:
|
|
150 |
fmt.Println("Truncated")
|
|
151 |
changesCount++
|
|
152 |
case <-mytomb.Dying():
|
|
153 |
return -1, errors.New("dying")
|
|
154 |
case <-close:
|
|
155 |
goto end
|
|
156 |
}
|
|
157 |
}
|
|
158 |
end:
|
|
159 |
mytomb.Done()
|
|
160 |
return changesCount, nil
|
|
161 |
}
|