import sys

import polib
import pytest

from dennis.linter import (
    BadFormatLintRule,
    BlankLintRule,
    MalformedNoTypeLintRule,
    MalformedMissingRightBraceLintRule,
    MalformedMissingLeftBraceLintRule,
    MismatchedHTMLLintRule,
    MissingVarsLintRule,
    InvalidVarsLintRule,
    UnchangedLintRule,
    LintedEntry,
    Linter
)
from dennis.templatelinter import (
    HardToReadNamesTLR,
    OneCharNamesTLR,
    MultipleUnnamedVarsTLR,
)
from dennis.tools import VariableTokenizer
from tests import build_po_string


class LinterTest:
    def test_linter(self):
        pofile = build_po_string(
            '#: foo/foo.py:5\n'
            'msgid "Foo"\n'
            'msgstr "Oof"\n')

        linter = Linter(
            ['python-format', 'python-brace-format'],
            ['E201', 'W202']
        )
        msgs = linter.verify_file(pofile)

        # No warnings or errors, so there are no messages.
        assert len(msgs) == 0

    def test_linter_fuzzy_strings(self):
        pofile = build_po_string(
            '#, fuzzy\n'
            '#~ msgid "Most Recent Message"\n'
            '#~ msgid_plural "Last %(count)s Messages"\n'
            '#~ msgstr[0] "Les {count} derniers messages"\n'
            '#~ msgstr[1] "Les {count} derniers messages"\n')

        linter = Linter(
            ['python-format', 'python-brace-format'],
            ['E201', 'W202']
        )
        msgs = linter.verify_file(pofile)

        # There were no non-fuzzy strings, so nothing to lint.
        assert len(msgs) == 0

    def test_linter_untranslated_strings(self):
        pofile = build_po_string(
            '#, fuzzy\n'
            '#~ msgid "Most Recent Message"\n'
            '#~ msgid_plural "Last %(count)s Messages"\n'
            '#~ msgstr[0] ""\n'
            '#~ msgstr[1] ""\n')

        linter = Linter(
            ['python-format', 'python-brace-format'],
            ['E201', 'W202']
        )
        msgs = linter.verify_file(pofile)

        # There were no translated strings, so nothing to lint.
        assert len(msgs) == 0


def build_linted_entry(po_data):
    po = polib.pofile(build_po_string(po_data))
    poentry = list(po)[0]
    return LintedEntry(poentry)


class LintRuleTestCase:
    vartok = VariableTokenizer(['python-format', 'python-brace-format'])


class TestBadFormatLintRule(LintRuleTestCase):
    lintrule = BadFormatLintRule()

    def test_fine(self):
        linted_entry = build_linted_entry(
            '#: foo/foo.py:5\n'
            'msgid "Foo"\n'
            'msgstr "FOO"\n'
        )
        msgs = self.lintrule.lint(self.vartok, linted_entry)
        assert msgs == []

        linted_entry = build_linted_entry(
            '#: foo/foo.py:5\n'
            'msgid "Foo %s"\n'
            'msgstr "FOO %s"\n'
        )
        msgs = self.lintrule.lint(self.vartok, linted_entry)
        assert msgs == []

    def test_bad_format_character(self):
        linted_entry = build_linted_entry(
            '#: foo/foo.py:5\n'
            'msgid "%s foo"\n'
            'msgstr "%a FOO"\n'
        )
        msgs = self.lintrule.lint(self.vartok, linted_entry)
        assert len(msgs) == 1
        assert msgs[0].kind == 'err'
        assert msgs[0].code == 'E104'
        assert msgs[0].msg == 'bad format character: %a'

    def test_last_character(self):
        linted_entry = build_linted_entry(
            '#: foo/foo.py:5\n'
            'msgid "foo %s"\n'
            'msgstr "FOO %"\n'
        )
        msgs = self.lintrule.lint(self.vartok, linted_entry)
        assert len(msgs) == 1
        assert msgs[0].kind == 'err'
        assert msgs[0].code == 'E104'
        assert msgs[0].msg == 'bad format character: %'

    def test_handle_double_percent(self):
        linted_entry = build_linted_entry(
            '#: foo/foo.py:5\n'
            'msgid "%% foo"\n'
            'msgstr "%% FOO"\n'
        )
        msgs = self.lintrule.lint(self.vartok, linted_entry)
        assert msgs == []

    def test_not_in_msgid(self):
        # If there are no vars in the msgid, then we don't want to throw errors about stuff we see
        # in the translated string because it's likely it's not being interpolated by %.
        linted_entry = build_linted_entry(
            '#: foo/foo.py:5\n'
            'msgid "foo 50% omg"\n'
            'msgstr "FOO 50% OMG"\n'
        )
        msgs = self.lintrule.lint(self.vartok, linted_entry)
        assert msgs == []

    def test_ignore_non_formatting_tokens(self):
        # If the formatting token is actually part of another formatting token, then we want to
        # ignore it.
        linted_entry = build_linted_entry(
            '#: foo/foo.py:5\n'
            'msgid "foo {startdate:%Y-%m-%d %H:%M} bar"\n'
            'msgstr "FOO {startdate:%Y-%m-%d %H:%M} BAR"\n'
        )
        msgs = self.lintrule.lint(self.vartok, linted_entry)
        assert msgs == []

    def test_varformat_empty(self):
        vartok = VariableTokenizer([])
        linted_entry = build_linted_entry(
            '#: foo/foo.py:5\n'
            'msgid "%s foo"\n'
            'msgstr "%a FOO"\n'
        )
        msgs = self.lintrule.lint(vartok, linted_entry)
        assert msgs == []


