Repository: codingjoe/relint

Issue:
Crash on empty configuration
```
Traceback (most recent call last):
  File "/path/bin/relint", line 8, in <module>
    sys.exit(main())
  File "/path/lib/python3.8/site-packages/relint.py", line 220, in main
    tests = list(load_config(args.config))
  File "/path/lib/python3.8/site-packages/relint.py", line 60, in load_config
    for test in yaml.safe_load(fs):
TypeError: 'NoneType' object is not iterable
```

Reproduction:
- create an empty file relint.yaml
- run with relint -c relint.yaml

Comments:

Patch:
diff --git a/relint.py b/relint.py
index f22aeeb..ba2ffd5 100644
--- a/relint.py
+++ b/relint.py
@@ -17,6 +17,14 @@ GIT_DIFF_SPLIT_PATTERN = re.compile(
     r"(?:\n|^)diff --git a\/.* b\/.*(?:\n|$)")
 
 
+class RelintError(Exception):
+    pass
+
+
+class ConfigError(ValueError, RelintError):
+    pass
+
+
 Test = namedtuple(
     'Test', (
         'name',
@@ -63,27 +71,36 @@ def parse_args(args):
 
 def load_config(path, fail_warnings):
     with open(path) as fs:
-        for test in yaml.safe_load(fs):
-            filename = test.get('filename')
-            if filename:
-                warnings.warn(
-                    "The glob style 'filename' configuration attribute has been"
-                    " deprecated in favor of a new RegEx based 'filePattern' attribute."
-                    " 'filename' support will be removed in relint version 2.0.",
-                    DeprecationWarning
+        try:
+            for test in yaml.safe_load(fs):
+                filename = test.get('filename')
+                if filename:
+                    warnings.warn(
+                        "The glob style 'filename' configuration attribute has been"
+                        " deprecated in favor of a new RegEx based 'filePattern' attribute."
+                        " 'filename' support will be removed in relint version 2.0.",
+                        DeprecationWarning
+                    )
+                    if not isinstance(filename, list):
+                        filename = list(filename)
+                file_pattern = test.get('filePattern', '.*')
+                file_pattern = re.compile(file_pattern)
+                yield Test(
+                    name=test['name'],
+                    pattern=re.compile(test['pattern'], re.MULTILINE),
+                    hint=test.get('hint'),
+                    file_pattern=file_pattern,
+                    filename=filename,
+                    error=test.get('error', True) or fail_warnings
                 )
-                if not isinstance(filename, list):
-                    filename = list(filename)
-            file_pattern = test.get('filePattern', '.*')
-            file_pattern = re.compile(file_pattern)
-            yield Test(
-                name=test['name'],
-                pattern=re.compile(test['pattern'], re.MULTILINE),
-                hint=test.get('hint'),
-                file_pattern=file_pattern,
-                filename=filename,
-                error=test.get('error', True) or fail_warnings
-            )
+        except yaml.YAMLError as e:
+            raise ConfigError("Error parsing your relint config file.") from e
+        except TypeError:
+            warnings.warn("Your relint config is empty, no tests were executed.", UserWarning)
+        except (AttributeError, ValueError) as e:
+            raise ConfigError(
+                "Your relint config is not a valid YAML list of relint tests."
+            ) from e
 
 
 def lint_file(filename, tests):


Test patch:
diff --git a/test_relint.py b/test_relint.py
index 36f67bf..fe8f190 100644
--- a/test_relint.py
+++ b/test_relint.py
@@ -3,9 +3,10 @@ import sys
 import warnings
 
 import pytest
+import yaml
 
 from relint import (main, match_with_diff_changes, parse_diff, parse_filenames,
-                    parse_line_numbers, split_diff_content_by_filename,)
+                    parse_line_numbers, split_diff_content_by_filename, ConfigError, )
 
 
 class TestMain:
@@ -165,3 +166,33 @@ class TestParseGitDiff:
         assert exc_info.value.code == 0
         assert issubclass(w[-1].category, DeprecationWarning)
         assert "'filename'" in str(w[-1].message)
+
+    def test_empty_config_file(self, tmpdir):
+        tmpdir.join('.relint.yml').write('')
+
+        with tmpdir.as_cwd():
+            with warnings.catch_warnings(record=True) as w:
+                with pytest.raises(SystemExit) as exc_info:
+                    main(['**'])
+
+        assert exc_info.value.code == 0
+        assert issubclass(w[-1].category, UserWarning)
+        assert "Your relint config is empty, no tests were executed." in str(w[-1].message)
+
+    def test_malformed_config_file(self, tmpdir):
+        tmpdir.join('.relint.yml').write('test:')
+
+        with tmpdir.as_cwd():
+            with pytest.raises(ConfigError) as exc_info:
+                main(['**'])
+
+        assert "Your relint config is not a valid YAML list of relint tests." in str(exc_info.value)
+
+    def test_corrupt_config_file(self, tmpdir):
+        tmpdir.join('.relint.yml').write(b'\x00')
+
+        with tmpdir.as_cwd():
+            with pytest.raises(ConfigError) as exc_info:
+                main(['**'])
+
+        assert 'Error parsing your relint config file.' in str(exc_info.value)