zold 0.14.17 → 0.14.18

Sign up to get free protection for your applications and to get access to all the features.
@@ -68,7 +68,6 @@ module Zold
68
68
  @modified = Queue.new
69
69
  @push = Thread.start do
70
70
  Thread.current.abort_on_exception = true
71
- Thread.current.priority = -100
72
71
  Thread.current.name = 'push'
73
72
  VerboseThread.new(@log).run(true) do
74
73
  loop do
@@ -28,6 +28,7 @@ require 'fileutils'
28
28
  require_relative 'backtrace'
29
29
  require_relative 'node/farm'
30
30
  require_relative 'atomic_file'
31
+ require_relative 'type'
31
32
 
32
33
  # The list of remotes.
33
34
  # Author:: Yegor Bugayenko (yegor256@gmail.com)
@@ -42,18 +43,11 @@ module Zold
42
43
  # At what amount of errors we delete the remote automatically
43
44
  TOLERANCE = 8
44
45
 
45
- # After this limit, the remote runtime must be recorded
46
- RUNTIME_LIMIT = 16
47
-
48
46
  # Default number of nodes to fetch.
49
47
  MAX_NODES = 16
50
48
 
51
49
  # Empty, for standalone mode
52
50
  class Empty < Remotes
53
- def initialize
54
- # Nothing here
55
- end
56
-
57
51
  def all
58
52
  []
59
53
  end
@@ -64,33 +58,28 @@ module Zold
64
58
  end
65
59
 
66
60
  # One remote.
67
- class Remote
68
- attr_reader :host, :port
69
- def initialize(host, port, score, idx, log: Log::Quiet.new, network: 'test')
70
- @host = host
71
- raise 'Post must be Integer' unless port.is_a?(Integer)
72
- @port = port
73
- raise 'Score must be of type Score' unless score.is_a?(Score)
74
- @score = score
75
- raise 'Idx must be of type Integer' unless idx.is_a?(Integer)
76
- @idx = idx
77
- raise 'Network can\'t be nil' if network.nil?
78
- @network = network
79
- @log = log
80
- end
61
+ class Remote < Dry::Struct
62
+ attribute :host, Types::Strict::String
63
+ attribute :port, Types::Strict::Integer.constrained(gteq: 0, lt: 65_535)
64
+ attribute :score, Score
65
+ attribute :idx, Types::Strict::Integer
66
+ attribute :network, Types::Strict::String.optional.default('test')
67
+ attribute :log, (Types::Class.constructor do |value|
68
+ value.nil? ? Log::Quiet.new : value
69
+ end)
81
70
 
82
71
  def http(path = '/')
83
- Http.new(uri: "http://#{@host}:#{@port}#{path}", score: @score, network: @network)
72
+ Http.new(uri: "http://#{host}:#{port}#{path}", score: score, network: network)
84
73
  end
85
74
 
86
75
  def to_s
87
- "#{@host}:#{@port}/#{@idx}"
76
+ "#{host}:#{port}/#{idx}"
88
77
  end
89
78
 
90
79
  def assert_code(code, response)
91
80
  msg = response.message.strip
92
81
  return if response.code.to_i == code
93
- @log.debug("#{response.code} \"#{response.message}\" at \"#{response.body}\"")
82
+ log.debug("#{response.code} \"#{response.message}\" at \"#{response.body}\"")
94
83
  raise "Unexpected HTTP code #{response.code}, instead of #{code}" if msg.empty?
95
84
  raise "#{msg} (HTTP code #{response.code}, instead of #{code})"
96
85
  end
@@ -101,8 +90,8 @@ module Zold
101
90
  end
102
91
 
103
92
  def assert_score_ownership(score)
104
- raise "Masqueraded host #{@host} as #{score.host}: #{score}" if @host != score.host
105
- raise "Masqueraded port #{@port} as #{score.port}: #{score}" if @port != score.port
93
+ raise "Masqueraded host #{host} as #{score.host}: #{score}" if host != score.host
94
+ raise "Masqueraded port #{port} as #{score.port}: #{score}" if port != score.port
106
95
  end
107
96
 
108
97
  def assert_score_strength(score)
@@ -114,12 +103,11 @@ module Zold
114
103
  end
115
104
  end
116
105
 
117
- def initialize(file, network: 'test')
118
- raise 'File can\'t be nil' if file.nil?
106
+ def initialize(file:, network: 'test', mutex: Mutex.new, timeout: 16)
119
107
  @file = file