class TestMalformedNoTypeLintRule(LintRuleTestCase):
    lintrule = MalformedNoTypeLintRule()

    def test_fine(self):
        linted_entry = build_linted_entry(
            '#: foo/foo.py:5\n'
            'msgid "Foo"\n'
            'msgstr "Oof"\n')

        msgs = self.lintrule.lint(self.vartok, linted_entry)
        assert len(msgs) == 0

        linted_entry = build_linted_entry(
            '#: foo/foo.py:5\n'
            'msgid "Foo: {foo}"\n'
            'msgstr "Oof: {foo}"\n')

        msgs = self.lintrule.lint(self.vartok, linted_entry)
        assert msgs == []

    def test_python_var_with_space(self):
        linted_entry = build_linted_entry(
            '#: kitsune/questions/templates/questions/answers.html:56\n'
            'msgid "%(count)s view"\n'
            'msgid_plural "%(count)s views"\n'
            'msgstr[0] "%(count) zoo"\n')

        msgs = self.lintrule.lint(self.vartok, linted_entry)
        assert len(msgs) == 1
        assert msgs[0].kind == 'err'
        assert msgs[0].code == 'E101'
        assert msgs[0].msg == 'type missing: %(count)'

    def test_python_var_end_of_line(self):
        linted_entry = build_linted_entry(
            '#: kitsune/questions/templates/questions/answers.html:56\n'
            'msgid "%(count)s"\n'
            'msgid_plural "%(count)s"\n'
            'msgstr[0] "%(count)"\n')

        msgs = self.lintrule.lint(self.vartok, linted_entry)
        assert len(msgs) == 1
        assert msgs[0].kind == 'err'
        assert msgs[0].code == 'E101'
        assert msgs[0].msg == 'type missing: %(count)'

    def test_python_var_punctuation(self):
        linted_entry = build_linted_entry(
            '#: kitsune/questions/templates/questions/answers.html:56\n'
            'msgid "%(count)s"\n'
            'msgstr "%(count)!"\n'
        )
        msgs = self.lintrule.lint(self.vartok, linted_entry)
        assert len(msgs) == 1
        assert msgs[0].kind == 'err'
        assert msgs[0].code == 'E101'
        assert msgs[0].msg == 'type missing: %(count)'

    def test_python_var_not_malformed(self):
        """This used to be a false positive"""
        linted_entry = build_linted_entry(
            '#: kitsune/questions/templates/questions/answers.html:56\n'
            'msgid "%(stars)s by %(user)s on %(date)s (%(locale)s)"\n'
            'msgstr "%(stars)s de %(user)s el %(date)s (%(locale)s)"\n')

        msgs = self.lintrule.lint(self.vartok, linted_entry)
        assert msgs == []

    def test_varformat_empty(self):
        vartok = VariableTokenizer([])

        linted_entry = build_linted_entry(
            '#: kitsune/questions/templates/questions/answers.html:56\n'
            'msgid "%(count)s view"\n'
            'msgid_plural "%(count)s views"\n'
            'msgstr[0] "%(count) zoo"\n')

        msgs = self.lintrule.lint(vartok, linted_entry)
        assert msgs == []


