[Avocado-devel] RFC: avocado.utils.process (or, how to handle the split stdout/stderr recording)

Amador Pahim apahim at redhat.com
Thu Nov 9 15:39:14 UTC 2017


On Thu, Nov 9, 2017 at 12:54 PM, Cleber Rosa <crosa at redhat.com> wrote:
>
>
> On 11/09/2017 03:38 AM, Amador Pahim wrote:
>> On Wed, Nov 8, 2017 at 4:01 PM, Cleber Rosa <crosa at redhat.com> wrote:
>>> Hi everyone,
>>>
>>> This is kind of brainstorm about the really annoying issue we've trying
>>> to deal with in Avocado.  The root problem we're trying to fix is that
>>> Avocado's "output check" feature can not be used when the test's
>>> generated content combines both stdout and stderr in a single file.  One
>>> example is QEMU's iotests[1].
>>>
>>> The root cause of this limitation is that avocado.utils.process.run()[2]
>>> (because of avocado.utils.process.SubProcess[3]) decided a long time ago
>>> to split the content of a process STDOUT and STDERR to separate files
>>> (and different logging streams).  This is reflected on Avocado's "output
>>> check" feature[4].  Users can choose to record the test's generated
>>> STDOUT, STDERR, both (all) or none of them.  This is how the command
>>> line options look like:
>>>
>>>   --output-check-record {none,all,stdout,stderr}
>>>                Record output streams of your tests to reference files
>>>                (valid options: none (do not record output streams),
>>>                all (record both stdout and stderr), stdout (record
>>>                only stderr), stderr (record only stderr). Current:
>>>                none
>>>
>>> Which map to the following recorded files:
>>>
>>> +----------+----------------------------+--------------------+
>>> | OPTION   | RECORDED FILES             | CONTENT            |
>>> +----------+----------------------------+--------------------+
>>> | none     |                            |                    |
>>> +----------+----------------------------+--------------------+
>>> |          | stdout                     | process FD 1       |
>>> + all      +----------------------------+--------------------+
>>> |          | stderr                     | process FD 2       |
>>> +----------+----------------------------+--------------------+
>>> | stdout   | stdout                     | process FD 1       |
>>> +----------+----------------------------+--------------------+
>>> | stderr   | stderr                     | process FD 2       |
>>> +----------+----------------------------+--------------------+
>>>
>>> Notice how the "all" option still creates two separate files, with
>>> content coming from individual file descriptors.  Adding another record
>>> option would actually be pretty simple, that is, something like:
>>>
>>> +----------+----------------------------+--------------------+
>>> | OPTION   | RECORDED FILES             | CONTENT            |
>>> +----------+----------------------------+--------------------+
>>> | combined | combined_stdout_stderr     | process FD 1 and 2 |
>>> +----------+----------------------------+--------------------+
>>>
>>> What is *not* easily possible, is to have two options such as "all" and
>>> "combined" at the same time.  The reason is that it the order of the
>>> content written to "combined_stdout_stderr" can not be guaranteed to be
>>> correct.
>>>
>>> I've attempted to allow for both options to be used at once, by using a
>>> pair of file-like objects with specialized writes that would either
>>> share single list of records or just share a secondary file with a lock
>>> (pseudo/prototype code):
>>>
>>> class TeeFile(file):
>>>     def __init__(self, path, tee_path, tee_lock, mode='a'):
>>>         super(TeeFile, self).__init__(path, mode)
>>>         self._tee_path = tee_path
>>>         self._tee_lock = tee_lock
>>>
>>>     def write(self, record):
>>>         self.tee_lock.acquire()
>>>         super(TeeFile, self).write(record)
>>>         with open(self._tee_path, 'a') as tee_file:
>>>             tee_file.write(record)
>>>             tee_file.flush()
>>>         self.tee_lock.release()
>>>
>>> But the fact that the Python standard library "subprocess.Popen"
>>> implementation works with file descriptors doesn't make it possible to
>>> implement a shared/synchronized write strategy.  Rewriting parts of
>>> "subprocess.Popen" or "avocado.utils.process.SubProcess" is, IMO, a
>>> clear sign to not attempt something way more complicated and error prone
>>> for the sake of this feature.
>>
>> Agreed. You're trying to control from above something handled many layers below.
>> I don't see a problem in having a mutual exclusive behavior, as long
>> as we have clear documentation on that.
>>
>>>
>>> Accepting the fact that either combined (stdout+stderr) or a split
>>> (stdout, stderr) will be available, the point of option names comes into
>>> play.  Both "stdout", "stderr" and "none" are aptly named.  But, with
>>> the introduction of a third *real* output record option/file, "all"
>>> becomes a rather bad option name.
>>>
>>> To keep things simple, I'd add an alias from "all" to "both",
>>> deprecating the former.  The new option would be named either "combined"
>>> and the generated file named "combined_stdout_stderr" by default.  It's
>>> a rather verbose name, but I'd go with explicit option names rather than
>>> confusing ones.
>>
>> For the usability, "both" in a list with more than 2 other options,
>> even though clear enough in this case, seems essentially wrong.
>> I'd simply deprecate/drop "all" and allow "--output-check-record=stdout,stderr".
>>
>> In this case, the new list of options, after the deprecation, would be:
>>
>> {none,stdout,stderr,combined}
>>
>> Where "stdout" and "stderr" can be used together and "combined" can
>> only be used alone.
>>
>
> I liked your proposal, until I thought of
> avocado.utils.process.SubProcess API which backs this feature and is
> closely related to it.  The "allow_output_check" parameter takes a "str"
> parameter, so providing a set of options isn't natural.
>
> While we can change the API, say to take a list and implement the same
> exclusion logic, it'd steer away from that.  One even worse option is to
> keep the "str" parameter type, and allow for values such as "stdout,stderr".
>
> So, my counter proposal is to name the both the API and command line
> options:
>
> {none,stdout,sterr,combined,split}
>
> How does that sound?

Well, "both" > "split". Let's keep your original proposal and use "both" :)

>
> - Cleber.
>
>>>
>>> Comments?
>>>
>>> --
>>>
>>> [1] -
>>> https://git.qemu.org/?p=qemu.git;a=blob;f=tests/qemu-iotests/check;h=e6b6ff7a047562099c2fb5f0fd35652780c9c006;hb=HEAD#l757
>>> [2] -
>>> http://avocado-framework.readthedocs.io/en/55.0/api/utils/avocado.utils.html#avocado.utils.process.run
>>> [3] -
>>> http://avocado-framework.readthedocs.io/en/55.0/api/utils/avocado.utils.html#avocado.utils.process.SubProcess
>>> [4] -
>>> http://avocado-framework.readthedocs.io/en/55.0/WritingTests.html#test-output-check-and-output-record-mode
>>>
>>> --
>>> Cleber Rosa
>>> [ Sr Software Engineer - Virtualization Team - Red Hat ]
>>> [ Avocado Test Framework - avocado-framework.github.io ]
>>> [  7ABB 96EB 8B46 B94D 5E0F  E9BB 657E 8D33 A5F2 09F3  ]
>>>
>>>
>
> --
> Cleber Rosa
> [ Sr Software Engineer - Virtualization Team - Red Hat ]
> [ Avocado Test Framework - avocado-framework.github.io ]
> [  7ABB 96EB 8B46 B94D 5E0F  E9BB 657E 8D33 A5F2 09F3  ]
>




More information about the Avocado-devel mailing list