blob: b7a8164eeae48b32bb1aec74325bef1c23d71ee4 [file] [log] [blame]
Simon Glass50883f92011-07-12 16:19:16 -07001#!/usr/bin/python
2
3# Copyright (c) 2011 The Chromium OS Authors. All rights reserved.
4# Use of this source code is governed by a BSD-style license that can be
5# found in the LICENSE file.
6
7"""This implements calling of scripts and other utilities/tools.
8
9We support running inside and outside the chroot. To do this, we permit
10a ## prefix which resolves to the chroot. Within the chroot this will be
11/ but outside it will be the full path to the chroot.
12
13So we can use filenames like this:
14
15 ##/usr/share/vboot/bitmaps/make_bmp_images.sh
16
17"""
18
Simon Glassc15a2cb2012-09-08 20:51:37 -070019import doctest
Simon Glass50883f92011-07-12 16:19:16 -070020import optparse
21import os
Simon Glassc15a2cb2012-09-08 20:51:37 -070022import re
Simon Glass951a2db2011-07-17 15:58:58 -070023import shutil
Gabe Black0f419b62013-01-10 22:05:00 -080024import struct
Simon Glass50883f92011-07-12 16:19:16 -070025import sys
Simon Glass951a2db2011-07-17 15:58:58 -070026import tempfile
Simon Glassc15a2cb2012-09-08 20:51:37 -070027import unittest
Simon Glass50883f92011-07-12 16:19:16 -070028
David Jamesb0a15c22013-01-02 18:32:30 -080029from chromite.lib import cros_build_lib
30from chromite.lib import git
Simon Glassab344e32011-07-17 09:17:07 -070031import cros_output
Simon Glass50883f92011-07-12 16:19:16 -070032
Vadim Bendeburyd9e9e682013-02-14 15:40:34 -080033# Attributes defined outside __init__
34#pylint: disable=W0201
Simon Glass50883f92011-07-12 16:19:16 -070035
36class CmdError(Exception):
37 """An error in the execution of a command."""
38 pass
39
40
41class Tools:
Simon Glassc15a2cb2012-09-08 20:51:37 -070042 """A class to encapsulate the external tools we want to run.
Simon Glass50883f92011-07-12 16:19:16 -070043
44 This provides convenient functions for running tools inside/outside the
Simon Glass290a1802011-07-17 13:54:32 -070045 chroot.
46
47 Public properties:
48 outdir: The output directory to write output files to.
Simon Glass6e336fb2012-03-09 15:45:40 -080049 search_paths: The list of directories to search for files we are asked
50 to read.
Simon Glass290a1802011-07-17 13:54:32 -070051
52 The tools class also provides common paths:
Simon Glass50883f92011-07-12 16:19:16 -070053
54 chroot_path: chroot directory
55 src_path: source directory
56 script_path: scripts directory (src/scripts)
57 overlay_path: overlays directory (src/overlays)
58 priv_overlay_path: private overlays directory (src/private-overlays)
59 board_path: build directory (/build in chroot)
60 third_party_path: third_parth directory (src/third_party)
61 cros_overlay_path: Chromium OS overlay (src/chromiumos-overlay)
62 """
Simon Glassc15a2cb2012-09-08 20:51:37 -070063
Simon Glassab344e32011-07-17 09:17:07 -070064 def __init__(self, output):
Simon Glass50883f92011-07-12 16:19:16 -070065 """Set up the tools system.
66
67 Args:
Simon Glassab344e32011-07-17 09:17:07 -070068 output: cros_output object to use for output.
Simon Glassc15a2cb2012-09-08 20:51:37 -070069
70 Raises:
71 IOError: Unable to find .repo directory
72
Simon Glass50883f92011-07-12 16:19:16 -070073 """
74 # Detect whether we're inside a chroot or not
75 self.in_chroot = cros_build_lib.IsInsideChroot()
Simon Glassab344e32011-07-17 09:17:07 -070076 self._out = output
Simon Glass64db3062011-07-14 21:58:54 -070077 self._root = None
Vadim Bendebury59ee2d32013-02-12 13:19:50 -080078 self.chroot_path = None
79 root_dir = None
Simon Glass64db3062011-07-14 21:58:54 -070080 if self.in_chroot:
81 root_dir = os.getenv('CROS_WORKON_SRCROOT')
82 else:
David Jamesb0a15c22013-01-02 18:32:30 -080083 repo = git.FindRepoDir('.')
Vadim Bendebury59ee2d32013-02-12 13:19:50 -080084 if repo:
85 root_dir = os.path.dirname(repo)
Simon Glass50883f92011-07-12 16:19:16 -070086
Vadim Bendebury59ee2d32013-02-12 13:19:50 -080087 if root_dir:
88 self._SetRoot(root_dir)
89 self._out.Info("Chroot is at '%s'" % self.chroot_path)
90 else:
91 self._out.Info('Running outside chroot')
92
Simon Glass50883f92011-07-12 16:19:16 -070093 self._tools = {
Simon Glassc15a2cb2012-09-08 20:51:37 -070094 'make_bmp_image': '##/usr/share/vboot/bitmaps/make_bmp_images.sh',
95 'bct_dump': '##/usr/bin/bct_dump',
96 'tegrarcm': '##/usr/bin/tegrarcm',
97 'gbb_utility': '##/usr/bin/gbb_utility',
98 'cbfstool': '##/usr/bin/cbfstool',
99 'fdisk': '##/sbin/fdisk',
Simon Glass50883f92011-07-12 16:19:16 -0700100 }
Simon Glassc15a2cb2012-09-08 20:51:37 -0700101 self.outdir = None # We have no output directory yet
Simon Glass6e336fb2012-03-09 15:45:40 -0800102 self.search_paths = []
Simon Glass951a2db2011-07-17 15:58:58 -0700103
Simon Glass1f778c92011-08-09 13:30:43 -0700104 def __enter__(self):
105 return self
106
Simon Glassc15a2cb2012-09-08 20:51:37 -0700107 def __exit__(self, the_type, value, traceback):
Simon Glass1f778c92011-08-09 13:30:43 -0700108 self.FinalizeOutputDir()
109 return False
Simon Glass50883f92011-07-12 16:19:16 -0700110
David Jamesb0a15c22013-01-02 18:32:30 -0800111 def _SetRoot(self, root_dir):
Simon Glass50883f92011-07-12 16:19:16 -0700112 """Sets the root directory for the build envionrment.
113
114 The root directory is the one containing .repo, chroot and src.
115
116 This should be called once the root is known. All other parts are
117 calculated from this.
118
119 Args:
Simon Glass05db7fd2011-07-14 22:02:19 -0700120 root_dir: The path to the root directory.
Simon Glass50883f92011-07-12 16:19:16 -0700121 """
122 self._root = os.path.normpath(root_dir)
123
124 # Set up the path to prepend to get to the chroot
125 if self.in_chroot:
126 self.chroot_path = '/'
127 else:
David Jamesb0a15c22013-01-02 18:32:30 -0800128 self.chroot_path = os.path.join(self._root, 'chroot')
Simon Glass50883f92011-07-12 16:19:16 -0700129 self.src_path = os.path.join(self._root, 'src')
130 self.script_path = os.path.join(self.src_path, 'scripts')
131 self.overlay_path = os.path.join(self.src_path, 'overlays')
132 self.priv_overlay_path = os.path.join(self.src_path,
133 'private-overlays')
134 self.board_path = os.path.join(self.chroot_path, 'build')
135 self.third_party_path = os.path.join(self.src_path, 'third_party')
136 self.cros_overlay_path = os.path.join(self.third_party_path,
Simon Glassc15a2cb2012-09-08 20:51:37 -0700137 'chromiumos-overlay')
Simon Glass50883f92011-07-12 16:19:16 -0700138
139 def Filename(self, fname):
Vadim Bendebury59ee2d32013-02-12 13:19:50 -0800140 """Resolve a file path to an absolute path.
Simon Glass50883f92011-07-12 16:19:16 -0700141
Vadim Bendebury59ee2d32013-02-12 13:19:50 -0800142 If fname starts with ##/ and chroot is available, ##/ gets replaced with
143 the chroot path. If chroot is not available, this file name can not be
144 resolved, `None' is returned.
145
146 If fname is not prepended with the above prefix, and is not an existing
147 file, the actual file name is retrieved from the passed in string and the
148 search_paths directories (if any) are searched to for the file. If found -
149 the path to the found file is returned, `None' is returned otherwise.
Simon Glass50883f92011-07-12 16:19:16 -0700150
151 Args:
Vadim Bendebury59ee2d32013-02-12 13:19:50 -0800152 fname: a string, the path to resolve.
Simon Glass50883f92011-07-12 16:19:16 -0700153
Simon Glassc15a2cb2012-09-08 20:51:37 -0700154 Returns:
Vadim Bendebury59ee2d32013-02-12 13:19:50 -0800155 Absolute path to the file or None if not found.
Simon Glass50883f92011-07-12 16:19:16 -0700156 """
157 if fname.startswith('##/'):
Vadim Bendebury59ee2d32013-02-12 13:19:50 -0800158 if self.chroot_path:
159 fname = os.path.join(self.chroot_path, fname[3:])
160 else:
161 return None
Simon Glass6e336fb2012-03-09 15:45:40 -0800162
163 # Search for a pathname that exists, and return it if found
Simon Glass1841e7d2012-07-11 14:50:05 +0200164 if fname and not os.path.exists(fname):
Simon Glass6e336fb2012-03-09 15:45:40 -0800165 for path in self.search_paths:
166 pathname = os.path.join(path, os.path.basename(fname))
167 if os.path.exists(pathname):
168 return pathname
169
170 # If not found, just return the standard, unchanged path
Simon Glass50883f92011-07-12 16:19:16 -0700171 return fname
172
Simon Glass86d16aa2012-03-09 15:29:05 -0800173 def Run(self, tool, args, cwd=None, sudo=False):
Simon Glass50883f92011-07-12 16:19:16 -0700174 """Run a tool with given arguments.
175
176 The tool name may be used unchanged or substituted with a full path if
177 required.
178
179 The tool and arguments can use ##/ to signify the chroot (at the beginning
180 of the tool/argument).
181
182 Args:
183 tool: Name of tool to run.
184 args: List of arguments to pass to tool.
185 cwd: Directory to change into before running tool (None if none).
Simon Glass86d16aa2012-03-09 15:29:05 -0800186 sudo: True to run the tool with sudo
Simon Glass50883f92011-07-12 16:19:16 -0700187
188 Returns:
189 Output of tool (stdout).
190
Simon Glassc15a2cb2012-09-08 20:51:37 -0700191 Raises:
192 CmdError: If running the tool, or the tool itself creates an error.
193 """
Simon Glass50883f92011-07-12 16:19:16 -0700194 if tool in self._tools:
195 tool = self._tools[tool]
196 tool = self.Filename(tool)
197 args = [self.Filename(arg) for arg in args]
198 cmd = [tool] + args
Vadim Bendebury281b3052013-02-19 12:38:15 -0800199 if sudo and os.getuid():
Simon Glass86d16aa2012-03-09 15:29:05 -0800200 cmd.insert(0, 'sudo')
Simon Glass50883f92011-07-12 16:19:16 -0700201 try:
David Jamesb0a15c22013-01-02 18:32:30 -0800202 result = cros_build_lib.RunCommandCaptureOutput(
203 cmd, cwd=cwd, print_cmd=self._out.verbose > 3,
204 combine_stdout_stderr=True, error_code_ok=True)
205 except cros_build_lib.RunCommandError as ex:
206 raise CmdError(str(ex))
207 stdout = result.output
208 if result.returncode:
Simon Glassab344e32011-07-17 09:17:07 -0700209 raise CmdError('Command failed: %s\n%s' % (' '.join(cmd), stdout))
210 self._out.Debug(stdout)
211 return stdout
Simon Glass50883f92011-07-12 16:19:16 -0700212
213 def ReadFile(self, fname):
214 """Read and return the contents of a file.
215
216 Args:
217 fname: path to filename to read, where ## signifiies the chroot.
218
219 Returns:
220 data read from file, as a string.
221 """
222 fd = open(self.Filename(fname), 'rb')
223 data = fd.read()
224 fd.close()
Simon Glassab344e32011-07-17 09:17:07 -0700225 self._out.Info("Read file '%s' size %d (%#0x)" %
Simon Glassc15a2cb2012-09-08 20:51:37 -0700226 (fname, len(data), len(data)))
Simon Glass50883f92011-07-12 16:19:16 -0700227 return data
228
229 def WriteFile(self, fname, data):
230 """Write data into a file.
231
232 Args:
233 fname: path to filename to write, where ## signifiies the chroot.
234 data: data to write to file, as a string.
235 """
Simon Glassab344e32011-07-17 09:17:07 -0700236 self._out.Info("Write file '%s' size %d (%#0x)" %
Simon Glassc15a2cb2012-09-08 20:51:37 -0700237 (fname, len(data), len(data)))
Simon Glass50883f92011-07-12 16:19:16 -0700238 fd = open(self.Filename(fname), 'wb')
239 fd.write(data)
240 fd.close()
241
Gabe Black0f419b62013-01-10 22:05:00 -0800242 def ReadFileAndConcat(self, filenames, compress=None, with_index=False):
Vic Yanga850b922012-08-11 14:08:43 +0800243 """Read several files and concat them.
244
245 Args:
246 filenames: a list containing name of the files to read.
Gabe Black0f419b62013-01-10 22:05:00 -0800247 with_index: If true, an index structure is prepended to the data.
Vic Yanga850b922012-08-11 14:08:43 +0800248
249 Returns:
250 A tuple of a string and two list. The string is the concated data read
251 from file, in the same order as in filenames, aligned to 4-byte. The
Vic Yangc09edff2012-08-16 07:57:44 +0800252 first list contains the offset of each file in the data string and
253 the second one contains the actual (non-padded) length of each file,
254 both in the same order.
Gabe Black0f419b62013-01-10 22:05:00 -0800255
256 The optional index structure is a 32 bit integer set to the number of
257 entries in the index, followed by that many pairs of integers which
258 describe the offset and length of each chunk.
Vic Yanga850b922012-08-11 14:08:43 +0800259 """
260 data = ''
Gabe Black0f419b62013-01-10 22:05:00 -0800261 offsets = []
262 lengths = []
Vic Yanga850b922012-08-11 14:08:43 +0800263 for fname in filenames:
Gabe Black0f419b62013-01-10 22:05:00 -0800264 offsets.append(len(data))
Vic Yanga850b922012-08-11 14:08:43 +0800265 content = self.ReadFile(fname)
266 pad_len = ((len(content) + 3) & ~3) - len(content)
Vic Yangc09edff2012-08-16 07:57:44 +0800267 data += content + chr(0xff) * pad_len
Gabe Black0f419b62013-01-10 22:05:00 -0800268 lengths.append(len(content))
269
270 if with_index:
271 index_size = 4 + len(filenames) * 8
272 index = struct.pack("<I", len(filenames))
273 offsets = tuple(offset + index_size for offset in offsets)
Vadim Bendeburyd9e9e682013-02-14 15:40:34 -0800274 for _, offset, length in zip(filenames, offsets, lengths):
Gabe Black0f419b62013-01-10 22:05:00 -0800275 index += struct.pack("<II", offset, length)
276 data = index + data
Simon Glass9bc399e2012-12-11 14:36:10 -0800277
278 if compress:
279 if compress == 'lzo':
280 # Would be nice to just pipe here. but we don't have RunPipe().
281 fname = self.GetOutputFilename('data.tmp')
282 outname = self.GetOutputFilename('data.tmp.lzo')
283 if os.path.exists(outname):
284 os.remove(outname)
285 self.WriteFile(fname, data)
286 args = ['-9', fname]
287 self.Run('lzop', args)
288 data = self.ReadFile(outname)
289 else:
290 raise ValueError("Unknown compression method '%s'" % compress)
Gabe Black0f419b62013-01-10 22:05:00 -0800291 return data, offsets, lengths
Vic Yanga850b922012-08-11 14:08:43 +0800292
Simon Glass50883f92011-07-12 16:19:16 -0700293 def GetChromeosVersion(self):
Simon Glassc15a2cb2012-09-08 20:51:37 -0700294 """Returns the ChromeOS version string.
Simon Glass50883f92011-07-12 16:19:16 -0700295
296 This works by finding and executing the version script:
297
298 src/third_party/chromiumos-overlay/chromeos/config/chromeos_version.sh
299
300 Returns:
301 Version string in the form '0.14.726.2011_07_07_1635'
302
303 Raises:
304 CmdError: If the version script cannot be found, or is found but cannot
305 be executed.
306 """
307 version_script = os.path.join(self.cros_overlay_path, 'chromeos', 'config',
Simon Glassc15a2cb2012-09-08 20:51:37 -0700308 'chromeos_version.sh')
Simon Glass50883f92011-07-12 16:19:16 -0700309
310 if os.path.exists(version_script):
Simon Glassc15a2cb2012-09-08 20:51:37 -0700311 result = self.Run('sh', ['-c', '. %s >/dev/null; '
312 'echo ${CHROMEOS_VERSION_STRING}'
313 % version_script])
314 return result.strip()
Simon Glass50883f92011-07-12 16:19:16 -0700315 raise CmdError("Cannot find version script 'chromeos_version.sh'")
316
Simon Glass2343e8f2012-10-01 14:48:24 -0700317 def CheckTool(self, name, ebuild=None):
Simon Glass50883f92011-07-12 16:19:16 -0700318 """Check that the specified tool exists.
319
320 If it does not exist in the PATH, then generate a useful error message
321 indicating how to install the ebuild that contains the required tool.
322
323 Args:
Simon Glass2343e8f2012-10-01 14:48:24 -0700324 name: filename of tool to look for on path.
Simon Glass50883f92011-07-12 16:19:16 -0700325 ebuild: name of ebuild which should be emerged to install this tool,
326 or None if it is the same as the filename.
327
328 Raises:
329 CmdError(msg) if the tool is not found.
330 """
331 try:
Simon Glass2343e8f2012-10-01 14:48:24 -0700332 filename = name
Simon Glass50883f92011-07-12 16:19:16 -0700333 if filename in self._tools:
334 filename = self._tools[filename]
335 filename = self.Filename(filename)
336 self.Run('which', [filename])
Simon Glassc15a2cb2012-09-08 20:51:37 -0700337 except CmdError:
Simon Glass50883f92011-07-12 16:19:16 -0700338 raise CmdError("The '%s' utility was not found in your path. "
Simon Glassc15a2cb2012-09-08 20:51:37 -0700339 "Run the following command in \nyour chroot to install "
340 "it: sudo -E emerge %s" % (filename, ebuild or name))
Simon Glass50883f92011-07-12 16:19:16 -0700341
Simon Glassab344e32011-07-17 09:17:07 -0700342 def OutputSize(self, label, filename, level=cros_output.NOTICE):
343 """Display the filename and size of an object.
344
345 Args:
346 label: Label for this file.
347 filename: Filename to output.
348 level: Verbosity level to attach to this message
349 """
350 filename = self.Filename(filename)
351 size = os.stat(filename).st_size
Simon Glassc15a2cb2012-09-08 20:51:37 -0700352 self._out.DoOutput(level, '%s: %s; size: %d / %#x' %
353 (label, filename, size, size))
Simon Glassab344e32011-07-17 09:17:07 -0700354
Simon Glass951a2db2011-07-17 15:58:58 -0700355 def PrepareOutputDir(self, outdir, preserve=False):
356 """Select an output directory, ensuring it exists.
357
358 This either creates a temporary directory or checks that the one supplied
359 by the user is valid. For a temporary directory, it makes a note to
360 remove it later if required.
361
362 Args:
Vadim Bendebury2692ad02013-02-15 10:19:57 -0800363 outdir: a string, name of the output directory to use to store
364 intermediate and output files. If is None - create a temporary
365 directory.
366 preserve: a Boolean. If outdir above is None and preserve is False, the
367 created temporary directory will be destroyed on exit.
Simon Glass951a2db2011-07-17 15:58:58 -0700368 Raises:
369 OSError: If it cannot create the output directory.
370 """
Vadim Bendebury2692ad02013-02-15 10:19:57 -0800371 self.preserve_outdir = outdir or preserve
372 if outdir:
373 self.outdir = outdir
Simon Glass951a2db2011-07-17 15:58:58 -0700374 if not os.path.isdir(self.outdir):
375 try:
376 os.makedirs(self.outdir)
377 except OSError as err:
378 raise CmdError("Cannot make output directory '%s': '%s'" %
Vadim Bendebury2692ad02013-02-15 10:19:57 -0800379 (self.outdir, err.strerror))
Simon Glass951a2db2011-07-17 15:58:58 -0700380 else:
Vadim Bendebury2692ad02013-02-15 10:19:57 -0800381 self.outdir = tempfile.mkdtemp(prefix='cros-dev.')
382 self._out.Debug("Using temporary directory '%s'" % self.outdir)
Simon Glass951a2db2011-07-17 15:58:58 -0700383
384 def FinalizeOutputDir(self):
Vadim Bendebury2692ad02013-02-15 10:19:57 -0800385 """Tidy up: delete output directory if temporary and not preserved."""
386 if self.outdir and not self.preserve_outdir:
387 shutil.rmtree(self.outdir)
Simon Glass951a2db2011-07-17 15:58:58 -0700388 self._out.Debug("Deleted temporary directory '%s'" %
Vadim Bendebury2692ad02013-02-15 10:19:57 -0800389 self.outdir)
390 self.outdir = None
Simon Glass951a2db2011-07-17 15:58:58 -0700391
Simon Glassc15a2cb2012-09-08 20:51:37 -0700392 def GetOutputFilename(self, fname):
393 """Return a filename within the output directory.
394
395 Args:
396 fname: Filename to use for new file
397
398 Returns:
399 The full path of the filename, within the output directory
400 """
401 return os.path.join(self.outdir, fname)
402
403
David Jamesb0a15c22013-01-02 18:32:30 -0800404# pylint: disable=W0212
Simon Glassc15a2cb2012-09-08 20:51:37 -0700405class ToolsTests(unittest.TestCase):
406 """Unit tests for this module."""
407
408 def setUp(self):
409 self.out = cros_output.Output(False)
410 self.tools = Tools(self.out)
411
412 def MakeOutsideChroot(self, base):
413 tools = Tools(self.out)
414 tools.in_chroot = False
David Jamesb0a15c22013-01-02 18:32:30 -0800415 tools._SetRoot(base)
Simon Glassc15a2cb2012-09-08 20:51:37 -0700416 return tools
417
418 def testPaths(self):
419 tools = self.tools
420
Simon Glassc15a2cb2012-09-08 20:51:37 -0700421 self.assertTrue(os.path.isdir(os.path.join(tools._root, '.repo')))
422
423 def _testToolsPaths(self, base, tools):
424 """Common paths tests to run inside and outside chroot.
425
426 These tests are the same inside and outside the choot, so we put them in a
427 separate function.
428
429 Args:
430 base: Base directory to use for testing (contains the 'src' directory).
431 tools: Tools object to use.
432 """
433 self.assertEqual(tools._root, base[:-1])
434 self.assertEqual(tools.src_path, base + 'src')
435 self.assertEqual(tools.script_path, base + 'src/scripts')
436 self.assertEqual(tools.overlay_path, base + 'src/overlays')
437 self.assertEqual(tools.priv_overlay_path, base + 'src/private-overlays')
438 self.assertEqual(tools.third_party_path, base + 'src/third_party')
439 self.assertEqual(tools.cros_overlay_path, base +
440 'src/third_party/chromiumos-overlay')
441
442 def testSetRootInsideChroot(self):
443 """Inside the chroot, paths are slightly different from outside."""
444 tools = Tools(self.out)
David Jamesb0a15c22013-01-02 18:32:30 -0800445 tools.in_chroot = True
Simon Glassc15a2cb2012-09-08 20:51:37 -0700446
447 # Force our own path.
448 base = '/air/bridge/'
449 tools._SetRoot(base)
450
451 # We should get a full path from that without the trailing '/'.
452 self.assertEqual(tools.chroot_path, '/')
453 self.assertEqual(tools.board_path, '/build')
454 self._testToolsPaths(base, tools)
455
456 def testSetRootOutsideChroot(self):
457 """Pretend to be outside the chroot, and check that paths are correct."""
458
459 # Force our own path, outside the chroot.
460 base = '/spotty/light/'
461 tools = self.MakeOutsideChroot(base)
462
463 # We should get a full path from that without the trailing '/'.
David Jamesb0a15c22013-01-02 18:32:30 -0800464 self.assertEqual(tools.chroot_path, base + 'chroot')
465 self.assertEqual(tools.board_path, tools.chroot_path + '/build')
Simon Glassc15a2cb2012-09-08 20:51:37 -0700466 self._testToolsPaths(base, tools)
467
468 def _testToolsFilenames(self, tools):
469 """Common filename tests to run inside and outside chroot.
470
471 These tests are the same inside and outside the choot, so we put them in a
472 separate function.
473
474 Args:
475 tools: Tools object to use.
476 """
477 self.assertEqual(tools.Filename('/root/based/'),
478 '/root/based/')
479
480 # Try search paths in /bin and /ls.
481 tools.search_paths = ['/bin', '/lib']
482 file_in_bin = os.listdir('/bin')[0]
483 self.assertEqual(tools.Filename(file_in_bin), '/bin/%s' % file_in_bin)
484 file_in_lib = os.listdir('/lib')[0]
485 self.assertEqual(tools.Filename(file_in_lib), '/lib/%s' % file_in_lib)
486 self.assertEqual(tools.Filename('i-am-not-here'), 'i-am-not-here')
487
488 # Don't search for an empty file.
489 self.assertEqual(tools.Filename(''), '')
490
491 def testFilenameInsideChroot(self):
492 """Test that we can specify search paths and they work correctly.
493
494 Test search patches inside the chroot.
495 """
496 tools = Tools(self.out)
David Jamesb0a15c22013-01-02 18:32:30 -0800497 tools.in_chroot = True
Simon Glassc15a2cb2012-09-08 20:51:37 -0700498
499 # Force our own path.
500 base = '/air/bridge/'
501 tools._SetRoot(base)
502
503 self.assertEqual(tools.Filename('##/fred'), '/fred')
504 self.assertEqual(tools.Filename('##/just/a/short/dir/'),
505 '/just/a/short/dir/')
506
507 self._testToolsFilenames(tools)
508
509 def testFilenameOutsideChroot(self):
510 """Test that we can specify search paths and they work correctly.
511
512 Test search patches outside the chroot.
513 """
514 base = '/home/'
515 tools = self.MakeOutsideChroot(base)
516
David Jamesb0a15c22013-01-02 18:32:30 -0800517 self.assertEqual(tools.Filename('##/fred'), base + 'chroot/fred')
Simon Glassc15a2cb2012-09-08 20:51:37 -0700518 self.assertEqual(tools.Filename('##/just/a/short/dir/'),
David Jamesb0a15c22013-01-02 18:32:30 -0800519 base + 'chroot/just/a/short/dir/')
Simon Glassc15a2cb2012-09-08 20:51:37 -0700520
521 self._testToolsFilenames(tools)
522
523 def testReadWriteFile(self):
524 """Test our read/write utility functions."""
525 tools = Tools(self.out)
526 tools.PrepareOutputDir(None)
527 data = 'some context here' * 2
528
529 fname = tools.GetOutputFilename('bang')
530 tools.WriteFile(fname, data)
531
532 # Check that the file looks correct.
533 compare = tools.ReadFile(fname)
534 self.assertEqual(data, compare)
535
536 def testReadFileAndConcat(self):
537 """Test 'cat' of several files."""
538 tools = Tools(self.out)
539 tools.PrepareOutputDir(None)
540 file_list = ['one', 'empty', 'two', 'three', 'four']
541 out_list = [tools.GetOutputFilename(fname) for fname in file_list]
542 file_list[1] = '' # Empty the 'empty' file.
543 for upto in range(len(file_list)):
544 tools.WriteFile(out_list[upto], file_list[upto])
545
546 data, offset, length = tools.ReadFileAndConcat(out_list)
547 self.assertEqual(len(data), 20)
548 self.assertEqual(offset, [0, 4, 4, 8, 16])
549 self.assertEqual(length, [3, 0, 3, 5, 4])
550
551 def testGetChromeosVersion(self):
552 """Test for GetChromeosVersion() inside and outside chroot.
553
554 This function returns a string like '2893.0.2012_09_16_2219'.
555 """
556 tools = self.tools
557
David James13200f02013-02-20 11:41:06 -0800558 re_version_pattern = '^\d{4}.\d+.(?:\d{4}_\d{2}_\d{2}_\d+|\d+)$'
Gilad Arnold8047ff42013-02-20 08:50:14 -0800559 re_version = re.compile(re_version_pattern)
560 reported_version = tools.GetChromeosVersion()
561 self.assertTrue(re_version.match(reported_version),
562 msg='%s !~= %s' % (reported_version, re_version_pattern))
Simon Glassc15a2cb2012-09-08 20:51:37 -0700563
564 tools = Tools(self.out)
565
566 # Force our own path, outside the chroot. This should fail.
567 base = 'invalid-dir'
568 tools = self.MakeOutsideChroot(base)
569 tools.in_chroot = False
570 self.assertRaises(CmdError, tools.GetChromeosVersion)
571
572 def testCheckTool(self):
573 """Test for the CheckTool() method."""
574 tools = self.tools
575
576 tools.CheckTool('fdisk')
577 tools.CheckTool('gbb_utility')
578 self.assertRaises(CmdError, tools.CheckTool, 'non-existent-tool')
579 tools.CheckTool('fdisk')
580 self.assertRaises(CmdError, tools.CheckTool, '/usr/bin/fdisk')
581
582 def testRun(self):
583 """Test for the Run() method."""
584 tools = self.tools
585
586 # Ask fdisk for its version - this utility must be in the chroot.
Olof Johansson3399a762014-01-15 16:15:58 -0800587 re_fdisk = re.compile('fdisk .*util-linux .*')
Simon Glassc15a2cb2012-09-08 20:51:37 -0700588 self.assertTrue(re_fdisk.match(tools.Run('fdisk', ['-v'])))
589
590 # We need sudo for looking at disks.
Simon Glassc15a2cb2012-09-08 20:51:37 -0700591 out = tools.Run('fdisk', ['-l', '/dev/sda'], sudo=True)
592
593 # Don't look at the specific output, but it will have > 5 lines.
594 self.assertTrue(len(out.splitlines()) > 5)
595
596 self.assertEqual(tools.Run('pwd', [], cwd='/tmp'), '/tmp\n')
597
598 def testOutputDir(self):
599 """Test output directory creation and deletion."""
600 tools = self.tools
601
602 # First check basic operation, creating and deleting a tmpdir.
603 tools.PrepareOutputDir(None)
604 fname = tools.GetOutputFilename('fred')
605 tools.WriteFile(fname, 'You are old, Father William, the young man said')
606 dirname = tools.outdir
607 tools.FinalizeOutputDir()
608 self.assertFalse(os.path.exists(fname))
609 self.assertFalse(os.path.exists(dirname))
610
611 # Try preserving it.
612 tools.PrepareOutputDir(None, True)
613 fname = tools.GetOutputFilename('fred')
614 tools.WriteFile(fname, 'and your hair has become very white')
615 dirname = tools.outdir
616 tools.FinalizeOutputDir()
617 self.assertTrue(os.path.exists(fname))
618 self.assertTrue(os.path.exists(dirname))
619 shutil.rmtree(dirname)
620
621 # Use our own directory, which is always preserved.
622 testdir = '/tmp/tools-test.test'
623 tools.PrepareOutputDir(testdir)
624 fname = tools.GetOutputFilename('fred')
625 tools.WriteFile(fname, 'and yet you incessantly stand on your head')
626 dirname = tools.outdir
627 tools.FinalizeOutputDir()
628 self.assertTrue(os.path.exists(fname))
629 self.assertTrue(os.path.exists(dirname))
630 shutil.rmtree(dirname)
631
632 # Try creating an invalid directory.
633 testdir = '/sys/cannot/do/this/here'
634 self.assertRaises(CmdError, tools.PrepareOutputDir, testdir)
635 fname = tools.GetOutputFilename('fred')
636 self.assertRaises(IOError, tools.WriteFile, fname,
637 'do you think at your age it is right?')
638 dirname = tools.outdir
639 tools.FinalizeOutputDir()
640
641 def _OutputMock(self, level, msg, color=None):
642 self._level = level
643 self._msg = msg
644 self._color = color
645
646 def testOutputSize(self):
647 """Test for OutputSize() function."""
648 tools = self.tools
649
650 # Rather than mocks, use a special Output object.
651 out = tools._out
652 out._Output = self._OutputMock
653
654 tools.PrepareOutputDir(None)
655 fname = tools.GetOutputFilename('fred')
656 text_string = 'test of output size'
657 tools.WriteFile(fname, text_string)
658
659 re_fname = re.compile('fred')
660 re_size = re.compile('.*size: (\d*)')
661
662 tools.OutputSize('first', fname, level=cros_output.ERROR)
663 self.assertEqual(self._level, cros_output.ERROR)
664 self.assertTrue(re_fname.search(self._msg))
665 self.assertEqual(self._color, None)
666
667 # Check the default level, and that the filename length is given.
668 tools.OutputSize('second', fname)
669 self.assertEqual(self._level, cros_output.NOTICE)
670 self.assertTrue(re_fname.search(self._msg))
671 self.assertEqual(self._color, None)
672 m = re_size.match(self._msg)
673 self.assertEqual(m.group(1), str(len(text_string)))
674
675 tools.FinalizeOutputDir()
676
677
678def _Test(argv):
Simon Glass50883f92011-07-12 16:19:16 -0700679 """Run any built-in tests."""
Simon Glassc15a2cb2012-09-08 20:51:37 -0700680 unittest.main(argv=argv)
Vadim Bendebury9ddeee12013-02-14 15:24:17 -0800681 assert doctest.testmod().failed == 0
Simon Glassc15a2cb2012-09-08 20:51:37 -0700682
Simon Glass50883f92011-07-12 16:19:16 -0700683
684def main():
685 """Main function for tools.
686
687 We provide a way to call a few of our useful functions.
688
689 TODO(sjg) Move into the Chromite libraries when these are ready.
690 """
691 parser = optparse.OptionParser()
692 parser.add_option('-v', '--verbosity', dest='verbosity', default=1,
Simon Glassc15a2cb2012-09-08 20:51:37 -0700693 type='int',
694 help='Control verbosity: 0=silent, 1=progress, 3=full, '
695 '4=debug')
Simon Glass50883f92011-07-12 16:19:16 -0700696
697 help_str = '%s [options] cmd [args]\n\nAvailable commands:\n' % sys.argv[0]
698 help_str += '\tchromeos-version\tDisplay Chrome OS version'
699 parser.usage = help_str
700
701 (options, args) = parser.parse_args(sys.argv)
702 args = args[1:]
703
Simon Glassab344e32011-07-17 09:17:07 -0700704 out = cros_output.Output(options.verbosity)
705 tools = Tools(out)
Simon Glass50883f92011-07-12 16:19:16 -0700706 if not args:
707 parser.error('No command provided')
708 elif args[0] == 'chromeos-version':
709 print tools.GetChromeosVersion()
710 else:
711 parser.error("Unknown command '%s'" % args[0])
712
713if __name__ == '__main__':
Simon Glassc15a2cb2012-09-08 20:51:37 -0700714 if sys.argv[1:2] == ['--test']:
715 _Test([sys.argv[0]] + sys.argv[2:])
Simon Glass50883f92011-07-12 16:19:16 -0700716 else:
717 main()