Using pytest patch decorator to test ray actors remote function - pytest

I'm trying to run unit test for a ray remote function. I am using a #patch decorator to patch the remote function. The
foo.py
class Foo(object):
def __init__(self):
self.value = 0
def bar(self):
self.value = 100
print("In original method")
assert False
test_foo.py
from unittest.mock import patch
import pytest
import unittest
import ray
from tests.foo import Foo
#pytest.fixture
def ray_fixture():
print("Initializing ray")
if not ray.is_initialized():
ray.init()
yield None
print("Terminating ray")
ray.shutdown()
def fake_bar(self):
print("In fake method")
assert True
#pytest.mark.usefixtures("ray_fixture")
class FooTestCase(unittest.TestCase):
"""Test cases for Foo module"""
#patch("foo.Foo.bar", new=fake_bar)
def test_bar(self):
Foo().bar()
#patch("foo.Foo.bar", new=fake_bar)
def test_bar_remote(self):
foo_actor = ray.remote(Foo).remote()
obj_ref = foo_actor.bar.remote()
ray.get(obj_ref)
The test test_bar passes and test_bar_remote fails.
If I use ray.init(local_mode=True) then both tests pass. I can not use local_mode=True due to other limitations.
How can we patch ray actor's remote method using #patch?

Here's an alternative. Subclass Foo with a stubbed/mocked implementation and use it in ray. That way, the Foo class would be intact, you would only update those that needs to be mocked e.g. the method bar().
test_foo.py
...
class FooStub(Foo):
def bar(self, *args, **kwargs):
print("In another fake method")
assert True
# Optionally, you can also call the real method if you want. You may update the arguments as needed.
# super().bar(*args, **kwargs)
#pytest.mark.usefixtures("ray_fixture")
class FooTestCase(unittest.TestCase):
...
def test_bar_remote(self):
foo_actor = ray.remote(FooStub).remote()
obj_ref = foo_actor.bar.remote()
ray.get(obj_ref)
...
Output
$ pytest -q -rP
..
================================================================================================= PASSES ==================================================================================================
__________________________________________________________________________________________ FooTestCase.test_bar ___________________________________________________________________________________________
------------------------------------------------------------------------------------------ Captured stdout setup ------------------------------------------------------------------------------------------
Initializing ray
------------------------------------------------------------------------------------------ Captured stdout call -------------------------------------------------------------------------------------------
In fake method
---------------------------------------------------------------------------------------- Captured stdout teardown -----------------------------------------------------------------------------------------
Terminating ray
_______________________________________________________________________________________ FooTestCase.test_bar_remote _______________________________________________________________________________________
------------------------------------------------------------------------------------------ Captured stdout setup ------------------------------------------------------------------------------------------
Initializing ray
---------------------------------------------------------------------------------------- Captured stdout teardown -----------------------------------------------------------------------------------------
Terminating ray
2 passed, 1 warning in 5.03s

I have found a below hacky way which involves change in original function (and check env variable to provide test implementation)
import os
class Foo(object):
def __init__(self):
self.value = 0
def bar(self):
self.value = 100
if os.environ.get['TEST'] == 'True':
print("In fake method")
assert True
else:
print("In original method")
assert False
runtime_env = {"env_vars": {"TEST": "True"}}
ray.remote(Foo) .options(runtime_env=runtime_env) .remote()

Related

Pytest Scoping Issue with Fixture Indirect

I have the following:
// conftest.py
import pytest
#pytest.fixture(scope="session")
def deviceundertest(request):
return request.param
#pytest.fixture(scope="session")
def setupteardown(deviceundertest):
yield "conn"
#pytest.fixture(scope="session")
def data1(setupteardown):
return response
#pytest.fixture(scope="session")
def data2(setupteardown):
return response
// test_000.py
import pytest
#pytest.mark.parametrize("deviceundertest", ["server1", "server2"], indirect=True)
def test1(data1):
assert True
#pytest.mark.parametrize("deviceundertest", ["server3", "server4"], indirect=True)
def test2(data2):
assert True
// test_001.py
import pytest
#pytest.mark.parametrize("deviceundertest", ["server1", "server2"], indirect=True)
def test3(data1):
assert True
#pytest.mark.parametrize("deviceundertest", ["server3", "server4"], indirect=True)
def test4(data2):
assert True
When I run a --setup-plan and look at one of the fixtures Im seeing the fixtures setup and teardown is being performed more then its actual scoop (i.e session). Example below:
❯ pytest example_dir --setup-plan | grep "SETUP S deviceundertest"
SETUP S deviceundertest['server1']
SETUP S deviceundertest['server3']
SETUP S deviceundertest['server1']
SETUP S deviceundertest['server3']
SETUP S deviceundertest['server2']
SETUP S deviceundertest['server4']
SETUP S deviceundertest['server2']
SETUP S deviceundertest['server4']
Thanks,
Pytest doesn't check if the arguments are the same in multiple tests, every test is independent. The scope="session" is relevant only if the fixture is not invoked explicitly, which you are doing, so every parameter in each test will invoke the deviceundertest fixture.
You can implement a simple logic in conftest.py to use the same server instance in multiple tests
servers = {}
#pytest.fixture
def deviceundertest(request):
server = request.param
if server not in servers:
servers[server] = Server() # create your server instance
return servers[server]
You are sending different params to the root fixture through "#pytest.mark.parametrize", so it's creating a different fixture each time with the value you sent.
A fixture doesn't repeat the setup/teardown process only if it will 100% return the same value, in you case each time a different value can be menifested.

