1 /* Reverse Engineer's Hex Editor
2 * Copyright (C) 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 #undef NDEBUG
19 #include "../src/platform.hpp"
20 #include <assert.h>
21
22 #include <gtest/gtest.h>
23 #include <wx/evtloop.h>
24 #include <wx/frame.h>
25
26 #include "../src/document.hpp"
27 #include "../src/search.hpp"
28 #include "../src/SharedDocumentPointer.hpp"
29
30 using namespace REHex;
31
32 class SearchBaseDummy: public Search
33 {
34 public:
35 bool should_wrap;
36 bool wrap_requested;
37 bool nothing_found;
38
SearchBaseDummy(wxWindow * parent,SharedDocumentPointer & doc)39 SearchBaseDummy(wxWindow *parent, SharedDocumentPointer &doc):
40 Search(parent, doc, "Dummy search class"),
41 should_wrap(false),
42 wrap_requested(false),
43 nothing_found(false) {}
44
45 /* NOTE: end_search() is called from subclass destructor rather than base to ensure search
46 * is stopped before the subclass becomes invalid, else there is a race where the base
47 * class will try calling the subclass's test() method and trigger undefined behaviour.
48 */
~SearchBaseDummy()49 virtual ~SearchBaseDummy()
50 {
51 if(running)
52 {
53 end_search();
54 }
55 }
56
test(const void * data,size_t data_size)57 virtual bool test(const void *data, size_t data_size)
58 {
59 return (data_size >= 6 && memcmp(data, "foobar", 6) == 0)
60 || (data_size >= 3 && memcmp(data, "baz", 3) == 0);
61 }
62
test_max_window()63 virtual size_t test_max_window() override
64 {
65 return 6;
66 }
67
set_range(off_t range_begin,off_t range_end)68 void set_range(off_t range_begin, off_t range_end)
69 {
70 this->range_begin = range_begin;
71 this->range_end = range_end;
72 }
73
is_running()74 bool is_running()
75 {
76 return running;
77 }
78
get_match()79 off_t get_match()
80 {
81 return match_found_at;
82 }
83
84 protected:
setup_window_controls(wxWindow * parent,wxSizer * sizer)85 virtual void setup_window_controls(wxWindow *parent, wxSizer *sizer) override {}
read_window_controls()86 virtual bool read_window_controls() override { return false; }
87
wrap_query(const char * message)88 virtual bool wrap_query(const char *message) override
89 {
90 wrap_requested = true;
91 return should_wrap;
92 }
93
not_found_notification()94 virtual void not_found_notification() override
95 {
96 nothing_found = true;
97 }
98 };
99
100 class SearchBaseTest: public ::testing::Test {
101 protected:
102 enum {
103 ID_CHECK_TIMER = 1,
104 ID_TIMEOUT_TIMER,
105 };
106
107 wxFrame frame;
108 SharedDocumentPointer doc;
109 SearchBaseDummy s;
110
111 wxTimer *check_timer;
112 wxTimer *timeout_timer;
113
SearchBaseTest()114 SearchBaseTest():
115 frame(NULL, wxID_ANY, "REHex Tests"),
116 doc(SharedDocumentPointer::make()),
117 s(&frame, doc)
118 {
119 const std::vector<unsigned char> DATA(0x8192, 0x00);
120 doc->insert_data(0, DATA.data(), DATA.size());
121
122 check_timer = new wxTimer(&frame, ID_CHECK_TIMER);
123 timeout_timer = new wxTimer(&frame, ID_TIMEOUT_TIMER);
124
125 frame.Bind(wxEVT_TIMER, [this](wxTimerEvent &event)
126 {
127 if(!s.is_running())
128 {
129 wxTheApp->GetMainLoop()->ScheduleExit();
130 }
131 }, ID_CHECK_TIMER, ID_CHECK_TIMER);
132
133 frame.Bind(wxEVT_TIMER, [this](wxTimerEvent &event)
134 {
135 wxTheApp->GetMainLoop()->ScheduleExit();
136 }, ID_TIMEOUT_TIMER, ID_TIMEOUT_TIMER);
137 }
138
wait_for_search()139 void wait_for_search()
140 {
141 check_timer->Start(100, wxTIMER_CONTINUOUS);
142 timeout_timer->Start(5000, wxTIMER_ONE_SHOT);
143
144 wxTheApp->OnRun();
145
146 timeout_timer->Stop();
147 check_timer->Stop();
148 }
149
search_for_match(off_t sub_range_begin,off_t sub_range_end,Search::SearchDirection direction,off_t expect_match_at,off_t window_size=128)150 void search_for_match(off_t sub_range_begin, off_t sub_range_end, Search::SearchDirection direction, off_t expect_match_at, off_t window_size = 128)
151 {
152 /* Starting a search will create a wxProgressDialog, which will install its
153 * own event loop(!) if one isn't already set up, which there isn't when
154 * running the tests, furthermore the dialog gets destroyed WITHIN the
155 * event loop that gets created by wxApp::OnRun() later, and the
156 * out-of-order event loop setup/destruction leads to a dangling event loop
157 * pointer! Yay!
158 *
159 * So we set up our own event loop just while the dialog is being created
160 * to avoid that.
161 */
162
163 {
164 wxEventLoop loop;
165 wxEventLoopActivator activate(&loop);
166
167 s.begin_search(sub_range_begin, sub_range_end, direction, window_size);
168 }
169
170 wait_for_search();
171
172 EXPECT_FALSE(s.is_running());
173 EXPECT_EQ(s.get_match(), expect_match_at);
174
175 if(s.should_wrap)
176 {
177 EXPECT_TRUE(s.wrap_requested);
178 }
179
180 EXPECT_FALSE(s.nothing_found);
181 }
182
search_for_no_match(off_t sub_range_begin,off_t sub_range_end,Search::SearchDirection direction,off_t window_size=128)183 void search_for_no_match(off_t sub_range_begin, off_t sub_range_end, Search::SearchDirection direction, off_t window_size = 128)
184 {
185 /* Starting a search will create a wxProgressDialog, which will install its
186 * own event loop(!) if one isn't already set up, which there isn't when
187 * running the tests, furthermore the dialog gets destroyed WITHIN the
188 * event loop that gets created by wxApp::OnRun() later, and the
189 * out-of-order event loop setup/destruction leads to a dangling event loop
190 * pointer! Yay!
191 *
192 * So we set up our own event loop just while the dialog is being created
193 * to avoid that.
194 */
195
196 {
197 wxEventLoop loop;
198 wxEventLoopActivator activate(&loop);
199
200 s.begin_search(sub_range_begin, sub_range_end, direction, window_size);
201 }
202
203 wait_for_search();
204
205 EXPECT_FALSE(s.is_running());
206 EXPECT_EQ(s.get_match(), -1);
207
208 if(s.should_wrap)
209 {
210 EXPECT_TRUE(s.wrap_requested);
211 EXPECT_TRUE(s.nothing_found);
212 }
213 else{
214 EXPECT_TRUE(s.wrap_requested ^ s.nothing_found);
215 }
216 }
217 };
218
TEST_F(SearchBaseTest,ForwardsNoMatch)219 TEST_F(SearchBaseTest, ForwardsNoMatch)
220 {
221 s.set_range(0, 8192);
222
223 search_for_no_match(0, 8192, Search::SearchDirection::FORWARDS);
224 }
225
TEST_F(SearchBaseTest,BackwardsNoMatch)226 TEST_F(SearchBaseTest, BackwardsNoMatch)
227 {
228 s.set_range(0, 8192);
229
230 search_for_no_match(0, 8192, Search::SearchDirection::BACKWARDS);
231 }
232
TEST_F(SearchBaseTest,ForwardsMatch)233 TEST_F(SearchBaseTest, ForwardsMatch)
234 {
235 doc->overwrite_data(1000, "foobar", 6);
236 doc->overwrite_data(1500, "baz", 3);
237
238 s.set_range(0, 8192);
239
240 search_for_match(0, 8192, Search::SearchDirection::FORWARDS, 1000);
241 }
242
TEST_F(SearchBaseTest,BackwardsMatch)243 TEST_F(SearchBaseTest, BackwardsMatch)
244 {
245 doc->overwrite_data(1000, "foobar", 6);
246 doc->overwrite_data(1500, "baz", 3);
247
248 s.set_range(0, 8192);
249
250 search_for_match(0, 8192, Search::SearchDirection::BACKWARDS, 1500);
251 }
252
TEST_F(SearchBaseTest,ForwardsMatchBeforeRange)253 TEST_F(SearchBaseTest, ForwardsMatchBeforeRange)
254 {
255 doc->overwrite_data(1000, "foobar", 6);
256 doc->overwrite_data(1500, "baz", 3);
257
258 s.set_range(1501, 8192);
259
260 search_for_no_match(1501, 8192, Search::SearchDirection::FORWARDS);
261 }
262
TEST_F(SearchBaseTest,ForwardsMatchAfterRange)263 TEST_F(SearchBaseTest, ForwardsMatchAfterRange)
264 {
265 doc->overwrite_data(1000, "foobar", 6);
266 doc->overwrite_data(1500, "baz", 3);
267
268 s.set_range(0, 1005);
269
270 search_for_no_match(0, 1005, Search::SearchDirection::FORWARDS);
271 }
272
TEST_F(SearchBaseTest,BackwardsMatchBeforeRange)273 TEST_F(SearchBaseTest, BackwardsMatchBeforeRange)
274 {
275 doc->overwrite_data(1000, "foobar", 6);
276 doc->overwrite_data(1500, "baz", 3);
277
278 s.set_range(1501, 8192);
279
280 search_for_no_match(1501, 8192, Search::SearchDirection::BACKWARDS);
281 }
282
TEST_F(SearchBaseTest,BackwardsMatchAfterRange)283 TEST_F(SearchBaseTest, BackwardsMatchAfterRange)
284 {
285 doc->overwrite_data(1000, "foobar", 6);
286 doc->overwrite_data(1500, "baz", 3);
287
288 s.set_range(0, 1005);
289
290 search_for_no_match(0, 1005, Search::SearchDirection::BACKWARDS);
291 }
292
TEST_F(SearchBaseTest,ForwardsMatchBeforeSubRangeNoWrap)293 TEST_F(SearchBaseTest, ForwardsMatchBeforeSubRangeNoWrap)
294 {
295 doc->overwrite_data(1000, "foobar", 6);
296 doc->overwrite_data(1500, "baz", 3);
297
298 s.set_range(0, 8192);
299
300 search_for_no_match(1501, 8192, Search::SearchDirection::FORWARDS);
301 }
302
TEST_F(SearchBaseTest,ForwardsMatchBeforeSubRangeWrap)303 TEST_F(SearchBaseTest, ForwardsMatchBeforeSubRangeWrap)
304 {
305 doc->overwrite_data(1000, "foobar", 6);
306 doc->overwrite_data(1500, "baz", 3);
307
308 s.set_range(0, 8192);
309 s.should_wrap = true;
310
311 search_for_match(1501, 8192, Search::SearchDirection::FORWARDS, 1000);
312 }
313
TEST_F(SearchBaseTest,ForwardsMatchAtStartOfRangeBeforeSubRangeWrap)314 TEST_F(SearchBaseTest, ForwardsMatchAtStartOfRangeBeforeSubRangeWrap)
315 {
316 doc->overwrite_data(1000, "foobar", 6);
317 doc->overwrite_data(1500, "baz", 3);
318
319 s.set_range(1000, 8192);
320 s.should_wrap = true;
321
322 search_for_match(1600, 8192, Search::SearchDirection::FORWARDS, 1000);
323 }
324
TEST_F(SearchBaseTest,ForwardsMatchStraddlingEndOfSubRangeBeforeSubRangeWrap)325 TEST_F(SearchBaseTest, ForwardsMatchStraddlingEndOfSubRangeBeforeSubRangeWrap)
326 {
327 doc->overwrite_data(1500, "baz", 3);
328
329 s.set_range(0, 8192);
330 s.should_wrap = true;
331
332 search_for_match(1502, 8192, Search::SearchDirection::FORWARDS, 1500);
333 }
334
TEST_F(SearchBaseTest,BackwardsMatchAfterSubRangeNoWrap)335 TEST_F(SearchBaseTest, BackwardsMatchAfterSubRangeNoWrap)
336 {
337 doc->overwrite_data(1000, "foobar", 6);
338 doc->overwrite_data(1500, "baz", 3);
339
340 s.set_range(0, 8192);
341
342 search_for_no_match(0, 1005, Search::SearchDirection::BACKWARDS);
343 }
344
TEST_F(SearchBaseTest,BackwardsMatchAfterSubRangeWrap)345 TEST_F(SearchBaseTest, BackwardsMatchAfterSubRangeWrap)
346 {
347 doc->overwrite_data(1000, "foobar", 6);
348 doc->overwrite_data(1500, "baz", 3);
349
350 s.set_range(0, 8192);
351 s.should_wrap = true;
352
353 search_for_match(0, 1005, Search::SearchDirection::BACKWARDS, 1500);
354 }
355
TEST_F(SearchBaseTest,BackwardsMatchAtEndOfRangeAfterSubRangeWrap)356 TEST_F(SearchBaseTest, BackwardsMatchAtEndOfRangeAfterSubRangeWrap)
357 {
358 doc->overwrite_data(1500, "baz", 3);
359
360 s.set_range(0, 1503);
361 s.should_wrap = true;
362
363 search_for_match(0, 1005, Search::SearchDirection::BACKWARDS, 1500);
364 }
365
TEST_F(SearchBaseTest,BackwardsMatchStraddlingStartOfSubRangeAfterSubRangeWrap)366 TEST_F(SearchBaseTest, BackwardsMatchStraddlingStartOfSubRangeAfterSubRangeWrap)
367 {
368 doc->overwrite_data(1000, "foobar", 6);
369
370 s.set_range(0, 8192);
371 s.should_wrap = true;
372
373 search_for_match(0, 1002, Search::SearchDirection::BACKWARDS, 1000);
374 }
375
TEST_F(SearchBaseTest,ForwardsMatchStartingBeforeRange)376 TEST_F(SearchBaseTest, ForwardsMatchStartingBeforeRange)
377 {
378 doc->overwrite_data(1000, "foobar", 6);
379 doc->overwrite_data(1500, "baz", 3);
380
381 s.set_range(1000, 8192);
382
383 search_for_match(0, 8192, Search::SearchDirection::FORWARDS, 1000);
384 }
385
TEST_F(SearchBaseTest,ForwardsMatchStartingAfterRange)386 TEST_F(SearchBaseTest, ForwardsMatchStartingAfterRange)
387 {
388 doc->overwrite_data(1000, "foobar", 6);
389 doc->overwrite_data(1500, "baz", 3);
390
391 s.set_range(1000, 4096);
392 s.should_wrap = true;
393
394 search_for_match(6000, 4096, Search::SearchDirection::FORWARDS, 1000);
395 }
396
TEST_F(SearchBaseTest,BackwardsMatchToBeforeRange)397 TEST_F(SearchBaseTest, BackwardsMatchToBeforeRange)
398 {
399 doc->overwrite_data(1000, "foobar", 6);
400 doc->overwrite_data(1500, "baz", 3);
401
402 s.set_range(1000, 4096);
403 s.should_wrap = true;
404
405 search_for_match(1000, 800, Search::SearchDirection::BACKWARDS, 1500);
406 }
407
TEST_F(SearchBaseTest,BackwardsMatchToAfterRange)408 TEST_F(SearchBaseTest, BackwardsMatchToAfterRange)
409 {
410 doc->overwrite_data(1000, "foobar", 6);
411 doc->overwrite_data(1500, "baz", 3);
412
413 s.set_range(1000, 4096);
414
415 search_for_match(1000, 8000, Search::SearchDirection::BACKWARDS, 1500);
416 }
417
TEST_F(SearchBaseTest,ForwardsZeroLengthRange)418 TEST_F(SearchBaseTest, ForwardsZeroLengthRange)
419 {
420 doc->overwrite_data(1000, "foobar", 6);
421 doc->overwrite_data(1500, "baz", 3);
422
423 s.set_range(1000, 1000);
424
425 search_for_no_match(1000, 1000, Search::SearchDirection::FORWARDS);
426 }
427
TEST_F(SearchBaseTest,BackwardsZeroLengthRange)428 TEST_F(SearchBaseTest, BackwardsZeroLengthRange)
429 {
430 doc->overwrite_data(1000, "foobar", 6);
431 doc->overwrite_data(1500, "baz", 3);
432
433 s.set_range(1000, 1000);
434
435 search_for_no_match(1000, 1000, Search::SearchDirection::BACKWARDS);
436 }
437