aboutsummaryrefslogtreecommitdiffstats
path: root/tests
diff options
context:
space:
mode:
Diffstat (limited to 'tests')
-rw-r--r--tests/CMakeLists.txt1
-rw-r--r--tests/auto/blackbox/testdata/lsp/lsp.qbs10
-rw-r--r--tests/auto/blackbox/testdata/lsp/modules/Prefix/m1/m1.qbs2
-rw-r--r--tests/auto/blackbox/testdata/lsp/modules/Prefix/m2/m2.qbs2
-rw-r--r--tests/auto/blackbox/testdata/lsp/modules/Prefix/m3/m3.qbs2
-rw-r--r--tests/auto/blackbox/testdata/lsp/modules/m/m.qbs2
-rw-r--r--tests/auto/blackbox/tst_blackbox.cpp104
-rw-r--r--tests/auto/blackbox/tst_blackbox.h2
-rw-r--r--tests/auto/language/tst_language.cpp8
-rw-r--r--tests/lspclient/CMakeLists.txt8
-rw-r--r--tests/lspclient/lspclient.cpp315
-rw-r--r--tests/lspclient/lspclient.qbs10
-rw-r--r--tests/tests.qbs1
13 files changed, 457 insertions, 10 deletions
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index 3a868fbac..55dcad789 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -1,3 +1,4 @@
add_subdirectory(auto)
add_subdirectory(benchmarker)
add_subdirectory(fuzzy-test)
+add_subdirectory(lspclient)
diff --git a/tests/auto/blackbox/testdata/lsp/lsp.qbs b/tests/auto/blackbox/testdata/lsp/lsp.qbs
new file mode 100644
index 000000000..2e30ad930
--- /dev/null
+++ b/tests/auto/blackbox/testdata/lsp/lsp.qbs
@@ -0,0 +1,10 @@
+Project {
+ Product {
+ name: "dep"
+ Depends { name: "m" }
+ Depends { name: "Prefix"; submodules: ["m1", "m2", "m3"] }
+ }
+ Product {
+ Depends { name: "dep" }
+ }
+}
diff --git a/tests/auto/blackbox/testdata/lsp/modules/Prefix/m1/m1.qbs b/tests/auto/blackbox/testdata/lsp/modules/Prefix/m1/m1.qbs
new file mode 100644
index 000000000..84957060c
--- /dev/null
+++ b/tests/auto/blackbox/testdata/lsp/modules/Prefix/m1/m1.qbs
@@ -0,0 +1,2 @@
+Module {
+}
diff --git a/tests/auto/blackbox/testdata/lsp/modules/Prefix/m2/m2.qbs b/tests/auto/blackbox/testdata/lsp/modules/Prefix/m2/m2.qbs
new file mode 100644
index 000000000..84957060c
--- /dev/null
+++ b/tests/auto/blackbox/testdata/lsp/modules/Prefix/m2/m2.qbs
@@ -0,0 +1,2 @@
+Module {
+}
diff --git a/tests/auto/blackbox/testdata/lsp/modules/Prefix/m3/m3.qbs b/tests/auto/blackbox/testdata/lsp/modules/Prefix/m3/m3.qbs
new file mode 100644
index 000000000..84957060c
--- /dev/null
+++ b/tests/auto/blackbox/testdata/lsp/modules/Prefix/m3/m3.qbs
@@ -0,0 +1,2 @@
+Module {
+}
diff --git a/tests/auto/blackbox/testdata/lsp/modules/m/m.qbs b/tests/auto/blackbox/testdata/lsp/modules/m/m.qbs
new file mode 100644
index 000000000..84957060c
--- /dev/null
+++ b/tests/auto/blackbox/testdata/lsp/modules/m/m.qbs
@@ -0,0 +1,2 @@
+Module {
+}
diff --git a/tests/auto/blackbox/tst_blackbox.cpp b/tests/auto/blackbox/tst_blackbox.cpp
index 1b4341f12..575f1a3cf 100644
--- a/tests/auto/blackbox/tst_blackbox.cpp
+++ b/tests/auto/blackbox/tst_blackbox.cpp
@@ -6321,6 +6321,102 @@ static QJsonObject getNextSessionPacket(QProcess &session, QByteArray &data)
return QJsonDocument::fromJson(QByteArray::fromBase64(msg)).object();
}
+static void sendSessionPacket(QProcess &sessionProc, const QJsonObject &message)
+{
+ const QByteArray data = QJsonDocument(message).toJson().toBase64();
+ sessionProc.write("qbsmsg:");
+ sessionProc.write(QByteArray::number(data.length()));
+ sessionProc.write("\n");
+ sessionProc.write(data);
+}
+
+void TestBlackbox::qbsLanguageServer_data()
+{
+ QTest::addColumn<QString>("request");
+ QTest::addColumn<QString>("location");
+ QTest::addColumn<QString>("expectedReply");
+
+ QTest::addRow("follow to module") << "--goto-def"
+ << (testDataDir + "/lsp/lsp.qbs:4:9")
+ << (testDataDir + "/lsp/modules/m/m.qbs:1:1");
+ QTest::addRow("follow to submodules")
+ << "--goto-def"
+ << (testDataDir + "/lsp/lsp.qbs:5:35")
+ << ((testDataDir + "/lsp/modules/Prefix/m1/m1.qbs:1:1\n")
+ + (testDataDir + "/lsp/modules/Prefix/m2/m2.qbs:1:1\n")
+ + (testDataDir + "/lsp/modules/Prefix/m3/m3.qbs:1:1"));
+ QTest::addRow("follow to product") << "--goto-def"
+ << (testDataDir + "/lsp/lsp.qbs:8:19")
+ << (testDataDir + "/lsp/lsp.qbs:2:5");
+}
+
+void TestBlackbox::qbsLanguageServer()
+{
+ QFETCH(QString, request);
+ QFETCH(QString, location);
+ QFETCH(QString, expectedReply);
+
+ QDir::setCurrent(testDataDir + "/lsp");
+ QProcess sessionProc;
+ sessionProc.start(qbsExecutableFilePath, QStringList("session"));
+
+ QVERIFY(sessionProc.waitForStarted());
+
+ QByteArray incomingData;
+
+ // Wait for and verify hello packet.
+ QJsonObject receivedMessage = getNextSessionPacket(sessionProc, incomingData);
+ const QString socketPath = receivedMessage.value("lsp-socket").toString();
+ QVERIFY(!socketPath.isEmpty());
+
+ // Resolve project.
+ QJsonObject resolveMessage;
+ resolveMessage.insert("type", "resolve-project");
+ resolveMessage.insert("top-level-profile", profileName());
+ resolveMessage.insert("project-file-path", QDir::currentPath() + "/lsp.qbs");
+ resolveMessage.insert("build-root", QDir::currentPath());
+ resolveMessage.insert("settings-directory", settings()->baseDirectory());
+ sendSessionPacket(sessionProc, resolveMessage);
+ bool receivedReply = false;
+ while (!receivedReply) {
+ receivedMessage = getNextSessionPacket(sessionProc, incomingData);
+ QVERIFY(!receivedMessage.isEmpty());
+ const QString msgType = receivedMessage.value("type").toString();
+ if (msgType == "project-resolved") {
+ receivedReply = true;
+ const QJsonObject error = receivedMessage.value("error").toObject();
+ if (!error.isEmpty())
+ qDebug() << error;
+ QVERIFY(error.isEmpty());
+ }
+ }
+ QVERIFY(receivedReply);
+
+ // Employ client app to send request.
+ QProcess lspClient;
+ const QFileInfo qbsFileInfo(qbsExecutableFilePath);
+ const QString clientFilePath = HostOsInfo::appendExecutableSuffix(
+ qbsFileInfo.absolutePath() + "/qbs_lspclient");
+ lspClient.start(clientFilePath, {"--socket", socketPath, request, location});
+ QVERIFY2(lspClient.waitForStarted(), qPrintable(lspClient.errorString()));
+ QVERIFY2(lspClient.waitForFinished(), qPrintable(lspClient.errorString()));
+ QString errMsg;
+ if (lspClient.exitStatus() != QProcess::NormalExit)
+ errMsg = lspClient.errorString();
+ if (errMsg.isEmpty())
+ errMsg = QString::fromLocal8Bit(lspClient.readAllStandardError());
+ QVERIFY2(lspClient.exitCode() == 0, qPrintable(errMsg));
+ QVERIFY2(errMsg.isEmpty(), qPrintable(errMsg));
+ QString output = QString::fromUtf8(lspClient.readAllStandardOutput().trimmed());
+ output.replace("\r\n", "\n");
+ QCOMPARE(output, expectedReply);
+
+ QJsonObject quitRequest;
+ quitRequest.insert("type", "quit");
+ sendSessionPacket(sessionProc, quitRequest);
+ QVERIFY(sessionProc.waitForFinished(3000));
+}
+
void TestBlackbox::qbsSession()
{
QDir::setCurrent(testDataDir + "/qbs-session");
@@ -6337,11 +6433,7 @@ void TestBlackbox::qbsSession()
QVERIFY(sessionProc.waitForStarted());
const auto sendPacket = [&sessionProc](const QJsonObject &message) {
- const QByteArray data = QJsonDocument(message).toJson().toBase64();
- sessionProc.write("qbsmsg:");
- sessionProc.write(QByteArray::number(data.length()));
- sessionProc.write("\n");
- sessionProc.write(data);
+ sendSessionPacket(sessionProc, message);
};
static const auto envToJson = [](const QProcessEnvironment &env) {
@@ -6365,7 +6457,7 @@ void TestBlackbox::qbsSession()
// Wait for and verify hello packet.
QJsonObject receivedMessage = getNextSessionPacket(sessionProc, incomingData);
QCOMPARE(receivedMessage.value("type"), "hello");
- QCOMPARE(receivedMessage.value("api-level").toInt(), 4);
+ QCOMPARE(receivedMessage.value("api-level").toInt(), 5);
QCOMPARE(receivedMessage.value("api-compat-level").toInt(), 2);
// Resolve & verify structure
diff --git a/tests/auto/blackbox/tst_blackbox.h b/tests/auto/blackbox/tst_blackbox.h
index c662395e3..4447709f4 100644
--- a/tests/auto/blackbox/tst_blackbox.h
+++ b/tests/auto/blackbox/tst_blackbox.h
@@ -265,6 +265,8 @@ private slots:
void qbsConfigImport_data();
void qbsConfigExport();
void qbsConfigExport_data();
+ void qbsLanguageServer_data();
+ void qbsLanguageServer();
void qbsSession();
void qbsVersion();
void qtBug51237();
diff --git a/tests/auto/language/tst_language.cpp b/tests/auto/language/tst_language.cpp
index 6a29876dc..343b059c9 100644
--- a/tests/auto/language/tst_language.cpp
+++ b/tests/auto/language/tst_language.cpp
@@ -739,7 +739,7 @@ void TestLanguage::enumerateProjectProperties()
auto products = productsFromProject(project);
QCOMPARE(products.size(), 1);
auto product = products.values().front();
- auto files = product->groups.front()->allFiles();
+ auto files = product->groups.front()->files;
QCOMPARE(product->groups.size(), size_t(1));
QCOMPARE(files.size(), size_t(1));
auto fileName = FileInfo::fileName(files.front()->absoluteFilePath);
@@ -3049,7 +3049,7 @@ void TestLanguage::relaxedErrorMode()
QVERIFY(missingFile->enabled);
QCOMPARE(missingFile->groups.size(), size_t(1));
QVERIFY(missingFile->groups.front()->enabled);
- QCOMPARE(missingFile->groups.front()->allFiles().size(), size_t(2));
+ QCOMPARE(missingFile->groups.front()->files.size(), size_t(2));
const ResolvedProductConstPtr fine = productMap.value("fine");
QVERIFY(fine->enabled);
QCOMPARE(fine->allFiles().size(), size_t(1));
@@ -3445,10 +3445,10 @@ void TestLanguage::wildcards()
group = product->groups.front();
}
QVERIFY(!!group);
- QCOMPARE(group->files.size(), size_t(0));
+ QCOMPARE(group->files.size(), expected.size()); // we assume all files are wildcards
QVERIFY(!!group->wildcards);
QStringList actualFilePaths;
- for (const SourceArtifactPtr &artifact : group->wildcards->files) {
+ for (const SourceArtifactPtr &artifact : group->files) {
QString str = artifact->absoluteFilePath;
int idx = str.indexOf(m_wildcardsTestDirPath);
if (idx != -1)
diff --git a/tests/lspclient/CMakeLists.txt b/tests/lspclient/CMakeLists.txt
new file mode 100644
index 000000000..ba6f3dbdc
--- /dev/null
+++ b/tests/lspclient/CMakeLists.txt
@@ -0,0 +1,8 @@
+add_qbs_app(qbs_lspclient
+ DEPENDS
+ Qt${QT_VERSION_MAJOR}::Core
+ Qt${QT_VERSION_MAJOR}::Network
+ qbscore
+ qtclsp
+ SOURCES lspclient.cpp
+ )
diff --git a/tests/lspclient/lspclient.cpp b/tests/lspclient/lspclient.cpp
new file mode 100644
index 000000000..caf3e5916
--- /dev/null
+++ b/tests/lspclient/lspclient.cpp
@@ -0,0 +1,315 @@
+/****************************************************************************
+**
+** Copyright (C) 2023 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qbs.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include <lsp/clientcapabilities.h>
+#include <lsp/initializemessages.h>
+#include <lsp/languagefeatures.h>
+
+#include <QBuffer>
+#include <QCommandLineOption>
+#include <QCommandLineParser>
+#include <QCoreApplication>
+#include <QLocalSocket>
+
+#include <cstdlib>
+#include <iostream>
+
+enum class Command { GotoDefinition, };
+
+class LspClient : public QObject
+{
+public:
+ LspClient(Command command, const QString &socketPath, const QString &filePath,
+ int line, int column);
+ void start();
+
+private:
+ void finishWithError(const QString &msg);
+ void exit(int code);
+ void initiateProtocol();
+ void handleIncomingData();
+ void sendMessage(const lsp::JsonObject &msg);
+ void sendMessage(const lsp::JsonRpcMessage &msg);
+ void handleCurrentMessage();
+ void handleInitializeReply();
+ void sendRequest();
+ void handleResponse();
+ void sendGotoDefinitionRequest();
+ void handleGotoDefinitionResponse();
+ lsp::DocumentUri uri() const;
+ lsp::DocumentUri::PathMapper mapper() const;
+
+ const Command m_command;
+ const QString m_socketPath;
+ const QString m_filePath;
+ const int m_line;
+ const int m_column;
+ QLocalSocket m_socket;
+ QBuffer m_incomingData;
+ lsp::BaseMessage m_currentMessage;
+ QJsonObject m_messageObject;
+
+ enum class State { Inactive, Connecting, Initializing, RunningCommand }
+ m_state = State::Inactive;
+};
+
+int main(int argc, char *argv[])
+{
+ QCoreApplication app(argc, argv);
+
+ const QCommandLineOption socketOption({"socket", "s"}, "The server socket to connect to.",
+ "socket");
+ const QCommandLineOption gotoDefinitionOption(
+ {"goto-def", "g"}, "Go to definition from the specified location.");
+ QCommandLineParser parser;
+ parser.addOptions({socketOption, gotoDefinitionOption});
+ parser.addHelpOption();
+ parser.addPositionalArgument("location", "The location at which to operate.",
+ "<file>:<line>:<column>");
+ parser.process(app);
+
+ const auto complainAndExit = [&](const char *text) {
+ std::cerr << text << std::endl;
+ parser.showHelp(EXIT_FAILURE);
+ };
+
+ if (!parser.isSet(socketOption))
+ complainAndExit("Socket must be specified.");
+
+ // Initialized to suppress warning. TODO: In C++23, mark lambdas as noreturn instead.
+ Command command = Command::GotoDefinition;
+
+ if (parser.isSet(gotoDefinitionOption))
+ command = Command::GotoDefinition;
+ else
+ complainAndExit("Don't know what to do.");
+
+ if (parser.positionalArguments().size() != 1)
+ complainAndExit("Need location.");
+ const auto complainAboutLocationString = [&] { complainAndExit("Invalid location."); };
+ const QString loc = parser.positionalArguments().first();
+ int sep1 = loc.indexOf(':');
+ if (sep1 <= 0)
+ complainAboutLocationString();
+ if (qbs::Internal::HostOsInfo::isWindowsHost()) {
+ sep1 = loc.indexOf(':', sep1 + 1);
+ if (sep1 < 0)
+ complainAboutLocationString();
+ }
+ const int sep2 = loc.indexOf(':', sep1 + 1);
+ if (sep2 == -1 || sep2 == loc.size() - 1)
+ complainAboutLocationString();
+ const auto extractNumber = [&](const QString &s) {
+ bool ok;
+ const int n = s.toInt(&ok);
+ if (!ok || n <= 0)
+ complainAboutLocationString();
+ return n;
+ };
+ const int line = extractNumber(loc.mid(sep1 + 1, sep2 - sep1 - 1));
+ const int column = extractNumber(loc.mid(sep2 + 1));
+
+ LspClient client(command, parser.value(socketOption),
+ QDir::fromNativeSeparators(loc.left(sep1)), line, column);
+ QMetaObject::invokeMethod(&client, &LspClient::start, Qt::QueuedConnection);
+
+ return app.exec();
+}
+
+LspClient::LspClient(Command command, const QString &socketPath, const QString &filePath,
+ int line, int column)
+ : m_command(command), m_socketPath(socketPath), m_filePath(filePath),
+ m_line(line), m_column(column)
+{
+ connect(&m_socket, &QLocalSocket::disconnected, this, [this] {
+ finishWithError("Server disconnected unexpectedly.");
+ });
+ connect(&m_socket, &QLocalSocket::errorOccurred, this, [this] {
+ finishWithError(QString::fromLatin1("Socket error: %1").arg(m_socket.errorString()));
+ });
+ connect(&m_socket, &QLocalSocket::connected, this, &LspClient::initiateProtocol);
+ connect(&m_socket, &QLocalSocket::readyRead, this, &LspClient::handleIncomingData);
+}
+
+void LspClient::start()
+{
+ m_state = State::Connecting;
+ m_incomingData.open(QIODevice::ReadWrite | QIODevice::Append);
+ m_socket.connectToServer(m_socketPath);
+}
+
+void LspClient::finishWithError(const QString &msg)
+{
+ std::cerr << qPrintable(msg) << std::endl;
+ m_socket.disconnectFromServer();
+ exit(EXIT_FAILURE);
+}
+
+void LspClient::exit(int code)
+{
+ m_socket.disconnect(this);
+ qApp->exit(code);
+}
+
+void LspClient::initiateProtocol()
+{
+ if (m_state != State::Connecting) {
+ finishWithError(QString::fromLatin1("State should be %1, was %2.")
+ .arg(int(State::Connecting), int(m_state)));
+ return;
+ }
+ m_state = State::Initializing;
+
+ lsp::DynamicRegistrationCapabilities definitionCaps;
+ definitionCaps.setDynamicRegistration(false);
+ lsp::TextDocumentClientCapabilities docCaps;
+ docCaps.setDefinition(definitionCaps);
+ lsp::ClientCapabilities clientCaps;
+ clientCaps.setTextDocument(docCaps);
+ lsp::InitializeParams initParams;
+ initParams.setCapabilities(clientCaps);
+ sendMessage(lsp::InitializeRequest(initParams));
+}
+
+void LspClient::handleIncomingData()
+{
+ const int pos = m_incomingData.pos();
+ m_incomingData.write(m_socket.readAll());
+ m_incomingData.seek(pos);
+ QString parseError;
+ lsp::BaseMessage::parse(&m_incomingData, parseError, m_currentMessage);
+ if (!parseError.isEmpty()) {
+ return finishWithError(QString::fromLatin1("Error parsing server message: %1.")
+ .arg(parseError));
+ }
+ if (m_currentMessage.isComplete()) {
+ m_incomingData.buffer().remove(0, m_incomingData.pos());
+ m_incomingData.seek(0);
+ handleCurrentMessage();
+ m_currentMessage = {};
+ m_messageObject = {};
+ if (m_socket.state() == QLocalSocket::ConnectedState)
+ handleIncomingData();
+ }
+}
+
+void LspClient::sendMessage(const lsp::JsonObject &msg)
+{
+ sendMessage(lsp::JsonRpcMessage(msg));
+}
+
+void LspClient::sendMessage(const lsp::JsonRpcMessage &msg)
+{
+ lsp::BaseMessage baseMsg = msg.toBaseMessage();
+ m_socket.write(baseMsg.header());
+ m_socket.write(baseMsg.content);
+}
+
+void LspClient::handleCurrentMessage()
+{
+ m_messageObject = lsp::JsonRpcMessage(m_currentMessage).toJsonObject();
+ switch (m_state) {
+ case State::Inactive:
+ case State::Connecting:
+ finishWithError("Received message in non-connected state.");
+ break;
+ case State::Initializing:
+ handleInitializeReply();
+ sendRequest();
+ break;
+ case State::RunningCommand:
+ handleResponse();
+ break;
+ }
+}
+
+void LspClient::handleInitializeReply()
+{
+ lsp::ServerCapabilities serverCaps = lsp::InitializeRequest::Response(
+ m_messageObject).result().value_or(lsp::InitializeResult()).capabilities();
+ const auto defProvider = serverCaps.definitionProvider();
+ if (!defProvider)
+ return finishWithError("Expected definition provider.");
+ const bool * const defProviderValue = std::get_if<bool>(&(*defProvider));
+ if (!defProviderValue || !*defProviderValue)
+ return finishWithError("Expected definition provider.");
+ sendMessage(lsp::InitializeNotification(lsp::InitializedParams()));
+}
+
+void LspClient::sendRequest()
+{
+ m_state = State::RunningCommand;
+ switch (m_command) {
+ case Command::GotoDefinition:
+ return sendGotoDefinitionRequest();
+ }
+}
+
+void LspClient::handleResponse()
+{
+ switch (m_command) {
+ case Command::GotoDefinition:
+ return handleGotoDefinitionResponse();
+ }
+}
+
+void LspClient::sendGotoDefinitionRequest()
+{
+ const lsp::TextDocumentIdentifier doc(uri());
+ const lsp::Position pos(m_line - 1, m_column - 1);
+ sendMessage(lsp::GotoDefinitionRequest({doc, pos}));
+}
+
+void LspClient::handleGotoDefinitionResponse()
+{
+ const lsp::GotoResult result(lsp::GotoDefinitionRequest::Response(m_messageObject)
+ .result().value_or(lsp::GotoResult()));
+ QList<lsp::Utils::Link> links;
+ const auto loc2Link = [this](const lsp::Location &loc) { return loc.toLink(mapper()); };
+ if (const auto loc = std::get_if<lsp::Location>(&result)) {
+ links << loc2Link(*loc);
+ } else if (const auto locs = std::get_if<QList<lsp::Location>>(&result)) {
+ links = lsp::Utils::transform(*locs, loc2Link);
+ }
+ for (const lsp::Utils::Link &link : std::as_const(links)) {
+ std::cout << qPrintable(link.targetFilePath) << ':' << link.targetLine << ':'
+ << (link.targetColumn + 1) << std::endl;
+ }
+ exit(EXIT_SUCCESS);
+}
+
+lsp::DocumentUri LspClient::uri() const
+{
+ return lsp::DocumentUri::fromFilePath(lsp::Utils::FilePath::fromUserInput(m_filePath),
+ mapper());
+}
+
+lsp::DocumentUri::PathMapper LspClient::mapper() const
+{
+ return [](const lsp::Utils::FilePath &fp) { return fp; };
+}
diff --git a/tests/lspclient/lspclient.qbs b/tests/lspclient/lspclient.qbs
new file mode 100644
index 000000000..c89b9e0c0
--- /dev/null
+++ b/tests/lspclient/lspclient.qbs
@@ -0,0 +1,10 @@
+QbsApp {
+ name: "qbs_lspclient"
+
+ Depends { name: "qtclsp" }
+ Depends { name: "Qt.network" }
+
+ cpp.defines: base.filter(function(d) { return d !== "QT_NO_CAST_FROM_ASCII"; })
+
+ files: "lspclient.cpp"
+}
diff --git a/tests/tests.qbs b/tests/tests.qbs
index 3cc757c8a..54bc80715 100644
--- a/tests/tests.qbs
+++ b/tests/tests.qbs
@@ -5,6 +5,7 @@ Project {
"auto/auto.qbs",
"benchmarker/benchmarker.qbs",
"fuzzy-test/fuzzy-test.qbs",
+ "lspclient/lspclient.qbs",
]
AutotestRunner {