rebase: fix on-line location extraction from devhelp files
[gtk-doc.git] / gtkdoc / check.py
blob7f32b605662ff530d3097c59a33475a6cdea58ff
1 # -*- python; coding: utf-8 -*-
3 # gtk-doc - GTK DocBook documentation generator.
4 # Copyright (C) 2007 David Nečas
5 # 2007-2017 Stefan Sauer
7 # This program is free software; you can redistribute it and/or modify
8 # it under the terms of the GNU General Public License as published by
9 # the Free Software Foundation; either version 2 of the License, or
10 # (at your option) any later version.
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU General Public License for more details.
17 # You should have received a copy of the GNU General Public License
18 # along with this program; if not, write to the Free Software
19 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
22 """
23 The check tool runs various tests on built documentation and outputs test
24 results. Can be run druring make check, by adding this to the documentations
25 Makefile.am: TESTS = $(GTKDOC_CHECK).
26 """
28 # Support both Python 2 and 3
29 from __future__ import print_function
31 import os
32 import re
33 from glob import glob
35 from . import common
38 class FileFormatError(Exception):
39 pass
42 def grep(regexp, lines, what):
43 pattern = re.compile(regexp)
44 for line in lines:
45 for match in re.finditer(pattern, line):
46 return match.group(1)
47 raise FileFormatError(what)
50 def check_empty(filename):
51 with open(filename, "rb") as f:
52 count = sum(1 for line in f if line.strip())
53 return count
56 def check_includes(filename):
57 # Check that each XML file in the xml directory is included in doc_main_file
58 with common.open_text(filename) as f:
59 lines = f.read().splitlines()
60 num_missing = 0
61 for include in glob('xml/*.xml'):
62 try:
63 next(line for line in lines if include in line)
64 except StopIteration:
65 num_missing += 1
66 print('%s:1:E: doesn\'t appear to include "%s"' % (filename, include))
68 return num_missing
71 def get_variable(env, lines, variable):
72 value = env.get(variable,
73 grep(r'^\s*' + variable + r'\s*=\s*(\S+)', lines, variable))
74 return value
77 def read_file(filename):
78 with common.open_text(filename) as f:
79 return f.read().splitlines()
82 def run_tests(workdir, doc_module, doc_main_file):
83 checks = 4
85 print('Running suite(s): gtk-doc-' + doc_module)
87 # Test #1
88 statusfilename = os.path.join(workdir, doc_module + '-undocumented.txt')
89 statusfile = read_file(statusfilename)
90 try:
91 undocumented = int(grep(r'^(\d+)\s+not\s+documented\.\s*$',
92 statusfile, 'number of undocumented symbols'))
93 incomplete = int(grep(r'^(\d+)\s+symbols?\s+incomplete\.\s*$',
94 statusfile, 'number of incomplete symbols'))
95 except FileFormatError as e:
96 print('Cannot find %s in %s' % (str(e), statusfilename))
97 return checks # consider all failed
99 total = undocumented + incomplete
100 if total:
101 print(doc_module + '-undocumented.txt:1:E: %d undocumented or incomplete symbols' % total)
103 # Test #2
104 undeclared = check_empty(os.path.join(workdir, doc_module + '-undeclared.txt'))
105 if undeclared:
106 print(doc_module + '-undeclared.txt:1:E: %d undeclared symbols\n' % undeclared)
108 # Test #3
109 unused = check_empty(os.path.join(workdir, doc_module + '-unused.txt'))
110 if unused:
111 print(doc_module + '-unused.txt:1:E: %d unused documentation entries\n' % unused)
113 # Test #4
114 missing_includes = check_includes(os.path.join(workdir, doc_main_file))
116 # Test Summary
117 failed = (total > 0) + (undeclared != 0) + (unused != 0) + (missing_includes != 0)
118 rate = 100.0 * (checks - failed) / checks
119 print("%.1f%%: Checks %d, Failures: %d" % (rate, checks, failed))
120 return failed
123 def run(options=None):
124 """Runs the tests.
126 Returns:
127 int: a system exit code.
130 # Get parameters from test env, if not there try to grab them from the makefile
131 # We like Makefile.am more but builddir does not necessarily contain one.
132 makefilename = 'Makefile.am'
133 if not os.path.exists(makefilename):
134 makefilename = 'Makefile'
135 makefile = []
136 try:
137 makefile = read_file(makefilename)
138 except (OSError, IOError):
139 pass
141 # For historic reasons tests are launched in srcdir
142 workdir = os.environ.get('BUILDDIR', None)
143 if not workdir:
144 workdir = '.'
146 try:
147 doc_module = get_variable(os.environ, makefile, 'DOC_MODULE')
148 doc_main_file = get_variable(os.environ, makefile, 'DOC_MAIN_SGML_FILE')
149 except FileFormatError as e:
150 print('Cannot find %s in %s' % (str(e), makefilename))
151 return 1
153 doc_main_file = doc_main_file.replace('$(DOC_MODULE)', doc_module)
155 return run_tests(workdir, doc_module, doc_main_file)