diff options
Diffstat (limited to 'meta/lib/patchtest/tests/test_mbox.py')
-rw-r--r-- | meta/lib/patchtest/tests/test_mbox.py | 159 |
1 files changed, 159 insertions, 0 deletions
diff --git a/meta/lib/patchtest/tests/test_mbox.py b/meta/lib/patchtest/tests/test_mbox.py new file mode 100644 index 0000000000..0b623b7d17 --- /dev/null +++ b/meta/lib/patchtest/tests/test_mbox.py | |||
@@ -0,0 +1,159 @@ | |||
1 | # Checks related to the patch's author | ||
2 | # | ||
3 | # Copyright (C) 2016 Intel Corporation | ||
4 | # | ||
5 | # SPDX-License-Identifier: GPL-2.0-only | ||
6 | |||
7 | import base | ||
8 | import collections | ||
9 | import parse_shortlog | ||
10 | import parse_signed_off_by | ||
11 | import pyparsing | ||
12 | import subprocess | ||
13 | from data import PatchTestInput | ||
14 | |||
15 | def headlog(): | ||
16 | output = subprocess.check_output( | ||
17 | "cd %s; git log --pretty='%%h#%%aN#%%cD:#%%s' -1" % PatchTestInput.repodir, | ||
18 | universal_newlines=True, | ||
19 | shell=True | ||
20 | ) | ||
21 | return output.split('#') | ||
22 | |||
23 | class TestMbox(base.Base): | ||
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 | ||
40 | paths = { | ||
41 | 'oe-core': ['meta-selftest', 'meta-skeleton', 'meta', 'scripts'], | ||
42 | 'bitbake': ['bitbake'], | ||
43 | 'documentation': ['documentation'], | ||
44 | 'poky': ['meta-poky','meta-yocto-bsp'], | ||
45 | 'oe': ['meta-gpe', 'meta-gnome', 'meta-efl', 'meta-networking', 'meta-multimedia','meta-initramfs', 'meta-ruby', 'contrib', 'meta-xfce', 'meta-filesystems', 'meta-perl', 'meta-webserver', 'meta-systemd', 'meta-oe', 'meta-python'] | ||
46 | } | ||
47 | |||
48 | # scripts folder is a mix of oe-core and poky, most is oe-core code except: | ||
49 | poky_scripts = ['scripts/yocto-bsp', 'scripts/yocto-kernel', 'scripts/yocto-layer', 'scripts/lib/bsp'] | ||
50 | |||
51 | Project = collections.namedtuple('Project', ['name', 'listemail', 'gitrepo', 'paths']) | ||
52 | |||
53 | bitbake = Project(name='Bitbake', listemail='bitbake-devel@lists.openembedded.org', gitrepo='http://git.openembedded.org/bitbake/', paths=paths['bitbake']) | ||
54 | doc = Project(name='Documentantion', listemail='yocto@yoctoproject.org', gitrepo='http://git.yoctoproject.org/cgit/cgit.cgi/yocto-docs/', paths=paths['documentation']) | ||
55 | poky = Project(name='Poky', listemail='poky@yoctoproject.org', gitrepo='http://git.yoctoproject.org/cgit/cgit.cgi/poky/', paths=paths['poky']) | ||
56 | oe = Project(name='oe', listemail='openembedded-devel@lists.openembedded.org', gitrepo='http://git.openembedded.org/meta-openembedded/', paths=paths['oe']) | ||
57 | |||
58 | |||
59 | def test_signed_off_by_presence(self): | ||
60 | for commit in TestMbox.commits: | ||
61 | # skip those patches that revert older commits, these do not required the tag presence | ||
62 | if self.revert_shortlog_regex.search_string(commit.shortlog): | ||
63 | continue | ||
64 | if not self.signoff_prog.search_string(commit.payload): | ||
65 | self.fail('Mbox is missing Signed-off-by. Add it manually or with "git commit --amend -s"', | ||
66 | commit=commit) | ||
67 | |||
68 | def test_shortlog_format(self): | ||
69 | for commit in TestMbox.commits: | ||
70 | shortlog = commit.shortlog | ||
71 | if not shortlog.strip(): | ||
72 | self.skip('Empty shortlog, no reason to execute shortlog format test') | ||
73 | else: | ||
74 | # no reason to re-check on revert shortlogs | ||
75 | if shortlog.startswith('Revert "'): | ||
76 | continue | ||
77 | try: | ||
78 | parse_shortlog.shortlog.parseString(shortlog) | ||
79 | except pyparsing.ParseException as pe: | ||
80 | self.fail('Commit shortlog (first line of commit message) should follow the format "<target>: <summary>"', | ||
81 | commit=commit) | ||
82 | |||
83 | def test_shortlog_length(self): | ||
84 | for commit in TestMbox.commits: | ||
85 | # no reason to re-check on revert shortlogs | ||
86 | shortlog = commit.shortlog | ||
87 | if shortlog.startswith('Revert "'): | ||
88 | continue | ||
89 | l = len(shortlog) | ||
90 | if l > self.maxlength: | ||
91 | self.fail('Edit shortlog so that it is %d characters or less (currently %d characters)' % (self.maxlength, l), | ||
92 | commit=commit) | ||
93 | |||
94 | def test_series_merge_on_head(self): | ||
95 | self.skip("Merge test is disabled for now") | ||
96 | if PatchTestInput.repo.branch != "master": | ||
97 | self.skip("Skipping merge test since patch is not intended for master branch. Target detected is %s" % PatchTestInput.repo.branch) | ||
98 | if not PatchTestInput.repo.ismerged: | ||
99 | commithash, author, date, shortlog = headlog() | ||
100 | self.fail('Series does not apply on top of target branch %s' % PatchTestInput.repo.branch, | ||
101 | data=[('Targeted branch', '%s (currently at %s)' % (PatchTestInput.repo.branch, commithash))]) | ||
102 | |||
103 | def test_target_mailing_list(self): | ||
104 | """In case of merge failure, check for other targeted projects""" | ||
105 | if PatchTestInput.repo.ismerged: | ||
106 | self.skip('Series merged, no reason to check other mailing lists') | ||
107 | |||
108 | # 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 | ||
110 | project_regex = pyparsing.Regex("\[(?P<project>meta-.+)\]") | ||
111 | for commit in TestMbox.commits: | ||
112 | match = project_regex.search_string(commit.subject) | ||
113 | if match: | ||
114 | self.fail('Series sent to the wrong mailing list or some patches from the series correspond to different mailing lists', | ||
115 | commit=commit) | ||
116 | |||
117 | for patch in self.patchset: | ||
118 | folders = patch.path.split('/') | ||
119 | base_path = folders[0] | ||
120 | for project in [self.bitbake, self.doc, self.oe, self.poky]: | ||
121 | if base_path in project.paths: | ||
122 | self.fail('Series sent to the wrong mailing list or some patches from the series correspond to different mailing lists', | ||
123 | data=[('Suggested ML', '%s [%s]' % (project.listemail, project.gitrepo)), | ||
124 | ('Patch\'s path:', patch.path)]) | ||
125 | |||
126 | # check for poky's scripts code | ||
127 | if base_path.startswith('scripts'): | ||
128 | for poky_file in self.poky_scripts: | ||
129 | if patch.path.startswith(poky_file): | ||
130 | self.fail('Series sent to the wrong mailing list or some patches from the series correspond to different mailing lists', | ||
131 | data=[('Suggested ML', '%s [%s]' % (self.poky.listemail, self.poky.gitrepo)),('Patch\'s path:', patch.path)]) | ||
132 | |||
133 | def test_mbox_format(self): | ||
134 | if self.unidiff_parse_error: | ||
135 | self.fail('Series has malformed diff lines. Create the series again using git-format-patch and ensure it applies using git am', | ||
136 | data=[('Diff line',self.unidiff_parse_error)]) | ||
137 | |||
138 | def test_commit_message_presence(self): | ||
139 | for commit in TestMbox.commits: | ||
140 | if not commit.commit_message.strip(): | ||
141 | self.fail('Please include a commit message on your patch explaining the change', commit=commit) | ||
142 | |||
143 | def test_bugzilla_entry_format(self): | ||
144 | for commit in TestMbox.commits: | ||
145 | if not self.rexp_detect.search_string(commit.commit_message): | ||
146 | self.skip("No bug ID found") | ||
147 | elif not self.rexp_validation.search_string(commit.commit_message): | ||
148 | self.fail('Bugzilla issue ID is not correctly formatted - specify it with format: "[YOCTO #<bugzilla ID>]"', commit=commit) | ||
149 | |||
150 | def test_author_valid(self): | ||
151 | for commit in self.commits: | ||
152 | for invalid in self.invalids: | ||
153 | if invalid.search_string(commit.author): | ||
154 | self.fail('Invalid author %s. Resend the series with a valid patch author' % commit.author, commit=commit) | ||
155 | |||
156 | def test_non_auh_upgrade(self): | ||
157 | for commit in self.commits: | ||
158 | if self.auh_email in commit.payload: | ||
159 | self.fail('Invalid author %s. Resend the series with a valid patch author' % self.auh_email, commit=commit) | ||