Skip to content

Commit 42595b2

Browse files
committed
test: split test cases
Signed-off-by: pengyiqiang <pengyiqiang@xiaomi.com>
1 parent ad2e1c0 commit 42595b2

8 files changed

Lines changed: 2178 additions & 2289 deletions

tests/conftest.py

Lines changed: 147 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,158 @@
11
#!/usr/bin/env python3
22
# -*- coding: utf-8 -*-
33
"""
4-
Pytest configuration for LibSurgeon tests.
4+
LibSurgeon Test Suite - Shared Fixtures
55
6-
This file is automatically loaded by pytest and sets up the Python path
7-
to allow importing modules from the parent directory.
6+
This module contains shared pytest fixtures used across all test modules.
87
"""
98

109
import os
10+
import shutil
11+
import subprocess
1112
import sys
13+
import tempfile
14+
15+
import pytest
1216

1317
# Add parent directory to path for imports
1418
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
19+
20+
21+
# ============================================================
22+
# Fixtures
23+
# ============================================================
24+
25+
26+
@pytest.fixture
27+
def temp_dir():
28+
"""Create a temporary directory for tests"""
29+
tmp = tempfile.mkdtemp(prefix="libsurgeon_test_")
30+
yield tmp
31+
shutil.rmtree(tmp, ignore_errors=True)
32+
33+
34+
@pytest.fixture
35+
def sample_cpp_file(temp_dir):
36+
"""Create a sample decompiled C++ file"""
37+
content = """/**
38+
* Auto-generated decompiled code from: TestModule.o
39+
* Generated by LibSurgeon (Ghidra-based decompiler)
40+
*/
41+
42+
#include <stdint.h>
43+
#include <stdbool.h>
44+
45+
namespace xxgfx {
46+
47+
// ============================================================
48+
// Class: TestClass
49+
// ============================================================
50+
51+
// Function: testFunction
52+
53+
void __thiscall TestClass::testFunction(TestClass *this, int param_1)
54+
{
55+
undefined4 local_10;
56+
local_10 = *(undefined4 *)(this + 8);
57+
if (param_1 == 0) {
58+
__assert_fail("param != 0", "framework/source/test.cpp", 42, "void test()");
59+
}
60+
return;
61+
}
62+
63+
// Function: anotherFunction
64+
65+
int TestClass::anotherFunction(void)
66+
{
67+
return *(int *)(this + 0x10);
68+
}
69+
70+
} // namespace xxgfx
71+
"""
72+
filepath = os.path.join(temp_dir, "TestModule.cpp")
73+
with open(filepath, "w") as f:
74+
f.write(content)
75+
return filepath
76+
77+
78+
@pytest.fixture
79+
def sample_bad_cpp_file(temp_dir):
80+
"""Create a sample decompiled file with quality issues"""
81+
content = """/**
82+
* Auto-generated with issues
83+
*/
84+
85+
void bad_function(void)
86+
{
87+
halt_baddata();
88+
halt_baddata();
89+
undefined4 local_10;
90+
undefined8 local_20;
91+
undefined local_30;
92+
goto LAB_001234;
93+
LAB_001234:
94+
return;
95+
}
96+
"""
97+
filepath = os.path.join(temp_dir, "BadModule.cpp")
98+
with open(filepath, "w") as f:
99+
f.write(content)
100+
return filepath
101+
102+
103+
@pytest.fixture
104+
def sample_c_file(temp_dir):
105+
"""Create a sample decompiled C file (not C++)"""
106+
content = """/**
107+
* Auto-generated decompiled C code
108+
* Generated by LibSurgeon
109+
*/
110+
111+
#include <stdint.h>
112+
113+
// Function: simple_c_function
114+
115+
int simple_c_function(int param)
116+
{
117+
int result = param * 2;
118+
return result;
119+
}
120+
121+
// Function: another_c_function
122+
123+
void another_c_function(void)
124+
{
125+
return;
126+
}
127+
"""
128+
filepath = os.path.join(temp_dir, "c_module.c")
129+
with open(filepath, "w") as f:
130+
f.write(content)
131+
return filepath
132+
133+
134+
@pytest.fixture
135+
def test_archive(temp_dir):
136+
"""Create a minimal test archive if ar is available"""
137+
# Create a simple C file
138+
c_file = os.path.join(temp_dir, "test.c")
139+
with open(c_file, "w") as f:
140+
f.write("int test_func(void) { return 42; }\n")
141+
142+
# Compile to object file
143+
o_file = os.path.join(temp_dir, "test.o")
144+
try:
145+
subprocess.run(
146+
["gcc", "-c", c_file, "-o", o_file], check=True, capture_output=True
147+
)
148+
except (subprocess.CalledProcessError, FileNotFoundError):
149+
pytest.skip("gcc not available")
150+
151+
# Create archive
152+
a_file = os.path.join(temp_dir, "libtest.a")
153+
try:
154+
subprocess.run(["ar", "rcs", a_file, o_file], check=True, capture_output=True)
155+
except (subprocess.CalledProcessError, FileNotFoundError):
156+
pytest.skip("ar not available")
157+
158+
return a_file

