Warning, file /education/cantor/src/backends/python/testpython.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).

0001 /*
0002     SPDX-License-Identifier: GPL-2.0-or-later
0003     SPDX-FileCopyrightText: 2015 Minh Ngo <minh@fedoraproject.org>
0004     SPDX-FileCopyrightText: 2021-2022 Alexander Semke <alexander.semke@web.de>
0005 */
0006 
0007 #include "testpython.h"
0008 
0009 #include "session.h"
0010 #include "expression.h"
0011 #include "imageresult.h"
0012 #include "defaultvariablemodel.h"
0013 #include "completionobject.h"
0014 
0015 #include "settings.h"
0016 
0017 QString TestPython3::backendName()
0018 {
0019     return QLatin1String("python");
0020 }
0021 
0022 void TestPython3::testSimpleCommand()
0023 {
0024     auto* e = evalExp(QLatin1String("2+2"));
0025 
0026     QVERIFY(e != nullptr);
0027     QVERIFY(e->result());
0028     QVERIFY(e->result()->data().toString() == QLatin1String("4"));
0029 }
0030 
0031 void TestPython3::testMultilineCommand()
0032 {
0033     auto* e = evalExp(QLatin1String("print(2+2)\nprint(7*5)"));
0034 
0035     QVERIFY(e != nullptr);
0036     QVERIFY(e->result());
0037     QVERIFY(e->result()->data().toString() == QLatin1String("4\n35"));
0038 }
0039 
0040 void TestPython3::testCommandQueue()
0041 {
0042     auto* e1 = session()->evaluateExpression(QLatin1String("0+1"));
0043     auto* e2 = session()->evaluateExpression(QLatin1String("1+1"));
0044     auto* e3 = evalExp(QLatin1String("1+2"));
0045 
0046     QVERIFY(e1 != nullptr);
0047     QVERIFY(e2 != nullptr);
0048     QVERIFY(e3 != nullptr);
0049 
0050     QVERIFY(e1->result());
0051     QVERIFY(e2->result());
0052     QVERIFY(e3->result());
0053 
0054     QCOMPARE(cleanOutput(e1->result()->data().toString()), QLatin1String("1"));
0055     QCOMPARE(cleanOutput(e2->result()->data().toString()), QLatin1String("2"));
0056     QCOMPARE(cleanOutput(e3->result()->data().toString()), QLatin1String("3"));
0057 }
0058 
0059 void TestPython3::testCommentExpression()
0060 {
0061     auto* e = evalExp(QLatin1String("#only comment"));
0062 
0063     QVERIFY(e != nullptr);
0064     QCOMPARE(e->status(), Cantor::Expression::Status::Done);
0065     QCOMPARE(e->results().size(), 0);
0066 }
0067 
0068 void TestPython3::testSimpleExpressionWithComment()
0069 {
0070     auto* e = evalExp(QLatin1String("2+2 # comment"));
0071 
0072     QVERIFY(e != nullptr);
0073     QVERIFY(e->result());
0074     QVERIFY(e->result()->data().toString() == QLatin1String("4"));
0075 }
0076 
0077 void TestPython3::testMultilineCommandWithComment()
0078 {
0079     auto* e = evalExp(QLatin1String(
0080         "print(2+2) \n"
0081         "#comment in middle \n"
0082         "print(7*5)"));
0083 
0084     QVERIFY(e != nullptr);
0085     QVERIFY(e->result());
0086     QVERIFY(e->result()->data().toString() == QLatin1String("4\n35"));
0087 }
0088 
0089 void TestPython3::testInvalidSyntax()
0090 {
0091     auto* e=evalExp( QLatin1String("2+2*+.") );
0092 
0093     QVERIFY( e!=nullptr );
0094     QCOMPARE( e->status(), Cantor::Expression::Error );
0095 }
0096 
0097 void TestPython3::testCompletion()
0098 {
0099     if(session()->status()==Cantor::Session::Running)
0100         waitForSignal(session(), SIGNAL(statusChanged(Cantor::Session::Status)));
0101 
0102     auto* help = session()->completionFor(QLatin1String("p"), 1);
0103     waitForSignal(help, SIGNAL(fetchingDone()));
0104 
0105     // Checks all completions for this request
0106     const auto& completions = help->completions();
0107     QCOMPARE(completions.size(), 4);
0108     QVERIFY(completions.contains(QLatin1String("pass")));
0109     QVERIFY(completions.contains(QLatin1String("pow")));
0110     QVERIFY(completions.contains(QLatin1String("print")));
0111     QVERIFY(completions.contains(QLatin1String("property")));
0112 }
0113 
0114 
0115 void TestPython3::testImportStatement()
0116 {
0117     auto* e = evalExp(QLatin1String("import sys"));
0118 
0119     QVERIFY(e != nullptr);
0120     QCOMPARE(e->status(), Cantor::Expression::Done);
0121 }
0122 
0123 void TestPython3::testCodeWithComments()
0124 {
0125     {
0126     auto* e = evalExp(QLatin1String("#comment\n1+2"));
0127 
0128     QVERIFY(e != nullptr);
0129     QVERIFY(e->result());
0130     QVERIFY(e->result()->data().toString() == QLatin1String("3"));
0131     }
0132 
0133     {
0134     auto* e = evalExp(QLatin1String("     #comment\n1+2"));
0135 
0136     QVERIFY(e != nullptr);
0137     QVERIFY(e->result());
0138     QVERIFY(e->result()->data().toString() == QLatin1String("3"));
0139     }
0140 }
0141 
0142 void TestPython3::testPython3Code()
0143 {
0144     {
0145     auto* e = evalExp(QLatin1String("print 1 + 2"));
0146 
0147     QVERIFY(e != nullptr);
0148     QVERIFY(!e->errorMessage().isEmpty());
0149     }
0150 
0151     {
0152     auto* e = evalExp(QLatin1String("print(1 + 2)"));
0153 
0154     QVERIFY(e != nullptr);
0155     QVERIFY(e->result());
0156     QVERIFY(e->result()->data().toString() == QLatin1String("3"));
0157     }
0158 }
0159 
0160 void TestPython3::testSimplePlot()
0161 {
0162     if (!PythonSettings::integratePlots())
0163         QSKIP("This test needs enabled plots integration in Python3 settings", SkipSingle);
0164 
0165     auto* e = evalExp(QLatin1String(
0166         "import matplotlib\n"
0167         "import matplotlib.pyplot as plt\n"
0168         "import numpy as np"
0169     ));
0170     QVERIFY(e != nullptr);
0171     QVERIFY(e->errorMessage().isEmpty() == true);
0172 
0173     //the variable model shouldn't have any entries after the module imports
0174     QAbstractItemModel* model = session()->variableModel();
0175     QVERIFY(model != nullptr);
0176     QVERIFY(model->rowCount() == 0);
0177 
0178     //create data for plotting
0179     e = evalExp(QLatin1String(
0180         "t = np.arange(0.0, 2.0, 0.01)\n"
0181         "s = 1 + np.sin(2 * np.pi * t)"
0182     ));
0183     QVERIFY(e != nullptr);
0184     QVERIFY(e->errorMessage().isEmpty() == true);
0185 
0186     if(session()->status()==Cantor::Session::Running)
0187         waitForSignal(session(), SIGNAL(statusChanged(Cantor::Session::Status)));
0188     //the variable model should have two entries now
0189     QVERIFY(model->rowCount() == 2);
0190 
0191     //plot the data and check the results
0192     e = evalExp(QLatin1String(
0193         "plt.plot(t,s)\n"
0194         "plt.show()"
0195     ));
0196 
0197     QVERIFY(e != nullptr);
0198     if (e->result() == nullptr)
0199         waitForSignal(e, SIGNAL(gotResult()));
0200 
0201     QVERIFY(e->errorMessage().isEmpty() == true);
0202     QVERIFY(model->rowCount() == 2); //still only two variables
0203 
0204     //there must be one single image result
0205     QVERIFY(e->results().size() == 1);
0206     const Cantor::ImageResult* result = dynamic_cast<const Cantor::ImageResult*>(e->result());
0207     QVERIFY(result != nullptr);
0208 
0209     evalExp(QLatin1String("del t; del s"));
0210 }
0211 
0212 void TestPython3::testVariablesCreatingFromCode()
0213 {
0214     if (!PythonSettings::variableManagement())
0215         QSKIP("This test needs enabled variable management in Python3 settings", SkipSingle);
0216 
0217     QAbstractItemModel* model = session()->variableModel();
0218     QVERIFY(model != nullptr);
0219 
0220     auto* e = evalExp(QLatin1String("a = 15; b = 'S';"));
0221     QVERIFY(e != nullptr);
0222 
0223     if(session()->status() == Cantor::Session::Running)
0224         waitForSignal(session(), SIGNAL(statusChanged(Cantor::Session::Status)));
0225 
0226     QCOMPARE(2, model->rowCount());
0227 
0228     QCOMPARE(model->index(0,0).data().toString(), QLatin1String("a"));
0229     QCOMPARE(model->index(0,1).data().toString(), QLatin1String("15"));
0230 
0231     QCOMPARE(model->index(1,0).data().toString(), QLatin1String("b"));
0232     QCOMPARE(model->index(1,1).data().toString(), QLatin1String("'S'"));
0233 
0234     evalExp(QLatin1String("del a; del b"));
0235 }
0236 
0237 void TestPython3::testVariableChangeSizeType()
0238 {
0239     if (!PythonSettings::variableManagement())
0240         QSKIP("This test needs enabled variable management in Python3 settings", SkipSingle);
0241 
0242     QAbstractItemModel* model = session()->variableModel();
0243     QVERIFY(model != nullptr);
0244 
0245     // create a text variable
0246     auto* e = evalExp(QLatin1String("test = \"abcd\";"));
0247     QVERIFY(e != nullptr);
0248 
0249     if(session()->status() == Cantor::Session::Running)
0250         waitForSignal(session(), SIGNAL(statusChanged(Cantor::Session::Status)));
0251 
0252     QCOMPARE(1, model->rowCount());
0253     QCOMPARE(model->index(0,0).data().toString(), QLatin1String("test"));
0254     QCOMPARE(model->index(0,1).data().toString(), QLatin1String("'abcd'"));
0255     QCOMPARE(model->index(0,2).data().toString(), QLatin1String("<class 'str'>"));
0256     QCOMPARE(model->index(0,3).data().toString(), QLatin1String("53"));
0257 
0258     // change from string to integer
0259     e = evalExp(QLatin1String("test = 1;"));
0260     QVERIFY(e != nullptr);
0261 
0262     if(session()->status() == Cantor::Session::Running)
0263         waitForSignal(session(), SIGNAL(statusChanged(Cantor::Session::Status)));
0264 
0265     QCOMPARE(1, model->rowCount());
0266     QCOMPARE(model->index(0,0).data().toString(), QLatin1String("test"));
0267     QCOMPARE(model->index(0,1).data().toString(), QLatin1String("1"));
0268     QCOMPARE(model->index(0,2).data().toString(), QLatin1String("<class 'int'>"));
0269     QCOMPARE(model->index(0,3).data().toString(), QLatin1String("28")); // 28 bytes for Python's int object
0270 
0271     evalExp(QLatin1String("del test;"));
0272 }
0273 
0274 void TestPython3::testVariableCleanupAfterRestart()
0275 {
0276     QAbstractItemModel* model = session()->variableModel();
0277     QVERIFY(model != nullptr);
0278 
0279     auto* e = evalExp(QLatin1String("a = 15; b = 'S';"));
0280     QVERIFY(e != nullptr);
0281 
0282     if(session()->status()==Cantor::Session::Running)
0283         waitForSignal(session(), SIGNAL(statusChanged(Cantor::Session::Status)));
0284 
0285     QCOMPARE(2, model->rowCount());
0286 
0287     session()->logout();
0288     session()->login();
0289 
0290     QCOMPARE(0, model->rowCount());
0291 }
0292 
0293 void TestPython3::testDictVariable()
0294 {
0295     if (!PythonSettings::variableManagement())
0296         QSKIP("This test needs enabled variable management in Python3 settings", SkipSingle);
0297 
0298     auto* model = session()->variableModel();
0299     QVERIFY(model != nullptr);
0300 
0301     auto* e = evalExp(QLatin1String("d = {'value': 33}"));
0302 
0303     QVERIFY(e != nullptr);
0304 
0305     if(session()->status() == Cantor::Session::Running)
0306         waitForSignal(session(), SIGNAL(statusChanged(Cantor::Session::Status)));
0307 
0308     QCOMPARE(1, static_cast<QAbstractItemModel*>(model)->rowCount());
0309     QCOMPARE(model->index(0,0).data().toString(), QLatin1String("d"));
0310     QCOMPARE(model->index(0,1).data().toString(), QLatin1String("{'value': 33}"));
0311 
0312     evalExp(QLatin1String("del d"));
0313 }
0314 
0315 void TestPython3::testInterrupt()
0316 {
0317     auto* e1 = session()->evaluateExpression(QLatin1String("import time; time.sleep(150)"));
0318     auto* e2 = session()->evaluateExpression(QLatin1String("2"));
0319 
0320     if (e1->status() != Cantor::Expression::Queued)
0321         waitForSignal(e1, SIGNAL(statusChanged(Cantor::Expression::Status)));
0322 
0323     if (e1->status() != Cantor::Expression::Computing)
0324         waitForSignal(e1, SIGNAL(statusChanged(Cantor::Expression::Status)));
0325 
0326     if (e2->status() != Cantor::Expression::Queued)
0327         waitForSignal(e2, SIGNAL(statusChanged(Cantor::Expression::Status)));
0328 
0329     // Without this delay, server don't interrupt even if got interrupt signal (via OS kill)
0330     // Also, if the server won't interrupt, the test will fail without reasonable reason
0331     QTest::qWait(100);
0332 
0333     QCOMPARE(e1->status(), Cantor::Expression::Computing);
0334     QCOMPARE(e2->status(), Cantor::Expression::Queued);
0335 
0336     while(session()->status() != Cantor::Session::Running)
0337         waitForSignal(session(), SIGNAL(statusChanged(Cantor::Session::Status)));
0338 
0339     session()->interrupt();
0340 
0341     while(session()->status() != Cantor::Session::Done)
0342         waitForSignal(session(), SIGNAL(statusChanged(Cantor::Session::Status)));
0343 
0344     QCOMPARE(e1->status(), Cantor::Expression::Interrupted);
0345     QCOMPARE(e2->status(), Cantor::Expression::Interrupted);
0346 
0347     auto* e = evalExp(QLatin1String("2+2"));
0348     QVERIFY(e != nullptr);
0349 
0350     qDebug()<<"### session status 1 " << session()->status();
0351     qDebug()<<"### expression status 1 " << e->status();
0352     if (session()->status() == Cantor::Session::Running)
0353         waitForSignal(session(), SIGNAL(statusChanged(Cantor::Session::Status)));
0354 
0355     qDebug()<<"### session status 2 " << session()->status();
0356     qDebug()<<"### expression status 2 " << e->status();
0357     QCOMPARE(e->status(), Cantor::Expression::Done);
0358     QVERIFY(e->result());
0359     QCOMPARE(e->result()->data().toString(), QLatin1String("4"));
0360 }
0361 
0362 void TestPython3::testWarning()
0363 {
0364     auto* e = evalExp(QLatin1String("import warnings; warnings.warn('Test')"));
0365 
0366     QVERIFY(e != nullptr);
0367 
0368     if (session()->status() == Cantor::Session::Running)
0369         waitForSignal(session(), SIGNAL(statusChanged(Cantor::Session::Status)));
0370 
0371     QCOMPARE(e->status(), Cantor::Expression::Status::Done);
0372     QCOMPARE(e->results().size(), 1);
0373 }
0374 
0375 QTEST_MAIN(TestPython3)