1 #include "../canvas/retcon.h" 2 #include "../../libshared/net/textmode.h" 3 #include "../../libshared/net/brushes.h" 4 5 #include <QtTest/QtTest> 6 7 using namespace protocol; 8 using namespace canvas; 9 10 Q_DECLARE_METATYPE(AffectedArea) 11 12 class TestRetcon : public QObject 13 { 14 Q_OBJECT 15 private slots: testAffectedAreas_data()16 void testAffectedAreas_data() 17 { 18 QTest::addColumn<AffectedArea>("a1"); 19 QTest::addColumn<AffectedArea>("a2"); 20 QTest::addColumn<bool>("concurrent"); 21 22 // Changes to EVERYTHING are not concurrent with anything 23 QTest::newRow("everything") 24 << AffectedArea(AffectedArea::EVERYTHING, 1, QRect(1,1,10,10)) 25 << AffectedArea(AffectedArea::EVERYTHING, 2, QRect(100,100,10,10)) 26 << false; 27 QTest::newRow("everything2") 28 << AffectedArea(AffectedArea::EVERYTHING, 1, QRect(1,1,10,10)) 29 << AffectedArea(AffectedArea::PIXELS, 2, QRect(100,100,10,10)) 30 << false; 31 32 // Same layer pixel changes are concurrent when bounding rects do not intersect 33 QTest::newRow("same-layer1") 34 << AffectedArea(AffectedArea::PIXELS, 1, QRect(1,1,10,10)) 35 << AffectedArea(AffectedArea::PIXELS, 1, QRect(100,1,10,10)) 36 << true; 37 QTest::newRow("same-layer2") 38 << AffectedArea(AffectedArea::PIXELS, 1, QRect(1,1,110,10)) 39 << AffectedArea(AffectedArea::PIXELS, 1, QRect(100,1,10,10)) 40 << false; 41 42 // Pixels changes on different layers are always concurrent 43 QTest::newRow("different-layer") 44 << AffectedArea(AffectedArea::PIXELS, 1, QRect(1,1,110,10)) 45 << AffectedArea(AffectedArea::PIXELS, 2, QRect(100,1,10,10)) 46 << true; 47 48 // Changes to different domains are always concurrent (excluding the EVERYTHING domain) 49 QTest::newRow("different-domains") 50 << AffectedArea(AffectedArea::PIXELS, 1, QRect(1,1,110,10)) 51 << AffectedArea(AffectedArea::LAYERATTRS, 1, QRect(1, 1, 10, 10)) // bounds parameter is unused for this domain 52 << true; 53 QTest::newRow("different-domains2") 54 << AffectedArea(AffectedArea::PIXELS, 1, QRect(1,1,110,10)) 55 << AffectedArea(AffectedArea::ANNOTATION, 1, QRect(1, 1, 10, 10)) // bounds parameter is unused for this domain 56 << true; 57 58 // Changes to different layers attributes are concurrent 59 QTest::newRow("attrs") 60 << AffectedArea(AffectedArea::LAYERATTRS, 1) 61 << AffectedArea(AffectedArea::LAYERATTRS, 1) 62 << false; 63 QTest::newRow("attrs2") 64 << AffectedArea(AffectedArea::LAYERATTRS, 1) 65 << AffectedArea(AffectedArea::LAYERATTRS, 2) 66 << true; 67 68 // Changes to different annotations are concurrent 69 QTest::newRow("annotations") 70 << AffectedArea(AffectedArea::ANNOTATION, 1) 71 << AffectedArea(AffectedArea::ANNOTATION, 2) 72 << true; 73 QTest::newRow("annotations2") 74 << AffectedArea(AffectedArea::ANNOTATION, 2) 75 << AffectedArea(AffectedArea::ANNOTATION, 2) 76 << false; 77 } 78 testAffectedAreas()79 void testAffectedAreas() 80 { 81 QFETCH(AffectedArea, a1); 82 QFETCH(AffectedArea, a2); 83 QFETCH(bool, concurrent); 84 QCOMPARE(a1.isConcurrentWith(a2), concurrent); 85 } 86 testConcurrent()87 void testConcurrent() 88 { 89 LocalFork lf; 90 MessagePtr localMsg1 = msg("1 classicdabs layer=0x0101 x=10 y=10 color=#00ffffff mode=1 {\n" 91 "0 0 512 255 255\n" 92 "5 5 512 255 255\n}"); 93 94 MessagePtr localMsg2 = msg("1 classicdabs layer=0x0101 x=20 y=10 color=#00ffffff mode=1 {\n" 95 "0 0 512 255 255\n" 96 "5 0 512 255 255\n}"); 97 98 MessagePtr remoteMsg1 = msg("2 classicdabs layer=0x0101 x=100 y=10 color=#00ffffff mode=1 {\n" 99 "0 0 512 255 255\n" 100 "5 5 512 255 255\n}"); 101 102 MessagePtr remoteMsg2 = msg("2 classicdabs layer=0x0101 x=120 y=10 color=#00ffffff mode=1 {\n" 103 "0 0 512 255 255\n" 104 "5 0 512 255 255\n}"); 105 106 // First, we'll add our own local messages 107 lf.addLocalMessage( 108 localMsg1, 109 AffectedArea(AffectedArea::PIXELS, 1, localMsg1.cast<DrawDabsClassic>().bounds()) 110 ); 111 lf.addLocalMessage( 112 localMsg2, 113 AffectedArea(AffectedArea::PIXELS, 1, localMsg2.cast<DrawDabsClassic>().bounds()) 114 ); 115 116 // Then, we'll "receive" messages from the server 117 // The received messages are concurrent (do not intersect) with out local fork 118 119 QCOMPARE( 120 lf.handleReceivedMessage( 121 remoteMsg1, 122 AffectedArea(AffectedArea::PIXELS, 1, remoteMsg1.cast<DrawDabsClassic>().bounds()) 123 ), 124 LocalFork::CONCURRENT 125 ); 126 QCOMPARE( 127 lf.handleReceivedMessage( 128 remoteMsg2, 129 AffectedArea(AffectedArea::PIXELS, 1, remoteMsg2.cast<DrawDabsClassic>().bounds()) 130 ), 131 LocalFork::CONCURRENT 132 ); 133 // Our own messages after making the roundtrip 134 QCOMPARE( 135 lf.handleReceivedMessage( 136 localMsg1, 137 AffectedArea(AffectedArea::PIXELS, 1, localMsg1.cast<DrawDabsClassic>().bounds()) 138 ), 139 LocalFork::ALREADYDONE 140 ); 141 QCOMPARE( 142 lf.handleReceivedMessage( 143 localMsg2, 144 AffectedArea(AffectedArea::PIXELS, 1, localMsg2.cast<DrawDabsClassic>().bounds()) 145 ), 146 LocalFork::ALREADYDONE 147 ); 148 } 149 testConflict()150 void testConflict() 151 { 152 LocalFork lf; 153 MessagePtr localMsg1 = msg("1 classicdabs layer=0x0101 x=10 y=10 color=#00ffffff mode=1 {\n" 154 "0 0 512 255 255\n" 155 "5 5 512 255 255\n}"); 156 157 MessagePtr localMsg2 = msg("1 classicdabs layer=0x0101 x=20 y=10 color=#00ffffff mode=1 {\n" 158 "0 0 512 255 255\n" 159 "5 0 512 255 255\n}"); 160 161 MessagePtr remoteMsg1 = msg("2 classicdabs layer=0x0101 x=200 y=10 color=#00ffffff mode=1 {\n" 162 "2 0 512 255 255\n" 163 "5 5 512 255 255\n}"); 164 165 MessagePtr remoteMsg2 = msg("2 classicdabs layer=0x0101 x=20 y=10 color=#00ffffff mode=1 {\n" 166 "2 0 512 255 255\n" 167 "5 0 512 255 255\n}"); 168 169 // First, we'll add our own local messages 170 lf.addLocalMessage( 171 localMsg1, 172 AffectedArea(AffectedArea::PIXELS, 1, localMsg1.cast<DrawDabsClassic>().bounds()) 173 ); 174 lf.addLocalMessage( 175 localMsg2, 176 AffectedArea(AffectedArea::PIXELS, 1, localMsg2.cast<DrawDabsClassic>().bounds()) 177 ); 178 179 // Then, we'll "receive" messages from the server 180 // The received messages are causally dependent on our messages, and thus 181 // trigger a conflict 182 QCOMPARE( 183 lf.handleReceivedMessage( 184 remoteMsg1, 185 AffectedArea(AffectedArea::PIXELS, 1, remoteMsg1.cast<DrawDabsClassic>().bounds()) 186 ), 187 LocalFork::CONCURRENT 188 ); 189 QCOMPARE( 190 lf.handleReceivedMessage( 191 remoteMsg2, 192 AffectedArea(AffectedArea::PIXELS, 1, remoteMsg2.cast<DrawDabsClassic>().bounds()) 193 ), 194 LocalFork::ROLLBACK 195 ); 196 } 197 testUnexpected()198 void testUnexpected() 199 { 200 LocalFork lf; 201 202 MessagePtr msg1 = msg("1 classicdabs layer=0x0101 x=10 y=10 color=#00ffffff mode=1 {\n" 203 "0 0 512 255 255\n}"); 204 205 MessagePtr msg2 = msg("1 classicdabs layer=0x0101 x=20 y=10 color=#00ffffff mode=1 {\n" 206 "0 0 512 255 255\n}"); 207 208 MessagePtr msg3 = msg("1 classicdabs layer=0x0101 x=22 y=10 color=#00ffffff mode=1 {\n" 209 "2 0 512 255 255\n}"); 210 211 // First, we'll add our own local messages 212 lf.addLocalMessage( 213 msg1, 214 AffectedArea(AffectedArea::PIXELS, 1, QRect(1,1,1,1)) 215 ); 216 lf.addLocalMessage( 217 msg2, 218 AffectedArea(AffectedArea::PIXELS, 1, QRect(1,1,10,10)) 219 ); 220 221 QVERIFY(!lf.isEmpty()); 222 223 // Then, we'll "receive" something unexpected! 224 QCOMPARE( 225 lf.handleReceivedMessage( 226 msg3, 227 AffectedArea(AffectedArea::PIXELS, 1, QRect(100,100,1,1)) 228 ), 229 LocalFork::ROLLBACK 230 ); 231 232 // Local fork should be automatically cleared 233 QCOMPARE(lf.isEmpty(), true); 234 } 235 testFallBehind()236 void testFallBehind() 237 { 238 LocalFork lf; 239 lf.setFallbehind(10); 240 241 lf.addLocalMessage(msg("1 penup"), AffectedArea(AffectedArea::PIXELS, 1, QRect(1,1,1,1))); 242 243 protocol::MessagePtr recv = msg("2 penup"); 244 for(int i=0;i<9;++i) { 245 QCOMPARE( 246 lf.handleReceivedMessage(recv, AffectedArea(AffectedArea::ANNOTATION, 1)), 247 LocalFork::CONCURRENT 248 ); 249 } 250 251 // Next one should go over the limit 252 QCOMPARE( 253 lf.handleReceivedMessage(recv, AffectedArea(AffectedArea::ANNOTATION, 1)), 254 LocalFork::ROLLBACK 255 ); 256 } 257 258 private: msg(const QString & line)259 MessagePtr msg(const QString &line) 260 { 261 text::Parser p; 262 QStringList lines = line.split('\n'); 263 text::Parser::Result r; 264 int i=0; 265 do { 266 r = p.parseLine(lines.at(i++)); 267 } while(r.status==text::Parser::Result::NeedMore); 268 269 if(r.status != text::Parser::Result::Ok || r.msg.isNull()) 270 qFatal("invalid message: %s", qPrintable(line)); 271 272 return MessagePtr::fromNullable(r.msg); 273 } 274 }; 275 276 277 QTEST_MAIN(TestRetcon) 278 #include "retcon.moc" 279 280