This repository is the nonnative Ruby gem. It provides a Ruby-first harness for end-to-end testing of services implemented in other languages by starting processes/servers/services, waiting for readiness (port checks), and optionally running fault-injection proxies.
Everything below is based on the files currently in this repo (no assumptions).
- Public API RDoc pass started:
- Added/expanded RDoc for the module entry point (
Nonnative) and core configuration/error types. - Goal: document public APIs only (gem consumers), not internal/private helpers.
- Added/expanded RDoc for the module entry point (
- README accuracy pass started:
- Fixed incorrect examples around services:
- Programmatic example incorrectly used
p.portinside aconfig.service do |s| ... endblock; corrected tos.host/s.port. - YAML example incorrectly used
processes:for services; corrected toservices:.
- Programmatic example incorrectly used
- Removed/avoided implying non-existent config fields (e.g. there is no top-level
config.wait;waitis per-runner).
- Fixed incorrect examples around services:
These changes were made to align docs/examples with the actual runtime/config objects under lib/nonnative/**/*.rb.
- Library code:
lib/nonnative/**/*.rb - Acceptance tests:
features/**/*.feature+features/support/**/*.rb+features/step_definitions/**/*.rb(Cucumber) - Generated gRPC Ruby stubs for tests:
test/grpc/**/*(excluded from RuboCop) - Proto definitions for test fixtures:
test/nonnative/v1/*.proto - Build system:
Makefileincludes make fragments from thebin/submodule (bin/build/make/*.mak).
This repo uses a git submodule at bin/:
.gitmodulespoints togit@github.com:alexfalkowski/bin.git.- CI runs
git submodule sync && git submodule update --initbefore runningmaketargets (.circleci/config.yml).
If you don’t have SSH access to that repo, make targets that include bin/build/make/*.mak will fail.
git submodule sync
git submodule update --initnonnative.gemspecrequires Ruby>= 3.4.0and< 4.0.0.
Ruby deps are managed by Bundler and installed into vendor/bundle:
make depImplementation lives in bin/build/make/ruby.mak (included by root Makefile).
make lintRuns RuboCop:
bundler exec rubocop(bin/build/make/ruby.mak:4-5)- Config:
.rubocop.yml(TargetRubyVersion 3.4; max line length 150)
Auto-fix:
make fix-lint
# or
make formatmake featuresThis calls bin/quality/ruby/feature, which runs:
bundler exec cucumber --profile report ... --tags "not @benchmark" ...- Cucumber profile
reportis defined in.config/cucumber.ymland writes:- JUnit XML to
test/reports - HTML report to
test/reports/index.html
- JUnit XML to
Run only benchmarks:
make benchmarksCucumber loads SimpleCov via features/support/env.rb and writes coverage output under test/reports/.
Codecov config exists in .codecov.yml and CI uploads coverage in .circleci/config.yml.
Local upload target:
make codecov-uploadmake clean-dep
make clean-reportsCucumber integration lives in lib/nonnative/cucumber.rb:
@startup: starts and stops Nonnative around each scenario.@manual: only stops after scenario (start is expected to be triggered manually in steps).@clear: callsNonnative.clearbefore scenario.@reset: resets proxies after scenario.
The “start once per test run” strategy is implemented by requiring nonnative/startup:
lib/nonnative/startup.rbcallsNonnative.startand registers anat_exitstop.
- Service vs process YAML keys:
- Services must be declared under
services:in YAML (notprocesses:). Code readscfg.servicesand maps them toNonnative::ConfigurationService.
- Services must be declared under
- No top-level
wait:waitis defined on runner configurations (ConfigurationRunner#wait) and is per process/server/service.
- Proxy wiring is easy to misunderstand:
- When a proxy kind like
fault_injectionis enabled, the proxy binds toservice.proxy.host/service.proxy.port. - Readiness checks and traffic should typically target the proxy host/port, not the underlying service host/port.
- When a proxy kind like
features/support/ contains small servers/clients used by cucumber scenarios (HTTP/TCP/gRPC). Example:
features/support/http_server.rbdefines a Sinatra app for/helloand health endpoints.
Scenarios start a local process via features/support/bin/start (referenced in step definitions and YAML configs like features/configs/processes.yml).
lib/nonnative.rbdefines theNonnativemodule singleton API:configure { |config| ... }start/stopclear,resetpoolis created onstart(Nonnative::Pool.new(configuration)).
Nonnative::Configuration(lib/nonnative/configuration.rb) holds arrays of:processes(Nonnative::ConfigurationProcess)servers(Nonnative::ConfigurationServer)services(Nonnative::ConfigurationService)
It can be populated either:
- programmatically via
config.process { ... },config.server { ... },config.service { ... } - via YAML using
config.load_file(path)which callsConfig.load_files(...)(theconfiggem)
Proxies are configured via ConfigurationProxy (lib/nonnative/configuration_proxy.rb) and attached to runners as a hash.
There are three runtime “runner” types, all subclassing Runner (lib/nonnative/runner.rb):
Nonnative::Process(lib/nonnative/process.rb):spawn(...)+Process.kill/waitpid2.Nonnative::Server(lib/nonnative/server.rb):Thread.new { perform_start }+perform_stop.Nonnative::Service(lib/nonnative/service.rb): no process management; proxy only.
Nonnative::Pool (lib/nonnative/pool.rb) owns collections of runners and orchestrates start/stop:
- starts services first, then servers/processes
- stops processes/servers first, then services
- readiness is determined via
Nonnative::Port#open?/#closed?(lib/nonnative/port.rb) which repeatedly triesTCPSocket.new(host, port)inside a timeout.
Proxy selection is keyed by kind:
- mapping:
Nonnative.proxiesinlib/nonnative.rb - default proxy config values:
ConfigurationProxy#initializesets kindnone, host0.0.0.0, wait0.1, etc.
Implemented proxies:
Nonnative::NoProxy(lib/nonnative/no_proxy.rb)Nonnative::FaultInjectionProxy(lib/nonnative/fault_injection_proxy.rb)- states:
:none,:close_all,:delay,:invalid_data - delegates behavior to socket-pair classes via
SocketPairFactory(lib/nonnative/socket_pair_factory.rb).
- states:
There is a helper for building a Go test binary command line with optional profiling/trace/coverage flags:
Nonnative.go_executableinlib/nonnative.rbNonnative::GoCommandinlib/nonnative/go_command.rb
This is used when YAML process config has a go: section (see Configuration#command in lib/nonnative/configuration.rb).
- Ruby style is enforced by RuboCop (
.rubocop.yml):- Target Ruby 3.4
- Line length 150
Style/Documentationdisabled
.editorconfig:indent_size = 2for most files- Makefiles use tabs
- Many Ruby files use
# frozen_string_literal: true.
CircleCI runs (see .circleci/config.yml):
make source-key(defined inbin/build/make/git.mak) to generate.source-keyused for cachingmake dep,make clean-depmake lint,make features- uploads
test/reportsartifacts
- Submodule required: root
Makefileonly includesbin/...make fragments; withoutbin/present/updated,makewon’t work. - SSH-only submodule URL:
.gitmodulesusesgit@github.com:...; CI or local environments without SSH keys will fail to init the submodule. - Local Ruby/Bundler mismatch can break native extensions: on macOS,
make lint/bundle exec ...may fail if the Ruby used to install gems differs from the Ruby used to run them (example error seen:prism.bundlemissinglibruby.3.4.dylib). - Requiring
nonnativeloads Cucumber DSL:lib/nonnative.rbrequireslib/nonnative/cucumber.rb, which callsWorld(...). Outside a Cucumber runtime this can raise (example error seen:Cucumber::Glue::Dsl.build_rb_world_factory). - Reports directory: Cucumber report profile writes into
test/reports/and the repo keeps atest/reports/.keepfile. - Port checks can be flaky if ports are reused: readiness is purely
TCPSocket-based (lib/nonnative/port.rb), so ensure test fixtures bind expected ports.
- Lifecycle orchestration:
lib/nonnative.rb,lib/nonnative/pool.rb - Readiness / timeouts:
lib/nonnative/port.rb,lib/nonnative/timeout.rb - Process management:
lib/nonnative/process.rb - Proxies / fault injection:
lib/nonnative/fault_injection_proxy.rb,lib/nonnative/socket_pair_factory.rb - Cucumber integration:
lib/nonnative/cucumber.rb,lib/nonnative/startup.rb,features/support/env.rb