120
- raise 'Network can\'t be nil' if network.nil?
121
108
  @network = network
122
- @mutex = Mutex.new
109
+ @mutex = mutex
110
+ @timeout = timeout
123
111
  end
124
112
 
125
113
  def all
@@ -134,7 +122,7 @@ module Zold
134
122
  end
135
123
 
136
124
  def clean
137
- save([])
125
+ modify { [] }
138
126
  end
139
127
 
140
128
  def reset
@@ -161,18 +149,18 @@ module Zold
161
149
  raise 'Port can\'t be negative' if port.negative?
162
150
  raise 'Port can\'t be over 65536' if port > 0xffff
163
151
  raise "#{host}:#{port} already exists" if exists?(host, port)
164
- list = load
165
- list << { host: host.downcase, port: port, score: 0 }
166
- save(list)
152
+ modify do |list|
153
+ list + [{ host: host.downcase, port: port, score: 0 }]
154
+ end
167
155
  end
168
156
 
169
157
  def remove(host, port = Remotes::PORT)
170
158
  raise 'Port has to be of type Integer' unless port.is_a?(Integer)
171
159
  raise 'Host can\'t be nil' if host.nil?
172
160
  raise 'Port can\'t be nil' if port.nil?
173
- list = load
174
- list.reject! { |r| r[:host] == host.downcase && r[:port] == port }
175
- save(list)
161
+ modify do |list|
162
+ list.reject { |r| r[:host] == host.downcase && r[:port] == port }
163
+ end
176
164
  end
177
165
 
178
166
  def iterate(log, farm: Farm::Empty.new)
@@ -188,12 +176,18 @@ module Zold
188
176
  pool.post do
189
177
  Thread.current.abort_on_exception = true
190
178
  Thread.current.name = 'remotes'
191
- Thread.current.priority = -100
192
179
  start = Time.now
193
180
  begin
194
- yield Remotes::Remote.new(r[:host], r[:port], score, idx, log: log, network: @network)
181
+ yield Remotes::Remote.new(
182
+ host: r[:host],
183
+ port: r[:port],
184
+ score: score,
185
+ idx: idx,
186
+ log: log,
187
+ network: @network
188
+ )
195
189
  idx += 1
196
- raise 'Took too long to execute' if (Time.now - start).round > Remotes::RUNTIME_LIMIT
190
+ raise 'Took too long to execute' if (Time.now - start).round > @timeout
197
191
  rescue StandardError => e
198
192
  error(r[:host], r[:port])
199
193
  errors = errors(r[:host], r[:port])
@@ -234,49 +228,48 @@ in #{(Time.now - start).round}s; errors=#{errors}")
234
228
 
235
229
  private
236
230
 
237
- def if_present(host, port)
238
- list = load
239
- remote = list.find { |r| r[:host] == host.downcase && r[:port] == port }
240
- return unless remote
241
- yield remote
242
- save(list)
243
- end
244
-
245
- def load
231
+ def modify
246
232
  @mutex.synchronize do
247
- raw = CSV.read(file).map do |r|
248
- {
249
- host: r[0],
250
- port: r[1].to_i,
251
- score: r[2].to_i,
252
- errors: r[3].to_i
253
- }
254
- end
255
- raw.reject { |r| !r[:host] || r[:port].zero? }.map do |r|
256
- r[:home] = URI("http://#{r[:host]}:#{r[:port]}/")
257
- r
258
- end
233
+ save(yield(load))
259
234
  end
260
235
  end
261
236
 
262
- def save(list)
263
- @mutex.synchronize do
264
- AtomicFile.new(file).write(
265
- list.uniq { |r| "#{r[:host]}:#{r[:port]}" }.map do |r|
266
- [
267
- r[:host],
268
- r[:port],
269
- r[:score],
270
- r[:errors]
271
- ].join(',')
272
- end.join("\n")
273
- )
237
+ def if_present(host, port)
238
+ modify do |list|
239
+ remote = list.find { |r| r[:host] == host.downcase && r[:port] == port }
240
+ return unless remote
241
+ yield remote
242
+ list
274
243
  end
275
244
  end
276
245
 
277
- def file
246
+ def load
278
247
  reset unless File.exist?(@file)
