def _WaitForOutput(self, match_strings): """Reads stdout from process until each string in match_strings is hit.""" idx = 0 output = [] def IndexInBounds(): return idx < len(match_strings) while IndexInBounds(): line = console_attr.Decode(self.p.stdout.readline()) if not line: break output.append(line) # We allow that a given line may satisfy multiple required strings while IndexInBounds() and match_strings[idx] in line: idx += 1 if idx == len(match_strings): # If we arrive here, this means that every line was matched return # Did not find text (stdout, stderr) = self.p.communicate() self.result = ExecutionResult( command=' '.join(self.args), return_code=self.p.returncode, stdout=''.join(output) + console_attr.Decode(stdout), stderr=console_attr.Decode(stderr)) raise ExecutionError( self.result, msg='Could not find required string in output')
def Run(self, test_file_path, path, style, suffix, exception=None): file_base = '_'.join(path) markdown_path = self.GetTestdataPath(test_file_path, file_base + '.src.md') try: markdown_data = console_attr.Decode( pkg_resources.GetResourceFromFile(markdown_path)) except IOError: file_base = '_'.join(['gcloud'] + path) markdown_path = self.GetTestdataPath(test_file_path, 'markdown', file_base + '.md') markdown_data = console_attr.Decode( pkg_resources.GetResourceFromFile(markdown_path)) f = io.StringIO(markdown_data) try: e = None render_document.RenderDocument(style, fin=f, out=log.out, notes='A special note.', title=' '.join(['gcloud'] + path)) except Exception as e: # pylint: disable=broad-except e = e.message if e != exception: if not exception: self.fail('Exception not expected but [%s] caught.' % e) else: self.fail('Exception [%s] expected but caught [%s].' % (exception, e)) self.AssertOutputIsGolden(test_file_path, file_base + suffix) self.ClearOutput()
def testDecodeException(self): self.assertEqual('ascii', console_attr.Decode(Exception('ascii'))) self.assertEqual('\xff', console_attr.Decode(Exception('\xff'))) self.assertEqual('\u1000', console_attr.Decode(Exception('\u1000'))) if six.PY2: self.assertEqual('\xff', console_attr.Decode(Exception(b'\xc3\xbf'))) else: # On Py3, bytes are not treated as strings and the str() of Exception # contains the repr(). This should be fine because on Py3 Exceptions # should not contain bytes anyway, and if they do, there is no expectation # that it can be decoded. self.assertEqual( r"b'\xc3\xbf'", console_attr.Decode(Exception(b'\xc3\xbf')))
def testDecodeUtf8FileSyetem(self): self.StartObjectPatch( console_attr.ConsoleAttr, 'GetEncoding').return_value = 'ascii' self.StartObjectPatch(sys, 'getfilesystemencoding').return_value = 'utf-8' expected = _UNICODE actual = console_attr.Decode(_UTF8) self.assertEqual(expected, actual)
def testDecodeIso8859_1None(self): self.StartObjectPatch( console_attr.ConsoleAttr, 'GetEncoding').return_value = 'ascii' self.StartObjectPatch(sys, 'getfilesystemencoding').return_value = 'ascii' self.StartObjectPatch(sys, 'getdefaultencoding').return_value = 'ascii' expected = _ISO_8859_1.decode('iso-8859-1') actual = console_attr.Decode(_ISO_8859_1) self.assertEqual(expected, actual)
def _Run(self): try: self.p = subprocess.Popen(self.args, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=self.env) (out, err) = self.p.communicate(self.stdin) self.result = ExecutionResult(command=' '.join( console_attr.SafeText(a) for a in self.args), return_code=self.p.returncode, stdout=console_attr.Decode(out), stderr=console_attr.Decode(err)) ExecutionError.RaiseIfError(self.result) # pylint: disable=bare-except, not catching, just saving to raise later except: self.exc_info = sys.exc_info()
def _Stringify(value): # pylint: disable=invalid-name """Represents value as a JSON string if it's not a string.""" if value is None: return '' elif isinstance(value, console_attr.Colorizer): return value elif isinstance(value, six.string_types): return console_attr.Decode(value) elif isinstance(value, float): return resource_transform.TransformFloat(value) elif hasattr(value, '__str__'): return six.text_type(value) else: return json.dumps(value, sort_keys=True)
def ReadStdin(binary=False): """Reads data from stdin, correctly accounting for encoding. Anything that needs to read sys.stdin must go through this method. Args: binary: bool, True to read raw bytes, False to read text. Returns: A text string if binary is False, otherwise a byte string. """ if binary: return files.ReadStdinBytes() else: data = sys.stdin.read() if six.PY2: # On Python 2, stdin comes in a a byte string. Convert it to text. data = console_attr.Decode(data) return data
def _ConvertNonAsciiArgsToUnicode(self, args): """Converts non-ascii args to unicode. The args most likely came from sys.argv, and Python 2.7 passes them in as bytestrings instead of unicode. Args: args: [str], The list of args to convert. Raises: InvalidCharacterInArgException if a non-ascii arg cannot be converted to unicode. Returns: A new list of args with non-ascii args converted to unicode. """ argv = [] for arg in args: if isinstance(arg, six.text_type): # Arg is already a text string, just use it. argv.append(arg) else: # Argument is a byte string. try: # For now we want to leave byte strings as is if they are only ascii # characters. Later we should always convert everything to text. decoded_arg = arg.decode('ascii') # If this passes, it's ascii only. # Add the original byte string if on PY2, for PY3 start living in the # text only world. argv.append(arg if six.PY2 else decoded_arg) except UnicodeError: # There are unicode characters in the byte string. try: argv.append(console_attr.Decode(arg)) except UnicodeError: raise exceptions.InvalidCharacterInArgException( [self.name] + args, arg) return argv
def GetDecodedArgv(): return [console_attr.Decode(arg) for arg in sys.argv]
def Execute(self, args=None, call_arg_complete=True): """Execute the CLI tool with the given arguments. Args: args: [str], The arguments from the command line or None to use sys.argv call_arg_complete: Call the _ArgComplete function if True Returns: The result of executing the command determined by the command implementation. Raises: ValueError: for ill-typed arguments. """ if isinstance(args, six.string_types): raise ValueError('Execute expects an iterable of strings, not a string.') # The argparse module does not handle unicode args when run in Python 2 # because it uses str(x) even when type(x) is unicode. This sets itself up # for failure because it converts unicode strings back to byte strings which # will trigger ASCII codec exceptions. It works in Python 3 because str() is # equivalent to unicode() in Python 3. The next Pythonically magic and dirty # statement coaxes the Python 3 behavior out of argparse running in # Python 2. Doing it here ensures that the workaround is in place for # calliope argparse use cases. argparse.str = six.text_type # We need the argparse 1.2.1 patch in _SubParsersActionCall. # TODO(b/77288697) delete after py3 tests use non-hermetic python if argparse.__version__ == '1.1': # pytype: disable=module-attr argparse._SubParsersAction.__call__ = _SubParsersActionCall # pylint: disable=protected-access if call_arg_complete: _ArgComplete(self.__top_element.ai) if not args: args = sys.argv[1:] # help ... is the same as ... --help or ... --document=style=help. We use # --document=style=help to signal the metrics.Help() 'help' label in # actions.RenderDocumentAction().Action(). It doesn't matter if we append # to a command that already has --help or --document=style=... because the # first --help/--document from the left takes effect. Note that # `help ... --help` produces help for the help command itself. if args and args[0] == 'help' and '--help' not in args: args = args[1:] + ['--document=style=help'] # Look for a --configuration flag and update property state based on # that before proceeding to the main argparse parse step. named_configs.FLAG_OVERRIDE_STACK.PushFromArgs(args) properties.VALUES.PushInvocationValues() # Set the command name in case an exception happens before the command name # is finished parsing. command_path_string = self.__name specified_arg_names = None # Convert py2 args to text. argv = [console_attr.Decode(arg) for arg in args] if six.PY2 else args old_user_output_enabled = None old_verbosity = None try: args = self.__parser.parse_args(argv) calliope_command = args._GetCommand() # pylint: disable=protected-access command_path_string = '.'.join(calliope_command.GetPath()) specified_arg_names = args.GetSpecifiedArgNames() # If the CLI has not been reloaded since the last command execution (e.g. # in test runs), args.CONCEPTS may contain cached values. if args.CONCEPTS is not None: args.CONCEPTS.Reset() # -h|--help|--document are dispatched by parse_args and never get here. # Now that we have parsed the args, reload the settings so the flags will # take effect. These will use the values from the properties. old_user_output_enabled = log.SetUserOutputEnabled(None) old_verbosity = log.SetVerbosity(None) # Set the command_name property so it is persisted until the process ends. # Only do this for the top level command that can be detected by looking # at the stack. It will have one initial level, and another level added by # the PushInvocationValues earlier in this method. if len(properties.VALUES.GetInvocationStack()) == 2: properties.VALUES.metrics.command_name.Set(command_path_string) # Set the invocation value for all commands, this is lost when popped properties.VALUES.SetInvocationValue( properties.VALUES.metrics.command_name, command_path_string, None) if properties.VALUES.core.capture_session_file.Get() is not None: capturer = session_capturer.SessionCapturer() capturer.CaptureArgs(args) capturer.CaptureState() capturer.CaptureProperties(properties.VALUES.AllValues()) session_capturer.SessionCapturer.capturer = capturer for hook in self.__pre_run_hooks: hook.Run(command_path_string) resources = calliope_command.Run(cli=self, args=args) for hook in self.__post_run_hooks: hook.Run(command_path_string) # Preserve generator or static list resources. if isinstance(resources, types.GeneratorType): def _Yield(): """Activates generator exceptions.""" try: for resource in resources: yield resource except Exception as exc: # pylint: disable=broad-except self._HandleAllErrors(exc, command_path_string, specified_arg_names) return _Yield() # Do this last. If there is an error, the error handler will log the # command execution along with the error. metrics.Commands( command_path_string, config.CLOUD_SDK_VERSION, specified_arg_names) return resources except Exception as exc: # pylint: disable=broad-except self._HandleAllErrors(exc, command_path_string, specified_arg_names) finally: if session_capturer.SessionCapturer.capturer is not None: with open(properties.VALUES.core.capture_session_file.Get(), 'w') as f: session_capturer.SessionCapturer.capturer.Print(f) properties.VALUES.PopInvocationValues() named_configs.FLAG_OVERRIDE_STACK.Pop() # Reset these values to their previous state now that we popped the flag # values. if old_user_output_enabled is not None: log.SetUserOutputEnabled(old_user_output_enabled) if old_verbosity is not None: log.SetVerbosity(old_verbosity)
def testDecodeObjectUnicode(self): obj = _Object(name='Ṁöë', value=".TꙅAꟻ ɘↄAlq oᴎ 'ᴎiTTɘg ɘᴙ'ɘW") expected = "Ṁöë=.TꙅAꟻ ɘↄAlq oᴎ 'ᴎiTTɘg ɘᴙ'ɘW" actual = console_attr.Decode(obj) self.assertEqual(expected, actual)
def testDecodeObjectAscii(self): obj = _Object() expected = 'abc=123' actual = console_attr.Decode(obj) self.assertEqual(expected, actual)
def testDecodeUtf8AttrKwarg(self): expected = _ISO_8859_1.decode('iso-8859-1') actual = console_attr.Decode(_ISO_8859_1, encoding='utf8') self.assertEqual(expected, actual)
def testDecodeUtf8Attr(self): self.StartObjectPatch(console_attr.ConsoleAttr, 'GetEncoding').return_value = 'utf8' expected = _UNICODE actual = console_attr.Decode(_UTF8) self.assertEqual(expected, actual)
def testDecodeAscii(self): expected = _ASCII actual = console_attr.Decode(_ASCII) self.assertEqual(expected, actual)