blob: a1820c8d9c01c2b5022a56119b5560d36357e65a [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
78 if self.in_chroot:
79 root_dir = os.getenv('CROS_WORKON_SRCROOT')
80 else:
David Jamesb0a15c22013-01-02 18:32:30 -080081 repo = git.FindRepoDir('.')
Simon Glass64db3062011-07-14 21:58:54 -070082 if not repo:
83 raise IOError('Cannot find .repo directory (must be below cwd level)')
84 root_dir = os.path.dirname(repo)
85 self._SetRoot(root_dir)
Simon Glass50883f92011-07-12 16:19:16 -070086
Simon Glassab344e32011-07-17 09:17:07 -070087 self._out.Info("Chroot is at '%s'" % self.chroot_path)
Simon Glass50883f92011-07-12 16:19:16 -070088 self._tools = {
Simon Glassc15a2cb2012-09-08 20:51:37 -070089 'make_bmp_image': '##/usr/share/vboot/bitmaps/make_bmp_images.sh',
90 'bct_dump': '##/usr/bin/bct_dump',
91 'tegrarcm': '##/usr/bin/tegrarcm',
92 'gbb_utility': '##/usr/bin/gbb_utility',
93 'cbfstool': '##/usr/bin/cbfstool',
94 'fdisk': '##/sbin/fdisk',
Simon Glass50883f92011-07-12 16:19:16 -070095 }
Simon Glassc15a2cb2012-09-08 20:51:37 -070096 self.outdir = None # We have no output directory yet
Simon Glass6e336fb2012-03-09 15:45:40 -080097 self.search_paths = []
Simon Glass951a2db2011-07-17 15:58:58 -070098
Simon Glass1f778c92011-08-09 13:30:43 -070099 def __enter__(self):
100 return self
101
Simon Glassc15a2cb2012-09-08 20:51:37 -0700102 def __exit__(self, the_type, value, traceback):
Simon Glass1f778c92011-08-09 13:30:43 -0700103 self.FinalizeOutputDir()
104 return False
Simon Glass50883f92011-07-12 16:19:16 -0700105
David Jamesb0a15c22013-01-02 18:32:30 -0800106 def _SetRoot(self, root_dir):
Simon Glass50883f92011-07-12 16:19:16 -0700107 """Sets the root directory for the build envionrment.
108
109 The root directory is the one containing .repo, chroot and src.
110
111 This should be called once the root is known. All other parts are
112 calculated from this.
113
114 Args:
Simon Glass05db7fd2011-07-14 22:02:19 -0700115 root_dir: The path to the root directory.
Simon Glass50883f92011-07-12 16:19:16 -0700116 """
117 self._root = os.path.normpath(root_dir)
118
119 # Set up the path to prepend to get to the chroot
120 if self.in_chroot:
121 self.chroot_path = '/'
122 else:
David Jamesb0a15c22013-01-02 18:32:30 -0800123 self.chroot_path = os.path.join(self._root, 'chroot')
Simon Glass50883f92011-07-12 16:19:16 -0700124 self.src_path = os.path.join(self._root, 'src')
125 self.script_path = os.path.join(self.src_path, 'scripts')
126 self.overlay_path = os.path.join(self.src_path, 'overlays')
127 self.priv_overlay_path = os.path.join(self.src_path,
128 'private-overlays')
129 self.board_path = os.path.join(self.chroot_path, 'build')
130 self.third_party_path = os.path.join(self.src_path, 'third_party')
131 self.cros_overlay_path = os.path.join(self.third_party_path,
Simon Glassc15a2cb2012-09-08 20:51:37 -0700132 'chromiumos-overlay')
Simon Glass50883f92011-07-12 16:19:16 -0700133
134 def Filename(self, fname):
135 """Resolve a chroot-relative filename to an absolute path.
136
137 This looks for ## at the beginning of the filename, and changes it to
138 the chroot directory, which will be / if inside the chroot, or a path
139 to the chroot if not.
140
141 Args:
142 fname: Filename to convert.
143
Simon Glassc15a2cb2012-09-08 20:51:37 -0700144 Returns:
Simon Glass50883f92011-07-12 16:19:16 -0700145 Absolute path to filename.
146 """
147 if fname.startswith('##/'):
148 fname = os.path.join(self.chroot_path, fname[3:])
Simon Glass6e336fb2012-03-09 15:45:40 -0800149
150 # Search for a pathname that exists, and return it if found
Simon Glass1841e7d2012-07-11 14:50:05 +0200151 if fname and not os.path.exists(fname):
Simon Glass6e336fb2012-03-09 15:45:40 -0800152 for path in self.search_paths:
153 pathname = os.path.join(path, os.path.basename(fname))
154 if os.path.exists(pathname):
155 return pathname
156
157 # If not found, just return the standard, unchanged path
Simon Glass50883f92011-07-12 16:19:16 -0700158 return fname
159
Simon Glass86d16aa2012-03-09 15:29:05 -0800160 def Run(self, tool, args, cwd=None, sudo=False):
Simon Glass50883f92011-07-12 16:19:16 -0700161 """Run a tool with given arguments.
162
163 The tool name may be used unchanged or substituted with a full path if
164 required.
165
166 The tool and arguments can use ##/ to signify the chroot (at the beginning
167 of the tool/argument).
168
169 Args:
170 tool: Name of tool to run.
171 args: List of arguments to pass to tool.
172 cwd: Directory to change into before running tool (None if none).
Simon Glass86d16aa2012-03-09 15:29:05 -0800173 sudo: True to run the tool with sudo
Simon Glass50883f92011-07-12 16:19:16 -0700174
175 Returns:
176 Output of tool (stdout).
177
Simon Glassc15a2cb2012-09-08 20:51:37 -0700178 Raises:
179 CmdError: If running the tool, or the tool itself creates an error.
180 """
Simon Glass50883f92011-07-12 16:19:16 -0700181 if tool in self._tools:
182 tool = self._tools[tool]
183 tool = self.Filename(tool)
184 args = [self.Filename(arg) for arg in args]
185 cmd = [tool] + args
Vadim Bendebury281b3052013-02-19 12:38:15 -0800186 if sudo and os.getuid():
Simon Glass86d16aa2012-03-09 15:29:05 -0800187 cmd.insert(0, 'sudo')
Simon Glass50883f92011-07-12 16:19:16 -0700188 try:
David Jamesb0a15c22013-01-02 18:32:30 -0800189 result = cros_build_lib.RunCommandCaptureOutput(
190 cmd, cwd=cwd, print_cmd=self._out.verbose > 3,
191 combine_stdout_stderr=True, error_code_ok=True)
192 except cros_build_lib.RunCommandError as ex:
193 raise CmdError(str(ex))
194 stdout = result.output
195 if result.returncode:
Simon Glassab344e32011-07-17 09:17:07 -0700196 raise CmdError('Command failed: %s\n%s' % (' '.join(cmd), stdout))
197 self._out.Debug(stdout)
198 return stdout
Simon Glass50883f92011-07-12 16:19:16 -0700199
200 def ReadFile(self, fname):
201 """Read and return the contents of a file.
202
203 Args:
204 fname: path to filename to read, where ## signifiies the chroot.
205
206 Returns:
207 data read from file, as a string.
208 """
209 fd = open(self.Filename(fname), 'rb')
210 data = fd.read()
211 fd.close()
Simon Glassab344e32011-07-17 09:17:07 -0700212 self._out.Info("Read file '%s' size %d (%#0x)" %
Simon Glassc15a2cb2012-09-08 20:51:37 -0700213 (fname, len(data), len(data)))
Simon Glass50883f92011-07-12 16:19:16 -0700214 return data
215
216 def WriteFile(self, fname, data):
217 """Write data into a file.
218
219 Args:
220 fname: path to filename to write, where ## signifiies the chroot.
221 data: data to write to file, as a string.
222 """
Simon Glassab344e32011-07-17 09:17:07 -0700223 self._out.Info("Write file '%s' size %d (%#0x)" %
Simon Glassc15a2cb2012-09-08 20:51:37 -0700224 (fname, len(data), len(data)))
Simon Glass50883f92011-07-12 16:19:16 -0700225 fd = open(self.Filename(fname), 'wb')
226 fd.write(data)
227 fd.close()
228
Gabe Black0f419b62013-01-10 22:05:00 -0800229 def ReadFileAndConcat(self, filenames, compress=None, with_index=False):
Vic Yanga850b922012-08-11 14:08:43 +0800230 """Read several files and concat them.
231
232 Args:
233 filenames: a list containing name of the files to read.
Gabe Black0f419b62013-01-10 22:05:00 -0800234 with_index: If true, an index structure is prepended to the data.
Vic Yanga850b922012-08-11 14:08:43 +0800235
236 Returns:
237 A tuple of a string and two list. The string is the concated data read
238 from file, in the same order as in filenames, aligned to 4-byte. The
Vic Yangc09edff2012-08-16 07:57:44 +0800239 first list contains the offset of each file in the data string and
240 the second one contains the actual (non-padded) length of each file,
241 both in the same order.
Gabe Black0f419b62013-01-10 22:05:00 -0800242
243 The optional index structure is a 32 bit integer set to the number of
244 entries in the index, followed by that many pairs of integers which
245 describe the offset and length of each chunk.
Vic Yanga850b922012-08-11 14:08:43 +0800246 """
247 data = ''
Gabe Black0f419b62013-01-10 22:05:00 -0800248 offsets = []
249 lengths = []
Vic Yanga850b922012-08-11 14:08:43 +0800250 for fname in filenames:
Gabe Black0f419b62013-01-10 22:05:00 -0800251 offsets.append(len(data))
Vic Yanga850b922012-08-11 14:08:43 +0800252 content = self.ReadFile(fname)
253 pad_len = ((len(content) + 3) & ~3) - len(content)
Vic Yangc09edff2012-08-16 07:57:44 +0800254 data += content + chr(0xff) * pad_len
Gabe Black0f419b62013-01-10 22:05:00 -0800255 lengths.append(len(content))
256
257 if with_index:
258 index_size = 4 + len(filenames) * 8
259 index = struct.pack("<I", len(filenames))
260 offsets = tuple(offset + index_size for offset in offsets)
Vadim Bendeburyd9e9e682013-02-14 15:40:34 -0800261 for _, offset, length in zip(filenames, offsets, lengths):
Gabe Black0f419b62013-01-10 22:05:00 -0800262 index += struct.pack("<II", offset, length)
263 data = index + data
Simon Glass9bc399e2012-12-11 14:36:10 -0800264
265 if compress:
266 if compress == 'lzo':
267 # Would be nice to just pipe here. but we don't have RunPipe().
268 fname = self.GetOutputFilename('data.tmp')
269 outname = self.GetOutputFilename('data.tmp.lzo')
270 if os.path.exists(outname):
271 os.remove(outname)
272 self.WriteFile(fname, data)
273 args = ['-9', fname]
274 self.Run('lzop', args)
275 data = self.ReadFile(outname)
276 else:
277 raise ValueError("Unknown compression method '%s'" % compress)
Gabe Black0f419b62013-01-10 22:05:00 -0800278 return data, offsets, lengths
Vic Yanga850b922012-08-11 14:08:43 +0800279
Simon Glass50883f92011-07-12 16:19:16 -0700280 def GetChromeosVersion(self):
Simon Glassc15a2cb2012-09-08 20:51:37 -0700281 """Returns the ChromeOS version string.
Simon Glass50883f92011-07-12 16:19:16 -0700282
283 This works by finding and executing the version script:
284
285 src/third_party/chromiumos-overlay/chromeos/config/chromeos_version.sh
286
287 Returns:
288 Version string in the form '0.14.726.2011_07_07_1635'
289
290 Raises:
291 CmdError: If the version script cannot be found, or is found but cannot
292 be executed.
293 """
294 version_script = os.path.join(self.cros_overlay_path, 'chromeos', 'config',
Simon Glassc15a2cb2012-09-08 20:51:37 -0700295 'chromeos_version.sh')
Simon Glass50883f92011-07-12 16:19:16 -0700296
297 if os.path.exists(version_script):
Simon Glassc15a2cb2012-09-08 20:51:37 -0700298 result = self.Run('sh', ['-c', '. %s >/dev/null; '
299 'echo ${CHROMEOS_VERSION_STRING}'
300 % version_script])
301 return result.strip()
Simon Glass50883f92011-07-12 16:19:16 -0700302 raise CmdError("Cannot find version script 'chromeos_version.sh'")
303
Simon Glass2343e8f2012-10-01 14:48:24 -0700304 def CheckTool(self, name, ebuild=None):
Simon Glass50883f92011-07-12 16:19:16 -0700305 """Check that the specified tool exists.
306
307 If it does not exist in the PATH, then generate a useful error message
308 indicating how to install the ebuild that contains the required tool.
309
310 Args:
Simon Glass2343e8f2012-10-01 14:48:24 -0700311 name: filename of tool to look for on path.
Simon Glass50883f92011-07-12 16:19:16 -0700312 ebuild: name of ebuild which should be emerged to install this tool,
313 or None if it is the same as the filename.
314
315 Raises:
316 CmdError(msg) if the tool is not found.
317 """
318 try:
Simon Glass2343e8f2012-10-01 14:48:24 -0700319 filename = name
Simon Glass50883f92011-07-12 16:19:16 -0700320 if filename in self._tools:
321 filename = self._tools[filename]
322 filename = self.Filename(filename)
323 self.Run('which', [filename])
Simon Glassc15a2cb2012-09-08 20:51:37 -0700324 except CmdError:
Simon Glass50883f92011-07-12 16:19:16 -0700325 raise CmdError("The '%s' utility was not found in your path. "
Simon Glassc15a2cb2012-09-08 20:51:37 -0700326 "Run the following command in \nyour chroot to install "
327 "it: sudo -E emerge %s" % (filename, ebuild or name))
Simon Glass50883f92011-07-12 16:19:16 -0700328
Simon Glassab344e32011-07-17 09:17:07 -0700329 def OutputSize(self, label, filename, level=cros_output.NOTICE):
330 """Display the filename and size of an object.
331
332 Args:
333 label: Label for this file.
334 filename: Filename to output.
335 level: Verbosity level to attach to this message
336 """
337 filename = self.Filename(filename)
338 size = os.stat(filename).st_size
Simon Glassc15a2cb2012-09-08 20:51:37 -0700339 self._out.DoOutput(level, '%s: %s; size: %d / %#x' %
340 (label, filename, size, size))
Simon Glassab344e32011-07-17 09:17:07 -0700341
Simon Glass951a2db2011-07-17 15:58:58 -0700342 def PrepareOutputDir(self, outdir, preserve=False):
343 """Select an output directory, ensuring it exists.
344
345 This either creates a temporary directory or checks that the one supplied
346 by the user is valid. For a temporary directory, it makes a note to
347 remove it later if required.
348
349 Args:
Vadim Bendebury2692ad02013-02-15 10:19:57 -0800350 outdir: a string, name of the output directory to use to store
351 intermediate and output files. If is None - create a temporary
352 directory.
353 preserve: a Boolean. If outdir above is None and preserve is False, the
354 created temporary directory will be destroyed on exit.
Simon Glass951a2db2011-07-17 15:58:58 -0700355 Raises:
356 OSError: If it cannot create the output directory.
357 """
Vadim Bendebury2692ad02013-02-15 10:19:57 -0800358 self.preserve_outdir = outdir or preserve
359 if outdir:
360 self.outdir = outdir
Simon Glass951a2db2011-07-17 15:58:58 -0700361 if not os.path.isdir(self.outdir):
362 try:
363 os.makedirs(self.outdir)
364 except OSError as err:
365 raise CmdError("Cannot make output directory '%s': '%s'" %
Vadim Bendebury2692ad02013-02-15 10:19:57 -0800366 (self.outdir, err.strerror))
Simon Glass951a2db2011-07-17 15:58:58 -0700367 else:
Vadim Bendebury2692ad02013-02-15 10:19:57 -0800368 self.outdir = tempfile.mkdtemp(prefix='cros-dev.')
369 self._out.Debug("Using temporary directory '%s'" % self.outdir)
Simon Glass951a2db2011-07-17 15:58:58 -0700370
371 def FinalizeOutputDir(self):
Vadim Bendebury2692ad02013-02-15 10:19:57 -0800372 """Tidy up: delete output directory if temporary and not preserved."""
373 if self.outdir and not self.preserve_outdir:
374 shutil.rmtree(self.outdir)
Simon Glass951a2db2011-07-17 15:58:58 -0700375 self._out.Debug("Deleted temporary directory '%s'" %
Vadim Bendebury2692ad02013-02-15 10:19:57 -0800376 self.outdir)
377 self.outdir = None
Simon Glass951a2db2011-07-17 15:58:58 -0700378
Simon Glassc15a2cb2012-09-08 20:51:37 -0700379 def GetOutputFilename(self, fname):
380 """Return a filename within the output directory.
381
382 Args:
383 fname: Filename to use for new file
384
385 Returns:
386 The full path of the filename, within the output directory
387 """
388 return os.path.join(self.outdir, fname)
389
390
David Jamesb0a15c22013-01-02 18:32:30 -0800391# pylint: disable=W0212
Simon Glassc15a2cb2012-09-08 20:51:37 -0700392class ToolsTests(unittest.TestCase):
393 """Unit tests for this module."""
394
395 def setUp(self):
396 self.out = cros_output.Output(False)
397 self.tools = Tools(self.out)
398
399 def MakeOutsideChroot(self, base):
400 tools = Tools(self.out)
401 tools.in_chroot = False
David Jamesb0a15c22013-01-02 18:32:30 -0800402 tools._SetRoot(base)
Simon Glassc15a2cb2012-09-08 20:51:37 -0700403 return tools
404
405 def testPaths(self):
406 tools = self.tools
407
Simon Glassc15a2cb2012-09-08 20:51:37 -0700408 self.assertTrue(os.path.isdir(os.path.join(tools._root, '.repo')))
409
410 def _testToolsPaths(self, base, tools):
411 """Common paths tests to run inside and outside chroot.
412
413 These tests are the same inside and outside the choot, so we put them in a
414 separate function.
415
416 Args:
417 base: Base directory to use for testing (contains the 'src' directory).
418 tools: Tools object to use.
419 """
420 self.assertEqual(tools._root, base[:-1])
421 self.assertEqual(tools.src_path, base + 'src')
422 self.assertEqual(tools.script_path, base + 'src/scripts')
423 self.assertEqual(tools.overlay_path, base + 'src/overlays')
424 self.assertEqual(tools.priv_overlay_path, base + 'src/private-overlays')
425 self.assertEqual(tools.third_party_path, base + 'src/third_party')
426 self.assertEqual(tools.cros_overlay_path, base +
427 'src/third_party/chromiumos-overlay')
428
429 def testSetRootInsideChroot(self):
430 """Inside the chroot, paths are slightly different from outside."""
431 tools = Tools(self.out)
David Jamesb0a15c22013-01-02 18:32:30 -0800432 tools.in_chroot = True
Simon Glassc15a2cb2012-09-08 20:51:37 -0700433
434 # Force our own path.
435 base = '/air/bridge/'
436 tools._SetRoot(base)
437
438 # We should get a full path from that without the trailing '/'.
439 self.assertEqual(tools.chroot_path, '/')
440 self.assertEqual(tools.board_path, '/build')
441 self._testToolsPaths(base, tools)
442
443 def testSetRootOutsideChroot(self):
444 """Pretend to be outside the chroot, and check that paths are correct."""
445
446 # Force our own path, outside the chroot.
447 base = '/spotty/light/'
448 tools = self.MakeOutsideChroot(base)
449
450 # We should get a full path from that without the trailing '/'.
David Jamesb0a15c22013-01-02 18:32:30 -0800451 self.assertEqual(tools.chroot_path, base + 'chroot')
452 self.assertEqual(tools.board_path, tools.chroot_path + '/build')
Simon Glassc15a2cb2012-09-08 20:51:37 -0700453 self._testToolsPaths(base, tools)
454
455 def _testToolsFilenames(self, tools):
456 """Common filename tests to run inside and outside chroot.
457
458 These tests are the same inside and outside the choot, so we put them in a
459 separate function.
460
461 Args:
462 tools: Tools object to use.
463 """
464 self.assertEqual(tools.Filename('/root/based/'),
465 '/root/based/')
466
467 # Try search paths in /bin and /ls.
468 tools.search_paths = ['/bin', '/lib']
469 file_in_bin = os.listdir('/bin')[0]
470 self.assertEqual(tools.Filename(file_in_bin), '/bin/%s' % file_in_bin)
471 file_in_lib = os.listdir('/lib')[0]
472 self.assertEqual(tools.Filename(file_in_lib), '/lib/%s' % file_in_lib)
473 self.assertEqual(tools.Filename('i-am-not-here'), 'i-am-not-here')
474
475 # Don't search for an empty file.
476 self.assertEqual(tools.Filename(''), '')
477
478 def testFilenameInsideChroot(self):
479 """Test that we can specify search paths and they work correctly.
480
481 Test search patches inside the chroot.
482 """
483 tools = Tools(self.out)
David Jamesb0a15c22013-01-02 18:32:30 -0800484 tools.in_chroot = True
Simon Glassc15a2cb2012-09-08 20:51:37 -0700485
486 # Force our own path.
487 base = '/air/bridge/'
488 tools._SetRoot(base)
489
490 self.assertEqual(tools.Filename('##/fred'), '/fred')
491 self.assertEqual(tools.Filename('##/just/a/short/dir/'),
492 '/just/a/short/dir/')
493
494 self._testToolsFilenames(tools)
495
496 def testFilenameOutsideChroot(self):
497 """Test that we can specify search paths and they work correctly.
498
499 Test search patches outside the chroot.
500 """
501 base = '/home/'
502 tools = self.MakeOutsideChroot(base)
503
David Jamesb0a15c22013-01-02 18:32:30 -0800504 self.assertEqual(tools.Filename('##/fred'), base + 'chroot/fred')
Simon Glassc15a2cb2012-09-08 20:51:37 -0700505 self.assertEqual(tools.Filename('##/just/a/short/dir/'),
David Jamesb0a15c22013-01-02 18:32:30 -0800506 base + 'chroot/just/a/short/dir/')
Simon Glassc15a2cb2012-09-08 20:51:37 -0700507
508 self._testToolsFilenames(tools)
509
510 def testReadWriteFile(self):
511 """Test our read/write utility functions."""
512 tools = Tools(self.out)
513 tools.PrepareOutputDir(None)
514 data = 'some context here' * 2
515
516 fname = tools.GetOutputFilename('bang')
517 tools.WriteFile(fname, data)
518
519 # Check that the file looks correct.
520 compare = tools.ReadFile(fname)
521 self.assertEqual(data, compare)
522
523 def testReadFileAndConcat(self):
524 """Test 'cat' of several files."""
525 tools = Tools(self.out)
526 tools.PrepareOutputDir(None)
527 file_list = ['one', 'empty', 'two', 'three', 'four']
528 out_list = [tools.GetOutputFilename(fname) for fname in file_list]
529 file_list[1] = '' # Empty the 'empty' file.
530 for upto in range(len(file_list)):
531 tools.WriteFile(out_list[upto], file_list[upto])
532
533 data, offset, length = tools.ReadFileAndConcat(out_list)
534 self.assertEqual(len(data), 20)
535 self.assertEqual(offset, [0, 4, 4, 8, 16])
536 self.assertEqual(length, [3, 0, 3, 5, 4])
537
538 def testGetChromeosVersion(self):
539 """Test for GetChromeosVersion() inside and outside chroot.
540
541 This function returns a string like '2893.0.2012_09_16_2219'.
542 """
543 tools = self.tools
544
Simon Glassc15a2cb2012-09-08 20:51:37 -0700545 re_version = re.compile('\d{4}.\d+.\d{4}_\d{2}_\d{2}_\d+')
546 self.assertTrue(re_version.match(tools.GetChromeosVersion()))
547
548 tools = Tools(self.out)
549
550 # Force our own path, outside the chroot. This should fail.
551 base = 'invalid-dir'
552 tools = self.MakeOutsideChroot(base)
553 tools.in_chroot = False
554 self.assertRaises(CmdError, tools.GetChromeosVersion)
555
556 def testCheckTool(self):
557 """Test for the CheckTool() method."""
558 tools = self.tools
559
560 tools.CheckTool('fdisk')
561 tools.CheckTool('gbb_utility')
562 self.assertRaises(CmdError, tools.CheckTool, 'non-existent-tool')
563 tools.CheckTool('fdisk')
564 self.assertRaises(CmdError, tools.CheckTool, '/usr/bin/fdisk')
565
566 def testRun(self):
567 """Test for the Run() method."""
568 tools = self.tools
569
570 # Ask fdisk for its version - this utility must be in the chroot.
571 re_fdisk = re.compile('fdisk \(util-linux .*\)')
572 self.assertTrue(re_fdisk.match(tools.Run('fdisk', ['-v'])))
573
574 # We need sudo for looking at disks.
575 self.assertEqual(tools.Run('fdisk', ['-l', '/dev/sda']),
576 'Cannot open /dev/sda\n')
577 out = tools.Run('fdisk', ['-l', '/dev/sda'], sudo=True)
578
579 # Don't look at the specific output, but it will have > 5 lines.
580 self.assertTrue(len(out.splitlines()) > 5)
581
582 self.assertEqual(tools.Run('pwd', [], cwd='/tmp'), '/tmp\n')
583
584 def testOutputDir(self):
585 """Test output directory creation and deletion."""
586 tools = self.tools
587
588 # First check basic operation, creating and deleting a tmpdir.
589 tools.PrepareOutputDir(None)
590 fname = tools.GetOutputFilename('fred')
591 tools.WriteFile(fname, 'You are old, Father William, the young man said')
592 dirname = tools.outdir
593 tools.FinalizeOutputDir()
594 self.assertFalse(os.path.exists(fname))
595 self.assertFalse(os.path.exists(dirname))
596
597 # Try preserving it.
598 tools.PrepareOutputDir(None, True)
599 fname = tools.GetOutputFilename('fred')
600 tools.WriteFile(fname, 'and your hair has become very white')
601 dirname = tools.outdir
602 tools.FinalizeOutputDir()
603 self.assertTrue(os.path.exists(fname))
604 self.assertTrue(os.path.exists(dirname))
605 shutil.rmtree(dirname)
606
607 # Use our own directory, which is always preserved.
608 testdir = '/tmp/tools-test.test'
609 tools.PrepareOutputDir(testdir)
610 fname = tools.GetOutputFilename('fred')
611 tools.WriteFile(fname, 'and yet you incessantly stand on your head')
612 dirname = tools.outdir
613 tools.FinalizeOutputDir()
614 self.assertTrue(os.path.exists(fname))
615 self.assertTrue(os.path.exists(dirname))
616 shutil.rmtree(dirname)
617
618 # Try creating an invalid directory.
619 testdir = '/sys/cannot/do/this/here'
620 self.assertRaises(CmdError, tools.PrepareOutputDir, testdir)
621 fname = tools.GetOutputFilename('fred')
622 self.assertRaises(IOError, tools.WriteFile, fname,
623 'do you think at your age it is right?')
624 dirname = tools.outdir
625 tools.FinalizeOutputDir()
626
627 def _OutputMock(self, level, msg, color=None):
628 self._level = level
629 self._msg = msg
630 self._color = color
631
632 def testOutputSize(self):
633 """Test for OutputSize() function."""
634 tools = self.tools
635
636 # Rather than mocks, use a special Output object.
637 out = tools._out
638 out._Output = self._OutputMock
639
640 tools.PrepareOutputDir(None)
641 fname = tools.GetOutputFilename('fred')
642 text_string = 'test of output size'
643 tools.WriteFile(fname, text_string)
644
645 re_fname = re.compile('fred')
646 re_size = re.compile('.*size: (\d*)')
647
648 tools.OutputSize('first', fname, level=cros_output.ERROR)
649 self.assertEqual(self._level, cros_output.ERROR)
650 self.assertTrue(re_fname.search(self._msg))
651 self.assertEqual(self._color, None)
652
653 # Check the default level, and that the filename length is given.
654 tools.OutputSize('second', fname)
655 self.assertEqual(self._level, cros_output.NOTICE)
656 self.assertTrue(re_fname.search(self._msg))
657 self.assertEqual(self._color, None)
658 m = re_size.match(self._msg)
659 self.assertEqual(m.group(1), str(len(text_string)))
660
661 tools.FinalizeOutputDir()
662
663
664def _Test(argv):
Simon Glass50883f92011-07-12 16:19:16 -0700665 """Run any built-in tests."""
Simon Glassc15a2cb2012-09-08 20:51:37 -0700666 unittest.main(argv=argv)
Vadim Bendebury9ddeee12013-02-14 15:24:17 -0800667 assert doctest.testmod().failed == 0
Simon Glassc15a2cb2012-09-08 20:51:37 -0700668
Simon Glass50883f92011-07-12 16:19:16 -0700669
670def main():
671 """Main function for tools.
672
673 We provide a way to call a few of our useful functions.
674
675 TODO(sjg) Move into the Chromite libraries when these are ready.
676 """
677 parser = optparse.OptionParser()
678 parser.add_option('-v', '--verbosity', dest='verbosity', default=1,
Simon Glassc15a2cb2012-09-08 20:51:37 -0700679 type='int',
680 help='Control verbosity: 0=silent, 1=progress, 3=full, '
681 '4=debug')
Simon Glass50883f92011-07-12 16:19:16 -0700682
683 help_str = '%s [options] cmd [args]\n\nAvailable commands:\n' % sys.argv[0]
684 help_str += '\tchromeos-version\tDisplay Chrome OS version'
685 parser.usage = help_str
686
687 (options, args) = parser.parse_args(sys.argv)
688 args = args[1:]
689
Simon Glassab344e32011-07-17 09:17:07 -0700690 out = cros_output.Output(options.verbosity)
691 tools = Tools(out)
Simon Glass50883f92011-07-12 16:19:16 -0700692 if not args:
693 parser.error('No command provided')
694 elif args[0] == 'chromeos-version':
695 print tools.GetChromeosVersion()
696 else:
697 parser.error("Unknown command '%s'" % args[0])
698
699if __name__ == '__main__':
Simon Glassc15a2cb2012-09-08 20:51:37 -0700700 if sys.argv[1:2] == ['--test']:
701 _Test([sys.argv[0]] + sys.argv[2:])
Simon Glass50883f92011-07-12 16:19:16 -0700702 else:
703 main()