1ac13d5f3SPatrick Williams#!/usr/bin/env python3
2ac13d5f3SPatrick Williams# ex:ts=4:sw=4:sts=4:et
3ac13d5f3SPatrick Williams# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
4ac13d5f3SPatrick Williams#
5ac13d5f3SPatrick Williams# patchtest: execute all unittest test cases discovered for a single patch
6ac13d5f3SPatrick Williams# Note that this script is currently under development and has been
7ac13d5f3SPatrick Williams# hard-coded with default values for testing purposes. This script
8ac13d5f3SPatrick Williams# should not be used without changing the default recipient, at minimum.
9ac13d5f3SPatrick Williams#
10ac13d5f3SPatrick Williams# Copyright (C) 2023 BayLibre Inc.
11ac13d5f3SPatrick Williams#
12ac13d5f3SPatrick Williams# SPDX-License-Identifier: GPL-2.0-only
13ac13d5f3SPatrick Williams#
14ac13d5f3SPatrick Williams
15ac13d5f3SPatrick Williamsimport argparse
16ac13d5f3SPatrick Williamsimport boto3
17ac13d5f3SPatrick Williamsimport configparser
18ac13d5f3SPatrick Williamsimport mailbox
19ac13d5f3SPatrick Williamsimport os
20ac13d5f3SPatrick Williamsimport re
21ac13d5f3SPatrick Williamsimport sys
22ac13d5f3SPatrick Williams
23ac13d5f3SPatrick Williamsgreeting = """Thank you for your submission. Patchtest identified one
24ac13d5f3SPatrick Williamsor more issues with the patch. Please see the log below for
25ac13d5f3SPatrick Williamsmore information:\n\n---\n"""
26ac13d5f3SPatrick Williams
27ac13d5f3SPatrick Williamssuggestions = """\n---\n\nPlease address the issues identified and
28ac13d5f3SPatrick Williamssubmit a new revision of the patch, or alternatively, reply to this
29ac13d5f3SPatrick Williamsemail with an explanation of why the patch should be accepted. If you
30ac13d5f3SPatrick Williamsbelieve these results are due to an error in patchtest, please submit a
31ac13d5f3SPatrick Williamsbug at https://bugzilla.yoctoproject.org/ (use the 'Patchtest' category
32ac13d5f3SPatrick Williamsunder 'Yocto Project Subprojects'). For more information on specific
33ac13d5f3SPatrick Williamsfailures, see: https://wiki.yoctoproject.org/wiki/Patchtest. Thank
34ac13d5f3SPatrick Williamsyou!"""
35ac13d5f3SPatrick Williams
36*73bd93f1SPatrick Williamsdef has_a_failed_test(raw_results):
37*73bd93f1SPatrick Williams    return any(raw_result.split(':')[0] == "FAIL" for raw_result in raw_results.splitlines())
38*73bd93f1SPatrick Williams
39ac13d5f3SPatrick Williamsparser = argparse.ArgumentParser(description="Send patchtest results to a submitter for a given patch")
40ac13d5f3SPatrick Williamsparser.add_argument("-p", "--patch", dest="patch", required=True, help="The patch file to summarize")
41*73bd93f1SPatrick Williamsparser.add_argument("-d", "--debug", dest="debug", required=False, action='store_true', help="Print raw email headers and content, but don't actually send it")
42ac13d5f3SPatrick Williamsargs = parser.parse_args()
43ac13d5f3SPatrick Williams
44ac13d5f3SPatrick Williamsif not os.path.exists(args.patch):
45ac13d5f3SPatrick Williams    print(f"Patch '{args.patch}' not found - did you provide the right path?")
46ac13d5f3SPatrick Williams    sys.exit(1)
47ac13d5f3SPatrick Williamselif not os.path.exists(args.patch + ".testresult"):
48ac13d5f3SPatrick Williams    print(f"Found patch '{args.patch}' but '{args.patch}.testresult' was not present. Have you run patchtest on the patch?")
49ac13d5f3SPatrick Williams    sys.exit(1)
50ac13d5f3SPatrick Williams
51ac13d5f3SPatrick Williamsresult_file = args.patch + ".testresult"
52ac13d5f3SPatrick Williamstestresult = None
53ac13d5f3SPatrick Williams
54ac13d5f3SPatrick Williamswith open(result_file, "r") as f:
55ac13d5f3SPatrick Williams    testresult = f.read()
56ac13d5f3SPatrick Williams
57ac13d5f3SPatrick Williams# we know these patch files will only contain a single patch, so only
58ac13d5f3SPatrick Williams# worry about the first element for getting the subject
59ac13d5f3SPatrick Williamsmbox = mailbox.mbox(args.patch)
60ac13d5f3SPatrick Williamsmbox_subject = mbox[0]['subject']
61ac13d5f3SPatrick Williamssubject_line = f"Patchtest results for {mbox_subject}"
62ac13d5f3SPatrick Williams
63ac13d5f3SPatrick Williams# extract the submitter email address and use it as the reply address
64ac13d5f3SPatrick Williams# for the results
65ac13d5f3SPatrick Williamsreply_address = mbox[0]['from']
66ac13d5f3SPatrick Williams
67ac13d5f3SPatrick Williams# extract the message ID and use that as the in-reply-to address
68*73bd93f1SPatrick Williams# TODO: This will need to change again when patchtest can handle a whole
69*73bd93f1SPatrick Williams# series at once
70*73bd93f1SPatrick Williamsin_reply_to = mbox[0]['Message-ID']
71ac13d5f3SPatrick Williams
72ac13d5f3SPatrick Williams# the address the results email is sent from
73ac13d5f3SPatrick Williamsfrom_address = "patchtest@automation.yoctoproject.org"
74ac13d5f3SPatrick Williams
75ac13d5f3SPatrick Williams# mailing list to CC
76ac13d5f3SPatrick Williamscc_address = "openembedded-core@lists.openembedded.org"
77ac13d5f3SPatrick Williams
78*73bd93f1SPatrick Williamsif has_a_failed_test(testresult):
79ac13d5f3SPatrick Williams    reply_contents = None
80ac13d5f3SPatrick Williams    if len(max(open(result_file, 'r'), key=len)) > 220:
81ac13d5f3SPatrick Williams        warning = "Tests failed for the patch, but the results log could not be processed due to excessive result line length."
82ac13d5f3SPatrick Williams        reply_contents = greeting + warning + suggestions
83ac13d5f3SPatrick Williams    else:
84ac13d5f3SPatrick Williams        reply_contents = greeting + testresult + suggestions
85ac13d5f3SPatrick Williams
86ac13d5f3SPatrick Williams    ses_client = boto3.client('ses', region_name='us-west-2')
87*73bd93f1SPatrick Williams
88*73bd93f1SPatrick Williams    # Construct the headers for the email. We only want to reply
89*73bd93f1SPatrick Williams    # directly to the tested patch, so make In-Reply-To and References
90*73bd93f1SPatrick Williams    # the same value.
91ac13d5f3SPatrick Williams    raw_data = 'From: ' + from_address + '\nTo: ' + reply_address + \
92ac13d5f3SPatrick Williams        '\nCC: ' + cc_address + '\nSubject:' + subject_line + \
93ac13d5f3SPatrick Williams        '\nIn-Reply-To:' + in_reply_to + \
94*73bd93f1SPatrick Williams        '\nReferences:' + in_reply_to + \
95ac13d5f3SPatrick Williams        '\nMIME-Version: 1.0" + \
96ac13d5f3SPatrick Williams        "\nContent-type: Multipart/Mixed;boundary="NextPart"\n\n--NextPart\nContent-Type: text/plain\n\n' + \
97ac13d5f3SPatrick Williams        reply_contents + '\n\n--NextPart'
98*73bd93f1SPatrick Williams
99*73bd93f1SPatrick Williams    if args.debug:
100*73bd93f1SPatrick Williams        print(f"RawMessage: \n\n{raw_data}")
101*73bd93f1SPatrick Williams    else:
102ac13d5f3SPatrick Williams        response = ses_client.send_raw_email(
103ac13d5f3SPatrick Williams            Source="patchtest@automation.yoctoproject.org",
104ac13d5f3SPatrick Williams            RawMessage={
105ac13d5f3SPatrick Williams                "Data": raw_data,
106ac13d5f3SPatrick Williams            },
107ac13d5f3SPatrick Williams        )
108ac13d5f3SPatrick Williams
109ac13d5f3SPatrick Williamselse:
110ac13d5f3SPatrick Williams    print(f"No failures identified for {args.patch}.")
111