waitutil 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.
- checksums.yaml +15 -0
- data/.gitignore +28 -0
- data/.travis.yml +7 -0
- data/.yardopts +2 -0
- data/Gemfile +3 -0
- data/README.md +35 -0
- data/Rakefile +14 -0
- data/VERSION +1 -0
- data/lib/waitutil/version.rb +3 -0
- data/lib/waitutil.rb +78 -0
- data/spec/Rakefile +0 -0
- data/spec/waitutil_spec.rb +135 -0
- data/waitutil.gemspec +25 -0
- metadata +113 -0
checksums.yaml
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
---
|
2
|
+
!binary "U0hBMQ==":
|
3
|
+
metadata.gz: !binary |-
|
4
|
+
ZjhlODkyZTdjOTQwNjVjNTFmZGMzYjkwYjdkNWRjOWZlZWM1NzQ3Mw==
|
5
|
+
data.tar.gz: !binary |-
|
6
|
+
ZTc0Y2RlYTM5MjZmZjI2YjIxMmYzNDZmY2U4OGJmYzljOTljNzljZQ==
|
7
|
+
SHA512:
|
8
|
+
metadata.gz: !binary |-
|
9
|
+
NDhkZWMzZWE1MDEyMDYxYmNjNTZhMDBlNDIxYjkwOWU4ZWM4NjBiOWRiMWVj
|
10
|
+
MjU2YjNmZWRlMjFjZGIxY2Y5YWRmMDhlZDQyY2ZmNmU1MDJlZmQxNDRkMDBk
|
11
|
+
N2IyM2Y0N2JiNmYyYTBjYmNmNGUzYmJlZDY5OGEyNmE0ZTA5ZWE=
|
12
|
+
data.tar.gz: !binary |-
|
13
|
+
NDM4N2Q3YTMxZGEwNmIwMzdlZDViYWZkOWQ3ODM3ODk2NDM1ODIwNjRkYzRh
|
14
|
+
NmFkMjIxODkyODQwNDlkOTYxNDgwZmI1MmM3YThhYjdhN2M3NzhhYTIxNDAw
|
15
|
+
NjYyYjkzOGUyOWFkMWU2NmViMjI4ODQ2MjcyODc5ZGJmOGMyMzE=
|
data/.gitignore
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
*.gem
|
2
|
+
*.rbc
|
3
|
+
/.config
|
4
|
+
/coverage/
|
5
|
+
/InstalledFiles
|
6
|
+
/pkg/
|
7
|
+
/spec/reports/
|
8
|
+
/test/tmp/
|
9
|
+
/test/version_tmp/
|
10
|
+
/tmp/
|
11
|
+
|
12
|
+
## Documentation cache and generated files:
|
13
|
+
/.yardoc/
|
14
|
+
/_yardoc/
|
15
|
+
/doc/
|
16
|
+
/rdoc/
|
17
|
+
|
18
|
+
## Environment normalisation:
|
19
|
+
/.bundle/
|
20
|
+
/lib/bundler/man/
|
21
|
+
|
22
|
+
Gemfile.lock
|
23
|
+
.ruby-version
|
24
|
+
.ruby-gemset
|
25
|
+
|
26
|
+
# unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
|
27
|
+
.rvmrc
|
28
|
+
|
data/.travis.yml
ADDED
data/.yardopts
ADDED
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
## waitutil
|
2
|
+
|
3
|
+
[](https://travis-ci.org/mbautin/waitutil)
|
4
|
+
|
5
|
+
`waitutil` provides tools for waiting for various conditions to occur, with a configurable
|
6
|
+
delay time, timeout, and logging.
|
7
|
+
|
8
|
+
### Examples
|
9
|
+
|
10
|
+
#### Waiting for conditions
|
11
|
+
|
12
|
+
Maximum wait time is one minute by default, and the delay time is one second.
|
13
|
+
```ruby
|
14
|
+
WaitUtil.wait_for_condition("my_event to happen") do
|
15
|
+
check_if_my_event_happened
|
16
|
+
end
|
17
|
+
```
|
18
|
+
|
19
|
+
Customized wait time and delay time:
|
20
|
+
```ruby
|
21
|
+
WaitUtil.wait_for_condition("my_event to happen", :timeout_sec => 30, :delay_sec => 0.5) do
|
22
|
+
check_if_my_event_happened
|
23
|
+
end
|
24
|
+
```
|
25
|
+
|
26
|
+
#### Waiting for service availability
|
27
|
+
|
28
|
+
Wait for a TCP server to be available:
|
29
|
+
```ruby
|
30
|
+
WaitUtil.wait_for_service('example.com', 8080)
|
31
|
+
```
|
32
|
+
|
33
|
+
### License
|
34
|
+
|
35
|
+
[Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.html)
|
data/Rakefile
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
$LOAD_PATH.unshift File.expand_path("../lib", __FILE__)
|
2
|
+
|
3
|
+
require 'bundler'
|
4
|
+
require 'rspec/core/rake_task'
|
5
|
+
require 'rake/clean'
|
6
|
+
require 'rubygems/tasks'
|
7
|
+
|
8
|
+
CLEAN.include("**/*.gem")
|
9
|
+
|
10
|
+
Bundler::GemHelper.install_tasks
|
11
|
+
|
12
|
+
RSpec::Core::RakeTask.new(:spec)
|
13
|
+
|
14
|
+
task :default => :spec
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.1.0
|
data/lib/waitutil.rb
ADDED
@@ -0,0 +1,78 @@
|
|
1
|
+
require 'logger'
|
2
|
+
|
3
|
+
module WaitUtil
|
4
|
+
|
5
|
+
extend self
|
6
|
+
|
7
|
+
class TimeoutError < StandardError
|
8
|
+
end
|
9
|
+
|
10
|
+
DEFAULT_TIMEOUT_SEC = 60
|
11
|
+
DEFAULT_DELAY_SEC = 1
|
12
|
+
|
13
|
+
@@logger = Logger.new(STDOUT)
|
14
|
+
@@logger.level = Logger::INFO
|
15
|
+
|
16
|
+
def self.logger
|
17
|
+
@@logger
|
18
|
+
end
|
19
|
+
|
20
|
+
# Wait until the condition computed by the given block is met. The supplied block may return a
|
21
|
+
# boolean or an array of two elements: whether the condition has been met and an additional
|
22
|
+
# message to display in case of timeout.
|
23
|
+
def wait_for_condition(description, options = {}, &block)
|
24
|
+
delay_sec = options.delete(:delay_sec) || DEFAULT_DELAY_SEC
|
25
|
+
timeout_sec = options.delete(:timeout_sec) || DEFAULT_TIMEOUT_SEC
|
26
|
+
verbose = options.delete(:verbose)
|
27
|
+
unless options.empty?
|
28
|
+
raise "Invalid options: #{options}"
|
29
|
+
end
|
30
|
+
|
31
|
+
if verbose
|
32
|
+
@@logger.info("Waiting for #{description} for up to #{timeout_sec} seconds")
|
33
|
+
end
|
34
|
+
|
35
|
+
start_time = Time.now
|
36
|
+
iteration = 0
|
37
|
+
until is_condition_met(condition_result = yield(iteration))
|
38
|
+
if Time.now - start_time >= timeout_sec
|
39
|
+
raise TimeoutError.new(
|
40
|
+
"Timed out waiting for #{description} (#{timeout_sec} seconds elapsed)" +
|
41
|
+
get_additional_message(condition_result)
|
42
|
+
)
|
43
|
+
end
|
44
|
+
sleep(delay_sec)
|
45
|
+
iteration += 1
|
46
|
+
end
|
47
|
+
if verbose
|
48
|
+
@@logger.info("Success waiting for #{description} (#{Time.now - start_time} seconds)")
|
49
|
+
end
|
50
|
+
true
|
51
|
+
end
|
52
|
+
|
53
|
+
# Wait until a service is available at the given host/port.
|
54
|
+
def wait_for_service(description, host, port, options = {})
|
55
|
+
wait_for_condition("#{description} port #{port} to become available on #{host}",
|
56
|
+
options) do
|
57
|
+
begin
|
58
|
+
s = TCPSocket.new(host, port)
|
59
|
+
s.close
|
60
|
+
true
|
61
|
+
rescue Errno::ECONNREFUSED, Errno::EHOSTUNREACH
|
62
|
+
false
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
private
|
68
|
+
|
69
|
+
def is_condition_met(condition_result)
|
70
|
+
condition_result.kind_of?(Array) ? condition_result[0] : condition_result
|
71
|
+
end
|
72
|
+
|
73
|
+
def get_additional_message(condition_result)
|
74
|
+
condition_result.kind_of?(Array) ? ': ' + condition_result[1] : ''
|
75
|
+
end
|
76
|
+
|
77
|
+
extend WaitUtil
|
78
|
+
end
|
data/spec/Rakefile
ADDED
File without changes
|
@@ -0,0 +1,135 @@
|
|
1
|
+
require 'waitutil'
|
2
|
+
require 'socket'
|
3
|
+
|
4
|
+
RSpec.configure do |configuration|
|
5
|
+
configuration.include WaitUtil
|
6
|
+
end
|
7
|
+
|
8
|
+
describe WaitUtil do
|
9
|
+
describe '.wait_for_condition' do
|
10
|
+
it 'logs if the verbose option is specified' do
|
11
|
+
iterations = []
|
12
|
+
WaitUtil.logger.should_receive(:info).with('Waiting for true for up to 60 seconds')
|
13
|
+
WaitUtil.logger.should_receive(:info) do |msg|
|
14
|
+
msg =~ /^Success waiting for true \(.*\)$/
|
15
|
+
end
|
16
|
+
|
17
|
+
ret = wait_for_condition('true', :verbose => true) do |iteration|
|
18
|
+
iterations << iteration
|
19
|
+
true
|
20
|
+
end
|
21
|
+
expect(ret).to be_true
|
22
|
+
expect(iterations).to eq([0])
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'returns immediately if the condition is true' do
|
26
|
+
iterations = []
|
27
|
+
ret = wait_for_condition('true') {|iteration| iterations << iteration; true }
|
28
|
+
expect(ret).to be_true
|
29
|
+
expect(iterations).to eq([0])
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'should time out if the condition is always false' do
|
33
|
+
iterations = []
|
34
|
+
start_time = Time.now
|
35
|
+
begin
|
36
|
+
wait_for_condition('false', :timeout_sec => 0.1, :delay_sec => 0.01) do |iteration|
|
37
|
+
iterations << iteration
|
38
|
+
false
|
39
|
+
end
|
40
|
+
fail 'Expected an exception'
|
41
|
+
rescue WaitUtil::TimeoutError => ex
|
42
|
+
expect(ex.to_s).to match(/^Timed out waiting for false /)
|
43
|
+
end
|
44
|
+
elapsed_sec = Time.now - start_time
|
45
|
+
expect(elapsed_sec).to be >= 0.1
|
46
|
+
expect(iterations.length).to be >= 9
|
47
|
+
expect(iterations.length).to be <= 11
|
48
|
+
expect(iterations).to eq((0..iterations.length - 1).to_a)
|
49
|
+
end
|
50
|
+
|
51
|
+
it 'should handle additional messages from the block' do
|
52
|
+
begin
|
53
|
+
wait_for_condition('false', :timeout_sec => 0.01, :delay_sec => 0.05) do |iteration|
|
54
|
+
[false, 'Some error']
|
55
|
+
end
|
56
|
+
fail 'Expected an exception'
|
57
|
+
rescue WaitUtil::TimeoutError => ex
|
58
|
+
expect(ex.to_s).to match(/^Timed out waiting for false (.*): Some error$/)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
it 'should treat the first element of returned tuple as condition status' do
|
63
|
+
iterations = []
|
64
|
+
ret = wait_for_condition('some condition', :timeout_sec => 1, :delay_sec => 0) do |iteration|
|
65
|
+
iterations << iteration
|
66
|
+
[iteration >= 3, 'some message']
|
67
|
+
end
|
68
|
+
expect(ret).to be_true
|
69
|
+
expect(iterations).to eq([0, 1, 2, 3])
|
70
|
+
end
|
71
|
+
|
72
|
+
it 'should evaluate the block return value as a boolean if it is not an array' do
|
73
|
+
iterations = []
|
74
|
+
ret = wait_for_condition('some condition', :timeout_sec => 1, :delay_sec => 0) do |iteration|
|
75
|
+
iterations << iteration
|
76
|
+
iteration >= 3
|
77
|
+
end
|
78
|
+
expect(ret).to be_true
|
79
|
+
expect(iterations).to eq([0, 1, 2, 3])
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
describe '.wait_for_service' do
|
84
|
+
BIND_IP = '127.0.0.1'
|
85
|
+
|
86
|
+
it 'should succeed immediately when there is a TCP server listening' do
|
87
|
+
# Find an unused port.
|
88
|
+
socket = Socket.new(:INET, :STREAM, 0)
|
89
|
+
sockaddr = if RUBY_ENGINE == 'jruby'
|
90
|
+
ServerSocket.pack_sockaddr_in(12345, "127.0.0.1")
|
91
|
+
else
|
92
|
+
Addrinfo.tcp(BIND_IP, 0)
|
93
|
+
end
|
94
|
+
socket.bind(sockaddr)
|
95
|
+
port = socket.local_address.ip_port
|
96
|
+
socket.close
|
97
|
+
|
98
|
+
server_thread = Thread.new do
|
99
|
+
server = TCPServer.new(port)
|
100
|
+
loop do
|
101
|
+
client = server.accept # Wait for a client to connect
|
102
|
+
client.puts "Hello !"
|
103
|
+
client.close
|
104
|
+
break
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
wait_for_service('wait for my service', BIND_IP, port, :delay_sec => 0.1, :timeout_sec => 0.3)
|
109
|
+
end
|
110
|
+
|
111
|
+
it 'should fail when there is no TCP server listening' do
|
112
|
+
port = nil
|
113
|
+
# Find a port that no one is listening on.
|
114
|
+
attempts = 0
|
115
|
+
while attempts < 100
|
116
|
+
port = 32768 + rand(61000 - 32768)
|
117
|
+
begin
|
118
|
+
TCPSocket.new(BIND_IP, port)
|
119
|
+
port = nil
|
120
|
+
rescue Errno::ECONNREFUSED
|
121
|
+
break
|
122
|
+
end
|
123
|
+
attempts += 1
|
124
|
+
end
|
125
|
+
fail 'Could not find a port no one is listening on' unless port
|
126
|
+
|
127
|
+
expect {
|
128
|
+
wait_for_service(
|
129
|
+
'wait for non-existent service', BIND_IP, port, :delay_sec => 0.1, :timeout_sec => 0.3
|
130
|
+
)
|
131
|
+
}.to raise_error(WaitUtil::TimeoutError)
|
132
|
+
end
|
133
|
+
|
134
|
+
end
|
135
|
+
end
|
data/waitutil.gemspec
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
GEM_NAME = 'waitutil'
|
3
|
+
|
4
|
+
require File.expand_path("../lib/#{GEM_NAME}/version", __FILE__)
|
5
|
+
|
6
|
+
Gem::Specification.new do |gem|
|
7
|
+
gem.authors = ['Mikhail Bautin']
|
8
|
+
gem.email = ['mbautin@gmail.com']
|
9
|
+
gem.description = 'Utilities for waiting for various conditions'
|
10
|
+
gem.summary = 'Utilities for waiting for various conditions'
|
11
|
+
gem.homepage = "http://github.com/mbautin/#{GEM_NAME}"
|
12
|
+
|
13
|
+
gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
14
|
+
gem.files = `git ls-files`.split("\n").map(&:strip)
|
15
|
+
gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
16
|
+
gem.name = GEM_NAME
|
17
|
+
gem.require_paths = ['lib']
|
18
|
+
gem.version = WaitUtil::VERSION
|
19
|
+
|
20
|
+
gem.add_development_dependency 'rake', '~> 10.1'
|
21
|
+
gem.add_development_dependency 'rspec', '~> 2.14'
|
22
|
+
gem.add_development_dependency 'rubygems-tasks', '~> 0.2'
|
23
|
+
|
24
|
+
gem.add_development_dependency 'webrick'
|
25
|
+
end
|
metadata
ADDED
@@ -0,0 +1,113 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: waitutil
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Mikhail Bautin
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-02-15 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rake
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ~>
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '10.1'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ~>
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '10.1'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rspec
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ~>
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '2.14'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ~>
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '2.14'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rubygems-tasks
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ~>
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0.2'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ~>
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0.2'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: webrick
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ! '>='
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
description: Utilities for waiting for various conditions
|
70
|
+
email:
|
71
|
+
- mbautin@gmail.com
|
72
|
+
executables: []
|
73
|
+
extensions: []
|
74
|
+
extra_rdoc_files: []
|
75
|
+
files:
|
76
|
+
- .gitignore
|
77
|
+
- .travis.yml
|
78
|
+
- .yardopts
|
79
|
+
- Gemfile
|
80
|
+
- README.md
|
81
|
+
- Rakefile
|
82
|
+
- VERSION
|
83
|
+
- lib/waitutil.rb
|
84
|
+
- lib/waitutil/version.rb
|
85
|
+
- spec/Rakefile
|
86
|
+
- spec/waitutil_spec.rb
|
87
|
+
- waitutil.gemspec
|
88
|
+
homepage: http://github.com/mbautin/waitutil
|
89
|
+
licenses: []
|
90
|
+
metadata: {}
|
91
|
+
post_install_message:
|
92
|
+
rdoc_options: []
|
93
|
+
require_paths:
|
94
|
+
- lib
|
95
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
96
|
+
requirements:
|
97
|
+
- - ! '>='
|
98
|
+
- !ruby/object:Gem::Version
|
99
|
+
version: '0'
|
100
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
101
|
+
requirements:
|
102
|
+
- - ! '>='
|
103
|
+
- !ruby/object:Gem::Version
|
104
|
+
version: '0'
|
105
|
+
requirements: []
|
106
|
+
rubyforge_project:
|
107
|
+
rubygems_version: 2.1.11
|
108
|
+
signing_key:
|
109
|
+
specification_version: 4
|
110
|
+
summary: Utilities for waiting for various conditions
|
111
|
+
test_files:
|
112
|
+
- spec/Rakefile
|
113
|
+
- spec/waitutil_spec.rb
|