xref: /openbsd-src/gnu/llvm/libcxx/utils/generate_header_inclusion_tests.py (revision d415bd752c734aee168c4ee86ff32e8cc249eb16)
1#!/usr/bin/env python
2
3import os
4
5
6def get_libcxx_paths():
7    utils_path = os.path.dirname(os.path.abspath(__file__))
8    script_name = os.path.basename(__file__)
9    assert os.path.exists(utils_path)
10    src_root = os.path.dirname(utils_path)
11    test_path = os.path.join(src_root, 'test', 'libcxx', 'inclusions')
12    assert os.path.exists(test_path)
13    assert os.path.exists(os.path.join(test_path, 'algorithm.inclusions.compile.pass.cpp'))
14    return script_name, src_root, test_path
15
16
17script_name, source_root, test_path = get_libcxx_paths()
18
19
20# This table was produced manually, by grepping the TeX source of the Standard's
21# library clauses for the string "#include". Each header's synopsis contains
22# explicit "#include" directives for its mandatory inclusions.
23# For example, [algorithm.syn] contains "#include <initializer_list>".
24#
25mandatory_inclusions = {
26    "algorithm": ["initializer_list"],
27    "array": ["compare", "initializer_list"],
28    "bitset": ["iosfwd", "string"],
29    "chrono": ["compare"],
30    "cinttypes": ["cstdint"],
31    "complex.h": ["complex"],
32    # TODO "coroutine": ["compare"],
33    "deque": ["compare", "initializer_list"],
34    "filesystem": ["compare"],
35    "forward_list": ["compare", "initializer_list"],
36    "ios": ["iosfwd"],
37    "iostream": ["ios", "istream", "ostream", "streambuf"],
38    "iterator": ["compare", "concepts"],
39    "list": ["compare", "initializer_list"],
40    "map": ["compare", "initializer_list"],
41    "memory": ["compare"],
42    "optional": ["compare"],
43    "queue": ["compare", "initializer_list"],
44    "random": ["initializer_list"],
45    "ranges": ["compare", "initializer_list", "iterator"],
46    "regex": ["compare", "initializer_list"],
47    "set": ["compare", "initializer_list"],
48    "stack": ["compare", "initializer_list"],
49    "string_view": ["compare"],
50    "string": ["compare", "initializer_list"],
51    # TODO "syncstream": ["ostream"],
52    "system_error": ["compare"],
53    "tgmath.h": ["cmath", "complex"],
54    "thread": ["compare"],
55    "tuple": ["compare"],
56    "typeindex": ["compare"],
57    "unordered_map": ["compare", "initializer_list"],
58    "unordered_set": ["compare", "initializer_list"],
59    "utility": ["compare", "initializer_list"],
60    "valarray": ["initializer_list"],
61    "variant": ["compare"],
62    "vector": ["compare", "initializer_list"],
63}
64
65new_in_version = {
66    "chrono": "11",
67    "compare": "20",
68    "concepts": "20",
69    "coroutine": "20",
70    "filesystem": "17",
71    "initializer_list": "11",
72    "optional": "17",
73    "ranges": "20",
74    "string_view": "17",
75    "syncstream": "20",
76    "system_error": "11",
77    "thread": "11",
78    "tuple": "11",
79    "unordered_map": "11",
80    "unordered_set": "11",
81    "variant": "17",
82}
83
84assert all(v == sorted(v) for k, v in mandatory_inclusions.items())
85
86# Map from each header to the Lit annotations that should be used for
87# tests that include that header.
88#
89# For example, when threads are not supported, any test
90# that includes <thread> should be marked as UNSUPPORTED, because including
91# <thread> is a hard error in that case.
92lit_markup = {
93  "atomic": ["UNSUPPORTED: libcpp-has-no-threads"],
94  "barrier": ["UNSUPPORTED: libcpp-has-no-threads"],
95  "filesystem": ["UNSUPPORTED: libcpp-has-no-filesystem-library"],
96  "format": ["UNSUPPORTED: libcpp-has-no-incomplete-format"],
97  "iomanip": ["UNSUPPORTED: libcpp-has-no-localization"],
98  "ios": ["UNSUPPORTED: libcpp-has-no-localization"],
99  "iostream": ["UNSUPPORTED: libcpp-has-no-localization"],
100  "istream": ["UNSUPPORTED: libcpp-has-no-localization"],
101  "latch": ["UNSUPPORTED: libcpp-has-no-threads"],
102  "locale": ["UNSUPPORTED: libcpp-has-no-localization"],
103  "ostream": ["UNSUPPORTED: libcpp-has-no-localization"],
104  "ranges": ["UNSUPPORTED: libcpp-has-no-incomplete-ranges"],
105  "regex": ["UNSUPPORTED: libcpp-has-no-localization"],
106  "semaphore": ["UNSUPPORTED: libcpp-has-no-threads"],
107  "shared_mutex": ["UNSUPPORTED: libcpp-has-no-threads"],
108  "thread": ["UNSUPPORTED: libcpp-has-no-threads"],
109}
110
111
112def get_std_ver_test(includee):
113    v = new_in_version.get(includee, "03")
114    if v == "03":
115        return ''
116    versions = ["03", "11", "14", "17", "20"]
117    return 'TEST_STD_VER > {} && '.format(max(i for i in versions if i < v))
118
119
120def get_unsupported_line(includee):
121    v = new_in_version.get(includee, "03")
122    return {
123        "03": [],
124        "11": ['UNSUPPORTED: c++03'],
125        "14": ['UNSUPPORTED: c++03, c++11'],
126        "17": ['UNSUPPORTED: c++03, c++11, c++14'],
127        "20": ['UNSUPPORTED: c++03, c++11, c++14, c++17'],
128        "2b": ['UNSUPPORTED: c++03, c++11, c++14, c++17, c++20'],
129    }[v]
130
131
132def get_libcpp_header_symbol(header_name):
133    return '_LIBCPP_' + header_name.upper().replace('.', '_')
134
135
136def get_includer_symbol_test(includer):
137    symbol = get_libcpp_header_symbol(includer)
138    return """
139#if !defined({symbol})
140 #   error "{message}"
141#endif
142    """.strip().format(
143        symbol=symbol,
144        message="<{}> was expected to define {}".format(includer, symbol),
145    )
146
147
148def get_ifdef(includer, includee):
149    version = max(new_in_version.get(h, "03") for h in [includer, includee])
150    symbol = get_libcpp_header_symbol(includee)
151    return """
152#if {includee_test}!defined({symbol})
153 #   error "{message}"
154#endif
155    """.strip().format(
156        includee_test=get_std_ver_test(includee),
157        symbol=symbol,
158        message="<{}> should include <{}> in C++{} and later".format(includer, includee, version)
159    )
160
161
162test_body_template = """
163//===----------------------------------------------------------------------===//
164//
165// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
166// See https://llvm.org/LICENSE.txt for license information.
167// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
168//
169//===----------------------------------------------------------------------===//
170//
171// WARNING: This test was generated by {script_name}
172// and should not be edited manually.
173//
174// clang-format off
175{markup}
176// <{header}>
177
178// Test that <{header}> includes all the other headers it's supposed to.
179
180#include <{header}>
181#include "test_macros.h"
182
183{test_includers_symbol}
184{test_per_includee}
185""".strip()
186
187
188def produce_tests():
189    for includer, includees in mandatory_inclusions.items():
190        markup_tags = get_unsupported_line(includer) + lit_markup.get(includer, [])
191        test_body = test_body_template.format(
192            script_name=script_name,
193            header=includer,
194            markup=('\n' + '\n'.join('// ' + m for m in markup_tags) + '\n') if markup_tags else '',
195            test_includers_symbol=get_includer_symbol_test(includer),
196            test_per_includee='\n'.join(get_ifdef(includer, includee) for includee in includees),
197        )
198        test_name = "{header}.inclusions.compile.pass.cpp".format(header=includer)
199        out_path = os.path.join(test_path, test_name)
200        with open(out_path, 'w', newline='\n') as f:
201            f.write(test_body + '\n')
202
203
204if __name__ == '__main__':
205    produce_tests()
206