Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Issue #533: rescue StandardError only #535

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion lib/listen/thread.rb
Expand Up @@ -24,7 +24,7 @@ def new(name, &block)

def rescue_and_log(method_name, *args, caller_stack: nil)
yield(*args)
rescue Exception => exception # rubocop:disable Lint/RescueException
rescue => exception
_log_exception(exception, method_name, caller_stack: caller_stack)
end

Expand Down
2 changes: 1 addition & 1 deletion listen.gemspec
Expand Up @@ -5,7 +5,7 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)

require 'listen/version'

Gem::Specification.new do |gem|
Gem::Specification.new do |gem| # rubocop:disable Metrics/BlockLength
gem.name = 'listen'
gem.version = Listen::VERSION
gem.license = 'MIT'
Expand Down
268 changes: 138 additions & 130 deletions spec/lib/listen/adapter/linux_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true

RSpec.describe Listen::Adapter::Linux do
describe 'class' do
describe 'class methods' do
subject { described_class }

if linux?
Expand All @@ -12,177 +12,185 @@
end

if linux?
before(:all) do
require 'rb-inotify'
end
let(:dir1) { Pathname.new("/foo/dir1") }

let(:config) { instance_double(Listen::Adapter::Config, "config") }
let(:queue) { instance_double(Queue, "queue") }
let(:silencer) { instance_double(Listen::Silencer, "silencer") }
let(:snapshot) { instance_double(Listen::Change, "snapshot") }
let(:record) { instance_double(Listen::Record, "record") }

# TODO: fix other adapters too!
subject { described_class.new(config) }

describe 'watch events' do
let(:directories) { [Pathname.pwd] }
let(:adapter_options) { {} }
let(:default_events) { [:recursive, :attrib, :create, :modify, :delete, :move, :close_write] }
let(:fake_worker) { double(:fake_worker) }
let(:fake_notifier) { double(:fake_notifier, new: fake_worker) }

before do
stub_const('INotify::Notifier', fake_notifier)

allow(config).to receive(:directories).and_return(directories)
allow(config).to receive(:adapter_options).and_return(adapter_options)
allow(config).to receive(:queue).and_return(queue)
allow(config).to receive(:silencer).and_return(silencer)
end

it 'starts by calling watch with default events' do
expect(fake_worker).to receive(:watch).with(*directories.map(&:to_s), *default_events)
subject.start
describe 'instance methods' do
before(:all) do
require 'rb-inotify'
end
end

describe 'inotify limit message' do
let(:directories) { [Pathname.pwd] }
let(:adapter_options) { {} }
let(:dir1) { Pathname.new("/foo/dir1") }

before do
fake_worker = double(:fake_worker)
allow(fake_worker).to receive(:watch).and_raise(Errno::ENOSPC)
let(:queue) { instance_double(Queue, "queue", close: nil) }
let(:config) { instance_double(Listen::Adapter::Config, "config", queue: queue) }
let(:silencer) { instance_double(Listen::Silencer, "silencer") }
let(:snapshot) { instance_double(Listen::Change, "snapshot") }
let(:record) { instance_double(Listen::Record, "record") }

fake_notifier = double(:fake_notifier, new: fake_worker)
stub_const('INotify::Notifier', fake_notifier)
# TODO: fix other adapters too!
subject { described_class.new(config) }

allow(config).to receive(:directories).and_return(directories)
allow(config).to receive(:adapter_options).and_return(adapter_options)
after do
subject.stop
end

it 'should be shown before calling abort' do
expected_message = described_class.const_get('INOTIFY_LIMIT_MESSAGE')
expect { subject.start }.to raise_error SystemExit, expected_message
end
end

# TODO: should probably be adapted to be more like adapter/base_spec.rb
describe '_callback' do
let(:directories) { [dir1] }
let(:adapter_options) { { events: [:recursive, :close_write] } }
describe 'watch events' do
let(:directories) { [Pathname.pwd] }
let(:adapter_options) { {} }
let(:default_events) { [:recursive, :attrib, :create, :modify, :delete, :move, :close_write] }
let(:fake_worker) { double(:fake_worker_for_watch_events) }
let(:fake_notifier) { double(:fake_notifier, new: fake_worker) }

