Showing 2 of 4 files from the diff.

@@ -0,0 +1,722 @@
Loading
1 +
module serialport.ut;
2 +
3 +
version (unittest): private:
4 +
5 +
import serialport;
6 +
7 +
import std.range;
8 +
import std.concurrency;
9 +
import std.exception;
10 +
import std.datetime;
11 +
import std.conv;
12 +
import std.string;
13 +
import std.stdio;
14 +
import std.random;
15 +
import std.process;
16 +
import core.thread;
17 +
18 +
enum BUFFER_SIZE = 1024;
19 +
20 +
interface ComPipe
21 +
{
22 +
    void open();
23 +
    void close();
24 +
    string command() const @property;
25 +
    string[2] ports() const @property;
26 +
}
27 +
28 +
class SocatPipe : ComPipe
29 +
{
30 +
    int bufferSize;
31 +
    ProcessPipes pipe;
32 +
    string[2] _ports;
33 +
    string _command;
34 +
35 +
    this(int bs)
36 +
    {
37 +
        bufferSize = bs;
38 +
        _command = ("socat -d -d -b%d pty,raw,"~
39 +
                    "echo=0 pty,raw,echo=0").format(bufferSize);
40 +
    }
41 +
42 +
    static string parsePort(string ln)
43 +
    {
44 +
        auto ret = ln.split[$-1];
45 +
        enforce(ret.startsWith("/dev/"),
46 +
        "unexpected last word in output line '%s'".format(ln));
47 +
        return ret;
48 +
    }
49 +
50 +
    override void close()
51 +
    {
52 +
        if (pipe.pid is null) return;
53 +
        kill(pipe.pid);
54 +
    }
55 +
56 +
    override void open()
57 +
    {
58 +
        pipe = pipeShell(_command);
59 +
        _ports[0] = parsePort(pipe.stderr.readln.strip);
60 +
        _ports[1] = parsePort(pipe.stderr.readln.strip);
61 +
    }
62 +
    
63 +
    override const @property
64 +
    {
65 +
        string command() { return _command; }
66 +
        string[2] ports() { return _ports; }
67 +
    }
68 +
}
69 +
70 +
unittest
71 +
{
72 +
    enum socat_out_ln = "2018/03/08 02:56:58 socat[30331] N PTY is /dev/pts/1";
73 +
    assert(SocatPipe.parsePort(socat_out_ln) == "/dev/pts/1");
74 +
    assertThrown(SocatPipe.parsePort("some string"));
75 +
}
76 +
77 +
class DefinedPorts : ComPipe
78 +
{
79 +
    string[2] env;
80 +
    string[2] _ports;
81 +
82 +
    this(string[2] envNames = ["SERIALPORT_TEST_PORT1", "SERIALPORT_TEST_PORT2"])
83 +
    { env = envNames; }
84 +
85 +
override:
86 +
87 +
    void open()
88 +
    {
89 +
        import std.process : environment;
90 +
        import std.range : lockstep;
91 +
        import std.algorithm : canFind;
92 +
93 +
        auto lst = SerialPort.listAvailable;
94 +
95 +
        foreach (ref e, ref p; lockstep(env[], _ports[]))
96 +
        {
97 +
            p = environment[e];
98 +
            enforce(lst.canFind(p), new Exception("unknown port '%s' in env var '%s'".format(p, e)));
99 +
        }
100 +
    }
101 +
102 +
    void close() { }
103 +
104 +
    string command() const @property
105 +
    {
106 +
        return "env: %s=%s, %s=%s".format(
107 +
            env[0], _ports[0],
108 +
            env[1], _ports[1]
109 +
        );
110 +
    }
111 +
112 +
    string[2] ports() const @property { return _ports; }
113 +
}
114 +
115 +
ComPipe getPlatformComPipe(int bufsz)
116 +
{
117 +
    stderr.writeln("available ports count: ", SerialPort.listAvailable.length);
118 +
119 +
    try
120 +
    {
121 +
        auto ret = new DefinedPorts;
122 +
        ret.open();
123 +
        return ret;
124 +
    }
125 +
    catch (Exception e)
126 +
    {
127 +
        stderr.writeln();
128 +
        stderr.writeln("error while open predefined ports: ", e.msg);
129 +
130 +
        version (Posix) return new SocatPipe(bufsz);
131 +
        else return null;
132 +
    }
133 +
}
134 +
135 +
// real test main
136 +
//version (realtest)
137 +
unittest
138 +
{
139 +
    stderr.writeln("=== start real test ===\n");
140 +
    scope (success) stderr.writeln("=== finish real test ===");
141 +
    scope (failure) stderr.writeln("!!!  fail real test  !!!");
142 +
    auto cp = getPlatformComPipe(BUFFER_SIZE);
143 +
    if (cp is null)
144 +
    {
145 +
        stderr.writeln("platform doesn't support real test");
146 +
        return;
147 +
    }
148 +
149 +
    stderr.writefln("port source `%s`\n", cp.command);
150 +
151 +
    void reopen()
152 +
    {
153 +
        cp.close();
154 +
        Thread.sleep(30.msecs);
155 +
        cp.open();
156 +
        stderr.writefln("pipe ports: %s <=> %s", cp.ports[0], cp.ports[1]);
157 +
    }
158 +
159 +
    reopen();
160 +
161 +
    utCall!(threadTest!SerialPortFR)("thread test for fiber ready", cp.ports);
162 +
    utCall!(threadTest!SerialPortBlk)("thread test for block", cp.ports);
163 +
    utCall!testNonBlock("test non block", cp.ports);
164 +
    utCall!fiberTest("fiber test", cp.ports);
165 +
    utCall!fiberTest2("fiber test 2", cp.ports);
166 +
    utCall!readTimeoutTest("read timeout test", cp.ports);
167 +
    alias rttc = readTimeoutTestConfig;
168 +
    alias rttc2 = readTimeoutTestConfig2;
169 +
    utCall!(rttc!SerialPortFR)( "read timeout test for FR  cr=zero", cp.ports, SerialPort.CanRead.zero);
170 +
    utCall!(rttc!SerialPortBlk)("read timeout test for Blk cr=zero", cp.ports, SerialPort.CanRead.zero);
171 +
    utCall!(rttc!SerialPortFR)( "read timeout test for FR  cr=anyNonZero", cp.ports, SerialPort.CanRead.anyNonZero);
172 +
    utCall!(rttc!SerialPortBlk)("read timeout test for Blk cr=anyNonZero", cp.ports, SerialPort.CanRead.anyNonZero);
173 +
    utCall!(rttc!SerialPortFR)( "read timeout test for FR  cr=allOrNothing", cp.ports, SerialPort.CanRead.allOrNothing);
174 +
    utCall!(rttc!SerialPortBlk)("read timeout test for Blk cr=allOrNothing", cp.ports, SerialPort.CanRead.allOrNothing);
175 +
    utCall!(rttc2!SerialPortFR)( "read timeout test 2 for FR  cr=zero", cp.ports, SerialPort.CanRead.zero);
176 +
    utCall!(rttc2!SerialPortBlk)("read timeout test 2 for Blk cr=zero", cp.ports, SerialPort.CanRead.zero);
177 +
    utCall!(rttc2!SerialPortFR)( "read timeout test 2 for FR  cr=anyNonZero", cp.ports, SerialPort.CanRead.anyNonZero);
178 +
    utCall!(rttc2!SerialPortBlk)("read timeout test 2 for Blk cr=anyNonZero", cp.ports, SerialPort.CanRead.anyNonZero);
179 +
    utCall!(rttc2!SerialPortFR)( "read timeout test 2 for FR  cr=allOrNothing", cp.ports, SerialPort.CanRead.allOrNothing);
180 +
    utCall!(rttc2!SerialPortBlk)("read timeout test 2 for Blk cr=allOrNothing", cp.ports, SerialPort.CanRead.allOrNothing);
181 +
    utCall!(fiberSleepFuncTest)("fiber sleep func test", cp.ports);
182 +
}
183 +
184 +
unittest
185 +
{
186 +
    enum name = "/some/path/to/notexisting/device";
187 +
    auto e = enforce(collectException(new SerialPortBlk(name, 19200)), "exception not thrown");
188 +
    auto sce = cast(SysCallException)e;
189 +
    assert (sce !is null);
190 +
    assert (sce.port == name, "wrong name");
191 +
    version (Posix)
192 +
    {
193 +
        assert(sce.fnc == "open", "'" ~ sce.fnc ~ "' is not 'open'");
194 +
        assert(sce.err == 2, "unexpectable errno %d".format(sce.err));
195 +
    }
196 +
    auto exp = format!"call '%s' (%s) failed: error %d"(sce.fnc, name, sce.err);
197 +
    if (!e.msg.startsWith(exp))
198 +
    {
199 +
        import std.stdio : stderr;
200 +
        stderr.writeln("exp: ", exp);
201 +
        stderr.writeln("msg: ", e.msg);
202 +
        assert(0, "wrong msg");
203 +
    }
204 +
}
205 +
206 +
void testPrint(Args...)(Args args) { stderr.write("    "); stderr.writeln(args); }
207 +
void testPrintf(Args...)(Args args) { stderr.write("    "); stderr.writefln(args); }
208 +
209 +
auto utCall(alias fnc, Args...)(string name, Args args)
210 +
{
211 +
    stderr.writefln(">>> run %s", name);
212 +
    scope (success) stderr.writefln("<<< success %s\n", name);
213 +
    scope (failure) stderr.writefln("!!! failure %s\n", name);
214 +
    return fnc(args);
215 +
}
216 +
217 +
void threadTest(SPT)(string[2] ports)
218 +
{
219 +
    assert(SerialPort.listAvailable.length != 0);
220 +
221 +
    static struct ExcStruct { string msg, type; }
222 +
223 +
    static void echoThread(string port)
224 +
    {
225 +
        void[BUFFER_SIZE] buffer = void;
226 +
        auto com = new SPT(port, "2400:8N1");
227 +
        scope (exit) com.close();
228 +
        com.flush();
229 +
230 +
        com.set(1200);
231 +
        assert(com.config.baudRate == 1200);
232 +
233 +
        com.baudRate = 38_400;
234 +
        assert(com.config.baudRate == 38_400);
235 +
236 +
        bool work = true;
237 +
        com.readTimeout = 1000.msecs;
238 +
239 +
        bool needRead;
240 +
241 +
        while (work)
242 +
        {
243 +
            try
244 +
            {
245 +
                if (needRead)
246 +
                {
247 +
                    Thread.sleep(500.msecs);
248 +
                    auto data = com.read(buffer, com.CanRead.zero);
249 +
250 +
                    if (data.length)
251 +
                    {
252 +
                        testPrint("child readed: ", cast(string)(data.idup));
253 +
                        send(ownerTid, cast(string)(data.idup));
254 +
                    }
255 +
                }
256 +
257 +
                receiveTimeout(500.msecs,
258 +
                    (SPConfig cfg)
259 +
                    {
260 +
                        com.config = cfg;
261 +
                        testPrint("child get cfg: ", cfg.mode);
262 +
                    },
263 +
                    (bool nr)
264 +
                    {
265 +
                        if (nr) needRead = true;
266 +
                        else
267 +
                        {
268 +
                            work = false;
269 +
                            needRead = false;
270 +
                        }
271 +
                        testPrint("get needRead ", nr);
272 +
                    },
273 +
                    (OwnerTerminated e) { work = false; }
274 +
                );
275 +
            }
276 +
            catch (Throwable e)
277 +
            {
278 +
                work = false;
279 +
                testPrint("exception in child: ", e);
280 +
                send(ownerTid, ExcStruct(e.msg, e.classinfo.stringof));
281 +
            }
282 +
        }
283 +
    }
284 +
285 +
    auto t = spawnLinked(&echoThread, ports[1]);
286 +
287 +
    auto com = new SPT(ports[0], 19_200);
288 +
    com.flush();
289 +
290 +
    assert(com.baudRate == 19_200);
291 +
    assert(com.dataBits == DataBits.data8);
292 +
    assert(com.parity == Parity.none);
293 +
    assert(com.stopBits == StopBits.one);
294 +
295 +
    assert(com.config.baudRate == 19_200);
296 +
    assert(com.config.dataBits == DataBits.data8);
297 +
    assert(com.config.parity == Parity.none);
298 +
    assert(com.config.stopBits == StopBits.one);
299 +
300 +
    scope (exit) com.close();
301 +
302 +
    string[] list;
303 +
304 +
    const sets = [
305 +
        SPConfig(38_400),
306 +
        SPConfig(2400),
307 +
        SPConfig.parse("19200:8N2"),
308 +
    ];
309 +
310 +
    auto cfg = SPConfig(38_400);
311 +
    com.config = cfg;
312 +
    send(t, cfg);
313 +
314 +
    Thread.sleep(1000.msecs);
315 +
316 +
    string msg = sets.front.mode;
317 +
    com.write(msg);
318 +
319 +
    bool work = true;
320 +
    send(t, true);
321 +
    while (work)
322 +
    {
323 +
        receive(
324 +
            (string rec)
325 +
            {
326 +
                enforce(rec == msg, "break message: '%s' != '%s'".format(msg, rec));
327 +
328 +
                if (list.empty)
329 +
                {
330 +
                    testPrint("owner send data finish");
331 +
                    send(t, false);
332 +
                }
333 +
                else
334 +
                {
335 +
                    msg = list.front;
336 +
                    list.popFront();
337 +
                }
338 +
339 +
                com.write(msg);
340 +
                testPrint("owner write msg to com: ", msg);
341 +
            },
342 +
            (ExcStruct e) { throw new Exception("%s:%s".format(e.type, e.msg)); },
343 +
            (LinkTerminated e)
344 +
            {
345 +
                work = false;
346 +
                testPrintf("link terminated for %s, child tid %s", e.tid, t);
347 +
                //assert(e.tid == t);
348 +
            }
349 +
        );
350 +
    }
351 +
}
352 +
353 +
void testNonBlock(string[2] ports)
354 +
{
355 +
    import std.datetime.stopwatch : StopWatch, AutoStart;
356 +
    enum mode = "38400:8N1";
357 +
358 +
    const data = "1234567890987654321qazxswedcvfrtgbnhyujm,ki";
359 +
360 +
    static void thfunc(string port)
361 +
    {
362 +
        auto com = new SerialPortNonBlk(port, mode);
363 +
        scope (exit) com.close();
364 +
365 +
        void[1024] buffer = void;
366 +
        size_t readed;
367 +
368 +
        const sw = StopWatch(AutoStart.yes);
369 +
370 +
        // flush
371 +
        while (sw.peek < 10.msecs)
372 +
        {
373 +
            com.read(buffer);
374 +
            Thread.sleep(1.msecs);
375 +
        }
376 +
377 +
        while (sw.peek < 1.seconds)
378 +
            readed += com.read(buffer[readed..$]).length;
379 +
380 +
        send(ownerTid, buffer[0..readed].idup);
381 +
382 +
        Thread.sleep(200.msecs);
383 +
    }
384 +
385 +
    auto com = new SerialPortNonBlk(ports[0], 38_400, "8N1");
386 +
    scope (exit) com.close();
387 +
388 +
    spawnLinked(&thfunc, ports[1]);
389 +
390 +
    Thread.sleep(100.msecs);
391 +
392 +
    size_t written;
393 +
    while (written < data.length)
394 +
        written += com.write(data[written..$]);
395 +
396 +
    receive((immutable(void)[] readed)
397 +
    {
398 +
        testPrint("readed: ", cast(string)readed);
399 +
        testPrint("  data: ", data);
400 +
        assert(cast(string)readed == data);
401 +
    });
402 +
403 +
    receive((LinkTerminated e) { });
404 +
}
405 +
406 +
class CF : Fiber
407 +
{
408 +
    void[] data;
409 +
410 +
    SerialPortFR com;
411 +
412 +
    this(SerialPortFR com, size_t bufsize)
413 +
    {
414 +
        this.com = com;
415 +
        this.com.flush();
416 +
        this.data = new void[bufsize];
417 +
        foreach (ref v; cast(ubyte[])data)
418 +
            v = cast(ubyte)uniform(0, 128);
419 +
        super(&run);
420 +
    }
421 +
422 +
    abstract void run();
423 +
}
424 +
425 +
class CFSlave : CF
426 +
{
427 +
    void[] result;
428 +
429 +
    Duration readTimeout = 40.msecs;
430 +
    Duration readGapTimeout = 100.msecs;
431 +
432 +
    this(SerialPortFR com, size_t bufsize)
433 +
    { super(com, bufsize); }
434 +
435 +
    override void run()
436 +
    {
437 +
        testPrint("start read loop");
438 +
        result = com.readContinues(data, readTimeout, readGapTimeout);
439 +
        testPrint("finish read loop ("~result.length.to!string~")");
440 +
    }
441 +
}
442 +
443 +
class CFMaster : CF
444 +
{
445 +
    CFSlave slave;
446 +
447 +
    Duration writeTimeout = 20.msecs;
448 +
449 +
    this(SerialPortFR com, size_t bufsize)
450 +
    { super(com, bufsize); }
451 +
452 +
    override void run()
453 +
    {
454 +
        testPrint("start write loop ("~data.length.to!string~")");
455 +
        com.writeTimeout = writeTimeout;
456 +
        com.write(data);
457 +
        testPrint("finish write loop");
458 +
    }
459 +
}
460 +
461 +
void fiberTest(string[2] ports)
462 +
{
463 +
    auto slave = new CFSlave(new SerialPortFR(ports[0]), BUFFER_SIZE);
464 +
    scope (exit) slave.com.close();
465 +
    auto master = new CFMaster(new SerialPortFR(ports[1]), BUFFER_SIZE);
466 +
    scope (exit) master.com.close();
467 +
468 +
    bool work = true;
469 +
    int step;
470 +
    while (work)
471 +
    {
472 +
        alias TERM = Fiber.State.TERM;
473 +
        if (master.state != TERM) master.call;
474 +
        if (slave.state != TERM) slave.call;
475 +
476 +
        step++;
477 +
        Thread.sleep(30.msecs);
478 +
        if (master.state == TERM && slave.state == TERM)
479 +
        {
480 +
            if (slave.result.length == master.data.length)
481 +
            {
482 +
                import std.algorithm : equal;
483 +
                enforce(equal(cast(ubyte[])slave.result, cast(ubyte[])master.data));
484 +
                work = false;
485 +
                testPrint("basic loop steps: ", step);
486 +
            }
487 +
            else throw new Exception(text(slave.result, " != ", master.data));
488 +
        }
489 +
    }
490 +
}
491 +
492 +
void fiberTest2(string[2] ports)
493 +
{
494 +
    string mode = "9600:8N1";
495 +
496 +
    auto scom = new SerialPortFR(ports[0], 9600, "8N1");
497 +
    auto mcom = new SerialPortFR(ports[1], "19200:8N1");
498 +
    scope (exit) scom.close();
499 +
    scope (exit) mcom.close();
500 +
501 +
    version (Posix)
502 +
        assertThrown!UnsupportedException(scom.baudRate = 9200);
503 +
504 +
    scom.reopen(ports[0], SPConfig.parse(mode));
505 +
    mcom.reopen(ports[1], SPConfig.parse(mode));
506 +
    scom.flush();
507 +
    mcom.flush();
508 +
509 +
    scom.config = mcom.config;
510 +
511 +
    scom.readTimeout = 1000.msecs;
512 +
    mcom.writeTimeout = 100.msecs;
513 +
514 +
    version (OSX) enum BS = BUFFER_SIZE / 2;
515 +
    else          enum BS = BUFFER_SIZE * 4;
516 +
517 +
    auto slave  = new CFSlave(scom,  BS);
518 +
    auto master = new CFMaster(mcom, BS);
519 +
520 +
    void run()
521 +
    {
522 +
        bool work = true;
523 +
        int step;
524 +
        alias TERM = Fiber.State.TERM;
525 +
        while (work)
526 +
        {
527 +
            if (master.state != TERM) master.call;
528 +
            Thread.sleep(5.msecs);
529 +
            if (slave.state != TERM) slave.call;
530 +
531 +
            step++;
532 +
            if (master.state == TERM && slave.state == TERM)
533 +
            {
534 +
                assert(slave.result.length == master.data.length);
535 +
                import std.algorithm : equal;
536 +
                enforce(equal(cast(ubyte[])slave.result, cast(ubyte[])master.data));
537 +
                work = false;
538 +
                testPrint("basic loop steps: ", step);
539 +
            }
540 +
        }
541 +
    }
542 +
543 +
    run();
544 +
}
545 +
546 +
void readTimeoutTest(string[2] ports)
547 +
{
548 +
    void[1024] buffer = void;
549 +
550 +
    auto comA = new SerialPortFR(ports[0], 19_200);
551 +
    scope (exit) comA.close();
552 +
    comA.flush();
553 +
    assertThrown!TimeoutException(comA.readContinues(buffer[], 1.msecs, 1.msecs));
554 +
    assertNotThrown!TimeoutException(comA.readContinues(buffer[], 1.msecs, 1.msecs, false));
555 +
    assertThrown!TimeoutException(comA.read(buffer[]));
556 +
    assertThrown!TimeoutException(comA.read(buffer[], comA.CanRead.anyNonZero));
557 +
558 +
    auto comB = new SerialPortBlk(ports[1], 19_200, "8N1");
559 +
    scope (exit) comB.close();
560 +
    comB.flush();
561 +
    comB.readTimeout = 1.msecs;
562 +
    assertThrown!TimeoutException(comB.read(buffer[]));
563 +
    assertThrown!TimeoutException(comB.read(buffer[], comB.CanRead.anyNonZero));
564 +
}
565 +
566 +
void readTimeoutTestConfig(SP : SerialPort)(string[2] ports, SerialPort.CanRead cr)
567 +
{
568 +
    enum mode = "38400:8N1";
569 +
570 +
    enum FULL = 100;
571 +
    enum SEND = "helloworld";
572 +
573 +
    static void thfunc(string port)
574 +
    {
575 +
        auto com = new SP(port, mode);
576 +
        com.flush();
577 +
        scope (exit) com.close();
578 +
        com.write(SEND);
579 +
    }
580 +
581 +
    auto com = new SP(ports[0], mode);
582 +
    scope (exit) com.close();
583 +
    auto rt = 300.msecs;
584 +
    com.readTimeout = rt;
585 +
    com.flush();
586 +
    assert(com.readTimeout == rt);
587 +
588 +
    void[FULL] buffer = void;
589 +
    void[] data;
590 +
591 +
    spawnLinked(&thfunc, ports[1]);
592 +
593 +
    Thread.sleep(rt);
594 +
595 +
    if (cr == SerialPort.CanRead.anyNonZero)
596 +
    {
597 +
        assertNotThrown(data = com.read(buffer, cr));
598 +
        assert(cast(string)data == SEND);
599 +
        assertThrown!TimeoutException(data = com.read(buffer, cr));
600 +
    }
601 +
    else if (cr == SerialPort.CanRead.allOrNothing)
602 +
        assertThrown!TimeoutException(data = com.read(buffer));
603 +
    else if (cr == SerialPort.CanRead.zero)
604 +
    {
605 +
        assertNotThrown(data = com.read(buffer, cr));
606 +
        assertNotThrown(data = com.read(buffer, cr));
607 +
        assertNotThrown(data = com.read(buffer, cr));
608 +
    }
609 +
    else assert(0, "not tested variant of CanRead");
610 +
611 +
    receive((LinkTerminated e) { });
612 +
}
613 +
614 +
void readTimeoutTestConfig2(SP : SerialPort)(string[2] ports, SerialPort.CanRead cr)
615 +
{
616 +
    enum mode = "38400:8N1";
617 +
618 +
    static void thfunc(string port)
619 +
    {
620 +
        auto com = new SP(port, mode);
621 +
        scope (exit) com.close();
622 +
        com.flush();
623 +
        Thread.sleep(200.msecs);
624 +
        com.write("one");
625 +
        Thread.sleep(200.msecs);
626 +
        com.write("two");
627 +
    }
628 +
629 +
    auto com = new SP(ports[0], mode);
630 +
    scope (exit) com.close();
631 +
    com.readTimeout = cr == SerialPort.CanRead.zero ? 10.msecs : 300.msecs;
632 +
    com.flush();
633 +
634 +
    void[6] buffer = void;
635 +
    void[] data;
636 +
637 +
    spawnLinked(&thfunc, ports[1]);
638 +
639 +
    if (cr == SerialPort.CanRead.anyNonZero)
640 +
    {
641 +
        assertNotThrown(data = com.read(buffer, cr));
642 +
        assert(cast(string)data == "one");
643 +
        assertNotThrown(data = com.read(buffer, cr));
644 +
        assert(cast(string)data == "two");
645 +
    }
646 +
    else if (cr == SerialPort.CanRead.allOrNothing)
647 +
        assertThrown!TimeoutException(data = com.read(buffer));
648 +
    else if (cr == SerialPort.CanRead.zero)
649 +
    {
650 +
        assertNotThrown(data = com.read(buffer, cr));
651 +
        assert(cast(string)data == "");
652 +
        Thread.sleep(300.msecs);
653 +
        assertNotThrown(data = com.read(buffer, cr));
654 +
        assert(cast(string)data == "one");
655 +
        assertNotThrown(data = com.read(buffer, cr));
656 +
        assert(cast(string)data == "");
657 +
        Thread.sleep(200.msecs);
658 +
        assertNotThrown(data = com.read(buffer, cr));
659 +
        assert(cast(string)data == "two");
660 +
        assertNotThrown(data = com.read(buffer, cr));
661 +
        assert(cast(string)data == "");
662 +
    }
663 +
    else assert(0, "not tested variant of CanRead");
664 +
665 +
    receive((LinkTerminated e) { });
666 +
}
667 +
668 +
void fiberSleepFuncTest(string[2] ports)
669 +
{
670 +
    import std.datetime.stopwatch : StopWatch, AutoStart;
671 +
672 +
    static void sf(Duration d) @nogc
673 +
    {
674 +
        const sw = StopWatch(AutoStart.yes);
675 +
        if (auto f = Fiber.getThis)
676 +
            while (sw.peek < d) f.yield();
677 +
        else Thread.sleep(d);
678 +
    }
679 +
680 +
    CFMaster master;
681 +
682 +
    size_t sf2_cnt;
683 +
    void sf2(Duration d) @nogc
684 +
    {
685 +
        const sw = StopWatch(AutoStart.yes);
686 +
        if (auto f = Fiber.getThis)
687 +
            while (sw.peek < d)
688 +
            {
689 +
                master.yield();
690 +
                sf2_cnt++;
691 +
            }
692 +
        else Thread.sleep(d);
693 +
    }
694 +
695 +
    auto slave = new CFSlave(new SerialPortFR(ports[0], &sf), BUFFER_SIZE);
696 +
    scope (exit) slave.com.close();
697 +
    master = new CFMaster(new SerialPortFR(ports[1], &sf2), BUFFER_SIZE);
698 +
    scope (exit) master.com.close();
699 +
700 +
    bool work = true;
701 +
    int step;
702 +
    while (work)
703 +
    {
704 +
        alias TERM = Fiber.State.TERM;
705 +
        if (master.state != TERM) master.call;
706 +
        if (slave.state != TERM) slave.call;
707 +
708 +
        step++;
709 +
        Thread.sleep(30.msecs);
710 +
        if (master.state == TERM && slave.state == TERM)
711 +
        {
712 +
            if (slave.result.length == master.data.length)
713 +
            {
714 +
                import std.algorithm : equal;
715 +
                enforce(equal(cast(ubyte[])slave.result, cast(ubyte[])master.data));
716 +
                work = false;
717 +
                testPrint("basic loop steps: ", step);
718 +
            }
719 +
            else throw new Exception(text(slave.result, " != ", master.data));
720 +
        }
721 +
    }
722 +
}

