From 149c6be1bfd4bd84381757940fece1be7b9801aa Mon Sep 17 00:00:00 2001 From: Allan Wang Date: Mon, 31 Dec 2018 18:57:28 -0500 Subject: Enhancement/coroutines (#1273) * Convert rest of fbcookie to suspended methods * Replace active checks with yield * Apply spotless * Switch cookie domain to exact url * Optimize imports and enable travis tests again * Update proguard rules * Remove unnecessary yield * Remove unused flyweight * Remove unused disposable and method * Use contexthelper instead of dispatcher main * Convert login activity to coroutines * Use kau helper methods for coroutines * Enhancement/offline site (#1288) * Begin conversion of offline site logic * Fix offline tests and add validation tests * Ignore cookie in jsoup if it is blank * Force load and zip to be in io * Use different zip files to fix tests * Log all test output * Do not log stdout * Allow test skip for fb offline --- .../frost/debugger/OfflineWebsiteTest.kt | 225 ++++++++++++++++++++- .../com/pitchedapps/frost/kotlin/FlyweightTest.kt | 123 +++++++++++ .../com/pitchedapps/frost/rx/FlyweightTest.kt | 124 ------------ .../frost/rx/ResettableFlyweightTest.kt | 73 ------- 4 files changed, 339 insertions(+), 206 deletions(-) create mode 100644 app/src/test/kotlin/com/pitchedapps/frost/kotlin/FlyweightTest.kt delete mode 100644 app/src/test/kotlin/com/pitchedapps/frost/rx/FlyweightTest.kt delete mode 100644 app/src/test/kotlin/com/pitchedapps/frost/rx/ResettableFlyweightTest.kt (limited to 'app/src/test/kotlin/com') diff --git a/app/src/test/kotlin/com/pitchedapps/frost/debugger/OfflineWebsiteTest.kt b/app/src/test/kotlin/com/pitchedapps/frost/debugger/OfflineWebsiteTest.kt index f7dad4d3..07c92fbf 100644 --- a/app/src/test/kotlin/com/pitchedapps/frost/debugger/OfflineWebsiteTest.kt +++ b/app/src/test/kotlin/com/pitchedapps/frost/debugger/OfflineWebsiteTest.kt @@ -18,24 +18,231 @@ package com.pitchedapps.frost.debugger import com.pitchedapps.frost.facebook.FB_URL_BASE import com.pitchedapps.frost.internal.COOKIE +import kotlinx.coroutines.runBlocking +import okhttp3.mockwebserver.Dispatcher +import okhttp3.mockwebserver.MockResponse +import okhttp3.mockwebserver.MockWebServer +import okhttp3.mockwebserver.RecordedRequest +import org.junit.Assume.assumeTrue import org.junit.Test import java.io.File -import java.util.concurrent.CountDownLatch +import java.util.zip.ZipFile +import kotlin.test.AfterTest +import kotlin.test.BeforeTest +import kotlin.test.assertEquals +import kotlin.test.assertNotNull +import kotlin.test.assertTrue /** * Created by Allan Wang on 05/01/18. */ class OfflineWebsiteTest { + lateinit var server: MockWebServer + lateinit var baseDir: File + + @BeforeTest + fun before() { + val buildPath = if (File("").absoluteFile.name == "app") "build/offline_test" else "app/build/offline_test" + baseDir = File(buildPath) + assertTrue(baseDir.deleteRecursively(), "Failed to clean base dir") + server = MockWebServer() + server.start() + } + + @AfterTest + fun after() { + server.shutdown() + } + + private fun zipAndFetch(url: String = server.url("/").toString(), cookie: String = ""): ZipFile { + val name = "test${System.currentTimeMillis()}" + runBlocking { + val success = OfflineWebsite(url, cookie, baseDir = baseDir) + .loadAndZip(name) + assertTrue(success, "An error occurred") + } + + return ZipFile(File(baseDir, "$name.zip")) + } + + private val tagWhitespaceRegex = Regex(">\\s+<", setOf(RegexOption.MULTILINE)) + + private fun ZipFile.assertContentEquals(path: String, content: String) { + val entry = getEntry(path) + assertNotNull(entry, "Entry $path not found") + val actualContent = getInputStream(entry).bufferedReader().use { it.readText() } + assertEquals( + content.replace(tagWhitespaceRegex, "><").toLowerCase(), + actualContent.replace(tagWhitespaceRegex, "><").toLowerCase(), "Content mismatch for $path" + ) + } + + @Test + fun fbOffline() { + // Not really a test. Skip in CI + assumeTrue(COOKIE.isNotEmpty()) + zipAndFetch(FB_URL_BASE) + } + + @Test + fun basicSingleFile() { + val content = """ + + + + +

Single File Test

+ + + """.trimIndent() + + server.enqueue(MockResponse().setBody(content)) + + val zip = zipAndFetch() + + assertEquals(1, zip.size(), "1 file expected") + zip.assertContentEquals("index.html", content) + } + @Test - fun basic() { - val countdown = CountDownLatch(1) - val buildPath = if (File(".").parentFile?.name == "app") "build/offline_test" else "app/build/offline_test" - OfflineWebsite(FB_URL_BASE, COOKIE, baseDir = File(buildPath)) - .loadAndZip("test") { - println("Outcome $it") - countdown.countDown() + fun withCssAsset() { + val cssUrl = server.url("1.css") + + val content = """ + + + + + + +

Css File Test

+ + + """.trimIndent() + + val css1 = """ + .hello { + display: none; } - countdown.await() + """.trimIndent() + + server.setDispatcher(object : Dispatcher() { + override fun dispatch(request: RecordedRequest): MockResponse = + when { + request.path.contains(cssUrl.encodedPath()) -> MockResponse().setBody(css1) + else -> MockResponse().setBody(content) + } + }) + + val zip = zipAndFetch() + + assertEquals(2, zip.size(), "2 files expected") + zip.assertContentEquals("index.html", content.replace(cssUrl.toString(), "assets/a0_1.css")) + zip.assertContentEquals("assets/a0_1.css", css1) + } + + @Test + fun withJsAsset() { + val jsUrl = server.url("1.js") + + val content = """ + + + + +

Js File Test

+ + + + """.trimIndent() + + val js1 = """ + console.log('hello'); + """.trimIndent() + + server.setDispatcher(object : Dispatcher() { + override fun dispatch(request: RecordedRequest): MockResponse = + when { + request.path.contains(jsUrl.encodedPath()) -> MockResponse().setBody(js1) + else -> MockResponse().setBody(content) + } + }) + + val zip = zipAndFetch() + + assertEquals(2, zip.size(), "2 files expected") + zip.assertContentEquals("index.html", content.replace(jsUrl.toString(), "assets/a0_1.js.txt")) + zip.assertContentEquals("assets/a0_1.js.txt", js1) + } + + @Test + fun fullTest() { + val css1Url = server.url("1.css") + val css2Url = server.url("2.css") + val js1Url = server.url("1.js") + val js2Url = server.url("2.js") + + val content = """ + + + + + + + +

Multi File Test

+ + + + + """.trimIndent() + + val css1 = """ + .hello { + display: none; + } + """.trimIndent() + + val css2 = """ + .world { + display: none; + } + """.trimIndent() + + val js1 = """ + console.log('hello'); + """.trimIndent() + + val js2 = """ + console.log('world'); + """.trimIndent() + + server.setDispatcher(object : Dispatcher() { + override fun dispatch(request: RecordedRequest): MockResponse = + when { + request.path.contains(css1Url.encodedPath()) -> MockResponse().setBody(css1) + request.path.contains(css2Url.encodedPath()) -> MockResponse().setBody(css2) + request.path.contains(js1Url.encodedPath()) -> MockResponse().setBody(js1) + request.path.contains(js2Url.encodedPath()) -> MockResponse().setBody(js2) + else -> MockResponse().setBody(content) + } + }) + + val zip = zipAndFetch() + + assertEquals(5, zip.size(), "2 files expected") + zip.assertContentEquals( + "index.html", content + .replace(css1Url.toString(), "assets/a0_1.css") + .replace(css2Url.toString(), "assets/a1_2.css") + .replace(js1Url.toString(), "assets/a2_1.js.txt") + .replace(js2Url.toString(), "assets/a3_2.js.txt") + ) + + zip.assertContentEquals("assets/a0_1.css", css1) + zip.assertContentEquals("assets/a1_2.css", css2) + zip.assertContentEquals("assets/a2_1.js.txt", js1) + zip.assertContentEquals("assets/a3_2.js.txt", js2) } } diff --git a/app/src/test/kotlin/com/pitchedapps/frost/kotlin/FlyweightTest.kt b/app/src/test/kotlin/com/pitchedapps/frost/kotlin/FlyweightTest.kt new file mode 100644 index 00000000..0eee530e --- /dev/null +++ b/app/src/test/kotlin/com/pitchedapps/frost/kotlin/FlyweightTest.kt @@ -0,0 +1,123 @@ +/* + * Copyright 2018 Allan Wang + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.pitchedapps.frost.kotlin + +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.async +import kotlinx.coroutines.runBlocking +import org.junit.Rule +import org.junit.rules.Timeout +import java.util.concurrent.atomic.AtomicInteger +import kotlin.test.BeforeTest +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertTrue +import kotlin.test.fail + +class FlyweightTest { + + @get:Rule + val globalTimeout: Timeout = Timeout.seconds(5) + + lateinit var flyweight: Flyweight + + lateinit var callCount: AtomicInteger + + private val LONG_RUNNING_KEY = -78 + + @BeforeTest + fun before() { + callCount = AtomicInteger(0) + flyweight = Flyweight(GlobalScope, 100, 200L) { + callCount.incrementAndGet() + when (it) { + LONG_RUNNING_KEY -> Thread.sleep(100000) + else -> Thread.sleep(100) + } + it * 2 + } + } + + @Test + fun basic() { + assertEquals(2, runBlocking { flyweight.fetch(1) }, "Invalid result") + assertEquals(1, callCount.get(), "1 call expected") + } + + @Test + fun multipleWithOneKey() { + val results: List = runBlocking { + (0..1000).map { + flyweight.scope.async { + flyweight.fetch(1) + } + }.map { it.await() } + } + assertEquals(1, callCount.get(), "1 call expected") + assertEquals(1001, results.size, "Incorrect number of results returned") + assertTrue(results.all { it == 2 }, "Result should all be 2") + } + + @Test + fun consecutiveReuse() { + runBlocking { + flyweight.fetch(1) + assertEquals(1, callCount.get(), "1 call expected") + flyweight.fetch(1) + assertEquals(1, callCount.get(), "Reuse expected") + Thread.sleep(300) + flyweight.fetch(1) + assertEquals(2, callCount.get(), "Refetch expected") + } + } + + @Test + fun invalidate() { + runBlocking { + flyweight.fetch(1) + assertEquals(1, callCount.get(), "1 call expected") + flyweight.invalidate(1) + flyweight.fetch(1) + assertEquals(2, callCount.get(), "New call expected") + } + } + + @Test + fun destroy() { + runBlocking { + val longRunningResult = async { flyweight.fetch(LONG_RUNNING_KEY) } + flyweight.fetch(1) + flyweight.cancel() + try { + flyweight.fetch(1) + fail("Flyweight should not be fulfilled after it is destroyed") + } catch (e: Exception) { + assertEquals("Flyweight is not active", e.message, "Incorrect error found on fetch after destruction") + } + try { + longRunningResult.await() + fail("Flyweight should have cancelled previously running requests") + } catch (e: Exception) { + assertEquals( + "Flyweight cancelled", + e.message, + "Incorrect error found on fetch cancelled by destruction" + ) + } + } + } +} diff --git a/app/src/test/kotlin/com/pitchedapps/frost/rx/FlyweightTest.kt b/app/src/test/kotlin/com/pitchedapps/frost/rx/FlyweightTest.kt deleted file mode 100644 index b58878cb..00000000 --- a/app/src/test/kotlin/com/pitchedapps/frost/rx/FlyweightTest.kt +++ /dev/null @@ -1,124 +0,0 @@ -/* - * Copyright 2018 Allan Wang - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package com.pitchedapps.frost.rx - -import kotlinx.coroutines.GlobalScope -import kotlinx.coroutines.async -import kotlinx.coroutines.runBlocking -import org.junit.Rule -import org.junit.rules.Timeout -import java.util.concurrent.atomic.AtomicInteger -import kotlin.test.BeforeTest -import kotlin.test.Test -import kotlin.test.assertEquals -import kotlin.test.assertTrue -import kotlin.test.fail - -class FlyweightTest { - - @get:Rule - val globalTimeout: Timeout = Timeout.seconds(5) - - lateinit var flyweight: Flyweight - - lateinit var callCount: AtomicInteger - - private val LONG_RUNNING_KEY = -78 - - @BeforeTest - fun before() { - callCount = AtomicInteger(0) - flyweight = Flyweight(GlobalScope, 100, 200L) { - callCount.incrementAndGet() - when (it) { - LONG_RUNNING_KEY -> Thread.sleep(100000) - else -> Thread.sleep(100) - } - it * 2 - } - } - - @Test - fun basic() { - assertEquals(2, runBlocking { flyweight.fetch(1) }, "Invalid result") - assertEquals(1, callCount.get(), "1 call expected") - } - - @Test - fun multipleWithOneKey() { - val results: List = runBlocking { - (0..1000).map { - flyweight.scope.async { - flyweight.fetch(1) - } - }.map { it.await() } - } - assertEquals(1, callCount.get(), "1 call expected") - assertEquals(1001, results.size, "Incorrect number of results returned") - assertTrue(results.all { it == 2 }, "Result should all be 2") - } - - @Test - fun consecutiveReuse() { - runBlocking { - flyweight.fetch(1) - assertEquals(1, callCount.get(), "1 call expected") - flyweight.fetch(1) - assertEquals(1, callCount.get(), "Reuse expected") - Thread.sleep(300) - flyweight.fetch(1) - assertEquals(2, callCount.get(), "Refetch expected") - } - } - - @Test - fun invalidate() { - runBlocking { - flyweight.fetch(1) - assertEquals(1, callCount.get(), "1 call expected") - flyweight.invalidate(1) - flyweight.fetch(1) - assertEquals(2, callCount.get(), "New call expected") - } - } - - @Test - fun destroy() { - runBlocking { - val longRunningResult = async { flyweight.fetch(LONG_RUNNING_KEY) } - flyweight.fetch(1) - flyweight.cancel() - try { - flyweight.fetch(1) - fail("Flyweight should not be fulfilled after it is destroyed") - } catch (e: Exception) { - assertEquals("Flyweight is not active", e.message, "Incorrect error found on fetch after destruction") - } - try { - longRunningResult.await() - fail("Flyweight should have cancelled previously running requests") - } catch (e: Exception) { - assertEquals( - "Flyweight cancelled", - e.message, - "Incorrect error found on fetch cancelled by destruction" - ) - } - println("Done") - } - } -} diff --git a/app/src/test/kotlin/com/pitchedapps/frost/rx/ResettableFlyweightTest.kt b/app/src/test/kotlin/com/pitchedapps/frost/rx/ResettableFlyweightTest.kt deleted file mode 100644 index 26a5a8de..00000000 --- a/app/src/test/kotlin/com/pitchedapps/frost/rx/ResettableFlyweightTest.kt +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright 2018 Allan Wang - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package com.pitchedapps.frost.rx - -import com.pitchedapps.frost.internal.concurrentTest -import org.junit.Before -import org.junit.Test - -/** - * Created by Allan Wang on 07/01/18. - */ -private inline val threadId - get() = Thread.currentThread().id - -class ResettableFlyweightTest { - - class IntFlyweight : RxFlyweight() { - override fun call(input: Int): Long { - println("Call for $input on thread $threadId") - Thread.sleep(20) - return System.currentTimeMillis() - } - - override fun validate(input: Int, cond: Long) = System.currentTimeMillis() - cond < 500 - - override fun cache(input: Int): Long = System.currentTimeMillis() - } - - private lateinit var flyweight: IntFlyweight - - @Before - fun init() { - flyweight = IntFlyweight() - } - - @Test - fun testCache() = concurrentTest { result -> - flyweight(1).subscribe { i, _ -> - flyweight(1).subscribe { j, _ -> - if (i != null && i == j) - result.onComplete() - else - result.onError("Did not use cache during calls") - } - } - } - - @Test - fun testNoCache() = concurrentTest { result -> - flyweight(1).subscribe { i, _ -> - flyweight(2).subscribe { j, _ -> - if (i != null && i != j) - result.onComplete() - else - result.onError("Should not use cache for calls with different keys") - } - } - } -} -- cgit v1.2.3