Skip to content

Commit

Permalink
Merge pull request #51 from avengineers/feature/48-array_params
Browse files Browse the repository at this point in the history
Supports array and function-type parameters
  • Loading branch information
theHolgi committed Mar 21, 2024
2 parents 7b0d938 + d488a3d commit c71d014
Show file tree
Hide file tree
Showing 8 changed files with 289 additions and 75 deletions.
169 changes: 132 additions & 37 deletions hammocking/hammocking.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,73 @@
from argparse import ArgumentParser
from pathlib import Path
from typing import List, Set, Union, Tuple, Iterator, Iterable, Optional
from clang.cindex import Index, TranslationUnit, Cursor, CursorKind, Config, TypeKind
from clang.cindex import Index, TranslationUnit, Cursor, CursorKind, Config, Type, TypeKind
from jinja2 import Environment, FileSystemLoader
import logging
import configparser


class RenderableType:
def __init__(self, t):
self.t = t

@staticmethod
def _collect_arguments(params) -> str:
# TODO: Merge with Function _collect_arguments
unnamed_index = 1
arguments = []
for param in params:
if not param.name:
param.name = 'unnamed' + str(unnamed_index)
unnamed_index = unnamed_index + 1
arguments.append(param.get_definition(True))

return ", ".join(arguments)

def render(self, name) -> str:
if self.t.kind == TypeKind.CONSTANTARRAY:
res = f"{name}[{self.t.get_array_size()}]"
element_type = RenderableType(self.t.get_array_element_type())
return element_type.render(res)
elif self.t.kind == TypeKind.INCOMPLETEARRAY:
res = f"{name}[]"
element_type = RenderableType(self.t.get_array_element_type())
return element_type.render(res)
elif self.t.kind == TypeKind.POINTER and self.t.get_pointee().kind == TypeKind.FUNCTIONPROTO:
# param is of type function pointer
pt = self.t.get_pointee()
args = [arg for arg in pt.argument_types()]
return f"{pt.get_result().spelling} (*{name})({','.join(arg.spelling for arg in pt.argument_types())})"
else:
return self.t.spelling + " " + name

@property
def is_basic(self) -> bool:
if self.t.kind == TypeKind.CONSTANTARRAY:
return False
return True

@property
def is_constant(self) -> bool:
return self.t.kind == TypeKind.CONSTANTARRAY or self.t.is_const_qualified()

@property
def is_array(self) -> bool:
# many array kinds will make problems, but they are array types.
return self.t.kind == TypeKind.CONSTANTARRAY \
or self.t.kind == TypeKind.INCOMPLETEARRAY \
or self.t.kind == TypeKind.VARIABLEARRAY \
or self.t.kind == TypeKind.DEPENDENTSIZEDARRAY

@property
def is_struct(self) -> bool:
fields = list(self.t.get_canonical().get_fields())
return len(fields) > 0

@property
def spelling(self) -> str:
return self.t.spelling

class ConfigReader:
section = "hammocking"
configfile = Path(__file__).parent / (section + ".ini")
Expand Down Expand Up @@ -49,45 +111,81 @@ def _scan(self, items: Iterator[Tuple[str, str]]) -> None:


class Variable:
def __init__(self, type: str, name: str, size: int = 0) -> None:
self.type = type
self.name = name
self.size = size
def __init__(self, c: Cursor) -> None:
self._type = RenderableType(c.type)
self.name = c.spelling

def get_definition(self) -> str:
return f"{self.type} {self.name}" + (f"[{self.size}]" if self.size > 0 else "")
@property
def type(self) -> str:
return self._type.spelling

def get_definition(self, with_type: bool = True) -> str:
if with_type:
return self._type.render(self.name)
else:
return self.name

def is_constant(self) -> bool:
return self._type.is_constant

def initializer(self) -> str:
if self._type.is_struct:
return f"({self._type.spelling}){{0}}"
elif self._type.is_array:
return "{0}"
else:
return f"({self._type.spelling})0"

def __repr__(self) -> str:
return f"<{self.get_definition()}>"

class Function:
def __init__(self, type: str, name: str, params: List[Variable], is_variadic: bool = False) -> None:
self.type = type
self.name = name
self.params = params
self.is_variadic = is_variadic
def __init__(self, c: Cursor) -> None:
self.type = RenderableType(c.result_type)
self.name = c.spelling
self.params = [Variable(arg) for arg in c.get_arguments()]
self.is_variadic = c.type.is_function_variadic() if c.type.kind == TypeKind.FUNCTIONPROTO else False

