ICS: fix broken copyLocalConfig again and test

This commit is contained in:
Vladimir Krivosheev
2015-09-26 08:10:43 +02:00
parent c2a96376b6
commit b23fb4683b
24 changed files with 309 additions and 189 deletions
@@ -0,0 +1,8 @@
<root>
<item name='java.nio.file.FileSystem java.nio.file.Path getPath(java.lang.String, java.lang.String...)'>
<annotation name='org.jetbrains.annotations.NotNull'/>
</item>
<item name='java.nio.file.Path java.nio.file.Path getParent()'>
<annotation name='org.jetbrains.annotations.Nullable'/>
</item>
</root>
@@ -20,11 +20,11 @@ import org.jetbrains.io.MessageDecoder
import org.jetbrains.io.NettyUtil
import org.junit.ClassRule
import org.junit.Test
import java.util.UUID
import java.util.*
import java.util.concurrent.TimeUnit
// we don't handle String in efficient way - because we want to test readContent/readChars also
public class BinaryRequestHandlerTest {
internal class BinaryRequestHandlerTest {
companion object {
@ClassRule val projectRule = ProjectRule()
}
@@ -47,13 +47,13 @@ public class BinaryRequestHandlerTest {
}
})
val port = BuiltInServerManager.getInstance().waitForStart().getPort()
val port = BuiltInServerManager.getInstance().waitForStart().port
val channel = bootstrap.connect(NetUtils.getLoopbackAddress(), port).syncUninterruptibly().channel()
val buffer = channel.alloc().buffer()
buffer.writeByte('C'.toInt())
buffer.writeByte('H'.toInt())
buffer.writeLong(MyBinaryRequestHandler.ID.getMostSignificantBits())
buffer.writeLong(MyBinaryRequestHandler.ID.getLeastSignificantBits())
buffer.writeLong(MyBinaryRequestHandler.ID.mostSignificantBits)
buffer.writeLong(MyBinaryRequestHandler.ID.leastSignificantBits)
val message = Unpooled.copiedBuffer(text, CharsetUtil.UTF_8)
buffer.writeShort(message.readableBytes())
@@ -67,7 +67,7 @@ public class BinaryRequestHandlerTest {
}
})
if (result.getState() == Promise.State.PENDING) {
if (result.state == Promise.State.PENDING) {
val semaphore = Semaphore()
semaphore.down()
result.processed { semaphore.up() }
@@ -32,9 +32,9 @@ import com.intellij.openapi.vfs.LocalFileSystem
import com.intellij.openapi.vfs.VfsUtil
import org.jdom.Element
class ApplicationPathMacroManager : BasePathMacroManager(null)
private class ApplicationPathMacroManager : BasePathMacroManager(null)
class ApplicationStoreImpl(private val application: Application, pathMacroManager: PathMacroManager) : ComponentStoreImpl() {
class ApplicationStoreImpl(private val application: Application, pathMacroManager: PathMacroManager? = null) : ComponentStoreImpl() {
override val storageManager = ApplicationStorageManager(application, pathMacroManager)
// number of app components require some state, so, we load default state in test mode
@@ -42,8 +42,9 @@ class ApplicationStoreImpl(private val application: Application, pathMacroManage
get() = if (application.isUnitTestMode) StateLoadPolicy.LOAD_ONLY_DEFAULT else StateLoadPolicy.LOAD
override fun setPath(path: String) {
storageManager.addMacro(ROOT_CONFIG, path)
// app config must be first, because collapseMacros collapse from fist to last, so, at first we must replace APP_CONFIG because it overlaps ROOT_CONFIG value
storageManager.addMacro(StoragePathMacros.APP_CONFIG, "$path/${ApplicationStorageManager.FILE_STORAGE_DIR}")
storageManager.addMacro(ROOT_CONFIG, path)
val configDir = LocalFileSystem.getInstance().refreshAndFindFileByPath(path)
if (configDir != null) {
@@ -110,9 +111,9 @@ class ApplicationStorageManager(private val application: Application, pathMacroM
return path
}
override fun expandNormalizedPath(path: String): String {
override fun expandMacros(path: String): String {
if (path[0] == '$') {
return super.expandNormalizedPath(path)
return super.expandMacros(path)
}
else {
return "${expandMacro(StoragePathMacros.APP_CONFIG)}/$path"
@@ -73,8 +73,6 @@ internal class DefaultProjectStoreImpl(override val project: ProjectImpl, privat
override fun expandMacros(file: String) = throw UnsupportedOperationException("Method expandMacros not implemented in " + javaClass)
override fun collapseMacros(path: String) = throw UnsupportedOperationException("Method collapseMacros not implemented in " + javaClass)
override fun getOldStorage(component: Any, componentName: String, operation: StateStorageOperation) = storage
}
@@ -35,9 +35,9 @@ class ProjectStateStorageManager(macroSubstitutor: TrackingPathMacroSubstitutor,
return path
}
override fun expandNormalizedPath(path: String): String {
override fun expandMacros(path: String): String {
if (path[0] == '$') {
return super.expandNormalizedPath(path)
return super.expandMacros(path)
}
else {
return "${expandMacro(StoragePathMacros.PROJECT_CONFIG_DIR)}/$path"
@@ -29,9 +29,9 @@ import com.intellij.util.containers.ContainerUtil
import com.intellij.util.lang.CompoundRuntimeException
import java.io.File
val ROOT_CONFIG: String = "\$ROOT_CONFIG$"
val ROOT_CONFIG = "\$ROOT_CONFIG$"
public abstract class SchemeManagerFactoryBase : SchemesManagerFactory(), SettingsSavingComponent {
sealed class SchemeManagerFactoryBase : SchemesManagerFactory(), SettingsSavingComponent {
private val managers = ContainerUtil.createLockFreeCopyOnWriteList<SchemeManagerImpl<Scheme, ExternalizableScheme>>()
abstract val componentManager: ComponentManager
@@ -85,27 +85,27 @@ public abstract class SchemeManagerFactoryBase : SchemesManagerFactory(), Settin
CompoundRuntimeException.throwIfNotEmpty(errors)
}
}
private class ApplicationSchemeManagerFactory : SchemeManagerFactoryBase() {
override val componentManager: ComponentManager
get() = ApplicationManager.getApplication()
private class ApplicationSchemeManagerFactory : SchemeManagerFactoryBase() {
override val componentManager: ComponentManager
get() = ApplicationManager.getApplication()
override fun checkPath(originalPath: String): String {
var path = super.checkPath(originalPath)
if (path.startsWith(ROOT_CONFIG)) {
path = path.substring(ROOT_CONFIG.length() + 1)
val message = "Path must not contains ROOT_CONFIG macro, corrected: $path"
if (ApplicationManager.getApplication().isUnitTestMode) throw AssertionError(message) else LOG.warn(message)
override fun checkPath(originalPath: String): String {
var path = super.checkPath(originalPath)
if (path.startsWith(ROOT_CONFIG)) {
path = path.substring(ROOT_CONFIG.length() + 1)
val message = "Path must not contains ROOT_CONFIG macro, corrected: $path"
if (ApplicationManager.getApplication().isUnitTestMode) throw AssertionError(message) else LOG.warn(message)
}
return path
}
return path
override fun pathToFile(path: String, storageManager: StateStorageManager) = File(storageManager.expandMacros("$ROOT_CONFIG/$path"))
}
override fun pathToFile(path: String, storageManager: StateStorageManager) = File(storageManager.expandMacros("$ROOT_CONFIG/$path"))
}
private class ProjectSchemeManagerFactory(private val project: Project) : SchemeManagerFactoryBase() {
override val componentManager = project
private class ProjectSchemeManagerFactory(private val project: Project) : SchemeManagerFactoryBase() {
override val componentManager = project
override fun pathToFile(path: String, storageManager: StateStorageManager) = File(project.basePath, if (ProjectUtil.isDirectoryBased(project)) "${Project.DIRECTORY_STORE_FOLDER}/$path" else ".$path")
override fun pathToFile(path: String, storageManager: StateStorageManager) = File(project.basePath, if (ProjectUtil.isDirectoryBased(project)) "${Project.DIRECTORY_STORE_FOLDER}/$path" else ".$path")
}
}
@@ -52,7 +52,7 @@ open class StateStorageManagerImpl(private val rootTagName: String,
private val storageLock = ReentrantLock()
private val storages = THashMap<String, StateStorage>()
public var streamProvider: StreamProvider? = null
var streamProvider: StreamProvider? = null
// access under storageLock
private var isUseVfsListener = if (componentManager == null) ThreeState.NO else ThreeState.UNSURE // unsure because depends on stream provider state
@@ -138,22 +138,16 @@ open class StateStorageManagerImpl(private val rootTagName: String,
return if (path.endsWith('/')) path.substring(0, path.length() - 1) else path
}
/**
* @param path Must be normalized (see normalizeFileSpec})
* @return System-independent path
*/
open fun expandNormalizedPath(path: String): String = expandMacros(path)
fun getOrCreateStorage(fileSpec: String,
fun getOrCreateStorage(collapsedPath: String,
roamingType: RoamingType = RoamingType.DEFAULT,
storageClass: Class<out StateStorage> = StateStorage::class.java,
@Suppress("DEPRECATED_SYMBOL_WITH_MESSAGE") @SuppressWarnings("deprecation") stateSplitter: Class<out StateSplitter> = StateSplitterEx::class.java): StateStorage {
val collapsedPath = normalizeFileSpec(fileSpec)
val key = if (storageClass == StateStorage::class.java) collapsedPath else storageClass.name
val normalizedCollapsedPath = normalizeFileSpec(collapsedPath)
val key = if (storageClass == StateStorage::class.java) normalizedCollapsedPath else storageClass.name
storageLock.withLock {
var storage = storages.get(key)
if (storage == null) {
storage = createStateStorage(storageClass, collapsedPath, roamingType, stateSplitter)
storage = createStateStorage(storageClass, normalizedCollapsedPath, roamingType, stateSplitter)
storages.put(key, storage)
}
return storage
@@ -197,7 +191,7 @@ open class StateStorageManagerImpl(private val rootTagName: String,
isUseVfsListener = ThreeState.fromBoolean(streamProvider == null || !streamProvider!!.enabled)
}
val filePath = expandNormalizedPath(collapsedPath)
val filePath = expandMacros(collapsedPath)
@Suppress("DEPRECATED_SYMBOL_WITH_MESSAGE")
if (stateSplitter != StateSplitter::class.java && stateSplitter != StateSplitterEx::class.java) {
val storage = MyDirectoryStorage(this, File(filePath), ReflectionUtil.newInstance(stateSplitter))
@@ -282,7 +276,7 @@ open class StateStorageManagerImpl(private val rootTagName: String,
try {
if (virtualFileTracker != null) {
storages.forEachEntry({ collapsedPath, storage ->
virtualFileTracker.remove(expandNormalizedPath(collapsedPath))
virtualFileTracker.remove(expandMacros(collapsedPath))
true
})
}
@@ -295,7 +289,7 @@ open class StateStorageManagerImpl(private val rootTagName: String,
protected open fun getMacroSubstitutor(fileSpec: String): TrackingPathMacroSubstitutor? = pathMacroSubstitutor
override final fun expandMacros(path: String): String {
override fun expandMacros(path: String): String {
// replacement can contains $ (php tests), so, this check must be performed before expand
val matcher = MACRO_PATTERN.matcher(path)
matcherLoop@
@@ -326,17 +320,17 @@ open class StateStorageManagerImpl(private val rootTagName: String,
throw IllegalArgumentException("Unknown macro $macro")
}
override final fun collapseMacros(path: String): String {
fun collapseMacros(path: String): String {
var result = path
for ((key, value) in macros) {
result = StringUtil.replace(result, value, key)
}
return result
return normalizeFileSpec(result)
}
override fun startExternalization() = StateStorageManagerExternalizationSession(this)
class StateStorageManagerExternalizationSession(protected val storageManager: StateStorageManagerImpl) : StateStorageManager.ExternalizationSession {
private class StateStorageManagerExternalizationSession(private val storageManager: StateStorageManagerImpl) : StateStorageManager.ExternalizationSession {
private val sessions = LinkedHashMap<StateStorage, StateStorage.ExternalizationSession>()
override fun setState(storageSpecs: Array<Storage>, component: Any, componentName: String, state: Any) {
@@ -67,7 +67,7 @@ internal class ApplicationStoreTest {
component.foo = "newValue"
componentStore.save(SmartList())
assertThat(streamProvider.data.get(RoamingType.DEFAULT)!!.get("new.xml")).isEqualTo("<application>\n" + " <component name=\"A\" foo=\"newValue\" />\n" + "</application>")
assertThat(streamProvider.data.get(RoamingType.DEFAULT)!!.get("new.xml")).isEqualTo("<application>\n <component name=\"A\" foo=\"newValue\" />\n</application>")
}
@Test fun `load from stream provider`() {
@@ -83,7 +83,7 @@ internal class ApplicationStoreTest {
componentStore.initComponent(component, false)
assertThat(component.foo).isEqualTo("newValue")
assertThat(Paths.get(componentStore.storageManager.expandNormalizedPath(fileSpec))).isRegularFile()
assertThat(Paths.get(componentStore.storageManager.expandMacros(fileSpec))).isRegularFile()
}
@Test fun `remove deprecated storage on write`() {
@@ -27,7 +27,7 @@ import org.junit.ClassRule
import org.junit.Test
import kotlin.properties.Delegates
class StorageManagerTest {
internal class StorageManagerTest {
companion object {
val MACRO = "\$MACRO1$"
@@ -36,27 +36,27 @@ class StorageManagerTest {
private var storageManager: StateStorageManagerImpl by Delegates.notNull()
public @Before fun setUp() {
@Before fun setUp() {
storageManager = StateStorageManagerImpl("foo")
storageManager.addMacro(MACRO, "/temp/m1")
}
public @Test fun createFileStateStorageMacroSubstituted() {
@Test fun createFileStateStorageMacroSubstituted() {
assertThat(storageManager.getOrCreateStorage("$MACRO/test.xml")).isNotNull()
}
public @Test fun `collapse macro`() {
@Test fun `collapse macro`() {
assertThat(storageManager.collapseMacros("/temp/m1/foo")).isEqualTo("$MACRO/foo")
assertThat(storageManager.collapseMacros("\\temp\\m1\\foo")).isEqualTo("\\temp\\m1\\foo")
assertThat(storageManager.collapseMacros("\\temp\\m1\\foo")).isEqualTo("/temp/m1/foo")
}
public @Test fun `add system-dependent macro`() {
@Test fun `add system-dependent macro`() {
val key = "\$INVALID$"
val expansion = "\\temp"
assertThatThrownBy({storageManager.addMacro(key, expansion) }).hasMessage("Macro $key set to system-dependent expansion $expansion")
}
public @Test fun `create storage assertion thrown when unknown macro`() {
@Test fun `create storage assertion thrown when unknown macro`() {
try {
storageManager.getOrCreateStorage("\$UNKNOWN_MACRO$/test.xml")
TestCase.fail("Exception expected")
@@ -66,7 +66,7 @@ class StorageManagerTest {
}
}
public @Test fun `create file storage macro substituted when expansion has$`() {
@Test fun `create file storage macro substituted when expansion has$`() {
storageManager.addMacro("\$DOLLAR_MACRO$", "/temp/d$")
assertThat(storageManager.getOrCreateStorage("\$DOLLAR_MACRO$/test.xml")).isNotNull()
}
@@ -161,7 +161,14 @@ public class ExportSettingsAction extends AnAction implements DumbAware {
}
@NotNull
public static MultiMap<File, ExportableComponent> getExportableComponentsMap(final boolean onlyExisting, final boolean computePresentableNames) {
public static MultiMap<File, ExportableComponent> getExportableComponentsMap(boolean onlyExisting, boolean computePresentableNames) {
ApplicationImpl application = (ApplicationImpl)ApplicationManager.getApplication();
StateStorageManager storageManager = ComponentsPackage.getStateStore(application).getStateStorageManager();
return getExportableComponentsMap(onlyExisting, computePresentableNames, storageManager);
}
@NotNull
public static MultiMap<File, ExportableComponent> getExportableComponentsMap(final boolean onlyExisting, final boolean computePresentableNames, final @NotNull StateStorageManager storageManager) {
@SuppressWarnings("deprecation")
List<ExportableApplicationComponent> components1 = ComponentsPackage.getComponents(ApplicationManager.getApplication(), ExportableApplicationComponent.class);
List<ExportableComponent> components2 = ServiceBean.loadServicesFromBeans(ExportableComponent.EXTENSION_POINT, ExportableComponent.class);
@@ -180,9 +187,7 @@ public class ExportSettingsAction extends AnAction implements DumbAware {
}
}
ApplicationImpl application = (ApplicationImpl)ApplicationManager.getApplication();
final StateStorageManager storageManager = ComponentsPackage.getStateStore(application).getStateStorageManager();
ServiceManagerImpl.processAllImplementationClasses(application, new PairProcessor<Class<?>, PluginDescriptor>() {
ServiceManagerImpl.processAllImplementationClasses((ApplicationImpl)ApplicationManager.getApplication(), new PairProcessor<Class<?>, PluginDescriptor>() {
@Override
public boolean process(@NotNull Class<?> aClass, @Nullable PluginDescriptor pluginDescriptor) {
State stateAnnotation = StoreUtil.getStateSpec(aClass);
@@ -48,12 +48,6 @@ public interface StateStorageManager {
@NotNull
String expandMacros(@NotNull String path);
@NotNull
/**
* @param path System-independent path.
*/
String collapseMacros(@NotNull String path);
interface ExternalizationSession {
void setState(@NotNull Storage[] storageSpecs, @NotNull Object component, @NotNull String componentName, @NotNull Object state);
@@ -169,9 +169,9 @@
serviceImplementation="com.intellij.execution.process.impl.OSProcessManagerImpl"/>
<applicationService serviceInterface="com.intellij.openapi.options.SchemesManagerFactory"
serviceImplementation="com.intellij.configurationStore.ApplicationSchemeManagerFactory"/>
serviceImplementation="com.intellij.configurationStore.SchemeManagerFactoryBase$ApplicationSchemeManagerFactory"/>
<projectService serviceInterface="com.intellij.openapi.options.SchemesManagerFactory"
serviceImplementation="com.intellij.configurationStore.ProjectSchemeManagerFactory"/>
serviceImplementation="com.intellij.configurationStore.SchemeManagerFactoryBase$ProjectSchemeManagerFactory"/>
<applicationService serviceImplementation="com.intellij.openapi.editor.colors.ex.DefaultColorSchemesManager"/>
<applicationService serviceInterface="com.intellij.openapi.editor.colors.TextAttributesKey$TextAttributeKeyDefaultsProvider"
@@ -26,17 +26,17 @@ import org.junit.runner.Description
import org.junit.runners.model.Statement
import java.io.File
import java.io.IOException
import java.nio.file.*
import java.nio.file.attribute.BasicFileAttributes
import java.nio.file.attribute.FileTime
import java.nio.file.Path
import java.nio.file.Paths
import kotlin.properties.Delegates
public class TemporaryDirectory : ExternalResource() {
class TemporaryDirectory : ExternalResource() {
private val paths = SmartList<Path>()
private var sanitizedName: String? = null
private var sanitizedName: String by Delegates.notNull()
override fun apply(base: Statement, description: Description): Statement {
sanitizedName = FileUtil.sanitizeFileName(description.getMethodName(), false)
sanitizedName = FileUtil.sanitizeFileName(description.methodName, false)
return super.apply(base, description)
}
@@ -61,7 +61,7 @@ public class TemporaryDirectory : ExternalResource() {
}
private fun generatePath(suffix: String?): Path {
var fileName = sanitizedName!!
var fileName = sanitizedName
if (suffix != null) {
fileName += "_$suffix"
}
@@ -93,65 +93,4 @@ public fun generateTemporaryPath(fileName: String?): Path {
throw IOException("Cannot generate unique random path")
}
return path
}
public fun Path.exists(): Boolean = Files.exists(this)
public fun Path.createDirectories(): Path = Files.createDirectories(this)
public fun Path.deleteRecursively(): Path = if (exists()) Files.walkFileTree(this, object : SimpleFileVisitor<Path>() {
override fun visitFile(file: Path, attrs: BasicFileAttributes): FileVisitResult {
Files.delete(file)
return FileVisitResult.CONTINUE
}
override fun postVisitDirectory(dir: Path, exc: IOException?): FileVisitResult {
Files.delete(dir)
return FileVisitResult.CONTINUE
}
}) else this
public fun Path.getLastModifiedTime(): FileTime? = Files.getLastModifiedTime(this)
public val Path.systemIndependentPath: String
get() = toString().replace(File.separatorChar, '/')
public val Path.parentSystemIndependentPath: String
get() = getParent()!!.toString().replace(File.separatorChar, '/')
public fun Path.readText(): String = Files.readAllBytes(this).toString(Charsets.UTF_8)
public fun VirtualFile.writeChild(relativePath: String, data: String): VirtualFile = VfsTestUtil.createFile(this, relativePath, data)
public fun Path.writeChild(relativePath: String, data: String): Path = writeChild(relativePath, data.toByteArray())
public fun Path.writeChild(relativePath: String, data: ByteArray): Path {
val path = resolve(relativePath)
path.getParent().createDirectories()
return Files.write(path, data)
}
public fun Path.isDirectory(): Boolean = Files.isDirectory(this)
public fun Path.isFile(): Boolean = Files.isRegularFile(this)
/**
* Opposite to ugly Java, parent directories will be created
*/
public fun Path.createFile() {
getParent()?.createDirectories()
Files.createFile(this)
}
public fun Path.refreshVfs() {
LocalFileSystem.getInstance()?.let { fs ->
// If a temp directory is reused from some previous test run, there might be cached children in its VFS. Ensure they're removed.
val virtualFile = fs.findFileByPath(systemIndependentPath)
if (virtualFile != null) {
VfsUtil.markDirtyAndRefresh(false, true, true, virtualFile)
}
}
}
val VirtualFile.path: String
get() = getPath()
}
@@ -0,0 +1,10 @@
package com.intellij.testFramework
import java.nio.file.FileSystem
fun FileSystem.file(path: String, data: ByteArray): FileSystem {
getPath(path).write(data)
return this
}
fun FileSystem.file(path: String, data: String) = file(path, data.toByteArray())
@@ -0,0 +1,72 @@
package com.intellij.testFramework
import com.intellij.openapi.vfs.LocalFileSystem
import com.intellij.openapi.vfs.VfsUtil
import com.intellij.openapi.vfs.VirtualFile
import java.io.File
import java.io.IOException
import java.nio.file.FileVisitResult
import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.SimpleFileVisitor
import java.nio.file.attribute.BasicFileAttributes
import java.nio.file.attribute.FileTime
fun Path.exists(): Boolean = Files.exists(this)
fun Path.createDirectories(): Path = Files.createDirectories(this)
fun Path.deleteRecursively(): Path = if (exists()) Files.walkFileTree(this, object : SimpleFileVisitor<Path>() {
override fun visitFile(file: Path, attrs: BasicFileAttributes): FileVisitResult {
Files.delete(file)
return FileVisitResult.CONTINUE
}
override fun postVisitDirectory(dir: Path, exc: IOException?): FileVisitResult {
Files.delete(dir)
return FileVisitResult.CONTINUE
}
}) else this
fun Path.getLastModifiedTime(): FileTime? = Files.getLastModifiedTime(this)
val Path.systemIndependentPath: String
get() = toString().replace(File.separatorChar, '/')
val Path.parentSystemIndependentPath: String
get() = parent!!.toString().replace(File.separatorChar, '/')
fun Path.readText() = Files.readAllBytes(this).toString(Charsets.UTF_8)
fun VirtualFile.writeChild(relativePath: String, data: String) = VfsTestUtil.createFile(this, relativePath, data)
fun Path.writeChild(relativePath: String, data: ByteArray) = resolve(relativePath).write(data)
fun Path.writeChild(relativePath: String, data: String) = writeChild(relativePath, data.toByteArray())
fun Path.write(data: ByteArray): Path {
parent?.createDirectories()
return Files.write(this, data)
}
fun Path.isDirectory() = Files.isDirectory(this)
fun Path.isFile() = Files.isRegularFile(this)
/**
* Opposite to Java, parent directories will be created
*/
fun Path.createFile() {
parent?.createDirectories()
Files.createFile(this)
}
fun Path.refreshVfs() {
LocalFileSystem.getInstance()?.let { fs ->
// If a temp directory is reused from some previous test run, there might be cached children in its VFS. Ensure they're removed.
val virtualFile = fs.findFileByPath(systemIndependentPath)
if (virtualFile != null) {
VfsUtil.markDirtyAndRefresh(false, true, true, virtualFile)
}
}
}
@@ -23,5 +23,17 @@
<orderEntry type="module" module-name="util-tests" scope="TEST" />
<orderEntry type="module" module-name="settings-repository" />
<orderEntry type="library" scope="TEST" name="assertJ" level="project" />
<orderEntry type="module-library">
<library name="memoryfilesystem">
<CLASSES>
<root url="jar://$MODULE_DIR$/lib/dev/memoryfilesystem-0.6.7.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
<root url="jar://$MODULE_DIR$/lib/dev/memoryfilesystem-0.6.7-sources.jar!/" />
</SOURCES>
</library>
</orderEntry>
<orderEntry type="module" module-name="test-framework-java8" />
</component>
</module>
+1 -1
View File
@@ -34,7 +34,7 @@ import com.intellij.openapi.vcs.VcsNotifier
import com.intellij.openapi.vcs.ui.VcsBalloonProblemNotifier
import java.util.concurrent.Future
class AutoSyncManager(private val icsManager: IcsManager) {
internal class AutoSyncManager(private val icsManager: IcsManager) {
private @Volatile var autoSyncFuture: Future<*>? = null
fun waitAutoSync(indicator: ProgressIndicator) {
@@ -15,6 +15,7 @@
*/
package org.jetbrains.settingsRepository
import com.intellij.configurationStore.ROOT_CONFIG
import com.intellij.configurationStore.StateStorageManagerImpl
import com.intellij.ide.actions.ExportSettingsAction
import com.intellij.openapi.application.ApplicationManager
@@ -23,14 +24,14 @@ import com.intellij.openapi.util.io.FileUtil
import com.intellij.openapi.util.io.FileUtilRt
import java.io.File
fun copyLocalConfig() {
val storageManager = ApplicationManager.getApplication()!!.stateStore.stateStorageManager as StateStorageManagerImpl
fun copyLocalConfig(storageManager: StateStorageManagerImpl = ApplicationManager.getApplication()!!.stateStore.stateStorageManager as StateStorageManagerImpl) {
val streamProvider = storageManager.streamProvider!! as IcsManager.IcsStreamProvider
val fileToComponents = ExportSettingsAction.getExportableComponentsMap(true, false)
val fileToComponents = ExportSettingsAction.getExportableComponentsMap(true, false, storageManager)
for (file in fileToComponents.keySet()) {
val absolutePath = FileUtilRt.toSystemIndependentName(file.absolutePath)
var fileSpec = storageManager.collapseMacros(absolutePath)
LOG.assertTrue(!fileSpec.contains(ROOT_CONFIG))
if (fileSpec.equals(absolutePath)) {
// we have not experienced such problem yet, but we are just aware
val canonicalPath = FileUtilRt.toSystemIndependentName(file.canonicalPath)
+9 -3
View File
@@ -83,13 +83,19 @@ class SyncManager(private val icsManager: IcsManager, private val autoSyncManage
when (syncType) {
SyncType.MERGE -> {
updateResult = repositoryManager.pull(indicator)
var doPush = true
if (localRepositoryInitializer != null) {
// must be performed only after initial pull, so, local changes will be relative to remote files
localRepositoryInitializer()
repositoryManager.commit(indicator)
updateResult = updateResult.concat(repositoryManager.pull(indicator))
if (!repositoryManager.commit(indicator, syncType) || repositoryManager.getAheadCommitsCount() == 0) {
// avoid error during findRemoteRefUpdatesFor on push - if localRepositoryInitializer specified and nothing to commit (failed or just no files to commit (empty local configuration - no files)),
// so, nothing to push
doPush = false
}
}
if (doPush) {
repositoryManager.push(indicator)
}
repositoryManager.push(indicator)
}
SyncType.OVERWRITE_LOCAL -> {
// we don't push - probably, repository will be modified/removed (user can do something, like undo) before any other next push activities (so, we don't want to disturb remote)
+74 -21
View File
@@ -15,13 +15,15 @@
*/
package org.jetbrains.settingsRepository.test
import com.intellij.configurationStore.ApplicationStoreImpl
import com.intellij.configurationStore.write
import com.intellij.mock.MockVirtualFileSystem
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.util.io.FileUtil
import com.intellij.openapi.vcs.merge.MergeSession
import com.intellij.openapi.vfs.CharsetToolkit
import com.intellij.testFramework.ProjectRule
import com.intellij.testFramework.path
import com.intellij.testFramework.file
import com.intellij.testFramework.writeChild
import com.intellij.util.PathUtilRt
import org.assertj.core.api.Assertions.assertThat
import org.eclipse.jgit.api.Git
@@ -31,6 +33,7 @@ import org.jetbrains.jgit.dirCache.writePath
import org.jetbrains.settingsRepository.CannotResolveConflictInTestMode
import org.jetbrains.settingsRepository.SyncType
import org.jetbrains.settingsRepository.conflictResolver
import org.jetbrains.settingsRepository.copyLocalConfig
import org.jetbrains.settingsRepository.git.GitRepositoryManager
import org.jetbrains.settingsRepository.git.commit
import org.jetbrains.settingsRepository.git.computeIndexDiff
@@ -39,16 +42,17 @@ import org.junit.ClassRule
import org.junit.Test
import java.io.File
import java.nio.charset.StandardCharsets
import java.util.Arrays
import java.nio.file.FileSystem
import java.util.*
import kotlin.properties.Delegates
// kotlin bug, cannot be val (.NoSuchMethodError: org.jetbrains.settingsRepository.SettingsRepositoryPackage.getMARKER_ACCEPT_MY()[B)
object AM {
internal object AM {
val MARKER_ACCEPT_MY: ByteArray = "__accept my__".toByteArray()
val MARKER_ACCEPT_THEIRS: ByteArray = "__accept theirs__".toByteArray()
}
class GitTest : IcsTestCase() {
internal class GitTest : IcsTestCase() {
companion object {
@ClassRule val projectRule = ProjectRule()
}
@@ -175,7 +179,7 @@ class GitTest : IcsTestCase() {
repositoryManager.commit()
repositoryManager.pull()
assertThat(FileUtil.loadFile(File(repository.getWorkTree(), file.name))).isEqualTo(String(file.data, CharsetToolkit.UTF8_CHARSET))
compareFiles(repository.workTree, remoteRepository.workTree, null, PathUtilRt.getFileName(file.name))
compareFiles(repository.workTree, remoteRepository.workTree, PathUtilRt.getFileName(file.name))
}
private fun createLocalRepository(remoteBranchName: String? = null) {
@@ -188,15 +192,20 @@ class GitTest : IcsTestCase() {
return addAndCommit("local.xml")
}
private fun MockVirtualFileSystem.compare() {
compareFiles(repository.workTree, remoteRepository.workTree, root)
private fun FileSystem.compare(): FileSystem {
val root = getPath("/")!!
compareFiles(root, repository.workTree)
compareFiles(root, remoteRepository.workTree)
return this
}
// never was merged. we reset using "merge with strategy "theirs", so, we must test - what's happen if it is not first merge? - see next test
@Test fun resetToTheirsIfFirstMerge() {
createLocalRepositoryAndCommit(null)
sync(SyncType.OVERWRITE_LOCAL)
fs().file(SAMPLE_FILE_NAME, SAMPLE_FILE_CONTENT).compare()
fs
.file(SAMPLE_FILE_NAME, SAMPLE_FILE_CONTENT)
.compare()
}
@Test fun resetToTheirsISecondMergeIsNull() {
@@ -205,8 +214,6 @@ class GitTest : IcsTestCase() {
restoreRemoteAfterPush()
val fs = MockVirtualFileSystem()
fun testRemote() {
fs
.file("local.xml", """<file path="local.xml" />""")
@@ -232,7 +239,7 @@ class GitTest : IcsTestCase() {
createLocalRepositoryAndCommit()
sync(SyncType.OVERWRITE_REMOTE)
restoreRemoteAfterPush()
fs().file("local.xml", """<file path="local.xml" />""").compare()
fs.file("local.xml", """<file path="local.xml" />""").compare()
}
@Test fun `reset to my, second merge is null`() {
@@ -241,8 +248,10 @@ class GitTest : IcsTestCase() {
restoreRemoteAfterPush()
val fs = fs().file("local.xml", """<file path="local.xml" />""").file(SAMPLE_FILE_NAME, SAMPLE_FILE_CONTENT)
fs.compare()
val fs = fs
.file("local.xml", """<file path="local.xml" />""")
.file(SAMPLE_FILE_NAME, SAMPLE_FILE_CONTENT)
.compare()
val localToFilePath = "_mac/local2.xml"
addAndCommit(localToFilePath)
@@ -269,7 +278,7 @@ class GitTest : IcsTestCase() {
sync(SyncType.MERGE)
restoreRemoteAfterPush()
fs().file(SAMPLE_FILE_NAME, data.toString(StandardCharsets.UTF_8)).compare()
fs.file(SAMPLE_FILE_NAME, data.toString(StandardCharsets.UTF_8)).compare()
}
@Test fun `merge - theirs file deleted, my modified, accept theirs`() {
@@ -286,7 +295,7 @@ class GitTest : IcsTestCase() {
sync(SyncType.MERGE)
fs().compare()
fs.compare()
}
@Test fun `merge - my file deleted, theirs modified, accept my`() {
@@ -294,16 +303,16 @@ class GitTest : IcsTestCase() {
sync(SyncType.MERGE)
provider.delete("remote.xml")
provider.delete(SAMPLE_FILE_NAME)
repositoryManager.commit()
remoteRepository.writePath("remote.xml", AM.MARKER_ACCEPT_THEIRS)
remoteRepository.writePath(SAMPLE_FILE_NAME, AM.MARKER_ACCEPT_THEIRS)
remoteRepository.commit("")
sync(SyncType.MERGE)
restoreRemoteAfterPush()
fs().compare()
fs.compare()
}
@Test fun `commit if unmerged`() {
@@ -327,7 +336,7 @@ class GitTest : IcsTestCase() {
}
sync(SyncType.MERGE)
fs().file(SAMPLE_FILE_NAME, SAMPLE_FILE_CONTENT).compare()
fs.file(SAMPLE_FILE_NAME, SAMPLE_FILE_CONTENT).compare()
}
// remote is uninitialized (empty - initial commit is not done)
@@ -368,6 +377,51 @@ class GitTest : IcsTestCase() {
}
}
@Test fun `initial copy to repository: no local files`() {
// check error during findRemoteRefUpdatesFor (no master ref)
testInitialCopy(false)
}
@Test fun `initial copy to repository: some local files`() {
// check error during findRemoteRefUpdatesFor (no master ref)
testInitialCopy(true)
}
private fun testInitialCopy(addLocalFiles: Boolean) {
createRemoteRepository(initialCommit = false)
repositoryManager.createRepositoryIfNeed()
repositoryManager.setUpstream(remoteRepository.getWorkTree().absolutePath)
val store = ApplicationStoreImpl(ApplicationManager.getApplication()!!)
val localConfigPath = tempDirManager.newPath("local_config")
val lafData = """<application>
<component name="UISettings">
<option name="HIDE_TOOL_STRIPES" value="false" />
</component>
</application>"""
if (addLocalFiles) {
localConfigPath.writeChild("options/ui.lnf.xml", lafData)
}
store.setPath(localConfigPath.toString())
store.storageManager.streamProvider = provider
icsManager.sync(SyncType.MERGE, projectRule.project, { copyLocalConfig(store.storageManager) })
if (addLocalFiles) {
assertThat(localConfigPath).isDirectory()
fs
.file("ui.lnf.xml", lafData)
restoreRemoteAfterPush()
}
else {
assertThat(localConfigPath).doesNotExist()
}
fs.compare()
}
private fun createRemoteRepository(branchName: String? = null, initialCommit: Boolean = true) {
val repository = tempDirManager.createRepository("upstream")
if (initialCommit) {
@@ -396,7 +450,6 @@ class GitTest : IcsTestCase() {
sync(syncType)
val fs = MockVirtualFileSystem()
if (syncType != SyncType.OVERWRITE_LOCAL) {
fs.file(path, data)
}
@@ -23,8 +23,8 @@ import org.jetbrains.jgit.dirCache.edit
import org.jetbrains.settingsRepository.IcsManager
import org.jetbrains.settingsRepository.git
import org.junit.Rule
import java.nio.file.FileSystem
import java.nio.file.Path
import kotlin.properties.Delegates
fun Repository.add(path: String, data: String) = add(path, data.toByteArray())
@@ -46,6 +46,12 @@ abstract class IcsTestCase {
val tempDirManager = TemporaryDirectory()
@Rule fun getTemporaryFolder() = tempDirManager
private val fsRule = InMemoryFsRule()
@Rule fun _inMemoryFsRule() = fsRule
val fs: FileSystem
get() = fsRule.fs
val icsManager by lazy(LazyThreadSafetyMode.NONE) {
val icsManager = IcsManager(tempDirManager.newDirectory())
icsManager.repositoryManager.createRepositoryIfNeed()
@@ -15,31 +15,58 @@
*/
package org.jetbrains.settingsRepository.test
import com.intellij.mock.MockVirtualFileSystem
import com.intellij.openapi.vfs.VfsUtilCore
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.testFramework.LightVirtualFile
import com.github.marschall.memoryfilesystem.MemoryFileSystemBuilder
import com.intellij.testFramework.isFile
import gnu.trove.THashSet
import org.assertj.core.api.Assertions.assertThat
import org.eclipse.jgit.lib.Constants
import org.junit.rules.ExternalResource
import org.junit.runner.Description
import org.junit.runners.model.Statement
import java.net.URLEncoder
import java.nio.file.FileSystem
import java.nio.file.Files
import java.nio.file.Path
import kotlin.properties.Delegates
data class FileInfo(val name: String, val data: ByteArray)
fun fs() = MockVirtualFileSystem()
class InMemoryFsRule : ExternalResource() {
private var _fs: FileSystem? = null
private var sanitizedName: String by Delegates.notNull()
override fun apply(base: Statement, description: Description): Statement {
sanitizedName = URLEncoder.encode(description.methodName, Charsets.UTF_8.name())
return super.apply(base, description)
}
val fs: FileSystem
get() {
var r = _fs
if (r == null) {
r = MemoryFileSystemBuilder
.newLinux()
.setCurrentWorkingDirectory("/")
.build(sanitizedName)
_fs = r
}
return r!!
}
override fun after() {
_fs?.close()
_fs = null
}
}
private fun getChildrenStream(path: Path, excludes: Array<out String>? = null) = Files.list(path)
.filter { !it.endsWith(Constants.DOT_GIT) && (excludes == null || !excludes.contains(it.fileName.toString())) }
.sorted()
internal fun compareFiles(path1: Path, path2: Path, path3: VirtualFile? = null, vararg localExcludes: String) {
fun compareFiles(path1: Path, path2: Path, vararg localExcludes: String) {
assertThat(path1).isDirectory()
assertThat(path2).isDirectory()
if (path3 != null) {
assertThat(path3.isDirectory).isTrue()
}
val notFound = THashSet<Path>()
for (path in getChildrenStream(path1, localExcludes)) {
@@ -47,21 +74,15 @@ internal fun compareFiles(path1: Path, path2: Path, path3: VirtualFile? = null,
}
for (child2 in getChildrenStream(path2)) {
val fileName = child2.fileName
val child1 = path1.resolve(fileName)
val child3 = path3?.findChild(fileName.toString())
val child1 = path1.resolve(child2.fileName.toString())
if (child1.isFile()) {
assertThat(child2).hasSameContentAs(child1)
if (child3 != null) {
assertThat(child3.isDirectory).isFalse()
assertThat(child1).hasContent(if (child3 is LightVirtualFile) child3.content.toString() else VfsUtilCore.loadText(child3))
}
}
else {
compareFiles(child1, child2, child3, *localExcludes)
compareFiles(child1, child2, *localExcludes)
}
notFound.remove(child1)
}
assertThat(notFound).isEmpty()
assertThat(notFound).`as`("Path '$path1' contains other files than '$path2'").isEmpty()
}