Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Mocking input using python wrapped c++ SWIG

I'm new to swig and python unittesting.

Here's what I'm trying to do. I have a c++ function that requires user input. The C++ code is wrapped into python code using SWIG. I'm trying to use pythons unittest module to mock the input. Ive tried mocking builtins.input and writing my own basic function in c++ that just returns a string and mocking that.

Mocking builtins.input still hangs when I get to the std::cin in the c++ code. and mocking the function that returns a string, doesnt return the mocked return_value.

My guess is for some reason I cant mock the return value of the function because its really c++ code and not true python.

Here's some example code I'm working with:

c++ I can include the header file is needed, but its really simple.

#include "MockTest.h"
#include <iostream>
#include <string>

MockTest::MockTest() {}

std::string MockTest::getInput()
{
    std::string name;
    std::cout << "Enter you name" << std::endl;
    name = this->mockInput("hi"); //std::cin >> name;
    std::string toReturn = "Hello " + name + " person";
    return toReturn;
}

std::string MockTest::mockInput(std::string input)
{ 
    return input;
}

swig interface file:

%module MockTest
%include "std_string.i"
%include "std_vector.i"
%include "std_iostream.i"

%{
#include "MockTest.h"
%}
%include <MockTest.h>

python test script

from unittest.mock import patch
from unittest import TestCase
import unittest

import MockTest


class Test(TestCase):

    @patch('builtins.input', return_value="Will")
    def test_Mocktest(self, input):
        self.assertEqual(MockTest.MockTest().getInput(), 'Hello Will person')

    @patch('MockTest.MockTest.mockInput', return_value="Will")
    def test_Mocktest2(self, mockInput):
        self.assertEqual(MockTest.MockTest().getInput(), 'Hello Will person')

if __name__ == '__main__':
    unittest.main()
like image 606
W. Scruggs Avatar asked Nov 07 '22 17:11

W. Scruggs


1 Answers

After spending some time out of which ~ one hour on a stupid mistake:

  • The fact that the generated module was incomplete (the linker wasn't complaining about undefined symbols when not supplying the MockTest object to it)
  • In the end that was generated because I forgot to add public: before class members in MockTest.h ... X:((((

and some more time investigating the real problem, I finally reached a conclusion: the problem is not because of the C++ language (at least not directly), but it lies in the intermediary layer(s) created by Swig.

Details

The MockTest class (whose mockInput method return value we are trying to patch) is defined in module MockTest (!!! that is MockTest.py !!!) which is automatically generated by Swig (the command in my case was swig -o MockTest_wrap.cpp -python -c++ MockTest.i). Here's its definition:

class MockTest(_object):
    __swig_setmethods__ = {}
    __setattr__ = lambda self, name, value: _swig_setattr(self, MockTest, name, value)
    __swig_getmethods__ = {}
    __getattr__ = lambda self, name: _swig_getattr(self, MockTest, name)
    __repr__ = _swig_repr

    def __init__(self):
        this = _MockTest.new_MockTest()
        try:
            self.this.append(this)
        except __builtin__.Exception:
            self.this = this
    __swig_destroy__ = _MockTest.delete_MockTest
    __del__ = lambda self: None

    def getInput(self):
        return _MockTest.MockTest_getInput(self)

    def mockInput(self, input):
        return _MockTest.MockTest_mockInput(self, input)

As you probably guessed, this mockInput is being patched and not the one from C++ (whose name is MockTest_mockInput and it's exported by the native module: _MockTest - the native name is _wrap_MockTest_mockInput defined in MocTest_wrap.cpp). Of course, since getInput calls MockTest_getInput, patching mockInput (the Python wrapper) has no effect (just like if you would modify its body to let's say: return "123").

Here's some sample code that I prepared to better illustrate the behavior (as I mentioned in my comment, I'm using Python 3.5.4). Please ignore the last 3 lines of code and output til you read the next paragraph:

from unittest.mock import patch
import MockTest


if __name__ == "__main__":
    return_value = "value overridden by `patch`"
    mock_input_arg = "argument passed to `MockTest.mockInput`"
    mock_test = MockTest.MockTest()
    with patch("MockTest.MockTest.mockInput", return_value=return_value):
        print("`mock_test.getInput()` returned: \"{}\"".format(mock_test.getInput()))
        print("`mock_test.mockInput(\"{}\")` returned: \"{}\"".format(mock_input_arg, mock_test.mockInput(mock_input_arg)))

    print("\nDon't mind the following output for now...\n")  # SAME THING about the following code

    with patch("MockTest._MockTest.MockTest_mockInput", return_value=return_value):
        print("`mock_test.getInput()` returned: \"{}\"".format(mock_test.getInput()))
        print("`mock_test.mockInput(\"{}\")` returned: \"{}\"".format(mock_input_arg, mock_test.mockInput(mock_input_arg)))

And the output:

c:\Work\Dev\StackOverflow\q45934545>"c:\Install\x64\Python\Python\3.5\python.exe" dummy.py
Enter you name
`mock_test.getInput()` returned: "Hello hi person"
`mock_test.mockInput("argument passed to `MockTest.mockInput`")` returned: "value overridden by `patch`"

Don't mind the following output for now...

Enter you name:
`mock_test.getInput()` returned: "Hello hi person"
`mock_test.mockInput("argument passed to `MockTest.mockInput`")` returned: "value overridden by `patch`"

Then I went further trying to patch MockTest._MockTest.MockTest_mockInput, but I got the same output, because I didn't patch MockTest::mockInput (from MockTest.cpp) but _wrap_MockTest_mockInput (from MocTest_wrap.cpp).

Below is a table with all the layers between C++ code and Python for mockInput (for getInput it's exactly the same):

mockInput layers

As getInput calls mockInput (layer 0), there is where the patch should happen, but unfortunately, that's not available to Python.

The 1st solution that comes into my mind, is patching getInput (layer 1) directly (if it's used in many places).

like image 68
CristiFati Avatar answered Nov 14 '22 22:11

CristiFati