tests/test_archive_elf.py

Lines changed: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
1+
#!/usr/bin/env python3
2+
# -*- coding: utf-8 -*-
3+
"""
4+
LibSurgeon Test Suite - Archive and ELF Processing Tests
5+
6+
Tests for archive extraction and ELF file detection/architecture detection.
7+
"""
8+
9+
import os
10+
11+
import pytest # noqa: F401 - used by fixtures
12+
13+
from libsurgeon import (
14+
ELF_MACHINE_MAP,
15+
detect_elf_architecture,
16+
extract_archive,
17+
is_archive_file,
18+
is_elf_file,
19+
)
20+
21+
22+
class TestArchiveProcessing:
23+
"""Tests for archive processing"""
24+
25+
def test_extract_archive(self, test_archive, temp_dir):
26+
"""Test archive extraction"""
27+
extract_dir = os.path.join(temp_dir, "extracted")
28+
obj_files = extract_archive(test_archive, extract_dir)
29+
30+
assert len(obj_files) >= 1
31+
assert any(f.endswith(".o") for f in obj_files)
32+
33+
def test_extract_archive_relative_path(self, test_archive, temp_dir):
34+
"""Test archive extraction with relative path"""
35+
orig_dir = os.getcwd()
36+
try:
37+
parent_dir = os.path.dirname(test_archive)
38+
archive_name = os.path.basename(test_archive)
39+
os.chdir(parent_dir)
40+
41+
extract_dir = os.path.join(temp_dir, "extracted_rel")
42+
obj_files = extract_archive(archive_name, extract_dir)
43+
44+
assert len(obj_files) >= 1
45+
assert any(f.endswith(".o") for f in obj_files)
46+
finally:
47+
os.chdir(orig_dir)
48+
49+
def test_is_archive_file(self, test_archive, temp_dir):
50+
"""Test archive magic number detection"""
51+
assert is_archive_file(test_archive) is True
52+
53+
fake_file = os.path.join(temp_dir, "fake.a")
54+
with open(fake_file, "w") as f:
55+
f.write("not an archive")
56+
assert is_archive_file(fake_file) is False
57+
58+
59+
class TestElfDetection:
60+
"""Tests for ELF file detection"""
61+
62+
def test_is_elf_file_valid(self, temp_dir):
63+
"""Test valid ELF magic number detection"""
64+
elf_file = os.path.join(temp_dir, "test.elf")
65+
with open(elf_file, "wb") as f:
66+
f.write(b"\x7fELF")
67+
f.write(b"\x00" * 100)
68+
assert is_elf_file(elf_file) is True
69+
70+
def test_is_elf_file_invalid(self, temp_dir):
71+
"""Test non-ELF file detection"""
72+
non_elf = os.path.join(temp_dir, "not_elf.bin")
73+
with open(non_elf, "wb") as f:
74+
f.write(b"NOT_ELF_CONTENT")
75+
assert is_elf_file(non_elf) is False
76+
77+
def test_is_elf_file_too_small(self, temp_dir):
78+
"""Test file too small for ELF header"""
79+
small_file = os.path.join(temp_dir, "small.elf")
80+
with open(small_file, "wb") as f:
81+
f.write(b"\x7fE")
82+
assert is_elf_file(small_file) is False
83+
84+
def test_is_elf_file_nonexistent(self):
85+
"""Test nonexistent file"""
86+
assert is_elf_file("/nonexistent/path/file.elf") is False
87+
88+
89+
class TestElfArchitectureDetection:
90+
"""Tests for ELF architecture detection"""
91+
92+
def test_detect_arm32_little_endian(self, temp_dir):
93+
"""Test ARM 32-bit little endian detection"""
94+
elf_file = os.path.join(temp_dir, "arm32le.o")
95+
with open(elf_file, "wb") as f:
96+
f.write(b"\x7fELF")
97+
f.write(b"\x01") # 32-bit
98+
f.write(b"\x01") # little endian
99+
f.write(b"\x01\x00\x00" + b"\x00" * 7)
100+
f.write(b"\x01\x00")
101+
f.write(b"\x28\x00") # ARM
102+
f.write(b"\x00" * 100)
103+
104+
result = detect_elf_architecture(elf_file)
105+
assert result is not None
106+
processor, cspec = result
107+
assert "ARM" in processor
108+
assert "LE" in processor
109+
assert "32" in processor
110+
111+
def test_detect_arm64_little_endian(self, temp_dir):
112+
"""Test ARM 64-bit (AARCH64) detection"""
113+
elf_file = os.path.join(temp_dir, "arm64le.o")
114+
with open(elf_file, "wb") as f:
115+
f.write(b"\x7fELF")
116+
f.write(b"\x02") # 64-bit
117+
f.write(b"\x01") # little endian
118+
f.write(b"\x01\x00\x00" + b"\x00" * 7)
119+
f.write(b"\x01\x00")
120+
f.write(b"\xb7\x00") # AARCH64
121+
f.write(b"\x00" * 100)
122+
123+
result = detect_elf_architecture(elf_file)
124+
assert result is not None
125+
processor, cspec = result
126+
assert "AARCH64" in processor
127+
assert "LE" in processor
128+
assert "64" in processor
129+
130+
def test_detect_x86_32(self, temp_dir):
131+
"""Test x86 32-bit detection"""
132+
elf_file = os.path.join(temp_dir, "x86_32.o")
133+
with open(elf_file, "wb") as f:
134+
f.write(b"\x7fELF")
135+
f.write(b"\x01")
136+
f.write(b"\x01")
137+
f.write(b"\x01\x00\x00" + b"\x00" * 7)
138+
f.write(b"\x01\x00")
139+
f.write(b"\x03\x00") # x86
140+
f.write(b"\x00" * 100)
141+
142+
result = detect_elf_architecture(elf_file)
143+
assert result is not None
144+
processor, cspec = result
145+
assert "x86" in processor
146+
assert "32" in processor
147+
148+
def test_detect_x86_64(self, temp_dir):
149+
"""Test x86-64 detection"""
150+
elf_file = os.path.join(temp_dir, "x86_64.o")
151+
with open(elf_file, "wb") as f:
152+
f.write(b"\x7fELF")
153+
f.write(b"\x02")
154+
f.write(b"\x01")
155+
f.write(b"\x01\x00\x00" + b"\x00" * 7)
156+
f.write(b"\x01\x00")
157+
f.write(b"\x3e\x00") # x86-64
158+
f.write(b"\x00" * 100)
159+
160+
result = detect_elf_architecture(elf_file)
161+
assert result is not None
162+
processor, cspec = result
163+
assert "x86" in processor
164+
assert "64" in processor
165+
166+
def test_detect_riscv32(self, temp_dir):
167+
"""Test RISC-V 32-bit detection"""
168+
elf_file = os.path.join(temp_dir, "riscv32.o")
169+
with open(elf_file, "wb") as f:
170+
f.write(b"\x7fELF")
171+
f.write(b"\x01")
172+
f.write(b"\x01")
173+
f.write(b"\x01\x00\x00" + b"\x00" * 7)
174+
f.write(b"\x01\x00")
175+
f.write(b"\xf3\x00") # RISC-V
176+
f.write(b"\x00" * 100)
177+
178+
result = detect_elf_architecture(elf_file)
179+
assert result is not None
180+
processor, cspec = result
181+
assert "RISCV" in processor
182+
assert "32" in processor
183+
184+
def test_detect_nonexistent_file(self):
185+
"""Test detection on nonexistent file"""
186+
result = detect_elf_architecture("/nonexistent/path/file.o")
187+
assert result is None
188+
189+
def test_detect_non_elf_file(self, temp_dir):
190+
"""Test detection on non-ELF file"""
191+
non_elf = os.path.join(temp_dir, "not_elf.bin")
192+
with open(non_elf, "wb") as f:
193+
f.write(b"NOT_ELF_CONTENT")
194+
result = detect_elf_architecture(non_elf)
195+
assert result is None
196+
197+
def test_elf_machine_map_exists(self):
198+
"""Test that ELF machine map has expected entries"""
199+
assert 0x03 in ELF_MACHINE_MAP # x86
200+
assert 0x3E in ELF_MACHINE_MAP # x86-64
201+
assert 0x28 in ELF_MACHINE_MAP # ARM
202+
assert 0xB7 in ELF_MACHINE_MAP # AARCH64
203+
assert 0xF3 in ELF_MACHINE_MAP # RISC-V

0 commit comments

Comments
 (0)