class TestMalformedMissingRightBraceLintRule(LintRuleTestCase):
    lintrule = MalformedMissingRightBraceLintRule()

    def test_python_var_missing_right_curly_brace(self):
        linted_entry = build_linted_entry(
            '#: kitsune/questions/templates/questions/answers.html:56\n'
            'msgid "{foo} bar is the best thing ever"\n'
            'msgstr "{foo) bar is the best thing ever"\n')

        msgs = self.lintrule.lint(self.vartok, linted_entry)
        assert len(msgs) == 1
        assert msgs[0].kind == 'err'
        assert msgs[0].code == 'E102'
        assert (
            msgs[0].msg ==
            'missing right curly-brace: {foo) bar is the best thing ever'
        )

        linted_entry = build_linted_entry(
            '#: kitsune/questions/templates/questions/answers.html:56\n'
            'msgid "{foo}"\n'
            'msgstr "{foo"\n')

        msgs = self.lintrule.lint(self.vartok, linted_entry)
        assert len(msgs) == 1
        assert msgs[0].kind == 'err'
        assert msgs[0].code == 'E102'
        assert (
            msgs[0].msg ==
            'missing right curly-brace: {foo'
        )

    def test_python_var_missing_right_curly_brace_two_vars(self):
        # Test right-most one
        linted_entry = build_linted_entry(
            'msgid "Value for key \\"{0}\\" exceeds the length of {1}"\n'
            'msgstr "Valor para la clave \\"{0}\\" excede el tamano de {1]"\n')

        msgs = self.lintrule.lint(self.vartok, linted_entry)
        assert len(msgs) == 1
        assert msgs[0].kind == 'err'
        assert msgs[0].code == 'E102'
        assert (
            msgs[0].msg ==
            'missing right curly-brace: {1]'
        )

        # Test left-most one
        linted_entry = build_linted_entry(
            'msgid "Value for key \\"{0}\\" exceeds the length of {1}"\n'
            'msgstr "Valor para la clave \\"{0]\\" excede el tamano de {1}"\n')

        msgs = self.lintrule.lint(self.vartok, linted_entry)
        assert len(msgs) == 1
        assert msgs[0].kind == 'err'
        assert msgs[0].code == 'E102'
        assert (
            msgs[0].msg ==
            'missing right curly-brace: {0]" excede el tamano de {'
        )

    def test_varformat_empty(self):
        vartok = VariableTokenizer([])

        linted_entry = build_linted_entry(
            'msgid "Value for key \\"{0}\\" exceeds the length of {1}"\n'
            'msgstr "Valor para la clave \\"{0}\\" excede el tamano de {1]"\n')

        msgs = self.lintrule.lint(vartok, linted_entry)
        assert msgs == []


class TestMalformedMissingLeftBraceLintRuleTest(LintRuleTestCase):
    lintrule = MalformedMissingLeftBraceLintRule()

    def test_python_var_missing_left_curly_brace(self):
        linted_entry = build_linted_entry(
            '#: kitsune/questions/templates/questions/answers.html:56\n'
            'msgid "{product} Support Forum"\n'
            'msgstr "product}-Hilfeforum"\n')

        msgs = self.lintrule.lint(self.vartok, linted_entry)
        assert len(msgs) == 1
        assert msgs[0].kind == 'err'
        assert msgs[0].code == 'E103'
        assert (
            msgs[0].msg ==
            'missing left curly-brace: product}'
        )

        linted_entry = build_linted_entry(
            '#: kitsune/questions/templates/questions/answers.html:56\n'
            'msgid "{q} | {product} Support Forum"\n'
            'msgstr "{q} | product}-Hilfeforum"\n')

        msgs = self.lintrule.lint(self.vartok, linted_entry)
        assert len(msgs) == 1
        assert msgs[0].kind == 'err'
        assert msgs[0].code == 'E103'
        assert (
            msgs[0].msg ==
            'missing left curly-brace: } | product}'
        )

        linted_entry = build_linted_entry(
            '#: kitsune/questions/templates/questions/question_details.html:14\n'
            'msgid "{q} | {product} Support Forum"\n'
            'msgstr "{q} | {product}} foo bar"\n')

        msgs = self.lintrule.lint(self.vartok, linted_entry)
        assert len(msgs) == 1
        assert msgs[0].kind == 'err'
        assert msgs[0].code == 'E103'
        assert (
            msgs[0].msg ==
            'missing left curly-brace: }}'
        )

    def test_varformat_empty(self):
        vartok = VariableTokenizer([])

        linted_entry = build_linted_entry(
            '#: kitsune/questions/templates/questions/question_details.html:14\n'
            'msgid "{q} | {product} Support Forum"\n'
            'msgstr "{q} | {product}} foo bar"\n')

        msgs = self.lintrule.lint(vartok, linted_entry)
        assert msgs == []


