blob: 9f0274c81adade2750d139195c8f56d3e4211298 [file] [log] [blame]
Simran Basi833814b2013-01-29 13:13:43 -08001# Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
Kuo-Hsin Yang4a006172018-04-25 14:44:55 +08005import json
Simran Basi833814b2013-01-29 13:13:43 -08006import logging
7import os
Simran Basi833814b2013-01-29 13:13:43 -08008import StringIO
9
Simran Basi833814b2013-01-29 13:13:43 -080010from autotest_lib.client.common_lib import error, utils
11from autotest_lib.client.common_lib.cros import dev_server
12
13
Dave Tu6a404e62013-11-05 15:54:48 -080014TELEMETRY_RUN_BENCHMARKS_SCRIPT = 'tools/perf/run_benchmark'
Ilja H. Friedel086bc3f2014-02-27 22:17:55 -080015TELEMETRY_RUN_TESTS_SCRIPT = 'tools/telemetry/run_tests'
Gurchetan Singhfaf75e92017-04-17 18:09:44 -070016TELEMETRY_RUN_GPU_TESTS_SCRIPT = 'content/test/gpu/run_gpu_integration_test.py'
Mao Huangc9642d72017-09-28 16:50:02 +080017TELEMETRY_TIMEOUT_MINS = 150
Simran Basi833814b2013-01-29 13:13:43 -080018
Ting-Yuan Huange5b19132016-03-22 13:02:41 +080019DUT_CHROME_ROOT = '/usr/local/telemetry/src'
20
Simran Basi833814b2013-01-29 13:13:43 -080021# Result Statuses
22SUCCESS_STATUS = 'SUCCESS'
23WARNING_STATUS = 'WARNING'
24FAILED_STATUS = 'FAILED'
25
Kuo-Hsin Yang07da7b62018-08-08 16:56:06 +080026# A list of telemetry tests that cannot run on dut.
Kuo-Hsin Yange0915472018-09-10 10:36:16 +080027ON_DUT_BLACKLIST = [
28 'loading.desktop', # crbug/882299
29 'rendering.desktop', # crbug/882291
30 'system_health.memory_desktop', # crbug/874386
31]
Simran Basi833814b2013-01-29 13:13:43 -080032
33class TelemetryResult(object):
34 """Class to represent the results of a telemetry run.
35
36 This class represents the results of a telemetry run, whether it ran
37 successful, failed or had warnings.
38 """
39
40
41 def __init__(self, exit_code=0, stdout='', stderr=''):
42 """Initializes this TelemetryResultObject instance.
43
44 @param status: Status of the telemtry run.
45 @param stdout: Stdout of the telemetry run.
46 @param stderr: Stderr of the telemetry run.
47 """
48 if exit_code == 0:
49 self.status = SUCCESS_STATUS
50 else:
51 self.status = FAILED_STATUS
52
Simran Basi833814b2013-01-29 13:13:43 -080053 self._stdout = stdout
54 self._stderr = stderr
55 self.output = '\n'.join([stdout, stderr])
56
57
Simran Basi833814b2013-01-29 13:13:43 -080058class TelemetryRunner(object):
59 """Class responsible for telemetry for a given build.
60
61 This class will extract and install telemetry on the devserver and is
62 responsible for executing the telemetry benchmarks and returning their
63 output to the caller.
64 """
65
Ting-Yuan Huang85dcde82016-04-08 17:41:32 +080066 def __init__(self, host, local=False, telemetry_on_dut=True):
Simran Basi833814b2013-01-29 13:13:43 -080067 """Initializes this telemetry runner instance.
68
69 If telemetry is not installed for this build, it will be.
Luis Lozano23ae3192013-11-08 16:22:46 -080070
Ting-Yuan Huange5b19132016-03-22 13:02:41 +080071 Basically, the following commands on the local pc on which test_that
72 will be executed, depending on the 4 possible combinations of
73 local x telemetry_on_dut:
74
75 local=True, telemetry_on_dut=False:
76 run_benchmark --browser=cros-chrome --remote=[dut] [test]
77
78 local=True, telemetry_on_dut=True:
79 ssh [dut] run_benchmark --browser=system [test]
80
81 local=False, telemetry_on_dut=False:
82 ssh [devserver] run_benchmark --browser=cros-chrome --remote=[dut] [test]
83
84 local=False, telemetry_on_dut=True:
85 ssh [devserver] ssh [dut] run_benchmark --browser=system [test]
86
Luis Lozano23ae3192013-11-08 16:22:46 -080087 @param host: Host where the test will be run.
88 @param local: If set, no devserver will be used, test will be run
89 locally.
Ting-Yuan Huange5b19132016-03-22 13:02:41 +080090 If not set, "ssh [devserver] " will be appended to test
91 commands.
92 @param telemetry_on_dut: If set, telemetry itself (the test harness)
93 will run on dut.
94 It decides browser=[system|cros-chrome]
Simran Basi833814b2013-01-29 13:13:43 -080095 """
96 self._host = host
Ilja H. Friedelc7bf3102014-05-13 17:31:25 -070097 self._devserver = None
98 self._telemetry_path = None
Ting-Yuan Huange5b19132016-03-22 13:02:41 +080099 self._telemetry_on_dut = telemetry_on_dut
Luis Lozano23ae3192013-11-08 16:22:46 -0800100 # TODO (llozano crbug.com/324964). Remove conditional code.
101 # Use a class hierarchy instead.
102 if local:
103 self._setup_local_telemetry()
104 else:
105 self._setup_devserver_telemetry()
106
107 logging.debug('Telemetry Path: %s', self._telemetry_path)
108
109
110 def _setup_devserver_telemetry(self):
111 """Setup Telemetry to use the devserver."""
112 logging.debug('Setting up telemetry for devserver testing')
Simran Basi833814b2013-01-29 13:13:43 -0800113 logging.debug('Grabbing build from AFE.')
Prathmesh Prabhucfff58a2017-02-06 10:07:43 -0800114 info = self._host.host_info_store.get()
115 if not info.build:
Simran Basi833814b2013-01-29 13:13:43 -0800116 logging.error('Unable to locate build label for host: %s.',
Dean Liaoe3e75f62017-11-14 10:36:43 +0800117 self._host.host_port)
Simran Basi833814b2013-01-29 13:13:43 -0800118 raise error.AutotestError('Failed to grab build for host %s.' %
Dean Liaoe3e75f62017-11-14 10:36:43 +0800119 self._host.host_port)
Simran Basi833814b2013-01-29 13:13:43 -0800120
Prathmesh Prabhucfff58a2017-02-06 10:07:43 -0800121 logging.debug('Setting up telemetry for build: %s', info.build)
Simran Basi833814b2013-01-29 13:13:43 -0800122
Prathmesh Prabhucfff58a2017-02-06 10:07:43 -0800123 self._devserver = dev_server.ImageServer.resolve(
124 info.build, hostname=self._host.hostname)
125 self._devserver.stage_artifacts(info.build, ['autotest_packages'])
126 self._telemetry_path = self._devserver.setup_telemetry(build=info.build)
Luis Lozano23ae3192013-11-08 16:22:46 -0800127
128
129 def _setup_local_telemetry(self):
130 """Setup Telemetry to use local path to its sources.
131
132 First look for chrome source root, either externally mounted, or inside
133 the chroot. Prefer chrome-src-internal source tree to chrome-src.
134 """
135 TELEMETRY_DIR = 'src'
136 CHROME_LOCAL_SRC = '/var/cache/chromeos-cache/distfiles/target/'
Josh Triplett05208c92014-07-17 13:21:29 -0700137 CHROME_EXTERNAL_SRC = os.path.expanduser('~/chrome_root/')
Luis Lozano23ae3192013-11-08 16:22:46 -0800138
139 logging.debug('Setting up telemetry for local testing')
140
141 sources_list = ('chrome-src-internal', 'chrome-src')
Josh Triplett05208c92014-07-17 13:21:29 -0700142 dir_list = [CHROME_EXTERNAL_SRC]
Luis Lozano23ae3192013-11-08 16:22:46 -0800143 dir_list.extend(
144 [os.path.join(CHROME_LOCAL_SRC, x) for x in sources_list])
145 if 'CHROME_ROOT' in os.environ:
146 dir_list.insert(0, os.environ['CHROME_ROOT'])
147
148 telemetry_src = ''
149 for dir in dir_list:
150 if os.path.exists(dir):
151 telemetry_src = os.path.join(dir, TELEMETRY_DIR)
152 break
153 else:
154 raise error.TestError('Telemetry source directory not found.')
155
156 self._devserver = None
157 self._telemetry_path = telemetry_src
158
159
Luis Lozano814c7182015-09-08 11:20:47 -0700160 def _get_telemetry_cmd(self, script, test_or_benchmark, *args):
Luis Lozano23ae3192013-11-08 16:22:46 -0800161 """Build command to execute telemetry based on script and benchmark.
162
163 @param script: Telemetry script we want to run. For example:
164 [path_to_telemetry_src]/src/tools/telemetry/run_tests.
165 @param test_or_benchmark: Name of the test or benchmark we want to run,
166 with the page_set (if required) as part of
167 the string.
Luis Lozano814c7182015-09-08 11:20:47 -0700168 @param args: additional list of arguments to pass to the script.
169
Luis Lozano23ae3192013-11-08 16:22:46 -0800170 @returns Full telemetry command to execute the script.
171 """
172 telemetry_cmd = []
173 if self._devserver:
Allen Lia5cfb972016-12-27 17:17:22 -0800174 devserver_hostname = self._devserver.hostname
Luis Lozano23ae3192013-11-08 16:22:46 -0800175 telemetry_cmd.extend(['ssh', devserver_hostname])
176
Ting-Yuan Huange5b19132016-03-22 13:02:41 +0800177 if self._telemetry_on_dut:
178 telemetry_cmd.extend(
Dean Liaoe3e75f62017-11-14 10:36:43 +0800179 [self._host.ssh_command(alive_interval=900,
180 connection_attempts=4),
Ting-Yuan Huange5b19132016-03-22 13:02:41 +0800181 'python',
182 script,
183 '--verbose',
184 '--output-format=chartjson',
185 '--output-dir=%s' % DUT_CHROME_ROOT,
186 '--browser=system'])
187 else:
188 telemetry_cmd.extend(
189 ['python',
190 script,
191 '--verbose',
192 '--browser=cros-chrome',
193 '--output-format=chartjson',
194 '--output-dir=%s' % self._telemetry_path,
Dean Liaoe3e75f62017-11-14 10:36:43 +0800195 '--remote=%s' % self._host.host_port])
Luis Lozano814c7182015-09-08 11:20:47 -0700196 telemetry_cmd.extend(args)
197 telemetry_cmd.append(test_or_benchmark)
198
Keith Haddow1e5c7012016-03-09 16:05:37 -0800199 return ' '.join(telemetry_cmd)
200
201
202 def _scp_telemetry_results_cmd(self, perf_results_dir):
203 """Build command to copy the telemetry results from the devserver.
204
205 @param perf_results_dir: directory path where test output is to be
206 collected.
207 @returns SCP command to copy the results json to the specified directory.
208 """
Dean Liaoe3e75f62017-11-14 10:36:43 +0800209 if not perf_results_dir:
210 return ''
211
212 scp_cmd = ['scp']
213 if self._telemetry_on_dut:
214 scp_cmd.append(self._host.make_ssh_options(alive_interval=900,
215 connection_attempts=4))
216 if not self._host.is_default_port:
217 scp_cmd.append('-P %d' % self._host.port)
218 src = 'root@%s:%s/results-chart.json' % (self._host.hostname,
219 DUT_CHROME_ROOT)
220 else:
221 devserver_hostname = ''
Ricky Liangd186f3e2016-03-15 16:50:55 +0800222 if self._devserver:
Allen Lia5cfb972016-12-27 17:17:22 -0800223 devserver_hostname = self._devserver.hostname + ':'
Dean Liaoe3e75f62017-11-14 10:36:43 +0800224 src = '%s%s/results-chart.json' % (devserver_hostname,
225 self._telemetry_path)
Keith Haddow1e5c7012016-03-09 16:05:37 -0800226
Dean Liaoe3e75f62017-11-14 10:36:43 +0800227 scp_cmd.extend([src, perf_results_dir])
Keith Haddow1e5c7012016-03-09 16:05:37 -0800228 return ' '.join(scp_cmd)
229
230
231 def _run_cmd(self, cmd):
232 """Execute an command in a external shell and capture the output.
233
234 @param cmd: String of is a valid shell command.
235
236 @returns The standard out, standard error and the integer exit code of
237 the executed command.
238 """
239 logging.debug('Running: %s', cmd)
240
241 output = StringIO.StringIO()
242 error_output = StringIO.StringIO()
243 exit_code = 0
244 try:
245 result = utils.run(cmd, stdout_tee=output,
246 stderr_tee=error_output,
247 timeout=TELEMETRY_TIMEOUT_MINS*60)
248 exit_code = result.exit_status
249 except error.CmdError as e:
250 logging.debug('Error occurred executing.')
251 exit_code = e.result_obj.exit_status
252
253 stdout = output.getvalue()
254 stderr = error_output.getvalue()
255 logging.debug('Completed with exit code: %d.\nstdout:%s\n'
256 'stderr:%s', exit_code, stdout, stderr)
257 return stdout, stderr, exit_code
Simran Basi833814b2013-01-29 13:13:43 -0800258
259
Luis Lozano814c7182015-09-08 11:20:47 -0700260 def _run_telemetry(self, script, test_or_benchmark, *args):
Simran Basi833814b2013-01-29 13:13:43 -0800261 """Runs telemetry on a dut.
262
263 @param script: Telemetry script we want to run. For example:
Luis Lozano23ae3192013-11-08 16:22:46 -0800264 [path_to_telemetry_src]/src/tools/telemetry/run_tests.
Simran Basi833814b2013-01-29 13:13:43 -0800265 @param test_or_benchmark: Name of the test or benchmark we want to run,
266 with the page_set (if required) as part of the
267 string.
Luis Lozano814c7182015-09-08 11:20:47 -0700268 @param args: additional list of arguments to pass to the script.
Simran Basi833814b2013-01-29 13:13:43 -0800269
270 @returns A TelemetryResult Instance with the results of this telemetry
271 execution.
272 """
Simran Basi1dbfc132013-05-02 10:11:02 -0700273 # TODO (sbasi crbug.com/239933) add support for incognito mode.
Simran Basi833814b2013-01-29 13:13:43 -0800274
Luis Lozano814c7182015-09-08 11:20:47 -0700275 telemetry_cmd = self._get_telemetry_cmd(script,
276 test_or_benchmark,
277 *args)
Keith Haddow1e5c7012016-03-09 16:05:37 -0800278 logging.debug('Running Telemetry: %s', telemetry_cmd)
Luis Lozano23ae3192013-11-08 16:22:46 -0800279
Keith Haddow1e5c7012016-03-09 16:05:37 -0800280 stdout, stderr, exit_code = self._run_cmd(telemetry_cmd)
Simran Basi833814b2013-01-29 13:13:43 -0800281
282 return TelemetryResult(exit_code=exit_code, stdout=stdout,
283 stderr=stderr)
284
285
Keith Haddow1e5c7012016-03-09 16:05:37 -0800286 def _run_scp(self, perf_results_dir):
287 """Runs telemetry on a dut.
288
289 @param perf_results_dir: The local directory that results are being
290 collected.
291 """
292 scp_cmd = self._scp_telemetry_results_cmd(perf_results_dir)
293 logging.debug('Retrieving Results: %s', scp_cmd)
Dean Liaoe4773c72017-11-09 16:15:38 +0800294 _, _, exit_code = self._run_cmd(scp_cmd)
295 if exit_code != 0:
296 raise error.TestFail('Unable to retrieve results.')
Keith Haddow1e5c7012016-03-09 16:05:37 -0800297
298
Luis Lozano814c7182015-09-08 11:20:47 -0700299 def _run_test(self, script, test, *args):
Simran Basi1dbfc132013-05-02 10:11:02 -0700300 """Runs a telemetry test on a dut.
301
302 @param script: Which telemetry test script we want to run. Can be
303 telemetry's base test script or the Chrome OS specific
304 test script.
305 @param test: Telemetry test we want to run.
Luis Lozano814c7182015-09-08 11:20:47 -0700306 @param args: additional list of arguments to pass to the script.
Simran Basi1dbfc132013-05-02 10:11:02 -0700307
308 @returns A TelemetryResult Instance with the results of this telemetry
309 execution.
310 """
311 logging.debug('Running telemetry test: %s', test)
312 telemetry_script = os.path.join(self._telemetry_path, script)
Luis Lozano814c7182015-09-08 11:20:47 -0700313 result = self._run_telemetry(telemetry_script, test, *args)
Simran Basi1dbfc132013-05-02 10:11:02 -0700314 if result.status is FAILED_STATUS:
Ilja H. Friedelc7bf3102014-05-13 17:31:25 -0700315 raise error.TestFail('Telemetry test %s failed.' % test)
Simran Basi1dbfc132013-05-02 10:11:02 -0700316 return result
317
318
Luis Lozano814c7182015-09-08 11:20:47 -0700319 def run_telemetry_test(self, test, *args):
Simran Basi833814b2013-01-29 13:13:43 -0800320 """Runs a telemetry test on a dut.
321
322 @param test: Telemetry test we want to run.
Luis Lozano814c7182015-09-08 11:20:47 -0700323 @param args: additional list of arguments to pass to the telemetry
324 execution script.
Simran Basi833814b2013-01-29 13:13:43 -0800325
326 @returns A TelemetryResult Instance with the results of this telemetry
327 execution.
328 """
Luis Lozano814c7182015-09-08 11:20:47 -0700329 return self._run_test(TELEMETRY_RUN_TESTS_SCRIPT, test, *args)
Simran Basi1dbfc132013-05-02 10:11:02 -0700330
331
Luis Lozano814c7182015-09-08 11:20:47 -0700332 def run_telemetry_benchmark(self, benchmark, perf_value_writer=None,
333 *args):
Simran Basi833814b2013-01-29 13:13:43 -0800334 """Runs a telemetry benchmark on a dut.
335
336 @param benchmark: Benchmark we want to run.
Fang Denge689e712013-11-13 18:27:06 -0800337 @param perf_value_writer: Should be an instance with the function
338 output_perf_value(), if None, no perf value
339 will be written. Typically this will be the
340 job object from an autotest test.
Luis Lozano814c7182015-09-08 11:20:47 -0700341 @param args: additional list of arguments to pass to the telemetry
342 execution script.
Simran Basi833814b2013-01-29 13:13:43 -0800343
344 @returns A TelemetryResult Instance with the results of this telemetry
345 execution.
346 """
Dave Tu6a404e62013-11-05 15:54:48 -0800347 logging.debug('Running telemetry benchmark: %s', benchmark)
Ting-Yuan Huange5b19132016-03-22 13:02:41 +0800348
Kuo-Hsin Yang07da7b62018-08-08 16:56:06 +0800349 if benchmark in ON_DUT_BLACKLIST:
Ting-Yuan Huange5b19132016-03-22 13:02:41 +0800350 self._telemetry_on_dut = False
351
352 if self._telemetry_on_dut:
353 telemetry_script = os.path.join(DUT_CHROME_ROOT,
354 TELEMETRY_RUN_BENCHMARKS_SCRIPT)
355 self._ensure_deps(self._host, benchmark)
356 else:
357 telemetry_script = os.path.join(self._telemetry_path,
358 TELEMETRY_RUN_BENCHMARKS_SCRIPT)
359
Luis Lozano814c7182015-09-08 11:20:47 -0700360 result = self._run_telemetry(telemetry_script, benchmark, *args)
Simran Basi833814b2013-01-29 13:13:43 -0800361
362 if result.status is WARNING_STATUS:
Dave Tu6a404e62013-11-05 15:54:48 -0800363 raise error.TestWarn('Telemetry Benchmark: %s'
364 ' exited with Warnings.' % benchmark)
Simran Basi833814b2013-01-29 13:13:43 -0800365 if result.status is FAILED_STATUS:
Dave Tu6a404e62013-11-05 15:54:48 -0800366 raise error.TestFail('Telemetry Benchmark: %s'
367 ' failed to run.' % benchmark)
Keith Haddow1e5c7012016-03-09 16:05:37 -0800368 if perf_value_writer:
369 self._run_scp(perf_value_writer.resultsdir)
Simran Basi833814b2013-01-29 13:13:43 -0800370 return result
Ting-Yuan Huange5b19132016-03-22 13:02:41 +0800371
Gurchetan Singhfaf75e92017-04-17 18:09:44 -0700372
373 def run_gpu_integration_test(self, test, *args):
374 """Runs a gpu test on a dut.
375
376 @param test: Gpu test we want to run.
377 @param args: additional list of arguments to pass to the telemetry
378 execution script.
379
380 @returns A TelemetryResult instance with the results of this telemetry
381 execution.
382 """
383 script = os.path.join(DUT_CHROME_ROOT,
384 TELEMETRY_RUN_GPU_TESTS_SCRIPT)
385 cmd = []
386 if self._devserver:
387 devserver_hostname = self._devserver.hostname
388 cmd.extend(['ssh', devserver_hostname])
389
390 cmd.extend(
Dean Liaoe3e75f62017-11-14 10:36:43 +0800391 [self._host.ssh_command(alive_interval=900, connection_attempts=4),
392 'python', script])
Gurchetan Singhfaf75e92017-04-17 18:09:44 -0700393 cmd.extend(args)
394 cmd.append(test)
395 cmd = ' '.join(cmd)
396 stdout, stderr, exit_code = self._run_cmd(cmd)
397
398 return TelemetryResult(exit_code=exit_code, stdout=stdout,
399 stderr=stderr)
400
401
Ting-Yuan Huange5b19132016-03-22 13:02:41 +0800402 def _ensure_deps(self, dut, test_name):
403 """
404 Ensure the dependencies are locally available on DUT.
405
406 @param dut: The autotest host object representing DUT.
407 @param test_name: Name of the telemetry test.
408 """
409 # Get DEPs using host's telemetry.
Kuo-Hsin Yang4a006172018-04-25 14:44:55 +0800410 # Example output, fetch_benchmark_deps.py --output-deps=deps octane:
411 # {'octane': ['tools/perf/page_sets/data/octane_002.wprgo']}
412 perf_path = os.path.join(self._telemetry_path, 'tools', 'perf')
413 deps_path = os.path.join(perf_path, 'fetch_benchmark_deps_result.json')
414 fetch_path = os.path.join(perf_path, 'fetch_benchmark_deps.py')
415 format_fetch = ('python %s --output-deps=%s %s')
416 command_fetch = format_fetch % (fetch_path, deps_path, test_name)
417 command_get = 'cat %s' % deps_path
Ting-Yuan Huange5b19132016-03-22 13:02:41 +0800418
419 if self._devserver:
420 devserver_hostname = self._devserver.url().split(
421 'http://')[1].split(':')[0]
Kuo-Hsin Yang4a006172018-04-25 14:44:55 +0800422 command_fetch = 'ssh %s %s' % (devserver_hostname, command_fetch)
423 command_get = 'ssh %s %s' % (devserver_hostname, command_get)
Ting-Yuan Huange5b19132016-03-22 13:02:41 +0800424
Kuo-Hsin Yang4a006172018-04-25 14:44:55 +0800425 logging.info('Getting DEPs: %s', command_fetch)
426 _, _, exit_code = self._run_cmd(command_fetch)
427 if exit_code != 0:
428 raise error.TestFail('Error occurred while fetching DEPs.')
429 stdout, _, exit_code = self._run_cmd(command_get)
430 if exit_code != 0:
Ting-Yuan Huange5b19132016-03-22 13:02:41 +0800431 raise error.TestFail('Error occurred while getting DEPs.')
432
433 # Download DEPs to DUT.
434 # send_file() relies on rsync over ssh. Couldn't be better.
Kuo-Hsin Yang4a006172018-04-25 14:44:55 +0800435 deps = json.loads(stdout)
436 for dep in deps[test_name]:
Ting-Yuan Huange5b19132016-03-22 13:02:41 +0800437 src = os.path.join(self._telemetry_path, dep)
438 dst = os.path.join(DUT_CHROME_ROOT, dep)
Ting-Yuan Huang8a2c7f72016-03-28 22:01:07 +0800439 if self._devserver:
440 logging.info('Copying: %s -> %s', src, dst)
Chung-yih Wangfd8eb242017-12-09 19:23:04 +0800441 rsync_cmd = utils.sh_escape('rsync %s %s %s:%s' %
442 (self._host.rsync_options(), src,
443 self._host.hostname, dst))
444 utils.run('ssh %s "%s"' % (devserver_hostname, rsync_cmd))
Ting-Yuan Huang8a2c7f72016-03-28 22:01:07 +0800445 else:
446 if not os.path.isfile(src):
447 raise error.TestFail('Error occurred while saving DEPs.')
448 logging.info('Copying: %s -> %s', src, dst)
449 dut.send_file(src, dst)