zold 0.14.17 → 0.14.18

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.
@@ -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