qa_robusta 0.1.3
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/.autotest +23 -0
- data/.gemtest +0 -0
- data/History.txt +6 -0
- data/Manifest.txt +101 -0
- data/README.txt +48 -0
- data/Rakefile +18 -0
- data/bin/qa_robusta +14 -0
- data/common/Rakefile +95 -0
- data/common/conf/monkey_patch.yaml +8 -0
- data/common/conf/monkey_patch.yaml.ex +10 -0
- data/common/lib/array.rb +9 -0
- data/common/lib/cache.rb +12 -0
- data/common/lib/constants.rb +35 -0
- data/common/lib/error_defns.rb +13 -0
- data/common/lib/format_html_tmp.html +34 -0
- data/common/lib/formatters.rb +18 -0
- data/common/lib/gem_helpers.rb +29 -0
- data/common/lib/gems/.svn/entries +43 -0
- data/common/lib/gems/cache/.svn/entries +28 -0
- data/common/lib/gems/doc/.svn/entries +28 -0
- data/common/lib/gems/gems/.svn/entries +28 -0
- data/common/lib/gems/installed/.svn/entries +40 -0
- data/common/lib/gems/installed/cache/.svn/entries +28 -0
- data/common/lib/gems/installed/doc/.svn/entries +28 -0
- data/common/lib/gems/installed/gems/.svn/entries +28 -0
- data/common/lib/gems/installed/specifications/.svn/entries +28 -0
- data/common/lib/gems/specifications/.svn/entries +28 -0
- data/common/lib/gen_suite_doc.rb +52 -0
- data/common/lib/load_test_data.rb +18 -0
- data/common/lib/monkey_patch.rb +149 -0
- data/common/lib/navigate_mech.rb +79 -0
- data/common/lib/update_element.rb +12 -0
- data/common/monkey_patches/mechanize.rb +589 -0
- data/common/monkey_patches/table.rb +363 -0
- data/common/monkey_patches/telnet.rb +420 -0
- data/common/monkey_patches/unit.rb +538 -0
- data/demo/demo_site.rb +38 -0
- data/demo/public/javascripts/jquery-1.6.2.min.js +18 -0
- data/demo/views/index.erb +35 -0
- data/lib/monkey_patch.rb +26 -0
- data/lib/qa_robusta.rb +3 -0
- data/mechanize_interface/conf/app.yaml +10 -0
- data/mechanize_interface/lib/agent.rb +18 -0
- data/mechanize_interface/lib/app_require.rb +19 -0
- data/mechanize_interface/lib/get_page.rb +20 -0
- data/mechanize_interface/lib/login.rb +30 -0
- data/mechanize_interface/lib/navigation_paths.rb +12 -0
- data/mechanize_interface/test/lib/mech_unit_test.rb +19 -0
- data/qa_observer/conf/dev_users.yaml +9 -0
- data/qa_observer/conf/development.yaml +3 -0
- data/qa_observer/conf/qa_observer_links.yaml +10 -0
- data/qa_observer/generators/site/site_generator.rb +61 -0
- data/qa_observer/generators/site/templates/base_urls.yaml.erb +6 -0
- data/qa_observer/generators/site/templates/create_flow.rb.erb +54 -0
- data/qa_observer/generators/site/templates/elements.rb.erb +61 -0
- data/qa_observer/generators/site/templates/html_elements.yaml +22 -0
- data/qa_observer/generators/site/templates/navigate.rb +32 -0
- data/qa_observer/generators/site/templates/reports.rb +7 -0
- data/qa_observer/generators/site/templates/users_list.yaml +21 -0
- data/qa_observer/generators/test_case/templates/test_case.rb.erb +62 -0
- data/qa_observer/generators/test_case/test_case_generator.rb +29 -0
- data/qa_observer/lib/doc.rb +103 -0
- data/qa_observer/lib/env_details.rb +2 -0
- data/qa_observer/lib/error_defns.rb +3 -0
- data/qa_observer/lib/form_helpers.rb +52 -0
- data/qa_observer/lib/reports.rb +7 -0
- data/qa_observer/lib/requires.rb +23 -0
- data/qa_observer/lib/system_test.rb +146 -0
- data/qa_observer/lib/test_extention.rb +29 -0
- data/qa_observer/lib/update_element.rb +12 -0
- data/qa_observer/lib/watir.rb +42 -0
- data/qa_observer/script/destroy +14 -0
- data/qa_observer/script/generate +14 -0
- data/qa_observer/sites/demo/conf/base_urls.yaml +6 -0
- data/qa_observer/sites/demo/conf/html_elements.yaml +22 -0
- data/qa_observer/sites/demo/conf/remote_machine.yaml +12 -0
- data/qa_observer/sites/demo/conf/users_list.yaml +21 -0
- data/qa_observer/sites/demo/elements/demo_elements.rb +9 -0
- data/qa_observer/sites/demo/flows/demo_flows.rb +37 -0
- data/qa_observer/sites/demo/helpers/.placeholder +0 -0
- data/qa_observer/sites/demo/lib/navigate.rb +32 -0
- data/qa_observer/sites/demo/lib/reports.rb +7 -0
- data/qa_observer/sites/demo/results/.placeholder +0 -0
- data/qa_observer/sites/demo/results/images/.placeholder +0 -0
- data/qa_observer/sites/demo/test_cases/home_page.rb +106 -0
- data/qa_observer/suites/demo_lg_chrome/lg.yaml +11 -0
- data/qa_observer/suites/demo_md_chrome/md.yaml +11 -0
- data/qa_observer/suites/demo_sm_chrome/sm.yaml +11 -0
- data/qa_observer/suites/init.rb +41 -0
- data/qa_observer/test_runner.rb +125 -0
- data/remote_unix/helpers/constants.rb +8 -0
- data/remote_unix/helpers/out_xforms.rb +48 -0
- data/remote_unix/lib/common.rb +52 -0
- data/remote_unix/lib/error_defn.rb +4 -0
- data/remote_unix/lib/general_unix.rb +308 -0
- data/remote_unix/lib/network_commands.rb +41 -0
- data/remote_unix/lib/network_info.rb +98 -0
- data/remote_unix/lib/postfix_commands.rb +87 -0
- data/remote_unix/lib/postfix_info.rb +30 -0
- data/remote_unix/lib/requires.rb +25 -0
- data/remote_unix/lib/scp.rb +22 -0
- data/remote_unix/lib/ssh.rb +46 -0
- data/test/test_qa_robusta.rb +8 -0
- metadata +219 -0
|
@@ -0,0 +1,363 @@
|
|
|
1
|
+
module Watir
|
|
2
|
+
|
|
3
|
+
# This class is used for dealing with tables.
|
|
4
|
+
# Normally a user would not need to create this object as it is returned by the Watir::Container#table method
|
|
5
|
+
#
|
|
6
|
+
# many of the methods available to this object are inherited from the Element class
|
|
7
|
+
#
|
|
8
|
+
class Table < Element
|
|
9
|
+
include Container
|
|
10
|
+
|
|
11
|
+
# Returns the table object containing the element
|
|
12
|
+
# * container - an instance of an IE object
|
|
13
|
+
# * anElement - a Watir object (TextField, Button, etc.)
|
|
14
|
+
def Table.create_from_element(container, element)
|
|
15
|
+
element.locate if defined?(element.locate)
|
|
16
|
+
o = element.ole_object.parentElement
|
|
17
|
+
o = o.parentElement until o.tagName == 'TABLE'
|
|
18
|
+
new container, :ole_object, o
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Returns an initialized instance of a table object
|
|
22
|
+
# * container - the container
|
|
23
|
+
# * how - symbol - how we access the table
|
|
24
|
+
# * what - what we use to access the table - id, name index etc
|
|
25
|
+
def initialize(container, how, what)
|
|
26
|
+
set_container container
|
|
27
|
+
@how = how
|
|
28
|
+
@what = what
|
|
29
|
+
super nil
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def locate
|
|
33
|
+
if @how == :xpath
|
|
34
|
+
@o = @container.element_by_xpath(@what)
|
|
35
|
+
elsif @how == :ole_object
|
|
36
|
+
@o = @what
|
|
37
|
+
else
|
|
38
|
+
@o = @container.locate_tagged_element('TABLE', @how, @what)
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# override the highlight method, as if the tables rows are set to have a background color,
|
|
43
|
+
# this will override the table background color, and the normal flash method won't work
|
|
44
|
+
def highlight(set_or_clear)
|
|
45
|
+
if set_or_clear == :set
|
|
46
|
+
begin
|
|
47
|
+
@original_border = @o.border.to_i
|
|
48
|
+
if @o.border.to_i==1
|
|
49
|
+
@o.border = 2
|
|
50
|
+
else
|
|
51
|
+
@o.border = 1
|
|
52
|
+
end
|
|
53
|
+
rescue
|
|
54
|
+
@original_border = nil
|
|
55
|
+
end
|
|
56
|
+
else
|
|
57
|
+
begin
|
|
58
|
+
@o.border= @original_border unless @original_border == nil
|
|
59
|
+
@original_border = nil
|
|
60
|
+
rescue
|
|
61
|
+
# we could be here for a number of reasons...
|
|
62
|
+
ensure
|
|
63
|
+
@original_border = nil
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
super
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# this method is used to populate the properties in the to_s method
|
|
70
|
+
def table_string_creator
|
|
71
|
+
n = []
|
|
72
|
+
n << "rows:".ljust(TO_S_SIZE) + self.row_count.to_s
|
|
73
|
+
n << "cols:".ljust(TO_S_SIZE) + self.column_count.to_s
|
|
74
|
+
return n
|
|
75
|
+
end
|
|
76
|
+
private :table_string_creator
|
|
77
|
+
|
|
78
|
+
# returns the properties of the object in a string
|
|
79
|
+
# raises an ObjectNotFound exception if the object cannot be found
|
|
80
|
+
def to_s
|
|
81
|
+
assert_exists
|
|
82
|
+
r = string_creator
|
|
83
|
+
r += table_string_creator
|
|
84
|
+
return r.join("\n")
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# iterates through the rows in the table. Yields a TableRow object
|
|
88
|
+
def each
|
|
89
|
+
assert_exists
|
|
90
|
+
0.upto(@o.getElementsByTagName("TR").length) do |i|
|
|
91
|
+
yield TableRow.new(@container, :ole_object, _row(i))
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# Returns a row in the table
|
|
96
|
+
# * index - the index of the row
|
|
97
|
+
def [](index)
|
|
98
|
+
assert_exists
|
|
99
|
+
return TableRow.new(@container, :ole_object, _row(index))
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# Returns the number of rows inside the table, including rows in nested tables.
|
|
103
|
+
def row_count
|
|
104
|
+
assert_exists
|
|
105
|
+
#return table_body.children.length
|
|
106
|
+
return @o.getElementsByTagName("TR").length
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# Returns the number of rows in the table, not including rows in nested tables.
|
|
110
|
+
def row_count_excluding_nested_tables
|
|
111
|
+
assert_exists
|
|
112
|
+
return @o.rows.length
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# This method returns the number of columns in a row of the table.
|
|
116
|
+
# Raises an UnknownObjectException if the table doesn't exist.
|
|
117
|
+
# * index - the index of the row
|
|
118
|
+
def column_count(index=0)
|
|
119
|
+
assert_exists
|
|
120
|
+
_row(index).cells.length
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
# This method returns the table as a 2 dimensional array.
|
|
124
|
+
# Don't expect too much if there are nested tables, colspan etc.
|
|
125
|
+
# Raises an UnknownObjectException if the table doesn't exist.
|
|
126
|
+
# http://www.w3.org/TR/html4/struct/tables.html
|
|
127
|
+
def to_a
|
|
128
|
+
assert_exists
|
|
129
|
+
y = []
|
|
130
|
+
table_rows = @o.getElementsByTagName("TR")
|
|
131
|
+
for row in table_rows
|
|
132
|
+
x = []
|
|
133
|
+
for td in row.getElementsbyTagName("TD")
|
|
134
|
+
x << td.innerText.strip
|
|
135
|
+
end
|
|
136
|
+
y << x
|
|
137
|
+
end
|
|
138
|
+
return y
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def table_body(index=1)
|
|
142
|
+
return @o.getElementsByTagName('TBODY')[index]
|
|
143
|
+
end
|
|
144
|
+
private :table_body
|
|
145
|
+
|
|
146
|
+
# returns a watir object
|
|
147
|
+
def body(how, what)
|
|
148
|
+
return TableBody.new(@container, how, what, self)
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
# returns a watir object
|
|
152
|
+
def bodies
|
|
153
|
+
assert_exists
|
|
154
|
+
return TableBodies.new(@container, @o)
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
# returns an ole object
|
|
158
|
+
def _row(index)
|
|
159
|
+
return @o.invoke("rows")[(index - 1).to_s]
|
|
160
|
+
end
|
|
161
|
+
private :_row
|
|
162
|
+
|
|
163
|
+
# Returns an array containing all the text values in the specified column
|
|
164
|
+
# Raises an UnknownCellException if the specified column does not exist in every
|
|
165
|
+
# Raises an UnknownObjectException if the table doesn't exist.
|
|
166
|
+
# row of the table
|
|
167
|
+
# * columnnumber - column index to extract values from
|
|
168
|
+
def column_values(columnnumber)
|
|
169
|
+
return (1..row_count).collect {|i| self[i][columnnumber].text}
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
# Returns an array containing all the text values in the specified row
|
|
173
|
+
# Raises an UnknownObjectException if the table doesn't exist.
|
|
174
|
+
# * rownumber - row index to extract values from
|
|
175
|
+
def row_values(rownumber)
|
|
176
|
+
return (1..column_count(rownumber)).collect {|i| self[rownumber][i].text}
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
# this class is a collection of the table body objects that exist in the table
|
|
182
|
+
# it wouldnt normally be created by a user, but gets returned by the bodies method of the Table object
|
|
183
|
+
# many of the methods available to this object are inherited from the Element class
|
|
184
|
+
#
|
|
185
|
+
class TableBodies < Element
|
|
186
|
+
def initialize(container, parent_table)
|
|
187
|
+
set_container container
|
|
188
|
+
@o = parent_table # in this case, @o is the parent table
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
# returns the number of TableBodies that exist in the table
|
|
192
|
+
def length
|
|
193
|
+
assert_exists
|
|
194
|
+
return @o.tBodies.length
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
# returns the n'th Body as a Watir TableBody object
|
|
198
|
+
def []n
|
|
199
|
+
assert_exists
|
|
200
|
+
return TableBody.new(@container, :ole_object, ole_table_body_at_index(n))
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
# returns an ole table body
|
|
204
|
+
def ole_table_body_at_index(n)
|
|
205
|
+
return @o.tBodies[(n-1).to_s]
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
# iterates through each of the TableBodies in the Table. Yields a TableBody object
|
|
209
|
+
def each
|
|
210
|
+
1.upto(@o.tBodies.length) do |i|
|
|
211
|
+
yield TableBody.new(@container, :ole_object, ole_table_body_at_index(i))
|
|
212
|
+
end
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
# this class is a table body
|
|
218
|
+
class TableBody < Element
|
|
219
|
+
def locate
|
|
220
|
+
@o = nil
|
|
221
|
+
if @how == :ole_object
|
|
222
|
+
@o = @what # in this case, @o is the table body
|
|
223
|
+
elsif @how == :index
|
|
224
|
+
@o = @parent_table.bodies.ole_table_body_at_index(@what)
|
|
225
|
+
end
|
|
226
|
+
@rows = []
|
|
227
|
+
if @o
|
|
228
|
+
@o.rows.each do |oo|
|
|
229
|
+
@rows << TableRow.new(@container, :ole_object, oo)
|
|
230
|
+
end
|
|
231
|
+
end
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
def initialize(container, how, what, parent_table=nil)
|
|
235
|
+
set_container container
|
|
236
|
+
@how = how
|
|
237
|
+
@what = what
|
|
238
|
+
@parent_table = parent_table
|
|
239
|
+
super nil
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
# returns the specified row as a TableRow object
|
|
243
|
+
def [](n)
|
|
244
|
+
assert_exists
|
|
245
|
+
return @rows[n - 1]
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
# iterates through all the rows in the table body
|
|
249
|
+
def each
|
|
250
|
+
locate
|
|
251
|
+
0.upto(@rows.length - 1) { |i| yield @rows[i] }
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
# returns the number of rows in this table body.
|
|
255
|
+
def length
|
|
256
|
+
return @rows.length
|
|
257
|
+
end
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
class TableRow < Element
|
|
261
|
+
|
|
262
|
+
def locate
|
|
263
|
+
@o = nil
|
|
264
|
+
if @how == :ole_object
|
|
265
|
+
@o = @what
|
|
266
|
+
elsif @how == :xpath
|
|
267
|
+
@o = @container.element_by_xpath(@what)
|
|
268
|
+
else
|
|
269
|
+
@o = @container.locate_tagged_element("TR", @how, @what)
|
|
270
|
+
end
|
|
271
|
+
if @o # cant call the assert_exists here, as an exists? method call will fail
|
|
272
|
+
@cells = []
|
|
273
|
+
@o.cells.each do |oo|
|
|
274
|
+
@cells << TableCell.new(@container, :ole_object, oo)
|
|
275
|
+
end
|
|
276
|
+
end
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
# Returns an initialized instance of a table row
|
|
280
|
+
# * o - the object contained in the row
|
|
281
|
+
# * container - an instance of an IE object
|
|
282
|
+
# * how - symbol - how we access the row
|
|
283
|
+
# * what - what we use to access the row - id, index etc. If how is :ole_object then what is a Internet Explorer Raw Row
|
|
284
|
+
def initialize(container, how, what)
|
|
285
|
+
set_container container
|
|
286
|
+
@how = how
|
|
287
|
+
@what = what
|
|
288
|
+
super nil
|
|
289
|
+
end
|
|
290
|
+
|
|
291
|
+
# this method iterates through each of the cells in the row. Yields a TableCell object
|
|
292
|
+
def each
|
|
293
|
+
locate
|
|
294
|
+
0.upto(@cells.length) { |i| yield @cells[i] }
|
|
295
|
+
end
|
|
296
|
+
|
|
297
|
+
# Returns an element from the row as a TableCell object
|
|
298
|
+
def [](index)
|
|
299
|
+
assert_exists
|
|
300
|
+
if @cells.length < index
|
|
301
|
+
raise UnknownCellException, "Unable to locate a cell at index #{index}"
|
|
302
|
+
end
|
|
303
|
+
return @cells[(index)]
|
|
304
|
+
end
|
|
305
|
+
|
|
306
|
+
# defaults all missing methods to the array of elements, to be able to
|
|
307
|
+
# use the row as an array
|
|
308
|
+
# def method_missing(aSymbol, *args)
|
|
309
|
+
# return @o.send(aSymbol, *args)
|
|
310
|
+
# end
|
|
311
|
+
def column_count
|
|
312
|
+
locate
|
|
313
|
+
@cells.length
|
|
314
|
+
end
|
|
315
|
+
end
|
|
316
|
+
|
|
317
|
+
# this class is a table cell - when called via the Table object
|
|
318
|
+
class TableCell < Element
|
|
319
|
+
include Watir::Exception
|
|
320
|
+
include Container
|
|
321
|
+
|
|
322
|
+
def locate
|
|
323
|
+
if @how == :xpath
|
|
324
|
+
@o = @container.element_by_xpath(@what)
|
|
325
|
+
elsif @how == :ole_object
|
|
326
|
+
@o = @what
|
|
327
|
+
else
|
|
328
|
+
@o = @container.locate_tagged_element("TD", @how, @what)
|
|
329
|
+
end
|
|
330
|
+
end
|
|
331
|
+
|
|
332
|
+
# Returns an initialized instance of a table cell
|
|
333
|
+
# * container - an IE object
|
|
334
|
+
# * how - symbol - how we access the cell
|
|
335
|
+
# * what - what we use to access the cell - id, name index etc
|
|
336
|
+
def initialize(container, how, what)
|
|
337
|
+
set_container container
|
|
338
|
+
@how = how
|
|
339
|
+
@what = what
|
|
340
|
+
super nil
|
|
341
|
+
end
|
|
342
|
+
|
|
343
|
+
def ole_inner_elements
|
|
344
|
+
locate
|
|
345
|
+
return @o.all
|
|
346
|
+
end
|
|
347
|
+
private :ole_inner_elements
|
|
348
|
+
|
|
349
|
+
def document
|
|
350
|
+
locate
|
|
351
|
+
return @o
|
|
352
|
+
end
|
|
353
|
+
|
|
354
|
+
alias to_s text
|
|
355
|
+
|
|
356
|
+
def colspan
|
|
357
|
+
locate
|
|
358
|
+
@o.colSpan
|
|
359
|
+
end
|
|
360
|
+
|
|
361
|
+
end
|
|
362
|
+
|
|
363
|
+
end
|
|
@@ -0,0 +1,420 @@
|
|
|
1
|
+
require 'rubygems'
|
|
2
|
+
require 'net/ssh'
|
|
3
|
+
|
|
4
|
+
module Net
|
|
5
|
+
module SSH
|
|
6
|
+
|
|
7
|
+
# == Net::SSH::Telnet
|
|
8
|
+
#
|
|
9
|
+
# Provides a simple send/expect interface with an API almost
|
|
10
|
+
# identical to Net::Telnet. Please see Net::Telnet for main documentation.
|
|
11
|
+
# Only the differences are documented here.
|
|
12
|
+
|
|
13
|
+
class Telnet
|
|
14
|
+
|
|
15
|
+
CR = "\015"
|
|
16
|
+
LF = "\012"
|
|
17
|
+
EOL = CR + LF
|
|
18
|
+
VERSION = '0.0.1'
|
|
19
|
+
|
|
20
|
+
# Wrapper to emulate the behaviour of Net::Telnet "Proxy" option, where
|
|
21
|
+
# the user passes in an already-open socket
|
|
22
|
+
|
|
23
|
+
class TinyFactory
|
|
24
|
+
def initialize(sock)
|
|
25
|
+
@sock = sock
|
|
26
|
+
end
|
|
27
|
+
def open(host, port)
|
|
28
|
+
s = @sock
|
|
29
|
+
@sock = nil
|
|
30
|
+
s
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Creates a new Net::SSH::Telnet object.
|
|
35
|
+
#
|
|
36
|
+
# The API is similar to Net::Telnet, although you will need to pass in
|
|
37
|
+
# either an existing Net::SSH::Session object or a Username and Password,
|
|
38
|
+
# as shown below.
|
|
39
|
+
#
|
|
40
|
+
# Note that unlike Net::Telnet there is no preprocess method automatically
|
|
41
|
+
# setting up options related to proper character translations, so if your
|
|
42
|
+
# remote ptty is configured differently than the typical linux one you may
|
|
43
|
+
# need to pass in a different terminator or call 'stty' remotely to set it
|
|
44
|
+
# into an expected mode. This is better explained by the author of perl's
|
|
45
|
+
# Net::SSH::Expect here:
|
|
46
|
+
#
|
|
47
|
+
# http://search.cpan.org/~bnegrao/Net-SSH-Expect-1.04/lib/Net/SSH/Expect.pod
|
|
48
|
+
# #IMPORTANT_NOTES_ABOUT_DEALING_WITH_SSH_AND_PSEUDO-TERMINALS
|
|
49
|
+
#
|
|
50
|
+
# though for most installs the default LF should be fine. See example 5
|
|
51
|
+
# below.
|
|
52
|
+
#
|
|
53
|
+
# A new option is added to correct a misfeature of Net::Telnet. If you
|
|
54
|
+
# pass "FailEOF" => true, then if the remote end disconnects while you
|
|
55
|
+
# are still waiting for your match pattern then an EOFError is raised.
|
|
56
|
+
# Otherwise, it reverts to the same behaviour as Net::Telnet, which is
|
|
57
|
+
# just to return whatever data was sent so far, or nil if no data was
|
|
58
|
+
# returned so far. (This is a poor design because you can't tell whether
|
|
59
|
+
# the expected pattern was successfully matched or the remote end
|
|
60
|
+
# disconnected unexpectedly, unless you perform a second match on the
|
|
61
|
+
# return string). See
|
|
62
|
+
# http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-core/11373
|
|
63
|
+
# http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-core/11380
|
|
64
|
+
#
|
|
65
|
+
# Example 1 - pass existing Net::SSH::Session object
|
|
66
|
+
#
|
|
67
|
+
# ssh = Net::SSH.start("127.0.0.1",
|
|
68
|
+
# :username=>"test123",
|
|
69
|
+
# :password=>"pass456"
|
|
70
|
+
# )
|
|
71
|
+
# s = Net::SSH::Telnet.new(
|
|
72
|
+
# "Dump_log" => "/dev/stdout",
|
|
73
|
+
# "Session" => ssh
|
|
74
|
+
# )
|
|
75
|
+
# puts "Logged in"
|
|
76
|
+
# p s.cmd("echo hello")
|
|
77
|
+
#
|
|
78
|
+
# This is the most flexible way as it allows you to set up the SSH
|
|
79
|
+
# session using whatever authentication system you like. When done this
|
|
80
|
+
# way, calling Net::SSH::Telnet.new multiple times will create
|
|
81
|
+
# multiple channels, and #close will only close one channel.
|
|
82
|
+
#
|
|
83
|
+
# In all later examples, calling #close will close the entire
|
|
84
|
+
# Net::SSH::Session object (and therefore drop the TCP connection)
|
|
85
|
+
#
|
|
86
|
+
# Example 2 - pass host, username and password
|
|
87
|
+
#
|
|
88
|
+
# s = Net::SSH::Telnet.new(
|
|
89
|
+
# "Dump_log" => "/dev/stdout",
|
|
90
|
+
# "Host" => "127.0.0.1",
|
|
91
|
+
# "Username" => "test123",
|
|
92
|
+
# "Password" => "pass456"
|
|
93
|
+
# )
|
|
94
|
+
# puts "Logged in"
|
|
95
|
+
# puts s.cmd("echo hello")
|
|
96
|
+
#
|
|
97
|
+
# Example 3 - pass open IO object, username and password (this is really
|
|
98
|
+
# just for compatibility with Net::Telnet Proxy feature)
|
|
99
|
+
#
|
|
100
|
+
# require 'socket'
|
|
101
|
+
# sock = TCPSocket.open("127.0.0.1",22)
|
|
102
|
+
# s = Net::SSH::Telnet.new(
|
|
103
|
+
# "Dump_log" => "/dev/stdout",
|
|
104
|
+
# "Proxy" => sock,
|
|
105
|
+
# "Username" => "test123",
|
|
106
|
+
# "Password" => "pass456"
|
|
107
|
+
# )
|
|
108
|
+
# puts "Logged in"
|
|
109
|
+
# puts s.cmd("echo hello")
|
|
110
|
+
#
|
|
111
|
+
# Example 4 - pass a connection factory, host, username and password;
|
|
112
|
+
# Net::SSH will call #open(host,port) on this object. Included just
|
|
113
|
+
# because it was easy :-)
|
|
114
|
+
#
|
|
115
|
+
# require 'socket'
|
|
116
|
+
# s = Net::SSH::Telnet.new(
|
|
117
|
+
# "Dump_log" => "/dev/stdout",
|
|
118
|
+
# "Factory" => TCPSocket,
|
|
119
|
+
# "Host" => "127.0.0.1",
|
|
120
|
+
# "Username" => "test123",
|
|
121
|
+
# "Password" => "pass456"
|
|
122
|
+
# )
|
|
123
|
+
# puts "Logged in"
|
|
124
|
+
# puts s.cmd("echo hello")
|
|
125
|
+
#
|
|
126
|
+
# Example 5 - connection to a SAN device running a customized NetBSD with
|
|
127
|
+
# different ptty defaults, it doesn't convert LF -> CR+LF (see the man
|
|
128
|
+
# page for 'stty') and uses a custom prompt
|
|
129
|
+
#
|
|
130
|
+
# s = Net::SSH::Telnet.new(
|
|
131
|
+
# "Host" => "192.168.1.1",
|
|
132
|
+
# "Username" => "test123",
|
|
133
|
+
# "Password" => "pass456",
|
|
134
|
+
# "Prompt" => %r{^\S+>\s.*$},
|
|
135
|
+
# "Terminator" => "\r"
|
|
136
|
+
# )
|
|
137
|
+
# puts "Logged in"
|
|
138
|
+
# puts s.cmd("show alerts")
|
|
139
|
+
|
|
140
|
+
def initialize(options, &blk) # :yield: mesg
|
|
141
|
+
@options = options
|
|
142
|
+
@options["Host"] = "localhost" unless @options.has_key?("Host")
|
|
143
|
+
@options["Port"] = 22 unless @options.has_key?("Port")
|
|
144
|
+
@options["Prompt"] = /[$%#>] \z/n unless @options.has_key?("Prompt")
|
|
145
|
+
@options["Timeout"] = 10 unless @options.has_key?("Timeout")
|
|
146
|
+
@options["Waittime"] = 0 unless @options.has_key?("Waittime")
|
|
147
|
+
@options["Terminator"] = LF unless @options.has_key?("Terminator")
|
|
148
|
+
|
|
149
|
+
unless @options.has_key?("Binmode")
|
|
150
|
+
@options["Binmode"] = false
|
|
151
|
+
else
|
|
152
|
+
unless (true == @options["Binmode"] or false == @options["Binmode"])
|
|
153
|
+
raise ArgumentError, "Binmode option must be true or false"
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
if @options.has_key?("Output_log")
|
|
158
|
+
@log = File.open(@options["Output_log"], 'a+')
|
|
159
|
+
@log.sync = true
|
|
160
|
+
@log.binmode
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
if @options.has_key?("Dump_log")
|
|
164
|
+
@dumplog = File.open(@options["Dump_log"], 'a+')
|
|
165
|
+
@dumplog.sync = true
|
|
166
|
+
@dumplog.binmode
|
|
167
|
+
def @dumplog.log_dump(dir, x) # :nodoc:
|
|
168
|
+
len = x.length
|
|
169
|
+
addr = 0
|
|
170
|
+
offset = 0
|
|
171
|
+
while 0 < len
|
|
172
|
+
if len < 16
|
|
173
|
+
line = x[offset, len]
|
|
174
|
+
else
|
|
175
|
+
line = x[offset, 16]
|
|
176
|
+
end
|
|
177
|
+
hexvals = line.unpack('H*')[0]
|
|
178
|
+
hexvals += ' ' * (32 - hexvals.length)
|
|
179
|
+
hexvals = format("%s %s %s %s " * 4, *hexvals.unpack('a2' * 16))
|
|
180
|
+
line = line.gsub(/[\000-\037\177-\377]/n, '.')
|
|
181
|
+
printf "%s 0x%5.5x: %s%s\n", dir, addr, hexvals, line
|
|
182
|
+
addr += 16
|
|
183
|
+
offset += 16
|
|
184
|
+
len -= 16
|
|
185
|
+
end
|
|
186
|
+
print "\n"
|
|
187
|
+
end
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
if @options.has_key?("Session")
|
|
191
|
+
@ssh = @options["Session"]
|
|
192
|
+
@close_all = false
|
|
193
|
+
elsif @options.has_key?("Proxy")
|
|
194
|
+
@ssh = Net::SSH.start(nil, nil,
|
|
195
|
+
:host_name => @options["Host"], # ignored
|
|
196
|
+
:port => @options["Port"], # ignored
|
|
197
|
+
:user => @options["Username"],
|
|
198
|
+
:password => @options["Password"],
|
|
199
|
+
:timeout => @options["Timeout"],
|
|
200
|
+
:proxy => TinyFactory.new(@options["Proxy"])
|
|
201
|
+
)
|
|
202
|
+
@close_all = true
|
|
203
|
+
else
|
|
204
|
+
message = "Trying " + @options["Host"] + "...\n"
|
|
205
|
+
yield(message) if block_given?
|
|
206
|
+
@log.write(message) if @options.has_key?("Output_log")
|
|
207
|
+
@dumplog.log_dump('#', message) if @options.has_key?("Dump_log")
|
|
208
|
+
|
|
209
|
+
begin
|
|
210
|
+
@ssh = Net::SSH.start(nil, nil,
|
|
211
|
+
:host_name => @options["Host"],
|
|
212
|
+
:port => @options["Port"],
|
|
213
|
+
:user => @options["Username"],
|
|
214
|
+
:password => @options["Password"],
|
|
215
|
+
:timeout => @options["Timeout"],
|
|
216
|
+
:proxy => @options["Factory"]
|
|
217
|
+
)
|
|
218
|
+
@close_all = true
|
|
219
|
+
rescue TimeoutError
|
|
220
|
+
raise TimeoutError, "timed out while opening a connection to the host"
|
|
221
|
+
rescue
|
|
222
|
+
@log.write($ERROR_INFO.to_s + "\n") if @options.has_key?("Output_log")
|
|
223
|
+
@dumplog.log_dump('#', $ERROR_INFO.to_s + "\n") if @options.has_key?("Dump_log")
|
|
224
|
+
raise
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
message = "Connected to " + @options["Host"] + ".\n"
|
|
228
|
+
yield(message) if block_given?
|
|
229
|
+
@log.write(message) if @options.has_key?("Output_log")
|
|
230
|
+
@dumplog.log_dump('#', message) if @options.has_key?("Dump_log")
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
@buf = ""
|
|
234
|
+
@eof = false
|
|
235
|
+
@channel = nil
|
|
236
|
+
@ssh.open_channel do |channel|
|
|
237
|
+
channel.request_pty { |ch,success|
|
|
238
|
+
if success == false
|
|
239
|
+
raise "Failed to open ssh pty"
|
|
240
|
+
end
|
|
241
|
+
}
|
|
242
|
+
channel.send_channel_request("shell") { |ch, success|
|
|
243
|
+
if success
|
|
244
|
+
@channel = ch
|
|
245
|
+
waitfor(@options['Prompt'], &blk)
|
|
246
|
+
return
|
|
247
|
+
else
|
|
248
|
+
raise "Failed to open ssh shell"
|
|
249
|
+
end
|
|
250
|
+
}
|
|
251
|
+
channel.on_data { |ch,data| @buf << data }
|
|
252
|
+
channel.on_extended_data { |ch,type,data| @buf << data if type == 1 }
|
|
253
|
+
channel.on_close { @eof = true }
|
|
254
|
+
end
|
|
255
|
+
@ssh.loop
|
|
256
|
+
end # initialize
|
|
257
|
+
|
|
258
|
+
# Close the ssh channel, and also the entire ssh session if we
|
|
259
|
+
# opened it.
|
|
260
|
+
|
|
261
|
+
def close
|
|
262
|
+
@channel.close if @channel
|
|
263
|
+
@channel = nil
|
|
264
|
+
@ssh.close if @close_all and @ssh
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
# The ssh session and channel we are using.
|
|
268
|
+
attr_reader :ssh, :channel
|
|
269
|
+
|
|
270
|
+
# Turn newline conversion on (+mode+ == false) or off (+mode+ == true),
|
|
271
|
+
# or return the current value (+mode+ is not specified).
|
|
272
|
+
def binmode(mode = nil)
|
|
273
|
+
case mode
|
|
274
|
+
when nil
|
|
275
|
+
@options["Binmode"]
|
|
276
|
+
when true, false
|
|
277
|
+
@options["Binmode"] = mode
|
|
278
|
+
else
|
|
279
|
+
raise ArgumentError, "argument must be true or false"
|
|
280
|
+
end
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
# Turn newline conversion on (false) or off (true).
|
|
284
|
+
def binmode=(mode)
|
|
285
|
+
if (true == mode or false == mode)
|
|
286
|
+
@options["Binmode"] = mode
|
|
287
|
+
else
|
|
288
|
+
raise ArgumentError, "argument must be true or false"
|
|
289
|
+
end
|
|
290
|
+
end
|
|
291
|
+
|
|
292
|
+
# Read data from the host until a certain sequence is matched.
|
|
293
|
+
|
|
294
|
+
def waitfor(options) # :yield: recvdata
|
|
295
|
+
time_out = @options["Timeout"]
|
|
296
|
+
waittime = @options["Waittime"]
|
|
297
|
+
fail_eof = @options["FailEOF"]
|
|
298
|
+
|
|
299
|
+
if options.kind_of?(Hash)
|
|
300
|
+
prompt = if options.has_key?("Match")
|
|
301
|
+
options["Match"]
|
|
302
|
+
elsif options.has_key?("Prompt")
|
|
303
|
+
options["Prompt"]
|
|
304
|
+
elsif options.has_key?("String")
|
|
305
|
+
Regexp.new( Regexp.quote(options["String"]) )
|
|
306
|
+
end
|
|
307
|
+
time_out = options["Timeout"] if options.has_key?("Timeout")
|
|
308
|
+
waittime = options["Waittime"] if options.has_key?("Waittime")
|
|
309
|
+
fail_eof = options["FailEOF"] if options.has_key?("FailEOF")
|
|
310
|
+
else
|
|
311
|
+
prompt = options
|
|
312
|
+
end
|
|
313
|
+
|
|
314
|
+
if time_out == false
|
|
315
|
+
time_out = nil
|
|
316
|
+
end
|
|
317
|
+
line = ''
|
|
318
|
+
buf = ''
|
|
319
|
+
rest = ''
|
|
320
|
+
sock = @ssh.transport.socket
|
|
321
|
+
init_time = Time.now.tv_sec
|
|
322
|
+
until prompt === line and ((@eof and @buf == "") or not IO::select([sock], nil, nil, waittime))
|
|
323
|
+
while @buf == "" and !@eof
|
|
324
|
+
# timeout is covered by net-ssh
|
|
325
|
+
@channel.connection.process(0.1)
|
|
326
|
+
# if waittime.to_i > 0
|
|
327
|
+
# exec_time = Time.now.tv_sec - init_time
|
|
328
|
+
# raise Timeout::Error if exec_time > waittime
|
|
329
|
+
# end
|
|
330
|
+
end
|
|
331
|
+
if @buf != ""
|
|
332
|
+
# if waittime.to_i > 0
|
|
333
|
+
# exec_time = Time.now.tv_sec - init_time
|
|
334
|
+
# raise Timeout::Error if exec_time > waittime
|
|
335
|
+
# end
|
|
336
|
+
|
|
337
|
+
c = @buf; @buf = ""
|
|
338
|
+
@dumplog.log_dump('<', c) if @options.has_key?("Dump_log")
|
|
339
|
+
buf = c
|
|
340
|
+
buf.gsub!(/#{EOL}/no, "\n") unless @options["Binmode"]
|
|
341
|
+
rest = ''
|
|
342
|
+
@log.print(buf) if @options.has_key?("Output_log")
|
|
343
|
+
line += buf
|
|
344
|
+
yield buf if block_given?
|
|
345
|
+
elsif @eof # End of file reached
|
|
346
|
+
break if prompt === line
|
|
347
|
+
raise EOFError if fail_eof
|
|
348
|
+
if line == ''
|
|
349
|
+
line = nil
|
|
350
|
+
yield nil if block_given?
|
|
351
|
+
end
|
|
352
|
+
break
|
|
353
|
+
end
|
|
354
|
+
end
|
|
355
|
+
line
|
|
356
|
+
end
|
|
357
|
+
|
|
358
|
+
# Write +string+ to the host.
|
|
359
|
+
#
|
|
360
|
+
# Does not perform any conversions on +string+. Will log +string+ to the
|
|
361
|
+
# dumplog, if the Dump_log option is set.
|
|
362
|
+
def write(string)
|
|
363
|
+
@dumplog.log_dump('>', string) if @options.has_key?("Dump_log")
|
|
364
|
+
@channel.send_data string
|
|
365
|
+
end
|
|
366
|
+
|
|
367
|
+
# Sends a string to the host.
|
|
368
|
+
#
|
|
369
|
+
# This does _not_ automatically append a newline to the string. Embedded
|
|
370
|
+
# newlines may be converted depending upon the values of binmode or
|
|
371
|
+
# terminator.
|
|
372
|
+
def print(string)
|
|
373
|
+
terminator = @options["Terminator"]
|
|
374
|
+
|
|
375
|
+
if @options["Binmode"]
|
|
376
|
+
self.write(string)
|
|
377
|
+
else
|
|
378
|
+
self.write(string.gsub(/\n/n, terminator))
|
|
379
|
+
end
|
|
380
|
+
end
|
|
381
|
+
|
|
382
|
+
# Sends a string to the host.
|
|
383
|
+
#
|
|
384
|
+
# Same as #print(), but appends a newline to the string.
|
|
385
|
+
def puts(string)
|
|
386
|
+
self.print(string + "\n")
|
|
387
|
+
end
|
|
388
|
+
|
|
389
|
+
# Send a command to the host.
|
|
390
|
+
#
|
|
391
|
+
# More exactly, sends a string to the host, and reads in all received
|
|
392
|
+
# data until is sees the prompt or other matched sequence.
|
|
393
|
+
#
|
|
394
|
+
# The command or other string will have the newline sequence appended
|
|
395
|
+
# to it.
|
|
396
|
+
|
|
397
|
+
def cmd(options) # :yield: recvdata
|
|
398
|
+
match = @options["Prompt"]
|
|
399
|
+
time_out = @options["Timeout"]
|
|
400
|
+
|
|
401
|
+
if options.kind_of?(Hash)
|
|
402
|
+
string = options["String"]
|
|
403
|
+
match = options["Match"] if options.has_key?("Match")
|
|
404
|
+
time_out = options["Timeout"] if options.has_key?("Timeout")
|
|
405
|
+
waittime = options["Waittime"] if options.has_key?("Waittime")
|
|
406
|
+
else
|
|
407
|
+
string = options
|
|
408
|
+
end
|
|
409
|
+
|
|
410
|
+
self.puts(string)
|
|
411
|
+
if block_given?
|
|
412
|
+
waitfor({"Prompt" => match, "Timeout" => time_out, "Waittime" => waittime}){|c| yield c }
|
|
413
|
+
else
|
|
414
|
+
waitfor({"Prompt" => match, "Timeout" => time_out, "Waittime" => waittime})
|
|
415
|
+
end
|
|
416
|
+
end
|
|
417
|
+
|
|
418
|
+
end # class Telnet
|
|
419
|
+
end # module SSH
|
|
420
|
+
end # module Net
|