Repository: PyCQA/flake8

Issue:
A missing explicitly specified config file is silently ignored
*Please describe how you installed Flake8*

```console
$ virtualenv venv
$ . venv/bin/activate
$ pip install flake8
```

*Please provide the exact, unmodified output of `flake8 --bug-report`*

```cnosole
$ flake8 --bug-report
{
  "dependencies": [],
  "platform": {
    "python_implementation": "CPython",
    "python_version": "3.8.8",
    "system": "Linux"
  },
  "plugins": [
    {
      "is_local": false,
      "plugin": "mccabe",
      "version": "0.6.1"
    },
    {
      "is_local": false,
      "plugin": "pycodestyle",
      "version": "2.8.0"
    },
    {
      "is_local": false,
      "plugin": "pyflakes",
      "version": "2.4.0"
    }
  ],
  "version": "4.0.1"
}
```

*Please describe the problem or feature*

flake8 silently ignores the specified config file if it is missing

```console
$ rm -f foo
$ touch t.py
$ flake8 --config foo t.py
$ echo $?
0
```

*If this is a bug report, please explain with examples (and example code) what you expected to happen and what actually happened.*

An error should be raised if the specified config file is missing.

Looks like the culprit is `configparser.read()`: ["If a file named in filenames cannot be opened, that file will be ignored"](https://docs.python.org/3/library/configparser.html#configparser.ConfigParser.read)

Discussion:
sigmavirus24: We have not documented that we would raise an error if the configuration file doesn't exist and if we wanted to split hairs, this is backwards incompatible behaviour meaning we couldn't easily just accept this at the moment
rkm: Thanks for reviewing. I hadn't considered there would be any worries about backwards incompatibility. Feel free to close this if you think that's a blocker. If so then could we at least update the docs to indicate the existing behaviour?

Patch:
diff --git a/src/flake8/options/config.py b/src/flake8/options/config.py
index d7519df..7cba936 100644
--- a/src/flake8/options/config.py
+++ b/src/flake8/options/config.py
@@ -8,6 +8,7 @@ from typing import List
 from typing import Optional
 from typing import Tuple
 
+from flake8 import exceptions
 from flake8.options.manager import OptionManager
 
 LOG = logging.getLogger(__name__)
@@ -60,7 +61,10 @@ def load_config(
 
     cfg = configparser.RawConfigParser()
     if config is not None:
-        cfg.read(config)
+        if not cfg.read(config):
+            raise exceptions.ExecutionError(
+                f"The specified config file does not exist: {config}"
+            )
         cfg_dir = os.path.dirname(config)
     else:
         cfg_dir = pwd


Test patch:
diff --git a/tests/integration/test_main.py b/tests/integration/test_main.py
index b351e9d..fe254b7 100644
--- a/tests/integration/test_main.py
+++ b/tests/integration/test_main.py
@@ -386,3 +386,21 @@ def test_early_keyboard_interrupt_does_not_crash(capsys):
     out, err = capsys.readouterr()
     assert out == "... stopped\n"
     assert err == ""
+
+
+def test_config_file_not_found(tmpdir, capsys):
+    """Ensure that an explicitly specified config file which is not found is an
+    error"""
+
+    expected = """\
+There was a critical error during execution of Flake8:
+The specified config file does not exist: missing.cfg
+"""
+
+    with tmpdir.as_cwd():
+        tmpdir.join("t.py").write("print('hello hello world')\n")
+        assert cli.main(["--config", "missing.cfg", "t.py"]) == 1
+
+    out, err = capsys.readouterr()
+    assert out == expected
+    assert err == ""
diff --git a/tests/unit/test_options_config.py b/tests/unit/test_options_config.py
index b288de0..c5d1476 100644
--- a/tests/unit/test_options_config.py
+++ b/tests/unit/test_options_config.py
@@ -2,6 +2,7 @@ import configparser
 
 import pytest
 
+from flake8 import exceptions
 from flake8.main.options import register_default_options
 from flake8.options import config
 from flake8.options.manager import OptionManager
@@ -164,3 +165,8 @@ def test_parse_config_ignores_unknowns(tmp_path, opt_manager, caplog):
             'Option "wat" is not registered. Ignoring.',
         )
     ]
+
+
+def test_load_config_missing_file_raises_exception(capsys):
+    with pytest.raises(exceptions.ExecutionError):
+        config.load_config("foo.cfg", [])