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