@@ -53,6 +53,8 @@
Loading
53 53
private E setFields(E: SerialPortException)(E e, string port, string msg,
54 54
                                              string file, size_t line)
55 55
{
56 +
    if (e is null) // assert(0) not omit on optimize by compiler
57 +
        assert(0, "setField get null exception object");
56 58
    e.port = port;
57 59
    e.msg = msg;
58 60
    e.file = file;
@@ -146,10 +148,27 @@
Loading
146 148
{
147 149
    // can't use origin getSymbolsByUDA because
148 150
    // https://issues.dlang.org/show_bug.cgi?id=20054
149 -
    // paste old impl at end of file
150 -
    static if (__VERSION__ != 2088) import std.traits : getSymbolsByUDA;
151 -
    static foreach (sym; getSymbolsByUDA!(mixin(__MODULE__), preallocated))
152 -
        sym = new typeof(sym);
151 +
    static if (__VERSION__ < 2088)
152 +
    {
153 +
        import std.traits : getSymbolsByUDA;
154 +
        alias plist = getSymbolsByUDA!(mixin(__MODULE__), preallocated);
155 +
    }
156 +
    else
157 +
    {
158 +
        import std.meta : AliasSeq;
159 +
160 +
        alias plist = AliasSeq!(
161 +
            preallocSerialPortException,
162 +
            preallocPortClosedException,
163 +
            preallocTimeoutException,
164 +
            preallocSysCallException,
165 +
            preallocReadException,
166 +
            preallocWriteException,
167 +
            preallocUnsupported
168 +
        );
169 +
    }
170 +
171 +
    static foreach (sym; plist) sym = new typeof(sym);
153 172
}
154 173
155 174
mixin throwSPEMix!SerialPortException;
@@ -160,7 +179,6 @@
Loading
160 179
mixin throwSPSCEMix!ReadException;
161 180
mixin throwSPSCEMix!WriteException;
162 181
163 -
164 182
import serialport.types;
165 183
import core.stdc.stdio;
166 184
@@ -210,68 +228,4 @@
Loading
210 228
    }
