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 { dependencies {
testImplementation "junit:junit:$junit_version" testImplementation "junit:junit:$junit_version"
androidTestImplementation 'androidx.test:runner:1.3.0' androidTestImplementation 'androidx.test:runner:1.4.0'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
api project(':apng') api project(':apng')
// 'api' // 'api'

View File

@ -41,7 +41,8 @@ android {
] ]
} }
buildFeatures { buildFeatures {
compose true // composeを導入できない
// compose true
} }
composeOptions { composeOptions {
kotlinCompilerExtensionVersion compose_version kotlinCompilerExtensionVersion compose_version
@ -115,6 +116,10 @@ dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs') implementation fileTree(include: ['*.jar'], dir: 'libs')
implementation fileTree(include: ['*.aar'], dir: 'src/main/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', { androidTestImplementation('androidx.test.espresso:espresso-core:3.1.0-alpha4', {
exclude group: 'com.android.support', module: 'support-annotations' exclude group: 'com.android.support', module: 'support-annotations'
}) })
@ -152,7 +157,6 @@ dependencies {
// https://firebase.google.com/support/release-notes/android // https://firebase.google.com/support/release-notes/android
implementation "com.google.firebase:firebase-messaging:22.0.0" 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" implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
testImplementation "org.jetbrains.kotlin:kotlin-test:$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:$okhttpVersion"
implementation "com.squareup.okhttp3:okhttp-urlconnection:$okhttpVersion" implementation "com.squareup.okhttp3:okhttp-urlconnection:$okhttpVersion"
testImplementation "com.squareup.okhttp3:mockwebserver:$okhttpVersion" testImplementation "com.squareup.okhttp3:mockwebserver:$okhttpVersion"
androidTestImplementation "'com.squareup.okhttp3:mockwebserver:$okhttpVersion" androidTestImplementation "com.squareup.okhttp3:mockwebserver:$okhttpVersion"
def glideVersion = '4.12.0' def glideVersion = '4.12.0'
implementation "com.github.bumptech.glide:glide:$glideVersion" implementation "com.github.bumptech.glide:glide:$glideVersion"
@ -241,18 +245,19 @@ dependencies {
// optional - Test helpers for LiveData // optional - Test helpers for LiveData
testImplementation "androidx.arch.core:core-testing:$arch_version" testImplementation "androidx.arch.core:core-testing:$arch_version"
implementation "com.google.accompanist:accompanist-flowlayout:0.20.0"
// composeはkotlin 1.5.31
implementation "androidx.compose.ui:ui:$compose_version" // https://gist.github.com/tateisu/cbda451135d2b5c9b69f7ac4599f9833 1.5.31使
implementation "androidx.compose.material:material:$compose_version" // compose導入を諦める
implementation "androidx.compose.ui:ui-tooling-preview:$compose_version" // implementation "androidx.compose.ui:ui:$compose_version"
implementation "androidx.compose.runtime:runtime-livedata:$compose_version" // implementation "androidx.compose.material:material:$compose_version"
implementation "androidx.compose.material:material-icons-extended:$compose_version" // implementation "androidx.compose.ui:ui-tooling-preview:$compose_version"
androidTestImplementation "androidx.compose.ui:ui-test-junit4:$compose_version" // implementation "androidx.compose.runtime:runtime-livedata:$compose_version"
debugImplementation "androidx.compose.ui:ui-tooling:$compose_version" // implementation "androidx.compose.material:material-icons-extended:$compose_version"
// androidTestImplementation "androidx.compose.ui:ui-test-junit4:$compose_version"
implementation 'androidx.activity:activity-compose:1.4.0-rc01' // 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 package jp.juggler.subwaytooter
import androidx.test.runner.AndroidJUnit4 import androidx.test.runner.AndroidJUnit4
import jp.juggler.util.jsonArray
import org.junit.Test
import org.junit.runner.RunWith
import jp.juggler.util.*
import org.jetbrains.anko.collections.forEachReversedByIndex import org.jetbrains.anko.collections.forEachReversedByIndex
import org.jetbrains.anko.collections.forEachReversedWithIndex import org.jetbrains.anko.collections.forEachReversedWithIndex
import org.junit.Assert.assertEquals 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). * Example local unit test, which will execute on the development machine (host).
@ -17,48 +15,48 @@ import org.junit.Assert.assertEquals
*/ */
@RunWith(AndroidJUnit4::class) @RunWith(AndroidJUnit4::class)
class JsonArrayForEach { class JsonArrayForEach {
@Test @Test
@Throws(Exception::class) @Throws(Exception::class)
fun test(){ fun test() {
val array = jsonArray{ val array = jsonArray {
add("a") add("a")
add("b") add("b")
add( null) add(null)
add( null) add(null)
} }
var count = 0 var count = 0
array.forEach { array.forEach {
println("JsonArray.forEach $it") println("JsonArray.forEach $it")
++count ++count
} }
array.forEachIndexed { i,v-> array.forEachIndexed { i, v ->
println("JsonArray.forEachIndexed $i $v") println("JsonArray.forEachIndexed $i $v")
++count ++count
} }
array.forEachReversedByIndex { array.forEachReversedByIndex {
println("JsonArray.downForEach $it") println("JsonArray.downForEach $it")
++count ++count
} }
array.forEachReversedWithIndex{ i,v-> array.forEachReversedWithIndex { i, v ->
println("JsonArray.downForEachIndexed $i $v") println("JsonArray.downForEachIndexed $i $v")
++count ++count
} }
for( o in array.iterator() ){ for (o in array.iterator()) {
println("JsonArray.iterator $o") println("JsonArray.iterator $o")
++count ++count
} }
for( o in array.asReversed().iterator() ){ for (o in array.asReversed().iterator()) {
println("JsonArray.reverseIterator $o") println("JsonArray.reverseIterator $o")
++count ++count
} }
assertEquals(count,24) assertEquals(count, 24)
} }
} }

View File

@ -10,91 +10,91 @@ import org.junit.runner.RunWith
// Android instrumentation test は run configuration を編集しないと Empty tests とかいうエラーになります // Android instrumentation test は run configuration を編集しないと Empty tests とかいうエラーになります
@RunWith(AndroidJUnit4::class) @RunWith(AndroidJUnit4::class)
class TestMisskeyMentionAndroid { 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は正規表現エンジンが異なるので仕方ない @Test
@Suppress("RegExpRedundantNestedCharacterClass") fun testBracket() {
assertEquals(true, """[[ ]]][ ]""".toRegex().matches(" ] ")) // [] 空の文字セットはパースエラーになる。
// 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 package jp.juggler.subwaytooter
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.runner.AndroidJUnit4
import jp.juggler.subwaytooter.api.TootApiCallback import jp.juggler.subwaytooter.api.TootApiCallback
import jp.juggler.subwaytooter.api.TootApiClient import jp.juggler.subwaytooter.api.TootApiClient
import jp.juggler.subwaytooter.api.entity.Host import jp.juggler.subwaytooter.api.entity.Host

View File

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

View File

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

View File

@ -1,269 +1,268 @@
package jp.juggler.subwaytooter.api.entity package jp.juggler.subwaytooter.api.entity
import androidx.test.runner.AndroidJUnit4
import android.test.mock.MockContext import android.test.mock.MockContext
import androidx.test.runner.AndroidJUnit4
import jp.juggler.subwaytooter.api.TootParser import jp.juggler.subwaytooter.api.TootParser
import jp.juggler.subwaytooter.table.SavedAccount import jp.juggler.subwaytooter.table.SavedAccount
import jp.juggler.util.JsonArray import jp.juggler.util.JsonArray
import jp.juggler.util.JsonObject import jp.juggler.util.JsonObject
import jp.juggler.util.notEmptyOrThrow
import jp.juggler.util.decodeJsonObject import jp.juggler.util.decodeJsonObject
import jp.juggler.util.notEmptyOrThrow
import org.junit.Assert.* import org.junit.Assert.*
import org.junit.runner.RunWith
import org.junit.Test import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class) @RunWith(AndroidJUnit4::class)
class TestEntityUtils { class TestEntityUtils {
class TestEntity(val s : String, val l : Long) : Mappable<String> { class TestEntity(val s: String, val l: Long) : Mappable<String> {
constructor(src : JsonObject) : this( constructor(src: JsonObject) : this(
s = src.stringOrThrow("s"), s = src.stringOrThrow("s"),
l = src.long("l") ?: 0L l = src.long("l") ?: 0L
) )
@Suppress("UNUSED_PARAMETER") @Suppress("UNUSED_PARAMETER")
constructor(parser : TootParser, src : JsonObject) : this( constructor(parser: TootParser, src: JsonObject) : this(
s = src.stringOrThrow("s"), s = src.stringOrThrow("s"),
l = src.long("l") ?: 0L l = src.long("l") ?: 0L
) )
override val mapKey : String override val mapKey: String
get() = s get() = s
} }
@Test @Test
fun testParseItem() { fun testParseItem() {
assertEquals(null, parseItem(::TestEntity, null)) assertEquals(null, parseItem(::TestEntity, null))
run { run {
val src = """{"s":null,"l":"100"}""".decodeJsonObject() val src = """{"s":null,"l":"100"}""".decodeJsonObject()
val item = parseItem(::TestEntity, src) val item = parseItem(::TestEntity, src)
assertNull(item) assertNull(item)
} }
run { run {
val src = """{"s":"","l":"100"}""".decodeJsonObject() val src = """{"s":"","l":"100"}""".decodeJsonObject()
val item = parseItem(::TestEntity, src) val item = parseItem(::TestEntity, src)
assertNull(item) assertNull(item)
} }
run { run {
val src = """{"s":"A","l":null}""".decodeJsonObject() val src = """{"s":"A","l":null}""".decodeJsonObject()
val item = parseItem(::TestEntity, src) val item = parseItem(::TestEntity, src)
assertNotNull(item) assertNotNull(item)
assertEquals(src.optString("s"), item?.s) assertEquals(src.optString("s"), item?.s)
assertEquals(src.optLong("l"), item?.l) assertEquals(src.optLong("l"), item?.l)
} }
run { run {
val src = """{"s":"A","l":""}""".decodeJsonObject() val src = """{"s":"A","l":""}""".decodeJsonObject()
val item = parseItem(::TestEntity, src) val item = parseItem(::TestEntity, src)
assertNotNull(item) assertNotNull(item)
assertEquals(src.optString("s"), item?.s) assertEquals(src.optString("s"), item?.s)
assertEquals(src.optLong("l"), item?.l) assertEquals(src.optLong("l"), item?.l)
} }
run { run {
val src = """{"s":"A","l":100}""".decodeJsonObject() val src = """{"s":"A","l":100}""".decodeJsonObject()
val item = parseItem(::TestEntity, src) val item = parseItem(::TestEntity, src)
assertNotNull(item) assertNotNull(item)
assertEquals(src.optString("s"), item?.s) assertEquals(src.optString("s"), item?.s)
assertEquals(src.optLong("l"), item?.l) assertEquals(src.optLong("l"), item?.l)
} }
run { run {
val src ="""{"s":"A","l":"100"}""".decodeJsonObject() val src = """{"s":"A","l":"100"}""".decodeJsonObject()
val item = parseItem(::TestEntity, src) val item = parseItem(::TestEntity, src)
assertNotNull(item) assertNotNull(item)
assertEquals(src.optString("s"), item?.s) assertEquals(src.optString("s"), item?.s)
assertEquals(src.optLong("l"), item?.l) assertEquals(src.optLong("l"), item?.l)
} }
} }
@Test @Test
fun testParseList() { fun testParseList() {
assertEquals(0, parseList(::TestEntity, null).size) assertEquals(0, parseList(::TestEntity, null).size)
val src = JsonArray() val src = JsonArray()
assertEquals(0, parseList(::TestEntity, src).size) assertEquals(0, parseList(::TestEntity, src).size)
src.add("""{"s":"A","l":"100"}""".decodeJsonObject()) src.add("""{"s":"A","l":"100"}""".decodeJsonObject())
assertEquals(1, parseList(::TestEntity, src).size) assertEquals(1, parseList(::TestEntity, src).size)
src.add("""{"s":"A","l":"100"}""".decodeJsonObject()) src.add("""{"s":"A","l":"100"}""".decodeJsonObject())
assertEquals(2, parseList(::TestEntity, src).size) assertEquals(2, parseList(::TestEntity, src).size)
// error // error
src.add("""{"s":"","l":"100"}""".decodeJsonObject()) src.add("""{"s":"","l":"100"}""".decodeJsonObject())
assertEquals(2, parseList(::TestEntity, src).size) assertEquals(2, parseList(::TestEntity, src).size)
} }
@Test @Test
fun testParseListOrNull() { fun testParseListOrNull() {
assertEquals(null, parseListOrNull(::TestEntity, null)) assertEquals(null, parseListOrNull(::TestEntity, null))
val src = JsonArray() val src = JsonArray()
assertEquals(null, parseListOrNull(::TestEntity, src)) assertEquals(null, parseListOrNull(::TestEntity, src))
src.add("""{"s":"A","l":"100"}""".decodeJsonObject()) src.add("""{"s":"A","l":"100"}""".decodeJsonObject())
assertEquals(1, parseListOrNull(::TestEntity, src)?.size) assertEquals(1, parseListOrNull(::TestEntity, src)?.size)
src.add("""{"s":"A","l":"100"}""".decodeJsonObject()) src.add("""{"s":"A","l":"100"}""".decodeJsonObject())
assertEquals(2, parseListOrNull(::TestEntity, src)?.size) assertEquals(2, parseListOrNull(::TestEntity, src)?.size)
// error // error
src.add("""{"s":"","l":"100"}""".decodeJsonObject()) src.add("""{"s":"","l":"100"}""".decodeJsonObject())
assertEquals(2, parseListOrNull(::TestEntity, src)?.size) assertEquals(2, parseListOrNull(::TestEntity, src)?.size)
} }
@Test @Test
fun testParseMap() { fun testParseMap() {
assertEquals(0, parseMap(::TestEntity, null).size) assertEquals(0, parseMap(::TestEntity, null).size)
val src = JsonArray() val src = JsonArray()
assertEquals(0, parseMap(::TestEntity, src).size) assertEquals(0, parseMap(::TestEntity, src).size)
src.add("""{"s":"A","l":"100"}""".decodeJsonObject()) src.add("""{"s":"A","l":"100"}""".decodeJsonObject())
assertEquals(1, parseMap(::TestEntity, src).size) assertEquals(1, parseMap(::TestEntity, src).size)
src.add("""{"s":"B","l":"100"}""".decodeJsonObject()) src.add("""{"s":"B","l":"100"}""".decodeJsonObject())
assertEquals(2, parseMap(::TestEntity, src).size) assertEquals(2, parseMap(::TestEntity, src).size)
// error // error
src.add("""{"s":"","l":"100"}""".decodeJsonObject()) src.add("""{"s":"","l":"100"}""".decodeJsonObject())
assertEquals(2, parseMap(::TestEntity, src).size) assertEquals(2, parseMap(::TestEntity, src).size)
} }
@Test @Test
fun testParseMapOrNull() { fun testParseMapOrNull() {
assertEquals(null, parseMapOrNull(::TestEntity, null)) assertEquals(null, parseMapOrNull(::TestEntity, null))
val src = JsonArray() val src = JsonArray()
assertEquals(null, parseMapOrNull(::TestEntity, src)) assertEquals(null, parseMapOrNull(::TestEntity, src))
src.add("""{"s":"A","l":"100"}""".decodeJsonObject()) src.add("""{"s":"A","l":"100"}""".decodeJsonObject())
assertEquals(1, parseMapOrNull(::TestEntity, src)?.size) assertEquals(1, parseMapOrNull(::TestEntity, src)?.size)
src.add("""{"s":"B","l":"100"}""".decodeJsonObject()) src.add("""{"s":"B","l":"100"}""".decodeJsonObject())
assertEquals(2, parseMapOrNull(::TestEntity, src)?.size) assertEquals(2, parseMapOrNull(::TestEntity, src)?.size)
// error // error
src.add("""{"s":"","l":"100"}""".decodeJsonObject()) src.add("""{"s":"","l":"100"}""".decodeJsonObject())
assertEquals(2, parseMapOrNull(::TestEntity, src)?.size) assertEquals(2, parseMapOrNull(::TestEntity, src)?.size)
} }
private val parser = TootParser(MockContext(), SavedAccount.na) private val parser = TootParser(MockContext(), SavedAccount.na)
@Test @Test
fun testParseItemWithParser() { fun testParseItemWithParser() {
assertEquals(null, parseItem(::TestEntity, parser, null)) assertEquals(null, parseItem(::TestEntity, parser, null))
run { run {
val src ="""{"s":null,"l":"100"}""".decodeJsonObject() val src = """{"s":null,"l":"100"}""".decodeJsonObject()
val item = parseItem(::TestEntity, parser, src) val item = parseItem(::TestEntity, parser, src)
assertNull(item) assertNull(item)
} }
run { run {
val src = """{"s":"","l":"100"}""".decodeJsonObject() val src = """{"s":"","l":"100"}""".decodeJsonObject()
val item = parseItem(::TestEntity, parser, src) val item = parseItem(::TestEntity, parser, src)
assertNull(item) assertNull(item)
} }
run { run {
val src = """{"s":"A","l":null}""".decodeJsonObject() val src = """{"s":"A","l":null}""".decodeJsonObject()
val item = parseItem(::TestEntity, parser, src) val item = parseItem(::TestEntity, parser, src)
assertNotNull(item) assertNotNull(item)
assertEquals(src.optString("s"), item?.s) assertEquals(src.optString("s"), item?.s)
assertEquals(src.optLong("l"), item?.l) assertEquals(src.optLong("l"), item?.l)
} }
run { run {
val src = """{"s":"A","l":""}""".decodeJsonObject() val src = """{"s":"A","l":""}""".decodeJsonObject()
val item = parseItem(::TestEntity, parser, src) val item = parseItem(::TestEntity, parser, src)
assertNotNull(item) assertNotNull(item)
assertEquals(src.optString("s"), item?.s) assertEquals(src.optString("s"), item?.s)
assertEquals(src.optLong("l"), item?.l) assertEquals(src.optLong("l"), item?.l)
} }
run { run {
val src = """{"s":"A","l":100}""".decodeJsonObject() val src = """{"s":"A","l":100}""".decodeJsonObject()
val item = parseItem(::TestEntity, parser, src) val item = parseItem(::TestEntity, parser, src)
assertNotNull(item) assertNotNull(item)
assertEquals(src.optString("s"), item?.s) assertEquals(src.optString("s"), item?.s)
assertEquals(src.optLong("l"), item?.l) assertEquals(src.optLong("l"), item?.l)
} }
run { run {
val src = """{"s":"A","l":"100"}""".decodeJsonObject() val src = """{"s":"A","l":"100"}""".decodeJsonObject()
val item = parseItem(::TestEntity, parser, src) val item = parseItem(::TestEntity, parser, src)
assertNotNull(item) assertNotNull(item)
assertEquals(src.optString("s"), item?.s) assertEquals(src.optString("s"), item?.s)
assertEquals(src.optLong("l"), item?.l) assertEquals(src.optLong("l"), item?.l)
} }
} }
@Test @Test
fun testParseListWithParser() { fun testParseListWithParser() {
assertEquals(0, parseList(::TestEntity, parser, null).size) assertEquals(0, parseList(::TestEntity, parser, null).size)
val src = JsonArray() val src = JsonArray()
assertEquals(0, parseList(::TestEntity, parser, src).size) assertEquals(0, parseList(::TestEntity, parser, src).size)
src.add("""{"s":"A","l":"100"}""".decodeJsonObject()) src.add("""{"s":"A","l":"100"}""".decodeJsonObject())
assertEquals(1, parseList(::TestEntity, parser, src).size) assertEquals(1, parseList(::TestEntity, parser, src).size)
src.add("""{"s":"A","l":"100"}""".decodeJsonObject()) src.add("""{"s":"A","l":"100"}""".decodeJsonObject())
assertEquals(2, parseList(::TestEntity, parser, src).size) assertEquals(2, parseList(::TestEntity, parser, src).size)
// error // error
src.add("""{"s":"","l":"100"}""".decodeJsonObject()) src.add("""{"s":"","l":"100"}""".decodeJsonObject())
assertEquals(2, parseList(::TestEntity, parser, src).size) assertEquals(2, parseList(::TestEntity, parser, src).size)
} }
@Test @Test
fun testParseListOrNullWithParser() { fun testParseListOrNullWithParser() {
assertEquals(null, parseListOrNull(::TestEntity, parser, null)) assertEquals(null, parseListOrNull(::TestEntity, parser, null))
val src = JsonArray() val src = JsonArray()
assertEquals(null, parseListOrNull(::TestEntity, parser, src)) assertEquals(null, parseListOrNull(::TestEntity, parser, src))
src.add("""{"s":"A","l":"100"}""".decodeJsonObject()) src.add("""{"s":"A","l":"100"}""".decodeJsonObject())
assertEquals(1, parseListOrNull(::TestEntity, parser, src)?.size) assertEquals(1, parseListOrNull(::TestEntity, parser, src)?.size)
src.add("""{"s":"A","l":"100"}""".decodeJsonObject()) src.add("""{"s":"A","l":"100"}""".decodeJsonObject())
assertEquals(2, parseListOrNull(::TestEntity, parser, src)?.size) assertEquals(2, parseListOrNull(::TestEntity, parser, src)?.size)
// error // error
src.add("""{"s":"","l":"100"}""".decodeJsonObject()) src.add("""{"s":"","l":"100"}""".decodeJsonObject())
assertEquals(2, parseListOrNull(::TestEntity, parser, src)?.size) assertEquals(2, parseListOrNull(::TestEntity, parser, src)?.size)
} }
@Test(expected = RuntimeException::class) @Test(expected = RuntimeException::class)
fun testNotEmptyOrThrow1() { fun testNotEmptyOrThrow1() {
println(notEmptyOrThrow("param1", null)) println(notEmptyOrThrow("param1", null))
} }
@Test(expected = RuntimeException::class) @Test(expected = RuntimeException::class)
fun testNotEmptyOrThrow2() { fun testNotEmptyOrThrow2() {
println(notEmptyOrThrow("param1", "")) println(notEmptyOrThrow("param1", ""))
} }
@Test @Test
fun testNotEmptyOrThrow3() { fun testNotEmptyOrThrow3() {
assertEquals("A", notEmptyOrThrow("param1", "A")) assertEquals("A", notEmptyOrThrow("param1", "A"))
} }
@Test(expected = RuntimeException::class) @Test(expected = RuntimeException::class)
fun testNotEmptyOrThrow4() { fun testNotEmptyOrThrow4() {
println("""{"param1":null}""".decodeJsonObject().stringOrThrow("param1")) println("""{"param1":null}""".decodeJsonObject().stringOrThrow("param1"))
} }
@Test(expected = RuntimeException::class) @Test(expected = RuntimeException::class)
fun testNotEmptyOrThrow5() { fun testNotEmptyOrThrow5() {
println("""{"param1":""}""".decodeJsonObject().stringOrThrow("param1")) println("""{"param1":""}""".decodeJsonObject().stringOrThrow("param1"))
} }
@Test @Test
fun testNotEmptyOrThrow6() { fun testNotEmptyOrThrow6() {
assertEquals("A", """{"param1":"A"}""".decodeJsonObject().stringOrThrow("param1")) 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 androidx.test.runner.AndroidJUnit4
import jp.juggler.subwaytooter.util.LinkHelper import jp.juggler.subwaytooter.util.LinkHelper
import org.junit.Assert.assertEquals
import org.junit.Test import org.junit.Test
import org.junit.runner.RunWith import org.junit.runner.RunWith
import org.junit.Assert.assertEquals
@RunWith(AndroidJUnit4::class) @RunWith(AndroidJUnit4::class)
class TestTootAccount { class TestTootAccount {
@Test @Test
@Throws(Exception::class) @Throws(Exception::class)
fun testFindHostFromUrl() { fun testFindHostFromUrl() {
// all null val emptyHost = Host.EMPTY
assertEquals(null, TootAccount.findHostFromUrl(null, null, null).first)
// all null
// find from acct var pair = TootAccount.findHostFromUrl(null, null, null)
assertEquals(null, TootAccount.findHostFromUrl("", null, null).first) assertEquals(null, pair.first)
assertEquals(null, TootAccount.findHostFromUrl("user", null, null).first)
assertEquals(null, TootAccount.findHostFromUrl("user@", null, null).first) // find from acct
assertEquals("host", TootAccount.findHostFromUrl("user@HOST", null, null).first) pair = TootAccount.findHostFromUrl("", null, null)
assertEquals(Host.UNKNOWN, pair.first)
// find from accessHost assertEquals(null, TootAccount.findHostFromUrl("user", null, null).first)
assertEquals( assertEquals(emptyHost, TootAccount.findHostFromUrl("user@", null, null).first)
"", assertEquals(
TootAccount.findHostFromUrl(null, LinkHelper.create(Host.parse("")), null).first "host",
) TootAccount.findHostFromUrl("user@HOST", null, null)
assertEquals( .first?.ascii
"any string is allowed", )
TootAccount.findHostFromUrl(
null, // find from accessHost
LinkHelper.create(Host.parse("any string is allowed")),
null assertEquals(
).first emptyHost,
) TootAccount.findHostFromUrl(null, LinkHelper.create(emptyHost), null).first
)
// find from url val testHost = Host.parse("any string is allowed")
assertEquals(null, TootAccount.findHostFromUrl(null, null, "").first) assertEquals(
assertEquals(null, TootAccount.findHostFromUrl(null, null, "xxx").first) testHost,
assertEquals( TootAccount.findHostFromUrl(
null, null,
TootAccount.findHostFromUrl(null, null, "mailto:tateisu@gmail.com").first LinkHelper.create(testHost),
) null
assertEquals( ).first
"mastodon.juggler.jp", )
TootAccount.findHostFromUrl(null, null, "https://MASTODON.juggler.jp/@tateisu").first
) // find from url
assertEquals( assertEquals(null, TootAccount.findHostFromUrl(null, null, "").first)
"mastodon.juggler.jp", assertEquals(null, TootAccount.findHostFromUrl(null, null, "xxx").first)
TootAccount.findHostFromUrl(null, null, "https://mastodon.juggler.jp/").first assertEquals(
) null,
assertEquals( TootAccount.findHostFromUrl(null, null, "mailto:tateisu@gmail.com").first
"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/@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 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.Test
import org.junit.runner.RunWith import org.junit.runner.RunWith
@Suppress("MemberVisibilityCanPrivate") @Suppress("MemberVisibilityCanPrivate")
@RunWith(AndroidJUnit4::class) @RunWith(AndroidJUnit4::class)
class TestBucketList { class TestBucketList {
@Test fun test1(){ @Test
val list = BucketList<String>(bucketCapacity=2) fun test1() {
assertEquals(true,list.isEmpty()) val list = BucketList<String>(bucketCapacity = 2)
list.addAll( listOf("A","B","C")) assertEquals(true, list.isEmpty())
list.addAll( 3, listOf("a","b","c")) list.addAll(listOf("A", "B", "C"))
list.addAll( 1, listOf("a","b","c")) list.addAll(3, listOf("a", "b", "c"))
list.removeAt(7) list.addAll(1, listOf("a", "b", "c"))
assertEquals(8,list.size) list.removeAt(7)
listOf("A","a","b","c","B","C","a","c").forEachIndexed { i,v-> assertEquals(8, list.size)
assertEquals( v,list[i]) 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 package jp.juggler.subwaytooter.util
import androidx.test.InstrumentationRegistry import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.runner.AndroidJUnit4 import androidx.test.runner.AndroidJUnit4
import jp.juggler.subwaytooter.api.entity.Host import jp.juggler.subwaytooter.api.entity.Host
import jp.juggler.util.neatSpaces import jp.juggler.util.neatSpaces
@ -12,46 +12,47 @@ import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class) @RunWith(AndroidJUnit4::class)
class TestHtmlDecoder { class TestHtmlDecoder {
class SpanMeta( class SpanMeta(
val span:Any, val span: Any,
val start:Int, val start: Int,
val end:Int, val end: Int,
val flags:Int, val flags: Int,
val text :String val text: String
){ ) {
override fun toString() = "[$start..$end) $flags ${span.javaClass.simpleName} $text" override fun toString() = "[$start..$end) $flags ${span.javaClass.simpleName} $text"
} }
@Test fun test1(){ @Test
// Context of the app under test. fun test1() {
val appContext = InstrumentationRegistry.getTargetContext() // Context of the app under test.
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
val options = DecodeOptions(appContext,LinkHelper.create(Host.parse("instance.test")))
val options = DecodeOptions(appContext, LinkHelper.create(Host.parse("instance.test")))
val html = """
val html = """
日本語で楽しめるMastodonサーバを提供しています 日本語で楽しめるMastodonサーバを提供しています
<a href="https://mastodon.juggler.jp/terms">利用規約</a>を読んでからサインアップしてください <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://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> <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 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() """.trimIndent()
val text = options.decodeHTML( html ).neatSpaces() val text = options.decodeHTML(html).neatSpaces()
val spanArray = text.getSpans(0,text.length,Any::class.java).map { val spanArray = text.getSpans(0, text.length, Any::class.java).map {
val start = text.getSpanStart(it) val start = text.getSpanStart(it)
val end = text.getSpanEnd(it) val end = text.getSpanEnd(it)
SpanMeta( SpanMeta(
span = it, span = it,
start = start, start = start,
end = end, end = end,
flags = text.getSpanFlags(it), flags = text.getSpanFlags(it),
text = text.subSequence(start, end).toString() text = text.subSequence(start, end).toString()
) )
} }
spanArray.forEach{ println(it)} spanArray.forEach { println(it) }
assertEquals(3,spanArray.size) assertEquals(5, spanArray.size)
assertEquals( "利用規約", spanArray[0].text) assertEquals("利用規約", spanArray[0].text)
assertEquals( "<img/>", spanArray[1].text) assertEquals("Androidアプリ", spanArray[1].text)
assertEquals( "<img/>", spanArray[2].text) assertEquals("<img Androidアプリ />", spanArray[2].text)
} }
} }

View File

@ -131,9 +131,9 @@ class App1 : Application() {
// 2021/5/11 59=>60 SavedAccountテーブルに項目追加 // 2021/5/11 59=>60 SavedAccountテーブルに項目追加
// 2021/5/23 60=>61 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, LogData,
SavedAccount, SavedAccount,
ClientInfo, ClientInfo,

View File

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

View File

@ -75,7 +75,7 @@ class AcctColor {
private val log = LogCategory("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 { val columnList: ColumnMeta.List = ColumnMeta.List(table, 9).apply {
// not used, but must be defined // not used, but must be defined

View File

@ -16,7 +16,7 @@ object AcctSet : TableCompanion {
private val log = LogCategory("AcctSet") 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 { val columnList: ColumnMeta.List = ColumnMeta.List(table, 7).apply {
ColumnMeta(this, 0, BaseColumns._ID, "INTEGER PRIMARY KEY", primary = true) ColumnMeta(this, 0, BaseColumns._ID, "INTEGER PRIMARY KEY", primary = true)

View File

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

View File

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

View File

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

View File

@ -19,7 +19,7 @@ class HighlightWord {
const val SOUND_TYPE_DEFAULT = 1 const val SOUND_TYPE_DEFAULT = 1
const val SOUND_TYPE_CUSTOM = 2 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_ID = BaseColumns._ID
const val COL_NAME = "name" const val COL_NAME = "name"
private const val COL_TIME_SAVE = "time_save" private const val COL_TIME_SAVE = "time_save"

View File

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

View File

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

View File

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

View File

@ -13,7 +13,7 @@ object MutedWord : TableCompanion {
private val log = LogCategory("MutedWord") private val log = LogCategory("MutedWord")
const val table = "word_mute" override val table = "word_mute"
const val COL_ID = "_id" const val COL_ID = "_id"
const val COL_NAME = "name" const val COL_NAME = "name"
private const val COL_TIME_SAVE = "time_save" 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 val log = LogCategory("NotificationCache")
private const val table = "noti_cache" override val table = "noti_cache"
private const val COL_ID = BaseColumns._ID private const val COL_ID = BaseColumns._ID

View File

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

View File

@ -34,7 +34,7 @@ class PostDraft {
private val log = LogCategory("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_ID = BaseColumns._ID
private const val COL_TIME_SAVE = "time_save" private const val COL_TIME_SAVE = "time_save"
private const val COL_JSON = "json" private const val COL_JSON = "json"

View File

@ -344,7 +344,7 @@ class SavedAccount(
private val log = LogCategory("SavedAccount") private val log = LogCategory("SavedAccount")
const val table = "access_info" override val table = "access_info"
val columnList = ColumnMeta.List(table, 0).apply { val columnList = ColumnMeta.List(table, 0).apply {
createExtra = { 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_HOST = ColumnMeta(columnList, 0, "h", "text not null")
private val COL_DOMAIN = ColumnMeta(columnList, 56, "d", "text") private val COL_DOMAIN = ColumnMeta(columnList, 56, "d", "text")
private val COL_USER = ColumnMeta(columnList, 0, "u", "text not null") 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_TOKEN = ColumnMeta(columnList, 0, "t", "text not null")
private val COL_VISIBILITY = ColumnMeta(columnList, 0, "visibility", "text") 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_CONFIRM_BOOST =
private val COL_DONT_HIDE_NSFW = ColumnMeta(columnList, 0, "dont_hide_nsfw", ColumnMeta.TS_ZERO) 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_MENTION =
private val COL_NOTIFICATION_BOOST = ColumnMeta(columnList, 2, "notification_boost", ColumnMeta.TS_TRUE) ColumnMeta(columnList, 2, "notification_mention", ColumnMeta.TS_TRUE)
private val COL_NOTIFICATION_FAVOURITE = ColumnMeta(columnList, 2, "notification_favourite", ColumnMeta.TS_TRUE) private val COL_NOTIFICATION_BOOST =
private val COL_NOTIFICATION_FOLLOW = ColumnMeta(columnList, 2, "notification_follow", ColumnMeta.TS_TRUE) 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 = private val COL_NOTIFICATION_FOLLOW_REQUEST =
ColumnMeta(columnList, 44, "notification_follow_request", ColumnMeta.TS_TRUE) 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_REACTION =
private val COL_NOTIFICATION_VOTE = ColumnMeta(columnList, 33, "notification_vote", ColumnMeta.TS_TRUE) ColumnMeta(columnList, 33, "notification_reaction", ColumnMeta.TS_TRUE)
private val COL_NOTIFICATION_POST = ColumnMeta(columnList, 57, "notification_post", 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 =
private val COL_CONFIRM_FOLLOW_LOCKED = ColumnMeta(columnList, 10, "confirm_follow_locked", ColumnMeta.TS_TRUE) ColumnMeta(columnList, 10, "confirm_follow", ColumnMeta.TS_TRUE)
private val COL_CONFIRM_UNFOLLOW = ColumnMeta(columnList, 10, "confirm_unfollow", ColumnMeta.TS_TRUE) private val COL_CONFIRM_FOLLOW_LOCKED =
private val COL_CONFIRM_POST = ColumnMeta(columnList, 10, "confirm_post", ColumnMeta.TS_TRUE) ColumnMeta(columnList, 10, "confirm_follow_locked", ColumnMeta.TS_TRUE)
private val COL_CONFIRM_FAVOURITE = ColumnMeta(columnList, 23, "confirm_favourite", ColumnMeta.TS_TRUE) private val COL_CONFIRM_UNFOLLOW =
private val COL_CONFIRM_UNBOOST = ColumnMeta(columnList, 24, "confirm_unboost", ColumnMeta.TS_TRUE) ColumnMeta(columnList, 10, "confirm_unfollow", ColumnMeta.TS_TRUE)
private val COL_CONFIRM_UNFAVOURITE = ColumnMeta(columnList, 24, "confirm_unfavourite", ColumnMeta.TS_TRUE) private val COL_CONFIRM_POST =
private val COL_CONFIRM_REACTION = ColumnMeta(columnList, 61, "confirm_reaction", ColumnMeta.TS_TRUE) 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から // スキーマ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から // スキーマ14から
val COL_REGISTER_KEY = ColumnMeta(columnList, 14, "register_key", ColumnMeta.TS_EMPTY) 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) private val COL_SOUND_URI = ColumnMeta(columnList, 16, "sound_uri", ColumnMeta.TS_EMPTY)
// スキーマ18から // スキーマ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から // スキーマ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から // スキーマ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扱いだったから // カラム名がおかしいのは、昔はboolean扱いだったから
// 0: not misskey // 0: not misskey
// 1: old(v10) misskey // 1: old(v10) misskey
// 11: misskey v11 // 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_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_NOTIFICATION_ERROR =
private val COL_LAST_SUBSCRIPTION_ERROR = ColumnMeta(columnList, 45, "last_subscription_error", "text") ColumnMeta(columnList, 42, "last_notification_error", "text")
private val COL_LAST_PUSH_ENDPOINT = ColumnMeta(columnList, 46, "last_push_endpoint", "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_RESIZE =
private val COL_IMAGE_MAX_MEGABYTES = ColumnMeta(columnList, 59, "image_max_megabytes", "text default null") ColumnMeta(columnList, 59, "image_resize", "text default null")
private val COL_MOVIE_MAX_MEGABYTES = ColumnMeta(columnList, 59, "movie_max_megabytes", "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") private val COL_PUSH_POLICY = ColumnMeta(columnList, 60, "push_policy", "text default null")
@ -563,7 +593,10 @@ class SavedAccount(
} catch (ex: Throwable) { } catch (ex: Throwable) {
log.trace(ex) log.trace(ex)
log.e(ex, "loadAccountList failed.") 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 this.loginAccount = ta
ContentValues().apply { ContentValues().apply {
put(COL_ACCOUNT, result.jsonObject.toString()) 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) PollingWorker.queueUpdateNotification(context)
} }
} }

View File

@ -13,7 +13,7 @@ object SubscriptionServerKey : TableCompanion {
private val log = LogCategory("ServerKey") 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_ID = BaseColumns._ID
private const val COL_CLIENT_IDENTIFIER = "ci" private const val COL_CLIENT_IDENTIFIER = "ci"
private const val COL_SERVER_KEY = "sk" private const val COL_SERVER_KEY = "sk"

View File

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

View File

@ -48,7 +48,7 @@ class UserRelation {
private val log = LogCategory("UserRelationMisskey") 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 { val columnList: ColumnMeta.List = ColumnMeta.List(table, 30).apply {
createExtra = { createExtra = {
@ -60,7 +60,8 @@ class UserRelation {
deleteBeforeCreate = true 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") private val COL_TIME_SAVE = ColumnMeta(columnList, 0, "time_save", "integer not null")
// SavedAccount のDB_ID。 疑似アカウント用のエントリは -2L // SavedAccount のDB_ID。 疑似アカウント用のエントリは -2L
@ -73,10 +74,12 @@ class UserRelation {
private val COL_BLOCKING = ColumnMeta(columnList, 0, "blocking", "integer not null") 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_MUTING = ColumnMeta(columnList, 0, "muting", "integer not null")
private val COL_REQUESTED = ColumnMeta(columnList, 0, "requested", "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_ENDORSED = ColumnMeta(columnList, 32, "endorsed", "integer default 0")
private val COL_BLOCKED_BY = ColumnMeta(columnList, 34, "blocked_by", "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_NOTE = ColumnMeta(columnList, 55, "note", "text default null")
private val COL_NOTIFYING = ColumnMeta(columnList, 58, "notifying", "integer default 0") 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.subwaytooter.App1
import jp.juggler.util.LogCategory import jp.juggler.util.LogCategory
import jp.juggler.util.TableCompanion import jp.juggler.util.TableCompanion
import jp.juggler.util.getBlobOrNull
import jp.juggler.util.getLong
import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.channels.Channel
import java.io.ByteArrayInputStream import java.io.ByteArrayInputStream
import java.lang.ref.WeakReference import java.lang.ref.WeakReference
@ -55,7 +57,7 @@ class CustomEmojiCache(
companion object : TableCompanion { companion object : TableCompanion {
const val table = "custom_emoji_cache" override val table = "custom_emoji_cache"
const val COL_ID = BaseColumns._ID const val COL_ID = BaseColumns._ID
const val COL_TIME_SAVE = "time_save" const val COL_TIME_SAVE = "time_save"
@ -91,9 +93,9 @@ class CustomEmojiCache(
)?.use { cursor -> )?.use { cursor ->
if (cursor.moveToNext()) { if (cursor.moveToNext()) {
DbCache( DbCache(
id = cursor.getLong(cursor.getColumnIndex(COL_ID)), id = cursor.getLong(COL_ID),
timeUsed = cursor.getLong(cursor.getColumnIndex(COL_TIME_USED)), timeUsed = cursor.getLong(COL_TIME_USED),
data = cursor.getBlob(cursor.getColumnIndex(COL_DATA)) data = cursor.getBlobOrNull(COL_DATA)!!
).apply { ).apply {
if (now - timeUsed >= 5 * 3600000L) { if (now - timeUsed >= 5 * 3600000L) {
db.update( db.update(

View File

@ -52,9 +52,16 @@ fun Cursor.getStringOrNull(keyIdx: Int) =
fun Cursor.getStringOrNull(key: String) = fun Cursor.getStringOrNull(key: String) =
getStringOrNull(getColumnIndex(key)) 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 { interface TableCompanion {
val table: String
fun onDBCreate(db: SQLiteDatabase) fun onDBCreate(db: SQLiteDatabase)
fun onDBUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) 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.lifecycle_version="2.4.0-rc01"
ext.arch_version = "2.1.0" 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.kotlinx_coroutines_version = '1.5.2'
ext.anko_version='0.10.8' ext.anko_version='0.10.8'

Binary file not shown.

View File

@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-bin.zip distributionPath=wrapper/dists
distributionPath=wrapper/dists distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip
zipStorePath=wrapper/dists zipStoreBase=GRADLE_USER_HOME
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. # Attempt to set APP_HOME
DEFAULT_JVM_OPTS=""
# 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_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. # Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum" MAX_FD=maximum
warn ( ) { warn () {
echo "$*" echo "$*"
} } >&2
die ( ) { die () {
echo echo
echo "$*" echo "$*"
echo echo
exit 1 exit 1
} } >&2
# OS specific support (must be 'true' or 'false'). # OS specific support (must be 'true' or 'false').
cygwin=false cygwin=false
msys=false msys=false
darwin=false darwin=false
case "`uname`" in nonstop=false
CYGWIN* ) case "$( uname )" in #(
cygwin=true CYGWIN* ) cygwin=true ;; #(
;; Darwin* ) darwin=true ;; #(
Darwin* ) MSYS* | MINGW* ) msys=true ;; #(
darwin=true NONSTOP* ) nonstop=true ;;
;;
MINGW* )
msys=true
;;
esac 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 CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM. # Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables # IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java" JAVACMD=$JAVA_HOME/jre/sh/java
else else
JAVACMD="$JAVA_HOME/bin/java" JAVACMD=$JAVA_HOME/bin/java
fi fi
if [ ! -x "$JAVACMD" ] ; then if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 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." location of your Java installation."
fi fi
else 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. 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 Please set the JAVA_HOME variable in your environment to match the
@ -85,76 +140,95 @@ location of your Java installation."
fi fi
# Increase the maximum file descriptors if we can. # Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
MAX_FD_LIMIT=`ulimit -H -n` case $MAX_FD in #(
if [ $? -eq 0 ] ; then max*)
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then MAX_FD=$( ulimit -H -n ) ||
MAX_FD="$MAX_FD_LIMIT" warn "Could not query maximum file descriptor limit"
fi esac
ulimit -n $MAX_FD case $MAX_FD in #(
if [ $? -ne 0 ] ; then '' | soft) :;; #(
warn "Could not set maximum file descriptor limit: $MAX_FD" *)
fi ulimit -n "$MAX_FD" ||
else warn "Could not set maximum file descriptor limit to $MAX_FD"
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" ;;
esac esac
fi fi
# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules # Collect all arguments for the java command, stacking in reverse order:
function splitJvmOpts() { # * args from the command line
JVM_OPTS=("$@") # * the main class name
} # * -classpath
eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS # * -D...appname settings
JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" # * --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 @if "%DEBUG%" == "" @echo off
@rem ########################################################################## @rem ##########################################################################
@rem @rem
@ -8,20 +24,23 @@
@rem Set local scope for the variables with windows NT shell @rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal 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 set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=. if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0 set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME% 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 @rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1 %JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init if "%ERRORLEVEL%" == "0" goto execute
echo. echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 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_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init if exist "%JAVA_EXE%" goto execute
echo. echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
@ -45,34 +64,14 @@ echo location of your Java installation.
goto fail 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 :execute
@rem Setup the command line @rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle @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 :end
@rem End local scope for the variables with windows NT shell @rem End local scope for the variables with windows NT shell