2121import threading
2222import unittest
2323
24- from google .cloud .dataflow .io import gcsio
2524
25+ import httplib2
26+
27+ from google .cloud .dataflow .io import gcsio
28+ from apitools .base .py .exceptions import HttpError
2629from google .cloud .dataflow .internal .clients import storage
2730
2831
@@ -53,14 +56,24 @@ class FakeGcsObjects(object):
5356
5457 def __init__ (self ):
5558 self .files = {}
59+ # Store the last generation used for a given object name. Note that this
60+ # has to persist even past the deletion of the object.
61+ self .last_generation = {}
5662 self .list_page_tokens = {}
5763
5864 def add_file (self , f ):
5965 self .files [(f .bucket , f .object )] = f
66+ self .last_generation [(f .bucket , f .object )] = f .generation
6067
6168 def get_file (self , bucket , obj ):
6269 return self .files .get ((bucket , obj ), None )
6370
71+ def delete_file (self , bucket , obj ):
72+ del self .files [(bucket , obj )]
73+
74+ def get_last_generation (self , bucket , obj ):
75+ return self .last_generation .get ((bucket , obj ), 0 )
76+
6477 def Get (self , get_request , download = None ): # pylint: disable=invalid-name
6578 f = self .get_file (get_request .bucket , get_request .object )
6679 if f is None :
@@ -77,10 +90,8 @@ def get_range_callback(start, end):
7790
7891 def Insert (self , insert_request , upload = None ): # pylint: disable=invalid-name
7992 assert upload is not None
80- generation = 1
81- f = self .get_file (insert_request .bucket , insert_request .name )
82- if f is not None :
83- generation = f .generation + 1
93+ generation = self .get_last_generation (insert_request .bucket ,
94+ insert_request .name ) + 1
8495 f = FakeFile (insert_request .bucket , insert_request .name , '' , generation )
8596
8697 # Stream data into file.
@@ -95,6 +106,26 @@ def Insert(self, insert_request, upload=None): # pylint: disable=invalid-name
95106
96107 self .add_file (f )
97108
109+ def Copy (self , copy_request ): # pylint: disable=invalid-name
110+ src_file = self .get_file (copy_request .sourceBucket ,
111+ copy_request .sourceObject )
112+ assert src_file is not None
113+ generation = self .get_last_generation (copy_request .destinationBucket ,
114+ copy_request .destinationObject ) + 1
115+ dest_file = FakeFile (copy_request .destinationBucket ,
116+ copy_request .destinationObject ,
117+ src_file .contents , generation )
118+ self .add_file (dest_file )
119+
120+ def Delete (self , delete_request ): # pylint: disable=invalid-name
121+ # Here, we emulate the behavior of the GCS service in raising a 404 error
122+ # if this object already exists.
123+ if self .get_file (delete_request .bucket , delete_request .object ):
124+ self .delete_file (delete_request .bucket , delete_request .object )
125+ else :
126+ raise HttpError (httplib2 .Response ({'status' : '404' }), '404 Not Found' ,
127+ 'https://fake/url' )
128+
98129 def List (self , list_request ): # pylint: disable=invalid-name
99130 bucket = list_request .bucket
100131 prefix = list_request .prefix or ''
@@ -154,6 +185,58 @@ def setUp(self):
154185 self .client = FakeGcsClient ()
155186 self .gcs = gcsio .GcsIO (self .client )
156187
188+ def test_delete (self ):
189+ file_name = 'gs://gcsio-test/delete_me'
190+ file_size = 1024
191+
192+ # Test deletion of non-existent file.
193+ self .gcs .delete (file_name )
194+
195+ self ._insert_random_file (self .client , file_name , file_size )
196+ self .assertTrue (gcsio .parse_gcs_path (file_name ) in
197+ self .client .objects .files )
198+
199+ self .gcs .delete (file_name )
200+
201+ self .assertFalse (gcsio .parse_gcs_path (file_name ) in
202+ self .client .objects .files )
203+
204+ def test_copy (self ):
205+ src_file_name = 'gs://gcsio-test/source'
206+ dest_file_name = 'gs://gcsio-test/dest'
207+ file_size = 1024
208+ self ._insert_random_file (self .client , src_file_name ,
209+ file_size )
210+ self .assertTrue (gcsio .parse_gcs_path (src_file_name ) in
211+ self .client .objects .files )
212+ self .assertFalse (gcsio .parse_gcs_path (dest_file_name ) in
213+ self .client .objects .files )
214+
215+ self .gcs .copy (src_file_name , dest_file_name )
216+
217+ self .assertTrue (gcsio .parse_gcs_path (src_file_name ) in
218+ self .client .objects .files )
219+ self .assertTrue (gcsio .parse_gcs_path (dest_file_name ) in
220+ self .client .objects .files )
221+
222+ def test_rename (self ):
223+ src_file_name = 'gs://gcsio-test/source'
224+ dest_file_name = 'gs://gcsio-test/dest'
225+ file_size = 1024
226+ self ._insert_random_file (self .client , src_file_name ,
227+ file_size )
228+ self .assertTrue (gcsio .parse_gcs_path (src_file_name ) in
229+ self .client .objects .files )
230+ self .assertFalse (gcsio .parse_gcs_path (dest_file_name ) in
231+ self .client .objects .files )
232+
233+ self .gcs .rename (src_file_name , dest_file_name )
234+
235+ self .assertFalse (gcsio .parse_gcs_path (src_file_name ) in
236+ self .client .objects .files )
237+ self .assertTrue (gcsio .parse_gcs_path (dest_file_name ) in
238+ self .client .objects .files )
239+
157240 def test_full_file_read (self ):
158241 file_name = 'gs://gcsio-test/full_file'
159242 file_size = 5 * 1024 * 1024 + 100
0 commit comments