before do
fake_worker = double(:fake_worker)
events = [:recursive, :close_write]
allow(fake_worker).to receive(:watch).with('/foo/dir1', *events)

fake_notifier = double(:fake_notifier, new: fake_worker)
stub_const('INotify::Notifier', fake_notifier)

allow(config).to receive(:directories).and_return(directories)
allow(config).to receive(:adapter_options).and_return(adapter_options)
allow(config).to receive(:queue).and_return(queue)
allow(config).to receive(:silencer).and_return(silencer)

allow(Listen::Record).to receive(:new).with(dir1).and_return(record)
allow(Listen::Change::Config).to receive(:new).with(queue, silencer).
and_return(config)
allow(Listen::Change).to receive(:new).with(config, record).
and_return(snapshot)
before do
stub_const('INotify::Notifier', fake_notifier)

allow(subject).to receive(:require).with('rb-inotify')
subject.configure
end
allow(config).to receive(:directories).and_return(directories)
allow(config).to receive(:adapter_options).and_return(adapter_options)
allow(config).to receive(:silencer).and_return(silencer)
allow(fake_worker).to receive(:close)
end

let(:expect_change) do
lambda do |change|
expect(snapshot).to receive(:invalidate).with(
:file,
'path/foo.txt',
cookie: 123,
change: change
)
after do
subject.stop
end
end

let(:event_callback) do
lambda do |flags|
callbacks = subject.instance_variable_get(:'@callbacks')
callbacks.values.flatten.each do |callback|
callback.call double(
:inotify_event,
name: 'foo.txt',
watcher: double(:watcher, path: '/foo/dir1/path'),
flags: flags,
cookie: 123)
end
it 'starts by calling watch with default events' do
expect(fake_worker).to receive(:watch).with(*directories.map(&:to_s), *default_events)
subject.start
end
end

# TODO: get fsevent adapter working like INotify
unless /1|true/ =~ ENV['LISTEN_GEM_SIMULATE_FSEVENT']
it 'recognizes close_write as modify' do
expect_change.call(:modified)
event_callback.call([:close_write])
end
describe 'inotify limit message' do
let(:directories) { [Pathname.pwd] }
let(:adapter_options) { {} }

it 'recognizes moved_to as moved_to' do
expect_change.call(:moved_to)
event_callback.call([:moved_to])
before do
fake_worker = double(:fake_worker_for_inotify_limit_message)
allow(fake_worker).to receive(:watch).and_raise(Errno::ENOSPC)
allow(fake_worker).to receive(:close)

fake_notifier = double(:fake_notifier, new: fake_worker)
stub_const('INotify::Notifier', fake_notifier)

allow(config).to receive(:directories).and_return(directories)
allow(config).to receive(:adapter_options).and_return(adapter_options)
end

it 'recognizes moved_from as moved_from' do
expect_change.call(:moved_from)
event_callback.call([:moved_from])
it 'should be shown before calling abort' do
expected_message = described_class.const_get('INOTIFY_LIMIT_MESSAGE')
expect { subject.start }.to raise_error SystemExit, expected_message
end
end
end

describe '#stop' do
let(:fake_worker) { double(:fake_worker, close: true) }
let(:directories) { [dir1] }
let(:adapter_options) { { events: [:recursive, :close_write] } }

before do
allow(config).to receive(:directories).and_return(directories)
allow(config).to receive(:adapter_options).and_return(adapter_options)
end
# TODO: should probably be adapted to be more like adapter/base_spec.rb
describe '_callback' do
let(:directories) { [dir1] }
let(:adapter_options) { { events: [:recursive, :close_write] } }

context 'when configured' do
before do
fake_worker = double(:fake_worker_for_callback)
events = [:recursive, :close_write]
allow(fake_worker).to receive(:watch).with('/foo/dir1', *events)
allow(fake_worker).to receive(:close)

fake_notifier = double(:fake_notifier, new: fake_worker)
stub_const('INotify::Notifier', fake_notifier)

allow(config).to receive(:queue).and_return(queue)
allow(queue).to receive(:close)
allow(config).to receive(:directories).and_return(directories)
allow(config).to receive(:adapter_options).and_return(adapter_options)
allow(config).to receive(:silencer).and_return(silencer)

