diff --git a/src/backends/julia/juliaexpression.cpp b/src/backends/julia/juliaexpression.cpp index 854edcda..fdf707c3 100644 --- a/src/backends/julia/juliaexpression.cpp +++ b/src/backends/julia/juliaexpression.cpp @@ -1,114 +1,114 @@ /* This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. --- Copyright (C) 2016 Ivan Lakhtanov */ #include "juliaexpression.h" #include #include #include "settings.h" #include "juliasession.h" #include "juliakeywords.h" #include "textresult.h" #include "imageresult.h" JuliaExpression::JuliaExpression(Cantor::Session *session, bool internal) : Cantor::Expression(session, internal) { } void JuliaExpression::evaluate() { setStatus(Cantor::Expression::Computing); auto juliaSession = static_cast(session()); - juliaSession->runExpression(this); + juliaSession->enqueueExpression(this); } QString JuliaExpression::internalCommand() { QString cmd = command(); auto juliaSession = static_cast(session()); // Plots integration m_plot_filename.clear(); if (juliaSession->integratePlots() && checkPlotShowingCommands()) { // Simply add plot saving command to the end of execution QStringList inlinePlotFormats; inlinePlotFormats << QLatin1String("svg"); inlinePlotFormats << QLatin1String("eps"); inlinePlotFormats << QLatin1String("png"); auto inlinePlotFormat = inlinePlotFormats[JuliaSettings::inlinePlotFormat()]; m_plot_filename = QDir::tempPath() + QString::fromLatin1("/cantor-julia-export-%1.%2") .arg(QUuid::createUuid().toString()).arg(inlinePlotFormat); QString saveFigCommand = QString::fromLatin1("\nGR.savefig(\"%1\")\n").arg(m_plot_filename); cmd = cmd.append(saveFigCommand); } return cmd; } void JuliaExpression::finalize() { auto juliaSession = static_cast(session()); setErrorMessage( juliaSession->getError() .replace(QLatin1String("\n"), QLatin1String("
")) ); if (juliaSession->getWasException()) { setResult(new Cantor::TextResult(juliaSession->getOutput())); setStatus(Cantor::Expression::Error); } else { if (!m_plot_filename.isEmpty() && QFileInfo(m_plot_filename).exists()) { // If we have plot in result, show it setResult( new Cantor::ImageResult(QUrl::fromLocalFile(m_plot_filename))); QDir().remove(m_plot_filename); } else { const QString& output = juliaSession->getOutput(); if (!output.isEmpty()) setResult(new Cantor::TextResult(output)); } setStatus(Cantor::Expression::Done); } } void JuliaExpression::interrupt() { setStatus(Cantor::Expression::Interrupted); } bool JuliaExpression::checkPlotShowingCommands() { for (auto showingCommand : JuliaKeywords::instance()->plotShowingCommands()) { if (command().contains(showingCommand + QLatin1String("("))) { return true; } } return false; } diff --git a/src/backends/julia/juliasession.cpp b/src/backends/julia/juliasession.cpp index 431d899a..df0b02b3 100644 --- a/src/backends/julia/juliasession.cpp +++ b/src/backends/julia/juliasession.cpp @@ -1,243 +1,248 @@ /* This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. --- Copyright (C) 2016 Ivan Lakhtanov */ #include "juliasession.h" #include #include #include #include #include #include "defaultvariablemodel.h" #include "juliaexpression.h" #include "settings.h" #include "juliahighlighter.h" #include "juliakeywords.h" #include "juliavariablemodel.h" #include "juliaextensions.h" #include "juliabackend.h" #include "juliacompletionobject.h" #include using namespace Cantor; JuliaSession::JuliaSession(Cantor::Backend *backend) : Session(backend) , m_process(nullptr) , m_interface(nullptr) - , m_currentExpression(nullptr) , m_variableModel(new JuliaVariableModel(this)) , m_needUpdate(false) { } void JuliaSession::login() { emit loginStarted(); if (m_process) { m_process->deleteLater(); } m_process = new KProcess(this); m_process->setOutputChannelMode(KProcess::SeparateChannels); (*m_process) << QStandardPaths::findExecutable(QLatin1String("cantor_juliaserver")); m_process->start(); m_process->waitForStarted(); m_process->waitForReadyRead(); QTextStream stream(m_process->readAllStandardOutput()); QString readyStatus = QLatin1String("ready"); while (m_process->state() == QProcess::Running) { const QString &rl = stream.readLine(); if (rl == readyStatus) { break; } } if (!QDBusConnection::sessionBus().isConnected()) { qWarning() << "Can't connect to the D-Bus session bus.\n" "To start it, run: eval `dbus-launch --auto-syntax`"; return; } const QString &serviceName = QString::fromLatin1("org.kde.Cantor.Julia-%1").arg(m_process->pid()); m_interface = new QDBusInterface( serviceName, QString::fromLatin1("/"), QString(), QDBusConnection::sessionBus() ); if (!m_interface->isValid()) { qWarning() << QDBusConnection::sessionBus().lastError().message(); return; } m_interface->call( QString::fromLatin1("login"), JuliaSettings::self()->replPath().path() ); m_variableModel->setJuliaServer(m_interface); m_variableModel->update(); // Plots integration if (integratePlots()) { runJuliaCommand( QLatin1String("import GR; ENV[\"GKS_WSTYPE\"] = \"nul\"") ); } changeStatus(Session::Done); emit loginDone(); qDebug() << "login to julia " << JULIA_VERSION_STRING << "done"; } void JuliaSession::logout() { m_process->terminate(); m_variableModel->clearVariables(); changeStatus(Status::Disable); } void JuliaSession::interrupt() { + if (expressionQueue().isEmpty()) + return; + if (m_process->pid()) { m_process->kill(); } - for (Cantor::Expression *e : m_runningExpressions) { - e->interrupt(); - } + qDebug()<<"interrupting " << expressionQueue().first()->command(); + foreach (Cantor::Expression* expression, expressionQueue()) + expression->setStatus(Cantor::Expression::Interrupted); + expressionQueue().clear(); - m_runningExpressions.clear(); changeStatus(Cantor::Session::Done); } Cantor::Expression *JuliaSession::evaluateExpression( const QString &cmd, Cantor::Expression::FinishingBehavior behave, bool internal) { JuliaExpression *expr = new JuliaExpression(this, internal); - changeStatus(Cantor::Session::Running); - expr->setFinishingBehavior(behave); expr->setCommand(cmd); expr->evaluate(); return expr; } Cantor::CompletionObject *JuliaSession::completionFor( const QString &command, int index) { return new JuliaCompletionObject(command, index, this); } QSyntaxHighlighter *JuliaSession::syntaxHighlighter(QObject *parent) { return new JuliaHighlighter(parent, this); } void JuliaSession::runJuliaCommand(const QString &command) const { m_interface->call(QLatin1String("runJuliaCommand"), command); } void JuliaSession::runJuliaCommandAsync(const QString &command) { m_interface->callWithCallback( QLatin1String("runJuliaCommand"), {command}, this, SLOT(onResultReady()) ); } void JuliaSession::onResultReady() { - m_currentExpression->finalize(); - m_needUpdate |= !m_currentExpression->isInternal(); - m_runningExpressions.removeAll(m_currentExpression); + JuliaExpression* expr = static_cast(expressionQueue().takeFirst()); + expr->finalize(); + m_needUpdate |= !expr->isInternal(); - if(m_needUpdate) + if (expressionQueue().isEmpty()) { - m_variableModel->update(); - m_needUpdate = false; + if(m_needUpdate) + { + m_variableModel->update(); + m_needUpdate = false; + } + changeStatus(Cantor::Session::Done); } - - changeStatus(Cantor::Session::Done); + else + runFirstExpression(); } -void JuliaSession::runExpression(JuliaExpression *expr) +void JuliaSession::runFirstExpression() { - m_runningExpressions.append(expr); - m_currentExpression = expr; + Cantor::Expression* expr = expressionQueue().first(); + expr->setStatus(Cantor::Expression::Computing); + runJuliaCommandAsync(expr->internalCommand()); } QString JuliaSession::getStringFromServer(const QString &method) { const QDBusReply &reply = m_interface->call(method); return (reply.isValid() ? reply.value() : reply.error().message()); } QString JuliaSession::getOutput() { return getStringFromServer(QLatin1String("getOutput")); } QString JuliaSession::getError() { return getStringFromServer(QLatin1String("getError")); } bool JuliaSession::getWasException() { const QDBusReply &reply = m_interface->call(QLatin1String("getWasException")); return reply.isValid() && reply.value(); } Cantor::DefaultVariableModel* JuliaSession::variableModel() const { return m_variableModel; } bool JuliaSession::integratePlots() { return JuliaSettings::integratePlots(); } diff --git a/src/backends/julia/juliasession.h b/src/backends/julia/juliasession.h index e98a1a51..843c0760 100644 --- a/src/backends/julia/juliasession.h +++ b/src/backends/julia/juliasession.h @@ -1,165 +1,156 @@ /* This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. --- Copyright (C) 2016 Ivan Lakhtanov */ #pragma once #include #include #include "session.h" class JuliaExpression; class JuliaCompletionObject; class JuliaVariableModel; class KProcess; class QDBusInterface; namespace Cantor { class DefaultVariableModel; } /** * Implements a Cantor session for the Julia backend * * It communicates through DBus interface with JuliaServer */ class JuliaSession: public Cantor::Session { Q_OBJECT public: /** * Constructs session * * @param backend owning backend */ explicit JuliaSession(Cantor::Backend *backend); /** * @see Cantor::Session::login */ void login() override; /** * @see Cantor::Session::logout */ void logout() override; /** * @see Cantor::Session::interrupt */ void interrupt() override; /** * @see Cantor::Session::evaluateExpression */ Cantor::Expression *evaluateExpression( const QString &command, Cantor::Expression::FinishingBehavior behave = Cantor::Expression::FinishingBehavior::DoNotDelete, bool internal = false) override; /** * @see Cantor::Session::completionFor */ Cantor::CompletionObject *completionFor( const QString &cmd, int index = -1) override; /** * @see Cantor::Session::syntaxHighlighter */ QSyntaxHighlighter *syntaxHighlighter(QObject *parent) override; /** * @see Cantor::Session::variableModel */ Cantor::DefaultVariableModel *variableModel() const override; /** * @return indicator if config says to integrate plots into worksheet */ bool integratePlots(); private Q_SLOTS: /** * Called when async call to JuliaServer is finished */ void onResultReady(); private: KProcess *m_process; //< process to run JuliaServer inside QDBusInterface *m_interface; //< interface to JuliaServer - /// Expressions running at the moment - QList m_runningExpressions; - JuliaExpression *m_currentExpression; //< current expression - /// Variable management model JuliaVariableModel *m_variableModel; bool m_needUpdate; /// Cache to speedup modules whos calls QMap m_whos_cache; friend JuliaExpression; friend JuliaCompletionObject; - /** - * Runs Julia expression - * - * @param expression expression to run - */ - void runExpression(JuliaExpression *expression); + void runFirstExpression() override; /** * Runs Julia piece of code in synchronous mode * * @param command command to execute */ void runJuliaCommand(const QString &command) const; /** * Runs Julia piece of code in asynchronous mode. When finished * onResultReady is called * * @param command command to execute */ void runJuliaCommandAsync(const QString &command); /** * Helper method to get QString returning function result * * @param method DBus method to call * @return result of the method */ QString getStringFromServer(const QString &method); /** * @return stdout of the last executed command */ QString getOutput(); /** * @return stderr of the last executed command */ QString getError(); /** * @return indicator of exception occurred during the last command execution */ bool getWasException(); }; diff --git a/src/backends/julia/testjulia.cpp b/src/backends/julia/testjulia.cpp index b5c233ef..4f379b00 100644 --- a/src/backends/julia/testjulia.cpp +++ b/src/backends/julia/testjulia.cpp @@ -1,290 +1,310 @@ /* * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. * * --- * Copyright (C) 2016 Ivan Lakhtanov */ #include "testjulia.h" #include "session.h" #include "backend.h" #include "expression.h" #include "result.h" #include "textresult.h" #include "imageresult.h" #include "defaultvariablemodel.h" #include "completionobject.h" QString TestJulia::backendName() { return QLatin1String("julia"); } void TestJulia::testOneLine() { Cantor::Expression *e = evalExp(QLatin1String("2 + 3")); QVERIFY(e != nullptr); QCOMPARE(e->status(), Cantor::Expression::Done); QVERIFY(e->result()->type() == Cantor::TextResult::Type); QCOMPARE(e->result()->data().toString(), QLatin1String("5")); QVERIFY(e->errorMessage().isEmpty()); } void TestJulia::testOneLineWithPrint() { Cantor::Expression *e = evalExp(QLatin1String("print(2 + 3)")); QVERIFY(e != nullptr); QCOMPARE(e->status(), Cantor::Expression::Done); QVERIFY(e->result()->type() == Cantor::TextResult::Type); QCOMPARE(e->result()->data().toString(), QLatin1String("5")); QVERIFY(e->errorMessage().isEmpty()); } void TestJulia::testException() { Cantor::Expression *e = evalExp(QLatin1String("sqrt(-1)")); QVERIFY(e != nullptr); QCOMPARE(e->status(), Cantor::Expression::Error); QVERIFY(e->result()); QVERIFY(e->result()->type() == Cantor::TextResult::Type); QVERIFY( !e->errorMessage().isEmpty() && e->errorMessage().contains(QLatin1String( "sqrt will only return a complex result if called with a " "complex argument." )) ); } void TestJulia::testSyntaxError() { Cantor::Expression *e = evalExp(QLatin1String("for i = 1:10\nprint(i)")); QVERIFY(e != nullptr); QCOMPARE(e->status(), Cantor::Expression::Error); QVERIFY(e->result()->type() == Cantor::TextResult::Type); QVERIFY( !e->errorMessage().isEmpty() && e->errorMessage().contains(QLatin1String( "syntax: incomplete: \"for\" at none:1 requires end" )) ); } void TestJulia::testMultilineCode() { Cantor::Expression *e = evalExp(QLatin1String( "q = 0; # comment\n" "# sdfsdf\n" "for i = 1:10\n" " global q\n" " q += i\n" "end\n" "print(q)" )); QVERIFY(e != nullptr); QCOMPARE(e->status(), Cantor::Expression::Done); QVERIFY(e->result()->type() == Cantor::TextResult::Type); QCOMPARE(e->result()->data().toString(), QLatin1String("55")); QVERIFY(e->errorMessage().isEmpty()); } void TestJulia::testPartialResultOnException() { Cantor::Expression *e = evalExp(QLatin1String( "for i = 1:5\n" " print(i)\n" "end\n" "sqrt(-1)\n" )); QVERIFY(e != nullptr); QCOMPARE(e->status(), Cantor::Expression::Error); QVERIFY(e->result()->type() == Cantor::TextResult::Type); QCOMPARE(e->result()->data().toString(), QLatin1String("12345")); QVERIFY( !e->errorMessage().isEmpty() && e->errorMessage().contains(QLatin1String( "sqrt will only return a complex result if called with a " "complex argument." )) ); } void TestJulia::testInlinePlot() { Cantor::Expression *e = evalExp(QLatin1String( "import GR\n" "GR.plot([-1, -0.75, -0.5, -0.25, 0, 0.25, 0.5, 0.75, 1], sin)" )); QVERIFY(e != nullptr); QCOMPARE(e->status(), Cantor::Expression::Done); QVERIFY(e->result()->type() == Cantor::ImageResult::Type); } void TestJulia::testInlinePlotWithExceptionAndPartialResult() { Cantor::Expression *e = evalExp(QLatin1String( "import GR\n" "print(\"gonna plot\")\n" "sqrt(-1)\n" "GR.plot([-1, -0.75, -0.5, -0.25, 0, 0.25, 0.5, 0.75, 1], sin)\n" )); QVERIFY(e != nullptr); QCOMPARE(e->status(), Cantor::Expression::Error); QVERIFY(e->result()->type() == Cantor::TextResult::Type); QCOMPARE(e->result()->data().toString(), QLatin1String("gonna plot")); QVERIFY( !e->errorMessage().isEmpty() && e->errorMessage().contains(QLatin1String( "sqrt will only return a complex result if called with a " "complex argument." )) ); } void TestJulia::testAddVariablesFromCode() { evalExp(QLatin1String("a = 0; b = 1; c = 2; d = 3\n")); auto variableModel = session()->variableModel(); QStringList variableNames = QString::fromLatin1("a b c d").split(QLatin1String(" ")); for (int i = 0; i < variableNames.size(); i++) { QModelIndexList matchedVariables = variableModel->match( variableModel->index(0, 0), Qt::DisplayRole, QVariant::fromValue(variableNames[i]), -1, Qt::MatchExactly ); QCOMPARE(matchedVariables.size(), 1); auto match = matchedVariables[0]; auto valueIndex = variableModel->index(match.row(), match.column() + 1); QVERIFY( valueIndex.data(Qt::DisplayRole) == QVariant::fromValue(QString::number(i)) ); } } void TestJulia::testAddVariablesFromManager() { Cantor::DefaultVariableModel* variableModel = session()->variableModel(); QStringList variableNames = QString::fromLatin1("a b c d").split(QLatin1String(" ")); for (int i = 0; i < variableNames.size(); i++) { variableModel->addVariable(variableNames[i], QString::number(i)); QModelIndexList matchedVariables = variableModel->match( variableModel->index(0, 0), Qt::DisplayRole, QVariant::fromValue(variableNames[i]), -1, Qt::MatchExactly ); QCOMPARE(matchedVariables.size(), 1); auto match = matchedVariables[0]; auto valueIndex = variableModel->index(match.row(), match.column() + 1); QVERIFY( valueIndex.data(Qt::DisplayRole) == QVariant::fromValue(QString::number(i)) ); } } void TestJulia::testRemoveVariables() { Cantor::DefaultVariableModel* variableModel = session()->variableModel(); QStringList variableNames = QString::fromLatin1("a b c d").split(QLatin1String(" ")); for (int i = 0; i < variableNames.size(); i++) { variableModel->addVariable(variableNames[i], QString::number(i)); } for (int i = 0; i < variableNames.size(); i += 2) { variableModel->removeVariable(variableNames[i]); } for (int i = 0; i < variableNames.size(); i++) { QModelIndexList matchedVariables = variableModel->match( variableModel->index(0, 0), Qt::DisplayRole, QVariant::fromValue(variableNames[i]), -1, Qt::MatchExactly ); if (i % 2 == 0) { QVERIFY(matchedVariables.isEmpty()); } else { QCOMPARE(matchedVariables.size(), 1); auto match = matchedVariables[0]; auto valueIndex = variableModel->index(match.row(), match.column() + 1); QVERIFY( valueIndex.data(Qt::DisplayRole) == QVariant::fromValue(QString::number(i)) ); } } } void TestJulia::testAutoCompletion() { auto prefix = QLatin1String("ex"); auto completionObject = session()->completionFor(prefix); waitForSignal(completionObject, SIGNAL(fetchingDone())); auto completions = completionObject->completions(); QStringList completionsToCheck; completionsToCheck << QLatin1String("exit"); completionsToCheck << QLatin1String("exponent"); completionsToCheck << QLatin1String("exp"); for (auto completion : completionsToCheck) { QVERIFY(completions.contains(completion)); } for (auto completion : completions) { QVERIFY(completion.startsWith(prefix)); } } void TestJulia::testComplexAutocompletion() { auto prefix = QLatin1String("Base.Ma"); auto completionObject = session()->completionFor(prefix); waitForSignal(completionObject, SIGNAL(fetchingDone())); auto completions = completionObject->completions(); QStringList completionsToCheck; completionsToCheck << QLatin1String("Base.MainInclude"); completionsToCheck << QLatin1String("Base.Math"); completionsToCheck << QLatin1String("Base.Matrix"); for (auto completion : completionsToCheck) { QVERIFY(completions.contains(completion)); } for (auto completion : completions) { QVERIFY(completion.startsWith(prefix)); } } +void TestJulia::testExpressionQueue() +{ + Cantor::Expression* e1=session()->evaluateExpression(QLatin1String("0+1")); + Cantor::Expression* e2=session()->evaluateExpression(QLatin1String("1+1")); + QVERIFY(session()->status() == Cantor::Session::Running); + Cantor::Expression* e3=evalExp(QLatin1String("1+2")); + + QVERIFY(e1!=nullptr); + QVERIFY(e2!=nullptr); + QVERIFY(e3!=nullptr); + + QVERIFY(e1->result()); + QVERIFY(e2->result()); + QVERIFY(e3->result()); + + QCOMPARE(cleanOutput(e1->result()->toHtml()), QLatin1String("1")); + QCOMPARE(cleanOutput(e2->result()->toHtml()), QLatin1String("2")); + QCOMPARE(cleanOutput(e3->result()->toHtml()), QLatin1String("3")); +} + QTEST_MAIN(TestJulia) diff --git a/src/backends/julia/testjulia.h b/src/backends/julia/testjulia.h index 0416de0b..7ba73bfc 100644 --- a/src/backends/julia/testjulia.h +++ b/src/backends/julia/testjulia.h @@ -1,85 +1,87 @@ /* * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. * * --- * Copyright (C) 2016 Ivan Lakhtanov */ #pragma once #include class TestJulia: public BackendTest { Q_OBJECT private Q_SLOTS: /** * Test simple one-line command. Check that last result is printed */ void testOneLine(); /** * Test one-line command returning `nothing`. No result is printed, except * what `print` does */ void testOneLineWithPrint(); /** * Test command, that emits exception */ void testException(); /** * Test command consisting of multiple lines, including comments. */ void testMultilineCode(); /** * Test command with malformed syntax */ void testSyntaxError(); /** * Test that results gathered before exception occurred are shown */ void testPartialResultOnException(); /** * Tests that inline plot is shown */ void testInlinePlot(); /** * Tests that when exception occurred and plotting is done, partial * text results shown to user */ void testInlinePlotWithExceptionAndPartialResult(); /** * Test registering new variables, when added by command */ void testAddVariablesFromCode(); /** * Test registering new variables, when added from variable manager */ void testAddVariablesFromManager(); /** * Test that removing variable unregisters it */ void testRemoveVariables(); /** * Test that auto completion provides expected results */ void testAutoCompletion(); void testComplexAutocompletion(); + void testExpressionQueue(); + private: QString backendName() override; };