Skip to content

Commit

Permalink
Implement Read for VISA and add loopback read/write unit test (#1004)
Browse files Browse the repository at this point in the history
* Add read write support and a loop back system test for read/write.

* Add tcp_echo_server

* Fix for Linux

* Create TcpEchoServer

* Follow Dan's comments

* Follow Dan's comments

* Follow Dan's comment

* Fix System tests on Linux

* Follow Dan's comments

* Fix indention.

* Test added by mistake
  • Loading branch information
danielhuani authored Oct 4, 2023
1 parent 7d4dbb1 commit 296e4a3
Show file tree
Hide file tree
Showing 3 changed files with 282 additions and 16 deletions.
18 changes: 9 additions & 9 deletions source/custom/visa_service.custom.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -334,28 +334,28 @@ ::grpc::Status VisaService::Read(::grpc::ServerContext* context, const ReadReque
if (context->IsCancelled()) {
return ::grpc::Status::CANCELLED;
}
#if 1
return ::grpc::Status(grpc::StatusCode::DO_NOT_USE, "Custom code not implemented yet");
#else
ViSession vi = VI_NULL;
try {
auto vi_grpc_session = request->vi();
ViSession vi = session_repository_->access_session(vi_grpc_session.name());
vi = session_repository_->access_session(vi_grpc_session.name());
ViUInt32 count = request->count();
std::string buffer(return_count, '\0');
std::vector<ViByte> buffer(count);
ViUInt32 return_count{};
auto status = library_->Read(vi, (ViByte*)buffer.data(), count, &return_count);
if (!status_ok(status)) {
auto status = library_->Read(vi, buffer.data(), count, &return_count);
if (!status_ok(status) && return_count == 0) {
return ConvertApiErrorStatusForViSession(context, status, vi);
}
response->set_status(status);
response->set_buffer(buffer);
response->set_buffer(buffer.data(), return_count);
response->set_return_count(return_count);
return ::grpc::Status::OK;
}
catch (std::bad_alloc&) {
return ConvertApiErrorStatusForViSession(context, VI_ERROR_ALLOC, vi);
}
catch (nidevice_grpc::NonDriverException& ex) {
return ex.GetStatus();
}
#endif
}

//---------------------------------------------------------------------
Expand Down
119 changes: 112 additions & 7 deletions source/tests/system/visa_driver_api_tests.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#include "device_server.h"
#include "tests/utilities/tcp_echo_server.h"
#include "tests/utilities/test_helpers.h"
#include "visa/visa_client.h"

Expand All @@ -13,7 +14,8 @@ class VisaDriverApiTest : public ::testing::Test {
protected:
VisaDriverApiTest()
: device_server_(DeviceServerInterface::Singleton()),
visa_stub_(visa::Visa::NewStub(device_server_->InProcessChannel()))
visa_stub_(visa::Visa::NewStub(device_server_->InProcessChannel())),
instrument_descriptor_ni_com_("TCPIP::www.ni.com::80::SOCKET")
{
device_server_->ResetServer();
}
Expand All @@ -22,10 +24,7 @@ class VisaDriverApiTest : public ::testing::Test {

void SetUp() override
{
#ifndef WIN32
GTEST_SKIP() << "Digital pattern is not supported on Linux.";
#endif
initialize_driver_session();
initialize_driver_session(instrument_descriptor_ni_com_);
}

void TearDown() override
Expand All @@ -46,12 +45,12 @@ class VisaDriverApiTest : public ::testing::Test {
}


void initialize_driver_session()
void initialize_driver_session(const std::string& instrument_descriptor)
{
::grpc::ClientContext context;
visa::OpenRequest request;
visa::OpenResponse response;
request.set_instrument_descriptor("TCPIP::www.ni.com::80::SOCKET");
request.set_instrument_descriptor(instrument_descriptor);
request.set_access_mode(visa::LOCK_STATE_NO_LOCK);
request.set_session_name("SessionName");
request.set_open_timeout(VI_TMO_IMMEDIATE);
Expand Down Expand Up @@ -119,6 +118,7 @@ class VisaDriverApiTest : public ::testing::Test {
DeviceServerInterface* device_server_;
std::unique_ptr<visa::Visa::Stub> visa_stub_;
std::unique_ptr<::nidevice_grpc::Session> driver_session_;
std::string instrument_descriptor_ni_com_;
};

TEST_F(VisaDriverApiTest, GetOriginalTimeout_TwoSeconds)
Expand Down Expand Up @@ -153,6 +153,111 @@ TEST_F(VisaDriverApiTest, SetNonTcpipAttribute_ReturnsAttributeError)
);
}

class VisaDriverLoopbackTest : public VisaDriverApiTest {
public:
VisaDriverLoopbackTest()
{
}
virtual ~VisaDriverLoopbackTest()
{
}

void SetUp() override
{
EXPECT_EQ(0, echoserver_.start());

std::string portNumber(std::to_string(echoserver_.get_server_port()));
std::string instrument_descriptor("TCPIP0::localhost::" + portNumber + "::SOCKET");
initialize_driver_session(instrument_descriptor);
}

void TearDown() override
{
close_driver_session();
echoserver_.stop();
}

void write(const std::string& data)
{
auto response = client::write(GetStub(), GetNamedSession(), data);
EXPECT_EQ(VI_SUCCESS, response.status());
EXPECT_EQ(data.size(), response.return_count());
}

void read(uint32_t count, const std::string& expectedData, ViStatus expectedStatus)
{
auto response = client::read(GetStub(), GetNamedSession(), count);
EXPECT_EQ(expectedStatus, response.status());
EXPECT_EQ(expectedData.size(), response.return_count());
EXPECT_EQ(expectedData, response.buffer());
}

private:
TcpEchoServer echoserver_;
};

TEST_F(VisaDriverLoopbackTest, WriteAndRead_Matches)
{
std::string writeData = "Visa gRPC read/write test";
write(writeData);
read(writeData.size(), writeData, VI_SUCCESS_MAX_CNT);
}

TEST_F(VisaDriverLoopbackTest, WriteTwice_ReadOnce_Matches)
{
std::string writeData = "Visa gRPC read/write test";
write(writeData + "1 ");
write(writeData + "2");
std::string expectedReadData = writeData + "1 " + writeData + "2";
read(expectedReadData.size(), expectedReadData, VI_SUCCESS_MAX_CNT);
}

TEST_F(VisaDriverLoopbackTest, ReadMoreThanWritten_ReturnTimeoutErrorWithDataInResponsePacket)
{
std::string writeData = "Visa gRPC read/write test";
write(writeData);
read(writeData.size() + 1, writeData, VI_ERROR_TMO);
}

TEST_F(VisaDriverLoopbackTest, ReadLessThanWritten_ReturnSuccess)
{
std::string writeData = "Visa gRPC read/write test";
write(writeData);
read(writeData.size() - 1, writeData.substr(0, writeData.size() - 1), VI_SUCCESS_MAX_CNT);
}

TEST_F(VisaDriverLoopbackTest, ReadZeroBytes_ReturnSuccess)
{
std::string writeData = "Visa gRPC read/write test";
write(writeData);
read(0, "", VI_SUCCESS_MAX_CNT);
}

TEST_F(VisaDriverLoopbackTest, ReadWithoutWrite_ThrowsError)
{
EXPECT_THROW_DRIVER_ERROR(
read(10, "", -1),
VI_ERROR_TMO);
}

TEST_F(VisaDriverLoopbackTest, WriteSpecialData_ReadMatches)
{
const char buffer[] = {'A', '\0', 'B', '\0', 'C'};
std::string writeData(buffer, sizeof(buffer));
write(writeData);
read(writeData.size(), writeData, VI_SUCCESS_MAX_CNT);
}

TEST_F(VisaDriverLoopbackTest, WriteWithTermChar_ReadMatches)
{
std::string writeData = "Hello\nWorld\n";

write(writeData);
set_bool_attribute(visa::VisaAttribute::VISA_ATTRIBUTE_TERMCHAR_EN, true);
read(255, "Hello\n", VI_SUCCESS_TERM_CHAR);
read(255, "World\n", VI_SUCCESS_TERM_CHAR);
}

} // namespace system
} // namespace tests
} // namespace ni
161 changes: 161 additions & 0 deletions source/tests/utilities/tcp_echo_server.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
#include <cstring>
#include <memory>
#include <string>
#include <thread>
#ifdef _WIN32
#include <winsock2.h>
#else
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#endif

