factbase 0.0.25 → 0.0.27

Sign up to get free protection for your applications and to get access to all the features.
data/lib/factbase.rb CHANGED
@@ -21,7 +21,6 @@
21
21
  # SOFTWARE.
22
22
 
23
23
  require 'json'
24
- require 'nokogiri'
25
24
  require 'yaml'
26
25
 
27
26
  # Factbase.
@@ -29,16 +28,19 @@ require 'yaml'
29
28
  # Copyright:: Copyright (c) 2024 Yegor Bugayenko
30
29
  # License:: MIT
31
30
  class Factbase
31
+ # Current version of the gem (changed by .rultor.yml on every release)
32
+ VERSION = '0.0.27'
33
+
32
34
  # Constructor.
33
- def initialize
34
- @maps = []
35
+ def initialize(facts = [])
36
+ @maps = facts
35
37
  @mutex = Mutex.new
36
38
  end
37
39
 
38
- # Is it empty?
39
- # @return [Boolean] TRUE if there are no facts inside
40
- def empty?
41
- @maps.empty?
40
+ # Make a duplicate of this factbase.
41
+ # @return [Factbase] A new factbase
42
+ def dup
43
+ Factbase.new(@maps.dup)
42
44
  end
43
45
 
44
46
  # Size.
@@ -63,11 +65,11 @@ class Factbase
63
65
  # There is a Lisp-like syntax, for example:
64
66
  #
65
67
  # (eq title 'Object Thinking')
66
- # (gt time '2024-03-23T03:21:43')
68
+ # (gt time 2024-03-23T03:21:43Z)
67
69
  # (gt cost 42)
68
70
  # (exists seenBy)
69
71
  # (and
70
- # (eq foo 42)
72
+ # (eq foo 42.998)
71
73
  # (or
72
74
  # (gt bar 200)
73
75
  # (absent zzz)))
@@ -78,6 +80,17 @@ class Factbase
78
80
  Factbase::Query.new(@maps, @mutex, query)
79
81
  end
80
82
 
83
+ # Run an ACID transaction, which will either modify the factbase
84
+ # or rollback in case of an error.
85
+ def txn(this = self)
86
+ copy = this.dup
87
+ yield copy
88
+ @mutex.synchronize do
89
+ @maps = []
90
+ import(copy.export)
91
+ end
92
+ end
93
+
81
94
  # Export it into a chain of bytes.
82
95
  def export
83
96
  Marshal.dump(@maps)
@@ -85,44 +98,6 @@ class Factbase
85
98
 
86
99
  # Import from a chain of bytes.
87
100
  def import(bytes)
88
- # rubocop:disable Security/MarshalLoad
89
101
  @maps += Marshal.load(bytes)
90
- # rubocop:enable Security/MarshalLoad
91
- end
92
-
93
- # Convert the entire factbase into JSON.
94
- # @return [String] The factbase in JSON format
95
- def to_json(_ = nil)
96
- @maps.to_json
97
- end
98
-
99
- # Convert the entire factbase into XML.
100
- # @return [String] The factbase in XML format
101
- def to_xml
102
- Nokogiri::XML::Builder.new(encoding: 'UTF-8') do |xml|
103
- xml.fb do
104
- @maps.each do |m|
105
- xml.f_ do
106
- m.each do |k, vv|
107
- if vv.is_a?(Array)
108
- xml.send(:"#{k}_") do
109
- vv.each do |v|
110
- xml.send(:v, v)
111
- end
112
- end
113
- else
114
- xml.send(:"#{k}_", vv)
115
- end
116
- end
117
- end
118
- end
119
- end
120
- end.to_xml
121
- end
122
-
123
- # Convert the entire factbase into YAML.
124
- # @return [String] The factbase in YAML format
125
- def to_yaml
126
- YAML.dump({ 'facts' => @maps })
127
102
  end
128
103
  end
@@ -22,6 +22,7 @@
22
22
 
23
23
  require 'minitest/autorun'
24
24
  require 'loog'
25
+ require_relative '../../lib/factbase'
25
26
  require_relative '../../lib/factbase/inv'
26
27
  require_relative '../../lib/factbase/pre'
27
28
 
@@ -73,7 +73,7 @@ class TestLooged < Minitest::Test
73
73
  fb.query('(not (exists bar))').delete!
