File indexing completed on 2024-04-28 16:08:41

0001 /***************************************************************************
0002  *   Copyright (C) 2013-2017 by Linuxstopmotion contributors;              *
0003  *   see the AUTHORS file for details.                                     *
0004  *                                                                         *
0005  *   This program is free software; you can redistribute it and/or modify  *
0006  *   it under the terms of the GNU General Public License as published by  *
0007  *   the Free Software Foundation; either version 2 of the License, or     *
0008  *   (at your option) any later version.                                   *
0009  *                                                                         *
0010  *   This program is distributed in the hope that it will be useful,       *
0011  *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
0012  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
0013  *   GNU General Public License for more details.                          *
0014  *                                                                         *
0015  *   You should have received a copy of the GNU General Public License     *
0016  *   along with this program; if not, write to the                         *
0017  *   Free Software Foundation, Inc.,                                       *
0018  *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
0019  ***************************************************************************/
0020 #include "texecutor.h"
0021 
0022 #include <QtTest/QtTest>
0023 
0024 #include <string.h>
0025 #include <memory>
0026 #include <limits>
0027 #include <stdint.h>
0028 #include <cstdlib>
0029 #include <stdio.h>
0030 #include <sstream>
0031 #include <exception>
0032 #include <assert.h>
0033 #include <utility>
0034 
0035 #include "src/domain/undo/executor.h"
0036 #include "src/domain/undo/command.h"
0037 #include "src/domain/undo/commandlogger.h"
0038 #include "src/domain/undo/filelogger.h"
0039 #include "src/domain/animation/errorhandler.h"
0040 
0041 #include "hash.h"
0042 #include "testundo.h"
0043 #include "oomtestutil.h"
0044 
0045 
0046 
0047 static const int32_t no_num = std::numeric_limits<int32_t>::min();
0048 
0049 class TestException : public std::exception {
0050     const char* msg;
0051 public:
0052     TestException(const char* message) : msg(message) {
0053     }
0054     const char* what() const throw() {
0055         return msg;
0056     }
0057 };
0058 
0059 /**
0060  * Test factory for commands that test parsing. When executed, they write a
0061  * string of their contents to a list of strings.
0062  */
0063 class EmptyTestCommandFactory : public CommandFactory {
0064     std::string name;
0065     typedef TestCommandFactory::output_t output_t;
0066     output_t& output;
0067 public:
0068     EmptyTestCommandFactory(const char* nameForCommand,
0069             output_t& executionOutput)
0070             : name(nameForCommand), output(executionOutput) {
0071     }
0072     class EtCommand : public Command {
0073     public:
0074         std::string name;
0075         output_t& output;
0076         std::string s1;
0077         int32_t i1;
0078         int32_t i2;
0079         EtCommand(std::string commandName, output_t& out)
0080                 : name(commandName), output(out),
0081                   s1(""), i1(no_num), i2(no_num) {
0082         }
0083         Command* execute() {
0084             std::stringstream ss;
0085             ss << name << ",i:" << i1 << ",s:" << s1 << ",i:" << i2;
0086             output.push_back(ss.str());
0087             return createNullCommand();
0088         }
0089         bool operator==(const EtCommand& other) const {
0090             return name == other.name
0091                     && s1 == other.s1
0092                     && i1 == other.i1
0093                     && i2 == other.i2;
0094         }
0095         bool operator!=(const EtCommand& other) const {
0096             return !(*this == other);
0097         }
0098     };
0099     ~EmptyTestCommandFactory() {
0100     }
0101     Command* create(Parameters& ps, ErrorHandler&) {
0102         EtCommand* e = new EtCommand(name, output);
0103         e->i1 = ps.getInteger(-RAND_MAX/2, RAND_MAX/2);
0104         ps.getString(e->s1, 0);
0105         e->i2 = ps.getInteger(-RAND_MAX/2, RAND_MAX/2);
0106         return e;
0107     }
0108     void Fail(const char* s) {
0109         QFAIL(s);
0110     }
0111 };
0112 
0113 /**
0114  * Re-executes the command that is logged.
0115  * This is used to test that executing from the log is the same as executing
0116  * normally, without the bother of actually using a log; the logged commands
0117  * are executed as we go along. We can then test the results of executing from
0118  * the log against the results of executing the commands normally.
0119  */
0120 class CloneLogger : public CommandLogger {
0121     Executor* ex;
0122     std::string command;
0123     bool alreadyIn;
0124     class AlreadyIn {
0125         bool& r;
0126     public:
0127         AlreadyIn(bool& a) : r(a) {
0128             r = true;
0129         }
0130         ~AlreadyIn() {
0131             r = false;
0132         }
0133     };
0134 public:
0135     CloneLogger() : ex(0), alreadyIn(false) {
0136     }
0137     ~CloneLogger() {
0138     }
0139     /** @param e Ownership is not passed. */
0140     void SetExecutor(Executor* e) {
0141         ex = e;
0142     }
0143     void writePendingCommand(const char* lineToLog) {
0144         command = lineToLog;
0145     }
0146     void commit() {
0147         if (!alreadyIn) {
0148             // Make sure we don't recursively call ourselves
0149             AlreadyIn a(alreadyIn);
0150             // Some tests are not logging; don't try to execute "!"
0151             if (!command.empty()) {
0152                 command.append(1, '!');
0153                 ex->executeFromLog(command.c_str(), *ErrorHandler::getThrower());
0154             }
0155         }
0156     }
0157     void writePendingUndo() {
0158         assert(false);
0159     }
0160     void writePendingRedo() {
0161         assert(false);
0162     }
0163     void flush() {
0164         // For the tests we will assume that commit() never fails, so we do not
0165         // need an implementation here.
0166     }
0167 };
0168 
0169 TestCommandFactory::TestCommandFactory()
0170         : ce(0), cl(0), str(0), strNext(0), strAllocLen(0) {
0171     cl = new CloneLogger();
0172     ce = makeExecutor();
0173     ce->setCommandLogger(cl);
0174     cl->SetExecutor(ce);
0175     std::unique_ptr<CommandFactory> et(
0176             new EmptyTestCommandFactory("et", executionOutput));
0177     std::unique_ptr<CommandFactory> sec(
0178             new EmptyTestCommandFactory("sec", executionOutput));
0179     ce->addCommand("et", std::move(et));
0180     ce->addCommand("sec", std::move(sec));
0181 }
0182 
0183 TestCommandFactory::~TestCommandFactory() {
0184     delete ce;
0185     delete[] str;
0186 }
0187 
0188 void TestCommandFactory::AddCharToRandomString(char c) {
0189     if (strNext == str + strAllocLen) {
0190         int32_t newLen = strAllocLen == 0? 64 : strAllocLen * 2;
0191         char* newStr = new char[newLen];
0192         strncpy(newStr, str, strAllocLen);
0193         delete[] str;
0194         strNext = newStr + (strNext - str);
0195         str = newStr;
0196         strAllocLen = newLen;
0197     }
0198     *strNext = c;
0199     ++strNext;
0200 }
0201 
0202 const char* TestCommandFactory::RandomString() {
0203     strNext = str;
0204     int type = rand() % 4;
0205     while (type != 0) {
0206         switch (type) {
0207         case 1:
0208             AddCharToRandomString('0' + (rand() % 10));
0209             break;
0210         case 2:
0211             AddCharToRandomString('a' + (rand() % 26));
0212             break;
0213         default:
0214             AddCharToRandomString('A' + (rand() % 26));
0215             break;
0216         }
0217         type = rand() % 4;
0218     }
0219     AddCharToRandomString('\0');
0220     return str;
0221 }
0222 
0223 void TestCommandFactory::emptyCommandReplayerThrows() {
0224     try {
0225         ce->execute("fakeCommand");
0226     } catch (UnknownCommandException& e) {
0227         return;
0228     }
0229     QFAIL("Empty CommandReplayer did not throw "
0230             "CommandFactoryNoSuchCommandException");
0231 }
0232 
0233 void TestCommandFactory::canParseFromLog() {
0234     executionOutput.clear();
0235     ce->executeFromLog("et -5 \"hello world!\" 412345!", *ErrorHandler::getThrower());
0236     QCOMPARE(executionOutput.begin()->c_str(),
0237             "et,i:-5,s:hello world!,i:412345");
0238 }
0239 
0240 int32_t randomInt() {
0241     return rand() - RAND_MAX/2;
0242 }
0243 
0244 void TestCommandFactory::parsingDescriptionIsCloning() {
0245     char description[512];
0246     const size_t desLen = sizeof(description)/sizeof(description[0]) - 1;
0247     description[desLen] = '\0';
0248     executionOutput.clear();
0249     for (int i = 0; i != 100; ++i) {
0250         const char* commandName = rand() % 2 == 0? "et" : "sec";
0251         int32_t i1 = randomInt();
0252         int32_t i2 = randomInt();
0253         std::string s1 = RandomString();
0254         ce->execute(commandName, i1, s1.c_str(), i2);
0255         output_t::iterator eo1 = executionOutput.begin();
0256         output_t::iterator eo2 = eo1;
0257         ++eo2;
0258         QCOMPARE(eo1->c_str(), eo2->c_str());
0259     }
0260 }
0261 
0262 namespace {
0263 const char alphanumeric[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
0264             "abcdefghijklmnopqrstuvwxyz";
0265 }
0266 
0267 class AddCharFactory : public CommandFactory {
0268     std::string* model;
0269 public:
0270     class AddChar : public Command {
0271         std::string* m;
0272         char c;
0273         int32_t p;
0274     public:
0275         AddChar(std::string& model, int32_t character, int32_t position)
0276             : m(&model), c(character), p(position) {
0277         }
0278         Command* execute();
0279     };
0280     AddCharFactory(std::string* m) : model(m) {
0281     }
0282     Command* create(Parameters& ps, ErrorHandler&) {
0283         int32_t i = ps.getInteger(0, sizeof(alphanumeric) - 1);
0284         int32_t character = alphanumeric[i];
0285         int32_t position = ps.getInteger(0, model->length());
0286         return new AddChar(*model, character, position);
0287     }
0288 };
0289 
0290 class DelCharFactory : public CommandFactory {
0291     std::string* model;
0292 public:
0293     class DelChar : public Command {
0294         std::string* m;
0295         int32_t p;
0296     public:
0297         DelChar(std::string& model, int32_t position)
0298             : m(&model), p(position) {
0299         }
0300         Command* execute();
0301     };
0302     DelCharFactory(std::string* m) : model(m) {
0303     }
0304     Command* create(Parameters& ps, ErrorHandler&) {
0305         int32_t len = model->length();
0306         if (len == 0)
0307             return 0;
0308         int32_t position = ps.getInteger(0, len - 1);
0309         return new DelChar(*model, position);
0310     }
0311 };
0312 
0313 Command* AddCharFactory::AddChar::execute() {
0314     if (p < 0 || static_cast<int32_t>(m->length()) < p)
0315         throw TestException("AddCharFactory parameters out-of-range");
0316     // insert might throw, so use an unique_ptr to avoid leaks.
0317     std::unique_ptr<Command> inv(new DelCharFactory::DelChar(*m, p));
0318     std::string::iterator i = m->begin();
0319     i += p;
0320     m->insert(i, c);
0321     delete this;
0322     return inv.release();
0323 }
0324 
0325 Command* DelCharFactory::DelChar::execute() {
0326     if (m->size() == 0) {
0327         return createNullCommand();
0328     }
0329     char removedChar = (*m)[p];
0330     Command* inv = new AddCharFactory::AddChar(*m, removedChar, p);
0331     m->erase(p, 1);
0332     delete this;
0333     return inv;
0334 }
0335 
0336 class StringModelTestHelper : public ModelTestHelper {
0337     // not owned
0338     std::string* s;
0339 public:
0340     StringModelTestHelper(std::string& model) : s(&model) {
0341     }
0342     ~StringModelTestHelper() {
0343     }
0344     void resetModel(Executor&) {
0345         s->clear();
0346     }
0347     Hash hashModel(const Executor&) {
0348         Hash h;
0349         h.add(s->c_str());
0350         return h;
0351     }
0352     void dumpModel(std::string& out, const Executor&) {
0353         out = *s;
0354     }
0355 };
0356 
0357 class AddDelTestBed {
0358     enum { lineBufferSize = 512 };
0359     char lineBuffer[lineBufferSize];
0360     std::string finalString;
0361     std::string originalString;
0362     std::string expected;
0363     std::unique_ptr<CommandFactory> af;
0364     std::unique_ptr<CommandFactory> df;
0365     std::unique_ptr<FileCommandLogger> logger;
0366     std::unique_ptr<Executor> ex;
0367     FILE* logFile;
0368     StringModelTestHelper helper;
0369 public:
0370     AddDelTestBed() :
0371             af(new AddCharFactory(&finalString)),
0372             df(new DelCharFactory(&finalString)),
0373             logger(new FileCommandLogger),
0374             ex(makeExecutor()),
0375             logFile(0),
0376             helper(finalString) {
0377         ex->setCommandLogger(logger->getLogger());
0378         ex->addCommand("add", std::move(af), true);
0379         ex->addCommand("del", std::move(df));
0380         lineBuffer[lineBufferSize - 1] = '\0';
0381     }
0382     void init(const char* initialString) {
0383         finalString = initialString;
0384         originalString = finalString;
0385         logFile = tmpfile();
0386         // ownership of logFile is passed here
0387         logger->setLogFile(logFile);
0388         ex->clearHistory();
0389     }
0390     void testUndo() {
0391         ::testUndo(*ex, helper);
0392     }
0393 };
0394 
0395 void TestCommandFactory::testUndo() {
0396     AddDelTestBed test;
0397     test.testUndo();
0398 }
0399 
0400 void TestCommandFactory::replayIsRobust() {
0401     loadOomTestUtil();
0402     setMallocsUntilFailure(0);
0403     QVERIFY2(0 == malloc(1), "SetMallocsUntilFailure(0) not working");
0404     setMallocsUntilFailure(1);
0405     void* shouldSucceed = malloc(1);
0406     QVERIFY2(shouldSucceed,
0407             "SetMallocsUntilFailure not allowing mallocs at all");
0408     free(shouldSucceed);
0409     QVERIFY2(0 == malloc(1), "SetMallocsUntilFailure(1) not working");
0410 }