nonnative 2.23.0 → 3.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 +4 -4
- data/.circleci/config.yml +4 -4
- data/Gemfile.lock +11 -11
- data/README.md +133 -51
- data/lib/nonnative/configuration.rb +45 -31
- data/lib/nonnative/configuration_process.rb +1 -1
- data/lib/nonnative/configuration_runner.rb +25 -4
- data/lib/nonnative/configuration_server.rb +1 -1
- data/lib/nonnative/configuration_service.rb +28 -0
- data/lib/nonnative/fault_injection_proxy.rb +3 -3
- data/lib/nonnative/grpc_server.rb +1 -1
- data/lib/nonnative/http_proxy_server.rb +5 -4
- data/lib/nonnative/http_server.rb +3 -3
- data/lib/nonnative/no_proxy.rb +2 -2
- data/lib/nonnative/pool.rb +13 -11
- data/lib/nonnative/port.rb +12 -10
- data/lib/nonnative/ports.rb +34 -0
- data/lib/nonnative/socket_pair.rb +7 -7
- data/lib/nonnative/version.rb +1 -1
- data/lib/nonnative.rb +3 -2
- data/nonnative.gemspec +1 -1
- metadata +4 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 3e20a9fa436249c182c4fc90bfaaeeaed91035899d31340eba35d1f4208a31ab
|
|
4
|
+
data.tar.gz: c68e618e0a2a48cf9958210a8200a96c2aff7c0406eeec332bea01cf9290e860
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: c228d28fa41f6e9d526c107feedb4aebfed673aca7b47d7262913db7ceb04468772a1ca068fd4cf696a46a50fc52f01f3dfb9934547945c6ef78b03298cd6cc4
|
|
7
|
+
data.tar.gz: 60ebed64503ac1505ccd4398330290627600f14519d40389ba96a5e02189762e7d54b314ee98fdd31220f9aba560fc6e09ad7340ffc3925b3f3359747bcf0c0f
|
data/.circleci/config.yml
CHANGED
|
@@ -3,7 +3,7 @@ version: 2.1
|
|
|
3
3
|
jobs:
|
|
4
4
|
build:
|
|
5
5
|
docker:
|
|
6
|
-
- image: alexfalkowski/ruby:3.
|
|
6
|
+
- image: alexfalkowski/ruby:3.14
|
|
7
7
|
working_directory: ~/nonnative
|
|
8
8
|
steps:
|
|
9
9
|
- checkout:
|
|
@@ -35,7 +35,7 @@ jobs:
|
|
|
35
35
|
resource_class: arm.large
|
|
36
36
|
sync:
|
|
37
37
|
docker:
|
|
38
|
-
- image: alexfalkowski/release:8.
|
|
38
|
+
- image: alexfalkowski/release:8.14
|
|
39
39
|
working_directory: ~/nonnative
|
|
40
40
|
steps:
|
|
41
41
|
- checkout:
|
|
@@ -46,7 +46,7 @@ jobs:
|
|
|
46
46
|
resource_class: arm.large
|
|
47
47
|
version:
|
|
48
48
|
docker:
|
|
49
|
-
- image: alexfalkowski/release:8.
|
|
49
|
+
- image: alexfalkowski/release:8.14
|
|
50
50
|
working_directory: ~/nonnative
|
|
51
51
|
steps:
|
|
52
52
|
- checkout:
|
|
@@ -58,7 +58,7 @@ jobs:
|
|
|
58
58
|
resource_class: arm.large
|
|
59
59
|
wait-all:
|
|
60
60
|
docker:
|
|
61
|
-
- image: alexfalkowski/ruby:3.
|
|
61
|
+
- image: alexfalkowski/ruby:3.14
|
|
62
62
|
steps:
|
|
63
63
|
- run: echo "all applicable jobs finished"
|
|
64
64
|
resource_class: arm.large
|
data/Gemfile.lock
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
|
-
nonnative (
|
|
4
|
+
nonnative (3.1.0)
|
|
5
5
|
concurrent-ruby (>= 1, < 2)
|
|
6
6
|
config (>= 5, < 6)
|
|
7
7
|
cucumber (>= 7, < 12)
|
|
@@ -30,7 +30,7 @@ GEM
|
|
|
30
30
|
config (5.6.1)
|
|
31
31
|
deep_merge (~> 1.2, >= 1.2.1)
|
|
32
32
|
ostruct
|
|
33
|
-
cucumber (11.
|
|
33
|
+
cucumber (11.1.0)
|
|
34
34
|
base64 (~> 0.2)
|
|
35
35
|
builder (~> 3.2)
|
|
36
36
|
cucumber-ci-environment (> 9, < 12)
|
|
@@ -64,24 +64,24 @@ GEM
|
|
|
64
64
|
get_process_mem (1.0.0)
|
|
65
65
|
bigdecimal (>= 2.0)
|
|
66
66
|
ffi (~> 1.0)
|
|
67
|
-
google-protobuf (4.
|
|
67
|
+
google-protobuf (4.35.0-x86_64-darwin)
|
|
68
68
|
bigdecimal
|
|
69
69
|
rake (~> 13.3)
|
|
70
|
-
google-protobuf (4.
|
|
70
|
+
google-protobuf (4.35.0-x86_64-linux-gnu)
|
|
71
71
|
bigdecimal
|
|
72
72
|
rake (~> 13.3)
|
|
73
|
-
googleapis-common-protos-types (1.
|
|
73
|
+
googleapis-common-protos-types (1.23.0)
|
|
74
74
|
google-protobuf (~> 4.26)
|
|
75
|
-
grpc (1.
|
|
75
|
+
grpc (1.81.0-x86_64-darwin)
|
|
76
76
|
google-protobuf (>= 3.25, < 5.0)
|
|
77
77
|
googleapis-common-protos-types (~> 1.0)
|
|
78
|
-
grpc (1.
|
|
78
|
+
grpc (1.81.0-x86_64-linux-gnu)
|
|
79
79
|
google-protobuf (>= 3.25, < 5.0)
|
|
80
80
|
googleapis-common-protos-types (~> 1.0)
|
|
81
81
|
http-accept (1.7.0)
|
|
82
82
|
http-cookie (1.1.6)
|
|
83
83
|
domain_name (~> 0.5)
|
|
84
|
-
json (2.19.
|
|
84
|
+
json (2.19.8)
|
|
85
85
|
language_server-protocol (3.17.0.5)
|
|
86
86
|
lint_roller (1.1.0)
|
|
87
87
|
logger (1.7.0)
|
|
@@ -101,7 +101,7 @@ GEM
|
|
|
101
101
|
ast (~> 2.4.1)
|
|
102
102
|
racc
|
|
103
103
|
prism (1.9.0)
|
|
104
|
-
puma (7.2.
|
|
104
|
+
puma (7.2.1)
|
|
105
105
|
nio4r (~> 2.0)
|
|
106
106
|
racc (1.8.1)
|
|
107
107
|
rack (3.2.6)
|
|
@@ -124,7 +124,7 @@ GEM
|
|
|
124
124
|
http-cookie (>= 1.0.2, < 2.0)
|
|
125
125
|
mime-types (>= 1.16, < 4.0)
|
|
126
126
|
netrc (~> 0.8)
|
|
127
|
-
retriable (3.
|
|
127
|
+
retriable (3.8.0)
|
|
128
128
|
rexml (3.4.4)
|
|
129
129
|
rspec (3.13.2)
|
|
130
130
|
rspec-core (~> 3.13.0)
|
|
@@ -146,7 +146,7 @@ GEM
|
|
|
146
146
|
rspec-support (3.13.7)
|
|
147
147
|
rspec-wait (1.0.2)
|
|
148
148
|
rspec (>= 3.4)
|
|
149
|
-
rubocop (1.
|
|
149
|
+
rubocop (1.87.0)
|
|
150
150
|
json (~> 2.3)
|
|
151
151
|
language_server-protocol (~> 3.17.0.2)
|
|
152
152
|
lint_roller (~> 1.1.0)
|
data/README.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
[](https://badge.fury.io/rb/nonnative)
|
|
4
4
|
[](https://masterminds.github.io/stability/active.html)
|
|
5
5
|
|
|
6
|
-
# Nonnative
|
|
6
|
+
# 🧪 Nonnative
|
|
7
7
|
|
|
8
8
|
Nonnative is a Ruby-first harness for end-to-end testing of systems implemented in other languages.
|
|
9
9
|
|
|
@@ -15,7 +15,10 @@ It helps you:
|
|
|
15
15
|
|
|
16
16
|
Once started, you can test however you like (TCP, HTTP, gRPC, etc).
|
|
17
17
|
|
|
18
|
-
## Installation
|
|
18
|
+
## 📦 Installation
|
|
19
|
+
|
|
20
|
+
> [!IMPORTANT]
|
|
21
|
+
> Nonnative currently supports Ruby `>= 4.0.0` and `< 5.0.0`.
|
|
19
22
|
|
|
20
23
|
Add this line to your application's Gemfile:
|
|
21
24
|
|
|
@@ -35,12 +38,15 @@ Or install it yourself as:
|
|
|
35
38
|
gem install nonnative
|
|
36
39
|
```
|
|
37
40
|
|
|
38
|
-
## Usage
|
|
41
|
+
## 🚀 Usage
|
|
39
42
|
|
|
40
43
|
Nonnative is configured via `Nonnative.configure` (programmatic) or `config.load_file(...)` (YAML).
|
|
41
44
|
YAML configuration is loaded as data only: ERB is not evaluated and arbitrary Ruby objects are not
|
|
42
45
|
deserialized.
|
|
43
46
|
|
|
47
|
+
> [!CAUTION]
|
|
48
|
+
> Treat YAML configuration as plain data. ERB is not evaluated and arbitrary Ruby object tags are rejected.
|
|
49
|
+
|
|
44
50
|
High-level configuration fields:
|
|
45
51
|
- `version`: configuration version (example: `"1.0"`).
|
|
46
52
|
- `name`: logical system name (used by `Nonnative.observability` for `/<name>/healthz`, etc).
|
|
@@ -52,18 +58,68 @@ High-level configuration fields:
|
|
|
52
58
|
|
|
53
59
|
Common runner fields:
|
|
54
60
|
- `name`: runner name used for lookup.
|
|
55
|
-
- `host
|
|
61
|
+
- `host`: client-facing host. Defaults to `127.0.0.1`.
|
|
56
62
|
|
|
57
63
|
Process/server fields:
|
|
64
|
+
- `ports`: client-facing ports. These are also used for readiness/shutdown port checks. When a `fault_injection` proxy is enabled, clients should hit the first configured port.
|
|
58
65
|
- `timeout`: max time (seconds) for readiness/shutdown port checks.
|
|
59
66
|
- `wait`: small sleep (seconds) between lifecycle steps.
|
|
60
67
|
- `log`: per-runner log file used by process output redirection or server implementations.
|
|
61
68
|
|
|
69
|
+
Service fields:
|
|
70
|
+
- `port`: client-facing proxy port. Services do not get TCP readiness/shutdown checks from Nonnative.
|
|
71
|
+
|
|
62
72
|
For `fault_injection`, the nested `proxy.host`/`proxy.port` describe the upstream target behind the proxy. Nested `proxy.host` also defaults to `127.0.0.1`. In-process server implementations typically bind there via `proxy.host` / `proxy.port`.
|
|
63
73
|
|
|
64
|
-
|
|
74
|
+
> [!IMPORTANT]
|
|
75
|
+
> When a proxy is enabled, tests and clients connect to the runner `host` and client-facing endpoint (`ports` first entry for processes/servers, `port` for services); the nested `proxy.host`/`proxy.port` is the upstream target behind the proxy.
|
|
76
|
+
|
|
77
|
+
Nonnative readiness and shutdown checks are TCP-only. Configure process/server ports that are dedicated to the test run; if another process is already listening on the same endpoint, results are undefined.
|
|
78
|
+
|
|
79
|
+
> [!WARNING]
|
|
80
|
+
> Readiness and shutdown checks only prove that a TCP port opened or closed. They do not verify HTTP status, gRPC health, schema readiness, migrations, or application-specific health.
|
|
81
|
+
|
|
82
|
+
Start and stop Nonnative around the test scope that should own the configured runners:
|
|
83
|
+
|
|
84
|
+
```ruby
|
|
85
|
+
require 'nonnative'
|
|
86
|
+
|
|
87
|
+
Nonnative.configure do |config|
|
|
88
|
+
config.load_file('configuration.yml')
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
Nonnative.start
|
|
92
|
+
# run tests...
|
|
93
|
+
Nonnative.stop
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
`Nonnative.start` starts services first, then servers and processes. `Nonnative.stop` stops processes and servers first, then services. If startup fails, Nonnative rolls back runners that already started and raises `Nonnative::StartError`; shutdown failures raise `Nonnative::StopError`.
|
|
97
|
+
|
|
98
|
+
> [!NOTE]
|
|
99
|
+
> `Nonnative.clear` clears memoized configuration, logger, observability client, and pool. Use it before reconfiguring Nonnative in the same Ruby process.
|
|
65
100
|
|
|
66
|
-
###
|
|
101
|
+
### 📈 Observability
|
|
102
|
+
|
|
103
|
+
`Nonnative.observability` is an HTTP client for common service endpoints under the configured `name` and `url`:
|
|
104
|
+
|
|
105
|
+
- `health(...)`: calls `/<name>/healthz`.
|
|
106
|
+
- `liveness(...)`: calls `/<name>/livez`.
|
|
107
|
+
- `readiness(...)`: calls `/<name>/readyz`.
|
|
108
|
+
- `metrics(...)`: calls `/<name>/metrics`.
|
|
109
|
+
|
|
110
|
+
Each method accepts RestClient options such as `headers`, `open_timeout`, and `read_timeout`.
|
|
111
|
+
|
|
112
|
+
```ruby
|
|
113
|
+
response = Nonnative.observability.health(
|
|
114
|
+
headers: { content_type: :json, accept: :json },
|
|
115
|
+
open_timeout: 2,
|
|
116
|
+
read_timeout: 2
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
expect(response.code).to eq(200)
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### 🔁 Lifecycle strategies (Cucumber integration)
|
|
67
123
|
|
|
68
124
|
Nonnative ships Cucumber hooks (when loaded) that support these tags/strategies:
|
|
69
125
|
- `@startup`: start before scenario; stop after scenario
|
|
@@ -84,7 +140,7 @@ The repo’s own Cucumber suite also uses taxonomy tags to classify coverage:
|
|
|
84
140
|
|
|
85
141
|
Requiring `nonnative` is enough; the Cucumber hooks and step definitions are installed lazily once Cucumber’s Ruby DSL is ready.
|
|
86
142
|
|
|
87
|
-
If you want
|
|
143
|
+
If you want "start once per test run", require:
|
|
88
144
|
|
|
89
145
|
```ruby
|
|
90
146
|
require 'nonnative/startup'
|
|
@@ -92,11 +148,13 @@ require 'nonnative/startup'
|
|
|
92
148
|
|
|
93
149
|
This calls `Nonnative.start` immediately and registers an `at_exit` stop.
|
|
94
150
|
|
|
95
|
-
### Processes
|
|
151
|
+
### ⚙️ Processes
|
|
96
152
|
|
|
97
153
|
A process is some sort of command that you would run locally.
|
|
98
|
-
|
|
99
|
-
|
|
154
|
+
Programmatic `p.command` values must be callables that return a shell string or an argv array. YAML `command` values can be scalars or lists and are wrapped internally. String commands preserve legacy shell semantics, while argv arrays avoid shell interpretation and are preferred for new configuration.
|
|
155
|
+
|
|
156
|
+
> [!TIP]
|
|
157
|
+
> Prefer argv arrays for new process commands. Use shell strings only when you intentionally need shell parsing, expansion, or redirection.
|
|
100
158
|
|
|
101
159
|
Set it up programmatically:
|
|
102
160
|
|
|
@@ -114,7 +172,7 @@ Nonnative.configure do |config|
|
|
|
114
172
|
p.command = -> { ['features/support/bin/start', '12_321'] }
|
|
115
173
|
p.timeout = 5
|
|
116
174
|
p.wait = 0.1
|
|
117
|
-
p.
|
|
175
|
+
p.ports = [12_321]
|
|
118
176
|
p.log = '12_321.log'
|
|
119
177
|
p.signal = 'INT' # Possible values are described in Signal.list.keys.
|
|
120
178
|
p.environment = { # Pass environment variables to process.
|
|
@@ -127,7 +185,7 @@ Nonnative.configure do |config|
|
|
|
127
185
|
p.command = -> { ['features/support/bin/start', '12_322'] }
|
|
128
186
|
p.timeout = 0.5
|
|
129
187
|
p.wait = 0.1
|
|
130
|
-
p.
|
|
188
|
+
p.ports = [12_322]
|
|
131
189
|
p.log = '12_322.log'
|
|
132
190
|
end
|
|
133
191
|
end
|
|
@@ -148,7 +206,8 @@ processes:
|
|
|
148
206
|
- "12_321"
|
|
149
207
|
timeout: 5
|
|
150
208
|
wait: 1
|
|
151
|
-
|
|
209
|
+
ports:
|
|
210
|
+
- 12321
|
|
152
211
|
log: 12_321.log
|
|
153
212
|
signal: INT # Possible values are described in Signal.list.keys.
|
|
154
213
|
environment: # Pass environment variables to process.
|
|
@@ -160,7 +219,8 @@ processes:
|
|
|
160
219
|
- "12_322"
|
|
161
220
|
timeout: 5
|
|
162
221
|
wait: 1
|
|
163
|
-
|
|
222
|
+
ports:
|
|
223
|
+
- 12322
|
|
164
224
|
log: 12_322.log
|
|
165
225
|
```
|
|
166
226
|
|
|
@@ -180,9 +240,9 @@ With cucumber you can also verify how much memory is used by the process:
|
|
|
180
240
|
Then the process 'start_1' should consume less than '25mb' of memory
|
|
181
241
|
```
|
|
182
242
|
|
|
183
|
-
### Servers
|
|
243
|
+
### 🖥️ Servers
|
|
184
244
|
|
|
185
|
-
A server is a
|
|
245
|
+
A server is an in-process Ruby fake or helper server that Nonnative starts in a thread. Use servers for dependencies that are easiest to model inside the test process, such as small TCP, HTTP, or gRPC fakes.
|
|
186
246
|
|
|
187
247
|
Define your server:
|
|
188
248
|
|
|
@@ -231,7 +291,7 @@ Nonnative.configure do |config|
|
|
|
231
291
|
s.name = 'server_1'
|
|
232
292
|
s.klass = Nonnative::TCPServer
|
|
233
293
|
s.timeout = 1
|
|
234
|
-
s.
|
|
294
|
+
s.ports = [12_323]
|
|
235
295
|
s.log = 'server_1.log'
|
|
236
296
|
end
|
|
237
297
|
|
|
@@ -239,7 +299,7 @@ Nonnative.configure do |config|
|
|
|
239
299
|
s.name = 'server_2'
|
|
240
300
|
s.klass = Nonnative::TCPServer
|
|
241
301
|
s.timeout = 1
|
|
242
|
-
s.
|
|
302
|
+
s.ports = [12_324]
|
|
243
303
|
s.log = 'server_2.log'
|
|
244
304
|
end
|
|
245
305
|
end
|
|
@@ -257,13 +317,15 @@ servers:
|
|
|
257
317
|
name: server_1
|
|
258
318
|
class: Nonnative::TCPServer
|
|
259
319
|
timeout: 1
|
|
260
|
-
|
|
320
|
+
ports:
|
|
321
|
+
- 12323
|
|
261
322
|
log: server_1.log
|
|
262
323
|
-
|
|
263
324
|
name: server_2
|
|
264
325
|
class: Nonnative::TCPServer
|
|
265
326
|
timeout: 1
|
|
266
|
-
|
|
327
|
+
ports:
|
|
328
|
+
- 12324
|
|
267
329
|
log: server_2.log
|
|
268
330
|
```
|
|
269
331
|
|
|
@@ -277,7 +339,7 @@ Nonnative.configure do |config|
|
|
|
277
339
|
end
|
|
278
340
|
```
|
|
279
341
|
|
|
280
|
-
#### HTTP
|
|
342
|
+
#### 🌐 HTTP
|
|
281
343
|
|
|
282
344
|
Define your server:
|
|
283
345
|
|
|
@@ -314,7 +376,7 @@ Nonnative.configure do |config|
|
|
|
314
376
|
s.name = 'http_server_1'
|
|
315
377
|
s.klass = Nonnative::Features::HTTPServer
|
|
316
378
|
s.timeout = 1
|
|
317
|
-
s.
|
|
379
|
+
s.ports = [4567]
|
|
318
380
|
s.log = 'http_server_1.log'
|
|
319
381
|
end
|
|
320
382
|
end
|
|
@@ -332,7 +394,8 @@ servers:
|
|
|
332
394
|
name: http_server_1
|
|
333
395
|
class: Nonnative::Features::HTTPServer
|
|
334
396
|
timeout: 1
|
|
335
|
-
|
|
397
|
+
ports:
|
|
398
|
+
- 4567
|
|
336
399
|
log: http_server_1.log
|
|
337
400
|
```
|
|
338
401
|
|
|
@@ -346,9 +409,9 @@ Nonnative.configure do |config|
|
|
|
346
409
|
end
|
|
347
410
|
```
|
|
348
411
|
|
|
349
|
-
##### Proxy
|
|
412
|
+
##### 🔀 Proxy
|
|
350
413
|
|
|
351
|
-
The system allows you to define
|
|
414
|
+
The system allows you to define an HTTP proxy for external systems, e.g. `api.github.com`.
|
|
352
415
|
|
|
353
416
|
Define your server:
|
|
354
417
|
|
|
@@ -379,7 +442,7 @@ Nonnative.configure do |config|
|
|
|
379
442
|
s.name = 'http_server_proxy'
|
|
380
443
|
s.klass = Nonnative::Features::HTTPProxyServer
|
|
381
444
|
s.timeout = 1
|
|
382
|
-
s.
|
|
445
|
+
s.ports = [4567]
|
|
383
446
|
s.log = 'http_server_proxy.log'
|
|
384
447
|
end
|
|
385
448
|
end
|
|
@@ -397,7 +460,8 @@ servers:
|
|
|
397
460
|
name: http_server_proxy
|
|
398
461
|
class: Nonnative::Features::HTTPProxyServer
|
|
399
462
|
timeout: 1
|
|
400
|
-
|
|
463
|
+
ports:
|
|
464
|
+
- 4567
|
|
401
465
|
log: http_server_proxy.log
|
|
402
466
|
```
|
|
403
467
|
|
|
@@ -411,7 +475,7 @@ Nonnative.configure do |config|
|
|
|
411
475
|
end
|
|
412
476
|
```
|
|
413
477
|
|
|
414
|
-
#### gRPC
|
|
478
|
+
#### 📡 gRPC
|
|
415
479
|
|
|
416
480
|
Define your server:
|
|
417
481
|
|
|
@@ -448,7 +512,7 @@ Nonnative.configure do |config|
|
|
|
448
512
|
s.name = 'grpc_server_1'
|
|
449
513
|
s.klass = Nonnative::Features::GRPCServer
|
|
450
514
|
s.timeout = 1
|
|
451
|
-
s.
|
|
515
|
+
s.ports = [9002]
|
|
452
516
|
s.log = 'grpc_server_1.log'
|
|
453
517
|
end
|
|
454
518
|
end
|
|
@@ -466,7 +530,8 @@ servers:
|
|
|
466
530
|
name: grpc_server_1
|
|
467
531
|
class: Nonnative::Features::GRPCServer
|
|
468
532
|
timeout: 1
|
|
469
|
-
|
|
533
|
+
ports:
|
|
534
|
+
- 9002
|
|
470
535
|
log: grpc_server_1.log
|
|
471
536
|
```
|
|
472
537
|
|
|
@@ -480,10 +545,12 @@ Nonnative.configure do |config|
|
|
|
480
545
|
end
|
|
481
546
|
```
|
|
482
547
|
|
|
483
|
-
### Services
|
|
548
|
+
### 🧩 Services
|
|
484
549
|
|
|
485
550
|
A service is an external dependency to your system that you **do not** want Nonnative to start (no OS process, no Ruby thread). Services are primarily useful when paired with proxies, because they let you inject failures into dependencies that are managed elsewhere (e.g. a DB running in Docker).
|
|
486
551
|
|
|
552
|
+
Services do not get process lifecycle management or TCP readiness/shutdown checks from Nonnative. They only provide a named runner and optional proxy lifecycle for a dependency that another tool already manages.
|
|
553
|
+
|
|
487
554
|
Set it up programmatically:
|
|
488
555
|
|
|
489
556
|
```ruby
|
|
@@ -537,16 +604,25 @@ Nonnative.configure do |config|
|
|
|
537
604
|
end
|
|
538
605
|
```
|
|
539
606
|
|
|
540
|
-
#### Proxies
|
|
607
|
+
#### 🕸️ Proxies
|
|
541
608
|
|
|
542
|
-
|
|
609
|
+
These proxies can simulate different situations. Available proxy kinds are:
|
|
543
610
|
|
|
544
611
|
- `none` (this is the default)
|
|
545
612
|
- `fault_injection`
|
|
546
613
|
|
|
547
|
-
|
|
614
|
+
> [!WARNING]
|
|
615
|
+
> Unknown proxy kinds fall back to `none`. If fault injection is not taking effect, check the `kind` spelling or register the custom kind before loading the configuration.
|
|
616
|
+
|
|
617
|
+
Custom proxy kinds can be registered through `Nonnative.proxies`:
|
|
618
|
+
|
|
619
|
+
```ruby
|
|
620
|
+
Nonnative.proxies['custom'] = CustomProxy
|
|
621
|
+
```
|
|
548
622
|
|
|
549
|
-
|
|
623
|
+
For `fault_injection`, keep the runner `host` and first `ports` entry as the client-facing endpoint and use nested `proxy.host`/`proxy.port` for the upstream target behind the proxy.
|
|
624
|
+
|
|
625
|
+
##### ⚙️ Process Proxies
|
|
550
626
|
|
|
551
627
|
Add this to an existing process configuration:
|
|
552
628
|
|
|
@@ -561,7 +637,7 @@ Nonnative.configure do |config|
|
|
|
561
637
|
|
|
562
638
|
config.process do |p|
|
|
563
639
|
p.host = '127.0.0.1'
|
|
564
|
-
p.
|
|
640
|
+
p.ports = [20_000]
|
|
565
641
|
|
|
566
642
|
p.proxy = {
|
|
567
643
|
kind: 'fault_injection',
|
|
@@ -587,7 +663,8 @@ log: nonnative.log
|
|
|
587
663
|
processes:
|
|
588
664
|
-
|
|
589
665
|
host: 127.0.0.1
|
|
590
|
-
|
|
666
|
+
ports:
|
|
667
|
+
- 20000
|
|
591
668
|
proxy:
|
|
592
669
|
kind: fault_injection
|
|
593
670
|
host: 127.0.0.1
|
|
@@ -598,7 +675,7 @@ processes:
|
|
|
598
675
|
delay: 5
|
|
599
676
|
```
|
|
600
677
|
|
|
601
|
-
##### Proxies
|
|
678
|
+
##### 🖥️ Server Proxies
|
|
602
679
|
|
|
603
680
|
Add this to an existing server configuration:
|
|
604
681
|
|
|
@@ -613,7 +690,7 @@ Nonnative.configure do |config|
|
|
|
613
690
|
|
|
614
691
|
config.server do |s|
|
|
615
692
|
s.host = '127.0.0.1'
|
|
616
|
-
s.
|
|
693
|
+
s.ports = [20_000]
|
|
617
694
|
|
|
618
695
|
s.proxy = {
|
|
619
696
|
kind: 'fault_injection',
|
|
@@ -639,7 +716,8 @@ log: nonnative.log
|
|
|
639
716
|
servers:
|
|
640
717
|
-
|
|
641
718
|
host: 127.0.0.1
|
|
642
|
-
|
|
719
|
+
ports:
|
|
720
|
+
- 20000
|
|
643
721
|
proxy:
|
|
644
722
|
kind: fault_injection
|
|
645
723
|
host: 127.0.0.1
|
|
@@ -650,7 +728,7 @@ servers:
|
|
|
650
728
|
delay: 5
|
|
651
729
|
```
|
|
652
730
|
|
|
653
|
-
##### Proxies
|
|
731
|
+
##### 🧩 Service Proxies
|
|
654
732
|
|
|
655
733
|
Add this to an existing service configuration:
|
|
656
734
|
|
|
@@ -704,17 +782,17 @@ services:
|
|
|
704
782
|
delay: 5
|
|
705
783
|
```
|
|
706
784
|
|
|
707
|
-
##### Fault Injection
|
|
785
|
+
##### 🧪 Fault Injection
|
|
708
786
|
|
|
709
787
|
The `fault_injection` proxy allows you to simulate failures by injecting them. We currently support the following:
|
|
710
788
|
|
|
711
|
-
Clients connect to the runner `host
|
|
789
|
+
Clients connect to the runner `host` and client-facing endpoint, while the proxy forwards traffic to nested `proxy.host`/`proxy.port`.
|
|
712
790
|
|
|
713
791
|
- `close_all` - Closes the socket as soon as it connects.
|
|
714
|
-
- `delay` -
|
|
715
|
-
- `invalid_data` -
|
|
792
|
+
- `delay` - Delays traffic on the connection. Defaults to 2 seconds and can be configured through options.
|
|
793
|
+
- `invalid_data` - Forwards client requests unchanged, then corrupts upstream responses before they reach the client.
|
|
716
794
|
|
|
717
|
-
###### Fault Injection Processes
|
|
795
|
+
###### ⚙️ Fault Injection Processes
|
|
718
796
|
|
|
719
797
|
Set it up programmatically:
|
|
720
798
|
|
|
@@ -733,7 +811,7 @@ Given I set the proxy for process 'process_1' to 'close_all'
|
|
|
733
811
|
Then I should reset the proxy for process 'process_1'
|
|
734
812
|
```
|
|
735
813
|
|
|
736
|
-
###### Fault Injection Servers
|
|
814
|
+
###### 🖥️ Fault Injection Servers
|
|
737
815
|
|
|
738
816
|
Set it up programmatically:
|
|
739
817
|
|
|
@@ -752,7 +830,7 @@ Given I set the proxy for server 'server_1' to 'close_all'
|
|
|
752
830
|
Then I should reset the proxy for server 'server_1'
|
|
753
831
|
```
|
|
754
832
|
|
|
755
|
-
###### Fault Injection Services
|
|
833
|
+
###### 🧩 Fault Injection Services
|
|
756
834
|
|
|
757
835
|
Set it up programmatically:
|
|
758
836
|
|
|
@@ -771,7 +849,7 @@ Given I set the proxy for service 'service_1' to 'close_all'
|
|
|
771
849
|
Then I should reset the proxy for service 'service_1'
|
|
772
850
|
```
|
|
773
851
|
|
|
774
|
-
### Go
|
|
852
|
+
### 🐹 Go
|
|
775
853
|
|
|
776
854
|
As we love using Go as a language for services we have added support to start binaries with defined parameters.
|
|
777
855
|
|
|
@@ -782,7 +860,7 @@ Nonnative.configure do |config|
|
|
|
782
860
|
config.process do |p|
|
|
783
861
|
p.name = 'go'
|
|
784
862
|
p.command = -> { Nonnative.go_argv(%w[cover], 'reports', 'your_binary', 'sub_command', '-i file:.config/server.yml') }
|
|
785
|
-
p.
|
|
863
|
+
p.ports = [12_345]
|
|
786
864
|
end
|
|
787
865
|
end
|
|
788
866
|
```
|
|
@@ -791,6 +869,9 @@ Use `Nonnative.go_argv(...)` when a process should execute without shell interpr
|
|
|
791
869
|
|
|
792
870
|
YAML `go:` configuration is for Go test binaries compiled with `go test -c`. It builds argv entries in this order: executable, optional `-test.*` profiling/trace/coverage flags, command, then parameters. Parameter strings are parsed into argv words with shell-style quoting, but the argv entries are executed without shell interpretation.
|
|
793
871
|
|
|
872
|
+
> [!IMPORTANT]
|
|
873
|
+
> If `tools` is omitted or empty, Nonnative enables all Go tools: `prof`, `trace`, and `cover`. Provide a subset, such as `tools: [cover]`, to limit the generated `-test.*` flags.
|
|
874
|
+
|
|
794
875
|
To get this to work you will need to create a `main_test.go` file with these contents:
|
|
795
876
|
|
|
796
877
|
```go
|
|
@@ -830,6 +911,7 @@ processes:
|
|
|
830
911
|
parameters:
|
|
831
912
|
- "-i file:.config/server.yml"
|
|
832
913
|
timeout: 5
|
|
833
|
-
|
|
914
|
+
ports:
|
|
915
|
+
- 8000
|
|
834
916
|
log: go.log
|
|
835
917
|
```
|
|
@@ -19,7 +19,7 @@ module Nonnative
|
|
|
19
19
|
# p.name = 'api'
|
|
20
20
|
# p.command = -> { './bin/api' }
|
|
21
21
|
# p.host = '127.0.0.1'
|
|
22
|
-
# p.
|
|
22
|
+
# p.ports = [8080, 9090]
|
|
23
23
|
# p.timeout = 10
|
|
24
24
|
# p.log = 'api.log'
|
|
25
25
|
# end
|
|
@@ -123,14 +123,14 @@ module Nonnative
|
|
|
123
123
|
|
|
124
124
|
def add_processes(cfg)
|
|
125
125
|
processes = cfg.processes || []
|
|
126
|
-
processes.each do |
|
|
127
|
-
process do |
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
runner_attributes(
|
|
132
|
-
|
|
133
|
-
|
|
126
|
+
processes.each do |loaded_process|
|
|
127
|
+
process do |process_config|
|
|
128
|
+
process_config.command = command(loaded_process)
|
|
129
|
+
process_config.signal = loaded_process.signal
|
|
130
|
+
process_config.environment = loaded_process.environment
|
|
131
|
+
runner_attributes(process_config, loaded_process)
|
|
132
|
+
|
|
133
|
+
assign_proxy(process_config, loaded_process.proxy)
|
|
134
134
|
end
|
|
135
135
|
end
|
|
136
136
|
end
|
|
@@ -149,25 +149,25 @@ module Nonnative
|
|
|
149
149
|
|
|
150
150
|
def add_servers(cfg)
|
|
151
151
|
servers = cfg.servers || []
|
|
152
|
-
servers.each do |
|
|
153
|
-
server do |
|
|
154
|
-
|
|
155
|
-
runner_attributes(
|
|
152
|
+
servers.each do |loaded_server|
|
|
153
|
+
server do |server_config|
|
|
154
|
+
server_config.klass = Object.const_get(server_class_name(loaded_server))
|
|
155
|
+
runner_attributes(server_config, loaded_server)
|
|
156
156
|
|
|
157
|
-
|
|
157
|
+
assign_proxy(server_config, loaded_server.proxy)
|
|
158
158
|
end
|
|
159
159
|
end
|
|
160
160
|
end
|
|
161
161
|
|
|
162
162
|
def add_services(cfg)
|
|
163
163
|
services = cfg.services || []
|
|
164
|
-
services.each do |
|
|
165
|
-
service do |
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
164
|
+
services.each do |loaded_service|
|
|
165
|
+
service do |service_config|
|
|
166
|
+
service_config.name = loaded_service.name
|
|
167
|
+
service_config.host = loaded_service.host if loaded_service.host
|
|
168
|
+
assign_service_port(service_config, loaded_service)
|
|
169
169
|
|
|
170
|
-
|
|
170
|
+
assign_proxy(service_config, loaded_service.proxy)
|
|
171
171
|
end
|
|
172
172
|
end
|
|
173
173
|
end
|
|
@@ -182,24 +182,38 @@ module Nonnative
|
|
|
182
182
|
runner.timeout = loaded.timeout
|
|
183
183
|
runner.wait = loaded.wait if loaded.wait
|
|
184
184
|
runner.host = loaded.host if loaded.host
|
|
185
|
-
runner
|
|
185
|
+
assign_ports(runner, loaded)
|
|
186
186
|
runner.log = loaded.log if loaded.respond_to?(:log)
|
|
187
187
|
end
|
|
188
188
|
|
|
189
|
-
def
|
|
190
|
-
|
|
189
|
+
def assign_ports(runner, loaded)
|
|
190
|
+
values = loaded.to_h
|
|
191
|
+
raise ArgumentError, "Use 'ports' instead of 'port' for runner '#{loaded.name}'" if values.key?(:port) || values.key?('port')
|
|
191
192
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
193
|
+
runner.ports = loaded.ports if loaded.ports
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
def assign_service_port(service, loaded)
|
|
197
|
+
values = loaded.to_h
|
|
198
|
+
raise ArgumentError, "Use 'port' instead of 'ports' for service '#{loaded.name}'" if values.key?(:ports) || values.key?('ports')
|
|
199
|
+
|
|
200
|
+
service.port = loaded.port if loaded.port
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
def assign_proxy(runner, loaded_proxy)
|
|
204
|
+
return unless loaded_proxy
|
|
205
|
+
|
|
206
|
+
proxy_attributes = {
|
|
207
|
+
kind: loaded_proxy.kind,
|
|
208
|
+
port: loaded_proxy.port,
|
|
209
|
+
log: loaded_proxy.log,
|
|
210
|
+
options: loaded_proxy.options
|
|
197
211
|
}
|
|
198
212
|
|
|
199
|
-
|
|
200
|
-
|
|
213
|
+
proxy_attributes[:host] = loaded_proxy.host if loaded_proxy.host
|
|
214
|
+
proxy_attributes[:wait] = loaded_proxy.wait if loaded_proxy.wait
|
|
201
215
|
|
|
202
|
-
runner.proxy =
|
|
216
|
+
runner.proxy = proxy_attributes
|
|
203
217
|
end
|
|
204
218
|
end
|
|
205
219
|
end
|
|
@@ -19,7 +19,7 @@ module Nonnative
|
|
|
19
19
|
# @return [String, nil] signal name to use for stopping (defaults to `"INT"` when not set)
|
|
20
20
|
attr_accessor :signal
|
|
21
21
|
|
|
22
|
-
# @return [Numeric] readiness timeout (seconds) used when waiting for
|
|
22
|
+
# @return [Numeric] readiness timeout (seconds) used when waiting for ports to open/close
|
|
23
23
|
attr_accessor :timeout
|
|
24
24
|
|
|
25
25
|
# @return [String] log file path to append process stdout/stderr to
|
|
@@ -15,9 +15,12 @@ module Nonnative
|
|
|
15
15
|
class ConfigurationRunner
|
|
16
16
|
# @return [String, nil] runner name used for lookup (for example via `pool.process_by_name`)
|
|
17
17
|
# @return [String] host to bind/connect to (defaults to `"127.0.0.1"`)
|
|
18
|
-
# @return [Integer]
|
|
18
|
+
# @return [Array<Integer>] ports to bind/connect to
|
|
19
19
|
# @return [Numeric] wait interval (seconds) used by runners between lifecycle steps
|
|
20
|
-
attr_accessor :name, :host, :
|
|
20
|
+
attr_accessor :name, :host, :wait
|
|
21
|
+
|
|
22
|
+
# @return [Array<Integer>] client-facing ports used for readiness/shutdown checks
|
|
23
|
+
attr_reader :ports
|
|
21
24
|
|
|
22
25
|
# Proxy configuration for this runner.
|
|
23
26
|
#
|
|
@@ -31,19 +34,37 @@ module Nonnative
|
|
|
31
34
|
#
|
|
32
35
|
# Defaults:
|
|
33
36
|
# - `host`: `"127.0.0.1"`
|
|
34
|
-
# - `
|
|
37
|
+
# - `ports`: `[0]`
|
|
35
38
|
# - `wait`: `0.1`
|
|
36
39
|
# - `proxy`: a new {Nonnative::ConfigurationProxy} with its own defaults
|
|
37
40
|
#
|
|
38
41
|
# @return [void]
|
|
39
42
|
def initialize
|
|
40
43
|
self.host = '127.0.0.1'
|
|
41
|
-
|
|
44
|
+
@ports = [0]
|
|
42
45
|
self.wait = 0.1
|
|
43
46
|
|
|
44
47
|
@proxy = Nonnative::ConfigurationProxy.new
|
|
45
48
|
end
|
|
46
49
|
|
|
50
|
+
# Sets the client-facing ports for this runner.
|
|
51
|
+
#
|
|
52
|
+
# @param value [Array<Integer>] ports to check for readiness/shutdown
|
|
53
|
+
# @return [void]
|
|
54
|
+
def ports=(value)
|
|
55
|
+
@ports = Array(value)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Returns the primary client-facing port.
|
|
59
|
+
#
|
|
60
|
+
# This preserves a single endpoint for proxy binding and client helpers while the public
|
|
61
|
+
# configuration contract uses {#ports}.
|
|
62
|
+
#
|
|
63
|
+
# @return [Integer]
|
|
64
|
+
def port
|
|
65
|
+
ports.first
|
|
66
|
+
end
|
|
67
|
+
|
|
47
68
|
# Sets proxy configuration using a hash-like value.
|
|
48
69
|
#
|
|
49
70
|
# This is primarily used when loading YAML configuration files, where proxy attributes are
|
|
@@ -12,7 +12,7 @@ module Nonnative
|
|
|
12
12
|
# @see Nonnative::Server
|
|
13
13
|
class ConfigurationServer < ConfigurationRunner
|
|
14
14
|
# @return [Class] a class that implements `#initialize(service)`, and lifecycle hooks expected by {Nonnative::Server}
|
|
15
|
-
# @return [Numeric] readiness timeout (seconds) used when waiting for
|
|
15
|
+
# @return [Numeric] readiness timeout (seconds) used when waiting for ports to open/close
|
|
16
16
|
# @return [String] log file path used by server implementations (for example Puma/gRPC log files)
|
|
17
17
|
attr_accessor :klass, :timeout, :log
|
|
18
18
|
end
|
|
@@ -11,5 +11,33 @@ module Nonnative
|
|
|
11
11
|
# @see Nonnative::Configuration
|
|
12
12
|
# @see Nonnative::Service
|
|
13
13
|
class ConfigurationService < ConfigurationRunner
|
|
14
|
+
# @return [Integer] client-facing port used by the service proxy
|
|
15
|
+
attr_accessor :port
|
|
16
|
+
|
|
17
|
+
# Creates a service configuration with defaults.
|
|
18
|
+
#
|
|
19
|
+
# @return [void]
|
|
20
|
+
def initialize
|
|
21
|
+
super
|
|
22
|
+
|
|
23
|
+
self.port = 0
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Services expose a single proxy listener, so plural runner ports are not supported.
|
|
27
|
+
#
|
|
28
|
+
# @return [void]
|
|
29
|
+
# @raise [ArgumentError] when plural service ports are read
|
|
30
|
+
def ports
|
|
31
|
+
raise ArgumentError, "Use 'port' instead of 'ports' for service '#{name}'"
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Services expose a single proxy listener, so plural runner ports are not supported.
|
|
35
|
+
#
|
|
36
|
+
# @param _value [Array<Integer>] ignored plural ports
|
|
37
|
+
# @return [void]
|
|
38
|
+
# @raise [ArgumentError] when plural service ports are assigned
|
|
39
|
+
def ports=(_value)
|
|
40
|
+
raise ArgumentError, "Use 'port' instead of 'ports' for service '#{name}'"
|
|
41
|
+
end
|
|
14
42
|
end
|
|
15
43
|
end
|
|
@@ -18,8 +18,8 @@ module Nonnative
|
|
|
18
18
|
#
|
|
19
19
|
# ## Wiring
|
|
20
20
|
#
|
|
21
|
-
# When enabled, your test/client should connect to the runner `host`
|
|
22
|
-
# and the proxy will forward traffic to the upstream target exposed by {#host}:{#port}.
|
|
21
|
+
# When enabled, your test/client should connect to the runner `host` and primary port (the proxy
|
|
22
|
+
# endpoint), and the proxy will forward traffic to the upstream target exposed by {#host}:{#port}.
|
|
23
23
|
#
|
|
24
24
|
# ## Configuration
|
|
25
25
|
#
|
|
@@ -62,7 +62,7 @@ module Nonnative
|
|
|
62
62
|
|
|
63
63
|
# Starts the proxy accept loop in a background thread.
|
|
64
64
|
#
|
|
65
|
-
# This binds a TCP server on the underlying runner’s `service.host`
|
|
65
|
+
# This binds a TCP server on the underlying runner’s `service.host` and primary port.
|
|
66
66
|
# Clients connect to that runner endpoint, while upstream traffic is forwarded to {#host}:{#port}.
|
|
67
67
|
#
|
|
68
68
|
# @return [void]
|
|
@@ -34,7 +34,7 @@ module Nonnative
|
|
|
34
34
|
# Binds the gRPC server and begins serving requests.
|
|
35
35
|
#
|
|
36
36
|
# The server binds to the upstream proxy host/port so the fault-injection proxy can expose the
|
|
37
|
-
# runner host
|
|
37
|
+
# runner host and first configured port as the client-facing endpoint used by readiness checks.
|
|
38
38
|
#
|
|
39
39
|
# @return [void]
|
|
40
40
|
def perform_start
|
|
@@ -50,9 +50,10 @@ module Nonnative
|
|
|
50
50
|
|
|
51
51
|
# Executes the upstream request and returns the response.
|
|
52
52
|
#
|
|
53
|
-
# @param
|
|
54
|
-
# @param
|
|
55
|
-
# @param
|
|
53
|
+
# @param method [Symbol] HTTP verb name (e.g. `:get`)
|
|
54
|
+
# @param url [String] upstream URL
|
|
55
|
+
# @param headers [Hash] request headers
|
|
56
|
+
# @param payload [String, nil] request payload
|
|
56
57
|
# @return [RestClient::Response] response for error statuses, otherwise RestClient return value
|
|
57
58
|
def api_response(method:, url:, headers:, payload: nil)
|
|
58
59
|
options = { method:, url:, headers: }
|
|
@@ -109,7 +110,7 @@ module Nonnative
|
|
|
109
110
|
# s.klass = Nonnative::Features::HTTPProxyServer
|
|
110
111
|
# s.timeout = 2
|
|
111
112
|
# s.host = '127.0.0.1'
|
|
112
|
-
# s.
|
|
113
|
+
# s.ports = [4567]
|
|
113
114
|
# s.log = 'proxy.log'
|
|
114
115
|
# end
|
|
115
116
|
# end
|
|
@@ -20,7 +20,7 @@ module Nonnative
|
|
|
20
20
|
# s.klass = ->(service) { Nonnative::HTTPServer.new(app, service) }
|
|
21
21
|
# s.timeout = 2
|
|
22
22
|
# s.host = '127.0.0.1'
|
|
23
|
-
# s.
|
|
23
|
+
# s.ports = [4567]
|
|
24
24
|
# s.log = 'http.log'
|
|
25
25
|
# end
|
|
26
26
|
# end
|
|
@@ -49,7 +49,7 @@ module Nonnative
|
|
|
49
49
|
# Binds the Puma server and begins serving.
|
|
50
50
|
#
|
|
51
51
|
# The listener binds to the upstream proxy host/port so the fault-injection proxy can expose the
|
|
52
|
-
# runner host
|
|
52
|
+
# runner host and first configured port as the client-facing endpoint used by readiness checks.
|
|
53
53
|
#
|
|
54
54
|
# @return [void]
|
|
55
55
|
def perform_start
|
|
@@ -66,6 +66,6 @@ module Nonnative
|
|
|
66
66
|
|
|
67
67
|
private
|
|
68
68
|
|
|
69
|
-
attr_reader :
|
|
69
|
+
attr_reader :server
|
|
70
70
|
end
|
|
71
71
|
end
|
data/lib/nonnative/no_proxy.rb
CHANGED
|
@@ -5,7 +5,7 @@ module Nonnative
|
|
|
5
5
|
#
|
|
6
6
|
# This is the default proxy when `service.proxy.kind` is `"none"` (or an unknown kind is provided).
|
|
7
7
|
# It does not bind/listen or alter traffic; it simply exposes the underlying runner's configured
|
|
8
|
-
# `host` and `port`.
|
|
8
|
+
# `host` and primary `port`.
|
|
9
9
|
#
|
|
10
10
|
# Runners can always call `start`, `stop`, and `reset` safely on this proxy.
|
|
11
11
|
#
|
|
@@ -50,7 +50,7 @@ module Nonnative
|
|
|
50
50
|
|
|
51
51
|
# Returns the port clients should connect to.
|
|
52
52
|
#
|
|
53
|
-
# For {NoProxy}, this is the underlying runner configuration port.
|
|
53
|
+
# For {NoProxy}, this is the first underlying runner configuration port.
|
|
54
54
|
#
|
|
55
55
|
# @return [Integer]
|
|
56
56
|
def port
|
data/lib/nonnative/pool.rb
CHANGED
|
@@ -9,7 +9,7 @@ module Nonnative
|
|
|
9
9
|
# - On start: services first, then servers/processes (in parallel port-check threads)
|
|
10
10
|
# - On stop: processes/servers first, then services
|
|
11
11
|
#
|
|
12
|
-
# Readiness and shutdown are determined via TCP port checks ({Nonnative::
|
|
12
|
+
# Readiness and shutdown are determined via TCP port checks ({Nonnative::Ports#open?} / {Nonnative::Ports#closed?}).
|
|
13
13
|
#
|
|
14
14
|
# @see Nonnative.start
|
|
15
15
|
# @see Nonnative.stop
|
|
@@ -35,7 +35,7 @@ module Nonnative
|
|
|
35
35
|
errors = []
|
|
36
36
|
|
|
37
37
|
errors.concat(service_lifecycle(services, :start, :start))
|
|
38
|
-
[servers, processes].each { |
|
|
38
|
+
[servers, processes].each { |runners| errors.concat(run_lifecycle_checks(runners, :start, :open?, :start, &)) }
|
|
39
39
|
|
|
40
40
|
errors
|
|
41
41
|
end
|
|
@@ -51,7 +51,7 @@ module Nonnative
|
|
|
51
51
|
def stop(&)
|
|
52
52
|
errors = []
|
|
53
53
|
|
|
54
|
-
[processes, servers].each { |
|
|
54
|
+
[processes, servers].each { |runners| errors.concat(run_lifecycle_checks(runners, :stop, :closed?, :stop, &)) }
|
|
55
55
|
errors.concat(service_lifecycle(services, :stop, :stop))
|
|
56
56
|
|
|
57
57
|
errors
|
|
@@ -69,7 +69,9 @@ module Nonnative
|
|
|
69
69
|
def rollback(&)
|
|
70
70
|
errors = []
|
|
71
71
|
|
|
72
|
-
[existing_processes, existing_servers].each
|
|
72
|
+
[existing_processes, existing_servers].each do |runners|
|
|
73
|
+
errors.concat(run_lifecycle_checks(runners, :stop, :closed?, :stop, &))
|
|
74
|
+
end
|
|
73
75
|
errors.concat(service_lifecycle(existing_services, :stop, :stop))
|
|
74
76
|
|
|
75
77
|
errors
|
|
@@ -129,7 +131,7 @@ module Nonnative
|
|
|
129
131
|
|
|
130
132
|
@processes = []
|
|
131
133
|
configuration.processes.each do |p|
|
|
132
|
-
@processes << [Nonnative::Process.new(p), Nonnative::
|
|
134
|
+
@processes << [Nonnative::Process.new(p), Nonnative::Ports.new(p)]
|
|
133
135
|
end
|
|
134
136
|
|
|
135
137
|
@processes
|
|
@@ -140,7 +142,7 @@ module Nonnative
|
|
|
140
142
|
|
|
141
143
|
@servers = []
|
|
142
144
|
configuration.servers.each do |s|
|
|
143
|
-
@servers << [s.klass.new(s), Nonnative::
|
|
145
|
+
@servers << [s.klass.new(s), Nonnative::Ports.new(s)]
|
|
144
146
|
end
|
|
145
147
|
|
|
146
148
|
@servers
|
|
@@ -177,15 +179,15 @@ module Nonnative
|
|
|
177
179
|
end
|
|
178
180
|
end
|
|
179
181
|
|
|
180
|
-
def
|
|
182
|
+
def run_lifecycle_checks(runners, lifecycle_method, port_method, action, &)
|
|
181
183
|
checks = []
|
|
182
184
|
errors = []
|
|
183
185
|
|
|
184
|
-
|
|
185
|
-
values =
|
|
186
|
-
checks << [
|
|
186
|
+
runners.each do |runner, port|
|
|
187
|
+
values = runner.send(lifecycle_method)
|
|
188
|
+
checks << [runner, values, Thread.new { check_port(port, port_method) }]
|
|
187
189
|
rescue StandardError => e
|
|
188
|
-
errors << lifecycle_error(action,
|
|
190
|
+
errors << lifecycle_error(action, runner, e)
|
|
189
191
|
end
|
|
190
192
|
|
|
191
193
|
errors.concat(yield_results(checks, action, &))
|
data/lib/nonnative/port.rb
CHANGED
|
@@ -3,21 +3,23 @@
|
|
|
3
3
|
module Nonnative
|
|
4
4
|
# Performs TCP port readiness/shutdown checks for a configured runner.
|
|
5
5
|
#
|
|
6
|
-
# Nonnative uses this to decide whether a process/server is ready after start, and whether it
|
|
7
|
-
# shut down after stop. The checks repeatedly attempt to open a TCP connection to `process.host`
|
|
8
|
-
# and `
|
|
6
|
+
# Nonnative uses this to decide whether a process/server port is ready after start, and whether it
|
|
7
|
+
# has shut down after stop. The checks repeatedly attempt to open a TCP connection to `process.host`
|
|
8
|
+
# and the configured `port` until either:
|
|
9
9
|
#
|
|
10
10
|
# - the expected condition is met, or
|
|
11
11
|
# - the configured timeout elapses (in which case the method returns `false`)
|
|
12
12
|
#
|
|
13
13
|
# The `process` argument is a runner configuration object (e.g. {Nonnative::ConfigurationProcess}
|
|
14
|
-
# or {Nonnative::ConfigurationServer}) that responds to `host
|
|
14
|
+
# or {Nonnative::ConfigurationServer}) that responds to `host` and `timeout`.
|
|
15
15
|
#
|
|
16
16
|
# @see Nonnative::Pool for how these checks are orchestrated during start/stop
|
|
17
17
|
class Port
|
|
18
|
-
# @param process [#host, #
|
|
19
|
-
|
|
18
|
+
# @param process [#host, #timeout] runner configuration providing connection details
|
|
19
|
+
# @param port [Integer] port to check
|
|
20
|
+
def initialize(process, port = process.port)
|
|
20
21
|
@process = process
|
|
22
|
+
@port = port
|
|
21
23
|
@timeout = Nonnative::Timeout.new(process.timeout)
|
|
22
24
|
end
|
|
23
25
|
|
|
@@ -28,7 +30,7 @@ module Nonnative
|
|
|
28
30
|
#
|
|
29
31
|
# @return [Boolean] `true` if the port opened in time; otherwise `false`
|
|
30
32
|
def open?
|
|
31
|
-
Nonnative.logger.info "checking if port '#{
|
|
33
|
+
Nonnative.logger.info "checking if port '#{port}' is open on host '#{process.host}'"
|
|
32
34
|
|
|
33
35
|
timeout.perform do
|
|
34
36
|
open_socket
|
|
@@ -46,7 +48,7 @@ module Nonnative
|
|
|
46
48
|
#
|
|
47
49
|
# @return [Boolean] `true` if the port closed in time; otherwise `false`
|
|
48
50
|
def closed?
|
|
49
|
-
Nonnative.logger.info "checking if port '#{
|
|
51
|
+
Nonnative.logger.info "checking if port '#{port}' is closed on host '#{process.host}'"
|
|
50
52
|
|
|
51
53
|
timeout.perform do
|
|
52
54
|
open_socket
|
|
@@ -61,10 +63,10 @@ module Nonnative
|
|
|
61
63
|
|
|
62
64
|
private
|
|
63
65
|
|
|
64
|
-
attr_reader :process, :timeout
|
|
66
|
+
attr_reader :process, :port, :timeout
|
|
65
67
|
|
|
66
68
|
def open_socket
|
|
67
|
-
TCPSocket.new(process.host,
|
|
69
|
+
TCPSocket.new(process.host, port).close
|
|
68
70
|
end
|
|
69
71
|
|
|
70
72
|
def sleep_interval
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Nonnative
|
|
4
|
+
# Performs aggregate TCP readiness/shutdown checks for all configured runner ports.
|
|
5
|
+
#
|
|
6
|
+
# A runner is considered ready only when every configured port is open, and stopped only when every
|
|
7
|
+
# configured port is closed.
|
|
8
|
+
#
|
|
9
|
+
# @see Nonnative::Port
|
|
10
|
+
class Ports
|
|
11
|
+
# @param runner [#host, #ports, #timeout] runner configuration providing connection details
|
|
12
|
+
def initialize(runner)
|
|
13
|
+
@ports = runner.ports.map { |port| Nonnative::Port.new(runner, port) }
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# Returns whether all configured ports become connectable before their timeouts elapse.
|
|
17
|
+
#
|
|
18
|
+
# @return [Boolean]
|
|
19
|
+
def open?
|
|
20
|
+
ports.all?(&:open?)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Returns whether all configured ports become non-connectable before their timeouts elapse.
|
|
24
|
+
#
|
|
25
|
+
# @return [Boolean]
|
|
26
|
+
def closed?
|
|
27
|
+
ports.all?(&:closed?)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
private
|
|
31
|
+
|
|
32
|
+
attr_reader :ports
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -65,18 +65,18 @@ module Nonnative
|
|
|
65
65
|
::TCPSocket.new(proxy.host, proxy.port)
|
|
66
66
|
end
|
|
67
67
|
|
|
68
|
-
# Pipes data from `
|
|
68
|
+
# Pipes data from `source_socket` to `destination_socket` when the source is readable.
|
|
69
69
|
#
|
|
70
70
|
# @param ready [Array<Array<IO>>] the result from `select`
|
|
71
|
-
# @param
|
|
72
|
-
# @param
|
|
71
|
+
# @param source_socket [IO] readable side
|
|
72
|
+
# @param destination_socket [IO] writable side
|
|
73
73
|
# @return [Boolean] whether the piping loop should terminate
|
|
74
|
-
def pipe?(ready,
|
|
75
|
-
if ready[0].include?(
|
|
76
|
-
data = read(
|
|
74
|
+
def pipe?(ready, source_socket, destination_socket)
|
|
75
|
+
if ready[0].include?(source_socket)
|
|
76
|
+
data = read(source_socket)
|
|
77
77
|
return true if data.empty?
|
|
78
78
|
|
|
79
|
-
write
|
|
79
|
+
write destination_socket, data
|
|
80
80
|
end
|
|
81
81
|
|
|
82
82
|
false
|
data/lib/nonnative/version.rb
CHANGED
data/lib/nonnative.rb
CHANGED
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
# p.name = 'api'
|
|
25
25
|
# p.command = -> { './bin/api' }
|
|
26
26
|
# p.host = '127.0.0.1'
|
|
27
|
-
# p.
|
|
27
|
+
# p.ports = [8080, 9090]
|
|
28
28
|
# p.timeout = 10
|
|
29
29
|
# p.log = 'api.log'
|
|
30
30
|
# end
|
|
@@ -68,6 +68,7 @@ require 'nonnative/stop_error'
|
|
|
68
68
|
require 'nonnative/not_found_error'
|
|
69
69
|
require 'nonnative/timeout'
|
|
70
70
|
require 'nonnative/port'
|
|
71
|
+
require 'nonnative/ports'
|
|
71
72
|
require 'nonnative/configuration_file'
|
|
72
73
|
require 'nonnative/configuration'
|
|
73
74
|
require 'nonnative/configuration_runner'
|
|
@@ -208,7 +209,7 @@ module Nonnative
|
|
|
208
209
|
|
|
209
210
|
# Starts all configured services, servers, and processes, and waits for readiness.
|
|
210
211
|
#
|
|
211
|
-
# Readiness is determined by attempting to connect to each runner's configured host/
|
|
212
|
+
# Readiness is determined by attempting to connect to each runner's configured host/ports.
|
|
212
213
|
#
|
|
213
214
|
# @return [void]
|
|
214
215
|
# @raise [Nonnative::StartError] if one or more runners fail to start or become ready in time
|
data/nonnative.gemspec
CHANGED
|
@@ -11,7 +11,7 @@ Gem::Specification.new do |spec|
|
|
|
11
11
|
spec.authors = ['Alejandro Falkowski']
|
|
12
12
|
spec.email = ['alexrfalkowski@gmail.com']
|
|
13
13
|
|
|
14
|
-
spec.summary = 'Allows you to keep using the power of
|
|
14
|
+
spec.summary = 'Allows you to keep using the power of Ruby to test other systems'
|
|
15
15
|
spec.description = spec.summary
|
|
16
16
|
spec.homepage = 'https://github.com/alexfalkowski/nonnative'
|
|
17
17
|
spec.license = 'MIT'
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: nonnative
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version:
|
|
4
|
+
version: 3.1.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Alejandro Falkowski
|
|
@@ -263,7 +263,7 @@ dependencies:
|
|
|
263
263
|
- - "<"
|
|
264
264
|
- !ruby/object:Gem::Version
|
|
265
265
|
version: '5'
|
|
266
|
-
description: Allows you to keep using the power of
|
|
266
|
+
description: Allows you to keep using the power of Ruby to test other systems
|
|
267
267
|
email:
|
|
268
268
|
- alexrfalkowski@gmail.com
|
|
269
269
|
executables: []
|
|
@@ -310,6 +310,7 @@ files:
|
|
|
310
310
|
- lib/nonnative/observability.rb
|
|
311
311
|
- lib/nonnative/pool.rb
|
|
312
312
|
- lib/nonnative/port.rb
|
|
313
|
+
- lib/nonnative/ports.rb
|
|
313
314
|
- lib/nonnative/process.rb
|
|
314
315
|
- lib/nonnative/proxy.rb
|
|
315
316
|
- lib/nonnative/proxy_factory.rb
|
|
@@ -348,5 +349,5 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
348
349
|
requirements: []
|
|
349
350
|
rubygems_version: 4.0.11
|
|
350
351
|
specification_version: 4
|
|
351
|
-
summary: Allows you to keep using the power of
|
|
352
|
+
summary: Allows you to keep using the power of Ruby to test other systems
|
|
352
353
|
test_files: []
|