modesty 0.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 (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