spinoza 0.1 → 0.2

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.
data/test/test-link.rb ADDED
@@ -0,0 +1,43 @@
1
+ require 'minitest/autorun'
2
+ require 'spinoza/system/link'
3
+
4
+ class TestLink < Minitest::Test
5
+ include Spinoza
6
+
7
+ class MockNode
8
+ attr_reader :msgs
9
+
10
+ def initialize
11
+ @msgs = []
12
+ end
13
+
14
+ def recv msg: nil
15
+ @msgs << msg
16
+ end
17
+ end
18
+
19
+ def setup
20
+ @timeline = Timeline.new
21
+ @node1 = MockNode.new
22
+ @node2 = MockNode.new
23
+ @link = Link[timeline: @timeline,
24
+ src: @node1, dst: @node2, latency: 1.0
25
+ ]
26
+ end
27
+
28
+ def test_link
29
+ @link.send_message "hello"
30
+ assert_equal [], @node2.msgs
31
+ @timeline.evolve 0.9
32
+ assert_equal [], @node2.msgs
33
+ @timeline.evolve 0.2
34
+ assert_equal ["hello"], @node2.msgs
35
+ end
36
+
37
+ def test_fifo
38
+ @link.send_message "foo"
39
+ @link.send_message "bar"
40
+ @timeline.evolve 1.0
41
+ assert_equal ["foo", "bar"], @node2.msgs
42
+ end
43
+ end
data/test/test-log.rb ADDED
@@ -0,0 +1,47 @@
1
+ require 'minitest/autorun'
2
+ require 'spinoza/system/log'
3
+
4
+ class TestLog < Minitest::Test
5
+ include Spinoza
6
+
7
+ class MockNode
8
+ attr_reader :time_now
9
+
10
+ def initialize time_now
11
+ @time_now = time_now
12
+ end
13
+
14
+ def evolve dt
15
+ @time_now += dt
16
+ end
17
+ end
18
+
19
+ def setup
20
+ @log = Log.new dt_durable: 0.300, dt_replicated: 0.500
21
+ @sender = MockNode.new 0.0
22
+ @recver = MockNode.new 0.0
23
+ end
24
+
25
+ def test_log
26
+ @log.write "a", 1, node: @sender
27
+ assert_raises Log::KeyConflictError do
28
+ @log.write "a", 1, node: @sender
29
+ end
30
+
31
+ assert_equal 0.300, @log.time_durable("a")
32
+ assert_equal 0.500, @log.time_replicated("a")
33
+
34
+ assert_equal 1, @log.read("a", node: @sender)
35
+ assert_equal nil, @log.read("a", node: @recver)
36
+
37
+ @sender.evolve 0.200
38
+ refute @log.durable? "a"
39
+ @sender.evolve 0.200
40
+ assert @log.durable? "a"
41
+
42
+ @recver.evolve 0.400
43
+ assert_equal nil, @log.read("a", node: @recver)
44
+ @recver.evolve 0.200
45
+ assert_equal 1, @log.read("a", node: @recver)
46
+ end
47
+ end
@@ -0,0 +1,63 @@
1
+ require 'minitest/autorun'
2
+ require 'spinoza/system/meta-log'
3
+
4
+ class TestMetaLog < Minitest::Test
5
+ include Spinoza
6
+
7
+ class MockNode
8
+ attr_reader :time_now, :timeline
9
+
10
+ def initialize time_now
11
+ @time_now = time_now
12
+ @timeline = [] # note: history and future
13
+ end
14
+
15
+ def evolve dt
16
+ @time_now += dt
17
+ end
18
+ end
19
+
20
+ def setup
21
+ @meta_log = MetaLog.new dt_quorum: 0.300, dt_replicated: 0.500
22
+ @sender = MockNode.new 0.0
23
+ @recver = MockNode.new 0.0
24
+ end
25
+
26
+ def test_meta_log
27
+ id = @meta_log.append "a", node: @sender
28
+
29
+ assert_equal 0.300, @meta_log.time_quorum(id)
30
+ assert_equal 0.500, @meta_log.time_replicated(id)
31
+
32
+ assert_equal "a", @meta_log.get(id, node: @sender)
33
+ assert_equal nil, @meta_log.get(id, node: @recver)
34
+
35
+ @sender.evolve 0.200
36
+ refute @meta_log.quorum? id
37
+ @sender.evolve 0.200
38
+ assert @meta_log.quorum? id
39
+
40
+ @recver.evolve 0.400
41
+ assert_equal nil, @meta_log.get(id, node: @recver)
42
+ @recver.evolve 0.200
43
+ assert_equal "a", @meta_log.get(id, node: @recver)
44
+ end
45
+
46
+ def test_listeners
47
+ listener = []
48
+ @meta_log.on_entry_available listener, :push
49
+
50
+ @meta_log.append "a", node: @sender
51
+ @meta_log.append "b", node: @sender
52
+
53
+ assert_equal 2, @sender.timeline.size
54
+
55
+ e = @sender.timeline[0]
56
+ assert_equal @sender.time_now + 0.500, e.time
57
+ assert_equal "a", e.data[:value]
58
+
59
+ e = @sender.timeline[1]
60
+ assert_equal @sender.time_now + 0.500, e.time
61
+ assert_equal "b", e.data[:value]
62
+ end
63
+ end
data/test/test-node.rb CHANGED
@@ -1,17 +1,18 @@
1
1
  require 'minitest/autorun'
