glowworm 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (55) hide show
  1. checksums.yaml +15 -0
  2. data/.gitignore +6 -0
  3. data/.yardopts +1 -0
  4. data/Gemfile +5 -0
  5. data/Gemfile.lock +129 -0
  6. data/LICENSE +19 -0
  7. data/README.md +326 -0
  8. data/Rakefile +29 -0
  9. data/bin/basic_server_tester +311 -0
  10. data/bin/em_server/config.ru +2 -0
  11. data/bin/em_server/em_server.rb +19 -0
  12. data/bin/em_server_tester +68 -0
  13. data/bin/glowworm +90 -0
  14. data/bin/load_tester +84 -0
  15. data/ci_jobs/glowworm-continuous-deploy-next-staging/run.sh +10 -0
  16. data/ci_jobs/glowworm-integrations/run.sh +15 -0
  17. data/ci_jobs/glowworm-performance/run.sh +2 -0
  18. data/ci_jobs/glowworm-robustness/run.sh +2 -0
  19. data/ci_jobs/glowworm-units/run.sh +13 -0
  20. data/ci_jobs/setup.sh +119 -0
  21. data/example/example_server.ecology +6 -0
  22. data/example/example_server.rb +32 -0
  23. data/glowworm.gemspec +54 -0
  24. data/lib/glowworm.rb +501 -0
  25. data/lib/glowworm/em.rb +8 -0
  26. data/lib/glowworm/no_bg.rb +2 -0
  27. data/lib/glowworm/version.rb +3 -0
  28. data/server/Gemfile +27 -0
  29. data/server/Gemfile.lock +87 -0
  30. data/server/PROTOCOL +39 -0
  31. data/server/check_mk_checks/check_glowworm_server +43 -0
  32. data/server/db_migrations/20111004214649_change_feature_accounts_to_string.rb +60 -0
  33. data/server/db_migrations/20111028104546_add_value_to_account_set_features.rb +12 -0
  34. data/server/db_migrations/20120217090636_add_fully_active_flag_to_features.rb +15 -0
  35. data/server/example_test_data.rb +66 -0
  36. data/server/glowworm_server.ecology.erb +16 -0
  37. data/server/glowworm_server.rb +226 -0
  38. data/server/run/server.sh +7 -0
  39. data/server/server_test.rb +72 -0
  40. data/server/version.rb +3 -0
  41. data/test/integration/basic_server_test.rb +90 -0
  42. data/test/integration/create_sqlite_data.rb +196 -0
  43. data/test/integration/em_server_test.rb +68 -0
  44. data/test/integration/gemfile_for_specific_glowworm_version +17 -0
  45. data/test/integration/gemfile_for_specific_glowworm_version.lock +55 -0
  46. data/test/integration/integration_test_helper.rb +153 -0
  47. data/test/integration/load_test.rb +59 -0
  48. data/test/integration/nginx.conf +23 -0
  49. data/test/integration/server_test.ecology.erb +6 -0
  50. data/test/test_helper.rb +47 -0
  51. data/test/units/em_test.rb +41 -0
  52. data/test/units/feature_flag_test.rb +297 -0
  53. data/test/units/no_bg_test.rb +40 -0
  54. data/test/units/request_test.rb +51 -0
  55. 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
+
@@ -0,0 +1,6 @@
1
+ {
2
+ "application": "glowworm server, test version",
3
+ "db": { "adapter": "sqlite", "database": "<%= ENV["GLOWWORM_ROOT"] || "." %>/test_data.sqlite" },
4
+ "port": <%= ENV["GLOWWORM_SERVER_PORT"] || 4999 %>,
5
+ "features": { "timeout": 10 }
6
+ }
@@ -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