package reloader import ( "fmt" "io/ioutil" "os" "reflect" "testing" "time" ) func noop([]byte, error) error { return nil } func TestNoStat(t *testing.T) { filename := os.TempDir() + "/doesntexist.123456789" _, err := New(filename, noop) if err == nil { t.Fatalf("Expected New to return error when the file doesn't exist.") } } func TestNoRead(t *testing.T) { f, _ := ioutil.TempFile("", "test-no-read.txt") defer os.Remove(f.Name()) f.Chmod(0) // no read permissions _, err := New(f.Name(), noop) if err == nil { t.Fatalf("Expected New to return error when permission denied.") } } func TestFirstError(t *testing.T) { f, _ := ioutil.TempFile("", "test-first-error.txt") defer os.Remove(f.Name()) _, err := New(f.Name(), func([]byte, error) error { return fmt.Errorf("i die") }) if err == nil { t.Fatalf("Expected New to return error when the callback returned error the first time.") } } func TestFirstSuccess(t *testing.T) { f, _ := ioutil.TempFile("", "test-first-success.txt") defer os.Remove(f.Name()) r, err := New(f.Name(), func([]byte, error) error { return nil }) if err != nil { t.Errorf("Expected New to succeed, got %s", err) } r.Stop() } // Override makeTicker for testing. // Returns a channel on which to send artificial ticks, and a function to // restore the default makeTicker. func makeFakeMakeTicker() (chan<- time.Time, func()) { origMakeTicker := makeTicker fakeTickChan := make(chan time.Time) makeTicker = func() (func(), <-chan time.Time) { return func() {}, fakeTickChan } return fakeTickChan, func() { makeTicker = origMakeTicker } } func TestReload(t *testing.T) { // Mock out makeTicker fakeTick, restoreMakeTicker := makeFakeMakeTicker() defer restoreMakeTicker() f, _ := ioutil.TempFile("", "test-reload.txt") filename := f.Name() defer os.Remove(filename) f.Write([]byte("first body")) f.Close() var bodies []string reloads := make(chan []byte, 1) r, err := New(filename, func(b []byte, err error) error { if err != nil { t.Fatalf("Got error in callback: %s", err) } bodies = append(bodies, string(b)) reloads <- b return nil }) if err != nil { t.Fatalf("Expected New to succeed, got %s", err) } defer r.Stop() expected := []string{"first body"} if !reflect.DeepEqual(bodies, expected) { t.Errorf("Expected bodies = %#v, got %#v", expected, bodies) } fakeTick <- time.Now() <-reloads if !reflect.DeepEqual(bodies, expected) { t.Errorf("Expected bodies = %#v, got %#v", expected, bodies) } // Write to the file, expect a reload. Sleep a few milliseconds first so the // timestamps actually differ. time.Sleep(15 * time.Millisecond) ioutil.WriteFile(filename, []byte("second body"), 0644) fakeTick <- time.Now() <-reloads expected = []string{"first body", "second body"} if !reflect.DeepEqual(bodies, expected) { t.Errorf("Expected bodies = %#v, got %#v", expected, bodies) } // Send twice on this blocking channel to make sure we go through at least on // iteration of the reloader's loop. fakeTick <- time.Now() fakeTick <- time.Now() if !reflect.DeepEqual(bodies, expected) { t.Errorf("Expected bodies = %#v, got %#v", expected, bodies) } } func TestReloadFailure(t *testing.T) { // Mock out makeTicker fakeTick, restoreMakeTicker := makeFakeMakeTicker() f, _ := ioutil.TempFile("", "test-reload-failure.txt") filename := f.Name() defer func() { restoreMakeTicker() os.Remove(filename) }() f.Write([]byte("first body")) f.Close() type res struct { b []byte err error } reloads := make(chan res, 1) _, err := New(filename, func(b []byte, err error) error { reloads <- res{b, err} return nil }) if err != nil { t.Fatalf("Expected New to succeed.") } <-reloads os.Remove(filename) fakeTick <- time.Now() select { case r := <-reloads: if r.err == nil { t.Errorf("Expected error trying to read missing file.") } case <-time.After(5 * time.Second): t.Fatalf("timed out waiting for reload") } time.Sleep(15 * time.Millisecond) // Create a file with no permissions ioutil.WriteFile(filename, []byte("second body"), 0) fakeTick <- time.Now() select { case r := <-reloads: if r.err == nil { t.Errorf("Expected error trying to read file with no permissions.") } case <-time.After(5 * time.Second): t.Fatalf("timed out waiting for reload") } err = os.Remove(filename) if err != nil { t.Fatal(err) } err = ioutil.WriteFile(filename, []byte("third body"), 0644) if err != nil { t.Fatal(err) } fakeTick <- time.Now() select { case r := <-reloads: if r.err != nil { t.Errorf("Unexpected error: %s", err) } if string(r.b) != "third body" { t.Errorf("Expected 'third body' reading file after restoring it.") } return case <-time.After(5 * time.Second): t.Fatalf("timed out waiting for successful reload") } }