Repository: jazzband/pip-tools

Issue:
Incorrect output on pip-compile upgrade with --dry-run and --quiet flags
##### Environment Versions

1. OS Type: any
1. Python version: any
1. pip version:  any
1. pip-tools version: 3.8.0

##### Steps to replicate

1. `echo "six" > requirements.in`
1. `pip-compile`
1. `pip-compile --upgrade --dry-run --quiet`

##### Expected result

- `--dry-run` should print changes to stdout/stderr
- `--quiet` should suppress _"Dry-run, so nothing updated."_ message 

```bash
#
# This file is autogenerated by pip-compile
# To update, run:
#
#    pip-compile
#
six==1.12.0
```

##### Actual result

But currently it behaves the opposite way:

```
Dry-run, so nothing updated.
```

---

Refs: 

- #843 
- https://github.com/jazzband/pip-tools/issues/841#issuecomment-508454216


Discussion:
I think the state now is that the dry run message is indeed suppressed by the quiet flag, but so is the output.

Do we want the dry run flag to effectively bypass the log level for the stderr copy of output? Something like

```diff
--- a/piptools/writer.py
+++ b/piptools/writer.py
@@ -245,10 +245,12 @@ class OutputWriter:
     ) -> None:

         for line in self._iter_lines(results, unsafe_requirements, markers, hashes):
-            log.info(line)
             if not self.dry_run:
+                log.info(line)
                 self.dst_file.write(unstyle(line).encode())
                 self.dst_file.write(os.linesep.encode())
+            else:
+                log.log(line)

```

The above change would break two current tests:

- `test_quiet_option`
- `test_dry_run_quiet_option`

This is because the command

```console
pip-compile --quiet --dry-run
```

would produce the header comment in stderr, instead of an empty stderr.

That doesn't violate what the tests intend to check for exactly, IMO.

Resolving patch:
diff --git a/piptools/writer.py b/piptools/writer.py
--- a/piptools/writer.py
+++ b/piptools/writer.py
@@ -245,8 +245,11 @@ def write(
     ) -> None:
 
         for line in self._iter_lines(results, unsafe_requirements, markers, hashes):
-            log.info(line)
-            if not self.dry_run:
+            if self.dry_run:
+                # Bypass the log level to always print this during a dry run
+                log.log(line)
+            else:
+                log.info(line)
                 self.dst_file.write(unstyle(line).encode())
                 self.dst_file.write(os.linesep.encode())
 


Test patch:
diff --git a/tests/test_cli_compile.py b/tests/test_cli_compile.py
--- a/tests/test_cli_compile.py
+++ b/tests/test_cli_compile.py
@@ -801,8 +801,9 @@ def test_upgrade_packages_version_option_and_upgrade_no_existing_file(pip_conf,
 def test_quiet_option(runner):
     with open("requirements", "w"):
         pass
-    out = runner.invoke(cli, ["--quiet", "-n", "requirements"])
-    # Pinned requirements result has not been written to output.
+    out = runner.invoke(cli, ["--quiet", "requirements"])
+    # Pinned requirements result has not been written to stdout or stderr:
+    assert not out.stdout_bytes
     assert not out.stderr_bytes
 
 
@@ -818,8 +819,12 @@ def test_dry_run_quiet_option(runner):
     with open("requirements", "w"):
         pass
     out = runner.invoke(cli, ["--dry-run", "--quiet", "requirements"])
-    # Dry-run message has not been written to output.
-    assert not out.stderr_bytes
+    # Neither dry-run message nor pinned requirements written to output:
+    assert not out.stdout_bytes
+    # Dry-run message has not been written to stderr:
+    assert "dry-run" not in out.stderr.lower()
+    # Pinned requirements (just the header in this case) *are* written to stderr:
+    assert "# " in out.stderr
 
 
 def test_generate_hashes_with_editable(pip_conf, runner):