Storing kernel-doc output is helpful to allow debugging problems on it and to preparate unit tests. Add a class to store such contents at the same format as defined at kdoc-test-schema.yaml.
Showing the changes to tools/lib/python (1 of 1 file). Show all
Unified · Split
| @@ -0,0 +1,155 @@ | ||
| 1 | +#!/usr/bin/env python3 | |
| 2 | +# SPDX-License-Identifier: GPL-2.0 | |
| 3 | +# Copyright(c) 2026: Mauro Carvalho Chehab <mchehab@kernel.org>. | |
| 4 | + | |
| 5 | +import os | |
| 6 | + | |
| 7 | +from kdoc.kdoc_output import ManFormat, RestFormat | |
| 8 | + | |
| 9 | + | |
| 10 | +class 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 | + | |
| 29 | + self.tests = [] | |
| 30 | + | |
| 31 | + out_dir = os.path.dirname(self.test_file) | |
| 32 | + if out_dir and not os.path.isdir(out_dir): | |
| 33 | + sys.exit(f"Directory {out_dir} doesn't exist.") | |
| 34 | + | |
| 35 | + self.out_style = [] | |
| 36 | + | |
| 37 | + if "man" in self.yaml_content: | |
| 38 | + out_style = ManFormat() | |
| 39 | + out_style.set_config(self.config) | |
| 40 | + | |
| 41 | + self.out_style.append(out_style) | |
| 42 | + | |
| 43 | + if "rst" in self.yaml_content: | |
| 44 | + out_style = RestFormat() | |
| 45 | + out_style.set_config(self.config) | |
| 46 | + | |
| 47 | + self.out_style.append(out_style) | |
| 48 | + | |
| 49 | + def set_filter(self, export, internal, symbol, nosymbol, | |
| 50 | + function_table, enable_lineno, no_doc_sections): | |
| 51 | + """ | |
| 52 | + Set filters at the output classes. | |
| 53 | + """ | |
| 54 | + for out_style in self.out_style: | |
| 55 | + out_style.set_filter(export, internal, symbol, | |
| 56 | + nosymbol, function_table, | |
| 57 | + enable_lineno, no_doc_sections) | |
| 58 | + | |
| 59 | + @staticmethod | |
| 60 | + def get_kdoc_item(arg, start_line=1): | |
| 61 | + | |
| 62 | + d = vars(arg) | |
| 63 | + | |
| 64 | + declaration_start_line = d.get("declaration_start_line") | |
| 65 | + if not declaration_start_line: | |
| 66 | + return d | |
| 67 | + | |
| 68 | + d["declaration_start_line"] = start_line | |
| 69 | + | |
| 70 | + parameterdesc_start_lines = d.get("parameterdesc_start_lines") | |
| 71 | + if parameterdesc_start_lines: | |
| 72 | + for key in parameterdesc_start_lines: | |
| 73 | + ln = parameterdesc_start_lines[key] | |
| 74 | + ln += start_line - declaration_start_line | |
| 75 | + | |
| 76 | + parameterdesc_start_lines[key] = ln | |
| 77 | + | |
| 78 | + sections_start_lines = d.get("sections_start_lines") | |
| 79 | + if sections_start_lines: | |
| 80 | + for key in sections_start_lines: | |
| 81 | + ln = sections_start_lines[key] | |
| 82 | + ln += start_line - declaration_start_line | |
| 83 | + | |
| 84 | + sections_start_lines[key] = ln | |
| 85 | + | |
| 86 | + return d | |
| 87 | + | |
| 88 | + def output_symbols(self, fname, symbols, source): | |
| 89 | + """ | |
| 90 | + Store source, symbols and output strings at self.tests. | |
| 91 | + """ | |
| 92 | + | |
| 93 | + # | |
| 94 | + # KdocItem needs to be converted into dicts | |
| 95 | + # | |
| 96 | + kdoc_item = [] | |
| 97 | + expected = [] | |
| 98 | + | |
| 99 | + if not symbols and not source: | |
| 100 | + return | |
| 101 | + | |
| 102 | + if not source or len(symbols) != len(source): | |
| 103 | + print(f"Warning: lengths are different. Ignoring {fname}") | |
| 104 | + | |
| 105 | + # Folding without line numbers is too hard. | |
| 106 | + # The right thing to do here to proceed would be to delete | |
| 107 | + # not-handled source blocks, as len(source) should be bigger | |
| 108 | + # than len(symbols) | |
| 109 | + return | |
| 110 | + | |
| 111 | + base_name = "test_" + fname.replace(".", "_").replace("/", "_") | |
| 112 | + expected_dict = {} | |
| 113 | + start_line=1 | |
| 114 | + | |
| 115 | + for i in range(0, len(symbols)): | |
| 116 | + arg = symbols[i] | |
| 117 | + | |
| 118 | + if "KdocItem" in self.yaml_content: | |
| 119 | + msg = self.get_kdoc_item(arg) | |
| 120 | + | |
| 121 | + expected_dict["kdoc_item"] = msg | |
| 122 | + | |
| 123 | + for out_style in self.out_style: | |
| 124 | + if isinstance(out_style, ManFormat): | |
| 125 | + key = "man" | |
| 126 | + else: | |
| 127 | + key = "rst" | |
| 128 | + | |
| 129 | + expected_dict[key]= out_style.output_symbols(fname, [arg]) | |
| 130 | + | |
| 131 | + name = f"{base_name}_{i:03d}" | |
| 132 | + | |
| 133 | + test = { | |
| 134 | + "name": name, | |
| 135 | + "description": f"{fname} line {source[i]["ln"]}", | |
| 136 | + "fname": fname, | |
| 137 | + "source": source[i]["data"], | |
| 138 | + "expected": [expected_dict] | |
| 139 | + } | |
| 140 | + | |
| 141 | + self.tests.append(test) | |
| 142 | + | |
| 143 | + expected_dict = {} | |
| 144 | + | |
| 145 | + def write(self): | |
| 146 | + """ | |
| 147 | + Output the content of self.tests to self.test_file. | |
| 148 | + """ | |
| 149 | + import yaml | |
| 150 | + | |
| 151 | + data = {"tests": self.tests} | |
| 152 | + | |
| 153 | + with open(self.test_file, "w", encoding="utf-8") as fp: | |
| 154 | + yaml.safe_dump(data, fp, sort_keys=False, default_style="|", | |
| 155 | + default_flow_style=False, allow_unicode=True) | |