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 }