factbase 0.15.0 → 0.15.1

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 (88) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile.lock +5 -5
  3. data/factbase.gemspec +4 -4
  4. data/lib/factbase/fact.rb +1 -1
  5. data/lib/factbase/fact_as_yaml.rb +10 -1
  6. data/lib/factbase/indexed/indexed_term.rb +6 -1
  7. data/lib/factbase/version.rb +1 -1
  8. metadata +7 -87
  9. data/.0pdd.yml +0 -8
  10. data/.gitattributes +0 -7
  11. data/.github/publish-benchmark.sh +0 -24
  12. data/.github/workflows/actionlint.yml +0 -25
  13. data/.github/workflows/benchmark.yml +0 -36
  14. data/.github/workflows/codecov.yml +0 -27
  15. data/.github/workflows/copyrights.yml +0 -19
  16. data/.github/workflows/markdown-lint.yml +0 -23
  17. data/.github/workflows/pdd.yml +0 -19
  18. data/.github/workflows/rake.yml +0 -28
  19. data/.github/workflows/reuse.yml +0 -19
  20. data/.github/workflows/typos.yml +0 -19
  21. data/.github/workflows/xcop.yml +0 -19
  22. data/.github/workflows/yamllint.yml +0 -19
  23. data/.gitignore +0 -11
  24. data/.pdd +0 -7
  25. data/.rubocop.yml +0 -51
  26. data/.rultor.yml +0 -27
  27. data/benchmark/bench_factbase.rb +0 -61
  28. data/benchmark/bench_large_query.rb +0 -128
  29. data/benchmark/bench_query.rb +0 -57
  30. data/benchmark/bench_taped.rb +0 -33
  31. data/fixtures/stories/agg.yml +0 -17
  32. data/fixtures/stories/always.yml +0 -16
  33. data/fixtures/stories/and.yml +0 -19
  34. data/fixtures/stories/as.yml +0 -16
  35. data/fixtures/stories/count.yml +0 -18
  36. data/fixtures/stories/empty.yml +0 -23
  37. data/fixtures/stories/eq.yml +0 -30
  38. data/fixtures/stories/gt.yml +0 -18
  39. data/fixtures/stories/join.yml +0 -22
  40. data/fixtures/stories/max.yml +0 -14
  41. data/fixtures/stories/min.yml +0 -14
  42. data/fixtures/stories/nth.yml +0 -14
  43. data/fixtures/stories/one.yml +0 -14
  44. data/fixtures/stories/or.yml +0 -18
  45. data/fixtures/stories/sprintf.yml +0 -12
  46. data/fixtures/stories/sum.yml +0 -14
  47. data/fixtures/stories/unique.yml +0 -30
  48. data/renovate.json +0 -6
  49. data/test/factbase/cached/test_cached_factbase.rb +0 -44
  50. data/test/factbase/cached/test_cached_query.rb +0 -100
  51. data/test/factbase/indexed/test_indexed_fact.rb +0 -23
  52. data/test/factbase/indexed/test_indexed_factbase.rb +0 -96
  53. data/test/factbase/indexed/test_indexed_query.rb +0 -208
  54. data/test/factbase/indexed/test_indexed_term.rb +0 -140
  55. data/test/factbase/sync/test_sync_factbase.rb +0 -22
  56. data/test/factbase/sync/test_sync_query.rb +0 -30
  57. data/test/factbase/terms/test_aggregates.rb +0 -63
  58. data/test/factbase/terms/test_aliases.rb +0 -58
  59. data/test/factbase/terms/test_casting.rb +0 -33
  60. data/test/factbase/terms/test_debug.rb +0 -111
  61. data/test/factbase/terms/test_defn.rb +0 -48
  62. data/test/factbase/terms/test_logical.rb +0 -57
  63. data/test/factbase/terms/test_math.rb +0 -122
  64. data/test/factbase/terms/test_meta.rb +0 -70
  65. data/test/factbase/terms/test_ordering.rb +0 -44
  66. data/test/factbase/terms/test_strings.rb +0 -36
  67. data/test/factbase/terms/test_system.rb +0 -31
  68. data/test/factbase/test_accum.rb +0 -74
  69. data/test/factbase/test_churn.rb +0 -41
  70. data/test/factbase/test_fact.rb +0 -108
  71. data/test/factbase/test_fact_as_yaml.rb +0 -77
  72. data/test/factbase/test_flatten.rb +0 -28
  73. data/test/factbase/test_impatient.rb +0 -176
  74. data/test/factbase/test_inv.rb +0 -44
  75. data/test/factbase/test_logged.rb +0 -155
  76. data/test/factbase/test_pre.rb +0 -35
  77. data/test/factbase/test_query.rb +0 -445
  78. data/test/factbase/test_rules.rb +0 -128
  79. data/test/factbase/test_syntax.rb +0 -166
  80. data/test/factbase/test_tallied.rb +0 -81
  81. data/test/factbase/test_taped.rb +0 -72
  82. data/test/factbase/test_tee.rb +0 -85
  83. data/test/factbase/test_term.rb +0 -87
  84. data/test/factbase/test_to_json.rb +0 -35
  85. data/test/factbase/test_to_xml.rb +0 -89
  86. data/test/factbase/test_to_yaml.rb +0 -39
  87. data/test/test__helper.rb +0 -41
  88. data/test/test_factbase.rb +0 -509
