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, cast() params, 94 Lookup(&container), 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 }