qemu-devel
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: [Qemu-devel] [PATCH v5 01/10] qemu.py: Pylint/style fixes


From: Lukáš Doktor
Subject: Re: [Qemu-devel] [PATCH v5 01/10] qemu.py: Pylint/style fixes
Date: Wed, 16 Aug 2017 19:48:24 +0200
User-agent: Mozilla/5.0 (X11; Linux x86_64; rv:52.0) Gecko/20100101 Thunderbird/52.2.1

Dne 16.8.2017 v 18:58 Markus Armbruster napsal(a):
> Lukáš Doktor <address@hidden> writes:
> 
>> Dne 15.8.2017 v 14:31 Markus Armbruster napsal(a):
>>> Lukáš Doktor <address@hidden> writes:
>>>
>>>> No actual code changes, just several pylint/style fixes and docstring
>>>> clarifications.
>>>>
>>>> Signed-off-by: Lukáš Doktor <address@hidden>
>>>> ---
>>>>  scripts/qemu.py | 76 
>>>> ++++++++++++++++++++++++++++++++++++++++-----------------
>>>>  1 file changed, 53 insertions(+), 23 deletions(-)
>>>>
>>>> diff --git a/scripts/qemu.py b/scripts/qemu.py
>>>> index 880e3e8..466aaab 100644
>>>> --- a/scripts/qemu.py
>>>> +++ b/scripts/qemu.py
>>>> @@ -23,8 +23,22 @@ import qmp.qmp
>>>>  class QEMUMachine(object):
>>>>      '''A QEMU VM'''
>>>>  
>>>> -    def __init__(self, binary, args=[], wrapper=[], name=None, 
>>>> test_dir="/var/tmp",
>>>> -                 monitor_address=None, socket_scm_helper=None, 
>>>> debug=False):
>>>> +    def __init__(self, binary, args=[], wrapper=[], name=None,
>>>> +                 test_dir="/var/tmp", monitor_address=None,
>>>> +                 socket_scm_helper=None, debug=False):
>>>> +        '''
>>>> +        Create a QEMUMachine object
>>>
>>> Initialize a QEMUMachine
>>>
>>> Rationale: it's __init__, not __create__, and "object" is redundant.
>>>
>>
>> sure
>>
>>>> +
>>>> +        @param binary: path to the qemu binary (str)
>>>
>>> Drop (str), because what else could it be?
>>
>> it could be shlex.split of arguments to be passed to process. Anyway no 
>> strong opinion here so I dropping it...
>>
>>>
>>>> +        @param args: initial list of extra arguments
>>>
>>> If this is the initial list, then what's the final list?
>>>
>>
>> It's the basic set of arguments which can be modified before the execution. 
>> Do you think it requires additional explanation, or would you like to 
>> improve it somehow?
> 
> Can this list of extra arguments really be *modified*?  Adding more
> arguments doesn't count for me --- I'd consider them added to the
> "non-extra" arguments.
> 

Yes, one can remove, shuffle or modify it.

> Drop "initial"?

I can do that but it can give false impression that the args will be present. 
Anyway it's probably just a corner case so I'll drop it.

> 
>>>> +        @param wrapper: list of arguments used as prefix to qemu binary
>>>> +        @param name: name of this object (used for log/monitor/... file 
>>>> names)
>>> prefix for socket and log file names (default: qemu-PID)
>>>
>>
>> Sure, both make sense to me.
>>
>>>> +        @param test_dir: base location to put log/monitor/... files in
>>>
>>> where to create socket and log file
>>>
>>> Aside: test_dir is a lousy name.
>>
>> Agree but changing names is tricky as people might be using kwargs to set 
>> it. Anyway using your description here, keeping the possible rename for a 
>> separate patchset (if needed).
> 
> I'm merely observing the lousiness of this name.  I'm not asking you to
> do anything about it :)
> 
>>>> +        @param monitor_address: custom address for QMP monitor
>>>
>>> Yes, but what *is* a custom address?  Its user _base_args() appears to
>>> expect either a pair of strings (host, port) or a string filename.
>>>
>>
>> If you insist I can add something like "a tuple(host, port) or string to 
>> specify path", but I find it unnecessary detailed...
> 
> I'm not the maintainer, I'm definitely not insisting on anything.
> 
> If you're aiming for brevity, then drop "custom".
> 

OK, removing in v6

>>>> +        @param socket_scm_helper: path to scm_helper binary (to forward 
>>>> fds)
>>>
>>> What is an scm_helper, and why would I want to use it?
>>>
>>
>> To forward a file descriptor. It's for example used in 
>> tests/qemu-iotests/045 or tests/qemu-iotests/147
> 
> What about "socket_scm_helper: helper program, required for send_fd_scm()"?
> 
>>>> +        @param debug: enable debug mode (forwarded to QMP helper and such)
>>>
>>> What is a QMP helper?  To what else is debug forwarded?
>>>
>>
>> Debug is set in `self._debug` and can be consumed by anyone who has access 
>> to this variable. Currently that is the QMP, but people can inherit and use 
>> that variable to adjust their behavior.
> 
> Drop the parenthesis?
> 

OK

>>>> +        @note: Qemu process is not started until launch() is used.
>>>
>>> until launch().
>>>
>>
>> OK
> 
> One more thing: what the use of "@param"?
> 

The API documentation can be autogenerated by doxygen, it uses those keywords 
to make it easier to read (and to create links, warnings, ...)

>>>> +        '''
>>>
>>> It's an improvement.
>>>
>>>>          if name is None:
>>>>              name = "qemu-%d" % os.getpid()
>>>>          if monitor_address is None:
>>>> @@ -33,12 +47,13 @@ class QEMUMachine(object):
>>>>          self._qemu_log_path = os.path.join(test_dir, name + ".log")
>>>>          self._popen = None
>>>>          self._binary = binary
>>>> -        self._args = list(args) # Force copy args in case we modify them
>>>> +        self._args = list(args)     # Force copy args in case we modify 
>>>> them
>>>>          self._wrapper = wrapper
>>>>          self._events = []
>>>>          self._iolog = None
>>>>          self._socket_scm_helper = socket_scm_helper
>>>>          self._debug = debug
>>>> +        self._qmp = None
>>>>  
>>>>      # This can be used to add an unused monitor instance.
>>>>      def add_monitor_telnet(self, ip, port):
>>>> @@ -64,16 +79,16 @@ class QEMUMachine(object):
>>>>          if self._socket_scm_helper is None:
>>>>              print >>sys.stderr, "No path to socket_scm_helper set"
>>>>              return -1
>>>> -        if os.path.exists(self._socket_scm_helper) == False:
>>>> +        if not os.path.exists(self._socket_scm_helper):
>>>>              print >>sys.stderr, "%s does not exist" % 
>>>> self._socket_scm_helper
>>>>              return -1
>>>>          fd_param = ["%s" % self._socket_scm_helper,
>>>>                      "%d" % self._qmp.get_sock_fd(),
>>>>                      "%s" % fd_file_path]
>>>>          devnull = open('/dev/null', 'rb')
>>>> -        p = subprocess.Popen(fd_param, stdin=devnull, stdout=sys.stdout,
>>>> -                             stderr=sys.stderr)
>>>> -        return p.wait()
>>>> +        proc = subprocess.Popen(fd_param, stdin=devnull, 
>>>> stdout=sys.stdout,
>>>> +                                stderr=sys.stderr)
>>>> +        return proc.wait()
>>>>  
>>>>      @staticmethod
>>>>      def _remove_if_exists(path):
>>>> @@ -99,8 +114,8 @@ class QEMUMachine(object):
>>>>          return self._popen.pid
>>>>  
>>>>      def _load_io_log(self):
>>>> -        with open(self._qemu_log_path, "r") as fh:
>>>> -            self._iolog = fh.read()
>>>> +        with open(self._qemu_log_path, "r") as iolog:
>>>> +            self._iolog = iolog.read()
>>>>  
>>>>      def _base_args(self):
>>>>          if isinstance(self._monitor_address, tuple):
>>>> @@ -114,8 +129,8 @@ class QEMUMachine(object):
>>>>                  '-display', 'none', '-vga', 'none']
>>>>  
>>>>      def _pre_launch(self):
>>>> -        self._qmp = qmp.qmp.QEMUMonitorProtocol(self._monitor_address, 
>>>> server=True,
>>>> -                                                debug=self._debug)
>>>> +        self._qmp = qmp.qmp.QEMUMonitorProtocol(self._monitor_address,
>>>> +                                                server=True, 
>>>> debug=self._debug)
>>>
>>> Recommend to break the line between the last two arguments.
>>>
>>
>> I'm not used to do that, but I can most certainly do that. Do you want me to 
>> change all places (eg. in the next chunk)
> 
> PEP8 asks you to limit all lines to a maximum of 79 characters.  This
> one is right at the maximum.  Tolerable, but I prefer my lines a bit
> shorter.  Use your judgement.
> 

