Coverage for /pythoncovmergedfiles/medio/medio/src/turbohtml/tools/fuzz/xml_island_oracles.py: 37%

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

52 statements  

1"""Finite XML islands pin lexical node kinds and attachment.""" 

2 

3from __future__ import annotations 

4 

5import re 

6from typing import TYPE_CHECKING, Final 

7 

8from turbohtml import CData, Comment, Document, Element, Html, Node, Text, parse_xml 

9 

10if TYPE_CHECKING: 

11 import random 

12 from collections.abc import Callable 

13 

14 

15def xml_island_check( 

16 case: str, 

17 serialize: Callable[[Node], str] | None = None, 

18 read: Callable[[str], Document] = parse_xml, 

19) -> str | None: 

20 """Keep literal node kinds independent of round-trip agreement.""" 

21 if (match := _CASE.fullmatch(case)) is None: 

22 raise UnsupportedXmlIslandCaseError(case) 

23 source, expected = _document(*match.groups()) 

24 root: Final = read(source) 

25 if _snapshot(root) != expected: 

26 return "parsed XML islands differ from literals" 

27 printed: Final = _serialize(root, serialize) 

28 reparsed: Final = read(printed) 

29 if _snapshot(reparsed) != expected: 

30 return "serialized XML islands differ from literals" 

31 return None if _serialize(reparsed, serialize) == printed else "XML island serialization changes on repeat" 

32 

33 

34def _document(mode: str, comment: str, cdata: str, count: str) -> tuple[str, tuple[tuple[str, str, str], ...]]: 

35 encoded_comment, decoded_comment = _COMMENTS[comment] 

36 encoded_cdata, decoded_cdata = _CDATA[cdata] 

37 group: Final = f"<!--{encoded_comment}--><![CDATA[{encoded_cdata}]]>&lt;&amp;" 

38 prefix: Final = "<!--lead &amp;-->" if mode == "outside" else "" 

39 suffix: Final = "<!--tail-->" if mode == "outside" else "" 

40 return prefix + "<Root>" + group * int(count) + "</Root>" + suffix, ( 

41 *([("Comment", "lead &amp;", "Document")] if mode == "outside" else []), 

42 ("Element", "Root", "Document"), 

43 *[ 

44 record 

45 for _ in range(int(count)) 

46 for record in [ 

47 ("Comment", decoded_comment, "Root"), 

48 ("CData", decoded_cdata, "Root"), 

49 ("Text", "<&", "Root"), 

50 ] 

51 ], 

52 *([("Comment", "tail", "Document")] if mode == "outside" else []), 

53 ) 

54 

55 

56def _snapshot(root: Document) -> tuple[tuple[str, str, str], ...]: 

57 records: Final[list[tuple[str, str, str]]] = [] 

58 for node in root.descendants: 

59 parent: Final = node.parent 

60 parent_tag: Final = parent.tag if isinstance(parent, Element) else "Document" 

61 if isinstance(node, Element): 

62 records.append(("Element", node.tag, parent_tag)) 

63 elif isinstance(node, (Comment, CData, Text)): 

64 records.append((type(node).__name__, node.data, parent_tag)) 

65 else: 

66 records.append(("other", type(node).__name__, parent_tag)) 

67 return tuple(records) 

68 

69 

70def _serialize(root: Node, serialize: Callable[[Node], str] | None) -> str: 

71 return root.serialize(Html(xml=True)) if serialize is None else serialize(root) 

72 

73 

74def xml_island_generate(rng: random.Random) -> str: 

75 """Bound lexical groups before parsing.""" 

76 return f"{rng.choice(_MODES)}:{rng.choice(tuple(_COMMENTS))}:{rng.choice(tuple(_CDATA))}:{rng.randint(1, 3)}" 

77 

78 

79def xml_island_seeds() -> list[str]: 

80 """Cover the finite productions without sampling gaps.""" 

81 return [ 

82 f"{mode}:{comment}:{cdata}:{count}" 

83 for mode in _MODES 

84 for comment in _COMMENTS 

85 for cdata in _CDATA 

86 for count in range(1, 4) 

87 ] 

88 

89 

90def xml_island_controls() -> dict[str, bool]: 

91 """Reject content-preserving kind loss and changed comment attachment.""" 

92 return { 

93 "CDATA converted to Text": xml_island_check( 

94 "inside:plain:plain:1", lambda _node: "<Root><!--note-->Value&lt;&amp;</Root>" 

95 ) 

96 is not None, 

97 "comment moved into Root": xml_island_check( 

98 "outside:plain:plain:1", 

99 lambda _node: "<!--lead &amp;--><Root><!--note--><![CDATA[Value]]>&lt;&amp;<!--tail--></Root>", 

100 ) 

101 is not None, 

102 "CDATA reference decoded": xml_island_check( 

103 "inside:plain:markup:1", lambda node: node.serialize(Html(xml=True)).replace("&amp;]]>", "&]]>") 

104 ) 

105 is not None, 

106 } 

107 

108 

109class UnsupportedXmlIslandCaseError(ValueError): 

110 """Separate malformed programs from failures of generated XML.""" 

111 

112 

113_CASE: Final = re.compile(r"(inside|outside):(plain|markup|lines):(plain|markup|lines):([1-3])") 

114_MODES: Final = ("inside", "outside") 

115_COMMENTS: Final = {"plain": ("note", "note"), "markup": ("<x> &amp;", "<x> &amp;"), "lines": ("a\r\nb\rc", "a\nb\nc")} 

116_CDATA: Final = {"plain": ("Value", "Value"), "markup": ("<x>&amp;", "<x>&amp;"), "lines": ("a\r\nb\rc", "a\nb\nc")} 

117 

118__all__ = [ 

119 "UnsupportedXmlIslandCaseError", 

120 "xml_island_check", 

121 "xml_island_controls", 

122 "xml_island_generate", 

123 "xml_island_seeds", 

124]