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