mirror of
git://git.yoctoproject.org/poky.git
synced 2025-07-19 21:09:03 +02:00
patchtest: add scripts to oe-core
Add the following from the patchtest repo: - patchtest: core patch testing tool - patchtest-get-branch: determine the target branch of a patch - patchtest-get-series: pull patch series from Patchwork - patchtest-send-results: send test results to selected mailing list - patchtest-setup-sharedir: create sharedir for use with patchtest guest mode - patchtest.README: instructions for using patchtest based on the README in the original repository Note that the patchtest script was modified slightly from the repo version to retain compatibility with the oe-core changes. patchtest-send-results and patchtest-setup-sharedir are also primarily intended for automated testing in guest mode, but are added for consistency. (From OE-Core rev: cf318c3c05fc050b8c838c04f28797325c569c5c) Signed-off-by: Trevor Gamblin <tgamblin@baylibre.com> Signed-off-by: Richard Purdie <richard.purdie@linuxfoundation.org>
This commit is contained in:
parent
9d137188ad
commit
6e53a778f1
233
scripts/patchtest
Executable file
233
scripts/patchtest
Executable file
|
@ -0,0 +1,233 @@
|
|||
#!/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
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License version 2 as
|
||||
# published by the Free Software Foundation.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License along
|
||||
# with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
#
|
||||
# Author: Leo Sandoval <leonardo.sandoval.gonzalez@linux.intel.com>
|
||||
#
|
||||
|
||||
import sys
|
||||
import os
|
||||
import unittest
|
||||
import fileinput
|
||||
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.startdir, 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
|
||||
|
||||
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')
|
||||
|
||||
if premerge_result == 2 and postmerge_result == 2:
|
||||
logger.error('patchtest: any test cases found - did you specify the correct suite directory?')
|
||||
|
||||
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
|
||||
|
||||
if os.path.isdir(patch_path):
|
||||
patch_list = [os.path.join(patch_path, filename) for filename in 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 startdir
|
||||
if not PatchTestInput.topdir:
|
||||
PatchTestInput.topdir = PatchTestInput.startdir
|
||||
|
||||
try:
|
||||
ret = main()
|
||||
except Exception:
|
||||
import traceback
|
||||
traceback.print_exc(5)
|
||||
|
||||
sys.exit(ret)
|
92
scripts/patchtest-get-branch
Executable file
92
scripts/patchtest-get-branch
Executable file
|
@ -0,0 +1,92 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
# Get target branch from the corresponding mbox
|
||||
#
|
||||
# NOTE: this script was based on patches coming to the openembedded-core
|
||||
# where target branch is defined inside brackets as subject prefix
|
||||
# i.e. [master], [rocko], etc.
|
||||
#
|
||||
# Copyright (C) 2016 Intel Corporation
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License version 2 as
|
||||
# published by the Free Software Foundation.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License along
|
||||
# with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
import mailbox
|
||||
import argparse
|
||||
import re
|
||||
import git
|
||||
import sys
|
||||
|
||||
re_prefix = re.compile("(\[.*\])", re.DOTALL)
|
||||
|
||||
def get_branch(filepath_repo, filepath_mbox, default_branch):
|
||||
branch = None
|
||||
|
||||
# get all remotes branches
|
||||
gitbranches = git.Git(filepath_repo).branch('-a').splitlines()
|
||||
|
||||
# from gitbranches, just get the names
|
||||
branches = [b.split('/')[-1] for b in gitbranches]
|
||||
|
||||
subject = ' '.join(mailbox.mbox(filepath_mbox)[0]['subject'].splitlines())
|
||||
|
||||
# we expect that patches will have somewhere between one and three
|
||||
# consecutive sets of square brackets with tokens inside, e.g.:
|
||||
# 1. [PATCH]
|
||||
# 2. [OE-core][PATCH]
|
||||
# 3. [OE-core][kirkstone][PATCH]
|
||||
# Some of them may also be part of a series, in which case the PATCH
|
||||
# token will be formatted like:
|
||||
# [PATCH 1/4]
|
||||
# or they will be revisions to previous patches, where it will be:
|
||||
# [PATCH v2]
|
||||
# Or they may contain both:
|
||||
# [PATCH v2 3/4]
|
||||
# In any case, we want mprefix to contain all of these tokens so
|
||||
# that we can search for branch names within them.
|
||||
mprefix = re.findall(r'\[.*?\]', subject)
|
||||
found_branch = None
|
||||
if mprefix:
|
||||
# Iterate over the tokens and compare against the branch list to
|
||||
# figure out which one the patch is targeting
|
||||
for token in mprefix:
|
||||
stripped = token.lower().strip('[]')
|
||||
if default_branch in stripped:
|
||||
found_branch = default_branch
|
||||
break
|
||||
else:
|
||||
for branch in branches:
|
||||
# ignore branches named "core"
|
||||
if branch != "core" and stripped.rfind(branch) != -1:
|
||||
found_branch = token.split(' ')[0].strip('[]')
|
||||
break
|
||||
|
||||
# if there's no mprefix content or no known branches were found in
|
||||
# the tokens, assume the target is master
|
||||
if found_branch is None:
|
||||
found_branch = "master"
|
||||
|
||||
return (subject, found_branch)
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('repo', metavar='REPO', help='Main repository')
|
||||
parser.add_argument('mbox', metavar='MBOX', help='mbox filename')
|
||||
parser.add_argument('--default-branch', metavar='DEFAULT_BRANCH', default='master', help='Use this branch if no one is found')
|
||||
parser.add_argument('--separator', '-s', metavar='SEPARATOR', default=' ', help='Char separator for output data')
|
||||
args = parser.parse_args()
|
||||
|
||||
subject, branch = get_branch(args.repo, args.mbox, args.default_branch)
|
||||
print("branch: %s" % branch)
|
||||
|
125
scripts/patchtest-get-series
Executable file
125
scripts/patchtest-get-series
Executable file
|
@ -0,0 +1,125 @@
|
|||
#!/bin/bash -e
|
||||
#
|
||||
# get-latest-series: Download latest patch series from Patchwork
|
||||
#
|
||||
# Copyright (C) 2023 BayLibre Inc.
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License version 2 as
|
||||
# published by the Free Software Foundation.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License along
|
||||
# with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
# the interval into the past which we want to check for new series, in minutes
|
||||
INTERVAL_MINUTES=30
|
||||
|
||||
# Maximum number of series to retrieve. the Patchwork API can support up to 250
|
||||
# at once
|
||||
SERIES_LIMIT=250
|
||||
|
||||
# Location to save patches
|
||||
DOWNLOAD_PATH="."
|
||||
|
||||
# Name of the file to use/check as a log of previously-tested series IDs
|
||||
SERIES_TEST_LOG=".series_test.log"
|
||||
|
||||
# Patchwork project to pull series patches from
|
||||
PROJECT="oe-core"
|
||||
|
||||
# The Patchwork server to pull from
|
||||
SERVER="https://patchwork.yoctoproject.org/api/1.2/"
|
||||
|
||||
help()
|
||||
{
|
||||
echo "Usage: get-latest-series [ -i | --interval MINUTES ]
|
||||
[ -d | --directory DIRECTORY ]
|
||||
[ -l | --limit COUNT ]
|
||||
[ -h | --help ]
|
||||
[ -t | --tested-series LOGFILE]
|
||||
[ -p | --project PROJECT ]
|
||||
[ -s | --server SERVER ]"
|
||||
exit 2
|
||||
}
|
||||
|
||||
while [ "$1" != "" ]; do
|
||||
case $1 in
|
||||
-i|--interval)
|
||||
INTERVAL_MINUTES=$2
|
||||
shift 2
|
||||
;;
|
||||
-l|--limit)
|
||||
SERIES_LIMIT=$2
|
||||
shift 2
|
||||
;;
|
||||
-d|--directory)
|
||||
DOWNLOAD_PATH=$2
|
||||
shift 2
|
||||
;;
|
||||
-p|--project)
|
||||
PROJECT=$2
|
||||
shift 2
|
||||
;;
|
||||
-s|--server)
|
||||
SERVER=$2
|
||||
shift 2
|
||||
;;
|
||||
-t|--tested-series)
|
||||
SERIES_TEST_LOG=$2
|
||||
shift 2
|
||||
;;
|
||||
-h|--help)
|
||||
help
|
||||
;;
|
||||
*)
|
||||
echo "Unknown option $1"
|
||||
help
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
# The time this script is running at
|
||||
START_TIME=$(date --date "now" +"%Y-%m-%dT%H:%M:%S")
|
||||
|
||||
# the corresponding timestamp we want to check against for new patch series
|
||||
SERIES_CHECK_LIMIT=$(date --date "now - ${INTERVAL_MINUTES} minutes" +"%Y-%m-%dT%H:%M:%S")
|
||||
|
||||
echo "Start time is $START_TIME"
|
||||
echo "Series check limit is $SERIES_CHECK_LIMIT"
|
||||
|
||||
# Create DOWNLOAD_PATH if it doesn't exist
|
||||
if [ ! -d "$DOWNLOAD_PATH" ]; then
|
||||
mkdir "${DOWNLOAD_PATH}"
|
||||
fi
|
||||
|
||||
# Create SERIES_TEST_LOG if it doesn't exist
|
||||
if [ ! -f "$SERIES_TEST_LOG" ]; then
|
||||
touch "${SERIES_TEST_LOG}"
|
||||
fi
|
||||
|
||||
# Retrieve a list of series IDs from the 'git-pw series list' output. The API
|
||||
# supports a maximum of 250 results, so make sure we allow that when required
|
||||
SERIES_LIST=$(git-pw --project "${PROJECT}" --server "${SERVER}" series list --since "${SERIES_CHECK_LIMIT}" --limit "${SERIES_LIMIT}" | awk '{print $2}' | xargs | sed -e 's/[^0-9 ]//g')
|
||||
|
||||
if [ -z "$SERIES_LIST" ]; then
|
||||
echo "No new series for project ${PROJECT} since ${SERIES_CHECK_LIMIT}"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Check each series ID
|
||||
for SERIES in $SERIES_LIST; do
|
||||
# Download the series only if it's not found in the SERIES_TEST_LOG
|
||||
if ! grep -w --quiet "${SERIES}" "${SERIES_TEST_LOG}"; then
|
||||
echo "Downloading $SERIES..."
|
||||
git-pw series download --separate "${SERIES}" "${DOWNLOAD_PATH}"
|
||||
echo "${SERIES}" >> "${SERIES_TEST_LOG}"
|
||||
else
|
||||
echo "Already tested ${SERIES}. Skipping..."
|
||||
fi
|
||||
done
|
93
scripts/patchtest-send-results
Executable file
93
scripts/patchtest-send-results
Executable file
|
@ -0,0 +1,93 @@
|
|||
#!/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
|
||||
# Note that this script is currently under development and has been
|
||||
# hard-coded with default values for testing purposes. This script
|
||||
# should not be used without changing the default recipient, at minimum.
|
||||
#
|
||||
# Copyright (C) 2023 BayLibre Inc.
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License version 2 as
|
||||
# published by the Free Software Foundation.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License along
|
||||
# with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
#
|
||||
# Author: Trevor Gamblin <tgamblin@baylibre.com>
|
||||
#
|
||||
|
||||
import argparse
|
||||
import boto3
|
||||
import configparser
|
||||
import mailbox
|
||||
import os
|
||||
import sys
|
||||
|
||||
greeting = """Thank you for your submission. Patchtest identified one
|
||||
or more issues with the patch. Please see the log below for
|
||||
more information:\n\n---\n"""
|
||||
|
||||
suggestions = """\n---\n\nPlease address the issues identified and
|
||||
submit a new revision of the patch, or alternatively, reply to this
|
||||
email with an explanation of why the patch format should be accepted.
|
||||
Note that patchtest may report failures in the merge-on-head test for
|
||||
patches that are part of a series if they rely on changes from
|
||||
preceeding entries.
|
||||
|
||||
If you believe these results are due to an error in patchtest, please
|
||||
submit a bug at https://bugzilla.yoctoproject.org/ (use the 'Patchtest'
|
||||
category under 'Yocto Project Subprojects'). Thank you!"""
|
||||
|
||||
parser = argparse.ArgumentParser(description="Send patchtest results to a submitter for a given patch")
|
||||
parser.add_argument("-p", "--patch", dest="patch", required=True, help="The patch file to summarize")
|
||||
args = parser.parse_args()
|
||||
|
||||
if not os.path.exists(args.patch):
|
||||
print(f"Patch '{args.patch}' not found - did you provide the right path?")
|
||||
sys.exit(1)
|
||||
elif not os.path.exists(args.patch + ".testresult"):
|
||||
print(f"Found patch '{args.patch}' but '{args.patch}.testresult' was not present. Have you run patchtest on the patch?")
|
||||
sys.exit(1)
|
||||
|
||||
result_file = args.patch + ".testresult"
|
||||
result_basename = os.path.basename(args.patch)
|
||||
testresult = None
|
||||
|
||||
with open(result_file, "r") as f:
|
||||
testresult = f.read()
|
||||
|
||||
reply_contents = greeting + testresult + suggestions
|
||||
subject_line = f"Patchtest results for {result_basename}"
|
||||
|
||||
if "FAIL" in testresult:
|
||||
ses_client = boto3.client('ses', region_name='us-west-2')
|
||||
response = ses_client.send_email(
|
||||
Source='patchtest@automation.yoctoproject.org',
|
||||
Destination={
|
||||
'ToAddresses': ['test-list@lists.yoctoproject.org'],
|
||||
},
|
||||
ReplyToAddresses=['test-list@lists.yoctoproject.org'],
|
||||
Message={
|
||||
'Subject': {
|
||||
'Data': subject_line,
|
||||
'Charset': 'utf-8'
|
||||
},
|
||||
'Body': {
|
||||
'Text': {
|
||||
'Data': reply_contents,
|
||||
'Charset': 'utf-8'
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
else:
|
||||
print(f"No failures identified for {args.patch}.")
|
95
scripts/patchtest-setup-sharedir
Executable file
95
scripts/patchtest-setup-sharedir
Executable file
|
@ -0,0 +1,95 @@
|
|||
#!/bin/bash -e
|
||||
#
|
||||
# patchtest-setup-sharedir: Setup a directory for storing mboxes and
|
||||
# repositories to be shared with the guest machine, including updates to
|
||||
# the repos if the directory already exists
|
||||
#
|
||||
# Copyright (C) 2023 BayLibre Inc.
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License version 2 as
|
||||
# published by the Free Software Foundation.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License along
|
||||
# with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
#
|
||||
# Author: Trevor Gamblin <tgamblin@baylibre.com>
|
||||
|
||||
# poky repository
|
||||
POKY_REPO="https://git.yoctoproject.org/poky"
|
||||
|
||||
# patchtest repository
|
||||
PATCHTEST_REPO="https://git.yoctoproject.org/patchtest"
|
||||
|
||||
# the name of the directory
|
||||
SHAREDIR="patchtest_share"
|
||||
|
||||
help()
|
||||
{
|
||||
echo "Usage: patchtest-setup-sharedir [ -d | --directory SHAREDIR ]
|
||||
[ -p | --patchtest PATCHTEST_REPO ]
|
||||
[ -y | --poky POKY_REPO ]"
|
||||
exit 2
|
||||
}
|
||||
|
||||
while [ "$1" != "" ]; do
|
||||
case $1 in
|
||||
-d|--directory)
|
||||
SHAREDIR=$2
|
||||
shift 2
|
||||
;;
|
||||
-p|--patchtest)
|
||||
PATCHTEST_REPO=$2
|
||||
shift 2
|
||||
;;
|
||||
-y|--poky)
|
||||
POKY_REPO=$2
|
||||
shift 2
|
||||
;;
|
||||
-h|--help)
|
||||
help
|
||||
;;
|
||||
*)
|
||||
echo "Unknown option $1"
|
||||
help
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
# define MBOX_DIR where the patch series will be stored by
|
||||
# get-latest-series
|
||||
MBOX_DIR="${SHAREDIR}/mboxes"
|
||||
|
||||
# Create SHAREDIR if it doesn't exist
|
||||
if [ ! -d "$SHAREDIR" ]; then
|
||||
mkdir -p "${SHAREDIR}"
|
||||
echo "Created ${SHAREDIR}"
|
||||
fi
|
||||
|
||||
# Create the mboxes directory if it doesn't exist
|
||||
if [ ! -d "$MBOX_DIR" ]; then
|
||||
mkdir -p "${MBOX_DIR}"
|
||||
echo "Created ${MBOX_DIR}"
|
||||
fi
|
||||
|
||||
# clone poky if it's not already present; otherwise, update it
|
||||
if [ ! -d "$POKY_REPO" ]; then
|
||||
BASENAME=$(basename ${POKY_REPO})
|
||||
git clone "${POKY_REPO}" "${SHAREDIR}/${BASENAME}"
|
||||
else
|
||||
(cd "${SHAREDIR}/$BASENAME" && git pull)
|
||||
fi
|
||||
|
||||
# clone patchtest if it's not already present; otherwise, update it
|
||||
if [ ! -d "$PATCHTEST_REPO" ]; then
|
||||
BASENAME=$(basename ${PATCHTEST_REPO})
|
||||
git clone "${PATCHTEST_REPO}" "${SHAREDIR}/${BASENAME}"
|
||||
else
|
||||
(cd "${SHAREDIR}/$BASENAME" && git pull)
|
||||
fi
|
152
scripts/patchtest.README
Normal file
152
scripts/patchtest.README
Normal file
|
@ -0,0 +1,152 @@
|
|||
# Patchtest
|
||||
|
||||
## Introduction
|
||||
|
||||
Patchtest is a test framework for community patches based on the standard
|
||||
unittest python module. As input, it needs tree elements to work properly:
|
||||
a patch in mbox format (either created with `git format-patch` or fetched
|
||||
from 'patchwork'), a test suite and a target repository.
|
||||
|
||||
The first test suite intended to be used with patchtest is found in the
|
||||
openembedded-core repository [1] targeted for patches that get into the
|
||||
openembedded-core mailing list [2]. This suite is also intended as a
|
||||
baseline for development of similar suites for other layers as needed.
|
||||
|
||||
Patchtest can either run on a host or a guest machine, depending on which
|
||||
environment the execution needs to be done. If you plan to test your own patches
|
||||
(a good practice before these are sent to the mailing list), the easiest way is
|
||||
to install and execute on your local host; in the other hand, if automatic
|
||||
testing is intended, the guest method is strongly recommended. The guest
|
||||
method requires the use of the patchtest layer, in addition to the tools
|
||||
available in oe-core: https://git.yoctoproject.org/patchtest/
|
||||
|
||||
## Installation
|
||||
|
||||
As a tool for use with the Yocto Project, the [quick start guide](https://docs.yoctoproject.org/brief-yoctoprojectqs/index.html)
|
||||
contains the necessary prerequisites for a basic project. In addition,
|
||||
patchtest relies on the following Python modules:
|
||||
|
||||
- boto3 (for sending automated results emails only)
|
||||
- git-pw>=2.5.0
|
||||
- jinja2
|
||||
- pylint
|
||||
- pyparsing>=3.0.9
|
||||
- unidiff
|
||||
|
||||
These can be installed by running `pip install -r
|
||||
meta/lib/patchtest/requirements.txt`. Note that git-pw is not
|
||||
automatically added to the user's PATH; by default, it is installed at
|
||||
~/.local/bin/git-pw.
|
||||
|
||||
For git-pw (and therefore scripts such as patchtest-get--series) to work, you need
|
||||
to provide a Patchwork instance in your user's .gitconfig, like so (the project
|
||||
can be specified using the --project argument):
|
||||
|
||||
git config --global pw.server "https://patchwork.yoctoproject.org/api/1.2/"
|
||||
|
||||
To work with patchtest, you should have the following repositories cloned:
|
||||
|
||||
1. https://git.openembedded.org/openembedded-core/ (or https://git.yoctoproject.org/poky/)
|
||||
2. https://git.openembedded.org/bitbake/ (if not using poky)
|
||||
3. https://git.yoctoproject.org/patchtest (if using guest mode)
|
||||
|
||||
## Usage
|
||||
|
||||
### Obtaining Patches
|
||||
|
||||
Patch files can be obtained directly from cloned repositories using `git
|
||||
format-patch -N` (where N is the number of patches starting from HEAD to
|
||||
generate). git-pw can also be used with filters for users, patch/series IDs,
|
||||
and timeboxes if specific patches are desired. For more information, see the
|
||||
git-pw [documentation](https://patchwork.readthedocs.io/projects/git-pw/en/latest/).
|
||||
|
||||
Alternatively, `scripts/patchtest-get-series` can be used to pull mbox files from
|
||||
the Patchwork instance configured previously in .gitconfig. It uses a log file
|
||||
called ".series_test.log" to store and compare series IDs so that the same
|
||||
versions of a patch are not tested multiple times unintentionally. By default,
|
||||
it will pull up to five patch series from the last 30 minutes using oe-core as
|
||||
the target project, but these parameters can be configured using the `--limit`,
|
||||
`--interval`, and `--project` arguments respectively. For more information, run
|
||||
`patchtest-get-series -h`.
|
||||
|
||||
### Host Mode
|
||||
|
||||
To run patchtest on the host, do the following:
|
||||
|
||||
1. In openembedded-core/poky, do `source oe-init-build-env`
|
||||
2. Generate patch files from the target repository by doing `git-format patch -N`,
|
||||
where N is the number of patches starting at HEAD, or by using git-pw
|
||||
or patchtest-get-series
|
||||
3. Run patchtest on a patch file by doing the following:
|
||||
|
||||
patchtest --patch /path/to/patch/file /path/to/target/repo /path/to/tests/directory
|
||||
|
||||
or, if you have stored the patch files in a directory, do:
|
||||
|
||||
patchtest --directory /path/to/patch/directory /path/to/target/repo /path/to/tests/directory
|
||||
|
||||
For example, to test `master-gcc-Fix--fstack-protector-issue-on-aarch64.patch` against the oe-core test suite:
|
||||
|
||||
patchtest --patch master-gcc-Fix--fstack-protector-issue-on-aarch64.patch /path/to/openembedded-core /path/to/openembedded-core/meta/lib/patchtest/tests
|
||||
|
||||
### Guest Mode
|
||||
|
||||
Patchtest's guest mode has been refactored to more closely mirror the
|
||||
typical Yocto Project image build workflow, but there are still some key
|
||||
differences to keep in mind. The primary objective is to provide a level
|
||||
of isolation from the host when testing patches pulled automatically
|
||||
from the mailing lists. When executed this way, the test process is
|
||||
essentially running random code from the internet and could be
|
||||
catastrophic if malicious bits or even poorly-handled edge cases aren't
|
||||
protected against. In order to use this mode, the
|
||||
https://git.yoctoproject.org/patchtest/ repository must be cloned and
|
||||
the meta-patchtest layer added to bblayers.conf.
|
||||
|
||||
The general flow of guest mode is:
|
||||
|
||||
1. Run patchtest-setup-sharedir --directory <dirname> to create a
|
||||
directory for mounting
|
||||
2. Collect patches via patchtest-get-series (or other manual step) into the
|
||||
<dirname>/mboxes path
|
||||
3. Ensure that a user with ID 1200 has appropriate read/write
|
||||
permissions to <dirname> and <dirname>/mboxes, so that the
|
||||
"patchtest" user in the core-image-patchtest image can function
|
||||
4. Build the core-image-patchtest image
|
||||
5. Run the core-image-patchtest image with the mounted sharedir, like
|
||||
so:
|
||||
`runqemu kvm nographic qemuparams="-snapshot -fsdev
|
||||
local,id=test_mount,path=/workspace/yocto/poky/build/patchtestdir,security_model=mapped
|
||||
-device virtio-9p-pci,fsdev=test_mount,mount_tag=test_mount -smp 4 -m
|
||||
2048"`
|
||||
|
||||
Patchtest runs as an initscript for the core-image-patchtest image and
|
||||
shuts down after completion, so there is no input required from a user
|
||||
during operation. Unlike in host mode, the guest is designed to
|
||||
automatically generate test result files, in the same directory as the
|
||||
targeted patch files but with .testresult as an extension. These contain
|
||||
the entire output of the patchtest run for each respective pass,
|
||||
including the PASS, FAIL, and SKIP indicators for each test run.
|
||||
|
||||
## Contributing
|
||||
|
||||
The yocto mailing list (yocto@lists.yoctoproject.org) is used for questions,
|
||||
comments and patch review. It is subscriber only, so please register before
|
||||
posting.
|
||||
|
||||
Send pull requests to yocto@lists.yoctoproject.org with '[patchtest]' in the
|
||||
subject.
|
||||
|
||||
When sending single patches, please use something like:
|
||||
|
||||
git send-email -M -1 --to=yocto@lists.yoctoproject.org --subject-prefix=patchtest][PATCH
|
||||
|
||||
## Maintenance
|
||||
-----------
|
||||
|
||||
Maintainers:
|
||||
Trevor Gamblin <tgamblin@baylibre.com>
|
||||
|
||||
## Links
|
||||
-----
|
||||
[1] https://git.openembedded.org/openembedded-core/
|
||||
[2] https://www.yoctoproject.org/community/mailing-lists/
|
Loading…
Reference in New Issue
Block a user