def get_signature(self) -> str:
return f"{self.type} {self.name}({self._collect_arguments(True)}{', ...' if self.is_variadic else ''})"
"""
Return the function declaration form
"""
return f"{self.type.render(self.name)}({self._collect_arguments(True)}{', ...' if self.is_variadic else ''})"

def _collect_arguments(self, with_types: bool) -> str:
unnamed_index = 1
arguments = []
for param in self.params:
arg_name = param.name if param.name else 'unnamed' + str(unnamed_index)
unnamed_index = unnamed_index + 1
arg_type = param.type + ' ' if with_types else ''
arguments.append(f"{arg_type}{arg_name}")
if not param.name:
param.name = 'unnamed' + str(unnamed_index)
unnamed_index = unnamed_index + 1
arguments.append(param.get_definition(with_types))

return ", ".join(arguments)

def has_return_value(self) -> bool:
return self.type != "void"
return self.type.t.kind != TypeKind.VOID

@property
def return_type(self) -> str:
return self.type.spelling # rendering includes the name, which is not what the user wants here.

def get_call(self) -> str:
return f"{self.name}({self._collect_arguments(False)})"
"""
Return a piece of C code to call the function
"""
if self.is_variadic and False: # TODO
return "TODO"
else:
return f"{self.name}({self._collect_arguments(False)})"

def get_param_types(self) -> str:
param_types = ", ".join(f"{param.type}" for param in self.params)
return f"{param_types}"

def __repr__(self) -> str:
return f"<{self.type} {self.name} ()>"


class MockupWriter:

Expand All @@ -98,6 +196,7 @@ def __init__(self, mockup_style="gmock", suffix=None) -> None:
self.template_dir = f"{dirname(__file__)}/templates"
self.mockup_style = mockup_style
self.suffix = suffix or ""
self.logger = logging.getLogger("HammocKing")
self.environment = Environment(
loader=FileSystemLoader(f"{self.template_dir}/{self.mockup_style}"),
keep_trailing_newline=True,
Expand All @@ -112,15 +211,15 @@ def add_header(self, name: str) -> None:
if name not in self.headers:
self.headers.append(name)

def add_variable(self, type: str, name: str, size: int = 0) -> None:
def add_variable(self, c: Cursor) -> None:
"""Add a variable definition"""
logging.info(f"HammocKing: Create mockup for variable {name}")
self.variables.append(Variable(type, name, size))
self.logger.info(f"Create mockup for variable {c.spelling}")
self.variables.append(Variable(c))

def add_function(self, type: str, name: str, params: List[Tuple[str, str]] = [], is_variadic: bool = False) -> None:
def add_function(self, c: Cursor) -> None:
"""Add a variable definition"""
logging.info(f"Create mockup for function {name}")
self.functions.append(Function(type, name, [Variable(param[0], param[1]) for param in params], is_variadic))
self.logger.info(f"Create mockup for function {c.spelling}")
self.functions.append(Function(c))

def get_mockup(self, file: str) -> str:
return self.render(Path(file + '.j2'))
Expand Down Expand Up @@ -188,10 +287,12 @@ def parse(self, input: Union[Path, str]) -> None:
if issubclass(type(input), Path):
# Read a path
parseOpts["path"] = input
basepath = input.parent.absolute()
else:
# Interpret a string as content of the file
parseOpts["path"] = "~.c"
parseOpts["unsaved_files"] = [("~.c", input)]
basepath = Path.cwd()

self.logger.debug(f"Symbols to be mocked: {self.symbols}")
translation_unit = Index.create(excludeDecls=True).parse(**parseOpts)
Expand All @@ -205,20 +306,14 @@ def parse(self, input: Union[Path, str]) -> None:
self.logger.debug(f"Found {child.spelling} in {child.location.file}")
in_header = child.location.file.name != translation_unit.spelling
if in_header: # We found it in the Source itself. Better not include the whole source!
self.writer.add_header(str(child.location.file))
headerpath = child.location.file.name
if headerpath.startswith("./"): # Replace reference to current directory with CWD's path
headerpath = (basepath / headerpath[2:]).as_posix()
self.writer.add_header(headerpath)
if child.kind == CursorKind.VAR_DECL:
child_type_array_size = child.type.get_array_size()
if child_type_array_size > 0:
self.writer.add_variable(child.type.element_type.spelling, child.spelling, child_type_array_size)
else:
self.writer.add_variable(child.type.spelling, child.spelling)
self.writer.add_variable(child)
elif child.kind == CursorKind.FUNCTION_DECL:
self.writer.add_function(
child.type.get_result().spelling,
child.spelling,
[(arg.type.spelling, arg.spelling) for arg in child.get_arguments()],
is_variadic = child.type.is_function_variadic() if child.type.kind == TypeKind.FUNCTIONPROTO else False
)
self.writer.add_function(child)
else:
self.logger.warning(f"Unknown kind of symbol: {child.kind}")
self.symbols.remove(child.spelling)
Expand Down
6 changes: 5 additions & 1 deletion hammocking/templates/gmock/mockup.cc.j2
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,11 @@
class_mockup *mockup_global_ptr = 0;

{% for variable in variables %}
{% if variable.is_constant() %}
{{ variable.get_definition() }} = {{ variable.initializer() }};
{% else %}
{{ variable.get_definition() }};
{% endif %}
{% endfor %}

extern "C" {
Expand All @@ -14,7 +18,7 @@ extern "C" {
if(0 != mockup_global_ptr)
return mockup_global_ptr->{{function.get_call()}};
else
return ({{function.type}})0;
return ({{function.return_type}})0;
{% else %}
if(0 != mockup_global_ptr)
mockup_global_ptr->{{function.get_call()}};
Expand Down
2 changes: 1 addition & 1 deletion hammocking/templates/gmock/mockup.h.j2
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ class class_mockup {

public:
{% for function in functions %}
MOCK_METHOD(({{function.type}}), {{function.name}}, ({{function.get_param_types()}}));
MOCK_METHOD(({{function.return_type}}), {{function.name}}, ({{function.get_param_types()}}));
{% endfor %}
}; /* class_mockup */

Expand Down
6 changes: 5 additions & 1 deletion hammocking/templates/plain_c/mockup.c.j2
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
#include "mockup{{suffix}}.h"

{% for variable in variables %}
{% if variable.is_constant %}
{{ variable.get_definition() }} = {{ variable.initializer() }};
{% else %}
{{ variable.get_definition() }};
{% endif %}
{% endfor %}


{% for function in functions %}

{{function.get_signature()}}{
{% if function.has_return_value() %}
return 0;
return ({{function.return_type}})0;
{% endif %}
} /* {{function.name}} */
{% endfor %}
12 changes: 7 additions & 5 deletions tests/data/gmock_test/test_mini_c_gmock/mockup.cc
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@
class_mockup *mockup_global_ptr = 0;

int a_y1;
a_y4_t a_y4;
int a_y4;
int c_u1;
const int const_a = (const int)0;
const int const_array[3] = {0};

extern "C" {

Expand All @@ -22,14 +24,14 @@ int a_get_y3_and_set_u5(int u5){
return (int)0;
} /* a_get_y3_and_set_u5 */

a_y5_t a_get_y5(){
int a_get_y5(){
if(0 != mockup_global_ptr)
return mockup_global_ptr->a_get_y5();
else
return (a_y5_t)0;
return (int)0;
} /* a_get_y5 */

void a_get_y6(int* y6){
void a_get_y6(int * y6){
if(0 != mockup_global_ptr)
mockup_global_ptr->a_get_y6(y6);
} /* a_get_y6 */
Expand All @@ -51,7 +53,7 @@ void c_set_u3_and_u4(int u3, int u4){
mockup_global_ptr->c_set_u3_and_u4(u3, u4);
} /* c_set_u3_and_u4 */

void c_set_u6(c_u6_t u6){
void c_set_u6(int u6){
if(0 != mockup_global_ptr)
mockup_global_ptr->c_set_u6(u6);
} /* c_set_u6 */
Expand Down
6 changes: 3 additions & 3 deletions tests/data/gmock_test/test_mini_c_gmock/mockup.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,12 @@ class class_mockup {
public:
MOCK_METHOD((int), a_get_y2, ());
MOCK_METHOD((int), a_get_y3_and_set_u5, (int));
MOCK_METHOD((a_y5_t), a_get_y5, ());
MOCK_METHOD((void), a_get_y6, (int*));
MOCK_METHOD((int), a_get_y5, ());
MOCK_METHOD((void), a_get_y6, (int *));
MOCK_METHOD((int), c_get_y3_and_set_u5, (int));
MOCK_METHOD((void), c_set_u2, (int));
MOCK_METHOD((void), c_set_u3_and_u4, (int, int));
MOCK_METHOD((void), c_set_u6, (c_u6_t));
MOCK_METHOD((void), c_set_u6, (int));
}; /* class_mockup */

extern class_mockup *mockup_global_ptr;
Expand Down
5 changes: 5 additions & 0 deletions tests/data/mini_c_test/includes/a.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@ void a_step(void);
extern int a_u1;
extern int a_y1;

typedef struct {
int a;
int b;
} s_t;

void a_set_u2(int u2);
int a_get_y2(void);

Expand Down
Loading

0 comments on commit c71d014

Please sign in to comment.