class TestMissingVarsLintRule(LintRuleTestCase):
    lintrule = MissingVarsLintRule()

    def test_fine(self):
        linted_entry = build_linted_entry(
            '#: foo/foo.py:5\n'
            'msgid "Foo"\n'
            'msgstr "Oof"\n')

        msgs = self.lintrule.lint(self.vartok, linted_entry)
        assert len(msgs) == 0

        linted_entry = build_linted_entry(
            '#: foo/foo.py:5\n'
            'msgid "Foo: {foo}"\n'
            'msgstr "Oof: {foo}"\n')

        msgs = self.lintrule.lint(self.vartok, linted_entry)
        assert len(msgs) == 0

    def test_missing(self):
        linted_entry = build_linted_entry(
            '#: foo/foo.py:5\n'
            'msgid "Foo: {foo}"\n'
            'msgstr "Oof"\n')

        msgs = self.lintrule.lint(self.vartok, linted_entry)
        assert len(msgs) == 1
        assert msgs[0].kind == 'warn'
        assert msgs[0].code == 'W202'
        assert (
            msgs[0].msg ==
            'missing variables: {foo}'
        )

        linted_entry = build_linted_entry(
            '#: foo/foo.py:5\n'
            'msgid "Foo: {foo} {bar} {baz}"\n'
            'msgstr "Oof: {foo}"\n')

        msgs = self.lintrule.lint(self.vartok, linted_entry)
        assert len(msgs) == 1
        assert msgs[0].kind == 'warn'
        assert msgs[0].code == 'W202'
        assert (
            msgs[0].msg ==
            'missing variables: {bar}, {baz}'
        )

    def test_complex(self):
        linted_entry = build_linted_entry(
            '#: foo/foo.py:5\n'
            'msgid "Foo: {bar} {baz}"\n'
            'msgstr "Oof: {foo} {bar}"\n')

        msgs = self.lintrule.lint(self.vartok, linted_entry)
        assert len(msgs) == 1
        assert msgs[0].kind == 'warn'
        assert msgs[0].code == 'W202'
        assert (
            msgs[0].msg ==
            'missing variables: {baz}'
        )

    def test_plurals(self):
        # It's possible for the msgid to have no variables in it and
        # the msgid_plural to have variables in it. msgstr[n] strings
        # should be compared against the set of variables in msgid and
        # msgid_plural. This tests the common case.
        linted_entry = build_linted_entry(
            '#: foo/foo.py:5\n'
            'msgid "1 reply"\n'
            'msgid_plural "{n} replies"\n'
            'msgstr[0] "{n} mooo"\n')

        msgs = self.lintrule.lint(self.vartok, linted_entry)
        assert len(msgs) == 0

    def test_plurals_not_missing(self):
        # If the msgstr doesn't have variables that are in msgid or
        # msgid_plural, that's ok since we don't know which plurality
        # it is.
        linted_entry = build_linted_entry(
            '#: foo/foo.py:5\n'
            'msgid "1 post"\n'
            'msgid_plural "{0} posts"\n'
            'msgstr[0] "1 moo"\n')

        msgs = self.lintrule.lint(self.vartok, linted_entry)
        assert len(msgs) == 0

    def test_double_percent(self):
        # Double-percent shouldn't be picked up as a variable.
        # Issue #28.
        linted_entry = build_linted_entry(
            '#: foo/foo.py:5\n'
            'msgid "50% of the traffic"\n'
            'msgstr "more than 50%% of the traffic"\n')

        msgs = self.lintrule.lint(self.vartok, linted_entry)
        assert len(msgs) == 0

    def test_urlencoded_urls(self):
        # urlencoding uses % and that shouldn't get picked up
        # as variables.
        # Issue #27.
        linted_entry = build_linted_entry(
            '#: foo/foo.py:5\n'
            'msgid "OMG! Best url is http://example.com/foo"\n'
            'msgstr "http://example.com/foo%20%E5%B4%A9%E6%BA%83 is best"\n')

        msgs = self.lintrule.lint(self.vartok, linted_entry)
        assert len(msgs) == 0

    def test_python_format_are_errors_unnamed(self):
        # python-format unnamed variables must be in the msgstr so they're errors.
        # Issue #57
        linted_entry = build_linted_entry(
            '#: kitsune/kbforums/feeds.py:23\n'
            'msgid "Recently updated threads about %s"\n'
            'msgstr "RECENTLY UPDATED THREADS"\n'
        )
        msgs = self.lintrule.lint(self.vartok, linted_entry)
        assert len(msgs) == 1
        assert msgs[0].kind == 'err'
        assert msgs[0].code == 'E202'
        assert (
            msgs[0].msg ==
            'missing variables: %s'
        )

    def test_varformat_empty(self):
        vartok = VariableTokenizer([])

        linted_entry = build_linted_entry(
            '#: kitsune/kbforums/feeds.py:23\n'
            'msgid "Recently updated threads about %s"\n'
            'msgstr "RECENTLY UPDATED THREADS"\n'
        )
        msgs = self.lintrule.lint(vartok, linted_entry)
        assert msgs == []


