diff --git a/app/build.gradle b/app/build.gradle index 6543b2f..0d2037e 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -10,16 +10,29 @@ buildscript { } android { - compileSdkVersion 30 + compileSdkVersion 31 dataBinding.enabled = true defaultConfig { applicationId "com.sample.browserstack.samplecalculator" minSdkVersion 15 - targetSdkVersion 30 + targetSdkVersion 31 versionCode 1 versionName "1.0" testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner' + // tell Gradle where our native CMake file lives + externalNativeBuild { + cmake { + // you can also restrict to specific ABIs here + abiFilters "arm64-v8a", "armeabi-v7a" + } + } + } + externalNativeBuild { + cmake { + path "src/main/cpp/CMakeLists.txt" + version "3.10.2" // or match your installed CMake version + } } buildTypes { release { @@ -38,10 +51,10 @@ android { dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) - implementation 'androidx.appcompat:appcompat:1.0.0' - implementation 'androidx.constraintlayout:constraintlayout:1.1.3' - androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.0' - androidTestImplementation 'androidx.test:rules:1.1.1' - androidTestImplementation 'androidx.test.ext:junit:1.1.1' - testImplementation 'junit:junit:4.12' -} \ No newline at end of file + implementation 'androidx.appcompat:appcompat:1.3.0' + implementation 'androidx.constraintlayout:constraintlayout:2.0.0' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' + androidTestImplementation 'androidx.test:rules:1.3.0' + androidTestImplementation 'androidx.test.ext:junit:1.1.3' + testImplementation 'junit:junit:4.13' +} diff --git a/app/src/androidTest/java/com/sample/browserstack/samplecalculator/CrashJavaButtonTest.java b/app/src/androidTest/java/com/sample/browserstack/samplecalculator/CrashJavaButtonTest.java new file mode 100644 index 0000000..5344887 --- /dev/null +++ b/app/src/androidTest/java/com/sample/browserstack/samplecalculator/CrashJavaButtonTest.java @@ -0,0 +1,27 @@ +package com.sample.browserstack.samplecalculator; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.rule.ActivityTestRule; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import static androidx.test.espresso.Espresso.onView; +import static androidx.test.espresso.action.ViewActions.click; +import static androidx.test.espresso.matcher.ViewMatchers.withId; + +@RunWith(AndroidJUnit4.class) +public class CrashJavaButtonTest { + + @Rule + public ActivityTestRule activityRule = + new ActivityTestRule<>(MainActivity.class); + + @Test + public void crashJavaAppUnderTest() { + // this will throw the RuntimeException in Java, + // test harness will catch it and the process will record the stack-trace + onView(withId(R.id.buttonCrashJava)).perform(click()); + } +} diff --git a/app/src/androidTest/java/com/sample/browserstack/samplecalculator/CrashViaButtonTest.java b/app/src/androidTest/java/com/sample/browserstack/samplecalculator/CrashViaButtonTest.java new file mode 100644 index 0000000..37dab7c --- /dev/null +++ b/app/src/androidTest/java/com/sample/browserstack/samplecalculator/CrashViaButtonTest.java @@ -0,0 +1,28 @@ +package com.sample.browserstack.samplecalculator; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.rule.ActivityTestRule; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import static androidx.test.espresso.Espresso.onView; +import static androidx.test.espresso.action.ViewActions.click; +import static androidx.test.espresso.matcher.ViewMatchers.withId; + +@RunWith(AndroidJUnit4.class) +public class CrashViaButtonTest { + + @Rule + public ActivityTestRule activityRule = + new ActivityTestRule<>(MainActivity.class); + + @Test + public void crashAppUnderTest() { + // Will invoke nativeCrash() and produce a real SIGSEGV + onView(withId(R.id.buttonCrash)).perform(click()); + + // (no further assertions—test will fail when the process dies) + } +} diff --git a/app/src/androidTest/java/com/sample/browserstack/samplecalculator/EnsureJavaCrash.java b/app/src/androidTest/java/com/sample/browserstack/samplecalculator/EnsureJavaCrash.java new file mode 100644 index 0000000..962f48a --- /dev/null +++ b/app/src/androidTest/java/com/sample/browserstack/samplecalculator/EnsureJavaCrash.java @@ -0,0 +1,27 @@ +package com.sample.browserstack.samplecalculator; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.rule.ActivityTestRule; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import static androidx.test.espresso.Espresso.onView; +import static androidx.test.espresso.action.ViewActions.click; +import static androidx.test.espresso.matcher.ViewMatchers.withId; + +@RunWith(AndroidJUnit4.class) +public class EnsureJavaCrash { + + @Rule + public ActivityTestRule activityRule = + new ActivityTestRule<>(MainActivity.class); + + @Test + public void crashJavaAppUnderTest() { + // this will throw the RuntimeException in Java, + // test harness will catch it and the process will record the stack-trace + onView(withId(R.id.buttonCrashJava)).perform(click()); + } +} diff --git a/app/src/androidTest/java/com/sample/browserstack/samplecalculator/EnsureSysCrash.java b/app/src/androidTest/java/com/sample/browserstack/samplecalculator/EnsureSysCrash.java new file mode 100644 index 0000000..f7582e1 --- /dev/null +++ b/app/src/androidTest/java/com/sample/browserstack/samplecalculator/EnsureSysCrash.java @@ -0,0 +1,28 @@ +package com.sample.browserstack.samplecalculator; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.rule.ActivityTestRule; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import static androidx.test.espresso.Espresso.onView; +import static androidx.test.espresso.action.ViewActions.click; +import static androidx.test.espresso.matcher.ViewMatchers.withId; + +@RunWith(AndroidJUnit4.class) +public class EnsureSysCrash { + + @Rule + public ActivityTestRule activityRule = + new ActivityTestRule<>(MainActivity.class); + + @Test + public void crashAppUnderTest() { + // Will invoke nativeCrash() and produce a real SIGSEGV + onView(withId(R.id.buttonCrash)).perform(click()); + + // (no further assertions—test will fail when the process dies) + } +} diff --git a/app/src/androidTest/java/com/sample/browserstack/samplecalculator/SysCrash.java b/app/src/androidTest/java/com/sample/browserstack/samplecalculator/SysCrash.java new file mode 100644 index 0000000..bf3a452 --- /dev/null +++ b/app/src/androidTest/java/com/sample/browserstack/samplecalculator/SysCrash.java @@ -0,0 +1,28 @@ +package com.sample.browserstack.samplecalculator; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.rule.ActivityTestRule; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import static androidx.test.espresso.Espresso.onView; +import static androidx.test.espresso.action.ViewActions.click; +import static androidx.test.espresso.matcher.ViewMatchers.withId; + +@RunWith(AndroidJUnit4.class) +public class SysCrash { + + @Rule + public ActivityTestRule activityRule = + new ActivityTestRule<>(MainActivity.class); + + @Test + public void crashAppUnderTest() { + // Will invoke nativeCrash() and produce a real SIGSEGV + onView(withId(R.id.buttonCrash)).perform(click()); + + // (no further assertions—test will fail when the process dies) + } +} diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 69a6e9f..5e96d57 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -9,8 +9,10 @@ android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" + android:requestLegacyExternalStorage="true" android:theme="@style/AppTheme"> - + diff --git a/app/src/main/cpp/CMakeLists.txt b/app/src/main/cpp/CMakeLists.txt new file mode 100644 index 0000000..397d990 --- /dev/null +++ b/app/src/main/cpp/CMakeLists.txt @@ -0,0 +1,9 @@ +cmake_minimum_required(VERSION 3.10.2) +project("crashlib") + +# Build a shared library called "crashlib" +add_library(crashlib SHARED crashlib.cpp) + +# Link against Android's log library if you ever want to log +find_library(log-lib log) +target_link_libraries(crashlib ${log-lib}) diff --git a/app/src/main/cpp/crashlib.cpp b/app/src/main/cpp/crashlib.cpp new file mode 100644 index 0000000..a34f26b --- /dev/null +++ b/app/src/main/cpp/crashlib.cpp @@ -0,0 +1,10 @@ +#include + +extern "C" +JNIEXPORT void JNICALL +Java_com_sample_browserstack_samplecalculator_MainActivity_nativeCrash( + JNIEnv* /* env */, jobject /* thiz */) { +// Force a write to address zero → produces SIGSEGV +volatile int* bad = nullptr; +*bad = 0; +} diff --git a/app/src/main/java/com/sample/browserstack/samplecalculator/MainActivity.java b/app/src/main/java/com/sample/browserstack/samplecalculator/MainActivity.java index e9e7331..8b73be9 100644 --- a/app/src/main/java/com/sample/browserstack/samplecalculator/MainActivity.java +++ b/app/src/main/java/com/sample/browserstack/samplecalculator/MainActivity.java @@ -11,6 +11,13 @@ public class MainActivity extends AppCompatActivity { + static { + // Load our native library at startup + System.loadLibrary("crashlib"); + } + // Native method declaration + private native void nativeCrash(); + private double firstNum = Double.NaN; private double secondNum; private boolean equalClicked = false; @@ -24,6 +31,25 @@ protected void onCreate(Bundle savedInstanceState) { binding = DataBindingUtil.setContentView(this, R.layout.activity_main); + + // Show the crash button and hook it into nativeCrash() + binding.buttonCrash.setVisibility(View.VISIBLE); + binding.buttonCrash.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + nativeCrash(); // <— this will SIGSEGV in native code + } + }); + + // 2) Java crash button + binding.buttonCrashJava.setVisibility(View.VISIBLE); + binding.buttonCrashJava.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + throw new RuntimeException("💥 Deliberate Java crash from test button"); + } + }); + binding.editText.setText(""); binding.buttonClear.setOnClickListener(new View.OnClickListener() { diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 6f36af3..f201ef0 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -29,6 +29,25 @@ android:maxLines="2" android:textSize="20sp" /> +