vanity 1.0.0 → 1.1.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 (57) hide show
  1. data/CHANGELOG +35 -0
  2. data/README.rdoc +33 -6
  3. data/lib/vanity.rb +13 -7
  4. data/lib/vanity/backport.rb +43 -0
  5. data/lib/vanity/commands/report.rb +13 -3
  6. data/lib/vanity/experiment/ab_test.rb +98 -66
  7. data/lib/vanity/experiment/base.rb +51 -5
  8. data/lib/vanity/metric.rb +213 -0
  9. data/lib/vanity/mock_redis.rb +76 -0
  10. data/lib/vanity/playground.rb +78 -61
  11. data/lib/vanity/rails/dashboard.rb +11 -2
  12. data/lib/vanity/rails/helpers.rb +3 -3
  13. data/lib/vanity/templates/_ab_test.erb +3 -4
  14. data/lib/vanity/templates/_experiment.erb +4 -4
  15. data/lib/vanity/templates/_experiments.erb +2 -2
  16. data/lib/vanity/templates/_metric.erb +9 -0
  17. data/lib/vanity/templates/_metrics.erb +13 -0
  18. data/lib/vanity/templates/_report.erb +14 -3
  19. data/lib/vanity/templates/flot.min.js +1 -0
  20. data/lib/vanity/templates/jquery.min.js +19 -0
  21. data/lib/vanity/templates/vanity.css +16 -4
  22. data/lib/vanity/templates/vanity.js +96 -0
  23. data/test/ab_test_test.rb +159 -96
  24. data/test/experiment_test.rb +99 -18
  25. data/test/experiments/age_and_zipcode.rb +1 -0
  26. data/test/experiments/metrics/cheers.rb +3 -0
  27. data/test/experiments/metrics/signups.rb +2 -0
  28. data/test/experiments/metrics/yawns.rb +3 -0
  29. data/test/experiments/null_abc.rb +1 -0
  30. data/test/metric_test.rb +287 -0
  31. data/test/playground_test.rb +1 -80
  32. data/test/rails_test.rb +9 -6
  33. data/test/test_helper.rb +37 -6
  34. data/vanity.gemspec +1 -1
  35. data/vendor/{redis-0.1 → redis-rb}/LICENSE +0 -0
  36. data/vendor/{redis-0.1 → redis-rb}/README.markdown +0 -0
  37. data/vendor/{redis-0.1 → redis-rb}/Rakefile +0 -0
  38. data/vendor/redis-rb/bench.rb +44 -0
  39. data/vendor/redis-rb/benchmarking/suite.rb +24 -0
  40. data/vendor/redis-rb/benchmarking/worker.rb +71 -0
  41. data/vendor/redis-rb/bin/distredis +33 -0
  42. data/vendor/redis-rb/examples/basic.rb +16 -0
  43. data/vendor/redis-rb/examples/incr-decr.rb +18 -0
  44. data/vendor/redis-rb/examples/list.rb +26 -0
  45. data/vendor/redis-rb/examples/sets.rb +36 -0
  46. data/vendor/{redis-0.1 → redis-rb}/lib/dist_redis.rb +0 -0
  47. data/vendor/{redis-0.1 → redis-rb}/lib/hash_ring.rb +0 -0
  48. data/vendor/{redis-0.1 → redis-rb}/lib/pipeline.rb +0 -2
  49. data/vendor/{redis-0.1 → redis-rb}/lib/redis.rb +25 -7
  50. data/vendor/{redis-0.1 → redis-rb}/lib/redis/raketasks.rb +0 -0
  51. data/vendor/redis-rb/profile.rb +22 -0
  52. data/vendor/redis-rb/redis-rb.gemspec +30 -0
  53. data/vendor/{redis-0.1 → redis-rb}/spec/redis_spec.rb +113 -0
  54. data/vendor/{redis-0.1 → redis-rb}/spec/spec_helper.rb +0 -0
  55. data/vendor/redis-rb/speed.rb +16 -0
  56. data/vendor/{redis-0.1 → redis-rb}/tasks/redis.tasks.rb +5 -1
  57. metadata +37 -14
