package patdroid.regtest; import com.google.common.collect.ImmutableList; import com.google.common.collect.Ordering; import com.google.common.io.Files; import com.google.common.io.PatternFilenameFilter; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; import patdroid.core.ClassInfo; import patdroid.core.MethodInfo; import patdroid.core.Scope; import patdroid.dalvik.Instruction; import patdroid.smali.SmaliClassDetailLoader; import java.io.*; import java.util.*; import java.util.logging.Logger; import java.util.zip.ZipFile; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; /** * Regression test: reads APK and compare with dump files. * * <p>Specify -Dregtest.apkpath=<apk path> to run regression test. * <p>Also specify -Dregtest.updatedump=true to update dump files. */ @RunWith(Parameterized.class) public class RegTest { private static final File FRAMEWORK_CLASSES_FOLDER = new File("apilevels"); private static final int API_LEVEL = 19; private static final Logger logger = Logger.getLogger(Logger.GLOBAL_LOGGER_NAME); private final Scope scope = new Scope(); private final File apkFile; private final File dumpFile; private final boolean updateDump; private BufferedReader dumpReader; private BufferedWriter dumpWriter; private int lineNumber; public RegTest(File apkFile, File dumpFile, boolean updateDump) { this.apkFile = apkFile; this.dumpFile = dumpFile; this.updateDump = updateDump; } @Before public void setUp() throws IOException { if (updateDump) { this.dumpWriter = new BufferedWriter(new FileWriter(dumpFile)); } else { this.dumpReader = new BufferedReader(new FileReader(dumpFile)); } this.lineNumber = 0; } @After public void tearDown() throws IOException { if (updateDump) { this.dumpWriter.close(); } } @Test public void run() throws IOException { if (updateDump) { logger.info("Updating dump for " + apkFile); } else { logger.info("Running regression test for " + apkFile); } SmaliClassDetailLoader.fromFramework(FRAMEWORK_CLASSES_FOLDER, API_LEVEL).loadAll(scope); SmaliClassDetailLoader.fromApkFile(new ZipFile(apkFile), API_LEVEL, true).loadAll(scope); List<ClassInfo> sortedClasses = Ordering.usingToString().sortedCopy(scope.getAllClasses()); for (ClassInfo c : sortedClasses) { if (c.isFrameworkClass()) { continue; } handleEntry(c.toString()); if (c.isMissing()) { handleEntry("\t(missing class)"); continue; } List<MethodInfo> sortedMethods = Ordering.usingToString().sortedCopy(c.getAllMethods()); for (MethodInfo m : sortedMethods) { handleEntry("\t" + m); if (m.insns == null) { handleEntry("\t\t(no instructions)"); } else { for (Instruction i : m.insns) { handleEntry("\t\t" + i); } } } } } private void handleEntry(String entry) throws IOException { BufferedReader entryReader = new BufferedReader(new StringReader(entry)); String line; while ((line = entryReader.readLine()) != null) { ++lineNumber; if (updateDump) { this.dumpWriter.write(line); this.dumpWriter.newLine(); } else { assertEquals("line " + lineNumber, this.dumpReader.readLine(), line); } } } @Parameterized.Parameters public static List<Object[]> params() { String apkPath = System.getProperty("regtest.apkpath", ""); if (apkPath.isEmpty()) { logger.warning("regtest.apkpath not set, skipping regression test."); return ImmutableList.of(); } String updateDumpProperty = System.getProperty("regtest.updatedump", ""); boolean updateDump = updateDumpProperty.equalsIgnoreCase("true"); File apkDir = new File(apkPath); File[] apkFiles = apkDir.listFiles(new PatternFilenameFilter("^.*.apk")); assertNotNull("failed to list files", apkFiles); ImmutableList.Builder<Object[]> params = ImmutableList.builder(); for (File apkFile : apkFiles) { String dumpFileName = Files.getNameWithoutExtension(apkFile.getName()) + ".txt"; File dumpFile = new File(apkFile.getParentFile(), dumpFileName); params.add(new Object[]{apkFile, dumpFile, updateDump}); } return params.build(); } }