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# SPDX-License-Identifier: GPL-2.0-only 13# 14 15import argparse 16import boto3 17import configparser 18import mailbox 19import os 20import re 21import sys 22 23greeting = """Thank you for your submission. Patchtest identified one 24or more issues with the patch. Please see the log below for 25more information:\n\n---\n""" 26 27suggestions = """\n---\n\nPlease address the issues identified and 28submit a new revision of the patch, or alternatively, reply to this 29email with an explanation of why the patch should be accepted. If you 30believe these results are due to an error in patchtest, please submit a 31bug at https://bugzilla.yoctoproject.org/ (use the 'Patchtest' category 32under 'Yocto Project Subprojects'). For more information on specific 33failures, see: https://wiki.yoctoproject.org/wiki/Patchtest. Thank 34you!""" 35 36def has_a_failed_test(raw_results): 37 return any(raw_result.split(':')[0] == "FAIL" for raw_result in raw_results.splitlines()) 38 39parser = argparse.ArgumentParser(description="Send patchtest results to a submitter for a given patch") 40parser.add_argument("-p", "--patch", dest="patch", required=True, help="The patch file to summarize") 41parser.add_argument("-d", "--debug", dest="debug", required=False, action='store_true', help="Print raw email headers and content, but don't actually send it") 42args = parser.parse_args() 43 44if not os.path.exists(args.patch): 45 print(f"Patch '{args.patch}' not found - did you provide the right path?") 46 sys.exit(1) 47elif not os.path.exists(args.patch + ".testresult"): 48 print(f"Found patch '{args.patch}' but '{args.patch}.testresult' was not present. Have you run patchtest on the patch?") 49 sys.exit(1) 50 51result_file = args.patch + ".testresult" 52testresult = None 53 54with open(result_file, "r") as f: 55 testresult = f.read() 56 57# we know these patch files will only contain a single patch, so only 58# worry about the first element for getting the subject 59mbox = mailbox.mbox(args.patch) 60mbox_subject = mbox[0]['subject'] 61subject_line = f"Patchtest results for {mbox_subject}" 62 63# extract the submitter email address and use it as the reply address 64# for the results 65reply_address = mbox[0]['from'] 66 67# extract the message ID and use that as the in-reply-to address 68# TODO: This will need to change again when patchtest can handle a whole 69# series at once 70in_reply_to = mbox[0]['Message-ID'] 71 72# the address the results email is sent from 73from_address = "patchtest@automation.yoctoproject.org" 74 75# mailing list to CC 76cc_address = "openembedded-core@lists.openembedded.org" 77 78if has_a_failed_test(testresult): 79 reply_contents = None 80 if len(max(open(result_file, 'r'), key=len)) > 220: 81 warning = "Tests failed for the patch, but the results log could not be processed due to excessive result line length." 82 reply_contents = greeting + warning + suggestions 83 else: 84 reply_contents = greeting + testresult + suggestions 85 86 ses_client = boto3.client('ses', region_name='us-west-2') 87 88 # Construct the headers for the email. We only want to reply 89 # directly to the tested patch, so make In-Reply-To and References 90 # the same value. 91 raw_data = 'From: ' + from_address + '\nTo: ' + reply_address + \ 92 '\nCC: ' + cc_address + '\nSubject:' + subject_line + \ 93 '\nIn-Reply-To:' + in_reply_to + \ 94 '\nReferences:' + in_reply_to + \ 95 '\nMIME-Version: 1.0" + \ 96 "\nContent-type: Multipart/Mixed;boundary="NextPart"\n\n--NextPart\nContent-Type: text/plain\n\n' + \ 97 reply_contents + '\n\n--NextPart' 98 99 if args.debug: 100 print(f"RawMessage: \n\n{raw_data}") 101 else: 102 response = ses_client.send_raw_email( 103 Source="patchtest@automation.yoctoproject.org", 104 RawMessage={ 105 "Data": raw_data, 106 }, 107 ) 108 109else: 110 print(f"No failures identified for {args.patch}.") 111