File: PropertiesStoreTest.java

package info (click to toggle)
openjdk-25 25.0.1%2B8-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 825,408 kB
  • sloc: java: 5,585,680; cpp: 1,333,948; xml: 1,321,242; ansic: 488,034; asm: 404,003; objc: 21,088; sh: 15,106; javascript: 13,265; python: 8,319; makefile: 2,518; perl: 357; awk: 351; pascal: 103; exp: 83; sed: 72; jsp: 24
file content (318 lines) | stat: -rw-r--r-- 13,758 bytes parent folder | download | duplicates (5)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
/*
 * Copyright (c) 2021, 2024, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

import org.testng.Assert;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Writer;
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.TreeSet;
import java.util.stream.Collectors;

/*
 * @test
 * @summary tests the order in which the Properties.store() method writes out the properties
 * @bug 8231640 8282023
 * @run testng/othervm PropertiesStoreTest
 */
public class PropertiesStoreTest {

    private static final String DATE_FORMAT_PATTERN = "EEE MMM dd HH:mm:ss zzz uuuu";
    // use Locale.US, since when the date comment was written by Properties.store(...),
    // it internally calls the Date.toString() which uses Locale.US for time zone names
    private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern(DATE_FORMAT_PATTERN, Locale.US);
    private static final Locale PREV_LOCALE = Locale.getDefault();

    @DataProvider(name = "propsProvider")
    private Object[][] createProps() {
        final Properties simple = new Properties();
        simple.setProperty("1", "one");
        simple.setProperty("2", "two");
        simple.setProperty("10", "ten");
        simple.setProperty("02", "zero-two");
        simple.setProperty("3", "three");
        simple.setProperty("0", "zero");
        simple.setProperty("00", "zero-zero");
        simple.setProperty("0", "zero-again");

        final Properties specialChars = new Properties();
        // some special chars
        simple.setProperty(" 1", "space-one");
        simple.setProperty("\t 3 7 \n", "tab-space-three-space-seven-space-newline");
        // add some simple chars
        simple.setProperty("3", "three");
        simple.setProperty("0", "zero");

        final Properties overrideCallsSuper = new OverridesEntrySetCallsSuper();
        overrideCallsSuper.putAll(simple);

        final OverridesEntrySet overridesEntrySet = new OverridesEntrySet();
        overridesEntrySet.putAll(simple);

        final Properties doesNotOverrideEntrySet = new DoesNotOverrideEntrySet();
        doesNotOverrideEntrySet.putAll(simple);

        return new Object[][]{
                {simple, naturalOrder(simple)},
                {specialChars, naturalOrder(specialChars)},
                {overrideCallsSuper, naturalOrder(overrideCallsSuper)},
                {overridesEntrySet, overridesEntrySet.expectedKeyOrder()},
                {doesNotOverrideEntrySet, naturalOrder(doesNotOverrideEntrySet)}
        };
    }

    /**
     * Returns a {@link Locale} to use for testing
     */
    @DataProvider(name = "localeProvider")
    private Object[][] provideLocales() {
        // pick a non-english locale for testing
        Set<Locale> locales = Arrays.stream(Locale.getAvailableLocales())
                .filter(l -> !l.getLanguage().isEmpty() && !l.getLanguage().equals("en"))
                .limit(1)
                .collect(Collectors.toCollection(HashSet::new));
        locales.add(Locale.getDefault()); // always test the default locale
        locales.add(Locale.US); // guaranteed to be present
        locales.add(Locale.ROOT); // guaranteed to be present

        // return the chosen locales
        return locales.stream()
                .map(m -> new Locale[] {m})
                .toArray(n -> new Object[n][0]);
    }

    /**
     * Tests that the {@link Properties#store(Writer, String)} API writes out the properties
     * in the expected order
     */
    @Test(dataProvider = "propsProvider")
    public void testStoreWriterKeyOrder(final Properties props, final String[] expectedOrder) throws Exception {
        // Properties.store(...) to a temp file
        final Path tmpFile = Files.createTempFile("8231640", "props");
        try (final Writer writer = Files.newBufferedWriter(tmpFile)) {
            props.store(writer, null);
        }
        testStoreKeyOrder(props, tmpFile, expectedOrder);
    }

    /**
     * Tests that the {@link Properties#store(OutputStream, String)} API writes out the properties
     * in the expected order
     */
    @Test(dataProvider = "propsProvider")
    public void testStoreOutputStreamKeyOrder(final Properties props, final String[] expectedOrder) throws Exception {
        // Properties.store(...) to a temp file
        final Path tmpFile = Files.createTempFile("8231640", "props");
        try (final OutputStream os = Files.newOutputStream(tmpFile)) {
            props.store(os, null);
        }
        testStoreKeyOrder(props, tmpFile, expectedOrder);
    }

    /**
     * {@link Properties#load(InputStream) Loads a Properties instance} from the passed
     * {@code Path} and then verifies that:
     * - the loaded properties instance "equals" the passed (original) "props" instance
     * - the order in which the properties appear in the file represented by the path
     * is the same as the passed "expectedOrder"
     */
    private void testStoreKeyOrder(final Properties props, final Path storedProps,
                                   final String[] expectedOrder) throws Exception {
        // Properties.load(...) from that stored file and verify that the loaded
        // Properties has expected content
        final Properties loaded = new Properties();
        try (final InputStream is = Files.newInputStream(storedProps)) {
            loaded.load(is);
        }
        Assert.assertEquals(loaded, props, "Unexpected properties loaded from stored state");

        // now read lines from the stored file and keep track of the order in which the keys were
        // found in that file. Compare that order with the expected store order of the keys.
        final List<String> actualOrder;
        try (final BufferedReader reader = Files.newBufferedReader(storedProps)) {
            actualOrder = readInOrder(reader);
        }
        Assert.assertEquals(actualOrder.size(), expectedOrder.length,
                "Unexpected number of keys read from stored properties");
        if (!Arrays.equals(actualOrder.toArray(new String[0]), expectedOrder)) {
            Assert.fail("Unexpected order of stored property keys. Expected order: " + Arrays.toString(expectedOrder)
                    + ", found order: " + actualOrder);
        }
    }

