• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/env python3
2# SPDX-License-Identifier: GPL-2.0+
3
4# Copyright (c) 2016 Google, Inc
5# Written by Simon Glass <sjg@chromium.org>
6#
7# Creates binary images from input files controlled by a description
8#
9
10"""See README for more information"""
11
12from __future__ import print_function
13
14from distutils.sysconfig import get_python_lib
15import glob
16import multiprocessing
17import os
18import site
19import sys
20import traceback
21import unittest
22
23# Bring in the patman and dtoc libraries (but don't override the first path
24# in PYTHONPATH)
25our_path = os.path.dirname(os.path.realpath(__file__))
26for dirname in ['../patman', '../dtoc', '..', '../concurrencytest']:
27    sys.path.insert(2, os.path.join(our_path, dirname))
28
29# Bring in the libfdt module
30sys.path.insert(2, 'scripts/dtc/pylibfdt')
31sys.path.insert(2, os.path.join(our_path,
32                '../../build-sandbox_spl/scripts/dtc/pylibfdt'))
33
34# When running under python-coverage on Ubuntu 16.04, the dist-packages
35# directories are dropped from the python path. Add them in so that we can find
36# the elffile module. We could use site.getsitepackages() here but unfortunately
37# that is not available in a virtualenv.
38sys.path.append(get_python_lib())
39
40import cmdline
41import command
42use_concurrent = True
43try:
44    from concurrencytest import ConcurrentTestSuite, fork_for_tests
45except:
46    use_concurrent = False
47import control
48import test_util
49
50def RunTests(debug, verbosity, processes, test_preserve_dirs, args, toolpath):
51    """Run the functional tests and any embedded doctests
52
53    Args:
54        debug: True to enable debugging, which shows a full stack trace on error
55        verbosity: Verbosity level to use
56        test_preserve_dirs: True to preserve the input directory used by tests
57            so that it can be examined afterwards (only useful for debugging
58            tests). If a single test is selected (in args[0]) it also preserves
59            the output directory for this test. Both directories are displayed
60            on the command line.
61        processes: Number of processes to use to run tests (None=same as #CPUs)
62        args: List of positional args provided to binman. This can hold a test
63            name to execute (as in 'binman test testSections', for example)
64        toolpath: List of paths to use for tools
65    """
66    import cbfs_util_test
67    import elf_test
68    import entry_test
69    import fdt_test
70    import ftest
71    import image_test
72    import test
73    import doctest
74
75    result = unittest.TestResult()
76    for module in []:
77        suite = doctest.DocTestSuite(module)
78        suite.run(result)
79
80    sys.argv = [sys.argv[0]]
81    if debug:
82        sys.argv.append('-D')
83    if verbosity:
84        sys.argv.append('-v%d' % verbosity)
85    if toolpath:
86        for path in toolpath:
87            sys.argv += ['--toolpath', path]
88
89    # Run the entry tests first ,since these need to be the first to import the
90    # 'entry' module.
91    test_name = args and args[0] or None
92    suite = unittest.TestSuite()
93    loader = unittest.TestLoader()
94    for module in (entry_test.TestEntry, ftest.TestFunctional, fdt_test.TestFdt,
95                   elf_test.TestElf, image_test.TestImage,
96                   cbfs_util_test.TestCbfs):
97        # Test the test module about our arguments, if it is interested
98        if hasattr(module, 'setup_test_args'):
99            setup_test_args = getattr(module, 'setup_test_args')
100            setup_test_args(preserve_indir=test_preserve_dirs,
101                preserve_outdirs=test_preserve_dirs and test_name is not None,
102                toolpath=toolpath, verbosity=verbosity)
103        if test_name:
104            try:
105                suite.addTests(loader.loadTestsFromName(test_name, module))
106            except AttributeError:
107                continue
108        else:
109            suite.addTests(loader.loadTestsFromTestCase(module))
110    if use_concurrent and processes != 1:
111        concurrent_suite = ConcurrentTestSuite(suite,
112                fork_for_tests(processes or multiprocessing.cpu_count()))
113        concurrent_suite.run(result)
114    else:
115        suite.run(result)
116
117    # Remove errors which just indicate a missing test. Since Python v3.5 If an
118    # ImportError or AttributeError occurs while traversing name then a
119    # synthetic test that raises that error when run will be returned. These
120    # errors are included in the errors accumulated by result.errors.
121    if test_name:
122        errors = []
123        for test, err in result.errors:
124            if ("has no attribute '%s'" % test_name) not in err:
125                errors.append((test, err))
126            result.testsRun -= 1
127        result.errors = errors
128
129    print(result)
130    for test, err in result.errors:
131        print(test.id(), err)
132    for test, err in result.failures:
133        print(err, result.failures)
134    if result.skipped:
135        print('%d binman test%s SKIPPED:' %
136              (len(result.skipped), 's' if len(result.skipped) > 1 else ''))
137        for skip_info in result.skipped:
138            print('%s: %s' % (skip_info[0], skip_info[1]))
139    if result.errors or result.failures:
140        print('binman tests FAILED')
141        return 1
142    return 0
143
144def GetEntryModules(include_testing=True):
145    """Get a set of entry class implementations
146
147    Returns:
148        Set of paths to entry class filenames
149    """
150    glob_list = glob.glob(os.path.join(our_path, 'etype/*.py'))
151    return set([os.path.splitext(os.path.basename(item))[0]
152                for item in glob_list
153                if include_testing or '_testing' not in item])
154
155def RunTestCoverage():
156    """Run the tests and check that we get 100% coverage"""
157    glob_list = GetEntryModules(False)
158    all_set = set([os.path.splitext(os.path.basename(item))[0]
159                   for item in glob_list if '_testing' not in item])
160    test_util.RunTestCoverage('tools/binman/binman.py', None,
161            ['*test*', '*binman.py', 'tools/patman/*', 'tools/dtoc/*'],
162            args.build_dir, all_set)
163
164def RunBinman(args):
165    """Main entry point to binman once arguments are parsed
166
167    Args:
168        args: Command line arguments Namespace object
169    """
170    ret_code = 0
171
172    if not args.debug:
173        sys.tracebacklimit = 0
174
175    if args.cmd == 'test':
176        if args.test_coverage:
177            RunTestCoverage()
178        else:
179            ret_code = RunTests(args.debug, args.verbosity, args.processes,
180                                args.test_preserve_dirs, args.tests,
181                                args.toolpath)
182
183    elif args.cmd == 'entry-docs':
184        control.WriteEntryDocs(GetEntryModules())
185
186    else:
187        try:
188            ret_code = control.Binman(args)
189        except Exception as e:
190            print('binman: %s' % e)
191            if args.debug:
192                print()
193                traceback.print_exc()
194            ret_code = 1
195    return ret_code
196
197
198if __name__ == "__main__":
199    args = cmdline.ParseArgs(sys.argv[1:])
200
201    ret_code = RunBinman(args)
202    sys.exit(ret_code)
203