279
- @file
248
+ raw = CSV.read(@file).map do |r|
249
+ {
250
+ host: r[0],
251
+ port: r[1].to_i,
252
+ score: r[2].to_i,
253
+ errors: r[3].to_i
254
+ }
255
+ end
256
+ raw.reject { |r| !r[:host] || r[:port].zero? }.map do |r|
257
+ r[:home] = URI("http://#{r[:host]}:#{r[:port]}/")
258
+ r
259
+ end
260
+ end
261
+
262
+ def save(list)
263
+ AtomicFile.new(@file).write(
264
+ list.uniq { |r| "#{r[:host]}:#{r[:port]}" }.map do |r|
265
+ [
266
+ r[:host],
267
+ r[:port],
268
+ r[:score],
269
+ r[:errors]
270
+ ].join(',')
271
+ end.join("\n")
272
+ )
280
273
  end
281
274
  end
282
275
  end
@@ -20,10 +20,11 @@
20
20
  # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
21
  # SOFTWARE.
22
22
 
23
- # @todo #98:30m/DEV Right now only Score and Http classes have been refactored for using
24
- # dry-types, even tough the issue has been boosted refactoring the whole project
25
- # is very cumbersome. Please refer to Score and Http class on how to perform all
26
- # the changes for the project to adopt dry-types
23
+ # @todo #394:30m/DEV Right now only Score, Http, Zold::Remotes and Zold::Remote
24
+ # classes have been refactored for using dry-types, even tough the issue has
25
+ # been boosted refactoring the whole project is very cumbersome. Please refer
26
+ # to Score and Http class on how to perform all the changes for the project to
27
+ # adopt dry-types
27
28
  require 'dry-types'
28
29
  require 'dry-struct'
29
30
 
@@ -25,6 +25,6 @@
25
25
  # Copyright:: Copyright (c) 2018 Yegor Bugayenko
26
26
  # License:: MIT
27
27
  module Zold
28
- VERSION = '0.14.17'
28
+ VERSION = '0.14.18'
29
29
  PROTOCOL = 2
30
30
  end
@@ -33,8 +33,8 @@ require_relative '../../../lib/zold/commands/routines/reconnect.rb'
33
33
  # License:: MIT
34
34
  class TestReconnect < Minitest::Test
35
35
  def test_reconnects
36
- Dir.mktmpdir 'test' do |dir|
37
- remotes = Zold::Remotes.new(File.join(dir, 'remotes.csv'))
36
+ Dir.mktmpdir do |dir|
37
+ remotes = Zold::Remotes.new(file: File.join(dir, 'remotes.csv'))
38
38
  remotes.clean
39
39
  stub_request(:get, 'http://b1.zold.io:80/remotes').to_return(status: 404)
40
40
  opts = { 'never-reboot' => true, 'routine-immediately' => true }
@@ -33,7 +33,7 @@ require_relative '../../lib/zold/commands/create'
33
33
  # License:: MIT
34
34
  class TestCreate < Minitest::Test
35
35
  def test_creates_wallet
36
- Dir.mktmpdir 'test' do |dir|
36
+ Dir.mktmpdir do |dir|
37
37
  wallet = Zold::Create.new(wallets: Zold::Wallets.new(dir), log: test_log).run(
38
38
  ['create', '--public-key=fixtures/id_rsa.pub']
39
39
  )
@@ -35,7 +35,7 @@ require_relative '../../lib/zold/commands/invoice'
35
35
  # License:: MIT
36
36
  class TestInvoice < Minitest::Test
37
37
  def test_generates_invoice
38
- Dir.mktmpdir 'test' do |dir|
38
+ Dir.mktmpdir do |dir|
39
39
  id = Zold::Id.new
40
40
  wallets = Zold::Wallets.new(dir)
41
41
  source = wallets.find(id)
@@ -34,7 +34,7 @@ require_relative '../../lib/zold/commands/list'
34
34
  # License:: MIT
35
35
  class TestList < Minitest::Test
36
36
  def test_lists_wallets_with_balances
37
- Dir.mktmpdir 'test' do |dir|
37
+ Dir.mktmpdir do |dir|
38
38
  id = Zold::Id.new
39
39
  wallets = Zold::Wallets.new(dir)
40
40
  wallet = wallets.find(id)
@@ -140,7 +140,7 @@ class TestMerge < Minitest::Test
140
140
  def test_merges_scenarios
141
141
  base = 'fixtures/merge'
142
142
  Dir.new(base).select { |f| File.directory?(File.join(base, f)) && !f.start_with?('.') }.each do |f|
