1cdf0c1d5Smjnelson#! /usr/bin/python 2cdf0c1d5Smjnelson# 3cdf0c1d5Smjnelson# CDDL HEADER START 4cdf0c1d5Smjnelson# 5cdf0c1d5Smjnelson# The contents of this file are subject to the terms of the 6cdf0c1d5Smjnelson# Common Development and Distribution License (the "License"). 7cdf0c1d5Smjnelson# You may not use this file except in compliance with the License. 8cdf0c1d5Smjnelson# 9cdf0c1d5Smjnelson# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 10cdf0c1d5Smjnelson# or http://www.opensolaris.org/os/licensing. 11cdf0c1d5Smjnelson# See the License for the specific language governing permissions 12cdf0c1d5Smjnelson# and limitations under the License. 13cdf0c1d5Smjnelson# 14cdf0c1d5Smjnelson# When distributing Covered Code, include this CDDL HEADER in each 15cdf0c1d5Smjnelson# file and include the License file at usr/src/OPENSOLARIS.LICENSE. 16cdf0c1d5Smjnelson# If applicable, add the following below this CDDL HEADER, with the 17cdf0c1d5Smjnelson# fields enclosed by brackets "[]" replaced with your own identifying 18cdf0c1d5Smjnelson# information: Portions Copyright [yyyy] [name of copyright owner] 19cdf0c1d5Smjnelson# 20cdf0c1d5Smjnelson# CDDL HEADER END 21cdf0c1d5Smjnelson# 22cdf0c1d5Smjnelson 23cdf0c1d5Smjnelson# 2441a5f560SMark J. Nelson# Copyright 2009 Sun Microsystems, Inc. All rights reserved. 25cdf0c1d5Smjnelson# Use is subject to license terms. 26cdf0c1d5Smjnelson# 27cdf0c1d5Smjnelson 282f54b716SRichard Lowe# Copyright 2007, 2010 Richard Lowe 2918ce2efcSAndy Fiddaman# Copyright 2019 OmniOS Community Edition (OmniOSce) Association. 30*3f8945a7SBill Sommerfeld# Copyright 2024 Bill Sommerfeld 312f54b716SRichard Lowe 32cdf0c1d5Smjnelson# 33cdf0c1d5Smjnelson# Check delta comments: 34ca13eaa5SAndy Fiddaman# - Have the correct form. 35ca13eaa5SAndy Fiddaman# - Have a synopsis matching that of the bug 36ca13eaa5SAndy Fiddaman# - Appear only once. 3718ce2efcSAndy Fiddaman# - Do not contain common spelling errors. 38cdf0c1d5Smjnelson# 39cdf0c1d5Smjnelson 40cdf0c1d5Smjnelsonimport re, sys 412f54b716SRichard Lowefrom onbld.Checks.DbLookups import BugDB 4218ce2efcSAndy Fiddamanfrom onbld.Checks.SpellCheck import spellcheck_line 432f54b716SRichard Lowe 44cdf0c1d5Smjnelson 452f54b716SRichard Lowebugre = re.compile(r'^(\d{2,7}) (.*)$') 46ef62fef3SRichard Lowe 47cdf0c1d5Smjnelson 48cdf0c1d5Smjnelsondef isBug(comment): 49cdf0c1d5Smjnelson return bugre.match(comment) 50cdf0c1d5Smjnelson 51*3f8945a7SBill Sommerfelddef changeid_present(comments): 52*3f8945a7SBill Sommerfeld if len(comments) < 3: 53*3f8945a7SBill Sommerfeld return False 54*3f8945a7SBill Sommerfeld if comments[-2] != '': 55*3f8945a7SBill Sommerfeld return False 56*3f8945a7SBill Sommerfeld if re.match('^Change-Id: I[0-9a-f]+', comments[-1]): 57*3f8945a7SBill Sommerfeld return True 58*3f8945a7SBill Sommerfeld return False 59cdf0c1d5Smjnelson 60*3f8945a7SBill Sommerfelddef comchk(comments, check_db=True, output=sys.stderr, bugs=None): 61c08a253cSJohn Sonnenschein '''Validate checkin comments against ON standards. 62ef62fef3SRichard Lowe 63c08a253cSJohn Sonnenschein Comments must be a list of one-line comments, with no trailing 64c08a253cSJohn Sonnenschein newline. 652f54b716SRichard Lowe 662f54b716SRichard Lowe If check_db is True (the default), validate bug synopses against the 672f54b716SRichard Lowe databases. 68ef62fef3SRichard Lowe 69c08a253cSJohn Sonnenschein Error messages intended for the user are written to output, 70c08a253cSJohn Sonnenschein which defaults to stderr 71c08a253cSJohn Sonnenschein ''' 722f54b716SRichard Lowe bugnospcre = re.compile(r'^(\d{2,7})([^ ].*)') 732f54b716SRichard Lowe ignorere = re.compile(r'^(' + 742f54b716SRichard Lowe r'Portions contributed by|' + 752f54b716SRichard Lowe r'Contributed by|' + 762f54b716SRichard Lowe r'Reviewed[ -]by|' + 772f54b716SRichard Lowe r'Approved[ -]by|' + 782f54b716SRichard Lowe r'back[ -]?out)' + 792f54b716SRichard Lowe r'[: ]') 80cdf0c1d5Smjnelson 81cdf0c1d5Smjnelson errors = { 'bugnospc': [], 82*3f8945a7SBill Sommerfeld 'changeid': [], 83cdf0c1d5Smjnelson 'mutant': [], 84cdf0c1d5Smjnelson 'dup': [], 85cdf0c1d5Smjnelson 'nomatch': [], 8618ce2efcSAndy Fiddaman 'nonexistent': [], 8718ce2efcSAndy Fiddaman 'spelling': [] } 88*3f8945a7SBill Sommerfeld if bugs is None: 89*3f8945a7SBill Sommerfeld bugs = {} 90*3f8945a7SBill Sommerfeld newbugs = set() 91ef62fef3SRichard Lowe ret = 0 92ef62fef3SRichard Lowe blanks = False 93cdf0c1d5Smjnelson 94*3f8945a7SBill Sommerfeld if changeid_present(comments): 95*3f8945a7SBill Sommerfeld comments = comments[:-2] 96*3f8945a7SBill Sommerfeld errors['changeid'].append('Change Id present') 97*3f8945a7SBill Sommerfeld 9818ce2efcSAndy Fiddaman lineno = 0 99cdf0c1d5Smjnelson for com in comments: 10018ce2efcSAndy Fiddaman lineno += 1 10118ce2efcSAndy Fiddaman 102c08a253cSJohn Sonnenschein # Our input must be newline-free, comments are line-wise. 103c08a253cSJohn Sonnenschein if com.find('\n') != -1: 104c08a253cSJohn Sonnenschein raise ValueError("newline in comment '%s'" % com) 105c08a253cSJohn Sonnenschein 106cdf0c1d5Smjnelson # Ignore valid comments we can't check 107cdf0c1d5Smjnelson if ignorere.search(com): 108cdf0c1d5Smjnelson continue 109cdf0c1d5Smjnelson 110cdf0c1d5Smjnelson if not com or com.isspace(): 111ef62fef3SRichard Lowe blanks = True 112cdf0c1d5Smjnelson continue 113cdf0c1d5Smjnelson 11418ce2efcSAndy Fiddaman for err in spellcheck_line(com): 11518ce2efcSAndy Fiddaman errors['spelling'].append( 11618ce2efcSAndy Fiddaman 'comment line {} - {}'.format(lineno, err)) 11718ce2efcSAndy Fiddaman 118cdf0c1d5Smjnelson match = bugre.search(com) 119cdf0c1d5Smjnelson if match: 120*3f8945a7SBill Sommerfeld (bugid, synopsis) = match.groups() 121*3f8945a7SBill Sommerfeld bugs.setdefault(bugid, []).append(synopsis) 122*3f8945a7SBill Sommerfeld newbugs.add(bugid) 123cdf0c1d5Smjnelson continue 124cdf0c1d5Smjnelson 125cdf0c1d5Smjnelson # 126cdf0c1d5Smjnelson # Bugs missing a space after the ID are still bugs 127cdf0c1d5Smjnelson # for the purposes of the duplicate ID and synopsis 128cdf0c1d5Smjnelson # checks. 129cdf0c1d5Smjnelson # 130cdf0c1d5Smjnelson match = bugnospcre.search(com) 131cdf0c1d5Smjnelson if match: 132*3f8945a7SBill Sommerfeld (bugid, synopsis) = match.groups() 133*3f8945a7SBill Sommerfeld bugs.setdefault(bugid, []).append(synopsis) 134*3f8945a7SBill Sommerfeld newbugs.add(bugid) 135cdf0c1d5Smjnelson errors['bugnospc'].append(com) 136cdf0c1d5Smjnelson continue 137cdf0c1d5Smjnelson 138cdf0c1d5Smjnelson # Anything else is bogus 139cdf0c1d5Smjnelson errors['mutant'].append(com) 140cdf0c1d5Smjnelson 141cdf0c1d5Smjnelson if len(bugs) > 0 and check_db: 142cdf0c1d5Smjnelson bugdb = BugDB() 143ca13eaa5SAndy Fiddaman results = bugdb.lookup(list(bugs.keys())) 144cdf0c1d5Smjnelson 145*3f8945a7SBill Sommerfeld for crid in sorted(newbugs): 146*3f8945a7SBill Sommerfeld insts = bugs[crid] 147cdf0c1d5Smjnelson if len(insts) > 1: 148cdf0c1d5Smjnelson errors['dup'].append(crid) 149cdf0c1d5Smjnelson 150cdf0c1d5Smjnelson if not check_db: 151cdf0c1d5Smjnelson continue 152cdf0c1d5Smjnelson 153cdf0c1d5Smjnelson if crid not in results: 154cdf0c1d5Smjnelson errors['nonexistent'].append(crid) 155cdf0c1d5Smjnelson continue 156cdf0c1d5Smjnelson 157ef62fef3SRichard Lowe # 158ef62fef3SRichard Lowe # For each synopsis, compare the real synopsis with 159ef62fef3SRichard Lowe # that in the comments, allowing for possible '(fix 160ef62fef3SRichard Lowe # stuff)'-like trailing text 161ef62fef3SRichard Lowe # 162cdf0c1d5Smjnelson for entered in insts: 163cdf0c1d5Smjnelson synopsis = results[crid]["synopsis"] 164ef62fef3SRichard Lowe if not re.search(r'^' + re.escape(synopsis) + 165c08a253cSJohn Sonnenschein r'( \([^)]+\))?$', entered): 166cdf0c1d5Smjnelson errors['nomatch'].append([crid, synopsis, 167c08a253cSJohn Sonnenschein entered]) 168c08a253cSJohn Sonnenschein 169cdf0c1d5Smjnelson 170cdf0c1d5Smjnelson if blanks: 171cdf0c1d5Smjnelson output.write("WARNING: Blank line(s) in comments\n") 172cdf0c1d5Smjnelson ret = 1 173cdf0c1d5Smjnelson 174cdf0c1d5Smjnelson if errors['dup']: 175cdf0c1d5Smjnelson ret = 1 176cdf0c1d5Smjnelson output.write("These IDs appear more than once in your " 177cdf0c1d5Smjnelson "comments:\n") 178cdf0c1d5Smjnelson for err in errors['dup']: 179cdf0c1d5Smjnelson output.write(" %s\n" % err) 180cdf0c1d5Smjnelson 181cdf0c1d5Smjnelson if errors['bugnospc']: 182cdf0c1d5Smjnelson ret = 1 183cdf0c1d5Smjnelson output.write("These bugs are missing a single space following " 184cdf0c1d5Smjnelson "the ID:\n") 185cdf0c1d5Smjnelson for com in errors['bugnospc']: 186cdf0c1d5Smjnelson output.write(" %s\n" % com) 187cdf0c1d5Smjnelson 188*3f8945a7SBill Sommerfeld if errors['changeid']: 189*3f8945a7SBill Sommerfeld ret = 1 190*3f8945a7SBill Sommerfeld output.write("NOTE: Change-Id present in comment\n") 191*3f8945a7SBill Sommerfeld 192cdf0c1d5Smjnelson if errors['mutant']: 193cdf0c1d5Smjnelson ret = 1 1942f54b716SRichard Lowe output.write("These comments are not valid bugs:\n") 195cdf0c1d5Smjnelson for com in errors['mutant']: 196cdf0c1d5Smjnelson output.write(" %s\n" % com) 197cdf0c1d5Smjnelson 198cdf0c1d5Smjnelson if errors['nonexistent']: 199cdf0c1d5Smjnelson ret = 1 2002f54b716SRichard Lowe output.write("These bugs were not found in the databases:\n") 201cdf0c1d5Smjnelson for id in errors['nonexistent']: 202cdf0c1d5Smjnelson output.write(" %s\n" % id) 203cdf0c1d5Smjnelson 204cdf0c1d5Smjnelson if errors['nomatch']: 205cdf0c1d5Smjnelson ret = 1 2062f54b716SRichard Lowe output.write("These bug synopses don't match " 207cdf0c1d5Smjnelson "the database entries:\n") 208cdf0c1d5Smjnelson for err in errors['nomatch']: 2092f54b716SRichard Lowe output.write("Synopsis of %s is wrong:\n" % err[0]) 210cdf0c1d5Smjnelson output.write(" should be: '%s'\n" % err[1]) 211cdf0c1d5Smjnelson output.write(" is: '%s'\n" % err[2]) 212cdf0c1d5Smjnelson 21318ce2efcSAndy Fiddaman if errors['spelling']: 21418ce2efcSAndy Fiddaman ret = 1 21518ce2efcSAndy Fiddaman output.write("Spellcheck:\n") 21618ce2efcSAndy Fiddaman for err in errors['spelling']: 21718ce2efcSAndy Fiddaman output.write('{}\n'.format(err)) 21818ce2efcSAndy Fiddaman 219cdf0c1d5Smjnelson return ret 220