glowworm 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|