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