qemu-devel.nongnu.org archive mirror
 help / color / mirror / Atom feed
From: John Snow <jsnow@redhat.com>
To: Max Reitz <mreitz@redhat.com>, qemu-block@nongnu.org
Cc: Kevin Wolf <kwolf@redhat.com>, qemu-devel@nongnu.org
Subject: Re: [PATCH 04/67] iotests.py: create_test_image, remove_test_image
Date: Wed, 2 Oct 2019 20:35:54 -0400	[thread overview]
Message-ID: <c9bf1dca-80c5-7271-2269-216338ef6405@redhat.com> (raw)
In-Reply-To: <8604aec2-2a09-7bcc-792a-9e11e8b6fb11@redhat.com>



On 10/2/19 7:00 AM, Max Reitz wrote:
> On 02.10.19 01:20, John Snow wrote:
>>
>>
>> On 10/1/19 3:46 PM, Max Reitz wrote:
>>> Python tests should use these two new functions instead of
>>> qemu_img('create', ...) + os.remove(), so that user-supplied image
>>> options are interpreted and handled correctly.
>>>
>>> Signed-off-by: Max Reitz <mreitz@redhat.com>
>>> ---
>>>  tests/qemu-iotests/iotests.py | 56 +++++++++++++++++++++++++++++++++++
>>>  1 file changed, 56 insertions(+)
>>>
>>> diff --git a/tests/qemu-iotests/iotests.py b/tests/qemu-iotests/iotests.py
>>> index b5ea424de4..fce1ab04c9 100644
>>> --- a/tests/qemu-iotests/iotests.py
>>> +++ b/tests/qemu-iotests/iotests.py
>>> @@ -122,6 +122,62 @@ def qemu_img_create(*args):
>>>  
>>>      return qemu_img(*args)
>>>  
>>> +def create_test_image(filename, size=None, fmt=imgfmt, opts=[],
>>> +                      backing_file=None, backing_fmt=None,
>>> +                      objects=[], unsafe=False):
>>
>> Python! It's the language that everybody loves and can do no wrong!
>>
>> Ah, wait, no, maybe the opposite.
>>
>> You want this:
>>
>> (..., opts=None, ...):
>>     opts = opts or []
>>
>> because, unfortunately, default parameters are bound at definition time
>> and not at call time, so the default list here is like a static local.
> 
> OK.  Interesting.
> 
> I suppose the same goes for @objects, then.
> 

It is by far the WORST thing about Python.

I realize we use this pattern a few places in iotests, but I think it's
also usually where we don't modify the list, so it's actually OK, but
serves as an example of a bad habit.

>>> +    if fmt == imgfmt:
>>> +        # Only use imgopts for the default format
>>> +        opts = imgopts + opts
>>> +
>>> +    for i, opt in enumerate(opts):
>>> +        if '$TEST_IMG' in opt:
>>> +            opts[i] = opt.replace('$TEST_IMG', filename)
>>> +
>>> +    # default luks support
>>> +    if fmt == 'luks':
>>> +        if not any('key-secret' in opt for opt in opts):
>>
>> You can write "if not 'key-secret' in opts"
> 
> Oh, that’s recursive?
> 

No, I was just mistaken about the shape of the data.
You are looking for 'key-secret=XXX', I was thinking that there was a
token that was really just 'key-secret'.

What you wrote is correct and good and I am wrong and bad.

>>> +            opts.append(luks_default_key_secret_opt)
>>
>> And here we might modify that default list.
>>
>>> +        objects.append(luks_default_secret_object)
>>> +
>>> +    args = ['create', '-f', fmt]
>>> +
>>> +    if len(opts) > 0:
>>> +        args += ['-o', ','.join(opts)]
>>> +
>>> +    if backing_file is not None:
>>> +        args += ['-b', backing_file]
>>> +
>>> +    if backing_fmt is not None:
>>> +        args += ['-F', backing_fmt]
>>> +
>>> +    if len(objects) > 0:
>>> +        # Generate a [['--object', $obj], [...], ...] list and flatten it
>>> +        args += [arg for objarg in (['--object', obj] for obj in objects) \
>>> +                     for arg in objarg]
>>
>> I may have mentioned at one point that I love comprehensions, but
>> dislike nested comprehensions.
> 
> I can’t remember but I do remember writing this piece of code, being sad
> that there is no .flatten, and wanting everyone to see the monster that
> arises.
> 
>> At this point, I think it's far simpler
>> to say:
>>
>> for obj in objects:
>>     args.extend(['--object', obj])
>>
>> or, even shorter:
>>     args += ['--object', obj]
> 
> OK, so now you saw it, I’m glad to make the flattening more flattering
> to read.
> 

