1 /* Reverse Engineer's Hex Editor
2  * Copyright (C) 2020-2021 Daniel Collins <solemnwarning@solemnwarning.net>
3  *
4  * This program is free software; you can redistribute it and/or modify it
5  * under the terms of the GNU General Public License version 2 as published by
6  * the Free Software Foundation.
7  *
8  * This program is distributed in the hope that it will be useful, but WITHOUT
9  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
10  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
11  * more details.
12  *
13  * You should have received a copy of the GNU General Public License along with
14  * this program; if not, write to the Free Software Foundation, Inc., 51
15  * Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
16 */
17 
18 #include "../src/platform.hpp"
19 
20 #include <gtest/gtest.h>
21 #include <list>
22 #include <vector>
23 #include <wx/frame.h>
24 
25 #include "../src/document.hpp"
26 #include "../src/DocumentCtrl.hpp"
27 #include "../src/SharedDocumentPointer.hpp"
28 #include "../src/StringPanel.hpp"
29 
30 using namespace REHex;
31 
32 class StringPanelTest: public ::testing::Test
33 {
34 	protected:
35 		enum {
36 			ID_CHECK_TIMER = 1,
37 			ID_TIMEOUT_TIMER,
38 		};
39 
40 		wxFrame frame;
41 
42 		SharedDocumentPointer doc;
43 		DocumentCtrl *main_doc_ctrl;
44 
45 		StringPanel *string_panel;
46 
47 		wxTimer *check_timer;
48 		wxTimer *timeout_timer;
49 
StringPanelTest()50 		StringPanelTest():
51 			frame(NULL, wxID_ANY, "REHex Tests"),
52 			doc(SharedDocumentPointer::make())
53 		{
54 			main_doc_ctrl = new DocumentCtrl(&frame, doc);
55 
56 			/* Need to put a Region in the DocumentCtrl to avoid crashes. */
57 			std::vector<DocumentCtrl::Region*> regions;
58 			regions.push_back(new DocumentCtrl::DataRegion(0, 0, 0));
59 			main_doc_ctrl->replace_all_regions(regions);
60 
61 			check_timer = new wxTimer(&frame, ID_CHECK_TIMER);
62 			timeout_timer = new wxTimer(&frame, ID_TIMEOUT_TIMER);
63 
64 			frame.Bind(wxEVT_TIMER, [this](wxTimerEvent &event)
65 			{
66 				if(string_panel->get_num_threads() == 0)
67 				{
68 					wxTheApp->ExitMainLoop();
69 				}
70 			}, ID_CHECK_TIMER, ID_CHECK_TIMER);
71 
72 			frame.Bind(wxEVT_TIMER, [this](wxTimerEvent &event)
73 			{
74 				wxTheApp->ExitMainLoop();
75 			}, ID_TIMEOUT_TIMER, ID_TIMEOUT_TIMER);
76 		}
77 
wait_for_idle(unsigned int timeout_ms)78 		void wait_for_idle(unsigned int timeout_ms)
79 		{
80 			check_timer->Start(100, wxTIMER_CONTINUOUS);
81 			timeout_timer->Start(timeout_ms, wxTIMER_ONE_SHOT);
82 
83 			wxTheApp->OnRun();
84 
85 			timeout_timer->Stop();
86 			check_timer->Stop();
87 		}
88 };
89 
TEST_F(StringPanelTest,EmptyFile)90 TEST_F(StringPanelTest, EmptyFile)
91 {
92 	string_panel = new StringPanel(&frame, doc, main_doc_ctrl);
93 	string_panel->set_min_string_length(4);
94 	string_panel->set_visible(true);
95 
96 	EXPECT_EQ(string_panel->get_num_threads(), 0U) << "StringPanel doesn't spawn workers for an empty file";
97 
98 	ByteRangeSet strings = string_panel->get_strings();
99 	std::vector<ByteRangeSet::Range> got_strings(strings.begin(), strings.end());
100 
101 	const std::vector<ByteRangeSet::Range> EXPECT_STRINGS = {};
102 
103 	EXPECT_EQ(got_strings, EXPECT_STRINGS) << "StringPanel doesn't find any strings in an empty file";
104 }
105 
TEST_F(StringPanelTest,TextOnlyFile)106 TEST_F(StringPanelTest, TextOnlyFile)
107 {
108 	const std::vector<unsigned char> DATA((1024 * 1024), 'A');
109 	doc->insert_data(0, DATA.data(), DATA.size());
110 
111 	string_panel = new StringPanel(&frame, doc, main_doc_ctrl);
112 	string_panel->set_min_string_length(4);
113 	string_panel->set_visible(true);
114 
115 	EXPECT_NE(string_panel->get_num_threads(), 0U) << "StringPanel spawns workers for non-empty file";
116 
117 	wait_for_idle(1000);
118 
119 	EXPECT_EQ(string_panel->get_clean_bytes(), (1024 * 1024)) << "StringPanel processed all data in file";
120 	EXPECT_EQ(string_panel->get_num_threads(), 0U) << "StringPanel workers exited";
121 
122 	ByteRangeSet strings = string_panel->get_strings();
123 	std::vector<ByteRangeSet::Range> got_strings(strings.begin(), strings.end());
124 
125 	const std::vector<ByteRangeSet::Range> EXPECT_STRINGS = {
126 		ByteRangeSet::Range(0, (1024 * 1024)),
127 	};
128 
129 	EXPECT_EQ(got_strings, EXPECT_STRINGS) << "StringPanel finds string encompassing entire text file";
130 }
131 
TEST_F(StringPanelTest,BinaryOnlyFile)132 TEST_F(StringPanelTest, BinaryOnlyFile)
133 {
134 	const std::vector<unsigned char> DATA((1024 * 1024), 255);
135 	doc->insert_data(0, DATA.data(), DATA.size());
136 
137 	string_panel = new StringPanel(&frame, doc, main_doc_ctrl);
138 	string_panel->set_min_string_length(4);
139 	string_panel->set_visible(true);
140 
141 	EXPECT_TRUE(string_panel->get_num_threads() > 0U) << "StringPanel spawns workers for non-empty file";
142 
143 	wait_for_idle(1000);
144 
145 	EXPECT_EQ(string_panel->get_clean_bytes(), (1024 * 1024)) << "StringPanel processed all data in file";
146 	EXPECT_EQ(string_panel->get_num_threads(), 0U) << "StringPanel workers exited";
147 
148 	ByteRangeSet strings = string_panel->get_strings();
149 	std::vector<ByteRangeSet::Range> got_strings(strings.begin(), strings.end());
150 
151 	const std::vector<ByteRangeSet::Range> EXPECT_STRINGS = {};
152 
153 	EXPECT_EQ(got_strings, EXPECT_STRINGS) << "StringPanel doesn't find any strings in non-text file";
154 }
155 
TEST_F(StringPanelTest,MixedFile)156 TEST_F(StringPanelTest, MixedFile)
157 {
158 	std::vector<unsigned char> data;
159 
160 	for(off_t i = 0; i < 1024; ++i)
161 	{
162 		data.push_back(i % 256);
163 	}
164 
165 	doc->insert_data(0, data.data(), data.size());
166 
167 	string_panel = new StringPanel(&frame, doc, main_doc_ctrl);
168 	string_panel->set_min_string_length(4);
169 	string_panel->set_visible(true);
170 
171 	EXPECT_NE(string_panel->get_num_threads(), 0U) << "StringPanel spawns workers for non-empty file";
172 
173 	wait_for_idle(1000);
174 
175 	EXPECT_EQ(string_panel->get_clean_bytes(), 1024U) << "StringPanel processed all data in file";
176 	EXPECT_EQ(string_panel->get_num_threads(), 0U) << "StringPanel workers exited";
177 
178 	ByteRangeSet strings = string_panel->get_strings();
179 	std::vector<ByteRangeSet::Range> got_strings(strings.begin(), strings.end());
180 
181 	const std::vector<ByteRangeSet::Range> EXPECT_STRINGS = {
182 		ByteRangeSet::Range( 32, 95),
183 		ByteRangeSet::Range(288, 95),
184 		ByteRangeSet::Range(544, 95),
185 		ByteRangeSet::Range(800, 95),
186 	};
187 
188 	EXPECT_EQ(got_strings, EXPECT_STRINGS) << "StringPanel finds strings in mixed file";
189 }
190 
TEST_F(StringPanelTest,OverwriteDataTruncatesString)191 TEST_F(StringPanelTest, OverwriteDataTruncatesString)
192 {
193 	const std::vector<unsigned char> BIN_DATA(1024, 0x1B);
194 
195 	doc->insert_data(0, BIN_DATA.data(), BIN_DATA.size());
196 
197 	doc->overwrite_data(128, "cemetery tedious lunchroom", 26);
198 	doc->overwrite_data(256, "crazy nutty grass", 17);
199 
200 	string_panel = new StringPanel(&frame, doc, main_doc_ctrl);
201 	string_panel->set_min_string_length(4);
202 	string_panel->set_visible(true);
203 
204 	wait_for_idle(1000);
205 
206 	ASSERT_EQ(string_panel->get_clean_bytes(), 1024U);
207 	ASSERT_EQ(string_panel->get_num_threads(), 0U);
208 
209 	{
210 		ByteRangeSet strings = string_panel->get_strings();
211 		std::vector<ByteRangeSet::Range> got_strings(strings.begin(), strings.end());
212 
213 		const std::vector<ByteRangeSet::Range> EXPECT_STRINGS = {
214 			ByteRangeSet::Range(128, 26),
215 			ByteRangeSet::Range(256, 17),
216 		};
217 
218 		ASSERT_EQ(got_strings, EXPECT_STRINGS);
219 	}
220 
221 	doc->overwrite_data(150, BIN_DATA.data(), 4);
222 
223 	EXPECT_EQ(string_panel->get_num_threads(), 1U) << "StringPanel spawns a worker for an overwrite";
224 
225 	wait_for_idle(1000);
226 
227 	EXPECT_EQ(string_panel->get_clean_bytes(), 1024U) << "StringPanel processed all data in file";
228 	EXPECT_EQ(string_panel->get_num_threads(), 0U) << "StringPanel workers exited";
229 
230 	{
231 		ByteRangeSet strings = string_panel->get_strings();
232 		std::vector<ByteRangeSet::Range> got_strings(strings.begin(), strings.end());
233 
234 		const std::vector<ByteRangeSet::Range> EXPECT_STRINGS = {
235 			ByteRangeSet::Range(128, 22),
236 			ByteRangeSet::Range(256, 17),
237 		};
238 
239 		EXPECT_EQ(got_strings, EXPECT_STRINGS) << "StringPanel adjusts strings truncated by overwrite";
240 	}
241 }
242 
TEST_F(StringPanelTest,OverwriteDataSplitsString)243 TEST_F(StringPanelTest, OverwriteDataSplitsString)
244 {
245 	const std::vector<unsigned char> BIN_DATA(1024, 0x1B);
246 
247 	doc->insert_data(0, BIN_DATA.data(), BIN_DATA.size());
248 
249 	doc->overwrite_data(128, "gold rapid macho", 16);
250 	doc->overwrite_data(256, "broad slope peep", 16);
251 
252 	string_panel = new StringPanel(&frame, doc, main_doc_ctrl);
253 	string_panel->set_min_string_length(4);
254 	string_panel->set_visible(true);
255 
256 	wait_for_idle(1000);
257 
258 	ASSERT_EQ(string_panel->get_clean_bytes(), 1024U);
259 	ASSERT_EQ(string_panel->get_num_threads(), 0U);
260 
261 	{
262 		ByteRangeSet strings = string_panel->get_strings();
263 		std::vector<ByteRangeSet::Range> got_strings(strings.begin(), strings.end());
264 
265 		const std::vector<ByteRangeSet::Range> EXPECT_STRINGS = {
266 			ByteRangeSet::Range(128, 16),
267 			ByteRangeSet::Range(256, 16),
268 		};
269 
270 		ASSERT_EQ(got_strings, EXPECT_STRINGS);
271 	}
272 
273 	doc->overwrite_data(132, BIN_DATA.data(), 7);
274 
275 	EXPECT_EQ(string_panel->get_num_threads(), 1U) << "StringPanel spawns a worker for an overwrite";
276 
277 	wait_for_idle(1000);
278 
279 	EXPECT_EQ(string_panel->get_clean_bytes(), 1024U) << "StringPanel processed all data in file";
280 	EXPECT_EQ(string_panel->get_num_threads(), 0U) << "StringPanel workers exited";
281 
282 	{
283 		ByteRangeSet strings = string_panel->get_strings();
284 		std::vector<ByteRangeSet::Range> got_strings(strings.begin(), strings.end());
285 
286 		const std::vector<ByteRangeSet::Range> EXPECT_STRINGS = {
287 			ByteRangeSet::Range(128, 4),
288 			ByteRangeSet::Range(139, 5),
289 			ByteRangeSet::Range(256, 16),
290 		};
291 
292 		EXPECT_EQ(got_strings, EXPECT_STRINGS) << "StringPanel adjusts strings split by overwrite";
293 	}
294 }
295 
TEST_F(StringPanelTest,OverwriteDataSplitsInvalidatesString)296 TEST_F(StringPanelTest, OverwriteDataSplitsInvalidatesString)
297 {
298 	const std::vector<unsigned char> BIN_DATA(1024, 0x1B);
299 
300 	doc->insert_data(0, BIN_DATA.data(), BIN_DATA.size());
301 
302 	doc->overwrite_data(128, "gold rapid macho", 16);
303 	doc->overwrite_data(256, "broad slope peep", 16);
304 
305 	string_panel = new StringPanel(&frame, doc, main_doc_ctrl);
306 	string_panel->set_min_string_length(4);
307 	string_panel->set_visible(true);
308 
309 	wait_for_idle(1000);
310 
311 	ASSERT_EQ(string_panel->get_clean_bytes(), 1024U);
312 	ASSERT_EQ(string_panel->get_num_threads(), 0U);
313 
314 	{
315 		ByteRangeSet strings = string_panel->get_strings();
316 		std::vector<ByteRangeSet::Range> got_strings(strings.begin(), strings.end());
317 
318 		const std::vector<ByteRangeSet::Range> EXPECT_STRINGS = {
319 			ByteRangeSet::Range(128, 16),
320 			ByteRangeSet::Range(256, 16),
321 		};
322 
323 		ASSERT_EQ(got_strings, EXPECT_STRINGS);
324 	}
325 
326 	doc->overwrite_data(131, BIN_DATA.data(), 8);
327 
328 	EXPECT_EQ(string_panel->get_num_threads(), 1U) << "StringPanel spawns a worker for an overwrite";
329 
330 	wait_for_idle(1000);
331 
332 	EXPECT_EQ(string_panel->get_clean_bytes(), 1024U) << "StringPanel processed all data in file";
333 	EXPECT_EQ(string_panel->get_num_threads(), 0U) << "StringPanel workers exited";
334 
335 	{
336 		ByteRangeSet strings = string_panel->get_strings();
337 		std::vector<ByteRangeSet::Range> got_strings(strings.begin(), strings.end());
338 
339 		const std::vector<ByteRangeSet::Range> EXPECT_STRINGS = {
340 			ByteRangeSet::Range(139, 5),
341 			ByteRangeSet::Range(256, 16),
342 		};
343 
344 		EXPECT_EQ(got_strings, EXPECT_STRINGS) << "StringPanel adjusts strings split and invalidated by overwrite";
345 	}
346 }
347 
TEST_F(StringPanelTest,OverwriteDataCompletesString)348 TEST_F(StringPanelTest, OverwriteDataCompletesString)
349 {
350 	const std::vector<unsigned char> BIN_DATA(1024, 0x1B);
351 
352 	doc->insert_data(0, BIN_DATA.data(), BIN_DATA.size());
353 
354 	doc->overwrite_data(128, "abc", 3);
355 
356 	string_panel = new StringPanel(&frame, doc, main_doc_ctrl);
357 	string_panel->set_min_string_length(4);
358 	string_panel->set_visible(true);
359 
360 	wait_for_idle(1000);
361 
362 	ASSERT_EQ(string_panel->get_clean_bytes(), 1024U);
363 	ASSERT_EQ(string_panel->get_num_threads(), 0U);
364 
365 	{
366 		ByteRangeSet strings = string_panel->get_strings();
367 		std::vector<ByteRangeSet::Range> got_strings(strings.begin(), strings.end());
368 
369 		const std::vector<ByteRangeSet::Range> EXPECT_STRINGS = {};
370 
371 		ASSERT_EQ(got_strings, EXPECT_STRINGS);
372 	}
373 
374 	unsigned const char DATA[] = { 'd' };
375 	doc->overwrite_data(131, DATA, 1);
376 
377 	EXPECT_EQ(string_panel->get_num_threads(), 1U) << "StringPanel spawns a worker for an overwrite";
378 
379 	wait_for_idle(1000);
380 
381 	EXPECT_EQ(string_panel->get_clean_bytes(), 1024U) << "StringPanel processed all data in file";
382 	EXPECT_EQ(string_panel->get_num_threads(), 0U) << "StringPanel workers exited";
383 
384 	{
385 		ByteRangeSet strings = string_panel->get_strings();
386 		std::vector<ByteRangeSet::Range> got_strings(strings.begin(), strings.end());
387 
388 		const std::vector<ByteRangeSet::Range> EXPECT_STRINGS = {
389 			ByteRangeSet::Range(128, 4),
390 		};
391 
392 		EXPECT_EQ(got_strings, EXPECT_STRINGS) << "StringPanel finds string completed by overwrite";
393 	}
394 }
395 
TEST_F(StringPanelTest,InsertData)396 TEST_F(StringPanelTest, InsertData)
397 {
398 	const std::vector<unsigned char> BIN_DATA(1024, 0x1B);
399 
400 	doc->insert_data(0, BIN_DATA.data(), BIN_DATA.size());
401 
402 	doc->overwrite_data(128, "bent historical malicious", 25);
403 	doc->overwrite_data(256, "jog idiotic flight", 18);
404 	doc->overwrite_data(512, "knowledge spotty identify", 25);
405 
406 	string_panel = new StringPanel(&frame, doc, main_doc_ctrl);
407 	string_panel->set_min_string_length(4);
408 	string_panel->set_visible(true);
409 
410 	wait_for_idle(1000);
411 
412 	ASSERT_EQ(string_panel->get_clean_bytes(), 1024U);
413 	ASSERT_EQ(string_panel->get_num_threads(), 0U);
414 
415 	{
416 		ByteRangeSet strings = string_panel->get_strings();
417 		std::vector<ByteRangeSet::Range> got_strings(strings.begin(), strings.end());
418 
419 		const std::vector<ByteRangeSet::Range> EXPECT_STRINGS = {
420 			ByteRangeSet::Range(128, 25),
421 			ByteRangeSet::Range(256, 18),
422 			ByteRangeSet::Range(512, 25),
423 		};
424 
425 		ASSERT_EQ(got_strings, EXPECT_STRINGS);
426 	}
427 
428 	const unsigned char INSERT_DATA[] = { 0x1B, 'A', 'A', 'A', 'A', 0x1B, 'B' };
429 
430 	doc->insert_data(259, INSERT_DATA, 7);
431 
432 	EXPECT_EQ(string_panel->get_num_threads(), 1U) << "StringPanel spawns a worker for an insert";
433 
434 	wait_for_idle(1000);
435 
436 	EXPECT_EQ(string_panel->get_clean_bytes(), 1031U) << "StringPanel processed all data in file";
437 	EXPECT_EQ(string_panel->get_num_threads(), 0U) << "StringPanel workers exited";
438 
439 	{
440 		ByteRangeSet strings = string_panel->get_strings();
441 		std::vector<ByteRangeSet::Range> got_strings(strings.begin(), strings.end());
442 
443 		const std::vector<ByteRangeSet::Range> EXPECT_STRINGS = {
444 			ByteRangeSet::Range(128, 25),
445 			ByteRangeSet::Range(260, 4),
446 			ByteRangeSet::Range(265, 16),
447 			ByteRangeSet::Range(519, 25),
448 		};
449 
450 		EXPECT_EQ(got_strings, EXPECT_STRINGS) << "StringPanel adjusts strings affected by insert";
451 	}
452 }
453 
TEST_F(StringPanelTest,InsertDataCompletesString)454 TEST_F(StringPanelTest, InsertDataCompletesString)
455 {
456 	const std::vector<unsigned char> BIN_DATA(1024, 0x1B);
457 
458 	doc->insert_data(0, BIN_DATA.data(), BIN_DATA.size());
459 
460 	doc->overwrite_data(128, "abc", 3);
461 
462 	string_panel = new StringPanel(&frame, doc, main_doc_ctrl);
463 	string_panel->set_min_string_length(4);
464 	string_panel->set_visible(true);
465 
466 	wait_for_idle(1000);
467 
468 	ASSERT_EQ(string_panel->get_clean_bytes(), 1024U);
469 	ASSERT_EQ(string_panel->get_num_threads(), 0U);
470 
471 	{
472 		ByteRangeSet strings = string_panel->get_strings();
473 		std::vector<ByteRangeSet::Range> got_strings(strings.begin(), strings.end());
474 
475 		const std::vector<ByteRangeSet::Range> EXPECT_STRINGS = {};
476 
477 		ASSERT_EQ(got_strings, EXPECT_STRINGS);
478 	}
479 
480 	const unsigned char INSERT_DATA[] = { 'd' };
481 
482 	doc->insert_data(131, INSERT_DATA, 1);
483 
484 	EXPECT_EQ(string_panel->get_num_threads(), 1U) << "StringPanel spawns a worker for an insert";
485 
486 	wait_for_idle(1000);
487 
488 	EXPECT_EQ(string_panel->get_clean_bytes(), 1025U) << "StringPanel processed all data in file";
489 	EXPECT_EQ(string_panel->get_num_threads(), 0U) << "StringPanel workers exited";
490 
491 	{
492 		ByteRangeSet strings = string_panel->get_strings();
493 		std::vector<ByteRangeSet::Range> got_strings(strings.begin(), strings.end());
494 
495 		const std::vector<ByteRangeSet::Range> EXPECT_STRINGS = {
496 			ByteRangeSet::Range(128, 4),
497 		};
498 
499 		EXPECT_EQ(got_strings, EXPECT_STRINGS) << "StringPanel finds strings completed by insert";
500 	}
501 }
502 
TEST_F(StringPanelTest,EraseData)503 TEST_F(StringPanelTest, EraseData)
504 {
505 	const std::vector<unsigned char> BIN_DATA(1024, 0x1B);
506 
507 	doc->insert_data(0, BIN_DATA.data(), BIN_DATA.size());
508 
509 	doc->overwrite_data(128, "harm morning homeless", 21);
510 	doc->overwrite_data(256, "rightful group cave",   19);
511 	doc->overwrite_data(512, "pumped stick feeble",   19);
512 
513 	string_panel = new StringPanel(&frame, doc, main_doc_ctrl);
514 	string_panel->set_min_string_length(4);
515 	string_panel->set_visible(true);
516 
517 	wait_for_idle(1000);
518 
519 	ASSERT_EQ(string_panel->get_clean_bytes(), 1024U);
520 	ASSERT_EQ(string_panel->get_num_threads(), 0U);
521 
522 	{
523 		ByteRangeSet strings = string_panel->get_strings();
524 		std::vector<ByteRangeSet::Range> got_strings(strings.begin(), strings.end());
525 
526 		const std::vector<ByteRangeSet::Range> EXPECT_STRINGS = {
527 			ByteRangeSet::Range(128, 21),
528 			ByteRangeSet::Range(256, 19),
529 			ByteRangeSet::Range(512, 19),
530 		};
531 
532 		ASSERT_EQ(got_strings, EXPECT_STRINGS);
533 	}
534 
535 	doc->erase_data(260, 6);
536 
537 	EXPECT_EQ(string_panel->get_num_threads(), 1U) << "StringPanel spawns a worker for an erase";
538 
539 	wait_for_idle(1000);
540 
541 	EXPECT_EQ(string_panel->get_clean_bytes(), 1018U) << "StringPanel processed all data in file";
542 	EXPECT_EQ(string_panel->get_num_threads(), 0U) << "StringPanel workers exited";
543 
544 	{
545 		ByteRangeSet strings = string_panel->get_strings();
546 		std::vector<ByteRangeSet::Range> got_strings(strings.begin(), strings.end());
547 
548 		const std::vector<ByteRangeSet::Range> EXPECT_STRINGS = {
549 			ByteRangeSet::Range(128, 21),
550 			ByteRangeSet::Range(256, 13),
551 			ByteRangeSet::Range(506, 19),
552 		};
553 
554 		EXPECT_EQ(got_strings, EXPECT_STRINGS) << "StringPanel adjusts strings affected by erase";
555 	}
556 }
557 
TEST_F(StringPanelTest,EraseDataInvalidate)558 TEST_F(StringPanelTest, EraseDataInvalidate)
559 {
560 	const std::vector<unsigned char> BIN_DATA(1024, 0x1B);
561 
562 	doc->insert_data(0, BIN_DATA.data(), BIN_DATA.size());
563 
564 	doc->overwrite_data(128, "murder lyrical touch", 20);
565 	doc->overwrite_data(256, "sturdy books scrape", 19);
566 
567 	string_panel = new StringPanel(&frame, doc, main_doc_ctrl);
568 	string_panel->set_min_string_length(4);
569 	string_panel->set_visible(true);
570 
571 	wait_for_idle(1000);
572 
573 	ASSERT_EQ(string_panel->get_clean_bytes(), 1024U);
574 	ASSERT_EQ(string_panel->get_num_threads(), 0U);
575 
576 	{
577 		ByteRangeSet strings = string_panel->get_strings();
578 		std::vector<ByteRangeSet::Range> got_strings(strings.begin(), strings.end());
579 
580 		const std::vector<ByteRangeSet::Range> EXPECT_STRINGS = {
581 			ByteRangeSet::Range(128, 20),
582 			ByteRangeSet::Range(256, 19),
583 		};
584 
585 		ASSERT_EQ(got_strings, EXPECT_STRINGS);
586 	}
587 
588 	doc->erase_data(259, 16);
589 
590 	EXPECT_EQ(string_panel->get_num_threads(), 1U) << "StringPanel spawns a worker for an erase";
591 
592 	wait_for_idle(1000);
593 
594 	EXPECT_EQ(string_panel->get_clean_bytes(), 1008U) << "StringPanel processed all data in file";
595 	EXPECT_EQ(string_panel->get_num_threads(), 0U) << "StringPanel workers exited";
596 
597 	{
598 		ByteRangeSet strings = string_panel->get_strings();
599 		std::vector<ByteRangeSet::Range> got_strings(strings.begin(), strings.end());
600 
601 		const std::vector<ByteRangeSet::Range> EXPECT_STRINGS = {
602 			ByteRangeSet::Range(128, 20),
603 		};
604 
605 		EXPECT_EQ(got_strings, EXPECT_STRINGS) << "StringPanel removes string invalidated by erase";
606 	}
607 }
608 
TEST_F(StringPanelTest,EraseDataMerge)609 TEST_F(StringPanelTest, EraseDataMerge)
610 {
611 	const std::vector<unsigned char> BIN_DATA(1024, 0x1B);
612 
613 	doc->insert_data(0, BIN_DATA.data(), BIN_DATA.size());
614 
615 	doc->overwrite_data(128, "salty peep party", 16);
616 	doc->overwrite_data(256, "kettle kneel supply", 19);
617 
618 	string_panel = new StringPanel(&frame, doc, main_doc_ctrl);
619 	string_panel->set_min_string_length(4);
620 	string_panel->set_visible(true);
621 
622 	wait_for_idle(1000);
623 
624 	ASSERT_EQ(string_panel->get_clean_bytes(), 1024U);
625 	ASSERT_EQ(string_panel->get_num_threads(), 0U);
626 
627 	{
628 		ByteRangeSet strings = string_panel->get_strings();
629 		std::vector<ByteRangeSet::Range> got_strings(strings.begin(), strings.end());
630 
631 		const std::vector<ByteRangeSet::Range> EXPECT_STRINGS = {
632 			ByteRangeSet::Range(128, 16),
633 			ByteRangeSet::Range(256, 19),
634 		};
635 
636 		ASSERT_EQ(got_strings, EXPECT_STRINGS);
637 	}
638 
639 	doc->erase_data(142, 114);
640 
641 	EXPECT_EQ(string_panel->get_num_threads(), 1U) << "StringPanel spawns a worker for an erase";
642 
643 	wait_for_idle(1000);
644 
645 	EXPECT_EQ(string_panel->get_clean_bytes(), 910U) << "StringPanel processed all data in file";
646 	EXPECT_EQ(string_panel->get_num_threads(), 0U) << "StringPanel workers exited";
647 
648 	{
649 		ByteRangeSet strings = string_panel->get_strings();
650 		std::vector<ByteRangeSet::Range> got_strings(strings.begin(), strings.end());
651 
652 		const std::vector<ByteRangeSet::Range> EXPECT_STRINGS = {
653 			ByteRangeSet::Range(128, 33),
654 		};
655 
656 		EXPECT_EQ(got_strings, EXPECT_STRINGS) << "StringPanel merges strings merged by erase";
657 	}
658 }
659 
TEST_F(StringPanelTest,EraseDataCompletesString)660 TEST_F(StringPanelTest, EraseDataCompletesString)
661 {
662 	const std::vector<unsigned char> BIN_DATA(1024, 0x1B);
663 
664 	doc->insert_data(0, BIN_DATA.data(), BIN_DATA.size());
665 
666 	doc->overwrite_data(128, "abc", 3);
667 	doc->overwrite_data(132, "d", 1);
668 
669 	string_panel = new StringPanel(&frame, doc, main_doc_ctrl);
670 	string_panel->set_min_string_length(4);
671 	string_panel->set_visible(true);
672 
673 	wait_for_idle(1000);
674 
675 	ASSERT_EQ(string_panel->get_clean_bytes(), 1024U);
676 	ASSERT_EQ(string_panel->get_num_threads(), 0U);
677 
678 	{
679 		ByteRangeSet strings = string_panel->get_strings();
680 		std::vector<ByteRangeSet::Range> got_strings(strings.begin(), strings.end());
681 
682 		const std::vector<ByteRangeSet::Range> EXPECT_STRINGS = {};
683 
684 		ASSERT_EQ(got_strings, EXPECT_STRINGS);
685 	}
686 
687 	doc->erase_data(131, 1);
688 
689 	EXPECT_EQ(string_panel->get_num_threads(), 1U) << "StringPanel spawns a worker for an erase";
690 
691 	wait_for_idle(1000);
692 
693 	EXPECT_EQ(string_panel->get_clean_bytes(), 1023U) << "StringPanel processed all data in file";
694 	EXPECT_EQ(string_panel->get_num_threads(), 0U) << "StringPanel workers exited";
695 
696 	{
697 		ByteRangeSet strings = string_panel->get_strings();
698 		std::vector<ByteRangeSet::Range> got_strings(strings.begin(), strings.end());
699 
700 		const std::vector<ByteRangeSet::Range> EXPECT_STRINGS = {
701 			ByteRangeSet::Range(128, 4),
702 		};
703 
704 		EXPECT_EQ(got_strings, EXPECT_STRINGS) << "StringPanel finds strings completed by erase";
705 	}
706 }
707 
TEST_F(StringPanelTest,BackToBackModifications)708 TEST_F(StringPanelTest, BackToBackModifications)
709 {
710 	static const size_t kiB = 1024;
711 	static const size_t MiB = kiB * 1024;
712 
713 	const std::vector<unsigned char> BIN_DATA(16 * MiB, 0x1B);
714 	const std::vector<unsigned char> TEXT_DATA(16 * MiB, 'X');
715 
716 	doc->insert_data(0, BIN_DATA.data(), 16 * MiB);
717 
718 	string_panel = new StringPanel(&frame, doc, main_doc_ctrl);
719 	string_panel->set_min_string_length(4);
720 	string_panel->set_visible(true);
721 
722 	wait_for_idle(5000);
723 
724 	ASSERT_EQ(string_panel->get_clean_bytes(), (off_t)(16 * MiB));
725 	ASSERT_EQ(string_panel->get_num_threads(), 0U);
726 
727 	{
728 		ByteRangeSet strings = string_panel->get_strings();
729 		std::vector<ByteRangeSet::Range> got_strings(strings.begin(), strings.end());
730 
731 		const std::vector<ByteRangeSet::Range> EXPECT_STRINGS = {};
732 
733 		ASSERT_EQ(got_strings, EXPECT_STRINGS);
734 	}
735 
736 	doc->insert_data(1 * MiB, TEXT_DATA.data(), 1 * MiB);
737 	doc->insert_data(4 * MiB, TEXT_DATA.data(), 512 * kiB);
738 	doc->insert_data(1536 * kiB, TEXT_DATA.data(), 1 * MiB);
739 	doc->overwrite_data(5 * MiB, BIN_DATA.data(), 256 * kiB);
740 	doc->erase_data(5 * MiB, 128 * kiB);
741 	doc->overwrite_data(256 * kiB, TEXT_DATA.data(), 256 * kiB);
742 	doc->erase_data(300 * kiB, 64 * kiB);
743 	doc->insert_data(10 * MiB, TEXT_DATA.data(), 16 * MiB);
744 	doc->insert_data(1 * MiB, BIN_DATA.data(), 16 * MiB);
745 	doc->erase_data(41 * MiB, 1 * MiB);
746 
747 	EXPECT_NE(string_panel->get_num_threads(), 0U) << "StringPanel spawned worker threads";
748 
749 	wait_for_idle(10000);
750 
751 	EXPECT_EQ(string_panel->get_clean_bytes(), (off_t)(49 * MiB + 320 * kiB)) << "StringPanel processed all data in file";
752 	EXPECT_EQ(string_panel->get_num_threads(), 0U) << "StringPanel workers exited";
753 
754 	{
755 		ByteRangeSet strings = string_panel->get_strings();
756 		std::vector<ByteRangeSet::Range> got_strings(strings.begin(), strings.end());
757 
758 		const std::vector<ByteRangeSet::Range> EXPECT_STRINGS = {
759 			ByteRangeSet::Range(256 * kiB, 192 * kiB),
760 			ByteRangeSet::Range(1 * MiB - 64 * kiB, 64 * kiB),
761 			ByteRangeSet::Range(17 * MiB, 2 * MiB - 64 * kiB),
762 			ByteRangeSet::Range(21 * MiB + 64 * kiB, 256 * kiB),
763 			ByteRangeSet::Range(26 * MiB, 15 * MiB),
764 		};
765 
766 		EXPECT_EQ(got_strings, EXPECT_STRINGS) << "StringPanel finds strings in result of combined operations";
767 	}
768 }
769 
TEST_F(StringPanelTest,UTF8)770 TEST_F(StringPanelTest, UTF8)
771 {
772 	const unsigned char DATA[] = {
773 		/* Padding */
774 		/* 0x00 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
775 
776 		/* Short ASCII-only string */
777 		/* 0x08 */ 'A', 'B', 'C', 0x00, 0x00, 0x00, 0x00, 0x00,
778 
779 		/* ASCII-only string */
780 		/* 0x10 */ 'A', 'B', 'C', 'D', 'E', 'F', 0x00, 0x00,
781 
782 		/* Short (enough bytes, but not enough code points) UTF-8 string */
783 		/* 0x18 */ 0xC2, 0xA3, 0xE2, 0x98, 0xAD, 0xE2, 0x98, 0x83,
784 
785 		/* Padding */
786 		/* 0x20 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
787 
788 		/* UTF-8 string */
789 		/* 0x28 */ 0xC3, 0xA8, 0xC3, 0xB4, 0xC3, 0xBC, 0xC3, 0xA1,
790 
791 		/* Padding */
792 		/* 0x30 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
793 
794 		/* Mixed ASCII/UTF-8 string */
795 		/* 0x38 */ 'A', 'B', 0xC3, 0xB4, 0xC3, 0xBC, 0x00, 0x00,
796 
797 		/* "Hello" in UTF-16LE */
798 		/* 0x40 */ 'H', 0x00,  'e', 0x00,  'l', 0x00,  'l', 0x00,
799 		/* 0x48 */ 'o', 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
800 
801 	};
802 
803 	doc->insert_data(0, DATA, sizeof(DATA));
804 
805 	string_panel = new StringPanel(&frame, doc, main_doc_ctrl);
806 	string_panel->set_encoding("UTF-8");
807 	string_panel->set_min_string_length(4);
808 	string_panel->set_visible(true);
809 
810 	wait_for_idle(1000);
811 
812 	ASSERT_EQ(string_panel->get_clean_bytes(), 0x50U);
813 	ASSERT_EQ(string_panel->get_num_threads(), 0U);
814 
815 	{
816 		ByteRangeSet strings = string_panel->get_strings();
817 		std::vector<ByteRangeSet::Range> got_strings(strings.begin(), strings.end());
818 
819 		const std::vector<ByteRangeSet::Range> EXPECT_STRINGS = {
820 			ByteRangeSet::Range(0x10, 6),
821 			ByteRangeSet::Range(0x28, 8),
822 			ByteRangeSet::Range(0x38, 6),
823 		};
824 
825 		EXPECT_EQ(got_strings, EXPECT_STRINGS);
826 	}
827 }
828 
TEST_F(StringPanelTest,UTF16)829 TEST_F(StringPanelTest, UTF16)
830 {
831 	const unsigned char DATA[] = {
832 		/* Padding */
833 		/* 0x00 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
834 
835 		/* Short ASCII-only string */
836 		/* 0x08 */ 'A', 'B', 'C', 0x00, 0x00, 0x00, 0x00, 0x00,
837 
838 		/* ASCII-only string */
839 		/* 0x10 */ 'A', 'B', 'C', 'D', 'E', 'F', 0x00, 0x00,
840 
841 		/* Padding */
842 		/* 0x18 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
843 
844 		/* Mixed ASCII/UTF-8 string */
845 		/* 0x20 */ 'A', 'B', 0xC3, 0xB4, 0xC3, 0xBC, 0x00, 0x00,
846 
847 		/* "Hello" in UTF-16LE */
848 		/* 0x28 */ 'H', 0x00,  'e', 0x00,  'l', 0x00,  'l', 0x00,
849 		/* 0x30 */ 'o', 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
850 
851 	};
852 
853 	doc->insert_data(0, DATA, sizeof(DATA));
854 
855 	string_panel = new StringPanel(&frame, doc, main_doc_ctrl);
856 	string_panel->set_encoding("UTF-16LE");
857 	string_panel->set_min_string_length(4);
858 	string_panel->set_visible(true);
859 
860 	wait_for_idle(1000);
861 
862 	ASSERT_EQ(string_panel->get_clean_bytes(), 0x38);
863 	ASSERT_EQ(string_panel->get_num_threads(), 0U);
864 
865 	{
866 		ByteRangeSet strings = string_panel->get_strings();
867 		std::vector<ByteRangeSet::Range> got_strings(strings.begin(), strings.end());
868 
869 		const std::vector<ByteRangeSet::Range> EXPECT_STRINGS = {
870 			ByteRangeSet::Range(0x28, 10),
871 		};
872 
873 		EXPECT_EQ(got_strings, EXPECT_STRINGS);
874 	}
875 }
876