@@ -1,41 +1,122 @@
1
1
  require "test/test_helper"
2
2
 
3
- class ExperimentTest < MiniTest::Unit::TestCase
3
+ class ExperimentTest < Test::Unit::TestCase
4
+
5
+ def setup
6
+ super
7
+ metric "Happiness"
8
+ end
9
+
10
+ # -- Defining experiment --
11
+
12
+ def test_can_access_experiment_by_id
13
+ exp = Vanity.playground.define(:ice_cream_flavor, :ab_test) { metrics :happiness }
14
+ assert_equal exp, experiment(:ice_cream_flavor)
15
+ end
16
+
17
+ def test_fail_when_defining_same_experiment_twice
18
+ Vanity.playground.define("Ice Cream Flavor", :ab_test) { metrics :happiness }
19
+ assert_raises RuntimeError do
20
+ Vanity.playground.define("Ice Cream Flavor", :ab_test) { metrics :happiness }
21
+ end
22
+ end
23
+
24
+ def test_uses_playground_namespace_for_experiment
25
+ Vanity.playground.define(:ice_cream_flavor, :ab_test) { metrics :happiness }
26
+ assert_equal "vanity:#{Vanity::Version::MAJOR}:ice_cream_flavor", experiment(:ice_cream_flavor).send(:key)
27
+ assert_equal "vanity:#{Vanity::Version::MAJOR}:ice_cream_flavor:participants", experiment(:ice_cream_flavor).send(:key, "participants")
28
+ end
29
+
30
+
31
+ # -- Loading experiments --
32
+
33
+ def test_fails_if_cannot_load_named_experiment
34
+ assert_raises NameError do
35
+ experiment(:ice_cream_flavor)
36
+ end
37
+ end
38
+
39
+ def test_loading_experiment
40
+ File.open "tmp/experiments/ice_cream_flavor.rb", "w" do |f|
41
+ f.write <<-RUBY
42
+ ab_test "Ice Cream Flavor" do
43
+ def xmts
44
+ "x"
45
+ end
46
+ metrics :happiness
47
+ end
48
+ RUBY
49
+ end
50
+ assert_equal "x", experiment(:ice_cream_flavor).xmts
51
+ end
52
+
53
+ def test_fails_if_error_loading_experiment
54
+ File.open "tmp/experiments/ice_cream_flavor.rb", "w" do |f|
55
+ f.write "fail 'yawn!'"
56
+ end
57
+ assert_raises NameError do
58
+ experiment(:ice_cream_flavor)
59
+ end
60
+ end
61
+
62
+ def test_complains_if_not_defined_where_expected
63
+ File.open "tmp/experiments/ice_cream_flavor.rb", "w" do |f|
64
+ f.write ""
65
+ end
66
+ assert_raises NameError do
67
+ experiment(:ice_cream_flavor)
68
+ end
69
+ end
70
+
71
+ def test_reloading_experiments
72
+ Vanity.playground.define(:ab, :ab_test) { metrics :happiness }
73
+ Vanity.playground.define(:cd, :ab_test) { metrics :happiness }
74
+ assert 2, Vanity.playground.experiments.size
75
+ Vanity.playground.reload!
76
+ assert Vanity.playground.experiments.empty?
77
+ end
78
+
79
+
80
+ # -- Attributes --
81
+
4
82
  def test_experiment_mapping_name_to_id
5
- experiment = Vanity.playground.define("Green Button/Alert", :ab_test) { }
6
- assert_equal "Green Button/Alert", experiment.name
7
- assert_equal :green_button_alert, experiment.id
83
+ experiment = Vanity.playground.define("Ice Cream Flavor/Tastes", :ab_test) { metrics :happiness }
84
+ assert_equal "Ice Cream Flavor/Tastes", experiment.name
85
+ assert_equal :ice_cream_flavor_tastes, experiment.id
8
86
  end
9
87
 
10
88
  def test_saving_experiment_after_definition
