c7c2ecc5e8a74eb13678b32408b5995519dcbf0b
[idea/community.git] / platform / configuration-store-impl / testSrc / SchemeManagerTest.kt
1 /*
2  * Copyright 2000-2015 JetBrains s.r.o.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.intellij.configurationStore
17
18 import com.intellij.openapi.options.ExternalizableScheme
19 import com.intellij.openapi.options.SchemeManagerFactory
20 import com.intellij.openapi.util.io.FileUtil
21 import com.intellij.openapi.util.text.StringUtil
22 import com.intellij.testFramework.PlatformTestUtil
23 import com.intellij.testFramework.ProjectRule
24 import com.intellij.testFramework.TemporaryDirectory
25 import com.intellij.util.SmartList
26 import com.intellij.util.io.createDirectories
27 import com.intellij.util.io.directoryStreamIfExists
28 import com.intellij.util.io.write
29 import com.intellij.util.lang.CompoundRuntimeException
30 import com.intellij.util.loadElement
31 import com.intellij.util.toByteArray
32 import com.intellij.util.xmlb.XmlSerializer
33 import com.intellij.util.xmlb.annotations.Tag
34 import com.intellij.util.xmlb.serialize
35 import gnu.trove.THashMap
36 import org.assertj.core.api.Assertions.assertThat
37 import org.assertj.core.api.Assertions.assertThatThrownBy
38 import org.junit.ClassRule
39 import org.junit.Rule
40 import org.junit.Test
41 import java.io.File
42 import java.nio.file.Path
43 import java.util.function.Function
44
45 internal val FILE_SPEC = "REMOTE"
46
47 /**
48  * Functionality without stream provider covered, ICS has own test suite
49  */
50 internal class SchemeManagerTest {
51   companion object {
52     @JvmField
53     @ClassRule val projectRule = ProjectRule()
54   }
55
56   private val tempDirManager = TemporaryDirectory()
57   @Rule fun getTemporaryFolder() = tempDirManager
58
59   private var localBaseDir: Path? = null
60   private var remoteBaseDir: Path? = null
61
62   private fun getTestDataPath() = PlatformTestUtil.getCommunityPath().replace(File.separatorChar, '/') + "/platform/platform-tests/testData/options"
63
64   @Test fun loadSchemes() {
65     doLoadSaveTest("options1", "1->first;2->second")
66   }
67
68   @Test fun loadSimpleSchemes() {
69     doLoadSaveTest("options", "1->1")
70   }
71
72   @Test fun deleteScheme() {
73     val manager = createAndLoad("options1")
74     manager.removeScheme("first")
75     manager.save()
76
77     checkSchemes("2->second")
78   }
79
80   @Test fun renameScheme() {
81     val manager = createAndLoad("options1")
82
83     val scheme = manager.findSchemeByName("first")
84     assertThat(scheme).isNotNull()
85     scheme!!.name = "renamed"
86     manager.save()
87
88     checkSchemes("2->second;renamed->renamed")
89   }
90
91   @Test fun testRenameScheme2() {
92     val manager = createAndLoad("options1")
93
94     val first = manager.findSchemeByName("first")
95     assertThat(first).isNotNull()
96     assert(first != null)
97     first!!.name = "2"
98     val second = manager.findSchemeByName("second")
99     assertThat(second).isNotNull()
100     assert(second != null)
101     second!!.name = "1"
102     manager.save()
103
104     checkSchemes("1->1;2->2")
105   }
106
107   @Test fun testDeleteRenamedScheme() {
108     val manager = createAndLoad("options1")
109
110     val firstScheme = manager.findSchemeByName("first")
111     assertThat(firstScheme).isNotNull()
112     assert(firstScheme != null)
113     firstScheme!!.name = "first_renamed"
114     manager.save()
115
116     checkSchemes(remoteBaseDir!!.resolve("REMOTE"), "first_renamed->first_renamed;2->second", true)
117     checkSchemes(localBaseDir!!, "", false)
118
119     firstScheme.name = "first_renamed2"
120     manager.removeScheme(firstScheme)
121     manager.save()
122
123     checkSchemes(remoteBaseDir!!.resolve("REMOTE"), "2->second", true)
124     checkSchemes(localBaseDir!!, "", false)
125   }
126
127   @Test fun testDeleteAndCreateSchemeWithTheSameName() {
128     val manager = createAndLoad("options1")
129     val firstScheme = manager.findSchemeByName("first")
130     assertThat(firstScheme).isNotNull()
131
132     manager.removeScheme(firstScheme!!)
133     manager.addScheme(TestScheme("first"))
134     manager.save()
135     checkSchemes("2->second;first->first")
136   }
137
138   @Test fun testGenerateUniqueSchemeName() {
139     val manager = createAndLoad("options1")
140     val scheme = TestScheme("first")
141     manager.addNewScheme(scheme, false)
142
143     assertThat("first2").isEqualTo(scheme.name)
144   }
145
146   fun TestScheme.save(file: Path) {
147     file.write(serialize().toByteArray())
148   }
149
150   @Test fun `different extensions`() {
151     val dir = tempDirManager.newPath()
152
153     val scheme = TestScheme("local", "true")
154     scheme.save(dir.resolve("1.icls"))
155     TestScheme("local", "false").save(dir.resolve("1.xml"))
156
157     class ATestSchemesProcessor : TestSchemesProcessor(), SchemeExtensionProvider {
158       override val schemeExtension = ".icls"
159     }
160
161     val schemesManager = SchemeManagerImpl(FILE_SPEC, ATestSchemesProcessor(), null, dir)
162     schemesManager.loadSchemes()
163     assertThat(schemesManager.allSchemes).containsOnly(scheme)
164
165     assertThat(dir.resolve("1.icls")).isRegularFile()
166     assertThat(dir.resolve("1.xml")).isRegularFile()
167
168     scheme.data = "newTrue"
169     schemesManager.save()
170
171     assertThat(dir.resolve("1.icls")).isRegularFile()
172     assertThat(dir.resolve("1.xml")).doesNotExist()
173   }
174
175   @Test fun setSchemes() {
176     val dir = tempDirManager.newPath()
177     val schemeManager = createSchemeManager(dir)
178     schemeManager.loadSchemes()
179     assertThat(schemeManager.allSchemes).isEmpty()
180
181     val scheme = TestScheme("s1")
182     schemeManager.setSchemes(listOf(scheme))
183
184     val schemes = schemeManager.allSchemes
185     assertThat(schemes).containsOnly(scheme)
186
187     assertThat(dir.resolve("s1.xml")).doesNotExist()
188
189     scheme.data = "newTrue"
190     schemeManager.save()
191
192     assertThat(dir.resolve("s1.xml")).isRegularFile()
193
194     schemeManager.setSchemes(emptyList())
195
196     schemeManager.save()
197
198     assertThat(dir).doesNotExist()
199   }
200
201   @Test fun `reload schemes`() {
202     val dir = tempDirManager.newPath()
203     val schemeManager = createSchemeManager(dir)
204     schemeManager.loadSchemes()
205     assertThat(schemeManager.allSchemes).isEmpty()
206
207     val scheme = TestScheme("s1", "oldData")
208     schemeManager.setSchemes(listOf(scheme))
209     assertThat(schemeManager.allSchemes).containsOnly(scheme)
210     schemeManager.save()
211
212     dir.resolve("s1.xml").write("""<scheme name="s1" data="newData" />""")
213     schemeManager.reload()
214
215     assertThat(schemeManager.allSchemes).containsOnly(TestScheme("s1", "newData"))
216   }
217
218   @Test fun `save only if scheme differs from bundled`() {
219     val dir = tempDirManager.newPath()
220     var schemeManager = createSchemeManager(dir)
221     val bundledPath = "/com/intellij/configurationStore/bundledSchemes/default"
222     schemeManager.loadBundledScheme(bundledPath, this)
223     val customScheme = TestScheme("default")
224     assertThat(schemeManager.allSchemes).containsOnly(customScheme)
225
226     schemeManager.save()
227     assertThat(dir).doesNotExist()
228
229     schemeManager.save()
230     schemeManager.setSchemes(listOf(customScheme))
231     assertThat(dir).doesNotExist()
232
233     assertThat(schemeManager.allSchemes).containsOnly(customScheme)
234
235     customScheme.data = "foo"
236     schemeManager.save()
237     assertThat(dir.resolve("default.xml")).isRegularFile()
238
239     schemeManager = createSchemeManager(dir)
240     schemeManager.loadBundledScheme(bundledPath, this)
241     schemeManager.loadSchemes()
242
243     assertThat(schemeManager.allSchemes).containsOnly(customScheme)
244   }
245
246   @Test fun `don't remove dir if no schemes but at least one non-hidden file exists`() {
247     val dir = tempDirManager.newPath()
248     val schemeManager = createSchemeManager(dir)
249
250     val scheme = TestScheme("s1")
251     schemeManager.setSchemes(listOf(scheme))
252
253     schemeManager.save()
254
255     val schemeFile = dir.resolve("s1.xml")
256     assertThat(schemeFile).isRegularFile()
257
258     schemeManager.setSchemes(emptyList())
259
260     dir.resolve("empty").write(byteArrayOf())
261
262     schemeManager.save()
263
264     assertThat(schemeFile).doesNotExist()
265     assertThat(dir).isDirectory()
266   }
267
268   @Test fun `remove empty directory only if some file was deleted`() {
269     val dir = tempDirManager.newPath()
270     val schemeManager = createSchemeManager(dir)
271     schemeManager.loadSchemes()
272
273     dir.createDirectories()
274     schemeManager.save()
275     assertThat(dir).isDirectory()
276
277     schemeManager.addScheme(TestScheme("test"))
278     schemeManager.save()
279     assertThat(dir).isDirectory()
280
281     schemeManager.setSchemes(emptyList())
282     schemeManager.save()
283     assertThat(dir).doesNotExist()
284   }
285
286   @Test fun rename() {
287     val dir = tempDirManager.newPath()
288     val schemeManager = createSchemeManager(dir)
289     schemeManager.loadSchemes()
290     assertThat(schemeManager.allSchemes).isEmpty()
291
292     val scheme = TestScheme("s1")
293     schemeManager.setSchemes(listOf(scheme))
294
295     val schemes = schemeManager.allSchemes
296     assertThat(schemes).containsOnly(scheme)
297
298     assertThat(dir.resolve("s1.xml")).doesNotExist()
299
300     scheme.data = "newTrue"
301     schemeManager.save()
302
303     assertThat(dir.resolve("s1.xml")).isRegularFile()
304
305     scheme.name = "s2"
306
307     schemeManager.save()
308
309     assertThat(dir.resolve("s1.xml")).doesNotExist()
310     assertThat(dir.resolve("s2.xml")).isRegularFile()
311   }
312
313   @Test fun `path must not contains ROOT_CONFIG macro`() {
314     assertThatThrownBy({ SchemeManagerFactory.getInstance().create("\$ROOT_CONFIG$/foo", TestSchemesProcessor()) }).hasMessage("Path must not contains ROOT_CONFIG macro, corrected: foo")
315   }
316
317   @Test fun `path must be system-independent`() {
318     assertThatThrownBy({ SchemeManagerFactory.getInstance().create("foo\\bar", TestSchemesProcessor())}).hasMessage("Path must be system-independent, use forward slash instead of backslash")
319   }
320
321   private fun createSchemeManager(dir: Path) = SchemeManagerImpl(FILE_SPEC, TestSchemesProcessor(), null, dir)
322
323   private fun createAndLoad(testData: String): SchemeManagerImpl<TestScheme, TestScheme> {
324     createTempFiles(testData)
325     return createAndLoad()
326   }
327
328   private fun doLoadSaveTest(testData: String, expected: String, localExpected: String = "") {
329     val schemesManager = createAndLoad(testData)
330     schemesManager.save()
331     checkSchemes(remoteBaseDir!!.resolve("REMOTE"), expected, true)
332     checkSchemes(localBaseDir!!, localExpected, false)
333   }
334
335   private fun checkSchemes(expected: String) {
336     checkSchemes(remoteBaseDir!!.resolve("REMOTE"), expected, true)
337     checkSchemes(localBaseDir!!, "", false)
338   }
339
340   private fun createAndLoad(): SchemeManagerImpl<TestScheme, TestScheme> {
341     val schemesManager = SchemeManagerImpl(FILE_SPEC, TestSchemesProcessor(), MockStreamProvider(remoteBaseDir!!), localBaseDir!!)
342     schemesManager.loadSchemes()
343     return schemesManager
344   }
345
346   private fun createTempFiles(testData: String) {
347     val temp = tempDirManager.newPath()
348     localBaseDir = temp.resolve("__local")
349     remoteBaseDir = temp
350     FileUtil.copyDir(File("${getTestDataPath()}/$testData"), temp.resolve("REMOTE").toFile())
351   }
352 }
353
354 private fun checkSchemes(baseDir: Path, expected: String, ignoreDeleted: Boolean) {
355   val filesToScheme = StringUtil.split(expected, ";")
356   val fileToSchemeMap = THashMap<String, String>()
357   for (fileToScheme in filesToScheme) {
358     val index = fileToScheme.indexOf("->")
359     fileToSchemeMap.put(fileToScheme.substring(0, index), fileToScheme.substring(index + 2))
360   }
361
362   baseDir.directoryStreamIfExists {
363     for (file in it) {
364       val fileName = FileUtil.getNameWithoutExtension(file.fileName.toString())
365       if ("--deleted" == fileName && ignoreDeleted) {
366         assertThat(fileToSchemeMap).containsKey(fileName)
367       }
368     }
369   }
370
371   for (file in fileToSchemeMap.keys) {
372     assertThat(baseDir.resolve("$file.xml")).isRegularFile()
373   }
374
375   baseDir.directoryStreamIfExists {
376     for (file in it) {
377       val scheme = XmlSerializer.deserialize(loadElement(file), TestScheme::class.java)!!
378       assertThat(fileToSchemeMap.get(FileUtil.getNameWithoutExtension(file.fileName.toString()))).isEqualTo(scheme.name)
379     }
380   }
381 }
382
383 @Tag("scheme")
384 data class TestScheme(@field:com.intellij.util.xmlb.annotations.Attribute @field:kotlin.jvm.JvmField var name: String = "", @field:com.intellij.util.xmlb.annotations.Attribute var data: String? = null) : ExternalizableScheme, SerializableScheme {
385   override fun getName() = name
386
387   override fun setName(value: String) {
388     name = value
389   }
390
391   override fun writeScheme() = serialize()
392 }
393
394 open class TestSchemesProcessor : LazySchemeProcessor<TestScheme, TestScheme>() {
395   override fun createScheme(dataHolder: SchemeDataHolder<TestScheme>,
396                             name: String,
397                             attributeProvider: Function<String, String?>,
398                             isBundled: Boolean): TestScheme {
399     val scheme = XmlSerializer.deserialize(dataHolder.read(), TestScheme::class.java)!!
400     dataHolder.updateDigest(scheme)
401     return scheme
402   }
403 }
404
405 fun SchemeManagerImpl<*, *>.save() {
406   val errors = SmartList<Throwable>()
407   save(errors)
408   CompoundRuntimeException.throwIfNotEmpty(errors)
409 }