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