1#!/usr/bin/env python3 2# SPDX-License-Identifier: GPL-2.0 3# Copyright(c) 2026: Mauro Carvalho Chehab <mchehab@kernel.org>. 4 5import os 6 7from kdoc.kdoc_output import ManFormat, RestFormat 8 9 10class KDocTestFile(): 11 """ 12 Handles the logic needed to store kernel‑doc output inside a YAML file. 13 Useful for unit tests and regression tests. 14 """ 15 16 def __init__(self, config, yaml_file, yaml_content): 17 # 18 # Bail out early if yaml is not available 19 # 20 try: 21 import yaml 22 except ImportError: 23 sys.exit("Warning: yaml package not available. Aborting it.") 24 25 self.config = config 26 self.test_file = os.path.expanduser(yaml_file) 27 self.yaml_content = yaml_content 28 self.test_names = set() 29 30 self.tests = [] 31 32 out_dir = os.path.dirname(self.test_file) 33 if out_dir and not os.path.isdir(out_dir): 34 sys.exit(f"Directory {out_dir} doesn't exist.") 35 36 self.out_style = [] 37 38 if "man" in self.yaml_content: 39 out_style = ManFormat() 40 out_style.set_config(self.config) 41 42 self.out_style.append(out_style) 43 44 if "rst" in self.yaml_content: 45 out_style = RestFormat() 46 out_style.set_config(self.config) 47 48 self.out_style.append(out_style) 49 50 def set_filter(self, export, internal, symbol, nosymbol, 51 function_table, enable_lineno, no_doc_sections): 52 """ 53 Set filters at the output classes. 54 """ 55 for out_style in self.out_style: 56 out_style.set_filter(export, internal, symbol, 57 nosymbol, function_table, 58 enable_lineno, no_doc_sections) 59 60 @staticmethod 61 def get_kdoc_item(arg, start_line=1): 62 63 d = vars(arg) 64 65 declaration_start_line = d.get("declaration_start_line") 66 if not declaration_start_line: 67 return d 68 69 d["declaration_start_line"] = start_line 70 71 parameterdesc_start_lines = d.get("parameterdesc_start_lines") 72 if parameterdesc_start_lines: 73 for key in parameterdesc_start_lines: 74 ln = parameterdesc_start_lines[key] 75 ln += start_line - declaration_start_line 76 77 parameterdesc_start_lines[key] = ln 78 79 sections_start_lines = d.get("sections_start_lines") 80 if sections_start_lines: 81 for key in sections_start_lines: 82 ln = sections_start_lines[key] 83 ln += start_line - declaration_start_line 84 85 sections_start_lines[key] = ln 86 87 return d 88 89 def output_symbols(self, fname, symbols): 90 """ 91 Store source, symbols and output strings at self.tests. 92 """ 93 94 # 95 # KdocItem needs to be converted into dicts 96 # 97 kdoc_item = [] 98 expected = [] 99 100 # 101 # Source code didn't produce any symbol 102 # 103 if not symbols: 104 return 105 106 expected_dict = {} 107 start_line=1 108 109 for arg in symbols: 110 source = arg.get("source", "") 111 112 if arg and "KdocItem" in self.yaml_content: 113 msg = self.get_kdoc_item(arg) 114 115 other_stuff = msg.get("other_stuff", {}) 116 if "source" in other_stuff: 117 del other_stuff["source"] 118 119 expected_dict["kdoc_item"] = msg 120 121 base_name = arg.name 122 if not base_name: 123 base_name = fname 124 base_name = base_name.lower().replace(".", "_").replace("/", "_") 125 126 127 # Don't add duplicated names 128 i = 0 129 name = base_name 130 while name in self.test_names: 131 i += 1 132 name = f"{base_name}_{i:03d}" 133 134 self.test_names.add(name) 135 136 for out_style in self.out_style: 137 if isinstance(out_style, ManFormat): 138 key = "man" 139 else: 140 key = "rst" 141 142 expected_dict[key]= out_style.output_symbols(fname, [arg]).strip() 143 144 test = { 145 "name": name, 146 "description": f"{fname} line {arg.declaration_start_line}", 147 "fname": fname, 148 "source": source, 149 "expected": [expected_dict] 150 } 151 152 self.tests.append(test) 153 154 expected_dict = {} 155 156 def write(self): 157 """ 158 Output the content of self.tests to self.test_file. 159 """ 160 import yaml 161 162 # Helper function to better handle multilines 163 def str_presenter(dumper, data): 164 if "\n" in data: 165 return dumper.represent_scalar("tag:yaml.org,2002:str", data, style="|") 166 167 return dumper.represent_scalar("tag:yaml.org,2002:str", data) 168 169 # Register the representer 170 yaml.add_representer(str, str_presenter) 171 172 data = {"tests": self.tests} 173 174 with open(self.test_file, "w", encoding="utf-8") as fp: 175 yaml.dump(data, fp, 176 sort_keys=False, width=120, indent=2, 177 default_flow_style=False, allow_unicode=True, 178 explicit_start=False, explicit_end=False) 179