modesty 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +13 -0
- data/Gemfile.lock +18 -0
- data/LICENSE +21 -0
- data/README.md +121 -0
- data/Rakefile +29 -0
- data/VERSION +1 -0
- data/init.rb +1 -0
- data/lib/modesty.rb +26 -0
- data/lib/modesty/api.rb +14 -0
- data/lib/modesty/core_ext.rb +5 -0
- data/lib/modesty/core_ext/array.rb +21 -0
- data/lib/modesty/core_ext/fixnum.rb +5 -0
- data/lib/modesty/core_ext/hash.rb +39 -0
- data/lib/modesty/core_ext/string.rb +9 -0
- data/lib/modesty/core_ext/symbol.rb +33 -0
- data/lib/modesty/datastore.rb +51 -0
- data/lib/modesty/datastore/redis.rb +180 -0
- data/lib/modesty/experiment.rb +87 -0
- data/lib/modesty/experiment/base.rb +47 -0
- data/lib/modesty/experiment/builder.rb +48 -0
- data/lib/modesty/experiment/console.rb +4 -0
- data/lib/modesty/experiment/data.rb +75 -0
- data/lib/modesty/experiment/interface.rb +29 -0
- data/lib/modesty/experiment/significance.rb +376 -0
- data/lib/modesty/experiment/stats.rb +163 -0
- data/lib/modesty/frameworks/rails.rb +27 -0
- data/lib/modesty/identity.rb +32 -0
- data/lib/modesty/load.rb +80 -0
- data/lib/modesty/load/load_experiments.rb +14 -0
- data/lib/modesty/load/load_metrics.rb +17 -0
- data/lib/modesty/metric.rb +56 -0
- data/lib/modesty/metric/base.rb +38 -0
- data/lib/modesty/metric/builder.rb +23 -0
- data/lib/modesty/metric/data.rb +133 -0
- data/modesty.gemspec +192 -0
- data/spec/core_ext_spec.rb +17 -0
- data/spec/experiment_spec.rb +239 -0
- data/spec/identity_spec.rb +161 -0
- data/spec/load_spec.rb +87 -0
- data/spec/metric_spec.rb +176 -0
- data/spec/rails_spec.rb +48 -0
- data/spec/redis_spec.rb +29 -0
- data/spec/significance_spec.rb +147 -0
- data/spec/spec.opts +1 -0
- data/test/myapp/config/modesty.yml +9 -0
- data/test/myapp/modesty/experiments/cookbook.rb +4 -0
- data/test/myapp/modesty/metrics/kitchen_metrics.rb +9 -0
- data/test/myapp/modesty/metrics/stove/burner_metrics.rb +2 -0
- data/vendor/.piston.yml +8 -0
- data/vendor/mock_redis/.gitignore +2 -0
- data/vendor/mock_redis/README +8 -0
- data/vendor/mock_redis/lib/mock_redis.rb +10 -0
- data/vendor/mock_redis/lib/mock_redis/hash.rb +61 -0
- data/vendor/mock_redis/lib/mock_redis/list.rb +6 -0
- data/vendor/mock_redis/lib/mock_redis/misc.rb +69 -0
- data/vendor/mock_redis/lib/mock_redis/set.rb +108 -0
- data/vendor/mock_redis/lib/mock_redis/string.rb +32 -0
- data/vendor/redis-rb/.gitignore +8 -0
- data/vendor/redis-rb/LICENSE +20 -0
- data/vendor/redis-rb/README.markdown +129 -0
- data/vendor/redis-rb/Rakefile +155 -0
- data/vendor/redis-rb/benchmarking/logging.rb +62 -0
- data/vendor/redis-rb/benchmarking/pipeline.rb +51 -0
- data/vendor/redis-rb/benchmarking/speed.rb +21 -0
- data/vendor/redis-rb/benchmarking/suite.rb +24 -0
- data/vendor/redis-rb/benchmarking/thread_safety.rb +38 -0
- data/vendor/redis-rb/benchmarking/worker.rb +71 -0
- data/vendor/redis-rb/examples/basic.rb +15 -0
- data/vendor/redis-rb/examples/dist_redis.rb +43 -0
- data/vendor/redis-rb/examples/incr-decr.rb +17 -0
- data/vendor/redis-rb/examples/list.rb +26 -0
- data/vendor/redis-rb/examples/pubsub.rb +31 -0
- data/vendor/redis-rb/examples/sets.rb +36 -0
- data/vendor/redis-rb/examples/unicorn/config.ru +3 -0
- data/vendor/redis-rb/examples/unicorn/unicorn.rb +20 -0
- data/vendor/redis-rb/lib/redis.rb +676 -0
- data/vendor/redis-rb/lib/redis/client.rb +201 -0
- data/vendor/redis-rb/lib/redis/compat.rb +21 -0
- data/vendor/redis-rb/lib/redis/connection.rb +134 -0
- data/vendor/redis-rb/lib/redis/distributed.rb +526 -0
- data/vendor/redis-rb/lib/redis/hash_ring.rb +131 -0
- data/vendor/redis-rb/lib/redis/pipeline.rb +13 -0
- data/vendor/redis-rb/lib/redis/subscribe.rb +79 -0
- data/vendor/redis-rb/redis.gemspec +29 -0
- data/vendor/redis-rb/test/commands_on_hashes_test.rb +46 -0
- data/vendor/redis-rb/test/commands_on_lists_test.rb +50 -0
- data/vendor/redis-rb/test/commands_on_sets_test.rb +78 -0
- data/vendor/redis-rb/test/commands_on_sorted_sets_test.rb +109 -0
- data/vendor/redis-rb/test/commands_on_strings_test.rb +70 -0
- data/vendor/redis-rb/test/commands_on_value_types_test.rb +88 -0
- data/vendor/redis-rb/test/connection_handling_test.rb +87 -0
- data/vendor/redis-rb/test/db/.gitignore +1 -0
- data/vendor/redis-rb/test/distributd_key_tags_test.rb +53 -0
- data/vendor/redis-rb/test/distributed_blocking_commands_test.rb +54 -0
- data/vendor/redis-rb/test/distributed_commands_on_hashes_test.rb +12 -0
- data/vendor/redis-rb/test/distributed_commands_on_lists_test.rb +18 -0
- data/vendor/redis-rb/test/distributed_commands_on_sets_test.rb +85 -0
- data/vendor/redis-rb/test/distributed_commands_on_strings_test.rb +50 -0
- data/vendor/redis-rb/test/distributed_commands_on_value_types_test.rb +73 -0
- data/vendor/redis-rb/test/distributed_commands_requiring_clustering_test.rb +141 -0
- data/vendor/redis-rb/test/distributed_connection_handling_test.rb +25 -0
- data/vendor/redis-rb/test/distributed_internals_test.rb +18 -0
- data/vendor/redis-rb/test/distributed_persistence_control_commands_test.rb +24 -0
- data/vendor/redis-rb/test/distributed_publish_subscribe_test.rb +90 -0
- data/vendor/redis-rb/test/distributed_remote_server_control_commands_test.rb +31 -0
- data/vendor/redis-rb/test/distributed_sorting_test.rb +21 -0
- data/vendor/redis-rb/test/distributed_test.rb +60 -0
- data/vendor/redis-rb/test/distributed_transactions_test.rb +34 -0
- data/vendor/redis-rb/test/encoding_test.rb +16 -0
- data/vendor/redis-rb/test/helper.rb +86 -0
- data/vendor/redis-rb/test/internals_test.rb +27 -0
- data/vendor/redis-rb/test/lint/hashes.rb +90 -0
- data/vendor/redis-rb/test/lint/internals.rb +53 -0
- data/vendor/redis-rb/test/lint/lists.rb +93 -0
- data/vendor/redis-rb/test/lint/sets.rb +66 -0
- data/vendor/redis-rb/test/lint/sorted_sets.rb +132 -0
- data/vendor/redis-rb/test/lint/strings.rb +98 -0
- data/vendor/redis-rb/test/lint/value_types.rb +84 -0
- data/vendor/redis-rb/test/persistence_control_commands_test.rb +22 -0
- data/vendor/redis-rb/test/pipelining_commands_test.rb +78 -0
- data/vendor/redis-rb/test/publish_subscribe_test.rb +151 -0
- data/vendor/redis-rb/test/redis_mock.rb +64 -0
- data/vendor/redis-rb/test/remote_server_control_commands_test.rb +56 -0
- data/vendor/redis-rb/test/sorting_test.rb +44 -0
- data/vendor/redis-rb/test/test.conf +8 -0
- data/vendor/redis-rb/test/thread_safety_test.rb +34 -0
- data/vendor/redis-rb/test/transactions_test.rb +91 -0
- data/vendor/redis-rb/test/unknown_commands_test.rb +14 -0
- data/vendor/redis-rb/test/url_param_test.rb +52 -0
- 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
|
data/modesty.gemspec
ADDED
@@ -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
|