class TestInvalidVarsLintRule(LintRuleTestCase):
    lintrule = InvalidVarsLintRule()

    def test_fine(self):
        linted_entry = build_linted_entry(
            '#: foo/foo.py:5\n'
            'msgid "Foo"\n'
            'msgstr "Oof"\n')

        msgs = self.lintrule.lint(self.vartok, linted_entry)
        assert msgs == []

        linted_entry = build_linted_entry(
            '#: foo/foo.py:5\n'
            'msgid "Foo: {foo}"\n'
            'msgstr "Oof: {foo}"\n')

        msgs = self.lintrule.lint(self.vartok, linted_entry)
        assert msgs == []

    def test_invalid(self):
        linted_entry = build_linted_entry(
            '#: foo/foo.py:5\n'
            'msgid "Foo {bar}"\n'
            'msgstr "Oof: {foo}"\n')

        msgs = self.lintrule.lint(self.vartok, linted_entry)
        assert len(msgs) == 1
        assert msgs[0].kind == 'err'
        assert msgs[0].code == 'E201'
        assert msgs[0].msg == 'invalid variables: {foo}'

        linted_entry = build_linted_entry(
            '#: foo/foo.py:5\n'
            'msgid "Foo: {foo}"\n'
            'msgstr "Oof: {foo} {bar} {baz}"\n')

        msgs = self.lintrule.lint(self.vartok, linted_entry)
        assert len(msgs) == 1
        assert msgs[0].kind == 'err'
        assert msgs[0].code == 'E201'
        assert msgs[0].msg == 'invalid variables: {bar}, {baz}'

    def test_complex(self):
        linted_entry = build_linted_entry(
            '#: foo/foo.py:5\n'
            'msgid "Foo: {bar} {baz}"\n'
            'msgstr "Oof: {foo} {bar}"\n')

        msgs = self.lintrule.lint(self.vartok, linted_entry)
        assert len(msgs) == 1
        assert msgs[0].kind == 'err'
        assert msgs[0].code == 'E201'
        assert msgs[0].msg == 'invalid variables: {foo}'

    def test_plurals(self):
        # It's possible for the msgid to have no variables in it and
        # the msgid_plural to have variables in it. msgstr[n] strings
        # should be compared against the set of variables in msgid and
        # msgid_plural. This tests the common case.
        linted_entry = build_linted_entry(
            '#: foo/foo.py:5\n'
            'msgid "1 reply"\n'
            'msgid_plural "{n} replies"\n'
            'msgstr[0] "{n} mooo"\n')

        msgs = self.lintrule.lint(self.vartok, linted_entry)
        assert msgs == []

    def test_double_percent(self):
        # Double-percent shouldn't be picked up as a variable.
        # Issue #28.
        linted_entry = build_linted_entry(
            '#: foo/foo.py:5\n'
            'msgid "50% of the traffic"\n'
            'msgstr "more than 50%% of the traffic"\n')

        msgs = self.lintrule.lint(self.vartok, linted_entry)
        assert msgs == []

    def test_urlencoded_urls(self):
        # urlencoding uses % and that shouldn't get picked up
        # as variables.
        # Issue #27.
        linted_entry = build_linted_entry(
            '#: foo/foo.py:5\n'
            'msgid "OMG! Best url is http://example.com/foo"\n'
            'msgstr "http://example.com/foo%20%E5%B4%A9%E6%BA%83 is best"\n')

        msgs = self.lintrule.lint(self.vartok, linted_entry)
        assert msgs == []

    def test_msgid_no_vars(self):
        linted_entry = build_linted_entry(
            '#: foo/foo.py:5\n'
            'msgid "http://en.wikipedia.org/wiki/Canvas_element"\n'
            'msgstr "http://it.wikipedia.org/wiki/Canvas_%28elemento_HTML%29"\n')

        msgs = self.lintrule.lint(self.vartok, linted_entry)
        assert msgs == []

    def test_varformat_empty(self):
        vartok = VariableTokenizer([])
        linted_entry = build_linted_entry(
            '#: foo/foo.py:5\n'
            'msgid "Foo {bar}"\n'
            'msgstr "Oof: {foo}"\n')

        msgs = self.lintrule.lint(vartok, linted_entry)
        assert msgs == []


