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 test.component.plantuml;
9 
10 import std.format : format;
11 import std.typecons : BlackHole, Flag, Yes, No, scoped;
12 
13 import unit_threaded;
14 import test.clang_util;
15 import test.helpers;
16 
17 import clang.TranslationUnit : TranslationUnit;
18 
19 import dextool.type;
20 import cpptooling.data : TypeKind, USRType;
21 import cpptooling.analyzer.clang.ast : ClangAST;
22 import cpptooling.analyzer.clang.context;
23 import cpptooling.data.symbol : Container;
24 import cpptooling.utility.virtualfilesystem : FileName, Content;
25 import dextool.plugin.frontend.plantuml : Lookup;
26 import dextool.plugin.backend.plantuml;
27 
28 private:
29 
30 alias BHController = BlackHole!Controller;
31 alias BHParameters = BlackHole!Parameters;
32 
33 /* These two lines are useful when debugging.
34 import unit_threaded;
35 writelnUt(be.container.toString);
36 writelnUt(be.uml_component.toString);
37 */
38 
39 ///
40 @safe class DummyController : BHController {
41     import dextool.type : FileName;
42 
43     override bool doFile(in string filename, in string info) {
44         return true;
45     }
46 
47     override FileName doComponentNameStrip(FileName fname) {
48         import std.path : dirName, baseName;
49 
50         return FileName((cast(string) fname).dirName.baseName);
51     }
52 }
53 
54 ///
55 pure const @safe class DummyParameters : BHParameters {
56     override Flag!"genClassMethod" genClassMethod() {
57         return cast(typeof(return)) true;
58     }
59 
60     override Flag!"genClassParamDependency" genClassParamDependency() {
61         return cast(typeof(return)) true;
62     }
63 
64     override Flag!"genClassInheritDependency" genClassInheritDependency() {
65         return cast(typeof(return)) true;
66     }
67 
68     override Flag!"genClassMemberDependency" genClassMemberDependency() {
69         return cast(typeof(return)) true;
70     }
71 }
72 
73 /** Emulate the data structures that the frontend uses to communicate with the
74  * backend.
75  */
76 class Backend {
77     DummyController ctrl;
78     DummyParameters params;
79     UMLClassDiagram uml_class;
80     UMLComponentDiagram uml_component;
81 
82     TransformToDiagram!(Controller, Parameters, Lookup) transform;
83     UMLVisitor!(Controller, typeof(transform)) visitor;
84 
85     Container container;
86     ClangAST!(typeof(visitor)) ast;
87     ClangContext ctx;
88 
89     this() {
90         ctrl = new DummyController;
91         params = new DummyParameters;
92         uml_class = new UMLClassDiagram;
93         uml_component = new UMLComponentDiagram;
94 
95         transform = new typeof(transform)(ctrl, params, Lookup(&container),
96                 uml_component, uml_class);
97         visitor = new typeof(visitor)(ctrl, transform, container);
98         ctx = ClangContext(Yes.useInternalHeaders, Yes.prependParamSyntaxOnly);
99     }
100 }
101 
102 // Test Cases ****************************************************************
103 
104 // Begin. Test of parameter dependency for component diagrams.
105 
106 void actTwoFiles(BackendT)(ref TranslationUnit tu0, ref TranslationUnit tu1, ref BackendT be)
107         if (is(BackendT == typeof(scoped!Backend()))) {
108     checkForCompilerErrors(tu0).shouldBeFalse;
109     checkForCompilerErrors(tu1).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.virtualFileSystem.openAndWrite(cast(FileName) "/comp/ctor.hpp",
151             cast(Content) format(comp_ctor, getValue!string));
152     be.ctx.virtualFileSystem.openAndWrite(cast(FileName) "/comp_a/a.hpp",
153             cast(Content) Snippet.comp_a);
154     auto tu0 = be.ctx.makeTranslationUnit("/comp/ctor.hpp", Snippet.includes);
155     auto tu1 = be.ctx.makeTranslationUnit("/comp_a/a.hpp", Snippet.includes);
156 
157     // act
158     actTwoFiles(tu0, tu1, be);
159 
160     // assert
161     auto result = be.uml_component.relateToFlatArray;
162 
163     result.length.shouldEqual(1);
164 
165     result[0].from.shouldEqual(USRType(Key.comp));
166     result[0].to.shouldEqual(USRType(Key.comp_a));
167 }
168 
169 @("Should be a component dependency from comp->comp_a via a methods parameter")
170 @Values("", "*", "&")
171 unittest {
172     enum comp_method = "
173 class A;
174 
175 class A_ByParam {
176     void param(A%s a);
177 };";
178 
179     // arrange
180     auto be = scoped!Backend();
181     be.ctx.virtualFileSystem.openAndWrite(cast(FileName) "/comp/a.hpp",
182             cast(Content) format(comp_method, getValue!string));
183     be.ctx.virtualFileSystem.openAndWrite(cast(FileName) "/comp_a/a.hpp",
184             cast(Content) Snippet.comp_a);
185     auto tu0 = be.ctx.makeTranslationUnit("/comp/a.hpp", Snippet.includes);
186     auto tu1 = be.ctx.makeTranslationUnit("/comp_a/a.hpp", Snippet.includes);
187 
188     // act
189     actTwoFiles(tu0, tu1, be);
190 
191     // assert
192     auto result = be.uml_component.relateToFlatArray;
193     result.length.shouldEqual(1);
194 
195     result[0].from.shouldEqual(USRType(Key.comp));
196     result[0].to.shouldEqual(USRType(Key.comp_a));
197 }
198 
199 @("Should be a component dependency from comp->comp_a via a functions parameter")
200 @Values("", "*", "&")
201 unittest {
202     enum comp_func = "
203 class A;
204 
205 void free_func(A%s a);
206 ";
207 
208     // arrange
209     auto be = scoped!Backend();
210     be.ctx.virtualFileSystem.openAndWrite(cast(FileName) "/comp/fun.hpp",
211             cast(Content) format(comp_func, getValue!string));
212     be.ctx.virtualFileSystem.openAndWrite(cast(FileName) "/comp_a/a.hpp",
213             cast(Content) Snippet.comp_a);
214     auto tu0 = be.ctx.makeTranslationUnit("/comp/fun.hpp", Snippet.includes);
215     auto tu1 = be.ctx.makeTranslationUnit("/comp_a/a.hpp", Snippet.includes);
216 
217     // act
218     actTwoFiles(tu0, tu1, be);
219 
220     // assert
221     auto result = be.uml_component.relateToFlatArray;
222     result.length.shouldEqual(1);
223 
224     result[0].from.shouldEqual(USRType(Key.comp));
225     result[0].to.shouldEqual(USRType(Key.comp_a));
226 }
227 
228 @("Should be a component dependency from comp->comp_a via a class member")
229 @Values("", "*", "&")
230 unittest {
231     enum comp_func = "
232 %s
233 class A;
234 
235 class A_ByMember {
236     A%s a;
237 };";
238 
239     // arrange
240     auto be = scoped!Backend();
241     be.ctx.virtualFileSystem.openAndWrite(cast(FileName) "/comp/fun.hpp",
242             cast(Content) format(comp_func, getValue!string.length == 0
243                 ? Snippet.include_comp_a : "", getValue!string));
244     be.ctx.virtualFileSystem.openAndWrite(cast(FileName) "/comp_a/a.hpp",
245             cast(Content) Snippet.comp_a);
246     auto tu0 = be.ctx.makeTranslationUnit("/comp/fun.hpp", Snippet.includes);
247     auto tu1 = be.ctx.makeTranslationUnit("/comp_a/a.hpp", Snippet.includes);
248 
249     // act
250     actTwoFiles(tu0, tu1, be);
251 
252     // assert
253     auto result = be.uml_component.relateToFlatArray;
254     result.length.shouldEqual(1);
255 
256     result[0].from.shouldEqual(USRType(Key.comp));
257     result[0].to.shouldEqual(USRType(Key.comp_a));
258 }
259 
260 @("Should be a component dependency from comp->comp_a via a free variable")
261 @Values("instantiation", "pointer")
262 unittest {
263     enum comp_free_variable = "
264 class A;
265 
266 A* a;
267 ";
268 
269     enum comp_global_instantiation = `
270 #include "/comp_a/a.hpp"
271 
272 A a;
273 `;
274 
275     string comp;
276 
277     switch (getValue!string) {
278     case "instantiation":
279         comp = comp_global_instantiation;
280         break;
281     case "pointer":
282         comp = comp_free_variable;
283         break;
284     default:
285         true.shouldBeFalse;
286     }
287 
288     // arrange
289     auto be = scoped!Backend();
290     be.ctx.virtualFileSystem.openAndWrite(cast(FileName) "/comp/fun.hpp", cast(Content) comp);
291     be.ctx.virtualFileSystem.openAndWrite(cast(FileName) "/comp_a/a.hpp",
292             cast(Content) Snippet.comp_a);
293     auto tu0 = be.ctx.makeTranslationUnit("/comp/fun.hpp", Snippet.includes);
294     auto tu1 = be.ctx.makeTranslationUnit("/comp_a/a.hpp", Snippet.includes);
295 
296     // act
297     actTwoFiles(tu0, tu1, be);
298 
299     // assert
300     auto result = be.uml_component.relateToFlatArray;
301     result.length.shouldEqual(1);
302 
303     result[0].from.shouldEqual(USRType(Key.comp));
304     result[0].to.shouldEqual(USRType(Key.comp_a));
305 }