factbase 0.4.0 → 0.5.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 (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 +98 -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 +21 -11
  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 +30 -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 +200 -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
@@ -25,34 +25,34 @@ require_relative '../../lib/factbase/term'
25
25
 
26
26
  # Term test.
27
27
  # Author:: Yegor Bugayenko (yegor256@gmail.com)
28
- # Copyright:: Copyright (c) 2024 Yegor Bugayenko
28
+ # Copyright:: Copyright (c) 2024-2025 Yegor Bugayenko
29
29
  # License:: MIT
30
30
  class TestTerm < Minitest::Test
31
31
  def test_false_matching
32
- t = Factbase::Term.new(:never, [])
33
- assert(!t.evaluate(fact('foo' => [100]), []))
32
+ t = Factbase::Term.new(Factbase.new, :never, [])
33
+ refute(t.evaluate(fact('foo' => [100]), []))
34
34
  end
35
35
 
36
36
  def test_size_matching
37
- t = Factbase::Term.new(:size, [:foo])
37
+ t = Factbase::Term.new(Factbase.new, :size, [:foo])
38
38
  assert_equal(3, t.evaluate(fact('foo' => [42, 12, -90]), []))
39
39
  assert_equal(0, t.evaluate(fact('bar' => 100), []))
40
40
  end
41
41
 
42
42
  def test_exists_matching
43
- t = Factbase::Term.new(:exists, [:foo])
43
+ t = Factbase::Term.new(Factbase.new, :exists, [:foo])
44
44
  assert(t.evaluate(fact('foo' => [42, 12, -90]), []))
45
- assert(!t.evaluate(fact('bar' => 100), []))
45
+ refute(t.evaluate(fact('bar' => 100), []))
46
46
  end
47
47
 
48
48
  def test_absent_matching
49
- t = Factbase::Term.new(:absent, [:foo])
49
+ t = Factbase::Term.new(Factbase.new, :absent, [:foo])
50
50
  assert(t.evaluate(fact('z' => [42, 12, -90]), []))
51
- assert(!t.evaluate(fact('foo' => 100), []))
51
+ refute(t.evaluate(fact('foo' => 100), []))
52
52
  end
53
53
 
54
54
  def test_type_matching
55
- t = Factbase::Term.new(:type, [:foo])
55
+ t = Factbase::Term.new(Factbase.new, :type, [:foo])
56
56
  assert_equal('Integer', t.evaluate(fact('foo' => 42), []))
57
57
  assert_equal('Integer', t.evaluate(fact('foo' => [42]), []))
58
58
  assert_equal('Array', t.evaluate(fact('foo' => [1, 2, 3]), []))
@@ -64,30 +64,30 @@ class TestTerm < Minitest::Test
64
64
  end
65
65
 
66
66
  def test_past
67
- t = Factbase::Term.new(:prev, [:foo])
67
+ t = Factbase::Term.new(Factbase.new, :prev, [:foo])
68
68
  assert_nil(t.evaluate(fact('foo' => 4), []))
69
69
  assert_equal([4], t.evaluate(fact('foo' => 5), []))
70
70
  end
71
71
 
72
72
  def test_at
73
- t = Factbase::Term.new(:at, [1, :foo])
73
+ t = Factbase::Term.new(Factbase.new, :at, [1, :foo])
74
74
  assert_nil(t.evaluate(fact('foo' => 4), []))
75
75
  assert_equal(5, t.evaluate(fact('foo' => [4, 5]), []))
76
76
  end
77
77
 
78
78
  def test_report_missing_term
79
- t = Factbase::Term.new(:something, [])
80
- msg = assert_raises do
79
+ t = Factbase::Term.new(Factbase.new, :something, [])
80
+ msg = assert_raises(StandardError) do
81
81
  t.evaluate(fact, [])
82
82
  end.message
83
- assert(msg.include?('not defined at (something)'), msg)
83
+ assert_includes(msg, 'not defined at (something)', msg)
84
84
  end
85
85
 
86
86
  def test_report_other_error
87
- t = Factbase::Term.new(:at, [])
88
- msg = assert_raises do
87
+ t = Factbase::Term.new(Factbase.new, :at, [])
88
+ msg = assert_raises(StandardError) do
89
89
  t.evaluate(fact, [])
90
90
  end.message
91
- assert(msg.include?('at (at)'), msg)
91
+ assert_includes(msg, 'at (at)', msg)
92
92
  end
93
93
  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
@@ -27,7 +27,7 @@ require_relative '../../lib/factbase/to_json'
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 TestToJSON < Minitest::Test
33
33
  def test_simple_rendering
@@ -37,7 +37,7 @@ class TestToJSON < Minitest::Test
37
37
  f.foo = 256
38
38
  to = Factbase::ToJSON.new(fb)
39
39
  json = JSON.parse(to.json)
40
- assert(42, json[0]['foo'][1])
40
+ assert_equal(256, json[0]['foo'][1])
41
41
  end
42
42
 
43
43
  def test_sort_keys
@@ -47,6 +47,6 @@ class TestToJSON < Minitest::Test
47
47
  f.b = 1
48
48
  f.a = 256
49
49
  json = Factbase::ToJSON.new(fb).json
50
- assert(json.include?('{"a":256,"b":1,"c":42}'), json)
50
+ assert_includes(json, '{"a":256,"b":1,"c":42}', json)
51
51
  end
52
52
  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
@@ -27,7 +27,7 @@ require_relative '../../lib/factbase/to_xml'
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 TestToXML < Minitest::Test
33
33
  def test_simple_rendering
@@ -35,11 +35,9 @@ class TestToXML < Minitest::Test
35
35
  fb.insert.t = Time.now
36
36
  to = Factbase::ToXML.new(fb)
37
37
  xml = Nokogiri::XML.parse(to.xml)
38
- assert(!xml.xpath('/fb/f[t]').empty?)
39
- assert(
40
- xml.xpath('/fb/f/t/text()').text.match?(
41
- /^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}Z$/
42
- )
38
+ refute_empty(xml.xpath('/fb/f[t]'))
39
+ assert_match(
40
+ /^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}Z$/, xml.xpath('/fb/f/t/text()').text
43
41
  )