2
2
  require 'spinoza/system/node'
3
3
 
4
- include Spinoza
5
-
6
4
  class TestNode < Minitest::Test
5
+ include Spinoza
6
+
7
7
  def setup
8
- @node = Node.new(
9
- TableSpec.new :foos, id: "integer", name: "string", len: "float"
10
- )
8
+ @node = Node[
9
+ Table[:foos, id: "integer", name: "string", len: "float"],
10
+ timeline: nil
11
+ ]
11
12
  end
12
13
 
13
14
  def test_store
14
- assert_equal [:foos], @node.store.tables
15
+ assert_equal Set[:foos], @node.store.tables
15
16
  end
16
17
 
17
18
  def test_ops
@@ -22,23 +23,18 @@ class TestNode < Minitest::Test
22
23
  rslt = store.execute op_i, op_r
23
24
 
24
25
  assert_equal(1, rslt.size)
25
- r_tuples = rslt[0].val
26
- assert_equal(1, r_tuples.size)
27
- assert_equal({id: 1, name: "a", len: 1.2}, r_tuples[0])
26
+ assert_equal({id: 1, name: "a", len: 1.2}, rslt[0].val)
28
27
 
29
28
  op_u = UpdateOperation.new(table: :foos, key: {id: 1}, row: {name: "b"})
30
29
  rslt = store.execute op_u, op_r
31
30
 
32
31
  assert_equal(1, rslt.size)
33
- r_tuples = rslt[0].val
34
- assert_equal(1, r_tuples.size)
35
- assert_equal({id: 1, name: "b", len: 1.2}, r_tuples[0])
32
+ assert_equal({id: 1, name: "b", len: 1.2}, rslt[0].val)
36
33
 
37
34
  op_d = DeleteOperation.new(table: :foos, key: {id: 1})
38
35
  rslt = store.execute op_d, op_r
39
36
  assert_equal(1, rslt.size)
40
- r_tuples = rslt[0].val
41
- assert_equal(0, r_tuples.size)
37
+ refute rslt[0].val
42
38
  end
43
39
 
44
40
  def test_locks
@@ -50,6 +46,7 @@ class TestNode < Minitest::Test
50
46
  lm.lock_read rs1, :t1
51
47
  assert lm.has_read_lock?(rs1, :t1)
52
48
  refute lm.has_read_lock?(rs1, :t2)
49
+ refute lm.has_read_lock?([:bars, 3], :t1)
53
50
 
54
51
  lm.lock_read rs1, :t2
55
52
  assert lm.has_read_lock?(rs1, :t1)
@@ -76,6 +73,8 @@ class TestNode < Minitest::Test
76
73
  lm.lock_read rs2, :t2
77
74
  end
78
75
  assert lm.has_write_lock?(rs2, :t1)
76
+ refute lm.has_write_lock?(rs2, :t3)
77
+ refute lm.has_write_lock?([:bars, 3], :t1)
79
78
 
80
79
  lm.lock_write rs2, :t1
81
80
  assert lm.has_write_lock?(rs2, :t1)
@@ -84,4 +83,26 @@ class TestNode < Minitest::Test
84
83
  lm.unlock_write rs2, :t1
85
84
  refute lm.has_write_lock?(rs2, :t1)
86
85
  end
86
+
87
+ def test_unlock_all
88
+ lm = @node.lock_manager
89
+
90
+ rs1 = [:foos, 1]
91
+ rs2 = [:foos, 2]
92
+
93
+ lm.lock_read rs1, :t1
94
+ lm.lock_write rs2, :t2
95
+
96
+ lm.unlock_all :t1
97
+ refute lm.has_write_lock?(rs1, :t1)
98
+ refute lm.has_write_lock?(rs2, :t1)
99
+ refute lm.has_write_lock?(rs1, :t2)
100
+ assert lm.has_write_lock?(rs2, :t2)
101
+
102
+ lm.unlock_all :t2
103
+ refute lm.has_write_lock?(rs1, :t1)
104
+ refute lm.has_write_lock?(rs2, :t1)
105
+ refute lm.has_write_lock?(rs1, :t2)
106
+ refute lm.has_write_lock?(rs2, :t2)
107
+ end
87
108
  end
