1 module dmocks.qualifiers; 2 3 import std.traits; 4 import std.algorithm; 5 import std.range; 6 import std.exception; 7 import std.array; 8 9 import dmocks.util; 10 11 /// Factory for qualifier matches 12 /// specifies match that exactly matches passed method T 13 QualifierMatch qualifierMatch(alias T)() 14 { 15 auto q = QualifierMatch(); 16 auto quals = qualifiers!T; 17 foreach(string name; quals) 18 { 19 q._qualifiers[name] = true; 20 } 21 foreach(string name; validQualifiers.filter!(a=>a !in q._qualifiers)) 22 { 23 q._qualifiers[name] = false; 24 } 25 debug enforceQualifierMatch(q._qualifiers); 26 return q; 27 } 28 29 /// Factory for qualifier match objects 30 /// Let's you create exact pattern for matching methods by qualifiers 31 /// Params: 32 /// quals - map specifying matching condition 33 /// - true - matches to method with qualifier 34 /// - false - matches to method without qualifier 35 /// - not included - qualifier is ignored in matching (optional) 36 QualifierMatch qualifierMatch(bool[string] quals) 37 { 38 enforceQualifierMatch(quals); 39 auto q = QualifierMatch(); 40 q._qualifiers = quals; 41 return q; 42 } 43 44 /// Helper function getting qualifiers string array 45 string[] qualifiers(alias T)() 46 { 47 return getFunctionAttributes!(T)() ~ getMethodAttributes!(T)(); 48 } 49 50 string formatQualifiers(alias T)() 51 { 52 return qualifiers!T.join(" "); 53 } 54 55 /// 56 version (DMocksTest) { 57 unittest { 58 class A 59 { 60 int a; 61 int make() const shared @property 62 { 63 return a; 64 } 65 66 int makePure() inout pure @safe 67 { 68 return a; 69 } 70 71 int makeImut() immutable nothrow @trusted 72 { 73 return a; 74 } 75 76 ref int makeRef() 77 { 78 return a; 79 } 80 } 81 auto aimut = new immutable(A); 82 auto aconst = new const shared(A); 83 auto amut = new A; 84 assert(qualifiers!(aimut.makeImut)().sort().array() == [Qual!"immutable", Qual!"nothrow", Qual!"@trusted"].sort); 85 assert(qualifiers!(aconst.makePure)().sort().array() == [Qual!"@safe", Qual!"inout", Qual!"pure"].sort); 86 assert(qualifiers!(aconst.make)().sort().array() == [Qual!"@property", Qual!"@system", Qual!"const", Qual!"shared"].sort); 87 assert(qualifiers!(amut.makeRef)().sort().array() == [Qual!"@system", Qual!"ref"]); 88 assert(qualifierMatch!(aconst.make).matches([Qual!"@property", Qual!"@system", Qual!"const", Qual!"shared"].sort)); 89 assert(!qualifierMatch!(aconst.make).matches([Qual!"@property", Qual!"@system", Qual!"shared"].sort)); 90 assert(!qualifierMatch!(aconst.make).matches([Qual!"@property", Qual!"ref", Qual!"@system", Qual!"shared"].sort)); 91 assert(!qualifierMatch!(aconst.make).matches(["property", Qual!"@system", Qual!"shared"].sort)); 92 } 93 } 94 95 private string[] getFunctionAttributes(alias T)() 96 { 97 import std.array; 98 enum attributes = functionAttributes!(typeof(&T)); 99 auto ret = appender!(string[]); 100 static if ((attributes & FunctionAttribute.nothrow_) != 0) 101 { 102 ret.put("nothrow"); 103 } 104 static if ((attributes & FunctionAttribute.pure_) != 0) 105 { 106 ret.put("pure"); 107 } 108 static if ((attributes & FunctionAttribute.ref_) != 0) 109 { 110 ret.put("ref"); 111 } 112 static if ((attributes & FunctionAttribute.property) != 0) 113 { 114 ret.put("@property"); 115 } 116 static if ((attributes & FunctionAttribute.trusted) != 0) 117 { 118 ret.put("@trusted"); 119 } 120 static if ((attributes & FunctionAttribute.safe) != 0) 121 { 122 ret.put("@safe"); 123 } 124 static if ((attributes & FunctionAttribute.safe) == 0 && (attributes & FunctionAttribute.trusted) == 0) 125 { 126 ret.put("@system"); 127 } 128 return ret.data; 129 } 130 131 private string[] getMethodAttributes(alias T)() 132 { 133 alias FunctionTypeOf!T TYPE; 134 import std.array; 135 auto ret = appender!(string[]); 136 static if (is(TYPE == const)) 137 { 138 ret.put("const"); 139 } 140 static if (is(TYPE == immutable)) 141 { 142 ret.put("immutable"); 143 } 144 static if (is(TYPE == shared)) 145 { 146 ret.put("shared"); 147 } 148 static if (is(TYPE == inout)) 149 { 150 ret.put("inout"); 151 } 152 return ret.data; 153 } 154 155 /// checks if qualifiers is a unique set of valid qualifiers 156 public void enforceQualifierNames(string[] qualifiers) 157 { 158 enforceEx!(MocksSetupException)(qualifiers.uniq.array == qualifiers,"Qualifiers: given qualifiers are not unique: " ~ qualifiers.join(" ")); 159 160 // bad perf, but data is small 161 foreach(string q; qualifiers) 162 { 163 enforceEx!(MocksSetupException)(validQualifiers.canFind(q), "Qualifiers: found invalid qualifier: " ~ q); 164 } 165 } 166 167 private immutable string[] validQualifiers = sort(["const", "shared", "immutable", "nothrow", "pure", "ref", "@property", "@trusted", "@safe", "inout", "@system"]).array; 168 169 /// validates qualifier name 170 template Qual(string val) 171 { 172 static assert(validQualifiers.canFind(val), "Incorrect qualifier name"); 173 enum Qual = val; 174 } 175 176 /// 177 version (DMocksTest) { 178 unittest { 179 static assert(__traits(compiles, Qual!"const")); 180 static assert(!__traits(compiles, Qual!"consta")); 181 enforceQualifierNames([Qual!"const", Qual!"@property"]); 182 assertThrown!(MocksSetupException)(enforceQualifierNames([Qual!"const", Qual!"const", Qual!"@property"])); 183 assertThrown!(MocksSetupException)(enforceQualifierNames(["consta", Qual!"@property"])); 184 } 185 } 186 187 /// check if qualifier match is correctly formulated 188 void enforceQualifierMatch(bool[string] qualifiers) 189 { 190 enforceQualifierNames(qualifiers.keys); 191 bool testBothSet(string first, string second)() 192 { 193 return Qual!first in qualifiers && Qual!second in qualifiers && qualifiers[Qual!first] && qualifiers[Qual!second]; 194 } 195 bool testThreeForbidden(string first, string second, string third)() 196 { 197 return Qual!first in qualifiers && Qual!second && Qual!second in qualifiers && !qualifiers[Qual!first] && !qualifiers[Qual!second] && !qualifiers[Qual!third]; 198 } 199 void enforceBothNotSet(string first, string second)() 200 { 201 enforceEx!(MocksSetupException)(!testBothSet!(first, second), "Qualifiers: cannot require both "~first~" and "~second); 202 } 203 void enforceThreeNotSet(string first, string second, string third)() 204 { 205 enforceEx!(MocksSetupException)(!testThreeForbidden!(first, second, third), "Qualifiers: cannot forbid all "~first~", "~second~" and "~third); 206 } 207 enforceBothNotSet!("@trusted", "@safe"); 208 enforceBothNotSet!("@system", "@trusted"); 209 enforceBothNotSet!("@system", "@safe"); 210 enforceBothNotSet!("const", "immutable"); 211 enforceBothNotSet!("const", "inout"); 212 enforceBothNotSet!("immutable", "inout"); 213 enforceThreeNotSet!("@system", "@safe", "@trusted"); 214 } 215 216 /++ 217 + type that allows you to specify which qualifiers are required in a match 218 + stores required and forbidden qualifiers 219 +/ 220 struct QualifierMatch 221 { 222 private bool[string] _qualifiers; 223 224 /// 225 string toString() const 226 { 227 auto opt = validQualifiers.dup.filter!((a)=> a !in _qualifiers)().join(" "); 228 return _qualifiers.keys.filter!((a)=> _qualifiers[a])().join(" ") ~ 229 (opt.length != 0 ? " (optional: " ~ opt ~")" : ""); 230 } 231 232 /// returns true if all required qualifiers are present and all forbidden are absent in against array 233 bool matches(string[] against) const 234 { 235 debugLog("QualifierMatch: match against: "~ against.join(" ")); 236 debugLog("state: " ~ toString()); 237 foreach(string searched; against) 238 { 239 const(bool)* found = searched in _qualifiers; 240 if (found is null) 241 continue; 242 if (!(*found)) 243 return false; 244 } 245 246 foreach(string key, const(bool) val; _qualifiers) 247 { 248 if (!val) 249 continue; 250 if (!against.canFind(key)) 251 return false; 252 } 253 return true; 254 } 255 }