Skip to content
This repository was archived by the owner on Aug 30, 2023. It is now read-only.
Draft
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
46 changes: 46 additions & 0 deletions .github/workflows/emulator-sample-install.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
name: Workflow Sample Install macOS
on:
push:
branches:
- master
pull_request:
branches:
- '*'

jobs:
build:
runs-on: macos-latest

steps:
- name: Git checkout
uses: actions/checkout@v2

# Zulu Community distribution of OpenJDK
- name: set up JDK 1.8
uses: actions/setup-java@v1
with:
java-version: 1.8

- name: Generate Gradle checksums
run: |
find buildSrc -name "*.kt" -type f | sort | xargs shasum > gradle-checksums.txt
cat gradle-checksums.txt
- uses: actions/cache@v2
with:
path: ~/.gradle
key: gradle-${{ hashFiles('gradle-checksums.txt') }}-${{ hashFiles('gradle/wrapper/gradle-wrapper.properties') }}
restore-keys: |
gradle-${{ hashFiles('gradle-checksums.txt') }}-

- name: run tests
- uses: reactivecircus/android-emulator-runner@v2
with:
api-level: 29
ndk: 21.0.6113669
cmake: 3.10.2.4988404
script: make runConnectedTests

# We stop gradle at the end to make sure the cache folders
# don't contain any lock files and are free to be cached.
- name: Make stop
run: make stop
6 changes: 5 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
.PHONY: clean compile dryRelease doRelease release update stop
.PHONY: clean compile dryRelease doRelease release update stop runConnectedTests

all: clean compile update dryRelease

Expand Down Expand Up @@ -29,3 +29,7 @@ update:
# don't contain any lock files and are free to be cached.
stop:
./gradlew --stop

