costan-zerg_support 0.0.9
Sign up to get free protection for your applications and to get access to all the features.
- 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
|