source: rtems-tools/rtemstoolkit/rtems.py @ 7e5cdea

Last change on this file since 7e5cdea was 7e5cdea, checked in by Chris Johns <chrisj@…>, on Nov 23, 2018 at 4:02:52 AM

rtemstoolkit: Add unit testing for the python modules

  • Add support to run the unit tests for the rtemstoolkit python modules from waf. Enter './waf test' for the tests to be run on python2 and python3.
  • Update the importing of rtemstoolkit modules to the standard method which works on python2 and python3.
  • Update the README.
  • Property mode set to 100755
File size: 17.7 KB
Line 
1#
2# RTEMS Tools Project (http://www.rtems.org/)
3# Copyright 2016-2018 Chris Johns (chrisj@rtems.org)
4# All rights reserved.
5#
6# This file is part of the RTEMS Tools package in 'rtems-tools'.
7#
8# Redistribution and use in source and binary forms, with or without
9# modification, are permitted provided that the following conditions are met:
10#
11# 1. Redistributions of source code must retain the above copyright notice,
12# this list of conditions and the following disclaimer.
13#
14# 2. Redistributions in binary form must reproduce the above copyright notice,
15# this list of conditions and the following disclaimer in the documentation
16# and/or other materials provided with the distribution.
17#
18# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
22# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
23# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
24# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
25# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
26# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
27# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
28# POSSIBILITY OF SUCH DAMAGE.
29#
30
31from __future__ import print_function
32
33import copy
34import os
35import re
36import sys
37import textwrap
38
39from rtemstoolkit import configuration as configuration_
40from rtemstoolkit import error
41from rtemstoolkit import path
42from rtemstoolkit import textbox
43
44#
45# The default path we install RTEMS under
46#
47_prefix_path = '/opt/rtems'
48
49def default_prefix():
50    from rtemstoolkit import version
51    return path.join(_prefix_path, version.version())
52
53def clean_windows_path():
54    '''On Windows MSYS2 prepends a path to itself to the environment path. This
55    means the RTEMS specific automake is not found and which breaks the
56    bootstrap. We need to remove the prepended path. Also remove any ACLOCAL
57    paths from the environment.
58
59    '''
60    if os.name == 'nt':
61        cspath = os.environ['PATH'].split(os.pathsep)
62        if 'msys' in cspath[0] and cspath[0].endswith('bin'):
63            os.environ['PATH'] = os.pathsep.join(cspath[1:])
64
65def configuration_path(prog = None):
66    '''Return the path the configuration data path for RTEMS. The path is relative
67    to the installed executable. Mangage the installed package and the in source
68    tree when running from within the rtems-tools repo.
69    Note:
70     1. This code assumes the executable is wrapped and not using 'env'.
71     2. Ok to directly call os.path.
72    '''
73    if prog is None:
74        if len(sys.argv) == 1:
75            exec_name = sys.argv[0]
76        else:
77            exec_name = sys.argv[1]
78    else:
79        exec_name = prog
80    exec_name = os.path.abspath(exec_name)
81    for top in [os.path.dirname(os.path.dirname(exec_name)),
82                os.path.dirname(exec_name)]:
83        config_path = path.join(top, 'share', 'rtems', 'config')
84        if path.exists(config_path):
85            break
86        config_path = path.join(top, 'config')
87        if path.exists(config_path):
88            break
89        config_path = None
90    return config_path
91
92def configuration_file(config, prog = None):
93    '''Return the path to a configuration file for RTEMS. The path is relative to
94    the installed executable or we are testing and running from within the
95    rtems-tools repo.
96
97    '''
98    return path.join(configuration_path(prog = prog), config)
99
100def bsp_configuration_file(prog = None):
101    '''Return the path to the BSP configuration file for RTEMS. The path is
102    relative to the installed executable or we are testing and running from
103    within the rtems-tools repo.
104
105    '''
106    return configuration_file('rtems-bsps.ini', prog = prog)
107
108class configuration:
109
110    def __init__(self):
111        self.config = configuration_.configuration()
112        self.archs = { }
113        self.profiles = { }
114
115    def __str__(self):
116        s = self.name + os.linesep
117        s += 'Archs:' + os.linesep + \
118             pprint.pformat(self.archs, indent = 1, width = 80) + os.linesep
119        s += 'Profiles:' + os.linesep + \
120             pprint.pformat(self.profiles, indent = 1, width = 80) + os.linesep
121        return s
122
123    def _build_options(self, build, nesting = 0):
124        if ':' in build:
125            section, name = build.split(':', 1)
126            opts = [self.config.get_item(section, name)]
127            return opts
128        builds = self.builds_['builds']
129        if build not in builds:
130            raise error.general('build %s not found' % (build))
131        if nesting > 20:
132            raise error.general('nesting build %s' % (build))
133        options = []
134        for option in self.builds_['builds'][build]:
135            if ':' in option:
136                section, name = option.split(':', 1)
137                opts = [self.config.get_item(section, name)]
138            else:
139                opts = self._build_options(option, nesting + 1)
140            for opt in opts:
141                if opt not in options:
142                    options += [opt]
143        return options
144
145    def load(self, name, build):
146        self.config.load(name)
147        archs = []
148        self.profiles['profiles'] = \
149            self.config.comma_list('profiles', 'profiles', err = False)
150        if len(self.profiles['profiles']) == 0:
151            self.profiles['profiles'] = ['tier-%d' % (t) for t in range(1,4)]
152        for p in self.profiles['profiles']:
153            profile = {}
154            profile['name'] = p
155            profile['archs'] = self.config.comma_list(profile['name'], 'archs', err = False)
156            archs += profile['archs']
157            for arch in profile['archs']:
158                bsps = 'bsps_%s' % (arch)
159                profile[bsps] = self.config.comma_list(profile['name'], bsps)
160            self.profiles[profile['name']] = profile
161        invalid_chars = re.compile(r'[^a-zA-Z0-9_-]')
162        for a in set(archs):
163            if len(invalid_chars.findall(a)) != 0:
164                raise error.general('invalid character(s) in arch name: %s' % (a))
165            arch = {}
166            arch['excludes'] = {}
167            for exclude in self.config.comma_list(a, 'exclude', err = False):
168                arch['excludes'][exclude] = ['all']
169            for i in self.config.get_items(a, False):
170                if i[0].startswith('exclude-'):
171                    exclude = i[0][len('exclude-'):]
172                    if exclude not in arch['excludes']:
173                        arch['excludes'][exclude] = []
174                    arch['excludes'][exclude] += \
175                        sorted(set([b.strip() for b in i[1].split(',')]))
176            arch['bsps'] = self.config.comma_list(a, 'bsps', err = False)
177            for b in arch['bsps']:
178                if len(invalid_chars.findall(b)) != 0:
179                    raise error.general('invalid character(s) in BSP name: %s' % (b))
180                arch[b] = {}
181                arch[b]['bspopts'] = \
182                    self.config.comma_list(a, 'bspopts_%s' % (b), err = False)
183            self.archs[a] = arch
184        builds = {}
185        builds['default'] = self.config.get_item('builds', 'default')
186        if build is None:
187            build = builds['default']
188        builds['config'] = { }
189        for config in self.config.get_items('config'):
190            builds['config'][config[0]] = config[1]
191        builds['build'] = build
192        builds_ = self.config.get_item_names('builds')
193        builds['builds'] = {}
194        for build in builds_:
195            build_builds = self.config.comma_list('builds', build)
196            has_config = False
197            has_build = False
198            for b in build_builds:
199                if ':' in b:
200                    if has_build:
201                        raise error.general('config and build in build: %s' % (build))
202                    has_config = True
203                else:
204                    if has_config:
205                        raise error.general('config and build in build: %s' % (build))
206                    has_build = True
207            builds['builds'][build] = build_builds
208        self.builds_ = builds
209
210    def configs(self):
211        return sorted(list(self.builds_['config'].keys()))
212
213    def config_flags(self, config):
214        if config not in self.builds_['config']:
215            raise error.general('config entry not found: %s' % (config))
216        return self.builds_['config'][config]
217
218    def build(self):
219        return self.builds_['build']
220
221    def builds(self):
222        if self.builds_['build'] in self.builds_['builds']:
223            build = copy.copy(self.builds_['builds'][self.builds_['build']])
224            if ':' in build[0]:
225                return [self.builds_['build']]
226            return build
227        return None
228
229    def build_options(self, build):
230        return ' '.join(self._build_options(build))
231
232    def excludes(self, arch, bsp):
233        return list(set(self.arch_excludes(arch) + self.bsp_excludes(arch, bsp)))
234
235    def exclude_options(self, arch, bsp):
236        return ' '.join([self.config_flags('no-' + e) for e in self.excludes(arch, bsp)])
237
238    def archs(self):
239        return sorted(list(self.archs.keys()))
240
241    def arch_present(self, arch):
242        return arch in self.archs
243
244    def arch_excludes(self, arch):
245        excludes = list(self.archs[arch]['excludes'].keys())
246        for exclude in self.archs[arch]['excludes']:
247            if 'all' not in self.archs[arch]['excludes'][exclude]:
248                excludes.remove(exclude)
249        return sorted(excludes)
250
251    def arch_bsps(self, arch):
252        return sorted(self.archs[arch]['bsps'])
253
254    def bsp_present(self, arch, bsp):
255        return bsp in self.archs[arch]['bsps']
256
257    def bsp_excludes(self, arch, bsp):
258        excludes = list(self.archs[arch]['excludes'].keys())
259        for exclude in self.archs[arch]['excludes']:
260            if 'all' not in self.archs[arch]['excludes'][exclude] and \
261               bsp not in self.archs[arch]['excludes'][exclude]:
262                excludes.remove(exclude)
263        return sorted(excludes)
264
265    def bspopts(self, arch, bsp):
266        if arch not in self.archs:
267            raise error.general('invalid architecture: %s' % (arch))
268        if bsp not in self.archs[arch]:
269            raise error.general('invalid BSP: %s' % (bsp))
270        return self.archs[arch][bsp]['bspopts']
271
272    def profile_present(self, profile):
273        return profile in self.profiles
274
275    def profile_archs(self, profile):
276        if profile not in self.profiles:
277            raise error.general('invalid profile: %s' % (profile))
278        return self.profiles[profile]['archs']
279
280    def profile_arch_bsps(self, profile, arch):
281        if profile not in self.profiles:
282            raise error.general('invalid profile: %s' % (profile))
283        if 'bsps_%s' % (arch) not in self.profiles[profile]:
284            raise error.general('invalid profile arch: %s' % (arch))
285        return ['%s/%s' % (arch, bsp) for bsp in self.profiles[profile]['bsps_%s' % (arch)]]
286
287    def report(self, profiles = True, builds = True, architectures = True):
288        width = 70
289        cols_1 = [width]
290        cols_2 = [10, width - 10]
291        s = textbox.line(cols_1, line = '=', marker = '+', indent = 1)
292        s1 = ' File(s)'
293        for f in self.config.files():
294            colon = ':'
295            for l in textwrap.wrap(f, width = cols_2[1] - 3):
296                s += textbox.row(cols_2, [s1, ' ' + l], marker = colon, indent = 1)
297                colon = ' '
298                s1 = ' ' * len(s1)
299        s += textbox.line(cols_1, marker = '+', indent = 1)
300        s += os.linesep
301        if profiles:
302            s += textbox.line(cols_1, line = '=', marker = '+', indent = 1)
303            profiles = sorted(self.profiles['profiles'])
304            archs = []
305            bsps = []
306            for profile in profiles:
307                archs += self.profiles[profile]['archs']
308                for arch in sorted(self.profiles[profile]['archs']):
309                    bsps += self.profiles[profile]['bsps_%s' % (arch)]
310            archs = len(set(archs))
311            bsps = len(set(bsps))
312            s += textbox.row(cols_1,
313                             [' Profiles : %d (archs:%d, bsps:%d)' % \
314                              (len(profiles), archs, bsps)],
315                             indent = 1)
316            for profile in profiles:
317                textbox.row(cols_2,
318                            [profile, self.profiles[profile]['name']],
319                            indent = 1)
320            s += textbox.line(cols_1, marker = '+', indent = 1)
321            for profile in profiles:
322                s += textbox.row(cols_1, [' %s' % (profile)], indent = 1)
323                profile = self.profiles[profile]
324                archs = sorted(profile['archs'])
325                for arch in archs:
326                    arch_bsps = ', '.join(profile['bsps_%s' % (arch)])
327                    if len(arch_bsps) > 0:
328                        s += textbox.line(cols_2, marker = '+', indent = 1)
329                        s1 = ' ' + arch
330                        for l in textwrap.wrap(arch_bsps,
331                                               width = cols_2[1] - 3):
332                            s += textbox.row(cols_2, [s1, ' ' + l], indent = 1)
333                            s1 = ' ' * len(s1)
334                s += textbox.line(cols_2, marker = '+', indent = 1)
335            s += os.linesep
336        if builds:
337            s += textbox.line(cols_1, line = '=', marker = '+', indent = 1)
338            s += textbox.row(cols_1,
339                             [' Builds:  %s (default)' % (self.builds_['default'])],
340                             indent = 1)
341            builds = self.builds_['builds']
342            bsize = 0
343            for build in builds:
344                if len(build) > bsize:
345                    bsize = len(build)
346            cols_b = [bsize + 2, width - bsize - 2]
347            s += textbox.line(cols_b, marker = '+', indent = 1)
348            for build in builds:
349                s1 = ' ' + build
350                for l in textwrap.wrap(', '.join(builds[build]),
351                                       width = cols_b[1] - 3):
352                    s += textbox.row(cols_b, [s1, ' ' + l], indent = 1)
353                    s1 = ' ' * len(s1)
354                s += textbox.line(cols_b, marker = '+', indent = 1)
355            configs = self.builds_['config']
356            s += textbox.row(cols_1,
357                             [' Configure Options: %d' % (len(configs))],
358                             indent = 1)
359            csize = 0
360            for config in configs:
361                if len(config) > csize:
362                    csize = len(config)
363            cols_c = [csize + 3, width - csize - 3]
364            s += textbox.line(cols_c, marker = '+', indent = 1)
365            for config in configs:
366                s1 = ' ' + config
367                for l in textwrap.wrap(configs[config], width = cols_c[1] - 3):
368                    s += textbox.row(cols_c, [s1, ' ' + l], indent = 1)
369                    s1 = ' ' * len(s1)
370                s += textbox.line(cols_c, marker = '+', indent = 1)
371            s += os.linesep
372        if architectures:
373            s += textbox.line(cols_1, line = '=', marker = '+', indent = 1)
374            archs = sorted(list(self.archs.keys()))
375            bsps = 0
376            asize = 0
377            for arch in archs:
378                if len(arch) > asize:
379                    asize = len(arch)
380                bsps += len(self.archs[arch]['bsps'])
381            s += textbox.row(cols_1,
382                             [' Architectures : %d (bsps: %d)' % (len(archs), bsps)],
383                             indent = 1)
384            cols_a = [asize + 2, width - asize - 2]
385            s += textbox.line(cols_a, marker = '+', indent = 1)
386            for arch in archs:
387                s += textbox.row(cols_a,
388                                 [' ' + arch, ' %d' % (len(self.archs[arch]['bsps']))],
389                                 indent = 1)
390            s += textbox.line(cols_a, marker = '+', indent = 1)
391            for archn in archs:
392                arch = self.archs[archn]
393                if len(arch['bsps']) > 0:
394                    bsize = 0
395                    for bsp in arch['bsps']:
396                        if len(bsp) > bsize:
397                            bsize = len(bsp)
398                    cols_b = [bsize + 3, width - bsize - 3]
399                    s += textbox.row(cols_1, [' ' + archn + ':'], indent = 1)
400                    s += textbox.line(cols_b, marker = '+', indent = 1)
401                    for bsp in arch['bsps']:
402                        s1 = ' ' + bsp
403                        bspopts = ' '.join(arch[bsp]['bspopts'])
404                        if len(bspopts):
405                            for l in textwrap.wrap('bopt: ' + bspopts,
406                                                   width = cols_b[1] - 3):
407                                s += textbox.row(cols_b, [s1, ' ' + l], indent = 1)
408                                s1 = ' ' * len(s1)
409                        excludes = []
410                        for exclude in arch['excludes']:
411                            if 'all' in arch['excludes'][exclude] or \
412                               bsp in arch['excludes'][exclude]:
413                                excludes += [exclude]
414                        excludes = ', '.join(excludes)
415                        if len(excludes):
416                            for l in textwrap.wrap('ex: ' + excludes,
417                                                   width = cols_b[1] - 3):
418                                s += textbox.row(cols_b, [s1, ' ' + l], indent = 1)
419                                s1 = ' ' * len(s1)
420                        if len(bspopts) == 0 and len(excludes) == 0:
421                            s += textbox.row(cols_b, [s1, ' '], indent = 1)
422                    s += textbox.line(cols_b, marker = '+', indent = 1)
423            s += os.linesep
424        return s
Note: See TracBrowser for help on using the repository browser.