View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *     https://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.configuration2;
18  
19  import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
20  import static org.junit.jupiter.api.Assertions.assertEquals;
21  import static org.junit.jupiter.api.Assertions.assertFalse;
22  import static org.junit.jupiter.api.Assertions.assertNotEquals;
23  import static org.junit.jupiter.api.Assertions.assertNull;
24  import static org.junit.jupiter.api.Assertions.assertSame;
25  import static org.junit.jupiter.api.Assertions.assertThrows;
26  import static org.junit.jupiter.api.Assertions.assertTrue;
27  
28  import java.io.Reader;
29  import java.io.StringReader;
30  import java.io.StringWriter;
31  import java.net.URL;
32  import java.util.Deque;
33  import java.util.Iterator;
34  
35  import org.apache.commons.configuration2.convert.DefaultListDelimiterHandler;
36  import org.apache.commons.configuration2.convert.LegacyListDelimiterHandler;
37  import org.apache.commons.configuration2.event.ConfigurationEvent;
38  import org.apache.commons.configuration2.event.EventListener;
39  import org.apache.commons.configuration2.ex.ConfigurationException;
40  import org.junit.jupiter.api.BeforeEach;
41  import org.junit.jupiter.api.Test;
42  
43  /**
44   * Test class for PropertiesConfigurationLayout.
45   */
46  public class TestPropertiesConfigurationLayout {
47  
48      /**
49       * A mock properties configuration implementation that is used to check whether some expected methods are called.
50       */
51      static class LayoutTestConfiguration extends PropertiesConfiguration {
52  
53          /** Stores a builder object. */
54          private PropertiesBuilder builder;
55  
56          /**
57           * Simulates the propertyLoaded() callback. If a builder was set, a load() call on the layout is invoked.
58           */
59          @Override
60          boolean propertyLoaded(final String key, final String value, final Deque<URL> seenStack) throws ConfigurationException {
61              if (builder == null) {
62                  return super.propertyLoaded(key, value, seenStack);
63              }
64              if (PropertiesConfiguration.getInclude().equals(key)) {
65                  getLayout().load(this, builder.getReader());
66                  return false;
67              }
68              return true;
69          }
70      }
71  
72      /**
73       * A helper class used for constructing test properties files.
74       */
75      static class PropertiesBuilder {
76  
77          /** A buffer for storing the data. */
78          private final StringBuilder buf = new StringBuilder();
79  
80          /** A counter for varying the comment character. */
81          private int commentCounter;
82  
83          /**
84           * Adds a comment line.
85           *
86           * @param s the comment (can be <strong>null</strong>, then a blank line is added)
87           */
88          public void addComment(final String s) {
89              if (s != null) {
90                  if (commentCounter % 2 == 0) {
91                      buf.append("# ");
92                  } else {
93                      buf.append("! ");
94                  }
95                  buf.append(s);
96                  commentCounter++;
97              }
98              buf.append(CR);
99          }
100 
101         /**
102          * Adds a line verbatim to the simulated file.
103          *
104          * @param s the content of the line
105          */
106         public void addLine(final String s) {
107             buf.append(s).append(CR);
108         }
109 
110         /**
111          * Adds a property to the simulated file.
112          *
113          * @param key the property key
114          * @param value the value
115          */
116         public void addProperty(final String key, final String value) {
117             buf.append(key).append(" = ").append(value).append(CR);
118         }
119 
120         /**
121          * Returns a reader for the simulated properties.
122          *
123          * @return a reader
124          */
125         public Reader getReader() {
126             return new StringReader(buf.toString());
127         }
128 
129         /**
130          * Returns a string representation of the buffer's content.
131          *
132          * @return the buffer as string
133          */
134         @Override
135         public String toString() {
136             return buf.toString();
137         }
138     }
139 
140     /** Constant for the line break character. */
141     private static final String CR = System.lineSeparator();
142 
143     /** Constant for the normalized line break character. */
144     private static final String CRNORM = "\n";
145 
146     /** Constant for a test property key. */
147     private static final String TEST_KEY = "myProperty";
148 
149     /** Constant for a test comment. */
150     private static final String TEST_COMMENT = "A comment for my test property";
151 
152     /** Constant for a test property value. */
153     private static final String TEST_VALUE = "myPropertyValue";
154 
155     /** The layout object under test. */
156     private PropertiesConfigurationLayout layout;
157 
158     /** The associated configuration object. */
159     private LayoutTestConfiguration config;
160 
161     /** A properties builder that can be used for testing. */
162     private PropertiesBuilder builder;
163 
164     /**
165      * Checks if the layout's output is correct.
166      *
167      * @param expected the expected result
168      * @throws ConfigurationException if an error occurs
169      */
170     private void checkLayoutString(final String expected) throws ConfigurationException {
171         assertEquals(expected, getLayoutString());
172     }
173 
174     /**
175      * Helper method for filling the layout object with some properties.
176      */
177     private void fillLayout() {
178         builder.addComment("A header comment");
179         builder.addComment(null);
180         builder.addProperty("prop", "value");
181         builder.addComment(TEST_COMMENT);
182         builder.addProperty(TEST_KEY, TEST_VALUE);
183         builder.addProperty("anotherProp", "anotherValue");
184         builder.addComment("A footer comment");
185         assertDoesNotThrow(() -> layout.load(config, builder.getReader()));
186     }
187 
188     /**
189      * Writes the layout's data into a string.
190      *
191      * @return the layout file's content as string
192      * @throws ConfigurationException if an error occurs
193      */
194     private String getLayoutString() throws ConfigurationException {
195         final StringWriter out = new StringWriter();
196         layout.save(config, out);
197         return out.toString();
198     }
199 
200     @BeforeEach
201     public void setUp() throws Exception {
202         config = new LayoutTestConfiguration();
203         config.setListDelimiterHandler(new LegacyListDelimiterHandler(','));
204         layout = new PropertiesConfigurationLayout();
205         config.setLayout(layout);
206         builder = new PropertiesBuilder();
207     }
208 
209     /**
210      * Tests whether blank lines before a property are correctly detected.
211      */
212     @Test
213     void testBlankLines() throws ConfigurationException {
214         builder.addProperty("prop", "value");
215         builder.addComment(null);
216         builder.addComment(null);
217         builder.addComment(TEST_COMMENT);
218         builder.addComment(null);
219         builder.addProperty(TEST_KEY, TEST_VALUE);
220         layout.load(config, builder.getReader());
221         assertEquals(2, layout.getBlankLinesBefore(TEST_KEY));
222         assertEquals(TEST_COMMENT + CRNORM, layout.getCanonicalComment(TEST_KEY, false));
223         assertEquals(TEST_VALUE, config.getString(TEST_KEY));
224     }
225 
226     /**
227      * Tests whether blank lines before a property are correctly detected if a header comment is present
228      */
229     @Test
230     void testBlankLinesWithHeaderComment() throws ConfigurationException {
231         builder.addComment(TEST_COMMENT);
232         builder.addComment(null);
233         builder.addComment(null);
234         builder.addComment(TEST_COMMENT);
235         builder.addProperty(TEST_KEY, TEST_VALUE);
236         layout.load(config, builder.getReader());
237         assertEquals(2, layout.getBlankLinesBefore(TEST_KEY));
238         assertEquals(TEST_COMMENT, layout.getCanonicalComment(TEST_KEY, false));
239         assertEquals(TEST_VALUE, config.getString(TEST_KEY));
240     }
241 
242     /**
243      * Tests whether comments are combined for multiple occurrences.
244      */
245     @Test
246     void testCombineComments() throws ConfigurationException {
247         builder.addComment(TEST_COMMENT);
248         builder.addProperty(TEST_KEY, TEST_VALUE);
249         builder.addComment(null);
250         builder.addComment(TEST_COMMENT);
251         builder.addProperty(TEST_KEY, TEST_VALUE + "2");
252         layout.load(config, builder.getReader());
253         assertEquals(TEST_COMMENT + CRNORM + TEST_COMMENT, layout.getCanonicalComment(TEST_KEY, false));
254         assertEquals(0, layout.getBlankLinesBefore(TEST_KEY));
255     }
256 
257     /**
258      * Tests if a property add event is correctly processed.
259      */
260     @Test
261     void testEventAdd() {
262         final ConfigurationEvent event = new ConfigurationEvent(this, ConfigurationEvent.ADD_PROPERTY, TEST_KEY, TEST_VALUE, false);
263         layout.onEvent(event);
264         assertTrue(layout.getKeys().contains(TEST_KEY));
265         assertEquals(0, layout.getBlankLinesBefore(TEST_KEY));
266         assertTrue(layout.isSingleLine(TEST_KEY));
267         assertEquals(" = ", layout.getSeparator(TEST_KEY));
268     }
269 
270     /**
271      * Tests if a before update event is correctly ignored.
272      */
273     @Test
274     void testEventAddBefore() {
275         final ConfigurationEvent event = new ConfigurationEvent(this, ConfigurationEvent.ADD_PROPERTY, TEST_KEY, TEST_VALUE, true);
276         layout.onEvent(event);
277         assertFalse(layout.getKeys().contains(TEST_KEY));
278     }
279 
280     /**
281      * Tests if an add event is correctly processed if the affected property is already stored in the layout object.
282      */
283     @Test
284     void testEventAddExisting() throws ConfigurationException {
285         builder.addComment(TEST_COMMENT);
286         builder.addProperty(TEST_KEY, TEST_VALUE);
287         layout.load(config, builder.getReader());
288         final ConfigurationEvent event = new ConfigurationEvent(this, ConfigurationEvent.ADD_PROPERTY, TEST_KEY, TEST_VALUE, false);
289         layout.onEvent(event);
290         assertFalse(layout.isSingleLine(TEST_KEY));
291         assertEquals(TEST_COMMENT, layout.getCanonicalComment(TEST_KEY, false));
292     }
293 
294     /**
295      * Tests adding a property multiple time through an event. The property should then be a multi-line property.
296      */
297     @Test
298     void testEventAddMultiple() {
299         final ConfigurationEvent event = new ConfigurationEvent(this, ConfigurationEvent.ADD_PROPERTY, TEST_KEY, TEST_VALUE, false);
300         layout.onEvent(event);
301         layout.onEvent(event);
302         assertFalse(layout.isSingleLine(TEST_KEY));
303     }
304 
305     /**
306      * Tests if a clear event is correctly processed.
307      */
308     @Test
309     void testEventClearConfig() throws Exception {
310         fillLayout();
311         final ConfigurationEvent event = new ConfigurationEvent(this, ConfigurationEvent.CLEAR, null, null, false);
312         layout.onEvent(event);
313         assertTrue(layout.getKeys().isEmpty());
314         assertNull(layout.getHeaderComment());
315     }
316 
317     /**
318      * Tests if a property delete event is correctly processed.
319      */
320     @Test
321     void testEventDelete() {
322         ConfigurationEvent event = new ConfigurationEvent(this, ConfigurationEvent.ADD_PROPERTY, TEST_KEY, TEST_VALUE, false);
323         layout.onEvent(event);
324         event = new ConfigurationEvent(this, ConfigurationEvent.CLEAR_PROPERTY, TEST_KEY, null, false);
325         layout.onEvent(event);
326         assertFalse(layout.getKeys().contains(TEST_KEY));
327     }
328 
329     /**
330      * Tests if a set property event for a non existing property is correctly handled.
331      */
332     @Test
333     void testEventSetNonExisting() {
334         final ConfigurationEvent event = new ConfigurationEvent(this, ConfigurationEvent.SET_PROPERTY, TEST_KEY, TEST_VALUE, false);
335         layout.onEvent(event);
336         assertTrue(layout.getKeys().contains(TEST_KEY));
337     }
338 
339     /**
340      * Tests accessing data for a property, which is not stored.
341      */
342     @Test
343     void testGetNonExistingLayouData() {
344         assertNull(layout.getComment("unknown"));
345         assertTrue(layout.isSingleLine("unknown"));
346         assertEquals(0, layout.getBlankLinesBefore("unknown"));
347     }
348 
349     /**
350      * Tests accessing a property with a null key. This should throw an exception.
351      */
352     @Test
353     void testGetNullLayouttData() {
354         assertThrows(IllegalArgumentException.class, () -> layout.setComment(null, TEST_COMMENT));
355     }
356 
357     /**
358      * Tests if a header comment is detected.
359      */
360     @Test
361     void testHeaderComment() throws ConfigurationException {
362         builder.addComment(TEST_COMMENT);
363         builder.addComment(null);
364         builder.addProperty(TEST_KEY, TEST_VALUE);
365         layout.load(config, builder.getReader());
366         assertEquals(TEST_COMMENT, layout.getCanonicalHeaderComment(false));
367         assertNull(layout.getCanonicalComment(TEST_KEY, false));
368     }
369 
370     /**
371      * Tests fetching a canonical header comment when no comment is set.
372      */
373     @Test
374     void testHeaderCommentNull() {
375         assertNull(layout.getCanonicalHeaderComment(true));
376         assertNull(layout.getCanonicalHeaderComment(false));
377     }
378 
379     /**
380      * Tests if a header comment containing blank lines is correctly detected.
381      */
382     @Test
383     void testHeaderCommentWithBlanks() throws ConfigurationException {
384         builder.addComment(TEST_COMMENT);
385         builder.addComment(null);
386         builder.addComment(TEST_COMMENT);
387         builder.addComment(null);
388         builder.addProperty(TEST_KEY, TEST_VALUE);
389         layout.load(config, builder.getReader());
390         assertEquals(TEST_COMMENT + CRNORM + CRNORM + TEST_COMMENT, layout.getCanonicalHeaderComment(false));
391         assertNull(layout.getComment(TEST_KEY));
392     }
393 
394     /**
395      * Tests if a header comment containing blank lines is correctly detected and doesn't overflow into the property comment
396      * in the case that the header comment is already set
397      */
398     @Test
399     void testHeaderCommentWithBlanksAndPresetHeaderComment() throws ConfigurationException {
400         final String presetHeaderComment = "preset" + TEST_COMMENT + CRNORM + CRNORM + TEST_COMMENT;
401         builder.addComment(TEST_COMMENT);
402         builder.addComment(null);
403         builder.addComment(TEST_COMMENT);
404         builder.addComment(null);
405         builder.addProperty(TEST_KEY, TEST_VALUE);
406         layout.setHeaderComment(presetHeaderComment);
407         layout.load(config, builder.getReader());
408         assertEquals(presetHeaderComment, layout.getCanonicalHeaderComment(false));
409         assertNull(layout.getComment(TEST_KEY));
410     }
411 
412     /**
413      * Tests if a header comment is correctly detected when it contains blank lines and the first property has a comment,
414      * too.
415      */
416     @Test
417     void testHeaderCommentWithBlanksAndPropComment() throws ConfigurationException {
418         builder.addComment(TEST_COMMENT);
419         builder.addComment(null);
420         builder.addComment(TEST_COMMENT);
421         builder.addComment(null);
422         builder.addComment(TEST_COMMENT);
423         builder.addProperty(TEST_KEY, TEST_VALUE);
424         layout.load(config, builder.getReader());
425         assertEquals(TEST_COMMENT + CRNORM + CRNORM + TEST_COMMENT, layout.getCanonicalHeaderComment(false));
426         assertEquals(TEST_COMMENT, layout.getCanonicalComment(TEST_KEY, false));
427     }
428 
429     /**
430      * Tests a newly created instance.
431      */
432     @Test
433     void testInit() {
434         assertTrue(layout.getKeys().isEmpty());
435         assertNull(layout.getHeaderComment());
436         final Iterator<EventListener<? super ConfigurationEvent>> it = config.getEventListeners(ConfigurationEvent.ANY).iterator();
437         assertTrue(it.hasNext());
438         assertSame(layout, it.next());
439         assertFalse(it.hasNext());
440         assertFalse(layout.isForceSingleLine());
441         assertNull(layout.getGlobalSeparator());
442     }
443 
444     /**
445      * Tests the copy constructor.
446      */
447     @Test
448     void testInitCopy() {
449         fillLayout();
450         final PropertiesConfigurationLayout l2 = new PropertiesConfigurationLayout(layout);
451         assertEquals(l2.getKeys(), layout.getKeys());
452         assertEquals(layout.getHeaderComment(), l2.getHeaderComment());
453         assertEquals(layout.getFooterComment(), l2.getFooterComment());
454     }
455 
456     /**
457      * Tests if the copy and the original are independent from each other.
458      */
459     @Test
460     void testInitCopyModify() {
461         fillLayout();
462         final PropertiesConfigurationLayout l2 = new PropertiesConfigurationLayout(layout);
463         assertEquals(layout.getComment(TEST_KEY), l2.getComment(TEST_KEY));
464         layout.setComment(TEST_KEY, "A new comment");
465         assertEquals(TEST_COMMENT, l2.getCanonicalComment(TEST_KEY, false));
466         l2.setBlankLinesBefore(TEST_KEY, l2.getBlankLinesBefore(TEST_KEY) + 1);
467         assertNotEquals(layout.getBlankLinesBefore(TEST_KEY), l2.getBlankLinesBefore(TEST_KEY));
468     }
469 
470     /**
471      * Tests the copy constructor if no other layout object is passed.
472      */
473     @Test
474     void testInitNull() {
475         layout = new PropertiesConfigurationLayout(null);
476         assertTrue(layout.getKeys().isEmpty());
477     }
478 
479     /**
480      * Tests the single line flag for a simple property definition.
481      */
482     @Test
483     void testIsSingleLine() throws ConfigurationException {
484         builder.addProperty(TEST_KEY, TEST_VALUE + "," + TEST_VALUE + "2");
485         layout.load(config, builder.getReader());
486         assertTrue(layout.isSingleLine(TEST_KEY));
487         assertEquals(2, config.getList(TEST_KEY).size());
488     }
489 
490     /**
491      * Tests the single line flag if there are multiple property definitions.
492      */
493     @Test
494     void testIsSingleLineMulti() throws ConfigurationException {
495         builder.addProperty(TEST_KEY, TEST_VALUE);
496         builder.addProperty("anotherProp", "a value");
497         builder.addProperty(TEST_KEY, TEST_VALUE + "2");
498         layout.load(config, builder.getReader());
499         assertFalse(layout.isSingleLine(TEST_KEY));
500         assertEquals(2, config.getList(TEST_KEY).size());
501     }
502 
503     /**
504      * Tests whether a line with whitespace is handled correctly. This is related to CONFIGURATION-582.
505      */
506     @Test
507     void testLineWithBlank() throws ConfigurationException {
508         builder.addComment(TEST_COMMENT);
509         builder.addLine(" ");
510         builder.addProperty(TEST_KEY, TEST_VALUE);
511         layout.load(config, builder.getReader());
512         assertEquals(TEST_COMMENT + CRNORM + " ", layout.getCanonicalComment(TEST_KEY, false));
513     }
514 
515     /**
516      * Tests whether the output of the layout object is identical to the source file (at least for simple properties files).
517      */
518     @Test
519     void testReadAndWrite() throws ConfigurationException {
520         builder.addComment("This is my test properties file,");
521         builder.addComment("which contains a header comment.");
522         builder.addComment(null);
523         builder.addComment(null);
524         builder.addComment(TEST_COMMENT);
525         builder.addProperty(TEST_KEY, TEST_COMMENT);
526         builder.addComment(null);
527         builder.addComment(null);
528         builder.addComment("Another comment");
529         builder.addProperty("property", "and a value");
530         layout.load(config, builder.getReader());
531         checkLayoutString(builder.toString());
532     }
533 
534     /**
535      * Tests reading a simple properties file.
536      */
537     @Test
538     void testReadSimple() throws ConfigurationException {
539         builder.addComment(TEST_COMMENT);
540         builder.addProperty(TEST_KEY, TEST_VALUE);
541         layout.load(config, builder.getReader());
542         assertNull(layout.getHeaderComment());
543         assertEquals(1, layout.getKeys().size());
544         assertTrue(layout.getKeys().contains(TEST_KEY));
545         assertEquals(TEST_COMMENT, layout.getCanonicalComment(TEST_KEY, false));
546         assertEquals(0, layout.getBlankLinesBefore(TEST_KEY));
547         assertTrue(layout.isSingleLine(TEST_KEY));
548         assertEquals(TEST_VALUE, config.getString(TEST_KEY));
549     }
550 
551     /**
552      * Tests a recursive load call.
553      */
554     @Test
555     void testRecursiveLoadCall() throws ConfigurationException {
556         final PropertiesBuilder b = new PropertiesBuilder();
557         b.addComment("A nested header comment.");
558         b.addComment("With multiple lines");
559         b.addComment(null);
560         b.addComment("Second comment");
561         b.addProperty(TEST_KEY, TEST_VALUE);
562         b.addProperty(TEST_KEY + "2", TEST_VALUE + "2");
563         config.builder = b;
564 
565         builder.addComment("Header comment");
566         builder.addComment(null);
567         builder.addComment(TEST_COMMENT);
568         builder.addProperty(TEST_KEY, TEST_VALUE);
569         builder.addComment("Include file");
570         builder.addProperty(PropertiesConfiguration.getInclude(), "test");
571 
572         layout.load(config, builder.getReader());
573 
574         assertEquals("Header comment", layout.getCanonicalHeaderComment(false));
575         assertFalse(layout.getKeys().contains(PropertiesConfiguration.getInclude()));
576         assertEquals(TEST_COMMENT + CRNORM + "A nested header comment." + CRNORM + "With multiple lines" + CRNORM + CRNORM + "Second comment",
577                 layout.getCanonicalComment(TEST_KEY, false));
578     }
579 
580     /**
581      * Tests if the content of the layout object is correctly written.
582      */
583     @Test
584     void testSave() throws ConfigurationException {
585         config.addProperty(TEST_KEY, TEST_VALUE);
586         layout.setComment(TEST_KEY, TEST_COMMENT);
587         config.addProperty(TEST_KEY, TEST_VALUE + "2");
588         config.addProperty("AnotherProperty", "AnotherValue");
589         config.addProperty("AnotherProperty", "3rdValue");
590         layout.setComment("AnotherProperty", "AnotherComment");
591         layout.setBlankLinesBefore("AnotherProperty", 2);
592         layout.setSingleLine("AnotherProperty", true);
593         layout.setHeaderComment("A header comment" + CRNORM + "for my properties");
594         checkLayoutString("# A header comment" + CR + "# for my properties" + CR + CR + "# " + TEST_COMMENT + CR + TEST_KEY + " = " + TEST_VALUE + CR + TEST_KEY
595             + " = " + TEST_VALUE + "2" + CR + CR + CR + "# AnotherComment" + CR + "AnotherProperty = AnotherValue,3rdValue" + CR);
596     }
597 
598     /**
599      * Tests saving when a comment for a non existing property is contained in the layout object. This comment should be
600      * ignored.
601      */
602     @Test
603     void testSaveCommentForUnexistingProperty() throws ConfigurationException {
604         fillLayout();
605         layout.setComment("NonExistingKey", "NonExistingComment");
606         final String output = getLayoutString();
607         assertFalse(output.contains("NonExistingKey"));
608         assertFalse(output.contains("NonExistingComment"));
609     }
610 
611     /**
612      * Tests saving an empty layout object.
613      */
614     @Test
615     void testSaveEmptyLayout() throws ConfigurationException {
616         checkLayoutString("");
617     }
618 
619     /**
620      * Tests the force single line flag.
621      */
622     @Test
623     void testSaveForceSingleLine() throws ConfigurationException {
624         config.setListDelimiterHandler(new DefaultListDelimiterHandler(';'));
625         config.addProperty(TEST_KEY, TEST_VALUE);
626         config.addProperty(TEST_KEY, TEST_VALUE + "2");
627         config.addProperty("AnotherProperty", "value1;value2;value3");
628         layout.setComment(TEST_KEY, TEST_COMMENT);
629         layout.setForceSingleLine(true);
630         checkLayoutString(
631             "# " + TEST_COMMENT + CR + TEST_KEY + " = " + TEST_VALUE + ';' + TEST_VALUE + "2" + CR + "AnotherProperty = value1;value2;value3" + CR);
632     }
633 
634     /**
635      * Tests setting the global separator. This separator should override the separators for all properties.
636      */
637     @Test
638     void testSetGlobalSeparator() throws ConfigurationException {
639         final String sep = "=";
640         config.addProperty(TEST_KEY, TEST_VALUE);
641         config.addProperty("key2", "value2");
642         layout.setSeparator(TEST_KEY, " : ");
643         layout.setGlobalSeparator(sep);
644         checkLayoutString(TEST_KEY + sep + TEST_VALUE + CR + "key2" + sep + "value2" + CR);
645     }
646 
647     /**
648      * Tests setting the line separator.
649      */
650     @Test
651     void testSetLineSeparator() throws ConfigurationException {
652         final String lf = CR + CR;
653         config.addProperty(TEST_KEY, TEST_VALUE);
654         layout.setBlankLinesBefore(TEST_KEY, 2);
655         layout.setComment(TEST_KEY, TEST_COMMENT);
656         layout.setHeaderComment("Header comment");
657         layout.setLineSeparator(lf);
658         checkLayoutString("# Header comment" + lf + lf + lf + "# " + TEST_COMMENT + lf + TEST_KEY + " = " + TEST_VALUE + lf);
659     }
660 
661     /**
662      * Tests whether the line separator is also taken into account within comments.
663      */
664     @Test
665     void testSetLineSeparatorInComments() throws ConfigurationException {
666         final String lf = "<-\n";
667         config.addProperty(TEST_KEY, TEST_VALUE);
668         layout.setComment(TEST_KEY, TEST_COMMENT + "\nMore comment");
669         layout.setHeaderComment("Header\ncomment");
670         layout.setLineSeparator(lf);
671         checkLayoutString("# Header" + lf + "# comment" + lf + lf + "# " + TEST_COMMENT + lf + "# More comment" + lf + TEST_KEY + " = " + TEST_VALUE + lf);
672     }
673 
674     /**
675      * Tests resetting a comment.
676      */
677     @Test
678     void testSetNullComment() {
679         fillLayout();
680         layout.setComment(TEST_KEY, null);
681         assertNull(layout.getComment(TEST_KEY));
682     }
683 
684     /**
685      * Tests changing the separator for a property.
686      */
687     @Test
688     void testSetSeparator() throws ConfigurationException {
689         config.addProperty(TEST_KEY, TEST_VALUE);
690         layout.setSeparator(TEST_KEY, ":");
691         checkLayoutString(TEST_KEY + ":" + TEST_VALUE + CR);
692     }
693 
694     /**
695      * Tests the trimComment method.
696      */
697     @Test
698     void testTrimComment() {
699         assertEquals("This is a comment" + CR + "that spans multiple" + CR + "lines in a" + CR + " complex way.",
700                 PropertiesConfigurationLayout.trimComment(
701                         "   # This is a comment" + CR + "that spans multiple" + CR + "!lines in a" + CR + " complex way.",
702                         false
703                 ));
704     }
705 
706     /**
707      * Tests enforcing comment characters in a comment.
708      */
709     @Test
710     void testTrimCommentFalse() {
711         assertEquals("# Comment with" + CR + " ! some mixed " + CR + "#comment" + CR + "# lines",
712                 PropertiesConfigurationLayout.trimComment("Comment with" + CR + " ! some mixed " + CR + "#comment" + CR + "lines", true));
713     }
714 
715     /**
716      * Tests trimming a comment with trailing CRs.
717      */
718     @Test
719     void testTrimCommentTrainlingCR() {
720         assertEquals("Comment with" + CR + "trailing CR" + CR,
721                 PropertiesConfigurationLayout.trimComment("Comment with" + CR + "! trailing CR" + CR, false));
722     }
723 }