blob: bb45321da48f0a86b22cf805c748c7e55b1399b8 [file] [log] [blame]
/*
* Copyright (C) 2018 Square, Inc.
*
* 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
*
* http://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.
*/
package okio
import okio.ByteString.Companion.encodeUtf8
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
import kotlin.test.fail
class CommonOptionsTest {
/** Confirm that options prefers the first-listed option, not the longest or shortest one. */
@Test fun optionOrderTakesPrecedence() {
assertSelect("abcdefg", 0, "abc", "abcdef")
assertSelect("abcdefg", 0, "abcdef", "abc")
}
@Test fun simpleOptionsTrie() {
assertEquals(
utf8Options("hotdog", "hoth", "hot").trieString(),
"""
|hot
| -> 2
| d
| og -> 0
| h -> 1
|""".trimMargin()
)
}
@Test fun realisticOptionsTrie() {
// These are the fields of OkHttpClient in 3.10.
val options = utf8Options(
"dispatcher",
"proxy",
"protocols",
"connectionSpecs",
"interceptors",
"networkInterceptors",
"eventListenerFactory",
"proxySelector", // No index 7 in the trie because 'proxy' is a prefix!
"cookieJar",
"cache",
"internalCache",
"socketFactory",
"sslSocketFactory",
"certificateChainCleaner",
"hostnameVerifier",
"certificatePinner",
"proxyAuthenticator", // No index 16 in the trie because 'proxy' is a prefix!
"authenticator",
"connectionPool",
"dns",
"followSslRedirects",
"followRedirects",
"retryOnConnectionFailure",
"connectTimeout",
"readTimeout",
"writeTimeout",
"pingInterval"
)
assertEquals(
options.trieString(),
"""
|a
| uthenticator -> 17
|c
| a
| che -> 9
| e
| rtificate
| C
| hainCleaner -> 13
| P
| inner -> 15
| o
| n
| nect
| T
| imeout -> 23
| i
| on
| P
| ool -> 18
| S
| pecs -> 3
| o
| kieJar -> 8
|d
| i
| spatcher -> 0
| n
| s -> 19
|e
| ventListenerFactory -> 6
|f
| ollow
| R
| edirects -> 21
| S
| slRedirects -> 20
|h
| ostnameVerifier -> 14
|i
| nter
| c
| eptors -> 4
| n
| alCache -> 10
|n
| etworkInterceptors -> 5
|p
| i
| ngInterval -> 26
| r
| o
| t
| ocols -> 2
| x
| y -> 1
|r
| e
| a
| dTimeout -> 24
| t
| ryOnConnectionFailure -> 22
|s
| o
| cketFactory -> 11
| s
| lSocketFactory -> 12
|w
| riteTimeout -> 25
|""".trimMargin()
)
assertSelect("", -1, options)
assertSelect("a", -1, options)
assertSelect("eventListenerFactor", -1, options)
assertSelect("dnst", 19, options)
assertSelect("proxyproxy", 1, options)
assertSelect("prox", -1, options)
assertSelect("dispatcher", 0, options)
assertSelect("proxy", 1, options)
assertSelect("protocols", 2, options)
assertSelect("connectionSpecs", 3, options)
assertSelect("interceptors", 4, options)
assertSelect("networkInterceptors", 5, options)
assertSelect("eventListenerFactory", 6, options)
assertSelect("proxySelector", 1, options) // 'proxy' is a prefix.
assertSelect("cookieJar", 8, options)
assertSelect("cache", 9, options)
assertSelect("internalCache", 10, options)
assertSelect("socketFactory", 11, options)
assertSelect("sslSocketFactory", 12, options)
assertSelect("certificateChainCleaner", 13, options)
assertSelect("hostnameVerifier", 14, options)
assertSelect("certificatePinner", 15, options)
assertSelect("proxyAuthenticator", 1, options) // 'proxy' is a prefix.
assertSelect("authenticator", 17, options)
assertSelect("connectionPool", 18, options)
assertSelect("dns", 19, options)
assertSelect("followSslRedirects", 20, options)
assertSelect("followRedirects", 21, options)
assertSelect("retryOnConnectionFailure", 22, options)
assertSelect("connectTimeout", 23, options)
assertSelect("readTimeout", 24, options)
assertSelect("writeTimeout", 25, options)
assertSelect("pingInterval", 26, options)
}
@Test fun emptyOptions() {
val options = utf8Options()
assertSelect("", -1, options)
assertSelect("a", -1, options)
assertSelect("abc", -1, options)
}
@Test fun emptyStringInOptionsTrie() {
assertFailsWith<IllegalArgumentException> {
utf8Options("")
}
assertFailsWith<IllegalArgumentException> {
utf8Options("abc", "")
}
}
@Test fun multipleIdenticalValues() {
try {
utf8Options("abc", "abc")
fail()
} catch (expected: IllegalArgumentException) {
assertEquals(expected.message, "duplicate option: [text=abc]")
}
}
@Test fun prefixesAreStripped() {
val options = utf8Options("abcA", "abc", "abcB")
assertEquals(
options.trieString(),
"""
|abc
| -> 1
| A -> 0
|""".trimMargin()
)
assertSelect("abc", 1, options)
assertSelect("abcA", 0, options)
assertSelect("abcB", 1, options)
assertSelect("abcC", 1, options)
assertSelect("ab", -1, options)
}
@Test fun multiplePrefixesAreStripped() {
assertEquals(
utf8Options("a", "ab", "abc", "abcd", "abcde").trieString(),
"""
|a -> 0
|""".trimMargin()
)
assertEquals(
utf8Options("abc", "a", "ab", "abe", "abcd", "abcf").trieString(),
"""
|a
| -> 1
| bc -> 0
|""".trimMargin()
)
assertEquals(
utf8Options("abc", "ab", "a").trieString(),
"""
|a
| -> 2
| b
| -> 1
| c -> 0
|""".trimMargin()
)
assertEquals(
utf8Options("abcd", "abce", "abc", "abcf", "abcg").trieString(),
"""
|abc
| -> 2
| d -> 0
| e -> 1
|""".trimMargin()
)
}
@Test fun scan() {
val options = utf8Options("abc")
assertSelect("abcde", 0, options)
}
@Test fun scanReturnsPrefix() {
val options = utf8Options("abcdefg", "ab")
assertSelect("ab", 1, options)
assertSelect("abcd", 1, options)
assertSelect("abcdefg", 0, options)
assertSelect("abcdefghi", 0, options)
assertSelect("abcdhi", 1, options)
}
@Test fun select() {
val options = utf8Options("a", "b", "c")
assertSelect("a", 0, options)
assertSelect("b", 1, options)
assertSelect("c", 2, options)
assertSelect("d", -1, options)
assertSelect("aa", 0, options)
assertSelect("bb", 1, options)
assertSelect("cc", 2, options)
assertSelect("dd", -1, options)
}
@Test fun selectSelect() {
val options = utf8Options("aa", "ab", "ba", "bb")
assertSelect("a", -1, options)
assertSelect("b", -1, options)
assertSelect("c", -1, options)
assertSelect("aa", 0, options)
assertSelect("ab", 1, options)
assertSelect("ac", -1, options)
assertSelect("ba", 2, options)
assertSelect("bb", 3, options)
assertSelect("bc", -1, options)
assertSelect("ca", -1, options)
assertSelect("cb", -1, options)
assertSelect("cc", -1, options)
}
@Test fun selectScan() {
val options = utf8Options("abcd", "defg")
assertSelect("a", -1, options)
assertSelect("d", -1, options)
assertSelect("h", -1, options)
assertSelect("ab", -1, options)
assertSelect("ae", -1, options)
assertSelect("de", -1, options)
assertSelect("db", -1, options)
assertSelect("hi", -1, options)
assertSelect("abcd", 0, options)
assertSelect("aefg", -1, options)
assertSelect("defg", 1, options)
assertSelect("dbcd", -1, options)
assertSelect("hijk", -1, options)
assertSelect("abcdh", 0, options)
assertSelect("defgh", 1, options)
assertSelect("hijkl", -1, options)
}
@Test fun scanSelect() {
val options = utf8Options("abcd", "abce")
assertSelect("a", -1, options)
assertSelect("f", -1, options)
assertSelect("abc", -1, options)
assertSelect("abf", -1, options)
assertSelect("abcd", 0, options)
assertSelect("abce", 1, options)
assertSelect("abcf", -1, options)
assertSelect("abcdf", 0, options)
assertSelect("abcef", 1, options)
}
@Test fun scanSpansSegments() {
val options = utf8Options("abcd")
assertSelect(bufferWithSegments("a", "bcd"), 0, options)
assertSelect(bufferWithSegments("a", "bcde"), 0, options)
assertSelect(bufferWithSegments("ab", "cd"), 0, options)
assertSelect(bufferWithSegments("ab", "cde"), 0, options)
assertSelect(bufferWithSegments("abc", "d"), 0, options)
assertSelect(bufferWithSegments("abc", "de"), 0, options)
assertSelect(bufferWithSegments("abcd", "e"), 0, options)
assertSelect(bufferWithSegments("a", "bce"), -1, options)
assertSelect(bufferWithSegments("a", "bce"), -1, options)
assertSelect(bufferWithSegments("ab", "ce"), -1, options)
assertSelect(bufferWithSegments("ab", "ce"), -1, options)
assertSelect(bufferWithSegments("abc", "e"), -1, options)
assertSelect(bufferWithSegments("abc", "ef"), -1, options)
assertSelect(bufferWithSegments("abce", "f"), -1, options)
}
@Test fun selectSpansSegments() {
val options = utf8Options("aa", "ab", "ba", "bb")
assertSelect(bufferWithSegments("a", "a"), 0, options)
assertSelect(bufferWithSegments("a", "b"), 1, options)
assertSelect(bufferWithSegments("a", "c"), -1, options)
assertSelect(bufferWithSegments("b", "a"), 2, options)
assertSelect(bufferWithSegments("b", "b"), 3, options)
assertSelect(bufferWithSegments("b", "c"), -1, options)
assertSelect(bufferWithSegments("c", "a"), -1, options)
assertSelect(bufferWithSegments("c", "b"), -1, options)
assertSelect(bufferWithSegments("c", "c"), -1, options)
assertSelect(bufferWithSegments("a", "ad"), 0, options)
assertSelect(bufferWithSegments("a", "bd"), 1, options)
assertSelect(bufferWithSegments("a", "cd"), -1, options)
assertSelect(bufferWithSegments("b", "ad"), 2, options)
assertSelect(bufferWithSegments("b", "bd"), 3, options)
assertSelect(bufferWithSegments("b", "cd"), -1, options)
assertSelect(bufferWithSegments("c", "ad"), -1, options)
assertSelect(bufferWithSegments("c", "bd"), -1, options)
assertSelect(bufferWithSegments("c", "cd"), -1, options)
}
private fun utf8Options(vararg options: String): Options {
return Options.of(*options.map { it.encodeUtf8() }.toTypedArray())
}
private fun assertSelect(data: String, expected: Int, options: Options) {
assertSelect(Buffer().writeUtf8(data), expected, options)
}
private fun assertSelect(data: String, expected: Int, vararg options: String) {
assertSelect(data, expected, utf8Options(*options))
}
private fun assertSelect(data: Buffer, expected: Int, options: Options) {
val initialSize = data.size
val actual = data.select(options)
assertEquals(actual, expected)
if (expected == -1) {
assertEquals(data.size, initialSize)
} else {
assertEquals(data.size + options[expected].size, initialSize)
}
}
private fun Options.trieString(): String {
val result = StringBuilder()
printTrieNode(result, 0)
return result.toString()
}
private fun Options.printTrieNode(out: StringBuilder, offset: Int = 0, indent: String = "") {
if (trie[offset + 1] != -1) {
// Print the prefix.
out.append("$indent-> ${trie[offset + 1]}\n")
}
if (trie[offset] > 0) {
// Print the select.
val selectChoiceCount = trie[offset]
for (i in 0 until selectChoiceCount) {
out.append("$indent${trie[offset + 2 + i].toChar()}")
printTrieResult(out, trie[offset + 2 + selectChoiceCount + i], "$indent ")
}
} else {
// Print the scan.
val scanByteCount = -1 * trie[offset]
out.append(indent)
for (i in 0 until scanByteCount) {
out.append(trie[offset + 2 + i].toChar())
}
printTrieResult(out, trie[offset + 2 + scanByteCount], "$indent${" ".repeat(scanByteCount)}")
}
}
private fun Options.printTrieResult(out: StringBuilder, result: Int, indent: String) {
if (result >= 0) {
out.append(" -> $result\n")
} else {
out.append("\n")
printTrieNode(out, -1 * result, indent)
}
}
}