1 /**
2     Utility test functions
3 */
4 module xlld.test.util;
5 
6 import xlld.sdk.xlcall: XLOPER12, XlType;
7 version(testingExcelD) import unit_threaded;
8 
9 ///
10 TestAllocator gTestAllocator;
11 /// emulates SRef types by storing what the referenced type actually is
12 XlType gReferencedType;
13 
14 // tracks calls to `coerce` and `free` to make sure memory allocations/deallocations match
15 int gNumXlAllocated;
16 ///
17 int gNumXlFree;
18 ///
19 enum maxAllocTrack = 1000;
20 ///
21 const(void)*[maxAllocTrack] gAllocated;
22 ///
23 const(void)*[maxAllocTrack] gFreed;
24 
25 alias XlFunction = int;
26 
27 /**
28    This stores what Excel12f should "return" for each integer XL "function"
29  */
30 XLOPER12[][XlFunction] gXlFuncResults;
31 
32 shared AA!(XLOPER12, XLOPER12) gAsyncReturns = void;
33 
34 
35 XLOPER12 asyncReturn(XLOPER12 asyncHandle) @safe {
36     return gAsyncReturns[asyncHandle];
37 }
38 
39 private void fakeAllocate(XLOPER12 oper) @nogc nothrow {
40     fakeAllocate(&oper);
41 }
42 
43 private void fakeAllocate(XLOPER12* oper) @nogc nothrow {
44     gAllocated[gNumXlAllocated++] = oper.val.str;
45 }
46 
47 
48 private void fakeFree(XLOPER12* oper) @nogc nothrow {
49     gFreed[gNumXlFree++] = oper.val.str;
50 }
51 
52 ///
53 extern(Windows) int excel12UnitTest(int xlfn, int numOpers, XLOPER12** opers, XLOPER12* result)
54     nothrow @nogc
55 {
56 
57     import xlld.sdk.xlcall;
58     import xlld.conv: toXlOper;
59     import xlld.conv.misc: stripMemoryBitmask;
60     import std.experimental.allocator.gc_allocator: GCAllocator;
61     import std.experimental.allocator.mallocator: Mallocator;
62     import std.array: front, popFront, empty;
63 
64     if(auto xlfnResults = xlfn in gXlFuncResults) {
65 
66         assert(!(*xlfnResults).empty, "No results to return for xlfn");
67 
68         auto mockResult = (*xlfnResults).front;
69         (*xlfnResults).popFront;
70 
71         if(xlfn == xlfCaller) {
72             fakeAllocate(mockResult);
73         }
74         *result = mockResult;
75         return xlretSuccess;
76     }
77 
78     switch(xlfn) {
79 
80     default:
81         return xlretFailed;
82 
83     case xlFree:
84         assert(numOpers == 1);
85         auto oper = opers[0];
86 
87         fakeFree(oper);
88 
89         if(oper.xltype == XlType.xltypeStr) {
90             try
91                 *oper = "".toXlOper(Mallocator.instance);
92             catch(Exception _) {
93                 assert(false, "Error converting in excel12UnitTest");
94             }
95         }
96 
97         return xlretSuccess;
98 
99     case xlCoerce:
100         assert(numOpers == 1);
101 
102         auto oper = opers[0];
103         fakeAllocate(oper);
104         *result = *oper;
105 
106         switch(oper.xltype) with(XlType) {
107 
108             case xltypeSRef:
109                 result.xltype = gReferencedType;
110                 break;
111 
112             case xltypeNum:
113             case xltypeStr:
114                 result.xltype = oper.xltype;
115                 break;
116 
117             case xltypeMissing:
118                 result.xltype = xltypeNil;
119                 break;
120 
121             default:
122         }
123 
124         return xlretSuccess;
125 
126     case xlAsyncReturn:
127         assert(numOpers == 2);
128         gAsyncReturns[*opers[0]] = *opers[1];
129         return xlretSuccess;
130     }
131 }
132 
133 
134 /// automatically converts from oper to compare with a D type
135 void shouldEqualDlang(U)
136                      (XLOPER12* actual,
137                       U expected,
138                       string file = __FILE__,
139                       size_t line = __LINE__)
140     @trusted
141 {
142     import xlld.memorymanager: allocator;
143     import xlld.conv.from: fromXlOper;
144     import xlld.conv.misc: stripMemoryBitmask;
145     import xlld.sdk.xlcall: XlType;
146     import std.traits: Unqual;
147     import std.conv: text;
148     import std.experimental.allocator.gc_allocator: GCAllocator;
149 
150     actual.shouldNotBeNull;
151 
152     const type = actual.xltype.stripMemoryBitmask;
153 
154     if(type == XlType.xltypeErr)
155         fail("XLOPER is of error type", file, line);
156 
157     static if(!is(Unqual!U == string))
158         if(type == XlType.xltypeStr)
159             fail(text("XLOPER is of string type. Value: ", actual.fromXlOper!string(GCAllocator.instance)), file, line);
160 
161     actual.fromXlOper!U(allocator).shouldEqual(expected, file, line);
162 }
163 
164 /// automatically converts from oper to compare with a D type
165 void shouldEqualDlang(U)(ref XLOPER12 actual, U expected, string file = __FILE__, size_t line = __LINE__) @trusted {
166     shouldEqualDlang(&actual, expected, file, line);
167 }
168 
169 /// automatically converts from oper to compare with a D type
170 void shouldEqualDlang(U)(XLOPER12 actual, U expected, string file = __FILE__, size_t line = __LINE__) @trusted {
171     shouldEqualDlang(actual, expected, file, line);
172 }
173 
174 
175 ///
176 XLOPER12 toSRef(T, A)(T val, ref A allocator) @trusted {
177     import xlld.conv: toXlOper;
178 
179     auto ret = toXlOper(val, allocator);
180     //hide real type somewhere to retrieve it
181     gReferencedType = ret.xltype;
182     ret.xltype = XlType.xltypeSRef;
183     return ret;
184 }
185 
186 /// Mimics Excel calling a particular D function, including freeing memory
187 void fromExcel(alias F, A...)(auto ref A args) {
188     import xlld.wrap: wrapModuleFunctionImpl;
189     import xlld.memorymanager: gTempAllocator, autoFree;
190 
191     auto oper = wrapModuleFunctionImpl!F(gTempAllocator, args);
192     autoFree(oper);
193 }
194 
195 
196 /// tracks allocations and throws in the destructor if there is a memory leak
197 /// it also throws when there is an attempt to deallocate memory that wasn't
198 /// allocated
199 struct TestAllocator {
200 
201     import std.experimental.allocator.common: platformAlignment;
202     import std.experimental.allocator.mallocator: Mallocator;
203 
204     ///
205     alias allocator = Mallocator.instance;
206 
207     private static struct ByteRange {
208         void* ptr;
209         size_t length;
210         inout(void)[] opSlice() @trusted @nogc inout nothrow {
211             return ptr[0 .. length];
212         }
213     }
214 
215     ///
216     bool debug_;
217     private ByteRange[] _allocations;
218     private ByteRange[] _deallocations;
219     private int _numAllocations;
220 
221     ///
222     enum uint alignment = platformAlignment;
223 
224     ///
225     void[] allocate(size_t numBytes) @trusted @nogc {
226         import std.experimental.allocator: makeArray, expandArray;
227         import core.stdc.stdio: printf;
228 
229         static __gshared immutable exception = new Exception("Allocation failed");
230 
231         ++_numAllocations;
232 
233         auto ret = allocator.allocate(numBytes);
234         if(numBytes > 0 && ret.length == 0)
235             throw exception;
236 
237         if(debug_) () @trusted { printf("+   allocate: %p for %d bytes\n", &ret[0], cast(int)ret.length); }();
238         auto newEntry = ByteRange(&ret[0], ret.length);
239 
240         if(_allocations is null)
241             _allocations = allocator.makeArray(1, newEntry);
242         else
243             () @trusted { allocator.expandArray(_allocations, 1, newEntry); }();
244 
245         return ret;
246     }
247 
248     ///
249     bool deallocate(void[] bytes) @trusted @nogc nothrow {
250         import std.experimental.allocator: makeArray, expandArray;
251         import std.algorithm: remove, find, canFind;
252         import std.array: front, empty;
253         import core.stdc.stdio: printf, sprintf;
254 
255         bool pred(ByteRange other) { return other.ptr == bytes.ptr && other.length == bytes.length; }
256 
257         static char[1024] buffer;
258 
259         if(debug_) printf("- deallocate: %p for %d bytes\n", &bytes[0], cast(int)bytes.length);
260 
261         auto findAllocations = _allocations.find!pred;
262 
263         if(findAllocations.empty) {
264             if(_deallocations.canFind!pred) {
265                 auto index = sprintf(&buffer[0],
266                                      "Double free on byte range Ptr: %p, length: %ld, allocations:\n",
267                                      &bytes[0], bytes.length);
268                 index = printAllocations(buffer, index);
269                 assert(false, buffer[0 .. index]);
270 
271             } else {
272                 auto index = sprintf(&buffer[0],
273                                      "Unknown deallocate byte range. Ptr: %p, length: %ld, allocations:\n",
274                                      &bytes[0], bytes.length);
275                 index = printAllocations(buffer, index);
276                 assert(false, buffer[0 .. index]);
277             }
278         }
279 
280         if(_deallocations is null)
281             _deallocations = allocator.makeArray(1, findAllocations.front);
282         else
283             () @trusted { allocator.expandArray(_allocations, 1, findAllocations.front); }();
284 
285 
286         _allocations = _allocations.remove!pred;
287 
288         return () @trusted { return allocator.deallocate(bytes); }();
289     }
290 
291     ///
292     bool deallocateAll() @safe @nogc nothrow {
293         import std.array: empty, front;
294 
295         while(!_allocations.empty) {
296             auto allocation = _allocations.front;
297             deallocate(allocation[]);
298         }
299         return true;
300     }
301 
302     ///
303     auto numAllocations() @safe @nogc pure nothrow const {
304         return _numAllocations;
305     }
306 
307     ///
308     ~this() @safe @nogc nothrow {
309         verify;
310     }
311 
312     ///
313     void verify() @trusted @nogc nothrow {
314 
315         static char[1024] buffer;
316 
317         if(_allocations.length) {
318             import core.stdc.stdio: sprintf;
319             auto index = sprintf(&buffer[0], "Memory leak in TestAllocator. Allocations:\n");
320             index = printAllocations(buffer, index);
321             assert(false, buffer[0 .. index]);
322         }
323     }
324 
325     ///
326     int printAllocations(int N)(ref char[N] buffer, int index = 0) @trusted @nogc const nothrow {
327         import core.stdc.stdio: sprintf;
328         index += sprintf(&buffer[index], "[\n");
329         foreach(ref allocation; _allocations) {
330             index += sprintf(&buffer[index], "    ByteRange(%p, %ld),\n",
331                              allocation.ptr, allocation.length);
332         }
333 
334         index += sprintf(&buffer[index], "]");
335         buffer[index++] = 0; // null terminate
336         return index;
337     }
338 }
339 
340 
341 /**
342    @nogc associative array
343  */
344 struct AA(K, V, int N = 100) {
345     import core.sync.mutex: Mutex;
346 
347     Entry[N] entries;
348     size_t index;
349     Mutex mutex;
350 
351     static struct Entry {
352         K key;
353         V value;
354     }
355 
356     @disable this();
357 
358     static shared(AA) create() @trusted {
359         shared AA aa = void;
360         aa.index = 0;
361         aa.mutex = new shared Mutex;
362         foreach(ref entry; aa.entries[]) {
363             entry = typeof(entry).init;
364         }
365         return aa;
366     }
367 
368     V opIndex(in K key) shared {
369         import std.algorithm: find;
370         import std.array: front, empty;
371 
372         mutex.lock_nothrow;
373         scope(exit) mutex.unlock_nothrow;
374 
375         auto fromKey = () @trusted { return entries[].find!(e => cast(K)e.key == key); }();
376         return fromKey.empty
377             ? V.init
378             : cast(V)fromKey.front.value;
379     }
380 
381     void opIndexAssign(V value, K key) shared {
382         import core.atomic: atomicOp;
383 
384         mutex.lock_nothrow;
385         scope(exit) mutex.unlock_nothrow;
386 
387         assert(index < N - 1, "No more space");
388         entries[index] = Entry(key, value);
389         index.atomicOp!"+="(1);
390     }
391 }
392 
393 @("AA")
394 @safe unittest {
395     import core.exception: AssertError;
396     auto aa = AA!(string, int).create;
397     aa["foo"] = 5;
398     aa["foo"].shouldEqual(5);
399     aa["bar"].shouldEqual(0);
400 }
401 
402 
403 XLOPER12 newAsyncHandle() @safe nothrow {
404     import xlld.sdk.xlcall: XlType;
405     import core.atomic: atomicOp;
406 
407     static shared typeof(XLOPER12.val.w) index = 1;
408     XLOPER12 asyncHandle;
409 
410     asyncHandle.xltype = XlType.xltypeBigData;
411     () @trusted { asyncHandle.val.bigdata.h.hdata = cast(void*)index; }();
412     index.atomicOp!"+="(1);
413 
414     return asyncHandle;
415 }
416 
417 struct MockXlFunction {
418 
419     XlFunction xlFunction;
420     typeof(gXlFuncResults) oldResults;
421 
422     this(int xlFunction, XLOPER12 result) @safe {
423         this(xlFunction, [result]);
424     }
425 
426     this(int xlFunction, XLOPER12[] results) @safe {
427         this.xlFunction = xlFunction;
428         this.oldResults = gXlFuncResults.dup;
429         gXlFuncResults[xlFunction] ~= results;
430     }
431 
432     this(int xlFunction, double value) @safe {
433         import xlld.conv.to: toXlOper;
434         import std.experimental.allocator.gc_allocator: GCAllocator;
435         this(xlFunction, value.toXlOper(GCAllocator.instance));
436     }
437 
438     ~this() @safe {
439         gXlFuncResults = oldResults;
440     }
441 }
442 
443 struct MockDateTime {
444 
445     import xlld.sdk.xlcall: xlfYear, xlfMonth, xlfDay, xlfHour, xlfMinute, xlfSecond;
446 
447     MockXlFunction year, month, day, hour, minute, second;
448 
449     this(int year, int month, int day, int hour, int minute, int second) @safe {
450         import xlld.conv: toXlOper;
451         import std.experimental.allocator.gc_allocator: GCAllocator;
452         alias theGC = GCAllocator.instance;
453 
454         this.year   = MockXlFunction(xlfYear,   double(year).toXlOper(theGC));
455         this.month  = MockXlFunction(xlfMonth,  double(month).toXlOper(theGC));
456         this.day    = MockXlFunction(xlfDay,    double(day).toXlOper(theGC));
457         this.hour   = MockXlFunction(xlfHour,   double(hour).toXlOper(theGC));
458         this.minute = MockXlFunction(xlfMinute, double(minute).toXlOper(theGC));
459         this.second = MockXlFunction(xlfSecond, double(second).toXlOper(theGC));
460     }
461 }
462 
463 struct MockDateTimes {
464 
465     import std.datetime: DateTime;
466     import std.range: isInputRange, ElementType;
467     import std.traits: Unqual;
468 
469     MockDateTime[] mocks;
470 
471     this(DateTime[] dateTimes...) @safe {
472         fromRange(dateTimes);
473     }
474 
475     this(R)(R dateTimes) @safe if(isInputRange!R && is(Unqual!(ElementType!R) == DateTime)) {
476         fromRange(dateTimes);
477     }
478 
479     private void fromRange(R)(R dateTimes) @trusted if(isInputRange!R && is(Unqual!(ElementType!R) == DateTime)) {
480         foreach(dateTime; dateTimes)
481             mocks ~= MockDateTime(dateTime.year, dateTime.month, dateTime.day,
482                                   dateTime.hour, dateTime.minute, dateTime.second);
483     }
484 }
485 
486 struct MockDates {
487     import std.range: isInputRange, ElementType;
488 
489     MockXlFunction[] mocks;
490 
491     this(R)(R dates) if(isInputRange!R) {
492         import xlld.sdk.xlcall: xlfDate;
493         foreach(date; dates)
494             mocks ~= MockXlFunction(xlfDate, double(date));
495     }
496 }
497 
498 struct MockTimes {
499     import std.range: isInputRange, ElementType;
500 
501     MockXlFunction[] mocks;
502 
503     this(R)(R dates) if(isInputRange!R) {
504         import xlld.sdk.xlcall: xlfTime;
505         foreach(date; dates)
506             mocks ~= MockXlFunction(xlfTime, double(date));
507     }
508 }
509 
510 
511 struct FailingAllocator {
512     void[] allocate(size_t numBytes) @safe @nogc pure nothrow {
513         return null;
514     }
515 
516     bool deallocate(void[] bytes) @safe @nogc pure nothrow {
517         assert(false);
518     }
519 }