#ifdef _WIN32
typedef int socklen_t;
#else
typedef int SOCKET;
#endif

class TcpEchoSession : public std::enable_shared_from_this<TcpEchoSession> {
public:
TcpEchoSession(SOCKET socket_fd)
: socket_fd_(socket_fd)
, stop_session_(false)
{
}

~TcpEchoSession() {
stop();
#ifdef _WIN32
closesocket(socket_fd_);
#else
close(socket_fd_);
#endif
}

void start()
{
while (!stop_session_) {
do_read();
}
}

void stop()
{
stop_session_ = true;
}

private:
void do_read()
{
auto self(shared_from_this());
char buffer[max_length] = {0};
int n = recv(socket_fd_, buffer, max_length, 0);
if (n > 0) {
do_write(buffer, n);
}
}

void do_write(const char* data, int length)
{
auto self(shared_from_this());
int n = send(socket_fd_, data, length, 0);
}

static const int max_length = 256;
SOCKET socket_fd_;
bool stop_session_;
};

class TcpEchoServer
{
public:
TcpEchoServer()
: server_fd_(-1)
{
}

~TcpEchoServer()
{
}

int start()
{
return start_server_session();
}

void stop()
{
close_server_session();
}

int get_server_port()
{
struct sockaddr_in assigned_address;
socklen_t len = sizeof(assigned_address);
if (getsockname(server_fd_, (struct sockaddr *)&assigned_address, &len) == -1) {
return -1;
}
else {
return ntohs(assigned_address.sin_port);
}
}

private:
int start_server_session()
{
#ifdef _WIN32
WSADATA wsa_data;
WSAStartup(MAKEWORD(2, 2), &wsa_data);
#endif
server_fd_ = socket(AF_INET, SOCK_STREAM, 0);
if (server_fd_ == -1) {
return -1;
}
struct sockaddr_in server_address;
std::memset(&server_address, 0, sizeof(server_address));
server_address.sin_family = AF_INET;
server_address.sin_addr.s_addr = inet_addr("127.0.0.1");
server_address.sin_port = htons(0);

if (bind(server_fd_, (struct sockaddr*)&server_address, sizeof(server_address)) != 0) {
return -1;
}
server_thread_ = std::thread(&TcpEchoServer::run_server, this, server_fd_);
return 0;
}

void run_server(SOCKET server_fd) {
listen(server_fd, 1);
SOCKET client_fd = accept(server_fd, NULL, NULL);
if (client_fd == -1) {
return;
}
std::thread([this, client_fd]() {
handle_client(client_fd);
}).detach();
}

void close_server_session()
{
session_->stop();
server_thread_.join();
#ifdef _WIN32
closesocket(server_fd_);
WSACleanup();
#else
close(server_fd_);
#endif
}

void handle_client(SOCKET client_fd)
{
session_ = std::make_shared<TcpEchoSession>(client_fd);
session_->start();
}

SOCKET server_fd_;
std::thread server_thread_;
std::shared_ptr<TcpEchoSession> session_;
};

0 comments on commit 296e4a3

Please sign in to comment.