1 /**
2 Copyright: Copyright (c) 2016-2017, Joakim Brännström. All rights reserved.
3 License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost Software License 1.0)
4 Author: Joakim Brännström (joakim.brannstrom@gmx.com)
5 
6 Test of the backend for the plugin plantuml.
7 */
8 module dextool.plugin.plantuml.unittest_;
9 
10 import std.format : format;
11 import std.typecons : BlackHole, Flag, Yes, No, scoped;
12 
13 import unit_threaded;
14 import blob_model;
15 
16 import clang.TranslationUnit : TranslationUnit;
17 
18 import dextool.type;
19 import cpptooling.data : TypeKind, USRType;
20 import cpptooling.analyzer.clang.ast : ClangAST;
21 import cpptooling.analyzer.clang.context;
22 import cpptooling.data.symbol : Container;
23 import dextool.plugin.frontend.plantuml : Lookup;
24 import dextool.plugin.backend.plantuml;
25 
26 private:
27 
28 alias BHController = BlackHole!Controller;
29 alias BHParameters = BlackHole!Parameters;
30 
31 /* These two lines are useful when debugging.
32 import unit_threaded;
33 writelnUt(be.container.toString);
34 writelnUt(be.uml_component.toString);
35 */
36 
37 ///
38 @safe class DummyController : BHController {
39     import dextool.type : FileName;
40 
41     override bool doFile(in string filename, in string info) {
42         return true;
43     }
44 
45     override FileName doComponentNameStrip(FileName fname) {
46         import std.path : dirName, baseName;
47 
48         return FileName((cast(string) fname).dirName.baseName);
49     }
50 }
51 
52 ///
53 pure const @safe class DummyParameters : BHParameters {
54     override Flag!"genClassMethod" genClassMethod() {
55         return cast(typeof(return)) true;
56     }
57 
58     override Flag!"genClassParamDependency" genClassParamDependency() {
59         return cast(typeof(return)) true;
60     }
61 
62     override Flag!"genClassInheritDependency" genClassInheritDependency() {
63         return cast(typeof(return)) true;
64     }
65 
66     override Flag!"genClassMemberDependency" genClassMemberDependency() {
67         return cast(typeof(return)) true;
68     }
69 }
70 
71 /** Emulate the data structures that the frontend uses to communicate with the
72  * backend.
73  */
74 class Backend {
75     DummyController ctrl;
76     DummyParameters params;
77     UMLClassDiagram uml_class;
78     UMLComponentDiagram uml_component;
79 
80     TransformToDiagram!(Controller, Parameters, Lookup) transform;
81     UMLVisitor!(Controller, typeof(transform)) visitor;
82 
83     Container container;
84     ClangAST!(typeof(visitor)) ast;
85     ClangContext ctx;
86 
87     this() {
88         ctrl = new DummyController;
89         params = new DummyParameters;
90         uml_class = new UMLClassDiagram;
91         uml_component = new UMLComponentDiagram;
92 
93         transform = new typeof(transform)(ctrl, params, Lookup(&container),
94                 uml_component, uml_class);
95         visitor = new typeof(visitor)(ctrl, transform, container);
96         ctx = ClangContext(Yes.useInternalHeaders, Yes.prependParamSyntaxOnly);
97     }
98 }
99 
100 // Test Cases ****************************************************************
101 
102 // Begin. Test of parameter dependency for component diagrams.
103 
104 void actTwoFiles(BackendT)(ref TranslationUnit tu0, ref TranslationUnit tu1, ref BackendT be)
105         if (is(BackendT == typeof(scoped!Backend()))) {
106     import cpptooling.analyzer.clang.check_parse_result : hasParseErrors;
107 
108     tu0.hasParseErrors.shouldBeFalse;
109     tu1.hasParseErrors.shouldBeFalse;
110 
111     be.ast.root = tu0.cursor;
112     be.ast.accept(be.visitor);
113 
114     be.ast.root = tu1.cursor;
115     be.ast.accept(be.visitor);
116 
117     be.transform.finalize();
118 }
119 
120 // Reusable code snippets
121 struct Snippet {
122     enum includes = ["-I/"];
123     enum include_comp_a = `#include "comp_a/a.hpp"`;
124     enum comp_a = "
125 class A {
126 };";
127 }
128 
129 // Generated component keys. See plugin.backend.plantuml.makeComponentKey
130 struct Key {
131     enum comp_a = "Y29tcF9h";
132     enum comp = "Y29tcA";
133 }
134 
135 @("Should be a component dependency from comp->comp_a via a c'tors parameter")
136 @Values("", "*", "&")
137 unittest {
138     // Testing that even though comp is processed first and have a forward
139     // declaration of a relation is still created to the definition of A
140 
141     enum comp_ctor = "
142 class A;
143 
144 class A_ByCtor {
145     A_ByCtor(A%s a);
146 };";
147 
148     // arrange
149     auto be = scoped!Backend();
150     be.ctx.vfs.open(new Blob(Uri("/comp/ctor.hpp"), format(comp_ctor, getValue!string)));
151     be.ctx.vfs.open(new Blob(Uri("/comp_a/a.hpp"), Snippet.comp_a));
152     auto tu0 = be.ctx.makeTranslationUnit("/comp/ctor.hpp", Snippet.includes);
153     auto tu1 = be.ctx.makeTranslationUnit("/comp_a/a.hpp", Snippet.includes);
154 
155     // act
156     actTwoFiles(tu0, tu1, be);
157 
158     // assert
159     auto result = be.uml_component.relateToFlatArray;
160 
161     result.length.shouldEqual(1);
162 
163     result[0].from.shouldEqual(USRType(Key.comp));
164     result[0].to.shouldEqual(USRType(Key.comp_a));
165 }
166 
167 @("Should be a component dependency from comp->comp_a via a methods parameter")
168 @Values("", "*", "&")
169 unittest {
170     enum comp_method = "
171 class A;
172 
173 class A_ByParam {
174     void param(A%s a);
175 };";
176 
177     // arrange
178     auto be = scoped!Backend();
179     be.ctx.vfs.open(new Blob(Uri("/comp/a.hpp"), format(comp_method, getValue!string)));
180     be.ctx.vfs.open(new Blob(Uri("/comp_a/a.hpp"), Snippet.comp_a));
181     auto tu0 = be.ctx.makeTranslationUnit("/comp/a.hpp", Snippet.includes);
182     auto tu1 = be.ctx.makeTranslationUnit("/comp_a/a.hpp", Snippet.includes);
183 
184     // act
185     actTwoFiles(tu0, tu1, be);
186 
187     // assert
188     auto result = be.uml_component.relateToFlatArray;
189     result.length.shouldEqual(1);
190 
191     result[0].from.shouldEqual(USRType(Key.comp));
192     result[0].to.shouldEqual(USRType(Key.comp_a));
193 }
194 
195 @("Should be a component dependency from comp->comp_a via a functions parameter")
196 @Values("", "*", "&")
197 unittest {
198     enum comp_func = "
199 class A;
200 
201 void free_func(A%s a);
202 ";
203 
204     // arrange
205     auto be = scoped!Backend();
206     be.ctx.vfs.open(new Blob(Uri("/comp/fun.hpp"), format(comp_func, getValue!string)));
207     be.ctx.vfs.open(new Blob(Uri("/comp_a/a.hpp"), Snippet.comp_a));
208     auto tu0 = be.ctx.makeTranslationUnit("/comp/fun.hpp", Snippet.includes);
209     auto tu1 = be.ctx.makeTranslationUnit("/comp_a/a.hpp", Snippet.includes);
210 
211     // act
212     actTwoFiles(tu0, tu1, be);
213 
214     // assert
215     auto result = be.uml_component.relateToFlatArray;
216     result.length.shouldEqual(1);
217 
218     result[0].from.shouldEqual(USRType(Key.comp));
219     result[0].to.shouldEqual(USRType(Key.comp_a));
220 }
221 
222 @("Should be a component dependency from comp->comp_a via a class member")
223 @Values("", "*", "&")
224 unittest {
225     enum comp_func = "
226 %s
227 class A;
228 
229 class A_ByMember {
230     A%s a;
231 };";
232 
233     // arrange
234     auto be = scoped!Backend();
235     be.ctx.vfs.open(new Blob(Uri("/comp/fun.hpp"), format(comp_func,
236             getValue!string.length == 0 ? Snippet.include_comp_a : "", getValue!string)));
237     be.ctx.vfs.open(new Blob(Uri("/comp_a/a.hpp"), Snippet.comp_a));
238     auto tu0 = be.ctx.makeTranslationUnit("/comp/fun.hpp", Snippet.includes);
239     auto tu1 = be.ctx.makeTranslationUnit("/comp_a/a.hpp", Snippet.includes);
240 
241     // act
242     actTwoFiles(tu0, tu1, be);
243 
244     // assert
245     auto result = be.uml_component.relateToFlatArray;
246     result.length.shouldEqual(1);
247 
248     result[0].from.shouldEqual(USRType(Key.comp));
249     result[0].to.shouldEqual(USRType(Key.comp_a));
250 }
251 
252 @("Should be a component dependency from comp->comp_a via a free variable")
253 @Values("instantiation", "pointer")
254 unittest {
255     enum comp_free_variable = "
256 class A;
257 
258 A* a;
259 ";
260 
261     enum comp_global_instantiation = `
262 #include "/comp_a/a.hpp"
263 
264 A a;
265 `;
266 
267     string comp;
268 
269     switch (getValue!string) {
270     case "instantiation":
271         comp = comp_global_instantiation;
272         break;
273     case "pointer":
274         comp = comp_free_variable;
275         break;
276     default:
277         true.shouldBeFalse;
278     }
279 
280     // arrange
281     auto be = scoped!Backend();
282     be.ctx.vfs.open(new Blob(Uri("/comp/fun.hpp"), comp));
283     be.ctx.vfs.open(new Blob(Uri("/comp_a/a.hpp"), Snippet.comp_a));
284     auto tu0 = be.ctx.makeTranslationUnit("/comp/fun.hpp", Snippet.includes);
285     auto tu1 = be.ctx.makeTranslationUnit("/comp_a/a.hpp", Snippet.includes);
286 
287     // act
288     actTwoFiles(tu0, tu1, be);
289 
290     // assert
291     auto result = be.uml_component.relateToFlatArray;
292     result.length.shouldEqual(1);
293 
294     result[0].from.shouldEqual(USRType(Key.comp));
295     result[0].to.shouldEqual(USRType(Key.comp_a));
296 }