OK, I though you want to enforce the one-arg-per-line limit. I usually go with 
<=79 (+ '\n') so this is fine by me, but I'll put it into next line if that 
makes you happier.

>>>>  
>>>>      def _post_launch(self):
>>>>          self._qmp.accept()
>>>> @@ -131,9 +146,11 @@ class QEMUMachine(object):
>>>>          qemulog = open(self._qemu_log_path, 'wb')
>>>>          try:
>>>>              self._pre_launch()
>>>> -            args = self._wrapper + [self._binary] + self._base_args() + 
>>>> self._args
>>>> +            args = (self._wrapper + [self._binary] + self._base_args() +
>>>> +                    self._args)
>>>>              self._popen = subprocess.Popen(args, stdin=devnull, 
>>>> stdout=qemulog,
>>>> -                                           stderr=subprocess.STDOUT, 
>>>> shell=False)
>>>> +                                           stderr=subprocess.STDOUT,
>>>> +                                           shell=False)
>>>>              self._post_launch()
>>>>          except:
>>>>              if self.is_running():
>>>> @@ -149,28 +166,34 @@ class QEMUMachine(object):
>>>>              try:
>>>>                  self._qmp.cmd('quit')
>>>>                  self._qmp.close()
>>>> -            except:
>>>> +            except:     # kill VM on any failure pylint: disable=W0702
>>>
>>> The bare except is almost certainly inappropriate.  Are you sure we
>>> should silence pylint?
>>>
>>> Note that there's another bare except in launch(), visible in the
>>> previous patch hunk.  I guess pylint is okay with that one because it
>>> re-throws the exception.
>>>
>>> In case we should silence pylint: what's the scope of this magic pylint
>>> comment?  Can we use the symbolic disable=bare-except?
>>>
>>
>> Yep, we can use symbolic name as well. Well more appropriate would be to 
>> catch `SystemExit` and `KeyboardInterrupt`, treat them and raise them and 
>> then ignore the rest of exceptions. I'll do that in the next version...
>>
>>>>                  self._popen.kill()
>>>>  
>>>>              exitcode = self._popen.wait()
>>>>              if exitcode < 0:
>>>> -                sys.stderr.write('qemu received signal %i: %s\n' % 
>>>> (-exitcode, ' '.join(self._args)))
>>>> +                sys.stderr.write('qemu received signal %i: %s\n'
>>>> +                                 % (-exitcode, ' '.join(self._args)))
>>>>              self._load_io_log()
>>>>              self._post_shutdown()
>>>>  
>>>>      underscore_to_dash = string.maketrans('_', '-')
>>>> +
>>>>      def qmp(self, cmd, conv_keys=True, **args):
>>>>          '''Invoke a QMP command and return the result dict'''
>>>
>>> Make that "and return the response", because that's how
>>> docs/interop/qmp-spec.txt calls the thing.
>>>
>>
>> Sure, no problem. I wanted to stress-out it's a dict and not encoded string, 
>> but it's probably given by the context.
> 
> "return the response dict" would be fine with me.
> 

