kotlin を1.5.30 に戻す。androidTestとtestを通す。

This commit is contained in:
tateisu 2021-10-28 08:37:39 +09:00
parent 9c68857e6e
commit 2e19190901
43 changed files with 1724 additions and 1367 deletions

View File

@ -34,8 +34,8 @@ repositories {
dependencies {
testImplementation "junit:junit:$junit_version"
androidTestImplementation 'androidx.test:runner:1.3.0'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
androidTestImplementation 'androidx.test:runner:1.4.0'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
api project(':apng')
// 'api'

View File

@ -41,7 +41,8 @@ android {
]
}
buildFeatures {
compose true
// composeを導入できない
// compose true
}
composeOptions {
kotlinCompilerExtensionVersion compose_version
@ -115,6 +116,10 @@ dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs')
implementation fileTree(include: ['*.aar'], dir: 'src/main/libs')
// targetSdkVersion 31 androidTest android:exported
// https://github.com/android/android-test/issues/1022
androidTestImplementation "androidx.test:core:1.4.0"
androidTestImplementation('androidx.test.espresso:espresso-core:3.1.0-alpha4', {
exclude group: 'com.android.support', module: 'support-annotations'
})
@ -152,7 +157,6 @@ dependencies {
// https://firebase.google.com/support/release-notes/android
implementation "com.google.firebase:firebase-messaging:22.0.0"
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
testImplementation "org.jetbrains.kotlin:kotlin-test:$kotlin_version"
@ -176,7 +180,7 @@ dependencies {
implementation "com.squareup.okhttp3:okhttp:$okhttpVersion"
implementation "com.squareup.okhttp3:okhttp-urlconnection:$okhttpVersion"
testImplementation "com.squareup.okhttp3:mockwebserver:$okhttpVersion"
androidTestImplementation "'com.squareup.okhttp3:mockwebserver:$okhttpVersion"
androidTestImplementation "com.squareup.okhttp3:mockwebserver:$okhttpVersion"
def glideVersion = '4.12.0'
implementation "com.github.bumptech.glide:glide:$glideVersion"
@ -241,18 +245,19 @@ dependencies {
// optional - Test helpers for LiveData
testImplementation "androidx.arch.core:core-testing:$arch_version"
implementation "com.google.accompanist:accompanist-flowlayout:0.20.0"
implementation "androidx.compose.ui:ui:$compose_version"
implementation "androidx.compose.material:material:$compose_version"
implementation "androidx.compose.ui:ui-tooling-preview:$compose_version"
implementation "androidx.compose.runtime:runtime-livedata:$compose_version"
implementation "androidx.compose.material:material-icons-extended:$compose_version"
androidTestImplementation "androidx.compose.ui:ui-test-junit4:$compose_version"
debugImplementation "androidx.compose.ui:ui-tooling:$compose_version"
implementation 'androidx.activity:activity-compose:1.4.0-rc01'
// composeはkotlin 1.5.31
// https://gist.github.com/tateisu/cbda451135d2b5c9b69f7ac4599f9833 1.5.31使
// compose導入を諦める
// implementation "androidx.compose.ui:ui:$compose_version"
// implementation "androidx.compose.material:material:$compose_version"
// implementation "androidx.compose.ui:ui-tooling-preview:$compose_version"
// implementation "androidx.compose.runtime:runtime-livedata:$compose_version"
// implementation "androidx.compose.material:material-icons-extended:$compose_version"
// androidTestImplementation "androidx.compose.ui:ui-test-junit4:$compose_version"
// debugImplementation "androidx.compose.ui:ui-tooling:$compose_version"
// implementation 'androidx.activity:activity-compose:1.4.0-rc01'
// implementation "com.google.accompanist:accompanist-flowlayout:0.20.0"
}

View File

@ -1,23 +0,0 @@
package jp.juggler.subwaytooter;
import android.content.Context;
import androidx.test.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
import static org.junit.Assert.*;
// Android instrumentation test run configuration を編集しないと Empty tests とかいうエラーになります
@RunWith(AndroidJUnit4.class)
public class ExampleInstrumentedTest {
@Test
public void useAppContext(){
// Context of the app under test.
Context appContext = InstrumentationRegistry.getTargetContext();
assertEquals( "jp.juggler.subwaytooter", appContext.getPackageName() );
}
}

View File

@ -0,0 +1,18 @@
package jp.juggler.subwaytooter
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.runner.AndroidJUnit4
import org.junit.Assert
import org.junit.Test
import org.junit.runner.RunWith
// Android instrumentation test は run configuration を編集しないと Empty tests とかいうエラーになります
@RunWith(AndroidJUnit4::class)
class ExampleInstrumentedTest {
@Test
fun useAppContext() {
// Context of the app under test.
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
Assert.assertEquals("jp.juggler.subwaytooter", appContext.packageName)
}
}

View File

@ -1,14 +1,12 @@
package jp.juggler.subwaytooter
import androidx.test.runner.AndroidJUnit4
import org.junit.Test
import org.junit.runner.RunWith
import jp.juggler.util.*
import jp.juggler.util.jsonArray
import org.jetbrains.anko.collections.forEachReversedByIndex
import org.jetbrains.anko.collections.forEachReversedWithIndex
import org.junit.Assert.assertEquals
import org.junit.Test
import org.junit.runner.RunWith
/**
* Example local unit test, which will execute on the development machine (host).
@ -17,48 +15,48 @@ import org.junit.Assert.assertEquals
*/
@RunWith(AndroidJUnit4::class)
class JsonArrayForEach {
@Test
@Throws(Exception::class)
fun test(){
val array = jsonArray{
add("a")
add("b")
add( null)
add( null)
}
var count = 0
array.forEach {
println("JsonArray.forEach $it")
++count
}
array.forEachIndexed { i,v->
println("JsonArray.forEachIndexed $i $v")
++count
}
array.forEachReversedByIndex {
println("JsonArray.downForEach $it")
++count
}
array.forEachReversedWithIndex{ i,v->
println("JsonArray.downForEachIndexed $i $v")
++count
}
for( o in array.iterator() ){
println("JsonArray.iterator $o")
++count
}
for( o in array.asReversed().iterator() ){
println("JsonArray.reverseIterator $o")
++count
}
assertEquals(count,24)
}
@Test
@Throws(Exception::class)
fun test() {
val array = jsonArray {
add("a")
add("b")
add(null)
add(null)
}
var count = 0
array.forEach {
println("JsonArray.forEach $it")
++count
}
array.forEachIndexed { i, v ->
println("JsonArray.forEachIndexed $i $v")
++count
}
array.forEachReversedByIndex {
println("JsonArray.downForEach $it")
++count
}
array.forEachReversedWithIndex { i, v ->
println("JsonArray.downForEachIndexed $i $v")
++count
}
for (o in array.iterator()) {
println("JsonArray.iterator $o")
++count
}
for (o in array.asReversed().iterator()) {
println("JsonArray.reverseIterator $o")
++count
}
assertEquals(count, 24)
}
}

View File

@ -10,91 +10,91 @@ import org.junit.runner.RunWith
// Android instrumentation test は run configuration を編集しないと Empty tests とかいうエラーになります
@RunWith(AndroidJUnit4::class)
class TestMisskeyMentionAndroid {
@Test
fun testBracket() {
// [] 空の文字セットはパースエラーになる。
// val re1="""[]""".toRegex() // error 空の文字クラス
// [[] や [[]] はパースエラーになる。
// val re1="""[[]""".toRegex() // error 閉じ括弧が足りない
// val re1="""[[]]""".toRegex() // error 内側が空の文字クラス
// 最低でも1文字を含む。
assertEquals(true, """[]]""".toRegex().matches("]"))
// 1文字あけた次からは閉じ括弧として扱われる。
assertEquals(true, """[ ]]""".toRegex().matches(" ]"))
// 閉じ括弧が単体で出たら文字クラスにならない。
assertEquals(true, """]""".toRegex().matches("]"))
// 閉じ括弧が足りないのはエラーになる。
// val a="""[[ ]""".toRegex()
// IDEで警告が出るが、Androidは正規表現エンジンが異なるので仕方ない
@Suppress("RegExpRedundantNestedCharacterClass")
assertEquals(true, """[[ ]]][ ]""".toRegex().matches(" ] "))
@Test
fun testBracket() {
// [] 空の文字セットはパースエラーになる。
// val re1="""[]""".toRegex() // error 空の文字クラス
// [[] や [[]] はパースエラーになる。
// val re1="""[[]""".toRegex() // error 閉じ括弧が足りない
// val re1="""[[]]""".toRegex() // error 内側が空の文字クラス
// 最低でも1文字を含む。
assertEquals(true, """[]]""".toRegex().matches("]"))
// 1文字あけた次からは閉じ括弧として扱われる。
assertEquals(true, """[ ]]""".toRegex().matches(" ]"))
// 閉じ括弧が単体で出たら文字クラスにならない。
assertEquals(true, """]""".toRegex().matches("]"))
// 閉じ括弧が足りないのはエラーになる。
// val a="""[[ ]""".toRegex()
// IDEで警告が出るが、Androidは正規表現エンジンが異なるので仕方ない
@Suppress("RegExpRedundantNestedCharacterClass")
assertEquals(true, """[[ ]]][ ]""".toRegex().matches(" ] "))
}
@Test
@Throws(Exception::class)
fun testAsciiPattern() {
// \w \d \W \D 以外の文字は素通しする
assertEquals("""ab\c\\""", """ab\c\\""".asciiPatternString())
assertEquals("""[A-Za-z0-9_]""", """\w""".asciiPatternString())
assertEquals("""[A-Za-z0-9_-]""", """[\w-]""".asciiPatternString())
assertEquals("""[^A-Za-z0-9_]""", """\W""".asciiPatternString())
assertEquals("""[0-9]""", """\d""".asciiPatternString())
assertEquals("""[0-9:-]""", """[\d:-]""".asciiPatternString())
assertEquals("""[^0-9]""", """\D""".asciiPatternString())
// 文字セットの中の \W \D は変換できないので素通しする
assertEquals("""[\W]""", """[\W]""".asciiPatternString())
assertEquals("""[\D]""", """[\D]""".asciiPatternString())
// エスケープ文字の後に何もない場合も素通しする
assertEquals("""\""", """\""".asciiPatternString())
}
@Test
fun testMisskeyMention() {
fun findMention(str: String): String? {
val m = TootAccount.reMisskeyMentionMFM.matcher(str)
return if (m.find()) m.group(0) else null
}
assertEquals(null, findMention(""))
assertEquals(null, findMention("tateisu"))
assertEquals("@tateisu", findMention("@tateisu"))
assertEquals("@tateisu", findMention("@tateisuほげ"))
assertEquals("@tateisu@mastodon.juggler.jp", findMention("@tateisu@mastodon.juggler.jp"))
assertEquals("@tateisu@mastodon.juggler.jp", findMention("@tateisu@mastodon.juggler.jpほげ"))
assertEquals("@tateisu", findMention("@tateisu@マストドン3.juggler.jp"))
assertEquals(
"@tateisu@xn--3-pfuzbe6htf.juggler.jp",
findMention("@tateisu@xn--3-pfuzbe6htf.juggler.jp")
)
}
@Test
fun testMastodonMention() {
fun findMention(str: String): String? {
val m = TootAccount.reCountMention.matcher(str)
return if (m.find()) m.group(0) else null
}
assertEquals(null, findMention(""))
assertEquals(null, findMention("tateisu"))
assertEquals("@tateisu", findMention("@tateisu"))
assertEquals("@tateisu", findMention("@tateisuほげ"))
assertEquals("@tateisu@mastodon.juggler.jp", findMention("@tateisu@mastodon.juggler.jp"))
assertEquals("@tateisu@mastodon.juggler.jp", findMention("@tateisu@mastodon.juggler.jpほげ"))
assertEquals("@tateisu@マストドン3.juggler.jp", findMention("@tateisu@マストドン3.juggler.jp"))
assertEquals(
"@tateisu@xn--3-pfuzbe6htf.juggler.jp",
findMention("@tateisu@xn--3-pfuzbe6htf.juggler.jp")
)
}
}
@Test
@Throws(Exception::class)
fun testAsciiPattern() {
// \w \d \W \D 以外の文字は素通しする
assertEquals("""ab\c\\""", """ab\c\\""".asciiPatternString())
assertEquals("""[A-Za-z0-9_]""", """\w""".asciiPatternString())
assertEquals("""[A-Za-z0-9_-]""", """[\w-]""".asciiPatternString())
assertEquals("""[^A-Za-z0-9_]""", """\W""".asciiPatternString())
assertEquals("""[0-9]""", """\d""".asciiPatternString())
assertEquals("""[0-9:-]""", """[\d:-]""".asciiPatternString())
assertEquals("""[^0-9]""", """\D""".asciiPatternString())
// 文字セットの中の \W \D は変換できないので素通しする
assertEquals("""[\W]""", """[\W]""".asciiPatternString())
assertEquals("""[\D]""", """[\D]""".asciiPatternString())
// エスケープ文字の後に何もない場合も素通しする
assertEquals("""\""", """\""".asciiPatternString())
}
@Test
fun testMisskeyMention() {
fun findMention(str : String) : String? {
val m = TootAccount.reMisskeyMentionMFM.matcher(str)
return if(m.find()) m.group(0) else null
}
assertEquals(null, findMention(""))
assertEquals(null, findMention("tateisu"))
assertEquals("@tateisu", findMention("@tateisu"))
assertEquals("@tateisu", findMention("@tateisuほげ"))
assertEquals("@tateisu@mastodon.juggler.jp", findMention("@tateisu@mastodon.juggler.jp"))
assertEquals("@tateisu@mastodon.juggler.jp", findMention("@tateisu@mastodon.juggler.jpほげ"))
assertEquals("@tateisu", findMention("@tateisu@マストドン3.juggler.jp"))
assertEquals(
"@tateisu@xn--3-pfuzbe6htf.juggler.jp",
findMention("@tateisu@xn--3-pfuzbe6htf.juggler.jp")
)
}
@Test
fun testMastodonMention() {
fun findMention(str : String) : String? {
val m = TootAccount.reCountMention.matcher(str)
return if(m.find()) m.group(0) else null
}
assertEquals(null, findMention(""))
assertEquals(null, findMention("tateisu"))
assertEquals("@tateisu", findMention("@tateisu"))
assertEquals("@tateisu", findMention("@tateisuほげ"))
assertEquals("@tateisu@mastodon.juggler.jp", findMention("@tateisu@mastodon.juggler.jp"))
assertEquals("@tateisu@mastodon.juggler.jp", findMention("@tateisu@mastodon.juggler.jpほげ"))
assertEquals("@tateisu@マストドン3.juggler.jp", findMention("@tateisu@マストドン3.juggler.jp"))
assertEquals(
"@tateisu@xn--3-pfuzbe6htf.juggler.jp",
findMention("@tateisu@xn--3-pfuzbe6htf.juggler.jp")
)
}
}

View File

@ -1,7 +1,7 @@
package jp.juggler.subwaytooter
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.runner.AndroidJUnit4
import jp.juggler.subwaytooter.api.TootApiCallback
import jp.juggler.subwaytooter.api.TootApiClient
import jp.juggler.subwaytooter.api.entity.Host

View File

@ -1,15 +1,12 @@
package jp.juggler.subwaytooter
import androidx.test.runner.AndroidJUnit4
import org.junit.Test
import org.junit.runner.RunWith
import jp.juggler.util.CharacterGroup
import jp.juggler.util.WordTrieTree
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotNull
import org.junit.Test
import org.junit.runner.RunWith
/**
* Instrumentation test, which will execute on an Android device.
@ -18,159 +15,199 @@ import org.junit.Assert.assertNotNull
*/
@RunWith(AndroidJUnit4::class)
class WordTrieTreeTest {
companion object {
private val whitespace_chars = charArrayOf(0x0009.toChar(), 0x000A.toChar(), 0x000B.toChar(), 0x000C.toChar(), 0x000D.toChar(), 0x001C.toChar(), 0x001D.toChar(), 0x001E.toChar(), 0x001F.toChar(), 0x0020.toChar(), 0x0085.toChar(), 0x00A0.toChar(), 0x1680.toChar(), 0x180E.toChar(), 0x2000.toChar(), 0x2001.toChar(), 0x2002.toChar(), 0x2003.toChar(), 0x2004.toChar(), 0x2005.toChar(), 0x2006.toChar(), 0x2007.toChar(), 0x2008.toChar(), 0x2009.toChar(), 0x200A.toChar(), 0x200B.toChar(), 0x200C.toChar(), 0x200D.toChar(), 0x2028.toChar(), 0x2029.toChar(), 0x202F.toChar(), 0x205F.toChar(), 0x2060.toChar(), 0x3000.toChar(), 0x3164.toChar(), 0xFEFF.toChar())
}
@Test
@Throws(Exception::class)
fun testCharacterGroupTokenizer() {
val whitespace = String(whitespace_chars)
val whitespace_len = whitespace.length
var id : Int
run {
// トークナイザーに空白だけの文字列を与えたら、next() 一回で終端になる。offsetは0のままである。
val tokenizer = CharacterGroup.Tokenizer().reset(whitespace, 0, whitespace.length)
id = tokenizer.next()
assertEquals(CharacterGroup.END, id)
assertEquals(0, tokenizer.offset.toLong())
}
run {
// トークナイザーに 空白+ABC+空白 を与えたら、A,B,C,終端になる。
val strTest = whitespace + "ABC" + whitespace
val tokenizer = CharacterGroup.Tokenizer().reset(strTest, 0, strTest.length)
//
id = tokenizer.next()
companion object {
private val whitespace_chars = charArrayOf(
0x0009.toChar(),
0x000A.toChar(),
0x000B.toChar(),
0x000C.toChar(),
0x000D.toChar(),
0x001C.toChar(),
0x001D.toChar(),
0x001E.toChar(),
0x001F.toChar(),
0x0020.toChar(),
0x0085.toChar(),
0x00A0.toChar(),
0x1680.toChar(),
0x180E.toChar(),
0x2000.toChar(),
0x2001.toChar(),
0x2002.toChar(),
0x2003.toChar(),
0x2004.toChar(),
0x2005.toChar(),
0x2006.toChar(),
0x2007.toChar(),
0x2008.toChar(),
0x2009.toChar(),
0x200A.toChar(),
0x200B.toChar(),
0x200C.toChar(),
0x200D.toChar(),
0x2028.toChar(),
0x2029.toChar(),
0x202F.toChar(),
0x205F.toChar(),
0x2060.toChar(),
0x3000.toChar(),
0x3164.toChar(),
0xFEFF.toChar()
)
}
@Test
@Throws(Exception::class)
fun testCharacterGroupTokenizer() {
val whitespace = String(whitespace_chars)
val whitespace_len = whitespace.length
var id: Int
run {
// トークナイザーに空白だけの文字列を与えたら、next() 一回で終端になる。offsetは0のままである。
val tokenizer = CharacterGroup.Tokenizer().reset(whitespace, 0, whitespace.length)
id = tokenizer.next()
assertEquals(CharacterGroup.END, id)
assertEquals(0, tokenizer.offset.toLong())
}
run {
// トークナイザーに 空白+ABC+空白 を与えたら、A,B,C,終端になる。
val strTest = whitespace + "ABC" + whitespace
val tokenizer = CharacterGroup.Tokenizer().reset(strTest, 0, strTest.length)
//
id = tokenizer.next()
assertEquals('A'.code, id)
assertEquals((whitespace_len + 1), tokenizer.offset) // offset は Aの次の位置になる
//
id = tokenizer.next()
assertEquals((whitespace_len + 1), tokenizer.offset) // offset は Aの次の位置になる
//
id = tokenizer.next()
assertEquals('B'.code, id)
assertEquals((whitespace_len + 2).toLong(), tokenizer.offset.toLong())
//
id = tokenizer.next()
assertEquals((whitespace_len + 2).toLong(), tokenizer.offset.toLong())
//
id = tokenizer.next()
assertEquals('C'.code, id)
assertEquals((whitespace_len + 3).toLong(), tokenizer.offset.toLong())
//
id = tokenizer.next()
assertEquals((whitespace_len + 3).toLong(), tokenizer.offset.toLong()) // offsetはCの次の位置のまま
assertEquals(CharacterGroup.END, id)
}
run {
// トークナイザーに 空白+abc+空白 を与えたら、A,B,C,終端になる。
val strTest = whitespace + "abc" + whitespace
val tokenizer = CharacterGroup.Tokenizer().reset(strTest, 0, strTest.length)
//
id = tokenizer.next()
assertEquals((whitespace_len + 1).toLong(), tokenizer.offset.toLong())
assertEquals((whitespace_len + 3).toLong(), tokenizer.offset.toLong())
//
id = tokenizer.next()
assertEquals(
(whitespace_len + 3).toLong(),
tokenizer.offset.toLong()
) // offsetはCの次の位置のまま
assertEquals(CharacterGroup.END, id)
}
run {
// トークナイザーに 空白+abc+空白 を与えたら、A,B,C,終端になる。
val strTest = whitespace + "abc" + whitespace
val tokenizer = CharacterGroup.Tokenizer().reset(strTest, 0, strTest.length)
//
id = tokenizer.next()
assertEquals((whitespace_len + 1).toLong(), tokenizer.offset.toLong())
assertEquals('A'.code, id)
//
id = tokenizer.next()
assertEquals((whitespace_len + 2).toLong(), tokenizer.offset.toLong())
//
id = tokenizer.next()
assertEquals((whitespace_len + 2).toLong(), tokenizer.offset.toLong())
assertEquals('B'.code, id)
//
id = tokenizer.next()
assertEquals((whitespace_len + 3).toLong(), tokenizer.offset.toLong())
//
id = tokenizer.next()
assertEquals((whitespace_len + 3).toLong(), tokenizer.offset.toLong())
assertEquals('C'.code, id)
//
id = tokenizer.next()
assertEquals((whitespace_len + 3).toLong(), tokenizer.offset.toLong())
assertEquals(CharacterGroup.END, id)
}
run {
// トークナイザーに 空白++空白 を与えたら、A,B,C,終端になる。
val strTest = whitespace + "" + whitespace
val tokenizer = CharacterGroup.Tokenizer().reset(strTest, 0, strTest.length)
//
id = tokenizer.next()
assertEquals((whitespace_len + 1).toLong(), tokenizer.offset.toLong())
//
id = tokenizer.next()
assertEquals((whitespace_len + 3).toLong(), tokenizer.offset.toLong())
assertEquals(CharacterGroup.END, id)
}
run {
// トークナイザーに 空白++空白 を与えたら、A,B,C,終端になる。
val strTest = whitespace + "" + whitespace
val tokenizer = CharacterGroup.Tokenizer().reset(strTest, 0, strTest.length)
//
id = tokenizer.next()
assertEquals((whitespace_len + 1).toLong(), tokenizer.offset.toLong())
assertEquals('A'.code, id)
//
id = tokenizer.next()
assertEquals((whitespace_len + 2).toLong(), tokenizer.offset.toLong())
//
id = tokenizer.next()
assertEquals((whitespace_len + 2).toLong(), tokenizer.offset.toLong())
assertEquals('B'.code, id)
//
id = tokenizer.next()
assertEquals((whitespace_len + 3).toLong(), tokenizer.offset.toLong())
//
id = tokenizer.next()
assertEquals((whitespace_len + 3).toLong(), tokenizer.offset.toLong())
assertEquals('C'.code, id)
//
id = tokenizer.next()
assertEquals((whitespace_len + 3).toLong(), tokenizer.offset.toLong())
assertEquals(CharacterGroup.END, id)
}
run {
// トークナイザーに 空白++空白 を与えたら、A,B,C,終端になる。
val strTest = whitespace + "" + whitespace
val tokenizer = CharacterGroup.Tokenizer().reset(strTest, 0, strTest.length)
//
id = tokenizer.next()
assertEquals((whitespace_len + 1).toLong(), tokenizer.offset.toLong())
//
id = tokenizer.next()
assertEquals((whitespace_len + 3).toLong(), tokenizer.offset.toLong())
assertEquals(CharacterGroup.END, id)
}
run {
// トークナイザーに 空白++空白 を与えたら、A,B,C,終端になる。
val strTest = whitespace + "" + whitespace
val tokenizer = CharacterGroup.Tokenizer().reset(strTest, 0, strTest.length)
//
id = tokenizer.next()
assertEquals((whitespace_len + 1).toLong(), tokenizer.offset.toLong())
assertEquals('A'.code, id)
//
id = tokenizer.next()
assertEquals((whitespace_len + 2).toLong(), tokenizer.offset.toLong())
//
id = tokenizer.next()
assertEquals((whitespace_len + 2).toLong(), tokenizer.offset.toLong())
assertEquals('B'.code, id)
//
id = tokenizer.next()
assertEquals((whitespace_len + 3).toLong(), tokenizer.offset.toLong())
//
id = tokenizer.next()
assertEquals((whitespace_len + 3).toLong(), tokenizer.offset.toLong())
assertEquals('C'.code, id)
//
id = tokenizer.next()
assertEquals((whitespace_len + 3).toLong(), tokenizer.offset.toLong())
assertEquals(CharacterGroup.END, id)
}
}
@Test
@Throws(Exception::class)
fun testWordTrieTree() {
var strTest : String
run {
val trie = WordTrieTree()
trie.add("")
trie.add(" ") // 単語の側に空白があっても無視される
trie.add("ABC")
trie.add("abc")
trie.add("abcdef")
trie.add("bbb")
trie.add("C C C") // 単語の側に空白があっても無視される
trie.add("ccc")
// 空文字列や空白を登録してもマッチしない
// 登録していない単語にマッチしない
assertEquals(false, trie.matchShort("ZZZ"))
// 登録した文字列にマッチする
assertEquals(true, trie.matchShort("abc"))
assertEquals(true, trie.matchShort("abcdef"))
// 単語の間に空白があってもマッチする
strTest = "///abcdef///a b c///bb b///c cc "
val list = trie.matchList(strTest)
assertNotNull(list)
assertEquals(4, list !!.size.toLong())
assertEquals("abcdef", list[0].word) // abcよりもabcdefを優先してマッチする
assertEquals(3, list[0].start.toLong()) // 元テキスト中でマッチした位置を取得できる
assertEquals(9, list[0].end.toLong())
assertEquals("ABC", list[1].word) // 文字種が違っても同一とみなす単語の場合、先に登録した方にマッチする
assertEquals("bbb", list[2].word)
assertEquals("C C C", list[3].word) // 文字種が違っても同一とみなす単語の場合、先に登録した方にマッチする
assertEquals(27, list[3].start.toLong()) // 元テキスト中でマッチした位置を取得できる
assertEquals(31, list[3].end.toLong())
assertEquals(33, strTest.length.toLong()) // 末尾の空白はマッチ範囲には含まれない
}
}
//
id = tokenizer.next()
assertEquals((whitespace_len + 3).toLong(), tokenizer.offset.toLong())
assertEquals(CharacterGroup.END, id)
}
}
@Test
@Throws(Exception::class)
fun testWordTrieTree() {
var strTest: String
run {
val trie = WordTrieTree()
trie.add("")
trie.add(" ") // 単語の側に空白があっても無視される
trie.add("ABC")
trie.add("abc")
trie.add("abcdef")
trie.add("bbb")
trie.add("C C C") // 単語の側に空白があっても無視される
trie.add("ccc")
// 空文字列や空白を登録してもマッチしない
// 登録していない単語にマッチしない
assertEquals(false, trie.matchShort("ZZZ"))
// 登録した文字列にマッチする
assertEquals(true, trie.matchShort("abc"))
assertEquals(true, trie.matchShort("abcdef"))
// 単語の間に空白があってもマッチする
strTest = "///abcdef///a b c///bb b///c cc "
val list = trie.matchList(strTest)
assertNotNull(list)
assertEquals(4, list!!.size.toLong())
assertEquals("abcdef", list[0].word) // abcよりもabcdefを優先してマッチする
assertEquals(3, list[0].start.toLong()) // 元テキスト中でマッチした位置を取得できる
assertEquals(9, list[0].end.toLong())
assertEquals("ABC", list[1].word) // 文字種が違っても同一とみなす単語の場合、先に登録した方にマッチする
assertEquals("bbb", list[2].word)
assertEquals("C C C", list[3].word) // 文字種が違っても同一とみなす単語の場合、先に登録した方にマッチする
assertEquals(27, list[3].start.toLong()) // 元テキスト中でマッチした位置を取得できる
assertEquals(31, list[3].end.toLong())
assertEquals(33, strTest.length.toLong()) // 末尾の空白はマッチ範囲には含まれない
}
}
}

View File

@ -1,220 +1,236 @@
package jp.juggler.subwaytooter.api
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.runner.AndroidJUnit4
import android.test.mock.MockContext
import jp.juggler.subwaytooter.api.entity.*
import jp.juggler.subwaytooter.table.SavedAccount
import jp.juggler.util.JsonObject
import jp.juggler.util.jsonObject
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotNull
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotNull
@RunWith(AndroidJUnit4::class)
class TestDuplicateMap {
private val parser = TootParser(
MockContext(),
SavedAccount(
db_id = 1,
acctArg = "user1@host1",
apiHostArg = null
)
)
private val generatedItems = ArrayList<TimelineItem>()
private fun genStatus(
parser : TootParser,
accountJson : JsonObject,
statusId : String,
uri : String?,
url : String?
):TootStatus{
val itemJson = jsonObject{
put("account", accountJson)
put("id", statusId)
if(uri != null) put("uri", uri)
if(url != null) put("url", url)
}
return TootStatus(parser,itemJson)
}
private fun checkStatus(
map : DuplicateMap,
parser : TootParser,
accountJson : JsonObject,
statusId : String,
uri : String?,
url : String?
) {
val item = genStatus(parser,accountJson,statusId,uri,url)
assertNotNull(item)
generatedItems.add(item)
assertEquals(false, map.isDuplicate(item))
assertEquals(true, map.isDuplicate(item))
}
private fun testDuplicateStatus() {
val account1Json = jsonObject{
put("username", "user1")
put("acct", "user1")
put("id", 1L)
put("url", "http://${parser.apiHost}/@user1")
}
val account1 = TootAccount(parser,account1Json)
assertNotNull(account1)
val map = DuplicateMap()
// 普通のステータス
checkStatus(
map,
parser,
account1Json,
"1",
"http://${parser.apiHost}/@${account1.username}/1",
"http://${parser.apiHost}/@${account1.username}/1"
)
// 別のステータス
checkStatus(
map,
parser,
account1Json,
"2",
"http://${parser.apiHost}/@${account1.username}/2",
"http://${parser.apiHost}/@${account1.username}/2"
)
// 今度はuriがない
checkStatus(
map,
parser,
account1Json,
"3",
null, // "http://${parser.accessHost}/@${account1.username}/3",
"http://${parser.apiHost}/@${account1.username}/3"
)
// 今度はuriとURLがない
checkStatus(
map,
parser,
account1Json,
"4",
null, // "http://${parser.accessHost}/@${account1.username}/4",
null //"http://${parser.accessHost}/@${account1.username}/4"
)
// 今度はIDがおかしい
checkStatus(
map,
parser,
account1Json,
"",
null, // "http://${parser.accessHost}/@${account1.username}/4",
null //"http://${parser.accessHost}/@${account1.username}/4"
)
}
private fun checkNotification(
map : DuplicateMap,
parser : TootParser,
id : Long
) {
val itemJson = JsonObject()
itemJson.apply {
put("type", TootNotification.TYPE_MENTION)
put("id", id)
}
val item = TootNotification( parser,itemJson )
assertNotNull(item)
generatedItems.add(item)
assertEquals(false, map.isDuplicate(item))
assertEquals(true, map.isDuplicate(item))
}
private fun testDuplicateNotification() {
val map = DuplicateMap()
checkNotification(map,parser,0L)
checkNotification(map,parser,1L)
checkNotification(map,parser,2L)
checkNotification(map,parser,3L)
}
private fun checkReport(
map : DuplicateMap,
id : Long
) {
val item = TootReport(JsonObject().apply{
put("id",id)
put("action_taken","eat")
})
assertNotNull(item)
generatedItems.add(item)
assertEquals(false, map.isDuplicate(item))
assertEquals(true, map.isDuplicate(item))
}
private fun testDuplicateReport() {
val map = DuplicateMap()
checkReport(map,0L)
checkReport(map,1L)
checkReport(map,2L)
checkReport(map,3L)
}
private fun checkAccount(
map : DuplicateMap,
parser : TootParser,
id : Long
) {
val itemJson = JsonObject()
itemJson.apply {
put("username", "user$id")
put("acct", "user$id")
put("id", id)
put("url", "http://${parser.apiHost}/@user$id")
}
val item = TootAccountRef.notNull(parser,TootAccount(parser,itemJson))
assertNotNull(item)
generatedItems.add(item)
assertEquals(false, map.isDuplicate(item))
assertEquals(true, map.isDuplicate(item)) // 二回目はtrueになる
}
private fun testDuplicateAccount() {
val map = DuplicateMap()
checkAccount(map,parser,0L)
checkAccount(map,parser,1L)
checkAccount(map,parser,2L)
checkAccount(map,parser,3L)
}
@Test fun testFilterList(){
generatedItems.clear()
testDuplicateStatus()
testDuplicateNotification()
testDuplicateReport()
testDuplicateAccount()
val map = DuplicateMap()
private val parser = TootParser(
InstrumentationRegistry.getInstrumentation().targetContext,
SavedAccount(
db_id = 1,
acctArg = "user1@host1",
apiHostArg = null
)
)
val dst = map.filterDuplicate( generatedItems)
assertEquals( generatedItems.size,dst.size)
private fun genStatus(
parser: TootParser,
accountJson: JsonObject,
statusId: String,
uri: String,
url: String?
): TootStatus {
val itemJson = jsonObject {
put("account", accountJson)
put("id", statusId)
put("uri", uri)
if (url != null) put("url", url)
}
val dst2 = map.filterDuplicate( generatedItems)
assertEquals( 0,dst2.size)
}
return TootStatus(parser, itemJson)
}
private fun testDuplicateStatus(): ArrayList<TimelineItem> {
val generatedItems = ArrayList<TimelineItem>()
fun checkStatus(
map: DuplicateMap,
parser: TootParser,
accountJson: JsonObject,
statusId: String,
uri: String,
url: String?
) {
val item = genStatus(parser, accountJson, statusId, uri, url)
assertNotNull(item)
generatedItems.add(item)
assertEquals(false, map.isDuplicate(item))
assertEquals(true, map.isDuplicate(item))
}
val account1Json = jsonObject {
put("username", "user1")
put("acct", "user1")
put("id", 1L)
put("url", "http://${parser.apiHost}/@user1")
}
val account1 = TootAccount(parser, account1Json)
assertNotNull(account1)
val map = DuplicateMap()
// 普通のステータス
checkStatus(
map,
parser,
account1Json,
"s1",
"http://${parser.apiHost}/@${account1.username}/1",
"http://${parser.apiHost}/@${account1.username}/1"
)
// 別のステータス
checkStatus(
map,
parser,
account1Json,
"s2",
"http://${parser.apiHost}/@${account1.username}/2",
"http://${parser.apiHost}/@${account1.username}/2"
)
// URIは必須になったので、URIなしのテストは行わない
// 今度はURLがない
checkStatus(
map,
parser,
account1Json,
"s4",
"http://${parser.apiHost}/@${account1.username}/4",
null //"http://${parser.apiHost}/@${account1.username}/4"
)
// 今度はIDがおかしい
checkStatus(
map,
parser,
account1Json,
"",
"http://${parser.apiHost}/@${account1.username}/5",
"http://${parser.apiHost}/@${account1.username}/5"
)
return generatedItems
}
private fun testDuplicateNotification(): ArrayList<TimelineItem> {
val generatedItems = ArrayList<TimelineItem>()
fun checkNotification(
map: DuplicateMap,
parser: TootParser,
id: String
) {
val itemJson = JsonObject()
itemJson.apply {
put("type", TootNotification.TYPE_MENTION)
put("id", id)
}
val item = TootNotification(parser, itemJson)
assertNotNull(item)
generatedItems.add(item)
assertEquals(false, map.isDuplicate(item))
assertEquals(true, map.isDuplicate(item))
}
val map = DuplicateMap()
checkNotification(map, parser, "n0")
checkNotification(map, parser, "n1")
checkNotification(map, parser, "n2")
checkNotification(map, parser, "n3")
return generatedItems
}
private fun testDuplicateReport(): ArrayList<TimelineItem> {
val generatedItems = ArrayList<TimelineItem>()
fun checkReport(
map: DuplicateMap,
id: String
) {
val item = TootReport(JsonObject().apply {
put("id", id)
put("action_taken", "eat")
})
assertNotNull(item)
generatedItems.add(item)
assertEquals(false, map.isDuplicate(item))
assertEquals(true, map.isDuplicate(item))
}
val map = DuplicateMap()
checkReport(map, "r0")
checkReport(map, "r1")
checkReport(map, "r2")
checkReport(map, "r3")
return generatedItems
}
private fun testDuplicateAccount(): ArrayList<TimelineItem> {
val generatedItems = ArrayList<TimelineItem>()
fun checkAccount(
map: DuplicateMap,
parser: TootParser,
id: String
) {
val itemJson = JsonObject()
itemJson.apply {
put("username", "user$id")
put("acct", "user$id")
put("id", id)
put("url", "http://${parser.apiHost}/@user$id")
}
val item = TootAccountRef.notNull(parser, TootAccount(parser, itemJson))
assertNotNull(item)
generatedItems.add(item)
assertEquals(false, map.isDuplicate(item))
assertEquals(true, map.isDuplicate(item)) // 二回目はtrueになる
}
val map = DuplicateMap()
checkAccount(map, parser, "a0")
checkAccount(map, parser, "a1")
checkAccount(map, parser, "a2")
checkAccount(map, parser, "a3")
return generatedItems
}
@Test
fun testFilterList() {
val statusItems = testDuplicateStatus()
val notiItems = testDuplicateNotification()
val reportItems = testDuplicateReport()
val accountItems = testDuplicateAccount()
var map = DuplicateMap()
var dst = map.filterDuplicate(statusItems)
assertEquals("statusItems", statusItems.size, dst.size)
map = DuplicateMap()
dst = map.filterDuplicate(notiItems)
assertEquals("notiItems", notiItems.size, dst.size)
map = DuplicateMap()
dst = map.filterDuplicate(reportItems)
assertEquals("reportItems", reportItems.size, dst.size)
map = DuplicateMap()
dst = map.filterDuplicate(accountItems)
assertEquals("accountItems", accountItems.size, dst.size)
val totalItems = ArrayList<TimelineItem>()
totalItems.addAll(statusItems)
totalItems.addAll(notiItems)
totalItems.addAll(reportItems)
totalItems.addAll(accountItems)
map = DuplicateMap()
dst = map.filterDuplicate(totalItems)
assertEquals("totalItems", totalItems.size, dst.size)
val dst2 = map.filterDuplicate(totalItems)
assertEquals(0, dst2.size)
}
}

View File

@ -2,7 +2,7 @@
package jp.juggler.subwaytooter.api
import androidx.test.InstrumentationRegistry
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.runner.AndroidJUnit4
import jp.juggler.subwaytooter.api.entity.Host
import jp.juggler.subwaytooter.api.entity.TootInstance
@ -28,29 +28,53 @@ import java.util.concurrent.atomic.AtomicReference
@RunWith(AndroidJUnit4::class)
class TestTootApiClient {
private val appContext = InstrumentationRegistry.getTargetContext()!!
private val appContext = InstrumentationRegistry.getInstrumentation().targetContext!!
class SimpleHttpClientMock(
private val responseGenerator: (request: Request) -> Response,
val webSocketGenerator: (request: Request, ws_listener: WebSocketListener) -> WebSocket
) : SimpleHttpClient {
private val responseGenerator: (request: Request) -> Response,
val webSocketGenerator: (request: Request, ws_listener: WebSocketListener) -> WebSocket
) : SimpleHttpClient {
override var onCallCreated: (Call) -> Unit = {}
// override var currentCallCallback : CurrentCallCallback? = null
override suspend fun getResponse(request: Request, tmpOkhttpClient: OkHttpClient?): Response {
override suspend fun getResponse(
request: Request,
tmpOkhttpClient: OkHttpClient?
): Response {
return responseGenerator(request)
}
override fun getWebSocket(
request: Request,
webSocketListener: WebSocketListener
): WebSocket {
request: Request,
webSocketListener: WebSocketListener
): WebSocket {
return webSocketGenerator(request, webSocketListener)
}
}
private fun <T> assertOneOf(actual: T?, vararg expect: T?) {
if (!expect.any { it == actual }) {
fail("actual=$actual, expected = one of [${expect.joinToString(", ")}]")
}
}
private fun assertParsingResponse(callback: ProgressRecordTootApiCallback) {
assertOneOf(
callback.progressString,
"Parsing response…",
"応答の解析中…"
)
}
private fun assertReading(callback: ProgressRecordTootApiCallback,path:String){
assertOneOf(
callback.progressString,
"Reading: GET $path",
"読込中: GET $path",
)
}
private fun requestBodyString(request: Request?): String? {
try {
val copyBody = request?.newBuilder()?.build()?.body ?: return null
@ -65,174 +89,174 @@ class TestTootApiClient {
private fun createHttpClientNormal(): SimpleHttpClient {
return SimpleHttpClientMock(
responseGenerator = { request: Request ->
responseGenerator = { request: Request ->
val bodyString = requestBodyString(request)
val bodyString = requestBodyString(request)
when (request.url.encodedPath) {
when (request.url.encodedPath) {
// クライアント登録
"/api/v1/apps" -> Response.Builder()
.request(request)
.protocol(Protocol.HTTP_1_1)
.code(200)
.message("status-message")
.body(
"""{"id":999,"redirect_uri":"urn:ietf:wg:oauth:2.0:oob","client_id":"DUMMY_ID","client_secret":"DUMMY_SECRET"}"""
.toResponseBody(MEDIA_TYPE_JSON)
)
.build()
// クライアント登録
"/api/v1/apps" -> Response.Builder()
.request(request)
.protocol(Protocol.HTTP_1_1)
.code(200)
.message("status-message")
.body(
"""{"id":999,"redirect_uri":"urn:ietf:wg:oauth:2.0:oob","client_id":"DUMMY_ID","client_secret":"DUMMY_SECRET"}"""
.toResponseBody(MEDIA_TYPE_JSON)
)
.build()
// client credentialの検証
"/api/v1/apps/verify_credentials" -> Response.Builder()
.request(request)
.protocol(Protocol.HTTP_1_1)
.code(200)
.message("status-message")
.body(
"""{"id":999,"redirect_uri":"urn:ietf:wg:oauth:2.0:oob","client_id":"DUMMY_ID","client_secret":"DUMMY_SECRET"}"""
.toResponseBody(MEDIA_TYPE_JSON)
)
.build()
// client credentialの検証
"/api/v1/apps/verify_credentials" -> Response.Builder()
.request(request)
.protocol(Protocol.HTTP_1_1)
.code(200)
.message("status-message")
.body(
"""{"id":999,"redirect_uri":"urn:ietf:wg:oauth:2.0:oob","client_id":"DUMMY_ID","client_secret":"DUMMY_SECRET"}"""
.toResponseBody(MEDIA_TYPE_JSON)
)
.build()
"/oauth/token" -> when {
// client credential の作成
bodyString?.contains("grant_type=client_credentials") == true -> {
Response.Builder()
.request(request)
.protocol(Protocol.HTTP_1_1)
.code(200)
.message("status-message")
.body(
"""{"access_token":"DUMMY_CLIENT_CREDENTIAL"}""".toResponseBody(
MEDIA_TYPE_JSON
)
)
.build()
"/oauth/token" -> when {
// client credential の作成
bodyString?.contains("grant_type=client_credentials") == true -> {
Response.Builder()
.request(request)
.protocol(Protocol.HTTP_1_1)
.code(200)
.message("status-message")
.body(
"""{"access_token":"DUMMY_CLIENT_CREDENTIAL"}""".toResponseBody(
MEDIA_TYPE_JSON
)
)
.build()
}
// アクセストークンの作成
bodyString?.contains("grant_type=authorization_code") == true -> {
Response.Builder()
.request(request)
.protocol(Protocol.HTTP_1_1)
.code(200)
.message("status-message")
.body(
"""{"access_token":"DUMMY_ACCESS_TOKEN"}""".toResponseBody(
MEDIA_TYPE_JSON
)
)
.build()
}
}
// アクセストークンの作成
bodyString?.contains("grant_type=authorization_code") == true -> {
Response.Builder()
.request(request)
.protocol(Protocol.HTTP_1_1)
.code(200)
.message("status-message")
.body(
"""{"access_token":"DUMMY_ACCESS_TOKEN"}""".toResponseBody(
MEDIA_TYPE_JSON
)
)
.build()
}
else -> {
createResponseErrorCode()
}
}
// ログインユーザの情報
"/api/v1/accounts/verify_credentials" -> {
val instance = request.url.host
val account1Json = JsonObject()
account1Json.apply {
put("username", "user1")
put("acct", "user1")
put("id", 1L)
put("url", "http://$instance/@user1")
}
else -> {
createResponseErrorCode()
}
}
// ログインユーザの情報
"/api/v1/accounts/verify_credentials" -> {
val instance = request.url.host
val account1Json = JsonObject()
account1Json.apply {
put("username", "user1")
put("acct", "user1")
put("id", 1L)
put("url", "http://$instance/@user1")
}
Response.Builder()
.request(request)
.protocol(Protocol.HTTP_1_1)
.code(200)
.message("status-message")
.body(account1Json.toString().toResponseBody(MEDIA_TYPE_JSON))
.build()
}
// インスタンス情報
"/api/v1/instance" -> Response.Builder()
.request(request)
.protocol(Protocol.HTTP_1_1)
.code(200)
.message("status-message")
.body(JsonObject().apply {
put("uri", "http://${request.url.host}/")
put("title", "dummy instance")
put("description", "dummy description")
put("version", "0.0.1")
}.toString().toResponseBody(MEDIA_TYPE_JSON))
.build()
Response.Builder()
.request(request)
.protocol(Protocol.HTTP_1_1)
.code(200)
.message("status-message")
.body(account1Json.toString().toResponseBody(MEDIA_TYPE_JSON))
.build()
}
// インスタンス情報
"/api/v1/instance" -> Response.Builder()
.request(request)
.protocol(Protocol.HTTP_1_1)
.code(200)
.message("status-message")
.body(JsonObject().apply {
put("uri", "http://${request.url.host}/")
put("title", "dummy instance")
put("description", "dummy description")
put("version", "0.0.1")
}.toString().toResponseBody(MEDIA_TYPE_JSON))
.build()
// 公開タイムライン
"/api/v1/timelines/public" -> {
val instance = request.url.host
// 公開タイムライン
"/api/v1/timelines/public" -> {
val instance = request.url.host
val username = "user1"
val username = "user1"
val account1Json = JsonObject()
account1Json.apply {
put("username", username)
put("acct", username)
put("id", 1L)
put("url", "http://$instance/@$username")
}
val account1Json = JsonObject()
account1Json.apply {
put("username", username)
put("acct", username)
put("id", 1L)
put("url", "http://$instance/@$username")
}
val array = jsonArray {
for (i in 0 until 10) {
add(jsonObject {
put("account", account1Json)
put("id", i.toLong())
put("uri", "https://$instance/@$username/$i")
put("url", "https://$instance/@$username/$i")
})
}
val array = jsonArray {
for (i in 0 until 10) {
add(jsonObject {
put("account", account1Json)
put("id", i.toLong())
put("uri", "https://$instance/@$username/$i")
put("url", "https://$instance/@$username/$i")
})
}
}
}
Response.Builder()
.request(request)
.protocol(Protocol.HTTP_1_1)
.code(200)
.message("status-message")
.body(array.toString().toResponseBody(MEDIA_TYPE_JSON))
.build()
}
Response.Builder()
.request(request)
.protocol(Protocol.HTTP_1_1)
.code(200)
.message("status-message")
.body(array.toString().toResponseBody(MEDIA_TYPE_JSON))
.build()
}
else ->
Response.Builder()
.request(request)
.protocol(Protocol.HTTP_1_1)
.code(200)
.message("status-message")
.body(request.url.toString().toResponseBody(mediaTypeTextPlain))
.build()
}
else ->
Response.Builder()
.request(request)
.protocol(Protocol.HTTP_1_1)
.code(200)
.message("status-message")
.body(request.url.toString().toResponseBody(mediaTypeTextPlain))
.build()
}
},
},
webSocketGenerator = { request: Request, _: WebSocketListener ->
object : WebSocket {
override fun queueSize(): Long = 4096L
webSocketGenerator = { request: Request, _: WebSocketListener ->
object : WebSocket {
override fun queueSize(): Long = 4096L
override fun send(text: String): Boolean = true
override fun send(text: String): Boolean = true
override fun send(bytes: ByteString): Boolean = true
override fun send(bytes: ByteString): Boolean = true
override fun close(code: Int, reason: String?): Boolean = true
override fun close(code: Int, reason: String?): Boolean = true
override fun cancel() = Unit
override fun cancel() = Unit
override fun request(): Request = request
}
}
)
override fun request(): Request = request
}
}
)
}
private fun createHttpClientNotImplemented(): SimpleHttpClient {
return SimpleHttpClientMock(
responseGenerator = { throw NotImplementedError() },
webSocketGenerator = { _, _ -> throw NotImplementedError() }
)
responseGenerator = { throw NotImplementedError() },
webSocketGenerator = { _, _ -> throw NotImplementedError() }
)
}
class ProgressRecordTootApiCallback : TootApiCallback {
@ -299,12 +323,12 @@ class TestTootApiClient {
.code(200)
.message("status-message")
.body(
object : ResponseBody() {
override fun contentLength() = 10L
override fun contentType(): MediaType = MEDIA_TYPE_JSON
override fun source(): BufferedSource = error("ExceptionBody")
}
)
object : ResponseBody() {
override fun contentLength() = 10L
override fun contentType(): MediaType = MEDIA_TYPE_JSON
override fun source(): BufferedSource = error("ExceptionBody")
}
)
.build()
private val strJsonArray1 = """["A!"]"""
@ -384,7 +408,7 @@ class TestTootApiClient {
.body("Error!".toResponseBody("text/plain".toMediaType()))
.build()
message =TootApiClient.simplifyErrorHtml(response)
message = TootApiClient.simplifyErrorHtml(response)
assertEquals("Error!", message)
// empty body
@ -400,7 +424,7 @@ class TestTootApiClient {
.body("".toResponseBody("text/plain".toMediaType()))
.build()
message = TootApiClient.simplifyErrorHtml(response=response,caption="caption" )
message = TootApiClient.simplifyErrorHtml(response = response, caption = "caption")
assertEquals("", message)
}
@ -424,7 +448,7 @@ class TestTootApiClient {
.message("This is test")
.build()
message = TootApiClient.formatResponse(response,"caption")
message = TootApiClient.formatResponse(response, "caption")
assertEquals("(HTTP 500 This is test) caption", message)
@ -441,7 +465,7 @@ class TestTootApiClient {
.body("""{"error":"Error!"}""".toResponseBody(MEDIA_TYPE_JSON))
.build()
message = TootApiClient.formatResponse(response,"caption")
message = TootApiClient.formatResponse(response, "caption")
assertEquals("Error! (HTTP 500 status-message) caption", message)
// json error (after reading body)
@ -460,8 +484,8 @@ class TestTootApiClient {
bodyString = response.body?.string()
message = TootApiClient.formatResponse(response,"caption",bodyString)
assertEquals("Error! (HTTP 500 status-message) caption", message)
message = TootApiClient.formatResponse(response, "caption", bodyString)
assertEquals("(HTTP 500 status-message) caption", message)
// without status message
request = Request.Builder()
@ -478,8 +502,12 @@ class TestTootApiClient {
bodyString = response.body?.string()
message = TootApiClient.formatResponse(response = response,caption = "caption",bodyString = bodyString)
assertEquals("Error! (HTTP 500) caption", message)
message = TootApiClient.formatResponse(
response = response,
caption = "caption",
bodyString = bodyString
)
assertEquals("(HTTP 500) caption", message)
}
@Test
@ -491,27 +519,27 @@ class TestTootApiClient {
var progressMax: Int? = null
val client = TootApiClient(
appContext,
httpClient = createHttpClientNotImplemented(),
callback = object : TootApiCallback {
override val isApiCancelled: Boolean
get() {
++flag
return true
}
appContext,
httpClient = createHttpClientNotImplemented(),
callback = object : TootApiCallback {
override val isApiCancelled: Boolean
get() {
++flag
return true
}
override suspend fun publishApiProgress(s: String) {
++flag
progressString = s
}
override suspend fun publishApiProgress(s: String) {
++flag
progressString = s
}
override suspend fun publishApiProgressRatio(value: Int, max: Int) {
++flag
progressValue = value
progressMax = max
}
}
)
override suspend fun publishApiProgressRatio(value: Int, max: Int) {
++flag
progressValue = value
progressMax = max
}
}
)
val isApiCancelled = client.isApiCancelled
client.publishApiProgress("testing")
client.publishApiProgressRatio(50, 100)
@ -533,17 +561,21 @@ class TestTootApiClient {
// 正常ケースではResponseが返ってくること
run {
val client = TootApiClient(
appContext,
httpClient = createHttpClientNormal(),
callback = callback
)
appContext,
httpClient = createHttpClientNormal(),
callback = callback
)
val result = TootApiResult.makeWithCaption("instance")
assertEquals(null, result.error)
callback.progressString = null
val bOk = client.sendRequest(result) { requestSimple }
assertEquals(true, bOk)
assertEquals("Acquiring: GET /", callback.progressString)
assertOneOf(
callback.progressString,
"Acquiring: GET /",
"取得中: GET /",
)
assertEquals(null, result.error)
assertNotNull(result.response)
}
@ -551,21 +583,26 @@ class TestTootApiClient {
// httpClient.getResponseが例外を出す場合に対応できること
run {
val client = TootApiClient(
appContext,
httpClient = createHttpClientNotImplemented(),
callback = callback
)
appContext,
httpClient = createHttpClientNotImplemented(),
callback = callback
)
val result = TootApiResult.makeWithCaption("instance")
assertEquals(null, result.error)
callback.progressString = null
val bOk = client.sendRequest(result) { requestSimple }
assertEquals(false, bOk)
assertEquals("Acquiring: GET /", callback.progressString)
assertEquals(
"instance: Network error.: NotImplementedError An operation is not implemented.",
result.error
)
assertOneOf(
callback.progressString,
"Acquiring: GET /",
"取得中: GET /",
)
assertOneOf(
result.error,
"instance: Network error.: NotImplementedError An operation is not implemented.",
"instance: 通信エラー。: NotImplementedError An operation is not implemented.",
)
assertNull(result.response)
}
@ -573,17 +610,21 @@ class TestTootApiClient {
// progressPath を指定したらpublishApiProgressに渡されること
run {
val client = TootApiClient(
appContext,
httpClient = createHttpClientNormal(),
callback = callback
)
appContext,
httpClient = createHttpClientNormal(),
callback = callback
)
val result = TootApiResult.makeWithCaption("instance")
assertEquals(null, result.error)
callback.progressString = null
val bOk = client.sendRequest(result, progressPath = "XXX") { requestSimple }
assertEquals(true, bOk)
assertEquals("Acquiring: GET XXX", callback.progressString)
assertOneOf(
callback.progressString,
"Acquiring: GET XXX",
"取得中: GET XXX",
)
assertEquals(null, result.error)
assertNotNull(result.response)
}
@ -595,10 +636,10 @@ class TestTootApiClient {
runBlocking {
val callback = ProgressRecordTootApiCallback()
val client = TootApiClient(
appContext,
httpClient = createHttpClientNormal(),
callback = callback
)
appContext,
httpClient = createHttpClientNormal(),
callback = callback
)
// キャンセルされてたらnullを返すこと
run {
@ -626,7 +667,7 @@ class TestTootApiClient {
val bodyString = client.readBodyString(result)
assertEquals(strJsonOk, bodyString)
assertEquals(strJsonOk, result.bodyString)
assertEquals("Parsing response…", callback.progressString)
assertParsingResponse(callback)
assertNull(result.error)
assertNull(result.data)
}
@ -642,7 +683,7 @@ class TestTootApiClient {
val bodyString = client.readBodyString(result)
assertEquals(null, bodyString)
assertEquals(null, result.bodyString)
assertEquals("Reading: GET instance", callback.progressString)
assertReading(callback,"instance")
assertEquals("Error! (HTTP 500 status-message) instance", result.error)
assertNull(result.data)
}
@ -657,7 +698,7 @@ class TestTootApiClient {
val bodyString = client.readBodyString(result)
assertEquals("", bodyString)
assertEquals("", result.bodyString)
assertEquals("Reading: GET instance", callback.progressString)
assertReading(callback,"instance")
assertEquals(null, result.error)
assertNull(result.data)
}
@ -672,7 +713,7 @@ class TestTootApiClient {
val bodyString = client.readBodyString(result)
assertEquals("", bodyString)
assertEquals("", result.bodyString)
assertEquals("Reading: GET instance", callback.progressString)
assertReading(callback,"instance")
assertEquals(null, result.error)
assertNull(result.data)
}
@ -704,10 +745,10 @@ class TestTootApiClient {
val callback = ProgressRecordTootApiCallback()
val client = TootApiClient(
appContext,
httpClient = createHttpClientNormal(),
callback = callback
)
appContext,
httpClient = createHttpClientNormal(),
callback = callback
)
// キャンセルされてたらnullを返すこと
run {
@ -736,7 +777,7 @@ class TestTootApiClient {
assertNotNull(r2)
assertEquals(strJsonOk, result.string)
assertEquals(strJsonOk, result.bodyString)
assertEquals("Parsing response…", callback.progressString)
assertParsingResponse(callback)
assertNull(result.error)
}
// 正常レスポンスならJSONにエラーがあってもreadStringは関知しない
@ -750,7 +791,7 @@ class TestTootApiClient {
assertNotNull(r2)
assertEquals(strJsonError, result.string)
assertEquals(strJsonError, result.bodyString)
assertEquals("Parsing response…", callback.progressString)
assertParsingResponse(callback)
assertNull(result.error)
}
@ -765,7 +806,7 @@ class TestTootApiClient {
assertNotNull(r2)
assertEquals(null, result.string)
assertEquals(null, result.bodyString)
assertEquals("Reading: GET instance", callback.progressString)
assertReading(callback,"instance")
assertEquals("Error! (HTTP 500 status-message) instance", result.error)
}
@ -780,7 +821,7 @@ class TestTootApiClient {
assertNotNull(r2)
assertEquals(null, result.string)
assertEquals(null, result.bodyString)
assertEquals("Reading: GET instance", callback.progressString)
assertReading(callback,"instance")
assertEquals("(no information) (HTTP 404 status-message) instance", result.error)
assertNull(result.data)
}
@ -796,7 +837,7 @@ class TestTootApiClient {
assertNotNull(r2)
assertEquals("", result.string)
assertEquals("", result.bodyString)
assertEquals("Reading: GET instance", callback.progressString)
assertReading(callback,"instance")
assertEquals(null, result.error)
assertEquals("", result.data)
}
@ -811,7 +852,7 @@ class TestTootApiClient {
assertNotNull(r2)
assertEquals(null, result.string)
assertEquals(null, result.bodyString)
assertEquals("Reading: GET instance", callback.progressString)
assertReading(callback,"instance")
assertEquals("(no information) (HTTP 200 status-message) instance", result.error)
assertNull(result.data)
}
@ -824,10 +865,10 @@ class TestTootApiClient {
runBlocking {
val callback = ProgressRecordTootApiCallback()
val client = TootApiClient(
appContext,
httpClient = createHttpClientNormal(),
callback = callback
)
appContext,
httpClient = createHttpClientNormal(),
callback = callback
)
// キャンセルされてたらnullを返すこと
run {
@ -857,7 +898,7 @@ class TestTootApiClient {
assertNotNull(r2)
assertEquals("A!", result.jsonObject?.optString("a"))
assertEquals(strJsonOk, result.bodyString)
assertEquals("Parsing response…", callback.progressString)
assertParsingResponse(callback)
assertNull(result.error)
}
// 正常ケースでもjsonデータにerror項目があれば
@ -872,7 +913,7 @@ class TestTootApiClient {
assertNotNull(r2)
assertEquals(null, result.data)
assertEquals(strJsonError, result.bodyString)
assertEquals("Parsing response…", callback.progressString)
assertParsingResponse(callback)
assertEquals("Error!", result.error)
}
@ -886,7 +927,7 @@ class TestTootApiClient {
assertNotNull(r2)
assertEquals(null, result.data)
assertEquals(null, result.bodyString)
assertEquals("Reading: GET instance", callback.progressString)
assertReading(callback,"instance")
assertEquals("Error! (HTTP 500 status-message) instance", result.error)
}
@ -901,7 +942,7 @@ class TestTootApiClient {
assertNotNull(r2)
assertEquals(0, result.jsonObject?.size)
assertEquals("", result.bodyString)
assertEquals("Reading: GET instance", callback.progressString)
assertReading(callback,"instance")
assertEquals(null, result.error)
}
@ -915,7 +956,7 @@ class TestTootApiClient {
assertNotNull(r2)
assertEquals(0, result.jsonObject?.size)
assertEquals("", result.bodyString)
assertEquals("Reading: GET instance", callback.progressString)
assertReading(callback,"instance")
assertEquals(null, result.error)
}
@ -929,7 +970,7 @@ class TestTootApiClient {
assertNotNull(r2)
assertEquals(null, result.data)
assertEquals(null, result.bodyString)
assertEquals("Reading: GET instance", callback.progressString)
assertReading(callback,"instance")
assertEquals("(no information) (HTTP 200 status-message) instance", result.error)
assertNull(result.data)
}
@ -945,7 +986,7 @@ class TestTootApiClient {
assertNotNull(r2)
assertEquals("A!", result.jsonArray?.optString(0))
assertEquals(strJsonArray1, result.bodyString)
assertEquals("Parsing response…", callback.progressString)
assertParsingResponse(callback)
assertNull(result.error)
}
@ -961,7 +1002,7 @@ class TestTootApiClient {
assertNotNull(r2)
assertEquals("A!", result.jsonArray?.optString(0))
assertEquals(strJsonArray2, result.bodyString)
assertEquals("Parsing response…", callback.progressString)
assertParsingResponse(callback)
assertNull(result.error)
}
@ -976,7 +1017,7 @@ class TestTootApiClient {
assertNotNull(r2)
assertEquals("A!", result.jsonObject?.optString("a"))
assertEquals(strJsonObject2, result.bodyString)
assertEquals("Parsing response…", callback.progressString)
assertParsingResponse(callback)
assertNull(result.error)
}
// JSONじゃない
@ -990,8 +1031,12 @@ class TestTootApiClient {
assertNotNull(r2)
assertEquals(null, result.data)
assertEquals(strPlainText, result.bodyString)
assertEquals("Parsing response…", callback.progressString)
assertEquals("API response is not JSON. Hello! (HTTP 200 status-message) https://dummy-url.net/", result.error)
assertParsingResponse(callback)
assertOneOf(
result.error,
"API response is not JSON. Hello! (HTTP 200 status-message) https://dummy-url.net/",
"APIの応答がJSONではありません Hello! (HTTP 200 status-message) https://dummy-url.net/",
)
}
}
}
@ -1001,10 +1046,10 @@ class TestTootApiClient {
runBlocking {
val callback = ProgressRecordTootApiCallback()
val client = TootApiClient(
appContext,
httpClient = createHttpClientNormal(),
callback = callback
)
appContext,
httpClient = createHttpClientNormal(),
callback = callback
)
val instance = Host.parse("unit-test")
client.apiHost = instance
val clientName = "SubwayTooterUnitTest"
@ -1052,7 +1097,7 @@ class TestTootApiClient {
// ブラウザからコールバックで受け取ったcodeを処理する
val refToken = AtomicReference<String>(null)
result = client.authentication2Mastodon(clientName, "DUMMY_CODE",refToken)
result = client.authentication2Mastodon(clientName, "DUMMY_CODE", refToken)
jsonObject = result?.jsonObject
assertNotNull(jsonObject)
if (jsonObject == null) return@runBlocking
@ -1080,13 +1125,13 @@ class TestTootApiClient {
runBlocking {
val callback = ProgressRecordTootApiCallback()
val client = TootApiClient(
appContext,
httpClient = createHttpClientNormal(),
callback = callback
)
appContext,
httpClient = createHttpClientNormal(),
callback = callback
)
val instance = Host.parse("unit-test")
client.apiHost = instance
val (instanceInfo, instanceResult) = TootInstance.get(client)
val (instanceInfo, instanceResult) = TootInstance.get(client)
assertNotNull(instanceInfo)
assertNotNull(instanceResult)
val json = instanceResult?.jsonObject
@ -1099,10 +1144,10 @@ class TestTootApiClient {
runBlocking {
val callback = ProgressRecordTootApiCallback()
val client = TootApiClient(
appContext,
httpClient = createHttpClientNormal(),
callback = callback
)
appContext,
httpClient = createHttpClientNormal(),
callback = callback
)
val result = client.getHttp("http://juggler.jp/")
val content = result?.string
assertNotNull(content)
@ -1117,17 +1162,17 @@ class TestTootApiClient {
tokenInfo["access_token"] = "DUMMY_ACCESS_TOKEN"
val accessInfo = SavedAccount(
db_id = 1,
acctArg = "user1@host1",
apiHostArg = null,
token_info = tokenInfo
)
db_id = 1,
acctArg = "user1@host1",
apiHostArg = null,
token_info = tokenInfo
)
val callback = ProgressRecordTootApiCallback()
val client = TootApiClient(
appContext,
httpClient = createHttpClientNormal(),
callback = callback
)
appContext,
httpClient = createHttpClientNormal(),
callback = callback
)
client.account = accessInfo
val result = client.request("/api/v1/timelines/public")
println(result?.bodyString)
@ -1146,21 +1191,21 @@ class TestTootApiClient {
}
val accessInfo = SavedAccount(
db_id = 1,
acctArg = "user1@host1",
apiHostArg = null,
token_info = tokenInfo
)
db_id = 1,
acctArg = "user1@host1",
apiHostArg = null,
token_info = tokenInfo
)
val callback = ProgressRecordTootApiCallback()
val client = TootApiClient(
appContext,
httpClient = createHttpClientNormal(),
callback = callback
)
appContext,
httpClient = createHttpClientNormal(),
callback = callback
)
client.account = accessInfo
val (_, ws) = client.webSocket("/api/v1/streaming/?stream=public:local",
object : WebSocketListener() {
})
val (_, ws) = client.webSocket("/api/v1/streaming/?stream=public:local",
object : WebSocketListener() {
})
assertNotNull(ws)
ws?.cancel()
}

View File

@ -1,269 +1,268 @@
package jp.juggler.subwaytooter.api.entity
import androidx.test.runner.AndroidJUnit4
import android.test.mock.MockContext
import androidx.test.runner.AndroidJUnit4
import jp.juggler.subwaytooter.api.TootParser
import jp.juggler.subwaytooter.table.SavedAccount
import jp.juggler.util.JsonArray
import jp.juggler.util.JsonObject
import jp.juggler.util.notEmptyOrThrow
import jp.juggler.util.decodeJsonObject
import jp.juggler.util.notEmptyOrThrow
import org.junit.Assert.*
import org.junit.runner.RunWith
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class TestEntityUtils {
class TestEntity(val s : String, val l : Long) : Mappable<String> {
constructor(src : JsonObject) : this(
s = src.stringOrThrow("s"),
l = src.long("l") ?: 0L
)
@Suppress("UNUSED_PARAMETER")
constructor(parser : TootParser, src : JsonObject) : this(
s = src.stringOrThrow("s"),
l = src.long("l") ?: 0L
)
override val mapKey : String
get() = s
}
@Test
fun testParseItem() {
assertEquals(null, parseItem(::TestEntity, null))
run {
val src = """{"s":null,"l":"100"}""".decodeJsonObject()
val item = parseItem(::TestEntity, src)
assertNull(item)
}
run {
val src = """{"s":"","l":"100"}""".decodeJsonObject()
val item = parseItem(::TestEntity, src)
assertNull(item)
}
run {
val src = """{"s":"A","l":null}""".decodeJsonObject()
val item = parseItem(::TestEntity, src)
assertNotNull(item)
assertEquals(src.optString("s"), item?.s)
assertEquals(src.optLong("l"), item?.l)
}
run {
val src = """{"s":"A","l":""}""".decodeJsonObject()
val item = parseItem(::TestEntity, src)
assertNotNull(item)
assertEquals(src.optString("s"), item?.s)
assertEquals(src.optLong("l"), item?.l)
}
run {
val src = """{"s":"A","l":100}""".decodeJsonObject()
val item = parseItem(::TestEntity, src)
assertNotNull(item)
assertEquals(src.optString("s"), item?.s)
assertEquals(src.optLong("l"), item?.l)
}
run {
val src ="""{"s":"A","l":"100"}""".decodeJsonObject()
val item = parseItem(::TestEntity, src)
assertNotNull(item)
assertEquals(src.optString("s"), item?.s)
assertEquals(src.optLong("l"), item?.l)
}
}
@Test
fun testParseList() {
assertEquals(0, parseList(::TestEntity, null).size)
val src = JsonArray()
assertEquals(0, parseList(::TestEntity, src).size)
src.add("""{"s":"A","l":"100"}""".decodeJsonObject())
assertEquals(1, parseList(::TestEntity, src).size)
src.add("""{"s":"A","l":"100"}""".decodeJsonObject())
assertEquals(2, parseList(::TestEntity, src).size)
// error
src.add("""{"s":"","l":"100"}""".decodeJsonObject())
assertEquals(2, parseList(::TestEntity, src).size)
}
@Test
fun testParseListOrNull() {
assertEquals(null, parseListOrNull(::TestEntity, null))
val src = JsonArray()
assertEquals(null, parseListOrNull(::TestEntity, src))
src.add("""{"s":"A","l":"100"}""".decodeJsonObject())
assertEquals(1, parseListOrNull(::TestEntity, src)?.size)
src.add("""{"s":"A","l":"100"}""".decodeJsonObject())
assertEquals(2, parseListOrNull(::TestEntity, src)?.size)
// error
src.add("""{"s":"","l":"100"}""".decodeJsonObject())
assertEquals(2, parseListOrNull(::TestEntity, src)?.size)
}
@Test
fun testParseMap() {
assertEquals(0, parseMap(::TestEntity, null).size)
val src = JsonArray()
assertEquals(0, parseMap(::TestEntity, src).size)
src.add("""{"s":"A","l":"100"}""".decodeJsonObject())
assertEquals(1, parseMap(::TestEntity, src).size)
src.add("""{"s":"B","l":"100"}""".decodeJsonObject())
assertEquals(2, parseMap(::TestEntity, src).size)
// error
src.add("""{"s":"","l":"100"}""".decodeJsonObject())
assertEquals(2, parseMap(::TestEntity, src).size)
}
@Test
fun testParseMapOrNull() {
assertEquals(null, parseMapOrNull(::TestEntity, null))
val src = JsonArray()
assertEquals(null, parseMapOrNull(::TestEntity, src))
src.add("""{"s":"A","l":"100"}""".decodeJsonObject())
assertEquals(1, parseMapOrNull(::TestEntity, src)?.size)
src.add("""{"s":"B","l":"100"}""".decodeJsonObject())
assertEquals(2, parseMapOrNull(::TestEntity, src)?.size)
// error
src.add("""{"s":"","l":"100"}""".decodeJsonObject())
assertEquals(2, parseMapOrNull(::TestEntity, src)?.size)
}
private val parser = TootParser(MockContext(), SavedAccount.na)
@Test
fun testParseItemWithParser() {
assertEquals(null, parseItem(::TestEntity, parser, null))
run {
val src ="""{"s":null,"l":"100"}""".decodeJsonObject()
val item = parseItem(::TestEntity, parser, src)
assertNull(item)
}
run {
val src = """{"s":"","l":"100"}""".decodeJsonObject()
val item = parseItem(::TestEntity, parser, src)
assertNull(item)
}
run {
val src = """{"s":"A","l":null}""".decodeJsonObject()
val item = parseItem(::TestEntity, parser, src)
assertNotNull(item)
assertEquals(src.optString("s"), item?.s)
assertEquals(src.optLong("l"), item?.l)
}
run {
val src = """{"s":"A","l":""}""".decodeJsonObject()
val item = parseItem(::TestEntity, parser, src)
assertNotNull(item)
assertEquals(src.optString("s"), item?.s)
assertEquals(src.optLong("l"), item?.l)
}
run {
val src = """{"s":"A","l":100}""".decodeJsonObject()
val item = parseItem(::TestEntity, parser, src)
assertNotNull(item)
assertEquals(src.optString("s"), item?.s)
assertEquals(src.optLong("l"), item?.l)
}
run {
val src = """{"s":"A","l":"100"}""".decodeJsonObject()
val item = parseItem(::TestEntity, parser, src)
assertNotNull(item)
assertEquals(src.optString("s"), item?.s)
assertEquals(src.optLong("l"), item?.l)
}
}
@Test
fun testParseListWithParser() {
assertEquals(0, parseList(::TestEntity, parser, null).size)
val src = JsonArray()
assertEquals(0, parseList(::TestEntity, parser, src).size)
src.add("""{"s":"A","l":"100"}""".decodeJsonObject())
assertEquals(1, parseList(::TestEntity, parser, src).size)
src.add("""{"s":"A","l":"100"}""".decodeJsonObject())
assertEquals(2, parseList(::TestEntity, parser, src).size)
// error
src.add("""{"s":"","l":"100"}""".decodeJsonObject())
assertEquals(2, parseList(::TestEntity, parser, src).size)
}
@Test
fun testParseListOrNullWithParser() {
assertEquals(null, parseListOrNull(::TestEntity, parser, null))
val src = JsonArray()
assertEquals(null, parseListOrNull(::TestEntity, parser, src))
src.add("""{"s":"A","l":"100"}""".decodeJsonObject())
assertEquals(1, parseListOrNull(::TestEntity, parser, src)?.size)
src.add("""{"s":"A","l":"100"}""".decodeJsonObject())
assertEquals(2, parseListOrNull(::TestEntity, parser, src)?.size)
// error
src.add("""{"s":"","l":"100"}""".decodeJsonObject())
assertEquals(2, parseListOrNull(::TestEntity, parser, src)?.size)
}
@Test(expected = RuntimeException::class)
fun testNotEmptyOrThrow1() {
println(notEmptyOrThrow("param1", null))
}
@Test(expected = RuntimeException::class)
fun testNotEmptyOrThrow2() {
println(notEmptyOrThrow("param1", ""))
}
@Test
fun testNotEmptyOrThrow3() {
assertEquals("A", notEmptyOrThrow("param1", "A"))
}
@Test(expected = RuntimeException::class)
fun testNotEmptyOrThrow4() {
println("""{"param1":null}""".decodeJsonObject().stringOrThrow("param1"))
}
@Test(expected = RuntimeException::class)
fun testNotEmptyOrThrow5() {
println("""{"param1":""}""".decodeJsonObject().stringOrThrow("param1"))
}
@Test
fun testNotEmptyOrThrow6() {
assertEquals("A", """{"param1":"A"}""".decodeJsonObject().stringOrThrow("param1"))
}
class TestEntity(val s: String, val l: Long) : Mappable<String> {
constructor(src: JsonObject) : this(
s = src.stringOrThrow("s"),
l = src.long("l") ?: 0L
)
@Suppress("UNUSED_PARAMETER")
constructor(parser: TootParser, src: JsonObject) : this(
s = src.stringOrThrow("s"),
l = src.long("l") ?: 0L
)
override val mapKey: String
get() = s
}
@Test
fun testParseItem() {
assertEquals(null, parseItem(::TestEntity, null))
run {
val src = """{"s":null,"l":"100"}""".decodeJsonObject()
val item = parseItem(::TestEntity, src)
assertNull(item)
}
run {
val src = """{"s":"","l":"100"}""".decodeJsonObject()
val item = parseItem(::TestEntity, src)
assertNull(item)
}
run {
val src = """{"s":"A","l":null}""".decodeJsonObject()
val item = parseItem(::TestEntity, src)
assertNotNull(item)
assertEquals(src.optString("s"), item?.s)
assertEquals(src.optLong("l"), item?.l)
}
run {
val src = """{"s":"A","l":""}""".decodeJsonObject()
val item = parseItem(::TestEntity, src)
assertNotNull(item)
assertEquals(src.optString("s"), item?.s)
assertEquals(src.optLong("l"), item?.l)
}
run {
val src = """{"s":"A","l":100}""".decodeJsonObject()
val item = parseItem(::TestEntity, src)
assertNotNull(item)
assertEquals(src.optString("s"), item?.s)
assertEquals(src.optLong("l"), item?.l)
}
run {
val src = """{"s":"A","l":"100"}""".decodeJsonObject()
val item = parseItem(::TestEntity, src)
assertNotNull(item)
assertEquals(src.optString("s"), item?.s)
assertEquals(src.optLong("l"), item?.l)
}
}
@Test
fun testParseList() {
assertEquals(0, parseList(::TestEntity, null).size)
val src = JsonArray()
assertEquals(0, parseList(::TestEntity, src).size)
src.add("""{"s":"A","l":"100"}""".decodeJsonObject())
assertEquals(1, parseList(::TestEntity, src).size)
src.add("""{"s":"A","l":"100"}""".decodeJsonObject())
assertEquals(2, parseList(::TestEntity, src).size)
// error
src.add("""{"s":"","l":"100"}""".decodeJsonObject())
assertEquals(2, parseList(::TestEntity, src).size)
}
@Test
fun testParseListOrNull() {
assertEquals(null, parseListOrNull(::TestEntity, null))
val src = JsonArray()
assertEquals(null, parseListOrNull(::TestEntity, src))
src.add("""{"s":"A","l":"100"}""".decodeJsonObject())
assertEquals(1, parseListOrNull(::TestEntity, src)?.size)
src.add("""{"s":"A","l":"100"}""".decodeJsonObject())
assertEquals(2, parseListOrNull(::TestEntity, src)?.size)
// error
src.add("""{"s":"","l":"100"}""".decodeJsonObject())
assertEquals(2, parseListOrNull(::TestEntity, src)?.size)
}
@Test
fun testParseMap() {
assertEquals(0, parseMap(::TestEntity, null).size)
val src = JsonArray()
assertEquals(0, parseMap(::TestEntity, src).size)
src.add("""{"s":"A","l":"100"}""".decodeJsonObject())
assertEquals(1, parseMap(::TestEntity, src).size)
src.add("""{"s":"B","l":"100"}""".decodeJsonObject())
assertEquals(2, parseMap(::TestEntity, src).size)
// error
src.add("""{"s":"","l":"100"}""".decodeJsonObject())
assertEquals(2, parseMap(::TestEntity, src).size)
}
@Test
fun testParseMapOrNull() {
assertEquals(null, parseMapOrNull(::TestEntity, null))
val src = JsonArray()
assertEquals(null, parseMapOrNull(::TestEntity, src))
src.add("""{"s":"A","l":"100"}""".decodeJsonObject())
assertEquals(1, parseMapOrNull(::TestEntity, src)?.size)
src.add("""{"s":"B","l":"100"}""".decodeJsonObject())
assertEquals(2, parseMapOrNull(::TestEntity, src)?.size)
// error
src.add("""{"s":"","l":"100"}""".decodeJsonObject())
assertEquals(2, parseMapOrNull(::TestEntity, src)?.size)
}
private val parser = TootParser(MockContext(), SavedAccount.na)
@Test
fun testParseItemWithParser() {
assertEquals(null, parseItem(::TestEntity, parser, null))
run {
val src = """{"s":null,"l":"100"}""".decodeJsonObject()
val item = parseItem(::TestEntity, parser, src)
assertNull(item)
}
run {
val src = """{"s":"","l":"100"}""".decodeJsonObject()
val item = parseItem(::TestEntity, parser, src)
assertNull(item)
}
run {
val src = """{"s":"A","l":null}""".decodeJsonObject()
val item = parseItem(::TestEntity, parser, src)
assertNotNull(item)
assertEquals(src.optString("s"), item?.s)
assertEquals(src.optLong("l"), item?.l)
}
run {
val src = """{"s":"A","l":""}""".decodeJsonObject()
val item = parseItem(::TestEntity, parser, src)
assertNotNull(item)
assertEquals(src.optString("s"), item?.s)
assertEquals(src.optLong("l"), item?.l)
}
run {
val src = """{"s":"A","l":100}""".decodeJsonObject()
val item = parseItem(::TestEntity, parser, src)
assertNotNull(item)
assertEquals(src.optString("s"), item?.s)
assertEquals(src.optLong("l"), item?.l)
}
run {
val src = """{"s":"A","l":"100"}""".decodeJsonObject()
val item = parseItem(::TestEntity, parser, src)
assertNotNull(item)
assertEquals(src.optString("s"), item?.s)
assertEquals(src.optLong("l"), item?.l)
}
}
@Test
fun testParseListWithParser() {
assertEquals(0, parseList(::TestEntity, parser, null).size)
val src = JsonArray()
assertEquals(0, parseList(::TestEntity, parser, src).size)
src.add("""{"s":"A","l":"100"}""".decodeJsonObject())
assertEquals(1, parseList(::TestEntity, parser, src).size)
src.add("""{"s":"A","l":"100"}""".decodeJsonObject())
assertEquals(2, parseList(::TestEntity, parser, src).size)
// error
src.add("""{"s":"","l":"100"}""".decodeJsonObject())
assertEquals(2, parseList(::TestEntity, parser, src).size)
}
@Test
fun testParseListOrNullWithParser() {
assertEquals(null, parseListOrNull(::TestEntity, parser, null))
val src = JsonArray()
assertEquals(null, parseListOrNull(::TestEntity, parser, src))
src.add("""{"s":"A","l":"100"}""".decodeJsonObject())
assertEquals(1, parseListOrNull(::TestEntity, parser, src)?.size)
src.add("""{"s":"A","l":"100"}""".decodeJsonObject())
assertEquals(2, parseListOrNull(::TestEntity, parser, src)?.size)
// error
src.add("""{"s":"","l":"100"}""".decodeJsonObject())
assertEquals(2, parseListOrNull(::TestEntity, parser, src)?.size)
}
@Test(expected = RuntimeException::class)
fun testNotEmptyOrThrow1() {
println(notEmptyOrThrow("param1", null))
}
@Test(expected = RuntimeException::class)
fun testNotEmptyOrThrow2() {
println(notEmptyOrThrow("param1", ""))
}
@Test
fun testNotEmptyOrThrow3() {
assertEquals("A", notEmptyOrThrow("param1", "A"))
}
@Test(expected = RuntimeException::class)
fun testNotEmptyOrThrow4() {
println("""{"param1":null}""".decodeJsonObject().stringOrThrow("param1"))
}
@Test(expected = RuntimeException::class)
fun testNotEmptyOrThrow5() {
println("""{"param1":""}""".decodeJsonObject().stringOrThrow("param1"))
}
@Test
fun testNotEmptyOrThrow6() {
assertEquals("A", """{"param1":"A"}""".decodeJsonObject().stringOrThrow("param1"))
}
}

View File

@ -2,60 +2,68 @@ package jp.juggler.subwaytooter.api.entity
import androidx.test.runner.AndroidJUnit4
import jp.juggler.subwaytooter.util.LinkHelper
import org.junit.Assert.assertEquals
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.Assert.assertEquals
@RunWith(AndroidJUnit4::class)
class TestTootAccount {
@Test
@Throws(Exception::class)
fun testFindHostFromUrl() {
// all null
assertEquals(null, TootAccount.findHostFromUrl(null, null, null).first)
// find from acct
assertEquals(null, TootAccount.findHostFromUrl("", null, null).first)
assertEquals(null, TootAccount.findHostFromUrl("user", null, null).first)
assertEquals(null, TootAccount.findHostFromUrl("user@", null, null).first)
assertEquals("host", TootAccount.findHostFromUrl("user@HOST", null, null).first)
// find from accessHost
assertEquals(
"",
TootAccount.findHostFromUrl(null, LinkHelper.create(Host.parse("")), null).first
)
assertEquals(
"any string is allowed",
TootAccount.findHostFromUrl(
null,
LinkHelper.create(Host.parse("any string is allowed")),
null
).first
)
// find from url
assertEquals(null, TootAccount.findHostFromUrl(null, null, "").first)
assertEquals(null, TootAccount.findHostFromUrl(null, null, "xxx").first)
assertEquals(
null,
TootAccount.findHostFromUrl(null, null, "mailto:tateisu@gmail.com").first
)
assertEquals(
"mastodon.juggler.jp",
TootAccount.findHostFromUrl(null, null, "https://MASTODON.juggler.jp/@tateisu").first
)
assertEquals(
"mastodon.juggler.jp",
TootAccount.findHostFromUrl(null, null, "https://mastodon.juggler.jp/").first
)
assertEquals(
"mastodon.juggler.jp",
TootAccount.findHostFromUrl(null, null, "https://mastodon.juggler.jp").first
)
}
@Test
@Throws(Exception::class)
fun testFindHostFromUrl() {
val emptyHost = Host.EMPTY
// all null
var pair = TootAccount.findHostFromUrl(null, null, null)
assertEquals(null, pair.first)
// find from acct
pair = TootAccount.findHostFromUrl("", null, null)
assertEquals(Host.UNKNOWN, pair.first)
assertEquals(null, TootAccount.findHostFromUrl("user", null, null).first)
assertEquals(emptyHost, TootAccount.findHostFromUrl("user@", null, null).first)
assertEquals(
"host",
TootAccount.findHostFromUrl("user@HOST", null, null)
.first?.ascii
)
// find from accessHost
assertEquals(
emptyHost,
TootAccount.findHostFromUrl(null, LinkHelper.create(emptyHost), null).first
)
val testHost = Host.parse("any string is allowed")
assertEquals(
testHost,
TootAccount.findHostFromUrl(
null,
LinkHelper.create(testHost),
null
).first
)
// find from url
assertEquals(null, TootAccount.findHostFromUrl(null, null, "").first)
assertEquals(null, TootAccount.findHostFromUrl(null, null, "xxx").first)
assertEquals(
null,
TootAccount.findHostFromUrl(null, null, "mailto:tateisu@gmail.com").first
)
assertEquals(
Host.parse("mastodon.juggler.jp"),
TootAccount.findHostFromUrl(null, null, "https://MASTODON.juggler.jp/@tateisu").first
)
assertEquals(
Host.parse("mastodon.juggler.jp"),
TootAccount.findHostFromUrl(null, null, "https://mastodon.juggler.jp/").first
)
assertEquals(
Host.parse("mastodon.juggler.jp"),
TootAccount.findHostFromUrl(null, null, "https://mastodon.juggler.jp").first
)
}
}

View File

@ -0,0 +1,26 @@
package jp.juggler.subwaytooter.database
import androidx.test.runner.AndroidJUnit4
import org.junit.Assert.assertEquals
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class TestArrayListSizeBug {
@Test
fun testArrayListSize() {
val list = ArrayList(arrayOf("c", "b", "a").toList())
assertEquals("ArrayList size", 3, list.size)
}
class ArrayListDerived<E>(args: List<E>) : ArrayList<E>(args)
@Test
fun testArrayListDerived() {
val list = ArrayListDerived(arrayOf("c", "b", "a").toList())
assertEquals("ArrayListDerived size", 3, list.size)
// kotlin 1.5.31で(Javaの) size() ではなく getSize() にアクセスしようとして例外を出していた
// kotlin 1.5.30では大丈夫だったが、JetPack Composeは 1.5.31を要求するのだった…。
}
}

View File

@ -0,0 +1,66 @@
package jp.juggler.subwaytooter.database
import android.content.Context
import android.database.sqlite.SQLiteDatabase
import android.database.sqlite.SQLiteOpenHelper
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.runner.AndroidJUnit4
import jp.juggler.subwaytooter.App1
import org.junit.Assert.assertNull
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class TestDatabase {
private class MockDbHelper(
context: Context,
dbName: String,
dbVersion: Int,
val create: (SQLiteDatabase) -> Unit,
val upgrade: (SQLiteDatabase, Int, Int) -> Unit,
) : SQLiteOpenHelper(context, dbName, null, dbVersion) {
override fun onCreate(db: SQLiteDatabase) {
create(db)
}
override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
upgrade(db, oldVersion, newVersion)
}
}
@Test
fun testCreateAll() {
val context = InstrumentationRegistry.getInstrumentation().targetContext
val dbName = "testCreateAll"
val helper = MockDbHelper(
context,
dbName,
App1.DB_VERSION,
create = { db ->
App1.tableList.forEach {
val ex = try {
it.onDBCreate(db)
null
} catch (ex: Throwable) {
ex
}
assertNull("${it.table} onDBCreate", ex)
}
},
upgrade = { db, oldV, newV ->
App1.tableList.forEach {
val ex = try {
it.onDBUpgrade(db, oldV, newV)
null
} catch (ex: Throwable) {
ex
}
assertNull("${it.table} onDBUpgrade", ex)
}
}
)
context.deleteDatabase(dbName)
helper.writableDatabase
}
}

View File

@ -1,24 +1,25 @@
package jp.juggler.subwaytooter.util
import androidx.test.runner.AndroidJUnit4
import org.junit.Assert.*
import androidx.test.runner.AndroidJUnit4
import org.junit.Assert.assertEquals
import org.junit.Test
import org.junit.runner.RunWith
@Suppress("MemberVisibilityCanPrivate")
@RunWith(AndroidJUnit4::class)
class TestBucketList {
@Test fun test1(){
val list = BucketList<String>(bucketCapacity=2)
assertEquals(true,list.isEmpty())
list.addAll( listOf("A","B","C"))
list.addAll( 3, listOf("a","b","c"))
list.addAll( 1, listOf("a","b","c"))
list.removeAt(7)
assertEquals(8,list.size)
listOf("A","a","b","c","B","C","a","c").forEachIndexed { i,v->
assertEquals( v,list[i])
}
}
@Test
fun test1() {
val list = BucketList<String>(bucketCapacity = 2)
assertEquals(true, list.isEmpty())
list.addAll(listOf("A", "B", "C"))
list.addAll(3, listOf("a", "b", "c"))
list.addAll(1, listOf("a", "b", "c"))
list.removeAt(7)
assertEquals(8, list.size)
listOf("A", "a", "b", "c", "B", "C", "a", "c").forEachIndexed { i, v ->
assertEquals(v, list[i])
}
}
}

View File

@ -1,6 +1,6 @@
package jp.juggler.subwaytooter.util
import androidx.test.InstrumentationRegistry
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.runner.AndroidJUnit4
import jp.juggler.subwaytooter.api.entity.Host
import jp.juggler.util.neatSpaces
@ -12,46 +12,47 @@ import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class TestHtmlDecoder {
class SpanMeta(
val span:Any,
val start:Int,
val end:Int,
val flags:Int,
val text :String
){
override fun toString() = "[$start..$end) $flags ${span.javaClass.simpleName} $text"
}
@Test fun test1(){
// Context of the app under test.
val appContext = InstrumentationRegistry.getTargetContext()
val options = DecodeOptions(appContext,LinkHelper.create(Host.parse("instance.test")))
val html = """
class SpanMeta(
val span: Any,
val start: Int,
val end: Int,
val flags: Int,
val text: String
) {
override fun toString() = "[$start..$end) $flags ${span.javaClass.simpleName} $text"
}
@Test
fun test1() {
// Context of the app under test.
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
val options = DecodeOptions(appContext, LinkHelper.create(Host.parse("instance.test")))
val html = """
日本語で楽しめるMastodonサーバを提供しています
<a href="https://mastodon.juggler.jp/terms">利用規約</a>を読んでからサインアップしてください
<a href="https://play.google.com/store/search?q=%E3%83%9E%E3%82%B9%E3%83%88%E3%83%89%E3%83%B3&amp;c=apps"><img alt="Androidアプリ" src='https://m1j.zzz.ac/ja_badge_web_generic.png' height="48"></a>
<a href="https://theappstore.org/search.php?search=mastodon&amp;platform=software"><img alt="iOSアプリ" src="https://linkmaker.itunes.apple.com/ja-jp/badge-lrg.svg?releaseDate=2017-08-09&amp;kind=iossoftware&amp;bubble=ios_apps" height="48"></a>
long text long text long text long text long text long text long text long text long text long text long text long text long text long text long text long text long text long text long text long text long text long text long text long text long text long text long text long text long text long text long text long text long text long text long text long text long text long text long text long text long text
""".trimIndent()
val text = options.decodeHTML( html ).neatSpaces()
val spanArray = text.getSpans(0,text.length,Any::class.java).map {
val start = text.getSpanStart(it)
val end = text.getSpanEnd(it)
SpanMeta(
span = it,
start = start,
end = end,
flags = text.getSpanFlags(it),
text = text.subSequence(start, end).toString()
)
}
spanArray.forEach{ println(it)}
assertEquals(3,spanArray.size)
assertEquals( "利用規約", spanArray[0].text)
assertEquals( "<img/>", spanArray[1].text)
assertEquals( "<img/>", spanArray[2].text)
}
val text = options.decodeHTML(html).neatSpaces()
val spanArray = text.getSpans(0, text.length, Any::class.java).map {
val start = text.getSpanStart(it)
val end = text.getSpanEnd(it)
SpanMeta(
span = it,
start = start,
end = end,
flags = text.getSpanFlags(it),
text = text.subSequence(start, end).toString()
)
}
spanArray.forEach { println(it) }
assertEquals(5, spanArray.size)
assertEquals("利用規約", spanArray[0].text)
assertEquals("Androidアプリ", spanArray[1].text)
assertEquals("<img Androidアプリ />", spanArray[2].text)
}
}

View File

@ -131,9 +131,9 @@ class App1 : Application() {
// 2021/5/11 59=>60 SavedAccountテーブルに項目追加
// 2021/5/23 60=>61 SavedAccountテーブルに項目追加
internal const val DB_VERSION = 61
const val DB_VERSION = 61
private val tableList = arrayOf(
val tableList = arrayOf(
LogData,
SavedAccount,
ClientInfo,

View File

@ -3,12 +3,11 @@ package jp.juggler.subwaytooter
import android.content.Context
import android.content.SharedPreferences
import android.graphics.Color
import androidx.preference.PreferenceManager
import jp.juggler.subwaytooter.itemviewholder.AdditionalButtonsPosition
import jp.juggler.util.optInt
fun Context.pref(): SharedPreferences =
PreferenceManager.getDefaultSharedPreferences(this)
this.getSharedPreferences(this.packageName + "_preferences", Context.MODE_PRIVATE)
@Suppress("EqualsOrHashCode")
abstract class BasePref<T>(val key: String, val defVal: T) {
@ -503,7 +502,8 @@ object PrefI {
const val VS_MISSKEY = 2
val ipVisibilityStyle = IntPref("ipVisibilityStyle", VS_BY_ACCOUNT)
val ipAdditionalButtonsPosition = IntPref("AdditionalButtonsPosition", AdditionalButtonsPosition.End.idx)
val ipAdditionalButtonsPosition =
IntPref("AdditionalButtonsPosition", AdditionalButtonsPosition.End.idx)
val ipFooterButtonBgColor = IntPref("footer_button_bg_color", 0)
val ipFooterButtonFgColor = IntPref("footer_button_fg_color", 0)

View File

@ -75,7 +75,7 @@ class AcctColor {
private val log = LogCategory("AcctColor")
const val table = "acct_color"
override val table = "acct_color"
val columnList: ColumnMeta.List = ColumnMeta.List(table, 9).apply {
// not used, but must be defined

View File

@ -16,7 +16,7 @@ object AcctSet : TableCompanion {
private val log = LogCategory("AcctSet")
private const val table = "acct_set"
override val table = "acct_set"
val columnList: ColumnMeta.List = ColumnMeta.List(table, 7).apply {
ColumnMeta(this, 0, BaseColumns._ID, "INTEGER PRIMARY KEY", primary = true)

View File

@ -9,7 +9,7 @@ import jp.juggler.util.*
object ClientInfo : TableCompanion {
private val log = LogCategory("ClientInfo")
const val table = "client_info2"
override val table = "client_info2"
val columnList: ColumnMeta.List = ColumnMeta.List(table, 19).apply {
ColumnMeta(this, 0, BaseColumns._ID, "INTEGER PRIMARY KEY", primary = true)
createExtra = {

View File

@ -11,7 +11,7 @@ import jp.juggler.util.*
object ContentWarning : TableCompanion {
private val log = LogCategory("ContentWarning")
private const val table = "content_warning"
override val table = "content_warning"
val columnList: ColumnMeta.List = ColumnMeta.List(table, 0).apply {
ColumnMeta(this, 0, BaseColumns._ID, "INTEGER PRIMARY KEY", primary = true)

View File

@ -13,7 +13,7 @@ object FavMute : TableCompanion {
private val log = LogCategory("FavMute")
const val table = "fav_mute"
override val table = "fav_mute"
const val COL_ID = "_id"
const val COL_ACCT = "acct"

View File

@ -19,7 +19,7 @@ class HighlightWord {
const val SOUND_TYPE_DEFAULT = 1
const val SOUND_TYPE_CUSTOM = 2
const val table = "highlight_word"
override val table = "highlight_word"
const val COL_ID = BaseColumns._ID
const val COL_NAME = "name"
private const val COL_TIME_SAVE = "time_save"

View File

@ -6,7 +6,7 @@ import jp.juggler.util.TableCompanion
object LogData : TableCompanion {
// private const val TAG = "SubwayTooter"
internal const val table = "warning"
override val table = "warning"
private const val COL_TIME = "t"
private const val COL_LEVEL = "l"

View File

@ -10,7 +10,7 @@ import jp.juggler.util.*
object MediaShown : TableCompanion {
private val log = LogCategory("MediaShown")
private const val table = "media_shown_misskey"
override val table = "media_shown_misskey"
val columnList: ColumnMeta.List = ColumnMeta.List(table, 30).apply {
ColumnMeta(this, 0, BaseColumns._ID, "INTEGER PRIMARY KEY", primary = true)

View File

@ -14,7 +14,7 @@ object MutedApp : TableCompanion {
private val log = LogCategory("MutedApp")
internal const val table = "app_mute"
override val table = "app_mute"
const val COL_ID = "_id"
const val COL_NAME = "name"
private const val COL_TIME_SAVE = "time_save"

View File

@ -13,7 +13,7 @@ object MutedWord : TableCompanion {
private val log = LogCategory("MutedWord")
const val table = "word_mute"
override val table = "word_mute"
const val COL_ID = "_id"
const val COL_NAME = "name"
private const val COL_TIME_SAVE = "time_save"

View File

@ -28,7 +28,7 @@ class NotificationCache(private val account_db_id: Long) {
private val log = LogCategory("NotificationCache")
private const val table = "noti_cache"
override val table = "noti_cache"
private const val COL_ID = BaseColumns._ID

View File

@ -83,7 +83,12 @@ class NotificationTracking {
val cv = ContentValues()
postId.putTo(cv, COL_POST_ID)
cv.put(COL_POST_TIME, postTime)
val rows = App1.database.update(table, cv, WHERE_AID, arrayOf(accountDbId.toString(), notificationType))
val rows = App1.database.update(
table,
cv,
WHERE_AID,
arrayOf(accountDbId.toString(), notificationType)
)
log.d("updatePost account_db_id=$accountDbId, nt=$notificationType, post=$postId,$postTime update_rows=$rows")
dirty = false
clearCache(accountDbId, notificationType)
@ -97,7 +102,7 @@ class NotificationTracking {
private val log = LogCategory("NotificationTracking")
private const val table = "noti_trac"
override val table = "noti_trac"
private const val COL_ID = BaseColumns._ID
@ -179,9 +184,13 @@ class NotificationTracking {
/////////////////////////////////////////////////////////////////////////////////
private val cache = ConcurrentHashMap<Long, ConcurrentHashMap<String, NotificationTracking>>()
private val cache =
ConcurrentHashMap<Long, ConcurrentHashMap<String, NotificationTracking>>()
private fun <K : Any, V : Any> ConcurrentHashMap<K, V>.getOrCreate(key: K, creator: () -> V): V {
private fun <K : Any, V : Any> ConcurrentHashMap<K, V>.getOrCreate(
key: K,
creator: () -> V
): V {
var v = this[key]
if (v == null) v = creator().also { this[key] = it }
return v
@ -193,7 +202,11 @@ class NotificationTracking {
private fun clearCache(accountDbId: Long, notificationType: String): NotificationTracking? =
cache[accountDbId]?.remove(notificationType)
private fun saveCache(accountDbId: Long, notificationType: String, nt: NotificationTracking) {
private fun saveCache(
accountDbId: Long,
notificationType: String,
nt: NotificationTracking
) {
cache.getOrCreate(accountDbId) {
ConcurrentHashMap<String, NotificationTracking>()
}[notificationType] = nt
@ -248,7 +261,8 @@ class NotificationTracking {
log.i("$acct/$notificationType read>show! clip to $show")
val cv = ContentValues()
show.putTo(cv, COL_NID_READ) //変数名とキー名が異なるのに注意
val where_args = arrayOf(accountDbId.toString(), notificationType)
val where_args =
arrayOf(accountDbId.toString(), notificationType)
App1.database.update(table, cv, WHERE_AID, where_args)
}
}

View File

@ -34,7 +34,7 @@ class PostDraft {
private val log = LogCategory("PostDraft")
private const val table = "post_draft"
override val table = "post_draft"
private const val COL_ID = BaseColumns._ID
private const val COL_TIME_SAVE = "time_save"
private const val COL_JSON = "json"

View File

@ -344,7 +344,7 @@ class SavedAccount(
private val log = LogCategory("SavedAccount")
const val table = "access_info"
override val table = "access_info"
val columnList = ColumnMeta.List(table, 0).apply {
createExtra = {
@ -355,7 +355,8 @@ class SavedAccount(
}
}
private val COL_ID = ColumnMeta(columnList, 0, BaseColumns._ID, "INTEGER PRIMARY KEY", primary = true)
private val COL_ID =
ColumnMeta(columnList, 0, BaseColumns._ID, "INTEGER PRIMARY KEY", primary = true)
private val COL_HOST = ColumnMeta(columnList, 0, "h", "text not null")
private val COL_DOMAIN = ColumnMeta(columnList, 56, "d", "text")
private val COL_USER = ColumnMeta(columnList, 0, "u", "text not null")
@ -363,30 +364,48 @@ class SavedAccount(
private val COL_TOKEN = ColumnMeta(columnList, 0, "t", "text not null")
private val COL_VISIBILITY = ColumnMeta(columnList, 0, "visibility", "text")
private val COL_CONFIRM_BOOST = ColumnMeta(columnList, 0, "confirm_boost", ColumnMeta.TS_TRUE)
private val COL_DONT_HIDE_NSFW = ColumnMeta(columnList, 0, "dont_hide_nsfw", ColumnMeta.TS_ZERO)
private val COL_CONFIRM_BOOST =
ColumnMeta(columnList, 0, "confirm_boost", ColumnMeta.TS_TRUE)
private val COL_DONT_HIDE_NSFW =
ColumnMeta(columnList, 0, "dont_hide_nsfw", ColumnMeta.TS_ZERO)
private val COL_NOTIFICATION_MENTION = ColumnMeta(columnList, 2, "notification_mention", ColumnMeta.TS_TRUE)
private val COL_NOTIFICATION_BOOST = ColumnMeta(columnList, 2, "notification_boost", ColumnMeta.TS_TRUE)
private val COL_NOTIFICATION_FAVOURITE = ColumnMeta(columnList, 2, "notification_favourite", ColumnMeta.TS_TRUE)
private val COL_NOTIFICATION_FOLLOW = ColumnMeta(columnList, 2, "notification_follow", ColumnMeta.TS_TRUE)
private val COL_NOTIFICATION_MENTION =
ColumnMeta(columnList, 2, "notification_mention", ColumnMeta.TS_TRUE)
private val COL_NOTIFICATION_BOOST =
ColumnMeta(columnList, 2, "notification_boost", ColumnMeta.TS_TRUE)
private val COL_NOTIFICATION_FAVOURITE =
ColumnMeta(columnList, 2, "notification_favourite", ColumnMeta.TS_TRUE)
private val COL_NOTIFICATION_FOLLOW =
ColumnMeta(columnList, 2, "notification_follow", ColumnMeta.TS_TRUE)
private val COL_NOTIFICATION_FOLLOW_REQUEST =
ColumnMeta(columnList, 44, "notification_follow_request", ColumnMeta.TS_TRUE)
private val COL_NOTIFICATION_REACTION = ColumnMeta(columnList, 33, "notification_reaction", ColumnMeta.TS_TRUE)
private val COL_NOTIFICATION_VOTE = ColumnMeta(columnList, 33, "notification_vote", ColumnMeta.TS_TRUE)
private val COL_NOTIFICATION_POST = ColumnMeta(columnList, 57, "notification_post", ColumnMeta.TS_TRUE)
private val COL_NOTIFICATION_REACTION =
ColumnMeta(columnList, 33, "notification_reaction", ColumnMeta.TS_TRUE)
private val COL_NOTIFICATION_VOTE =
ColumnMeta(columnList, 33, "notification_vote", ColumnMeta.TS_TRUE)
private val COL_NOTIFICATION_POST =
ColumnMeta(columnList, 57, "notification_post", ColumnMeta.TS_TRUE)
private val COL_CONFIRM_FOLLOW = ColumnMeta(columnList, 10, "confirm_follow", ColumnMeta.TS_TRUE)
private val COL_CONFIRM_FOLLOW_LOCKED = ColumnMeta(columnList, 10, "confirm_follow_locked", ColumnMeta.TS_TRUE)
private val COL_CONFIRM_UNFOLLOW = ColumnMeta(columnList, 10, "confirm_unfollow", ColumnMeta.TS_TRUE)
private val COL_CONFIRM_POST = ColumnMeta(columnList, 10, "confirm_post", ColumnMeta.TS_TRUE)
private val COL_CONFIRM_FAVOURITE = ColumnMeta(columnList, 23, "confirm_favourite", ColumnMeta.TS_TRUE)
private val COL_CONFIRM_UNBOOST = ColumnMeta(columnList, 24, "confirm_unboost", ColumnMeta.TS_TRUE)
private val COL_CONFIRM_UNFAVOURITE = ColumnMeta(columnList, 24, "confirm_unfavourite", ColumnMeta.TS_TRUE)
private val COL_CONFIRM_REACTION = ColumnMeta(columnList, 61, "confirm_reaction", ColumnMeta.TS_TRUE)
private val COL_CONFIRM_FOLLOW =
ColumnMeta(columnList, 10, "confirm_follow", ColumnMeta.TS_TRUE)
private val COL_CONFIRM_FOLLOW_LOCKED =
ColumnMeta(columnList, 10, "confirm_follow_locked", ColumnMeta.TS_TRUE)
private val COL_CONFIRM_UNFOLLOW =
ColumnMeta(columnList, 10, "confirm_unfollow", ColumnMeta.TS_TRUE)
private val COL_CONFIRM_POST =
ColumnMeta(columnList, 10, "confirm_post", ColumnMeta.TS_TRUE)
private val COL_CONFIRM_FAVOURITE =
ColumnMeta(columnList, 23, "confirm_favourite", ColumnMeta.TS_TRUE)
private val COL_CONFIRM_UNBOOST =
ColumnMeta(columnList, 24, "confirm_unboost", ColumnMeta.TS_TRUE)
private val COL_CONFIRM_UNFAVOURITE =
ColumnMeta(columnList, 24, "confirm_unfavourite", ColumnMeta.TS_TRUE)
private val COL_CONFIRM_REACTION =
ColumnMeta(columnList, 61, "confirm_reaction", ColumnMeta.TS_TRUE)
// スキーマ13から
val COL_NOTIFICATION_TAG = ColumnMeta(columnList, 13, "notification_server", ColumnMeta.TS_EMPTY)
val COL_NOTIFICATION_TAG =
ColumnMeta(columnList, 13, "notification_server", ColumnMeta.TS_EMPTY)
// スキーマ14から
val COL_REGISTER_KEY = ColumnMeta(columnList, 14, "register_key", ColumnMeta.TS_EMPTY)
@ -396,29 +415,40 @@ class SavedAccount(
private val COL_SOUND_URI = ColumnMeta(columnList, 16, "sound_uri", ColumnMeta.TS_EMPTY)
// スキーマ18から
private val COL_DONT_SHOW_TIMEOUT = ColumnMeta(columnList, 18, "dont_show_timeout", ColumnMeta.TS_ZERO)
private val COL_DONT_SHOW_TIMEOUT =
ColumnMeta(columnList, 18, "dont_show_timeout", ColumnMeta.TS_ZERO)
// スキーマ27から
private val COL_DEFAULT_TEXT = ColumnMeta(columnList, 27, "default_text", ColumnMeta.TS_EMPTY)
private val COL_DEFAULT_TEXT =
ColumnMeta(columnList, 27, "default_text", ColumnMeta.TS_EMPTY)
// スキーマ28から
private val COL_MISSKEY_VERSION = ColumnMeta(columnList, 28, "is_misskey", ColumnMeta.TS_ZERO)
private val COL_MISSKEY_VERSION =
ColumnMeta(columnList, 28, "is_misskey", ColumnMeta.TS_ZERO)
// カラム名がおかしいのは、昔はboolean扱いだったから
// 0: not misskey
// 1: old(v10) misskey
// 11: misskey v11
private val COL_DEFAULT_SENSITIVE = ColumnMeta(columnList, 38, "default_sensitive", ColumnMeta.TS_ZERO)
private val COL_DEFAULT_SENSITIVE =
ColumnMeta(columnList, 38, "default_sensitive", ColumnMeta.TS_ZERO)
private val COL_EXPAND_CW = ColumnMeta(columnList, 38, "expand_cw", ColumnMeta.TS_ZERO)
private val COL_MAX_TOOT_CHARS = ColumnMeta(columnList, 39, "max_toot_chars", ColumnMeta.TS_ZERO)
private val COL_MAX_TOOT_CHARS =
ColumnMeta(columnList, 39, "max_toot_chars", ColumnMeta.TS_ZERO)
private val COL_LAST_NOTIFICATION_ERROR = ColumnMeta(columnList, 42, "last_notification_error", "text")
private val COL_LAST_SUBSCRIPTION_ERROR = ColumnMeta(columnList, 45, "last_subscription_error", "text")
private val COL_LAST_PUSH_ENDPOINT = ColumnMeta(columnList, 46, "last_push_endpoint", "text")
private val COL_LAST_NOTIFICATION_ERROR =
ColumnMeta(columnList, 42, "last_notification_error", "text")
private val COL_LAST_SUBSCRIPTION_ERROR =
ColumnMeta(columnList, 45, "last_subscription_error", "text")
private val COL_LAST_PUSH_ENDPOINT =
ColumnMeta(columnList, 46, "last_push_endpoint", "text")
private val COL_IMAGE_RESIZE = ColumnMeta(columnList, 59, "image_resize", "text default null")
private val COL_IMAGE_MAX_MEGABYTES = ColumnMeta(columnList, 59, "image_max_megabytes", "text default null")
private val COL_MOVIE_MAX_MEGABYTES = ColumnMeta(columnList, 59, "movie_max_megabytes", "text default null")
private val COL_IMAGE_RESIZE =
ColumnMeta(columnList, 59, "image_resize", "text default null")
private val COL_IMAGE_MAX_MEGABYTES =
ColumnMeta(columnList, 59, "image_max_megabytes", "text default null")
private val COL_MOVIE_MAX_MEGABYTES =
ColumnMeta(columnList, 59, "movie_max_megabytes", "text default null")
private val COL_PUSH_POLICY = ColumnMeta(columnList, 60, "push_policy", "text default null")
@ -563,7 +593,10 @@ class SavedAccount(
} catch (ex: Throwable) {
log.trace(ex)
log.e(ex, "loadAccountList failed.")
context.showToast(true, ex.withCaption("(SubwayTooter) broken in-app database?"))
context.showToast(
true,
ex.withCaption("(SubwayTooter) broken in-app database?")
)
}
}
@ -827,7 +860,14 @@ class SavedAccount(
this.loginAccount = ta
ContentValues().apply {
put(COL_ACCOUNT, result.jsonObject.toString())
}.let { App1.database.update(table, it, "$COL_ID=?", arrayOf(db_id.toString())) }
}.let {
App1.database.update(
table,
it,
"$COL_ID=?",
arrayOf(db_id.toString())
)
}
PollingWorker.queueUpdateNotification(context)
}
}

View File

@ -13,7 +13,7 @@ object SubscriptionServerKey : TableCompanion {
private val log = LogCategory("ServerKey")
private const val table = "subscription_server_key2"
override val table = "subscription_server_key2"
private const val COL_ID = BaseColumns._ID
private const val COL_CLIENT_IDENTIFIER = "ci"
private const val COL_SERVER_KEY = "sk"

View File

@ -2,18 +2,16 @@ package jp.juggler.subwaytooter.table
import android.content.ContentValues
import android.database.sqlite.SQLiteDatabase
import java.util.ArrayList
import jp.juggler.subwaytooter.App1
import jp.juggler.util.LogCategory
import jp.juggler.util.TableCompanion
import java.util.*
object TagSet : TableCompanion {
private val log = LogCategory("TagSet")
private const val table = "tag_set"
override val table = "tag_set"
private const val COL_TIME_SAVE = "time_save"
private const val COL_TAG = "tag" // タグ。先頭の#を含まない

View File

@ -48,7 +48,7 @@ class UserRelation {
private val log = LogCategory("UserRelationMisskey")
private const val table = "user_relation_misskey"
override val table = "user_relation_misskey"
val columnList: ColumnMeta.List = ColumnMeta.List(table, 30).apply {
createExtra = {
@ -60,7 +60,8 @@ class UserRelation {
deleteBeforeCreate = true
}
val COL_ID = ColumnMeta(columnList, 0, BaseColumns._ID, "INTEGER PRIMARY KEY", primary = true)
val COL_ID =
ColumnMeta(columnList, 0, BaseColumns._ID, "INTEGER PRIMARY KEY", primary = true)
private val COL_TIME_SAVE = ColumnMeta(columnList, 0, "time_save", "integer not null")
// SavedAccount のDB_ID。 疑似アカウント用のエントリは -2L
@ -73,10 +74,12 @@ class UserRelation {
private val COL_BLOCKING = ColumnMeta(columnList, 0, "blocking", "integer not null")
private val COL_MUTING = ColumnMeta(columnList, 0, "muting", "integer not null")
private val COL_REQUESTED = ColumnMeta(columnList, 0, "requested", "integer not null")
private val COL_FOLLOWING_REBLOGS = ColumnMeta(columnList, 0, "following_reblogs", "integer not null")
private val COL_FOLLOWING_REBLOGS =
ColumnMeta(columnList, 0, "following_reblogs", "integer not null")
private val COL_ENDORSED = ColumnMeta(columnList, 32, "endorsed", "integer default 0")
private val COL_BLOCKED_BY = ColumnMeta(columnList, 34, "blocked_by", "integer default 0")
private val COL_REQUESTED_BY = ColumnMeta(columnList, 35, "requested_by", "integer default 0")
private val COL_REQUESTED_BY =
ColumnMeta(columnList, 35, "requested_by", "integer default 0")
private val COL_NOTE = ColumnMeta(columnList, 55, "note", "text default null")
private val COL_NOTIFYING = ColumnMeta(columnList, 58, "notifying", "integer default 0")

View File

@ -17,6 +17,8 @@ import jp.juggler.apng.ApngFrames
import jp.juggler.subwaytooter.App1
import jp.juggler.util.LogCategory
import jp.juggler.util.TableCompanion
import jp.juggler.util.getBlobOrNull
import jp.juggler.util.getLong
import kotlinx.coroutines.channels.Channel
import java.io.ByteArrayInputStream
import java.lang.ref.WeakReference
@ -55,7 +57,7 @@ class CustomEmojiCache(
companion object : TableCompanion {
const val table = "custom_emoji_cache"
override val table = "custom_emoji_cache"
const val COL_ID = BaseColumns._ID
const val COL_TIME_SAVE = "time_save"
@ -91,9 +93,9 @@ class CustomEmojiCache(
)?.use { cursor ->
if (cursor.moveToNext()) {
DbCache(
id = cursor.getLong(cursor.getColumnIndex(COL_ID)),
timeUsed = cursor.getLong(cursor.getColumnIndex(COL_TIME_USED)),
data = cursor.getBlob(cursor.getColumnIndex(COL_DATA))
id = cursor.getLong(COL_ID),
timeUsed = cursor.getLong(COL_TIME_USED),
data = cursor.getBlobOrNull(COL_DATA)!!
).apply {
if (now - timeUsed >= 5 * 3600000L) {
db.update(

View File

@ -52,9 +52,16 @@ fun Cursor.getStringOrNull(keyIdx: Int) =
fun Cursor.getStringOrNull(key: String) =
getStringOrNull(getColumnIndex(key))
fun Cursor.getBlobOrNull(keyIdx: Int) =
if (isNull(keyIdx)) null else getBlob(keyIdx)
fun Cursor.getBlobOrNull(key: String) =
getBlobOrNull(getColumnIndex(key))
/////////////////////////////////////////////////////////////
interface TableCompanion {
val table: String
fun onDBCreate(db: SQLiteDatabase)
fun onDBUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int)
}

View File

@ -0,0 +1,23 @@
package jp.juggler.subwaytooter
import org.junit.Assert.assertEquals
import org.junit.Test
class TestArrayListSizeBug {
@Test
fun testArrayListSize() {
val list = ArrayList(arrayOf("c", "b", "a").toList())
assertEquals("ArrayList size", 3, list.size)
}
class ArrayListDerived<E>(args: List<E>) : ArrayList<E>(args)
@Test
fun testArrayListDerived() {
val list = ArrayListDerived(arrayOf("c", "b", "a").toList())
assertEquals("ArrayListDerived size", 3, list.size)
// kotlin 1.5.31で(Javaの) size() ではなく getSize() にアクセスしようとして例外を出していた
// kotlin 1.5.30では大丈夫だったが、JetPack Composeは 1.5.31を要求するのだった…。
}
}

View File

@ -8,7 +8,7 @@ buildscript {
ext.lifecycle_version="2.4.0-rc01"
ext.arch_version = "2.1.0"
ext.kotlin_version = '1.5.31'
ext.kotlin_version = '1.5.30'
ext.kotlinx_coroutines_version = '1.5.2'
ext.anko_version='0.10.8'

Binary file not shown.

View File

@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-bin.zip
distributionPath=wrapper/dists
zipStorePath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

298
gradlew vendored
View File

@ -1,74 +1,129 @@
#!/usr/bin/env bash
#!/bin/sh
#
# Copyright © 2015-2021 the original authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
##############################################################################
##
## Gradle start up script for UN*X
##
#
# Gradle start up script for POSIX generated by Gradle.
#
# Important for running:
#
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
# noncompliant, but you have some other compliant shell such as ksh or
# bash, then to run this script, type that shell name before the whole
# command line, like:
#
# ksh Gradle
#
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
# * functions;
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
# * compound commands having a testable exit status, especially «case»;
# * various built-in commands including «command», «set», and «ulimit».
#
# Important for patching:
#
# (2) This script targets any POSIX shell, so it avoids extensions provided
# by Bash, Ksh, etc; in particular arrays are avoided.
#
# The "traditional" practice of packing multiple parameters into a
# space-separated string is a well documented source of bugs and security
# problems, so this is (mostly) avoided, by progressively accumulating
# options in "$@", and eventually passing that to Java.
#
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
# see the in-line comments for details.
#
# There are tweaks for specific operating systems such as AIX, CygWin,
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
#
##############################################################################
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS=""
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
app_path=$0
# Need this for daisy-chained symlinks.
while
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
[ -h "$app_path" ]
do
ls=$( ls -ld "$app_path" )
link=${ls#*' -> '}
case $link in #(
/*) app_path=$link ;; #(
*) app_path=$APP_HOME$link ;;
esac
done
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
APP_BASE_NAME=${0##*/}
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
MAX_FD=maximum
warn ( ) {
warn () {
echo "$*"
}
} >&2
die ( ) {
die () {
echo
echo "$*"
echo
exit 1
}
} >&2
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
nonstop=false
case "$( uname )" in #(
CYGWIN* ) cygwin=true ;; #(
Darwin* ) darwin=true ;; #(
MSYS* | MINGW* ) msys=true ;; #(
NONSTOP* ) nonstop=true ;;
esac
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
JAVACMD=$JAVA_HOME/jre/sh/java
else
JAVACMD="$JAVA_HOME/bin/java"
JAVACMD=$JAVA_HOME/bin/java
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
@ -77,7 +132,7 @@ Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
JAVACMD=java
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
@ -85,76 +140,95 @@ location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin, switch paths to Windows format before running java
if $cygwin ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=$((i+1))
done
case $i in
(0) set -- ;;
(1) set -- "$args0" ;;
(2) set -- "$args0" "$args1" ;;
(3) set -- "$args0" "$args1" "$args2" ;;
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
fi
# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
function splitJvmOpts() {
JVM_OPTS=("$@")
}
eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
# Collect all arguments for the java command, stacking in reverse order:
# * args from the command line
# * the main class name
# * -classpath
# * -D...appname settings
# * --module-path (only if needed)
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
# For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
JAVACMD=$( cygpath --unix "$JAVACMD" )
# Now convert the arguments - kludge to limit ourselves to /bin/sh
for arg do
if
case $arg in #(
-*) false ;; # don't mess with options #(
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
[ -e "$t" ] ;; #(
*) false ;;
esac
then
arg=$( cygpath --path --ignore --mixed "$arg" )
fi
# Roll the args list around exactly as many times as the number of
# args, so each arg winds up back in the position where it started, but
# possibly modified.
#
# NB: a `for` loop captures its iteration list before it begins, so
# changing the positional parameters here affects neither the number of
# iterations, nor the values presented in `arg`.
shift # remove old arg
set -- "$@" "$arg" # push replacement arg
done
fi
# Collect all arguments for the java command;
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
# shell script including quotes and variable substitutions, so put them in
# double quotes to make sure that they get re-expanded; and
# * put everything else in single quotes, so that it's not re-expanded.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
org.gradle.wrapper.GradleWrapperMain \
"$@"
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
#
# In Bash we could simply go:
#
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
# set -- "${ARGS[@]}" "$@"
#
# but POSIX shell has neither arrays nor command substitution, so instead we
# post-process each arg (as a line of input to sed) to backslash-escape any
# character that might be a shell metacharacter, then use eval to reverse
# that process (while maintaining the separation between arguments), and wrap
# the whole thing up as a single "set" statement.
#
# This will of course break if any of these variables contains a newline or
# an unmatched quote.
#
eval "set -- $(
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
xargs -n1 |
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
tr '\n' ' '
)" '"$@"'
exec "$JAVACMD" "$@"

53
gradlew.bat vendored
View File

@ -1,3 +1,19 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@ -8,20 +24,23 @@
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS=
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init
if "%ERRORLEVEL%" == "0" goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
@ -35,7 +54,7 @@ goto fail
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
@ -45,34 +64,14 @@ echo location of your Java installation.
goto fail
:init
@rem Get command-line arguments, handling Windowz variants
if not "%OS%" == "Windows_NT" goto win9xME_args
if "%@eval[2+2]" == "4" goto 4NT_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
goto execute
:4NT_args
@rem Get arguments from the 4NT Shell from JP Software
set CMD_LINE_ARGS=%$
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end
@rem End local scope for the variables with windows NT shell