allow(Listen::Record).to receive(:new).with(dir1).and_return(record)
allow(Listen::Change::Config).to receive(:new).with(queue, silencer).
and_return(config)
allow(Listen::Change).to receive(:new).with(config, record).
and_return(snapshot)

allow(subject).to receive(:require).with('rb-inotify')
subject.configure
end

it 'stops the worker' do
expect(fake_worker).to receive(:close)
subject.stop
let(:expect_change) do
lambda do |change|
expect(snapshot).to receive(:invalidate).with(
:file,
'path/foo.txt',
cookie: 123,
change: change
)
end
end

let(:event_callback) do
lambda do |flags|
callbacks = subject.instance_variable_get(:'@callbacks')
callbacks.values.flatten.each do |callback|
callback.call double(
:inotify_event,
name: 'foo.txt',
watcher: double(:watcher, path: '/foo/dir1/path'),
flags: flags,
cookie: 123)
end
end
end

# TODO: get fsevent adapter working like INotify
unless /1|true/ =~ ENV['LISTEN_GEM_SIMULATE_FSEVENT']
it 'recognizes close_write as modify' do
expect_change.call(:modified)
event_callback.call([:close_write])
end

it 'recognizes moved_to as moved_to' do
expect_change.call(:moved_to)
event_callback.call([:moved_to])
end

it 'recognizes moved_from as moved_from' do
expect_change.call(:moved_from)
event_callback.call([:moved_from])
end
end
end

context 'when not even initialized' do
describe '#stop' do
let(:fake_worker) { double(:fake_worker_for_stop, close: true) }
let(:directories) { [dir1] }
let(:adapter_options) { { events: [:recursive, :close_write] } }

before do
allow(config).to receive(:queue).and_return(queue)
allow(queue).to receive(:close)
allow(config).to receive(:directories).and_return(directories)
allow(config).to receive(:adapter_options).and_return(adapter_options)
end

it 'does not crash' do
expect do
context 'when configured' do
before do
events = [:recursive, :close_write]
allow(fake_worker).to receive(:watch).with('/foo/dir1', *events)

fake_notifier = double(:fake_notifier, new: fake_worker)
stub_const('INotify::Notifier', fake_notifier)

allow(config).to receive(:silencer).and_return(silencer)

allow(subject).to receive(:require).with('rb-inotify')
subject.configure
end

it 'stops the worker' do
subject.stop
end.to_not raise_error
end
end

context 'when not even initialized' do
before do
allow(queue).to receive(:close)
end

it 'does not crash' do
expect do
subject.stop
end.to_not raise_error
end
end
end
end
Expand Down
6 changes: 3 additions & 3 deletions spec/lib/listen/event/processor_spec.rb
Expand Up @@ -224,20 +224,20 @@ def status_for_time(time)
end

describe '_process_changes' do
context 'when it raises an exception derived from StandardError or not' do
context 'when it raises an exception derived from StandardError' do
before do
allow(event_queue).to receive(:empty?).and_return(true)
allow(config).to receive(:callable?).and_return(true)
resulting_changes = { modified: ['foo'], added: [], removed: [] }
allow(config).to receive(:optimize_changes).with(anything).and_return(resulting_changes)
expect(config).to receive(:call).and_raise(ArgumentError, "bang!")
expect(config).to receive(:call).and_return(nil)
expect(config).to receive(:call).and_raise(ScriptError, "ruby typo!")
expect(config).to receive(:call).and_raise("error!")
end

it 'rescues and logs exception and continues' do
expect(Listen.logger).to receive(:error).with(/Exception rescued in _process_changes:\nArgumentError: bang!/)
expect(Listen.logger).to receive(:error).with(/Exception rescued in _process_changes:\nScriptError: ruby typo!/)
expect(Listen.logger).to receive(:error).with(/Exception rescued in _process_changes:\nRuntimeError: error!/)
expect(Listen.logger).to receive(:debug).with(/Callback \(exception\) took/)
expect(Listen.logger).to receive(:debug).with(/Callback took/)
expect(Listen.logger).to receive(:debug).with(/Callback \(exception\) took/)
Expand Down