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

Add tests for PR #529 #531

Merged
merged 5 commits into from Jan 14, 2021
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
4 changes: 2 additions & 2 deletions CONTRIBUTING.md
Expand Up @@ -4,7 +4,7 @@ Contribute to Listen
File an issue
-------------

If you haven't already, first see [TROUBLESHOOTING](https://github.com/guard/listen/wiki/Troubleshooting) for known issues, solutions and workarounds.
If you haven't already, first see [TROUBLESHOOTING](https://github.com/guard/listen/blob/master/README.md#Issues-and-Troubleshooting) for known issues, solutions and workarounds.

You can report bugs and feature requests to [GitHub Issues](https://github.com/guard/listen/issues).

Expand All @@ -16,7 +16,7 @@ Try to figure out where the issue belongs to: Is it an issue with Listen itself

**It's most likely that your bug gets resolved faster if you provide as much information as possible!**

The MOST useful information is debugging output from Listen (`LISTEN_GEM_DEBUGGING=1`) - see [TROUBLESHOOTING](https://github.com/guard/listen/wiki/Troubleshooting) for details.
The MOST useful information is debugging output from Listen (`LISTEN_GEM_DEBUGGING=1`) - see [TROUBLESHOOTING](https://github.com/guard/listen/blob/master/README.md#Issues-and-Troubleshooting) for details.


Development
Expand Down
2 changes: 0 additions & 2 deletions README.md
Expand Up @@ -2,8 +2,6 @@

The `listen` gem listens to file modifications and notifies you about the changes.

:exclamation: `Listen` is currently accepting more maintainers. Please [read this](https://github.com/guard/guard/wiki/Maintainers) if you're interested in joining the team.

[![Development Status](https://github.com/guard/listen/workflows/Development/badge.svg)](https://github.com/guard/listen/actions?workflow=Development)
[![Gem Version](https://badge.fury.io/rb/listen.svg)](http://badge.fury.io/rb/listen)
[![Code Climate](https://codeclimate.com/github/guard/listen.svg)](https://codeclimate.com/github/guard/listen)
Expand Down
6 changes: 3 additions & 3 deletions lib/listen/adapter/linux.rb
Expand Up @@ -21,12 +21,12 @@ class Linux < Base

private

WIKI_URL = 'https://github.com/guard/listen'\
README_URL = 'https://github.com/guard/listen'\
'/blob/master/README.md#increasing-the-amount-of-inotify-watchers'

INOTIFY_LIMIT_MESSAGE = <<-EOS.gsub(/^\s*/, '')
INOTIFY_LIMIT_MESSAGE = <<-EOS
FATAL: Listen error: unable to monitor directories for changes.
Visit #{WIKI_URL} for info on how to fix this.
Visit #{README_URL} for info on how to fix this.
EOS

def _configure(directory, &callback)
Expand Down
10 changes: 10 additions & 0 deletions lib/listen/error.rb
@@ -0,0 +1,10 @@
# frozen_string_literal: true

# Besides programming error exceptions like ArgumentError,
# all public interface exceptions should be declared here and inherit from Listen::Error.
module Listen
class Error < RuntimeError
class NotStarted < Error; end
class SymlinkLoop < Error; end
end
end
7 changes: 4 additions & 3 deletions lib/listen/event/loop.rb
Expand Up @@ -5,15 +5,15 @@
require 'timeout'
require 'listen/event/processor'
require 'listen/thread'
require 'listen/error'

module Listen
module Event
class Loop
include Listen::FSM

class Error < RuntimeError
class NotStarted < Error; end
end
Error = ::Listen::Error
NotStarted = ::Listen::Error::NotStarted # for backward compatibility

start_state :pre_start
state :pre_start
Expand All @@ -40,6 +40,7 @@ def started?

MAX_STARTUP_SECONDS = 5.0

# @raises Error::NotStarted if background thread hasn't started in MAX_STARTUP_SECONDS
def start
# TODO: use a Fiber instead?
return unless state == :pre_start
Expand Down
3 changes: 3 additions & 0 deletions lib/listen/fsm.rb
Expand Up @@ -53,6 +53,9 @@ def initialize_fsm
# if not already, waits for a state change (up to timeout seconds--`nil` means infinite)
# returns truthy iff the transition to one of the desired state has occurred
def wait_for_state(*wait_for_states, timeout: nil)
wait_for_states.each do |state|
state.is_a?(Symbol) or raise ArgumentError, "states must be symbols (got #{state.inspect})"
end
@mutex.synchronize do
if !wait_for_states.include?(@state)
@state_changed.wait(@mutex, timeout)
Expand Down
12 changes: 6 additions & 6 deletions lib/listen/record/symlink_detector.rb
@@ -1,12 +1,13 @@
# frozen_string_literal: true

require 'set'
require 'listen/error'

module Listen
# @private api
class Record
class SymlinkDetector
WIKI = 'https://github.com/guard/listen/wiki/Duplicate-directory-errors'
README_URL = 'https://github.com/guard/listen/blob/master/README.md'

SYMLINK_LOOP_ERROR = <<-EOS
** ERROR: directory is already being watched! **
Expand All @@ -15,26 +16,25 @@ class SymlinkDetector

is already being watched through: %s

MORE INFO: #{WIKI}
MORE INFO: #{README_URL}
EOS

class Error < RuntimeError
end
Error = ::Listen::Error # for backward compatibility

def initialize
@real_dirs = Set.new
end

def verify_unwatched!(entry)
real_path = entry.real_path
@real_dirs.add?(real_path) || _fail(entry.sys_path, real_path)
@real_dirs.add?(real_path) or _fail(entry.sys_path, real_path)
end

private

def _fail(symlinked, real_path)
warn(format(SYMLINK_LOOP_ERROR, symlinked, real_path))
raise Error, 'Failed due to looped symlinks'
raise ::Listen::Error::SymlinkLoop, 'Failed due to looped symlinks'
end
end
end
Expand Down
3 changes: 1 addition & 2 deletions listen.gemspec
Expand Up @@ -21,8 +21,7 @@ Gem::Specification.new do |gem|
'changelog_uri' => "#{gem.homepage}/releases",
'documentation_uri' => "https://www.rubydoc.info/gems/listen/#{gem.version}",
'homepage_uri' => gem.homepage,
'source_code_uri' => "#{gem.homepage}/tree/v#{gem.version}",
'wiki_uri' => "#{gem.homepage}/wiki"
'source_code_uri' => "#{gem.homepage}/tree/v#{gem.version}"
}

gem.files = `git ls-files -z`.split("\x0").select do |f|
Expand Down
28 changes: 21 additions & 7 deletions spec/lib/listen/event/loop_spec.rb
Expand Up @@ -45,26 +45,40 @@
end

describe '#start' do
before do
it 'is started' do
expect(processor).to receive(:loop_for).with(1.234)
expect(Thread).to receive(:new) do |&block|
block.call
thread
end

expect(processor).to receive(:loop_for).with(1.234)

subject.start
end

it 'is started' do
expect(subject).to be_started
end

context 'when start is called again' do
it 'returns silently' do
expect(processor).to receive(:loop_for).with(1.234)
expect(Thread).to receive(:new) do |&block|
block.call
thread
end
subject.start
expect { subject.start }.to_not raise_exception
end
end

context 'when state change to :started takes longer than 5 seconds' do
before do
expect(Thread).to receive(:new) { thread }
expect_any_instance_of(::ConditionVariable).to receive(:wait) { } # return immediately
end

it 'raises Error::NotStarted' do
expect do
subject.start
end.to raise_exception(::Listen::Error::NotStarted, "thread didn't start in 5.0 seconds (in state: :starting)")
end
end
end

context 'when set up / started' do
Expand Down
38 changes: 38 additions & 0 deletions spec/lib/listen/fsm_spec.rb
Expand Up @@ -60,6 +60,44 @@ def initialize
expect { subject.transition(:started) }.to raise_exception(NoMethodError, /private.*transition/)
expect { subject.transition!(:started) }.to raise_exception(NoMethodError, /private.*transition!/)
end

describe '#wait_for_state' do
it 'returns truthy immediately if already in the desired state' do
expect(subject.instance_variable_get(:@state_changed)).to_not receive(:wait)
result = subject.wait_for_state(:initial)
expect(result).to be_truthy
end

it 'waits for the next state change and returns truthy if then in the desired state' do
expect(subject.instance_variable_get(:@state_changed)).to receive(:wait).with(anything, anything) do
subject.instance_variable_set(:@state, :started)
end
result = subject.wait_for_state(:started)
expect(result).to be_truthy
end

it 'waits for the next state change and returns falsey if then not the desired state' do
expect(subject.instance_variable_get(:@state_changed)).to receive(:wait).with(anything, anything)
result = subject.wait_for_state(:started)
expect(result).to be_falsey
end

it 'passes the timeout: down to wait, if given' do
expect(subject.instance_variable_get(:@state_changed)).to receive(:wait).with(anything, 5.0)
subject.wait_for_state(:started, timeout: 5.0)
end

it 'passes nil (infinite) timeout: down to wait, if none given' do
expect(subject.instance_variable_get(:@state_changed)).to receive(:wait).with(anything, nil)
subject.wait_for_state(:started)
end

it 'enforces precondition that states must be symbols' do
expect do
subject.wait_for_state(:started, 'stopped')
end.to raise_exception(ArgumentError, /states must be symbols .*got "stopped"/)
end
end
end

context 'FSM with no start state' do
Expand Down