glowworm 0.3.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 +15 -0
- data/.gitignore +6 -0
- data/.yardopts +1 -0
- data/Gemfile +5 -0
- data/Gemfile.lock +129 -0
- data/LICENSE +19 -0
- data/README.md +326 -0
- data/Rakefile +29 -0
- data/bin/basic_server_tester +311 -0
- data/bin/em_server/config.ru +2 -0
- data/bin/em_server/em_server.rb +19 -0
- data/bin/em_server_tester +68 -0
- data/bin/glowworm +90 -0
- data/bin/load_tester +84 -0
- data/ci_jobs/glowworm-continuous-deploy-next-staging/run.sh +10 -0
- data/ci_jobs/glowworm-integrations/run.sh +15 -0
- data/ci_jobs/glowworm-performance/run.sh +2 -0
- data/ci_jobs/glowworm-robustness/run.sh +2 -0
- data/ci_jobs/glowworm-units/run.sh +13 -0
- data/ci_jobs/setup.sh +119 -0
- data/example/example_server.ecology +6 -0
- data/example/example_server.rb +32 -0
- data/glowworm.gemspec +54 -0
- data/lib/glowworm.rb +501 -0
- data/lib/glowworm/em.rb +8 -0
- data/lib/glowworm/no_bg.rb +2 -0
- data/lib/glowworm/version.rb +3 -0
- data/server/Gemfile +27 -0
- data/server/Gemfile.lock +87 -0
- data/server/PROTOCOL +39 -0
- data/server/check_mk_checks/check_glowworm_server +43 -0
- data/server/db_migrations/20111004214649_change_feature_accounts_to_string.rb +60 -0
- data/server/db_migrations/20111028104546_add_value_to_account_set_features.rb +12 -0
- data/server/db_migrations/20120217090636_add_fully_active_flag_to_features.rb +15 -0
- data/server/example_test_data.rb +66 -0
- data/server/glowworm_server.ecology.erb +16 -0
- data/server/glowworm_server.rb +226 -0
- data/server/run/server.sh +7 -0
- data/server/server_test.rb +72 -0
- data/server/version.rb +3 -0
- data/test/integration/basic_server_test.rb +90 -0
- data/test/integration/create_sqlite_data.rb +196 -0
- data/test/integration/em_server_test.rb +68 -0
- data/test/integration/gemfile_for_specific_glowworm_version +17 -0
- data/test/integration/gemfile_for_specific_glowworm_version.lock +55 -0
- data/test/integration/integration_test_helper.rb +153 -0
- data/test/integration/load_test.rb +59 -0
- data/test/integration/nginx.conf +23 -0
- data/test/integration/server_test.ecology.erb +6 -0
- data/test/test_helper.rb +47 -0
- data/test/units/em_test.rb +41 -0
- data/test/units/feature_flag_test.rb +297 -0
- data/test/units/no_bg_test.rb +40 -0
- data/test/units/request_test.rb +51 -0
- metadata +410 -0
@@ -0,0 +1,55 @@
|
|
1
|
+
GEM
|
2
|
+
remote: http://rubygems.org/
|
3
|
+
remote: http://gems.sv2/
|
4
|
+
remote: http://gems.us-east-1.ooyala.com:8080/
|
5
|
+
specs:
|
6
|
+
ansi (1.4.2)
|
7
|
+
ecology (0.0.18)
|
8
|
+
erubis
|
9
|
+
multi_json
|
10
|
+
erubis (2.7.0)
|
11
|
+
glowworm (0.0.22)
|
12
|
+
ecology (~> 0.0.12)
|
13
|
+
httparty
|
14
|
+
multi_json
|
15
|
+
termite (~> 0.0.13)
|
16
|
+
trollop
|
17
|
+
httparty (0.8.3)
|
18
|
+
multi_json (~> 1.0)
|
19
|
+
multi_xml
|
20
|
+
json (1.6.6)
|
21
|
+
metaclass (0.0.1)
|
22
|
+
minitap (0.3.3.1)
|
23
|
+
minitest
|
24
|
+
tapout (>= 0.3.0)
|
25
|
+
minitest (2.12.1)
|
26
|
+
mocha (0.11.3)
|
27
|
+
metaclass (~> 0.0.1)
|
28
|
+
multi_json (1.3.2)
|
29
|
+
multi_xml (0.4.4)
|
30
|
+
rainbow (1.1.4)
|
31
|
+
rake (0.9.2.2)
|
32
|
+
scope (0.2.3)
|
33
|
+
minitest
|
34
|
+
tapout (0.4.1)
|
35
|
+
ansi
|
36
|
+
json
|
37
|
+
termite (0.0.18)
|
38
|
+
ecology (~> 0.0.6)
|
39
|
+
multi_json
|
40
|
+
rainbow (~> 1.1.3)
|
41
|
+
trollop (1.16.2)
|
42
|
+
|
43
|
+
PLATFORMS
|
44
|
+
ruby
|
45
|
+
|
46
|
+
DEPENDENCIES
|
47
|
+
ansi
|
48
|
+
glowworm (= 0.0.22)
|
49
|
+
httparty
|
50
|
+
minitap (>= 0.3.3.1)
|
51
|
+
minitest
|
52
|
+
mocha
|
53
|
+
rake
|
54
|
+
scope
|
55
|
+
tapout
|
@@ -0,0 +1,153 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# This must be first, before anything else
|
3
|
+
#require_relative "../glowworm_simplecov"
|
4
|
+
|
5
|
+
require "nodule"
|
6
|
+
require "nodule/util"
|
7
|
+
require 'nodule/tempfile'
|
8
|
+
require "scope"
|
9
|
+
require "minitest/autorun"
|
10
|
+
require "timeout"
|
11
|
+
|
12
|
+
# use minitap if TAP output is requested
|
13
|
+
if ENV["TAP_OUTPUT"] == "true"
|
14
|
+
require "minitap"
|
15
|
+
end
|
16
|
+
|
17
|
+
GLOWWORM_ROOT = File.join(File.dirname(__FILE__), "..", "..")
|
18
|
+
GLOWWORM_SERVER_BIN="#{GLOWWORM_ROOT}/server/glowworm_server.rb"
|
19
|
+
GLOWWORM_ECOLOGY_FILE = File.join(File.dirname(__FILE__), "server_test.ecology")
|
20
|
+
GLOWWORM_TCP_PORT = 4999 # Nodule::Util.random_tcp_port
|
21
|
+
GW_URI = "http://localhost:#{GLOWWORM_TCP_PORT}"
|
22
|
+
|
23
|
+
def set_test_alarm(timeout=30)
|
24
|
+
Signal.trap("ALRM") do
|
25
|
+
assert false, "Timed out."
|
26
|
+
Thread.list.each { |t| t.kill unless t == Thread.current }
|
27
|
+
exit
|
28
|
+
end
|
29
|
+
LibC.alarm(timeout)
|
30
|
+
end
|
31
|
+
|
32
|
+
def cancel_test_alarm
|
33
|
+
LibC.alarm(0)
|
34
|
+
end
|
35
|
+
|
36
|
+
class Scope::TestCase
|
37
|
+
# The ruby distribution (version + gemset) to be used for the server
|
38
|
+
def server_ruby_dist
|
39
|
+
ENV["TEST_SERVER_RUBY_DIST"] || ENV["TEST_RUBY_DIST"] || "#{RUBY_VERSION}-p#{RUBY_PATCHLEVEL}"
|
40
|
+
end
|
41
|
+
|
42
|
+
# The ruby distribution (version + gemset) to be used for the client.
|
43
|
+
def client_ruby_dist
|
44
|
+
ENV["TEST_CLIENT_RUBY_DIST"] || ENV["TEST_RUBY_DIST"] || "#{RUBY_VERSION}-p#{RUBY_PATCHLEVEL}"
|
45
|
+
end
|
46
|
+
|
47
|
+
def cmd_in_server_ruby_dist(cmd, *args)
|
48
|
+
"eval \"$(rbenv init -)\" && rbenv shell #{server_ruby_dist} && bundle exec ruby #{cmd} " + args.join(' ')
|
49
|
+
end
|
50
|
+
|
51
|
+
def cmd_in_client_ruby_dist(cmd, *args)
|
52
|
+
|
53
|
+
# If no glowworm version is requested or the requested version is "latest" then execute ruby
|
54
|
+
# in the context of the client's bundle. This bundle is set up to include the lib directory
|
55
|
+
# in this case.
|
56
|
+
"eval \"$(rbenv init -)\" && rbenv shell #{client_ruby_dist} && bundle exec ruby #{cmd} " + args.join(' ')
|
57
|
+
end
|
58
|
+
|
59
|
+
def results_dir
|
60
|
+
# if RESULTS_DIR is defined place results under there. Otherwise place under test
|
61
|
+
out_path = ENV["RESULTS_DIR"] || File.join(GLOWWORM_ROOT, "test", "results")
|
62
|
+
Dir.mkdir out_path unless Dir.exists? out_path
|
63
|
+
out_path
|
64
|
+
end
|
65
|
+
|
66
|
+
def results_path(in_name)
|
67
|
+
File.join(results_dir, in_name)
|
68
|
+
end
|
69
|
+
|
70
|
+
def client_gemfile
|
71
|
+
if nil == ENV["TEST_GLOWWORM_VERSION"] || "latest" == ENV["TEST_GLOWWORM_VERSION"]
|
72
|
+
"#{GLOWWORM_ROOT}/Gemfile"
|
73
|
+
else
|
74
|
+
"#{GLOWWORM_ROOT}/test/integration/gemfile_for_specific_glowworm_version"
|
75
|
+
end
|
76
|
+
end
|
77
|
+
# starts a glowworm server along with the given tester. The tester cmd is executed by bash in the
|
78
|
+
# glowworm directory. The tester is expected to output TAP to STDOUT and all information messages
|
79
|
+
# to STDERR.
|
80
|
+
def start_tester_topology(tester_cmd, sec_to_sleep = 0, extra_server = {})
|
81
|
+
|
82
|
+
@hash = {
|
83
|
+
:greenio => Nodule::Console.new(:fg => :green),
|
84
|
+
:redio => Nodule::Console.new(:fg => :red),
|
85
|
+
:cyanio => Nodule::Console.new(:fg => :cyan),
|
86
|
+
:yellowio => Nodule::Console.new(:fg => :yellow),
|
87
|
+
|
88
|
+
:gw_nginx => Nodule::Process.new(
|
89
|
+
"cp #{GLOWWORM_ROOT}/server/config/nginx.conf /tmp/glowworm-nginx.conf; nginx -c $PWD/test/integration/nginx.conf",
|
90
|
+
:stdout => :greenio, :stderr => :redio, :verbose => :cyanio
|
91
|
+
),
|
92
|
+
|
93
|
+
:gw_server => Nodule::Process.new(
|
94
|
+
{ "ECOLOGY_SPEC" => GLOWWORM_ECOLOGY_FILE,
|
95
|
+
"GLOWWORM_SERVER_PORT" => GLOWWORM_TCP_PORT.to_s,
|
96
|
+
"GLOWWORM_ROOT" => GLOWWORM_ROOT },
|
97
|
+
*cmd_in_server_ruby_dist(GLOWWORM_SERVER_BIN),
|
98
|
+
:stdout => :greenio, :stderr => :capture, :verbose => :cyanio
|
99
|
+
),
|
100
|
+
|
101
|
+
:gw_tester => Nodule::Process.new(
|
102
|
+
{ "RUBYOPT" => "-rubygems",
|
103
|
+
"ECOLOGY_SPEC" => GLOWWORM_ECOLOGY_FILE,
|
104
|
+
"GW_URI" => GW_URI,
|
105
|
+
"BUNDLE_GEMFILE" => client_gemfile },
|
106
|
+
"bash", "-l", "-c", tester_cmd,
|
107
|
+
:stdout => :greenio, :verbose => :cyanio
|
108
|
+
)
|
109
|
+
}.merge! extra_server
|
110
|
+
|
111
|
+
@topology = Nodule::Topology.new( @hash )
|
112
|
+
|
113
|
+
@topology.start :gw_nginx
|
114
|
+
|
115
|
+
([:gw_server] + extra_server.keys).each do |key|
|
116
|
+
@topology.start(key)
|
117
|
+
STDERR.puts "Starting #{key.to_s}"
|
118
|
+
begin
|
119
|
+
Timeout::timeout(60) do
|
120
|
+
elapsed = 0
|
121
|
+
while true
|
122
|
+
if key == :gw_server
|
123
|
+
break if @topology[key].stderr.any? do |line|
|
124
|
+
line["NEW_DATA"] || line["No new data"]
|
125
|
+
end
|
126
|
+
else
|
127
|
+
break if elapsed >= sec_to_sleep
|
128
|
+
end
|
129
|
+
sleep 0.01
|
130
|
+
elapsed = elapsed + 0.01
|
131
|
+
|
132
|
+
# failed to execute the command most likely
|
133
|
+
if @topology[key].done?
|
134
|
+
fail "could not start #{key.to_s}"
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
rescue Timeout::Error
|
139
|
+
fail "#{key.to_s} did not start in 60 seconds"
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
@topology.start_all
|
144
|
+
@topology[:gw_tester].waitpid(0)
|
145
|
+
@topology
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
# TODO(corey): This should output to a test specific tap file.
|
150
|
+
if ENV["TAP_OUTPUT"] == "true"
|
151
|
+
MiniTest::Unit.runner = MiniTest::TapY.new
|
152
|
+
end
|
153
|
+
|
@@ -0,0 +1,59 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require File.join(File.dirname(__FILE__), "integration_test_helper")
|
4
|
+
require "net/http"
|
5
|
+
require 'multi_json'
|
6
|
+
require "trollop"
|
7
|
+
require "rainbow"
|
8
|
+
|
9
|
+
LOAD_TESTER_BIN = "#{GLOWWORM_ROOT}/bin/load_tester"
|
10
|
+
|
11
|
+
class LoadTest < Scope::TestCase
|
12
|
+
def run_tester(concurrent = 1, runs = 1, account = 123, feature = "foo")
|
13
|
+
@topology = nil
|
14
|
+
begin
|
15
|
+
tester_tapy = results_path('load_test.stdout')
|
16
|
+
|
17
|
+
tester_cmd = cmd_in_client_ruby_dist(LOAD_TESTER_BIN, "--account #{account} \
|
18
|
+
--feature #{feature} --server #{GW_URI} --fork true --concurrent #{concurrent} --runs #{runs} \
|
19
|
+
--header false | tee #{tester_tapy}")
|
20
|
+
|
21
|
+
@topology = start_tester_topology(tester_cmd)
|
22
|
+
tester_output = File.read(tester_tapy)
|
23
|
+
ensure
|
24
|
+
@topology.stop_all if nil != @topology
|
25
|
+
|
26
|
+
# TODO(noah): Hideous hack until we have real RVM/Nodule integration
|
27
|
+
system("pkill -9 -f glowworm_server.rb")
|
28
|
+
end
|
29
|
+
tester_output
|
30
|
+
end
|
31
|
+
|
32
|
+
setup_once do
|
33
|
+
system("ECOLOGY_SPEC=\"#{GLOWWORM_ECOLOGY_FILE}\" #{GLOWWORM_ROOT}/test/integration/create_sqlite_data.rb")
|
34
|
+
fail "Couldn't create SQLite data successfully!" unless $?.success?
|
35
|
+
end
|
36
|
+
|
37
|
+
context "with Glowworm server and tester" do
|
38
|
+
should "test server response for one process many requests" do
|
39
|
+
# TODO(jbhat): write the test to check @tester_output
|
40
|
+
puts "Running 1 process making 100 requests".color(:yellow)
|
41
|
+
tester_output = run_tester(1, 100)
|
42
|
+
puts tester_output.color(:magenta)
|
43
|
+
end
|
44
|
+
|
45
|
+
should "test server response for many processes one request" do
|
46
|
+
# TODO(jbhat): write the test to check @tester_output
|
47
|
+
puts "Running 100 process making 1 requests".color(:yellow)
|
48
|
+
tester_output = run_tester(100, 1)
|
49
|
+
puts tester_output.color(:magenta)
|
50
|
+
end
|
51
|
+
|
52
|
+
should "test server response for some processes and some stats" do
|
53
|
+
# TODO(jbhat): write the test to check @tester_output
|
54
|
+
puts "Running 10 process making 10 requests".color(:yellow)
|
55
|
+
tester_output = run_tester(10, 10)
|
56
|
+
puts tester_output.color(:magenta)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
worker_processes 1;
|
2
|
+
|
3
|
+
error_log /tmp/glowworm-nginx-error.log;
|
4
|
+
pid /tmp/glowworm-nginx-nginx.pid;
|
5
|
+
|
6
|
+
events {
|
7
|
+
worker_connections 1024;
|
8
|
+
}
|
9
|
+
|
10
|
+
http {
|
11
|
+
access_log /tmp/glowworm-nginx-access.log;
|
12
|
+
|
13
|
+
sendfile on;
|
14
|
+
|
15
|
+
keepalive_timeout 65;
|
16
|
+
tcp_nodelay on;
|
17
|
+
|
18
|
+
gzip on;
|
19
|
+
gzip_disable "MSIE [1-6]\.(?!.*SV1)";
|
20
|
+
|
21
|
+
include /tmp/glowworm-nginx.conf;
|
22
|
+
}
|
23
|
+
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
require "rubygems"
|
2
|
+
require "bundler"
|
3
|
+
Bundler.require(:default, :development)
|
4
|
+
require "minitest/autorun"
|
5
|
+
require "scope"
|
6
|
+
|
7
|
+
# use minitap if TAP output is requested
|
8
|
+
if ENV["TAP_OUTPUT"] == "true"
|
9
|
+
require "minitap"
|
10
|
+
end
|
11
|
+
|
12
|
+
# For testing Glowworm itself, use the local version *first*.
|
13
|
+
$LOAD_PATH.unshift File.join(File.dirname(__FILE__), "..", "lib")
|
14
|
+
|
15
|
+
require "glowworm"
|
16
|
+
require "ecology/test_methods"
|
17
|
+
|
18
|
+
class Scope::TestCase
|
19
|
+
include Ecology::Test
|
20
|
+
|
21
|
+
def mock_http_response(body, options = {})
|
22
|
+
options[:status] ||= 200
|
23
|
+
options[:server] ||= "http://fake-server.com"
|
24
|
+
options[:account] ||= "12345"
|
25
|
+
options[:feature] ||= "foo_feature"
|
26
|
+
|
27
|
+
response = mock "HTTP response"
|
28
|
+
response.expects(:code).returns(options[:status]).at_least_once
|
29
|
+
response.expects(:to_str).returns(body).at_least_once
|
30
|
+
|
31
|
+
expectation = RestClient.expects(:get).
|
32
|
+
with("#{options[:server]}/account/#{options[:account]}/#{options[:feature]}")
|
33
|
+
|
34
|
+
if options[:raise]
|
35
|
+
expectation.raises(RuntimeError)
|
36
|
+
else
|
37
|
+
expectation.returns(response).yields(response)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
|
43
|
+
# TODO(corey): This should output to a test specific tap file.
|
44
|
+
if ENV["TAP_OUTPUT"] == "true"
|
45
|
+
MiniTest::Unit.runner = MiniTest::TapY.new
|
46
|
+
end
|
47
|
+
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), "..", "test_helper.rb")
|
2
|
+
require "glowworm"
|
3
|
+
|
4
|
+
class EmTest < Scope::TestCase
|
5
|
+
context "with an ecology and mocked server" do
|
6
|
+
setup do
|
7
|
+
Ecology.reset
|
8
|
+
|
9
|
+
set_up_ecology <<ECOLOGY_TEXT
|
10
|
+
{
|
11
|
+
"application": "foo_app",
|
12
|
+
"features": {
|
13
|
+
"server": "http://fake-server.com",
|
14
|
+
"refresh": 300,
|
15
|
+
"timeout": 5
|
16
|
+
}
|
17
|
+
}
|
18
|
+
ECOLOGY_TEXT
|
19
|
+
|
20
|
+
# Turn off loud logging for tests
|
21
|
+
Glowworm.termite_logger = stub("fake termite logger", :debug => nil, :info => nil, :warn => nil)
|
22
|
+
|
23
|
+
# @response = mock "HTTP response"
|
24
|
+
# @response.stubs(:headers).returns({ "last-modified" => "" })
|
25
|
+
# @response.stubs(:code).returns(200)
|
26
|
+
# HTTParty.stubs(:get).returns(@response)
|
27
|
+
Glowworm.stubs(:update_cache)
|
28
|
+
end
|
29
|
+
|
30
|
+
should "call the non-em-safe version of update cache when not in Glowworm.em" do
|
31
|
+
Glowworm.expects(:_update_cache_in_foreground_)
|
32
|
+
Glowworm.no_bg
|
33
|
+
end
|
34
|
+
|
35
|
+
should "call the em-safe version of update cache when in Glowworm.em" do
|
36
|
+
EM.expects(:synchrony)
|
37
|
+
Glowworm.stubs(:require_em)
|
38
|
+
Glowworm.em
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,297 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), "..", "test_helper.rb")
|
2
|
+
require "glowworm"
|
3
|
+
|
4
|
+
class FeatureFlagTest < Scope::TestCase
|
5
|
+
context "with an ecology and mocked server" do
|
6
|
+
setup do
|
7
|
+
Ecology.reset
|
8
|
+
|
9
|
+
set_up_ecology <<ECOLOGY_TEXT
|
10
|
+
{
|
11
|
+
"application": "foo_app",
|
12
|
+
"features": {
|
13
|
+
"server": "http://fake-server.com",
|
14
|
+
"refresh": 300,
|
15
|
+
"timeout": 5
|
16
|
+
}
|
17
|
+
}
|
18
|
+
ECOLOGY_TEXT
|
19
|
+
|
20
|
+
# Turn off loud logging for tests
|
21
|
+
Glowworm.termite_logger = stub("fake termite logger", :debug => nil, :info => nil, :warn => nil)
|
22
|
+
|
23
|
+
Ecology.read
|
24
|
+
|
25
|
+
@response = mock "HTTP response"
|
26
|
+
@response.stubs(:headers).returns({ "last-modified" => "" })
|
27
|
+
end
|
28
|
+
|
29
|
+
should "have a timeout of 5" do
|
30
|
+
assert_equal(5, Glowworm.timeout, "timeout will be 5 if the ecology is loaded correctly.")
|
31
|
+
end
|
32
|
+
|
33
|
+
should "return default with no connection and for weird query values" do
|
34
|
+
@response.expects(:code).returns(500).at_least_once
|
35
|
+
HTTParty.expects(:get).returns(@response)
|
36
|
+
assert_equal false, Glowworm.feature_flag("", "")
|
37
|
+
assert_equal false, Glowworm.feature_flag(12345, "foo_feature")
|
38
|
+
|
39
|
+
# Check long values
|
40
|
+
assert_equal false, Glowworm.feature_flag("*" * 10_000, "*" * 10_000)
|
41
|
+
|
42
|
+
# Check values that don't work
|
43
|
+
assert_raises RuntimeError do
|
44
|
+
assert_equal false, Glowworm.feature_flag("", nil)
|
45
|
+
end
|
46
|
+
assert_raises RuntimeError do
|
47
|
+
assert_equal false, Glowworm.feature_flag(nil, "")
|
48
|
+
end
|
49
|
+
assert_raises RuntimeError do
|
50
|
+
assert_equal false, Glowworm.feature_flag(nil, nil)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
should "call HTTP GET on the first query to feature_flag" do
|
55
|
+
@response.expects(:code).returns(200).at_least_once
|
56
|
+
@response.expects(:body).returns(<<JSON).at_least_once
|
57
|
+
{
|
58
|
+
"version": 1,
|
59
|
+
"account_sets": { "12345": [ "1" ] },
|
60
|
+
"features" : {
|
61
|
+
"foo_feature": { "1" : 1 }
|
62
|
+
}
|
63
|
+
}
|
64
|
+
JSON
|
65
|
+
HTTParty.expects(:get).with("http://fake-server.com/all_if_modified", anything).
|
66
|
+
returns(@response)
|
67
|
+
Glowworm.feature_flag(12345, "foo_feature")
|
68
|
+
end
|
69
|
+
|
70
|
+
should "return true when feature is set for account_set" do
|
71
|
+
@response.expects(:code).returns(200).at_least_once
|
72
|
+
@response.expects(:body).returns(<<JSON).at_least_once
|
73
|
+
{
|
74
|
+
"version": 1,
|
75
|
+
"account_sets": { "12345": [ "1" ] },
|
76
|
+
"features" : {
|
77
|
+
"foo_feature": { "1" : 1 }
|
78
|
+
}
|
79
|
+
}
|
80
|
+
JSON
|
81
|
+
HTTParty.expects(:get).with("http://fake-server.com/all_if_modified", anything).
|
82
|
+
returns(@response)
|
83
|
+
assert_equal true, Glowworm.feature_flag(12345, "foo_feature")
|
84
|
+
end
|
85
|
+
|
86
|
+
should "return true if there is only an override" do
|
87
|
+
@response.expects(:code).returns(200).at_least_once
|
88
|
+
@response.stubs(:body).returns(<<JSON).at_least_once
|
89
|
+
{
|
90
|
+
"version": 1,
|
91
|
+
"features" : {
|
92
|
+
"foo_feature": { "1" : 1 }
|
93
|
+
},
|
94
|
+
"overrides" : {
|
95
|
+
"foo_feature": { "12345" : 1 }
|
96
|
+
}
|
97
|
+
}
|
98
|
+
JSON
|
99
|
+
HTTParty.expects(:get).with("http://fake-server.com/all_if_modified", anything).
|
100
|
+
returns(@response)
|
101
|
+
assert_equal true, Glowworm.feature_flag(12345, "foo_feature")
|
102
|
+
end
|
103
|
+
|
104
|
+
should "return false for otherwise-active feature if there is an override" do
|
105
|
+
@response.expects(:code).returns(200).at_least_once
|
106
|
+
@response.stubs(:body).returns(<<JSON).at_least_once
|
107
|
+
{
|
108
|
+
"version": 1,
|
109
|
+
"account_sets": { "12345": [ "1" ] },
|
110
|
+
"features" : {
|
111
|
+
"foo_feature": { "1" : 1 }
|
112
|
+
},
|
113
|
+
"overrides" : {
|
114
|
+
"foo_feature": { "12345" : 0 }
|
115
|
+
}
|
116
|
+
}
|
117
|
+
JSON
|
118
|
+
HTTParty.expects(:get).with("http://fake-server.com/all_if_modified", anything).
|
119
|
+
returns(@response)
|
120
|
+
assert_equal false, Glowworm.feature_flag(12345, "foo_feature", :default => true)
|
121
|
+
end
|
122
|
+
|
123
|
+
should "return false if both override and account_set say so" do
|
124
|
+
@response.expects(:code).returns(200).at_least_once
|
125
|
+
@response.stubs(:body).returns(<<JSON).at_least_once
|
126
|
+
{
|
127
|
+
"version": 1,
|
128
|
+
"overrides" : {
|
129
|
+
"foo_feature": { "12345" : 0 }
|
130
|
+
}
|
131
|
+
}
|
132
|
+
JSON
|
133
|
+
HTTParty.expects(:get).with("http://fake-server.com/all_if_modified", anything).
|
134
|
+
returns(@response)
|
135
|
+
assert_equal false, Glowworm.feature_flag(12345, "foo_feature", :default => true)
|
136
|
+
end
|
137
|
+
|
138
|
+
should "return true if both override and account_set say so" do
|
139
|
+
@response.expects(:code).returns(200).at_least_once
|
140
|
+
@response.stubs(:body).returns(<<JSON).at_least_once
|
141
|
+
{
|
142
|
+
"version": 1,
|
143
|
+
"account_sets": { "12345": [ "1" ] },
|
144
|
+
"features" : {
|
145
|
+
"foo_feature": { "1" : 1 }
|
146
|
+
},
|
147
|
+
"overrides" : {
|
148
|
+
"foo_feature": { "12345" : 1 }
|
149
|
+
}
|
150
|
+
}
|
151
|
+
JSON
|
152
|
+
HTTParty.expects(:get).with("http://fake-server.com/all_if_modified", anything).
|
153
|
+
returns(@response)
|
154
|
+
assert_equal true, Glowworm.feature_flag(12345, "foo_feature")
|
155
|
+
end
|
156
|
+
|
157
|
+
should "return a value if option is get_value" do
|
158
|
+
@response.expects(:code).returns(200).at_least_once
|
159
|
+
@response.stubs(:body).returns(<<JSON).at_least_once
|
160
|
+
{
|
161
|
+
"version": 1,
|
162
|
+
"account_sets": { "12345": [ "1" ] },
|
163
|
+
"features" : {
|
164
|
+
"foo_feature": { "1" : 2 }
|
165
|
+
}
|
166
|
+
}
|
167
|
+
JSON
|
168
|
+
HTTParty.expects(:get).with("http://fake-server.com/all_if_modified", anything).
|
169
|
+
returns(@response)
|
170
|
+
assert_equal 2, Glowworm.feature_value(12345, "foo_feature", :get_value => true)
|
171
|
+
end
|
172
|
+
|
173
|
+
should "call HTTP GET again if TTL is 0" do
|
174
|
+
@response.expects(:code).returns(200).at_least_once
|
175
|
+
@response.expects(:body).returns(<<JSON).at_least_once
|
176
|
+
{
|
177
|
+
"version": 1,
|
178
|
+
"account_sets": { "12345": [ "1" ] },
|
179
|
+
"features" : {
|
180
|
+
"foo_feature": { "1" : 1 }
|
181
|
+
}
|
182
|
+
}
|
183
|
+
JSON
|
184
|
+
HTTParty.expects(:get).times(5).with("http://fake-server.com/all_if_modified", anything).
|
185
|
+
returns(@response)
|
186
|
+
Glowworm.feature_flag(12345, "foo_feature")
|
187
|
+
Glowworm.feature_flag(12345, "foo_feature", :ttl => 0.0)
|
188
|
+
Glowworm.feature_flag(12345, "foo_feature", :ttl => 0.0)
|
189
|
+
Glowworm.feature_flag(12345, "foo_feature", :ttl => 0.0)
|
190
|
+
Glowworm.feature_flag(12345, "foo_feature", :ttl => 0.0)
|
191
|
+
end
|
192
|
+
|
193
|
+
should "call HTTP GET again if TTL is less than 0" do
|
194
|
+
@response.expects(:code).returns(200).at_least_once
|
195
|
+
@response.expects(:body).returns(<<JSON).at_least_once
|
196
|
+
{
|
197
|
+
"version": 1,
|
198
|
+
"account_sets": { "12345": [ "1" ] },
|
199
|
+
"features" : {
|
200
|
+
"foo_feature": { "1" : 1 }
|
201
|
+
}
|
202
|
+
}
|
203
|
+
JSON
|
204
|
+
HTTParty.expects(:get).times(5).with("http://fake-server.com/all_if_modified", anything).
|
205
|
+
returns(@response)
|
206
|
+
Glowworm.feature_flag(12345, "foo_feature")
|
207
|
+
Glowworm.feature_flag(12345, "foo_feature", :ttl => -10.0)
|
208
|
+
Glowworm.feature_flag(12345, "foo_feature", :ttl => -10.0)
|
209
|
+
Glowworm.feature_flag(12345, "foo_feature", :ttl => -10.0)
|
210
|
+
Glowworm.feature_flag(12345, "foo_feature", :ttl => -10.0)
|
211
|
+
end
|
212
|
+
|
213
|
+
should "not call HTTP GET again if TTL is very high" do
|
214
|
+
@response.expects(:code).returns(200).at_least_once
|
215
|
+
@response.expects(:body).returns(<<JSON).at_least_once
|
216
|
+
{
|
217
|
+
"version": 1,
|
218
|
+
"account_sets": { "12345": [ "1" ] },
|
219
|
+
"features" : {
|
220
|
+
"foo_feature": { "1" : 1 }
|
221
|
+
}
|
222
|
+
}
|
223
|
+
JSON
|
224
|
+
HTTParty.expects(:get).once.with("http://fake-server.com/all_if_modified", anything).
|
225
|
+
returns(@response)
|
226
|
+
Glowworm.feature_flag(12345, "foo_feature")
|
227
|
+
Glowworm.feature_flag(12345, "foo_feature", :ttl => 10_000)
|
228
|
+
Glowworm.feature_flag(12345, "foo_feature", :ttl => 10_000)
|
229
|
+
Glowworm.feature_flag(12345, "foo_feature", :ttl => 10_000)
|
230
|
+
Glowworm.feature_flag(12345, "foo_feature", :ttl => 10_000)
|
231
|
+
Glowworm.feature_flag(12345, "foo_feature", :ttl => 10_000)
|
232
|
+
Glowworm.feature_flag(12345, "foo_feature", :ttl => 10_000)
|
233
|
+
end
|
234
|
+
|
235
|
+
should "call HTTP GET again after TTL time is up" do
|
236
|
+
@response.expects(:code).returns(200).at_least_once
|
237
|
+
@response.expects(:body).returns(<<JSON).at_least_once
|
238
|
+
{
|
239
|
+
"version": 1,
|
240
|
+
"account_sets": { "12345": [ "1" ] },
|
241
|
+
"features" : {
|
242
|
+
"foo_feature": { "1" : 1 }
|
243
|
+
}
|
244
|
+
}
|
245
|
+
JSON
|
246
|
+
HTTParty.expects(:get).twice.with("http://fake-server.com/all_if_modified", anything).
|
247
|
+
returns(@response)
|
248
|
+
initial_time = Time.now
|
249
|
+
Glowworm.feature_flag(12345, "foo_feature")
|
250
|
+
|
251
|
+
# Stub Time.now to return the first time plus ten seconds
|
252
|
+
Time.expects(:now).at_least_once.returns(initial_time + 10.0)
|
253
|
+
|
254
|
+
# Then make sure we do another HTTP get afterward
|
255
|
+
Glowworm.feature_flag(12345, "foo_feature", :ttl => 8.0)
|
256
|
+
end
|
257
|
+
|
258
|
+
should "not wait for HTTP request with timeout 0" do
|
259
|
+
# May or may not get called, depending on timing
|
260
|
+
@response.stubs(:code).returns(500)
|
261
|
+
HTTParty.stubs(:get).returns(@response)
|
262
|
+
|
263
|
+
# Expect and then simulate a 0-second join
|
264
|
+
Thread.any_instance.expects(:join).with(0.0).returns(false)
|
265
|
+
|
266
|
+
Glowworm.feature_flag(12345, "foo_feature", :timeout => 0.0)
|
267
|
+
end
|
268
|
+
|
269
|
+
=begin
|
270
|
+
should "update cached value later for HTTP request with timeout 1.0" do
|
271
|
+
@response.expects(:code).returns(200).at_least_once
|
272
|
+
@response.expects(:body).returns(<<JSON).at_least_once
|
273
|
+
{
|
274
|
+
"version": 1,
|
275
|
+
"account_sets": { "12345": [ "1" ] },
|
276
|
+
"features" : {
|
277
|
+
"foo_feature": { "1" : 1 }
|
278
|
+
}
|
279
|
+
}
|
280
|
+
JSON
|
281
|
+
HTTParty.expects(:get).with("http://fake-server.com/all_if_modified").
|
282
|
+
returns(@response)
|
283
|
+
|
284
|
+
# Expect and then simulate a 1-second join that timed out
|
285
|
+
Thread.any_instance.expects(:join).with(1.0).returns(false)
|
286
|
+
|
287
|
+
# But require that the cache update still get called
|
288
|
+
Glowworm.expects(:refresh_cache_from_response)
|
289
|
+
|
290
|
+
Glowworm.feature_flag(12345, "foo_feature", :timeout => 1.0)
|
291
|
+
|
292
|
+
# Give the background thread time to rejoin
|
293
|
+
sleep 0.25
|
294
|
+
end
|
295
|
+
=end
|
296
|
+
end
|
297
|
+
end
|