rspec-background-process 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.
@@ -0,0 +1,48 @@
1
+ require 'open-uri'
2
+ require 'file-tail'
3
+ require 'retries'
4
+
5
+ require_relative 'process_pool'
6
+
7
+ module RSpecBackgroundProcess
8
+ class ProcessPool
9
+ class ProcessDefinition
10
+ def ready_when_log_includes(log_line)
11
+ ready_test do |instance|
12
+ log_line = instance.render(log_line)
13
+
14
+ # NOTE: log file my not be crated just after process is started (spawned) so we need to retry
15
+ with_retries(
16
+ max_tries: 10000,
17
+ base_sleep_seconds: 0.01,
18
+ max_sleep_seconds: 0.2,
19
+ rescue: Errno::ENOENT
20
+ ) do
21
+ File::Tail::Logfile.tail(instance.log_file, forward: 0, interval: 0.01, max_interval: 1, suspicious_interval: 4) do |line|
22
+ line.include?(log_line) and break true
23
+ end
24
+ end
25
+ end
26
+ end
27
+
28
+ def ready_when_url_response_status(uri, status = 'OK')
29
+ ready_test do |instance|
30
+ _uri = instance.render(uri) # NOTE: new variable (_uri) is needed or strange things happen...
31
+
32
+ begin
33
+ with_retries(
34
+ max_tries: 10000,
35
+ base_sleep_seconds: 0.06,
36
+ max_sleep_seconds: 0.2,
37
+ rescue: Errno::ECONNREFUSED
38
+ ) do
39
+ open(_uri).status.last.strip == status and break true
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
47
+
48
+
@@ -0,0 +1,15 @@
1
+ require_relative 'process_pool'
2
+
3
+ module RSpecBackgroundProcess
4
+ class ProcessPool
5
+ class ProcessDefinition
6
+ def refresh_command(command)
7
+ refresh_action do |instance|
8
+ _command = instance.render(command)
9
+ system _command
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
15
+
@@ -0,0 +1,49 @@
1
+ module RSpecBackgroundProcess
2
+ class ProcessPool
3
+ class ProcessDefinition
4
+ def http_port_allocated_form(base_port, port_count = 1)
5
+ extend RSpecBackgroundProcess::BackgroundProcess::Server, base_port: base_port, port_count: port_count
6
+ end
7
+ end
8
+ end
9
+
10
+ class BackgroundProcess
11
+ module Server
12
+ def self.extended(mod)
13
+ mod.allocate_ports
14
+ end
15
+
16
+ def template_variables
17
+ super.merge(
18
+ /allocated port (\d)/ => ->(port_no) { allocated_port(port_no) }
19
+ )
20
+ end
21
+
22
+ def allocate_ports
23
+ base_port = @options[:base_port] or fail "no base_port option set for #{self}: #{@options}"
24
+ port_count = @options[:port_count] or fail "no port_count option set for #{self}: #{@options}"
25
+
26
+ global_ports = @options[:global_context][:ports] ||= Set[]
27
+
28
+ begin
29
+ @ports = (base_port ... base_port + port_count).to_a
30
+ base_port += port_count
31
+ end until (global_ports & @ports).empty?
32
+
33
+ @options[:global_context][:ports] = global_ports + @ports
34
+ end
35
+
36
+ def ports
37
+ @ports
38
+ end
39
+
40
+ def allocated_port(port_no)
41
+ @ports[port_no.to_i - 1] or fail "no port #{port_no} allocated: #{@ports}"
42
+ end
43
+
44
+ def to_s
45
+ super + "{ports: #{ports.join(', ')}}"
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,96 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = "rspec-background-process"
8
+ s.version = "0.1.0"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Jakub Pastuszek"]
12
+ s.date = "2014-09-29"
13
+ s.description = "RSpec and Cucumber DSL that allows definition of processes with their arguments, working directory, time outs, port numbers etc. and start/stop them during test runs. Processes with same definitions can pooled and reused between example runs to save time on startup/shutdown. Pooling supports process number limiting with LRU to limit memory used."
14
+ s.email = "jpastuszek@gmail.com"
15
+ s.extra_rdoc_files = [
16
+ "LICENSE.txt",
17
+ "README.rdoc"
18
+ ]
19
+ s.files = [
20
+ ".document",
21
+ ".rspec",
22
+ "Gemfile",
23
+ "Gemfile.lock",
24
+ "LICENSE.txt",
25
+ "README.rdoc",
26
+ "Rakefile",
27
+ "VERSION",
28
+ "lib/rspec-background-process.rb",
29
+ "lib/rspec-background-process/background_process.rb",
30
+ "lib/rspec-background-process/background_process_helpers.rb",
31
+ "lib/rspec-background-process/process_pool.rb",
32
+ "lib/rspec-background-process/readiness_checks.rb",
33
+ "lib/rspec-background-process/refresh_actions.rb",
34
+ "lib/rspec-background-process/server.rb",
35
+ "rspec-background-process.gemspec",
36
+ "spec/background_process_helpers_spec.rb",
37
+ "spec/background_process_spec.rb",
38
+ "spec/features/cwd_spec.rb",
39
+ "spec/features/dead_detection_spec.rb",
40
+ "spec/features/exec_and_loading_spec.rb",
41
+ "spec/features/pool_lru_spec.rb",
42
+ "spec/features/ready_test_spec.rb",
43
+ "spec/features/refresh_spec.rb",
44
+ "spec/features/reuse_spec.rb",
45
+ "spec/features/server_spec.rb",
46
+ "spec/features/variables_replacement_spec.rb",
47
+ "spec/process_definition_spec.rb",
48
+ "spec/spec_helper.rb",
49
+ "spec/support/test_die",
50
+ "spec/support/test_http_server",
51
+ "spec/support/test_process",
52
+ "spec/support/test_slow_die"
53
+ ]
54
+ s.homepage = "http://github.com/jpastuszek/rspec-background-process"
55
+ s.licenses = ["MIT"]
56
+ s.require_paths = ["lib"]
57
+ s.rubygems_version = "1.8.23"
58
+ s.summary = "RSpec and Cucumber DSL library that helps managing background processes during test runs"
59
+
60
+ if s.respond_to? :specification_version then
61
+ s.specification_version = 3
62
+
63
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
64
+ s.add_development_dependency(%q<faraday>, [">= 0.8"])
65
+ s.add_development_dependency(%q<rspec>, ["~> 3.1"])
66
+ s.add_development_dependency(%q<jeweler>, ["~> 1.8.4"])
67
+ s.add_development_dependency(%q<bundler>, ["~> 1.0"])
68
+ s.add_development_dependency(%q<daemon>, ["~> 1.2"])
69
+ s.add_development_dependency(%q<micromachine>, ["~> 1.1"])
70
+ s.add_development_dependency(%q<rufus-lru>, ["~> 1.0"])
71
+ s.add_development_dependency(%q<file-tail>, ["~> 1.0"])
72
+ s.add_development_dependency(%q<retries>, ["~> 0.0.5"])
73
+ else
74
+ s.add_dependency(%q<faraday>, [">= 0.8"])
75
+ s.add_dependency(%q<rspec>, ["~> 3.1"])
76
+ s.add_dependency(%q<jeweler>, ["~> 1.8.4"])
77
+ s.add_dependency(%q<bundler>, ["~> 1.0"])
78
+ s.add_dependency(%q<daemon>, ["~> 1.2"])
79
+ s.add_dependency(%q<micromachine>, ["~> 1.1"])
80
+ s.add_dependency(%q<rufus-lru>, ["~> 1.0"])
81
+ s.add_dependency(%q<file-tail>, ["~> 1.0"])
82
+ s.add_dependency(%q<retries>, ["~> 0.0.5"])
83
+ end
84
+ else
85
+ s.add_dependency(%q<faraday>, [">= 0.8"])
86
+ s.add_dependency(%q<rspec>, ["~> 3.1"])
87
+ s.add_dependency(%q<jeweler>, ["~> 1.8.4"])
88
+ s.add_dependency(%q<bundler>, ["~> 1.0"])
89
+ s.add_dependency(%q<daemon>, ["~> 1.2"])
90
+ s.add_dependency(%q<micromachine>, ["~> 1.1"])
91
+ s.add_dependency(%q<rufus-lru>, ["~> 1.0"])
92
+ s.add_dependency(%q<file-tail>, ["~> 1.0"])
93
+ s.add_dependency(%q<retries>, ["~> 0.0.5"])
94
+ end
95
+ end
96
+
@@ -0,0 +1,32 @@
1
+ require_relative 'spec_helper'
2
+
3
+ describe BackgroundProcessHelpers, with: :background_process do
4
+ describe '#process_pool' do
5
+ it 'should provide singleton pool object ' do
6
+ expect {
7
+ process_pool
8
+ }.not_to change {
9
+ process_pool
10
+ }
11
+ end
12
+ end
13
+
14
+ describe '#background_process' do
15
+ it 'should allow specifying executable to run' do
16
+ process = background_process('features/support/test_process')
17
+ expect(process.instance.command).to include 'features/support/test_process'
18
+ end
19
+
20
+ describe 'load option' do
21
+ it 'when set to true will change instance type to LoadedBackgroundProcess' do
22
+ process = background_process('features/support/test_process', load: true)
23
+ expect(process.instance).to be_a RSpecBackgroundProcess::LoadedBackgroundProcess
24
+ end
25
+ end
26
+
27
+ it 'should return process definition' do
28
+ process = background_process('features/support/test_process')
29
+ expect(process).to be_a RSpecBackgroundProcess::ProcessPool::ProcessDefinition
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,90 @@
1
+ require_relative 'spec_helper'
2
+
3
+ describe RSpecBackgroundProcess::BackgroundProcess, subject: :instance do
4
+ describe 'running' do
5
+ it '#command should represent command to be executed' do
6
+ expect(subject.command).to include 'test_process'
7
+ end
8
+
9
+ it '#start should spawn the process by calling #spawn' do
10
+ expect(subject).to receive(:spawn).once.and_return([0, Thread.new{}])
11
+ subject.start
12
+ end
13
+
14
+ it '#stop should stop the process by sending TERM signal' do
15
+ expect(subject).to receive(:spawn).once.and_return([0, Thread.new{sleep 0.1}])
16
+ subject.start
17
+
18
+ expect(Process).to receive(:kill).once.with('TERM', kind_of(Numeric))
19
+ subject.stop
20
+ end
21
+
22
+ it '#restart should stop and then start the process' do
23
+ expect(subject).to receive(:spawn).once.and_return([0, Thread.new{sleep 0.1}])
24
+ subject.start
25
+
26
+ expect(subject).to receive(:spawn).once.and_return([1, Thread.new{sleep 0.1}])
27
+ expect(Process).to receive(:kill).once.with('TERM', kind_of(Numeric))
28
+
29
+ expect {
30
+ subject.restart
31
+ }.to change {
32
+ subject.pid
33
+ }.from(0).to(1)
34
+ end
35
+
36
+ describe 'related predicates' do
37
+ it '#pid should be integer when process is running and nil otherwise' do
38
+ expect(subject.pid).to be_nil
39
+
40
+ expect(subject).to receive(:spawn).once.and_return([42, Thread.new{sleep 0.1}])
41
+ subject.start
42
+
43
+ expect(subject.pid).to be_a(Integer).and eq(42)
44
+
45
+ expect(Process).to receive(:kill).once.with('TERM', kind_of(Numeric))
46
+ subject.stop
47
+ expect(subject.pid).to be_nil
48
+ end
49
+
50
+ it '#running? should be true when process is running' do
51
+ expect(subject).not_to be_running
52
+ expect(subject).to receive(:spawn).once.and_return([42, Thread.new{sleep 0.1}])
53
+ subject.start
54
+ expect(subject).to be_running
55
+ end
56
+
57
+ it '#dead? should be true when process exited but was not stopped by us' do
58
+ expect(subject).not_to be_dead
59
+ expect(subject).to receive(:spawn).once.and_return([42, Thread.new{sleep 0.1}])
60
+ subject.start
61
+ expect(subject).not_to be_dead
62
+
63
+ expect(Process).to receive(:kill).once.with('TERM', kind_of(Numeric))
64
+ subject.stop
65
+ expect(subject).not_to be_dead
66
+
67
+ expect(subject).to receive(:spawn).once.and_return([42, Thread.new{}])
68
+ subject.start
69
+ sleep 0.2
70
+ expect(subject).to be_dead
71
+ end
72
+ end
73
+ end
74
+
75
+ describe 'readiness', subject: :process do
76
+ it 'instance #wait_ready should call readiness block with self as argument' do
77
+ expect { |b|
78
+ subject.ready_test do |*args|
79
+ b.to_proc.call(*args)
80
+ # need to return true
81
+ true
82
+ end
83
+ instance = subject.instance
84
+ expect(instance).to receive(:spawn).once.and_return([42, Thread.new{sleep 0.1}])
85
+ instance.start
86
+ instance.wait_ready
87
+ }.to yield_with_args(RSpecBackgroundProcess::BackgroundProcess)
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,51 @@
1
+ require_relative '../spec_helper'
2
+
3
+ def process_cwd_from_log(log)
4
+ log.readlines.grep(/^cwd:/).first.split(': ', 2).last.match(/'(.*)'/).captures.first.strip || fail('cannot extract cwd from log')
5
+ end
6
+
7
+ feature 'auto managing current working directory', subject: :process_ready_variables do
8
+ scenario 'processes by default run in own different directory' do
9
+ instance1 = subject.start.wait_ready
10
+ instance2 = subject.with{|p| p.argument 'blah'}.start.wait_ready
11
+
12
+ # reported cwd
13
+ expect(instance1.working_directory.to_s).not_to eq(instance2.working_directory.to_s)
14
+
15
+ # actual cwd
16
+ expect(process_cwd_from_log(instance1.log_file)).to eq(instance1.working_directory.realpath.to_s)
17
+ expect(process_cwd_from_log(instance2.log_file)).to eq(instance2.working_directory.realpath.to_s)
18
+ end
19
+ end
20
+
21
+ feature 'process current working directory does not affect test current directory', subject: :process_ready_variables do
22
+ scenario 'master process current working directory unchanged over instance start' do
23
+ expect {
24
+ subject.start.wait_ready
25
+ }.not_to change {
26
+ Dir.pwd
27
+ }
28
+
29
+ # reported cwd
30
+ expect(subject.instance.working_directory.to_s).not_to eq(Dir.pwd)
31
+
32
+ # actual cwd
33
+ expect(process_cwd_from_log(subject.instance.log_file)).to eq(subject.instance.working_directory.realpath.to_s)
34
+ end
35
+ end
36
+
37
+ feature 'current working directory can be configured to custom directory', subject: :process_ready_variables do
38
+ let :test_dir do
39
+ Pathname.new(Dir.pwd) + 'tmp'
40
+ end
41
+
42
+ scenario 'setting current working directory to test directory' do
43
+ instance = subject.with{|p| p.working_directory test_dir}.start.wait_ready
44
+
45
+ # reported cwd
46
+ expect(instance.working_directory.to_s).to eq(test_dir.to_s)
47
+
48
+ # actual cwd
49
+ expect(process_cwd_from_log(instance.log_file)).to eq(instance.working_directory.realpath.to_s)
50
+ end
51
+ end
@@ -0,0 +1,81 @@
1
+ require_relative '../spec_helper'
2
+
3
+ feature 'background process is marked as dead if it exits before stop was called', subject: :dying_process do
4
+ scenario 'running dieing process renders it marked as dead' do
5
+ instance = subject.start
6
+ expect(instance).not_to be_dead
7
+
8
+ # git it some time to spawn and exit
9
+ sleep 0.5
10
+ expect(instance).to be_dead
11
+ end
12
+ end
13
+
14
+ feature 'dead instance should not be usable any more', subject: :dying_process do
15
+ scenario 'trying to #start dead instance' do
16
+ instance = subject.start
17
+
18
+ # git it some time to spawn and exit
19
+ sleep 0.5
20
+
21
+ expect {
22
+ instance.start
23
+ }.to raise_error RSpecBackgroundProcess::BackgroundProcess::StateError, /can't start when in state: dead/
24
+ end
25
+
26
+ scenario 'trying to #stop or #restart dead instance' do
27
+ instance = subject.start
28
+
29
+ # git it some time to spawn and exit
30
+ sleep 0.5
31
+
32
+ expect {
33
+ instance.stop
34
+ }.to raise_error RSpecBackgroundProcess::BackgroundProcess::StateError, /can't stop when in state: dead/
35
+
36
+ expect {
37
+ instance.restart
38
+ }.to raise_error RSpecBackgroundProcess::BackgroundProcess::StateError, /can't stop when in state: dead/
39
+ end
40
+
41
+ scenario 'trying to #wait_ready on dead instance' do
42
+ instance = subject.start
43
+
44
+ # git it some time to spawn and exit
45
+ sleep 0.5
46
+
47
+ expect {
48
+ instance.wait_ready
49
+ }.to raise_error RSpecBackgroundProcess::BackgroundProcess::StateError, /can't wait ready when in state: dead/
50
+ end
51
+ end
52
+
53
+ feature 'detecting instance death wile waiting for it to become ready', subject: :slowly_dying_process do
54
+ context 'with log file based check' do
55
+ scenario 'instance dying while we wait for it to become ready' do
56
+ subject.ready_when_log_includes 'bye'
57
+
58
+ instance = subject.start
59
+
60
+ expect {
61
+ instance.wait_ready
62
+ }.to raise_error RSpecBackgroundProcess::BackgroundProcess::ProcessExitedError, /exited with exit code: 0/
63
+
64
+ expect(instance).to be_dead
65
+ end
66
+ end
67
+
68
+ context 'with URL based check' do
69
+ scenario 'instance dying while we wait for it to become ready' do
70
+ subject.ready_when_url_response_status 'http://localhost:1234/health_check', 'OK'
71
+
72
+ instance = subject.start
73
+
74
+ expect {
75
+ instance.wait_ready
76
+ }.to raise_error RSpecBackgroundProcess::BackgroundProcess::ProcessExitedError, /exited with exit code: 0/
77
+
78
+ expect(instance).to be_dead
79
+ end
80
+ end
81
+ end