I am sorry I ever mentioned liking Python. I will accept your punishments.

>>> +
>>> +    if unsafe:
>>> +        args.append('-u')
>>> +
>>> +    args.append(filename)
>>> +    if size is not None:
>>> +        args.append(str(size))
>>> +
>>> +    return qemu_img(*args)
>>> +
>>> +# Use this to remove images create with create_test_image in the
>>
>> created
>>
>> and you might as well move the # comment to a """docstring""" while
>> you're here.
>>
>>> +# default image format (iotests.imgfmt)
>>> +def remove_test_image(filename):
>>> +    try:
>>> +        os.remove(filename)
>>> +
>>> +        data_file = next(opt.replace('data_file=', '') \
>>> +                            .replace('$TEST_IMG', filename) \
>>> +                         for opt in imgopts if opt.startswith('data_file='))
>>> +
>>
>> Learned something today: you can use next() to get the first value from
>> a generator expression.
> 
> I was sad for a bit that Python doesn’t have a find(), but then I
> noticed this works as well.  (Already used extensively in “iotests: Add
> VM.assert_block_path()” from my “block: Fix check_to_replace_node()”
> series.)
> 

I honestly tried to rewrite this a few times because it looks so chunky,
but realized there isn't ... a great way to do this without implying
that you might find more than one result.

You can filter to a new list and assert that the length is one, but
that's not less chunky.

>>> +        os.remove(data_file)
>>
>> Keep in mind that if the generator expression returns no results, that
>> next() will throw an exception and we won't make it here. That's ok, but,
> 
> I did.  If there are no results, it’s good we won’t get here.
> 
> This code would be wrong if the next() didn’t throw an exception.
> 

It just wasn't clear, because the except is doing the lifting for both
the remove and the finding.

Oh well, it's not really important.

>>> +    except:
>>> +        pass
>>> +
>>
>> The unqualified except doesn't help me know which errors you expected
>> and which you didn't.
> 
> What I’m expecting: FileNotFound, StopIteration.
> 
> But the thing is that I feel like maybe removing a file should always
> pass, regardless of the exact exception.  (I can imagine to be wrong.)
> 

I wonder if that's true ... I just don't know what the full set of
errors we might get are. I don't really like exception driven code,
honestly.

"It's wrong to catch ANY exception because you might suppress errors too
broadly."

"It's wrong to be too specific, because you'll miss cases you meant to
catch."

Awful.

Anyway, like I said I was just being fiddly because I found this odd to
read, but really don't have suggestions that are clearly nicer, so ...
carry on.

>> We have a function like this elsewhere in the python directory:
>>
>> def remove_if_exists(filename):
>>     try:
>>         os.remove(filename)
>>     except FileNotFoundError:
>>         pass
> 
> We do?  I can’t find it.  I find a _remove_if_exists in machine.py,
> which I’m not sure whether it’s supposed to be used outside, and it
> works a bit different, actually (but probably to the same effect).
> 

Yeah, that's the one. Don't worry about plucking it out here for this,
just nothing that we do this in a few places. We might want a util
eventually that gets it exactly right.

Or not, because what's "exactly right" anyway. Ah, ah, ah.

>> Can we use that here and remove the try:/except: from this function? It
>> will require you to change the list search to something like this instead:
>>
>> remove_if_exists(filename)
>> for opt in (x for x in imgopts if etc):
>>     data_file = opt.replace('etc', 'etc')
>>     remove_if_exists(data_file)
>>
>> to avoid the exception when you call next().
> 
> I don’t know why I’d avoid the exception, though.
> 
> This is probably because I don’t like pythonic code, again, but I prefer
> a next() + exception over a for loop that just iterates once or not at all.
> 

Nah, Python people LOVE exceptions. They don't like "bare except"
statements, though. I am the weird person in that I like to avoid
exceptions whenever it's elegant and pretty to do so.

