2

I've a written a high level wrapper and command collection that uses the RProc/RPMsg interface to communicate with a microprocessor, in order to test as much as possible I've written unit tests for it that use a Linux pseudoterminal in place of the 'real' interface.

My tests didn't work. I eventually simplified the test to the point where it contained no traces of my code - and it still doesn't work:

BOOST_AUTO_TEST_CASE(constructor)
{
    const auto master_pty = ::posix_openpt(O_RDWR);
    BOOST_REQUIRE(master_pty != -1);
    BOOST_REQUIRE(::grantpt(master_pty) != -1);
    BOOST_REQUIRE(::unlockpt(master_pty) != -1);

    const auto slave_pty_name = ::ptsname(master_pty);
    const auto slave_pty = ::open(slave_pty_name, O_RDWR);
    BOOST_REQUIRE(slave_pty != -1);

    const auto in_data = std::array<std::uint8_t, 4>{1, 2, 3, 4};
    const auto bytes_written = ::write(master_pty, in_data.data(), in_data.size());
    if (bytes_written < 0) {
        BOOST_FAIL("Failed to write: " << errno);
    }
    BOOST_REQUIRE_EQUAL(in_data.size(), bytes_written);

    auto out_data = std::array<std::uint8_t, in_data.size()>{};
    const auto bytes_read = ::read(slave_pty, out_data.data(), out_data.size());
    BOOST_CHECK_EQUAL(bytes_read, bytes_written);
    if (bytes_read < 0) {
        BOOST_FAIL("::read failed: " << errno);
    }

    ::close(slave_pty);
    ::close(master_pty);
}

The output of which is:

*** 1 failure is detected in the test module "cmPTP Test Suite"
# cmPTP-manager-test -l all --run_test=comms_rproc_interface/constructor
Running 1 test case...
Entering test module "cmPTP Test Suite"
interface_test.cpp(17): Entering test suite "comms_rproc_interface"
interface_test.cpp(19): Entering test case "constructor"
interface_test.cpp(23): info: check master_pty != -1 has passed
interface_test.cpp(24): info: check grantpt(master_pty) != -1 has passed
interface_test.cpp(25): info: check unlockpt(master_pty) != -1 has passed
interface_test.cpp(32): info: check slave_pty != -1 has passed
interface_test.cpp(72): info: check in_data.size() == bytes_written has passed
interface_test.cpp(77): error: in "comms_rproc_interface/constructor": check bytes_read == bytes_written has failed [0 != 4]
interface_test.cpp(19): Leaving test case "constructor"; testing time: 15282us
interface_test.cpp(17): Leaving test suite "comms_rproc_interface"; testing time: 15931us
Leaving test module "cmPTP Test Suite"; testing time: 16879us

*** 1 failure is detected in the test module "cmPTP Test Suite"

I can write my 4 bytes fine, but nothing is ever present on the slave side. Why?

cmannett85
  • 21,725
  • 8
  • 76
  • 119
  • That doesn't look like the read actually fails, but that it doesn't retrieve the bytes. Has the write buffer been flushed? – starturtle May 06 '17 at 10:57
  • @starturtle Should have: "POSIX requires that a read(2) which can be proved to occur after a write() has returned returns the new data. Note that not all file systems are POSIX conforming." I am of course assuming Linux/EXT4 is POSIX conforming... How can I force a flush? – cmannett85 May 06 '17 at 11:11
  • @starturtle Actually it's just dawned on me that a PTS is _two_ file descriptors so the above doesn't apply. – cmannett85 May 06 '17 at 11:13
  • My question was answered [here](http://stackoverflow.com/questions/259355/how-can-you-flush-a-write-using-a-file-descriptor) - `open` is not buffered, so there's no buffer to flush. Given that it's a system-related function, I think it's safe to assume it acts the same for C and C++. – starturtle May 07 '17 at 10:09
  • @starturtle I figured what I was doing wrong, it was sort of to do with flushing. See my answer. – cmannett85 May 07 '17 at 10:15

1 Answers1

0

It's because I forgot I was creating a pseudo terminal and not a serial device. The PTY was treating my input values as ASCII (which probably freaked it out a bit), and was waiting for a line return before flushing - so @starturtle's comment about flushing was correct, just not in a 'normal' file-writing-way.

This trival SSCCE shows how I should have done it:

#include <unistd.h>
#include <pty.h>

#include <array>
#include <iostream>

int main()
{
    // Make the PTY 'raw' to disable traditional terminal-like
    // behaviour
    struct termios ts;
    cfmakeraw(&ts);

    auto master_pty = -1;
    auto slave_pty  = -1;
    if (::openpty(&master_pty, &slave_pty, nullptr, &ts, nullptr) < 0) {
        std::cerr << "Cannot create PTY: " << errno << std::endl;
        return EXIT_FAILURE;
    }

    const auto in_data = std::array<std::uint8_t, 5>{1, 2, 3, 4, 5};
    const auto bytes_written = ::write(master_pty, in_data.data(), in_data.size());
    if (bytes_written < 0) {
        std::cerr << "Failed to write: " << errno << std::endl;
        return EXIT_FAILURE;
    }

    auto out_data = std::array<std::uint8_t, in_data.size()>{};
    out_data.fill(0);

    const auto bytes_read = ::read(slave_pty, out_data.data(), out_data.size());
    if (bytes_read < 0) {
        std::cerr << "::read failed: " << errno << std::endl;
        return EXIT_FAILURE;
    }

    for (auto&& c : out_data) {
        std::cout << static_cast<int>(c);
    }
    std::cout << std::endl;

    ::close(slave_pty);
    ::close(master_pty);
}
cmannett85
  • 21,725
  • 8
  • 76
  • 119