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 }