diff --git a/.idea/gradle.xml b/.idea/gradle.xml
index abddf4a..a393f8b 100644
--- a/.idea/gradle.xml
+++ b/.idea/gradle.xml
@@ -12,6 +12,7 @@
+
diff --git a/divviup/build.gradle.kts b/divviup/build.gradle.kts
index f173e79..cca60d7 100644
--- a/divviup/build.gradle.kts
+++ b/divviup/build.gradle.kts
@@ -5,7 +5,7 @@ plugins {
android {
namespace = "org.divviup.android"
- compileSdk = 33
+ compileSdk = 34
ndkVersion = "26.1.10909125"
@@ -30,7 +30,10 @@ 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")
diff --git a/divviup/commontest/.gitignore b/divviup/commontest/.gitignore
new file mode 100644
index 0000000..796b96d
--- /dev/null
+++ b/divviup/commontest/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/divviup/commontest/build.gradle.kts b/divviup/commontest/build.gradle.kts
new file mode 100644
index 0000000..62d3823
--- /dev/null
+++ b/divviup/commontest/build.gradle.kts
@@ -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")
+}
diff --git a/divviup/commontest/consumer-rules.pro b/divviup/commontest/consumer-rules.pro
new file mode 100644
index 0000000..e69de29
diff --git a/divviup/commontest/proguard-rules.pro b/divviup/commontest/proguard-rules.pro
new file mode 100644
index 0000000..f1b4245
--- /dev/null
+++ b/divviup/commontest/proguard-rules.pro
@@ -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
diff --git a/divviup/commontest/src/main/AndroidManifest.xml b/divviup/commontest/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..8bdb7e1
--- /dev/null
+++ b/divviup/commontest/src/main/AndroidManifest.xml
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/divviup/commontest/src/main/java/org/divviup/commontest/MockAggregator.java b/divviup/commontest/src/main/java/org/divviup/commontest/MockAggregator.java
new file mode 100644
index 0000000..c52c3ff
--- /dev/null
+++ b/divviup/commontest/src/main/java/org/divviup/commontest/MockAggregator.java
@@ -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;
+ }
+}
diff --git a/divviup/src/androidTest/resources/hpke_config_list.bin b/divviup/commontest/src/main/resources/hpke_config_list.bin
similarity index 100%
rename from divviup/src/androidTest/resources/hpke_config_list.bin
rename to divviup/commontest/src/main/resources/hpke_config_list.bin
diff --git a/divviup/src/androidTest/java/org/divviup/android/ExampleInstrumentedTest.java b/divviup/src/androidTest/java/org/divviup/android/ExampleInstrumentedTest.java
deleted file mode 100644
index ca2e862..0000000
--- a/divviup/src/androidTest/java/org/divviup/android/ExampleInstrumentedTest.java
+++ /dev/null
@@ -1,128 +0,0 @@
-package org.divviup.android;
-
-import android.content.Context;
-
-import androidx.test.platform.app.InstrumentationRegistry;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import static org.junit.Assert.*;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.net.URI;
-import java.util.Objects;
-
-import okhttp3.mockwebserver.MockResponse;
-import okhttp3.mockwebserver.MockWebServer;
-import okhttp3.mockwebserver.RecordedRequest;
-import okio.Buffer;
-
-/**
- * Instrumented test, which will execute on an Android device.
- *
- * @see Testing documentation
- */
-@RunWith(AndroidJUnit4.class)
-public class ExampleInstrumentedTest {
- @Test
- public void useAppContext() {
- // Context of the app under test.
- Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
- assertEquals("org.divviup.android.test", appContext.getPackageName());
- }
-
- private static Buffer loadHpkeConfigList() throws IOException {
- Buffer hpkeConfigListBuffer;
- ClassLoader classLoader = Objects.requireNonNull(ExampleInstrumentedTest.class.getClassLoader());
- try (InputStream is = classLoader.getResourceAsStream("hpke_config_list.bin")) {
- hpkeConfigListBuffer = new Buffer();
- hpkeConfigListBuffer.readFrom(is);
- }
- return hpkeConfigListBuffer;
- }
-
- private 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;
- }
-
- @Test
- public void smokeTestPrio3Count() throws IOException, InterruptedException {
- try (MockWebServer server = setupMockServer()) {
- URI uri = server.url("/").uri();
- TaskId taskId = TaskId.parse("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA");
- Client client = Client.createPrio3Count(uri, uri, taskId, 300);
- client.sendMeasurement(true);
-
- basicUploadChecks(server);
- }
- }
-
- @Test
- public void smokeTestPrio3Sum() throws IOException, InterruptedException {
- try (MockWebServer server = setupMockServer()) {
- URI uri = server.url("/").uri();
- TaskId taskId = TaskId.parse("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA");
- Client client = Client.createPrio3Sum(uri, uri, taskId, 300, 32);
- client.sendMeasurement(1000000L);
-
- basicUploadChecks(server);
- }
- }
-
- @Test
- public void smokeTestPrio3SumVec() throws IOException, InterruptedException {
- try (MockWebServer server = setupMockServer()) {
- URI uri = server.url("/").uri();
- TaskId taskId = TaskId.parse("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA");
- Client client = Client.createPrio3SumVec(uri, uri, taskId, 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 = setupMockServer()) {
- URI uri = server.url("/").uri();
- TaskId taskId = TaskId.parse("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA");
- Client client = Client.createPrio3Histogram(uri, uri, taskId, 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);
- }
-}
diff --git a/divviup/src/androidTest/java/org/divviup/android/InstrumentedSmokeTest.java b/divviup/src/androidTest/java/org/divviup/android/InstrumentedSmokeTest.java
new file mode 100644
index 0000000..6cc582c
--- /dev/null
+++ b/divviup/src/androidTest/java/org/divviup/android/InstrumentedSmokeTest.java
@@ -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 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 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 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 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);
+ }
+}
diff --git a/divviup/src/test/java/android/util/Base64.java b/divviup/src/test/java/android/util/Base64.java
new file mode 100644
index 0000000..31afabc
--- /dev/null
+++ b/divviup/src/test/java/android/util/Base64.java
@@ -0,0 +1,18 @@
+package android.util;
+
+public class Base64 {
+ public static final int NO_PADDING = 1, NO_WRAP = 2, URL_SAFE = 8;
+ private static final int DAP_BASE64_FLAGS = NO_PADDING | NO_WRAP | URL_SAFE;
+
+ public static String encodeToString(byte[] input, int flags) {
+ assert flags == DAP_BASE64_FLAGS;
+ java.util.Base64.Encoder encoder = java.util.Base64.getUrlEncoder().withoutPadding();
+ return encoder.encodeToString(input);
+ }
+
+ public static byte[] decode(String str, int flags) {
+ assert flags == DAP_BASE64_FLAGS;
+ java.util.Base64.Decoder decoder = java.util.Base64.getUrlDecoder();
+ return decoder.decode(str);
+ }
+}
diff --git a/divviup/src/test/java/org/divviup/android/ExampleUnitTest.java b/divviup/src/test/java/org/divviup/android/ExampleUnitTest.java
deleted file mode 100644
index 7f31c32..0000000
--- a/divviup/src/test/java/org/divviup/android/ExampleUnitTest.java
+++ /dev/null
@@ -1,22 +0,0 @@
-package org.divviup.android;
-
-import org.junit.Test;
-
-import static org.junit.Assert.*;
-
-/**
- * Example local unit test, which will execute on the development machine (host).
- *
- * @see Testing documentation
- */
-public class ExampleUnitTest {
- @Test
- public void addition_isCorrect() {
- assertEquals(4, 2 + 2);
- }
-
- @Test
- public void loadDesktopNativeLibrary() {
- System.loadLibrary("divviup_android");
- }
-}
diff --git a/divviup/src/test/java/org/divviup/android/HostSmokeTest.java b/divviup/src/test/java/org/divviup/android/HostSmokeTest.java
new file mode 100644
index 0000000..9c66785
--- /dev/null
+++ b/divviup/src/test/java/org/divviup/android/HostSmokeTest.java
@@ -0,0 +1,76 @@
+package org.divviup.android;
+
+import static org.junit.Assert.*;
+
+import org.divviup.commontest.MockAggregator;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.net.URI;
+
+import okhttp3.mockwebserver.MockWebServer;
+import okhttp3.mockwebserver.RecordedRequest;
+
+public class HostSmokeTest {
+ 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 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 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 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 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);
+ }
+}
diff --git a/settings.gradle.kts b/settings.gradle.kts
index 9387d5e..dd5a672 100644
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -15,4 +15,5 @@ dependencyResolutionManagement {
rootProject.name = "Divvi Up"
include(":divviup")
+include(":divviup:commontest")
include(":sampleapp")