AndroidTacticalAssaultKit-CIV
AndroidTacticalAssaultKit-CIV copied to clipboard
Instrumentation Testing Yields 0 Tests
So, I've been working on adding tests to my ATAK CIV plugin. Unit tests work splendidly, but for some reason, Instrumentations do not work. I've been extending the ATAKTestClass with my tests and just copying down the testing instructions received from the wiki. Since there may be a bit too many variables to consider from my current project, I tried opening up the HelloWorld example and following the same instructions. I expected the test to work out the box (after setup), but to no avail.
Version: sdk release version 4.4.0.0 or hash 2b67f92
package com.atakmap.android.helloworld;
import android.content.Context;
import android.preference.PreferenceManager;
import android.view.View;
import androidx.test.espresso.Espresso;
import androidx.test.espresso.action.ViewActions;
import androidx.test.espresso.matcher.ViewMatchers;
import androidx.test.platform.app.InstrumentationRegistry;
import com.atakmap.android.maps.MapView;
import com.atakmap.android.maps.Marker;
import com.atakmap.android.test.helpers.ATAKTestClass;
import com.atakmap.android.test.helpers.DrawableMatcher;
import com.atakmap.app.R;
import com.atakmap.coremap.maps.coords.GeoPoint;
import org.hamcrest.Matcher;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import java.util.concurrent.Callable;
/**
* Basic class to begin testing on the ATAK Map. This class is for generic testing and
* testing of common code helper functions.
*/
public class MapTest extends ATAKTestClass {
private final Context appContext = InstrumentationRegistry
.getInstrumentation().getTargetContext();
// TODO: move these to ATAKTestClass if they're useful to generalize, but may need to be tailored a bit for each test class
@BeforeClass
public static void setupBeforeAllTests() throws Exception {
helper.deleteAllMarkers();
}
@Before
public void setupBeforeEachTest() {
helper.panZoomTo(0.0, new GeoPoint(.5, .5));
}
@After
public void cleanupAfterEachTests() throws InterruptedException {
helper.pressBackTimes(5);
helper.deleteAllMarkers();
}
// TODO: move this to a helper class?
public static Matcher<View> withDrawable(final int resourceId) {
return new DrawableMatcher(resourceId);
}
@Test
public void useAppContext() throws Exception {
Assert.assertEquals("com.atakmap.app.civ", appContext.getPackageName());
}
@Test
public void openOverflowMenu() throws Exception {
Espresso.openActionBarOverflowOrOptionsMenu(appContext);
helper.pressBackTimes(1);
}
@Test
public void placeMarker() throws Exception {
// Short-term fix for first hint dialog, just disable that hint so the rest of the test works:
PreferenceManager.getDefaultSharedPreferences(appContext)
.edit()
.putBoolean("atak.hint.iconset", false)
.apply();
// Open marker palette
helper.pressButtonFromLayoutManager("Point Dropper");
// Close hint dialog that appears the first time this tool is run
//TODO: fix the next line -- I think Espresso is probably getting hung up on the animation the Point Dropper does while the hint dialog is open? Long-term, might need to have a way to either disable the animation or animate in a way Espresso can deal with.
//onView(withId(android.R.id.button1)).inRoot(isDialog()).perform(click());
// Select friendly marker button
Espresso.onView(ViewMatchers.withId(R.id.enterLocationTypeFriendly))
.perform(ViewActions.click());
// Close hint dialog that appears the first time this button is pressed
helper.closeHelperDialog();
// Place a marker at 0,0
helper.pressMapLocationMinScale(new GeoPoint(0, 0));
// Verify newly placed marker exists
Assert.assertNotNull(helper.nullWait(new Callable<Marker>() {
@Override
public Marker call() {
return helper.getMarkerOfType("a-f-G");
}
}, 3000));
}
@Test
public void selectRadialMenuButton() throws Exception {
helper.pressButtonFromLayoutManager("Red X Tool");
//see assets\menus\redx_menu.xml for all the actions you can perform for red x radial
helper.pressMapLocation(MapView.getMapView().getCenterPoint().get());
helper.pressMarkerNameOnMap("Red X");
helper.pressRadialButton(helper.getMarkerOfName("Red X"),
"asset://icons/target.png");
}
}
The above is all boilerplate code, and the following is my gradle.build file for the module:
////////////////////////////////////////////////////////////////////////////////
//
// PLUGIN_VERSION is the common version name when describing the plugin.
// ATAK_VERSION is for the version of ATAK this plugin should be compatible
// with some examples include 3.11.0, 3.11.0.civ 3.11.1.fvey
//
////////////////////////////////////////////////////////////////////////////////
buildscript {
ext.PLUGIN_VERSION = "1.0"
ext.ATAK_VERSION = "4.4.0"
def takdevVersion = '2.+'
ext.getValueFromPropertiesFile = { propFile, key ->
if(!propFile.isFile() || !propFile.canRead())
return null
def prop = new Properties()
def reader = propFile.newReader()
try {
prop.load(reader)
} finally {
reader.close()
}
return prop.get(key)
}
def getProperty = { name, defValue ->
def prop = project.properties[name] ?:
getValueFromPropertiesFile(project.rootProject.file('local.properties'), name)
return (null == prop) ? defValue : prop
}
def urlKey = 'takrepo.url'
ext.isDevKitEnabled = { ->
return getProperty(urlKey, null) != null
}
ext.takrepoUrl = getProperty(urlKey, 'http://localhost/')
ext.takrepoUser = getProperty('takrepo.user', 'invalid')
ext.takrepoPassword = getProperty('takrepo.password', 'invalid')
ext.takdevPlugin = getProperty('takdev.plugin', "${rootDir}/../../atak-gradle-takdev.jar")
repositories {
mavenCentral()
google()
mavenLocal()
maven {
url "https://jitpack.io"
}
maven {
url = takrepoUrl
credentials {
username = takrepoUser
password = takrepoPassword
}
}
}
dependencies {
classpath 'com.android.tools.build:gradle:4.0.2'
if(isDevKitEnabled()) {
classpath "com.atakmap.gradle:atak-gradle-takdev:${takdevVersion}"
} else {
classpath files(takdevPlugin)
}
}
}
allprojects {
repositories {
mavenCentral()
google()
mavenLocal()
maven {
url "https://jitpack.io"
}
}
}
apply plugin: 'com.android.application'
apply plugin: 'atak-takdev-plugin'
// Attempt to get a suitable version name for the plugin based on
// either a git or svn repository
def getVersionName() {
try {
def stdout = new ByteArrayOutputStream()
exec {
commandLine 'git', 'rev-parse', '--short=8', 'HEAD'
standardOutput = stdout
}
def describe = stdout.toString().trim()
println("versionName[git]: $describe")
return describe
} catch (Exception ignored) {
println("error occured, using revision of 1")
return 1
}
}
// Attempt to get a suitable version code for the plugin based on
// either a git or svn repository
def getVersionCode() {
try {
new ByteArrayOutputStream().withStream { os ->
def result = exec {
executable = 'git'
args = ['show', '-s', '--format=%ct']
standardOutput = os
ignoreExitValue = true
}
def outputAsString = os.toString()
ext.revision = "$outputAsString".toInteger()
println("version[git]: $revision")
}
} catch (Exception ignored) {
println("error occured, using revision of 1")
ext.revision = 1
}
return revision
}
android {
compileSdkVersion 28
buildToolsVersion "29.0.2"
// dexOptions {
// jumboMode = true
// }
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
lintOptions {
checkReleaseBuilds true
// Or, if you prefer, you can continue to check for errors in release builds,
// but continue the build even when errors are found:
abortOnError true
}
signingConfigs {
debug {
def kf = getValueFromPropertiesFile(project.rootProject.file('local.properties'), 'takDebugKeyFile')
def kfp = getValueFromPropertiesFile(project.rootProject.file('local.properties'), 'takDebugKeyFilePassword')
def ka = getValueFromPropertiesFile(project.rootProject.file('local.properties'), 'takDebugKeyAlias')
def kp = getValueFromPropertiesFile(project.rootProject.file('local.properties'), 'takDebugKeyPassword')
if (kf == null) {
throw new GradleException("No signing key configured!")
}
storeFile file(kf)
if (kfp != null) storePassword kfp
if (ka != null) keyAlias ka
if (kp != null) keyPassword kp
}
release {
def kf = getValueFromPropertiesFile(project.rootProject.file('local.properties'), 'takReleaseKeyFile')
def kfp = getValueFromPropertiesFile(project.rootProject.file('local.properties'), 'takReleaseKeyFilePassword')
def ka = getValueFromPropertiesFile(project.rootProject.file('local.properties'), 'takReleaseKeyAlias')
def kp = getValueFromPropertiesFile(project.rootProject.file('local.properties'), 'takReleaseKeyPassword')
if (kf == null) {
throw new GradleException("No signing key configured!")
}
storeFile file(kf)
if (kfp != null) storePassword kfp
if (ka != null) keyAlias ka
if (kp != null) keyPassword kp
}
}
buildTypes {
debug {
debuggable true
matchingFallbacks = ['sdk']
}
release {
minifyEnabled true
proguardFile 'proguard-gradle.txt'
signingConfig signingConfigs.release
matchingFallbacks = ['odk']
}
}
packagingOptions {
exclude 'META-INF/INDEX.LIST'
}
sourceSets {
main {
setProperty("archivesBaseName", "ATAK-Plugin-" + rootProject.name + "-" + PLUGIN_VERSION + "-" + getVersionName() + "-" + ATAK_VERSION)
defaultConfig.versionCode = getVersionCode()
defaultConfig.versionName = PLUGIN_VERSION + " (" + getVersionName() + ") - [" + ATAK_VERSION + "]"
}
// Move the tests to tests/java, tests/res, etc...
//instrumentTest.setRoot('tests')
// Move the build types to build-types/<type>
// For instance, build-types/debug/java, build-types/debug/AndroidManifest.xml, ...
// This moves them out of them default location under src/<type>/... which would
// conflict with src/ being used by the main source set.
// Adding new build types or product flavors should be accompanied
// by a similar customization.
debug.setRoot('build-types/debug')
release.setRoot('build-types/release')
}
defaultConfig {
minSdkVersion 21
ndk {
abiFilters "armeabi-v7a", "arm64-v8a", "x86"
}
}
flavorDimensions "application"
productFlavors {
mil {
getIsDefault().set(true)
dimension "application"
manifestPlaceholders = [atakApiVersion: "com.atakmap.app@" + ATAK_VERSION + ".MIL"]
buildConfigField 'String', 'ATAK_PACKAGE_NAME', '"com.atakmap.app.civ"'
}
civ {
dimension "application"
manifestPlaceholders = [atakApiVersion: "com.atakmap.app@" + ATAK_VERSION + ".CIV"]
buildConfigField 'String', 'ATAK_PACKAGE_NAME', '"com.atakmap.app.civ"'
}
fvey {
dimension "application"
manifestPlaceholders = [atakApiVersion: "com.atakmap.app@" + ATAK_VERSION + ".FVEY"]
buildConfigField 'String', 'ATAK_PACKAGE_NAME', '"com.atakmap.app.civ"'
}
aus {
dimension "application"
manifestPlaceholders = [atakApiVersion: "com.atakmap.app@" + ATAK_VERSION + ".AUS"]
buildConfigField 'String', 'ATAK_PACKAGE_NAME', '"com.atakmap.app.civ"'
}
nzl {
dimension "application"
manifestPlaceholders = [atakApiVersion: "com.atakmap.app@" + ATAK_VERSION + ".NZL"]
buildConfigField 'String', 'ATAK_PACKAGE_NAME', '"com.atakmap.app.civ"'
}
prt {
dimension "application"
manifestPlaceholders = [atakApiVersion: "com.atakmap.app@" + ATAK_VERSION + ".PRT"]
buildConfigField 'String', 'ATAK_PACKAGE_NAME', '"com.atakmap.app.civ"'
}
nor {
dimension "application"
manifestPlaceholders = [atakApiVersion: "com.atakmap.app@" + ATAK_VERSION + ".NOR"]
buildConfigField 'String', 'ATAK_PACKAGE_NAME', '"com.atakmap.app.civ"'
}
hun {
dimension "application"
manifestPlaceholders = [atakApiVersion: "com.atakmap.app@" + ATAK_VERSION + ".HUN"]
buildConfigField 'String', 'ATAK_PACKAGE_NAME', '"com.atakmap.app.civ"'
}
bel {
dimension "application"
manifestPlaceholders = [atakApiVersion: "com.atakmap.app@" + ATAK_VERSION + ".BEL"]
buildConfigField 'String', 'ATAK_PACKAGE_NAME', '"com.atakmap.app.civ"'
}
gov {
dimension "application"
manifestPlaceholders = [atakApiVersion: "com.atakmap.app@" + ATAK_VERSION + ".CIV"] // CIV API with possible distributiion restrictions (see PluginTemplate)
}
swe {
dimension "application"
manifestPlaceholders = [atakApiVersion: "com.atakmap.app@" + ATAK_VERSION + ".SWE"]
}
natosof {
dimension "application"
manifestPlaceholders = [atakApiVersion: "com.atakmap.app@" + ATAK_VERSION + ".NATOSOF"]
}
gbr {
dimension "application"
manifestPlaceholders = [atakApiVersion: "com.atakmap.app@" + ATAK_VERSION + ".GBR"]
buildConfigField 'String', 'ATAK_PACKAGE_NAME', '"com.atakmap.app.civ"'
}
gov {
dimension "application"
manifestPlaceholders = [atakApiVersion: "com.atakmap.app@" + ATAK_VERSION + ".GOV"]
buildConfigField 'String', 'ATAK_PACKAGE_NAME', '"com.atakmap.app.fvey"'
}
can {
dimension "application"
manifestPlaceholders = [atakApiVersion: "com.atakmap.app@" + ATAK_VERSION + ".CAN"]
buildConfigField 'String', 'ATAK_PACKAGE_NAME', '"com.atakmap.app.civ"'
}
applicationVariants.all { variant ->
variant.resValue "string", "versionName", variant.versionName
}
}
}
dependencies {
implementation fileTree(dir: 'libs', include: '*.jar')
// Recyclerview version depends on some androidx libraries which
// are supplied by core, so they should be excluded. Otherwise
// bad things happen in the release builds after proguarding
implementation ('androidx.recyclerview:recyclerview:1.1.0') {
exclude module: 'collection'
exclude module: 'core'
exclude module: 'lifecycle'
exclude module: 'core-common'
exclude module: 'collection'
exclude module: 'customview'
}
androidTestImplementation 'androidx.lifecycle:lifecycle-runtime:2.2.0'
}
I've done minimal changes such as moving the dependency list to the bottom and replacing line 16 from a function definition to a property initialization. The function definition was causing a cannot resolve symbol Gradle Exception. I tried to keep everything as similar as possible. I finally copied the "espresso" folder to the root of the project "atak-civ/plugin-examples/helloworld/" rebuilt and refreshed the gradle dependencies.
I made sure the atak dev apk as well as the plugin were installed prior and ran the tests to receive:

I'm not sure what I could be doing wrong, especially since this is straight from the helloworld plugin example. How do I get Android Studio to run the class MapTest and all of the tests that it has? I've been scratching my head on this in a while, so I wouldn't be surprised if the answer is super obvious.
Thanks in advanced. Let me know if you need anything else to help.