factbase 0.4.0 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (79) hide show
  1. checksums.yaml +4 -4
  2. data/.0pdd.yml +1 -1
  3. data/.github/workflows/actionlint.yml +1 -1
  4. data/.github/workflows/benchmark.yml +64 -0
  5. data/.github/workflows/codecov.yml +4 -2
  6. data/.github/workflows/copyrights.yml +2 -2
  7. data/.github/workflows/markdown-lint.yml +1 -1
  8. data/.github/workflows/pdd.yml +1 -1
  9. data/.github/workflows/rake.yml +2 -2
  10. data/.github/workflows/xcop.yml +1 -1
  11. data/.github/workflows/yamllint.yml +1 -1
  12. data/.gitignore +1 -1
  13. data/.rubocop.yml +6 -1
  14. data/.rultor.yml +2 -2
  15. data/.simplecov +1 -1
  16. data/.yamllint.yml +9 -4
  17. data/Gemfile +9 -7
  18. data/Gemfile.lock +99 -78
  19. data/LICENSE.txt +1 -1
  20. data/README.md +33 -0
  21. data/Rakefile +6 -1
  22. data/benchmarks/simple.rb +96 -0
  23. data/factbase.gemspec +1 -1
  24. data/lib/factbase/accum.rb +2 -2
  25. data/lib/factbase/fact.rb +6 -4
  26. data/lib/factbase/flatten.rb +2 -2
  27. data/lib/factbase/inv.rb +2 -2
  28. data/lib/factbase/looged.rb +6 -5
  29. data/lib/factbase/pre.rb +2 -2
  30. data/lib/factbase/query.rb +16 -8
  31. data/lib/factbase/query_once.rb +71 -0
  32. data/lib/factbase/rules.rb +3 -3
  33. data/lib/factbase/syntax.rb +13 -8
  34. data/lib/factbase/tee.rb +2 -2
  35. data/lib/factbase/term.rb +33 -5
  36. data/lib/factbase/term_once.rb +84 -0
  37. data/lib/factbase/terms/aggregates.rb +4 -4
  38. data/lib/factbase/terms/aliases.rb +5 -5
  39. data/lib/factbase/terms/casting.rb +2 -2
  40. data/lib/factbase/terms/debug.rb +2 -2
  41. data/lib/factbase/terms/defn.rb +2 -2
  42. data/lib/factbase/terms/logical.rb +3 -3
  43. data/lib/factbase/terms/math.rb +2 -2
  44. data/lib/factbase/terms/meta.rb +2 -2
  45. data/lib/factbase/terms/ordering.rb +2 -2
  46. data/lib/factbase/terms/strings.rb +2 -2
  47. data/lib/factbase/terms/system.rb +2 -2
  48. data/lib/factbase/to_json.rb +2 -2
  49. data/lib/factbase/to_xml.rb +2 -2
  50. data/lib/factbase/to_yaml.rb +2 -2
  51. data/lib/factbase.rb +16 -6
  52. data/test/factbase/terms/test_aggregates.rb +6 -6
  53. data/test/factbase/terms/test_aliases.rb +6 -6
  54. data/test/factbase/terms/test_casting.rb +6 -6
  55. data/test/factbase/terms/test_debug.rb +53 -0
  56. data/test/factbase/terms/test_defn.rb +15 -15
  57. data/test/factbase/terms/test_logical.rb +15 -13
  58. data/test/factbase/terms/test_math.rb +42 -42
  59. data/test/factbase/terms/test_meta.rb +87 -0
  60. data/test/factbase/terms/test_ordering.rb +45 -0
  61. data/test/factbase/terms/test_strings.rb +8 -8
  62. data/test/factbase/terms/test_system.rb +6 -6
  63. data/test/factbase/test_accum.rb +9 -9
  64. data/test/factbase/test_fact.rb +22 -22
  65. data/test/factbase/test_flatten.rb +6 -6
  66. data/test/factbase/test_inv.rb +3 -3
  67. data/test/factbase/test_looged.rb +13 -13
  68. data/test/factbase/test_pre.rb +2 -2
  69. data/test/factbase/test_query.rb +17 -17
  70. data/test/factbase/test_rules.rb +8 -8
  71. data/test/factbase/test_syntax.rb +29 -9
  72. data/test/factbase/test_tee.rb +11 -11
  73. data/test/factbase/test_term.rb +18 -18
  74. data/test/factbase/test_to_json.rb +4 -4
  75. data/test/factbase/test_to_xml.rb +12 -14
  76. data/test/factbase/test_to_yaml.rb +3 -3
  77. data/test/test__helper.rb +2 -2
  78. data/test/test_factbase.rb +198 -18
  79. metadata +12 -5
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Copyright (c) 2024 Yegor Bugayenko
3
+ # Copyright (c) 2024-2025 Yegor Bugayenko
4
4
  #