74
74
  [
75
75
  'Inserted new fact',
76
- 'Set \'bar\' to "3" (Integer)',
76
+ 'Set \'bar\' to 3 (Integer)',
77
77
  'Found 1 fact(s) by \'(exists bar)\'',
78
78
  'Deleted 2 fact(s) by \'(not (exists bar))\''
79
79
  ].each do |s|
@@ -54,6 +54,9 @@ class TestQuery < Minitest::Test
54
54
  "(and (lt pi 100) \n\n (gt num 1000))" => 0,
55
55
  '(exists pi)' => 1,
56
56
  '(not (exists hello))' => 3,
57
+ '(gt (size num) 2)' => 1,
58
+ '(lt (size num) 2)' => 2,
59
+ '(eq (size hello) 0)' => 3,
57
60
  '(absent time)' => 2,
58
61
  '(and (absent time) (exists pi))' => 1,
59
62
  "(and (exists time) (not (\t\texists pi)))" => 1,
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright (c) 2024 Yegor Bugayenko
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ # of this software and associated documentation files (the 'Software'), to deal
7
+ # in the Software without restriction, including without limitation the rights
8
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ # copies of the Software, and to permit persons to whom the Software is
10
+ # furnished to do so, subject to the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be included in all
13
+ # copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFINGEMENT. IN NO EVENT SHALL THE
18
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ # SOFTWARE.
22
+
23
+ require 'minitest/autorun'
24
+ require_relative '../../lib/factbase'
25
+ require_relative '../../lib/factbase/rules'
26
+
27
+ # Test.
28
+ # Author:: Yegor Bugayenko (yegor256@gmail.com)
29
+ # Copyright:: Copyright (c) 2024 Yegor Bugayenko
30
+ # License:: MIT
31
+ class TestRules < Minitest::Test
32
+ def test_simple_checking
33
+ fb = Factbase::Rules.new(
34
+ Factbase.new,
35
+ '(when (exists first) (exists second))'
36
+ )
37
+ f1 = fb.insert
38
+ f1.second = 2
39
+ f1.first = 1
40
+ assert_raises do
41
+ f2 = fb.insert
42
+ f2.first = 1
43
+ end
44
+ end
45
+ end
@@ -45,6 +45,7 @@ class TestSyntax < Minitest::Test
45
45
  '(foo)',
46
46
  '(foo (bar) (zz 77) )',
47
47
  "(eq foo \n\n 'Hello, world!'\n)\n",
48
+ "# this is a comment\n(eq foo # test\n 42)\n\n# another comment\n",
48
49
  "(or ( a 4) (b 5) () (and () (c 5) \t\t(r 7 8s 8is 'Foo')))"
49
50
  ].each do |q|
50
51
  Factbase::Syntax.new(q).to_term
@@ -62,6 +63,7 @@ class TestSyntax < Minitest::Test
62
63
  "(foo x y z t f 42 'Hi!' 33)",
63
64
  '(foo (x) y z)',
64
65
  '(eq t 2024-05-25T19:43:48Z)',
66
+ '(eq t 2024-05-25T19:43:48Z)',
65
67
  '(eq t 3.1415926)',
66
68
  '(eq t 3.0e+21)',
67
69
  "(foo (x (f (t (y 42 'Hey you'))) (f) (r 3)) y z)"
@@ -81,7 +83,7 @@ class TestSyntax < Minitest::Test
81
83
  '(or (eq bar 888) (eq z 1))' => true,
82
84
  "(or (gt bar 100) (eq foo 'Hello, world!'))" => true
83
85
  }.each do |k, v|
84
- assert_equal(v, Factbase::Syntax.new(k).to_term.matches?(m), k)
86
+ assert_equal(v, Factbase::Syntax.new(k).to_term.eval(m), k)
85
87
  end
86
88
  end
87
89
 
