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 }