summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorTrevor Gamblin <tgamblin@baylibre.com>2023-10-16 15:44:57 -0400
committerRichard Purdie <richard.purdie@linuxfoundation.org>2023-10-17 11:41:34 +0100
commit6e53a778f10c77eab3c0172a0cbc4d63efc663e9 (patch)
tree2790a50300d53a809ede675f0d163a999149331d
parent9d137188ad03c111ff8df7396b6b3dfd59307ac0 (diff)
downloadpoky-6e53a778f10c77eab3c0172a0cbc4d63efc663e9.tar.gz
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>
-rwxr-xr-xscripts/patchtest233
-rwxr-xr-xscripts/patchtest-get-branch92
-rwxr-xr-xscripts/patchtest-get-series125
-rwxr-xr-xscripts/patchtest-send-results93
-rwxr-xr-xscripts/patchtest-setup-sharedir95
-rw-r--r--scripts/patchtest.README152
6 files changed, 790 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)
diff --git a/scripts/patchtest-get-branch b/scripts/patchtest-get-branch
new file mode 100755
index 0000000000..9415de98ef
--- /dev/null
+++ b/scripts/patchtest-get-branch
@@ -0,0 +1,92 @@
1#!/usr/bin/env python3
2
3# Get target branch from the corresponding mbox
4#
5# NOTE: this script was based on patches coming to the openembedded-core
6# where target branch is defined inside brackets as subject prefix
7# i.e. [master], [rocko], etc.
8#
9# Copyright (C) 2016 Intel Corporation
10#
11# This program is free software; you can redistribute it and/or modify
12# it under the terms of the GNU General Public License version 2 as
13# published by the Free Software Foundation.
14#
15# This program is distributed in the hope that it will be useful,
16# but WITHOUT ANY WARRANTY; without even the implied warranty of
17# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18# GNU General Public License for more details.
19#
20# You should have received a copy of the GNU General Public License along
21# with this program; if not, write to the Free Software Foundation, Inc.,
22# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
23
24import mailbox
25import argparse
26import re
27import git
28import sys
29
30re_prefix = re.compile("(\[.*\])", re.DOTALL)
31
32def get_branch(filepath_repo, filepath_mbox, default_branch):
33 branch = None
34
35 # get all remotes branches
36 gitbranches = git.Git(filepath_repo).branch('-a').splitlines()
37
38 # from gitbranches, just get the names
39 branches = [b.split('/')[-1] for b in gitbranches]
40
41 subject = ' '.join(mailbox.mbox(filepath_mbox)[0]['subject'].splitlines())
42
43 # we expect that patches will have somewhere between one and three
44 # consecutive sets of square brackets with tokens inside, e.g.:
45 # 1. [PATCH]
46 # 2. [OE-core][PATCH]
47 # 3. [OE-core][kirkstone][PATCH]
48 # Some of them may also be part of a series, in which case the PATCH
49 # token will be formatted like:
50 # [PATCH 1/4]
51 # or they will be revisions to previous patches, where it will be:
52 # [PATCH v2]
53 # Or they may contain both:
54 # [PATCH v2 3/4]
55 # In any case, we want mprefix to contain all of these tokens so
56 # that we can search for branch names within them.
57 mprefix = re.findall(r'\[.*?\]', subject)
58 found_branch = None
59 if mprefix:
60 # Iterate over the tokens and compare against the branch list to
61 # figure out which one the patch is targeting
62 for token in mprefix:
63 stripped = token.lower().strip('[]')
64 if default_branch in stripped:
65 found_branch = default_branch
66 break
67 else:
68 for branch in branches:
69 # ignore branches named "core"
70 if branch != "core" and stripped.rfind(branch) != -1:
71 found_branch = token.split(' ')[0].strip('[]')
72 break
73
74 # if there's no mprefix content or no known branches were found in
75 # the tokens, assume the target is master
76 if found_branch is None:
77 found_branch = "master"
78
79 return (subject, found_branch)
80
81if __name__ == '__main__':
82
83 parser = argparse.ArgumentParser()
84 parser.add_argument('repo', metavar='REPO', help='Main repository')
85 parser.add_argument('mbox', metavar='MBOX', help='mbox filename')
86 parser.add_argument('--default-branch', metavar='DEFAULT_BRANCH', default='master', help='Use this branch if no one is found')
87 parser.add_argument('--separator', '-s', metavar='SEPARATOR', default=' ', help='Char separator for output data')
88 args = parser.parse_args()
89
90 subject, branch = get_branch(args.repo, args.mbox, args.default_branch)
91 print("branch: %s" % branch)
92
diff --git a/scripts/patchtest-get-series b/scripts/patchtest-get-series
new file mode 100755
index 0000000000..773701f80b
--- /dev/null
+++ b/scripts/patchtest-get-series
@@ -0,0 +1,125 @@
1#!/bin/bash -e
2#
3# get-latest-series: Download latest patch series from Patchwork
4#
5# Copyright (C) 2023 BayLibre Inc.
6#
7# This program is free software; you can redistribute it and/or modify
8# it under the terms of the GNU General Public License version 2 as
9# published by the Free Software Foundation.
10#
11# This program is distributed in the hope that it will be useful,
12# but WITHOUT ANY WARRANTY; without even the implied warranty of
13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14# GNU General Public License for more details.
15#
16# You should have received a copy of the GNU General Public License along
17# with this program; if not, write to the Free Software Foundation, Inc.,
18# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19
20# the interval into the past which we want to check for new series, in minutes
21INTERVAL_MINUTES=30
22
23# Maximum number of series to retrieve. the Patchwork API can support up to 250
24# at once
25SERIES_LIMIT=250
26
27# Location to save patches
28DOWNLOAD_PATH="."
29
30# Name of the file to use/check as a log of previously-tested series IDs
31SERIES_TEST_LOG=".series_test.log"
32
33# Patchwork project to pull series patches from
34PROJECT="oe-core"
35
36# The Patchwork server to pull from
37SERVER="https://patchwork.yoctoproject.org/api/1.2/"
38
39help()
40{
41 echo "Usage: get-latest-series [ -i | --interval MINUTES ]
42 [ -d | --directory DIRECTORY ]
43 [ -l | --limit COUNT ]
44 [ -h | --help ]
45 [ -t | --tested-series LOGFILE]
46 [ -p | --project PROJECT ]
47 [ -s | --server SERVER ]"
48 exit 2
49}
50
51while [ "$1" != "" ]; do
52 case $1 in
53 -i|--interval)
54 INTERVAL_MINUTES=$2
55 shift 2
56 ;;
57 -l|--limit)
58 SERIES_LIMIT=$2
59 shift 2
60 ;;
61 -d|--directory)
62 DOWNLOAD_PATH=$2
63 shift 2
64 ;;
65 -p|--project)
66 PROJECT=$2
67 shift 2
68 ;;
69 -s|--server)
70 SERVER=$2
71 shift 2
72 ;;
73 -t|--tested-series)
74 SERIES_TEST_LOG=$2
75 shift 2
76 ;;
77 -h|--help)
78 help
79 ;;
80 *)
81 echo "Unknown option $1"
82 help
83 ;;
84 esac
85done
86
87# The time this script is running at
88START_TIME=$(date --date "now" +"%Y-%m-%dT%H:%M:%S")
89
90# the corresponding timestamp we want to check against for new patch series
91SERIES_CHECK_LIMIT=$(date --date "now - ${INTERVAL_MINUTES} minutes" +"%Y-%m-%dT%H:%M:%S")
92
93echo "Start time is $START_TIME"
94echo "Series check limit is $SERIES_CHECK_LIMIT"
95
96# Create DOWNLOAD_PATH if it doesn't exist
97if [ ! -d "$DOWNLOAD_PATH" ]; then
98 mkdir "${DOWNLOAD_PATH}"
99fi
100
101# Create SERIES_TEST_LOG if it doesn't exist
102if [ ! -f "$SERIES_TEST_LOG" ]; then
103 touch "${SERIES_TEST_LOG}"
104fi
105
106# Retrieve a list of series IDs from the 'git-pw series list' output. The API
107# supports a maximum of 250 results, so make sure we allow that when required
108SERIES_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')
109
110if [ -z "$SERIES_LIST" ]; then
111 echo "No new series for project ${PROJECT} since ${SERIES_CHECK_LIMIT}"
112 exit 0
113fi
114
115# Check each series ID
116for SERIES in $SERIES_LIST; do
117 # Download the series only if it's not found in the SERIES_TEST_LOG
118 if ! grep -w --quiet "${SERIES}" "${SERIES_TEST_LOG}"; then
119 echo "Downloading $SERIES..."
120 git-pw series download --separate "${SERIES}" "${DOWNLOAD_PATH}"
121 echo "${SERIES}" >> "${SERIES_TEST_LOG}"
122 else
123 echo "Already tested ${SERIES}. Skipping..."
124 fi
125done
diff --git a/scripts/patchtest-send-results b/scripts/patchtest-send-results
new file mode 100755
index 0000000000..2a2c57a10e
--- /dev/null
+++ b/scripts/patchtest-send-results
@@ -0,0 +1,93 @@
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# Note that this script is currently under development and has been
7# hard-coded with default values for testing purposes. This script
8# should not be used without changing the default recipient, at minimum.
9#
10# Copyright (C) 2023 BayLibre Inc.
11#
12# This program is free software; you can redistribute it and/or modify
13# it under the terms of the GNU General Public License version 2 as
14# published by the Free Software Foundation.
15#
16# This program is distributed in the hope that it will be useful,
17# but WITHOUT ANY WARRANTY; without even the implied warranty of
18# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19# GNU General Public License for more details.
20#
21# You should have received a copy of the GNU General Public License along
22# with this program; if not, write to the Free Software Foundation, Inc.,
23# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
24#
25# Author: Trevor Gamblin <tgamblin@baylibre.com>
26#
27
28import argparse
29import boto3
30import configparser
31import mailbox
32import os
33import sys
34
35greeting = """Thank you for your submission. Patchtest identified one
36or more issues with the patch. Please see the log below for
37more information:\n\n---\n"""
38
39suggestions = """\n---\n\nPlease address the issues identified and
40submit a new revision of the patch, or alternatively, reply to this
41email with an explanation of why the patch format should be accepted.
42Note that patchtest may report failures in the merge-on-head test for
43patches that are part of a series if they rely on changes from
44preceeding entries.
45
46If you believe these results are due to an error in patchtest, please
47submit a bug at https://bugzilla.yoctoproject.org/ (use the 'Patchtest'
48category under 'Yocto Project Subprojects'). Thank you!"""
49
50parser = argparse.ArgumentParser(description="Send patchtest results to a submitter for a given patch")
51parser.add_argument("-p", "--patch", dest="patch", required=True, help="The patch file to summarize")
52args = parser.parse_args()
53
54if not os.path.exists(args.patch):
55 print(f"Patch '{args.patch}' not found - did you provide the right path?")
56 sys.exit(1)
57elif not os.path.exists(args.patch + ".testresult"):
58 print(f"Found patch '{args.patch}' but '{args.patch}.testresult' was not present. Have you run patchtest on the patch?")
59 sys.exit(1)
60
61result_file = args.patch + ".testresult"
62result_basename = os.path.basename(args.patch)
63testresult = None
64
65with open(result_file, "r") as f:
66 testresult = f.read()
67
68reply_contents = greeting + testresult + suggestions
69subject_line = f"Patchtest results for {result_basename}"
70
71if "FAIL" in testresult:
72 ses_client = boto3.client('ses', region_name='us-west-2')
73 response = ses_client.send_email(
74 Source='patchtest@automation.yoctoproject.org',
75 Destination={
76 'ToAddresses': ['test-list@lists.yoctoproject.org'],
77 },
78 ReplyToAddresses=['test-list@lists.yoctoproject.org'],
79 Message={
80 'Subject': {
81 'Data': subject_line,
82 'Charset': 'utf-8'
83 },
84 'Body': {
85 'Text': {
86 'Data': reply_contents,
87 'Charset': 'utf-8'
88 }
89 }
90 }
91 )
92else:
93 print(f"No failures identified for {args.patch}.")
diff --git a/scripts/patchtest-setup-sharedir b/scripts/patchtest-setup-sharedir
new file mode 100755
index 0000000000..a1497987cb
--- /dev/null
+++ b/scripts/patchtest-setup-sharedir
@@ -0,0 +1,95 @@
1#!/bin/bash -e
2#
3# patchtest-setup-sharedir: Setup a directory for storing mboxes and
4# repositories to be shared with the guest machine, including updates to
5# the repos if the directory already exists
6#
7# Copyright (C) 2023 BayLibre Inc.
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: Trevor Gamblin <tgamblin@baylibre.com>
23
24# poky repository
25POKY_REPO="https://git.yoctoproject.org/poky"
26
27# patchtest repository
28PATCHTEST_REPO="https://git.yoctoproject.org/patchtest"
29
30# the name of the directory
31SHAREDIR="patchtest_share"
32
33help()
34{
35 echo "Usage: patchtest-setup-sharedir [ -d | --directory SHAREDIR ]
36 [ -p | --patchtest PATCHTEST_REPO ]
37 [ -y | --poky POKY_REPO ]"
38 exit 2
39}
40
41while [ "$1" != "" ]; do
42 case $1 in
43 -d|--directory)
44 SHAREDIR=$2
45 shift 2
46 ;;
47 -p|--patchtest)
48 PATCHTEST_REPO=$2
49 shift 2
50 ;;
51 -y|--poky)
52 POKY_REPO=$2
53 shift 2
54 ;;
55 -h|--help)
56 help
57 ;;
58 *)
59 echo "Unknown option $1"
60 help
61 ;;
62 esac
63done
64
65# define MBOX_DIR where the patch series will be stored by
66# get-latest-series
67MBOX_DIR="${SHAREDIR}/mboxes"
68
69# Create SHAREDIR if it doesn't exist
70if [ ! -d "$SHAREDIR" ]; then
71 mkdir -p "${SHAREDIR}"
72 echo "Created ${SHAREDIR}"
73fi
74
75# Create the mboxes directory if it doesn't exist
76if [ ! -d "$MBOX_DIR" ]; then
77 mkdir -p "${MBOX_DIR}"
78 echo "Created ${MBOX_DIR}"
79fi
80
81# clone poky if it's not already present; otherwise, update it
82if [ ! -d "$POKY_REPO" ]; then
83 BASENAME=$(basename ${POKY_REPO})
84 git clone "${POKY_REPO}" "${SHAREDIR}/${BASENAME}"
85else
86 (cd "${SHAREDIR}/$BASENAME" && git pull)
87fi
88
89# clone patchtest if it's not already present; otherwise, update it
90if [ ! -d "$PATCHTEST_REPO" ]; then
91 BASENAME=$(basename ${PATCHTEST_REPO})
92 git clone "${PATCHTEST_REPO}" "${SHAREDIR}/${BASENAME}"
93else
94 (cd "${SHAREDIR}/$BASENAME" && git pull)
95fi
diff --git a/scripts/patchtest.README b/scripts/patchtest.README
new file mode 100644
index 0000000000..689d513df5
--- /dev/null
+++ b/scripts/patchtest.README
@@ -0,0 +1,152 @@
1# Patchtest
2
3## Introduction
4
5Patchtest is a test framework for community patches based on the standard
6unittest python module. As input, it needs tree elements to work properly:
7a patch in mbox format (either created with `git format-patch` or fetched
8from 'patchwork'), a test suite and a target repository.
9
10The first test suite intended to be used with patchtest is found in the
11openembedded-core repository [1] targeted for patches that get into the
12openembedded-core mailing list [2]. This suite is also intended as a
13baseline for development of similar suites for other layers as needed.
14
15Patchtest can either run on a host or a guest machine, depending on which
16environment the execution needs to be done. If you plan to test your own patches
17(a good practice before these are sent to the mailing list), the easiest way is
18to install and execute on your local host; in the other hand, if automatic
19testing is intended, the guest method is strongly recommended. The guest
20method requires the use of the patchtest layer, in addition to the tools
21available in oe-core: https://git.yoctoproject.org/patchtest/
22
23## Installation
24
25As a tool for use with the Yocto Project, the [quick start guide](https://docs.yoctoproject.org/brief-yoctoprojectqs/index.html)
26contains the necessary prerequisites for a basic project. In addition,
27patchtest relies on the following Python modules:
28
29- boto3 (for sending automated results emails only)
30- git-pw>=2.5.0
31- jinja2
32- pylint
33- pyparsing>=3.0.9
34- unidiff
35
36These can be installed by running `pip install -r
37meta/lib/patchtest/requirements.txt`. Note that git-pw is not
38automatically added to the user's PATH; by default, it is installed at
39~/.local/bin/git-pw.
40
41For git-pw (and therefore scripts such as patchtest-get--series) to work, you need
42to provide a Patchwork instance in your user's .gitconfig, like so (the project
43can be specified using the --project argument):
44
45 git config --global pw.server "https://patchwork.yoctoproject.org/api/1.2/"
46
47To work with patchtest, you should have the following repositories cloned:
48
491. https://git.openembedded.org/openembedded-core/ (or https://git.yoctoproject.org/poky/)
502. https://git.openembedded.org/bitbake/ (if not using poky)
513. https://git.yoctoproject.org/patchtest (if using guest mode)
52
53## Usage
54
55### Obtaining Patches
56
57Patch files can be obtained directly from cloned repositories using `git
58format-patch -N` (where N is the number of patches starting from HEAD to
59generate). git-pw can also be used with filters for users, patch/series IDs,
60and timeboxes if specific patches are desired. For more information, see the
61git-pw [documentation](https://patchwork.readthedocs.io/projects/git-pw/en/latest/).
62
63Alternatively, `scripts/patchtest-get-series` can be used to pull mbox files from
64the Patchwork instance configured previously in .gitconfig. It uses a log file
65called ".series_test.log" to store and compare series IDs so that the same
66versions of a patch are not tested multiple times unintentionally. By default,
67it will pull up to five patch series from the last 30 minutes using oe-core as
68the target project, but these parameters can be configured using the `--limit`,
69`--interval`, and `--project` arguments respectively. For more information, run
70`patchtest-get-series -h`.
71
72### Host Mode
73
74To run patchtest on the host, do the following:
75
761. In openembedded-core/poky, do `source oe-init-build-env`
772. Generate patch files from the target repository by doing `git-format patch -N`,
78 where N is the number of patches starting at HEAD, or by using git-pw
79 or patchtest-get-series
803. Run patchtest on a patch file by doing the following:
81
82 patchtest --patch /path/to/patch/file /path/to/target/repo /path/to/tests/directory
83
84 or, if you have stored the patch files in a directory, do:
85
86 patchtest --directory /path/to/patch/directory /path/to/target/repo /path/to/tests/directory
87
88 For example, to test `master-gcc-Fix--fstack-protector-issue-on-aarch64.patch` against the oe-core test suite:
89
90 patchtest --patch master-gcc-Fix--fstack-protector-issue-on-aarch64.patch /path/to/openembedded-core /path/to/openembedded-core/meta/lib/patchtest/tests
91
92### Guest Mode
93
94Patchtest's guest mode has been refactored to more closely mirror the
95typical Yocto Project image build workflow, but there are still some key
96differences to keep in mind. The primary objective is to provide a level
97of isolation from the host when testing patches pulled automatically
98from the mailing lists. When executed this way, the test process is
99essentially running random code from the internet and could be
100catastrophic if malicious bits or even poorly-handled edge cases aren't
101protected against. In order to use this mode, the
102https://git.yoctoproject.org/patchtest/ repository must be cloned and
103the meta-patchtest layer added to bblayers.conf.
104
105The general flow of guest mode is:
106
1071. Run patchtest-setup-sharedir --directory <dirname> to create a
108 directory for mounting
1092. Collect patches via patchtest-get-series (or other manual step) into the
110 <dirname>/mboxes path
1113. Ensure that a user with ID 1200 has appropriate read/write
112 permissions to <dirname> and <dirname>/mboxes, so that the
113 "patchtest" user in the core-image-patchtest image can function
1144. Build the core-image-patchtest image
1155. Run the core-image-patchtest image with the mounted sharedir, like
116 so:
117 `runqemu kvm nographic qemuparams="-snapshot -fsdev
118 local,id=test_mount,path=/workspace/yocto/poky/build/patchtestdir,security_model=mapped
119 -device virtio-9p-pci,fsdev=test_mount,mount_tag=test_mount -smp 4 -m
120 2048"`
121
122Patchtest runs as an initscript for the core-image-patchtest image and
123shuts down after completion, so there is no input required from a user
124during operation. Unlike in host mode, the guest is designed to
125automatically generate test result files, in the same directory as the
126targeted patch files but with .testresult as an extension. These contain
127the entire output of the patchtest run for each respective pass,
128including the PASS, FAIL, and SKIP indicators for each test run.
129
130## Contributing
131
132The yocto mailing list (yocto@lists.yoctoproject.org) is used for questions,
133comments and patch review. It is subscriber only, so please register before
134posting.
135
136Send pull requests to yocto@lists.yoctoproject.org with '[patchtest]' in the
137subject.
138
139When sending single patches, please use something like:
140
141 git send-email -M -1 --to=yocto@lists.yoctoproject.org --subject-prefix=patchtest][PATCH
142
143## Maintenance
144-----------
145
146Maintainers:
147 Trevor Gamblin <tgamblin@baylibre.com>
148
149## Links
150-----
151[1] https://git.openembedded.org/openembedded-core/
152[2] https://www.yoctoproject.org/community/mailing-lists/