Python Coverage for C++ PyImport

不羁岁月 提交于 2019-12-23 12:08:21

问题


Situation:
I'm attempting to get coverage reports on all python code in my current project. I've utilized Coverage.py with great success for the most part. Currently I'm using it like this taking advantage of the sitecustomize.py process. For everything that's being started from the command line, and it works amazing.

Issue:
I can't get python modules run from C++ via PyImport_Import() type statements to actually trace and output coverage data.

Example:

[test.cpp]

#include <stdio.h>
#include <iostream>
#include <Python.h>
int main()
{
    Py_Initialize();
    PyObject* sysPath = PySys_GetObject("path");
    PyList_Append(sysPath, PyString_FromString("."));
    // Load the module
    PyObject *pName = PyString_FromString("test_mod");
    PyObject *pModule = PyImport_Import(pName);
    if (pModule != NULL) {
        std::cout << "Python module found\n";
        // Load all module level attributes as a dictionary
        PyObject *pDict = PyModule_GetDict(pModule);
        PyObject *pFunc = PyObject_GetAttrString(pModule, "getInteger");
        if(pFunc)
        {
            if(PyCallable_Check(pFunc))
            {
                PyObject *pValue = PyObject_CallObject(pFunc, NULL);
                std::cout << PyLong_AsLong(pValue) << std::endl;
            }
            else
            {
                printf("ERROR: function getInteger()\n");
            }

        }
        else
        {
            printf("ERROR: pFunc is NULL\n");
        }
    }
    else
        std::cout << "Python Module not found\n";
    return 0;
}

[test_mod.py]

#!/bin/python
def getInteger():
     print('Python function getInteger() called')
     c = 100*50/30
     return c
print('Randomness')

Output:
If I manually run test_mod.py it outputs as expected. However, if I run the compiled test.cpp binary, it doesn't output anything for coverage data. I know sitecustomize.py is still being hit, as I added some debugging to ensure I wasn't going insane. I can also see in the coverage debug log that it does indeed want to trace the module..

[cov.log]

New process: executable: /usr/bin/python
New process: cmd: ???
New process: parent pid: 69073
-- config ----------------------------------------------------
_include: None
_omit: None
attempted_config_files: /tmp/.coveragerc
branch: True
concurrency: thread
multiprocessing
config_files: /tmp/.coveragerc
cover_pylib: False
data_file: /tmp/python_data/.coverage
debug: process
trace
sys
config
callers
dataop
dataio
disable_warnings: -none-
exclude_list: #\s*(pragma|PRAGMA)[:\s]?\s*(no|NO)\s*(cover|COVER)
extra_css: None
fail_under: 0.0
html_dir: htmlcov
html_title: Coverage report
ignore_errors: False
note: None
New Section 1 Page 2note: None
parallel: True
partial_always_list: while (True|1|False|0):
if (True|1|False|0):
partial_list: #\s*(pragma|PRAGMA)[:\s]?\s*(no|NO)\s*(branch|BRANCH)
paths: {'source': ['/tmp/python_source', '/opt/test']}
plugin_options: {}
plugins: -none-
precision: 0
report_include: None
report_omit: None
run_include: None
run_omit: None
show_missing: False
skip_covered: False
source: /opt/test/
timid: False
xml_output: coverage.xml
xml_package_depth: 99
-- sys -------------------------------------------------------
version: 4.5.4
coverage: /usr/lib64/python2.7/site-packages/coverage/__init__.pyc
cover_paths: /usr/lib64/python2.7/site-packages/coverage
pylib_paths: /usr/lib64/python2.7
tracer: PyTracer
plugins.file_tracers: -none-
plugins.configurers: -none-
config_files: /tmp/.coveragerc
configs_read: /tmp/.coveragerc
data_path: /tmp/python_data/.coverage
python: 2.7.5 (default, Jun 11 2019, 14:33:56) [GCC 4.8.5 20150623 (Red Hat 4.8.5-39)]
platform: Linux-3.10.0-1062.el7.x86_64-x86_64-with-redhat-7.7-Maipo
implementation: CPython
executable: /usr/bin/python
cwd: /opt/test
path: /usr/lib64/python27.zip
/usr/lib64/python2.7
/usr/lib64/python2.7/plat-linux2
/usr/lib64/python2.7/lib-tk
/usr/lib64/python2.7/lib-old
/usr/lib64/python2.7/lib-dynload
/usr/lib64/python2.7/site-packages
environment: COVERAGE_DEBUG = process,trace,sys,config,callers,dataop,dataio
COVERAGE_DEBUG_FILE = /tmp/cov.log
COVERAGE_PROCESS_START = /tmp/.coveragerc
command_line: ???
source_match: /opt/test
source_pkgs_match: -none-
include_match: -none-
omit_match: -none-
cover_match: -none-
pylib_match: -none-
-- end -------------------------------------------------------
<module> : /usr/lib64/python2.7/site.py @556
New Section 1 Page 3<module> : /usr/lib64/python2.7/site.py @556
main : /usr/lib64/python2.7/site.py @539
addsitepackages : /usr/lib64/python2.7/site.py @317
addsitedir : /usr/lib64/python2.7/site.py @190
addpackage : /usr/lib64/python2.7/site.py @152
<module> : <string> @1
process_startup : /usr/lib64/python2.7/site-packages/coverage/control.py @1289
start : /usr/lib64/python2.7/site-packages/coverage/control.py @690
_init : /usr/lib64/python2.7/site-packages/coverage/control.py @362
_write_startup_debug : /usr/lib64/python2.7/site-packages/coverage/control.py @382
write_formatted_info : /usr/lib64/python2.7/site-packages/coverage/debug.py @120
Not tracing '/usr/lib64/python2.7/threading.py': falls outside the --source trees
<module> : /usr/lib64/python2.7/site.py @556
main : /usr/lib64/python2.7/site.py @539
addsitepackages : /usr/lib64/python2.7/site.py @317
addsitedir : /usr/lib64/python2.7/site.py @190
addpackage : /usr/lib64/python2.7/site.py @152
<module> : <string> @1
process_startup : /usr/lib64/python2.7/site-packages/coverage/control.py @1289
start : /usr/lib64/python2.7/site-packages/coverage/control.py @701
start : /usr/lib64/python2.7/site-packages/coverage/collector.py @318
settrace : /usr/lib64/python2.7/threading.py @99
_trace : /usr/lib64/python2.7/site-packages/coverage/pytracer.py @111
_should_trace : /usr/lib64/python2.7/site-packages/coverage/control.py @593


