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 C test double implementation from data about the structural 7 representation. 8 */ 9 module dextool.plugin.ctestdouble.backend.cvariant; 10 11 import std.algorithm : filter, each, map, joiner; 12 import std.typecons : Flag, Yes, No; 13 import logger = std.experimental.logger; 14 import std.array : array, empty; 15 16 import dsrcgen.cpp : CppModule, CppHModule; 17 import my.sumtype; 18 19 import dextool.type : Path, DextoolVersion; 20 import cpptooling.data.symbol; 21 import libclang_ast.ast : Visitor; 22 import cpptooling.testdouble.header_filter : LocationType; 23 import cpptooling.type : MainName, StubPrefix, CustomHeader, MainNs, MainInterface; 24 25 /// Control various aspects of the analyze and generation like what nodes to 26 /// process. 27 @safe interface Controller { 28 /// Query the controller with the filename of the AST node for a decision 29 /// if it shall be processed. 30 bool doFile(in string filename, in string info); 31 32 /** Query the controller for a decision if it shall be processed. */ 33 bool doSymbol(string symbol); 34 35 /** A list of includes for the test double header. 36 * 37 * Part of the controller because they are dynamic, may change depending on 38 * for example calls to doFile. 39 */ 40 Path[] getIncludes(); 41 42 /// Controls generation of google mock. 43 bool doGoogleMock(); 44 45 /// Generate a pre_include header file from internal template? 46 bool doPreIncludes(); 47 48 /// Generate a #include of the pre include header 49 bool doIncludeOfPreIncludes(); 50 51 /// Generate a post_include header file from internal template? 52 bool doPostIncludes(); 53 54 /// Generate a #include of the post include header 55 bool doIncludeOfPostIncludes(); 56 57 /// Generate location of symbols as comments 58 bool doLocationAsComment(); 59 } 60 61 /// Parameters used during generation. 62 /// Important aspect that they do NOT change, therefore it is pure. 63 @safe pure interface Parameters { 64 static struct Files { 65 Path hdr; 66 Path impl; 67 Path globals; 68 Path gmock; 69 Path gmock_impl; 70 Path pre_incl; 71 Path post_incl; 72 } 73 74 /// Source files used to generate the test double. 75 Path[] getIncludes(); 76 77 /// Output directory to store files in. 78 Path getOutputDirectory(); 79 80 /// Files to write generated test double data to. 81 Files getFiles(); 82 83 /// Name affecting interface, namespace and output file. 84 MainName getMainName(); 85 86 /** Namespace for the generated test double. 87 * 88 * Contains the adapter, C++ interface, gmock etc. 89 */ 90 MainNs getMainNs(); 91 92 /** Name of the interface of the test double. 93 * 94 * Used in Adapter. 95 */ 96 MainInterface getMainInterface(); 97 98 /** Prefix to use for the generated files. 99 * 100 * Affects both the filename and the preprocessor #include. 101 */ 102 StubPrefix getFilePrefix(); 103 104 /// Prefix used for test artifacts. 105 StubPrefix getArtifactPrefix(); 106 107 /// Dextool Tool version. 108 DextoolVersion getToolVersion(); 109 110 /// Custom header to prepend generated files with. 111 CustomHeader getCustomHeader(); 112 113 /** If an implementation of the interface for globals that zeroes them 114 * shall be generated. 115 */ 116 Flag!"generateZeroGlobals" generateZeroGlobals(); 117 } 118 119 /// Data produced by the generator like files. 120 @safe interface Products { 121 /** Data pushed from the test double generator to be written to files. 122 * 123 * The put value is the code generation tree. It allows the caller of 124 * Generator to inject more data in the tree before writing. For 125 * example a custom header. 126 * 127 * Params: 128 * fname = file the content is intended to be written to. 129 * hdr_data = data to write to the file. 130 */ 131 void putFile(Path fname, CppHModule hdr_data); 132 133 /// ditto. 134 void putFile(Path fname, CppModule impl_data); 135 136 /** During the translation phase the location of symbols that aren't 137 * filtered out are pushed to the variant. 138 * 139 * It is intended that the variant control the #include directive strategy. 140 * Just the files that was input? 141 * Deduplicated list of files where the symbols was found? 142 */ 143 void putLocation(Path loc, LocationType type); 144 } 145 146 /** Generator of test doubles for C code. 147 */ 148 struct Generator { 149 import cpptooling.data : CppRoot; 150 151 private static struct Modules { 152 static Modules make() @safe { 153 return Modules(new CppModule, new CppModule, new CppModule, new CppModule, new CppModule); 154 } 155 156 CppModule hdr; 157 CppModule impl; 158 CppModule globals; 159 CppModule gmock; 160 CppModule gmock_impl; 161 } 162 163 /// 164 this(Controller ctrl, Parameters params, Products products) { 165 this.ctrl = ctrl; 166 this.params = params; 167 this.products = products; 168 } 169 170 /** Filter and aggregate data for future processing. 171 */ 172 void aggregate(ref CppRoot root, ref Container container) { 173 import std.typecons : Nullable; 174 import cpptooling.data.symbol.types : USRType; 175 176 rawFilter(root, ctrl, products, filtered, 177 (Nullable!USRType usr) => container.find!LocationTag(usr.get)); 178 } 179 180 /** Process structural data to a test double. 181 * 182 * aggregated -> translate -> code generation. 183 * 184 * Translate analyzes what is left after filtering. 185 * On demand extra data is created. An example of on demand is --gmock. 186 * 187 * Code generation is a straight up translation. 188 * Logical decisions should have been handled in earlier stages. 189 */ 190 void process(ref Container container) { 191 logger.tracef("Filtered:\n%s\n", filtered.toString()); 192 193 auto implementation = makeImplementation(filtered, ctrl, params, container); 194 logger.trace("Post processed:\n", implementation.toString()); 195 logger.tracef("kind: %s\nglobals: %s\nadapterKind: %s\n", 196 implementation.kind, implementation.globals, implementation.adapterKind); 197 198 auto m = Modules.make(); 199 generate(implementation, ctrl, params, container, m.hdr, m.impl, 200 m.globals, m.gmock, m.gmock_impl); 201 202 postProcess(m, ctrl, params, products); 203 } 204 205 private: 206 CppRoot filtered; 207 Controller ctrl; 208 Parameters params; 209 Products products; 210 211 static void postProcess(Modules modules, Controller ctrl, Parameters params, Products prod) { 212 import cpptooling.generator.includes : convToIncludeGuard, 213 generatePreInclude, generatePostInclude, makeHeader; 214 215 /** Generate the C++ header file of the test double. 216 * Params: 217 * fname = intended output filename, used for ifndef guard. 218 */ 219 static auto outputHdr(CppModule hdr, Path fname, DextoolVersion ver, 220 CustomHeader custom_hdr) { 221 auto o = CppHModule(convToIncludeGuard(fname)); 222 o.header.append(makeHeader(fname, ver, custom_hdr)); 223 o.content.append(hdr); 224 225 return o; 226 } 227 228 static auto output(CppModule code, Path incl_fname, Path dest, 229 DextoolVersion ver, CustomHeader custom_hdr) { 230 import std.path : baseName; 231 232 auto o = new CppModule; 233 o.suppressIndent(1); 234 o.append(makeHeader(dest, ver, custom_hdr)); 235 o.include(incl_fname.baseName); 236 o.sep(2); 237 o.append(code); 238 239 return o; 240 } 241 242 prod.putFile(params.getFiles.hdr, outputHdr(modules.hdr, 243 params.getFiles.hdr, params.getToolVersion, params.getCustomHeader)); 244 prod.putFile(params.getFiles.impl, output(modules.impl, 245 params.getFiles.hdr, params.getFiles.impl, 246 params.getToolVersion, params.getCustomHeader)); 247 prod.putFile(params.getFiles.globals, output(modules.globals, 248 params.getFiles.hdr, params.getFiles.globals, 249 params.getToolVersion, params.getCustomHeader)); 250 251 if (ctrl.doPreIncludes) { 252 prod.putFile(params.getFiles.pre_incl, generatePreInclude(params.getFiles.pre_incl)); 253 } 254 if (ctrl.doPostIncludes) { 255 prod.putFile(params.getFiles.post_incl, generatePostInclude(params.getFiles.post_incl)); 256 } 257 258 //TODO refactor. should never reach this stage. 259 if (ctrl.doGoogleMock) { 260 import cpptooling.generator.gmock : generateGmockHdr, generateGmockImpl; 261 262 prod.putFile(params.getFiles.gmock, generateGmockHdr(params.getFiles.hdr, 263 params.getFiles.gmock, params.getToolVersion, 264 params.getCustomHeader, modules.gmock)); 265 prod.putFile(params.getFiles.gmock_impl, generateGmockImpl(params.getFiles.gmock, 266 params.getFiles.gmock_impl, params.getToolVersion, 267 params.getCustomHeader, modules.gmock_impl)); 268 } 269 } 270 } 271 272 final class CVisitor : Visitor { 273 import std.typecons : scoped; 274 275 import libclang_ast.ast : VarDecl, FunctionDecl, TranslationUnit, generateIndentIncrDecr; 276 import cpptooling.analyzer.clang.analyze_helper : analyzeFunctionDecl, analyzeVarDecl; 277 import cpptooling.data : CppRoot; 278 import cpptooling.data.symbol : Container; 279 import libclang_ast.cursor_logger : logNode, mixinNodeLog; 280 281 alias visit = Visitor.visit; 282 mixin generateIndentIncrDecr; 283 284 CppRoot root; 285 Container container; 286 287 private { 288 Controller ctrl; 289 Products prod; 290 } 291 292 this(Controller ctrl, Products prod) { 293 this.ctrl = ctrl; 294 this.prod = prod; 295 } 296 297 void clearRoot() @safe { 298 this.root = CppRoot.init; 299 } 300 301 override void visit(scope const VarDecl v) { 302 import cpptooling.data : TypeKindVariable; 303 import clang.c.Index : CX_StorageClass; 304 305 mixin(mixinNodeLog!()); 306 307 //TODO ugly hack. Move this information to the representation. But for 308 //now skipping all definitions 309 if (v.cursor.storageClass() == CX_StorageClass.extern_) { 310 auto result = analyzeVarDecl(v, container, indent); 311 auto var = CxGlobalVariable(result.instanceUSR, 312 TypeKindVariable(result.type, result.name)); 313 root.put(var); 314 } 315 } 316 317 override void visit(scope const FunctionDecl v) { 318 import cpptooling.data.type : CxReturnType; 319 320 mixin(mixinNodeLog!()); 321 322 auto result = analyzeFunctionDecl(v, container, indent); 323 if (result.isValid) { 324 auto func = CFunction(result.type.kind.usr, result.name, result.params, 325 CxReturnType(result.returnType), result.isVariadic, result.storageClass); 326 root.put(func); 327 } 328 } 329 330 override void visit(scope const TranslationUnit v) { 331 import cpptooling.analyzer.clang.type : makeLocation; 332 333 mixin(mixinNodeLog!()); 334 335 LocationTag tu_loc; 336 () @trusted { tu_loc = LocationTag(Location(v.cursor.spelling, 0, 0)); }(); 337 338 if (tu_loc.kind != LocationTag.Kind.noloc && ctrl.doFile(tu_loc.file, 339 "root " ~ tu_loc.toString)) { 340 prod.putLocation(Path(tu_loc.file), LocationType.Root); 341 } 342 343 v.accept(this); 344 } 345 346 void toString(Writer)(scope Writer w) @safe { 347 import std.format : FormatSpec; 348 import std.range.primitives : put; 349 350 auto fmt = FormatSpec!char("%u"); 351 fmt.writeUpToNextSpec(w); 352 353 root.toString(w, fmt); 354 put(w, "\n"); 355 container.toString(w, FormatSpec!char("%s")); 356 } 357 358 override string toString() { 359 import std.exception : assumeUnique; 360 361 char[] buf; 362 buf.reserve(100); 363 toString((const(char)[] s) { buf ~= s; }); 364 auto trustedUnique(T)(T t) @trusted { 365 return assumeUnique(t); 366 } 367 368 return trustedUnique(buf); 369 } 370 } 371 372 private: 373 @safe: 374 375 import cpptooling.data : CppRoot, CppClass, CppMethod, CppCtor, CppDtor, 376 CFunction, CppNamespace, CxGlobalVariable, USRType, LocationTag, Location; 377 import dsrcgen.cpp : E, noIndent; 378 379 /** Contain data for code generation. 380 */ 381 struct ImplData { 382 import cpptooling.data.type : CppMethodName; 383 import dextool.plugin.ctestdouble.backend.adapter : AdapterKind; 384 import dextool.plugin.ctestdouble.backend.global : MutableGlobal; 385 386 CppRoot root; 387 alias root this; 388 389 /// Tagging of nodes in the root 390 Kind[size_t] kind; 391 /// Global, mutable variables 392 MutableGlobal[] globals; 393 /// Constructor kinds for ctors in an adapter 394 AdapterKind[USRType] adapterKind; 395 396 void tag(size_t id, Kind kind_) { 397 kind[id] = kind_; 398 } 399 400 Kind lookup(size_t id) { 401 if (auto k = id in kind) { 402 return *k; 403 } 404 405 return Kind.none; 406 } 407 408 MutableGlobal lookupGlobal(CppMethodName name) { 409 foreach (item; globals) { 410 if (item.name == name) { 411 return item; 412 } 413 } 414 415 // Methods shall always be 1:1 mapped with the globals list. 416 assert(0); 417 } 418 } 419 420 enum Kind { 421 none, 422 /// Adapter class 423 adapter, 424 /// gmock class 425 gmock, 426 /// interface for globals 427 initGlobalInterface, 428 initGlobalsToZero, 429 testDoubleNamespace, 430 testDoubleSingleton, 431 testDoubleInterface, 432 } 433 434 /** Structurally transformed the input to a test double implementation. 435 * 436 * This stage: 437 * - removes C++ code. 438 * - removes according to directives via ctrl. 439 * 440 * Params: 441 * input = structural representation of the source code 442 * ctrl = controll what nodes to keep 443 * prod = push location data of the nodes that are kept 444 * filtered = output structural representation 445 * lookup = callback function supporting lookup of locations 446 */ 447 void rawFilter(LookupT)(ref CppRoot input, Controller ctrl, Products prod, 448 ref CppRoot filtered, LookupT lookup) { 449 import std.range : tee; 450 import cpptooling.data : StorageClass; 451 import cpptooling.generator.utility : filterAnyLocation; 452 453 // dfmt off 454 input.funcRange 455 .filter!(a => !a.usr.isNull) 456 // by definition static functions can't be replaced by test doubles 457 .filter!(a => a.storageClass != StorageClass.Static) 458 // ask controller if the user wants to generate a test double function for the symbol. 459 // note: using the fact that C do NOT have name mangling. 460 .filter!(a => ctrl.doSymbol(a.name)) 461 // ask controller if to generate a test double for the function 462 .filterAnyLocation!(a => ctrl.doFile(a.location.file, cast(string) a.value.name))(lookup) 463 // pass on location as a product to be used to calculate #include 464 .tee!(a => prod.putLocation(Path(a.location.file), LocationType.Leaf)) 465 .each!(a => filtered.put(a.value)); 466 467 input.globalRange() 468 .filter!(a => !a.usr.isNull) 469 // ask controller if the user wants to generate a global for the symbol. 470 // note: using the fact that C do NOT have name mangling. 471 .filter!(a => ctrl.doSymbol(a.name)) 472 // ask controller if to generate a test double for the function 473 .filterAnyLocation!(a => ctrl.doFile(a.location.file, cast(string) a.value.name))(lookup) 474 // pass on location as a product to be used to calculate #include 475 .tee!(a => prod.putLocation(Path(a.location.file), LocationType.Leaf)) 476 .each!(a => filtered.put(a.value)); 477 // dfmt on 478 } 479 480 /** Transform the content of the root to a test double implementation root. 481 * 482 * Make an adapter. 483 * Make a namespace holding the test double. 484 * Make an interface for initialization of globals. 485 * Make a google mock if asked by the user. 486 */ 487 auto makeImplementation(ref CppRoot root, Controller ctrl, Parameters params, 488 ref Container container) @trusted { 489 import cpptooling.data : CppNamespace, CppNs, CppClassName, CppInherit, 490 CppAccess, AccessType, makeUniqueUSR, nextUniqueID, MergeMode; 491 import cpptooling.generator.func : makeFuncInterface; 492 import cpptooling.generator.gmock : makeGmock; 493 import dextool.plugin.ctestdouble.backend.adapter : makeSingleton, makeAdapter; 494 import dextool.plugin.ctestdouble.backend.global : makeGlobalInterface, 495 makeZeroGlobal, filterMutable; 496 497 ImplData impl; 498 impl.root.merge(root, MergeMode.shallow); 499 500 impl.globals = impl.globalRange.filterMutable(container).array; 501 502 const has_mutable_globals = impl.globals.length != 0; 503 const has_functions = !root.funcRange.empty; 504 505 if (!has_functions && !has_mutable_globals) { 506 return impl; 507 } 508 509 auto test_double_ns = CppNamespace.make(CppNs(params.getMainNs)); 510 511 if (has_functions) { 512 auto singleton = makeSingleton(CppNs(params.getMainNs), 513 CppClassName(params.getMainInterface), "test_double_inst"); 514 impl.kind[singleton.id] = Kind.testDoubleSingleton; 515 impl.put(singleton); // (1) 516 517 auto c_if = makeFuncInterface(impl.funcRange, CppClassName(params.getMainInterface)); 518 impl.tag(c_if.id, Kind.testDoubleInterface); 519 test_double_ns.put(c_if); 520 521 if (ctrl.doGoogleMock) { 522 auto mock = makeGmock(c_if); 523 impl.tag(mock.id, Kind.gmock); 524 test_double_ns.put(mock); 525 } 526 } 527 528 if (has_mutable_globals) { 529 auto if_name = CppClassName(params.getMainInterface ~ "_InitGlobals"); 530 auto global_if = makeGlobalInterface(impl.globals[], if_name); 531 impl.tag(global_if.id, Kind.initGlobalInterface); 532 test_double_ns.put(global_if); 533 534 if (params.generateZeroGlobals) { 535 auto global_init_zero = makeZeroGlobal(impl.globals[], 536 CppClassName(params.getArtifactPrefix ~ "ZeroGlobals"), 537 params.getArtifactPrefix, CppInherit(if_name, CppAccess(AccessType.Public))); 538 impl.tag(global_init_zero.id, Kind.initGlobalsToZero); 539 test_double_ns.put(global_init_zero); 540 } 541 } 542 543 // MUST be added after the singleton (1) 544 impl.tag(test_double_ns.id, Kind.testDoubleNamespace); 545 546 { 547 // dfmt off 548 auto adapter = makeAdapter(params.getMainInterface) 549 .makeTestDouble(has_functions) 550 .makeInitGlobals(has_mutable_globals) 551 .makeZeroGlobals(params.generateZeroGlobals) 552 .finalize(impl); 553 impl.tag(adapter.id, Kind.adapter); 554 test_double_ns.put(adapter); 555 // dfmt on 556 } 557 558 impl.put(test_double_ns); 559 return impl; 560 } 561 562 void generate(ref ImplData data, Controller ctrl, Parameters params, ref Container container, 563 CppModule hdr, CppModule impl, CppModule globals, CppModule gmock, CppModule gmock_impl) { 564 import cpptooling.data.symbol.types : USRType; 565 import cpptooling.generator.func : generateFuncImpl; 566 import cpptooling.generator.includes : generateWrapIncludeInExternC; 567 import dextool.plugin.ctestdouble.backend.adapter : generateSingleton; 568 569 generateWrapIncludeInExternC(ctrl, params, hdr); 570 generateGlobal(data.globalRange, ctrl, params, container, globals); 571 572 auto mutable_extern_hook = impl.base; 573 mutable_extern_hook.suppressIndent(1); 574 575 foreach (ns; data.namespaceRange) { 576 switch (data.lookup(ns.id)) { 577 case Kind.testDoubleSingleton: 578 generateSingleton(ns, impl); 579 break; 580 case Kind.testDoubleNamespace: 581 generateNsTestDoubleHdr(ns, 582 cast(Flag!"locationAsComment") ctrl.doLocationAsComment, params, hdr, gmock, 583 (USRType usr) => container.find!LocationTag(usr), (size_t id) => data.lookup( 584 id)); 585 generateNsTestDoubleImpl(ns, params, impl, gmock_impl, 586 mutable_extern_hook, data, params.getArtifactPrefix, container); 587 break; 588 589 default: 590 } 591 } 592 593 // The generated functions must be extern C declared. 594 auto extern_c = impl.suite("extern \"C\""); 595 extern_c.suppressIndent(1); 596 foreach (a; data.funcRange) { 597 generateFuncImpl(a, extern_c); 598 } 599 } 600 601 /// Generate the global definitions and macros for initialization. 602 void generateGlobal(RangeT)(RangeT r, Controller ctrl, Parameters params, 603 ref Container container, CppModule globals) { 604 import cpptooling.data : TypeKind, Void; 605 606 void generateDefinitions(ref CxGlobalVariable global, Flag!"locationAsComment" loc_as_comment, 607 string prefix, ref Container container, CppModule code) 608 in { 609 global.type.kind.info.match!(restrictTo!(TypeKind.CtorInfo, TypeKind.DtorInfo, (_) { 610 assert(0, "wrong type"); 611 }), (_) {}); 612 } 613 do { 614 import std.format : format; 615 import std.string : toUpper; 616 617 string d_name = (prefix ~ "Init_").toUpper ~ global.name; 618 619 if (loc_as_comment) { 620 // dfmt off 621 foreach (loc; container.find!LocationTag(global.usr.get) 622 // both declaration and definition is OK 623 .map!(a => a.any) 624 .joiner) { 625 code.comment("Origin " ~ loc.toString)[$.begin = "/// "]; 626 } 627 // dfmt on 628 } 629 code.stmt(d_name); 630 } 631 632 void generatePreProcessor(ref CxGlobalVariable global, string prefix, CppModule code) { 633 import std.string : toUpper; 634 import cpptooling.data : toStringDecl; 635 636 auto d_name = E((prefix ~ "Init_").toUpper ~ global.name); 637 auto ifndef = code.IFNDEF(d_name); 638 639 void handler() { 640 ifndef.define(d_name ~ E(global.type.toStringDecl(global.name))); 641 } 642 643 // example: #define TEST_INIT_extern_a int extern_a[4] 644 global.type.kind.info.match!(restrictTo!(TypeKind.ArrayInfo, TypeKind.FuncInfo, 645 TypeKind.FuncPtrInfo, TypeKind.FuncSignatureInfo, TypeKind.PointerInfo, TypeKind.PrimitiveInfo, 646 TypeKind.RecordInfo, TypeKind.SimpleInfo, TypeKind.TypeRefInfo, (a) { 647 handler; 648 }), restrictTo!(TypeKind.CtorInfo, TypeKind.DtorInfo, (a) { 649 assert(0, "unexpected c++ code in preprocessor macros"); 650 }), (Void a) { 651 logger.error("Type of global definition is null. Identifier ", global.name); 652 }); 653 } 654 655 auto global_macros = globals.base; 656 global_macros.suppressIndent(1); 657 globals.sep; 658 auto global_definitions = globals.base; 659 global_definitions.suppressIndent(1); 660 661 foreach (a; r) { 662 generatePreProcessor(a, params.getArtifactPrefix, global_macros); 663 generateDefinitions(a, cast(Flag!"locationAsComment") ctrl.doLocationAsComment, 664 params.getArtifactPrefix, container, global_definitions); 665 } 666 } 667 668 void generateNsTestDoubleHdr(LookupT, KindLookupT)(ref CppNamespace ns, Flag!"locationAsComment" loc_as_comment, 669 Parameters params, CppModule hdr, CppModule gmock, LookupT lookup, KindLookupT kind_lookup) { 670 import cpptooling.generator.classes : generateHdr; 671 import cpptooling.generator.gmock : generateGmock; 672 673 auto test_double_ns = hdr.namespace(ns.name); 674 test_double_ns.suppressIndent(1); 675 hdr.sep(2); 676 677 foreach (class_; ns.classRange()) { 678 switch (kind_lookup(class_.id)) { 679 case Kind.none: 680 case Kind.initGlobalsToZero: 681 case Kind.adapter: 682 generateHdr(class_, test_double_ns, loc_as_comment, lookup); 683 break; 684 case Kind.initGlobalInterface: 685 case Kind.testDoubleInterface: 686 generateHdr(class_, test_double_ns, loc_as_comment, lookup, Yes.inlineDtor); 687 break; 688 case Kind.gmock: 689 auto mock_ns = gmock.namespace(params.getMainNs).noIndent; 690 generateGmock(class_, mock_ns, No.inlineCtorDtor); 691 break; 692 default: 693 } 694 } 695 } 696 697 void generateNsTestDoubleImpl(ref CppNamespace ns, Parameters params, CppModule impl, CppModule gmock_impl, 698 CppModule mutable_extern_hook, ref ImplData data, StubPrefix prefix, ref Container container) { 699 import dextool.plugin.ctestdouble.backend.global : generateGlobalExterns, 700 generateInitGlobalsToZero; 701 import dextool.plugin.ctestdouble.backend.adapter : generateClassImplAdapter = generateImpl; 702 import cpptooling.generator.gmock : generateGmockImpl; 703 704 auto test_double_ns = impl.namespace(ns.name); 705 test_double_ns.suppressIndent(1); 706 impl.sep(2); 707 708 auto gmock_ns = gmock_impl.namespace(ns.name); 709 gmock_ns.suppressIndent(1); 710 gmock_ns.sep(2); 711 712 auto lookup(USRType usr) { 713 return usr in data.adapterKind; 714 } 715 716 foreach (class_; ns.classRange) { 717 switch (data.lookup(class_.id)) { 718 case Kind.adapter: 719 generateClassImplAdapter(class_, data.globals, 720 prefix, test_double_ns, &lookup); 721 break; 722 723 case Kind.initGlobalsToZero: 724 generateGlobalExterns(data.globals[], 725 mutable_extern_hook, container); 726 generateInitGlobalsToZero(class_, test_double_ns, prefix, &data.lookupGlobal); 727 break; 728 729 case Kind.gmock: 730 generateGmockImpl(class_, gmock_ns); 731 break; 732 733 default: 734 } 735 } 736 }