| /* |
| * Copyright 2010-2018 JetBrains s.r.o. and Kotlin Programming Language contributors. |
| * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. |
| */ |
| |
| @file:Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER") |
| |
| package test.io |
| |
| import org.junit.Test |
| import java.nio.charset.Charset |
| import kotlin.random.Random |
| import kotlin.random.nextInt |
| import kotlin.test.* |
| |
| class ConsoleTest { |
| private val linuxLineSeparator: String = "\n" |
| private val windowsLineSeparator: String = "\r\n" |
| |
| @Test |
| fun shouldReadEmptyLine() { |
| testReadLine("", emptyList()) |
| } |
| |
| @Test |
| fun shouldReadSingleLine() { |
| for (length in 1..3) { |
| val line = buildString { repeat(length) { append('a' + it) } } |
| testReadLine(line, listOf(line)) |
| } |
| } |
| |
| @Test |
| fun trailingEmptyLineIsIgnored() { |
| testReadLine(linuxLineSeparator, listOf("")) |
| testReadLine(windowsLineSeparator, listOf("")) |
| testReadLine("a$linuxLineSeparator", listOf("a")) |
| testReadLine("a$windowsLineSeparator", listOf("a")) |
| } |
| |
| @Test |
| fun shouldReadOneLine() { |
| testReadLine("first", listOf("first")) |
| } |
| |
| @Test |
| fun shouldReadTwoLines() { |
| testReadLine("first${linuxLineSeparator}second", listOf("first", "second")) |
| } |
| |
| @Test |
| fun shouldReadMultipleEmptyLines() { |
| testReadLine( |
| "first${linuxLineSeparator}second${linuxLineSeparator}${linuxLineSeparator}${linuxLineSeparator}", |
| listOf("first", "second", "", "") |
| ) |
| } |
| |
| @Test |
| fun shouldReadAloneCarriageReturn() { |
| val result = readLines("\r", Charsets.UTF_8) |
| assertEquals(listOf("\r"), result) |
| } |
| |
| @Test |
| fun shouldReadConsecutiveEmptyLines() { |
| testReadLine("$linuxLineSeparator$linuxLineSeparator", listOf("", "")) |
| testReadLine("$linuxLineSeparator$windowsLineSeparator", listOf("", "")) |
| testReadLine("$windowsLineSeparator$linuxLineSeparator", listOf("", "")) |
| testReadLine("$windowsLineSeparator$windowsLineSeparator", listOf("", "")) |
| } |
| |
| @Test |
| fun shouldReadWindowsLineSeparator() { |
| testReadLine("first${windowsLineSeparator}second", listOf("first", "second")) |
| } |
| |
| @Test |
| fun shouldReadMultibyteEncodings() { |
| testReadLine("first${linuxLineSeparator}second", listOf("first", "second"), charset = Charsets.UTF_32) |
| } |
| |
| @Test |
| fun shouldReadAllSupportedEncodings() { |
| val lines = listOf( |
| "ONE", "TWICE", "", "0123456", |
| "This is a very long line that will overflow buffers that are allocated in the code of LineReader object", |
| "This line is quite short", |
| "x".repeat(1000), // stress |
| "7", "8", "9" // some short stuff at the end |
| ) |
| // Filter all available charsets that can be encoded |
| val charsets: List<Charset> = Charset.availableCharsets().values.filter { charset -> |
| try { |
| charset.newEncoder() |
| true // take it |
| } catch (e: UnsupportedOperationException) { |
| false // we can only test charset that supports encoding, skip it |
| } |
| } |
| // Run the test |
| for (separator in listOf(linuxLineSeparator, windowsLineSeparator)) { |
| val text = lines.joinToString(separator) |
| for (charset in charsets) { |
| val reference = readLinesReference(text, charset) |
| if (reference != lines) continue // this encoding does not support ASCII chars that we test, skip |
| // Now we can test readLine function |
| val actual = readLines(text, charset) |
| assertEquals(lines, actual, "Comparing with $charset") |
| } |
| } |
| } |
| |
| @Test |
| fun shouldReadAllUnicodeCodePoints() { |
| // Generate lines of ever-increasing length with sample unicode code points to stress all corner-cases in |
| // line lengths and ability to handle different bit-lengths of unicode code points. |
| var cp = 0 |
| val rnd = Random(1) |
| val logFactor = 7 // log of number of code points that are sampled per each bit length of code point |
| fun nextCP(): Int { |
| if (cp == 10 || cp == 13) cp++ // skip line endings |
| if (cp in 0xD800..0xFFFF) cp = 0x10000 // skip surrogates |
| // to make the test run faster don't test all code points, the larger they are, the sparser they are sampled |
| // For each bit length of the code point we randomly sample ~2^logFactor code points for this test |
| val maxStep = cp.coerceAtLeast(1 shl logFactor).takeHighestOneBit() shr logFactor |
| val step = rnd.nextInt(1..maxStep) |
| return cp.also { cp += step } |
| } |
| val lines = ArrayList<String>().apply { |
| var len = 1 |
| while (cp < Character.MAX_CODE_POINT) { |
| add(buildString { |
| repeat(len) { |
| appendCodePoint(nextCP()) |
| if (cp >= Character.MAX_CODE_POINT) return@buildString |
| } |
| }) |
| len++ |
| } |
| } |
| // test all standard unicode encoding that should be able to represent all code points |
| for (separator in listOf(linuxLineSeparator, windowsLineSeparator)) { |
| val text = lines.joinToString(separator) |
| for (charset in listOf(Charsets.UTF_8, Charsets.UTF_16BE, Charsets.UTF_16LE, Charsets.UTF_32BE, Charsets.UTF_32LE)) { |
| testReadLine(text, lines, charset) |
| } |
| } |
| } |
| |
| @Test |
| fun readSurrogatePairs() { |
| val c = "\uD83D\uDC4D" // thumb-up emoji |
| testReadLine("$c$linuxLineSeparator", listOf(c)) |
| testReadLine("e $c$linuxLineSeparator", listOf("e $c")) |
| testReadLine("$c$windowsLineSeparator", listOf(c)) |
| testReadLine("e $c$c", listOf("e $c$c")) |
| testReadLine("e $c$linuxLineSeparator$c", listOf("e $c", c)) |
| } |
| |
| private fun testReadLine(text: String, expected: List<String>, charset: Charset = Charsets.UTF_8) { |
| val actual = readLines(text, charset) |
| assertEquals(expected, actual, "Comparing with $charset") |
| val referenceExpected = readLinesReference(text, charset) |
| assertEquals(referenceExpected, actual, "Comparing to reference readLine") |
| } |
| |
| private fun readLines(text: String, charset: Charset): List<String> { |
| text.byteInputStream(charset).use { stream -> |
| @Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER") |
| return generateSequence { LineReader.readLine(stream, charset) }.toList().also { |
| assertTrue("All bytes should be read") { stream.read() == -1 } |
| } |
| } |
| } |
| |
| private fun readLinesReference(text: String, charset: Charset): List<String> { |
| text.byteInputStream(charset).bufferedReader(charset).use { reader -> |
| return generateSequence { reader.readLine() }.toList() |
| } |
| } |
| } |