]> git.netwichtig.de Git - user/henk/code/ruby/rbot.git/blob - test/test_journal.rb
f1653c164783e35eb3830228aac58531403f337e
[user/henk/code/ruby/rbot.git] / test / test_journal.rb
1 $:.unshift File.join(File.dirname(__FILE__), '../lib')
2
3 require 'test/unit'
4 require 'rbot/ircbot'
5 require 'rbot/journal'
6 require 'rbot/journal/postgres.rb'
7 require 'rbot/journal/mongo.rb'
8
9 require 'benchmark'
10
11 DAY=60*60*24
12
13 class JournalMessageTest < Test::Unit::TestCase
14
15   include Irc::Bot::Journal
16
17   def test_get
18     m = JournalMessage.create('foo', {'bar': 42, 'baz': nil, 'qux': {'quxx': 23}})
19     assert_equal(42, m.get('bar'))
20     assert_raise ArgumentError do
21       m.get('nope')
22     end
23     assert_nil(m.get('nope', nil))
24     assert_nil(m.get('baz'))
25     assert_equal(23, m['qux.quxx'])
26     assert_equal(nil, m['qux.nope'])
27     assert_raise(ArgumentError) { m.get('qux.nope') }
28   end
29
30 end
31
32 class QueryTest < Test::Unit::TestCase
33
34   include Irc::Bot::Journal
35
36   def test_define
37
38     q = Query.define do
39       id 'foo'
40       id 'bar', 'baz'
41       topic 'log.irc.*'
42       topic 'log.core', 'baz'
43       timestamp from: Time.now, to: Time.now + 60 * 10
44       payload 'action': :privmsg, 'alice': 'bob'
45       payload 'channel': '#rbot'
46       payload 'foo.bar': 'baz'
47     end
48     assert_equal(['foo', 'bar', 'baz'], q.id)
49     assert_equal(['log.irc.*', 'log.core', 'baz'], q.topic)
50     assert_equal([:from, :to], q.timestamp.keys)
51     assert_equal(Time, q.timestamp[:to].class)
52     assert_equal(Time, q.timestamp[:from].class)
53     assert_equal({
54       'action': :privmsg, 'alice': 'bob',
55       'channel': '#rbot',
56       'foo.bar': 'baz'
57     }, q.payload)
58
59   end
60
61   def test_topic_matches
62     q = Query.define do
63       topic 'foo'
64     end
65     assert_true(q.topic_matches?('foo'))
66     assert_false(q.topic_matches?('bar'))
67     assert_false(q.topic_matches?('foo.bar'))
68
69     q = Query.define do
70       topic 'foo.bar'
71     end
72     assert_false(q.topic_matches?('foo'))
73     assert_false(q.topic_matches?('bar'))
74     assert_true(q.topic_matches?('foo.bar'))
75
76     q = Query.define do
77       topic 'foo.*'
78     end
79     assert_false(q.topic_matches?('foo'))
80     assert_false(q.topic_matches?('bar'))
81     assert_true(q.topic_matches?('foo.bar'))
82     assert_true(q.topic_matches?('foo.baz'))
83
84     q = Query.define do
85       topic '*.bar'
86     end
87     assert_false(q.topic_matches?('foo'))
88     assert_false(q.topic_matches?('bar'))
89     assert_true(q.topic_matches?('foo.bar'))
90     assert_true(q.topic_matches?('bar.bar'))
91     assert_false(q.topic_matches?('foo.foo'))
92
93     q = Query.define do
94       topic '*.*'
95     end
96     assert_false(q.topic_matches?('foo'))
97     assert_true(q.topic_matches?('foo.bar'))
98
99     q = Query.define do
100       topic 'foo'
101       topic 'bar'
102       topic 'baz.alice.bob.*.foo'
103     end
104     assert_true(q.topic_matches?('foo'))
105     assert_true(q.topic_matches?('bar'))
106     assert_true(q.topic_matches?('baz.alice.bob.asdf.foo'))
107     assert_false(q.topic_matches?('baz.alice.bob..foo'))
108
109   end
110   def test_matches
111     q = Query.define do
112       #id 'foo', 'bar'
113       topic 'log.irc.*', 'log.core'
114       timestamp from: Time.now - DAY, to: Time.now + DAY
115       payload 'action': 'privmsg', 'foo.bar': 'baz'
116     end
117     assert_true(q.matches? JournalMessage.create('log.irc.raw', {'action' => 'privmsg'}))
118     assert_false(q.matches? JournalMessage.create('baz', {}))
119     assert_true(q.matches? JournalMessage.create('log.core', {foo: {bar: 'baz'}}))
120
121     # tests timestamp from/to:
122     assert_true(q.matches? JournalMessage.new(
123       id: 'foo',
124       topic: 'log.core',
125       timestamp: Time.now,
126       payload: {action: 'privmsg'}))
127     assert_false(q.matches? JournalMessage.new(
128       id: 'foo',
129       topic: 'log.core',
130       timestamp: Time.now - DAY*3,
131       payload: {action: 'privmsg'}))
132     assert_false(q.matches? JournalMessage.new(
133       id: 'foo',
134       topic: 'log.core',
135       timestamp: Time.now + DAY*3,
136       payload: {action: 'privmsg'}))
137   end
138
139 end
140
141 class JournalBrokerTest < Test::Unit::TestCase
142
143   include Irc::Bot::Journal
144
145   def test_publish
146     received = []
147     journal = JournalBroker.new(consumer: Proc.new { |message|
148       received << message
149     })
150
151     # publish some messages:
152     journal.publish 'log.irc',
153       source: 'alice', message: '<3 pg'
154     journal.publish 'log.irc',
155       source: 'bob', message: 'mysql > pg'
156     journal.publish 'log.irc',
157       source: 'alice', target: 'bob', action: :kick
158
159     # wait for messages to be consumed:
160     sleep 0.1
161     assert_equal(3, received.length)
162   end
163
164   def test_subscribe
165     received = []
166     journal = JournalBroker.new
167
168     # subscribe to messages for topic foo:
169     sub = journal.subscribe('foo') do |message|
170       received << message
171     end
172
173     # publish some messages:
174     journal.publish 'foo', {}
175     journal.publish 'bar', {}
176     journal.publish 'foo', {}
177
178     # wait for messages to be consumed:
179     sleep 0.1
180     assert_equal(2, received.length)
181
182     received.clear
183
184     journal.publish 'foo', {}
185     sleep 0.1
186     sub.cancel
187     journal.publish 'foo', {}
188     sleep 0.1
189     assert_equal(1, received.length)
190   end
191
192 end
193
194 module JournalStorageTestMixin
195
196   include Irc::Bot::Journal
197
198   def teardown
199     @storage.drop
200   end
201
202   def test_operations
203     # insertion
204     m = JournalMessage.create('log.core', {foo: {bar: 'baz', qux: 42}})
205     @storage.insert(m)
206
207     # query by id
208     res = @storage.find(Query.define { id m.id })
209     assert_equal(1, res.length)
210     assert_equal(m, res.first)
211
212     # check timestamp was returned correctly:
213     assert_equal(m.timestamp.strftime('%Y-%m-%d %H:%M:%S%z'),
214                  res.first.timestamp.strftime('%Y-%m-%d %H:%M:%S%z'))
215
216     # check if payload was returned correctly:
217     assert_equal({'foo' => {'bar' => 'baz', 'qux' => 42}}, res.first.payload)
218
219     # query by topic
220     assert_equal(m, @storage.find(Query.define { topic('log.core') }).first)
221     assert_equal(m, @storage.find(Query.define { topic('log.*') }).first)
222     assert_equal(m, @storage.find(Query.define { topic('*.*') }).first)
223
224     # query by timestamp range
225     assert_equal(1, @storage.find(Query.define {
226       timestamp(from: Time.now-DAY, to: Time.now+DAY) }).length)
227     assert_equal(0, @storage.find(Query.define {
228       timestamp(from: Time.now-DAY*2, to: Time.now-DAY) }).length)
229
230     # query by payload
231     res = @storage.find(Query.define { payload('foo.bar' => 'baz') })
232     assert_equal(m, res.first)
233     res = @storage.find(Query.define { payload('foo.bar' => 'x') })
234     assert_true(res.empty?)
235
236     # without arguments: find and count
237     assert_equal(1, @storage.count)
238     assert_equal(m, @storage.find.first)
239   end
240
241   def test_find
242     # tests limit/offset and block parameters of find()
243     @storage.insert(JournalMessage.create('irclogs', {message: 'foo'}))
244     @storage.insert(JournalMessage.create('irclogs', {message: 'bar'}))
245     @storage.insert(JournalMessage.create('irclogs', {message: 'baz'}))
246     @storage.insert(JournalMessage.create('irclogs', {message: 'qux'}))
247
248     msgs = []
249     @storage.find(Query.define({topic: 'irclogs'}), 2, 1) do |m|
250       msgs << m
251     end
252     assert_equal(2, msgs.length)
253     assert_equal('bar', msgs.first['message'])
254     assert_equal('baz', msgs.last['message'])
255
256     msgs = []
257     @storage.find(Query.define({topic: 'irclogs'})) do |m|
258       msgs << m
259     end
260     assert_equal(4, msgs.length)
261     assert_equal('foo', msgs.first['message'])
262     assert_equal('qux', msgs.last['message'])
263
264   end
265
266   def test_operations_multiple
267     # test operations on multiple messages
268     # insert a bunch:
269     @storage.insert(JournalMessage.create('test.topic', {name: 'one'}))
270     @storage.insert(JournalMessage.create('test.topic', {name: 'two'}))
271     @storage.insert(JournalMessage.create('test.topic', {name: 'three'}))
272     @storage.insert(JournalMessage.create('archived.topic', {name: 'four'},
273       timestamp: Time.now - DAY*100))
274     @storage.insert(JournalMessage.create('complex', {name: 'five', country: {
275       name: 'Italy'
276     }}))
277     @storage.insert(JournalMessage.create('complex', {name: 'six', country: {
278       name: 'Austria'
279     }}))
280
281     # query by topic
282     assert_equal(3, @storage.find(Query.define { topic 'test.*' }).length)
283     # query by payload
284     assert_equal(1, @storage.find(Query.define {
285       payload('country.name' => 'Austria') }).length)
286     # query by timestamp range
287     assert_equal(1, @storage.find(Query.define {
288       timestamp(from: Time.now - DAY*150, to: Time.now - DAY*50) }).length)
289
290     # count with query
291     assert_equal(2, @storage.count(Query.define { topic('complex') }))
292     assert_equal(6, @storage.count)
293     @storage.remove(Query.define { topic('archived.*') })
294     assert_equal(5, @storage.count)
295     @storage.remove
296     assert_equal(0, @storage.count)
297   end
298
299   def test_broker_interface
300     journal = JournalBroker.new(storage: @storage) 
301
302     journal.publish 'irclogs', message: 'foo'
303     journal.publish 'irclogs', message: 'bar'
304     journal.publish 'irclogs', message: 'baz'
305     journal.publish 'irclogs', message: 'qux'
306
307     # wait for messages to be consumed:
308     sleep 0.1
309
310     msgs = []
311     journal.find({topic: 'irclogs'}, 2, 1) do |m|
312       msgs << m
313     end
314     assert_equal(2, msgs.length)
315     assert_equal('bar', msgs.first['message'])
316     assert_equal('baz', msgs.last['message'])
317
318     journal.ensure_payload_index('foo.bar.baz')
319   end
320
321   NUM=100 # 1_000_000
322   def test_benchmark
323     puts
324
325     assert_equal(0, @storage.count)
326     # prepare messages to insert, we benchmark the storage backend not ruby
327     num = 0
328     messages = (0...NUM).map do
329       num += 1
330       JournalMessage.create(
331             'test.topic.num_'+num.to_s, {answer: {number: '42', word: 'forty-two'}})
332     end
333
334     # iter is the number of operations performed WITHIN block
335     def benchmark(label, iter, &block)
336       time = Benchmark.realtime do
337         yield
338       end
339       puts label + ' %d iterations, duration: %.3fms (%.3fms / iteration)' % [iter, time*1000, (time*1000) / iter]
340     end
341
342     benchmark(@storage.class.to_s+'~insert', messages.length) do
343       messages.each { |m|
344         @storage.insert(m)
345       }
346     end
347
348     benchmark(@storage.class.to_s+'~find_by_id', messages.length) do
349       messages.each { |m|
350         @storage.find(Query.define { id m.id })
351       }
352     end
353     benchmark(@storage.class.to_s+'~find_by_topic', messages.length) do
354       messages.each { |m|
355         @storage.find(Query.define { topic m.topic })
356       }
357     end
358     benchmark(@storage.class.to_s+'~find_by_topic_wildcard', messages.length) do
359       messages.each { |m|
360         @storage.find(Query.define { topic m.topic.gsub('topic', '*') })
361       }
362     end
363   end
364
365 end
366
367 if ENV['PG_URI']
368 class JournalStoragePostgresTest < Test::Unit::TestCase
369
370   include JournalStorageTestMixin
371
372   def setup
373     @storage = Storage::PostgresStorage.new(
374       uri: ENV['PG_URI'] || 'postgresql://localhost/rbot_journal',
375       drop: true)
376   end
377
378   def test_query_to_sql
379     q = Query.define do
380       id 'foo'
381       id 'bar', 'baz'
382       topic 'log.irc.*'
383       topic 'log.core', 'baz'
384       timestamp from: Time.now, to: Time.now + 60 * 10
385       payload 'action': :privmsg, 'alice': 'bob'
386       payload 'channel': '#rbot'
387       payload 'foo.bar': 'baz'
388     end
389     sql = @storage.query_to_sql(q)
390     assert_equal("(id = $1 OR id = $2 OR id = $3) AND (topic ILIKE $4 OR topic ILIKE $5 OR topic ILIKE $6) AND (timestamp >= $7 AND timestamp <= $8) AND (payload->>'action' = $9 OR payload->>'alice' = $10 OR payload->>'channel' = $11 OR payload->'foo'->>'bar' = $12)", sql[0])
391     q = Query.define do
392       id 'foo'
393     end
394     assert_equal('(id = $1)', @storage.query_to_sql(q)[0])
395     q = Query.define do
396       topic 'foo.*.bar'
397     end
398     assert_equal('(topic ILIKE $1)', @storage.query_to_sql(q)[0])
399     assert_equal(['foo.%.bar'], @storage.query_to_sql(q)[1])
400   end
401
402 end
403 else
404   puts 'NOTE: Set PG_URI environment variable to test postgresql storage.'
405 end
406
407 if ENV['MONGO_URI']
408 class JournalStorageMongoTest < Test::Unit::TestCase
409
410   include JournalStorageTestMixin
411
412   def setup
413     @storage = Storage::MongoStorage.new(
414       uri: ENV['MONGO_URI'] || 'mongodb://127.0.0.1:27017/rbot',
415       drop: true)
416   end
417 end
418 else
419   puts 'NOTE: Set MONGO_URI environment variable to test postgresql storage.'
420 end
421