spinoza 0.1 → 0.2

Sign up to get free protection for your applications and to get access to all the features.
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