diff --git a/apktool-lib/pom.xml b/apktool-lib/pom.xml
index 6defecd9..53cf5d65 100644
--- a/apktool-lib/pom.xml
+++ b/apktool-lib/pom.xml
@@ -34,6 +34,7 @@
src/main/java/com/mindprod/**
src/main/java/android/**
+ src/test/resources/brut/apktool/testapp/**
@@ -81,5 +82,15 @@
xpp3
1.1.4c
+
+ junit
+ junit
+ 4.8.2
+
+
+ xmlunit
+ xmlunit
+ 1.3
+
diff --git a/apktool-lib/src/test/java/brut/androlib/BuildAndDecodeTest.java b/apktool-lib/src/test/java/brut/androlib/BuildAndDecodeTest.java
new file mode 100644
index 00000000..995d7622
--- /dev/null
+++ b/apktool-lib/src/test/java/brut/androlib/BuildAndDecodeTest.java
@@ -0,0 +1,113 @@
+/**
+ * Copyright 2011 Ryszard Wiśniewski
+ *
+ * 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
+ *
+ * http://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.res.util.ExtFile;
+import brut.common.BrutException;
+import brut.util.OS;
+import java.io.*;
+import java.util.logging.Logger;
+import org.custommonkey.xmlunit.*;
+import org.junit.*;
+import static org.junit.Assert.*;
+import org.xml.sax.SAXException;
+
+
+/**
+ * @author Ryszard Wiśniewski
+ */
+public class BuildAndDecodeTest {
+
+ @BeforeClass
+ public static void beforeClass() throws BrutException {
+ sTmpDir = new ExtFile(OS.createTempDirectory());
+ sTestOrigDir = new ExtFile(sTmpDir, "testapp-orig");
+ sTestNewDir = new ExtFile(sTmpDir, "testapp-new");
+ File testApk = new File(sTmpDir, "testapp.apk");
+
+ LOGGER.info("Unpacking testapp...");
+ TestUtils.copyResourceDir(BuildAndDecodeTest.class,
+ "brut/apktool/testapp/", sTestOrigDir);
+
+ LOGGER.info("Building testapp.apk...");
+ new Androlib().build(sTestOrigDir, testApk, false, false);
+
+ LOGGER.info("Decoding testapp.apk...");
+ ApkDecoder apkDecoder = new ApkDecoder(testApk);
+ apkDecoder.setOutDir(sTestNewDir);
+ apkDecoder.decode();
+ }
+
+ @AfterClass
+ public static void afterClass() throws BrutException {
+ OS.rmdir(sTmpDir);
+ }
+
+ @Test
+ public void literalStringsTest() throws BrutException {
+ compareValuesFiles("values-mcc001/strings.xml");
+ }
+
+ @Test
+ public void referenceStringsTest() throws BrutException {
+ compareValuesFiles("values-mcc002/strings.xml");
+ }
+
+ @Test
+ public void layout1Test() throws BrutException {
+ compareXmlFiles("res/layout/layout1.xml");
+ }
+
+ private void compareValuesFiles(String path) throws BrutException {
+ compareXmlFiles("res/" + path,
+ new ElementNameAndAttributeQualifier("name"));
+ }
+
+ private void compareXmlFiles(String path) throws BrutException {
+ compareXmlFiles(path, null);
+ }
+
+ private void compareXmlFiles(String path,
+ ElementQualifier qualifier) throws BrutException {
+ DetailedDiff diff;
+ try {
+ Reader control = new FileReader(
+ new File(sTestOrigDir, path));
+ Reader test = new FileReader(new File(sTestNewDir, path));
+
+ diff = new DetailedDiff(new Diff(control, test));
+ } catch (SAXException ex) {
+ throw new BrutException(ex);
+ } catch (IOException ex) {
+ throw new BrutException(ex);
+ }
+
+ if (qualifier != null) {
+ diff.overrideElementQualifier(qualifier);
+ }
+
+ assertTrue(path + ": " +
+ diff.getAllDifferences().toString(), diff.similar());
+ }
+
+ private static ExtFile sTmpDir;
+ private static ExtFile sTestOrigDir;
+ private static ExtFile sTestNewDir;
+
+ private final static Logger LOGGER =
+ Logger.getLogger(BuildAndDecodeTest.class.getName());
+}
diff --git a/apktool-lib/src/test/java/brut/androlib/TestUtils.java b/apktool-lib/src/test/java/brut/androlib/TestUtils.java
new file mode 100644
index 00000000..53d59c87
--- /dev/null
+++ b/apktool-lib/src/test/java/brut/androlib/TestUtils.java
@@ -0,0 +1,138 @@
+/**
+ * Copyright 2011 Ryszard Wiśniewski
+ *
+ * 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
+ *
+ * http://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.common.BrutException;
+import brut.directory.*;
+import java.io.*;
+import java.net.URL;
+import java.net.URLDecoder;
+import java.util.HashMap;
+import java.util.Map;
+import org.custommonkey.xmlunit.ElementQualifier;
+import org.w3c.dom.Element;
+import org.xmlpull.v1.*;
+
+/**
+ * @author Ryszard Wiśniewski
+ */
+public abstract class TestUtils {
+
+ public static Map parseStringsXml(File file)
+ throws BrutException {
+ try {
+ XmlPullParser xpp = XmlPullParserFactory.newInstance().newPullParser();
+ xpp.setInput(new FileReader(file));
+
+ int eventType;
+ String key = null;
+ Map map = new HashMap();
+ while ((eventType = xpp.next()) != XmlPullParser.END_DOCUMENT) {
+ switch (eventType) {
+ case XmlPullParser.START_TAG:
+ if ("string".equals(xpp.getName())) {
+ int attrCount = xpp.getAttributeCount();
+ for (int i = 0; i < attrCount; i++) {
+ if ("name".equals(xpp.getAttributeName(i))) {
+ key = xpp.getAttributeValue(i);
+ break;
+ }
+ }
+ }
+ break;
+ case XmlPullParser.END_TAG:
+ if ("string".equals(xpp.getName())) {
+ key = null;
+ }
+ break;
+ case XmlPullParser.TEXT:
+ if (key != null) {
+ map.put(key, xpp.getText());
+ }
+ break;
+ }
+ }
+
+ return map;
+ } catch (IOException ex) {
+ throw new BrutException(ex);
+ } catch (XmlPullParserException ex) {
+ throw new BrutException(ex);
+ }
+ }
+
+ /* TODO: move to brut.util.Jar - it's not possible for now, because below
+ * implementation uses brut.dir. I think I should merge all my projects to
+ * single brut.common .
+ */
+ public static void copyResourceDir(Class class_, String dirPath, File out)
+ throws BrutException {
+ if (! out.exists()) {
+ out.mkdirs();
+ }
+ copyResourceDir(class_, dirPath, new FileDirectory(out));
+ }
+
+ public static void copyResourceDir(Class class_, String dirPath,
+ Directory out) throws BrutException {
+ if (class_ == null) {
+ class_ = Class.class;
+ }
+
+ URL dirURL = class_.getClassLoader().getResource(dirPath);
+ if (dirURL != null && dirURL.getProtocol().equals("file")) {
+ DirUtil.copyToDir(new FileDirectory(dirURL.getFile()), out);
+ return;
+ }
+
+ if (dirURL == null) {
+ String className = class_.getName().replace(".", "/") + ".class";
+ dirURL = class_.getClassLoader().getResource(className);
+ }
+
+
+ if (dirURL.getProtocol().equals("jar")) {
+ String jarPath;
+ try {
+ jarPath = URLDecoder.decode(dirURL.getPath().substring(
+ 5, dirURL.getPath().indexOf("!")), "UTF-8");
+ } catch (UnsupportedEncodingException ex) {
+ throw new BrutException(ex);
+ }
+ DirUtil.copyToDir(new FileDirectory(jarPath), out);
+ }
+ }
+
+
+ public static class ResValueElementQualifier implements ElementQualifier {
+
+ public boolean qualifyForComparison(Element control, Element test) {
+ String controlType = control.getTagName();
+ if ("item".equals(controlType)) {
+ controlType = control.getAttribute("type");
+ }
+
+ String testType = test.getTagName();
+ if ("item".equals(testType)) {
+ testType = test.getAttribute("type");
+ }
+
+ return controlType.equals(testType) && control.getAttribute("name")
+ .equals(test.getAttribute("name"));
+ }
+ }
+}
diff --git a/apktool-lib/src/test/resources/brut/apktool/testapp/AndroidManifest.xml b/apktool-lib/src/test/resources/brut/apktool/testapp/AndroidManifest.xml
new file mode 100644
index 00000000..c6a6a653
--- /dev/null
+++ b/apktool-lib/src/test/resources/brut/apktool/testapp/AndroidManifest.xml
@@ -0,0 +1,3 @@
+
+
diff --git a/apktool-lib/src/test/resources/brut/apktool/testapp/apktool.yml b/apktool-lib/src/test/resources/brut/apktool/testapp/apktool.yml
new file mode 100644
index 00000000..555ac244
--- /dev/null
+++ b/apktool-lib/src/test/resources/brut/apktool/testapp/apktool.yml
@@ -0,0 +1,6 @@
+version: 1.3.2
+apkFileName: testapp.apk
+isFrameworkApk: false
+usesFramework:
+ ids:
+ - 1
diff --git a/apktool-lib/src/test/resources/brut/apktool/testapp/res/layout/layout1.xml b/apktool-lib/src/test/resources/brut/apktool/testapp/res/layout/layout1.xml
new file mode 100644
index 00000000..6685f20d
--- /dev/null
+++ b/apktool-lib/src/test/resources/brut/apktool/testapp/res/layout/layout1.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
diff --git a/apktool-lib/src/test/resources/brut/apktool/testapp/res/values-mcc001/strings.xml b/apktool-lib/src/test/resources/brut/apktool/testapp/res/values-mcc001/strings.xml
new file mode 100644
index 00000000..5e9db678
--- /dev/null
+++ b/apktool-lib/src/test/resources/brut/apktool/testapp/res/values-mcc001/strings.xml
@@ -0,0 +1,8 @@
+
+
+
+ Lorem ipsum...
+ \@
+ \?
+ &
+
diff --git a/apktool-lib/src/test/resources/brut/apktool/testapp/res/values-mcc002/strings.xml b/apktool-lib/src/test/resources/brut/apktool/testapp/res/values-mcc002/strings.xml
new file mode 100644
index 00000000..e0ebc5d9
--- /dev/null
+++ b/apktool-lib/src/test/resources/brut/apktool/testapp/res/values-mcc002/strings.xml
@@ -0,0 +1,4 @@
+
+
+ @android:string/ok
+
diff --git a/apktool-lib/src/test/resources/brut/apktool/testapp/res/values/strings.xml b/apktool-lib/src/test/resources/brut/apktool/testapp/res/values/strings.xml
new file mode 100644
index 00000000..a8cb8262
--- /dev/null
+++ b/apktool-lib/src/test/resources/brut/apktool/testapp/res/values/strings.xml
@@ -0,0 +1,4 @@
+
+
+
+