blob: e6f3a326d13aed9c1a182b9493b00ca0ae67fdf8 [file] [log] [blame]
Matthew Barthccb7f852016-11-23 17:43:02 -06001#!/usr/bin/env python
2
3"""
4This script determines the given package's openbmc dependencies from its
5configure.ac file where it downloads, configures, builds, and installs each of
6these dependencies. Then the given package is configured, built, and installed
7prior to executing its unit tests.
8"""
9
Matthew Barthd1810372016-12-19 16:57:21 -060010from git import Repo
Matthew Barthccb7f852016-11-23 17:43:02 -060011from urlparse import urljoin
Andrew Jefferya4e31c62018-03-08 13:45:28 +103012from subprocess import check_call, call, CalledProcessError
Matthew Barthccb7f852016-11-23 17:43:02 -060013import os
14import sys
Matthew Barth33df8792016-12-19 14:30:17 -060015import argparse
William A. Kennington IIIa2156732018-06-30 18:38:09 -070016import multiprocessing
Leonel Gonzaleza62a1a12017-03-24 11:03:47 -050017import re
William A. Kennington III9a32d5e2018-12-06 17:38:53 -080018import sets
William A. Kennington IIIe67f5fc2018-12-06 17:40:30 -080019import subprocess
William A. Kennington III3f1d1202018-12-06 18:02:07 -080020import shutil
William A. Kennington III4e1d0a12018-07-16 12:04:03 -070021import platform
Leonel Gonzaleza62a1a12017-03-24 11:03:47 -050022
23
24class DepTree():
25 """
26 Represents package dependency tree, where each node is a DepTree with a
27 name and DepTree children.
28 """
29
30 def __init__(self, name):
31 """
32 Create new DepTree.
33
34 Parameter descriptions:
35 name Name of new tree node.
36 """
37 self.name = name
38 self.children = list()
39
40 def AddChild(self, name):
41 """
42 Add new child node to current node.
43
44 Parameter descriptions:
45 name Name of new child
46 """
47 new_child = DepTree(name)
48 self.children.append(new_child)
49 return new_child
50
51 def AddChildNode(self, node):
52 """
53 Add existing child node to current node.
54
55 Parameter descriptions:
56 node Tree node to add
57 """
58 self.children.append(node)
59
60 def RemoveChild(self, name):
61 """
62 Remove child node.
63
64 Parameter descriptions:
65 name Name of child to remove
66 """
67 for child in self.children:
68 if child.name == name:
69 self.children.remove(child)
70 return
71
72 def GetNode(self, name):
73 """
74 Return node with matching name. Return None if not found.
75
76 Parameter descriptions:
77 name Name of node to return
78 """
79 if self.name == name:
80 return self
81 for child in self.children:
82 node = child.GetNode(name)
83 if node:
84 return node
85 return None
86
87 def GetParentNode(self, name, parent_node=None):
88 """
89 Return parent of node with matching name. Return none if not found.
90
91 Parameter descriptions:
92 name Name of node to get parent of
93 parent_node Parent of current node
94 """
95 if self.name == name:
96 return parent_node
97 for child in self.children:
98 found_node = child.GetParentNode(name, self)
99 if found_node:
100 return found_node
101 return None
102
103 def GetPath(self, name, path=None):
104 """
105 Return list of node names from head to matching name.
106 Return None if not found.
107
108 Parameter descriptions:
109 name Name of node
110 path List of node names from head to current node
111 """
112 if not path:
113 path = []
114 if self.name == name:
115 path.append(self.name)
116 return path
117 for child in self.children:
118 match = child.GetPath(name, path + [self.name])
119 if match:
120 return match
121 return None
122
123 def GetPathRegex(self, name, regex_str, path=None):
124 """
125 Return list of node paths that end in name, or match regex_str.
126 Return empty list if not found.
127
128 Parameter descriptions:
129 name Name of node to search for
130 regex_str Regex string to match node names
131 path Path of node names from head to current node
132 """
133 new_paths = []
134 if not path:
135 path = []
136 match = re.match(regex_str, self.name)
137 if (self.name == name) or (match):
138 new_paths.append(path + [self.name])
139 for child in self.children:
140 return_paths = None
141 full_path = path + [self.name]
142 return_paths = child.GetPathRegex(name, regex_str, full_path)
143 for i in return_paths:
144 new_paths.append(i)
145 return new_paths
146
147 def MoveNode(self, from_name, to_name):
148 """
149 Mode existing from_name node to become child of to_name node.
150
151 Parameter descriptions:
152 from_name Name of node to make a child of to_name
153 to_name Name of node to make parent of from_name
154 """
155 parent_from_node = self.GetParentNode(from_name)
156 from_node = self.GetNode(from_name)
157 parent_from_node.RemoveChild(from_name)
158 to_node = self.GetNode(to_name)
159 to_node.AddChildNode(from_node)
160
161 def ReorderDeps(self, name, regex_str):
162 """
163 Reorder dependency tree. If tree contains nodes with names that
164 match 'name' and 'regex_str', move 'regex_str' nodes that are
165 to the right of 'name' node, so that they become children of the
166 'name' node.
167
168 Parameter descriptions:
169 name Name of node to look for
170 regex_str Regex string to match names to
171 """
172 name_path = self.GetPath(name)
173 if not name_path:
174 return
175 paths = self.GetPathRegex(name, regex_str)
176 is_name_in_paths = False
177 name_index = 0
178 for i in range(len(paths)):
179 path = paths[i]
180 if path[-1] == name:
181 is_name_in_paths = True
182 name_index = i
183 break
184 if not is_name_in_paths:
185 return
186 for i in range(name_index + 1, len(paths)):
187 path = paths[i]
188 if name in path:
189 continue
190 from_name = path[-1]
191 self.MoveNode(from_name, name)
192
193 def GetInstallList(self):
194 """
195 Return post-order list of node names.
196
197 Parameter descriptions:
198 """
199 install_list = []
200 for child in self.children:
201 child_install_list = child.GetInstallList()
202 install_list.extend(child_install_list)
203 install_list.append(self.name)
204 return install_list
205
206 def PrintTree(self, level=0):
207 """
208 Print pre-order node names with indentation denoting node depth level.
209
210 Parameter descriptions:
211 level Current depth level
212 """
213 INDENT_PER_LEVEL = 4
214 print ' ' * (level * INDENT_PER_LEVEL) + self.name
215 for child in self.children:
216 child.PrintTree(level + 1)
Matthew Barth33df8792016-12-19 14:30:17 -0600217
218
William A. Kennington III1fddb972019-02-06 18:03:53 -0800219def check_call_cmd(*cmd):
Matthew Barth33df8792016-12-19 14:30:17 -0600220 """
221 Verbose prints the directory location the given command is called from and
222 the command, then executes the command using check_call.
223
224 Parameter descriptions:
225 dir Directory location command is to be called from
226 cmd List of parameters constructing the complete command
227 """
William A. Kennington III1fddb972019-02-06 18:03:53 -0800228 printline(os.getcwd(), ">", " ".join(cmd))
Matthew Barth33df8792016-12-19 14:30:17 -0600229 check_call(cmd)
Matthew Barthccb7f852016-11-23 17:43:02 -0600230
231
Andrew Geisslera61acb52019-01-03 16:32:44 -0600232def clone_pkg(pkg, branch):
Matthew Barth33df8792016-12-19 14:30:17 -0600233 """
234 Clone the given openbmc package's git repository from gerrit into
235 the WORKSPACE location
236
237 Parameter descriptions:
238 pkg Name of the package to clone
Andrew Geisslera61acb52019-01-03 16:32:44 -0600239 branch Branch to clone from pkg
Matthew Barth33df8792016-12-19 14:30:17 -0600240 """
Andrew Jeffery7be94ca2018-03-08 13:15:33 +1030241 pkg_dir = os.path.join(WORKSPACE, pkg)
242 if os.path.exists(os.path.join(pkg_dir, '.git')):
243 return pkg_dir
Matthew Barthccb7f852016-11-23 17:43:02 -0600244 pkg_repo = urljoin('https://gerrit.openbmc-project.xyz/openbmc/', pkg)
Andrew Jeffery7be94ca2018-03-08 13:15:33 +1030245 os.mkdir(pkg_dir)
Andrew Geisslera61acb52019-01-03 16:32:44 -0600246 printline(pkg_dir, "> git clone", pkg_repo, branch, "./")
247 try:
248 # first try the branch
249 repo_inst = Repo.clone_from(pkg_repo, pkg_dir,
250 branch=branch).working_dir
251 except:
252 printline("Input branch not found, default to master")
253 repo_inst = Repo.clone_from(pkg_repo, pkg_dir,
254 branch="master").working_dir
255 return repo_inst
Matthew Barth33df8792016-12-19 14:30:17 -0600256
257
William A. Kennington IIIc048cc02018-12-06 15:39:18 -0800258def get_autoconf_deps(pkgdir):
259 """
260 Parse the given 'configure.ac' file for package dependencies and return
261 a list of the dependencies found. If the package is not autoconf it is just
262 ignored.
263
264 Parameter descriptions:
265 pkgdir Directory where package source is located
266 """
267 configure_ac = os.path.join(pkgdir, 'configure.ac')
268 if not os.path.exists(configure_ac):
269 return []
270
William A. Kennington IIIe67f5fc2018-12-06 17:40:30 -0800271 configure_ac_contents = ''
272 # Prepend some special function overrides so we can parse out dependencies
273 for macro in DEPENDENCIES.iterkeys():
274 configure_ac_contents += ('m4_define([' + macro + '], [' +
275 macro + '_START$' + str(DEPENDENCIES_OFFSET[macro] + 1) +
276 macro + '_END])\n')
William A. Kennington IIIc048cc02018-12-06 15:39:18 -0800277 with open(configure_ac, "rt") as f:
William A. Kennington IIIe67f5fc2018-12-06 17:40:30 -0800278 configure_ac_contents += f.read()
279
280 autoconf_process = subprocess.Popen(['autoconf', '-Wno-undefined', '-'],
281 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
282 stderr=subprocess.PIPE)
283 (stdout, stderr) = autoconf_process.communicate(input=configure_ac_contents)
284 if not stdout:
285 print(stderr)
286 raise Exception("Failed to run autoconf for parsing dependencies")
287
288 # Parse out all of the dependency text
289 matches = []
290 for macro in DEPENDENCIES.iterkeys():
291 pattern = '(' + macro + ')_START(.*?)' + macro + '_END'
292 for match in re.compile(pattern).finditer(stdout):
293 matches.append((match.group(1), match.group(2)))
294
295 # Look up dependencies from the text
296 found_deps = []
297 for macro, deptext in matches:
298 for potential_dep in deptext.split(' '):
299 for known_dep in DEPENDENCIES[macro].iterkeys():
300 if potential_dep.startswith(known_dep):
301 found_deps.append(DEPENDENCIES[macro][known_dep])
302
303 return found_deps
Matthew Barthccb7f852016-11-23 17:43:02 -0600304
William A. Kennington III3f1d1202018-12-06 18:02:07 -0800305def get_meson_deps(pkgdir):
306 """
307 Parse the given 'meson.build' file for package dependencies and return
308 a list of the dependencies found. If the package is not meson compatible
309 it is just ignored.
310
311 Parameter descriptions:
312 pkgdir Directory where package source is located
313 """
314 meson_build = os.path.join(pkgdir, 'meson.build')
315 if not os.path.exists(meson_build):
316 return []
317
318 found_deps = []
319 for root, dirs, files in os.walk(pkgdir):
320 if 'meson.build' not in files:
321 continue
322 with open(os.path.join(root, 'meson.build'), 'rt') as f:
323 build_contents = f.read()
324 for match in re.finditer(r"dependency\('([^']*)'.*?\)\n", build_contents):
325 maybe_dep = DEPENDENCIES['PKG_CHECK_MODULES'].get(match.group(1))
326 if maybe_dep is not None:
327 found_deps.append(maybe_dep)
328
329 return found_deps
330
William A. Kennington IIIa2156732018-06-30 18:38:09 -0700331make_parallel = [
332 'make',
333 # Run enough jobs to saturate all the cpus
334 '-j', str(multiprocessing.cpu_count()),
335 # Don't start more jobs if the load avg is too high
336 '-l', str(multiprocessing.cpu_count()),
337 # Synchronize the output so logs aren't intermixed in stdout / stderr
338 '-O',
339]
340
William A. Kennington IIIa0454912018-12-06 14:47:16 -0800341def enFlag(flag, enabled):
342 """
343 Returns an configure flag as a string
344
345 Parameters:
346 flag The name of the flag
347 enabled Whether the flag is enabled or disabled
348 """
349 return '--' + ('enable' if enabled else 'disable') + '-' + flag
350
William A. Kennington III3f1d1202018-12-06 18:02:07 -0800351def mesonFeature(val):
352 """
353 Returns the meson flag which signifies the value
354
355 True is enabled which requires the feature.
356 False is disabled which disables the feature.
357 None is auto which autodetects the feature.
358
359 Parameters:
360 val The value being converted
361 """
362 if val is True:
363 return "enabled"
364 elif val is False:
365 return "disabled"
366 elif val is None:
367 return "auto"
368 else:
369 raise Exception("Bad meson feature value")
370
William A. Kennington III6764d5f2018-12-13 12:27:03 -0800371def parse_meson_options(options_file):
372 """
373 Returns a set of options defined in the provides meson_options.txt file
374
375 Parameters:
376 options_file The file containing options
377 """
378 options_contents = ''
379 with open(options_file, "rt") as f:
380 options_contents += f.read()
381 options = sets.Set()
382 pattern = 'option\\(\\s*\'([^\']*)\''
383 for match in re.compile(pattern).finditer(options_contents):
384 options.add(match.group(1))
385 return options
William A. Kennington III3f1d1202018-12-06 18:02:07 -0800386
William A. Kennington IIIa0454912018-12-06 14:47:16 -0800387def build_and_install(pkg, build_for_testing=False):
William A. Kennington III780ec092018-12-06 14:46:50 -0800388 """
389 Builds and installs the package in the environment. Optionally
390 builds the examples and test cases for package.
391
392 Parameter description:
393 pkg The package we are building
William A. Kennington IIIa0454912018-12-06 14:47:16 -0800394 build_for_testing Enable options related to testing on the package?
William A. Kennington III780ec092018-12-06 14:46:50 -0800395 """
William A. Kennington III90b106a2019-02-06 18:08:24 -0800396 os.chdir(os.path.join(WORKSPACE, pkg))
William A. Kennington III54d4faf2018-12-06 17:46:24 -0800397
398 # Refresh dynamic linker run time bindings for dependencies
William A. Kennington III1fddb972019-02-06 18:03:53 -0800399 check_call_cmd('sudo', '-n', '--', 'ldconfig')
William A. Kennington III54d4faf2018-12-06 17:46:24 -0800400
William A. Kennington III780ec092018-12-06 14:46:50 -0800401 # Build & install this package
William A. Kennington III3f1d1202018-12-06 18:02:07 -0800402 # Always try using meson first
403 if os.path.exists('meson.build'):
Brad Bishopf5a98292019-03-29 16:44:36 -0400404 meson_options = sets.Set()
405 if os.path.exists("meson_options.txt"):
406 meson_options = parse_meson_options("meson_options.txt")
William A. Kennington III3f1d1202018-12-06 18:02:07 -0800407 meson_flags = [
408 '-Db_colorout=never',
William A. Kennington III73b0d6e2019-01-17 14:35:41 -0800409 '-Dwerror=true',
410 '-Dwarning_level=3',
William A. Kennington III3f1d1202018-12-06 18:02:07 -0800411 ]
William A. Kennington III25e58142018-12-13 14:30:43 -0800412 if build_for_testing:
413 meson_flags.append('--buildtype=debug')
414 else:
415 meson_flags.append('--buildtype=debugoptimized')
William A. Kennington III6764d5f2018-12-13 12:27:03 -0800416 if 'tests' in meson_options:
417 meson_flags.append('-Dtests=' + mesonFeature(build_for_testing))
418 if 'examples' in meson_options:
419 meson_flags.append('-Dexamples=' + str(build_for_testing).lower())
William A. Kennington III3f1d1202018-12-06 18:02:07 -0800420 if MESON_FLAGS.get(pkg) is not None:
421 meson_flags.extend(MESON_FLAGS.get(pkg))
422 try:
William A. Kennington III1fddb972019-02-06 18:03:53 -0800423 check_call_cmd('meson', 'setup', '--reconfigure', 'build', *meson_flags)
William A. Kennington III3f1d1202018-12-06 18:02:07 -0800424 except:
425 shutil.rmtree('build')
William A. Kennington III1fddb972019-02-06 18:03:53 -0800426 check_call_cmd('meson', 'setup', 'build', *meson_flags)
427 check_call_cmd('ninja', '-C', 'build')
428 check_call_cmd('sudo', '-n', '--', 'ninja', '-C', 'build', 'install')
William A. Kennington III3f1d1202018-12-06 18:02:07 -0800429 # Assume we are autoconf otherwise
430 else:
431 conf_flags = [
432 enFlag('silent-rules', False),
433 enFlag('examples', build_for_testing),
434 enFlag('tests', build_for_testing),
William A. Kennington III3f1d1202018-12-06 18:02:07 -0800435 ]
William A. Kennington III65b37fa2019-01-31 15:15:17 -0800436 if not TEST_ONLY:
437 conf_flags.extend([
438 enFlag('code-coverage', build_for_testing),
439 enFlag('valgrind', build_for_testing),
440 ])
William A. Kennington III3f1d1202018-12-06 18:02:07 -0800441 # Add any necessary configure flags for package
442 if CONFIGURE_FLAGS.get(pkg) is not None:
443 conf_flags.extend(CONFIGURE_FLAGS.get(pkg))
444 for bootstrap in ['bootstrap.sh', 'bootstrap', 'autogen.sh']:
445 if os.path.exists(bootstrap):
William A. Kennington III1fddb972019-02-06 18:03:53 -0800446 check_call_cmd('./' + bootstrap)
William A. Kennington III3f1d1202018-12-06 18:02:07 -0800447 break
William A. Kennington III1fddb972019-02-06 18:03:53 -0800448 check_call_cmd('./configure', *conf_flags)
449 check_call_cmd(*make_parallel)
450 check_call_cmd('sudo', '-n', '--', *(make_parallel + [ 'install' ]))
William A. Kennington III780ec092018-12-06 14:46:50 -0800451
Andrew Geisslera61acb52019-01-03 16:32:44 -0600452def build_dep_tree(pkg, pkgdir, dep_added, head, branch, dep_tree=None):
Leonel Gonzaleza62a1a12017-03-24 11:03:47 -0500453 """
454 For each package(pkg), starting with the package to be unit tested,
455 parse its 'configure.ac' file from within the package's directory(pkgdir)
456 for each package dependency defined recursively doing the same thing
457 on each package found as a dependency.
458
459 Parameter descriptions:
460 pkg Name of the package
461 pkgdir Directory where package source is located
William A. Kennington IIIc048cc02018-12-06 15:39:18 -0800462 dep_added Current dict of dependencies and added status
Leonel Gonzaleza62a1a12017-03-24 11:03:47 -0500463 head Head node of the dependency tree
Andrew Geisslera61acb52019-01-03 16:32:44 -0600464 branch Branch to clone from pkg
Leonel Gonzaleza62a1a12017-03-24 11:03:47 -0500465 dep_tree Current dependency tree node
466 """
467 if not dep_tree:
468 dep_tree = head
William A. Kennington IIIc048cc02018-12-06 15:39:18 -0800469
William A. Kennington IIIbe6aab22018-12-06 15:01:54 -0800470 with open("/tmp/depcache", "r") as depcache:
William A. Kennington IIIc048cc02018-12-06 15:39:18 -0800471 cache = depcache.readline()
472
473 # Read out pkg dependencies
474 pkg_deps = []
475 pkg_deps += get_autoconf_deps(pkgdir)
William A. Kennington III3f1d1202018-12-06 18:02:07 -0800476 pkg_deps += get_meson_deps(pkgdir)
William A. Kennington IIIc048cc02018-12-06 15:39:18 -0800477
William A. Kennington III9a32d5e2018-12-06 17:38:53 -0800478 for dep in sets.Set(pkg_deps):
William A. Kennington IIIc048cc02018-12-06 15:39:18 -0800479 if dep in cache:
480 continue
481 # Dependency package not already known
482 if dep_added.get(dep) is None:
483 # Dependency package not added
484 new_child = dep_tree.AddChild(dep)
485 dep_added[dep] = False
Andrew Geisslera61acb52019-01-03 16:32:44 -0600486 dep_pkgdir = clone_pkg(dep,branch)
William A. Kennington IIIc048cc02018-12-06 15:39:18 -0800487 # Determine this dependency package's
488 # dependencies and add them before
489 # returning to add this package
490 dep_added = build_dep_tree(dep,
491 dep_pkgdir,
492 dep_added,
493 head,
Andrew Geisslera61acb52019-01-03 16:32:44 -0600494 branch,
William A. Kennington IIIc048cc02018-12-06 15:39:18 -0800495 new_child)
496 else:
497 # Dependency package known and added
498 if dep_added[dep]:
Andrew Jeffery2cb0c7a2018-03-08 13:19:08 +1030499 continue
Leonel Gonzaleza62a1a12017-03-24 11:03:47 -0500500 else:
William A. Kennington IIIc048cc02018-12-06 15:39:18 -0800501 # Cyclic dependency failure
502 raise Exception("Cyclic dependencies found in "+pkg)
Leonel Gonzaleza62a1a12017-03-24 11:03:47 -0500503
504 if not dep_added[pkg]:
505 dep_added[pkg] = True
506
507 return dep_added
Matthew Barthccb7f852016-11-23 17:43:02 -0600508
William A. Kennington III0f0a6802018-07-16 11:52:33 -0700509def make_target_exists(target):
510 """
511 Runs a check against the makefile in the current directory to determine
512 if the target exists so that it can be built.
513
514 Parameter descriptions:
515 target The make target we are checking
516 """
517 try:
518 cmd = [ 'make', '-n', target ]
519 with open(os.devnull, 'w') as devnull:
520 check_call(cmd, stdout=devnull, stderr=devnull)
521 return True
522 except CalledProcessError:
523 return False
524
William A. Kennington III90b106a2019-02-06 18:08:24 -0800525def run_unit_tests():
William A. Kennington III0f0a6802018-07-16 11:52:33 -0700526 """
527 Runs the unit tests for the package via `make check`
William A. Kennington III0f0a6802018-07-16 11:52:33 -0700528 """
529 try:
530 cmd = make_parallel + [ 'check' ]
531 for i in range(0, args.repeat):
William A. Kennington III1fddb972019-02-06 18:03:53 -0800532 check_call_cmd(*cmd)
William A. Kennington III0f0a6802018-07-16 11:52:33 -0700533 except CalledProcessError:
William A. Kennington III90b106a2019-02-06 18:08:24 -0800534 for root, _, files in os.walk(os.getcwd()):
William A. Kennington III0f0a6802018-07-16 11:52:33 -0700535 if 'test-suite.log' not in files:
536 continue
William A. Kennington III1fddb972019-02-06 18:03:53 -0800537 check_call_cmd('cat', os.path.join(root, 'test-suite.log'))
William A. Kennington III0f0a6802018-07-16 11:52:33 -0700538 raise Exception('Unit tests failed')
539
William A. Kennington III90b106a2019-02-06 18:08:24 -0800540def run_cppcheck():
Brad Bishop48424d42020-01-07 13:01:31 -0500541 match_re = re.compile('((?!\.mako\.).)*\.[ch](?:pp)?$', re.I)
542 cppcheck_files = []
543 stdout = subprocess.check_output(['git', 'ls-files'])
Patrick Venturead4354e2018-10-12 16:59:54 -0700544
Brad Bishop48424d42020-01-07 13:01:31 -0500545 for f in stdout.decode('utf-8').split():
546 if match_re.match(f):
547 cppcheck_files.append(f)
548
549 if not cppcheck_files:
550 # skip cppcheck if there arent' any c or cpp sources.
551 print("no files")
552 return None
553
554 # http://cppcheck.sourceforge.net/manual.pdf
555 params = ['cppcheck', '-j', str(multiprocessing.cpu_count()),
556 '--enable=all', '--file-list=-']
557
558 cppcheck_process = subprocess.Popen(
559 params,
560 stdout=subprocess.PIPE,
561 stderr=subprocess.PIPE,
562 stdin=subprocess.PIPE)
563 (stdout, stderr) = cppcheck_process.communicate(
564 input='\n'.join(cppcheck_files))
565
566 if cppcheck_process.wait():
Patrick Venturead4354e2018-10-12 16:59:54 -0700567 raise Exception('Cppcheck failed')
Brad Bishop48424d42020-01-07 13:01:31 -0500568 print(stdout)
569 print(stderr)
William A. Kennington III0f0a6802018-07-16 11:52:33 -0700570
William A. Kennington III37a89a22018-12-13 14:32:02 -0800571def is_valgrind_safe():
572 """
573 Returns whether it is safe to run valgrind on our platform
574 """
William A. Kennington III0326ded2019-02-07 00:33:28 -0800575 src = 'unit-test-vg.c'
576 exe = './unit-test-vg'
577 with open(src, 'w') as h:
William A. Kennington IIIafb0f982019-04-26 17:30:28 -0700578 h.write('#include <errno.h>\n')
579 h.write('#include <stdio.h>\n')
William A. Kennington III0326ded2019-02-07 00:33:28 -0800580 h.write('#include <stdlib.h>\n')
581 h.write('#include <string.h>\n')
582 h.write('int main() {\n')
583 h.write('char *heap_str = malloc(16);\n')
584 h.write('strcpy(heap_str, "RandString");\n')
585 h.write('int res = strcmp("RandString", heap_str);\n')
586 h.write('free(heap_str);\n')
William A. Kennington IIIafb0f982019-04-26 17:30:28 -0700587 h.write('char errstr[64];\n')
588 h.write('strerror_r(EINVAL, errstr, sizeof(errstr));\n')
589 h.write('printf("%s\\n", errstr);\n')
William A. Kennington III0326ded2019-02-07 00:33:28 -0800590 h.write('return res;\n')
591 h.write('}\n')
592 try:
593 with open(os.devnull, 'w') as devnull:
594 check_call(['gcc', '-O2', '-o', exe, src],
595 stdout=devnull, stderr=devnull)
596 check_call(['valgrind', '--error-exitcode=99', exe],
597 stdout=devnull, stderr=devnull)
598 return True
599 except:
600 sys.stderr.write("###### Platform is not valgrind safe ######\n")
601 return False
602 finally:
603 os.remove(src)
604 os.remove(exe)
William A. Kennington III37a89a22018-12-13 14:32:02 -0800605
William A. Kennington III282e3302019-02-04 16:55:05 -0800606def is_sanitize_safe():
607 """
608 Returns whether it is safe to run sanitizers on our platform
609 """
William A. Kennington III0b7fb2b2019-02-07 00:33:42 -0800610 src = 'unit-test-sanitize.c'
611 exe = './unit-test-sanitize'
612 with open(src, 'w') as h:
613 h.write('int main() { return 0; }\n')
614 try:
615 with open(os.devnull, 'w') as devnull:
616 check_call(['gcc', '-O2', '-fsanitize=address',
617 '-fsanitize=undefined', '-o', exe, src],
618 stdout=devnull, stderr=devnull)
619 check_call([exe], stdout=devnull, stderr=devnull)
620 return True
621 except:
622 sys.stderr.write("###### Platform is not sanitize safe ######\n")
623 return False
624 finally:
625 os.remove(src)
626 os.remove(exe)
William A. Kennington III282e3302019-02-04 16:55:05 -0800627
William A. Kennington III49d4e592019-02-06 17:59:27 -0800628def meson_setup_exists(setup):
629 """
630 Returns whether the meson build supports the named test setup.
631
632 Parameter descriptions:
633 setup The setup target to check
634 """
635 try:
636 with open(os.devnull, 'w') as devnull:
637 output = subprocess.check_output(
638 ['meson', 'test', '-C', 'build',
639 '--setup', setup, '-t', '0'],
640 stderr=subprocess.STDOUT)
641 except CalledProcessError as e:
642 output = e.output
643 return not re.search('Test setup .* not found from project', output)
644
Andrew Geissler9ced4ed2019-11-18 14:33:58 -0600645def run_unit_tests_meson():
646 """
647 Runs the unit tests for the meson based package
648 """
649 try:
650 check_call_cmd('meson', 'test', '-C', 'build')
651 except CalledProcessError:
652 for root, _, files in os.walk(os.getcwd()):
653 if 'testlog.txt' not in files:
654 continue
655 check_call_cmd('cat', os.path.join(root, 'testlog.txt'))
656 raise Exception('Unit tests failed')
657
William A. Kennington III49d4e592019-02-06 17:59:27 -0800658def maybe_meson_valgrind():
659 """
660 Potentially runs the unit tests through valgrind for the package
661 via `meson test`. The package can specify custom valgrind configurations
662 by utilizing add_test_setup() in a meson.build
663 """
664 if not is_valgrind_safe():
William A. Kennington III75130192019-02-07 00:34:14 -0800665 sys.stderr.write("###### Skipping valgrind ######\n")
William A. Kennington III49d4e592019-02-06 17:59:27 -0800666 return
Andrew Geissler4c9928e2020-02-18 08:16:43 -0600667 try:
668 if meson_setup_exists('valgrind'):
669 check_call_cmd('meson', 'test', '-C', 'build',
670 '--setup', 'valgrind')
671 else:
672 check_call_cmd('meson', 'test', '-C', 'build',
673 '--wrapper', 'valgrind')
674 except CalledProcessError:
675 for root, _, files in os.walk(os.getcwd()):
676 if 'testlog-valgrind.txt' not in files:
677 continue
678 check_call_cmd('cat', os.path.join(root, 'testlog-valgrind.txt'))
679 raise Exception('Valgrind tests failed')
William A. Kennington III49d4e592019-02-06 17:59:27 -0800680
William A. Kennington IIIeaff24a2019-02-06 16:57:42 -0800681def maybe_make_valgrind():
William A. Kennington III0f0a6802018-07-16 11:52:33 -0700682 """
683 Potentially runs the unit tests through valgrind for the package
684 via `make check-valgrind`. If the package does not have valgrind testing
685 then it just skips over this.
William A. Kennington III0f0a6802018-07-16 11:52:33 -0700686 """
William A. Kennington III4e1d0a12018-07-16 12:04:03 -0700687 # Valgrind testing is currently broken by an aggressive strcmp optimization
688 # that is inlined into optimized code for POWER by gcc 7+. Until we find
689 # a workaround, just don't run valgrind tests on POWER.
690 # https://github.com/openbmc/openbmc/issues/3315
William A. Kennington III37a89a22018-12-13 14:32:02 -0800691 if not is_valgrind_safe():
William A. Kennington III75130192019-02-07 00:34:14 -0800692 sys.stderr.write("###### Skipping valgrind ######\n")
William A. Kennington III4e1d0a12018-07-16 12:04:03 -0700693 return
William A. Kennington III0f0a6802018-07-16 11:52:33 -0700694 if not make_target_exists('check-valgrind'):
695 return
696
697 try:
698 cmd = make_parallel + [ 'check-valgrind' ]
William A. Kennington III1fddb972019-02-06 18:03:53 -0800699 check_call_cmd(*cmd)
William A. Kennington III0f0a6802018-07-16 11:52:33 -0700700 except CalledProcessError:
William A. Kennington III90b106a2019-02-06 18:08:24 -0800701 for root, _, files in os.walk(os.getcwd()):
William A. Kennington III0f0a6802018-07-16 11:52:33 -0700702 for f in files:
703 if re.search('test-suite-[a-z]+.log', f) is None:
704 continue
William A. Kennington III1fddb972019-02-06 18:03:53 -0800705 check_call_cmd('cat', os.path.join(root, f))
William A. Kennington III0f0a6802018-07-16 11:52:33 -0700706 raise Exception('Valgrind tests failed')
707
William A. Kennington IIIeaff24a2019-02-06 16:57:42 -0800708def maybe_make_coverage():
William A. Kennington III0f0a6802018-07-16 11:52:33 -0700709 """
710 Potentially runs the unit tests through code coverage for the package
711 via `make check-code-coverage`. If the package does not have code coverage
712 testing then it just skips over this.
William A. Kennington III0f0a6802018-07-16 11:52:33 -0700713 """
714 if not make_target_exists('check-code-coverage'):
715 return
716
717 # Actually run code coverage
718 try:
719 cmd = make_parallel + [ 'check-code-coverage' ]
William A. Kennington III1fddb972019-02-06 18:03:53 -0800720 check_call_cmd(*cmd)
William A. Kennington III0f0a6802018-07-16 11:52:33 -0700721 except CalledProcessError:
722 raise Exception('Code coverage failed')
Matthew Barthccb7f852016-11-23 17:43:02 -0600723
Matt Spinler9bfaaad2019-10-25 09:51:50 -0500724def find_file(filename, basedir):
725 """
726 Finds all occurrences of a file in the base directory
727 and passes them back with their relative paths.
728
729 Parameter descriptions:
730 filename The name of the file to find
731 basedir The base directory search in
732 """
733
734 filepaths = []
735 for root, dirs, files in os.walk(basedir):
736 if filename in files:
737 filepaths.append(os.path.join(root, filename))
738 return filepaths
739
Matthew Barthccb7f852016-11-23 17:43:02 -0600740if __name__ == '__main__':
741 # CONFIGURE_FLAGS = [GIT REPO]:[CONFIGURE FLAGS]
742 CONFIGURE_FLAGS = {
Matthew Barth1d1c6732017-03-24 10:00:28 -0500743 'sdbusplus': ['--enable-transaction'],
744 'phosphor-logging':
Matt Spinler0744bb82020-01-16 08:23:35 -0600745 ['--enable-metadata-processing', '--enable-openpower-pel-extension',
Deepak Kodihalli3a4e1b42017-06-08 09:52:35 -0500746 'YAML_DIR=/usr/local/share/phosphor-dbus-yaml/yaml']
Matthew Barthccb7f852016-11-23 17:43:02 -0600747 }
748
William A. Kennington III3f1d1202018-12-06 18:02:07 -0800749 # MESON_FLAGS = [GIT REPO]:[MESON FLAGS]
750 MESON_FLAGS = {
751 }
752
Matthew Barthccb7f852016-11-23 17:43:02 -0600753 # DEPENDENCIES = [MACRO]:[library/header]:[GIT REPO]
754 DEPENDENCIES = {
755 'AC_CHECK_LIB': {'mapper': 'phosphor-objmgr'},
Matthew Barth710f3f02017-01-18 15:20:19 -0600756 'AC_CHECK_HEADER': {
757 'host-ipmid': 'phosphor-host-ipmid',
Patrick Ventureb41a4462018-10-03 17:27:38 -0700758 'blobs-ipmid': 'phosphor-ipmi-blobs',
Matthew Barth710f3f02017-01-18 15:20:19 -0600759 'sdbusplus': 'sdbusplus',
William A. Kennington IIIb4f730a2018-09-12 11:21:20 -0700760 'sdeventplus': 'sdeventplus',
William A. Kennington III23705242019-01-15 18:17:25 -0800761 'stdplus': 'stdplus',
Patrick Venture22329962018-09-14 10:23:04 -0700762 'gpioplus': 'gpioplus',
Saqib Khan66145052017-02-14 12:02:07 -0600763 'phosphor-logging/log.hpp': 'phosphor-logging',
Patrick Williamseab8a372017-01-30 11:21:32 -0600764 },
Brad Bishopebb49112017-02-13 11:07:26 -0500765 'AC_PATH_PROG': {'sdbus++': 'sdbusplus'},
Patrick Williamseab8a372017-01-30 11:21:32 -0600766 'PKG_CHECK_MODULES': {
Matthew Barth19e261e2017-02-01 12:55:22 -0600767 'phosphor-dbus-interfaces': 'phosphor-dbus-interfaces',
Patrick Williamsf128b402017-03-29 06:45:59 -0500768 'openpower-dbus-interfaces': 'openpower-dbus-interfaces',
Matt Spinler7be19032018-04-13 09:43:14 -0500769 'ibm-dbus-interfaces': 'ibm-dbus-interfaces',
William A. Kennington III993ab332019-02-07 02:12:31 -0800770 'libipmid': 'phosphor-host-ipmid',
771 'libipmid-host': 'phosphor-host-ipmid',
Brad Bishopebb49112017-02-13 11:07:26 -0500772 'sdbusplus': 'sdbusplus',
William A. Kennington IIIb4f730a2018-09-12 11:21:20 -0700773 'sdeventplus': 'sdeventplus',
William A. Kennington III23705242019-01-15 18:17:25 -0800774 'stdplus': 'stdplus',
Patrick Venture22329962018-09-14 10:23:04 -0700775 'gpioplus': 'gpioplus',
Brad Bishopebb49112017-02-13 11:07:26 -0500776 'phosphor-logging': 'phosphor-logging',
Marri Devender Raoa3eee8a2018-08-13 05:34:27 -0500777 'phosphor-snmp': 'phosphor-snmp',
Patrick Venturee584c3b2019-03-07 09:44:16 -0800778 'ipmiblob': 'ipmi-blob-tool',
Brad Bishopebb49112017-02-13 11:07:26 -0500779 },
Matthew Barthccb7f852016-11-23 17:43:02 -0600780 }
781
William A. Kennington IIIe67f5fc2018-12-06 17:40:30 -0800782 # Offset into array of macro parameters MACRO(0, 1, ...N)
783 DEPENDENCIES_OFFSET = {
784 'AC_CHECK_LIB': 0,
785 'AC_CHECK_HEADER': 0,
786 'AC_PATH_PROG': 1,
787 'PKG_CHECK_MODULES': 1,
788 }
789
Leonel Gonzaleza62a1a12017-03-24 11:03:47 -0500790 # DEPENDENCIES_REGEX = [GIT REPO]:[REGEX STRING]
791 DEPENDENCIES_REGEX = {
Patrick Ventured37b8052018-10-16 16:03:03 -0700792 'phosphor-logging': r'\S+-dbus-interfaces$'
Leonel Gonzaleza62a1a12017-03-24 11:03:47 -0500793 }
794
Matthew Barth33df8792016-12-19 14:30:17 -0600795 # Set command line arguments
796 parser = argparse.ArgumentParser()
797 parser.add_argument("-w", "--workspace", dest="WORKSPACE", required=True,
798 help="Workspace directory location(i.e. /home)")
799 parser.add_argument("-p", "--package", dest="PACKAGE", required=True,
800 help="OpenBMC package to be unit tested")
William A. Kennington III65b37fa2019-01-31 15:15:17 -0800801 parser.add_argument("-t", "--test-only", dest="TEST_ONLY",
802 action="store_true", required=False, default=False,
803 help="Only run test cases, no other validation")
Matthew Barth33df8792016-12-19 14:30:17 -0600804 parser.add_argument("-v", "--verbose", action="store_true",
805 help="Print additional package status messages")
Andrew Jeffery468309d2018-03-08 13:46:33 +1030806 parser.add_argument("-r", "--repeat", help="Repeat tests N times",
807 type=int, default=1)
Andrew Geisslera61acb52019-01-03 16:32:44 -0600808 parser.add_argument("-b", "--branch", dest="BRANCH", required=False,
809 help="Branch to target for dependent repositories",
810 default="master")
Lei YU7ef93302019-11-06 13:53:21 +0800811 parser.add_argument("-n", "--noformat", dest="FORMAT",
812 action="store_false", required=False,
813 help="Whether or not to run format code")
Matthew Barth33df8792016-12-19 14:30:17 -0600814 args = parser.parse_args(sys.argv[1:])
815 WORKSPACE = args.WORKSPACE
816 UNIT_TEST_PKG = args.PACKAGE
William A. Kennington III65b37fa2019-01-31 15:15:17 -0800817 TEST_ONLY = args.TEST_ONLY
Andrew Geisslera61acb52019-01-03 16:32:44 -0600818 BRANCH = args.BRANCH
Lei YU7ef93302019-11-06 13:53:21 +0800819 FORMAT_CODE = args.FORMAT
Matthew Barth33df8792016-12-19 14:30:17 -0600820 if args.verbose:
821 def printline(*line):
822 for arg in line:
823 print arg,
824 print
825 else:
826 printline = lambda *l: None
Matthew Barthccb7f852016-11-23 17:43:02 -0600827
Lei YU7ef93302019-11-06 13:53:21 +0800828 CODE_SCAN_DIR = WORKSPACE + "/" + UNIT_TEST_PKG
829
James Feist878df5c2018-07-26 14:54:28 -0700830 # First validate code formatting if repo has style formatting files.
Adriana Kobylakbcee22b2018-01-10 16:58:27 -0600831 # The format-code.sh checks for these files.
Lei YU7ef93302019-11-06 13:53:21 +0800832 if FORMAT_CODE:
833 check_call_cmd("./format-code.sh", CODE_SCAN_DIR)
Andrew Geisslera28286d2018-01-10 11:00:00 -0800834
William A. Kennington III3f1d1202018-12-06 18:02:07 -0800835 # Automake and meson
836 if (os.path.isfile(CODE_SCAN_DIR + "/configure.ac") or
837 os.path.isfile(CODE_SCAN_DIR + '/meson.build')):
James Feist878df5c2018-07-26 14:54:28 -0700838 prev_umask = os.umask(000)
839 # Determine dependencies and add them
840 dep_added = dict()
841 dep_added[UNIT_TEST_PKG] = False
842 # Create dependency tree
843 dep_tree = DepTree(UNIT_TEST_PKG)
844 build_dep_tree(UNIT_TEST_PKG,
845 os.path.join(WORKSPACE, UNIT_TEST_PKG),
846 dep_added,
Andrew Geisslera61acb52019-01-03 16:32:44 -0600847 dep_tree,
848 BRANCH)
James Feist878df5c2018-07-26 14:54:28 -0700849
850 # Reorder Dependency Tree
851 for pkg_name, regex_str in DEPENDENCIES_REGEX.iteritems():
852 dep_tree.ReorderDeps(pkg_name, regex_str)
853 if args.verbose:
854 dep_tree.PrintTree()
855 install_list = dep_tree.GetInstallList()
William A. Kennington IIId61316d2018-12-06 14:56:12 -0800856 # We don't want to treat our package as a dependency
857 install_list.remove(UNIT_TEST_PKG)
James Feist878df5c2018-07-26 14:54:28 -0700858 # install reordered dependencies
William A. Kennington IIId61316d2018-12-06 14:56:12 -0800859 for dep in install_list:
860 build_and_install(dep, False)
William A. Kennington III90b106a2019-02-06 18:08:24 -0800861 os.chdir(os.path.join(WORKSPACE, UNIT_TEST_PKG))
James Feist878df5c2018-07-26 14:54:28 -0700862 # Run package unit tests
William A. Kennington IIId61316d2018-12-06 14:56:12 -0800863 build_and_install(UNIT_TEST_PKG, True)
William A. Kennington III3f1d1202018-12-06 18:02:07 -0800864 if os.path.isfile(CODE_SCAN_DIR + '/meson.build'):
William A. Kennington III65b37fa2019-01-31 15:15:17 -0800865 if not TEST_ONLY:
William A. Kennington III49d4e592019-02-06 17:59:27 -0800866 maybe_meson_valgrind()
William A. Kennington III40d5c7c2018-12-13 14:37:59 -0800867
William A. Kennington III65b37fa2019-01-31 15:15:17 -0800868 # Run clang-tidy only if the project has a configuration
869 if os.path.isfile('.clang-tidy'):
William A. Kennington IIIf6769412019-06-26 12:14:51 -0700870 check_call_cmd('run-clang-tidy-8.py', '-p',
William A. Kennington III65b37fa2019-01-31 15:15:17 -0800871 'build')
872 # Run the basic clang static analyzer otherwise
873 else:
William A. Kennington III1fddb972019-02-06 18:03:53 -0800874 check_call_cmd('ninja', '-C', 'build',
William A. Kennington III65b37fa2019-01-31 15:15:17 -0800875 'scan-build')
876
877 # Run tests through sanitizers
878 # b_lundef is needed if clang++ is CXX since it resolves the
879 # asan symbols at runtime only. We don't want to set it earlier
880 # in the build process to ensure we don't have undefined
881 # runtime code.
William A. Kennington III282e3302019-02-04 16:55:05 -0800882 if is_sanitize_safe():
William A. Kennington III1fddb972019-02-06 18:03:53 -0800883 check_call_cmd('meson', 'configure', 'build',
William A. Kennington III282e3302019-02-04 16:55:05 -0800884 '-Db_sanitize=address,undefined',
885 '-Db_lundef=false')
William A. Kennington III1fddb972019-02-06 18:03:53 -0800886 check_call_cmd('meson', 'test', '-C', 'build',
William A. Kennington III282e3302019-02-04 16:55:05 -0800887 '--logbase', 'testlog-ubasan')
888 # TODO: Fix memory sanitizer
William A. Kennington III1fddb972019-02-06 18:03:53 -0800889 #check_call_cmd('meson', 'configure', 'build',
William A. Kennington III282e3302019-02-04 16:55:05 -0800890 # '-Db_sanitize=memory')
William A. Kennington III1fddb972019-02-06 18:03:53 -0800891 #check_call_cmd('meson', 'test', '-C', 'build'
William A. Kennington III282e3302019-02-04 16:55:05 -0800892 # '--logbase', 'testlog-msan')
William A. Kennington III1fddb972019-02-06 18:03:53 -0800893 check_call_cmd('meson', 'configure', 'build',
William A. Kennington III282e3302019-02-04 16:55:05 -0800894 '-Db_sanitize=none', '-Db_lundef=true')
William A. Kennington III75130192019-02-07 00:34:14 -0800895 else:
896 sys.stderr.write("###### Skipping sanitizers ######\n")
William A. Kennington III65b37fa2019-01-31 15:15:17 -0800897
898 # Run coverage checks
William A. Kennington III1fddb972019-02-06 18:03:53 -0800899 check_call_cmd('meson', 'configure', 'build',
William A. Kennington III65b37fa2019-01-31 15:15:17 -0800900 '-Db_coverage=true')
Andrew Geissler9ced4ed2019-11-18 14:33:58 -0600901 run_unit_tests_meson()
William A. Kennington III65b37fa2019-01-31 15:15:17 -0800902 # Only build coverage HTML if coverage files were produced
903 for root, dirs, files in os.walk('build'):
904 if any([f.endswith('.gcda') for f in files]):
William A. Kennington III1fddb972019-02-06 18:03:53 -0800905 check_call_cmd('ninja', '-C', 'build',
William A. Kennington III65b37fa2019-01-31 15:15:17 -0800906 'coverage-html')
907 break
William A. Kennington III1fddb972019-02-06 18:03:53 -0800908 check_call_cmd('meson', 'configure', 'build',
William A. Kennington III65b37fa2019-01-31 15:15:17 -0800909 '-Db_coverage=false')
910 else:
Andrew Geissler9ced4ed2019-11-18 14:33:58 -0600911 run_unit_tests_meson()
912
William A. Kennington III3f1d1202018-12-06 18:02:07 -0800913 else:
William A. Kennington III90b106a2019-02-06 18:08:24 -0800914 run_unit_tests()
William A. Kennington III65b37fa2019-01-31 15:15:17 -0800915 if not TEST_ONLY:
William A. Kennington IIIeaff24a2019-02-06 16:57:42 -0800916 maybe_make_valgrind()
917 maybe_make_coverage()
William A. Kennington III65b37fa2019-01-31 15:15:17 -0800918 if not TEST_ONLY:
William A. Kennington III90b106a2019-02-06 18:08:24 -0800919 run_cppcheck()
James Feist878df5c2018-07-26 14:54:28 -0700920
921 os.umask(prev_umask)
922
923 # Cmake
924 elif os.path.isfile(CODE_SCAN_DIR + "/CMakeLists.txt"):
William A. Kennington III90b106a2019-02-06 18:08:24 -0800925 os.chdir(os.path.join(WORKSPACE, UNIT_TEST_PKG))
William A. Kennington III1fddb972019-02-06 18:03:53 -0800926 check_call_cmd('cmake', '-DCMAKE_EXPORT_COMPILE_COMMANDS=ON', '.')
927 check_call_cmd('cmake', '--build', '.', '--', '-j',
James Feist878df5c2018-07-26 14:54:28 -0700928 str(multiprocessing.cpu_count()))
929 if make_target_exists('test'):
William A. Kennington III1fddb972019-02-06 18:03:53 -0800930 check_call_cmd('ctest', '.')
William A. Kennington III65b37fa2019-01-31 15:15:17 -0800931 if not TEST_ONLY:
William A. Kennington IIIeaff24a2019-02-06 16:57:42 -0800932 maybe_make_valgrind()
933 maybe_make_coverage()
William A. Kennington III90b106a2019-02-06 18:08:24 -0800934 run_cppcheck()
William A. Kennington III65b37fa2019-01-31 15:15:17 -0800935 if os.path.isfile('.clang-tidy'):
William A. Kennington IIIf6769412019-06-26 12:14:51 -0700936 check_call_cmd('run-clang-tidy-8.py', '-p', '.')
James Feist878df5c2018-07-26 14:54:28 -0700937
938 else:
Andrew Geissler71a7cc12018-01-31 14:18:37 -0800939 print "Not a supported repo for CI Tests, exit"
940 quit()
Matt Spinler9bfaaad2019-10-25 09:51:50 -0500941
942 # Run any custom CI scripts the repo has, of which there can be
943 # multiple of and anywhere in the repository.
944 ci_scripts = find_file('run-ci.sh', os.path.join(WORKSPACE, UNIT_TEST_PKG))
945 if ci_scripts:
946 os.chdir(os.path.join(WORKSPACE, UNIT_TEST_PKG))
947 for ci_script in ci_scripts:
948 check_call_cmd('sh', ci_script)