/*
* Copyright 2014 Shazam Entertainment Limited
*
* 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.shazam.fork;
import com.android.ddmlib.testrunner.IRemoteAndroidTestRunner;
import com.shazam.fork.system.axmlparser.InstrumentationInfo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.util.Collection;
import java.util.Collections;
import java.util.regex.Pattern;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.shazam.fork.system.axmlparser.InstumentationInfoFactory.parseFromFile;
import static java.util.Arrays.asList;
public class Configuration {
private static final Logger logger = LoggerFactory.getLogger(Configuration.class);
private final File androidSdk;
private final File applicationApk;
private final File instrumentationApk;
private final String applicationPackage;
private final String instrumentationPackage;
private final String testRunnerClass;
private final File output;
private final String title;
private final String subtitle;
private final Pattern testClassPattern;
private final String testPackage;
private final long testOutputTimeout;
private final IRemoteAndroidTestRunner.TestSize testSize;
private final Collection<String> excludedSerials;
private final boolean fallbackToScreenshots;
private final int totalAllowedRetryQuota;
private final int retryPerTestCaseQuota;
private final boolean isCoverageEnabled;
private final PoolingStrategy poolingStrategy;
private final boolean autoGrantPermissions;
private final String excludedAnnotation;
private Configuration(Builder builder) {
androidSdk = builder.androidSdk;
applicationApk = builder.applicationApk;
instrumentationApk = builder.instrumentationApk;
applicationPackage = builder.applicationPackage;
instrumentationPackage = builder.instrumentationPackage;
testRunnerClass = builder.testRunnerClass;
output = builder.output;
title = builder.title;
subtitle = builder.subtitle;
testClassPattern = Pattern.compile(builder.testClassRegex);
testPackage = builder.testPackage;
testOutputTimeout = builder.testOutputTimeout;
testSize = builder.testSize;
excludedSerials = builder.excludedSerials;
fallbackToScreenshots = builder.fallbackToScreenshots;
totalAllowedRetryQuota = builder.totalAllowedRetryQuota;
retryPerTestCaseQuota = builder.retryPerTestCaseQuota;
isCoverageEnabled = builder.isCoverageEnabled;
poolingStrategy = builder.poolingStrategy;
autoGrantPermissions = builder.autoGrantPermissions;
this.excludedAnnotation = builder.excludedAnnotation;
}
@Nonnull
public File getAndroidSdk() {
return androidSdk;
}
@Nonnull
public File getApplicationApk() {
return applicationApk;
}
@Nonnull
public File getInstrumentationApk() {
return instrumentationApk;
}
@Nonnull
public String getApplicationPackage() {
return applicationPackage;
}
@Nonnull
public String getInstrumentationPackage() {
return instrumentationPackage;
}
@Nonnull
public String getTestRunnerClass() {
return testRunnerClass;
}
@Nonnull
public File getOutput() {
return output;
}
@Nonnull
public String getTitle() {
return title;
}
@Nonnull
public String getSubtitle() {
return subtitle;
}
@Nonnull
public Pattern getTestClassPattern() {
return testClassPattern;
}
@Nonnull
public String getTestPackage() {
return testPackage;
}
public long getTestOutputTimeout() {
return testOutputTimeout;
}
@Nullable
public IRemoteAndroidTestRunner.TestSize getTestSize() {
return testSize;
}
@Nonnull
public Collection<String> getExcludedSerials() {
return excludedSerials;
}
public boolean canFallbackToScreenshots() {
return fallbackToScreenshots;
}
public int getTotalAllowedRetryQuota() {
return totalAllowedRetryQuota;
}
public int getRetryPerTestCaseQuota() {
return retryPerTestCaseQuota;
}
public boolean isCoverageEnabled() {
return isCoverageEnabled;
}
public PoolingStrategy getPoolingStrategy() {
return poolingStrategy;
}
public boolean isAutoGrantingPermissions() {
return autoGrantPermissions;
}
public String getExcludedAnnotation() {
return excludedAnnotation;
}
public static class Builder {
private File androidSdk;
private File applicationApk;
private File instrumentationApk;
private String applicationPackage;
private String instrumentationPackage;
private String testRunnerClass;
private File output;
private String title;
private String subtitle;
private String testClassRegex;
private String testPackage;
private long testOutputTimeout;
private IRemoteAndroidTestRunner.TestSize testSize;
private Collection<String> excludedSerials;
private boolean fallbackToScreenshots;
private int totalAllowedRetryQuota;
private int retryPerTestCaseQuota;
private boolean isCoverageEnabled;
private PoolingStrategy poolingStrategy;
private boolean autoGrantPermissions;
private String excludedAnnotation;
public static Builder configuration() {
return new Builder();
}
public Builder withAndroidSdk(@Nonnull File androidSdk) {
this.androidSdk = androidSdk;
return this;
}
public Builder withApplicationApk(@Nonnull File applicationApk) {
this.applicationApk = applicationApk;
return this;
}
public Builder withInstrumentationApk(@Nonnull File instrumentationApk) {
this.instrumentationApk = instrumentationApk;
return this;
}
public Builder withOutput(@Nonnull File output) {
this.output = output;
return this;
}
public Builder withTitle(String title) {
this.title = title;
return this;
}
public Builder withSubtitle(String subtitle) {
this.subtitle = subtitle;
return this;
}
public Builder withTestClassRegex(String testClassRegex) {
this.testClassRegex = testClassRegex;
return this;
}
public Builder withTestPackage(String testPackage) {
this.testPackage = testPackage;
return this;
}
public Builder withTestOutputTimeout(int testOutputTimeout) {
this.testOutputTimeout = testOutputTimeout;
return this;
}
public Builder withTestSize(String testSize) {
this.testSize = (testSize == null ? null : IRemoteAndroidTestRunner.TestSize.getTestSize(testSize));
return this;
}
public Builder withExcludedSerials(Collection<String> excludedSerials) {
this.excludedSerials = excludedSerials;
return this;
}
public Builder withFallbackToScreenshots(boolean fallbackToScreenshots) {
this.fallbackToScreenshots = fallbackToScreenshots;
return this;
}
public Builder withTotalAllowedRetryQuota(int totalAllowedRetryQuota) {
this.totalAllowedRetryQuota = totalAllowedRetryQuota;
return this;
}
public Builder withRetryPerTestCaseQuota(int retryPerTestCaseQuota) {
this.retryPerTestCaseQuota = retryPerTestCaseQuota;
return this;
}
public Builder withCoverageEnabled(boolean isCoverageEnabled) {
this.isCoverageEnabled = isCoverageEnabled;
return this;
}
public Builder withPoolingStrategy(@Nullable PoolingStrategy poolingStrategy) {
this.poolingStrategy = poolingStrategy;
return this;
}
public Builder withAutoGrantPermissions(boolean autoGrantPermissions) {
this.autoGrantPermissions = autoGrantPermissions;
return this;
}
public Builder withExcludedAnnotation(String excludedAnnotation) {
this.excludedAnnotation = excludedAnnotation;
return this;
}
public Configuration build() {
checkNotNull(androidSdk, "SDK is required.");
checkArgument(androidSdk.exists(), "SDK directory does not exist.");
checkNotNull(applicationApk, "Application APK is required.");
checkArgument(applicationApk.exists(), "Application APK file does not exist.");
checkNotNull(instrumentationApk, "Instrumentation APK is required.");
checkArgument(instrumentationApk.exists(), "Instrumentation APK file does not exist.");
InstrumentationInfo instrumentationInfo = parseFromFile(instrumentationApk);
checkNotNull(instrumentationInfo.getApplicationPackage(), "Application package was not found in test APK");
applicationPackage = instrumentationInfo.getApplicationPackage();
checkNotNull(instrumentationInfo.getInstrumentationPackage(), "Instrumentation package was not found in test APK");
instrumentationPackage = instrumentationInfo.getInstrumentationPackage();
checkNotNull(instrumentationInfo.getTestRunnerClass(), "Test runner class was not found in test APK");
testRunnerClass = instrumentationInfo.getTestRunnerClass();
checkNotNull(output, "Output path is required.");
title = assignValueOrDefaultIfNull(title, Defaults.TITLE);
subtitle = assignValueOrDefaultIfNull(subtitle, Defaults.SUBTITLE);
testClassRegex = assignValueOrDefaultIfNull(testClassRegex, CommonDefaults.TEST_CLASS_REGEX);
testPackage = assignValueOrDefaultIfNull(testPackage, instrumentationInfo.getInstrumentationPackage());
testOutputTimeout = assignValueOrDefaultIfZero(testOutputTimeout, Defaults.TEST_OUTPUT_TIMEOUT_MILLIS);
excludedSerials = assignValueOrDefaultIfNull(excludedSerials, Collections.<String>emptyList());
checkArgument(totalAllowedRetryQuota >= 0, "Total allowed retry quota should not be negative.");
checkArgument(retryPerTestCaseQuota >= 0, "Retry per test case quota should not be negative.");
retryPerTestCaseQuota = assignValueOrDefaultIfZero(retryPerTestCaseQuota, Defaults.RETRY_QUOTA_PER_TEST_CASE);
logArgumentsBadInteractions();
poolingStrategy = validatePoolingStrategy(poolingStrategy);
return new Configuration(this);
}
private static <T> T assignValueOrDefaultIfNull(T value, T defaultValue) {
return value != null ? value : defaultValue;
}
private static <T extends Number> T assignValueOrDefaultIfZero(T value, T defaultValue) {
return value.longValue() != 0 ? value : defaultValue;
}
private void logArgumentsBadInteractions() {
if (totalAllowedRetryQuota > 0 && totalAllowedRetryQuota < retryPerTestCaseQuota) {
logger.warn("Total allowed retry quota [" + totalAllowedRetryQuota + "] " +
"is smaller than Retry per test case quota [" + retryPerTestCaseQuota + "]. " +
"This is suspicious as the first mentioned parameter is an overall cap.");
}
}
/**
* We need to make sure zero or one strategy has been passed. If zero default to pool per device. If more than one
* we throw an exception.
*/
private PoolingStrategy validatePoolingStrategy(PoolingStrategy poolingStrategy) {
if (poolingStrategy == null) {
logger.warn("No strategy was chosen in configuration, so defaulting to one pool per device");
poolingStrategy = new PoolingStrategy();
poolingStrategy.eachDevice = true;
} else {
long selectedStrategies = asList(
poolingStrategy.eachDevice,
poolingStrategy.splitTablets,
poolingStrategy.computed,
poolingStrategy.manual)
.stream()
.filter(p -> p != null)
.count();
if (selectedStrategies > Defaults.STRATEGY_LIMIT) {
throw new IllegalArgumentException("You have selected more than one strategies in configuration. " +
"You can only select up to one.");
}
}
return poolingStrategy;
}
}
}