modesty 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (130) hide show
  1. data/Gemfile +13 -0
  2. data/Gemfile.lock +18 -0
  3. data/LICENSE +21 -0
  4. data/README.md +121 -0
  5. data/Rakefile +29 -0
  6. data/VERSION +1 -0
  7. data/init.rb +1 -0
  8. data/lib/modesty.rb +26 -0
  9. data/lib/modesty/api.rb +14 -0
  10. data/lib/modesty/core_ext.rb +5 -0
  11. data/lib/modesty/core_ext/array.rb +21 -0
  12. data/lib/modesty/core_ext/fixnum.rb +5 -0
  13. data/lib/modesty/core_ext/hash.rb +39 -0
  14. data/lib/modesty/core_ext/string.rb +9 -0
  15. data/lib/modesty/core_ext/symbol.rb +33 -0
  16. data/lib/modesty/datastore.rb +51 -0
  17. data/lib/modesty/datastore/redis.rb +180 -0
  18. data/lib/modesty/experiment.rb +87 -0
  19. data/lib/modesty/experiment/base.rb +47 -0
  20. data/lib/modesty/experiment/builder.rb +48 -0
  21. data/lib/modesty/experiment/console.rb +4 -0
  22. data/lib/modesty/experiment/data.rb +75 -0
  23. data/lib/modesty/experiment/interface.rb +29 -0
  24. data/lib/modesty/experiment/significance.rb +376 -0
  25. data/lib/modesty/experiment/stats.rb +163 -0
  26. data/lib/modesty/frameworks/rails.rb +27 -0
  27. data/lib/modesty/identity.rb +32 -0
  28. data/lib/modesty/load.rb +80 -0
  29. data/lib/modesty/load/load_experiments.rb +14 -0
  30. data/lib/modesty/load/load_metrics.rb +17 -0
  31. data/lib/modesty/metric.rb +56 -0
  32. data/lib/modesty/metric/base.rb +38 -0
  33. data/lib/modesty/metric/builder.rb +23 -0
  34. data/lib/modesty/metric/data.rb +133 -0
  35. data/modesty.gemspec +192 -0
  36. data/spec/core_ext_spec.rb +17 -0
  37. data/spec/experiment_spec.rb +239 -0
  38. data/spec/identity_spec.rb +161 -0
  39. data/spec/load_spec.rb +87 -0
  40. data/spec/metric_spec.rb +176 -0
  41. data/spec/rails_spec.rb +48 -0
  42. data/spec/redis_spec.rb +29 -0
  43. data/spec/significance_spec.rb +147 -0
  44. data/spec/spec.opts +1 -0
  45. data/test/myapp/config/modesty.yml +9 -0
  46. data/test/myapp/modesty/experiments/cookbook.rb +4 -0
  47. data/test/myapp/modesty/metrics/kitchen_metrics.rb +9 -0
  48. data/test/myapp/modesty/metrics/stove/burner_metrics.rb +2 -0
  49. data/vendor/.piston.yml +8 -0
  50. data/vendor/mock_redis/.gitignore +2 -0
  51. data/vendor/mock_redis/README +8 -0
  52. data/vendor/mock_redis/lib/mock_redis.rb +10 -0
  53. data/vendor/mock_redis/lib/mock_redis/hash.rb +61 -0
  54. data/vendor/mock_redis/lib/mock_redis/list.rb +6 -0
  55. data/vendor/mock_redis/lib/mock_redis/misc.rb +69 -0
  56. data/vendor/mock_redis/lib/mock_redis/set.rb +108 -0
  57. data/vendor/mock_redis/lib/mock_redis/string.rb +32 -0
  58. data/vendor/redis-rb/.gitignore +8 -0
  59. data/vendor/redis-rb/LICENSE +20 -0
  60. data/vendor/redis-rb/README.markdown +129 -0
  61. data/vendor/redis-rb/Rakefile +155 -0
  62. data/vendor/redis-rb/benchmarking/logging.rb +62 -0
  63. data/vendor/redis-rb/benchmarking/pipeline.rb +51 -0
  64. data/vendor/redis-rb/benchmarking/speed.rb +21 -0
  65. data/vendor/redis-rb/benchmarking/suite.rb +24 -0
  66. data/vendor/redis-rb/benchmarking/thread_safety.rb +38 -0
  67. data/vendor/redis-rb/benchmarking/worker.rb +71 -0
  68. data/vendor/redis-rb/examples/basic.rb +15 -0
  69. data/vendor/redis-rb/examples/dist_redis.rb +43 -0
  70. data/vendor/redis-rb/examples/incr-decr.rb +17 -0
  71. data/vendor/redis-rb/examples/list.rb +26 -0
  72. data/vendor/redis-rb/examples/pubsub.rb +31 -0
  73. data/vendor/redis-rb/examples/sets.rb +36 -0
  74. data/vendor/redis-rb/examples/unicorn/config.ru +3 -0
  75. data/vendor/redis-rb/examples/unicorn/unicorn.rb +20 -0
  76. data/vendor/redis-rb/lib/redis.rb +676 -0
  77. data/vendor/redis-rb/lib/redis/client.rb +201 -0
  78. data/vendor/redis-rb/lib/redis/compat.rb +21 -0
  79. data/vendor/redis-rb/lib/redis/connection.rb +134 -0
  80. data/vendor/redis-rb/lib/redis/distributed.rb +526 -0
  81. data/vendor/redis-rb/lib/redis/hash_ring.rb +131 -0
  82. data/vendor/redis-rb/lib/redis/pipeline.rb +13 -0
  83. data/vendor/redis-rb/lib/redis/subscribe.rb +79 -0
  84. data/vendor/redis-rb/redis.gemspec +29 -0
  85. data/vendor/redis-rb/test/commands_on_hashes_test.rb +46 -0
  86. data/vendor/redis-rb/test/commands_on_lists_test.rb +50 -0
  87. data/vendor/redis-rb/test/commands_on_sets_test.rb +78 -0
  88. data/vendor/redis-rb/test/commands_on_sorted_sets_test.rb +109 -0
  89. data/vendor/redis-rb/test/commands_on_strings_test.rb +70 -0
  90. data/vendor/redis-rb/test/commands_on_value_types_test.rb +88 -0
  91. data/vendor/redis-rb/test/connection_handling_test.rb +87 -0
  92. data/vendor/redis-rb/test/db/.gitignore +1 -0
  93. data/vendor/redis-rb/test/distributd_key_tags_test.rb +53 -0
  94. data/vendor/redis-rb/test/distributed_blocking_commands_test.rb +54 -0
  95. data/vendor/redis-rb/test/distributed_commands_on_hashes_test.rb +12 -0
  96. data/vendor/redis-rb/test/distributed_commands_on_lists_test.rb +18 -0
  97. data/vendor/redis-rb/test/distributed_commands_on_sets_test.rb +85 -0
  98. data/vendor/redis-rb/test/distributed_commands_on_strings_test.rb +50 -0
  99. data/vendor/redis-rb/test/distributed_commands_on_value_types_test.rb +73 -0
  100. data/vendor/redis-rb/test/distributed_commands_requiring_clustering_test.rb +141 -0
  101. data/vendor/redis-rb/test/distributed_connection_handling_test.rb +25 -0
  102. data/vendor/redis-rb/test/distributed_internals_test.rb +18 -0
  103. data/vendor/redis-rb/test/distributed_persistence_control_commands_test.rb +24 -0
  104. data/vendor/redis-rb/test/distributed_publish_subscribe_test.rb +90 -0
  105. data/vendor/redis-rb/test/distributed_remote_server_control_commands_test.rb +31 -0
  106. data/vendor/redis-rb/test/distributed_sorting_test.rb +21 -0
  107. data/vendor/redis-rb/test/distributed_test.rb +60 -0
  108. data/vendor/redis-rb/test/distributed_transactions_test.rb +34 -0
  109. data/vendor/redis-rb/test/encoding_test.rb +16 -0
  110. data/vendor/redis-rb/test/helper.rb +86 -0
  111. data/vendor/redis-rb/test/internals_test.rb +27 -0
  112. data/vendor/redis-rb/test/lint/hashes.rb +90 -0
  113. data/vendor/redis-rb/test/lint/internals.rb +53 -0
  114. data/vendor/redis-rb/test/lint/lists.rb +93 -0
  115. data/vendor/redis-rb/test/lint/sets.rb +66 -0
  116. data/vendor/redis-rb/test/lint/sorted_sets.rb +132 -0
  117. data/vendor/redis-rb/test/lint/strings.rb +98 -0
  118. data/vendor/redis-rb/test/lint/value_types.rb +84 -0
  119. data/vendor/redis-rb/test/persistence_control_commands_test.rb +22 -0
  120. data/vendor/redis-rb/test/pipelining_commands_test.rb +78 -0
  121. data/vendor/redis-rb/test/publish_subscribe_test.rb +151 -0
  122. data/vendor/redis-rb/test/redis_mock.rb +64 -0
  123. data/vendor/redis-rb/test/remote_server_control_commands_test.rb +56 -0
  124. data/vendor/redis-rb/test/sorting_test.rb +44 -0
  125. data/vendor/redis-rb/test/test.conf +8 -0
  126. data/vendor/redis-rb/test/thread_safety_test.rb +34 -0
  127. data/vendor/redis-rb/test/transactions_test.rb +91 -0
  128. data/vendor/redis-rb/test/unknown_commands_test.rb +14 -0
  129. data/vendor/redis-rb/test/url_param_test.rb +52 -0
  130. metadata +277 -0
