summaryrefslogtreecommitdiffstats
path: root/meta/lib/patchtest/tests
diff options
context:
space:
mode:
Diffstat (limited to 'meta/lib/patchtest/tests')
-rw-r--r--meta/lib/patchtest/tests/base.py49
-rw-r--r--meta/lib/patchtest/tests/pyparsing/common.py26
-rw-r--r--meta/lib/patchtest/tests/pyparsing/parse_cve_tags.py18
-rw-r--r--meta/lib/patchtest/tests/pyparsing/parse_shortlog.py14
-rw-r--r--meta/lib/patchtest/tests/pyparsing/parse_signed_off_by.py22
-rw-r--r--meta/lib/patchtest/tests/pyparsing/parse_upstream_status.py24
-rw-r--r--meta/lib/patchtest/tests/test_mbox.py114
-rw-r--r--meta/lib/patchtest/tests/test_metadata.py107
-rw-r--r--meta/lib/patchtest/tests/test_patch.py100
-rw-r--r--meta/lib/patchtest/tests/test_python_pylint.py2
10 files changed, 224 insertions, 252 deletions
diff --git a/meta/lib/patchtest/tests/base.py b/meta/lib/patchtest/tests/base.py
index 424e61b5be..919ca136bb 100644
--- a/meta/lib/patchtest/tests/base.py
+++ b/meta/lib/patchtest/tests/base.py
@@ -8,20 +8,23 @@ import unittest
8import logging 8import logging
9import json 9import json
10import unidiff 10import unidiff
11from data import PatchTestInput 11from patchtest_parser import PatchtestParser
12import mailbox 12import mailbox
13import patchtest_patterns
13import collections 14import collections
14import sys 15import sys
15import os 16import os
16import re 17import re
17 18
18sys.path.insert(0, os.path.join(os.path.dirname(__file__), 'pyparsing')) 19logger = logging.getLogger("patchtest")
20debug = logger.debug
21info = logger.info
22warn = logger.warn
23error = logger.error
19 24
20logger = logging.getLogger('patchtest') 25Commit = collections.namedtuple(
21debug=logger.debug 26 "Commit", ["author", "subject", "commit_message", "shortlog", "payload"]
22info=logger.info 27)
23warn=logger.warn
24error=logger.error
25 28
26Commit = collections.namedtuple('Commit', ['author', 'subject', 'commit_message', 'shortlog', 'payload']) 29Commit = collections.namedtuple('Commit', ['author', 'subject', 'commit_message', 'shortlog', 'payload'])
27 30
@@ -34,10 +37,6 @@ class PatchtestOEError(Exception):
34class Base(unittest.TestCase): 37class Base(unittest.TestCase):
35 # if unit test fails, fail message will throw at least the following JSON: {"id": <testid>} 38 # if unit test fails, fail message will throw at least the following JSON: {"id": <testid>}
36 39
37 endcommit_messages_regex = re.compile(r'\(From \w+-\w+ rev:|(?<!\S)Signed-off-by|(?<!\S)---\n')
38 patchmetadata_regex = re.compile(r'-{3} \S+|\+{3} \S+|@{2} -\d+,\d+ \+\d+,\d+ @{2} \S+')
39
40
41 @staticmethod 40 @staticmethod
42 def msg_to_commit(msg): 41 def msg_to_commit(msg):
43 payload = msg.get_payload() 42 payload = msg.get_payload()
@@ -50,7 +49,7 @@ class Base(unittest.TestCase):
50 @staticmethod 49 @staticmethod
51 def commit_message(payload): 50 def commit_message(payload):
52 commit_message = payload.__str__() 51 commit_message = payload.__str__()
53 match = Base.endcommit_messages_regex.search(payload) 52 match = patchtest_patterns.endcommit_messages_regex.search(payload)
54 if match: 53 if match:
55 commit_message = payload[:match.start()] 54 commit_message = payload[:match.start()]
56 return commit_message 55 return commit_message
@@ -66,13 +65,15 @@ class Base(unittest.TestCase):
66 def setUpClass(cls): 65 def setUpClass(cls):
67 66
68 # General objects: mailbox.mbox and patchset 67 # General objects: mailbox.mbox and patchset
69 cls.mbox = mailbox.mbox(PatchTestInput.repo.patch) 68 cls.mbox = mailbox.mbox(PatchtestParser.repo.patch.path)
70 69
71 # Patch may be malformed, so try parsing it 70 # Patch may be malformed, so try parsing it
72 cls.unidiff_parse_error = '' 71 cls.unidiff_parse_error = ''
73 cls.patchset = None 72 cls.patchset = None
74 try: 73 try:
75 cls.patchset = unidiff.PatchSet.from_filename(PatchTestInput.repo.patch, encoding=u'UTF-8') 74 cls.patchset = unidiff.PatchSet.from_filename(
75 PatchtestParser.repo.patch.path, encoding="UTF-8"
76 )
76 except unidiff.UnidiffParseError as upe: 77 except unidiff.UnidiffParseError as upe:
77 cls.patchset = [] 78 cls.patchset = []
78 cls.unidiff_parse_error = str(upe) 79 cls.unidiff_parse_error = str(upe)
@@ -149,7 +150,7 @@ class Metadata(Base):
149 150
150 # import relevant libraries 151 # import relevant libraries
151 try: 152 try:
152 scripts_path = os.path.join(PatchTestInput.repodir, 'scripts', 'lib') 153 scripts_path = os.path.join(PatchtestParser.repodir, "scripts", "lib")
153 if scripts_path not in sys.path: 154 if scripts_path not in sys.path:
154 sys.path.insert(0, scripts_path) 155 sys.path.insert(0, scripts_path)
155 import scriptpath 156 import scriptpath
@@ -224,11 +225,23 @@ class Metadata(Base):
224 for patch in patchset: 225 for patch in patchset:
225 if patch.path.endswith('.bb') or patch.path.endswith('.bbappend') or patch.path.endswith('.inc'): 226 if patch.path.endswith('.bb') or patch.path.endswith('.bbappend') or patch.path.endswith('.inc'):
226 if patch.is_added_file: 227 if patch.is_added_file:
227 added_paths.append(os.path.join(os.path.abspath(PatchTestInput.repodir), patch.path)) 228 added_paths.append(
229 os.path.join(
230 os.path.abspath(PatchtestParser.repodir), patch.path
231 )
232 )
228 elif patch.is_modified_file: 233 elif patch.is_modified_file:
229 modified_paths.append(os.path.join(os.path.abspath(PatchTestInput.repodir), patch.path)) 234 modified_paths.append(
235 os.path.join(
236 os.path.abspath(PatchtestParser.repodir), patch.path
237 )
238 )
230 elif patch.is_removed_file: 239 elif patch.is_removed_file:
231 removed_paths.append(os.path.join(os.path.abspath(PatchTestInput.repodir), patch.path)) 240 removed_paths.append(
241 os.path.join(
242 os.path.abspath(PatchtestParser.repodir), patch.path
243 )
244 )
232 245
233 data = cls.tinfoil.cooker.recipecaches[''].pkg_fn.items() 246 data = cls.tinfoil.cooker.recipecaches[''].pkg_fn.items()
234 247
diff --git a/meta/lib/patchtest/tests/pyparsing/common.py b/meta/lib/patchtest/tests/pyparsing/common.py
deleted file mode 100644
index cbce4c38bc..0000000000
--- a/meta/lib/patchtest/tests/pyparsing/common.py
+++ /dev/null
@@ -1,26 +0,0 @@
1# common pyparsing variables
2#
3# Copyright (C) 2016 Intel Corporation
4#
5# SPDX-License-Identifier: GPL-2.0-only
6
7import pyparsing
8
9# general
10colon = pyparsing.Literal(":")
11start = pyparsing.LineStart()
12end = pyparsing.LineEnd()
13at = pyparsing.Literal("@")
14lessthan = pyparsing.Literal("<")
15greaterthan = pyparsing.Literal(">")
16opensquare = pyparsing.Literal("[")
17closesquare = pyparsing.Literal("]")
18inappropriate = pyparsing.CaselessLiteral("Inappropriate")
19submitted = pyparsing.CaselessLiteral("Submitted")
20
21# word related
22nestexpr = pyparsing.nestedExpr(opener='[', closer=']')
23inappropriateinfo = pyparsing.Literal("Inappropriate") + nestexpr
24submittedinfo = pyparsing.Literal("Submitted") + nestexpr
25word = pyparsing.Word(pyparsing.alphas)
26worddot = pyparsing.Word(pyparsing.alphas+".")
diff --git a/meta/lib/patchtest/tests/pyparsing/parse_cve_tags.py b/meta/lib/patchtest/tests/pyparsing/parse_cve_tags.py
deleted file mode 100644
index f7fb82ec2b..0000000000
--- a/meta/lib/patchtest/tests/pyparsing/parse_cve_tags.py
+++ /dev/null
@@ -1,18 +0,0 @@
1# signed-off-by pyparsing definition
2#
3# Copyright (C) 2016 Intel Corporation
4#
5# SPDX-License-Identifier: GPL-2.0-only
6
7
8import pyparsing
9import common
10
11name = pyparsing.Regex('\S+.*(?= <)')
12username = pyparsing.OneOrMore(common.worddot)
13domain = pyparsing.OneOrMore(common.worddot)
14cve = pyparsing.Regex('CVE\-\d{4}\-\d+')
15cve_mark = pyparsing.Literal("CVE:")
16
17cve_tag = pyparsing.AtLineStart(cve_mark + cve)
18patch_cve_tag = pyparsing.AtLineStart("+" + cve_mark + cve)
diff --git a/meta/lib/patchtest/tests/pyparsing/parse_shortlog.py b/meta/lib/patchtest/tests/pyparsing/parse_shortlog.py
deleted file mode 100644
index 30d3ab35b3..0000000000
--- a/meta/lib/patchtest/tests/pyparsing/parse_shortlog.py
+++ /dev/null
@@ -1,14 +0,0 @@
1# subject pyparsing definition
2#
3# Copyright (C) 2016 Intel Corporation
4#
5# SPDX-License-Identifier: GPL-2.0-only
6
7# NOTE:This is an oversimplified syntax of the mbox's summary
8
9import pyparsing
10import common
11
12target = pyparsing.OneOrMore(pyparsing.Word(pyparsing.printables.replace(':','')))
13summary = pyparsing.OneOrMore(pyparsing.Word(pyparsing.printables))
14shortlog = common.start + target + common.colon + summary + common.end
diff --git a/meta/lib/patchtest/tests/pyparsing/parse_signed_off_by.py b/meta/lib/patchtest/tests/pyparsing/parse_signed_off_by.py
deleted file mode 100644
index 692ebec3ff..0000000000
--- a/meta/lib/patchtest/tests/pyparsing/parse_signed_off_by.py
+++ /dev/null
@@ -1,22 +0,0 @@
1# signed-off-by pyparsing definition
2#
3# Copyright (C) 2016 Intel Corporation
4#
5# SPDX-License-Identifier: GPL-2.0-only
6
7
8import pyparsing
9import common
10
11name = pyparsing.Regex('\S+.*(?= <)')
12username = pyparsing.OneOrMore(common.worddot)
13domain = pyparsing.OneOrMore(common.worddot)
14
15# taken from https://pyparsing-public.wikispaces.com/Helpful+Expressions
16email = pyparsing.Regex(r"(?P<user>[A-Za-z0-9._%+-]+)@(?P<hostname>[A-Za-z0-9.-]+)\.(?P<domain>[A-Za-z]{2,})")
17
18email_enclosed = common.lessthan + email + common.greaterthan
19
20signed_off_by_mark = pyparsing.Literal("Signed-off-by:")
21signed_off_by = pyparsing.AtLineStart(signed_off_by_mark + name + email_enclosed)
22patch_signed_off_by = pyparsing.AtLineStart("+" + signed_off_by_mark + name + email_enclosed)
diff --git a/meta/lib/patchtest/tests/pyparsing/parse_upstream_status.py b/meta/lib/patchtest/tests/pyparsing/parse_upstream_status.py
deleted file mode 100644
index bc6c427c4c..0000000000
--- a/meta/lib/patchtest/tests/pyparsing/parse_upstream_status.py
+++ /dev/null
@@ -1,24 +0,0 @@
1# upstream-status pyparsing definition
2#
3# Copyright (C) 2016 Intel Corporation
4#
5# SPDX-License-Identifier: GPL-2.0-only
6
7
8import common
9import pyparsing
10
11upstream_status_literal_valid_status = ["Pending", "Backport", "Denied", "Inappropriate", "Submitted"]
12upstream_status_nonliteral_valid_status = ["Pending", "Backport", "Denied", "Inappropriate [reason]", "Submitted [where]"]
13
14upstream_status_valid_status = pyparsing.Or(
15 [pyparsing.Literal(status) for status in upstream_status_literal_valid_status]
16)
17
18upstream_status_mark = pyparsing.Literal("Upstream-Status")
19inappropriate_status_mark = common.inappropriate
20submitted_status_mark = common.submitted
21
22upstream_status = common.start + upstream_status_mark + common.colon + upstream_status_valid_status
23upstream_status_inappropriate_info = common.start + upstream_status_mark + common.colon + common.inappropriateinfo
24upstream_status_submitted_info = common.start + upstream_status_mark + common.colon + common.submittedinfo
diff --git a/meta/lib/patchtest/tests/test_mbox.py b/meta/lib/patchtest/tests/test_mbox.py
index 0b623b7d17..dab733ea77 100644
--- a/meta/lib/patchtest/tests/test_mbox.py
+++ b/meta/lib/patchtest/tests/test_mbox.py
@@ -6,15 +6,15 @@
6 6
7import base 7import base
8import collections 8import collections
9import parse_shortlog 9import patchtest_patterns
10import parse_signed_off_by
11import pyparsing 10import pyparsing
11import re
12import subprocess 12import subprocess
13from data import PatchTestInput 13from patchtest_parser import PatchtestParser
14 14
15def headlog(): 15def headlog():
16 output = subprocess.check_output( 16 output = subprocess.check_output(
17 "cd %s; git log --pretty='%%h#%%aN#%%cD:#%%s' -1" % PatchTestInput.repodir, 17 "cd %s; git log --pretty='%%h#%%aN#%%cD:#%%s' -1" % PatchtestParser.repodir,
18 universal_newlines=True, 18 universal_newlines=True,
19 shell=True 19 shell=True
20 ) 20 )
@@ -22,20 +22,6 @@ def headlog():
22 22
23class TestMbox(base.Base): 23class TestMbox(base.Base):
24 24
25 auh_email = 'auh@auh.yoctoproject.org'
26
27 invalids = [pyparsing.Regex("^Upgrade Helper.+"),
28 pyparsing.Regex(auh_email),
29 pyparsing.Regex("uh@not\.set"),
30 pyparsing.Regex("\S+@example\.com")]
31
32 rexp_detect = pyparsing.Regex('\[\s?YOCTO.*\]')
33 rexp_validation = pyparsing.Regex('\[(\s?YOCTO\s?#\s?(\d+)\s?,?)+\]')
34 revert_shortlog_regex = pyparsing.Regex('Revert\s+".*"')
35 signoff_prog = parse_signed_off_by.signed_off_by
36 revert_shortlog_regex = pyparsing.Regex('Revert\s+".*"')
37 maxlength = 90
38
39 # base paths of main yocto project sub-projects 25 # base paths of main yocto project sub-projects
40 paths = { 26 paths = {
41 'oe-core': ['meta-selftest', 'meta-skeleton', 'meta', 'scripts'], 27 'oe-core': ['meta-selftest', 'meta-skeleton', 'meta', 'scripts'],
@@ -57,16 +43,18 @@ class TestMbox(base.Base):
57 43
58 44
59 def test_signed_off_by_presence(self): 45 def test_signed_off_by_presence(self):
60 for commit in TestMbox.commits: 46 for commit in self.commits:
61 # skip those patches that revert older commits, these do not required the tag presence 47 # skip those patches that revert older commits, these do not required the tag presence
62 if self.revert_shortlog_regex.search_string(commit.shortlog): 48 if patchtest_patterns.mbox_revert_shortlog_regex.search_string(commit.shortlog):
63 continue 49 continue
64 if not self.signoff_prog.search_string(commit.payload): 50 if not patchtest_patterns.signed_off_by.search_string(commit.payload):
65 self.fail('Mbox is missing Signed-off-by. Add it manually or with "git commit --amend -s"', 51 self.fail(
66 commit=commit) 52 'Mbox is missing Signed-off-by. Add it manually or with "git commit --amend -s"',
53 commit=commit,
54 )
67 55
68 def test_shortlog_format(self): 56 def test_shortlog_format(self):
69 for commit in TestMbox.commits: 57 for commit in self.commits:
70 shortlog = commit.shortlog 58 shortlog = commit.shortlog
71 if not shortlog.strip(): 59 if not shortlog.strip():
72 self.skip('Empty shortlog, no reason to execute shortlog format test') 60 self.skip('Empty shortlog, no reason to execute shortlog format test')
@@ -75,40 +63,54 @@ class TestMbox(base.Base):
75 if shortlog.startswith('Revert "'): 63 if shortlog.startswith('Revert "'):
76 continue 64 continue
77 try: 65 try:
78 parse_shortlog.shortlog.parseString(shortlog) 66 patchtest_patterns.shortlog.parseString(shortlog)
79 except pyparsing.ParseException as pe: 67 except pyparsing.ParseException as pe:
80 self.fail('Commit shortlog (first line of commit message) should follow the format "<target>: <summary>"', 68 self.fail('Commit shortlog (first line of commit message) should follow the format "<target>: <summary>"',
81 commit=commit) 69 commit=commit)
82 70
83 def test_shortlog_length(self): 71 def test_shortlog_length(self):
84 for commit in TestMbox.commits: 72 for commit in self.commits:
85 # no reason to re-check on revert shortlogs 73 # no reason to re-check on revert shortlogs
86 shortlog = commit.shortlog 74 shortlog = re.sub('^(\[.*?\])+ ', '', commit.shortlog)
87 if shortlog.startswith('Revert "'): 75 if shortlog.startswith('Revert "'):
88 continue 76 continue
89 l = len(shortlog) 77 l = len(shortlog)
90 if l > self.maxlength: 78 if l > patchtest_patterns.mbox_shortlog_maxlength:
91 self.fail('Edit shortlog so that it is %d characters or less (currently %d characters)' % (self.maxlength, l), 79 self.fail(
92 commit=commit) 80 "Edit shortlog so that it is %d characters or less (currently %d characters)"
81 % (patchtest_patterns.mbox_shortlog_maxlength, l),
82 commit=commit,
83 )
93 84
94 def test_series_merge_on_head(self): 85 def test_series_merge_on_head(self):
95 self.skip("Merge test is disabled for now") 86 self.skip("Merge test is disabled for now")
96 if PatchTestInput.repo.branch != "master": 87 if PatchtestParser.repo.patch.branch != "master":
97 self.skip("Skipping merge test since patch is not intended for master branch. Target detected is %s" % PatchTestInput.repo.branch) 88 self.skip(
98 if not PatchTestInput.repo.ismerged: 89 "Skipping merge test since patch is not intended"
90 " for master branch. Target detected is %s"
91 % PatchtestParser.repo.patch.branch
92 )
93 if not PatchtestParser.repo.canbemerged:
99 commithash, author, date, shortlog = headlog() 94 commithash, author, date, shortlog = headlog()
100 self.fail('Series does not apply on top of target branch %s' % PatchTestInput.repo.branch, 95 self.fail(
101 data=[('Targeted branch', '%s (currently at %s)' % (PatchTestInput.repo.branch, commithash))]) 96 "Series does not apply on top of target branch %s"
97 % PatchtestParser.repo.patch.branch,
98 data=[
99 (
100 "Targeted branch",
101 "%s (currently at %s)"
102 % (PatchtestParser.repo.patch.branch, commithash),
103 )
104 ],
105 )
102 106
103 def test_target_mailing_list(self): 107 def test_target_mailing_list(self):
104 """In case of merge failure, check for other targeted projects""" 108 """Check for other targeted projects"""
105 if PatchTestInput.repo.ismerged:
106 self.skip('Series merged, no reason to check other mailing lists')
107 109
108 # a meta project may be indicted in the message subject, if this is the case, just fail 110 # a meta project may be indicted in the message subject, if this is the case, just fail
109 # TODO: there may be other project with no-meta prefix, we also need to detect these 111 # TODO: there may be other project with no-meta prefix, we also need to detect these
110 project_regex = pyparsing.Regex("\[(?P<project>meta-.+)\]") 112 project_regex = pyparsing.Regex("\[(?P<project>meta-.+)\]")
111 for commit in TestMbox.commits: 113 for commit in self.commits:
112 match = project_regex.search_string(commit.subject) 114 match = project_regex.search_string(commit.subject)
113 if match: 115 if match:
114 self.fail('Series sent to the wrong mailing list or some patches from the series correspond to different mailing lists', 116 self.fail('Series sent to the wrong mailing list or some patches from the series correspond to different mailing lists',
@@ -136,24 +138,42 @@ class TestMbox(base.Base):
136 data=[('Diff line',self.unidiff_parse_error)]) 138 data=[('Diff line',self.unidiff_parse_error)])
137 139
138 def test_commit_message_presence(self): 140 def test_commit_message_presence(self):
139 for commit in TestMbox.commits: 141 for commit in self.commits:
140 if not commit.commit_message.strip(): 142 if not commit.commit_message.strip():
141 self.fail('Please include a commit message on your patch explaining the change', commit=commit) 143 self.fail('Please include a commit message on your patch explaining the change', commit=commit)
142 144
145 # This may incorrectly report a failure if something such as a
146 # Python decorator is included in the commit message, but this
147 # scenario is much less common than the username case it is written
148 # to protect against
149 def test_commit_message_user_tags(self):
150 for commit in self.commits:
151 if patchtest_patterns.mbox_github_username.search_string(commit.commit_message):
152 self.fail('Mbox includes one or more GitHub-style username tags. Ensure that any "@" symbols are stripped out of usernames', commit=commit)
153
143 def test_bugzilla_entry_format(self): 154 def test_bugzilla_entry_format(self):
144 for commit in TestMbox.commits: 155 for commit in self.commits:
145 if not self.rexp_detect.search_string(commit.commit_message): 156 if not patchtest_patterns.mbox_bugzilla.search_string(commit.commit_message):
146 self.skip("No bug ID found") 157 self.skip("No bug ID found")
147 elif not self.rexp_validation.search_string(commit.commit_message): 158 elif not patchtest_patterns.mbox_bugzilla_validation.search_string(
148 self.fail('Bugzilla issue ID is not correctly formatted - specify it with format: "[YOCTO #<bugzilla ID>]"', commit=commit) 159 commit.commit_message
160 ):
161 self.fail(
162 'Bugzilla issue ID is not correctly formatted - specify it with format: "[YOCTO #<bugzilla ID>]"',
163 commit=commit,
164 )
149 165
150 def test_author_valid(self): 166 def test_author_valid(self):
151 for commit in self.commits: 167 for commit in self.commits:
152 for invalid in self.invalids: 168 for invalid in patchtest_patterns.invalid_submitters:
153 if invalid.search_string(commit.author): 169 if invalid.search_string(commit.author):
154 self.fail('Invalid author %s. Resend the series with a valid patch author' % commit.author, commit=commit) 170 self.fail('Invalid author %s. Resend the series with a valid patch author' % commit.author, commit=commit)
155 171
156 def test_non_auh_upgrade(self): 172 def test_non_auh_upgrade(self):
157 for commit in self.commits: 173 for commit in self.commits:
158 if self.auh_email in commit.payload: 174 if patchtest_patterns.auh_email in commit.commit_message:
159 self.fail('Invalid author %s. Resend the series with a valid patch author' % self.auh_email, commit=commit) 175 self.fail(
176 "Invalid author %s. Resend the series with a valid patch author"
177 % patchtest_patterns.auh_email,
178 commit=commit,
179 )
diff --git a/meta/lib/patchtest/tests/test_metadata.py b/meta/lib/patchtest/tests/test_metadata.py
index be609dbd04..2dee80b002 100644
--- a/meta/lib/patchtest/tests/test_metadata.py
+++ b/meta/lib/patchtest/tests/test_metadata.py
@@ -5,28 +5,16 @@
5# SPDX-License-Identifier: GPL-2.0-only 5# SPDX-License-Identifier: GPL-2.0-only
6 6
7import base 7import base
8import collections
8import os 9import os
10import patchtest_patterns
9import pyparsing 11import pyparsing
10from data import PatchTestInput, PatchTestDataStore 12from patchtest_parser import PatchtestParser
13
14# Data store commonly used to share values between pre and post-merge tests
15PatchTestDataStore = collections.defaultdict(str)
11 16
12class TestMetadata(base.Metadata): 17class TestMetadata(base.Metadata):
13 metadata_lic = 'LICENSE'
14 invalid_license = 'PATCHTESTINVALID'
15 metadata_chksum = 'LIC_FILES_CHKSUM'
16 license_var = 'LICENSE'
17 closed = 'CLOSED'
18 lictag_re = pyparsing.AtLineStart("License-Update:")
19 lic_chksum_added = pyparsing.AtLineStart("+" + metadata_chksum)
20 lic_chksum_removed = pyparsing.AtLineStart("-" + metadata_chksum)
21 add_mark = pyparsing.Regex('\+ ')
22 max_length = 200
23 metadata_src_uri = 'SRC_URI'
24 md5sum = 'md5sum'
25 sha256sum = 'sha256sum'
26 git_regex = pyparsing.Regex('^git\:\/\/.*')
27 metadata_summary = 'SUMMARY'
28 cve_check_ignore_var = 'CVE_CHECK_IGNORE'
29 cve_status_var = 'CVE_STATUS'
30 18
31 def test_license_presence(self): 19 def test_license_presence(self):
32 if not self.added: 20 if not self.added:
@@ -41,13 +29,13 @@ class TestMetadata(base.Metadata):
41 open_flag = 'a' 29 open_flag = 'a'
42 with open(auto_conf, open_flag) as fd: 30 with open(auto_conf, open_flag) as fd:
43 for pn in self.added: 31 for pn in self.added:
44 fd.write('LICENSE ??= "%s"\n' % self.invalid_license) 32 fd.write('LICENSE ??= "%s"\n' % patchtest_patterns.invalid_license)
45 33
46 no_license = False 34 no_license = False
47 for pn in self.added: 35 for pn in self.added:
48 rd = self.tinfoil.parse_recipe(pn) 36 rd = self.tinfoil.parse_recipe(pn)
49 license = rd.getVar(self.metadata_lic) 37 license = rd.getVar(patchtest_patterns.metadata_lic)
50 if license == self.invalid_license: 38 if license == patchtest_patterns.invalid_license:
51 no_license = True 39 no_license = True
52 break 40 break
53 41
@@ -74,11 +62,13 @@ class TestMetadata(base.Metadata):
74 # we are not interested in images 62 # we are not interested in images
75 if '/images/' in pathname: 63 if '/images/' in pathname:
76 continue 64 continue
77 lic_files_chksum = rd.getVar(self.metadata_chksum) 65 lic_files_chksum = rd.getVar(patchtest_patterns.metadata_chksum)
78 if rd.getVar(self.license_var) == self.closed: 66 if rd.getVar(patchtest_patterns.license_var) == patchtest_patterns.closed:
79 continue 67 continue
80 if not lic_files_chksum: 68 if not lic_files_chksum:
81 self.fail('%s is missing in newly added recipe' % self.metadata_chksum) 69 self.fail(
70 "%s is missing in newly added recipe" % patchtest_patterns.metadata_chksum
71 )
82 72
83 def test_lic_files_chksum_modified_not_mentioned(self): 73 def test_lic_files_chksum_modified_not_mentioned(self):
84 if not self.modified: 74 if not self.modified:
@@ -89,11 +79,13 @@ class TestMetadata(base.Metadata):
89 if patch.path.endswith('.patch'): 79 if patch.path.endswith('.patch'):
90 continue 80 continue
91 payload = str(patch) 81 payload = str(patch)
92 if (self.lic_chksum_added.search_string(payload) or self.lic_chksum_removed.search_string(payload)): 82 if patchtest_patterns.lic_chksum_added.search_string(
83 payload
84 ) or patchtest_patterns.lic_chksum_removed.search_string(payload):
93 # if any patch on the series contain reference on the metadata, fail 85 # if any patch on the series contain reference on the metadata, fail
94 for commit in self.commits: 86 for commit in self.commits:
95 if self.lictag_re.search_string(commit.commit_message): 87 if patchtest_patterns.lictag_re.search_string(commit.commit_message):
96 break 88 break
97 else: 89 else:
98 self.fail('LIC_FILES_CHKSUM changed without "License-Update:" tag and description in commit message') 90 self.fail('LIC_FILES_CHKSUM changed without "License-Update:" tag and description in commit message')
99 91
@@ -104,16 +96,22 @@ class TestMetadata(base.Metadata):
104 continue 96 continue
105 payload = str(patch) 97 payload = str(patch)
106 for line in payload.splitlines(): 98 for line in payload.splitlines():
107 if self.add_mark.search_string(line): 99 if patchtest_patterns.add_mark.search_string(line):
108 current_line_length = len(line[1:]) 100 current_line_length = len(line[1:])
109 if current_line_length > self.max_length: 101 if current_line_length > patchtest_patterns.patch_max_line_length:
110 self.fail('Patch line too long (current length %s, maximum is %s)' % (current_line_length, self.max_length), 102 self.fail(
111 data=[('Patch', patch.path), ('Line', '%s ...' % line[0:80])]) 103 "Patch line too long (current length %s, maximum is %s)"
104 % (current_line_length, patchtest_patterns.patch_max_line_length),
105 data=[
106 ("Patch", patch.path),
107 ("Line", "%s ..." % line[0:80]),
108 ],
109 )
112 110
113 def pretest_src_uri_left_files(self): 111 def pretest_src_uri_left_files(self):
114 # these tests just make sense on patches that can be merged 112 # these tests just make sense on patches that can be merged
115 if not PatchTestInput.repo.canbemerged: 113 if not PatchtestParser.repo.canbemerged:
116 self.skip('Patch cannot be merged') 114 self.skip("Patch cannot be merged")
117 if not self.modified: 115 if not self.modified:
118 self.skip('No modified recipes, skipping pretest') 116 self.skip('No modified recipes, skipping pretest')
119 117
@@ -123,12 +121,14 @@ class TestMetadata(base.Metadata):
123 if 'core-image' in pn: 121 if 'core-image' in pn:
124 continue 122 continue
125 rd = self.tinfoil.parse_recipe(pn) 123 rd = self.tinfoil.parse_recipe(pn)
126 PatchTestDataStore['%s-%s-%s' % (self.shortid(), self.metadata_src_uri, pn)] = rd.getVar(self.metadata_src_uri) 124 PatchTestDataStore[
125 "%s-%s-%s" % (self.shortid(), patchtest_patterns.metadata_src_uri, pn)
126 ] = rd.getVar(patchtest_patterns.metadata_src_uri)
127 127
128 def test_src_uri_left_files(self): 128 def test_src_uri_left_files(self):
129 # these tests just make sense on patches that can be merged 129 # these tests just make sense on patches that can be merged
130 if not PatchTestInput.repo.canbemerged: 130 if not PatchtestParser.repo.canbemerged:
131 self.skip('Patch cannot be merged') 131 self.skip("Patch cannot be merged")
132 if not self.modified: 132 if not self.modified:
133 self.skip('No modified recipes, skipping pretest') 133 self.skip('No modified recipes, skipping pretest')
134 134
@@ -138,11 +138,17 @@ class TestMetadata(base.Metadata):
138 if 'core-image' in pn: 138 if 'core-image' in pn:
139 continue 139 continue
140 rd = self.tinfoil.parse_recipe(pn) 140 rd = self.tinfoil.parse_recipe(pn)
141 PatchTestDataStore['%s-%s-%s' % (self.shortid(), self.metadata_src_uri, pn)] = rd.getVar(self.metadata_src_uri) 141 PatchTestDataStore[
142 "%s-%s-%s" % (self.shortid(), patchtest_patterns.metadata_src_uri, pn)
143 ] = rd.getVar(patchtest_patterns.metadata_src_uri)
142 144
143 for pn in self.modified: 145 for pn in self.modified:
144 pretest_src_uri = PatchTestDataStore['pre%s-%s-%s' % (self.shortid(), self.metadata_src_uri, pn)].split() 146 pretest_src_uri = PatchTestDataStore[
145 test_src_uri = PatchTestDataStore['%s-%s-%s' % (self.shortid(), self.metadata_src_uri, pn)].split() 147 "pre%s-%s-%s" % (self.shortid(), patchtest_patterns.metadata_src_uri, pn)
148 ].split()
149 test_src_uri = PatchTestDataStore[
150 "%s-%s-%s" % (self.shortid(), patchtest_patterns.metadata_src_uri, pn)
151 ].split()
146 152
147 pretest_files = set([os.path.basename(patch) for patch in pretest_src_uri if patch.startswith('file://')]) 153 pretest_files = set([os.path.basename(patch) for patch in pretest_src_uri if patch.startswith('file://')])
148 test_files = set([os.path.basename(patch) for patch in test_src_uri if patch.startswith('file://')]) 154 test_files = set([os.path.basename(patch) for patch in test_src_uri if patch.startswith('file://')])
@@ -175,23 +181,32 @@ class TestMetadata(base.Metadata):
175 if 'core-image' in pn: 181 if 'core-image' in pn:
176 continue 182 continue
177 rd = self.tinfoil.parse_recipe(pn) 183 rd = self.tinfoil.parse_recipe(pn)
178 summary = rd.getVar(self.metadata_summary) 184 summary = rd.getVar(patchtest_patterns.metadata_summary)
179 185
180 # "${PN} version ${PN}-${PR}" is the default, so fail if default 186 # "${PN} version ${PN}-${PR}" is the default, so fail if default
181 if summary.startswith('%s version' % pn): 187 if summary.startswith("%s version" % pn):
182 self.fail('%s is missing in newly added recipe' % self.metadata_summary) 188 self.fail(
189 "%s is missing in newly added recipe" % patchtest_patterns.metadata_summary
190 )
183 191
184 def test_cve_check_ignore(self): 192 def test_cve_check_ignore(self):
185 # Skip if we neither modified a recipe or target branches are not 193 # Skip if we neither modified a recipe or target branches are not
186 # Nanbield and newer. CVE_CHECK_IGNORE was first deprecated in Nanbield. 194 # Nanbield and newer. CVE_CHECK_IGNORE was first deprecated in Nanbield.
187 if not self.modified or PatchTestInput.repo.branch == "kirkstone" or PatchTestInput.repo.branch == "dunfell": 195 if (
188 self.skip('No modified recipes or older target branch, skipping test') 196 not self.modified
197 or PatchtestParser.repo.patch.branch == "kirkstone"
198 or PatchtestParser.repo.patch.branch == "dunfell"
199 ):
200 self.skip("No modified recipes or older target branch, skipping test")
189 for pn in self.modified: 201 for pn in self.modified:
190 # we are not interested in images 202 # we are not interested in images
191 if 'core-image' in pn: 203 if 'core-image' in pn:
192 continue 204 continue
193 rd = self.tinfoil.parse_recipe(pn) 205 rd = self.tinfoil.parse_recipe(pn)
194 cve_check_ignore = rd.getVar(self.cve_check_ignore_var) 206 cve_check_ignore = rd.getVar(patchtest_patterns.cve_check_ignore_var)
195 207
196 if cve_check_ignore is not None: 208 if cve_check_ignore is not None:
197 self.fail('%s is deprecated and should be replaced by %s' % (self.cve_check_ignore_var, self.cve_status_var)) 209 self.fail(
210 "%s is deprecated and should be replaced by %s"
211 % (patchtest_patterns.cve_check_ignore_var, patchtest_patterns.cve_status_var)
212 )
diff --git a/meta/lib/patchtest/tests/test_patch.py b/meta/lib/patchtest/tests/test_patch.py
index d7187a0cb1..d08b8a5019 100644
--- a/meta/lib/patchtest/tests/test_patch.py
+++ b/meta/lib/patchtest/tests/test_patch.py
@@ -7,16 +7,11 @@
7 7
8import base 8import base
9import os 9import os
10import parse_signed_off_by 10import patchtest_patterns
11import parse_upstream_status
12import pyparsing 11import pyparsing
13 12
14class TestPatch(base.Base): 13class TestPatch(base.Base):
15 14
16 re_cve_pattern = pyparsing.Regex("CVE\-\d{4}\-\d+")
17 re_cve_payload_tag = pyparsing.Regex("\+CVE:(\s+CVE\-\d{4}\-\d+)+")
18 upstream_status_regex = pyparsing.AtLineStart("+" + "Upstream-Status")
19
20 @classmethod 15 @classmethod
21 def setUpClassLocal(cls): 16 def setUpClassLocal(cls):
22 cls.newpatches = [] 17 cls.newpatches = []
@@ -25,17 +20,17 @@ class TestPatch(base.Base):
25 if patch.path.endswith('.patch') and patch.is_added_file: 20 if patch.path.endswith('.patch') and patch.is_added_file:
26 cls.newpatches.append(patch) 21 cls.newpatches.append(patch)
27 22
28 cls.mark = str(parse_signed_off_by.signed_off_by_mark).strip('"') 23 cls.mark = str(patchtest_patterns.signed_off_by_prefix).strip('"')
29 24
30 # match PatchSignedOffBy.mark with '+' preceding it 25 # match PatchSignedOffBy.mark with '+' preceding it
31 cls.prog = parse_signed_off_by.patch_signed_off_by 26 cls.prog = patchtest_patterns.patch_signed_off_by
32 27
33 def setUp(self): 28 def setUp(self):
34 if self.unidiff_parse_error: 29 if self.unidiff_parse_error:
35 self.skip('Parse error %s' % self.unidiff_parse_error) 30 self.skip('Parse error %s' % self.unidiff_parse_error)
36 31
37 self.valid_status = ', '.join(parse_upstream_status.upstream_status_nonliteral_valid_status) 32 self.valid_status = ", ".join(patchtest_patterns.upstream_status_nonliteral_valid_status)
38 self.standard_format = 'Upstream-Status: <Valid status>' 33 self.standard_format = "Upstream-Status: <Valid status>"
39 34
40 # we are just interested in series that introduce CVE patches, thus discard other 35 # we are just interested in series that introduce CVE patches, thus discard other
41 # possibilities: modification to current CVEs, patch directly introduced into the 36 # possibilities: modification to current CVEs, patch directly introduced into the
@@ -50,31 +45,62 @@ class TestPatch(base.Base):
50 45
51 for newpatch in TestPatch.newpatches: 46 for newpatch in TestPatch.newpatches:
52 payload = newpatch.__str__() 47 payload = newpatch.__str__()
53 if not self.upstream_status_regex.search_string(payload): 48 if not patchtest_patterns.upstream_status_regex.search_string(payload):
54 self.fail('Added patch file is missing Upstream-Status: <Valid status> in the commit message', 49 self.fail(
55 data=[('Standard format', self.standard_format), ('Valid status', self.valid_status)]) 50 "Added patch file is missing Upstream-Status: <Valid status> in the commit message",
51 data=[
52 ("Standard format", self.standard_format),
53 ("Valid status", self.valid_status),
54 ],
55 )
56 for line in payload.splitlines(): 56 for line in payload.splitlines():
57 if self.patchmetadata_regex.match(line): 57 if patchtest_patterns.patchmetadata_regex.match(line):
58 continue 58 continue
59 if self.upstream_status_regex.search_string(line): 59 if patchtest_patterns.upstream_status_regex.search_string(line):
60 if parse_upstream_status.inappropriate_status_mark.searchString(line): 60 if patchtest_patterns.inappropriate.searchString(line):
61 try: 61 try:
62 parse_upstream_status.upstream_status_inappropriate_info.parseString(line.lstrip('+')) 62 patchtest_patterns.upstream_status_inappropriate_info.parseString(
63 except pyparsing.ParseException as pe: 63 line.lstrip("+")
64 self.fail('Upstream-Status is Inappropriate, but no reason was provided', 64 )
65 data=[('Current', pe.pstr), ('Standard format', 'Upstream-Status: Inappropriate [reason]')]) 65 except pyparsing.ParseException as pe:
66 elif parse_upstream_status.submitted_status_mark.searchString(line): 66 self.fail(
67 try: 67 "Upstream-Status is Inappropriate, but no reason was provided",
68 parse_upstream_status.upstream_status_submitted_info.parseString(line.lstrip('+')) 68 data=[
69 except pyparsing.ParseException as pe: 69 ("Current", pe.pstr),
70 self.fail('Upstream-Status is Submitted, but it is not mentioned where', 70 (
71 data=[('Current', pe.pstr), ('Standard format', 'Upstream-Status: Submitted [where]')]) 71 "Standard format",
72 else: 72 "Upstream-Status: Inappropriate [reason]",
73 try: 73 ),
74 parse_upstream_status.upstream_status.parseString(line.lstrip('+')) 74 ],
75 except pyparsing.ParseException as pe: 75 )
76 self.fail('Upstream-Status is in incorrect format', 76 elif patchtest_patterns.submitted.searchString(line):
77 data=[('Current', pe.pstr), ('Standard format', self.standard_format), ('Valid status', self.valid_status)]) 77 try:
78 patchtest_patterns.upstream_status_submitted_info.parseString(
79 line.lstrip("+")
80 )
81 except pyparsing.ParseException as pe:
82 self.fail(
83 "Upstream-Status is Submitted, but it is not mentioned where",
84 data=[
85 ("Current", pe.pstr),
86 (
87 "Standard format",
88 "Upstream-Status: Submitted [where]",
89 ),
90 ],
91 )
92 else:
93 try:
94 patchtest_patterns.upstream_status.parseString(line.lstrip("+"))
95 except pyparsing.ParseException as pe:
96 self.fail(
97 "Upstream-Status is in incorrect format",
98 data=[
99 ("Current", pe.pstr),
100 ("Standard format", self.standard_format),
101 ("Valid status", self.valid_status),
102 ],
103 )
78 104
79 def test_signed_off_by_presence(self): 105 def test_signed_off_by_presence(self):
80 if not TestPatch.newpatches: 106 if not TestPatch.newpatches:
@@ -83,7 +109,7 @@ class TestPatch(base.Base):
83 for newpatch in TestPatch.newpatches: 109 for newpatch in TestPatch.newpatches:
84 payload = newpatch.__str__() 110 payload = newpatch.__str__()
85 for line in payload.splitlines(): 111 for line in payload.splitlines():
86 if self.patchmetadata_regex.match(line): 112 if patchtest_patterns.patchmetadata_regex.match(line):
87 continue 113 continue
88 if TestPatch.prog.search_string(payload): 114 if TestPatch.prog.search_string(payload):
89 break 115 break
@@ -92,10 +118,12 @@ class TestPatch(base.Base):
92 118
93 def test_cve_tag_format(self): 119 def test_cve_tag_format(self):
94 for commit in TestPatch.commits: 120 for commit in TestPatch.commits:
95 if self.re_cve_pattern.search_string(commit.shortlog) or self.re_cve_pattern.search_string(commit.commit_message): 121 if patchtest_patterns.cve.search_string(
122 commit.shortlog
123 ) or patchtest_patterns.cve.search_string(commit.commit_message):
96 tag_found = False 124 tag_found = False
97 for line in commit.payload.splitlines(): 125 for line in commit.payload.splitlines():
98 if self.re_cve_payload_tag.search_string(line): 126 if patchtest_patterns.cve_payload_tag.search_string(line):
99 tag_found = True 127 tag_found = True
100 break 128 break
101 if not tag_found: 129 if not tag_found:
diff --git a/meta/lib/patchtest/tests/test_python_pylint.py b/meta/lib/patchtest/tests/test_python_pylint.py
index ef315e591c..ec9129bc79 100644
--- a/meta/lib/patchtest/tests/test_python_pylint.py
+++ b/meta/lib/patchtest/tests/test_python_pylint.py
@@ -6,7 +6,7 @@
6 6
7import base 7import base
8from io import StringIO 8from io import StringIO
9from data import PatchTestInput 9from patchtest_parser import PatchtestParser
10from pylint.reporters.text import TextReporter 10from pylint.reporters.text import TextReporter
11import pylint.lint as lint 11import pylint.lint as lint
12 12