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.trace("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, 269 analyzeVarDecl; 270 import cpptooling.data : CppRoot; 271 import cpptooling.data.symbol : Container; 272 import cpptooling.analyzer.clang.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(FileName(tu_loc.file), LocationType.Root); 335 } 336 337 v.accept(this); 338 } 339 340 void toString(Writer)(scope Writer w) @safe const { 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() const { 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 // by definition static functions can't be replaced by test doubles 455 .filter!(a => a.storageClass != StorageClass.Static) 456 // ask controller if the user wants to generate a test double function for the symbol. 457 // note: using the fact that C do NOT have name mangling. 458 .filter!(a => ctrl.doSymbol(a.name)) 459 // ask controller if to generate a test double for the function 460 .filterAnyLocation!(a => ctrl.doFile(a.location.file, cast(string) a.value.name))(lookup) 461 // pass on location as a product to be used to calculate #include 462 .tee!(a => prod.putLocation(FileName(a.location.file), LocationType.Leaf)) 463 .each!(a => filtered.put(a.value)); 464 465 input.globalRange() 466 // ask controller if the user wants to generate a global for the symbol. 467 // note: using the fact that C do NOT have name mangling. 468 .filter!(a => ctrl.doSymbol(a.name)) 469 // ask controller if to generate a test double for the function 470 .filterAnyLocation!(a => ctrl.doFile(a.location.file, cast(string) a.value.name))(lookup) 471 // pass on location as a product to be used to calculate #include 472 .tee!(a => prod.putLocation(FileName(a.location.file), LocationType.Leaf)) 473 .each!(a => filtered.put(a.value)); 474 // dfmt on 475 } 476 477 /** Transform the content of the root to a test double implementation root. 478 * 479 * Make an adapter. 480 * Make a namespace holding the test double. 481 * Make an interface for initialization of globals. 482 * Make a google mock if asked by the user. 483 */ 484 auto makeImplementation(ref CppRoot root, Controller ctrl, Parameters params, 485 const ref Container container) { 486 import std.algorithm : filter; 487 import std.array : array; 488 import cpptooling.data : CppNamespace, CppNs, CppClassName, CppInherit, 489 CppAccess, AccessType, makeUniqueUSR, nextUniqueID, MergeMode; 490 import cpptooling.generator.func : makeFuncInterface; 491 import cpptooling.generator.gmock : makeGmock; 492 import dextool.plugin.ctestdouble.backend.adapter : makeSingleton, 493 makeAdapter; 494 import dextool.plugin.ctestdouble.backend.global : makeGlobalInterface, 495 makeZeroGlobal, filterMutable; 496 497 auto 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 const 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 const ref Container container, CppModule globals) { 604 void generateDefinitions(ref CxGlobalVariable global, Flag!"locationAsComment" loc_as_comment, 605 string prefix, ref const(Container) container, CppModule code) 606 in { 607 import std.algorithm : among; 608 import cpptooling.data : TypeKind; 609 610 assert(!global.type.kind.info.kind.among(TypeKind.Info.Kind.ctor, TypeKind.Info.Kind.dtor)); 611 } 612 body { 613 import std.algorithm : map, joiner; 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) 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 : TypeKind, toStringDecl; 635 636 auto d_name = E((prefix ~ "Init_").toUpper ~ global.name); 637 auto ifndef = code.IFNDEF(d_name); 638 639 // example: #define TEST_INIT_extern_a int extern_a[4] 640 final switch (global.type.kind.info.kind) with (TypeKind.Info) { 641 case Kind.array: 642 case Kind.func: 643 case Kind.funcPtr: 644 case Kind.funcSignature: 645 case Kind.pointer: 646 case Kind.primitive: 647 case Kind.record: 648 case Kind.simple: 649 case Kind.typeRef: 650 ifndef.define(d_name ~ E(global.type.toStringDecl(global.name))); 651 break; 652 case Kind.ctor: 653 // a C test double shold never have preprocessor macros for a C++ ctor 654 assert(false); 655 case Kind.dtor: 656 // a C test double shold never have preprocessor macros for a C++ dtor 657 assert(false); 658 case Kind.null_: 659 logger.error("Type of global definition is null. Identifier ", global.name); 660 break; 661 } 662 } 663 664 auto global_macros = globals.base; 665 global_macros.suppressIndent(1); 666 globals.sep; 667 auto global_definitions = globals.base; 668 global_definitions.suppressIndent(1); 669 670 foreach (a; r) { 671 generatePreProcessor(a, params.getArtifactPrefix, global_macros); 672 generateDefinitions(a, cast(Flag!"locationAsComment") ctrl.doLocationAsComment, 673 params.getArtifactPrefix, container, global_definitions); 674 } 675 } 676 677 void generateNsTestDoubleHdr(LookupT, KindLookupT)(ref CppNamespace ns, Flag!"locationAsComment" loc_as_comment, 678 Parameters params, CppModule hdr, CppModule gmock, LookupT lookup, KindLookupT kind_lookup) { 679 import cpptooling.generator.classes : generateHdr; 680 import cpptooling.generator.gmock : generateGmock; 681 682 auto test_double_ns = hdr.namespace(ns.name); 683 test_double_ns.suppressIndent(1); 684 hdr.sep(2); 685 686 foreach (class_; ns.classRange()) { 687 switch (kind_lookup(class_.id)) { 688 case Kind.none: 689 case Kind.initGlobalsToZero: 690 case Kind.adapter: 691 generateHdr(class_, test_double_ns, loc_as_comment, lookup); 692 break; 693 case Kind.initGlobalInterface: 694 case Kind.testDoubleInterface: 695 generateHdr(class_, test_double_ns, loc_as_comment, lookup, Yes.inlineDtor); 696 break; 697 case Kind.gmock: 698 auto mock_ns = gmock.namespace(params.getMainNs).noIndent; 699 generateGmock(class_, mock_ns); 700 break; 701 default: 702 } 703 } 704 } 705 706 void generateNsTestDoubleImpl(ref CppNamespace ns, CppModule impl, CppModule mutable_extern_hook, 707 ref ImplData data, StubPrefix prefix, ref const Container container) { 708 import dextool.plugin.ctestdouble.backend.global : generateGlobalExterns, 709 generateInitGlobalsToZero; 710 import dextool.plugin.ctestdouble.backend.adapter : generateClassImplAdapter = generateImpl; 711 712 auto test_double_ns = impl.namespace(ns.name); 713 test_double_ns.suppressIndent(1); 714 impl.sep(2); 715 716 auto lookup(USRType usr) { 717 return usr in data.adapterKind; 718 } 719 720 foreach (class_; ns.classRange) { 721 switch (data.lookup(class_.id)) { 722 case Kind.adapter: 723 generateClassImplAdapter(class_, data.globals, 724 prefix, test_double_ns, &lookup); 725 break; 726 727 case Kind.initGlobalsToZero: 728 generateGlobalExterns(data.globals[], 729 mutable_extern_hook, container); 730 generateInitGlobalsToZero(class_, test_double_ns, prefix, &data.lookupGlobal); 731 break; 732 733 default: 734 } 735 } 736 }