Skip to content

Commit c56f61a

Browse files
committed
Add tests for automatic Rizin installation
1 parent 63257d9 commit c56f61a

File tree

1 file changed

+335
-4
lines changed

1 file changed

+335
-4
lines changed

tests/utils/test_tools.py

Lines changed: 335 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,56 @@
1+
import os
2+
import re
3+
import shutil
4+
import subprocess
5+
from unittest.mock import patch
6+
17
import pytest
8+
from quark import config
9+
from quark.utils.tools import (contains, descriptor_to_androguard_format,
10+
download_rizin, find_rizin_instance,
11+
get_rizin_version, remove_dup_list,
12+
update_rizin)
13+
14+
15+
@pytest.fixture(scope="module")
16+
def rizin_in_system_path():
17+
path = shutil.which("rizin")
18+
assert path
19+
20+
return path
21+
22+
23+
@pytest.fixture(scope="module")
24+
def rizin_version(rizin_in_system_path):
25+
try:
26+
process = subprocess.run(
27+
[rizin_in_system_path, "-v"],
28+
timeout=5,
29+
check=True,
30+
stdout=subprocess.PIPE,
31+
)
32+
result = str(process.stdout)
233

3-
from quark.utils.tools import (
4-
contains,
5-
descriptor_to_androguard_format,
6-
remove_dup_list,
34+
matched_versions = re.finditer(
35+
r"[0-9]+\.[0-9]+\.[0-9]+", result[: result.index("@")]
36+
)
37+
first_matched = next(matched_versions, None)
38+
39+
assert first_matched
40+
41+
return first_matched.group(0)
42+
except TimeoutError:
43+
assert False
44+
except subprocess.CalledProcessError:
45+
assert False
46+
47+
48+
@pytest.fixture(
49+
scope="function",
50+
params=((True), (False)),
751
)
52+
def disable_rizin_installation(request):
53+
return request.param
854

955

1056
def test_remove_dup_list_with_invalid_arg():
@@ -112,3 +158,288 @@ def test_descriptor_to_androguard_format_with_combination():
112158
result = descriptor_to_androguard_format(descriptor)
113159

114160
assert result == "(I Ljava/lang/String; [B J)"
161+
162+
163+
def test_get_rizin_version_with_valid_path(
164+
rizin_in_system_path, rizin_version
165+
):
166+
expected_version = rizin_version
167+
168+
found_version = get_rizin_version(rizin_in_system_path)
169+
170+
assert found_version == expected_version
171+
172+
173+
def test_get_rizin_version_with_invalid_path(tmp_path):
174+
assert not get_rizin_version(tmp_path)
175+
176+
177+
def test_download_rizin_successfully(tmp_path):
178+
target_path = tmp_path / "rizin"
179+
180+
download_rizin(target_path)
181+
182+
assert os.access(target_path, os.F_OK | os.X_OK)
183+
184+
185+
def test_fail_to_download_rizin_due_to_unavailable_network(tmp_path):
186+
target_path = tmp_path / "rizin"
187+
188+
with patch("subprocess.Popen") as mock:
189+
mock.side_effect = subprocess.CalledProcessError(
190+
"1",
191+
"mock command",
192+
stderr=b"fatal: unable to access 'https://github.com/rizinorg/rizin/'.",
193+
)
194+
195+
assert not download_rizin(target_path)
196+
197+
198+
def test_fail_to_download_rizin_due_to_unknown_errors(tmp_path):
199+
target_path = tmp_path / "rizin"
200+
201+
with patch("subprocess.Popen") as mock:
202+
mock.side_effect = subprocess.CalledProcessError(
203+
"1", "mock command", stderr=b""
204+
)
205+
206+
assert not download_rizin(target_path)
207+
208+
209+
def test_update_rizin(tmp_path):
210+
target_path = tmp_path / "rizin"
211+
target_commit = config.RIZIN_COMMIT
212+
213+
download_rizin(target_path)
214+
215+
update_rizin(target_path, target_commit)
216+
check_commit = subprocess.run(
217+
["git", "rev-parse", "HEAD"],
218+
stdout=subprocess.PIPE,
219+
stderr=subprocess.PIPE,
220+
check=True,
221+
cwd=target_path,
222+
)
223+
real_commit = check_commit.stdout.strip().decode()
224+
225+
assert real_commit == target_commit
226+
assert os.access(
227+
target_path / "build" / "binrz" / "rizin" / "rizin", os.F_OK | os.X_OK
228+
)
229+
230+
231+
def test_fail_to_update_rizin_due_to_any_errors(tmp_path):
232+
target_path = tmp_path / "rizin"
233+
target_commit = config.RIZIN_COMMIT
234+
235+
with patch("subprocess.Popen") as mock:
236+
mock.side_effect = subprocess.CalledProcessError(
237+
"1", "mock command", stderr=b"Error message"
238+
)
239+
240+
assert not update_rizin(target_path, target_commit)
241+
242+
243+
def test_find_rizin_instance_in_system_path(rizin_in_system_path):
244+
rizin_path = find_rizin_instance()
245+
246+
assert rizin_path == rizin_in_system_path
247+
248+
249+
def test_find_rizin_instance_installed_in_quark_directory():
250+
rizin_source_path = "rizin_source_path"
251+
rizin_executable_path = rizin_source_path + "build/binrz/rizin/rizin"
252+
target_commit = "Unused"
253+
254+
with patch("shutil.which") as mocked_which:
255+
# Pretent there is no Rizin instance installed in the system.
256+
mocked_which.return_value = None
257+
258+
with patch(
259+
"quark.utils.tools.get_rizin_version"
260+
) as mocked_get_version:
261+
# Pretent the Rizin instance installed in the Quark directory is compatible.
262+
mocked_get_version.return_value = config.COMPATIBLE_RAZIN_VERSIONS[
263+
0
264+
]
265+
266+
# Must use the instance in the Quark directory.
267+
assert (
268+
find_rizin_instance(rizin_source_path, target_commit)
269+
== rizin_executable_path
270+
)
271+
272+
mocked_which.assert_called() # Must check the system path first.
273+
mocked_get_version.assert_called() # Must check the version of the instance in the Quark directory.
274+
275+
276+
def test_find_outdated_rizin_instance_installed_in_quark_directory(
277+
disable_rizin_installation,
278+
):
279+
rizin_source_path = "rizin_source_path"
280+
rizin_executable_path = rizin_source_path + "build/binrz/rizin/rizin"
281+
target_commit = "Unused"
282+
283+
with patch("shutil.which") as mocked_which:
284+
# Pretent there is no Rizin instance installed in the system.
285+
mocked_which.return_value = None
286+
287+
with patch(
288+
"quark.utils.tools.get_rizin_version"
289+
) as mocked_get_version:
290+
# Pretent the Rizin instance installed in the Quark directory is not compatible.
291+
mocked_get_version.return_value = "0.0.0"
292+
293+
with patch(
294+
"quark.utils.tools.update_rizin"
295+
) as mocked_update_rizin:
296+
# Pretent the upgrade is finished successfully.
297+
mocked_update_rizin.return_value = True
298+
299+
# Must use the instance in the Quark directory.
300+
assert (
301+
find_rizin_instance(
302+
rizin_source_path,
303+
target_commit,
304+
disable_rizin_installation,
305+
)
306+
== rizin_executable_path
307+
)
308+
309+
mocked_which.assert_called() # Must check the system path first.
310+
mocked_get_version.assert_called() # Must check the version of the instance in the Quark directory.
311+
if disable_rizin_installation:
312+
mocked_update_rizin.assert_not_called() # Must not update the instance
313+
else:
314+
mocked_update_rizin.assert_called() # Must update the instance to a compatible version
315+
316+
317+
_compatible_trigger = None
318+
319+
320+
def _side_effort_for_downloading_rizin(arg):
321+
global _compatible_trigger
322+
_compatible_trigger = True
323+
return True
324+
325+
326+
def test_find_broken_rizin_instance_installed_in_quark_directory(
327+
disable_rizin_installation,
328+
):
329+
rizin_source_path = "rizin_source_path"
330+
rizin_executable_path = rizin_source_path + "build/binrz/rizin/rizin"
331+
target_commit = "Unused"
332+
333+
with patch("shutil.which") as mocked_which:
334+
# Pretent there is no Rizin instance installed in the system.
335+
mocked_which.return_value = "rizin_installed_in_system"
336+
337+
with patch(
338+
"quark.utils.tools.get_rizin_version"
339+
) as mocked_get_version:
340+
# Pretent -
341+
# 1. the Rizin instance in the system path is not compatible
342+
# 2. the Rizin instance in the Quark directory is broken.
343+
mocked_get_version.side_effect = (
344+
lambda x: "0.0.0"
345+
if x == "rizin_installed_in_system"
346+
else _compatible_trigger
347+
)
348+
349+
with patch(
350+
"quark.utils.tools.download_rizin"
351+
) as mocked_download_rizin:
352+
# Pretent we can download the source code successfully.
353+
mocked_download_rizin.side_effect = (
354+
_side_effort_for_downloading_rizin
355+
)
356+
357+
with patch(
358+
"quark.utils.tools.update_rizin"
359+
) as mocked_update_rizin:
360+
# Pretent we can finish the upgrade successfully.
361+
mocked_update_rizin.return_value = True
362+
363+
result = find_rizin_instance(
364+
rizin_source_path,
365+
target_commit,
366+
disable_rizin_installation,
367+
)
368+
if disable_rizin_installation:
369+
# No Rizin instance exists
370+
assert result == None
371+
else:
372+
# Must use the instance in the Quark directory.
373+
assert result == rizin_executable_path
374+
375+
mocked_which.assert_called() # Must check the system path first.
376+
mocked_get_version.assert_called() # Must check the version of the instance in the Quark directory.
377+
378+
if disable_rizin_installation:
379+
mocked_download_rizin.assert_not_called() # Must not download the source code.
380+
mocked_update_rizin.assert_not_called() # Must not update and compile a Rizin instance.
381+
else:
382+
mocked_download_rizin.assert_called() # Must download the source code.
383+
mocked_update_rizin.assert_called() # Must update and compile a Rizin instance.
384+
385+
386+
def test_find_rizin_instance_failed_to_download_the_source():
387+
rizin_source_path = "rizin_source_path"
388+
target_commit = "Unused"
389+
390+
with patch("shutil.which") as mocked_which:
391+
# Pretent there is no Rizin instance installed in the system.
392+
mocked_which.return_value = None
393+
394+
with patch(
395+
"quark.utils.tools.get_rizin_version"
396+
) as mocked_get_version:
397+
# Pretent the Rizin instance installed in the Quark directory is broken.
398+
mocked_get_version.return_value = None
399+
400+
with patch(
401+
"quark.utils.tools.download_rizin"
402+
) as mocked_download_rizin:
403+
# Fail to download the source of Rizin.
404+
mocked_download_rizin.return_value = False
405+
406+
# Must use the instance in the Quark directory.
407+
assert (
408+
find_rizin_instance(rizin_source_path, target_commit)
409+
== None
410+
)
411+
412+
mocked_which.assert_called() # Must check the system path first.
413+
mocked_get_version.assert_called() # Must check the version of the instance in the Quark directory.
414+
mocked_download_rizin.assert_called() # Must try to download the source code of the Rizin.
415+
416+
417+
def test_find_rizin_instance_failed_to_compile_or_update_the_source():
418+
rizin_source_path = "rizin_source_path"
419+
target_commit = "Unused"
420+
421+
with patch("shutil.which") as mocked_which:
422+
# Pretent there is no Rizin instance installed in the system.
423+
mocked_which.return_value = None
424+
425+
with patch(
426+
"quark.utils.tools.get_rizin_version"
427+
) as mocked_get_version:
428+
# Pretent the Rizin instance installed in the Quark directory is not compatible.
429+
mocked_get_version.return_value = "0.0.0"
430+
431+
with patch(
432+
"quark.utils.tools.update_rizin"
433+
) as mocked_update_rizin:
434+
# Pretent the upgrade is finished successfully.
435+
mocked_update_rizin.return_value = False
436+
437+
# Must use the instance in the Quark directory.
438+
assert (
439+
find_rizin_instance(rizin_source_path, target_commit)
440+
== None
441+
)
442+
443+
mocked_which.assert_called() # Must check the system path first.
444+
mocked_get_version.assert_called() # Must check the version of the instance in the Quark directory.
445+
mocked_update_rizin.assert_called() # Must try to update and compile a Rizin instance.

0 commit comments

Comments
 (0)