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 libclang_ast.ast : ClangAST;
21 import libclang_ast.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 : Path;
40 
41     override bool doFile(in string filename, in string info) {
42         return true;
43     }
44 
45     override Path doComponentNameStrip(Path fname) {
46         import std.path : dirName, baseName;
47 
48         return Path((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 libclang_ast.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 unittest {
137     // Testing that even though comp is processed first and have a forward
138     // declaration of a relation is still created to the definition of A
139 
140     enum comp_ctor = "
141 class A;
142 
143 class A_ByCtor {
144     A_ByCtor(A%s a);
145 };";
146 
147     foreach (getValue; ["", "*", "&"]) {
148         // arrange
149         auto be = scoped!Backend();
150         be.ctx.vfs.open(new Blob(Uri("/comp/ctor.hpp"), format(comp_ctor, getValue)));
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 
168 @("Should be a component dependency from comp->comp_a via a methods parameter")
169 unittest {
170     enum comp_method = "
171 class A;
172 
173 class A_ByParam {
174     void param(A%s a);
175 };";
176 
177     foreach (getValue; ["", "*", "&"]) {
178         // arrange
179         auto be = scoped!Backend();
180         be.ctx.vfs.open(new Blob(Uri("/comp/a.hpp"), format(comp_method, getValue)));
181         be.ctx.vfs.open(new Blob(Uri("/comp_a/a.hpp"), Snippet.comp_a));
182         auto tu0 = be.ctx.makeTranslationUnit("/comp/a.hpp", Snippet.includes);
183         auto tu1 = be.ctx.makeTranslationUnit("/comp_a/a.hpp", Snippet.includes);
184 
185         // act
186         actTwoFiles(tu0, tu1, be);
187 
188         // assert
189         auto result = be.uml_component.relateToFlatArray;
190         result.length.shouldEqual(1);
191 
192         result[0].from.shouldEqual(USRType(Key.comp));
193         result[0].to.shouldEqual(USRType(Key.comp_a));
194     }
195 }
196 
197 @("Should be a component dependency from comp->comp_a via a functions parameter")
198 unittest {
199     enum comp_func = "
200 class A;
201 
202 void free_func(A%s a);
203 ";
204 
205     foreach (getValue; ["", "*", "&"]) {
206         // arrange
207         auto be = scoped!Backend();
208         be.ctx.vfs.open(new Blob(Uri("/comp/fun.hpp"), format(comp_func, getValue)));
209         be.ctx.vfs.open(new Blob(Uri("/comp_a/a.hpp"), Snippet.comp_a));
210         auto tu0 = be.ctx.makeTranslationUnit("/comp/fun.hpp", Snippet.includes);
211         auto tu1 = be.ctx.makeTranslationUnit("/comp_a/a.hpp", Snippet.includes);
212 
213         // act
214         actTwoFiles(tu0, tu1, be);
215 
216         // assert
217         auto result = be.uml_component.relateToFlatArray;
218         result.length.shouldEqual(1);
219 
220         result[0].from.shouldEqual(USRType(Key.comp));
221         result[0].to.shouldEqual(USRType(Key.comp_a));
222     }
223 }
224 
225 @("Should be a component dependency from comp->comp_a via a class member")
226 unittest {
227     enum comp_func = "
228 %s
229 class A;
230 
231 class A_ByMember {
232     A%s a;
233 };";
234 
235     foreach (getValue; ["", "*", "&"]) {
236         // arrange
237         auto be = scoped!Backend();
238         be.ctx.vfs.open(new Blob(Uri("/comp/fun.hpp"), format(comp_func,
239                 getValue.length == 0 ? Snippet.include_comp_a : "", getValue)));
240         be.ctx.vfs.open(new Blob(Uri("/comp_a/a.hpp"), Snippet.comp_a));
241         auto tu0 = be.ctx.makeTranslationUnit("/comp/fun.hpp", Snippet.includes);
242         auto tu1 = be.ctx.makeTranslationUnit("/comp_a/a.hpp", Snippet.includes);
243 
244         // act
245         actTwoFiles(tu0, tu1, be);
246 
247         // assert
248         auto result = be.uml_component.relateToFlatArray;
249         result.length.shouldEqual(1);
250 
251         result[0].from.shouldEqual(USRType(Key.comp));
252         result[0].to.shouldEqual(USRType(Key.comp_a));
253     }
254 }
255 
256 @("Should be a component dependency from comp->comp_a via a free variable")
257 unittest {
258     enum comp_free_variable = "
259 class A;
260 
261 A* a;
262 ";
263 
264     enum comp_global_instantiation = `
265 #include "/comp_a/a.hpp"
266 
267 A a;
268 `;
269 
270     string comp;
271 
272     foreach (getValue; ["instantiation", "pointer"]) {
273         switch (getValue) {
274         case "instantiation":
275             comp = comp_global_instantiation;
276             break;
277         case "pointer":
278             comp = comp_free_variable;
279             break;
280         default:
281             true.shouldBeFalse;
282         }
283 
284         // arrange
285         auto be = scoped!Backend();
286         be.ctx.vfs.open(new Blob(Uri("/comp/fun.hpp"), comp));
287         be.ctx.vfs.open(new Blob(Uri("/comp_a/a.hpp"), Snippet.comp_a));
288         auto tu0 = be.ctx.makeTranslationUnit("/comp/fun.hpp", Snippet.includes);
289         auto tu1 = be.ctx.makeTranslationUnit("/comp_a/a.hpp", Snippet.includes);
290 
291         // act
292         actTwoFiles(tu0, tu1, be);
293 
294         // assert
295         auto result = be.uml_component.relateToFlatArray;
296         result.length.shouldEqual(1);
297 
298         result[0].from.shouldEqual(USRType(Key.comp));
299         result[0].to.shouldEqual(USRType(Key.comp_a));
300     }
301 }