Skip to content

Commit c36b372

Browse files
authored
chore: add e2e test suite (#211)
First pass at adding the standard gherkin suite to flagd ruby. I was able to add quite a lot of coverage without changes - most of what's missing is just the in-process stuff which isn't implemented yet. --------- Signed-off-by: Todd Baert <todd.baert@dynatrace.com>
1 parent 7171b12 commit c36b372

9 files changed

Lines changed: 220 additions & 0 deletions

File tree

.github/workflows/ruby.yml

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,32 @@ jobs:
4444
run: bundle exec rubocop
4545
- name: Run tests
4646
run: bundle exec rspec
47+
48+
test_flagd_provider_e2e:
49+
runs-on: ubuntu-latest
50+
defaults:
51+
run:
52+
working-directory: ./providers/openfeature-flagd-provider
53+
strategy:
54+
fail-fast: false
55+
matrix:
56+
ruby-version:
57+
- "3.3"
58+
- "3.4"
59+
- "4.0"
60+
steps:
61+
- uses: actions/checkout@v7
62+
with:
63+
submodules: recursive
64+
- name: Set up Ruby
65+
uses: ruby/setup-ruby@v1
66+
with:
67+
ruby-version: ${{ matrix.ruby-version }}
68+
bundler-cache: true
69+
working-directory: ./providers/openfeature-flagd-provider
70+
- name: Run e2e (flagd-testbed / cucumber)
71+
run: bundle exec rake e2e
72+
4773
test_meta_provider:
4874
runs-on: ubuntu-latest
4975
defaults:

.gitmodules

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
11
[submodule "providers/openfeature-flagd-provider/schemas"]
22
path = providers/openfeature-flagd-provider/schemas
33
url = https://github.com/open-feature/flagd-schemas
4+
[submodule "providers/openfeature-flagd-provider/test-harness"]
5+
path = providers/openfeature-flagd-provider/test-harness
6+
url = https://github.com/open-feature/flagd-testbed.git

providers/openfeature-flagd-provider/Gemfile.lock

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,37 @@ GEM
1010
specs:
1111
ast (2.4.3)
1212
bigdecimal (4.0.1)
13+
builder (3.3.0)
14+
cucumber (9.2.1)
15+
builder (~> 3.2)
16+
cucumber-ci-environment (> 9, < 11)
17+
cucumber-core (> 13, < 14)
18+
cucumber-cucumber-expressions (~> 17.0)
19+
cucumber-gherkin (> 24, < 28)
20+
cucumber-html-formatter (> 20.3, < 22)
21+
cucumber-messages (> 19, < 25)
22+
diff-lcs (~> 1.5)
23+
mini_mime (~> 1.1)
24+
multi_test (~> 1.1)
25+
sys-uname (~> 1.2)
26+
cucumber-ci-environment (10.0.1)
27+
cucumber-core (13.0.3)
28+
cucumber-gherkin (>= 27, < 28)
29+
cucumber-messages (>= 20, < 23)
30+
cucumber-tag-expressions (> 5, < 7)
31+
cucumber-cucumber-expressions (17.1.0)
32+
bigdecimal
33+
cucumber-gherkin (27.0.0)
34+
cucumber-messages (>= 19.1.4, < 23)
35+
cucumber-html-formatter (21.15.1)
36+
cucumber-messages (> 19, < 28)
37+
cucumber-messages (22.0.0)
38+
cucumber-tag-expressions (6.1.2)
1339
diff-lcs (1.5.1)
1440
docile (1.4.1)
41+
ffi (1.17.4)
42+
ffi (1.17.4-arm64-darwin)
43+
ffi (1.17.4-x86_64-linux-gnu)
1544
google-protobuf (4.34.0)
1645
bigdecimal
1746
rake (~> 13.3)
@@ -35,6 +64,10 @@ GEM
3564
json (2.21.2)
3665
language_server-protocol (3.17.0.5)
3766
lint_roller (1.1.0)
67+
logger (1.7.0)
68+
memoist3 (1.0.0)
69+
mini_mime (1.1.5)
70+
multi_test (1.1.0)
3871
openfeature-sdk (0.3.1)
3972
parallel (1.27.0)
4073
parser (3.3.10.2)
@@ -95,6 +128,9 @@ GEM
95128
standard-performance (1.9.0)
96129
lint_roller (~> 1.1)
97130
rubocop-performance (~> 1.26.0)
131+
sys-uname (1.5.1)
132+
ffi (~> 1.1)
133+
memoist3 (~> 1.0.0)
98134
unicode-display_width (3.2.0)
99135
unicode-emoji (~> 4.1)
100136
unicode-emoji (4.2.0)
@@ -106,6 +142,8 @@ PLATFORMS
106142
x86_64-linux
107143

108144
DEPENDENCIES
145+
cucumber (~> 9.2)
146+
logger
109147
openfeature-flagd-provider!
110148
rake (~> 13.0)
111149
rspec (~> 3.12.0)

providers/openfeature-flagd-provider/Rakefile

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,12 @@
22

33
require "bundler/gem_tasks"
44
require "rspec/core/rake_task"
5+
require "cucumber/rake/task"
56

67
RSpec::Core::RakeTask.new(:spec)
78

9+
# End-to-end suite: runs the shared flagd-testbed gherkin features against a testbed container.
10+
# Requires Docker; not part of the default task.
11+
Cucumber::Rake::Task.new(:e2e)
12+
813
task default: :spec
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
default: --require features --publish-quiet --tags "not @deprecated and not @no-default-variant and not @fractional-v1 and not @fractional-v3" test-harness/gherkin/evaluation.feature test-harness/gherkin/targeting.feature
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
# frozen_string_literal: true
2+
3+
require "json"
4+
5+
module StepHelpers
6+
def cast(type, value)
7+
case type
8+
when "Boolean" then value == "true"
9+
when "Integer" then value.to_i
10+
when "Float" then value.to_f
11+
when "Object" then JSON.parse(value)
12+
else value
13+
end
14+
end
15+
16+
# flagd returns object values as a protobuf Struct; normalise to a Hash for comparison.
17+
def unwrap(value)
18+
value.respond_to?(:to_h) ? value.to_h : value
19+
end
20+
end
21+
World(StepHelpers)
22+
23+
Given(/^an option "[^"]*" of type "[^"]*" with value "[^"]*"$/) do
24+
# no-op: caching/streaming options aren't applicable here
25+
end
26+
27+
Given(/^a stable flagd provider$/) do
28+
@client = OpenFeature::Flagd::Provider.build_client
29+
@context = {}
30+
end
31+
32+
Given(/^a (\w+)-flag with key "([^"]*)" and a default value "([^"]*)"$/) do |type, key, default|
33+
@type = type
34+
@key = key
35+
@default = default
36+
end
37+
38+
Given(/^a context containing a key "([^"]*)", with type "([^"]*)" and with value "([^"]*)"$/) do |key, type, value|
39+
@context[key] = cast(type, value)
40+
end
41+
42+
Given(/^a context containing a targeting key with value "([^"]*)"$/) do |value|
43+
# the provider maps the "targeting_key" field to flagd's targetingKey
44+
@context["targeting_key"] = value
45+
end
46+
47+
Given(/^a context containing a nested property with outer key "([^"]*)" and inner key "([^"]*)", with value "([^"]*)"$/) do |outer, inner, value|
48+
(@context[outer] ||= {})[inner] = value
49+
end
50+
51+
When(/^the flag was evaluated with details$/) do
52+
ctx = @context.empty? ? nil : OpenFeature::SDK::EvaluationContext.new(**@context.transform_keys(&:to_sym))
53+
54+
@details =
55+
case @type
56+
when "Boolean" then @client.fetch_boolean_value(flag_key: @key, default_value: cast("Boolean", @default), evaluation_context: ctx)
57+
when "String" then @client.fetch_string_value(flag_key: @key, default_value: @default, evaluation_context: ctx)
58+
when "Integer" then @client.fetch_integer_value(flag_key: @key, default_value: @default.to_i, evaluation_context: ctx)
59+
when "Float" then @client.fetch_float_value(flag_key: @key, default_value: @default.to_f, evaluation_context: ctx)
60+
when "Object" then @client.fetch_object_value(flag_key: @key, default_value: cast("Object", @default), evaluation_context: ctx)
61+
else raise "unsupported flag type: #{@type}"
62+
end
63+
end
64+
65+
Then(/^the resolved details value should be "([^"]*)"$/) do |value|
66+
actual = (@type == "Object") ? unwrap(@details[:value]) : @details[:value]
67+
expect(actual).to eq(cast(@type, value))
68+
end
69+
70+
Then(/^the reason should be "([^"]*)"$/) do |reason|
71+
expect(@details[:reason]).to eq(reason)
72+
end
73+
74+
Then(/^the error-code should be "([^"]*)"$/) do |code|
75+
if code.empty?
76+
expect(@details[:error_code]).to be_nil
77+
else
78+
expect(@details[:error_code]).to eq(code)
79+
end
80+
end
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
# frozen_string_literal: true
2+
3+
require "net/http"
4+
require "uri"
5+
require "rspec/expectations"
6+
require "open_feature/sdk"
7+
require "openfeature/flagd/provider"
8+
9+
World(RSpec::Matchers)
10+
11+
# Boots the shared flagd testbed container and drives its launchpad so flagd is serving the
12+
# standard flag set before the suite runs. Uses the Docker CLI directly (reliable on CI).
13+
module Testbed
14+
VERSION = File.read(File.expand_path("../../test-harness/version.txt", __dir__)).strip
15+
IMAGE = "ghcr.io/open-feature/flagd-testbed:v#{VERSION}"
16+
NAME = "flagd-e2e-ruby-#{Process.pid}"
17+
18+
module_function
19+
20+
def start
21+
sh("docker rm -f #{NAME}")
22+
raise "failed to start #{IMAGE} (is docker available?)" unless
23+
sh("docker run -d --name #{NAME} -p 8013:8013 -p 8014:8014 -p 8080:8080 #{IMAGE}")
24+
25+
# launchpad starts flagd with the default flag set
26+
wait_for { http_code(:post, "http://localhost:8080/start") }
27+
wait_for { http_code(:get, "http://localhost:8014/healthz") == "200" } ||
28+
raise("flagd testbed did not become healthy in time")
29+
end
30+
31+
def stop
32+
sh("docker rm -f #{NAME}")
33+
end
34+
35+
def sh(cmd)
36+
system(cmd, out: File::NULL, err: File::NULL)
37+
end
38+
39+
def http_code(verb, url)
40+
uri = URI(url)
41+
res = if verb == :post
42+
Net::HTTP.post(uri, "")
43+
else
44+
Net::HTTP.get_response(uri)
45+
end
46+
res.code
47+
rescue
48+
nil
49+
end
50+
51+
def wait_for(timeout: 60)
52+
deadline = Time.now + timeout
53+
loop do
54+
return true if yield
55+
return false if Time.now > deadline
56+
57+
sleep 0.5
58+
end
59+
end
60+
end
61+
62+
# register teardown before startup so a container is cleaned up even if start raises
63+
at_exit { Testbed.stop }
64+
Testbed.start

providers/openfeature-flagd-provider/openfeature-flagd-provider.gemspec

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ Gem::Specification.new do |spec|
3535

3636
spec.add_development_dependency "rake", "~> 13.0"
3737
spec.add_development_dependency "rspec", "~> 3.12.0"
38+
spec.add_development_dependency "cucumber", "~> 9.2"
39+
spec.add_development_dependency "logger"
3840
spec.add_development_dependency "standard"
3941
spec.add_development_dependency "rubocop"
4042
spec.add_development_dependency "simplecov", "~> 0.22"
Submodule test-harness added at 6dea1da

0 commit comments

Comments
 (0)