1 /** 2 Date: 2015-2017, Joakim Brännström 3 License: MPL-2, Mozilla Public License 2.0 4 Author: Joakim Brännström (joakim.brannstrom@gmx.com) 5 6 Generate a google mock implementation of a C++ class with at least one virtual. 7 */ 8 module cpptooling.generator.gmock; 9 10 import std.ascii : newline; 11 import std.algorithm : joiner, map; 12 import std.conv : text; 13 import std.format : format; 14 import std.range : chain, only, retro, takeOne; 15 import std.typecons : Yes, No, Flag; 16 import std.variant : visit; 17 18 import logger = std.experimental.logger; 19 20 import dsrcgen.cpp : CppModule, noIndent; 21 22 import dextool.type : DextoolVersion, CustomHeader, FileName; 23 import cpptooling.data.representation : CppCtor, CppDtor, CppClass, 24 CppNamespace, CppMethodOp, CppMethod, joinParams, joinParamNames; 25 import cpptooling.data : toStringDecl; 26 27 @safe: 28 29 private string gmockMacro(size_t len, bool isConst) 30 in { 31 assert(len <= 10); 32 } 33 body { 34 if (isConst) 35 return "MOCK_CONST_METHOD" ~ len.text; 36 else 37 return "MOCK_METHOD" ~ len.text; 38 } 39 40 private void ignore() { 41 } 42 43 private void genOp(const CppMethodOp m, CppModule hdr) { 44 import cpptooling.data : MemberVirtualType; 45 46 static string translateOp(string op) { 47 switch (op) { 48 case "=": 49 return "opAssign"; 50 case "==": 51 return "opEqual"; 52 default: 53 logger.errorf( 54 "Operator '%s' is not supported. Create an issue on github containing the operator and example code.", 55 op); 56 return "operator not supported"; 57 } 58 } 59 60 static void genMockMethod(const CppMethodOp m, CppModule hdr) { 61 string params = m.paramRange().joinParams(); 62 string gmock_name = translateOp(m.op); 63 string gmock_macro = gmockMacro(m.paramRange().length, m.isConst); 64 //TODO should use the toString function for TypeKind + TypeAttr, otherwise const isn't affecting it. 65 string stmt = format("%s(%s, %s(%s))", gmock_macro, gmock_name, 66 m.returnType.toStringDecl, params); 67 hdr.stmt(stmt); 68 } 69 70 static void genMockCaller(const CppMethodOp m, CppModule hdr) { 71 import dsrcgen.cpp : E; 72 73 string gmock_name = translateOp(m.op); 74 75 //TODO should use the toString function for TypeKind + TypeAttr, otherwise const isn't affecting it. 76 CppModule code = hdr.method_inline(Yes.isVirtual, m.returnType.toStringDecl, 77 m.name, m.isConst ? Yes.isConst : No.isConst, m.paramRange().joinParams()); 78 auto call = E(gmock_name)(m.paramRange().joinParamNames); 79 80 if (m.returnType.toStringDecl == "void") { 81 code.stmt(call); 82 } else { 83 code.return_(call); 84 } 85 } 86 87 genMockMethod(m, hdr); 88 genMockCaller(m, hdr); 89 } 90 91 private void genMethod(const CppMethod m, CppModule hdr) { 92 enum MAX_GMOCK_PARAMS = 10; 93 94 void genMethodWithFewParams(const CppMethod m, CppModule hdr) { 95 hdr.stmt(format("%s(%s, %s(%s))", gmockMacro(m.paramRange().length, 96 m.isConst), m.name, m.returnType.toStringDecl, m.paramRange().joinParams())); 97 return; 98 } 99 100 void genMethodWithManyParams(const CppMethod m, CppModule hdr) { 101 import std.range : chunks, enumerate, dropBackOne; 102 103 static string partName(string name, size_t part_no) { 104 return format("%s_MockPart%s", name, part_no); 105 } 106 107 static void genPart(T)(size_t part_no, T a, const CppMethod m, 108 CppModule code, CppModule delegate_mock) { 109 import dsrcgen.cpp : E; 110 111 // dfmt off 112 // inject gmock macro 113 code.stmt(format("%s(%s, void(%s))", 114 gmockMacro(a.length, m.isConst), 115 partName(m.name, part_no), 116 a.joinParams)); 117 // inject delegation call to gmock macro 118 delegate_mock.stmt(E(partName(m.name, part_no))(a.joinParamNames)); 119 // dfmt on 120 } 121 122 static void genLastPart(T)(size_t part_no, T p, const CppMethod m, 123 CppModule code, CppModule delegate_mock) { 124 import dsrcgen.cpp : E; 125 126 auto part_name = partName(m.name, part_no); 127 code.stmt(format("%s(%s, %s(%s))", gmockMacro(p.length, m.isConst), 128 part_name, m.returnType.toStringDecl, p.joinParams)); 129 130 auto stmt = E(part_name)(p.joinParamNames); 131 132 if (m.returnType.toStringDecl == "void") { 133 delegate_mock.stmt(stmt); 134 } else { 135 delegate_mock.return_(stmt); 136 } 137 } 138 139 // Code block for gmock macros 140 auto code = hdr.base(); 141 code.suppressIndent(1); 142 143 // Generate mock method that delegates to partial mock methods 144 auto delegate_mock = hdr.method_inline(Yes.isVirtual, m.returnType.toStringDecl, 145 m.name, cast(Flag!"isConst") m.isConst, m.paramRange().joinParams()); 146 147 auto param_chunks = m.paramRange.chunks(MAX_GMOCK_PARAMS); 148 149 // dfmt off 150 foreach (a; param_chunks 151 .save // don't modify the range 152 .dropBackOne // separate last chunk to simply logic, 153 // all methods will thus return void 154 .enumerate(1)) { 155 genPart(a.index, a.value, m, code, delegate_mock); 156 } 157 // dfmt on 158 159 // if the mocked function returns a value it is simulated via the "last 160 // part". 161 genLastPart(param_chunks.length, param_chunks.back, m, code, delegate_mock); 162 } 163 164 if (m.paramRange().length <= MAX_GMOCK_PARAMS) { 165 genMethodWithFewParams(m, hdr); 166 } else { 167 genMethodWithManyParams(m, hdr); 168 } 169 } 170 171 /** Generate a Google Mock that implements in_c. 172 * 173 * Gmock has a restriction of max 10 parameters in a method. This gmock 174 * generator has a work-around for the limitation by splitting the parameters 175 * over many gmock functions. To fulfill the interface the generator then 176 * generates an inlined function that in turn calls the gmocked functions. 177 * 178 * See test case class_interface_more_than_10_params.hpp. 179 * 180 * Params: 181 * in_c = Class to generate a mock implementation of. 182 * hdr = Header to generate the code in. 183 * ns_name = namespace the mock is generated in. 184 */ 185 void generateGmock(const CppClass in_c, CppModule hdr) 186 in { 187 assert(in_c.isVirtual); 188 } 189 body { 190 // dfmt off 191 // fully qualified class the mock inherit from 192 auto base_class = "public " ~ 193 chain( 194 // when joined ensure the qualifier start with "::" 195 in_c.nsNestingRange.takeOne.map!(a => ""), 196 in_c.nsNestingRange.retro.map!(a => a), 197 only(cast(string) in_c.name)) 198 .joiner("::") 199 .text; 200 // dfmt on 201 auto c = hdr.class_("Mock" ~ in_c.name, base_class); 202 auto pub = c.public_(); 203 pub.dtor(Yes.isVirtual, "Mock" ~ in_c.name)[$.end = " {}" ~ newline]; 204 205 foreach (m; in_c.methodRange()) { 206 // dfmt off 207 () @trusted { 208 m.visit!((const CppMethod m) => genMethod(m, pub), 209 (const CppMethodOp m) => genOp(m, pub), 210 (const CppCtor m) => ignore(), 211 (const CppDtor m) => ignore()); 212 }(); 213 // dfmt on 214 } 215 hdr.sep(2); 216 } 217 218 auto generateGmockHdr(FileName if_file, FileName incl_guard, DextoolVersion ver, 219 CustomHeader custom_hdr, CppModule gmock) { 220 import std.path : baseName; 221 import dsrcgen.cpp : CppHModule; 222 import cpptooling.generator.includes : convToIncludeGuard, makeHeader; 223 224 auto o = CppHModule(convToIncludeGuard(incl_guard)); 225 o.header.append(makeHeader(incl_guard, ver, custom_hdr)); 226 o.content.include(if_file.baseName); 227 o.content.include("gmock/gmock.h"); 228 o.content.sep(2); 229 o.content.append(gmock); 230 231 return o; 232 } 233 234 /** Make a gmock implementation of the class. 235 * 236 * The generated gmock have unique USR and ID. 237 */ 238 auto makeGmock(const CppClass c) { 239 import std.array : array; 240 import std.variant : visit; 241 import cpptooling.data : makeUniqueUSR, nextUniqueID, getName, CppAccess, 242 CppConstMethod, CppVirtualMethod, AccessType, MemberVirtualType; 243 import cpptooling.utility.sort : indexSort; 244 245 // Make all protected and private methods public to allow testing, for good 246 // and bad. The policy can be changed. It is not set in stone. It is what I 247 // thought was good at the time. If it creates problems in the future. 248 // Identified use cases etc. Change it. 249 static auto conv(T)(T m_) { 250 import std.array : array; 251 252 auto params = m_.paramRange.array(); 253 254 auto m = CppMethod(m_.usr, m_.name, params, m_.returnType, CppAccess(AccessType.Public), 255 CppConstMethod(m_.isConst), CppVirtualMethod(MemberVirtualType.Pure)); 256 257 return m; 258 } 259 260 auto rclass = CppClass(c.name, c.inherits, c.resideInNs); 261 rclass.usr = makeUniqueUSR; 262 rclass.unsafeForceID = nextUniqueID; 263 264 //dfmt off 265 foreach (m_in; c.methodRange 266 .array() 267 .indexSort!((ref a, ref b) => getName(a) < getName(b)) 268 ) { 269 () @trusted{ 270 m_in.visit!((const CppMethod m) => m.isVirtual ? rclass.put(conv(m)) : false, 271 (const CppMethodOp m) => m.isVirtual ? rclass.put(conv(m)) : false, 272 (const CppCtor m) {}, 273 (const CppDtor m) => m.isVirtual ? rclass.put(m) : false); 274 }(); 275 } 276 //dfmt on 277 278 return rclass; 279 }