@@ -89,6 +91,7 @@ class TestSyntax < Minitest::Test
89
91
  [
90
92
  '',
91
93
  '(foo',
94
+ 'some text',
92
95
  '"hello, world!',
93
96
  '(foo 7',
94
97
  "(foo 7 'Dude'",
@@ -30,79 +30,95 @@ require_relative '../../lib/factbase/term'
30
30
  class TestTerm < Minitest::Test
31
31
  def test_simple_matching
32
32
  t = Factbase::Term.new(:eq, [:foo, 42])
33
- assert(t.matches?(fact('foo' => [42])))
34
- assert(!t.matches?(fact('foo' => 'Hello!')))
35
- assert(!t.matches?(fact('bar' => ['Hello!'])))
33
+ assert(t.eval(fact('foo' => [42])))
34
+ assert(!t.eval(fact('foo' => 'Hello!')))
35
+ assert(!t.eval(fact('bar' => ['Hello!'])))
36
36
  end
37
37
 
38
38
  def test_eq_matching
39
39
  t = Factbase::Term.new(:eq, [:foo, 42])
40
- assert(t.matches?(fact('foo' => 42)))
41
- assert(t.matches?(fact('foo' => [10, 5, 6, -8, 'hey', 42, 9, 'fdsf'])))
42
- assert(!t.matches?(fact('foo' => [100])))
43
- assert(!t.matches?(fact('foo' => [])))
44
- assert(!t.matches?(fact('bar' => [])))
40
+ assert(t.eval(fact('foo' => 42)))
41
+ assert(t.eval(fact('foo' => [10, 5, 6, -8, 'hey', 42, 9, 'fdsf'])))
42
+ assert(!t.eval(fact('foo' => [100])))
43
+ assert(!t.eval(fact('foo' => [])))
44
+ assert(!t.eval(fact('bar' => [])))
45
45
  end
46
46
 
47
47
  def test_eq_matching_time
48
48
  now = Time.now
49
49
  t = Factbase::Term.new(:eq, [:foo, Time.parse(now.iso8601)])
50
- assert(t.matches?(fact('foo' => now)))
51
- assert(t.matches?(fact('foo' => [now, Time.now])))
50
+ assert(t.eval(fact('foo' => now)))
51
+ assert(t.eval(fact('foo' => [now, Time.now])))
52
52
  end
53
53
 
54
54
  def test_lt_matching
55
55
  t = Factbase::Term.new(:lt, [:foo, 42])
56
- assert(t.matches?(fact('foo' => [10])))
57
- assert(!t.matches?(fact('foo' => [100])))
58
- assert(!t.matches?(fact('foo' => 100)))
59
- assert(!t.matches?(fact('bar' => 100)))
56
+ assert(t.eval(fact('foo' => [10])))
57
+ assert(!t.eval(fact('foo' => [100])))
58
+ assert(!t.eval(fact('foo' => 100)))
59
+ assert(!t.eval(fact('bar' => 100)))
60
60
  end
61
61
 
62
62
  def test_gt_matching
63
63
  t = Factbase::Term.new(:gt, [:foo, 42])
64
- assert(t.matches?(fact('foo' => [100])))
65
- assert(t.matches?(fact('foo' => 100)))
66
- assert(!t.matches?(fact('foo' => [10])))
67
- assert(!t.matches?(fact('foo' => 10)))
68
- assert(!t.matches?(fact('bar' => 10)))
64
+ assert(t.eval(fact('foo' => [100])))
65
+ assert(t.eval(fact('foo' => 100)))
66
+ assert(!t.eval(fact('foo' => [10])))
67
+ assert(!t.eval(fact('foo' => 10)))
68
+ assert(!t.eval(fact('bar' => 10)))
69
69
  end
70
70
 
71
71
  def test_lt_matching_time
72
72
  t = Factbase::Term.new(:lt, [:foo, Time.now])
73
- assert(t.matches?(fact('foo' => [Time.now - 100])))
74
- assert(!t.matches?(fact('foo' => [Time.now + 100])))
75
- assert(!t.matches?(fact('bar' => [100])))
73
+ assert(t.eval(fact('foo' => [Time.now - 100])))
74
+ assert(!t.eval(fact('foo' => [Time.now + 100])))
75
+ assert(!t.eval(fact('bar' => [100])))
76
76
  end
77
77
 
78
78
  def test_gt_matching_time
79
79
  t = Factbase::Term.new(:gt, [:foo, Time.now])
80
- assert(t.matches?(fact('foo' => [Time.now + 100])))
81
- assert(!t.matches?(fact('foo' => [Time.now - 100])))
82
- assert(!t.matches?(fact('bar' => [100])))
80
+ assert(t.eval(fact('foo' => [Time.now + 100])))
81
+ assert(!t.eval(fact('foo' => [Time.now - 100])))
82
+ assert(!t.eval(fact('bar' => [100])))
83
83
  end
84
84
 
85
85
  def test_not_matching
86
86
  t = Factbase::Term.new(:not, [Factbase::Term.new(:nil, [])])
87
- assert(!t.matches?(fact('foo' => [100])))
87
+ assert(!t.eval(fact('foo' => [100])))
88
88
  end
89
89
 
90
90
  def test_not_eq_matching
91
91
  t = Factbase::Term.new(:not, [Factbase::Term.new(:eq, [:foo, 100])])
92
- assert(t.matches?(fact('foo' => [42, 12, -90])))
93
- assert(!t.matches?(fact('foo' => 100)))
92
+ assert(t.eval(fact('foo' => [42, 12, -90])))
93
+ assert(!t.eval(fact('foo' => 100)))
94
+ end
95
+
96
+ def test_size_matching
97
+ t = Factbase::Term.new(:size, [:foo])
98
+ assert_equal(3, t.eval(fact('foo' => [42, 12, -90])))
99
+ assert_equal(0, t.eval(fact('bar' => 100)))
94
100
  end
95
101
 
96
102
  def test_exists_matching
97
103
  t = Factbase::Term.new(:exists, [:foo])
98
- assert(t.matches?(fact('foo' => [42, 12, -90])))
99
- assert(!t.matches?(fact('bar' => 100)))
104
+ assert(t.eval(fact('foo' => [42, 12, -90])))
105
+ assert(!t.eval(fact('bar' => 100)))
100
106
  end
101
107
 
102
108
  def test_absent_matching
103
109
  t = Factbase::Term.new(:absent, [:foo])
104
- assert(t.matches?(fact('z' => [42, 12, -90])))
105
- assert(!t.matches?(fact('foo' => 100)))
110
+ assert(t.eval(fact('z' => [42, 12, -90])))
111
+ assert(!t.eval(fact('foo' => 100)))
112
+ end
113
+
114
+ def test_type_matching
115
+ t = Factbase::Term.new(:type, [:foo])
116
+ assert_equal('Integer', t.eval(fact('foo' => 42)))
117
+ assert_equal('Array', t.eval(fact('foo' => [1, 2, 3])))
118
+ assert_equal('String', t.eval(fact('foo' => 'Hello, world!')))
119
+ assert_equal('Float', t.eval(fact('foo' => 3.14)))
120
+ assert_equal('Time', t.eval(fact('foo' => Time.now)))
121
+ assert_equal('nil', t.eval(fact))
106
122
  end
107
123
 
108
124
  def test_or_matching
@@ -113,14 +129,27 @@ class TestTerm < Minitest::Test
113
129
  Factbase::Term.new(:eq, [:bar, 5])
114
130
  ]