Yes, I like that.

>>>>          qmp_args = dict()
>>>> -        for k in args.keys():
>>>> +        for key in args.keys():
>>>>              if conv_keys:
>>>> -                qmp_args[k.translate(self.underscore_to_dash)] = args[k]
>>>> +                qmp_args[key.translate(self.underscore_to_dash)] = 
>>>> args[key]
>>>>              else:
>>>> -                qmp_args[k] = args[k]
>>>> +                qmp_args[key] = args[key]
>>>>  
>>>>          return self._qmp.cmd(cmd, args=qmp_args)
>>>>  
>>>>      def command(self, cmd, conv_keys=True, **args):
>>>> +        '''
>>>> +        Invoke a QMP command and on success return result dict or on 
>>>> failure
>>>> +        raise exception with details.
>>>> +        '''
>>>
>>> I'm afraid "result dict" is wrong: it need not be a dict.  "on failure
>>> raise exception" adds precious little information, and "with details"
>>> adds none.
>>>
>>> Perhaps:
>>>
>>>            Invoke a QMP command.
>>>            On success return the return value.
>>>            On failure raise an exception.
>>>
>>
>> That is quite more verbose than I'd like and result in the same (for 
>> non-native speaker), but I have no strong opinion here so changing it to 
>> your version in v2.
>>
>>> The last sentence is too vague, but it's hard to do better because...
>>>
>>>>          reply = self.qmp(cmd, conv_keys, **args)
>>>>          if reply is None:
>>>>              raise Exception("Monitor is closed")
>>>
>>> ... we raise Exception!  This code is a *turd* (pardon my french), and
>>> PEP8 / pylint violations are the least of its problems.
>>>
>>
>> Yep, adding rework-exceptions to my TODO list (for a future patchset)
>>
>>>> @@ -193,15 +216,18 @@ class QEMUMachine(object):
>>>>          return events
>>>>  
>>>>      def event_wait(self, name, timeout=60.0, match=None):
>>>> -        # Test if 'match' is a recursive subset of 'event'
>>>> -        def event_match(event, match=None):
>>>> +        '''Wait for event in QMP, optionally filter results by match.'''
>>>
>>> What is match?
>>>
>>> name and timeout not worth mentioning?
>>>
>>
>> IMO this does not require full-blown documentation, it's not really a 
>> user-facing API and sometimes shorter is better. Anyway if you insist I can 
>> add full documentation of each parameter. I can even add `:type:` and 
>> `:returns:` and perhaps some `:warns:` and `:notes:` and I should descend 
>> and note all the possible `:raises:`.
>>
>> ... the point I'm trying to make is I don't think it's necessary to go that 
>> much into details. Anyway if you think the params are necessary to list, 
>> then I can do that.
> 
> Use your judgement.  The more detailed comments you add, the more
> questions you'll get ;)
> 

Sure, I'll try to improve (but not too much) that in the v6.

>>>> +        # Test if 'match' is a recursive subset of 'event'; skips branch
>>>> +        # processing on match's value `None`
>>>
>>> What's a "recursive subset"?  What's "branch processing"?
>>>
>>> There's an unusual set of quotes around `None`.
>>>
>>
>> Basically I was surprised why this function exists as one can directly 
>> compare dicts. It's because it works as the stock dict compare only on value 
>> "None" it breaks and assumes that "branch" matches. That's why I listed the 
>> example as I'm unsure how to explain it in a human language.
>>
>> As for the `None`, in sphinx/doxygen it becomes "block" so I'm used to 
>> these, anyway people use `'`s and `"`s here so I'll change it in next 
>> version to be consistent.
> 
> According to git-grep, we're using neither sphinx nor doxygen right now.
> 

yep, as I said I'll change it for `"`.

>>>> +        #    {"foo": {"bar": 1} matches {"foo": None}
>>>
>>> Aha: None servers as wildcard.
>>
>> Exactly.
>>
>>>
>>>> +        def _event_match(event, match=None):
>>>
>>> Any particular reason for renaming this function?
>>>
>>
>> To emphasize it's internal, not a strong opinion but IMO looks better this 
>> way.
> 
> It's a nested function, how could it *not* be internal?
> 

It is always internal for the computer, but humans sometimes need more 
pointers. I can drop this part if you really dislike that change but I'm used 
to this notation.

>>>>              if match is None:
>>>>                  return True
>>>>  
>>>>              for key in match:
>>>>                  if key in event:
>>>>                      if isinstance(event[key], dict):
>>>> -                        if not event_match(event[key], match[key]):
>>>> +                        if not _event_match(event[key], match[key]):
>>>>                              return False
>>>>                      elif event[key] != match[key]:
>>>>                          return False
>>>> @@ -212,18 +238,22 @@ class QEMUMachine(object):
>>>>  
>>>>          # Search cached events
>>>>          for event in self._events:
>>>> -            if (event['event'] == name) and event_match(event, match):
>>>> +            if (event['event'] == name) and _event_match(event, match):
>>>>                  self._events.remove(event)
>>>>                  return event
>>>>  
>>>>          # Poll for new events
>>>>          while True:
>>>>              event = self._qmp.pull_event(wait=timeout)
>>>> -            if (event['event'] == name) and event_match(event, match):
>>>> +            if (event['event'] == name) and _event_match(event, match):
>>>>                  return event
>>>>              self._events.append(event)
>>>>  
>>>>          return None
>>>>  
>>>>      def get_log(self):
>>>> +        '''
>>>> +        After self.shutdown or failed qemu execution this returns the 
>>>> output
>>>
>>> Comma after execution, please.
>>>
>>
>> OK
>>
>>>> +        of the qemu process.
>>>> +        '''
>>>>          return self._iolog
>>>
>>> I understand this code was factored out of qemu-iotests for use by
>>> qtest.py.  That's okay, but I'd rather not serve as its maintainer.
>>> -E2MUCH...
>>>
>>
>> Yep, anyway it contains several useful methods/helpers and fixing basic 
>> issues is the simplest way to get to know the code (and the submitting 
>> process). Thank you for your time.
> 
> You're welcome!
> 

Thanks again,
Lukáš

Attachment: signature.asc
Description: OpenPGP digital signature


reply via email to

[Prev in Thread] Current Thread [Next in Thread]