Skip to content

Commit

Permalink
Make py_cstest a proper package to not run into weird import problems
Browse files Browse the repository at this point in the history
  • Loading branch information
Rot127 committed Aug 16, 2024
1 parent 0af3e5d commit b294376
Show file tree
Hide file tree
Showing 12 changed files with 250 additions and 9 deletions.
6 changes: 3 additions & 3 deletions .github/workflows/python-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,13 @@ jobs:
- name: Build and install capstone
run: pip install ./bindings/python

- name: Install cstest dependencies
run: pip install pyyaml
- name: Install py_cstest
run: pip install ./bindings/python/py_cstest

- name: Run legacy tests
run: python ./bindings/python/tests/test_all.py

- name: cstest.py integration tests
run: |
cd suite/cstest/test/
python3 ./integration_tests.py "python3 ../../../bindings/python/py_cstest/cstest.py"
python3 ./integration_tests.py py_cstest
1 change: 0 additions & 1 deletion bindings/python/.gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
MANIFEST
dist/
src/
capstone/lib
capstone/include
pyx/lib
Expand Down
18 changes: 18 additions & 0 deletions bindings/python/py_cstest/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Copyright © 2024 Rot127 <[email protected]>
# SPDX-License-Identifier: BSD-3

[project]
name = "py_cstest"
version = "0.1.0"
dependencies = [
"pyyaml >= 6.0.2",
"capstone >= 5.0.0",
]
requires-python = ">= 3.8"

[tool.setuptools]
packages = ["py_cstest"]
package-dir = {"" = "src"}

[project.scripts]
py_cstest = "py_cstest.cstest:main"
6 changes: 6 additions & 0 deletions bindings/python/py_cstest/src/py_cstest.egg-info/PKG-INFO
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
Metadata-Version: 2.1
Name: py_cstest
Version: 0.1.0
Requires-Python: >=3.8
Requires-Dist: pyyaml>=6.0.2
Requires-Dist: capstone>=5.0.0
11 changes: 11 additions & 0 deletions bindings/python/py_cstest/src/py_cstest.egg-info/SOURCES.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
README.md
pyproject.toml
src/py_cstest/compare.py
src/py_cstest/cs_modes.py
src/py_cstest/cstest.py
src/py_cstest.egg-info/PKG-INFO
src/py_cstest.egg-info/SOURCES.txt
src/py_cstest.egg-info/dependency_links.txt
src/py_cstest.egg-info/entry_points.txt
src/py_cstest.egg-info/requires.txt
src/py_cstest.egg-info/top_level.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@

Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[console_scripts]
py_cstest = py_cstest.cstest:main
2 changes: 2 additions & 0 deletions bindings/python/py_cstest/src/py_cstest.egg-info/requires.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
pyyaml>=6.0.2
capstone>=5.0.0
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
py_cstest
155 changes: 155 additions & 0 deletions bindings/python/py_cstest/src/py_cstest/compare.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
# Copyright © 2024 Rot127 <[email protected]>
# SPDX-License-Identifier: BSD-3

import capstone
import logging as log
import re


def compare_asm_text(a_insn: capstone.CsInsn, expected: str, arch_bits: int) -> bool:
actual = f"{a_insn.mnemonic} {a_insn.op_str}"
actual = actual.strip()
actual = re.sub(r"\s+", " ", actual)
# Replace hex numbers with decimals
for hex_num in re.findall(r"0x[0-9a-fA-F]+", actual):
actual = re.sub(hex_num, f"{int(hex_num, base=16)}", actual)
# Replace negatives with twos-complement
for num in re.findall(r"\d+", actual):
actual = re.sub(num, f"{~(num % (1 << arch_bits)) + 1}", actual)
actual = actual.lower()

if actual != expected:
log.error(
"Normalized asm-text doesn't match:\n"
f"decoded: '{actual}'\n"
f"expected: '{expected}'\n"
)
return False
return True


def compare_str(actual: str, expected: str, msg: str) -> bool:
if actual != expected:
log.error(f"{msg}: {actual} != {expected}")
return False
return True


def compare_tbool(actual: bool, expected: int, msg: str) -> bool:
if expected == 0:
# Unset
return True

if (expected < 0 and actual) or (expected > 0 and not actual):
log.error(f"{msg}: {actual} != {expected}")
return False
return True


def compare_uint8(actual: int, expected: int, msg: str) -> bool:
actual = actual & 0xFF
expected = expected & 0xFF
if actual != expected:
log.error(f"{msg}: {actual} != {expected}")
return False
return True


def compare_int8(actual: int, expected: int, msg: str) -> bool:
actual = actual & 0xFF
expected = expected & 0xFF
if actual != expected:
log.error(f"{msg}: {actual} != {expected}")
return False
return True


def compare_uint16(actual: int, expected: int, msg: str) -> bool:
actual = actual & 0xFFFF
expected = expected & 0xFFFF
if actual != expected:
log.error(f"{msg}: {actual} != {expected}")
return False
return True


def compare_int16(actual: int, expected: int, msg: str) -> bool:
actual = actual & 0xFFFF
expected = expected & 0xFFFF
if actual != expected:
log.error(f"{msg}: {actual} != {expected}")
return False
return True


def compare_uint32(actual: int, expected: int, msg: str) -> bool:
actual = actual & 0xFFFFFFFF
expected = expected & 0xFFFFFFFF
if actual != expected:
log.error(f"{msg}: {actual} != {expected}")
return False
return True


