Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Host-based unit tests #17

Merged
merged 2 commits into from
Nov 9, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .idea/gradle.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

21 changes: 21 additions & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,24 @@ plugins {
id("com.android.library") version "8.1.2" apply false
id("org.mozilla.rust-android-gradle.rust-android") version "0.9.3" apply false
}

val osName: String? = System.getProperty("os.name")
val osArch: String? = System.getProperty("os.arch")
when {
osName == "Linux" && osArch == "amd64" -> extra["hostRustTarget"] = "linux-x86-64"
// see https://github.com/openjdk/jdk/blob/master/src/java.base/macosx/native/libjava/java_props_macosx.c
osName == "Mac OS X" && osArch == "amd64" -> extra["hostRustTarget"] = "darwin-x86-64"
osName == "Mac OS X" && osArch == "aarch64" -> extra["hostRustTarget"] = "darwin-aarch64"
// see https://github.com/openjdk/jdk/blob/master/src/java.base/windows/native/libjava/java_props_md.c
osName != null && osName.startsWith("Windows") && osArch == "amd64" -> extra["hostRustTarget"] =
"win32-x86-64-msvc"

else -> throw GradleException("Unsupported host operating system and architecture")
}
extra["rustTargets"] = listOf(
"arm",
"arm64",
"x86",
"x86_64",
extra["hostRustTarget"]
)
22 changes: 16 additions & 6 deletions divviup/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ plugins {

android {
namespace = "org.divviup.android"
compileSdk = 33
compileSdk = 34

ndkVersion = "26.1.10909125"

Expand All @@ -30,23 +30,33 @@ android {

dependencies {
implementation("commons-io:commons-io:2.15.0")
testImplementation(project(":divviup:commontest"))
testImplementation("junit:junit:4.13.2")
testImplementation("com.squareup.okhttp3:mockwebserver:4.12.0")
androidTestImplementation(project(":divviup:commontest"))
androidTestImplementation("androidx.test.ext:junit:1.1.5")
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
androidTestImplementation("com.squareup.okhttp3:mockwebserver:4.12.0")
}

val rustTargets: List<String> by rootProject.extra
val hostRustTarget: String by rootProject.extra

cargo {
module = "./rust"
libname = "divviup_android"
targets = listOf("arm", "arm64", "x86", "x86_64")

targets = rustTargets
profile = "release"

pythonCommand = "python3"
}

tasks.matching { it.name.matches(Regex("merge.*JniLibFolders")) }.configureEach {
this.inputs.dir(File(buildDir, "rustJniLibs/android"))
this.dependsOn(tasks.named("cargoBuild"))
inputs.dir(File(buildDir, "rustJniLibs/android"))
dependsOn(tasks.named("cargoBuild"))
}

tasks.withType<Test>().matching { it.name.matches(Regex("test.*UnitTest"))}.configureEach {
systemProperty("java.library.path", "${buildDir}/rustJniLibs/desktop/${hostRustTarget}")
val capitalizedHostRustTarget = hostRustTarget.replaceFirstChar { it.uppercase() }
dependsOn(tasks.named("cargoBuild${capitalizedHostRustTarget}"))
}
1 change: 1 addition & 0 deletions divviup/commontest/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/build
33 changes: 33 additions & 0 deletions divviup/commontest/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
plugins {
id("com.android.library")
}

android {
namespace = "org.divviup.commontest"
compileSdk = 34

defaultConfig {
minSdk = 21

testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles("consumer-rules.pro")
}

buildTypes {
release {
isMinifyEnabled = false
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
}

dependencies {
implementation("com.squareup.okhttp3:mockwebserver:4.12.0")
}
Empty file.
21 changes: 21 additions & 0 deletions divviup/commontest/proguard-rules.pro
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What are these .pro files? Since they're either empty or completely commented out, can we omit them?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They're Proguard files, introduced by the Android Studio new module wizard. Proguard is responsible for minifying/obfuscating class files, so these files govern exclusions of classes, methods, fields, etc. that need to have their names preserved if they are used by reflection, etc. The files are directly referenced by proguardFiles() and consumerProguardFiles() directives in the build system. The AAR includes a proguard.txt file that is probably merged from consumerProguardFiles() inputs. I'm inclined to keep them, since they're a normal part of an Android project.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fair enough, I don't have a good enough argument to push for removing them.

Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html

# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}

# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable

# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
4 changes: 4 additions & 0 deletions divviup/commontest/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">

</manifest>
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package org.divviup.commontest;

import java.io.IOException;
import java.io.InputStream;
import java.util.Objects;

import okhttp3.mockwebserver.MockResponse;
import okhttp3.mockwebserver.MockWebServer;
import okio.Buffer;

public class MockAggregator {
private static Buffer loadHpkeConfigList() throws IOException {
Buffer hpkeConfigListBuffer;
ClassLoader classLoader = Objects.requireNonNull(MockAggregator.class.getClassLoader());
try (InputStream is = classLoader.getResourceAsStream("hpke_config_list.bin")) {
hpkeConfigListBuffer = new Buffer();
hpkeConfigListBuffer.readFrom(is);
}
return hpkeConfigListBuffer;
}

public static MockWebServer setupMockServer() throws IOException {
Buffer hpkeConfigListBuffer = loadHpkeConfigList();
MockWebServer server = new MockWebServer();
server.enqueue(
new MockResponse()
.setHeader("Content-Type", "application/dap-hpke-config-list")
.setBody(hpkeConfigListBuffer)
);
server.enqueue(
new MockResponse()
.setHeader("Content-Type", "application/dap-hpke-config-list")
.setBody(hpkeConfigListBuffer)
);
server.enqueue(new MockResponse());
server.start();
return server;
}
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package org.divviup.android;

import androidx.test.ext.junit.runners.AndroidJUnit4;

import org.divviup.commontest.MockAggregator;
import org.junit.Test;
import org.junit.runner.RunWith;

import static org.junit.Assert.*;

import java.io.IOException;
import java.net.URI;

import okhttp3.mockwebserver.MockWebServer;
import okhttp3.mockwebserver.RecordedRequest;

@RunWith(AndroidJUnit4.class)
public class InstrumentedSmokeTest {
private static final TaskId ZERO_TASK_ID = TaskId.parse("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA");

@Test
public void smokeTestPrio3Count() throws IOException, InterruptedException {
try (MockWebServer server = MockAggregator.setupMockServer()) {
URI uri = server.url("/").uri();
Client<Boolean> client = Client.createPrio3Count(uri, uri, ZERO_TASK_ID, 300);
client.sendMeasurement(true);

basicUploadChecks(server);
}
}

@Test
public void smokeTestPrio3Sum() throws IOException, InterruptedException {
try (MockWebServer server = MockAggregator.setupMockServer()) {
URI uri = server.url("/").uri();
Client<Long> client = Client.createPrio3Sum(uri, uri, ZERO_TASK_ID, 300, 32);
client.sendMeasurement(1000000L);

basicUploadChecks(server);
}
}

@Test
public void smokeTestPrio3SumVec() throws IOException, InterruptedException {
try (MockWebServer server = MockAggregator.setupMockServer()) {
URI uri = server.url("/").uri();
Client<long[]> client = Client.createPrio3SumVec(uri, uri, ZERO_TASK_ID, 300, 10, 8, 12);
client.sendMeasurement(new long[] {252L, 7L, 80L, 194L, 190L, 217L, 141L, 85L, 222L, 243L});

basicUploadChecks(server);
}
}

@Test
public void smokeTestPrio3Histogram() throws IOException, InterruptedException {
try (MockWebServer server = MockAggregator.setupMockServer()) {
URI uri = server.url("/").uri();
Client<Long> client = Client.createPrio3Histogram(uri, uri, ZERO_TASK_ID, 300, 5, 2);
client.sendMeasurement(2L);

basicUploadChecks(server);
}
}

private static void basicUploadChecks(MockWebServer server) throws InterruptedException {
RecordedRequest r1 = server.takeRequest();
assertEquals(r1.getMethod(), "GET");
assertEquals(r1.getPath(), "/hpke_config?task_id=AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA");

RecordedRequest r2 = server.takeRequest();
assertEquals(r2.getMethod(), "GET");
assertEquals(r2.getPath(), "/hpke_config?task_id=AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA");

RecordedRequest r3 = server.takeRequest();
assertEquals(r3.getMethod(), "PUT");
assertEquals(r3.getPath(), "/tasks/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/reports");
assertEquals(r3.getHeader("Content-Type"), "application/dap-report");
assertTrue(r3.getBody().size() > 0);
}
}
Loading