implement scheme overrides read-only

Settings Repository tests uses scheme manager test classes (processor, scheme, helper methods), so, we move scheme manager test from idea-tests to platform-tests (as it should  be) to avoid dependency on java test framework.
This commit is contained in:
Vladimir Krivosheev
2015-07-02 14:10:02 +02:00
parent 460c86d453
commit f18b59f9cd
13 changed files with 443 additions and 20 deletions
@@ -1,5 +1,6 @@
package org.jetbrains.ide
import com.intellij.openapi.application.invokeAndWaitIfNeed
import com.intellij.openapi.application.writeAction
import com.intellij.openapi.roots.ModuleRootManager
import com.intellij.openapi.roots.ModuleRootModificationUtil
@@ -7,7 +8,6 @@ import com.intellij.openapi.util.io.FileUtilRt
import com.intellij.openapi.util.text.StringUtil
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.testFramework.fixtures.IdeaProjectTestFixture
import org.jetbrains.testFramework.invokeAndWaitIfNeed
import org.junit.rules.TestWatcher
import org.junit.runner.Description
import java.io.File
@@ -15,6 +15,8 @@
*/
package com.intellij.openapi.application
import javax.swing.SwingUtilities
public inline fun writeAction(runnable: () -> Unit) {
val token = WriteAction.start()
try {
@@ -23,4 +25,14 @@ public inline fun writeAction(runnable: () -> Unit) {
finally {
token.finish()
}
}
}
public fun invokeAndWaitIfNeed(runnable: () -> Unit) {
val app = ApplicationManager.getApplication()
if (app == null) {
if (SwingUtilities.isEventDispatchThread()) runnable() else SwingUtilities.invokeAndWait(runnable)
}
else {
if (app.isDispatchThread()) runnable() else app.invokeAndWait(runnable, ModalityState.any())
}
}
@@ -43,7 +43,7 @@ public interface StreamProvider {
/**
* You must close passed input stream.
*/
public open fun processChildren(path: String, roamingType: RoamingType, filter: (name: String) -> Boolean, processor: (name: String, input: InputStream) -> Boolean) {
public open fun processChildren(path: String, roamingType: RoamingType, filter: (name: String) -> Boolean, processor: (name: String, input: InputStream, readOnly: Boolean) -> Boolean) {
for (name in listSubFiles(path, roamingType)) {
if (!filter(name)) {
continue
@@ -59,7 +59,7 @@ public interface StreamProvider {
}
if (input != null && !processor(name, input)) {
if (input != null && !processor(name, input, false)) {
break
}
}
@@ -61,7 +61,7 @@ public class SchemeManagerImpl<T : Scheme, E : ExternalizableScheme>(private val
private val provider: StreamProvider?,
private val ioDirectory: File) : SchemesManager<T, E>(), SafeWriteRequestor {
private val schemes = ArrayList<T>()
private val bundledExternalizableSchemes = THashMap<String, E>()
private val readOnlyExternalizableSchemes = THashMap<String, E>()
private var currentScheme: T? = null
private var currentSchemeName: String? = null
@@ -215,7 +215,7 @@ public class SchemeManagerImpl<T : Scheme, E : ExternalizableScheme>(private val
@suppress("UNCHECKED_CAST")
val oldInfo = schemeToInfo.put(scheme as E, info)
LOG.assertTrue(oldInfo == null)
val oldScheme = bundledExternalizableSchemes.put(scheme.getName(), scheme)
val oldScheme = readOnlyExternalizableSchemes.put(scheme.getName(), scheme)
if (oldScheme != null) {
LOG.warn("Duplicated scheme ${scheme.getName()} - old: $oldScheme, new $scheme")
}
@@ -250,8 +250,11 @@ public class SchemeManagerImpl<T : Scheme, E : ExternalizableScheme>(private val
override fun loadSchemes(): Collection<E> {
val newSchemesOffset = schemes.size()
if (provider != null && provider.isEnabled()) {
provider.processChildren(fileSpec, roamingType, { canRead(it) }) { name, input ->
loadScheme(name, input, true)
provider.processChildren(fileSpec, roamingType, { canRead(it) }) { name, input, readOnly ->
val scheme = loadScheme(name, input, true)
if (readOnly && scheme != null) {
readOnlyExternalizableSchemes.put(scheme.name, scheme)
}
true
}
}
@@ -304,7 +307,7 @@ public class SchemeManagerImpl<T : Scheme, E : ExternalizableScheme>(private val
}
private fun isOverwriteOnLoad(existingScheme: E): Boolean {
if (bundledExternalizableSchemes.get(existingScheme.getName()) === existingScheme) {
if (readOnlyExternalizableSchemes.get(existingScheme.getName()) === existingScheme) {
// so, bundled scheme is shadowed
return true
}
@@ -488,7 +491,7 @@ public class SchemeManagerImpl<T : Scheme, E : ExternalizableScheme>(private val
}
// save only if scheme differs from bundled
val bundledScheme = bundledExternalizableSchemes.get(scheme.getName())
val bundledScheme = readOnlyExternalizableSchemes.get(scheme.getName())
if (bundledScheme != null && schemeToInfo.get(bundledScheme)!!.hash == newHash) {
externalInfo?.scheduleDelete()
return
@@ -651,7 +654,7 @@ public class SchemeManagerImpl<T : Scheme, E : ExternalizableScheme>(private val
schemeToInfo.retainEntries(object : TObjectObjectProcedure<E, ExternalInfo> {
override fun execute(scheme: E, info: ExternalInfo): Boolean {
if (bundledExternalizableSchemes.get(scheme.getName()) == scheme) {
if (readOnlyExternalizableSchemes.get(scheme.getName()) == scheme) {
return true
}
@@ -731,9 +734,7 @@ public class SchemeManagerImpl<T : Scheme, E : ExternalizableScheme>(private val
schemeToInfo.clear()
}
override fun getAllSchemes(): List<T> {
return Collections.unmodifiableList(schemes)
}
override fun getAllSchemes() = Collections.unmodifiableList(schemes)
override fun findSchemeByName(schemeName: String): T? {
for (scheme in schemes) {
@@ -789,7 +790,7 @@ public class SchemeManagerImpl<T : Scheme, E : ExternalizableScheme>(private val
return names
}
override fun isMetadataEditable(scheme: E) = !bundledExternalizableSchemes.containsKey(scheme.name)
override fun isMetadataEditable(scheme: E) = !readOnlyExternalizableSchemes.containsKey(scheme.name)
private class ExternalInfo(var fileNameWithoutExtension: String, var fileExtension: String?) {
// we keep it to detect rename
@@ -0,0 +1 @@
<scheme name="1" data="true"/>
@@ -0,0 +1 @@
<scheme name="1" data="false"/>
@@ -0,0 +1 @@
<scheme name="1"/>
@@ -0,0 +1 @@
<scheme name="first"/>
@@ -0,0 +1 @@
<scheme name="second"/>
@@ -0,0 +1 @@
<scheme name="default"/>
@@ -0,0 +1,40 @@
package com.intellij.options
import com.intellij.openapi.components.RoamingType
import com.intellij.openapi.components.impl.stores.StreamProvider
import com.intellij.openapi.util.io.FileUtil
import com.intellij.util.SmartList
import java.io.File
import java.io.FileInputStream
import java.io.InputStream
class MockStreamProvider(private val myBaseDir: File) : StreamProvider {
override fun isApplicable(fileSpec: String, roamingType: RoamingType) = roamingType === RoamingType.PER_USER
override fun saveContent(fileSpec: String, content: ByteArray, size: Int, roamingType: RoamingType) {
FileUtil.writeToFile(File(myBaseDir, fileSpec), content, 0, size)
}
override fun loadContent(fileSpec: String, roamingType: RoamingType): InputStream? {
val file = File(myBaseDir, fileSpec)
//noinspection IOResourceOpenedButNotSafelyClosed
return if (file.exists()) FileInputStream(file) else null
}
override fun listSubFiles(fileSpec: String, roamingType: RoamingType): Collection<String> {
if (roamingType !== RoamingType.PER_USER) {
return emptyList()
}
val files = File(myBaseDir, fileSpec).listFiles() ?: return emptyList()
val names = SmartList<String>()
for (file in files) {
names.add(file.getName())
}
return names
}
override fun delete(fileSpec: String, roamingType: RoamingType) {
FileUtil.delete(File(myBaseDir, fileSpec))
}
}
@@ -0,0 +1,368 @@
package com.intellij.options
import com.intellij.openapi.application.invokeAndWaitIfNeed
import com.intellij.openapi.components.RoamingType
import com.intellij.openapi.options.BaseSchemeProcessor
import com.intellij.openapi.options.ExternalizableScheme
import com.intellij.openapi.options.SchemeManagerImpl
import com.intellij.openapi.util.JDOMUtil
import com.intellij.openapi.util.io.BufferExposingByteArrayOutputStream
import com.intellij.openapi.util.io.FileUtil
import com.intellij.openapi.util.text.StringUtil
import com.intellij.testFramework.PlatformTestUtil
import com.intellij.util.SmartList
import com.intellij.util.lang.CompoundRuntimeException
import com.intellij.util.xmlb.SkipDefaultValuesSerializationFilters
import com.intellij.util.xmlb.XmlSerializer
import com.intellij.util.xmlb.annotations.Attribute
import com.intellij.util.xmlb.annotations.Tag
import com.intellij.util.xmlb.annotations.Transient
import gnu.trove.THashMap
import org.hamcrest.CoreMatchers.equalTo
import org.hamcrest.CoreMatchers.not
import org.hamcrest.CoreMatchers.notNullValue
import org.hamcrest.CoreMatchers.sameInstance
import org.hamcrest.MatcherAssert.assertThat
import org.hamcrest.collection.IsMapContaining.hasKey
import org.jdom.Element
import org.jetbrains.testFramework.FixtureRule
import org.jetbrains.testFramework.TemporaryDirectory
import org.jetbrains.testFramework.exists
import org.junit.Rule
import org.junit.Test
import java.io.File
val FILE_SPEC = "REMOTE"
class SchemeManagerTest {
private val fixtureManager = FixtureRule()
public Rule fun getFixtureManager(): FixtureRule = fixtureManager
private val tempDirManager = TemporaryDirectory()
public Rule fun getTemporaryFolder(): TemporaryDirectory = tempDirManager
private var localBaseDir: File? = null
private var remoteBaseDir: File? = null
private fun getTestDataPath() = PlatformTestUtil.getCommunityPath().replace(File.separatorChar, '/') + "/platform/platform-tests/testData/options"
public Test fun testLoadSchemes() {
doLoadSaveTest("options1", "1->first;2->second")
}
public Test fun testLoadSimpleSchemes() {
doLoadSaveTest("options", "1->1")
}
public Test fun testDeleteScheme() {
val manager = createAndLoad("options1")
manager.removeScheme(TestScheme("first"))
manager.save()
checkSchemes("2->second")
}
public Test fun testRenameScheme() {
val manager = createAndLoad("options1")
val scheme = manager.findSchemeByName("first")
assertThat(scheme, notNullValue())
assert(scheme != null)
scheme!!.setName("renamed")
manager.save()
checkSchemes("2->second;renamed->renamed")
}
public Test fun testRenameScheme2() {
val manager = createAndLoad("options1")
val first = manager.findSchemeByName("first")
assertThat(first, notNullValue())
assert(first != null)
first!!.setName("2")
val second = manager.findSchemeByName("second")
assertThat(second, notNullValue())
assert(second != null)
second!!.setName("1")
manager.save()
checkSchemes("1->1;2->2")
}
public Test fun testDeleteRenamedScheme() {
val manager = createAndLoad("options1")
val firstScheme = manager.findSchemeByName("first")
assertThat(firstScheme, notNullValue())
assert(firstScheme != null)
firstScheme!!.setName("first_renamed")
manager.save()
checkSchemes(File(remoteBaseDir, "REMOTE"), "first_renamed->first_renamed;2->second", true)
checkSchemes(localBaseDir!!, "", false)
firstScheme.setName("first_renamed2")
manager.removeScheme(firstScheme)
manager.save()
checkSchemes(File(remoteBaseDir, "REMOTE"), "2->second", true)
checkSchemes(localBaseDir!!, "", false)
}
public Test fun testDeleteAndCreateSchemeWithTheSameName() {
val manager = createAndLoad("options1")
val firstScheme = manager.findSchemeByName("first")
assertThat(firstScheme, notNullValue())
manager.removeScheme(firstScheme!!)
manager.addScheme(TestScheme("first"))
manager.save()
checkSchemes("2->second;first->first")
}
public Test fun testGenerateUniqueSchemeName() {
val manager = createAndLoad("options1")
val scheme = TestScheme("first")
manager.addNewScheme(scheme, false)
assertThat("first2", equalTo(scheme.getName()))
}
public Test fun `different extensions`() {
val dir = tempDirManager.newDirectory()
FileUtil.copyDir(File("${getTestDataPath()}/different-extensions"), dir, false)
val schemesManager = SchemeManagerImpl<TestScheme, TestScheme>(FILE_SPEC, object: TestSchemesProcessor() {
override fun isUpgradeNeeded() = true
override fun getSchemeExtension() = ".icls"
}, RoamingType.PER_USER, null, dir)
schemesManager.loadSchemes()
val schemes = schemesManager.getAllSchemes()
assertThat(schemes.size(), equalTo(1))
val scheme = schemes.get(0)
assertThat(scheme.data, equalTo("true"))
assertThat(File(dir, "1.icls"), exists())
assertThat(File(dir, "1.xml"), exists())
scheme.data = "newTrue"
save(schemesManager)
assertThat(File(dir, "1.icls"), exists())
assertThat(File(dir, "1.xml"), not(exists()))
}
public Test fun setSchemes() {
val dir = tempDirManager.newDirectory()
val schemesManager = createSchemeManager(dir)
schemesManager.loadSchemes()
assertThat(schemesManager.getAllSchemes().isEmpty(), equalTo(true))
val scheme = TestScheme("s1")
schemesManager.setSchemes(listOf(scheme))
val schemes = schemesManager.getAllSchemes()
assertThat(schemes.size(), equalTo(1))
assertThat(schemes.get(0), sameInstance(scheme))
assertThat(File(dir, "s1.xml"), not(exists()))
scheme.data = "newTrue"
save(schemesManager)
assertThat(File(dir, "s1.xml"), exists())
schemesManager.setSchemes(emptyList())
save(schemesManager)
assertThat(dir, not(exists()))
}
public Test fun `save only if scheme differs from bundled`() {
val dir = tempDirManager.newDirectory()
var schemeManager = createSchemeManager(dir)
val converter: (Element) -> TestScheme = { XmlSerializer.deserialize(it, javaClass<TestScheme>())!! }
val bundledPath = "/bundledSchemes/default"
schemeManager.loadBundledScheme(bundledPath, this, converter)
var schemes = schemeManager.getAllSchemes()
assertThat(schemes.size(), equalTo(1))
val customScheme = TestScheme("default")
assertThat(schemes.get(0), equalTo(customScheme))
save(schemeManager)
assertThat(dir, not(exists()))
save(schemeManager)
schemeManager.setSchemes(listOf(customScheme))
assertThat(dir, not(exists()))
schemes = schemeManager.getAllSchemes()
assertThat(schemes.size(), equalTo(1))
assertThat(schemes.get(0), sameInstance(customScheme))
customScheme.data = "foo"
save(schemeManager)
val schemeFile = File(dir, "default.xml")
assertThat(schemeFile, exists())
schemeManager = createSchemeManager(dir)
schemeManager.loadBundledScheme(bundledPath, this, converter)
schemeManager.loadSchemes()
schemes = schemeManager.getAllSchemes()
assertThat(schemes.get(0), equalTo(customScheme))
}
private fun createSchemeManager(dir: File) = SchemeManagerImpl<TestScheme, TestScheme>(FILE_SPEC, TestSchemesProcessor(), RoamingType.PER_USER, null, dir)
public Test fun `don't remove dir if no schemes but at least one non-hidden file exists`() {
val dir = tempDirManager.newDirectory()
val schemesManager = SchemeManagerImpl<TestScheme, TestScheme>(FILE_SPEC, TestSchemesProcessor(), RoamingType.PER_USER, null, dir)
val scheme = TestScheme("s1")
schemesManager.setSchemes(listOf(scheme))
save(schemesManager)
val schemeFile = File(dir, "s1.xml")
assertThat(schemeFile.exists(), equalTo(true))
schemesManager.setSchemes(emptyList())
FileUtil.writeToFile(File(dir, "empty"), byteArrayOf())
save(schemesManager)
assertThat(schemeFile.exists(), equalTo(false))
assertThat(dir.exists(), equalTo(true))
}
public Test fun rename() {
val dir = tempDirManager.newDirectory()
val schemesManager = SchemeManagerImpl<TestScheme, TestScheme>(FILE_SPEC, TestSchemesProcessor(), RoamingType.PER_USER, null, dir)
schemesManager.loadSchemes()
assertThat(schemesManager.getAllSchemes().isEmpty(), equalTo(true))
val scheme = TestScheme("s1")
schemesManager.setSchemes(listOf(scheme))
val schemes = schemesManager.getAllSchemes()
assertThat(schemes.size(), equalTo(1))
assertThat(schemes.get(0), sameInstance(scheme))
assertThat(File(dir, "s1.xml").exists(), equalTo(false))
scheme.data = "newTrue"
save(schemesManager)
assertThat(File(dir, "s1.xml").exists(), equalTo(true))
scheme.setName("s2")
save(schemesManager)
assertThat(File(dir, "s1.xml").exists(), equalTo(false))
assertThat(File(dir, "s2.xml").exists(), equalTo(true))
}
private fun save(schemeManager: SchemeManagerImpl<TestScheme, TestScheme>) {
invokeAndWaitIfNeed {
schemeManager.save()
}
}
private fun createAndLoad(testData: String): SchemeManagerImpl<TestScheme, TestScheme> {
createTempFiles(testData)
return createAndLoad()
}
private fun doLoadSaveTest(testData: String, expected: String, localExpected: String = "") {
val schemesManager = createAndLoad(testData)
schemesManager.save()
checkSchemes(File(remoteBaseDir, "REMOTE"), expected, true)
checkSchemes(localBaseDir!!, localExpected, false)
}
private fun checkSchemes(expected: String) {
checkSchemes(File(remoteBaseDir, "REMOTE"), expected, true)
checkSchemes(localBaseDir!!, "", false)
}
private fun createAndLoad(): SchemeManagerImpl<TestScheme, TestScheme> {
val schemesManager = SchemeManagerImpl<TestScheme, TestScheme>(FILE_SPEC, TestSchemesProcessor(), RoamingType.PER_USER, MockStreamProvider(remoteBaseDir!!), localBaseDir!!)
schemesManager.loadSchemes()
return schemesManager
}
private fun createTempFiles(testData: String) {
val temp = tempDirManager.newDirectory()
localBaseDir = File(temp, "__local")
remoteBaseDir = temp
FileUtil.copyDir(File("${getTestDataPath()}/$testData"), File(temp, "REMOTE"))
}
}
private fun checkSchemes(baseDir: File, expected: String, ignoreDeleted: Boolean) {
val filesToScheme = StringUtil.split(expected, ";")
val fileToSchemeMap = THashMap<String, String>()
for (fileToScheme in filesToScheme) {
val index = fileToScheme.indexOf("->")
fileToSchemeMap.put(fileToScheme.substring(0, index), fileToScheme.substring(index + 2))
}
val files = baseDir.listFiles()
if (files != null) {
for (file in files) {
val fileName = FileUtil.getNameWithoutExtension(file)
if ("--deleted" == fileName && ignoreDeleted) {
assertThat<Map<String, String>>(fileToSchemeMap, hasKey(fileName))
}
}
}
for (file in fileToSchemeMap.keySet()) {
assertThat(File(baseDir, "$file.xml").isFile(), equalTo(true))
}
if (files != null) {
val schemesProcessor = TestSchemesProcessor()
for (file in files) {
val fileName = FileUtil.getNameWithoutExtension(file)
val scheme = schemesProcessor.readScheme(JDOMUtil.load(file), true)
assertThat(fileToSchemeMap.get(fileName), equalTo(scheme!!.getName()))
}
}
}
public data Tag("scheme") class TestScheme(Attribute private var name: String = "", Attribute var data: String? = null) : ExternalizableScheme {
override fun getName() = name
override Transient fun setName(newName: String) {
name = newName
}
@suppress("DEPRECATED_SYMBOL_WITH_MESSAGE")
override fun getExternalInfo() = null
}
public open class TestSchemesProcessor : BaseSchemeProcessor<TestScheme>() {
override fun readScheme(element: Element) = XmlSerializer.deserialize(element, javaClass<TestScheme>())
override fun writeScheme(scheme: TestScheme) = scheme.serialize()
}
fun SchemeManagerImpl<*, *>.save() {
val errors = SmartList<Throwable>()
save(errors)
CompoundRuntimeException.doThrow(errors)
}
public fun <T : Any> T.serialize(): Element = XmlSerializer.serialize(this, SkipDefaultValuesSerializationFilters())
public fun Element.toByteArray(): ByteArray {
val out = BufferExposingByteArrayOutputStream(512)
JDOMUtil.writeParent(this, out, "\n")
return out.toByteArray()
}
@@ -15,14 +15,10 @@
*/
package org.jetbrains.testFramework
import com.intellij.openapi.application.invokeAndWaitIfNeed
import com.intellij.testFramework.UsefulTestCase
import com.intellij.testFramework.fixtures.IdeaTestFixtureFactory
import org.junit.rules.ExternalResource
import javax.swing.SwingUtilities
public fun invokeAndWaitIfNeed(runnable: () -> Unit) {
if (SwingUtilities.isEventDispatchThread()) runnable() else SwingUtilities.invokeAndWait(runnable)
}
public class FixtureRule() : ExternalResource() {
val projectFixture = IdeaTestFixtureFactory.getFixtureFactory().createLightFixtureBuilder().getFixture()