| Index: presubmit_support.py
|
| diff --git a/presubmit_support.py b/presubmit_support.py
|
| index fc1c7f23b0c09d666c88b2e03cb19eb50b828ba4..7ee2b0d1326856a9e8343c7ba1545a3916a31a99 100755
|
| --- a/presubmit_support.py
|
| +++ b/presubmit_support.py
|
| @@ -78,12 +78,6 @@ def normpath(path):
|
| return os.path.normpath(path)
|
|
|
|
|
| -def PromptYesNo(input_stream, output_stream, prompt):
|
| - output_stream.write(prompt)
|
| - response = input_stream.readline().strip().lower()
|
| - return response == 'y' or response == 'yes'
|
| -
|
| -
|
| def _RightHandSideLinesImpl(affected_files):
|
| """Implements RightHandSideLines for InputApi and GclChange."""
|
| for af in affected_files:
|
| @@ -92,14 +86,46 @@ def _RightHandSideLinesImpl(affected_files):
|
| yield (af, line[0], line[1])
|
|
|
|
|
| +class PresubmitOutput(object):
|
| + def __init__(self, input_stream=None, output_stream=None):
|
| + self.input_stream = input_stream
|
| + self.output_stream = output_stream
|
| + self.reviewers = []
|
| + self.written_output = []
|
| + self.error_count = 0
|
| +
|
| + def prompt_yes_no(self, prompt_string):
|
| + self.write(prompt_string)
|
| + if self.input_stream:
|
| + response = self.input_stream.readline().strip().lower()
|
| + if response not in ('y', 'yes'):
|
| + self.fail()
|
| + else:
|
| + self.fail()
|
| +
|
| + def fail(self):
|
| + self.error_count += 1
|
| +
|
| + def should_continue(self):
|
| + return not self.error_count
|
| +
|
| + def write(self, s):
|
| + self.written_output.append(s)
|
| + if self.output_stream:
|
| + self.output_stream.write(s)
|
| +
|
| + def getvalue(self):
|
| + return ''.join(self.written_output)
|
| +
|
| +
|
| class OutputApi(object):
|
| """This class (more like a module) gets passed to presubmit scripts so that
|
| they can specify various types of results.
|
| """
|
| - # Method could be a function
|
| - # pylint: disable=R0201
|
| class PresubmitResult(object):
|
| """Base class for result objects."""
|
| + fatal = False
|
| + should_prompt = False
|
|
|
| def __init__(self, message, items=None, long_text=''):
|
| """
|
| @@ -113,19 +139,11 @@ class OutputApi(object):
|
| self._items = items
|
| self._long_text = long_text.rstrip()
|
|
|
| - def _Handle(self, output_stream, input_stream, may_prompt=True):
|
| - """Writes this result to the output stream.
|
| -
|
| - Args:
|
| - output_stream: Where to write
|
| -
|
| - Returns:
|
| - True if execution may continue, False otherwise.
|
| - """
|
| - output_stream.write(self._message)
|
| - output_stream.write('\n')
|
| + def handle(self, output):
|
| + output.write(self._message)
|
| + output.write('\n')
|
| if len(self._items) > 0:
|
| - output_stream.write(' ' + ' \\\n '.join(map(str, self._items)) + '\n')
|
| + output.write(' ' + ' \\\n '.join(map(str, self._items)) + '\n')
|
| if self._long_text:
|
| # Sometimes self._long_text is a ascii string, a codepage string
|
| # (on windows), or a unicode object.
|
| @@ -134,41 +152,27 @@ class OutputApi(object):
|
| except UnicodeDecodeError:
|
| long_text = self._long_text.decode('ascii', 'replace')
|
|
|
| - output_stream.write('\n***************\n%s\n***************\n' %
|
| + output.write('\n***************\n%s\n***************\n' %
|
| long_text)
|
| + if self.fatal:
|
| + output.fail()
|
|
|
| - if self.ShouldPrompt() and may_prompt:
|
| - if not PromptYesNo(input_stream, output_stream,
|
| - 'Are you sure you want to continue? (y/N): '):
|
| - return False
|
| + class PresubmitAddReviewers(PresubmitResult):
|
| + """Add some suggested reviewers to the change."""
|
| + def __init__(self, reviewers):
|
| + super(OutputApi.PresubmitAddReviewers, self).__init__('')
|
| + self.reviewers = reviewers
|
|
|
| - return not self.IsFatal()
|
| -
|
| - def IsFatal(self):
|
| - """An error that is fatal stops g4 mail/submit immediately, i.e. before
|
| - other presubmit scripts are run.
|
| - """
|
| - return False
|
| -
|
| - def ShouldPrompt(self):
|
| - """Whether this presubmit result should result in a prompt warning."""
|
| - return False
|
| -
|
| - class PresubmitAddText(PresubmitResult):
|
| - """Propagates a line of text back to the caller."""
|
| - def __init__(self, message, items=None, long_text=''):
|
| - super(OutputApi.PresubmitAddText, self).__init__("ADD: " + message,
|
| - items, long_text)
|
| + def handle(self, output):
|
| + output.reviewers.extend(self.reviewers)
|
|
|
| class PresubmitError(PresubmitResult):
|
| """A hard presubmit error."""
|
| - def IsFatal(self):
|
| - return True
|
| + fatal = True
|
|
|
| class PresubmitPromptWarning(PresubmitResult):
|
| """An warning that prompts the user if they want to continue."""
|
| - def ShouldPrompt(self):
|
| - return True
|
| + should_prompt = True
|
|
|
| class PresubmitNotifyResult(PresubmitResult):
|
| """Just print something to the screen -- but it's not even a warning."""
|
| @@ -993,6 +997,7 @@ class PresubmitExecuter(object):
|
| os.chdir(main_path)
|
| return result
|
|
|
| +
|
| # TODO(dpranke): make all callers pass in tbr, host_url?
|
| def DoPresubmitChecks(change,
|
| committing,
|
| @@ -1029,25 +1034,27 @@ def DoPresubmitChecks(change,
|
| SHOULD be sys.stdin.
|
|
|
| Return:
|
| - True if execution can continue, False if not.
|
| + A PresubmitOutput object. Use output.should_continue() to figure out
|
| + if there were errors or warnings and the caller should abort.
|
| """
|
| - print "Running presubmit hooks..."
|
| + output = PresubmitOutput(input_stream, output_stream)
|
| + output.write("Running presubmit hooks...\n")
|
| start_time = time.time()
|
| presubmit_files = ListRelevantPresubmitFiles(change.AbsoluteLocalPaths(True),
|
| change.RepositoryRoot())
|
| if not presubmit_files and verbose:
|
| - output_stream.write("Warning, no presubmit.py found.\n")
|
| + output.write("Warning, no presubmit.py found.\n")
|
| results = []
|
| executer = PresubmitExecuter(change, committing, tbr, host_url)
|
| if default_presubmit:
|
| if verbose:
|
| - output_stream.write("Running default presubmit script.\n")
|
| + output.write("Running default presubmit script.\n")
|
| fake_path = os.path.join(change.RepositoryRoot(), 'PRESUBMIT.py')
|
| results += executer.ExecPresubmitScript(default_presubmit, fake_path)
|
| for filename in presubmit_files:
|
| filename = os.path.abspath(filename)
|
| if verbose:
|
| - output_stream.write("Running %s\n" % filename)
|
| + output.write("Running %s\n" % filename)
|
| # Accept CRLF presubmit script.
|
| presubmit_script = gclient_utils.FileRead(filename, 'rU')
|
| results += executer.ExecPresubmitScript(presubmit_script, filename)
|
| @@ -1056,44 +1063,37 @@ def DoPresubmitChecks(change,
|
| notifications = []
|
| warnings = []
|
| for result in results:
|
| - if not result.IsFatal() and not result.ShouldPrompt():
|
| - notifications.append(result)
|
| - elif result.ShouldPrompt():
|
| + if result.fatal:
|
| + errors.append(result)
|
| + elif result.should_prompt:
|
| warnings.append(result)
|
| else:
|
| - errors.append(result)
|
| + notifications.append(result)
|
|
|
| - error_count = 0
|
| for name, items in (('Messages', notifications),
|
| ('Warnings', warnings),
|
| ('ERRORS', errors)):
|
| if items:
|
| - output_stream.write('** Presubmit %s **\n' % name)
|
| + output.write('** Presubmit %s **\n' % name)
|
| for item in items:
|
| - # Access to a protected member XXX of a client class
|
| - # pylint: disable=W0212
|
| - if not item._Handle(output_stream, input_stream,
|
| - may_prompt=False):
|
| - error_count += 1
|
| - output_stream.write('\n')
|
| + item.handle(output)
|
| + output.write('\n')
|
|
|
| total_time = time.time() - start_time
|
| if total_time > 1.0:
|
| - print "Presubmit checks took %.1fs to calculate." % total_time
|
| + output.write("Presubmit checks took %.1fs to calculate.\n" % total_time)
|
|
|
| if not errors and warnings and may_prompt:
|
| - if not PromptYesNo(input_stream, output_stream,
|
| - 'There were presubmit warnings. '
|
| - 'Are you sure you wish to continue? (y/N): '):
|
| - error_count += 1
|
| + output.prompt_yes_no('There were presubmit warnings. '
|
| + 'Are you sure you wish to continue? (y/N): ')
|
|
|
| global _ASKED_FOR_FEEDBACK
|
| # Ask for feedback one time out of 5.
|
| if (len(results) and random.randint(0, 4) == 0 and not _ASKED_FOR_FEEDBACK):
|
| - output_stream.write("Was the presubmit check useful? Please send feedback "
|
| - "& hate mail to maruel@chromium.org!\n")
|
| + output.write("Was the presubmit check useful? Please send feedback "
|
| + "& hate mail to maruel@chromium.org!\n")
|
| _ASKED_FOR_FEEDBACK = True
|
| - return (error_count == 0)
|
| + return output
|
|
|
|
|
| def ScanSubDirs(mask, recursive):
|
| @@ -1179,18 +1179,19 @@ def Main(argv):
|
| print "Found %d files." % len(options.files)
|
| else:
|
| print "Found 1 file."
|
| - return not DoPresubmitChecks(change_class(options.name,
|
| - options.description,
|
| - options.root,
|
| - options.files,
|
| - options.issue,
|
| - options.patchset),
|
| - options.commit,
|
| - options.verbose,
|
| - sys.stdout,
|
| - sys.stdin,
|
| - options.default_presubmit,
|
| - options.may_prompt)
|
| + results = DoPresubmitChecks(change_class(options.name,
|
| + options.description,
|
| + options.root,
|
| + options.files,
|
| + options.issue,
|
| + options.patchset),
|
| + options.commit,
|
| + options.verbose,
|
| + sys.stdout,
|
| + sys.stdin,
|
| + options.default_presubmit,
|
| + options.may_prompt)
|
| + return not results.should_continue()
|
|
|
|
|
| if __name__ == '__main__':
|
|
|