blob: ff11c36057688927ab442ac22ec9f3f578d97707 [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:
Yu-Ju Hong196ff8c2014-01-30 14:15:36 -0800202 result = cros_build_lib.RunCommand(
203 cmd, cwd=cwd, print_cmd=self._out.verbose > 3, capture_output=True,
David Jamesb0a15c22013-01-02 18:32:30 -0800204 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.
Yu-Ju Hong196ff8c2014-01-30 14:15:36 -0800368
Simon Glass951a2db2011-07-17 15:58:58 -0700369 Raises:
370 OSError: If it cannot create the output directory.
371 """
Vadim Bendebury2692ad02013-02-15 10:19:57 -0800372 self.preserve_outdir = outdir or preserve
373 if outdir:
374 self.outdir = outdir
Simon Glass951a2db2011-07-17 15:58:58 -0700375 if not os.path.isdir(self.outdir):
376 try:
377 os.makedirs(self.outdir)
378 except OSError as err:
379 raise CmdError("Cannot make output directory '%s': '%s'" %
Vadim Bendebury2692ad02013-02-15 10:19:57 -0800380 (self.outdir, err.strerror))
Simon Glass951a2db2011-07-17 15:58:58 -0700381 else:
Vadim Bendebury2692ad02013-02-15 10:19:57 -0800382 self.outdir = tempfile.mkdtemp(prefix='cros-dev.')
383 self._out.Debug("Using temporary directory '%s'" % self.outdir)
Simon Glass951a2db2011-07-17 15:58:58 -0700384
385 def FinalizeOutputDir(self):
Vadim Bendebury2692ad02013-02-15 10:19:57 -0800386 """Tidy up: delete output directory if temporary and not preserved."""
387 if self.outdir and not self.preserve_outdir:
388 shutil.rmtree(self.outdir)
Simon Glass951a2db2011-07-17 15:58:58 -0700389 self._out.Debug("Deleted temporary directory '%s'" %
Vadim Bendebury2692ad02013-02-15 10:19:57 -0800390 self.outdir)
391 self.outdir = None
Simon Glass951a2db2011-07-17 15:58:58 -0700392
Simon Glassc15a2cb2012-09-08 20:51:37 -0700393 def GetOutputFilename(self, fname):
394 """Return a filename within the output directory.
395
396 Args:
397 fname: Filename to use for new file
398
399 Returns:
400 The full path of the filename, within the output directory
401 """
402 return os.path.join(self.outdir, fname)
403
404
David Jamesb0a15c22013-01-02 18:32:30 -0800405# pylint: disable=W0212
Simon Glassc15a2cb2012-09-08 20:51:37 -0700406class ToolsTests(unittest.TestCase):
407 """Unit tests for this module."""
408
409 def setUp(self):
410 self.out = cros_output.Output(False)
411 self.tools = Tools(self.out)
412
413 def MakeOutsideChroot(self, base):
414 tools = Tools(self.out)
415 tools.in_chroot = False
David Jamesb0a15c22013-01-02 18:32:30 -0800416 tools._SetRoot(base)
Simon Glassc15a2cb2012-09-08 20:51:37 -0700417 return tools
418
419 def testPaths(self):
420 tools = self.tools
421
Simon Glassc15a2cb2012-09-08 20:51:37 -0700422 self.assertTrue(os.path.isdir(os.path.join(tools._root, '.repo')))
423
424 def _testToolsPaths(self, base, tools):
425 """Common paths tests to run inside and outside chroot.
426
427 These tests are the same inside and outside the choot, so we put them in a
428 separate function.
429
430 Args:
431 base: Base directory to use for testing (contains the 'src' directory).
432 tools: Tools object to use.
433 """
434 self.assertEqual(tools._root, base[:-1])
435 self.assertEqual(tools.src_path, base + 'src')
436 self.assertEqual(tools.script_path, base + 'src/scripts')
437 self.assertEqual(tools.overlay_path, base + 'src/overlays')
438 self.assertEqual(tools.priv_overlay_path, base + 'src/private-overlays')
439 self.assertEqual(tools.third_party_path, base + 'src/third_party')
440 self.assertEqual(tools.cros_overlay_path, base +
441 'src/third_party/chromiumos-overlay')
442
443 def testSetRootInsideChroot(self):
444 """Inside the chroot, paths are slightly different from outside."""
445 tools = Tools(self.out)
David Jamesb0a15c22013-01-02 18:32:30 -0800446 tools.in_chroot = True
Simon Glassc15a2cb2012-09-08 20:51:37 -0700447
448 # Force our own path.
449 base = '/air/bridge/'
450 tools._SetRoot(base)
451
452 # We should get a full path from that without the trailing '/'.
453 self.assertEqual(tools.chroot_path, '/')
454 self.assertEqual(tools.board_path, '/build')
455 self._testToolsPaths(base, tools)
456
457 def testSetRootOutsideChroot(self):
458 """Pretend to be outside the chroot, and check that paths are correct."""
459
460 # Force our own path, outside the chroot.
461 base = '/spotty/light/'
462 tools = self.MakeOutsideChroot(base)
463
464 # We should get a full path from that without the trailing '/'.
David Jamesb0a15c22013-01-02 18:32:30 -0800465 self.assertEqual(tools.chroot_path, base + 'chroot')
466 self.assertEqual(tools.board_path, tools.chroot_path + '/build')
Simon Glassc15a2cb2012-09-08 20:51:37 -0700467 self._testToolsPaths(base, tools)
468
469 def _testToolsFilenames(self, tools):
470 """Common filename tests to run inside and outside chroot.
471
472 These tests are the same inside and outside the choot, so we put them in a
473 separate function.
474
475 Args:
476 tools: Tools object to use.
477 """
478 self.assertEqual(tools.Filename('/root/based/'),
479 '/root/based/')
480
481 # Try search paths in /bin and /ls.
482 tools.search_paths = ['/bin', '/lib']
483 file_in_bin = os.listdir('/bin')[0]
484 self.assertEqual(tools.Filename(file_in_bin), '/bin/%s' % file_in_bin)
485 file_in_lib = os.listdir('/lib')[0]
486 self.assertEqual(tools.Filename(file_in_lib), '/lib/%s' % file_in_lib)
487 self.assertEqual(tools.Filename('i-am-not-here'), 'i-am-not-here')
488
489 # Don't search for an empty file.
490 self.assertEqual(tools.Filename(''), '')
491
492 def testFilenameInsideChroot(self):
493 """Test that we can specify search paths and they work correctly.
494
495 Test search patches inside the chroot.
496 """
497 tools = Tools(self.out)
David Jamesb0a15c22013-01-02 18:32:30 -0800498 tools.in_chroot = True
Simon Glassc15a2cb2012-09-08 20:51:37 -0700499
500 # Force our own path.
501 base = '/air/bridge/'
502 tools._SetRoot(base)
503
504 self.assertEqual(tools.Filename('##/fred'), '/fred')
505 self.assertEqual(tools.Filename('##/just/a/short/dir/'),
506 '/just/a/short/dir/')
507
508 self._testToolsFilenames(tools)
509
510 def testFilenameOutsideChroot(self):
511 """Test that we can specify search paths and they work correctly.
512
513 Test search patches outside the chroot.
514 """
515 base = '/home/'
516 tools = self.MakeOutsideChroot(base)
517
David Jamesb0a15c22013-01-02 18:32:30 -0800518 self.assertEqual(tools.Filename('##/fred'), base + 'chroot/fred')
Simon Glassc15a2cb2012-09-08 20:51:37 -0700519 self.assertEqual(tools.Filename('##/just/a/short/dir/'),
David Jamesb0a15c22013-01-02 18:32:30 -0800520 base + 'chroot/just/a/short/dir/')
Simon Glassc15a2cb2012-09-08 20:51:37 -0700521
522 self._testToolsFilenames(tools)
523
524 def testReadWriteFile(self):
525 """Test our read/write utility functions."""
526 tools = Tools(self.out)
527 tools.PrepareOutputDir(None)
528 data = 'some context here' * 2
529
530 fname = tools.GetOutputFilename('bang')
531 tools.WriteFile(fname, data)
532
533 # Check that the file looks correct.
534 compare = tools.ReadFile(fname)
535 self.assertEqual(data, compare)
536
537 def testReadFileAndConcat(self):
538 """Test 'cat' of several files."""
539 tools = Tools(self.out)
540 tools.PrepareOutputDir(None)
541 file_list = ['one', 'empty', 'two', 'three', 'four']
542 out_list = [tools.GetOutputFilename(fname) for fname in file_list]
543 file_list[1] = '' # Empty the 'empty' file.
544 for upto in range(len(file_list)):
545 tools.WriteFile(out_list[upto], file_list[upto])
546
547 data, offset, length = tools.ReadFileAndConcat(out_list)
548 self.assertEqual(len(data), 20)
549 self.assertEqual(offset, [0, 4, 4, 8, 16])
550 self.assertEqual(length, [3, 0, 3, 5, 4])
551
552 def testGetChromeosVersion(self):
553 """Test for GetChromeosVersion() inside and outside chroot.
554
555 This function returns a string like '2893.0.2012_09_16_2219'.
556 """
557 tools = self.tools
558
David James13200f02013-02-20 11:41:06 -0800559 re_version_pattern = '^\d{4}.\d+.(?:\d{4}_\d{2}_\d{2}_\d+|\d+)$'
Gilad Arnold8047ff42013-02-20 08:50:14 -0800560 re_version = re.compile(re_version_pattern)
561 reported_version = tools.GetChromeosVersion()
562 self.assertTrue(re_version.match(reported_version),
563 msg='%s !~= %s' % (reported_version, re_version_pattern))
Simon Glassc15a2cb2012-09-08 20:51:37 -0700564
565 tools = Tools(self.out)
566
567 # Force our own path, outside the chroot. This should fail.
568 base = 'invalid-dir'
569 tools = self.MakeOutsideChroot(base)
570 tools.in_chroot = False
571 self.assertRaises(CmdError, tools.GetChromeosVersion)
572
573 def testCheckTool(self):
574 """Test for the CheckTool() method."""
575 tools = self.tools
576
577 tools.CheckTool('fdisk')
578 tools.CheckTool('gbb_utility')
579 self.assertRaises(CmdError, tools.CheckTool, 'non-existent-tool')
580 tools.CheckTool('fdisk')
581 self.assertRaises(CmdError, tools.CheckTool, '/usr/bin/fdisk')
582
583 def testRun(self):
584 """Test for the Run() method."""
585 tools = self.tools
586
587 # Ask fdisk for its version - this utility must be in the chroot.
Olof Johansson3399a762014-01-15 16:15:58 -0800588 re_fdisk = re.compile('fdisk .*util-linux .*')
Simon Glassc15a2cb2012-09-08 20:51:37 -0700589 self.assertTrue(re_fdisk.match(tools.Run('fdisk', ['-v'])))
590
591 # We need sudo for looking at disks.
Simon Glassc15a2cb2012-09-08 20:51:37 -0700592 out = tools.Run('fdisk', ['-l', '/dev/sda'], sudo=True)
593
594 # Don't look at the specific output, but it will have > 5 lines.
595 self.assertTrue(len(out.splitlines()) > 5)
596
597 self.assertEqual(tools.Run('pwd', [], cwd='/tmp'), '/tmp\n')
598
599 def testOutputDir(self):
600 """Test output directory creation and deletion."""
601 tools = self.tools
602
603 # First check basic operation, creating and deleting a tmpdir.
604 tools.PrepareOutputDir(None)
605 fname = tools.GetOutputFilename('fred')
606 tools.WriteFile(fname, 'You are old, Father William, the young man said')
607 dirname = tools.outdir
608 tools.FinalizeOutputDir()
609 self.assertFalse(os.path.exists(fname))
610 self.assertFalse(os.path.exists(dirname))
611
612 # Try preserving it.
613 tools.PrepareOutputDir(None, True)
614 fname = tools.GetOutputFilename('fred')
615 tools.WriteFile(fname, 'and your hair has become very white')
616 dirname = tools.outdir
617 tools.FinalizeOutputDir()
618 self.assertTrue(os.path.exists(fname))
619 self.assertTrue(os.path.exists(dirname))
620 shutil.rmtree(dirname)
621
622 # Use our own directory, which is always preserved.
623 testdir = '/tmp/tools-test.test'
624 tools.PrepareOutputDir(testdir)
625 fname = tools.GetOutputFilename('fred')
626 tools.WriteFile(fname, 'and yet you incessantly stand on your head')
627 dirname = tools.outdir
628 tools.FinalizeOutputDir()
629 self.assertTrue(os.path.exists(fname))
630 self.assertTrue(os.path.exists(dirname))
631 shutil.rmtree(dirname)
632
633 # Try creating an invalid directory.
634 testdir = '/sys/cannot/do/this/here'
635 self.assertRaises(CmdError, tools.PrepareOutputDir, testdir)
636 fname = tools.GetOutputFilename('fred')
637 self.assertRaises(IOError, tools.WriteFile, fname,
638 'do you think at your age it is right?')
639 dirname = tools.outdir
640 tools.FinalizeOutputDir()
641
642 def _OutputMock(self, level, msg, color=None):
643 self._level = level
644 self._msg = msg
645 self._color = color
646
647 def testOutputSize(self):
648 """Test for OutputSize() function."""
649 tools = self.tools
650
651 # Rather than mocks, use a special Output object.
652 out = tools._out
653 out._Output = self._OutputMock
654
655 tools.PrepareOutputDir(None)
656 fname = tools.GetOutputFilename('fred')
657 text_string = 'test of output size'
658 tools.WriteFile(fname, text_string)
659
660 re_fname = re.compile('fred')
661 re_size = re.compile('.*size: (\d*)')
662
663 tools.OutputSize('first', fname, level=cros_output.ERROR)
664 self.assertEqual(self._level, cros_output.ERROR)
665 self.assertTrue(re_fname.search(self._msg))
666 self.assertEqual(self._color, None)
667
668 # Check the default level, and that the filename length is given.
669 tools.OutputSize('second', fname)
670 self.assertEqual(self._level, cros_output.NOTICE)
671 self.assertTrue(re_fname.search(self._msg))
672 self.assertEqual(self._color, None)
673 m = re_size.match(self._msg)
674 self.assertEqual(m.group(1), str(len(text_string)))
675
676 tools.FinalizeOutputDir()
677
678
679def _Test(argv):
Simon Glass50883f92011-07-12 16:19:16 -0700680 """Run any built-in tests."""
Simon Glassc15a2cb2012-09-08 20:51:37 -0700681 unittest.main(argv=argv)
Vadim Bendebury9ddeee12013-02-14 15:24:17 -0800682 assert doctest.testmod().failed == 0
Simon Glassc15a2cb2012-09-08 20:51:37 -0700683
Simon Glass50883f92011-07-12 16:19:16 -0700684
685def main():
686 """Main function for tools.
687
688 We provide a way to call a few of our useful functions.
689
690 TODO(sjg) Move into the Chromite libraries when these are ready.
691 """
692 parser = optparse.OptionParser()
693 parser.add_option('-v', '--verbosity', dest='verbosity', default=1,
Simon Glassc15a2cb2012-09-08 20:51:37 -0700694 type='int',
695 help='Control verbosity: 0=silent, 1=progress, 3=full, '
696 '4=debug')
Simon Glass50883f92011-07-12 16:19:16 -0700697
698 help_str = '%s [options] cmd [args]\n\nAvailable commands:\n' % sys.argv[0]
699 help_str += '\tchromeos-version\tDisplay Chrome OS version'
700 parser.usage = help_str
701
702 (options, args) = parser.parse_args(sys.argv)
703 args = args[1:]
704
Simon Glassab344e32011-07-17 09:17:07 -0700705 out = cros_output.Output(options.verbosity)
706 tools = Tools(out)
Simon Glass50883f92011-07-12 16:19:16 -0700707 if not args:
708 parser.error('No command provided')
709 elif args[0] == 'chromeos-version':
710 print tools.GetChromeosVersion()
711 else:
712 parser.error("Unknown command '%s'" % args[0])
713
714if __name__ == '__main__':
Simon Glassc15a2cb2012-09-08 20:51:37 -0700715 if sys.argv[1:2] == ['--test']:
716 _Test([sys.argv[0]] + sys.argv[2:])
Simon Glass50883f92011-07-12 16:19:16 -0700717 else:
718 main()