listen-compat 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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