44
42
  end
45
43
 
@@ -48,7 +46,7 @@ class TestToXML < Minitest::Test
48
46
  fb.insert.t = "\uffff < > & ' \""
49
47
  to = Factbase::ToXML.new(fb)
50
48
  xml = Nokogiri::XML.parse(to.xml)
51
- assert(!xml.xpath('/fb/f[t]').empty?)
49
+ refute_empty(xml.xpath('/fb/f[t]'))
52
50
  end
53
51
 
54
52
  def test_meta_data_presence
@@ -56,8 +54,8 @@ class TestToXML < Minitest::Test
56
54
  fb.insert.x = 42
57
55
  to = Factbase::ToXML.new(fb)
58
56
  xml = Nokogiri::XML.parse(to.xml)
59
- assert(!xml.xpath('/fb[@version]').empty?)
60
- assert(!xml.xpath('/fb[@size]').empty?)
57
+ refute_empty(xml.xpath('/fb[@version]'))
58
+ refute_empty(xml.xpath('/fb[@size]'))
61
59
  end
62
60
 
63
61
  def test_to_xml_with_short_names
@@ -67,8 +65,8 @@ class TestToXML < Minitest::Test
67
65
  f.f = 2
68
66
  to = Factbase::ToXML.new(fb)
69
67
  xml = Nokogiri::XML.parse(to.xml)
70
- assert(!xml.xpath('/fb/f/type').empty?)
71
- assert(!xml.xpath('/fb/f/f').empty?)
68
+ refute_empty(xml.xpath('/fb/f/type'))
69
+ refute_empty(xml.xpath('/fb/f/f'))
72
70
  end
73
71
 
74
72
  def test_show_types_as_attributes
@@ -89,7 +87,7 @@ class TestToXML < Minitest::Test
89
87
  '/fb/f/d[@t="T"]',
90
88
  '/fb/f/e/v[@t="S"]',
91
89
  '/fb/f/e/v[@t="I"]'
92
- ].each { |x| assert(!xml.xpath(x).empty?, out) }
90
+ ].each { |x| refute_empty(xml.xpath(x), out) }
93
91
  end
94
92
 
95
93
  def test_sorts_keys
@@ -102,7 +100,7 @@ class TestToXML < Minitest::Test
102
100
  to = Factbase::ToXML.new(fb)
103
101
  xml = Nokogiri::XML.parse(to.xml)
104
102
  %w[a c t x].each_with_index do |e, i|
105
- assert(!xml.xpath("/fb/f/#{e}[count(preceding-sibling::*) = #{i}]").empty?, e)
103
+ refute_empty(xml.xpath("/fb/f/#{e}[count(preceding-sibling::*) = #{i}]"), e)
106
104
  end
