snapshot of https://github.com/zulip/zulip.git @ 48c69911c7dd4e03b20552eb3bd77ad8151c1357 (2026-09-15)
Some checks failed
Code scanning / CodeQL (push) Failing after 15s
Zulip production suite / Ubuntu 22.04 production build (push) Failing after 26s
Zulip production suite / Debian 12 production install with custom db name and user (push) Has been skipped
Zulip production suite / Ubuntu 22.04 production install and PostgreSQL upgrade with pgroonga (push) Has been skipped
Zulip production suite / Ubuntu 24.04 production install (push) Has been skipped
Zulip production suite / Ubuntu 26.04 production install (push) Has been skipped
Zulip production suite / Debian 13 production install (push) Has been skipped
Zulip production suite / 7.0 Version Upgrade (push) Has been skipped
Zulip production suite / 8.0 Version Upgrade (push) Has been skipped
Zulip production suite / 6.0 Version Upgrade (push) Has been skipped
Zulip production suite / 10.0 Version Upgrade (push) Has been skipped
Zulip production suite / 9.0 Version Upgrade (push) Has been skipped
Zulip production suite / 12.0 Version Upgrade (push) Has been skipped
Zulip production suite / 11.0 Version Upgrade (push) Has been skipped
GitHub Actions Security Analysis with zizmor / zizmor (push) Failing after 3s
Zulip CI / Debian 12 (Python 3.11, backend + documentation) (push) Failing after 26s
Zulip CI / Ubuntu 22.04 (Python 3.10, backend + frontend) (push) Failing after 3s
Zulip CI / Ubuntu 24.04 (Python 3.12, backend) (push) Failing after 25s
Zulip CI / Ubuntu 26.04 (Python 3.14, backend) (push) Failing after 25s
Zulip CI / Debian 13 (Python 3.13, backend) (push) Failing after 28s
Zulip production suite / Required jobs (push) Has been cancelled
Zulip CI / Required jobs (push) Has been cancelled
API Documentation Update Check / check-feature-level-updated (push) Has been cancelled
API Documentation Update Check / notify-if-api-docs-changed (push) Has been cancelled

This commit is contained in:
upstream-mirror
2026-09-15 15:05:30 +05:00
commit 64898b2c79
8999 changed files with 1757358 additions and 0 deletions

0
tools/tests/__init__.py Normal file
View File

View File

