Revision: 85
Author: janne.t.harkonen
Date: Tue Nov 2 06:49:31 2010
Log: Documentation improvements, issue 23, whitespace cleanup
http://code.google.com/p/robotframework-sshlibrary/source/detail?r=85
Modified:
/trunk/src/SSHLibrary/__init__.py
=======================================
--- /trunk/src/SSHLibrary/__init__.py Mon Nov 30 23:42:23 2009
+++ /trunk/src/SSHLibrary/__init__.py Tue Nov 2 06:49:31 2010
@@ -27,39 +27,39 @@
from javaclient import SSHClient
else:
from pythonclient import SSHClient
-
+
__version__ = 'trunk'
-
+
class SSHLibrary:
- """SSH Library is a test library for Robot Framework that enables
+ """SSH Library is a test library for Robot Framework that enables
executing commands and transferring files over an SSH connection.
-
+
SSHLibrary works with both Python and Jython interpreters. To use
SSHLibrary with Python, you must first install paramiko SSH
implementation[1] and its dependencies. To use SSHLibrary with
Jython, you
must have jar distribution of Trilead SSH implementation[2] in the
CLASSPATH during test execution
-
+
[1] http://www.lag.net/paramiko/
[2] http://www.trilead.com/Products/Trilead_SSH_for_Java/
-
+
Currently, there are two modes of operation:
-
- 1. When keyword `Execute Command` or `Start Command` is used to execute
+
+ 1. When keyword `Execute Command` or `Start Command` is used to execute
something, a new channel is opened over the SSH connection. In
practice it
- means that no session information is stored.
-
+ means that no session information is stored.
+
2. Keywords `Write` and `Read XXX` operate in an interactive shell,
which
means that changes to state are visible to next keywords. Note that in
interactive mode, a prompt must be set before using any of the
Write-keywords. Prompt can be set either on `library importing` or
when a new connection is opened using `Open Connection`, or using
keyword
`Set Prompt`.
-
+
Both modes require that a connection is opened with `Open Connection`.
"""
-
+
ROBOT_LIBRARY_SCOPE = 'GLOBAL'
ROBOT_LIBRARY_VERSION = __version__
@@ -78,11 +78,11 @@
self.set_timeout(timeout or 3)
self._default_log_level = 'INFO'
self._prompt = prompt
-
- def open_connection(self, host, alias=None, port=22, timeout=None,
+
+ def open_connection(self, host, alias=None, port=22, timeout=None,
newline=None, prompt=None):
"""Opens a new SSH connection to given `host` and `port`.
-
+
Possible already opened connections are cached.
Returns the index of this connection which can be used later to
switch
@@ -92,7 +92,7 @@
Optional `alias` is a name for the connection and it can be used
for
switching between connections similarly as the index. See `Switch
Connection` for more details about that.
-
+
Default values for `timeout`, `newline` and `prompt` can be given
on
`library importing`. See also `Set Timeout`, `Set Newline` and
`Set Prompt` for more information.
@@ -116,9 +116,9 @@
Index is got from `Open Connection` and alias can be given to it.
- Returns the index of previous connection, which can be used to
restore
+ Returns the index of previous connection, which can be used to
restore
the connection later. This works with Robot Framework 2.0.2 or
newer.
-
+
Example:
| Open Connection | myhost.net | |
@@ -159,13 +159,13 @@
except AttributeError:
pass
self._client = self._cache.current
-
+
def enable_ssh_logging(self, logfile):
"""Enables logging of SSH protocol output to given `logfile`
-
+
`logfile` can be relative or absolute path to a file that is
writable by
current user. In case that it already exists, it will be
overwritten.
-
+
Note that this keyword only works with Python, e.g. when executing
the
tests with `pybot`
"""
@@ -198,7 +198,7 @@
`password` is used to unlock `keyfile` if unlocking is required.
"""
self._verify_key_file(keyfile)
- self._info("Logging into '%s:%s' as '%s'."
+ self._info("Logging into '%s:%s' as '%s'."
% (self._host, self._port, username))
try:
self._client.login_with_public_key(username, keyfile, password)
@@ -216,10 +216,15 @@
def execute_command(self, command, ret_mode='stdout'):
"""Executes command on remote host over existing SSH connection
and returns stdout and/or stderr.
-
- This keyword waits until the command is completed. If non-blocking
+
+ This keyword waits until the command is completed. If non-blocking
behavior is required, use `Start Command` instead.
-
+
+ Multiple calls of `Execute Command` use separate SSH sessions.
Thus,
+ possible changes to the environment are not shared between these
calls.
+ `Write` and `Read XXX` keywords can be used for running multiple
commands
+ on same session.
+
Examples:
| ${out}= | Execute Command | some command | |
#stdout is returned | |
| ${err}= | Execute Command | some command | stderr |
#stderr is returned | |
@@ -230,13 +235,13 @@
def start_command(self, command):
"""Starts command execution on remote host over existing SSH
connection.
-
- This keyword doesn't return anything. Use `Read Command Output` to
read
+
+ This keyword doesn't return anything. Use `Read Command Output` to
read
the output generated from command execution.
-
- Note that the `Read Command Output` keyword always reads the
output of
+
+ Note that the `Read Command Output` keyword always reads the
output of
the most recently started command.
-
+
Example:
| Start Command | some command |
"""
@@ -245,40 +250,43 @@
self._client.start_command(command)
def read_command_output(self, ret_mode='stdout'):
- """Reads and returns/logs everything currently available on the
output (stdout and/or stderr).
+ """Reads and returns/logs output (stdout and/or stderr) of a
command.
+
+ Command must have been started using `Start Command` before this
keyword can be used.
Examples:
+ | Start Command | some command |
| ${out}= | Read Command Output |
| | |
| ${err}= | Read Command Output | stderr | #stderr is
returned | |
| ${out} | ${err}= | Read Command Output |
both | #stdout and stderr are returned |
"""
self._info("Reading output of command '%s'" % self._command)
return
self._process_output(self._client.read_command_output(ret_mode))
-
+
def _process_output(self, output):
if isinstance(output, tuple):
return [self._strip_possible_newline(out) for out in output]
return self._strip_possible_newline(output)
-
+
def _strip_possible_newline(self, output):
if output.endswith('\n'):
return output[:-1]
return output
-
+
def set_timeout(self, timeout):
"""Sets the timeout used in read operations to given value.
- `timeout` is given in Robot Framework's time format
+ `timeout` is given in Robot Framework's time format
(e.g. 1 minute 20 seconds).
- The read operations of keywords `Read Until`, `Read Until Prompt`,
- `Read Until Regexp` and `Write Until Expected Output` will try to
read
+ The read operations of keywords `Read Until`, `Read Until Prompt`,
+ `Read Until Regexp` and `Write Until Expected Output` will try to
read
the output until either the expected text appears in the output or
the
- timeout expires. For commands that take long time to produce their
- output, this timeout must be set properly.
-
+ timeout expires. For commands that take long time to produce their
+ output, this timeout must be set properly.
+
Old timeout is returned and it can be used to restore it later.
-
+
Example:
| ${tout} = | Set Timeout | 2 minute 30 seconds |
| Do Something |
@@ -287,37 +295,37 @@
old = hasattr(self, '_timeout') and self._timeout or None
self._timeout = utils.timestr_to_secs(timeout)
return old is not None and utils.secs_to_timestr(old) or None
-
+
def set_newline(self, newline):
"""Sets the newline used by `Write` keyword.
-
+
Old newline is returned and it can be used to restore it later.
- See `Set Timeout` for an example.
+ See `Set Timeout` for an example.
"""
old = self._newline
self._newline = self._parse_newline(newline)
return old
-
+
def _parse_newline(self, newline):
return newline.upper().replace('LF','\n').replace('CR','\r')
def set_prompt(self, prompt):
"""Sets the prompt used by `Read Until Prompt` keyword.
-
+
Old prompt is returned and it can be used to restore it later.
-
+
Example:
- | ${prompt} | Set Prompt | $ |
+ | ${prompt} | Set Prompt | $ |
| Do Something |
- | Set Prompt | ${prompt} |
+ | Set Prompt | ${prompt} |
"""
old = hasattr(self, '_prompt') and self._prompt or ''
self._prompt = prompt
return old
-
+
def set_default_log_level(self, level):
"""Sets the default log level used by all read keywords.
-
+
The possible values are TRACE, DEBUG, INFO and WARN. The default is
INFO. The old value is returned and can be used to restore it
later,
similarly as with `Set Timeout`.
@@ -326,15 +334,15 @@
old = self._default_log_level
self._default_log_level = level.upper()
return old
-
+
def write(self, text, loglevel=None):
"""Writes given text over the connection and appends newline.
-
- Consumes the written text (until the appended newline) from output
+
+ Consumes the written text (until the appended newline) from output
and returns it. Given text must not contain newlines.
-
- Note: This keyword does not return the possible output of the
executed
- command. To get the output, one of the `Read XXX` keywords must be
+
+ Note: This keyword does not return the possible output of the
executed
+ command. To get the output, one of the `Read XXX` keywords must be
used.
"""
self.write_bare(text + self._newline)
@@ -346,7 +354,7 @@
try:
text = str(text)
except UnicodeError:
- raise ValueError('Only ascii characters are allowed in SSH.'
+ raise ValueError('Only ascii characters are allowed in SSH.'
'Got: %s' % text)
if self._prompt is None:
msg = ("Using 'Write' or 'Write Bare' keyword requires
setting "
@@ -362,10 +370,13 @@
def read(self, loglevel=None):
"""Reads and returns/logs everything currently available on the
output.
- Read message is always returned and logged. Default log level is
+ Read message is always returned and logged. Default log level is
either 'INFO', or the level set with `Set Default Log Level`.
`loglevel` can be used to override the default log level, and
available
levels are TRACE, DEBUG, INFO and WARN.
+
+ This keyword is most useful for reading everything from the output
buffer,
+ thus clearing it.
"""
if self._client.shell is None:
self._client.open_shell()
@@ -378,14 +389,14 @@
Text up until and including the match will be returned, If no
match is
found, the keyword fails.
-
+
The timeout is by default three seconds but can be changed either
on
`library importing` or by using `Set Timeout` keyword.
-
+
See `Read` for more information on `loglevel`.
"""
return self._read_until(expected, loglevel)
-
+
def _read_until(self, expected, loglevel):
if self._client.shell is None:
self._client.open_shell()
@@ -400,19 +411,19 @@
self._log(ret, loglevel)
if not isinstance(expected, basestring):
expected = expected.pattern
- raise AssertionError("No match found for '%s' in %s"
+ raise AssertionError("No match found for '%s' in %s"
% (expected,
utils.secs_to_timestr(self._timeout)))
def read_until_regexp(self, regexp, loglevel=None):
"""Reads from the current output until a match to `regexp` is
found or timeout expires.
`regexp` can be a pattern or a compiled re-object.
-
+
Returns text up until and including the regexp.
The timeout is by default three seconds but can be changed either
on
`library importing` or by using `Set Timeout` keyword.
-
+
See `Read` for more information on `loglevel`.
Examples:
| Read Until Regexp | (#|$) |
@@ -424,35 +435,39 @@
def read_until_prompt(self, loglevel=None):
"""Reads and returns text from the current output until prompt is
found.
-
- Prompt must have been set, either in `library importing` or by
using
+
+ Prompt must have been set, either in `library importing` or by
using
`Set Prompt` -keyword.
-
+
See `Read` for more information on `loglevel`.
+
+ This keyword is most useful for reading output of a single
command, when
+ it is known that the output buffer is clear before the command
starts and
+ that the command does not produce prompt characters in its output.
"""
if not self._prompt:
raise RuntimeError('Prompt is not set')
return self.read_until(self._prompt, loglevel)
- def write_until_expected_output(self, text, expected, timeout,
+ def write_until_expected_output(self, text, expected, timeout,
retry_interval, loglevel=None):
"""Writes given text repeatedly until `expected` appears in output.
-
+
`text` is written without appending newline. `retry_interval`
defines
the time that is waited before writing `text` again. `text` will be
- consumed from the output before `expected` is tried to be read.
-
+ consumed from the output before `expected` is tried to be read.
+
If `expected` does not appear on output within `timeout`, this
keyword
- fails.
-
+ fails.
+
See `Read` for more information on `loglevel`.
-
+
Example:
| Write Until Expected Output | ps -ef| grep myprocess\\n |
myprocess |
| ... | 5s | 0.5s |
-
- This will write the 'ps -ef | grep myprocess\\n' until 'myprocess'
- appears on the output. The command is written every 0.5 seconds and
+
+ This will write the 'ps -ef | grep myprocess\\n' until 'myprocess'
+ appears on the output. The command is written every 0.5 seconds and
the keyword will fail if 'myprocess' does not appear on the output
in
5 seconds.
"""
@@ -469,9 +484,9 @@
except AssertionError:
pass
self.set_timeout(old_timeout)
- raise AssertionError("No match found for '%s' in %s"
+ raise AssertionError("No match found for '%s' in %s"
% (expected, utils.secs_to_timestr(timeout)))
-
+
def put_file(self, source, destination='.', mode='0744'):
"""Copies file(s) from local host to remote host using existing
SSH connection.
@@ -479,28 +494,28 @@
over it.
2. If the destination is an existing directory, the src file is
copied into it. Possible file with same name is overwritten.
- 3. If the destination does not exist and it ends with path
separator ('/'),
- it is considered a directory. That directory is created and src
file
+ 3. If the destination does not exist and it ends with path
separator ('/'),
+ it is considered a directory. That directory is created and src
file
copied into it. Possibly missing intermediate directories are also
created.
4. If the destination does not exist and it does not end with path
separator, it is considered a file. If the path to the file does
not
exist it is created.
5. By default, destination is empty and the user's home directory
in the
remote machine is used as destination.
-
+
Using wild cards like '*' and '?' are allowed.
When wild cards are used, destination MUST be a directory and only
files
- are copied from the src, sub directories are ignored. If the
contents
+ are copied from the src, sub directories are ignored. If the
contents
of sub directories are also needed, use the keyword again.
Default file permission is 0744 (-rwxr--r--) and can be changed by
giving a value to the optional `mode` parameter.
-
+
Examples:
| Put File | /path_to_local_file/local_file.txt |
/path_to_remote_file/remote_file.txt | # single file
| |
| Put File | /path_to_local_files/*.txt |
/path_to_remote_files/ | # multiple files with wild cards
| |
| Put File | /path_to_local_files/*.txt |
/path_to_remote_files/ | 0777 | #
file permissions |
-
+
"""
mode = int(mode,8)
self._client.create_sftp_client()
@@ -512,25 +527,25 @@
self._info("Putting '%s' to '%s'" % (src, dst))
self._client.put_file(src, dst, mode)
self._client.close_sftp_client()
-
+
def _get_put_file_sources(self, source):
sources = [f for f in glob.glob(source.replace('/', os.sep)) if
os.path.isfile(f)]
if not sources:
- raise AssertionError("There were no source files
matching '%s'" % source)
+ raise AssertionError("There were no source files
matching '%s'" % source)
return sources
-
+
def _get_put_file_destinations(self, sources, dest):
dest = dest.split(':')[-1].replace('\\', '/')
if dest == '.':
dest = self._client.homedir + '/'
if len(sources) > 1 and dest[-1] != '/':
- raise ValueError('It is not possible to copy multiple source
files '
+ raise ValueError('It is not possible to copy multiple source
files '
'to one destination file.')
dirpath, filename = self._parse_path_elements(dest)
if filename:
return [ posixpath.join(dirpath, filename) ], dirpath
return [ posixpath.join(dirpath, os.path.split(path)[1]) for path
in sources ], dirpath
-
+
def _parse_path_elements(self, dest):
if not posixpath.isabs(dest):
dest = posixpath.join(self._client.homedir, dest)
@@ -543,27 +558,27 @@
over it.
2. If the destination is an existing directory, the source file is
copied into it. Possible file with same name is overwritten.
- 3. If the destination does not exist and it ends with path
separator
- ('/' in unixes, '\\' in Windows), it is considered a directory.
That
+ 3. If the destination does not exist and it ends with path
separator
+ ('/' in unixes, '\\' in Windows), it is considered a directory.
That
directory is created and source file copied into it. Possible
missing
intermediate directories are also created.
4. If the destination does not exist and it does not end with path
separator, it is considered a file. If the path to the file does
not
exist it is created.
5. By default, destination is empty, and in that case the current
working
- directory in the local machine is used as destination. This will
most
+ directory in the local machine is used as destination. This will
most
probably be the directory where test execution was started.
Using wild cards like '*' and '?' are allowed.
When wild cards are used, destination MUST be a directory and only
files
- are copied from the source, sub directories are ignored. If the
contents
+ are copied from the source, sub directories are ignored. If the
contents
of sub directories are also needed, use the keyword again.
-
+
Examples:
| Get File | /path_to_remote_file/remote_file.txt |
/path_to_local_file/local_file.txt | # single file |
| Get File | /path_to_remote_files/*.txt |
/path_to_local_files/ | # multiple files with wild cards |
-
+
"""
self._client.create_sftp_client()
remotefiles = self._get_get_file_sources(source)
@@ -573,7 +588,7 @@
self._info('Getting %s to %s' % (src, dst))
self._client.get_file(src, dst)
self._client.close_sftp_client()
-
+
def _get_get_file_sources(self, source):
path, pattern = posixpath.split(source)
if not path:
@@ -587,21 +602,21 @@
if not sourcefiles:
raise AssertionError("There were no source files
matching '%s'" % source)
return sourcefiles
-
+
def _get_get_file_destinations(self, sourcefiles, dest):
if dest == '.':
dest += os.sep
is_dir = dest.endswith(os.sep)
if not is_dir and len(sourcefiles) > 1:
- raise ValueError('It is not possible to copy multiple source
files '
+ raise ValueError('It is not possible to copy multiple source
files '
'to one destination file.')
- dest = os.path.abspath(dest.replace('/', os.sep))
+ dest = os.path.abspath(dest.replace('/', os.sep))
self._create_missing_local_dirs(dest, is_dir)
if is_dir:
return [ os.path.join(dest, os.path.split(name)[1]) for name
in sourcefiles ]
else:
return [ dest ]
-
+
def _create_missing_local_dirs(self, dest, is_dir):
if not is_dir:
dest = os.path.dirname(dest)
@@ -611,10 +626,10 @@
def _info(self, msg):
self._log(msg, 'INFO')
-
+
def _debug(self, msg):
self._log(msg, 'DEBUG')
-
+
def _log(self, msg, level=None):
self._is_valid_log_level(level, raise_if_invalid=True)
msg = msg.strip()
@@ -632,4 +647,4 @@
if not raise_if_invalid:
return False
raise AssertionError("Invalid log level '%s'" % level)
-
+