Skip to content

Commit 77d0404

Browse files
Fix over-eager file matching in skip files and --file option (#4670)
* Remove '*' from skip file discorvery to match documentation better --file=a.c should not match a.cpp * Add '*' to entries that trivially folders * Revert "Add '*' to entries that trivially folders" This reverts commit b1ac6f6. * Add in regex to only match all files if the specified input is a directory * Fix regex, add comment explaining what does it do * More sophisticated removal of '\Z' in skip file regex * Handle windows paths correctly in skipfiles * Update platform specific separator * Escape that separator every time * Remove the version specific line * Remove unnecesarry import * Generalyze analyze command * Add test for filenames that are prefixes for each other * Add test for folder skip recognition * Remove trailing whitespaces * Fix linting errors * Fix typos, filenames, and Makefiles * Rewrite test to be more extensible * Comment skip_directory test * Fix lint errors * Fix test * change *.o to $(obgs) Co-authored-by: Barnabás Domozi <142594183+barnabasdomozi@users.noreply.github.com> * Remove unnecesary space Co-authored-by: Barnabás Domozi <142594183+barnabasdomozi@users.noreply.github.com> * Change skipme/*.o to $(OBJS) Co-authored-by: Barnabás Domozi <142594183+barnabasdomozi@users.noreply.github.com> * Update comment in analyzer/tests/functional/skip/test_skip.py Co-authored-by: Barnabás Domozi <142594183+barnabasdomozi@users.noreply.github.com> * Update comment in analyzer/tests/functional/skip/test_skip.py Co-authored-by: Barnabás Domozi <142594183+barnabasdomozi@users.noreply.github.com> * Remove unnecesary space Co-authored-by: Barnabás Domozi <142594183+barnabasdomozi@users.noreply.github.com> * Fix comments and make files * Fragment comment to avoid too long line * Update analyzer/tests/functional/skip/test_skip.py Co-authored-by: Barnabás Domozi <142594183+barnabasdomozi@users.noreply.github.com> * Update analyzer/tests/functional/skip/test_files/similar/Makefile Co-authored-by: Barnabás Domozi <142594183+barnabasdomozi@users.noreply.github.com> * Update analyzer/tests/functional/skip/test_skip.py Co-authored-by: Barnabás Domozi <142594183+barnabasdomozi@users.noreply.github.com> --------- Co-authored-by: Barnabás Domozi <142594183+barnabasdomozi@users.noreply.github.com>
1 parent 15686e1 commit 77d0404

7 files changed

Lines changed: 167 additions & 34 deletions

File tree

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
C_OBJS = $(C_SRCS:.c=_c.o)
2+
CPP_OBJS = $(CPP_SRCS:.cpp=_cpp.o)
3+
4+
OBJS = $(C_OBJS) $(CPP_OBJS)
5+
6+
CXXFLAGS += -Wno-all -Wno-extra -Wno-division-by-zero
7+
CFLAGS += -Wno-division-by-zero
8+
9+
C_SRCS = simple.c
10+
CPP_SRCS = simple.cpp
11+
12+
all: $(OBJS)
13+
14+
$(CPP_OBJS): %_cpp.o: %.cpp
15+
$(CXX) $(CXXFLAGS) -c $< -o $@
16+
17+
$(C_OBJS): %_c.o: %.c
18+
$(CC) $(CFLAGS) -c $< -o $@
19+
20+
clean:
21+
rm -rf $(OBJS)
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// -------------------------------------------------------------------------
2+
// Part of the CodeChecker project, under the Apache License v2.0 with
3+
// LLVM Exceptions. See LICENSE for license information.
4+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
5+
// -------------------------------------------------------------------------
6+
7+
// This file is to be included in a --file argument.
8+
9+
void main(int z) {
10+
int x;
11+
if (z == 0)
12+
x = 1 / z; // warn
13+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
// -------------------------------------------------------------------------
2+
// Part of the CodeChecker project, under the Apache License v2.0 with
3+
// LLVM Exceptions. See LICENSE for license information.
4+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
5+
// -------------------------------------------------------------------------
6+
7+
// This file is not to be added to a --file argument.
8+
9+
void skipped_test(int z) {
10+
if (z == 0)
11+
int x = 1 / z; // warn
12+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
OBJS = $(SRCS:.cpp=.o)
2+
3+
CXXFLAGS += -Wno-all -Wno-extra -Wno-division-by-zero
4+
5+
SRCS = skipme/skipme.cpp
6+
7+
all: $(OBJS)
8+
9+
.cpp.o:
10+
$(CXX) $(CXXFLAGS) -c $< -o $@
11+
12+
clean:
13+
rm -rf $(OBJS)
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
// -------------------------------------------------------------------------
2+
// Part of the CodeChecker project, under the Apache License v2.0 with
3+
// LLVM Exceptions. See LICENSE for license information.
4+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
5+
// -------------------------------------------------------------------------
6+
7+
// This file is to be included in a skip list file.
8+
9+
void skipped_test(int z) {
10+
if (z == 0)
11+
int x = 1 / z; // warn
12+
}

analyzer/tests/functional/skip/test_skip.py

Lines changed: 85 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -65,13 +65,14 @@ def setup_method(self, _):
6565
self.report_dir = os.path.join(self.test_workspace, "reports")
6666
self.test_dir = os.path.join(os.path.dirname(__file__), 'test_files')
6767

68-
def __analyze_simple(self,
69-
build_json,
70-
analyzer_extra_options=None,
71-
cwd=None):
72-
""" Analyze the 'simple' project. """
68+
def __analyze(self,
69+
target,
70+
build_json,
71+
analyzer_extra_options=None,
72+
cwd=None):
73+
""" Analyze the target project. """
7374
if not cwd:
74-
cwd = os.path.join(self.test_dir, "simple")
75+
cwd = os.path.join(self.test_dir, target)
7576

7677
analyze_cmd = [
7778
self._codechecker_cmd, "analyze", "-c", build_json,
@@ -94,9 +95,9 @@ def __analyze_simple(self,
9495
self.assertEqual(process.returncode, 0)
9596
return out, err
9697

97-
def __log_and_analyze_simple(self, analyzer_extra_options=None):
98-
""" Log and analyze the 'simple' project. """
99-
test_dir = os.path.join(self.test_dir, "simple")
98+
def __log_and_analyze(self, target, analyzer_extra_options=None):
99+
""" Log and analyze the target project. """
100+
test_dir = os.path.join(self.test_dir, target)
100101
build_json = os.path.join(self.test_workspace, "build.json")
101102

102103
clean_cmd = ["make", "clean"]
@@ -113,7 +114,7 @@ def __log_and_analyze_simple(self, analyzer_extra_options=None):
113114
encoding="utf-8", errors="ignore")
114115
print(out)
115116
# Create and run analyze command.
116-
return self.__analyze_simple(build_json, analyzer_extra_options)
117+
return self.__analyze(target, build_json, analyzer_extra_options)
117118

118119
def __run_parse(self, extra_options=None):
119120
""" Run parse command with the given extra options. """
@@ -138,7 +139,7 @@ def test_skip(self):
138139
"""Analyze a project with a skip file."""
139140
# we should see a report from skip.h
140141
# we should not see a report from file_to_be_skipped.cpp
141-
self.__log_and_analyze_simple(["--ignore", "skipfile_drop"])
142+
self.__log_and_analyze("simple", ["--ignore", "skipfile_drop"])
142143

143144
# Check if file is skipped.
144145
report_dir_files = os.listdir(self.report_dir)
@@ -170,13 +171,39 @@ def test_skip(self):
170171
"Reports from headers should be kept"
171172
" if the header is not on the skiplist")
172173

174+
def test_skip_directory(self):
175+
"""Test whether directories in skipfile entries works correctly"""
176+
skip_file_contents: list[list[str]] = [
177+
["-*skipme/*"], # The way it is in the documentation
178+
["-*skipme/"], # If the user specified that this is a directory
179+
["-*skipme"], # If we can't even be sure that this is a directory
180+
]
181+
for skip_file_content in skip_file_contents:
182+
with tempfile.NamedTemporaryFile(
183+
mode="w", suffix="skipfile", encoding="utf-8"
184+
) as skip_file:
185+
186+
# Skip the `skipme` folder
187+
skip_file.write('\n'.join(skip_file_content))
188+
skip_file.flush()
189+
self.__log_and_analyze(
190+
"skip_folder", ["--ignore", skip_file.name]
191+
)
192+
193+
# Check if the folder `skipme` is skipped
194+
# There shouldn't be any report
195+
# generated for `skipme/skipme.cpp`
196+
report_dir_files = os.listdir(self.report_dir)
197+
for f in report_dir_files:
198+
self.assertFalse("skipme.cpp" in f)
199+
173200
def test_drop_reports(self):
174201
"""Analyze a project with a skip file."""
175202
# we should not see a report from skip.h
176203
# we should not see a report from file_to_be_skipped.cpp
177204

178-
self.__log_and_analyze_simple(["--ignore", "skipfile",
179-
"--drop-reports-from-skipped-files"])
205+
self.__log_and_analyze("simple", ["--ignore", "skipfile",
206+
"--drop-reports-from-skipped-files"])
180207

181208
# Check if file is skipped.
182209
report_dir_files = os.listdir(self.report_dir)
@@ -296,15 +323,15 @@ def test_analyze_skip_everything(self):
296323
skip_file.write('-*')
297324
skip_file.flush()
298325

299-
self.__log_and_analyze_simple([
300-
"--ignore", skip_file.name])
326+
self.__log_and_analyze("simple",
327+
["--ignore", skip_file.name])
301328
self.assertFalse(
302329
glob.glob(os.path.join(self.report_dir, '*.plist')))
303330

304331
def test_analyze_header_with_file_option(self):
305332
""" Analyze a header file with the --file option. """
306333
header_file = os.path.join(self.test_dir, "simple", "skip.h")
307-
out, _ = self.__log_and_analyze_simple(["--file", header_file])
334+
out, _ = self.__log_and_analyze("simple", ["--file", header_file])
308335
self.assertIn(
309336
f"Get dependent source files for '{header_file}'...", out)
310337
self.assertIn(
@@ -342,7 +369,7 @@ def check_parse_out(out):
342369
shutil.copytree(Path(self.test_dir, "simple"),
343370
Path(self.test_workspace, "rel_simple"))
344371
# Obtain a compilation database, copy it to the prepared test workspace
345-
self.__log_and_analyze_simple()
372+
self.__log_and_analyze("simple")
346373
build_json = Path(self.test_workspace, "rel_simple", "build.json")
347374
shutil.copy(Path(self.test_workspace, "build.json"), build_json)
348375

@@ -358,8 +385,17 @@ def check_parse_out(out):
358385
json.dump(compilation_commands, f)
359386

360387
# Do the CodeChecker Analyze with --file
361-
out, _ = self.__analyze_simple(build_json, ["--clean", "--file", Path(
362-
self.test_workspace, "rel_simple", "skip_header.cpp").absolute()])
388+
out, _ = self.__analyze(
389+
"simple",
390+
build_json,
391+
[
392+
"--clean",
393+
"--file",
394+
Path(
395+
self.test_workspace, "rel_simple", "skip_header.cpp"
396+
).absolute(),
397+
],
398+
)
363399
check_analyze_out(out)
364400

365401
out = self.__run_parse()
@@ -374,14 +410,18 @@ def check_parse_out(out):
374410

375411
# Do the CodeChecker Analyze with --file
376412
# We also need to set cwd to test_workspace
377-
out, _ = self.__analyze_simple(build_json,
378-
["--clean",
379-
"--file",
380-
Path(
381-
self.test_workspace,
382-
"rel_simple",
383-
"skip_header.cpp").absolute()],
384-
cwd=self.test_workspace)
413+
out, _ = self.__analyze(
414+
"simple",
415+
build_json,
416+
[
417+
"--clean",
418+
"--file",
419+
Path(
420+
self.test_workspace, "rel_simple", "skip_header.cpp"
421+
).absolute(),
422+
],
423+
cwd=self.test_workspace,
424+
)
385425
check_analyze_out(out)
386426

387427
out = self.__run_parse()
@@ -409,7 +449,7 @@ def test_analyze_header_with_file_option_and_intercept_json(self):
409449
json.dump(build_actions, f)
410450

411451
header_file = os.path.join(self.test_dir, "simple", "skip.h")
412-
out, _ = self.__analyze_simple(build_json, ["--file", header_file])
452+
out, _ = self.__analyze("simple", build_json, ["--file", header_file])
413453
self.assertIn(
414454
f"Get dependent source files for '{header_file}'...", out)
415455
self.assertIn(
@@ -432,7 +472,7 @@ def test_analyze_file_option_skip_everything(self):
432472
skip_file.write('-*')
433473
skip_file.flush()
434474

435-
self.__log_and_analyze_simple([
475+
self.__log_and_analyze("simple", [
436476
"--ignore", skip_file.name,
437477
"--file", "*/file_to_be_skipped.cpp"])
438478
self.assertFalse(
@@ -456,7 +496,7 @@ def test_analyze_file_option(self):
456496
]))
457497
skip_file.flush()
458498

