semian 0.0.8 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data/.travis.yml +4 -1
- data/README.md +1 -1
- data/Rakefile +7 -6
- data/ext/semian/semian.c +7 -13
- data/lib/semian/circuit_breaker.rb +135 -0
- data/lib/semian/instrumentable.rb +22 -0
- data/lib/semian/mysql2.rb +77 -0
- data/lib/semian/platform.rb +2 -2
- data/lib/semian/protected_resource.rb +36 -0
- data/lib/semian/resource.rb +26 -0
- data/lib/semian/version.rb +2 -2
- data/lib/semian.rb +80 -27
- data/scripts/install_toxiproxy.sh +25 -0
- data/semian.gemspec +3 -0
- data/test/fixtures/toxiproxy.json +7 -0
- data/test/test_circuit_breaker.rb +114 -0
- data/test/test_instrumentation.rb +62 -0
- data/test/test_mysql2.rb +186 -0
- data/test/{test_semian.rb → test_resource.rb} +73 -55
- data/test/test_unsupported.rb +7 -3
- data.tar.gz.sig +0 -0
- metadata +55 -4
- metadata.gz.sig +1 -2
- data/lib/semian/unsupported.rb +0 -26
@@ -0,0 +1,114 @@
|
|
1
|
+
require 'minitest/autorun'
|
2
|
+
require 'semian'
|
3
|
+
require 'timecop'
|
4
|
+
|
5
|
+
class TestCircuitBreaker < MiniTest::Unit::TestCase
|
6
|
+
SomeError = Class.new(StandardError)
|
7
|
+
|
8
|
+
def setup
|
9
|
+
Semian.destroy(:testing) rescue nil
|
10
|
+
Semian.register(:testing, tickets: 1, exceptions: [SomeError], error_threshold: 2, error_timeout: 5, success_threshold: 1)
|
11
|
+
@resource = Semian[:testing]
|
12
|
+
end
|
13
|
+
|
14
|
+
def test_with_fallback_value_returns_the_value
|
15
|
+
result = @resource.with_fallback(42) do
|
16
|
+
raise SomeError
|
17
|
+
end
|
18
|
+
assert_equal 42, result
|
19
|
+
end
|
20
|
+
|
21
|
+
def test_with_fallback_block_call_the_block
|
22
|
+
result = @resource.with_fallback(-> { 42 }) do
|
23
|
+
raise SomeError
|
24
|
+
end
|
25
|
+
assert_equal 42, result
|
26
|
+
end
|
27
|
+
|
28
|
+
def test_unknown_exceptions_are_not_rescued
|
29
|
+
assert_raises RuntimeError do
|
30
|
+
@resource.with_fallback(42) do
|
31
|
+
raise RuntimeError
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def test_all_semian_exceptions_are_rescued
|
37
|
+
result = @resource.with_fallback(42) do
|
38
|
+
raise Semian::BaseError
|
39
|
+
end
|
40
|
+
assert_equal 42, result
|
41
|
+
end
|
42
|
+
|
43
|
+
def test_acquire_yield_when_the_circuit_is_closed
|
44
|
+
block_called = false
|
45
|
+
@resource.acquire { block_called = true }
|
46
|
+
assert_equal true, block_called
|
47
|
+
end
|
48
|
+
|
49
|
+
def test_acquire_raises_circuit_open_error_when_the_circuit_is_open
|
50
|
+
open_circuit!
|
51
|
+
assert_raises Semian::OpenCircuitError do
|
52
|
+
@resource.acquire { 1 + 1 }
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def test_after_error_threshold_the_circuit_is_open
|
57
|
+
open_circuit!
|
58
|
+
assert_circuit_opened
|
59
|
+
end
|
60
|
+
|
61
|
+
def test_after_error_timeout_is_elapsed_requests_are_attempted_again
|
62
|
+
half_open_cicuit!
|
63
|
+
assert_circuit_closed
|
64
|
+
end
|
65
|
+
|
66
|
+
def test_until_success_threshold_is_reached_a_single_error_will_reopen_the_circuit
|
67
|
+
half_open_cicuit!
|
68
|
+
trigger_error!
|
69
|
+
assert_circuit_opened
|
70
|
+
end
|
71
|
+
|
72
|
+
def test_once_success_threshold_is_reached_only_error_threshold_will_open_the_circuit_again
|
73
|
+
half_open_cicuit!
|
74
|
+
assert_circuit_closed
|
75
|
+
trigger_error!
|
76
|
+
assert_circuit_closed
|
77
|
+
trigger_error!
|
78
|
+
assert_circuit_opened
|
79
|
+
end
|
80
|
+
|
81
|
+
def test_reset_allow_to_close_the_circuit_and_forget_errors
|
82
|
+
open_circuit!
|
83
|
+
@resource.reset
|
84
|
+
assert_circuit_closed
|
85
|
+
end
|
86
|
+
|
87
|
+
private
|
88
|
+
|
89
|
+
def open_circuit!
|
90
|
+
2.times { trigger_error! }
|
91
|
+
end
|
92
|
+
|
93
|
+
def half_open_cicuit!
|
94
|
+
Timecop.travel(Time.now - 10) do
|
95
|
+
open_circuit!
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
def trigger_error!
|
100
|
+
@resource.with_fallback(42) { raise SomeError }
|
101
|
+
end
|
102
|
+
|
103
|
+
def assert_circuit_closed
|
104
|
+
block_called = false
|
105
|
+
@resource.with_fallback(42) { block_called = true }
|
106
|
+
assert block_called, 'Expected the circuit to be closed, but it was open'
|
107
|
+
end
|
108
|
+
|
109
|
+
def assert_circuit_opened
|
110
|
+
block_called = false
|
111
|
+
@resource.with_fallback(42) { block_called = true }
|
112
|
+
refute block_called, 'Expected the circuit to be open, but it was closed'
|
113
|
+
end
|
114
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
require 'minitest/autorun'
|
2
|
+
require 'semian'
|
3
|
+
|
4
|
+
class TestInstrumentation < MiniTest::Unit::TestCase
|
5
|
+
def setup
|
6
|
+
Semian.destroy(:testing) if Semian[:testing]
|
7
|
+
Semian.register(:testing, tickets: 1, error_threshold: 1, error_timeout: 5, success_threshold: 1)
|
8
|
+
end
|
9
|
+
|
10
|
+
def test_occupied_instrumentation
|
11
|
+
assert_notify(:success, :occupied) do
|
12
|
+
Semian[:testing].acquire do
|
13
|
+
assert_raises Semian::TimeoutError do
|
14
|
+
Semian[:testing].acquire {}
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def test_circuit_open_instrumentation
|
21
|
+
assert_notify(:success, :occupied) do
|
22
|
+
Semian[:testing].acquire do
|
23
|
+
assert_raises Semian::TimeoutError do
|
24
|
+
Semian[:testing].acquire {}
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
assert_notify(:circuit_open) do
|
30
|
+
assert_raises Semian::OpenCircuitError do
|
31
|
+
Semian[:testing].acquire {}
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def test_success_instrumentation
|
37
|
+
assert_notify(:success) do
|
38
|
+
Semian[:testing].acquire {}
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def test_success_instrumentation_when_unknown_exceptions_occur
|
43
|
+
assert_notify(:success) do
|
44
|
+
assert_raises RuntimeError do
|
45
|
+
Semian[:testing].acquire { raise "Some error" }
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
def assert_notify(*expected_events)
|
53
|
+
events = []
|
54
|
+
subscription = Semian.subscribe do |event, resource|
|
55
|
+
events << event
|
56
|
+
end
|
57
|
+
yield
|
58
|
+
assert_equal expected_events, events, "The timeline of events was not as expected"
|
59
|
+
ensure
|
60
|
+
Semian.unsubscribe(subscription)
|
61
|
+
end
|
62
|
+
end
|
data/test/test_mysql2.rb
ADDED
@@ -0,0 +1,186 @@
|
|
1
|
+
require 'minitest/autorun'
|
2
|
+
require 'semian/mysql2'
|
3
|
+
require 'toxiproxy'
|
4
|
+
require 'timecop'
|
5
|
+
|
6
|
+
class TestMysql2 < MiniTest::Unit::TestCase
|
7
|
+
ERROR_TIMEOUT = 5
|
8
|
+
ERROR_THRESHOLD = 1
|
9
|
+
SEMIAN_OPTIONS = {
|
10
|
+
name: :testing,
|
11
|
+
tickets: 1,
|
12
|
+
timeout: 0,
|
13
|
+
error_threshold: ERROR_THRESHOLD,
|
14
|
+
success_threshold: 2,
|
15
|
+
error_timeout: ERROR_TIMEOUT,
|
16
|
+
}
|
17
|
+
|
18
|
+
attr_writer :threads
|
19
|
+
def setup
|
20
|
+
@proxy = Toxiproxy[:semian_test_mysql]
|
21
|
+
Semian.destroy(:mysql_testing)
|
22
|
+
end
|
23
|
+
|
24
|
+
def teardown
|
25
|
+
threads.each { |t| t.kill }
|
26
|
+
self.threads = []
|
27
|
+
end
|
28
|
+
|
29
|
+
def test_semian_identifier
|
30
|
+
assert_equal :mysql_foo, FakeMysql.new(semian: {name: 'foo'}).semian_identifier
|
31
|
+
assert_equal :'mysql_localhost:3306', FakeMysql.new.semian_identifier
|
32
|
+
assert_equal :'mysql_127.0.0.1:3306', FakeMysql.new(host: '127.0.0.1').semian_identifier
|
33
|
+
assert_equal :'mysql_example.com:42', FakeMysql.new(host: 'example.com', port: 42).semian_identifier
|
34
|
+
end
|
35
|
+
|
36
|
+
def test_connect_instrumentation
|
37
|
+
notified = false
|
38
|
+
subscriber = Semian.subscribe do |event, resource, scope|
|
39
|
+
notified = true
|
40
|
+
assert_equal :success, event
|
41
|
+
assert_equal Semian[:mysql_testing], resource
|
42
|
+
assert_equal :connect, scope
|
43
|
+
end
|
44
|
+
|
45
|
+
connect_to_mysql!
|
46
|
+
|
47
|
+
assert notified, 'No notification have been emitted'
|
48
|
+
ensure
|
49
|
+
Semian.unsubscribe(subscriber)
|
50
|
+
end
|
51
|
+
|
52
|
+
def test_resource_acquisition_for_connect
|
53
|
+
client = connect_to_mysql!
|
54
|
+
|
55
|
+
Semian[:mysql_testing].acquire do
|
56
|
+
assert_raises Mysql2::ResourceOccupiedError do
|
57
|
+
connect_to_mysql!
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def test_resource_timeout_on_connect
|
63
|
+
@proxy.downstream(:latency, latency: 500).apply do
|
64
|
+
background { connect_to_mysql! }
|
65
|
+
|
66
|
+
assert_raises Mysql2::ResourceOccupiedError do
|
67
|
+
connect_to_mysql!
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def test_circuit_breaker_on_connect
|
73
|
+
@proxy.downstream(:latency, latency: 500).apply do
|
74
|
+
background { connect_to_mysql! }
|
75
|
+
|
76
|
+
ERROR_THRESHOLD.times do
|
77
|
+
assert_raises Mysql2::ResourceOccupiedError do
|
78
|
+
connect_to_mysql!
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
yield_to_background
|
84
|
+
|
85
|
+
assert_raises Mysql2::CircuitOpenError do
|
86
|
+
connect_to_mysql!
|
87
|
+
end
|
88
|
+
|
89
|
+
Timecop.travel(ERROR_TIMEOUT + 1) do
|
90
|
+
connect_to_mysql!
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def test_query_instrumentation
|
95
|
+
client = connect_to_mysql!
|
96
|
+
|
97
|
+
notified = false
|
98
|
+
subscriber = Semian.subscribe do |event, resource, scope|
|
99
|
+
notified = true
|
100
|
+
assert_equal :success, event
|
101
|
+
assert_equal Semian[:mysql_testing], resource
|
102
|
+
assert_equal :query, scope
|
103
|
+
end
|
104
|
+
|
105
|
+
client.query('SELECT 1 + 1;')
|
106
|
+
|
107
|
+
assert notified, 'No notification have been emitted'
|
108
|
+
ensure
|
109
|
+
Semian.unsubscribe(subscriber)
|
110
|
+
end
|
111
|
+
|
112
|
+
def test_resource_acquisition_for_query
|
113
|
+
client = connect_to_mysql!
|
114
|
+
|
115
|
+
Semian[:mysql_testing].acquire do
|
116
|
+
assert_raises Mysql2::ResourceOccupiedError do
|
117
|
+
client.query('SELECT 1 + 1;')
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
def test_resource_timeout_on_query
|
123
|
+
client = connect_to_mysql!
|
124
|
+
client2 = connect_to_mysql!
|
125
|
+
|
126
|
+
@proxy.downstream(:latency, latency: 500).apply do
|
127
|
+
background { client2.query('SELECT 1 + 1;') }
|
128
|
+
|
129
|
+
assert_raises Mysql2::ResourceOccupiedError do
|
130
|
+
client.query('SELECT 1 + 1;')
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
def test_circuit_breaker_on_query
|
136
|
+
client = connect_to_mysql!
|
137
|
+
client2 = connect_to_mysql!
|
138
|
+
|
139
|
+
@proxy.downstream(:latency, latency: 1000).apply do
|
140
|
+
background { client2.query('SELECT 1 + 1;') }
|
141
|
+
|
142
|
+
ERROR_THRESHOLD.times do
|
143
|
+
assert_raises Mysql2::ResourceOccupiedError do
|
144
|
+
client.query('SELECT 1 + 1;')
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
yield_to_background
|
150
|
+
|
151
|
+
assert_raises Mysql2::CircuitOpenError do
|
152
|
+
client.query('SELECT 1 + 1;')
|
153
|
+
end
|
154
|
+
|
155
|
+
Timecop.travel(ERROR_TIMEOUT + 1) do
|
156
|
+
assert_equal 2, client.query('SELECT 1 + 1 as sum;').to_a.first['sum']
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
private
|
161
|
+
|
162
|
+
def background(&block)
|
163
|
+
thread = Thread.new(&block)
|
164
|
+
threads << thread
|
165
|
+
thread.join(0.1)
|
166
|
+
thread
|
167
|
+
end
|
168
|
+
|
169
|
+
def threads
|
170
|
+
@threads ||= []
|
171
|
+
end
|
172
|
+
|
173
|
+
def yield_to_background
|
174
|
+
threads.each(&:join)
|
175
|
+
end
|
176
|
+
|
177
|
+
def connect_to_mysql!(semian_options = {})
|
178
|
+
Mysql2::Client.new(host: '127.0.0.1', port: '13306', semian: SEMIAN_OPTIONS.merge(semian_options))
|
179
|
+
end
|
180
|
+
|
181
|
+
class FakeMysql < Mysql2::Client
|
182
|
+
private
|
183
|
+
def connect(*)
|
184
|
+
end
|
185
|
+
end
|
186
|
+
end
|
@@ -1,25 +1,29 @@
|
|
1
|
-
require '
|
1
|
+
require 'minitest/autorun'
|
2
2
|
require 'semian'
|
3
3
|
require 'tempfile'
|
4
4
|
require 'fileutils'
|
5
5
|
|
6
|
-
class
|
6
|
+
class TestResource < MiniTest::Unit::TestCase
|
7
7
|
def setup
|
8
|
-
Semian
|
8
|
+
Semian.destroy(:testing) rescue nil
|
9
9
|
end
|
10
10
|
|
11
|
-
def
|
11
|
+
def teardown
|
12
|
+
destroy_resources
|
13
|
+
end
|
14
|
+
|
15
|
+
def test_initialize_invalid_args
|
12
16
|
assert_raises TypeError do
|
13
|
-
|
17
|
+
create_resource 123, tickets: 2
|
14
18
|
end
|
15
19
|
assert_raises ArgumentError do
|
16
|
-
|
20
|
+
create_resource :testing, tickets: -1
|
17
21
|
end
|
18
22
|
assert_raises ArgumentError do
|
19
|
-
|
23
|
+
create_resource :testing, tickets: 1_000_000
|
20
24
|
end
|
21
25
|
assert_raises TypeError do
|
22
|
-
|
26
|
+
create_resource :testing, tickets: 2, permissions: 'test'
|
23
27
|
end
|
24
28
|
end
|
25
29
|
|
@@ -28,30 +32,30 @@ class TestSemian < Test::Unit::TestCase
|
|
28
32
|
end
|
29
33
|
|
30
34
|
def test_register
|
31
|
-
|
35
|
+
create_resource :testing, tickets: 2
|
32
36
|
end
|
33
37
|
|
34
38
|
def test_register_with_no_tickets_raises
|
35
39
|
assert_raises Semian::SyscallError do
|
36
|
-
|
40
|
+
create_resource :testing, tickets: 0
|
37
41
|
end
|
38
42
|
end
|
39
43
|
|
40
44
|
def test_acquire
|
41
45
|
acquired = false
|
42
|
-
|
43
|
-
|
46
|
+
resource = create_resource :testing, tickets: 1
|
47
|
+
resource.acquire { acquired = true }
|
44
48
|
assert acquired
|
45
49
|
end
|
46
50
|
|
47
51
|
def test_acquire_return_val
|
48
|
-
|
49
|
-
val =
|
52
|
+
resource = create_resource :testing, tickets: 1
|
53
|
+
val = resource.acquire { 1234 }
|
50
54
|
assert_equal 1234, val
|
51
55
|
end
|
52
56
|
|
53
57
|
def test_acquire_timeout
|
54
|
-
|
58
|
+
resource = create_resource :testing, tickets: 1, timeout: 0.05
|
55
59
|
|
56
60
|
acquired = false
|
57
61
|
m = Monitor.new
|
@@ -61,12 +65,12 @@ class TestSemian < Test::Unit::TestCase
|
|
61
65
|
m.synchronize do
|
62
66
|
cond.wait_until { acquired }
|
63
67
|
assert_raises Semian::TimeoutError do
|
64
|
-
|
68
|
+
resource.acquire { refute true }
|
65
69
|
end
|
66
70
|
end
|
67
71
|
end
|
68
72
|
|
69
|
-
|
73
|
+
resource.acquire do
|
70
74
|
acquired = true
|
71
75
|
m.synchronize { cond.signal }
|
72
76
|
sleep 0.2
|
@@ -78,7 +82,7 @@ class TestSemian < Test::Unit::TestCase
|
|
78
82
|
end
|
79
83
|
|
80
84
|
def test_acquire_timeout_override
|
81
|
-
|
85
|
+
resource = create_resource :testing, tickets: 1, timeout: 0.01
|
82
86
|
|
83
87
|
acquired = false
|
84
88
|
thread_acquired = false
|
@@ -88,11 +92,11 @@ class TestSemian < Test::Unit::TestCase
|
|
88
92
|
t = Thread.start do
|
89
93
|
m.synchronize do
|
90
94
|
cond.wait_until { acquired }
|
91
|
-
|
95
|
+
resource.acquire(timeout: 1) { thread_acquired = true }
|
92
96
|
end
|
93
97
|
end
|
94
98
|
|
95
|
-
|
99
|
+
resource.acquire do
|
96
100
|
acquired = true
|
97
101
|
m.synchronize { cond.signal }
|
98
102
|
sleep 0.2
|
@@ -105,14 +109,13 @@ class TestSemian < Test::Unit::TestCase
|
|
105
109
|
end
|
106
110
|
|
107
111
|
def test_acquire_with_fork
|
108
|
-
|
112
|
+
resource = create_resource :testing, tickets: 2, timeout: 0.5
|
109
113
|
|
110
|
-
|
114
|
+
resource.acquire do
|
111
115
|
pid = fork do
|
112
|
-
|
113
|
-
Semian[:testing].acquire do
|
116
|
+
resource.acquire do
|
114
117
|
assert_raises Semian::TimeoutError do
|
115
|
-
|
118
|
+
resource.acquire { }
|
116
119
|
end
|
117
120
|
end
|
118
121
|
end
|
@@ -123,7 +126,7 @@ class TestSemian < Test::Unit::TestCase
|
|
123
126
|
|
124
127
|
def test_acquire_releases_on_kill
|
125
128
|
begin
|
126
|
-
|
129
|
+
resource = create_resource :testing, tickets: 1, timeout: 0.1
|
127
130
|
acquired = false
|
128
131
|
|
129
132
|
# Ghetto process synchronization
|
@@ -132,7 +135,7 @@ class TestSemian < Test::Unit::TestCase
|
|
132
135
|
file.close!
|
133
136
|
|
134
137
|
pid = fork do
|
135
|
-
|
138
|
+
resource.acquire do
|
136
139
|
FileUtils.touch(path)
|
137
140
|
sleep 1000
|
138
141
|
end
|
@@ -140,11 +143,11 @@ class TestSemian < Test::Unit::TestCase
|
|
140
143
|
|
141
144
|
sleep 0.1 until File.exists?(path)
|
142
145
|
assert_raises Semian::TimeoutError do
|
143
|
-
|
146
|
+
resource.acquire {}
|
144
147
|
end
|
145
148
|
|
146
149
|
Process.kill("KILL", pid)
|
147
|
-
|
150
|
+
resource.acquire { acquired = true }
|
148
151
|
assert acquired
|
149
152
|
|
150
153
|
Process.wait
|
@@ -154,50 +157,51 @@ class TestSemian < Test::Unit::TestCase
|
|
154
157
|
end
|
155
158
|
|
156
159
|
def test_count
|
157
|
-
|
160
|
+
resource = create_resource :testing, tickets: 2
|
158
161
|
acquired = false
|
159
162
|
|
160
|
-
|
163
|
+
resource.acquire do
|
161
164
|
acquired = true
|
162
|
-
assert_equal 1,
|
165
|
+
assert_equal 1, resource.count
|
166
|
+
assert_equal 2, resource.tickets
|
163
167
|
end
|
164
168
|
|
165
169
|
assert acquired
|
166
170
|
end
|
167
171
|
|
168
172
|
def test_sem_undo
|
169
|
-
|
173
|
+
resource = create_resource :testing, tickets: 1
|
170
174
|
|
171
175
|
# Ensure we don't hit ERANGE errors caused by lack of SEM_UNDO on semop* calls
|
172
176
|
# by doing an acquire > SEMVMX (32767) times:
|
173
177
|
#
|
174
178
|
# See: http://lxr.free-electrons.com/source/ipc/sem.c?v=3.8#L419
|
175
179
|
(1 << 16).times do # do an acquire 64k times
|
176
|
-
|
180
|
+
resource.acquire do
|
177
181
|
1
|
178
182
|
end
|
179
183
|
end
|
180
184
|
end
|
181
185
|
|
182
186
|
def test_destroy
|
183
|
-
|
184
|
-
|
187
|
+
resource = create_resource :testing, tickets: 1
|
188
|
+
resource.destroy
|
185
189
|
assert_raises Semian::SyscallError do
|
186
|
-
|
190
|
+
resource.acquire { }
|
187
191
|
end
|
188
192
|
end
|
189
193
|
|
190
194
|
def test_permissions
|
191
|
-
|
192
|
-
semid =
|
195
|
+
resource = create_resource :testing, permissions: 0600, tickets: 1
|
196
|
+
semid = resource.semid
|
193
197
|
`ipcs -s `.lines.each do |line|
|
194
198
|
if /\s#{semid}\s/.match(line)
|
195
199
|
assert_equal '600', line.split[3]
|
196
200
|
end
|
197
201
|
end
|
198
202
|
|
199
|
-
|
200
|
-
semid =
|
203
|
+
resource = create_resource :testing, permissions: 0660, tickets: 1
|
204
|
+
semid = resource.semid
|
201
205
|
`ipcs -s `.lines.each do |line|
|
202
206
|
if /\s#{semid}\s/.match(line)
|
203
207
|
assert_equal '660', line.split[3]
|
@@ -206,7 +210,7 @@ class TestSemian < Test::Unit::TestCase
|
|
206
210
|
end
|
207
211
|
|
208
212
|
def test_resize_tickets_increase
|
209
|
-
|
213
|
+
resource = create_resource :testing, tickets: 1
|
210
214
|
|
211
215
|
acquired = false
|
212
216
|
m = Monitor.new
|
@@ -216,14 +220,14 @@ class TestSemian < Test::Unit::TestCase
|
|
216
220
|
m.synchronize do
|
217
221
|
cond.wait_until { acquired }
|
218
222
|
|
219
|
-
|
220
|
-
assert_equal 4,
|
223
|
+
resource = create_resource :testing, tickets: 5
|
224
|
+
assert_equal 4, resource.count
|
221
225
|
end
|
222
226
|
end
|
223
227
|
|
224
|
-
assert_equal 1,
|
228
|
+
assert_equal 1, resource.count
|
225
229
|
|
226
|
-
|
230
|
+
resource.acquire do
|
227
231
|
acquired = true
|
228
232
|
m.synchronize { cond.signal }
|
229
233
|
sleep 0.2
|
@@ -231,11 +235,11 @@ class TestSemian < Test::Unit::TestCase
|
|
231
235
|
|
232
236
|
t.join
|
233
237
|
|
234
|
-
assert_equal 5,
|
238
|
+
assert_equal 5, resource.count
|
235
239
|
end
|
236
240
|
|
237
241
|
def test_resize_tickets_decrease
|
238
|
-
|
242
|
+
resource = create_resource :testing, tickets: 5
|
239
243
|
|
240
244
|
acquired = false
|
241
245
|
m = Monitor.new
|
@@ -245,14 +249,14 @@ class TestSemian < Test::Unit::TestCase
|
|
245
249
|
m.synchronize do
|
246
250
|
cond.wait_until { acquired }
|
247
251
|
|
248
|
-
|
249
|
-
assert_equal 0,
|
252
|
+
resource = create_resource :testing, tickets: 1
|
253
|
+
assert_equal 0, resource.count
|
250
254
|
end
|
251
255
|
end
|
252
256
|
|
253
|
-
assert_equal 5,
|
257
|
+
assert_equal 5, resource.count
|
254
258
|
|
255
|
-
|
259
|
+
resource.acquire do
|
256
260
|
acquired = true
|
257
261
|
m.synchronize { cond.signal }
|
258
262
|
sleep 0.2
|
@@ -260,7 +264,7 @@ class TestSemian < Test::Unit::TestCase
|
|
260
264
|
|
261
265
|
t.join
|
262
266
|
|
263
|
-
assert_equal 1,
|
267
|
+
assert_equal 1, resource.count
|
264
268
|
end
|
265
269
|
|
266
270
|
def test_multiple_register_with_fork
|
@@ -275,7 +279,7 @@ class TestSemian < Test::Unit::TestCase
|
|
275
279
|
acquired = false
|
276
280
|
|
277
281
|
f.flock(File::LOCK_SH)
|
278
|
-
|
282
|
+
create_resource(:testing, tickets: 5).acquire do |resource|
|
279
283
|
assert resource.count < 5
|
280
284
|
acquired = true
|
281
285
|
end
|
@@ -290,10 +294,24 @@ class TestSemian < Test::Unit::TestCase
|
|
290
294
|
children.delete(Process.wait)
|
291
295
|
end
|
292
296
|
|
293
|
-
assert_equal 5,
|
297
|
+
assert_equal 5, create_resource(:testing, tickets: 0).count
|
294
298
|
ensure
|
295
299
|
f.close!
|
296
300
|
end
|
297
301
|
end
|
298
302
|
|
303
|
+
def create_resource(*args)
|
304
|
+
@resources ||= []
|
305
|
+
resource = Semian::Resource.new(*args)
|
306
|
+
@resources << resource
|
307
|
+
resource
|
308
|
+
end
|
309
|
+
|
310
|
+
def destroy_resources
|
311
|
+
return unless @resources
|
312
|
+
@resources.each do |resource|
|
313
|
+
resource.destroy rescue nil
|
314
|
+
end
|
315
|
+
@resources = []
|
316
|
+
end
|
299
317
|
end
|
data/test/test_unsupported.rb
CHANGED
@@ -1,10 +1,14 @@
|
|
1
|
-
require '
|
1
|
+
require 'minitest/autorun'
|
2
2
|
require 'semian'
|
3
3
|
|
4
|
-
class TestSemian <
|
4
|
+
class TestSemian < MiniTest::Unit::TestCase
|
5
|
+
def setup
|
6
|
+
Semian.destroy(:testing) rescue nil
|
7
|
+
end
|
8
|
+
|
5
9
|
def test_unsupported_acquire_yields
|
6
10
|
acquired = false
|
7
|
-
Semian.register :testing, tickets: 1
|
11
|
+
Semian.register :testing, tickets: 1, error_threshold: 1, error_timeout: 2, success_threshold: 1
|
8
12
|
Semian[:testing].acquire { acquired = true }
|
9
13
|
assert acquired
|
10
14
|
end
|
data.tar.gz.sig
CHANGED
Binary file
|