nonnative 2.8.0 → 2.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 -1
- data/AGENTS.md +16 -1
- data/Gemfile.lock +1 -1
- data/README.md +11 -0
- data/lib/nonnative/cucumber.rb +24 -4
- data/lib/nonnative/invalid_data_socket_pair.rb +45 -5
- data/lib/nonnative/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 304dcea40b84eda206fe9964fe1479a54e0188f52f2a5300dbcf992101cab2b3
|
|
4
|
+
data.tar.gz: a6adb1b816c424709ab337d1b845dd106ff5141ddb6f26bfe73318c4f51bed23
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a7d8afad5ad7ff9b550c67fe73a683da85ae1bcfa611e0b25b0d570f44ebd5beba47ab50287d58206a51d6d61722bf42ae893929d2c4a99d5ea53b2e033fae91
|
|
7
|
+
data.tar.gz: 506907d924a16c49d583a91e78a7c6e17bb03e3e5d879f00f43e8d4313fb57957bf545538d2953d056ab0c3eecb94ff29ab9a1af22547c890c1a1e56e3f0335c
|
data/.circleci/config.yml
CHANGED
data/AGENTS.md
CHANGED
|
@@ -59,6 +59,9 @@ Readiness and shutdown checks are TCP-only via `Nonnative::Port#open?` and `#clo
|
|
|
59
59
|
|
|
60
60
|
Cucumber integration lives in `lib/nonnative/cucumber.rb`.
|
|
61
61
|
|
|
62
|
+
Treat `lib/nonnative/cucumber.rb` as a public compatibility surface for library consumers.
|
|
63
|
+
Existing hooks and step text should not be removed or renamed unless the user explicitly wants a breaking change.
|
|
64
|
+
|
|
62
65
|
Supported tags:
|
|
63
66
|
|
|
64
67
|
- `@startup`: start before scenario, stop after scenario
|
|
@@ -66,6 +69,18 @@ Supported tags:
|
|
|
66
69
|
- `@clear`: call `Nonnative.clear` before scenario
|
|
67
70
|
- `@reset`: reset proxies after scenario
|
|
68
71
|
|
|
72
|
+
Repo-owned feature files also use suite taxonomy tags:
|
|
73
|
+
|
|
74
|
+
- `@acceptance`: end-to-end runner and client flows
|
|
75
|
+
- `@contract`: lower-level lifecycle / command coverage
|
|
76
|
+
- `@proxy`: proxy-specific coverage
|
|
77
|
+
- `@config`: scenarios or example sets that load YAML/configuration
|
|
78
|
+
- `@service`: coverage centered on external services
|
|
79
|
+
- `@benchmark`: benchmark-only scenarios
|
|
80
|
+
- `@slow`: slower-running scenarios, currently benchmarks
|
|
81
|
+
|
|
82
|
+
`make features` excludes `@benchmark`; `make benchmarks` runs only `@benchmark`.
|
|
83
|
+
|
|
69
84
|
`Nonnative.clear` now clears:
|
|
70
85
|
|
|
71
86
|
- configuration
|
|
@@ -115,7 +130,7 @@ Fault injection states:
|
|
|
115
130
|
|
|
116
131
|
## Important limitations / gotchas
|
|
117
132
|
|
|
118
|
-
- Ruby version is constrained by `nonnative.gemspec` to `>=
|
|
133
|
+
- Ruby version is constrained by `nonnative.gemspec` to `>= 4.0.0` and `< 5.0.0`
|
|
119
134
|
- The `grpc` Ruby library uses a global logger; per-server gRPC loggers are not really supported
|
|
120
135
|
- `make` depends on the `bin/` submodule being present
|
|
121
136
|
- Local Ruby/Bundler mismatches can break native extensions on macOS
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
|
@@ -64,6 +64,17 @@ Nonnative ships Cucumber hooks (when loaded) that support these tags/strategies:
|
|
|
64
64
|
- `@clear`: clears memoized configuration, logger, observability client, and pool before scenario
|
|
65
65
|
- `@reset`: resets proxies after scenario
|
|
66
66
|
|
|
67
|
+
The repo’s own Cucumber suite also uses taxonomy tags to classify coverage:
|
|
68
|
+
- `@acceptance`: end-to-end behavior across configured runners and clients
|
|
69
|
+
- `@contract`: lower-level contract and lifecycle behavior
|
|
70
|
+
- `@proxy`: proxy-specific behavior and failure injection
|
|
71
|
+
- `@config`: coverage that exercises YAML/config loading
|
|
72
|
+
- `@service`: scenarios centered on externally managed dependencies
|
|
73
|
+
- `@benchmark`: benchmark-only scenarios run by `make benchmarks`
|
|
74
|
+
- `@slow`: slower scenarios, currently used by benchmark coverage
|
|
75
|
+
|
|
76
|
+
`make features` excludes `@benchmark`, while `make benchmarks` runs only `@benchmark`.
|
|
77
|
+
|
|
67
78
|
Requiring `nonnative` is enough; the Cucumber hooks and step definitions are installed lazily once Cucumber’s Ruby DSL is ready.
|
|
68
79
|
|
|
69
80
|
If you want “start once per test run”, require:
|
data/lib/nonnative/cucumber.rb
CHANGED
|
@@ -75,20 +75,40 @@ module Nonnative
|
|
|
75
75
|
module LifecycleSteps
|
|
76
76
|
def install_state_steps
|
|
77
77
|
install_start_step
|
|
78
|
+
install_attempt_start_step
|
|
79
|
+
install_attempt_stop_step
|
|
78
80
|
install_unhealthy_step
|
|
79
81
|
install_healthy_step
|
|
80
82
|
end
|
|
81
83
|
|
|
82
84
|
def install_start_step
|
|
83
|
-
|
|
85
|
+
When('I start the system') do
|
|
84
86
|
Nonnative.start
|
|
85
87
|
end
|
|
86
88
|
end
|
|
87
89
|
|
|
90
|
+
def install_attempt_start_step
|
|
91
|
+
When('I attempt to start the system') do
|
|
92
|
+
@start_error = nil
|
|
93
|
+
Nonnative.start
|
|
94
|
+
rescue StandardError => e
|
|
95
|
+
@start_error = e
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def install_attempt_stop_step
|
|
100
|
+
When('I attempt to stop the system') do
|
|
101
|
+
@stop_error = nil
|
|
102
|
+
Nonnative.stop
|
|
103
|
+
rescue StandardError => e
|
|
104
|
+
@stop_error = e
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
88
108
|
def install_unhealthy_step
|
|
89
109
|
opts = observability_options
|
|
90
110
|
|
|
91
|
-
|
|
111
|
+
Then('I should see {string} as unhealthy') do |service|
|
|
92
112
|
wait_for { Nonnative.observability.health(opts).code }.to eq(503)
|
|
93
113
|
wait_for { Nonnative.observability.health(opts).body }.to include(service)
|
|
94
114
|
end
|
|
@@ -132,11 +152,11 @@ module Nonnative
|
|
|
132
152
|
|
|
133
153
|
def install_error_assertion_steps
|
|
134
154
|
Then('starting the system should raise an error') do
|
|
135
|
-
expect
|
|
155
|
+
expect(@start_error).to be_a(Nonnative::StartError)
|
|
136
156
|
end
|
|
137
157
|
|
|
138
158
|
Then('stopping the system should raise an error') do
|
|
139
|
-
expect
|
|
159
|
+
expect(@stop_error).to be_a(Nonnative::StopError)
|
|
140
160
|
end
|
|
141
161
|
end
|
|
142
162
|
|
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
module Nonnative
|
|
4
4
|
# Socket-pair variant used by the fault-injection proxy to simulate corrupted/incoherent traffic.
|
|
5
5
|
#
|
|
6
|
-
# When active, data written to the upstream socket is corrupted by shuffling the
|
|
7
|
-
#
|
|
6
|
+
# When active, data written to the upstream socket is corrupted by shuffling the payload bytes
|
|
7
|
+
# before forwarding.
|
|
8
8
|
#
|
|
9
9
|
# This behavior is enabled by calling {Nonnative::FaultInjectionProxy#invalid_data}.
|
|
10
10
|
#
|
|
@@ -12,7 +12,10 @@ module Nonnative
|
|
|
12
12
|
# @see Nonnative::SocketPairFactory
|
|
13
13
|
# @see Nonnative::SocketPair
|
|
14
14
|
class InvalidDataSocketPair < SocketPair
|
|
15
|
-
# Writes corrupted data to the socket by shuffling
|
|
15
|
+
# Writes corrupted data to the socket by shuffling bytes.
|
|
16
|
+
#
|
|
17
|
+
# The payload must always change, otherwise short or repetitive inputs such as "test" can
|
|
18
|
+
# occasionally pass through unchanged and make fault-injection scenarios flaky.
|
|
16
19
|
#
|
|
17
20
|
# @param socket [IO] the socket to write to
|
|
18
21
|
# @param data [String] the original payload
|
|
@@ -20,9 +23,46 @@ module Nonnative
|
|
|
20
23
|
def write(socket, data)
|
|
21
24
|
Nonnative.logger.info "shuffling socket data '#{socket.inspect}' for 'invalid_data' pair"
|
|
22
25
|
|
|
23
|
-
|
|
26
|
+
super(socket, corrupt(data))
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
private
|
|
30
|
+
|
|
31
|
+
def corrupt(data)
|
|
32
|
+
payload, delimiter = split_trailing_delimiter(data)
|
|
33
|
+
return "\0#{delimiter}" if payload.empty?
|
|
34
|
+
|
|
35
|
+
"#{corrupt_payload(payload)}#{delimiter}"
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def split_trailing_delimiter(data)
|
|
39
|
+
index = trailing_delimiter_start(data)
|
|
40
|
+
payload = data.byteslice(0, index)
|
|
41
|
+
delimiter = data.byteslice(index, data.bytesize - index) || ''
|
|
42
|
+
|
|
43
|
+
[payload, delimiter]
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def trailing_delimiter_start(data)
|
|
47
|
+
index = data.bytesize
|
|
48
|
+
|
|
49
|
+
while index.positive?
|
|
50
|
+
byte = data.getbyte(index - 1)
|
|
51
|
+
break unless [10, 13].include?(byte)
|
|
52
|
+
|
|
53
|
+
index -= 1
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
index
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def corrupt_payload(payload)
|
|
60
|
+
bytes = payload.bytes
|
|
61
|
+
corrupted = bytes.shuffle
|
|
62
|
+
return corrupted.pack('C*') unless corrupted == bytes
|
|
24
63
|
|
|
25
|
-
|
|
64
|
+
corrupted[0] = (corrupted[0] + 1) % 256
|
|
65
|
+
corrupted.pack('C*')
|
|
26
66
|
end
|
|
27
67
|
end
|
|
28
68
|
end
|
data/lib/nonnative/version.rb
CHANGED