5
5
  # Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  # of this software and associated documentation files (the 'Software'), to deal
@@ -27,7 +27,7 @@ require_relative '../../lib/factbase/to_yaml'
27
27
 
28
28
  # Test.
29
29
  # Author:: Yegor Bugayenko (yegor256@gmail.com)
30
- # Copyright:: Copyright (c) 2024 Yegor Bugayenko
30
+ # Copyright:: Copyright (c) 2024-2025 Yegor Bugayenko
31
31
  # License:: MIT
32
32
  class TestToYAML < Minitest::Test
33
33
  def test_simple_rendering
@@ -51,6 +51,6 @@ class TestToYAML < Minitest::Test
51
51
  f.a = 256
52
52
  f.c = 10
53
53
  yaml = Factbase::ToYAML.new(fb).yaml
54
- assert(yaml.include?("a: 256\n b: 42\n c: 10"), yaml)
54
+ assert_includes(yaml, "a: 256\n b: 42\n c: 10", yaml)
55
55
  end
56
56
  end
data/test/test__helper.rb CHANGED
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Copyright (c) 2024 Yegor Bugayenko
3
+ # Copyright (c) 2024-2025 Yegor Bugayenko
4
4
  #
5
5
  # Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  # of this software and associated documentation files (the 'Software'), to deal
@@ -37,6 +37,6 @@ Minitest::Reporters.use! [Minitest::Reporters::SpecReporter.new]
37
37
  class Minitest::Test
38
38
  def fact(map = {})
39
39
  require 'factbase/fact'
40
- Factbase::Fact.new(Mutex.new, map)
40
+ Factbase::Fact.new(Factbase.new, Mutex.new, map)
41
41
  end
42
42
  end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Copyright (c) 2024 Yegor Bugayenko
3
+ # Copyright (c) 2024-2025 Yegor Bugayenko
4
4
  #
5
5
  # Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  # of this software and associated documentation files (the 'Software'), to deal
@@ -22,6 +22,7 @@
22
22
 
23
23
  require 'minitest/autorun'
24
24
  require 'loog'
25
+ require 'threads'
25
26
  require_relative '../lib/factbase'
26
27
  require_relative '../lib/factbase/rules'
27
28
  require_relative '../lib/factbase/inv'
@@ -30,7 +31,7 @@ require_relative '../lib/factbase/looged'
30
31
 
31
32
  # Factbase main module test.
32
33
  # Author:: Yegor Bugayenko (yegor256@gmail.com)
33
- # Copyright:: Copyright (c) 2024 Yegor Bugayenko
34
+ # Copyright:: Copyright (c) 2024-2025 Yegor Bugayenko
34
35
  # License:: MIT
35
36
  class TestFactbase < Minitest::Test
36
37
  def test_injects_data_correctly
@@ -48,13 +49,22 @@ class TestFactbase < Minitest::Test
48
49
  assert_equal([2, 3], maps[1]['bar'])
49
50
  end
50
51
 
52
+ def test_query_many_times
53
+ fb = Factbase.new
54
+ total = 5
55
+ total.times { fb.insert }
56
+ total.times do
57
+ assert_equal(5, fb.query('(always)').each.to_a.size)
58
+ end
59
+ end
60
+
51
61
  def test_simple_setting
52
62
  fb = Factbase.new
53
63
  fb.insert
54
64
  fb.insert.bar = 88
55
65
  found = 0
56
66
  fb.query('(exists bar)').each do |f|
57
- assert(42, f.bar.positive?)
67
+ assert_predicate(f.bar, :positive?)
58
68
  f.foo = 42
59
69
  assert_equal(42, f.foo)
60
70
  found += 1
@@ -87,17 +97,17 @@ class TestFactbase < Minitest::Test
87
97
  File.binwrite(f.path, f1.export)
88
98
  f2.import(File.binread(f.path))
89
99
  end
90
- assert_equal(1, f2.query('(eq foo 42)').each.to_a.count)
100
+ assert_equal(1, f2.query('(eq foo 42)').each.to_a.size)
91
101
  end
