Back to Repositories

Testing Custom Java Class Loading Implementation in JADX Decompiler

This test suite validates the custom class loading functionality in the JADX decompiler, focusing on different methods of loading Java class files. It tests various input mechanisms including direct file loading, input streams, and custom loading scenarios for both regular and inner classes.

Test Coverage Overview

The test suite provides comprehensive coverage of JADX’s class loading capabilities:

  • Multiple file loading scenarios including regular and inner classes
  • Stream-based class loading implementations
  • Single class file loading functionality
  • Custom loader implementation testing
  • Inner class relationship verification

Implementation Analysis

The testing approach employs JUnit 5 framework with a systematic setup/teardown pattern. Each test method focuses on a specific loading scenario using the JavaInputPlugin class.

  • BeforeEach/AfterEach hooks for JadxDecompiler initialization
  • AssertJ fluent assertions for verification
  • Stream API usage for file handling
  • Exception handling validation

Technical Details

  • JUnit Jupiter test framework
  • AssertJ assertion library
  • JADX API integration (JadxDecompiler, JadxArgs)
  • Java NIO for file operations
  • Custom ICodeLoader implementation
  • Sample class files in resources/samples/

Best Practices Demonstrated

The test suite exemplifies several testing best practices and patterns:

  • Proper resource management with try-with-resources
  • Isolated test cases with clear objectives
  • Comprehensive assertion chains
  • Proper setup and cleanup procedures
  • Clear test method naming conventions
  • Effective error handling and reporting

skylot/jadx

jadx-plugins/jadx-java-input/src/test/java/jadx/plugins/input/java/CustomLoadTest.java

            
package jadx.plugins.input.java;

import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import jadx.api.JadxArgs;
import jadx.api.JadxDecompiler;
import jadx.api.plugins.input.ICodeLoader;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.fail;

class CustomLoadTest {

	private JadxDecompiler jadx;

	@BeforeEach
	void init() {
		jadx = new JadxDecompiler(new JadxArgs());
	}

	@AfterEach
	void close() {
		jadx.close();
	}

	@Test
	void loadFiles() {
		List<Path> files = Stream.of("HelloWorld.class", "HelloWorld$HelloInner.class")
				.map(this::getSample)
				.collect(Collectors.toList());
		ICodeLoader loadResult = JavaInputPlugin.loadClassFiles(files);
		loadDecompiler(loadResult);
		assertThat(jadx.getClassesWithInners())
				.hasSize(2)
				.satisfiesOnlyOnce(cls -> assertThat(cls.getName()).isEqualTo("HelloWorld"))
				.satisfiesOnlyOnce(cls -> assertThat(cls.getName()).isEqualTo("HelloInner"));
	}

	@Test
	void loadFromInputStream() throws IOException {
		String fileName = "HelloWorld$HelloInner.class";
		try (InputStream in = Files.newInputStream(getSample(fileName))) {
			ICodeLoader loadResult = JavaInputPlugin.loadFromInputStream(in, fileName);
			loadDecompiler(loadResult);
			assertThat(jadx.getClassesWithInners())
					.hasSize(1)
					.satisfiesOnlyOnce(cls -> assertThat(cls.getName()).isEqualTo("HelloWorld$HelloInner"));

			System.out.println(jadx.getClassesWithInners().get(0).getCode());
		}
	}

	@Test
	void loadSingleClass() throws IOException {
		String fileName = "HelloWorld.class";
		byte[] content = Files.readAllBytes(getSample(fileName));
		ICodeLoader loadResult = JavaInputPlugin.loadSingleClass(content, fileName);
		loadDecompiler(loadResult);
		assertThat(jadx.getClassesWithInners())
				.hasSize(1)
				.satisfiesOnlyOnce(cls -> assertThat(cls.getName()).isEqualTo("HelloWorld"));

		System.out.println(jadx.getClassesWithInners().get(0).getCode());
	}

	@Test
	void load() {
		ICodeLoader loadResult = JavaInputPlugin.load(loader -> {
			List<JavaClassReader> inputs = new ArrayList<>(2);
			try {
				String hello = "HelloWorld.class";
				byte[] content = Files.readAllBytes(getSample(hello));
				inputs.add(loader.loadClass(content, hello));

				String helloInner = "HelloWorld$HelloInner.class";
				InputStream in = Files.newInputStream(getSample(helloInner));
				inputs.addAll(loader.loadInputStream(in, helloInner));
			} catch (Exception e) {
				fail(e);
			}
			return inputs;
		});
		loadDecompiler(loadResult);
		assertThat(jadx.getClassesWithInners())
				.hasSize(2)
				.satisfiesOnlyOnce(cls -> assertThat(cls.getName()).isEqualTo("HelloWorld"))
				.satisfiesOnlyOnce(cls -> {
					assertThat(cls.getName()).isEqualTo("HelloInner");
					assertThat(cls.getCode()).isEmpty(); // no code for moved inner class
				});

		assertThat(jadx.getClasses())
				.hasSize(1)
				.satisfiesOnlyOnce(cls -> assertThat(cls.getName()).isEqualTo("HelloWorld"))
				.satisfiesOnlyOnce(cls -> assertThat(cls.getInnerClasses()).hasSize(1)
						.satisfiesOnlyOnce(inner -> assertThat(inner.getName()).isEqualTo("HelloInner")));

		jadx.getClassesWithInners().forEach(cls -> System.out.println(cls.getCode()));
	}

	public void loadDecompiler(ICodeLoader codeLoader) {
		try {
			jadx.addCustomCodeLoader(codeLoader);
			jadx.load();
		} catch (Exception e) {
			fail("Failed to load sample", e);
		}
	}

	public Path getSample(String name) {
		try {
			return Paths.get(ClassLoader.getSystemResource("samples/" + name).toURI());
		} catch (Exception e) {
			return fail("Failed to load sample", e);
		}
	}
}