vanity 1.8.4 → 1.9.0.beta

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