@@ -0,0 +1,145 @@
from unittest import TestCase
from bs4 import BeautifulSoup
from tools.lib.capitalization import check_capitalization, get_safe_text, is_capitalized
class GetSafeTextTestCase(TestCase):
def test_get_safe_text(self) -> None:
string = "Zulip Zulip. Zulip some text!"
safe_text = get_safe_text(string)
self.assertEqual(safe_text, "Zulip zulip. Zulip some text!")
string = "Zulip Zulip? Zulip some text!"
safe_text = get_safe_text(string)
self.assertEqual(safe_text, "Zulip zulip? Zulip some text!")
string = "Zulip Zulip! Zulip some text!"
safe_text = get_safe_text(string)
self.assertEqual(safe_text, "Zulip zulip! Zulip some text!")
string = "Zulip Zulip, Zulip some text!"
safe_text = get_safe_text(string)
self.assertEqual(safe_text, "Zulip zulip, zulip some text!")
string = "Not Ignored Phrase"
safe_text = get_safe_text(string)
self.assertEqual(safe_text, "Not Ignored Phrase")
string = "Not ignored phrase"
safe_text = get_safe_text(string)
self.assertEqual(safe_text, "Not ignored phrase")
string = ""
safe_text = get_safe_text(string)
self.assertEqual(safe_text, "")
string = """
<p>Please re-enter your password to confirm your identity.
(<a href="/accounts/password/reset/" target="_blank">Forgotten it?</a>)</p>
"""
safe_text = get_safe_text(string)
soup = BeautifulSoup(safe_text, "lxml")
rendered_text = " ".join(soup.text.split())
self.assertEqual(safe_text, rendered_text)
string = "Edited (__last_edit_timestr__)"
safe_text = get_safe_text(string)
self.assertEqual(safe_text, string)
class IsCapitalizedTestCase(TestCase):
def test_process_text(self) -> None:
string = "Zulip zulip. Zulip some text!"
capitalized = is_capitalized(string)
self.assertTrue(capitalized)
string = "Zulip zulip? Zulip some text!"
capitalized = is_capitalized(string)
self.assertTrue(capitalized)
string = "Zulip zulip! Zulip some text!"
capitalized = is_capitalized(string)
self.assertTrue(capitalized)
string = "Zulip zulip, Zulip some text!"
capitalized = is_capitalized(string)
self.assertTrue(capitalized)
string = "Some number 25mib"
capitalized = is_capitalized(string)
self.assertTrue(capitalized)
string = "Not Ignored Phrase"
capitalized = is_capitalized(string)
self.assertFalse(capitalized)
string = "Not ignored phrase"
capitalized = is_capitalized(string)
self.assertTrue(capitalized)
string = ""
capitalized = is_capitalized(string)
self.assertTrue(capitalized)
string = "Please re-enter your password to confirm your identity. (Forgotten it?)"
capitalized = is_capitalized(string)
self.assertTrue(capitalized)
string = "Edited (__last_edit_timestr__)"
capitalized = is_capitalized(string)
self.assertTrue(capitalized)
string = "Iphone application"
capitalized = is_capitalized(string)
self.assertTrue(capitalized)
string = "One two etc_ three"
capitalized = is_capitalized(string)
self.assertTrue(capitalized)
class CheckCapitalizationTestCase(TestCase):
def test_check_capitalization(self) -> None:
strings = [
"Zulip Zulip. Zulip some text!",
"Zulip Zulip? Zulip some text!",
"Zulip Zulip! Zulip some text!",
"Zulip Zulip, Zulip some text!",
"Not Ignored Phrase",
"Not ignored phrase",
"Some text with realm in it",
"Realm in capital case",
]
errored, ignored, banned = check_capitalization(strings)
self.assertEqual(errored, ["Not Ignored Phrase"])
self.assertEqual(
ignored,
sorted(
[
"Zulip Zulip. Zulip some text!",
"Zulip Zulip? Zulip some text!",
"Zulip Zulip! Zulip some text!",
"Zulip Zulip, Zulip some text!",
]
),
)
self.assertEqual(
banned,
sorted(
[
(
"realm found in 'Some text with realm in it'. "
"The term realm should not appear in user-facing "
"strings. Use organization instead."
),
(
"realm found in 'Realm in capital case'. "
"The term realm should not appear in user-facing "
"strings. Use organization instead."
),
]
),
)

View File

@@ -0,0 +1,83 @@
import time
from unittest import TestCase, mock
from scripts.lib.check_rabbitmq_queue import CRITICAL, OK, UNKNOWN, WARNING, analyze_queue_stats
class AnalyzeQueueStatsTests(TestCase):
def test_no_stats_available(self) -> None:
result = analyze_queue_stats("name", {}, 0)
self.assertEqual(result["status"], UNKNOWN)
def test_queue_stuck(self) -> None:
"""Last update > 5 minutes ago and there's events in the queue."""
result = analyze_queue_stats("name", {"update_time": time.time() - 301}, 100)
self.assertEqual(result["status"], CRITICAL)
self.assertIn("queue appears to be stuck", result["message"])
def test_queue_just_started(self) -> None:
"""
We just started processing a burst of events, and haven't processed enough
to log productivity statistics yet.
"""
result = analyze_queue_stats(
"name",
{
"update_time": time.time(),
"recent_average_consume_time": None,
},
10000,
)
self.assertEqual(result["status"], OK)
def test_queue_normal(self) -> None:
"""10000 events and each takes a second => it'll take a long time to empty."""
result = analyze_queue_stats(
"name",
{
"update_time": time.time(),
"queue_last_emptied_timestamp": time.time() - 10000,
"recent_average_consume_time": 1,
},
10000,
)
self.assertEqual(result["status"], CRITICAL)
self.assertIn("clearing the backlog", result["message"])
# If we're doing 10K/sec, it's OK.
result = analyze_queue_stats(
"name",
{
"update_time": time.time(),
"queue_last_emptied_timestamp": time.time() - 10000,
"recent_average_consume_time": 0.0001,
},
10000,
)
self.assertEqual(result["status"], OK)
# Verify logic around whether it'll take MAX_SECONDS_TO_CLEAR to clear queue.
with mock.patch.dict("scripts.lib.check_rabbitmq_queue.MAX_SECONDS_TO_CLEAR", {"name": 10}):
result = analyze_queue_stats(
"name",
{
"update_time": time.time(),
"queue_last_emptied_timestamp": time.time() - 10000,
"recent_average_consume_time": 1,
},
11,
)
self.assertEqual(result["status"], WARNING)
self.assertIn("clearing the backlog", result["message"])
result = analyze_queue_stats(
"name",
{
"update_time": time.time(),
"queue_last_emptied_timestamp": time.time() - 10000,
"recent_average_consume_time": 1,
},
9,
)
self.assertEqual(result["status"], OK)