143
- Dir.mktmpdir 'test' do |dir|
143
+ Dir.mktmpdir do |dir|
144
144
  FileUtils.cp_r(File.join('fixtures/merge', "#{f}/."), dir)
145
145
  scores = File.join(dir, 'copies/0123456789abcdef/scores.z')
146
146
  File.write(scores, File.read(scores).gsub(/NOW/, Time.now.utc.iso8601))
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright (c) 2018 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 'webmock/minitest'
25
+ require_relative '../fake_home'
26
+ require_relative '../test__helper'
27
+ require_relative '../../lib/zold/id'
28
+ require_relative '../../lib/zold/json_page'
29
+ require_relative '../../lib/zold/commands/pull'
30
+
31
+ # PUSH test.
32
+ # Author:: Yegor Bugayenko (yegor256@gmail.com)
33
+ # Copyright:: Copyright (c) 2018 Yegor Bugayenko
34
+ # License:: MIT
35
+ class TestPull < Minitest::Test
36
+ def test_pull_wallet
37
+ FakeHome.new.run do |home|
38
+ remotes = home.remotes
39
+ remotes.add('localhost', 80)
40
+ json = home.create_wallet_json
41
+ id = Zold::JsonPage.new(json).to_hash['id']
42
+ stub_request(:get, "http://localhost:80/wallet/#{id}").to_return(status: 200, body: json)
43
+ Zold::Pull.new(wallets: home.wallets, remotes: remotes, copies: home.copies.root.to_s, log: test_log).run(
44
+ ['--ignore-this-stupid-option', 'pull', id.to_s]
45
+ )
46
+ assert(home.wallets.find(Zold::Id.new(id)).exists?)
47
+ end
48
+ end
49
+ end
@@ -38,8 +38,8 @@ require_relative '../../lib/zold/commands/remote'
38
38
  # License:: MIT
39
39
  class TestRemote < Minitest::Test
40
40
  def test_updates_remote
41
- Dir.mktmpdir 'test' do |dir|
42
- remotes = Zold::Remotes.new(File.join(dir, 'a/b/c/remotes'))
41
+ Dir.mktmpdir do |dir|
42
+ remotes = Zold::Remotes.new(file: File.join(dir, 'a/b/c/remotes'))
43
43
  zero = Zold::Score::ZERO
44
44
  stub_request(:get, "http://#{zero.host}:#{zero.port}/remotes").to_return(
45
45
  status: 200,
@@ -73,9 +73,9 @@ class TestRemote < Minitest::Test
73
73
  end
74
74
 
75
75
  def test_elects_a_remote
76
- Dir.mktmpdir 'test' do |dir|
76
+ Dir.mktmpdir do |dir|
77
77
  zero = Zold::Score::ZERO
78
- remotes = Zold::Remotes.new(File.join(dir, 'remotes.txt'))
78
+ remotes = Zold::Remotes.new(file: File.join(dir, 'remotes.txt'))
79
79
  remotes.clean
80
80
  remotes.add(zero.host, zero.port)
81
81
  stub_request(:get, "http://#{zero.host}:#{zero.port}/").to_return(
@@ -92,8 +92,8 @@ class TestRemote < Minitest::Test
92
92
  end
93
93
 
94
94
  def test_remote_trim_with_tolerate
95
- Dir.mktmpdir 'test' do |dir|
96
- remotes = Zold::Remotes.new(File.join(dir, 'remotes.txt'))
95
+ Dir.mktmpdir do |dir|
96
+ remotes = Zold::Remotes.new(file: File.join(dir, 'remotes.txt'))
97
97
  zero = Zold::Score::ZERO
98
98
  stub_request(:get, "http://#{zero.host}:#{zero.port}/remotes").to_return(
99
99
  status: 200,
@@ -105,14 +105,13 @@ class TestRemote < Minitest::Test
105
105
  ]
106
106
  }.to_json
107
107
  )
108
- stub_request(:get, 'http://localhost:888/remotes').to_return(
109
- status: 404
110
- )
108
+ stub_request(:get, 'http://localhost:888/remotes').to_return(status: 404)
111
109
  cmd = Zold::Remote.new(remotes: remotes, log: test_log)
112
110
  cmd.run(%w[remote clean])
113
111
  assert(remotes.all.empty?)
114
112
  cmd.run(['remote', 'add', zero.host, zero.port.to_s, '--skip-ping'])
