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")