| OLD | NEW |
| (Empty) | |
| 1 # Copyright 2011 Google Inc. |
| 2 # |
| 3 # Licensed under the Apache License, Version 2.0 (the "License"); |
| 4 # you may not use this file except in compliance with the License. |
| 5 # You may obtain a copy of the License at |
| 6 # |
| 7 # http://www.apache.org/licenses/LICENSE-2.0 |
| 8 # |
| 9 # Unless required by applicable law or agreed to in writing, software |
| 10 # distributed under the License is distributed on an "AS IS" BASIS, |
| 11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 12 # See the License for the specific language governing permissions and |
| 13 # limitations under the License. |
| 14 |
| 15 import subprocess |
| 16 import unittest |
| 17 import os |
| 18 import re |
| 19 import getpass |
| 20 import platform |
| 21 |
| 22 from gslib.command import Command |
| 23 from gslib.command import COMMAND_NAME |
| 24 from gslib.command import COMMAND_NAME_ALIASES |
| 25 from gslib.command import CONFIG_REQUIRED |
| 26 from gslib.command import FILE_URIS_OK |
| 27 from gslib.command import MAX_ARGS |
| 28 from gslib.command import MIN_ARGS |
| 29 from gslib.command import PROVIDER_URIS_OK |
| 30 from gslib.command import SUPPORTED_SUB_ARGS |
| 31 from gslib.command import URIS_START_ARG |
| 32 from gslib.exception import CommandException |
| 33 from gslib.help_provider import HELP_NAME |
| 34 from gslib.help_provider import HELP_NAME_ALIASES |
| 35 from gslib.help_provider import HELP_ONE_LINE_SUMMARY |
| 36 from gslib.help_provider import HELP_TEXT |
| 37 from gslib.help_provider import HelpType |
| 38 from gslib.help_provider import HELP_TYPE |
| 39 from gslib.util import NO_MAX |
| 40 from tests.s3.mock_storage_service import MockBucketStorageUri |
| 41 |
| 42 _detailed_help_text = (""" |
| 43 <B>SYNOPSIS</B> |
| 44 gsutil test [command command...] |
| 45 |
| 46 |
| 47 <B>DESCRIPTION</B> |
| 48 The gsutil test command runs end-to-end tests of gsutil commands (i.e., |
| 49 tests that send requests to the production service. This stands in contrast |
| 50 to tests that use an in-memory mock storage service implementation (see |
| 51 "gsutil help dev" for more details on the latter). |
| 52 |
| 53 To run all end-to-end tests run the command with no arguments: |
| 54 |
| 55 gsutil test |
| 56 |
| 57 To see additional details for test failures: |
| 58 |
| 59 gsutil -d test |
| 60 |
| 61 To run tests for one or more individual commands add those commands as |
| 62 arguments. For example: |
| 63 |
| 64 gsutil test cp mv |
| 65 |
| 66 will run the cp and mv command tests. |
| 67 |
| 68 Note: the end-to-end tests are defined in the code for each command (e.g., |
| 69 cp end-to-end tests are in gslib/commands/cp.py). See the comments around |
| 70 'test_steps' in each of the Command subclasses. |
| 71 """) |
| 72 |
| 73 |
| 74 class TestCommand(Command): |
| 75 """Implementation of gsutil test command.""" |
| 76 |
| 77 # Command specification (processed by parent class). |
| 78 command_spec = { |
| 79 # Name of command. |
| 80 COMMAND_NAME : 'test', |
| 81 # List of command name aliases. |
| 82 COMMAND_NAME_ALIASES : [], |
| 83 # Min number of args required by this command. |
| 84 MIN_ARGS : 0, |
| 85 # Max number of args required by this command, or NO_MAX. |
| 86 MAX_ARGS : NO_MAX, |
| 87 # Getopt-style string specifying acceptable sub args. |
| 88 SUPPORTED_SUB_ARGS : '', |
| 89 # True if file URIs acceptable for this command. |
| 90 FILE_URIS_OK : True, |
| 91 # True if provider-only URIs acceptable for this command. |
| 92 PROVIDER_URIS_OK : False, |
| 93 # Index in args of first URI arg. |
| 94 URIS_START_ARG : 0, |
| 95 # True if must configure gsutil before running command. |
| 96 CONFIG_REQUIRED : True, |
| 97 } |
| 98 help_spec = { |
| 99 # Name of command or auxiliary help info for which this help applies. |
| 100 HELP_NAME : 'test', |
| 101 # List of help name aliases. |
| 102 HELP_NAME_ALIASES : [], |
| 103 # Type of help: |
| 104 HELP_TYPE : HelpType.COMMAND_HELP, |
| 105 # One line summary of this help. |
| 106 HELP_ONE_LINE_SUMMARY : 'Run end to end gsutil tests', |
| 107 # The full help text. |
| 108 HELP_TEXT : _detailed_help_text, |
| 109 } |
| 110 |
| 111 # Define constants & class attributes for command testing. |
| 112 username = getpass.getuser().lower() |
| 113 _test_prefix_file = 'gsutil_test_file_' + username + '_' |
| 114 _test_prefix_bucket = 'gsutil_test_bucket_' + username + '_' |
| 115 _test_prefix_object = 'gsutil_test_object_' + username + '_' |
| 116 # Replacement regexps for format specs in test_steps values. |
| 117 _test_replacements = { |
| 118 r'\$B(\d)' : _test_prefix_bucket + r'\1', |
| 119 r'\$O(\d)' : _test_prefix_object + r'\1', |
| 120 r'\$F(\d)' : _test_prefix_file + r'\1', |
| 121 } |
| 122 |
| 123 def _TestRunner(self, cmd, debug): |
| 124 """Run a test command in a subprocess and return result. If debugging |
| 125 requested, display the command, otherwise redirect stdout & stderr |
| 126 to /dev/null. |
| 127 """ |
| 128 if not debug and '>' not in cmd: |
| 129 cmd += ' >/dev/null 2>&1' |
| 130 if debug: |
| 131 print 'cmd:', cmd |
| 132 return subprocess.call(cmd, shell=True) |
| 133 |
| 134 def global_setup(self, debug): |
| 135 """General test setup. |
| 136 |
| 137 For general testing use create three buckets, one empty, one |
| 138 containing one object and one containing two objects. Also create |
| 139 three files for general use. |
| 140 """ |
| 141 print 'Global setup started...' |
| 142 |
| 143 # Build lists of buckets and files. |
| 144 bucket_list = ['gs://$B%d' % i for i in range(0, 10)] |
| 145 file_list = ['$F%d' % i for i in range(0, 3)] |
| 146 |
| 147 # Create test buckets. |
| 148 bucket_cmd = self.gsutil_cmd + ' mb ' + ' '.join(bucket_list) |
| 149 bucket_cmd = self.sub_format_specs(bucket_cmd) |
| 150 self._TestRunner(bucket_cmd, debug) |
| 151 |
| 152 # Create test objects - zero in first bucket, one in second, two in third. |
| 153 for i in range(0, 3): |
| 154 for j in range(0, i): |
| 155 object_cmd = 'echo test | ' + self.gsutil_cmd + \ |
| 156 ' cp - gs://$B%d/$O%d' % (i, j) |
| 157 object_cmd = self.sub_format_specs(object_cmd) |
| 158 self._TestRunner(object_cmd, debug) |
| 159 |
| 160 # Create three test files of size 10MB each. |
| 161 for file in file_list: |
| 162 file = self.sub_format_specs(file) |
| 163 f = open(file, 'w') |
| 164 f.write(os.urandom(10**6)) |
| 165 f.close() |
| 166 |
| 167 print 'Global setup completed.' |
| 168 |
| 169 def global_teardown(self, debug): |
| 170 """General test cleanup. |
| 171 |
| 172 Remove all buckets, objects and files used by this test facility. |
| 173 """ |
| 174 print 'Global teardown started...' |
| 175 # Build commands to remove objects, buckets and files. |
| 176 bucket_list = ['gs://$B%d' % i for i in range(0, 10)] |
| 177 object_list = ['gs://$B%d/*' % i for i in range(0, 10)] |
| 178 file_list = ['$F%d' % i for i in range(0, 10)] |
| 179 bucket_cmd = self.gsutil_cmd + ' rb ' + ' '.join(bucket_list) |
| 180 object_cmd = self.gsutil_cmd + ' rm -f ' + ' '.join(object_list) |
| 181 for f in file_list: |
| 182 f = self.sub_format_specs(f) |
| 183 if os.path.exists(f): |
| 184 os.unlink(f) |
| 185 |
| 186 # Substitute format specifiers ($Bn, $On, $Fn). |
| 187 bucket_cmd = self.sub_format_specs(bucket_cmd) |
| 188 if not debug: |
| 189 bucket_cmd += ' >/dev/null 2>&1' |
| 190 object_cmd = self.sub_format_specs(object_cmd) |
| 191 if not debug: |
| 192 object_cmd += ' >/dev/null 2>&1' |
| 193 |
| 194 # Run the commands. |
| 195 self._TestRunner(object_cmd, debug) |
| 196 self._TestRunner(bucket_cmd, debug) |
| 197 |
| 198 print 'Global teardown completed.' |
| 199 |
| 200 # Command entry point. |
| 201 def RunCommand(self): |
| 202 |
| 203 # To avoid testing aliases, we keep track of previous tests. |
| 204 already_tested = {} |
| 205 |
| 206 self.gsutil_cmd = '' |
| 207 # If running on Windows, invoke python interpreter explicitly. |
| 208 if platform.system() == "Windows": |
| 209 self.gsutil_cmd += 'python ' |
| 210 # Add full path to gsutil to make sure we test the correct version. |
| 211 self.gsutil_cmd += os.path.join(self.gsutil_bin_dir, 'gsutil') |
| 212 |
| 213 # Set sim option on exec'ed commands if user requested mock provider. |
| 214 if issubclass(self.bucket_storage_uri_class, MockBucketStorageUri): |
| 215 self.gsutil_cmd += ' -s' |
| 216 |
| 217 # Instantiate test generator for creating test functions on the fly. |
| 218 gen = test_generator() |
| 219 |
| 220 # Set list of commands to test to include user supplied commands or all |
| 221 # commands if none specified by user ('gsutil test' implies test all). |
| 222 commands_to_test = [] |
| 223 if self.args: |
| 224 for name in self.args: |
| 225 if name in self.command_runner.command_map: |
| 226 commands_to_test.append(name) |
| 227 else: |
| 228 raise CommandException('Test requested for unknown command %s.' |
| 229 % name) |
| 230 else: |
| 231 # No commands specified so test all commands. |
| 232 commands_to_test = self.command_runner.command_map.keys() |
| 233 |
| 234 for name in commands_to_test: |
| 235 cmd = self.command_runner.command_map[name] |
| 236 |
| 237 # Skip this command if test steps not defined or empty. |
| 238 if not hasattr(cmd, 'test_steps') or not cmd.test_steps: |
| 239 if self.debug: |
| 240 print 'Skipping %s command because no test steps defined.' % name |
| 241 continue |
| 242 |
| 243 # Skip aliases for commands we've already tested. |
| 244 if cmd in already_tested: |
| 245 continue |
| 246 already_tested[cmd] = 1 |
| 247 |
| 248 # Run global test setup. |
| 249 self.global_setup(self.debug) |
| 250 |
| 251 # If command has a test_setup method, run per command setup here. |
| 252 if hasattr(cmd, 'test_setup'): |
| 253 cmd.test_setup(self.debug) |
| 254 |
| 255 # Instantiate a test suite, which we'll dynamically add tests to. |
| 256 suite = unittest.TestSuite() |
| 257 |
| 258 # Iterate over the entries in this command's test specification. |
| 259 for (cmdname, cmdline, expect_ret, diff) in cmd.test_steps: |
| 260 cmdline = cmdline.replace('gsutil ', self.gsutil_cmd + ' ') |
| 261 if platform.system() == 'Windows': |
| 262 cmdline = cmdline.replace('cat ', 'type ') |
| 263 |
| 264 # Store file names requested for diff. |
| 265 result_file = None |
| 266 expect_file = None |
| 267 if diff: |
| 268 (result_file, expect_file) = diff |
| 269 |
| 270 # Substitute format specifiers ($Bn, $On, $Fn). |
| 271 cmdline = self.sub_format_specs(cmdline) |
| 272 result_file = self.sub_format_specs(result_file) |
| 273 expect_file = self.sub_format_specs(expect_file) |
| 274 |
| 275 # Generate test function, wrap in a test case and add to test suite. |
| 276 func = gen.genTest(self._TestRunner, cmdline, expect_ret, |
| 277 result_file, expect_file, self.debug) |
| 278 test_case = unittest.FunctionTestCase(func, description=cmdname) |
| 279 suite.addTest(test_case) |
| 280 |
| 281 # Run the tests we've just accumulated. |
| 282 print 'Running tests for', name, 'command.' |
| 283 unittest.TextTestRunner(verbosity=2).run(suite) |
| 284 |
| 285 # If command has a test_teardown method, run per command teardown here. |
| 286 if hasattr(cmd, 'test_teardown'): |
| 287 cmd.test_teardown(self.debug) |
| 288 |
| 289 # Run global test teardown. |
| 290 self.global_teardown(self.debug) |
| 291 |
| 292 def sub_format_specs(self, s): |
| 293 """Perform iterative regexp substitutions on passed string. |
| 294 |
| 295 This method iteratively substitutes values in a passed string, |
| 296 returning the modified string when done. |
| 297 """ |
| 298 # Don't bother if the passed string is empty or None. |
| 299 if s: |
| 300 for (template, repl_str) in self._test_replacements.items(): |
| 301 while re.search(template, s): |
| 302 # Keep substituting as long as the template is found. |
| 303 s = re.sub(template, repl_str, s) |
| 304 return s |
| 305 |
| 306 |
| 307 class test_generator(unittest.TestCase): |
| 308 """Dynamic test generator for use with unittest module. |
| 309 |
| 310 This class is used to generate a test case function. It |
| 311 inherits from unittest.TestCase so that it has access to |
| 312 all the TestCase componentry (e.g. self.assertEqual, etc.). |
| 313 """ |
| 314 |
| 315 def runTest(): |
| 316 """Required method to instantiate unittest.TestCase derived class.""" |
| 317 pass |
| 318 |
| 319 def genTest(self, runner, cmd, expect_ret, result_file, expect_file, debug): |
| 320 """Create and return a function to execute unittest module test cases. |
| 321 |
| 322 This method generates a test function based on the passed |
| 323 input and some inherited methods and returns the generated |
| 324 function to the caller. |
| 325 """ |
| 326 |
| 327 def test_func(): |
| 328 # Run the test command and capture the result in ret. |
| 329 ret = runner(cmd, debug) |
| 330 if expect_ret is not None: |
| 331 # If an expected return code was passed, make sure we got it. |
| 332 self.assertEqual(ret, expect_ret) |
| 333 if result_file and expect_file: |
| 334 # If cmd generated output, diff it against expected output. |
| 335 if platform.system() == 'Windows': |
| 336 diff_cmd = 'echo n | comp ' |
| 337 else: |
| 338 diff_cmd = 'diff ' |
| 339 diff_cmd += '%s %s' % (result_file, expect_file) |
| 340 diff_ret = runner(diff_cmd, debug) |
| 341 self.assertEqual(diff_ret, 0) |
| 342 # Return the generated function to the caller. |
| 343 return test_func |
| 344 |
| OLD | NEW |