BuiltInWebServerTest

This commit is contained in:
Vladimir Krivosheev
2015-10-06 17:56:36 +02:00
parent bf682470e0
commit af7dff8682
11 changed files with 200 additions and 105 deletions
@@ -7,10 +7,13 @@ import com.intellij.openapi.vfs.VfsUtilCore
import com.intellij.openapi.vfs.VirtualFile
import java.io.File
class PathInfo(val ioFile: File?, val file: VirtualFile?, val root: VirtualFile, moduleName: String? = null, private val isLibrary: Boolean = false) {
class PathInfo(val ioFile: File?, val file: VirtualFile?, val root: VirtualFile, moduleName: String? = null, val isLibrary: Boolean = false) {
var moduleName: String? = moduleName
set
/**
* URL path.
*/
val path: String by lazy {
val builder = StringBuilder()
if (moduleName != null) {
@@ -30,6 +33,11 @@ class PathInfo(val ioFile: File?, val file: VirtualFile?, val root: VirtualFile,
builder.toString()
}
/**
* System-dependent path to file.
*/
val filePath: String by lazy { if (ioFile == null) FileUtilRt.toSystemDependentName(file!!.path) else ioFile.path }
val isValid: Boolean
get() = if (ioFile == null) file!!.isValid else ioFile.exists()
@@ -38,4 +46,8 @@ class PathInfo(val ioFile: File?, val file: VirtualFile?, val root: VirtualFile,
val fileType: FileType
get() = if (ioFile == null) file!!.fileType else FileTypeManager.getInstance().getFileTypeByFileName(ioFile.name)
val isDirectory: Boolean
get() = if (ioFile == null) file!!.isDirectory else ioFile.isDirectory
}
@@ -49,31 +49,29 @@ private class DefaultWebServerPathHandler : WebServerPathHandler() {
return false
}
}
else {
var virtualFile = pathInfo.file
val isDirectory = if (virtualFile == null) pathInfo.ioFile!!.isDirectory else virtualFile.isDirectory
if (isDirectory) {
if (!endsWithSlash(decodedRawPath)) {
WebServerPathHandler.redirectToDirectory(request, channel, if (isCustomHost) path else (projectName + '/' + path))
return true
}
if (virtualFile == null) {
virtualFile = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(pathInfo.ioFile!!)
}
virtualFile = if (virtualFile == null) null else findIndexFile(virtualFile)
if (virtualFile == null) {
Responses.sendStatus(HttpResponseStatus.NOT_FOUND, channel, "Index file doesn't exist.", request)
return true
}
indexUsed = true
}
}
pathToFileManager.pathToInfoCache.put(path, pathInfo)
}
else if (!endsWithName(path, pathInfo.name)) {
if (pathInfo.isDirectory) {
if (!endsWithSlash(decodedRawPath)) {
WebServerPathHandler.redirectToDirectory(request, channel, if (isCustomHost) path else ("$projectName/$path"))
return true
}
var virtualFile = pathInfo.file ?: LocalFileSystem.getInstance().refreshAndFindFileByIoFile(pathInfo.ioFile!!)
virtualFile = if (virtualFile == null) null else findIndexFile(virtualFile)
if (virtualFile == null) {
Responses.sendStatus(HttpResponseStatus.NOT_FOUND, channel, "Index file doesn't exist.", request)
return true
}
indexUsed = true
pathInfo = PathInfo(null, virtualFile, pathInfo.root, pathInfo.moduleName, pathInfo.isLibrary)
pathToFileManager.pathToInfoCache.put(path, pathInfo)
}
if (!indexUsed && !endsWithName(path, pathInfo.name)) {
if (endsWithSlash(decodedRawPath)) {
indexUsed = true
}
@@ -67,10 +67,12 @@ class DefaultWebServerRootsProvider : WebServerRootsProvider() {
val modules = runReadAction { ModuleManager.getInstance(project).modules }
val resolver = WebServerPathToFileManager.getInstance(project).getResolver(effectivePath)
for (rootProvider in RootProvider.values()) {
val result = findByRelativePath(project, effectivePath, modules, rootProvider, resolver)
if (result != null) {
return result
if (!modules.isEmpty()) {
for (rootProvider in RootProvider.values()) {
val result = findByRelativePath(project, effectivePath, modules, rootProvider, resolver)
if (result != null) {
return result
}
}
}
return findInLibraries(project, modules, effectivePath, resolver)
@@ -61,7 +61,7 @@ class WebServerPathToFileManager(application: Application, private val project:
return pathInfo.file ?: return LocalFileSystem.getInstance().findFileByIoFile(pathInfo.ioFile!!)
}
fun getPathInfo(path: String, cacheResult: Boolean = true): PathInfo? {
@JvmOverloads fun getPathInfo(path: String, cacheResult: Boolean = true): PathInfo? {
var pathInfo = pathToInfoCache.getIfPresent(path)
if (pathInfo == null || !pathInfo.isValid) {
pathInfo = doFindByRelativePath(path)
@@ -33,7 +33,7 @@ class FastCgiRequest(val requestId: Int, allocator: ByteBufAllocator) {
fun writeFileHeaders(pathInfo: PathInfo, canonicalRequestPath: CharSequence) {
val root = pathInfo.root
addHeader("DOCUMENT_ROOT", root.path.separatorsToSystem())
addHeader("SCRIPT_FILENAME", if (pathInfo.ioFile == null) pathInfo.file!!.path.separatorsToSystem() else pathInfo.ioFile!!.path)
addHeader("SCRIPT_FILENAME", pathInfo.filePath)
addHeader("SCRIPT_NAME", canonicalRequestPath)
}
@@ -0,0 +1,57 @@
package org.jetbrains.ide
import com.intellij.testFramework.DisposeModulesRule
import com.intellij.testFramework.ProjectRule
import com.intellij.testFramework.RuleChain
import com.intellij.testFramework.TemporaryDirectory
import io.netty.handler.codec.http.HttpResponseStatus
import org.assertj.core.api.Assertions.assertThat
import org.junit.ClassRule
import org.junit.Rule
import org.junit.rules.Timeout
import java.net.HttpURLConnection
import java.net.URL
import java.util.concurrent.TimeUnit
internal abstract class BuiltInServerTestCase {
companion object {
@ClassRule val projectRule = ProjectRule()
}
protected val tempDirManager = TemporaryDirectory()
protected val manager = TestManager(projectRule, tempDirManager)
private val ruleChain = RuleChain(
tempDirManager,
Timeout(60, TimeUnit.SECONDS),
manager,
DisposeModulesRule(projectRule))
@Rule fun getChain() = ruleChain
protected open val urlPathPrefix = ""
protected fun doTest(filePath: String? = manager.filePath, additionalCheck: ((connection: HttpURLConnection) -> Unit)? = null) {
val serviceUrl = "http://localhost:${BuiltInServerManager.getInstance().port}$urlPathPrefix"
var url = serviceUrl + (if (filePath == null) "" else ("/$filePath"))
val line = manager.annotation?.line ?: -1
if (line != -1) {
url += ":$line"
}
val column = manager.annotation?.column ?: -1
if (column != -1) {
url += ":$column"
}
val connection = URL(url).openConnection() as HttpURLConnection
val expectedStatus = HttpResponseStatus.valueOf(manager.annotation?.status ?: 200)
assertThat(HttpResponseStatus.valueOf(connection.responseCode)).isEqualTo(expectedStatus)
check(serviceUrl, expectedStatus)
if (additionalCheck != null) {
additionalCheck(connection)
}
}
protected open fun check(serviceUrl: String, expectedStatus: HttpResponseStatus) {
}
}
@@ -0,0 +1,56 @@
package org.jetbrains.ide
import com.intellij.openapi.application.runWriteAction
import com.intellij.openapi.module.EmptyModuleType
import com.intellij.openapi.module.ModuleManager
import com.intellij.openapi.roots.ModuleRootModificationUtil
import com.intellij.testFramework.refreshVfs
import com.intellij.testFramework.runInEdtAndWait
import com.intellij.testFramework.systemIndependentPath
import com.intellij.testFramework.writeChild
import org.assertj.core.api.Assertions.assertThat
import org.junit.Test
private class BuiltInWebServerTest : BuiltInServerTestCase() {
override val urlPathPrefix: String
get() = "/${BuiltInServerTestCase.projectRule.project.name}"
@Test
@TestManager.TestDescriptor(filePath = "foo/index.html", doNotCreate = true, status = 200)
fun `get only dir without end slash`() {
testIndex("foo")
}
@Test
@TestManager.TestDescriptor(filePath = "foo/index.html", doNotCreate = true, status = 200)
fun `get only dir with end slash`() {
testIndex("foo/")
}
@Test
@TestManager.TestDescriptor(filePath = "foo/index.html", doNotCreate = true, status = 200)
fun `get index file and then dir`() {
testIndex("foo/index.html", "foo")
}
private fun testIndex(vararg paths: String) {
val project = BuiltInServerTestCase.projectRule.project
val newPath = tempDirManager.newPath(refreshVfs = false)
newPath.writeChild(manager.filePath!!, "hello")
newPath.refreshVfs()
runInEdtAndWait {
runWriteAction {
val systemIndependentPath = newPath.systemIndependentPath
val module = ModuleManager.getInstance(project).newModule("$systemIndependentPath/test.iml", EmptyModuleType.EMPTY_MODULE)
ModuleRootModificationUtil.addContentRoot(module, systemIndependentPath)
}
}
for (path in paths) {
doTest(path) {
assertThat(it.inputStream.reader().readText()).isEqualTo("hello")
}
}
}
}
@@ -2,32 +2,17 @@ package org.jetbrains.ide
import com.google.gson.stream.JsonWriter
import com.intellij.openapi.vfs.CharsetToolkit
import com.intellij.testFramework.ProjectRule
import com.intellij.testFramework.RuleChain
import com.intellij.testFramework.TemporaryDirectory
import io.netty.handler.codec.http.HttpResponseStatus
import org.assertj.core.api.Assertions.assertThat
import org.jetbrains.ide.TestManager.TestDescriptor
import org.junit.ClassRule
import org.junit.Rule
import org.junit.Test
import org.junit.rules.Timeout
import java.io.BufferedOutputStream
import java.io.OutputStreamWriter
import java.net.HttpURLConnection
import java.net.URL
import java.util.concurrent.TimeUnit
class RestApiTest {
companion object {
@ClassRule val projectRule = ProjectRule()
}
private val tempDirManager = TemporaryDirectory()
private val manager = TestManager(projectRule, tempDirManager)
private val ruleChain = RuleChain(tempDirManager, Timeout(60, TimeUnit.SECONDS), manager)
@Rule fun getChain() = ruleChain
private class RestApiTest : BuiltInServerTestCase() {
override val urlPathPrefix = "/api/file"
@Test
@TestDescriptor(filePath = "", status = 400)
@@ -77,23 +62,11 @@ class RestApiTest {
doTest()
}
private fun doTest() {
val serviceUrl = "http://localhost:${BuiltInServerManager.getInstance().port}/api/file"
var url = serviceUrl + (if (manager.filePath == null) "" else ("/${manager.filePath}"))
override fun check(serviceUrl: String, expectedStatus: HttpResponseStatus) {
val line = manager.annotation?.line ?: -1
if (line != -1) {
url += ":$line"
}
val column = manager.annotation?.column ?: -1
if (column != -1) {
url += ":$column"
}
var connection = URL(url).openConnection() as HttpURLConnection
val expectedStatus = HttpResponseStatus.valueOf(manager.annotation?.status ?: 200)
assertThat(HttpResponseStatus.valueOf(connection.responseCode)).isEqualTo(expectedStatus)
connection = URL("$serviceUrl?file=${manager.filePath ?: ""}&line=$line&column=$column").openConnection() as HttpURLConnection
var connection = URL("$serviceUrl?file=${manager.filePath ?: ""}&line=$line&column=$column").openConnection() as HttpURLConnection
assertThat(HttpResponseStatus.valueOf(connection.responseCode)).isEqualTo(expectedStatus)
connection = URL("$serviceUrl").openConnection() as HttpURLConnection
@@ -109,3 +82,4 @@ class RestApiTest {
assertThat(HttpResponseStatus.valueOf(connection.responseCode)).isEqualTo(expectedStatus)
}
}
+16 -25
View File
@@ -10,16 +10,10 @@ import com.intellij.testFramework.*
import org.junit.rules.TestWatcher
import org.junit.runner.Description
import java.io.File
import java.lang.annotation.ElementType
import java.lang.annotation.Retention
import java.lang.annotation.RetentionPolicy
import java.lang.annotation.Target
class TestManager(val projectRule: ProjectRule, private val tempDirManager: TemporaryDirectory) : TestWatcher() {
companion object {
private val EXCLUDED_DIR_NAME = "excludedDir"
}
private val EXCLUDED_DIR_NAME = "excludedDir"
internal class TestManager(val projectRule: ProjectRule, private val tempDirManager: TemporaryDirectory) : TestWatcher() {
var annotation: TestDescriptor? = null
var filePath: String? = null
@@ -27,15 +21,14 @@ class TestManager(val projectRule: ProjectRule, private val tempDirManager: Temp
private var ioFileToDelete: File? = null
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
annotation public class TestDescriptor(public val filePath: String,
public val line: Int = -1,
public val column: Int = -1,
public val relativeToProject: Boolean = false,
public val excluded: Boolean = false,
public val doNotCreate: Boolean = false,
public val status: Int = 200)
@Target(AnnotationTarget.FUNCTION)
annotation class TestDescriptor(val filePath: String,
val line: Int = -1,
val column: Int = -1,
val relativeToProject: Boolean = false,
val excluded: Boolean = false,
val doNotCreate: Boolean = false,
val status: Int = 200)
override fun starting(description: Description) {
annotation = description.getAnnotation(TestDescriptor::class.java)
@@ -49,9 +42,7 @@ class TestManager(val projectRule: ProjectRule, private val tempDirManager: Temp
}
// trigger project creation
runInEdtAndWait {
projectRule.project
}
projectRule.project
if (filePath!! == "_tmp_") {
val file = tempDirManager.newPath(".txt")
@@ -69,7 +60,7 @@ class TestManager(val projectRule: ProjectRule, private val tempDirManager: Temp
runInEdtAndWait {
val normalizedFilePath = FileUtilRt.toSystemIndependentName(filePath!!)
if (annotation!!.relativeToProject) {
val root = projectRule.project.getBaseDir()
val root = projectRule.project.baseDir
runWriteAction {
fileToDelete = root.findOrCreateChildData(this@TestManager, normalizedFilePath)
}
@@ -78,8 +69,8 @@ class TestManager(val projectRule: ProjectRule, private val tempDirManager: Temp
val module = projectRule.module
if (annotation!!.excluded) {
ModuleRootModificationUtil.updateModel(module) { model ->
val contentEntry = model.getContentEntries()[0]
val contentRoot = contentEntry.getFile()!!
val contentEntry = model.contentEntries[0]
val contentRoot = contentEntry.file!!
runWriteAction {
contentRoot.findChild(EXCLUDED_DIR_NAME)?.delete(this@TestManager)
fileToDelete = contentRoot.createChildDirectory(this@TestManager, EXCLUDED_DIR_NAME)
@@ -91,7 +82,7 @@ class TestManager(val projectRule: ProjectRule, private val tempDirManager: Temp
filePath = "$EXCLUDED_DIR_NAME/$filePath"
}
else {
val root = ModuleRootManager.getInstance(module).getSourceRoots()[0]
val root = ModuleRootManager.getInstance(module).sourceRoots[0]
runWriteAction {
fileToDelete = root.findOrCreateChildData(this@TestManager, normalizedFilePath)
}
@@ -102,7 +93,7 @@ class TestManager(val projectRule: ProjectRule, private val tempDirManager: Temp
override fun finished(description: Description?) {
if (annotation!!.excluded) {
ModuleRootModificationUtil.updateModel(projectRule.module) { model -> model.getContentEntries()[0].removeExcludeFolder(EXCLUDED_DIR_NAME) }
ModuleRootModificationUtil.updateModel(projectRule.module) { model -> model.contentEntries[0].removeExcludeFolder(EXCLUDED_DIR_NAME) }
}
if (fileToDelete != null) {
@@ -46,6 +46,8 @@ import java.io.PrintStream
import java.nio.file.Files
import java.util.concurrent.atomic.AtomicBoolean
private var sharedModule: Module? = null
/**
* Project created on request, so, could be used as a bare (only application).
*/
@@ -56,7 +58,6 @@ class ProjectRule() : ExternalResource() {
}
private var sharedProject: ProjectEx? = null
private var sharedModule: Module? = null
private val projectOpened = AtomicBoolean()
private fun createLightProject(): ProjectEx {
@@ -234,30 +235,34 @@ inline fun <T> Project.runInLoadComponentStateMode(task: () -> T): T {
class DisposeModulesRule(private val projectRule: ProjectRule) : ExternalResource() {
override fun after() {
projectRule.projectIfOpened?.let {
var errors: MutableList<Throwable>? = null
val moduleManager = ModuleManager.getInstance(it)
runInEdtAndWait {
for (module in moduleManager.modules) {
if (module.isDisposed) {
continue
}
try {
moduleManager.disposeModule(module)
}
catch (e: Throwable) {
if (errors == null) {
errors = SmartList()
}
errors!!.add(e)
moduleManager.modules.forEachGuaranteed {
if (!it.isDisposed && it !== sharedModule) {
moduleManager.disposeModule(it)
}
}
}
CompoundRuntimeException.throwIfNotEmpty(errors)
}
}
}
inline fun <T> Array<out T>.forEachGuaranteed(operation: (T) -> Unit): Unit {
var errors: MutableList<Throwable>? = null
for (element in this) {
try {
operation(element)
}
catch (e: Throwable) {
if (errors == null) {
errors = SmartList()
}
errors.add(e)
}
}
CompoundRuntimeException.throwIfNotEmpty(errors)
}
/**
* Only and only if "before" logic in case of exception doesn't require "after" logic - must be no side effects if "before" finished abnormally.
* So, should be one task per rule.
@@ -58,9 +58,9 @@ class TemporaryDirectory : ExternalResource() {
/**
* Directory is not created.
*/
public fun newDirectory(directoryName: String? = null): File = generatePath(directoryName).toFile()
fun newDirectory(directoryName: String? = null): File = generatePath(directoryName).toFile()
public fun newPath(directoryName: String? = null, refreshVfs: Boolean = true): Path {
fun newPath(directoryName: String? = null, refreshVfs: Boolean = true): Path {
val path = generatePath(directoryName)
if (refreshVfs) {
path.refreshVfs()
@@ -79,7 +79,7 @@ class TemporaryDirectory : ExternalResource() {
return path
}
public fun newVirtualDirectory(directoryName: String? = null): VirtualFile {
fun newVirtualDirectory(directoryName: String? = null): VirtualFile {
val path = generatePath(directoryName)
path.createDirectories()
val virtualFile = LocalFileSystem.getInstance().refreshAndFindFileByPath(path.systemIndependentPath)
@@ -88,7 +88,7 @@ class TemporaryDirectory : ExternalResource() {
}
}
public fun generateTemporaryPath(fileName: String?): Path {
fun generateTemporaryPath(fileName: String?): Path {
val tempDirectory = Paths.get(FileUtilRt.getTempDirectory())
var path = tempDirectory.resolve(fileName)
var i = 0