Example situation. Package A defines some integrat...
# general
n
Example situation. Package A defines some integration tests that need to run the binary from package B. I would expect that it would be sufficient to add a dependency on B_dist (python_distribution providing the binary) into A/tests/BUILD and the B package respectively the binary will be installed in the A/tests environment. But it seems that the required binary is not available there (call to subprocess.Popen fails). Do I need to start that binary using another approach? What would you recommend?
I can also think of starting the binary by "./pants run src/python/package_B/scripts:required_binary" within the package A's integration test script. But still I would prefer the original approach with dependency on _dist as at the time when script starts, everything is already built.
h
To clarify the role of B_dist - it's just a way of providing metadata that gets passed to
setup.py
. Depending on it doesn't do anything. If you want to depend on a published version of B then you do so like any other "3rdparty" requirement (but you have to have published it first). But I'm guessing that's not what you want? Typically since
A/tests
and
B
are in the same repo you would have
A/tests
depend on
B
as an internal dependency. That is,
A/tests
's
python_tests
should depend on B's
python_library
(not its
python_distribution
). Then the test can invoke B's code directly (rather than spawning a process to run it).
If you do still need to spawn a process to run the binary, rather than invoke B's code directly, then I'll need to think about how to achieve that. Your idea of depending on the
_dist
target is interesting, but there
isn't enough information there to know how to build the
setup.py
into a dist (for example, should it build a wheel or an sdist?)
Could you open an issue at https://github.com/pantsbuild/pants/issues/new ? We can use that to discuss the various ideas and see what we can implement that will be useful.
n
There are also unit tests where I'm using imported code... But certain integration tests where inter-process communication happens rely on spawning processes (e.g. mock of database, etc.). You are right - depending on a published version is not what I want here. These integration tests (among others) should be run for each PR/push, during development...
I will open the issue. Thanks for your feedback!
h
Would this be what you’re looking for? https://www.pantsbuild.org/v2.0/docs/rules-api-testing#approach-4-run_pants-integration-tests-for-pants This test utility allows you to run full Pants commands as an integration test. In your test, you could run ./pants setup-py for example. You could then do whatever you want with the result, including inspecting the files that were created.
n
@hundreds-father-404 Could be! Initially, I thought it is only for testing of plugins but it can probably be used to run any script within the project, right? With something like:
run_pants(["run", "src/python/package_A/script"])
Using this approach, is it possible to run multiple binaries at the same time? I will give it a try, thanks for the tip!
h
Initially, I thought it is only for testing of plugins but it can probably be used to run any script within the project, right?
Typically, it’s designed for running on synthetic test projects created via
setup_tmpdir()
. I imagine you want to run on your actual project, rather than some contrived example, right? This is where things would start to fall apart. Tests run in a chroot (tmpdir) in Pants, meaning that they will only copy over files that are known about through the
dependencies()
field. (Why? This allows us to safely run tests in parallel and remote execution). So, your test would not have access to
src/python/package_A/script
. You could put a dependency on the
python_binary
target in
src/python/package_A/script
, but that won’t do what you’re expecting. That will copy over the Python files with the source roots stripped, like
package_A/script
. It won’t copy over the raw file, and it won’t copy over the BUILD file either. So,
run_pants(["run", "src/python/package_A/script"])
would complain “No BUILD file in src/python/package_A/script”. You could work around that by declaring a
files()
target owning that script, and owning its original BUILD file. A
files()
target doesn’t strip source roots - the files get copied exactly as-is. Then, add a dependency to the
files()
target to the
python_tests()
. That works fine, and we do that for several Pants integration tests. Unless…`src/python/package_A/script` depends on a bunch of other files, like most projects do. You would need a
files()
target owning all of its transitive dependencies..which we’ve found is not feasible to do. So, my suggestion is only helpful if either a) you’re okay with creating a synthetic target, or b)
"src/python/package_A/script"
has very few dependencies. I suspect neither of those are true, unfortunately. -- Given the above, Benjy is thinking through more robust ways to do what you’re asking
n
Thanks for the exhausting info!
I imagine you want to run on your actual project, rather than some contrived example, right
Exactly. Unfortunately, scripts have quite a lot of dependencies. Looking forward to hearing about some more robust way 🙂 At the moment I had to disable integration tests. Many thanks!
I just discovered another issue. There is a script S1 depending on the package A. The S1 and A are both within my project. The S1 downloads another script S2 (that also imports something from A) and runs it as a subprocess. Unfortunately, it seems that S2 can't import from A:
Copy code
ModuleNotFoundError: No module named 'A'
I tried running S1 with ./pants run S1 as well as "./dist/S1.pex" with the same result. How can this be solved? Maybe it is just a matter of PYTHONPATH or something like that? I thought that pex is "just" virtual environment so anything that runs within it should be able to import anything that is there 🙂 This can be "solved" by deploying packages through pypi but we also need to build docker images for our scripts and it would be nice if we can just copy one pex file into each docker image. Also, being able to easily test this functionality during development would be nice.
...ModuleNotFoundError fixed by altering PYTHONPATH of the subprocess like this. Not sure if it might be useful for someone else, but it is simple as this:
Copy code
pypath = ":".join(sys.path)
myenv = os.environ.copy()
myenv["PYTHONPATH"] = pypath
PROCESS = await asyncio.create_subprocess_exec(
    script_path,
    stdin=asyncio.subprocess.PIPE,
    stdout=asyncio.subprocess.PIPE,
    stderr=asyncio.subprocess.STDOUT,
    env=myenv,