proud-dentist-22844
05/13/2021, 6:22 PM./pants test
goal) to ensure that services are running. If they are not, then it should bail with helpful instructions on setting up the developer environment.
new target: service
depend on it from python_tests target:
service(name="service-mongo", ... other metadata describing the service here ...)
python_tests(
name="tests",
dependencies=["//:service-mongo"],
)
Then, we add an @rule
with targets: Targets
in the input that filters down to the service
targets. That rule can use Process
or similar to check for that the service is running.
then rules()
would return [*collect_rules(), UnionRule(TestFieldSet, AssertServiceRequest)]
I think?
But is there a way to insert my rule so that it has to run first, before pytest? Am I in the ballpark?hundreds-father-404
05/13/2021, 6:24 PMhundreds-father-404
05/13/2021, 6:25 PMdef my_plugin(test_field_set: PytestFieldSet) -> None:
Or
def my_plugin(test_field_set: PytestFieldSet) -> PluginTestSetup:
where that's a dataclass with some fields like allowing you to add env vars into the test process, or change the argv of the processhundreds-father-404
05/13/2021, 6:27 PMsetup_py()
kwargs, the author defines an is_applicable()
function, for example: https://www.pantsbuild.org/v2.5/docs/plugins-setup-py#1-set-up-a-subclass-of-setupkwargsrequestproud-dentist-22844
05/13/2021, 6:28 PMproud-dentist-22844
05/13/2021, 6:29 PMis_applicable
method would be good in this case too, to only select test targets that require the service.hundreds-father-404
05/13/2021, 6:30 PMAs far as what the interface looks like, this is my first dive into pants plugins, so I'm still digesting.Totally fair, I think what's most helpful is sharing what you need the plugin to do, and then we can reverse engineer the interface and implement that plugin hook to unblock you (Also ack you have some comments in the other thread. Most of the Pants team is a bit scattered this week because of Pycon right now, pardon!)
proud-dentist-22844
05/13/2021, 6:32 PMpython_tests()
is actually a little odd because only certain files need those services, not all of the test files in a given directory. That might just mean that they should eventually be partitioned into separate directories based on whether they need a service or not.hundreds-father-404
05/13/2021, 6:33 PMdependencies
field to express this, you could add a field to your python_tests
target like uses_mongo=True
. Then, is_applicable()
looks for that field to be True
You can add new fields to existing targets: https://www.pantsbuild.org/v2.5/docs/target-api-extending-targetsproud-dentist-22844
05/13/2021, 6:34 PMproud-dentist-22844
05/13/2021, 6:39 PMpython_tests(..., uses=["mongo", "rabbitmq"])
and then register each service with its help message somewhere. Though python_tests(..., uses_mongo=True, uses_rabbitmq=True)
could also work I suppose. Do you think a dependencies-style list or boolean style make a difference as far as implementation simplicity is concerned?proud-dentist-22844
05/13/2021, 6:43 PMmy_plugin
examples are a @rule right?
def my_plugin(test_field_set: PytestFieldSet) -> None:
hundreds-father-404
05/13/2021, 6:44 PM./pants help
message for each service, or it's fine to bundle in a single help message.
def is_applicable(cls, tgt):
return tgt.get(UsesMongoField).value is True
vs
def is_applicable(cls, tgt):
return "mongo" in tgt.get(UsesServicesField).value
If you go with the string approach, you can use an enum for validation they're using a valid string. https://www.pantsbuild.org/v2.5/docs/target-api-new-fields#stringfieldhundreds-father-404
05/13/2021, 6:44 PMhundreds-father-404
05/13/2021, 6:45 PMYour my_plugin examples are a @rule right?Yes, they would be. (Technically an
@rule
can't return None
and would need a distinct return type, but same general idea)proud-dentist-22844
05/13/2021, 6:47 PMdef my_plugin(test_field_set: PytestFieldSet) -> PluginTestSetup:
PluginTestSetup
is the dataclass that I would populate to adjust the env vars or whatever? And then the pytest runner stuff would take that into account?hundreds-father-404
05/13/2021, 6:51 PM@rule
def my_plugin(request: PytestPluginSetupRequest) -> PytestPluginSetup:
Where both are dataclasses. What is TBD is what should go into those dataclass fields.
To start, PytestPluginSetupRequest
should probably give you access to the Target
being run against. It should also make you implement is_applicable()
.
We could start with PytestPluginSetup
having no fields, and then add new features like "plugins can set env vars" as plugin authors request itproud-dentist-22844
05/13/2021, 6:52 PMPytestPluginSetup
as empty to begin with would probably be the way to go.hundreds-father-404
05/13/2021, 6:54 PMproud-dentist-22844
05/13/2021, 6:54 PMmy_plugin
?hundreds-father-404
05/13/2021, 6:55 PMproud-dentist-22844
05/13/2021, 6:56 PMuses_mongo
would all fail if the first one fails, but only print the message once.
Or does raising in a plugin kill pants at that point?proud-dentist-22844
05/13/2021, 6:56 PMproud-dentist-22844
05/13/2021, 6:57 PMas a way to keep complexity lower and quality higheramen. Totally get that.
hundreds-father-404
05/13/2021, 7:13 PMI wonder if the results of the plugin would be cached such that any pytest that uses_mongo would all fail if the first one fails, but only print the message once.You can control the caching behavior of running a Process. It defaults to not caching failures, and you can change to always cache no matter what, or never cache. Here, you probably never want to cache because it's very side-effecty seeing if a server is running
Or does raising in a plugin kill pants at that point?Yeah, raising an exception kills Pants, the daemon too iirc
And what happens if multiple targets are run in parallel and they all raise at about the same time? lots of noise / repeated error message?Should only be a single error, one exception cancels the work of everything else
proud-dentist-22844
05/13/2021, 7:21 PMhundreds-father-404
05/13/2021, 7:23 PMPytestPluginSetup
dataclass - the hook is only intended for side effectsproud-dentist-22844
05/13/2021, 7:49 PMIf you go with the string approach, you can use an enum for validation they're using a valid string. https://www.pantsbuild.org/v2.5/docs/target-api-new-fields#stringfieldI would actually be using a
StringSequenceField
, not just a StringField
, which means valid_choices
/ enum is not available, right?
https://github.com/pantsbuild/pants/blob/2.5.x/src/python/pants/engine/target.py#L1169-L1177
https://github.com/pantsbuild/pants/blob/2.5.x/src/python/pants/engine/target.py#L1108hundreds-father-404
05/13/2021, 7:49 PMhelp
message what is validproud-dentist-22844
05/13/2021, 8:17 PMuses=[]
field to python_tests()
. It doesn't do much, but its a start 🙂proud-dentist-22844
05/13/2021, 9:03 PMhundreds-father-404
05/13/2021, 9:07 PMmongo_running
, I recommend first answering how would you do this w/o Pants, e.g. what bash command would you run? Then it becomes fairly mechanical to port to Process
proud-dentist-22844
05/13/2021, 9:09 PMhundreds-father-404
05/13/2021, 9:12 PMsubprocess.run()
? Reading file system? Reminder that rules need to avoid side effects outside of using Pants's intrinsics for those things so that caching works properlyproud-dentist-22844
05/13/2021, 9:12 PMproud-dentist-22844
05/13/2021, 9:13 PMhundreds-father-404
05/13/2021, 9:16 PM@_uncacheable_rule
instead of @rule
so that the rule never gets memoized, which allows you to do this safelyproud-dentist-22844
05/13/2021, 9:18 PMProcess
somehow make it cacheable?proud-dentist-22844
05/13/2021, 9:20 PMproud-dentist-22844
05/13/2021, 9:28 PM./pants test ...
and not for every target?
I can see how you wouldn't want that to cache across runs.hundreds-father-404
05/13/2021, 9:36 PM@rule
specifically to find if it's running or not. Your skeleton will look something like:
@dataclass(frozen=True)
class MongoStatus:
is_running: bool
@rule
async def mongo_status() -> MongoStatus:
return MongoStatus(True)
@rule
async def prep_pytest(request: ..., mongo_status: MongoStatus) -> ...:
if not mongo_status.is_running:
...
The engine will ensure that the @rule
mongo_status
only runs once.hundreds-father-404
05/13/2021, 9:38 PMdoes using Process somehow make it cacheable?It can, you decide. You don't want it cacheable though:
You can control the caching behavior of running a Process. It defaults to not caching failures, and you can change to always cache no matter what, or never cache.
Here, you probably never want to cache because it's very side-effecty seeing if a server is runningFwict, you can use that stackoverflow snippet instead so long as the
mongo_status
rule is @_uncacheable_rule
instead of @rule
hundreds-father-404
05/14/2021, 12:33 AMsetup_py()
hook does not, in part because we don't want to handle merge conflicts between conflicting implementations. We could add logic for it, but simpler to avoid the question altogether
I bias here towards saying no, start w/ only 0-1 implementations per target. Encourage plugin author to reach out if they have need for >1 impl on the same target
Thoughts as a future consumer of this API?proud-dentist-22844
05/14/2021, 12:44 AMsetup_py()
, avoiding the pain of merging makes sense. But here we're not going to modify anything, so there isn't really anything to merge.
I actually figured I would do one hook for each service so that I can keep all the instructions and service health checking in separate files. If we only do one hook, then I'd have to do some kind of dispatch pattern which isn't as clear to implement with rules.
If there is a restriction, then I think that would come into play if something can update the env vars or args of pytest, So maybe the restriction is, only one PytestPluginSetup
can be returned that changes something?hundreds-father-404
05/14/2021, 12:46 AMdigest: Digest
field to add files to the process's chroot. https://www.pantsbuild.org/v2.5/docs/python-test-goal#testing-your-packaging-pipelinehundreds-father-404
05/14/2021, 12:54 AMI'm realizing that we could refactor this nifty mechanism of Pants to use this new plugin hookWhich made me for a second wonder if this plugin hook should really love on the generic
core/goals/test.py
and apply to all test runners, rather than be Pytest specific...
But I talked myself out of it. I think it's useful to have a small surface area here, so that you don't unintentionally have your Pytest setup do unnecessary database setup for Java tests or shunit2_tests
for example
The beauty of the Rules API is that you can factor up common functionality across test runners by adding an intermediate @rule
proud-dentist-22844
05/14/2021, 1:25 AM