File: evented_file_update_checker_test.rb

package info (click to toggle)
rails 2%3A7.2.2.1%2Bdfsg-7
  • links: PTS, VCS
  • area: main
  • in suites: trixie
  • size: 43,352 kB
  • sloc: ruby: 349,799; javascript: 30,703; yacc: 46; sql: 43; sh: 29; makefile: 27
file content (180 lines) | stat: -rw-r--r-- 4,592 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
# frozen_string_literal: true

require_relative "abstract_unit"
require "pathname"
require "weakref"
require_relative "file_update_checker_shared_tests"

class EventedFileUpdateCheckerTest < ActiveSupport::TestCase
  include FileUpdateCheckerSharedTests

  def setup
    skip if ENV["LISTEN"] == "0"
    require "listen"
    super
  end

  def new_checker(files = [], dirs = {}, &block)
    ActiveSupport::EventedFileUpdateChecker.new(files, dirs, &block).tap do |c|
      wait
    end
  end

  def teardown
    super
    Listen.stop
  end

  def wait
    sleep 0.5
  end

  def mkdir(dirs)
    super
    wait # wait for the events to fire
  end

  def touch(files)
    super
    wait # wait for the events to fire
  end

  def rm_f(files)
    super
    wait # wait for the events to fire
  end

  test "notifies forked processes" do
    skip "Forking not available" unless Process.respond_to?(:fork)

    FileUtils.touch(tmpfiles)

    checker = new_checker(tmpfiles) { }
    assert_not_predicate checker, :updated?

    # Pipes used for flow control across fork.
    boot_reader, boot_writer = IO.pipe
    touch_reader, touch_writer = IO.pipe
    result_reader, result_writer = IO.pipe

    pid = fork do
      assert_not_predicate checker, :updated?

      # The listen gem start multiple background threads that need to reach a ready state.
      # Unfortunately, it doesn't look like there is a clean way to block until they are ready.
      wait

      # Fork is booted, ready for file to be touched
      # notify parent process.
      boot_writer.write("booted")

      # Wait for parent process to signal that file
      # has been touched.
      IO.select([touch_reader])

      assert_predicate checker, :updated?
    rescue Exception => ex
      result_writer.write("#{ex.class.name}: #{ex.message}")
      raise
    ensure
      result_writer.close
    end

    assert pid

    result_writer.close

    # Wait for fork to be booted before touching files.
    IO.select([boot_reader])
    touch(tmpfiles)

    # Notify fork that files have been touched.
    touch_writer.write("touched")

    assert_predicate checker, :updated?

    Process.wait(pid)

    assert_equal "", result_reader.read
  end

  test "can be garbage collected" do
    # Use a separate thread to isolate objects and ensure they will be garbage collected.
    checker_ref, listener_threads = Thread.new do
      threads_before_checker = Thread.list
      checker = ActiveSupport::EventedFileUpdateChecker.new([], tmpdir => ".rb") { }

      # Wait for listener thread to start processing events.
      wait

      [WeakRef.new(checker), Thread.list - threads_before_checker]
    end.value

    GC.start
    listener_threads.each { |t| t.join(1) }

    assert_not checker_ref.weakref_alive?, "EventedFileUpdateChecker was not garbage collected"
    assert_empty Thread.list & listener_threads
  end

  test "should detect changes through symlink" do
    actual_dir = File.join(tmpdir, "actual")
    linked_dir = File.join(tmpdir, "linked")

    Dir.mkdir(actual_dir)
    FileUtils.ln_s(actual_dir, linked_dir)

    checker = new_checker([], linked_dir => ".rb") { }

    assert_not_predicate checker, :updated?

    touch(File.join(actual_dir, "a.rb"))

    assert_predicate checker, :updated?
    assert checker.execute_if_updated
  end

  test "updated should become true when nonexistent directory is added later" do
    watched_dir = File.join(tmpdir, "app")
    unwatched_dir = File.join(tmpdir, "node_modules")
    not_exist_watched_dir = File.join(tmpdir, "test")

    Dir.mkdir(watched_dir)
    Dir.mkdir(unwatched_dir)

    checker = new_checker([], watched_dir => ".rb", not_exist_watched_dir => ".rb") { }

    touch(File.join(watched_dir, "a.rb"))
    assert_predicate checker, :updated?
    assert checker.execute_if_updated

    Dir.mkdir(not_exist_watched_dir)
    wait
    assert_predicate checker, :updated?
    assert checker.execute_if_updated

    touch(File.join(unwatched_dir, "a.rb"))
    assert_not_predicate checker, :updated?
    assert_not checker.execute_if_updated
  end

  test "does not stop other checkers when nonexistent directory is added later" do
    dir1 = File.join(tmpdir, "app")
    dir2 = File.join(tmpdir, "test")

    Dir.mkdir(dir2)

    checker1 = new_checker([], dir1 => ".rb") { }
    checker2 = new_checker([], dir2 => ".rb") { }

    Dir.mkdir(dir1)

    touch(File.join(dir1, "a.rb"))
    assert_predicate checker1, :updated?

    assert_not_predicate checker2, :updated?

    touch(File.join(dir2, "a.rb"))
    assert_predicate checker2, :updated?
  end
end