-
Notifications
You must be signed in to change notification settings - Fork 501
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Some API Consumers keep a long-running instance of the KtLint engine alive. In case an `.editorconfig` file is changed, which was already loaded into the internal cache of the KtLint engine this change would not be taken into account by KtLint. One way to deal with this, was to clear the entire KtLint cache after each change in an `.editorconfig` file. Now, the API consumer can reload an `.editorconfig`. If the `.editorconfig` with given path is actually found in the cached, it will be replaced with the new value directly. If the file is not yet loaded in the cache, loading will be deferred until the file is actually requested again.
- Loading branch information
1 parent
069583e
commit 7b028ba
Showing
4 changed files
with
215 additions
and
8 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
64 changes: 56 additions & 8 deletions
64
...nt-core/src/main/kotlin/com/pinterest/ktlint/core/internal/ThreadSafeEditorConfigCache.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
135 changes: 135 additions & 0 deletions
135
...ore/src/test/kotlin/com/pinterest/ktlint/core/internal/ThreadSafeEditorConfigCacheTest.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,135 @@ | ||
package com.pinterest.ktlint.core.internal | ||
|
||
import ch.qos.logback.classic.Level | ||
import ch.qos.logback.classic.Logger | ||
import com.pinterest.ktlint.core.initKtLintKLogger | ||
import com.pinterest.ktlint.core.setDefaultLoggerModifier | ||
import java.nio.charset.StandardCharsets | ||
import java.nio.file.Paths | ||
import mu.KotlinLogging | ||
import org.assertj.core.api.Assertions.assertThat | ||
import org.ec4j.core.EditorConfigLoader | ||
import org.ec4j.core.Resource | ||
import org.ec4j.core.model.EditorConfig | ||
import org.ec4j.core.model.Glob | ||
import org.ec4j.core.model.Property | ||
import org.ec4j.core.model.Section | ||
import org.junit.jupiter.api.Test | ||
|
||
class ThreadSafeEditorConfigCacheTest { | ||
init { | ||
// Overwrite default logging with TRACE logging by initializing *and* printing first log statement before | ||
// loading any other classes. | ||
KotlinLogging | ||
.logger {} | ||
.setDefaultLoggerModifier { logger -> (logger.underlyingLogger as Logger).level = Level.TRACE } | ||
.initKtLintKLogger() | ||
.trace { "Enable trace logging for unit test" } | ||
} | ||
|
||
@Test | ||
fun `Given a file which is requested multiple times then it is read only once and then stored into and retrieved from the cache`() { | ||
val threadSafeEditorConfigCache = ThreadSafeEditorConfigCache() | ||
|
||
val editorConfigLoader = EditorConfigLoaderMock(EDIT_CONFIG_1) | ||
val actual = listOf( | ||
threadSafeEditorConfigCache.get(FILE_1, editorConfigLoader), | ||
threadSafeEditorConfigCache.get(FILE_1, editorConfigLoader), | ||
threadSafeEditorConfigCache.get(FILE_1, editorConfigLoader), | ||
) | ||
|
||
// In logs, it can also be seen that the EditConfig entry is created only once and retrieved multiple times | ||
assertThat(editorConfigLoader.loadCount).isEqualTo(1) | ||
assertThat(actual).containsExactly( | ||
EDIT_CONFIG_1, | ||
EDIT_CONFIG_1, | ||
EDIT_CONFIG_1, | ||
) | ||
} | ||
|
||
@Test | ||
fun `Given that multiple files are stored into the cache and one of those files is requested another time then this file is still being retrived from the cache`() { | ||
val threadSafeEditorConfigCache = ThreadSafeEditorConfigCache() | ||
val editorConfigLoaderFile1 = EditorConfigLoaderMock(EDIT_CONFIG_1) | ||
val editorConfigLoaderFile2 = EditorConfigLoaderMock(EDIT_CONFIG_2) | ||
|
||
val actual = listOf( | ||
threadSafeEditorConfigCache.get(FILE_1, editorConfigLoaderFile1), | ||
threadSafeEditorConfigCache.get(FILE_2, editorConfigLoaderFile2), | ||
threadSafeEditorConfigCache.get(FILE_1, editorConfigLoaderFile1), | ||
) | ||
|
||
// In logs, it can also be seen that the EditConfig entry for FILE_1 and FILE_2 are created only once and | ||
// retrieved once more for FILE_1 | ||
assertThat(editorConfigLoaderFile1.loadCount).isEqualTo(1) | ||
assertThat(editorConfigLoaderFile2.loadCount).isEqualTo(1) | ||
assertThat(actual).containsExactly( | ||
EDIT_CONFIG_1, | ||
EDIT_CONFIG_2, | ||
EDIT_CONFIG_1, | ||
) | ||
} | ||
|
||
@Test | ||
fun `Given that a file is stored in the cache and then the cache is cleared and the file is requested again then the file is to be reloaded`() { | ||
val threadSafeEditorConfigCache = ThreadSafeEditorConfigCache() | ||
|
||
val editorConfigLoaderFile1 = EditorConfigLoaderMock(EDIT_CONFIG_1) | ||
threadSafeEditorConfigCache.get(FILE_1, editorConfigLoaderFile1) | ||
threadSafeEditorConfigCache.clear() | ||
threadSafeEditorConfigCache.get(FILE_1, editorConfigLoaderFile1) | ||
threadSafeEditorConfigCache.get(FILE_1, editorConfigLoaderFile1) | ||
|
||
assertThat(editorConfigLoaderFile1.loadCount).isEqualTo(2) | ||
} | ||
|
||
@Test | ||
fun `Given that a file is stored in the cache and then file is explicitly reloaded`() { | ||
val threadSafeEditorConfigCache = ThreadSafeEditorConfigCache() | ||
|
||
val editorConfigLoaderFile1 = EditorConfigLoaderMock(EDIT_CONFIG_1) | ||
threadSafeEditorConfigCache.get(FILE_1, editorConfigLoaderFile1) | ||
threadSafeEditorConfigCache.reloadIfExists(FILE_1) | ||
threadSafeEditorConfigCache.reloadIfExists(FILE_1) | ||
|
||
assertThat(editorConfigLoaderFile1.loadCount).isEqualTo(3) | ||
} | ||
|
||
private companion object { | ||
const val SOME_PROPERTY = "some-property" | ||
|
||
private fun String.resource() = | ||
Resource.Resources.ofPath(Paths.get(this), StandardCharsets.UTF_8) | ||
val FILE_1: Resource = "/some/path/to/file/1".resource() | ||
val FILE_2: Resource = "/some/path/to/file/2".resource() | ||
|
||
// Create unique instances of the EditConfig by setting a value to a property | ||
fun createUniqueInstanceOfEditConfig(id: String): EditorConfig = | ||
EditorConfig | ||
.builder() | ||
.section( | ||
Section | ||
.builder() | ||
.glob(Glob("*.kt")) | ||
.properties( | ||
Property | ||
.builder() | ||
.name(SOME_PROPERTY) | ||
.value(id), | ||
), | ||
) | ||
.build() | ||
|
||
val EDIT_CONFIG_1: EditorConfig = createUniqueInstanceOfEditConfig("edit-config-1") | ||
val EDIT_CONFIG_2: EditorConfig = createUniqueInstanceOfEditConfig("edit-config-2") | ||
} | ||
|
||
private class EditorConfigLoaderMock(private var initial: EditorConfig) : EditorConfigLoader(null, null) { | ||
var loadCount = 0 | ||
|
||
override fun load(configFile: Resource): EditorConfig { | ||
loadCount++ | ||
return initial | ||
} | ||
} | ||
} |