View File

@@ -0,0 +1,66 @@
import os
import unittest
import tools.lib.template_parser
from tools.lib.html_branches import build_id_dict, get_tag_info, split_for_id_and_class
ZULIP_PATH = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
TEST_TEMPLATES_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "test_template_data")
class TestHtmlBranches(unittest.TestCase):
def test_get_tag_info(self) -> None:
html = """<p id="test" class="test1 test2">foo</p>"""
start_tag, text, end_tag = tools.lib.template_parser.tokenize(html)
start_tag_info = get_tag_info(start_tag)
end_tag_info = get_tag_info(end_tag)
self.assertEqual(start_tag_info.text(), "p.test1.test2#test")
self.assertEqual(end_tag_info.text(), "p")
self.assertEqual(text.s, "foo")
def test_build_id_dict(self) -> None:
templates = ["test_template1.html", "test_template2.html"]
templates = [os.path.join(TEST_TEMPLATES_DIR, fn) for fn in templates]
template_id_dict = build_id_dict(templates)
self.assertEqual(
set(template_id_dict.keys()), {"below_navbar", "hello_{{ message }}", "intro"}
)
self.assertEqual(
template_id_dict["hello_{{ message }}"],
[
f"Line 12:{ZULIP_PATH}/tools/tests/test_template_data/test_template1.html",
f"Line 12:{ZULIP_PATH}/tools/tests/test_template_data/test_template2.html",
],
)
self.assertEqual(
template_id_dict["intro"],
[
f"Line 10:{ZULIP_PATH}/tools/tests/test_template_data/test_template1.html",
f"Line 11:{ZULIP_PATH}/tools/tests/test_template_data/test_template1.html",
f"Line 11:{ZULIP_PATH}/tools/tests/test_template_data/test_template2.html",
],
)
self.assertEqual(
template_id_dict["below_navbar"],
[f"Line 10:{ZULIP_PATH}/tools/tests/test_template_data/test_template2.html"],
)
def test_split_for_id_and_class(self) -> None:
id1 = "{{ red|blue }}"
id2 = "search_box_{{ page }}"
class1 = "chat_box message"
class2 = "stream_{{ topic }}"
class3 = "foo {{ a|b|c }} bar"
self.assertEqual(split_for_id_and_class(id1), ["{{ red|blue }}"])
self.assertEqual(split_for_id_and_class(id2), ["search_box_{{ page }}"])
self.assertEqual(split_for_id_and_class(class1), ["chat_box", "message"])
self.assertEqual(split_for_id_and_class(class2), ["stream_{{ topic }}"])
self.assertEqual(split_for_id_and_class(class3), ["foo", "{{ a|b|c }}", "bar"])

View File

