timberline 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,229 @@
1
+ ## This is code stolen from the definition file for Minitest::Spec. We really
2
+ # like everything about Minitest::Spec except for the expectations part, so we
3
+ # are stealing it and using it here. Shamelessly.
4
+ #
5
+ # This code may go out of date in future versions of Ruby, so we should keep an
6
+ # eye on that. But that's better than reinventing the wheel.
7
+
8
+ module Kernel # :nodoc:
9
+ ##
10
+ # Describe a series of expectations for a given target +desc+.
11
+ #
12
+ # TODO: find good tutorial url.
13
+ #
14
+ # Defines a test class subclassing from either MiniTest::Spec or
15
+ # from the surrounding describe's class. The surrounding class may
16
+ # subclass MiniTest::Spec manually in order to easily share code:
17
+ #
18
+ # class MySpec < MiniTest::Spec
19
+ # # ... shared code ...
20
+ # end
21
+ #
22
+ # class TestStuff < MySpec
23
+ # it "does stuff" do
24
+ # # shared code available here
25
+ # end
26
+ # describe "inner stuff" do
27
+ # it "still does stuff" do
28
+ # # ...and here
29
+ # end
30
+ # end
31
+ # end
32
+
33
+ def describe desc, additional_desc = nil, &block # :doc:
34
+ stack = MiniTest::Spec.describe_stack
35
+ name = [stack.last, desc, additional_desc].compact.join("::")
36
+ sclas = stack.last || if Class === self && self < MiniTest::Spec then
37
+ self
38
+ else
39
+ MiniTest::Spec.spec_type desc
40
+ end
41
+
42
+ cls = sclas.create name, desc
43
+
44
+ stack.push cls
45
+ cls.class_eval(&block)
46
+ stack.pop
47
+ cls
48
+ end
49
+ private :describe
50
+ end
51
+
52
+ ##
53
+ # MiniTest::Spec -- The faster, better, less-magical spec framework!
54
+ #
55
+ # For a list of expectations, see MiniTest::Expectations.
56
+
57
+ class MiniTest::Spec < MiniTest::Unit::TestCase
58
+ ##
59
+ # Contains pairs of matchers and Spec classes to be used to
60
+ # calculate the superclass of a top-level describe. This allows for
61
+ # automatically customizable spec types.
62
+ #
63
+ # See: register_spec_type and spec_type
64
+
65
+ TYPES = [[//, MiniTest::Spec]]
66
+
67
+ ##
68
+ # Register a new type of spec that matches the spec's description.
69
+ # This method can take either a Regexp and a spec class or a spec
70
+ # class and a block that takes the description and returns true if
71
+ # it matches.
72
+ #
73
+ # Eg:
74
+ #
75
+ # register_spec_type(/Controller$/, MiniTest::Spec::Rails)
76
+ #
77
+ # or:
78
+ #
79
+ # register_spec_type(MiniTest::Spec::RailsModel) do |desc|
80
+ # desc.superclass == ActiveRecord::Base
81
+ # end
82
+
83
+ def self.register_spec_type(*args, &block)
84
+ if block then
85
+ matcher, klass = block, args.first
86
+ else
87
+ matcher, klass = *args
88
+ end
89
+ TYPES.unshift [matcher, klass]
90
+ end
91
+
92
+ ##
93
+ # Figure out the spec class to use based on a spec's description. Eg:
94
+ #
95
+ # spec_type("BlahController") # => MiniTest::Spec::Rails
96
+
97
+ def self.spec_type desc
98
+ TYPES.find { |matcher, klass|
99
+ if matcher.respond_to? :call then
100
+ matcher.call desc
101
+ else
102
+ matcher === desc.to_s
103
+ end
104
+ }.last
105
+ end
106
+
107
+ @@describe_stack = []
108
+ def self.describe_stack # :nodoc:
109
+ @@describe_stack
110
+ end
111
+
112
+ ##
113
+ # Returns the children of this spec.
114
+
115
+ def self.children
116
+ @children ||= []
117
+ end
118
+
119
+ def self.nuke_test_methods! # :nodoc:
120
+ self.public_instance_methods.grep(/^test_/).each do |name|
121
+ self.send :undef_method, name
122
+ end
123
+ end
124
+
125
+ ##
126
+ # Define a 'before' action. Inherits the way normal methods should.
127
+ #
128
+ # NOTE: +type+ is ignored and is only there to make porting easier.
129
+ #
130
+ # Equivalent to MiniTest::Unit::TestCase#setup.
131
+
132
+ def self.before type = :each, &block
133
+ raise "unsupported before type: #{type}" unless type == :each
134
+
135
+ add_setup_hook {|tc| tc.instance_eval(&block) }
136
+ end
137
+
138
+ ##
139
+ # Define an 'after' action. Inherits the way normal methods should.
140
+ #
141
+ # NOTE: +type+ is ignored and is only there to make porting easier.
142
+ #
143
+ # Equivalent to MiniTest::Unit::TestCase#teardown.
144
+
145
+ def self.after type = :each, &block
146
+ raise "unsupported after type: #{type}" unless type == :each
147
+
148
+ add_teardown_hook {|tc| tc.instance_eval(&block) }
149
+ end
150
+
151
+ ##
152
+ # Define an expectation with name +desc+. Name gets morphed to a
153
+ # proper test method name. For some freakish reason, people who
154
+ # write specs don't like class inheritence, so this goes way out of
155
+ # its way to make sure that expectations aren't inherited.
156
+ #
157
+ # This is also aliased to #specify and doesn't require a +desc+ arg.
158
+ #
159
+ # Hint: If you _do_ want inheritence, use minitest/unit. You can mix
160
+ # and match between assertions and expectations as much as you want.
161
+
162
+ def self.it desc = "anonymous", &block
163
+ block ||= proc { skip "(no tests defined)" }
164
+
165
+ @specs ||= 0
166
+ @specs += 1
167
+
168
+ name = "test_%04d_%s" % [ @specs, desc.gsub(/\W+/, '_').downcase ]
169
+
170
+ define_method name, &block
171
+
172
+ self.children.each do |mod|
173
+ mod.send :undef_method, name if mod.public_method_defined? name
174
+ end
175
+ end
176
+
177
+ ##
178
+ # Essentially, define an accessor for +name+ with +block+.
179
+ #
180
+ # Why use let instead of def? I honestly don't know.
181
+
182
+ def self.let name, &block
183
+ define_method name do
184
+ @_memoized ||= {}
185
+ @_memoized.fetch(name) { |k| @_memoized[k] = instance_eval(&block) }
186
+ end
187
+ end
188
+
189
+ ##
190
+ # Another lazy man's accessor generator. Made even more lazy by
191
+ # setting the name for you to +subject+.
192
+
193
+ def self.subject &block
194
+ let :subject, &block
195
+ end
196
+
197
+ def self.create name, desc # :nodoc:
198
+ cls = Class.new(self) do
199
+ @name = name
200
+ @desc = desc
201
+
202
+ nuke_test_methods!
203
+ end
204
+
205
+ children << cls
206
+
207
+ cls
208
+ end
209
+
210
+ def self.to_s # :nodoc:
211
+ defined?(@name) ? @name : super
212
+ end
213
+
214
+ # :stopdoc:
215
+ def after_setup
216
+ run_setup_hooks
217
+ end
218
+
219
+ def before_teardown
220
+ run_teardown_hooks
221
+ end
222
+
223
+ class << self
224
+ attr_reader :desc
225
+ alias :specify :it
226
+ alias :name :to_s
227
+ end
228
+ # :startdoc:
229
+ end
@@ -0,0 +1,6 @@
1
+ host: localhost
2
+ port: 12345
3
+ timeout: 10
4
+ password: foo
5
+ database: 3
6
+ namespace: treecurve
@@ -0,0 +1,15 @@
1
+ require 'test/unit'
2
+
3
+ require 'partial_minispec'
4
+
5
+ # include the gem
6
+ require 'timberline'
7
+
8
+ # Use database 15 for testing, so we don't risk overwriting any data that's
9
+ # actually useful
10
+ def clear_test_db
11
+ Timberline.config do |c|
12
+ c.database = 15
13
+ end
14
+ Timberline.redis.flushdb
15
+ end
@@ -0,0 +1,115 @@
1
+ require 'test_helper'
2
+
3
+ describe Timberline::Config do
4
+ describe "Without any preset YAML configs" do
5
+ before do
6
+ @config = Timberline::Config.new
7
+ end
8
+
9
+ it "builds a proper config hash for Redis" do
10
+ @logger = Logger.new STDERR
11
+
12
+ @config.host = "localhost"
13
+ @config.port = 12345
14
+ @config.timeout = 10
15
+ @config.password = "foo"
16
+ @config.database = 3
17
+ @config.logger = @logger
18
+
19
+ config = @config.redis_config
20
+
21
+ assert_equal "localhost", config[:host]
22
+ assert_equal 12345, config[:port]
23
+ assert_equal 10, config[:timeout]
24
+ assert_equal "foo", config[:password]
25
+ assert_equal 3, config[:db]
26
+ assert_equal @logger, config[:logger]
27
+
28
+ end
29
+
30
+ it "reads configuration from a YAML config file" do
31
+ base_dir = File.dirname(File.path(__FILE__))
32
+ yaml_file = File.join(base_dir, "..", "test_config.yaml")
33
+ @config.load_from_yaml(yaml_file)
34
+ assert_equal "localhost", @config.host
35
+ assert_equal 12345, @config.port
36
+ assert_equal 10, @config.timeout
37
+ assert_equal "foo", @config.password
38
+ assert_equal 3, @config.database
39
+ assert_equal "treecurve", @config.namespace
40
+ end
41
+ end
42
+
43
+ describe "when in a Rails app without a config file" do
44
+ before do
45
+ RAILS_ROOT = File.join(File.dirname(File.path(__FILE__)), "..", "gibberish")
46
+ @config = Timberline::Config.new
47
+ end
48
+
49
+ after do
50
+ Object.send(:remove_const, :RAILS_ROOT)
51
+ end
52
+
53
+ it "should load successfully without any configs." do
54
+ ["database","host","port","timeout","password","logger","namespace"].each do |setting|
55
+ assert_equal nil, @config.instance_variable_get("@#{setting}")
56
+ end
57
+ end
58
+ end
59
+
60
+ describe "when in a Rails app with a config file" do
61
+ before do
62
+ RAILS_ROOT = File.join(File.dirname(File.path(__FILE__)), "..", "fake_rails")
63
+ @config = Timberline::Config.new
64
+ end
65
+
66
+ after do
67
+ Object.send(:remove_const, :RAILS_ROOT)
68
+ end
69
+
70
+ it "should load the config/timberline.yaml file" do
71
+ assert_equal "localhost", @config.host
72
+ assert_equal 12345, @config.port
73
+ assert_equal 10, @config.timeout
74
+ assert_equal "foo", @config.password
75
+ assert_equal 3, @config.database
76
+ assert_equal "treecurve", @config.namespace
77
+ end
78
+ end
79
+
80
+ describe "when TIMBERLINE_YAML is defined" do
81
+ before do
82
+ TIMBERLINE_YAML = File.join(File.dirname(File.path(__FILE__)), "..", "test_config.yaml")
83
+ @config = Timberline::Config.new
84
+ end
85
+
86
+ after do
87
+ Object.send(:remove_const, :TIMBERLINE_YAML)
88
+ end
89
+
90
+ it "should load the specified yaml file" do
91
+ assert_equal "localhost", @config.host
92
+ assert_equal 12345, @config.port
93
+ assert_equal 10, @config.timeout
94
+ assert_equal "foo", @config.password
95
+ assert_equal 3, @config.database
96
+ assert_equal "treecurve", @config.namespace
97
+ end
98
+ end
99
+
100
+ describe "when TIMBERLINE_YAML is defined, but doesn't exist" do
101
+ before do
102
+ TIMBERLINE_YAML = File.join(File.dirname(File.path(__FILE__)), "..", "fake_config.yaml")
103
+ end
104
+
105
+ after do
106
+ Object.send(:remove_const, :TIMBERLINE_YAML)
107
+ end
108
+
109
+ it "should raise an exception" do
110
+ assert_raises RuntimeError do
111
+ @config = Timberline::Config.new
112
+ end
113
+ end
114
+ end
115
+ end
@@ -0,0 +1,67 @@
1
+ require 'test_helper'
2
+ require 'date'
3
+
4
+ describe Timberline::Envelope do
5
+ before do
6
+ @envelope = Timberline::Envelope.new
7
+ end
8
+
9
+ describe "newly instantiated" do
10
+ it "raises a MissingContentException when to_s is called because the contents are nil" do
11
+ assert_raises Timberline::MissingContentException do
12
+ @envelope.to_s
13
+ end
14
+ end
15
+
16
+ it "has an empty hash for metadata" do
17
+ assert_equal({}, @envelope.metadata)
18
+ end
19
+
20
+ it "allows for the reading of attributes via method_missing magic" do
21
+ @envelope.metadata["original_queue"] = "test_queue"
22
+ assert_equal "test_queue", @envelope.original_queue
23
+ end
24
+
25
+ it "allows for the setting of attributes via method_missing magic" do
26
+ @envelope.original_queue = "test_queue"
27
+ assert_equal "test_queue", @envelope.metadata["original_queue"]
28
+ end
29
+ end
30
+
31
+ describe "with contents" do
32
+ before do
33
+ @envelope.contents = "Test data"
34
+ end
35
+
36
+ it "returns a JSON string when to_s is called" do
37
+ json_string = @envelope.to_s
38
+ json_data = JSON.parse(json_string)
39
+ assert_equal "Test data", json_data["contents"]
40
+ end
41
+
42
+ it "only includes a 'contents' parameter by default" do
43
+ json_string = @envelope.to_s
44
+ json_data = JSON.parse(json_string)
45
+ assert_equal 1, json_data.keys.size
46
+ end
47
+
48
+ it "also includes metadata, if provided" do
49
+ time = DateTime.now
50
+ time_s = DateTime.now.to_s
51
+ @envelope.first_posted = time
52
+ @envelope.origin_queue = "test_queue"
53
+
54
+ json_string = @envelope.to_s
55
+ json_data = JSON.parse(json_string)
56
+ assert_equal "test_queue", json_data["origin_queue"]
57
+ assert_equal time_s, json_data["first_posted"]
58
+ end
59
+
60
+ it "parses itself back correctly using from_json" do
61
+ json_string = @envelope.to_s
62
+ new_envelope = Timberline::Envelope.from_json(json_string)
63
+ assert_equal @envelope.contents, new_envelope.contents
64
+ assert_equal @envelope.metadata, new_envelope.metadata
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,75 @@
1
+ require 'test_helper'
2
+
3
+ describe Timberline::Queue do
4
+ describe "newly instantiated" do
5
+ before do
6
+ clear_test_db
7
+ @queue = Timberline::Queue.new("test_queue")
8
+ end
9
+
10
+ it "saves the passed-in string as its queue name" do
11
+ assert_equal "test_queue", @queue.queue_name
12
+ end
13
+
14
+ it "has a length of 0" do
15
+ assert_equal 0, @queue.length
16
+ end
17
+
18
+ it "has a default read_timeout of 0" do
19
+ assert_equal 0, @queue.read_timeout
20
+ end
21
+
22
+ it "responds nil to a pop request after the read_timeout occurs" do
23
+ # Let's set the read_timeout to 1 in order for this test to return
24
+ @queue.instance_variable_set("@read_timeout", 1)
25
+ assert_equal nil, @queue.pop
26
+ end
27
+
28
+ it "puts an item on the queue when that item is pushed" do
29
+ test_item = "Test Queue Item"
30
+ assert_equal 1, @queue.push(test_item)
31
+ end
32
+
33
+ it "wraps an item in an envelope when that item is pushed" do
34
+ test_item = "Test Queue Item"
35
+ assert_equal 1, @queue.push(test_item)
36
+ data = @queue.pop
37
+ assert_kind_of Timberline::Envelope, data
38
+ assert_equal test_item, data.contents
39
+ end
40
+
41
+ it "doesn't wrap an envelope that gets pushed in another envelope" do
42
+ test_item = "Test Queue Item"
43
+ env = Timberline::Envelope.new
44
+ env.contents = test_item
45
+ assert_equal 1, @queue.push(env)
46
+ data = @queue.pop
47
+ assert_kind_of Timberline::Envelope, data
48
+ assert_equal test_item, data.contents
49
+ end
50
+ end
51
+
52
+ describe "with one item" do
53
+ before do
54
+ clear_test_db
55
+ @test_item = "Test Queue Item"
56
+ @queue = Timberline::Queue.new("test_queue")
57
+ @queue.push(@test_item)
58
+ end
59
+
60
+ it "has a length of 1" do
61
+ assert_equal 1, @queue.length
62
+ end
63
+
64
+ it "responds to pop with the one item" do
65
+ assert_equal @test_item, @queue.pop.contents
66
+ end
67
+
68
+ it "responds nil to a second pop" do
69
+ # Let's set the read_timeout to 1 in order for this test to return
70
+ @queue.instance_variable_set("@read_timeout", 1)
71
+ assert_equal @test_item, @queue.pop.contents
72
+ assert_equal nil, @queue.pop
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,154 @@
1
+ require 'test_helper'
2
+
3
+ describe Timberline::QueueManager do
4
+ describe "newly instantiated" do
5
+ before do
6
+ clear_test_db
7
+ @qm = Timberline::QueueManager.new
8
+ end
9
+
10
+ it "has an empty queue list" do
11
+ assert_equal 0, @qm.queue_list.size
12
+ end
13
+
14
+ it "instantiates an error_queue" do
15
+ assert_kind_of Timberline::Queue, @qm.error_queue
16
+ assert_equal "timberline_errors", @qm.error_queue.queue_name
17
+ end
18
+
19
+ it "creates a new queue when asked if it doesn't exist" do
20
+ queue = @qm.queue("test_queue")
21
+ assert_kind_of Timberline::Queue, queue
22
+ assert_equal 1, @qm.queue_list.size
23
+ assert_equal queue, @qm.queue_list["test_queue"]
24
+ end
25
+
26
+ it "doesn't create a new queue if a queue by the same name already exists" do
27
+ queue = @qm.queue("test_queue")
28
+ new_queue = @qm.queue("test_queue")
29
+ assert_equal queue, new_queue
30
+ assert_equal 1, @qm.queue_list.size
31
+ end
32
+
33
+ it "creates a new queue as necessary when 'push' is called and pushes the item" do
34
+ @qm.push("test_queue", "Howdy kids.")
35
+ assert_equal 1, @qm.queue_list.size
36
+ queue = @qm.queue("test_queue")
37
+ assert_equal 1, queue.length
38
+ assert_equal "Howdy kids.", queue.pop.contents
39
+ end
40
+
41
+ it "uses any passed-in metadata to push when building the envelope" do
42
+ @qm.push("test_queue", "Howdy kids.", { :special_notes => "Super-awesome."})
43
+ queue = @qm.queue("test_queue")
44
+ assert_equal 1, queue.length
45
+ data = queue.pop
46
+ assert_equal "Howdy kids.", data.contents
47
+ assert_equal "Super-awesome.", data.special_notes
48
+ end
49
+
50
+ it "includes some default information in the metadata" do
51
+ @qm.push("test_queue", "Howdy kids.")
52
+ queue = @qm.queue("test_queue")
53
+ data = queue.pop
54
+ assert_equal "Howdy kids.", data.contents
55
+ assert_kind_of DateTime, DateTime.parse(data.submitted_at)
56
+ assert_equal "test_queue", data.origin_queue
57
+ assert_equal 0, data.retries
58
+ assert_equal 1, data.job_id
59
+ end
60
+
61
+ it "allows you to retry a job that has failed" do
62
+ @qm.push("test_queue", "Howdy kids.")
63
+ queue = @qm.queue("test_queue")
64
+ data = queue.pop
65
+
66
+ assert_equal 0, queue.length
67
+
68
+ @qm.retry_job(data)
69
+
70
+ assert_equal 1, queue.length
71
+
72
+ data = queue.pop
73
+ assert_equal 1, data.retries
74
+ assert_kind_of DateTime, DateTime.parse(data.last_tried_at)
75
+ end
76
+
77
+ it "will continue retrying until we pass the max retries (defaults to 5)" do
78
+ @qm.push("test_queue", "Howdy kids.")
79
+ queue = @qm.queue("test_queue")
80
+ data = queue.pop
81
+
82
+ 5.times do |i|
83
+ @qm.retry_job(data)
84
+ assert_equal 1, queue.length
85
+ data = queue.pop
86
+ assert_equal i + 1, data.retries
87
+ end
88
+
89
+ @qm.retry_job(data)
90
+ assert_equal 0, queue.length
91
+ assert_equal 1, @qm.error_queue.length
92
+ end
93
+
94
+ it "will allow you to directly error out a job" do
95
+ @qm.push("test_queue", "Howdy kids.")
96
+ queue = @qm.queue("test_queue")
97
+ data = queue.pop
98
+
99
+ @qm.error_job(data)
100
+ assert_equal 1, @qm.error_queue.length
101
+ data = @qm.error_queue.pop
102
+ assert_kind_of DateTime, DateTime.parse(data.fatal_error_at)
103
+ end
104
+
105
+ it "will allow you to watch a queue" do
106
+ @qm.push("test_queue", "Howdy kids.")
107
+
108
+ assert_raises RuntimeError do
109
+ @qm.watch "test_queue" do |job|
110
+ if "Howdy kids." == job.contents
111
+ raise "This works."
112
+ end
113
+ end
114
+ end
115
+
116
+ assert_equal 0, @qm.queue("test_queue").length
117
+ end
118
+
119
+ it "will allow you to retry from a queue watcher" do
120
+ @qm.push("test_queue", "Howdy kids.")
121
+
122
+ assert_raises RuntimeError do
123
+ @qm.watch "test_queue" do |job|
124
+ if "Howdy kids." == job.contents
125
+ retry_job job
126
+ raise "Job retried."
127
+ end
128
+ end
129
+ end
130
+
131
+ assert_equal 1, @qm.queue("test_queue").length
132
+ data = @qm.queue("test_queue").pop
133
+ assert_equal 1, data.retries
134
+ end
135
+
136
+ it "will allow you to error from a queue watcher" do
137
+ @qm.push("test_queue", "Howdy kids.")
138
+
139
+ assert_raises RuntimeError do
140
+ @qm.watch "test_queue" do |job|
141
+ if "Howdy kids." == job.contents
142
+ error_job job
143
+ raise "Job errored."
144
+ end
145
+ end
146
+ end
147
+
148
+ assert_equal 1, @qm.error_queue.length
149
+ data = @qm.error_queue.pop
150
+ assert_kind_of DateTime, DateTime.parse(data.fatal_error_at)
151
+ end
152
+ end
153
+
154
+ end