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,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