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

35 statements  

1"""Bounded custom-property productions preserve opaque ASCII payloads.""" 

2 

3from __future__ import annotations 

4 

5import re 

6from typing import TYPE_CHECKING, Final 

7 

8from turbohtml.clean import minify_css, minify_css_inline 

9 

10if TYPE_CHECKING: 

11 import random 

12 from collections.abc import Callable 

13 

14 

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" 

37 

38 

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)}" 

42 

43 

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 ] 

53 

54 

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 } 

63 

64 

65class UnsupportedCssCustomCaseError(ValueError): 

66 """Separate unsupported programs from failures for valid generated CSS.""" 

67 

68 

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} 

77 

78__all__ = [ 

79 "UnsupportedCssCustomCaseError", 

80 "css_custom_check", 

81 "css_custom_controls", 

82 "css_custom_generate", 

83 "css_custom_seeds", 

84]