8
8
from __future__ import annotations
9
9
10
10
import json
11
- import tempfile
12
- from pathlib import Path
13
- from typing import Any , Callable
11
+ import pathlib
12
+ import typing as t
14
13
15
14
import hypothesis .strategies as st
16
15
import yaml
17
- from hypothesis import given , settings
16
+ from hypothesis import HealthCheck , given , settings
18
17
19
18
from vcspull .config .loader import load_config , resolve_includes , save_config
20
19
from vcspull .config .models import Repository , Settings , VCSPullConfig
21
20
22
21
23
22
# Reuse strategies from test_models_property.py
24
23
@st .composite
25
- def valid_url_strategy (draw : Callable [[st .SearchStrategy [Any ]], Any ]) -> str :
24
+ def valid_url_strategy (draw : t . Callable [[st .SearchStrategy [t . Any ]], t . Any ]) -> str :
26
25
"""Generate valid URLs for repositories."""
27
26
protocols = ["https://" , "http://" , "git://" , "ssh://git@" ]
28
27
domains = ["github.com" , "gitlab.com" , "bitbucket.org" , "example.com" ]
@@ -53,7 +52,7 @@ def valid_url_strategy(draw: Callable[[st.SearchStrategy[Any]], Any]) -> str:
53
52
54
53
55
54
@st .composite
56
- def valid_path_strategy (draw : Callable [[st .SearchStrategy [Any ]], Any ]) -> str :
55
+ def valid_path_strategy (draw : t . Callable [[st .SearchStrategy [t . Any ]], t . Any ]) -> str :
57
56
"""Generate valid paths for repositories."""
58
57
base_dirs = ["~/code" , "~/projects" , "/tmp" , "./projects" ]
59
58
sub_dirs = [
@@ -78,7 +77,9 @@ def valid_path_strategy(draw: Callable[[st.SearchStrategy[Any]], Any]) -> str:
78
77
79
78
80
79
@st .composite
81
- def repository_strategy (draw : Callable [[st .SearchStrategy [Any ]], Any ]) -> Repository :
80
+ def repository_strategy (
81
+ draw : t .Callable [[st .SearchStrategy [t .Any ]], t .Any ],
82
+ ) -> Repository :
82
83
"""Generate valid Repository instances."""
83
84
name = draw (st .one_of (st .none (), st .text (min_size = 1 , max_size = 20 )))
84
85
url = draw (valid_url_strategy ())
@@ -130,7 +131,7 @@ def repository_strategy(draw: Callable[[st.SearchStrategy[Any]], Any]) -> Reposi
130
131
131
132
132
133
@st .composite
133
- def settings_strategy (draw : Callable [[st .SearchStrategy [Any ]], Any ]) -> Settings :
134
+ def settings_strategy (draw : t . Callable [[st .SearchStrategy [t . Any ]], t . Any ]) -> Settings :
134
135
"""Generate valid Settings instances."""
135
136
sync_remotes = draw (st .booleans ())
136
137
default_vcs = draw (st .one_of (st .none (), st .sampled_from (["git" , "hg" , "svn" ])))
@@ -145,14 +146,14 @@ def settings_strategy(draw: Callable[[st.SearchStrategy[Any]], Any]) -> Settings
145
146
146
147
@st .composite
147
148
def vcspull_config_strategy (
148
- draw : Callable [[st .SearchStrategy [Any ]], Any ],
149
+ draw : t . Callable [[st .SearchStrategy [t . Any ]], t . Any ],
149
150
with_includes : bool = False ,
150
151
) -> VCSPullConfig :
151
152
"""Generate valid VCSPullConfig instances.
152
153
153
154
Parameters
154
155
----------
155
- draw : Callable
156
+ draw : t. Callable
156
157
Hypothesis draw function
157
158
with_includes : bool, optional
158
159
Whether to add include files to the config, by default False
@@ -181,192 +182,171 @@ def vcspull_config_strategy(
181
182
)
182
183
183
184
184
- # Helper function to save a config to a temporary file
185
- def save_temp_config (config : VCSPullConfig , suffix : str = ".yaml" ) -> Path :
186
- """Save a config to a temporary file.
187
-
188
- Parameters
189
- ----------
190
- config : VCSPullConfig
191
- Configuration to save
192
- suffix : str, optional
193
- File suffix, by default ".yaml"
194
-
195
- Returns
196
- -------
197
- Path
198
- Path to the saved file
199
- """
200
- with tempfile .NamedTemporaryFile (suffix = suffix , delete = False ) as f :
201
- temp_path = Path (f .name )
202
-
203
- # Save the config to the temporary file
204
- format_type = "yaml" if suffix in (".yaml" , ".yml" ) else "json"
205
- save_config (config , temp_path , format_type = format_type )
206
-
207
- return temp_path
208
-
209
-
210
185
class TestConfigLoaderProperties :
211
186
"""Property-based tests for configuration loading."""
212
187
213
188
@given (config = vcspull_config_strategy ())
214
189
@settings (
215
- max_examples = 10
216
- ) # Limit the number of examples to avoid too many temp files
217
- def test_load_save_roundtrip (self , config : VCSPullConfig ) -> None :
190
+ max_examples = 10 , # Limit examples to avoid too many temp files
191
+ suppress_health_check = [HealthCheck .function_scoped_fixture ],
192
+ )
193
+ def test_load_save_roundtrip (
194
+ self , config : VCSPullConfig , tmp_path : pathlib .Path
195
+ ) -> None :
218
196
"""Test that saving and loading a configuration preserves its content."""
219
197
# Save the config to a temporary YAML file
220
- yaml_path = save_temp_config (config , suffix = ".yaml" )
221
- try :
222
- # Load the config back
223
- loaded_config = load_config (yaml_path )
224
-
225
- # Check that loaded config matches original
226
- assert loaded_config .settings .model_dump () == config .settings .model_dump ()
227
- assert len (loaded_config .repositories ) == len (config .repositories )
228
- for i , repo in enumerate (config .repositories ):
229
- assert loaded_config .repositories [i ].url == repo .url
230
- assert loaded_config .repositories [i ].path == repo .path
231
-
232
- # Also test with JSON format
233
- json_path = save_temp_config (config , suffix = ".json" )
234
- try :
235
- json_loaded_config = load_config (json_path )
236
-
237
- # Check that JSON loaded config matches original
238
- assert (
239
- json_loaded_config .settings .model_dump ()
240
- == config .settings .model_dump ()
241
- )
242
- assert len (json_loaded_config .repositories ) == len (config .repositories )
243
- finally :
244
- # Cleanup JSON temp file
245
- json_path .unlink (missing_ok = True )
198
+ yaml_path = tmp_path / "config.yaml"
199
+ save_config (config , yaml_path , format_type = "yaml" )
246
200
247
- finally :
248
- # Cleanup YAML temp file
249
- yaml_path .unlink (missing_ok = True )
201
+ # Load the config back
202
+ loaded_config = load_config (yaml_path )
203
+
204
+ # Check that loaded config matches original
205
+ assert loaded_config .settings .model_dump () == config .settings .model_dump ()
206
+ assert len (loaded_config .repositories ) == len (config .repositories )
207
+ for i , repo in enumerate (config .repositories ):
208
+ assert loaded_config .repositories [i ].url == repo .url
209
+ assert loaded_config .repositories [i ].path == repo .path
210
+
211
+ # Also test with JSON format
212
+ json_path = tmp_path / "config.json"
213
+ save_config (config , json_path , format_type = "json" )
214
+
215
+ # Load JSON config
216
+ json_loaded_config = load_config (json_path )
217
+
218
+ # Check that JSON loaded config matches original
219
+ assert json_loaded_config .settings .model_dump () == config .settings .model_dump ()
220
+ assert len (json_loaded_config .repositories ) == len (config .repositories )
250
221
251
222
@given (
252
223
main_config = vcspull_config_strategy (with_includes = True ),
253
224
included_configs = st .lists (vcspull_config_strategy (), min_size = 1 , max_size = 3 ),
254
225
)
255
- @settings (max_examples = 10 ) # Limit the number of examples
226
+ @settings (
227
+ max_examples = 10 , # Limit the number of examples
228
+ suppress_health_check = [HealthCheck .function_scoped_fixture ],
229
+ )
256
230
def test_include_resolution (
257
- self , main_config : VCSPullConfig , included_configs : list [VCSPullConfig ]
231
+ self ,
232
+ main_config : VCSPullConfig ,
233
+ included_configs : list [VCSPullConfig ],
234
+ tmp_path : pathlib .Path ,
258
235
) -> None :
259
236
"""Test that include resolution properly merges configurations."""
260
- with tempfile .TemporaryDirectory () as temp_dir :
261
- temp_dir_path = Path (temp_dir )
262
-
263
- # Create and save included configs
264
- included_paths = []
265
- for i , include_config in enumerate (included_configs ):
266
- include_path = temp_dir_path / f"include{ i } .yaml"
267
- save_config (include_config , include_path )
268
- included_paths .append (include_path )
237
+ # Create and save included configs
238
+ included_paths = []
239
+ for i , include_config in enumerate (included_configs ):
240
+ include_path = tmp_path / f"include{ i } .yaml"
241
+ save_config (include_config , include_path )
242
+ included_paths .append (include_path )
269
243
270
- # Update main config's includes to point to the actual files
271
- main_config .includes = [str (path ) for path in included_paths ]
244
+ # Update main config's includes to point to the actual files
245
+ main_config .includes = [str (path ) for path in included_paths ]
272
246
273
- # Save main config
274
- main_path = temp_dir_path / "main.yaml"
275
- save_config (main_config , main_path )
247
+ # Save main config
248
+ main_path = tmp_path / "main.yaml"
249
+ save_config (main_config , main_path )
276
250
277
- # Load and resolve includes
278
- loaded_config = load_config (main_path )
279
- resolved_config = resolve_includes (loaded_config , main_path .parent )
251
+ # Load and resolve includes
252
+ loaded_config = load_config (main_path )
253
+ resolved_config = resolve_includes (loaded_config , main_path .parent )
280
254
281
- # Verify all repositories are present in the resolved config
282
- all_repos = list (main_config .repositories )
283
- for include_config in included_configs :
284
- all_repos .extend (include_config .repositories )
255
+ # Verify all repositories are present in the resolved config
256
+ all_repos = list (main_config .repositories )
257
+ for include_config in included_configs :
258
+ all_repos .extend (include_config .repositories )
285
259
286
- # Check that all repositories are present in the resolved config
287
- assert len (resolved_config .repositories ) == len (all_repos )
260
+ # Check that all repositories are present in the resolved config
261
+ assert len (resolved_config .repositories ) == len (all_repos )
288
262
289
- # Check that includes are cleared
290
- assert len (resolved_config .includes ) == 0
263
+ # Check that includes are cleared
264
+ assert len (resolved_config .includes ) == 0
291
265
292
- # Verify URLs of repositories match (as a basic check)
293
- resolved_urls = {repo .url for repo in resolved_config .repositories }
294
- original_urls = {repo .url for repo in all_repos }
295
- assert resolved_urls == original_urls
266
+ # Verify URLs of repositories match (as a basic check)
267
+ resolved_urls = {repo .url for repo in resolved_config .repositories }
268
+ original_urls = {repo .url for repo in all_repos }
269
+ assert resolved_urls == original_urls
296
270
297
271
@given (configs = st .lists (vcspull_config_strategy (), min_size = 2 , max_size = 4 ))
298
- @settings (max_examples = 10 )
299
- def test_nested_includes_resolution (self , configs : list [VCSPullConfig ]) -> None :
272
+ @settings (
273
+ max_examples = 10 ,
274
+ suppress_health_check = [HealthCheck .function_scoped_fixture ],
275
+ )
276
+ def test_nested_includes_resolution (
277
+ self ,
278
+ configs : list [VCSPullConfig ],
279
+ tmp_path : pathlib .Path ,
280
+ ) -> None :
300
281
"""Test that nested includes are resolved properly."""
301
- with tempfile .TemporaryDirectory () as temp_dir :
302
- temp_dir_path = Path (temp_dir )
303
-
304
- # Save configs with nested includes
305
- # Last config has no includes
306
- paths = []
307
- for i , config in enumerate (configs ):
308
- config_path = temp_dir_path / f"config{ i } .yaml"
282
+ # Save configs with nested includes
283
+ # Last config has no includes
284
+ paths = []
285
+ for i , config in enumerate (configs ):
286
+ config_path = tmp_path / f"config{ i } .yaml"
309
287
310
- # Add includes to each config (except the last one)
311
- if i < len (configs ) - 1 :
312
- config .includes = [f"config{ i + 1 } .yaml" ]
313
- else :
314
- config .includes = []
288
+ # Add includes to each config (except the last one)
289
+ if i < len (configs ) - 1 :
290
+ config .includes = [f"config{ i + 1 } .yaml" ]
291
+ else :
292
+ config .includes = []
315
293
316
- save_config (config , config_path )
317
- paths .append (config_path )
294
+ save_config (config , config_path )
295
+ paths .append (config_path )
318
296
319
- # Load and resolve includes for the first config
320
- first_config = load_config (paths [0 ])
321
- resolved_config = resolve_includes (first_config , temp_dir_path )
297
+ # Load and resolve includes for the first config
298
+ first_config = load_config (paths [0 ])
299
+ resolved_config = resolve_includes (first_config , tmp_path )
322
300
323
- # Gather all repositories from original configs
324
- all_repos = []
325
- for config in configs :
326
- all_repos .extend (config .repositories )
301
+ # Gather all repositories from original configs
302
+ all_repos = []
303
+ for config in configs :
304
+ all_repos .extend (config .repositories )
327
305
328
- # Check repository count
329
- assert len (resolved_config .repositories ) == len (all_repos )
306
+ # Check repository count
307
+ assert len (resolved_config .repositories ) == len (all_repos )
330
308
331
- # Check all repositories are included
332
- resolved_urls = {repo .url for repo in resolved_config .repositories }
333
- original_urls = {repo .url for repo in all_repos }
334
- assert resolved_urls == original_urls
309
+ # Check all repositories are included
310
+ resolved_urls = {repo .url for repo in resolved_config .repositories }
311
+ original_urls = {repo .url for repo in all_repos }
312
+ assert resolved_urls == original_urls
335
313
336
- # Check no includes remain
337
- assert len (resolved_config .includes ) == 0
314
+ # Check no includes remain
315
+ assert len (resolved_config .includes ) == 0
338
316
339
317
@given (config = vcspull_config_strategy ())
340
- @settings (max_examples = 10 )
341
- def test_save_config_formats (self , config : VCSPullConfig ) -> None :
318
+ @settings (
319
+ max_examples = 10 ,
320
+ suppress_health_check = [HealthCheck .function_scoped_fixture ],
321
+ )
322
+ def test_save_config_formats (
323
+ self , config : VCSPullConfig , tmp_path : pathlib .Path
324
+ ) -> None :
342
325
"""Test that configs can be saved in different formats."""
343
- with tempfile .TemporaryDirectory () as temp_dir :
344
- temp_dir_path = Path (temp_dir )
345
-
346
- # Save in YAML format
347
- yaml_path = temp_dir_path / "config.yaml"
348
- saved_yaml_path = save_config (config , yaml_path , format_type = "yaml" )
349
- assert saved_yaml_path .exists ()
350
-
351
- # Verify YAML file is valid
352
- with saved_yaml_path .open () as f :
353
- yaml_content = yaml .safe_load (f )
354
- assert isinstance (yaml_content , dict )
355
-
356
- # Save in JSON format
357
- json_path = temp_dir_path / "config.json"
358
- saved_json_path = save_config (config , json_path , format_type = "json" )
359
- assert saved_json_path .exists ()
360
-
361
- # Verify JSON file is valid
362
- with saved_json_path .open () as f :
363
- json_content = json .load (f )
364
- assert isinstance (json_content , dict )
365
-
366
- # Load both formats and compare
367
- yaml_config = load_config (saved_yaml_path )
368
- json_config = load_config (saved_json_path )
369
-
370
- # Check that both loaded configs match the original
371
- assert yaml_config .model_dump () == config .model_dump ()
372
- assert json_config .model_dump () == config .model_dump ()
326
+ # Save in YAML format
327
+ yaml_path = tmp_path / "config.yaml"
328
+ saved_yaml_path = save_config (config , yaml_path , format_type = "yaml" )
329
+ assert saved_yaml_path .exists ()
330
+
331
+ # Verify YAML file is valid
332
+ with saved_yaml_path .open () as f :
333
+ yaml_content = yaml .safe_load (f )
334
+ assert isinstance (yaml_content , dict )
335
+
336
+ # Save in JSON format
337
+ json_path = tmp_path / "config.json"
338
+ saved_json_path = save_config (config , json_path , format_type = "json" )
339
+ assert saved_json_path .exists ()
340
+
341
+ # Verify JSON file is valid
342
+ with saved_json_path .open () as f :
343
+ json_content = json .load (f )
344
+ assert isinstance (json_content , dict )
345
+
346
+ # Load both formats and compare
347
+ yaml_config = load_config (saved_yaml_path )
348
+ json_config = load_config (saved_json_path )
349
+
350
+ # Check that both loaded configs match the original
351
+ assert yaml_config .model_dump () == config .model_dump ()
352
+ assert json_config .model_dump () == config .model_dump ()
0 commit comments