File indexing completed on 2024-04-28 11:20:37

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     QSKIP("doesn't work on CI", SkipSingle);
0163 
0164     if (!PythonSettings::integratePlots())
0165         QSKIP("This test needs enabled plots integration in Python3 settings", SkipSingle);
0166 
0167     auto* e = evalExp(QLatin1String(
0168         "import matplotlib\n"
0169         "import matplotlib.pyplot as plt\n"
0170         "import numpy as np"
0171     ));
0172     QVERIFY(e != nullptr);
0173     QVERIFY(e->errorMessage().isEmpty() == true);
0174 
0175     //the variable model shouldn't have any entries after the module imports
0176     QAbstractItemModel* model = session()->variableModel();
0177     QVERIFY(model != nullptr);
0178     QVERIFY(model->rowCount() == 0);
0179 
0180     //create data for plotting
0181     e = evalExp(QLatin1String(
0182         "t = np.arange(0.0, 2.0, 0.01)\n"
0183         "s = 1 + np.sin(2 * np.pi * t)"
0184     ));
0185     QVERIFY(e != nullptr);
0186     QVERIFY(e->errorMessage().isEmpty() == true);
0187 
0188     if(session()->status()==Cantor::Session::Running)
0189         waitForSignal(session(), SIGNAL(statusChanged(Cantor::Session::Status)));
0190     //the variable model should have two entries now
0191     QVERIFY(model->rowCount() == 2);
0192 
0193     //plot the data and check the results
0194     e = evalExp(QLatin1String(
0195         "plt.plot(t,s)\n"
0196         "plt.show()"
0197     ));
0198 
0199     QVERIFY(e != nullptr);
0200     if (e->result() == nullptr)
0201         waitForSignal(e, SIGNAL(gotResult()));
0202 
0203     QVERIFY(e->errorMessage().isEmpty() == true);
0204     QVERIFY(model->rowCount() == 2); //still only two variables
0205 
0206     //there must be one single image result
0207     QVERIFY(e->results().size() == 1);
0208     const Cantor::ImageResult* result = dynamic_cast<const Cantor::ImageResult*>(e->result());
0209     QVERIFY(result != nullptr);
0210 
0211     evalExp(QLatin1String("del t; del s"));
0212 }
0213 
0214 void TestPython3::testVariablesCreatingFromCode()
0215 {
0216     if (!PythonSettings::variableManagement())
0217         QSKIP("This test needs enabled variable management in Python3 settings", SkipSingle);
0218 
0219     QAbstractItemModel* model = session()->variableModel();
0220     QVERIFY(model != nullptr);
0221 
0222     auto* e = evalExp(QLatin1String("a = 15; b = 'S';"));
0223     QVERIFY(e != nullptr);
0224 
0225     if(session()->status() == Cantor::Session::Running)
0226         waitForSignal(session(), SIGNAL(statusChanged(Cantor::Session::Status)));
0227 
0228     QCOMPARE(2, model->rowCount());
0229 
0230     QCOMPARE(model->index(0,0).data().toString(), QLatin1String("a"));
0231     QCOMPARE(model->index(0,1).data().toString(), QLatin1String("15"));
0232 
0233     QCOMPARE(model->index(1,0).data().toString(), QLatin1String("b"));
0234     QCOMPARE(model->index(1,1).data().toString(), QLatin1String("'S'"));
0235 
0236     evalExp(QLatin1String("del a; del b"));
0237 }
0238 
0239 void TestPython3::testVariableChangeSizeType()
0240 {
0241     if (!PythonSettings::variableManagement())
0242         QSKIP("This test needs enabled variable management in Python3 settings", SkipSingle);
0243 
0244     QAbstractItemModel* model = session()->variableModel();
0245     QVERIFY(model != nullptr);
0246 
0247     // create a text variable
0248     auto* e = evalExp(QLatin1String("test = \"abcd\";"));
0249     QVERIFY(e != nullptr);
0250 
0251     if(session()->status() == Cantor::Session::Running)
0252         waitForSignal(session(), SIGNAL(statusChanged(Cantor::Session::Status)));
0253 
0254     QCOMPARE(1, model->rowCount());
0255     QCOMPARE(model->index(0,0).data().toString(), QLatin1String("test"));
0256     QCOMPARE(model->index(0,1).data().toString(), QLatin1String("'abcd'"));
0257     QCOMPARE(model->index(0,2).data().toString(), QLatin1String("<class 'str'>"));
0258     QCOMPARE(model->index(0,3).data().toString(), QLatin1String("53"));
0259 
0260     // change from string to integer
0261     e = evalExp(QLatin1String("test = 1;"));
0262     QVERIFY(e != nullptr);
0263 
0264     if(session()->status() == Cantor::Session::Running)
0265         waitForSignal(session(), SIGNAL(statusChanged(Cantor::Session::Status)));
0266 
0267     QCOMPARE(1, model->rowCount());
0268     QCOMPARE(model->index(0,0).data().toString(), QLatin1String("test"));
0269     QCOMPARE(model->index(0,1).data().toString(), QLatin1String("1"));
0270     QCOMPARE(model->index(0,2).data().toString(), QLatin1String("<class 'int'>"));
0271     QCOMPARE(model->index(0,3).data().toString(), QLatin1String("28")); // 28 bytes for Python's int object
0272 
0273     evalExp(QLatin1String("del test;"));
0274 }
0275 
0276 void TestPython3::testVariableCleanupAfterRestart()
0277 {
0278     QAbstractItemModel* model = session()->variableModel();
0279     QVERIFY(model != nullptr);
0280 
0281     auto* e = evalExp(QLatin1String("a = 15; b = 'S';"));
0282     QVERIFY(e != nullptr);
0283 
0284     if(session()->status()==Cantor::Session::Running)
0285         waitForSignal(session(), SIGNAL(statusChanged(Cantor::Session::Status)));
0286 
0287     QCOMPARE(2, model->rowCount());
0288 
0289     session()->logout();
0290     session()->login();
0291 
0292     QCOMPARE(0, model->rowCount());
0293 }
0294 
0295 void TestPython3::testDictVariable()
0296 {
0297     if (!PythonSettings::variableManagement())
0298         QSKIP("This test needs enabled variable management in Python3 settings", SkipSingle);
0299 
0300     auto* model = session()->variableModel();
0301     QVERIFY(model != nullptr);
0302 
0303     auto* e = evalExp(QLatin1String("d = {'value': 33}"));
0304 
0305     QVERIFY(e != nullptr);
0306 
0307     if(session()->status() == Cantor::Session::Running)
0308         waitForSignal(session(), SIGNAL(statusChanged(Cantor::Session::Status)));
0309 
0310     QCOMPARE(1, static_cast<QAbstractItemModel*>(model)->rowCount());
0311     QCOMPARE(model->index(0,0).data().toString(), QLatin1String("d"));
0312     QCOMPARE(model->index(0,1).data().toString(), QLatin1String("{'value': 33}"));
0313 
0314     evalExp(QLatin1String("del d"));
0315 }
0316 
0317 void TestPython3::testInterrupt()
0318 {
0319     QSKIP("doesn't work on CI", SkipSingle);
0320 
0321     auto* e1 = session()->evaluateExpression(QLatin1String("import time; time.sleep(150)"));
0322     auto* e2 = session()->evaluateExpression(QLatin1String("2"));
0323 
0324     if (e1->status() != Cantor::Expression::Queued)
0325         waitForSignal(e1, SIGNAL(statusChanged(Cantor::Expression::Status)));
0326 
0327     if (e1->status() != Cantor::Expression::Computing)
0328         waitForSignal(e1, SIGNAL(statusChanged(Cantor::Expression::Status)));
0329 
0330     if (e2->status() != Cantor::Expression::Queued)
0331         waitForSignal(e2, SIGNAL(statusChanged(Cantor::Expression::Status)));
0332 
0333     // Without this delay, server don't interrupt even if got interrupt signal (via OS kill)
0334     // Also, if the server won't interrupt, the test will fail without reasonable reason
0335     QTest::qWait(100);
0336 
0337     QCOMPARE(e1->status(), Cantor::Expression::Computing);
0338     QCOMPARE(e2->status(), Cantor::Expression::Queued);
0339 
0340     while(session()->status() != Cantor::Session::Running)
0341         waitForSignal(session(), SIGNAL(statusChanged(Cantor::Session::Status)));
0342 
0343     session()->interrupt();
0344 
0345     while(session()->status() != Cantor::Session::Done)
0346         waitForSignal(session(), SIGNAL(statusChanged(Cantor::Session::Status)));
0347 
0348     QCOMPARE(e1->status(), Cantor::Expression::Interrupted);
0349     QCOMPARE(e2->status(), Cantor::Expression::Interrupted);
0350 
0351     auto* e = evalExp(QLatin1String("2+2"));
0352     QVERIFY(e != nullptr);
0353 
0354     if (session()->status() == Cantor::Session::Running)
0355         waitForSignal(session(), SIGNAL(statusChanged(Cantor::Session::Status)));
0356 
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)