# run connectedCheck
runConnectedTests:
./gradlew connectedDebugAndroidTest
11 changes: 8 additions & 3 deletions buildSrc/src/main/java/Config.kt
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ object Config {

object Libs {
val appCompat = "androidx.appcompat:appcompat:1.1.0"
val timber = "com.jakewharton.timber:timber:4.7.1"
// only bump gson if https://github.com/google/gson/issues/1597 is fixed
val gson = "com.google.code.gson:gson:2.8.5"
val leakCanary = "com.squareup.leakcanary:leakcanary-android:2.3"
Expand All @@ -33,15 +32,21 @@ object Config {
}

object TestLibs {
private val androidxTestVersion = "1.2.0"
private val androidxTestVersion = "1.3.0-rc01"

val kotlinTestJunit = "org.jetbrains.kotlin:kotlin-test-junit:$kotlinVersion"
val androidxCore = "androidx.test:core:$androidxTestVersion"
val androidxRunner = "androidx.test:runner:$androidxTestVersion"
val androidxJunit = "androidx.test.ext:junit:1.1.1"
val androidxJunit = "androidx.test.ext:junit:1.1.2-rc01"
val androidxRules = "androidx.test:rules:$androidxTestVersion"
val androidxCoreKtx = "androidx.test:core-ktx:$androidxTestVersion"
val orchestrator = "androidx.test:orchestrator:$androidxTestVersion"
val androidxJunitKtx = "androidx.test.ext:junit-ktx:1.1.2-rc01"
val espressoCore = "androidx.test.espresso:espresso-core:3.3.0-rc01"
val robolectric = "org.robolectric:robolectric:4.3.1"
val mockitoKotlin = "com.nhaarman.mockitokotlin2:mockito-kotlin:2.2.0"
val awaitility = "org.awaitility:awaitility-kotlin:4.0.3"
val retrofit = "com.squareup.retrofit2:retrofit:2.9.0"
}

object QualityPlugins {
Expand Down
28 changes: 26 additions & 2 deletions sentry-sample/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,14 @@ android {
setAbiFilters(Config.Android.abiFilters)
ndkVersion = Config.Android.ndkVersion
}
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"


testInstrumentationRunnerArguments = mapOf("clearPackageData" to "true")

testOptions {
execution = "ANDROIDX_TEST_ORCHESTRATOR"
}
}

buildFeatures {
Expand Down Expand Up @@ -87,6 +95,10 @@ android {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}

kotlinOptions {
jvmTarget = JavaVersion.VERSION_1_8.toString()
}
}

dependencies {
Expand All @@ -101,8 +113,20 @@ dependencies {
// }

implementation(Config.Libs.appCompat)
compileOnly(Config.CompileOnly.jetbrainsAnnotations)

androidTestImplementation(Config.TestLibs.espressoCore)
androidTestImplementation(Config.TestLibs.androidxRunner)
androidTestImplementation(Config.TestLibs.androidxJunit)
androidTestImplementation(Config.TestLibs.androidxRules)
androidTestImplementation(Config.TestLibs.androidxCoreKtx)
androidTestImplementation(Config.TestLibs.androidxJunitKtx)
androidTestImplementation(Config.TestLibs.kotlinTestJunit)
androidTestImplementation(Config.TestLibs.orchestrator)
androidTestImplementation(Config.TestLibs.retrofit)

// debugging purpose
implementation(Config.Libs.timber)
debugImplementation(Config.Libs.leakCanary)
debugImplementation(Config.Libs.leakCanary) {
exclude(group = "org.jetbrains", module = "annotations")
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
package io.sentry.sample

import android.util.Log
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.action.ViewActions.click
import androidx.test.espresso.matcher.ViewMatchers.withId
import androidx.test.ext.junit.rules.activityScenarioRule
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest
import io.sentry.core.protocol.SentryId
import java.util.Timer
import java.util.concurrent.CountDownLatch
import kotlin.concurrent.schedule
import okhttp3.ResponseBody
import org.junit.Rule
import org.junit.runner.RunWith
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertNotEquals
import kotlin.test.assertTrue
//import kotlin.test.BeforeTest

@LargeTest
@RunWith(AndroidJUnit4::class)
class MainActivityTest {

@get:Rule
val rule = activityScenarioRule<MainActivity>()

// @BeforeTest
// fun beforeTest() {
// val token = System.getenv("TEST")
// }

@Test
fun mainActivityTest() {
onView(withId(R.id.send_message)).perform(click())
onView(withId(R.id.capture_exception)).perform(click())
onView(withId(R.id.breadcrumb)).perform(click())
onView(withId(R.id.native_capture)).perform(click())

// val retrofit = Retrofit.Builder()
// .baseUrl("https://sentry.io/")
// .client(OkHttpClient.Builder().addInterceptor { chain ->
// // token from https://sentry.io/settings/account/api/auth-tokens/
// val request = chain.request().newBuilder().addHeader("Authorization", "Bearer ${TOKEN}}").build()
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@bruno-garcia do you know if its possible to generate a token on demand? cus we can't read from a system env as this runs on the emulator.
also, it's not "nice" to leak the token on the code, Reverse engineering is too easy at this point.

Copy link
Member

Choose a reason for hiding this comment

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

How would we get the generated token?
I agree it's not ideal to embed the key in the apk. We should be able to provide an apk alone and test it out since the testing code is not part of the apk

// chain.proceed(request)
// }.build())
// .build()
// val service = retrofit.create(SentryService::class.java)

// val count = CountDownLatch(4)
rule.scenario.onActivity {
val ids = it.ids
assertEquals(4, ids.size)
// val apiCallback = ApiCallback(count)
ids.forEach { id ->
assertNotEquals(SentryId.EMPTY_ID, id)

// TODO: try 5 times with interval of 1 sec? event might not be processed yet
// service.getEvent(id).enqueue(apiCallback)
}
}

val count = CountDownLatch(1)

Timer(true).schedule(5000) {
count.countDown()
}

// might be possible to replace with https://developer.android.com/training/testing/espresso/idling-resource
// little waiting time for getting events processed
// count.await(1, TimeUnit.MINUTES)
count.await()
}

internal class ApiCallback(private val count: CountDownLatch) : Callback<ResponseBody> {

override fun onFailure(call: Call<ResponseBody>, t: Throwable) {
Log.e("Sentry", "error: ${t.message}", t)
throw t
}

override fun onResponse(call: Call<ResponseBody>, response: Response<ResponseBody>) {
// TODO: assert its content? right now ony checking if the event exist
if (!response.isSuccessful) {
Log.i("Sentry", response.message())
Log.e("Sentry", "error: ${response.errorBody()?.string()}")
Log.i("Sentry", "http code: ${response.code()}")
} else {
Log.i("Sentry", "success: ${response.body()?.string()}")
}
assertTrue(response.isSuccessful)
count.countDown()
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package io.sentry.sample

import okhttp3.ResponseBody
import retrofit2.Call
import retrofit2.http.GET
import retrofit2.http.Path

interface SentryService {
@GET("api/0/projects/sentry-test/android/events/{id}/")
fun getEvent(@Path("id") id: String): Call<ResponseBody>
}
5 changes: 4 additions & 1 deletion sentry-sample/src/main/cpp/native-sample.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,17 @@ JNIEXPORT void JNICALL Java_io_sentry_sample_NativeSample_crash(JNIEnv *env, jcl
*ptr += 1;
}

JNIEXPORT void JNICALL Java_io_sentry_sample_NativeSample_message(JNIEnv *env, jclass cls) {
JNIEXPORT jstring JNICALL Java_io_sentry_sample_NativeSample_message(JNIEnv *env, jclass cls) {
__android_log_print(ANDROID_LOG_WARN, TAG, "Sending message.");
sentry_value_t event = sentry_value_new_message_event(
/* level */ SENTRY_LEVEL_INFO,
/* logger */ "custom",
/* message */ "It works!"
);
sentry_capture_event(event);
sentry_value_t event_id = sentry_value_get_by_key(event, "event_id");
const char *uuid_str = sentry_value_as_string(event_id);
return env->NewStringUTF(uuid_str);
}

}
51 changes: 35 additions & 16 deletions sentry-sample/src/main/java/io/sentry/sample/MainActivity.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,64 +3,83 @@
import android.os.Bundle;
import androidx.appcompat.app.AppCompatActivity;
import io.sentry.core.Sentry;
import io.sentry.core.protocol.SentryId;
import io.sentry.core.protocol.User;
import io.sentry.sample.databinding.ActivityMainBinding;
import java.util.Collections;
import timber.log.Timber;
import java.util.ArrayList;
import java.util.List;
import org.jetbrains.annotations.TestOnly;

public class MainActivity extends AppCompatActivity {

private final List<SentryId> ids = new ArrayList<>();

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

ActivityMainBinding binding = ActivityMainBinding.inflate(getLayoutInflater());

Timber.i("Sentry.isEnabled() = %s", Sentry.isEnabled());

binding.crashFromJava.setOnClickListener(
view -> {
throw new RuntimeException("Uncaught Exception from Java.");
});

binding.sendMessage.setOnClickListener(view -> Sentry.captureMessage("Some message."));
binding.sendMessage.setOnClickListener(
view -> {
SentryId sentryId = Sentry.captureMessage("Some message.");
ids.add(sentryId);
});

binding.captureException.setOnClickListener(
view ->
Sentry.captureException(
new Exception(new Exception(new Exception("Some exception.")))));
view -> {
SentryId sentryId =
Sentry.captureException(
new Exception(new Exception(new Exception("Some exception."))));
ids.add(sentryId);
});

binding.breadcrumb.setOnClickListener(
view -> {
Sentry.addBreadcrumb("Breadcrumb");
Sentry.setExtra("extra", "extra");
Sentry.setFingerprint(Collections.singletonList("fingerprint"));
Sentry.setTransaction("transaction");
User user = new User();
user.setUsername("username");
Sentry.setUser(user);
Sentry.setTag("tag", "tag");
Sentry.captureException(new Exception("Some exception with scope."));
SentryId sentryId =
Sentry.captureException(new Exception("Some exception with scope and breadcrumbs."));
ids.add(sentryId);
});

binding.nativeCrash.setOnClickListener(view -> NativeSample.crash());

binding.nativeCapture.setOnClickListener(view -> NativeSample.message());
binding.nativeCapture.setOnClickListener(
view -> {
String id = NativeSample.message();
SentryId sentryId = new SentryId(id);
ids.add(sentryId);
});

binding.anr.setOnClickListener(
view -> {
// Try cause ANR by blocking for 2.5 seconds.
// By default the SDK sends an event if blocked by at least 4 seconds.
// The time was configurable (see manifest) to 1 second for demo purposes.
// Try cause ANR by blocking for 10 seconds.
// By default the SDK sends an event if blocked by at least 5 seconds.
// you must keep clicking on the UI, so OS will detect that the UI is not responding.
// NOTE: By default it doesn't raise if the debugger is attached. That can also be
// configured.
try {
Thread.sleep(2500);
Thread.sleep(10000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});

setContentView(binding.getRoot());
}

@TestOnly
public List<SentryId> getIds() {
return ids;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

import android.app.Application;
import android.os.StrictMode;
import timber.log.Timber;

// import io.sentry.android.core.SentryAndroid;

Expand All @@ -14,8 +13,6 @@ public void onCreate() {
strictMode();
super.onCreate();

Timber.plant(new Timber.DebugTree());

// Example how to initialize the SDK manually which allows access to SentryOptions callbacks.
// Make sure you disable the auto init via manifest meta-data: io.sentry.auto-init=false
// SentryAndroid.init(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
public class NativeSample {
public static native void crash();

public static native void message();
public static native String message();

static {
System.loadLibrary("native-sample");
Expand Down
Loading