From 3ec06985c47758ec9cf8d33592349bd5137d5620 Mon Sep 17 00:00:00 2001 From: Manoel Aranda Neto <5731772+marandaneto@users.noreply.github.com> Date: Mon, 4 Oct 2021 14:49:43 +0200 Subject: [PATCH] Crashed last run (#1739) --- CHANGELOG.md | 1 + sentry/api/sentry.api | 14 ++++ sentry/src/main/java/io/sentry/Hub.java | 6 ++ .../src/main/java/io/sentry/HubAdapter.java | 5 ++ sentry/src/main/java/io/sentry/IHub.java | 13 +++ sentry/src/main/java/io/sentry/NoOpHub.java | 5 ++ sentry/src/main/java/io/sentry/Sentry.java | 14 ++++ .../io/sentry/SentryCrashLastRunState.java | 81 +++++++++++++++++++ .../java/io/sentry/cache/EnvelopeCache.java | 49 ++++++++++- sentry/src/test/java/io/sentry/HubTest.kt | 33 +++++++- .../io/sentry/SentryCrashLastRunStateTest.kt | 71 ++++++++++++++++ sentry/src/test/java/io/sentry/SentryTest.kt | 12 +++ .../java/io/sentry/cache/EnvelopeCacheTest.kt | 73 ++++++++++++++++- 13 files changed, 368 insertions(+), 9 deletions(-) create mode 100644 sentry/src/main/java/io/sentry/SentryCrashLastRunState.java create mode 100644 sentry/src/test/java/io/sentry/SentryCrashLastRunStateTest.kt diff --git a/CHANGELOG.md b/CHANGELOG.md index dc8a5861b8..2155a9f0f0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## Unreleased +* Feat: Add isCrashedLastRun support (#1739) * Fix: Handle exception if Context.registerReceiver throws (#1747) * Feat: Attach Java vendor and version to events and transactions (#1703) diff --git a/sentry/api/sentry.api b/sentry/api/sentry.api index fc20cf5d61..cae89377e1 100644 --- a/sentry/api/sentry.api +++ b/sentry/api/sentry.api @@ -128,6 +128,7 @@ public final class io/sentry/Hub : io/sentry/IHub { public fun getLastEventId ()Lio/sentry/protocol/SentryId; public fun getOptions ()Lio/sentry/SentryOptions; public fun getSpan ()Lio/sentry/ISpan; + public fun isCrashedLastRun ()Ljava/lang/Boolean; public fun isEnabled ()Z public fun popScope ()V public fun pushScope ()V @@ -168,6 +169,7 @@ public final class io/sentry/HubAdapter : io/sentry/IHub { public fun getLastEventId ()Lio/sentry/protocol/SentryId; public fun getOptions ()Lio/sentry/SentryOptions; public fun getSpan ()Lio/sentry/ISpan; + public fun isCrashedLastRun ()Ljava/lang/Boolean; public fun isEnabled ()Z public fun popScope ()V public fun pushScope ()V @@ -224,6 +226,7 @@ public abstract interface class io/sentry/IHub { public abstract fun getLastEventId ()Lio/sentry/protocol/SentryId; public abstract fun getOptions ()Lio/sentry/SentryOptions; public abstract fun getSpan ()Lio/sentry/ISpan; + public abstract fun isCrashedLastRun ()Ljava/lang/Boolean; public abstract fun isEnabled ()Z public abstract fun popScope ()V public abstract fun pushScope ()V @@ -384,6 +387,7 @@ public final class io/sentry/NoOpHub : io/sentry/IHub { public fun getLastEventId ()Lio/sentry/protocol/SentryId; public fun getOptions ()Lio/sentry/SentryOptions; public fun getSpan ()Lio/sentry/ISpan; + public fun isCrashedLastRun ()Ljava/lang/Boolean; public fun isEnabled ()Z public fun popScope ()V public fun pushScope ()V @@ -606,6 +610,7 @@ public final class io/sentry/Sentry { public static fun init (Lio/sentry/Sentry$OptionsConfiguration;Z)V public static fun init (Lio/sentry/SentryOptions;)V public static fun init (Ljava/lang/String;)V + public static fun isCrashedLastRun ()Ljava/lang/Boolean; public static fun isEnabled ()Z public static fun popScope ()V public static fun pushScope ()V @@ -692,6 +697,13 @@ public final class io/sentry/SentryClient : io/sentry/ISentryClient { public fun isEnabled ()Z } +public final class io/sentry/SentryCrashLastRunState { + public static fun getInstance ()Lio/sentry/SentryCrashLastRunState; + public fun isCrashedLastRun (Ljava/lang/String;Z)Ljava/lang/Boolean; + public fun reset ()V + public fun setCrashedLastRun (Z)V +} + public final class io/sentry/SentryEnvelope { public fun (Lio/sentry/SentryEnvelopeHeader;Ljava/lang/Iterable;)V public fun (Lio/sentry/protocol/SentryId;Lio/sentry/protocol/SdkVersion;Lio/sentry/SentryEnvelopeItem;)V @@ -1327,6 +1339,8 @@ public final class io/sentry/adapters/TimeZoneSerializerAdapter : com/google/gso } public final class io/sentry/cache/EnvelopeCache : io/sentry/cache/IEnvelopeCache { + public static final field CRASH_MARKER_FILE Ljava/lang/String; + public static final field NATIVE_CRASH_MARKER_FILE Ljava/lang/String; public static final field PREFIX_CURRENT_SESSION_FILE Ljava/lang/String; public static final field SUFFIX_ENVELOPE_FILE Ljava/lang/String; protected static final field UTF_8 Ljava/nio/charset/Charset; diff --git a/sentry/src/main/java/io/sentry/Hub.java b/sentry/src/main/java/io/sentry/Hub.java index c055bc1598..3e534dfca5 100644 --- a/sentry/src/main/java/io/sentry/Hub.java +++ b/sentry/src/main/java/io/sentry/Hub.java @@ -440,6 +440,12 @@ public void pushScope() { return this.stack.peek().getOptions(); } + @Override + public @Nullable Boolean isCrashedLastRun() { + return SentryCrashLastRunState.getInstance() + .isCrashedLastRun(options.getCacheDirPath(), !options.isEnableAutoSessionTracking()); + } + @Override public void popScope() { if (!isEnabled()) { diff --git a/sentry/src/main/java/io/sentry/HubAdapter.java b/sentry/src/main/java/io/sentry/HubAdapter.java index 941489fa08..b0fd237fed 100644 --- a/sentry/src/main/java/io/sentry/HubAdapter.java +++ b/sentry/src/main/java/io/sentry/HubAdapter.java @@ -228,4 +228,9 @@ public void setSpanContext( public @NotNull SentryOptions getOptions() { return Sentry.getCurrentHub().getOptions(); } + + @Override + public @Nullable Boolean isCrashedLastRun() { + return Sentry.isCrashedLastRun(); + } } diff --git a/sentry/src/main/java/io/sentry/IHub.java b/sentry/src/main/java/io/sentry/IHub.java index 8a42125938..78ba3751e1 100644 --- a/sentry/src/main/java/io/sentry/IHub.java +++ b/sentry/src/main/java/io/sentry/IHub.java @@ -485,4 +485,17 @@ void setSpanContext( */ @NotNull SentryOptions getOptions(); + + /** + * Returns if the App has crashed (Process has terminated) during the last run. It only returns + * true or false if offline caching {{@link SentryOptions#getCacheDirPath()} } is set with a valid + * dir. + * + *

If the call to this method is early in the App lifecycle and the SDK could not check if the + * App has crashed in the background, the check is gonna do IO in the calling thread. + * + * @return true if App has crashed, false otherwise, and null if not evaluated yet + */ + @Nullable + Boolean isCrashedLastRun(); } diff --git a/sentry/src/main/java/io/sentry/NoOpHub.java b/sentry/src/main/java/io/sentry/NoOpHub.java index a5147f289a..728e6f5b63 100644 --- a/sentry/src/main/java/io/sentry/NoOpHub.java +++ b/sentry/src/main/java/io/sentry/NoOpHub.java @@ -177,4 +177,9 @@ public void setSpanContext( public @NotNull SentryOptions getOptions() { return emptyOptions; } + + @Override + public @Nullable Boolean isCrashedLastRun() { + return null; + } } diff --git a/sentry/src/main/java/io/sentry/Sentry.java b/sentry/src/main/java/io/sentry/Sentry.java index 6a779f8c18..e4f1cb64e7 100644 --- a/sentry/src/main/java/io/sentry/Sentry.java +++ b/sentry/src/main/java/io/sentry/Sentry.java @@ -699,6 +699,20 @@ public static void endSession() { return getCurrentHub().getSpan(); } + /** + * Returns if the App has crashed (Process has terminated) during the last run. It only returns + * true or false if offline caching {{@link SentryOptions#getCacheDirPath()} } is set with a valid + * dir. + * + *

If the call to this method is early in the App lifecycle and the SDK could not check if the + * App has crashed in the background, the check is gonna do IO in the calling thread. + * + * @return true if App has crashed, false otherwise, and null if not evaluated yet + */ + public static @Nullable Boolean isCrashedLastRun() { + return getCurrentHub().isCrashedLastRun(); + } + /** * Configuration options callback * diff --git a/sentry/src/main/java/io/sentry/SentryCrashLastRunState.java b/sentry/src/main/java/io/sentry/SentryCrashLastRunState.java new file mode 100644 index 0000000000..97c2b2d207 --- /dev/null +++ b/sentry/src/main/java/io/sentry/SentryCrashLastRunState.java @@ -0,0 +1,81 @@ +package io.sentry; + +import io.sentry.cache.EnvelopeCache; +import java.io.File; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.TestOnly; + +@ApiStatus.Internal +public final class SentryCrashLastRunState { + + private static final SentryCrashLastRunState INSTANCE = new SentryCrashLastRunState(); + + private boolean readCrashedLastRun; + private @Nullable Boolean crashedLastRun; + + private final @NotNull Object crashedLastRunLock = new Object(); + + private SentryCrashLastRunState() {} + + public static SentryCrashLastRunState getInstance() { + return INSTANCE; + } + + public @Nullable Boolean isCrashedLastRun( + final @Nullable String cacheDirPath, final boolean deleteFile) { + synchronized (crashedLastRunLock) { + if (readCrashedLastRun) { + return crashedLastRun; + } + + if (cacheDirPath == null) { + return null; + } + readCrashedLastRun = true; + + final File javaMarker = new File(cacheDirPath, EnvelopeCache.CRASH_MARKER_FILE); + final File nativeMarker = new File(cacheDirPath, EnvelopeCache.NATIVE_CRASH_MARKER_FILE); + boolean exists = false; + try { + if (javaMarker.exists()) { + exists = true; + + javaMarker.delete(); + } else if (nativeMarker.exists()) { + exists = true; + // only delete if release health is disabled, otherwise the session crashed by the native + // side won't be marked as crashed correctly. + if (deleteFile) { + nativeMarker.delete(); + } + } + } catch (Exception e) { + // ignore + } + + crashedLastRun = exists; + } + + return crashedLastRun; + } + + public void setCrashedLastRun(final boolean crashedLastRun) { + synchronized (crashedLastRunLock) { + if (!readCrashedLastRun) { + this.crashedLastRun = crashedLastRun; + // mark readCrashedLastRun as true since its being set directly + readCrashedLastRun = true; + } + } + } + + @TestOnly + public void reset() { + synchronized (crashedLastRunLock) { + readCrashedLastRun = false; + crashedLastRun = null; + } + } +} diff --git a/sentry/src/main/java/io/sentry/cache/EnvelopeCache.java b/sentry/src/main/java/io/sentry/cache/EnvelopeCache.java index 86160c1b2a..fa2da9829b 100644 --- a/sentry/src/main/java/io/sentry/cache/EnvelopeCache.java +++ b/sentry/src/main/java/io/sentry/cache/EnvelopeCache.java @@ -7,12 +7,14 @@ import static java.lang.String.format; import io.sentry.DateUtils; +import io.sentry.SentryCrashLastRunState; import io.sentry.SentryEnvelope; import io.sentry.SentryEnvelopeItem; import io.sentry.SentryItemType; import io.sentry.SentryLevel; import io.sentry.SentryOptions; import io.sentry.Session; +import io.sentry.hints.DiskFlushNotification; import io.sentry.hints.SessionEnd; import io.sentry.hints.SessionStart; import io.sentry.transport.NoOpEnvelopeCache; @@ -51,7 +53,8 @@ public final class EnvelopeCache extends CacheStrategy implements IEnvelopeCache public static final String PREFIX_CURRENT_SESSION_FILE = "session"; static final String SUFFIX_CURRENT_SESSION_FILE = ".json"; - static final String CRASH_MARKER_FILE = ".sentry-native/last_crash"; + public static final String CRASH_MARKER_FILE = "last_crash"; + public static final String NATIVE_CRASH_MARKER_FILE = ".sentry-native/" + CRASH_MARKER_FILE; private final @NotNull Map fileNameMap = new WeakHashMap<>(); @@ -88,6 +91,7 @@ public void store(final @NotNull SentryEnvelope envelope, final @Nullable Object } if (hint instanceof SessionStart) { + boolean crashedLastRun = false; // TODO: should we move this to AppLifecycleIntegration? and do on SDK init? but it's too much // on main-thread @@ -107,7 +111,8 @@ public void store(final @NotNull SentryEnvelope envelope, final @Nullable Object "Stream from path %s resulted in a null envelope.", currentSessionFile.getAbsolutePath()); } else { - final File crashMarkerFile = new File(options.getCacheDirPath(), CRASH_MARKER_FILE); + final File crashMarkerFile = + new File(options.getCacheDirPath(), NATIVE_CRASH_MARKER_FILE); Date timestamp = null; if (crashMarkerFile.exists()) { options @@ -115,6 +120,8 @@ public void store(final @NotNull SentryEnvelope envelope, final @Nullable Object .log(INFO, "Crash marker file exists, last Session is gonna be Crashed."); timestamp = getTimestampFromCrashMarkerFile(crashMarkerFile); + + crashedLastRun = true; if (!crashMarkerFile.delete()) { options .getLogger() @@ -146,6 +153,28 @@ public void store(final @NotNull SentryEnvelope envelope, final @Nullable Object } } updateCurrentSession(currentSessionFile, envelope); + + // check java marker file if the native marker isnt there + if (!crashedLastRun) { + final File javaCrashMarkerFile = new File(options.getCacheDirPath(), CRASH_MARKER_FILE); + if (javaCrashMarkerFile.exists()) { + options + .getLogger() + .log(INFO, "Crash marker file exists, crashedLastRun will return true."); + + crashedLastRun = true; + if (!javaCrashMarkerFile.delete()) { + options + .getLogger() + .log( + ERROR, + "Failed to delete the crash marker file. %s.", + javaCrashMarkerFile.getAbsolutePath()); + } + } + } + + SentryCrashLastRunState.getInstance().setCrashedLastRun(crashedLastRun); } // TODO: probably we need to update the current session file for session updates to because of @@ -167,6 +196,22 @@ public void store(final @NotNull SentryEnvelope envelope, final @Nullable Object } writeEnvelopeToDisk(envelopeFile, envelope); + + // write file to the disk when its about to crash so crashedLastRun can be marked on restart + if (hint instanceof DiskFlushNotification) { + writeCrashMarkerFile(); + } + } + + private void writeCrashMarkerFile() { + final File crashMarkerFile = new File(options.getCacheDirPath(), CRASH_MARKER_FILE); + try (final OutputStream outputStream = new FileOutputStream(crashMarkerFile)) { + final String timestamp = DateUtils.getTimestamp(DateUtils.getCurrentDateTime()); + outputStream.write(timestamp.getBytes(UTF_8)); + outputStream.flush(); + } catch (Exception e) { + options.getLogger().log(ERROR, "Error writing the crash marker file to the disk", e); + } } /** diff --git a/sentry/src/test/java/io/sentry/HubTest.kt b/sentry/src/test/java/io/sentry/HubTest.kt index 3c9b6d6e39..52dfa9b9bd 100644 --- a/sentry/src/test/java/io/sentry/HubTest.kt +++ b/sentry/src/test/java/io/sentry/HubTest.kt @@ -14,6 +14,7 @@ import com.nhaarman.mockitokotlin2.times import com.nhaarman.mockitokotlin2.verify import com.nhaarman.mockitokotlin2.verifyNoMoreInteractions import com.nhaarman.mockitokotlin2.whenever +import io.sentry.cache.EnvelopeCache import io.sentry.hints.SessionEndHint import io.sentry.hints.SessionStartHint import io.sentry.protocol.SentryId @@ -1187,9 +1188,9 @@ class HubTest { @Test fun `when startTransaction and no tracing sampling is configured, event is not sampled`() { - val hub = generateHub(Sentry.OptionsConfiguration { + val hub = generateHub { it.tracesSampleRate = 0.0 - }) + } val transaction = hub.startTransaction("name", "op") assertFalse(transaction.isSampled!!) @@ -1221,10 +1222,10 @@ class HubTest { @Test fun `when tracesSampleRate and tracesSampler are not set on SentryOptions, startTransaction returns NoOp`() { - val hub = generateHub(Sentry.OptionsConfiguration { + val hub = generateHub { it.tracesSampleRate = null it.tracesSampler = null - }) + } val transaction = hub.startTransaction(TransactionContext("name", "op", true)) assertTrue(transaction is NoOpTransaction) } @@ -1297,6 +1298,30 @@ class HubTest { } // endregion + @Test + fun `isCrashedLastRun does not delete native marker if auto session is enabled`() { + val nativeMarker = File(file.absolutePath, EnvelopeCache.NATIVE_CRASH_MARKER_FILE) + nativeMarker.mkdirs() + nativeMarker.createNewFile() + val hub = generateHub() as Hub + + assertTrue(hub.isCrashedLastRun!!) + assertTrue(nativeMarker.exists()) + } + + @Test + fun `isCrashedLastRun deletes the native marker if auto session is disabled`() { + val nativeMarker = File(file.absolutePath, EnvelopeCache.NATIVE_CRASH_MARKER_FILE) + nativeMarker.mkdirs() + nativeMarker.createNewFile() + val hub = generateHub { + it.isEnableAutoSessionTracking = false + } + + assertTrue(hub.isCrashedLastRun!!) + assertFalse(nativeMarker.exists()) + } + private fun generateHub(optionsConfiguration: Sentry.OptionsConfiguration? = null): IHub { val options = SentryOptions().apply { dsn = "https://key@sentry.io/proj" diff --git a/sentry/src/test/java/io/sentry/SentryCrashLastRunStateTest.kt b/sentry/src/test/java/io/sentry/SentryCrashLastRunStateTest.kt new file mode 100644 index 0000000000..d3b561b068 --- /dev/null +++ b/sentry/src/test/java/io/sentry/SentryCrashLastRunStateTest.kt @@ -0,0 +1,71 @@ +package io.sentry + +import io.sentry.cache.EnvelopeCache.CRASH_MARKER_FILE +import io.sentry.cache.EnvelopeCache.NATIVE_CRASH_MARKER_FILE +import java.io.File +import java.nio.file.Files +import kotlin.test.AfterTest +import kotlin.test.BeforeTest +import kotlin.test.Test +import kotlin.test.assertFalse +import kotlin.test.assertNull +import kotlin.test.assertTrue + +class SentryCrashLastRunStateTest { + + private val sentryCrashLastRunState = SentryCrashLastRunState.getInstance() + + private lateinit var file: File + + @BeforeTest + fun `before test`() { + file = Files.createTempDirectory("sentry-disk-cache-test").toAbsolutePath().toFile() + sentryCrashLastRunState.reset() + } + + @AfterTest + fun shutdown() { + file.deleteRecursively() + } + + @Test + fun `reset returns null if no cacheDirPath given`() { + assertNull(sentryCrashLastRunState.isCrashedLastRun(null, false)) + } + + @Test + fun `returns true if Java Marker exists`() { + val javaMarker = File(file.absolutePath, CRASH_MARKER_FILE) + javaMarker.createNewFile() + + assertTrue(sentryCrashLastRunState.isCrashedLastRun(file.absolutePath, false)!!) + assertFalse(javaMarker.exists()) + } + + @Test + fun `returns true if Native Marker exists`() { + val nativeMarker = File(file.absolutePath, NATIVE_CRASH_MARKER_FILE) + nativeMarker.mkdirs() + nativeMarker.createNewFile() + + assertTrue(sentryCrashLastRunState.isCrashedLastRun(file.absolutePath, false)!!) + assertTrue(nativeMarker.exists()) + } + + @Test + fun `returns true if Native Marker exists and should delete the file`() { + val nativeMarker = File(file.absolutePath, NATIVE_CRASH_MARKER_FILE) + nativeMarker.mkdirs() + nativeMarker.createNewFile() + + assertTrue(sentryCrashLastRunState.isCrashedLastRun(file.absolutePath, true)!!) + assertFalse(nativeMarker.exists()) + } + + @Test + fun `returns cached value if already checked`() { + sentryCrashLastRunState.setCrashedLastRun(true) + + assertTrue(sentryCrashLastRunState.isCrashedLastRun(file.absolutePath, false)!!) + } +} diff --git a/sentry/src/test/java/io/sentry/SentryTest.kt b/sentry/src/test/java/io/sentry/SentryTest.kt index 10137626be..07d2af0d4b 100644 --- a/sentry/src/test/java/io/sentry/SentryTest.kt +++ b/sentry/src/test/java/io/sentry/SentryTest.kt @@ -24,6 +24,7 @@ class SentryTest { @AfterTest fun beforeTest() { Sentry.close() + SentryCrashLastRunState.getInstance().reset() } @Test @@ -166,6 +167,17 @@ class SentryTest { assertEquals("desc", transaction.description) } + @Test + fun `isCrashedLastRun returns true if crashedLastRun is set`() { + Sentry.init { + it.dsn = dsn + } + + SentryCrashLastRunState.getInstance().setCrashedLastRun(true) + + assertTrue(Sentry.isCrashedLastRun()!!) + } + private fun getTempPath(): String { val tempFile = Files.createTempDirectory("cache").toFile() tempFile.delete() diff --git a/sentry/src/test/java/io/sentry/cache/EnvelopeCacheTest.kt b/sentry/src/test/java/io/sentry/cache/EnvelopeCacheTest.kt index e0b73196dd..2352fad055 100644 --- a/sentry/src/test/java/io/sentry/cache/EnvelopeCacheTest.kt +++ b/sentry/src/test/java/io/sentry/cache/EnvelopeCacheTest.kt @@ -7,18 +7,22 @@ import com.nhaarman.mockitokotlin2.verify import com.nhaarman.mockitokotlin2.whenever import io.sentry.ILogger import io.sentry.ISerializer +import io.sentry.SentryCrashLastRunState import io.sentry.SentryEnvelope +import io.sentry.SentryEvent import io.sentry.SentryLevel import io.sentry.SentryOptions import io.sentry.Session import io.sentry.cache.EnvelopeCache.PREFIX_CURRENT_SESSION_FILE import io.sentry.cache.EnvelopeCache.SUFFIX_CURRENT_SESSION_FILE +import io.sentry.hints.DiskFlushNotification import io.sentry.hints.SessionEndHint import io.sentry.hints.SessionStartHint import io.sentry.protocol.User import java.io.File import java.nio.file.Files import java.nio.file.Path +import kotlin.test.BeforeTest import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertFalse @@ -27,7 +31,6 @@ import kotlin.test.assertTrue class EnvelopeCacheTest { private class Fixture { - val maxSize = 5 val dir: Path = Files.createTempDirectory("sentry-session-cache-test") val serializer = mock() val options = SentryOptions() @@ -50,6 +53,11 @@ class EnvelopeCacheTest { private val fixture = Fixture() + @BeforeTest + fun `set up`() { + SentryCrashLastRunState.getInstance().reset() + } + @Test fun `stores envelopes`() { val cache = fixture.getSUT() @@ -146,7 +154,7 @@ class EnvelopeCacheTest { val cache = fixture.getSUT() val file = File(fixture.options.cacheDirPath!!) - val markerFile = File(fixture.options.cacheDirPath!!, EnvelopeCache.CRASH_MARKER_FILE) + val markerFile = File(fixture.options.cacheDirPath!!, EnvelopeCache.NATIVE_CRASH_MARKER_FILE) markerFile.mkdirs() assertTrue(markerFile.exists()) @@ -165,7 +173,7 @@ class EnvelopeCacheTest { fun `when session start, current file already exist and crash marker file exist, end session with given timestamp`() { val cache = fixture.getSUT() val file = File(fixture.options.cacheDirPath!!) - val markerFile = File(fixture.options.cacheDirPath!!, EnvelopeCache.CRASH_MARKER_FILE) + val markerFile = File(fixture.options.cacheDirPath!!, EnvelopeCache.NATIVE_CRASH_MARKER_FILE) File(fixture.options.cacheDirPath!!, ".sentry-native").mkdirs() markerFile.createNewFile() val date = "2020-02-07T14:16:00.000Z" @@ -181,6 +189,65 @@ class EnvelopeCacheTest { File(fixture.options.cacheDirPath!!).deleteRecursively() } + @Test + fun `when native crash marker file exist, mark isCrashedLastRun`() { + val cache = fixture.getSUT() + + val file = File(fixture.options.cacheDirPath!!) + val markerFile = File(fixture.options.cacheDirPath!!, EnvelopeCache.NATIVE_CRASH_MARKER_FILE) + markerFile.mkdirs() + assertTrue(markerFile.exists()) + + val envelope = SentryEnvelope.from(fixture.serializer, createSession(), null) + cache.store(envelope, SessionStartHint()) + + val newEnvelope = SentryEnvelope.from(fixture.serializer, createSession(), null) + + // since the first store call would set as readCrashedLastRun=true + SentryCrashLastRunState.getInstance().reset() + + cache.store(newEnvelope, SessionStartHint()) + verify(fixture.logger).log(eq(SentryLevel.INFO), eq("Crash marker file exists, last Session is gonna be Crashed.")) + assertFalse(markerFile.exists()) + file.deleteRecursively() + + // passing empty string since readCrashedLastRun is already set + assertTrue(SentryCrashLastRunState.getInstance().isCrashedLastRun("", false)!!) + } + + @Test + fun `when java crash marker file exist, mark isCrashedLastRun`() { + val cache = fixture.getSUT() + + val markerFile = File(fixture.options.cacheDirPath!!, EnvelopeCache.CRASH_MARKER_FILE) + markerFile.mkdirs() + assertTrue(markerFile.exists()) + + val envelope = SentryEnvelope.from(fixture.serializer, createSession(), null) + cache.store(envelope, SessionStartHint()) + + // passing empty string since readCrashedLastRun is already set + assertTrue(SentryCrashLastRunState.getInstance().isCrashedLastRun("", false)!!) + assertFalse(markerFile.exists()) + } + + @Test + fun `write java marker file to disk when disk flush hint`() { + val cache = fixture.getSUT() + + val markerFile = File(fixture.options.cacheDirPath!!, EnvelopeCache.CRASH_MARKER_FILE) + assertFalse(markerFile.exists()) + + val envelope = SentryEnvelope.from(fixture.serializer, SentryEvent(), null) + cache.store(envelope, DiskFlushHint()) + + assertTrue(markerFile.exists()) + } + + internal class DiskFlushHint : DiskFlushNotification { + override fun markFlushed() {} + } + private fun createSession(): Session { return Session("dis", User(), "env", "rel") }