@hot-updater/react-native 1.0.0-rc.17 → 1.0.0-rc.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/commonjs/checkForUpdate.js +7 -9
- package/lib/commonjs/checkForUpdate.js.map +1 -1
- package/lib/commonjs/clientPlugin.js +7 -43
- package/lib/commonjs/clientPlugin.js.map +1 -1
- package/lib/commonjs/httpClient.js +7 -9
- package/lib/commonjs/httpClient.js.map +1 -1
- package/lib/commonjs/index.js +10 -0
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/native.js +4 -4
- package/lib/commonjs/native.js.map +1 -1
- package/lib/commonjs/pluginHost.js +26 -190
- package/lib/commonjs/pluginHost.js.map +1 -1
- package/lib/commonjs/releaseCatalogCache.js +7 -59
- package/lib/commonjs/releaseCatalogCache.js.map +1 -1
- package/lib/commonjs/sdkVersion.js +1 -1
- package/lib/module/checkForUpdate.js +1 -3
- package/lib/module/checkForUpdate.js.map +1 -1
- package/lib/module/clientPlugin.js +1 -41
- package/lib/module/clientPlugin.js.map +1 -1
- package/lib/module/httpClient.js +1 -3
- package/lib/module/httpClient.js.map +1 -1
- package/lib/module/index.js +2 -0
- package/lib/module/index.js.map +1 -1
- package/lib/module/native.js +1 -1
- package/lib/module/native.js.map +1 -1
- package/lib/module/pluginHost.js +22 -187
- package/lib/module/pluginHost.js.map +1 -1
- package/lib/module/releaseCatalogCache.js +3 -54
- package/lib/module/releaseCatalogCache.js.map +1 -1
- package/lib/module/sdkVersion.js +1 -1
- package/lib/typescript/commonjs/checkForUpdate.d.ts.map +1 -1
- package/lib/typescript/commonjs/clientPlugin.d.ts +1 -184
- package/lib/typescript/commonjs/clientPlugin.d.ts.map +1 -1
- package/lib/typescript/commonjs/httpClient.d.ts +1 -2
- package/lib/typescript/commonjs/httpClient.d.ts.map +1 -1
- package/lib/typescript/commonjs/index.d.ts +1 -0
- package/lib/typescript/commonjs/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/native.d.ts +1 -1
- package/lib/typescript/commonjs/native.d.ts.map +1 -1
- package/lib/typescript/commonjs/pluginHost.d.ts +3 -28
- package/lib/typescript/commonjs/pluginHost.d.ts.map +1 -1
- package/lib/typescript/commonjs/releaseCatalogCache.d.ts +1 -13
- package/lib/typescript/commonjs/releaseCatalogCache.d.ts.map +1 -1
- package/lib/typescript/commonjs/sdkVersion.d.ts +1 -1
- package/lib/typescript/commonjs/types.d.ts +1 -1
- package/lib/typescript/commonjs/types.d.ts.map +1 -1
- package/lib/typescript/commonjs/wrap.types.d.ts +2 -2
- package/lib/typescript/module/checkForUpdate.d.ts.map +1 -1
- package/lib/typescript/module/clientPlugin.d.ts +1 -184
- package/lib/typescript/module/clientPlugin.d.ts.map +1 -1
- package/lib/typescript/module/httpClient.d.ts +1 -2
- package/lib/typescript/module/httpClient.d.ts.map +1 -1
- package/lib/typescript/module/index.d.ts +1 -0
- package/lib/typescript/module/index.d.ts.map +1 -1
- package/lib/typescript/module/native.d.ts +1 -1
- package/lib/typescript/module/native.d.ts.map +1 -1
- package/lib/typescript/module/pluginHost.d.ts +3 -28
- package/lib/typescript/module/pluginHost.d.ts.map +1 -1
- package/lib/typescript/module/releaseCatalogCache.d.ts +1 -13
- package/lib/typescript/module/releaseCatalogCache.d.ts.map +1 -1
- package/lib/typescript/module/sdkVersion.d.ts +1 -1
- package/lib/typescript/module/types.d.ts +1 -1
- package/lib/typescript/module/types.d.ts.map +1 -1
- package/lib/typescript/module/wrap.types.d.ts +2 -2
- package/package.json +7 -16
- package/src/checkForUpdate.ts +4 -6
- package/src/clientPlugin.ts +17 -231
- package/src/httpClient.ts +4 -4
- package/src/index.ts +7 -0
- package/src/native.ts +1 -1
- package/src/pluginHost.ts +24 -241
- package/src/releaseCatalogCache.ts +7 -136
- package/src/sdkVersion.ts +1 -1
- package/src/types.ts +1 -1
- package/src/wrap.types.ts +2 -2
- package/android/src/test/java/com/hotupdater/BsdiffPatchTest.kt +0 -46
- package/android/src/test/java/com/hotupdater/BundleFileStorageServiceTest.kt +0 -2236
- package/android/src/test/java/com/hotupdater/HotUpdaterExceptionTest.kt +0 -220
- package/android/src/test/java/com/hotupdater/HotUpdaterImplTest.kt +0 -264
- package/android/src/test/java/com/hotupdater/HotUpdaterRecoveryStateTest.kt +0 -97
- package/android/src/test/java/com/hotupdater/InstallIdentityServiceTest.kt +0 -71
- package/android/src/test/java/com/hotupdater/KeyValueStorageServiceTest.kt +0 -106
- package/android/src/test/java/com/hotupdater/NativeConfigUtilsTest.kt +0 -20
- package/android/src/test/java/com/hotupdater/NoBackupContext.kt +0 -11
- package/android/src/test/java/com/hotupdater/OkHttpDownloadServiceTest.kt +0 -397
- package/android/src/test/java/com/hotupdater/PendingCrashRecoveryTest.kt +0 -44
- package/android/src/test/java/com/hotupdater/PreviousProcessExitTest.kt +0 -57
- package/android/src/test/java/com/hotupdater/RelativePathResolverTest.kt +0 -41
- package/android/src/test/java/com/hotupdater/ReleaseCatalogCacheServiceTest.kt +0 -118
- package/android/src/test/java/com/hotupdater/TarBrArchiveExtractorTest.kt +0 -206
- package/lib/commonjs/appReady.spec.js +0 -360
- package/lib/commonjs/appReady.spec.js.map +0 -1
- package/lib/commonjs/appReady.test-utils.js +0 -25
- package/lib/commonjs/appReady.test-utils.js.map +0 -1
- package/lib/commonjs/baseURL.js +0 -16
- package/lib/commonjs/baseURL.js.map +0 -1
- package/lib/commonjs/catalogCacheNative.spec.js +0 -31
- package/lib/commonjs/catalogCacheNative.spec.js.map +0 -1
- package/lib/commonjs/checkForUpdate.releaseCatalog.spec.js +0 -936
- package/lib/commonjs/checkForUpdate.releaseCatalog.spec.js.map +0 -1
- package/lib/commonjs/entryImportGraph.spec.js +0 -50
- package/lib/commonjs/entryImportGraph.spec.js.map +0 -1
- package/lib/commonjs/httpClient.spec.js +0 -171
- package/lib/commonjs/httpClient.spec.js.map +0 -1
- package/lib/commonjs/index.spec.js +0 -395
- package/lib/commonjs/index.spec.js.map +0 -1
- package/lib/commonjs/iosInterop.spec.js +0 -18
- package/lib/commonjs/iosInterop.spec.js.map +0 -1
- package/lib/commonjs/native.spec.js +0 -910
- package/lib/commonjs/native.spec.js.map +0 -1
- package/lib/commonjs/pluginHost.spec.js +0 -292
- package/lib/commonjs/pluginHost.spec.js.map +0 -1
- package/lib/commonjs/plugins/insights/eventId.js +0 -129
- package/lib/commonjs/plugins/insights/eventId.js.map +0 -1
- package/lib/commonjs/plugins/insights/eventId.spec.js +0 -47
- package/lib/commonjs/plugins/insights/eventId.spec.js.map +0 -1
- package/lib/commonjs/plugins/insights/index.js +0 -272
- package/lib/commonjs/plugins/insights/index.js.map +0 -1
- package/lib/commonjs/plugins/insights/insights.spec.js +0 -640
- package/lib/commonjs/plugins/insights/insights.spec.js.map +0 -1
- package/lib/commonjs/plugins/insights/sender.js +0 -128
- package/lib/commonjs/plugins/insights/sender.js.map +0 -1
- package/lib/commonjs/plugins/insights/sender.spec.js +0 -265
- package/lib/commonjs/plugins/insights/sender.spec.js.map +0 -1
- package/lib/commonjs/plugins/insights/state.js +0 -145
- package/lib/commonjs/plugins/insights/state.js.map +0 -1
- package/lib/commonjs/releaseCatalogCache.spec.js +0 -213
- package/lib/commonjs/releaseCatalogCache.spec.js.map +0 -1
- package/lib/commonjs/releaseCatalogCacheNativeContract.spec.js +0 -64
- package/lib/commonjs/releaseCatalogCacheNativeContract.spec.js.map +0 -1
- package/lib/commonjs/store.notification.spec.js +0 -84
- package/lib/commonjs/store.notification.spec.js.map +0 -1
- package/lib/commonjs/store.spec.js +0 -182
- package/lib/commonjs/store.spec.js.map +0 -1
- package/lib/commonjs/updateError.spec.js +0 -149
- package/lib/commonjs/updateError.spec.js.map +0 -1
- package/lib/commonjs/wrap.progress.spec.js +0 -195
- package/lib/commonjs/wrap.progress.spec.js.map +0 -1
- package/lib/commonjs/wrap.spec.js +0 -232
- package/lib/commonjs/wrap.spec.js.map +0 -1
- package/lib/module/appReady.spec.js +0 -359
- package/lib/module/appReady.spec.js.map +0 -1
- package/lib/module/appReady.test-utils.js +0 -19
- package/lib/module/appReady.test-utils.js.map +0 -1
- package/lib/module/baseURL.js +0 -11
- package/lib/module/baseURL.js.map +0 -1
- package/lib/module/catalogCacheNative.spec.js +0 -31
- package/lib/module/catalogCacheNative.spec.js.map +0 -1
- package/lib/module/checkForUpdate.releaseCatalog.spec.js +0 -935
- package/lib/module/checkForUpdate.releaseCatalog.spec.js.map +0 -1
- package/lib/module/entryImportGraph.spec.js +0 -50
- package/lib/module/entryImportGraph.spec.js.map +0 -1
- package/lib/module/httpClient.spec.js +0 -171
- package/lib/module/httpClient.spec.js.map +0 -1
- package/lib/module/index.spec.js +0 -394
- package/lib/module/index.spec.js.map +0 -1
- package/lib/module/iosInterop.spec.js +0 -18
- package/lib/module/iosInterop.spec.js.map +0 -1
- package/lib/module/native.spec.js +0 -909
- package/lib/module/native.spec.js.map +0 -1
- package/lib/module/pluginHost.spec.js +0 -291
- package/lib/module/pluginHost.spec.js.map +0 -1
- package/lib/module/plugins/insights/eventId.js +0 -122
- package/lib/module/plugins/insights/eventId.js.map +0 -1
- package/lib/module/plugins/insights/eventId.spec.js +0 -47
- package/lib/module/plugins/insights/eventId.spec.js.map +0 -1
- package/lib/module/plugins/insights/index.js +0 -268
- package/lib/module/plugins/insights/index.js.map +0 -1
- package/lib/module/plugins/insights/insights.spec.js +0 -639
- package/lib/module/plugins/insights/insights.spec.js.map +0 -1
- package/lib/module/plugins/insights/sender.js +0 -123
- package/lib/module/plugins/insights/sender.js.map +0 -1
- package/lib/module/plugins/insights/sender.spec.js +0 -265
- package/lib/module/plugins/insights/sender.spec.js.map +0 -1
- package/lib/module/plugins/insights/state.js +0 -139
- package/lib/module/plugins/insights/state.js.map +0 -1
- package/lib/module/releaseCatalogCache.spec.js +0 -213
- package/lib/module/releaseCatalogCache.spec.js.map +0 -1
- package/lib/module/releaseCatalogCacheNativeContract.spec.js +0 -64
- package/lib/module/releaseCatalogCacheNativeContract.spec.js.map +0 -1
- package/lib/module/store.notification.spec.js +0 -85
- package/lib/module/store.notification.spec.js.map +0 -1
- package/lib/module/store.spec.js +0 -181
- package/lib/module/store.spec.js.map +0 -1
- package/lib/module/updateError.spec.js +0 -149
- package/lib/module/updateError.spec.js.map +0 -1
- package/lib/module/wrap.progress.spec.js +0 -196
- package/lib/module/wrap.progress.spec.js.map +0 -1
- package/lib/module/wrap.spec.js +0 -233
- package/lib/module/wrap.spec.js.map +0 -1
- package/lib/typescript/commonjs/appReady.test-utils.d.ts +0 -9
- package/lib/typescript/commonjs/appReady.test-utils.d.ts.map +0 -1
- package/lib/typescript/commonjs/baseURL.d.ts +0 -4
- package/lib/typescript/commonjs/baseURL.d.ts.map +0 -1
- package/lib/typescript/commonjs/plugins/insights/eventId.d.ts +0 -18
- package/lib/typescript/commonjs/plugins/insights/eventId.d.ts.map +0 -1
- package/lib/typescript/commonjs/plugins/insights/index.d.ts +0 -40
- package/lib/typescript/commonjs/plugins/insights/index.d.ts.map +0 -1
- package/lib/typescript/commonjs/plugins/insights/sender.d.ts +0 -74
- package/lib/typescript/commonjs/plugins/insights/sender.d.ts.map +0 -1
- package/lib/typescript/commonjs/plugins/insights/state.d.ts +0 -46
- package/lib/typescript/commonjs/plugins/insights/state.d.ts.map +0 -1
- package/lib/typescript/module/appReady.test-utils.d.ts +0 -9
- package/lib/typescript/module/appReady.test-utils.d.ts.map +0 -1
- package/lib/typescript/module/baseURL.d.ts +0 -4
- package/lib/typescript/module/baseURL.d.ts.map +0 -1
- package/lib/typescript/module/plugins/insights/eventId.d.ts +0 -18
- package/lib/typescript/module/plugins/insights/eventId.d.ts.map +0 -1
- package/lib/typescript/module/plugins/insights/index.d.ts +0 -40
- package/lib/typescript/module/plugins/insights/index.d.ts.map +0 -1
- package/lib/typescript/module/plugins/insights/sender.d.ts +0 -74
- package/lib/typescript/module/plugins/insights/sender.d.ts.map +0 -1
- package/lib/typescript/module/plugins/insights/state.d.ts +0 -46
- package/lib/typescript/module/plugins/insights/state.d.ts.map +0 -1
- package/plugins/insights/package.json +0 -8
- package/src/appReady.spec.ts +0 -393
- package/src/appReady.test-utils.ts +0 -31
- package/src/baseURL.ts +0 -15
- package/src/catalogCacheNative.spec.ts +0 -63
- package/src/checkForUpdate.releaseCatalog.spec.ts +0 -966
- package/src/entryImportGraph.spec.ts +0 -65
- package/src/httpClient.spec.ts +0 -216
- package/src/index.spec.ts +0 -434
- package/src/iosInterop.spec.ts +0 -30
- package/src/native.spec.ts +0 -978
- package/src/pluginHost.spec.ts +0 -297
- package/src/plugins/insights/eventId.spec.ts +0 -60
- package/src/plugins/insights/eventId.ts +0 -171
- package/src/plugins/insights/index.ts +0 -367
- package/src/plugins/insights/insights.spec.ts +0 -732
- package/src/plugins/insights/sender.spec.ts +0 -287
- package/src/plugins/insights/sender.ts +0 -221
- package/src/plugins/insights/state.ts +0 -184
- package/src/releaseCatalogCache.spec.ts +0 -304
- package/src/releaseCatalogCacheNativeContract.spec.ts +0 -80
- package/src/store.notification.spec.ts +0 -102
- package/src/store.spec.ts +0 -215
- package/src/updateError.spec.ts +0 -144
- package/src/wrap.progress.spec.ts +0 -232
- package/src/wrap.spec.ts +0 -262
|
@@ -1,106 +0,0 @@
|
|
|
1
|
-
package com.hotupdater
|
|
2
|
-
|
|
3
|
-
import org.json.JSONObject
|
|
4
|
-
import org.junit.Assert.assertEquals
|
|
5
|
-
import org.junit.Assert.assertNull
|
|
6
|
-
import org.junit.Rule
|
|
7
|
-
import org.junit.Test
|
|
8
|
-
import org.junit.rules.TemporaryFolder
|
|
9
|
-
import java.io.File
|
|
10
|
-
|
|
11
|
-
class KeyValueStorageServiceTest {
|
|
12
|
-
@get:Rule
|
|
13
|
-
val temporaryFolder = TemporaryFolder()
|
|
14
|
-
|
|
15
|
-
@Test
|
|
16
|
-
fun `sets replaces and removes values`() {
|
|
17
|
-
val storage = KeyValueStorageService(storageFile())
|
|
18
|
-
assertNull(storage.getItem("plugins/insights/queue"))
|
|
19
|
-
|
|
20
|
-
storage.setItem("plugins/insights/queue", "[1]")
|
|
21
|
-
storage.setItem("plugins/insights/day", "2026-09-30")
|
|
22
|
-
storage.setItem("plugins/insights/queue", "[1,2]")
|
|
23
|
-
storage.setItem("plugins/other/empty", "")
|
|
24
|
-
assertEquals("[1,2]", storage.getItem("plugins/insights/queue"))
|
|
25
|
-
assertEquals("", storage.getItem("plugins/other/empty"))
|
|
26
|
-
|
|
27
|
-
storage.setItem("plugins/insights/queue", null)
|
|
28
|
-
storage.setItem("plugins/never-set", null)
|
|
29
|
-
assertNull(storage.getItem("plugins/insights/queue"))
|
|
30
|
-
assertEquals("2026-09-30", storage.getItem("plugins/insights/day"))
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
@Test
|
|
34
|
-
fun `values persist across instances`() {
|
|
35
|
-
val file = storageFile()
|
|
36
|
-
KeyValueStorageService(file).apply {
|
|
37
|
-
setItem("plugins/insights/queue", "[{\"id\":\"é\",\"note\":\"line\\nbreak\"}]")
|
|
38
|
-
setItem("plugins/insights/day", "2026-09-30")
|
|
39
|
-
setItem("plugins/insights/day", null)
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
val reloaded = KeyValueStorageService(file)
|
|
43
|
-
|
|
44
|
-
assertEquals("[{\"id\":\"é\",\"note\":\"line\\nbreak\"}]", reloaded.getItem("plugins/insights/queue"))
|
|
45
|
-
assertNull(reloaded.getItem("plugins/insights/day"))
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
@Test
|
|
49
|
-
fun `a corrupt file reads as empty and the next set replaces it`() {
|
|
50
|
-
val file = storageFile().apply { writeText("{\"plugins/a\":") }
|
|
51
|
-
val storage = KeyValueStorageService(file)
|
|
52
|
-
|
|
53
|
-
assertNull(storage.getItem("plugins/a"))
|
|
54
|
-
storage.setItem("plugins/b", "value")
|
|
55
|
-
|
|
56
|
-
assertEquals(mapOf("plugins/b" to "value"), readStored(file))
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
@Test
|
|
60
|
-
fun `values that are not strings read as missing`() {
|
|
61
|
-
val file = storageFile().apply { writeText("{\"plugins/a\":1,\"plugins/b\":\"kept\",\"plugins/c\":null}") }
|
|
62
|
-
val storage = KeyValueStorageService(file)
|
|
63
|
-
|
|
64
|
-
assertNull(storage.getItem("plugins/a"))
|
|
65
|
-
assertEquals("kept", storage.getItem("plugins/b"))
|
|
66
|
-
assertNull(storage.getItem("plugins/c"))
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
@Test
|
|
70
|
-
fun `concurrent sets are all persisted`() {
|
|
71
|
-
val file = storageFile()
|
|
72
|
-
val storage = KeyValueStorageService(file)
|
|
73
|
-
|
|
74
|
-
(0 until 4)
|
|
75
|
-
.map { worker ->
|
|
76
|
-
Thread {
|
|
77
|
-
repeat(25) { index -> storage.setItem("plugins/worker-$worker/$index", "$worker:$index") }
|
|
78
|
-
}.apply { start() }
|
|
79
|
-
}.forEach(Thread::join)
|
|
80
|
-
|
|
81
|
-
val stored = readStored(file)
|
|
82
|
-
assertEquals(100, stored.size)
|
|
83
|
-
(0 until 4).forEach { worker ->
|
|
84
|
-
repeat(25) { index -> assertEquals("$worker:$index", stored["plugins/worker-$worker/$index"]) }
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
@Test
|
|
89
|
-
fun `is stored under noBackupFilesDir`() {
|
|
90
|
-
val noBackupDirectory = temporaryFolder.newFolder("no_backup")
|
|
91
|
-
|
|
92
|
-
KeyValueStorageService.create(NoBackupContext(noBackupDirectory)).setItem("plugins/insights/queue", "[]")
|
|
93
|
-
|
|
94
|
-
assertEquals(
|
|
95
|
-
mapOf("plugins/insights/queue" to "[]"),
|
|
96
|
-
readStored(File(noBackupDirectory, "hot-updater/storage.json")),
|
|
97
|
-
)
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
private fun storageFile(): File = File(temporaryFolder.newFolder(), "hot-updater/storage.json").apply { parentFile?.mkdirs() }
|
|
101
|
-
|
|
102
|
-
private fun readStored(file: File): Map<String, String> {
|
|
103
|
-
val json = JSONObject(file.readText())
|
|
104
|
-
return json.keys().asSequence().associateWith { json.getString(it) }
|
|
105
|
-
}
|
|
106
|
-
}
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
package com.hotupdater
|
|
2
|
-
|
|
3
|
-
import org.junit.Assert.assertEquals
|
|
4
|
-
import org.junit.Assert.assertNull
|
|
5
|
-
import org.junit.Test
|
|
6
|
-
|
|
7
|
-
class NativeConfigUtilsTest {
|
|
8
|
-
@Test
|
|
9
|
-
fun `coerces typed manifest metadata to strings`() {
|
|
10
|
-
assertEquals("123", NativeConfigUtils.coerceManifestMetaDataString(123))
|
|
11
|
-
assertEquals("true", NativeConfigUtils.coerceManifestMetaDataString(true))
|
|
12
|
-
assertEquals("-16711936", NativeConfigUtils.coerceManifestMetaDataString(-16711936))
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
@Test
|
|
16
|
-
fun `ignores missing and empty manifest metadata`() {
|
|
17
|
-
assertNull(NativeConfigUtils.coerceManifestMetaDataString(null))
|
|
18
|
-
assertNull(NativeConfigUtils.coerceManifestMetaDataString(""))
|
|
19
|
-
}
|
|
20
|
-
}
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
package com.hotupdater
|
|
2
|
-
|
|
3
|
-
import android.content.ContextWrapper
|
|
4
|
-
import java.io.File
|
|
5
|
-
|
|
6
|
-
/** A context whose `noBackupFilesDir` is a test directory. */
|
|
7
|
-
internal class NoBackupContext(
|
|
8
|
-
private val noBackupDirectory: File,
|
|
9
|
-
) : ContextWrapper(null) {
|
|
10
|
-
override fun getNoBackupFilesDir(): File = noBackupDirectory
|
|
11
|
-
}
|
|
@@ -1,397 +0,0 @@
|
|
|
1
|
-
package com.hotupdater
|
|
2
|
-
|
|
3
|
-
import kotlinx.coroutines.runBlocking
|
|
4
|
-
import okhttp3.ResponseBody.Companion.toResponseBody
|
|
5
|
-
import org.junit.Assert.assertArrayEquals
|
|
6
|
-
import org.junit.Assert.assertEquals
|
|
7
|
-
import org.junit.Assert.assertFalse
|
|
8
|
-
import org.junit.Assert.assertNull
|
|
9
|
-
import org.junit.Assert.assertTrue
|
|
10
|
-
import org.junit.Rule
|
|
11
|
-
import org.junit.Test
|
|
12
|
-
import org.junit.rules.TemporaryFolder
|
|
13
|
-
import java.io.File
|
|
14
|
-
import java.net.InetAddress
|
|
15
|
-
import java.net.ServerSocket
|
|
16
|
-
import java.net.Socket
|
|
17
|
-
import java.net.URL
|
|
18
|
-
import kotlin.concurrent.thread
|
|
19
|
-
|
|
20
|
-
class OkHttpDownloadServiceTest {
|
|
21
|
-
@get:Rule
|
|
22
|
-
val temporaryFolder = TemporaryFolder()
|
|
23
|
-
|
|
24
|
-
@Test
|
|
25
|
-
fun `downloadFile succeeds when content length is unknown`() =
|
|
26
|
-
runBlocking {
|
|
27
|
-
val payload = "bundle-content-without-content-length".repeat(512).toByteArray()
|
|
28
|
-
val server = ChunkedResponseServer(payload)
|
|
29
|
-
|
|
30
|
-
try {
|
|
31
|
-
val destinationDir = temporaryFolder.newFolder("downloads")
|
|
32
|
-
val destination = File(destinationDir, "bundle.android.bundle")
|
|
33
|
-
var reportedSize: Long? = null
|
|
34
|
-
|
|
35
|
-
val result =
|
|
36
|
-
OkHttpDownloadService().downloadFile(
|
|
37
|
-
fileUrl = URL("http://127.0.0.1:${server.port}/bundle"),
|
|
38
|
-
destination = destination,
|
|
39
|
-
fileSizeCallback = { size -> reportedSize = size },
|
|
40
|
-
progressCallback = {},
|
|
41
|
-
)
|
|
42
|
-
|
|
43
|
-
assertTrue(result is DownloadResult.Success)
|
|
44
|
-
assertEquals(payload.size.toLong(), destination.length())
|
|
45
|
-
assertArrayEquals(payload, destination.readBytes())
|
|
46
|
-
assertNull(reportedSize)
|
|
47
|
-
} finally {
|
|
48
|
-
server.close()
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
@Test
|
|
53
|
-
fun `downloadFile reports a non-2xx response with its status and origin error code`() =
|
|
54
|
-
runBlocking {
|
|
55
|
-
val errorBody =
|
|
56
|
-
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
|
|
57
|
-
"<Error><Code>NoSuchKey</Code><Message>The specified key does not exist.</Message>" +
|
|
58
|
-
"<Key>bundles/secret/manifest.json</Key></Error>"
|
|
59
|
-
val server = ChunkedResponseServer(errorBody.toByteArray(), statusLine = "404 Not Found")
|
|
60
|
-
|
|
61
|
-
try {
|
|
62
|
-
val destination = File(temporaryFolder.newFolder("http-error"), "manifest.json")
|
|
63
|
-
|
|
64
|
-
val result =
|
|
65
|
-
OkHttpDownloadService().downloadFile(
|
|
66
|
-
fileUrl = URL("http://127.0.0.1:${server.port}/manifest.json"),
|
|
67
|
-
destination = destination,
|
|
68
|
-
progressCallback = {},
|
|
69
|
-
)
|
|
70
|
-
|
|
71
|
-
val error = (result as DownloadResult.Error).exception
|
|
72
|
-
assertTrue("Expected HttpStatusException, got $error", error is HttpStatusException)
|
|
73
|
-
assertEquals(404, (error as HttpStatusException).statusCode)
|
|
74
|
-
assertEquals("NoSuchKey", error.originCode)
|
|
75
|
-
assertEquals("HTTP error 404: Not Found", error.message)
|
|
76
|
-
assertFalse(destination.exists())
|
|
77
|
-
} finally {
|
|
78
|
-
server.close()
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
@Test
|
|
83
|
-
fun `origin error codes come only from an XML Error Code of safe characters`() {
|
|
84
|
-
val cases =
|
|
85
|
-
mapOf(
|
|
86
|
-
"<Error><Code>AccessDenied</Code><Message>Access Denied</Message></Error>" to "AccessDenied",
|
|
87
|
-
"<?xml version='1.0' encoding='UTF-8'?><Error><Code>SignatureDoesNotMatch</Code></Error>" to "SignatureDoesNotMatch",
|
|
88
|
-
"<Error xmlns=\"urn:storage\">\n <Message>first</Message>\n <Code> ExpiredToken </Code>\n</Error>" to "ExpiredToken",
|
|
89
|
-
"<Error><Code>Rate.Limit_Exceeded-2</Code></Error>" to "Rate.Limit_Exceeded-2",
|
|
90
|
-
"<Error><Code>${"A".repeat(64)}</Code></Error>" to "A".repeat(64),
|
|
91
|
-
"<Error><Code>${"A".repeat(65)}</Code></Error>" to null,
|
|
92
|
-
"<Error><Code>Access Denied</Code></Error>" to null,
|
|
93
|
-
"<Error><Code>bundles/secret.json</Code></Error>" to null,
|
|
94
|
-
"<Error><Code></Code></Error>" to null,
|
|
95
|
-
"<Error><Message>No code</Message></Error><Code>Outside</Code>" to null,
|
|
96
|
-
"<Response><Code>NotAnError</Code></Response>" to null,
|
|
97
|
-
"Not Found" to null,
|
|
98
|
-
)
|
|
99
|
-
|
|
100
|
-
cases.forEach { (body, expected) -> assertEquals(body, expected, parseOriginErrorCode(body)) }
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
@Test
|
|
104
|
-
fun `origin error codes are read from the first 4 KB of the body only`() {
|
|
105
|
-
val limit = MAX_ORIGIN_ERROR_BODY_BYTES.toInt()
|
|
106
|
-
val code = "<Error><Code>AccessDenied</Code>"
|
|
107
|
-
val endsAtLimit = " ".repeat(limit - code.length) + code + "</Error>"
|
|
108
|
-
val closesPastLimit = " ".repeat(limit - code.length + 1) + code + "</Error>"
|
|
109
|
-
|
|
110
|
-
assertEquals("AccessDenied", readOriginErrorCode(endsAtLimit.toResponseBody()))
|
|
111
|
-
assertNull(readOriginErrorCode(closesPastLimit.toResponseBody()))
|
|
112
|
-
assertNull(readOriginErrorCode(null))
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
@Test
|
|
116
|
-
fun `downloadFile reports a destination it cannot write as a local storage failure`() =
|
|
117
|
-
runBlocking {
|
|
118
|
-
val server = ChunkedResponseServer("bundle-content".toByteArray())
|
|
119
|
-
|
|
120
|
-
try {
|
|
121
|
-
val notADirectory = temporaryFolder.newFile("not-a-directory")
|
|
122
|
-
|
|
123
|
-
val result =
|
|
124
|
-
OkHttpDownloadService().downloadFile(
|
|
125
|
-
fileUrl = URL("http://127.0.0.1:${server.port}/bundle"),
|
|
126
|
-
destination = File(notADirectory, "index.android.bundle"),
|
|
127
|
-
progressCallback = {},
|
|
128
|
-
)
|
|
129
|
-
|
|
130
|
-
val error = (result as DownloadResult.Error).exception
|
|
131
|
-
assertTrue("Expected LocalStorageException, got $error", error is LocalStorageException)
|
|
132
|
-
} finally {
|
|
133
|
-
server.close()
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
@Test
|
|
138
|
-
fun `downloadFile tries a transient failure again and succeeds on the next attempt`() =
|
|
139
|
-
runBlocking {
|
|
140
|
-
val payload = "bundle-after-retry".toByteArray()
|
|
141
|
-
val server =
|
|
142
|
-
SequencedResponseServer(
|
|
143
|
-
listOf(
|
|
144
|
-
"503 Service Unavailable" to "busy".toByteArray(),
|
|
145
|
-
"200 OK" to payload,
|
|
146
|
-
),
|
|
147
|
-
)
|
|
148
|
-
|
|
149
|
-
try {
|
|
150
|
-
val destination = File(temporaryFolder.newFolder("retry"), "index.android.bundle")
|
|
151
|
-
|
|
152
|
-
val result =
|
|
153
|
-
OkHttpDownloadService(initialRetryDelayMs = 1).downloadFile(
|
|
154
|
-
fileUrl = URL("http://127.0.0.1:${server.port}/bundle"),
|
|
155
|
-
destination = destination,
|
|
156
|
-
progressCallback = {},
|
|
157
|
-
)
|
|
158
|
-
|
|
159
|
-
assertTrue("Expected success, got $result", result is DownloadResult.Success)
|
|
160
|
-
assertArrayEquals(payload, destination.readBytes())
|
|
161
|
-
assertEquals(2, server.requestCount)
|
|
162
|
-
} finally {
|
|
163
|
-
server.close()
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
@Test
|
|
168
|
-
fun `downloadFile tries a 404 once`() =
|
|
169
|
-
runBlocking {
|
|
170
|
-
val server =
|
|
171
|
-
SequencedResponseServer(
|
|
172
|
-
listOf(
|
|
173
|
-
"404 Not Found" to "missing".toByteArray(),
|
|
174
|
-
"200 OK" to "never-sent".toByteArray(),
|
|
175
|
-
),
|
|
176
|
-
)
|
|
177
|
-
|
|
178
|
-
try {
|
|
179
|
-
val result =
|
|
180
|
-
OkHttpDownloadService(initialRetryDelayMs = 1).downloadFile(
|
|
181
|
-
fileUrl = URL("http://127.0.0.1:${server.port}/bundle"),
|
|
182
|
-
destination = File(temporaryFolder.newFolder("not-found"), "index.android.bundle"),
|
|
183
|
-
progressCallback = {},
|
|
184
|
-
)
|
|
185
|
-
|
|
186
|
-
val error = (result as DownloadResult.Error).exception
|
|
187
|
-
assertEquals(404, (error as HttpStatusException).statusCode)
|
|
188
|
-
assertEquals(1, server.requestCount)
|
|
189
|
-
} finally {
|
|
190
|
-
server.close()
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
@Test
|
|
195
|
-
fun `downloadFile stops after three attempts and returns the last classified failure`() =
|
|
196
|
-
runBlocking {
|
|
197
|
-
val server =
|
|
198
|
-
SequencedResponseServer(
|
|
199
|
-
listOf(
|
|
200
|
-
"500 Internal Server Error" to "one".toByteArray(),
|
|
201
|
-
"429 Too Many Requests" to "two".toByteArray(),
|
|
202
|
-
"502 Bad Gateway" to "<Error><Code>SlowDown</Code></Error>".toByteArray(),
|
|
203
|
-
"200 OK" to "never-sent".toByteArray(),
|
|
204
|
-
),
|
|
205
|
-
)
|
|
206
|
-
|
|
207
|
-
try {
|
|
208
|
-
val result =
|
|
209
|
-
OkHttpDownloadService(initialRetryDelayMs = 1).downloadFile(
|
|
210
|
-
fileUrl = URL("http://127.0.0.1:${server.port}/bundle"),
|
|
211
|
-
destination = File(temporaryFolder.newFolder("exhausted"), "index.android.bundle"),
|
|
212
|
-
progressCallback = {},
|
|
213
|
-
)
|
|
214
|
-
|
|
215
|
-
val error = (result as DownloadResult.Error).exception as HttpStatusException
|
|
216
|
-
assertEquals(502, error.statusCode)
|
|
217
|
-
assertEquals("SlowDown", error.originCode)
|
|
218
|
-
assertEquals(UpdateFailure.http(502, "SlowDown"), UpdateFailure.ofDownloadError(error))
|
|
219
|
-
assertEquals(OkHttpDownloadService.MAX_ATTEMPTS, server.requestCount)
|
|
220
|
-
} finally {
|
|
221
|
-
server.close()
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
@Test
|
|
226
|
-
fun `downloadFileOnce does not retry`() =
|
|
227
|
-
runBlocking {
|
|
228
|
-
val server =
|
|
229
|
-
SequencedResponseServer(
|
|
230
|
-
listOf(
|
|
231
|
-
"503 Service Unavailable" to "busy".toByteArray(),
|
|
232
|
-
"200 OK" to "never-sent".toByteArray(),
|
|
233
|
-
),
|
|
234
|
-
)
|
|
235
|
-
|
|
236
|
-
try {
|
|
237
|
-
val result =
|
|
238
|
-
OkHttpDownloadService(initialRetryDelayMs = 1).downloadFileOnce(
|
|
239
|
-
fileUrl = URL("http://127.0.0.1:${server.port}/bundle.tar.br"),
|
|
240
|
-
destination = File(temporaryFolder.newFolder("once"), "bundle.tar.br"),
|
|
241
|
-
progressCallback = {},
|
|
242
|
-
)
|
|
243
|
-
|
|
244
|
-
assertTrue(result is DownloadResult.Error)
|
|
245
|
-
assertEquals(1, server.requestCount)
|
|
246
|
-
} finally {
|
|
247
|
-
server.close()
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
@Test
|
|
252
|
-
fun `only transient download failures are retryable`() {
|
|
253
|
-
val retryable =
|
|
254
|
-
listOf(
|
|
255
|
-
HttpStatusException(408, "Request Timeout"),
|
|
256
|
-
HttpStatusException(429, "Too Many Requests"),
|
|
257
|
-
HttpStatusException(500, "Internal Server Error"),
|
|
258
|
-
HttpStatusException(503, "Service Unavailable"),
|
|
259
|
-
java.net.SocketTimeoutException("timeout"),
|
|
260
|
-
java.net.UnknownHostException("updates.example.com"),
|
|
261
|
-
java.net.ConnectException("Connection refused"),
|
|
262
|
-
IncompleteDownloadException(expectedSize = 10, actualSize = 4),
|
|
263
|
-
)
|
|
264
|
-
val final =
|
|
265
|
-
listOf(
|
|
266
|
-
HttpStatusException(400, "Bad Request"),
|
|
267
|
-
HttpStatusException(403, "Forbidden", "ExpiredToken"),
|
|
268
|
-
HttpStatusException(404, "Not Found"),
|
|
269
|
-
LocalStorageException(java.io.IOException("No space left on device")),
|
|
270
|
-
javax.net.ssl.SSLHandshakeException("certificate expired"),
|
|
271
|
-
java.io.IOException("Canceled"),
|
|
272
|
-
java.net.MalformedURLException("no protocol"),
|
|
273
|
-
IllegalArgumentException("unexpected url"),
|
|
274
|
-
Exception("Response body is null"),
|
|
275
|
-
)
|
|
276
|
-
|
|
277
|
-
retryable.forEach { assertTrue("$it should be retryable", isRetryableDownloadError(it)) }
|
|
278
|
-
final.forEach { assertFalse("$it should not be retryable", isRetryableDownloadError(it)) }
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
/** Answers each connection with the next response, then closes. */
|
|
282
|
-
private class SequencedResponseServer(
|
|
283
|
-
private val responses: List<Pair<String, ByteArray>>,
|
|
284
|
-
) : AutoCloseable {
|
|
285
|
-
private val serverSocket = ServerSocket(0, responses.size, InetAddress.getByName("127.0.0.1"))
|
|
286
|
-
|
|
287
|
-
@Volatile
|
|
288
|
-
var requestCount = 0
|
|
289
|
-
private set
|
|
290
|
-
|
|
291
|
-
private val worker =
|
|
292
|
-
thread(start = true, isDaemon = true) {
|
|
293
|
-
try {
|
|
294
|
-
for ((statusLine, payload) in responses) {
|
|
295
|
-
serverSocket.accept().use { client ->
|
|
296
|
-
requestCount++
|
|
297
|
-
respond(client, statusLine, payload)
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
} catch (_: java.net.SocketException) {
|
|
301
|
-
// The test closed the server before every response was asked for.
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
val port: Int = serverSocket.localPort
|
|
306
|
-
|
|
307
|
-
private fun respond(
|
|
308
|
-
client: Socket,
|
|
309
|
-
statusLine: String,
|
|
310
|
-
payload: ByteArray,
|
|
311
|
-
) {
|
|
312
|
-
drainRequestHeaders(client)
|
|
313
|
-
client.getOutputStream().use { output ->
|
|
314
|
-
output.write(
|
|
315
|
-
(
|
|
316
|
-
"HTTP/1.1 $statusLine\r\n" +
|
|
317
|
-
"Content-Type: application/octet-stream\r\n" +
|
|
318
|
-
"Content-Length: ${payload.size}\r\n" +
|
|
319
|
-
"Connection: close\r\n" +
|
|
320
|
-
"\r\n"
|
|
321
|
-
).toByteArray(),
|
|
322
|
-
)
|
|
323
|
-
output.write(payload)
|
|
324
|
-
output.flush()
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
override fun close() {
|
|
329
|
-
serverSocket.close()
|
|
330
|
-
worker.join(1_000)
|
|
331
|
-
}
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
private class ChunkedResponseServer(
|
|
335
|
-
private val payload: ByteArray,
|
|
336
|
-
private val statusLine: String = "200 OK",
|
|
337
|
-
) : AutoCloseable {
|
|
338
|
-
private val serverSocket = ServerSocket(0, 1, InetAddress.getByName("127.0.0.1"))
|
|
339
|
-
private val worker =
|
|
340
|
-
thread(start = true, isDaemon = true) {
|
|
341
|
-
serverSocket.use { socket ->
|
|
342
|
-
socket.accept().use(::respond)
|
|
343
|
-
}
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
val port: Int = serverSocket.localPort
|
|
347
|
-
|
|
348
|
-
private fun respond(client: Socket) {
|
|
349
|
-
drainRequestHeaders(client)
|
|
350
|
-
|
|
351
|
-
client.getOutputStream().use { output ->
|
|
352
|
-
output.write(
|
|
353
|
-
(
|
|
354
|
-
"HTTP/1.1 $statusLine\r\n" +
|
|
355
|
-
"Content-Type: application/octet-stream\r\n" +
|
|
356
|
-
"Transfer-Encoding: chunked\r\n" +
|
|
357
|
-
"Connection: close\r\n" +
|
|
358
|
-
"\r\n"
|
|
359
|
-
).toByteArray(),
|
|
360
|
-
)
|
|
361
|
-
output.write(payload.size.toString(16).toByteArray())
|
|
362
|
-
output.write("\r\n".toByteArray())
|
|
363
|
-
output.write(payload)
|
|
364
|
-
output.write("\r\n0\r\n\r\n".toByteArray())
|
|
365
|
-
output.flush()
|
|
366
|
-
}
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
override fun close() {
|
|
370
|
-
serverSocket.close()
|
|
371
|
-
worker.join(1_000)
|
|
372
|
-
}
|
|
373
|
-
}
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
/** Reads a request up to the blank line after its headers. */
|
|
377
|
-
private fun drainRequestHeaders(client: Socket) {
|
|
378
|
-
val input = client.getInputStream()
|
|
379
|
-
var matched = 0
|
|
380
|
-
val terminator = byteArrayOf('\r'.code.toByte(), '\n'.code.toByte(), '\r'.code.toByte(), '\n'.code.toByte())
|
|
381
|
-
|
|
382
|
-
while (matched < terminator.size) {
|
|
383
|
-
val next = input.read()
|
|
384
|
-
if (next == -1) {
|
|
385
|
-
break
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
matched =
|
|
389
|
-
if (next.toByte() == terminator[matched]) {
|
|
390
|
-
matched + 1
|
|
391
|
-
} else if (next.toByte() == terminator[0]) {
|
|
392
|
-
1
|
|
393
|
-
} else {
|
|
394
|
-
0
|
|
395
|
-
}
|
|
396
|
-
}
|
|
397
|
-
}
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
package com.hotupdater
|
|
2
|
-
|
|
3
|
-
import org.junit.Assert.assertEquals
|
|
4
|
-
import org.junit.Assert.assertFalse
|
|
5
|
-
import org.junit.Assert.assertNull
|
|
6
|
-
import org.junit.Rule
|
|
7
|
-
import org.junit.Test
|
|
8
|
-
import org.junit.rules.TemporaryFolder
|
|
9
|
-
import java.io.File
|
|
10
|
-
|
|
11
|
-
class PendingCrashRecoveryTest {
|
|
12
|
-
@get:Rule
|
|
13
|
-
val temporaryFolder = TemporaryFolder()
|
|
14
|
-
|
|
15
|
-
@Test
|
|
16
|
-
fun `a watchdog reader waits until the native crash marker is fully written`() {
|
|
17
|
-
val marker = temporaryFolder.newFile("crash-marker.json")
|
|
18
|
-
marker.writeText("{\"bundleId\":\"crashed-bundle\",\"shouldRollback\":")
|
|
19
|
-
|
|
20
|
-
assertNull(PendingCrashRecovery.loadFromFile(marker))
|
|
21
|
-
|
|
22
|
-
marker.appendText("true}\n")
|
|
23
|
-
assertEquals(
|
|
24
|
-
PendingCrashRecovery("crashed-bundle", true),
|
|
25
|
-
PendingCrashRecovery.loadFromFile(marker),
|
|
26
|
-
)
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
@Test
|
|
30
|
-
fun `missing or empty markers do not request a restart`() {
|
|
31
|
-
assertNull(PendingCrashRecovery.loadFromFile(File(temporaryFolder.root, "missing.json")))
|
|
32
|
-
assertNull(PendingCrashRecovery.loadFromFile(temporaryFolder.newFile("empty.json")))
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
@Test
|
|
36
|
-
fun `a crash after content appeared does not request rollback`() {
|
|
37
|
-
val marker = temporaryFolder.newFile("verified-crash.json")
|
|
38
|
-
marker.writeText("{\"bundleId\":\"verified-bundle\",\"shouldRollback\":false}")
|
|
39
|
-
|
|
40
|
-
val recovery = requireNotNull(PendingCrashRecovery.loadFromFile(marker))
|
|
41
|
-
assertFalse(recovery.shouldRollback)
|
|
42
|
-
assertEquals("verified-bundle", recovery.launchedBundleId)
|
|
43
|
-
}
|
|
44
|
-
}
|
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
package com.hotupdater
|
|
2
|
-
|
|
3
|
-
import com.hotupdater.PreviousProcessExit.ExitRecord
|
|
4
|
-
import org.junit.Assert.assertEquals
|
|
5
|
-
import org.junit.Assert.assertNull
|
|
6
|
-
import org.junit.Test
|
|
7
|
-
|
|
8
|
-
class PreviousProcessExitTest {
|
|
9
|
-
@Test
|
|
10
|
-
fun `reasons are named after ApplicationExitInfo without the REASON prefix`() {
|
|
11
|
-
val names =
|
|
12
|
-
listOf(
|
|
13
|
-
"UNKNOWN",
|
|
14
|
-
"EXIT_SELF",
|
|
15
|
-
"SIGNALED",
|
|
16
|
-
"LOW_MEMORY",
|
|
17
|
-
"CRASH",
|
|
18
|
-
"CRASH_NATIVE",
|
|
19
|
-
"ANR",
|
|
20
|
-
"INITIALIZATION_FAILURE",
|
|
21
|
-
"PERMISSION_CHANGE",
|
|
22
|
-
"EXCESSIVE_RESOURCE_USAGE",
|
|
23
|
-
"USER_REQUESTED",
|
|
24
|
-
"USER_STOPPED",
|
|
25
|
-
"DEPENDENCY_DIED",
|
|
26
|
-
"OTHER",
|
|
27
|
-
"FREEZER",
|
|
28
|
-
"PACKAGE_STATE_CHANGE",
|
|
29
|
-
"PACKAGE_UPDATED",
|
|
30
|
-
)
|
|
31
|
-
|
|
32
|
-
assertEquals(names, names.indices.map(PreviousProcessExit::reasonName))
|
|
33
|
-
assertEquals("UNKNOWN", PreviousProcessExit.reasonName(99))
|
|
34
|
-
assertEquals("UNKNOWN", PreviousProcessExit.reasonName(-1))
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
@Test
|
|
38
|
-
fun `the most recent exit of the main process is selected`() {
|
|
39
|
-
val records =
|
|
40
|
-
listOf(
|
|
41
|
-
ExitRecord("com.example:hotupdater_restart", reason = 1, timestamp = 300),
|
|
42
|
-
ExitRecord("com.example", reason = 4, timestamp = 100),
|
|
43
|
-
ExitRecord("com.example", reason = 6, timestamp = 200),
|
|
44
|
-
ExitRecord("com.example:sync", reason = 3, timestamp = 250),
|
|
45
|
-
ExitRecord(null, reason = 5, timestamp = 400),
|
|
46
|
-
)
|
|
47
|
-
|
|
48
|
-
assertEquals(6, PreviousProcessExit.selectReason(records, "com.example"))
|
|
49
|
-
assertNull(PreviousProcessExit.selectReason(records.filter { it.processName != "com.example" }, "com.example"))
|
|
50
|
-
assertNull(PreviousProcessExit.selectReason(emptyList(), "com.example"))
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
@Test
|
|
54
|
-
fun `nothing is reported before HotUpdater initializes`() {
|
|
55
|
-
assertNull(PreviousProcessExit.get())
|
|
56
|
-
}
|
|
57
|
-
}
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
package com.hotupdater
|
|
2
|
-
|
|
3
|
-
import org.junit.Assert.assertEquals
|
|
4
|
-
import org.junit.Assert.assertNull
|
|
5
|
-
import org.junit.Rule
|
|
6
|
-
import org.junit.Test
|
|
7
|
-
import org.junit.rules.TemporaryFolder
|
|
8
|
-
import java.io.File
|
|
9
|
-
|
|
10
|
-
class RelativePathResolverTest {
|
|
11
|
-
@get:Rule
|
|
12
|
-
val temporaryFolder = TemporaryFolder()
|
|
13
|
-
|
|
14
|
-
@Test
|
|
15
|
-
fun `resolves a standard archive directory entry ending in a slash`() {
|
|
16
|
-
val root = temporaryFolder.root
|
|
17
|
-
|
|
18
|
-
val resolved = RelativePathResolver.resolveInside(root, "drawable-mdpi/")
|
|
19
|
-
|
|
20
|
-
assertEquals(
|
|
21
|
-
File(root, "drawable-mdpi").canonicalFile,
|
|
22
|
-
resolved?.canonicalFile,
|
|
23
|
-
)
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
@Test
|
|
27
|
-
fun `rejects unsafe archive directory paths ending in a slash`() {
|
|
28
|
-
val root = temporaryFolder.root
|
|
29
|
-
val unsafePaths =
|
|
30
|
-
listOf(
|
|
31
|
-
"/",
|
|
32
|
-
"../",
|
|
33
|
-
"drawable-mdpi/../",
|
|
34
|
-
"drawable-mdpi//",
|
|
35
|
-
)
|
|
36
|
-
|
|
37
|
-
unsafePaths.forEach { path ->
|
|
38
|
-
assertNull(path, RelativePathResolver.resolveInside(root, path))
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
}
|