summaryrefslogtreecommitdiffstats
path: root/meta/lib/patchtest/tests/test_mbox.py
blob: 0b623b7d17bb0780f7d63f3095552d807d15eac5 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
# Checks related to the patch's author
#
# Copyright (C) 2016 Intel Corporation
#
# SPDX-License-Identifier: GPL-2.0-only

import base
import collections
import parse_shortlog
import parse_signed_off_by
import pyparsing
import subprocess
from data import PatchTestInput

def headlog():
    output = subprocess.check_output(
        "cd %s; git log --pretty='%%h#%%aN#%%cD:#%%s' -1" % PatchTestInput.repodir,
        universal_newlines=True,
        shell=True
        )
    return output.split('#')

class TestMbox(base.Base):

    auh_email = 'auh@auh.yoctoproject.org'

    invalids = [pyparsing.Regex("^Upgrade Helper.+"),
                pyparsing.Regex(auh_email),
                pyparsing.Regex("uh@not\.set"),
                pyparsing.Regex("\S+@example\.com")]

    rexp_detect = pyparsing.Regex('\[\s?YOCTO.*\]')
    rexp_validation = pyparsing.Regex('\[(\s?YOCTO\s?#\s?(\d+)\s?,?)+\]')
    revert_shortlog_regex = pyparsing.Regex('Revert\s+".*"')
    signoff_prog = parse_signed_off_by.signed_off_by
    revert_shortlog_regex = pyparsing.Regex('Revert\s+".*"')
    maxlength = 90

    # base paths of main yocto project sub-projects
    paths = {
        'oe-core': ['meta-selftest', 'meta-skeleton', 'meta', 'scripts'],
        'bitbake': ['bitbake'],
        'documentation': ['documentation'],
        'poky': ['meta-poky','meta-yocto-bsp'],
        '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']
        }

    # scripts folder is a mix of oe-core and poky, most is oe-core code except:
    poky_scripts = ['scripts/yocto-bsp', 'scripts/yocto-kernel', 'scripts/yocto-layer', 'scripts/lib/bsp']

    Project = collections.namedtuple('Project', ['name', 'listemail', 'gitrepo', 'paths'])

    bitbake = Project(name='Bitbake', listemail='bitbake-devel@lists.openembedded.org', gitrepo='http://git.openembedded.org/bitbake/', paths=paths['bitbake'])
    doc     = Project(name='Documentantion', listemail='yocto@yoctoproject.org', gitrepo='http://git.yoctoproject.org/cgit/cgit.cgi/yocto-docs/', paths=paths['documentation'])
    poky    = Project(name='Poky', listemail='poky@yoctoproject.org', gitrepo='http://git.yoctoproject.org/cgit/cgit.cgi/poky/', paths=paths['poky'])
    oe      = Project(name='oe', listemail='openembedded-devel@lists.openembedded.org', gitrepo='http://git.openembedded.org/meta-openembedded/', paths=paths['oe'])


    def test_signed_off_by_presence(self):
        for commit in TestMbox.commits:
            # skip those patches that revert older commits, these do not required the tag presence
            if self.revert_shortlog_regex.search_string(commit.shortlog):
                continue
            if not self.signoff_prog.search_string(commit.payload):
                self.fail('Mbox is missing Signed-off-by. Add it manually or with "git commit --amend -s"',
                          commit=commit)

    def test_shortlog_format(self):
        for commit in TestMbox.commits:
            shortlog = commit.shortlog
            if not shortlog.strip():
                self.skip('Empty shortlog, no reason to execute shortlog format test')
            else:
                # no reason to re-check on revert shortlogs
                if shortlog.startswith('Revert "'):
                    continue
                try:
                    parse_shortlog.shortlog.parseString(shortlog)
                except pyparsing.ParseException as pe:
                    self.fail('Commit shortlog (first line of commit message) should follow the format "<target>: <summary>"',
                              commit=commit)

    def test_shortlog_length(self):
        for commit in TestMbox.commits:
            # no reason to re-check on revert shortlogs
            shortlog = commit.shortlog
            if shortlog.startswith('Revert "'):
                continue
            l = len(shortlog)
            if l > self.maxlength:
                self.fail('Edit shortlog so that it is %d characters or less (currently %d characters)' % (self.maxlength, l),
                          commit=commit)

    def test_series_merge_on_head(self):
        self.skip("Merge test is disabled for now")
        if PatchTestInput.repo.branch != "master":
            self.skip("Skipping merge test since patch is not intended for master branch. Target detected is %s" % PatchTestInput.repo.branch)
        if not PatchTestInput.repo.ismerged:
            commithash, author, date, shortlog = headlog()
            self.fail('Series does not apply on top of target branch %s' % PatchTestInput.repo.branch,
                      data=[('Targeted branch', '%s (currently at %s)' % (PatchTestInput.repo.branch, commithash))])

    def test_target_mailing_list(self):
        """In case of merge failure, check for other targeted projects"""
        if PatchTestInput.repo.ismerged:
            self.skip('Series merged, no reason to check other mailing lists')

        # a meta project may be indicted in the message subject, if this is the case, just fail
        # TODO: there may be other project with no-meta prefix, we also need to detect these
        project_regex = pyparsing.Regex("\[(?P<project>meta-.+)\]")
        for commit in TestMbox.commits:
            match = project_regex.search_string(commit.subject)
            if match:
                self.fail('Series sent to the wrong mailing list or some patches from the series correspond to different mailing lists',
                          commit=commit)

        for patch in self.patchset:
            folders = patch.path.split('/')
            base_path = folders[0]
            for project in [self.bitbake, self.doc, self.oe, self.poky]:
                if base_path in  project.paths:
                    self.fail('Series sent to the wrong mailing list or some patches from the series correspond to different mailing lists',
                              data=[('Suggested ML', '%s [%s]' % (project.listemail, project.gitrepo)),
                                    ('Patch\'s path:', patch.path)])

            # check for poky's scripts code
            if base_path.startswith('scripts'):
                for poky_file in self.poky_scripts:
                    if patch.path.startswith(poky_file):
                        self.fail('Series sent to the wrong mailing list or some patches from the series correspond to different mailing lists',
                                  data=[('Suggested ML', '%s [%s]' % (self.poky.listemail, self.poky.gitrepo)),('Patch\'s path:', patch.path)])

    def test_mbox_format(self):
        if self.unidiff_parse_error:
            self.fail('Series has malformed diff lines. Create the series again using git-format-patch and ensure it applies using git am',
                      data=[('Diff line',self.unidiff_parse_error)])

    def test_commit_message_presence(self):
        for commit in TestMbox.commits:
            if not commit.commit_message.strip():
                self.fail('Please include a commit message on your patch explaining the change', commit=commit)

    def test_bugzilla_entry_format(self):
        for commit in TestMbox.commits:
            if not self.rexp_detect.search_string(commit.commit_message):
                self.skip("No bug ID found")
            elif not self.rexp_validation.search_string(commit.commit_message):
                self.fail('Bugzilla issue ID is not correctly formatted - specify it with format: "[YOCTO #<bugzilla ID>]"', commit=commit)

    def test_author_valid(self):
        for commit in self.commits:
            for invalid in self.invalids:
                if invalid.search_string(commit.author):
                    self.fail('Invalid author %s. Resend the series with a valid patch author' % commit.author, commit=commit)

    def test_non_auh_upgrade(self):
        for commit in self.commits:
            if self.auh_email in commit.payload:
                self.fail('Invalid author %s. Resend the series with a valid patch author' % self.auh_email, commit=commit)