{"id":30615648,"url":"https://github.com/alexfalkowski/nonnative","last_synced_at":"2026-04-09T11:20:35.460Z","repository":{"id":35036518,"uuid":"199154013","full_name":"alexfalkowski/nonnative","owner":"alexfalkowski","description":"Allows you to keep using the power of ruby to test other systems.","archived":false,"fork":false,"pushed_at":"2025-08-22T04:12:25.000Z","size":1008,"stargazers_count":7,"open_issues_count":0,"forks_count":1,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-08-22T06:23:32.682Z","etag":null,"topics":["bdd","cucumber","ruby"],"latest_commit_sha":null,"homepage":"https://alexfalkowski.github.io/nonnative","language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/alexfalkowski.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2019-07-27T11:07:11.000Z","updated_at":"2025-08-22T04:12:28.000Z","dependencies_parsed_at":"2023-02-16T08:01:29.122Z","dependency_job_id":"ac0d7f1e-a72a-4116-8122-96ec3313d270","html_url":"https://github.com/alexfalkowski/nonnative","commit_stats":{"total_commits":517,"total_committers":3,"mean_commits":"172.33333333333334","dds":0.2437137330754352,"last_synced_commit":"47e245ee781cde16f56f595069aaa09a9a588300"},"previous_names":[],"tags_count":318,"template":false,"template_full_name":null,"purl":"pkg:github/alexfalkowski/nonnative","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alexfalkowski%2Fnonnative","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alexfalkowski%2Fnonnative/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alexfalkowski%2Fnonnative/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alexfalkowski%2Fnonnative/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/alexfalkowski","download_url":"https://codeload.github.com/alexfalkowski/nonnative/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alexfalkowski%2Fnonnative/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":272821200,"owners_count":24998599,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-08-30T02:00:09.474Z","response_time":77,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["bdd","cucumber","ruby"],"created_at":"2025-08-30T08:06:17.957Z","updated_at":"2026-04-09T11:20:35.448Z","avatar_url":"https://github.com/alexfalkowski.png","language":"Ruby","readme":"[![CircleCI](https://circleci.com/gh/alexfalkowski/nonnative.svg?style=shield)](https://circleci.com/gh/alexfalkowski/nonnative)\n[![codecov](https://codecov.io/gh/alexfalkowski/nonnative/graph/badge.svg?token=4ISVHEZ72O)](https://codecov.io/gh/alexfalkowski/nonnative)\n[![Gem Version](https://badge.fury.io/rb/nonnative.svg)](https://badge.fury.io/rb/nonnative)\n[![Stability: Active](https://masterminds.github.io/stability/active.svg)](https://masterminds.github.io/stability/active.html)\n\n# Nonnative\n\nNonnative is a Ruby-first harness for end-to-end testing of systems implemented in other languages.\n\nIt helps you:\n- start **OS processes** (e.g. your Go/Java/Rust service binary),\n- start **in-process Ruby servers** (e.g. small HTTP/TCP/gRPC fakes for dependencies),\n- optionally start **proxies** in front of processes/servers/services for fault-injection,\n- wait for readiness/shutdown using **TCP port checks**.\n\nOnce started, you can test however you like (TCP, HTTP, gRPC, etc).\n\n## Installation\n\nAdd this line to your application's Gemfile:\n\n```ruby\ngem 'nonnative'\n```\n\nAnd then execute:\n\n```bash\nbundle\n```\n\nOr install it yourself as:\n\n```bash\ngem install nonnative\n```\n\n## Usage\n\nNonnative is configured via {#Nonnative.configure} (programmatic) or `config.load_file(...)` (YAML).\n\nHigh-level configuration fields:\n- `version`: configuration version (example: `\"1.0\"`).\n- `name`: logical system name (used by `Nonnative.observability` for `/\u003cname\u003e/healthz`, etc).\n- `url`: base URL for observability queries (example: `http://localhost:4567`).\n- `log`: path for the Nonnative logger output.\n- `processes`: child processes to `spawn`.\n- `servers`: in-process Ruby servers started in threads.\n- `services`: external dependencies (proxy-only; no process/thread started by Nonnative).\n\nRunner fields (process/server/service):\n- `timeout`: max time (seconds) for readiness/shutdown port checks.\n- `wait`: small sleep (seconds) between lifecycle steps.\n- `host`/`port`: client-facing address used for readiness/shutdown port checks. When a `fault_injection` proxy is enabled, this is the endpoint your tests/clients should hit.\n- `log`: per-runner log file (used by process output redirection or server implementations).\n\nFor `fault_injection`, the nested `proxy.host`/`proxy.port` describe the upstream target behind the proxy. In-process server implementations typically bind there via `proxy.host` / `proxy.port`.\n\n### Lifecycle strategies (Cucumber integration)\n\nNonnative ships Cucumber hooks (when loaded) that support these tags/strategies:\n- `@startup`: start before scenario; stop after scenario\n- `@manual`: stop after scenario (start is expected to be triggered manually in steps)\n- `@clear`: clears memoized configuration, logger, observability client, and pool before scenario\n- `@reset`: resets proxies after scenario\n\nRequiring `nonnative` is enough; the Cucumber hooks and step definitions are installed lazily once Cucumber’s Ruby DSL is ready.\n\nIf you want “start once per test run”, require:\n\n```ruby\nrequire 'nonnative/startup'\n```\n\nThis calls `Nonnative.start` immediately and registers an `at_exit` stop.\n\n### Processes\n\nA process is some sort of command that you would run locally.\n\nSetup it up programmatically:\n\n```ruby\nrequire 'nonnative'\n\nNonnative.configure do |config|\n  config.version = '1.0'\n  config.name = 'test'\n  config.url = 'http://localhost:4567'\n  config.log = 'nonnative.log'\n\n  config.process do |p|\n    p.name = 'start_1'\n    p.command = -\u003e { 'features/support/bin/start 12_321' }\n    p.timeout = 5\n    p.wait = 0.1\n    p.port = 12_321\n    p.log = '12_321.log'\n    p.signal = 'INT' # Possible values are described in Signal.list.keys.\n    p.environment = { # Pass environment variables to process.\n      'TEST' =\u003e 'true'\n    }\n  end\n\n  config.process do |p|\n    p.name = 'start_2'\n    p.command = -\u003e { 'features/support/bin/start 12_322' }\n    p.timeout = 0.5\n    p.wait = 0.1\n    p.port = 12_322\n    p.log = '12_322.log'\n  end\nend\n```\n\nSetup it up through configuration:\n\n```yaml\nversion: \"1.0\"\nname: test\nurl: http://localhost:4567\nlog: nonnative.log\nprocesses:\n  -\n    name: start_1\n    command: features/support/bin/start 12_321\n    timeout: 5\n    wait: 1\n    port: 12321\n    log: 12_321.log\n    signal: INT # Possible values are described in Signal.list.keys.\n    environment: # Pass environment variables to process.\n      TEST: true\n  -\n    name: start_2\n    command: features/support/bin/start 12_322\n    timeout: 5\n    wait: 1\n    port: 12322\n    log: 12_322.log\n```\n\nThen load the file with\n\n```ruby\nrequire 'nonnative'\n\nNonnative.configure do |config|\n  config.load_file('configuration.yml')\nend\n```\n\nWith cucumber you can also verify how much memory is used by the process:\n\n```cucumber\nThen the process 'start_1' should consume less than '25mb' of memory\n```\n\n### Servers\n\nA server is a dependency to some external API.\n\nDefine your server:\n\n```ruby\nmodule Nonnative\n  class TCPServer \u003c Nonnative::Server\n    def initialize(service)\n      super\n\n      @socket_server = ::TCPServer.new(proxy.host, proxy.port)\n    end\n\n    def perform_start\n      loop do\n        client_socket = socket_server.accept\n        client_socket.puts 'Hello World!'\n        client_socket.close\n      end\n    rescue StandardError\n      socket_server.close\n    end\n\n    def perform_stop\n      socket_server.close\n    end\n\n    private\n\n    attr_reader :socket_server\n  end\nend\n```\n\nSetup it up programmatically:\n\n```ruby\nrequire 'nonnative'\n\nNonnative.configure do |config|\n  config.version = '1.0'\n  config.name = 'test'\n  config.url = 'http://localhost:4567'\n  config.log = 'nonnative.log'\n\n  config.server do |s|\n    s.name = 'server_1'\n    s.klass = Nonnative::EchoServer\n    s.timeout = 1\n    s.port = 12_323\n    s.log = 'server_1.log'\n  end\n\n  config.server do |s|\n    s.name = 'server_2'\n    s.klass = Nonnative::EchoServer\n    s.timeout = 1\n    s.port = 12_324\n    s.log = 'server_2.log'\n  end\nend\n```\n\nSetup it up through configuration:\n\n```yaml\nversion: \"1.0\"\nname: test\nurl: http://localhost:4567\nlog: nonnative.log\nservers:\n  -\n    name: server_1\n    class: Nonnative::EchoServer\n    timeout: 1\n    port: 12323\n    log: server_1.log\n  -\n    name: server_2\n    class: Nonnative::EchoServer\n    timeout: 1\n    port: 12324\n    log: server_2.log\n```\n\nThen load the file with:\n\n```ruby\nrequire 'nonnative'\n\nNonnative.configure do |config|\n  config.load_file('configuration.yml')\nend\n```\n\n#### HTTP\n\nDefine your server:\n\n```ruby\nmodule Nonnative\n  module Features\n    class Hello \u003c Sinatra::Application\n      get '/hello' do\n        'Hello World!'\n      end\n    end\n\n    class HTTPServer \u003c Nonnative::HTTPServer\n      def initialize(service)\n        super(Sinatra.new(Hello), service)\n      end\n    end\n  end\nend\n```\n\nSetup it up programmatically:\n\n```ruby\nrequire 'nonnative'\n\nNonnative.configure do |config|\n  config.version = '1.0'\n  config.name = 'test'\n  config.url = 'http://localhost:4567'\n  config.log = 'nonnative.log'\n\n  config.server do |s|\n    s.name = 'http_server_1'\n    s.klass = Nonnative::Features::HTTPServer\n    s.timeout = 1\n    s.port = 4567\n    s.log = 'http_server_1.log'\n  end\nend\n```\n\nSetup it up through configuration:\n\n```yaml\nversion: \"1.0\"\nname: test\nurl: http://localhost:4567\nlog: nonnative.log\nservers:\n  -\n    name: http_server_1\n    class: Nonnative::Features::HTTPServer\n    timeout: 1\n    port: 4567\n    log: http_server_1.log\n```\n\nThen load the file with:\n\n```ruby\nrequire 'nonnative'\n\nNonnative.configure do |config|\n  config.load_file('configuration.yml')\nend\n```\n\n##### Proxy\n\nThe system allows you to define a http proxy for external systems, e.g api.github.com\n\nDefine your server:\n\n```ruby\nmodule Nonnative\n  module Features\n    class HTTPProxyServer \u003c Nonnative::HTTPProxyServer\n      def initialize(service)\n        super('www.afalkowski.com', service)\n      end\n    end\n  end\nend\n```\n\nSetup it up programmatically:\n\n```ruby\nrequire 'nonnative'\n\nNonnative.configure do |config|\n  config.version = '1.0'\n  config.name = 'test'\n  config.url = 'http://localhost:4567'\n  config.log = 'nonnative.log'\n\n  config.server do |s|\n    s.name = 'http_server_proxy'\n    s.klass = Nonnative::Features::HTTPProxyServer\n    s.timeout = 1\n    s.port = 4567\n    s.log = 'http_server_proxy.log'\n  end\nend\n```\n\nSetup it up through configuration:\n\n```yaml\nversion: \"1.0\"\nname: test\nurl: http://localhost:4567\nlog: nonnative.log\nservers:\n  -\n    name: http_server_proxy\n    class: Nonnative::Features::HTTPProxyServer\n    timeout: 1\n    port: 4567\n    log: http_server_proxy.log\n```\n\nThen load the file with:\n\n```ruby\nrequire 'nonnative'\n\nNonnative.configure do |config|\n  config.load_file('configuration.yml')\nend\n```\n\n#### gRPC\n\nDefine your server:\n\n```ruby\nmodule Nonnative\n  module Features\n    class Greeter \u003c GreeterService::Service\n      def say_hello(request, _call)\n        Nonnative::Features::SayHelloResponse.new(message: request.name.to_s)\n      end\n    end\n\n    class GRPCServer \u003c Nonnative::GRPCServer\n      def initialize(service)\n        super(Greeter.new, service)\n      end\n    end\n  end\nend\n```\n\nSetup it up programmatically:\n\n```ruby\nrequire 'nonnative'\n\nNonnative.configure do |config|\n  config.version = '1.0'\n  config.name = 'test'\n  config.url = 'http://localhost:4567'\n  config.log = 'nonnative.log'\n\n  config.server do |s|\n    s.name = 'grpc_server_1'\n    s.klass = Nonnative::Features::GRPCServer\n    s.timeout = 1\n    s.port = 9002\n    s.log = 'grpc_server_1.log'\n  end\nend\n```\n\nSetup it up through configuration:\n\n```yaml\nversion: \"1.0\"\nname: test\nurl: http://localhost:4567\nlog: nonnative.log\nservers:\n  -\n    name: grpc_server_1\n    class: Nonnative::Features::GRPCServer\n    timeout: 1\n    port: 9002\n    log: grpc_server_1.log\n```\n\nThen load the file with:\n\n```ruby\nrequire 'nonnative'\n\nNonnative.configure do |config|\n  config.load_file('configuration.yml')\nend\n```\n\n### Services\n\nA 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).\n\nSet it up programmatically:\n\n```ruby\nrequire 'nonnative'\n\nNonnative.configure do |config|\n  config.version = '1.0'\n  config.name = 'test'\n  config.url = 'http://localhost:4567'\n  config.log = 'nonnative.log'\n\n  config.service do |s|\n    s.name = 'postgres'\n    s.host = '127.0.0.1'\n    s.port = 5432\n  end\n\n  config.service do |s|\n    s.name = 'redis'\n    s.host = '127.0.0.1'\n    s.port = 6379\n  end\nend\n```\n\nSet it up through configuration (YAML):\n\n```yaml\nversion: \"1.0\"\nname: test\nurl: http://localhost:4567\nlog: nonnative.log\nservices:\n  -\n    name: postgres\n    host: 127.0.0.1\n    port: 5432\n  -\n    name: redis\n    host: 127.0.0.1\n    port: 6379\n```\n\nThen load the file with:\n\n```ruby\nrequire 'nonnative'\n\nNonnative.configure do |config|\n  config.load_file('configuration.yml')\nend\n```\n\n#### Proxies\n\nWe allow different proxies to be configured. These proxies can be used to simulate all kind of situations. The proxies that can be configured are:\n\n- `none` (this is the default)\n- `fault_injection`\n\nFor `fault_injection`, keep the runner `host`/`port` as the client-facing endpoint and use nested `proxy.host`/`proxy.port` for the upstream target behind the proxy.\n\n##### Proxies Processes\n\nAdd this to an existing process configuration:\n\n```ruby\nrequire 'nonnative'\n\nNonnative.configure do |config|\n  config.version = '1.0'\n  config.name = 'test'\n  config.url = 'http://localhost:4567'\n  config.log = 'nonnative.log'\n\n  config.process do |p|\n    p.proxy = {\n      kind: 'fault_injection',\n      port: 20_000,\n      log: 'proxy_server.log',\n      wait: 1,\n      options: {\n        delay: 5\n      }\n    }\n  end\nend\n```\n\nYAML fragment:\n\n```yaml\nversion: \"1.0\"\nname: test\nurl: http://localhost:4567\nlog: nonnative.log\nprocesses:\n  -\n    proxy:\n      kind: fault_injection\n      port: 20000\n      log: proxy_server.log\n      wait: 1\n      options:\n        delay: 5\n```\n\n##### Proxies Servers\n\nAdd this to an existing server configuration:\n\n```ruby\nrequire 'nonnative'\n\nNonnative.configure do |config|\n  config.version = '1.0'\n  config.name = 'test'\n  config.url = 'http://localhost:4567'\n  config.log = 'nonnative.log'\n\n  config.server do |s|\n    s.proxy = {\n      kind: 'fault_injection',\n      port: 20_000,\n      log: 'proxy_server.log',\n      wait: 1,\n      options: {\n        delay: 5\n      }\n    }\n  end\nend\n```\n\nYAML fragment:\n\n```yaml\nversion: \"1.0\"\nname: test\nurl: http://localhost:4567\nlog: nonnative.log\nservers:\n  -\n    proxy:\n      kind: fault_injection\n      port: 20000\n      log: proxy_server.log\n      wait: 1\n      options:\n        delay: 5\n```\n\n##### Proxies Services\n\nAdd this to an existing service configuration:\n\n```ruby\nrequire 'nonnative'\n\nNonnative.configure do |config|\n  config.version = '1.0'\n  config.name = 'test'\n  config.url = 'http://localhost:4567'\n  config.log = 'nonnative.log'\n\n  config.service do |s|\n    s.name = 'redis'\n    s.host = '127.0.0.1'\n    s.port = 16_379\n\n    s.proxy = {\n      kind: 'fault_injection',\n      host: '127.0.0.1',\n      port: 6379,\n      log: 'proxy_server.log',\n      wait: 1,\n      options: {\n        delay: 5\n      }\n    }\n  end\nend\n```\n\nYAML fragment:\n\n```yaml\nversion: \"1.0\"\nname: test\nurl: http://localhost:4567\nlog: nonnative.log\nservices:\n  -\n    name: redis\n    host: 127.0.0.1\n    port: 16379\n    proxy:\n      kind: fault_injection\n      host: 127.0.0.1\n      port: 6379\n      log: proxy_server.log\n      wait: 1\n      options:\n        delay: 5\n```\n\n##### Fault Injection\n\nThe `fault_injection` proxy allows you to simulate failures by injecting them. We currently support the following:\n\n- `close_all` - Closes the socket as soon as it connects.\n- `delay` - This delays the communication between the connection. Default is 2 secs can be configured through options.\n- `invalid_data` - This takes the input and rearranges it to produce invalid data.\n\n###### Fault Injection Processes\n\nSetup it up programmatically:\n\n```ruby\nname = 'name of process in configuration'\nserver = Nonnative.pool.process_by_name(name)\n\nserver.proxy.close_all # To use close_all.\nserver.proxy.reset # To reset it back to a good state.\n```\n\nWith cucumber:\n\n```cucumber\nGiven I set the proxy for process 'process_1' to 'close_all'\nThen I should reset the proxy for process 'process_1'\n```\n\n###### Fault Injection Servers\n\nSetup it up programmatically:\n\n```ruby\nname = 'name of server in configuration'\nserver = Nonnative.pool.server_by_name(name)\n\nserver.proxy.close_all # To use close_all.\nserver.proxy.reset # To reset it back to a good state.\n```\n\nWith cucumber:\n\n```cucumber\nGiven I set the proxy for server 'server_1' to 'close_all'\nThen I should reset the proxy for server 'server_1'\n```\n\n###### Fault Injection Services\n\nSetup it up programmatically:\n\n```ruby\nname = 'name of service in configuration'\nservice = Nonnative.pool.service_by_name(name)\n\nservice.proxy.close_all # To use close_all.\nservice.proxy.reset # To reset it back to a good state.\n```\n\nWith cucumber:\n\n```cucumber\nGiven I set the proxy for service 'service_1' to 'close_all'\nThen I should reset the proxy for service 'service_1'\n```\n\n### Go\n\nAs we love using go as a language for services we have added support to start binaries with defined parameters. This expects that you build your services in the format of `command sub_command --params`\n\nTo get this to work you will need to create a `main_test.go` file with these contents:\n\n```go\n// +build features\n\npackage main\n\nimport \"testing\"\n\nfunc TestFeatures(t *testing.T) {\n main()\n}\n```\n\nThen to compile this binary you will need to do the following:\n\n```sh\ngo test -mod vendor -c -tags features -covermode=count -o your_binary -coverpkg=./... github.com/your_location\n```\n\nSetup it up programmatically:\n\n```ruby\ntools = %w[prof trace cover]\n\nNonnative.go_executable(tools, 'reports', 'your_binary', 'sub_command', '--config config.yaml')\n```\n\nSetup it up through configuration:\n\n```yaml\nversion: \"1.0\"\nname: test\nurl: http://localhost:4567\nlog: nonnative.log\nprocesses:\n  -\n    name: go\n    go:\n      tools: [prof, trace, cover]\n      output: reports\n      executable: your_binary\n      command: sub_command\n      parameters:\n        - --config config.yaml\n    timeout: 5\n    port: 8000\n    log: go.log\n```\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Falexfalkowski%2Fnonnative","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Falexfalkowski%2Fnonnative","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Falexfalkowski%2Fnonnative/lists"}