| #!/usr/bin/env python3 |
| # ex:ts=4:sw=4:sts=4:et |
| # -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- |
| # |
| # patchtest: execute all unittest test cases discovered for a single patch |
| # |
| # Copyright (C) 2016 Intel Corporation |
| # |
| # SPDX-License-Identifier: GPL-2.0-only |
| # |
| |
| import sys |
| import os |
| import unittest |
| import logging |
| import traceback |
| import json |
| |
| # Include current path so test cases can see it |
| sys.path.insert(0, os.path.dirname(os.path.realpath(__file__))) |
| |
| # Include patchtest library |
| sys.path.insert(0, os.path.join(os.path.dirname(os.path.realpath(__file__)), '../meta/lib/patchtest')) |
| |
| from data import PatchTestInput |
| from repo import PatchTestRepo |
| |
| import utils |
| logger = utils.logger_create('patchtest') |
| info = logger.info |
| error = logger.error |
| |
| import repo |
| |
| def getResult(patch, mergepatch, logfile=None): |
| |
| class PatchTestResult(unittest.TextTestResult): |
| """ Patchtest TextTestResult """ |
| shouldStop = True |
| longMessage = False |
| |
| success = 'PASS' |
| fail = 'FAIL' |
| skip = 'SKIP' |
| |
| def startTestRun(self): |
| # let's create the repo already, it can be used later on |
| repoargs = { |
| 'repodir': PatchTestInput.repodir, |
| 'commit' : PatchTestInput.basecommit, |
| 'branch' : PatchTestInput.basebranch, |
| 'patch' : patch, |
| } |
| |
| self.repo_error = False |
| self.test_error = False |
| self.test_failure = False |
| |
| try: |
| self.repo = PatchTestInput.repo = PatchTestRepo(**repoargs) |
| except: |
| logger.error(traceback.print_exc()) |
| self.repo_error = True |
| self.stop() |
| return |
| |
| if mergepatch: |
| self.repo.merge() |
| |
| def addError(self, test, err): |
| self.test_error = True |
| (ty, va, trace) = err |
| logger.error(traceback.print_exc()) |
| |
| def addFailure(self, test, err): |
| test_description = test.id().split('.')[-1].replace('_', ' ').replace("cve", "CVE").replace("signed off by", |
| "Signed-off-by").replace("upstream status", |
| "Upstream-Status").replace("non auh", |
| "non-AUH").replace("presence format", "presence") |
| self.test_failure = True |
| fail_str = '{}: {}: {} ({})'.format(self.fail, |
| test_description, json.loads(str(err[1]))["issue"], |
| test.id()) |
| print(fail_str) |
| if logfile: |
| with open(logfile, "a") as f: |
| f.write(fail_str + "\n") |
| |
| def addSuccess(self, test): |
| test_description = test.id().split('.')[-1].replace('_', ' ').replace("cve", "CVE").replace("signed off by", |
| "Signed-off-by").replace("upstream status", |
| "Upstream-Status").replace("non auh", |
| "non-AUH").replace("presence format", "presence") |
| success_str = '{}: {} ({})'.format(self.success, |
| test_description, test.id()) |
| print(success_str) |
| if logfile: |
| with open(logfile, "a") as f: |
| f.write(success_str + "\n") |
| |
| def addSkip(self, test, reason): |
| test_description = test.id().split('.')[-1].replace('_', ' ').replace("cve", "CVE").replace("signed off by", |
| "Signed-off-by").replace("upstream status", |
| "Upstream-Status").replace("non auh", |
| "non-AUH").replace("presence format", "presence") |
| skip_str = '{}: {}: {} ({})'.format(self.skip, |
| test_description, json.loads(str(reason))["issue"], |
| test.id()) |
| print(skip_str) |
| if logfile: |
| with open(logfile, "a") as f: |
| f.write(skip_str + "\n") |
| |
| def stopTestRun(self): |
| |
| # in case there was an error on repo object creation, just return |
| if self.repo_error: |
| return |
| |
| self.repo.clean() |
| |
| return PatchTestResult |
| |
| def _runner(resultklass, prefix=None): |
| # load test with the corresponding prefix |
| loader = unittest.TestLoader() |
| if prefix: |
| loader.testMethodPrefix = prefix |
| |
| # create the suite with discovered tests and the corresponding runner |
| suite = loader.discover(start_dir=PatchTestInput.testdir, pattern=PatchTestInput.pattern, top_level_dir=PatchTestInput.topdir) |
| ntc = suite.countTestCases() |
| |
| # if there are no test cases, just quit |
| if not ntc: |
| return 2 |
| runner = unittest.TextTestRunner(resultclass=resultklass, verbosity=0) |
| |
| try: |
| result = runner.run(suite) |
| except: |
| logger.error(traceback.print_exc()) |
| logger.error('patchtest: something went wrong') |
| return 1 |
| if result.test_failure or result.test_error: |
| return 1 |
| |
| return 0 |
| |
| def run(patch, logfile=None): |
| """ Load, setup and run pre and post-merge tests """ |
| # Get the result class and install the control-c handler |
| unittest.installHandler() |
| |
| # run pre-merge tests, meaning those methods with 'pretest' as prefix |
| premerge_resultklass = getResult(patch, False, logfile) |
| premerge_result = _runner(premerge_resultklass, 'pretest') |
| |
| # run post-merge tests, meaning those methods with 'test' as prefix |
| postmerge_resultklass = getResult(patch, True, logfile) |
| postmerge_result = _runner(postmerge_resultklass, 'test') |
| |
| print('----------------------------------------------------------------------\n') |
| if premerge_result == 2 and postmerge_result == 2: |
| logger.error('patchtest: No test cases found - did you specify the correct suite directory?') |
| if premerge_result == 1 or postmerge_result == 1: |
| logger.error('WARNING: patchtest: At least one patchtest caused a failure or an error - please check https://wiki.yoctoproject.org/wiki/Patchtest for further guidance') |
| else: |
| logger.info('OK: patchtest: All patchtests passed') |
| print('----------------------------------------------------------------------\n') |
| return premerge_result or postmerge_result |
| |
| def main(): |
| tmp_patch = False |
| patch_path = PatchTestInput.patch_path |
| log_results = PatchTestInput.log_results |
| log_path = None |
| patch_list = None |
| |
| git_status = os.popen("(cd %s && git status)" % PatchTestInput.repodir).read() |
| status_matches = ["Changes not staged for commit", "Changes to be committed"] |
| if any([match in git_status for match in status_matches]): |
| logger.error("patchtest: there are uncommitted changes in the target repo that would be overwritten. Please commit or restore them before running patchtest") |
| return 1 |
| |
| if os.path.isdir(patch_path): |
| patch_list = [os.path.join(patch_path, filename) for filename in sorted(os.listdir(patch_path))] |
| else: |
| patch_list = [patch_path] |
| |
| for patch in patch_list: |
| if os.path.getsize(patch) == 0: |
| logger.error('patchtest: patch is empty') |
| return 1 |
| |
| logger.info('Testing patch %s' % patch) |
| |
| if log_results: |
| log_path = patch + ".testresult" |
| with open(log_path, "a") as f: |
| f.write("Patchtest results for patch '%s':\n\n" % patch) |
| |
| try: |
| if log_path: |
| run(patch, log_path) |
| else: |
| run(patch) |
| finally: |
| if tmp_patch: |
| os.remove(patch) |
| |
| if __name__ == '__main__': |
| ret = 1 |
| |
| # Parse the command line arguments and store it on the PatchTestInput namespace |
| PatchTestInput.set_namespace() |
| |
| # set debugging level |
| if PatchTestInput.debug: |
| logger.setLevel(logging.DEBUG) |
| |
| # if topdir not define, default it to testdir |
| if not PatchTestInput.topdir: |
| PatchTestInput.topdir = PatchTestInput.testdir |
| |
| try: |
| ret = main() |
| except Exception: |
| import traceback |
| traceback.print_exc(5) |
| |
| sys.exit(ret) |