@@ -0,0 +1,305 @@
import unittest
from tools.lib.pretty_print import pretty_print_html
from tools.lib.template_parser import validate
# Note that GOOD_HTML isn't necessarily beautiful HTML. Apart
# from adjusting indentation, we mostly leave things alone to
# respect whatever line-wrapping styles were in place before.
BAD_HTML = """
<!-- test -->
<!DOCTYPE html>
<html>
<!-- test -->
<head>
<title>Test</title>
<meta charset="utf-8" />
<link rel="stylesheet" href="style.css" />
</head>
<body>
<div><p>Hello<br />world!</p></div>
<p>Goodbye<!-- test -->world!</p>
<table>
<tr>
<td>5</td>
</tr>
</table>
<pre>
print 'hello world'
</pre>
<div class = "foo"
id = "bar"
role = "whatever">{{ bla }}
</div>
</body>
</html>
<!-- test -->
"""
GOOD_HTML = """
<!-- test -->
<!DOCTYPE html>
<html>
<!-- test -->
<head>
<title>Test</title>
<meta charset="utf-8" />
<link rel="stylesheet" href="style.css" />
</head>
<body>
<div><p>Hello<br />world!</p></div>
<p>Goodbye<!-- test -->world!</p>
<table>
<tr>
<td>5</td>
</tr>
</table>
<pre>
print 'hello world'
</pre>
<div class = "foo"
id = "bar"
role = "whatever">{{ bla }}
</div>
</body>
</html>
<!-- test -->
"""
BAD_HTML1 = """
<html>
<body>
foobarfoobarfoo<b>bar</b>
</body>
</html>
"""
GOOD_HTML1 = """
<html>
<body>
foobarfoobarfoo<b>bar</b>
</body>
</html>
"""
BAD_HTML2 = """
<html>
<body>
{{# foobar area}}
foobarfoobarfoo<b>bar</b>
{{/ foobar}}
</body>
</html>
"""
GOOD_HTML2 = """
<html>
<body>
{{# foobar area}}
foobarfoobarfoo<b>bar</b>
{{/ foobar}}
</body>
</html>
"""
# The old GOOD_HTML3 test was flawed.
BAD_HTML4 = """
<div>
foo
<p>hello</p>
bar
</div>
"""
GOOD_HTML4 = """
<div>
foo
<p>hello</p>
bar
</div>
"""
BAD_HTML5 = """
<div>
foo
{{#if foobar}}
hello
{{else}}
bye
{{/if}}
bar
</div>
"""
GOOD_HTML5 = """
<div>
foo
{{#if foobar}}
hello
{{else}}
bye
{{/if}}
bar
</div>
"""
BAD_HTML6 = """
<div>
<p> <strong> <span class = "whatever">foobar </span> </strong></p>
</div>
"""
GOOD_HTML6 = """
<div>
<p> <strong> <span class = "whatever">foobar </span> </strong></p>
</div>
"""
BAD_HTML7 = """
<div class="foobar">
<input type="foobar" name="temp" value="{{dyn_name}}"
{{#unless invite_only}}checked="checked"{{/unless}} /> {{dyn_name}}
{{#if invite_only}}<i class="zulip-icon zulip-icon-lock"></i>{{/if}}
</div>
"""
GOOD_HTML7 = """
<div class="foobar">
<input type="foobar" name="temp" value="{{dyn_name}}"
{{#unless invite_only}}checked="checked"{{/unless}} /> {{dyn_name}}
{{#if invite_only}}<i class="zulip-icon zulip-icon-lock"></i>{{/if}}
</div>
"""
BAD_HTML8 = """
{{#each test}}
{{#with this}}
{{#if foobar}}
<div class="anything">{{{test_html}}}</div>
{{/if}}
{{#if foobar2}}
{{> teststuff}}
{{/if}}
{{/with}}
{{/each}}
"""
GOOD_HTML8 = """
{{#each test}}
{{#with this}}
{{#if foobar}}
<div class="anything">{{{test_html}}}</div>
{{/if}}
{{#if foobar2}}
{{> teststuff}}
{{/if}}
{{/with}}
{{/each}}
"""
BAD_HTML9 = """
<form id="foobar" class="whatever">
{{! <div class="anothertest"> }}
<input value="test" />
<button type="button"><i class="test"></i></button>
<button type="button"><i class="test"></i></button>
{{! </div> }}
<div class="test"></div>
</form>
"""
GOOD_HTML9 = """
<form id="foobar" class="whatever">
{{! <div class="anothertest"> }}
<input value="test" />
<button type="button"><i class="test"></i></button>
<button type="button"><i class="test"></i></button>
{{! </div> }}
<div class="test"></div>
</form>
"""
BAD_HTML10 = """
{% block portico_content %}
<div class="test">
<i class='test'></i> foobar
</div>
<div class="test1">
{% for row in data %}
<div class="test2">
{% for group in (row[0:2], row[2:4]) %}
<div class="test2">
</div>
{% endfor %}
</div>
{% endfor %}
</div>
{% endblock %}
"""
GOOD_HTML10 = """
{% block portico_content %}
<div class="test">
<i class='test'></i> foobar
</div>
<div class="test1">
{% for row in data %}
<div class="test2">
{% for group in (row[0:2], row[2:4]) %}
<div class="test2">
</div>
{% endfor %}
</div>
{% endfor %}
</div>
{% endblock %}
"""
BAD_HTML11 = """
<div class="test1">
<div class="test2">
foobar
<div class="test2">
</div>
</div>
</div>
"""
GOOD_HTML11 = """
<div class="test1">
<div class="test2">
foobar
<div class="test2">
</div>
</div>
</div>
"""
def pretty_print(html: str, template_format: str | None = None) -> str:
fn = "<test str>"
tokens = validate(fn=fn, text=html, template_format=template_format)
return pretty_print_html(tokens, fn=fn)
class TestPrettyPrinter(unittest.TestCase):
def compare(self, a: str, b: str) -> None:
self.assertEqual(a.split("\n"), b.split("\n"))
def test_pretty_print(self) -> None:
self.compare(pretty_print(GOOD_HTML), GOOD_HTML)
self.compare(pretty_print(BAD_HTML), GOOD_HTML)
self.compare(pretty_print(BAD_HTML1), GOOD_HTML1)
self.compare(pretty_print(BAD_HTML2, template_format="handlebars"), GOOD_HTML2)
self.compare(pretty_print(BAD_HTML4), GOOD_HTML4)
self.compare(pretty_print(BAD_HTML5, template_format="handlebars"), GOOD_HTML5)
self.compare(pretty_print(BAD_HTML6), GOOD_HTML6)
self.compare(pretty_print(BAD_HTML7, template_format="handlebars"), GOOD_HTML7)
self.compare(pretty_print(BAD_HTML8, template_format="handlebars"), GOOD_HTML8)
self.compare(pretty_print(BAD_HTML9, template_format="handlebars"), GOOD_HTML9)
self.compare(pretty_print(BAD_HTML10, template_format="django"), GOOD_HTML10)
self.compare(pretty_print(BAD_HTML11), GOOD_HTML11)

