Back to Repositories

Testing Smali File Processing and Class Node Generation in jadx

This test suite provides comprehensive testing functionality for handling Smali files in the JADX decompiler project. It offers methods for loading and processing Smali files, converting them to ClassNode objects, and validating the decompilation process.

Test Coverage Overview

The test suite provides extensive coverage for Smali file processing and class node generation.

Key functionality includes:
  • Single and multiple Smali file loading
  • Class node generation with various package configurations
  • File path resolution and validation
  • Directory structure handling
Edge cases covered include missing files, invalid paths, and different project directory structures.

Integration points focus on the JADX decompiler core functionality and file system operations.

Implementation Analysis

The testing approach utilizes JUnit Jupiter framework with a hierarchical structure extending IntegrationTest. The implementation employs method overloading patterns for flexible Smali file processing, with specific technical handling for file paths and class names.

Framework features utilized include:
  • @BeforeEach annotations for test initialization
  • Assumptions for conditional test execution
  • AssertJ assertions for validation
  • Stream API for file collection processing

Technical Details

Testing tools and configuration:
  • JUnit Jupiter test framework
  • AssertJ assertion library
  • Custom JadxAssertions utility
  • Smali test files location: src/test/smali
  • File extension: .smali
  • Integration with JADX internal API

Best Practices Demonstrated

The test suite demonstrates high-quality testing practices through modular and reusable test methods.

Notable practices include:
  • Abstract base class design for common functionality
  • Consistent error handling and validation
  • Clear method naming conventions
  • Flexible configuration options
  • Separation of concerns in test methods
Code organization follows a hierarchical structure with clear separation between file handling and class node processing logic.

skylot/jadx

jadx-core/src/test/java/jadx/tests/api/SmaliTest.java

            
package jadx.tests.api;

import java.io.File;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import org.jetbrains.annotations.Nullable;
import org.junit.jupiter.api.Assumptions;
import org.junit.jupiter.api.BeforeEach;

import jadx.api.JadxInternalAccess;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.RootNode;

import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat;
import static org.assertj.core.api.Assertions.assertThat;

public abstract class SmaliTest extends IntegrationTest {

	private static final String SMALI_TESTS_DIR = "src/test/smali";
	private static final String SMALI_TESTS_EXT = ".smali";

	private String currentProject = "jadx-core";

	public void setCurrentProject(String currentProject) {
		this.currentProject = currentProject;
	}

	@BeforeEach
	public void init() {
		Assumptions.assumeFalse(USE_JAVA_INPUT, "skip smali test for java input tests");
		super.init();
		this.useDexInput();
	}

	protected ClassNode getClassNodeFromSmali(String file, String clsName) {
		File smaliFile = getSmaliFile(file);
		return getClassNodeFromFiles(Collections.singletonList(smaliFile), clsName);
	}

	/**
	 * Preferred method for one file smali test
	 */
	protected ClassNode getClassNodeFromSmali() {
		return getClassNodeFromSmaliWithPkg(getTestPkg(), getTestName());
	}

	protected ClassNode getClassNodeFromSmaliWithClsName(String fullClsName) {
		return getClassNodeFromSmali(getTestPkg() + File.separatorChar + getTestName(), fullClsName);
	}

	protected ClassNode getClassNodeFromSmaliWithPath(String path, String clsName) {
		return getClassNodeFromSmali(path + File.separatorChar + clsName, clsName);
	}

	protected ClassNode getClassNodeFromSmaliWithPkg(String pkg, String clsName) {
		return getClassNodeFromSmali(pkg + File.separatorChar + clsName, pkg + '.' + clsName);
	}

	protected ClassNode getClassNodeFromSmaliFiles(String pkg, String testName, String clsName) {
		return getClassNodeFromFiles(collectSmaliFiles(pkg, testName), pkg + '.' + clsName);
	}

	protected ClassNode getClassNodeFromSmaliFiles(String clsName) {
		return searchCls(loadFromSmaliFiles(), getTestPkg() + '.' + clsName);
	}

	protected ClassNode getClassNodeFromSmaliFiles() {
		return searchCls(loadFromSmaliFiles(), getTestPkg() + '.' + getTestName());
	}

	protected List<ClassNode> loadFromSmaliFiles() {
		jadxDecompiler = loadFiles(collectSmaliFiles(getTestPkg(), getTestName()));
		RootNode root = JadxInternalAccess.getRoot(jadxDecompiler);
		List<ClassNode> classes = root.getClasses(false);
		decompileAndCheck(classes);
		return classes;
	}

	private List<File> collectSmaliFiles(String pkg, @Nullable String testDir) {
		String smaliFilesDir;
		if (testDir == null) {
			smaliFilesDir = pkg + File.separatorChar;
		} else {
			smaliFilesDir = pkg + File.separatorChar + testDir + File.separatorChar;
		}
		File smaliDir = getSmaliDir(smaliFilesDir);
		String[] smaliFileNames = smaliDir.list((dir, name) -> name.endsWith(".smali"));
		assertThat(smaliFileNames).as("Smali files not found in " + smaliDir).isNotNull();
		return Stream.of(smaliFileNames)
				.map(file -> new File(smaliDir, file))
				.collect(Collectors.toList());
	}

	private File getSmaliFile(String baseName) {
		File smaliFile = new File(SMALI_TESTS_DIR, baseName + SMALI_TESTS_EXT);
		if (smaliFile.exists()) {
			return smaliFile;
		}
		File pathFromRoot = new File(currentProject, smaliFile.getPath());
		if (pathFromRoot.exists()) {
			return pathFromRoot;
		}
		throw new AssertionError("Smali file not found: " + smaliFile.getPath());
	}

	private File getSmaliDir(String baseName) {
		File smaliDir = new File(SMALI_TESTS_DIR, baseName);
		if (smaliDir.exists()) {
			return smaliDir;
		}
		File pathFromRoot = new File(currentProject, smaliDir.getPath());
		if (pathFromRoot.exists()) {
			return pathFromRoot;
		}
		throw new AssertionError("Smali dir not found: " + smaliDir.getPath());
	}
}