115
- cmd.run(%w[remote add localhost 888 --skip-ping])
113
+ cmd.run(['remote', 'update', '--ignore-score-weakness', '--skip-ping'])
114
+ assert_equal(2, remotes.all.count)
116
115
  cmd.run(['remote', 'update', '--ignore-score-weakness'])
117
116
  cmd.run(['remote', 'trim', '--tolerate=0'])
118
117
  assert_equal(1, remotes.all.count)
@@ -127,8 +126,8 @@ class TestRemote < Minitest::Test
127
126
  end
128
127
 
129
128
  def test_select_respects_max_nodes_option
130
- Dir.mktmpdir 'test' do |dir|
131
- remotes = Zold::Remotes.new(File.join(dir, 'remotes.txt'))
129
+ Dir.mktmpdir do |dir|
130
+ remotes = Zold::Remotes.new(file: File.join(dir, 'remotes.txt'))
132
131
  zero = Zold::Score::ZERO
133
132
  cmd = Zold::Remote.new(remotes: remotes, log: test_log)
134
133
  (5000..5010).each do |port|
@@ -35,7 +35,7 @@ require_relative '../../lib/zold/commands/show'
35
35
  # License:: MIT
36
36
  class TestShow < Minitest::Test
37
37
  def test_checks_wallet_balance
38
- Dir.mktmpdir 'test' do |dir|
38
+ Dir.mktmpdir do |dir|
39
39
  id = Zold::Id.new
40
40
  wallets = Zold::Wallets.new(dir)
41
41
  wallet = wallets.find(id)
@@ -25,6 +25,9 @@ require_relative '../lib/zold/id'
25
25
  require_relative '../lib/zold/wallet'
26
26
  require_relative '../lib/zold/wallets'
27
27
  require_relative '../lib/zold/key'
28
+ require_relative '../lib/zold/version'
29
+ require_relative '../lib/zold/remotes'
30
+ require_relative '../lib/zold/atomic_file'
28
31
 
29
32
  # Fake home dir.
30
33
  # Author:: Yegor Bugayenko (yegor256@gmail.com)
@@ -37,7 +40,7 @@ class FakeHome
37
40
  end
38
41
 
39
42
  def run
40
- Dir.mktmpdir 'test' do |dir|
43
+ Dir.mktmpdir do |dir|
41
44
  FileUtils.copy(File.join(__dir__, '../fixtures/id_rsa'), File.join(dir, 'id_rsa'))
42
45
  yield FakeHome.new(dir)
43
46
  end
@@ -47,18 +50,37 @@ class FakeHome
47
50
  Zold::Wallets.new(@dir)
48
51
  end
49
52
 
50
- def create_wallet(id = Zold::Id.new)
51
- wallet = Zold::Wallet.new(File.join(@dir, id.to_s))
53
+ def create_wallet(id = Zold::Id.new, dir = @dir)
54
+ wallet = Zold::Wallet.new(File.join(dir, id.to_s))
52
55
  wallet.init(id, Zold::Key.new(file: File.join(__dir__, '../fixtures/id_rsa.pub')))
53
56
  wallet
54
57
  end
55
58
 
59
+ def create_wallet_json(id = Zold::Id.new)
60
+ require_relative '../lib/zold/score'
61
+ score = Zold::Score::ZERO
62
+ Dir.mktmpdir 'wallets' do |external_dir|
63
+ wallet = create_wallet(id, external_dir)
64
+ {
65
+ version: Zold::VERSION,
66
+ protocol: Zold::PROTOCOL,
67
+ id: wallet.id.to_s,
68
+ score: score.to_h,
69
+ wallets: 1,
70
+ mtime: wallet.mtime.utc.iso8601,
71
+ digest: wallet.digest,
72
+ balance: wallet.balance.to_i,
73
+ body: Zold::AtomicFile.new(wallet.path).read
74
+ }.to_json
75
+ end
76
+ end
77
+
56
78
  def copies(wallet = create_wallet)
57
79
  Zold::Copies.new(File.join(@dir, "copies/#{wallet.id}"))
58
80
  end
59
81
 
60
82
  def remotes
61
- remotes = Zold::Remotes.new(File.join(@dir, 'secrets/remotes'))
83
+ remotes = Zold::Remotes.new(file: File.join(@dir, 'secrets/remotes'))
62
84
  remotes.clean
63
85
  remotes
64
86
  end