def compare_int32(actual: int, expected: int, msg: str) -> bool:
actual = actual & 0xFFFFFFFF
expected = expected & 0xFFFFFFFF
if actual != expected:
log.error(f"{msg}: {actual} != {expected}")
return False
return True


def compare_uint64(actual: int, expected: int, msg: str) -> bool:
actual = actual & 0xFFFFFFFFFFFFFFFF
expected = expected & 0xFFFFFFFFFFFFFFFF
if actual != expected:
log.error(f"{msg}: {actual} != {expected}")
return False
return True


def compare_int64(actual: int, expected: int, msg: str) -> bool:
actual = actual & 0xFFFFFFFFFFFFFFFF
expected = expected & 0xFFFFFFFFFFFFFFFF
if actual != expected:
log.error(f"{msg}: {actual} != {expected}")
return False
return True


def compare_fp(actual: float, expected: float, msg: str) -> bool:
if actual != expected:
log.error(f"{msg}: {actual} != {expected}")
return False
return True


def compare_enum(actual, expected, msg: str) -> bool:
enum_val = getattr(capstone, expected)
if not enum_val:
log.error(f"capstone package doesn't have the an attribute '{expected}'")
return False
if actual != expected:
log.error(f"{msg}: {actual} != {expected}")
return False
return True


def compare_bit_flags(actual: int, expected: list[str], msg: str) -> bool:
for flag in expected:
enum_val = getattr(capstone, flag)
if not enum_val:
log.error(f"capstone package doesn't have the an attribute '{expected}'")
return False
if not actual & enum_val:
log.error(f"{msg}: In {actual:x} the flag {expected} isn't set.")
return False
return True


def compare_reg(handle: capstone.Cs, actual: int, expected: str, msg: str) -> bool:
if handle.reg_name(actual) != expected:
log.error(f"{msg}: {actual} != {expected}")
return False
return True
41 changes: 41 additions & 0 deletions bindings/python/py_cstest/src/py_cstest/cs_modes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# Copyright © 2024 Rot127 <[email protected]>
# SPDX-License-Identifier: BSD-3

import capstone as cs

configs = {
"CS_OPT_DETAIL": {"type": cs.CS_OPT_DETAIL, "val": cs.CS_OPT_ON},
"CS_OPT_DETAIL_REAL": {
"type": cs.CS_OPT_DETAIL,
"val": cs.CS_OPT_DETAIL_REAL | cs.CS_OPT_ON,
},
"CS_OPT_SKIPDATA": {"type": cs.CS_OPT_SKIPDATA, "val": cs.CS_OPT_ON},
"CS_OPT_UNSIGNED": {"type": cs.CS_OPT_UNSIGNED, "val": cs.CS_OPT_ON},
"CS_OPT_NO_BRANCH_OFFSET": {
"type": cs.CS_OPT_NO_BRANCH_OFFSET,
"val": cs.CS_OPT_ON,
},
"CS_OPT_SYNTAX_DEFAULT": {
"type": cs.CS_OPT_SYNTAX,
"val": cs.CS_OPT_SYNTAX_DEFAULT,
},
"CS_OPT_SYNTAX_INTEL": {"type": cs.CS_OPT_SYNTAX, "val": cs.CS_OPT_SYNTAX_INTEL},
"CS_OPT_SYNTAX_ATT": {"type": cs.CS_OPT_SYNTAX, "val": cs.CS_OPT_SYNTAX_ATT},
"CS_OPT_SYNTAX_NOREGNAME": {
"type": cs.CS_OPT_SYNTAX,
"val": cs.CS_OPT_SYNTAX_NOREGNAME,
},
"CS_OPT_SYNTAX_MASM": {"type": cs.CS_OPT_SYNTAX, "val": cs.CS_OPT_SYNTAX_MASM},
"CS_OPT_SYNTAX_MOTOROLA": {
"type": cs.CS_OPT_SYNTAX,
"val": cs.CS_OPT_SYNTAX_MOTOROLA,
},
"CS_OPT_SYNTAX_CS_REG_ALIAS": {
"type": cs.CS_OPT_SYNTAX,
"val": cs.CS_OPT_SYNTAX_CS_REG_ALIAS,
},
"CS_OPT_SYNTAX_PERCENT": {
"type": cs.CS_OPT_SYNTAX,
"val": cs.CS_OPT_SYNTAX_PERCENT,
},
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,11 @@
import os
import yaml
import capstone
import cs_modes

from capstone import CsInsn, Cs, CS_ARCH_AARCH64, CS_MODE_64, CS_MODE_16
from compare import (

from py_cstest.cs_modes import configs
from py_cstest.compare import (
compare_asm_text,
compare_str,
compare_tbool,
Expand Down Expand Up @@ -185,8 +186,8 @@ def setup(self):
if mode:
new_mode |= mode
continue
if "CS_OPT_" in opt and opt in cs_modes.configs:
mtype, val = cs_modes.configs[opt]
if "CS_OPT_" in opt and opt in configs:
mtype, val = configs[opt]
self.handle.option(mtype, val)
continue
log.warning(f"Option: '{opt}' not used")
Expand Down Expand Up @@ -452,7 +453,7 @@ def parse_args() -> argparse.Namespace:
return arguments


if __name__ == "__main__":
def main():
log_levels = {
"debug": logging.DEBUG,
"info": logging.INFO,
Expand All @@ -477,3 +478,7 @@ def parse_args() -> argparse.Namespace:
log.addHandler(h1)
log.addHandler(h2)
CSTest(args.search_dir, args.exclude, args.include).run_tests()


if __name__ == "__main__":
main()

0 comments on commit b294376

Please sign in to comment.