1 # -*- coding: utf-8 -*- 2 # Copyright 2014 Google Inc. All Rights Reserved. 3 # 4 # Licensed under the Apache License, Version 2.0 (the "License"); 5 # you may not use this file except in compliance with the License. 6 # You may obtain a copy of the License at 7 # 8 # http://www.apache.org/licenses/LICENSE-2.0 9 # 10 # Unless required by applicable law or agreed to in writing, software 11 # distributed under the License is distributed on an "AS IS" BASIS, 12 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 # See the License for the specific language governing permissions and 14 # limitations under the License. 15 """Integration tests for tab completion.""" 16 17 from __future__ import absolute_import 18 19 import os 20 import time 21 22 from gslib.command import CreateGsutilLogger 23 from gslib.tab_complete import CloudObjectCompleter 24 from gslib.tab_complete import TAB_COMPLETE_CACHE_TTL 25 from gslib.tab_complete import TabCompletionCache 26 import gslib.tests.testcase as testcase 27 from gslib.tests.util import ARGCOMPLETE_AVAILABLE 28 from gslib.tests.util import SetBotoConfigForTest 29 from gslib.tests.util import unittest 30 from gslib.tests.util import WorkingDirectory 31 from gslib.util import GetTabCompletionCacheFilename 32 33 34 @unittest.skipUnless(ARGCOMPLETE_AVAILABLE, 35 'Tab completion requires argcomplete') 36 class TestTabComplete(testcase.GsUtilIntegrationTestCase): 37 """Integration tests for tab completion.""" 38 39 def setUp(self): 40 super(TestTabComplete, self).setUp() 41 self.logger = CreateGsutilLogger('tab_complete') 42 43 def test_single_bucket(self): 44 """Tests tab completion matching a single bucket.""" 45 46 bucket_base_name = self.MakeTempName('bucket') 47 bucket_name = bucket_base_name + '-suffix' 48 self.CreateBucket(bucket_name) 49 50 request = '%s://%s' % (self.default_provider, bucket_base_name) 51 expected_result = '//%s/' % bucket_name 52 53 self.RunGsUtilTabCompletion(['ls', request], 54 expected_results=[expected_result]) 55 56 def test_bucket_only_single_bucket(self): 57 """Tests bucket-only tab completion matching a single bucket.""" 58 59 bucket_base_name = self.MakeTempName('bucket') 60 bucket_name = bucket_base_name + '-s' 61 self.CreateBucket(bucket_name) 62 63 request = '%s://%s' % (self.default_provider, bucket_base_name) 64 expected_result = '//%s ' % bucket_name 65 66 self.RunGsUtilTabCompletion(['rb', request], 67 expected_results=[expected_result]) 68 69 def test_bucket_only_no_objects(self): 70 """Tests that bucket-only tab completion doesn't match objects.""" 71 72 object_base_name = self.MakeTempName('obj') 73 object_name = object_base_name + '-suffix' 74 object_uri = self.CreateObject(object_name=object_name, contents='data') 75 76 request = '%s://%s/%s' % ( 77 self.default_provider, object_uri.bucket_name, object_base_name) 78 79 self.RunGsUtilTabCompletion(['rb', request], expected_results=[]) 80 81 def test_single_subdirectory(self): 82 """Tests tab completion matching a single subdirectory.""" 83 84 object_base_name = self.MakeTempName('obj') 85 object_name = object_base_name + '/subobj' 86 object_uri = self.CreateObject(object_name=object_name, contents='data') 87 88 request = '%s://%s/' % (self.default_provider, object_uri.bucket_name) 89 expected_result = '//%s/%s/' % (object_uri.bucket_name, object_base_name) 90 91 self.RunGsUtilTabCompletion(['ls', request], 92 expected_results=[expected_result]) 93 94 def test_multiple_buckets(self): 95 """Tests tab completion matching multiple buckets.""" 96 97 bucket_base_name = self.MakeTempName('bucket') 98 bucket1_name = bucket_base_name + '-suffix1' 99 self.CreateBucket(bucket1_name) 100 bucket2_name = bucket_base_name + '-suffix2' 101 self.CreateBucket(bucket2_name) 102 103 request = '%s://%s' % (self.default_provider, bucket_base_name) 104 expected_result1 = '//%s/' % bucket1_name 105 expected_result2 = '//%s/' % bucket2_name 106 107 self.RunGsUtilTabCompletion(['ls', request], expected_results=[ 108 expected_result1, expected_result2]) 109 110 def test_single_object(self): 111 """Tests tab completion matching a single object.""" 112 113 object_base_name = self.MakeTempName('obj') 114 object_name = object_base_name + '-suffix' 115 object_uri = self.CreateObject(object_name=object_name, contents='data') 116 117 request = '%s://%s/%s' % ( 118 self.default_provider, object_uri.bucket_name, object_base_name) 119 expected_result = '//%s/%s ' % (object_uri.bucket_name, object_name) 120 121 self.RunGsUtilTabCompletion(['ls', request], 122 expected_results=[expected_result]) 123 124 def test_multiple_objects(self): 125 """Tests tab completion matching multiple objects.""" 126 127 bucket_uri = self.CreateBucket() 128 129 object_base_name = self.MakeTempName('obj') 130 object1_name = object_base_name + '-suffix1' 131 self.CreateObject( 132 bucket_uri=bucket_uri, object_name=object1_name, contents='data') 133 object2_name = object_base_name + '-suffix2' 134 self.CreateObject( 135 bucket_uri=bucket_uri, object_name=object2_name, contents='data') 136 137 request = '%s://%s/%s' % ( 138 self.default_provider, bucket_uri.bucket_name, object_base_name) 139 expected_result1 = '//%s/%s' % (bucket_uri.bucket_name, object1_name) 140 expected_result2 = '//%s/%s' % (bucket_uri.bucket_name, object2_name) 141 142 self.RunGsUtilTabCompletion(['ls', request], expected_results=[ 143 expected_result1, expected_result2]) 144 145 def test_subcommands(self): 146 """Tests tab completion for commands with subcommands.""" 147 148 bucket_base_name = self.MakeTempName('bucket') 149 bucket_name = bucket_base_name + '-suffix' 150 self.CreateBucket(bucket_name) 151 152 bucket_request = '%s://%s' % (self.default_provider, bucket_base_name) 153 expected_bucket_result = '//%s ' % bucket_name 154 155 local_file = 'a_local_file' 156 local_dir = self.CreateTempDir(test_files=[local_file]) 157 158 local_file_request = '%s%s' % (local_dir, os.sep) 159 expected_local_file_result = '%s ' % os.path.join(local_dir, local_file) 160 161 # Should invoke Cloud bucket URL completer. 162 self.RunGsUtilTabCompletion(['cors', 'get', bucket_request], 163 expected_results=[expected_bucket_result]) 164 165 # Should invoke File URL completer which should match the local file. 166 self.RunGsUtilTabCompletion(['cors', 'set', local_file_request], 167 expected_results=[expected_local_file_result]) 168 169 # Should invoke Cloud bucket URL completer. 170 self.RunGsUtilTabCompletion(['cors', 'set', 'some_file', bucket_request], 171 expected_results=[expected_bucket_result]) 172 173 def test_invalid_partial_bucket_name(self): 174 """Tests tab completion with a partial URL that by itself is not valid. 175 176 The bucket name in a Cloud URL cannot end in a dash, but a partial URL 177 during tab completion may end in a dash and completion should still work. 178 """ 179 180 bucket_base_name = self.MakeTempName('bucket') 181 bucket_name = bucket_base_name + '-s' 182 self.CreateBucket(bucket_name) 183 184 request = '%s://%s-' % (self.default_provider, bucket_base_name) 185 expected_result = '//%s/' % bucket_name 186 187 self.RunGsUtilTabCompletion(['ls', request], 188 expected_results=[expected_result]) 189 190 def test_acl_argument(self): 191 """Tests tab completion for ACL arguments.""" 192 193 local_file = 'a_local_file' 194 local_dir = self.CreateTempDir(test_files=[local_file]) 195 196 local_file_request = '%s%s' % (local_dir, os.sep) 197 expected_local_file_result = '%s ' % os.path.join(local_dir, local_file) 198 199 # Should invoke File URL completer which should match the local file. 200 self.RunGsUtilTabCompletion(['acl', 'set', local_file_request], 201 expected_results=[expected_local_file_result]) 202 203 # Should match canned ACL name. 204 self.RunGsUtilTabCompletion(['acl', 'set', 'priv'], 205 expected_results=['private ']) 206 207 local_file = 'priv_file' 208 local_dir = self.CreateTempDir(test_files=[local_file]) 209 with WorkingDirectory(local_dir): 210 # Should match both a file and a canned ACL since argument takes 211 # either one. 212 self.RunGsUtilTabCompletion(['acl', 'set', 'priv'], 213 expected_results=[local_file, 'private']) 214 215 216 def _WriteTabCompletionCache(prefix, results, timestamp=None, 217 partial_results=False): 218 if timestamp is None: 219 timestamp = time.time() 220 cache = TabCompletionCache(prefix, results, timestamp, partial_results) 221 cache.WriteToFile(GetTabCompletionCacheFilename()) 222 223 224 @unittest.skipUnless(ARGCOMPLETE_AVAILABLE, 225 'Tab completion requires argcomplete') 226 class TestTabCompleteUnitTests(testcase.unit_testcase.GsUtilUnitTestCase): 227 """Unit tests for tab completion.""" 228 229 def test_cached_results(self): 230 """Tests tab completion results returned from cache.""" 231 232 with SetBotoConfigForTest([('GSUtil', 'state_dir', self.CreateTempDir())]): 233 request = 'gs://prefix' 234 cached_results = ['gs://prefix1', 'gs://prefix2'] 235 236 _WriteTabCompletionCache(request, cached_results) 237 238 completer = CloudObjectCompleter(self.MakeGsUtilApi()) 239 results = completer(request) 240 241 self.assertEqual(cached_results, results) 242 243 def test_expired_cached_results(self): 244 """Tests tab completion results not returned from cache when too old.""" 245 246 with SetBotoConfigForTest([('GSUtil', 'state_dir', self.CreateTempDir())]): 247 bucket_base_name = self.MakeTempName('bucket') 248 bucket_name = bucket_base_name + '-suffix' 249 self.CreateBucket(bucket_name) 250 251 request = '%s://%s' % (self.default_provider, bucket_base_name) 252 expected_result = '%s://%s/' % (self.default_provider, bucket_name) 253 254 cached_results = ['//%s1' % bucket_name, '//%s2' % bucket_name] 255 256 _WriteTabCompletionCache(request, cached_results, 257 time.time() - TAB_COMPLETE_CACHE_TTL) 258 259 completer = CloudObjectCompleter(self.MakeGsUtilApi()) 260 results = completer(request) 261 262 self.assertEqual([expected_result], results) 263 264 def test_prefix_caching(self): 265 """Tests tab completion results returned from cache with prefix match. 266 267 If the tab completion prefix is an extension of the cached prefix, tab 268 completion should return results from the cache that start with the prefix. 269 """ 270 271 with SetBotoConfigForTest([('GSUtil', 'state_dir', self.CreateTempDir())]): 272 cached_prefix = 'gs://prefix' 273 cached_results = ['gs://prefix-first', 'gs://prefix-second'] 274 _WriteTabCompletionCache(cached_prefix, cached_results) 275 276 request = 'gs://prefix-f' 277 completer = CloudObjectCompleter(self.MakeGsUtilApi()) 278 results = completer(request) 279 280 self.assertEqual(['gs://prefix-first'], results) 281 282 def test_prefix_caching_boundary(self): 283 """Tests tab completion prefix caching not spanning directory boundaries. 284 285 If the tab completion prefix is an extension of the cached prefix, but is 286 not within the same bucket/sub-directory then the cached results should not 287 be used. 288 """ 289 290 with SetBotoConfigForTest([('GSUtil', 'state_dir', self.CreateTempDir())]): 291 object_uri = self.CreateObject( 292 object_name='subdir/subobj', contents='test data') 293 294 cached_prefix = '%s://%s/' % ( 295 self.default_provider, object_uri.bucket_name) 296 cached_results = ['%s://%s/subdir' % ( 297 self.default_provider, object_uri.bucket_name)] 298 _WriteTabCompletionCache(cached_prefix, cached_results) 299 300 request = '%s://%s/subdir/' % ( 301 self.default_provider, object_uri.bucket_name) 302 expected_result = '%s://%s/subdir/subobj' % ( 303 self.default_provider, object_uri.bucket_name) 304 305 completer = CloudObjectCompleter(self.MakeGsUtilApi()) 306 results = completer(request) 307 308 self.assertEqual([expected_result], results) 309 310 def test_prefix_caching_no_results(self): 311 """Tests tab completion returning empty result set using cached prefix. 312 313 If the tab completion prefix is an extension of the cached prefix, but does 314 not match any of the cached results then no remote request should be made 315 and an empty result set should be returned. 316 """ 317 318 with SetBotoConfigForTest([('GSUtil', 'state_dir', self.CreateTempDir())]): 319 object_uri = self.CreateObject(object_name='obj', contents='test data') 320 321 cached_prefix = '%s://%s/' % ( 322 self.default_provider, object_uri.bucket_name) 323 cached_results = [] 324 _WriteTabCompletionCache(cached_prefix, cached_results) 325 326 request = '%s://%s/o' % (self.default_provider, object_uri.bucket_name) 327 328 completer = CloudObjectCompleter(self.MakeGsUtilApi()) 329 results = completer(request) 330 331 self.assertEqual([], results) 332 333 def test_prefix_caching_partial_results(self): 334 """Tests tab completion prefix matching ignoring partial cached results. 335 336 If the tab completion prefix is an extension of the cached prefix, but the 337 cached result set is partial, the cached results should not be used because 338 the matching results for the prefix may be incomplete. 339 """ 340 341 with SetBotoConfigForTest([('GSUtil', 'state_dir', self.CreateTempDir())]): 342 object_uri = self.CreateObject(object_name='obj', contents='test data') 343 344 cached_prefix = '%s://%s/' % ( 345 self.default_provider, object_uri.bucket_name) 346 cached_results = [] 347 _WriteTabCompletionCache(cached_prefix, cached_results, 348 partial_results=True) 349 350 request = '%s://%s/o' % (self.default_provider, object_uri.bucket_name) 351 352 completer = CloudObjectCompleter(self.MakeGsUtilApi()) 353 results = completer(request) 354 355 self.assertEqual([str(object_uri)], results) 356