3232 format_for_console
3333)
3434
35+ @pytest .fixture
36+ def sample_match ():
37+ return {
38+ "file_name" : "setup.py" ,
39+ "file_path" : "google-cloud-python/main/packages/pkg_a/setup.py" ,
40+ "repo_path" : "packages/pkg_a/setup.py" ,
41+ "package_name" : "pkg_a" ,
42+ "rule_name" : "python_requires_check" ,
43+ "line_number" : "123" ,
44+ "matched_string" : "3.7" ,
45+ "context_line" : "python_requires = '>=3.7'" ,
46+ "dependency" : "python" ,
47+ "version" : "3.7"
48+ }
49+
50+
51+ @pytest .mark .parametrize (
52+ "exception_to_raise, required, silent_missing, expected_exit, expected_output, expected_return" ,
53+ [
54+ (None , True , False , False , None , "file content" ), # Success
55+ (FileNotFoundError (), True , True , False , None , None ), # Silent missing FileNotFoundError
56+ (FileNotFoundError (), True , False , True , "Error: Test_desc not found" , None ), # Required FileNotFoundError
57+ (FileNotFoundError (), False , False , False , "Warning: Test_desc not found" , None ), # Optional FileNotFoundError
58+ (PermissionError (), True , False , True , "Error: Permission denied reading test_desc" , None ), # Required PermissionError
59+ (PermissionError (), False , False , False , "Warning: Permission denied reading test_desc" , None ), # Optional PermissionError
60+ (IOError ("disk full" ), True , False , True , "Error reading test_desc" , None ), # Required IOError
61+ (IOError ("disk full" ), False , False , False , "Warning: Error reading test_desc" , None ), # Optional IOError
62+ ]
63+ )
64+ def test_safe_read_file_scenarios (
65+ capsys , exception_to_raise , required , silent_missing , expected_exit , expected_output , expected_return
66+ ):
67+ from version_scanner import _safe_read_file
68+
69+ if exception_to_raise :
70+ mock_open = mock .mock_open ()
71+ mock_open .side_effect = exception_to_raise
72+ else :
73+ mock_open = mock .mock_open (read_data = "file content" )
74+
75+ with patch ("builtins.open" , mock_open ):
76+ if expected_exit :
77+ with pytest .raises (SystemExit ) as excinfo :
78+ _safe_read_file ("dummy.txt" , required = required , description = "test_desc" , silent_missing = silent_missing )
79+ assert excinfo .value .code == 1
80+ else :
81+ res = _safe_read_file ("dummy.txt" , required = required , description = "test_desc" , silent_missing = silent_missing )
82+ assert res == expected_return
83+
84+ if expected_output :
85+ captured = capsys .readouterr ()
86+ assert expected_output in captured .err
87+
88+
3589# Test ConfigManager
3690@pytest .mark .parametrize ("dependency, version, expected" , [
3791 (
@@ -682,34 +736,13 @@ def test_safe_int():
682736 assert _safe_int (None ) == 0
683737 assert _safe_int ("abc" ) == 0
684738
685- def test_format_for_raw_csv_handles_empty_line_number ():
686- match = {
687- "file_path" : "google-cloud-python/main/packages/pkg_a/setup.py" ,
688- "repo_path" : "packages/pkg_a/setup.py" ,
689- "package_name" : "pkg_a" ,
690- "rule_name" : "python_requires_check" ,
691- "line_number" : "" ,
692- "matched_string" : "3.7" ,
693- "context_line" : "python_requires = '>=3.7'"
694- }
695- formatted = format_for_raw_csv (match )
739+ def test_format_for_raw_csv_handles_empty_line_number (sample_match ):
740+ sample_match ["line_number" ] = ""
741+ formatted = format_for_raw_csv (sample_match )
696742 assert formatted ["line_number" ] == 0
697743
698- def test_format_for_raw_csv ():
699- match = {
700- "file_name" : "setup.py" ,
701- "file_path" : "google-cloud-python/main/packages/pkg_a/setup.py" ,
702- "repo_path" : "packages/pkg_a/setup.py" ,
703- "package_name" : "pkg_a" ,
704- "rule_name" : "python_requires_check" ,
705- "line_number" : "123" ,
706- "matched_string" : "3.7" ,
707- "context_line" : "python_requires = '>=3.7'" ,
708- "dependency" : "python" ,
709- "version" : "3.7"
710- }
711-
712- formatted = format_for_raw_csv (match )
744+ def test_format_for_raw_csv (sample_match ):
745+ formatted = format_for_raw_csv (sample_match )
713746
714747 assert formatted ["file_name" ] == "setup.py"
715748 assert formatted ["file_path" ] == "google-cloud-python/main/packages/pkg_a/setup.py"
@@ -721,64 +754,117 @@ def test_format_for_raw_csv():
721754 assert formatted ["dependency" ] == "python"
722755 assert formatted ["version" ] == "3.7"
723756
724- def test_format_for_raw_csv_fallback_filename ():
725- match = {
726- "file_path" : "google-cloud-python/main/packages/pkg_a/setup.py" ,
727- "repo_path" : "packages/pkg_a/setup.py" ,
728- "package_name" : "pkg_a" ,
729- "rule_name" : "python_requires_check" ,
730- "line_number" : "123" ,
731- "matched_string" : "3.7" ,
732- "context_line" : "python_requires = '>=3.7'" ,
733- "dependency" : "python" ,
734- "version" : "3.7"
735- }
736-
737- formatted = format_for_raw_csv (match )
757+ def test_format_for_raw_csv_fallback_filename (sample_match ):
758+ del sample_match ["file_name" ]
759+ formatted = format_for_raw_csv (sample_match )
738760 assert formatted ["file_name" ] == "setup.py"
739761
740- def test_format_for_spreadsheet ():
741- match = {
742- "file_name" : "setup.py" ,
743- "file_path" : "google-cloud-python/main/packages/pkg_a/setup.py" ,
744- "repo_path" : "packages/pkg_a/setup.py" ,
745- "package_name" : "pkg_a" ,
746- "rule_name" : "python_requires_check" ,
747- "line_number" : 123 ,
748- "matched_string" : "3.7" ,
749- "context_line" : "python_requires = '>=3.7'" ,
750- "dependency" : "python" ,
751- "version" : "3.7"
752- }
753-
762+ def test_format_for_spreadsheet (sample_match ):
754763 # Without github_repo
755- formatted_no_repo = format_for_spreadsheet (match )
764+ formatted_no_repo = format_for_spreadsheet (sample_match )
756765 assert formatted_no_repo ["file_name" ] == "setup.py"
757766 assert formatted_no_repo ["line_number" ] == 123
758767 assert formatted_no_repo ["matched_string" ] == '="3.7"' # Decimal protection formula
759768 assert formatted_no_repo ["dependency" ] == "python"
760769 assert formatted_no_repo ["version" ] == "3.7"
761770
762771 # With github_repo
763- formatted_repo = format_for_spreadsheet (match , github_repo = "https://github.com/user/repo" , branch = "main" )
772+ formatted_repo = format_for_spreadsheet (sample_match , github_repo = "https://github.com/user/repo" , branch = "main" )
764773 expected_url = "https://github.com/user/repo/blob/main/packages/pkg_a/setup.py#L123"
765774 assert formatted_repo ["line_number" ] == f'=HYPERLINK("{ expected_url } ", "123")'
766775 assert formatted_repo ["matched_string" ] == '="3.7"'
767776
768- def test_format_for_console ():
769- match = {
770- "file_path" : "google-cloud-python/main/packages/pkg_a/setup.py" ,
771- "repo_path" : "packages/pkg_a/setup.py" ,
772- "package_name" : "pkg_a" ,
773- "rule_name" : "python_requires_check" ,
774- "line_number" : 123 ,
775- "matched_string" : "3.7" ,
776- "context_line" : "python_requires = '>=3.7'"
777- }
778-
779- log_str = format_for_console (match )
777+ def test_format_for_console (sample_match ):
778+ log_str = format_for_console (sample_match )
780779 assert "google-cloud-python/main/packages/pkg_a/setup.py:123" in log_str
781780 assert "[python_requires_check]" in log_str
782781 assert "3.7" in log_str
783782 assert "python_requires = " not in log_str # Slim format doesn't print context line
784783
784+
785+ def test_parse_targets_file (tmp_path ):
786+ from version_scanner import parse_targets_file
787+ yaml_file = tmp_path / "targets.yaml"
788+ yaml_file .write_text ("""
789+ python:
790+ - "3.7"
791+ - "3.8"
792+ protobuf: "4.25.8"
793+ """ )
794+ targets = parse_targets_file (str (yaml_file ))
795+ assert targets == [("python" , "3.7" ), ("python" , "3.8" ), ("protobuf" , "4.25.8" )]
796+
797+ @pytest .mark .parametrize (
798+ "file_content, file_exists" ,
799+ [
800+ (None , False ), # File not found
801+ ("invalid: {" , True ), # Invalid YAML
802+ ("- not_a_mapping" , True ), # Invalid structure (list instead of map)
803+ ("python:\n - null" , True ), # Invalid version type (null/None value)
804+ ]
805+ )
806+ def test_parse_targets_file_failures (tmp_path , file_content , file_exists ):
807+ from version_scanner import parse_targets_file
808+
809+ if file_exists :
810+ yaml_file = tmp_path / "targets_failures.yaml"
811+ yaml_file .write_text (file_content )
812+ path = str (yaml_file )
813+ else :
814+ path = "nonexistent_file.yaml"
815+
816+ with pytest .raises (SystemExit ) as excinfo :
817+ parse_targets_file (path )
818+ assert excinfo .value .code == 1
819+
820+ def test_scan_repository_multi_targets (tmp_path ):
821+ # Setup files in tmp repository
822+ file1 = tmp_path / "packages" / "pkg1" / "setup.py"
823+ file1 .parent .mkdir (parents = True )
824+ file1 .write_text ("python_requires = '>=3.7'\n " )
825+
826+ file2 = tmp_path / "packages" / "pkg2" / "requirements.txt"
827+ file2 .parent .mkdir (parents = True )
828+ file2 .write_text ("protobuf==4.25.8\n " )
829+
830+ # Let's mock a config file with rules for both python and protobuf
831+ config_file = tmp_path / "regex_config.yaml"
832+ config_file .write_text ("""
833+ rules:
834+ - name: python_requires_check
835+ applies_to:
836+ - python
837+ rules:
838+ - python_requires\\ s*=\\ s*['\" ]>={version}['\" ]
839+ - name: protobuf_check
840+ applies_to:
841+ - protobuf
842+ rules:
843+ - protobuf=={version}
844+ """ )
845+
846+ from version_scanner import ConfigManager , scan_repository
847+
848+ targets = [("python" , "3.7" ), ("protobuf" , "4.25.8" )]
849+ rules = []
850+ for dep , ver in targets :
851+ cm = ConfigManager (str (config_file ), dep , ver )
852+ rules .extend (cm .load_config ())
853+
854+ results = scan_repository (str (tmp_path ), rules , targets = targets )
855+
856+ # We should have 2 matches
857+ assert len (results ) == 2
858+
859+ # Match for python
860+ python_match = [r for r in results if r ["dependency" ] == "python" ]
861+ assert len (python_match ) == 1
862+ assert python_match [0 ]["version" ] == "3.7"
863+ assert python_match [0 ]["rule_name" ] == "python_requires_check"
864+
865+ # Match for protobuf
866+ protobuf_match = [r for r in results if r ["dependency" ] == "protobuf" ]
867+ assert len (protobuf_match ) == 1
868+ assert protobuf_match [0 ]["version" ] == "4.25.8"
869+ assert protobuf_match [0 ]["rule_name" ] == "protobuf_check"
870+
0 commit comments