arborist 0.0.1.pre20160106113421

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 (74) hide show
  1. checksums.yaml +7 -0
  2. data/.document +4 -0
  3. data/.simplecov +9 -0
  4. data/ChangeLog +417 -0
  5. data/Events.md +20 -0
  6. data/History.md +4 -0
  7. data/LICENSE +29 -0
  8. data/Manifest.txt +72 -0
  9. data/Monitors.md +141 -0
  10. data/Nodes.md +0 -0
  11. data/Observers.md +72 -0
  12. data/Protocol.md +214 -0
  13. data/README.md +75 -0
  14. data/Rakefile +81 -0
  15. data/TODO.md +24 -0
  16. data/bin/amanagerd +10 -0
  17. data/bin/amonitord +12 -0
  18. data/bin/aobserverd +12 -0
  19. data/lib/arborist.rb +182 -0
  20. data/lib/arborist/client.rb +191 -0
  21. data/lib/arborist/event.rb +61 -0
  22. data/lib/arborist/event/node_acked.rb +18 -0
  23. data/lib/arborist/event/node_delta.rb +20 -0
  24. data/lib/arborist/event/node_matching.rb +34 -0
  25. data/lib/arborist/event/node_update.rb +19 -0
  26. data/lib/arborist/event/sys_reloaded.rb +15 -0
  27. data/lib/arborist/exceptions.rb +21 -0
  28. data/lib/arborist/manager.rb +508 -0
  29. data/lib/arborist/manager/event_publisher.rb +97 -0
  30. data/lib/arborist/manager/tree_api.rb +207 -0
  31. data/lib/arborist/mixins.rb +363 -0
  32. data/lib/arborist/monitor.rb +377 -0
  33. data/lib/arborist/monitor/socket.rb +163 -0
  34. data/lib/arborist/monitor_runner.rb +217 -0
  35. data/lib/arborist/node.rb +700 -0
  36. data/lib/arborist/node/host.rb +87 -0
  37. data/lib/arborist/node/root.rb +60 -0
  38. data/lib/arborist/node/service.rb +112 -0
  39. data/lib/arborist/observer.rb +176 -0
  40. data/lib/arborist/observer/action.rb +125 -0
  41. data/lib/arborist/observer/summarize.rb +105 -0
  42. data/lib/arborist/observer_runner.rb +181 -0
  43. data/lib/arborist/subscription.rb +82 -0
  44. data/spec/arborist/client_spec.rb +282 -0
  45. data/spec/arborist/event/node_update_spec.rb +71 -0
  46. data/spec/arborist/event_spec.rb +64 -0
  47. data/spec/arborist/manager/event_publisher_spec.rb +66 -0
  48. data/spec/arborist/manager/tree_api_spec.rb +458 -0
  49. data/spec/arborist/manager_spec.rb +442 -0
  50. data/spec/arborist/mixins_spec.rb +195 -0
  51. data/spec/arborist/monitor/socket_spec.rb +195 -0
  52. data/spec/arborist/monitor_runner_spec.rb +152 -0
  53. data/spec/arborist/monitor_spec.rb +251 -0
  54. data/spec/arborist/node/host_spec.rb +104 -0
  55. data/spec/arborist/node/root_spec.rb +29 -0
  56. data/spec/arborist/node/service_spec.rb +98 -0
  57. data/spec/arborist/node_spec.rb +552 -0
  58. data/spec/arborist/observer/action_spec.rb +205 -0
  59. data/spec/arborist/observer/summarize_spec.rb +294 -0
  60. data/spec/arborist/observer_spec.rb +146 -0
  61. data/spec/arborist/subscription_spec.rb +71 -0
  62. data/spec/arborist_spec.rb +146 -0
  63. data/spec/data/monitors/pings.rb +80 -0
  64. data/spec/data/monitors/port_checks.rb +27 -0
  65. data/spec/data/monitors/system_resources.rb +30 -0
  66. data/spec/data/monitors/web_services.rb +17 -0
  67. data/spec/data/nodes/duir.rb +20 -0
  68. data/spec/data/nodes/localhost.rb +15 -0
  69. data/spec/data/nodes/sidonie.rb +29 -0
  70. data/spec/data/nodes/yevaud.rb +26 -0
  71. data/spec/data/observers/auditor.rb +23 -0
  72. data/spec/data/observers/webservices.rb +18 -0
  73. data/spec/spec_helper.rb +117 -0
  74. metadata +368 -0
@@ -0,0 +1,442 @@
1
+ #!/usr/bin/env rspec -cfd
2
+
3
+ require_relative '../spec_helper'
4
+
5
+ require 'timecop'
6
+ require 'arborist/manager'
7
+
8
+
9
+ describe Arborist::Manager do
10
+
11
+ after( :each ) do
12
+ Arborist::Node::Root.reset
13
+ end
14
+
15
+
16
+ let( :manager ) { described_class.new }
17
+
18
+
19
+
20
+ #
21
+ # Examples
22
+ #
23
+
24
+ it "starts with a root node" do
25
+ expect( described_class.new.root ).to be_a( Arborist::Node )
26
+ end
27
+
28
+
29
+ it "starts with a node registry with the root node and itself" do
30
+ result = manager.nodes
31
+ expect( result ).to include( '_' )
32
+ expect( result['_'] ).to be( manager.root )
33
+ end
34
+
35
+
36
+ it "knows how long it has been running" do
37
+ Timecop.freeze do
38
+ manager.start_time = Time.now
39
+
40
+ Timecop.travel( 10 ) do
41
+ expect( manager.uptime ).to be_within( 1 ).of( 10 )
42
+ end
43
+ end
44
+ end
45
+
46
+
47
+ it "has an uptime of 0 if it hasn't yet been started" do
48
+ expect( manager.uptime ).to eq( 0 )
49
+ end
50
+
51
+
52
+ context "a new empty manager" do
53
+
54
+ let( :node ) do
55
+ testing_node 'italian_lessons'
56
+ end
57
+ let( :node2 ) do
58
+ testing_node 'french_laundry'
59
+ end
60
+ let( :node3 ) do
61
+ testing_node 'german_oak_cats'
62
+ end
63
+
64
+
65
+ it "has a nodecount of 1" do
66
+ expect( manager.nodecount ).to eq( 1 )
67
+ end
68
+
69
+
70
+ it "can have a node added to it" do
71
+ manager.add_node( node )
72
+ expect( manager.nodes ).to include( 'italian_lessons' )
73
+ expect( manager.nodes['italian_lessons'] ).to be( node )
74
+ expect( manager.nodecount ).to eq( 2 )
75
+ expect( manager.nodelist ).to include( '_', 'italian_lessons' )
76
+ end
77
+
78
+
79
+ it "can load its tree from an Enumerator that yields nodes" do
80
+ manager.load_tree([ node, node2, node3 ])
81
+ expect( manager.nodes ).to include( 'italian_lessons', 'french_laundry', 'german_oak_cats' )
82
+ expect( manager.nodes['italian_lessons'] ).to be( node )
83
+ expect( manager.nodes['french_laundry'] ).to be( node2 )
84
+ expect( manager.nodes['german_oak_cats'] ).to be( node3 )
85
+ expect( manager.nodecount ).to eq( 4 )
86
+ expect( manager.nodelist ).to include(
87
+ '_', 'italian_lessons', 'french_laundry', 'german_oak_cats'
88
+ )
89
+ end
90
+
91
+
92
+ it "can replace an existing node" do
93
+ manager.add_node( node )
94
+ another_node = testing_node( 'italian_lessons' )
95
+ manager.add_node( another_node )
96
+
97
+ expect( manager.nodes ).to include( 'italian_lessons' )
98
+ expect( manager.nodes['italian_lessons'] ).to_not be( node )
99
+ expect( manager.nodes['italian_lessons'] ).to be( another_node )
100
+
101
+ expect( manager.nodecount ).to eq( 2 )
102
+ expect( manager.nodelist ).to include( '_', 'italian_lessons' )
103
+ end
104
+
105
+
106
+ it "can have a node removed from it" do
107
+ manager.add_node( node )
108
+ deleted_node = manager.remove_node( 'italian_lessons' )
109
+
110
+ expect( deleted_node ).to be( node )
111
+ expect( manager.nodes ).to_not include( 'italian_lessons' )
112
+
113
+ expect( manager.nodecount ).to eq( 1 )
114
+ expect( manager.nodelist ).to include( '_' )
115
+ end
116
+
117
+
118
+ it "disallows removal of operational nodes" do
119
+ expect {
120
+ manager.remove_node('_')
121
+ }.to raise_error( /can't remove an operational node/i )
122
+ end
123
+
124
+ end
125
+
126
+
127
+ context "a manager with some loaded nodes" do
128
+
129
+ let( :trunk_node ) do
130
+ testing_node( 'trunk' )
131
+ end
132
+ let( :branch_node ) do
133
+ testing_node( 'branch', 'trunk' )
134
+ end
135
+ let( :leaf_node ) do
136
+ testing_node( 'leaf', 'branch' )
137
+ end
138
+
139
+ let( :manager ) do
140
+ instance = described_class.new
141
+ instance.load_tree([ branch_node, leaf_node, trunk_node ])
142
+ instance
143
+ end
144
+
145
+
146
+ it "has a tree built out of its nodes" do
147
+ expect( manager.root ).to have_children
148
+ end
149
+
150
+
151
+ it "knows what nodes have been loaded" do
152
+ expect( manager.nodelist ).to include( 'trunk', 'branch', 'leaf' )
153
+ end
154
+
155
+
156
+ it "errors if any of its nodes are missing their parent" do
157
+ manager = described_class.new
158
+ orphan = testing_node( 'orphan' ) do
159
+ parent 'daddy_warbucks'
160
+ end
161
+
162
+ expect {
163
+ manager.load_tree([ orphan ])
164
+ }.to raise_error( /no parent 'daddy_warbucks' node loaded for/i )
165
+ end
166
+
167
+
168
+ it "grafts a node into the tree when one with a previously unknown identifier is added" do
169
+ new_node = testing_node( 'new' ) do
170
+ parent 'branch'
171
+ end
172
+
173
+ manager.add_node( new_node )
174
+ expect( manager.nodes['branch'].children ).to include( 'new' )
175
+ end
176
+
177
+
178
+ it "replaces a node in the tree when a node with an existing identifier is added" do
179
+ updated_node = testing_node( 'leaf' ) do
180
+ parent 'trunk'
181
+ end
182
+
183
+ manager.add_node( updated_node )
184
+ expect( manager.nodes['branch'].children ).to_not include( 'leaf' => leaf_node )
185
+ expect( manager.nodes['trunk'].children ).to include( 'leaf' => updated_node )
186
+ end
187
+
188
+
189
+ it "rebuilds the tree when a node is removed from it" do
190
+ manager.remove_node( 'branch' )
191
+
192
+ expect( manager.nodes['trunk'].children ).to_not include( 'branch' )
193
+ expect( manager.nodes ).to_not include( 'branch' )
194
+ expect( manager.nodes ).to_not include( 'leaf' )
195
+ end
196
+
197
+ end
198
+
199
+
200
+ describe "tree traversal" do
201
+
202
+ let( :tree ) do
203
+ # router
204
+ # host_a host_b host_c
205
+ # www smtp imap www nfs ssh www
206
+
207
+ [
208
+ testing_node( 'router' ),
209
+ testing_node( 'host_a', 'router' ),
210
+ testing_node( 'host_a_www', 'host_a' ),
211
+ testing_node( 'host_a_smtp', 'host_a' ),
212
+ testing_node( 'host_a_imap', 'host_a' ),
213
+ testing_node( 'host_b', 'router' ),
214
+ testing_node( 'host_b_www', 'host_b' ),
215
+ testing_node( 'host_b_nfs', 'host_b' ),
216
+ testing_node( 'host_b_ssh', 'host_b' ),
217
+ testing_node( 'host_c', 'router' ),
218
+ testing_node( 'host_c_www', 'host_c' ),
219
+ ]
220
+ end
221
+
222
+ let( :manager ) do
223
+ instance = described_class.new
224
+ instance.load_tree( tree )
225
+ instance
226
+ end
227
+
228
+
229
+ it "can traverse all nodes in its node tree" do
230
+ iter = manager.all_nodes
231
+ expect( iter ).to be_a( Enumerator )
232
+ expect( iter.to_a ).to eq( [manager.root] + tree )
233
+ end
234
+
235
+
236
+ it "can traverse all nodes whose status is 'up'" do
237
+ manager.nodes.each {|_, node| node.status = :up }
238
+ manager.nodes[ 'host_a' ].update( error: "ping failed" )
239
+ expect( manager.nodes[ 'host_a' ] ).to be_down
240
+ manager.nodes[ 'host_c' ].update( error: "gamma rays" )
241
+ expect( manager.nodes[ 'host_c' ] ).to be_down
242
+
243
+ iter = manager.reachable_nodes
244
+
245
+ expect( iter ).to be_a( Enumerator )
246
+
247
+ nodes = iter.to_a
248
+ expect( nodes.size ).to eq( 6 )
249
+ expect( nodes.map(&:identifier) ).to include(
250
+ "_",
251
+ "router",
252
+ "host_b",
253
+ "host_b_www",
254
+ "host_b_nfs",
255
+ "host_b_ssh"
256
+ )
257
+ end
258
+
259
+
260
+ it "can create an Enumerator for all of a node's parents from leaf to root"
261
+
262
+ end
263
+
264
+
265
+ describe "node updates and events" do
266
+
267
+ let( :tree ) do
268
+ # router
269
+ # host_a host_b host_c
270
+ # www smtp imap www nfs ssh www
271
+
272
+ [
273
+ testing_node( 'router' ),
274
+ testing_node( 'host_a', 'router' ),
275
+ testing_node( 'host_a_www', 'host_a' ),
276
+ testing_node( 'host_a_smtp', 'host_a' ),
277
+ testing_node( 'host_a_imap', 'host_a' ),
278
+ testing_node( 'host_b', 'router' ),
279
+ testing_node( 'host_b_www', 'host_b' ),
280
+ testing_node( 'host_b_nfs', 'host_b' ),
281
+ testing_node( 'host_b_ssh', 'host_b' ),
282
+ testing_node( 'host_c', 'router' ),
283
+ testing_node( 'host_c_www', 'host_c' ),
284
+ ]
285
+ end
286
+
287
+ let( :manager ) do
288
+ instance = described_class.new
289
+ instance.load_tree( tree )
290
+ instance
291
+ end
292
+
293
+
294
+ it "can fetch a Hash of node states" do
295
+ states = manager.fetch_matching_node_states( {}, [] )
296
+ expect( states.size ).to eq( manager.nodes.size )
297
+ expect( states ).to include( 'host_b_nfs', 'host_c', 'router' )
298
+ expect( states['host_b_nfs'] ).to be_a( Hash )
299
+ expect( states['host_c'] ).to be_a( Hash )
300
+ expect( states['router'] ).to be_a( Hash )
301
+ end
302
+
303
+
304
+ it "can update an event by identifier" do
305
+ manager.update_node( 'host_b_www', http: { status: 200 } )
306
+ expect(
307
+ manager.nodes['host_b_www'].properties
308
+ ).to include( 'http' => { 'status' => 200 } )
309
+ end
310
+
311
+
312
+ it "ignores updates to an identifier that is not (any longer) in the tree" do
313
+ expect {
314
+ manager.update_node( 'host_y', asset_tag: '2by-n86y7t' )
315
+ }.to_not raise_error
316
+ end
317
+
318
+
319
+ it "propagates events from an update up the node tree" do
320
+ expect( manager.root ).to receive( :publish_events ).
321
+ at_least( :once ).
322
+ and_call_original
323
+ expect( manager.nodes['host_c'] ).to receive( :publish_events ).
324
+ at_least( :once ).
325
+ and_call_original
326
+ manager.update_node( 'host_c_www', response_status: 504, error: 'Timeout talking to web service.' )
327
+ end
328
+
329
+
330
+ it "only propagates events to a node's ancestors" do
331
+ expect( manager.root ).to receive( :publish_events ).
332
+ at_least( :once ).
333
+ and_call_original
334
+ expect( manager.nodes['host_c'] ).to_not receive( :publish_events )
335
+
336
+ manager.update_node( 'host_b_www', response_status: 504, error: 'Timeout talking to web service.' )
337
+ end
338
+
339
+ end
340
+
341
+
342
+ describe "subscriptions" do
343
+
344
+ let( :tree ) {[ testing_node('host_c') ]}
345
+ let( :manager ) do
346
+ instance = described_class.new
347
+ instance.load_tree( tree )
348
+ instance
349
+ end
350
+
351
+
352
+ it "can attach subscriptions to a node by its identifier" do
353
+ sub = subid = nil
354
+ expect {
355
+ sub = manager.create_subscription( 'host_c', 'node.update', type: 'host' )
356
+ }.to change { manager.subscriptions.size }.by( 1 )
357
+
358
+ node = manager.subscriptions[ sub.id ]
359
+
360
+ expect( sub ).to be_a( Arborist::Subscription )
361
+ expect( node ).to be( manager.nodes['host_c'] )
362
+ end
363
+
364
+
365
+ it "can detach subscriptions from a node given the subscription ID" do
366
+ sub = manager.create_subscription( 'host_c', 'node.ack', type: 'service' )
367
+ rval = nil
368
+
369
+ expect {
370
+ rval = manager.remove_subscription( sub.id )
371
+ }.to change { manager.subscriptions.size }.by( -1 ).and(
372
+ change { manager.nodes['host_c'].subscriptions.size }.by( -1 )
373
+ )
374
+
375
+ expect( rval ).to be( sub )
376
+ end
377
+
378
+ end
379
+
380
+
381
+ describe "sockets" do
382
+
383
+ let( :zmq_context ) { Arborist.zmq_context }
384
+ let( :zmq_loop ) { instance_double(ZMQ::Loop) }
385
+ let( :tree_sock ) { instance_double(ZMQ::Socket::Rep, "tree API socket") }
386
+ let( :event_sock ) { instance_double(ZMQ::Socket::Pub, "event socket") }
387
+ let( :tree_pollitem ) { instance_double(ZMQ::Pollitem, "tree API pollitem") }
388
+ let( :event_pollitem ) { instance_double(ZMQ::Pollitem, "event API pollitem") }
389
+ let( :signal_timer ) { instance_double(ZMQ::Timer, "signal timer") }
390
+
391
+ before( :each ) do
392
+ allow( ZMQ::Loop ).to receive( :new ).and_return( zmq_loop )
393
+
394
+ allow( zmq_context ).to receive( :socket ).with( :REP ).and_return( tree_sock )
395
+ allow( zmq_context ).to receive( :socket ).with( :PUB ).and_return( event_sock )
396
+
397
+ allow( zmq_loop ).to receive( :remove ).with( tree_pollitem )
398
+ allow( zmq_loop ).to receive( :remove ).with( event_pollitem )
399
+
400
+ allow( tree_pollitem ).to receive( :pollable ).and_return( tree_sock )
401
+ allow( tree_sock ).to receive( :close )
402
+ allow( event_pollitem ).to receive( :pollable ).and_return( event_sock )
403
+ allow( event_sock ).to receive( :close )
404
+ end
405
+
406
+
407
+
408
+ it "sets up its sockets with handlers and starts the ZMQ loop when started" do
409
+ expect( tree_sock ).to receive( :bind ).with( Arborist.tree_api_url )
410
+ expect( tree_sock ).to receive( :linger= ).with( 0 )
411
+
412
+ expect( event_sock ).to receive( :bind ).with( Arborist.event_api_url )
413
+ expect( event_sock ).to receive( :linger= ).with( 0 )
414
+
415
+ expect( ZMQ::Pollitem ).to receive( :new ).with( tree_sock, ZMQ::POLLIN|ZMQ::POLLOUT ).
416
+ and_return( tree_pollitem )
417
+ expect( ZMQ::Pollitem ).to receive( :new ).with( event_sock, ZMQ::POLLOUT ).
418
+ and_return( event_pollitem )
419
+
420
+ expect( tree_pollitem ).to receive( :handler= ).
421
+ with( an_instance_of(Arborist::Manager::TreeAPI) )
422
+ expect( zmq_loop ).to receive( :register ).with( tree_pollitem )
423
+ expect( event_pollitem ).to receive( :handler= ).
424
+ with( an_instance_of(Arborist::Manager::EventPublisher) )
425
+ expect( zmq_loop ).to receive( :register ).with( event_pollitem )
426
+
427
+ expect( ZMQ::Timer ).to receive( :new ).
428
+ with( described_class::SIGNAL_INTERVAL, 0, manager.method(:process_signal_queue) ).
429
+ and_return( signal_timer )
430
+ expect( zmq_loop ).to receive( :register_timer ).with( signal_timer )
431
+ expect( zmq_loop ).to receive( :start )
432
+
433
+ expect( zmq_loop ).to receive( :remove ).with( tree_pollitem )
434
+ expect( zmq_loop ).to receive( :remove ).with( event_pollitem )
435
+
436
+ manager.run
437
+ end
438
+ end
439
+
440
+
441
+ end
442
+
@@ -0,0 +1,195 @@
1
+ #!/usr/bin/env rspec -cfd
2
+
3
+ require_relative '../spec_helper'
4
+
5
+ require 'timecop'
6
+
7
+ require 'arborist/mixins'
8
+
9
+
10
+ describe Arborist, "mixins" do
11
+
12
+ describe Arborist::MethodUtilities, 'used to extend a class' do
13
+
14
+ let!( :extended_class ) do
15
+ klass = Class.new
16
+ klass.extend( Arborist::MethodUtilities )
17
+ klass
18
+ end
19
+
20
+ it "can declare a class-level attribute reader" do
21
+ extended_class.singleton_attr_reader :foo
22
+ expect( extended_class ).to respond_to( :foo )
23
+ expect( extended_class ).to_not respond_to( :foo= )
24
+ expect( extended_class ).to_not respond_to( :foo? )
25
+ end
26
+
27
+ it "can declare a class-level attribute writer" do
28
+ extended_class.singleton_attr_writer :foo
29
+ expect( extended_class ).to_not respond_to( :foo )
30
+ expect( extended_class ).to respond_to( :foo= )
31
+ expect( extended_class ).to_not respond_to( :foo? )
32
+ end
33
+
34
+ it "can declare a class-level attribute reader and writer" do
35
+ extended_class.singleton_attr_accessor :foo
36
+ expect( extended_class ).to respond_to( :foo )
37
+ expect( extended_class ).to respond_to( :foo= )
38
+ expect( extended_class ).to_not respond_to( :foo? )
39
+ end
40
+
41
+ it "can declare a class-level alias" do
42
+ def extended_class.foo
43
+ return "foo"
44
+ end
45
+ extended_class.singleton_method_alias( :bar, :foo )
46
+
47
+ expect( extended_class.bar ).to eq( 'foo' )
48
+ end
49
+
50
+ it "can declare an instance attribute predicate method" do
51
+ extended_class.attr_predicate :foo
52
+ instance = extended_class.new
53
+
54
+ expect( instance ).to_not respond_to( :foo )
55
+ expect( instance ).to_not respond_to( :foo= )
56
+ expect( instance ).to respond_to( :foo? )
57
+
58
+ expect( instance.foo? ).to be_falsey
59
+
60
+ instance.instance_variable_set( :@foo, 1 )
61
+ expect( instance.foo? ).to be_truthy
62
+ end
63
+
64
+ it "can declare an instance attribute predicate and writer" do
65
+ extended_class.attr_predicate_accessor :foo
66
+ instance = extended_class.new
67
+
68
+ expect( instance ).to_not respond_to( :foo )
69
+ expect( instance ).to respond_to( :foo= )
70
+ expect( instance ).to respond_to( :foo? )
71
+
72
+ expect( instance.foo? ).to be_falsey
73
+
74
+ instance.foo = 1
75
+ expect( instance.foo? ).to be_truthy
76
+ end
77
+
78
+ it "can declare a class-level attribute predicate and writer" do
79
+ extended_class.singleton_predicate_accessor :foo
80
+ expect( extended_class ).to_not respond_to( :foo )
81
+ expect( extended_class ).to respond_to( :foo= )
82
+ expect( extended_class ).to respond_to( :foo? )
83
+ end
84
+
85
+ it "can declare a class-level predicate method" do
86
+ extended_class.singleton_predicate_reader :foo
87
+ expect( extended_class ).to_not respond_to( :foo )
88
+ expect( extended_class ).to_not respond_to( :foo= )
89
+ expect( extended_class ).to respond_to( :foo? )
90
+ end
91
+
92
+ end
93
+
94
+
95
+ describe Arborist::TimeRefinements do
96
+
97
+ using( described_class )
98
+
99
+ context "used to extend Time objects" do
100
+
101
+ it "makes them aware of whether they're in the future or not" do
102
+ Timecop.freeze do
103
+ time = Time.now
104
+ expect( time.future? ).to be_falsey
105
+
106
+ future_time = time + 1
107
+ expect( future_time.future? ).to be_truthy
108
+
109
+ past_time = time - 1
110
+ expect( past_time.future? ).to be_falsey
111
+ end
112
+ end
113
+
114
+
115
+ it "makes them aware of whether they're in the past or not" do
116
+ Timecop.freeze do
117
+ time = Time.now
118
+ expect( time.past? ).to be_falsey
119
+
120
+ future_time = time + 1
121
+ expect( future_time.past? ).to be_falsey
122
+
123
+ past_time = time - 1
124
+ expect( past_time.past? ).to be_truthy
125
+ end
126
+ end
127
+
128
+
129
+ it "adds the ability to express themselves as an offset in English" do
130
+ Timecop.freeze do
131
+ expect( 1.second.ago.as_delta ).to eq( 'less than a minute ago' )
132
+ expect( 1.second.from_now.as_delta ).to eq( 'less than a minute from now' )
133
+
134
+ expect( 1.minute.ago.as_delta ).to eq( 'a minute ago' )
135
+ expect( 1.minute.from_now.as_delta ).to eq( 'a minute from now' )
136
+ expect( 68.seconds.ago.as_delta ).to eq( 'a minute ago' )
137
+ expect( 68.seconds.from_now.as_delta ).to eq( 'a minute from now' )
138
+ expect( 2.minutes.ago.as_delta ).to eq( '2 minutes ago' )
139
+ expect( 2.minutes.from_now.as_delta ).to eq( '2 minutes from now' )
140
+ expect( 38.minutes.ago.as_delta ).to eq( '38 minutes ago' )
141
+ expect( 38.minutes.from_now.as_delta ).to eq( '38 minutes from now' )
142
+
143
+ expect( 1.hour.ago.as_delta ).to eq( 'about an hour ago' )
144
+ expect( 1.hour.from_now.as_delta ).to eq( 'about an hour from now' )
145
+ expect( 75.minutes.ago.as_delta ).to eq( 'about an hour ago' )
146
+ expect( 75.minutes.from_now.as_delta ).to eq( 'about an hour from now' )
147
+
148
+ expect( 2.hours.ago.as_delta ).to eq( '2 hours ago' )
149
+ expect( 2.hours.from_now.as_delta ).to eq( '2 hours from now' )
150
+ expect( 14.hours.ago.as_delta ).to eq( '14 hours ago' )
151
+ expect( 14.hours.from_now.as_delta ).to eq( '14 hours from now' )
152
+
153
+ expect( 22.hours.ago.as_delta ).to eq( 'about a day ago' )
154
+ expect( 22.hours.from_now.as_delta ).to eq( 'about a day from now' )
155
+ expect( 28.hours.ago.as_delta ).to eq( 'about a day ago' )
156
+ expect( 28.hours.from_now.as_delta ).to eq( 'about a day from now' )
157
+
158
+ expect( 36.hours.ago.as_delta ).to eq( '2 days ago' )
159
+ expect( 36.hours.from_now.as_delta ).to eq( '2 days from now' )
160
+ expect( 4.days.ago.as_delta ).to eq( '4 days ago' )
161
+ expect( 4.days.from_now.as_delta ).to eq( '4 days from now' )
162
+
163
+ expect( 1.week.ago.as_delta ).to eq( 'about a week ago' )
164
+ expect( 1.week.from_now.as_delta ).to eq( 'about a week from now' )
165
+ expect( 8.days.ago.as_delta ).to eq( 'about a week ago' )
166
+ expect( 8.days.from_now.as_delta ).to eq( 'about a week from now' )
167
+
168
+ expect( 15.days.ago.as_delta ).to eq( '2 weeks ago' )
169
+ expect( 15.days.from_now.as_delta ).to eq( '2 weeks from now' )
170
+ expect( 3.weeks.ago.as_delta ).to eq( '3 weeks ago' )
171
+ expect( 3.weeks.from_now.as_delta ).to eq( '3 weeks from now' )
172
+
173
+ expect( 1.month.ago.as_delta ).to eq( '4 weeks ago' )
174
+ expect( 1.month.from_now.as_delta ).to eq( '4 weeks from now' )
175
+ expect( 36.days.ago.as_delta ).to eq( '5 weeks ago' )
176
+ expect( 36.days.from_now.as_delta ).to eq( '5 weeks from now' )
177
+
178
+ expect( 6.months.ago.as_delta ).to eq( '6 months ago' )
179
+ expect( 6.months.from_now.as_delta ).to eq( '6 months from now' )
180
+ expect( 14.months.ago.as_delta ).to eq( '14 months ago' )
181
+ expect( 14.months.from_now.as_delta ).to eq( '14 months from now' )
182
+
183
+ expect( 6.year.ago.as_delta ).to eq( '6 years ago' )
184
+ expect( 6.year.from_now.as_delta ).to eq( '6 years from now' )
185
+ expect( 14.years.ago.as_delta ).to eq( '14 years ago' )
186
+ expect( 14.years.from_now.as_delta ).to eq( '14 years from now' )
187
+ end
188
+ end
189
+
190
+ end
191
+
192
+ end
193
+
194
+ end
195
+