1 module ut.wrap.module_;
2 
3 import test;
4 import xlld.wrap;
5 import xlld.conv.to: toXlOper;
6 import std.datetime;
7 import std.experimental.allocator.mallocator: Mallocator;
8 alias theMallocator = Mallocator.instance;
9 
10 
11 mixin("import xlld.wrap.traits: Async;\n" ~
12       wrapModuleWorksheetFunctionsString!"test.d_funcs");
13 
14 
15 ///
16 @("Wrap double[][] -> double")
17 @system unittest {
18     import xlld.memorymanager: allocator;
19 
20     auto arg = toSRef(cast(double[][])[[1, 2, 3, 4], [11, 12, 13, 14]], allocator);
21     FuncAddEverything(&arg).shouldEqualDlang(60.0);
22 
23     arg = toSRef(cast(double[][])[[0, 1, 2, 3], [4, 5, 6, 7]], allocator);
24     FuncAddEverything(&arg).shouldEqualDlang(28.0);
25 }
26 
27 ///
28 @("Wrap double[][] -> double[][]")
29 @system unittest {
30     import xlld.memorymanager: allocator;
31 
32     auto arg = toSRef(cast(double[][])[[1, 2, 3, 4], [11, 12, 13, 14]], allocator);
33     FuncTripleEverything(&arg).shouldEqualDlang(cast(double[][])[[3, 6, 9, 12], [33, 36, 39, 42]]);
34 
35     arg = toSRef(cast(double[][])[[0, 1, 2, 3], [4, 5, 6, 7]], allocator);
36     FuncTripleEverything(&arg).shouldEqualDlang(cast(double[][])[[0, 3, 6, 9], [12, 15, 18, 21]]);
37 }
38 
39 
40 ///
41 @("Wrap string[][] -> double")
42 @system unittest {
43 
44     import xlld.memorymanager: allocator;
45 
46     auto arg = toSRef([["foo", "bar", "baz", "quux"], ["toto", "titi", "tutu", "tete"]], allocator);
47     FuncAllLengths(&arg).shouldEqualDlang(29.0);
48 
49     arg = toSRef([["", "", "", ""], ["", "", "", ""]], allocator);
50     FuncAllLengths(&arg).shouldEqualDlang(0.0);
51 }
52 
53 ///
54 @("Wrap string[][] -> double[][]")
55 @system unittest {
56 
57     import xlld.memorymanager: allocator;
58 
59     auto arg = toSRef([["foo", "bar", "baz", "quux"], ["toto", "titi", "tutu", "tete"]], allocator);
60     FuncLengths(&arg).shouldEqualDlang(cast(double[][])[[3, 3, 3, 4], [4, 4, 4, 4]]);
61 
62     arg = toSRef([["", "", ""], ["", "", "huh"]], allocator);
63     FuncLengths(&arg).shouldEqualDlang(cast(double[][])[[0, 0, 0], [0, 0, 3]]);
64 }
65 
66 ///
67 @("Wrap string[][] -> string[][]")
68 @system unittest {
69 
70     import xlld.memorymanager: allocator;
71 
72     auto arg = toSRef([["foo", "bar", "baz", "quux"], ["toto", "titi", "tutu", "tete"]], allocator);
73     FuncBob(&arg).shouldEqualDlang([["foobob", "barbob", "bazbob", "quuxbob"],
74                                     ["totobob", "titibob", "tutubob", "tetebob"]]);
75 }
76 
77 ///
78 @("Wrap string[] -> double")
79 @system unittest {
80     import xlld.memorymanager: allocator;
81 
82     auto arg = toSRef([["foo", "bar"], ["baz", "quux"]], allocator);
83     FuncStringSlice(&arg).shouldEqualDlang(4.0);
84 }
85 
86 ///
87 @("Wrap double[] -> double")
88 @system unittest {
89     import xlld.memorymanager: allocator;
90     auto arg = toSRef([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]], allocator);
91     FuncDoubleSlice(&arg).shouldEqualDlang(6.0);
92 }
93 
94 ///
95 @("Wrap double[] -> double[]")
96 @system unittest {
97     import xlld.memorymanager: allocator;
98     auto arg = toSRef([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]], allocator);
99     FuncSliceTimes3(&arg).shouldEqualDlang([3.0, 6.0, 9.0, 12.0, 15.0, 18.0]);
100 }
101 
102 ///
103 @("Wrap string[] -> string[]")
104 @system unittest {
105     import xlld.memorymanager: allocator;
106     auto arg = toSRef(["quux", "toto"], allocator);
107     StringsToStrings(&arg).shouldEqualDlang(["quuxfoo", "totofoo"]);
108 }
109 
110 ///
111 @("Wrap string[] -> string")
112 @system unittest {
113     import xlld.memorymanager: allocator;
114     auto arg = toSRef(["quux", "toto"], allocator);
115     StringsToString(&arg).shouldEqualDlang("quux, toto");
116 }
117 
118 ///
119 @("Wrap string -> string")
120 @system unittest {
121     import xlld.memorymanager: allocator;
122     auto arg = toXlOper("foo", allocator);
123     StringToString(&arg).shouldEqualDlang("foobar");
124 }
125 
126 ///
127 @("Wrap string, string, string -> string")
128 @system unittest {
129     import xlld.memorymanager: allocator;
130     auto arg0 = toXlOper("foo", allocator);
131     auto arg1 = toXlOper("bar", allocator);
132     auto arg2 = toXlOper("baz", allocator);
133     ManyToString(&arg0, &arg1, &arg2).shouldEqualDlang("foobarbaz");
134 }
135 
136 ///
137 @("nothrow functions")
138 @system unittest {
139     import xlld.memorymanager: allocator;
140     auto arg = toXlOper(2.0, allocator);
141     static assert(__traits(compiles, FuncThrows(&arg)));
142 }
143 
144 ///
145 @("FuncAddEverything wrapper is @nogc")
146 @system @nogc unittest {
147     import std.experimental.allocator.mallocator: Mallocator;
148     import xlld.sdk.framework: freeXLOper;
149 
150     auto arg = toXlOper(2.0, Mallocator.instance);
151     scope(exit) freeXLOper(&arg, Mallocator.instance);
152     FuncAddEverything(&arg);
153 }
154 
155 ///
156 @("Wrap a function that throws")
157 @system unittest {
158     auto arg = toSRef(33.3, theGC);
159     FuncThrows(&arg); // should not actually throw
160 }
161 
162 ///
163 @("Wrap a function that asserts")
164 @system unittest {
165     auto arg = toSRef(33.3, theGC);
166     FuncAsserts(&arg); // should not actually throw
167 }
168 
169 ///
170 @("Wrap a function that accepts DateTime")
171 @system unittest {
172     import xlld.sdk.xlcall: XlType;
173     import xlld.conv.misc: stripMemoryBitmask;
174     import std.conv: text;
175 
176     // the argument doesn't matter since we mock extracting the year from it
177     // but it does have to be of double type (DateTime for Excel)
178     auto arg = 33.3.toXlOper(theGC);
179 
180     const year = 2017;
181     const mock = MockDateTime(year, 1, 2, 3, 4, 5);
182     const ret = DateTimeToDouble(&arg);
183 
184     try
185         ret.xltype.stripMemoryBitmask.shouldEqual(XlType.xltypeNum);
186     catch(Exception _)
187         assert(false, text("Expected xltypeNum, got ", *ret));
188 
189     ret.val.num.shouldEqual(year * 2);
190 }
191 
192 ///
193 @("Wrap a function that accepts DateTime[]")
194 @system unittest {
195     //the arguments don't matter since we mock extracting year, etc. from them
196     //they just need to be double (DateTime to Excel)
197     auto arg = [0.1, 0.2].toXlOper(theGC);
198 
199     auto mockDateTimes = MockDateTimes(DateTime(1, 1, 31),
200                                        DateTime(1, 1, 30));
201 
202     auto ret = DateTimesToString(&arg);
203 
204     ret.shouldEqualDlang("31, 30");
205 }
206 
207 
208 @Serial
209 @("Wrap a function that takes an enum")
210 @safe unittest {
211     import test.d_funcs: MyEnum;
212 
213     auto arg = MyEnum.baz.toXlOper(theGC);
214     auto ret = () @trusted { return FuncMyEnumArg(&arg); }();
215     ret.shouldEqualDlang("prefix_baz");
216 }
217 
218 @Serial
219 @("Wrap a function that returns an enum")
220 @safe unittest {
221     import test.d_funcs: MyEnum;
222 
223     auto arg = 1.toXlOper(theGC);
224     auto ret = () @trusted { return FuncMyEnumRet(&arg); }();
225     ret.shouldEqualDlang("bar");
226 }
227 
228 
229 @Serial
230 @("Register a custom to enum conversion")
231 @safe unittest {
232     import std.conv: to;
233     import test.d_funcs: MyEnum;
234     import xlld.conv.from: registerConversionTo, unregisterConversionTo;
235 
236     registerConversionTo!MyEnum((str) => str[3 .. $].to!MyEnum);
237     scope(exit) unregisterConversionTo!MyEnum;
238 
239     auto arg = "___baz".toXlOper(theGC);
240     auto ret = () @trusted { return FuncMyEnumArg(&arg); }();
241 
242     ret.shouldEqualDlang("prefix_baz");
243 }
244 
245 @Serial
246 @("Register a custom from enum conversion")
247 @safe unittest {
248 
249     import std.conv: text;
250     import test.d_funcs: MyEnum;
251     import xlld.conv: registerConversionFrom, unregisterConversionFrom;
252 
253     registerConversionFrom!MyEnum((val) => "___" ~ text(cast(MyEnum)val));
254     scope(exit)unregisterConversionFrom!MyEnum;
255 
256     auto arg = 1.toXlOper(theGC);
257     auto ret = () @trusted { return FuncMyEnumRet(&arg); }();
258 
259     ret.shouldEqualDlang("___bar");
260 }
261 
262 @("Wrap a function that takes a struct using 1D array")
263 unittest {
264     auto arg = [2, 3].toXlOper(theGC);
265     auto ret = () @trusted { return FuncPointArg(&arg); }();
266 
267     ret.shouldEqualDlang(5);
268 }
269 
270 @("Wrap a function that returns a struct")
271 unittest {
272     auto arg1 = 2.toXlOper(theGC);
273     auto arg2 = 3.toXlOper(theGC);
274     auto ret = () @trusted { return FuncPointRet(&arg1, &arg2); }();
275 
276     ret.shouldEqualDlang("Point(2, 3)");
277 }
278 
279 @("Wrap a function that returns a simple tuple")
280 unittest {
281     import xlld.conv.from: fromXlOper;
282     import xlld.conv.misc: stripMemoryBitmask;
283     import std.conv: to;
284 
285     auto arg1 = 42.toXlOper(theGC);
286     auto arg2 = "foo".toXlOper(theGC);
287     auto ret = () @trusted { return FuncSimpleTupleRet(&arg1, &arg2); }();
288 
289     if(ret.xltype.stripMemoryBitmask != XlType.xltypeMulti) {
290         if(ret.xltype.stripMemoryBitmask == XlType.xltypeStr)
291             throw new Exception(ret.fromXlOper!string(theGC));
292         else
293             throw new Exception("Oper not of multi type: " ~ to!string(*ret));
294     }
295 
296     ret.val.array.rows.shouldEqual(1);
297     ret.val.array.columns.shouldEqual(2);
298     ret.val.array.lparray[0].fromXlOper!int(theGC).shouldEqual(42);
299     ret.val.array.lparray[1].fromXlOper!string(theGC).shouldEqual("foo");
300 }
301 
302 @("Wrap a function that returns a complex tuple")
303 unittest {
304     import xlld.conv.from: fromXlOper;
305     import xlld.conv.misc: stripMemoryBitmask;
306     import std.conv: to;
307 
308     auto dates = MockDates([1.0, 2.0, 3.0, 4.0]);
309     auto times = MockTimes([101.0, 102.0, 103.0, 104.0]);
310     auto dateTimes = MockDateTimes(DateTime(2017, 1, 2), DateTime(2017, 2, 2),
311                                    DateTime(2018, 1, 3), DateTime(2018, 2, 3));
312     auto arg1 = 2.toXlOper(theGC);
313     auto arg2 = 3.toXlOper(theGC);
314     auto ret = () @trusted { return FuncComplexTupleRet(&arg1, &arg2); }();
315 
316     if(ret.xltype.stripMemoryBitmask != XlType.xltypeMulti) {
317         if(ret.xltype.stripMemoryBitmask == XlType.xltypeStr)
318             throw new Exception(ret.fromXlOper!string(theGC));
319         else
320             throw new Exception("Oper not of multi type: " ~ to!string(*ret));
321     }
322 
323     ret.xltype.stripMemoryBitmask.shouldEqual(XlType.xltypeMulti);
324     ret.val.array.rows.shouldEqual(1);
325     ret.val.array.columns.shouldEqual(2);
326 
327     ret.val.array.lparray[0].fromXlOper!(DateTime[])(theGC).shouldEqual(
328         [DateTime(2017, 1, 2), DateTime(2017, 2, 2)]);
329 
330     ret.val.array.lparray[1].fromXlOper!(DateTime[])(theGC).shouldEqual(
331         [DateTime(2018, 1, 3), DateTime(2018, 2, 3)]);
332 }
333 
334 @("Wrap a function that returns an array of tuples")
335 unittest {
336     import xlld.conv.from: fromXlOper;
337     import xlld.conv.misc: stripMemoryBitmask;
338     import std.conv: to;
339 
340     auto dates = MockDates([0.1, 0.2, 0.3]);
341     auto times = MockTimes([10.1, 11.1, 12.1]);
342     auto dateTimes = MockDateTimes(DateTime(2017, 1, 1), DateTime(2018, 1, 1), DateTime(2019, 1, 1));
343 
344     auto oper = () @trusted { return FuncTupleArrayRet(); }();
345 
346     void assertMulti(in XLOPER12 oper) {
347         if(oper.xltype.stripMemoryBitmask != XlType.xltypeMulti) {
348             if(oper.xltype.stripMemoryBitmask == XlType.xltypeStr)
349                 throw new Exception(oper.fromXlOper!string(theGC));
350             else
351                 throw new Exception("Oper not of multi type: " ~ to!string(oper));
352         }
353     }
354 
355     assertMulti(*oper);
356     oper.val.array.rows.shouldEqual(1);
357     oper.val.array.columns.shouldEqual(3);
358 
359     auto elts = oper.val.array.lparray[0 .. 3];
360     foreach(elt; elts) assertMulti(elt);
361 
362     elts[0].val.array.lparray[0].fromXlOper!DateTime(theGC).shouldEqual(DateTime(2017, 1, 1));
363     elts[0].val.array.lparray[1].fromXlOper!double(theGC).shouldEqual(11.1);
364 
365     elts[1].val.array.lparray[0].fromXlOper!DateTime(theGC).shouldEqual(DateTime(2018, 1, 1));
366     elts[1].val.array.lparray[1].fromXlOper!double(theGC).shouldEqual(22.2);
367 
368     elts[2].val.array.lparray[0].fromXlOper!DateTime(theGC).shouldEqual(DateTime(2019, 1, 1));
369     elts[2].val.array.lparray[1].fromXlOper!double(theGC).shouldEqual(33.3);
370 }
371 
372 @("Wrap a function that takes an enum array")
373 unittest {
374     import test.d_funcs: MyEnum;
375     auto arg = [MyEnum.foo].toXlOper(theGC);
376     FuncEnumArray(&arg);
377 }
378 
379 ///
380 @("wrapModuleFunctionStr")
381 @system unittest {
382     import xlld.wrap.worksheet;
383     import std.traits: getUDAs;
384 
385     mixin(wrapModuleFunctionStr!("test.d_funcs", "FuncAddEverything"));
386     alias registerAttrs = getUDAs!(FuncAddEverything, Register);
387     static assert(registerAttrs[0].argumentText.value == "Array to add");
388 }
389 
390 
391 ///
392 @("No memory allocation bugs in wrapModuleFunctionImpl for double return Mallocator")
393 @system unittest {
394     import test.d_funcs: FuncAddEverything;
395     import xlld.sdk.xlcall: xlbitDLLFree;
396     import xlld.memorymanager: autoFree;
397 
398     TestAllocator allocator;
399     auto arg = toSRef([1.0, 2.0], theMallocator);
400     auto oper = wrapModuleFunctionImpl!FuncAddEverything(allocator, &arg);
401     (oper.xltype & xlbitDLLFree).shouldBeTrue;
402     allocator.numAllocations.shouldEqual(2);
403     oper.shouldEqualDlang(3.0);
404     autoFree(oper); // normally this is done by Excel
405 }
406 
407 ///
408 @("No memory allocation bugs in wrapModuleFunctionImpl for double[][] return Mallocator")
409 @system unittest {
410     import test.d_funcs: FuncTripleEverything;
411     import xlld.sdk.xlcall: xlbitDLLFree, XlType;
412     import xlld.memorymanager: autoFree;
413 
414     TestAllocator allocator;
415     auto arg = toSRef([1.0, 2.0, 3.0], theMallocator);
416     auto oper = wrapModuleFunctionImpl!FuncTripleEverything(allocator, &arg);
417     (oper.xltype & xlbitDLLFree).shouldBeTrue;
418     (oper.xltype & ~xlbitDLLFree).shouldEqual(XlType.xltypeMulti);
419     allocator.numAllocations.shouldEqual(2);
420     oper.shouldEqualDlang([[3.0, 6.0, 9.0]]);
421     autoFree(oper); // normally this is done by Excel
422 }
423 
424 ///
425 @("No memory allocation bugs in wrapModuleFunctionImpl for double[][] return pool")
426 @system unittest {
427     import std.typecons: Ternary;
428     import xlld.memorymanager: gTempAllocator, autoFree;
429     import test.d_funcs: FuncTripleEverything;
430 
431     auto arg = toSRef([1.0, 2.0, 3.0], gTempAllocator);
432     auto oper = wrapModuleFunctionImpl!FuncTripleEverything(gTempAllocator, &arg);
433     gTempAllocator.empty.shouldEqual(Ternary.yes);
434     oper.shouldEqualDlang([[3.0, 6.0, 9.0]]);
435     autoFree(oper); // normally this is done by Excel
436 }
437 
438 ///
439 @("No memory allocation bugs in wrapModuleFunctionImpl for string")
440 @system unittest {
441     import std.typecons: Ternary;
442     import xlld.memorymanager: gTempAllocator;
443     import test.d_funcs: StringToString;
444 
445     auto arg = "foo".toSRef(gTempAllocator);
446     auto oper = wrapModuleFunctionImpl!StringToString(gTempAllocator, &arg);
447     gTempAllocator.empty.shouldEqual(Ternary.yes);
448     oper.shouldEqualDlang("foobar");
449 }
450 
451 ///
452 @("No memory allocation bugs in wrapModuleFunctionImpl for Any[][] -> Any[][] -> Any[][] mallocator")
453 @system unittest {
454     import xlld.memorymanager: allocatorContext;
455     import test.d_funcs: FirstOfTwoAnyArrays;
456 
457     with(allocatorContext(theGC)) {
458         auto dArg = [[any(1.0), any("foo"), any(3.0)], [any(4.0), any(5.0), any(6.0)]];
459         auto arg = toXlOper(dArg);
460         auto oper = wrapModuleFunctionImpl!FirstOfTwoAnyArrays(theMallocator, &arg, &arg);
461         oper.shouldEqualDlang(dArg);
462     }
463 }
464 
465 ///
466 @("No memory allocation bugs in wrapModuleFunctionImpl for Any[][] -> Any[][] -> Any[][] TestAllocator")
467 @system unittest {
468     import xlld.memorymanager: allocatorContext;
469     import test.d_funcs: FirstOfTwoAnyArrays;
470 
471     auto testAllocator = TestAllocator();
472 
473     with(allocatorContext(theGC)) {
474         auto dArg = [
475             [ any(1.0), any("foo"), any(3.0) ],
476             [ any(4.0), any(5.0),   any(6.0) ],
477         ];
478         auto arg = toXlOper(dArg);
479         auto oper = wrapModuleFunctionImpl!FirstOfTwoAnyArrays(testAllocator, &arg, &arg);
480         oper.shouldEqualDlang(dArg);
481     }
482 }
483 
484 ///
485 @("Correct number of coercions and frees in wrapModuleFunctionImpl")
486 @system unittest {
487     import test.d_funcs: FuncAddEverything;
488     import xlld.test.util: gNumXlAllocated, gNumXlFree;
489 
490     const oldNumAllocated = gNumXlAllocated;
491     const oldNumFree = gNumXlFree;
492 
493     auto arg = toSRef([1.0, 2.0], theGC);
494     auto oper = wrapModuleFunctionImpl!FuncAddEverything(theGC, &arg);
495 
496     (gNumXlAllocated - oldNumAllocated).shouldEqual(1);
497     (gNumXlFree   - oldNumFree).shouldEqual(1);
498 }
499 
500 
501 ///
502 @("Can't return empty 1D array to Excel")
503 @system unittest {
504     import xlld.memorymanager: allocatorContext;
505     import test.d_funcs: EmptyStrings1D;
506 
507     with(allocatorContext(theGC)) {
508         auto dArg = any(1.0);
509         auto arg = toXlOper(dArg);
510         auto oper = wrapModuleFunctionImpl!EmptyStrings1D(theGC, &arg);
511         oper.shouldEqualDlang("#ERROR: empty result");
512     }
513 }
514 
515 
516 ///
517 @("Can't return empty 2D array to Excel")
518 @system unittest {
519     import xlld.memorymanager: allocatorContext;
520     import test.d_funcs: EmptyStrings2D;
521 
522     with(allocatorContext(theGC)) {
523         auto dArg = any(1.0);
524         auto arg = toXlOper(dArg);
525         auto oper = wrapModuleFunctionImpl!EmptyStrings2D(theGC, &arg);
526         oper.shouldEqualDlang("#ERROR: empty result");
527     }
528 }
529 
530 ///
531 @("Can't return half empty 2D array to Excel")
532 @system unittest {
533     import xlld.memorymanager: allocatorContext;
534     import test.d_funcs: EmptyStringsHalfEmpty2D;
535 
536     with(allocatorContext(theGC)) {
537         auto dArg = any(1.0);
538         auto arg = toXlOper(dArg);
539         auto oper = wrapModuleFunctionImpl!EmptyStringsHalfEmpty2D(theGC, &arg);
540         oper.shouldEqualDlang("#ERROR: empty result");
541     }
542 }
543 
544 ///
545 @("issue 25 - make sure to reserve memory for all dArgs")
546 @system unittest {
547     import std.typecons: Ternary;
548     import xlld.memorymanager: allocatorContext, MemoryPool;
549     import test.d_funcs: FirstOfTwoAnyArrays;
550 
551     auto pool = MemoryPool();
552 
553     with(allocatorContext(theGC)) {
554         auto dArg = [[any(1.0), any("foo"), any(3.0)], [any(4.0), any(5.0), any(6.0)]];
555         auto arg = toSRef(dArg);
556         auto oper = wrapModuleFunctionImpl!FirstOfTwoAnyArrays(pool, &arg, &arg);
557     }
558 
559     pool.empty.shouldEqual(Ternary.yes); // deallocateAll in wrapImpl
560 }
561 
562 
563 ///
564 @("wrapModuleFunctionStr function that returns Any[][]")
565 @safe unittest {
566     mixin(wrapModuleFunctionStr!("test.d_funcs", "DoubleArrayToAnyArray"));
567 
568     auto oper = [[1.0, 2.0], [3.0, 4.0]].toSRef(theGC);
569     auto arg = () @trusted { return &oper; }();
570     auto ret = DoubleArrayToAnyArray(arg);
571 
572     auto opers = () @trusted { return ret.val.array.lparray[0 .. 4]; }();
573     opers[0].shouldEqualDlang(2.0);
574     opers[1].shouldEqualDlang(6.0);
575     opers[2].shouldEqualDlang("3quux");
576     opers[3].shouldEqualDlang("4toto");
577 }
578 
579 ///
580 @("wrapModuleFunctionStr int -> int")
581 @safe unittest {
582     mixin(wrapModuleFunctionStr!("test.d_funcs", "Twice"));
583 
584     auto oper = 3.toSRef(theGC);
585     auto arg = () @trusted { return &oper; }();
586     Twice(arg).shouldEqualDlang(6);
587 }
588 
589 ///
590 @("issue 31 - D functions can have const arguments")
591 @safe unittest {
592     mixin(wrapModuleFunctionStr!("test.d_funcs", "FuncConstDouble"));
593 
594     auto oper = (3.0).toSRef(theGC);
595     auto arg = () @trusted { return &oper; }();
596     FuncConstDouble(arg).shouldEqualDlang(3.0);
597 }
598 
599 
600 @Flaky
601 @("wrapModuleFunctionStr async double -> double")
602 unittest {
603     import xlld.conv.from: fromXlOper;
604     import xlld.wrap.traits: Async;
605     import xlld.test.util: asyncReturn, newAsyncHandle;
606     import core.time: MonoTime;
607     import core.thread;
608 
609     mixin(wrapModuleFunctionStr!("test.d_funcs", "AsyncDoubleToDouble"));
610 
611     auto oper = (3.0).toXlOper(theGC);
612     auto arg = () @trusted { return &oper; }();
613     auto asyncHandle = newAsyncHandle;
614     () @trusted { AsyncDoubleToDouble(arg, &asyncHandle); }();
615 
616     const start = MonoTime.currTime;
617     const expected = 6.0;
618     while(asyncReturn(asyncHandle).fromXlOper!double(theGC) != expected &&
619           MonoTime.currTime - start < 1.seconds)
620     {
621         Thread.sleep(10.msecs);
622     }
623     asyncReturn(asyncHandle).shouldEqualDlang(expected);
624 }
625 
626 @("wrapModuleFunctionStr () -> NaN")
627 unittest {
628     mixin(wrapModuleFunctionStr!("test.d_funcs", "NaN"));
629     NaN().shouldEqualDlang("#NaN");
630 }