1 /** 2 * Handels all BDD information concerning the Feature 3 * Comments, description, function-names, scenarios and actions 4 */ 5 module tagion.behaviour.BehaviourFeature; 6 7 import std.format; 8 import std.meta : Alias, AliasSeq, ApplyLeft, ApplyRight, Erase, Filter, aliasSeqOf, aliasSeqOf, allSatisfy, anySatisfy; 9 import std.traits; 10 import std.typecons; 11 import tagion.basic.basic : isOneOf, staticSearchIndexOf; 12 import tagion.basic.traits : hasOneMemberUDA; 13 import tagion.hibon.Document; 14 import tagion.hibon.HiBONRecord; 15 16 /* 17 * Set the common property for 18 * Feature, Scenario and the Actions (Given,When,Then and But) 19 */ 20 @safe: 21 mixin template Property() { 22 string description; 23 @label(VOID) @optional string[] comments; 24 mixin HiBONRecord!(q{ 25 this(string description, string[] comments=null ) pure nothrow { 26 this.description = description; 27 this.comments = comments; 28 } 29 this(T)(T prop) pure nothrow { 30 description = prop.description; 31 comments = prop.comments; 32 } 33 }); 34 } 35 36 @recordType("Feature") 37 struct Feature { 38 mixin Property; 39 } 40 41 struct Scenario { 42 mixin Property; 43 } 44 45 /// Action property for Given 46 struct Given { 47 mixin Property; 48 } 49 50 /// Action property for When 51 struct When { 52 mixin Property; 53 } 54 55 /// Action property for Then 56 struct Then { 57 mixin Property; 58 } 59 60 /// Action property for But 61 struct But { 62 mixin Property; 63 } 64 65 enum isDescriptor(T) = hasMember!(T, "description"); 66 67 /* 68 * Contains the information for the Protorty including the name id and the result of the Property 69 * The Property is as a general container for the Feature, Scenario and Actions 70 */ 71 struct Info(alias Property) { 72 Property property; /// The property is a Feature, Scenario or an Action 73 string name; /// Name of the function member, scenario call or feature module 74 Document result; /// The result after execution of the property (See BehaviourResult) 75 mixin HiBONRecord!(); 76 } 77 78 /// Returns: true if I is a Info template 79 enum isInfo(alias I) = __traits(isSame, TemplateOf!I, Info); 80 81 /** 82 * The Action group contains a list of acrion with the property defined which is a part of the 83 * behaviour property 84 */ 85 struct ActionGroup(Property) if (isOneOf!(Property, ActionProperties)) { 86 Info!Property[] infos; 87 mixin HiBONRecord!(); 88 } 89 90 /// Returns: true if I is a ActionGroup 91 enum isActionGroup(alias I) = __traits(isSame, TemplateOf!I, ActionGroup); 92 93 /** 94 * Contains all information of a scenario 95 * the class name of the scenario and the description 96 * it also contains all the action groups of the scenario 97 */ 98 @safe 99 struct ScenarioGroup { 100 @("Scenario") Info!Scenario info; 101 ActionGroup!(Given) given; /// Given actions 102 @optional ActionGroup!(When) when; /// When actions 103 ActionGroup!(Then) then; /// Then actions 104 @optional ActionGroup!(But) but; /// But actions 105 mixin HiBONRecord!(); 106 } 107 108 /** 109 * Contains all the sceanrio groups and information of the Feature 110 */ 111 @safe 112 struct FeatureGroup { 113 @optional string alternative; 114 Info!Feature info; /// Information of the Feature 115 ScenarioGroup[] scenarios; /// This all the information of each Sceanrio 116 mixin HiBONRecord!(); 117 } 118 119 /// All action-properties of a Scenario 120 alias ActionProperties = AliasSeq!(Given, When, Then, But); 121 /// All mandatory actions of a Scenario (Given, Then) 122 alias MandatoryActionProperties = Erase!(When, Erase!(But, ActionProperties)); 123 124 /** 125 * Params: 126 * T = Scenario class 127 * name = the action member function 128 * Returns: method in a scenario class 129 * void if no member has been found 130 */ 131 template getMethod(alias T, string name) { 132 alias method = __traits(getOverloads, T, name); 133 static if (method.length > 0) { 134 alias getMethod = method[0]; 135 } 136 else { 137 alias getMethod = void; 138 } 139 } 140 141 // Check of the getMethod 142 static unittest { 143 static assert(isCallable!(getMethod!(BehaviourUnittest.Some_awesome_feature, "is_debited"))); 144 static assert(!isCallable!(getMethod!(BehaviourUnittest.Some_awesome_feature, "count"))); 145 } 146 147 /** 148 * Returns: an alias-sequency of all the callable members of an object/Scenario 149 */ 150 template getAllCallables(T) if (is(T == class) || is(T == struct)) { 151 alias all_members = aliasSeqOf!([__traits(allMembers, T)]); 152 alias all_members_as_aliases = staticMap!(ApplyLeft!(getMethod, T), all_members); 153 alias getAllCallables = Filter!(isCallable, all_members_as_aliases); 154 } 155 156 static unittest { // Test of getAllCallable 157 import tagion.behaviour.BehaviourUnittest; 158 159 alias all_callables = getAllCallables!(Some_awesome_feature); 160 static assert(all_callables.length == 13); 161 static assert(allSatisfy!(isCallable, all_callables)); 162 } 163 164 /** 165 * Returns: true if the alias T is an Action 166 */ 167 template hasActions(alias T) if (isCallable!T) { 168 alias hasProperty = ApplyLeft!(hasOneMemberUDA, T); 169 enum hasActions = anySatisfy!(hasProperty, ActionProperties); 170 } 171 172 // Check if a function is an action or not 173 static unittest { 174 static assert(hasActions!(BehaviourUnittest.Some_awesome_feature.is_valid)); 175 static assert(!hasActions!(BehaviourUnittest.Some_awesome_feature.helper_function)); 176 } 177 178 /** 179 Returns: 180 all the actions in a scenario 181 */ 182 template getAllActions(T) if (is(T == class) || is(T == struct)) { 183 alias get_all_callable = getAllCallables!T; 184 alias getAllActions = Filter!(hasActions, get_all_callable); 185 } 186 187 /// 188 static unittest { // Test of getAllActions 189 alias actions = getAllActions!(BehaviourUnittest.Some_awesome_feature); 190 static assert(actions.length == 7); 191 static assert(allSatisfy!(isCallable, actions)); 192 static assert(allSatisfy!(hasActions, actions)); 193 } 194 195 /** 196 This template get the action with the behaviour-Property from a Behaviour object 197 Returns: The function with the behaviour-Property 198 The function fails if there is more than one behaviour with this behaviour 199 and returns void if no behaviour-Property has been found 200 */ 201 template getActions(T, Property) if (is(T == class) || is(T == struct)) { 202 alias behaviours = getAllActions!T; 203 alias behaviour_with_property = Filter!(ApplyRight!(hasOneMemberUDA, Property), behaviours); 204 static if (behaviour_with_property.length > 0) { 205 alias getActions = behaviour_with_property; 206 } 207 else { 208 alias getActions = void; 209 } 210 211 } 212 213 /// 214 unittest { 215 alias behaviour_with_given = getActions!(BehaviourUnittest.Some_awesome_feature, Given); 216 static assert(allSatisfy!(isCallable, behaviour_with_given)); 217 218 static assert(allSatisfy!(ApplyRight!(hasOneMemberUDA, Given), behaviour_with_given)); 219 static assert(is(getActions!(BehaviourUnittest.Some_awesome_feature_bad_format_missing_given, Given) == void)); 220 221 alias behaviour_with_when = getActions!(BehaviourUnittest.Some_awesome_feature, When); 222 static assert(isCallable!(behaviour_with_when)); 223 static assert(hasOneMemberUDA!(behaviour_with_when, When)); 224 225 } 226 227 /// Returns: true if T has the Property 228 enum hasProperty(alias T, Property) = !is(getActions!(T, Property) == void); 229 230 /// 231 unittest { 232 static assert(hasProperty!(BehaviourUnittest.Some_awesome_feature, Then)); 233 static assert(!hasProperty!(BehaviourUnittest.Some_awesome_feature_bad_format_missing_given, Given)); 234 } 235 236 /** 237 * Get the action propery of the alias T 238 * Returns: The behaviour property of T and void if T does not have a behaviour property 239 */ 240 template getProperty(alias T) { 241 import tagion.basic.traits : getMemberUDAs; 242 243 alias getUDAsProperty = ApplyLeft!(getMemberUDAs, T); 244 alias all_behaviour_properties = staticMap!(getUDAsProperty, ActionProperties); 245 static assert(all_behaviour_properties.length <= 1, 246 format!"The behaviour %s has more than one property %s"(T.strinof, all_behaviour_properties.stringof)); 247 static if (all_behaviour_properties.length is 1) { 248 alias getProperty = all_behaviour_properties[0]; 249 } 250 else { 251 alias getProperty = void; 252 } 253 } 254 255 /// Examples: How get the behaviour property 256 unittest { 257 alias properties = getProperty!(BehaviourUnittest.Some_awesome_feature.request_cash); 258 static assert(is(typeof(properties) == When)); 259 static assert(is(getProperty!(BehaviourUnittest.Some_awesome_feature.helper_function) == void)); 260 } 261 262 // Test of the getActions and check that the action-property is correct 263 @safe 264 unittest { 265 alias behaviour_of_given = getActions!(BehaviourUnittest.Some_awesome_feature, Given); 266 static assert(behaviour_of_given.length is 3); 267 static assert(getProperty!(behaviour_of_given[0]) == Given("the card is valid")); 268 static assert(getProperty!(behaviour_of_given[1]) == Given("the account is in credit")); 269 static assert(getProperty!(behaviour_of_given[2]) == Given("the dispenser contains cash")); 270 271 alias behaviour_of_when = getActions!(BehaviourUnittest.Some_awesome_feature, When); 272 static assert(behaviour_of_when.length is 1); 273 static assert(getProperty!(behaviour_of_when[0]) == When("the Customer request cash")); 274 275 alias behaviour_of_then = getActions!(BehaviourUnittest.Some_awesome_feature, Then); 276 static assert(behaviour_of_then.length is 2); 277 static assert(getProperty!(behaviour_of_then[0]) == Then("the account is debited")); 278 static assert(getProperty!(behaviour_of_then[1]) == Then("the cash is dispensed")); 279 280 alias behaviour_of_but = getActions!(BehaviourUnittest.Some_awesome_feature, But); 281 static assert(behaviour_of_but.length is 1); 282 static assert(getProperty!(behaviour_of_but[0]) == 283 But("if the Customer does not take his card, then the card must be swollowed")); 284 } 285 286 ///Returns: true of T is a Scenario 287 enum isScenario(T) = (is(T == struct) || is(T == class)) && hasUDA!(T, Scenario); 288 289 /// 290 static unittest { 291 static assert(isScenario!(BehaviourUnittest.Some_awesome_feature)); 292 static assert(!isScenario!(BehaviourUnittest.This_is_not_a_scenario)); 293 } 294 295 enum feature_name = "feature"; /// Default enum name of an Feature module 296 297 /** 298 * Params: 299 * M = the module 300 * Returns: true if M is a feature module 301 */ 302 template isFeature(alias M) if (__traits(isModule, M)) { 303 import std.algorithm.searching : any; 304 305 enum feature_found = [__traits(allMembers, M)].any!(a => a == feature_name); 306 static if (feature_found) { 307 enum obtainFeature = __traits(getMember, M, feature_name); 308 enum isFeature = is(typeof(obtainFeature) == Feature); 309 } 310 else { 311 enum isFeature = false; 312 } 313 } 314 315 /// 316 @safe 317 unittest { 318 import tagion.behaviour.BehaviourUnittest; 319 320 static assert(isFeature!(tagion.behaviour.BehaviourUnittest)); 321 static assert(!isFeature!(tagion.behaviour.BehaviourFeature)); 322 } 323 324 /** 325 Returns: 326 The Feature of a Module 327 If the Modules does not contain a feature then a false is returned 328 */ 329 template obtainFeature(alias M) if (__traits(isModule, M)) { 330 static if (isFeature!M) { 331 enum obtainFeature = __traits(getMember, M, feature_name); 332 } 333 else { 334 enum obtainFeature = false; 335 } 336 } 337 338 /// 339 @safe 340 unittest { // The obtainFeature of a module 341 import tagion.behaviour.BehaviourUnittest; 342 343 static assert(obtainFeature!(tagion.behaviour.BehaviourUnittest) == 344 Feature( 345 "Some awesome feature should print some cash out of the blue", null)); 346 static assert(!obtainFeature!(tagion.behaviour.BehaviourFeature)); 347 } 348 349 /// Helper template for Scenarios 350 protected template _Scenarios(alias M, string[] names) if (isFeature!M) { 351 static if (names.length is 0) { 352 alias _Scenarios = AliasSeq!(); 353 } 354 else { 355 alias member = __traits(getMember, M, names[0]); 356 enum is_object = is(member == class) || is(member == struct); 357 static if (is_object) { 358 enum is_scenario = hasUDA!(member, Scenario); 359 } 360 else { 361 enum is_scenario = false; 362 } 363 static if (is_scenario) { 364 alias _Scenarios = 365 AliasSeq!( 366 member, 367 _Scenarios!(M, names[1 .. $]) 368 ); 369 } 370 else { 371 alias _Scenarios = _Scenarios!(M, names[1 .. $]); 372 } 373 } 374 } 375 376 /** 377 * Returns: An alias-sequency of all scenarios in the feature module M 378 */ 379 template Scenarios(alias M) if (isFeature!M) { 380 alias Scenarios = _Scenarios!(M, [__traits(allMembers, M)]); 381 } 382 383 /// 384 static unittest { // 385 import tagion.behaviour.BehaviourUnittest; 386 387 alias scenarios = Scenarios!(tagion.behaviour.BehaviourUnittest); 388 alias expected_scenarios = AliasSeq!( 389 Some_awesome_feature, 390 Some_awesome_feature_bad_format_double_property, 391 Some_awesome_feature_bad_format_missing_given, 392 Some_awesome_feature_bad_format_missing_then); 393 394 static assert(scenarios.length == expected_scenarios.length); 395 static assert(__traits(isSame, scenarios, expected_scenarios)); 396 } 397 398 /** 399 * Returns: The Scenario UDA of T and if T is not a Scenario then result is false 400 */ 401 template getScenario(T) if (is(T == class) || is(T == struct)) { 402 enum scenario_attr = getUDAs!(T, Scenario); 403 static assert(scenario_attr.length <= 1, 404 format!"%s is not a %s"(T.stringof, Scenario.stringof)); 405 static if (scenario_attr.length is 1) { 406 enum getScenario = scenario_attr[0]; 407 } 408 else { 409 enum getScenario = false; 410 } 411 } 412 413 // Checks the getScenario 414 static unittest { 415 import tagion.behaviour.BehaviourUnittest; 416 417 enum scenario = getScenario!(Some_awesome_feature); 418 static assert(is(typeof(scenario) == Scenario)); 419 static assert(scenario is Scenario("Some awesome money printer", null)); 420 enum not_a_scenario = getScenario!(This_is_not_a_scenario); 421 static assert(!not_a_scenario); 422 } 423 424 version (unittest) { 425 import std.algorithm.comparison : equal; 426 import std.algorithm.iteration : joiner, map; 427 import std.array; 428 import std.range : only, zip; 429 import std.stdio; 430 import std.typecons; 431 import BehaviourUnittest = tagion.behaviour.BehaviourUnittest; 432 }