77//! - Workspace directories are not configured
88//! - The pyproject.toml is not in the workspace directories
99//! - The environment is in the Poetry cache but wasn't enumerated
10+ //! - The environment is an in-project .venv with virtualenvs.in-project = true
1011//!
11- //! The fix adds a fallback path-based detection that checks if the environment
12- //! path matches Poetry's naming pattern ({name}-{8-char-hash}-py{version}) and
13- //! is located in a Poetry cache directory (containing "pypoetry/virtualenvs").
12+ //! The fix adds fallback path-based detection that checks:
13+ //! 1. If the environment path matches Poetry's cache naming pattern
14+ //! ({name}-{8-char-hash}-py{version}) in "pypoetry/virtualenvs"
15+ //! 2. If the environment is an in-project .venv with a pyproject.toml
16+ //! containing Poetry configuration
1417
18+ use std:: fs;
1519use std:: path:: PathBuf ;
1620
1721#[ cfg( test) ]
1822mod tests {
1923 use super :: * ;
2024
21- // Helper function to test the regex pattern matching
25+ // Helper function to test the regex pattern matching for cache environments
2226 // This tests the core logic without needing actual filesystem structures
23- fn test_poetry_path_pattern ( path_str : & str ) -> bool {
27+ fn test_poetry_cache_path_pattern ( path_str : & str ) -> bool {
2428 use regex:: Regex ;
2529 let path = PathBuf :: from ( path_str) ;
2630 let path_str = path. to_str ( ) . unwrap_or_default ( ) ;
@@ -34,70 +38,215 @@ mod tests {
3438 false
3539 }
3640
41+ // Helper function to test in-project poetry environment detection
42+ // Requires actual filesystem structure
43+ fn test_in_project_poetry_env ( path : & std:: path:: Path ) -> bool {
44+ // Check if this is a .venv directory
45+ let dir_name = path
46+ . file_name ( )
47+ . and_then ( |n| n. to_str ( ) )
48+ . unwrap_or_default ( ) ;
49+ if dir_name != ".venv" {
50+ return false ;
51+ }
52+
53+ // Check if the parent directory has a pyproject.toml with Poetry configuration
54+ if let Some ( parent) = path. parent ( ) {
55+ let pyproject_toml = parent. join ( "pyproject.toml" ) ;
56+ if pyproject_toml. is_file ( ) {
57+ if let Ok ( contents) = std:: fs:: read_to_string ( & pyproject_toml) {
58+ if contents. contains ( "[tool.poetry]" )
59+ || contents. contains ( "poetry.core.masonry.api" )
60+ || contents. contains ( "poetry-core" )
61+ {
62+ return true ;
63+ }
64+ }
65+ }
66+ }
67+ false
68+ }
69+
3770 #[ test]
3871 fn test_poetry_path_pattern_macos ( ) {
39- assert ! ( test_poetry_path_pattern (
72+ assert ! ( test_poetry_cache_path_pattern (
4073 "/Users/eleanorboyd/Library/Caches/pypoetry/virtualenvs/nestedpoetry-yJwtIF_Q-py3.11"
4174 ) ) ;
4275 }
4376
4477 #[ test]
4578 fn test_poetry_path_pattern_linux ( ) {
46- assert ! ( test_poetry_path_pattern (
79+ assert ! ( test_poetry_cache_path_pattern (
4780 "/home/user/.cache/pypoetry/virtualenvs/myproject-a1B2c3D4-py3.10"
4881 ) ) ;
4982 }
5083
5184 #[ test]
5285 fn test_poetry_path_pattern_windows ( ) {
53- assert ! ( test_poetry_path_pattern (
86+ assert ! ( test_poetry_cache_path_pattern (
5487 r"C:\Users\user\AppData\Local\pypoetry\Cache\virtualenvs\myproject-f7sQRtG5-py3.11"
5588 ) ) ;
5689 }
5790
5891 #[ test]
5992 fn test_poetry_path_pattern_no_version ( ) {
60- assert ! ( test_poetry_path_pattern (
93+ assert ! ( test_poetry_cache_path_pattern (
6194 "/home/user/.cache/pypoetry/virtualenvs/testproject-XyZ12345-py"
6295 ) ) ;
6396 }
6497
6598 #[ test]
6699 fn test_non_poetry_path_rejected ( ) {
67- assert ! ( !test_poetry_path_pattern ( "/home/user/projects/myenv" ) ) ;
68- assert ! ( !test_poetry_path_pattern ( "/home/user/.venv" ) ) ;
69- assert ! ( !test_poetry_path_pattern ( "/usr/local/venv" ) ) ;
100+ assert ! ( !test_poetry_cache_path_pattern ( "/home/user/projects/myenv" ) ) ;
101+ assert ! ( !test_poetry_cache_path_pattern ( "/home/user/.venv" ) ) ;
102+ assert ! ( !test_poetry_cache_path_pattern ( "/usr/local/venv" ) ) ;
70103 }
71104
72105 #[ test]
73106 fn test_poetry_path_without_pypoetry_rejected ( ) {
74107 // Should reject paths that look like the pattern but aren't in pypoetry directory
75- assert ! ( !test_poetry_path_pattern (
108+ assert ! ( !test_poetry_cache_path_pattern (
76109 "/home/user/virtualenvs/myproject-a1B2c3D4-py3.10"
77110 ) ) ;
78111 }
79112
80113 #[ test]
81114 fn test_poetry_path_wrong_hash_length_rejected ( ) {
82115 // Hash should be exactly 8 characters
83- assert ! ( !test_poetry_path_pattern (
116+ assert ! ( !test_poetry_cache_path_pattern (
84117 "/home/user/.cache/pypoetry/virtualenvs/myproject-a1B2c3D456-py3.10"
85118 ) ) ;
86- assert ! ( !test_poetry_path_pattern (
119+ assert ! ( !test_poetry_cache_path_pattern (
87120 "/home/user/.cache/pypoetry/virtualenvs/myproject-a1B2c3-py3.10"
88121 ) ) ;
89122 }
90123
91124 #[ test]
92125 fn test_real_world_poetry_paths ( ) {
93126 // Test actual Poetry paths from the bug report and real usage
94- assert ! ( test_poetry_path_pattern (
127+ assert ! ( test_poetry_cache_path_pattern (
95128 "/Users/eleanorboyd/Library/Caches/pypoetry/virtualenvs/nestedpoetry-yJwtIF_Q-py3.11"
96129 ) ) ;
97130
98131 // Another real-world example from documentation
99- assert ! ( test_poetry_path_pattern (
132+ assert ! ( test_poetry_cache_path_pattern (
100133 "/Users/donjayamanne/.cache/pypoetry/virtualenvs/poetry-demo-gNT2WXAV-py3.9"
101134 ) ) ;
102135 }
136+
137+ // Tests for in-project Poetry environment detection (issue #282)
138+
139+ #[ test]
140+ fn test_in_project_poetry_env_with_tool_poetry ( ) {
141+ let temp_dir = tempfile:: tempdir ( ) . unwrap ( ) ;
142+ let project_dir = temp_dir. path ( ) ;
143+ let venv_dir = project_dir. join ( ".venv" ) ;
144+
145+ // Create .venv directory
146+ fs:: create_dir ( & venv_dir) . unwrap ( ) ;
147+
148+ // Create pyproject.toml with [tool.poetry] section
149+ let pyproject_content = r#"
150+ [tool.poetry]
151+ name = "my-project"
152+ version = "0.1.0"
153+ description = ""
154+ authors = ["Test User <test@example.com>"]
155+
156+ [tool.poetry.dependencies]
157+ python = "^3.10"
158+
159+ [build-system]
160+ requires = ["poetry-core"]
161+ build-backend = "poetry.core.masonry.api"
162+ "# ;
163+ fs:: write ( project_dir. join ( "pyproject.toml" ) , pyproject_content) . unwrap ( ) ;
164+
165+ // Test that the .venv is recognized as a Poetry environment
166+ assert ! ( test_in_project_poetry_env( & venv_dir) ) ;
167+ }
168+
169+ #[ test]
170+ fn test_in_project_poetry_env_with_poetry_core_backend ( ) {
171+ let temp_dir = tempfile:: tempdir ( ) . unwrap ( ) ;
172+ let project_dir = temp_dir. path ( ) ;
173+ let venv_dir = project_dir. join ( ".venv" ) ;
174+
175+ // Create .venv directory
176+ fs:: create_dir ( & venv_dir) . unwrap ( ) ;
177+
178+ // Create pyproject.toml with poetry.core.masonry.api as build backend
179+ let pyproject_content = r#"
180+ [project]
181+ name = "my-project"
182+ version = "0.1.0"
183+
184+ [build-system]
185+ requires = ["poetry-core>=1.0.0"]
186+ build-backend = "poetry.core.masonry.api"
187+ "# ;
188+ fs:: write ( project_dir. join ( "pyproject.toml" ) , pyproject_content) . unwrap ( ) ;
189+
190+ // Test that the .venv is recognized as a Poetry environment
191+ assert ! ( test_in_project_poetry_env( & venv_dir) ) ;
192+ }
193+
194+ #[ test]
195+ fn test_in_project_non_poetry_env_rejected ( ) {
196+ let temp_dir = tempfile:: tempdir ( ) . unwrap ( ) ;
197+ let project_dir = temp_dir. path ( ) ;
198+ let venv_dir = project_dir. join ( ".venv" ) ;
199+
200+ // Create .venv directory
201+ fs:: create_dir ( & venv_dir) . unwrap ( ) ;
202+
203+ // Create pyproject.toml without Poetry configuration
204+ let pyproject_content = r#"
205+ [project]
206+ name = "my-project"
207+ version = "0.1.0"
208+
209+ [build-system]
210+ requires = ["setuptools>=45"]
211+ build-backend = "setuptools.build_meta"
212+ "# ;
213+ fs:: write ( project_dir. join ( "pyproject.toml" ) , pyproject_content) . unwrap ( ) ;
214+
215+ // Test that the .venv is NOT recognized as a Poetry environment
216+ assert ! ( !test_in_project_poetry_env( & venv_dir) ) ;
217+ }
218+
219+ #[ test]
220+ fn test_in_project_env_no_pyproject_rejected ( ) {
221+ let temp_dir = tempfile:: tempdir ( ) . unwrap ( ) ;
222+ let project_dir = temp_dir. path ( ) ;
223+ let venv_dir = project_dir. join ( ".venv" ) ;
224+
225+ // Create .venv directory without pyproject.toml
226+ fs:: create_dir ( & venv_dir) . unwrap ( ) ;
227+
228+ // Test that the .venv is NOT recognized as a Poetry environment
229+ assert ! ( !test_in_project_poetry_env( & venv_dir) ) ;
230+ }
231+
232+ #[ test]
233+ fn test_non_venv_directory_rejected ( ) {
234+ let temp_dir = tempfile:: tempdir ( ) . unwrap ( ) ;
235+ let project_dir = temp_dir. path ( ) ;
236+ let custom_venv = project_dir. join ( "myenv" ) ;
237+
238+ // Create custom env directory (not named .venv)
239+ fs:: create_dir ( & custom_venv) . unwrap ( ) ;
240+
241+ // Create pyproject.toml with Poetry configuration
242+ let pyproject_content = r#"
243+ [tool.poetry]
244+ name = "my-project"
245+ version = "0.1.0"
246+ "# ;
247+ fs:: write ( project_dir. join ( "pyproject.toml" ) , pyproject_content) . unwrap ( ) ;
248+
249+ // Test that non-.venv directories are NOT recognized
250+ assert ! ( !test_in_project_poetry_env( & custom_venv) ) ;
251+ }
103252}
0 commit comments