115
131
  )
116
- assert(t.matches?(fact('foo' => [4])))
117
- assert(t.matches?(fact('bar' => [5])))
118
- assert(!t.matches?(fact('bar' => [42])))
132
+ assert(t.eval(fact('foo' => [4])))
133
+ assert(t.eval(fact('bar' => [5])))
134
+ assert(!t.eval(fact('bar' => [42])))
135
+ end
136
+
137
+ def test_when_matching
138
+ t = Factbase::Term.new(
139
+ :when,
140
+ [
141
+ Factbase::Term.new(:eq, [:foo, 4]),
142
+ Factbase::Term.new(:eq, [:bar, 5])
143
+ ]
144
+ )
145
+ assert(t.eval(fact('foo' => 4, 'bar' => 5)))
146
+ assert(!t.eval(fact('foo' => 4)))
147
+ assert(t.eval(fact('foo' => 5, 'bar' => 5)))
119
148
  end
120
149
 
121
150
  private
122
151
 
123
- def fact(map)
152
+ def fact(map = {})
124
153
  Factbase::Fact.new(Mutex.new, map)
125
154
  end
126
155
  end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright (c) 2024 Yegor Bugayenko
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ # of this software and associated documentation files (the 'Software'), to deal
7
+ # in the Software without restriction, including without limitation the rights
8
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ # copies of the Software, and to permit persons to whom the Software is
10
+ # furnished to do so, subject to the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be included in all
13
+ # copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFINGEMENT. IN NO EVENT SHALL THE
18
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ # SOFTWARE.
22
+
23
+ require 'minitest/autorun'
24
+ require 'loog'
25
+ require_relative '../../lib/factbase'
26
+ require_relative '../../lib/factbase/to_json'
27
+
28
+ # Test.
29
+ # Author:: Yegor Bugayenko (yegor256@gmail.com)
30
+ # Copyright:: Copyright (c) 2024 Yegor Bugayenko
31
+ # License:: MIT
32
+ class TestToJSON < Minitest::Test
33
+ def test_simple_rendering
34
+ fb = Factbase.new
35
+ f = fb.insert
36
+ f.foo = 42
37
+ f.foo = 256
38
+ to = Factbase::ToJSON.new(fb)
39
+ json = JSON.parse(to.json)
40
+ assert(42, json[0]['foo'][1])
41
+ end
42
+ end
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright (c) 2024 Yegor Bugayenko
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ # of this software and associated documentation files (the 'Software'), to deal
7
+ # in the Software without restriction, including without limitation the rights
8
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ # copies of the Software, and to permit persons to whom the Software is
10
+ # furnished to do so, subject to the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be included in all
13
+ # copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFINGEMENT. IN NO EVENT SHALL THE
18
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ # SOFTWARE.
22
+
23
+ require 'minitest/autorun'
24
+ require 'loog'
25
+ require_relative '../../lib/factbase'
26
+ require_relative '../../lib/factbase/to_xml'
27
+
28
+ # Test.
29
+ # Author:: Yegor Bugayenko (yegor256@gmail.com)
30
+ # Copyright:: Copyright (c) 2024 Yegor Bugayenko
31
+ # License:: MIT
32
+ class TestToXML < Minitest::Test
33
+ def test_simple_rendering
34
+ fb = Factbase.new
35
+ fb.insert.t = Time.now
36
+ to = Factbase::ToXML.new(fb)
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
+ )
43
+ )
44
+ end
45
+
46
+ def test_complex_rendering
47
+ fb = Factbase.new
48
+ fb.insert.t = "\uffff < > & ' \""
49
+ to = Factbase::ToXML.new(fb)
50
+ xml = Nokogiri::XML.parse(to.xml)
51
+ assert(!xml.xpath('/fb/f[t]').empty?)
52
+ end
53
+
54
+ def test_meta_data_presence
55
+ fb = Factbase.new
56
+ fb.insert.x = 42
57
+ to = Factbase::ToXML.new(fb)
58
+ xml = Nokogiri::XML.parse(to.xml)
59
+ assert(!xml.xpath('/fb[@dob]').empty?)
60
+ assert(!xml.xpath('/fb[@factbase_version]').empty?)
61
+ end
62
+
63
+ def test_to_xml_with_short_names
64
+ fb = Factbase.new
65
+ f = fb.insert
66
+ f.type = 1
67
+ f.f = 2
68
+ f.class = 3
69
+ to = Factbase::ToXML.new(fb)
70
+ xml = Nokogiri::XML.parse(to.xml)
71
+ assert(!xml.xpath('/fb/f/type').empty?)
72
+ assert(!xml.xpath('/fb/f/f').empty?)
73
+ assert(!xml.xpath('/fb/f/class').empty?)
74
+ end
75
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright (c) 2024 Yegor Bugayenko
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ # of this software and associated documentation files (the 'Software'), to deal
7
+ # in the Software without restriction, including without limitation the rights
8
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ # copies of the Software, and to permit persons to whom the Software is
10
+ # furnished to do so, subject to the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be included in all
13
+ # copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFINGEMENT. IN NO EVENT SHALL THE
18
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ # SOFTWARE.
22
+
23
+ require 'minitest/autorun'
24
+ require 'loog'
25
+ require_relative '../../lib/factbase'
26
+ require_relative '../../lib/factbase/to_yaml'
27
+
28
+ # Test.
29
+ # Author:: Yegor Bugayenko (yegor256@gmail.com)
30
+ # Copyright:: Copyright (c) 2024 Yegor Bugayenko
31
+ # License:: MIT
32
+ class TestToYAML < Minitest::Test
33
+ def test_simple_rendering
34
+ fb = Factbase.new
35
+ f = fb.insert
36
+ f.foo = 42
37
+ f.foo = 256
38
+ fb.insert
39
+ to = Factbase::ToYAML.new(fb)
40
+ yaml = YAML.load(to.yaml)
41
+ assert_equal(2, yaml['facts'].size)
42
+ assert_equal(42, yaml['facts'][0]['foo'][0])
43
+ assert_equal(256, yaml['facts'][0]['foo'][1])
44
+ end
45
+ end
@@ -21,9 +21,7 @@
21
21
  # SOFTWARE.