class TestBlankLintRule(LintRuleTestCase):
    lintrule = BlankLintRule()

    def test_fine(self):
        linted_entry = build_linted_entry(
            '#: foo/foo.py:5\n'
            'msgid "Foo"\n'
            'msgstr ""\n')

        msgs = self.lintrule.lint(self.vartok, linted_entry)
        assert len(msgs) == 0

    def test_whitespace(self):
        testdata = [
            ' ',
            '  ',
            '\t'
        ]
        for data in testdata:
            linted_entry = build_linted_entry(
                '#: foo/foo.py:5\n'
                'msgid "Foo"\n'
                'msgstr "%s"\n' % data)

            msgs = self.lintrule.lint(self.vartok, linted_entry)
            assert len(msgs) == 1
            assert msgs[0].kind == 'warn'
            assert msgs[0].code == 'W301'
            assert msgs[0].msg == u'translated string is solely whitespace'


class TestUnchangedLintRule(LintRuleTestCase):
    lintrule = UnchangedLintRule()

    def test_unchanged(self):
        linted_entry = build_linted_entry(
            '#: foo/foo.py:5\n'
            'msgid "Foo"\n'
            'msgstr "Foo"\n')

        msgs = self.lintrule.lint(self.vartok, linted_entry)
        assert len(msgs) == 1
        assert msgs[0].kind == 'warn'
        assert msgs[0].code == 'W302'
        assert msgs[0].msg == u'translated string is same as source string'


