Files
modulus/tests/test_formatting.py
T
khodowany 097cb61d2d
publish / publish (push) Successful in 11m55s
Formatting
2026-09-14 23:54:30 -05:00

166 lines
12 KiB
Python

import ast
import contextlib
import io
import json
import os
import pathlib
import subprocess
import tempfile
import sys
import unittest
from unittest.mock import patch
import modulus.cli
import modulus.config
import modulus.export
import modulus.format_layout
import modulus.formatting
class formatting_test(unittest.TestCase):
def formatted(self, source, suffix):
result = modulus.format_layout.format_source(source, suffix)
self.assertEqual(modulus.format_layout.format_source(result, suffix), result)
return result
def test_cpp_units_namespaces_and_include_groups(self):
source = '#include <vector>\n#include <string>\n\n#include "local.hpp"\n\nnamespace sample {\nint one() {\n\nint x=1;\n\nx++;\nreturn x;\n}\nint two() {return 2;}\n}\n'
result = self.formatted(source, ".cpp")
self.assertTrue(result.startswith(source.split("namespace")[0]))
self.assertIn("namespace sample {\n\nint one()", result)
self.assertIn("int x = 1;\n x++;\n return x;", result)
self.assertIn("}\n\nint two()", result)
self.assertTrue(result.endswith("}\n\n}\n"))
def test_cpp_no_column_limit_and_simple_lists(self):
name = "argument_" + "a" * 180
result = self.formatted(f"void function(\nint {name},\nint b) {{\n call(\n{name},\nb);\n}}\n", ".cpp")
self.assertIn(f"void function(int {name}, int b)", result)
self.assertIn(f"call({name}, b);", result)
def test_cpp_class_and_struct_members_have_no_blank_lines(self):
for kind in ("class", "struct"):
with self.subTest(kind=kind):
source = f'{kind} source_location {{\npublic:\n\n source_location() = default;\n\n source_location(source_location const&) = delete;\n\n int line() const;\n\n int value() const {{\n\n return line_;\n\n }}\n\n struct nested {{\n\n int value;\n\n }};\n\nprivate:\n\n int line_;\n\n}};\n\nstruct next {{ int value; }};\n'
result = self.formatted(source, ".hpp")
body = result.split(f"{kind} source_location {{\n", 1)[1].split("\n};", 1)[0]
self.assertNotIn("\n\n", body)
self.assertIn("};\n\nstruct next", result)
def test_cpp_class_keeps_literal_and_comment_blank_lines(self):
literal = 'R"raw(first\n\nsecond)raw"'
comment = '/* first\n\nsecond */'
source = f'struct example {{\n\n const char* text = {literal};\n\n {comment}\n\n int value;\n}};\n'
result = self.formatted(source, ".hpp")
self.assertIn(literal, result)
self.assertIn(comment, result)
self.assertNotIn("\n\n", result.replace(literal, "literal").replace(comment, "comment"))
def test_cpp_literals_macros_templates_and_lambdas(self):
literal = 'R"raw(first\n\n second\n)raw"'
source = f'template<class T>\nvoid test(T v) {{\n const char* s = {literal};\n\n visit(v, [](auto a) {{\n\nreturn a;\n}});\n}}\nBOOST_AUTO_TEST_CASE(example) {{\n\nCHECK(true);\n\n}}\n'
result = self.formatted(source, ".cpp")
self.assertIn(literal, result)
self.assertIn("BOOST_AUTO_TEST_CASE(example) {\n CHECK(true);\n}", result)
self.assertIn("[](auto a) {\n", result)
def test_python_ast_literals_decorators_and_nested_units(self):
source = 'import os\n\nimport pathlib\n\n@decorator\ndef outer(\n first,\n second,\n):\n text = """first\n\n second\n"""\n\n first += 1\n def nested():\n return 1\n return call(\n first,\n second,\n )\n'
result = self.formatted(source, ".py")
self.assertEqual(ast.dump(ast.parse(source)), ast.dump(ast.parse(result)))
self.assertIn('"""first\n\n second\n"""', result)
self.assertIn("@decorator\ndef outer(first, second):", result)
self.assertIn("first += 1\n\n def nested()", result)
self.assertIn("return 1\n\n return call(first, second)", result)
self.assertTrue(result.startswith("import os\n\nimport pathlib\n"))
def test_python_no_column_limit_and_structured_literals(self):
name = "a" * 250
source = f"def test({name},\n other):\n return call({name},\nother)\n"
result = self.formatted(source, ".py")
self.assertIn(f"def test({name}, other):", result)
data = 'def test():\n return call({\n "a": 1,\n "b": 2,\n })\n'
self.assertIn('{\n', self.formatted(data, ".py"))
def test_python_classes_keep_members_together(self):
source = 'class Example:\n\n """first\n\nsecond"""\n\n field = 1\n\n @staticmethod\n def value():\n\n return 1\n\n class Nested:\n\n field = 2\n\n async def other(self):\n\n return 2\n\nclass Next:\n\n field = 3\n'
result = self.formatted(source, ".py")
self.assertEqual(ast.dump(ast.parse(source)), ast.dump(ast.parse(result)))
self.assertIn('"""first\n\nsecond"""', result)
self.assertNotIn("\n\n", result.split("\n\nclass Next", 1)[0].replace('"""first\n\nsecond"""', 'docstring'))
self.assertIn("return 2\n\nclass Next:\n field = 3", result)
def test_python_tuple_continuations_use_two_spaces(self):
source = 'runtime_ignore_patterns = (\n ".git",\n ".tmp",\n "__pycache__",\n)\n'
expected = 'runtime_ignore_patterns = (\n ".git",\n ".tmp",\n "__pycache__",\n)\n'
self.assertEqual(self.formatted(source, ".py"), expected)
def test_python_nested_collections_use_two_spaces_per_opening_line(self):
source = 'def example():\n result = build({\n "items": [\n (\n 1,\n 2,\n ),\n ],\n "names": {\n "a",\n "b",\n },\n })\n return result\n'
expected = 'def example():\n result = build({\n "items": [\n (\n 1,\n 2,\n ),\n ],\n "names": {\n "a",\n "b",\n },\n })\n return result\n'
self.assertEqual(self.formatted(source, ".py"), expected)
self.assertEqual(self.formatted("values = [[\n 1,\n 2,\n]]\n", ".py"), "values = [[\n 1,\n 2,\n]]\n")
def test_python_continued_imports_headers_and_comments(self):
source = 'from somewhere import (\n first,\n second,\n)\n\n@decorator(\n first, # comment\n second,\n)\ndef example(\n first, # comment\n second,\n):\n if (\n first # condition\n and second\n ):\n return first\n'
expected = 'from somewhere import (\n first,\n second,\n)\n\n@decorator(\n first, # comment\n second,\n)\ndef example(\n first, # comment\n second,\n):\n if (\n first # condition\n and second\n ):\n return first\n'
self.assertEqual(self.formatted(source, ".py"), expected)
def test_python_explicit_continuations_use_two_spaces(self):
source = 'def example():\n with first() as one, \\\n second() as two:\n return one + \\\n two\n'
expected = 'def example():\n with first() as one, \\\n second() as two:\n return one + \\\n two\n'
self.assertEqual(self.formatted(source, ".py"), expected)
def test_python_continuations_preserve_multiline_literal_contents(self):
source = 'def example():\n return [\n # first value\n f"{call({1: 2})}",\n """first\n second\n\n third""",\n f"""start {name}\n {value}\n\n end""",\n "next",\n ]\n'
expected = 'def example():\n return [\n # first value\n f"{call({1: 2})}",\n """first\n second\n\n third""",\n f"""start {name}\n {value}\n\n end""",\n "next",\n ]\n'
self.assertEqual(self.formatted(source, ".py"), expected)
def test_python_adjacent_literals_are_continuations_not_literal_contents(self):
source = 'def example():\n text = (\n "first"\n "second"\n f"{value}"\n f"{other}"\n )\n return text\n'
expected = 'def example():\n text = (\n "first"\n "second"\n f"{value}"\n f"{other}"\n )\n return text\n'
self.assertEqual(self.formatted(source, ".py"), expected)
def test_aliases_and_portability_exception(self):
cpp = "namespace std { using bool_t = decltype(true); }\nusing integer = int;\nnamespace fs = std::filesystem;\ntypedef int count;\n"
self.assertEqual(len(list(modulus.format_layout.alias_diagnostics(cpp, ".hpp"))), 4)
self.assertEqual(len(list(modulus.format_layout.alias_diagnostics(cpp, ".hpp", True))), 3)
python = "import pathlib as p\nfrom typing import Literal\nRole = Literal['a', 'b']\n"
self.assertEqual(len(list(modulus.format_layout.alias_diagnostics(python, ".py"))), 2)
def test_stdin_failure_has_no_output_and_never_builds_image(self):
with tempfile.TemporaryDirectory() as directory:
root = pathlib.Path(directory)
context = modulus.cli.build_context(root, modulus.config.normalize({"package": {"name": "sample"}}))
context.docker_marker = root / "not-in-container"
output, errors = io.StringIO(), io.StringIO()
with patch.dict(os.environ, {"MODULUS_DIRECT": "1"}), patch("sys.stdin", io.StringIO("def broken(")), contextlib.redirect_stdout(output), contextlib.redirect_stderr(errors):
self.assertEqual(modulus.formatting.launch(context, ["--stdin-filename", "test.py"]), 1)
self.assertEqual(output.getvalue(), "")
with patch.dict(os.environ, {"MODULUS_DIRECT": "0"}), patch.object(modulus.clangd, "current_image", return_value=None), patch.object(modulus.cli, "ensure_build_image") as build, contextlib.redirect_stderr(errors):
self.assertEqual(modulus.formatting.launch(context, ["--check"]), 1)
build.assert_not_called()
def test_discovery_excludes_generated_and_symlinks(self):
with tempfile.TemporaryDirectory() as directory:
root = pathlib.Path(directory)
subprocess.run(["git", "init", "-q", str(root)], check=True)
for name in ["keep.cpp", "skip.cpp", "build/generated.cpp", "vendor/library.cpp"]:
path = root / name
path.parent.mkdir(parents=True, exist_ok=True)
path.touch()
(root / "link.cpp").symlink_to(root / "keep.cpp")
self.assertEqual(modulus.formatting.source_files(root, [], ["skip.cpp"]), [root / "keep.cpp"])
def test_nested_lists_join_in_one_pass_without_changing_string_spaces(self):
cpp = 'void example() { outer(\n inner(\n 1,\n 2),\n "a b"); }\n'
self.assertIn('outer(inner(1, 2), "a b");', self.formatted(cpp, ".cpp"))
python = 'def example():\n return outer(\n inner(\n 1,\n 2),\n "a b")\n'
self.assertIn('outer(inner(1, 2), "a b")', self.formatted(python, ".py"))
def test_multiline_fstrings_preserve_blank_lines_on_new_python_tokenizers(self):
source = 'def example(name):\n return f"""first {name}\n\nsecond\n\nthird"""\n'
self.assertEqual(self.formatted(source, ".py"), source)
def test_alias_check_distinguishes_module_aliases_from_saved_callbacks(self):
source = 'import modulus.cli\nsaved = modulus.cli.docker_run\nconfig = modulus.cli\nItems = list[int]\n'
self.assertEqual([line for line, _ in modulus.format_layout.alias_diagnostics(source, ".py")], [3, 4])
def test_exported_stdin_formatter_needs_no_compile_database(self):
with tempfile.TemporaryDirectory() as directory:
root = pathlib.Path(directory)
(root / "modulus.json").write_text(json.dumps({"package": {"name": "sample"}}))
modulus.export.sync_project(root, pathlib.Path(__file__).resolve().parent.parent)
result = subprocess.run([sys.executable, str(root / "build/build_tools/format.py"), "--stdin-filename", "sample.py"], cwd=root, env=dict(os.environ, MODULUS_DIRECT="1", PYTHONDONTWRITEBYTECODE="1"), input="def sample():\n values = (\n 1,\n 2,\n )\n return values\n", text=True, capture_output=True)
self.assertEqual(result.returncode, 0, result.stderr)
self.assertEqual(result.stdout, "def sample():\n values = (\n 1,\n 2,\n )\n return values\n")
self.assertEqual(result.stderr, "")
@unittest.skipUnless(os.environ.get("MODULUS_FORMAT_REPO") and os.environ.get("MODULUS_CONFORM_PATH"), "set MODULUS_FORMAT_REPO and MODULUS_CONFORM_PATH for the Neovim integration test")
def test_neovim_container_formatting(self):
root = pathlib.Path(__file__).resolve().parent.parent
env = dict(os.environ, MODULUS_FORMAT_HELPER=str(root / "tools/nvim/modulus.lua"))
result = subprocess.run(["nvim", "--headless", "-u", "NONE", "-l", str(root / "tests/nvim_format.lua")], env=env, text=True, capture_output=True, timeout=60)
self.assertEqual(result.returncode, 0, result.stdout + result.stderr)
self.assertIn("MODULUS_FORMAT_OK", result.stdout + result.stderr)
if __name__ == "__main__":
unittest.main()