initial commit
This commit is contained in:
@@ -0,0 +1,185 @@
|
||||
#include "common/ipc.h"
|
||||
#include "common/windows_pipe.h"
|
||||
|
||||
#include <QElapsedTimer>
|
||||
#include <QSignalSpy>
|
||||
#include <QtTest>
|
||||
#ifdef Q_OS_WIN
|
||||
#ifndef NOMINMAX
|
||||
#define NOMINMAX
|
||||
#endif
|
||||
#include <windows.h>
|
||||
#endif
|
||||
|
||||
using namespace docview;
|
||||
|
||||
class WindowsPipeTest : public QObject {
|
||||
Q_OBJECT
|
||||
private slots:
|
||||
void invalidHandlesAreRejected() {
|
||||
WindowsPipeDevice device;
|
||||
QString error;
|
||||
QVERIFY(!device.adopt(0, 0, &error));
|
||||
QVERIFY(!device.isOpen());
|
||||
QVERIFY(!error.isEmpty());
|
||||
#ifndef Q_OS_WIN
|
||||
QVERIFY(!createWindowsPipePair(&error));
|
||||
QVERIFY(error.startsWith("E_SANDBOX_UNAVAILABLE"));
|
||||
#endif
|
||||
}
|
||||
|
||||
void duplexFramingAndPartialTransfers() {
|
||||
#ifdef Q_OS_WIN
|
||||
QString error;
|
||||
auto pair = createWindowsPipePair(&error);
|
||||
QVERIFY2(pair.has_value(), qPrintable(error));
|
||||
WindowsPipeDevice broker, worker;
|
||||
QVERIFY(broker.adopt(pair->broker.takeReadHandle(), pair->broker.takeWriteHandle(), &error));
|
||||
QVERIFY(worker.adopt(pair->worker.takeReadHandle(), pair->worker.takeWriteHandle(), &error));
|
||||
IpcChannel parent(&broker), child(&worker);
|
||||
QSignalSpy childReceived(&child, &IpcChannel::messageReceived);
|
||||
QSignalSpy parentReceived(&parent, &IpcChannel::messageReceived);
|
||||
QSignalSpy parentErrors(&parent, &IpcChannel::protocolError);
|
||||
QSignalSpy childErrors(&child, &IpcChannel::protocolError);
|
||||
const QCborMap request{{"protocolVersion", 1}, {"sessionId", "native-pipe"},
|
||||
{"requestId", 1}, {"generation", 1},
|
||||
{"operation", "render"}, { "payload", QCborMap { {"page", 0} } }};
|
||||
QVERIFY(parent.send(request));
|
||||
QTRY_COMPARE_WITH_TIMEOUT(childReceived.size(), 1, 5000);
|
||||
QCOMPARE(qvariant_cast<QCborMap>(childReceived[0][0]), request);
|
||||
auto reply = request;
|
||||
reply.remove(QStringLiteral("payload"));
|
||||
const QByteArray pixels(6 * 1024 * 1024, char(0xa5));
|
||||
reply.insert(QStringLiteral("result"), QCborMap{{ "data", pixels }});
|
||||
QVERIFY(child.send(reply));
|
||||
QTRY_COMPARE_WITH_TIMEOUT(parentReceived.size(), 1, 15000);
|
||||
QCOMPARE(qvariant_cast<QCborMap>(parentReceived[0][0]), reply);
|
||||
QTRY_COMPARE(worker.bytesToWrite(), qint64(0));
|
||||
QCOMPARE(parentErrors.size(), 0);
|
||||
QCOMPARE(childErrors.size(), 0);
|
||||
QCOMPARE(broker.bytesAvailable(), qint64(0));
|
||||
#else
|
||||
QSKIP("Native Windows pipe execution requires Windows; Linux stub is not a native pass");
|
||||
#endif
|
||||
}
|
||||
|
||||
void boundedWriteDoesNotWaitForReader() {
|
||||
#ifdef Q_OS_WIN
|
||||
auto pair = createWindowsPipePair();
|
||||
QVERIFY(pair.has_value());
|
||||
WindowsPipeDevice broker;
|
||||
QVERIFY(broker.adopt(pair->broker.takeReadHandle(), pair->broker.takeWriteHandle()));
|
||||
// Keep the other endpoint open without issuing a read. A blocking pipe
|
||||
// write would hang here after its kernel buffer fills.
|
||||
const QByteArray bytes(WindowsPipeDevice::MaxWriteBuffer, 'x');
|
||||
QElapsedTimer elapsed;
|
||||
elapsed.start();
|
||||
QCOMPARE(broker.write(bytes), qint64(bytes.size()));
|
||||
QVERIFY2(elapsed.elapsed() < 500, "The GUI write path waited for a pipe reader");
|
||||
QCOMPARE(broker.bytesToWrite(), WindowsPipeDevice::MaxWriteBuffer);
|
||||
QCOMPARE(broker.write("x", 1), qint64(-1));
|
||||
QVERIFY(broker.bytesToWrite() <= WindowsPipeDevice::MaxWriteBuffer);
|
||||
WindowsPipeDevice worker;
|
||||
QVERIFY(worker.adopt(pair->worker.takeReadHandle(), pair->worker.takeWriteHandle()));
|
||||
qint64 received = 0;
|
||||
connect(&worker, &QIODevice::readyRead, this, [&] { received += worker.readAll().size(); });
|
||||
QTRY_COMPARE_WITH_TIMEOUT(received, qint64(bytes.size()), 15000);
|
||||
QTRY_COMPARE(broker.bytesToWrite(), qint64(0));
|
||||
#else
|
||||
QSKIP("Native Windows pipe execution requires Windows; Linux stub is not a native pass");
|
||||
#endif
|
||||
}
|
||||
|
||||
void workerPipeCannotAcquireBrokerImpersonationToken() {
|
||||
#ifdef Q_OS_WIN
|
||||
auto pair = createWindowsPipePair();
|
||||
QVERIFY(pair.has_value());
|
||||
const auto workerRead = reinterpret_cast<HANDLE>(pair->worker.readHandle());
|
||||
WindowsPipeDevice broker, worker;
|
||||
QVERIFY(broker.adopt(pair->broker.takeReadHandle(), pair->broker.takeWriteHandle()));
|
||||
QVERIFY(worker.adopt(pair->worker.takeReadHandle(), pair->worker.takeWriteHandle()));
|
||||
QCOMPARE(broker.write("probe", 5), qint64(5));
|
||||
QTRY_COMPARE_WITH_TIMEOUT(worker.bytesAvailable(), qint64(5), 5000);
|
||||
QCOMPARE(worker.readAll(), QByteArray("probe"));
|
||||
|
||||
// Exercise the server end actually handed to a worker. The test runs
|
||||
// outside LPAC to test the pipe's own SQOS boundary independently of
|
||||
// any additional token restrictions imposed by the OS sandbox.
|
||||
const bool impersonated = ImpersonateNamedPipeClient(workerRead);
|
||||
HANDLE token = nullptr;
|
||||
const bool opened = impersonated && OpenThreadToken(GetCurrentThread(), TOKEN_QUERY, TRUE, &token);
|
||||
const DWORD tokenError = GetLastError();
|
||||
if (token)
|
||||
CloseHandle(token);
|
||||
const bool reverted = !impersonated || RevertToSelf();
|
||||
QVERIFY(reverted);
|
||||
QVERIFY(impersonated);
|
||||
QVERIFY(!opened);
|
||||
QCOMPARE(tokenError, DWORD(ERROR_CANT_OPEN_ANONYMOUS));
|
||||
#else
|
||||
QSKIP("Native Windows pipe execution requires Windows; Linux stub is not a native pass");
|
||||
#endif
|
||||
}
|
||||
|
||||
void closeCancelsPendingIoWithoutStaleSignals() {
|
||||
#ifdef Q_OS_WIN
|
||||
DWORD before = 0;
|
||||
QVERIFY(GetProcessHandleCount(GetCurrentProcess(), &before));
|
||||
for (int iteration = 0; iteration < 16; ++iteration) {
|
||||
auto pair = createWindowsPipePair();
|
||||
QVERIFY(pair.has_value());
|
||||
WindowsPipeDevice broker;
|
||||
QVERIFY(broker.adopt(pair->broker.takeReadHandle(), pair->broker.takeWriteHandle()));
|
||||
QSignalSpy reads(&broker, &QIODevice::readyRead);
|
||||
QSignalSpy writes(&broker, &QIODevice::bytesWritten);
|
||||
QSignalSpy errors(&broker, &WindowsPipeDevice::transportError);
|
||||
QCoreApplication::processEvents(); // Arm an overlapped read.
|
||||
QCOMPARE(broker.write(QByteArray(1024 * 1024, 'x')), qint64(1024 * 1024));
|
||||
QElapsedTimer elapsed;
|
||||
elapsed.start();
|
||||
broker.close();
|
||||
QVERIFY2(elapsed.elapsed() < 500, "close waited on a pipe operation on the GUI thread");
|
||||
pair->worker.close();
|
||||
QVERIFY(!broker.isOpen());
|
||||
QCOMPARE(broker.bytesToWrite(), qint64(0));
|
||||
QTest::qWait(5);
|
||||
QCOMPARE(reads.size(), 0);
|
||||
QCOMPARE(writes.size(), 0);
|
||||
QCOMPARE(errors.size(), 0);
|
||||
}
|
||||
auto handleCount = [] {
|
||||
QCoreApplication::sendPostedEvents(nullptr, QEvent::DeferredDelete);
|
||||
DWORD count = 0;
|
||||
return GetProcessHandleCount(GetCurrentProcess(), &count) ? count : DWORD(-1);
|
||||
};
|
||||
// Qt may initialize process-wide wait infrastructure on its first use.
|
||||
QTRY_VERIFY_WITH_TIMEOUT(handleCount() <= before + 8, 5000);
|
||||
#else
|
||||
QSKIP("Native Windows pipe execution requires Windows; Linux stub is not a native pass");
|
||||
#endif
|
||||
}
|
||||
|
||||
void disconnectFailsOnce() {
|
||||
#ifdef Q_OS_WIN
|
||||
auto pair = createWindowsPipePair();
|
||||
QVERIFY(pair.has_value());
|
||||
WindowsPipeDevice broker;
|
||||
QVERIFY(broker.adopt(pair->broker.takeReadHandle(), pair->broker.takeWriteHandle()));
|
||||
QSignalSpy errors(&broker, &WindowsPipeDevice::transportError);
|
||||
QSignalSpy disconnected(&broker, &WindowsPipeDevice::disconnected);
|
||||
pair->worker.close();
|
||||
QTRY_COMPARE_WITH_TIMEOUT(errors.size(), 1, 5000);
|
||||
QCOMPARE(disconnected.size(), 1);
|
||||
QVERIFY(!broker.isOpen());
|
||||
broker.close();
|
||||
QTest::qWait(20);
|
||||
QCOMPARE(errors.size(), 1);
|
||||
QCOMPARE(disconnected.size(), 1);
|
||||
#else
|
||||
QSKIP("Native Windows pipe execution requires Windows; Linux stub is not a native pass");
|
||||
#endif
|
||||
}
|
||||
};
|
||||
|
||||
QTEST_GUILESS_MAIN(WindowsPipeTest)
|
||||
#include "test_windows_pipe.moc"
|
||||
Reference in New Issue
Block a user