#!/usr/bin/env python # # Copyright 2016 Google Inc. # # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. import default_flavor import os import posixpath import subprocess import ssh_devices """Utils for running tests remotely over SSH.""" class SSHFlavorUtils(default_flavor.DefaultFlavorUtils): def __init__(self, *args, **kwargs): super(SSHFlavorUtils, self).__init__(*args, **kwargs) slave_info = ssh_devices.SLAVE_INFO.get(self._bot_info.slave_name, ssh_devices.SLAVE_INFO['default']) self._host = slave_info.ssh_host self._port = slave_info.ssh_port self._user = slave_info.ssh_user @property def host(self): return self._host @property def port(self): return self._port @property def user(self): return self._user def ssh(self, cmd, **kwargs): """Run the given SSH command.""" ssh_cmd = ['ssh'] if self.port: ssh_cmd.extend(['-p', self.port]) dest = self.host if self.user: dest = self.user + '@' + dest ssh_cmd.append(dest) ssh_cmd.extend(cmd) return self._bot_info.run(ssh_cmd, **kwargs) def step(self, *args, **kwargs): """Run the given step over SSH.""" self.ssh(*args, **kwargs) def device_path_join(self, *args): """Like os.path.join(), but for paths on a remote machine.""" return posixpath.join(*args) def device_path_exists(self, path): # pragma: no cover """Like os.path.exists(), but for paths on a remote device.""" try: self.ssh(['test', '-e', path]) return True except subprocess.CalledProcessError: return False def _remove_device_dir(self, path): """Remove the directory on the device.""" self.ssh(['rm', '-rf', path]) def _create_device_dir(self, path): """Create the directory on the device.""" self.ssh(['mkdir', '-p', path]) def create_clean_device_dir(self, path): """Like shutil.rmtree() + os.makedirs(), but on a remote device.""" self._remove_device_dir(path) self._create_device_dir(path) def _make_scp_cmd(self, remote_path, recurse=True): """Prepare an SCP command. Returns a partial SCP command and an adjusted remote path. """ cmd = ['scp'] if recurse: cmd.append('-r') if self.port: cmd.extend(['-P', self.port]) adj_remote_path = self.host + ':' + remote_path if self.user: adj_remote_path = self.user + '@' + adj_remote_path return cmd, adj_remote_path def copy_directory_contents_to_device(self, host_dir, device_dir): """Like shutil.copytree(), but for copying to a remote device.""" _, remote_path = self._make_scp_cmd(device_dir) cmd = [os.path.join(self._bot_info.skia_dir, 'tools', 'scp_dir_contents.sh'), host_dir, remote_path] self._bot_info.run(cmd) def copy_directory_contents_to_host(self, device_dir, host_dir): """Like shutil.copytree(), but for copying from a remote device.""" _, remote_path = self._make_scp_cmd(device_dir) cmd = [os.path.join(self._bot_info.skia_dir, 'tools', 'scp_dir_contents.sh'), remote_path, host_dir] self._bot_info.run(cmd) def copy_file_to_device(self, host_path, device_path): """Like shutil.copyfile, but for copying to a connected device.""" cmd, remote_path = self._make_scp_cmd(device_path, recurse=False) cmd.extend([host_path, remote_path]) self._bot_info.run(cmd) def read_file_on_device(self, path): return self.ssh(['cat', path]).rstrip() def remove_file_on_device(self, path): """Delete the given file.""" return self.ssh(['rm', '-f', path])