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 }