From a42177b331e4e15bbbab44e7b552a0fb3bd48cad Mon Sep 17 00:00:00 2001 From: "F.Tibor" Date: Mon, 16 Mar 2026 13:47:57 +0100 Subject: [PATCH 01/22] Integrate tests into bazel --- test/common/BUILD | 15 ++++++++ test/common/grep_check.py | 73 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 88 insertions(+) create mode 100644 test/common/BUILD create mode 100644 test/common/grep_check.py diff --git a/test/common/BUILD b/test/common/BUILD new file mode 100644 index 00000000..35047442 --- /dev/null +++ b/test/common/BUILD @@ -0,0 +1,15 @@ +# Copyright 2023 Ericsson AB +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +exports_files(["grep_check.py"]) diff --git a/test/common/grep_check.py b/test/common/grep_check.py new file mode 100644 index 00000000..d895e5ed --- /dev/null +++ b/test/common/grep_check.py @@ -0,0 +1,73 @@ +# Copyright 2023 Ericsson AB +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" +Validates wether a list of patterns is found in a file + +This test reads a file and asserts that all provided patterns +are present within its contents. + +Intended to be used as the main of a py_test Bazel target. +""" + +import argparse +import logging +import shlex +import subprocess +import sys + + +def parse_args() -> argparse.Namespace: + """ + Parse command-line arguments. + Returns: + Parsed arguments containing the file path and list of patterns. + """ + parser = argparse.ArgumentParser( + description=( + "Assert that all given patterns exist in the provided file." + ) + ) + parser.add_argument( + "--file", + required=True, + help="Path to the file to search within (e.g., a test log file).", + ) + parser.add_argument( + "--patterns", + nargs="+", + required=True, + help="One or more patterns to assert are present in the file.", + ) + return parser.parse_args() + +def main() -> None: + """Entry point for the pattern-matching test.""" + args = parse_args() + with open(args.file, "r", encoding="utf-8") as f: + content = f.read() + + all_passed = True + for pattern in args.patterns: + if pattern not in content: + print(f" [FAIL] Pattern missing: '{pattern}'") + all_passed = False + + if not all_passed: + print("\nOne or more patterns missing. Test FAILED.") + sys.exit(1) + + +if __name__ == "__main__": + main() From c9c86e8a9f6de05ab0c703f6c04479f78defcc6c Mon Sep 17 00:00:00 2001 From: "F.Tibor" Date: Thu, 19 Mar 2026 16:47:32 +0100 Subject: [PATCH 02/22] Update grep_check script Allow accepting multiple files Remove incorrect help (we cannot check log files of failing tests) --- test/common/grep_check.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/test/common/grep_check.py b/test/common/grep_check.py index d895e5ed..0c56b61e 100644 --- a/test/common/grep_check.py +++ b/test/common/grep_check.py @@ -41,14 +41,21 @@ def parse_args() -> argparse.Namespace: ) parser.add_argument( "--file", + nargs="+", required=True, - help="Path to the file to search within (e.g., a test log file).", + help="Path to the file(s) to search within.", ) parser.add_argument( "--patterns", nargs="+", required=True, - help="One or more patterns to assert are present in the file.", + help="One or more patterns to assert are present in the file(s).", + ) + parser.add_argument( + "--negative_patterns", + nargs="+", + required=True, + help="One or more patterns to assert are not present in the file(s).", ) return parser.parse_args() From ae74e8d0d7a1eec1453309671d6b25082dbc8a2c Mon Sep 17 00:00:00 2001 From: "F.Tibor" Date: Thu, 19 Mar 2026 17:13:06 +0100 Subject: [PATCH 03/22] Support regexes --- test/common/grep_check.py | 48 ++++++++++++++++++++++++++++----------- 1 file changed, 35 insertions(+), 13 deletions(-) diff --git a/test/common/grep_check.py b/test/common/grep_check.py index 0c56b61e..d969cce7 100644 --- a/test/common/grep_check.py +++ b/test/common/grep_check.py @@ -22,11 +22,12 @@ """ import argparse -import logging -import shlex -import subprocess +import glob +import re import sys +from pyparsing import Regex + def parse_args() -> argparse.Namespace: """ @@ -40,36 +41,57 @@ def parse_args() -> argparse.Namespace: ) ) parser.add_argument( - "--file", + "--files", nargs="+", required=True, - help="Path to the file(s) to search within.", + help="Path or glob pattern to the file(s) to search within.", ) parser.add_argument( "--patterns", nargs="+", - required=True, + required=False, help="One or more patterns to assert are present in the file(s).", ) parser.add_argument( "--negative_patterns", nargs="+", - required=True, + required=False, help="One or more patterns to assert are not present in the file(s).", ) return parser.parse_args() + def main() -> None: """Entry point for the pattern-matching test.""" args = parse_args() - with open(args.file, "r", encoding="utf-8") as f: - content = f.read() + + if not args.negative_patterns and not args.patterns: + print(" [ERROR] Must define at least one pattern or negative pattern.") + sys.exit(1) all_passed = True - for pattern in args.patterns: - if pattern not in content: - print(f" [FAIL] Pattern missing: '{pattern}'") - all_passed = False + + file_paths = [] + for file_pattern in args.files: + matched_files = glob.glob(file_pattern, recursive=True) + if not matched_files: + print(f" [WARN] No files matched pattern/path: '{file_pattern}'") + file_paths.extend(matched_files) + for file in file_paths: + with open(file, "r", encoding="utf-8") as f: + content = f.read() + + if args.patterns: + for pattern in args.patterns: + if not re.search(pattern, content): + print(f" [FAIL] Pattern missing: '{pattern}'") + all_passed = False + + if args.negative_patterns: + for pattern in args.negative_patterns: + if re.search(pattern, content): + print(f" [FAIL] Negative pattern found: '{pattern}'") + all_passed = False if not all_passed: print("\nOne or more patterns missing. Test FAILED.") From 3f23c1b350ec75114029946cc2b47c451bf65290 Mon Sep 17 00:00:00 2001 From: "F.Tibor" Date: Mon, 30 Mar 2026 09:50:23 +0200 Subject: [PATCH 04/22] ReworkRefactor grep_check to allow pattern only present in single file --- test/common/grep_check.py | 141 +++++++++++++++++++++++++++++++++----- 1 file changed, 123 insertions(+), 18 deletions(-) diff --git a/test/common/grep_check.py b/test/common/grep_check.py index d969cce7..9787c79b 100644 --- a/test/common/grep_check.py +++ b/test/common/grep_check.py @@ -23,10 +23,10 @@ import argparse import glob +from itertools import chain import re import sys - -from pyparsing import Regex +from typing import Callable def parse_args() -> argparse.Namespace: @@ -58,18 +58,113 @@ def parse_args() -> argparse.Namespace: required=False, help="One or more patterns to assert are not present in the file(s).", ) + parser.add_argument( + "--regex_patterns", + nargs="+", + required=False, + help="One or more patterns to assert are present in the file(s).", + ) + parser.add_argument( + "--negative_regex_patterns", + nargs="+", + required=False, + help="One or more patterns to assert are not present in the file(s).", + ) + parser.add_argument( + "--any", + required=False, + action="store_true", + help="If provided, the program will succeed if at least one file " + "contains the patterns", + ) return parser.parse_args() +def check_args(args): + """Checks wether the arguments are correct, aborts if not""" + if ( + not args.patterns + and not args.negative_patterns + and not args.regex_patterns + and not args.negative_regex_patterns + ): + print(" [ERROR] Must define at least one pattern or negative pattern.") + sys.exit(1) + + +def exact_match(pattern: str, content: str) -> bool: + """Default search: checks if pattern is exactly in content.""" + return pattern in content + + +def check_patterns( + content: str, + patterns: list[str], + search: Callable[[str, str], bool] = exact_match, + negative: bool = False, +) -> tuple[bool, set[str], set[str]]: + """ + Checks wether a string contains every pattern in a list. + + Args: + content: Text to search in. + patterns: List of search patterns. + search: Function with signature func(pattern, content) -> bool. + Defaults to `pattern in content`. + negative: Boolean, wether to check patterns as positive or negative. + Returns: + bool - Wether all patterns are correctly (not) found. + set[str] - Set of patterns that are correctly (not) found. + set[str] - Set of patterns that are incorrectly (not) found. + """ + all_passed = True + found_patterns = set() + missing_pattern = set() + for pattern in patterns: + if bool(search(pattern, content)) == negative: + missing_pattern.add(pattern) + all_passed = False + else: + found_patterns.add(pattern) + return all_passed, found_patterns, missing_pattern + + +def check_file(content: str, args) -> tuple[bool, set[str], set[str]]: + """ + Checks if file contains all regexes. + Returns boolean value, and set of patterns correctly identified. + """ + all_passed = True + found_patterns = set() + missing_patterns = set() + + groups = [ + (args.patterns, exact_match, False), + (args.negative_patterns, exact_match, True), + (args.regex_patterns, re.search, False), + (args.negative_regex_patterns, re.search, True), + ] + + for patterns, search, negative in groups: + if patterns: + group_pass, found, missing = check_patterns( + content, patterns, search, negative + ) + all_passed = all_passed and group_pass + found_patterns.update(found) + missing_patterns.update(missing) + + return all_passed, found_patterns, missing_patterns + + def main() -> None: """Entry point for the pattern-matching test.""" args = parse_args() - - if not args.negative_patterns and not args.patterns: - print(" [ERROR] Must define at least one pattern or negative pattern.") - sys.exit(1) + check_args(args) all_passed = True + found_patterns = set() + missing_patterns = set() file_paths = [] for file_pattern in args.files: @@ -77,23 +172,33 @@ def main() -> None: if not matched_files: print(f" [WARN] No files matched pattern/path: '{file_pattern}'") file_paths.extend(matched_files) + for file in file_paths: with open(file, "r", encoding="utf-8") as f: content = f.read() - - if args.patterns: - for pattern in args.patterns: - if not re.search(pattern, content): - print(f" [FAIL] Pattern missing: '{pattern}'") - all_passed = False - - if args.negative_patterns: - for pattern in args.negative_patterns: - if re.search(pattern, content): - print(f" [FAIL] Negative pattern found: '{pattern}'") - all_passed = False + all_found_in_file, patterns_in_file, missing_patterns_in_file = ( + check_file(content, args) + ) + all_passed = all_passed and all_found_in_file + found_patterns.update(patterns_in_file) + for pattern in missing_patterns_in_file: + missing_patterns.add((file, pattern)) + + if args.any: + all_passed = True + for pattern in chain( + args.patterns or [], + args.negative_patterns or [], + args.regex_patterns or [], + args.negative_regex_patterns or [], + ): + if pattern not in found_patterns: + all_passed = False + break if not all_passed: + for file, pattern in missing_patterns: + print(f"Missing pattern {pattern} in file {file}") print("\nOne or more patterns missing. Test FAILED.") sys.exit(1) From 95400881f79d811eb38039381ac1b9ddf5321acd Mon Sep 17 00:00:00 2001 From: "F.Tibor" Date: Mon, 30 Mar 2026 09:50:34 +0200 Subject: [PATCH 05/22] Fix tests --- test/unit/virtual_include/BUILD | 31 +++++++++++++++++++ .../virtual_include/test_virtual_include.py | 25 --------------- 2 files changed, 31 insertions(+), 25 deletions(-) diff --git a/test/unit/virtual_include/BUILD b/test/unit/virtual_include/BUILD index f8f2ccca..c01d87d7 100644 --- a/test/unit/virtual_include/BUILD +++ b/test/unit/virtual_include/BUILD @@ -87,3 +87,34 @@ codechecker_test( "virtual_implementation_deps_include", ], ) + +py_test( + name = "per_file_plist_path_resolved_test", + srcs = ["//test/common:grep_check.py"], + main = "grep_check.py", + data = [":per_file_virtual_include"], + args = [ + "--files", + "test/unit/virtual_include/per_file_virtual_include/**/*.plist", + # FIXME: In the postprocessed plists, all _virtual_include paths + # should've been removed. Update to negative_patterns + "--patterns", + r"/_virtual_includes/", + ], +) + +py_test( + name = "codechecker_plist_path_resolved_test", + srcs = ["//test/common:grep_check.py"], + main = "grep_check.py", + data = [":codechecker_virtual_include"], + args = [ + "--files", + "test/unit/virtual_include/codechecker_virtual_include/**/*.plist", + # FIXME: In the postprocessed plists, all _virtual_include paths + # should've been removed. Update to negative_patterns + "--patterns", + r"/_virtual_includes/", + "--any", + ], +) diff --git a/test/unit/virtual_include/test_virtual_include.py b/test/unit/virtual_include/test_virtual_include.py index 04eb7fb3..e1fc1971 100644 --- a/test/unit/virtual_include/test_virtual_include.py +++ b/test/unit/virtual_include/test_virtual_include.py @@ -67,31 +67,6 @@ def contains_in_files(self, regex, file_list): result.append(file) return result - def test_bazel_per_file_plist_path_resolved(self): - """Test: bazel build :per_file_virtual_include""" - ret, _, stderr = self.run_command( - "bazel build //test/unit/virtual_include:per_file_virtual_include", - ) - self.assertEqual(ret, 0, stderr) - plist_files = glob.glob( - os.path.join( - self.BAZEL_BIN_DIR, # pyright: ignore - "per_file_virtual_include", - "**", - "*.plist", - ), - recursive=True, - ) - # Test whether the _virtual_include directory was actually created. - self.assertTrue( - os.path.isdir(f"{self.BAZEL_BIN_DIR}/_virtual_includes") - ) - # FIXME: In the postprocessed plists, all _virtual_include paths - # should've been removed. Possible fix is in the github PR #14. - self.assertNotEqual( - self.contains_in_files(r"/_virtual_includes/", plist_files), [] - ) - def test_bazel_codechecker_plist_path_resolved(self): """Test: bazel build :codechecker_virtual_include""" ret, _, stderr = self.run_command( From 95c70b248f884b00f0249919cb792000d676a89f Mon Sep 17 00:00:00 2001 From: "F.Tibor" Date: Tue, 31 Mar 2026 22:19:04 +0200 Subject: [PATCH 06/22] Add wrapper macro for unittests --- test/common/unit_test.bzl | 93 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 93 insertions(+) create mode 100644 test/common/unit_test.bzl diff --git a/test/common/unit_test.bzl b/test/common/unit_test.bzl new file mode 100644 index 00000000..196df648 --- /dev/null +++ b/test/common/unit_test.bzl @@ -0,0 +1,93 @@ +# Copyright 2026 Ericsson AB +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" +Macro for generating unit tests for rules_codechecker. + +Each unit_test() generates a local py_test that: + Depends on an other action, and checks for patterns on its output. + +Example: + unit_test( + name = "my_unit_test", + files = ["my_target_file.ext"], + patterns = ["my_pattern"], + negative_patterns = ["my_negative_pattern"], + regex_patterns = ["my_.*regex.*_pattern"], + negative_regex_patterns = ["my_.*negative_regex.*_pattern"], + ) +""" + +def unit_test( + name, + files, + patterns = None, + negative_patterns = None, + regex_patterns = None, + negative_regex_patterns = None, + any = False, + tags = [], + size = "medium", + **kwargs): + """Generate a py_test that checks if provided patterns are in the files. + + Args: + name: Test name. + files: Path or glob to the files to be checked. + patterns: Patterns that should be inside the files. + negative_patterns: Patterns that shouldn't be inside the files. + regex_patterns: Regex patterns that should be inside the files. + negative_regex_patterns: Regex patterns that shouldn't be inside the files. + any: If enabled its enough if every pattern is found in at least one file. + tags: Additional test tags. + size: Test size (default: medium). + **kwargs: Forwarded to py_test. + """ + if type(files) == "string": + files = [files] + if type(patterns) == "string": + patterns = [patterns] + if type(negative_patterns) == "string": + negative_patterns = [negative_patterns] + if type(regex_patterns) == "string": + regex_patterns = [regex_patterns] + if type(negative_regex_patterns) == "string": + negative_regex_patterns = [negative_regex_patterns] + + python_args = ["--files"] + files + if patterns: + python_args.append("--patterns") + python_args.extend(patterns) + if negative_patterns: + python_args.append("--negative_patterns") + python_args.extend(negative_patterns) + if regex_patterns: + python_args.append("--regex_patterns") + python_args.extend(regex_patterns) + if negative_regex_patterns: + python_args.append("--negative_regex_patterns") + python_args.extend(negative_regex_patterns) + if any: + python_args.append("--any") + + native.py_test( + name = name, + srcs = ["//test/common:grep_check.py"], + main = "grep_check.py", + args = python_args, + local = True, + tags = ["unit"] + tags, + size = size, + **kwargs + ) From 96606de987c2907b555f9b005bfa7bb7a41af01b Mon Sep 17 00:00:00 2001 From: "F.Tibor" Date: Tue, 31 Mar 2026 22:27:29 +0200 Subject: [PATCH 07/22] Update test in virtual_include --- test/unit/virtual_include/BUILD | 42 +++++++++++++++------------------ 1 file changed, 19 insertions(+), 23 deletions(-) diff --git a/test/unit/virtual_include/BUILD b/test/unit/virtual_include/BUILD index c01d87d7..5c23f1df 100644 --- a/test/unit/virtual_include/BUILD +++ b/test/unit/virtual_include/BUILD @@ -18,6 +18,16 @@ load( "cc_library", ) +# codechecker rules +load( + "//src:codechecker.bzl", + "codechecker_test", +) +load( + "//test/common:unit_test.bzl", + "unit_test", +) + # codechecker rules load( "//src:codechecker.bzl", @@ -88,33 +98,19 @@ codechecker_test( ], ) -py_test( +unit_test( name = "per_file_plist_path_resolved_test", - srcs = ["//test/common:grep_check.py"], - main = "grep_check.py", data = [":per_file_virtual_include"], - args = [ - "--files", - "test/unit/virtual_include/per_file_virtual_include/**/*.plist", - # FIXME: In the postprocessed plists, all _virtual_include paths - # should've been removed. Update to negative_patterns - "--patterns", - r"/_virtual_includes/", - ], + files = "test/unit/virtual_include/per_file_virtual_include/**/*.plist", + patterns = r"/_virtual_includes/", ) -py_test( +unit_test( name = "codechecker_plist_path_resolved_test", - srcs = ["//test/common:grep_check.py"], - main = "grep_check.py", + any = True, data = [":codechecker_virtual_include"], - args = [ - "--files", - "test/unit/virtual_include/codechecker_virtual_include/**/*.plist", - # FIXME: In the postprocessed plists, all _virtual_include paths - # should've been removed. Update to negative_patterns - "--patterns", - r"/_virtual_includes/", - "--any", - ], + files = "test/unit/virtual_include/codechecker_virtual_include/**/*.plist", + # FIXME: In the postprocessed plists, all _virtual_include paths + # should've been removed. Update to negative_patterns + patterns = r"/_virtual_includes/", ) From ba72ad537d4c6b9aed6b35e130dd607bb0d74062 Mon Sep 17 00:00:00 2001 From: "F.Tibor" Date: Tue, 31 Mar 2026 22:59:00 +0200 Subject: [PATCH 08/22] Remove duplicate test --- .../virtual_include/test_virtual_include.py | 26 ------------------- 1 file changed, 26 deletions(-) diff --git a/test/unit/virtual_include/test_virtual_include.py b/test/unit/virtual_include/test_virtual_include.py index e1fc1971..b770c8b0 100644 --- a/test/unit/virtual_include/test_virtual_include.py +++ b/test/unit/virtual_include/test_virtual_include.py @@ -67,32 +67,6 @@ def contains_in_files(self, regex, file_list): result.append(file) return result - def test_bazel_codechecker_plist_path_resolved(self): - """Test: bazel build :codechecker_virtual_include""" - ret, _, stderr = self.run_command( - "bazel build " - "//test/unit/virtual_include:codechecker_virtual_include" - ) - self.assertEqual(ret, 0, stderr) - plist_files = glob.glob( - os.path.join( - self.BAZEL_BIN_DIR, # pyright: ignore - "codechecker_virtual_include", - "**", - "*.plist", - ), - recursive=True, - ) - # Test whether the _virtual_include directory was actually created. - self.assertTrue( - os.path.isdir(f"{self.BAZEL_BIN_DIR}/_virtual_includes") - ) - # FIXME: In the postprocessed plists, all _virtual_include paths - # should've been removed. Possible fix is in the github PR #14. - self.assertNotEqual( - self.contains_in_files(r"/_virtual_includes/", plist_files), [] - ) - def test_bazel_codechecker_implementation_deps_virtual_include(self): """Test: bazel build :codechecker_impl_deps_include""" ret, _, stderr = self.run_command( From 56f2b72a53cd761453c7505fbe438db951327fff Mon Sep 17 00:00:00 2001 From: "F.Tibor" Date: Wed, 1 Apr 2026 06:38:11 +0200 Subject: [PATCH 09/22] Move unit_test to unit folder --- test/{common => unit}/BUILD | 0 test/{common => unit}/grep_check.py | 0 test/{common => unit}/unit_test.bzl | 2 +- test/unit/virtual_include/BUILD | 2 +- 4 files changed, 2 insertions(+), 2 deletions(-) rename test/{common => unit}/BUILD (100%) rename test/{common => unit}/grep_check.py (100%) rename test/{common => unit}/unit_test.bzl (98%) diff --git a/test/common/BUILD b/test/unit/BUILD similarity index 100% rename from test/common/BUILD rename to test/unit/BUILD diff --git a/test/common/grep_check.py b/test/unit/grep_check.py similarity index 100% rename from test/common/grep_check.py rename to test/unit/grep_check.py diff --git a/test/common/unit_test.bzl b/test/unit/unit_test.bzl similarity index 98% rename from test/common/unit_test.bzl rename to test/unit/unit_test.bzl index 196df648..8cbedf68 100644 --- a/test/common/unit_test.bzl +++ b/test/unit/unit_test.bzl @@ -83,7 +83,7 @@ def unit_test( native.py_test( name = name, - srcs = ["//test/common:grep_check.py"], + srcs = ["//test/unit:grep_check.py"], main = "grep_check.py", args = python_args, local = True, diff --git a/test/unit/virtual_include/BUILD b/test/unit/virtual_include/BUILD index 5c23f1df..953239b2 100644 --- a/test/unit/virtual_include/BUILD +++ b/test/unit/virtual_include/BUILD @@ -24,7 +24,7 @@ load( "codechecker_test", ) load( - "//test/common:unit_test.bzl", + "//test/unit:unit_test.bzl", "unit_test", ) From 0a90a8e5235fbfac22e8e096d57492632bf7e4a5 Mon Sep 17 00:00:00 2001 From: "F.Tibor" Date: Wed, 1 Apr 2026 09:55:51 +0200 Subject: [PATCH 10/22] Remove unused import --- test/unit/virtual_include/test_virtual_include.py | 1 - 1 file changed, 1 deletion(-) diff --git a/test/unit/virtual_include/test_virtual_include.py b/test/unit/virtual_include/test_virtual_include.py index b770c8b0..8d1b5ca9 100644 --- a/test/unit/virtual_include/test_virtual_include.py +++ b/test/unit/virtual_include/test_virtual_include.py @@ -25,7 +25,6 @@ import logging import os import unittest -import glob from typing import final from common.base import TestBase From e31fb7dbf224d3a9de0d6620217f7eb66b7db359 Mon Sep 17 00:00:00 2001 From: "F.Tibor" Date: Wed, 10 Jun 2026 13:46:22 +0200 Subject: [PATCH 11/22] Fix target of unittest --- test/unit/unit_test.bzl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/unit/unit_test.bzl b/test/unit/unit_test.bzl index 8cbedf68..2441ac28 100644 --- a/test/unit/unit_test.bzl +++ b/test/unit/unit_test.bzl @@ -84,7 +84,7 @@ def unit_test( native.py_test( name = name, srcs = ["//test/unit:grep_check.py"], - main = "grep_check.py", + main = "//test/unit:grep_check.py", args = python_args, local = True, tags = ["unit"] + tags, From 1d3f13b7d47749d02ed762470bb5c335c4644878 Mon Sep 17 00:00:00 2001 From: "F.Tibor" Date: Wed, 10 Jun 2026 15:38:16 +0200 Subject: [PATCH 12/22] Change parameter name for any --- test/unit/unit_test.bzl | 9 ++++++--- test/unit/virtual_include/BUILD | 8 +------- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/test/unit/unit_test.bzl b/test/unit/unit_test.bzl index 2441ac28..69616277 100644 --- a/test/unit/unit_test.bzl +++ b/test/unit/unit_test.bzl @@ -36,7 +36,7 @@ def unit_test( negative_patterns = None, regex_patterns = None, negative_regex_patterns = None, - any = False, + require_patterns_in_each_file = True, tags = [], size = "medium", **kwargs): @@ -49,7 +49,7 @@ def unit_test( negative_patterns: Patterns that shouldn't be inside the files. regex_patterns: Regex patterns that should be inside the files. negative_regex_patterns: Regex patterns that shouldn't be inside the files. - any: If enabled its enough if every pattern is found in at least one file. + require_patterns_in_each_file: If False its enough if every pattern is found in at least one file. tags: Additional test tags. size: Test size (default: medium). **kwargs: Forwarded to py_test. @@ -78,9 +78,12 @@ def unit_test( if negative_regex_patterns: python_args.append("--negative_regex_patterns") python_args.extend(negative_regex_patterns) - if any: + if not require_patterns_in_each_file: python_args.append("--any") + # Since we use a custom python toolchain instead of rules_python in WORKSPACE + # we cannot include py_test + # buildifier: disable=native-pys native.py_test( name = name, srcs = ["//test/unit:grep_check.py"], diff --git a/test/unit/virtual_include/BUILD b/test/unit/virtual_include/BUILD index 953239b2..f8c2df45 100644 --- a/test/unit/virtual_include/BUILD +++ b/test/unit/virtual_include/BUILD @@ -28,12 +28,6 @@ load( "unit_test", ) -# codechecker rules -load( - "//src:codechecker.bzl", - "codechecker_test", -) - # Test for strip_include_prefix cc_library( name = "test_inc", @@ -107,10 +101,10 @@ unit_test( unit_test( name = "codechecker_plist_path_resolved_test", - any = True, data = [":codechecker_virtual_include"], files = "test/unit/virtual_include/codechecker_virtual_include/**/*.plist", # FIXME: In the postprocessed plists, all _virtual_include paths # should've been removed. Update to negative_patterns patterns = r"/_virtual_includes/", + require_patterns_in_each_file = False, ) From ae7aeed1f9f0ed8a05de635c8c66c333c0b44d22 Mon Sep 17 00:00:00 2001 From: "F.Tibor" Date: Wed, 10 Jun 2026 15:45:37 +0200 Subject: [PATCH 13/22] Migrate tests not asserting on output to unit_test macro --- test/unit/virtual_include/BUILD | 15 ++++ test/unit/virtual_include/__init__.py | 13 --- .../virtual_include/test_virtual_include.py | 87 ------------------- 3 files changed, 15 insertions(+), 100 deletions(-) delete mode 100644 test/unit/virtual_include/__init__.py delete mode 100644 test/unit/virtual_include/test_virtual_include.py diff --git a/test/unit/virtual_include/BUILD b/test/unit/virtual_include/BUILD index f8c2df45..e915dcfd 100644 --- a/test/unit/virtual_include/BUILD +++ b/test/unit/virtual_include/BUILD @@ -108,3 +108,18 @@ unit_test( patterns = r"/_virtual_includes/", require_patterns_in_each_file = False, ) + +unit_test( + name = "per_file_impl_deps_include_test", + data = [":per_file_impl_deps_include"], + files = "test/unit/virtual_include/per_file_impl_deps_include/**/*.plist", + patterns = "Division by zero", + require_patterns_in_each_file = False, +) + +unit_test( + name = "codechecker_impl_deps_include_test", + data = [":codechecker_impl_deps_include"], + files = "test/unit/virtual_include/codechecker_impl_deps_include/codechecker.log", + patterns = "core.DivideZero", +) diff --git a/test/unit/virtual_include/__init__.py b/test/unit/virtual_include/__init__.py deleted file mode 100644 index 78bab5f1..00000000 --- a/test/unit/virtual_include/__init__.py +++ /dev/null @@ -1,13 +0,0 @@ -# Copyright 2023 Ericsson AB -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. diff --git a/test/unit/virtual_include/test_virtual_include.py b/test/unit/virtual_include/test_virtual_include.py deleted file mode 100644 index 8d1b5ca9..00000000 --- a/test/unit/virtual_include/test_virtual_include.py +++ /dev/null @@ -1,87 +0,0 @@ -# Copyright 2023 Ericsson AB -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -""" -We want CodeChecker to point to the original files in its results, this needs -post processing. -Bazel creates _virtual_includes folder for headers, declared in a cc_library -rule with the include_prefix or strip_include_prefix. When warnings are found -in these headers, their paths in the plist files should get resolved to the -original file path. -This unittest test whether these paths containing `_virtual_include` have been -resolved -""" -import logging -import os -import unittest -from typing import final -from common.base import TestBase - - -class TestVirtualInclude(TestBase): - """Tests checking virtual include path resolution""" - - # Set working directory - __test_path__ = os.path.dirname(os.path.abspath(__file__)) - BAZEL_BIN_DIR = os.path.join( - "../../..", "bazel-bin", "test", "unit", "virtual_include" - ) - BAZEL_TESTLOGS_DIR = os.path.join( - "../../..", "bazel-testlogs", "test", "unit", "virtual_include" - ) - - @final - @classmethod - def setUpClass(cls): - """Set up before the test suite""" - super().setUpClass() - cls.run_command("bazel clean") - - def contains_in_files(self, regex, file_list): - """ - Select files containing a regex from a list of files. - - Args: - regex - Pattern to be searched. - file_list - List of files to be checked. - - Returns: - List of files containing the regex pattern - """ - result = [] - for file in file_list: - logging.debug("Checking file: %s", file) - if self.contains_regex_in_file(file, regex): - result.append(file) - return result - - def test_bazel_codechecker_implementation_deps_virtual_include(self): - """Test: bazel build :codechecker_impl_deps_include""" - ret, _, stderr = self.run_command( - "bazel build --experimental_cc_implementation_deps " - "//test/unit/virtual_include:codechecker_impl_deps_include" - ) - self.assertEqual(ret, 0, stderr) - - def test_bazel_per_file_implementation_deps_virtual_include(self): - """Test: bazel build :per_file_impl_deps_include""" - ret, _, stderr = self.run_command( - "bazel build --experimental_cc_implementation_deps " - "//test/unit/virtual_include:per_file_impl_deps_include" - ) - self.assertEqual(ret, 0, stderr) - - -if __name__ == "__main__": - unittest.main(buffer=True) From 9a9f25a389493a882e340509563ab5495b353731 Mon Sep 17 00:00:00 2001 From: "F.Tibor" Date: Fri, 12 Jun 2026 16:33:36 +0200 Subject: [PATCH 14/22] Update documentation for unit test creation --- test/README.md | 30 ++++++++++++++++++++++-------- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/test/README.md b/test/README.md index 14bde169..1b7e8cd1 100644 --- a/test/README.md +++ b/test/README.md @@ -2,11 +2,11 @@ ## Running Tests -Our projects use both **`pytest`** and **`unittest`** frameworks. -You can run tests using either method. -The **`-vvv`** flag is used for **verbosity**, which provides more detailed output and is very helpful for debugging. +Our projects use both **`bazel tests`** and **`pytest`**. +You can run bazel tests with: `bazel test //...`. +For more verbosity in python tests use **`-vvv`** or **`--log-cli-level=DEBUG`** for pytest. -### To run all tests, use one of the following command: +### To run all unit tests, use one of the following command: * **Using Pytest:** ```bash pytest unit -vvv @@ -32,11 +32,12 @@ python3 -m unittest discover unit/my_test_dir -vvv Inside the `unit` directory, create a folder for your new test. This folder should contain: - All source/header files needed for the test - `BUILD` - - A python test script - - `__init__.py` 2. **Creating the BUILD File** - - Make sure that all failing test targets get the `"manual"` tag. For example: + - Create the `cc_binary/library` targets. + - Create the `codechecker_test` targets. + - Create `unit_test` targets to assert on the outputs of the codechecker targets. (See `unit/unit_test.bzl` for documentation) + - Make sure that all failing `codechecker_test` targets get the `"manual"` tag. For example: ``` # This is a test I expect to fail codechecker_test( @@ -49,8 +50,10 @@ python3 -m unittest discover unit/my_test_dir -vvv ], ) ``` + - Tip: To test these failing tests, create a unit_test target and assert the bug being found. -2. **Creating the Test File** +3. **Create a python test if you must** + - If you are writing a python test, have an `__init__.py` file in the test directory! - Your test script must follow the naming convention: ```text test_*.py @@ -70,6 +73,17 @@ python3 -m unittest discover unit/my_test_dir -vvv ## Testing on open source projects +### To run all FOSS tests, use one of the following command: +* **Using Pytest:** + ```bash + pytest foss -vvv + ``` + +* **Using Unittest:** + ```bash + python3 -m unittest discover foss -vvv + ``` + ## Add a new open source project: 1. Create a folder in the foss folder with the name of the project. From 5eb0ec6bcd97d961269af9b93256a6d7bb1d5e54 Mon Sep 17 00:00:00 2001 From: "F.Tibor" Date: Fri, 12 Jun 2026 18:17:22 +0200 Subject: [PATCH 15/22] Fix patterns with spaces being checked separately --- test/unit/unit_test.bzl | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/unit/unit_test.bzl b/test/unit/unit_test.bzl index 69616277..b9e2efb7 100644 --- a/test/unit/unit_test.bzl +++ b/test/unit/unit_test.bzl @@ -68,16 +68,16 @@ def unit_test( python_args = ["--files"] + files if patterns: python_args.append("--patterns") - python_args.extend(patterns) + python_args.extend(["\"{}\"".format(pat) for pat in patterns]) if negative_patterns: python_args.append("--negative_patterns") - python_args.extend(negative_patterns) + python_args.extend(["\"{}\"".format(pat) for pat in negative_patterns]) if regex_patterns: python_args.append("--regex_patterns") - python_args.extend(regex_patterns) + python_args.extend(["\"{}\"".format(pat) for pat in regex_patterns]) if negative_regex_patterns: python_args.append("--negative_regex_patterns") - python_args.extend(negative_regex_patterns) + python_args.extend(["\"{}\"".format(pat) for pat in negative_regex_patterns]) if not require_patterns_in_each_file: python_args.append("--any") From 9fd8699054b2d9a170910a3ce1998e78023cee5c Mon Sep 17 00:00:00 2001 From: "F.Tibor" Date: Fri, 12 Jun 2026 18:18:31 +0200 Subject: [PATCH 16/22] Update template test to use unit_test --- test/unit/template/BUILD | 70 +++++++++++++++++++ test/unit/template/template.cpp | 21 ++++++ ...st_template.py => test_python_template.py} | 0 3 files changed, 91 insertions(+) create mode 100644 test/unit/template/BUILD create mode 100644 test/unit/template/template.cpp rename test/unit/template/{test_template.py => test_python_template.py} (100%) diff --git a/test/unit/template/BUILD b/test/unit/template/BUILD new file mode 100644 index 00000000..72e7556b --- /dev/null +++ b/test/unit/template/BUILD @@ -0,0 +1,70 @@ +# Copyright 2023 Ericsson AB +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +load( + "@rules_cc//cc:defs.bzl", + "cc_library", +) +load( + "//src:codechecker.bzl", + "codechecker_test", +) +load( + "//test/unit:unit_test.bzl", + "unit_test", +) + +cc_library( + name = "template_target", + srcs = ["template.cpp"], + tags = ["manual"], +) + +codechecker_test( + name = "template_codechecker", + tags = ["manual"], + targets = [ + "template_target", + ], +) + +codechecker_test( + name = "template_per_file", + per_file = True, + tags = ["manual"], + targets = [ + "template_target", + ], +) + +unit_test( + name = "template_codechecker_test", + data = [":template_codechecker"], + files = "test/unit/template/template_codechecker/codechecker.log", + negative_patterns = ["Text not in file"], + negative_regex_patterns = ["(This regex is not in the file)+"], + patterns = ["core.DivideZero"], + regex_patterns = ["[a-z]"], +) + +unit_test( + name = "template_per_file_test", + data = [":template_per_file"], + files = "test/unit/template/template_per_file/**/*.plist", + negative_patterns = ["Text not in file"], + negative_regex_patterns = ["(This regex is not in the file)+"], + patterns = ["Division by zero"], + regex_patterns = ["[a-z]"], + require_patterns_in_each_file = False, +) diff --git a/test/unit/template/template.cpp b/test/unit/template/template.cpp new file mode 100644 index 00000000..ea73f4ec --- /dev/null +++ b/test/unit/template/template.cpp @@ -0,0 +1,21 @@ +/* + * Copyright 2023 Ericsson AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +int main(){ + int a = 0; + int b = 0/a; + return b; +} diff --git a/test/unit/template/test_template.py b/test/unit/template/test_python_template.py similarity index 100% rename from test/unit/template/test_template.py rename to test/unit/template/test_python_template.py From 14a5ede457658ac96b705678d1c721599957909c Mon Sep 17 00:00:00 2001 From: "F.Tibor" Date: Fri, 12 Jun 2026 18:19:56 +0200 Subject: [PATCH 17/22] Format template python test --- test/unit/template/test_python_template.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/test/unit/template/test_python_template.py b/test/unit/template/test_python_template.py index 8113cdd2..947e5389 100644 --- a/test/unit/template/test_python_template.py +++ b/test/unit/template/test_python_template.py @@ -15,6 +15,7 @@ """ TODO: Describe what this file does """ + import os import unittest from typing import final @@ -23,13 +24,16 @@ class TestTemplate(TestBase): """TODO: Add a description""" + # Set working directory __test_path__ = os.path.dirname(os.path.abspath(__file__)) # TODO: fix folder name - BAZEL_BIN_DIR = os.path.join("../../..", "bazel-bin", "test", - "unit", "my_test_folder") - BAZEL_TESTLOGS_DIR = os.path.join("../../..", "bazel-testlogs", "test", - "unit", "my_test_folder") + BAZEL_BIN_DIR = os.path.join( + "../../..", "bazel-bin", "test", "unit", "my_test_folder" + ) + BAZEL_TESTLOGS_DIR = os.path.join( + "../../..", "bazel-testlogs", "test", "unit", "my_test_folder" + ) @final @classmethod From d86f33f4082cdc15b1e98c343a9e0381f69dd9f7 Mon Sep 17 00:00:00 2001 From: "F.Tibor" Date: Fri, 12 Jun 2026 18:20:28 +0200 Subject: [PATCH 18/22] Update pylintrc --- .pylintrc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pylintrc b/.pylintrc index a8a353ac..5ccda61c 100644 --- a/.pylintrc +++ b/.pylintrc @@ -1,7 +1,7 @@ [MASTER] # To find common lib for tests init-hook='import sys; import os; sys.path.append("test"); sys.path.append(os.getcwd())' -ignore = test_template.py +ignore = test_python_template.py [FORMAT] max-line-length=80 From fde6a47266c63cfe148b5f13b713a380a454a671 Mon Sep 17 00:00:00 2001 From: "F.Tibor" Date: Tue, 16 Jun 2026 07:12:05 +0200 Subject: [PATCH 19/22] Revert changes to virtual_include --- test/unit/virtual_include/BUILD | 36 ----- .../virtual_include/test_virtual_include.py | 139 ++++++++++++++++++ 2 files changed, 139 insertions(+), 36 deletions(-) create mode 100644 test/unit/virtual_include/test_virtual_include.py diff --git a/test/unit/virtual_include/BUILD b/test/unit/virtual_include/BUILD index e915dcfd..f8f2ccca 100644 --- a/test/unit/virtual_include/BUILD +++ b/test/unit/virtual_include/BUILD @@ -23,10 +23,6 @@ load( "//src:codechecker.bzl", "codechecker_test", ) -load( - "//test/unit:unit_test.bzl", - "unit_test", -) # Test for strip_include_prefix cc_library( @@ -91,35 +87,3 @@ codechecker_test( "virtual_implementation_deps_include", ], ) - -unit_test( - name = "per_file_plist_path_resolved_test", - data = [":per_file_virtual_include"], - files = "test/unit/virtual_include/per_file_virtual_include/**/*.plist", - patterns = r"/_virtual_includes/", -) - -unit_test( - name = "codechecker_plist_path_resolved_test", - data = [":codechecker_virtual_include"], - files = "test/unit/virtual_include/codechecker_virtual_include/**/*.plist", - # FIXME: In the postprocessed plists, all _virtual_include paths - # should've been removed. Update to negative_patterns - patterns = r"/_virtual_includes/", - require_patterns_in_each_file = False, -) - -unit_test( - name = "per_file_impl_deps_include_test", - data = [":per_file_impl_deps_include"], - files = "test/unit/virtual_include/per_file_impl_deps_include/**/*.plist", - patterns = "Division by zero", - require_patterns_in_each_file = False, -) - -unit_test( - name = "codechecker_impl_deps_include_test", - data = [":codechecker_impl_deps_include"], - files = "test/unit/virtual_include/codechecker_impl_deps_include/codechecker.log", - patterns = "core.DivideZero", -) diff --git a/test/unit/virtual_include/test_virtual_include.py b/test/unit/virtual_include/test_virtual_include.py new file mode 100644 index 00000000..04eb7fb3 --- /dev/null +++ b/test/unit/virtual_include/test_virtual_include.py @@ -0,0 +1,139 @@ +# Copyright 2023 Ericsson AB +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" +We want CodeChecker to point to the original files in its results, this needs +post processing. +Bazel creates _virtual_includes folder for headers, declared in a cc_library +rule with the include_prefix or strip_include_prefix. When warnings are found +in these headers, their paths in the plist files should get resolved to the +original file path. +This unittest test whether these paths containing `_virtual_include` have been +resolved +""" +import logging +import os +import unittest +import glob +from typing import final +from common.base import TestBase + + +class TestVirtualInclude(TestBase): + """Tests checking virtual include path resolution""" + + # Set working directory + __test_path__ = os.path.dirname(os.path.abspath(__file__)) + BAZEL_BIN_DIR = os.path.join( + "../../..", "bazel-bin", "test", "unit", "virtual_include" + ) + BAZEL_TESTLOGS_DIR = os.path.join( + "../../..", "bazel-testlogs", "test", "unit", "virtual_include" + ) + + @final + @classmethod + def setUpClass(cls): + """Set up before the test suite""" + super().setUpClass() + cls.run_command("bazel clean") + + def contains_in_files(self, regex, file_list): + """ + Select files containing a regex from a list of files. + + Args: + regex - Pattern to be searched. + file_list - List of files to be checked. + + Returns: + List of files containing the regex pattern + """ + result = [] + for file in file_list: + logging.debug("Checking file: %s", file) + if self.contains_regex_in_file(file, regex): + result.append(file) + return result + + def test_bazel_per_file_plist_path_resolved(self): + """Test: bazel build :per_file_virtual_include""" + ret, _, stderr = self.run_command( + "bazel build //test/unit/virtual_include:per_file_virtual_include", + ) + self.assertEqual(ret, 0, stderr) + plist_files = glob.glob( + os.path.join( + self.BAZEL_BIN_DIR, # pyright: ignore + "per_file_virtual_include", + "**", + "*.plist", + ), + recursive=True, + ) + # Test whether the _virtual_include directory was actually created. + self.assertTrue( + os.path.isdir(f"{self.BAZEL_BIN_DIR}/_virtual_includes") + ) + # FIXME: In the postprocessed plists, all _virtual_include paths + # should've been removed. Possible fix is in the github PR #14. + self.assertNotEqual( + self.contains_in_files(r"/_virtual_includes/", plist_files), [] + ) + + def test_bazel_codechecker_plist_path_resolved(self): + """Test: bazel build :codechecker_virtual_include""" + ret, _, stderr = self.run_command( + "bazel build " + "//test/unit/virtual_include:codechecker_virtual_include" + ) + self.assertEqual(ret, 0, stderr) + plist_files = glob.glob( + os.path.join( + self.BAZEL_BIN_DIR, # pyright: ignore + "codechecker_virtual_include", + "**", + "*.plist", + ), + recursive=True, + ) + # Test whether the _virtual_include directory was actually created. + self.assertTrue( + os.path.isdir(f"{self.BAZEL_BIN_DIR}/_virtual_includes") + ) + # FIXME: In the postprocessed plists, all _virtual_include paths + # should've been removed. Possible fix is in the github PR #14. + self.assertNotEqual( + self.contains_in_files(r"/_virtual_includes/", plist_files), [] + ) + + def test_bazel_codechecker_implementation_deps_virtual_include(self): + """Test: bazel build :codechecker_impl_deps_include""" + ret, _, stderr = self.run_command( + "bazel build --experimental_cc_implementation_deps " + "//test/unit/virtual_include:codechecker_impl_deps_include" + ) + self.assertEqual(ret, 0, stderr) + + def test_bazel_per_file_implementation_deps_virtual_include(self): + """Test: bazel build :per_file_impl_deps_include""" + ret, _, stderr = self.run_command( + "bazel build --experimental_cc_implementation_deps " + "//test/unit/virtual_include:per_file_impl_deps_include" + ) + self.assertEqual(ret, 0, stderr) + + +if __name__ == "__main__": + unittest.main(buffer=True) From e5fd0691507e0420a17796c4e934e536e3847c96 Mon Sep 17 00:00:00 2001 From: "F.Tibor" Date: Tue, 16 Jun 2026 07:35:00 +0200 Subject: [PATCH 20/22] Rename pattern names --- test/unit/grep_check.py | 29 ++++++++++----------------- test/unit/template/BUILD | 10 ++++------ test/unit/unit_test.bzl | 42 ++++++++++++++++------------------------ 3 files changed, 31 insertions(+), 50 deletions(-) diff --git a/test/unit/grep_check.py b/test/unit/grep_check.py index 9787c79b..46985241 100644 --- a/test/unit/grep_check.py +++ b/test/unit/grep_check.py @@ -47,16 +47,16 @@ def parse_args() -> argparse.Namespace: help="Path or glob pattern to the file(s) to search within.", ) parser.add_argument( - "--patterns", + "--contains", nargs="+", required=False, - help="One or more patterns to assert are present in the file(s).", + help="One or more string to assert are present in the file(s).", ) parser.add_argument( - "--negative_patterns", + "--excludes", nargs="+", required=False, - help="One or more patterns to assert are not present in the file(s).", + help="One or more string to assert are not present in the file(s).", ) parser.add_argument( "--regex_patterns", @@ -64,12 +64,6 @@ def parse_args() -> argparse.Namespace: required=False, help="One or more patterns to assert are present in the file(s).", ) - parser.add_argument( - "--negative_regex_patterns", - nargs="+", - required=False, - help="One or more patterns to assert are not present in the file(s).", - ) parser.add_argument( "--any", required=False, @@ -83,10 +77,9 @@ def parse_args() -> argparse.Namespace: def check_args(args): """Checks wether the arguments are correct, aborts if not""" if ( - not args.patterns - and not args.negative_patterns + not args.contains + and not args.excludes and not args.regex_patterns - and not args.negative_regex_patterns ): print(" [ERROR] Must define at least one pattern or negative pattern.") sys.exit(1) @@ -139,10 +132,9 @@ def check_file(content: str, args) -> tuple[bool, set[str], set[str]]: missing_patterns = set() groups = [ - (args.patterns, exact_match, False), - (args.negative_patterns, exact_match, True), + (args.contains, exact_match, False), + (args.excludes, exact_match, True), (args.regex_patterns, re.search, False), - (args.negative_regex_patterns, re.search, True), ] for patterns, search, negative in groups: @@ -187,10 +179,9 @@ def main() -> None: if args.any: all_passed = True for pattern in chain( - args.patterns or [], - args.negative_patterns or [], + args.contains or [], + args.excludes or [], args.regex_patterns or [], - args.negative_regex_patterns or [], ): if pattern not in found_patterns: all_passed = False diff --git a/test/unit/template/BUILD b/test/unit/template/BUILD index 72e7556b..2b451e67 100644 --- a/test/unit/template/BUILD +++ b/test/unit/template/BUILD @@ -50,21 +50,19 @@ codechecker_test( unit_test( name = "template_codechecker_test", + contains = ["core.DivideZero"], data = [":template_codechecker"], + excludes = ["Text not in file"], files = "test/unit/template/template_codechecker/codechecker.log", - negative_patterns = ["Text not in file"], - negative_regex_patterns = ["(This regex is not in the file)+"], - patterns = ["core.DivideZero"], regex_patterns = ["[a-z]"], ) unit_test( name = "template_per_file_test", + contains = ["Division by zero"], data = [":template_per_file"], + excludes = ["Text not in file"], files = "test/unit/template/template_per_file/**/*.plist", - negative_patterns = ["Text not in file"], - negative_regex_patterns = ["(This regex is not in the file)+"], - patterns = ["Division by zero"], regex_patterns = ["[a-z]"], require_patterns_in_each_file = False, ) diff --git a/test/unit/unit_test.bzl b/test/unit/unit_test.bzl index b9e2efb7..0079ea08 100644 --- a/test/unit/unit_test.bzl +++ b/test/unit/unit_test.bzl @@ -22,20 +22,18 @@ Example: unit_test( name = "my_unit_test", files = ["my_target_file.ext"], - patterns = ["my_pattern"], - negative_patterns = ["my_negative_pattern"], + contains = ["my_pattern"], + excludes = ["my_negative_pattern"], regex_patterns = ["my_.*regex.*_pattern"], - negative_regex_patterns = ["my_.*negative_regex.*_pattern"], ) """ def unit_test( name, files, - patterns = None, - negative_patterns = None, + contains = None, + excludes = None, regex_patterns = None, - negative_regex_patterns = None, require_patterns_in_each_file = True, tags = [], size = "medium", @@ -45,10 +43,9 @@ def unit_test( Args: name: Test name. files: Path or glob to the files to be checked. - patterns: Patterns that should be inside the files. - negative_patterns: Patterns that shouldn't be inside the files. - regex_patterns: Regex patterns that should be inside the files. - negative_regex_patterns: Regex patterns that shouldn't be inside the files. + contains: Text that should be inside the files. + excludes: Text that shouldn't be inside the files. + regex_patterns: Regex patterns that should be found inside the files. require_patterns_in_each_file: If False its enough if every pattern is found in at least one file. tags: Additional test tags. size: Test size (default: medium). @@ -56,28 +53,23 @@ def unit_test( """ if type(files) == "string": files = [files] - if type(patterns) == "string": - patterns = [patterns] - if type(negative_patterns) == "string": - negative_patterns = [negative_patterns] + if type(contains) == "string": + contains = [contains] + if type(excludes) == "string": + excludes = [excludes] if type(regex_patterns) == "string": regex_patterns = [regex_patterns] - if type(negative_regex_patterns) == "string": - negative_regex_patterns = [negative_regex_patterns] python_args = ["--files"] + files - if patterns: - python_args.append("--patterns") - python_args.extend(["\"{}\"".format(pat) for pat in patterns]) - if negative_patterns: - python_args.append("--negative_patterns") - python_args.extend(["\"{}\"".format(pat) for pat in negative_patterns]) + if contains: + python_args.append("--contains") + python_args.extend(["\"{}\"".format(pat) for pat in contains]) + if excludes: + python_args.append("--excludes") + python_args.extend(["\"{}\"".format(pat) for pat in excludes]) if regex_patterns: python_args.append("--regex_patterns") python_args.extend(["\"{}\"".format(pat) for pat in regex_patterns]) - if negative_regex_patterns: - python_args.append("--negative_regex_patterns") - python_args.extend(["\"{}\"".format(pat) for pat in negative_regex_patterns]) if not require_patterns_in_each_file: python_args.append("--any") From 973d898521b52a89d9cf6d7b269379d1f6ef7e97 Mon Sep 17 00:00:00 2001 From: "F.Tibor" Date: Tue, 16 Jun 2026 07:37:30 +0200 Subject: [PATCH 21/22] Revert pyton template changes --- .pylintrc | 2 +- .../unit/template/{test_python_template.py => test_template.py} | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) rename test/unit/template/{test_python_template.py => test_template.py} (96%) diff --git a/.pylintrc b/.pylintrc index 5ccda61c..a8a353ac 100644 --- a/.pylintrc +++ b/.pylintrc @@ -1,7 +1,7 @@ [MASTER] # To find common lib for tests init-hook='import sys; import os; sys.path.append("test"); sys.path.append(os.getcwd())' -ignore = test_python_template.py +ignore = test_template.py [FORMAT] max-line-length=80 diff --git a/test/unit/template/test_python_template.py b/test/unit/template/test_template.py similarity index 96% rename from test/unit/template/test_python_template.py rename to test/unit/template/test_template.py index 947e5389..3de86808 100644 --- a/test/unit/template/test_python_template.py +++ b/test/unit/template/test_template.py @@ -14,6 +14,8 @@ """ TODO: Describe what this file does + +Do not write python tests, unless absolutely necessary! """ import os From 0cac0bcf4b191a67b982c1cf61863197eff732c1 Mon Sep 17 00:00:00 2001 From: "F.Tibor" Date: Thu, 18 Jun 2026 14:44:07 +0200 Subject: [PATCH 22/22] Detect no input file specified --- test/unit/grep_check.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/test/unit/grep_check.py b/test/unit/grep_check.py index 46985241..74175afa 100644 --- a/test/unit/grep_check.py +++ b/test/unit/grep_check.py @@ -76,11 +76,7 @@ def parse_args() -> argparse.Namespace: def check_args(args): """Checks wether the arguments are correct, aborts if not""" - if ( - not args.contains - and not args.excludes - and not args.regex_patterns - ): + if not args.contains and not args.excludes and not args.regex_patterns: print(" [ERROR] Must define at least one pattern or negative pattern.") sys.exit(1) @@ -165,6 +161,10 @@ def main() -> None: print(f" [WARN] No files matched pattern/path: '{file_pattern}'") file_paths.extend(matched_files) + if not file_paths: + print(" [ERR] No file collected to be checked.") + sys.exit(1) + for file in file_paths: with open(file, "r", encoding="utf-8") as f: content = f.read()