A lightweight mechanism to provide an *instant kickstart* to a Go web server instance upon changing any Go source files under the project directory (and its subdirectories).

integration_darwin_test.go 3.9KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147
  1. // Copyright 2016 The Go Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style
  3. // license that can be found in the LICENSE file.
  4. package fsnotify
  5. import (
  6. "os"
  7. "path/filepath"
  8. "testing"
  9. "time"
  10. "golang.org/x/sys/unix"
  11. )
  12. // testExchangedataForWatcher tests the watcher with the exchangedata operation on macOS.
  13. //
  14. // This is widely used for atomic saves on macOS, e.g. TextMate and in Apple's NSDocument.
  15. //
  16. // See https://developer.apple.com/library/mac/documentation/Darwin/Reference/ManPages/man2/exchangedata.2.html
  17. // Also see: https://github.com/textmate/textmate/blob/cd016be29489eba5f3c09b7b70b06da134dda550/Frameworks/io/src/swap_file_data.cc#L20
  18. func testExchangedataForWatcher(t *testing.T, watchDir bool) {
  19. // Create directory to watch
  20. testDir1 := tempMkdir(t)
  21. // For the intermediate file
  22. testDir2 := tempMkdir(t)
  23. defer os.RemoveAll(testDir1)
  24. defer os.RemoveAll(testDir2)
  25. resolvedFilename := "TestFsnotifyEvents.file"
  26. // TextMate does:
  27. //
  28. // 1. exchangedata (intermediate, resolved)
  29. // 2. unlink intermediate
  30. //
  31. // Let's try to simulate that:
  32. resolved := filepath.Join(testDir1, resolvedFilename)
  33. intermediate := filepath.Join(testDir2, resolvedFilename+"~")
  34. // Make sure we create the file before we start watching
  35. createAndSyncFile(t, resolved)
  36. watcher := newWatcher(t)
  37. // Test both variants in isolation
  38. if watchDir {
  39. addWatch(t, watcher, testDir1)
  40. } else {
  41. addWatch(t, watcher, resolved)
  42. }
  43. // Receive errors on the error channel on a separate goroutine
  44. go func() {
  45. for err := range watcher.Errors {
  46. t.Fatalf("error received: %s", err)
  47. }
  48. }()
  49. // Receive events on the event channel on a separate goroutine
  50. eventstream := watcher.Events
  51. var removeReceived counter
  52. var createReceived counter
  53. done := make(chan bool)
  54. go func() {
  55. for event := range eventstream {
  56. // Only count relevant events
  57. if event.Name == filepath.Clean(resolved) {
  58. if event.Op&Remove == Remove {
  59. removeReceived.increment()
  60. }
  61. if event.Op&Create == Create {
  62. createReceived.increment()
  63. }
  64. }
  65. t.Logf("event received: %s", event)
  66. }
  67. done <- true
  68. }()
  69. // Repeat to make sure the watched file/directory "survives" the REMOVE/CREATE loop.
  70. for i := 1; i <= 3; i++ {
  71. // The intermediate file is created in a folder outside the watcher
  72. createAndSyncFile(t, intermediate)
  73. // 1. Swap
  74. if err := unix.Exchangedata(intermediate, resolved, 0); err != nil {
  75. t.Fatalf("[%d] exchangedata failed: %s", i, err)
  76. }
  77. time.Sleep(50 * time.Millisecond)
  78. // 2. Delete the intermediate file
  79. err := os.Remove(intermediate)
  80. if err != nil {
  81. t.Fatalf("[%d] remove %s failed: %s", i, intermediate, err)
  82. }
  83. time.Sleep(50 * time.Millisecond)
  84. }
  85. // We expect this event to be received almost immediately, but let's wait 500 ms to be sure
  86. time.Sleep(500 * time.Millisecond)
  87. // The events will be (CHMOD + REMOVE + CREATE) X 2. Let's focus on the last two:
  88. if removeReceived.value() < 3 {
  89. t.Fatal("fsnotify remove events have not been received after 500 ms")
  90. }
  91. if createReceived.value() < 3 {
  92. t.Fatal("fsnotify create events have not been received after 500 ms")
  93. }
  94. watcher.Close()
  95. t.Log("waiting for the event channel to become closed...")
  96. select {
  97. case <-done:
  98. t.Log("event channel closed")
  99. case <-time.After(2 * time.Second):
  100. t.Fatal("event stream was not closed after 2 seconds")
  101. }
  102. }
  103. // TestExchangedataInWatchedDir test exchangedata operation on file in watched dir.
  104. func TestExchangedataInWatchedDir(t *testing.T) {
  105. testExchangedataForWatcher(t, true)
  106. }
  107. // TestExchangedataInWatchedDir test exchangedata operation on watched file.
  108. func TestExchangedataInWatchedFile(t *testing.T) {
  109. testExchangedataForWatcher(t, false)
  110. }
  111. func createAndSyncFile(t *testing.T, filepath string) {
  112. f1, err := os.OpenFile(filepath, os.O_WRONLY|os.O_CREATE, 0666)
  113. if err != nil {
  114. t.Fatalf("creating %s failed: %s", filepath, err)
  115. }
  116. f1.Sync()
  117. f1.Close()
  118. }