xref: /illumos-gate/usr/src/tools/onbld/Checks/Comments.py (revision a24e89c4a1eec8361718d94a6275e6720643284e)
1#! /usr/bin/python
2#
3# CDDL HEADER START
4#
5# The contents of this file are subject to the terms of the
6# Common Development and Distribution License (the "License").
7# You may not use this file except in compliance with the License.
8#
9# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10# or http://www.opensolaris.org/os/licensing.
11# See the License for the specific language governing permissions
12# and limitations under the License.
13#
14# When distributing Covered Code, include this CDDL HEADER in each
15# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16# If applicable, add the following below this CDDL HEADER, with the
17# fields enclosed by brackets "[]" replaced with your own identifying
18# information: Portions Copyright [yyyy] [name of copyright owner]
19#
20# CDDL HEADER END
21#
22
23#
24# Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
25# Use is subject to license terms.
26#
27
28#
29# Check delta comments:
30# 	- Have the correct form.
31# 	- Have a synopsis matching that of the CR or ARC case.
32# 	- Appear only once.
33#
34
35import re, sys
36from onbld.Checks.DbLookups import BugDB, ARC
37
38arcre = re.compile(r'^([A-Z][A-Z]*ARC[/ \t][12]\d{3}/\d{3}) (.*)$')
39bugre = re.compile(r'^(\d{7}) (.*)$')
40
41def isARC(comment):
42	return arcre.match(comment)
43
44def isBug(comment):
45	return bugre.match(comment)
46
47#
48# Translate any acceptable case number format into "<ARC> <YEAR>/<NUM>"
49# format.
50#
51def normalize_arc(caseid):
52	return re.sub(r'^([A-Z][A-Z]*ARC)[/ \t]', '\\1 ', caseid)
53
54def comchk(comments, check_db=True, output=sys.stderr, arcPath=None):
55	'''Validate checkin comments against ON standards.
56
57	Comments must be a list of one-line comments, with no trailing
58	newline.
59
60	If check_db is True (the default), validate CR and ARC
61	synopses against the databases.
62
63	Error messages intended for the user are written to output,
64	which defaults to stderr
65	'''
66	bugnospcre = re.compile(r'^(\d{7})([^ ].*)')
67	ignorere = re.compile(r'^(Portions contributed by |Contributed by |back[ -]?out )')
68
69	errors = { 'bugnospc': [],
70		   'mutant': [],
71		   'dup': [],
72		   'nomatch': [],
73		   'nonexistent': [] }
74	bugs = {}
75	arcs = {}
76	ret = 0
77	blanks = False
78
79	for com in comments:
80		# Our input must be newline-free, comments are line-wise.
81		if com.find('\n') != -1:
82			raise ValueError("newline in comment '%s'" % com)
83
84		# Ignore valid comments we can't check
85		if ignorere.search(com):
86			continue
87
88		if not com or com.isspace():
89			blanks = True
90			continue
91
92		match = bugre.search(com)
93		if match:
94			if match.group(1) not in bugs:
95				bugs[match.group(1)] = []
96			bugs[match.group(1)].append(match.group(2))
97			continue
98
99		#
100		# Bugs missing a space after the ID are still bugs
101		# for the purposes of the duplicate ID and synopsis
102		# checks.
103		#
104		match = bugnospcre.search(com)
105		if match:
106			if match.group(1) not in bugs:
107				bugs[match.group(1)] = []
108			bugs[match.group(1)].append(match.group(2))
109			errors['bugnospc'].append(com)
110			continue
111
112		# ARC case
113		match = arcre.search(com)
114		if match:
115			arc, case = re.split('[/ \t]', match.group(1), 1)
116			arcs.setdefault((arc, case), []).append(match.group(2))
117			continue
118
119		# Anything else is bogus
120		errors['mutant'].append(com)
121
122	if len(bugs) > 0 and check_db:
123		bugdb = BugDB()
124		results = bugdb.lookup(bugs.keys())
125
126	for crid, insts in bugs.iteritems():
127		if len(insts) > 1:
128			errors['dup'].append(crid)
129
130		if not check_db:
131			continue
132
133		if crid not in results:
134			errors['nonexistent'].append(crid)
135			continue
136
137		#
138		# For each synopsis, compare the real synopsis with
139		# that in the comments, allowing for possible '(fix
140		# stuff)'-like trailing text
141		#
142		for entered in insts:
143			synopsis = results[crid]["synopsis"]
144			if not re.search(r'^' + re.escape(synopsis) +
145					r'( \([^)]+\))?$', entered):
146				errors['nomatch'].append([crid, synopsis,
147							entered])
148
149	if check_db:
150		valid = ARC(arcs.keys(), arcPath)
151
152	for case, insts in arcs.iteritems():
153		if len(insts) > 1:
154			errors['dup'].append(' '.join(case))
155
156 		if not check_db:
157			continue
158
159		if not case in valid:
160			errors['nonexistent'].append(' '.join(case))
161			continue
162
163		#
164		# We first try a direct match between the actual case name
165		# and the entered comment.  If that fails we remove a possible
166		# trailing (fix nit)-type comment, and re-try.
167		#
168		for entered in insts:
169			if entered == valid[case]:
170				break
171			else:
172				# Try again with trailing (fix ...) removed.
173				dbcom = re.sub(r' \([^)]+\)$', '', entered)
174				if dbcom != valid[case]:
175					errors['nomatch'].append(
176						[' '.join(case), valid[case],
177						 entered])
178
179	if blanks:
180		output.write("WARNING: Blank line(s) in comments\n")
181		ret = 1
182
183	if errors['dup']:
184		ret = 1
185		output.write("These IDs appear more than once in your "
186			     "comments:\n")
187		for err in errors['dup']:
188			output.write("  %s\n" % err)
189
190	if errors['bugnospc']:
191		ret = 1
192		output.write("These bugs are missing a single space following "
193			     "the ID:\n")
194		for com in errors['bugnospc']:
195			output.write("  %s\n" % com)
196
197	if errors['mutant']:
198		ret = 1
199		output.write("These comments are neither bug nor ARC case:\n")
200		for com in errors['mutant']:
201			output.write("  %s\n" % com)
202
203	if errors['nonexistent']:
204		ret = 1
205		output.write("These bugs/ARC cases were not found in the "
206			     "databases:\n")
207		for id in errors['nonexistent']:
208			output.write("  %s\n" % id)
209
210	if errors['nomatch']:
211		ret = 1
212		output.write("These bugs/ARC case synopsis/names don't match "
213			     "the database entries:\n")
214		for err in errors['nomatch']:
215			output.write("Synopsis/name of %s is wrong:\n" % err[0])
216			output.write("  should be: '%s'\n" % err[1])
217			output.write("         is: '%s'\n" % err[2])
218
219	return ret
220