main
py 198 lines 6.11 KB
Raw
1 # SPDX-License-Identifier: Apache-2.0
2 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
4
5 import tempfile
6 from pathlib import Path
7 from unittest.mock import MagicMock, call, patch
8
9 import pytest
10 from huggingface_hub import _CACHED_NO_EXIST
11
12 from vllm.transformers_utils.repo_utils import (
13 any_pattern_in_repo_files,
14 get_hf_file_to_dict,
15 is_mistral_model_repo,
16 list_filtered_repo_files,
17 with_retry,
18 )
19
20
21 @pytest.mark.parametrize(
22 "allow_patterns,expected_relative_files",
23 [
24 (
25 ["*.json", "correct*.txt"],
26 ["json_file.json", "subfolder/correct.txt", "correct_2.txt"],
27 ),
28 ],
29 )
30 def test_list_filtered_repo_files(
31 allow_patterns: list[str], expected_relative_files: list[str]
32 ):
33 with tempfile.TemporaryDirectory() as tmp_dir:
34 # Prep folder and files
35 path_tmp_dir = Path(tmp_dir)
36 subfolder = path_tmp_dir / "subfolder"
37 subfolder.mkdir()
38 (path_tmp_dir / "json_file.json").touch()
39 (path_tmp_dir / "correct_2.txt").touch()
40 (path_tmp_dir / "incorrect.txt").touch()
41 (path_tmp_dir / "incorrect.jpeg").touch()
42 (subfolder / "correct.txt").touch()
43 (subfolder / "incorrect_sub.txt").touch()
44
45 def _glob_path() -> list[str]:
46 return [
47 str(file.relative_to(path_tmp_dir))
48 for file in path_tmp_dir.glob("**/*")
49 if file.is_file()
50 ]
51
52 # Patch list_repo_files called by fn
53 with patch(
54 "vllm.transformers_utils.repo_utils.list_repo_files",
55 MagicMock(return_value=_glob_path()),
56 ) as mock_list_repo_files:
57 out_files = sorted(
58 list_filtered_repo_files(
59 tmp_dir, allow_patterns, "revision", "model", "token"
60 )
61 )
62 assert out_files == sorted(expected_relative_files)
63 assert mock_list_repo_files.call_count == 1
64 assert mock_list_repo_files.call_args_list[0] == call(
65 repo_id=tmp_dir,
66 revision="revision",
67 repo_type="model",
68 token="token",
69 )
70
71
72 @pytest.mark.parametrize(
73 ("allow_patterns", "expected_bool"),
74 [
75 (["*.json", "correct*.txt"], True),
76 (
77 ["*.jpeg"],
78 True,
79 ),
80 (
81 ["not_found.jpeg"],
82 False,
83 ),
84 ],
85 )
86 def test_one_filtered_repo_files(allow_patterns: list[str], expected_bool: bool):
87 with tempfile.TemporaryDirectory() as tmp_dir:
88 # Prep folder and files
89 path_tmp_dir = Path(tmp_dir)
90 subfolder = path_tmp_dir / "subfolder"
91 subfolder.mkdir()
92 (path_tmp_dir / "incorrect.jpeg").touch()
93 (subfolder / "correct.txt").touch()
94
95 def _glob_path() -> list[str]:
96 return [
97 str(file.relative_to(path_tmp_dir))
98 for file in path_tmp_dir.glob("**/*")
99 if file.is_file()
100 ]
101
102 # Patch list_repo_files called by fn
103 with patch(
104 "vllm.transformers_utils.repo_utils.list_repo_files",
105 MagicMock(return_value=_glob_path()),
106 ) as mock_list_repo_files:
107 assert (
108 any_pattern_in_repo_files(
109 tmp_dir, allow_patterns, "revision", "model", "token"
110 )
111 ) is expected_bool
112 assert mock_list_repo_files.call_count == 1
113 assert mock_list_repo_files.call_args_list[0] == call(
114 repo_id=tmp_dir,
115 revision="revision",
116 repo_type="model",
117 token="token",
118 )
119
120
121 @pytest.mark.parametrize(
122 ("cache_result", "should_download"),
123 [
124 # HF Hub recorded a prior 404: don't re-probe the Hub.
125 (_CACHED_NO_EXIST, False),
126 # File not in cache and existence unknown: preserve download behavior.
127 (None, True),
128 ],
129 )
130 def test_get_hf_file_to_dict_honors_no_exist_marker(
131 cache_result: object, should_download: bool
132 ):
133 with (
134 patch(
135 "vllm.transformers_utils.repo_utils.try_to_load_from_cache",
136 MagicMock(return_value=cache_result),
137 ),
138 patch(
139 "vllm.transformers_utils.repo_utils._try_download_from_hf_hub",
140 MagicMock(return_value=None),
141 ) as mock_download,
142 ):
143 result = get_hf_file_to_dict("processor_config.json", "some/repo")
144 assert result is None
145 assert mock_download.call_count == int(should_download)
146
147
148 @pytest.mark.parametrize(
149 ("files", "expected_bool"),
150 [
151 (["consolidated.safetensors", "incorrect.txt"], True),
152 (["consolidated-1.safetensors", "incorrect.txt"], True),
153 (
154 ["consolidated-1.json"],
155 False,
156 ),
157 ],
158 )
159 def test_is_mistral_model_repo(files: list[str], expected_bool: bool):
160 with tempfile.TemporaryDirectory() as tmp_dir:
161 # Prep folder and files
162 path_tmp_dir = Path(tmp_dir)
163 for file in files:
164 (path_tmp_dir / file).touch()
165
166 def _glob_path() -> list[str]:
167 return [
168 str(file.relative_to(path_tmp_dir))
169 for file in path_tmp_dir.glob("**/*")
170 if file.is_file()
171 ]
172
173 # Patch list_repo_files called by fn
174 with patch(
175 "vllm.transformers_utils.repo_utils.list_repo_files",
176 MagicMock(return_value=_glob_path()),
177 ) as mock_list_repo_files:
178 assert (
179 is_mistral_model_repo(tmp_dir, "revision", "model", "token")
180 is expected_bool
181 )
182 assert mock_list_repo_files.call_count == 1
183 assert mock_list_repo_files.call_args_list[0] == call(
184 repo_id=tmp_dir,
185 revision="revision",
186 repo_type="model",
187 token="token",
188 )
189
190
191 def test_with_retry_does_not_retry_fatal_errors():
192 """A definitive answer must not be retried, which costs a delay per attempt."""
193 func = MagicMock(side_effect=FileNotFoundError("no such file"))
194
195 with pytest.raises(FileNotFoundError):
196 with_retry(func, "Error", fatal_errors=(FileNotFoundError,))
197
198 func.assert_called_once()