Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/unit_test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ jobs:
- name: Run tests
run: |
cd test
bazel test //...
python3 -m unittest discover unit -vvv

rhel9_test:
Expand Down Expand Up @@ -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
15 changes: 15 additions & 0 deletions test/unit/BUILD
Original file line number Diff line number Diff line change
@@ -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"])
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess this should not be needed

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch.

207 changes: 207 additions & 0 deletions test/unit/grep_check.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
# 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 glob
from itertools import chain
import re
import sys
from typing import Callable


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(
"--files",
nargs="+",
required=True,
help="Path or glob pattern to the file(s) to search within.",
)
parser.add_argument(
"--patterns",
nargs="+",
required=False,
help="One or more patterns to assert are present in the file(s).",
)
parser.add_argument(
"--negative_patterns",
nargs="+",
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()
check_args(args)

all_passed = True
found_patterns = set()
missing_patterns = set()

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()
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)


if __name__ == "__main__":
main()
40 changes: 35 additions & 5 deletions test/unit/legacy/BUILD
Original file line number Diff line number Diff line change
@@ -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",
Expand Down Expand Up @@ -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/unit:unit_test.bzl",
"unit_test",
Comment on lines +36 to +37
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please dont touch "my" legacy tests so far :)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, I will revert them.

)

# Test for strip_include_prefix
Expand Down Expand Up @@ -80,6 +84,18 @@ compile_commands(
],
)

unit_test(
name = "compile_commands_pass_test",
data = [
":compile_commands_pass/compile_commands.json",
],
files = "$(location :compile_commands_pass/compile_commands.json)",
patterns = [
r"pass\.cc",
r"\/gcc",
],
)

# CodeChecker configuration options specification
# based on Bazel configuration approach
codechecker_config(
Expand Down Expand Up @@ -146,6 +162,20 @@ codechecker_test(
],
)

unit_test(
name = "codechecker_fail_test",
data = [
":codechecker_fail/codechecker.log",
],
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",
],
)

# This codechecker_test CTU example supposed to fail showing findings report
# Note "manual" tag (means should not be run with other tests)
codechecker_test(
Expand Down
30 changes: 0 additions & 30 deletions test/unit/legacy/test_legacy.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -121,16 +109,6 @@ 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)
Expand All @@ -152,21 +130,13 @@ 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)
logfile = os.path.join(
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)
Expand Down
Loading
Loading