Back to Repositories

Testing Android Resource XML Generation in JADX Decompiler

This test suite validates the XML resource generation functionality in JADX, focusing on handling various Android resource types including attributes, styles, strings and arrays. The tests ensure proper XML formatting and resource value processing.

Test Coverage Overview

The test suite provides comprehensive coverage of XML resource generation scenarios.

Key areas tested include:
  • Simple attribute generation
  • Enum and flag attribute handling
  • Style and theme inheritance
  • String resource processing with formatting
  • Array resource generation with escape sequences
Edge cases covered include minimum value attributes, string formatting flags, and special character escaping.

Implementation Analysis

The testing approach uses JUnit 5 with a setup-execute-verify pattern. Each test initializes a ResourceStorage instance, populates test data, and verifies the generated XML output.

Technical implementation features:
  • BeforeEach setup for consistent testing environment
  • AssertJ assertions for readable verifications
  • Structured test data preparation using ResourceEntry objects
  • XML output validation against expected formatted strings

Technical Details

Testing infrastructure includes:
  • JUnit Jupiter framework
  • AssertJ assertion library
  • Custom test utilities: ResourceStorage, ValuesParser
  • XML generation components: ResXmlGen, ManifestAttributes
  • Security implementation through JadxSecurity

Best Practices Demonstrated

The test suite exemplifies high-quality testing practices through clear organization and thorough validation.

Notable practices include:
  • Isolated test cases for each resource type
  • Consistent test structure and naming
  • Comprehensive assertion checks
  • Proper resource cleanup
  • Clear separation of setup and verification logic

skylot/jadx

jadx-core/src/test/java/jadx/core/xmlgen/ResXmlGenTest.java

            
package jadx.core.xmlgen;

import java.util.ArrayList;
import java.util.List;

import org.assertj.core.util.Lists;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import jadx.api.JadxArgs;
import jadx.api.security.IJadxSecurity;
import jadx.api.security.JadxSecurityFlag;
import jadx.api.security.impl.JadxSecurity;
import jadx.core.xmlgen.entry.RawNamedValue;
import jadx.core.xmlgen.entry.RawValue;
import jadx.core.xmlgen.entry.ResourceEntry;
import jadx.core.xmlgen.entry.ValuesParser;

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

class ResXmlGenTest {
	private final JadxArgs args = new JadxArgs();
	private final IJadxSecurity security = new JadxSecurity(JadxSecurityFlag.all());
	private final ManifestAttributes manifestAttributes = new ManifestAttributes(security);

	@BeforeEach
	void init() {
		args.setCodeNewLineStr("\n");
	}

	@Test
	void testSimpleAttr() {
		ResourceStorage resStorage = new ResourceStorage(security);
		ResourceEntry re = new ResourceEntry(2130903103, "jadx.gui.app", "attr", "size", "");
		re.setNamedValues(Lists.list(new RawNamedValue(16777216, new RawValue(16, 64))));
		resStorage.add(re);

		ValuesParser vp = new ValuesParser(null, resStorage.getResourcesNames());
		ResXmlGen resXmlGen = new ResXmlGen(resStorage, vp, manifestAttributes);
		List<ResContainer> files = resXmlGen.makeResourcesXml(args);

		assertThat(files).hasSize(1);
		assertThat(files.get(0).getName()).isEqualTo("res/values/attrs.xml");
		String input = files.get(0).getText().toString();
		assertThat(input).isEqualTo("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
				+ "<resources>\n"
				+ "    <attr name=\"size\" format=\"dimension\">\n"
				+ "    </attr>\n"
				+ "</resources>");
	}