22
22
 
23
23
  require 'minitest/autorun'
24
- require 'json'
25
- require 'nokogiri'
26
- require 'yaml'
24
+ require 'loog'
27
25
  require_relative '../lib/factbase'
28
26
 
29
27
  # Factbase main module test.
@@ -59,52 +57,76 @@ class TestFactbase < Minitest::Test
59
57
 
60
58
  def test_empty_or_not
61
59
  fb = Factbase.new
62
- assert(fb.empty?)
60
+ assert_equal(0, fb.size)
63
61
  fb.insert
64
- assert(!fb.empty?)
62
+ assert_equal(1, fb.size)
65
63
  end
66
64
 
67
- def test_to_json
68
- fb = Factbase.new
69
- f = fb.insert
70
- f.foo = 42
71
- f.foo = 256
72
- json = JSON.parse(fb.to_json)
73
- assert(42, json[0]['foo'][1])
65
+ def test_makes_duplicate
66
+ fb1 = Factbase.new
67
+ fb1.insert
68
+ assert_equal(1, fb1.size)
69
+ fb2 = fb1.dup
70
+ fb2.insert
71
+ assert_equal(1, fb1.size)
72
+ assert_equal(2, fb2.size)
74
73
  end
75
74
 
76
- def test_to_xml
75
+ def test_run_txn
77
76
  fb = Factbase.new