@@ -0,0 +1,87 @@
1
+ require 'minitest/autorun'
2
+ require 'spinoza/calvin/readcaster'
3
+ require 'spinoza/system/node'
4
+ require 'spinoza/system/link'
5
+ require 'spinoza/system/timeline'
6
+ require 'spinoza/transaction'
7
+
8
+ class TestReadcaster < Minitest::Test
9
+ include Spinoza
10
+
11
+ def setup
12
+ @timeline = Spinoza::Timeline.new
13
+
14
+ @node = Node[
15
+ Table[:as, id: "integer", name: "string"],
16
+ Table[:bs, id: "integer", name: "string"],
17
+ timeline: @timeline
18
+ ]
19
+
20
+ @na = Node[
21
+ Table[:as, id: "integer", name: "string"],
22
+ timeline: @timeline
23
+ ]
24
+
25
+ @nb = Node[
26
+ Table[:bs, id: "integer", name: "string"],
27
+ timeline: @timeline
28
+ ]
29
+
30
+ @node.link @na, latency: 1.0
31
+ @node.link @nb, latency: 1.0
32
+
33
+ store = @node.store
34
+
35
+ op_ia = InsertOperation.new(table: :as, row: {id: 2, name: "a2"})
36
+ op_ib = InsertOperation.new(table: :bs, row: {id: 2, name: "b2"})
37
+ store.execute op_ia, op_ib
38
+
39
+ # normally this is part of a Calvin::Node, but here we have a System::Node
40
+ @readcaster = Calvin::Readcaster.new(node: @node)
41
+
42
+ @txn = transaction do
43
+ at(:as).insert id: 1, name: "a1"
44
+ at(:as, id: 2).read
45
+ at(:bs, id: 2).read
46
+ end
47
+ end
48
+
49
+ def test_readcaster
50
+ local_read_results = @readcaster.execute_local_reads @txn
51
+ assert_equal 2, local_read_results.size
52
+ assert_equal "a2", local_read_results[0].val[:name]
53
+ assert_equal "b2", local_read_results[1].val[:name]
54
+ end
55
+
56
+ def test_serve_reads
57
+ results = []
58
+ class << @readcaster; self; end.send :define_method,
59
+ :send_read do |link, **opts|
60
+ results << [link, opts]
61
+ end
62
+
63
+ local_read_results = @readcaster.execute_local_reads @txn
64
+ @readcaster.serve_reads @txn, local_read_results
65
+
66
+ assert_equal 1, results.size
67
+ link, opts = results.first
68
+
69
+ assert_equal @node.links[@na], link
70
+ table, read_results = opts.values_at(:table, :read_results)
71
+ assert_equal :bs, table
72
+ assert_equal 1, read_results.size
73
+ assert_equal "b2", read_results[0].val[:name]
74
+ end
75
+
76
+ def test_all_reads_local
77
+ assert @txn.all_reads_are_local? @node
78
+ refute @txn.all_reads_are_local? @na
79
+ refute @txn.all_reads_are_local? @nb
80
+ end
81
+
82
+ def test_remote_read_tables
83
+ assert_equal Set[], @txn.remote_read_tables(@node)
84
+ assert_equal Set[:bs], @txn.remote_read_tables(@na)
85
+ assert_equal Set[:as], @txn.remote_read_tables(@nb)
86
+ end
87
+ end
@@ -0,0 +1,163 @@
1
+ require 'minitest/autorun'
2
+ require 'spinoza/transaction'
3
+ require 'spinoza/system/timeline'
4
+ require 'spinoza/system/log'
5
+ require 'spinoza/system/meta-log'
6
+ require 'spinoza/calvin/node'
7
+
8
+ #===========#
9
+ # DEBUGGING #
10
+ #===========#
11
+ #
12
+ # require 'pp'
13
+ # pp @results
14
+ # pp @timeline.history.select {|time, event| event.action != :step_epoch}
15
+
16
+ class TestScheduler < Minitest::Test
17
+ include Spinoza
18
+
19
+ def mkresults node, txn, rslt
20
+ {
21
+ transaction: txn,
22
+ time: node.timeline.now,
23
+ values: rslt.map {|rr| rr.val}
24
+ }
25
+ end
26
+
27
+ FOOS = Table[:foos, id: "integer", name: "string"]
28
+ BARS = Table[:bars, id: "integer", name: "string"]
29
+
30
+ # spec is an array of arrays of Tables, such as [ [FOOS], [FOOS, BARS] ]
31
+ def mknodes spec
32
+ @nodes = spec.map.with_index do |tables, i|
33
+ Calvin::Node[
34
+ *tables,
35
+ name: i,
36
+ timeline: @timeline,
37
+ log: @log,
38
+ meta_log: @meta_log,
39
+ sequencer: nil, # so the default will get created
40
+ scheduler: nil ## TODO dependency injection
41
+ ]
42
+ end
43
+
44
+ @results = {}
45
+ @nodes.each do |node|
46
+ rs = @results[node] = []
47
+ node.on_transaction_finish do |txn, rslt|
48
+ rs << mkresults(node, txn, rslt)
49
+ #@node.default_output txn, rslt
50
+ end
51
+ end
52
+ end
53
+
54
+ def setup
55
+ @timeline = Timeline.new
56
+ @log = Log.new dt_durable: 0.300, dt_replicated: 0.500
57
+ @meta_log = MetaLog.new dt_quorum: 0.300, dt_replicated: 0.500
58
+ end
59
+
60
+ def test_one_node
61
+ mknodes [ [FOOS] ]
62
+
63
+ txn1 = transaction do
64
+ at(:foos).insert id: 1, name: "a"
65
+ at(:foos).insert id: 2, name: "b"
66
+ at(:foos).insert id: 3, name: "c"
67
+ end
68
+
69
+ txn2 = transaction do
70
+ at(:foos, id: 1).read
71
+ at(:foos, id: 2).delete
72
+ at(:foos, id: 3).update name: "cc"
73
+ end
74
+
75
+ txn3 = transaction do
76
+ at(:foos, id: 1).read
77
+ at(:foos, id: 2).read
78
+ at(:foos, id: 3).read
79
+ end
80
+
81
+ @nodes[0].sequencer.accept_transaction txn1
82
+ @nodes[0].sequencer.accept_transaction txn2
83
+ @nodes[0].sequencer.accept_transaction txn3
84
+
85
+ @timeline.evolve 1.0
86
+
87
+ rs = @results[@nodes[0]]
88
+ assert_equal [0.81, 0.81, 0.81], rs.map{|r| r[:time]}
89
+
90
+ assert_empty rs[0][:values]
91
+ assert_equal [{id: 1, name: "a"}], rs[1][:values]
92
+ assert_equal [{id: 1, name: "a"}, nil, {id: 3, name: "cc"}],
93
+ rs[2][:values]
94
+ end
95
+
96
+ def test_two_nodes_same_tables
97
+ mknodes [ [FOOS], [FOOS] ]
98
+
99
+ txn1 = transaction do
100
+ at(:foos).insert id: 1, name: "a"
101
+ at(:foos).insert id: 2, name: "b"
102
+ at(:foos).insert id: 3, name: "c"
103
+ end
104
+
105
+ txn2 = transaction do
106
+ at(:foos, id: 1).read
107
+ at(:foos, id: 2).delete
108
+ at(:foos, id: 3).update name: "cc"
109
+ end
110
+
111
+ txn3 = transaction do
112
+ at(:foos, id: 1).read
113
+ at(:foos, id: 2).read
114
+ at(:foos, id: 3).read
115
+ end
116
+
117
+ @nodes[0].sequencer.accept_transaction txn1
118
+ @nodes[0].sequencer.accept_transaction txn2
119
+ @nodes[0].sequencer.accept_transaction txn3
120
+
121
+ @timeline.evolve 1.0
122
+
123
+ @results.each do |node, rs|
124
+ desc = node.inspect
125
+ assert_equal [0.81, 0.81, 0.81], rs.map{|r| r[:time]}, desc
126
+
127
+ assert_empty rs[0][:values], desc
128
+ assert_equal [{id: 1, name: "a"}], rs[1][:values], desc
129
+ assert_equal [{id: 1, name: "a"}, nil, {id: 3, name: "cc"}],
130
+ rs[2][:values], desc
131
+ end
132
+ end
133
+
134
+ def test_remote_reads
135
+ mknodes [ [FOOS], [BARS] ]
136
+
137
+ @nodes[0].link @nodes[1], latency: 0.010
138
+
139
+ txn1 = transaction do
140
+ at(:foos).insert id: 1, name: "a"
141
+ at(:bars).insert id: 2, name: "b"
142
+ end
143
+
144
+ txn2 = transaction do
145
+ at(:foos, id: 1).read
146
+ at(:bars, id: 2).update name: "bb"
147
+ # write must be present or else msg to inactive node is optimized away
148
+ end
149
+
150
+ @nodes[0].sequencer.accept_transaction txn1
151
+ @nodes[0].sequencer.accept_transaction txn2
152
+
153
+ @timeline.evolve 2.0
154
+
155
+ rs = @results[@nodes[1]]
156
+
157
+ assert_equal 0.81, rs[0][:time]
158
+ assert_empty rs[0][:values]
159
+
160
+ assert_equal 0.81 + 0.010, rs[1][:time]
161
+ assert_equal [{id: 1, name: "a"}], rs[1][:values]
162
+ end
163
+ end