	@Test
	void testAttrEnum() {
		ResourceStorage resStorage = new ResourceStorage(security);
		ResourceEntry re = new ResourceEntry(2130903103, "jadx.gui.app", "attr", "size", "");
		re.setNamedValues(
				Lists.list(
						new RawNamedValue(0x01000000, new RawValue(16, 65536)),
						new RawNamedValue(0x01040000, new RawValue(16, 1))));
		resStorage.add(re);

		ValuesParser vp = new ValuesParser(null, resStorage.getResourcesNames());
		ResXmlGen resXmlGen = new ResXmlGen(resStorage, vp, manifestAttributes);
		List<ResContainer> files = resXmlGen.makeResourcesXml(args);

		assertThat(files).hasSize(1);
		assertThat(files.get(0).getName()).isEqualTo("res/values/attrs.xml");
		String input = files.get(0).getText().toString();
		assertThat(input).isEqualTo("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
				+ "<resources>\n"
				+ "    <attr name=\"size\">\n"
				+ "        <enum name=\"android:string.cancel\" value=\"1\" />\n"
				+ "    </attr>\n"
				+ "</resources>");
	}

	@Test
	void testAttrFlag() {
		ResourceStorage resStorage = new ResourceStorage(security);
		ResourceEntry re = new ResourceEntry(2130903103, "jadx.gui.app", "attr", "size", "");
		re.setNamedValues(
				Lists.list(
						new RawNamedValue(0x01000000, new RawValue(16, 131072)),
						new RawNamedValue(0x01040000, new RawValue(16, 1))));
		resStorage.add(re);

		ValuesParser vp = new ValuesParser(null, resStorage.getResourcesNames());
		ResXmlGen resXmlGen = new ResXmlGen(resStorage, vp, manifestAttributes);
		List<ResContainer> files = resXmlGen.makeResourcesXml(args);

		assertThat(files).hasSize(1);
		assertThat(files.get(0).getName()).isEqualTo("res/values/attrs.xml");
		String input = files.get(0).getText().toString();
		assertThat(input).isEqualTo("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
				+ "<resources>\n"
				+ "    <attr name=\"size\">\n"
				+ "        <flag name=\"android:string.cancel\" value=\"1\" />\n"
				+ "    </attr>\n"
				+ "</resources>");
	}

	@Test
	void testAttrMin() {
		ResourceStorage resStorage = new ResourceStorage(security);
		ResourceEntry re = new ResourceEntry(2130903103, "jadx.gui.app", "attr", "size", "");
		re.setNamedValues(
				Lists.list(new RawNamedValue(16777216, new RawValue(16, 4)), new RawNamedValue(16777217, new RawValue(16, 1))));
		resStorage.add(re);

		ValuesParser vp = new ValuesParser(null, resStorage.getResourcesNames());
		ResXmlGen resXmlGen = new ResXmlGen(resStorage, vp, manifestAttributes);
		List<ResContainer> files = resXmlGen.makeResourcesXml(args);

		assertThat(files).hasSize(1);
		assertThat(files.get(0).getName()).isEqualTo("res/values/attrs.xml");
		String input = files.get(0).getText().toString();
		assertThat(input).isEqualTo("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
				+ "<resources>\n"
				+ "    <attr name=\"size\" format=\"integer\" min=\"1\">\n"
				+ "    </attr>\n"
				+ "</resources>");
	}

	@Test
	void testStyle() {
		ResourceStorage resStorage = new ResourceStorage(security);
		ResourceEntry re = new ResourceEntry(2130903103, "jadx.gui.app", "style", "JadxGui", "");
		re.setNamedValues(Lists.list(new RawNamedValue(16842836, new RawValue(1, 17170445))));
		resStorage.add(re);

		re = new ResourceEntry(2130903104, "jadx.gui.app", "style", "JadxGui.Dialog", "");
		re.setParentRef(2130903103);
		re.setNamedValues(new ArrayList<>());
		resStorage.add(re);
		ValuesParser vp = new ValuesParser(null, resStorage.getResourcesNames());
		ResXmlGen resXmlGen = new ResXmlGen(resStorage, vp, manifestAttributes);
		List<ResContainer> files = resXmlGen.makeResourcesXml(args);

		assertThat(files).hasSize(1);
		assertThat(files.get(0).getName()).isEqualTo("res/values/styles.xml");
		String input = files.get(0).getText().toString();
		assertThat(input).isEqualTo("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
				+ "<resources>\n"
				+ "    <style name=\"JadxGui\" parent=\"\">\n"
				+ "        <item name=\"android:windowBackground\">@android:color/transparent</item>\n"
				+ "    </style>\n"
				+ "    <style name=\"JadxGui.Dialog\" parent=\"@style/JadxGui\">\n"
				+ "    </style>\n"
				+ "</resources>");
	}

