factbase 0.3.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 +3 -3
  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 +15 -4
  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 +17 -8
  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 +16 -2
  69. data/test/factbase/test_query.rb +17 -17
  70. data/test/factbase/test_rules.rb +11 -8
  71. data/test/factbase/test_syntax.rb +29 -9
  72. data/test/factbase/test_tee.rb +18 -9
  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
@@ -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,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.3.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-08 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: []