Merge tools/gyp from https://chromium.googlesource.com/external/gyp.git at b38e5f2f93fa5b31a6001bd21b9d8dcbf73e44e7

This commit was generated by merge_from_chromium.py.

Change-Id: Ide8ea95cde4d50b827c99556bef533b85232e6b9
diff --git a/pylib/gyp/generator/analyzer.py b/pylib/gyp/generator/analyzer.py
index 8a8ac70..ba53e49 100644
--- a/pylib/gyp/generator/analyzer.py
+++ b/pylib/gyp/generator/analyzer.py
@@ -14,7 +14,13 @@
 warning: only supplied if there is a warning.
 targets: the set of targets passed in via targets that either directly or
   indirectly depend upon the set of paths supplied in files.
-status: indicates if any of the supplied files matched at least one target.
+build_targets: minimal set of targets that directly depend on the changed
+  files and need to be built. The expectation is this set of targets is passed
+  into a build step.
+status: outputs one of three values: none of the supplied files were found,
+  one of the include files changed so that it should be assumed everything
+  changed (in this case targets and build_targets are not output) or at
+  least one file was found.
 
 If the generator flag analyzer_output_path is specified, output is written
 there. Otherwise output is written to stdout.
@@ -31,6 +37,8 @@
 
 found_dependency_string = 'Found dependency'
 no_dependency_string = 'No dependencies'
+# Status when it should be assumed that everything has changed.
+all_changed_string = 'Found dependency (all)'
 
 # MatchStatus is used indicate if and how a target depends upon the supplied
 # sources.
@@ -143,7 +151,7 @@
   if 'sources' in target_dict:
     _AddSources(target_dict['sources'], base_path, base_path_components,
                 results)
-  # Include the inputs from any actions. Any changes to these effect the
+  # Include the inputs from any actions. Any changes to these affect the
   # resulting output.
   if 'actions' in target_dict:
     for action in target_dict['actions']:
@@ -158,41 +166,47 @@
 
 class Target(object):
   """Holds information about a particular target:
-  deps: set of the names of direct dependent targets.
-  match_staus: one of the MatchStatus values"""
-  def __init__(self):
+  deps: set of Targets this Target depends upon. This is not recursive, only the
+    direct dependent Targets.
+  match_status: one of the MatchStatus values.
+  back_deps: set of Targets that have a dependency on this Target.
+  visited: used during iteration to indicate whether we've visited this target.
+    This is used for two iterations, once in building the set of Targets and
+    again in _GetBuildTargets().
+  name: fully qualified name of the target.
+  requires_build: True if the target type is such that it needs to be built.
+    See _DoesTargetTypeRequireBuild for details.
+  added_to_compile_targets: used when determining if the target was added to the
+    set of targets that needs to be built.
+  in_roots: true if this target is a descendant of one of the root nodes."""
+  def __init__(self, name):
     self.deps = set()
     self.match_status = MATCH_STATUS_TBD
+    self.back_deps = set()
+    self.name = name
+    # TODO(sky): I don't like hanging this off Target. This state is specific
+    # to certain functions and should be isolated there.
+    self.visited = False
+    self.requires_build = False
+    self.added_to_compile_targets = False
+    self.in_roots = False
 
 
 class Config(object):
   """Details what we're looking for
-  look_for_dependency_only: if true only search for a target listing any of
-                            the files in files.
   files: set of files to search for
-  targets: see file description for details"""
+  targets: see file description for details."""
   def __init__(self):
-    self.look_for_dependency_only = True
     self.files = []
-    self.targets = []
+    self.targets = set()
 
   def Init(self, params):
-    """Initializes Config. This is a separate method as it may raise an
-    exception if there is a parse error."""
+    """Initializes Config. This is a separate method as it raises an exception
+    if there is a parse error."""
     generator_flags = params.get('generator_flags', {})
-    # TODO(sky): nuke file_path and look_for_dependency_only once migrate
-    # recipes.
-    file_path = generator_flags.get('file_path', None)
-    if file_path:
-      self._InitFromFilePath(file_path)
-      return
-
-    # If |file_path| wasn't specified then we look for config_path.
-    # TODO(sky): always look for config_path once migrated recipes.
     config_path = generator_flags.get('config_path', None)
     if not config_path:
       return
-    self.look_for_dependency_only = False
     try:
       f = open(config_path, 'r')
       config = json.load(f)
@@ -204,20 +218,7 @@
     if not isinstance(config, dict):
       raise Exception('config_path must be a JSON file containing a dictionary')
     self.files = config.get('files', [])
-    # Coalesce duplicates
-    self.targets = list(set(config.get('targets', [])))
-
-  def _InitFromFilePath(self, file_path):
-    try:
-      f = open(file_path, 'r')
-      for file_name in f:
-        if file_name.endswith('\n'):
-          file_name = file_name[0:len(file_name) - 1]
-          if len(file_name):
-            self.files.append(file_name)
-      f.close()
-    except IOError:
-      raise Exception('Unable to open file', file_path)
+    self.targets = set(config.get('targets', []))
 
 
 def _WasBuildFileModified(build_file, data, files):
@@ -244,60 +245,105 @@
   return False
 
 
-def _GenerateTargets(data, target_list, target_dicts, toplevel_dir, files):
-  """Generates a dictionary with the key the name of a target and the value a
-  Target. |toplevel_dir| is the root of the source tree. If the sources of
-  a target match that of |files|, then |target.matched| is set to True.
-  This returns a tuple of the dictionary and whether at least one target's
-  sources listed one of the paths in |files|."""
+def _GetOrCreateTargetByName(targets, target_name):
+  """Creates or returns the Target at targets[target_name]. If there is no
+  Target for |target_name| one is created. Returns a tuple of whether a new
+  Target was created and the Target."""
+  if target_name in targets:
+    return False, targets[target_name]
+  target = Target(target_name)
+  targets[target_name] = target
+  return True, target
+
+
+def _DoesTargetTypeRequireBuild(target_dict):
+  """Returns true if the target type is such that it needs to be built."""
+  # If a 'none' target has rules or actions we assume it requires a build.
+  return target_dict['type'] != 'none' or \
+      target_dict.get('actions') or target_dict.get('rules')
+
+
+def _GenerateTargets(data, target_list, target_dicts, toplevel_dir, files,
+                     build_files):
+  """Returns a tuple of the following:
+  . A dictionary mapping from fully qualified name to Target.
+  . A list of the targets that have a source file in |files|.
+  . Set of root Targets reachable from the the files |build_files|.
+  This sets the |match_status| of the targets that contain any of the source
+  files in |files| to MATCH_STATUS_MATCHES.
+  |toplevel_dir| is the root of the source tree."""
+  # Maps from target name to Target.
   targets = {}
 
+  # Targets that matched.
+  matching_targets = []
+
   # Queue of targets to visit.
   targets_to_visit = target_list[:]
 
-  matched = False
-
   # Maps from build file to a boolean indicating whether the build file is in
   # |files|.
   build_file_in_files = {}
 
+  # Root targets across all files.
+  roots = set()
+
+  # Set of Targets in |build_files|.
+  build_file_targets = set()
+
   while len(targets_to_visit) > 0:
     target_name = targets_to_visit.pop()
-    if target_name in targets:
+    created_target, target = _GetOrCreateTargetByName(targets, target_name)
+    if created_target:
+      roots.add(target)
+    elif target.visited:
       continue
 
-    target = Target()
-    targets[target_name] = target
+    target.visited = True
+    target.requires_build = _DoesTargetTypeRequireBuild(
+        target_dicts[target_name])
 
     build_file = gyp.common.ParseQualifiedTarget(target_name)[0]
     if not build_file in build_file_in_files:
       build_file_in_files[build_file] = \
           _WasBuildFileModified(build_file, data, files)
 
+    if build_file in build_files:
+      build_file_targets.add(target)
+
     # If a build file (or any of its included files) is modified we assume all
     # targets in the file are modified.
     if build_file_in_files[build_file]:
+      print 'matching target from modified build file', target_name
       target.match_status = MATCH_STATUS_MATCHES
-      matched = True
+      matching_targets.append(target)
     else:
       sources = _ExtractSources(target_name, target_dicts[target_name],
                                 toplevel_dir)
       for source in sources:
         if source in files:
+          print 'target', target_name, 'matches', source
           target.match_status = MATCH_STATUS_MATCHES
-          matched = True
+          matching_targets.append(target)
           break
 
+    # Add dependencies to visit as well as updating back pointers for deps.
     for dep in target_dicts[target_name].get('dependencies', []):
-      targets[target_name].deps.add(dep)
       targets_to_visit.append(dep)
 
-  return targets, matched
+      created_dep_target, dep_target = _GetOrCreateTargetByName(targets, dep)
+      if not created_dep_target:
+        roots.discard(dep_target)
+
+      target.deps.add(dep_target)
+      dep_target.back_deps.add(target)
+
+  return targets, matching_targets, roots & build_file_targets
 
 
-def _GetUnqualifiedToQualifiedMapping(all_targets, to_find):
-  """Returns a mapping (dictionary) from unqualified name to qualified name for
-  all the targets in |to_find|."""
+def _GetUnqualifiedToTargetMapping(all_targets, to_find):
+  """Returns a mapping (dictionary) from unqualified name to Target for all the
+  Targets in |to_find|."""
   result = {}
   if not to_find:
     return result
@@ -306,47 +352,91 @@
     extracted = gyp.common.ParseQualifiedTarget(target_name)
     if len(extracted) > 1 and extracted[1] in to_find:
       to_find.remove(extracted[1])
-      result[extracted[1]] = target_name
+      result[extracted[1]] = all_targets[target_name]
       if not to_find:
         return result
   return result
 
 
-def _DoesTargetDependOn(target, all_targets):
+def _DoesTargetDependOn(target):
   """Returns true if |target| or any of its dependencies matches the supplied
   set of paths. This updates |matches| of the Targets as it recurses.
-  target: the Target to look for.
-  all_targets: mapping from target name to Target.
-  matching_targets: set of targets looking for."""
+  target: the Target to look for."""
   if target.match_status == MATCH_STATUS_DOESNT_MATCH:
     return False
   if target.match_status == MATCH_STATUS_MATCHES or \
       target.match_status == MATCH_STATUS_MATCHES_BY_DEPENDENCY:
     return True
-  for dep_name in target.deps:
-    dep_target = all_targets[dep_name]
-    if _DoesTargetDependOn(dep_target, all_targets):
-      dep_target.match_status = MATCH_STATUS_MATCHES_BY_DEPENDENCY
+  for dep in target.deps:
+    if _DoesTargetDependOn(dep):
+      target.match_status = MATCH_STATUS_MATCHES_BY_DEPENDENCY
       return True
-    dep_target.match_status = MATCH_STATUS_DOESNT_MATCH
+  target.match_status = MATCH_STATUS_DOESNT_MATCH
   return False
 
 
-def _GetTargetsDependingOn(all_targets, possible_targets):
-  """Returns the list of targets in |possible_targets| that depend (either
-  directly on indirectly) on the matched files.
-  all_targets: mapping from target name to Target.
+def _GetTargetsDependingOn(possible_targets):
+  """Returns the list of Targets in |possible_targets| that depend (either
+  directly on indirectly) on the matched targets.
   possible_targets: targets to search from."""
   found = []
   for target in possible_targets:
-    if _DoesTargetDependOn(all_targets[target], all_targets):
-      # possible_targets was initially unqualified, keep it unqualified.
-      found.append(gyp.common.ParseQualifiedTarget(target)[1])
+    if _DoesTargetDependOn(target):
+      found.append(target)
   return found
 
 
+def _AddBuildTargets(target, roots, add_if_no_ancestor, result):
+  """Recurses through all targets that depend on |target|, adding all targets
+  that need to be built (and are in |roots|) to |result|.
+  roots: set of root targets.
+  add_if_no_ancestor: If true and there are no ancestors of |target| then add
+  |target| to |result|. |target| must still be in |roots|.
+  result: targets that need to be built are added here."""
+  if target.visited:
+    return
+
+  target.visited = True
+  target.in_roots = not target.back_deps and target in roots
+
+  for back_dep_target in target.back_deps:
+    _AddBuildTargets(back_dep_target, roots, False, result)
+    target.added_to_compile_targets |= back_dep_target.added_to_compile_targets
+    target.in_roots |= back_dep_target.in_roots
+
+  if not target.added_to_compile_targets and target.in_roots and \
+        (add_if_no_ancestor or target.requires_build):
+    result.add(target)
+    target.added_to_compile_targets = True
+
+
+def _GetBuildTargets(matching_targets, roots):
+  """Returns the set of Targets that require a build.
+  matching_targets: targets that changed and need to be built.
+  roots: set of root targets in the build files to search from."""
+  result = set()
+  for target in matching_targets:
+    _AddBuildTargets(target, roots, True, result)
+  return result
+
+
 def _WriteOutput(params, **values):
   """Writes the output, either to stdout or a file is specified."""
