nonnative 3.9.0 → 3.11.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 +2 -2
- data/AGENTS.md +5 -0
- data/Gemfile.lock +1 -1
- data/README.md +49 -12
- data/lib/nonnative/configuration_readiness.rb +10 -1
- data/lib/nonnative/cucumber.rb +2 -1
- data/lib/nonnative/http_probe.rb +1 -1
- data/lib/nonnative/no_proxy.rb +4 -4
- data/lib/nonnative/process.rb +7 -11
- data/lib/nonnative/startup.rb +5 -0
- data/lib/nonnative/version.rb +1 -1
- data/lib/nonnative.rb +8 -0
- data/nonnative.gemspec +2 -2
- 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: 6a7d0e5ea6dab1d00eedd363fc81d05d2dd3e4401a97729043bcd3132a071432
|
|
4
|
+
data.tar.gz: 53c589eda9e895f3c815446003c97e77859ccec3b0c021182dc1437d0632b267
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: be0b0b0d4cf1f606fe973f7bb6e38b3f2679b8445d6f24f7f05c4ca37eea62fc36f8910f6d0bcf44c1329b71d6638625d7d4a0d3226a14912662d91980523c1e
|
|
7
|
+
data.tar.gz: 15caeabfe50acdf2513ac2519bc953fb70effcc662c948c293be646d949bdb60c570359fb0817a6eb528207004f6a7cadfd440628026b58a0e9375003f650389
|
data/.circleci/config.yml
CHANGED
|
@@ -32,13 +32,13 @@ jobs:
|
|
|
32
32
|
- restore_cache:
|
|
33
33
|
name: restore deps
|
|
34
34
|
keys:
|
|
35
|
-
- nonnative-ruby-cache-{{ checksum "Gemfile.lock" }}-{{ checksum "~/.ruby-version" }}
|
|
35
|
+
- nonnative-ruby-cache-{{ checksum "Gemfile.lock" }}-{{ checksum "~/.ruby-version" }}
|
|
36
36
|
- nonnative-ruby-cache-
|
|
37
37
|
- run: make dep
|
|
38
38
|
- run: make clean-dep
|
|
39
39
|
- save_cache:
|
|
40
40
|
name: save deps
|
|
41
|
-
key: nonnative-ruby-cache-{{ checksum "Gemfile.lock" }}-{{ checksum "~/.ruby-version" }}
|
|
41
|
+
key: nonnative-ruby-cache-{{ checksum "Gemfile.lock" }}-{{ checksum "~/.ruby-version" }}
|
|
42
42
|
paths:
|
|
43
43
|
- vendor
|
|
44
44
|
- run: make lint
|
data/AGENTS.md
CHANGED
|
@@ -74,6 +74,11 @@ of dependencies.
|
|
|
74
74
|
use `nonnative` against real dependencies. Do not flag the in-repository
|
|
75
75
|
service proxy success scenario's connection-only assertion as a test gap
|
|
76
76
|
unless the task is explicitly about changing service proxy forwarding.
|
|
77
|
+
- `Nonnative::FaultInjectionProxy` owns its listener thread and stops it by
|
|
78
|
+
closing the owned `TCPServer`, which wakes the accept loop before joining the
|
|
79
|
+
thread. Do not flag the listener `join` as an unbounded reliability gap unless
|
|
80
|
+
the task is explicitly about proxy shutdown behavior or there is a normal-use
|
|
81
|
+
reproducer, failing CI evidence, or platform-specific hang evidence.
|
|
77
82
|
|
|
78
83
|
## Runtime Model
|
|
79
84
|
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
|
@@ -38,6 +38,18 @@ Or install it yourself as:
|
|
|
38
38
|
gem install nonnative
|
|
39
39
|
```
|
|
40
40
|
|
|
41
|
+
## 🛠️ Contributor Bootstrap
|
|
42
|
+
|
|
43
|
+
Fresh clones need the shared `bin/` submodule before Make targets can load:
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
git submodule sync && git submodule update --init
|
|
47
|
+
make help
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Use `make dep` before local validation when dependencies are missing. The CI-parity checks are
|
|
51
|
+
`make lint`, `make sec`, `make features`, and `make benchmarks`.
|
|
52
|
+
|
|
41
53
|
## 🚀 Usage
|
|
42
54
|
|
|
43
55
|
Nonnative is configured via `Nonnative.configure` (programmatic) or `config.load_file(...)` (YAML).
|
|
@@ -67,12 +79,12 @@ Process/server fields:
|
|
|
67
79
|
- `log`: per-runner log file used by process output redirection or server implementations.
|
|
68
80
|
|
|
69
81
|
Process-only fields:
|
|
70
|
-
- `readiness`: optional HTTP startup readiness check with explicit `port` and `path`.
|
|
82
|
+
- `readiness`: optional HTTP startup readiness check with explicit `port` and path-only `path`.
|
|
71
83
|
|
|
72
84
|
Service fields:
|
|
73
85
|
- `port`: client-facing service port. Services do not get TCP readiness/shutdown checks from Nonnative.
|
|
74
86
|
|
|
75
|
-
Nonnative readiness and shutdown checks are TCP port checks by default. Configure process/server ports that are dedicated to the test run; if another process is already listening on the same endpoint, results are undefined. Processes can also opt into an HTTP readiness check that runs after TCP readiness succeeds.
|
|
87
|
+
Nonnative readiness and shutdown checks are TCP port checks by default. Configure process/server ports that are dedicated to the test run; if another process is already listening on the same endpoint, results are undefined. Processes can also opt into an HTTP readiness check that runs after TCP readiness succeeds. HTTP readiness paths must be path-only values, such as `/test/readyz`; absolute URLs and scheme-relative URLs are rejected.
|
|
76
88
|
|
|
77
89
|
> [!WARNING]
|
|
78
90
|
> TCP readiness and shutdown checks only prove that a TCP port opened or closed. HTTP readiness is process-only, checks for a 2xx response, and does not verify gRPC health, schema readiness, migrations, or other application-specific health.
|
|
@@ -94,7 +106,40 @@ Nonnative.stop
|
|
|
94
106
|
`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`.
|
|
95
107
|
|
|
96
108
|
> [!NOTE]
|
|
97
|
-
> `Nonnative.
|
|
109
|
+
> `Nonnative.start` / `Nonnative.stop` manage one lifecycle for the current pool.
|
|
110
|
+
> Call `Nonnative.clear` before reconfiguring Nonnative or starting a new lifecycle in the same Ruby process.
|
|
111
|
+
> `Nonnative.clear` clears memoized configuration, logger, observability client, and pool.
|
|
112
|
+
|
|
113
|
+
### 🧩 Test framework setup
|
|
114
|
+
|
|
115
|
+
For Cucumber, load and configure Nonnative in `features/support/env.rb`, then use lifecycle tags on scenarios:
|
|
116
|
+
|
|
117
|
+
```ruby
|
|
118
|
+
require 'nonnative'
|
|
119
|
+
|
|
120
|
+
Nonnative.configure do |config|
|
|
121
|
+
config.load_file('configuration.yml')
|
|
122
|
+
end
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
```cucumber
|
|
126
|
+
@startup
|
|
127
|
+
Scenario: run with Nonnative around this scenario
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
For RSpec or another suite that should start Nonnative once per test run, configure first and then require the startup integration:
|
|
131
|
+
|
|
132
|
+
```ruby
|
|
133
|
+
require 'nonnative'
|
|
134
|
+
|
|
135
|
+
Nonnative.configure do |config|
|
|
136
|
+
config.load_file('configuration.yml')
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
require 'nonnative/startup'
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
`nonnative/startup` calls `Nonnative.start` immediately and registers an `at_exit` stop, so load configuration before requiring it.
|
|
98
143
|
|
|
99
144
|
### 📈 Observability
|
|
100
145
|
|
|
@@ -121,7 +166,7 @@ expect(response.code).to eq(200)
|
|
|
121
166
|
|
|
122
167
|
Nonnative ships Cucumber hooks (when loaded) that support these tags/strategies:
|
|
123
168
|
- `@startup`: start before scenario; stop after scenario
|
|
124
|
-
- `@manual`: stop after scenario
|
|
169
|
+
- `@manual`: stop after scenario; use `When I start the system` to start manually
|
|
125
170
|
- `@clear`: clears memoized configuration, logger, observability client, and pool before scenario
|
|
126
171
|
- `@reset`: resets proxies after scenario
|
|
127
172
|
|
|
@@ -138,14 +183,6 @@ The repo’s own Cucumber suite also uses taxonomy tags to classify coverage:
|
|
|
138
183
|
|
|
139
184
|
Requiring `nonnative` is enough; the Cucumber hooks and step definitions are installed lazily once Cucumber’s Ruby DSL is ready.
|
|
140
185
|
|
|
141
|
-
If you want "start once per test run", require:
|
|
142
|
-
|
|
143
|
-
```ruby
|
|
144
|
-
require 'nonnative/startup'
|
|
145
|
-
```
|
|
146
|
-
|
|
147
|
-
This calls `Nonnative.start` immediately and registers an `at_exit` stop.
|
|
148
|
-
|
|
149
186
|
### ⚙️ Processes
|
|
150
187
|
|
|
151
188
|
A process is some sort of command that you would run locally.
|
|
@@ -9,7 +9,7 @@ module Nonnative
|
|
|
9
9
|
# @return [Integer] process HTTP readiness port
|
|
10
10
|
attr_accessor :port
|
|
11
11
|
|
|
12
|
-
# @return [String] HTTP readiness path
|
|
12
|
+
# @return [String] path-only HTTP readiness path
|
|
13
13
|
attr_accessor :path
|
|
14
14
|
|
|
15
15
|
# @param value [Hash, #to_h] readiness attributes
|
|
@@ -30,6 +30,15 @@ module Nonnative
|
|
|
30
30
|
def validate!
|
|
31
31
|
raise ArgumentError, "Process readiness requires 'port'" if port.nil?
|
|
32
32
|
raise ArgumentError, "Process readiness requires 'path'" if path.nil?
|
|
33
|
+
raise ArgumentError, 'Process readiness path must be path-only' unless path_only?
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def path_only?
|
|
37
|
+
uri = URI.parse(path.to_s)
|
|
38
|
+
|
|
39
|
+
uri.scheme.nil? && uri.host.nil?
|
|
40
|
+
rescue URI::InvalidURIError
|
|
41
|
+
false
|
|
33
42
|
end
|
|
34
43
|
end
|
|
35
44
|
end
|
data/lib/nonnative/cucumber.rb
CHANGED
|
@@ -43,10 +43,11 @@ module Nonnative
|
|
|
43
43
|
end
|
|
44
44
|
|
|
45
45
|
def install_hooks
|
|
46
|
+
# Register @clear before startup hooks so combined tags reset stale state before creating a pool.
|
|
47
|
+
Before('@clear') { Nonnative.clear }
|
|
46
48
|
Before('@startup') { Nonnative.start }
|
|
47
49
|
After('@startup') { Nonnative.stop }
|
|
48
50
|
After('@manual') { Nonnative.stop }
|
|
49
|
-
Before('@clear') { Nonnative.clear }
|
|
50
51
|
After('@reset') { Nonnative.reset }
|
|
51
52
|
end
|
|
52
53
|
end
|
data/lib/nonnative/http_probe.rb
CHANGED
data/lib/nonnative/no_proxy.rb
CHANGED
|
@@ -4,8 +4,8 @@ module Nonnative
|
|
|
4
4
|
# No-op proxy implementation.
|
|
5
5
|
#
|
|
6
6
|
# This is the default proxy when `service.proxy.kind` is `"none"`.
|
|
7
|
-
# It does not bind/listen or alter traffic; it simply exposes the
|
|
8
|
-
# `host` and
|
|
7
|
+
# It does not bind/listen or alter traffic; it simply exposes the service configuration's
|
|
8
|
+
# client-facing `host` and `port`.
|
|
9
9
|
#
|
|
10
10
|
# Services can always call `start`, `stop`, and `reset` safely on this proxy.
|
|
11
11
|
#
|
|
@@ -41,7 +41,7 @@ module Nonnative
|
|
|
41
41
|
|
|
42
42
|
# Returns the host clients should connect to.
|
|
43
43
|
#
|
|
44
|
-
# For {NoProxy}, this is the
|
|
44
|
+
# For {NoProxy}, this is the service configuration's client-facing `host`.
|
|
45
45
|
#
|
|
46
46
|
# @return [String]
|
|
47
47
|
def host
|
|
@@ -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
|
|
53
|
+
# For {NoProxy}, this is the service configuration's client-facing `port`.
|
|
54
54
|
#
|
|
55
55
|
# @return [Integer]
|
|
56
56
|
def port
|
data/lib/nonnative/process.rb
CHANGED
|
@@ -20,6 +20,9 @@ module Nonnative
|
|
|
20
20
|
@timeout = Nonnative::Timeout.new(service.timeout)
|
|
21
21
|
end
|
|
22
22
|
|
|
23
|
+
# @return [GetProcessMem, nil] memory reader for the current process lifecycle
|
|
24
|
+
attr_reader :memory
|
|
25
|
+
|
|
23
26
|
# Spawns the configured process if it is not already running.
|
|
24
27
|
#
|
|
25
28
|
# @return [Array<(Integer, Boolean)>]
|
|
@@ -29,6 +32,8 @@ module Nonnative
|
|
|
29
32
|
def start
|
|
30
33
|
unless process_exists?
|
|
31
34
|
@pid = process_spawn
|
|
35
|
+
# Keep memory reads bound to the child spawned for this lifecycle.
|
|
36
|
+
@memory = GetProcessMem.new(pid)
|
|
32
37
|
wait_start
|
|
33
38
|
end
|
|
34
39
|
|
|
@@ -53,17 +58,8 @@ module Nonnative
|
|
|
53
58
|
end
|
|
54
59
|
|
|
55
60
|
[pid, stopped]
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
# Returns a memoized memory reader for the spawned process.
|
|
59
|
-
#
|
|
60
|
-
# This is primarily used by acceptance tests to assert memory usage.
|
|
61
|
-
#
|
|
62
|
-
# @return [GetProcessMem, nil] a memory reader for the pid, or `nil` if not started
|
|
63
|
-
def memory
|
|
64
|
-
return if pid.nil?
|
|
65
|
-
|
|
66
|
-
@memory ||= GetProcessMem.new(pid)
|
|
61
|
+
ensure
|
|
62
|
+
@memory = nil
|
|
67
63
|
end
|
|
68
64
|
|
|
69
65
|
protected
|
data/lib/nonnative/startup.rb
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
# Starts the configured Nonnative pool immediately and registers an `at_exit` stop hook.
|
|
4
|
+
#
|
|
5
|
+
# Configure Nonnative before requiring this file; it is intended for suites that keep one
|
|
6
|
+
# Nonnative lifecycle open for the whole Ruby process.
|
|
7
|
+
|
|
3
8
|
at_exit do
|
|
4
9
|
Nonnative.stop
|
|
5
10
|
end
|
data/lib/nonnative/version.rb
CHANGED
data/lib/nonnative.rb
CHANGED
|
@@ -48,6 +48,7 @@ require 'yaml'
|
|
|
48
48
|
require 'open3'
|
|
49
49
|
require 'securerandom'
|
|
50
50
|
require 'shellwords'
|
|
51
|
+
require 'uri'
|
|
51
52
|
|
|
52
53
|
require 'grpc'
|
|
53
54
|
require 'sinatra'
|
|
@@ -204,8 +205,12 @@ module Nonnative
|
|
|
204
205
|
|
|
205
206
|
# Resolves a proxy implementation for a configured kind.
|
|
206
207
|
#
|
|
208
|
+
# `nil` and `"none"` resolve to {Nonnative::NoProxy}; any other kind must be registered in
|
|
209
|
+
# {Nonnative.proxies}.
|
|
210
|
+
#
|
|
207
211
|
# @param kind [String] proxy kind name (for example `"fault_injection"`)
|
|
208
212
|
# @return [Class] a subclass of {Nonnative::Proxy}
|
|
213
|
+
# @raise [ArgumentError] if the kind is not `"none"` and has not been registered
|
|
209
214
|
def proxy(kind)
|
|
210
215
|
kind.nil? || kind == 'none' ? NoProxy : proxies.fetch(kind) { raise ArgumentError, "Unsupported proxy kind '#{kind}'" }
|
|
211
216
|
end
|
|
@@ -286,6 +291,9 @@ module Nonnative
|
|
|
286
291
|
|
|
287
292
|
# Clears memoized configuration, logger, observability client, and pool.
|
|
288
293
|
#
|
|
294
|
+
# Call this before reconfiguring Nonnative or starting a new lifecycle in the same Ruby process.
|
|
295
|
+
# `start`/`stop` are intended to manage one lifecycle for the current pool.
|
|
296
|
+
#
|
|
289
297
|
# @return [void]
|
|
290
298
|
def clear
|
|
291
299
|
clear_logger
|
data/nonnative.gemspec
CHANGED
|
@@ -11,8 +11,8 @@ Gem::Specification.new do |spec|
|
|
|
11
11
|
spec.authors = ['Alejandro Falkowski']
|
|
12
12
|
spec.email = ['alexrfalkowski@gmail.com']
|
|
13
13
|
|
|
14
|
-
spec.summary = '
|
|
15
|
-
spec.description =
|
|
14
|
+
spec.summary = 'Ruby-first end-to-end harness for testing systems implemented in other languages'
|
|
15
|
+
spec.description = 'Starts OS processes, in-process Ruby servers, and proxy-only services with TCP readiness checks and fault-injection proxies.'
|
|
16
16
|
spec.homepage = 'https://github.com/alexfalkowski/nonnative'
|
|
17
17
|
spec.license = 'MIT'
|
|
18
18
|
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
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: 3.
|
|
4
|
+
version: 3.11.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Alejandro Falkowski
|
|
@@ -263,7 +263,8 @@ dependencies:
|
|
|
263
263
|
- - "<"
|
|
264
264
|
- !ruby/object:Gem::Version
|
|
265
265
|
version: '5'
|
|
266
|
-
description:
|
|
266
|
+
description: Starts OS processes, in-process Ruby servers, and proxy-only services
|
|
267
|
+
with TCP readiness checks and fault-injection proxies.
|
|
267
268
|
email:
|
|
268
269
|
- alexrfalkowski@gmail.com
|
|
269
270
|
executables: []
|
|
@@ -351,5 +352,5 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
351
352
|
requirements: []
|
|
352
353
|
rubygems_version: 4.0.11
|
|
353
354
|
specification_version: 4
|
|
354
|
-
summary:
|
|
355
|
+
summary: Ruby-first end-to-end harness for testing systems implemented in other languages
|
|
355
356
|
test_files: []
|