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 }