blob: bed665196e973aca20327c3141fc0715fc9ef069 [file] [log] [blame]
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001#!/usr/bin/python
2
3# ex:ts=4:sw=4:sts=4:et
4# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
5#
6# Copyright (C) 2015 Alexandru Damian for Intel Corp.
7#
8# This program is free software; you can redistribute it and/or modify
9# it under the terms of the GNU General Public License version 2 as
10# published by the Free Software Foundation.
11#
12# This program is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15# GNU General Public License for more details.
16#
17# You should have received a copy of the GNU General Public License along
18# with this program; if not, write to the Free Software Foundation, Inc.,
19# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20
21
22# This is the main test execution controller. It is designed to be run
23# manually from the command line, or to be called from a different program
24# that schedules test execution.
25#
26# Execute runner.py -h for help.
27
28
29
30from __future__ import print_function
31import sys, os
32import unittest, importlib
33import logging, pprint, json
34import re
35from shellutils import ShellCmdException, mkdirhier, run_shell_cmd
36
37import config
38
39# we also log to a file, in addition to console, because our output is important
40__log_file_name__ = os.path.join(os.path.dirname(__file__), "log/tts_%d.log" % config.OWN_PID)
41mkdirhier(os.path.dirname(__log_file_name__))
42__log_file__ = open(__log_file_name__, "w")
43__file_handler__ = logging.StreamHandler(__log_file__)
44__file_handler__.setFormatter(logging.Formatter("%(asctime)s %(levelname)s: %(message)s"))
45
46config.logger.addHandler(__file_handler__)
47
48# set up log directory
49try:
50 if not os.path.exists(config.LOGDIR):
51 os.mkdir(config.LOGDIR)
52 else:
53 if not os.path.isdir(config.LOGDIR):
54 raise Exception("Expected log dir '%s' is not actually a directory." % config.LOGDIR)
55except OSError as exc:
56 raise exc
57
58# creates the under-test-branch as a separate directory
59def set_up_test_branch(settings, branch_name):
60 testdir = "%s/%s.%d" % (settings['workdir'], config.TEST_DIR_NAME, config.OWN_PID)
61
62 # creates the host dir
63 if os.path.exists(testdir):
64 raise Exception("Test dir '%s'is already there, aborting" % testdir)
65
66 # may raise OSError, is to be handled by the caller
67 os.makedirs(testdir)
68
69
70 # copies over the .git from the localclone
71 run_shell_cmd("cp -a '%s'/.git '%s'" % (settings['localclone'], testdir))
72
73 # add the remote if it doesn't exist
74 crt_remotes = run_shell_cmd("git remote -v", cwd=testdir)
75 remotes = [word for line in crt_remotes.split("\n") for word in line.split()]
76 if not config.CONTRIB_REPO in remotes:
77 remote_name = "tts_contrib"
78 run_shell_cmd("git remote add %s %s" % (remote_name, config.CONTRIB_REPO), cwd=testdir)
79 else:
80 remote_name = remotes[remotes.index(config.CONTRIB_REPO) - 1]
81
82 # do the fetch
83 run_shell_cmd("git fetch %s -p" % remote_name, cwd=testdir)
84
85 # do the checkout
86 run_shell_cmd("git checkout origin/master && git branch -D %s; git checkout %s/%s -b %s && git reset --hard" % (branch_name, remote_name, branch_name, branch_name), cwd=testdir)
87
88 return testdir
89
90
91def __search_for_tests():
92 # we find all classes that can run, and run them
93 tests = []
94 for _, _, files_list in os.walk(os.path.dirname(os.path.abspath(__file__))):
95 for module_file in [f[:-3] for f in files_list if f.endswith(".py") and not f.startswith("__init__")]:
96 config.logger.debug("Inspecting module %s", module_file)
97 current_module = importlib.import_module(module_file)
98 crtclass_names = vars(current_module)
99 for name in crtclass_names:
100 tested_value = crtclass_names[name]
101 if isinstance(tested_value, type(unittest.TestCase)) and issubclass(tested_value, unittest.TestCase):
102 tests.append((module_file, name))
103 break
104 return tests
105
106
107# boilerplate to self discover tests and run them
108def execute_tests(dir_under_test, testname):
109
110 if testname is not None and "." in testname:
111 tests = []
112 tests.append(tuple(testname.split(".", 2)))
113 else:
114 tests = __search_for_tests()
115
116 # let's move to the directory under test
117 crt_dir = os.getcwd()
118 os.chdir(dir_under_test)
119
120 # execute each module
121 # pylint: disable=broad-except
122 # we disable the broad-except because we want to actually catch all possible exceptions
123 try:
124 # sorting the tests by the numeric order in the class name
125 tests = sorted(tests, key=lambda x: int(re.search(r"[0-9]+", x[1]).group(0)))
126 config.logger.debug("Discovered test clases: %s", pprint.pformat(tests))
127 unittest.installHandler()
128 suite = unittest.TestSuite()
129 loader = unittest.TestLoader()
130 result = unittest.TestResult()
131 result.failfast = True
132 for module_file, test_name in tests:
133 suite.addTest(loader.loadTestsFromName("%s.%s" % (module_file, test_name)))
134 config.logger.info("Running %d test(s)", suite.countTestCases())
135 suite.run(result)
136
137 for error in result.errors:
138 config.logger.error("Exception on test: %s\n%s", error[0],
139 "\n".join(["-- %s" % x for x in error[1].split("\n")]))
140
141 for failure in result.failures:
142 config.logger.error("Failed test: %s:\n%s\n", failure[0],
143 "\n".join(["-- %s" % x for x in failure[1].split("\n")]))
144
145 config.logger.info("Test results: %d ran, %d errors, %d failures", result.testsRun, len(result.errors), len(result.failures))
146
147 except Exception as exc:
148 import traceback
149 config.logger.error("Exception while running test. Tracedump: \n%s", traceback.format_exc(exc))
150 finally:
151 os.chdir(crt_dir)
152 return len(result.failures)
153
154# verify that we had a branch-under-test name as parameter
155def validate_args():
156 from optparse import OptionParser
157 parser = OptionParser(usage="usage: %prog [options] branch_under_test")
158
159 parser.add_option("-t", "--test-dir", dest="testdir", default=None, help="Use specified directory to run tests, inhibits the checkout.")
160 parser.add_option("-s", "--single", dest="singletest", default=None, help="Run only the specified test")
161
162 (options, args) = parser.parse_args()
163 if len(args) < 1:
164 raise Exception("Please specify the branch to run on. Use option '-h' when in doubt.")
165 return (options, args)
166
167
168
169
170# load the configuration options
171def read_settings():
172 if not os.path.exists(config.SETTINGS_FILE) or not os.path.isfile(config.SETTINGS_FILE):
173 raise Exception("Config file '%s' cannot be openend" % config.SETTINGS_FILE)
174 return json.loads(open(config.SETTINGS_FILE, "r").read())
175
176
177# cleanup !
178def clean_up(testdir):
179 run_shell_cmd("rm -rf -- '%s'" % testdir)
180
181def dump_info(settings, options, args):
182 """ detailed information about current run configuration, for debugging purposes.
183 """
184 config.logger.debug("Settings:\n%s\nOptions:\n%s\nArguments:\n%s\n", settings, options, args)
185
186def main():
187 (options, args) = validate_args()
188
189 settings = read_settings()
190 need_cleanup = False
191
192 # dump debug info
193 dump_info(settings, options, args)
194
195 testdir = None
196 no_failures = 1
197 try:
198 if options.testdir is not None and os.path.exists(options.testdir):
199 testdir = os.path.abspath(options.testdir)
200 config.logger.info("No checkout, using %s", testdir)
201 else:
202 need_cleanup = True
203 testdir = set_up_test_branch(settings, args[0]) # we expect a branch name as first argument
204
205 config.TESTDIR = testdir # we let tests know where to run
206
207 # ensure that the test dir only contains no *.pyc leftovers
208 run_shell_cmd("find '%s' -type f -name *.pyc -exec rm {} \\;" % testdir)
209
210 no_failures = execute_tests(testdir, options.singletest)
211
212 except ShellCmdException as exc:
213 import traceback
214 config.logger.error("Error while setting up testing. Traceback: \n%s", traceback.format_exc(exc))
215 finally:
216 if need_cleanup and testdir is not None:
217 clean_up(testdir)
218
219 sys.exit(no_failures)
220
221if __name__ == "__main__":
222 main()