78
- f = fb.insert
79
- f.foo = 42
80
- f.foo = 256
81
- fb.insert.bar = 5
82
- xml = Nokogiri::XML.parse(fb.to_xml)
83
- assert(!xml.xpath('/fb[count(f) = 2]').empty?)
84
- assert(!xml.xpath('/fb/f/foo[v="42"]').empty?)
77
+ fb.txn do |fbt|
78
+ fbt.insert.bar = 42
79
+ fbt.insert.z = 42
80
+ end
81
+ assert_equal(2, fb.size)
82
+ assert(
83
+ assert_raises do
84
+ fb.txn do |fbt|
85
+ fbt.insert.foo = 42
86
+ throw 'intentionally'
87
+ end
88
+ end.message.include?('intentionally')
89
+ )
90
+ assert_equal(2, fb.size)
85
91
  end
86
92
 
87
- def test_to_xml_with_short_names
88
- fb = Factbase.new
89
- f = fb.insert
90
- f.type = 1
91
- f.f = 2
92
- f.class = 3
93
- xml = Nokogiri::XML.parse(fb.to_xml)
94
- assert(!xml.xpath('/fb/f/type').empty?)
95
- assert(!xml.xpath('/fb/f/f').empty?)
96
- assert(!xml.xpath('/fb/f/class').empty?)
93
+ def test_run_txn_with_inv
94
+ fb = Factbase::Inv.new(Factbase.new) { |_p, v| throw 'oops' if v == 42 }
95
+ fb.insert.bar = 3
96
+ fb.insert.foo = 5
97
+ assert_equal(2, fb.size)
98
+ assert(
99
+ assert_raises do
100
+ fb.txn do |fbt|
101
+ fbt.insert.foo = 42
102
+ end
103
+ end.message.include?('oops')
104
+ )
105
+ assert_equal(2, fb.size)
97
106
  end
98
107
 
99
- def test_to_yaml
100
- fb = Factbase.new
101
- f = fb.insert
102
- f.foo = 42
103
- f.foo = 256
104
- fb.insert
105
- yaml = YAML.load(fb.to_yaml)
106
- assert_equal(2, yaml['facts'].size)
107
- assert_equal(42, yaml['facts'][0]['foo'][0])
108
- assert_equal(256, yaml['facts'][0]['foo'][1])
108
+ def test_all_decorators
109
+ [
110
+ Factbase::Rules.new(Factbase.new, '()'),
111
+ Factbase::Inv.new(Factbase.new) { |_, _| true },
112
+ Factbase::Pre.new(Factbase.new) { |_| true },
113
+ Factbase::Looged.new(Factbase.new, Loog::NULL),
114
+ Factbase::Spy.new(Factbase.new, 'ff')
115
+ ].each do |d|
116
+ f = d.insert
117
+ f.foo = 42
118
+ d.txn do |fbt|
119
+ fbt.insert.bar = 455
120
+ end
121
+ assert_raises do
122
+ d.txn do |fbt|
123
+ fbt.insert
124
+ throw 'oops'
125
+ end
126
+ end
127
+ d.import(d.export)
128
+ assert_equal(4, d.size)
129
+ assert_equal(4, d.query('()').each.to_a.size)
130
+ end
109
131
  end
110
132
  end