| # SPDX-License-Identifier: Apache-2.0 |
| # Copyright 2016-2021 The Meson development team |
| |
| import re |
| import unittest |
| from itertools import chain |
| from pathlib import Path |
| from unittest import mock |
| |
| import mesonbuild.mlog |
| import mesonbuild.depfile |
| import mesonbuild.dependencies.base |
| import mesonbuild.dependencies.factory |
| import mesonbuild.envconfig |
| import mesonbuild.environment |
| import mesonbuild.coredata |
| import mesonbuild.options |
| import mesonbuild.modules.gnome |
| from mesonbuild.interpreter import Interpreter |
| from mesonbuild.ast import AstInterpreter |
| from mesonbuild.mesonlib import MachineChoice |
| from mesonbuild.options import OptionKey |
| from mesonbuild.compilers import ( |
| detect_c_compiler, detect_cpp_compiler |
| ) |
| import mesonbuild.modules.pkgconfig |
| |
| |
| from run_tests import ( |
| FakeBuild, get_fake_env |
| ) |
| |
| from .helpers import * |
| |
| @unittest.skipIf(is_tarball(), 'Skipping because this is a tarball release') |
| class DataTests(unittest.TestCase): |
| |
| def test_snippets(self): |
| hashcounter = re.compile('^ *(#)+') |
| snippet_dir = Path('docs/markdown/snippets') |
| self.assertTrue(snippet_dir.is_dir()) |
| for f in snippet_dir.glob('*'): |
| self.assertTrue(f.is_file()) |
| if f.parts[-1].endswith('~'): |
| continue |
| if f.suffix == '.md': |
| in_code_block = False |
| with f.open(encoding='utf-8') as snippet: |
| for line in snippet: |
| if line.startswith(' '): |
| continue |
| if line.startswith('```'): |
| in_code_block = not in_code_block |
| if in_code_block: |
| continue |
| m = re.match(hashcounter, line) |
| if m: |
| self.assertEqual(len(m.group(0)), 2, 'All headings in snippets must have two hash symbols: ' + f.name) |
| self.assertFalse(in_code_block, 'Unclosed code block.') |
| else: |
| if f.name != 'add_release_note_snippets_here': |
| self.assertTrue(False, 'A file without .md suffix in snippets dir: ' + f.name) |
| |
| def test_compiler_options_documented(self): |
| ''' |
| Test that C and C++ compiler options and base options are documented in |
| Builtin-Options.md. Only tests the default compiler for the current |
| platform on the CI. |
| ''' |
| md = None |
| with open('docs/markdown/Builtin-options.md', encoding='utf-8') as f: |
| md = f.read() |
| self.assertIsNotNone(md) |
| env = get_fake_env() |
| # FIXME: Support other compilers |
| cc = detect_c_compiler(env, MachineChoice.HOST) |
| cpp = detect_cpp_compiler(env, MachineChoice.HOST) |
| for comp in (cc, cpp): |
| for opt in comp.get_options(): |
| self.assertIn(str(opt), md) |
| for opt in comp.base_options: |
| self.assertIn(str(opt), md) |
| self.assertNotIn('b_unknown', md) |
| |
| @staticmethod |
| def _get_section_content(name, sections, md): |
| for section in sections: |
| if section and section.group(1) == name: |
| try: |
| next_section = next(sections) |
| end = next_section.start() |
| except StopIteration: |
| end = len(md) |
| # Extract the content for this section |
| return md[section.end():end] |
| raise RuntimeError(f'Could not find "{name}" heading') |
| |
| def test_builtin_options_documented(self): |
| ''' |
| Test that universal options and base options are documented in |
| Builtin-Options.md. |
| ''' |
| from itertools import tee |
| md = None |
| with open('docs/markdown/Builtin-options.md', encoding='utf-8') as f: |
| md = f.read() |
| self.assertIsNotNone(md) |
| |
| found_entries = set() |
| sections = re.finditer(r"^## (.+)$", md, re.MULTILINE) |
| # Extract the content for this section |
| u_subcontents = [] |
| content = self._get_section_content("Universal options", sections, md) |
| subsections = tee(re.finditer(r"^### (.+)$", content, re.MULTILINE)) |
| u_subcontents.append(self._get_section_content("Directories", subsections[0], content)) |
| u_subcontents.append(self._get_section_content("Core options", subsections[1], content)) |
| |
| mod_subcontents = [] |
| content = self._get_section_content("Module options", sections, md) |
| subsections = tee(re.finditer(r"^### (.+)$", content, re.MULTILINE)) |
| for idx, mod in enumerate(['Pkgconfig', 'Python']): |
| mod_subcontents.append(self._get_section_content(f'{mod} module', subsections[idx], content)) |
| for subcontent in u_subcontents + mod_subcontents: |
| # Find the option names |
| options = set() |
| # Match either a table row or a table heading separator: | ------ | |
| rows = re.finditer(r"^\|(?: (\w+) .* | *-+ *)\|", subcontent, re.MULTILINE) |
| # Skip the header of the first table |
| next(rows) |
| # Skip the heading separator of the first table |
| next(rows) |
| for m in rows: |
| value = m.group(1) |
| # End when the `buildtype` table starts |
| if value is None: |
| break |
| options.add(value) |
| self.assertEqual(len(found_entries & options), 0) |
| found_entries |= options |
| |
| self.assertEqual(found_entries, { |
| *(str(k.without_module_prefix()) for k in mesonbuild.options.BUILTIN_OPTIONS), |
| *(str(k.without_module_prefix()) for k in mesonbuild.options.BUILTIN_OPTIONS_PER_MACHINE), |
| }) |
| |
| # Check that `buildtype` table inside `Core options` matches how |
| # setting of builtin options behaves |
| # |
| # Find all tables inside this subsection |
| tables = re.finditer(r"^\| (\w+) .* \|\n\| *[-|\s]+ *\|$", u_subcontents[1], re.MULTILINE) |
| # Get the table we want using the header of the first column |
| table = self._get_section_content('buildtype', tables, u_subcontents[1]) |
| # Get table row data |
| rows = re.finditer(r"^\|(?: (\w+)\s+\| (\w+)\s+\| (\w+) .* | *-+ *)\|", table, re.MULTILINE) |
| env = get_fake_env() |
| for m in rows: |
| buildtype, debug, opt = m.groups() |
| if debug == 'true': |
| debug = True |
| elif debug == 'false': |
| debug = False |
| else: |
| raise RuntimeError(f'Invalid debug value {debug!r} in row:\n{m.group()}') |
| env.coredata.set_option(OptionKey('buildtype'), buildtype) |
| self.assertEqual(env.coredata.optstore.get_value('buildtype'), buildtype) |
| self.assertEqual(env.coredata.optstore.get_value('optimization'), opt) |
| self.assertEqual(env.coredata.optstore.get_value('debug'), debug) |
| |
| def test_cpu_families_documented(self): |
| with open("docs/markdown/Reference-tables.md", encoding='utf-8') as f: |
| md = f.read() |
| self.assertIsNotNone(md) |
| |
| sections = re.finditer(r"^## (.+)$", md, re.MULTILINE) |
| content = self._get_section_content("CPU families", sections, md) |
| # Find the list entries |
| arches = [m.group(1) for m in re.finditer(r"^\| (\w+) +\|", content, re.MULTILINE)] |
| # Drop the header |
| arches = set(arches[1:]) |
| self.assertEqual(arches, set(mesonbuild.environment.known_cpu_families)) |
| |
| def test_markdown_files_in_sitemap(self): |
| ''' |
| Test that each markdown files in docs/markdown is referenced in sitemap.txt |
| ''' |
| with open("docs/sitemap.txt", encoding='utf-8') as f: |
| md = f.read() |
| self.assertIsNotNone(md) |
| toc = list(m.group(1) for m in re.finditer(r"^\s*(\w.*)$", md, re.MULTILINE)) |
| markdownfiles = [f.name for f in Path("docs/markdown").iterdir() if f.is_file() and f.suffix == '.md'] |
| exceptions = ['_Sidebar.md'] |
| for f in markdownfiles: |
| if f not in exceptions and not f.startswith('_include'): |
| self.assertIn(f, toc) |
| |
| def test_modules_in_navbar(self): |
| ''' |
| Test that each module is referenced in navbar_links.html |
| ''' |
| with open("docs/theme/extra/templates/navbar_links.html", encoding='utf-8') as f: |
| html = f.read().lower() |
| self.assertIsNotNone(html) |
| for f in Path('mesonbuild/modules').glob('*.py'): |
| if f.name.startswith('_') or f.name == 'modtest.py': |
| continue |
| name = f'{f.stem}-module.html' |
| name = name.replace('unstable_', '') |
| name = name.replace('python3', 'python-3') |
| name = name.replace('_', '-') |
| self.assertIn(name, html) |
| |
| @mock.patch.dict(os.environ) |
| @mock.patch.object(Interpreter, 'load_root_meson_file', mock.Mock(return_value=None)) |
| @mock.patch.object(Interpreter, 'sanity_check_ast', mock.Mock(return_value=None)) |
| @mock.patch.object(Interpreter, 'parse_project', mock.Mock(return_value=None)) |
| def test_vim_syntax_highlighting(self): |
| ''' |
| Ensure that vim syntax highlighting files were updated for new |
| functions in the global namespace in build files. |
| ''' |
| # Disable unit test specific syntax |
| del os.environ['MESON_RUNNING_IN_PROJECT_TESTS'] |
| env = get_fake_env() |
| interp = Interpreter(FakeBuild(env)) |
| with open('data/syntax-highlighting/vim/syntax/meson.vim', encoding='utf-8') as f: |
| res = re.search(r'syn keyword mesonBuiltin(\s+\\\s\w+)+', f.read(), re.MULTILINE) |
| defined = set([a.strip() for a in res.group().split('\\')][1:]) |
| self.assertEqual(defined, set(chain(interp.funcs.keys(), interp.builtin.keys()))) |
| |
| @mock.patch.dict(os.environ) |
| @mock.patch.object(Interpreter, 'load_root_meson_file', mock.Mock(return_value=None)) |
| @mock.patch.object(Interpreter, 'sanity_check_ast', mock.Mock(return_value=None)) |
| @mock.patch.object(Interpreter, 'parse_project', mock.Mock(return_value=None)) |
| def test_all_functions_defined_in_ast_interpreter(self): |
| ''' |
| Ensure that the all functions defined in the Interpreter are also defined |
| in the AstInterpreter (and vice versa). |
| ''' |
| # Disable unit test specific syntax |
| del os.environ['MESON_RUNNING_IN_PROJECT_TESTS'] |
| env = get_fake_env() |
| interp = Interpreter(FakeBuild(env)) |
| astint = AstInterpreter('.', '', '') |
| self.assertEqual(set(interp.funcs.keys()), set(astint.funcs.keys())) |