1212from google .cloud import storage
1313
1414from gcsfs import GCSFileSystem
15- from gcsfs .tests .settings import TEST_BUCKET , TEST_VERSIONED_BUCKET
15+ from gcsfs .tests .settings import TEST_BUCKET , TEST_VERSIONED_BUCKET , TEST_ZONAL_BUCKET
1616
1717files = {
1818 "test/accounts.1.json" : (
@@ -60,7 +60,7 @@ def stop_docker(container):
6060 subprocess .call (["docker" , "rm" , "-f" , "-v" , cid ])
6161
6262
63- @pytest .fixture (scope = "module " )
63+ @pytest .fixture (scope = "session " )
6464def docker_gcs ():
6565 if "STORAGE_EMULATOR_HOST" in os .environ :
6666 # assume using real API or otherwise have a server already set up
@@ -91,7 +91,7 @@ def docker_gcs():
9191 stop_docker (container )
9292
9393
94- @pytest .fixture
94+ @pytest .fixture ( scope = "session" )
9595def gcs_factory (docker_gcs ):
9696 params ["endpoint_url" ] = docker_gcs
9797
@@ -102,44 +102,83 @@ def factory(**kwargs):
102102 return factory
103103
104104
105+ @pytest .fixture (scope = "session" )
106+ def buckets_to_delete ():
107+ """A set to keep track of buckets created during the test session."""
108+ return set ()
109+
110+
105111@pytest .fixture
106- def gcs (gcs_factory , populate = True ):
112+ def gcs (gcs_factory , buckets_to_delete , populate = True ):
107113 gcs = gcs_factory ()
108- try :
109- # ensure we're empty.
110- try :
111- gcs .rm (TEST_BUCKET , recursive = True )
112- except FileNotFoundError :
113- pass
114- try :
114+ try : # ensure we're empty.
115+ # Create the bucket if it doesn't exist, otherwise clean it.
116+ if not gcs .exists (TEST_BUCKET ):
115117 gcs .mkdir (TEST_BUCKET )
116- except Exception :
117- pass
118+ buckets_to_delete .add (TEST_BUCKET )
119+ else :
120+ try :
121+ gcs .rm (gcs .find (TEST_BUCKET ))
122+ except Exception as e :
123+ logging .warning (f"Failed to empty bucket { TEST_BUCKET } : { e } " )
118124
119125 if populate :
120126 gcs .pipe ({TEST_BUCKET + "/" + k : v for k , v in allfiles .items ()})
121127 gcs .invalidate_cache ()
122128 yield gcs
123129 finally :
124- try :
125- gcs .rm (gcs .find (TEST_BUCKET ))
126- gcs .rm (TEST_BUCKET )
127- except : # noqa: E722
128- pass
130+ _cleanup_gcs (gcs )
129131
130132
131- def _cleanup_gcs (gcs , is_real_gcs ):
132- """Only remove the bucket/contents if we are NOT using the real GCS, logging a warning on failure."""
133- if is_real_gcs :
134- return
133+ def _cleanup_gcs (gcs ):
134+ """Clean the bucket contents, logging a warning on failure."""
135135 try :
136- gcs .rm (TEST_BUCKET , recursive = True )
136+ gcs .rm (gcs . find ( TEST_BUCKET ) )
137137 except Exception as e :
138138 logging .warning (f"Failed to clean up GCS bucket { TEST_BUCKET } : { e } " )
139139
140140
141+ @pytest .fixture (scope = "session" , autouse = True )
142+ def final_cleanup (gcs_factory , buckets_to_delete ):
143+ """A session-scoped fixture to delete the test buckets after all tests are run."""
144+ yield
145+ # This code runs after the entire test session finishes
146+ use_extended_gcs = os .getenv (
147+ "GCSFS_EXPERIMENTAL_ZB_HNS_SUPPORT" , "false"
148+ ).lower () in (
149+ "true" ,
150+ "1" ,
151+ )
152+
153+ if use_extended_gcs :
154+ is_real_gcs = (
155+ os .environ .get ("STORAGE_EMULATOR_HOST" ) == "https://storage.googleapis.com"
156+ )
157+ mock_authentication_manager = (
158+ patch ("google.auth.default" , return_value = (None , "fake-project" ))
159+ if not is_real_gcs
160+ else nullcontext ()
161+ )
162+ else :
163+ mock_authentication_manager = nullcontext ()
164+
165+ with mock_authentication_manager :
166+ gcs = gcs_factory ()
167+ for bucket in buckets_to_delete :
168+ # For real GCS, only delete if created by the test suite.
169+ # For emulators, always delete.
170+ try :
171+ if gcs .exists (bucket ):
172+ gcs .rm (bucket , recursive = True )
173+ logging .info (f"Cleaned up bucket: { bucket } " )
174+ except Exception as e :
175+ logging .warning (
176+ f"Failed to perform final cleanup for bucket { bucket } : { e } "
177+ )
178+
179+
141180@pytest .fixture
142- def extended_gcsfs (gcs_factory , populate = True ):
181+ def extended_gcsfs (gcs_factory , buckets_to_delete , populate = True ):
143182 # Check if we are running against a real GCS endpoint
144183 is_real_gcs = (
145184 os .environ .get ("STORAGE_EMULATOR_HOST" ) == "https://storage.googleapis.com"
@@ -159,52 +198,62 @@ def extended_gcsfs(gcs_factory, populate=True):
159198 # Only create/delete/populate the bucket if we are NOT using the real GCS endpoint
160199 if not is_real_gcs :
161200 try :
162- extended_gcsfs .rm (TEST_BUCKET , recursive = True )
201+ extended_gcsfs .rm (TEST_ZONAL_BUCKET , recursive = True )
163202 except FileNotFoundError :
164203 pass
165- extended_gcsfs .mkdir (TEST_BUCKET )
204+ extended_gcsfs .mkdir (TEST_ZONAL_BUCKET )
205+ buckets_to_delete .add (TEST_ZONAL_BUCKET )
166206 if populate :
167207 extended_gcsfs .pipe (
168- {TEST_BUCKET + "/" + k : v for k , v in allfiles .items ()}
208+ {TEST_ZONAL_BUCKET + "/" + k : v for k , v in allfiles .items ()}
169209 )
170210 extended_gcsfs .invalidate_cache ()
171211 yield extended_gcsfs
172212 finally :
173- _cleanup_gcs (extended_gcsfs , is_real_gcs )
213+ _cleanup_gcs (extended_gcsfs )
174214
175215
176216@pytest .fixture
177- def gcs_versioned (gcs_factory ):
217+ def gcs_versioned (gcs_factory , buckets_to_delete ):
178218 gcs = gcs_factory ()
179219 gcs .version_aware = True
180220 is_real_gcs = (
181221 os .environ .get ("STORAGE_EMULATOR_HOST" ) == "https://storage.googleapis.com"
182222 )
183223 try : # ensure we're empty.
224+ # The versioned bucket might be created by `is_versioning_enabled`
225+ # in test_core_versioned.py. We must register it for cleanup only if
226+ # it was created by this test run.
227+ try :
228+ from gcsfs .tests .test_core_versioned import (
229+ _VERSIONED_BUCKET_CREATED_BY_TESTS ,
230+ )
231+
232+ if _VERSIONED_BUCKET_CREATED_BY_TESTS :
233+ buckets_to_delete .add (TEST_VERSIONED_BUCKET )
234+ except ImportError :
235+ pass # test_core_versioned is not being run
184236 if is_real_gcs :
185- # For real GCS, we assume the bucket exists and only clean its contents.
186- try :
187- cleanup_versioned_bucket (gcs , TEST_VERSIONED_BUCKET )
188- except Exception as e :
189- logging .warning (
190- f"Failed to empty versioned bucket { TEST_VERSIONED_BUCKET } : { e } "
191- )
237+ cleanup_versioned_bucket (gcs , TEST_VERSIONED_BUCKET )
192238 else :
193- # For emulators, we delete and recreate the bucket for a clean state.
239+ # For emulators, we delete and recreate the bucket for a clean state
194240 try :
195241 gcs .rm (TEST_VERSIONED_BUCKET , recursive = True )
196242 except FileNotFoundError :
197243 pass
198244 gcs .mkdir (TEST_VERSIONED_BUCKET , enable_versioning = True )
245+ buckets_to_delete .add (TEST_VERSIONED_BUCKET )
199246 gcs .invalidate_cache ()
200247 yield gcs
201248 finally :
249+ # Ensure the bucket is empty after the test.
202250 try :
203- if not is_real_gcs :
204- gcs .rm (gcs .find (TEST_VERSIONED_BUCKET , versions = True ))
205- gcs .rm (TEST_VERSIONED_BUCKET )
206- except : # noqa: E722
207- pass
251+ if is_real_gcs :
252+ cleanup_versioned_bucket (gcs , TEST_VERSIONED_BUCKET )
253+ except Exception as e :
254+ logging .warning (
255+ f"Failed to clean up versioned bucket { TEST_VERSIONED_BUCKET } after test: { e } "
256+ )
208257
209258
210259def cleanup_versioned_bucket (gcs , bucket_name , prefix = None ):
0 commit comments