Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Selectively skip doctest #1251

Closed
wants to merge 3 commits into from

Conversation

timokau
Copy link

@timokau timokau commented Jan 20, 2019

The intention of this is to make it possible to test pwntools on systems where some functionality is known not to work. This makes use of a new sphinx feature to selectively disable doctests. In particular, doctests that require binutils/qemu for a specific architecture, doctests that require internet access and doctests that depend on specific machine setup (marked travis) can be skipped.

This is a work in progress. Currently all flags are hardcoded as False, instead they should default to True and be changeable through flags or environment variables. But it should be ready for general feedback on the main idea. Is this something you'd in principle be willing to consider @zachriggle?

My use-case for this is automated testing for distribution packaging. Another usecase would be a possibility for contributers to run at least some of the testsuite locally without having the full setup that is available on travis.

There seems to be no reason for this and it breaks the testsuite with a
never gdb version. The lower limit may have a reason (I haven't tested)
and suffices as a test for the version function.
Introduce binutils_arch and qemu_arch options to set weather or not the
system can analyze/build and execute binaries for arch. Also introduces
the `travis` option for various assumptions that are true on travis.
@zachriggle
Copy link
Member

This is pretty neat! One recommendation that I might suggest is that there should not be a need to modify every single shellcraft/**/foo.py to have this block.

We should be able to add a conditional to the .rst files in docs/ that achieve the same affect without being so very verbose (and prone to future maintenance errors).

Separately, if it's at all possible I would prefer to have dicts instead of multiple snake_case variables. For example skip if test_qemu['arm'] instead of skip if not qemu_arm. This should let us be more flexible and set module-level variables to be used, e.g. in arm.rst we can set:

skip_binutils = skip_binutils or not have_binutils['arm']

This would allow us to use skip_binutils everywhere, instead of having per-architecture details spread throughout the test metadata. It would just be set to the "right" value by the .rst file.

@zachriggle
Copy link
Member

zachriggle commented Jan 21, 2019

Following up further, it may also be useful to create our own doctest_xyz Sphinx directive / class / decorator / whatever the correct term is.

This would allow us to have more streamlined docs, e.g.

.. doctest::
   :skipif: not binutils_arm

vs

.. doctest_binutils("arm"):
# or if we use a dict as per above
.. doctest_binutils['arm']:
# or, possibly if we can auto-detect the architecture from a module-level variable
.. doctest_binutils:

Finally, in order to accept this pull request I think we need an update to the Travis CI test matrix / test setup stuff which actually ensures that all of these configurations bits work. We don't need every combination, but at least the following:

  • No foreign-architecture binutils at all
  • Each individual binutils foreign architecture
  • All binutils architecture but no qemu
  • All qemu architecture but no binutils

@zachriggle
Copy link
Member

One very final request would be that we should add a standard way for end-users of pwntools (and our own internal use) to determine at-runtime what toolchains are available.

For example, pwnlib.asm.supported(arch=None) will return whether we are able to assemble code for the provided (or, default to context.arch) architecture.

@timokau
Copy link
Author

timokau commented Jan 21, 2019

This is pretty neat!

I'm great there is a positive general sentiment :)

One recommendation that I might suggest is that there should not be a need to modify every single shellcraft/**/foo.py to have this block.

I agree, that also felt a little wrong while doing it. It does have the advantage of explicitness and consistency, but that is maybe not worth it.

We should be able to add a conditional to the .rst files in docs/ that achieve the same affect without being so very verbose (and prone to future maintenance errors).

I wasn't sure if that is possible (my experience with sphinx is very limited). Not sure how :skipif: works together with automodule.

Separately, if it's at all possible I would prefer to have dicts instead of multiple snake_case variables. For example skip if test_qemu['arm'] instead of skip if not qemu_arm.

That should be a trivial change.

This should let us be more flexible and set module-level variables to be used, e.g. in arm.rst we can set:

skip_binutils = skip_binutils or not have_binutils['arm']

This would allow us to use skip_binutils everywhere, instead of having per-architecture details spread throughout the test metadata. It would just be set to the "right" value by the .rst file.

I'm not sure about that one. I see little benefit of skip_binutils vs. test_binutils['arm']. skip_binutils wouldn't work in every case (since there may be a single test using mips in the arm file, other files may not have a sensible default at all) so this only increases the amount of stuff a developer has to remember. I don't feel strongly about this however.

@timokau
Copy link
Author

timokau commented Jan 21, 2019

Following up further, it may also be useful to create our own doctest_xyz Sphinx directive / class / decorator / whatever the correct term is.

That may be neat, I didn't know that was possible. I don't think .. doctests_binutils['arm'] vs. .. doctest:: :skipif no test_binutils['arm'] make that much of a difference though. I'm fine with either.

@timokau
Copy link
Author

timokau commented Jan 21, 2019

Also having it in the test matrix would be amazing! Is there no problem with compute resources if most tests need to be run 4 times?

@timokau
Copy link
Author

timokau commented Jan 21, 2019

One very final request would be that we should add a standard way for end-users of pwntools (and our own internal use) to determine at-runtime what toolchains are available.

For example, pwnlib.asm.supported(arch=None) will return whether we are able to assemble code for the provided (or, default to context.arch) architecture.

That could basically reduce to whatever context.arch already does when I set it to an invalid architecture right? Not sure if that is in-scope of this PR though.

@zachriggle
Copy link
Member

One very final request would be that we should add a standard way for end-users of pwntools (and our own internal use) to determine at-runtime what toolchains are available.
For example, pwnlib.asm.supported(arch=None) will return whether we are able to assemble code for the provided (or, default to context.arch) architecture.

That could basically reduce to whatever context.arch already does when I set it to an invalid architecture right? Not sure if that is in-scope of this PR though.

You can set context.arch to any valid value, regardless of whether there is (1) a toolchain to assemble shellcode or (2) QEMU support for running binaries of that architecture.

@zachriggle
Copy link
Member

Also having it in the test matrix would be amazing! Is there no problem with compute resources if most tests need to be run 4 times?

It's not a huge deal as long as it's restricted to the stable and beta branches. Doing it on dev would indeed take far too long.

@timokau
Copy link
Author

timokau commented Jan 21, 2019

You can set context.arch to any valid value, regardless of whether there is (1) a toolchain to assemble shellcode or (2) QEMU support for running binaries of that architecture.

Oh, I thought that would check for a valid binutils. But I just re-tried. Must have misremembered.

@timokau
Copy link
Author

timokau commented Jan 21, 2019

After quite some digging into sphinx, the only way to skip the shellcraft tests without touching every file is skipping them completely (when in doctest mode) by (ab-)using autodoc's autodoc-skip-member function.

Feels pretty hacky. What do you think? Did you have something specific in mind when you made that suggestion?

@@ -199,7 +211,7 @@ ELF Manipulation

Stop hard-coding things! Look them up at runtime with :mod:`pwnlib.elf`.

>>> e = ELF('/bin/cat')
>>> e = ELF('/bin/cat') # doctest: +SKIP
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are we skipping this?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because all the follow-up tests were already skipped.

@@ -211,6 +223,9 @@ Stop hard-coding things! Look them up at runtime with :mod:`pwnlib.elf`.

You can even patch and save the files.

.. doctest::
:skipif: not travis
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why would we skip these if we're not on Travis?

@@ -192,6 +192,9 @@ class Thread(threading.Thread):

Examples:

.. doctest::
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is no need to skip this. We're not actually using binutils here.

@@ -287,6 +290,9 @@ class ContextType(object):

Examples:

.. doctest::
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe that this will break the formatting under "Examples". Have you looked at the output of make -C docs html to see if the output changes or additional formatting warnings are generated?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I haven't yet. According to sphinx doc,

Some text

>>> print("Inline example")
Inline example

Is equivalent to

Some text

.. doctest::

   >>> print("some inline example")
   some inline example

Examples: (FIXME)

.. doctest::
:skipif: True
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are we skipping this? Is the test broken?

@@ -90,6 +90,9 @@ def archname():
Returns the name which QEMU uses for the currently selected
architecture.

.. doctest::
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are we skipping this test here? And only for powerpc? It's doing a dict lookup.

>>> pwnlib.qemu.user_path(arch='thumb')
'qemu-arm-static'
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No.

@@ -398,6 +413,9 @@ class ROP(object):
0x0078: 0x2b ss
0x007c: 0x0 fpstate

.. doctest::
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why have you broken this into multiple doctest blocks?

They are all part of the same example, AND they all have the same requirements. Separately, binutils_i386 and qemu_i386 should not exist at all. We always assume that Pwntools is running from an amd64 machine which has binutils installed, and can execute i386 binaries natively.

amd64 is the ONLY supported architecture to run Pwntools from.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It already was multiple doctest blocks. Empty lines are interpreted as a block separator.

binutils_thumb=False
qemu_mips=False
qemu_arm=False
qemu_amd64=False
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All instances of {qemu,binutils}_{i386,amd64} should be removed entirely. These are always available. If they are not available, you're running Pwntools from an unsupported machine type and the tests are not expected to work.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They are not always available. The intention is to be able to verify that basic pwntools functionality works as intended, even if for example no mips binutils are available.

Are multiple people using this account? I feel like your comments contradict your previous sentiment.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please re-read my comment. It does not refer to MIPS binutils, but specifically the i386 and amd64 binutils and QEMU.

No valid testing environment exists where you cannot execute i386 and amd64 binaries, as a requirement for using Pwntools is to use amd64 as the host architecture. Other architectures may work, but it is not worth the support burden to the maintenance team to ensure they work.

Separately, in a testing environment, why is the MIPS toolchain not available? We have the ability to install any packages we want.

This is my personal account. I am currently the only Pwntools maintainer.

qemu_aarch64=False
qemu_powerpc=False
qemu_thumb=False
travis=False
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand the intent of not travis. The only supported environments for running the Pwntools tests are:

1.) From the included Docker image, which sets up the environment similar to Travis. See travis/Docker or just run it: make -C travis/docker ANDROID=no.
2.) On Travis itself.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is poorly named. It is used when certain binaries are expected to be in certain locations. It should be named not fhs or something similar instead.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When will e.g. /bin/sh not exist at /bin/sh? There are lots of situations that I can think of, but none for which we should be bending our tests.

