timberline 0.1.2

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.
@@ -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