1import json 2import pytest 3import conftest 4import os 5import re 6 7 8@pytest.fixture() 9def json_doc(): 10 json_filepath = os.path.join(conftest.BINDIR, 'doc/hlwm-doc.json') 11 with open(json_filepath, 'r') as fh: 12 doc = json.loads(fh.read()) 13 return doc 14 15 16def create_client(hlwm): 17 winid, _ = hlwm.create_client() 18 return f'clients.{winid}' 19 20 21def create_clients_with_all_links(hlwm): 22 # enforce that 'clients.focus' exists 23 winid, _ = hlwm.create_client() 24 # enforce that 'clients.dragged' exists 25 hlwm.call('floating on') 26 hlwm.call('drag "" move') 27 return 'clients' 28 29 30def create_frame_split(hlwm): 31 hlwm.call('split explode') 32 return 'tags.0.tiling.root' 33 34 35def create_tag_with_all_links(hlwm): 36 """create a tag with focused_client set""" 37 hlwm.create_client() 38 return 'tags.0' 39 40 41# map every c++ class name to a function ("constructor") accepting an hlwm 42# fixture and returning the path to an example object of the C++ class 43classname2examplepath = [ 44 ('ByName', lambda _: 'monitors.by-name'), 45 ('Client', create_client), 46 ('ClientManager', create_clients_with_all_links), 47 ('DecTriple', lambda _: 'theme.tiling'), 48 ('DecorationScheme', lambda _: 'theme.tiling.urgent'), 49 ('FrameLeaf', lambda _: 'tags.0.tiling.root'), 50 ('FrameSplit', create_frame_split), 51 ('HSTag', create_tag_with_all_links), 52 ('Monitor', lambda _: 'monitors.0'), 53 ('MonitorManager', lambda _: 'monitors'), 54 ('Root', lambda _: ''), 55 ('Settings', lambda _: 'settings'), 56 ('TagManager', lambda _: 'tags'), 57 ('Theme', lambda _: 'theme'), 58] 59 60 61@pytest.mark.parametrize('clsname,object_path', classname2examplepath) 62def test_documented_attributes_writable(hlwm, clsname, object_path, json_doc): 63 """test whether the writable field is correct. This checks the 64 existence of the attributes implicitly 65 """ 66 object_path = object_path(hlwm) 67 for _, attr in json_doc['objects'][clsname]['attributes'].items(): 68 print("checking attribute {}::{}".format(clsname, attr['cpp_name'])) 69 full_attr_path = '{}.{}'.format(object_path, attr['name']).lstrip('.') 70 value = hlwm.get_attr(full_attr_path) 71 if value == 'default': 72 continue 73 if attr['writable']: 74 hlwm.call(['set_attr', full_attr_path, value]) 75 else: 76 hlwm.call_xfail(['set_attr', full_attr_path, value]) \ 77 .expect_stderr('attribute is read-only') 78 79 80def types_and_shorthands(): 81 """a mapping from type names in the json doc to their 82 one letter short hands in the output of 'attr' 83 """ 84 return { 85 'int': 'i', 86 'uint': 'u', 87 'bool': 'b', 88 'decimal': 'd', 89 'color': 'c', 90 'string': 's', 91 'regex': 'r', 92 'SplitAlign': 'n', 93 'LayoutAlgorithm': 'n', 94 'font': 'f', 95 'Rectangle': 'R', 96 } 97 98 99@pytest.mark.parametrize('clsname,object_path', classname2examplepath) 100def test_documented_attribute_type(hlwm, clsname, object_path, json_doc): 101 object_path = object_path(hlwm) 102 attr_output = hlwm.call(['attr', object_path]).stdout.splitlines() 103 attr_output = [line.split(' ') for line in attr_output if '=' in line] 104 attrname2shorttype = {line[4]: line[1] for line in attr_output} 105 fulltype2shorttype = types_and_shorthands() 106 for _, attr in json_doc['objects'][clsname]['attributes'].items(): 107 assert fulltype2shorttype[attr['type']] == attrname2shorttype[attr['name']] 108 109 110@pytest.mark.parametrize('clsname,object_path', classname2examplepath) 111def test_documented_children_exist(hlwm, clsname, object_path, json_doc): 112 object_path = object_path(hlwm) 113 object_path_dot = object_path + '.' if object_path != '' else '' 114 for _, child in json_doc['objects'][clsname]['children'].items(): 115 hlwm.call(['object_tree', object_path_dot + child['name']]) 116 117 118@pytest.mark.parametrize('clsname,object_path', classname2examplepath) 119def test_attributes_and_children_are_documented(hlwm, clsname, object_path, json_doc): 120 # if a path matches the following re, then it's OK if it 121 # is not mentioned explicitly in the docs 122 undocumented_paths = '|'.join([ 123 r'tags\.[0-9]+', 124 r'clients\.0x[0-9a-f]+', 125 r'monitors\.[0-9]+', 126 r'tags\.by-name\.default', 127 ]) 128 undocumented_path_re = re.compile(r'^({})[\. ]*$'.format(undocumented_paths)) 129 130 object_path = object_path(hlwm) 131 object_path_dot = object_path + '.' if object_path != '' else '' 132 133 entries = hlwm.complete(['get_attr', object_path_dot], position=1, partial=True) 134 for full_entry_path in entries: 135 if undocumented_path_re.match(full_entry_path): 136 continue 137 assert full_entry_path[0:len(object_path_dot)] == object_path_dot 138 entry = full_entry_path[len(object_path_dot):] 139 if entry[-1] == '.': 140 assert entry[0:-1] in json_doc['objects'][clsname]['children'] 141 else: 142 assert entry[-1] == ' ', "it's an attribute if it's no child" 143 assert entry[0:-1] in json_doc['objects'][clsname]['attributes'] 144 145 146@pytest.mark.parametrize('clsname,object_path', classname2examplepath) 147def test_class_doc(hlwm, clsname, object_path, json_doc): 148 path = object_path(hlwm) 149 attr_output = hlwm.call(['attr', path]).stdout 150 151 object_doc = json_doc['objects'][clsname].get('doc', None) 152 if object_doc is not None: 153 assert attr_output.startswith(object_doc) 154 else: 155 # if no class doc is in the json file, then there 156 # is indeed none: 157 assert re.match(r'1 child:|[0-9]* children[\.:]$', attr_output.splitlines()[0]) 158 159 160@pytest.mark.parametrize('clsname,object_path', classname2examplepath) 161def test_help_on_attribute_vs_json(hlwm, clsname, object_path, json_doc): 162 path = object_path(hlwm) 163 attrs_doc = json_doc['objects'][clsname]['attributes'] 164 for _, attr in attrs_doc.items(): 165 attr_name = attr['name'] 166 help_txt = hlwm.call(['help', f'{path}.{attr_name}'.lstrip('.')]).stdout 167 168 assert f"Attribute '{attr_name}'" in help_txt 169 doc = attr.get('doc', '') 170 assert doc in help_txt 171 172 173@pytest.mark.parametrize('clsname,object_path', classname2examplepath) 174def test_help_on_children_vs_json(hlwm, clsname, object_path, json_doc): 175 path = object_path(hlwm) 176 child_doc = json_doc['objects'][clsname]['children'] 177 for _, child in child_doc.items(): 178 name = child['name'] 179 help_txt = hlwm.call(['help', f'{path}.{name}'.lstrip('.')]).stdout 180 181 if 'doc' in child: 182 assert f"Entry '{name}'" in help_txt 183 assert child['doc'] in help_txt 184