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 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
@@ -0,0 +1,7 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.9.3
4
+ - 2.0.0
5
+ - 2.1.0
6
+ - jruby-19mode # JRuby in 1.9 mode
7
+ - rbx-2.1.1
data/.yardopts ADDED
@@ -0,0 +1,2 @@
1
+ --no-private --protected lib/**/*.rb -m markdown - README.md
2
+
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
data/README.md ADDED
@@ -0,0 +1,35 @@
1
+ ## waitutil
2
+
3
+ [![Build Status](https://travis-ci.org/mbautin/waitutil.png?branch=master)](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
@@ -0,0 +1,3 @@
1
+ module WaitUtil
2
+ VERSION = IO.read(File.expand_path("../../../VERSION", __FILE__))
3
+ end
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