1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213
|
# Load in our dependencies
from __future__ import absolute_import
import os
import subprocess
import sys
import textwrap
from unittest import TestCase
import restructuredtext_lint
_dir = os.path.dirname(os.path.abspath(__file__))
valid_rst = os.path.join(_dir, 'test_files', 'valid.rst')
warning_rst = os.path.join(_dir, 'test_files', 'second_short_heading.rst')
dir_rst = os.path.join(_dir, 'test_files', 'dir')
invalid_rst = os.path.join(_dir, 'test_files', 'invalid.rst')
rst_lint_path = os.path.join(_dir, os.pardir, 'cli.py')
"""
# TODO: Implement this as a class (options) with a sugar function that lints a string against a set of options
An invalid rst file
when linted with the `fail_first` parameter
raises on the first error
"""
class TestRestructuredtextLint(TestCase):
def _load_file(self, filepath):
"""Load a file into memory"""
f = open(filepath)
file = f.read()
f.close()
return file
def _lint_file(self, *args, **kwargs):
"""Lint the file and preserve any errors"""
return restructuredtext_lint.lint(*args, **kwargs)
def test_passes_valid_rst(self):
"""A valid reStructuredText file will not raise any errors"""
content = self._load_file(valid_rst)
errors = self._lint_file(content)
self.assertEqual(errors, [])
def test_raises_on_invalid_rst(self):
"""An invalid reStructuredText file when linted raises errors"""
# Load and lint invalid file
content = self._load_file(invalid_rst)
actual_errors = self._lint_file(content, invalid_rst)
# Assert errors against expected errors
self.assertEqual(len(actual_errors), 1)
self.assertEqual(actual_errors[0].line, 2)
self.assertEqual(actual_errors[0].level, 2)
self.assertEqual(actual_errors[0].type, 'WARNING')
self.assertEqual(actual_errors[0].source, invalid_rst)
self.assertEqual(actual_errors[0].message, 'Title underline too short.')
def test_encoding_utf8(self):
"""A document with utf-8 characters is valid."""
filepath = os.path.join(_dir, 'test_files', 'utf8.rst')
errors = restructuredtext_lint.lint_file(filepath, encoding='utf-8')
self.assertEqual(errors, [])
def test_second_heading_short_line_number(self):
"""A document with a short second heading raises errors that include a line number
This is a regression test for https://github.com/twolfson/restructuredtext-lint/issues/5
"""
filepath = os.path.join(_dir, 'test_files', 'second_short_heading.rst')
errors = restructuredtext_lint.lint_file(filepath)
self.assertEqual(errors[0].line, 6)
self.assertEqual(errors[0].source, filepath)
def test_invalid_target(self):
"""A document with an invalid target name raises an error
This is a regression test for https://github.com/twolfson/restructuredtext-lint/issues/6
"""
filepath = os.path.join(_dir, 'test_files', 'invalid_target.rst')
errors = restructuredtext_lint.lint_file(filepath)
self.assertIn('Unknown target name', errors[0].message)
def test_invalid_line_mismatch(self):
"""A document with an overline/underline mismatch raises an error
This is a regression test for https://github.com/twolfson/restructuredtext-lint/issues/7
"""
filepath = os.path.join(_dir, 'test_files', 'invalid_line_mismatch.rst')
errors = restructuredtext_lint.lint_file(filepath)
self.assertIn('Title overline & underline mismatch', errors[0].message)
def test_invalid_link(self):
"""A document with a bad link raises an error
This is a regression test for https://github.com/twolfson/restructuredtext-lint/issues/12
"""
filepath = os.path.join(_dir, 'test_files', 'invalid_link.rst')
errors = restructuredtext_lint.lint_file(filepath)
self.assertIn('Anonymous hyperlink mismatch: 1 references but 0 targets.', errors[0].message)
self.assertIn('Hyperlink target "hello" is not referenced.', errors[1].message)
def test_rst_prolog_basic(self):
"""A document using substitutions from an `rst-prolog` has no errors"""
# https://github.com/twolfson/restructuredtext-lint/issues/39
# Set up our common content
rst_prolog = textwrap.dedent("""
.. |World| replace:: Moon
""")
content = textwrap.dedent("""
Hello
=====
|World|
""")
# Verify we have errors about substitutions without our `--rst-prolog`
errors = restructuredtext_lint.lint(content)
self.assertEqual(len(errors), 1)
self.assertIn('Undefined substitution referenced: "World"', errors[0].message)
# Verify we have no errors with our `--rst-prolog`
errors = restructuredtext_lint.lint(content, rst_prolog=rst_prolog)
self.assertEqual(len(errors), 0)
def test_rst_prolog_line_offset(self):
"""A document with errors using an `rst-prolog` offsets our error lines"""
# https://github.com/twolfson/restructuredtext-lint/issues/39
# Perform our setup
rst_prolog = textwrap.dedent("""
.. |World| replace:: Moon
""")
content = textwrap.dedent("""
Hello
==
|World|
""")
# Lint our content and assert its errors
errors = restructuredtext_lint.lint(content, rst_prolog=rst_prolog)
self.assertEqual(len(errors), 1)
self.assertIn('Possible title underline, too short for the title', errors[0].message)
# DEV: Without adjustments, this would be 6 due to empty lines in multiline strings
self.assertEqual(errors[0].line, 3)
class TestRestructuredtextLintCLI(TestCase):
""" Tests for 'rst-lint' CLI command """
def test_rst_lint_filepaths_not_given(self):
"""The `rst-lint` command is available and prints error if no filepath was given."""
with self.assertRaises(subprocess.CalledProcessError) as e:
# python ../cli.py
subprocess.check_output((sys.executable, rst_lint_path), stderr=subprocess.STDOUT)
output = str(e.exception.output)
# Python 2: "too few arguments"
# Python 3: "the following arguments are required: filepath"
self.assertIn('arguments', output)
def test_rst_lint_correct_file(self):
"""The `rst-lint` command prints nothing if rst file is correct."""
# python ../cli.py test_files/valid.rst
raw_output = subprocess.check_output((sys.executable, rst_lint_path, valid_rst), universal_newlines=True)
output = str(raw_output)
self.assertEqual(output, '')
def test_rst_lint_folder(self):
"""The `rst-lint` command should print errors for files inside folders."""
with self.assertRaises(subprocess.CalledProcessError) as e:
subprocess.check_output((sys.executable, rst_lint_path, dir_rst), universal_newlines=True)
output = str(e.exception.output)
# Verify exactly 1 error is produced
self.assertEqual(output.count('WARNING'), 1)
def test_rst_lint_many_files(self):
"""The `rst-lint` command accepts many rst file paths and prints respective information for each of them."""
with self.assertRaises(subprocess.CalledProcessError) as e:
# python ../cli.py test_files/valid.rst invalid.rst
subprocess.check_output((sys.executable, rst_lint_path, valid_rst, invalid_rst), universal_newlines=True)
output = str(e.exception.output)
# 'rst-lint' should exit with error code 2 as linting failed:
self.assertEqual(e.exception.returncode, 2)
# There should be no clean output:
# DEV: This verifies only 1 line of output which is our invalid line
self.assertEqual(output.count('\n'), 1, output)
# There should be a least one invalid rst file:
self.assertIn('WARNING', output)
def test_level_fail(self):
"""Confirm low --level threshold fails file with warnings only"""
# This is the expected behaviour we are checking:
# $ rst-lint --level warning second_short_heading.rst ; echo "Return code $?"
# WARNING second_short_heading.rst:6 Title underline too short.
# WARNING second_short_heading.rst:6 Title underline too short.
# Return code 2
with self.assertRaises(subprocess.CalledProcessError) as e:
subprocess.check_output((sys.executable, rst_lint_path, '--level', 'warning', warning_rst),
universal_newlines=True)
output = str(e.exception.output)
self.assertEqual(output.count('\n'), 2, output)
self.assertEqual(output.count('WARNING'), 2, output)
# The expected 2 warnings should be treated as failing
self.assertEqual(e.exception.returncode, 2)
def test_level_high(self):
"""Confirm high --level threshold accepts file with warnings only"""
# This is the expected behaviour we are checking:
# $ rst-lint --level error second_short_heading.rst ; echo "Return code $?"
# Return code 0
raw_output = subprocess.check_output((sys.executable, rst_lint_path, '--level', 'error', warning_rst),
universal_newlines=True)
# `check_output` doesn't raise an exception code so it's error code 0
output = str(raw_output)
self.assertEqual(output, '')
|