211 229
    auto ln = sprintf(UEMPB.ptr, "unsupported parity: %s", str.ptr);
212 230
    throw preallocUnsupported.setFields(port, cast(immutable)UEMPB[0..ln], file, line);
213 -
}
214 -
215 -
static if (__VERSION__ == 2088)
216 -
{
217 -
    mixin(q{
218 -
219 -
    // use old version of getSymbolsByUDA
220 -
    private template getSymbolsByUDA(alias symbol, alias attribute)
221 -
    {
222 -
        import std.traits : hasUDA;
223 -
        alias membersWithUDA = getSymbolsByUDAImpl!(symbol, attribute, __traits(allMembers, symbol));
224 -
225 -
        // if the symbol itself has the UDA, tack it on to the front of the list
226 -
        static if (hasUDA!(symbol, attribute))
227 -
            alias getSymbolsByUDA = AliasSeq!(symbol, membersWithUDA);
228 -
        else
229 -
            alias getSymbolsByUDA = membersWithUDA;
230 -
    }
231 -
232 -
    private template getSymbolsByUDAImpl(alias symbol, alias attribute, names...)
233 -
    {
234 -
        import std.meta : Alias, AliasSeq, Filter;
235 -
        static if (names.length == 0)
236 -
        {
237 -
            alias getSymbolsByUDAImpl = AliasSeq!();
238 -
        }
239 -
        else
240 -
        {
241 -
            alias tail = getSymbolsByUDAImpl!(symbol, attribute, names[1 .. $]);
242 -
243 -
            // Filtering inaccessible members.
244 -
            static if (!__traits(compiles, __traits(getMember, symbol, names[0])))
245 -
            {
246 -
                alias getSymbolsByUDAImpl = tail;
247 -
            }
248 -
            else
249 -
            {
250 -
                alias member = __traits(getMember, symbol, names[0]);
251 -
252 -
                // Filtering not compiled members such as alias of basic types.
253 -
                static if (!__traits(compiles, hasUDA!(member, attribute)))
254 -
                {
255 -
                    alias getSymbolsByUDAImpl = tail;
256 -
                }
257 -
                // Get overloads for functions, in case different overloads have different sets of UDAs.
258 -
                else static if (isFunction!member)
259 -
                {
260 -
                    enum hasSpecificUDA(alias member) = hasUDA!(member, attribute);
261 -
                    alias overloadsWithUDA = Filter!(hasSpecificUDA, __traits(getOverloads, symbol, names[0]));
262 -
                    alias getSymbolsByUDAImpl = AliasSeq!(overloadsWithUDA, tail);
263 -
                }
264 -
                else static if (hasUDA!(member, attribute))
265 -
                {
266 -
                    alias getSymbolsByUDAImpl = AliasSeq!(member, tail);
267 -
                }
268 -
                else
269 -
                {
270 -
                    alias getSymbolsByUDAImpl = tail;
271 -
                }
272 -
            }
273 -
        }
274 -
    }
275 -
    
276 -
    });
277 231
}
Files Coverage
source/serialport 87.56%
Project Totals (8 files) 87.56%
107.10
TRAVIS_OS_NAME=linux
107.14
TRAVIS_OS_NAME=linux
107.11
TRAVIS_OS_NAME=linux
101.1
TRAVIS_OS_NAME=linux
101.3
TRAVIS_OS_NAME=linux
101.8
TRAVIS_OS_NAME=linux
101.5
TRAVIS_OS_NAME=linux
101.10
TRAVIS_OS_NAME=linux
101.7
TRAVIS_OS_NAME=linux
101.14
TRAVIS_OS_NAME=linux
101.11
TRAVIS_OS_NAME=linux
102.5
TRAVIS_OS_NAME=linux
102.3
TRAVIS_OS_NAME=linux
102.7
TRAVIS_OS_NAME=linux
102.1
TRAVIS_OS_NAME=linux