@zachriggle
Copy link
Member

zachriggle commented Jan 22, 2019

Overall I disagree with the intent implementation of this Pull Request. See TESTING.md and travis/Docker/readme.md for the correct way to run tests not-on-Travis CI.

Separately, the support burden is pretty high here -- much higher than just using Docker to run the tests.

Finally, you can run individual module tests by specifying them in the same way we do for the Docker image. Again, see travis/Docker for more information.

If there is interest in supporting vendor packaging without using Travis, I recommend creating a Dockerfile which has all of the appropriate dependencies in the same way as travis/Docker. This will allow you to perform whatever setup you need, without pushing the support burden onto the Pwntools maintenance team.

@zachriggle zachriggle closed this Jan 22, 2019
@timokau
Copy link
Author

timokau commented Jan 22, 2019

I'm surprised. What happened in between

This is pretty neat!

and

Overall I disagree with the intent implementation of this Pull Request. See TESTING.md and travis/Docker/readme.md for the correct way to run tests not-on-Travis CI.

?

@timokau
Copy link
Author

timokau commented Jan 22, 2019

Separately, the support burden is pretty high here -- much higher than just using Docker to run the tests.

That goes against the point of testing weather or not pwntools works in a particular environment though. It also won't solve the networking issue.

@timokau
Copy link
Author