I find exceptions as normal control flow to be quite hard to deal with;
but Pythonistas seem to love it.

>>>  def qemu_img_verbose(*args):
>>>      '''Run qemu-img without suppressing its output and return the exit code'''
>>>      exitcode = subprocess.call(qemu_img_args + list(args))
>>>
>>
>> My fussiness with the remove() function is just optional picky stuff,
>> but the rest matters, I think.
> 
> OK.  Indeed it does!
> 
> Max
> 


  reply	other threads:[~2019-10-03  0:36 UTC|newest]

Thread overview: 81+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2019-10-01 19:46 [PATCH 00/67] iotests: Honor $IMGOPTS in Python tests Max Reitz
2019-10-01 19:46 ` [PATCH 01/67] iotests.py: Read $IMGOPTS Max Reitz
2019-10-01 22:16   ` John Snow
2019-10-03 15:03     ` Vladimir Sementsov-Ogievskiy
2019-10-03 15:08   ` Vladimir Sementsov-Ogievskiy
2019-10-01 19:46 ` [PATCH 02/67] iotests.py: Add @skip_for_imgopts() Max Reitz
2019-10-01 22:16   ` John Snow
2019-10-03 15:19   ` Vladimir Sementsov-Ogievskiy
2019-10-04 12:55     ` Max Reitz
2019-10-01 19:46 ` [PATCH 03/67] iotests.py: Add unsupported_imgopts Max Reitz
2019-10-01 22:18   ` John Snow
2019-10-03 15:21   ` Vladimir Sementsov-Ogievskiy
2019-10-01 19:46 ` [PATCH 04/67] iotests.py: create_test_image, remove_test_image Max Reitz
2019-10-01 23:20   ` John Snow
2019-10-01 23:30     ` John Snow
2019-10-02 11:00     ` Max Reitz
2019-10-03  0:35       ` John Snow [this message]
2019-10-01 19:46 ` [PATCH 05/67] iotests.py: Add ImagePaths Max Reitz
2019-10-01 19:46 ` [PATCH 06/67] iotests.py: Add image_path() Max Reitz
2019-10-01 19:46 ` [PATCH 07/67] iotests.py: Filter data_file in filter_img_info Max Reitz
2019-10-01 19:46 ` [PATCH 08/67] iotests.py: Add filter_json_filename() Max Reitz
2019-10-01 19:46 ` [PATCH 09/67] iotests.py: Add @hide_fields to img_info_log Max Reitz
2019-10-01 19:46 ` [PATCH 10/67] iotests/169: Skip persistent cases for compat=0.10 Max Reitz
2019-10-01 19:46 ` [PATCH 11/67] iotests/224: Filter json:{} from commit command Max Reitz
2019-10-01 19:46 ` [PATCH 12/67] iotests/228: Filter json:{} filenames Max Reitz
2019-10-01 19:46 ` [PATCH 13/67] iotests/242: Hide refcount bit information Max Reitz
2019-10-01 19:46 ` [PATCH 14/67] iotests: Use unsupported_imgopts in Python tests Max Reitz
2019-10-01 19:46 ` [PATCH 15/67] iotests/030: Honor $IMGOPTS Max Reitz
2019-10-01 19:46 ` [PATCH 16/67] iotests/040: " Max Reitz
2019-10-01 19:46 ` [PATCH 17/67] iotests/041: " Max Reitz
2019-10-01 19:46 ` [PATCH 18/67] iotests/044: " Max Reitz
2019-10-01 19:46 ` [PATCH 19/67] iotests/045: " Max Reitz
2019-10-01 19:46 ` [PATCH 20/67] iotests/055: " Max Reitz
2019-10-01 19:46 ` [PATCH 21/67] iotests/056: " Max Reitz
2019-10-01 19:46 ` [PATCH 22/67] iotests/057: " Max Reitz
2019-10-01 19:46 ` [PATCH 23/67] iotests/065: " Max Reitz
2019-10-01 19:46 ` [PATCH 24/67] iotests/096: " Max Reitz
2019-10-01 19:46 ` [PATCH 25/67] iotests/118: " Max Reitz
2019-10-01 19:46 ` [PATCH 26/67] iotests/124: " Max Reitz
2019-10-01 19:46 ` [PATCH 27/67] iotests/129: " Max Reitz
2019-10-01 19:46 ` [PATCH 28/67] iotests/132: " Max Reitz
2019-10-01 19:46 ` [PATCH 29/67] iotests/139: " Max Reitz
2019-10-01 19:46 ` [PATCH 30/67] iotests/147: " Max Reitz
2019-10-01 19:46 ` [PATCH 31/67] iotests/148: " Max Reitz
2019-10-01 19:46 ` [PATCH 32/67] iotests/151: " Max Reitz
2019-10-01 19:46 ` [PATCH 33/67] iotests/152: " Max Reitz
2019-10-01 19:46 ` [PATCH 34/67] iotests/155: " Max Reitz
2019-10-01 19:46 ` [PATCH 35/67] iotests/163: " Max Reitz
2019-10-01 19:46 ` [PATCH 36/67] iotests/165: " Max Reitz
2019-10-01 19:46 ` [PATCH 37/67] iotests/169: " Max Reitz
2019-10-01 19:46 ` [PATCH 38/67] iotests/194: " Max Reitz
2019-10-01 19:46 ` [PATCH 39/67] iotests/196: " Max Reitz
2019-10-01 19:46 ` [PATCH 40/67] iotests/199: " Max Reitz
2019-10-01 19:46 ` [PATCH 41/67] iotests/202: " Max Reitz
2019-10-01 19:46 ` [PATCH 42/67] iotests/203: " Max Reitz
2019-10-01 19:46 ` [PATCH 43/67] iotests/205: " Max Reitz
2019-10-01 19:46 ` [PATCH 44/67] iotests/208: " Max Reitz
2019-10-01 19:46 ` [PATCH 45/67] " Max Reitz
2019-10-01 19:46 ` [PATCH 46/67] iotests/216: " Max Reitz
2019-10-01 19:46 ` [PATCH 47/67] iotests/218: " Max Reitz
2019-10-01 19:46 ` [PATCH 48/67] iotests/219: " Max Reitz
2019-10-01 19:46 ` [PATCH 49/67] iotests/222: " Max Reitz
2019-10-01 19:46 ` [PATCH 50/67] iotests/224: " Max Reitz
2019-10-01 19:46 ` [PATCH 51/67] iotests/228: " Max Reitz
2019-10-01 19:47 ` [PATCH 52/67] iotests/234: " Max Reitz
2019-10-01 19:47 ` [PATCH 53/67] iotests/235: " Max Reitz
2019-10-01 19:47 ` [PATCH 54/67] iotests/236: " Max Reitz
2019-10-01 19:47 ` [PATCH 55/67] iotests/237: " Max Reitz
2019-10-01 19:47 ` [PATCH 56/67] iotests/242: " Max Reitz
2019-10-01 19:47 ` [PATCH 57/67] iotests/245: " Max Reitz
2019-10-01 19:47 ` [PATCH 58/67] iotests/246: " Max Reitz
2019-10-01 19:47 ` [PATCH 59/67] iotests/248: " Max Reitz
2019-10-01 19:47 ` [PATCH 60/67] iotests/254: " Max Reitz
2019-10-01 19:47 ` [PATCH 61/67] iotests/255: " Max Reitz
2019-10-01 19:47 ` [PATCH 62/67] iotests/256: " Max Reitz
2019-10-01 19:47 ` [PATCH 63/67] iotests/257: " Max Reitz
2019-10-01 19:47 ` [PATCH 64/67] iotests/258: " Max Reitz
2019-10-01 19:47 ` [PATCH 65/67] iotests/262: " Max Reitz
2019-10-01 19:47 ` [PATCH 66/67] iotests.py: Forbid qemu_img*('create', ...) Max Reitz
2019-10-01 19:47 ` [PATCH 67/67] iotests.py: Drop qemu_img_log(), qemu_img_create() Max Reitz
2019-10-01 21:47 ` [PATCH 00/67] iotests: Honor $IMGOPTS in Python tests John Snow

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=c9bf1dca-80c5-7271-2269-216338ef6405@redhat.com \
    --to=jsnow@redhat.com \
    --cc=kwolf@redhat.com \
    --cc=mreitz@redhat.com \
    --cc=qemu-block@nongnu.org \
    --cc=qemu-devel@nongnu.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).