Issue 36395: Add deferred single-threaded/fake executor to concurrent.futures (original) (raw)

Created on 2019-03-22 01:39 by Brian McCutchon, last changed 2022-04-11 14:59 by admin.

Messages (9)

msg338576 - (view)

Author: Brian McCutchon (Brian McCutchon)

Date: 2019-03-22 01:39

Currently, it is possible to make a basic single-threaded executor for unit testing:

class FakeExecutor(futures.Executor):

def submit(self, f, *args, **kwargs): future = futures.Future() future.set_result(f(*args, **kwargs)) return future

def shutdown(self, wait=True): pass

However, this evaluates the provided function eagerly, which may be undesirable for tests. It prevents the tests from catching a whole class of errors (those where the caller forgot to call .result() on a future that is only desirable for its side-effects). It would be great to have an Executor implementation where the function is only called when .result() is called so tests can catch those errors.

I might add that, while future.set_result is documented as being supported for unit tests, a comment in the CPython source says that Future.init() "Should not be called by clients" (https://github.com/python/cpython/blob/master/Lib/concurrent/futures/_base.py#L317), suggesting that even the above code is unsupported and leaving me wondering how I should test future-heavy code without using mock.patch on everything.

------ Alternatives that don't work ------

One might think it possible to create a FakeFuture to do this:

class FakeFuture(object):

def init(self, to_invoke): self._to_invoke = to_invoke

def result(self, timeout=None): return self._to_invoke()

However, futures.wait is not happy with this:

futures.wait([FakeFuture(lambda x: 1)]) # AttributeError: 'FakeFuture' object has no attribute '_condition'

If FakeFuture is made to extend futures.Future, the above line instead hangs:

class FakeFuture(futures.Future):

def init(self, to_invoke): super(FakeFuture, self).init() self._to_invoke = to_invoke

def result(self, timeout=None): return self._to_invoke()

I feel like I shouldn't have to patch out wait() in order to get good unit tests.

msg341616 - (view)

Author: Brian Quinlan (bquinlan) * (Python committer)

Date: 2019-05-06 19:29

Hey Brian, why can't you use threads in your unit tests? Are you worried about non-determinism or resource usage? Could you make a ThreadPoolExecutor with a single worker?

msg341624 - (view)

Author: Brian McCutchon (Brian McCutchon)

Date: 2019-05-06 19:42

Mostly nondeterminism. It seems like creating a ThreadPoolExecutor with one worker could still be nondeterministic, as there are two threads: the main thread and the worker thread. It gets worse if multiple executors are needed.

Another option would be to design and document futures.Executor to be extended so that I can make my own fake executor.

msg341625 - (view)

Author: Brian Quinlan (bquinlan) * (Python committer)

Date: 2019-05-06 19:46

Do you have a example that you could share?

I can't think of any other fakes in the standard library and I'm hesitant to be the person who adds the first one ;-)

msg341660 - (view)

Author: Brian McCutchon (Brian McCutchon)

Date: 2019-05-06 23:05

I understand your hesitation to add a fake. Would it be better to make it possible to subclass Executor so that a third party implementation of this can be developed?

As for an example, here is an example of nondeterminism when using a ThreadPoolExecutor with a single worker. It sometimes prints "False" and sometimes "True" on my machine.

from concurrent import futures import time

complete = False

def complete_eventually(): global complete for _ in range(150000): pass complete = True

with futures.ThreadPoolExecutor(max_workers=1) as pool: pool.submit(complete_eventually) print(complete)

msg341740 - (view)

Author: Brian Quinlan (bquinlan) * (Python committer)

Date: 2019-05-07 14:53

Hey Brian,

I understand the non-determinism. I was wondering if you had a non-theoretical example i.e. some case where the non-determinism had impacted a real test that you wrote?

msg341797 - (view)

Author: Brian McCutchon (Brian McCutchon)

Date: 2019-05-07 18:55

No, I do not have such an example, as most of my tests try to fake the executors.

msg341890 - (view)

Author: Brian Quinlan (bquinlan) * (Python committer)

Date: 2019-05-08 15:31

Brian, I was looking for an example where the current executor isn't sufficient for testing i.e. a useful test that would be difficult to write with a real executor but would be easier with a fake.

Maybe you have such an example from your tests?

msg369603 - (view)

Author: Leonardo Santagada (santagada)

Date: 2020-05-22 14:18

I have a single example:

Profiling. As most python profilers don't support threads or processes, it would be very convenient to have a in process executor in those cases.

History

Date

User

Action

Args

2022-04-11 14:59:12

admin

set

github: 80576

2020-05-22 14🔞17

santagada

set

nosy: + santagada
messages: +

2019-05-08 15:31:39

bquinlan

set

messages: +

2019-05-07 18:55:39

Brian McCutchon

set

messages: +

2019-05-07 14:53:47

bquinlan

set

messages: +

2019-05-06 23:05:13

Brian McCutchon

set

messages: +

2019-05-06 19:46:49

bquinlan

set

messages: +

2019-05-06 19:42:16

Brian McCutchon

set

messages: +

2019-05-06 19:29:26

bquinlan

set

messages: +

2019-03-22 01:55:07

xtreak

set

nosy: + bquinlan, pitrou

2019-03-22 01:39:20

Brian McCutchon

create