+  if 'error' in values:
+    print 'Error:', values['error']
+  if 'status' in values:
+    print values['status']
+  if 'targets' in values:
+    values['targets'].sort()
+    print 'Supplied targets that depend on changed files:'
+    for target in values['targets']:
+      print '\t', target
+  if 'build_targets' in values:
+    values['build_targets'].sort()
+    print 'Targets that require a build:'
+    for target in values['build_targets']:
+      print '\t', target
+
   output_path = params.get('generator_flags', {}).get(
       'analyzer_output_path', None)
   if not output_path:
@@ -360,6 +450,28 @@
     print 'Error writing to output file', output_path, str(e)
 
 
+def _WasGypIncludeFileModified(params, files):
+  """Returns true if one of the files in |files| is in the set of included
+  files."""
+  if params['options'].includes:
+    for include in params['options'].includes:
+      if _ToGypPath(include) in files:
+        print 'Include file modified, assuming all changed', include
+        return True
+  return False
+
+
+def _NamesNotIn(names, mapping):
+  """Returns a list of the values in |names| that are not in |mapping|."""
+  return [name for name in names if name not in mapping]
+
+
+def _LookupTargets(names, mapping):
+  """Returns a list of the mapping[name] for each value in |names| that is in
+  |mapping|."""
+  return [mapping[name] for name in names if name in mapping]
+
+
 def CalculateVariables(default_variables, params):
   """Calculate additional variables for use in the build (called by gyp)."""
   flavor = gyp.common.GetFlavor(params)
@@ -389,9 +501,6 @@
   try:
     config.Init(params)
     if not config.files:
-      if config.look_for_dependency_only:
-        print 'Must specify files to analyze via file_path generator flag'
-        return
       raise Exception('Must specify files to analyze via config_path generator '
                       'flag')
 
@@ -399,53 +508,42 @@
     if debug:
       print 'toplevel_dir', toplevel_dir
 
-    matched = False
-    matched_include = False
-
-    # If one of the modified files is an include file then everything is
-    # affected.
-    if params['options'].includes:
-      for include in params['options'].includes:
-        if _ToGypPath(include) in config.files:
-          if debug:
-            print 'include path modified', include
-          matched_include = True
-          matched = True
-          break
-
-    if not matched:
-      all_targets, matched = _GenerateTargets(data, target_list, target_dicts,
-                                              toplevel_dir,
-                                              frozenset(config.files))
-
-    # Set of targets that refer to one of the files.
-    if config.look_for_dependency_only:
-      print found_dependency_string if matched else no_dependency_string
+    if _WasGypIncludeFileModified(params, config.files):
+      result_dict = { 'status': all_changed_string,
+                      'targets': list(config.targets) }
+      _WriteOutput(params, **result_dict)
       return
 
-    warning = None
-    if matched_include:
-      output_targets = config.targets
-    elif matched:
-      unqualified_mapping = _GetUnqualifiedToQualifiedMapping(
-          all_targets, config.targets)
-      if len(unqualified_mapping) != len(config.targets):
-        not_found = []
-        for target in config.targets:
-          if not target in unqualified_mapping:
-            not_found.append(target)
-        warning = 'Unable to find all targets: ' + str(not_found)
-      qualified_targets = []
-      for target in config.targets:
-        if target in unqualified_mapping:
-          qualified_targets.append(unqualified_mapping[target])
-      output_targets = _GetTargetsDependingOn(all_targets, qualified_targets)
-    else:
-      output_targets = []
+    all_targets, matching_targets, roots = _GenerateTargets(
+      data, target_list, target_dicts, toplevel_dir, frozenset(config.files),
+      params['build_files'])
 
-    result_dict = { 'targets': output_targets,
-                    'status': found_dependency_string if matched else
-                              no_dependency_string }
+    warning = None
+    unqualified_mapping = _GetUnqualifiedToTargetMapping(all_targets,
+                                                         config.targets)
+    if len(unqualified_mapping) != len(config.targets):
+      not_found = _NamesNotIn(config.targets, unqualified_mapping)
+      warning = 'Unable to find all targets: ' + str(not_found)
+
+    if matching_targets:
+      search_targets = _LookupTargets(config.targets, unqualified_mapping)
+      matched_search_targets = _GetTargetsDependingOn(search_targets)
+      # Reset the visited status for _GetBuildTargets.
+      for target in all_targets.itervalues():
+        target.visited = False
+      build_targets = _GetBuildTargets(matching_targets, roots)
+      matched_search_targets = [gyp.common.ParseQualifiedTarget(target.name)[1]
+                                for target in matched_search_targets]
+      build_targets = [gyp.common.ParseQualifiedTarget(target.name)[1]
+                       for target in build_targets]
+    else:
+      matched_search_targets = []
+      build_targets = []
+
+    result_dict = { 'targets': matched_search_targets,
+                    'status': found_dependency_string if matching_targets else
+                              no_dependency_string,
+                    'build_targets': build_targets}
     if warning:
       result_dict['warning'] = warning
     _WriteOutput(params, **result_dict)
diff --git a/pylib/gyp/msvs_emulation.py b/pylib/gyp/msvs_emulation.py
index 5f71e9e..5384df1 100644
--- a/pylib/gyp/msvs_emulation.py
+++ b/pylib/gyp/msvs_emulation.py
@@ -370,7 +370,7 @@
     output_file = self._Setting(('VCLinkerTool', 'ProgramDatabaseFile'), config)
     generate_debug_info = self._Setting(
         ('VCLinkerTool', 'GenerateDebugInformation'), config)
-    if generate_debug_info:
+    if generate_debug_info == 'true':
       if output_file:
         return expand_special(self.ConvertVSMacros(output_file, config=config))
       else:
diff --git a/test/analyzer/gyptest-analyzer.new.py b/test/analyzer/gyptest-analyzer.new.py
deleted file mode 100644
index b736867..0000000
--- a/test/analyzer/gyptest-analyzer.new.py
+++ /dev/null
@@ -1,229 +0,0 @@
-#!/usr/bin/env python
-# Copyright (c) 2014 Google Inc. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-"""Tests for analyzer
-"""
-
-import json
-import TestGyp
-
-# TODO(sky): when done migrating recipes rename to gyptest-analyzer and nuke
-# existing gyptest-analyzer.
-
-found = 'Found dependency'
-not_found = 'No dependencies'
-
-def _CreateTestFile(files, targets):
-  f = open('test_file', 'w')
-  to_write = {'files': files, 'targets': targets }
-  json.dump(to_write, f)
-  f.close()
-
-def _CreateBogusTestFile():
-  f = open('test_file','w')
-  f.write('bogus')
-  f.close()
-
-def _ReadOutputFileContents():
-  f = open('analyzer_output', 'r')
-  result = json.load(f)
-  f.close()
-  return result
-
-# NOTE: this would be clearer if it subclassed TestGypCustom, but that trips
-# over a bug in pylint (E1002).
-test = TestGyp.TestGypCustom(format='analyzer')
-
-def run_analyzer(*args, **kw):
-  """Runs the test specifying a particular config and output path."""
-  args += ('-Gconfig_path=test_file',
-           '-Ganalyzer_output_path=analyzer_output')
-  test.run_gyp('test.gyp', *args, **kw)
-
-def run_analyzer2(*args, **kw):
-  """Runs the test specifying a particular config and output path."""
-  args += ('-Gconfig_path=test_file',
-           '-Ganalyzer_output_path=analyzer_output')
-  test.run_gyp('test2.gyp', *args, **kw)
-
-def EnsureContains(targets=set(), matched=False):
-  """Verifies output contains |targets|."""
-  result = _ReadOutputFileContents()
-  if result.get('error', None):
-    print 'unexpected error', result.get('error')
-    test.fail_test()
-
-  if result.get('warning', None):
-    print 'unexpected warning', result.get('warning')
-    test.fail_test()
-
-  actual_targets = set(result['targets'])
-  if actual_targets != targets:
-    print 'actual targets:', actual_targets, '\nexpected targets:', targets
-    test.fail_test()
-
-  if matched and result['status'] != found:
-    print 'expected', found, 'got', result['status']
-    test.fail_test()
-  elif not matched and result['status'] != not_found:
-    print 'expected', not_found, 'got', result['status']
-    test.fail_test()
-
-def EnsureError(expected_error_string):
-  """Verifies output contains the error string."""
-  result = _ReadOutputFileContents()
-  if result.get('error', '').find(expected_error_string) == -1:
-    print 'actual error:', result.get('error', ''), '\nexpected error:', \
-        expected_error_string
-    test.fail_test()
-
-def EnsureWarning(expected_warning_string):
-  """Verifies output contains the warning string."""
-  result = _ReadOutputFileContents()
-  if result.get('warning', '').find(expected_warning_string) == -1:
-    print 'actual warning:', result.get('warning', ''), \
-        '\nexpected warning:', expected_warning_string
-    test.fail_test()
-
-# Verifies file_path must be specified.
-test.run_gyp('test.gyp',
-             stdout='Must specify files to analyze via file_path generator '
-             'flag\n')
-
-# Verifies config_path must point to a valid file.
-test.run_gyp('test.gyp', '-Gconfig_path=bogus_file',
-             '-Ganalyzer_output_path=analyzer_output')
-EnsureError('Unable to open file bogus_file')
-
-# Verify get error when bad target is specified.
-_CreateTestFile(['exe2.c'], ['bad_target'])
-run_analyzer()
-EnsureWarning('Unable to find all targets')
-
-# Verifies config_path must point to a valid json file.
-_CreateBogusTestFile()
-run_analyzer()
-EnsureError('Unable to parse config file test_file')
-
-# Trivial test of a source.
-_CreateTestFile(['foo.c'], [])
-run_analyzer()
-EnsureContains(matched=True)
-
-# Conditional source that is excluded.
-_CreateTestFile(['conditional_source.c'], [])
-run_analyzer()
-EnsureContains(matched=False)
-
-# Conditional source that is included by way of argument.
-_CreateTestFile(['conditional_source.c'], [])
-run_analyzer('-Dtest_variable=1')
-EnsureContains(matched=True)
-
-# Two unknown files.
-_CreateTestFile(['unknown1.c', 'unoknow2.cc'], [])
-run_analyzer()
-EnsureContains()
-
-# Two unknown files.
-_CreateTestFile(['unknown1.c', 'subdir/subdir_sourcex.c'], [])
-run_analyzer()
-EnsureContains()
-
-# Included dependency
-_CreateTestFile(['unknown1.c', 'subdir/subdir_source.c'], [])
-run_analyzer()
-EnsureContains(matched=True)
-
-# Included inputs to actions.
-_CreateTestFile(['action_input.c'], [])
-run_analyzer()
-EnsureContains(matched=True)
-
-# Don't consider outputs.
-_CreateTestFile(['action_output.c'], [])
-run_analyzer()
-EnsureContains(matched=False)
-
-# Rule inputs.
-_CreateTestFile(['rule_input.c'], [])
-run_analyzer()
-EnsureContains(matched=True)
-
-# Ignore path specified with PRODUCT_DIR.
-_CreateTestFile(['product_dir_input.c'], [])
-run_analyzer()
-EnsureContains(matched=False)
-
-# Path specified via a variable.
-_CreateTestFile(['subdir/subdir_source2.c'], [])
-run_analyzer()
-EnsureContains(matched=True)
-
-# Verifies paths with // are fixed up correctly.
-_CreateTestFile(['parent_source.c'], [])
-run_analyzer()
-EnsureContains(matched=True)
-
-# Verifies relative paths are resolved correctly.
-_CreateTestFile(['subdir/subdir_source.h'], [])
-run_analyzer()
-EnsureContains(matched=True)
-
-# Various permutations when passing in targets.
-_CreateTestFile(['exe2.c', 'subdir/subdir2b_source.c'], ['exe', 'exe3'])
-run_analyzer()
-EnsureContains(matched=True, targets={'exe3'})
-
-_CreateTestFile(['exe2.c', 'subdir/subdir2b_source.c'], ['exe'])
-run_analyzer()
-EnsureContains(matched=True)
-
-# Verifies duplicates are ignored.
-_CreateTestFile(['exe2.c', 'subdir/subdir2b_source.c'], ['exe', 'exe'])
-run_analyzer()
-EnsureContains(matched=True)
-
-_CreateTestFile(['exe2.c'], ['exe'])
-run_analyzer()
-EnsureContains(matched=True)
-
-_CreateTestFile(['exe2.c'], [])
-run_analyzer()
-EnsureContains(matched=True)
-
-_CreateTestFile(['subdir/subdir2b_source.c', 'exe2.c'], [])
-run_analyzer()
-EnsureContains(matched=True)
-
-_CreateTestFile(['exe2.c'], [])
-run_analyzer()
-EnsureContains(matched=True)
-
-# Assertions when modifying build (gyp/gypi) files, especially when said files
-# are included.
-_CreateTestFile(['subdir2/d.cc'], ['exe', 'exe2', 'foo', 'exe3'])
-run_analyzer2()
-EnsureContains(matched=True, targets={'exe', 'foo'})
-
-_CreateTestFile(['subdir2/subdir.includes.gypi'],
-                ['exe', 'exe2', 'foo', 'exe3'])
-run_analyzer2()
-EnsureContains(matched=True, targets={'exe', 'foo'})
-
-_CreateTestFile(['subdir2/subdir.gyp'], ['exe', 'exe2', 'foo', 'exe3'])
-run_analyzer2()
-EnsureContains(matched=True, targets={'exe', 'foo'})
-
-_CreateTestFile(['test2.includes.gypi'], ['exe', 'exe2', 'foo', 'exe3'])
-run_analyzer2()
-EnsureContains(matched=True, targets={'exe', 'exe2', 'exe3'})
-
-# Verify modifying a file included makes all targets dirty.
-_CreateTestFile(['common.gypi'], ['exe', 'exe2', 'foo', 'exe3'])
-run_analyzer2('-Icommon.gypi')
-EnsureContains(matched=True, targets={'exe', 'foo', 'exe2', 'exe3'})
-
-test.pass_test()
diff --git a/test/analyzer/gyptest-analyzer.py b/test/analyzer/gyptest-analyzer.py
index e374627..89d6252 100644
--- a/test/analyzer/gyptest-analyzer.py
+++ b/test/analyzer/gyptest-analyzer.py
@@ -6,75 +6,350 @@
 """Tests for analyzer
 """
 
+import json
 import TestGyp
 
-found = 'Found dependency\n'
-not_found = 'No dependencies\n'
+found = 'Found dependency'
+found_all = 'Found dependency (all)'
+not_found = 'No dependencies'
 
-def __CreateTestFile(files):
+
+def _CreateConfigFile(files, targets):
+  """Creates the analyzer conflig file, which is used as the input to analyzer.
+  See description of analyzer.py for description of the arguments."""
   f = open('test_file', 'w')
-  for file in files:
-    f.write(file + '\n')
+  to_write = {'files': files, 'targets': targets }
+  json.dump(to_write, f)
   f.close()
 
+
+def _CreateBogusConfigFile():
+  f = open('test_file','w')
+  f.write('bogus')
+  f.close()
+
+
+def _ReadOutputFileContents():
+  f = open('analyzer_output', 'r')
+  result = json.load(f)
+  f.close()
+  return result
+
+
+# NOTE: this would be clearer if it subclassed TestGypCustom, but that trips
+# over a bug in pylint (E1002).
 test = TestGyp.TestGypCustom(format='analyzer')
 
-# Verifies file_path must be specified.
-test.run_gyp('test.gyp',
-             stdout='Must specify files to analyze via file_path generator '
-             'flag\n')
+def CommonArgs():
+  return ('-Gconfig_path=test_file',
+           '-Ganalyzer_output_path=analyzer_output')
+
+
+def run_analyzer(*args, **kw):
+  """Runs the test specifying a particular config and output path."""
+  args += CommonArgs()
+  test.run_gyp('test.gyp', *args, **kw)
+
+
+def run_analyzer2(*args, **kw):
+  """Same as run_analyzer(), but passes in test2.gyp instead of test.gyp."""
+  args += CommonArgs()
+  test.run_gyp('test2.gyp', *args, **kw)
+
+
+def run_analyzer3(*args, **kw):
+  """Same as run_analyzer(), but passes in test3.gyp instead of test.gyp."""
+  args += CommonArgs()
+  test.run_gyp('test3.gyp', *args, **kw)
+
+
+def run_analyzer4(*args, **kw):
+  """Same as run_analyzer(), but passes in test3.gyp instead of test.gyp."""
+  args += CommonArgs()
+  test.run_gyp('test4.gyp', *args, **kw)
+
+
+def EnsureContains(targets=set(), matched=False, build_targets=set()):
+  """Verifies output contains |targets|."""
+  result = _ReadOutputFileContents()
+  if result.get('error', None):
+    print 'unexpected error', result.get('error')
+    test.fail_test()
+
+  if result.get('warning', None):
+    print 'unexpected warning', result.get('warning')
+    test.fail_test()
+
+  actual_targets = set(result['targets'])
+  if actual_targets != targets:
+    print 'actual targets:', actual_targets, '\nexpected targets:', targets
+    test.fail_test()
+
+  actual_build_targets = set(result['build_targets'])
+  if actual_build_targets != build_targets:
+    print 'actual build_targets:', actual_build_targets, \
+           '\nexpected build_targets:', build_targets
+    test.fail_test()
+
+  if matched and result['status'] != found:
+    print 'expected', found, 'got', result['status']
+    test.fail_test()
+  elif not matched and result['status'] != not_found:
+    print 'expected', not_found, 'got', result['status']
+    test.fail_test()
+
+
+def EnsureMatchedAll(targets):
+  result = _ReadOutputFileContents()
+  if result.get('error', None):
+    print 'unexpected error', result.get('error')
+    test.fail_test()
+
+  if result.get('warning', None):
+    print 'unexpected warning', result.get('warning')
+    test.fail_test()
+
+  if result['status'] != found_all:
+    print 'expected', found_all, 'got', result['status']
+    test.fail_test()
+
+  actual_targets = set(result['targets'])
+  if actual_targets != targets:
+    print 'actual targets:', actual_targets, '\nexpected targets:', targets
+    test.fail_test()
+
+
+def EnsureError(expected_error_string):
+  """Verifies output contains the error string."""
+  result = _ReadOutputFileContents()
+  if result.get('error', '').find(expected_error_string) == -1:
+    print 'actual error:', result.get('error', ''), '\nexpected error:', \
+        expected_error_string
+    test.fail_test()
+
+
+def EnsureStdoutContains(expected_error_string):
+  if test.stdout().find(expected_error_string) == -1:
+    print 'actual stdout:', test.stdout(), '\nexpected stdout:', \
+        expected_error_string
+    test.fail_test()
+
+
+def EnsureWarning(expected_warning_string):
+  """Verifies output contains the warning string."""
+  result = _ReadOutputFileContents()
+  if result.get('warning', '').find(expected_warning_string) == -1:
+    print 'actual warning:', result.get('warning', ''), \
+        '\nexpected warning:', expected_warning_string
+    test.fail_test()
+
+# Verifies config_path must be specified.
+test.run_gyp('test.gyp')
+EnsureStdoutContains('Must specify files to analyze via config_path')
+
+# Verifies config_path must point to a valid file.
+test.run_gyp('test.gyp', '-Gconfig_path=bogus_file',
+             '-Ganalyzer_output_path=analyzer_output')
+EnsureError('Unable to open file bogus_file')
+
+# Verify get warning when bad target is specified.
+_CreateConfigFile(['exe2.c'], ['bad_target'])
+run_analyzer()
+EnsureWarning('Unable to find all targets')
+
+# Verifies config_path must point to a valid json file.
+_CreateBogusConfigFile()
+run_analyzer()
+EnsureError('Unable to parse config file test_file')
 
 # Trivial test of a source.
-__CreateTestFile(['foo.c'])
-test.run_gyp('test.gyp', '-Gfile_path=test_file', stdout=found)
+_CreateConfigFile(['foo.c'], [])
+run_analyzer()
+EnsureContains(matched=True, build_targets={'exe'})
 
 # Conditional source that is excluded.
-__CreateTestFile(['conditional_source.c'])
-test.run_gyp('test.gyp', '-Gfile_path=test_file', stdout=not_found)
+_CreateConfigFile(['conditional_source.c'], [])
+run_analyzer()
+EnsureContains(matched=False)
 
 # Conditional source that is included by way of argument.
-__CreateTestFile(['conditional_source.c'])
-test.run_gyp('test.gyp', '-Gfile_path=test_file', '-Dtest_variable=1',
-             stdout=found)
+_CreateConfigFile(['conditional_source.c'], [])
+run_analyzer('-Dtest_variable=1')
+EnsureContains(matched=True, build_targets={'exe'})
 
 # Two unknown files.
-__CreateTestFile(['unknown1.c', 'unoknow2.cc'])
-test.run_gyp('test.gyp', '-Gfile_path=test_file', stdout=not_found)
+_CreateConfigFile(['unknown1.c', 'unoknow2.cc'], [])
+run_analyzer()
+EnsureContains()
 
 # Two unknown files.
-__CreateTestFile(['unknown1.c', 'subdir/subdir_sourcex.c'])
-test.run_gyp('test.gyp', '-Gfile_path=test_file', stdout=not_found)
+_CreateConfigFile(['unknown1.c', 'subdir/subdir_sourcex.c'], [])
+run_analyzer()
+EnsureContains()
 
 # Included dependency
-__CreateTestFile(['unknown1.c', 'subdir/subdir_source.c'])
-test.run_gyp('test.gyp', '-Gfile_path=test_file', stdout=found)
+_CreateConfigFile(['unknown1.c', 'subdir/subdir_source.c'], [])
+run_analyzer()
+EnsureContains(matched=True, build_targets={'exe', 'exe3'})
 
 # Included inputs to actions.
-__CreateTestFile(['action_input.c'])
-test.run_gyp('test.gyp', '-Gfile_path=test_file', stdout=found)
+_CreateConfigFile(['action_input.c'], [])
+run_analyzer()
+EnsureContains(matched=True, build_targets={'exe'})
 
 # Don't consider outputs.
-__CreateTestFile(['action_output.c'])
-test.run_gyp('test.gyp', '-Gfile_path=test_file', stdout=not_found)
+_CreateConfigFile(['action_output.c'], [])
+run_analyzer()
+EnsureContains(matched=False)
 
 # Rule inputs.
-__CreateTestFile(['rule_input.c'])
-test.run_gyp('test.gyp', '-Gfile_path=test_file', stdout=found)
+_CreateConfigFile(['rule_input.c'], [])
+run_analyzer()
+EnsureContains(matched=True, build_targets={'exe'})
 
-# Ignore patch specified with PRODUCT_DIR.
-__CreateTestFile(['product_dir_input.c'])
-test.run_gyp('test.gyp', '-Gfile_path=test_file', stdout=not_found)
+# Ignore path specified with PRODUCT_DIR.
+_CreateConfigFile(['product_dir_input.c'], [])
+run_analyzer()
+EnsureContains(matched=False)
 
 # Path specified via a variable.
-__CreateTestFile(['subdir/subdir_source2.c'])
-test.run_gyp('test.gyp', '-Gfile_path=test_file', stdout=found)
+_CreateConfigFile(['subdir/subdir_source2.c'], [])
+run_analyzer()
+EnsureContains(matched=True, build_targets={'exe'})
 
 # Verifies paths with // are fixed up correctly.
-__CreateTestFile(['parent_source.c'])
-test.run_gyp('test.gyp', '-Gfile_path=test_file', stdout=found)
+_CreateConfigFile(['parent_source.c'], [])
+run_analyzer()
+EnsureContains(matched=True, build_targets={'exe', 'exe3'})
 
 # Verifies relative paths are resolved correctly.
-__CreateTestFile(['subdir/subdir_source.h'])
-test.run_gyp('test.gyp', '-Gfile_path=test_file', stdout=found)
+_CreateConfigFile(['subdir/subdir_source.h'], [])
+run_analyzer()
+EnsureContains(matched=True, build_targets={'exe'})
+
+# Various permutations when passing in targets.
+_CreateConfigFile(['exe2.c', 'subdir/subdir2b_source.c'], ['exe', 'exe3'])
+run_analyzer()
+EnsureContains(matched=True, targets={'exe3'}, build_targets={'exe2', 'exe3'})
+
+_CreateConfigFile(['exe2.c', 'subdir/subdir2b_source.c'], ['exe'])
+run_analyzer()
+EnsureContains(matched=True, build_targets={'exe2', 'exe3'})
+
+# Verifies duplicates are ignored.
+_CreateConfigFile(['exe2.c', 'subdir/subdir2b_source.c'], ['exe', 'exe'])
+run_analyzer()
+EnsureContains(matched=True, build_targets={'exe2', 'exe3'})
+
+_CreateConfigFile(['exe2.c'], ['exe'])
+run_analyzer()
+EnsureContains(matched=True, build_targets={'exe2'})
+
+_CreateConfigFile(['exe2.c'], [])
+run_analyzer()
+EnsureContains(matched=True, build_targets={'exe2'})
+
+_CreateConfigFile(['subdir/subdir2b_source.c', 'exe2.c'], [])
+run_analyzer()
+EnsureContains(matched=True, build_targets={'exe2', 'exe3'})
+
+_CreateConfigFile(['subdir/subdir2b_source.c'], ['exe3'])
+run_analyzer()
+EnsureContains(matched=True, targets={'exe3'}, build_targets={'exe3'})
+
+_CreateConfigFile(['exe2.c'], [])
+run_analyzer()
+EnsureContains(matched=True, build_targets={'exe2'})
+
+_CreateConfigFile(['foo.c'], [])
+run_analyzer()
+EnsureContains(matched=True, build_targets={'exe'})
+
+# Assertions when modifying build (gyp/gypi) files, especially when said files
+# are included.
+_CreateConfigFile(['subdir2/d.cc'], ['exe', 'exe2', 'foo', 'exe3'])
+run_analyzer2()
+EnsureContains(matched=True, targets={'exe', 'foo'}, build_targets={'exe'})
+
+_CreateConfigFile(['subdir2/subdir.includes.gypi'],
+                ['exe', 'exe2', 'foo', 'exe3'])
+run_analyzer2()
+EnsureContains(matched=True, targets={'exe', 'foo'}, build_targets={'exe'})
+
+_CreateConfigFile(['subdir2/subdir.gyp'], ['exe', 'exe2', 'foo', 'exe3'])
+run_analyzer2()
+EnsureContains(matched=True, targets={'exe', 'foo'}, build_targets={'exe'})
+
+_CreateConfigFile(['test2.includes.gypi'], ['exe', 'exe2', 'foo', 'exe3'])
+run_analyzer2()
+EnsureContains(matched=True, targets={'exe', 'exe2', 'exe3'},
+               build_targets={'exe', 'exe2', 'exe3'})
+
+# Verify modifying a file included makes all targets dirty.
+_CreateConfigFile(['common.gypi'], ['exe', 'exe2', 'foo', 'exe3'])
+run_analyzer2('-Icommon.gypi')
+EnsureMatchedAll({'exe', 'exe2', 'foo', 'exe3'})
+
+# Assertions from test3.gyp.
+_CreateConfigFile(['d.c', 'f.c'], ['a'])
+run_analyzer3()
+EnsureContains(matched=True, targets={'a'}, build_targets={'a', 'b'})
+
+_CreateConfigFile(['f.c'], ['a'])
+run_analyzer3()
+EnsureContains(matched=True, targets={'a'}, build_targets={'a', 'b'})
+
+_CreateConfigFile(['f.c'], [])
+run_analyzer3()
+EnsureContains(matched=True, build_targets={'a', 'b'})
+
+_CreateConfigFile(['c.c', 'e.c'], [])
+run_analyzer3()
+EnsureContains(matched=True, build_targets={'a', 'b'})
+
+_CreateConfigFile(['d.c'], ['a'])
+run_analyzer3()
+EnsureContains(matched=True, targets={'a'}, build_targets={'a', 'b'})
+
+_CreateConfigFile(['a.c'], ['a', 'b'])
+run_analyzer3()
+EnsureContains(matched=True, targets={'a'}, build_targets={'a'})
+
+_CreateConfigFile(['a.c'], ['a', 'b'])
+run_analyzer3()
+EnsureContains(matched=True, targets={'a'}, build_targets={'a'})
+
+_CreateConfigFile(['d.c'], ['a', 'b'])
+run_analyzer3()
+EnsureContains(matched=True, targets={'a', 'b'}, build_targets={'a', 'b'})
+
+_CreateConfigFile(['f.c'], ['a'])
+run_analyzer3()
+EnsureContains(matched=True, targets={'a'}, build_targets={'a', 'b'})
+
+_CreateConfigFile(['a.c'], ['a'])
+run_analyzer3()
+EnsureContains(matched=True, targets={'a'}, build_targets={'a'})
+
+_CreateConfigFile(['a.c'], [])
+run_analyzer3()
+EnsureContains(matched=True, build_targets={'a'})
+
+_CreateConfigFile(['d.c'], [])
+run_analyzer3()
+EnsureContains(matched=True, build_targets={'a', 'b'})
+
+# Assertions around test4.gyp.
+_CreateConfigFile(['f.c'], [])
+run_analyzer4()
+EnsureContains(matched=True, build_targets={'e'})
+
+_CreateConfigFile(['d.c'], [])
+run_analyzer4()
+EnsureContains(matched=True, build_targets={'a'})
+
+_CreateConfigFile(['i.c'], [])
+run_analyzer4()
+EnsureContains(matched=True, build_targets={'h'})
 
 test.pass_test()
diff --git a/test/analyzer/test.gyp b/test/analyzer/test.gyp
index afc312b..a5201cb 100644
--- a/test/analyzer/test.gyp
+++ b/test/analyzer/test.gyp
@@ -2,6 +2,36 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
+# These gyp files create the following dependencies:
+#
+# test.gyp:
+#   #exe -> subdir/subdir.gyp#foo, subdir/subdir2/subdir2.gyp#subdir2
+#     foo.c
+#     subdir/subdir_source2.c
+#     conditional_source.c (if test_variable==1)
+#     action_input.c
+#     action_output.c
+#     rule_input.c
+#     rule_output.pdf
+#   #exe2
+#     exe2.c
+#   #exe3 -> subdir/subdir.gyp#foo, subdir/subdir.gyp#subdir2a
+#     exe3.c
+#   #all (type none) -> exe, exe3
+# 
+# subdir/subdir.gyp
+#   #foo
+#     subdir/subdir_source.c
+#     parent_source.c
+#   #subdir2a -> subdir2b
+#     subdir/subdir2_source.c
+#   #subdir2b
+#     subdir/subdir2b_source.c
+# 
+# subdir/subdir2/subdir2.gyp
+#   #subdir2
+#     subdir/subdir_source.h
+
 {
   'variables': {
     'test_variable%': 0,
@@ -73,7 +103,7 @@
     },
     {
       'target_name': 'all',
-      'type': 'executable',
+      'type': 'none',
       'dependencies': [
         'exe',
         'exe3',
diff --git a/test/analyzer/test3.gyp b/test/analyzer/test3.gyp
new file mode 100644
index 0000000..a1fdfdd
--- /dev/null
+++ b/test/analyzer/test3.gyp
@@ -0,0 +1,77 @@
+# Copyright (c) 2014 Google Inc. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+{
+  'targets': [
+    {
+      'target_name': 'all',
+      'type': 'none',
+      'dependencies': [
+        'a',
+        'b',
+      ],
+    },
+    {
+      'target_name': 'a',
+      'type': 'executable',
+      'sources': [
+        'a.c',
+      ],
+      'dependencies': [
+        'c',
+        'd',
+      ],
+    },
+    {
+      'target_name': 'b',
+      'type': 'executable',
+      'sources': [
+        'b.c',
+      ],
+      'dependencies': [
+        'd',
+        'e',
+      ],
+    },
+    {
+      'target_name': 'c',
+      'type': 'executable',
+      'sources': [
+        'c.c',
+      ],
+    },
+    {
+      'target_name': 'd',
+      'type': 'none',
+      'sources': [
+        'd.c',
+      ],
+      'dependencies': [
+        'f',
+        'g',
+      ],
+    },
+    {
+      'target_name': 'e',
+      'type': 'executable',
+      'sources': [
+        'e.c',
+      ],
+    },
+    {
+      'target_name': 'f',
+      'type': 'executable',
+      'sources': [
+        'f.c',
+      ],
+    },
+    {
+      'target_name': 'g',
+      'type': 'executable',
+      'sources': [
+        'g.c',
+      ],
+    },
+  ],
+}
diff --git a/test/analyzer/test4.gyp b/test/analyzer/test4.gyp
new file mode 100644
index 0000000..91cea56
--- /dev/null
+++ b/test/analyzer/test4.gyp
@@ -0,0 +1,80 @@
+# Copyright (c) 2014 Google Inc. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+{
+  'targets': [
+    {
+      'target_name': 'a',
+      'type': 'executable',
+      'sources': [
+        'a.c',
+      ],
+      'dependencies': [
+        'b',
+        'c',
+      ],
+    },
+    {
+      'target_name': 'b',
+      'type': 'executable',
+      'sources': [
+        'b.c',
+      ],
+      'dependencies': [
+        'd',
+      ],
+    },
+    {
+      'target_name': 'c',
+      'type': 'executable',
+      'sources': [
+        'c.c',
+      ],
+      'dependencies': [
+        'b',
+        'd',
+      ],
+    },
+    {
+      'target_name': 'd',
+      'type': 'executable',
+      'sources': [
+        'd.c',
+      ],
+    },
+    {
+      'target_name': 'e',
+      'type': 'executable',
+      'dependencies': [
+        'test5.gyp:f',
+      ],
+    },
+    {
+      'target_name': 'h',
+      'type': 'none',
+      'dependencies': [
+        'i',
+      ],
+      'rules': [
+        {
+          'rule_name': 'rule',
+          'extension': 'pdf',
+          'inputs': [
+            'rule_input.c',
+          ],
+          'outputs': [
+            'rule_output.pdf',
+          ],
+        },
+      ],
+    },
+    {
+      'target_name': 'i',
+      'type': 'static_library',
+      'sources': [
+        'i.c',
+      ],
+    },
+  ],
+}
diff --git a/test/analyzer/test5.gyp b/test/analyzer/test5.gyp
new file mode 100644
index 0000000..f3ea5b0
--- /dev/null
+++ b/test/analyzer/test5.gyp
@@ -0,0 +1,25 @@
+# Copyright (c) 2014 Google Inc. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+{
+  'targets': [
+    {
+      'target_name': 'f',
+      'type': 'executable',
+      'sources': [
+        'f.c',
+      ],
+    },
+    {
+      'target_name': 'g',
+      'type': 'executable',
+      'sources': [
+        'g.c',
+      ],
+      'dependencies': [
+        'f',
+      ],
+    },
+  ],
+}
diff --git a/test/win/gyptest-link-pdb-no-output.py b/test/win/gyptest-link-pdb-no-output.py
new file mode 100644
index 0000000..6da0aea
--- /dev/null
+++ b/test/win/gyptest-link-pdb-no-output.py
@@ -0,0 +1,25 @@
+#!/usr/bin/env python
+
+# Copyright (c) 2014 Google Inc. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""
+Ensure that when debug information is not output, a pdb is not expected.
+"""
+
+import TestGyp
+
+import os
+import sys
+
+if sys.platform == 'win32':
+  test = TestGyp.TestGyp()
+  CHDIR = 'linker-flags'
+  test.run_gyp('pdb-output.gyp', chdir=CHDIR)
+  test.build('pdb-output.gyp', 'test_pdb_output_disabled', chdir=CHDIR)
+  # Make sure that the build doesn't expect a PDB to be generated when there
+  # will be none.
+  test.up_to_date('pdb-output.gyp', 'test_pdb_output_disabled', chdir=CHDIR)
+
+  test.pass_test()
diff --git a/test/win/gyptest-link-pdb-output.py b/test/win/gyptest-link-pdb-output.py
index 8080410..27245f7 100644
--- a/test/win/gyptest-link-pdb-output.py
+++ b/test/win/gyptest-link-pdb-output.py
@@ -31,4 +31,3 @@
     test.fail_test()
 
   test.pass_test()
-
diff --git a/test/win/linker-flags/pdb-output.gyp b/test/win/linker-flags/pdb-output.gyp
index 21d3cd7..1a03c67 100644
--- a/test/win/linker-flags/pdb-output.gyp
+++ b/test/win/linker-flags/pdb-output.gyp
@@ -32,5 +32,18 @@
         },
       },
     },
+    {
+      'target_name': 'test_pdb_output_disabled',
+      'type': 'executable',
+      'sources': ['hello.cc'],
+      'msvs_settings': {
+        'VCCLCompilerTool': {
+          'DebugInformationFormat': '0'
+        },
+        'VCLinkerTool': {
+          'GenerateDebugInformation': 'false',
+        },
+      },
+    },
   ]
 }