[... Not tracing a bunch of common python code ...]


Tracing './test_mod.py'
<module> : ./test_mod.py @3
_trace : /usr/lib64/python2.7/site-packages/coverage/pytracer.py @111
_should_trace : /usr/lib64/python2.7/site-packages/coverage/control.py @593

回答1:


I reproduced the issue using your code and you only forgot to call Py_Finalize(). As a result, the report is never generated whereas the data were collected.

It works with the following piece of code:

#include <stdio.h>
#include <iostream>
#include <Python.h>
int main()
{
    Py_Initialize();
    PyEval_InitThreads();
    PyObject* sysPath = PySys_GetObject("path");
    PyList_Append(sysPath, PyString_FromString("."));
    // Load the module
    PyObject *pName = PyString_FromString("test_mod");
    PyObject *pModule = PyImport_Import(pName);
    if (pModule != NULL) {
        std::cout << "Python module found\n";
        // Load all module level attributes as a dictionary
        PyObject *pDict = PyModule_GetDict(pModule);
        PyObject *pFunc = PyObject_GetAttrString(pModule, "getInteger");
        if(pFunc)
        {
            if(PyCallable_Check(pFunc))
            {
                PyObject *pValue = PyObject_CallObject(pFunc, NULL);
                std::cout << PyLong_AsLong(pValue) << std::endl;
            }
            else
            {
                printf("ERROR: function getInteger()\n");
            }

        }
        else
        {
            printf("ERROR: pFunc is NULL\n");
        }
    }
    else
        std::cout << "Python Module not found\n";
    Py_Finalize();
    return 0;



回答2:


I'm only just starting with the Python-C API myself, but my understanding is that importing modules doesn't actually add them to your main module. You need to do that separately. I'm not sure if this will help with your issue, but my approach that's worked (minus the error checking) has been as follows:

// Initialize main module
PyObject* mainModule = PyImport_AddModule("__main__");;

//  Initialize module to be added
PyObject* moduleNamePyObject= PyUnicode_DecodeFSDefault("moduleName");
PyImport_Import(moduleNamePyObject);

// Add module to main module
PyObject_SetAttrString(mainModulePtr, "moduleName", modulePyObject);



回答3:


Normally, when importing a module, Python tries to find the module file next to the importing module (the module that contains the import statement). Python then tries the directories in “sys.path”. The current working directory is usually not considered. In our case, the import is performed via the API, so there is no importing module in whose directory Python could search for “test_mod.py”. The plug-in is also not on “sys.path”. One way of enabling Python to find the plug-in is to add the current working directory to the module search path by doing the equivalent of “sys.path.append(‘.’)” via the API.

Py_Initialize();
PyObject* sysPath = PySys_GetObject((char*)"path");
PyObject* programName = PyString_FromString(<DIRECTORY>.c_str());
PyList_Append(sysPath, programName);
Py_DECREF(programName);

If you are using python3 ,

Change PyString_FromString to PyUnicode_FromString.

Sources :

https://realmike.org/blog/2012/07/08/embedding-python-tutorial-part-1/

Python Embedding: PyImport_Import not from the current directory




回答4:


PyObject *PySys_GetObject(char *name) returns a borrowed reference. Is not it the case that the reference count should be incremented? What about:

// ...
PyObject* sysPath = PySys_GetObject("path");
Py_INCREF(sysPath);
PyList_Append(sysPath, PyString_FromString("."));
Py_DECREF(sysPath);
// sysPath = NULL;
// ...


来源:https://stackoverflow.com/questions/59150922/python-coverage-for-c-pyimport

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!