Skip to content

Commit

Permalink
CI: Lint: Add more linters to test files (#725)
Browse files Browse the repository at this point in the history
* CI: Lint: Add more linters to test files

* Fix line lengths in test files

* Fix type hints

* Use fstrings in tests

* Make tests use tuple as default iterable

* Add more lint rules and move some to 'common'

* lint: Fail on error
  • Loading branch information
Grazfather committed Sep 25, 2021
1 parent 143e60a commit 6400ef7
Show file tree
Hide file tree
Showing 3 changed files with 93 additions and 59 deletions.
11 changes: 7 additions & 4 deletions Makefile
@@ -1,9 +1,12 @@
NB_CORES := $(shell grep --count '^processor' /proc/cpuinfo)
PYLINT_DISABLE:= all
PYLINT_ENABLE := F,E,unreachable,duplicate-key,unnecessary-semicolon,global-variable-not-assigned,unused-variable,binary-op-exception,bad-format-string,anomalous-backslash-in-string,bad-open-mode
PYLINT_ENABLE := F,E,unreachable,duplicate-key,unnecessary-semicolon,unused-variable,binary-op-exception,bad-format-string,anomalous-backslash-in-string,bad-open-mode,dangerous-default-value,trailing-whitespace,unneeded-not,singleton-comparison,unused-import
PYLINT_TEST_ENABLE := $(PYLINT_ENABLE),line-too-long,multiple-statements,consider-using-f-string,global-variable-not-assigned
PYLINT_JOBS := $(NB_CORES)
PYLINT_SUGGEST_FIX := y
PYLINT_PARAMETERS := --disable=$(PYLINT_DISABLE) --enable=$(PYLINT_ENABLE) --jobs=$(PYLINT_JOBS) --suggestion-mode=$(PYLINT_SUGGEST_FIX) --exit-zero
PYLINT_COMMON_PARAMETERS := --jobs=$(PYLINT_JOBS) --suggestion-mode=$(PYLINT_SUGGEST_FIX)
PYLINT_GEF_PARAMETERS := --disable=$(PYLINT_DISABLE) --enable=$(PYLINT_ENABLE) $(PYLINT_COMMON_PARAMETERS)
PYLINT_TEST_PARAMETERS := --disable=$(PYLINT_DISABLE) --enable=$(PYLINT_TEST_ENABLE) $(PYLINT_COMMON_PARAMETERS)
TARGET := $(shell lscpu | head -1 | sed -e 's/Architecture:\s*//g')

test: testbins
Expand All @@ -23,5 +26,5 @@ testbins: tests/binaries/*.c
@$(MAKE) -j $(NB_CORES) -C tests/binaries TARGET=$(TARGET) all

lint:
python3 -m pylint $(PYLINT_PARAMETERS) gef.py
python3 -m pylint $(PYLINT_PARAMETERS) tests/*.py
python3 -m pylint $(PYLINT_GEF_PARAMETERS) gef.py
python3 -m pylint $(PYLINT_TEST_PARAMETERS) tests/*.py
59 changes: 37 additions & 22 deletions tests/helpers.py
@@ -1,4 +1,4 @@
from typing import List, Iterable
from typing import Iterable
import re
import subprocess
import os
Expand All @@ -21,7 +21,8 @@ def ansi_clean(s: str) -> str:
return ansi_escape.sub("", s)


def gdb_run_cmd(cmd: str, before: List[str]=[], after: List[str]=[], target: str=PATH_TO_DEFAULT_BINARY, strip_ansi=STRIP_ANSI_DEFAULT) -> str:
def gdb_run_cmd(cmd: str, before: Iterable[str]=(), after: Iterable[str]=(),
target: str=PATH_TO_DEFAULT_BINARY, strip_ansi=STRIP_ANSI_DEFAULT) -> str:
"""Execute a command inside GDB. `before` and `after` are lists of commands to be executed
before (resp. after) the command to test."""
command = [
Expand All @@ -44,10 +45,12 @@ def gdb_run_cmd(cmd: str, before: List[str]=[], after: List[str]=[], target: str
output = b"\n".join(lines)
result = None

# The following is necessary because ANSI escape sequences might have been added in the middle of multibyte
# characters, e.g. \x1b[H\x1b[2J is added into the middle of \xe2\x94\x80 to become \xe2\x1b[H\x1b[2J\x94\x80
# which causes a UnicodeDecodeError when trying to decode \xe2.
# Such broken multibyte characters would need to be removed, otherwise the test will result in an error.
# The following is necessary because ANSI escape sequences might have been
# added in the middle of multibyte characters, e.g. \x1b[H\x1b[2J is added
# into the middle of \xe2\x94\x80 to become \xe2\x1b[H\x1b[2J\x94\x80 which
# causes a UnicodeDecodeError when trying to decode \xe2. Such broken
# multibyte characters would need to be removed, otherwise the test will
# result in an error.
while not result:
try:
result = output.decode("utf-8")
Expand All @@ -62,35 +65,47 @@ def gdb_run_cmd(cmd: str, before: List[str]=[], after: List[str]=[], target: str
return result


def gdb_run_silent_cmd(cmd, before: List[str]=[], after: List[str]=[], target: str=PATH_TO_DEFAULT_BINARY, strip_ansi: str=STRIP_ANSI_DEFAULT) -> str:
def gdb_run_silent_cmd(cmd, before: Iterable[str]=(), after: Iterable[str]=(),
target: str=PATH_TO_DEFAULT_BINARY,
strip_ansi: bool=STRIP_ANSI_DEFAULT) -> str:
"""Disable the output and run entirely the `target` binary."""
before += ["gef config context.clear_screen False",
"gef config context.layout '-code -stack'",
"run"]
before = [*before, "gef config context.clear_screen False",
"gef config context.layout '-code -stack'",
"run"]
return gdb_run_cmd(cmd, before, after, target, strip_ansi)


def gdb_run_cmd_last_line(cmd, before: List[str]=[], after: List[str]=[], target: str=PATH_TO_DEFAULT_BINARY, strip_ansi: str=STRIP_ANSI_DEFAULT) -> str:
def gdb_run_cmd_last_line(cmd, before: Iterable[str]=(), after: Iterable[str]=(),
target: str=PATH_TO_DEFAULT_BINARY,
strip_ansi: bool=STRIP_ANSI_DEFAULT) -> str:
"""Execute a command in GDB, and return only the last line of its output."""
return gdb_run_cmd(cmd, before, after, target, strip_ansi).splitlines()[-1]


def gdb_start_silent_cmd(cmd, before: List[str]=[], after: List[str]=[], target=PATH_TO_DEFAULT_BINARY, strip_ansi=STRIP_ANSI_DEFAULT, context=DEFAULT_CONTEXT) -> str:
"""Execute a command in GDB by starting an execution context. This command disables the `context`
and sets a tbreak at the most convenient entry point."""
before += ["gef config context.clear_screen False",
"gef config context.layout '{}'".format(context),
"entry-break"]
def gdb_start_silent_cmd(cmd, before: Iterable[str]=(), after: Iterable[str]=(),
target=PATH_TO_DEFAULT_BINARY, strip_ansi=STRIP_ANSI_DEFAULT,
context=DEFAULT_CONTEXT) -> str:
"""Execute a command in GDB by starting an execution context. This command
disables the `context` and sets a tbreak at the most convenient entry
point."""
before = [*before, "gef config context.clear_screen False",
f"gef config context.layout '{context}'",
"entry-break"]
return gdb_run_cmd(cmd, before, after, target, strip_ansi)


def gdb_start_silent_cmd_last_line(cmd, before: List[str]=[], after: List[str]=[], target=PATH_TO_DEFAULT_BINARY, strip_ansi=STRIP_ANSI_DEFAULT) -> str:
def gdb_start_silent_cmd_last_line(cmd, before: Iterable[str]=(), after: Iterable[str]=(),
target=PATH_TO_DEFAULT_BINARY,
strip_ansi=STRIP_ANSI_DEFAULT) -> str:
"""Execute `gdb_start_silent_cmd()` and return only the last line of its output."""
return gdb_start_silent_cmd(cmd, before, after, target, strip_ansi).splitlines()[-1]


def gdb_test_python_method(meth: str, before: str="", after: str="", target: str=PATH_TO_DEFAULT_BINARY, strip_ansi: str=STRIP_ANSI_DEFAULT) -> str:
cmd = "pi {}print({});{}".format(before+";" if before else "", meth, after)
def gdb_test_python_method(meth: str, before: str="", after: str="",
target: str=PATH_TO_DEFAULT_BINARY,
strip_ansi: bool=STRIP_ANSI_DEFAULT) -> str:
brk = before + ";" if before else ""
cmd = f"pi {brk}print({meth});{after}"
return gdb_start_silent_cmd(cmd, target=target, strip_ansi=strip_ansi)


Expand All @@ -106,7 +121,7 @@ def inner_f(*args, **kwargs):
return wrapper


def exclude_for_architectures(invalid_architectures: Iterable[str] = []):
def exclude_for_architectures(invalid_architectures: Iterable[str]=()):
def wrapper(f):
def inner_f(*args, **kwargs):
if ARCH not in invalid_architectures:
Expand All @@ -115,4 +130,4 @@ def inner_f(*args, **kwargs):
sys.stderr.write(f"SKIPPED for {ARCH} ")
sys.stderr.flush()
return inner_f
return wrapper
return wrapper
82 changes: 49 additions & 33 deletions tests/runtests.py
Expand Up @@ -18,10 +18,9 @@
gdb_start_silent_cmd_last_line,
gdb_test_python_method,
include_for_architectures,
exclude_for_architectures,
ARCH,
is_64b
) # pylint: disable=import-error
)


class GdbAssertionError(AssertionError):
Expand All @@ -33,7 +32,7 @@ class GefUnitTestGeneric(unittest.TestCase):

@staticmethod
def assertException(buf):
"""Expect an exception to be raised"""
"""Assert that GEF raised an Exception."""
if not ("Python Exception <" in buf
or "Traceback" in buf
or "'gdb.error'" in buf
Expand All @@ -43,12 +42,12 @@ def assertException(buf):

@staticmethod
def assertNoException(buf):
"""No exception should be raised"""
if not ("Python Exception <" not in buf
and "Traceback" not in buf
and "'gdb.error'" not in buf
and "Exception raised" not in buf
and "failed to execute properly, reason:" not in buf):
"""Assert that no Exception was raised from GEF."""
if ("Python Exception <" in buf
or "Traceback" in buf
or "'gdb.error'" in buf
or "Exception raised" in buf
or "failed to execute properly, reason:" in buf):
raise GdbAssertionError("Unexpected GDB Exception raised")

@staticmethod
Expand Down Expand Up @@ -193,7 +192,7 @@ def test_cmd_got(self):

def test_cmd_gef_remote(self):
def start_gdbserver(exe="/tmp/default.out", port=1234):
return subprocess.Popen(["gdbserver", ":{}".format(port), exe],
return subprocess.Popen(["gdbserver", f":{port}", exe],
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)

def stop_gdbserver(gdbserver):
Expand Down Expand Up @@ -318,7 +317,7 @@ def test_cmd_heap_analysis(self):
self.assertIn("calloc(32)=", res)
addr = int(res.split("calloc(32)=")[1].split("\n")[0], 0)
self.assertRegex(res, r"realloc\(.+, 48")
self.assertIn("free({:#x}".format(addr), res)
self.assertIn(f"free({addr:#x}", res)
return

def test_cmd_hexdump(self):
Expand Down Expand Up @@ -357,7 +356,7 @@ def test_cmd_memory_watch(self):
self.assertNotIn("cafebabe", res)
res = gdb_start_silent_cmd("memory watch &myglobal",
before=["set args 0xcafebabe",],
after=["continue", ],
after=["continue"],
target=target,
context="memory")
self.assertIn("cafebabe", res)
Expand Down Expand Up @@ -438,13 +437,15 @@ def test_cmd_patch_word(self):
return

def test_cmd_patch_dword(self):
res = gdb_start_silent_cmd_last_line("patch dword $pc 0xcccccccc", after=["display/8bx $pc",])
res = gdb_start_silent_cmd_last_line("patch dword $pc 0xcccccccc",
after=["display/8bx $pc",])
self.assertNoException(res)
self.assertRegex(res, r"(0xcc\s*)(\1\1\1)0x[^c]{2}")
return

def test_cmd_patch_qword(self):
res = gdb_start_silent_cmd_last_line("patch qword $pc 0xcccccccccccccccc", after=["display/8bx $pc",])
res = gdb_start_silent_cmd_last_line("patch qword $pc 0xcccccccccccccccc",
after=["display/8bx $pc",])
self.assertNoException(res)
self.assertRegex(res, r"(0xcc\s*)(\1\1\1\1\1\1)0xcc")
return
Expand All @@ -460,7 +461,8 @@ def test_cmd_patch_qword_symbol(self):
return

def test_cmd_patch_string(self):
res = gdb_start_silent_cmd_last_line("patch string $sp \"Gef!Gef!Gef!Gef!\"", after=["grep Gef!Gef!Gef!Gef!",])
res = gdb_start_silent_cmd_last_line("patch string $sp \"Gef!Gef!Gef!Gef!\"",
after=["grep Gef!Gef!Gef!Gef!",])
self.assertNoException(res)
self.assertIn("Gef!Gef!Gef!Gef!", res)
return
Expand Down Expand Up @@ -490,13 +492,15 @@ def test_cmd_pattern_search(self):

cmd = f"pattern search {r}"
target = "/tmp/pattern.out"
res = gdb_run_cmd(cmd, before=["set args aaaabaaacaaadaaaeaaafaaagaaahaaa", "run"], target=target)
res = gdb_run_cmd(cmd, before=["set args aaaabaaacaaadaaaeaaafaaagaaahaaa", "run"],
target=target)
self.assertNoException(res)
self.assertIn("Found at offset", res)

cmd = f"pattern search --period 8 {r}"
target = "/tmp/pattern.out"
res = gdb_run_cmd(cmd, before=["set args aaaaaaaabaaaaaaacaaaaaaadaaaaaaa", "run"], target=target)
res = gdb_run_cmd(cmd, before=["set args aaaaaaaabaaaaaaacaaaaaaadaaaaaaa", "run"],
target=target)
self.assertNoException(res)
self.assertIn("Found at offset", res)
return
Expand Down Expand Up @@ -524,15 +528,18 @@ def test_cmd_process_status(self):
return

def test_cmd_process_search(self):
res = gdb_start_silent_cmd("process-search", target="/tmp/pattern.out", before=["set args w00tw00t", ])
res = gdb_start_silent_cmd("process-search", target="/tmp/pattern.out",
before=["set args w00tw00t"])
self.assertNoException(res)
self.assertIn("/tmp/pattern.out", res)

res = gdb_start_silent_cmd("process-search gdb.*fakefake", target="/tmp/pattern.out", before=["set args w00tw00t", ])
res = gdb_start_silent_cmd("process-search gdb.*fakefake",
target="/tmp/pattern.out", before=["set args w00tw00t"])
self.assertNoException(res)
self.assertIn("gdb", res)

res = gdb_start_silent_cmd("process-search --smart-scan gdb.*fakefake", target="/tmp/pattern.out", before=["set args w00tw00t", ])
res = gdb_start_silent_cmd("process-search --smart-scan gdb.*fakefake",
target="/tmp/pattern.out", before=["set args w00tw00t"])
self.assertNoException(res)
self.assertNotIn("gdb", res)
return
Expand Down Expand Up @@ -605,7 +612,8 @@ def test_cmd_set_permission(self):
stack_address = int(stack_line.split()[0], 0)

# compare the new permissions
res = gdb_start_silent_cmd(f"set-permission {stack_address:#x}", after=[f"xinfo {stack_address:#x}",], target=target)
res = gdb_start_silent_cmd(f"set-permission {stack_address:#x}",
after=[f"xinfo {stack_address:#x}",], target=target)
self.assertNoException(res)
line = [l.strip() for l in res.splitlines() if l.startswith("Permissions: ")][0]
self.assertEqual(line.split()[1], "rwx")
Expand Down Expand Up @@ -677,12 +685,12 @@ def test_cmd_theme(self):
]
for t in possible_themes:
# testing command viewing
res = gdb_run_cmd("theme {}".format(t))
res = gdb_run_cmd(f"theme {t}")
self.assertNoException(res)

# testing command setting
v = "blue blah 10 -1 0xfff bold"
res = gdb_run_cmd("theme {} {}".format(t, v))
res = gdb_run_cmd(f"theme {t} {v}")
self.assertNoException(res)
return

Expand All @@ -692,21 +700,22 @@ def test_cmd_trace_run(self):
self.assertFailIfInactiveSession(res)

cmd = "trace-run $pc+1"
res = gdb_start_silent_cmd(cmd, before=["gef config trace-run.tracefile_prefix /tmp/gef-trace-"])
res = gdb_start_silent_cmd(cmd,
before=["gef config trace-run.tracefile_prefix /tmp/gef-trace-"])
self.assertNoException(res)
self.assertIn("Tracing from", res)
return

@include_for_architectures(["x86_64"])
def test_cmd_unicorn_emulate(self):
nb_insn = 4
cmd = "emu {}".format(nb_insn)
cmd = f"emu {nb_insn}"
res = gdb_run_silent_cmd(cmd)
self.assertFailIfInactiveSession(res)

target = "/tmp/unicorn.out"
before = ["break function1", ]
after = ["si", ]
before = ["break function1"]
after = ["si"]
start_marker = "= Starting emulation ="
end_marker = "Final registers"
res = gdb_run_silent_cmd(cmd, target=target, before=before, after=after)
Expand Down Expand Up @@ -785,11 +794,14 @@ def test_cmd_aliases(self):
add_res = gdb_start_silent_cmd("aliases add alias_function_test example")
self.assertNoException(add_res)
# test list functionality
list_res = gdb_start_silent_cmd("aliases ls", before=["aliases add alias_function_test example"])
list_res = gdb_start_silent_cmd("aliases ls",
before=["aliases add alias_function_test example"])
self.assertNoException(list_res)
self.assertIn("alias_function_test", list_res)
# test rm functionality
rm_res = gdb_start_silent_cmd("aliases ls", before=["aliases add alias_function_test example", "aliases rm alias_function_test"])
rm_res = gdb_start_silent_cmd("aliases ls",
before=["aliases add alias_function_test example",
"aliases rm alias_function_test"])
self.assertNoException(rm_res)
self.assertNotIn("alias_function_test", rm_res)
return
Expand Down Expand Up @@ -948,9 +960,12 @@ def test_registers_show_registers_in_correct_order(self):
"""Ensure the registers are printed in the correct order (PR #670)."""
cmd = "registers"
if ARCH == "i686":
registers_in_correct_order = ["$eax", "$ebx", "$ecx", "$edx", "$esp", "$ebp", "$esi", "$edi", "$eip", "$eflags", "$cs", ]
registers_in_correct_order = ["$eax", "$ebx", "$ecx", "$edx", "$esp", "$ebp", "$esi",
"$edi", "$eip", "$eflags", "$cs"]
elif ARCH == "x86_64":
registers_in_correct_order = ["$rax", "$rbx", "$rcx", "$rdx", "$rsp", "$rbp", "$rsi", "$rdi", "$rip", "$r8", "$r9", "$r10", "$r11", "$r12", "$r13", "$r14", "$r15", "$eflags", "$cs", ]
registers_in_correct_order = ["$rax", "$rbx", "$rcx", "$rdx", "$rsp", "$rbp", "$rsi",
"$rdi", "$rip", "$r8", "$r9", "$r10", "$r11", "$r12",
"$r13", "$r14", "$r15", "$eflags", "$cs"]
else:
raise ValueError("Unknown architecture")
lines = gdb_start_silent_cmd(cmd).splitlines()[-len(registers_in_correct_order):]
Expand All @@ -961,8 +976,9 @@ def test_registers_show_registers_in_correct_order(self):
@include_for_architectures(["x86_64",])
def test_context_correct_registers_refresh_with_frames(self):
"""Ensure registers are correctly refreshed when changing frame (PR #668)"""
lines = gdb_run_silent_cmd("registers", after=["frame 5", "registers"], target="/tmp/nested.out").splitlines()
rips = [ x for x in lines if x.startswith("$rip") ]
lines = gdb_run_silent_cmd("registers", after=["frame 5", "registers"],
target="/tmp/nested.out").splitlines()
rips = [x for x in lines if x.startswith("$rip")]
self.assertEqual(len(rips), 2) # we must have only 2 entries
self.assertNotEqual(rips[0], rips[1]) # they must be different
self.assertIn("<f10", rips[0]) # the first one must be in the f10 frame
Expand Down

0 comments on commit 6400ef7

Please sign in to comment.