From 6e8aa12dd6ffa66c3cf4afc2d68a503059ea88f6 Mon Sep 17 00:00:00 2001 From: Yuri Schimke Date: Sat, 6 Feb 2021 13:49:05 +0000 Subject: [PATCH] Okio Filesystem (#6500) * Testing okio * Working tests * Working tests * Working tests * Working tests * okio 3 * Fix dependencies * File system * Cleanup * Cleanup * Cleanup * Cache fixes * Cache fixes * Review comments * Cleanup * Cleanup * Build fixes --- build.gradle | 12 +- native-image-tests/build.gradle | 1 + .../okhttp3/internal/io/InMemoryFileSystem.kt | 116 ---- .../okhttp3/internal/io/WindowsFileSystem.kt | 104 ---- .../kotlin/okhttp3/okio/LoggingFilesystem.kt | 66 +++ okhttp/build.gradle | 1 + okhttp/src/main/kotlin/okhttp3/Cache.kt | 104 ++-- .../internal/NativeImageTestsAccessors.kt | 8 +- .../src/main/kotlin/okhttp3/internal/Util.kt | 81 ++- .../okhttp3/internal/cache/DiskLruCache.kt | 123 ++-- .../kotlin/okhttp3/internal/io/FileSystem.kt | 149 ----- .../test/java/okhttp3/CacheCorruptionTest.kt | 76 ++- okhttp/src/test/java/okhttp3/CacheTest.java | 47 +- okhttp/src/test/java/okhttp3/CallTest.java | 12 +- .../internal/cache/DiskLruCacheTest.kt | 524 +++++++++--------- .../okhttp3/internal/io/FaultyFileSystem.java | 114 ---- .../okhttp3/internal/io/FaultyFileSystem.kt | 89 +++ .../okhttp3/internal/io/FileSystemTest.kt | 175 ------ 18 files changed, 710 insertions(+), 1092 deletions(-) delete mode 100644 okhttp-testing-support/src/main/kotlin/okhttp3/internal/io/InMemoryFileSystem.kt delete mode 100644 okhttp-testing-support/src/main/kotlin/okhttp3/internal/io/WindowsFileSystem.kt create mode 100644 okhttp-testing-support/src/main/kotlin/okhttp3/okio/LoggingFilesystem.kt delete mode 100644 okhttp/src/main/kotlin/okhttp3/internal/io/FileSystem.kt delete mode 100644 okhttp/src/test/java/okhttp3/internal/io/FaultyFileSystem.java create mode 100644 okhttp/src/test/java/okhttp3/internal/io/FaultyFileSystem.kt delete mode 100644 okhttp/src/test/java/okhttp3/internal/io/FileSystemTest.kt diff --git a/build.gradle b/build.gradle index c6aefc6e7..f452f072c 100644 --- a/build.gradle +++ b/build.gradle @@ -16,7 +16,7 @@ buildscript { 'junit5': '5.7.0', 'kotlin': '1.4.30', 'moshi': '1.11.0', - 'okio': '2.9.0', + 'okio': '3.0.0-alpha.1', 'ktlint': '0.38.0', 'picocli': '4.5.1', 'openjsse': '1.1.0', @@ -50,6 +50,7 @@ buildscript { 'moshi': "com.squareup.moshi:moshi:${versions.moshi}", 'moshiKotlin': "com.squareup.moshi:moshi-kotlin-codegen:${versions.moshi}", 'okio': "com.squareup.okio:okio:${versions.okio}", + 'okioFakeFileSystem': "com.squareup.okio:okio-fakefilesystem:${versions.okio}", 'openjsse': "org.openjsse:openjsse:${versions.openjsse}", 'bnd': "biz.aQute.bnd:biz.aQute.bnd.gradle:${versions.bnd}", 'bndResolve': "biz.aQute.bnd:biz.aQute.resolve:${versions.bnd}", @@ -110,6 +111,13 @@ allprojects { project.ext.artifactId = rootProject.ext.publishedArtifactId(project) version = '5.0.0-SNAPSHOT' + repositories { + mavenCentral() + maven { url 'https://dl.bintray.com/kotlin/dokka' } + maven { url 'https://kotlin.bintray.com/kotlinx/' } + google() + } + task downloadDependencies() { description 'Download all dependencies to the Gradle cache' doLast { @@ -358,7 +366,7 @@ def alpnBootVersion() { return alpnBootVersionForPatchVersion(javaVersion, patchVersion) } -def alpnBootVersionForPatchVersion(String javaVersion, int patchVersion) { +static def alpnBootVersionForPatchVersion(String javaVersion, int patchVersion) { // https://www.eclipse.org/jetty/documentation/current/alpn-chapter.html#alpn-versions switch (patchVersion) { case 0..24: diff --git a/native-image-tests/build.gradle b/native-image-tests/build.gradle index ed700fa63..b9784e7e0 100644 --- a/native-image-tests/build.gradle +++ b/native-image-tests/build.gradle @@ -9,6 +9,7 @@ dependencies { implementation deps.junit5Api implementation deps.junit5JupiterEngine implementation deps.junitPlatformConsole + implementation deps.okioFakeFileSystem implementation project(':okhttp') implementation project(':okhttp-brotli') diff --git a/okhttp-testing-support/src/main/kotlin/okhttp3/internal/io/InMemoryFileSystem.kt b/okhttp-testing-support/src/main/kotlin/okhttp3/internal/io/InMemoryFileSystem.kt deleted file mode 100644 index 4ef272271..000000000 --- a/okhttp-testing-support/src/main/kotlin/okhttp3/internal/io/InMemoryFileSystem.kt +++ /dev/null @@ -1,116 +0,0 @@ -/* - * Copyright (C) 2015 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 okhttp3.internal.io - -import java.io.File -import java.io.FileNotFoundException -import java.io.IOException -import java.util.IdentityHashMap -import okio.Buffer -import okio.ForwardingSink -import okio.ForwardingSource -import okio.Sink -import okio.Source -import okhttp3.TestUtil.isDescendentOf -import org.junit.jupiter.api.extension.AfterEachCallback -import org.junit.jupiter.api.extension.ExtensionContext - -/** A simple file system where all files are held in memory. Not safe for concurrent use. */ -class InMemoryFileSystem : FileSystem, AfterEachCallback { - val files = mutableMapOf() - private val openSources = IdentityHashMap() - private val openSinks = IdentityHashMap() - - override fun afterEach(context: ExtensionContext?) { - ensureResourcesClosed() - } - - fun ensureResourcesClosed() { - val openResources = mutableListOf() - for (file in openSources.values) { - openResources.add("Source for $file") - } - for (file in openSinks.values) { - openResources.add("Sink for $file") - } - check(openResources.isEmpty()) { - "Resources acquired but not closed:\n * ${openResources.joinToString(separator = "\n * ")}" - } - } - - @Throws(FileNotFoundException::class) - override fun source(file: File): Source { - val result = files[file] ?: throw FileNotFoundException() - val source: Source = result.clone() - openSources[source] = file - return object : ForwardingSource(source) { - override fun close() { - openSources.remove(source) - super.close() - } - } - } - - @Throws(FileNotFoundException::class) - override fun sink(file: File) = sink(file, false) - - @Throws(FileNotFoundException::class) - override fun appendingSink(file: File) = sink(file, true) - - private fun sink(file: File, appending: Boolean): Sink { - var result: Buffer? = null - if (appending) { - result = files[file] - } - if (result == null) { - result = Buffer() - } - files[file] = result - val sink: Sink = result - openSinks[sink] = file - return object : ForwardingSink(sink) { - override fun close() { - openSinks.remove(sink) - super.close() - } - } - } - - @Throws(IOException::class) - override fun delete(file: File) { - files.remove(file) - } - - override fun exists(file: File) = files.containsKey(file) - - override fun size(file: File) = files[file]?.size ?: 0L - - @Throws(IOException::class) - override fun rename(from: File, to: File) { - files[to] = files.remove(from) ?: throw FileNotFoundException() - } - - @Throws(IOException::class) - override fun deleteContents(directory: File) { - val i = files.keys.iterator() - while (i.hasNext()) { - val file = i.next() - if (file.isDescendentOf(directory)) i.remove() - } - } - - override fun toString() = "InMemoryFileSystem" -} diff --git a/okhttp-testing-support/src/main/kotlin/okhttp3/internal/io/WindowsFileSystem.kt b/okhttp-testing-support/src/main/kotlin/okhttp3/internal/io/WindowsFileSystem.kt deleted file mode 100644 index c07ebdd91..000000000 --- a/okhttp-testing-support/src/main/kotlin/okhttp3/internal/io/WindowsFileSystem.kt +++ /dev/null @@ -1,104 +0,0 @@ -/* - * Copyright (C) 2020 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 okhttp3.internal.io - -import java.io.File -import java.util.Collections -import okhttp3.TestUtil.isDescendentOf -import okio.ForwardingSink -import okio.ForwardingSource -import okio.IOException -import okio.Sink -import okio.Source - -/** - * Emulate Windows file system limitations on any file system. In particular, Windows will throw an - * [IOException] when asked to delete or rename an open file. - */ -class WindowsFileSystem(val delegate: FileSystem) : FileSystem { - /** Guarded by itself. */ - private val openFiles = Collections.synchronizedList(mutableListOf()) - - override fun source(file: File): Source = FileSource(file, delegate.source(file)) - - override fun sink(file: File): Sink = FileSink(file, delegate.sink(file)) - - override fun appendingSink(file: File): Sink = FileSink(file, delegate.appendingSink(file)) - - override fun delete(file: File) { - val fileOpen = file in openFiles - if (fileOpen) throw IOException("file is open $file") - delegate.delete(file) - } - - override fun exists(file: File) = delegate.exists(file) - - override fun size(file: File) = delegate.size(file) - - override fun rename(from: File, to: File) { - val fromOpen = from in openFiles - if (fromOpen) throw IOException("file is open $from") - - val toOpen = to in openFiles - if (toOpen) throw IOException("file is open $to") - - delegate.rename(from, to) - } - - override fun deleteContents(directory: File) { - val openChild = synchronized(openFiles) { - openFiles.firstOrNull { it.isDescendentOf(directory) } - } - if (openChild != null) throw IOException("file is open $openChild") - delegate.deleteContents(directory) - } - - private inner class FileSink(val file: File, delegate: Sink) : ForwardingSink(delegate) { - var closed = false - - init { - openFiles += file - } - - override fun close() { - if (!closed) { - closed = true - val removed = openFiles.remove(file) - check(removed) - } - delegate.close() - } - } - - private inner class FileSource(val file: File, delegate: Source) : ForwardingSource(delegate) { - var closed = false - - init { - openFiles += file - } - - override fun close() { - if (!closed) { - closed = true - val removed = openFiles.remove(file) - check(removed) - } - delegate.close() - } - } - - override fun toString() = "$delegate for Windows™" -} diff --git a/okhttp-testing-support/src/main/kotlin/okhttp3/okio/LoggingFilesystem.kt b/okhttp-testing-support/src/main/kotlin/okhttp3/okio/LoggingFilesystem.kt new file mode 100644 index 000000000..80e58cf24 --- /dev/null +++ b/okhttp-testing-support/src/main/kotlin/okhttp3/okio/LoggingFilesystem.kt @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2020 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 okhttp3.okio + +import okio.ExperimentalFileSystem +import okio.FileSystem +import okio.ForwardingFileSystem +import okio.Path +import okio.Sink +import okio.Source + +@OptIn(ExperimentalFileSystem::class) +class LoggingFilesystem(fileSystem: FileSystem) : ForwardingFileSystem(fileSystem) { + fun log(line: String) { + println(line) + } + + override fun appendingSink(file: Path): Sink { + log("appendingSink($file)") + + return super.appendingSink(file) + } + + override fun atomicMove(source: Path, target: Path) { + log("atomicMove($source, $target)") + + super.atomicMove(source, target) + } + + override fun createDirectory(dir: Path) { + log("createDirectory($dir)") + + super.createDirectory(dir) + } + + override fun delete(path: Path) { + log("delete($path)") + + super.delete(path) + } + + override fun sink(path: Path): Sink { + log("sink($path)") + + return super.sink(path) + } + + override fun source(file: Path): Source { + log("source($file)") + + return super.source(file) + } +} diff --git a/okhttp/build.gradle b/okhttp/build.gradle index 79560032b..7e7906387 100644 --- a/okhttp/build.gradle +++ b/okhttp/build.gradle @@ -82,6 +82,7 @@ dependencies { testImplementation project(':okhttp-brotli') testImplementation project(':okhttp-dnsoverhttps') testImplementation project(':okhttp-sse') + testImplementation deps.okioFakeFileSystem testImplementation deps.conscrypt testImplementation deps.junit testImplementation deps.junit5Api diff --git a/okhttp/src/main/kotlin/okhttp3/Cache.kt b/okhttp/src/main/kotlin/okhttp3/Cache.kt index f505a1488..c4408a13c 100644 --- a/okhttp/src/main/kotlin/okhttp3/Cache.kt +++ b/okhttp/src/main/kotlin/okhttp3/Cache.kt @@ -25,7 +25,6 @@ import okhttp3.internal.closeQuietly import okhttp3.internal.concurrent.TaskRunner import okhttp3.internal.http.HttpMethod import okhttp3.internal.http.StatusLine -import okhttp3.internal.io.FileSystem import okhttp3.internal.platform.Platform import okhttp3.internal.platform.Platform.Companion.WARN import okhttp3.internal.toLongOrDefault @@ -35,8 +34,12 @@ import okio.BufferedSource import okio.ByteString.Companion.decodeBase64 import okio.ByteString.Companion.encodeUtf8 import okio.ByteString.Companion.toByteString +import okio.ExperimentalFileSystem +import okio.FileSystem import okio.ForwardingSink import okio.ForwardingSource +import okio.Path +import okio.Path.Companion.toOkioPath import okio.Sink import okio.Source import okio.buffer @@ -50,14 +53,6 @@ import java.security.cert.CertificateException import java.security.cert.CertificateFactory import java.util.NoSuchElementException import java.util.TreeSet -import kotlin.collections.ArrayList -import kotlin.collections.List -import kotlin.collections.MutableIterator -import kotlin.collections.MutableSet -import kotlin.collections.Set -import kotlin.collections.emptyList -import kotlin.collections.emptySet -import kotlin.collections.none /** * Caches HTTP and HTTPS responses to the filesystem so they may be reused, saving time and @@ -148,18 +143,20 @@ import kotlin.collections.none * * [rfc_7234]: http://tools.ietf.org/html/rfc7234 */ -class Cache internal constructor( - directory: File, +@OptIn(ExperimentalFileSystem::class) +class Cache +internal constructor( + directory: Path, maxSize: Long, fileSystem: FileSystem ) : Closeable, Flushable { internal val cache = DiskLruCache( - fileSystem = fileSystem, - directory = directory, - appVersion = VERSION, - valueCount = ENTRY_COUNT, - maxSize = maxSize, - taskRunner = TaskRunner.INSTANCE + fileSystem = fileSystem, + directory = directory, + appVersion = VERSION, + valueCount = ENTRY_COUNT, + maxSize = maxSize, + taskRunner = TaskRunner.INSTANCE ) // read and write statistics, all guarded by 'this'. @@ -173,7 +170,10 @@ class Cache internal constructor( get() = cache.isClosed() /** Create a cache of at most [maxSize] bytes in [directory]. */ - constructor(directory: File, maxSize: Long) : this(directory, maxSize, FileSystem.SYSTEM) + @OptIn(ExperimentalFileSystem::class) + constructor(directory: File, maxSize: Long) : this( + directory.toOkioPath(), maxSize, FileSystem.SYSTEM + ) internal fun get(request: Request): Response? { val key = key(request.url) @@ -365,14 +365,18 @@ class Cache internal constructor( } @get:JvmName("directory") val directory: File + get() = cache.directory.toFile() + + @get:JvmName("directoryPath") val directoryPath: Path get() = cache.directory @JvmName("-deprecated_directory") @Deprecated( - message = "moved to val", - replaceWith = ReplaceWith(expression = "directory"), - level = DeprecationLevel.ERROR) - fun directory(): File = cache.directory + message = "moved to val", + replaceWith = ReplaceWith(expression = "directory"), + level = DeprecationLevel.ERROR + ) + fun directory(): File = cache.directory.toFile() @Synchronized internal fun trackResponse(cacheStrategy: CacheStrategy) { requestCount++ @@ -575,27 +579,27 @@ class Cache internal constructor( sink.writeDecimalLong(varyHeaders.size.toLong()).writeByte('\n'.toInt()) for (i in 0 until varyHeaders.size) { sink.writeUtf8(varyHeaders.name(i)) - .writeUtf8(": ") - .writeUtf8(varyHeaders.value(i)) - .writeByte('\n'.toInt()) + .writeUtf8(": ") + .writeUtf8(varyHeaders.value(i)) + .writeByte('\n'.toInt()) } sink.writeUtf8(StatusLine(protocol, code, message).toString()).writeByte('\n'.toInt()) sink.writeDecimalLong((responseHeaders.size + 2).toLong()).writeByte('\n'.toInt()) for (i in 0 until responseHeaders.size) { sink.writeUtf8(responseHeaders.name(i)) - .writeUtf8(": ") - .writeUtf8(responseHeaders.value(i)) - .writeByte('\n'.toInt()) + .writeUtf8(": ") + .writeUtf8(responseHeaders.value(i)) + .writeByte('\n'.toInt()) } sink.writeUtf8(SENT_MILLIS) - .writeUtf8(": ") - .writeDecimalLong(sentRequestMillis) - .writeByte('\n'.toInt()) + .writeUtf8(": ") + .writeDecimalLong(sentRequestMillis) + .writeByte('\n'.toInt()) sink.writeUtf8(RECEIVED_MILLIS) - .writeUtf8(": ") - .writeDecimalLong(receivedResponseMillis) - .writeByte('\n'.toInt()) + .writeUtf8(": ") + .writeDecimalLong(receivedResponseMillis) + .writeByte('\n'.toInt()) if (isHttps) { sink.writeByte('\n'.toInt()) @@ -643,29 +647,29 @@ class Cache internal constructor( fun matches(request: Request, response: Response): Boolean { return url == request.url && - requestMethod == request.method && - varyMatches(response, varyHeaders, request) + requestMethod == request.method && + varyMatches(response, varyHeaders, request) } fun response(snapshot: DiskLruCache.Snapshot): Response { val contentType = responseHeaders["Content-Type"] val contentLength = responseHeaders["Content-Length"] val cacheRequest = Request.Builder() - .url(url) - .method(requestMethod, null) - .headers(varyHeaders) - .build() + .url(url) + .method(requestMethod, null) + .headers(varyHeaders) + .build() return Response.Builder() - .request(cacheRequest) - .protocol(protocol) - .code(code) - .message(message) - .headers(responseHeaders) - .body(CacheResponseBody(snapshot, contentType, contentLength)) - .handshake(handshake) - .sentRequestAtMillis(sentRequestMillis) - .receivedResponseAtMillis(receivedResponseMillis) - .build() + .request(cacheRequest) + .protocol(protocol) + .code(code) + .message(message) + .headers(responseHeaders) + .body(CacheResponseBody(snapshot, contentType, contentLength)) + .handshake(handshake) + .sentRequestAtMillis(sentRequestMillis) + .receivedResponseAtMillis(receivedResponseMillis) + .build() } companion object { diff --git a/okhttp/src/main/kotlin/okhttp3/internal/NativeImageTestsAccessors.kt b/okhttp/src/main/kotlin/okhttp3/internal/NativeImageTestsAccessors.kt index a30be5163..b7f49c864 100644 --- a/okhttp/src/main/kotlin/okhttp3/internal/NativeImageTestsAccessors.kt +++ b/okhttp/src/main/kotlin/okhttp3/internal/NativeImageTestsAccessors.kt @@ -21,10 +21,12 @@ import okhttp3.Response import okhttp3.internal.connection.Exchange import okhttp3.internal.connection.RealCall import okhttp3.internal.connection.RealConnection -import okhttp3.internal.io.FileSystem -import java.io.File +import okio.ExperimentalFileSystem +import okio.FileSystem +import okio.Path -fun buildCache(file: File, maxSize: Long, fileSystem: FileSystem): Cache { +@OptIn(ExperimentalFileSystem::class) +fun buildCache(file: Path, maxSize: Long, fileSystem: FileSystem): Cache { return Cache(file, maxSize, fileSystem) } diff --git a/okhttp/src/main/kotlin/okhttp3/internal/Util.kt b/okhttp/src/main/kotlin/okhttp3/internal/Util.kt index 4574cd5c9..ae9ce710b 100644 --- a/okhttp/src/main/kotlin/okhttp3/internal/Util.kt +++ b/okhttp/src/main/kotlin/okhttp3/internal/Util.kt @@ -17,8 +17,27 @@ package okhttp3.internal +import okhttp3.EventListener +import okhttp3.Headers +import okhttp3.Headers.Companion.headersOf +import okhttp3.HttpUrl +import okhttp3.OkHttp +import okhttp3.OkHttpClient +import okhttp3.RequestBody.Companion.toRequestBody +import okhttp3.Response +import okhttp3.ResponseBody.Companion.toResponseBody +import okhttp3.internal.http2.Header +import okio.Buffer +import okio.BufferedSink +import okio.BufferedSource +import okio.ByteString.Companion.decodeHex +import okio.ExperimentalFileSystem +import okio.FileSystem +import okio.Options +import okio.Path +import okio.Source import java.io.Closeable -import java.io.File +import java.io.FileNotFoundException import java.io.IOException import java.io.InterruptedIOException import java.net.InetSocketAddress @@ -38,24 +57,6 @@ import java.util.concurrent.ThreadFactory import java.util.concurrent.TimeUnit import kotlin.text.Charsets.UTF_32BE import kotlin.text.Charsets.UTF_32LE -import okhttp3.Call -import okhttp3.EventListener -import okhttp3.Headers -import okhttp3.Headers.Companion.headersOf -import okhttp3.HttpUrl -import okhttp3.OkHttp -import okhttp3.OkHttpClient -import okhttp3.RequestBody.Companion.toRequestBody -import okhttp3.Response -import okhttp3.ResponseBody.Companion.toResponseBody -import okhttp3.internal.http2.Header -import okhttp3.internal.io.FileSystem -import okio.Buffer -import okio.BufferedSink -import okio.BufferedSource -import okio.ByteString.Companion.decodeHex -import okio.Options -import okio.Source @JvmField val EMPTY_BYTE_ARRAY = ByteArray(0) @@ -530,7 +531,8 @@ fun ServerSocket.closeQuietly() { * * @param file a file in the directory to check. This file shouldn't already exist! */ -fun FileSystem.isCivilized(file: File): Boolean { +@OptIn(ExperimentalFileSystem::class) +fun FileSystem.isCivilized(file: Path): Boolean { sink(file).use { try { delete(file) @@ -542,6 +544,45 @@ fun FileSystem.isCivilized(file: File): Boolean { return false } +/** Delete file we expect but don't require to exist. */ +@OptIn(ExperimentalFileSystem::class) +fun FileSystem.deleteIfExists(path: Path) { + try { + delete(path) + } catch (fnfe: FileNotFoundException) { + return + } +} + +/** + * Tolerant delete, try to clear as many files as possible even after a failure. + */ +@OptIn(ExperimentalFileSystem::class) +fun FileSystem.deleteContents(directory: Path) { + var exception: IOException? = null + val files = try { + list(directory) + } catch (fnfe: FileNotFoundException) { + return + } + for (file in files) { + try { + if (metadata(file).isDirectory) { + deleteContents(file) + } + + delete(file) + } catch (ioe: IOException) { + if (exception == null) { + exception = ioe + } + } + } + if (exception != null) { + throw exception + } +} + fun Long.toHexString(): String = java.lang.Long.toHexString(this) fun Int.toHexString(): String = Integer.toHexString(this) diff --git a/okhttp/src/main/kotlin/okhttp3/internal/cache/DiskLruCache.kt b/okhttp/src/main/kotlin/okhttp3/internal/cache/DiskLruCache.kt index f4b059822..1f520601f 100644 --- a/okhttp/src/main/kotlin/okhttp3/internal/cache/DiskLruCache.kt +++ b/okhttp/src/main/kotlin/okhttp3/internal/cache/DiskLruCache.kt @@ -20,16 +20,27 @@ import okhttp3.internal.cache.DiskLruCache.Editor import okhttp3.internal.closeQuietly import okhttp3.internal.concurrent.Task import okhttp3.internal.concurrent.TaskRunner -import okhttp3.internal.io.FileSystem +import okhttp3.internal.deleteContents +import okhttp3.internal.deleteIfExists import okhttp3.internal.isCivilized import okhttp3.internal.okHttpName import okhttp3.internal.platform.Platform import okhttp3.internal.platform.Platform.Companion.WARN -import okio.* -import java.io.* +import okio.BufferedSink +import okio.ExperimentalFileSystem +import okio.FileNotFoundException +import okio.FileSystem +import okio.ForwardingFileSystem +import okio.ForwardingSource +import okio.Path +import okio.Sink +import okio.Source +import okio.blackholeSink +import okio.buffer +import java.io.Closeable import java.io.EOFException +import java.io.Flushable import java.io.IOException -import java.util.* /** * A cache that uses a bounded amount of space on a filesystem. Each cache entry has a string key @@ -74,11 +85,12 @@ import java.util.* * @param valueCount the number of values per cache entry. Must be positive. * @param maxSize the maximum number of bytes this cache should use to store. */ +@OptIn(ExperimentalFileSystem::class) class DiskLruCache( - internal val fileSystem: FileSystem, + fileSystem: FileSystem, /** Returns the directory where this cache stores its data. */ - val directory: File, + val directory: Path, private val appVersion: Int, @@ -90,6 +102,18 @@ class DiskLruCache( /** Used for asynchronous journal rebuilds. */ taskRunner: TaskRunner ) : Closeable, Flushable { + internal val fileSystem: FileSystem = object : ForwardingFileSystem(fileSystem) { + override fun sink(file: Path): Sink { + file.parent?.let { + // TODO from okhttp3.internal.io.FileSystem + if (!exists(it)) { + createDirectories(it) + } + } + return super.sink(file) + } + } + /** The maximum number of bytes that this cache should use to store its data. */ @get:Synchronized @set:Synchronized var maxSize: Long = maxSize set(value) { @@ -139,9 +163,9 @@ class DiskLruCache( * compaction; that file should be deleted if it exists when the cache is opened. */ - private val journalFile: File - private val journalFileTmp: File - private val journalFileBackup: File + private val journalFile: Path + private val journalFileTmp: Path + private val journalFileBackup: Path private var size: Long = 0L private var journalWriter: BufferedSink? = null internal val lruEntries = LinkedHashMap(0, 0.75f, true) @@ -195,9 +219,9 @@ class DiskLruCache( require(maxSize > 0L) { "maxSize <= 0" } require(valueCount > 0) { "valueCount <= 0" } - this.journalFile = File(directory, JOURNAL_FILE) - this.journalFileTmp = File(directory, JOURNAL_FILE_TEMP) - this.journalFileBackup = File(directory, JOURNAL_FILE_BACKUP) + this.journalFile = directory / JOURNAL_FILE + this.journalFileTmp = directory / JOURNAL_FILE_TEMP + this.journalFileBackup = directory / JOURNAL_FILE_BACKUP } @Synchronized @Throws(IOException::class) @@ -214,7 +238,7 @@ class DiskLruCache( if (fileSystem.exists(journalFile)) { fileSystem.delete(journalFileBackup) } else { - fileSystem.rename(journalFileBackup, journalFile) + fileSystem.atomicMove(journalFileBackup, journalFile) } } @@ -250,12 +274,12 @@ class DiskLruCache( @Throws(IOException::class) private fun readJournal() { - fileSystem.source(journalFile).buffer().use { source -> - val magic = source.readUtf8LineStrict() - val version = source.readUtf8LineStrict() - val appVersionString = source.readUtf8LineStrict() - val valueCountString = source.readUtf8LineStrict() - val blank = source.readUtf8LineStrict() + fileSystem.read(journalFile) { + val magic = readUtf8LineStrict() + val version = readUtf8LineStrict() + val appVersionString = readUtf8LineStrict() + val valueCountString = readUtf8LineStrict() + val blank = readUtf8LineStrict() if (MAGIC != magic || VERSION_1 != version || @@ -269,7 +293,7 @@ class DiskLruCache( var lineCount = 0 while (true) { try { - readJournalLine(source.readUtf8LineStrict()) + readJournalLine(readUtf8LineStrict()) lineCount++ } catch (_: EOFException) { break // End of journal. @@ -279,7 +303,7 @@ class DiskLruCache( redundantOpCount = lineCount - lruEntries.size // If we ended on a truncated line, rebuild the journal before appending to it. - if (!source.exhausted()) { + if (!exhausted()) { rebuildJournal() } else { journalWriter = newJournalWriter() @@ -348,7 +372,7 @@ class DiskLruCache( */ @Throws(IOException::class) private fun processJournal() { - fileSystem.delete(journalFileTmp) + fileSystem.deleteIfExists(journalFileTmp) val i = lruEntries.values.iterator() while (i.hasNext()) { val entry = i.next() @@ -359,8 +383,8 @@ class DiskLruCache( } else { entry.currentEditor = null for (t in 0 until valueCount) { - fileSystem.delete(entry.cleanFiles[t]) - fileSystem.delete(entry.dirtyFiles[t]) + fileSystem.deleteIfExists(entry.cleanFiles[t]) + fileSystem.deleteIfExists(entry.dirtyFiles[t]) } i.remove() } @@ -375,32 +399,34 @@ class DiskLruCache( internal fun rebuildJournal() { journalWriter?.close() - fileSystem.sink(journalFileTmp).buffer().use { sink -> - sink.writeUtf8(MAGIC).writeByte('\n'.toInt()) - sink.writeUtf8(VERSION_1).writeByte('\n'.toInt()) - sink.writeDecimalLong(appVersion.toLong()).writeByte('\n'.toInt()) - sink.writeDecimalLong(valueCount.toLong()).writeByte('\n'.toInt()) - sink.writeByte('\n'.toInt()) + fileSystem.write(journalFileTmp) { + writeUtf8(MAGIC).writeByte('\n'.toInt()) + writeUtf8(VERSION_1).writeByte('\n'.toInt()) + writeDecimalLong(appVersion.toLong()).writeByte('\n'.toInt()) + writeDecimalLong(valueCount.toLong()).writeByte('\n'.toInt()) + writeByte('\n'.toInt()) for (entry in lruEntries.values) { if (entry.currentEditor != null) { - sink.writeUtf8(DIRTY).writeByte(' '.toInt()) - sink.writeUtf8(entry.key) - sink.writeByte('\n'.toInt()) + writeUtf8(DIRTY).writeByte(' '.toInt()) + writeUtf8(entry.key) + writeByte('\n'.toInt()) } else { - sink.writeUtf8(CLEAN).writeByte(' '.toInt()) - sink.writeUtf8(entry.key) - entry.writeLengths(sink) - sink.writeByte('\n'.toInt()) + writeUtf8(CLEAN).writeByte(' '.toInt()) + writeUtf8(entry.key) + entry.writeLengths(this) + writeByte('\n'.toInt()) } } } if (fileSystem.exists(journalFile)) { - fileSystem.rename(journalFile, journalFileBackup) + fileSystem.atomicMove(journalFile, journalFileBackup) + fileSystem.atomicMove(journalFileTmp, journalFile) + fileSystem.deleteIfExists(journalFileBackup) + } else { + fileSystem.atomicMove(journalFileTmp, journalFile) } - fileSystem.rename(journalFileTmp, journalFile) - fileSystem.delete(journalFileBackup) journalWriter = newJournalWriter() hasJournalErrors = false @@ -519,14 +545,15 @@ class DiskLruCache( if (success && !entry.zombie) { if (fileSystem.exists(dirty)) { val clean = entry.cleanFiles[i] - fileSystem.rename(dirty, clean) + fileSystem.atomicMove(dirty, clean) val oldLength = entry.lengths[i] - val newLength = fileSystem.size(clean) + // TODO check null behaviour + val newLength = fileSystem.metadata(clean).size ?: 0 entry.lengths[i] = newLength size = size - oldLength + newLength } } else { - fileSystem.delete(dirty) + fileSystem.deleteIfExists(dirty) } } @@ -613,7 +640,7 @@ class DiskLruCache( entry.currentEditor?.detach() // Prevent the edit from completing normally. for (i in 0 until valueCount) { - fileSystem.delete(entry.cleanFiles[i]) + fileSystem.deleteIfExists(entry.cleanFiles[i]) size -= entry.lengths[i] entry.lengths[i] = 0 } @@ -916,8 +943,8 @@ class DiskLruCache( /** Lengths of this entry's files. */ internal val lengths: LongArray = LongArray(valueCount) - internal val cleanFiles = mutableListOf() - internal val dirtyFiles = mutableListOf() + internal val cleanFiles = mutableListOf() + internal val dirtyFiles = mutableListOf() /** True if this entry has ever been published. */ internal var readable: Boolean = false @@ -946,9 +973,9 @@ class DiskLruCache( val truncateTo = fileBuilder.length for (i in 0 until valueCount) { fileBuilder.append(i) - cleanFiles += File(directory, fileBuilder.toString()) + cleanFiles += directory / fileBuilder.toString() fileBuilder.append(".tmp") - dirtyFiles += File(directory, fileBuilder.toString()) + dirtyFiles += directory / fileBuilder.toString() fileBuilder.setLength(truncateTo) } } diff --git a/okhttp/src/main/kotlin/okhttp3/internal/io/FileSystem.kt b/okhttp/src/main/kotlin/okhttp3/internal/io/FileSystem.kt deleted file mode 100644 index 8217ddb33..000000000 --- a/okhttp/src/main/kotlin/okhttp3/internal/io/FileSystem.kt +++ /dev/null @@ -1,149 +0,0 @@ -/* - * Copyright (C) 2015 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 okhttp3.internal.io - -import java.io.File -import java.io.FileNotFoundException -import java.io.IOException -import okio.Sink -import okio.Source -import okio.appendingSink -import okio.sink -import okio.source - -/** - * Access to read and write files on a hierarchical data store. Most callers should use the - * [SYSTEM] implementation, which uses the host machine's local file system. Alternate - * implementations may be used to inject faults (for testing) or to transform stored data (to add - * encryption, for example). - * - * All operations on a file system are racy. For example, guarding a call to [source] with - * [exists] does not guarantee that [FileNotFoundException] will not be thrown. The - * file may be moved between the two calls! - * - * This interface is less ambitious than [java.nio.file.FileSystem] introduced in Java 7. - * It lacks important features like file watching, metadata, permissions, and disk space - * information. In exchange for these limitations, this interface is easier to implement and works - * on all versions of Java and Android. - */ -interface FileSystem { - - companion object { - /** The host machine's local file system. */ - @JvmField - val SYSTEM: FileSystem = SystemFileSystem() - private class SystemFileSystem : FileSystem { - @Throws(FileNotFoundException::class) - override fun source(file: File): Source = file.source() - - @Throws(FileNotFoundException::class) - override fun sink(file: File): Sink { - return try { - file.sink() - } catch (_: FileNotFoundException) { - // Maybe the parent directory doesn't exist? Try creating it first. - file.parentFile.mkdirs() - file.sink() - } - } - - @Throws(FileNotFoundException::class) - override fun appendingSink(file: File): Sink { - return try { - file.appendingSink() - } catch (_: FileNotFoundException) { - // Maybe the parent directory doesn't exist? Try creating it first. - file.parentFile.mkdirs() - file.appendingSink() - } - } - - @Throws(IOException::class) - override fun delete(file: File) { - // If delete() fails, make sure it's because the file didn't exist! - if (!file.delete() && file.exists()) { - throw IOException("failed to delete $file") - } - } - - override fun exists(file: File): Boolean = file.exists() - - override fun size(file: File): Long = file.length() - - @Throws(IOException::class) - override fun rename(from: File, to: File) { - delete(to) - if (!from.renameTo(to)) { - throw IOException("failed to rename $from to $to") - } - } - - @Throws(IOException::class) - override fun deleteContents(directory: File) { - val files = directory.listFiles() ?: throw IOException("not a readable directory: $directory") - for (file in files) { - if (file.isDirectory) { - deleteContents(file) - } - if (!file.delete()) { - throw IOException("failed to delete $file") - } - } - } - - override fun toString() = "FileSystem.SYSTEM" - } - } - - /** Reads from [file]. */ - @Throws(FileNotFoundException::class) - fun source(file: File): Source - - /** - * Writes to [file], discarding any data already present. Creates parent directories if - * necessary. - */ - @Throws(FileNotFoundException::class) - fun sink(file: File): Sink - - /** - * Writes to [file], appending if data is already present. Creates parent directories if - * necessary. - */ - @Throws(FileNotFoundException::class) - fun appendingSink(file: File): Sink - - /** Deletes [file] if it exists. Throws if the file exists and cannot be deleted. */ - @Throws(IOException::class) - fun delete(file: File) - - /** Returns true if [file] exists on the file system. */ - fun exists(file: File): Boolean - - /** Returns the number of bytes stored in [file], or 0 if it does not exist. */ - fun size(file: File): Long - - /** Renames [from] to [to]. Throws if the file cannot be renamed. */ - @Throws(IOException::class) - fun rename(from: File, to: File) - - /** - * Recursively delete the contents of [directory]. Throws an IOException if any file could - * not be deleted, or if `dir` is not a readable directory. - */ - @Throws(IOException::class) - fun deleteContents(directory: File) -} diff --git a/okhttp/src/test/java/okhttp3/CacheCorruptionTest.kt b/okhttp/src/test/java/okhttp3/CacheCorruptionTest.kt index 801245037..4b06997f3 100644 --- a/okhttp/src/test/java/okhttp3/CacheCorruptionTest.kt +++ b/okhttp/src/test/java/okhttp3/CacheCorruptionTest.kt @@ -18,15 +18,17 @@ package okhttp3 import mockwebserver3.MockResponse import mockwebserver3.MockWebServer import okhttp3.internal.buildCache -import okhttp3.internal.io.InMemoryFileSystem +import okhttp3.okio.LoggingFilesystem import okhttp3.testing.PlatformRule import okhttp3.tls.internal.TlsUtil.localhost +import okio.ExperimentalFileSystem +import okio.Path.Companion.toPath +import okio.fakefilesystem.FakeFileSystem import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.RegisterExtension -import java.io.File import java.net.CookieManager import java.net.ResponseCache import java.text.DateFormat @@ -38,12 +40,20 @@ import java.util.concurrent.TimeUnit import javax.net.ssl.HostnameVerifier import javax.net.ssl.SSLSession +@OptIn(ExperimentalFileSystem::class) class CacheCorruptionTest( var server: MockWebServer ) { - @JvmField @RegisterExtension var fileSystem = InMemoryFileSystem() - @JvmField @RegisterExtension val clientTestRule = OkHttpClientTestRule() - @JvmField @RegisterExtension val platform = PlatformRule() + @OptIn(ExperimentalFileSystem::class) + var fileSystem = FakeFileSystem() + + @JvmField + @RegisterExtension + val clientTestRule = OkHttpClientTestRule() + + @JvmField + @RegisterExtension + val platform = PlatformRule() private val handshakeCertificates = localhost() private lateinit var client: OkHttpClient @@ -52,25 +62,29 @@ class CacheCorruptionTest( HostnameVerifier { _: String?, _: SSLSession? -> true } private val cookieManager = CookieManager() - @BeforeEach fun setUp() { + @BeforeEach + fun setUp() { platform.assumeNotOpenJSSE() platform.assumeNotBouncyCastle() server.protocolNegotiationEnabled = false - cache = buildCache(File("/cache/"), Int.MAX_VALUE.toLong(), fileSystem) + val loggingFileSystem = LoggingFilesystem(fileSystem) + cache = buildCache("/cache/".toPath(), Int.MAX_VALUE.toLong(), loggingFileSystem) client = clientTestRule.newClientBuilder() .cache(cache) .cookieJar(JavaNetCookieJar(cookieManager)) .build() } - @AfterEach fun tearDown() { + @AfterEach + fun tearDown() { ResponseCache.setDefault(null) if (this::cache.isInitialized) { cache.delete() } } - @Test fun corruptedCipher() { + @Test + fun corruptedCipher() { val response = testCorruptingCache { corruptMetadata { // mess with cipher suite @@ -86,7 +100,8 @@ class CacheCorruptionTest( assertThat(response.handshake?.cipherSuite?.javaName).startsWith("SLT_") } - @Test fun truncatedMetadataEntry() { + @Test + fun truncatedMetadataEntry() { val response = testCorruptingCache { corruptMetadata { // truncate metadata to 1/4 of length @@ -115,30 +130,41 @@ class CacheCorruptionTest( } private fun corruptMetadata(corruptor: (String) -> String) { - val metadataFile = fileSystem.files.keys.find { it.name.endsWith(".0") } - val metadataBuffer = fileSystem.files[metadataFile] + val metadataFile = fileSystem.allPaths.find { + it.name.endsWith(".0") + } - val contents = metadataBuffer!!.peek().readUtf8() + if (metadataFile != null) { + val contents = fileSystem.read(metadataFile) { + readUtf8() + } - metadataBuffer.clear() - metadataBuffer.writeUtf8(corruptor(contents)) + fileSystem.write(metadataFile) { + writeUtf8(corruptor(contents)) + } + } } private fun testCorruptingCache(corruptor: () -> Unit): Response { server.useHttps(handshakeCertificates.sslSocketFactory(), false) - server.enqueue(MockResponse() + server.enqueue( + MockResponse() .addHeader("Last-Modified: " + formatDate(-1, TimeUnit.HOURS)) .addHeader("Expires: " + formatDate(1, TimeUnit.HOURS)) - .setBody("ABC.1")) - server.enqueue(MockResponse() - .addHeader("Last-Modified: " + formatDate(-1, TimeUnit.HOURS)) - .addHeader("Expires: " + formatDate(1, TimeUnit.HOURS)) - .setBody("ABC.2")) + .setBody("ABC.1") + ) + server.enqueue( + MockResponse() + .addHeader("Last-Modified: " + formatDate(-1, TimeUnit.HOURS)) + .addHeader("Expires: " + formatDate(1, TimeUnit.HOURS)) + .setBody("ABC.2") + ) client = client.newBuilder() - .sslSocketFactory( - handshakeCertificates.sslSocketFactory(), handshakeCertificates.trustManager) - .hostnameVerifier(NULL_HOSTNAME_VERIFIER) - .build() + .sslSocketFactory( + handshakeCertificates.sslSocketFactory(), handshakeCertificates.trustManager + ) + .hostnameVerifier(NULL_HOSTNAME_VERIFIER) + .build() val request: Request = Request.Builder().url(server.url("/")).build() val response1: Response = client.newCall(request).execute() val bodySource = response1.body!!.source() diff --git a/okhttp/src/test/java/okhttp3/CacheTest.java b/okhttp/src/test/java/okhttp3/CacheTest.java index addeae6ca..614b2adf3 100644 --- a/okhttp/src/test/java/okhttp3/CacheTest.java +++ b/okhttp/src/test/java/okhttp3/CacheTest.java @@ -38,7 +38,6 @@ import mockwebserver3.MockResponse; import mockwebserver3.MockWebServer; import mockwebserver3.RecordedRequest; import okhttp3.internal.Internal; -import okhttp3.internal.io.InMemoryFileSystem; import okhttp3.internal.platform.Platform; import okhttp3.testing.PlatformRule; import okhttp3.tls.HandshakeCertificates; @@ -47,6 +46,8 @@ import okio.BufferedSink; import okio.BufferedSource; import okio.GzipSink; import okio.Okio; +import okio.Path; +import okio.fakefilesystem.FakeFileSystem; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Tag; @@ -64,7 +65,7 @@ import static org.junit.jupiter.api.Assertions.fail; public final class CacheTest { private static final HostnameVerifier NULL_HOSTNAME_VERIFIER = (name, session) -> true; - @RegisterExtension public InMemoryFileSystem fileSystem = new InMemoryFileSystem(); + public FakeFileSystem fileSystem = new FakeFileSystem(); @RegisterExtension public final OkHttpClientTestRule clientTestRule = new OkHttpClientTestRule(); @RegisterExtension public final PlatformRule platform = new PlatformRule(); @@ -83,7 +84,7 @@ public final class CacheTest { platform.assumeNotBouncyCastle(); server.setProtocolNegotiationEnabled(false); - cache = new Cache(new File("/cache/"), Integer.MAX_VALUE, fileSystem); + cache = new Cache(Path.get("/cache/"), Integer.MAX_VALUE, fileSystem); client = clientTestRule.newClientBuilder() .cache(cache) .cookieJar(new JavaNetCookieJar(cookieManager)) @@ -2061,10 +2062,11 @@ public final class CacheTest { + "2\n" + "\n" + "CLEAN " + urlKey + " " + entryMetadata.length() + " " + entryBody.length() + "\n"; - writeFile(cache.directory(), urlKey + ".0", entryMetadata); - writeFile(cache.directory(), urlKey + ".1", entryBody); - writeFile(cache.directory(), "journal", journalBody); - cache = new Cache(cache.directory(), Integer.MAX_VALUE, fileSystem); + fileSystem.createDirectory(cache.directoryPath()); + writeFile(cache.directoryPath(), urlKey + ".0", entryMetadata); + writeFile(cache.directoryPath(), urlKey + ".1", entryBody); + writeFile(cache.directoryPath(), "journal", journalBody); + cache = new Cache(Path.get(cache.directory().getPath()), Integer.MAX_VALUE, fileSystem); client = client.newBuilder() .cache(cache) .build(); @@ -2110,11 +2112,12 @@ public final class CacheTest { + "\n" + "DIRTY " + urlKey + "\n" + "CLEAN " + urlKey + " " + entryMetadata.length() + " " + entryBody.length() + "\n"; - writeFile(cache.directory(), urlKey + ".0", entryMetadata); - writeFile(cache.directory(), urlKey + ".1", entryBody); - writeFile(cache.directory(), "journal", journalBody); + fileSystem.createDirectory(cache.directoryPath()); + writeFile(cache.directoryPath(), urlKey + ".0", entryMetadata); + writeFile(cache.directoryPath(), urlKey + ".1", entryBody); + writeFile(cache.directoryPath(), "journal", journalBody); cache.close(); - cache = new Cache(cache.directory(), Integer.MAX_VALUE, fileSystem); + cache = new Cache(Path.get(cache.directory().getPath()), Integer.MAX_VALUE, fileSystem); client = client.newBuilder() .cache(cache) .build(); @@ -2160,11 +2163,12 @@ public final class CacheTest { + "\n" + "DIRTY " + urlKey + "\n" + "CLEAN " + urlKey + " " + entryMetadata.length() + " " + entryBody.length() + "\n"; - writeFile(cache.directory(), urlKey + ".0", entryMetadata); - writeFile(cache.directory(), urlKey + ".1", entryBody); - writeFile(cache.directory(), "journal", journalBody); + fileSystem.createDirectory(cache.directoryPath()); + writeFile(cache.directoryPath(), urlKey + ".0", entryMetadata); + writeFile(cache.directoryPath(), urlKey + ".1", entryBody); + writeFile(cache.directoryPath(), "journal", journalBody); cache.close(); - cache = new Cache(cache.directory(), Integer.MAX_VALUE, fileSystem); + cache = new Cache(Path.get(cache.directory().getPath()), Integer.MAX_VALUE, fileSystem); client = client.newBuilder() .cache(cache) .build(); @@ -2197,11 +2201,12 @@ public final class CacheTest { + "\n" + "DIRTY " + urlKey + "\n" + "CLEAN " + urlKey + " " + entryMetadata.length() + " " + entryBody.length() + "\n"; - writeFile(cache.directory(), urlKey + ".0", entryMetadata); - writeFile(cache.directory(), urlKey + ".1", entryBody); - writeFile(cache.directory(), "journal", journalBody); + fileSystem.createDirectory(cache.directoryPath()); + writeFile(cache.directoryPath(), urlKey + ".0", entryMetadata); + writeFile(cache.directoryPath(), urlKey + ".1", entryBody); + writeFile(cache.directoryPath(), "journal", journalBody); cache.close(); - cache = new Cache(cache.directory(), Integer.MAX_VALUE, fileSystem); + cache = new Cache(Path.get(cache.directory().getPath()), Integer.MAX_VALUE, fileSystem); client = client.newBuilder() .cache(cache) .build(); @@ -2496,8 +2501,8 @@ public final class CacheTest { return client.newCall(request).execute(); } - private void writeFile(File directory, String file, String content) throws IOException { - BufferedSink sink = Okio.buffer(fileSystem.sink(new File(directory, file))); + private void writeFile(Path directory, String file, String content) throws IOException { + BufferedSink sink = Okio.buffer(fileSystem.sink(directory.resolve(file))); sink.writeUtf8(content); sink.close(); } diff --git a/okhttp/src/test/java/okhttp3/CallTest.java b/okhttp/src/test/java/okhttp3/CallTest.java index fb312c054..aba11eb6b 100644 --- a/okhttp/src/test/java/okhttp3/CallTest.java +++ b/okhttp/src/test/java/okhttp3/CallTest.java @@ -15,7 +15,6 @@ */ package okhttp3; -import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; @@ -69,7 +68,7 @@ import okhttp3.internal.DoubleInetAddressDns; import okhttp3.internal.RecordingOkAuthenticator; import okhttp3.internal.Util; import okhttp3.internal.http.RecordingProxySelector; -import okhttp3.internal.io.InMemoryFileSystem; +import okhttp3.okio.LoggingFilesystem; import okhttp3.testing.Flaky; import okhttp3.testing.PlatformRule; import okhttp3.tls.HandshakeCertificates; @@ -80,6 +79,8 @@ import okio.BufferedSource; import okio.ForwardingSource; import okio.GzipSink; import okio.Okio; +import okio.Path; +import okio.fakefilesystem.FakeFileSystem; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Disabled; @@ -104,7 +105,7 @@ import static org.junit.jupiter.api.Assumptions.assumeFalse; @Timeout(30) public final class CallTest { @RegisterExtension final PlatformRule platform = new PlatformRule(); - @RegisterExtension final InMemoryFileSystem fileSystem = new InMemoryFileSystem(); + final FakeFileSystem fileSystem = new FakeFileSystem(); @RegisterExtension final OkHttpClientTestRule clientTestRule = new OkHttpClientTestRule(); @RegisterExtension final TestLogHandler testLogHandler = new TestLogHandler(OkHttpClient.class); @@ -116,7 +117,7 @@ public final class CallTest { .eventListenerFactory(clientTestRule.wrap(listener)) .build(); private RecordingCallback callback = new RecordingCallback(); - private Cache cache = new Cache(new File("/cache/"), Integer.MAX_VALUE, fileSystem); + private Cache cache = new Cache(Path.get("/cache"), Integer.MAX_VALUE, new LoggingFilesystem(fileSystem)); public CallTest(MockWebServer server, MockWebServer server2) { this.server = server; @@ -129,7 +130,8 @@ public final class CallTest { } @AfterEach public void tearDown() throws Exception { - cache.delete(); + cache.close(); + fileSystem.checkNoOpenFiles(); } @Test public void get() throws Exception { diff --git a/okhttp/src/test/java/okhttp3/internal/cache/DiskLruCacheTest.kt b/okhttp/src/test/java/okhttp3/internal/cache/DiskLruCacheTest.kt index 98f807790..35212c1ea 100644 --- a/okhttp/src/test/java/okhttp3/internal/cache/DiskLruCacheTest.kt +++ b/okhttp/src/test/java/okhttp3/internal/cache/DiskLruCacheTest.kt @@ -21,11 +21,13 @@ import okhttp3.internal.cache.DiskLruCache.Editor import okhttp3.internal.cache.DiskLruCache.Snapshot import okhttp3.internal.concurrent.TaskFaker import okhttp3.internal.io.FaultyFileSystem -import okhttp3.internal.io.FileSystem -import okhttp3.internal.io.InMemoryFileSystem -import okhttp3.internal.io.WindowsFileSystem +import okio.ExperimentalFileSystem +import okio.FileSystem +import okio.Path +import okio.Path.Companion.toPath import okio.Source import okio.buffer +import okio.fakefilesystem.FakeFileSystem import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.Assumptions @@ -41,23 +43,26 @@ import java.io.IOException import java.util.ArrayDeque import java.util.NoSuchElementException -class FileSystemParamProvider: SimpleProvider() { +@OptIn(ExperimentalFileSystem::class) +class FilesystemParamProvider: SimpleProvider() { override fun arguments() = listOf( + FakeFileSystem() to false, FileSystem.SYSTEM to TestUtil.windows, - WindowsFileSystem(InMemoryFileSystem()) to true, - InMemoryFileSystem() to false + FakeFileSystem(windowsLimitations = true) to true ) } @Timeout(60) @Tag("Slow") +@OptIn(ExperimentalFileSystem::class) class DiskLruCacheTest { - private lateinit var fileSystem: FaultyFileSystem + private lateinit var filesystem: FaultyFileSystem private var windows: Boolean = false - @TempDir lateinit var cacheDir: File + @TempDir lateinit var cacheDirFile: File + lateinit var cacheDir: Path private val appVersion = 100 - private lateinit var journalFile: File - private lateinit var journalBkpFile: File + private lateinit var journalFile: Path + private lateinit var journalBkpFile: Path private val taskFaker = TaskFaker() private val taskRunner = taskFaker.taskRunner private lateinit var cache: DiskLruCache @@ -68,18 +73,22 @@ class DiskLruCacheTest { } private fun createNewCacheWithSize(maxSize: Int) { - cache = DiskLruCache(fileSystem, cacheDir, appVersion, 2, maxSize.toLong(), taskRunner) + cache = DiskLruCache(filesystem, cacheDir, appVersion, 2, maxSize.toLong(), taskRunner) synchronized(cache) { cache.initialize() } toClose.add(cache) } - fun setUp(baseFileSystem: FileSystem, windows: Boolean) { - this.fileSystem = FaultyFileSystem(baseFileSystem) + fun setUp(baseFilesystem: FileSystem, windows: Boolean) { + this.cacheDir = + if (baseFilesystem is FakeFileSystem) "/cache".toPath() else cacheDirFile.path.toPath() + this.filesystem = FaultyFileSystem(baseFilesystem) this.windows = windows - fileSystem.deleteContents(cacheDir) - journalFile = File(cacheDir, DiskLruCache.JOURNAL_FILE) - journalBkpFile = File(cacheDir, DiskLruCache.JOURNAL_FILE_BACKUP) + if (filesystem.exists(cacheDir)) { + filesystem.deleteRecursively(cacheDir) + } + journalFile = cacheDir / DiskLruCache.JOURNAL_FILE + journalBkpFile = cacheDir / DiskLruCache.JOURNAL_FILE_BACKUP createNewCache() } @@ -90,7 +99,7 @@ class DiskLruCacheTest { } @ParameterizedTest - @ArgumentsSource(FileSystemParamProvider::class) + @ArgumentsSource(FilesystemParamProvider::class) fun emptyCache(parameters: Pair) { setUp(parameters.first, parameters.second) cache.close() @@ -98,7 +107,7 @@ class DiskLruCacheTest { } @ParameterizedTest - @ArgumentsSource(FileSystemParamProvider::class) + @ArgumentsSource(FilesystemParamProvider::class) fun recoverFromInitializationFailure(parameters: Pair) { setUp(parameters.first, parameters.second) // Add an uncommitted entry. This will get detected on initialization, and the cache will @@ -109,10 +118,10 @@ class DiskLruCacheTest { it.writeUtf8("Hello") } - // Simulate a severe filesystem failure on the first initialization. - fileSystem.setFaultyDelete(File(cacheDir, "k1.0.tmp"), true) - fileSystem.setFaultyDelete(cacheDir, true) - cache = DiskLruCache(fileSystem, cacheDir, appVersion, 2, Int.MAX_VALUE.toLong(), taskRunner) + // Simulate a severe Filesystem failure on the first initialization. + filesystem.setFaultyDelete(cacheDir / "k1.0.tmp", true) + filesystem.setFaultyDelete(cacheDir, true) + cache = DiskLruCache(filesystem, cacheDir, appVersion, 2, Int.MAX_VALUE.toLong(), taskRunner) toClose.add(cache) try { cache["k1"] @@ -121,14 +130,14 @@ class DiskLruCacheTest { } // Now let it operate normally. - fileSystem.setFaultyDelete(File(cacheDir, "k1.0.tmp"), false) - fileSystem.setFaultyDelete(cacheDir, false) + filesystem.setFaultyDelete(cacheDir / "k1.0.tmp", false) + filesystem.setFaultyDelete(cacheDir, false) val snapshot = cache["k1"] assertThat(snapshot).isNull() } @ParameterizedTest - @ArgumentsSource(FileSystemParamProvider::class) + @ArgumentsSource(FilesystemParamProvider::class) fun validateKey(parameters: Pair) { setUp(parameters.first, parameters.second) var key: String? = null @@ -191,7 +200,7 @@ class DiskLruCacheTest { } @ParameterizedTest - @ArgumentsSource(FileSystemParamProvider::class) + @ArgumentsSource(FilesystemParamProvider::class) fun writeAndReadEntry(parameters: Pair) { setUp(parameters.first, parameters.second) val creator = cache.edit("k1")!! @@ -206,7 +215,7 @@ class DiskLruCacheTest { } @ParameterizedTest - @ArgumentsSource(FileSystemParamProvider::class) + @ArgumentsSource(FilesystemParamProvider::class) fun readAndWriteEntryAcrossCacheOpenAndClose(parameters: Pair) { setUp(parameters.first, parameters.second) val creator = cache.edit("k1")!! @@ -222,7 +231,7 @@ class DiskLruCacheTest { } @ParameterizedTest - @ArgumentsSource(FileSystemParamProvider::class) + @ArgumentsSource(FilesystemParamProvider::class) fun readAndWriteEntryWithoutProperClose(parameters: Pair) { setUp(parameters.first, parameters.second) val creator = cache.edit("k1")!! @@ -239,7 +248,7 @@ class DiskLruCacheTest { } @ParameterizedTest - @ArgumentsSource(FileSystemParamProvider::class) + @ArgumentsSource(FilesystemParamProvider::class) fun journalWithEditAndPublish(parameters: Pair) { setUp(parameters.first, parameters.second) val creator = cache.edit("k1")!! @@ -252,7 +261,7 @@ class DiskLruCacheTest { } @ParameterizedTest - @ArgumentsSource(FileSystemParamProvider::class) + @ArgumentsSource(FilesystemParamProvider::class) fun revertedNewFileIsRemoveInJournal(parameters: Pair) { setUp(parameters.first, parameters.second) val creator = cache.edit("k1")!! @@ -266,7 +275,7 @@ class DiskLruCacheTest { /** On Windows we have to wait until the edit is committed before we can delete its files. */ @ParameterizedTest - @ArgumentsSource(FileSystemParamProvider::class) + @ArgumentsSource(FilesystemParamProvider::class) fun `unterminated edit is reverted on cache close`(parameters: Pair) { setUp(parameters.first, parameters.second) val editor = cache.edit("k1")!! @@ -280,7 +289,7 @@ class DiskLruCacheTest { } @ParameterizedTest - @ArgumentsSource(FileSystemParamProvider::class) + @ArgumentsSource(FilesystemParamProvider::class) fun journalDoesNotIncludeReadOfYetUnpublishedValue(parameters: Pair) { setUp(parameters.first, parameters.second) val creator = cache.edit("k1")!! @@ -293,7 +302,7 @@ class DiskLruCacheTest { } @ParameterizedTest - @ArgumentsSource(FileSystemParamProvider::class) + @ArgumentsSource(FilesystemParamProvider::class) fun journalWithEditAndPublishAndRead(parameters: Pair) { setUp(parameters.first, parameters.second) val k1Creator = cache.edit("k1")!! @@ -311,7 +320,7 @@ class DiskLruCacheTest { } @ParameterizedTest - @ArgumentsSource(FileSystemParamProvider::class) + @ArgumentsSource(FilesystemParamProvider::class) fun cannotOperateOnEditAfterPublish(parameters: Pair) { setUp(parameters.first, parameters.second) val editor = cache.edit("k1")!! @@ -322,7 +331,7 @@ class DiskLruCacheTest { } @ParameterizedTest - @ArgumentsSource(FileSystemParamProvider::class) + @ArgumentsSource(FilesystemParamProvider::class) fun cannotOperateOnEditAfterRevert(parameters: Pair) { setUp(parameters.first, parameters.second) val editor = cache.edit("k1")!! @@ -333,7 +342,7 @@ class DiskLruCacheTest { } @ParameterizedTest - @ArgumentsSource(FileSystemParamProvider::class) + @ArgumentsSource(FilesystemParamProvider::class) fun explicitRemoveAppliedToDiskImmediately(parameters: Pair) { setUp(parameters.first, parameters.second) val editor = cache.edit("k1")!! @@ -343,11 +352,11 @@ class DiskLruCacheTest { val k1 = getCleanFile("k1", 0) assertThat(readFile(k1)).isEqualTo("ABC") cache.remove("k1") - assertThat(fileSystem.exists(k1)).isFalse() + assertThat(filesystem.exists(k1)).isFalse() } @ParameterizedTest - @ArgumentsSource(FileSystemParamProvider::class) + @ArgumentsSource(FilesystemParamProvider::class) fun removePreventsActiveEditFromStoringAValue(parameters: Pair) { setUp(parameters.first, parameters.second) set("a", "a", "a") @@ -364,7 +373,7 @@ class DiskLruCacheTest { * the same key can see different data. */ @ParameterizedTest - @ArgumentsSource(FileSystemParamProvider::class) + @ArgumentsSource(FilesystemParamProvider::class) fun readAndWriteOverlapsMaintainConsistency(parameters: Pair) { setUp(parameters.first, parameters.second) Assumptions.assumeFalse(windows) // Can't edit while a read is in progress. @@ -396,7 +405,7 @@ class DiskLruCacheTest { } @ParameterizedTest - @ArgumentsSource(FileSystemParamProvider::class) + @ArgumentsSource(FilesystemParamProvider::class) fun openWithDirtyKeyDeletesAllFilesForThatKey(parameters: Pair) { setUp(parameters.first, parameters.second) cache.close() @@ -410,15 +419,15 @@ class DiskLruCacheTest { writeFile(dirtyFile1, "D") createJournal("CLEAN k1 1 1", "DIRTY k1") createNewCache() - assertThat(fileSystem.exists(cleanFile0)).isFalse() - assertThat(fileSystem.exists(cleanFile1)).isFalse() - assertThat(fileSystem.exists(dirtyFile0)).isFalse() - assertThat(fileSystem.exists(dirtyFile1)).isFalse() + assertThat(filesystem.exists(cleanFile0)).isFalse() + assertThat(filesystem.exists(cleanFile1)).isFalse() + assertThat(filesystem.exists(dirtyFile0)).isFalse() + assertThat(filesystem.exists(dirtyFile1)).isFalse() assertThat(cache["k1"]).isNull() } @ParameterizedTest - @ArgumentsSource(FileSystemParamProvider::class) + @ArgumentsSource(FilesystemParamProvider::class) fun openWithInvalidVersionClearsDirectory(parameters: Pair) { setUp(parameters.first, parameters.second) cache.close() @@ -429,7 +438,7 @@ class DiskLruCacheTest { } @ParameterizedTest - @ArgumentsSource(FileSystemParamProvider::class) + @ArgumentsSource(FilesystemParamProvider::class) fun openWithInvalidAppVersionClearsDirectory(parameters: Pair) { setUp(parameters.first, parameters.second) cache.close() @@ -440,7 +449,7 @@ class DiskLruCacheTest { } @ParameterizedTest - @ArgumentsSource(FileSystemParamProvider::class) + @ArgumentsSource(FilesystemParamProvider::class) fun openWithInvalidValueCountClearsDirectory(parameters: Pair) { setUp(parameters.first, parameters.second) cache.close() @@ -451,7 +460,7 @@ class DiskLruCacheTest { } @ParameterizedTest - @ArgumentsSource(FileSystemParamProvider::class) + @ArgumentsSource(FilesystemParamProvider::class) fun openWithInvalidBlankLineClearsDirectory(parameters: Pair) { setUp(parameters.first, parameters.second) cache.close() @@ -462,7 +471,7 @@ class DiskLruCacheTest { } @ParameterizedTest - @ArgumentsSource(FileSystemParamProvider::class) + @ArgumentsSource(FilesystemParamProvider::class) fun openWithInvalidJournalLineClearsDirectory(parameters: Pair) { setUp(parameters.first, parameters.second) cache.close() @@ -474,7 +483,7 @@ class DiskLruCacheTest { } @ParameterizedTest - @ArgumentsSource(FileSystemParamProvider::class) + @ArgumentsSource(FilesystemParamProvider::class) fun openWithInvalidFileSizeClearsDirectory(parameters: Pair) { setUp(parameters.first, parameters.second) cache.close() @@ -486,14 +495,14 @@ class DiskLruCacheTest { } @ParameterizedTest - @ArgumentsSource(FileSystemParamProvider::class) + @ArgumentsSource(FilesystemParamProvider::class) fun openWithTruncatedLineDiscardsThatLine(parameters: Pair) { setUp(parameters.first, parameters.second) cache.close() writeFile(getCleanFile("k1", 0), "A") writeFile(getCleanFile("k1", 1), "B") - fileSystem.sink(journalFile).buffer().use { - it.writeUtf8( + filesystem.write(journalFile) { + writeUtf8( """ |${DiskLruCache.MAGIC} |${DiskLruCache.VERSION_1} @@ -514,7 +523,7 @@ class DiskLruCacheTest { } @ParameterizedTest - @ArgumentsSource(FileSystemParamProvider::class) + @ArgumentsSource(FilesystemParamProvider::class) fun openWithTooManyFileSizesClearsDirectory(parameters: Pair) { setUp(parameters.first, parameters.second) cache.close() @@ -526,7 +535,7 @@ class DiskLruCacheTest { } @ParameterizedTest - @ArgumentsSource(FileSystemParamProvider::class) + @ArgumentsSource(FilesystemParamProvider::class) fun keyWithSpaceNotPermitted(parameters: Pair) { setUp(parameters.first, parameters.second) try { @@ -537,7 +546,7 @@ class DiskLruCacheTest { } @ParameterizedTest - @ArgumentsSource(FileSystemParamProvider::class) + @ArgumentsSource(FilesystemParamProvider::class) fun keyWithNewlineNotPermitted(parameters: Pair) { setUp(parameters.first, parameters.second) try { @@ -548,7 +557,7 @@ class DiskLruCacheTest { } @ParameterizedTest - @ArgumentsSource(FileSystemParamProvider::class) + @ArgumentsSource(FilesystemParamProvider::class) fun keyWithCarriageReturnNotPermitted(parameters: Pair) { setUp(parameters.first, parameters.second) try { @@ -559,7 +568,7 @@ class DiskLruCacheTest { } @ParameterizedTest - @ArgumentsSource(FileSystemParamProvider::class) + @ArgumentsSource(FilesystemParamProvider::class) fun createNewEntryWithTooFewValuesFails(parameters: Pair) { setUp(parameters.first, parameters.second) val creator = cache.edit("k1")!! @@ -569,10 +578,10 @@ class DiskLruCacheTest { fail("") } catch (_: IllegalStateException) { } - assertThat(fileSystem.exists(getCleanFile("k1", 0))).isFalse() - assertThat(fileSystem.exists(getCleanFile("k1", 1))).isFalse() - assertThat(fileSystem.exists(getDirtyFile("k1", 0))).isFalse() - assertThat(fileSystem.exists(getDirtyFile("k1", 1))).isFalse() + assertThat(filesystem.exists(getCleanFile("k1", 0))).isFalse() + assertThat(filesystem.exists(getCleanFile("k1", 1))).isFalse() + assertThat(filesystem.exists(getDirtyFile("k1", 0))).isFalse() + assertThat(filesystem.exists(getDirtyFile("k1", 1))).isFalse() assertThat(cache["k1"]).isNull() val creator2 = cache.edit("k1")!! creator2.setString(0, "B") @@ -581,21 +590,21 @@ class DiskLruCacheTest { } @ParameterizedTest - @ArgumentsSource(FileSystemParamProvider::class) + @ArgumentsSource(FilesystemParamProvider::class) fun revertWithTooFewValues(parameters: Pair) { setUp(parameters.first, parameters.second) val creator = cache.edit("k1")!! creator.setString(1, "A") creator.abort() - assertThat(fileSystem.exists(getCleanFile("k1", 0))).isFalse() - assertThat(fileSystem.exists(getCleanFile("k1", 1))).isFalse() - assertThat(fileSystem.exists(getDirtyFile("k1", 0))).isFalse() - assertThat(fileSystem.exists(getDirtyFile("k1", 1))).isFalse() + assertThat(filesystem.exists(getCleanFile("k1", 0))).isFalse() + assertThat(filesystem.exists(getCleanFile("k1", 1))).isFalse() + assertThat(filesystem.exists(getDirtyFile("k1", 0))).isFalse() + assertThat(filesystem.exists(getDirtyFile("k1", 1))).isFalse() assertThat(cache["k1"]).isNull() } @ParameterizedTest - @ArgumentsSource(FileSystemParamProvider::class) + @ArgumentsSource(FilesystemParamProvider::class) fun updateExistingEntryWithTooFewValuesReusesPreviousValues(parameters: Pair) { setUp(parameters.first, parameters.second) val creator = cache.edit("k1")!! @@ -612,7 +621,7 @@ class DiskLruCacheTest { } @ParameterizedTest - @ArgumentsSource(FileSystemParamProvider::class) + @ArgumentsSource(FilesystemParamProvider::class) fun growMaxSize(parameters: Pair) { setUp(parameters.first, parameters.second) cache.close() @@ -625,7 +634,7 @@ class DiskLruCacheTest { } @ParameterizedTest - @ArgumentsSource(FileSystemParamProvider::class) + @ArgumentsSource(FilesystemParamProvider::class) fun shrinkMaxSizeEvicts(parameters: Pair) { setUp(parameters.first, parameters.second) cache.close() @@ -638,7 +647,7 @@ class DiskLruCacheTest { } @ParameterizedTest - @ArgumentsSource(FileSystemParamProvider::class) + @ArgumentsSource(FilesystemParamProvider::class) fun evictOnInsert(parameters: Pair) { setUp(parameters.first, parameters.second) cache.close() @@ -676,7 +685,7 @@ class DiskLruCacheTest { } @ParameterizedTest - @ArgumentsSource(FileSystemParamProvider::class) + @ArgumentsSource(FilesystemParamProvider::class) fun evictOnUpdate(parameters: Pair) { setUp(parameters.first, parameters.second) cache.close() @@ -696,7 +705,7 @@ class DiskLruCacheTest { } @ParameterizedTest - @ArgumentsSource(FileSystemParamProvider::class) + @ArgumentsSource(FilesystemParamProvider::class) fun evictionHonorsLruFromCurrentSession(parameters: Pair) { setUp(parameters.first, parameters.second) cache.close() @@ -723,7 +732,7 @@ class DiskLruCacheTest { } @ParameterizedTest - @ArgumentsSource(FileSystemParamProvider::class) + @ArgumentsSource(FilesystemParamProvider::class) fun evictionHonorsLruFromPreviousSession(parameters: Pair) { setUp(parameters.first, parameters.second) set("a", "a", "a") @@ -749,7 +758,7 @@ class DiskLruCacheTest { } @ParameterizedTest - @ArgumentsSource(FileSystemParamProvider::class) + @ArgumentsSource(FilesystemParamProvider::class) fun cacheSingleEntryOfSizeGreaterThanMaxSize(parameters: Pair) { setUp(parameters.first, parameters.second) cache.close() @@ -760,7 +769,7 @@ class DiskLruCacheTest { } @ParameterizedTest - @ArgumentsSource(FileSystemParamProvider::class) + @ArgumentsSource(FilesystemParamProvider::class) fun cacheSingleValueOfSizeGreaterThanMaxSize(parameters: Pair) { setUp(parameters.first, parameters.second) cache.close() @@ -771,36 +780,36 @@ class DiskLruCacheTest { } @ParameterizedTest - @ArgumentsSource(FileSystemParamProvider::class) + @ArgumentsSource(FilesystemParamProvider::class) fun constructorDoesNotAllowZeroCacheSize(parameters: Pair) { setUp(parameters.first, parameters.second) try { - DiskLruCache(fileSystem, cacheDir, appVersion, 2, 0, taskRunner) + DiskLruCache(filesystem, cacheDir, appVersion, 2, 0, taskRunner) fail("") } catch (_: IllegalArgumentException) { } } @ParameterizedTest - @ArgumentsSource(FileSystemParamProvider::class) + @ArgumentsSource(FilesystemParamProvider::class) fun constructorDoesNotAllowZeroValuesPerEntry(parameters: Pair) { setUp(parameters.first, parameters.second) try { - DiskLruCache(fileSystem, cacheDir, appVersion, 0, 10, taskRunner) + DiskLruCache(filesystem, cacheDir, appVersion, 0, 10, taskRunner) fail("") } catch (_: IllegalArgumentException) { } } @ParameterizedTest - @ArgumentsSource(FileSystemParamProvider::class) + @ArgumentsSource(FilesystemParamProvider::class) fun removeAbsentElement(parameters: Pair) { setUp(parameters.first, parameters.second) cache.remove("a") } @ParameterizedTest - @ArgumentsSource(FileSystemParamProvider::class) + @ArgumentsSource(FilesystemParamProvider::class) fun readingTheSameStreamMultipleTimes(parameters: Pair) { setUp(parameters.first, parameters.second) set("a", "a", "b") @@ -810,7 +819,7 @@ class DiskLruCacheTest { } @ParameterizedTest - @ArgumentsSource(FileSystemParamProvider::class) + @ArgumentsSource(FilesystemParamProvider::class) fun rebuildJournalOnRepeatedReads(parameters: Pair) { setUp(parameters.first, parameters.second) set("a", "a", "a") @@ -822,7 +831,7 @@ class DiskLruCacheTest { } @ParameterizedTest - @ArgumentsSource(FileSystemParamProvider::class) + @ArgumentsSource(FilesystemParamProvider::class) fun rebuildJournalOnRepeatedEdits(parameters: Pair) { setUp(parameters.first, parameters.second) while (taskFaker.isIdle()) { @@ -838,7 +847,7 @@ class DiskLruCacheTest { /** @see [Issue 28](https://github.com/JakeWharton/DiskLruCache/issues/28) */ @ParameterizedTest - @ArgumentsSource(FileSystemParamProvider::class) + @ArgumentsSource(FilesystemParamProvider::class) fun rebuildJournalOnRepeatedReadsWithOpenAndClose(parameters: Pair) { setUp(parameters.first, parameters.second) set("a", "a", "a") @@ -853,7 +862,7 @@ class DiskLruCacheTest { /** @see [Issue 28](https://github.com/JakeWharton/DiskLruCache/issues/28) */ @ParameterizedTest - @ArgumentsSource(FileSystemParamProvider::class) + @ArgumentsSource(FilesystemParamProvider::class) fun rebuildJournalOnRepeatedEditsWithOpenAndClose(parameters: Pair) { setUp(parameters.first, parameters.second) while (taskFaker.isIdle()) { @@ -865,7 +874,7 @@ class DiskLruCacheTest { } @ParameterizedTest - @ArgumentsSource(FileSystemParamProvider::class) + @ArgumentsSource(FilesystemParamProvider::class) fun rebuildJournalFailurePreventsEditors(parameters: Pair) { setUp(parameters.first, parameters.second) while (taskFaker.isIdle()) { @@ -874,7 +883,7 @@ class DiskLruCacheTest { } // Cause the rebuild action to fail. - fileSystem.setFaultyRename(File(cacheDir, DiskLruCache.JOURNAL_FILE_BACKUP), true) + filesystem.setFaultyRename(cacheDir / DiskLruCache.JOURNAL_FILE_BACKUP, true) taskFaker.runNextTask() // Don't allow edits under any circumstances. @@ -886,7 +895,7 @@ class DiskLruCacheTest { } @ParameterizedTest - @ArgumentsSource(FileSystemParamProvider::class) + @ArgumentsSource(FilesystemParamProvider::class) fun rebuildJournalFailureIsRetried(parameters: Pair) { setUp(parameters.first, parameters.second) while (taskFaker.isIdle()) { @@ -895,7 +904,7 @@ class DiskLruCacheTest { } // Cause the rebuild action to fail. - fileSystem.setFaultyRename(File(cacheDir, DiskLruCache.JOURNAL_FILE_BACKUP), true) + filesystem.setFaultyRename(cacheDir / DiskLruCache.JOURNAL_FILE_BACKUP, true) taskFaker.runNextTask() // The rebuild is retried on cache hits and on cache edits. @@ -909,13 +918,13 @@ class DiskLruCacheTest { assertThat(taskFaker.isIdle()).isFalse() // Let the rebuild complete successfully. - fileSystem.setFaultyRename(File(cacheDir, DiskLruCache.JOURNAL_FILE_BACKUP), false) + filesystem.setFaultyRename(cacheDir / DiskLruCache.JOURNAL_FILE_BACKUP, false) taskFaker.runNextTask() assertJournalEquals("CLEAN a 1 1", "CLEAN b 1 1") } @ParameterizedTest - @ArgumentsSource(FileSystemParamProvider::class) + @ArgumentsSource(FilesystemParamProvider::class) fun rebuildJournalFailureWithInFlightEditors(parameters: Pair) { setUp(parameters.first, parameters.second) while (taskFaker.isIdle()) { @@ -927,7 +936,7 @@ class DiskLruCacheTest { cache.edit("e") // Grab an editor, but don't do anything with it. // Cause the rebuild action to fail. - fileSystem.setFaultyRename(File(cacheDir, DiskLruCache.JOURNAL_FILE_BACKUP), true) + filesystem.setFaultyRename(cacheDir / DiskLruCache.JOURNAL_FILE_BACKUP, true) taskFaker.runNextTask() // In-flight editors can commit and have their values retained. @@ -938,13 +947,13 @@ class DiskLruCacheTest { abortEditor.abort() // Let the rebuild complete successfully. - fileSystem.setFaultyRename(File(cacheDir, DiskLruCache.JOURNAL_FILE_BACKUP), false) + filesystem.setFaultyRename(cacheDir / DiskLruCache.JOURNAL_FILE_BACKUP, false) taskFaker.runNextTask() assertJournalEquals("CLEAN a 1 1", "CLEAN b 1 1", "DIRTY e", "CLEAN c 1 1") } @ParameterizedTest - @ArgumentsSource(FileSystemParamProvider::class) + @ArgumentsSource(FilesystemParamProvider::class) fun rebuildJournalFailureWithEditorsInFlightThenClose(parameters: Pair) { setUp(parameters.first, parameters.second) while (taskFaker.isIdle()) { @@ -956,7 +965,7 @@ class DiskLruCacheTest { cache.edit("e") // Grab an editor, but don't do anything with it. // Cause the rebuild action to fail. - fileSystem.setFaultyRename(File(cacheDir, DiskLruCache.JOURNAL_FILE_BACKUP), true) + filesystem.setFaultyRename(cacheDir / DiskLruCache.JOURNAL_FILE_BACKUP, true) taskFaker.runNextTask() commitEditor.setString(0, "c") commitEditor.setString(1, "c") @@ -976,7 +985,7 @@ class DiskLruCacheTest { } @ParameterizedTest - @ArgumentsSource(FileSystemParamProvider::class) + @ArgumentsSource(FilesystemParamProvider::class) fun rebuildJournalFailureAllowsRemovals(parameters: Pair) { setUp(parameters.first, parameters.second) while (taskFaker.isIdle()) { @@ -985,19 +994,19 @@ class DiskLruCacheTest { } // Cause the rebuild action to fail. - fileSystem.setFaultyRename(File(cacheDir, DiskLruCache.JOURNAL_FILE_BACKUP), true) + filesystem.setFaultyRename(cacheDir / DiskLruCache.JOURNAL_FILE_BACKUP, true) taskFaker.runNextTask() assertThat(cache.remove("a")).isTrue() assertAbsent("a") // Let the rebuild complete successfully. - fileSystem.setFaultyRename(File(cacheDir, DiskLruCache.JOURNAL_FILE_BACKUP), false) + filesystem.setFaultyRename(cacheDir / DiskLruCache.JOURNAL_FILE_BACKUP, false) taskFaker.runNextTask() assertJournalEquals("CLEAN b 1 1") } @ParameterizedTest - @ArgumentsSource(FileSystemParamProvider::class) + @ArgumentsSource(FilesystemParamProvider::class) fun rebuildJournalFailureWithRemovalThenClose(parameters: Pair) { setUp(parameters.first, parameters.second) while (taskFaker.isIdle()) { @@ -1006,7 +1015,7 @@ class DiskLruCacheTest { } // Cause the rebuild action to fail. - fileSystem.setFaultyRename(File(cacheDir, DiskLruCache.JOURNAL_FILE_BACKUP), true) + filesystem.setFaultyRename(cacheDir / DiskLruCache.JOURNAL_FILE_BACKUP, true) taskFaker.runNextTask() assertThat(cache.remove("a")).isTrue() assertAbsent("a") @@ -1022,7 +1031,7 @@ class DiskLruCacheTest { } @ParameterizedTest - @ArgumentsSource(FileSystemParamProvider::class) + @ArgumentsSource(FilesystemParamProvider::class) fun rebuildJournalFailureAllowsEvictAll(parameters: Pair) { setUp(parameters.first, parameters.second) while (taskFaker.isIdle()) { @@ -1031,7 +1040,7 @@ class DiskLruCacheTest { } // Cause the rebuild action to fail. - fileSystem.setFaultyRename(File(cacheDir, DiskLruCache.JOURNAL_FILE_BACKUP), true) + filesystem.setFaultyRename(cacheDir / DiskLruCache.JOURNAL_FILE_BACKUP, true) taskFaker.runNextTask() cache.evictAll() assertThat(cache.size()).isEqualTo(0) @@ -1050,7 +1059,7 @@ class DiskLruCacheTest { } @ParameterizedTest - @ArgumentsSource(FileSystemParamProvider::class) + @ArgumentsSource(FilesystemParamProvider::class) fun rebuildJournalFailureWithCacheTrim(parameters: Pair) { setUp(parameters.first, parameters.second) while (taskFaker.isIdle()) { @@ -1059,8 +1068,8 @@ class DiskLruCacheTest { } // Cause the rebuild action to fail. - fileSystem.setFaultyRename( - File(cacheDir, DiskLruCache.JOURNAL_FILE_BACKUP), true + filesystem.setFaultyRename( + cacheDir / DiskLruCache.JOURNAL_FILE_BACKUP, true ) taskFaker.runNextTask() @@ -1072,7 +1081,7 @@ class DiskLruCacheTest { } @ParameterizedTest - @ArgumentsSource(FileSystemParamProvider::class) + @ArgumentsSource(FilesystemParamProvider::class) fun restoreBackupFile(parameters: Pair) { setUp(parameters.first, parameters.second) val creator = cache.edit("k1")!! @@ -1080,18 +1089,18 @@ class DiskLruCacheTest { creator.setString(1, "DE") creator.commit() cache.close() - fileSystem.rename(journalFile, journalBkpFile) - assertThat(fileSystem.exists(journalFile)).isFalse() + filesystem.atomicMove(journalFile, journalBkpFile) + assertThat(filesystem.exists(journalFile)).isFalse() createNewCache() val snapshot = cache["k1"]!! snapshot.assertValue(0, "ABC") snapshot.assertValue(1, "DE") - assertThat(fileSystem.exists(journalBkpFile)).isFalse() - assertThat(fileSystem.exists(journalFile)).isTrue() + assertThat(filesystem.exists(journalBkpFile)).isFalse() + assertThat(filesystem.exists(journalFile)).isTrue() } @ParameterizedTest - @ArgumentsSource(FileSystemParamProvider::class) + @ArgumentsSource(FilesystemParamProvider::class) fun journalFileIsPreferredOverBackupFile(parameters: Pair) { setUp(parameters.first, parameters.second) var creator = cache.edit("k1")!! @@ -1099,14 +1108,14 @@ class DiskLruCacheTest { creator.setString(1, "DE") creator.commit() cache.flush() - copyFile(journalFile, journalBkpFile) + filesystem.copy(journalFile, journalBkpFile) creator = cache.edit("k2")!! creator.setString(0, "F") creator.setString(1, "GH") creator.commit() cache.close() - assertThat(fileSystem.exists(journalFile)).isTrue() - assertThat(fileSystem.exists(journalBkpFile)).isTrue() + assertThat(filesystem.exists(journalFile)).isTrue() + assertThat(filesystem.exists(journalBkpFile)).isTrue() createNewCache() val snapshotA = cache["k1"]!! snapshotA.assertValue(0, "ABC") @@ -1114,35 +1123,35 @@ class DiskLruCacheTest { val snapshotB = cache["k2"]!! snapshotB.assertValue(0, "F") snapshotB.assertValue(1, "GH") - assertThat(fileSystem.exists(journalBkpFile)).isFalse() - assertThat(fileSystem.exists(journalFile)).isTrue() + assertThat(filesystem.exists(journalBkpFile)).isFalse() + assertThat(filesystem.exists(journalFile)).isTrue() } @ParameterizedTest - @ArgumentsSource(FileSystemParamProvider::class) + @ArgumentsSource(FilesystemParamProvider::class) fun openCreatesDirectoryIfNecessary(parameters: Pair) { setUp(parameters.first, parameters.second) cache.close() - val dir = File(cacheDir, "testOpenCreatesDirectoryIfNecessary").also { it.mkdirs() } - cache = DiskLruCache(fileSystem, dir, appVersion, 2, Int.MAX_VALUE.toLong(), taskRunner) + val dir = (cacheDir / "testOpenCreatesDirectoryIfNecessary").also { filesystem.createDirectories(it) } + cache = DiskLruCache(filesystem, dir, appVersion, 2, Int.MAX_VALUE.toLong(), taskRunner) set("a", "a", "a") - assertThat(fileSystem.exists(File(dir, "a.0"))).isTrue() - assertThat(fileSystem.exists(File(dir, "a.1"))).isTrue() - assertThat(fileSystem.exists(File(dir, "journal"))).isTrue() + assertThat(filesystem.exists(dir / "a.0")).isTrue() + assertThat(filesystem.exists(dir / "a.1")).isTrue() + assertThat(filesystem.exists(dir / "journal")).isTrue() } @ParameterizedTest - @ArgumentsSource(FileSystemParamProvider::class) + @ArgumentsSource(FilesystemParamProvider::class) fun fileDeletedExternally(parameters: Pair) { setUp(parameters.first, parameters.second) set("a", "a", "a") - fileSystem.delete(getCleanFile("a", 1)) + filesystem.delete(getCleanFile("a", 1)) assertThat(cache["a"]).isNull() assertThat(cache.size()).isEqualTo(0) } @ParameterizedTest - @ArgumentsSource(FileSystemParamProvider::class) + @ArgumentsSource(FilesystemParamProvider::class) fun editSameVersion(parameters: Pair) { setUp(parameters.first, parameters.second) set("a", "a", "a") @@ -1155,7 +1164,7 @@ class DiskLruCacheTest { } @ParameterizedTest - @ArgumentsSource(FileSystemParamProvider::class) + @ArgumentsSource(FilesystemParamProvider::class) fun editSnapshotAfterChangeAborted(parameters: Pair) { setUp(parameters.first, parameters.second) set("a", "a", "a") @@ -1171,7 +1180,7 @@ class DiskLruCacheTest { } @ParameterizedTest - @ArgumentsSource(FileSystemParamProvider::class) + @ArgumentsSource(FilesystemParamProvider::class) fun editSnapshotAfterChangeCommitted(parameters: Pair) { setUp(parameters.first, parameters.second) set("a", "a", "a") @@ -1184,7 +1193,7 @@ class DiskLruCacheTest { } @ParameterizedTest - @ArgumentsSource(FileSystemParamProvider::class) + @ArgumentsSource(FilesystemParamProvider::class) fun editSinceEvicted(parameters: Pair) { setUp(parameters.first, parameters.second) cache.close() @@ -1198,7 +1207,7 @@ class DiskLruCacheTest { } @ParameterizedTest - @ArgumentsSource(FileSystemParamProvider::class) + @ArgumentsSource(FilesystemParamProvider::class) fun editSinceEvictedAndRecreated(parameters: Pair) { setUp(parameters.first, parameters.second) cache.close() @@ -1215,42 +1224,42 @@ class DiskLruCacheTest { /** @see [Issue 2](https://github.com/JakeWharton/DiskLruCache/issues/2) */ @ParameterizedTest - @ArgumentsSource(FileSystemParamProvider::class) + @ArgumentsSource(FilesystemParamProvider::class) fun aggressiveClearingHandlesWrite(parameters: Pair) { setUp(parameters.first, parameters.second) Assumptions.assumeFalse(windows) // Can't deleteContents while the journal is open. - fileSystem.deleteContents(cacheDir) + filesystem.deleteRecursively(cacheDir) set("a", "a", "a") assertValue("a", "a", "a") } /** @see [Issue 2](https://github.com/JakeWharton/DiskLruCache/issues/2) */ @ParameterizedTest - @ArgumentsSource(FileSystemParamProvider::class) + @ArgumentsSource(FilesystemParamProvider::class) fun aggressiveClearingHandlesEdit(parameters: Pair) { setUp(parameters.first, parameters.second) Assumptions.assumeFalse(windows) // Can't deleteContents while the journal is open. set("a", "a", "a") val a = cache.edit("a")!! - fileSystem.deleteContents(cacheDir) + filesystem.deleteRecursively(cacheDir) a.setString(1, "a2") a.commit() } @ParameterizedTest - @ArgumentsSource(FileSystemParamProvider::class) + @ArgumentsSource(FilesystemParamProvider::class) fun removeHandlesMissingFile(parameters: Pair) { setUp(parameters.first, parameters.second) set("a", "a", "a") - getCleanFile("a", 0).delete() + filesystem.delete(getCleanFile("a", 0)) cache.remove("a") } /** @see [Issue 2](https://github.com/JakeWharton/DiskLruCache/issues/2) */ @ParameterizedTest - @ArgumentsSource(FileSystemParamProvider::class) + @ArgumentsSource(FilesystemParamProvider::class) fun aggressiveClearingHandlesPartialEdit(parameters: Pair) { setUp(parameters.first, parameters.second) Assumptions.assumeFalse(windows) // Can't deleteContents while the journal is open. @@ -1259,7 +1268,7 @@ class DiskLruCacheTest { set("b", "b", "b") val a = cache.edit("a")!! a.setString(0, "a1") - fileSystem.deleteContents(cacheDir) + filesystem.deleteRecursively(cacheDir) a.setString(1, "a2") a.commit() assertThat(cache["a"]).isNull() @@ -1267,12 +1276,12 @@ class DiskLruCacheTest { /** @see [Issue 2](https://github.com/JakeWharton/DiskLruCache/issues/2) */ @ParameterizedTest - @ArgumentsSource(FileSystemParamProvider::class) + @ArgumentsSource(FilesystemParamProvider::class) fun aggressiveClearingHandlesRead(parameters: Pair) { setUp(parameters.first, parameters.second) Assumptions.assumeFalse(windows) // Can't deleteContents while the journal is open. - fileSystem.deleteContents(cacheDir) + filesystem.deleteRecursively(cacheDir) assertThat(cache["a"]).isNull() } @@ -1281,7 +1290,7 @@ class DiskLruCacheTest { * being edited required deletion for the operation to complete. */ @ParameterizedTest - @ArgumentsSource(FileSystemParamProvider::class) + @ArgumentsSource(FilesystemParamProvider::class) fun trimToSizeWithActiveEdit(parameters: Pair) { setUp(parameters.first, parameters.second) val expectedByteCount = if (windows) 10L else 0L @@ -1304,7 +1313,7 @@ class DiskLruCacheTest { } @ParameterizedTest - @ArgumentsSource(FileSystemParamProvider::class) + @ArgumentsSource(FilesystemParamProvider::class) fun evictAll(parameters: Pair) { setUp(parameters.first, parameters.second) set("a", "a", "a") @@ -1316,7 +1325,7 @@ class DiskLruCacheTest { } @ParameterizedTest - @ArgumentsSource(FileSystemParamProvider::class) + @ArgumentsSource(FilesystemParamProvider::class) fun evictAllWithPartialCreate(parameters: Pair) { setUp(parameters.first, parameters.second) val a = cache.edit("a")!! @@ -1329,7 +1338,7 @@ class DiskLruCacheTest { } @ParameterizedTest - @ArgumentsSource(FileSystemParamProvider::class) + @ArgumentsSource(FilesystemParamProvider::class) fun evictAllWithPartialEditDoesNotStoreAValue(parameters: Pair) { setUp(parameters.first, parameters.second) val expectedByteCount = if (windows) 2L else 0L @@ -1345,7 +1354,7 @@ class DiskLruCacheTest { } @ParameterizedTest - @ArgumentsSource(FileSystemParamProvider::class) + @ArgumentsSource(FilesystemParamProvider::class) fun evictAllDoesntInterruptPartialRead(parameters: Pair) { setUp(parameters.first, parameters.second) val expectedByteCount = if (windows) 2L else 0L @@ -1364,7 +1373,7 @@ class DiskLruCacheTest { } @ParameterizedTest - @ArgumentsSource(FileSystemParamProvider::class) + @ArgumentsSource(FilesystemParamProvider::class) fun editSnapshotAfterEvictAllReturnsNullDueToStaleValue(parameters: Pair) { setUp(parameters.first, parameters.second) val expectedByteCount = if (windows) 2L else 0L @@ -1382,7 +1391,7 @@ class DiskLruCacheTest { } @ParameterizedTest - @ArgumentsSource(FileSystemParamProvider::class) + @ArgumentsSource(FilesystemParamProvider::class) fun iterator(parameters: Pair) { setUp(parameters.first, parameters.second) set("a", "a1", "a2") @@ -1416,7 +1425,7 @@ class DiskLruCacheTest { } @ParameterizedTest - @ArgumentsSource(FileSystemParamProvider::class) + @ArgumentsSource(FilesystemParamProvider::class) fun iteratorElementsAddedDuringIterationAreOmitted(parameters: Pair) { setUp(parameters.first, parameters.second) set("a", "a1", "a2") @@ -1433,7 +1442,7 @@ class DiskLruCacheTest { } @ParameterizedTest - @ArgumentsSource(FileSystemParamProvider::class) + @ArgumentsSource(FilesystemParamProvider::class) fun iteratorElementsUpdatedDuringIterationAreUpdated(parameters: Pair) { setUp(parameters.first, parameters.second) set("a", "a1", "a2") @@ -1451,7 +1460,7 @@ class DiskLruCacheTest { } @ParameterizedTest - @ArgumentsSource(FileSystemParamProvider::class) + @ArgumentsSource(FilesystemParamProvider::class) fun iteratorElementsRemovedDuringIterationAreOmitted(parameters: Pair) { setUp(parameters.first, parameters.second) set("a", "a1", "a2") @@ -1465,7 +1474,7 @@ class DiskLruCacheTest { } @ParameterizedTest - @ArgumentsSource(FileSystemParamProvider::class) + @ArgumentsSource(FilesystemParamProvider::class) fun iteratorRemove(parameters: Pair) { setUp(parameters.first, parameters.second) set("a", "a1", "a2") @@ -1477,7 +1486,7 @@ class DiskLruCacheTest { } @ParameterizedTest - @ArgumentsSource(FileSystemParamProvider::class) + @ArgumentsSource(FilesystemParamProvider::class) fun iteratorRemoveBeforeNext(parameters: Pair) { setUp(parameters.first, parameters.second) set("a", "a1", "a2") @@ -1490,7 +1499,7 @@ class DiskLruCacheTest { } @ParameterizedTest - @ArgumentsSource(FileSystemParamProvider::class) + @ArgumentsSource(FilesystemParamProvider::class) fun iteratorRemoveOncePerCallToNext(parameters: Pair) { setUp(parameters.first, parameters.second) set("a", "a1", "a2") @@ -1506,7 +1515,7 @@ class DiskLruCacheTest { } @ParameterizedTest - @ArgumentsSource(FileSystemParamProvider::class) + @ArgumentsSource(FilesystemParamProvider::class) fun cacheClosedTruncatesIterator(parameters: Pair) { setUp(parameters.first, parameters.second) set("a", "a1", "a2") @@ -1516,11 +1525,11 @@ class DiskLruCacheTest { } @ParameterizedTest - @ArgumentsSource(FileSystemParamProvider::class) + @ArgumentsSource(FilesystemParamProvider::class) fun isClosed_uninitializedCache(parameters: Pair) { setUp(parameters.first, parameters.second) // Create an uninitialized cache. - cache = DiskLruCache(fileSystem, cacheDir, appVersion, 2, Int.MAX_VALUE.toLong(), taskRunner) + cache = DiskLruCache(filesystem, cacheDir, appVersion, 2, Int.MAX_VALUE.toLong(), taskRunner) toClose.add(cache) assertThat(cache.isClosed()).isFalse() cache.close() @@ -1528,23 +1537,23 @@ class DiskLruCacheTest { } @ParameterizedTest - @ArgumentsSource(FileSystemParamProvider::class) + @ArgumentsSource(FilesystemParamProvider::class) fun journalWriteFailsDuringEdit(parameters: Pair) { setUp(parameters.first, parameters.second) set("a", "a", "a") set("b", "b", "b") // We can't begin the edit if writing 'DIRTY' fails. - fileSystem.setFaultyWrite(journalFile, true) + filesystem.setFaultyWrite(journalFile, true) assertThat(cache.edit("c")).isNull() // Once the journal has a failure, subsequent writes aren't permitted. - fileSystem.setFaultyWrite(journalFile, false) + filesystem.setFaultyWrite(journalFile, false) assertThat(cache.edit("d")).isNull() // Confirm that the fault didn't corrupt entries stored before the fault was introduced. cache.close() - cache = DiskLruCache(fileSystem, cacheDir, appVersion, 2, Int.MAX_VALUE.toLong(), taskRunner) + cache = DiskLruCache(filesystem, cacheDir, appVersion, 2, Int.MAX_VALUE.toLong(), taskRunner) assertValue("a", "a", "a") assertValue("b", "b", "b") assertAbsent("c") @@ -1556,7 +1565,7 @@ class DiskLruCacheTest { * https://github.com/square/okhttp/issues/1211 */ @ParameterizedTest - @ArgumentsSource(FileSystemParamProvider::class) + @ArgumentsSource(FilesystemParamProvider::class) fun journalWriteFailsDuringEditorCommit(parameters: Pair) { setUp(parameters.first, parameters.second) set("a", "a", "a") @@ -1566,16 +1575,16 @@ class DiskLruCacheTest { val editor = cache.edit("c")!! editor.setString(0, "c") editor.setString(1, "c") - fileSystem.setFaultyWrite(journalFile, true) + filesystem.setFaultyWrite(journalFile, true) editor.commit() // Once the journal has a failure, subsequent writes aren't permitted. - fileSystem.setFaultyWrite(journalFile, false) + filesystem.setFaultyWrite(journalFile, false) assertThat(cache.edit("d")).isNull() // Confirm that the fault didn't corrupt entries stored before the fault was introduced. cache.close() - cache = DiskLruCache(fileSystem, cacheDir, appVersion, 2, Int.MAX_VALUE.toLong(), taskRunner) + cache = DiskLruCache(filesystem, cacheDir, appVersion, 2, Int.MAX_VALUE.toLong(), taskRunner) assertValue("a", "a", "a") assertValue("b", "b", "b") assertAbsent("c") @@ -1583,7 +1592,7 @@ class DiskLruCacheTest { } @ParameterizedTest - @ArgumentsSource(FileSystemParamProvider::class) + @ArgumentsSource(FilesystemParamProvider::class) fun journalWriteFailsDuringEditorAbort(parameters: Pair) { setUp(parameters.first, parameters.second) set("a", "a", "a") @@ -1593,16 +1602,16 @@ class DiskLruCacheTest { val editor = cache.edit("c")!! editor.setString(0, "c") editor.setString(1, "c") - fileSystem.setFaultyWrite(journalFile, true) + filesystem.setFaultyWrite(journalFile, true) editor.abort() // Once the journal has a failure, subsequent writes aren't permitted. - fileSystem.setFaultyWrite(journalFile, false) + filesystem.setFaultyWrite(journalFile, false) assertThat(cache.edit("d")).isNull() // Confirm that the fault didn't corrupt entries stored before the fault was introduced. cache.close() - cache = DiskLruCache(fileSystem, cacheDir, appVersion, 2, Int.MAX_VALUE.toLong(), taskRunner) + cache = DiskLruCache(filesystem, cacheDir, appVersion, 2, Int.MAX_VALUE.toLong(), taskRunner) assertValue("a", "a", "a") assertValue("b", "b", "b") assertAbsent("c") @@ -1610,26 +1619,26 @@ class DiskLruCacheTest { } @ParameterizedTest - @ArgumentsSource(FileSystemParamProvider::class) + @ArgumentsSource(FilesystemParamProvider::class) fun journalWriteFailsDuringRemove(parameters: Pair) { setUp(parameters.first, parameters.second) set("a", "a", "a") set("b", "b", "b") // Remove, but the journal write will fail. - fileSystem.setFaultyWrite(journalFile, true) + filesystem.setFaultyWrite(journalFile, true) assertThat(cache.remove("a")).isTrue() // Confirm that the entry was still removed. - fileSystem.setFaultyWrite(journalFile, false) + filesystem.setFaultyWrite(journalFile, false) cache.close() - cache = DiskLruCache(fileSystem, cacheDir, appVersion, 2, Int.MAX_VALUE.toLong(), taskRunner) + cache = DiskLruCache(filesystem, cacheDir, appVersion, 2, Int.MAX_VALUE.toLong(), taskRunner) assertAbsent("a") assertValue("b", "b", "b") } @ParameterizedTest - @ArgumentsSource(FileSystemParamProvider::class) + @ArgumentsSource(FilesystemParamProvider::class) fun cleanupTrimFailurePreventsNewEditors(parameters: Pair) { setUp(parameters.first, parameters.second) cache.maxSize = 8 @@ -1638,7 +1647,7 @@ class DiskLruCacheTest { set("b", "bb", "bbb") // Cause the cache trim job to fail. - fileSystem.setFaultyDelete(File(cacheDir, "a.0"), true) + filesystem.setFaultyDelete(cacheDir / "a.0", true) taskFaker.runNextTask() // Confirm that edits are prevented after a cache trim failure. @@ -1647,11 +1656,11 @@ class DiskLruCacheTest { assertThat(cache.edit("c")).isNull() // Allow the test to clean up. - fileSystem.setFaultyDelete(File(cacheDir, "a.0"), false) + filesystem.setFaultyDelete(cacheDir / "a.0", false) } @ParameterizedTest - @ArgumentsSource(FileSystemParamProvider::class) + @ArgumentsSource(FilesystemParamProvider::class) fun cleanupTrimFailureRetriedOnEditors(parameters: Pair) { setUp(parameters.first, parameters.second) cache.maxSize = 8 @@ -1660,7 +1669,7 @@ class DiskLruCacheTest { set("b", "bb", "bbb") // Cause the cache trim job to fail. - fileSystem.setFaultyDelete(File(cacheDir, "a.0"), true) + filesystem.setFaultyDelete(cacheDir / "a.0", true) taskFaker.runNextTask() // An edit should now add a job to clean up if the most recent trim failed. @@ -1668,7 +1677,7 @@ class DiskLruCacheTest { taskFaker.runNextTask() // Confirm a successful cache trim now allows edits. - fileSystem.setFaultyDelete(File(cacheDir, "a.0"), false) + filesystem.setFaultyDelete(cacheDir / "a.0", false) assertThat(cache.edit("c")).isNull() taskFaker.runNextTask() set("c", "cc", "cc") @@ -1676,7 +1685,7 @@ class DiskLruCacheTest { } @ParameterizedTest - @ArgumentsSource(FileSystemParamProvider::class) + @ArgumentsSource(FilesystemParamProvider::class) fun cleanupTrimFailureWithInFlightEditor(parameters: Pair) { setUp(parameters.first, parameters.second) cache.maxSize = 8 @@ -1686,7 +1695,7 @@ class DiskLruCacheTest { val inFlightEditor = cache.edit("c")!! // Cause the cache trim job to fail. - fileSystem.setFaultyDelete(File(cacheDir, "a.0"), true) + filesystem.setFaultyDelete(cacheDir / "a.0", true) taskFaker.runNextTask() // The in-flight editor can still write after a trim failure. @@ -1695,13 +1704,13 @@ class DiskLruCacheTest { inFlightEditor.commit() // Confirm the committed values are present after a successful cache trim. - fileSystem.setFaultyDelete(File(cacheDir, "a.0"), false) + filesystem.setFaultyDelete(cacheDir / "a.0", false) taskFaker.runNextTask() assertValue("c", "cc", "cc") } @ParameterizedTest - @ArgumentsSource(FileSystemParamProvider::class) + @ArgumentsSource(FilesystemParamProvider::class) fun cleanupTrimFailureAllowsSnapshotReads(parameters: Pair) { setUp(parameters.first, parameters.second) cache.maxSize = 8 @@ -1710,7 +1719,7 @@ class DiskLruCacheTest { set("b", "bb", "bbb") // Cause the cache trim job to fail. - fileSystem.setFaultyDelete(File(cacheDir, "a.0"), true) + filesystem.setFaultyDelete(cacheDir / "a.0", true) taskFaker.runNextTask() // Confirm we still allow snapshot reads after a trim failure. @@ -1718,11 +1727,11 @@ class DiskLruCacheTest { assertValue("b", "bb", "bbb") // Allow the test to clean up. - fileSystem.setFaultyDelete(File(cacheDir, "a.0"), false) + filesystem.setFaultyDelete(cacheDir / "a.0", false) } @ParameterizedTest - @ArgumentsSource(FileSystemParamProvider::class) + @ArgumentsSource(FilesystemParamProvider::class) fun cleanupTrimFailurePreventsSnapshotWrites(parameters: Pair) { setUp(parameters.first, parameters.second) cache.maxSize = 8 @@ -1731,7 +1740,7 @@ class DiskLruCacheTest { set("b", "bb", "bbb") // Cause the cache trim job to fail. - fileSystem.setFaultyDelete(File(cacheDir, "a.0"), true) + filesystem.setFaultyDelete(cacheDir / "a.0", true) taskFaker.runNextTask() // Confirm snapshot writes are prevented after a trim failure. @@ -1743,11 +1752,11 @@ class DiskLruCacheTest { } // Allow the test to clean up. - fileSystem.setFaultyDelete(File(cacheDir, "a.0"), false) + filesystem.setFaultyDelete(cacheDir / "a.0", false) } @ParameterizedTest - @ArgumentsSource(FileSystemParamProvider::class) + @ArgumentsSource(FilesystemParamProvider::class) fun evictAllAfterCleanupTrimFailure(parameters: Pair) { setUp(parameters.first, parameters.second) cache.maxSize = 8 @@ -1756,21 +1765,21 @@ class DiskLruCacheTest { set("b", "bb", "bbb") // Cause the cache trim job to fail. - fileSystem.setFaultyDelete(File(cacheDir, "a.0"), true) + filesystem.setFaultyDelete(cacheDir / "a.0", true) taskFaker.runNextTask() // Confirm we prevent edits after a trim failure. assertThat(cache.edit("c")).isNull() // A successful eviction should allow new writes. - fileSystem.setFaultyDelete(File(cacheDir, "a.0"), false) + filesystem.setFaultyDelete(cacheDir / "a.0", false) cache.evictAll() set("c", "cc", "cc") assertValue("c", "cc", "cc") } @ParameterizedTest - @ArgumentsSource(FileSystemParamProvider::class) + @ArgumentsSource(FilesystemParamProvider::class) fun manualRemovalAfterCleanupTrimFailure(parameters: Pair) { setUp(parameters.first, parameters.second) cache.maxSize = 8 @@ -1779,21 +1788,21 @@ class DiskLruCacheTest { set("b", "bb", "bbb") // Cause the cache trim job to fail. - fileSystem.setFaultyDelete(File(cacheDir, "a.0"), true) + filesystem.setFaultyDelete(cacheDir / "a.0", true) taskFaker.runNextTask() // Confirm we prevent edits after a trim failure. assertThat(cache.edit("c")).isNull() // A successful removal which trims the cache should allow new writes. - fileSystem.setFaultyDelete(File(cacheDir, "a.0"), false) + filesystem.setFaultyDelete(cacheDir / "a.0", false) cache.remove("a") set("c", "cc", "cc") assertValue("c", "cc", "cc") } @ParameterizedTest - @ArgumentsSource(FileSystemParamProvider::class) + @ArgumentsSource(FilesystemParamProvider::class) fun flushingAfterCleanupTrimFailure(parameters: Pair) { setUp(parameters.first, parameters.second) cache.maxSize = 8 @@ -1802,21 +1811,21 @@ class DiskLruCacheTest { set("b", "bb", "bbb") // Cause the cache trim job to fail. - fileSystem.setFaultyDelete(File(cacheDir, "a.0"), true) + filesystem.setFaultyDelete(cacheDir / "a.0", true) taskFaker.runNextTask() // Confirm we prevent edits after a trim failure. assertThat(cache.edit("c")).isNull() // A successful flush trims the cache and should allow new writes. - fileSystem.setFaultyDelete(File(cacheDir, "a.0"), false) + filesystem.setFaultyDelete(cacheDir / "a.0", false) cache.flush() set("c", "cc", "cc") assertValue("c", "cc", "cc") } @ParameterizedTest - @ArgumentsSource(FileSystemParamProvider::class) + @ArgumentsSource(FilesystemParamProvider::class) fun cleanupTrimFailureWithPartialSnapshot(parameters: Pair) { setUp(parameters.first, parameters.second) cache.maxSize = 8 @@ -1825,7 +1834,7 @@ class DiskLruCacheTest { set("b", "bb", "bbb") // Cause the cache trim to fail on the second value leaving a partial snapshot. - fileSystem.setFaultyDelete(File(cacheDir, "a.1"), true) + filesystem.setFaultyDelete(cacheDir / "a.1", true) taskFaker.runNextTask() // Confirm the partial snapshot is not returned. @@ -1835,13 +1844,13 @@ class DiskLruCacheTest { assertThat(cache.edit("a")).isNull() // Confirm the partial snapshot is not returned after a successful trim. - fileSystem.setFaultyDelete(File(cacheDir, "a.1"), false) + filesystem.setFaultyDelete(cacheDir / "a.1", false) taskFaker.runNextTask() assertThat(cache["a"]).isNull() } @ParameterizedTest - @ArgumentsSource(FileSystemParamProvider::class) + @ArgumentsSource(FilesystemParamProvider::class) fun noSizeCorruptionAfterCreatorDetached(parameters: Pair) { setUp(parameters.first, parameters.second) Assumptions.assumeFalse(windows) // Windows can't have two concurrent editors. @@ -1863,7 +1872,7 @@ class DiskLruCacheTest { } @ParameterizedTest - @ArgumentsSource(FileSystemParamProvider::class) + @ArgumentsSource(FilesystemParamProvider::class) fun noSizeCorruptionAfterEditorDetached(parameters: Pair) { setUp(parameters.first, parameters.second) Assumptions.assumeFalse(windows) // Windows can't have two concurrent editors. @@ -1887,7 +1896,7 @@ class DiskLruCacheTest { } @ParameterizedTest - @ArgumentsSource(FileSystemParamProvider::class) + @ArgumentsSource(FilesystemParamProvider::class) fun noNewSourceAfterEditorDetached(parameters: Pair) { setUp(parameters.first, parameters.second) set("k1", "a", "a") @@ -1897,7 +1906,7 @@ class DiskLruCacheTest { } @ParameterizedTest - @ArgumentsSource(FileSystemParamProvider::class) + @ArgumentsSource(FilesystemParamProvider::class) fun `edit discarded after editor detached`(parameters: Pair) { setUp(parameters.first, parameters.second) set("k1", "a", "a") @@ -1914,7 +1923,7 @@ class DiskLruCacheTest { } @ParameterizedTest - @ArgumentsSource(FileSystemParamProvider::class) + @ArgumentsSource(FilesystemParamProvider::class) fun `edit discarded after editor detached with concurrent write`(parameters: Pair) { setUp(parameters.first, parameters.second) Assumptions.assumeFalse(windows) // Windows can't have two concurrent editors. @@ -1936,7 +1945,7 @@ class DiskLruCacheTest { } @ParameterizedTest - @ArgumentsSource(FileSystemParamProvider::class) + @ArgumentsSource(FilesystemParamProvider::class) fun abortAfterDetach(parameters: Pair) { setUp(parameters.first, parameters.second) set("k1", "a", "a") @@ -1948,7 +1957,7 @@ class DiskLruCacheTest { } @ParameterizedTest - @ArgumentsSource(FileSystemParamProvider::class) + @ArgumentsSource(FilesystemParamProvider::class) fun dontRemoveUnfinishedEntryWhenCreatingSnapshot(parameters: Pair) { setUp(parameters.first, parameters.second) val creator = cache.edit("k1")!! @@ -1966,7 +1975,7 @@ class DiskLruCacheTest { } @ParameterizedTest - @ArgumentsSource(FileSystemParamProvider::class) + @ArgumentsSource(FilesystemParamProvider::class) fun `Windows cannot read while writing`(parameters: Pair) { setUp(parameters.first, parameters.second) Assumptions.assumeTrue(windows) @@ -1978,7 +1987,7 @@ class DiskLruCacheTest { } @ParameterizedTest - @ArgumentsSource(FileSystemParamProvider::class) + @ArgumentsSource(FilesystemParamProvider::class) fun `Windows cannot write while reading`(parameters: Pair) { setUp(parameters.first, parameters.second) Assumptions.assumeTrue(windows) @@ -1990,7 +1999,7 @@ class DiskLruCacheTest { } @ParameterizedTest - @ArgumentsSource(FileSystemParamProvider::class) + @ArgumentsSource(FilesystemParamProvider::class) fun `can read while reading`(parameters: Pair) { setUp(parameters.first, parameters.second) set("k1", "a", "a") @@ -2005,7 +2014,7 @@ class DiskLruCacheTest { } @ParameterizedTest - @ArgumentsSource(FileSystemParamProvider::class) + @ArgumentsSource(FilesystemParamProvider::class) fun `remove while reading creates zombie that is removed when read finishes`(parameters: Pair) { setUp(parameters.first, parameters.second) val afterRemoveFileContents = if (windows) "a" else null @@ -2031,7 +2040,7 @@ class DiskLruCacheTest { } @ParameterizedTest - @ArgumentsSource(FileSystemParamProvider::class) + @ArgumentsSource(FilesystemParamProvider::class) fun `remove while writing creates zombie that is removed when write finishes`(parameters: Pair) { setUp(parameters.first, parameters.second) val afterRemoveFileContents = if (windows) "a" else null @@ -2052,7 +2061,7 @@ class DiskLruCacheTest { } @ParameterizedTest - @ArgumentsSource(FileSystemParamProvider::class) + @ArgumentsSource(FilesystemParamProvider::class) fun `Windows cannot read zombie entry`(parameters: Pair) { setUp(parameters.first, parameters.second) Assumptions.assumeTrue(windows) @@ -2065,7 +2074,7 @@ class DiskLruCacheTest { } @ParameterizedTest - @ArgumentsSource(FileSystemParamProvider::class) + @ArgumentsSource(FilesystemParamProvider::class) fun `Windows cannot write zombie entry`(parameters: Pair) { setUp(parameters.first, parameters.second) Assumptions.assumeTrue(windows) @@ -2078,7 +2087,7 @@ class DiskLruCacheTest { } @ParameterizedTest - @ArgumentsSource(FileSystemParamProvider::class) + @ArgumentsSource(FilesystemParamProvider::class) fun `removed entry absent when iterating`(parameters: Pair) { setUp(parameters.first, parameters.second) set("k1", "a", "a") @@ -2090,7 +2099,7 @@ class DiskLruCacheTest { } @ParameterizedTest - @ArgumentsSource(FileSystemParamProvider::class) + @ArgumentsSource(FilesystemParamProvider::class) fun `close with zombie read`(parameters: Pair) { setUp(parameters.first, parameters.second) val afterRemoveFileContents = if (windows) "a" else null @@ -2113,7 +2122,7 @@ class DiskLruCacheTest { } @ParameterizedTest - @ArgumentsSource(FileSystemParamProvider::class) + @ArgumentsSource(FilesystemParamProvider::class) fun `close with zombie write`(parameters: Pair) { setUp(parameters.first, parameters.second) val afterRemoveCleanFileContents = if (windows) "a" else null @@ -2137,7 +2146,7 @@ class DiskLruCacheTest { } @ParameterizedTest - @ArgumentsSource(FileSystemParamProvider::class) + @ArgumentsSource(FilesystemParamProvider::class) fun `close with completed zombie write`(parameters: Pair) { setUp(parameters.first, parameters.second) val afterRemoveCleanFileContents = if (windows) "a" else null @@ -2180,8 +2189,8 @@ class DiskLruCacheTest { blank: String, vararg bodyLines: String ) { - fileSystem.sink(journalFile).buffer().use { sink -> - sink.writeUtf8( + filesystem.write(journalFile) { + writeUtf8( """ |$magic |$version @@ -2191,68 +2200,71 @@ class DiskLruCacheTest { |""".trimMargin() ) for (line in bodyLines) { - sink.writeUtf8(line) - sink.writeUtf8("\n") + writeUtf8(line) + writeUtf8("\n") } } } private fun readJournalLines(): List { val result = mutableListOf() - fileSystem.source(journalFile).buffer().use { source -> + filesystem.read(journalFile) { while (true) { - val line = source.readUtf8Line() ?: break + val line = readUtf8Line() ?: break result.add(line) } } return result } - private fun getCleanFile(key: String, index: Int) = File(cacheDir, "$key.$index") + private fun getCleanFile(key: String, index: Int) = cacheDir / "$key.$index" - private fun getDirtyFile(key: String, index: Int) = File(cacheDir, "$key.$index.tmp") + private fun getDirtyFile(key: String, index: Int) = cacheDir / "$key.$index.tmp" - private fun readFile(file: File): String { - fileSystem.source(file).buffer().use { source -> - return source.readUtf8() + private fun readFile(file: Path): String { + return filesystem.read(file) { + readUtf8() } } - private fun readFileOrNull(file: File): String? { - try { - fileSystem.source(file).buffer().use { - return it.readUtf8() + private fun readFileOrNull(file: Path): String? { + return try { + filesystem.read(file) { + readUtf8() } } catch (_: FileNotFoundException) { - return null + null } } - fun writeFile(file: File, content: String) { - fileSystem.sink(file).buffer().use { sink -> - sink.writeUtf8(content) + fun writeFile(file: Path, content: String) { + file.parent?.let { + filesystem.createDirectories(it) + } + filesystem.write(file) { + writeUtf8(content) } } private fun generateSomeGarbageFiles() { - val dir1 = File(cacheDir, "dir1") - val dir2 = File(dir1, "dir2") + val dir1 = cacheDir / "dir1" + val dir2 = dir1 / "dir2" writeFile(getCleanFile("g1", 0), "A") writeFile(getCleanFile("g1", 1), "B") writeFile(getCleanFile("g2", 0), "C") writeFile(getCleanFile("g2", 1), "D") writeFile(getCleanFile("g2", 1), "D") - writeFile(File(cacheDir, "otherFile0"), "E") - writeFile(File(dir2, "otherFile1"), "F") + writeFile(cacheDir / "otherFile0", "E") + writeFile(dir2 / "otherFile1", "F") } private fun assertGarbageFilesAllDeleted() { - assertThat(fileSystem.exists(getCleanFile("g1", 0))).isFalse() - assertThat(fileSystem.exists(getCleanFile("g1", 1))).isFalse() - assertThat(fileSystem.exists(getCleanFile("g2", 0))).isFalse() - assertThat(fileSystem.exists(getCleanFile("g2", 1))).isFalse() - assertThat(fileSystem.exists(File(cacheDir, "otherFile0"))).isFalse() - assertThat(fileSystem.exists(File(cacheDir, "dir1"))).isFalse() + assertThat(filesystem.exists(getCleanFile("g1", 0))).isFalse() + assertThat(filesystem.exists(getCleanFile("g1", 1))).isFalse() + assertThat(filesystem.exists(getCleanFile("g2", 0))).isFalse() + assertThat(filesystem.exists(getCleanFile("g2", 1))).isFalse() + assertThat(filesystem.exists(cacheDir / "otherFile0")).isFalse() + assertThat(filesystem.exists(cacheDir / "dir1")).isFalse() } private operator fun set(key: String, value0: String, value1: String) { @@ -2268,18 +2280,18 @@ class DiskLruCacheTest { snapshot.close() fail("") } - assertThat(fileSystem.exists(getCleanFile(key, 0))).isFalse() - assertThat(fileSystem.exists(getCleanFile(key, 1))).isFalse() - assertThat(fileSystem.exists(getDirtyFile(key, 0))).isFalse() - assertThat(fileSystem.exists(getDirtyFile(key, 1))).isFalse() + assertThat(filesystem.exists(getCleanFile(key, 0))).isFalse() + assertThat(filesystem.exists(getCleanFile(key, 1))).isFalse() + assertThat(filesystem.exists(getDirtyFile(key, 0))).isFalse() + assertThat(filesystem.exists(getDirtyFile(key, 1))).isFalse() } private fun assertValue(key: String, value0: String, value1: String) { cache[key]!!.use { it.assertValue(0, value0) it.assertValue(1, value1) - assertThat(fileSystem.exists(getCleanFile(key, 0))).isTrue() - assertThat(fileSystem.exists(getCleanFile(key, 1))).isTrue() + assertThat(filesystem.exists(getCleanFile(key, 0))).isTrue() + assertThat(filesystem.exists(getCleanFile(key, 1))).isTrue() } } @@ -2292,14 +2304,6 @@ class DiskLruCacheTest { private fun sourceAsString(source: Source) = source.buffer().readUtf8() - private fun copyFile(from: File, to: File) { - fileSystem.source(from).use { source -> - fileSystem.sink(to).buffer().use { sink -> - sink.writeAll(source) - } - } - } - private fun Editor.assertInoperable() { try { setString(0, "A") diff --git a/okhttp/src/test/java/okhttp3/internal/io/FaultyFileSystem.java b/okhttp/src/test/java/okhttp3/internal/io/FaultyFileSystem.java deleted file mode 100644 index 4e3b9f33d..000000000 --- a/okhttp/src/test/java/okhttp3/internal/io/FaultyFileSystem.java +++ /dev/null @@ -1,114 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * - * 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 okhttp3.internal.io; - -import java.io.File; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.util.LinkedHashSet; -import java.util.Set; -import okio.Buffer; -import okio.ForwardingSink; -import okio.Sink; -import okio.Source; - -public final class FaultyFileSystem implements FileSystem { - private final FileSystem delegate; - private final Set writeFaults = new LinkedHashSet<>(); - private final Set deleteFaults = new LinkedHashSet<>(); - private final Set renameFaults = new LinkedHashSet<>(); - - public FaultyFileSystem(FileSystem delegate) { - this.delegate = delegate; - } - - public void setFaultyWrite(File file, boolean faulty) { - if (faulty) { - writeFaults.add(file); - } else { - writeFaults.remove(file); - } - } - - public void setFaultyDelete(File file, boolean faulty) { - if (faulty) { - deleteFaults.add(file); - } else { - deleteFaults.remove(file); - } - } - - public void setFaultyRename(File file, boolean faulty) { - if (faulty) { - renameFaults.add(file); - } else { - renameFaults.remove(file); - } - } - - @Override public Source source(File file) throws FileNotFoundException { - return delegate.source(file); - } - - @Override public Sink sink(File file) throws FileNotFoundException { - return new FaultySink(delegate.sink(file), file); - } - - @Override public Sink appendingSink(File file) throws FileNotFoundException { - return new FaultySink(delegate.appendingSink(file), file); - } - - @Override public void delete(File file) throws IOException { - if (deleteFaults.contains(file)) throw new IOException("boom!"); - delegate.delete(file); - } - - @Override public boolean exists(File file) { - return delegate.exists(file); - } - - @Override public long size(File file) { - return delegate.size(file); - } - - @Override public void rename(File from, File to) throws IOException { - if (renameFaults.contains(from) || renameFaults.contains(to)) throw new IOException("boom!"); - delegate.rename(from, to); - } - - @Override public void deleteContents(File directory) throws IOException { - if (deleteFaults.contains(directory)) throw new IOException("boom!"); - delegate.deleteContents(directory); - } - - private class FaultySink extends ForwardingSink { - private final File file; - - public FaultySink(Sink delegate, File file) { - super(delegate); - this.file = file; - } - - @Override public void write(Buffer source, long byteCount) throws IOException { - if (writeFaults.contains(file)) throw new IOException("boom!"); - super.write(source, byteCount); - } - } - - @Override public String toString() { - return "Faulty " + delegate; - } -} diff --git a/okhttp/src/test/java/okhttp3/internal/io/FaultyFileSystem.kt b/okhttp/src/test/java/okhttp3/internal/io/FaultyFileSystem.kt new file mode 100644 index 000000000..7ae0bbe50 --- /dev/null +++ b/okhttp/src/test/java/okhttp3/internal/io/FaultyFileSystem.kt @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * 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 okhttp3.internal.io + +import okio.Buffer +import okio.ExperimentalFileSystem +import okio.FileSystem +import okio.ForwardingFileSystem +import okio.ForwardingSink +import okio.Path +import okio.Sink +import java.io.IOException +import java.util.LinkedHashSet + +@OptIn(ExperimentalFileSystem::class) +class FaultyFileSystem constructor(delegate: FileSystem?) : ForwardingFileSystem(delegate!!) { + private val writeFaults: MutableSet = LinkedHashSet() + private val deleteFaults: MutableSet = LinkedHashSet() + private val renameFaults: MutableSet = LinkedHashSet() + + fun setFaultyWrite(file: Path, faulty: Boolean) { + if (faulty) { + writeFaults.add(file) + } else { + writeFaults.remove(file) + } + } + + fun setFaultyDelete(file: Path, faulty: Boolean) { + if (faulty) { + deleteFaults.add(file) + } else { + deleteFaults.remove(file) + } + } + + fun setFaultyRename(file: Path, faulty: Boolean) { + if (faulty) { + renameFaults.add(file) + } else { + renameFaults.remove(file) + } + } + + @Throws(IOException::class) + override fun atomicMove(source: Path, target: Path) { + if (renameFaults.contains(source) || renameFaults.contains(target)) throw IOException("boom!") + super.atomicMove(source, target) + } + + @Throws(IOException::class) + override fun delete(path: Path) { + if (deleteFaults.contains(path)) throw IOException("boom!") + super.delete(path) + } + + @Throws(IOException::class) + override fun deleteRecursively(fileOrDirectory: Path) { + if (deleteFaults.contains(fileOrDirectory)) throw IOException("boom!") + super.deleteRecursively(fileOrDirectory) + } + + override fun appendingSink(file: Path): Sink = FaultySink(super.appendingSink(file), file) + + override fun sink(file: Path): Sink = FaultySink(super.sink(file), file) + + inner class FaultySink(sink: Sink, private val file: Path) : ForwardingSink(sink) { + override fun write(source: Buffer, byteCount: Long) { + if (writeFaults.contains(file)) { + throw IOException("boom!") + } else { + super.write(source, byteCount) + } + } + } +} \ No newline at end of file diff --git a/okhttp/src/test/java/okhttp3/internal/io/FileSystemTest.kt b/okhttp/src/test/java/okhttp3/internal/io/FileSystemTest.kt deleted file mode 100644 index 137d3212b..000000000 --- a/okhttp/src/test/java/okhttp3/internal/io/FileSystemTest.kt +++ /dev/null @@ -1,175 +0,0 @@ -/* - * Copyright (C) 2020 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 okhttp3.internal.io - -import okhttp3.SimpleProvider -import okhttp3.TestUtil -import okio.buffer -import org.assertj.core.api.Assertions.assertThat -import org.junit.jupiter.api.io.TempDir -import org.junit.jupiter.params.ParameterizedTest -import org.junit.jupiter.params.provider.ArgumentsSource -import java.io.File -import java.io.IOException - -class FileSystemParamProvider: SimpleProvider() { - override fun arguments() = listOf( - FileSystem.SYSTEM to TestUtil.windows, - InMemoryFileSystem() to false, - WindowsFileSystem(FileSystem.SYSTEM) to true, - WindowsFileSystem(InMemoryFileSystem()) to true - ) -} - -/** - * Test that our file system abstraction is consistent and sufficient for OkHttp's needs. We're - * particularly interested in what happens when open files are moved or deleted on Windows. - */ -class FileSystemTest { - @TempDir lateinit var temporaryFolder: File - - private lateinit var fileSystem: FileSystem - private var windows: Boolean = false - - internal fun setUp(fileSystem: FileSystem, windows: Boolean) { - this.fileSystem = fileSystem - this.windows = windows - } - - @ParameterizedTest - @ArgumentsSource(FileSystemParamProvider::class) - fun `delete open for writing fails on Windows`( - parameters: Pair - ) { - setUp(parameters.first, parameters.second) - val file = File(temporaryFolder, "file.txt") - expectIOExceptionOnWindows { - fileSystem.sink(file).use { - fileSystem.delete(file) - } - } - } - - @ParameterizedTest - @ArgumentsSource(FileSystemParamProvider::class) - fun `delete open for appending fails on Windows`( - parameters: Pair - ) { - setUp(parameters.first, parameters.second) - val file = File(temporaryFolder, "file.txt") - file.write("abc") - expectIOExceptionOnWindows { - fileSystem.appendingSink(file).use { - fileSystem.delete(file) - } - } - } - - @ParameterizedTest - @ArgumentsSource(FileSystemParamProvider::class) - fun `delete open for reading fails on Windows`( - parameters: Pair - ) { - setUp(parameters.first, parameters.second) - val file = File(temporaryFolder, "file.txt") - file.write("abc") - expectIOExceptionOnWindows { - fileSystem.source(file).use { - fileSystem.delete(file) - } - } - } - - @ParameterizedTest - @ArgumentsSource(FileSystemParamProvider::class) - fun `rename target exists succeeds on all platforms`( - parameters: Pair - ) { - setUp(parameters.first, parameters.second) - val from = File(temporaryFolder, "from.txt") - val to = File(temporaryFolder, "to.txt") - from.write("source file") - to.write("target file") - fileSystem.rename(from, to) - } - - @ParameterizedTest - @ArgumentsSource(FileSystemParamProvider::class) - fun `rename source is open fails on Windows`( - parameters: Pair - ) { - setUp(parameters.first, parameters.second) - val from = File(temporaryFolder, "from.txt") - val to = File(temporaryFolder, "to.txt") - from.write("source file") - to.write("target file") - expectIOExceptionOnWindows { - fileSystem.source(from).use { - fileSystem.rename(from, to) - } - } - } - - @ParameterizedTest - @ArgumentsSource(FileSystemParamProvider::class) - fun `rename target is open fails on Windows`( - parameters: Pair - ) { - setUp(parameters.first, parameters.second) - val from = File(temporaryFolder, "from.txt") - val to = File(temporaryFolder, "to.txt") - from.write("source file") - to.write("target file") - expectIOExceptionOnWindows { - fileSystem.source(to).use { - fileSystem.rename(from, to) - } - } - } - - @ParameterizedTest - @ArgumentsSource(FileSystemParamProvider::class) - fun `delete contents of parent of file open for reading fails on Windows`( - parameters: Pair - ) { - setUp(parameters.first, parameters.second) - val parentA = File(temporaryFolder, "a").also { it.mkdirs() } - val parentAB = File(parentA, "b") - val parentABC = File(parentAB, "c") - val file = File(parentABC, "file.txt") - file.write("child file") - expectIOExceptionOnWindows { - fileSystem.source(file).use { - fileSystem.deleteContents(parentA) - } - } - } - - private fun File.write(content: String) { - fileSystem.sink(this).buffer().use { - it.writeUtf8(content) - } - } - - private fun expectIOExceptionOnWindows(block: () -> Unit) { - try { - block() - assertThat(windows).isFalse() - } catch (_: IOException) { - assertThat(windows).isTrue() - } - } -}