timokau commented Jan 22, 2019

Regarding (nearly) all of your inline comments: This was not ready for detailed review. I posted a WIP to get general feedback before investing more time into this. Lots of hacky parts to get the distro package to test successfully.

@zachriggle
Copy link
Member

zachriggle commented Jan 23, 2019

I'm surprised. What happened in between

This is pretty neat!

and

Overall I disagree with the intent implementation of this Pull Request. See TESTING.md and travis/Docker/readme.md for the correct way to run tests not-on-Travis CI.

?

I re-read the reason for the pull request -- which is to support testing on some environment that isn't a Ubuntu LTS release:

My use-case for this is automated testing for distribution packaging.

My initial hot take was to support running tests in an environment where e.g. qemu-user is simply not possible (e.g. on Darwin / macOS).

For any Linux distribution, it is infinitely easier to install all of the appropriate binutils and QEMU, and ensure network connectivity -- than to force a Python package's test infrastructure to support the self-elected limitations of the testing environment.

Neat!: Selectively disabling tests for where they're impossible to run
Burden: Making tests more complicated in order to support some restricted testing environment.

Another usecase would be a possibility for contributers to run at least some of the testsuite locally without having the full setup that is available on travis.

See TESTING.md.

@timokau
Copy link
Author

timokau commented Apr 18, 2019

I know this has been a while, but anyways.

I re-read the reason for the pull request -- which is to support testing on some environment that isn't a Ubuntu LTS release:

I just wanted to say that I don't think you handled that optimally. I intentionally posted a very early version first to ask for feedback. I did that because I wasn't sure if the work would be welcome upstream. After very positive feedback, I invested a lot of time into improving this PR, just for it to suddenly get shot down. That doesn't make me enjoy contributing very much.

I know this probably wasn't your intention, which is why I'm providing feedback for the future :)

For any Linux distribution, it is infinitely easier to install all of the appropriate binutils and QEMU, and ensure network connectivity -- than to force a Python package's test infrastructure to support the self-elected limitations of the testing environment.

I don't agree with this. First the point of distro testing is making sure that the version of the package the user actually installs is working as expected. That will usually not include all binutils & QEMU dependencies. Second internet based tests are always problematic, as they may fail for various reasons and depend on external services.

I won't push this further, just giving my opinion.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants