vanity 1.8.4 → 1.9.0.beta

Sign up to get free protection for your applications and to get access to all the features.
Files changed (53) hide show
  1. data/.travis.yml +3 -2
  2. data/CHANGELOG +12 -0
  3. data/Gemfile +6 -3
  4. data/Gemfile.lock +12 -10
  5. data/README.rdoc +45 -16
  6. data/Rakefile +14 -9
  7. data/doc/_layouts/page.html +4 -6
  8. data/doc/ab_testing.textile +1 -1
  9. data/doc/configuring.textile +2 -4
  10. data/doc/email.textile +1 -3
  11. data/doc/index.textile +3 -63
  12. data/doc/rails.textile +34 -8
  13. data/gemfiles/rails3.gemfile +12 -3
  14. data/gemfiles/rails3.gemfile.lock +37 -11
  15. data/gemfiles/rails31.gemfile +12 -3
  16. data/gemfiles/rails31.gemfile.lock +37 -11
  17. data/gemfiles/rails32.gemfile +12 -3
  18. data/gemfiles/rails32.gemfile.lock +37 -11
  19. data/gemfiles/rails4.gemfile +12 -3
  20. data/gemfiles/rails4.gemfile.lock +37 -11
  21. data/lib/vanity/adapters/abstract_adapter.rb +4 -0
  22. data/lib/vanity/adapters/active_record_adapter.rb +18 -10
  23. data/lib/vanity/adapters/mock_adapter.rb +8 -4
  24. data/lib/vanity/adapters/mongodb_adapter.rb +11 -7
  25. data/lib/vanity/adapters/redis_adapter.rb +88 -37
  26. data/lib/vanity/commands/report.rb +9 -9
  27. data/lib/vanity/experiment/ab_test.rb +120 -101
  28. data/lib/vanity/experiment/alternative.rb +21 -21
  29. data/lib/vanity/experiment/base.rb +5 -5
  30. data/lib/vanity/experiment/bayesian_bandit_score.rb +51 -51
  31. data/lib/vanity/experiment/definition.rb +10 -10
  32. data/lib/vanity/frameworks/rails.rb +39 -36
  33. data/lib/vanity/helpers.rb +6 -4
  34. data/lib/vanity/metric/active_record.rb +1 -1
  35. data/lib/vanity/metric/base.rb +23 -24
  36. data/lib/vanity/metric/google_analytics.rb +5 -5
  37. data/lib/vanity/playground.rb +118 -24
  38. data/lib/vanity/templates/_report.erb +20 -6
  39. data/lib/vanity/templates/vanity.css +2 -0
  40. data/lib/vanity/version.rb +1 -1
  41. data/test/adapters/redis_adapter_test.rb +106 -1
  42. data/test/dummy/config/database.yml +21 -4
  43. data/test/dummy/config/routes.rb +1 -1
  44. data/test/experiment/ab_test.rb +93 -13
  45. data/test/metric/active_record_test.rb +9 -4
  46. data/test/passenger_test.rb +43 -42
  47. data/test/playground_test.rb +50 -1
  48. data/test/rails_dashboard_test.rb +38 -1
  49. data/test/rails_helper_test.rb +5 -0
  50. data/test/rails_test.rb +66 -15
  51. data/test/test_helper.rb +24 -2
  52. data/vanity.gemspec +0 -2
  53. metadata +45 -57
@@ -13,6 +13,28 @@ class PlaygroundTest < Test::Unit::TestCase
13
13
  assert Vanity.playground.using_js?
14
14
  end
15
15
 
16
+ def test_be_failover_on_datastore_error
17
+ assert !Vanity.playground.failover_on_datastore_error?
18
+ Vanity.playground.failover_on_datastore_error!
19
+ assert Vanity.playground.failover_on_datastore_error?
20
+ end
21
+
22
+ def test_default_failover_on_datastore_error
23
+ proc = Vanity.playground.on_datastore_error
24
+ assert proc.respond_to?(:call)
25
+ assert_nothing_raised do
26
+ proc.call(Exception.new("datastore error"), self.class, caller[0][/`.*'/][1..-2], [1, 2, 3])
27
+ end
28
+ end
29
+
30
+ def test_request_filter
31
+ proc = Vanity.playground.request_filter
32
+ assert proc.respond_to?(:call)
33
+ assert_nothing_raised do
34
+ proc.call(dummy_request)
35
+ end
36
+ end
37
+
16
38
  def test_chooses_path_sets_default
17
39
  assert_equal Vanity.playground.add_participant_path, Vanity::Playground::DEFAULT_ADD_PARTICIPANT_PATH
18
40
  end
@@ -23,17 +45,44 @@ class PlaygroundTest < Test::Unit::TestCase
23
45
  assert_equal Vanity.playground.connection.to_s, "mock:/"
24
46
  end
25
47
 
26
- def test_autoconnect_establishes_connection_by_default
48
+ def test_autoconnect_establishes_connection_by_default_with_connection
27
49
  instance = Vanity::Playground.new(:connection=>"mock:/")
28
50
  assert instance.connected?
29
51
  end
30
52
 
53
+ def test_autoconnect_establishes_connection_by_default
54
+ Vanity::Playground.any_instance.expects(:establish_connection)
55
+ Vanity::Playground.new
56
+ end
57
+
31
58
  def test_autoconnect_can_skip_connection
32
59
  Vanity::Autoconnect.stubs(:playground_should_autoconnect?).returns(false)
33
60
  instance = Vanity::Playground.new(:connection=>"mock:/")
34
61
  assert !instance.connected?
35
62
  end
36
63
 
64
+ def test_experiments_persisted_returns_true
65
+ metric "Coolness"
66
+ new_ab_test :foobar do
67
+ alternatives "foo", "bar"
68
+ identify { "abcdef" }
69
+ metrics :coolness
70
+ end
71
+
72
+ assert Vanity.playground.experiments_persisted?
73
+ end
74
+
75
+ def test_experiments_persisted_finds_returns_false
76
+ name = 'Price'
77
+ id = :price
78
+ experiment = Vanity::Experiment::AbTest.new(Vanity.playground, id, name)
79
+ Vanity.playground.experiments[id] = experiment
80
+
81
+ assert !Vanity.playground.experiments_persisted?
82
+
83
+ Vanity.playground.experiments.delete(id)
84
+ end
85
+
37
86
  def test_participant_info
38
87
  assert_equal [], Vanity.playground.participant_info("abcdef")
39
88
  metric "Coolness"
@@ -17,12 +17,49 @@ class RailsDashboardTest < ActionController::TestCase
17
17
  end
18
18
  end
19
19
 
20
- # -- Actions accessible to everyone, e.g. sign in, community search --
20
+ # -- Test dashboard --
21
+
22
+ def test_index
23
+ get :index
24
+ assert_response :success
25
+ assert @response.body =~ %r{div class="vanity"}
26
+ end
27
+
28
+ def test_assigns_experiments
29
+ get :index
30
+ experiments = assigns(:experiments).with_indifferent_access
31
+
32
+ assert experiments.respond_to?(:keys)
33
+ assert experiments.keys.include?("food")
34
+ assert experiments.values.first.name == :food
35
+ end
36
+
37
+ def test_assigns_metrics
38
+ get :index
39
+ metrics = assigns(:metrics).with_indifferent_access
40
+ assert metrics.respond_to?(:keys)
41
+ assert metrics.keys.include?("sugar_high")
42
+ assert metrics.values.first.name == "sugar_high"
43
+ end
44
+
45
+ def test_assigns_experiments_persisted
46
+ get :index
47
+ assert assigns(:experiments_persisted)
48
+ end
49
+
50
+ # -- Actions used in non-admin actions --
21
51
 
22
52
  def test_add_participant
23
53
  xhr :post, :add_participant, :e => "food", :a => 0
24
54
  assert_response :success
25
55
  assert @response.body.blank?
56
+ assert_equal 1, experiment(:food).alternatives.map(&:participants).sum
57
+ end
58
+
59
+ def test_add_participant_with_invalid_request
60
+ @request.user_agent = "Googlebot/2.1 ( http://www.google.com/bot.html)"
61
+ xhr :post, :add_participant, :e => "food", :a => 0
62
+ assert_equal 0, experiment(:food).alternatives.map(&:participants).sum
26
63
  end
27
64
 
28
65
  def test_add_participant_no_params
@@ -26,6 +26,11 @@ class RailsHelperTest < ActionView::TestCase
26
26
  end
27
27
  end
28
28
 
29
+ def test_ab_test_adds_participant_to_experiment
30
+ ab_test(:pie_or_cake)
31
+ assert_equal 1, experiment(:pie_or_cake).alternatives.map(&:participants).sum
32
+ end
33
+
29
34
  def test_vanity_track_url_for_returns_url_with_identity_and_metrics
30
35
  self.expects(:url_for).with(:controller => "controller", :action => "action", :_identity => '123', :_track => :sugar_high)
31
36
  vanity_track_url_for("123", :sugar_high, :controller => "controller", :action => "action")
data/test/rails_test.rb CHANGED
@@ -47,6 +47,16 @@ class UseVanityControllerTest < ActionController::TestCase
47
47
  assert_equal 'false', @response.body
48
48
  end
49
49
 
50
+ def test_adds_participant_to_experiment
51
+ get :index
52
+ assert_equal 1, experiment(:pie_or_cake).alternatives.map(&:participants).sum
53
+ end
54
+
55
+ def test_does_not_add_invalid_participant_to_experiment
56
+ @request.user_agent = "Googlebot/2.1 ( http://www.google.com/bot.html)"
57
+ get :index
58
+ assert_equal 0, experiment(:pie_or_cake).alternatives.map(&:participants).sum
59
+ end
50
60
 
51
61
  def test_vanity_cookie_is_persistent
52
62
  get :index
@@ -59,17 +69,19 @@ class UseVanityControllerTest < ActionController::TestCase
59
69
 
60
70
  def test_vanity_cookie_default_id
61
71
  get :index
62
- assert cookies['vanity_id'] =~ /^[a-f0-9]{32}$/
72
+ assert cookies["vanity_id"] =~ /^[a-f0-9]{32}$/
63
73
  end
64
74
 
65
75
  def test_vanity_cookie_retains_id
66
- @request.cookies['vanity_id'] = "from_last_time"
76
+ @request.cookies["vanity_id"] = "from_last_time"
67
77
  get :index
68
- assert_equal "from_last_time", cookies['vanity_id']
78
+ # Rails 2 funkieness: if the cookie isn't explicitly set in the response,
79
+ # cookies[] is empty. Just make sure it's not re-set.
80
+ assert_equal rails3? ? "from_last_time" : nil, cookies["vanity_id"]
69
81
  end
70
82
 
71
83
  def test_vanity_identity_set_from_cookie
72
- @request.cookies['vanity_id'] = "from_last_time"
84
+ @request.cookies["vanity_id"] = "from_last_time"
73
85
  get :index
74
86
  assert_equal "from_last_time", @controller.send(:vanity_identity)
75
87
  end
@@ -86,7 +98,7 @@ class UseVanityControllerTest < ActionController::TestCase
86
98
  end
87
99
  @controller.current_user = Object.new
88
100
  get :index
89
- assert cookies['vanity_id'] =~ /^[a-f0-9]{32}$/
101
+ assert cookies["vanity_id"] =~ /^[a-f0-9]{32}$/
90
102
  end
91
103
 
92
104
  def test_vanity_identity_set_with_block
@@ -102,13 +114,34 @@ class UseVanityControllerTest < ActionController::TestCase
102
114
  def test_vanity_identity_set_with_indentity_paramater
103
115
  get :index, :_identity => "id_from_params"
104
116
  assert_equal "id_from_params", @controller.send(:vanity_identity)
117
+ end
105
118
 
119
+ def test_vanity_identity_prefers_block_over_symbol
120
+ UseVanityController.class_eval do
121
+ attr_accessor :project_id
122
+ use_vanity(:current_user) { |controller| controller.project_id }
123
+ end
124
+ @controller.project_id = "576"
125
+ @controller.current_user = stub(:id=>"user_id")
126
+
127
+ get :index
128
+ assert_equal "576", @controller.send(:vanity_identity)
129
+ end
130
+
131
+ def test_vanity_identity_prefers_parameter_over_cookie
106
132
  @request.cookies['vanity_id'] = "old_id"
107
133
  get :index, :_identity => "id_from_params"
108
134
  assert_equal "id_from_params", @controller.send(:vanity_identity)
109
135
  assert cookies['vanity_id'], "id_from_params"
110
136
  end
111
137
 
138
+ def test_vanity_identity_prefers_cookie_over_object
139
+ @request.cookies['vanity_id'] = "from_last_time"
140
+ @controller.current_user = stub(:id=>"user_id")
141
+ get :index
142
+ assert_equal "from_last_time", @controller.send(:vanity_identity)
143
+ end
144
+
112
145
  # query parameter filter
113
146
 
114
147
  def test_redirects_and_loses_vanity_query_parameter
@@ -234,9 +267,11 @@ $stdout << Vanity.playground.load_path
234
267
  end
235
268
 
236
269
  def test_absolute_load_path
237
- assert_equal File.expand_path("/tmp/var"), load_rails(%Q{\nVanity.playground.load_path = "/tmp/var"\n}, <<-RB)
270
+ Dir.mktmpdir do |dir|
271
+ assert_equal dir, load_rails(%Q{\nVanity.playground.load_path = "#{dir}"\n}, <<-RB)
238
272
  $stdout << Vanity.playground.load_path
239
- RB
273
+ RB
274
+ end
240
275
  end
241
276
 
242
277
  if ENV['DB'] == 'redis'
@@ -321,8 +356,6 @@ $stdout << Vanity.playground.connection
321
356
  ensure
322
357
  File.unlink yml.path
323
358
  end
324
-
325
-
326
359
  end
327
360
 
328
361
  if ENV['DB'] == 'mongo'
@@ -345,7 +378,7 @@ $stdout << Vanity.playground.connection
345
378
  File.unlink "tmp/config/vanity.yml"
346
379
  end
347
380
 
348
- unless ENV['CI'] == 'true'
381
+ unless ENV['CI'] == 'true' #TODO this doesn't get tested on CI
349
382
  def test_mongodb_replica_set_connection
350
383
  FileUtils.mkpath "tmp/config"
351
384
  File.open("tmp/config/vanity.yml", "w") do |io|
@@ -380,9 +413,12 @@ production:
380
413
  adapter: redis
381
414
  YML
382
415
  end
383
- assert_equal "No configuration for development", load_rails("", <<-RB, "development")
384
- $stdout << (Vanity.playground.connection rescue $!.message)
385
- RB
416
+
417
+ assert_equal "No configuration for development", load_rails("\nbegin\n", <<-RB, "development")
418
+ rescue RuntimeError => e
419
+ $stdout << e.message
420
+ end
421
+ RB
386
422
  ensure
387
423
  File.unlink "tmp/config/vanity.yml"
388
424
  end
@@ -393,6 +429,7 @@ $stdout << (Vanity.playground.connection rescue $!.message)
393
429
  io.write <<-YML
394
430
  production:
395
431
  collecting: false
432
+ adapter: mock
396
433
  YML
397
434
  end
398
435
  assert_equal "false", load_rails("", <<-RB)
@@ -414,8 +451,8 @@ $stdout << Vanity.playground.collecting?
414
451
  RB
415
452
  end
416
453
 
417
- def test_collection_false_in_development_by_default
418
- assert_equal "false", load_rails("", <<-RB, "development")
454
+ def test_collection_true_in_development_by_default
455
+ assert_equal "true", load_rails("", <<-RB, "development")
419
456
  $stdout << Vanity.playground.collecting?
420
457
  RB
421
458
  end
@@ -433,6 +470,20 @@ $stdout << Vanity.playground.collecting?
433
470
  RB
434
471
  end
435
472
 
473
+ def test_playground_loads_if_connected
474
+ assert_equal "{}", load_rails("", <<-RB)
475
+ $stdout << Vanity.playground.instance_variable_get(:@experiments).inspect
476
+ RB
477
+ end
478
+
479
+ def test_playground_does_not_load_if_not_connected
480
+ ENV['VANITY_DISABLED'] = '1'
481
+ assert_equal "nil", load_rails("", <<-RB)
482
+ $stdout << Vanity.playground.instance_variable_get(:@experiments).inspect
483
+ RB
484
+ ENV['VANITY_DISABLED'] = nil
485
+ end
486
+
436
487
  def load_rails(before_initialize, after_initialize, env="production")
437
488
  tmp = Tempfile.open("test.rb")
438
489
  begin
data/test/test_helper.rb CHANGED
@@ -5,6 +5,7 @@ ENV["RACK_ENV"] = "test"
5
5
  ENV["DB"] ||= "redis"
6
6
 
7
7
  require "test/unit"
8
+ require "tmpdir"
8
9
  require "mocha"
9
10
  require "action_controller"
10
11
  require "action_controller/test_case"
@@ -43,6 +44,9 @@ Vanity::Rails.load!
43
44
  if $VERBOSE
44
45
  $logger = Logger.new(STDOUT)
45
46
  $logger.level = Logger::DEBUG
47
+ else
48
+ $logger = Logger.new(STDOUT)
49
+ $logger.level = Logger::FATAL
46
50
  end
47
51
 
48
52
  module VanityTestHelpers
@@ -87,7 +91,7 @@ module VanityTestHelpers
87
91
  Vanity.playground.establish_connection DATABASE
88
92
  end
89
93
 
90
- # Defines the specified metrics (one or more names). Returns metric, or array
94
+ # Defines the specified metrics (one or more names). Returns metric, or array
91
95
  # of metric (if more than one argument).
92
96
  def metric(*names)
93
97
  metrics = names.map do |name|
@@ -120,6 +124,10 @@ module VanityTestHelpers
120
124
  Vanity.playground.stubs(:connection).returns(stub(:flushdb=>nil))
121
125
  end
122
126
 
127
+ def dummy_request
128
+ defined?(ActionDispatch) ? ActionDispatch::TestRequest.new() : ActionController::TestRequest.new()
129
+ end
130
+
123
131
  # Defining setup/tear down in a module and including it below doesn't
124
132
  # override the built-in setup/teardown methods, so we alias_method_chain
125
133
  # them to run.
@@ -146,9 +154,23 @@ if defined?(ActiveSupport::TestCase)
146
154
  end
147
155
  end
148
156
 
157
+ if defined?(ActionController::TestCase)
158
+ module ActionController
159
+ class TestCase
160
+ alias :setup_controller_request_and_response_without_vanity :setup_controller_request_and_response
161
+ # Sets Vanity.context to the current controller, so you can do things like:
162
+ # experiment(:simple).chooses(:green)
163
+ def setup_controller_request_and_response
164
+ setup_controller_request_and_response_without_vanity
165
+ Vanity.context = @controller
166
+ end
167
+ end
168
+ end
169
+ end
170
+
149
171
  if ENV["DB"] == "postgres"
150
172
  ActiveRecord::Base.establish_connection :adapter=>"postgresql", :database=>"vanity_test"
151
- else
173
+ elsif ENV["DB"] == "mysql"
152
174
  ActiveRecord::Base.establish_connection :adapter=>"mysql", :database=>"vanity_test"
153
175
  end
154
176
  ActiveRecord::Base.logger = $logger
data/vanity.gemspec CHANGED
@@ -22,6 +22,4 @@ Gem::Specification.new do |spec|
22
22
  "--webcvs", "http://github.com/assaf/#{spec.name}"
23
23
 
24
24
  spec.required_ruby_version = ">= 1.8.7"
25
- spec.add_dependency "redis", ">= 2.1"
26
- spec.add_dependency "redis-namespace", ">= 1.1.0"
27
25
  end
metadata CHANGED
@@ -1,57 +1,34 @@
1
- --- !ruby/object:Gem::Specification
1
+ --- !ruby/object:Gem::Specification
2
2
  name: vanity
3
- version: !ruby/object:Gem::Version
4
- version: 1.8.4
5
- prerelease:
3
+ version: !ruby/object:Gem::Version
4
+ hash: 1752004685
5
+ prerelease: 6
6
+ segments:
7
+ - 1
8
+ - 9
9
+ - 0
10
+ - beta
11
+ version: 1.9.0.beta
6
12
  platform: ruby
7
- authors:
13
+ authors:
8
14
  - Assaf Arkin
9
15
  autorequire:
10
16
  bindir: bin
11
17
  cert_chain: []
12
- date: 2015-06-27 00:00:00.000000000 Z
13
- dependencies:
14
- - !ruby/object:Gem::Dependency
15
- name: redis
16
- requirement: !ruby/object:Gem::Requirement
17
- none: false
18
- requirements:
19
- - - ! '>='
20
- - !ruby/object:Gem::Version
21
- version: '2.1'
22
- type: :runtime
23
- prerelease: false
24
- version_requirements: !ruby/object:Gem::Requirement
25
- none: false
26
- requirements:
27
- - - ! '>='
28
- - !ruby/object:Gem::Version
29
- version: '2.1'
30
- - !ruby/object:Gem::Dependency
31
- name: redis-namespace
32
- requirement: !ruby/object:Gem::Requirement
33
- none: false
34
- requirements:
35
- - - ! '>='
36
- - !ruby/object:Gem::Version
37
- version: 1.1.0
38
- type: :runtime
39
- prerelease: false
40
- version_requirements: !ruby/object:Gem::Requirement
41
- none: false
42
- requirements:
43
- - - ! '>='
44
- - !ruby/object:Gem::Version
45
- version: 1.1.0
18
+
19
+ date: 2014-01-18 00:00:00 Z
20
+ dependencies: []
21
+
46
22
  description: Mirror, mirror on the wall ...
47
23
  email: assaf@labnotes.org
48
- executables:
24
+ executables:
49
25
  - vanity
50
26
  extensions: []
51
- extra_rdoc_files:
27
+
28
+ extra_rdoc_files:
52
29
  - README.rdoc
53
30
  - CHANGELOG
54
- files:
31
+ files:
55
32
  - .autotest
56
33
  - .gitignore
57
34
  - .travis.yml
@@ -191,37 +168,48 @@ files:
191
168
  - test/test_helper.rb
192
169
  - vanity.gemspec
193
170
  homepage: http://vanity.labnotes.org
194
- licenses:
171
+ licenses:
195
172
  - MIT
196
173
  post_install_message: To get started run vanity --help
197
- rdoc_options:
174
+ rdoc_options:
198
175
  - --title
199
- - Vanity 1.8.4
176
+ - Vanity 1.9.0.beta
200
177
  - --main
201
178
  - README.rdoc
202
179
  - --webcvs
203
180
  - http://github.com/assaf/vanity
204
- require_paths:
181
+ require_paths:
205
182
  - lib
206
- required_ruby_version: !ruby/object:Gem::Requirement
183
+ required_ruby_version: !ruby/object:Gem::Requirement
207
184
  none: false
208
- requirements:
209
- - - ! '>='
210
- - !ruby/object:Gem::Version
185
+ requirements:
186
+ - - ">="
187
+ - !ruby/object:Gem::Version
188
+ hash: 57
189
+ segments:
190
+ - 1
191
+ - 8
192
+ - 7
211
193
  version: 1.8.7
212
- required_rubygems_version: !ruby/object:Gem::Requirement
194
+ required_rubygems_version: !ruby/object:Gem::Requirement
213
195
  none: false
214
- requirements:
215
- - - ! '>='
216
- - !ruby/object:Gem::Version
217
- version: '0'
196
+ requirements:
197
+ - - ">"
198
+ - !ruby/object:Gem::Version
199
+ hash: 25
200
+ segments:
201
+ - 1
202
+ - 3
203
+ - 1
204
+ version: 1.3.1
218
205
  requirements: []
206
+
219
207
  rubyforge_project:
220
- rubygems_version: 1.8.23
208
+ rubygems_version: 1.8.25
221
209
  signing_key:
222
210
  specification_version: 3
223
211
  summary: Experience Driven Development framework for Ruby
224
- test_files:
212
+ test_files:
225
213
  - test/adapters/redis_adapter_test.rb
226
214
  - test/autoconnect_test.rb
227
215
  - test/cli_test.rb