#!/usr/bin/env python3 """Tests for weaseljson_schemagen.py.""" import json import os import re import shutil import subprocess import sys import tempfile import textwrap import unittest SCRIPT = os.path.join(os.path.dirname(__file__), "weaseljson_schemagen.py") class SchemagenEnumTest(unittest.TestCase): def run_schemagen(self, schema, args=None): """Run schemagen on a schema dict. Returns (returncode, stdout, stderr).""" with tempfile.NamedTemporaryFile("w", suffix=".json", delete=False) as fp: json.dump(schema, fp) schema_path = fp.name try: cmd = [sys.executable, SCRIPT, schema_path] if args: cmd.extend(args) result = subprocess.run(cmd, capture_output=True, text=True, check=False) return result.returncode, result.stdout, result.stderr finally: os.unlink(schema_path) def test_colliding_enum_values_deduplicate(self): schema = { "type": "object", "additionalProperties": False, "properties": {"role": {"enum": ["foo-bar", "foo_bar"]}}, } rc, stdout, stderr = self.run_schemagen(schema) self.assertEqual(rc, 0, msg=stderr) self.assertIn("enum class Role : int { foo_bar, foo_bar_1 };", stdout) self.assertIn( 'static constexpr const char *Role_names[] = { "foo-bar", "foo_bar" };', stdout, ) def test_distinct_enum_values_generate(self): schema = { "type": "object", "additionalProperties": False, "properties": {"role": {"enum": ["admin", "user", "guest"]}}, } rc, stdout, stderr = self.run_schemagen(schema) self.assertEqual(rc, 0, msg=stderr) self.assertIn("enum class Role : int { admin, user, guest };", stdout) class SchemagenKeywordTest(unittest.TestCase): def run_schemagen(self, schema, args=None): """Run schemagen on a schema dict. Returns (returncode, stdout, stderr).""" with tempfile.NamedTemporaryFile("w", suffix=".json", delete=False) as fp: json.dump(schema, fp) schema_path = fp.name try: cmd = [sys.executable, SCRIPT, schema_path] if args: cmd.extend(args) result = subprocess.run(cmd, capture_output=True, text=True, check=False) return result.returncode, result.stdout, result.stderr finally: os.unlink(schema_path) def test_cpp20_keywords_are_sanitized(self): """C++20 keywords used as JSON property names must be suffixed.""" keywords = [ "concept", "consteval", "constinit", "co_await", "co_return", "co_yield", "requires", "module", "import", ] schema = {"type": "object", "additionalProperties": False, "properties": {}} for kw in keywords: schema["properties"][kw] = {"type": "string"} rc, stdout, stderr = self.run_schemagen(schema) self.assertEqual(rc, 0, msg=stderr) for kw in keywords: self.assertIn(f"std::optional {kw}_;", stdout) self.assertNotIn(f"std::optional {kw};", stdout) class SchemagenCollisionTest(unittest.TestCase): """Regression tests for issue #21: generated Root alias / Kind enum collisions.""" def setUp(self): self.repo_root = os.path.dirname( os.path.dirname(os.path.dirname(os.path.abspath(__file__))) ) self.include_dir = os.path.join(self.repo_root, "include") self.compiler = shutil.which("c++") def generate_and_compile(self, schema): """Run schemagen on schema and syntax-check the resulting header.""" with tempfile.TemporaryDirectory() as tmpdir: schema_path = os.path.join(tmpdir, "schema.json") with open(schema_path, "w") as fp: json.dump(schema, fp) header_path = os.path.join(tmpdir, "gen.h") cmd = [ sys.executable, SCRIPT, schema_path, "-o", header_path, "--namespace", "test_schema", ] result = subprocess.run(cmd, capture_output=True, text=True, check=False) self.assertEqual(result.returncode, 0, msg=result.stderr) if self.compiler: cpp_path = os.path.join(tmpdir, "test.cpp") with open(cpp_path, "w") as fp: fp.write( '#include "gen.h"\n' "int main() {\n" " test_schema::RootBuilder b;\n" " test_schema::Root r = b.take();\n" " (void)r;\n" "}\n" ) comp = subprocess.run( [ self.compiler, "-std=c++20", "-fsyntax-only", "-I", self.include_dir, "-I", tmpdir, cpp_path, ], capture_output=True, text=True, check=False, ) self.assertEqual(comp.returncode, 0, msg=comp.stderr) with open(header_path) as fp: return fp.read() def test_root_alias_does_not_collide_with_user_type(self): schema = { "type": "array", "items": {"$ref": "#/$defs/Root"}, "$defs": {"Root": {"enum": ["a", "b"]}}, } out = self.generate_and_compile(schema) self.assertIn("enum class Root1 : int { a, b };", out) self.assertIn("using Root = std::vector;", out) self.assertNotIn("using Root = std::vector;", out) def test_kind_enum_does_not_duplicate_arr0(self): schema = { "type": "object", "additionalProperties": False, "properties": { "arr": {"type": "array", "items": {"type": "string"}}, "obj": {"$ref": "#/$defs/Arr0"}, }, "$defs": { "Arr0": { "type": "object", "additionalProperties": False, "properties": {}, } }, } out = self.generate_and_compile(schema) self.assertIn("struct Arr0", out) m = re.search(r"enum class Kind : uint8_t \{([^}]+)\}", out) self.assertIsNotNone(m) enumerators = [e.strip() for e in m.group(1).split(",")] self.assertIn("Arr0", enumerators) self.assertIn("Arr1", enumerators) self.assertEqual(len(enumerators), len(set(enumerators))) def test_skip_user_type_is_allowed(self): schema = { "type": "array", "items": {"$ref": "#/$defs/Skip"}, "$defs": { "Skip": { "type": "object", "additionalProperties": False, "properties": {}, } }, } out = self.generate_and_compile(schema) self.assertIn("struct Skip", out) self.assertNotIn("struct Skip1", out) m = re.search(r"enum class Kind : uint8_t \{([^}]+)\}", out) self.assertIsNotNone(m) enumerators = [e.strip() for e in m.group(1).split(",")] self.assertIn("Skip", enumerators) self.assertIn("Arr0", enumerators) self.assertEqual(len(enumerators), len(set(enumerators))) def test_non_object_defs_reused_across_refs(self): """Regression test for issue #17: enum and array $defs must be reused.""" schema = { "type": "object", "additionalProperties": False, "properties": { "role1": {"$ref": "#/$defs/Role"}, "role2": {"$ref": "#/$defs/Role"}, "roles1": {"$ref": "#/$defs/Roles"}, "roles2": {"$ref": "#/$defs/Roles"}, }, "$defs": { "Role": {"enum": ["admin", "user"]}, "Roles": {"type": "array", "items": {"$ref": "#/$defs/Role"}}, }, } out = self.generate_and_compile(schema) # Exactly one Role enum is generated. self.assertIn("enum class Role : int { admin, user };", out) self.assertNotIn("enum class Role1 : int", out) self.assertNotIn("enum class Role2 : int", out) # The array of enum is represented by a single kind. m = re.search(r"enum class Kind : uint8_t \{([^}]+)\}", out) self.assertIsNotNone(m) enumerators = [e.strip() for e in m.group(1).split(",")] self.assertEqual(enumerators.count("Arr0"), 1) # All four fields use the same C++ types. self.assertIn("std::optional role1;", out) self.assertIn("std::optional role2;", out) self.assertIn("std::optional> roles1;", out) self.assertIn("std::optional> roles2;", out) class SchemagenAdditionalPropertiesTest(unittest.TestCase): def run_schemagen(self, schema, args=None): """Run schemagen on a schema dict. Returns (returncode, stdout, stderr).""" with tempfile.NamedTemporaryFile("w", suffix=".json", delete=False) as fp: json.dump(schema, fp) schema_path = fp.name try: cmd = [sys.executable, SCRIPT, schema_path] if args: cmd.extend(args) result = subprocess.run(cmd, capture_output=True, text=True, check=False) return result.returncode, result.stdout, result.stderr finally: os.unlink(schema_path) def test_additional_properties_true_rejected(self): schema = { "type": "object", "additionalProperties": True, "properties": {"name": {"type": "string"}}, } rc, stdout, stderr = self.run_schemagen(schema) self.assertNotEqual(rc, 0) self.assertIn("additionalProperties: true is not supported", stderr) def test_additional_properties_absent_rejected(self): schema = { "type": "object", "properties": {"name": {"type": "string"}}, } rc, stdout, stderr = self.run_schemagen(schema) self.assertNotEqual(rc, 0) self.assertIn("additionalProperties is required for object schemas", stderr) def test_additional_properties_false_accepted(self): schema = { "type": "object", "additionalProperties": False, "properties": {"name": {"type": "string"}}, } rc, stdout, stderr = self.run_schemagen(schema) self.assertEqual(rc, 0, msg=stderr) class SchemagenCyclicArrayTest(unittest.TestCase): """Regression tests for issue #33: cyclic array $ref targets.""" def run_schemagen(self, schema, args=None): """Run schemagen on a schema dict. Returns (returncode, stdout, stderr).""" with tempfile.NamedTemporaryFile("w", suffix=".json", delete=False) as fp: json.dump(schema, fp) schema_path = fp.name try: cmd = [sys.executable, SCRIPT, schema_path] if args: cmd.extend(args) result = subprocess.run(cmd, capture_output=True, text=True, check=False) return result.returncode, result.stdout, result.stderr finally: os.unlink(schema_path) def test_direct_self_referential_array_rejected(self): schema = { "type": "array", "items": {"$ref": "#/$defs/Node"}, "$defs": { "Node": { "type": "array", "items": {"$ref": "#/$defs/Node"}, } }, } rc, stdout, stderr = self.run_schemagen(schema) self.assertNotEqual(rc, 0) self.assertIn("recursive array type is not supported", stderr) self.assertIn("Node", stderr) def test_object_field_to_self_referential_array_rejected(self): schema = { "type": "object", "additionalProperties": False, "properties": {"items": {"$ref": "#/$defs/Items"}}, "$defs": { "Items": { "type": "array", "items": {"$ref": "#/$defs/Items"}, } }, } rc, stdout, stderr = self.run_schemagen(schema) self.assertNotEqual(rc, 0) self.assertIn("recursive array type is not supported", stderr) self.assertIn("Items", stderr) def test_chain_of_array_refs_rejected(self): schema = { "type": "object", "additionalProperties": False, "properties": {"x": {"$ref": "#/$defs/A"}}, "$defs": { "A": {"type": "array", "items": {"$ref": "#/$defs/B"}}, "B": {"type": "array", "items": {"$ref": "#/$defs/A"}}, }, } rc, stdout, stderr = self.run_schemagen(schema) self.assertNotEqual(rc, 0) self.assertIn("recursive array type is not supported", stderr) def test_non_recursive_array_refs_still_allowed(self): schema = { "type": "object", "additionalProperties": False, "properties": {"roles": {"$ref": "#/$defs/Roles"}}, "$defs": { "Role": {"enum": ["admin", "user"]}, "Roles": { "type": "array", "items": {"$ref": "#/$defs/Role"}, }, }, } rc, stdout, stderr = self.run_schemagen(schema) self.assertEqual(rc, 0, msg=stderr) self.assertIn("std::optional> roles;", stdout) class SchemagenCyclicNullableObjectTest(unittest.TestCase): """Regression tests for issue #14: nullable cyclic $ref targets.""" def setUp(self): self.repo_root = os.path.dirname( os.path.dirname(os.path.dirname(os.path.abspath(__file__))) ) self.include_dir = os.path.join(self.repo_root, "include") self.lib_src = os.path.join(self.repo_root, "src", "lib.cpp") self.compiler = shutil.which("c++") def _compile_harness(self, tmpdir, schema, harness): schema_path = os.path.join(tmpdir, "schema.json") with open(schema_path, "w") as fp: json.dump(schema, fp) header_path = os.path.join(tmpdir, "gen.h") result = subprocess.run( [ sys.executable, SCRIPT, schema_path, "-o", header_path, "--namespace", "test_schema", ], capture_output=True, text=True, check=False, ) self.assertEqual(result.returncode, 0, msg=result.stderr) lib_obj = os.path.join(tmpdir, "lib.o") comp_lib = subprocess.run( [ self.compiler, "-std=c++20", "-I", self.include_dir, "-I", os.path.join(self.repo_root, "third_party", "include"), "-I", os.path.join(self.repo_root, "third_party", "valgrind"), "-c", self.lib_src, "-o", lib_obj, ], capture_output=True, text=True, check=False, ) self.assertEqual(comp_lib.returncode, 0, msg=comp_lib.stderr) cpp_path = os.path.join(tmpdir, "test.cpp") with open(cpp_path, "w") as fp: fp.write(harness) exe_path = os.path.join(tmpdir, "test") comp = subprocess.run( [ self.compiler, "-std=c++20", "-I", self.include_dir, "-I", tmpdir, lib_obj, cpp_path, "-o", exe_path, ], capture_output=True, text=True, check=False, ) self.assertEqual(comp.returncode, 0, msg=comp.stderr) run = subprocess.run([exe_path], capture_output=True, text=True, check=False) self.assertEqual(run.returncode, 0, msg=run.stdout + run.stderr) def test_self_referential_nullable_object_accepts_nested_null(self): """A nullable object $def must stay nullable on recursive $refs.""" if not self.compiler: self.skipTest("C++ compiler not available") schema = { "type": "object", "additionalProperties": False, "properties": {"self": {"$ref": "#/$defs/Self"}}, "$defs": { "Self": { "type": ["object", "null"], "additionalProperties": False, "properties": {"self": {"$ref": "#/$defs/Self"}}, } }, } harness = textwrap.dedent( """ #include "gen.h" #include #include struct Case { const char *s; WeaselJsonStatus expected; }; int main() { Case cases[] = { { R"({"self": null})", WeaselJson_OK }, { R"({"self": {"self": null}})", WeaselJson_OK }, { R"({"self": {"self": {}}})", WeaselJson_OK }, }; for (const auto &c : cases) { test_schema::RootBuilder b; char buf[256]; std::strncpy(buf, c.s, sizeof(buf) - 1); buf[sizeof(buf) - 1] = '\\0'; WeaselJsonStatus st = b.feed(buf, std::strlen(buf)); st = b.finish(); if (st != c.expected) { std::printf("case %s expected %d got %d\\n", c.s, c.expected, st); return 1; } } return 0; } """ ) with tempfile.TemporaryDirectory() as tmpdir: self._compile_harness(tmpdir, schema, harness) class SchemagenIntegerBoundaryTest(unittest.TestCase): """Regression tests for issue #19: integer slot parsing near int64 boundaries.""" def setUp(self): self.repo_root = os.path.dirname( os.path.dirname(os.path.dirname(os.path.abspath(__file__))) ) self.include_dir = os.path.join(self.repo_root, "include") self.lib_src = os.path.join(self.repo_root, "src", "lib.cpp") self.compiler = shutil.which("c++") def _compile_harness(self, tmpdir, schema, harness): schema_path = os.path.join(tmpdir, "schema.json") with open(schema_path, "w") as fp: json.dump(schema, fp) header_path = os.path.join(tmpdir, "gen.h") result = subprocess.run( [ sys.executable, SCRIPT, schema_path, "-o", header_path, "--namespace", "test_schema", ], capture_output=True, text=True, check=False, ) self.assertEqual(result.returncode, 0, msg=result.stderr) lib_obj = os.path.join(tmpdir, "lib.o") comp_lib = subprocess.run( [ self.compiler, "-std=c++20", "-I", self.include_dir, "-I", os.path.join(self.repo_root, "third_party", "include"), "-I", os.path.join(self.repo_root, "third_party", "valgrind"), "-c", self.lib_src, "-o", lib_obj, ], capture_output=True, text=True, check=False, ) self.assertEqual(comp_lib.returncode, 0, msg=comp_lib.stderr) cpp_path = os.path.join(tmpdir, "test.cpp") with open(cpp_path, "w") as fp: fp.write(harness) exe_path = os.path.join(tmpdir, "test") comp = subprocess.run( [ self.compiler, "-std=c++20", "-I", self.include_dir, "-I", tmpdir, lib_obj, cpp_path, "-o", exe_path, ], capture_output=True, text=True, check=False, ) self.assertEqual(comp.returncode, 0, msg=comp.stderr) run = subprocess.run([exe_path], capture_output=True, text=True, check=False) self.assertEqual(run.returncode, 0, msg=run.stdout + run.stderr) def _build_cases_array(self, name, entries): lines = [ f" struct {name}Case {{ const char *s; WeaselJsonStatus expected; long long v; }};" ] lines.append(f" {name}Case {name}_cases[] = {{") for s, exp, v in entries: val = f"{v}LL" if isinstance(v, int) else v lines.append(f' {{ R"({s})", {exp}, {val} }},') lines.append(" };") return "\n".join(lines) def test_integer_boundary_root(self): if not self.compiler: self.skipTest("C++ compiler not available") cases = [ ("9223372036854775807.0", "WeaselJson_OK", 9223372036854775807), ("9223372036854775807", "WeaselJson_OK", 9223372036854775807), ("9223372036854775806.0", "WeaselJson_OK", 9223372036854775806), ("9223372036854775808", "WeaselJson_REJECT", 0), ("-9223372036854775808", "WeaselJson_OK", "-9223372036854775807LL - 1"), ("-9223372036854775808.0", "WeaselJson_OK", "-9223372036854775807LL - 1"), ("-9223372036854775809", "WeaselJson_REJECT", 0), ("1e3", "WeaselJson_OK", 1000), ("2.0", "WeaselJson_OK", 2), ("0.001", "WeaselJson_REJECT", 0), ("1e-3", "WeaselJson_REJECT", 0), ("1000e-3", "WeaselJson_OK", 1), ("100.0e-2", "WeaselJson_OK", 1), ("0.0001e4", "WeaselJson_OK", 1), ("0.001e3", "WeaselJson_OK", 1), ("123.0", "WeaselJson_OK", 123), ("9e18", "WeaselJson_OK", 9000000000000000000), ("10e18", "WeaselJson_REJECT", 0), # Issue #56: zero written with a negative exponent must be accepted. ("0e-1", "WeaselJson_OK", 0), ("0e-2", "WeaselJson_OK", 0), ("0.0e-2", "WeaselJson_OK", 0), ("-0e-2", "WeaselJson_OK", 0), ("0e-20", "WeaselJson_OK", 0), ("0.000e-5", "WeaselJson_OK", 0), ] cases_src = self._build_cases_array("root", cases) harness = textwrap.dedent( f""" #include "gen.h" #include #include {cases_src} int main() {{ for (const auto \u0026c : root_cases) {{ test_schema::RootBuilder b; char buf[512]; std::strncpy(buf, c.s, sizeof(buf) - 1); buf[sizeof(buf) - 1] = '\\0'; WeaselJsonStatus st = b.feed(buf, std::strlen(buf)); st = b.finish(); if (st != c.expected) {{ std::printf("root case %s expected %d got %d\\n", c.s, c.expected, st); return 1; }} if (st == WeaselJson_OK \u0026\u0026 b.take() != c.v) {{ std::printf("root case %s value mismatch\\n", c.s); return 2; }} }} return 0; }} """ ) with tempfile.TemporaryDirectory() as tmpdir: self._compile_harness(tmpdir, {"type": "integer"}, harness) def test_integer_boundary_object_field(self): if not self.compiler: self.skipTest("C++ compiler not available") cases = [ ('{"age":9223372036854775807.0}', "WeaselJson_OK", 9223372036854775807), ('{"age":-9223372036854775809}', "WeaselJson_REJECT", 0), ('{"age":1e3}', "WeaselJson_OK", 1000), ('{"age":2.0}', "WeaselJson_OK", 2), ('{"age":0.0001e4}', "WeaselJson_OK", 1), ('{"age":0.001}', "WeaselJson_REJECT", 0), # Issue #56: zero written with a negative exponent must be accepted. ('{"age":0e-2}', "WeaselJson_OK", 0), ('{"age":-0e-20}', "WeaselJson_OK", 0), ( '{"age":-9223372036854775808.0}', "WeaselJson_OK", "-9223372036854775807LL - 1", ), ] cases_src = self._build_cases_array("object", cases) harness = textwrap.dedent( f""" #include "gen.h" #include #include {cases_src} int main() {{ for (const auto \u0026c : object_cases) {{ test_schema::RootBuilder b; char buf[512]; std::strncpy(buf, c.s, sizeof(buf) - 1); buf[sizeof(buf) - 1] = '\\0'; WeaselJsonStatus st = b.feed(buf, std::strlen(buf)); st = b.finish(); if (st != c.expected) {{ std::printf("object case %s expected %d got %d\\n", c.s, c.expected, st); return 3; }} if (st == WeaselJson_OK \u0026\u0026 b.take().age != c.v) {{ std::printf("object case %s value mismatch\\n", c.s); return 4; }} }} return 0; }} """ ) schema = { "type": "object", "additionalProperties": False, "properties": {"age": {"type": "integer"}}, "required": ["age"], } with tempfile.TemporaryDirectory() as tmpdir: self._compile_harness(tmpdir, schema, harness) def test_integer_no_quadratic_leading_zero_loop(self): """Regression test for issue #34: leading-zero stripping must not be quadratic.""" with tempfile.TemporaryDirectory() as tmpdir: schema_path = os.path.join(tmpdir, "schema.json") with open(schema_path, "w") as fp: json.dump({"type": "integer"}, fp) result = subprocess.run( [sys.executable, SCRIPT, schema_path], capture_output=True, text=True, check=False, ) self.assertEqual(result.returncode, 0, msg=result.stderr) self.assertIn("parseJsonInt64", result.stdout) self.assertNotIn("digits.erase(digits.begin())", result.stdout) def test_integer_large_fractional_leading_zeros(self): """Numbers with many leading fractional zeros must parse correctly.""" if not self.compiler: self.skipTest("C++ compiler not available") harness = textwrap.dedent( """ #include "gen.h" #include #include int main() { const int n = 100000; std::string s = std::string("0.") + std::string(n - 1, '0') + "1e" + std::to_string(n); test_schema::RootBuilder b; WeaselJsonStatus st = b.feed(s.data(), static_cast(s.size())); st = b.finish(); if (st != WeaselJson_OK) { std::printf("expected OK, got %d\\n", st); return 1; } if (b.take() != 1) { std::printf("expected value 1\\n"); return 2; } return 0; } """ ) with tempfile.TemporaryDirectory() as tmpdir: self._compile_harness(tmpdir, {"type": "integer"}, harness) class SchemagenStringEscapeTest(unittest.TestCase): """Regression tests for issue #35: control characters in string literals.""" def setUp(self): self.repo_root = os.path.dirname( os.path.dirname(os.path.dirname(os.path.abspath(__file__))) ) self.include_dir = os.path.join(self.repo_root, "include") self.compiler = shutil.which("c++") def generate_and_compile(self, schema): """Run schemagen on schema and syntax-check the resulting header.""" with tempfile.TemporaryDirectory() as tmpdir: schema_path = os.path.join(tmpdir, "schema.json") with open(schema_path, "w") as fp: json.dump(schema, fp) header_path = os.path.join(tmpdir, "gen.h") cmd = [ sys.executable, SCRIPT, schema_path, "-o", header_path, "--namespace", "test_schema", ] result = subprocess.run(cmd, capture_output=True, text=True, check=False) self.assertEqual(result.returncode, 0, msg=result.stderr) if self.compiler: cpp_path = os.path.join(tmpdir, "test.cpp") with open(cpp_path, "w") as fp: fp.write( '#include "gen.h"\n' "int main() {\n" " test_schema::RootBuilder b;\n" " test_schema::Root r = b.take();\n" " (void)r;\n" "}\n" ) comp = subprocess.run( [ self.compiler, "-std=c++20", "-fsyntax-only", "-I", self.include_dir, "-I", tmpdir, cpp_path, ], capture_output=True, text=True, check=False, ) self.assertEqual(comp.returncode, 0, msg=comp.stderr) with open(header_path) as fp: return fp.read() def test_newline_in_property_key(self): """A JSON key containing a newline must become a valid C++ literal.""" schema = { "type": "object", "additionalProperties": False, "properties": {"a\nb": {"type": "string"}}, } out = self.generate_and_compile(schema) self.assertIn('// "a\\nb"', out) self.assertIn('if (key == "a\\nb") return 0;', out) def test_newline_in_enum_value(self): """An enum value containing a newline must become a valid C++ literal.""" schema = { "type": "object", "additionalProperties": False, "properties": {"x": {"enum": ["a\nb"]}}, } out = self.generate_and_compile(schema) self.assertIn('static constexpr const char *X_names[] = { "a\\nb" };', out) def test_mixed_control_chars_in_enum_value(self): """Mixed control characters in an enum value must be escaped.""" schema = { "type": "object", "additionalProperties": False, "properties": { "x": {"enum": ["x\ny\rz\tw\vq\x00\x01"]}, }, } out = self.generate_and_compile(schema) self.assertIn( 'static constexpr const char *X_names[] = { "x\\ny\\rz\\tw\\vq\\u0000\\u0001" };', out, ) def test_backslash_and_quote_still_escaped(self): """Existing escaping for backslash and double quote must remain correct.""" schema = { "type": "object", "additionalProperties": False, "properties": {'a"b\\c': {"type": "string"}}, } out = self.generate_and_compile(schema) self.assertIn('// "a\\"b\\\\c"', out) self.assertIn('if (key == "a\\"b\\\\c") return 0;', out) if __name__ == "__main__": unittest.main()