Back to Repositories

Validating APK Processing and XML Comparison Implementation in Apktool

The BaseTest class serves as the foundation for APKTool’s testing infrastructure, providing essential setup and comparison utilities for Android application testing. This JUnit-based test suite implements comprehensive XML and binary file comparison mechanisms while managing test environments and framework configurations.

Test Coverage Overview

The test suite provides extensive coverage for APK file processing and validation:

  • Binary folder comparison capabilities for APK contents
  • XML file validation with customizable comparison rules
  • Values file verification for Android resource files
  • Framework file management and cleanup procedures

Implementation Analysis

The testing approach utilizes JUnit 4 with custom XML comparison utilities from XMLUnit. The implementation features a robust setup/teardown lifecycle using @BeforeClass and @AfterClass annotations, ensuring proper test isolation and resource management.

Key patterns include hierarchical test organization, utility method abstraction, and flexible comparison strategies using ElementQualifier interfaces.

Technical Details

Testing tools and configuration:

  • JUnit 4 test framework
  • XMLUnit for XML comparison
  • Custom ExtFile implementation for directory handling
  • Temporary directory management via OS utility class
  • XPath evaluation capabilities for XML validation
  • Configurable XML comparison settings for whitespace and attribute ordering

Best Practices Demonstrated

The test suite exemplifies several testing best practices:

  • Proper resource cleanup and management
  • Flexible comparison mechanisms for different file types
  • Centralized configuration handling
  • Detailed error reporting with file paths
  • Security considerations with XXE protection
  • Reusable utility methods for common testing operations

ibotpeaches/apktool

brut.apktool/apktool-lib/src/test/java/brut/androlib/BaseTest.java

            
/*
 *  Copyright (C) 2010 Ryszard Wiśniewski <[email protected]>
 *  Copyright (C) 2010 Connor Tumbleson <[email protected]>
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *       https://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
package brut.androlib;

import brut.androlib.Config;
import brut.androlib.res.Framework;
import brut.common.BrutException;
import brut.directory.ExtFile;
import brut.util.OS;
import brut.xml.XmlUtils;
import org.w3c.dom.Document;
import org.xml.sax.SAXException;

import javax.xml.parsers.ParserConfigurationException;
import javax.xml.xpath.XPathExpressionException;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.Reader;
import java.util.logging.Logger;

import org.junit.*;
import static org.junit.Assert.*;

import org.custommonkey.xmlunit.*;
import static org.custommonkey.xmlunit.XMLAssert.assertXMLEqual;

public class BaseTest {
    protected static final Logger LOGGER = Logger.getLogger(BaseTest.class.getName());

    protected static Config sConfig;
    protected static File sTmpDir;
    protected static ExtFile sTestOrigDir;
    protected static ExtFile sTestNewDir;

    private static void cleanFrameworkFile() throws BrutException {
        File apkFile = new File(new Framework(sConfig).getDirectory(), "1.apk");
        if (apkFile.isFile()) {
            OS.rmfile(apkFile.getAbsolutePath());
        }
    }

    @BeforeClass
    public static void beforeEachClass() throws Exception {
        sConfig = new Config();
        cleanFrameworkFile();

        sTmpDir = OS.createTempDirectory();
    }

    @AfterClass
    public static void afterEachClass() throws Exception {
        if (sTestOrigDir != null) {
            sTestOrigDir.close();
            sTestOrigDir = null;
        }

        if (sTestNewDir != null) {
            sTestNewDir.close();
            sTestNewDir = null;
        }

        OS.rmdir(sTmpDir);
        sTmpDir = null;

        cleanFrameworkFile();
        sConfig = null;
    }

    @Before
    public void beforeEachTest() throws Exception {
        sConfig = new Config();
    }

    protected void compareBinaryFolder(String path) throws BrutException {
        compareBinaryFolder(sTestOrigDir, sTestNewDir, path);
    }

    protected void compareBinaryFolder(File controlDir, File testDir, String path) throws BrutException {
        ExtFile controlBase = new ExtFile(controlDir, path);
        File testBase = new File(testDir, path);

        boolean exists = true;

        for (String fileName : controlBase.getDirectory().getFiles(true)) {
            File control = new File(controlBase, fileName);
            File test = new File(testBase, fileName);

            if (!control.isFile() || !test.isFile()) {
                exists = false;
            }
        }

        assertTrue(exists);
    }

    protected void compareValuesFiles(String path) throws BrutException {
        compareValuesFiles(sTestOrigDir, sTestNewDir, path);
    }

    protected void compareValuesFiles(File controlDir, File testDir, String path) throws BrutException {
        compareXmlFiles(controlDir, testDir, "res/" + path, new ElementNameAndAttributeQualifier("name"));
    }

    protected void compareXmlFiles(String path) throws BrutException {
        compareXmlFiles(sTestOrigDir, sTestNewDir, path);
    }

    protected void compareXmlFiles(File controlDir, File testDir, String path) throws BrutException {
        compareXmlFiles(controlDir, testDir, path, null);
    }

    private void compareXmlFiles(File controlDir, File testDir, String path, ElementQualifier qualifier)
            throws BrutException {
        try {
            Reader control = new FileReader(new File(controlDir, path));
            Reader test = new FileReader(new File(testDir, path));

            XMLUnit.setEnableXXEProtection(true);

            if (qualifier == null) {
                XMLUnit.setIgnoreWhitespace(true);
                XMLUnit.setIgnoreAttributeOrder(true);
                XMLUnit.setCompareUnmatched(false);

                assertXMLEqual(control, test);
                return;
            }

            DetailedDiff diff = new DetailedDiff(new Diff(control, test));
            diff.overrideElementQualifier(qualifier);

            assertTrue(path + ": " + diff.getAllDifferences().toString(), diff.similar());
        } catch (IOException | SAXException ex) {
            throw new BrutException(ex);
        }
    }

    protected static Document loadDocument(File file) throws BrutException {
        try {
            return XmlUtils.loadDocument(file);
        } catch (IOException | SAXException | ParserConfigurationException ex) {
            throw new BrutException(ex);
        }
    }

    protected static <T> T evaluateXPath(Document doc, String expression, Class<T> returnType)
            throws BrutException {
        try {
            return XmlUtils.evaluateXPath(doc, expression, returnType);
        } catch (XPathExpressionException ex) {
            throw new BrutException(ex);
        }
    }
}