@@ -100,3 +100,232 @@ fn get_version(folder_name: &str) -> Option<String> {
100100 }
101101 }
102102}
103+
104+ #[ cfg( test) ]
105+ mod tests {
106+ use super :: * ;
107+ use std:: fs;
108+ use std:: path:: PathBuf ;
109+ use tempfile:: tempdir;
110+
111+ // get_version tests
112+ #[ test]
113+ fn get_version_parses_stable_version ( ) {
114+ assert_eq ! ( get_version( "3.10.10" ) , Some ( "3.10.10" . to_string( ) ) ) ;
115+ assert_eq ! ( get_version( "3.12.0" ) , Some ( "3.12.0" . to_string( ) ) ) ;
116+ assert_eq ! ( get_version( "2.7.18" ) , Some ( "2.7.18" . to_string( ) ) ) ;
117+ }
118+
119+ #[ test]
120+ fn get_version_parses_dev_version ( ) {
121+ assert_eq ! ( get_version( "3.10-dev" ) , Some ( "3.10-dev" . to_string( ) ) ) ;
122+ assert_eq ! ( get_version( "3.13-dev" ) , Some ( "3.13-dev" . to_string( ) ) ) ;
123+ }
124+
125+ #[ test]
126+ fn get_version_parses_alpha_rc_version ( ) {
127+ assert_eq ! ( get_version( "3.10.0a3" ) , Some ( "3.10.0a3" . to_string( ) ) ) ;
128+ assert_eq ! ( get_version( "3.12.0b1" ) , Some ( "3.12.0b1" . to_string( ) ) ) ;
129+ }
130+
131+ #[ test]
132+ fn get_version_returns_none_for_multi_letter_prerelease ( ) {
133+ // Known limitation: BETA_PYTHON_VERSION regex uses \w (single char) so multi-letter
134+ // pre-release tags like "rc" are not captured. Real pyenv installs can have rc versions
135+ // (e.g. 3.13.0rc1), but version detection falls back to header files in that case.
136+ assert_eq ! ( get_version( "3.11.0rc2" ) , None ) ;
137+ }
138+
139+ #[ test]
140+ fn get_version_parses_win32_version ( ) {
141+ assert_eq ! ( get_version( "3.11.0a4-win32" ) , Some ( "3.11.0a4" . to_string( ) ) ) ;
142+ }
143+
144+ #[ test]
145+ fn get_version_returns_none_for_non_version_strings ( ) {
146+ assert_eq ! ( get_version( "mambaforge-4.10.1-4" ) , None ) ;
147+ assert_eq ! ( get_version( "pypy3.9-7.3.15" ) , None ) ;
148+ assert_eq ! ( get_version( "my-virtual-env" ) , None ) ;
149+ assert_eq ! ( get_version( "" ) , None ) ;
150+ }
151+
152+ #[ test]
153+ fn get_version_returns_none_for_partial_version ( ) {
154+ assert_eq ! ( get_version( "3.10" ) , None ) ;
155+ }
156+
157+ // get_generic_python_environment tests
158+ #[ test]
159+ fn get_generic_python_environment_with_stable_version_folder ( ) {
160+ let root = tempdir ( ) . unwrap ( ) ;
161+ let env_path = root. path ( ) . join ( "3.12.0" ) ;
162+ let bin_dir = if cfg ! ( windows) {
163+ env_path. join ( "Scripts" )
164+ } else {
165+ env_path. join ( "bin" )
166+ } ;
167+ fs:: create_dir_all ( & bin_dir) . unwrap ( ) ;
168+ let exe = if cfg ! ( windows) {
169+ bin_dir. join ( "python.exe" )
170+ } else {
171+ bin_dir. join ( "python" )
172+ } ;
173+ fs:: write ( & exe, b"" ) . unwrap ( ) ;
174+
175+ let result = get_generic_python_environment ( & exe, & env_path, & None ) . unwrap ( ) ;
176+
177+ assert_eq ! ( result. kind, Some ( PythonEnvironmentKind :: Pyenv ) ) ;
178+ assert_eq ! ( result. executable, Some ( exe) ) ;
179+ assert_eq ! ( result. version, Some ( "3.12.0" . to_string( ) ) ) ;
180+ assert_eq ! ( result. prefix, Some ( env_path) ) ;
181+ assert ! ( result. manager. is_none( ) ) ;
182+ }
183+
184+ #[ test]
185+ fn get_generic_python_environment_with_win32_folder_sets_x86_arch ( ) {
186+ let root = tempdir ( ) . unwrap ( ) ;
187+ let env_path = root. path ( ) . join ( "3.11.0a4-win32" ) ;
188+ let bin_dir = if cfg ! ( windows) {
189+ env_path. join ( "Scripts" )
190+ } else {
191+ env_path. join ( "bin" )
192+ } ;
193+ fs:: create_dir_all ( & bin_dir) . unwrap ( ) ;
194+ let exe = if cfg ! ( windows) {
195+ bin_dir. join ( "python.exe" )
196+ } else {
197+ bin_dir. join ( "python" )
198+ } ;
199+ fs:: write ( & exe, b"" ) . unwrap ( ) ;
200+
201+ let result = get_generic_python_environment ( & exe, & env_path, & None ) . unwrap ( ) ;
202+
203+ assert_eq ! ( result. arch, Some ( Architecture :: X86 ) ) ;
204+ }
205+
206+ #[ test]
207+ fn get_generic_python_environment_with_non_win32_folder_has_no_arch ( ) {
208+ let root = tempdir ( ) . unwrap ( ) ;
209+ let env_path = root. path ( ) . join ( "3.12.0" ) ;
210+ let bin_dir = if cfg ! ( windows) {
211+ env_path. join ( "Scripts" )
212+ } else {
213+ env_path. join ( "bin" )
214+ } ;
215+ fs:: create_dir_all ( & bin_dir) . unwrap ( ) ;
216+ let exe = if cfg ! ( windows) {
217+ bin_dir. join ( "python.exe" )
218+ } else {
219+ bin_dir. join ( "python" )
220+ } ;
221+ fs:: write ( & exe, b"" ) . unwrap ( ) ;
222+
223+ let result = get_generic_python_environment ( & exe, & env_path, & None ) . unwrap ( ) ;
224+
225+ assert ! ( result. arch. is_none( ) ) ;
226+ }
227+
228+ #[ test]
229+ fn get_generic_python_environment_includes_manager_when_provided ( ) {
230+ let root = tempdir ( ) . unwrap ( ) ;
231+ let env_path = root. path ( ) . join ( "3.12.0" ) ;
232+ let bin_dir = if cfg ! ( windows) {
233+ env_path. join ( "Scripts" )
234+ } else {
235+ env_path. join ( "bin" )
236+ } ;
237+ fs:: create_dir_all ( & bin_dir) . unwrap ( ) ;
238+ let exe = if cfg ! ( windows) {
239+ bin_dir. join ( "python.exe" )
240+ } else {
241+ bin_dir. join ( "python" )
242+ } ;
243+ fs:: write ( & exe, b"" ) . unwrap ( ) ;
244+
245+ let mgr = EnvManager :: new (
246+ PathBuf :: from ( "/usr/bin/pyenv" ) ,
247+ pet_core:: manager:: EnvManagerType :: Pyenv ,
248+ Some ( "2.4.0" . to_string ( ) ) ,
249+ ) ;
250+ let result = get_generic_python_environment ( & exe, & env_path, & Some ( mgr. clone ( ) ) ) . unwrap ( ) ;
251+
252+ assert_eq ! ( result. manager, Some ( mgr) ) ;
253+ }
254+
255+ #[ test]
256+ fn get_generic_python_environment_with_unrecognized_folder_name ( ) {
257+ let root = tempdir ( ) . unwrap ( ) ;
258+ let env_path = root. path ( ) . join ( "mambaforge-4.10.1-4" ) ;
259+ let bin_dir = if cfg ! ( windows) {
260+ env_path. join ( "Scripts" )
261+ } else {
262+ env_path. join ( "bin" )
263+ } ;
264+ fs:: create_dir_all ( & bin_dir) . unwrap ( ) ;
265+ let exe = if cfg ! ( windows) {
266+ bin_dir. join ( "python.exe" )
267+ } else {
268+ bin_dir. join ( "python" )
269+ } ;
270+ fs:: write ( & exe, b"" ) . unwrap ( ) ;
271+
272+ let result = get_generic_python_environment ( & exe, & env_path, & None ) . unwrap ( ) ;
273+
274+ assert_eq ! ( result. kind, Some ( PythonEnvironmentKind :: Pyenv ) ) ;
275+ // No version extractable from folder name and no header files
276+ assert ! ( result. version. is_none( ) ) ;
277+ }
278+
279+ // get_virtual_env_environment tests
280+ #[ test]
281+ fn get_virtual_env_returns_none_without_pyvenv_cfg ( ) {
282+ let root = tempdir ( ) . unwrap ( ) ;
283+ let env_path = root. path ( ) . join ( "my-venv" ) ;
284+ let bin_dir = if cfg ! ( windows) {
285+ env_path. join ( "Scripts" )
286+ } else {
287+ env_path. join ( "bin" )
288+ } ;
289+ fs:: create_dir_all ( & bin_dir) . unwrap ( ) ;
290+ let exe = if cfg ! ( windows) {
291+ bin_dir. join ( "python.exe" )
292+ } else {
293+ bin_dir. join ( "python" )
294+ } ;
295+ fs:: write ( & exe, b"" ) . unwrap ( ) ;
296+
297+ let result = get_virtual_env_environment ( & exe, & env_path, & None ) ;
298+
299+ assert ! ( result. is_none( ) ) ;
300+ }
301+
302+ #[ test]
303+ fn get_virtual_env_returns_env_with_pyvenv_cfg ( ) {
304+ let root = tempdir ( ) . unwrap ( ) ;
305+ let env_path = root. path ( ) . join ( "my-venv" ) ;
306+ let bin_dir = if cfg ! ( windows) {
307+ env_path. join ( "Scripts" )
308+ } else {
309+ env_path. join ( "bin" )
310+ } ;
311+ fs:: create_dir_all ( & bin_dir) . unwrap ( ) ;
312+ let exe = if cfg ! ( windows) {
313+ bin_dir. join ( "python.exe" )
314+ } else {
315+ bin_dir. join ( "python" )
316+ } ;
317+ fs:: write ( & exe, b"" ) . unwrap ( ) ;
318+ fs:: write (
319+ env_path. join ( "pyvenv.cfg" ) ,
320+ "version = 3.12.0\n home = /usr/bin\n " ,
321+ )
322+ . unwrap ( ) ;
323+
324+ let result = get_virtual_env_environment ( & exe, & env_path, & None ) . unwrap ( ) ;
325+
326+ assert_eq ! ( result. kind, Some ( PythonEnvironmentKind :: PyenvVirtualEnv ) ) ;
327+ assert_eq ! ( result. version, Some ( "3.12.0" . to_string( ) ) ) ;
328+ assert_eq ! ( result. executable, Some ( exe) ) ;
329+ assert_eq ! ( result. prefix, Some ( env_path) ) ;
330+ }
331+ }
0 commit comments