Coverage for /pythoncovmergedfiles/medio/medio/src/turbohtml/tools/fuzz/css_custom_oracles.py: 49%
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1"""Bounded custom-property productions preserve opaque ASCII payloads."""
3from __future__ import annotations
5import re
6from typing import TYPE_CHECKING, Final
8from turbohtml.clean import minify_css, minify_css_inline
10if TYPE_CHECKING:
11 import random
12 from collections.abc import Callable
15def css_custom_check(case: str, minify: Callable[[str], str] | None = None) -> str | None:
16 """Check literal custom tokens without sharing the minifier's parser."""
17 if (match := _CASE.fullmatch(case)) is None:
18 raise UnsupportedCssCustomCaseError(case)
19 mode, kind, count, offset = match.groups()
20 declarations: Final = [
21 (_NAMES[(int(offset) + index) % 4], _VALUES[kind][(int(offset) + index) % 8]) for index in range(int(count))
22 ]
23 source: Final = " ; ".join(f"{name} : {repr(value) if kind == 'string' else value}" for name, value in declarations)
24 text: Final = f"a {{ {source} ; }}" if mode == "sheet" else f" {source} ; "
25 printer: Final = minify or (minify_css if mode == "sheet" else minify_css_inline)
26 output: Final = printer(text)
27 expected: Final = ";".join(
28 re.escape(name)
29 + ":"
30 + (f"(?:'{re.escape(value)}'|\"{re.escape(value)}\")" if kind == "string" else re.escape(value))
31 for name, value in declarations
32 )
33 pattern: Final = r"a\{" + expected + r"\}" if mode == "sheet" else expected
34 if re.fullmatch(pattern, output) is None:
35 return "custom-property tokens differ from literals"
36 return None if printer(output) == output else "custom-property output changes on repeat"
39def css_custom_generate(rng: random.Random) -> str:
40 """Finite leaves bound depth and declaration count."""
41 return f"{rng.choice(_MODES)}:{rng.choice(_KINDS)}:{rng.randint(1, 4)}:{rng.randrange(8)}"
44def css_custom_seeds() -> list[str]:
45 """Sweep each root, payload production, count and offset."""
46 return [
47 f"{mode}:{kind}:{count}:{offset}"
48 for mode in _MODES
49 for kind in _KINDS
50 for count in range(1, 5)
51 for offset in range(8)
52 ]
55def css_custom_controls() -> dict[str, bool]:
56 """Require formatting, case identity and quoted-content conservation."""
57 return {
58 "identity minifier": css_custom_check("sheet:ident:2:0", lambda text: text) is not None,
59 "folded name": css_custom_check("sheet:ident:2:0", lambda _text: "a{--foo:MiXeD;--foo:UPPER}") is not None,
60 "folded payload": css_custom_check("inline:ident:1:0", lambda _text: "--Foo:mixed") is not None,
61 "lost string content": css_custom_check("inline:string:1:0", lambda _text: '--Foo:"AB"') is not None,
62 }
65class UnsupportedCssCustomCaseError(ValueError):
66 """Separate unsupported programs from failures for valid generated CSS."""
69_CASE: Final = re.compile(r"(sheet|inline):(ident|string):([1-4]):([0-7])")
70_MODES: Final = ("sheet", "inline")
71_KINDS: Final = ("ident", "string")
72_NAMES: Final = ("--Foo", "--foo", "--Bar", "--bar")
73_VALUES: Final = {
74 "ident": ("MiXeD", "UPPER", "Lower", "Token", "Alpha", "Beta", "Gamma", "Delta"),
75 "string": ("A B", "x;y", "MiXeD", "lower", "A:B", "A{B}", "A[B]", "A(B)"),
76}
78__all__ = [
79 "UnsupportedCssCustomCaseError",
80 "css_custom_check",
81 "css_custom_controls",
82 "css_custom_generate",
83 "css_custom_seeds",
84]