From 57915607e12f9ad1a6b7de7a57242227e09bdd56 Mon Sep 17 00:00:00 2001 From: "F.Tibor" Date: Mon, 16 Mar 2026 13:47:57 +0100 Subject: [PATCH 01/13] Integrate tests into bazel --- test/common/BUILD | 15 +++++++ test/common/grep_check.py | 73 +++++++++++++++++++++++++++++++++ test/unit/legacy/BUILD | 35 ++++++++++++++++ test/unit/legacy/test_legacy.py | 38 ----------------- 4 files changed, 123 insertions(+), 38 deletions(-) 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() diff --git a/test/unit/legacy/BUILD b/test/unit/legacy/BUILD index 3e263675..ac620a67 100644 --- a/test/unit/legacy/BUILD +++ b/test/unit/legacy/BUILD @@ -32,6 +32,7 @@ load( "cc_binary", "cc_library", ) +load("@bazel_skylib//rules:analysis_test.bzl", "analysis_test") # Test for strip_include_prefix cc_library( @@ -80,6 +81,22 @@ compile_commands( ], ) +py_test( + name = "compile_commands_pass_test", + srcs = ["//test/common:grep_check.py"], + data = [ + ":compile_commands_pass/compile_commands.json", + ], + args = [ + "--file", + "$(location :compile_commands_pass/compile_commands.json)", + "--patterns", + r"pass\.cc", + r"\/gcc", + ], + main = "grep_check.py", +) + # CodeChecker configuration options specification # based on Bazel configuration approach codechecker_config( @@ -146,6 +163,24 @@ codechecker_test( ], ) +py_test( + name = "codechecker_fail_test", + srcs = ["//test/common:grep_check.py"], + data = [ + ":codechecker_fail/codechecker.log", + ], + args = [ + "--file", + "$(location :codechecker_fail/codechecker.log)", + "--patterns", + r"clang-diagnostic-unused-variable\s+\|\s+MEDIUM\s+\|\s+1", + r"core.NullDereference\s+\|\s+HIGH\s+\|\s+1", + r"deadcode.DeadStores\s+\|\s+LOW\s+\|\s+1", + r"lib.cc\s+\|\s+3", + ], + main = "grep_check.py", +) + # This codechecker_test CTU example supposed to fail showing findings report # Note "manual" tag (means should not be run with other tests) codechecker_test( diff --git a/test/unit/legacy/test_legacy.py b/test/unit/legacy/test_legacy.py index 5aae4312..3453b451 100644 --- a/test/unit/legacy/test_legacy.py +++ b/test/unit/legacy/test_legacy.py @@ -98,18 +98,6 @@ def test_bazel_build_pass(self): """Test: bazel build :test_pass""" self.check_command("bazel build :test_pass") - def test_bazel_test_fail(self): - """Test: bazel test :codechecker_fail""" - self.check_command("bazel test :codechecker_fail", exit_code=3) - logfile = os.path.join( - self.BAZEL_BIN_DIR, "codechecker_fail", "codechecker.log") - self.grep_file( - logfile, - r"clang-diagnostic-unused-variable\s+\|\s+MEDIUM\s+\|\s+1") - self.grep_file(logfile, r"core.NullDereference\s+\|\s+HIGH\s+\|\s+1") - self.grep_file(logfile, r"deadcode.DeadStores\s+\|\s+LOW\s+\|\s+1") - self.grep_file(logfile, r"lib.cc\s+\|\s+3") - def test_bazel_test_ctu(self): """Test: bazel test :codechecker_ctu""" self.check_command("bazel test :codechecker_ctu", exit_code=3) @@ -121,28 +109,10 @@ def test_bazel_build_fail(self): """Test: bazel build :test_fail""" self.check_command("bazel build :test_fail", exit_code=0) - def test_bazel_compile_commands(self): - """Test: bazel build --build_tag_filters=compile_commands ...""" - self.check_command( - "bazel build --build_tag_filters=compile_commands ...") - compile_commands = os.path.join( - self.BAZEL_BIN_DIR, "compile_commands_pass", - "compile_commands.json") - self.grep_file(compile_commands, r"pass\.cc") - self.grep_file(compile_commands, r"\/gcc") - - def test_bazel_build_clang_tidy_pass(self): - """Test: bazel build :clang_tidy_pass""" - self.check_command("bazel build :clang_tidy_pass", exit_code=0) - def test_bazel_build_clang_tidy_fail(self): """Test: bazel build :clang_tidy_fail""" self.check_command("bazel build :clang_tidy_fail", exit_code=1) - def test_bazel_build_clang_analyze_pass(self): - """Test: bazel build :clang_analyze_pass""" - self.check_command("bazel build :clang_analyze_pass", exit_code=0) - def test_bazel_build_clang_analyze_fail(self): """Test: bazel build :clang_analyze_fail""" self.check_command("bazel build :clang_analyze_fail", exit_code=1) @@ -152,10 +122,6 @@ def test_bazel_build_clang_ctu(self): self.check_command( "bazel build :clang_ctu_pass :clang_ctu_fail", exit_code=0) - def test_bazel_test_clang_ctu_pass(self): - """Test: bazel test :clang_ctu_pass""" - self.check_command("bazel test :clang_ctu_pass", exit_code=0) - def test_bazel_test_clang_ctu_fail(self): """Test: bazel test :clang_ctu_fail""" self.check_command("bazel test :clang_ctu_fail", exit_code=3) @@ -163,10 +129,6 @@ def test_bazel_test_clang_ctu_fail(self): self.BAZEL_TESTLOGS_DIR, "clang_ctu_fail", "test.log") self.grep_file(logfile, "// CTU example") - def test_bazel_test_code_checker_pass(self): - """Test: bazel test :code_checker_pass""" - self.check_command("bazel test :code_checker_pass", exit_code=0) - def test_bazel_test_code_checker_fail(self): """Test: bazel test :code_checker_fail""" self.check_command("bazel test :code_checker_fail", exit_code=3) From bec39733c009f0ff1ec8c027158c3dda38457dfb Mon Sep 17 00:00:00 2001 From: "F.Tibor" Date: Thu, 19 Mar 2026 16:47:32 +0100 Subject: [PATCH 02/13] 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 d23caca7b8abbd5c32368a52eb1820a906507ff6 Mon Sep 17 00:00:00 2001 From: "F.Tibor" Date: Thu, 19 Mar 2026 17:13:06 +0100 Subject: [PATCH 03/13] Fix legacy tests --- test/common/grep_check.py | 48 ++++++++++++++++++++++++++++----------- test/unit/legacy/BUILD | 9 ++++---- 2 files changed, 39 insertions(+), 18 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.") diff --git a/test/unit/legacy/BUILD b/test/unit/legacy/BUILD index ac620a67..e62d056f 100644 --- a/test/unit/legacy/BUILD +++ b/test/unit/legacy/BUILD @@ -32,7 +32,6 @@ load( "cc_binary", "cc_library", ) -load("@bazel_skylib//rules:analysis_test.bzl", "analysis_test") # Test for strip_include_prefix cc_library( @@ -173,10 +172,10 @@ py_test( "--file", "$(location :codechecker_fail/codechecker.log)", "--patterns", - r"clang-diagnostic-unused-variable\s+\|\s+MEDIUM\s+\|\s+1", - r"core.NullDereference\s+\|\s+HIGH\s+\|\s+1", - r"deadcode.DeadStores\s+\|\s+LOW\s+\|\s+1", - r"lib.cc\s+\|\s+3", + r"clang-diagnostic-unused-variable\\s+\\|\\s+MEDIUM\\s+\\|\\s+1", + r"core.NullDereference\\s+\\|\\s+HIGH\\s+\\|\\s+1", + r"deadcode.DeadStores\\s+\\|\\s+LOW\\s+\\|\\s+1", + r"lib.cc\\s+\\|\\s+3", ], main = "grep_check.py", ) From ffd934d6cc48ec3bfcb9b2eab30caa0b4331a12a Mon Sep 17 00:00:00 2001 From: "F.Tibor" Date: Mon, 30 Mar 2026 09:50:23 +0200 Subject: [PATCH 04/13] 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 94d19492070b397066fd5e23f5440f32586b2604 Mon Sep 17 00:00:00 2001 From: "F.Tibor" Date: Mon, 30 Mar 2026 09:50:34 +0200 Subject: [PATCH 05/13] Fix tests --- test/unit/legacy/BUILD | 2 +- test/unit/virtual_include/BUILD | 31 +++++++++++++++++++ .../virtual_include/test_virtual_include.py | 25 --------------- 3 files changed, 32 insertions(+), 26 deletions(-) diff --git a/test/unit/legacy/BUILD b/test/unit/legacy/BUILD index e62d056f..6c64d469 100644 --- a/test/unit/legacy/BUILD +++ b/test/unit/legacy/BUILD @@ -171,7 +171,7 @@ py_test( args = [ "--file", "$(location :codechecker_fail/codechecker.log)", - "--patterns", + "--regex_patterns", r"clang-diagnostic-unused-variable\\s+\\|\\s+MEDIUM\\s+\\|\\s+1", r"core.NullDereference\\s+\\|\\s+HIGH\\s+\\|\\s+1", r"deadcode.DeadStores\\s+\\|\\s+LOW\\s+\\|\\s+1", diff --git a/test/unit/virtual_include/BUILD b/test/unit/virtual_include/BUILD index beaad605..a117beec 100644 --- a/test/unit/virtual_include/BUILD +++ b/test/unit/virtual_include/BUILD @@ -86,3 +86,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 3c2ad4d013aa61046b2b159535de7149358def4a Mon Sep 17 00:00:00 2001 From: "F.Tibor" Date: Tue, 31 Mar 2026 22:19:04 +0200 Subject: [PATCH 06/13] 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 9567fc4d0441c9c43a5ba3aa8b0e5c1bacae61f4 Mon Sep 17 00:00:00 2001 From: "F.Tibor" Date: Tue, 31 Mar 2026 22:22:28 +0200 Subject: [PATCH 07/13] Change legacy tests to use new unit test --- test/unit/legacy/BUILD | 34 +++++++++++++++------------------- 1 file changed, 15 insertions(+), 19 deletions(-) diff --git a/test/unit/legacy/BUILD b/test/unit/legacy/BUILD index 6c64d469..7cd61b02 100644 --- a/test/unit/legacy/BUILD +++ b/test/unit/legacy/BUILD @@ -1,3 +1,10 @@ +# cc_binary for simple C++ tests +load( + "@rules_cc//cc:defs.bzl", + "cc_binary", + "cc_library", +) + # clang-tidy and clang -analyze rules load( "//src:clang.bzl", @@ -25,12 +32,9 @@ load( "//src:compile_commands.bzl", "compile_commands", ) - -# cc_binary for simple C++ tests load( - "@rules_cc//cc:defs.bzl", - "cc_binary", - "cc_library", + "//test/common:unit_test.bzl", + "unit_test", ) # Test for strip_include_prefix @@ -80,20 +84,16 @@ compile_commands( ], ) -py_test( +unit_test( name = "compile_commands_pass_test", - srcs = ["//test/common:grep_check.py"], data = [ ":compile_commands_pass/compile_commands.json", ], - args = [ - "--file", - "$(location :compile_commands_pass/compile_commands.json)", - "--patterns", + files = "$(location :compile_commands_pass/compile_commands.json)", + patterns = [ r"pass\.cc", r"\/gcc", ], - main = "grep_check.py", ) # CodeChecker configuration options specification @@ -162,22 +162,18 @@ codechecker_test( ], ) -py_test( +unit_test( name = "codechecker_fail_test", - srcs = ["//test/common:grep_check.py"], data = [ ":codechecker_fail/codechecker.log", ], - args = [ - "--file", - "$(location :codechecker_fail/codechecker.log)", - "--regex_patterns", + files = "$(location :codechecker_fail/codechecker.log)", + regex_patterns = [ r"clang-diagnostic-unused-variable\\s+\\|\\s+MEDIUM\\s+\\|\\s+1", r"core.NullDereference\\s+\\|\\s+HIGH\\s+\\|\\s+1", r"deadcode.DeadStores\\s+\\|\\s+LOW\\s+\\|\\s+1", r"lib.cc\\s+\\|\\s+3", ], - main = "grep_check.py", ) # This codechecker_test CTU example supposed to fail showing findings report From 66059f5bfa34aac3719e9ecf02995514a2a83536 Mon Sep 17 00:00:00 2001 From: "F.Tibor" Date: Tue, 31 Mar 2026 22:27:29 +0200 Subject: [PATCH 08/13] Update test in virtual_include --- test/unit/virtual_include/BUILD | 44 +++++++++++++-------------------- 1 file changed, 17 insertions(+), 27 deletions(-) diff --git a/test/unit/virtual_include/BUILD b/test/unit/virtual_include/BUILD index a117beec..f92f1c64 100644 --- a/test/unit/virtual_include/BUILD +++ b/test/unit/virtual_include/BUILD @@ -12,16 +12,20 @@ # See the License for the specific language governing permissions and # limitations under the License. +# cc_binary for simple C++ tests +load( + "@rules_cc//cc:defs.bzl", + "cc_library", +) + # codechecker rules load( "//src:codechecker.bzl", "codechecker_test", ) - -# cc_binary for simple C++ tests load( - "@rules_cc//cc:defs.bzl", - "cc_library", + "//test/common:unit_test.bzl", + "unit_test", ) # Test for strip_include_prefix @@ -87,33 +91,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 b5bec72d795f10362b249d637c6cdf23fbba958b Mon Sep 17 00:00:00 2001 From: "F.Tibor" Date: Tue, 31 Mar 2026 22:43:37 +0200 Subject: [PATCH 09/13] Add Bazel test //... to CI Since tests are not being invoked from a python file anymore we need to run bazel test //... --- .github/workflows/unit_test.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/unit_test.yaml b/.github/workflows/unit_test.yaml index dd1fb12b..70e8b2b3 100644 --- a/.github/workflows/unit_test.yaml +++ b/.github/workflows/unit_test.yaml @@ -71,6 +71,7 @@ jobs: - name: Run tests run: | cd test + bazel test //... python3 -m unittest discover unit -vvv rhel9_test: @@ -108,4 +109,5 @@ jobs: - name: Run tests run: | cd test + runuser -u test -- bazel test //... runuser -u test -- python3 -m unittest discover unit -vvv From 5387f575018fb04f6a8662e6dafad79754889fe8 Mon Sep 17 00:00:00 2001 From: "F.Tibor" Date: Tue, 31 Mar 2026 22:48:48 +0200 Subject: [PATCH 10/13] Readd mistakenly removed tests --- test/unit/legacy/test_legacy.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/test/unit/legacy/test_legacy.py b/test/unit/legacy/test_legacy.py index 3453b451..7b4f0566 100644 --- a/test/unit/legacy/test_legacy.py +++ b/test/unit/legacy/test_legacy.py @@ -109,10 +109,18 @@ def test_bazel_build_fail(self): """Test: bazel build :test_fail""" self.check_command("bazel build :test_fail", exit_code=0) + def test_bazel_build_clang_tidy_pass(self): + """Test: bazel build :clang_tidy_pass""" + self.check_command("bazel build :clang_tidy_pass", exit_code=0) + def test_bazel_build_clang_tidy_fail(self): """Test: bazel build :clang_tidy_fail""" self.check_command("bazel build :clang_tidy_fail", exit_code=1) + def test_bazel_build_clang_analyze_pass(self): + """Test: bazel build :clang_analyze_pass""" + self.check_command("bazel build :clang_analyze_pass", exit_code=0) + def test_bazel_build_clang_analyze_fail(self): """Test: bazel build :clang_analyze_fail""" self.check_command("bazel build :clang_analyze_fail", exit_code=1) From 6803a88b5a830f1ff04b405031d200443c9cad17 Mon Sep 17 00:00:00 2001 From: "F.Tibor" Date: Tue, 31 Mar 2026 22:59:00 +0200 Subject: [PATCH 11/13] 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 3013225c4a5a0dbef059be2de8661fa88aaa0145 Mon Sep 17 00:00:00 2001 From: "F.Tibor" Date: Wed, 1 Apr 2026 06:38:11 +0200 Subject: [PATCH 12/13] Move unit_test to unit folder --- test/{common => unit}/BUILD | 0 test/{common => unit}/grep_check.py | 0 test/unit/legacy/BUILD | 2 +- test/{common => unit}/unit_test.bzl | 2 +- test/unit/virtual_include/BUILD | 2 +- 5 files changed, 3 insertions(+), 3 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/unit/legacy/BUILD b/test/unit/legacy/BUILD index 7cd61b02..cef04529 100644 --- a/test/unit/legacy/BUILD +++ b/test/unit/legacy/BUILD @@ -33,7 +33,7 @@ load( "compile_commands", ) load( - "//test/common:unit_test.bzl", + "//test/unit:unit_test.bzl", "unit_test", ) 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 f92f1c64..54b79117 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 ffeae115567f7e50861b216e7f51405ec5be89e7 Mon Sep 17 00:00:00 2001 From: "F.Tibor" Date: Wed, 1 Apr 2026 09:55:51 +0200 Subject: [PATCH 13/13] 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