@@ -1,509 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # SPDX-FileCopyrightText: Copyright (c) 2024-2025 Yegor Bugayenko
4
- # SPDX-License-Identifier: MIT
5
-
6
- require 'loog'
7
- require 'threads'
8
- require_relative '../lib/factbase'
9
- require_relative '../lib/factbase/inv'
10
- require_relative '../lib/factbase/logged'
11
- require_relative '../lib/factbase/pre'
12
- require_relative '../lib/factbase/rules'
13
- require_relative 'test__helper'
14
-
15
- # Factbase main module test.
16
- # Author:: Yegor Bugayenko (yegor256@gmail.com)
17
- # Copyright:: Copyright (c) 2024-2025 Yegor Bugayenko
18
- # License:: MIT
19
- class TestFactbase < Factbase::Test
20
- def test_injects_data_correctly
21
- maps = []
22
- fb = Factbase.new(maps)
23
- fb.insert
24
- f = fb.insert
25
- f.foo = 1
26
- f.bar = 2
27
- f.bar = 3
28
- assert_equal(2, maps.size)
29
- assert_equal(0, maps[0].size)
30
- assert_equal(2, maps[1].size)
31
- assert_equal([1], maps[1]['foo'])
32
- assert_equal([2, 3], maps[1]['bar'])
33
- end
34
-
35
- def test_query_many_times
36
- fb = Factbase.new
37
- total = 5
38
- total.times { fb.insert }
39
- total.times do
40
- assert_equal(5, fb.query('(always)').each.to_a.size)
41
- end
42
- end
43
-
44
- def test_converts_query_to_term
45
- fb = Factbase.new
46
- term = fb.to_term('(eq foo 42)')
47
- assert_equal('(eq foo 42)', term.to_s)
48
- end
49
-
50
- def test_simple_setting
51
- fb = Factbase.new
52
- fb.insert
53
- fb.insert.bar = 88
54
- found = 0
55
- fb.query('(exists bar)').each do |f|
56
- assert_predicate(f.bar, :positive?)
57
- f.foo = 42
58
- assert_equal(42, f.foo)
59
- found += 1
60
- end
61
- assert_equal(1, found)
62
- assert_equal(2, fb.size)
63
- end
64
-
65
- def test_modify_via_query
66
- fb = Factbase.new
67
- fb.insert.bar = 1
68
- fb.query('(exists bar)').each do |f|
69
- f.bar = 42
70
- assert_equal(2, f['bar'].size)
71
- end
72
- found = 0
73
- fb.query('(always)').each do |f|
74
- assert_equal(2, f['bar'].size)
75
- found += 1
76
- end
77
- assert_equal(1, found)
78
- assert_equal(2, fb.query('(always)').each.to_a[0]['bar'].size)
79
- end
80
-
81
- def test_serialize_and_deserialize
82
- f1 = Factbase.new
83
- f2 = Factbase.new
84
- f1.insert.foo = 42
85
- Tempfile.open do |f|
86
- File.binwrite(f.path, f1.export)
87
- f2.import(File.binread(f.path))
88
- end
89
- assert_equal(1, f2.query('(eq foo 42)').each.to_a.size)
90
- end
91
-
92
- def test_reads_from_empty_file
93
- fb = Factbase.new
94
- Tempfile.open do |f|
95
- File.binwrite(f.path, '')
96
- assert_includes(
97
- assert_raises(StandardError) do
98
- fb.import(File.binread(f.path))
99
- end.message, 'cannot load a factbase'
100
- )
101
- end
102
- end
103
-
104
- def test_empty_or_not
105
- fb = Factbase.new
106
- assert_equal(0, fb.size)
107
- fb.insert
108
- assert_equal(1, fb.size)
109
- end
110
-
111
- def test_txn_returns_boolean
112
- fb = Factbase.new
113
- assert_equal(1, fb.txn(&:insert).to_i)
114
- assert_equal(1, fb.txn { |fbt| fbt.insert.bar = 42 }.to_i)
115
- assert_equal(0, fb.txn { |fbt| fbt.query('(always)').each.to_a }.to_i)
116
- assert_equal(2, fb.txn { |fbt| fbt.query('(always)').each { |f| f.hello = 33 } }.to_i)
117
- assert_equal(1, fb.txn { |fbt| fbt.query('(always)').each.to_a[0].zzz = 33 }.to_i)
118
- end
119
-
120
- def test_appends_in_txn
121
- fb = Factbase.new
122
- fb.insert.foo = 443
123
- assert(fb.txn { |fbt| fbt.query('(always)').each { |f| f.hello = 33 } })
124
- end
125
-
126
- def test_run_txn
127
- fb = Factbase.new
128
- fb.txn do |fbt|
129
- fbt.insert.bar = 42
130
- fbt.insert.z = 42
131
- end
132
- assert_equal(2, fb.size)
133
- assert_includes(
134
- assert_raises(StandardError) do
135
- fb.txn do |fbt|
136
- fbt.insert.foo = 42
137
- raise 'intentionally'
138
- end
139
- end.message, 'intentionally'
140
- )
141
- assert_equal(2, fb.size)
142
- end
143
-
144
- def test_deals_with_arrays_in_txn
145
- fb = Factbase.new
146
- n = fb.insert
147
- n.foo = 1
148
- n.foo = 2
149
- fb.txn do |fbt|
150
- f = fbt.query('(gt foo 0)').each.to_a.first
151
- assert_equal(1, f.foo)
152
- end
153
- end
154
-
155
- def test_run_txn_via_query
156
- fb = Factbase.new
157
- fb.insert.foo = 1
158
- assert(
159
- fb.txn do |fbt|
160
- fbt.query('(always)').each { |f| f.foo = 42 }
161
- end
162
- )
163
- assert_equal([1, 42], fb.query('(always)').each.to_a[0]['foo'])
164
- end
165
-
166
- def test_run_txn_with_inv
167
- fb = Factbase::Inv.new(Factbase.new) { |_p, v| throw 'oops' if v == 42 }
168
- fb.insert.bar = 3
169
- fb.insert.foo = 5
170
- assert_equal(2, fb.size)
171
- assert_includes(
172
- assert_raises(StandardError) do
173
- fb.txn do |fbt|
174
- fbt.insert.foo = 42
175
- end
176
- end.message, 'oops'
177
- )
178
- assert_equal(2, fb.size)
179
- end
180
-
181
- def test_all_decorators
182
- [
183
- Factbase::Rules.new(Factbase.new, '(always)'),
184
- Factbase::Inv.new(Factbase.new) { |_, _| true },
185
- Factbase::Pre.new(Factbase.new) { |_| true },
186
- Factbase::Logged.new(Factbase.new, Loog::NULL)
187
- ].each do |d|
188
- f = d.insert
189
- f.foo = 42
190
- d.txn do |fbt|
191
- fbt.insert.bar = 455
192
- end
193
- assert_raises(StandardError) do
194
- d.txn do |fbt|
195
- fbt.insert
196
- raise 'oops'
197
- end
198
- end
199
- d.import(d.export)
200
- assert_equal(4, d.size)
201
- assert_equal(4, d.query('(always)').each.to_a.size)
202
- end
203
- end
204
-
205
- def test_txn_inside_query
206
- fb = Factbase.new
207
- fact = fb.insert
208
- fact.foo = 42
209
- fact.foo = 555
210
- fb.query('(exists foo)').each do |f|
211
- fb.txn do |fbt|
212
- assert_equal(42, fb.query('(always)').each.to_a.first.foo)
213
- assert_equal(42, fb.query('(always)').each.to_a.first['foo'].first)
214
- fbt.insert.bar = 33
215
- end
216
- f.xyz = 1
217
- end
218
- assert_equal(1, fb.query('(exists xyz)').each.to_a.size)
219
- end
220
-
221
- def test_evals_complex_txn
222
- fb = Factbase.new
223
- n = fb.insert
224
- n.foo = 42
225
- n.bar = 555
226
- fb.txn do |fbt|
227
- fbt.insert.foo = 333
228
- fbt.insert.bar = 333
229
- fbt.query('(eq bar 555)').delete!
230
- fbt.insert.bar = 555
231
- fbt.query('(eq bar 777)').delete! # nothing to be deleted
232
- n1 = fbt.insert
233
- n1.bar = 9
234
- n1.bar = 99
235
- end
236
- assert_equal(4, fb.query('(always)').each.to_a.size)
237
- end
238
-
239
- def test_txn_with_rollback
240
- fb = Factbase.new
241
- n = fb.insert
242
- n.bar = 55
243
- modified =
244
- fb.txn do |fbt|
245
- fbt.insert.bar = 33
246
- fbt.query('(eq bar 55)').each.to_a.first.boom = 44
247
- raise Factbase::Rollback
248
- end
249
- assert_equal(0, modified.to_i)
250
- assert_equal(1, fb.query('(always)').each.to_a.size)
251
- assert_equal(0, fb.query('(exists boom)').each.to_a.size)
252
- end
253
-
254
- def test_modifies_existing_fact_in_txn
255
- fb = Factbase.new
256
- n = fb.insert
257
- n.foo = 1
258
- fb.txn do |fbt|
259
- f = fbt.query('(always)').each.to_a.first
260
- f.bar = 2
261
- end
262
- assert_equal(1, fb.query('(eq foo 1)').each.to_a.size)
263
- end
264
-
265
- def test_simple_concurrent_inserts
266
- fb = Factbase.new
267
- t = Concurrent.processor_count * 20
268
- Threads.new(t).assert do
269
- fb.insert
270
- end
271
- assert_equal(t, fb.size)
272
- end
273
-
274
- def test_simple_concurrent_inserts_in_txns
275
- fb = Factbase.new
276
- t = Concurrent.processor_count * 7
277
- mul = 23
278
- Threads.new(t).assert do
279
- assert(
280
- fb.txn do |fbt|
281
- mul.times do |m|
282
- fbt.insert.foo = m
283
- end
284
- end
285
- )
286
- end
287
- assert_equal(t * mul, fb.size)
288
- end
289
-
290
- def test_simple_concurrent_inserts_in_txns_with_sleep
291
- fb = Factbase.new
292
- t = Concurrent.processor_count * 13
293
- mul = 53
294
- Threads.new(t).assert do
295
- assert(
296
- fb.txn do |fbt|
297
- sleep(0.01)
298
- mul.times do |m|
299
- fbt.insert.foo = m
300
- end
301
- end
302
- )
303
- end
304
- assert_equal(t * mul, fb.size)
305
- end
306
-
307
- def test_concurrent_inserts
308
- fb = Factbase.new
309
- t = Concurrent.processor_count * 20
310
- Threads.new(t).assert do
311
- fact = fb.insert
312
- fact.foo = 42
313
- fact.bar = 49
314
- fact.value = fact.foo * fact.bar
315
- end
316
- assert_equal(t, fb.size)
317
- assert_equal(t, fb.query('(eq foo 42)').each.to_a.size)
318
- assert_equal(t, fb.query('(eq bar 49)').each.to_a.size)
319
- assert_equal(t, fb.query("(eq value #{42 * 49})").each.to_a.size)
320
- end
321
-
322
- def test_different_values_when_concurrent_inserts
323
- fb = Factbase.new
324
- t = Concurrent.processor_count * 16
325
- Threads.new(t).assert do |i|
326
- fb.insert.foo = i
327
- end
328
- assert_equal(t, fb.size)
329
- Threads.new(t) do |i|
330
- f = fb.query("(eq foo #{i})").each.to_a
331
- assert_equal(1, f.count)
332
- assert_equal(i, f.first.foo)
333
- end
334
- end
335
-
336
- # @todo #98:1h I assumed that the test `test_different_properties_when_concurrent_inserts` would be passed.
337
- # I see like this:
338
- # ```
339
- # [2024-08-22 21:14:53.962] ERROR -- Expected: 1
340
- # Actual: 0: nil
341
- # [2024-08-22 21:14:53.962] ERROR -- Expected: 1
342
- # Actual: 0: nil
343
- # test_different_properties_when_concurrent_inserts ERROR (0.01s)
344
- # Minitest::UnexpectedError: RuntimeError: Only 0 out of 5 threads completed successfully
345
- # /home/suban/.rbenv/versions/3.3.4/lib/ruby/gems/3.3.0/gems/threads-0.4.0/lib/threads.rb:73:in `assert'
346
- # test/test_factbase.rb:265:in `test_different_properties_when_concurrent_inserts'
347
- # ```
348
- def test_different_properties_when_concurrent_inserts
349
- skip('Does not work')
350
- fb = Factbase.new
351
- Threads.new(5).assert do |i|
352
- fb.insert.send(:"prop_#{i}=", i)
353
- end
354
- assert_equal(5, fb.size)
355
- Threads.new(5).assert do |i|
356
- prop = "prop_#{i}"
357
- f = fb.query("(eq #{prop} #{i})").each.to_a
358
- assert_equal(1, f.count)
359
- assert_equal(i, f.first.send(prop.to_sym))
360
- end
361
- end
362
-
363
- # @todo #98:1h I assumed that the test `test_concurrent_transactions_inserts` would be passed.
364
- # I see like this:
365
- # ```
366
- # Expected: 100
367
- # Actual: 99
368
- # D:/a/factbase/factbase/test/test_factbase.rb:281:in `test_concurrent_transactions_inserts'
369
- # ```
370
- # See details here https://github.com/yegor256/factbase/actions/runs/10492255419/job/29068637032
371
- def test_concurrent_transactions_inserts
372
- skip('Does not work')
373
- t = Concurrent.processor_count * 19
374
- fb = Factbase.new
375
- Threads.new(t).assert do |i|
376
- fb.txn do |fbt|
377
- fact = fbt.insert
378
- fact.thread_id = i
379
- end
380
- end
381
- assert_equal(t, fb.size)
382
- assert_equal(t, fb.query('(exists thread_id)').each.to_a.size)
383
- end
384
-
385
- def test_concurrent_transactions_with_rollbacks
386
- fb = Factbase.new
387
- Threads.new.assert do |i|
388
- fb.txn do |fbt|
389
- fact = fbt.insert
390
- fact.thread_id = i
391
- raise Factbase::Rollback
392
- end
393
- end
394
- assert_equal(0, fb.size)
395
- end
396
-
397
- def test_concurrent_transactions_successful
398
- fb = Factbase.new
399
- t = Concurrent.processor_count * 17
400
- Threads.new(t).assert do |i|
401
- fb.txn do |fbt|
402
- fact = fbt.insert
403
- fact.thread_id = i
404
- fact.value = i * 10
405
- end
406
- end
407
- facts = fb.query('(exists thread_id)').each.to_a
408
- assert_equal(t, facts.size)
409
- facts.each do |fact|
410
- assert_equal(fact.value, fact.thread_id * 10)
411
- end
412
- end
413
-
414
- # @todo #98:1h I assumed that the test `test_concurrent_queries` would be passed.
415
- # I see like this:
416
- # ```
417
- # [2024-08-22 17:40:19.224] ERROR -- Expected: [0, 1]
418
- # Actual: [0, 0]: nil
419
- # [2024-08-22 17:40:19.224] ERROR -- Expected: [0, 1]
420
- # Actual: [0, 0]: nil
421
- # test_concurrent_queries ERROR (0.00s)
422
- # Minitest::UnexpectedError: RuntimeError: Only 0 out of 2 threads completed successfully
423
- # /home/suban/.rbenv/versions/3.3.4/lib/ruby/gems/3.3.0/gems/threads-0.4.0/lib/threads.rb:73:in `assert'
424
- # test/test_factbase.rb:329:in `test_concurrent_queries'
425
- # ```
426
- def test_concurrent_queries
427
- skip('Does not work')
428
- fb = Factbase.new
429
- Threads.new(2).assert do |i|
430
- fact = fb.insert
431
- fact.thread_id = i
432
- fact.value = i * 10
433
- end
434
- Threads.new(2).assert do
435
- results = fb.query('(exists thread_id)').each.to_a
436
- assert_equal(2, results.size)
437
- thread_ids = results.map(&:thread_id)
438
- assert_equal((0..1).to_a, thread_ids.sort)
439
- end
440
- end
441
-
442
- def test_export_import_concurrent
443
- fb = Factbase.new
444
- Threads.new(100).assert do
445
- fact = fb.insert
446
- fact.value = 42
447
- end
448
- Threads.new(5).assert do
449
- new_fb = Factbase.new
450
- new_fb.import(fb.export)
451
- assert_equal(fb.size, new_fb.size)
452
- facts = fb.query('(eq value 42)').each.to_a
453
- assert_equal(100, facts.size)
454
- facts.each do |fact|
455
- new_fact = new_fb.query("(eq value #{fact.value})").each.to_a.first
456
- assert_equal(fact.value, new_fact.value)
457
- end
458
- end
459
- end
460
-
461
- def test_dup_concurrent
462
- fb = Factbase.new
463
- mutex = Mutex.new
464
- Threads.new(100).assert do
465
- fact = fb.insert
466
- fact.foo = 42
467
- end
468
- fbs = []
469
- Threads.new(100).assert do
470
- mutex.synchronize do
471
- fbs << fb.dup
472
- end
473
- end
474
- assert_equal(100, fbs.size)
475
- fbs.each do |factbase|
476
- assert_equal(100, factbase.query('(eq foo 42)').each.to_a.size)
477
- end
478
- end
479
-
480
- def test_commits_on_exit_by_throw
481
- fb = Factbase.new
482
- fb.txn do |fbt|
483
- fbt.insert.foo = 1
484
- throw :commit
485
- end
486
- assert_equal(1, fb.size)
487
- end
488
-
489
- def test_rolls_back
490
- fb = Factbase.new
491
- fb.txn do |fbt|
492
- fbt.insert.foo = 1
493
- throw :rollback
494
- end
495
- assert_equal(0, fb.size)
496
- end
497
-
498
- def test_get_raise_for_empty_fact
499
- fb = Factbase.new
500
- fb.txn do |fbt|
501
- f = fbt.insert
502
- f.foo = 123
503
- f = fbt.query('(always)').each.to_a.first
504
- assert_equal(123, f.foo)
505
- ex = assert_raises(RuntimeError) { f.bar }
506
- assert_equal("Can't find 'bar' attribute out of [foo]", ex.message)
507
- end
508
- end
509
- end