11
- Vanity.playground.define :simple, :ab_test do
89
+ Vanity.playground.define :ice_cream_flavor, :ab_test do
90
+ metrics :happiness
12
91
  expects(:save)
13
92
  end
14
93
  end
15
94
 
16
95
  def test_experiment_has_created_timestamp
17
- Vanity.playground.define(:simple, :ab_test) {}
18
- assert_instance_of Time, experiment(:simple).created_at
19
- assert_in_delta experiment(:simple).created_at.to_i, Time.now.to_i, 1
96
+ Vanity.playground.define(:ice_cream_flavor, :ab_test) { metrics :happiness }
97
+ assert_instance_of Time, experiment(:ice_cream_flavor).created_at
98
+ assert_in_delta experiment(:ice_cream_flavor).created_at.to_i, Time.now.to_i, 1
20
99
  end
21
100
 
22
101
  def test_experiment_keeps_created_timestamp_across_definitions
23
- early, late = Time.now - 1.day, Time.now
24
- Time.expects(:now).once.returns(early)
25
- Vanity.playground.define(:simple, :ab_test) {}
26
- assert_equal early.to_i, experiment(:simple).created_at.to_i
102
+ past = Date.today - 1
103
+ Timecop.travel past do
104
+ Vanity.playground.define(:ice_cream_flavor, :ab_test) { metrics :happiness }
105
+ assert_equal past.to_time.to_i, experiment(:ice_cream_flavor).created_at.to_i
106
+ end
27
107
 
28
108
  new_playground
29
- Time.expects(:now).once.returns(late)
30
- Vanity.playground.define(:simple, :ab_test) {}
31
- assert_equal early.to_i, experiment(:simple).created_at.to_i
109
+ metric :happiness
110
+ Vanity.playground.define(:ice_cream_flavor, :ab_test) { metrics :happiness }
111
+ assert_equal past.to_time.to_i, experiment(:ice_cream_flavor).created_at.to_i
32
112
  end
33
113
 
34
114
  def test_experiment_has_description
35
- Vanity.playground.define :simple, :ab_test do
36
- description "Simple experiment"
115
+ Vanity.playground.define :ice_cream_flavor, :ab_test do
116
+ description "Because 31 is not enough ..."
117
+ metrics :happiness
37
118
  end
38
- assert_equal "Simple experiment", experiment(:simple).description
119
+ assert_equal "Because 31 is not enough ...", experiment(:ice_cream_flavor).description
39
120
  end
40
121
 
41
122
  end
@@ -6,6 +6,7 @@ the existing form, and option B adds age and zipcode fields.
6
6
  We know option B will convert less, but higher quality leads. If we lose less
7
7
  than 20% conversions, we're going to switch to option B.
8
8
  TEXT
9
+ metrics :signups
9
10
 
10
11
  complete_if do
11
12
  alternatives.all? { |alt| alt.participants > 100 }
@@ -0,0 +1,3 @@
1
+ metric "Cheers" do
2
+ description "They love us, don't they?"
3
+ end
@@ -0,0 +1,2 @@
1
+ metric "Signups" do
2
+ end
@@ -0,0 +1,3 @@
1
+ metric "Yawns" do
2
+ description "How many yawns/sec can our video-sharing micro-blogging social network elicit?"
3
+ end
@@ -1,4 +1,5 @@
1
1
  ab_test "Null/ABC" do
2
2
  description "Testing three alternatives"
3
3
  alternatives nil, :red, :green, :blue
4
+ metrics :signups
4
5
  end
