mirror of
https://github.com/mamoe/mirai.git
synced 2025-04-09 02:10:10 +08:00
Implement MiraiFile on Windows
This commit is contained in:
parent
58409f602b
commit
3d57f4cb68
mirai-core-utils/src
commonMain/kotlin
mingwX64Main/kotlin
mingwX64Test/kotlin
nativeTest/kotlin
unixTest/kotlin
@ -76,7 +76,7 @@ public fun MiraiFile.writeText(text: String) {
|
||||
}
|
||||
|
||||
public fun MiraiFile.readText(): String {
|
||||
return input().use { it.readText() }
|
||||
return input().use { it.readAllText() }
|
||||
}
|
||||
|
||||
public fun MiraiFile.readBytes(): ByteArray {
|
||||
|
@ -167,6 +167,8 @@ public fun Input._readTLVMap(
|
||||
return map
|
||||
}
|
||||
|
||||
public fun Input.readAllText(): String = Charsets.UTF_8.newDecoder().decode(this)
|
||||
|
||||
public inline fun Input.readString(length: Int, charset: Charset = Charsets.UTF_8): String =
|
||||
String(this.readBytes(length), charset = charset) // stdlib
|
||||
|
||||
|
@ -9,13 +9,46 @@
|
||||
|
||||
package net.mamoe.mirai.utils
|
||||
|
||||
import io.ktor.utils.io.bits.*
|
||||
import io.ktor.utils.io.core.*
|
||||
import io.ktor.utils.io.errors.*
|
||||
import kotlinx.cinterop.*
|
||||
import platform.posix.PATH_MAX
|
||||
import platform.posix.fopen
|
||||
import platform.posix.getcwd
|
||||
import platform.posix.*
|
||||
import platform.windows.*
|
||||
|
||||
private fun getFullPathName(path: String): String = memScoped {
|
||||
try {
|
||||
println("getFullPathName")
|
||||
ShortArray(MAX_PATH).usePinned { pin ->
|
||||
val len = GetFullPathNameW(path, MAX_PATH, pin.addressOf(0).reinterpret(), null).toInt()
|
||||
if (len != 0) {
|
||||
return pin.get().toKStringFromUtf16(len)
|
||||
} else {
|
||||
when (val errno = errno) {
|
||||
ENOTDIR -> return@memScoped path
|
||||
EACCES -> return@memScoped path // permission denied
|
||||
ENOENT -> return@memScoped path // no such file
|
||||
else -> throw IllegalArgumentException(
|
||||
"Invalid path($errno): $path",
|
||||
cause = PosixException.forErrno(posixFunctionName = "GetFullPathNameW()")
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
println("getFullPathName finished")
|
||||
}
|
||||
}
|
||||
|
||||
private fun ShortArray.toKStringFromUtf16(len: Int): String {
|
||||
val chars = CharArray(len)
|
||||
var index = 0
|
||||
while (index < len) {
|
||||
chars[index] = this[index].toInt().toChar()
|
||||
++index
|
||||
}
|
||||
return chars.concatToString()
|
||||
}
|
||||
|
||||
internal actual class MiraiFileImpl actual constructor(
|
||||
// canonical
|
||||
@ -40,14 +73,16 @@ internal actual class MiraiFileImpl actual constructor(
|
||||
}
|
||||
}
|
||||
|
||||
override val absolutePath: String = kotlin.run {
|
||||
val result = ROOT_REGEX.matchEntire(path) ?: return@run path.dropLastWhile { it.isSeparator() }
|
||||
return@run result.groups.first()!!.value
|
||||
override val absolutePath: String by lazy {
|
||||
val result = ROOT_REGEX.matchEntire(this.path)
|
||||
?: return@lazy getFullPathName(this.path).removeSuffix(SEPARATOR.toString())
|
||||
return@lazy result.groups.first()!!.value
|
||||
}
|
||||
|
||||
private fun Char.isSeparator() = this == '/' || this == '\\'
|
||||
|
||||
override val parent: MiraiFile? by lazy {
|
||||
if (ROOT_REGEX.matchEntire(this.path) != null) return@lazy null
|
||||
val absolute = absolutePath
|
||||
val p = absolute.substringBeforeLast(SEPARATOR, "")
|
||||
if (p.isEmpty()) {
|
||||
@ -66,41 +101,63 @@ internal actual class MiraiFileImpl actual constructor(
|
||||
|
||||
override val name: String
|
||||
get() = if (absolutePath.matches(ROOT_REGEX)) absolutePath
|
||||
else absolutePath.substringAfterLast('/')
|
||||
else absolutePath.substringAfterLast(SEPARATOR)
|
||||
|
||||
init {
|
||||
checkName(absolutePath.substringAfterLast('/')) // do not check drive letter
|
||||
checkName(absolutePath.substringAfterLast(SEPARATOR)) // do not check drive letter
|
||||
}
|
||||
|
||||
private fun checkName(name: String) {
|
||||
name.substringAfterLast('/').forEach { c ->
|
||||
name.substringAfterLast(SEPARATOR).forEach { c ->
|
||||
if (c in """\/:?*"><|""") {
|
||||
throw IllegalArgumentException("'${name}' contains illegal character '$c'.")
|
||||
}
|
||||
}
|
||||
|
||||
memScoped {
|
||||
val b = alloc<WINBOOLVar>()
|
||||
CheckNameLegalDOS8Dot3A(absolutePath, nullPtr(), 0, nullPtr(), b.ptr)
|
||||
if (b.value != 1) {
|
||||
throw IllegalArgumentException("'${name}' contains illegal character.")
|
||||
}
|
||||
}
|
||||
// memScoped {
|
||||
// val b = alloc<WINBOOLVar>()
|
||||
// CheckNameLegalDOS8Dot3A(absolutePath, nullPtr(), 0, nullPtr(), b.ptr)
|
||||
// if (b.value != 1) {
|
||||
// throw IllegalArgumentException("'${name}' contains illegal character.")
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
override val length: Long
|
||||
get() = useStat { it.st_size.convert() } ?: 0
|
||||
// memScoped {
|
||||
// val handle = CreateFileW(
|
||||
// absolutePath,
|
||||
// GENERIC_READ,
|
||||
// FILE_SHARE_READ,
|
||||
// null,
|
||||
// OPEN_EXISTING,
|
||||
// FILE_ATTRIBUTE_NORMAL,
|
||||
// null
|
||||
// ) ?: return@memScoped 0
|
||||
// val length = alloc<DWORDVar>()
|
||||
// if (GetFileSize(handle, length.ptr) == INVALID_FILE_SIZE) {
|
||||
// if (GetLastError() == NO_ERROR.toUInt()) {
|
||||
// return INVALID_FILE_SIZE.convert()
|
||||
// }
|
||||
// throw PosixException.forErrno(posixFunctionName = "GetFileSize()").wrapIO()
|
||||
// }
|
||||
// if (CloseHandle(handle) == FALSE) {
|
||||
// throw PosixException.forErrno(posixFunctionName = "CloseHandle()").wrapIO()
|
||||
// }
|
||||
// length.value.convert()
|
||||
// }
|
||||
|
||||
|
||||
override val isFile: Boolean
|
||||
get() = getFileAttributes() flag FILE_ATTRIBUTE_NORMAL
|
||||
get() = useStat { it.st_mode.convert<UInt>() flag S_IFREG } ?: false
|
||||
|
||||
override val isDirectory: Boolean
|
||||
get() = getFileAttributes() flag FILE_ATTRIBUTE_DIRECTORY
|
||||
get() = useStat { it.st_mode.convert<UInt>() flag S_IFDIR } ?: false
|
||||
|
||||
override fun exists(): Boolean = getFileAttributes() != INVALID_FILE_ATTRIBUTES
|
||||
|
||||
private fun getFileAttributes(): DWORD = memScoped { GetFileAttributesA(absolutePath) }
|
||||
private fun getFileAttributes(): DWORD = memScoped { GetFileAttributesW(absolutePath) }
|
||||
|
||||
override fun resolve(path: String): MiraiFile {
|
||||
when (path) {
|
||||
@ -121,54 +178,177 @@ internal actual class MiraiFileImpl actual constructor(
|
||||
}
|
||||
|
||||
override fun createNewFile(): Boolean {
|
||||
memScoped {
|
||||
// https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilea
|
||||
val handle = CreateFileA(
|
||||
absolutePath,
|
||||
GENERIC_READ,
|
||||
FILE_SHARE_WRITE,
|
||||
nullPtr(),
|
||||
CREATE_NEW,
|
||||
FILE_ATTRIBUTE_NORMAL,
|
||||
nullPtr()
|
||||
)
|
||||
if (handle == NULL) return false
|
||||
CloseHandle(handle)
|
||||
return true
|
||||
// https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilea
|
||||
val handle = CreateFileW(
|
||||
absolutePath,
|
||||
GENERIC_READ,
|
||||
FILE_SHARE_DELETE,
|
||||
null,
|
||||
CREATE_NEW,
|
||||
FILE_ATTRIBUTE_NORMAL,
|
||||
null
|
||||
)
|
||||
if (handle == null || handle == INVALID_HANDLE_VALUE) {
|
||||
return false
|
||||
}
|
||||
if (CloseHandle(handle) == FALSE) {
|
||||
throw PosixException.forErrno(posixFunctionName = "CloseHandle()").wrapIO()
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
override fun delete(): Boolean {
|
||||
return if (isFile) {
|
||||
DeleteFileA(absolutePath) == 0
|
||||
DeleteFileW(absolutePath) != 0
|
||||
} else {
|
||||
RemoveDirectoryA(absolutePath) == 0
|
||||
RemoveDirectoryW(absolutePath) != 0
|
||||
}
|
||||
}
|
||||
|
||||
override fun mkdir(): Boolean {
|
||||
memScoped {
|
||||
val v = alloc<_SECURITY_ATTRIBUTES>()
|
||||
return CreateDirectoryA(absolutePath, v.ptr) == 0
|
||||
return CreateDirectoryW(absolutePath, v.ptr) != 0
|
||||
}
|
||||
}
|
||||
|
||||
override fun mkdirs(): Boolean {
|
||||
if (this.parent?.mkdirs() == false) {
|
||||
return false
|
||||
}
|
||||
this.parent?.mkdirs()
|
||||
return mkdir()
|
||||
}
|
||||
|
||||
override fun input(): Input {
|
||||
val handle = fopen(absolutePath, "r")
|
||||
if (handle == NULL) throw IllegalStateException("Failed to open file '$absolutePath'")
|
||||
return PosixInputForFile(handle!!)
|
||||
// println(absolutePath)
|
||||
// val handle2 = fopen(absolutePath, "rb") ?:throw IOException(
|
||||
// "Failed to open file '$absolutePath'",
|
||||
// PosixException.forErrno(posixFunctionName = "fopen()")
|
||||
// )
|
||||
// return PosixInputForFile(handle2)
|
||||
// Will get I/O operation failed due to posix error code 2
|
||||
|
||||
val handle = CreateFileW(
|
||||
absolutePath,
|
||||
GENERIC_READ,
|
||||
FILE_SHARE_DELETE,
|
||||
null,
|
||||
OPEN_EXISTING,
|
||||
FILE_ATTRIBUTE_NORMAL,
|
||||
null
|
||||
)
|
||||
if (handle == null || handle == INVALID_HANDLE_VALUE) throw IOException(
|
||||
"Failed to open file '$absolutePath'",
|
||||
PosixException.forErrno(posixFunctionName = "CreateFileW()")
|
||||
)
|
||||
return WindowsFileInput(handle)
|
||||
}
|
||||
|
||||
override fun output(): Output {
|
||||
val handle = fopen(absolutePath, "w")
|
||||
if (handle == NULL) throw IllegalStateException("Failed to open file '$absolutePath'")
|
||||
return PosixFileInstanceOutput(handle!!)
|
||||
// val handle2 = fopen(absolutePath, "wb")
|
||||
// ?: throw IOException(
|
||||
// "Failed to open file '$absolutePath'",
|
||||
// PosixException.forErrno(posixFunctionName = "fopen()")
|
||||
// )
|
||||
// return PosixFileInstanceOutput(handle)
|
||||
//
|
||||
println(absolutePath)
|
||||
val handle = CreateFileW(
|
||||
absolutePath,
|
||||
GENERIC_WRITE,
|
||||
FILE_SHARE_DELETE,
|
||||
null,
|
||||
(if (exists()) TRUNCATE_EXISTING else CREATE_NEW).toUInt(),
|
||||
FILE_ATTRIBUTE_NORMAL,
|
||||
null
|
||||
)
|
||||
if (handle == null || handle == INVALID_HANDLE_VALUE) throw IOException(
|
||||
"Failed to open file '$absolutePath'",
|
||||
PosixException.forErrno(posixFunctionName = "CreateFileW()")
|
||||
)
|
||||
return WindowsFileOutput(handle)
|
||||
}
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
return this.path.hashCode()
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (other == null) return false
|
||||
if (!isSameType(this, other)) return false
|
||||
return this.path == other.path
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return "MiraiFileImpl($path)"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
internal class WindowsFileInput(private val file: HANDLE) : Input() {
|
||||
private var closed = false
|
||||
|
||||
override fun fill(destination: Memory, offset: Int, length: Int): Int {
|
||||
if (file == INVALID_HANDLE_VALUE) return 0
|
||||
|
||||
println("fill: ${destination.pointer}, $offset, $length")
|
||||
memScoped {
|
||||
val n = alloc<DWORDVar>()
|
||||
if (ReadFile(file, destination.pointer + offset, length.convert(), n.ptr, null) == FALSE) {
|
||||
println("ERR! LastErr= ${GetLastError()}")
|
||||
throw PosixException.forErrno(posixFunctionName = "ReadFile()").wrapIO()
|
||||
}
|
||||
|
||||
println("LastErr= ${GetLastError()}")
|
||||
println("${n.value}, ${n.value.convert<UInt>().toInt()}")
|
||||
return n.value.convert<UInt>().toInt()
|
||||
}
|
||||
}
|
||||
|
||||
override fun closeSource() {
|
||||
println("closing")
|
||||
if (closed) return
|
||||
closed = true
|
||||
|
||||
if (file != INVALID_HANDLE_VALUE) {
|
||||
if (CloseHandle(file) == FALSE) {
|
||||
throw PosixException.forErrno(posixFunctionName = "CloseHandle()").wrapIO()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
internal class WindowsFileOutput(private val file: HANDLE) : Output() {
|
||||
private var closed = false
|
||||
|
||||
override fun flush(source: Memory, offset: Int, length: Int) {
|
||||
val end = offset + length
|
||||
var currentOffset = offset
|
||||
|
||||
memScoped {
|
||||
val written = alloc<UIntVar>()
|
||||
while (currentOffset < end) {
|
||||
val result = WriteFile(
|
||||
file,
|
||||
source.pointer + currentOffset.convert(),
|
||||
(end - currentOffset).convert(),
|
||||
written.ptr,
|
||||
null
|
||||
).convert<Int>()
|
||||
if (result == FALSE) {
|
||||
throw PosixException.forErrno(posixFunctionName = "WriteFile()").wrapIO()
|
||||
}
|
||||
currentOffset += written.value.toInt()
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
override fun closeDestination() {
|
||||
if (closed) return
|
||||
closed = true
|
||||
|
||||
if (CloseHandle(file) == FALSE) {
|
||||
throw PosixException.forErrno(posixFunctionName = "CloseHandle()").wrapIO()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -16,20 +16,30 @@ import kotlin.test.assertEquals
|
||||
|
||||
internal class WindowsMiraiFileImplTest : AbstractNativeMiraiFileImplTest() {
|
||||
private val rand = Random.nextInt().absoluteValue
|
||||
override val baseTempDir: MiraiFile = MiraiFile.create("mirai_unit_tests")
|
||||
override val tempPath = "mirai_unit_tests/temp$rand"
|
||||
override val baseTempDir: MiraiFile = MiraiFile.create("C:\\mirai_unit_tests")
|
||||
override val tempPath = "C:\\mirai_unit_tests\\temp$rand"
|
||||
|
||||
@Test
|
||||
override fun parent() {
|
||||
assertEquals("C:/Users/Shared/mirai_test", tempDir.parent!!.absolutePath)
|
||||
assertEquals("C:\\mirai_unit_tests", tempDir.parent!!.absolutePath)
|
||||
super.parent()
|
||||
}
|
||||
|
||||
override fun `canonical paths for non-canonical input`() {
|
||||
super.`canonical paths for non-canonical input`()
|
||||
|
||||
// extra /sss/..
|
||||
MiraiFile.create("$tempPath/sss/..").resolve("test").let {
|
||||
assertPathEquals("${tempPath}/test", it.path) // Windows resolves always
|
||||
assertPathEquals("${tempPath}/test", it.absolutePath)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
override fun `resolve absolute`() {
|
||||
MiraiFile.create("$tempPath/").resolve("C:/Users").let {
|
||||
assertEquals("C:/Users", it.path)
|
||||
assertEquals("C:/Users", it.absolutePath)
|
||||
MiraiFile.create("$tempPath/").resolve("C:\\mirai_unit_tests").let {
|
||||
assertEquals("C:\\mirai_unit_tests", it.path)
|
||||
assertEquals("C:\\mirai_unit_tests", it.absolutePath)
|
||||
}
|
||||
}
|
||||
}
|
@ -24,7 +24,7 @@ internal abstract class AbstractNativeMiraiFileImplTest {
|
||||
@AfterTest
|
||||
fun afterTest() {
|
||||
println("Cleaning up...")
|
||||
baseTempDir.deleteRecursively()
|
||||
println("deleteRecursively:" + baseTempDir.deleteRecursively())
|
||||
}
|
||||
|
||||
@BeforeTest
|
||||
@ -35,8 +35,8 @@ internal abstract class AbstractNativeMiraiFileImplTest {
|
||||
|
||||
@Test
|
||||
fun `canonical paths for canonical input`() {
|
||||
assertEquals(tempPath, tempDir.path)
|
||||
assertEquals(tempPath, tempDir.absolutePath)
|
||||
assertPathEquals(tempPath, tempDir.path)
|
||||
assertPathEquals(tempPath, tempDir.absolutePath)
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -46,31 +46,26 @@ internal abstract class AbstractNativeMiraiFileImplTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `canonical paths for non-canonical input`() {
|
||||
open fun `canonical paths for non-canonical input`() {
|
||||
// extra /
|
||||
MiraiFile.create("$tempPath/").resolve("test").let {
|
||||
assertEquals("${tempPath}/test", it.path)
|
||||
assertEquals("${tempPath}/test", it.absolutePath)
|
||||
assertPathEquals("${tempPath}/test", it.path)
|
||||
assertPathEquals("${tempPath}/test", it.absolutePath)
|
||||
}
|
||||
// extra //
|
||||
MiraiFile.create("$tempPath//").resolve("test").let {
|
||||
assertEquals("${tempPath}/test", it.path)
|
||||
assertEquals("${tempPath}/test", it.absolutePath)
|
||||
assertPathEquals("${tempPath}/test", it.path)
|
||||
assertPathEquals("${tempPath}/test", it.absolutePath)
|
||||
}
|
||||
// extra /.
|
||||
MiraiFile.create("$tempPath/.").resolve("test").let {
|
||||
assertEquals("${tempPath}/test", it.path)
|
||||
assertEquals("${tempPath}/test", it.absolutePath)
|
||||
assertPathEquals("${tempPath}/test", it.path)
|
||||
assertPathEquals("${tempPath}/test", it.absolutePath)
|
||||
}
|
||||
// extra /./.
|
||||
MiraiFile.create("$tempPath/./.").resolve("test").let {
|
||||
assertEquals("${tempPath}/test", it.path)
|
||||
assertEquals("${tempPath}/test", it.absolutePath)
|
||||
}
|
||||
// extra /sss/..
|
||||
MiraiFile.create("$tempPath/sss/..").resolve("test").let {
|
||||
assertEquals("${tempPath}/sss/../test", it.path) // because file is not found
|
||||
assertEquals("${tempPath}/sss/../test", it.absolutePath)
|
||||
assertPathEquals("${tempPath}/test", it.path)
|
||||
assertPathEquals("${tempPath}/test", it.absolutePath)
|
||||
}
|
||||
}
|
||||
|
||||
@ -90,7 +85,7 @@ internal abstract class AbstractNativeMiraiFileImplTest {
|
||||
assertFalse { tempDir.resolve("not_existing_dir").exists() }
|
||||
assertEquals(0L, tempDir.resolve("not_existing_dir").length)
|
||||
assertTrue { tempDir.resolve("not_existing_dir").mkdir() }
|
||||
assertNotEquals(0L, tempDir.resolve("not_existing_dir").length)
|
||||
// assertNotEquals(0L, tempDir.resolve("not_existing_dir").length) // length is platform-dependent, on Windows it is 0 but on unix it is not
|
||||
assertTrue { tempDir.resolve("not_existing_dir").exists() }
|
||||
}
|
||||
|
||||
@ -98,20 +93,27 @@ internal abstract class AbstractNativeMiraiFileImplTest {
|
||||
fun `isFile isDirectory`() {
|
||||
assertTrue { tempDir.exists() }
|
||||
|
||||
println("1")
|
||||
assertFalse { tempDir.resolve("not_existing_file.txt").exists() }
|
||||
assertEquals(false, tempDir.resolve("not_existing_file.txt").isFile)
|
||||
println("1")
|
||||
assertEquals(false, tempDir.resolve("not_existing_file.txt").isDirectory)
|
||||
println("1")
|
||||
assertTrue { tempDir.resolve("not_existing_file.txt").createNewFile() }
|
||||
assertEquals(true, tempDir.resolve("not_existing_file.txt").isFile)
|
||||
assertEquals(false, tempDir.resolve("not_existing_file.txt").isDirectory)
|
||||
println("1")
|
||||
assertTrue { tempDir.resolve("not_existing_file.txt").exists() }
|
||||
|
||||
println("1")
|
||||
assertFalse { tempDir.resolve("not_existing_dir").exists() }
|
||||
assertEquals(false, tempDir.resolve("not_existing_dir").isFile)
|
||||
assertEquals(false, tempDir.resolve("not_existing_dir").isDirectory)
|
||||
println("1")
|
||||
assertTrue { tempDir.resolve("not_existing_dir").mkdir() }
|
||||
assertEquals(false, tempDir.resolve("not_existing_dir").isFile)
|
||||
assertEquals(true, tempDir.resolve("not_existing_dir").isDirectory)
|
||||
println("1")
|
||||
assertTrue { tempDir.resolve("not_existing_dir").exists() }
|
||||
}
|
||||
|
||||
@ -134,7 +136,7 @@ internal abstract class AbstractNativeMiraiFileImplTest {
|
||||
|
||||
@Test
|
||||
fun readText() {
|
||||
tempDir.resolve("readText1.txt").let { file ->
|
||||
tempDir.resolve("readText2.txt").let { file ->
|
||||
assertTrue { !file.exists() }
|
||||
assertFailsWith<IOException> { file.readText() }
|
||||
|
||||
@ -143,4 +145,39 @@ internal abstract class AbstractNativeMiraiFileImplTest {
|
||||
assertEquals(text, file.readText())
|
||||
}
|
||||
}
|
||||
|
||||
private val bigText = "some text".repeat(10000)
|
||||
|
||||
@Test
|
||||
fun writeBigText() {
|
||||
// new file
|
||||
tempDir.resolve("writeText3.txt").let { file ->
|
||||
file.writeText(bigText)
|
||||
assertEquals(bigText.length, file.length.toInt())
|
||||
}
|
||||
|
||||
// override
|
||||
tempDir.resolve("writeText4.txt").let { file ->
|
||||
file.writeText(bigText)
|
||||
assertEquals(bigText.length, file.length.toInt())
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun readBigText() {
|
||||
tempDir.resolve("readText4.txt").let { file ->
|
||||
assertTrue { !file.exists() }
|
||||
assertFailsWith<IOException> { file.readText() }
|
||||
|
||||
file.writeText(bigText)
|
||||
println("reading text")
|
||||
val read = file.readText()
|
||||
assertEquals(bigText.length, read.length)
|
||||
assertEquals(bigText, read)
|
||||
}
|
||||
}
|
||||
|
||||
protected fun assertPathEquals(expected: String, actual: String, message: String? = null) {
|
||||
asserter.assertEquals(message, expected.replace("\\", "/"), actual.replace("\\", "/"))
|
||||
}
|
||||
}
|
@ -28,6 +28,16 @@ internal class UnixMiraiFileImplTest : AbstractNativeMiraiFileImplTest() {
|
||||
super.parent()
|
||||
}
|
||||
|
||||
override fun `canonical paths for non-canonical input`() {
|
||||
super.`canonical paths for non-canonical input`()
|
||||
|
||||
// extra /sss/..
|
||||
MiraiFile.create("$tempPath/sss/..").resolve("test").let {
|
||||
assertPathEquals("${tempPath}/sss/../test", it.path) // because file is not found
|
||||
assertPathEquals("${tempPath}/sss/../test", it.absolutePath)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
override fun `resolve absolute`() {
|
||||
MiraiFile.create("$tempPath/").resolve("/Users").let {
|
||||
|
Loading…
Reference in New Issue
Block a user