diff --git a/src/backends/python/import_default_modules.py b/src/backends/python/import_default_modules.py deleted file mode 100644 index 3ab3a100..00000000 --- a/src/backends/python/import_default_modules.py +++ /dev/null @@ -1,4 +0,0 @@ -try: - import numpy -except ModuleNotFoundError: - pass diff --git a/src/backends/python/python.qrc b/src/backends/python/python.qrc index 0c824d14..5c6b133b 100644 --- a/src/backends/python/python.qrc +++ b/src/backends/python/python.qrc @@ -1,8 +1,7 @@ - import_default_modules.py variables_cleaner.py variables_loader.py variables_saver.py diff --git a/src/backends/python/pythonserver.cpp b/src/backends/python/pythonserver.cpp index 3df158af..029e4e49 100644 --- a/src/backends/python/pythonserver.cpp +++ b/src/backends/python/pythonserver.cpp @@ -1,203 +1,211 @@ /* 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) 2015 Minh Ngo */ #include "pythonserver.h" #include PythonServer::PythonServer(QObject* parent) : QObject(parent), m_pModule(nullptr) { } namespace { QString pyObjectToQString(PyObject* obj) { #if PY_MAJOR_VERSION == 3 return QString::fromUtf8(PyUnicode_AsUTF8(obj)); #elif PY_MAJOR_VERSION == 2 return QString::fromLocal8Bit(PyString_AsString(obj)); #else #warning Unknown Python version #endif } } void PythonServer::login() { Py_InspectFlag = 1; Py_Initialize(); m_pModule = PyImport_AddModule("__main__"); filePath = QStringLiteral("python_cantor_worksheet"); } void PythonServer::runPythonCommand(const QString& command) const { PyObject* py_dict = PyModule_GetDict(m_pModule); const char* prepareCommand = "import sys;\n"\ "class CatchOutPythonBackend:\n"\ " def __init__(self):\n"\ " self.value = ''\n"\ " def write(self, txt):\n"\ " self.value += txt\n"\ "outputPythonBackend = CatchOutPythonBackend()\n"\ "errorPythonBackend = CatchOutPythonBackend()\n"\ "sys.stdout = outputPythonBackend\n"\ "sys.stderr = errorPythonBackend\n"; PyRun_SimpleString(prepareCommand); #if PY_MAJOR_VERSION == 3 PyObject* compile = Py_CompileString(command.toStdString().c_str(), filePath.toStdString().c_str(), Py_single_input); // There are two reasons for the error: // 1) This code is not single expression, so we can't compile this with flag Py_single_input // 2) There are errors in the code if (PyErr_Occurred()) { PyErr_Clear(); // Try to recompile code as sequence of expressions compile = Py_CompileString(command.toStdString().c_str(), filePath.toStdString().c_str(), Py_file_input); if (PyErr_Occurred()) { // We now know, that we have a syntax error, so print the traceback and exit PyErr_PrintEx(0); return; } } PyEval_EvalCode(compile, py_dict, py_dict); #elif PY_MAJOR_VERSION == 2 // Python 2.X don't check, that input string contains only one expression. // So for checking this, we compile string as file and as single expression and compare bytecode // FIXME? PyObject* codeFile = Py_CompileString(command.toStdString().c_str(), filePath.toStdString().c_str(), Py_file_input); if (PyErr_Occurred()) { PyErr_PrintEx(0); return; } PyObject* codeSingle = Py_CompileString(command.toStdString().c_str(), filePath.toStdString().c_str(), Py_single_input); if (PyErr_Occurred()) { // We have error with Py_single_input, but haven't error with Py_file_input // So, the code can't be compiled as singel input -> use file input right away PyErr_Clear(); PyEval_EvalCode((PyCodeObject*)codeFile, py_dict, py_dict); } else { PyObject* bytecode1 = ((PyCodeObject*)codeSingle)->co_code; PyObject* bytecode2 = ((PyCodeObject*)codeFile)->co_code; if (PyObject_Length(bytecode1) >= PyObject_Length(bytecode2)) { PyEval_EvalCode((PyCodeObject*)codeSingle, py_dict, py_dict); } else { PyEval_EvalCode((PyCodeObject*)codeFile, py_dict, py_dict); } } #else #warning Unknown Python version #endif if (PyErr_Occurred()) PyErr_PrintEx(0); } QString PythonServer::getError() const { PyObject *errorPython = PyObject_GetAttrString(m_pModule, "errorPythonBackend"); PyObject *error = PyObject_GetAttrString(errorPython, "value"); return pyObjectToQString(error); } QString PythonServer::getOutput() const { PyObject *outputPython = PyObject_GetAttrString(m_pModule, "outputPythonBackend"); PyObject *output = PyObject_GetAttrString(outputPython, "value"); return pyObjectToQString(output); } void PythonServer::setFilePath(const QString& path) { this->filePath = path; PyRun_SimpleString(("__file__ = '"+path.toStdString()+"'").c_str()); } QString PythonServer::variables(bool parseValue) const { PyRun_SimpleString( "try: \n" " import numpy \n" " __cantor_numpy_internal__ = numpy.get_printoptions()['threshold'] \n" " numpy.set_printoptions(threshold=100000000) \n" +#if PY_MAJOR_VERSION == 3 "except ModuleNotFoundError: \n" +#elif PY_MAJOR_VERSION == 2 + "except ImportError: \n" +#endif " pass \n" ); PyRun_SimpleString("__tmp_globals__ = globals()"); PyObject* globals = PyObject_GetAttrString(m_pModule,"__tmp_globals__"); PyObject *key, *value; Py_ssize_t pos = 0; QStringList vars; while (PyDict_Next(globals, &pos, &key, &value)) { const QString& keyString = pyObjectToQString(key); if (keyString.startsWith(QLatin1String("__"))) continue; if (keyString == QLatin1String("CatchOutPythonBackend") || keyString == QLatin1String("errorPythonBackend") || keyString == QLatin1String("outputPythonBackend")) continue; if (PyModule_Check(value)) continue; if (PyFunction_Check(value)) continue; if (PyType_Check(value)) continue; QString valueString; if (parseValue) valueString = pyObjectToQString(PyObject_Repr(value)); vars.append(keyString + QChar(17) + valueString); } PyRun_SimpleString( "try: \n" " import numpy \n" " numpy.set_printoptions(threshold=__cantor_numpy_internal__) \n" " del __cantor_numpy_internal__ \n" +#if PY_MAJOR_VERSION == 3 "except ModuleNotFoundError: \n" +#elif PY_MAJOR_VERSION == 2 + "except ImportError: \n" +#endif " pass \n" ); return vars.join(QChar(18))+QChar(18); } diff --git a/src/backends/python/pythonsession.cpp b/src/backends/python/pythonsession.cpp index d9e681cf..444d6846 100644 --- a/src/backends/python/pythonsession.cpp +++ b/src/backends/python/pythonsession.cpp @@ -1,239 +1,234 @@ /* 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) 2012 Filipe Saraiva Copyright (C) 2015 Minh Ngo */ #include #include "pythonsession.h" #include "pythonexpression.h" #include "pythonvariablemodel.h" #include "pythonhighlighter.h" #include "pythoncompletionobject.h" #include "pythonkeywords.h" #include "pythonutils.h" #include #include #include #include #include #ifndef Q_OS_WIN #include #endif const QChar recordSep(30); const QChar unitSep(31); const QChar messageEnd = 29; PythonSession::PythonSession(Cantor::Backend* backend, int pythonVersion, const QString serverName) : Session(backend) , m_process(nullptr) , serverName(serverName) , m_pythonVersion(pythonVersion) { setVariableModel(new PythonVariableModel(this)); } PythonSession::~PythonSession() { if (m_process) { m_process->kill(); m_process->deleteLater(); } } void PythonSession::login() { qDebug()<<"login"; emit loginStarted(); if (m_process) m_process->deleteLater(); m_process = new QProcess(this); m_process->setProcessChannelMode(QProcess::ForwardedErrorChannel); m_process->start(QStandardPaths::findExecutable(serverName)); m_process->waitForStarted(); m_process->waitForReadyRead(); QTextStream stream(m_process->readAllStandardOutput()); const QString& readyStatus = QString::fromLatin1("ready"); while (m_process->state() == QProcess::Running) { const QString& rl = stream.readLine(); if (rl == readyStatus) break; } connect(m_process, &QProcess::readyReadStandardOutput, this, &PythonSession::readOutput); sendCommand(QLatin1String("login")); sendCommand(QLatin1String("setFilePath"), QStringList(worksheetPath)); const QStringList& scripts = autorunScripts(); if(!scripts.isEmpty()){ QString autorunScripts = scripts.join(QLatin1String("\n")); evaluateExpression(autorunScripts, Cantor::Expression::DeleteOnFinish, true); variableModel()->update(); } - const QString& importerFile = QLatin1String(":py/import_default_modules.py"); - - evaluateExpression(fromSource(importerFile), Cantor::Expression::DeleteOnFinish, true); - - changeStatus(Session::Done); emit loginDone(); } void PythonSession::logout() { if (!m_process) return; sendCommand(QLatin1String("exit")); m_process = nullptr; variableModel()->clearVariables(); qDebug()<<"logout"; changeStatus(Status::Disable); } void PythonSession::interrupt() { if(!expressionQueue().isEmpty()) { qDebug()<<"interrupting " << expressionQueue().first()->command(); if(m_process->state() != QProcess::NotRunning) { #ifndef Q_OS_WIN const int pid=m_process->pid(); kill(pid, SIGINT); #else ; //TODO: interrupt the process on windows #endif } for (Cantor::Expression* expression : expressionQueue()) expression->setStatus(Cantor::Expression::Interrupted); expressionQueue().clear(); // Cleanup inner state and call octave prompt printing // If we move this code for interruption to Session, we need add function for // cleaning before setting Done status m_output.clear(); m_process->write("\n"); qDebug()<<"done interrupting"; } changeStatus(Cantor::Session::Done); } Cantor::Expression* PythonSession::evaluateExpression(const QString& cmd, Cantor::Expression::FinishingBehavior behave, bool internal) { qDebug() << "evaluating: " << cmd; PythonExpression* expr = new PythonExpression(this, internal); changeStatus(Cantor::Session::Running); expr->setFinishingBehavior(behave); expr->setCommand(cmd); expr->evaluate(); return expr; } QSyntaxHighlighter* PythonSession::syntaxHighlighter(QObject* parent) { return new PythonHighlighter(parent, this, m_pythonVersion); } Cantor::CompletionObject* PythonSession::completionFor(const QString& command, int index) { return new PythonCompletionObject(command, index, this); } void PythonSession::runFirstExpression() { if (expressionQueue().isEmpty()) return; Cantor::Expression* expr = expressionQueue().first(); const QString command = expr->internalCommand(); qDebug() << "run first expression" << command; expr->setStatus(Cantor::Expression::Computing); if (expr->isInternal() && command.startsWith(QLatin1String("%variables "))) { const QString arg = command.section(QLatin1String(" "), 1); sendCommand(QLatin1String("model"), QStringList(arg)); } else sendCommand(QLatin1String("code"), QStringList(expr->internalCommand())); } void PythonSession::sendCommand(const QString& command, const QStringList arguments) const { qDebug() << "send command: " << command << arguments; const QString& message = command + recordSep + arguments.join(unitSep) + messageEnd; m_process->write(message.toLocal8Bit()); } void PythonSession::readOutput() { while (m_process->bytesAvailable() > 0) m_output.append(QString::fromLocal8Bit(m_process->readAll())); qDebug() << "m_output: " << m_output; if (!m_output.contains(messageEnd)) return; const QStringList packages = m_output.split(messageEnd, QString::SkipEmptyParts); if (m_output.endsWith(messageEnd)) m_output.clear(); else m_output = m_output.section(messageEnd, -1); for (const QString& message: packages) { if (expressionQueue().isEmpty()) break; const QString& output = message.section(unitSep, 0, 0); const QString& error = message.section(unitSep, 1, 1); if(error.isEmpty()){ static_cast(expressionQueue().first())->parseOutput(output); } else { static_cast(expressionQueue().first())->parseError(error); } finishFirstExpression(true); } } void PythonSession::setWorksheetPath(const QString& path) { worksheetPath = path; } diff --git a/src/backends/python2/testpython2.cpp b/src/backends/python2/testpython2.cpp index 51b4f9df..433f1d77 100644 --- a/src/backends/python2/testpython2.cpp +++ b/src/backends/python2/testpython2.cpp @@ -1,279 +1,279 @@ /* 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) 2013 Tuukka Verho */ #include "testpython2.h" #include "session.h" #include "backend.h" #include "expression.h" #include "result.h" #include "defaultvariablemodel.h" #include "imageresult.h" #include "completionobject.h" #include "settings.h" QString TestPython2::backendName() { return QLatin1String("python2"); } void TestPython2::testCommandQueue() { Cantor::Expression* e1=session()->evaluateExpression(QLatin1String("0+1")); Cantor::Expression* e2=session()->evaluateExpression(QLatin1String("1+1")); 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()->data().toString()), QLatin1String("1")); QCOMPARE(cleanOutput(e2->result()->data().toString()), QLatin1String("2")); QCOMPARE(cleanOutput(e3->result()->data().toString()), QLatin1String("3")); } -void TestPython2::testImportNumpy() +void TestPython2::testImportStatement() { - Cantor::Expression* e = evalExp(QLatin1String("import numpy")); + Cantor::Expression* e = evalExp(QLatin1String("import sys")); QVERIFY(e != nullptr); QCOMPARE(e->status(), Cantor::Expression::Done); } void TestPython2::testCodeWithComments() { { Cantor::Expression* e = evalExp(QLatin1String("#comment\n1+2")); QVERIFY(e != nullptr); QVERIFY(e->result()); QVERIFY(e->result()->data().toString() == QLatin1String("3")); } { Cantor::Expression* e = evalExp(QLatin1String(" #comment\n1+2")); QVERIFY(e != nullptr); QVERIFY(e->result()); QVERIFY(e->result()->data().toString() == QLatin1String("3")); } } void TestPython2::testSimpleCode() { Cantor::Expression* e=evalExp( QLatin1String("2+2")); QVERIFY( e!=nullptr ); QVERIFY( e->result()!=nullptr ); QCOMPARE( cleanOutput(e->result()->data().toString()), QLatin1String("4") ); } void TestPython2::testMultilineCode() { Cantor::Expression* e=evalExp(QLatin1String( "a = 2+2\n" "b = 3+3\n" "print a,b" )); QVERIFY( e!=nullptr ); QVERIFY( e->result()!=nullptr ); QCOMPARE( cleanOutput(e->result()->data().toString()), QLatin1String("4 6") ); evalExp(QLatin1String("del a; del b")); } void TestPython2::testVariablesCreatingFromCode() { QAbstractItemModel* model = session()->variableModel(); QVERIFY(model != nullptr); Cantor::Expression* e=evalExp(QLatin1String("a = 15; b = 'S';")); QVERIFY(e!=nullptr); if(session()->status()==Cantor::Session::Running) waitForSignal(session(), SIGNAL(statusChanged(Cantor::Session::Status))); QCOMPARE(2, model->rowCount()); QCOMPARE(model->index(0,0).data().toString(), QLatin1String("a")); QCOMPARE(model->index(0,1).data().toString(), QLatin1String("15")); QCOMPARE(model->index(1,0).data().toString(), QLatin1String("b")); QCOMPARE(model->index(1,1).data().toString(), QLatin1String("'S'")); evalExp(QLatin1String("del a; del b")); } void TestPython2::testVariableCleanupAfterRestart() { Cantor::DefaultVariableModel* model = session()->variableModel(); QVERIFY(model != nullptr); Cantor::Expression* e=evalExp(QLatin1String("a = 15; b = 'S';")); QVERIFY(e!=nullptr); if(session()->status()==Cantor::Session::Running) waitForSignal(session(), SIGNAL(statusChanged(Cantor::Session::Status))); QCOMPARE(2, static_cast(model)->rowCount()); session()->logout(); session()->login(); QCOMPARE(0, static_cast(model)->rowCount()); } void TestPython2::testDictVariable() { Cantor::DefaultVariableModel* model = session()->variableModel(); QVERIFY(model != nullptr); Cantor::Expression* e=evalExp(QLatin1String("d = {'value': 33}")); QVERIFY(e!=nullptr); if(session()->status()==Cantor::Session::Running) waitForSignal(session(), SIGNAL(statusChanged(Cantor::Session::Status))); QCOMPARE(1, static_cast(model)->rowCount()); QCOMPARE(model->index(0,0).data().toString(), QLatin1String("d")); QCOMPARE(model->index(0,1).data().toString(), QLatin1String("{'value': 33}")); evalExp(QLatin1String("del d")); } void TestPython2::testCommentExpression() { Cantor::Expression* e = evalExp(QLatin1String("#only comment")); QVERIFY(e != nullptr); QCOMPARE(e->status(), Cantor::Expression::Status::Done); QCOMPARE(e->results().size(), 0); } void TestPython2::testInvalidSyntax() { Cantor::Expression* e=evalExp( QLatin1String("2+2*+.") ); QVERIFY( e!=nullptr ); QCOMPARE( e->status(), Cantor::Expression::Error ); } void TestPython2::testSimplePlot() { if (!PythonSettings::integratePlots()) QSKIP("This test needs enabled plots integration in Python2 settings", SkipSingle); Cantor::Expression* e = evalExp(QLatin1String( "import matplotlib\n" "import matplotlib.pyplot as plt\n" "import numpy as np" )); QVERIFY(e != nullptr); QVERIFY(e->errorMessage().isEmpty() == true); //the variable model shouldn't have any entries after the module imports QAbstractItemModel* model = session()->variableModel(); QVERIFY(model != nullptr); if(session()->status()==Cantor::Session::Running) waitForSignal(session(), SIGNAL(statusChanged(Cantor::Session::Status))); QVERIFY(model->rowCount() == 0); //create data for plotting e = evalExp(QLatin1String( "t = np.arange(0.0, 2.0, 0.01)\n" "s = 1 + np.sin(2 * np.pi * t)" )); QVERIFY(e != nullptr); QVERIFY(e->errorMessage().isEmpty() == true); if(session()->status()==Cantor::Session::Running) waitForSignal(session(), SIGNAL(statusChanged(Cantor::Session::Status))); //the variable model should have two entries now QVERIFY(model->rowCount() == 2); //plot the data and check the results e = evalExp(QLatin1String( "plt.plot(t,s)\n" "plt.show()" )); QVERIFY(e != nullptr); if (e->result() == nullptr) waitForSignal(e, SIGNAL(gotResult())); QVERIFY(e->errorMessage().isEmpty() == true); QVERIFY(model->rowCount() == 2); //still only two variables //there must be one single image result QVERIFY(e->results().size() == 1); const Cantor::ImageResult* result = dynamic_cast(e->result()); QVERIFY(result != nullptr); evalExp(QLatin1String("del t; del s")); } void TestPython2::testSimpleExpressionWithComment() { Cantor::Expression* e = evalExp(QLatin1String("2+2 # comment")); QVERIFY(e != nullptr); QVERIFY(e->result()); QVERIFY(e->result()->data().toString() == QLatin1String("4")); } void TestPython2::testMultilineCommandWithComment() { Cantor::Expression* e = evalExp(QLatin1String( "print 2+2 \n" "#comment in middle \n" "print 7*5")); QVERIFY(e != nullptr); QVERIFY(e->result()); QVERIFY(e->result()->data().toString() == QLatin1String("4\n35")); } void TestPython2::testCompletion() { if(session()->status()==Cantor::Session::Running) waitForSignal(session(), SIGNAL(statusChanged(Cantor::Session::Status))); Cantor::CompletionObject* help = session()->completionFor(QLatin1String("ma"), 2); waitForSignal(help, SIGNAL(fetchingDone())); // Checks all completions for this request // This correct for Python 2.7.15 const QStringList& completions = help->completions(); qDebug() << completions; QCOMPARE(completions.size(), 2); QVERIFY(completions.contains(QLatin1String("map"))); QVERIFY(completions.contains(QLatin1String("max"))); } QTEST_MAIN(TestPython2) diff --git a/src/backends/python2/testpython2.h b/src/backends/python2/testpython2.h index 692c8d45..2aa1f060 100644 --- a/src/backends/python2/testpython2.h +++ b/src/backends/python2/testpython2.h @@ -1,54 +1,54 @@ /* 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) 2013 Tuukka Verho */ #ifndef _TESTPYTHON2_H #define _TESTPYTHON2_H #include "backendtest.h" class TestPython2 : public BackendTest { Q_OBJECT private Q_SLOTS: void testCodeWithComments(); void testSimpleCode(); void testMultilineCode(); void testCommandQueue(); void testSimplePlot(); - void testImportNumpy(); + void testImportStatement(); void testInvalidSyntax(); void testSimpleExpressionWithComment(); void testCommentExpression(); void testMultilineCommandWithComment(); void testVariablesCreatingFromCode(); void testVariableCleanupAfterRestart(); void testDictVariable(); void testCompletion(); private: QString backendName() override; }; #endif /* _TESTPYTHON2_H */ diff --git a/src/backends/python3/testpython3.cpp b/src/backends/python3/testpython3.cpp index 8d19e133..0735327c 100644 --- a/src/backends/python3/testpython3.cpp +++ b/src/backends/python3/testpython3.cpp @@ -1,290 +1,290 @@ /* 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) 2015 Minh Ngo */ #include "testpython3.h" #include "session.h" #include "backend.h" #include "expression.h" #include "imageresult.h" #include "defaultvariablemodel.h" #include "completionobject.h" #include "settings.h" QString TestPython3::backendName() { return QLatin1String("python3"); } void TestPython3::testSimpleCommand() { Cantor::Expression* e = evalExp(QLatin1String("2+2")); QVERIFY(e != nullptr); QVERIFY(e->result()); QVERIFY(e->result()->data().toString() == QLatin1String("4")); } void TestPython3::testMultilineCommand() { Cantor::Expression* e = evalExp(QLatin1String("print(2+2)\nprint(7*5)")); QVERIFY(e != nullptr); QVERIFY(e->result()); QVERIFY(e->result()->data().toString() == QLatin1String("4\n35")); } void TestPython3::testCommandQueue() { Cantor::Expression* e1=session()->evaluateExpression(QLatin1String("0+1")); Cantor::Expression* e2=session()->evaluateExpression(QLatin1String("1+1")); 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()->data().toString()), QLatin1String("1")); QCOMPARE(cleanOutput(e2->result()->data().toString()), QLatin1String("2")); QCOMPARE(cleanOutput(e3->result()->data().toString()), QLatin1String("3")); } void TestPython3::testCommentExpression() { Cantor::Expression* e = evalExp(QLatin1String("#only comment")); QVERIFY(e != nullptr); QCOMPARE(e->status(), Cantor::Expression::Status::Done); QCOMPARE(e->results().size(), 0); } void TestPython3::testSimpleExpressionWithComment() { Cantor::Expression* e = evalExp(QLatin1String("2+2 # comment")); QVERIFY(e != nullptr); QVERIFY(e->result()); QVERIFY(e->result()->data().toString() == QLatin1String("4")); } void TestPython3::testMultilineCommandWithComment() { Cantor::Expression* e = evalExp(QLatin1String( "print(2+2) \n" "#comment in middle \n" "print(7*5)")); QVERIFY(e != nullptr); QVERIFY(e->result()); QVERIFY(e->result()->data().toString() == QLatin1String("4\n35")); } void TestPython3::testInvalidSyntax() { Cantor::Expression* e=evalExp( QLatin1String("2+2*+.") ); QVERIFY( e!=nullptr ); QCOMPARE( e->status(), Cantor::Expression::Error ); } void TestPython3::testCompletion() { if(session()->status()==Cantor::Session::Running) waitForSignal(session(), SIGNAL(statusChanged(Cantor::Session::Status))); Cantor::CompletionObject* help = session()->completionFor(QLatin1String("p"), 1); waitForSignal(help, SIGNAL(fetchingDone())); // Checks all completions for this request // This correct for Python 3.6.7 const QStringList& completions = help->completions(); qDebug() << completions; QCOMPARE(completions.size(), 4); QVERIFY(completions.contains(QLatin1String("pass"))); QVERIFY(completions.contains(QLatin1String("pow"))); QVERIFY(completions.contains(QLatin1String("print"))); QVERIFY(completions.contains(QLatin1String("property"))); } -void TestPython3::testImportNumpy() +void TestPython3::testImportStatement() { - Cantor::Expression* e = evalExp(QLatin1String("import numpy")); + Cantor::Expression* e = evalExp(QLatin1String("import sys")); QVERIFY(e != nullptr); QCOMPARE(e->status(), Cantor::Expression::Done); } void TestPython3::testCodeWithComments() { { Cantor::Expression* e = evalExp(QLatin1String("#comment\n1+2")); QVERIFY(e != nullptr); QVERIFY(e->result()); QVERIFY(e->result()->data().toString() == QLatin1String("3")); } { Cantor::Expression* e = evalExp(QLatin1String(" #comment\n1+2")); QVERIFY(e != nullptr); QVERIFY(e->result()); QVERIFY(e->result()->data().toString() == QLatin1String("3")); } } void TestPython3::testPython3Code() { { Cantor::Expression* e = evalExp(QLatin1String("print 1 + 2")); QVERIFY(e != nullptr); QVERIFY(!e->errorMessage().isEmpty()); } { Cantor::Expression* e = evalExp(QLatin1String("print(1 + 2)")); QVERIFY(e != nullptr); QVERIFY(e->result()); QVERIFY(e->result()->data().toString() == QLatin1String("3")); } } void TestPython3::testSimplePlot() { if (!PythonSettings::integratePlots()) QSKIP("This test needs enabled plots integration in Python3 settings", SkipSingle); Cantor::Expression* e = evalExp(QLatin1String( "import matplotlib\n" "import matplotlib.pyplot as plt\n" "import numpy as np" )); QVERIFY(e != nullptr); QVERIFY(e->errorMessage().isEmpty() == true); //the variable model shouldn't have any entries after the module imports QAbstractItemModel* model = session()->variableModel(); QVERIFY(model != nullptr); QVERIFY(model->rowCount() == 0); //create data for plotting e = evalExp(QLatin1String( "t = np.arange(0.0, 2.0, 0.01)\n" "s = 1 + np.sin(2 * np.pi * t)" )); QVERIFY(e != nullptr); QVERIFY(e->errorMessage().isEmpty() == true); if(session()->status()==Cantor::Session::Running) waitForSignal(session(), SIGNAL(statusChanged(Cantor::Session::Status))); //the variable model should have two entries now QVERIFY(model->rowCount() == 2); //plot the data and check the results e = evalExp(QLatin1String( "plt.plot(t,s)\n" "plt.show()" )); QVERIFY(e != nullptr); if (e->result() == nullptr) waitForSignal(e, SIGNAL(gotResult())); QVERIFY(e->errorMessage().isEmpty() == true); QVERIFY(model->rowCount() == 2); //still only two variables //there must be one single image result QVERIFY(e->results().size() == 1); const Cantor::ImageResult* result = dynamic_cast(e->result()); QVERIFY(result != nullptr); evalExp(QLatin1String("del t; del s")); } void TestPython3::testVariablesCreatingFromCode() { QAbstractItemModel* model = session()->variableModel(); QVERIFY(model != nullptr); Cantor::Expression* e=evalExp(QLatin1String("a = 15; b = 'S';")); QVERIFY(e!=nullptr); if(session()->status()==Cantor::Session::Running) waitForSignal(session(), SIGNAL(statusChanged(Cantor::Session::Status))); QCOMPARE(2, model->rowCount()); QCOMPARE(model->index(0,0).data().toString(), QLatin1String("a")); QCOMPARE(model->index(0,1).data().toString(), QLatin1String("15")); QCOMPARE(model->index(1,0).data().toString(), QLatin1String("b")); QCOMPARE(model->index(1,1).data().toString(), QLatin1String("'S'")); evalExp(QLatin1String("del a; del b")); } void TestPython3::testVariableCleanupAfterRestart() { Cantor::DefaultVariableModel* model = session()->variableModel(); QVERIFY(model != nullptr); Cantor::Expression* e=evalExp(QLatin1String("a = 15; b = 'S';")); QVERIFY(e!=nullptr); if(session()->status()==Cantor::Session::Running) waitForSignal(session(), SIGNAL(statusChanged(Cantor::Session::Status))); QCOMPARE(2, static_cast(model)->rowCount()); session()->logout(); session()->login(); QCOMPARE(0, static_cast(model)->rowCount()); } void TestPython3::testDictVariable() { Cantor::DefaultVariableModel* model = session()->variableModel(); QVERIFY(model != nullptr); Cantor::Expression* e=evalExp(QLatin1String("d = {'value': 33}")); QVERIFY(e!=nullptr); if(session()->status()==Cantor::Session::Running) waitForSignal(session(), SIGNAL(statusChanged(Cantor::Session::Status))); QCOMPARE(1, static_cast(model)->rowCount()); QCOMPARE(model->index(0,0).data().toString(), QLatin1String("d")); QCOMPARE(model->index(0,1).data().toString(), QLatin1String("{'value': 33}")); evalExp(QLatin1String("del d")); } QTEST_MAIN(TestPython3) diff --git a/src/backends/python3/testpython3.h b/src/backends/python3/testpython3.h index 61663dda..8c28cab2 100644 --- a/src/backends/python3/testpython3.h +++ b/src/backends/python3/testpython3.h @@ -1,54 +1,54 @@ /* 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) 2015 Minh Ngo */ #ifndef _TESTPYTHON3_H #define _TESTPYTHON3_H #include class TestPython3 : public BackendTest { Q_OBJECT private Q_SLOTS: void testSimpleCommand(); void testMultilineCommand(); void testCodeWithComments(); void testCommandQueue(); void testSimplePlot(); - void testImportNumpy(); + void testImportStatement(); void testPython3Code(); void testInvalidSyntax(); void testSimpleExpressionWithComment(); void testCommentExpression(); void testMultilineCommandWithComment(); void testVariablesCreatingFromCode(); void testVariableCleanupAfterRestart(); void testDictVariable(); void testCompletion(); private: QString backendName() override; }; #endif /* _TESTPYTHON3_H */