vanity 1.0.0 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
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