@@ -0,0 +1,133 @@
1
+ module Modesty
2
+ class Metric
3
+
4
+ def data
5
+ @data ||= (Modesty.data.class)::MetricData.new(self)
6
+ end
7
+
8
+ def parse_date(date)
9
+ if date.is_a? Symbol
10
+ return date if date == :all
11
+ Date.send(date)
12
+ elsif date.nil?
13
+ Date.today
14
+ else
15
+ date.to_date
16
+ end
17
+ end
18
+
19
+ def parse_date_or_range(start=nil,fin=nil)
20
+ if fin
21
+ parse_date(start)..parse_date(fin)
22
+ elsif start.is_a?(Range)
23
+ parse_date(start.first)..parse_date(start.last)
24
+ else
25
+ parse_date(start)
26
+ end
27
+ end
28
+
29
+
30
+ def count(*dates)
31
+ date_or_range = parse_date_or_range(*dates)
32
+
33
+ case date_or_range
34
+ when Range
35
+ if self.data.respond_to? :count_by_range
36
+ self.data.count_range(date_or_range)
37
+ else
38
+ date_or_range.map do |date|
39
+ self.data.count(date)
40
+ end
41
+ end
42
+ when Date, :all
43
+ self.data.count(date_or_range)
44
+ end
45
+ end
46
+
47
+ # for grep:
48
+ # def all
49
+ # def unique
50
+ # def distribution_by
51
+ # def aggregate_by
52
+ [
53
+ :all,
54
+ :unique,
55
+ :distribution_by,
56
+ :aggregate_by
57
+ ].each do |data_type|
58
+ by_range = :"#{data_type}_range"
59
+ define_method(data_type) do |sym, *dates|
60
+ sym = sym.to_s.singularize.to_sym
61
+ date_or_range = (dates.empty?) ? :all : parse_date_or_range(*dates)
62
+ if date_or_range.is_a? Range
63
+ if self.data.respond_to?(by_range)
64
+ return self.data.send(by_range, sym, date_or_range)
65
+ else
66
+ return date_or_range.map do |date|
67
+ self.data.send(data_type, sym, date)
68
+ end
69
+ end
70
+ elsif date_or_range.is_a?(Date) || date_or_range == :all
71
+ return self.data.send(data_type, sym, date_or_range)
72
+ end
73
+ end
74
+ end
75
+
76
+ # def distribution
77
+ # def aggregate
78
+ # helpers to use the default context if it's an experiment-generated metric
79
+ # otherwise default to users
80
+ %w(
81
+ distribution
82
+ aggregate
83
+ ).each do |data_type|
84
+ define_method data_type do |*dates|
85
+ context = if @experiment
86
+ @experiment.identity_for(self.parent)
87
+ else
88
+ :user
89
+ end
90
+ send("#{data_type}_by", context, *dates)
91
+ end
92
+ end
93
+
94
+ def track!(count=1, options={})
95
+ if count.is_a? Hash
96
+ options = count
97
+ count = options[:count] || 1
98
+ end
99
+
100
+ with = options[:with] || {}
101
+
102
+ with[:user] ||= Modesty.identity if Modesty.identity
103
+
104
+ self.experiments.each do |exp|
105
+ # only track the for the experiment group if
106
+ # the user has previously hit the experiment
107
+ identity_slug = exp.identity_for(self)
108
+ identity = with[identity_slug]
109
+
110
+ if !identity && exp.metric_contexts.has_key?(self.slug)
111
+ raise IdentityError, <<-msg.squish
112
+ #{exp.inspect} requires #{self.inspect} to be tracked
113
+ with #{identity_slug.to_s.singularize.to_sym.inspect}.
114
+
115
+ It was tracked :with => #{with.inspect}
116
+ msg
117
+ end
118
+
119
+ if identity
120
+ alt = exp.data.get_cached_alternative(identity)
121
+ if alt
122
+ (self/(exp.slug/alt)).data.track!(count, with)
123
+ end
124
+ end
125
+ end
126
+
127
+ self.data.track!(count, with)
128
+ @parent.track!(count, :with => with) if @parent
129
+ rescue Datastore::ConnectionError => e
130
+ Modesty.handle_error(e)
131
+ end
132
+ end
133
+ end
@@ -0,0 +1,192 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{modesty}
8
+ s.version = "0.1.0"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Jay Adkisson", "Kevin Ball", "Kristj\303\241n P\303\251tursson"]
12
+ s.date = %q{2011-04-26}
13
+ s.description = %q{Modesty is simple and scalable split testing and event tracking framework. It was inspired by assaf's Vanity (github.com/assaf/vanity).}
14
+ s.email = %q{jay@causes.com}
15
+ s.extra_rdoc_files = [
16
+ "LICENSE",
17
+ "README.md"
18
+ ]
19
+ s.files = [
20
+ "Gemfile",
21
+ "Gemfile.lock",
22
+ "LICENSE",
23
+ "README.md",
24
+ "Rakefile",
25
+ "VERSION",
26
+ "init.rb",
27
+ "lib/modesty.rb",
28
+ "lib/modesty/api.rb",
29
+ "lib/modesty/core_ext.rb",
30
+ "lib/modesty/core_ext/array.rb",
31
+ "lib/modesty/core_ext/fixnum.rb",
32
+ "lib/modesty/core_ext/hash.rb",
33
+ "lib/modesty/core_ext/string.rb",
34
+ "lib/modesty/core_ext/symbol.rb",
35
+ "lib/modesty/datastore.rb",
36
+ "lib/modesty/datastore/redis.rb",
37
+ "lib/modesty/experiment.rb",
38
+ "lib/modesty/experiment/base.rb",
39
+ "lib/modesty/experiment/builder.rb",
40
+ "lib/modesty/experiment/console.rb",
41
+ "lib/modesty/experiment/data.rb",
42
+ "lib/modesty/experiment/interface.rb",
43
+ "lib/modesty/experiment/significance.rb",
44
+ "lib/modesty/experiment/stats.rb",
45
+ "lib/modesty/frameworks/rails.rb",
46
+ "lib/modesty/identity.rb",
47
+ "lib/modesty/load.rb",
48
+ "lib/modesty/load/load_experiments.rb",
49
+ "lib/modesty/load/load_metrics.rb",
50
+ "lib/modesty/metric.rb",
51
+ "lib/modesty/metric/base.rb",
52
+ "lib/modesty/metric/builder.rb",
53
+ "lib/modesty/metric/data.rb",
54
+ "modesty.gemspec",
55
+ "spec/core_ext_spec.rb",
56
+ "spec/experiment_spec.rb",
57
+ "spec/identity_spec.rb",
58
+ "spec/load_spec.rb",
59
+ "spec/metric_spec.rb",
60
+ "spec/rails_spec.rb",
61
+ "spec/redis_spec.rb",
62
+ "spec/significance_spec.rb",
63
+ "spec/spec.opts",
64
+ "test/myapp/config/modesty.yml",
65
+ "test/myapp/modesty/experiments/cookbook.rb",
66
+ "test/myapp/modesty/metrics/kitchen_metrics.rb",
67
+ "test/myapp/modesty/metrics/stove/burner_metrics.rb",
68
+ "vendor/.piston.yml",
69
+ "vendor/mock_redis/.gitignore",
70
+ "vendor/mock_redis/README",
71
+ "vendor/mock_redis/lib/mock_redis.rb",
72
+ "vendor/mock_redis/lib/mock_redis/hash.rb",
73
+ "vendor/mock_redis/lib/mock_redis/list.rb",
74
+ "vendor/mock_redis/lib/mock_redis/misc.rb",
75
+ "vendor/mock_redis/lib/mock_redis/set.rb",
76
+ "vendor/mock_redis/lib/mock_redis/string.rb",
77
+ "vendor/redis-rb/.gitignore",
78
+ "vendor/redis-rb/LICENSE",
79
+ "vendor/redis-rb/README.markdown",
80
+ "vendor/redis-rb/Rakefile",
81
+ "vendor/redis-rb/benchmarking/logging.rb",
82
+ "vendor/redis-rb/benchmarking/pipeline.rb",
83
+ "vendor/redis-rb/benchmarking/speed.rb",
84
+ "vendor/redis-rb/benchmarking/suite.rb",
85
+ "vendor/redis-rb/benchmarking/thread_safety.rb",
86
+ "vendor/redis-rb/benchmarking/worker.rb",
87
+ "vendor/redis-rb/examples/basic.rb",
88
+ "vendor/redis-rb/examples/dist_redis.rb",
89
+ "vendor/redis-rb/examples/incr-decr.rb",
90
+ "vendor/redis-rb/examples/list.rb",
91
+ "vendor/redis-rb/examples/pubsub.rb",
92
+ "vendor/redis-rb/examples/sets.rb",
93
+ "vendor/redis-rb/examples/unicorn/config.ru",
94
+ "vendor/redis-rb/examples/unicorn/unicorn.rb",
95
+ "vendor/redis-rb/lib/redis.rb",
96
+ "vendor/redis-rb/lib/redis/client.rb",
97
+ "vendor/redis-rb/lib/redis/compat.rb",
98
+ "vendor/redis-rb/lib/redis/connection.rb",
99
+ "vendor/redis-rb/lib/redis/distributed.rb",
100
+ "vendor/redis-rb/lib/redis/hash_ring.rb",
101
+ "vendor/redis-rb/lib/redis/pipeline.rb",
102
+ "vendor/redis-rb/lib/redis/subscribe.rb",
103
+ "vendor/redis-rb/redis.gemspec",
104
+ "vendor/redis-rb/test/commands_on_hashes_test.rb",
105
+ "vendor/redis-rb/test/commands_on_lists_test.rb",
106
+ "vendor/redis-rb/test/commands_on_sets_test.rb",
107
+ "vendor/redis-rb/test/commands_on_sorted_sets_test.rb",
108
+ "vendor/redis-rb/test/commands_on_strings_test.rb",
109
+ "vendor/redis-rb/test/commands_on_value_types_test.rb",
110
+ "vendor/redis-rb/test/connection_handling_test.rb",
111
+ "vendor/redis-rb/test/db/.gitignore",
112
+ "vendor/redis-rb/test/distributd_key_tags_test.rb",
113
+ "vendor/redis-rb/test/distributed_blocking_commands_test.rb",
114
+ "vendor/redis-rb/test/distributed_commands_on_hashes_test.rb",
115
+ "vendor/redis-rb/test/distributed_commands_on_lists_test.rb",
116
+ "vendor/redis-rb/test/distributed_commands_on_sets_test.rb",
117
+ "vendor/redis-rb/test/distributed_commands_on_strings_test.rb",
118
+ "vendor/redis-rb/test/distributed_commands_on_value_types_test.rb",
119
+ "vendor/redis-rb/test/distributed_commands_requiring_clustering_test.rb",
120
+ "vendor/redis-rb/test/distributed_connection_handling_test.rb",
121
+ "vendor/redis-rb/test/distributed_internals_test.rb",
122
+ "vendor/redis-rb/test/distributed_persistence_control_commands_test.rb",
123
+ "vendor/redis-rb/test/distributed_publish_subscribe_test.rb",
124
+ "vendor/redis-rb/test/distributed_remote_server_control_commands_test.rb",
125
+ "vendor/redis-rb/test/distributed_sorting_test.rb",
126
+ "vendor/redis-rb/test/distributed_test.rb",
127
+ "vendor/redis-rb/test/distributed_transactions_test.rb",
128
+ "vendor/redis-rb/test/encoding_test.rb",
129
+ "vendor/redis-rb/test/helper.rb",
130
+ "vendor/redis-rb/test/internals_test.rb",
131
+ "vendor/redis-rb/test/lint/hashes.rb",
132
+ "vendor/redis-rb/test/lint/internals.rb",
133
+ "vendor/redis-rb/test/lint/lists.rb",
134
+ "vendor/redis-rb/test/lint/sets.rb",
135
+ "vendor/redis-rb/test/lint/sorted_sets.rb",
136
+ "vendor/redis-rb/test/lint/strings.rb",
137
+ "vendor/redis-rb/test/lint/value_types.rb",
138
+ "vendor/redis-rb/test/persistence_control_commands_test.rb",
139
+ "vendor/redis-rb/test/pipelining_commands_test.rb",
140
+ "vendor/redis-rb/test/publish_subscribe_test.rb",
141
+ "vendor/redis-rb/test/redis_mock.rb",
142
+ "vendor/redis-rb/test/remote_server_control_commands_test.rb",
143
+ "vendor/redis-rb/test/sorting_test.rb",
144
+ "vendor/redis-rb/test/test.conf",
145
+ "vendor/redis-rb/test/thread_safety_test.rb",
146
+ "vendor/redis-rb/test/transactions_test.rb",
147
+ "vendor/redis-rb/test/unknown_commands_test.rb",
148
+ "vendor/redis-rb/test/url_param_test.rb"
149
+ ]
150
+ s.homepage = %q{http://github.com/causes/modesty}
151
+ s.require_paths = ["lib"]
152
+ s.rubygems_version = %q{1.6.2}
153
+ s.summary = %q{Modesty is simple and scalable split testing and event tracking framework.}
154
+ s.test_files = [
155
+ "spec/core_ext_spec.rb",
156
+ "spec/experiment_spec.rb",
157
+ "spec/identity_spec.rb",
158
+ "spec/load_spec.rb",
159
+ "spec/metric_spec.rb",
160
+ "spec/rails_spec.rb",
161
+ "spec/redis_spec.rb",
162
+ "spec/significance_spec.rb",
163
+ "test/myapp/modesty/experiments/cookbook.rb",
164
+ "test/myapp/modesty/metrics/kitchen_metrics.rb",
165
+ "test/myapp/modesty/metrics/stove/burner_metrics.rb"
166
+ ]
167
+
168
+ if s.respond_to? :specification_version then
169
+ s.specification_version = 3
170
+
171
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
172
+ s.add_runtime_dependency(%q<redis>, [">= 0"])
173
+ s.add_development_dependency(%q<rake>, [">= 0"])
174
+ s.add_development_dependency(%q<jeweler>, [">= 0"])
175
+ s.add_development_dependency(%q<rspec>, [">= 0"])
176
+ s.add_runtime_dependency(%q<redis>, [">= 0"])
177
+ else
178
+ s.add_dependency(%q<redis>, [">= 0"])
179
+ s.add_dependency(%q<rake>, [">= 0"])
180
+ s.add_dependency(%q<jeweler>, [">= 0"])
181
+ s.add_dependency(%q<rspec>, [">= 0"])
182
+ s.add_dependency(%q<redis>, [">= 0"])
183
+ end
184
+ else
185
+ s.add_dependency(%q<redis>, [">= 0"])
186
+ s.add_dependency(%q<rake>, [">= 0"])
187
+ s.add_dependency(%q<jeweler>, [">= 0"])
188
+ s.add_dependency(%q<rspec>, [">= 0"])
189
+ s.add_dependency(%q<redis>, [">= 0"])
190
+ end
191
+ end
192
+
@@ -0,0 +1,17 @@
1
+ require 'modesty'
2
+
3
+ describe Symbol do
4
+ [
5
+ :abc,
6
+ :abc=,
7
+ :a/:bc/:def,
8
+ :/,
9
+ :"////",
10
+ :':',
11
+ :"http://www.google.com/",
12
+ ].each do |sym|
13
+ it "eval(:\"#{sym}\".inspect) == :\"#{sym}\"" do
14
+ eval(sym.inspect).should == sym
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,239 @@
1
+ require 'modesty'
2
+
3
+ describe Modesty::Experiment, "creating an experiment" do
4
+ before :each do
5
+ Modesty.metrics.clear
6
+ Modesty.experiments.clear
7
+ Modesty.new_metric :foo do |m|
8
+ m.description "Foo"
9
+ m.submetric :bar do |m|
10
+ m.description "Bar"
11
+ end
12
+ end
13
+
14
+ Modesty.new_metric :baz do |m|
15
+ m.description "Baz"
16
+ end
17
+
18
+ @e = Modesty.new_experiment(:creation_page) do |m|
19
+ m.description "Three versions of the creation page"
20
+ m.alternatives :heavyweight, :lightweight
21
+ m.metrics :foo/:bar, :baz
22
+ end
23
+
24
+ @f = Modesty.new_experiment :ab_test do |e|
25
+ e.description "only two groups"
26
+ e.metrics :baz
27
+ end
28
+ end
29
+
30
+ it "can create an experiment with a block" do
31
+ Modesty.experiments.should include :creation_page
32
+ Modesty.experiments[:creation_page].should == @e
33
+
34
+ @e.metrics.should include Modesty.metrics[:foo/:bar]
35
+ @e.metrics.should include Modesty.metrics[:baz]
36
+ @e.alternatives.should == [:control, :heavyweight, :lightweight]
37
+ @e.description.should == "Three versions of the creation page"
38
+ end
39
+
40
+ it "uses [:control, :experiment] as the default experiment groups" do
41
+ @f.alternatives.should == [:control, :experiment]
42
+ end
43
+
44
+ it "auto-creates metrics" do
45
+ Modesty.metrics.keys.should include :foo/:bar/:creation_page/:control
46
+ Modesty.metrics.keys.should include :foo/:bar/:creation_page/:heavyweight
47
+ Modesty.metrics.keys.should include :foo/:bar/:creation_page/:lightweight
48
+ Modesty.metrics.keys.should include :baz/:creation_page/:control
49
+ Modesty.metrics.keys.should include :baz/:creation_page/:heavyweight
50
+ Modesty.metrics.keys.should include :baz/:creation_page/:lightweight
51
+
52
+ Modesty.metrics.keys.should include :baz/:ab_test/:control
53
+ Modesty.metrics.keys.should include :baz/:ab_test/:experiment
54
+ end
55
+
56
+ it "grabs metrics nicely with e.metrics(alt)" do
57
+ @e.alternatives.each do |alt|
58
+ @e.metrics(alt).should == {
59
+ :foo/:bar => Modesty.metrics[:foo/:bar/:creation_page/alt],
60
+ :baz => Modesty.metrics[:baz/:creation_page/alt],
61
+ }
62
+ end
63
+
64
+ lambda do
65
+ @e.metrics(:i_dont_exist)
66
+ end.should raise_error(Modesty::Experiment::Error)
67
+ end
68
+ end
69
+
70
+ describe "A/B testing" do
71
+ before :all do
72
+ Modesty.set_store :redis, :mock => true
73
+ Modesty.data.flushdb
74
+ end
75
+
76
+ before :each do
77
+ @e = Modesty.experiments[:creation_page]
78
+ end
79
+
80
+ it "Selects evenly between alternatives" do
81
+ (0..(3*100-1)).each do |i|
82
+ Modesty.identify! i
83
+ Modesty.experiment :creation_page do |exp|
84
+ [:control, :lightweight, :heavyweight].each do |alt|
85
+ exp.group alt do
86
+ Modesty.track! :baz
87
+ end
88
+ end
89
+ end
90
+ Modesty.metrics[:baz].count.should == 1+i
91
+ end
92
+ end
93
+
94
+ it "raises Modesty::Experiment::MissingError if exp is missing" do
95
+ lambda do
96
+ Modesty.group(:idontexist)
97
+ end.should raise_error(Modesty::Experiment::Error)
98
+ end
99
+
100
+ it "assigns guests to :control" do
101
+ Modesty.identify! nil
102
+ Modesty.group(:ab_test).should == :control
103
+ Modesty.group?(:ab_test/:control).should == true
104
+
105
+ Modesty.identify! 200
106
+ end
107
+
108
+ it "can use a passed-in identity" do
109
+ leet = Modesty.experiment :ab_test, :on => 1337 do |exp|
110
+ [:control, :experiment].each do |alt|
111
+ exp.group alt do
112
+ Modesty.identity
113
+ end
114
+ end
115
+ end
116
+
117
+ leet.should == 1337
118
+ end
119
+
120
+ it "can used a passed-in nil identity" do
121
+ test = Modesty.experiment :ab_test, :on => nil do |exp|
122
+ exp.group :control do
123
+ Modesty.identity
124
+ end
125
+ exp.group :experiment do
126
+ "fail"
127
+ end
128
+ end
129
+
130
+ test.should be_nil
131
+ end
132
+
133
+ it "tracks the users in each experimental group" do
134
+ e = Modesty.experiments[:creation_page]
135
+ lambda { e.users }.should_not raise_error
136
+ u = e.users
137
+ u.should be_a Array
138
+ (1..(3*100-1)).each do |i|
139
+ u.should include i
140
+ end
141
+ end
142
+
143
+ it "tracks the number of users in each experimental group" do
144
+ e = Modesty.experiments[:creation_page]
145
+ e.num_users.should == 3*100
146
+ [:lightweight, :control, :heavyweight].each do |alt|
147
+ e.num_users(alt).should be_close 3*100/4, 2 + 3*100/6
148
+ end
149
+ end
150
+
151
+ it "uses cached alternative" do
152
+ # should ask Redis for the correct group
153
+ # instead of running generate_group
154
+ (1..10).each do |i|
155
+ Modesty.identify! i
156
+ Modesty.group :creation_page
157
+ end
158
+
159
+ @e.should_not_receive(:generate_group)
160
+ (1..10).each do |i|
161
+ Modesty.identify! i
162
+ Modesty.group :creation_page
163
+ end
164
+ end
165
+
166
+ it "tracks the experiment group if you've hit the experiment" do
167
+ Modesty.identify! 500 #not in the experiment group yet
168
+ [:control, :lightweight, :heavyweight].each do |alt|
169
+ lambda do
170
+ Modesty.track! :baz
171
+ end.should_not change(Modesty.metrics[:baz/:creation_page/alt], :count)
172
+ end
173
+
174
+ alt = Modesty.group :creation_page
175
+ lambda do
176
+ Modesty.track! :baz, 5
177
+ end.should change(Modesty.metrics[:baz/:creation_page/alt], :count).by(5)
178
+ end
179
+
180
+ it "allows for manually setting your experiment group" do
181
+ Modesty.identify! 50
182
+ e = Modesty.experiments[:creation_page]
183
+ 2.times do
184
+ e.alternatives.each do |alt|
185
+ lambda do
186
+ e.chooses alt
187
+ e.group.should == alt
188
+ end.should_not change(e, :num_users)
189
+ end
190
+ end
191
+ end
192
+ end
193
+
194
+ describe "Datastore failing" do
195
+ before :each do
196
+ Modesty.data.flushdb
197
+ Modesty.experiments.clear
198
+ Modesty.metrics.clear
199
+
200
+ Modesty.new_metric :foo
201
+
202
+ Modesty.new_experiment :my_exp do |e|
203
+ e.metric :foo
204
+ end
205
+
206
+ Modesty.data.stub!(:get_cached_alternative).and_raise(
207
+ Modesty::Datastore::ConnectionError
208
+ )
209
+
210
+ Modesty.data.stub!(:register!).and_raise(
211
+ Modesty::Datastore::ConnectionError
212
+ )
213
+ end
214
+
215
+ it "fails gracefully for choose_group" do
216
+ Modesty.identify! 9876
217
+ first_group = second_group = nil
218
+ lambda do
219
+ first_group = Modesty.group :my_exp
220
+ end.should_not raise_error(Modesty::Datastore::ConnectionError)
221
+
222
+ lambda do
223
+ second_group = Modesty.group :my_exp
224
+ end.should_not raise_error(Modesty::Datastore::ConnectionError)
225
+
226
+ #should be consistent
227
+ first_group.should == second_group
228
+ end
229
+ end
230
+
231
+ describe "regression for 740de0b1f" do
232
+ it "doesn't blow up on startup" do
233
+ Modesty.api.instance_variable_set("@experiments", nil)
234
+ lambda do
235
+ Modesty.new_experiment :foo do
236
+ end
237
+ end.should_not raise_error
238
+ end
239
+ end