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