summaryrefslogtreecommitdiffstats
path: root/scripts/patchtest
diff options
context:
space:
mode:
Diffstat (limited to 'scripts/patchtest')
-rwxr-xr-xscripts/patchtest233
1 files changed, 233 insertions, 0 deletions
diff --git a/scripts/patchtest b/scripts/patchtest
new file mode 100755
index 0000000000..9525a2be17
--- /dev/null
+++ b/scripts/patchtest
@@ -0,0 +1,233 @@
1#!/usr/bin/env python3
2# ex:ts=4:sw=4:sts=4:et
3# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
4#
5# patchtest: execute all unittest test cases discovered for a single patch
6#
7# Copyright (C) 2016 Intel Corporation
8#
9# This program is free software; you can redistribute it and/or modify
10# it under the terms of the GNU General Public License version 2 as
11# published by the Free Software Foundation.
12#
13# This program is distributed in the hope that it will be useful,
14# but WITHOUT ANY WARRANTY; without even the implied warranty of
15# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16# GNU General Public License for more details.
17#
18# You should have received a copy of the GNU General Public License along
19# with this program; if not, write to the Free Software Foundation, Inc.,
20# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21#
22# Author: Leo Sandoval <leonardo.sandoval.gonzalez@linux.intel.com>
23#
24
25import sys
26import os
27import unittest
28import fileinput
29import logging
30import traceback
31import json
32
33# Include current path so test cases can see it
34sys.path.insert(0, os.path.dirname(os.path.realpath(__file__)))
35
36# Include patchtest library
37sys.path.insert(0, os.path.join(os.path.dirname(os.path.realpath(__file__)), '../meta/lib/patchtest'))
38
39from data import PatchTestInput
40from repo import PatchTestRepo
41
42import utils
43logger = utils.logger_create('patchtest')
44info = logger.info
45error = logger.error
46
47import repo
48
49def getResult(patch, mergepatch, logfile=None):
50
51 class PatchTestResult(unittest.TextTestResult):
52 """ Patchtest TextTestResult """
53 shouldStop = True
54 longMessage = False
55
56 success = 'PASS'
57 fail = 'FAIL'
58 skip = 'SKIP'
59
60 def startTestRun(self):
61 # let's create the repo already, it can be used later on
62 repoargs = {
63 'repodir': PatchTestInput.repodir,
64 'commit' : PatchTestInput.basecommit,
65 'branch' : PatchTestInput.basebranch,
66 'patch' : patch,
67 }
68
69 self.repo_error = False
70 self.test_error = False
71 self.test_failure = False
72
73 try:
74 self.repo = PatchTestInput.repo = PatchTestRepo(**repoargs)
75 except:
76 logger.error(traceback.print_exc())
77 self.repo_error = True
78 self.stop()
79 return
80
81 if mergepatch:
82 self.repo.merge()
83
84 def addError(self, test, err):
85 self.test_error = True
86 (ty, va, trace) = err
87 logger.error(traceback.print_exc())
88
89 def addFailure(self, test, err):
90 test_description = test.id().split('.')[-1].replace('_', ' ').replace("cve", "CVE").replace("signed off by",
91 "Signed-off-by").replace("upstream status",
92 "Upstream-Status").replace("non auh",
93 "non-AUH").replace("presence format", "presence")
94 self.test_failure = True
95 fail_str = '{}: {}: {} ({})'.format(self.fail,
96 test_description, json.loads(str(err[1]))["issue"],
97 test.id())
98 print(fail_str)
99 if logfile:
100 with open(logfile, "a") as f:
101 f.write(fail_str + "\n")
102
103 def addSuccess(self, test):
104 test_description = test.id().split('.')[-1].replace('_', ' ').replace("cve", "CVE").replace("signed off by",
105 "Signed-off-by").replace("upstream status",
106 "Upstream-Status").replace("non auh",
107 "non-AUH").replace("presence format", "presence")
108 success_str = '{}: {} ({})'.format(self.success,
109 test_description, test.id())
110 print(success_str)
111 if logfile:
112 with open(logfile, "a") as f:
113 f.write(success_str + "\n")
114
115 def addSkip(self, test, reason):
116 test_description = test.id().split('.')[-1].replace('_', ' ').replace("cve", "CVE").replace("signed off by",
117 "Signed-off-by").replace("upstream status",
118 "Upstream-Status").replace("non auh",
119 "non-AUH").replace("presence format", "presence")
120 skip_str = '{}: {}: {} ({})'.format(self.skip,
121 test_description, json.loads(str(reason))["issue"],
122 test.id())
123 print(skip_str)
124 if logfile:
125 with open(logfile, "a") as f:
126 f.write(skip_str + "\n")
127
128 def stopTestRun(self):
129
130 # in case there was an error on repo object creation, just return
131 if self.repo_error:
132 return
133
134 self.repo.clean()
135
136 return PatchTestResult
137
138def _runner(resultklass, prefix=None):
139 # load test with the corresponding prefix
140 loader = unittest.TestLoader()
141 if prefix:
142 loader.testMethodPrefix = prefix
143
144 # create the suite with discovered tests and the corresponding runner
145 suite = loader.discover(start_dir=PatchTestInput.startdir, pattern=PatchTestInput.pattern, top_level_dir=PatchTestInput.topdir)
146 ntc = suite.countTestCases()
147
148 # if there are no test cases, just quit
149 if not ntc:
150 return 2
151 runner = unittest.TextTestRunner(resultclass=resultklass, verbosity=0)
152
153 try:
154 result = runner.run(suite)
155 except:
156 logger.error(traceback.print_exc())
157 logger.error('patchtest: something went wrong')
158 return 1
159
160 return 0
161
162def run(patch, logfile=None):
163 """ Load, setup and run pre and post-merge tests """
164 # Get the result class and install the control-c handler
165 unittest.installHandler()
166
167 # run pre-merge tests, meaning those methods with 'pretest' as prefix
168 premerge_resultklass = getResult(patch, False, logfile)
169 premerge_result = _runner(premerge_resultklass, 'pretest')
170
171 # run post-merge tests, meaning those methods with 'test' as prefix
172 postmerge_resultklass = getResult(patch, True, logfile)
173 postmerge_result = _runner(postmerge_resultklass, 'test')
174
175 if premerge_result == 2 and postmerge_result == 2:
176 logger.error('patchtest: any test cases found - did you specify the correct suite directory?')
177
178 return premerge_result or postmerge_result
179
180def main():
181 tmp_patch = False
182 patch_path = PatchTestInput.patch_path
183 log_results = PatchTestInput.log_results
184 log_path = None
185 patch_list = None
186
187 if os.path.isdir(patch_path):
188 patch_list = [os.path.join(patch_path, filename) for filename in os.listdir(patch_path)]
189 else:
190 patch_list = [patch_path]
191
192 for patch in patch_list:
193 if os.path.getsize(patch) == 0:
194 logger.error('patchtest: patch is empty')
195 return 1
196
197 logger.info('Testing patch %s' % patch)
198
199 if log_results:
200 log_path = patch + ".testresult"
201 with open(log_path, "a") as f:
202 f.write("Patchtest results for patch '%s':\n\n" % patch)
203
204 try:
205 if log_path:
206 run(patch, log_path)
207 else:
208 run(patch)
209 finally:
210 if tmp_patch:
211 os.remove(patch)
212
213if __name__ == '__main__':
214 ret = 1
215
216 # Parse the command line arguments and store it on the PatchTestInput namespace
217 PatchTestInput.set_namespace()
218
219 # set debugging level
220 if PatchTestInput.debug:
221 logger.setLevel(logging.DEBUG)
222
223 # if topdir not define, default it to startdir
224 if not PatchTestInput.topdir:
225 PatchTestInput.topdir = PatchTestInput.startdir
226
227 try:
228 ret = main()
229 except Exception:
230 import traceback
231 traceback.print_exc(5)
232
233 sys.exit(ret)