@@ -0,0 +1,287 @@
1
+ require "test/test_helper"
2
+
3
+ class MetricTest < Test::Unit::TestCase
4
+
5
+ # -- Via the playground --
6
+
7
+ def test_playground_tracks_all_loaded_metrics
8
+ metric "Yawns/sec", "Cheers/sec"
9
+ assert Vanity.playground.metrics.keys.include?(:yawns_sec)
10
+ assert Vanity.playground.metrics.keys.include?(:cheers_sec)
11
+ end
12
+
13
+ def test_playground_fails_without_metric_file
14
+ assert_raises NameError do
15
+ Vanity.playground.metric(:yawns_sec)
16
+ end
17
+ end
18
+
19
+ def test_playground_loads_metric_definition
20
+ File.open "tmp/experiments/metrics/yawns_sec.rb", "w" do |f|
21
+ f.write <<-RUBY
22
+ metric "Yawns/sec" do
23
+ def xmts
24
+ "x"
25
+ end
26
+ end
27
+ RUBY
28
+ end
29
+ assert_equal "x", Vanity.playground.metric(:yawns_sec).xmts
30
+ end
31
+
32
+ def test_metric_loading_handles_name_and_id
33
+ File.open "tmp/experiments/metrics/yawns_sec.rb", "w" do |f|
34
+ f.write <<-RUBY
35
+ metric "Yawns/sec" do
36
+ end
37
+ RUBY
38
+ end
39
+ assert metric = Vanity.playground.metric(:yawns_sec)
40
+ assert_equal "Yawns/sec", metric.name
41
+ end
42
+
43
+ def test_metric_loading_errors_bubble_up
44
+ File.open "tmp/experiments/metrics/yawns_sec.rb", "w" do |f|
45
+ f.write "fail 'yawn!'"
46
+ end
47
+ assert_raises NameError do
48
+ Vanity.playground.metric(:yawns_sec)
49
+ end
50
+ end
51
+
52
+ def test_metric_name_must_match_file_name
53
+ File.open "tmp/experiments/metrics/yawns_hour.rb", "w" do |f|
54
+ f.write <<-RUBY
55
+ metric "yawns/hour" do
56
+ end
57
+ RUBY
58
+ end
59
+ assert_raises NameError do
60
+ Vanity.playground.metric(:yawns_sec)
61
+ end
62
+ end
63
+
64
+ def test_reloading_metrics
65
+ metric "Yawns/sec", "Cheers/sec"
66
+ Vanity.playground.metric(:yawns_sec)
67
+ Vanity.playground.metric(:cheers_sec)
68
+ assert 2, Vanity.playground.metrics.size
69
+ metrics = Vanity.playground.metrics.values
70
+ Vanity.playground.reload!
71
+ assert 2, Vanity.playground.metrics.size
72
+ assert_not_equal metrics, Vanity.playground.metrics.values
73
+ end
74
+
75
+ def test_undefined_metric_in_database
76
+ metric "Yawns/sec"
77
+ Vanity.playground.reload!
78
+ assert Vanity.playground.metrics.empty?
79
+ end
80
+
81
+
82
+ # -- Tracking --
83
+
84
+ def test_tracking_can_count
85
+ metric "Yawns/sec", "Cheers/sec"
86
+ 4.times { Vanity.playground.track! :yawns_sec }
87
+ 2.times { Vanity.playground.track! :cheers_sec }
88
+ yawns = Vanity.playground.metric(:yawns_sec).values(today, today).first
89
+ cheers = Vanity.playground.metric(:cheers_sec).values(today, today).first
90
+ assert yawns = 2 * cheers
91
+ end
92
+
93
+ def test_tracking_with_value
94
+ metric "Yawns/sec", "Cheers/sec", "Looks"
95
+ Vanity.playground.track! :yawns_sec, 0
96
+ Vanity.playground.track! :cheers_sec, -1
97
+ Vanity.playground.track! :looks, 10
98
+ assert_equal 0, Vanity.playground.metric(:yawns_sec).values(today, today).sum
99
+ assert_equal 0, Vanity.playground.metric(:cheers_sec).values(today, today).sum
100
+ assert_equal 10, Vanity.playground.metric(:looks).values(today, today).sum
101
+ end
102
+
103
+ def test_tracking_can_tell_the_time
104
+ metric "Yawns/sec"
105
+ Timecop.travel(today - 4) { 4.times { Vanity.playground.track! :yawns_sec } }
106
+ Timecop.travel(today - 2) { 2.times { Vanity.playground.track! :yawns_sec } }
107
+ 1.times { Vanity.playground.track! :yawns_sec }
108
+ boredom = Vanity.playground.metric(:yawns_sec).values(today - 5, today)
109
+ assert_equal [0,4,0,2,0,1], boredom
110
+ end
111
+
112
+ def test_tracking_with_count
113
+ metric "Yawns/sec"
114
+ Timecop.travel(today - 4) { Vanity.playground.track! :yawns_sec, 4 }
115
+ Timecop.travel(today - 2) { Vanity.playground.track! :yawns_sec, 2 }
116
+ Vanity.playground.track! :yawns_sec
117
+ boredom = Vanity.playground.metric(:yawns_sec).values(today - 5, today)
118
+ assert_equal [0,4,0,2,0,1], boredom
119
+ end
120
+
121
+ def test_tracking_runs_hook
122
+ metric "Many Happy Returns"
123
+ total = 0
124
+ Vanity.playground.metric(:many_happy_returns).hook do |metric_id, timestamp, count|
125
+ assert_equal :many_happy_returns, metric_id
126
+ assert_in_delta Time.now.to_i, timestamp.to_i, 1
127
+ total += count
128
+ end
129
+ Vanity.playground.track! :many_happy_returns, 6
130
+ assert_equal 6, total
131
+ end
132
+
133
+ def test_tracking_runs_multiple_hooks
134
+ metric "Many Happy Returns"
135
+ returns = 0
136
+ Vanity.playground.metric(:many_happy_returns).hook { returns += 1 }
137
+ Vanity.playground.metric(:many_happy_returns).hook { returns += 1 }
138
+ Vanity.playground.metric(:many_happy_returns).hook { returns += 1 }
139
+ Vanity.playground.track! :many_happy_returns
140
+ assert_equal 3, returns
141
+ end
142
+
143
+ def test_destroy_metric_wipes_data
144
+ metric "Many Happy Returns"
145
+ Vanity.playground.track! :many_happy_returns, 3
146
+ assert_equal [3], Vanity.playground.metric(:many_happy_returns).values(today, today)
147
+ Vanity.playground.metric(:many_happy_returns).destroy!
148
+ assert_equal [0], Vanity.playground.metric(:many_happy_returns).values(today, today)
149
+ end
150
+
151
+
152
+ # -- Metric name --
153
+
154
+ def test_name_from_definition
155
+ File.open "tmp/experiments/metrics/yawns_sec.rb", "w" do |f|
156
+ f.write <<-RUBY
157
+ metric "Yawns/sec" do
158
+ end
159
+ RUBY
160
+ end
161
+ assert_equal "Yawns/sec", Vanity.playground.metric(:yawns_sec).name
162
+ end
163
+
164
+
165
+ # -- Description helper --
166
+
167
+ def test_description_for_metric_with_description
168
+ File.open "tmp/experiments/metrics/yawns_sec.rb", "w" do |f|
169
+ f.write <<-RUBY
170
+ metric "Yawns/sec" do
171
+ description "Am I that boring?"
172
+ end
173
+ RUBY
174
+ end
175
+ assert_equal "Am I that boring?", Vanity::Metric.description(Vanity.playground.metric(:yawns_sec))
176
+ end
177
+
178
+ def test_description_for_metric_with_no_description
179
+ File.open "tmp/experiments/metrics/yawns_sec.rb", "w" do |f|
180
+ f.write <<-RUBY
181
+ metric "Yawns/sec" do
182
+ end
183
+ RUBY
184
+ end
185
+ assert_nil Vanity::Metric.description(Vanity.playground.metric(:yawns_sec))
186
+ end
187
+
188
+ def test_description_for_metric_with_no_description_method
189
+ metric = Object.new
190
+ assert_nil Vanity::Metric.description(metric)
191
+ end
192
+
193
+
194
+ # -- Metric bounds --
195
+
196
+ def test_bounds_helper_for_metric_with_bounds
197
+ File.open "tmp/experiments/metrics/sky_is_limit.rb", "w" do |f|
198
+ f.write <<-RUBY
199
+ metric "Sky is limit" do
200
+ def bounds
201
+ [6,12]
202
+ end
203
+ end
204
+ RUBY
205
+ end
206
+ assert_equal [6,12], Vanity::Metric.bounds(Vanity.playground.metric(:sky_is_limit))
207
+ end
208
+
209
+ def test_bounds_helper_for_metric_with_no_bounds
210
+ metric "Sky is limit"
211
+ assert_equal [nil, nil], Vanity::Metric.bounds(Vanity.playground.metric(:sky_is_limit))
212
+ end
213
+
214
+ def test_bounds_helper_for_metric_with_no_bounds_method
215
+ metric = Object.new
216
+ assert_equal [nil, nil], Vanity::Metric.bounds(metric)
217
+ end
218
+
219
+
220
+ # -- Timestamp --
221
+
222
+ def test_metric_has_created_timestamp
223
+ metric "Coolness"
224
+ metric = Vanity.playground.metric(:coolness)
225
+ assert_instance_of Time, metric.created_at
226
+ assert_in_delta metric.created_at.to_i, Time.now.to_i, 1
227
+ end
228
+
229
+ def test_metric_keeps_created_timestamp_across_restarts
230
+ past = Date.today - 1
231
+ Timecop.travel past do
232
+ metric "Coolness"
233
+ coolness = Vanity.playground.metric(:coolness)
234
+ assert_in_delta coolness.created_at.to_i, past.to_time.to_i, 1
235
+ end
236
+
237
+ new_playground
238
+ metric "Coolness"
239
+ new_cool = Vanity.playground.metric(:coolness)
240
+ assert_in_delta new_cool.created_at.to_i, past.to_time.to_i, 1
241
+ end
242
+
243
+
244
+ # -- Data helper --
245
+
246
+ def test_data_with_explicit_dates
247
+ metric "Yawns/sec"
248
+ Timecop.travel(today - 4) { Vanity.playground.track! :yawns_sec, 4 }
249
+ Timecop.travel(today - 2) { Vanity.playground.track! :yawns_sec, 2 }
250
+ Vanity.playground.track! :yawns_sec
251
+ boredom = Vanity::Metric.data(Vanity.playground.metric(:yawns_sec), Date.today - 5, Date.today)
252
+ assert_equal [[today - 5, 0], [today - 4, 4], [today - 3, 0], [today - 2, 2], [today - 1, 0], [today, 1]], boredom
253
+ end
254
+
255
+ def test_data_with_start_date
256
+ metric "Yawns/sec"
257
+ Timecop.travel(today - 4) { Vanity.playground.track! :yawns_sec, 4 }
258
+ Timecop.travel(today - 2) { Vanity.playground.track! :yawns_sec, 2 }
259
+ Vanity.playground.track! :yawns_sec
260
+ boredom = Vanity::Metric.data(Vanity.playground.metric(:yawns_sec), Date.today - 5)
261
+ assert_equal [[today - 5, 0], [today - 4, 4], [today - 3, 0], [today - 2, 2], [today - 1, 0], [today, 1]], boredom
262
+ end
263
+
264
+ def test_data_with_duration
265
+ metric "Yawns/sec"
266
+ Timecop.travel(today - 4) { Vanity.playground.track! :yawns_sec, 4 }
267
+ Timecop.travel(today - 2) { Vanity.playground.track! :yawns_sec, 2 }
268
+ Vanity.playground.track! :yawns_sec
269
+ boredom = Vanity::Metric.data(Vanity.playground.metric(:yawns_sec), 5)
270
+ assert_equal [[today - 5, 0], [today - 4, 4], [today - 3, 0], [today - 2, 2], [today - 1, 0], [today, 1]], boredom
271
+ end
272
+
273
+ def test_data_with_no_dates
274
+ metric "Yawns/sec"
275
+ boredom = Vanity::Metric.data(Vanity.playground.metric(:yawns_sec))
276
+ assert_equal [today - 90, 0], boredom.first
277
+ assert_equal [today, 0], boredom.last
278
+ end
279
+
280
+
281
+ # -- Helper methods --
282
+
283
+ def today
284
+ @today ||= Date.today
285
+ end
286
+
287
+ end