Skip to content

Commit

Permalink
Merge branch 'develop' into ci-integrations
Browse files Browse the repository at this point in the history
  • Loading branch information
Vacxe authored Aug 31, 2024
2 parents a894ffb + d9dda98 commit c876d6a
Show file tree
Hide file tree
Showing 10 changed files with 3,163 additions and 12,196 deletions.
2 changes: 1 addition & 1 deletion buildSrc/src/main/kotlin/Versions.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ object Versions {
val coroutinesTest = coroutines

val androidCommon = "31.2.2"
val adam = "0.5.7"
val adam = "0.5.8"
val dexTestParser = "2.3.4"
val kotlinLogging = "3.0.5"
val logbackClassic = "1.4.14"
Expand Down
9 changes: 8 additions & 1 deletion docs/cloud/intro/overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ Whenever you submit an application and test application to Marathon Cloud, the f

When you run tests with Marathon Cloud for the first time, we begin storing the test history in our database.
The next time you run these tests, we already have information on the average time and the probability of a successful execution for each test.
Using this data, we calculate the necessary number of devices to ensure that your tests will complete within 15 minutes.
Using this data, we calculate the necessary number of devices to ensure that your tests will be completed within 15 minutes, be it 5 or 200.
We also monitor the progress of test executions and adjust the distribution of tests across devices as needed.
While the tests are running, our service can dynamically increase the number of devices to expedite the execution process.

Expand All @@ -35,6 +35,13 @@ If you prefer, you can enable the "isolated" parameter to manage device cleaning
but please note that this may lead to an increase in the number of devices required and the overall time of devices taken for testing.
However, the total execution time will still be 15 minutes.

### Retries
Retries are a cornerstone of effective testing. The larger your test suite, the greater the likelihood that one test might fail due to non-determinism, commonly known as test flakiness. While adding retries can improve test stability, it also increases the total testing time.
Marathon offers two retry strategies and several enhancements to strike a balance between stability and efficiency:
- Predictive retries: By analyzing past test executions, we can determine the success rate of each test. For flaky tests, we can run multiple executions to ensure at least one succeeds.
- Common retries: If a test fails, we will rerun it up to three times. Marathon will attempt to run the test on a different device to minimize the environmental impact on the results.
- Additional features: Sorting, batching, test rebalancing, and more.




Expand Down
14,222 changes: 2,542 additions & 11,680 deletions docs/package-lock.json

Large diffs are not rendered by default.

4 changes: 1 addition & 3 deletions docs/src/styles/components/_toc.scss
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,9 @@ html[data-theme='dark'] {
border-block-end: 1px solid var(--toc-separator-c);

line-height: 1.4;
--ifm-toc-border-color: transparent;
}

--ifm-toc-border-color: transparent;

.toc-wrapper {
overflow: auto;
position: sticky;
Expand All @@ -40,7 +39,6 @@ html[data-theme='dark'] {
padding-inline-end: 2rem;
padding-inline-start: 2rem;
padding-block-end: 2rem;

h2 {
font-size: 0.625rem;
line-height: 12px;
Expand Down
815 changes: 426 additions & 389 deletions docs/yarn.lock

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package com.malinskiy.marathon.android
import com.malinskiy.marathon.android.exception.DeviceNotSupportedException
import com.malinskiy.marathon.android.exception.InstallException
import com.malinskiy.marathon.android.extension.testBundlesCompat
import com.malinskiy.marathon.android.model.AndroidTestBundle
import com.malinskiy.marathon.config.Configuration
import com.malinskiy.marathon.config.vendor.VendorConfiguration
import com.malinskiy.marathon.exceptions.DeviceSetupException
Expand All @@ -26,31 +27,33 @@ class AndroidAppInstaller(configuration: Configuration) {
suspend fun prepareInstallation(device: AndroidDevice) {
val testBundles = androidConfiguration.testBundlesCompat()
testBundles.forEach { bundle ->
val apkParser = ApkParser()
val applicationInfo = apkParser.parseInstrumentationInfo(bundle.testApplication)
preparePartialInstallation(device, bundle)
}
}

logger.debug { "Installing application output to ${device.serialNumber}" }
bundle.application?.let { applicationApk ->
if (bundle.splitApks.isNullOrEmpty()) {
if (device.apiLevel < 21) {
throw DeviceNotSupportedException("Device api level should be more then 20")
}
suspend fun preparePartialInstallation(device: AndroidDevice, bundle: AndroidTestBundle) {
val applicationInfo = bundle.instrumentationInfo
logger.debug { "Installing application output to ${device.serialNumber}" }
bundle.application?.let { applicationApk ->
if (bundle.splitApks.isNullOrEmpty()) {
if (device.apiLevel < 21) {
throw DeviceNotSupportedException("Device api level should be more then 20")
}
reinstall(device, applicationInfo.applicationPackage, applicationApk, bundle.splitApks ?: emptyList())
}
reinstall(device, applicationInfo.applicationPackage, applicationApk, bundle.splitApks ?: emptyList())
}

bundle.extraApplications?.let { extraApplications ->
extraApplications.forEach { extraApplication ->
logger.debug { "Installing extra application to ${device.serialNumber}" }
val extraApplicationPackage = apkParser.parseAppPackageName(extraApplication)
reinstall(device, extraApplicationPackage, extraApplication)
}
bundle.extraApplications?.let { extraApplications ->
extraApplications.forEach { extraApplication ->
logger.debug { "Installing extra application to ${device.serialNumber}" }
val extraApplicationPackage = AndroidTestBundle.apkParser.parseAppPackageName(extraApplication)
reinstall(device, extraApplicationPackage, extraApplication)
}

logger.debug { "Installing instrumentation package to ${device.serialNumber}" }
reinstall(device, applicationInfo.instrumentationPackage, bundle.testApplication)
logger.debug { "Prepare installation finished for ${device.serialNumber}" }
}

logger.debug { "Installing instrumentation package to ${device.serialNumber}" }
reinstall(device, applicationInfo.instrumentationPackage, bundle.testApplication)
logger.debug { "Prepare installation finished for ${device.serialNumber}" }
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import com.malinskiy.adam.request.framebuffer.BufferedImageScreenCaptureAdapter
import com.malinskiy.adam.request.framebuffer.ScreenCaptureRequest
import com.malinskiy.adam.request.pkg.InstallRemotePackageRequest
import com.malinskiy.adam.request.pkg.InstallSplitPackageRequest
import com.malinskiy.adam.request.pkg.StreamingPackageInstallRequest
import com.malinskiy.adam.request.pkg.UninstallRemotePackageRequest
import com.malinskiy.adam.request.pkg.multi.ApkSplitInstallationPackage
import com.malinskiy.adam.request.prop.GetPropRequest
Expand Down Expand Up @@ -306,6 +307,36 @@ class AdamAndroidDevice(
absolutePath: String,
reinstall: Boolean,
optionalParams: List<String>
): MarathonShellCommandResult {
return if (supportedFeatures.contains(Feature.ABB_EXEC) || supportedFeatures.contains(Feature.CMD)) {
installPackageStreaming(absolutePath, reinstall, optionalParams)
} else {
installPackageLegacy(absolutePath, reinstall, optionalParams)
}
}

private suspend fun installPackageStreaming(
absolutePath: String,
reinstall: Boolean,
optionalParams: List<String>
): MarathonShellCommandResult {
val result = withTimeoutOrNull(androidConfiguration.timeoutConfiguration.install) {
client.execute(
StreamingPackageInstallRequest(
File(absolutePath),
supportedFeatures,
reinstall,
extraArgs = optionalParams.filter { it.isNotBlank() },
), serial = adbSerial
)
} ?: throw InstallException("Timeout installing $absolutePath")
return com.malinskiy.marathon.android.model.ShellCommandResult(result.output, if (result.success) 0 else 1)
}

private suspend fun installPackageLegacy(
absolutePath: String,
reinstall: Boolean,
optionalParams: List<String>
): MarathonShellCommandResult {
val file = File(absolutePath)
//Very simple escaping for the name of the file
Expand Down Expand Up @@ -399,8 +430,10 @@ class AdamAndroidDevice(
}
} catch (e: CancellationException) {
logger.warn(e) { "screenrecord start was interrupted" }
throw e
} catch (e: Exception) {
logger.error("Unable to start screenrecord", e)
throw e
}
}

Expand Down
Loading

0 comments on commit c876d6a

Please sign in to comment.