semian 0.0.8 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
@@ -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 'test/unit'
1
+ require 'minitest/autorun'
2
2
  require 'semian'
3
3
  require 'tempfile'
4
4
  require 'fileutils'
5
5
 
6
- class TestSemian < Test::Unit::TestCase
6
+ class TestResource < MiniTest::Unit::TestCase
7
7
  def setup
8
- Semian[:testing].destroy rescue Semian::BaseError
8
+ Semian.destroy(:testing) rescue nil
9
9
  end
10
10
 
11
- def test_register_invalid_args
11
+ def teardown
12
+ destroy_resources
13
+ end
14
+
15
+ def test_initialize_invalid_args
12
16
  assert_raises TypeError do
13
- Semian.register 123
17
+ create_resource 123, tickets: 2
14
18
  end
15
19
  assert_raises ArgumentError do
16
- Semian.register :testing, tickets: -1
20
+ create_resource :testing, tickets: -1
17
21
  end
18
22
  assert_raises ArgumentError do
19
- Semian.register :testing, tickets: 1_000_000
23
+ create_resource :testing, tickets: 1_000_000
20
24
  end
21
25
  assert_raises TypeError do
22
- Semian.register :testing, permissions: "test"
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
- Semian.register :testing, tickets: 2
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
- Semian.register :testing
40
+ create_resource :testing, tickets: 0
37
41
  end
38
42
  end
39
43
 
40
44
  def test_acquire
41
45
  acquired = false
42
- Semian.register :testing, tickets: 1
43
- Semian[:testing].acquire { acquired = true }
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
- Semian.register :testing, tickets: 1
49
- val = Semian[:testing].acquire { 1234 }
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
- Semian.register :testing, tickets: 1, timeout: 0.05
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
- Semian[:testing].acquire { refute true }
68
+ resource.acquire { refute true }
65
69
  end
66
70
  end
67
71
  end
68
72
 
69
- Semian[:testing].acquire do
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
- Semian.register :testing, tickets: 1, timeout: 0.01
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
- Semian[:testing].acquire(timeout: 1) { thread_acquired = true }
95
+ resource.acquire(timeout: 1) { thread_acquired = true }
92
96
  end
93
97
  end
94
98
 
95
- Semian[:testing].acquire do
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
- Semian.register :testing, tickets: 2, timeout: 0.5
112
+ resource = create_resource :testing, tickets: 2, timeout: 0.5
109
113
 
110
- Semian[:testing].acquire do
114
+ resource.acquire do
111
115
  pid = fork do
112
- Semian.register :testing, timeout: 0.5
113
- Semian[:testing].acquire do
116
+ resource.acquire do
114
117
  assert_raises Semian::TimeoutError do
115
- Semian[:testing].acquire { }
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
- Semian.register :testing, tickets: 1, timeout: 0.1
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
- Semian[:testing].acquire do
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
- Semian[:testing].acquire {}
146
+ resource.acquire {}
144
147
  end
145
148
 
146
149
  Process.kill("KILL", pid)
147
- Semian[:testing].acquire { acquired = true }
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
- Semian.register :testing, tickets: 2
160
+ resource = create_resource :testing, tickets: 2
158
161
  acquired = false
159
162
 
160
- Semian[:testing].acquire do
163
+ resource.acquire do
161
164
  acquired = true
162
- assert_equal 1, Semian[:testing].count
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
- Semian.register :testing, tickets: 1
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
- Semian[:testing].acquire do
180
+ resource.acquire do
177
181
  1
178
182
  end
179
183
  end
180
184
  end
181
185
 
182
186
  def test_destroy
183
- Semian.register :testing, tickets: 1
184
- Semian[:testing].destroy
187
+ resource = create_resource :testing, tickets: 1
188
+ resource.destroy
185
189
  assert_raises Semian::SyscallError do
186
- Semian[:testing].acquire { }
190
+ resource.acquire { }
187
191
  end
188
192
  end
189
193
 
190
194
  def test_permissions
191
- Semian.register :testing, permissions: 0600, tickets: 1
192
- semid = Semian[:testing].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
- Semian.register :testing, permissions: 0660, tickets: 1
200
- semid = Semian[:testing].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
- Semian.register :testing, tickets: 1
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
- Semian.register :testing, tickets: 5
220
- assert_equal 4, Semian[:testing].count
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, Semian[:testing].count
228
+ assert_equal 1, resource.count
225
229
 
226
- Semian[:testing].acquire do
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, Semian[:testing].count
238
+ assert_equal 5, resource.count
235
239
  end
236
240
 
237
241
  def test_resize_tickets_decrease
238
- Semian.register :testing, tickets: 5
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
- Semian.register :testing, tickets: 1
249
- assert_equal 0, Semian[:testing].count
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, Semian[:testing].count
257
+ assert_equal 5, resource.count
254
258
 
255
- Semian[:testing].acquire do
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, Semian[:testing].count
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
- Semian.register(:testing, tickets: 5).acquire do |resource|
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, Semian.register(:testing).count
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
@@ -1,10 +1,14 @@
1
- require 'test/unit'
1
+ require 'minitest/autorun'
2
2
  require 'semian'
3
3
 
4
- class TestSemian < Test::Unit::TestCase
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