listen-compat 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: bac4b0ee225554a721aaa8daada780e569fbdc8c
4
+ data.tar.gz: 035bc356f079cea0a7ded926c471ac93a7cc8435
5
+ SHA512:
6
+ metadata.gz: b8b7b7f57c0011f7e6cbb19c902fb1ad1a15e801d4ceca0a3f0df519c1ec89122991f9855cc81e8314fdc1a6d83dae28d205ff1208fc2250fea58a1f1656aa0e
7
+ data.tar.gz: 7b8c356db968632ae02e95c6348e0cfc67efd9de70d3644c362b51f9d8cfc9f2ed3f5e41d6341ae811c1a4b28a6d1d5aa18ae1676f57cc26ce38ed6c34f491f8
@@ -0,0 +1,14 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ *.bundle
11
+ *.so
12
+ *.o
13
+ *.a
14
+ mkmf.log
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --color
2
+ --require spec_helper
3
+ --format doc
@@ -0,0 +1,2 @@
1
+ inherit_from:
2
+ - .rubocop_todo.yml
@@ -0,0 +1,14 @@
1
+ # This configuration was generated by `rubocop --auto-gen-config`
2
+ # on 2014-11-27 16:11:17 +0100 using RuboCop version 0.27.1.
3
+ # The point is for the user to remove these configuration records
4
+ # one by one as the offenses are removed from the code base.
5
+ # Note that changes in the inspected code, or installation of new
6
+ # versions of RuboCop, may require this file to be generated again.
7
+
8
+ # Offense count: 1
9
+ Lint/HandleExceptions:
10
+ Enabled: false
11
+
12
+ # Offense count: 6
13
+ Style/Documentation:
14
+ Enabled: false
data/Gemfile ADDED
@@ -0,0 +1,8 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
4
+
5
+ group :development do
6
+ gem 'rspec', require: false
7
+ gem 'rubocop', require: false
8
+ end
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Cezary Baginski
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,139 @@
1
+ # Listen::Compat
2
+
3
+ A wrapper for [Listen](https://github.com/guard/listen) to "guarantee" a
4
+ simplified and unchanging (future-compatible) API for cross-platform watching
5
+ files and directories.
6
+
7
+ It is designed to work with any version of Listen installed (it contains
8
+ workarounds for buggy or incomplete old versions).
9
+
10
+ This is useful for app/gem developers who want to "just watch files until
11
+ Ctrl-C". (And not care about historical incompatibilities or bug fixes /
12
+ regressions related to Listen).
13
+
14
+ It also helps easily write unit tests for using listen in other apps, without
15
+ having to deal with threads, locks, queues, sleeping, Listen API changes, etc.
16
+
17
+ ## Installation
18
+
19
+ As long as users of you application have *any* version of Listen installed,
20
+ listen-compat should work.
21
+
22
+ Example Gemfile:
23
+
24
+ ```ruby
25
+ gem 'listen-compat'
26
+ gem 'listen' # hopefully, any version you like will work
27
+ ```
28
+
29
+ ## Usage
30
+
31
+ You can assume the following interface will never change.
32
+
33
+
34
+ ```ruby
35
+ require 'listen/compat/wrapper'
36
+
37
+ listener = Listen::Compat::Wrapper.create
38
+
39
+ directories = %w(foo bar baz)
40
+ options = { force_polling: false }
41
+
42
+ listener.listen(directories, options) do |modified, added, removed|
43
+ puts "Modified: #{modified.inspect}"
44
+ puts "Added: #{added.inspect}"
45
+ puts "Removed: #{removed.inspect}"
46
+ end
47
+ ```
48
+
49
+ (You can assume compatibility will become better and more robust without having
50
+ to make any changes in your app's code.)
51
+
52
+
53
+ ## Details
54
+
55
+ This will always be guaranteed to include everything necessary:
56
+
57
+ ```ruby
58
+ require 'listen/compat/wrapper'
59
+ ```
60
+
61
+ The following will do whatever magic necessary to find a usable version of
62
+ Listen, require it, and return the right wrapper.
63
+
64
+
65
+ ```ruby
66
+ listener = Listen::Compat::Wrapper.create
67
+ ```
68
+
69
+ You can assume directories can be passed in any form (relative, absolute,
70
+ Pathname, real-only directories) and any encoding and this gem's
71
+ responsibility is to deal with it.
72
+
73
+ ```ruby
74
+ directories = %w(foo bar baz)
75
+ ```
76
+
77
+ While different version of listen support different options, it's
78
+ Listen-Compat's responsibility to make sure they are properly translated or
79
+ ignored, and possible to set without changes in your code (e.g. environment
80
+ variables, listen config files, etc.)
81
+
82
+ ```ruby
83
+ options = { force_polling: false }
84
+ ```
85
+
86
+ You can assume your callback will always receive an array of 3 arrays:
87
+ - possibly changed files (but maybe no longer existing)
88
+ - possibly added files (but maybe no longer existing)
89
+ - possibly removed files (but may be existing again)
90
+
91
+
92
+ ```ruby
93
+ listener.listen(directories, options) do |modified, added, removed|
94
+ ```
95
+
96
+ ## Unit testing Listen in you app
97
+
98
+ Listen-compat provides a fast-enough and thorough enough test helper for you to
99
+ just add a single unit test to accurately simulate a real blocking session with
100
+ Listen.
101
+
102
+ It also lets you conveniently use relative files for simulating events (even
103
+ though the actual Listen implementation reports full paths).
104
+
105
+
106
+ ```ruby
107
+ require "listen/compat/test/session"
108
+
109
+ def test_if_watching_files_works
110
+
111
+ session = Listen::Compat::Test::Session.new do
112
+ # put whatever code would cause Listen to block here:
113
+ myapp.start_listening_for_changes
114
+ end
115
+
116
+ # simulate changes
117
+ session.simulate_events(["foo.png"], [], [])
118
+
119
+ # simulate the user stopping the listening with Ctrl-C
120
+ session.interrupt
121
+
122
+ # whatever tests to make you app's callback was called:
123
+ assert_equal(%w(foo.png), myapp.updated_files_or_something)
124
+ end
125
+ ```
126
+
127
+ ## Summary
128
+
129
+ By using the above interface and the single using test above, you shouldn't
130
+ have to care about anything else related about how Listen works with your app.
131
+
132
+
133
+ ## Contributing
134
+
135
+ 1. Fork it ( https://github.com/[my-github-username]/listen-compat/fork )
136
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
137
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
138
+ 4. Push to the branch (`git push origin my-new-feature`)
139
+ 5. Create a new Pull Request
@@ -0,0 +1,10 @@
1
+ require 'bundler/gem_tasks'
2
+
3
+ require 'rspec/core/rake_task'
4
+ RSpec::Core::RakeTask.new(:spec) do |t|
5
+ t.verbose = false unless ENV.key?('CI')
6
+ end
7
+
8
+ require 'rubocop/rake_task'
9
+ RuboCop::RakeTask.new(:rubocop)
10
+ task default: [:spec, :rubocop]
@@ -0,0 +1,12 @@
1
+ require 'listen/compat/wrapper'
2
+
3
+ listener = Listen::Compat::Wrapper.create
4
+
5
+ directories = %w(lib pkg spec)
6
+ options = { force_polling: false }
7
+
8
+ listener.listen(directories, options) do |modified, added, removed|
9
+ puts "Modified: #{modified.inspect}"
10
+ puts "Added: #{added.inspect}"
11
+ puts "Removed: #{removed.inspect}"
12
+ end
@@ -0,0 +1,2 @@
1
+ require 'listen/compat/version'
2
+ require 'listen/compat/wrapper'
@@ -0,0 +1 @@
1
+ require 'listen/compat/test/session'
@@ -0,0 +1,61 @@
1
+ require 'listen/compat/wrapper'
2
+
3
+ module Listen
4
+ module Compat
5
+ module Test
6
+ class Fake < Listen::Compat::Wrapper::Common
7
+ def self.fire_events(thread, *args)
8
+ processed = _processed(thread)
9
+ processed.pop until processed.empty?
10
+ _events(thread) << args
11
+ processed.pop
12
+ end
13
+
14
+ def self.collect_instances(thread)
15
+ return [] if _instances(thread).empty?
16
+ result = []
17
+ result << _instances(thread).pop until _instances(thread).empty?
18
+ result
19
+ end
20
+
21
+ attr_reader :directories
22
+
23
+ def initialize
24
+ thread = Thread.current
25
+
26
+ thread[:fake_instances] = Queue.new
27
+ thread[:fake_events] = Queue.new
28
+ thread[:fake_processed_events] = Queue.new
29
+
30
+ Fake._instances(thread) << self
31
+ end
32
+
33
+ private
34
+
35
+ def _start_and_wait(*args, &block)
36
+ @directories = args[0..-2]
37
+ loop do
38
+ ev = Fake._events(Thread.current).pop
39
+ block.call(*ev)
40
+ Fake._processed(Thread.current) << ev
41
+ end
42
+ end
43
+
44
+ def _stop
45
+ end
46
+
47
+ def self._processed(thread)
48
+ thread[:fake_processed_events]
49
+ end
50
+
51
+ def self._events(thread)
52
+ thread[:fake_events]
53
+ end
54
+
55
+ def self._instances(thread)
56
+ thread[:fake_instances]
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,70 @@
1
+ require 'listen/compat/wrapper'
2
+ require 'listen/compat/test/fake'
3
+ require 'listen/compat/test/simple'
4
+
5
+ module Listen
6
+ module Compat
7
+ module Test
8
+ # Class for conveniently simulating interaction with Listen
9
+ class Session
10
+ # Calls the potentially blocking given block in a background thread
11
+ def initialize(wrapper_class = nil, &block)
12
+ Wrapper.wrapper_class = wrapper_class || Listen::Compat::Test::Fake
13
+ Wrapper.listen_module = Listen::Compat::Test::Simple
14
+
15
+ fail 'No block given!' unless block_given?
16
+
17
+ @thread = Thread.new { _supervise(&block) }
18
+ end
19
+
20
+ # Simulate a Ctrl-C from the user
21
+ def interrupt
22
+ _wait_until_ready
23
+ @thread.raise Interrupt
24
+ @thread.join
25
+ end
26
+
27
+ # Simulate Listen events you want passed asynchronously to your callback
28
+ def simulate_events(modified, added, removed)
29
+ _wait_until_ready
30
+ Fake.fire_events(@thread, *_events(modified, added, removed))
31
+ end
32
+
33
+ # Return a list of fake Listen instances actually created
34
+ def instances
35
+ @instances ||= Fake.collect_instances(@thread)
36
+ end
37
+
38
+ private
39
+
40
+ def _supervise(&block)
41
+ block.call
42
+ rescue StandardError => e
43
+ msg = "\n\nERROR: Watched listen thread failed: %s: \n%s"
44
+ STDERR.puts format(msg, e.message, e.backtrace * "\n")
45
+ raise
46
+ end
47
+
48
+ def _events(modified, added, removed)
49
+ [_abs_paths(modified), _abs_paths(added), _abs_paths(removed)]
50
+ end
51
+
52
+ def _abs_paths(paths)
53
+ paths.map { |path| ::File.expand_path(path) }
54
+ end
55
+
56
+ def _wait_until_ready
57
+ sleep 0.1
58
+ sleep 0.1 while @thread.status == 'running'
59
+
60
+ # Show error on crashes
61
+ @thread.join if @thread.status.nil?
62
+
63
+ return if @thread.status == 'sleep'
64
+
65
+ fail "Unexpected thread state: #{@thread.status.inspect}"
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,15 @@
1
+ module Listen
2
+ module Compat
3
+ module Test
4
+ # Simple stub for a real Listen instance, which just forwards events
5
+ class Simple
6
+ def self.to(*_args)
7
+ Simple.new
8
+ end
9
+
10
+ def start
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,5 @@
1
+ module Listen
2
+ module Compat
3
+ VERSION = '0.1.0'
4
+ end
5
+ end
@@ -0,0 +1,180 @@
1
+ require 'listen/compat/version' # just for convenience
2
+
3
+ module Listen
4
+ module Compat
5
+ # Tries to require Listen using rubygems or vendored version
6
+ module Loader
7
+ module_function
8
+
9
+ def load!
10
+ defined?(gem) ? try_rubygems : try_without_rubygems
11
+ end
12
+
13
+ def try_rubygems
14
+ gem 'listen', '>= 1.1.0', '< 3.0.0'
15
+ require 'listen'
16
+ rescue LoadError, Gem::LoadError => e
17
+ e.message.replace(format("%s\n%s", e.message, msg_about_gem_install))
18
+ raise
19
+ end
20
+
21
+ def compatible_version
22
+ !older_than_193? ? '~> 2.7' : '~> 1.1'
23
+ end
24
+
25
+ def older_than_193?
26
+ Gem::Version.new(RUBY_VERSION) >= Gem::Version.new('1.9.3')
27
+ end
28
+
29
+ def msg_about_gem_install
30
+ format("Run \"gem install listen --version '%s'\" to get it.",
31
+ compatible_version)
32
+ end
33
+ end
34
+
35
+ module Wrapper
36
+ class << self
37
+ attr_writer :listen_module
38
+ attr_accessor :wrapper_class
39
+
40
+ def listen_module
41
+ @listen_module ||= Listen
42
+ end
43
+ end
44
+
45
+ # History of bugs/workarounds:
46
+ #
47
+ # Ancient (< 2.0.0) - very old version
48
+ # - uses start!
49
+ # - polling method
50
+ # - start method blocks
51
+ # - fails on readonly directories and files
52
+ #
53
+ # Old (>= 2.0.0) - major API change
54
+ # - uses Celluloid, so shutdown/thread handling is different
55
+ # - start returns a thread (not sure if 2.0.0 or later)
56
+ # - sleep is needed to block
57
+ # = 2.7.6 - start() returns adapter thread (instead of wait_thread)
58
+ #
59
+ # Stale (>= 2.7.7) - broke mutliple dir handling on OSX (#243)
60
+ # - devious threads hack in Sass works by accident (!)
61
+ # = 2.7.11 - last version where start still returns a thread
62
+ #
63
+ # Current (>= 2.7.12)- start() no longer returns a thread
64
+ # - fixed multiple dir handling (#243)
65
+ # = 2.8.0 - current version
66
+
67
+ # "Expected" functionality from any Listen version
68
+ class Common
69
+ # Run listen continously to monitor changes and gracefully terminate
70
+ # on Ctrl-C
71
+ def listen(*args, &block)
72
+ _start_and_wait(*args, &block)
73
+ rescue Interrupt
74
+ _stop
75
+ end
76
+
77
+ protected
78
+
79
+ # Overridable method so a fake implementation can be used in tests
80
+ def _listen_module
81
+ Wrapper.listen_module
82
+ end
83
+
84
+ # Overridable method so a fake implementation can be used in tests
85
+ def _listen_class
86
+ _listen_module::Listener
87
+ end
88
+
89
+ private
90
+
91
+ # Run listen continuously, regardless whether it blocks or starts
92
+ # a background thread
93
+ def _start_and_wait(*args, &block)
94
+ _listen_module.to(*args, &block).start
95
+ sleep
96
+ end
97
+
98
+ # Gracefully shutdown Listen after a Ctrl-C, join threads, etc.
99
+ def _stop
100
+ _listen_module.stop
101
+ end
102
+ end
103
+
104
+ # Workarounds for pre 2.0 versions of Listen
105
+ class Ancient < Common
106
+ NEXT_VERSION = Gem::Version.new('2.0.0')
107
+
108
+ # A Listen version prior to 2.0 will write a test file to a directory
109
+ # to see if a watcher supports watching that directory. That breaks
110
+ # horribly on read-only directories, so we filter those out.
111
+ def watchable_directories(directories)
112
+ directories.select { |d| ::File.directory?(d) && ::File.writable?(d) }
113
+ end
114
+
115
+ def listen(*args, &block)
116
+ options = args.last.is_a?(Hash) ? args.pop : {}
117
+ # Note: force_polling is a method here (since Listen 2.0.0 it's an
118
+ # option passed to Listen.new)
119
+ poll = options[:force_polling]
120
+
121
+ directories = watchable_directories(args.flatten)
122
+
123
+ # Don't optimize this out because of Ruby 1.8
124
+ args = directories
125
+ args << options
126
+
127
+ listener = _listen_class.new(*args, &block)
128
+ listener.force_polling(true) if poll
129
+ listener.start!
130
+ rescue Interrupt
131
+ end
132
+ end
133
+
134
+ # >= 2.0.0, <= 2.7.6
135
+ class Old < Common
136
+ NEXT_VERSION = Gem::Version.new('2.7.7')
137
+ end
138
+
139
+ # >= 2.7.7, <= 2.7.11
140
+ class Stale < Common
141
+ NEXT_VERSION = Gem::Version.new('2.7.12')
142
+
143
+ # Work around guard/listen#243 (>= v2.7.9, < v2.8.0)
144
+ def _start_and_wait(*args, &block)
145
+ options = args.pop if args.last.is_a?(Hash)
146
+ listeners = args.map do |dir|
147
+ _listen_module.to(dir, options, &block)
148
+ end
149
+ listeners.map(&:start)
150
+ sleep
151
+ end
152
+ end
153
+
154
+ # >= 2.7.12, <= 2.8.0
155
+ class Current < Common
156
+ NEXT_VERSION = Gem::Version.new('2.99.99')
157
+ end
158
+
159
+ # Returns a wrapper matching the listen version
160
+ # @param version_string overrides detection (e.g. for testing)
161
+ def self.create(version_string = nil)
162
+ return Wrapper.wrapper_class.new if Wrapper.wrapper_class
163
+
164
+ version = Gem::Version.new(version_string || _detect_listen_version)
165
+
166
+ [Ancient, Old, Stale, Current].each do |klass|
167
+ return klass.new if version < klass.const_get('NEXT_VERSION')
168
+ end
169
+ end
170
+
171
+ private
172
+
173
+ def self._detect_listen_version
174
+ Loader.load!
175
+ require 'listen/version'
176
+ Listen::VERSION
177
+ end
178
+ end
179
+ end
180
+ end
@@ -0,0 +1,25 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'listen/compat/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'listen-compat'
8
+ spec.version = Listen::Compat::VERSION
9
+ spec.authors = ['Cezary Baginski']
10
+ spec.email = ['cezary@chronomantic.net']
11
+ spec.summary = 'Simplified compatibility layer for Listen gem'
12
+ spec.description = 'For developers to have a minimal, guaranteed API for \
13
+ using Listen'
14
+
15
+ spec.homepage = 'https://github.com/guard/listen-compat'
16
+ spec.license = 'MIT'
17
+
18
+ spec.files = `git ls-files -z`.split("\x0")
19
+ spec.executables = spec.files.grep(/^bin\//) { |f| File.basename(f) }
20
+ spec.test_files = spec.files.grep(/^(test|spec|features)\//)
21
+ spec.require_paths = ['lib']
22
+
23
+ spec.add_development_dependency 'bundler', '~> 1.7'
24
+ spec.add_development_dependency 'rake', '~> 10.0'
25
+ end
@@ -0,0 +1,39 @@
1
+ require 'listen/compat/test/session'
2
+
3
+ class MyExampleApp
4
+ attr_reader :changed
5
+
6
+ def start_listening_for_changes
7
+ listener = Listen::Compat::Wrapper.create
8
+
9
+ directories = %w(foo bar baz)
10
+ options = { force_polling: false }
11
+
12
+ listener.listen(directories, options) do |modified, _added, _removed|
13
+ @changed ||= []
14
+ @changed += modified.map do |full_path|
15
+ Pathname(full_path).relative_path_from(Pathname.pwd).to_s
16
+ end
17
+ end
18
+ end
19
+ end
20
+
21
+ RSpec.describe MyExampleApp do
22
+ it 'works' do
23
+ myapp = MyExampleApp.new
24
+
25
+ session = Listen::Compat::Test::Session.new do
26
+ # put whatever code would cause Listen to block here:
27
+ myapp.start_listening_for_changes
28
+ end
29
+
30
+ # simulate changes
31
+ session.simulate_events(['foo.png'], [], [])
32
+
33
+ # simulate the user stopping the listening with Ctrl-C
34
+ session.interrupt
35
+
36
+ # whatever tests to make you app's callback was called:
37
+ expect(myapp.changed).to eq(%w(foo.png))
38
+ end
39
+ end
@@ -0,0 +1,228 @@
1
+ require 'fileutils'
2
+ require 'pathname'
3
+ require 'tmpdir'
4
+
5
+ require 'listen/compat/wrapper'
6
+
7
+ # TODO: since we're using RSpec now, this is not necessary
8
+ module MockListen
9
+ class Listener
10
+ def initialize(*args)
11
+ @calls = Queue.new
12
+ @calls << [__method__, *args]
13
+ MockListen.add_instance(self)
14
+ end
15
+
16
+ def method_missing(meth, *args, &_block)
17
+ @calls << [meth, *args]
18
+ end
19
+
20
+ def calls
21
+ MockListen.dump(@calls)
22
+ end
23
+ end
24
+
25
+ class << self
26
+ def dump(queue)
27
+ res = []
28
+ res << queue.pop until queue.empty?
29
+ res
30
+ end
31
+
32
+ def setup_for_tests
33
+ Listen::Compat::Wrapper.listen_module = MockListen
34
+ Listen::Compat::Wrapper.wrapper_class = nil # autodetect
35
+ @calls = Queue.new
36
+ @mocks = Queue.new
37
+ @responses = {}
38
+ @called = []
39
+ end
40
+
41
+ def reset_for_tests
42
+ instance_variables.each do |var|
43
+ instance_variable_set(var, :unset)
44
+ end
45
+ Listen::Compat::Wrapper.listen_module = nil
46
+ Listen::Compat::Wrapper.wrapper_class = nil
47
+ end
48
+
49
+ # setup actions
50
+ attr_reader :responses
51
+
52
+ def add_instance(obj)
53
+ @mocks << obj
54
+ end
55
+
56
+ # Get results
57
+ def instances
58
+ MockListen.dump(@mocks)
59
+ end
60
+
61
+ def calls
62
+ MockListen.dump(@calls)
63
+ end
64
+
65
+ def method_missing(meth, *args, &block)
66
+ @calls << [meth, *args]
67
+ block = responses[meth]
68
+ block.call(*args) unless block.nil?
69
+ end
70
+ end
71
+ end
72
+
73
+ module DelayedInterruptHelper
74
+ def delayed_interrupt(&block)
75
+ th = Thread.new(&block)
76
+ sleep 0.1
77
+ sleep 0.1 while (status = th.status) == 'running'
78
+ th.raise Interrupt
79
+ th.join
80
+ status
81
+ end
82
+ end
83
+
84
+ RSpec.describe Listen::Compat::Wrapper::Ancient do
85
+ let(:wrapper) { Listen::Compat::Wrapper.create('0.1.0') }
86
+
87
+ before do
88
+ MockListen.setup_for_tests
89
+ MockListen.responses[:start!] = proc { fail Interrupt }
90
+ end
91
+
92
+ after do
93
+ MockListen.reset_for_tests
94
+ end
95
+
96
+ it { is_expected.to be_a described_class }
97
+
98
+ it 'readonly dirs are avoided' do
99
+ tmpdir, result = Dir.mktmpdir do |dir|
100
+ ro_dir = File.join(dir, 'foo')
101
+ FileUtils.mkdir(ro_dir, mode: 0444)
102
+ [dir, wrapper.watchable_directories([dir, ro_dir, '.'])]
103
+ end
104
+
105
+ expect(result).to eq([tmpdir, '.'])
106
+ end
107
+
108
+ it 'passes parameters to listen' do
109
+ wrapper.listen('.', {})
110
+ expect(MockListen.instances[0].calls[0]).to eq([:initialize, '.', {}])
111
+ end
112
+
113
+ it 'calls start' do
114
+ wrapper.listen('.', {})
115
+ expect(MockListen.instances[0].calls[1]).to eq([:start!])
116
+ end
117
+
118
+ it 'does not call stop' do
119
+ wrapper.listen('.', {})
120
+ expect(MockListen.calls).to eq([])
121
+ end
122
+ end
123
+
124
+ RSpec.describe Listen::Compat::Wrapper::Old do
125
+ include DelayedInterruptHelper
126
+
127
+ let(:wrapper) { Listen::Compat::Wrapper.create('2.7.6') }
128
+
129
+ before do
130
+ MockListen.setup_for_tests
131
+ MockListen.responses[:to] = proc { |*args| MockListen::Listener.new(*args) }
132
+ end
133
+
134
+ after do
135
+ MockListen.reset_for_tests
136
+ end
137
+
138
+ it { is_expected.to be_a described_class }
139
+
140
+ it 'passes parameters to listen' do
141
+ delayed_interrupt { wrapper.listen('.', {}) }
142
+ expect(MockListen.calls[0]).to eq([:to, '.', {}])
143
+ end
144
+
145
+ it 'calls start' do
146
+ delayed_interrupt { wrapper.listen('.', {}) }
147
+ expect(MockListen.instances[0].calls[1]).to eq([:start])
148
+ end
149
+
150
+ it 'sleeps after start' do
151
+ status = delayed_interrupt { wrapper.listen('.', {}) }
152
+ expect(status).to eq('sleep')
153
+ end
154
+
155
+ it 'calls stop' do
156
+ delayed_interrupt { wrapper.listen('.', {}) }
157
+ expect(MockListen.calls[1]).to eq([:stop])
158
+ end
159
+ end
160
+
161
+ RSpec.describe Listen::Compat::Wrapper::Stale do
162
+ include DelayedInterruptHelper
163
+
164
+ let(:wrapper) { Listen::Compat::Wrapper.create('2.7.11') }
165
+
166
+ before do
167
+ MockListen.setup_for_tests
168
+ MockListen.responses[:to] = proc { |*args| MockListen::Listener.new(*args) }
169
+ end
170
+
171
+ after do
172
+ MockListen.reset_for_tests
173
+ end
174
+
175
+ it { is_expected.to be_a described_class }
176
+
177
+ it 'calls start' do
178
+ delayed_interrupt { wrapper.listen('.', {}) }
179
+ expect(MockListen.instances[0].calls[1]).to eq([:start])
180
+ end
181
+
182
+ it 'sleeps after start' do
183
+ status = delayed_interrupt { wrapper.listen('.', {}) }
184
+ expect(status).to eq('sleep')
185
+ end
186
+
187
+ it 'calls stop' do
188
+ delayed_interrupt { wrapper.listen('.', {}) }
189
+ expect(MockListen.calls[1]).to eq([:stop])
190
+ end
191
+ end
192
+
193
+ RSpec.describe Listen::Compat::Wrapper::Current do
194
+ include DelayedInterruptHelper
195
+
196
+ let(:wrapper) { Listen::Compat::Wrapper.create('2.7.12') }
197
+
198
+ before do
199
+ MockListen.setup_for_tests
200
+ MockListen.responses[:to] = proc { |*args| MockListen::Listener.new(*args) }
201
+ end
202
+
203
+ after do
204
+ MockListen.reset_for_tests
205
+ end
206
+
207
+ it { is_expected.to be_a described_class }
208
+
209
+ it 'passes parameters to listen' do
210
+ delayed_interrupt { wrapper.listen('.', {}) }
211
+ expect(MockListen.calls[0]).to eq([:to, '.', {}])
212
+ end
213
+
214
+ it 'calls start' do
215
+ delayed_interrupt { wrapper.listen('.', {}) }
216
+ expect(MockListen.instances[0].calls[1]).to eq([:start])
217
+ end
218
+
219
+ it 'sleeps after start' do
220
+ status = delayed_interrupt { wrapper.listen('.', {}) }
221
+ expect(status).to eq('sleep')
222
+ end
223
+
224
+ it 'calls stop' do
225
+ delayed_interrupt { wrapper.listen('.', {}) }
226
+ expect(MockListen.calls[1]).to eq([:stop])
227
+ end
228
+ end
@@ -0,0 +1,73 @@
1
+ # The `.rspec` file also contains a few flags that are not defaults but that
2
+ # users commonly want.
3
+ #
4
+ # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
5
+ RSpec.configure do |config|
6
+ # rspec-expectations config goes here. You can use an alternate
7
+ # assertion/expectation library such as wrong or the stdlib/minitest
8
+ # assertions if you prefer.
9
+ config.expect_with :rspec do |expectations|
10
+ # This option will default to `true` in RSpec 4. It makes the `description`
11
+ # and `failure_message` of custom matchers include text for helper methods
12
+ # defined using `chain`, e.g.:
13
+ # be_bigger_than(2).and_smaller_than(4).description
14
+ # # => "be bigger than 2 and smaller than 4"
15
+ # ...rather than:
16
+ # # => "be bigger than 2"
17
+ expectations.include_chain_clauses_in_custom_matcher_descriptions = true
18
+ end
19
+
20
+ # rspec-mocks config goes here. You can use an alternate test double
21
+ # library (such as bogus or mocha) by changing the `mock_with` option here.
22
+ config.mock_with :rspec do |mocks|
23
+ # Prevents you from mocking or stubbing a method that does not exist on
24
+ # a real object. This is generally recommended, and will default to
25
+ # `true` in RSpec 4.
26
+ mocks.verify_doubled_constant_names = true
27
+ mocks.verify_partial_doubles = true
28
+ end
29
+
30
+ # The settings below are suggested to provide a good initial experience
31
+ # with RSpec, but feel free to customize to your heart's content.
32
+
33
+ # These two settings work together to allow you to limit a spec run
34
+ # to individual examples or groups you care about by tagging them with
35
+ # `:focus` metadata. When nothing is tagged with `:focus`, all examples
36
+ # get run.
37
+ config.filter_run focus: ENV['CI'] != 'true'
38
+
39
+ config.run_all_when_everything_filtered = true
40
+
41
+ config.disable_monkey_patching!
42
+
43
+ # This setting enables warnings. It's recommended, but in some cases may
44
+ # be too noisy due to issues in dependencies.
45
+ config.warnings = true
46
+
47
+ # Many RSpec users commonly either run the entire suite or an individual
48
+ # file, and it's useful to allow more verbose output when running an
49
+ # individual spec file.
50
+ if config.files_to_run.one?
51
+ # Use the documentation formatter for detailed output,
52
+ # unless a formatter has already been configured
53
+ # (e.g. via a command-line flag).
54
+ config.default_formatter = 'doc'
55
+ end
56
+
57
+ # Print the 10 slowest examples and example groups at the
58
+ # end of the spec run, to help surface which specs are running
59
+ # particularly slow.
60
+ # config.profile_examples = 10
61
+
62
+ # Run specs in random order to surface order dependencies. If you find an
63
+ # order dependency and want to debug it, you can fix the order by providing
64
+ # the seed, which is printed after each run.
65
+ # --seed 1234
66
+ config.order = :random
67
+
68
+ # Seed global randomization in this process using the `--seed` CLI option.
69
+ # Setting this allows you to use `--seed` to deterministically reproduce
70
+ # test failures related to randomization by passing the same `--seed` value
71
+ # as the one that triggered the failure.
72
+ Kernel.srand config.seed
73
+ end
metadata ADDED
@@ -0,0 +1,97 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: listen-compat
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Cezary Baginski
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-11-27 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.7'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.7'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ description: |-
42
+ For developers to have a minimal, guaranteed API for \
43
+ using Listen
44
+ email:
45
+ - cezary@chronomantic.net
46
+ executables: []
47
+ extensions: []
48
+ extra_rdoc_files: []
49
+ files:
50
+ - ".gitignore"
51
+ - ".rspec"
52
+ - ".rubocop.yml"
53
+ - ".rubocop_todo.yml"
54
+ - Gemfile
55
+ - LICENSE.txt
56
+ - README.md
57
+ - Rakefile
58
+ - example.rb
59
+ - lib/listen/compat.rb
60
+ - lib/listen/compat/test.rb
61
+ - lib/listen/compat/test/fake.rb
62
+ - lib/listen/compat/test/session.rb
63
+ - lib/listen/compat/test/simple.rb
64
+ - lib/listen/compat/version.rb
65
+ - lib/listen/compat/wrapper.rb
66
+ - listen-compat.gemspec
67
+ - spec/listen/compat/example_spec.rb
68
+ - spec/listen/compat/wrapper_spec.rb
69
+ - spec/spec_helper.rb
70
+ homepage: https://github.com/guard/listen-compat
71
+ licenses:
72
+ - MIT
73
+ metadata: {}
74
+ post_install_message:
75
+ rdoc_options: []
76
+ require_paths:
77
+ - lib
78
+ required_ruby_version: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ required_rubygems_version: !ruby/object:Gem::Requirement
84
+ requirements:
85
+ - - ">="
86
+ - !ruby/object:Gem::Version
87
+ version: '0'
88
+ requirements: []
89
+ rubyforge_project:
90
+ rubygems_version: 2.2.2
91
+ signing_key:
92
+ specification_version: 4
93
+ summary: Simplified compatibility layer for Listen gem
94
+ test_files:
95
+ - spec/listen/compat/example_spec.rb
96
+ - spec/listen/compat/wrapper_spec.rb
97
+ - spec/spec_helper.rb