blob: 291ff134ab30312525ddf682ff3866e007ec05a2 [file] [log] [blame]
/*
* Copyright (C) 2015 The Android Open Source Project
*
* 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 com.android.build.gradle.integration.common.truth;
import com.android.annotations.NonNull;
import com.android.annotations.VisibleForTesting;
import com.android.build.gradle.integration.common.utils.ApkHelper;
import com.android.build.gradle.integration.common.utils.SdkHelper;
import com.android.builder.core.ApkInfoParser;
import com.android.ide.common.process.DefaultProcessExecutor;
import com.android.ide.common.process.ProcessException;
import com.android.ide.common.process.ProcessExecutor;
import com.android.ide.common.process.ProcessInfoBuilder;
import com.android.utils.StdLogger;
import com.google.common.truth.FailureStrategy;
import com.google.common.truth.IterableSubject;
import com.google.common.truth.SubjectFactory;
import com.google.common.truth.Truth;
import org.junit.Assert;
import java.io.File;
import java.io.IOException;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Truth support for apk files.
*/
public class ApkSubject extends AbstractAndroidSubject<ApkSubject> {
private static final Pattern PATTERN_CLASS_DESC = Pattern.compile(
"^Class descriptor\\W*:\\W*'(L.+;)'$");
private static final Pattern PATTERN_MAX_SDK_VERSION = Pattern.compile(
"^maxSdkVersion\\W*:\\W*'(.+)'$");
static class Factory extends SubjectFactory<ApkSubject, File> {
@NonNull
public static Factory get() {
return new Factory();
}
private Factory() {}
@Override
public ApkSubject getSubject(
@NonNull FailureStrategy failureStrategy,
@NonNull File subject) {
return new ApkSubject(failureStrategy, subject);
}
}
public ApkSubject(
@NonNull FailureStrategy failureStrategy,
@NonNull File subject) {
super(failureStrategy, subject);
}
@NonNull
public IterableSubject<? extends IterableSubject<?, String, List<String>>, String, List<String>> locales() throws ProcessException {
File apk = getSubject();
List<String> locales = ApkHelper.getLocales(apk);
if (locales == null) {
Assert.fail(String.format("locales not found in badging output for %s", apk));
}
return Truth.assertThat(locales);
}
@SuppressWarnings("NonBooleanMethodNameMayNotStartWithQuestion")
public void hasVersionCode(int versionCode) throws ProcessException {
File apk = getSubject();
ApkInfoParser.ApkInfo apkInfo = getApkInfo(apk);
Integer actualVersionCode = apkInfo.getVersionCode();
if (actualVersionCode == null) {
failWithRawMessage("Unable to query %s for versionCode", getDisplaySubject());
}
if (!apkInfo.getVersionCode().equals(versionCode)) {
failWithBadResults("has versionCode", versionCode, "is", actualVersionCode);
}
}
@SuppressWarnings("NonBooleanMethodNameMayNotStartWithQuestion")
public void hasVersionName(@NonNull String versionName) throws ProcessException {
File apk = getSubject();
ApkInfoParser.ApkInfo apkInfo = getApkInfo(apk);
String actualVersionName = apkInfo.getVersionName();
if (actualVersionName == null) {
failWithRawMessage("Unable to query %s for versionName", getDisplaySubject());
}
if (!apkInfo.getVersionName().equals(versionName)) {
failWithBadResults("has versionName", versionName, "is", actualVersionName);
}
}
@SuppressWarnings("NonBooleanMethodNameMayNotStartWithQuestion")
public void hasMaxSdkVersion(int maxSdkVersion) throws ProcessException {
List<String> output = ApkHelper.getApkBadging(getSubject());
checkMaxSdkVersion(output, maxSdkVersion);
}
@Override
protected String getDisplaySubject() {
String name = (internalCustomName() == null) ? "" : "\"" + internalCustomName() + "\" ";
return name + "<" + getSubject().getName() + ">";
}
/**
* Returns true if the provided class is present in the file.
* @param expectedClassName the class name in the format Lpkg1/pk2/Name;
*/
@Override
protected boolean checkForClass(
@NonNull String expectedClassName)
throws ProcessException, IOException {
// get the dexdump exec
File dexDump = SdkHelper.getDexDump();
ProcessExecutor executor = new DefaultProcessExecutor(
new StdLogger(StdLogger.Level.ERROR));
ProcessInfoBuilder builder = new ProcessInfoBuilder();
builder.setExecutable(dexDump);
builder.addArgs(getSubject().getAbsolutePath());
List<String> output = ApkHelper.runAndGetOutput(builder.createProcess(), executor);
for (String line : output) {
Matcher m = PATTERN_CLASS_DESC.matcher(line.trim());
if (m.matches()) {
String className = m.group(1);
if (expectedClassName.equals(className)) {
return true;
}
}
}
return false;
}
@NonNull
private static ApkInfoParser.ApkInfo getApkInfo(@NonNull File apk) throws ProcessException {
ProcessExecutor processExecutor = new DefaultProcessExecutor(
new StdLogger(StdLogger.Level.ERROR));
ApkInfoParser parser = new ApkInfoParser(SdkHelper.getAapt(), processExecutor);
return parser.parseApk(apk);
}
@VisibleForTesting
void checkMaxSdkVersion(@NonNull List<String> output, int maxSdkVersion) {
for (String line : output) {
Matcher m = PATTERN_MAX_SDK_VERSION.matcher(line.trim());
if (m.matches()) {
String actual = m.group(1);
try {
Integer i = Integer.parseInt(actual);
if (!i.equals(maxSdkVersion)) {
failWithBadResults("has maxSdkVersion", maxSdkVersion, "is", i);
}
return;
} catch (NumberFormatException e) {
failureStrategy.fail(
String.format(
"maxSdkVersion in badging for %s is not a number: %s",
getDisplaySubject(), actual),
e);
}
}
}
failWithRawMessage("maxSdkVersion not found in badging output for %s", getDisplaySubject());
}
}