Commit c6e476d5 authored by nik's avatar nik
Browse files

test framework: add Kotlin DSL for specifying contents of directories

It can be used to either check that a given directory matches some specification or to generate files in a directory accordingly to a specification.
parent 78ead261
Branches unavailable Tags unavailable
No related merge requests found
Showing with 396 additions and 52 deletions
+396 -52
......@@ -15,15 +15,15 @@
*/
package org.jetbrains.jps.builders
import com.intellij.util.io.directoryContent
import org.jetbrains.jps.builders.rebuild.JpsRebuildTestCase
import org.jetbrains.jps.builders.rebuild.fs
/**
* @author nik
*/
class JavacFileEncodingTest: JpsRebuildTestCase() {
fun test() {
doTest("javacFileEncoding/javacFileEncoding.ipr", fs {
doTest("javacFileEncoding/javacFileEncoding.ipr", directoryContent {
dir("production") {
dir("javacFileEncoding") {
file("MyClass.class")
......
......@@ -17,6 +17,7 @@ package org.jetbrains.jps.builders.rebuild
import com.intellij.openapi.util.io.FileUtil
import com.intellij.util.io.ZipUtil
import com.intellij.util.io.directoryContent
import java.io.File
import java.io.FileInputStream
import java.util.jar.Attributes
......@@ -29,10 +30,10 @@ class ArtifactRebuildTest: JpsRebuildTestCase() {
fun testArtifactIncludesArchiveArtifact() {
val name = "artifactIncludesArchiveArtifact"
try {
doTest("$name/${name}.ipr", fs {
doTest("$name/${name}.ipr", directoryContent {
dir("artifacts") {
dir("data") {
archive("a.jar") {
zip("a.jar") {
file("a.txt")
}
}
......@@ -49,7 +50,7 @@ class ArtifactRebuildTest: JpsRebuildTestCase() {
loadProject("artifactWithoutOutput/artifactWithoutOutput.ipr", mapOf("OUTPUT_DIR" to outDir))
rebuild()
assertOutput(outDir, fs {
assertOutput(outDir, directoryContent {
dir("artifacts") {
dir("main") {
file("data.txt")
......@@ -60,7 +61,7 @@ class ArtifactRebuildTest: JpsRebuildTestCase() {
}
fun testExtractDir() {
doTest("extractDirTest/extractDirTest.ipr", fs {
doTest("extractDirTest/extractDirTest.ipr", directoryContent {
dir("artifacts") {
dir("extractDir") {
file("b.txt", "b")
......@@ -74,12 +75,12 @@ class ArtifactRebuildTest: JpsRebuildTestCase() {
}
}
dir("packedDir") {
archive("packedDir.jar") {
zip("packedDir.jar") {
file("b.txt", "b")
}
}
dir("packedRoot") {
archive("packedRoot.jar") {
zip("packedRoot.jar") {
dir("dir") {
file("b.txt", "b")
}
......@@ -103,7 +104,7 @@ class ArtifactRebuildTest: JpsRebuildTestCase() {
}
fun testOverwriteArtifacts() {
doTest("overwriteTest/overwriteTest.ipr", fs {
doTest("overwriteTest/overwriteTest.ipr", directoryContent {
dir("artifacts") {
dir("classes") {
file("a.xml", "<root2/>")
......@@ -132,7 +133,7 @@ class ArtifactRebuildTest: JpsRebuildTestCase() {
fun testPathVariablesInArtifact() {
val externalDir = "${testDataRootPath}/pathVariables/external"
doTest("pathVariables/pathVariables.ipr", mapOf("EXTERNAL_DIR" to externalDir), fs {
doTest("pathVariables/pathVariables.ipr", mapOf("EXTERNAL_DIR" to externalDir), directoryContent {
dir("artifacts") {
dir("fileCopy") {
dir("dir") {
......@@ -144,7 +145,7 @@ class ArtifactRebuildTest: JpsRebuildTestCase() {
}
fun testModuleTestOutputElement() {
doTest("moduleTestOutput/moduleTestOutput.ipr", fs {
doTest("moduleTestOutput/moduleTestOutput.ipr", directoryContent {
dir("artifacts") {
dir("tests") {
file("MyTest.class")
......
......@@ -17,8 +17,8 @@ package org.jetbrains.jps.builders.rebuild;
import com.intellij.openapi.application.ex.PathManagerEx
import com.intellij.openapi.util.io.FileUtil
import com.intellij.util.io.TestFileSystemBuilder
import com.intellij.util.io.TestFileSystemItem
import com.intellij.util.io.DirectoryContentSpec
import com.intellij.util.io.assertMatches
import org.jetbrains.jps.builders.CompileScopeTestBuilder
import org.jetbrains.jps.builders.JpsBuildTestCase
import org.jetbrains.jps.model.java.JpsJavaExtensionService
......@@ -39,17 +39,17 @@ abstract class JpsRebuildTestCase: JpsBuildTestCase() {
addJdk("1.6");
}
fun doTest(projectPath: String, expectedOutput: TestFileSystemItem) {
fun doTest(projectPath: String, expectedOutput: DirectoryContentSpec) {
doTest(projectPath, LinkedHashMap<String, String>(), expectedOutput);
}
fun doTest(projectPath: String, pathVariables: Map<String, String>, expectedOutput: TestFileSystemItem) {
fun doTest(projectPath: String, pathVariables: Map<String, String>, expectedOutput: DirectoryContentSpec) {
loadAndRebuild(projectPath, pathVariables);
assertOutput(myOutputDirectory.absolutePath, expectedOutput);
}
fun assertOutput(targetFolder: String, expectedOutput: TestFileSystemItem) {
expectedOutput.assertDirectoryEqual(File(FileUtil.toSystemDependentName(targetFolder)));
fun assertOutput(targetFolder: String, expectedOutput: DirectoryContentSpec) {
File(targetFolder).assertMatches(expectedOutput)
}
fun loadAndRebuild(projectPath: String, pathVariables: Map<String, String>) {
......@@ -68,32 +68,4 @@ abstract class JpsRebuildTestCase: JpsBuildTestCase() {
override fun getTestDataRootPath(): String {
return PathManagerEx.findFileUnderCommunityHome("jps/jps-builders/testData/output")!!.absolutePath;
}
}
fun fs(init: TestFileSystemBuilderBuilder.() -> Unit): TestFileSystemItem {
val builder = TestFileSystemBuilder.fs()
TestFileSystemBuilderBuilder(builder).init()
return builder.build()
}
class TestFileSystemBuilderBuilder(val current: TestFileSystemBuilder) {
fun file(name: String) {
current.file(name)
}
fun file(name: String, content: String) {
current.file(name, content)
}
inline fun dir(name: String, init: TestFileSystemBuilderBuilder.() -> Unit) {
val dir = current.dir(name)
TestFileSystemBuilderBuilder(dir).init()
dir.end()
}
inline fun archive(name: String, init: TestFileSystemBuilderBuilder.() -> Unit) {
val dir = current.archive(name)
TestFileSystemBuilderBuilder(dir).init()
dir.end()
}
}
\ No newline at end of file
......@@ -15,17 +15,18 @@
*/
package org.jetbrains.jps.builders.rebuild
import org.jetbrains.jps.util.JpsPathUtil
import com.intellij.util.PathUtil
import com.intellij.util.io.directoryContent
import org.jetbrains.jps.model.java.JavaResourceRootType
import org.jetbrains.jps.model.java.JpsJavaExtensionService
import org.jetbrains.jps.util.JpsPathUtil
/**
* @author nik
*/
class ModuleRebuildTest: JpsRebuildTestCase() {
fun testModuleCycle() {
doTest("moduleCycle/moduleCycle.ipr", fs {
doTest("moduleCycle/moduleCycle.ipr", directoryContent {
dir("production") {
dir("module1") {
file("Bar1.class")
......@@ -41,7 +42,7 @@ class ModuleRebuildTest: JpsRebuildTestCase() {
}
fun testOverlappingSourceRoots() {
doTest("overlappingSourceRoots/overlappingSourceRoots.ipr", fs {
doTest("overlappingSourceRoots/overlappingSourceRoots.ipr", directoryContent {
dir("production") {
dir("inner") {
dir("y") {
......@@ -59,7 +60,7 @@ class ModuleRebuildTest: JpsRebuildTestCase() {
}
fun testContentRootUnderExcluded() {
doTest("contentRootUnderExcluded/contentRootUnderExcluded.ipr", fs {
doTest("contentRootUnderExcluded/contentRootUnderExcluded.ipr", directoryContent {
dir("production") {
dir("contentRootUnderExcluded") {
file("A.class")
......@@ -70,7 +71,7 @@ class ModuleRebuildTest: JpsRebuildTestCase() {
}
fun testSourceRootUnderExcluded() {
doTest("sourceRootUnderExcluded/sourceRootUnderExcluded.ipr", fs {
doTest("sourceRootUnderExcluded/sourceRootUnderExcluded.ipr", directoryContent {
dir("production") {
dir("sourceRootUnderExcluded") {
file("A.class")
......@@ -81,7 +82,7 @@ class ModuleRebuildTest: JpsRebuildTestCase() {
}
fun testResourceCopying() {
doTest("resourceCopying/resourceCopying.ipr", fs {
doTest("resourceCopying/resourceCopying.ipr", directoryContent {
dir("production") {
dir("resourceCopying") {
dir("copy") {
......@@ -106,7 +107,7 @@ class ModuleRebuildTest: JpsRebuildTestCase() {
val url = JpsPathUtil.pathToUrl(res)
m.addSourceRoot(url, JavaResourceRootType.RESOURCE, JpsJavaExtensionService.getInstance().createResourceRootProperties("foo", false))
rebuild()
assertOutput(getAbsolutePath("out/production/m"), fs {
assertOutput(getAbsolutePath("out/production/m"), directoryContent {
dir("foo") {
file("a.txt", "42")
}
......
/*
* Copyright 2000-2017 JetBrains s.r.o.
*
* 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 com.intellij.util.io
import org.junit.Test
import java.io.File
import kotlin.test.fail
/**
* @author nik
*/
class DirectoryContentSpecTest {
@Test
fun `files in directory`() {
val dir = directoryContent {
file("a.txt")
file("b.txt")
}.generateInTempDir()
dir.assertMatches(directoryContent {
file("a.txt")
file("b.txt")
})
dir.assertNotMatches(directoryContent {
file("a.txt")
})
dir.assertNotMatches(directoryContent {
file("a.txt")
file("b.txt")
file("c.txt")
})
}
@Test
fun `directory in directory`() {
val dir = directoryContent {
dir("a") {
file("a.txt")
}
}.generateInTempDir()
dir.assertMatches(directoryContent {
dir("a") {
file("a.txt")
}
})
dir.assertNotMatches(directoryContent {
dir("b") {
file("a.txt")
}
})
dir.assertNotMatches(directoryContent {
dir("a") {
file("b.txt")
}
})
}
@Test
fun `file content`() {
val dir = directoryContent {
file("a.txt", "text")
}.generateInTempDir()
dir.assertMatches(directoryContent {
file("a.txt", "text")
})
dir.assertMatches(directoryContent {
file("a.txt")
})
dir.assertNotMatches(directoryContent {
file("a.txt", "a")
})
}
@Test
fun `file in zip`() {
val dir = directoryContent {
zip("a.zip") {
file("a.txt", "text")
}
}.generateInTempDir()
dir.assertMatches(directoryContent {
zip("a.zip") {
file("a.txt", "text")
}
})
dir.assertNotMatches(directoryContent {
dir("a.zip") {
file("a.txt", "text")
}
})
dir.assertNotMatches(directoryContent {
zip("a.zip") {
file("a.txt", "a")
}
})
}
}
private fun File.assertNotMatches(spec: DirectoryContentSpec) {
try {
assertMatches(spec)
fail("File matches to spec by it must not")
}
catch (ignored: AssertionError) {
}
}
\ No newline at end of file
/*
* Copyright 2000-2017 JetBrains s.r.o.
*
* 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 com.intellij.util.io
import com.intellij.util.io.impl.*
import java.io.File
/**
* Builds a data structure specifying content (files, their content, sub-directories, archives) of a directory. It can be used to either check
* that a given directory matches this specification or to generate files in a directory accordingly to the specification.
*
* @author nik
*/
inline fun directoryContent(content: DirectoryContentBuilder.() -> Unit): DirectoryContentSpec {
val builder = DirectoryContentBuilderImpl(DirectorySpec())
builder.content()
return builder.result
}
abstract class DirectoryContentBuilder {
/**
* File with name [name] and any content
*/
abstract fun file(name: String)
abstract fun file(name: String, text: String)
inline fun dir(name: String, content: DirectoryContentBuilder.() -> Unit) {
val dirDefinition = DirectorySpec()
DirectoryContentBuilderImpl(dirDefinition).content()
addChild(name, dirDefinition)
}
inline fun zip(name: String, content: DirectoryContentBuilder.() -> Unit) {
val zipDefinition = ZipSpec()
DirectoryContentBuilderImpl(zipDefinition).content()
addChild(name, zipDefinition)
}
/**
* This method isn't supposed to be called directly, use other methods instead.
*/
abstract fun addChild(name: String, spec: DirectoryContentSpecImpl)
}
interface DirectoryContentSpec {
/**
* Generates files, directories and archives accordingly to this specification in [target] directory
*/
fun generate(target: File)
/**
* Generates files, directories and archives accordingly to this specification in a temp directory and return that directory.
*/
fun generateInTempDir(): File
}
/**
* Checks that contents of the given directory matches [spec].
*/
fun File.assertMatches(spec: DirectoryContentSpec) {
assertDirectoryContentMatches(this, spec as DirectoryContentSpecImpl, "")
}
\ No newline at end of file
......@@ -3,6 +3,8 @@ package com.intellij.util.io;
import org.jetbrains.annotations.NotNull;
/**
* Consider using {@link com.intellij.util.io.DirectoryContentBuilder} instead, it provides more convenient Kotlin DSL.
*
* @author nik
*/
public class TestFileSystemBuilder {
......
......@@ -27,6 +27,8 @@ import java.util.Map;
import java.util.Set;
/**
* Consider using {@link com.intellij.util.io.DirectoryContentBuilder} instead, it provides more convenient Kotlin DSL.
*
* @author nik
*/
public class TestFileSystemItem {
......
/*
* Copyright 2000-2017 JetBrains s.r.o.
*
* 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 com.intellij.util.io.impl
import com.intellij.openapi.util.io.FileUtil
import com.intellij.openapi.vfs.CharsetToolkit
import com.intellij.util.io.DirectoryContentBuilder
import com.intellij.util.io.DirectoryContentSpec
import com.intellij.util.io.ZipUtil
import org.junit.Assert.*
import java.io.BufferedOutputStream
import java.io.File
import java.io.IOException
import java.util.*
import java.util.zip.ZipOutputStream
/**
* @author nik
*/
sealed class DirectoryContentSpecImpl : DirectoryContentSpec {
}
abstract class DirectorySpecBase : DirectoryContentSpecImpl() {
protected val children = LinkedHashMap<String, DirectoryContentSpecImpl>()
fun addChild(name: String, spec: DirectoryContentSpecImpl) {
if (name in children) {
throw IllegalArgumentException("'$name' already exists")
}
children[name] = spec
}
protected fun generateInDirectory(target: File) {
for ((name, child) in children) {
child.generate(File(target, name))
}
}
override fun generateInTempDir(): File {
val target = FileUtil.createTempDirectory("directory-by-spec", null, true)
generate(target)
return target
}
fun getChildren() : Map<String, DirectoryContentSpecImpl> = Collections.unmodifiableMap(children)
}
class DirectorySpec : DirectorySpecBase() {
override fun generate(target: File) {
if (!FileUtil.createDirectory(target)) {
throw IOException("Cannot create directory $target")
}
generateInDirectory(target)
}
}
class ZipSpec : DirectorySpecBase() {
override fun generate(target: File) {
val contentDir = FileUtil.createTempDirectory("zip-content", null, false)
generateInDirectory(contentDir)
ZipOutputStream(BufferedOutputStream(target.outputStream())).use {
ZipUtil.addDirToZipRecursively(it, null, contentDir, "",null, null)
}
FileUtil.delete(contentDir)
}
}
class FileSpec(val content: ByteArray?) : DirectoryContentSpecImpl() {
override fun generate(target: File) {
FileUtil.writeToFile(target, content ?: ByteArray(0))
}
override fun generateInTempDir(): File {
val target = FileUtil.createTempFile("file-by-spec", null, true)
generate(target)
return target
}
}
class DirectoryContentBuilderImpl(val result: DirectorySpecBase) : DirectoryContentBuilder() {
override fun addChild(name: String, spec: DirectoryContentSpecImpl) {
result.addChild(name, spec)
}
override fun file(name: String) {
addChild(name, FileSpec(null))
}
override fun file(name: String, text: String) {
addChild(name, FileSpec(text.toByteArray()))
}
}
fun assertDirectoryContentMatches(file: File, spec: DirectoryContentSpecImpl, relativePath: String) {
when (spec) {
is DirectorySpec -> {
assertDirectoryMatches(file, spec, relativePath)
}
is ZipSpec -> {
val dirForExtracted = FileUtil.createTempDirectory("extracted-${file.name}", null, false)
ZipUtil.extract(file, dirForExtracted, null)
assertDirectoryMatches(dirForExtracted, spec, relativePath)
FileUtil.delete(dirForExtracted)
}
is FileSpec -> {
assertTrue("$file is not a file", file.isFile)
if (spec.content != null) {
val actualBytes = FileUtil.loadFileBytes(file)
if (!Arrays.equals(actualBytes, spec.content)) {
val actualString = actualBytes.convertToText()
val expectedString = spec.content.convertToText()
val place = if (relativePath != "") " at $relativePath" else ""
if (actualString != null && expectedString != null) {
assertEquals("File content mismatch$place:", expectedString, actualString)
}
else {
fail("Binary file content mismatch$place")
}
}
}
}
}
}
private fun ByteArray.convertToText(): String? {
val encoding = CharsetToolkit(this, Charsets.UTF_8).guessFromContent(size)
val charset = when (encoding) {
CharsetToolkit.GuessedEncoding.SEVEN_BIT -> Charsets.US_ASCII
CharsetToolkit.GuessedEncoding.VALID_UTF8 -> Charsets.UTF_8
else -> return null
}
return String(this, charset)
}
private fun assertDirectoryMatches(file: File, spec: DirectorySpecBase, relativePath: String) {
assertTrue("$file is not a directory", file.isDirectory)
val actualChildrenNames = file.list().sortedWith(String.CASE_INSENSITIVE_ORDER)
val children = spec.getChildren()
val expectedChildrenNames = children.keys.sortedWith(String.CASE_INSENSITIVE_ORDER)
assertEquals("Directory content mismatch${if (relativePath != "") " at $relativePath" else ""}:",
expectedChildrenNames.joinToString("\n"), actualChildrenNames.joinToString("\n"))
actualChildrenNames.forEach { child ->
assertDirectoryContentMatches(File(file, child), children[child]!!, "$relativePath/$child")
}
}
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment