Repository: pantsbuild/pants

Issue:
--quiet and --color should not restart daemon
those are the bootstrap options that should not affect the result.

Example, daemon restarts between
```
./pants list x
./pants list x > /dev/null
```


Discussion:
Hm... these already appear to be marked as `daemon=False` here:
https://github.com/pantsbuild/pants/blob/1148b07af07456d7e0495f4613803cb34993d689/src/python/pants/option/global_options.py#L54-L60

...so AFAIK, they should already not be causing restarts. Suspicious.

Resolving patch:
diff --git a/src/python/pants/option/options.py b/src/python/pants/option/options.py
--- a/src/python/pants/option/options.py
+++ b/src/python/pants/option/options.py
@@ -329,6 +329,7 @@ def get_fingerprintable_for_scope(self, bottom_scope, include_passthru=False,
     :API: public
     """
     fingerprint_key = fingerprint_key or 'fingerprint'
+    fingerprint_default = bool(invert)
     pairs = []
 
     if include_passthru:
@@ -346,7 +347,7 @@ def get_fingerprintable_for_scope(self, bottom_scope, include_passthru=False,
       for (_, kwargs) in sorted(parser.option_registrations_iter()):
         if kwargs.get('recursive', False) and not kwargs.get('recursive_root', False):
           continue  # We only need to fprint recursive options once.
-        if bool(invert) == bool(kwargs.get(fingerprint_key, False)):
+        if kwargs.get(fingerprint_key, fingerprint_default) is not True:
           continue
         # Note that we read the value from scope, even if the registration was on an enclosing
         # scope, to get the right value for recursive options (and because this mirrors what


Test patch:
diff --git a/tests/python/pants_test/option/test_options.py b/tests/python/pants_test/option/test_options.py
--- a/tests/python/pants_test/option/test_options.py
+++ b/tests/python/pants_test/option/test_options.py
@@ -57,7 +57,8 @@ class OptionsTest(unittest.TestCase):
                         task('simple-dashed'),
                         task('scoped.a.bit'),
                         task('scoped.and-dashed'),
-                        task('fromfile')]
+                        task('fromfile'),
+                        task('fingerprinting')]
 
   def _register(self, options):
     def register_global(*args, **kwargs):
@@ -151,6 +152,12 @@ def register_global(*args, **kwargs):
     options.register('fromfile', '--listvalue', type=list, fromfile=True)
     options.register('fromfile', '--appendvalue', type=list, member_type=int, fromfile=True)
 
+    # For fingerprint tests
+    options.register('fingerprinting', '--inverted')  # Implicitly: daemon=True
+    options.register('fingerprinting', '--definitely-not-inverted', daemon=False)
+    options.register('fingerprinting', '--fingerprinted', fingerprint=True)
+    options.register('fingerprinting', '--definitely-not-fingerprinted', fingerprint=False)
+
   def _create_config(self, config):
     with open(os.path.join(safe_mkdtemp(), 'test_config.ini'), 'w') as fp:
       for section, options in config.items():
@@ -1067,6 +1074,40 @@ def test_get_fingerprintable_for_scope_include_passthru(self):
                        (int, 77)],
                       pairs)
 
+  def test_fingerprintable(self):
+    options = self._parse(
+      './pants fingerprinting --fingerprinted=shall_be_fingerprinted '
+      ' --definitely-not-fingerprinted=shant_be_fingerprinted'
+    )
+    pairs = options.get_fingerprintable_for_scope('fingerprinting')
+    self.assertIn(
+      (str, 'shall_be_fingerprinted'),
+      pairs
+    )
+    self.assertNotIn(
+      (str, 'shant_be_fingerprinted'),
+      pairs
+    )
+
+  def test_fingerprintable_inverted(self):
+    options = self._parse(
+      './pants fingerprinting --inverted=shall_be_fingerprinted '
+      ' --definitely-not-inverted=shant_be_fingerprinted'
+    )
+    pairs = options.get_fingerprintable_for_scope(
+      'fingerprinting',
+      fingerprint_key='daemon',
+      invert=True
+    )
+    self.assertIn(
+      (str, 'shall_be_fingerprinted'),
+      pairs
+    )
+    self.assertNotIn(
+      (str, 'shant_be_fingerprinted'),
+      pairs
+    )
+
   def assert_fromfile(self, parse_func, expected_append=None, append_contents=None):
     def _do_assert_fromfile(dest, expected, contents):
       with temporary_file() as fp:
diff --git a/tests/python/pants_test/pantsd/test_pantsd_integration.py b/tests/python/pants_test/pantsd/test_pantsd_integration.py
--- a/tests/python/pants_test/pantsd/test_pantsd_integration.py
+++ b/tests/python/pants_test/pantsd/test_pantsd_integration.py
@@ -244,6 +244,22 @@ def test_pantsd_lifecycle_invalidation(self):
         pantsd_run(cmd[1:], {'GLOBAL': {'level': cmd[0]}})
         checker.assert_running()
 
+  def test_pantsd_lifecycle_non_invalidation(self):
+    with self.pantsd_successful_run_context() as (pantsd_run, checker, _):
+      variants = (
+        ['-q', 'help'],
+        ['--no-colors', 'help'],
+        ['help']
+      )
+      last_pid = None
+      for cmd in itertools.chain(*itertools.repeat(variants, 3)):
+        # Run with a CLI flag.
+        pantsd_run(cmd)
+        next_pid = checker.await_pantsd()
+        if last_pid is not None:
+          self.assertEqual(last_pid, next_pid)
+        last_pid = next_pid
+
   def test_pantsd_stray_runners(self):
     # Allow env var overrides for local stress testing.
     attempts = int(os.environ.get('PANTS_TEST_PANTSD_STRESS_ATTEMPTS', 20))