View File

@@ -0,0 +1,17 @@
<!-- test -->
<!DOCTYPE html>
<html>
<head>
<title>Test</title>
<meta charset="utf-8" />
<link rel="stylesheet" href="style.css" />
</head>
<body>
<div id="intro">
<p id="intro">
<span id="hello_{{ message }}">Hello world!!</span>
This is a test file for checking correct working of duplicate id detection module.
</p>
</div>
</body>
</html>

View File

@@ -0,0 +1,17 @@
<!-- test -->
<!DOCTYPE html>
<html>
<head>
<title>Test</title>
<meta charset="utf-8" />
<link rel="stylesheet" href="style.css" />
</head>
<body>
<div id="below_navbar">
<p id="intro">
<span id="hello_{{ message }}">Hello world!!</span>
This is a test file for checking correct working of duplicate id detection module.
</p>
</div>
</body>
</html>

View File

@@ -0,0 +1,430 @@
import sys
import unittest
try:
from tools.lib.template_parser import (
TemplateParserError,
is_django_block_tag,
tokenize,
validate,
)
except ImportError:
print("ERROR!!! You need to run this via tools/test-tools.")
sys.exit(1)
class ParserTest(unittest.TestCase):
def _assert_validate_error(
self,
error: str,
fn: str | None = None,
text: str | None = None,
template_format: str | None = None,
) -> None:
with self.assertRaisesRegex(TemplateParserError, error):
validate(fn=fn, text=text, template_format=template_format)
def test_is_django_block_tag(self) -> None:
self.assertTrue(is_django_block_tag("block"))
self.assertFalse(is_django_block_tag("not a django tag"))
def test_validate_vanilla_html(self) -> None:
"""
Verify that validate() does not raise errors for
well-formed HTML.
"""
my_html = """
<table>
<tr>
<td>foo</td>
</tr>
</table>"""
validate(text=my_html)
def test_validate_handlebars(self) -> None:
my_html = """
{{#with stream}}
<p>{{stream}}</p>
{{/with}}
"""
validate(text=my_html, template_format="handlebars")
def test_validate_handlebars_partial_block(self) -> None:
my_html = """
{{#> generic_thing }}
<p>hello!</p>
{{/generic_thing}}
"""
validate(text=my_html, template_format="handlebars")
def test_validate_bad_handlebars_partial_block(self) -> None:
my_html = """
{{#> generic_thing }}
<p>hello!</p>
{{# generic_thing}}
"""
self._assert_validate_error(
"Missing end tag for the token at row 4 13!", text=my_html, template_format="handlebars"
)
def test_validate_comment(self) -> None:
my_html = """
<!---
<h1>foo</h1>
-->"""
validate(text=my_html)
def test_validate_django(self) -> None:
my_html = """
{% include "some_other.html" %}
{% if foo %}
<p>bar</p>
{% endif %}
"""
validate(text=my_html, template_format="django")
my_html = """
{% block "content" %}
{% with className="class" %}
{% include 'foobar' %}
{% endwith %}
{% endblock %}
"""
validate(text=my_html)
def test_validate_no_start_tag(self) -> None:
my_html = """
foo</p>
"""
self._assert_validate_error("No start tag", text=my_html)
def test_validate_mismatched_tag(self) -> None:
my_html = """
<b>foo</i>
"""
self._assert_validate_error(r"Mismatched tags: \(b != i\)", text=my_html)
def test_validate_bad_indentation(self) -> None:
my_html = """
<p>
foo
</p>
"""
self._assert_validate_error("Indentation for start/end tags does not match.", text=my_html)
def test_validate_state_depth(self) -> None:
my_html = """
<b>
"""
self._assert_validate_error("Missing end tag", text=my_html)
def test_validate_incomplete_handlebars_tag_1(self) -> None:
my_html = """
{{# foo
"""
self._assert_validate_error(
'''Tag missing "}}" at line 2 col 13:"{{# foo
"''',
text=my_html,
template_format="handlebars",
)
def test_validate_incomplete_handlebars_tag_2(self) -> None:
my_html = """
{{# foo }
"""
self._assert_validate_error(
'Tag missing "}}" at line 2 col 13:"{{# foo }\n"',
text=my_html,
template_format="handlebars",
)
def test_validate_incomplete_handlebars_tag_3(self) -> None:
my_html = "{{# foo}"
self._assert_validate_error(
'Tag missing "}}" at line 1 col 1:"{{# foo}"',
text=my_html,
template_format="handlebars",
)
def test_validate_triple_stache_var_1(self) -> None:
my_html = """
{{{ foo}}
"""
self._assert_validate_error(
'Tag missing "}}}" at line 2 col 13:"{{{ foo}}\n"',
text=my_html,
template_format="handlebars",
)
def test_validate_triple_stache_var_2(self) -> None:
my_html = """
{{{ foo}~}
"""
self._assert_validate_error(
'Tag missing "}}}" at line 2 col 13:"{{{ foo}~}"',
text=my_html,
template_format="handlebars",
)
def test_validate_triple_stache_var_3(self) -> None:
my_html = """
{{{ foo }}}
"""
self._assert_validate_error(
"Unescaped variables in triple staches {{{ }}} must be suffixed with `_html`",
text=my_html,
template_format="handlebars",
)
def test_validate_triple_stache_var_4(self) -> None:
my_html = """
{{{ foo_html }~}}
"""
validate(text=my_html, template_format="handlebars")
def test_validate_triple_stache_var_5(self) -> None:
my_html = "{{{ foo_html}}}"
validate(text=my_html, template_format="handlebars")
def test_validate_triple_stache_var_6(self) -> None:
my_html = "{{~{ bar_html}}}"
validate(text=my_html, template_format="handlebars")
def test_validate_triple_stache_var_7(self) -> None:
my_html = "{{~{ bar_html}~}}"
validate(text=my_html, template_format="handlebars")
def test_validate_incomplete_django_tag_1(self) -> None:
my_html = """
{% foo
"""
self._assert_validate_error(
'''Tag missing "%}" at line 2 col 13:"{% foo
"''',
text=my_html,
template_format="django",
)
def test_validate_incomplete_django_tag_2(self) -> None:
my_html = """
{% foo %
"""
self._assert_validate_error(
'Tag missing "%}" at line 2 col 13:"{% foo %\n"',
text=my_html,
template_format="django",
)
def test_validate_incomplete_html_tag_1(self) -> None:
my_html = """
<b
"""
self._assert_validate_error(
'''Tag missing ">" at line 2 col 13:"<b
"''',
text=my_html,
)
def test_validate_incomplete_html_tag_2(self) -> None:
my_html = """
<a href="
"""
my_html1 = """
<a href=""
"""
self._assert_validate_error(
'''Tag missing ">" at line 2 col 13:"<a href=""
"''',
text=my_html1,
)
self._assert_validate_error(
'''Unbalanced quotes at line 2 col 13:"<a href="
"''',
text=my_html,
)
def test_validate_empty_html_tag(self) -> None:
my_html = """
< >
"""
self._assert_validate_error("Tag name missing", text=my_html)
def test_code_blocks(self) -> None:
# This is fine.
my_html = """
<code>
x = 5
y = x + 1
</code>"""
validate(text=my_html)
# This is also fine.
my_html = "<code>process_widgets()</code>"
validate(text=my_html)
# This is illegal.
my_html = """
<code>x =
5</code>
"""
self._assert_validate_error("Code tag is split across two lines.", text=my_html)
def test_anchor_blocks(self) -> None:
# This is allowed, although strange.
my_html = """
<a href="/some/url">
Click here
for more info.
</a>"""
validate(text=my_html)
# This is fine.
my_html = '<a href="/some/url">click here</a>'
validate(text=my_html)
# Even this is fine.
my_html = """
<a class="twitter-timeline" href="https://twitter.com/ZulipStatus"
data-widget-id="443457763394334720"
data-screen-name="ZulipStatus"
>@ZulipStatus on Twitter</a>.
"""
validate(text=my_html)
def test_validate_jinja2_whitespace_markers_1(self) -> None:
my_html = """
{% if foo -%}
this is foo
{% endif %}
"""
validate(
text=my_html,
template_format="django",
)
def test_validate_jinja2_whitespace_markers_2(self) -> None:
my_html = """
{% if foo %}
this is foo
{%- endif %}
"""
validate(
text=my_html,
template_format="django",
)
def test_validate_jinja2_whitespace_markers_3(self) -> None:
my_html = """
{% if foo %}
this is foo
{% endif -%}
"""
validate(
text=my_html,
template_format="django",
)
def test_validate_jinja2_whitespace_markers_4(self) -> None:
my_html = """
{%- if foo %}
this is foo
{% endif %}
"""
validate(
text=my_html,
template_format="django",
)
def test_validate_mismatch_jinja2_whitespace_markers_1(self) -> None:
my_html = """
{% if foo %}
this is foo
{%- if bar %}
"""
self._assert_validate_error(
"Missing end tag",
text=my_html,
template_format="django",
)
def test_validate_jinja2_whitespace_type2_markers(self) -> None:
my_html = """
{%- if foo -%}
this is foo
{% endif %}
"""
validate(
text=my_html,
template_format="django",
)
def test_tokenize(self) -> None:
tag = "<!DOCTYPE html>"
token = tokenize(tag)[0]
self.assertEqual(token.kind, "html_doctype")
tag = "<a>bla"
token = tokenize(tag)[0]
self.assertEqual(token.kind, "html_start")
self.assertEqual(token.tag, "a")
tag = "<br />bla"
token = tokenize(tag)[0]
self.assertEqual(token.kind, "html_singleton")
self.assertEqual(token.tag, "br")
tag = "<input>bla"
token = tokenize(tag)[0]
self.assertEqual(token.kind, "html_start") # We later mark this an error.
self.assertEqual(token.tag, "input")
tag = "<input />bla"
token = tokenize(tag)[0]
self.assertEqual(token.kind, "html_singleton")
self.assertEqual(token.tag, "input")
tag = "</a>bla"
token = tokenize(tag)[0]
self.assertEqual(token.kind, "html_end")
self.assertEqual(token.tag, "a")
tag = "{{#with foo}}bla"
token = tokenize(tag, template_format="handlebars")[0]
self.assertEqual(token.kind, "handlebars_start")
self.assertEqual(token.tag, "with")
tag = "{{/with}}bla"
token = tokenize(tag, template_format="handlebars")[0]
self.assertEqual(token.kind, "handlebars_end")
self.assertEqual(token.tag, "with")
tag = "{{#> compose_banner }}bla"
token = tokenize(tag, template_format="handlebars")[0]
self.assertEqual(token.kind, "handlebars_partial_block")
self.assertEqual(token.tag, "compose_banner")
tag = "{% if foo %}bla"
token = tokenize(tag, template_format="django")[0]
self.assertEqual(token.kind, "django_start")
self.assertEqual(token.tag, "if")
tag = "{% endif %}bla"
token = tokenize(tag, template_format="django")[0]
self.assertEqual(token.kind, "django_end")
self.assertEqual(token.tag, "if")
tag = "{% if foo -%}bla"
token = tokenize(tag, template_format="django")[0]
self.assertEqual(token.kind, "jinja2_whitespace_stripped_start")
self.assertEqual(token.tag, "if")
tag = "{%- endif %}bla"
token = tokenize(tag, template_format="django")[0]
self.assertEqual(token.kind, "jinja2_whitespace_stripped_end")
self.assertEqual(token.tag, "if")
tag = "{%- if foo -%}bla"
token = tokenize(tag, template_format="django")[0]
self.assertEqual(token.kind, "jinja2_whitespace_stripped_type2_start")
self.assertEqual(token.tag, "if")

