Bug 875605 - Add tests to check-moz-style. r=ms2ger
authorBenoit Girard <b56girard@gmail.com>
Sun, 26 May 2013 09:42:31 -0400
changeset 265641 2c0d1469d2bffa2cd75d6a4696957335d7e6b648
parent 265640 127f29415cfaa05f10ca2949f54f9bd0b01654c1
child 265642 2b27e3f1780dd67cf23ce39e63a4716c52a08e92
push idunknown
push userunknown
push dateunknown
reviewersms2ger
bugs875605
milestone44.0a1
Bug 875605 - Add tests to check-moz-style. r=ms2ger
tools/check-moz-style/check-moz-style
tools/check-moz-style/checkmozstyle.py
tools/check-moz-style/modules/cpplint.py
tools/check-moz-style/modules/diff_parser.py
tools/check-moz-style/run_tests.py
tools/check-moz-style/tests/test1.cpp
tools/check-moz-style/tests/test1.out
tools/check-moz-style/tests/test1.patch
tools/check-moz-style/tests/test2.cpp
tools/check-moz-style/tests/test2.out
tools/check-moz-style/tests/test2.patch
tools/check-moz-style/tests/test3.out
tools/check-moz-style/tests/test3.patch
tools/check-moz-style/tests/test4.cpp
tools/check-moz-style/tests/test4.out
tools/check-moz-style/tests/test4.patch
tools/check-moz-style/tests/test5.cpp
tools/check-moz-style/tests/test5.out
tools/check-moz-style/tests/test5.patch
rename from tools/check-moz-style/check-moz-style
rename to tools/check-moz-style/checkmozstyle.py
--- a/tools/check-moz-style/check-moz-style
+++ b/tools/check-moz-style/checkmozstyle.py
@@ -27,16 +27,17 @@
 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 """Script to run the linter for source code of WebKit."""
 
 import os
 import os.path
+import re
 import sys
 
 import modules.cpplint as cpplint
 from modules.diff_parser import DiffParser
 from modules.scm import detect_scm_system
 
 
 # Override the usage of the lint tool.
