aboutsummaryrefslogtreecommitdiffstats
path: root/contrib/catch2/tools/scripts/checkConvenienceHeaders.py
diff options
context:
space:
mode:
authorLexi Winter <lexi@le-fay.org>2025-06-29 19:28:09 +0100
committerLexi Winter <lexi@le-fay.org>2025-06-29 19:28:09 +0100
commit67b2fae1fa8b033045a44c1355d9dfd8f83e0d9b (patch)
tree1ecd818f4bcf7d12622d43dc92c4d4bb9b746d0f /contrib/catch2/tools/scripts/checkConvenienceHeaders.py
parenta8b0ea58e60bb0326b7f7c8f3c736d89ce9ef1df (diff)
parentbc524d70253a4ab2fe40c3ca3e5666e267c0a4d1 (diff)
downloadnihil-67b2fae1fa8b033045a44c1355d9dfd8f83e0d9b.tar.gz
nihil-67b2fae1fa8b033045a44c1355d9dfd8f83e0d9b.tar.bz2
Add 'contrib/catch2/' from commit 'bc524d70253a4ab2fe40c3ca3e5666e267c0a4d1'
git-subtree-dir: contrib/catch2 git-subtree-mainline: a8b0ea58e60bb0326b7f7c8f3c736d89ce9ef1df git-subtree-split: bc524d70253a4ab2fe40c3ca3e5666e267c0a4d1
Diffstat (limited to 'contrib/catch2/tools/scripts/checkConvenienceHeaders.py')
-rwxr-xr-xcontrib/catch2/tools/scripts/checkConvenienceHeaders.py151
1 files changed, 151 insertions, 0 deletions
diff --git a/contrib/catch2/tools/scripts/checkConvenienceHeaders.py b/contrib/catch2/tools/scripts/checkConvenienceHeaders.py
new file mode 100755
index 0000000..41b52ce
--- /dev/null
+++ b/contrib/catch2/tools/scripts/checkConvenienceHeaders.py
@@ -0,0 +1,151 @@
+#!/usr/bin/env python3
+
+"""
+Checks that all of the "catch_foo_all.hpp" headers include all subheaders.
+
+The logic is simple: given a folder, e.g. `catch2/matchers`, then the
+ccorresponding header is called `catch_matchers_all.hpp` and contains
+* all headers in `catch2/matchers`,
+* all headers in `catch2/matchers/{internal, detail}`,
+* all convenience catch_matchers_*_all.hpp headers from any non-internal subfolders
+
+The top level header is called `catch_all.hpp`.
+"""
+
+internal_dirs = ['detail', 'internal']
+
+from scriptCommon import catchPath
+from glob import glob
+from pprint import pprint
+import os
+import re
+
+def normalized_path(path):
+ """Replaces \ in paths on Windows with /"""
+ return path.replace('\\', '/')
+
+def normalized_paths(paths):
+ """Replaces \ with / in every path"""
+ return [normalized_path(path) for path in paths]
+
+source_path = catchPath + '/src/catch2'
+source_path = normalized_path(source_path)
+include_parser = re.compile(r'#include <(catch2/.+\.hpp)>')
+
+errors_found = False
+
+def headers_in_folder(folder):
+ return glob(folder + '/*.hpp')
+
+def folders_in_folder(folder):
+ return [x for x in os.scandir(folder) if x.is_dir()]
+
+def collated_includes(folder):
+ base = headers_in_folder(folder)
+ for subfolder in folders_in_folder(folder):
+ if subfolder.name in internal_dirs:
+ base.extend(headers_in_folder(subfolder.path))
+ else:
+ base.append(subfolder.path + '/catch_{}_all.hpp'.format(subfolder.name))
+ return normalized_paths(sorted(base))
+
+def includes_from_file(header):
+ includes = []
+ with open(header, 'r', encoding = 'utf-8') as file:
+ for line in file:
+ if not line.startswith('#include'):
+ continue
+ match = include_parser.match(line)
+ if match:
+ includes.append(match.group(1))
+ return normalized_paths(includes)
+
+def normalize_includes(includes):
+ """Returns """
+ return [include[len(catchPath)+5:] for include in includes]
+
+def get_duplicates(xs):
+ seen = set()
+ duplicated = []
+ for x in xs:
+ if x in seen:
+ duplicated.append(x)
+ seen.add(x)
+ return duplicated
+
+def verify_convenience_header(folder):
+ """
+ Performs the actual checking of convenience header for specific folder.
+ Checks that
+ 1) The header even exists
+ 2) That all includes in the header are sorted
+ 3) That there are no duplicated includes
+ 4) That all includes that should be in the header are actually present in the header
+ 5) That there are no superfluous includes that should not be in the header
+ """
+ global errors_found
+
+ path = normalized_path(folder.path)
+
+ assert path.startswith(source_path), '{} does not start with {}'.format(path, source_path)
+ stripped_path = path[len(source_path) + 1:]
+ path_pieces = stripped_path.split('/')
+
+ if path == source_path:
+ header_name = 'catch_all.hpp'
+ else:
+ header_name = 'catch_{}_all.hpp'.format('_'.join(path_pieces))
+
+ # 1) Does it exist?
+ full_path = path + '/' + header_name
+ if not os.path.isfile(full_path):
+ errors_found = True
+ print('Missing convenience header: {}'.format(full_path))
+ return
+ file_incs = includes_from_file(path + '/' + header_name)
+ # 2) Are the includes are sorted?
+ if sorted(file_incs) != file_incs:
+ errors_found = True
+ print("'{}': Includes are not in sorted order!".format(header_name))
+
+ # 3) Are there no duplicates?
+ duplicated = get_duplicates(file_incs)
+ for duplicate in duplicated:
+ errors_found = True
+ print("'{}': Duplicated include: '{}'".format(header_name, duplicate))
+
+ target_includes = normalize_includes(collated_includes(path))
+ # Avoid requiring the convenience header to include itself
+ target_includes = [x for x in target_includes if header_name not in x]
+ # 4) Are all required headers present?
+ file_incs_set = set(file_incs)
+ for include in target_includes:
+ if (include not in file_incs_set and
+ include != 'catch2/internal/catch_windows_h_proxy.hpp'):
+ errors_found = True
+ print("'{}': missing include '{}'".format(header_name, include))
+
+ # 5) Are there any superfluous headers?
+ desired_set = set(target_includes)
+ for include in file_incs:
+ if include not in desired_set:
+ errors_found = True
+ print("'{}': superfluous include '{}'".format(header_name, include))
+
+
+
+def walk_source_folders(current):
+ verify_convenience_header(current)
+ for folder in folders_in_folder(current.path):
+ fname = folder.name
+ if fname not in internal_dirs:
+ walk_source_folders(folder)
+
+# This is an ugly hack because we cannot instantiate DirEntry manually
+base_dir = [x for x in os.scandir(catchPath + '/src') if x.name == 'catch2']
+walk_source_folders(base_dir[0])
+
+# Propagate error "code" upwards
+if not errors_found:
+ print('Everything ok')
+exit(errors_found)