    /**
     * Tests that {@link Properties#store(Writer, String)} writes out a proper date comment
     */
    @Test(dataProvider = "localeProvider")
    public void testStoreWriterDateComment(final Locale testLocale) throws Exception {
        // switch the default locale to the one being tested
        Locale.setDefault(testLocale);
        System.out.println("Using locale: " + testLocale + " for Properties#store(Writer) test");
        try {
            final Properties props = new Properties();
            props.setProperty("a", "b");
            final Path tmpFile = Files.createTempFile("8231640", "props");
            try (final Writer writer = Files.newBufferedWriter(tmpFile)) {
                props.store(writer, null);
            }
            testDateComment(tmpFile);
        } finally {
            // reset to the previous one
            Locale.setDefault(PREV_LOCALE);
        }
    }

    /**
     * Tests that {@link Properties#store(OutputStream, String)} writes out a proper date comment
     */
    @Test(dataProvider = "localeProvider")
    public void testStoreOutputStreamDateComment(final Locale testLocale) throws Exception {
        // switch the default locale to the one being tested
        Locale.setDefault(testLocale);
        System.out.println("Using locale: " + testLocale + " for Properties#store(OutputStream) test");
        try {
            final Properties props = new Properties();
            props.setProperty("a", "b");
            final Path tmpFile = Files.createTempFile("8231640", "props");
            try (final Writer writer = Files.newBufferedWriter(tmpFile)) {
                props.store(writer, null);
            }
            testDateComment(tmpFile);
        } finally {
            // reset to the previous one
            Locale.setDefault(PREV_LOCALE);
        }
    }

    /**
     * Reads each line in the {@code file} and verifies that there is only one comment line
     * and that comment line can be parsed into a {@link java.util.Date}
     */
    private void testDateComment(Path file) throws Exception {
        String comment = null;
        try (final BufferedReader reader = Files.newBufferedReader(file)) {
            String line = null;
            while ((line = reader.readLine()) != null) {
                if (line.startsWith("#")) {
                    if (comment != null) {
                        Assert.fail("More than one comment line found in the stored properties file " + file);
                    }
                    comment = line.substring(1);
                }
            }
        }
        if (comment == null) {
            Assert.fail("No comment line found in the stored properties file " + file);
        }
        try {
            FORMATTER.parse(comment);
        } catch (DateTimeParseException pe) {
            Assert.fail("Unexpected date comment: " + comment, pe);
        }
    }

    // returns the property keys in their natural order
    private static String[] naturalOrder(final Properties props) {
        return new TreeSet<>(props.stringPropertyNames()).toArray(new String[0]);
    }

    // reads each non-comment line and keeps track of the order in which the property key lines
    // were read
    private static List<String> readInOrder(final BufferedReader reader) throws IOException {
        final List<String> readKeys = new ArrayList<>();
        String line;
        while ((line = reader.readLine()) != null) {
            if (line.startsWith("#")) {
                continue;
            }
            final String key = line.substring(0, line.indexOf("="));
            // the Properties.store(...) APIs write out the keys in a specific format for certain
            // special characters. Our test uses some of the keys which have those special characters.
            // Here we handle such special character conversion (for only those characters that this test uses).
            // replace the backslash character followed by the t character with the tab character
            String replacedKey = key.replace("\\t", "\t");
            // replace the backslash character followed by the n character with the newline character
            replacedKey = replacedKey.replace("\\n", "\n");
            // replace backslash character followed by the space character with the space character
            replacedKey = replacedKey.replace("\\ ", " ");
            readKeys.add(replacedKey);
        }
        return readKeys;
    }

    // Extends java.util.Properties and overrides entrySet() to return a reverse
    // sorted entries set
    private static class OverridesEntrySet extends Properties {
        @Override
        @SuppressWarnings("unchecked")
        public Set<Map.Entry<Object, Object>> entrySet() {
            // return a reverse sorted entries set
            var entries = super.entrySet();
            Comparator<Map.Entry<String, String>> comparator = Map.Entry.comparingByKey(Comparator.reverseOrder());
            TreeSet<Map.Entry<String, String>> reverseSorted = new TreeSet<>(comparator);
            reverseSorted.addAll((Set) entries);
            return (Set) reverseSorted;
        }

        String[] expectedKeyOrder() {
            // returns in reverse order of the property keys' natural ordering
            var keys = new ArrayList<>(stringPropertyNames());
            keys.sort(Comparator.reverseOrder());
            return keys.toArray(new String[0]);
        }
    }

    // Extends java.util.Properties and overrides entrySet() to just return "super.entrySet()"
    private static class OverridesEntrySetCallsSuper extends Properties {
        @Override
        public Set<Map.Entry<Object, Object>> entrySet() {
            return super.entrySet();
        }
    }

    // Extends java.util.Properties but doesn't override entrySet() method
    private static class DoesNotOverrideEntrySet extends Properties {

        @Override
        public String toString() {
            return "DoesNotOverrideEntrySet - " + super.toString();
        }
    }
}