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 2008 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):
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			case = normalize_arc(match.group(1))
116			if case not in arcs: arcs[case] = []
117			arcs[case].append(match.group(2))
118			continue
119
120		# Anything else is bogus
121		errors['mutant'].append(com)
122
123	if len(bugs) > 0 and check_db:
124		bugdb = BugDB()
125		results = bugdb.lookup(bugs.keys())
126
127	for crid, insts in bugs.iteritems():
128		if len(insts) > 1:
129			errors['dup'].append(crid)
130
131		if not check_db:
132			continue
133
134		if crid not in results:
135			errors['nonexistent'].append(crid)
136			continue
137
138		#
139		# For each synopsis, compare the real synopsis with
140		# that in the comments, allowing for possible '(fix
141		# stuff)'-like trailing text
142		#
143		for entered in insts:
144			synopsis = results[crid]["synopsis"]
145			if not re.search(r'^' + re.escape(synopsis) +
146					 r'( \([^)]+\))?$', entered):
147				errors['nomatch'].append([crid, synopsis,
148							  entered])
149
150	for case, insts in arcs.iteritems():
151		if len(insts) > 1:
152			errors['dup'].append(case)
153
154		if not check_db:
155			continue
156
157		com, id = case.split(' ')
158		arc = ARC(com, id)
159
160		if not arc.valid():
161			errors['nonexistent'].append(case)
162			continue
163
164		#
165		# The opensolaris.org ARC interfaces only give us the
166		# first 40 characters of the case name, so we must limit
167		# our checking similarly.
168		#
169		# We first try a direct match between the actual case name
170		# and the entered comment.  If that fails we remove a possible
171		# trailing (fix nit)-type comment, and re-try.
172		#
173		for entered in insts:
174			if entered[0:40] == arc.name():
175				continue
176			else:
177				# Try again with trailing (fix ...) removed.
178				dbcom = re.sub(r' \([^)]+\)$', '', entered)
179				if dbcom[0:40] != arc.name():
180					errors['nomatch'].append([case,
181								  arc.name(),
182								  entered])
183
184	if blanks:
185		output.write("WARNING: Blank line(s) in comments\n")
186		ret = 1
187
188	if errors['dup']:
189		ret = 1
190		output.write("These IDs appear more than once in your "
191			     "comments:\n")
192		for err in errors['dup']:
193			output.write("  %s\n" % err)
194
195	if errors['bugnospc']:
196		ret = 1
197		output.write("These bugs are missing a single space following "
198			     "the ID:\n")
199		for com in errors['bugnospc']:
200			output.write("  %s\n" % com)
201
202	if errors['mutant']:
203		ret = 1
204		output.write("These comments are neither bug nor ARC case:\n")
205		for com in errors['mutant']:
206			output.write("  %s\n" % com)
207
208	if errors['nonexistent']:
209		ret = 1
210		output.write("These bugs/ARC cases were not found in the "
211			     "databases:\n")
212		for id in errors['nonexistent']:
213			output.write("  %s\n" % id)
214
215	if errors['nomatch']:
216		ret = 1
217		output.write("These bugs/ARC case synopsis/names don't match "
218			     "the database entries:\n")
219		for err in errors['nomatch']:
220			output.write("Synopsis/name of %s is wrong:\n" % err[0])
221			output.write("  should be: '%s'\n" % err[1])
222			output.write("         is: '%s'\n" % err[2])
223
224	return ret
225