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.
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