Use pyfakefs in generate_buildbot_json_unittest.

Creating a fake so that read_file and write_file operate on a dictionary
means that many operations do not perform the same between the test and
production (e.g. raising a KeyError when attempting to read a
non-existent file instead of an IOError or finding the actual file on
the filesystem for a file that should not exist when using glob).

The fake filesystem provided by pyfakefs means that the test will only
see files that have actually been created by the test (modulo some
default files such as /tmp) and no mocking is necessary without paying
the cost of writing out to disk.

Change-Id: Ib3401b62cdac93289e9ba327d9a8f39d47bb45c9
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2267502
Reviewed-by: Stephen Martinis <[email protected]>
Commit-Queue: Stephen Martinis <[email protected]>
Auto-Submit: Garrett Beaty <[email protected]>
Cr-Commit-Position: refs/heads/master@{#782609}
diff --git a/testing/buildbot/generate_buildbot_json.py b/testing/buildbot/generate_buildbot_json.py
index c9e9db4..180719f 100755
--- a/testing/buildbot/generate_buildbot_json.py
+++ b/testing/buildbot/generate_buildbot_json.py
@@ -213,9 +213,10 @@
   """Ensure comound reference's don't target other compounds"""
   del kwargs
   if sub_suite in other_test_suites or sub_suite in target_test_suites:
-      raise BBGenErr('%s may not refer to other composition type test '
-                     'suites (error found while processing %s)'
-                     % (test_type, suite))
+    raise BBGenErr('%s may not refer to other composition type test '
+                   'suites (error found while processing %s)' %
+                   (test_type, suite))
+
 
 def check_basic_references(basic_suites=None,
                            sub_suite=None,
@@ -224,8 +225,9 @@
   """Ensure test has a basic suite reference"""
   del kwargs
   if sub_suite not in basic_suites:
-      raise BBGenErr('Unable to find reference to %s while processing %s'
-                     % (sub_suite, suite))
+    raise BBGenErr('Unable to find reference to %s while processing %s' %
+                   (sub_suite, suite))
+
 
 def check_conflicting_definitions(basic_suites=None,
                                   seen_tests=None,
@@ -268,9 +270,9 @@
 
 
 class BBJSONGenerator(object):
-  def __init__(self):
+  def __init__(self, args):
     self.this_dir = THIS_DIR
-    self.args = None
+    self.args = args
     self.waterfalls = None
     self.test_suites = None
     self.exceptions = None
@@ -278,22 +280,99 @@
     self.gn_isolate_map = None
     self.variants = None
 
+  @staticmethod
+  def parse_args(argv):
+
+    # RawTextHelpFormatter allows for styling of help statement
+    parser = argparse.ArgumentParser(
+        formatter_class=argparse.RawTextHelpFormatter)
+
+    group = parser.add_mutually_exclusive_group()
+    group.add_argument(
+        '-c',
+        '--check',
+        action='store_true',
+        help=
+        'Do consistency checks of configuration and generated files and then '
+        'exit. Used during presubmit. '
+        'Causes the tool to not generate any files.')
+    group.add_argument(
+        '--query',
+        type=str,
+        help=(
+            "Returns raw JSON information of buildbots and tests.\n" +
+            "Examples:\n" + "  List all bots (all info):\n" +
+            "    --query bots\n\n" +
+            "  List all bots and only their associated tests:\n" +
+            "    --query bots/tests\n\n" +
+            "  List all information about 'bot1' " +
+            "(make sure you have quotes):\n" + "    --query bot/'bot1'\n\n" +
+            "  List tests running for 'bot1' (make sure you have quotes):\n" +
+            "    --query bot/'bot1'/tests\n\n" + "  List all tests:\n" +
+            "    --query tests\n\n" +
+            "  List all tests and the bots running them:\n" +
+            "    --query tests/bots\n\n" +
+            "  List all tests that satisfy multiple parameters\n" +
+            "  (separation of parameters by '&' symbol):\n" +
+            "    --query tests/'device_os:Android&device_type:hammerhead'\n\n" +
+            "  List all tests that run with a specific flag:\n" +
+            "    --query bots/'--test-launcher-print-test-studio=always'\n\n" +
+            "  List specific test (make sure you have quotes):\n"
+            "    --query test/'test1'\n\n"
+            "  List all bots running 'test1' " +
+            "(make sure you have quotes):\n" + "    --query test/'test1'/bots"))
+    parser.add_argument(
+        '-n',
+        '--new-files',
+        action='store_true',
+        help=
+        'Write output files as .new.json. Useful during development so old and '
+        'new files can be looked at side-by-side.')
+    parser.add_argument('-v',
+                        '--verbose',
+                        action='store_true',
+                        help='Increases verbosity. Affects consistency checks.')
+    parser.add_argument('waterfall_filters',
+                        metavar='waterfalls',
+                        type=str,
+                        nargs='*',
+                        help='Optional list of waterfalls to generate.')
+    parser.add_argument(
+        '--pyl-files-dir',
+        type=os.path.realpath,
+        help='Path to the directory containing the input .pyl files.')
+    parser.add_argument(
+        '--json',
+        metavar='JSON_FILE_PATH',
+        help='Outputs results into a json file. Only works with query function.'
+    )
+    parser.add_argument(
+        '--infra-config-dir',
+        help='Path to the LUCI services configuration directory',
+        default=os.path.abspath(
+            os.path.join(os.path.dirname(__file__), '..', '..', 'infra',
+                         'config')))
+    args = parser.parse_args(argv)
+    if args.json and not args.query:
+      parser.error(
+          "The --json flag can only be used with --query.")  # pragma: no cover
+    args.infra_config_dir = os.path.abspath(args.infra_config_dir)
+    return args
+
   def generate_abs_file_path(self, relative_path):
-    return os.path.join(self.this_dir, relative_path) # pragma: no cover
+    return os.path.join(self.this_dir, relative_path)
 
   def print_line(self, line):
     # Exists so that tests can mock
     print line # pragma: no cover
 
   def read_file(self, relative_path):
-    with open(self.generate_abs_file_path(
-        relative_path)) as fp: # pragma: no cover
-      return fp.read() # pragma: no cover
+    with open(self.generate_abs_file_path(relative_path)) as fp:
+      return fp.read()
 
   def write_file(self, relative_path, contents):
-    with open(self.generate_abs_file_path(
-        relative_path), 'wb') as fp: # pragma: no cover
-      fp.write(contents) # pragma: no cover
+    with open(self.generate_abs_file_path(relative_path), 'wb') as fp:
+      fp.write(contents)
 
   def pyl_file_path(self, filename):
     if self.args and self.args.pyl_files_dir:
@@ -1801,74 +1880,6 @@
     self.check_input_file_consistency(verbose) # pragma: no cover
     self.check_output_file_consistency(verbose) # pragma: no cover
 
-  def parse_args(self, argv): # pragma: no cover
-
-    # RawTextHelpFormatter allows for styling of help statement
-    parser = argparse.ArgumentParser(formatter_class=
-                                     argparse.RawTextHelpFormatter)
-
-    group = parser.add_mutually_exclusive_group()
-    group.add_argument(
-      '-c', '--check', action='store_true', help=
-      'Do consistency checks of configuration and generated files and then '
-      'exit. Used during presubmit. Causes the tool to not generate any files.')
-    group.add_argument(
-      '--query', type=str, help=
-        ("Returns raw JSON information of buildbots and tests.\n" +
-        "Examples:\n" +
-          "  List all bots (all info):\n" +
-          "    --query bots\n\n" +
-          "  List all bots and only their associated tests:\n" +
-          "    --query bots/tests\n\n" +
-          "  List all information about 'bot1' " +
-               "(make sure you have quotes):\n" +
-          "    --query bot/'bot1'\n\n" +
-          "  List tests running for 'bot1' (make sure you have quotes):\n" +
-          "    --query bot/'bot1'/tests\n\n" +
-          "  List all tests:\n" +
-          "    --query tests\n\n" +
-          "  List all tests and the bots running them:\n" +
-          "    --query tests/bots\n\n"+
-          "  List all tests that satisfy multiple parameters\n" +
-          "  (separation of parameters by '&' symbol):\n" +
-          "    --query tests/'device_os:Android&device_type:hammerhead'\n\n" +
-          "  List all tests that run with a specific flag:\n" +
-          "    --query bots/'--test-launcher-print-test-studio=always'\n\n" +
-          "  List specific test (make sure you have quotes):\n"
-          "    --query test/'test1'\n\n"
-          "  List all bots running 'test1' " +
-               "(make sure you have quotes):\n" +
-          "    --query test/'test1'/bots" ))
-    parser.add_argument(
-      '-n', '--new-files', action='store_true', help=
-      'Write output files as .new.json. Useful during development so old and '
-      'new files can be looked at side-by-side.')
-    parser.add_argument(
-      '-v', '--verbose', action='store_true', help=
-      'Increases verbosity. Affects consistency checks.')
-    parser.add_argument(
-      'waterfall_filters', metavar='waterfalls', type=str, nargs='*',
-      help='Optional list of waterfalls to generate.')
-    parser.add_argument(
-      '--pyl-files-dir', type=os.path.realpath,
-      help='Path to the directory containing the input .pyl files.')
-    parser.add_argument(
-      '--json', help=
-      ("Outputs results into a json file. Only works with query function.\n" +
-      "Examples:\n" +
-      "  Outputs file into specified json file: \n" +
-      "    --json <file-name-here.json>"))
-    parser.add_argument(
-      '--infra-config-dir',
-      help='Path to the LUCI services configuration directory',
-      default=os.path.abspath(
-          os.path.join(os.path.dirname(__file__),
-                       '..', '..', 'infra', 'config')))
-    self.args = parser.parse_args(argv)
-    if self.args.json and not self.args.query:
-      parser.error("The --json flag can only be used with --query.")
-    self.args.infra_config_dir = os.path.abspath(self.args.infra_config_dir)
-
   def does_test_match(self, test_info, params_dict):
     """Checks to see if the test matches the parameters given.
 
@@ -2142,8 +2153,7 @@
       self.error_msg("Your command did not match any valid commands." +
                      "Try starting with 'bots', 'bot', 'tests', or 'test'.")
 
-  def main(self, argv): # pragma: no cover
-    self.parse_args(argv)
+  def main(self):  # pragma: no cover
     if self.args.check:
       self.check_consistency(verbose=self.args.verbose)
     elif self.args.query:
@@ -2153,5 +2163,5 @@
     return 0
 
 if __name__ == "__main__": # pragma: no cover
-  generator = BBJSONGenerator()
-  sys.exit(generator.main(sys.argv[1:]))
+  generator = BBJSONGenerator(BBJSONGenerator.parse_args(sys.argv[1:]))
+  sys.exit(generator.main())