Skip to content

Fix signature and private module errors that comes from C extension modules #60

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Feb 22, 2024

Conversation

BergLucas
Copy link
Contributor

Hello,

I've tried to fix the bug that I've explained in issue #59.

My solution to the "no signature found" error is just to provide a default method signature that is very generic and that works on every function. This default method signature is only used when the inspect module can't find a method signature and it's just to prevent Pynguin from crashing. This creates a signature that is the same as for this function:

def foo(*args, **kwargs): ...

However, after correcting this error, I came across two others that were very closely related to it, so I've decided to correct them here too.

First, I got :

╭─────────────────────────────── Traceback (most recent call last) ────────────────────────────────╮
│ /home/lucas/.conda/envs/2324-master-thesis/bin/pynguin:8 in <module>                             │
│                                                                                                  │
│   5 from pynguin.cli import main                                                                 │
│   6 if __name__ == '__main__':                                                                   │
│   7 │   sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])                         │
│ ❱ 8 │   sys.exit(main())                                                                         │
│   9                                                                                              │
│                                                                                                  │
│ /home/lucas/Documents/GitHub/pynguin/src/pynguin/cli.py:193 in main                              │
│                                                                                                  │
│   190 │   set_configuration(parsed.config)                                                       │
│   191 │   if console is not None:                                                                │
│   192 │   │   with console.status("Running Pynguin..."):                                         │
│ ❱ 193 │   │   │   return run_pynguin().value                                                     │
│   194 │   else:                                                                                  │
│   195 │   │   return run_pynguin().value                                                         │
│   196                                                                                            │
│                                                                                                  │
│ /home/lucas/Documents/GitHub/pynguin/src/pynguin/generator.py:108 in run_pynguin                 │
│                                                                                                  │
│   105 │   """
│   106 │   try:                                                                                   │
│   107 │   │   _LOGGER.info("Start Pynguin Test Generation…")                                     │
│ ❱ 108 │   │   return _run()                                                                      │
│   109 │   finally:                                                                               │
│   110 │   │   _LOGGER.info("Stop Pynguin Test Generation…")                                      │
│   111                                                                                            │
│                                                                                                  │
│ /home/lucas/Documents/GitHub/pynguin/src/pynguin/generator.py:507 in _run                        │
│                                                                                                  │
│   504                                                                                            │
│   505                                                                                            │
│   506 def _run() -> ReturnCode:                                                                  │
│ ❱ 507 │   if (setup_result := _setup_and_check()) is None:                                       │
│   508 │   │   return ReturnCode.SETUP_FAILED                                                     │
│   509 │   executor, test_cluster, constant_provider = setup_result                               │
│   510 │   # traces slices for test cases after execution                                         │
│                                                                                                  │
│ /home/lucas/Documents/GitHub/pynguin/src/pynguin/generator.py:258 in _setup_and_check            │
│                                                                                                  │
│   255 │                                                                                          │
│   256 │   # Analyzing the SUT should not cause any coverage.                                     │
│   257 │   tracer.disable()                                                                       │
│ ❱ 258 │   if (test_cluster := _setup_test_cluster()) is None:                                    │
│   259 │   │   return None                                                                        │
│   260 │   tracer.enable()                                                                        │
│   261                                                                                            │
│                                                                                                  │
│ /home/lucas/Documents/GitHub/pynguin/src/pynguin/generator.py:114 in _setup_test_cluster         │
│                                                                                                  │
│   111                                                                                            │
│   112                                                                                            │
│   113 def _setup_test_cluster() -> ModuleTestCluster | None:                                     │
│ ❱ 114 │   test_cluster = generate_test_cluster(                                                  │
│   115 │   │   config.configuration.module_name,                                                  │
│   116 │   │   config.configuration.type_inference.type_inference_strategy,                       │
│   117 │   │   query_type4py=config.configuration.type_inference.type4py,                         │
│                                                                                                  │
│ /home/lucas/Documents/GitHub/pynguin/src/pynguin/analyses/module.py:1450 in                      │
│ generate_test_cluster                                                                            │
│                                                                                                  │
│   1447 │   Returns:                                                                              │
│   1448 │   │   A new test cluster for the given module                                           │
│   1449 │   """                                                                                   │
│ ❱ 1450 │   return analyse_module(                                                                │
│   1451 │   │   parse_module(module_name, query_type4py=query_type4py),                           │
│   1452 │   │   type_inference_strategy,                                                          │
│   1453 │   │   query_type4py=query_type4py,                                                      │
│                                                                                                  │
│ /home/lucas/Documents/GitHub/pynguin/src/pynguin/analyses/module.py:1425 in analyse_module       │
│                                                                                                  │
│   1422 │   │   A test cluster for the module                                                     │
│   1423 │   """
│   1424 │   test_cluster = ModuleTestCluster(linenos=parsed_module.linenos)                       │
│ ❱ 1425 │   __resolve_dependencies(                                                               │
│   1426 │   │   root_module=parsed_module,                                                        │
│   1427 │   │   type_inference_strategy=type_inference_strategy,                                  │
│   1428 │   │   test_cluster=test_cluster,                                                        │
│                                                                                                  │
│ /home/lucas/Documents/GitHub/pynguin/src/pynguin/analyses/module.py:1295 in                      │
│ __resolve_dependencies                                                                           │
│                                                                                                  │
│   1292 │   │   │   continue                                                                      │
│   1293 │   │                                                                                     │
│   1294 │   │   # Analyze all classes found in the current module                                 │
│ ❱ 1295 │   │   __analyse_included_classes(                                                       │
│   1296 │   │   │   module=current_module,                                                        │
│   1297 │   │   │   root_module_name=root_module.module_name,                                     │
│   1298 │   │   │   type_inference_strategy=type_inference_strategy,                              │
│                                                                                                  │
│ /home/lucas/Documents/GitHub/pynguin/src/pynguin/analyses/module.py:1355 in                      │
│ __analyse_included_classes                                                                       │
│                                                                                                  │
│   1352 │   │                                                                                     │
│   1353 │   │   type_info = test_cluster.type_system.to_type_info(current)                        │
│   1354 │   │                                                                                     │
│ ❱ 1355 │   │   results = parse_results[current.__module__]                                       │
│   1356 │   │                                                                                     │
│   1357 │   │   __analyse_class(                                                                  │
│   1358 │   │   │   type_info=type_info,                                                          │
│                                                                                                  │
│ /home/lucas/Documents/GitHub/pynguin/src/pynguin/analyses/module.py:1249 in __missing__          │
│                                                                                                  │
│   1246 │                                                                                         │
│   1247 │   def __missing__(self, key):                                                           │
│   1248 │   │   # Parse module on demand                                                          │
│ ❱ 1249 │   │   res = self[key] = parse_module(key, query_type4py=self._query_type4py)            │
│   1250 │   │   return res                                                                        │
│   1251                                                                                           │
│   1252                                                                                           │
│                                                                                                  │
│ /home/lucas/Documents/GitHub/pynguin/src/pynguin/analyses/module.py:248 in parse_module          │
│                                                                                                  │
│    245 │   Returns:                                                                              │
│    246 │   │   A tuple of the imported module type and its optional AST                          │
│    247 │   """                                                                                   │
│ ❱  248 │   module = importlib.import_module(module_name)                                         │
│    249 │   type4py_data: Type4pyData | None = None                                               │
│    250 │   syntax_tree: astroid.Module | None = None                                             │
│    251 │   linenos: int = -1                                                                     │
│                                                                                                  │
│ /home/lucas/.conda/envs/2324-master-thesis/lib/python3.10/importlib/__init__.py:126 in           │
│ import_module                                                                                    │
│                                                                                                  │
│   123 │   │   │   if character != '.':                                                           │
│   124 │   │   │   │   break                                                                      │
│   125 │   │   │   level += 1                                                                     │
│ ❱ 126 │   return _bootstrap._gcd_import(name[level:], package, level)                            │
│   127                                                                                            │
│   128                                                                                            │
│   129 _RELOADING = {}                                                                            │
│ in _gcd_import:1050                                                                              │
│ in _find_and_load:1027                                                                           │
│ in _find_and_load_unlocked:1004                                                                  │
╰──────────────────────────────────────────────────────────────────────────────────────────────────╯
ModuleNotFoundError: No module named 'pybind11_builtins'

I've searched online and I think that this module is only available from C code and not from Python code so I've added a condition that skips modules that come from C code if they are not found using the inspect module.

Then, I got the following error:

╭─────────────────────────────── Traceback (most recent call last) ────────────────────────────────╮
│ /home/lucas/.conda/envs/2324-master-thesis/bin/pynguin:8 in <module>                             │
│                                                                                                  │
│   5 from pynguin.cli import main                                                                 │
│   6 if __name__ == '__main__':                                                                   │
│   7 │   sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])                         │
│ ❱ 8 │   sys.exit(main())                                                                         │
│   9                                                                                              │
│                                                                                                  │
│ /home/lucas/Documents/GitHub/pynguin/src/pynguin/cli.py:193 in main                              │
│                                                                                                  │
│   190 │   set_configuration(parsed.config)                                                       │
│   191 │   if console is not None:                                                                │
│   192 │   │   with console.status("Running Pynguin..."):                                         │
│ ❱ 193 │   │   │   return run_pynguin().value                                                     │
│   194 │   else:                                                                                  │
│   195 │   │   return run_pynguin().value                                                         │
│   196                                                                                            │
│                                                                                                  │
│ /home/lucas/Documents/GitHub/pynguin/src/pynguin/generator.py:108 in run_pynguin                 │
│                                                                                                  │
│   105 │   """
│   106 │   try:                                                                                   │
│   107 │   │   _LOGGER.info("Start Pynguin Test Generation…")                                     │
│ ❱ 108 │   │   return _run()                                                                      │
│   109 │   finally:                                                                               │
│   110 │   │   _LOGGER.info("Stop Pynguin Test Generation…")                                      │
│   111                                                                                            │
│                                                                                                  │
│ /home/lucas/Documents/GitHub/pynguin/src/pynguin/generator.py:507 in _run                        │
│                                                                                                  │
│   504                                                                                            │
│   505                                                                                            │
│   506 def _run() -> ReturnCode:                                                                  │
│ ❱ 507 │   if (setup_result := _setup_and_check()) is None:                                       │
│   508 │   │   return ReturnCode.SETUP_FAILED                                                     │
│   509 │   executor, test_cluster, constant_provider = setup_result                               │
│   510 │   # traces slices for test cases after execution                                         │
│                                                                                                  │
│ /home/lucas/Documents/GitHub/pynguin/src/pynguin/generator.py:258 in _setup_and_check            │
│                                                                                                  │
│   255 │                                                                                          │
│   256 │   # Analyzing the SUT should not cause any coverage.                                     │
│   257 │   tracer.disable()                                                                       │
│ ❱ 258 │   if (test_cluster := _setup_test_cluster()) is None:                                    │
│   259 │   │   return None                                                                        │
│   260 │   tracer.enable()                                                                        │
│   261                                                                                            │
│                                                                                                  │
│ /home/lucas/Documents/GitHub/pynguin/src/pynguin/generator.py:114 in _setup_test_cluster         │
│                                                                                                  │
│   111                                                                                            │
│   112                                                                                            │
│   113 def _setup_test_cluster() -> ModuleTestCluster | None:                                     │
│ ❱ 114 │   test_cluster = generate_test_cluster(                                                  │
│   115 │   │   config.configuration.module_name,                                                  │
│   116 │   │   config.configuration.type_inference.type_inference_strategy,                       │
│   117 │   │   query_type4py=config.configuration.type_inference.type4py,                         │
│                                                                                                  │
│ /home/lucas/Documents/GitHub/pynguin/src/pynguin/analyses/module.py:1495 in                      │
│ generate_test_cluster                                                                            │
│                                                                                                  │
│   1492 │   Returns:                                                                              │
│   1493 │   │   A new test cluster for the given module                                           │
│   1494 │   """                                                                                   │
│ ❱ 1495 │   return analyse_module(                                                                │
│   1496 │   │   parse_module(module_name, query_type4py=query_type4py),                           │
│   1497 │   │   type_inference_strategy,                                                          │
│   1498 │   │   query_type4py=query_type4py,                                                      │
│                                                                                                  │
│ /home/lucas/Documents/GitHub/pynguin/src/pynguin/analyses/module.py:1470 in analyse_module       │
│                                                                                                  │
│   1467 │   │   A test cluster for the module                                                     │
│   1468 │   """
│   1469 │   test_cluster = ModuleTestCluster(linenos=parsed_module.linenos)                       │
│ ❱ 1470 │   __resolve_dependencies(                                                               │
│   1471 │   │   root_module=parsed_module,                                                        │
│   1472 │   │   type_inference_strategy=type_inference_strategy,                                  │
│   1473 │   │   test_cluster=test_cluster,                                                        │
│                                                                                                  │
│ /home/lucas/Documents/GitHub/pynguin/src/pynguin/analyses/module.py:1331 in                      │
│ __resolve_dependencies                                                                           │
│                                                                                                  │
│   1328 │   │   │   continue                                                                      │
│   1329 │   │                                                                                     │
│   1330 │   │   # Analyze all classes found in the current module                                 │
│ ❱ 1331 │   │   __analyse_included_classes(                                                       │
│   1332 │   │   │   module=current_module,                                                        │
│   1333 │   │   │   root_module_name=root_module.module_name,                                     │
│   1334 │   │   │   type_inference_strategy=type_inference_strategy,                              │
│                                                                                                  │
│ /home/lucas/Documents/GitHub/pynguin/src/pynguin/analyses/module.py:1393 in                      │
│ __analyse_included_classes                                                                       │
│                                                                                                  │
│   1390 │   │                                                                                     │
│   1391 │   │   # Skip some C-extension modules that are not publicly accessible.                 │
│   1392 │   │   try:                                                                              │
│ ❱ 1393 │   │   │   results = parse_results[current.__module__]                                   │
│   1394 │   │   except ModuleNotFoundError as error:                                              │
│   1395 │   │   │   if getattr(current, "__file__", None) is None or Path(                        │
│   1396 │   │   │   │   current.__file__                                                          │
│                                                                                                  │
│ /home/lucas/Documents/GitHub/pynguin/src/pynguin/analyses/module.py:1285 in __missing__          │
│                                                                                                  │
│   1282 │                                                                                         │
│   1283 │   def __missing__(self, key):                                                           │
│   1284 │   │   # Parse module on demand                                                          │
│ ❱ 1285 │   │   res = self[key] = parse_module(key, query_type4py=self._query_type4py)            │
│   1286 │   │   return res                                                                        │
│   1287                                                                                           │
│   1288                                                                                           │
│                                                                                                  │
│ /home/lucas/Documents/GitHub/pynguin/src/pynguin/analyses/module.py:284 in parse_module          │
│                                                                                                  │
│    281 │   Returns:                                                                              │
│    282 │   │   A tuple of the imported module type and its optional AST                          │
│    283 │   """                                                                                   │
│ ❱  284 │   module = import_module(module_name)                                                   │
│    285 │   type4py_data: Type4pyData | None = None                                               │
│    286 │   syntax_tree: astroid.Module | None = None                                             │
│    287 │   linenos: int = -1                                                                     │
│                                                                                                  │
│ /home/lucas/Documents/GitHub/pynguin/src/pynguin/analyses/module.py:244 in import_module         │
│                                                                                                  │
│    241 │   │   The imported module                                                               │
│    242 │   """
│    243 │   try:                                                                                  │
│ ❱  244 │   │   return importlib.import_module(module_name)                                       │
│    245 │   except ModuleNotFoundError as error:                                                  │
│    246 │   │   try:                                                                              │
│    247 │   │   │   package_name, submodule_name = module_name.rsplit(".", 1)                     │
│                                                                                                  │
│ /home/lucas/.conda/envs/2324-master-thesis/lib/python3.10/importlib/__init__.py:117 in           │
│ import_module                                                                                    │
│                                                                                                  │
│   114 │                                                                                          │
│   115 │   """                                                                                    │
│   116 │   level = 0                                                                              │
│ ❱ 117 │   if name.startswith('.'):                                                               │
│   118 │   │   if not package:                                                                    │
│   119 │   │   │   msg = ("the 'package' argument is required to perform a relative "             │
│   120 │   │   │   │      "import for {!r}")                                                      │
╰──────────────────────────────────────────────────────────────────────────────────────────────────╯
AttributeError: 'property' object has no attribute 'startswith'

This error comes from the _ObjectProxyMethods class that overrides the __module__ attribute. I think that normally, the __module__ attribute is defined on the class and not on instances. I tried to create a custom property class that would keep the existing behavior but that would add the normal behavior on the _ObjectProxyMethods class but that custom property would only work on "normal" methods and not on dunder methods so this idea was abandoned. Instead, I've just added a condition that skips when a __module__ attribute is a property instead of a str.

What do you think of this solution?

Kind regards and have a nice day!

@stephanlukasczyk
Copy link
Member

The _ObjectProxyMethods is quite a fragile thing, I agree. It's probably not optimal regarding usability and portability to different subjects. C extensions always cause trouble, also regarding the bytecode instrumentation. However, I appreciate your approach and will happily incorporate in the upcoming Pynguin release.

stephanlukasczyk added a commit that referenced this pull request Feb 22, 2024
- Fix `TypeError` bug in instrumentation of bytecode (closes GitHub PR
  #51)
- Add a dump method for type-information statistics
- Fix handling of aliased modules (fixes GitHub issue #57, merges #58)
- Fix method-signature handling for C extensions (fixed GitHub issue
  #59, merges #60)
@stephanlukasczyk stephanlukasczyk merged commit 8ff615d into se2p:main Feb 22, 2024
@BergLucas BergLucas deleted the fix/c-extension-module branch February 22, 2024 15:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants