github.com/wtsi-ssg/wrstat@v1.1.4-0.20221008232152-3030622a8cf8/watch/watch_test.go (about) 1 /******************************************************************************* 2 * Copyright (c) 2022 Genome Research Ltd. 3 * 4 * Author: Sendu Bala <sb10@sanger.ac.uk> 5 * 6 * Permission is hereby granted, free of charge, to any person obtaining 7 * a copy of this software and associated documentation files (the 8 * "Software"), to deal in the Software without restriction, including 9 * without limitation the rights to use, copy, modify, merge, publish, 10 * distribute, sublicense, and/or sell copies of the Software, and to 11 * permit persons to whom the Software is furnished to do so, subject to 12 * the following conditions: 13 * 14 * The above copyright notice and this permission notice shall be included 15 * in all copies or substantial portions of the Software. 16 * 17 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 18 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 19 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 20 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 21 * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 22 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 23 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 24 ******************************************************************************/ 25 26 package watch 27 28 import ( 29 "os" 30 "path/filepath" 31 "sync" 32 "testing" 33 "time" 34 35 . "github.com/smartystreets/goconvey/convey" 36 ) 37 38 // testMtimeTracker lets us test mtime updates in our WatcherCallback in a 39 // thread-safe way. 40 type testMtimeTracker struct { 41 sync.RWMutex 42 calls int 43 latest time.Time 44 } 45 46 // update increments calls and sets latest to the given mtime. 47 func (m *testMtimeTracker) update(mtime time.Time) { 48 m.Lock() 49 defer m.Unlock() 50 51 m.calls++ 52 m.latest = mtime 53 } 54 55 // report returns the latest mtime and true if there has both been a call to 56 // update() since the last call to report(), and the given time is older than 57 // the latest mtime. 58 func (m *testMtimeTracker) report(previous time.Time) (time.Time, bool) { 59 m.Lock() 60 defer m.Unlock() 61 62 calls := m.calls 63 m.calls = 0 64 65 return m.latest, calls == 1 && previous.Before(m.latest) 66 } 67 68 // numCalls tells you how many calls to update() there have been since the last 69 // call to report(). 70 func (m *testMtimeTracker) numCalls() int { 71 m.RLock() 72 defer m.RUnlock() 73 74 return m.calls 75 } 76 77 func TestWatch(t *testing.T) { 78 pollFrequency := 10 * time.Millisecond 79 80 Convey("Given a file to watch", t, func() { 81 // lustre can record mtimes earlier than local time, so our 'before' 82 // time has to be even more before 83 before := time.Now().Add(-1 * time.Second) 84 85 path := createTestFile(t) 86 87 Convey("You can create a watcher, which immediately finds the file's mtime", func() { 88 tracker := &testMtimeTracker{} 89 90 cb := func(mtime time.Time) { 91 tracker.update(mtime) 92 } 93 94 w, err := New(path, cb, pollFrequency) 95 So(err, ShouldBeNil) 96 defer w.Stop() 97 98 calls := tracker.numCalls() 99 So(calls, ShouldEqual, 0) 100 _, ok := tracker.report(before) 101 So(ok, ShouldBeFalse) 102 103 latest := w.Mtime() 104 So(latest.After(before), ShouldBeTrue) 105 106 Convey("Changing the file's mtime calls cb after some time", func() { 107 <-time.After(2 * pollFrequency) 108 calls := tracker.numCalls() 109 So(calls, ShouldEqual, 0) 110 _, ok = tracker.report(latest) 111 So(ok, ShouldBeFalse) 112 113 touchTestFile(path) 114 <-time.After(2 * pollFrequency) 115 116 latest, ok = tracker.report(latest) 117 So(ok, ShouldBeTrue) 118 119 Convey("Stop() ends the polling", func() { 120 w.Stop() 121 tracker.report(latest) 122 calls := tracker.numCalls() 123 So(calls, ShouldEqual, 0) 124 <-time.After(100 * time.Millisecond) 125 126 touchTestFile(path) 127 <-time.After(2 * pollFrequency) 128 129 calls = tracker.numCalls() 130 So(calls, ShouldEqual, 0) 131 _, ok = tracker.report(latest) 132 So(ok, ShouldBeFalse) 133 }) 134 }) 135 }) 136 }) 137 138 Convey("You can't create a watcher with a bad file", t, func() { 139 w, err := New("/foo£@£$%", func(time.Time) {}, pollFrequency) 140 So(err, ShouldNotBeNil) 141 So(w, ShouldBeNil) 142 }) 143 } 144 145 // createTestFile creates a file to test with that will be auto-cleaned up after 146 // the test. Returns its path. 147 func createTestFile(t *testing.T) string { 148 t.Helper() 149 150 dir := t.TempDir() 151 path := filepath.Join(dir, "file") 152 f, err := os.Create(path) 153 So(err, ShouldBeNil) 154 err = f.Close() 155 So(err, ShouldBeNil) 156 157 return path 158 } 159 160 // touchTestFile modifies path's a and mtime to the current time. 161 func touchTestFile(path string) { 162 now := time.Now().Local() 163 err := os.Chtimes(path, now, now) 164 So(err, ShouldBeNil) 165 }