diff --git a/hammocking/hammocking.py b/hammocking/hammocking.py index b3d87b0..1089287 100755 --- a/hammocking/hammocking.py +++ b/hammocking/hammocking.py @@ -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") @@ -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: @@ -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, @@ -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')) @@ -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) @@ -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) diff --git a/hammocking/templates/gmock/mockup.cc.j2 b/hammocking/templates/gmock/mockup.cc.j2 index 59975c4..cb359da 100644 --- a/hammocking/templates/gmock/mockup.cc.j2 +++ b/hammocking/templates/gmock/mockup.cc.j2 @@ -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" { @@ -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()}}; diff --git a/hammocking/templates/gmock/mockup.h.j2 b/hammocking/templates/gmock/mockup.h.j2 index 0ce2855..2ad3516 100644 --- a/hammocking/templates/gmock/mockup.h.j2 +++ b/hammocking/templates/gmock/mockup.h.j2 @@ -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 */ diff --git a/hammocking/templates/plain_c/mockup.c.j2 b/hammocking/templates/plain_c/mockup.c.j2 index a6bd404..581dcb1 100644 --- a/hammocking/templates/plain_c/mockup.c.j2 +++ b/hammocking/templates/plain_c/mockup.c.j2 @@ -1,7 +1,11 @@ #include "mockup{{suffix}}.h" {% for variable in variables %} +{% if variable.is_constant %} +{{ variable.get_definition() }} = {{ variable.initializer() }}; +{% else %} {{ variable.get_definition() }}; +{% endif %} {% endfor %} @@ -9,7 +13,7 @@ {{function.get_signature()}}{ {% if function.has_return_value() %} - return 0; + return ({{function.return_type}})0; {% endif %} } /* {{function.name}} */ {% endfor %} diff --git a/tests/data/gmock_test/test_mini_c_gmock/mockup.cc b/tests/data/gmock_test/test_mini_c_gmock/mockup.cc index 4ec311b..5b2a464 100644 --- a/tests/data/gmock_test/test_mini_c_gmock/mockup.cc +++ b/tests/data/gmock_test/test_mini_c_gmock/mockup.cc @@ -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" { @@ -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 */ @@ -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 */ diff --git a/tests/data/gmock_test/test_mini_c_gmock/mockup.h b/tests/data/gmock_test/test_mini_c_gmock/mockup.h index c04615b..2c91875 100644 --- a/tests/data/gmock_test/test_mini_c_gmock/mockup.h +++ b/tests/data/gmock_test/test_mini_c_gmock/mockup.h @@ -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; diff --git a/tests/data/mini_c_test/includes/a.h b/tests/data/mini_c_test/includes/a.h index f52d665..424521b 100644 --- a/tests/data/mini_c_test/includes/a.h +++ b/tests/data/mini_c_test/includes/a.h @@ -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); diff --git a/tests/hammocking_test.py b/tests/hammocking_test.py index 56529f0..a99da6d 100644 --- a/tests/hammocking_test.py +++ b/tests/hammocking_test.py @@ -9,22 +9,60 @@ # Apply default config ConfigReader() +def clang_parse(snippet: str): + parseOpts = { + "path": "~.c", + "unsaved_files": [("~.c", snippet)], + "options": TranslationUnit.PARSE_SKIP_FUNCTION_BODIES | TranslationUnit.PARSE_INCOMPLETE, + } + translation_unit = Index.create(excludeDecls=True).parse(**parseOpts) + def is_var_or_func(c: Cursor) -> bool: + return c.kind == CursorKind.VAR_DECL or c.kind == CursorKind.FUNCTION_DECL + return next(filter (is_var_or_func, Hammock.iter_children(translation_unit.cursor))) + class TestVariable: - def test_creation(self): - v = Variable("char", "x") + def test_simple(self): + v = Variable(clang_parse("char x")) assert v.name == "x" - assert v.type == "char" assert v.get_definition() == "char x" - w = Variable("int", "my_array", 2) + def test_array(self): + w = Variable(clang_parse("int my_array[2]")) assert w.name == "my_array" - assert w.type == "int" assert w.get_definition() == "int my_array[2]" + def test_unlimited_array(self): + w = Variable(clang_parse("int my_array[]")) + assert w.name == "my_array" + assert w.get_definition() == "int my_array[]" + + def test_constant(self): + w = Variable(clang_parse("const int y;")) + assert w.name == "y" + assert w.is_constant() == True + assert w.get_definition() == "const int y" + assert w.initializer() == "(const int)0" + + def test_constant_array(self): + w = Variable(clang_parse("const int y[3];")) + assert w.name == "y" + assert w.is_constant() == True + assert w.get_definition() == "const int y[3]" + assert w.initializer() == "{0}" + + def test_constant_struct(self): + w = Variable(clang_parse(""" + typedef struct { int a; int b; } y_t; + extern const y_t y;""")) + assert w.name == "y" + assert w.is_constant() == True + assert w.get_definition() == "const y_t y" + assert w.initializer() == "(const y_t){0}" + class TestFunction: def test_void_void(self): - f = Function(type="void", name="func", params=[]) + f = Function(clang_parse("void func(void);")) assert f.name == "func" assert f.get_signature() == "void func()" assert f.get_call() == "func()" @@ -32,7 +70,7 @@ def test_void_void(self): assert f.has_return_value() == False def test_void_int(self): - f = Function(type="void", name="set", params=[Variable('int', 'a')]) + f = Function(clang_parse("void set(int a);")) assert f.name == "set" assert f.get_signature() == "void set(int a)" assert f.get_call() == "set(a)" @@ -40,7 +78,7 @@ def test_void_int(self): assert f.has_return_value() == False def test_int_void(self): - f = Function(type="int", name="get", params=[]) + f = Function(clang_parse("int get(void);")) assert f.name == "get" assert f.get_signature() == "int get()" assert f.get_call() == "get()" @@ -48,7 +86,7 @@ def test_int_void(self): assert f.has_return_value() == True def test_void_int_double(self): - f = Function(type="void", name="set", params=[Variable('int', 'a'), Variable('double', 'b')]) + f = Function(clang_parse("void set(int a, double b);")) assert f.name == "set" assert f.get_signature() == "void set(int a, double b)" assert f.get_call() == "set(a, b)" @@ -56,12 +94,49 @@ def test_void_int_double(self): # assert f.has_return_value() == False def test_function_with_unnamed_arguments(self): - f = Function(type="float", name="my_func", params=[Variable('float', ''), Variable('float', '')]) + f = Function(clang_parse("float my_func(float, float);")) assert f.name == "my_func" assert f.get_signature() == "float my_func(float unnamed1, float unnamed2)" assert f.get_call() == "my_func(unnamed1, unnamed2)" assert f.get_param_types() == "float, float" + def test_variadic_function(self): + f = Function(clang_parse("int printf_func(const char* fmt, ...);")) + assert f.name == "printf_func" + assert f.get_signature() == "int printf_func(const char * fmt, ...)" + assert f.get_call() == "printf_func(fmt)" # TODO + assert f.get_param_types() == "const char *" # ? + + def test_array_param(self): + f = Function(clang_parse("void x(int arg[]);")) + assert f.name == "x" + assert f.get_signature() == "void x(int arg[])" + assert f.get_call() == "x(arg)" + assert f.get_param_types() == "int[]" + + def test_funcptr_param(self): + f = Function(clang_parse("void x(int (*cb)(void));")) + assert f.name == "x" + assert f.get_signature() == "void x(int (*cb)())" + assert f.get_call() == "x(cb)" + assert f.get_param_types() == "int (*)(void)" + + def test_blank_func(self): + f = Function(clang_parse("void x();")) + assert f.name == "x" + assert f.get_signature() == "void x()" + assert f.get_call() == "x()" + assert f.get_param_types() == "" + + + def test_ptr_return(self): + f = Function(clang_parse("const char* const x(void);")) + assert f.name == "x" + assert f.return_type == "const char *const" + assert f.get_signature() == "const char *const x()" + assert f.get_call() == "x()" + assert f.get_param_types() == "" + class TestMockupWriter: def test_empty_templates(self): @@ -114,9 +189,9 @@ class class_mockup { def test_add_variable(self): writer = MockupWriter(suffix="_new") - writer.add_variable("float", "y") - writer.add_variable("unsigned int", "a") - writer.add_variable("int", "x") + writer.add_variable(clang_parse("float y")) + writer.add_variable(clang_parse("unsigned int a")) + writer.add_variable(clang_parse("int x")) assert ( writer.get_mockup('mockup.h') @@ -158,13 +233,13 @@ class class_mockup { def test_add_function_get(self): writer = MockupWriter() - writer.add_function("int", "a_get_y2", []) + writer.add_function(clang_parse("int a_get_y2();")) assert writer.get_mockup('mockup.h') == open("tests/data/gmock_test/test_add_function_get/mockup.h").read() assert writer.get_mockup('mockup.cc') == open("tests/data/gmock_test/test_add_function_get/mockup.cc").read() def test_add_function_set_one_arg(self): writer = MockupWriter() - writer.add_function("void", "set_some_int", [("int", "some_value")]) + writer.add_function(clang_parse("void set_some_int(int some_value);")) assert writer.get_mockup('mockup.h') == open( "tests/data/gmock_test/test_add_function_set_one_arg/mockup.h").read() assert writer.get_mockup('mockup.cc') == open( @@ -172,7 +247,7 @@ def test_add_function_set_one_arg(self): def test_add_function_with_unnamed_arg(self): writer = MockupWriter() - writer.add_function("float", "my_func", [("float", "")]) + writer.add_function(clang_parse("float my_func(float);")) assert writer.get_mockup('mockup.h') == open( "tests/data/gmock_test/test_add_function_with_unnamed_arg/mockup.h").read() assert writer.get_mockup('mockup.cc') == open( @@ -182,17 +257,19 @@ def test_mini_c_gmock(self): writer = MockupWriter() writer.add_header("a.h") writer.add_header("c.h") - writer.add_variable("int", "a_y1") - writer.add_variable("int", "c_u1") - writer.add_variable("a_y4_t", "a_y4") - writer.add_function("int", "a_get_y2", []) - writer.add_function("int", "a_get_y3_and_set_u5", [("int", "u5")]) - writer.add_function("a_y5_t", "a_get_y5", []) - writer.add_function("void", "a_get_y6", [("int*", "y6")]) - writer.add_function("int", "c_get_y3_and_set_u5", [("int", "u5")]) - writer.add_function("void", "c_set_u2", [("int", "u2")]) - writer.add_function("void", "c_set_u3_and_u4", [("int", "u3"), ("int", "u4")]) - writer.add_function("void", "c_set_u6", [("c_u6_t", "u6")]) + writer.add_variable(clang_parse("extern int a_y1")) + writer.add_variable(clang_parse("extern int c_u1")) + writer.add_variable(clang_parse("extern a_y4_t a_y4")) + writer.add_variable(clang_parse("extern const int const_a;")) + writer.add_variable(clang_parse("extern const int const_array[3];")) + writer.add_function(clang_parse("int a_get_y2();")) + writer.add_function(clang_parse("int a_get_y3_and_set_u5(int u5);")) + writer.add_function(clang_parse("a_y5_t a_get_y5();")) + writer.add_function(clang_parse("void a_get_y6(int* y6);")) + writer.add_function(clang_parse("int c_get_y3_and_set_u5(int u5);")) + writer.add_function(clang_parse("void c_set_u2(int u2);")) + writer.add_function(clang_parse("void c_set_u3_and_u4(int u3, int u4);")) + writer.add_function(clang_parse("void c_set_u6(c_u6_t u6);")) assert writer.get_mockup('mockup.h') == open("tests/data/gmock_test/test_mini_c_gmock/mockup.h").read() assert writer.get_mockup('mockup.cc') == open("tests/data/gmock_test/test_mini_c_gmock/mockup.cc").read() @@ -231,6 +308,33 @@ def test_variable(self): self.assertEqual(len(mock.writer.variables), 1, "Mockup shall have a variable") self.assertEqual(mock.writer.variables[0].get_definition(), "int a", "Variable shall be created in the mockup") + def test_struct_variable(self): + """Mock a struct variable""" + mock = Hammock(["x"]) + self.assertFalse(mock.done, "Should not be done yet") + self.assertListEqual(mock.symbols, ["x"]) + + mock.parse("""typedef struct { int a; int b; } struct_t; + extern struct_t x;""") + self.assertTrue(mock.done, "Should be done now") + self.assertListEqual(mock.symbols, []) + self.assertEqual(len(mock.writer.variables), 1, "Mockup shall have a variable") + self.assertEqual(mock.writer.variables[0].get_definition(), "struct_t x", "Variable shall be created in the mockup") + + def test_const_struct_variable(self): + """Mock a constant struct variable""" + mock = Hammock(["cx"]) + self.assertFalse(mock.done, "Should not be done yet") + self.assertListEqual(mock.symbols, ["cx"]) + + mock.parse("""typedef struct { int a; int b; } struct_t; + extern const struct_t cx;""") + self.assertTrue(mock.done, "Should be done now") + self.assertListEqual(mock.symbols, []) + self.assertEqual(len(mock.writer.variables), 1, "Mockup shall have a variable") + self.assertEqual(mock.writer.variables[0].get_definition(), "const struct_t cx", "Constant shall be created in the mockup") + self.assertEqual(mock.writer.variables[0].initializer(), "(const struct_t){0}", "Constant shall be initialized with struct initializer") + def test_void_func(self): """Mock a void(void) function""" mock = Hammock(["x"])