459-
self.__log_and_analyze_simple([
499+
self.__log_and_analyze("simple", [
460500
"--ignore", skip_file.name,
461501
"--file", "*/skip_header.cpp"])
462502
print(glob.glob(
@@ -473,7 +513,7 @@ def test_analyze_file_option(self):
473513
]))
474514
skip_file.flush()
475515

476-
self.__log_and_analyze_simple([
516+
self.__log_and_analyze("simple", [
477517
"--ignore", skip_file.name,
478518
"--file", "*/skip_header.cpp"])
479519
print(glob.glob(
@@ -482,11 +522,23 @@ def test_analyze_file_option(self):
482522
any('skip_header.cpp' not in f for f in glob.glob(
483523
os.path.join(self.report_dir, '*.plist'))))
484524

525+
def test_analyze_similar_file_option(self):
526+
"""
527+
Test analyze command --file option for edge case,
528+
where one filename is a prefix of another.
529+
"""
530+
self.__log_and_analyze("similar", ["--file", "*/simple.c"])
531+
print(glob.glob(
532+
os.path.join(self.report_dir, '*.plist')))
533+
self.assertFalse(
534+
any('simple.cpp' in f for f in glob.glob(
535+
os.path.join(self.report_dir, '*.plist'))))
536+
485537
def test_analyze_only_file_option(self):
486538
"""
487539
Test analyze command --file option without a skip file.
488540
"""
489-
self.__log_and_analyze_simple([
541+
self.__log_and_analyze("simple", [
490542
"--file", "*/skip_header.cpp"])
491543
self.assertFalse(
492544
any('skip_header.cpp' not in f for f in glob.glob(
@@ -496,7 +548,7 @@ def test_parse_file_option(self):
496548
""" Test parse command --file option. """
497549
skipfile = os.path.join(self.test_dir, "simple", "skipfile")
498550

499-
self.__log_and_analyze_simple()
551+
self.__log_and_analyze("simple")
500552

501553
# Only reports from the given files are returned.
502554
out, _, returncode = self.__run_parse(

codechecker_common/skiplist_handler.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,18 @@ def __gen_regex(self, skip_lines):
5252
"""
5353
for skip_line in skip_lines:
5454
norm_skip_path = os.path.normpath(skip_line[1:].strip())
55+
# fnmatch places a '\Z' (end of input) at the end, in this case,
56+
# this is equivalent to '$' (end of line). This must be removed.
57+
# Optionally we match the user given path with a '/.*' ending.
58+
# This, if the given input is a folder will
59+
# resolve all files contained within.
60+
# Note: normalization removes '/' from the end, see:
61+
# https://docs.python.org/3/library/os.path.html#os.path.normpath
62+
translated_glob = fnmatch.translate(norm_skip_path)
63+
if translated_glob.endswith(r"\Z"):
64+
translated_glob = translated_glob[:-2]
5565
rexpr = re.compile(
56-
fnmatch.translate(norm_skip_path + '*'))
66+
translated_glob + fr"(?:\{os.path.sep}.*)?$")
5767
self.__skip.append((skip_line, rexpr))
5868

5969
def __check_line_format(self, skip_lines):

0 commit comments

Comments
 (0)