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 }