forked from quickshell/quickshell
refactor: move socket and datastream types to Io module
This commit is contained in:
parent
f45d298b66
commit
14e1d2d162
12 changed files with 26 additions and 13 deletions
16
src/io/CMakeLists.txt
Normal file
16
src/io/CMakeLists.txt
Normal file
|
@ -0,0 +1,16 @@
|
|||
qt_add_library(quickshell-io STATIC
|
||||
datastream.cpp
|
||||
)
|
||||
|
||||
if (SOCKETS)
|
||||
target_sources(quickshell-io PRIVATE socket.cpp)
|
||||
endif()
|
||||
|
||||
qt_add_qml_module(quickshell-io URI Quickshell.Io)
|
||||
target_link_libraries(quickshell-io PRIVATE ${QT_DEPS})
|
||||
|
||||
target_link_libraries(quickshell PRIVATE quickshell-ioplugin)
|
||||
|
||||
if (TESTS)
|
||||
add_subdirectory(test)
|
||||
endif()
|
108
src/io/datastream.cpp
Normal file
108
src/io/datastream.cpp
Normal file
|
@ -0,0 +1,108 @@
|
|||
#include "datastream.hpp"
|
||||
#include <algorithm>
|
||||
#include <utility>
|
||||
|
||||
#include <qlocalsocket.h>
|
||||
#include <qobject.h>
|
||||
#include <qtmetamacros.h>
|
||||
#include <qtypes.h>
|
||||
|
||||
DataStreamParser* DataStream::reader() const { return this->mReader; }
|
||||
|
||||
void DataStream::setReader(DataStreamParser* reader) {
|
||||
if (reader == this->mReader) return;
|
||||
|
||||
if (this->mReader != nullptr) {
|
||||
QObject::disconnect(this->mReader, nullptr, this, nullptr);
|
||||
}
|
||||
|
||||
this->mReader = reader;
|
||||
|
||||
if (reader != nullptr) {
|
||||
QObject::connect(reader, &QObject::destroyed, this, &DataStream::onReaderDestroyed);
|
||||
}
|
||||
|
||||
emit this->readerChanged();
|
||||
|
||||
if (reader != nullptr && !this->buffer.isEmpty()) {
|
||||
reader->parseBytes(this->buffer, this->buffer);
|
||||
}
|
||||
}
|
||||
|
||||
void DataStream::onReaderDestroyed() {
|
||||
this->mReader = nullptr;
|
||||
emit this->readerChanged();
|
||||
}
|
||||
|
||||
void DataStream::onBytesAvailable() {
|
||||
auto buf = this->ioDevice()->readAll();
|
||||
this->mReader->parseBytes(buf, this->buffer);
|
||||
}
|
||||
|
||||
void SplitParser::parseBytes(QByteArray& incoming, QByteArray& buffer) {
|
||||
if (this->mSplitMarker.isEmpty()) {
|
||||
if (!buffer.isEmpty()) {
|
||||
emit this->read(QString(buffer));
|
||||
buffer.clear();
|
||||
}
|
||||
|
||||
emit this->read(QString(incoming));
|
||||
return;
|
||||
}
|
||||
|
||||
// make sure we dont miss any delimiters in the buffer if the delimiter changes
|
||||
if (this->mSplitMarkerChanged) {
|
||||
this->mSplitMarkerChanged = false;
|
||||
this->parseBytes(buffer, buffer);
|
||||
}
|
||||
|
||||
auto marker = this->mSplitMarker.toUtf8();
|
||||
auto mlen = marker.length();
|
||||
|
||||
auto blen = buffer.size();
|
||||
auto ilen = incoming.size();
|
||||
|
||||
qsizetype start = &incoming == &buffer ? 0 : -blen;
|
||||
for (auto readi = -std::min(blen, mlen - 1); readi <= ilen - mlen; readi++) {
|
||||
for (auto marki = 0; marki < mlen; marki++) {
|
||||
qint8 byte; // NOLINT
|
||||
if (readi + marki < 0) byte = buffer[blen + readi + marki];
|
||||
else byte = incoming[readi + marki];
|
||||
|
||||
if (byte != marker[marki]) goto fail;
|
||||
}
|
||||
|
||||
{
|
||||
QByteArray slice;
|
||||
if (start < 0) slice = buffer.sliced(0, std::min(blen, blen + readi));
|
||||
if (readi > 0) {
|
||||
auto sstart = std::max(static_cast<qsizetype>(0), start);
|
||||
slice.append(incoming.sliced(sstart, readi - sstart));
|
||||
}
|
||||
readi += mlen;
|
||||
start = readi;
|
||||
emit this->read(QString(slice));
|
||||
}
|
||||
|
||||
fail:;
|
||||
}
|
||||
|
||||
if (start < 0) {
|
||||
buffer.append(incoming);
|
||||
} else {
|
||||
// Will break the init case if inlined. Must be before clear.
|
||||
auto slice = incoming.sliced(start);
|
||||
buffer.clear();
|
||||
buffer.insert(0, slice);
|
||||
}
|
||||
}
|
||||
|
||||
QString SplitParser::splitMarker() const { return this->mSplitMarker; }
|
||||
|
||||
void SplitParser::setSplitMarker(QString marker) {
|
||||
if (marker == this->mSplitMarker) return;
|
||||
|
||||
this->mSplitMarker = std::move(marker);
|
||||
this->mSplitMarkerChanged = true;
|
||||
emit this->splitMarkerChanged();
|
||||
}
|
90
src/io/datastream.hpp
Normal file
90
src/io/datastream.hpp
Normal file
|
@ -0,0 +1,90 @@
|
|||
#pragma once
|
||||
|
||||
#include <qbytearray.h>
|
||||
#include <qlocalsocket.h>
|
||||
#include <qobject.h>
|
||||
#include <qqmlintegration.h>
|
||||
#include <qtmetamacros.h>
|
||||
#include <qvariant.h>
|
||||
|
||||
class DataStreamParser;
|
||||
|
||||
///! Data source that can be streamed into a parser.
|
||||
/// See also: [DataStreamParser](../datastreamparser)
|
||||
class DataStream: public QObject {
|
||||
Q_OBJECT;
|
||||
/// The parser to stream data from this source into.
|
||||
/// If the parser is null no data will be read.
|
||||
Q_PROPERTY(DataStreamParser* parser READ reader WRITE setReader NOTIFY readerChanged);
|
||||
QML_ELEMENT;
|
||||
QML_UNCREATABLE("base class");
|
||||
|
||||
public:
|
||||
explicit DataStream(QObject* parent = nullptr): QObject(parent) {}
|
||||
|
||||
[[nodiscard]] DataStreamParser* reader() const;
|
||||
void setReader(DataStreamParser* reader);
|
||||
|
||||
signals:
|
||||
void readerChanged();
|
||||
|
||||
public slots:
|
||||
void onBytesAvailable();
|
||||
|
||||
protected:
|
||||
[[nodiscard]] virtual QIODevice* ioDevice() const = 0;
|
||||
|
||||
private slots:
|
||||
void onReaderDestroyed();
|
||||
|
||||
private:
|
||||
DataStreamParser* mReader = nullptr;
|
||||
QByteArray buffer;
|
||||
};
|
||||
|
||||
///! Parser for streamed input data.
|
||||
/// See also: [DataStream](../datastream), [SplitParser](../splitparser)
|
||||
class DataStreamParser: public QObject {
|
||||
Q_OBJECT;
|
||||
QML_ELEMENT;
|
||||
QML_UNCREATABLE("base class");
|
||||
|
||||
public:
|
||||
explicit DataStreamParser(QObject* parent = nullptr): QObject(parent) {}
|
||||
|
||||
// the buffer will be sent in both slots if there is data remaining from a previous parser
|
||||
virtual void parseBytes(QByteArray& incoming, QByteArray& buffer) = 0;
|
||||
|
||||
signals:
|
||||
/// Emitted when data is read from the stream.
|
||||
void read(QString data);
|
||||
};
|
||||
|
||||
///! Parser for delimited data streams.
|
||||
/// Parser for delimited data streams. [read()] is emitted once per delimited chunk of the stream.
|
||||
///
|
||||
/// [read()]: ../datastreamparser#sig.read
|
||||
class SplitParser: public DataStreamParser {
|
||||
Q_OBJECT;
|
||||
/// The delimiter for parsed data. May be multiple characters. Defaults to `\n`.
|
||||
///
|
||||
/// If the delimiter is empty read lengths may be arbitrary (whatever is returned by the
|
||||
/// underlying read call.)
|
||||
Q_PROPERTY(QString splitMarker READ splitMarker WRITE setSplitMarker NOTIFY splitMarkerChanged);
|
||||
QML_ELEMENT;
|
||||
|
||||
public:
|
||||
explicit SplitParser(QObject* parent = nullptr): DataStreamParser(parent) {}
|
||||
|
||||
void parseBytes(QByteArray& incoming, QByteArray& buffer) override;
|
||||
|
||||
[[nodiscard]] QString splitMarker() const;
|
||||
void setSplitMarker(QString marker);
|
||||
|
||||
signals:
|
||||
void splitMarkerChanged();
|
||||
|
||||
private:
|
||||
QString mSplitMarker = "\n";
|
||||
bool mSplitMarkerChanged = false;
|
||||
};
|
7
src/io/module.md
Normal file
7
src/io/module.md
Normal file
|
@ -0,0 +1,7 @@
|
|||
name = "Quickshell.Io"
|
||||
description = "Io types"
|
||||
headers = [
|
||||
"datastream.hpp",
|
||||
"socket.hpp",
|
||||
]
|
||||
-----
|
199
src/io/socket.cpp
Normal file
199
src/io/socket.cpp
Normal file
|
@ -0,0 +1,199 @@
|
|||
#include "socket.hpp"
|
||||
#include <utility>
|
||||
|
||||
#include <qfile.h>
|
||||
#include <qlocalserver.h>
|
||||
#include <qlocalsocket.h>
|
||||
#include <qlogging.h>
|
||||
#include <qobject.h>
|
||||
#include <qqmlcomponent.h>
|
||||
#include <qqmlengine.h>
|
||||
#include <qtmetamacros.h>
|
||||
|
||||
#include "datastream.hpp"
|
||||
|
||||
void Socket::setSocket(QLocalSocket* socket) {
|
||||
if (this->socket != nullptr) this->socket->deleteLater();
|
||||
this->socket = socket;
|
||||
|
||||
if (socket != nullptr) {
|
||||
socket->setParent(this);
|
||||
|
||||
// clang-format off
|
||||
QObject::connect(this->socket, &QLocalSocket::connected, this, &Socket::onSocketConnected);
|
||||
QObject::connect(this->socket, &QLocalSocket::disconnected, this, &Socket::onSocketDisconnected);
|
||||
QObject::connect(this->socket, &QLocalSocket::errorOccurred, this, &Socket::error);
|
||||
QObject::connect(this->socket, &QLocalSocket::readyRead, this, &DataStream::onBytesAvailable);
|
||||
// clang-format on
|
||||
|
||||
if (socket->isOpen()) this->onSocketConnected();
|
||||
}
|
||||
}
|
||||
|
||||
QString Socket::path() const { return this->mPath; }
|
||||
|
||||
void Socket::setPath(QString path) {
|
||||
if ((this->connected && !this->disconnecting) || path == this->mPath) return;
|
||||
this->mPath = std::move(path);
|
||||
emit this->pathChanged();
|
||||
|
||||
if (this->targetConnected && this->socket == nullptr) this->connectPathSocket();
|
||||
}
|
||||
|
||||
void Socket::onSocketConnected() {
|
||||
this->connected = true;
|
||||
this->targetConnected = false;
|
||||
this->disconnecting = false;
|
||||
emit this->connectionStateChanged();
|
||||
}
|
||||
|
||||
void Socket::onSocketDisconnected() {
|
||||
this->connected = false;
|
||||
this->disconnecting = false;
|
||||
this->socket->deleteLater();
|
||||
this->socket = nullptr;
|
||||
emit this->connectionStateChanged();
|
||||
|
||||
if (this->targetConnected) this->connectPathSocket();
|
||||
}
|
||||
|
||||
bool Socket::isConnected() const { return this->connected; }
|
||||
|
||||
void Socket::setConnected(bool connected) {
|
||||
this->targetConnected = connected;
|
||||
|
||||
if (!connected) {
|
||||
if (this->socket != nullptr && !this->disconnecting) {
|
||||
this->disconnecting = true;
|
||||
this->socket->disconnectFromServer();
|
||||
}
|
||||
} else if (this->socket == nullptr) this->connectPathSocket();
|
||||
}
|
||||
|
||||
QIODevice* Socket::ioDevice() const { return this->socket; }
|
||||
|
||||
void Socket::connectPathSocket() {
|
||||
if (!this->mPath.isEmpty()) {
|
||||
auto* socket = new QLocalSocket();
|
||||
socket->setServerName(this->mPath);
|
||||
this->setSocket(socket);
|
||||
this->socket->connectToServer(QIODevice::ReadWrite);
|
||||
}
|
||||
}
|
||||
|
||||
void Socket::write(const QString& data) {
|
||||
if (this->socket != nullptr) {
|
||||
this->socket->write(data.toUtf8());
|
||||
}
|
||||
}
|
||||
|
||||
SocketServer::~SocketServer() { this->disableServer(); }
|
||||
|
||||
void SocketServer::onPostReload() {
|
||||
this->postReload = true;
|
||||
if (this->isActivatable()) this->enableServer();
|
||||
}
|
||||
|
||||
bool SocketServer::isActive() const { return this->server != nullptr; }
|
||||
|
||||
void SocketServer::setActive(bool active) {
|
||||
this->activeTarget = active;
|
||||
if (active == (this->server != nullptr)) return;
|
||||
|
||||
if (active) {
|
||||
if (this->isActivatable()) this->enableServer();
|
||||
} else this->disableServer();
|
||||
}
|
||||
|
||||
QString SocketServer::path() const { return this->mPath; }
|
||||
|
||||
void SocketServer::setPath(QString path) {
|
||||
if (this->mPath == path) return;
|
||||
this->mPath = std::move(path);
|
||||
emit this->pathChanged();
|
||||
|
||||
if (this->isActivatable()) this->enableServer();
|
||||
}
|
||||
|
||||
QQmlComponent* SocketServer::handler() const { return this->mHandler; }
|
||||
|
||||
void SocketServer::setHandler(QQmlComponent* handler) {
|
||||
if (this->mHandler != nullptr) this->mHandler->deleteLater();
|
||||
this->mHandler = handler;
|
||||
|
||||
if (handler != nullptr) {
|
||||
handler->setParent(this);
|
||||
}
|
||||
}
|
||||
|
||||
bool SocketServer::isActivatable() {
|
||||
return this->server == nullptr && this->postReload && this->activeTarget && !this->mPath.isEmpty()
|
||||
&& this->handler() != nullptr;
|
||||
}
|
||||
|
||||
void SocketServer::enableServer() {
|
||||
this->disableServer();
|
||||
|
||||
this->server = new QLocalServer(this);
|
||||
QObject::connect(
|
||||
this->server,
|
||||
&QLocalServer::newConnection,
|
||||
this,
|
||||
&SocketServer::onNewConnection
|
||||
);
|
||||
|
||||
if (!this->server->listen(this->mPath)) {
|
||||
qWarning() << "could not start socket server at" << this->mPath;
|
||||
this->disableServer();
|
||||
}
|
||||
|
||||
this->activeTarget = false;
|
||||
emit this->activeStatusChanged();
|
||||
}
|
||||
|
||||
void SocketServer::disableServer() {
|
||||
auto wasActive = this->server != nullptr;
|
||||
|
||||
if (this->server != nullptr) {
|
||||
for (auto* socket: this->mSockets) {
|
||||
socket->deleteLater();
|
||||
}
|
||||
|
||||
this->mSockets.clear();
|
||||
this->server->deleteLater();
|
||||
this->server = nullptr;
|
||||
}
|
||||
|
||||
if (this->mPath != nullptr) {
|
||||
if (QFile::exists(this->mPath) && !QFile::remove(this->mPath)) {
|
||||
qWarning() << "failed to delete socket file at" << this->mPath;
|
||||
}
|
||||
}
|
||||
|
||||
if (wasActive) emit this->activeStatusChanged();
|
||||
}
|
||||
|
||||
void SocketServer::onNewConnection() {
|
||||
if (auto* connection = this->server->nextPendingConnection()) {
|
||||
auto* instanceObj = this->mHandler->create(QQmlEngine::contextForObject(this));
|
||||
auto* instance = qobject_cast<Socket*>(instanceObj);
|
||||
|
||||
if (instance == nullptr) {
|
||||
qWarning() << "SocketServer.handler does not create a Socket. Dropping connection.";
|
||||
if (instanceObj != nullptr) instanceObj->deleteLater();
|
||||
connection->deleteLater();
|
||||
return;
|
||||
}
|
||||
|
||||
this->mSockets.append(instance);
|
||||
instance->setParent(this);
|
||||
|
||||
if (instance->isConnected()) {
|
||||
qWarning() << "SocketServer.handler created a socket with an existing connection. Dropping "
|
||||
"new connection.";
|
||||
connection->deleteLater();
|
||||
} else {
|
||||
instance->setSocket(connection);
|
||||
}
|
||||
}
|
||||
}
|
143
src/io/socket.hpp
Normal file
143
src/io/socket.hpp
Normal file
|
@ -0,0 +1,143 @@
|
|||
#pragma once
|
||||
|
||||
#include <qlocalserver.h>
|
||||
#include <qlocalsocket.h>
|
||||
#include <qobject.h>
|
||||
#include <qqmlcomponent.h>
|
||||
#include <qqmlintegration.h>
|
||||
#include <qtclasshelpermacros.h>
|
||||
#include <qtmetamacros.h>
|
||||
|
||||
#include "datastream.hpp"
|
||||
#include "../core/reload.hpp"
|
||||
|
||||
///! Unix socket listener.
|
||||
class Socket: public DataStream {
|
||||
Q_OBJECT;
|
||||
/// Returns if the socket is currently connected.
|
||||
///
|
||||
/// Writing to this property will set the target connection state and will not
|
||||
/// update the property immediately. Setting the property to false will begin disconnecting
|
||||
/// the socket, and setting it to true will begin connecting the socket if path is not empty.
|
||||
Q_PROPERTY(bool connected READ isConnected WRITE setConnected NOTIFY connectionStateChanged);
|
||||
/// The path to connect this socket to when `connected` is set to true.
|
||||
///
|
||||
/// Changing this property will have no effect while the connection is active.
|
||||
Q_PROPERTY(QString path READ path WRITE setPath NOTIFY pathChanged);
|
||||
QML_ELEMENT;
|
||||
|
||||
public:
|
||||
explicit Socket(QObject* parent = nullptr): DataStream(parent) {}
|
||||
|
||||
/// Write data to the socket. Does nothing if not connected.
|
||||
Q_INVOKABLE void write(const QString& data);
|
||||
|
||||
// takes ownership
|
||||
void setSocket(QLocalSocket* socket);
|
||||
|
||||
[[nodiscard]] bool isConnected() const;
|
||||
void setConnected(bool connected);
|
||||
|
||||
[[nodiscard]] QString path() const;
|
||||
void setPath(QString path);
|
||||
|
||||
signals:
|
||||
/// This signal is sent whenever a socket error is encountered.
|
||||
void error(QLocalSocket::LocalSocketError error);
|
||||
|
||||
void connectionStateChanged();
|
||||
void pathChanged();
|
||||
|
||||
protected:
|
||||
[[nodiscard]] QIODevice* ioDevice() const override;
|
||||
|
||||
private slots:
|
||||
void onSocketConnected();
|
||||
void onSocketDisconnected();
|
||||
|
||||
private:
|
||||
void connectPathSocket();
|
||||
|
||||
QLocalSocket* socket = nullptr;
|
||||
bool connected = false;
|
||||
bool disconnecting = false;
|
||||
bool targetConnected = false;
|
||||
QString mPath;
|
||||
};
|
||||
|
||||
///! Unix socket server.
|
||||
/// #### Example
|
||||
/// ```qml
|
||||
/// SocketServer {
|
||||
/// active: true
|
||||
/// path: "/path/too/socket.sock"
|
||||
/// handler: Socket {
|
||||
/// onConnectedChanged: {
|
||||
/// console.log(connected ? "new connection!" : "connection dropped!")
|
||||
/// }
|
||||
/// parser: SplitParser {
|
||||
/// onRead: message => console.log(`read message from socket: ${message}`)
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
class SocketServer
|
||||
: public QObject
|
||||
, public PostReloadHook {
|
||||
Q_OBJECT;
|
||||
/// If the socket server is currently active. Defaults to false.
|
||||
///
|
||||
/// Setting this to false will destory all active connections and delete
|
||||
/// the socket file on disk.
|
||||
///
|
||||
/// If path is empty setting this property will have no effect.
|
||||
Q_PROPERTY(bool active READ isActive WRITE setActive NOTIFY activeStatusChanged);
|
||||
/// The path to create the socket server at.
|
||||
///
|
||||
/// Setting this property while the server is active will have no effect.
|
||||
Q_PROPERTY(QString path READ path WRITE setPath NOTIFY pathChanged);
|
||||
/// Connection handler component. Must creeate a `Socket`.
|
||||
///
|
||||
/// The created socket should not set `connected` or `path` or the incoming
|
||||
/// socket connection will be dropped (they will be set by the socket server.)
|
||||
/// Setting `connected` to false on the created socket after connection will
|
||||
/// close and delete it.
|
||||
Q_PROPERTY(QQmlComponent* handler READ handler WRITE setHandler NOTIFY handlerChanged);
|
||||
QML_ELEMENT;
|
||||
|
||||
public:
|
||||
explicit SocketServer(QObject* parent = nullptr): QObject(parent) {}
|
||||
~SocketServer() override;
|
||||
Q_DISABLE_COPY_MOVE(SocketServer);
|
||||
|
||||
void onPostReload() override;
|
||||
|
||||
[[nodiscard]] bool isActive() const;
|
||||
void setActive(bool active);
|
||||
|
||||
[[nodiscard]] QString path() const;
|
||||
void setPath(QString path);
|
||||
|
||||
[[nodiscard]] QQmlComponent* handler() const;
|
||||
void setHandler(QQmlComponent* handler);
|
||||
|
||||
signals:
|
||||
void activeStatusChanged();
|
||||
void pathChanged();
|
||||
void handlerChanged();
|
||||
|
||||
private slots:
|
||||
void onNewConnection();
|
||||
|
||||
private:
|
||||
bool isActivatable();
|
||||
void enableServer();
|
||||
void disableServer();
|
||||
|
||||
QLocalServer* server = nullptr;
|
||||
QQmlComponent* mHandler = nullptr;
|
||||
QList<Socket*> mSockets;
|
||||
bool activeTarget = false;
|
||||
bool postReload = false;
|
||||
QString mPath;
|
||||
};
|
7
src/io/test/CMakeLists.txt
Normal file
7
src/io/test/CMakeLists.txt
Normal file
|
@ -0,0 +1,7 @@
|
|||
function (qs_test name)
|
||||
add_executable(${name} ${ARGN})
|
||||
target_link_libraries(${name} PRIVATE ${QT_DEPS} Qt6::Test)
|
||||
add_test(NAME ${name} WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}" COMMAND $<TARGET_FILE:${name}>)
|
||||
endfunction()
|
||||
|
||||
qs_test(datastream datastream.cpp ../datastream.cpp)
|
110
src/io/test/datastream.cpp
Normal file
110
src/io/test/datastream.cpp
Normal file
|
@ -0,0 +1,110 @@
|
|||
#include "../datastream.hpp"
|
||||
|
||||
#include <qbytearray.h>
|
||||
#include <qlist.h>
|
||||
#include <qlogging.h>
|
||||
#include <qobject.h>
|
||||
#include <qsignalspy.h>
|
||||
#include <qtest.h>
|
||||
#include <qtestcase.h>
|
||||
#include <qtmetamacros.h>
|
||||
|
||||
class TestSplitParser: public QObject {
|
||||
Q_OBJECT;
|
||||
private slots:
|
||||
void splits_data() { // NOLINT
|
||||
QTest::addColumn<QString>("mark");
|
||||
QTest::addColumn<QString>("buffer"); // max that can go in the buffer
|
||||
QTest::addColumn<QString>("incoming"); // data that has to be tested on the end in one go
|
||||
QTest::addColumn<QList<QString>>("results");
|
||||
QTest::addColumn<QString>("remainder");
|
||||
|
||||
// NOLINTBEGIN
|
||||
// clang-format off
|
||||
QTest::addRow("simple") << "-"
|
||||
<< "foo" << "-"
|
||||
<< QList<QString>("foo") << "";
|
||||
|
||||
QTest::addRow("multiple") << "-"
|
||||
<< "foo" << "-bar-baz-"
|
||||
<< QList<QString>({ "foo", "bar", "baz" }) << "";
|
||||
|
||||
QTest::addRow("incomplete") << "-"
|
||||
<< "foo" << "-bar-baz"
|
||||
<< QList<QString>({ "foo", "bar" }) << "baz";
|
||||
|
||||
QTest::addRow("longsplit") << "12345"
|
||||
<< "foo1234" << "5bar12345"
|
||||
<< QList<QString>({ "foo", "bar" }) << "";
|
||||
|
||||
QTest::addRow("longsplit-incomplete") << "123"
|
||||
<< "foo12" << "3bar123baz"
|
||||
<< QList<QString>({ "foo", "bar" }) << "baz";
|
||||
// clang-format on
|
||||
// NOLINTEND
|
||||
}
|
||||
|
||||
void splits() { // NOLINT
|
||||
// NOLINTBEGIN
|
||||
QFETCH(QString, mark);
|
||||
QFETCH(QString, buffer);
|
||||
QFETCH(QString, incoming);
|
||||
QFETCH(QList<QString>, results);
|
||||
QFETCH(QString, remainder);
|
||||
// NOLINTEND
|
||||
|
||||
auto bufferArray = buffer.toUtf8();
|
||||
auto incomingArray = incoming.toUtf8();
|
||||
|
||||
for (auto i = 0; i <= bufferArray.length(); i++) {
|
||||
auto buffer = bufferArray.sliced(0, i);
|
||||
auto incoming = bufferArray.sliced(i);
|
||||
incoming.append(incomingArray);
|
||||
|
||||
qInfo() << "BUFFER" << QString(buffer);
|
||||
qInfo() << "INCOMING" << QString(incoming);
|
||||
|
||||
auto parser = SplitParser();
|
||||
auto spy = QSignalSpy(&parser, &DataStreamParser::read);
|
||||
|
||||
parser.setSplitMarker(mark);
|
||||
parser.parseBytes(incoming, buffer);
|
||||
|
||||
auto actualResults = QList<QString>();
|
||||
for (auto& read: spy) {
|
||||
actualResults.push_back(read[0].toString());
|
||||
}
|
||||
|
||||
qInfo() << "EXPECTED RESULTS" << results;
|
||||
qInfo() << "ACTUAL RESULTS" << actualResults;
|
||||
qInfo() << "EXPECTED REMAINDER" << remainder;
|
||||
qInfo() << "ACTUAL REMAINDER" << remainder;
|
||||
QCOMPARE(actualResults, results);
|
||||
QCOMPARE(buffer, remainder);
|
||||
}
|
||||
}
|
||||
|
||||
void initBuffer() { // NOLINT
|
||||
auto parser = SplitParser();
|
||||
auto spy = QSignalSpy(&parser, &DataStreamParser::read);
|
||||
|
||||
auto buf = QString("foo-bar-baz").toUtf8();
|
||||
auto expected = QList<QString>({"foo", "bar"});
|
||||
|
||||
parser.setSplitMarker("-");
|
||||
parser.parseBytes(buf, buf);
|
||||
|
||||
auto actualResults = QList<QString>();
|
||||
for (auto& read: spy) {
|
||||
actualResults.push_back(read[0].toString());
|
||||
}
|
||||
|
||||
qInfo() << "EXPECTED RESULTS" << expected;
|
||||
qInfo() << "ACTUAL RESULTS" << actualResults;
|
||||
QCOMPARE(actualResults, expected);
|
||||
QCOMPARE(buf, "baz");
|
||||
}
|
||||
};
|
||||
|
||||
QTEST_MAIN(TestSplitParser)
|
||||
#include "datastream.moc"
|
Loading…
Add table
Add a link
Reference in a new issue