Coverage for /pythoncovmergedfiles/medio/medio/src/turbohtml/tools/fuzz/css_structure_generators.py: 67%

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

61 statements  

1"""Charge complete CSS syntax trees so lexical helpers do not consume fictitious nodes.""" 

2 

3from __future__ import annotations 

4 

5import argparse 

6import random 

7import re 

8from pathlib import Path 

9from typing import TYPE_CHECKING, Final, TypeAlias 

10 

11from fuzz.css_generation_tables import CSS_ROWS, SOURCE_URLS, CssRow 

12from fuzz.structure_generators import ( 

13 BudgetError, 

14 Generated, 

15 GenerationBudget, 

16 Grammar, 

17 GrammarError, 

18 Identifier, 

19 Production, 

20 ProductionFloorError, 

21 Reference, 

22 compile_grammar, 

23 generate, 

24 generation_sweep, 

25 write_corpus, 

26) 

27 

28if TYPE_CHECKING: 

29 from collections.abc import Sequence 

30 

31 

32def main(argv: Sequence[str] | None = None) -> int: 

33 """Preserve source bytes for the coverage-guided corpus.""" 

34 parser: Final = argparse.ArgumentParser() 

35 parser.add_argument("--output", type=Path, required=True) 

36 parser.add_argument("--budget", type=int, default=512) 

37 parser.add_argument("--steps", type=int, default=128) 

38 parser.add_argument("--count", type=int, default=0) 

39 parser.add_argument("--seed", type=int, default=0) 

40 args: Final = parser.parse_args(argv) 

41 if args.count < 0: 

42 parser.error("count must be nonnegative") 

43 try: 

44 cases: Final = ( 

45 tuple( 

46 css_generate(random.Random(args.seed + index), args.budget, steps=args.steps) 

47 for index in range(args.count) 

48 ) 

49 if args.count 

50 else generation_sweep(_GRAMMAR, budget=GenerationBudget(args.budget, args.steps)) 

51 ) 

52 except (BudgetError, GrammarError, ProductionFloorError) as error: 

53 parser.error(str(error)) 

54 write_corpus(cases, args.output) 

55 return 0 

56 

57 

58def css_generate(rng: random.Random, budget: int = 96, *, steps: int = 64) -> Generated: 

59 """Reserve node and expansion budgets across nested at-rules and sibling sheets.""" 

60 return generate(_GRAMMAR, rng, GenerationBudget(budget, steps)) 

61 

62 

63def css_grammar(rows: Sequence[CssRow] = CSS_ROWS) -> Grammar: 

64 """Retain source alternatives as distinct productions for forced coverage.""" 

65 return compile_grammar( 

66 ( 

67 Production("css:sheet", "root", (*_PREFIX, Reference("body")), 30, _STANDARD, 7), 

68 Production("css:leaf", "body", (b"a{color:red}",), 8, _STANDARD, 4), 

69 Production("css:siblings", "body", (Reference("body", 0), Reference("body", 0)), 0, _STANDARD), 

70 *( 

71 Production( 

72 f"css:nested:{name}", 

73 "body", 

74 (prefix, Reference("body", 2), b"}"), 

75 nodes, 

76 _STANDARD, 

77 height, 

78 ) 

79 for name, prefix, nodes, height in ( 

80 ("media", b"@media all{", 5, 3), 

81 ("supports", b"@supports(display:grid){", 8, 6), 

82 ("container", b"@container(width>1px){", 7, 4), 

83 ("layer", b"@layer x{", 5, 3), 

84 ) 

85 ), 

86 *(_production(row) for row in rows), 

87 ), 

88 "root", 

89 identifiers=("x", "y", "z", "u", "v"), 

90 ) 

91 

92 

93def _production(row: CssRow) -> Production: 

94 origin: Final = SOURCE_URLS[row[0]] + f"#L{row[1]}" 

95 name: Final = f"css:{'blink' if row[0] == 'blink-css.dict' else 'domato:' + row[0]}:{row[1]}" 

96 parts: Final = _references(row[4]) 

97 if row[9] or row[10]: 

98 return Production( 

99 name, 

100 "root", 

101 (*parts, *_PREFIX) if row[10] else (*_PREFIX, *parts), 

102 row[5], 

103 origin, 

104 row[6], 

105 ) 

106 return Production(name, "body", parts, row[7] - 1, origin, row[8] - 1) 

107 

108 

109def _references(source: str) -> Parts: 

110 parts: Final[list[bytes | Identifier]] = [] 

111 position = 0 

112 for token in _BINDING.finditer(source): 

113 parts.append(source[position : token.start()].encode("utf-8")) 

114 if token[0].startswith("--"): 

115 parts.append(b"--") 

116 parts.append(Identifier(token[0].removeprefix("--"), definition=False)) 

117 position = token.end() 

118 parts.append(source[position:].encode("utf-8")) 

119 return tuple(parts) 

120 

121 

122def css_source_generate(rng: random.Random) -> str: 

123 """Use whole sheets so at-rules reach minification.""" 

124 return css_generate(rng).data.decode("utf-8") 

125 

126 

127def css_source_seeds() -> list[str]: 

128 """Force pinned alternatives through their shortest emitting root paths.""" 

129 return [case.data.decode("utf-8") for case in generation_sweep(_GRAMMAR, budget=GenerationBudget(512, 128))] 

130 

131 

132Parts: TypeAlias = tuple[bytes | Identifier, ...] 

133_STANDARD: Final = "https://www.w3.org/TR/2021/CRD-css-syntax-3-20211224/" 

134_BINDING: Final = re.compile(r"(?<![a-zA-Z0-9_-])(?:--cssvar[a-d]|anim)(?![a-zA-Z0-9_-])") 

135_PREFIX: Final[Parts] = ( 

136 b":root{", 

137 *( 

138 part 

139 for name in ("cssvara", "cssvarb", "cssvarc", "cssvard") 

140 for part in (b"--", Identifier(name, definition=True), b":red;") 

141 ), 

142 b"}@keyframes ", 

143 Identifier("anim", definition=True), 

144 b"{from{opacity:0}}", 

145) 

146_GRAMMAR: Final = css_grammar() 

147 

148__all__ = ["css_generate", "css_grammar", "css_source_generate", "css_source_seeds", "main"] 

149 

150if __name__ == "__main__": 

151 raise SystemExit(main())