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 }