diff --git a/fingerprint/build.gradle.kts b/fingerprint/build.gradle.kts
index 1b02384a..f14ad1e7 100644
--- a/fingerprint/build.gradle.kts
+++ b/fingerprint/build.gradle.kts
@@ -84,6 +84,12 @@ android {
}
}
+androidComponents {
+ onVariants {
+ it.androidTest?.packaging?.resources?.excludes?.add("META-INF/*")
+ }
+}
+
tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile::class.java) {
if (!this.name.contains("Test")) {
kotlinOptions.freeCompilerArgs += "-Xexplicit-api=warning"
@@ -94,7 +100,9 @@ dependencies {
implementation("org.jetbrains.kotlin:kotlin-stdlib:${Constants.kotlinVersion}")
implementation("androidx.appcompat:appcompat:1.6.1")
testImplementation("junit:junit:4.13.2")
- testImplementation("io.mockk:mockk:1.12.7")
+ testImplementation("io.mockk:mockk:1.12.8")
+ androidTestImplementation("io.mockk:mockk:1.12.8")
+ androidTestImplementation ("io.mockk:mockk-android:1.12.8")
androidTestImplementation("androidx.test.ext:junit-ktx:1.1.5")
androidTestImplementation("androidx.test:runner:1.5.2")
}
diff --git a/fingerprint/src/androidTest/AndroidManifest.xml b/fingerprint/src/androidTest/AndroidManifest.xml
new file mode 100644
index 00000000..83377617
--- /dev/null
+++ b/fingerprint/src/androidTest/AndroidManifest.xml
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/fingerprint/src/androidTest/java/com/fingerprintjs/android/playground/InstrumentedTests.kt b/fingerprint/src/androidTest/java/com/fingerprintjs/android/fingerprint/InstrumentedTests.kt
similarity index 86%
rename from fingerprint/src/androidTest/java/com/fingerprintjs/android/playground/InstrumentedTests.kt
rename to fingerprint/src/androidTest/java/com/fingerprintjs/android/fingerprint/InstrumentedTests.kt
index 29e5c0fd..6f118d9a 100644
--- a/fingerprint/src/androidTest/java/com/fingerprintjs/android/playground/InstrumentedTests.kt
+++ b/fingerprint/src/androidTest/java/com/fingerprintjs/android/fingerprint/InstrumentedTests.kt
@@ -1,17 +1,18 @@
-package com.fingerprintjs.android.playground
+package com.fingerprintjs.android.fingerprint
import android.content.Context
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
-import com.fingerprintjs.android.fingerprint.Configuration
-import com.fingerprintjs.android.fingerprint.Fingerprinter
-import com.fingerprintjs.android.fingerprint.FingerprinterFactory
import com.fingerprintjs.android.fingerprint.signal_providers.StabilityLevel
import com.fingerprintjs.android.fingerprint.tools.FingerprintingLegacySchemeSupportExtensions.getDeviceStateSignals
import com.fingerprintjs.android.fingerprint.tools.FingerprintingLegacySchemeSupportExtensions.getHardwareSignals
import com.fingerprintjs.android.fingerprint.tools.FingerprintingLegacySchemeSupportExtensions.getInstalledAppsSignals
import com.fingerprintjs.android.fingerprint.tools.FingerprintingLegacySchemeSupportExtensions.getOsBuildSignals
-import com.fingerprintjs.android.playground.utils.callbackToSync
+import com.fingerprintjs.android.fingerprint.tools.threading.safe.Safe
+import com.fingerprintjs.android.fingerprint.utils.callbackToSync
+import io.mockk.every
+import io.mockk.mockkObject
+import junit.framework.TestCase
import org.junit.Assert.*
import org.junit.Test
import org.junit.runner.RunWith
@@ -182,4 +183,25 @@ class InstrumentedTests {
}
}
}
+
+ @Test
+ fun nestedSafeCallNeverHappens() {
+ if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.P)
+ return // object mocks are not supported
+
+ var logCalled = false
+ mockkObject(Safe)
+ every { Safe.logIllegalSafeWithTimeoutUsage() } answers { logCalled = true }
+
+ Fingerprinter.Version.values().forEach { version ->
+ val fingerprinter = FingerprinterFactory.create(context)
+ val deviceId = callbackToSync { fingerprinter.getDeviceId(version = version) { emit(it) } }
+ StabilityLevel.values().forEach { stabilityLevel ->
+ val fingerprint = callbackToSync { fingerprinter.getFingerprint(version, stabilityLevel) { emit(it) } }
+ }
+ val fingerprintingSignalsProvider = fingerprinter.getFingerprintingSignalsProvider()!!
+ }
+
+ TestCase.assertEquals(false, logCalled)
+ }
}
diff --git a/fingerprint/src/androidTest/java/com/fingerprintjs/android/playground/utils/CallbackUtils.kt b/fingerprint/src/androidTest/java/com/fingerprintjs/android/fingerprint/utils/CallbackUtils.kt
similarity index 93%
rename from fingerprint/src/androidTest/java/com/fingerprintjs/android/playground/utils/CallbackUtils.kt
rename to fingerprint/src/androidTest/java/com/fingerprintjs/android/fingerprint/utils/CallbackUtils.kt
index b33f41f8..98540ef2 100644
--- a/fingerprint/src/androidTest/java/com/fingerprintjs/android/playground/utils/CallbackUtils.kt
+++ b/fingerprint/src/androidTest/java/com/fingerprintjs/android/fingerprint/utils/CallbackUtils.kt
@@ -1,4 +1,4 @@
-package com.fingerprintjs.android.playground.utils
+package com.fingerprintjs.android.fingerprint.utils
import java.util.concurrent.CountDownLatch
diff --git a/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/Fingerprinter.kt b/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/Fingerprinter.kt
index 3b80e580..b3611e6d 100644
--- a/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/Fingerprinter.kt
+++ b/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/Fingerprinter.kt
@@ -19,7 +19,6 @@ import com.fingerprintjs.android.fingerprint.tools.hashers.MurMur3x64x128Hasher
import com.fingerprintjs.android.fingerprint.tools.logs.Logger
import com.fingerprintjs.android.fingerprint.tools.logs.ePleaseReport
import com.fingerprintjs.android.fingerprint.tools.threading.runOnAnotherThread
-import com.fingerprintjs.android.fingerprint.tools.threading.safe.Safe
import com.fingerprintjs.android.fingerprint.tools.threading.safe.safe
@@ -32,7 +31,7 @@ public class Fingerprinter internal constructor(
// does not ruin an entire operation.
// another option could be to not use timeout at all, since we have a lot of timeouts
// deep inside.
- safe(timeoutMs = Safe.timeoutLong) { implFactory() }
+ safe { implFactory() }
}
/**
diff --git a/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/FingerprinterFactory.kt b/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/FingerprinterFactory.kt
index 779f8efa..4f31d66e 100644
--- a/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/FingerprinterFactory.kt
+++ b/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/FingerprinterFactory.kt
@@ -27,7 +27,7 @@ import com.fingerprintjs.android.fingerprint.signal_providers.installed_apps.Ins
import com.fingerprintjs.android.fingerprint.signal_providers.os_build.OsBuildSignalGroupProvider
import com.fingerprintjs.android.fingerprint.tools.hashers.Hasher
import com.fingerprintjs.android.fingerprint.tools.hashers.MurMur3x64x128Hasher
-import com.fingerprintjs.android.fingerprint.tools.threading.safe.safe
+import com.fingerprintjs.android.fingerprint.tools.threading.safe.safeWithTimeout
/**
@@ -191,9 +191,9 @@ public object FingerprinterFactory {
private fun createMemoryInfoProvider(context: Context): MemInfoProvider {
return MemInfoProviderImpl(
- activityManager = safe { context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager }.getOrNull(),
- internalStorageStats = safe { StatFs(Environment.getRootDirectory()!!.absolutePath!!) }.getOrNull(),
- externalStorageStats = safe {
+ activityManager = safeWithTimeout { context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager }.getOrNull(),
+ internalStorageStats = safeWithTimeout { StatFs(Environment.getRootDirectory()!!.absolutePath!!) }.getOrNull(),
+ externalStorageStats = safeWithTimeout {
context.getExternalFilesDir(null)
?.takeIf { it.canRead() }
?.let { StatFs(it.absolutePath!!) }!!
@@ -204,51 +204,51 @@ public object FingerprinterFactory {
private fun createOsBuildInfoProvider() = OsBuildInfoProviderImpl()
private fun createGsfIdProvider(context: Context) = GsfIdProvider(
- safe { context.contentResolver!! }.getOrDefault(null)
+ safeWithTimeout { context.contentResolver!! }.getOrDefault(null)
)
private fun createMediaDrmProvider() = MediaDrmIdProvider()
private fun createAndroidIdProvider(context: Context) = AndroidIdProvider(
- safe { context.contentResolver!! }.getOrDefault(null)
+ safeWithTimeout { context.contentResolver!! }.getOrDefault(null)
)
private fun createSensorDataSource(context: Context) = SensorDataSourceImpl(
- safe { context.getSystemService(Context.SENSOR_SERVICE) as SensorManager }.getOrNull()
+ safeWithTimeout { context.getSystemService(Context.SENSOR_SERVICE) as SensorManager }.getOrNull()
)
private fun createInputDevicesDataSource(context: Context) = InputDevicesDataSourceImpl(
- safe { context.getSystemService(Context.INPUT_SERVICE) as InputManager }.getOrNull()
+ safeWithTimeout { context.getSystemService(Context.INPUT_SERVICE) as InputManager }.getOrNull()
)
private fun createPackageManagerDataSource(context: Context) = PackageManagerDataSourceImpl(
- safe { context.packageManager!! }.getOrNull()
+ safeWithTimeout { context.packageManager!! }.getOrNull()
)
private fun createSettingsDataSource(context: Context) = SettingsDataSourceImpl(
- safe { context.contentResolver!! }.getOrNull()
+ safeWithTimeout { context.contentResolver!! }.getOrNull()
)
private fun createDevicePersonalizationDataSource(context: Context) =
DevicePersonalizationInfoProviderImpl(
- ringtoneManager = safe { RingtoneManager(context) }.getOrNull(),
- assetManager = safe { context.assets!! }.getOrNull(),
- configuration = safe { context.resources!!.configuration!! }.getOrNull(),
+ ringtoneManager = safeWithTimeout { RingtoneManager(context) }.getOrNull(),
+ assetManager = safeWithTimeout { context.assets!! }.getOrNull(),
+ configuration = safeWithTimeout { context.resources!!.configuration!! }.getOrNull(),
)
private fun createFingerprintSensorStatusProvider(context: Context) =
FingerprintSensorInfoProviderImpl(
- safe { FingerprintManagerCompat.from(context)!! }.getOrNull()
+ safeWithTimeout { FingerprintManagerCompat.from(context)!! }.getOrNull()
)
private fun createDeviceSecurityProvider(context: Context) = DeviceSecurityInfoProviderImpl(
- safe { context.getSystemService(Context.DEVICE_POLICY_SERVICE) as DevicePolicyManager }.getOrNull(),
- safe { context.getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager }.getOrNull(),
+ safeWithTimeout { context.getSystemService(Context.DEVICE_POLICY_SERVICE) as DevicePolicyManager }.getOrNull(),
+ safeWithTimeout { context.getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager }.getOrNull(),
)
private fun createCodecInfoProvider() = CodecInfoProviderImpl(
- safe { MediaCodecList(MediaCodecList.ALL_CODECS) }.getOrNull()
+ safeWithTimeout { MediaCodecList(MediaCodecList.ALL_CODECS) }.getOrNull()
)
private fun createBatteryInfoDataSource(context: Context) = BatteryInfoProviderImpl(context)
@@ -256,7 +256,7 @@ public object FingerprinterFactory {
private fun createCameraInfoProvider(): CameraInfoProvider = CameraInfoProviderImpl()
private fun createGpuInfoProvider(context: Context) =
- GpuInfoProviderImpl(safe { context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager }.getOrNull())
+ GpuInfoProviderImpl(safeWithTimeout { context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager }.getOrNull())
//endregion
diff --git a/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/FingerprinterImpl.kt b/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/FingerprinterImpl.kt
index 9834c045..9f71b332 100644
--- a/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/FingerprinterImpl.kt
+++ b/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/FingerprinterImpl.kt
@@ -14,7 +14,6 @@ import com.fingerprintjs.android.fingerprint.signal_providers.os_build.OsBuildSi
import com.fingerprintjs.android.fingerprint.tools.DeprecationMessages
import com.fingerprintjs.android.fingerprint.tools.FingerprintingLegacySchemeSupportExtensions
import com.fingerprintjs.android.fingerprint.tools.hashers.Hasher
-import com.fingerprintjs.android.fingerprint.tools.threading.safe.Safe
import com.fingerprintjs.android.fingerprint.tools.threading.safe.safe
@@ -25,6 +24,7 @@ internal class FingerprinterImpl internal constructor(
) {
@Volatile
private var deviceIdResult: DeviceIdResult? = null
+
@Volatile
private var fingerprintResult: FingerprintResult? = null
@@ -33,7 +33,7 @@ internal class FingerprinterImpl internal constructor(
fun getDeviceId(): Result {
require(legacyArgs != null)
- return safe(timeoutMs = Safe.timeoutLong) {
+ return safe {
deviceIdResult?.let { return@safe it }
val deviceIdResult = DeviceIdResult(
legacyArgs.deviceIdProvider.fingerprint(),
@@ -48,7 +48,7 @@ internal class FingerprinterImpl internal constructor(
@WorkerThread
fun getDeviceId(version: Fingerprinter.Version): Result {
- return safe(timeoutMs = Safe.timeoutLong) {
+ return safe {
DeviceIdResult(
deviceId = deviceIdSignalsProvider.getSignalMatching(version).getIdString(),
gsfId = deviceIdSignalsProvider.gsfIdSignal.getIdString(),
@@ -65,7 +65,7 @@ internal class FingerprinterImpl internal constructor(
): Result {
require(legacyArgs != null)
- return safe(timeoutMs = Safe.timeoutLong) {
+ return safe {
fingerprintResult?.let { return@safe it }
val fingerprintSb = StringBuilder()
@@ -101,7 +101,7 @@ internal class FingerprinterImpl internal constructor(
hasher: Hasher,
): Result {
return if (version < Fingerprinter.Version.fingerprintingFlattenedSignalsFirstVersion) {
- safe(timeoutMs = Safe.timeoutLong) {
+ safe {
val joinedHashes = with(FingerprintingLegacySchemeSupportExtensions) {
listOf(
hasher.hash(fpSignalsProvider.getHardwareSignals(version, stabilityLevel)),
@@ -126,7 +126,7 @@ internal class FingerprinterImpl internal constructor(
fingerprintingSignals: List>,
hasher: Hasher,
): Result {
- return safe(timeoutMs = Safe.timeoutLong) { hasher.hash(fingerprintingSignals) }
+ return safe { hasher.hash(fingerprintingSignals) }
}
internal fun getFingerprintingSignalsProvider(): FingerprintingSignalsProvider {
diff --git a/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/device_id_providers/AndroidIdProvider.kt b/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/device_id_providers/AndroidIdProvider.kt
index fefda16e..a1fabfea 100644
--- a/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/device_id_providers/AndroidIdProvider.kt
+++ b/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/device_id_providers/AndroidIdProvider.kt
@@ -5,7 +5,7 @@ import android.annotation.SuppressLint
import android.content.ContentResolver
import android.provider.Settings
import com.fingerprintjs.android.fingerprint.tools.DeprecationMessages
-import com.fingerprintjs.android.fingerprint.tools.threading.safe.safe
+import com.fingerprintjs.android.fingerprint.tools.threading.safe.safeWithTimeout
@Deprecated(message = DeprecationMessages.UNREACHABLE_SYMBOL_UNINTENDED_PUBLIC_API)
@@ -14,7 +14,7 @@ public class AndroidIdProvider(
) {
@SuppressLint("HardwareIds")
public fun getAndroidId(): String {
- return safe {
+ return safeWithTimeout {
Settings.Secure.getString(
contentResolver!!,
Settings.Secure.ANDROID_ID
diff --git a/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/device_id_providers/GsfIdProvider.kt b/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/device_id_providers/GsfIdProvider.kt
index 76c43204..7f0e63d7 100644
--- a/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/device_id_providers/GsfIdProvider.kt
+++ b/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/device_id_providers/GsfIdProvider.kt
@@ -4,7 +4,7 @@ package com.fingerprintjs.android.fingerprint.device_id_providers
import android.content.ContentResolver
import android.net.Uri
import com.fingerprintjs.android.fingerprint.tools.DeprecationMessages
-import com.fingerprintjs.android.fingerprint.tools.threading.safe.safe
+import com.fingerprintjs.android.fingerprint.tools.threading.safe.safeWithTimeout
@Deprecated(message = DeprecationMessages.UNREACHABLE_SYMBOL_UNINTENDED_PUBLIC_API)
@@ -13,7 +13,7 @@ public class GsfIdProvider(
) {
public fun getGsfAndroidId(): String? {
- return safe {
+ return safeWithTimeout {
getGsfId()
}.getOrDefault("")
}
diff --git a/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/device_id_providers/MediaDrmIdProvider.kt b/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/device_id_providers/MediaDrmIdProvider.kt
index 35266ba8..64495dce 100644
--- a/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/device_id_providers/MediaDrmIdProvider.kt
+++ b/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/device_id_providers/MediaDrmIdProvider.kt
@@ -4,14 +4,14 @@ package com.fingerprintjs.android.fingerprint.device_id_providers
import android.media.MediaDrm
import android.os.Build
import com.fingerprintjs.android.fingerprint.tools.DeprecationMessages
-import com.fingerprintjs.android.fingerprint.tools.threading.safe.safe
+import com.fingerprintjs.android.fingerprint.tools.threading.safe.safeWithTimeout
import java.security.MessageDigest
import java.util.UUID
@Deprecated(message = DeprecationMessages.UNREACHABLE_SYMBOL_UNINTENDED_PUBLIC_API)
public class MediaDrmIdProvider {
- public fun getMediaDrmId(): String? = safe {
+ public fun getMediaDrmId(): String? = safeWithTimeout {
mediaDrmId()
}.getOrDefault(null)
diff --git a/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/info_providers/BatteryInfoProvider.kt b/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/info_providers/BatteryInfoProvider.kt
index c9317d78..1f7e1137 100644
--- a/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/info_providers/BatteryInfoProvider.kt
+++ b/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/info_providers/BatteryInfoProvider.kt
@@ -7,7 +7,7 @@ import android.content.Intent
import android.content.IntentFilter
import android.os.BatteryManager
import com.fingerprintjs.android.fingerprint.tools.DeprecationMessages
-import com.fingerprintjs.android.fingerprint.tools.threading.safe.safe
+import com.fingerprintjs.android.fingerprint.tools.threading.safe.safeWithTimeout
@Deprecated(message = DeprecationMessages.UNREACHABLE_SYMBOL_UNINTENDED_PUBLIC_API)
@@ -20,7 +20,7 @@ internal class BatteryInfoProviderImpl(
private val applicationContext: Context
) : BatteryInfoProvider {
override fun batteryHealth(): String {
- return safe {
+ return safeWithTimeout {
val intent = applicationContext
.registerReceiver(null, IntentFilter(Intent.ACTION_BATTERY_CHANGED))!!
@@ -36,7 +36,7 @@ internal class BatteryInfoProviderImpl(
@SuppressLint("PrivateApi")
override fun batteryTotalCapacity(): String {
- return safe {
+ return safeWithTimeout {
val mPowerProfile = Class.forName(POWER_PROFILE_CLASS_NAME)
.getConstructor(Context::class.java)
.newInstance(applicationContext)
diff --git a/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/info_providers/CameraInfoProvider.kt b/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/info_providers/CameraInfoProvider.kt
index a6b0e049..406a1fe0 100644
--- a/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/info_providers/CameraInfoProvider.kt
+++ b/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/info_providers/CameraInfoProvider.kt
@@ -3,7 +3,7 @@ package com.fingerprintjs.android.fingerprint.info_providers
import android.hardware.Camera
import com.fingerprintjs.android.fingerprint.tools.DeprecationMessages
-import com.fingerprintjs.android.fingerprint.tools.threading.safe.safe
+import com.fingerprintjs.android.fingerprint.tools.threading.safe.safeWithTimeout
import java.util.LinkedList
@@ -22,7 +22,7 @@ public interface CameraInfoProvider {
internal class CameraInfoProviderImpl(
) : CameraInfoProvider {
override fun getCameraInfo(): List {
- return safe {
+ return safeWithTimeout {
extractInfo()
}.getOrDefault(emptyList())
}
diff --git a/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/info_providers/CodecInfoProvider.kt b/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/info_providers/CodecInfoProvider.kt
index b326806c..2ee40f53 100644
--- a/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/info_providers/CodecInfoProvider.kt
+++ b/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/info_providers/CodecInfoProvider.kt
@@ -3,7 +3,7 @@ package com.fingerprintjs.android.fingerprint.info_providers
import android.media.MediaCodecList
import com.fingerprintjs.android.fingerprint.tools.DeprecationMessages
-import com.fingerprintjs.android.fingerprint.tools.threading.safe.safe
+import com.fingerprintjs.android.fingerprint.tools.threading.safe.safeWithTimeout
public data class MediaCodecInfo(
@@ -20,7 +20,7 @@ internal class CodecInfoProviderImpl(
private val codecList: MediaCodecList?,
) : CodecInfoProvider {
override fun codecsList(): List {
- return safe {
+ return safeWithTimeout {
extractCodecInfo()
}.getOrDefault(emptyList())
}
diff --git a/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/info_providers/CpuInfoProvider.kt b/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/info_providers/CpuInfoProvider.kt
index 01931ef3..abde100f 100644
--- a/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/info_providers/CpuInfoProvider.kt
+++ b/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/info_providers/CpuInfoProvider.kt
@@ -4,7 +4,7 @@ package com.fingerprintjs.android.fingerprint.info_providers
import android.os.Build
import com.fingerprintjs.android.fingerprint.tools.DeprecationMessages
import com.fingerprintjs.android.fingerprint.tools.parsers.parseCpuInfo
-import com.fingerprintjs.android.fingerprint.tools.threading.safe.safe
+import com.fingerprintjs.android.fingerprint.tools.threading.safe.safeWithTimeout
import java.io.File
import java.util.Scanner
@@ -33,26 +33,26 @@ public interface CpuInfoProvider {
internal class CpuInfoProviderImpl :
CpuInfoProvider {
override fun cpuInfo(): Map {
- return safe {
+ return safeWithTimeout {
getCpuInfo()
}.getOrDefault(emptyMap())
}
override fun cpuInfoV2(): CpuInfo {
- return safe {
+ return safeWithTimeout {
getCpuInfoV2()
}.getOrDefault(CpuInfo.EMPTY)
}
@Suppress("DEPRECATION")
override fun abiType(): String {
- return safe {
+ return safeWithTimeout {
Build.SUPPORTED_ABIS[0]!!
}.getOrDefault("")
}
override fun coresCount(): Int {
- return safe {
+ return safeWithTimeout {
Runtime.getRuntime()!!.availableProcessors()
}.getOrDefault(0)
}
diff --git a/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/info_providers/DevicePersonalizationInfoProvider.kt b/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/info_providers/DevicePersonalizationInfoProvider.kt
index 41f9d716..6da485c9 100644
--- a/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/info_providers/DevicePersonalizationInfoProvider.kt
+++ b/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/info_providers/DevicePersonalizationInfoProvider.kt
@@ -5,7 +5,7 @@ import android.content.res.AssetManager
import android.content.res.Configuration
import android.media.RingtoneManager
import com.fingerprintjs.android.fingerprint.tools.DeprecationMessages
-import com.fingerprintjs.android.fingerprint.tools.threading.safe.safe
+import com.fingerprintjs.android.fingerprint.tools.threading.safe.safeWithTimeout
import java.util.Locale
import java.util.TimeZone
@@ -26,11 +26,11 @@ internal class DevicePersonalizationInfoProviderImpl(
) : DevicePersonalizationInfoProvider {
override fun ringtoneSource(): String {
- return safe{ ringtoneManager!!.getRingtoneUri(0)!!.toString()!! }.getOrDefault("")
+ return safeWithTimeout { ringtoneManager!!.getRingtoneUri(0)!!.toString()!! }.getOrDefault("")
}
override fun availableLocales(): Array {
- return safe {
+ return safeWithTimeout {
assetManager!!.locales!!
.map { locale: String? -> locale.toString() }.toTypedArray()
}.getOrDefault(emptyArray())
@@ -38,14 +38,14 @@ internal class DevicePersonalizationInfoProviderImpl(
@Suppress("DEPRECATION")
override fun regionCountry(): String {
- return safe{ configuration!!.locale!!.country!! }.getOrDefault("")
+ return safeWithTimeout{ configuration!!.locale!!.country!! }.getOrDefault("")
}
override fun defaultLanguage(): String {
- return safe { Locale.getDefault()!!.language!! } .getOrDefault("")
+ return safeWithTimeout { Locale.getDefault()!!.language!! } .getOrDefault("")
}
override fun timezone(): String {
- return safe { TimeZone.getDefault()!!.displayName!! }.getOrDefault("")
+ return safeWithTimeout { TimeZone.getDefault()!!.displayName!! }.getOrDefault("")
}
}
\ No newline at end of file
diff --git a/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/info_providers/DeviceSecurityInfoProvider.kt b/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/info_providers/DeviceSecurityInfoProvider.kt
index 85139c01..52c3638b 100644
--- a/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/info_providers/DeviceSecurityInfoProvider.kt
+++ b/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/info_providers/DeviceSecurityInfoProvider.kt
@@ -4,7 +4,7 @@ package com.fingerprintjs.android.fingerprint.info_providers
import android.app.KeyguardManager
import android.app.admin.DevicePolicyManager
import com.fingerprintjs.android.fingerprint.tools.DeprecationMessages
-import com.fingerprintjs.android.fingerprint.tools.threading.safe.safe
+import com.fingerprintjs.android.fingerprint.tools.threading.safe.safeWithTimeout
import java.security.Security
@@ -20,20 +20,20 @@ internal class DeviceSecurityInfoProviderImpl(
private val keyguardManager: KeyguardManager?,
) : DeviceSecurityInfoProvider {
override fun encryptionStatus(): String {
- return safe {
+ return safeWithTimeout {
stringDescriptionForEncryptionStatus(devicePolicyManager!!.storageEncryptionStatus)
}.getOrDefault("")
}
override fun securityProvidersData(): List> {
- return safe {
+ return safeWithTimeout {
Security.getProviders().map {
Pair(it!!.name!!, it.info ?: "")
}
}.getOrDefault(emptyList())
}
- override fun isPinSecurityEnabled() = safe {
+ override fun isPinSecurityEnabled() = safeWithTimeout {
keyguardManager!!.isKeyguardSecure
}.getOrDefault(false)
}
diff --git a/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/info_providers/FingerprintSensorInfoProvider.kt b/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/info_providers/FingerprintSensorInfoProvider.kt
index fb673201..3e12e490 100644
--- a/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/info_providers/FingerprintSensorInfoProvider.kt
+++ b/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/info_providers/FingerprintSensorInfoProvider.kt
@@ -4,7 +4,7 @@ package com.fingerprintjs.android.fingerprint.info_providers
import android.os.Build
import androidx.core.hardware.fingerprint.FingerprintManagerCompat
import com.fingerprintjs.android.fingerprint.tools.DeprecationMessages
-import com.fingerprintjs.android.fingerprint.tools.threading.safe.safe
+import com.fingerprintjs.android.fingerprint.tools.threading.safe.safeWithTimeout
@Deprecated(message = DeprecationMessages.UNREACHABLE_SYMBOL_UNINTENDED_PUBLIC_API)
@@ -16,7 +16,7 @@ internal class FingerprintSensorInfoProviderImpl(
private val fingerprintManager: FingerprintManagerCompat?,
) : FingerprintSensorInfoProvider {
override fun getStatus(): FingerprintSensorStatus {
- return safe {
+ return safeWithTimeout {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
FingerprintSensorStatus.NOT_SUPPORTED
} else if (!fingerprintManager!!.isHardwareDetected) {
diff --git a/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/info_providers/GpuInfoProvider.kt b/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/info_providers/GpuInfoProvider.kt
index c0205b63..fb93aa2c 100644
--- a/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/info_providers/GpuInfoProvider.kt
+++ b/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/info_providers/GpuInfoProvider.kt
@@ -3,7 +3,7 @@ package com.fingerprintjs.android.fingerprint.info_providers
import android.app.ActivityManager
import com.fingerprintjs.android.fingerprint.tools.DeprecationMessages
-import com.fingerprintjs.android.fingerprint.tools.threading.safe.safe
+import com.fingerprintjs.android.fingerprint.tools.threading.safe.safeWithTimeout
@Deprecated(message = DeprecationMessages.UNREACHABLE_SYMBOL_UNINTENDED_PUBLIC_API)
@@ -15,7 +15,7 @@ internal class GpuInfoProviderImpl(
private val activityManager: ActivityManager?,
) : GpuInfoProvider {
override fun glesVersion(): String {
- return safe {
+ return safeWithTimeout {
activityManager!!.deviceConfigurationInfo!!.glEsVersion!!
}.getOrDefault("")
}
diff --git a/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/info_providers/InputDevicesInfoProvider.kt b/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/info_providers/InputDevicesInfoProvider.kt
index 3aa9640c..3e15370b 100644
--- a/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/info_providers/InputDevicesInfoProvider.kt
+++ b/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/info_providers/InputDevicesInfoProvider.kt
@@ -3,7 +3,7 @@ package com.fingerprintjs.android.fingerprint.info_providers
import android.hardware.input.InputManager
import com.fingerprintjs.android.fingerprint.tools.DeprecationMessages
-import com.fingerprintjs.android.fingerprint.tools.threading.safe.safe
+import com.fingerprintjs.android.fingerprint.tools.threading.safe.safeWithTimeout
@Deprecated(message = DeprecationMessages.UNREACHABLE_SYMBOL_UNINTENDED_PUBLIC_API)
@@ -20,7 +20,7 @@ internal class InputDevicesDataSourceImpl(
private val inputDeviceManager: InputManager?,
) : InputDeviceDataSource {
override fun getInputDeviceData(): List {
- return safe {
+ return safeWithTimeout {
inputDeviceManager!!.inputDeviceIds!!.map {
val inputDevice = inputDeviceManager.getInputDevice(it)!!
val vendorId = inputDevice.vendorId.toString()
diff --git a/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/info_providers/MemInfoProvider.kt b/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/info_providers/MemInfoProvider.kt
index a023e0ab..1754f35d 100644
--- a/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/info_providers/MemInfoProvider.kt
+++ b/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/info_providers/MemInfoProvider.kt
@@ -4,7 +4,7 @@ package com.fingerprintjs.android.fingerprint.info_providers
import android.app.ActivityManager
import android.os.StatFs
import com.fingerprintjs.android.fingerprint.tools.DeprecationMessages
-import com.fingerprintjs.android.fingerprint.tools.threading.safe.safe
+import com.fingerprintjs.android.fingerprint.tools.threading.safe.safeWithTimeout
@Deprecated(message = DeprecationMessages.UNREACHABLE_SYMBOL_UNINTENDED_PUBLIC_API)
@@ -20,7 +20,7 @@ internal class MemInfoProviderImpl(
private val externalStorageStats: StatFs?,
) : MemInfoProvider {
override fun totalRAM(): Long {
- return safe {
+ return safeWithTimeout {
val memoryInfo = ActivityManager.MemoryInfo()
activityManager!!.getMemoryInfo(memoryInfo)
memoryInfo.totalMem
@@ -28,10 +28,10 @@ internal class MemInfoProviderImpl(
}
override fun totalInternalStorageSpace(): Long {
- return safe { internalStorageStats!!.totalBytes }.getOrDefault(0)
+ return safeWithTimeout { internalStorageStats!!.totalBytes }.getOrDefault(0)
}
override fun totalExternalStorageSpace(): Long {
- return safe { externalStorageStats!!.totalBytes }.getOrDefault(0)
+ return safeWithTimeout { externalStorageStats!!.totalBytes }.getOrDefault(0)
}
}
\ No newline at end of file
diff --git a/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/info_providers/OsBuildInfoProvider.kt b/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/info_providers/OsBuildInfoProvider.kt
index ff3d462e..d6a7dd79 100644
--- a/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/info_providers/OsBuildInfoProvider.kt
+++ b/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/info_providers/OsBuildInfoProvider.kt
@@ -3,7 +3,7 @@ package com.fingerprintjs.android.fingerprint.info_providers
import android.os.Build
import com.fingerprintjs.android.fingerprint.tools.DeprecationMessages
-import com.fingerprintjs.android.fingerprint.tools.threading.safe.safe
+import com.fingerprintjs.android.fingerprint.tools.threading.safe.safeWithTimeout
@Deprecated(message = DeprecationMessages.UNREACHABLE_SYMBOL_UNINTENDED_PUBLIC_API)
@@ -19,26 +19,26 @@ public interface OsBuildInfoProvider {
internal class OsBuildInfoProviderImpl :
OsBuildInfoProvider {
override fun modelName(): String {
- return safe { Build.MODEL!! }.getOrDefault("")
+ return safeWithTimeout { Build.MODEL!! }.getOrDefault("")
}
override fun manufacturerName(): String {
- return safe { Build.MANUFACTURER!! }.getOrDefault("")
+ return safeWithTimeout { Build.MANUFACTURER!! }.getOrDefault("")
}
override fun androidVersion(): String {
- return safe { Build.VERSION.RELEASE!! }.getOrDefault("")
+ return safeWithTimeout { Build.VERSION.RELEASE!! }.getOrDefault("")
}
override fun sdkVersion(): String {
- return safe { Build.VERSION.SDK_INT.toString() }.getOrDefault("")
+ return safeWithTimeout { Build.VERSION.SDK_INT.toString() }.getOrDefault("")
}
override fun kernelVersion(): String {
- return safe { System.getProperty("os.version")!! }.getOrDefault("")
+ return safeWithTimeout { System.getProperty("os.version")!! }.getOrDefault("")
}
override fun fingerprint(): String {
- return safe { Build.FINGERPRINT!! }.getOrDefault("")
+ return safeWithTimeout { Build.FINGERPRINT!! }.getOrDefault("")
}
}
\ No newline at end of file
diff --git a/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/info_providers/PackageManagerInfoProvider.kt b/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/info_providers/PackageManagerInfoProvider.kt
index 51046a85..ebbb142c 100644
--- a/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/info_providers/PackageManagerInfoProvider.kt
+++ b/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/info_providers/PackageManagerInfoProvider.kt
@@ -4,7 +4,7 @@ package com.fingerprintjs.android.fingerprint.info_providers
import android.annotation.SuppressLint
import android.content.pm.PackageManager
import com.fingerprintjs.android.fingerprint.tools.DeprecationMessages
-import com.fingerprintjs.android.fingerprint.tools.threading.safe.safe
+import com.fingerprintjs.android.fingerprint.tools.threading.safe.safeWithTimeout
@Deprecated(message = DeprecationMessages.UNREACHABLE_SYMBOL_UNINTENDED_PUBLIC_API)
@@ -25,7 +25,7 @@ internal class PackageManagerDataSourceImpl(
private val packageManager: PackageManager?,
) : PackageManagerDataSource {
@SuppressLint("QueryPermissionsNeeded")
- override fun getApplicationsList() = safe {
+ override fun getApplicationsList() = safeWithTimeout {
packageManager!!
.getInstalledApplications(PackageManager.GET_META_DATA)
.map {
@@ -37,7 +37,7 @@ internal class PackageManagerDataSourceImpl(
@SuppressLint("QueryPermissionsNeeded")
- override fun getSystemApplicationsList() = safe {
+ override fun getSystemApplicationsList() = safeWithTimeout {
packageManager!!
.getInstalledApplications(PackageManager.GET_META_DATA)
.filter {
diff --git a/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/info_providers/SensorsInfoProvider.kt b/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/info_providers/SensorsInfoProvider.kt
index b7d62a02..c7cfde2f 100644
--- a/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/info_providers/SensorsInfoProvider.kt
+++ b/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/info_providers/SensorsInfoProvider.kt
@@ -4,7 +4,7 @@ package com.fingerprintjs.android.fingerprint.info_providers
import android.hardware.Sensor
import android.hardware.SensorManager
import com.fingerprintjs.android.fingerprint.tools.DeprecationMessages
-import com.fingerprintjs.android.fingerprint.tools.threading.safe.safe
+import com.fingerprintjs.android.fingerprint.tools.threading.safe.safeWithTimeout
public class SensorData(
@@ -21,7 +21,7 @@ internal class SensorDataSourceImpl(
private val sensorManager: SensorManager?,
) : SensorDataSource {
override fun sensors(): List {
- return safe {
+ return safeWithTimeout {
sensorManager!!.getSensorList(Sensor.TYPE_ALL)!!.map {
SensorData(
it!!.name!!,
diff --git a/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/info_providers/SettingsInfoProvider.kt b/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/info_providers/SettingsInfoProvider.kt
index ee33d215..85bb675a 100644
--- a/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/info_providers/SettingsInfoProvider.kt
+++ b/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/info_providers/SettingsInfoProvider.kt
@@ -5,7 +5,7 @@ import android.content.ContentResolver
import android.os.Build
import android.provider.Settings
import com.fingerprintjs.android.fingerprint.tools.DeprecationMessages
-import com.fingerprintjs.android.fingerprint.tools.threading.safe.safe
+import com.fingerprintjs.android.fingerprint.tools.threading.safe.safeWithTimeout
@Deprecated(message = DeprecationMessages.UNREACHABLE_SYMBOL_UNINTENDED_PUBLIC_API)
@@ -158,19 +158,19 @@ internal class SettingsDataSourceImpl(
//endregion
private fun extractGlobalSettingsParam(key: String): String {
- return safe {
+ return safeWithTimeout {
Settings.Global.getString(contentResolver!!, key)!!
}.getOrDefault("")
}
private fun extractSecureSettingsParam(key: String): String {
- return safe {
+ return safeWithTimeout {
Settings.Secure.getString(contentResolver!!, key)!!
}.getOrDefault("")
}
private fun extractSystemSettingsParam(key: String): String {
- return safe {
+ return safeWithTimeout {
Settings.System.getString(contentResolver!!, key)!!
}.getOrDefault("")
}
diff --git a/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/tools/threading/AnotherThread.kt b/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/tools/threading/AnotherThread.kt
index 78d51a8f..9c5a233e 100644
--- a/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/tools/threading/AnotherThread.kt
+++ b/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/tools/threading/AnotherThread.kt
@@ -1,5 +1,7 @@
package com.fingerprintjs.android.fingerprint.tools.threading
+import com.fingerprintjs.android.fingerprint.tools.threading.safe.safe
+
/**
* Returns immediately. The error relates to posting the job to another thread, not the error
* that could have occurred on that thread.
@@ -7,6 +9,6 @@ package com.fingerprintjs.android.fingerprint.tools.threading
*/
internal fun runOnAnotherThread(
block: () -> Unit,
-): Result = runCatching {
+): Result = safe {
sharedExecutor.submit { block() }
}
diff --git a/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/tools/threading/safe/Safe.kt b/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/tools/threading/safe/Safe.kt
index 40cfeca0..e9cfe61c 100644
--- a/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/tools/threading/safe/Safe.kt
+++ b/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/tools/threading/safe/Safe.kt
@@ -1,42 +1,54 @@
package com.fingerprintjs.android.fingerprint.tools.threading.safe
+import androidx.annotation.VisibleForTesting
+import com.fingerprintjs.android.fingerprint.tools.logs.Logger
+import com.fingerprintjs.android.fingerprint.tools.logs.ePleaseReport
import com.fingerprintjs.android.fingerprint.tools.threading.sharedExecutor
import java.util.concurrent.Callable
import java.util.concurrent.TimeUnit
import java.util.concurrent.TimeoutException
import java.util.concurrent.atomic.AtomicReference
+import kotlin.concurrent.getOrSet
internal object Safe {
const val timeoutShort = 1_000L
- const val timeoutLong = 3_000L
- const val timeoutNop = 0L
+
+ @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
+ internal val runningInsideSafeWithTimeout = ThreadLocal()
+
+ @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
+ internal fun logIllegalSafeWithTimeoutUsage() {
+ Logger.ePleaseReport(IllegalStateException())
+ }
}
+internal inline fun safe(block: () -> T) =
+ runCatching(block)
+
/**
* Runs the [block], catching all exceptions and handling unexpected execution locks (configured with [timeoutMs]).
*/
-internal fun safe(
+internal fun safeWithTimeout(
timeoutMs: Long = Safe.timeoutShort,
block: () -> T,
-): Result {
- return when (timeoutMs) {
- Safe.timeoutNop -> runCatching { block() }
- else -> safeWithTimeout(timeoutMs, block)
- }
-}
-
-private fun safeWithTimeout(
- timeoutMs: Long,
- block: () -> T,
): Result {
// we can't make a local variable volatile, hence using atomic reference here
val executionThread = AtomicReference(null)
+ if (Safe.runningInsideSafeWithTimeout.getOrSet { false }) {
+ Safe.logIllegalSafeWithTimeoutUsage()
+ }
+
val future = runCatching {
sharedExecutor.submit(
Callable {
+ Safe.runningInsideSafeWithTimeout.set(true)
executionThread.set(Thread.currentThread())
- block()
+ try {
+ block()
+ } finally {
+ Safe.runningInsideSafeWithTimeout.remove()
+ }
}
)!!
}.getOrElse { return Result.failure(it) }
diff --git a/fingerprint/src/test/java/com/fingerprintjs/android/fingerprint/SafeTests.kt b/fingerprint/src/test/java/com/fingerprintjs/android/fingerprint/SafeTests.kt
index 26dc89bf..95654cde 100644
--- a/fingerprint/src/test/java/com/fingerprintjs/android/fingerprint/SafeTests.kt
+++ b/fingerprint/src/test/java/com/fingerprintjs/android/fingerprint/SafeTests.kt
@@ -3,13 +3,19 @@ package com.fingerprintjs.android.fingerprint
import com.fingerprintjs.android.fingerprint.tools.threading.createSharedExecutor
import com.fingerprintjs.android.fingerprint.tools.threading.runOnAnotherThread
import com.fingerprintjs.android.fingerprint.tools.threading.safe.ExecutionTimeoutException
-import com.fingerprintjs.android.fingerprint.tools.threading.safe.safe
+import com.fingerprintjs.android.fingerprint.tools.threading.safe.Safe
+import com.fingerprintjs.android.fingerprint.tools.threading.safe.safeWithTimeout
import com.fingerprintjs.android.fingerprint.tools.threading.sharedExecutor
+import io.mockk.every
+import io.mockk.mockkObject
+import io.mockk.unmockkObject
import junit.framework.TestCase
import org.junit.After
import org.junit.Test
+import java.util.concurrent.Callable
import java.util.concurrent.CountDownLatch
import java.util.concurrent.ExecutionException
+import kotlin.concurrent.getOrSet
class SafeTests {
@@ -19,37 +25,31 @@ class SafeTests {
}
@Test
- fun safeValueReturned() {
- val v = safe { 0 }
+ fun safeWithTimeoutValueReturned() {
+ val v = safeWithTimeout { 0 }
TestCase.assertEquals(v.getOrNull(), 0)
}
@Test
- fun safeNestedValueReturned() {
- val v = safe { safe { 0 } }
- TestCase.assertEquals(v.getOrNull()!!.getOrNull(), 0)
- }
-
- @Test
- fun safeErrorRetrievable() {
+ fun safeWithTimeoutErrorRetrievable() {
val errorId = "Hello"
- val v = safe { throw Exception(errorId) }
+ val v = safeWithTimeout { throw Exception(errorId) }
val err = v.exceptionOrNull() as ExecutionException
val errCause = err.cause!!
TestCase.assertTrue(errCause is Exception && errCause.message == errorId)
}
@Test
- fun safeExecutionNeverStuck() {
+ fun safeWithTimeoutExecutionNeverStuck() {
val elapsedTime = elapsedTimeMs {
- safe(timeoutMs = TimeConstants.t1) { Thread.sleep(TimeConstants.t4) }
+ safeWithTimeout(timeoutMs = TimeConstants.t1) { Thread.sleep(TimeConstants.t4) }
}
TestCase.assertTrue(elapsedTime - TimeConstants.t1 < TimeConstants.epsilon)
}
@Test
- fun safeExecutionStuckThreadStackTraceReturned() {
- val res = safe(timeoutMs = TimeConstants.t1) { Thread.sleep(TimeConstants.t4) }
+ fun safeWithTimeoutExecutionStuckThreadStackTraceReturned() {
+ val res = safeWithTimeout(timeoutMs = TimeConstants.t1) { Thread.sleep(TimeConstants.t4) }
val err = res.exceptionOrNull()!!
TestCase.assertTrue(
err is ExecutionTimeoutException
@@ -59,20 +59,20 @@ class SafeTests {
}
@Test
- fun safeFromMultipleThreadsIsNotBlocked() {
+ fun safeWithTimeoutFromMultipleThreadsIsNotBlocked() {
val countDownLatch = CountDownLatch(2)
val elapsedTime = elapsedTimeMs {
- runOnAnotherThread { safe { Thread.sleep(TimeConstants.t1); countDownLatch.countDown() } }
- runOnAnotherThread { safe { Thread.sleep(TimeConstants.t1); countDownLatch.countDown() } }
+ runOnAnotherThread { safeWithTimeout { Thread.sleep(TimeConstants.t1); countDownLatch.countDown() } }
+ runOnAnotherThread { safeWithTimeout { Thread.sleep(TimeConstants.t1); countDownLatch.countDown() } }
countDownLatch.await()
}
TestCase.assertTrue(elapsedTime - TimeConstants.t1 < TimeConstants.epsilon)
}
@Test
- fun safeThreadsAreReused() {
+ fun safeWithTimeoutThreadsAreReused() {
for (i in 0 until 4) {
- safe { }
+ safeWithTimeout { }
TestCase.assertEquals(1, sharedExecutor.poolSize)
Thread.sleep(TimeConstants.epsilon)
}
@@ -80,19 +80,19 @@ class SafeTests {
// this is a sad fact but we will leave it as it is
@Test
- fun safeThreadCountGrowsIfThreadsCantInterrupt() {
+ fun safeWithTimeoutThreadCountGrowsIfThreadsCantInterrupt() {
for (i in 1 until 5) {
- safe(timeoutMs = TimeConstants.epsilon) { neverReturn() }
+ safeWithTimeout(timeoutMs = TimeConstants.epsilon) { neverReturn() }
TestCase.assertEquals(i, sharedExecutor.poolSize)
Thread.sleep(TimeConstants.epsilon)
}
}
@Test
- fun safeOuterTimeoutDominatesOverInner() {
+ fun safeWithTimeoutOuterTimeoutDominatesOverInner() {
val elapsedTime = elapsedTimeMs {
- safe(timeoutMs = TimeConstants.t1) {
- safe(timeoutMs = TimeConstants.t2) {
+ safeWithTimeout(timeoutMs = TimeConstants.t1) {
+ safeWithTimeout(timeoutMs = TimeConstants.t2) {
Thread.sleep(TimeConstants.t3)
}
}
@@ -100,14 +100,21 @@ class SafeTests {
TestCase.assertTrue(elapsedTime - TimeConstants.t1 < TimeConstants.epsilon)
}
+ /**
+ * This test illustrates the behaviour when using one safe call inside the another.
+ * Such usage is prohibited, but we'd rather know the what-ifs.
+ */
@Test
- fun safeNestedSafeInterrupted() {
+ fun safeWithTimeoutNestedSafeInterruptedBehaviour() {
val errLvl1: Throwable?
var errLvl2: Throwable? = null
var errLvl3: Throwable? = null
val countDownLatch = CountDownLatch(2)
- errLvl1 = safe(timeoutMs = TimeConstants.t1) {
- errLvl2 = safe(timeoutMs = TimeConstants.t2) {
+ mockkObject(Safe)
+ every { Safe.logIllegalSafeWithTimeoutUsage() } answers {}
+
+ errLvl1 = safeWithTimeout(timeoutMs = TimeConstants.t1) {
+ errLvl2 = safeWithTimeout(timeoutMs = TimeConstants.t2) {
try {
Thread.sleep(TimeConstants.t3)
} catch (t: Throwable) {
@@ -118,10 +125,72 @@ class SafeTests {
countDownLatch.countDown()
}.exceptionOrNull()
countDownLatch.await()
+
+ unmockkObject(Safe)
TestCase.assertTrue(errLvl1 is ExecutionTimeoutException)
TestCase.assertTrue(errLvl2 is InterruptedException)
TestCase.assertTrue(errLvl3 is InterruptedException)
}
+
+
+ /**
+ * Same motivation for the test as for the above.
+ */
+ @Test
+ fun safeWithTimeoutNestedValueReturned() {
+ mockkObject(Safe)
+ every { Safe.logIllegalSafeWithTimeoutUsage() } answers { }
+
+ val v = safeWithTimeout { safeWithTimeout { 0 } }
+
+ unmockkObject(Safe)
+ TestCase.assertEquals(v.getOrNull()!!.getOrNull(), 0)
+ }
+
+ @Test
+ fun safeContextFlagUnsetWhenSafeBlockReturns() =
+ safeWithTimeoutContextFlagUnset(whenBlockThrows = false)
+
+ @Test
+ fun safeContextFlagUnsetWhenSafeBlockThrows() =
+ safeWithTimeoutContextFlagUnset(whenBlockThrows = true)
+
+ private fun safeWithTimeoutContextFlagUnset(whenBlockThrows: Boolean) {
+ var executionThread1: Long? = null
+ var executionThread2: Long? = null
+
+ safeWithTimeout {
+ executionThread1 = Thread.currentThread().id
+ if (whenBlockThrows)
+ throw Exception()
+ }
+
+ val countDownLatch = CountDownLatch(1)
+ var insideSafe: Boolean? = null
+ sharedExecutor.submit(Callable {
+ executionThread2 = Thread.currentThread().id
+ insideSafe = Safe.runningInsideSafeWithTimeout.getOrSet { false }
+ countDownLatch.countDown()
+ })
+ countDownLatch.await()
+
+ unmockkObject(Safe)
+ TestCase.assertEquals(executionThread1, executionThread2)
+ TestCase.assertEquals(false, insideSafe)
+ }
+
+ @Test
+ fun safeWithTimeoutNestedUsageReported() {
+ var logCalled = false
+ mockkObject(Safe)
+ every { Safe.logIllegalSafeWithTimeoutUsage() } answers { logCalled = true }
+
+ safeWithTimeout { safeWithTimeout {} }
+
+ unmockkObject(Safe)
+ TestCase.assertEquals(true, logCalled)
+ }
+
}
private object TimeConstants {