mirror of
https://hubproxy.babadafafafafa.cn/https://github.com/telegramdesktop/tdesktop
synced 2026-09-20 08:03:45 +08:00
Added tests of house rules hook.
This commit is contained in:
@@ -9,7 +9,8 @@ run-clang-tidy -p out/clang-tidy -j 10 -quiet <files>
|
|||||||
Both runners ship with LLVM, under `share/clang`. Needs clang-tidy 20 or
|
Both runners ship with LLVM, under `share/clang`. Needs clang-tidy 20 or
|
||||||
newer: an older one ignores `CustomFunctions` without a word.
|
newer: an older one ignores `CustomFunctions` without a word.
|
||||||
|
|
||||||
The commit hook, once per clone, submodules included:
|
The commit hook, once per clone, submodules included; its tests run with
|
||||||
|
`python3 tools/rules/test_hook.py`:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
for repo in . $(git config -f .gitmodules --get-regexp path \
|
for repo in . $(git config -f .gitmodules --get-regexp path \
|
||||||
|
|||||||
258
tools/rules/test_hook.py
Normal file
258
tools/rules/test_hook.py
Normal file
@@ -0,0 +1,258 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Tests of hook.py: python3 tools/rules/test_hook.py"""
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
import tempfile
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
HOOK = os.path.join(HERE, "hook.py")
|
||||||
|
LICENSE = "/*\nThis file is part of Telegram Desktop,\nthe official desktop"\
|
||||||
|
" application for the Telegram messaging service.\n\nFor license and"\
|
||||||
|
" copyright information please follow this link:\n"\
|
||||||
|
"https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL\n*/\n"
|
||||||
|
|
||||||
|
|
||||||
|
def edit(new, old="x", path="/tmp/rules/foo.cpp"):
|
||||||
|
return run({"tool_name": "Edit", "tool_input": {
|
||||||
|
"file_path": path, "old_string": old, "new_string": new}})
|
||||||
|
|
||||||
|
|
||||||
|
def write(content, path):
|
||||||
|
return run({"tool_name": "Write", "tool_input": {
|
||||||
|
"file_path": path, "content": content}})
|
||||||
|
|
||||||
|
|
||||||
|
def run(payload):
|
||||||
|
done = subprocess.run([sys.executable, HOOK], input=json.dumps(payload),
|
||||||
|
capture_output=True, text=True)
|
||||||
|
return done.returncode, done.stderr
|
||||||
|
|
||||||
|
|
||||||
|
class EditHook(unittest.TestCase):
|
||||||
|
def test_one_line_comment_passes(self):
|
||||||
|
self.assertEqual(edit("// the server rejects more\n++count;\n")[0], 0)
|
||||||
|
|
||||||
|
def test_two_line_comment_fails(self):
|
||||||
|
code, why = edit("// the server rejects more,\n// so we stop\nf();\n")
|
||||||
|
self.assertEqual(code, 2)
|
||||||
|
self.assertIn("2-line comment: a comment is one line", why)
|
||||||
|
self.assertIn("// WHY:", why)
|
||||||
|
|
||||||
|
def test_why_block_of_three_passes(self):
|
||||||
|
self.assertEqual(edit("// WHY: a\n// b\n// c\nf();\n")[0], 0)
|
||||||
|
|
||||||
|
def test_why_block_of_four_fails(self):
|
||||||
|
code, why = edit("// WHY: a\n// b\n// c\n// d\nf();\n")
|
||||||
|
self.assertEqual(code, 2)
|
||||||
|
self.assertIn("4-line comment: a // WHY: block runs 3 lines", why)
|
||||||
|
|
||||||
|
def test_why_marker_must_open_the_block(self):
|
||||||
|
self.assertEqual(edit("// a\n// WHY: b\nf();\n")[0], 2)
|
||||||
|
|
||||||
|
def test_commented_out_code_is_a_comment(self):
|
||||||
|
self.assertEqual(edit("//foo();\n//bar();\n//baz();\n")[0], 2)
|
||||||
|
|
||||||
|
def test_block_comment_over_two_lines_fails(self):
|
||||||
|
code, _ = edit("/* the server\n rejects more */\nf();\n")
|
||||||
|
self.assertEqual(code, 2)
|
||||||
|
|
||||||
|
def test_one_line_block_comment_passes(self):
|
||||||
|
self.assertEqual(edit("/* rejected */ f();\n")[0], 0)
|
||||||
|
|
||||||
|
def test_trailing_comment_opening_a_block_is_one_comment(self):
|
||||||
|
code, _ = edit("f(); /* the server\nrejects more */\n")
|
||||||
|
self.assertEqual(code, 2)
|
||||||
|
|
||||||
|
def test_edit_has_no_budget(self):
|
||||||
|
many = "".join(f"a{i} = {i}; // set a{i}\n" for i in range(6))
|
||||||
|
self.assertEqual(edit(many)[0], 0)
|
||||||
|
self.assertEqual(edit("// one\nf();\n// two\ng();\n")[0], 0)
|
||||||
|
|
||||||
|
def test_moved_block_is_not_new(self):
|
||||||
|
block = "// the server\n// rejects more\n"
|
||||||
|
self.assertEqual(edit(block + "g();\n", old=block + "f();\n")[0], 0)
|
||||||
|
|
||||||
|
def test_harness_is_exempt_from_comment_rules(self):
|
||||||
|
path = "/tmp/Telegram/SourceFiles/test/harness.cpp"
|
||||||
|
self.assertEqual(edit("// a\n// b\n// c\n// d\n", path=path)[0], 0)
|
||||||
|
self.assertEqual(edit("qMin(a, b);\n", path=path)[0], 2)
|
||||||
|
|
||||||
|
def test_banned_function_fails_with_replacement(self):
|
||||||
|
code, why = edit("w = qMin(a, b);\n")
|
||||||
|
self.assertEqual(code, 2)
|
||||||
|
self.assertIn("qMin is banned in this repository -- write std::min",
|
||||||
|
why)
|
||||||
|
self.assertIn("std::clamp", edit("w = qBound(0, v, 9);\n")[1])
|
||||||
|
self.assertIn("std::abs", edit("w = qAbs (v);\n")[1])
|
||||||
|
|
||||||
|
def test_banned_name_inside_identifier_passes(self):
|
||||||
|
self.assertEqual(edit("w = myqMin(a, b) + qMinimum(c);\n")[0], 0)
|
||||||
|
|
||||||
|
def test_banned_name_in_comment_or_string_passes(self):
|
||||||
|
self.assertEqual(edit("// qMin(a, b) was here\n")[0], 0)
|
||||||
|
self.assertEqual(edit('log("qMin(a, b)");\n')[0], 0)
|
||||||
|
|
||||||
|
def test_banned_call_after_url_string_still_fails(self):
|
||||||
|
self.assertEqual(edit('u = "http://x"; v = qMin(a, b);\n')[0], 2)
|
||||||
|
|
||||||
|
def test_untouched_banned_call_on_edited_line_still_fails(self):
|
||||||
|
self.assertEqual(
|
||||||
|
edit("w = qMin(a, b) + 2;\n", old="w = qMin(a, b) + 1;\n")[0], 2)
|
||||||
|
|
||||||
|
def test_non_source_paths_are_ignored(self):
|
||||||
|
self.assertEqual(edit("// a\n// b\n// c\n", path="/tmp/x.py")[0], 0)
|
||||||
|
self.assertEqual(edit("qMin(a, b)\n", path="/tmp/x.style")[0], 0)
|
||||||
|
|
||||||
|
def test_malformed_payload_passes(self):
|
||||||
|
done = subprocess.run([sys.executable, HOOK], input="not json",
|
||||||
|
capture_output=True, text=True)
|
||||||
|
self.assertEqual(done.returncode, 0)
|
||||||
|
|
||||||
|
def test_write_of_new_file_with_license_passes(self):
|
||||||
|
with tempfile.TemporaryDirectory() as folder:
|
||||||
|
path = os.path.join(folder, "new.cpp")
|
||||||
|
code, why = write(LICENSE + "#include \"a.h\"\n\nf();\n", path)
|
||||||
|
self.assertEqual(code, 0, why)
|
||||||
|
code, why = write(LICENSE + "// a\n// b\nf();\n", path)
|
||||||
|
self.assertEqual(code, 2)
|
||||||
|
code, why = write(LICENSE + "// a\nf();\n", path)
|
||||||
|
self.assertEqual(code, 0, why)
|
||||||
|
|
||||||
|
def test_multi_edit_shape_is_read(self):
|
||||||
|
code, why = run({"tool_name": "Edit", "tool_input": {
|
||||||
|
"file_path": "/tmp/rules/foo.cpp",
|
||||||
|
"edits": [{"old_string": "x", "new_string": "qMin(a, b);\n"}]}})
|
||||||
|
self.assertEqual(code, 2)
|
||||||
|
|
||||||
|
|
||||||
|
class Repository:
|
||||||
|
def __init__(self):
|
||||||
|
self.folder = tempfile.TemporaryDirectory()
|
||||||
|
self.path = self.folder.name
|
||||||
|
self.git("init", "-q")
|
||||||
|
self.git("config", "user.email", "t@t")
|
||||||
|
self.git("config", "user.name", "t")
|
||||||
|
|
||||||
|
def git(self, *arguments):
|
||||||
|
return subprocess.run(["git", *arguments], cwd=self.path, check=True,
|
||||||
|
capture_output=True, text=True).stdout
|
||||||
|
|
||||||
|
def put(self, files):
|
||||||
|
for name, content in files.items():
|
||||||
|
full = os.path.join(self.path, name)
|
||||||
|
os.makedirs(os.path.dirname(full), exist_ok=True)
|
||||||
|
with open(full, "w", encoding="utf-8") as stream:
|
||||||
|
stream.write(content)
|
||||||
|
self.git("add", name)
|
||||||
|
|
||||||
|
def commit(self, files):
|
||||||
|
self.put(files)
|
||||||
|
self.git("commit", "-q", "-m", "base")
|
||||||
|
|
||||||
|
def staged(self):
|
||||||
|
done = subprocess.run([sys.executable, HOOK, "--staged"],
|
||||||
|
cwd=self.path, capture_output=True, text=True)
|
||||||
|
return done.returncode, done.stderr
|
||||||
|
|
||||||
|
|
||||||
|
class StagedHook(unittest.TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
self.repo = Repository()
|
||||||
|
self.repo.commit({"a.cpp": "int a() {\n\treturn 1;\n}\n"})
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
self.repo.folder.cleanup()
|
||||||
|
|
||||||
|
def stage(self, files):
|
||||||
|
self.repo.put(files)
|
||||||
|
return self.repo.staged()
|
||||||
|
|
||||||
|
def test_two_plain_lines_pass(self):
|
||||||
|
code, why = self.stage({"a.cpp": "// one\nint a() {\n"
|
||||||
|
"\treturn 1; // two\n}\n"})
|
||||||
|
self.assertEqual(code, 0, why)
|
||||||
|
|
||||||
|
def test_third_plain_line_fails(self):
|
||||||
|
code, why = self.stage({"a.cpp": "// one\nint a() {\n"
|
||||||
|
"\t// two\n\treturn 1;\n}\n// three\n"})
|
||||||
|
self.assertEqual(code, 1)
|
||||||
|
self.assertIn("adds 3 comment lines; 2 is the budget", why)
|
||||||
|
|
||||||
|
def test_trailing_comments_count(self):
|
||||||
|
code, why = self.stage({"a.cpp": "int a() {\n\tint b = 1; // b\n"
|
||||||
|
"\tint c = 2; // c\n\treturn b + c; // sum\n}\n"})
|
||||||
|
self.assertEqual(code, 1)
|
||||||
|
self.assertIn("adds 3 comment lines", why)
|
||||||
|
|
||||||
|
def test_closers_and_directives_carry_free_labels(self):
|
||||||
|
code, why = self.stage({"a.cpp": "#ifndef A\n#define A\n"
|
||||||
|
"namespace A {\nint a() {\n\treturn 1; // one\n}\n"
|
||||||
|
"} // namespace A\n#endif // A\n// two\n"})
|
||||||
|
self.assertEqual(code, 0, why)
|
||||||
|
|
||||||
|
def test_moved_comment_is_free(self):
|
||||||
|
self.repo.commit({"a.cpp": "// one\n// two\n// three\nint a() {\n"
|
||||||
|
"\treturn 1;\n}\n"})
|
||||||
|
code, why = self.stage({
|
||||||
|
"a.cpp": "int a() {\n\treturn 1;\n}\n",
|
||||||
|
"b.cpp": "// one\n// two\n// three\nint b() {\n\treturn 2;\n}\n"})
|
||||||
|
self.assertEqual(code, 0, why)
|
||||||
|
|
||||||
|
def test_edited_line_keeps_its_trailing_comment(self):
|
||||||
|
self.repo.commit({"a.cpp": "int a() {\n\treturn 1; // the server\n"
|
||||||
|
"}\n"})
|
||||||
|
code, why = self.stage({"a.cpp": "int a() {\n"
|
||||||
|
"\treturn 2; // the server\n}\n// one\n// two\n"})
|
||||||
|
self.assertEqual(code, 1)
|
||||||
|
self.assertIn("a.cpp:4: a comment is one line", why)
|
||||||
|
self.assertNotIn("budget", why)
|
||||||
|
|
||||||
|
def test_one_why_block_on_top_of_budget(self):
|
||||||
|
code, why = self.stage({"a.cpp": "// one\n\n// two\n\n// three\n\n"
|
||||||
|
"// WHY: a\n// b\n// c\nint a() {\n\treturn 1;\n}\n"})
|
||||||
|
self.assertEqual(code, 1)
|
||||||
|
self.assertIn("adds 3 comment lines", why)
|
||||||
|
code, why = self.stage({"a.cpp": "// one\n\n// two\n\n// WHY: a\n"
|
||||||
|
"// b\n// c\nint a() {\n\treturn 1;\n}\n"})
|
||||||
|
self.assertEqual(code, 0, why)
|
||||||
|
|
||||||
|
def test_second_why_block_fails(self):
|
||||||
|
code, why = self.stage({"a.cpp": "// WHY: a\n// b\nint a() {\n"
|
||||||
|
"\treturn 1;\n}\n// WHY: c\n// d\n"})
|
||||||
|
self.assertEqual(code, 1)
|
||||||
|
self.assertIn("takes 2 // WHY: exceptions; 1 is the budget", why)
|
||||||
|
|
||||||
|
def test_long_block_is_named_by_line(self):
|
||||||
|
code, why = self.stage({"a.cpp": "int a() {\n\treturn 1;\n}\n\n"
|
||||||
|
"// one\n// two\n"})
|
||||||
|
self.assertEqual(code, 1)
|
||||||
|
self.assertIn("a.cpp:5: a comment is one line", why)
|
||||||
|
|
||||||
|
def test_banned_call_is_named_by_line(self):
|
||||||
|
code, why = self.stage({"a.cpp": "int a() {\n\treturn qMax(1, 2);\n"
|
||||||
|
"}\n"})
|
||||||
|
self.assertEqual(code, 1)
|
||||||
|
self.assertIn("a.cpp:2: qMax is banned -- write std::max", why)
|
||||||
|
|
||||||
|
def test_harness_is_exempt_from_comment_rules(self):
|
||||||
|
code, why = self.stage({"Telegram/SourceFiles/test/t.cpp":
|
||||||
|
"// a\n// b\n// c\n// d\nint t() {\n\treturn 1;\n}\n"})
|
||||||
|
self.assertEqual(code, 0, why)
|
||||||
|
|
||||||
|
def test_other_files_are_ignored(self):
|
||||||
|
code, why = self.stage({
|
||||||
|
"t.py": "# a\n# b\n// c\n// d\nqMin(1, 2)\n",
|
||||||
|
"a.style": "// a\n// b\nx: 1px;\n"})
|
||||||
|
self.assertEqual(code, 0, why)
|
||||||
|
|
||||||
|
def test_license_header_of_new_file_is_free(self):
|
||||||
|
code, why = self.stage({"b.cpp": LICENSE + "int b() {\n"
|
||||||
|
"\treturn 2;\n}\n"})
|
||||||
|
self.assertEqual(code, 0, why)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
||||||
Reference in New Issue
Block a user