class TestMismatchedHTMLLintRule(LintRuleTestCase):
    lintrule = MismatchedHTMLLintRule()

    def test_fine(self):
        linted_entry = build_linted_entry(
            '#: foo/foo.py:5\n'
            'msgid "<b>Foo</b>"\n'
            'msgstr "<b>ARGH</b>"\n')

        msgs = self.lintrule.lint(self.vartok, linted_entry)
        assert len(msgs) == 0

    def test_fail(self):
        linted_entry = build_linted_entry(
            '#: foo/foo.py:5\n'
            'msgid "<b>Foo</b>"\n'
            'msgstr "<em>ARGH</em>"\n')

        msgs = self.lintrule.lint(self.vartok, linted_entry)
        assert len(msgs) == 1
        assert msgs[0].kind == 'warn'
        assert msgs[0].code == 'W303'
        assert msgs[0].msg == 'different html: "</b>" vs. "</em>"'

    def test_different_numbers(self):
        linted_entry = build_linted_entry(
            '#: foo/foo.py:5\n'
            'msgid "<b>Foo"\n'
            'msgstr "<b>ARGH</b>"\n')

        msgs = self.lintrule.lint(self.vartok, linted_entry)
        assert len(msgs) == 1
        assert msgs[0].kind == 'warn'
        assert msgs[0].code == 'W303'
        assert msgs[0].msg == 'different html: "<b>" vs. "</b>"'

        linted_entry = build_linted_entry(
            '#: foo/foo.py:5\n'
            'msgid "<b>Foo</b>"\n'
            'msgstr "<b>ARGH"\n')

        msgs = self.lintrule.lint(self.vartok, linted_entry)
        assert len(msgs) == 1
        assert msgs[0].kind == 'warn'
        assert msgs[0].code == 'W303'
        assert msgs[0].msg == 'different html: "</b>" vs. "<b>"'

    def test_invalid_html(self):
        linted_entry = build_linted_entry(
            u'#: foo/foo.py:5\n' +
            u'msgid "<a>Foo</a>"\n' +
            u'msgstr "<a>ARGH</\u0430>"\n')

        msgs = self.lintrule.lint(self.vartok, linted_entry)
        assert len(msgs) == 1
        assert msgs[0].kind == 'warn'
        assert msgs[0].code == 'W303'
        # HTMLParser doesn't recognize </\u0430> as a valid HTML tag,
        # so you end up with:
        #
        # [<html </a>>, <html <a>>]
        #
        # vs.
        #
        # [<html <a>>]
        assert msgs[0].msg == u'different html: "</a>" vs. "<a>"'


class TLRTestCase:
    vartok = VariableTokenizer(['python-format', 'python-brace-format'])


class TestHardToReadNamesTLR(TLRTestCase):
    lintrule = HardToReadNamesTLR()

    def test_hard_to_read_names(self):
        for c in ('o', 'O', '0', 'l', '1'):
            linted_entry = build_linted_entry(
                '#: foo/foo.py:5\n'
                'msgid "Foo: %(' + c + ')s"\n'
                'msgstr ""\n')

            msgs = self.lintrule.lint(self.vartok, linted_entry)
            assert len(msgs) == 1

            linted_entry = build_linted_entry(
                '#: foo/foo.py:5\n'
                'msgid "Foo: {' + c + '}"\n'
                'msgstr ""\n')

            msgs = self.lintrule.lint(self.vartok, linted_entry)
            assert len(msgs) == 1
        # FIXME: flesh out this test


class TestMultipleUnnamedVarsTLR(TLRTestCase):
    lintrule = MultipleUnnamedVarsTLR()

    def test_multi_vars_no_name(self):
        linted_entry = build_linted_entry(
            '#: foo/foo.py:5\n'
            'msgid "Foo: %s %s"\n'
            'msgstr ""\n')

        msgs = self.lintrule.lint(self.vartok, linted_entry)
        assert len(msgs) == 1

        linted_entry = build_linted_entry(
            '#: foo/foo.py:5\n'
            'msgid "Foo: {} {}"\n'
            'msgstr ""\n')

        msgs = self.lintrule.lint(self.vartok, linted_entry)
        assert len(msgs) == 1
        # FIXME: flesh out this test


class TestOneCharNamesTLR(TLRTestCase):
    lintrule = OneCharNamesTLR()

    def test_one_character_names(self):
        linted_entry = build_linted_entry(
            '#: foo/foo.py:5\n'
            'msgid "Foo: %(c)s"\n'
            'msgstr ""\n')

        msgs = self.lintrule.lint(self.vartok, linted_entry)
        assert len(msgs) == 1

        linted_entry = build_linted_entry(
            '#: foo/foo.py:5\n'
            'msgid "Foo: {c}"\n'
            'msgstr ""\n')

        msgs = self.lintrule.lint(self.vartok, linted_entry)
        assert len(msgs) == 1
        # FIXME: flesh out this test
