Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions okhttp-testing-support/src/main/kotlin/okhttp3/TestUtil.kt
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
*/
package okhttp3

import java.io.File
import java.net.InetAddress
import java.net.InetSocketAddress
import java.net.UnknownHostException
Expand Down Expand Up @@ -42,6 +43,12 @@ object TestUtil {
return String(array)
}

tailrec fun File.isDescendentOf(directory: File): Boolean {
val parentFile = parentFile ?: return false
if (parentFile == directory) return true
return parentFile.isDescendentOf(directory)
}

/**
* See FinalizationTester for discussion on how to best trigger GC in tests.
* https://android.googlesource.com/platform/libcore/+/master/support/src/test/java/libcore/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import java.io.File
import java.io.FileNotFoundException
import java.io.IOException
import java.util.IdentityHashMap
import okhttp3.TestUtil.isDescendentOf
import okio.Buffer
import okio.ForwardingSink
import okio.ForwardingSource
Expand All @@ -30,70 +31,52 @@ import org.junit.runners.model.Statement

/** A simple file system where all files are held in memory. Not safe for concurrent use. */
class InMemoryFileSystem : FileSystem, TestRule {
private val files: MutableMap<File, Buffer> = mutableMapOf()
private val openSources: MutableMap<Source, File> = IdentityHashMap()
private val openSinks: MutableMap<Sink, File> = IdentityHashMap()
private val files = mutableMapOf<File, Buffer>()
private val openSources = IdentityHashMap<Source, File>()
private val openSinks = IdentityHashMap<Sink, File>()

override fun apply(
base: Statement,
description: Description
): Statement {
override fun apply(base: Statement, description: Description): Statement {
return object : Statement() {
@Throws(Throwable::class) override fun evaluate() {
override fun evaluate() {
base.evaluate()
ensureResourcesClosed()
}
}
}

fun ensureResourcesClosed() {
val openResources: MutableList<String> = mutableListOf()
val openResources = mutableListOf<String>()
for (file in openSources.values) {
openResources.add("Source for $file")
}
for (file in openSinks.values) {
openResources.add("Sink for $file")
}
if (!openResources.isEmpty()) {
val builder =
StringBuilder("Resources acquired but not closed:")
for (resource in openResources) {
builder.append("\n * ")
.append(resource)
}
throw IllegalStateException(builder.toString())
check(openResources.isEmpty()) {
"Resources acquired but not closed:\n * ${openResources.joinToString(separator = "\n * ")}"
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice

}
}

@Throws(
FileNotFoundException::class
) override fun source(file: File): Source {
@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) {
@Throws(IOException::class) override fun close() {
override fun close() {
openSources.remove(source)
super.close()
}
}
}

@Throws(FileNotFoundException::class)
override fun sink(file: File): Sink {
return sink(file, false)
}
override fun sink(file: File) = sink(file, false)

@Throws(
FileNotFoundException::class
) override fun appendingSink(file: File): Sink {
return sink(file, true)
}
@Throws(FileNotFoundException::class)
override fun appendingSink(file: File) = sink(file, true)

private fun sink(
file: File,
appending: Boolean
): Sink {
private fun sink(file: File, appending: Boolean): Sink {
var result: Buffer? = null
if (appending) {
result = files[file]
Expand All @@ -105,7 +88,7 @@ class InMemoryFileSystem : FileSystem, TestRule {
val sink: Sink = result
openSinks[sink] = file
return object : ForwardingSink(sink) {
@Throws(IOException::class) override fun close() {
override fun close() {
openSinks.remove(sink)
super.close()
}
Expand All @@ -117,33 +100,21 @@ class InMemoryFileSystem : FileSystem, TestRule {
files.remove(file)
}

override fun exists(file: File): Boolean {
return files.containsKey(file)
}
override fun exists(file: File) = files.containsKey(file)

override fun size(file: File): Long {
val buffer = files[file]
return buffer?.size ?: 0L
}
override fun size(file: File) = files[file]?.size ?: 0L

@Throws(IOException::class) override fun rename(
from: File,
to: File
) {
val buffer = files.remove(from) ?: throw FileNotFoundException()
files[to] = buffer
@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 prefix = "$directory/"
@Throws(IOException::class)
override fun deleteContents(directory: File) {
val i = files.keys.iterator()
while (i.hasNext()) {
val file = i.next()
if (file.toString()
.startsWith(prefix)
) i.remove()
if (file.isDescendentOf(directory)) i.remove()
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ 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
Expand Down Expand Up @@ -65,12 +66,6 @@ class WindowsFileSystem(val delegate: FileSystem) : FileSystem {
delegate.deleteContents(directory)
}

private tailrec fun File.isDescendentOf(directory: File): Boolean {
val parentFile = parentFile ?: return false
if (parentFile == directory) return true
return parentFile.isDescendentOf(directory)
}

private inner class FileSink(val file: File, delegate: Sink) : ForwardingSink(delegate) {
var closed = false

Expand Down
25 changes: 25 additions & 0 deletions okhttp/src/main/kotlin/okhttp3/internal/Util.kt
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
package okhttp3.internal

import java.io.Closeable
import java.io.File
import java.io.IOException
import java.io.InterruptedIOException
import java.net.InetSocketAddress
Expand Down Expand Up @@ -48,6 +49,7 @@ 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
Expand Down Expand Up @@ -514,6 +516,29 @@ fun ServerSocket.closeQuietly() {
}
}

/**
* Returns true if file streams can be manipulated independently of their paths. This is typically
* true for systems like Mac, Unix, and Linux that use inodes in their file system interface. It is
* typically false on Windows.
*
* If this returns false we won't permit simultaneous reads and writes. When writes commit we need
* to delete the previous snapshots, and that won't succeed if the file is open. (We do permit
* multiple simultaneous reads.)
*
* @param file a file in the directory to check. This file shouldn't already exist!
*/
fun FileSystem.isCivilized(file: File): Boolean {
sink(file).use {
try {
delete(file)
return true
} catch (_: IOException) {
}
}
delete(file)
return false
}

fun Long.toHexString(): String = java.lang.Long.toHexString(this)

fun Int.toHexString(): String = Integer.toHexString(this)
Expand Down
Loading