107
105
  end
108
106
  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
@@ -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,179 @@ 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
+ total = 100
298
+ fb = Factbase.new
299
+ Threads.new(total).assert do |i|
300
+ fb.txn do |fbt|
301
+ fact = fbt.insert
302
+ fact.thread_id = i
303
+ end
304
+ end
305
+ assert_equal(total, fb.size)
306
+ assert_equal(total, fb.query('(exists thread_id)').each.to_a.size)
307
+ end
308
+
309
+ def test_concurrent_transactions_with_rollbacks
310
+ fb = Factbase.new
311
+ Threads.new.assert do |i|
312
+ fb.txn do |fbt|
313
+ fact = fbt.insert
314
+ fact.thread_id = i
315
+ raise Factbase::Rollback
316
+ end
317
+ end
318
+ assert_equal(0, fb.size)
319
+ end
320
+
321
+ def test_concurrent_transactions_successful
322
+ fb = Factbase.new
323
+ total = 100
324
+ Threads.new(total).assert do |i|
325
+ fb.txn do |fbt|
326
+ fact = fbt.insert
327
+ fact.thread_id = i
328
+ fact.value = i * 10
329
+ end
330
+ end
331
+ facts = fb.query('(exists thread_id)').each.to_a
332
+ assert_equal(total, facts.size)
333
+ facts.each do |fact|
334
+ assert_equal(fact.value, fact.thread_id * 10)
335
+ end
336
+ end
337
+
338
+ # @todo #98:1h I assumed that the test `test_concurrent_queries` would be passed.
339
+ # I see like this:
340
+ # ```
341
+ # [2024-08-22 17:40:19.224] ERROR -- Expected: [0, 1]
342
+ # Actual: [0, 0]: nil
343
+ # [2024-08-22 17:40:19.224] ERROR -- Expected: [0, 1]
344
+ # Actual: [0, 0]: nil
345
+ # test_concurrent_queries ERROR (0.00s)
346
+ # Minitest::UnexpectedError: RuntimeError: Only 0 out of 2 threads completed successfully
347
+ # /home/suban/.rbenv/versions/3.3.4/lib/ruby/gems/3.3.0/gems/threads-0.4.0/lib/threads.rb:73:in `assert'
348
+ # test/test_factbase.rb:329:in `test_concurrent_queries'
349
+ # ```
350
+ def test_concurrent_queries
351
+ skip('Does not work')
352
+ fb = Factbase.new
353
+ Threads.new(2).assert do |i|
354
+ fact = fb.insert
355
+ fact.thread_id = i
356
+ fact.value = i * 10
357
+ end
358
+ Threads.new(2).assert do
359
+ results = fb.query('(exists thread_id)').each.to_a
360
+ assert_equal(2, results.size)
361
+
362
+ thread_ids = results.map(&:thread_id)
363
+ assert_equal((0..1).to_a, thread_ids.sort)
364
+ end
365
+ end
366
+
367
+ def test_export_import_concurrent
368
+ fb = Factbase.new
369
+ Threads.new(100).assert do
370
+ fact = fb.insert
371
+ fact.value = 42
372
+ end
373
+ Threads.new(5).assert do
374
+ new_fb = Factbase.new
375
+ new_fb.import(fb.export)
376
+ assert_equal(fb.size, new_fb.size)
377
+ facts = fb.query('(eq value 42)').each.to_a
378
+ assert_equal(100, facts.size)
379
+ facts.each do |fact|
380
+ new_fact = new_fb.query("(eq value #{fact.value})").each.to_a.first
381
+ assert_equal(fact.value, new_fact.value)
382
+ end
383
+ end
384
+ end
385
+
386
+ def test_dup_concurrent
387
+ fb = Factbase.new
388
+ mutex = Mutex.new
389
+ Threads.new(100).assert do
390
+ fact = fb.insert
391
+ fact.foo = 42
392
+ end
393
+ fbs = []
394
+ Threads.new(100).assert do
395
+ mutex.synchronize do
396
+ fbs << fb.dup
397
+ end
398
+ end
399
+ assert_equal(100, fbs.size)
400
+ fbs.each do |factbase|
401
+ assert_equal(100, factbase.query('(eq foo 42)').each.to_a.size)
402
+ end
403
+ end
222
404
  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.1
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-28 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: []