Bug 875605 - Add tests to check-moz-style. r=ms2ger
authorBenoit Girard <b56girard@gmail.com>
Sun, 26 May 2013 09:42:31 -0400
changeset 265557 2c0d1469d2bffa2cd75d6a4696957335d7e6b648
parent 265556 127f29415cfaa05f10ca2949f54f9bd0b01654c1
child 265558 2b27e3f1780dd67cf23ce39e63a4716c52a08e92
push id65974
push userb56girard@gmail.com
push dateThu, 01 Oct 2015 19:38:53 +0000
treeherdermozilla-inbound@2c0d1469d2bf [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersms2ger
bugs875605
milestone44.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
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;
++}