costan-zerg_support 0.0.9
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.
- data/CHANGELOG +17 -0
- data/LICENSE +21 -0
- data/Manifest +22 -0
- data/README +0 -0
- data/RUBYFORGE +40 -0
- data/Rakefile +26 -0
- data/lib/zerg_support/event_machine/connection_mocks.rb +46 -0
- data/lib/zerg_support/event_machine/frame_protocol.rb +68 -0
- data/lib/zerg_support/event_machine/object_protocol.rb +24 -0
- data/lib/zerg_support/gems.rb +60 -0
- data/lib/zerg_support/open_ssh.rb +104 -0
- data/lib/zerg_support/process.rb +225 -0
- data/lib/zerg_support/spawn.rb +185 -0
- data/lib/zerg_support.rb +21 -0
- data/test/fork_tree.rb +34 -0
- data/test/test_connection_mocks.rb +57 -0
- data/test/test_frame_protocol.rb +119 -0
- data/test/test_gems.rb +29 -0
- data/test/test_object_protocol.rb +55 -0
- data/test/test_open_ssh.rb +125 -0
- data/test/test_process.rb +78 -0
- data/test/test_spawn.rb +163 -0
- data/zerg_support.gemspec +41 -0
- metadata +125 -0
@@ -0,0 +1,185 @@
|
|
1
|
+
#:nodoc:
|
2
|
+
module Zerg::Support::Process
|
3
|
+
# perhaps this should be merged in process
|
4
|
+
end
|
5
|
+
|
6
|
+
if RUBY_PLATFORM =~ /win/ and RUBY_PLATFORM !~ /darwin/
|
7
|
+
require 'win32/process'
|
8
|
+
|
9
|
+
#:nodoc: all
|
10
|
+
module Zerg::Support::Process
|
11
|
+
def self.spawn(binary, args = [], options = {})
|
12
|
+
# command line processing
|
13
|
+
command_line = '"' + binary + '" "' +
|
14
|
+
args.map { |a| a.gsub '"', '""' }.join('" "') + '"'
|
15
|
+
|
16
|
+
# environment processing
|
17
|
+
environment_string = nil
|
18
|
+
if options[:env]
|
19
|
+
if options[:unsetenv_others]
|
20
|
+
environment = options[:env]
|
21
|
+
else
|
22
|
+
environment = Hash.new.merge(ENV).merge(options[:env])
|
23
|
+
end
|
24
|
+
environment_string = environment.keys.sort.
|
25
|
+
map { |k| "#{k}=#{environment[k]}" }.join "\0"
|
26
|
+
environment_string << "\0"
|
27
|
+
end
|
28
|
+
|
29
|
+
# redirection processing
|
30
|
+
startup_info = {}
|
31
|
+
files = {}
|
32
|
+
stream_files = {}
|
33
|
+
deferred_opens = []
|
34
|
+
[[STDIN, :stdin], [STDOUT, :stdout], [STDERR, :stderr]].each do |pair|
|
35
|
+
next unless options[pair.first]
|
36
|
+
if options[pair.first].kind_of? String
|
37
|
+
filename = options[pair.first]
|
38
|
+
files[filename] ||= File.open(filename,
|
39
|
+
(pair.last == :stdin) ? 'r' : 'w+')
|
40
|
+
startup_info[pair.last] = files[filename]
|
41
|
+
stream_files[pair.first] = files[filename]
|
42
|
+
else
|
43
|
+
deferred_opens << Kernel.proc do
|
44
|
+
io = stream_files[options[pair.first]] || pair.first
|
45
|
+
startup_info[pair.last] = stream_files[pair.first] = io
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
deferred_opens.each { |d| d.call }
|
50
|
+
|
51
|
+
# process leader
|
52
|
+
creation_flags = 0
|
53
|
+
if options[:pgroup]
|
54
|
+
creation_flags |= Process::DETACHED_PROCESS
|
55
|
+
if options[:pgroup].kind_of? Numeric and options[:pgroup] > 0
|
56
|
+
# TODO: what now?
|
57
|
+
else
|
58
|
+
creation_flags |= Process::CREATE_NEW_PROCESS_GROUP
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
info = Process.create :command_line => command_line,
|
63
|
+
:cwd => options[:chdir] || Dir.pwd,
|
64
|
+
:environment => environment_string,
|
65
|
+
:creation_flags => creation_flags,
|
66
|
+
:startup_info => startup_info
|
67
|
+
files.each { |name, io| io.close }
|
68
|
+
|
69
|
+
return info[:process_id]
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
else
|
74
|
+
|
75
|
+
#:nodoc:
|
76
|
+
module Zerg::Support::Process
|
77
|
+
# Spawns a new process and returns its pid immediately.
|
78
|
+
# Like Kernel.spawn in ruby1.9, except that the environment is passed
|
79
|
+
# as an option with the key :env
|
80
|
+
def self.spawn(binary, args = [], options = {})
|
81
|
+
if Kernel.respond_to? :spawn
|
82
|
+
# ruby1.9+: spawn!
|
83
|
+
options = options.dup
|
84
|
+
env = options.delete(:env)
|
85
|
+
Kernel.spawn *([env, binary] + args + [options])
|
86
|
+
else
|
87
|
+
# below 1.9: emulate
|
88
|
+
|
89
|
+
# chdir option
|
90
|
+
Dir.chdir options[:chdir] if options[:chdir]
|
91
|
+
|
92
|
+
child_pid = fork do
|
93
|
+
Helpers.do_redirects options
|
94
|
+
Helpers.close_fds options
|
95
|
+
Helpers.set_process_group options
|
96
|
+
Helpers.set_environment options
|
97
|
+
Helpers.set_rlimits options
|
98
|
+
|
99
|
+
Kernel.exec *([binary] + args)
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
return child_pid
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
# Helpers for spawning processes.
|
109
|
+
module Zerg::Support::Process::Helpers
|
110
|
+
# Closes all open file descriptors except for stdin/stdout/stderr
|
111
|
+
def self.close_fds(options)
|
112
|
+
return if options[:close_others] == false
|
113
|
+
|
114
|
+
ObjectSpace.each_object(IO) do |io|
|
115
|
+
next if [STDIN, STDOUT, STDERR].include? io
|
116
|
+
|
117
|
+
begin
|
118
|
+
io.close unless io.closed?
|
119
|
+
rescue Exception
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
# Sets process limits (rlimits) according to the given options. The options
|
125
|
+
# follow the convention of Kernel.spawn in ruby1.9
|
126
|
+
def self.set_rlimits(options)
|
127
|
+
# rlimit options
|
128
|
+
options.each do |k, v|
|
129
|
+
next unless k.kind_of? Symbol and k.to_s[0, 7] == 'rlimit_'
|
130
|
+
rconst = Process.const_get k.to_s.upcase.to_sym
|
131
|
+
if v.kind_of? Enumerable
|
132
|
+
Process.setrlimit rconst, v.first, v.last
|
133
|
+
else
|
134
|
+
Process.setrlimit rconst, v
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
# Sets the process' group according to the given options. The options
|
140
|
+
# follow the convention of Kernel.spawn in ruby1.9
|
141
|
+
def self.set_process_group(options)
|
142
|
+
return unless options[:pgroup]
|
143
|
+
|
144
|
+
if options[:pgroup].kind_of? Numeric and options[:pgroup] > 0
|
145
|
+
Process.setpgid 0, options[:pgroup]
|
146
|
+
else
|
147
|
+
Process.setsid
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
# Sets the process' environment according to the given options. The options
|
152
|
+
# follow the convention of Kernel.spawn in ruby1.9
|
153
|
+
def self.set_environment(options)
|
154
|
+
return unless options[:env]
|
155
|
+
|
156
|
+
ENV.clear if options[:unsetenv_others]
|
157
|
+
options[:env].each do |key, value|
|
158
|
+
ENV[key] = value
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
# Performs IO redirections according to the given options. The options
|
163
|
+
# follow the convention of Kernel.spawn in ruby1.9
|
164
|
+
def self.do_redirects(options)
|
165
|
+
redirected_files = {}
|
166
|
+
file_redirects = []
|
167
|
+
fd_redirects = []
|
168
|
+
options.each do |key, value|
|
169
|
+
next unless key.kind_of? IO
|
170
|
+
if value.kind_of? String
|
171
|
+
if redirected_files[value]
|
172
|
+
fd_redirects << [key, redirected_files[value]]
|
173
|
+
else
|
174
|
+
file_redirects << [key, value]
|
175
|
+
redirected_files[value] = key
|
176
|
+
end
|
177
|
+
else
|
178
|
+
fd_redirects << [key, value]
|
179
|
+
end
|
180
|
+
end
|
181
|
+
(file_redirects + fd_redirects).each do |r|
|
182
|
+
r.first.reopen r.last
|
183
|
+
end
|
184
|
+
end
|
185
|
+
end
|
data/lib/zerg_support.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
# :nodoc
|
2
|
+
module Zerg
|
3
|
+
end
|
4
|
+
|
5
|
+
# TODO(victor): document this
|
6
|
+
module Zerg::Support
|
7
|
+
end
|
8
|
+
|
9
|
+
require 'rubygems'
|
10
|
+
require 'zerg_support/gems.rb'
|
11
|
+
require 'zerg_support/process.rb'
|
12
|
+
require 'zerg_support/open_ssh.rb'
|
13
|
+
require 'zerg_support/spawn.rb'
|
14
|
+
|
15
|
+
# TODO(victor): document this
|
16
|
+
module Zerg::Support::EventMachine
|
17
|
+
end
|
18
|
+
|
19
|
+
require 'zerg_support/event_machine/connection_mocks.rb'
|
20
|
+
require 'zerg_support/event_machine/frame_protocol.rb'
|
21
|
+
require 'zerg_support/event_machine/object_protocol.rb'
|
data/test/fork_tree.rb
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
# Used in test_process.rb to produce a tree of children.
|
2
|
+
# Usage:
|
3
|
+
# ruby test/fork_tree.rb level interval cookie
|
4
|
+
# Arguments:
|
5
|
+
# level - the fork level; 0 means don't fork, otherwise fork two children
|
6
|
+
# with fork level-1
|
7
|
+
# interval - the time to sleep (in seconds)
|
8
|
+
# cookie - an argument that's preserved to help track down the process
|
9
|
+
# also, the PID will be written to cookie.pid unless the file already
|
10
|
+
# exists (this means that only the fork tree's root writes it)
|
11
|
+
|
12
|
+
require 'English'
|
13
|
+
|
14
|
+
# extract arguments
|
15
|
+
level = ARGV[1].to_i
|
16
|
+
interval = ARGV[2].to_f
|
17
|
+
cookie = ARGV[3]
|
18
|
+
|
19
|
+
# write PID file
|
20
|
+
pid_file = cookie + ".pid"
|
21
|
+
unless File.exist? pid_file
|
22
|
+
File.open(pid_file, "w") { |f| f.write $PID.to_s }
|
23
|
+
end
|
24
|
+
|
25
|
+
# spawn children
|
26
|
+
unless level == 0
|
27
|
+
2.times do
|
28
|
+
Thread.new do
|
29
|
+
system "ruby test/fork_tree.rb -- #{level - 1} #{interval} #{cookie}"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
sleep interval
|
@@ -0,0 +1,57 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
|
3
|
+
require 'zerg_support'
|
4
|
+
|
5
|
+
require 'rubygems'
|
6
|
+
require 'flexmock/test_unit'
|
7
|
+
|
8
|
+
class HollowReceiveMock < Zerg::Support::EventMachine::ReceiveMock
|
9
|
+
end
|
10
|
+
|
11
|
+
# Collects groups of 3 letters, stores them into idenity hashes.
|
12
|
+
class ReceiveFooMock < Zerg::Support::EventMachine::ReceiveMock
|
13
|
+
object_name :foo
|
14
|
+
|
15
|
+
def receive_data(data)
|
16
|
+
@s ||= ''
|
17
|
+
data.each_byte do |c|
|
18
|
+
@s << c
|
19
|
+
if @s.length == 3
|
20
|
+
receive_foo @s => @s
|
21
|
+
@s = ''
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
class ConnectionMocksTest < Test::Unit::TestCase
|
28
|
+
def test_send_mock
|
29
|
+
send_mock = Zerg::Support::EventMachine::SendMock.new
|
30
|
+
assert_equal send_mock.string, ''
|
31
|
+
|
32
|
+
send_mock.send_data('a')
|
33
|
+
assert_equal send_mock.string, 'a'
|
34
|
+
|
35
|
+
send_mock.send_data('bcd')
|
36
|
+
assert_equal send_mock.string, 'abcd'
|
37
|
+
|
38
|
+
send_mock.send_data('em')
|
39
|
+
assert_equal send_mock.string, 'abcdem'
|
40
|
+
end
|
41
|
+
|
42
|
+
def test_receive_mock_calls_recieve_data
|
43
|
+
recv_mock = HollowReceiveMock.new ['a', 'bcd', 'em']
|
44
|
+
flexmock(recv_mock).should_receive(:receive_data).with('a').once
|
45
|
+
flexmock(recv_mock).should_receive(:receive_data).with('bcd').once
|
46
|
+
flexmock(recv_mock).should_receive(:receive_data).with('em').once
|
47
|
+
recv_mock.replay
|
48
|
+
end
|
49
|
+
|
50
|
+
def test_receive_mock_collects_objects
|
51
|
+
foos = ReceiveFooMock.new(['a', 'bcd', 'emx']).replay.foos
|
52
|
+
assert_equal [{'abc' => 'abc'}, {'dem' => 'dem'}], foos
|
53
|
+
|
54
|
+
foos = ReceiveFooMock.new('ab').replay.foos
|
55
|
+
assert_equal [], foos
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,119 @@
|
|
1
|
+
require 'zerg_support'
|
2
|
+
|
3
|
+
require 'test/unit'
|
4
|
+
|
5
|
+
class TestFrameProtocol < Test::Unit::TestCase
|
6
|
+
FP = Zerg::Support::EventMachine::FrameProtocol
|
7
|
+
|
8
|
+
# Send mock for frames.
|
9
|
+
class SendFramesMock < Zerg::Support::EventMachine::SendMock
|
10
|
+
include FP
|
11
|
+
end
|
12
|
+
|
13
|
+
# Receive mock for frames.
|
14
|
+
class ReceiveFramesMock < Zerg::Support::EventMachine::ReceiveMock
|
15
|
+
include FP
|
16
|
+
object_name :frame
|
17
|
+
end
|
18
|
+
|
19
|
+
def setup
|
20
|
+
super
|
21
|
+
@send_mock = SendFramesMock.new
|
22
|
+
end
|
23
|
+
|
24
|
+
def teardown
|
25
|
+
super
|
26
|
+
end
|
27
|
+
|
28
|
+
def continuous_data_test(frames)
|
29
|
+
truncated_data_test frames, []
|
30
|
+
end
|
31
|
+
|
32
|
+
def truncated_data_test(frames, sub_lengths)
|
33
|
+
frames.each { |frame| @send_mock.send_frame frame }
|
34
|
+
in_string = @send_mock.string
|
35
|
+
in_strings, i = [], 0
|
36
|
+
sub_lengths.each do |sublen|
|
37
|
+
in_strings << in_string[i, sublen]
|
38
|
+
i += sublen
|
39
|
+
end
|
40
|
+
in_strings << in_string[i..-1] if i < in_string.length
|
41
|
+
out_frames = ReceiveFramesMock.new(@send_mock.string).replay.frames
|
42
|
+
assert_equal frames, out_frames
|
43
|
+
end
|
44
|
+
|
45
|
+
def test_empty_frame
|
46
|
+
continuous_data_test ['']
|
47
|
+
end
|
48
|
+
|
49
|
+
def test_byte_frame
|
50
|
+
continuous_data_test ['F']
|
51
|
+
end
|
52
|
+
|
53
|
+
def test_string_frame
|
54
|
+
continuous_data_test [(32...128).to_a.pack('C*')]
|
55
|
+
end
|
56
|
+
|
57
|
+
def test_multiple_frames
|
58
|
+
continuous_data_test [(32...128).to_a.pack('C*'), '', 'F', '', '1234567890']
|
59
|
+
end
|
60
|
+
|
61
|
+
def test_truncated_border
|
62
|
+
truncated_data_test ['A', 'A'], [1, 0, 2, 0]
|
63
|
+
end
|
64
|
+
|
65
|
+
def test_truncated_border_and_joined_data_size
|
66
|
+
truncated_data_test ['A', 'A'], [1, 1, 1, 1]
|
67
|
+
end
|
68
|
+
|
69
|
+
def test_truncated_size
|
70
|
+
long_frame = (32...128).to_a.pack('C*') * 5
|
71
|
+
truncated_data_test [long_frame], [1]
|
72
|
+
end
|
73
|
+
|
74
|
+
def test_truncated_size_and_data
|
75
|
+
long_frame = (32...128).to_a.pack('C*') * 5
|
76
|
+
truncated_data_test [long_frame], [1, 16]
|
77
|
+
end
|
78
|
+
|
79
|
+
def test_badass
|
80
|
+
# TODO(not_me): this test takes 4 seconds; replace with more targeted tests
|
81
|
+
|
82
|
+
# build the badass string
|
83
|
+
s2_frame = 'qwertyuiopasdfgh' * 8 * 128 # 16384 characters, size is 3 bytes
|
84
|
+
@send_mock.send_frame s2_frame
|
85
|
+
s2_string = @send_mock.string
|
86
|
+
s2_count = 3
|
87
|
+
send_string = s2_string * s2_count
|
88
|
+
ex_frames = [s2_frame] * s2_count
|
89
|
+
|
90
|
+
# build cut points in a string
|
91
|
+
s2_points = [0, 1, 2, 3, 4, 5, 127, 128, 8190, 16381, 16382, 16383]
|
92
|
+
cut_points = []
|
93
|
+
0.upto(s2_count - 1) do |i|
|
94
|
+
cut_points += s2_points.map { |p| p + i * s2_string.length }
|
95
|
+
end
|
96
|
+
|
97
|
+
# try all combinations of cutting up the string in 4 pieces
|
98
|
+
0.upto(cut_points.length - 1) do |i|
|
99
|
+
(i + 1).upto(cut_points.length - 1) do |j|
|
100
|
+
(j + 1).upto(cut_points.length - 1) do |k|
|
101
|
+
packets = [0...cut_points[i], cut_points[i]...cut_points[j],
|
102
|
+
cut_points[j]...cut_points[k], cut_points[k]..-1].
|
103
|
+
map { |r| send_string[r] }
|
104
|
+
assert_equal ex_frames, ReceiveFramesMock.new(packets).replay.frames
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
def test_natural_encoding
|
111
|
+
table = [[0, "\0"], [1, "\x01"], [127, "\x7f"], [128, "\x80\x01"],
|
112
|
+
[65535, "\xff\xff\x03"], [0xf0f0f0, "\xf0\xe1\xc3\x07"],
|
113
|
+
[0xaa55aa55aa55, "\xd5\xd4\xd6\xd2\xda\xca\x2a"]]
|
114
|
+
table.each do |entry|
|
115
|
+
assert_equal entry.last, FP.encode_natural(entry.first)
|
116
|
+
assert_equal entry.first, FP.decode_natural(entry.last)
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
data/test/test_gems.rb
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'digest/sha1'
|
2
|
+
require 'test/unit'
|
3
|
+
|
4
|
+
require 'zerg_support'
|
5
|
+
|
6
|
+
class TestGems < Test::Unit::TestCase
|
7
|
+
def hash_gems_file
|
8
|
+
file_path = File.join(File.dirname(__FILE__),
|
9
|
+
'../lib/zerg_support/gems.rb')
|
10
|
+
Digest::SHA1.hexdigest File.read(file_path)
|
11
|
+
end
|
12
|
+
|
13
|
+
def test_source_is_manually_tested
|
14
|
+
golden_hash = '21ba9ca1e8fa3aa205b468a884746eb37697df9b'
|
15
|
+
source_hash = hash_gems_file
|
16
|
+
|
17
|
+
assert_equal golden_hash, source_hash, <<END_MESSAGE
|
18
|
+
lib/zerg_support/zerg_support.rb has changed
|
19
|
+
|
20
|
+
You need to manually test the file, then replace golden_hash in this test.
|
21
|
+
Manual testing plan:
|
22
|
+
1. rake install this gem (zerg_support)
|
23
|
+
2. install zerg
|
24
|
+
3. validate that the installation does not crash and the binary gets symlinked
|
25
|
+
in the correct place
|
26
|
+
4. replace golden_hash with #{source_hash}
|
27
|
+
END_MESSAGE
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
|
3
|
+
require 'zerg_support'
|
4
|
+
|
5
|
+
class ObjectProtocolTest < Test::Unit::TestCase
|
6
|
+
OP = Zerg::Support::EventMachine::ObjectProtocol
|
7
|
+
|
8
|
+
class SendObjectMock < Zerg::Support::EventMachine::SendMock
|
9
|
+
include OP
|
10
|
+
end
|
11
|
+
class ReceiveObjectMock < Zerg::Support::EventMachine::ReceiveMock
|
12
|
+
include OP
|
13
|
+
object_name :object
|
14
|
+
end
|
15
|
+
|
16
|
+
def one_test(*objects)
|
17
|
+
send_mock = SendObjectMock.new
|
18
|
+
objects.each { |o| send_mock.send_object o }
|
19
|
+
assert_equal objects, ReceiveObjectMock.new(send_mock.string).replay.objects
|
20
|
+
end
|
21
|
+
|
22
|
+
def test_border_cases
|
23
|
+
one_test({})
|
24
|
+
one_test(nil)
|
25
|
+
one_test('')
|
26
|
+
one_test(false)
|
27
|
+
one_test(0)
|
28
|
+
one_test([])
|
29
|
+
end
|
30
|
+
|
31
|
+
def test_literals
|
32
|
+
one_test(1)
|
33
|
+
one_test(true)
|
34
|
+
one_test('A')
|
35
|
+
one_test("A\nB\nC\tD")
|
36
|
+
end
|
37
|
+
|
38
|
+
def test_arrays
|
39
|
+
one_test(['A'])
|
40
|
+
one_test(['B', 'C'])
|
41
|
+
one_test([0, 'C', [9, 'e'], true])
|
42
|
+
end
|
43
|
+
|
44
|
+
def test_hashes
|
45
|
+
one_test({:command => 'run', :binary => '/bin/sh', :quick => false})
|
46
|
+
one_test({:command => 'run', :args => { :key => 'v3', :log => true}})
|
47
|
+
end
|
48
|
+
|
49
|
+
def test_multiple_nested
|
50
|
+
one_test({:command => 'init', :user => "m\0e", :key => false },
|
51
|
+
{:command => 'package', :map => {'dir' => 1, 'NA' => true} },
|
52
|
+
"1234567890",
|
53
|
+
{:command => 'run', :sequence => [1, nil, 2, true, "Q\n"]})
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,125 @@
|
|
1
|
+
require 'zerg_support'
|
2
|
+
|
3
|
+
require 'test/unit'
|
4
|
+
|
5
|
+
module OpenSSHData; end
|
6
|
+
|
7
|
+
class OpenSshTest < Test::Unit::TestCase
|
8
|
+
include OpenSSHData
|
9
|
+
OpenSSH = Zerg::Support::OpenSSH
|
10
|
+
|
11
|
+
def setup
|
12
|
+
@rsa_key = OpenSSH.load_key StringIO.new(obsidian_rsa_privkey)
|
13
|
+
@dsa_key = OpenSSH.load_key StringIO.new(obsidian_dsa_privkey)
|
14
|
+
end
|
15
|
+
|
16
|
+
@@mpi_test_table = [[0, "\0"], [0x80, "\0\x80"], [0x1234, "\x12\x34"],
|
17
|
+
[0xAA55, "\0\xAA\x55"]]
|
18
|
+
def test_encode_mpi
|
19
|
+
@@mpi_test_table.each {|e| assert_equal e.last, OpenSSH.encode_mpi(e.first) }
|
20
|
+
end
|
21
|
+
|
22
|
+
def test_decode_mpi
|
23
|
+
@@mpi_test_table.each {|e| assert_equal e.first, OpenSSH.decode_mpi(e.last) }
|
24
|
+
end
|
25
|
+
|
26
|
+
@@pubkey_pack_table = [[[], ''], [[''], "\0\0\0\0"],
|
27
|
+
[['A'], "\0\0\0\1A"],
|
28
|
+
[['A', 'BC'], "\0\0\0\1A\0\0\0\2BC"]]
|
29
|
+
|
30
|
+
def test_pack_pubkey_components
|
31
|
+
@@pubkey_pack_table.each do |e|
|
32
|
+
assert_equal e.last, OpenSSH.pack_pubkey_components(e.first)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def test_unpack_pubkey_components
|
37
|
+
@@pubkey_pack_table.each do |e|
|
38
|
+
assert_equal e.first, OpenSSH.unpack_pubkey_components(e.last)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def test_load_key
|
43
|
+
assert_equal obsidian_rsa_privkey, @rsa_key.to_pem
|
44
|
+
assert_equal obsidian_dsa_privkey, @dsa_key.to_pem
|
45
|
+
end
|
46
|
+
|
47
|
+
def test_encode_pubkey
|
48
|
+
assert_equal obsidian_rsa_pubkey, OpenSSH.encode_pubkey(@rsa_key)
|
49
|
+
assert_equal obsidian_dsa_pubkey, OpenSSH.encode_pubkey(@dsa_key)
|
50
|
+
end
|
51
|
+
|
52
|
+
def test_decode_pubkey
|
53
|
+
assert_equal @rsa_key.public_key.to_pem,
|
54
|
+
OpenSSH.decode_pubkey(obsidian_rsa_pubkey).to_pem
|
55
|
+
assert_equal @dsa_key.public_key.to_pem,
|
56
|
+
OpenSSH.decode_pubkey(obsidian_dsa_pubkey).to_pem
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
module OpenSSHData
|
61
|
+
def known_hosts_file
|
62
|
+
<<END_KH
|
63
|
+
github.com,65.74.177.129 ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAq2A7hRGmdnm9tUDbO9IDSwBK6TbQa+PXYPCPy6rbTrTtw7PHkccKrpp0yVhp5HdEIcKr6pLlVDBfOLX9QUsyCOV0wzfjIJNlGEYsdlLJizHhbn2mUjvSAHQqZETYP81eFzLQNnPHt4EVVUh7VfDESU84KezmD5QlWpXLmvU31/yMf+Se8xhHTvKSCZIFImWwoG6mbUoWf9nzpIoaSjB+weqqUUmpaaasXVal72J+UX2B+2RPW3RcT0eOzQgqlJL3RKrTJvdsjE3JEAvGq3lGHSZXy28G3skua2SmVi/w4yCE6gbODqnTWlg7+wC604ydGXA8VJiS5ap43JXiUFFAaQ==
|
64
|
+
obsidian.local ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAq7VciLo2wB+/2ThHXejC/kgkN205zlyS5cvN40Mqi3qvRVS75X1RawDoLot8eJ9KCYqZFr2Dr73d/EQNltN7dKJoPIj1IKxoraWkyNFbhhzpYuOltg9oO5UBSTNLupqla7zcdj4IwCCkBYk4+TS0dwmi20buOJ0FPY5PgbzmMnUiV9ipBqeJSdZB+TePH1gqlt7AP/6ti/0gxb2K7F69dZl/BSxMEzRCfBlTFC3f/4n8IdCuSJvNxxY+TtRnLL5CKUhj9QaIBan6JCkdRvVOBY7wmsNT8nGDzfDFSDD3KKn93g4LRkyMeaYlSDLxKy8PnNhjWgBNH1YNYyicsGfBKQ==
|
65
|
+
rubyforge.org,205.234.109.19 ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEA3tPhdPUFGAYyrT1quRSOevLbAdKAJ6Ovwqw0m99R0QkqUwMUh09pgedWZeij7HtAHtoWPrNFev8FrwcwnL14NgA/gwNnXxbqd4twC1HyFShUf7POry8bz3Qk+84STHeMY8++hhn8LgNyfuVQswHoW661aqieLM6pF8q8xIUtkXA7daNpJAL4nTN1TxgUoCDpCa0EbUPkGpwFPNtGPuokRXNOCR9g8T6LmPQbzGUTc4CzFfQ9rrHaimqkEmRWJbBOaik1bdQNqOh6MUDDuUSpkJV7fwu3bl4fF5/1kw2HCREEjJmESOYnZhOS+MCp1qAUcuXqpMqXD8ATNsuXqrIhTQ==
|
66
|
+
END_KH
|
67
|
+
end
|
68
|
+
|
69
|
+
def obsidian_rsa_pubkey
|
70
|
+
'AAAAB3NzaC1yc2EAAAABIwAAAQEAq7VciLo2wB+/2ThHXejC/kgkN205zlyS5cvN40Mqi3qvRVS75X1RawDoLot8eJ9KCYqZFr2Dr73d/EQNltN7dKJoPIj1IKxoraWkyNFbhhzpYuOltg9oO5UBSTNLupqla7zcdj4IwCCkBYk4+TS0dwmi20buOJ0FPY5PgbzmMnUiV9ipBqeJSdZB+TePH1gqlt7AP/6ti/0gxb2K7F69dZl/BSxMEzRCfBlTFC3f/4n8IdCuSJvNxxY+TtRnLL5CKUhj9QaIBan6JCkdRvVOBY7wmsNT8nGDzfDFSDD3KKn93g4LRkyMeaYlSDLxKy8PnNhjWgBNH1YNYyicsGfBKQ=='
|
71
|
+
end
|
72
|
+
|
73
|
+
def obsidian_dsa_pubkey
|
74
|
+
'AAAAB3NzaC1kc3MAAACBAPdWp4HhWUoNawosyEBvrhPSbjhCJiKnVWiUS6bo0BCGzTHhugrkv2HgtlKhWo8nqTw5E4YxzFVyZ0YQt4m7NYDLTZVjrqbIpL/3F5qNXco127O/im0cG27AKC8Jf7knmUTjd8EBhtK65tNDmxPtzKQtemlNTVPX1VccOn6eLtn1AAAAFQDtQC21TJrf/p5WNsU9UIJzO9/hIwAAAIEAgwTrIfleQMEAK9N3xeMVZpAGfSAoX6owLtk3z+iQ3rM9FRvM/CgezOgezLowJghkw/bcQDmMuudBUuijrM3zZWdr6eqoNbFTR/KKiUx3cYf0LAHNPbXfVz+P7BXqjcEj75qnwuHQMp7vNMg+dmV40UA2TiC5/8QlaZVwkOSPN6gAAACAPMGJEFkoR0ayfkd0S/tnY9ilO17T6rdoDuF25ATtNUd6Zji6tslxBkQFWtTeinO3rGkqJRPndq0wp3E33AOHhJE/FOIlWl4Tf6aeU95Y4enYujKQDiImSTXmdiw5wq/LFdc3a2waOUvuI+647wxgHhqTmD7xI2biGZLYN9Oasy0='
|
75
|
+
end
|
76
|
+
|
77
|
+
def obsidian_rsa_privkey
|
78
|
+
<<END_RSAK
|
79
|
+
-----BEGIN RSA PRIVATE KEY-----
|
80
|
+
MIIEoQIBAAKCAQEAq7VciLo2wB+/2ThHXejC/kgkN205zlyS5cvN40Mqi3qvRVS7
|
81
|
+
5X1RawDoLot8eJ9KCYqZFr2Dr73d/EQNltN7dKJoPIj1IKxoraWkyNFbhhzpYuOl
|
82
|
+
tg9oO5UBSTNLupqla7zcdj4IwCCkBYk4+TS0dwmi20buOJ0FPY5PgbzmMnUiV9ip
|
83
|
+
BqeJSdZB+TePH1gqlt7AP/6ti/0gxb2K7F69dZl/BSxMEzRCfBlTFC3f/4n8IdCu
|
84
|
+
SJvNxxY+TtRnLL5CKUhj9QaIBan6JCkdRvVOBY7wmsNT8nGDzfDFSDD3KKn93g4L
|
85
|
+
RkyMeaYlSDLxKy8PnNhjWgBNH1YNYyicsGfBKQIBIwKCAQA/xwUc13Nr7okWKtit
|
86
|
+
2hyKVU9HyXve7y8/aPSzf1jx+l5blIBN7LfXSXrPdaNCvtJbULyEygxXN+S8yNHY
|
87
|
+
73b/b4XNV3D9gd281x/y0WsjL09fPpyitUP435LDatL8Ks+6TXZ1D7obedaFtqAi
|
88
|
+
DEMHpH5Rch33xUsW3RY3gK1F8GHzYh+zMd0llH8w6IVBv0hrWttyp+4By1dhXA3L
|
89
|
+
alYYOlGbu5cNuJfz50v4T/uB9F63JqHtAxEjqD7SvjCUbogGPxEH1k4Or+3xGuvR
|
90
|
+
yTPaiuR9kehBstjbqc91rTLnGOr5a1VUIq6++0tCVOXQa+x5J5zCu/baGTdfkgRU
|
91
|
+
loILAoGBAOHQcGIEgD1xHwFX9AafarF+1j4pPkM1p2u+9DNTIl1f2Rk6yOHlHZGF
|
92
|
+
mfozMyh6l4HpvPTT7nflM4YR0JWJNbQIpCY8CdYKMXBVWcH4PXuqwyGKJBZMXWkn
|
93
|
+
hKvelJ0UZUwx5+n8fTmSxLcIIlz5uKEE5k23/3cKkP7P6+Z6YuIZAoGBAMKpYBkM
|
94
|
+
A4JMJ3QJ5TVsdFOoO0b1wThRgQTZR6ic9rNeXnZiL0mmr44DX2T1rTnEm6KZv2f2
|
95
|
+
kWLQp5jx8BPFI+TUv16j4xrnnuHPhuBordCYQizSPcxXqL8b8a/KU+S5xofRjwB6
|
96
|
+
+8szd/JmGFDY8WgWrtIL32TdRhcIFg0Dg1mRAoGAE1sCUYtbcvsRSUINmirr41QD
|
97
|
+
vC9rvJ4y6/pss/Eu1M2zhdHWtEbWphLEDiGlTJzLKGR96Rl61xOlVKJw9t/gCB3/
|
98
|
+
cP3U9RbRCaDqb7YxJ9tvz6zBQ71nF6RNM02XtbFKgt+0yunBlzh3QuNwqOI0ZZK0
|
99
|
+
p5Ntqx4pr3DoVZV2MKMCgYBprGdeDdYE54MhvDqZWCHkRWIB8x+/fLPAzbkvpap+
|
100
|
+
oPFzdyD8GKhxqg82zoKbs991hqm8GCMJwboRMuFp0WtBtVHxjCrUF1ZAEZJckJje
|
101
|
+
85GjTY9DCwPVdZHUdSY6VjiS33mD6v23c7YkgJDbbnRrtIrJy+5MsqJkRjfbLcr2
|
102
|
+
GwKBgQCWDKVM8mZJcmM3KJwzBWh+b5T7tIbOBFaCvOBq4G0UUZGytWVUaddSbdh5
|
103
|
+
YJ+1CoLDB+Wp3U95B1USQuSYwwr0wOYy42HBII7HnkaphT9HgDIhgvttFhX162oC
|
104
|
+
bRizr31NgiGtgJbHJ9QPhaLvn3mZtZAFfRaEEdZslUzqRSACww==
|
105
|
+
-----END RSA PRIVATE KEY-----
|
106
|
+
END_RSAK
|
107
|
+
end
|
108
|
+
|
109
|
+
def obsidian_dsa_privkey
|
110
|
+
<<END_DSAK
|
111
|
+
-----BEGIN DSA PRIVATE KEY-----
|
112
|
+
MIIBvAIBAAKBgQD3VqeB4VlKDWsKLMhAb64T0m44QiYip1VolEum6NAQhs0x4boK
|
113
|
+
5L9h4LZSoVqPJ6k8OROGMcxVcmdGELeJuzWAy02VY66myKS/9xeajV3KNduzv4pt
|
114
|
+
HBtuwCgvCX+5J5lE43fBAYbSuubTQ5sT7cykLXppTU1T19VXHDp+ni7Z9QIVAO1A
|
115
|
+
LbVMmt/+nlY2xT1QgnM73+EjAoGBAIME6yH5XkDBACvTd8XjFWaQBn0gKF+qMC7Z
|
116
|
+
N8/okN6zPRUbzPwoHszoHsy6MCYIZMP23EA5jLrnQVLoo6zN82Vna+nqqDWxU0fy
|
117
|
+
iolMd3GH9CwBzT2131c/j+wV6o3BI++ap8Lh0DKe7zTIPnZleNFANk4guf/EJWmV
|
118
|
+
cJDkjzeoAoGAPMGJEFkoR0ayfkd0S/tnY9ilO17T6rdoDuF25ATtNUd6Zji6tslx
|
119
|
+
BkQFWtTeinO3rGkqJRPndq0wp3E33AOHhJE/FOIlWl4Tf6aeU95Y4enYujKQDiIm
|
120
|
+
STXmdiw5wq/LFdc3a2waOUvuI+647wxgHhqTmD7xI2biGZLYN9Oasy0CFQDL/9Z1
|
121
|
+
fSLn88m1sUeWAZ4Ys2IIxw==
|
122
|
+
-----END DSA PRIVATE KEY-----
|
123
|
+
END_DSAK
|
124
|
+
end
|
125
|
+
end
|