1 module xlld.test_util;
2 
3 version(unittest):
4 
5 import unit_threaded;
6 
7 import xlld.xlcall: LPXLOPER12, XLOPER12, XlType;
8 
9 TestAllocator gTestAllocator;
10 /// emulates SRef types by storing what the referenced type actually is
11 XlType gReferencedType;
12 
13 // tracks calls to `coerce` and `free` to make sure memory allocations/deallocations match
14 int gNumXlCoerce;
15 int gNumXlFree;
16 enum maxCoerce = 1000;
17 void*[maxCoerce] gCoerced;
18 void*[maxCoerce] gFreed;
19 double[] gDates;
20 double[] gTimes;
21 
22 version(unittest)
23 {
24     static this() {
25         import xlld.xlcallcpp: SetExcel12EntryPt;
26         // this effectively "implements" the Excel12v function
27         // so that the code can be unit tested without needing to link
28         // with the Excel SDK
29         SetExcel12EntryPt(&excel12UnitTest);
30     }
31 
32     static ~this() {
33         import unit_threaded.should: shouldBeSameSetAs;
34         gCoerced[0 .. gNumXlCoerce].shouldBeSameSetAs(gFreed[0 .. gNumXlFree]);
35     }
36 }
37 
38 extern(Windows) int excel12UnitTest(int xlfn, int numOpers, LPXLOPER12 *opers, LPXLOPER12 result) nothrow @nogc {
39 
40     import xlld.xlcall;
41     import xlld.wrap: toXlOper;
42     import std.experimental.allocator.mallocator: Mallocator;
43     import std.array: front, popFront, empty;
44 
45     switch(xlfn) {
46 
47     default:
48         return xlretFailed;
49 
50     case xlFree:
51         assert(numOpers == 1);
52         auto oper = opers[0];
53 
54         gFreed[gNumXlFree++] = oper.val.str;
55 
56         if(oper.xltype == XlType.xltypeStr) {
57             try
58                 *oper = "".toXlOper(Mallocator.instance);
59             catch(Exception _) {
60                 assert(false, "Error converting in excel12UnitTest");
61             }
62         }
63 
64         return xlretSuccess;
65 
66     case xlCoerce:
67         assert(numOpers == 1);
68 
69         auto oper = opers[0];
70         gCoerced[gNumXlCoerce++] = oper.val.str;
71         *result = *oper;
72 
73         switch(oper.xltype) with(XlType) {
74 
75             case xltypeSRef:
76                 result.xltype = gReferencedType;
77                 break;
78 
79             case xltypeNum:
80             case xltypeStr:
81                 result.xltype = oper.xltype;
82                 break;
83 
84             case xltypeMissing:
85                 result.xltype = xltypeNil;
86                 break;
87 
88             default:
89         }
90 
91         return xlretSuccess;
92 
93     case xlfDate:
94 
95         const ret = gDates.empty ? 0.0 : gDates.front;
96         if(!gDates.empty) gDates.popFront;
97         *result = ret.toXlOper(Mallocator.instance);
98         return xlretSuccess;
99 
100     case xlfTime:
101         const ret = gTimes.empty ? 0.0 : gTimes.front;
102         if(!gTimes.empty) gTimes.popFront;
103 
104         *result = ret.toXlOper(Mallocator.instance);
105         return xlretSuccess;
106     }
107 }
108 
109 // automatically converts from oper to compare with a D type
110 void shouldEqualDlang(U)(LPXLOPER12 actual, U expected, string file = __FILE__, size_t line = __LINE__) @trusted {
111     import xlld.memorymanager: allocator;
112     import xlld.wrap: fromXlOper, stripMemoryBitmask;
113     import xlld.xlcall: XlType;
114     import std.traits: Unqual;
115     import std.conv: text;
116     import std.experimental.allocator.gc_allocator: GCAllocator;
117 
118     actual.shouldNotBeNull;
119 
120     const type = actual.xltype.stripMemoryBitmask;
121 
122     if(type == XlType.xltypeErr)
123         fail("XLOPER is of error type", file, line);
124 
125     static if(!is(Unqual!U == string))
126         if(type == XlType.xltypeStr)
127             fail(text("XLOPER is of string type. Value: ", actual.fromXlOper!string(GCAllocator.instance)), file, line);
128 
129     actual.fromXlOper!U(allocator).shouldEqual(expected, file, line);
130 }
131 
132 // automatically converts from oper to compare with a D type
133 void shouldEqualDlang(U)(ref XLOPER12 actual, U expected, string file = __FILE__, size_t line = __LINE__) @trusted {
134     shouldEqualDlang(&actual, expected, file, line);
135 }
136 
137 // automatically converts from oper to compare with a D type
138 void shouldEqualDlang(U)(XLOPER12 actual, U expected, string file = __FILE__, size_t line = __LINE__) @trusted {
139     shouldEqualDlang(actual, expected, file, line);
140 }
141 
142 
143 XLOPER12 toSRef(T, A)(T val, ref A allocator) @trusted {
144     import xlld.wrap: toXlOper;
145 
146     auto ret = toXlOper(val, allocator);
147     //hide real type somewhere to retrieve it
148     gReferencedType = ret.xltype;
149     ret.xltype = XlType.xltypeSRef;
150     return ret;
151 }
152 
153 // Mimics Excel calling a particular D function, including freeing memory
154 void fromExcel(alias F, A...)(auto ref A args) {
155     import xlld.wrap: wrapModuleFunctionImpl;
156     import xlld.memorymanager: gTempAllocator, autoFree;
157 
158     auto oper = wrapModuleFunctionImpl!F(gTempAllocator, args);
159     autoFree(oper);
160 }
161 
162 
163 // tracks allocations and throws in the destructor if there is a memory leak
164 // it also throws when there is an attempt to deallocate memory that wasn't
165 // allocated
166 struct TestAllocator {
167 
168     import std.experimental.allocator.common: platformAlignment;
169     import std.experimental.allocator.mallocator: Mallocator;
170 
171     alias allocator = Mallocator.instance;
172 
173     private static struct ByteRange {
174         void* ptr;
175         size_t length;
176         inout(void)[] opSlice() @trusted @nogc inout nothrow {
177             return ptr[0 .. length];
178         }
179     }
180 
181     bool debug_;
182     private ByteRange[] _allocations;
183     private ByteRange[] _deallocations;
184     private int _numAllocations;
185 
186     enum uint alignment = platformAlignment;
187 
188     void[] allocate(size_t numBytes) @trusted @nogc {
189         import std.experimental.allocator: makeArray, expandArray;
190         import core.stdc.stdio: printf;
191 
192         static __gshared immutable exception = new Exception("Allocation failed");
193 
194         ++_numAllocations;
195 
196         auto ret = allocator.allocate(numBytes);
197         if(numBytes > 0 && ret.length == 0)
198             throw exception;
199 
200         if(debug_) () @trusted { printf("+   allocate: %p for %d bytes\n", &ret[0], cast(int)ret.length); }();
201         auto newEntry = ByteRange(&ret[0], ret.length);
202 
203         if(_allocations is null)
204             _allocations = allocator.makeArray(1, newEntry);
205         else
206             () @trusted { allocator.expandArray(_allocations, 1, newEntry); }();
207 
208         return ret;
209     }
210 
211     bool deallocate(void[] bytes) @trusted @nogc nothrow {
212         import std.experimental.allocator: makeArray, expandArray;
213         import std.algorithm: remove, find, canFind;
214         import std.array: front, empty;
215         import core.stdc.stdio: printf, sprintf;
216 
217         bool pred(ByteRange other) { return other.ptr == bytes.ptr && other.length == bytes.length; }
218 
219         static char[1024] buffer;
220 
221         if(debug_) printf("- deallocate: %p for %d bytes\n", &bytes[0], cast(int)bytes.length);
222 
223         auto findAllocations = _allocations.find!pred;
224 
225         if(findAllocations.empty) {
226             if(_deallocations.canFind!pred) {
227                 auto index = sprintf(&buffer[0],
228                                      "Double free on byte range Ptr: %p, length: %ld, allocations:\n",
229                                      &bytes[0], bytes.length);
230                 index = printAllocations(buffer, index);
231                 assert(false, buffer[0 .. index]);
232 
233             } else {
234                 auto index = sprintf(&buffer[0],
235                                      "Unknown deallocate byte range. Ptr: %p, length: %ld, allocations:\n",
236                                      &bytes[0], bytes.length);
237                 index = printAllocations(buffer, index);
238                 assert(false, buffer[0 .. index]);
239             }
240         }
241 
242         if(_deallocations is null)
243             _deallocations = allocator.makeArray(1, findAllocations.front);
244         else
245             () @trusted { allocator.expandArray(_allocations, 1, findAllocations.front); }();
246 
247 
248         _allocations = _allocations.remove!pred;
249 
250         return () @trusted { return allocator.deallocate(bytes); }();
251     }
252 
253     bool deallocateAll() @safe @nogc nothrow {
254         import std.array: empty, front;
255 
256         while(!_allocations.empty) {
257             auto allocation = _allocations.front;
258             deallocate(allocation[]);
259         }
260         return true;
261     }
262 
263     auto numAllocations() @safe @nogc pure nothrow const {
264         return _numAllocations;
265     }
266 
267     ~this() @safe @nogc nothrow {
268         verify;
269     }
270 
271     void verify() @trusted @nogc nothrow {
272 
273         static char[1024] buffer;
274 
275         if(_allocations.length) {
276             import core.stdc.stdio: sprintf;
277             auto index = sprintf(&buffer[0], "Memory leak in TestAllocator. Allocations:\n");
278             index = printAllocations(buffer, index);
279             assert(false, buffer[0 .. index]);
280         }
281     }
282 
283     int printAllocations(int N)(ref char[N] buffer, int index = 0) @trusted @nogc const nothrow {
284         import core.stdc.stdio: sprintf;
285         index += sprintf(&buffer[index], "[\n");
286         foreach(ref allocation; _allocations) {
287             index += sprintf(&buffer[index], "    ByteRange(%p, %ld),\n",
288                              allocation.ptr, allocation.length);
289         }
290 
291         index += sprintf(&buffer[index], "]");
292         buffer[index++] = 0; // null terminate
293         return index;
294     }
295 }