initial commit

This commit is contained in:
2026-09-21 13:41:40 +09:00
commit 855c7328df
411 changed files with 85352 additions and 0 deletions
+185
View File
@@ -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"