	@Test
	void testString() {
		ResourceStorage resStorage = new ResourceStorage(security);
		ResourceEntry re = new ResourceEntry(2130903103, "jadx.gui.app", "string", "app_name", "");
		re.setSimpleValue(new RawValue(3, 0));
		re.setNamedValues(Lists.list());
		resStorage.add(re);

		BinaryXMLStrings strings = new BinaryXMLStrings();
		strings.put(0, "Jadx Decompiler App");
		ValuesParser vp = new ValuesParser(strings, resStorage.getResourcesNames());
		ResXmlGen resXmlGen = new ResXmlGen(resStorage, vp, manifestAttributes);
		List<ResContainer> files = resXmlGen.makeResourcesXml(args);

		assertThat(files).hasSize(1);
		assertThat(files.get(0).getName()).isEqualTo("res/values/strings.xml");
		String input = files.get(0).getText().toString();
		assertThat(input).isEqualTo("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
				+ "<resources>\n"
				+ "    <string name=\"app_name\">Jadx Decompiler App</string>\n"
				+ "</resources>");
	}

	@Test
	void testStringFormattedFalse() {
		ResourceStorage resStorage = new ResourceStorage(security);
		ResourceEntry re = new ResourceEntry(2130903103, "jadx.gui.app", "string", "app_name", "");
		re.setSimpleValue(new RawValue(3, 0));
		re.setNamedValues(Lists.list());
		resStorage.add(re);

		BinaryXMLStrings strings = new BinaryXMLStrings();
		strings.put(0, "%s at %s");
		ValuesParser vp = new ValuesParser(strings, resStorage.getResourcesNames());
		ResXmlGen resXmlGen = new ResXmlGen(resStorage, vp, manifestAttributes);
		List<ResContainer> files = resXmlGen.makeResourcesXml(args);

		assertThat(files).hasSize(1);
		assertThat(files.get(0).getName()).isEqualTo("res/values/strings.xml");
		String input = files.get(0).getText().toString();
		assertThat(input).isEqualTo("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
				+ "<resources>\n"
				+ "    <string name=\"app_name\" formatted=\"false\">%s at %s</string>\n"
				+ "</resources>");
	}

	@Test
	void testArrayEscape() {
		ResourceStorage resStorage = new ResourceStorage(security);
		ResourceEntry re = new ResourceEntry(2130903103, "jadx.gui.app", "array", "single_quote_escape_sample", "");
		re.setNamedValues(
				Lists.list(new RawNamedValue(16777216, new RawValue(3, 0))));
		resStorage.add(re);

		BinaryXMLStrings strings = new BinaryXMLStrings();
		strings.put(0, "Let's go");
		ValuesParser vp = new ValuesParser(strings, resStorage.getResourcesNames());
		ResXmlGen resXmlGen = new ResXmlGen(resStorage, vp, manifestAttributes);
		List<ResContainer> files = resXmlGen.makeResourcesXml(args);

		assertThat(files).hasSize(1);
		assertThat(files.get(0).getName()).isEqualTo("res/values/arrays.xml");
		String input = files.get(0).getText().toString();
		assertThat(input).isEqualTo("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
				+ "<resources>\n"
				+ "    <array name=\"single_quote_escape_sample\">\n"
				+ "        <item>Let\\'s go</item>\n"
				+ "    </array>\n"
				+ "</resources>");
	}
}