Repository: intel/cve-bin-tool

Issue:
test: Enhance `test_checkers.py` to cover new situations
There is currently a `MyFakeChecker` class in `test/test_checkers.py` which tests two things:
1. Are ignore patterns working correctly?
2. Is version number parsing working correctly?

These test cases both work by running the test checker against a single string, then checking the first element of the returned list against the expected return value.

After #4911, checkers can now support finding multiple versions of a single product during a run, and we don't have tests confirming this works OK. I think we need at least these new test cases:
1. When the file contains version strings A and B, does the returned list contain version numbers A and B?
2. When the file contains version strings A and B, where both A and B are ignored, does the returned list have `"UNKNOWN"` as its only element?
3. When the file contains version strings A, B, and C, where B is ignored, does the returned list contain version numbers A and C?

Discussion:
@devesh-2002: Hi. I would like to work on this issue.@Saksham-Sirohi: Hey @devesh-2002 , I've already completed some of the work on this. Would it be possible for you to take up another issue instead? Let me know what you think!@terriko: @22f1001635 Quick heads up: issues marked as "Good first issue" are meant for folk who haven't had any merged PRs. You're too experienced to be working on them now, and there's a lot of GSoC folk who would really like to work on the easier stuff so please leave them for other people.  Since @stvml has already been working on code review with you I'm going to let this one be, but next time please go ahead and work on one of the other 130+ issues available that haven't been specially tagged for beginners.

If you're looking for a challenge, I'd *really* like someone to work on #4417 -- it's a lot of reading because of the debugging we did but my current thinking is that it's a bug where we can't handle "/" in a productname in the way the submitter was using the scanner.  Given your experience writing tests, maybe you could start with some tests for special characters in productnames and see if you can figure out where it's going wrong?@Saksham-Sirohi: Hi @terriko I will avoid good first issues moving forward Also, I will be taking a look at #4417; it definitely looks challenging 

Solution patch:
diff --git a/cve_bin_tool/checkers/sqlite.py b/cve_bin_tool/checkers/sqlite.py
index e6a92afd..186d5a2a 100644
--- a/cve_bin_tool/checkers/sqlite.py
+++ b/cve_bin_tool/checkers/sqlite.py
@@ -97,6 +97,6 @@ class SqliteChecker(Checker):
             # https://www.sqlite.org/c3ref/c_source_id.html
             if mapping[1][:-4] in lines:
                 # overwrite version with the version found by sha mapping
-                version_info.versions.append(mapping[0])
+                version_info.versions.add(mapping[0])
 
         return version_info
diff --git a/cve_bin_tool/util.py b/cve_bin_tool/util.py
index beb9d11f..11ee0533 100644
--- a/cve_bin_tool/util.py
+++ b/cve_bin_tool/util.py
@@ -215,22 +215,22 @@ class VersionMatchInfo:
     attributes:
         matched_filename: bool
         matched_contains: bool
-        versions: list[str]
+        versions: set[str]
     """
 
     matched_filename: bool
     matched_contains: bool
-    versions: list[str]
+    versions: set[str]
 
     def __init__(
         self,
         matched_filename: bool = False,
         matched_contains: bool = False,
-        versions: list[str] = [],
+        versions: set[str] | None = None,
     ):
         self.matched_filename = matched_filename
         self.matched_contains = matched_contains
-        self.versions = versions or []
+        self.versions = versions if versions is not None else set()
 
 
 class VersionInfo(NamedTuple):
@@ -280,24 +280,22 @@ class CVEData(DefaultDict[str, Union[List[CVE], Set[str]]]):
 
 def regex_find(
     lines: str, version_patterns: list[Pattern[str]], ignore: list[Pattern[str]]
-) -> list[str]:
+) -> set[str]:
     """Search a set of lines to find all matches for the given regex"""
-    versions = list()
+    versions = set()
 
     for pattern in version_patterns:
         version_matches = pattern.finditer(lines)
         for match in version_matches:
             # before collecting a potential version number, ensure the version string isn't on the ignore list
-            if not check_ignored(match.string, ignore):
+            matched_text = match.group(0)
+            if not check_ignored(matched_text, ignore):
                 version = match.group(1).strip()
                 version = version.replace("_", ".").replace("-", ".")
-                versions.append(version)
+                versions.add(version)
 
-    # if we searched and found no matches, just return a one-element list containing "UNKNOWN"
-    if not versions:
-        versions.append("UNKNOWN")
-
-    return versions
+    # if we searched and found no matches, just return a one-element set containing "UNKNOWN"
+    return versions if versions else {"UNKNOWN"}
 
 
 def check_ignored(possible_version_string: str, ignore: list[Pattern[str]]) -> bool:


Test patch:
diff --git a/test/test_checkers.py b/test/test_checkers.py
index e1d833d2..745d5b4d 100644
--- a/test/test_checkers.py
+++ b/test/test_checkers.py
@@ -145,12 +145,33 @@ class TestCheckerVersionParser:
         VENDOR_PRODUCT = [("my", "checker")]
         IGNORE_PATTERNS = [r"mychecker-5.6"]
 
-    string = "Some other lines. \n Ignore this version pattern mychecker-5.6. \n Consider this version pattern mychecker-5.8. \n Some more lines."
-    lines = string.split("\n")
-    checker = MyFakeChecker()
-
-    result1 = checker.get_versions(lines[1], "")
-    assert result1.versions[0] == "UNKNOWN"
-
-    result2 = checker.get_versions(lines[2], "")
-    assert result2.versions[0] == "5.8"
+    class TestVersionParsing:
+        """Unit tests for MyFakeChecker class, validating version parsing and detection logic."""
+
+        @pytest.mark.parametrize(
+            "description, input_text, expected_versions",
+            [
+                (
+                    "Multiple valid versions",
+                    "mychecker-5.8\nmychecker-6.0",
+                    {"5.8", "6.0"},
+                ),
+                ("All versions ignored", "mychecker-5.6\nmychecker-5.6", {"UNKNOWN"}),
+                (
+                    "Mixed valid and ignored versions",
+                    "mychecker-5.8\nmychecker-5.6\nmychecker-6.0",
+                    {"5.8", "6.0"},
+                ),
+                ("No version pattern", "random text", set()),
+                (
+                    "Duplicate versions",
+                    "mychecker-5.8\nmychecker-5.8\nmychecker-6.0",
+                    {"5.8", "6.0"},
+                ),
+            ],
+        )
+        def test_checker_version(self, description, input_text, expected_versions):
+            """Test get_versions logic using MyFakeChecker class, ensuring multiple versions are processed correctly."""
+            checker = TestCheckerVersionParser.MyFakeChecker()
+            result = checker.get_versions(input_text, "")
+            assert result.versions == expected_versions, f"{description} failed"