11# Copyright (C) 2024 twyleg
22# fmt: off
3+ import os
34import shutil
45import sys
5- from typing import List
6+ from collections import namedtuple
7+ from dataclasses import dataclass
8+ from typing import Dict , Any
9+
610
711import pygit2
812import pytest
13+ import yaml
914from _pytest .monkeypatch import MonkeyPatch
1015
1116from pathlib import Path
1621FILE_DIR = Path (__file__ ).parent
1722
1823
24+ @dataclass
25+ class TestProject :
26+ path : Path
27+ config : Dict [str , Any ]
28+ placeholder_target_pairs : Dict [str , str ]
29+
30+
1931def prepare_test_project_git_repo (test_project_path : Path ) -> None :
2032 git_file_from_submodule = test_project_path / ".git"
2133 git_file_from_submodule .unlink ()
@@ -26,140 +38,258 @@ def prepare_test_project_git_repo(test_project_path: Path) -> None:
2638 test_project_repo .remotes .create ("origin" , test_project_dummy_remote_url )
2739
2840
41+ def read_template_config (test_project_config_file_path : Path ) -> Dict [str , Any ]:
42+ with open (test_project_config_file_path , "r" ) as file :
43+ return yaml .safe_load (file )
44+
45+
2946def create_test_project_from_template_and_chdir (template_project_src_dir_path : Path ,
3047 template_project_dst_dir_path : Path ,
31- monkeypatch : MonkeyPatch ) -> Path :
48+ template_project_config_filename : str ,
49+ template_project_placeholder_target_pairs : Dict [str , str ],
50+ monkeypatch : MonkeyPatch ,
51+ ) -> TestProject :
3252 template_project_dst_dir_path = template_project_dst_dir_path / template_project_src_dir_path .name
3353 shutil .copytree (template_project_src_dir_path , template_project_dst_dir_path )
3454 monkeypatch .chdir (template_project_dst_dir_path )
3555 prepare_test_project_git_repo (template_project_dst_dir_path )
36- return template_project_dst_dir_path
56+ template_project_config = read_template_config (template_project_dst_dir_path / template_project_config_filename )
57+
58+ return TestProject (
59+ path = template_project_dst_dir_path ,
60+ config = template_project_config ,
61+ placeholder_target_pairs = template_project_placeholder_target_pairs
62+ )
3763
3864
3965@pytest .fixture
4066def template_project_cpp_master (tmp_path , monkeypatch ):
4167 return create_test_project_from_template_and_chdir (
42- FILE_DIR / "../external/template_project_cpp_master" ,
43- tmp_path ,
44- monkeypatch
68+ template_project_src_dir_path = FILE_DIR / "../external/template_project_cpp_master" ,
69+ template_project_dst_dir_path = tmp_path ,
70+ template_project_config_filename = "template_config.yaml" ,
71+ template_project_placeholder_target_pairs = {"template_project_cpp" : "test_target_name" },
72+ monkeypatch = monkeypatch
4573 )
4674
4775
4876@pytest .fixture
4977def template_project_cpp_usecase_qt_qml_app (tmp_path , monkeypatch ):
5078 return create_test_project_from_template_and_chdir (
51- FILE_DIR / "../external/template_project_cpp_usecase_qt_qml_app" ,
52- tmp_path ,
53- monkeypatch
79+ template_project_src_dir_path = FILE_DIR / "../external/template_project_cpp_usecase_qt_qml_app" ,
80+ template_project_dst_dir_path = tmp_path ,
81+ template_project_config_filename = "template_config.yaml" ,
82+ template_project_placeholder_target_pairs = {"template_project_cpp" : "test_target_name" },
83+ monkeypatch = monkeypatch
5484 )
5585
5686
5787@pytest .fixture
5888def template_project_kicad_master (tmp_path , monkeypatch ):
5989 return create_test_project_from_template_and_chdir (
60- FILE_DIR / "../external/template_project_kicad_master" ,
61- tmp_path ,
62- monkeypatch
90+ template_project_src_dir_path = FILE_DIR / "../external/template_project_kicad_master" ,
91+ template_project_dst_dir_path = tmp_path ,
92+ template_project_config_filename = "template_config.yaml" ,
93+ template_project_placeholder_target_pairs = {"template_project_kicad" : "test_target_name" },
94+ monkeypatch = monkeypatch
6395 )
6496
6597
6698@pytest .fixture
6799def template_project_python_master (tmp_path , monkeypatch ):
68100 return create_test_project_from_template_and_chdir (
69- FILE_DIR / "../external/template_project_python_master" ,
70- tmp_path ,
71- monkeypatch
101+ template_project_src_dir_path = FILE_DIR / "../external/template_project_python_master" ,
102+ template_project_dst_dir_path = tmp_path ,
103+ template_project_config_filename = "template_config.yaml" ,
104+ template_project_placeholder_target_pairs = {
105+ "template_project_python" : "test_target_name" ,
106+ "template-project-python" : "test-target-name" ,
107+ },
108+ monkeypatch = monkeypatch
72109 )
73110
74111
112+ @pytest .fixture
113+ def template_project_python_master_with_alternative_config_filename (template_project_python_master ):
114+ old_config_file_name = template_project_python_master .path / "template_config.yaml"
115+ new_config_file_name = template_project_python_master .path / "template_config_alt.yaml"
116+ os .rename (old_config_file_name , new_config_file_name )
117+ new_config_file_name .write_text (new_config_file_name .read_text ().replace ("- template_config.yaml" , "- template_config_alt.yaml" ))
118+ return template_project_python_master
119+
120+
75121@pytest .fixture
76122def template_project_python_usecase_qt_qml_app (tmp_path , monkeypatch ):
77123 return create_test_project_from_template_and_chdir (
78- FILE_DIR / "../external/template_project_python_usecase_qt_qml_app" ,
79- tmp_path ,
80- monkeypatch
124+ template_project_src_dir_path = FILE_DIR / "../external/template_project_python_usecase_qt_qml_app" ,
125+ template_project_dst_dir_path = tmp_path ,
126+ template_project_config_filename = "template_config.yaml" ,
127+ template_project_placeholder_target_pairs = {"template_project_python" : "test_target_name" },
128+ monkeypatch = monkeypatch
81129 )
82130
83131
84- def is_git_remote_origin_still_available ( test_project_path : Path ) -> bool :
85- repo = pygit2 .Repository (str (test_project_path ))
132+ def is_git_remote_origin_still_existing ( test_project : TestProject ) -> bool :
133+ repo = pygit2 .Repository (str (test_project . path ))
86134 remote_collection = pygit2 .remotes .RemoteCollection (repo )
87135 return "origin" in remote_collection .names ()
88136
89137
90- def is_any_placeholder_still_available ( test_project_path : Path , keywords : List [ str ] ):
138+ def is_any_placeholder_still_existing ( test_project : TestProject ):
91139 file_name_count = 0
92140 dir_name_count = 0
93141 file_content_count = 0
94142
95- for path in test_project_path .rglob ("*" ):
96- if path .is_relative_to (test_project_path / ".git/" ):
143+ for path in test_project . path .rglob ("*" ):
144+ if path .is_relative_to (test_project . path / ".git/" ):
97145 pass # Ignore
98- elif path .is_relative_to (test_project_path / "venv/" ):
146+ elif path .is_relative_to (test_project . path / "venv/" ):
99147 pass # Ignore
100- elif path .is_relative_to (test_project_path / "logs/" ):
148+ elif path .is_relative_to (test_project . path / "logs/" ):
101149 pass # Ignore
102150 elif path .is_dir ():
103- relative_path = str (path .relative_to (test_project_path ))
104- for keyword in keywords :
105- if keyword in relative_path :
151+ relative_path = str (path .relative_to (test_project . path ))
152+ for placeholder in test_project . placeholder_target_pairs . keys () :
153+ if placeholder in relative_path :
106154 dir_name_count += 1
107- print (f"Error: Keyword \" { keyword } \" found in directory name \" { relative_path } \" " , file = sys .stderr )
155+ print (f"Error: Placeholder \" { placeholder } \" found in directory name \" { relative_path } \" " , file = sys .stderr )
108156 elif path .is_file ():
109- relative_path = str (path .relative_to (test_project_path ))
110- for keyword in keywords :
111- if keyword in relative_path :
157+ relative_path = str (path .relative_to (test_project . path ))
158+ for placeholder in test_project . placeholder_target_pairs . keys () :
159+ if placeholder in relative_path :
112160 file_name_count += 1
113- print (f"Error: Keyword \" { keyword } \" found in file name \" { relative_path } \" " , file = sys .stderr )
161+ print (f"Error: Placeholder \" { placeholder } \" found in file name \" { relative_path } \" " , file = sys .stderr )
114162
115163 try :
116164 content = path .read_text ()
117- for keyword in keywords :
118- count = content .count (keyword )
165+ for placeholder in test_project . placeholder_target_pairs . keys () :
166+ count = content .count (placeholder )
119167 file_content_count += count
120168 if count :
121- print (f"Error: Keyword \" { keyword } \" found { count } times in file \" { path } \" " , file = sys .stderr )
169+ print (f"Error: Placeholder \" { placeholder } \" found { count } times in file \" { path } \" " , file = sys .stderr )
122170 except UnicodeDecodeError as e :
123171 pass
124172 return file_name_count > 0 or dir_name_count > 0 or file_content_count > 0
125173
126174
127- def is_project_correctly_initialized (test_project_path : Path , template_project_keywords : List [str ]) -> bool :
128- if is_any_placeholder_still_available (test_project_path , template_project_keywords ):
129- return False
130- elif is_git_remote_origin_still_available (test_project_path ):
131- return False
132- return True
175+ def is_any_file_diff_not_plausible (test_project : TestProject ) -> bool :
176+ if test_project .config ["update_files" ]:
177+ for update_file_name in test_project .config ["update_files" ]:
178+
179+ for placeholder , target in test_project .placeholder_target_pairs .items ():
180+ update_file_name = update_file_name .replace (placeholder , target )
181+
182+ update_file_path = test_project .path / update_file_name
183+ if update_file_path .stat ().st_size == 0 :
184+ return True
185+ return False
186+
187+
188+ def is_any_remove_file_candidate_still_existing (test_project : TestProject ) -> bool :
189+ if test_project .config ["remove_files" ]:
190+ for remove_file_name in test_project .config ["remove_files" ]:
191+ remove_file_path = test_project .path / remove_file_name
192+ if remove_file_path .exists ():
193+ return True
194+ return False
133195
134196
135- class TestInitializer :
197+ def is_any_remove_dir_candidate_still_existing (test_project : TestProject ) -> bool :
198+ if test_project .config ["remove_dirs" ]:
199+ for remove_dir_name in test_project .config ["remove_dirs" ]:
200+ remove_dir_path = test_project .path / remove_dir_name
201+ if remove_dir_path .exists ():
202+ return True
203+ return False
204+
205+
206+ def is_any_rename_file_candidate_still_existing (test_project : TestProject ) -> bool :
207+ if test_project .config ["rename_files" ]:
208+ for rename_file_name in test_project .config ["rename_files" ]:
209+ rename_file_path = test_project .path / rename_file_name
210+ if rename_file_path .exists ():
211+ return True
212+ return False
213+
214+
215+ def is_any_rename_dir_candidate_still_existing (test_project : TestProject ) -> bool :
216+ if test_project .config ["rename_dirs" ]:
217+ for rename_dir_name in test_project .config ["rename_dirs" ]:
218+ rename_dir_path = test_project .path / rename_dir_name
219+ if rename_dir_path .exists ():
220+ return True
221+ return False
222+
223+
224+ def assert_project_correctly_initialized (test_project : TestProject ) -> None :
225+ assert not is_any_placeholder_still_existing (test_project )
226+ assert not is_any_file_diff_not_plausible (test_project )
227+ assert not is_any_rename_file_candidate_still_existing (test_project )
228+ assert not is_any_rename_dir_candidate_still_existing (test_project )
229+ assert not is_any_remove_file_candidate_still_existing (test_project )
230+ assert not is_any_remove_dir_candidate_still_existing (test_project )
231+ assert not is_git_remote_origin_still_existing (test_project )
232+
233+
234+ class TestInitializerForAllTemplateTypes :
136235 def test_ValidTemplateProjectCppMaster_InitializeTemplate_InitializationSuccessful (self , template_project_cpp_master ):
137- template_initializer = TemplateInitializer (template_project_cpp_master / "template_config.yaml" )
236+ template_initializer = TemplateInitializer (template_project_cpp_master . path )
138237 template_initializer .init ({"template_project_cpp" : "test_target_name" })
139- assert is_project_correctly_initialized (template_project_cpp_master , [ "template_project_cpp" ] )
238+ assert_project_correctly_initialized (template_project_cpp_master )
140239
141240 def test_ValidTemplateProjectCppUsecaseQtQmlApp_InitializeTemplate_InitializationSuccessful (self , template_project_cpp_usecase_qt_qml_app ):
142- template_initializer = TemplateInitializer (template_project_cpp_usecase_qt_qml_app / "template_config.yaml" )
241+ template_initializer = TemplateInitializer (template_project_cpp_usecase_qt_qml_app . path )
143242 template_initializer .init ({"template_project_cpp" : "test_target_name" })
144- assert is_project_correctly_initialized (template_project_cpp_usecase_qt_qml_app , [ "template_project_cpp" ] )
243+ assert_project_correctly_initialized (template_project_cpp_usecase_qt_qml_app )
145244
146245 def test_ValidTemplateProjectKicadMaster_InitializeTemplate_InitializationSuccessful (self , template_project_kicad_master ):
147- template_initializer = TemplateInitializer (template_project_kicad_master / "template_config.yaml" )
246+ template_initializer = TemplateInitializer (template_project_kicad_master . path )
148247 template_initializer .init ({"template_project_kicad" : "test_target_name" })
149- assert is_project_correctly_initialized (template_project_kicad_master , [ "template_project_kicad" ] )
248+ assert_project_correctly_initialized (template_project_kicad_master )
150249
151250 def test_ValidTemplateProjectPythonMaster_InitializeTemplate_InitializationSuccessful (self , template_project_python_master ):
152- template_initializer = TemplateInitializer (template_project_python_master / "template_config.yaml" )
251+ template_initializer = TemplateInitializer (template_project_python_master . path )
153252 template_initializer .init ({
154253 "template_project_python" : "test_target_name" ,
155254 "template-project-python" : "test-target-name" ,
156255 })
157- assert is_project_correctly_initialized (template_project_python_master , [ "template_project_python" ] )
256+ assert_project_correctly_initialized (template_project_python_master )
158257
159258 def test_ValidTemplateProjectPythonUsecaseQtQmlApp_InitializeTemplate_InitializationSuccessful (self , template_project_python_usecase_qt_qml_app ):
160- template_initializer = TemplateInitializer (template_project_python_usecase_qt_qml_app / "template_config.yaml" )
259+ template_initializer = TemplateInitializer (template_project_python_usecase_qt_qml_app .path )
260+ template_initializer .init ({
261+ "template_project_python" : "test_target_name" ,
262+ "template-project-python" : "test-target-name" ,
263+ })
264+ assert_project_correctly_initialized (template_project_python_usecase_qt_qml_app )
265+
266+
267+ class TestInitializerDetails :
268+
269+ def test_ValidTemplateProjectPythonMasterWithCustomTemplateConfigFilename_InitializeTemplate_InitializationSuccessful (
270+ self ,
271+ template_project_python_master_with_alternative_config_filename
272+ ):
273+ template_initializer = TemplateInitializer (template_project_python_master_with_alternative_config_filename .path / "template_config_alt.yaml" )
161274 template_initializer .init ({
162275 "template_project_python" : "test_target_name" ,
163276 "template-project-python" : "test-target-name" ,
164277 })
165- assert is_project_correctly_initialized (template_project_python_usecase_qt_qml_app , ["template_project_python" ])
278+ assert_project_correctly_initialized (template_project_python_master_with_alternative_config_filename )
279+
280+ def test_ValidTemplateProjectPythonMasterWithInvalidConfigFileNameParameter_InitializeTemplate_RuntimeErrorRaised (
281+ self ,
282+ template_project_python_master
283+ ):
284+ with pytest .raises (RuntimeError ):
285+ TemplateInitializer (template_project_python_master .path / "template_config_not_existing.yaml" )
286+
287+ def test_ValidTemplateProjectPythonMasterWithInvalidWorkingDirParameter_InitializeTemplate_RuntimeErrorRaised (
288+ self ,
289+ template_project_python_master
290+ ):
291+ with pytest .raises (RuntimeError ):
292+ TemplateInitializer (
293+ config_file_or_dir_path = template_project_python_master .path / "template_config.yaml" ,
294+ working_dir_path = Path ("/not/existing/working/dir" )
295+ )
0 commit comments