@@ -82,64 +83,90 @@ Syntax: %(program_name)s [--verbose=#] [
       Examples: --filter=-whitespace,+whitespace/braces
                 --filter=whitespace,runtime/printf,+runtime/printf_format
                 --filter=-,+build/include_what_you_use
 
       To see a list of all the categories used in %(program_name)s, pass no arg:
          --filter=
 """ % {'program_name': sys.argv[0]}
 
-
-def process_patch(patch_string, cwd, scm):
+def process_patch(patch_string, root, cwd, scm):
     """Does lint on a single patch.
 
     Args:
       patch_string: A string of a patch.
     """
     patch = DiffParser(patch_string.splitlines())
 
+    if not len(patch.files):
+        cpplint.error("patch", 0, "patch/notempty", 3,
+                      "Patch does not appear to diff against any file.")
+        return
+
+    if not patch.status_line:
+        cpplint.error("patch", 0, "patch/nosummary", 3,
+                      "Patch does not have a summary.")
+    else:
+        proper_format = re.match(r"^Bug [0-9]+ - ", patch.status_line)
+        if not proper_format:
+            proper_format = re.match(r"^No bug - ", patch.status_line)
+            cpplint.error("patch", 0, "patch/bugnumber", 3,
+                          "Patch summary should begin with 'Bug XXXXX - ' " +
+                          "or 'No bug -'.")
+
+    if not patch.patch_description:
+        cpplint.error("patch", 0, "patch/nodescription", 3,
+                      "Patch does not have a description.")
+
     for filename, diff in patch.files.iteritems():
         file_extension = os.path.splitext(filename)[1]
 
         if file_extension in ['.cpp', '.c', '.h']:
             line_numbers = set()
+            orig_filename = filename
 
-            def error_for_patch(filename, line_number, category, confidence, message):
+            def error_for_patch(filename, line_number, category, confidence,
+                                message):
                 """Wrapper function of cpplint.error for patches.
 
                 This function outputs errors only if the line number
                 corresponds to lines which are modified or added.
                 """
                 if not line_numbers:
                     for line in diff.lines:
                         # When deleted line is not set, it means that
                         # the line is newly added.
                         if not line[0]:
                             line_numbers.add(line[1])
 
                 if line_number in line_numbers:
-                    cpplint.error(filename, line_number, category, confidence, message)
+                    cpplint.error(orig_filename, line_number,
+                                  category, confidence, message)
 
-            cpplint.process_file(os.path.join(scm.find_checkout_root(cwd), filename), error=error_for_patch)
+            cpplint.process_file(os.path.join(root, filename),
+                                 relative_name=orig_filename,
+                                 error=error_for_patch)
 
 
 def main():
     cpplint.use_mozilla_styles()
 
     (args, flags) = cpplint.parse_arguments(sys.argv[1:], ["git-commit="])
     if args:
-        sys.stderr.write("ERROR: We don't support files as arguments for now.\n" + cpplint._USAGE)
+        sys.stderr.write("ERROR: We don't support files as arguments for " +
+                         "now.\n" + cpplint._USAGE)
         sys.exit(1)
 
     cwd = os.path.abspath('.')
     scm = detect_scm_system(cwd)
+    root = scm.find_checkout_root(cwd)
 
     if "--git-commit" in flags:
-        process_patch(scm.create_patch_from_local_commit(flags["--git-commit"]), cwd, scm)
+        process_patch(scm.create_patch_from_local_commit(flags["--git-commit"]), root, cwd, scm)
     else:
-        process_patch(scm.create_patch(), cwd, scm)
+        process_patch(scm.create_patch(), root, cwd, scm)
 
     sys.stderr.write('Total errors found: %d\n' % cpplint.error_count())
     sys.exit(cpplint.error_count() > 0)
 
 
 if __name__ == "__main__":
     main()
--- a/tools/check-moz-style/modules/cpplint.py
+++ b/tools/check-moz-style/modules/cpplint.py
@@ -357,16 +357,18 @@ class _CppLintState(object):
         # filters to apply when emitting error messages
         self.filters = _DEFAULT_FILTERS[:]
 
         # output format:
         # "emacs" - format that emacs can parse (default)
         # "vs7" - format that Microsoft Visual Studio 7 can parse
         self.output_format = 'emacs'
 
+        self.output_stream = sys.stderr
+
     def set_output_format(self, output_format):
         """Sets the output format for errors."""
         self.output_format = output_format
 
     def set_verbose_level(self, level):
         """Sets the module's verbosity, and returns the previous setting."""
         last_verbose_level = self.verbose_level
         self.verbose_level = level
@@ -400,16 +402,22 @@ class _CppLintState(object):
     def reset_error_count(self):
         """Sets the module's error statistic back to zero."""
         self.error_count = 0
 
     def increment_error_count(self):
         """Bumps the module's error statistic."""
         self.error_count += 1
 
+    def set_stream(self, stream):
+        self.output_stream = stream
+
+    def write_error(self, error):
+        self.output_stream.write(error)
+
 
 _cpplint_state = _CppLintState()
 
 
 def _output_format():
     """Gets the module's output format."""
     return _cpplint_state.output_format
 
@@ -640,20 +648,20 @@ def error(filename, line_number, categor
                   and 1 meaning that it could be a legitimate construct.
       message: The error message.
     """
     # There are two ways we might decide not to print an error message:
     # the verbosity level isn't high enough, or the filters filter it out.
     if _should_print_error(category, confidence):
         _cpplint_state.increment_error_count()
         if _cpplint_state.output_format == 'vs7':
-            sys.stderr.write('%s(%s):  %s  [%s] [%d]\n' % (
+            write_error('%s(%s):  %s  [%s] [%d]\n' % (
                 filename, line_number, message, category, confidence))
         else:
-            sys.stderr.write('%s:%s:  %s  [%s] [%d]\n' % (
+            write_error('%s:%s:  %s  [%s] [%d]\n' % (
                 filename, line_number, message, category, confidence))
 
 
 # Matches standard C++ escape esequences per 2.13.2.3 of the C++ standard.
 _RE_PATTERN_CLEANSE_LINE_ESCAPES = re.compile(
     r'\\([abfnrtv?"\\\']|\d+|x[0-9a-fA-F]+)')
 # Matches strings.  Escape codes should already be removed by ESCAPES.
 _RE_PATTERN_CLEANSE_LINE_DOUBLE_QUOTES = re.compile(r'"[^"]*"')
@@ -842,19 +850,18 @@ def check_for_copyright(filename, lines,
     """Logs an error if no Copyright message appears at the top of the file."""
 
     # We'll say it should occur by line 10. Don't forget there's a
     # dummy line at the front.
     for line in xrange(1, min(len(lines), 11)):
         if re.search(r'Copyright|License', lines[line], re.I):
             break
     else:                       # means no copyright line was found
-        error(filename, 0, 'legal/copyright', 5,
-              'No copyright message found.  '
-              'You should have a line: "Copyright [year] <Copyright Owner>"')
+        error(filename, 1, 'legal/copyright', 3,
+              'No copyright message found.')
 
 
 def get_header_guard_cpp_variable(filename):
     """Returns the CPP variable that should be used as a header guard.
 
     Args:
       filename: The name of a C++ header file.
 
@@ -898,17 +905,17 @@ def check_for_header_guard(filename, lin
             if not define and line_split[0] == '#define':
                 define = line_split[1]
         # find the last occurrence of #endif, save entire line
         if line.startswith('#endif'):
             endif = line
             endif_line_number = line_number
 
     if not ifndef or not define or ifndef != define:
-        error(filename, 0, 'build/header_guard', 5,
+        error(filename, 1, 'build/header_guard', 5,
               'No #ifndef header guard found, suggested CPP variable is: %s' %
               cppvar)
         return
 
     # The guard should be PATH_FILE_H_, but we also allow PATH_FILE_H__
     # for backward compatibility.
     if ifndef != cppvar:
         error_level = 0
@@ -2453,17 +2460,17 @@ def check_language(filename, clean_lines
               'For a static/global string constant, use a C style string instead: '
               '"%schar %s[]".' %
               (matched.group(1), matched.group(2)))
 
     # Check that we're not using RTTI outside of testing code.
     if search(r'\bdynamic_cast<', line) and not _is_test_filename(filename):
         error(filename, line_number, 'runtime/rtti', 5,
               'Do not use dynamic_cast<>.  If you need to cast within a class '
-              "hierarchy, use static_cast<> to upcast.  Google doesn't support "
+              "hierarchy, use static_cast<> to upcast.  Mozilla doesn't support "
               'RTTI.')
 
     if search(r'\b([A-Za-z0-9_]*_)\(\1\)', line):
         error(filename, line_number, 'runtime/init', 4,
               'You seem to be initializing a member variable with itself.')
 
     if file_extension == 'h':
         # FIXME: check that 1-arg constructors are explicit.
@@ -2940,23 +2947,27 @@ def process_file_data(filename, file_ext
 
     # We check here rather than inside process_line so that we see raw
     # lines rather than "cleaned" lines.
     check_for_unicode_replacement_characters(filename, lines, error)
 
     check_for_new_line_at_eof(filename, lines, error)
 
 
-def process_file(filename, error=error):
+def process_file(filename, relative_name=None, error=error):
     """Performs cpplint on a single file.
 
     Args:
       filename: The name of the file to parse.
       error: The function to call with any errors found.
     """
+
+    if not relative_name:
+        relative_name = filename
+
     try:
         # Support the UNIX convention of using "-" for stdin.  Note that
         # we are not opening the file with universal newline support
         # (which codecs doesn't support anyway), so the resulting lines do
         # contain trailing '\r' characters if we are reading a file that
         # has CRLF endings.
         # If after the split a trailing '\r' is present, it is removed
         # below. If it is not expected to be present (i.e. os.linesep !=
@@ -2974,59 +2985,59 @@ def process_file(filename, error=error):
         carriage_return_found = False
         # Remove trailing '\r'.
         for line_number in range(len(lines)):
             if lines[line_number].endswith('\r'):
                 lines[line_number] = lines[line_number].rstrip('\r')
                 carriage_return_found = True
 
     except IOError:
-        sys.stderr.write(
-            "Skipping input '%s': Can't open for reading\n" % filename)
+        write_error(
+            "Skipping input '%s': Can't open for reading\n" % relative_name)
         return
 
     # Note, if no dot is found, this will give the entire filename as the ext.
     file_extension = filename[filename.rfind('.') + 1:]
 
     # When reading from stdin, the extension is unknown, so no cpplint tests
     # should rely on the extension.
     if (filename != '-' and file_extension != 'h' and file_extension != 'cpp'
         and file_extension != 'c'):
-        sys.stderr.write('Ignoring %s; not a .cpp, .c or .h file\n' % filename)
+        write_error('Ignoring %s; not a .cpp, .c or .h file\n' % filename)
     else:
-        process_file_data(filename, file_extension, lines, error)
+        process_file_data(relative_name, file_extension, lines, error)
         if carriage_return_found and os.linesep != '\r\n':
             # Use 0 for line_number since outputing only one error for potentially
             # several lines.
-            error(filename, 0, 'whitespace/newline', 1,
+            error(relative_name, 1, 'whitespace/newline', 1,
                   'One or more unexpected \\r (^M) found;'
                   'better to use only a \\n')
 
-    sys.stderr.write('Done processing %s\n' % filename)
+    write_error('Done processing %s\n' % relative_name)
 
 
 def print_usage(message):
     """Prints a brief usage string and exits, optionally with an error message.
 
     Args:
       message: The optional error message.
     """
-    sys.stderr.write(_USAGE)
+    write_error(_USAGE)
     if message:
         sys.exit('\nFATAL ERROR: ' + message)
     else:
         sys.exit(1)
 
 
 def print_categories():
     """Prints a list of all the error-categories used by error messages.
 
     These are the categories used to filter messages via --filter.
     """
-    sys.stderr.write(_ERROR_CATEGORIES)
+    write_error(_ERROR_CATEGORIES)
     sys.exit(0)
 
 
 def parse_arguments(args, additional_flags=[]):
     """Parses the command line arguments.
 
     This may set the output format and verbosity level as side-effects.
 
@@ -3069,39 +3080,47 @@ def parse_arguments(args, additional_fla
 
     _set_output_format(output_format)
     _set_verbose_level(verbosity)
     _set_filters(filters)
 
     return (filenames, additional_flag_values)
 
 
+def set_stream(stream):
+    _cpplint_state.set_stream(stream)
+
+def write_error(error):
+    _cpplint_state.write_error(error)
+
 def use_mozilla_styles():
     """Disables some features which are not suitable for WebKit."""
     # FIXME: For filters we will never want to have, remove them.
     #        For filters we want to have similar functionalities,
     #        modify the implementation and enable them.
     global _DEFAULT_FILTERS
     _DEFAULT_FILTERS = [
         '-whitespace/comments-doublespace',
         '-whitespace/blank_line',
+        '-build/include',  # Webkit specific
         '-build/include_what_you_use',  # <string> for std::string
         '-readability/braces',  # int foo() {};
+        '-readability/null',
         '-readability/fn_size',
         '-build/storage_class',  # const static
         '-build/endif_comment',
         '-whitespace/labels',
         '-runtime/arrays',  # variable length array
-        '-build/header_guard',
+        '-build/header_guard', # TODO Write a mozilla header_guard variant
         '-runtime/casting',
     ]
 
 
 def main():
-    sys.stderr.write(
+    write_error(
         '''********************* WARNING WARNING WARNING *********************
 
 This tool is in the process of development and may give inaccurate
 results at present.  Please file bugs (and/or patches) for things
 that you notice that it flags incorrectly.
 
 ********************* WARNING WARNING WARNING *********************
 
@@ -3118,14 +3137,14 @@ that you notice that it flags incorrectl
     sys.stderr = codecs.StreamReaderWriter(sys.stderr,
                                            codecs.getreader('utf8'),
                                            codecs.getwriter('utf8'),
                                            'replace')
 
     _cpplint_state.reset_error_count()
     for filename in filenames:
         process_file(filename)
-    sys.stderr.write('Total errors found: %d\n' % _cpplint_state.error_count)
+    write_error('Total errors found: %d\n' % _cpplint_state.error_count)
     sys.exit(_cpplint_state.error_count > 0)
 
 
 if __name__ == '__main__':
     main()
--- a/tools/check-moz-style/modules/diff_parser.py
+++ b/tools/check-moz-style/modules/diff_parser.py
@@ -113,25 +113,31 @@ class DiffParser:
         """Parses a diff.
 
         Args:
           diff_input: An iterable object.
         """
         state = _INITIAL_STATE
 
         self.files = {}
+        self.status_line = None
+        self.patch_description = None
         current_file = None
         old_diff_line = None
         new_diff_line = None
         for line in diff_input:
             line = line.rstrip("\n")
             if state == _INITIAL_STATE:
                 transform_line = get_diff_converter(line)
             line = transform_line(line)
 
+            comment_line = match(r"^\#", line)
+            if comment_line:
+                continue
+
             file_declaration = match(r"^Index: (?P<FilePath>.+)", line)
             if file_declaration:
                 filename = file_declaration.group('FilePath')
                 current_file = DiffFile(filename)
                 self.files[filename] = current_file
                 state = _DECLARED_FILE_PATH
                 continue
 
@@ -155,8 +161,20 @@ class DiffParser:
                     current_file.add_unchanged_line(old_diff_line, new_diff_line, line[1:])
                     old_diff_line += 1
                     new_diff_line += 1
                 elif line == '\\ No newline at end of file':
                     # Nothing to do.  We may still have some added lines.
                     pass
                 else:
                     logging.error('Unexpected diff format when parsing a chunk: %r' % line)
+
+            # Patch description
+            if state == _INITIAL_STATE:
+                if not self.status_line:
+                    self.status_line = line
+                else:
+                    if not self.patch_description:
+                        # Skip the first blank line after the patch description
+                        if line != "":
+                            self.patch_description = line
+                    else:
+                        self.patch_description = self.patch_description + "\n" + line
new file mode 100755
--- /dev/null
+++ b/tools/check-moz-style/run_tests.py
@@ -0,0 +1,78 @@
+#!/usr/bin/python
+#
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+#
+
+from __future__ import print_function
+from modules.scm import detect_scm_system
+from contextlib import closing
+import checkmozstyle
+import os
+import modules.cpplint as cpplint
+import StringIO
+
+TESTS = [
+    # Empty patch
+    {
+        "patch": "tests/test1.patch",
+        "cpp": "tests/test1.cpp",
+        "out": "tests/test1.out"
+    },
+    # Bad header
+    {
+        "patch": "tests/test2.patch",
+        "cpp": "tests/test2.cpp",
+        "out": "tests/test2.out"
+    },
+    # Bad Description
+    {
+        "patch": "tests/test3.patch",
+        "cpp": "tests/test3.cpp",
+        "out": "tests/test3.out"
+    },
+    # readability tests
+    {
+        "patch": "tests/test4.patch",
+        "cpp": "tests/test4.cpp",
+        "out": "tests/test4.out"
+    },
+    # runtime tests
+    {
+        "patch": "tests/test5.patch",
+        "cpp": "tests/test5.cpp",
+        "out": "tests/test5.out"
+    },
+]
+
+
+def main():
+    cwd = os.path.abspath('.')
+    scm = detect_scm_system(cwd)
+    cpplint.use_mozilla_styles()
+    (args, flags) = cpplint.parse_arguments([])
+
+    for test in TESTS:
+        with open(test["patch"]) as fh:
+            patch = fh.read()
+
+        with closing(StringIO.StringIO()) as output:
+            cpplint.set_stream(output)
+            checkmozstyle.process_patch(patch, cwd, cwd, scm)
+            result = output.getvalue()
+
+        with open(test["out"]) as fh:
+            expected_output = fh.read()
+
+        test_status = "PASSED"
+        if result != expected_output:
+            test_status = "FAILED"
+            print("TEST " + test["patch"] + " " + test_status)
+            print("Got result:\n" + result + "Expected:\n" + expected_output)
+        else:
+            print("TEST " + test["patch"] + " " + test_status)
+
+
+if __name__ == "__main__":
+        main()
+
new file mode 100644
new file mode 100644
--- /dev/null
+++ b/tools/check-moz-style/tests/test1.out
@@ -0,0 +1,1 @@
+patch:0:  Patch does not appear to diff against any file.  [patch/notempty] [3]
new file mode 100644
--- /dev/null
+++ b/tools/check-moz-style/tests/test1.patch
@@ -0,0 +1,1 @@
+Bad patch that doesn't diff any files
new file mode 100644
--- /dev/null
+++ b/tools/check-moz-style/tests/test2.cpp
@@ -0,0 +1,3 @@
+int main() {
+  return 0;
+}
new file mode 100644
--- /dev/null
+++ b/tools/check-moz-style/tests/test2.out
@@ -0,0 +1,4 @@
+patch:0:  Patch does not have a summary.  [patch/nosummary] [3]
+patch:0:  Patch does not have a description.  [patch/nodescription] [3]
+tests/test2.cpp:1:  No copyright message found.  [legal/copyright] [3]
+Done processing tests/test2.cpp
new file mode 100644
--- /dev/null
+++ b/tools/check-moz-style/tests/test2.patch
@@ -0,0 +1,9 @@
+# Test
+diff --git a/tests/test2.cpp b/tests/test2.cpp
+new file mode 100644
+--- /dev/null
++++ b/tests/test2.cpp
+@@ -0,0 +1,3 @@
++int main() {
++  return 0;
++}
new file mode 100644
--- /dev/null
+++ b/tools/check-moz-style/tests/test3.out
@@ -0,0 +1,3 @@
+patch:0:  Patch summary should begin with 'Bug XXXXX - ' or 'No bug -'.  [patch/bugnumber] [3]
+tests/test2.cpp:1:  No copyright message found.  [legal/copyright] [3]
+Done processing tests/test2.cpp
new file mode 100644
--- /dev/null
+++ b/tools/check-moz-style/tests/test3.patch
@@ -0,0 +1,12 @@
+# Test
+patch summary with no bug number
+
+Some bogus patch description
+diff --git a/tests/test2.cpp b/tests/test2.cpp
+new file mode 100644
+--- /dev/null
++++ b/tests/test2.cpp
+@@ -0,0 +1,3 @@
++int main() {
++  return 0;
++}
new file mode 100644
--- /dev/null
+++ b/tools/check-moz-style/tests/test4.cpp
@@ -0,0 +1,40 @@
+class ShouldUseExplicit {
+  // runtime/explicit
+  ShouldUseExplicit(int i);
+};
+
+// readability/function
+int foo(int) {
+}
+
+int main() {
+  int i = 0;
+
+  // readability/control_flow
+  // XXX This doesn't trigger it. It needs to be fixed.
+  if (i) {
+    return;
+  } else {
+    i++;
+  }
+
+  // whitespace/parens
+  if(i){}
+
+  // readability/casting
+  void* bad = (void*)i;
+
+  // readability/comparison_to_zero
+  if (i == true) {}
+  if (i == false) {}
+  if (i != true) {}
+  if (i != false) {}
+  if (i == NULL) {}
+  if (i != NULL) {}
+  if (i == nullptr) {}
+  if (i != nullptr) {}
+  if (i) {}
+  if (!i) {}
+
+  return 0;
+}
new file mode 100644
--- /dev/null
+++ b/tools/check-moz-style/tests/test4.out
@@ -0,0 +1,13 @@
+tests/test4.cpp:1:  No copyright message found.  [legal/copyright] [3]
+tests/test4.cpp:3:  Single-argument constructors should be marked explicit.  [runtime/explicit] [5]
+tests/test4.cpp:7:  All parameters should be named in a function  [readability/function] [3]
+tests/test4.cpp:22:  Missing space before ( in if(  [whitespace/parens] [5]
+tests/test4.cpp:22:  Missing space before {  [whitespace/braces] [5]
+tests/test4.cpp:25:  Using C-style cast.  Use reinterpret_cast<void*>(...) instead  [readability/casting] [4]
+tests/test4.cpp:28:  Tests for true/false, null/non-null, and zero/non-zero should all be done without equality comparisons.  [readability/comparison_to_zero] [5]
+tests/test4.cpp:29:  Tests for true/false, null/non-null, and zero/non-zero should all be done without equality comparisons.  [readability/comparison_to_zero] [5]
+tests/test4.cpp:30:  Tests for true/false, null/non-null, and zero/non-zero should all be done without equality comparisons.  [readability/comparison_to_zero] [5]
+tests/test4.cpp:31:  Tests for true/false, null/non-null, and zero/non-zero should all be done without equality comparisons.  [readability/comparison_to_zero] [5]
+tests/test4.cpp:32:  Tests for true/false, null/non-null, and zero/non-zero should all be done without equality comparisons.  [readability/comparison_to_zero] [5]
+tests/test4.cpp:33:  Tests for true/false, null/non-null, and zero/non-zero should all be done without equality comparisons.  [readability/comparison_to_zero] [5]
+Done processing tests/test4.cpp
new file mode 100644
--- /dev/null
+++ b/tools/check-moz-style/tests/test4.patch
@@ -0,0 +1,49 @@
+# Test
+Bug 12 - patch summary with no bug number
+
+Some bogus patch description
+diff --git a/tests/test4.cpp b/tests/test4.cpp
+new file mode 100644
+--- /dev/null
++++ b/tests/test4.cpp
+@@ -0,0 +1,49 @@
++class ShouldUseExplicit {
++  // runtime/explicit
++  ShouldUseExplicit(int i);
++};
++
++// readability/function
++int foo(int) {
++}
++
++int main() {
++  int i = 0;
++
++  // readability/control_flow
++  // XXX This doesn't trigger it. It needs to be fixed.
++  if (i) {
++    return;
++  } else {
++    i++;
++  }
++
++  // whitespace/parens
++  if(i){}
++
++  // readability/casting
++  void* bad = (void*)i;
++
++  // readability/comparison_to_zero
++  if (i == true) {}
++  if (i == false) {}
++  if (i != true) {}
++  if (i != false) {}
++  if (i == NULL) {}
++  if (i != NULL) {}
++  if (i == nullptr) {}
++  if (i != nullptr) {}
++  if (i) {}
++  if (!i) {}
++
++  return 0;
++}
new file mode 100644
--- /dev/null
+++ b/tools/check-moz-style/tests/test5.cpp
@@ -0,0 +1,24 @@
+// License bogus
+
+// runtime/virtual
+class ShouldHaveVirtualDes {
+  virtual foo();
+};
+
+int main() {
+  // runtime/memset
+  memset(blah, sizeof(blah), 0);
+
+  // runtime/rtti
+  dynamic_cast<Derived*>(obj);
+
+  // runtime/sizeof
+  int varname = 0;
+  int mySize = sizeof(int);
+
+  // runtime/threadsafe_fn
+  getpwuid();
+  strtok();
+
+  return 0;
+}
new file mode 100644
--- /dev/null
+++ b/tools/check-moz-style/tests/test5.out
@@ -0,0 +1,7 @@
+tests/test5.cpp:4:  The class ShouldHaveVirtualDes probably needs a virtual destructor due to having virtual method(s), one declared at line 5.  [runtime/virtual] [4]
+tests/test5.cpp:10:  Did you mean "memset(blah, 0, sizeof(blah))"?  [runtime/memset] [4]
+tests/test5.cpp:13:  Do not use dynamic_cast<>.  If you need to cast within a class hierarchy, use static_cast<> to upcast.  Mozilla doesn't support RTTI.  [runtime/rtti] [5]
+tests/test5.cpp:17:  Using sizeof(type).  Use sizeof(varname) instead if possible  [runtime/sizeof] [1]
+tests/test5.cpp:20:  Consider using getpwuid_r(...) instead of getpwuid(...) for improved thread safety.  [runtime/threadsafe_fn] [2]
+tests/test5.cpp:21:  Consider using strtok_r(...) instead of strtok(...) for improved thread safety.  [runtime/threadsafe_fn] [2]
+Done processing tests/test5.cpp
new file mode 100644
--- /dev/null
+++ b/tools/check-moz-style/tests/test5.patch
@@ -0,0 +1,33 @@
+# Test
+Bug 12 - patch summary with no bug number
+
+Some bogus patch description
+diff --git a/tests/test5.cpp b/tests/test5.cpp
+new file mode 100644
+--- /dev/null
++++ b/tests/test5.cpp
+@@ -0,0 +1,24 @@
++// License bogus
++
++// runtime/virtual
++class ShouldHaveVirtualDes {
++  virtual foo();
++};
++
++int main() {
++  // runtime/memset
++  memset(blah, sizeof(blah), 0);
++
++  // runtime/rtti
++  dynamic_cast<Derived*>(obj);
++
++  // runtime/sizeof
++  int varname = 0;
++  int mySize = sizeof(int);
++
++  // runtime/threadsafe_fn
++  getpwuid();
++  strtok();
++
++  return 0;
++}