View File

@@ -0,0 +1,68 @@
import os
from io import StringIO
from unittest import TestCase
from unittest.mock import patch
from typing_extensions import override
from zulint.custom_rules import RuleList
from tools.linter_lib.custom_check import non_py_rules, python_rules
ROOT_DIR = os.path.abspath(os.path.join(__file__, "..", "..", ".."))
CHECK_MESSAGE = "Fix the corresponding rule in `tools/linter_lib/custom_check.py`."
class TestRuleList(TestCase):
@override
def setUp(self) -> None:
all_rules = list(python_rules.rules)
for rule in non_py_rules:
all_rules.extend(rule.rules)
self.all_rules = all_rules
def test_paths_in_rules(self) -> None:
"""Verifies that the paths mentioned in linter rules actually exist"""
for rule in self.all_rules:
for path in rule.get("exclude", {}):
abs_path = os.path.abspath(os.path.join(ROOT_DIR, path))
self.assertTrue(
os.path.exists(abs_path),
f"'{path}' is neither an existing file, nor a directory. {CHECK_MESSAGE}",
)
for line_tuple in rule.get("exclude_line", {}):
path = line_tuple[0]
abs_path = os.path.abspath(os.path.join(ROOT_DIR, path))
self.assertTrue(
os.path.isfile(abs_path), f"The file '{path}' doesn't exist. {CHECK_MESSAGE}"
)
for path in rule.get("include_only", {}):
if not os.path.splitext(path)[1]:
self.assertTrue(
path.endswith("/"),
f"The path '{path}' should end with '/'. {CHECK_MESSAGE}",
)
def test_rule_patterns(self) -> None:
"""Verifies that the search regex specified in a custom rule actually matches
the expectation and doesn't throw false positives."""
for rule in self.all_rules:
pattern = rule["pattern"]
for line in rule.get("good_lines", []):
with patch("builtins.open", return_value=StringIO(line + "\n\n"), autospec=True):
self.assertFalse(
RuleList([], [rule]).custom_check_file("foo.bar", "baz", ""),
f"The pattern '{pattern}' matched the line '{line}' while it shouldn't.",
)
for line in rule.get("bad_lines", []):
for filename in rule.get("include_only", {"foo.bar"}):
with (
patch("builtins.open", return_value=StringIO(line + "\n\n"), autospec=True),
patch("builtins.print"),
):
self.assertTrue(
RuleList([], [rule]).custom_check_file(filename, "baz", ""),
f"The pattern '{pattern}' didn't match the line '{line}' while it should.",
)