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 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 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, Path 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, Path incl_fname, Path 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 libclang_ast.ast : VarDecl, FunctionDecl, TranslationUnit, generateIndentIncrDecr; 267 import cpptooling.analyzer.clang.analyze_helper : analyzeFunctionDecl, analyzeVarDecl; 268 import cpptooling.data : CppRoot; 269 import cpptooling.data.symbol : Container; 270 import libclang_ast.cursor_logger : logNode, mixinNodeLog; 271 272 alias visit = Visitor.visit; 273 mixin generateIndentIncrDecr; 274 275 CppRoot root; 276 Container container; 277 278 private { 279 Controller ctrl; 280 Products prod; 281 } 282 283 this(Controller ctrl, Products prod) { 284 this.ctrl = ctrl; 285 this.prod = prod; 286 this.root = CppRoot.make; 287 } 288 289 void clearRoot() @safe { 290 this.root = CppRoot.make; 291 } 292 293 override void visit(const VarDecl v) @trusted { 294 import cpptooling.data : TypeKindVariable; 295 import clang.c.Index : CX_StorageClass; 296 297 mixin(mixinNodeLog!()); 298 299 //TODO ugly hack. Move this information to the representation. But for 300 //now skipping all definitions 301 if (v.cursor.storageClass() == CX_StorageClass.extern_) { 302 auto result = analyzeVarDecl(v, container, indent); 303 auto var = CxGlobalVariable(result.instanceUSR, 304 TypeKindVariable(result.type, result.name)); 305 root.put(var); 306 } 307 } 308 309 override void visit(const FunctionDecl v) { 310 import cpptooling.data.type : CxReturnType; 311 312 mixin(mixinNodeLog!()); 313 314 auto result = analyzeFunctionDecl(v, container, indent); 315 if (result.isValid) { 316 auto func = CFunction(result.type.kind.usr, result.name, result.params, 317 CxReturnType(result.returnType), result.isVariadic, result.storageClass); 318 root.put(func); 319 } 320 } 321 322 override void visit(const TranslationUnit v) { 323 import cpptooling.analyzer.clang.type : makeLocation; 324 325 mixin(mixinNodeLog!()); 326 327 LocationTag tu_loc; 328 () @trusted { tu_loc = LocationTag(Location(v.cursor.spelling, 0, 0)); }(); 329 330 if (tu_loc.kind != LocationTag.Kind.noloc && ctrl.doFile(tu_loc.file, 331 "root " ~ tu_loc.toString)) { 332 prod.putLocation(Path(tu_loc.file), LocationType.Root); 333 } 334 335 v.accept(this); 336 } 337 338 void toString(Writer)(scope Writer w) @safe { 339 import std.format : FormatSpec; 340 import std.range.primitives : put; 341 342 auto fmt = FormatSpec!char("%u"); 343 fmt.writeUpToNextSpec(w); 344 345 root.toString(w, fmt); 346 put(w, "\n"); 347 container.toString(w, FormatSpec!char("%s")); 348 } 349 350 override string toString() { 351 import std.exception : assumeUnique; 352 353 char[] buf; 354 buf.reserve(100); 355 toString((const(char)[] s) { buf ~= s; }); 356 auto trustedUnique(T)(T t) @trusted { 357 return assumeUnique(t); 358 } 359 360 return trustedUnique(buf); 361 } 362 } 363 364 private: 365 @safe: 366 367 import cpptooling.data : CppRoot, CppClass, CppMethod, CppCtor, CppDtor, 368 CFunction, CppNamespace, CxGlobalVariable, USRType, LocationTag, Location; 369 import dsrcgen.cpp : E, noIndent; 370 371 /** Contain data for code generation. 372 */ 373 struct ImplData { 374 import cpptooling.data.type : CppMethodName; 375 import dextool.plugin.ctestdouble.backend.adapter : AdapterKind; 376 import dextool.plugin.ctestdouble.backend.global : MutableGlobal; 377 378 CppRoot root; 379 alias root this; 380 381 /// Tagging of nodes in the root 382 Kind[size_t] kind; 383 /// Global, mutable variables 384 MutableGlobal[] globals; 385 /// Constructor kinds for ctors in an adapter 386 AdapterKind[USRType] adapterKind; 387 388 static auto make() { 389 return ImplData(CppRoot.make); 390 } 391 392 void tag(size_t id, Kind kind_) { 393 kind[id] = kind_; 394 } 395 396 Kind lookup(size_t id) { 397 if (auto k = id in kind) { 398 return *k; 399 } 400 401 return Kind.none; 402 } 403 404 MutableGlobal lookupGlobal(CppMethodName name) { 405 foreach (item; globals) { 406 if (item.name == name) { 407 return item; 408 } 409 } 410 411 // Methods shall always be 1:1 mapped with the globals list. 412 assert(0); 413 } 414 } 415 416 enum Kind { 417 none, 418 /// Adapter class 419 adapter, 420 /// gmock class 421 gmock, 422 /// interface for globals 423 initGlobalInterface, 424 initGlobalsToZero, 425 testDoubleNamespace, 426 testDoubleSingleton, 427 testDoubleInterface, 428 } 429 430 /** Structurally transformed the input to a test double implementation. 431 * 432 * This stage: 433 * - removes C++ code. 434 * - removes according to directives via ctrl. 435 * 436 * Params: 437 * input = structural representation of the source code 438 * ctrl = controll what nodes to keep 439 * prod = push location data of the nodes that are kept 440 * filtered = output structural representation 441 * lookup = callback function supporting lookup of locations 442 */ 443 void rawFilter(LookupT)(ref CppRoot input, Controller ctrl, Products prod, 444 ref CppRoot filtered, LookupT lookup) { 445 import std.algorithm : filter, each; 446 import std.range : tee; 447 import cpptooling.data : StorageClass; 448 import cpptooling.generator.utility : filterAnyLocation; 449 450 // dfmt off 451 input.funcRange 452 // by definition static functions can't be replaced by test doubles 453 .filter!(a => a.payload.storageClass != StorageClass.Static) 454 // ask controller if the user wants to generate a test double function for the symbol. 455 // note: using the fact that C do NOT have name mangling. 456 .filter!(a => ctrl.doSymbol(a.payload.name)) 457 // ask controller if to generate a test double for the function 458 .filterAnyLocation!(a => ctrl.doFile(a.location.file, cast(string) a.value.name))(lookup) 459 // pass on location as a product to be used to calculate #include 460 .tee!(a => prod.putLocation(Path(a.location.file), LocationType.Leaf)) 461 .each!(a => filtered.put(a.value)); 462 463 input.globalRange() 464 // ask controller if the user wants to generate a global for the symbol. 465 // note: using the fact that C do NOT have name mangling. 466 .filter!(a => ctrl.doSymbol(a.name)) 467 // ask controller if to generate a test double for the function 468 .filterAnyLocation!(a => ctrl.doFile(a.location.file, cast(string) a.value.name))(lookup) 469 // pass on location as a product to be used to calculate #include 470 .tee!(a => prod.putLocation(Path(a.location.file), LocationType.Leaf)) 471 .each!(a => filtered.put(a.value)); 472 // dfmt on 473 } 474 475 /** Transform the content of the root to a test double implementation root. 476 * 477 * Make an adapter. 478 * Make a namespace holding the test double. 479 * Make an interface for initialization of globals. 480 * Make a google mock if asked by the user. 481 */ 482 auto makeImplementation(ref CppRoot root, Controller ctrl, Parameters params, 483 ref Container container) @trusted { 484 import std.algorithm : filter; 485 import std.array : array; 486 import cpptooling.data : CppNamespace, CppNs, CppClassName, CppInherit, 487 CppAccess, AccessType, makeUniqueUSR, nextUniqueID, MergeMode; 488 import cpptooling.generator.func : makeFuncInterface; 489 import cpptooling.generator.gmock : makeGmock; 490 import dextool.plugin.ctestdouble.backend.adapter : makeSingleton, makeAdapter; 491 import dextool.plugin.ctestdouble.backend.global : makeGlobalInterface, 492 makeZeroGlobal, filterMutable; 493 494 ImplData impl = ImplData.make; 495 impl.root.merge(root, MergeMode.shallow); 496 497 impl.globals = impl.globalRange.filterMutable(container).array; 498 499 const has_mutable_globals = impl.globals.length != 0; 500 const has_functions = !root.funcRange.empty; 501 502 if (!has_functions && !has_mutable_globals) { 503 return impl; 504 } 505 506 auto test_double_ns = CppNamespace.make(CppNs(params.getMainNs)); 507 508 if (has_functions) { 509 auto singleton = makeSingleton(CppNs(params.getMainNs), 510 CppClassName(params.getMainInterface), "test_double_inst"); 511 impl.kind[singleton.id] = Kind.testDoubleSingleton; 512 impl.put(singleton); // (1) 513 514 auto c_if = makeFuncInterface(impl.funcRange, CppClassName(params.getMainInterface)); 515 impl.tag(c_if.id, Kind.testDoubleInterface); 516 test_double_ns.put(c_if); 517 518 if (ctrl.doGoogleMock) { 519 auto mock = makeGmock(c_if); 520 impl.tag(mock.id, Kind.gmock); 521 test_double_ns.put(mock); 522 } 523 } 524 525 if (has_mutable_globals) { 526 auto if_name = CppClassName(params.getMainInterface ~ "_InitGlobals"); 527 auto global_if = makeGlobalInterface(impl.globals[], if_name); 528 impl.tag(global_if.id, Kind.initGlobalInterface); 529 test_double_ns.put(global_if); 530 531 if (params.generateZeroGlobals) { 532 auto global_init_zero = makeZeroGlobal(impl.globals[], 533 CppClassName(params.getArtifactPrefix ~ "ZeroGlobals"), 534 params.getArtifactPrefix, CppInherit(if_name, CppAccess(AccessType.Public))); 535 impl.tag(global_init_zero.id, Kind.initGlobalsToZero); 536 test_double_ns.put(global_init_zero); 537 } 538 } 539 540 // MUST be added after the singleton (1) 541 impl.tag(test_double_ns.id, Kind.testDoubleNamespace); 542 543 { 544 // dfmt off 545 auto adapter = makeAdapter(params.getMainInterface) 546 .makeTestDouble(has_functions) 547 .makeInitGlobals(has_mutable_globals) 548 .makeZeroGlobals(params.generateZeroGlobals) 549 .finalize(impl); 550 impl.tag(adapter.id, Kind.adapter); 551 test_double_ns.put(adapter); 552 // dfmt on 553 } 554 555 impl.put(test_double_ns); 556 return impl; 557 } 558 559 void generate(ref ImplData data, Controller ctrl, Parameters params, ref Container container, 560 CppModule hdr, CppModule impl, CppModule globals, CppModule gmock) { 561 import cpptooling.data.symbol.types : USRType; 562 import cpptooling.generator.func : generateFuncImpl; 563 import cpptooling.generator.includes : generateWrapIncludeInExternC; 564 import dextool.plugin.ctestdouble.backend.adapter : generateSingleton; 565 566 generateWrapIncludeInExternC(ctrl, params, hdr); 567 generateGlobal(data.globalRange, ctrl, params, container, globals); 568 569 auto mutable_extern_hook = impl.base; 570 mutable_extern_hook.suppressIndent(1); 571 572 foreach (ns; data.namespaceRange) { 573 switch (data.lookup(ns.id)) { 574 case Kind.testDoubleSingleton: 575 generateSingleton(ns, impl); 576 break; 577 case Kind.testDoubleNamespace: 578 generateNsTestDoubleHdr(ns, 579 cast(Flag!"locationAsComment") ctrl.doLocationAsComment, params, hdr, gmock, 580 (USRType usr) => container.find!LocationTag(usr), (size_t id) => data.lookup( 581 id)); 582 generateNsTestDoubleImpl(ns, impl, mutable_extern_hook, data, 583 params.getArtifactPrefix, container); 584 break; 585 586 default: 587 } 588 } 589 590 // The generated functions must be extern C declared. 591 auto extern_c = impl.suite("extern \"C\""); 592 extern_c.suppressIndent(1); 593 foreach (a; data.funcRange) { 594 generateFuncImpl(a, extern_c); 595 } 596 } 597 598 /// Generate the global definitions and macros for initialization. 599 void generateGlobal(RangeT)(RangeT r, Controller ctrl, Parameters params, 600 ref Container container, CppModule globals) { 601 import cpptooling.data : TypeKind, Void; 602 603 void generateDefinitions(ref CxGlobalVariable global, Flag!"locationAsComment" loc_as_comment, 604 string prefix, ref Container container, CppModule code) 605 in { 606 global.type.kind.info.match!(restrictTo!(TypeKind.CtorInfo, TypeKind.DtorInfo, (_) { 607 assert(0, "wrong type"); 608 }), (_) {}); 609 } 610 do { 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 : toStringDecl; 633 634 auto d_name = E((prefix ~ "Init_").toUpper ~ global.name); 635 auto ifndef = code.IFNDEF(d_name); 636 637 void handler() { 638 ifndef.define(d_name ~ E(global.type.toStringDecl(global.name))); 639 } 640 641 // example: #define TEST_INIT_extern_a int extern_a[4] 642 global.type.kind.info.match!(restrictTo!(TypeKind.ArrayInfo, TypeKind.FuncInfo, 643 TypeKind.FuncPtrInfo, TypeKind.FuncSignatureInfo, TypeKind.PointerInfo, TypeKind.PrimitiveInfo, 644 TypeKind.RecordInfo, TypeKind.SimpleInfo, TypeKind.TypeRefInfo, (a) { 645 handler; 646 }), restrictTo!(TypeKind.CtorInfo, TypeKind.DtorInfo, (a) { 647 assert(0, "unexpected c++ code in preprocessor macros"); 648 }), (Void a) { 649 logger.error("Type of global definition is null. Identifier ", global.name); 650 }); 651 } 652 653 auto global_macros = globals.base; 654 global_macros.suppressIndent(1); 655 globals.sep; 656 auto global_definitions = globals.base; 657 global_definitions.suppressIndent(1); 658 659 foreach (a; r) { 660 generatePreProcessor(a, params.getArtifactPrefix, global_macros); 661 generateDefinitions(a, cast(Flag!"locationAsComment") ctrl.doLocationAsComment, 662 params.getArtifactPrefix, container, global_definitions); 663 } 664 } 665 666 void generateNsTestDoubleHdr(LookupT, KindLookupT)(ref CppNamespace ns, Flag!"locationAsComment" loc_as_comment, 667 Parameters params, CppModule hdr, CppModule gmock, LookupT lookup, KindLookupT kind_lookup) { 668 import cpptooling.generator.classes : generateHdr; 669 import cpptooling.generator.gmock : generateGmock; 670 671 auto test_double_ns = hdr.namespace(ns.name); 672 test_double_ns.suppressIndent(1); 673 hdr.sep(2); 674 675 foreach (class_; ns.classRange()) { 676 switch (kind_lookup(class_.id)) { 677 case Kind.none: 678 case Kind.initGlobalsToZero: 679 case Kind.adapter: 680 generateHdr(class_, test_double_ns, loc_as_comment, lookup); 681 break; 682 case Kind.initGlobalInterface: 683 case Kind.testDoubleInterface: 684 generateHdr(class_, test_double_ns, loc_as_comment, lookup, Yes.inlineDtor); 685 break; 686 case Kind.gmock: 687 auto mock_ns = gmock.namespace(params.getMainNs).noIndent; 688 generateGmock(class_, mock_ns); 689 break; 690 default: 691 } 692 } 693 } 694 695 void generateNsTestDoubleImpl(ref CppNamespace ns, CppModule impl, 696 CppModule mutable_extern_hook, ref ImplData data, StubPrefix prefix, ref Container container) { 697 import dextool.plugin.ctestdouble.backend.global : generateGlobalExterns, 698 generateInitGlobalsToZero; 699 import dextool.plugin.ctestdouble.backend.adapter : generateClassImplAdapter = generateImpl; 700 701 auto test_double_ns = impl.namespace(ns.name); 702 test_double_ns.suppressIndent(1); 703 impl.sep(2); 704 705 auto lookup(USRType usr) { 706 return usr in data.adapterKind; 707 } 708 709 foreach (class_; ns.classRange) { 710 switch (data.lookup(class_.id)) { 711 case Kind.adapter: 712 generateClassImplAdapter(class_, data.globals, 713 prefix, test_double_ns, &lookup); 714 break; 715 716 case Kind.initGlobalsToZero: 717 generateGlobalExterns(data.globals[], 718 mutable_extern_hook, container); 719 generateInitGlobalsToZero(class_, test_double_ns, prefix, &data.lookupGlobal); 720 break; 721 722 default: 723 } 724 } 725 }