92
102
 
93
103
  def test_reads_from_empty_file
94
104
  fb = Factbase.new
95
105
  Tempfile.open do |f|
96
106
  File.binwrite(f.path, '')
97
- assert(
98
- assert_raises do
107
+ assert_includes(
108
+ assert_raises(StandardError) do
99
109
  fb.import(File.binread(f.path))
100
- end.message.include?('cannot load a factbase')
110
+ end.message, 'cannot load a factbase'
101
111
  )
102
112
  end
103
113
  end
@@ -121,10 +131,10 @@ class TestFactbase < Minitest::Test
121
131
 
122
132
  def test_txn_returns_boolean
123
133
  fb = Factbase.new
124
- assert(fb.txn { true }.is_a?(FalseClass))
125
- assert(fb.txn(&:insert).is_a?(TrueClass))
134
+ assert_kind_of(FalseClass, fb.txn { true })
135
+ assert_kind_of(TrueClass, fb.txn(&:insert))
126
136
  assert(fb.txn { |fbt| fbt.insert.bar = 42 })
127
- assert(!fb.txn { |fbt| fbt.query('(always)').each.to_a })
137
+ refute(fb.txn { |fbt| fbt.query('(always)').each.to_a })
128
138
  assert(fb.txn { |fbt| fbt.query('(always)').each { |f| f.hello = 33 } })
129
139
  assert(fb.txn { |fbt| fbt.query('(always)').each.to_a[0].zzz = 33 })
130
140
  end
@@ -136,13 +146,13 @@ class TestFactbase < Minitest::Test
136
146
  fbt.insert.z = 42
137
147
  end
138
148
  assert_equal(2, fb.size)
139
- assert(
140
- assert_raises do
149
+ assert_includes(
150
+ assert_raises(StandardError) do
141
151
  fb.txn do |fbt|
142
152
  fbt.insert.foo = 42
143
153
  throw 'intentionally'
144
154
  end
145
- end.message.include?('intentionally')
155
+ end.message, 'intentionally'
146
156
  )
147
157
  assert_equal(2, fb.size)
148
158
  end
@@ -163,12 +173,12 @@ class TestFactbase < Minitest::Test
163
173
  fb.insert.bar = 3
164
174
  fb.insert.foo = 5
165
175
  assert_equal(2, fb.size)
166
- assert(
167
- assert_raises do
176
+ assert_includes(
177
+ assert_raises(StandardError) do
168
178
  fb.txn do |fbt|
169
179
  fbt.insert.foo = 42
170
180
  end
171
- end.message.include?('oops')
181
+ end.message, 'oops'
172
182
  )
173
183
  assert_equal(2, fb.size)
174
184
  end
@@ -185,7 +195,7 @@ class TestFactbase < Minitest::Test
185
195
  d.txn do |fbt|
186
196
  fbt.insert.bar = 455
187
197
  end
188
- assert_raises do
198
+ assert_raises(StandardError) do
189
199
  d.txn do |fbt|
190
200
  fbt.insert
191
201
  throw 'oops'
@@ -216,7 +226,177 @@ class TestFactbase < Minitest::Test
216
226
  fbt.insert.bar = 33
217
227
  raise Factbase::Rollback
218
228
  end
219
- assert(!modified)
229
+ refute(modified)
220
230
  assert_equal(0, fb.query('(always)').each.to_a.size)
221
231
  end
232
+
233
+ def test_concurrent_inserts
234
+ fb = Factbase.new
235
+ Threads.new(100).assert do
236
+ fact = fb.insert
237
+ fact.foo = 42
238
+ fact.bar = 49
239
+ fact.value = fact.foo * fact.bar
240
+ end
241
+ assert_equal(100, fb.size)
242
+ assert_equal(100, fb.query('(eq foo 42)').each.to_a.size)
243
+ assert_equal(100, fb.query('(eq bar 49)').each.to_a.size)
244
+ assert_equal(100, fb.query("(eq value #{42 * 49})").each.to_a.size)
245
+ end
246
+
247
+ def test_different_values_when_concurrent_inserts
248
+ fb = Factbase.new
249
+ Threads.new(100).assert do |i|
250
+ fb.insert.foo = i
251
+ end
252
+ assert_equal(100, fb.size)
253
+ Threads.new(100) do |i|
254
+ f = fb.query("(eq foo #{i})").each.to_a
255
+ assert_equal(1, f.count)
256
+ assert_equal(i, f.first.foo)
257
+ end
258
+ end
259
+
260
+ # @todo #98:1h I assumed that the test `test_different_properties_when_concurrent_inserts` would be passed.
261
+ # I see like this:
262
+ # ```
263
+ # [2024-08-22 21:14:53.962] ERROR -- Expected: 1
264
+ # Actual: 0: nil
265
+ # [2024-08-22 21:14:53.962] ERROR -- Expected: 1
266
+ # Actual: 0: nil
267
+ # test_different_properties_when_concurrent_inserts ERROR (0.01s)
268
+ # Minitest::UnexpectedError: RuntimeError: Only 0 out of 5 threads completed successfully
269
+ # /home/suban/.rbenv/versions/3.3.4/lib/ruby/gems/3.3.0/gems/threads-0.4.0/lib/threads.rb:73:in `assert'
270
+ # test/test_factbase.rb:265:in `test_different_properties_when_concurrent_inserts'
271
+ # ```
272
+ def test_different_properties_when_concurrent_inserts
273
+ skip('Does not work')
274
+ fb = Factbase.new
275
+ Threads.new(5).assert do |i|
276
+ fb.insert.send(:"prop_#{i}=", i)
277
+ end
278
+ assert_equal(5, fb.size)
279
+ Threads.new(5).assert do |i|
280
+ prop = "prop_#{i}"
281
+ f = fb.query("(eq #{prop} #{i})").each.to_a
282
+ assert_equal(1, f.count)
283
+ assert_equal(i, f.first.send(prop.to_sym))
284
+ end
285
+ end
286
+
287
+ # @todo #98:1h I assumed that the test `test_concurrent_transactions_inserts` would be passed.
288
+ # I see like this:
289
+ # ```
290
+ # Expected: 100
291
+ # Actual: 99
292
+ # D:/a/factbase/factbase/test/test_factbase.rb:281:in `test_concurrent_transactions_inserts'
293
+ # ```
294
+ # See details here https://github.com/yegor256/factbase/actions/runs/10492255419/job/29068637032
295
+ def test_concurrent_transactions_inserts
296
+ skip('Does not work')
297
+ fb = Factbase.new
298
+ Threads.new(100).assert do |i|
299
+ fb.txn do |fbt|
300
+ fact = fbt.insert
301
+ fact.thread_id = i
302
+ end
303
+ end
304
+ assert_equal(100, fb.size)
305
+ assert_equal(100, fb.query('(exists thread_id)').each.to_a.size)
306
+ end
307
+
308
+ def test_concurrent_transactions_with_rollbacks
309
+ fb = Factbase.new
310
+ Threads.new(100).assert do |i|
311
+ fb.txn do |fbt|
312
+ fact = fbt.insert
313
+ fact.thread_id = i
314
+ raise Factbase::Rollback
315
+ end
316
+ end
317
+ assert_equal(0, fb.size)
318
+ end
319
+
320
+ def test_concurrent_transactions_successful
321
+ fb = Factbase.new
322
+ Threads.new(100).assert do |i|
323
+ fb.txn do |fbt|
324
+ fact = fbt.insert
325
+ fact.thread_id = i
326
+ fact.value = i * 10
327
+ end
328
+ end
329
+ facts = fb.query('(exists thread_id)').each.to_a
330
+ assert_equal(100, facts.size)
331
+ facts.each do |fact|
332
+ assert_equal(fact.value, fact.thread_id * 10)
333
+ end
334
+ end
335
+
336
+ # @todo #98:1h I assumed that the test `test_concurrent_queries` would be passed.
337
+ # I see like this:
338
+ # ```
339
+ # [2024-08-22 17:40:19.224] ERROR -- Expected: [0, 1]
340
+ # Actual: [0, 0]: nil
341
+ # [2024-08-22 17:40:19.224] ERROR -- Expected: [0, 1]
342
+ # Actual: [0, 0]: nil
343
+ # test_concurrent_queries ERROR (0.00s)
344
+ # Minitest::UnexpectedError: RuntimeError: Only 0 out of 2 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:329:in `test_concurrent_queries'
347
+ # ```
348
+ def test_concurrent_queries
349
+ skip('Does not work')
350
+ fb = Factbase.new
351
+ Threads.new(2).assert do |i|
352
+ fact = fb.insert
353
+ fact.thread_id = i
354
+ fact.value = i * 10
355
+ end
356
+ Threads.new(2).assert do
357
+ results = fb.query('(exists thread_id)').each.to_a
358
+ assert_equal(2, results.size)
359
+
360
+ thread_ids = results.map(&:thread_id)
361
+ assert_equal((0..1).to_a, thread_ids.sort)
362
+ end
363
+ end
364
+
365
+ def test_export_import_concurrent
366
+ fb = Factbase.new
367
+ Threads.new(100).assert do
368
+ fact = fb.insert
369
+ fact.value = 42
370
+ end
371
+ Threads.new(5).assert do
372
+ new_fb = Factbase.new
373
+ new_fb.import(fb.export)
374
+ assert_equal(fb.size, new_fb.size)
375
+ facts = fb.query('(eq value 42)').each.to_a
376
+ assert_equal(100, facts.size)
377
+ facts.each do |fact|
378
+ new_fact = new_fb.query("(eq value #{fact.value})").each.to_a.first
379
+ assert_equal(fact.value, new_fact.value)
380
+ end
381
+ end
382
+ end
383
+
384
+ def test_dup_concurrent
385
+ fb = Factbase.new
386
+ mutex = Mutex.new
387
+ Threads.new(100).assert do
388
+ fact = fb.insert
389
+ fact.foo = 42
390
+ end
391
+ fbs = []
392
+ Threads.new(100).assert do
393
+ mutex.synchronize do
394
+ fbs << fb.dup
395
+ end
396
+ end
397
+ assert_equal(100, fbs.size)
398
+ fbs.each do |factbase|
399
+ assert_equal(100, factbase.query('(eq foo 42)').each.to_a.size)
400
+ end
401
+ end
222
402
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: factbase
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.0
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Yegor Bugayenko
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-08-12 00:00:00.000000000 Z
11
+ date: 2025-01-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: backtrace
@@ -137,6 +137,7 @@ files:
137
137
  - ".0pdd.yml"
138
138
  - ".gitattributes"
139
139
  - ".github/workflows/actionlint.yml"
140
+ - ".github/workflows/benchmark.yml"
140
141
  - ".github/workflows/codecov.yml"
141
142
  - ".github/workflows/copyrights.yml"
142
143
  - ".github/workflows/markdown-lint.yml"
@@ -155,6 +156,7 @@ files:
155
156
  - LICENSE.txt
156
157
  - README.md
157
158
  - Rakefile
159
+ - benchmarks/simple.rb
158
160
  - factbase.gemspec
159
161
  - lib/factbase.rb
160
162
  - lib/factbase/accum.rb
@@ -164,10 +166,12 @@ files:
164
166
  - lib/factbase/looged.rb
165
167
  - lib/factbase/pre.rb
166
168
  - lib/factbase/query.rb
169
+ - lib/factbase/query_once.rb
167
170
  - lib/factbase/rules.rb
168
171
  - lib/factbase/syntax.rb
169
172
  - lib/factbase/tee.rb
170
173
  - lib/factbase/term.rb
174
+ - lib/factbase/term_once.rb
171
175
  - lib/factbase/terms/aggregates.rb
172
176
  - lib/factbase/terms/aliases.rb
173
177
  - lib/factbase/terms/casting.rb
@@ -186,9 +190,12 @@ files:
186
190
  - test/factbase/terms/test_aggregates.rb
187
191
  - test/factbase/terms/test_aliases.rb
188
192
  - test/factbase/terms/test_casting.rb
193
+ - test/factbase/terms/test_debug.rb
189
194
  - test/factbase/terms/test_defn.rb
190
195
  - test/factbase/terms/test_logical.rb
191
196
  - test/factbase/terms/test_math.rb
197
+ - test/factbase/terms/test_meta.rb
198
+ - test/factbase/terms/test_ordering.rb
192
199
  - test/factbase/terms/test_strings.rb
193
200
  - test/factbase/terms/test_system.rb
194
201
  - test/factbase/test_accum.rb
@@ -212,7 +219,7 @@ licenses:
212
219
  - MIT
213
220
  metadata:
214
221
  rubygems_mfa_required: 'true'
215
- post_install_message:
222
+ post_install_message:
216
223
  rdoc_options:
217
224
  - "--charset=UTF-8"
218
225
  require_paths:
@@ -229,7 +236,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
229
236
  version: '0'
230
237
  requirements: []
231
238
  rubygems_version: 3.4.10
232
- signing_key:
239
+ signing_key:
233
240
  specification_version: 4
234
241
  summary: Factbase
235
242
  test_files: []