Warning, file /education/cantor/src/backends/maxima/testmaxima.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: 2009 Alexander Rieder <alexanderrieder@gmail.com>
0004     SPDX-FileCopyrightText: 2018-2022 by Alexander Semke (alexander.semke@web.de)
0005 */
0006 
0007 #include "testmaxima.h"
0008 
0009 #include "session.h"
0010 #include "backend.h"
0011 #include "expression.h"
0012 #include "result.h"
0013 #include "textresult.h"
0014 #include "imageresult.h"
0015 #include "epsresult.h"
0016 #include "syntaxhelpobject.h"
0017 #include "completionobject.h"
0018 #include "defaultvariablemodel.h"
0019 
0020 #include <config-cantorlib.h>
0021 
0022 QString TestMaxima::backendName()
0023 {
0024     return QLatin1String("maxima");
0025 }
0026 
0027 void TestMaxima::initTestCase() {
0028     if (QStandardPaths::findExecutable(QLatin1String("maxima")).isEmpty())
0029         QSKIP("Maxima executable not found");
0030     BackendTest::initTestCase();
0031 }
0032 
0033 void TestMaxima::testSimpleCommand()
0034 {
0035     auto* e = evalExp( QLatin1String("2+2") );
0036 
0037     QVERIFY( e!=nullptr );
0038     QVERIFY( e->result()!=nullptr );
0039 
0040     QCOMPARE( cleanOutput( e->result()->data().toString() ), QLatin1String("4") );
0041 }
0042 
0043 void TestMaxima::testMultilineCommand()
0044 {
0045     auto* e = evalExp( QLatin1String("2+2;3+3") );
0046 
0047     QVERIFY(e != nullptr);
0048     QVERIFY(e->results().size() == 2);
0049 
0050     QCOMPARE(e->results().at(0)->data().toString(), QLatin1String("4"));
0051     QCOMPARE(e->results().at(1)->data().toString(), QLatin1String("6"));
0052 }
0053 
0054 //WARNING: for this test to work, Integration of Plots must be enabled
0055 //and CantorLib must be compiled with EPS-support
0056 void TestMaxima::testPlot()
0057 {
0058     if(QStandardPaths::findExecutable(QLatin1String("gnuplot")).isNull())
0059         QSKIP("gnuplot not found, maxima needs it for plotting", SkipSingle);
0060 
0061     auto* e = evalExp( QLatin1String("plot2d(sin(x), [x, -10,10])") );
0062 
0063     QVERIFY( e!=nullptr );
0064     QVERIFY( e->result()!=nullptr );
0065 
0066     if(session()->status() == Cantor::Session::Running)
0067         waitForSignal(session(), SIGNAL(statusChanged(Cantor::Session::Status)));
0068 
0069     QCOMPARE( e->result()->type(), (int)Cantor::ImageResult::Type );
0070     QVERIFY( !e->result()->data().isNull() );
0071     QVERIFY( e->errorMessage().isNull() );
0072 }
0073 
0074 void TestMaxima::testPlotMultiline()
0075 {
0076     if(QStandardPaths::findExecutable(QLatin1String("gnuplot")).isNull())
0077         QSKIP("gnuplot not found, maxima needs it for plotting", SkipSingle);
0078 
0079     auto* e = evalExp(QLatin1String(
0080                 "plot2d (x^2-y^3+3*y=2,\n"
0081                 "[x,-2.5,2.5],\n"
0082                 "[y,-2.5,2.5])"
0083     ));
0084 
0085     QVERIFY(e != nullptr);
0086     QVERIFY(e->result() != nullptr);
0087 
0088     if(session()->status() == Cantor::Session::Running)
0089         waitForSignal(session(), SIGNAL(statusChanged(Cantor::Session::Status)));
0090 
0091     QCOMPARE(e->result()->type(), (int)Cantor::ImageResult::Type);
0092     QVERIFY(!e->result()->data().isNull());
0093     QVERIFY(e->errorMessage().isNull());
0094 }
0095 
0096 void TestMaxima::testPlotWithWhitespaces()
0097 {
0098     if(QStandardPaths::findExecutable(QLatin1String("gnuplot")).isNull())
0099         QSKIP("gnuplot not found, maxima needs it for plotting", SkipSingle);
0100 
0101     auto* e = evalExp(QLatin1String(
0102                 "\n"
0103                 "plot2d (x^2-y^3+3*y=2,\n"
0104                 "[x,-2.5,2.5],\n"
0105                 "[y,-2.5,2.5])"
0106                 "\n"
0107     ));
0108 
0109     QVERIFY(e != nullptr);
0110     QVERIFY(e->result() != nullptr);
0111 
0112     if(session()->status() == Cantor::Session::Running)
0113         waitForSignal(session(), SIGNAL(statusChanged(Cantor::Session::Status)));
0114 
0115     QCOMPARE(e->result()->type(), (int)Cantor::ImageResult::Type);
0116     QVERIFY(!e->result()->data().isNull());
0117     QVERIFY(e->errorMessage().isNull());
0118 }
0119 
0120 void TestMaxima::testPlotWithAnotherTextResults()
0121 {
0122     if(QStandardPaths::findExecutable(QLatin1String("gnuplot")).isNull())
0123         QSKIP("gnuplot not found, maxima needs it for plotting", SkipSingle);
0124 
0125     auto* e = evalExp( QLatin1String(
0126         "2*2; \n"
0127         "plot2d(sin(x), [x, -10,10]); \n"
0128         "4*4;"
0129     ));
0130 
0131     if(session()->status() == Cantor::Session::Running)
0132         waitForSignal(session(), SIGNAL(statusChanged(Cantor::Session::Status)));
0133 
0134     QVERIFY( e!=nullptr );
0135     QVERIFY( e->errorMessage().isNull() );
0136     QCOMPARE(e->results().size(), 3);
0137 
0138     QCOMPARE(e->results().at(0)->data().toString(), QLatin1String("4"));
0139 
0140     QCOMPARE( e->results().at(1)->type(), (int)Cantor::ImageResult::Type );
0141     QVERIFY( !e->results().at(1)->data().isNull() );
0142 
0143     QCOMPARE(e->results().at(2)->data().toString(), QLatin1String("16"));
0144 }
0145 
0146 void TestMaxima::testDraw()
0147 {
0148     if(QStandardPaths::findExecutable(QLatin1String("gnuplot")).isNull())
0149         QSKIP("gnuplot not found, maxima needs it for plotting", SkipSingle);
0150 
0151     auto* e = evalExp( QLatin1String("draw3d(explicit(x^2+y^2,x,-1,1,y,-1,1))") );
0152 
0153     QVERIFY(e != nullptr);
0154     QVERIFY(e->result() != nullptr);
0155 
0156     if(!e->result())
0157         waitForSignal(e, SIGNAL(gotResult()));
0158 
0159     QCOMPARE( e->result()->type(), (int)Cantor::ImageResult::Type );
0160     QVERIFY( !e->result()->data().isNull() );
0161     QVERIFY( e->errorMessage().isNull() );
0162 }
0163 
0164 void TestMaxima::testDrawMultiline()
0165 {
0166     if(QStandardPaths::findExecutable(QLatin1String("gnuplot")).isNull())
0167         QSKIP("gnuplot not found, maxima needs it for plotting", SkipSingle);
0168 
0169     auto* e = evalExp( QLatin1String(
0170         "draw(\n"
0171             "gr2d(\n"
0172                 "key=\"sin (x)\",grid=[2,2],\n"
0173                 "explicit(\n"
0174                     "sin(x),\n"
0175                     "x,0,2*%pi\n"
0176                 ")\n"
0177             "),\n"
0178             "gr2d(\n"
0179                 "key=\"cos (x)\",grid=[2,2],\n"
0180                 "explicit(\n"
0181                     "cos(x),\n"
0182                     "x,0,2*%pi\n"
0183                 ")\n"
0184             "))"
0185     ));
0186 
0187 
0188     QVERIFY(e != nullptr);
0189     QVERIFY(e->result() != nullptr);
0190 
0191     if(!e->result())
0192         waitForSignal(e, SIGNAL(gotResult()));
0193 
0194     QCOMPARE(e->result()->type(), (int)Cantor::ImageResult::Type);
0195     QVERIFY(!e->result()->data().isNull());
0196     QVERIFY(e->errorMessage().isNull());
0197 }
0198 
0199 void TestMaxima::testDrawWithAnotherTextResults()
0200 {
0201     if(QStandardPaths::findExecutable(QLatin1String("gnuplot")).isNull())
0202         QSKIP("gnuplot not found, maxima needs it for plotting", SkipSingle);
0203 
0204     auto* e = evalExp( QLatin1String(
0205         "2*2; \n"
0206         "draw3d(explicit(x^2+y^2,x,-1,1,y,-1,1)); \n"
0207         "4*4;"
0208     ));
0209 
0210     if (e->results().at(1)->type() == Cantor::TextResult::Type)
0211         waitForSignal(e, SIGNAL(resultReplaced));
0212 
0213     QVERIFY( e!=nullptr );
0214     QVERIFY( e->errorMessage().isNull() );
0215     QCOMPARE(e->results().size(), 3);
0216 
0217     QCOMPARE(e->results().at(0)->data().toString(), QLatin1String("4"));
0218 
0219     QCOMPARE( e->results().at(1)->type(), (int)Cantor::ImageResult::Type );
0220     QVERIFY( !e->results().at(1)->data().isNull() );
0221 
0222     QCOMPARE(e->results().at(2)->data().toString(), QLatin1String("16"));
0223 }
0224 
0225 void TestMaxima::testInvalidSyntax()
0226 {
0227     auto* e = evalExp( QLatin1String("2+2*(") );
0228 
0229     QVERIFY( e!=nullptr );
0230     QVERIFY( e->status()==Cantor::Expression::Error );
0231     QVERIFY( !e->errorMessage().isNull() );
0232     QCOMPARE(e->results().size(), 0);
0233 }
0234 
0235 void TestMaxima::testWarning01()
0236 {
0237     auto* e = evalExp( QLatin1String("rat(0.75*10)") );
0238 
0239     QVERIFY(e != nullptr);
0240     QVERIFY(e->results().size() == 2); //two results, the warning and the actual result of the calculation
0241 
0242     //the actual warning string "rat: replaced 7.5 by 15/2 = 7.5" which we don't checked since it's translated,
0243     //we just check it's existance.
0244     auto* result = dynamic_cast<Cantor::TextResult*>(e->results().at(0));
0245     QVERIFY(e != nullptr);
0246     QVERIFY(result->data().toString().isEmpty() == false);
0247     QVERIFY(result->isWarning() == true);
0248 
0249     //the result of the calculation
0250     QCOMPARE(e->results().at(1)->data().toString(), QLatin1String("15/2"));
0251 }
0252 
0253 /*!
0254  * test the output of the tex() function which is similarly formatted as other functions producing warning
0255  * but which shouldn't be treated as a warning.
0256  * */
0257 void TestMaxima::testWarning02()
0258 {
0259     auto* e = evalExp( QLatin1String("tex(\"sin(x)\")") );
0260 
0261     QVERIFY(e != nullptr);
0262     QVERIFY(e->results().size() == 2); //two results, the TeX output and an additional 'false'
0263 
0264     //the actual TeX string is of no interest for us, we just check it's existance.
0265     auto* result = dynamic_cast<Cantor::TextResult*>(e->results().at(0));
0266     QVERIFY(e != nullptr);
0267     QVERIFY(result->data().toString().isEmpty() == false);
0268     QVERIFY(result->isWarning() == false);
0269 }
0270 
0271 void TestMaxima::testExprNumbering()
0272 {
0273     auto* e = evalExp( QLatin1String("kill(labels)") ); //first reset the labels
0274 
0275     e=evalExp( QLatin1String("2+2") );
0276     QVERIFY( e!=nullptr );
0277     int id=e->id();
0278     QCOMPARE( id, 1 );
0279 
0280     e=evalExp( QString::fromLatin1("%o%1+1" ).arg( id ) );
0281     QVERIFY( e != nullptr );
0282     QVERIFY( e->result()!=nullptr );
0283     QCOMPARE( cleanOutput( e->result()->data().toString() ), QLatin1String( "5" ) );
0284 }
0285 
0286 void TestMaxima::testCommandQueue()
0287 {
0288     //only wait for the last Expression to return, so the queue gets
0289     //actually filled
0290 
0291     auto* e1 = session()->evaluateExpression(QLatin1String("0+1"));
0292     auto* e2 = session()->evaluateExpression(QLatin1String("1+1"));
0293     auto* e3 = evalExp(QLatin1String("1+2"));
0294 
0295     QVERIFY(e1!=nullptr);
0296     QVERIFY(e2!=nullptr);
0297     QVERIFY(e3!=nullptr);
0298 
0299     QVERIFY(e1->result());
0300     QVERIFY(e2->result());
0301     QVERIFY(e3->result());
0302 
0303     QCOMPARE(cleanOutput(e1->result()->data().toString()), QLatin1String("1"));
0304     QCOMPARE(cleanOutput(e2->result()->data().toString()), QLatin1String("2"));
0305     QCOMPARE(cleanOutput(e3->result()->data().toString()), QLatin1String("3"));
0306 }
0307 
0308 void TestMaxima::testSimpleExpressionWithComment()
0309 {
0310     auto* e = evalExp(QLatin1String("/*this is a comment*/2+2"));
0311     QVERIFY(e!=nullptr);
0312     QVERIFY(e->result()!=nullptr);
0313 
0314     QCOMPARE(cleanOutput(e->result()->data().toString()), QLatin1String("4"));
0315 }
0316 
0317 void TestMaxima::testCommentExpression()
0318 {
0319     auto* e = evalExp(QLatin1String("/*this is a comment*/"));
0320     QVERIFY(e!=nullptr);
0321     QVERIFY(e->result()==nullptr||e->result()->data().toString().isEmpty());
0322 }
0323 
0324 void TestMaxima::testNestedComment()
0325 {
0326     auto* e = evalExp(QLatin1String("/*/*this is still a comment*/*/2+2/*still/*a*/comment*//**/"));
0327     QVERIFY(e!=nullptr);
0328     QVERIFY(e->result()!=nullptr);
0329 
0330     QCOMPARE(cleanOutput(e->result()->data().toString()), QLatin1String("4"));
0331 }
0332 
0333 void TestMaxima::testUnmatchedComment()
0334 {
0335     auto* e = evalExp(QLatin1String("/*this comment doesn't end here!"));
0336     QVERIFY(e!=nullptr);
0337     QVERIFY(e->result()==nullptr);
0338     QVERIFY(e->status()==Cantor::Expression::Error);
0339 }
0340 
0341 void TestMaxima::testInvalidAssignment()
0342 {
0343     auto* e = evalExp(QLatin1String("0:a"));
0344     QVERIFY(e!=nullptr);
0345     //QVERIFY(e->result()==0);
0346     //QVERIFY(e->status()==Cantor::Expression::Error);
0347 
0348     if(session()->status() == Cantor::Session::Running)
0349         waitForSignal(session(), SIGNAL(statusChanged(Cantor::Session::Status)));
0350 
0351     //make sure we didn't screw up the session
0352     auto* e2=evalExp(QLatin1String("2+2"));
0353     QVERIFY(e2!=nullptr);
0354     QVERIFY(e2->result()!=nullptr);
0355 
0356     QCOMPARE(cleanOutput(e2->result()->data().toString()), QLatin1String("4"));
0357 }
0358 
0359 void TestMaxima::testHelpRequest()
0360 {
0361     //execute "??print"
0362     auto* e = session()->evaluateExpression(QLatin1String("??print"));
0363     QVERIFY(e != nullptr);
0364 
0365     //help result will be shown, but maxima still expects further input
0366     waitForSignal(e, SIGNAL(needsAdditionalInformation(QString)));
0367     QVERIFY(e->status() != Cantor::Expression::Done);
0368     QVERIFY(e->results().size() == 1); // one HelpResult showing the possible options for the answer
0369 
0370     //ask for help for the first flag of the print command
0371     e->addInformation(QLatin1String("0"));
0372 
0373     //no further input is required, we're done
0374     if (e->status() == Cantor::Expression::Computing)
0375         waitForSignal(e, SIGNAL(statusChanged(Cantor::Expression::Status)));
0376 
0377     QVERIFY(e->status() == Cantor::Expression::Done);
0378     QVERIFY(e->results().size() == 1); // final HelpResult
0379 }
0380 
0381 void TestMaxima::testInformationRequest()
0382 {
0383     auto* e = session()->evaluateExpression(QLatin1String("integrate(x^n,x)"));
0384     QVERIFY(e!=nullptr);
0385     waitForSignal(e, SIGNAL(needsAdditionalInformation(QString)));
0386     e->addInformation(QLatin1String("N"));
0387 
0388     waitForSignal(e, SIGNAL(statusChanged(Cantor::Expression::Status)));
0389     QVERIFY(e->result()!=nullptr);
0390 
0391     QCOMPARE(cleanOutput(e->result()->data().toString()), QLatin1String("x^(n+1)/(n+1)"));
0392 }
0393 
0394 void TestMaxima::testSyntaxHelp()
0395 {
0396     auto* help = session()->syntaxHelpFor(QLatin1String("simplify_sum"));
0397     help->fetchSyntaxHelp();
0398     waitForSignal(help, SIGNAL(done()));
0399 
0400     bool trueHelpMessage= help->toHtml().contains(QLatin1String("simplify_sum"));
0401     bool problemsWithMaximaDocs = help->toHtml().contains(QLatin1String("INTERNAL-SIMPLE-FILE-ERROR"));
0402     QVERIFY(trueHelpMessage || problemsWithMaximaDocs);
0403 }
0404 
0405 void TestMaxima::testCompletion()
0406 {
0407     auto* help = session()->completionFor(QLatin1String("ask"), 3);
0408     waitForSignal(help, SIGNAL(fetchingDone()));
0409 
0410     // Checks all completions for this request
0411     // This correct for Maxima 5.41.0
0412     const QStringList& completions = help->completions();
0413     QVERIFY(completions.contains(QLatin1String("asksign")));
0414     QVERIFY(completions.contains(QLatin1String("askinteger")));
0415     QVERIFY(completions.contains(QLatin1String("askexp")));
0416 }
0417 
0418 void TestMaxima::testTextQuotes()
0419 {
0420     // check simple string
0421     auto* e1 = evalExp(QLatin1String("t1: \"test string\""));
0422     QVERIFY(e1 != nullptr);
0423 
0424     if(session()->status() == Cantor::Session::Running)
0425         waitForSignal(session(), SIGNAL(statusChanged(Cantor::Session::Status)));
0426 
0427     QVERIFY(e1->result() != nullptr);
0428     QCOMPARE(e1->result()->type(), (int)Cantor::TextResult::Type );
0429     QCOMPARE(e1->result()->data().toString(), QLatin1String("test string"));
0430 
0431     // check string with quotes inside
0432     auto* e2 = evalExp(QLatin1String("t2: \"this is a \\\"quoted string\\\"\""));
0433     QVERIFY(e2 != nullptr);
0434 
0435     if(session()->status() == Cantor::Session::Running)
0436         waitForSignal(session(), SIGNAL(statusChanged(Cantor::Session::Status)));
0437 
0438     QVERIFY(e2->result() != nullptr);
0439     QCOMPARE(e2->result()->type(), (int)Cantor::TextResult::Type );
0440     QCOMPARE(e2->result()->data().toString(), QLatin1String("this is a \"quoted string\""));
0441 }
0442 
0443 void TestMaxima::testVariableModel()
0444 {
0445     QAbstractItemModel* model = session()->variableModel();
0446     QVERIFY(model != nullptr);
0447 
0448     auto* e1 = evalExp(QLatin1String("a: 15"));
0449     auto* e2 = evalExp(QLatin1String("a: 15; b: \"Hello, world!\""));
0450     auto* e3 = evalExp(QLatin1String("l: [1,2,3]"));
0451     auto* e4 = evalExp(QLatin1String("t: \"this is a \\\"quoted string\\\"\""));
0452     QVERIFY(e1 != nullptr);
0453     QVERIFY(e2 != nullptr);
0454     QVERIFY(e3 != nullptr);
0455     QVERIFY(e4 != nullptr);
0456 
0457     if(session()->status() == Cantor::Session::Running)
0458         waitForSignal(session(), SIGNAL(statusChanged(Cantor::Session::Status)));
0459 
0460     QCOMPARE(4, model->rowCount());
0461 
0462     QVariant name = model->index(0,0).data();
0463     QCOMPARE(name.toString(),QLatin1String("a"));
0464 
0465     QVariant value = model->index(0,1).data();
0466     QCOMPARE(value.toString(),QLatin1String("15"));
0467 
0468     QVariant name1 = model->index(1,0).data();
0469     QCOMPARE(name1.toString(),QLatin1String("b"));
0470 
0471     QVariant value1 = model->index(1,1).data();
0472     QCOMPARE(value1.toString(),QLatin1String("Hello, world!"));
0473 
0474     QVariant name2 = model->index(2,0).data();
0475     QCOMPARE(name2.toString(),QLatin1String("l"));
0476 
0477     QVariant value2 = model->index(2,1).data();
0478     QCOMPARE(value2.toString(),QLatin1String("[1,2,3]"));
0479 
0480     QVariant name3 = model->index(3,0).data();
0481     QCOMPARE(name3.toString(),QLatin1String("t"));
0482 
0483     QVariant value3 = model->index(3,1).data();
0484     QCOMPARE(value3.toString(),QLatin1String("this is a \"quoted string\""));
0485 }
0486 
0487 void TestMaxima::testLispMode01()
0488 {
0489     //switch to the Lisp-mode
0490     auto* e1 = evalExp(QLatin1String("to_lisp();"));
0491     QVERIFY(e1 != nullptr);
0492 
0493     //evaluate a Lisp command and check the result
0494     auto* e2 = evalExp(QLatin1String("(cons 'a 'b)"));
0495     QVERIFY(e2 != nullptr);
0496     QVERIFY(e2->result() != nullptr);
0497     QCOMPARE(cleanOutput(e2->result()->data().toString()), QLatin1String("(A . B)"));
0498 
0499     //switch back to Maxima mode
0500     auto* e3 = evalExp(QLatin1String("(to-maxima)"));
0501     QVERIFY(e3 != nullptr);
0502 
0503     //evaluate a simple Maxima command
0504     auto* e4 = evalExp(QLatin1String("5+5"));
0505     QVERIFY(e4 != nullptr);
0506 
0507     //TODO: doesn't work in the test, works in Cantor though...
0508 //     QVERIFY(e4->result() != nullptr);
0509 //     QCOMPARE(cleanOutput(e4->result()->data().toString()), QLatin1String("10"));
0510 }
0511 
0512 void TestMaxima::testLoginLogout()
0513 {
0514     // Logout from session twice and all must works fine
0515     session()->logout();
0516     session()->logout();
0517 
0518     // Login in session twice and all must works fine
0519     session()->login();
0520     session()->login();
0521 }
0522 
0523 void TestMaxima::testRestartWhileRunning()
0524 {
0525     QSKIP("This test is not working yet.", SkipSingle);
0526     auto* e1 = session()->evaluateExpression(QLatin1String(":lisp (sleep 5)"));
0527     QVERIFY(e1 != nullptr);
0528 
0529     session()->logout();
0530 
0531     QCOMPARE(e1->status(), Cantor::Expression::Interrupted);
0532     session()->login();
0533 
0534     auto* e2=evalExp( QLatin1String("2+2") );
0535 
0536     QVERIFY(e2 != nullptr);
0537     QVERIFY(e2->result() != nullptr);
0538 
0539     QCOMPARE(cleanOutput(e2->result()->data().toString() ), QLatin1String("4"));
0540 }
0541 
0542 QTEST_MAIN( TestMaxima )
0543