Can pytest_sessionfinish still call the fixture with session as scope?

I need call one fixture when all the tests are successful. I tried to check if all tests are successful in pytest_sessionfinish. I also put my fixture with the session scope #pytest.fixture(scope="session"). However, the code in pytest_sessionfinish can not call this fixture at all. Are there some way to solve this issue?
#pytest.fixture(scope="session")
def myfunction(
ser: Serial, logger: Logger
) -> bool:
print("In my function")
...
#pytest.hookimpl(trylast=True)
def pytest_sessionfinish(session, exitstatus):
print("Prepare to call myfunction")
myfunction
It only showes "Prepare to call myfunction" as the output.
Thanks!

Patching class does not work its definition is get from dict

I have a problem with patching, how can I patch A class properly?
a.py
class A:
pass
plugin.py
from a import A
CONFIG = {'aenum': A}
def do():
instance = CONFIG['aenum']() # the A class is NOT patched using this
# instance = A() # the test passes with this
return instance.auth()
test.py (using pytest to run)
from unittest.mock import Mock, patch
import plugin
def test_do():
a_instance_mock = Mock()
a_instance_mock.auth.return_value = 42
with patch("plugin.A", return_value=a_instance_mock):
assert plugin.do() == 42
patch overrides only the specific item from given namespace; the class name is patched AFTER a module is imported, not in the importation time.
That is why the CONFIG["aenum"] is left untouched.
Use patch.dict instead
from unittest.mock import Mock, patch
from plugin import do
def test_do():
a_instance_mock = Mock()
a_instance_mock.authenticate.return_value = 42
a_class_mock = Mock(return_value=a_instance_mock)
with patch.dict("plugin.CONFIG", {"aenum": a_class_mock}):
assert do() == 42

Can not use autouse fixture to import module

I have a test module has one autouse fixture
import pytest
#pytest.fixture(autouse=True):
def set_env_config(monkeypatch):
palladium_config = os.path.join(os.path.dirname(os.path.dirname(os.getcwd())), 'config.py')
monkeypatch.setenv('PALLADIUM_CONFIG', palladium_config)
from A import B
and in every followed test within this test module class B is needed, but this importation can not be achieved for any tests.
In the other way, I patch only the environment variable only
#pytest.fixture(autouse=True):
def set_env_config(monkeypatch):
palladium_config = os.path.join(os.path.dirname(os.path.dirname(os.getcwd())), 'config.py')
monkeypatch.setenv('PALLADIUM_CONFIG', palladium_config)
and import the class B in every test case, it succeeded.
Why is that ? why can't I import class within autouse fixture
thanks a lot
I am guessing that you are expecting something like the following:
#pytest.fixture(autouse=True)
def do_an_import():
from A import B
def test_foo():
assert B.do_my_thing() == 'foo'
That doesn't work, while doing the following does what you want:
def test_foo():
from A import B
assert B.do_my_thing() == 'foo'
Unfortunately doing an import in the fixture (the first example) will add B into that fixture function's namespace, but not the test function's namespace.
Similarly, this wouldn't work for the same reason:
#pytest.fixture
def not_an_autouse_fixture():
from A import B
def test_foo(not_an_autouse_fixture):
assert B.do_my_thing() == 'foo'
B is being imported into the fixture's namespace, which is different from the test's namespace. You could instead do:
#pytest.fixture
def Cls():
from A import B
return B
def test_foo(Cls):
assert Cls.do_my_thing() == 'foo'
or you could import it at the top level of your module like:
from A import B
def test_foo(B):
assert B.do_my_thing() == 'foo'

Why can't the compiler find implicit ExecutionContext with Implicits.global imported?

I have the following piece of code:
import java.util.concurrent.Executor
import scala.concurrent.Future
trait Storage {
def store(location: String, content: Array[Byte])(implicit exec: Executor): Future[String]
}
object LocalStorage extends Storage {
override def store(location: String, content: Array[Byte])(implicit exec: Executor): Future[String] =
Future {
... do some stuff ...
"Hello"
}
}
And here comes the code for testing:
object LocalStorageTest extends App{
import scala.concurrent.ExecutionContext.Implicits.global
val testImage = AsyncWebClient get "some url"
val result = testImage.flatMap{content => LocalStorage.store("logo.png",content)}
val status =Await.result(result, 30 seconds)
println(status)
AsyncWebClient.shutDown
}
Whenever I try to run the code, I am getting the following error:
Cannot find an implicit ExecutionContext.
Why? Isn't scala.concurrent.ExecutionContext.Implicits.global in scope already? When this import is added directly to LocalStorage object, it works (!)
BUT I am loosing a possibility to alter the context. Since I do not want to run this code on global every time. As this is a part of an akka application and for "production" runtime it is supposed to run on some dispatcher. Just for testing I would like to run it on a global execution context.
Am I missing some important concept here or is my design wrong that I am loosing such flexibility? Where is the issue?!
An ExecutionContext is not the same thing as an Executor. You should make the method accept an implicit exec: ExecutionContext (scala.concurrent.ExecutionContext) and then it will behave as you expect.