rspec-background-process 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,26 @@
1
+ require_relative '../spec_helper'
2
+
3
+ feature 'support for starting new process via fort to exec or fork followed by Ruby load', with: :background_process do
4
+ context 'default value is used' do
5
+ scenario 'replace forked ruby interpreter with given executable via exec' do
6
+ process = background_process('spec/support/test_process')
7
+
8
+ process.ready_when_log_includes "ENV['PROCESS_SPAWN_TYPE']"
9
+ instance = process.start.wait_ready
10
+
11
+ expect(instance.log_file.read).to include "ENV['PROCESS_SPAWN_TYPE']: exec"
12
+ end
13
+ end
14
+
15
+ context 'load is set to true' do
16
+ scenario 'load ruby code directly into forked interpreter via load' do
17
+ process = background_process('spec/support/test_process', load: true)
18
+
19
+ process.ready_when_log_includes "ENV['PROCESS_SPAWN_TYPE']"
20
+ instance = process.start.wait_ready
21
+
22
+ expect(instance.log_file.read).to include "ENV['PROCESS_SPAWN_TYPE']: load"
23
+ end
24
+ end
25
+ end
26
+
@@ -0,0 +1,23 @@
1
+ require_relative '../spec_helper'
2
+
3
+ feature 'process pool can keep limited number of processes running between test evicting processes using LRU', subject: :process do
4
+ scenario 'extra LRU processes (over 4 by default) are stopped between tests' do
5
+ instances = []
6
+ instances << subject.with{|p| p.argument '1'}.start
7
+ instances << subject.with{|p| p.argument '2'}.start
8
+ instances << subject.with{|p| p.argument '3'}.start
9
+ instances << subject.with{|p| p.argument '4'}.start
10
+ instances << subject.with{|p| p.argument '5'}.start
11
+
12
+ # keeps all running
13
+ expect(instances).to all be_running
14
+
15
+ # let LRU do the job (executed after each scenario/example)
16
+ process_pool.cleanup
17
+
18
+ # first instance should not be running (LRU)
19
+ expect(instances.shift).not_to be_running
20
+ # remaining 4 should be kept running
21
+ expect(instances).to all be_running
22
+ end
23
+ end
@@ -0,0 +1,48 @@
1
+ require_relative '../spec_helper'
2
+
3
+ feature 'background process readiness verification' do
4
+ context 'with log file based check', subject: :process do
5
+ scenario 'raise exception when no readiness check was defined for process instance' do
6
+ expect {
7
+ subject.start.wait_ready
8
+ }.to raise_error RuntimeError, 'no readiness check defined'
9
+ end
10
+
11
+ scenario 'starting a background process with readiness check based on log file contents' do
12
+ process = subject.with do |process|
13
+ process.ready_when_log_includes 'started'
14
+ end
15
+
16
+ instance = process.instance
17
+ instance.start
18
+
19
+ expect(instance).to be_running
20
+ expect(instance.log_file.read).not_to include 'started'
21
+
22
+ instance.wait_ready
23
+
24
+ expect(instance).to be_ready
25
+ expect(instance.log_file.read).to include 'started'
26
+ end
27
+ end
28
+
29
+ context 'with URL based check', subject: :http_process do
30
+ scenario 'starting a background process with readiness check based on HTTP request' do
31
+ process = subject.with do |process|
32
+ process.ready_when_url_response_status 'http://localhost:1234/health_check', 'OK'
33
+ end
34
+
35
+ instance = process.instance
36
+ instance.start
37
+
38
+ expect(instance).to be_running
39
+ expect(instance.log_file.read).not_to include 'got health_check request'
40
+
41
+ instance.wait_ready
42
+
43
+ expect(instance).to be_ready
44
+ expect(instance.log_file.read).to include 'got health_check request'
45
+ end
46
+ end
47
+ end
48
+
@@ -0,0 +1,46 @@
1
+ require_relative '../spec_helper'
2
+
3
+ feature 'refreshing pooled processes state', subject: :process do
4
+ scenario 'by default processes are restarted on refresh' do
5
+ instance = subject.start
6
+
7
+ expect {
8
+ instance.refresh
9
+ }.to change {
10
+ instance.pid
11
+ }
12
+ end
13
+
14
+ context 'with custom command' do
15
+ let! :test_marker do
16
+ Pathname.new('/tmp/processtest1')
17
+ end
18
+
19
+ before do
20
+ test_marker.exist? and test_marker.unlink
21
+ end
22
+
23
+ scenario 'refresh executes custom command' do
24
+ instance = subject.with do |process|
25
+ process.refresh_command "touch #{test_marker}"
26
+ end.start
27
+
28
+ expect(test_marker).not_to exist
29
+
30
+ instance.refresh
31
+
32
+ expect(test_marker).to exist
33
+ end
34
+
35
+ scenario 'refresh execute command in current working directory of the process' do
36
+ instance = subject.with do |process|
37
+ process.refresh_command "pwd > /tmp/pwd"
38
+ end.start
39
+
40
+ instance.refresh
41
+
42
+ expect(Pathname.new(Pathname.new('/tmp/pwd').read.strip).realpath.to_s).to eq instance.working_directory.realpath.to_s
43
+ end
44
+ end
45
+ end
46
+
@@ -0,0 +1,81 @@
1
+ require_relative '../spec_helper'
2
+
3
+ feature 'processes with same definition reuse single instance', subject: :process do
4
+ context 'arguments' do
5
+ scenario 'defining two instances with same arguments' do
6
+ instance1 = subject.with{|p| p.argument 'foo'}.instance
7
+ instance2 = subject.with{|p| p.argument 'foo'}.instance
8
+
9
+ expect(instance1).to eq(instance2)
10
+ end
11
+
12
+ scenario 'defining two instances with different arguments' do
13
+ instance1 = subject.with{|p| p.argument 'foo'}.instance
14
+ instance2 = subject.with{|p| p.argument 'bar'}.instance
15
+
16
+ expect(instance1).not_to eq(instance2)
17
+ end
18
+ end
19
+
20
+ context 'working directory' do
21
+ scenario 'defining two instances with same working directory' do
22
+ instance1 = subject.with{|p| p.working_directory 'foo'}.instance
23
+ instance2 = subject.with{|p| p.working_directory 'foo'}.instance
24
+
25
+ expect(instance1).to eq(instance2)
26
+ end
27
+
28
+ scenario 'defining two instances with different working directory' do
29
+ instance1 = subject.with{|p| p.working_directory 'foo'}.instance
30
+ instance2 = subject.with{|p| p.working_directory 'bar'}.instance
31
+
32
+ expect(instance1).not_to eq(instance2)
33
+ end
34
+ end
35
+
36
+ context 'extensions' do
37
+ scenario 'defining two instances with same extensions but different options' do
38
+ instance1 = subject.with{|p| p.extend RSpecBackgroundProcess::BackgroundProcess::Server, port_count: 3, base_port: 1200}.instance
39
+ instance2 = subject.with{|p| p.extend RSpecBackgroundProcess::BackgroundProcess::Server, port_count: 1, base_port: 1200}.instance
40
+
41
+ expect(instance1).to eq(instance2)
42
+ end
43
+
44
+ scenario 'defining two instances with different extensions' do
45
+ instance1 = subject.instance
46
+ instance2 = subject.with{|p| p.extend RSpecBackgroundProcess::BackgroundProcess::Server, port_count: 1, base_port: 1200}.instance
47
+
48
+ expect(instance1).not_to eq(instance2)
49
+ end
50
+ end
51
+
52
+ context 'options not affecting instance key (reused)' do
53
+ scenario 'logging settings differ' do
54
+ instance1 = subject.instance
55
+ instance2 = subject.with{|p| p.logging_enabled}.instance
56
+
57
+ expect(instance1).to eq(instance2)
58
+ end
59
+
60
+ scenario 'ready test differ' do
61
+ instance1 = subject.with{|p| p.ready_test{|i| false}}.instance
62
+ instance2 = subject.with{|p| p.ready_test{|i| true}}.instance
63
+
64
+ expect(instance1).to eq(instance2)
65
+ end
66
+
67
+ scenario 'refresh action differ' do
68
+ instance1 = subject.with{|p| p.refresh_action{|i| false}}.instance
69
+ instance2 = subject.with{|p| p.refresh_action{|i| true}}.instance
70
+
71
+ expect(instance1).to eq(instance2)
72
+ end
73
+
74
+ scenario 'timeouts differ' do
75
+ instance1 = subject.with{|p| p.ready_timeout(1); p.kill_timeout(1); p.term_timeout(1)}.instance
76
+ instance2 = subject.with{|p| p.ready_timeout(2); p.kill_timeout(2); p.term_timeout(2)}.instance
77
+
78
+ expect(instance1).to eq(instance2)
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,35 @@
1
+ require_relative '../spec_helper'
2
+
3
+ feature 'server process with port allocation', subject: :http_process_ready_variables do
4
+ scenario 'allocating port for new process instance form pool' do
5
+ instance = subject.with do |process|
6
+ process.http_port_allocated_form 1400, 4
7
+ end.instance
8
+
9
+ expect(instance.ports).to contain_exactly 1400, 1401, 1402, 1403
10
+ end
11
+
12
+ scenario 'new ports allocated for new instance' do
13
+ # use up a port
14
+ subject.with do |process|
15
+ process.http_port_allocated_form 1500, 1
16
+ end.instance
17
+
18
+ instance = subject.with do |process|
19
+ process.argument 'foo'
20
+ process.http_port_allocated_form 1500, 1
21
+ end.instance
22
+
23
+ expect(instance.ports).to contain_exactly 1501
24
+ end
25
+
26
+ scenario 'using port number with argument value' do
27
+ instance = subject.with do |process|
28
+ process.argument '--listen', 'localhost:<allocated port 1>'
29
+ process.http_port_allocated_form 1600, 1
30
+ end.instance
31
+ instance.start.wait_ready
32
+
33
+ expect(instance.log_file.read).to include('"localhost:1600"').and include('listening on port: 1600')
34
+ end
35
+ end
@@ -0,0 +1,68 @@
1
+ require_relative '../spec_helper'
2
+
3
+ feature 'instance variable replacement', subject: :process_ready_variables do
4
+ # some variables are only available while the instance is about to be started or even after it was forked
5
+ # to be able to use this variables to set up the process run they need to be replaced in arguments etc.
6
+
7
+ context 'argument expansion' do
8
+ scenario 'with log file' do
9
+ instance = subject.with do |process|
10
+ process.argument 'log file:<log file>'
11
+ end.instance
12
+ instance.start.wait_ready
13
+
14
+ expect(instance.log_file.read).to include "ARGV: [\"log file:#{instance.log_file}\"]"
15
+ end
16
+
17
+ scenario 'with pid file' do
18
+ instance = subject.with do |process|
19
+ process.argument 'pid file:<pid file>'
20
+ end.instance
21
+ instance.start.wait_ready
22
+
23
+ expect(instance.log_file.read).to include "ARGV: [\"pid file:#{instance.pid_file}\"]"
24
+ end
25
+
26
+ scenario 'with working directory' do
27
+ instance = subject.with do |process|
28
+ process.argument 'working directory:<working directory>'
29
+ end.instance
30
+ instance.start.wait_ready
31
+
32
+ expect(instance.log_file.read).to include "ARGV: [\"working directory:#{instance.working_directory}\"]"
33
+ end
34
+
35
+ scenario 'with instance name' do
36
+ instance = subject.with do |process|
37
+ process.argument 'name:<name>'
38
+ end.instance
39
+ instance.start.wait_ready
40
+
41
+ expect(instance.log_file.read).to include "ARGV: [\"name:#{instance.name}\"]"
42
+ end
43
+
44
+ scenario 'with project directory' do
45
+ instance = subject.with do |process|
46
+ process.argument 'project directory:<project directory>'
47
+ end.instance
48
+ instance.start.wait_ready
49
+
50
+ cwd = Dir.pwd
51
+
52
+ expect(instance.log_file.read).to include "ARGV: [\"project directory:#{cwd}\"]"
53
+ end
54
+
55
+ context 'server', subject: :http_process_ready_variables do
56
+ scenario 'with port numbers' do
57
+ instance = subject.with do |process|
58
+ process.argument 'port number 1:<allocated port 1>'
59
+ process.argument 'port number 2:<allocated port 2>'
60
+ process.http_port_allocated_form 1700, 2
61
+ end.instance
62
+ instance.start.wait_ready
63
+
64
+ expect(instance.log_file.read).to include("ARGV: [\"port number 1:1700\", \"port number 2:1701\"]")
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,182 @@
1
+ require_relative 'spec_helper'
2
+
3
+ describe RSpecBackgroundProcess::ProcessPool::ProcessDefinition, subject: :process do
4
+ describe '#with' do
5
+ it 'should execute passed block it in context of cloned process definition' do
6
+ expect { |b|
7
+ subject.with(&b)
8
+ }.to yield_with_args(
9
+ an_instance_of(RSpecBackgroundProcess::ProcessPool::ProcessDefinition)
10
+ .and different_than subject
11
+ )
12
+ end
13
+
14
+ example 'defining process variation' do
15
+ hello_process = subject.with do |process|
16
+ process.argument 'hello'
17
+ process.logging_enabled
18
+ end
19
+
20
+ hello_foo_bar_process = hello_process.with do |process|
21
+ process.argument 'foo-bar'
22
+ end
23
+
24
+ expect {
25
+ hello_process.start
26
+ }.to output(
27
+ a_string_matching 'starting.*spec/support/test_process hello`'
28
+ ).to_stdout
29
+
30
+ expect {
31
+ hello_foo_bar_process.start
32
+ }.to output(
33
+ a_string_matching 'starting.*spec/support/test_process hello foo-bar'
34
+ ).to_stdout
35
+ end
36
+ end
37
+
38
+ describe '#instance' do
39
+ it 'should create new process instance' do
40
+ expect(RSpecBackgroundProcess::BackgroundProcess).to receive(:new)
41
+ .and_call_original
42
+
43
+ subject.instance
44
+ end
45
+
46
+ it 'should not crate new process when called more than once' do
47
+ expect(RSpecBackgroundProcess::BackgroundProcess).to receive(:new)
48
+ .once
49
+ .and_call_original
50
+
51
+ subject.instance
52
+ subject.instance
53
+ end
54
+ end
55
+
56
+ describe '#argument' do
57
+ it 'should pass given argument to process' do
58
+ expect(RSpecBackgroundProcess::BackgroundProcess).to receive(:new)
59
+ .with(anything, anything, a_collection_containing_exactly('foo bar', 'baz'), anything, anything)
60
+ .and_call_original
61
+
62
+ subject.argument 'foo bar'
63
+ subject.argument 'baz'
64
+
65
+ subject.instance
66
+ end
67
+
68
+ it 'should pass given option argument to process' do
69
+ expect(RSpecBackgroundProcess::BackgroundProcess).to receive(:new)
70
+ .with(anything, anything, a_collection_containing_exactly('--foo', 'bar', '--baz', 'hello world'), anything, anything)
71
+ .and_call_original
72
+
73
+ subject.argument '--foo', 'bar'
74
+ subject.argument '--baz', 'hello world'
75
+
76
+ subject.instance
77
+ end
78
+ end
79
+
80
+ describe '#extend' do
81
+ it 'should extend instance with given extension' do
82
+ TestExtension = Module.new
83
+ extension = class_spy('TestExtension')
84
+
85
+ process = subject.with do |process|
86
+ process.extend extension
87
+ end
88
+
89
+ # used for key generation
90
+ expect(extension).to receive(:name).and_return('test')
91
+
92
+ # actually used to extend background process
93
+ expect(extension).to receive(:extended) do |instance|
94
+ expect(instance).to be_an_instance_of(RSpecBackgroundProcess::LoadedBackgroundProcess)
95
+ end.once
96
+
97
+ process.instance
98
+ end
99
+ end
100
+
101
+ describe '#read_timeout' do
102
+ it 'should pass given argument to process' do
103
+ expect(RSpecBackgroundProcess::BackgroundProcess).to receive(:new)
104
+ .with(anything, anything, anything, anything, a_hash_including(ready_timeout: 42))
105
+ .and_call_original
106
+
107
+ subject.ready_timeout 42
108
+
109
+ subject.instance
110
+ end
111
+ end
112
+
113
+ describe '#term_timeout' do
114
+ it 'should pass given argument to process' do
115
+ expect(RSpecBackgroundProcess::BackgroundProcess).to receive(:new)
116
+ .with(anything, anything, anything, anything, a_hash_including(term_timeout: 42))
117
+ .and_call_original
118
+
119
+ subject.term_timeout 42
120
+
121
+ subject.instance
122
+ end
123
+ end
124
+
125
+ describe '#kill_timeout' do
126
+ it 'should pass given argument to process' do
127
+ expect(RSpecBackgroundProcess::BackgroundProcess).to receive(:new)
128
+ .with(anything, anything, anything, anything, a_hash_including(kill_timeout: 42))
129
+ .and_call_original
130
+
131
+ subject.kill_timeout 42
132
+
133
+ subject.instance
134
+ end
135
+ end
136
+
137
+ describe '#ready_test' do
138
+ it 'should register a block to be called when process is verified' do
139
+ expect { |b|
140
+ subject.ready_test do |*args|
141
+ b.to_proc.call(*args)
142
+ # need to return true
143
+ true
144
+ end
145
+
146
+ #TODO: only test if the block was passed to the instance
147
+ subject.instance.start.wait_ready
148
+ }.to yield_control
149
+ end
150
+ end
151
+
152
+ describe 'logging' do
153
+ specify 'logging should be disabled by default' do
154
+ process_pool.logging_enabled? and skip 'logging enabled by default'
155
+ expect {
156
+ subject.instance.start
157
+ }.not_to output.to_stdout
158
+ end
159
+
160
+ describe '#logging_enable' do
161
+ it 'should make the instance to print out state changes' do
162
+ subject.logging_enabled
163
+
164
+ expect {
165
+ subject.instance.start
166
+ }.to output(
167
+ a_string_including 'process is now running'
168
+ ).to_stdout
169
+ end
170
+ end
171
+
172
+ describe '#logging_enabled?' do
173
+ it 'should be true when instance logging is enabled' do
174
+ process_pool.logging_enabled? and skip 'logging enabled by default'
175
+
176
+ expect(subject).not_to be_logging_enabled
177
+ subject.logging_enabled
178
+ expect(subject).to be_logging_enabled
179
+ end
180
+ end
181
+ end
182
+ end