1// Copyright 2018 Frank Schroeder. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5package properties
6
7import (
8	"bytes"
9	"flag"
10	"fmt"
11	"math"
12	"os"
13	"reflect"
14	"regexp"
15	"runtime"
16	"strconv"
17	"strings"
18	"testing"
19	"time"
20
21	"github.com/magiconair/properties/assert"
22)
23
24var verbose = flag.Bool("verbose", false, "Verbose output")
25
26func init() {
27	ErrorHandler = PanicHandler
28}
29
30// ----------------------------------------------------------------------------
31
32// define test cases in the form of
33// {"input", "key1", "value1", "key2", "value2", ...}
34var complexTests = [][]string{
35	// whitespace prefix
36	{" key=value", "key", "value"},     // SPACE prefix
37	{"\fkey=value", "key", "value"},    // FF prefix
38	{"\tkey=value", "key", "value"},    // TAB prefix
39	{" \f\tkey=value", "key", "value"}, // mix prefix
40
41	// multiple keys
42	{"key1=value1\nkey2=value2\n", "key1", "value1", "key2", "value2"},
43	{"key1=value1\rkey2=value2\r", "key1", "value1", "key2", "value2"},
44	{"key1=value1\r\nkey2=value2\r\n", "key1", "value1", "key2", "value2"},
45
46	// blank lines
47	{"\nkey=value\n", "key", "value"},
48	{"\rkey=value\r", "key", "value"},
49	{"\r\nkey=value\r\n", "key", "value"},
50	{"\nkey=value\n \nkey2=value2", "key", "value", "key2", "value2"},
51	{"\nkey=value\n\t\nkey2=value2", "key", "value", "key2", "value2"},
52
53	// escaped chars in key
54	{"k\\ ey = value", "k ey", "value"},
55	{"k\\:ey = value", "k:ey", "value"},
56	{"k\\=ey = value", "k=ey", "value"},
57	{"k\\fey = value", "k\fey", "value"},
58	{"k\\ney = value", "k\ney", "value"},
59	{"k\\rey = value", "k\rey", "value"},
60	{"k\\tey = value", "k\tey", "value"},
61
62	// escaped chars in value
63	{"key = v\\ alue", "key", "v alue"},
64	{"key = v\\:alue", "key", "v:alue"},
65	{"key = v\\=alue", "key", "v=alue"},
66	{"key = v\\falue", "key", "v\falue"},
67	{"key = v\\nalue", "key", "v\nalue"},
68	{"key = v\\ralue", "key", "v\ralue"},
69	{"key = v\\talue", "key", "v\talue"},
70
71	// silently dropped escape character
72	{"k\\zey = value", "kzey", "value"},
73	{"key = v\\zalue", "key", "vzalue"},
74
75	// unicode literals
76	{"key\\u2318 = value", "key⌘", "value"},
77	{"k\\u2318ey = value", "k⌘ey", "value"},
78	{"key = value\\u2318", "key", "value⌘"},
79	{"key = valu\\u2318e", "key", "valu⌘e"},
80
81	// multiline values
82	{"key = valueA,\\\n    valueB", "key", "valueA,valueB"},   // SPACE indent
83	{"key = valueA,\\\n\f\f\fvalueB", "key", "valueA,valueB"}, // FF indent
84	{"key = valueA,\\\n\t\t\tvalueB", "key", "valueA,valueB"}, // TAB indent
85	{"key = valueA,\\\n \f\tvalueB", "key", "valueA,valueB"},  // mix indent
86
87	// comments
88	{"# this is a comment\n! and so is this\nkey1=value1\nkey#2=value#2\n\nkey!3=value!3\n# and another one\n! and the final one", "key1", "value1", "key#2", "value#2", "key!3", "value!3"},
89
90	// expansion tests
91	{"key=value\nkey2=${key}", "key", "value", "key2", "value"},
92	{"key=value\nkey2=aa${key}", "key", "value", "key2", "aavalue"},
93	{"key=value\nkey2=${key}bb", "key", "value", "key2", "valuebb"},
94	{"key=value\nkey2=aa${key}bb", "key", "value", "key2", "aavaluebb"},
95	{"key=value\nkey2=${key}\nkey3=${key2}", "key", "value", "key2", "value", "key3", "value"},
96	{"key=value\nkey2=${key}${key}", "key", "value", "key2", "valuevalue"},
97	{"key=value\nkey2=${key}${key}${key}${key}", "key", "value", "key2", "valuevaluevaluevalue"},
98	{"key=value\nkey2=${key}${key3}\nkey3=${key}", "key", "value", "key2", "valuevalue", "key3", "value"},
99	{"key=value\nkey2=${key3}${key}${key4}\nkey3=${key}\nkey4=${key}", "key", "value", "key2", "valuevaluevalue", "key3", "value", "key4", "value"},
100	{"key=${USER}", "key", os.Getenv("USER")},
101	{"key=${USER}\nUSER=value", "key", "value", "USER", "value"},
102}
103
104// ----------------------------------------------------------------------------
105
106var commentTests = []struct {
107	input, key, value string
108	comments          []string
109}{
110	{"key=value", "key", "value", nil},
111	{"#\nkey=value", "key", "value", []string{""}},
112	{"#comment\nkey=value", "key", "value", []string{"comment"}},
113	{"# comment\nkey=value", "key", "value", []string{"comment"}},
114	{"#  comment\nkey=value", "key", "value", []string{"comment"}},
115	{"# comment\n\nkey=value", "key", "value", []string{"comment"}},
116	{"# comment1\n# comment2\nkey=value", "key", "value", []string{"comment1", "comment2"}},
117	{"# comment1\n\n# comment2\n\nkey=value", "key", "value", []string{"comment1", "comment2"}},
118	{"!comment\nkey=value", "key", "value", []string{"comment"}},
119	{"! comment\nkey=value", "key", "value", []string{"comment"}},
120	{"!  comment\nkey=value", "key", "value", []string{"comment"}},
121	{"! comment\n\nkey=value", "key", "value", []string{"comment"}},
122	{"! comment1\n! comment2\nkey=value", "key", "value", []string{"comment1", "comment2"}},
123	{"! comment1\n\n! comment2\n\nkey=value", "key", "value", []string{"comment1", "comment2"}},
124}
125
126// ----------------------------------------------------------------------------
127
128var errorTests = []struct {
129	input, msg string
130}{
131	// unicode literals
132	{"key\\u1 = value", "invalid unicode literal"},
133	{"key\\u12 = value", "invalid unicode literal"},
134	{"key\\u123 = value", "invalid unicode literal"},
135	{"key\\u123g = value", "invalid unicode literal"},
136	{"key\\u123", "invalid unicode literal"},
137
138	// circular references
139	{"key=${key}", `circular reference in:\nkey=\$\{key\}`},
140	{"key1=${key2}\nkey2=${key1}", `circular reference in:\n(key1=\$\{key2\}\nkey2=\$\{key1\}|key2=\$\{key1\}\nkey1=\$\{key2\})`},
141
142	// malformed expressions
143	{"key=${ke", "malformed expression"},
144	{"key=valu${ke", "malformed expression"},
145}
146
147// ----------------------------------------------------------------------------
148
149var writeTests = []struct {
150	input, output, encoding string
151}{
152	// ISO-8859-1 tests
153	{"key = value", "key = value\n", "ISO-8859-1"},
154	{"key = value \\\n   continued", "key = value continued\n", "ISO-8859-1"},
155	{"key⌘ = value", "key\\u2318 = value\n", "ISO-8859-1"},
156	{"ke\\ \\:y = value", "ke\\ \\:y = value\n", "ISO-8859-1"},
157	{"ke\\\\y = val\\\\ue", "ke\\\\y = val\\\\ue\n", "ISO-8859-1"},
158
159	// UTF-8 tests
160	{"key = value", "key = value\n", "UTF-8"},
161	{"key = value \\\n   continued", "key = value continued\n", "UTF-8"},
162	{"key⌘ = value⌘", "key⌘ = value⌘\n", "UTF-8"},
163	{"ke\\ \\:y = value", "ke\\ \\:y = value\n", "UTF-8"},
164	{"ke\\\\y = val\\\\ue", "ke\\\\y = val\\\\ue\n", "UTF-8"},
165}
166
167// ----------------------------------------------------------------------------
168
169var writeCommentTests = []struct {
170	input, output, encoding string
171}{
172	// ISO-8859-1 tests
173	{"key = value", "key = value\n", "ISO-8859-1"},
174	{"#\nkey = value", "key = value\n", "ISO-8859-1"},
175	{"#\n#\n#\nkey = value", "key = value\n", "ISO-8859-1"},
176	{"# comment\nkey = value", "# comment\nkey = value\n", "ISO-8859-1"},
177	{"\n# comment\nkey = value", "# comment\nkey = value\n", "ISO-8859-1"},
178	{"# comment\n\nkey = value", "# comment\nkey = value\n", "ISO-8859-1"},
179	{"# comment1\n# comment2\nkey = value", "# comment1\n# comment2\nkey = value\n", "ISO-8859-1"},
180	{"#comment1\nkey1 = value1\n#comment2\nkey2 = value2", "# comment1\nkey1 = value1\n\n# comment2\nkey2 = value2\n", "ISO-8859-1"},
181	// prevent double encoding \\ -> \\\\ -> \\\\\\\\
182	{"# com\\\\ment\nkey = value", "# com\\\\ment\nkey = value\n", "ISO-8859-1"},
183
184	// UTF-8 tests
185	{"key = value", "key = value\n", "UTF-8"},
186	{"# comment⌘\nkey = value⌘", "# comment⌘\nkey = value⌘\n", "UTF-8"},
187	{"\n# comment⌘\nkey = value⌘", "# comment⌘\nkey = value⌘\n", "UTF-8"},
188	{"# comment⌘\n\nkey = value⌘", "# comment⌘\nkey = value⌘\n", "UTF-8"},
189	{"# comment1⌘\n# comment2⌘\nkey = value⌘", "# comment1⌘\n# comment2⌘\nkey = value⌘\n", "UTF-8"},
190	{"#comment1⌘\nkey1 = value1⌘\n#comment2⌘\nkey2 = value2⌘", "# comment1⌘\nkey1 = value1⌘\n\n# comment2⌘\nkey2 = value2⌘\n", "UTF-8"},
191	// prevent double encoding \\ -> \\\\ -> \\\\\\\\
192	{"# com\\\\ment⌘\nkey = value⌘", "# com\\\\ment⌘\nkey = value⌘\n", "UTF-8"},
193}
194
195// ----------------------------------------------------------------------------
196
197var boolTests = []struct {
198	input, key string
199	def, value bool
200}{
201	// valid values for TRUE
202	{"key = 1", "key", false, true},
203	{"key = on", "key", false, true},
204	{"key = On", "key", false, true},
205	{"key = ON", "key", false, true},
206	{"key = true", "key", false, true},
207	{"key = True", "key", false, true},
208	{"key = TRUE", "key", false, true},
209	{"key = yes", "key", false, true},
210	{"key = Yes", "key", false, true},
211	{"key = YES", "key", false, true},
212
213	// valid values for FALSE (all other)
214	{"key = 0", "key", true, false},
215	{"key = off", "key", true, false},
216	{"key = false", "key", true, false},
217	{"key = no", "key", true, false},
218
219	// non existent key
220	{"key = true", "key2", false, false},
221}
222
223// ----------------------------------------------------------------------------
224
225var durationTests = []struct {
226	input, key string
227	def, value time.Duration
228}{
229	// valid values
230	{"key = 1", "key", 999, 1},
231	{"key = 0", "key", 999, 0},
232	{"key = -1", "key", 999, -1},
233	{"key = 0123", "key", 999, 123},
234
235	// invalid values
236	{"key = 0xff", "key", 999, 999},
237	{"key = 1.0", "key", 999, 999},
238	{"key = a", "key", 999, 999},
239
240	// non existent key
241	{"key = 1", "key2", 999, 999},
242}
243
244// ----------------------------------------------------------------------------
245
246var parsedDurationTests = []struct {
247	input, key string
248	def, value time.Duration
249}{
250	// valid values
251	{"key = -1ns", "key", 999, -1 * time.Nanosecond},
252	{"key = 300ms", "key", 999, 300 * time.Millisecond},
253	{"key = 5s", "key", 999, 5 * time.Second},
254	{"key = 3h", "key", 999, 3 * time.Hour},
255	{"key = 2h45m", "key", 999, 2*time.Hour + 45*time.Minute},
256
257	// invalid values
258	{"key = 0xff", "key", 999, 999},
259	{"key = 1.0", "key", 999, 999},
260	{"key = a", "key", 999, 999},
261	{"key = 1", "key", 999, 999},
262	{"key = 0", "key", 999, 0},
263
264	// non existent key
265	{"key = 1", "key2", 999, 999},
266}
267
268// ----------------------------------------------------------------------------
269
270var floatTests = []struct {
271	input, key string
272	def, value float64
273}{
274	// valid values
275	{"key = 1.0", "key", 999, 1.0},
276	{"key = 0.0", "key", 999, 0.0},
277	{"key = -1.0", "key", 999, -1.0},
278	{"key = 1", "key", 999, 1},
279	{"key = 0", "key", 999, 0},
280	{"key = -1", "key", 999, -1},
281	{"key = 0123", "key", 999, 123},
282
283	// invalid values
284	{"key = 0xff", "key", 999, 999},
285	{"key = a", "key", 999, 999},
286
287	// non existent key
288	{"key = 1", "key2", 999, 999},
289}
290
291// ----------------------------------------------------------------------------
292
293var int64Tests = []struct {
294	input, key string
295	def, value int64
296}{
297	// valid values
298	{"key = 1", "key", 999, 1},
299	{"key = 0", "key", 999, 0},
300	{"key = -1", "key", 999, -1},
301	{"key = 0123", "key", 999, 123},
302
303	// invalid values
304	{"key = 0xff", "key", 999, 999},
305	{"key = 1.0", "key", 999, 999},
306	{"key = a", "key", 999, 999},
307
308	// non existent key
309	{"key = 1", "key2", 999, 999},
310}
311
312// ----------------------------------------------------------------------------
313
314var uint64Tests = []struct {
315	input, key string
316	def, value uint64
317}{
318	// valid values
319	{"key = 1", "key", 999, 1},
320	{"key = 0", "key", 999, 0},
321	{"key = 0123", "key", 999, 123},
322
323	// invalid values
324	{"key = -1", "key", 999, 999},
325	{"key = 0xff", "key", 999, 999},
326	{"key = 1.0", "key", 999, 999},
327	{"key = a", "key", 999, 999},
328
329	// non existent key
330	{"key = 1", "key2", 999, 999},
331}
332
333// ----------------------------------------------------------------------------
334
335var stringTests = []struct {
336	input, key string
337	def, value string
338}{
339	// valid values
340	{"key = abc", "key", "def", "abc"},
341	{"key = ab\\\\c", "key", "def", "ab\\c"},
342
343	// non existent key
344	{"key = abc", "key2", "def", "def"},
345}
346
347// ----------------------------------------------------------------------------
348
349var keysTests = []struct {
350	input string
351	keys  []string
352}{
353	{"", []string{}},
354	{"key = abc", []string{"key"}},
355	{"key = abc\nkey2=def", []string{"key", "key2"}},
356	{"key2 = abc\nkey=def", []string{"key2", "key"}},
357	{"key = abc\nkey=def", []string{"key"}},
358	{"key\\\\with\\\\backslashes = abc", []string{"key\\with\\backslashes"}},
359}
360
361// ----------------------------------------------------------------------------
362
363var filterTests = []struct {
364	input   string
365	pattern string
366	keys    []string
367	err     string
368}{
369	{"", "", []string{}, ""},
370	{"", "abc", []string{}, ""},
371	{"key=value", "", []string{"key"}, ""},
372	{"key=value", "key=", []string{}, ""},
373	{"key=value\nfoo=bar", "", []string{"foo", "key"}, ""},
374	{"key=value\nfoo=bar", "f", []string{"foo"}, ""},
375	{"key=value\nfoo=bar", "fo", []string{"foo"}, ""},
376	{"key=value\nfoo=bar", "foo", []string{"foo"}, ""},
377	{"key=value\nfoo=bar", "fooo", []string{}, ""},
378	{"key=value\nkey2=value2\nfoo=bar", "ey", []string{"key", "key2"}, ""},
379	{"key=value\nkey2=value2\nfoo=bar", "key", []string{"key", "key2"}, ""},
380	{"key=value\nkey2=value2\nfoo=bar", "^key", []string{"key", "key2"}, ""},
381	{"key=value\nkey2=value2\nfoo=bar", "^(key|foo)", []string{"foo", "key", "key2"}, ""},
382	{"key=value\nkey2=value2\nfoo=bar", "[ abc", nil, "error parsing regexp.*"},
383}
384
385// ----------------------------------------------------------------------------
386
387var filterPrefixTests = []struct {
388	input  string
389	prefix string
390	keys   []string
391}{
392	{"", "", []string{}},
393	{"", "abc", []string{}},
394	{"key=value", "", []string{"key"}},
395	{"key=value", "key=", []string{}},
396	{"key=value\nfoo=bar", "", []string{"foo", "key"}},
397	{"key=value\nfoo=bar", "f", []string{"foo"}},
398	{"key=value\nfoo=bar", "fo", []string{"foo"}},
399	{"key=value\nfoo=bar", "foo", []string{"foo"}},
400	{"key=value\nfoo=bar", "fooo", []string{}},
401	{"key=value\nkey2=value2\nfoo=bar", "key", []string{"key", "key2"}},
402}
403
404// ----------------------------------------------------------------------------
405
406var filterStripPrefixTests = []struct {
407	input  string
408	prefix string
409	keys   []string
410}{
411	{"", "", []string{}},
412	{"", "abc", []string{}},
413	{"key=value", "", []string{"key"}},
414	{"key=value", "key=", []string{}},
415	{"key=value\nfoo=bar", "", []string{"foo", "key"}},
416	{"key=value\nfoo=bar", "f", []string{"foo"}},
417	{"key=value\nfoo=bar", "fo", []string{"foo"}},
418	{"key=value\nfoo=bar", "foo", []string{"foo"}},
419	{"key=value\nfoo=bar", "fooo", []string{}},
420	{"key=value\nkey2=value2\nfoo=bar", "key", []string{"key", "key2"}},
421}
422
423// ----------------------------------------------------------------------------
424
425var setTests = []struct {
426	input      string
427	key, value string
428	prev       string
429	ok         bool
430	err        string
431	keys       []string
432}{
433	{"", "", "", "", false, "", []string{}},
434	{"", "key", "value", "", false, "", []string{"key"}},
435	{"key=value", "key2", "value2", "", false, "", []string{"key", "key2"}},
436	{"key=value", "abc", "value3", "", false, "", []string{"key", "abc"}},
437	{"key=value", "key", "value3", "value", true, "", []string{"key"}},
438}
439
440// ----------------------------------------------------------------------------
441
442// TestBasic tests basic single key/value combinations with all possible
443// whitespace, delimiter and newline permutations.
444func TestBasic(t *testing.T) {
445	testWhitespaceAndDelimiterCombinations(t, "key", "")
446	testWhitespaceAndDelimiterCombinations(t, "key", "value")
447	testWhitespaceAndDelimiterCombinations(t, "key", "value   ")
448}
449
450func TestComplex(t *testing.T) {
451	for _, test := range complexTests {
452		testKeyValue(t, test[0], test[1:]...)
453	}
454}
455
456func TestErrors(t *testing.T) {
457	for _, test := range errorTests {
458		_, err := Load([]byte(test.input), ISO_8859_1)
459		assert.Equal(t, err != nil, true, fmt.Sprintf("want error: %s", test.input))
460		re := regexp.MustCompile(test.msg)
461		assert.Equal(t, re.MatchString(err.Error()), true, fmt.Sprintf("expected %s, got %s", test.msg, err.Error()))
462	}
463}
464
465func TestVeryDeep(t *testing.T) {
466	input := "key0=value\n"
467	prefix := "${"
468	postfix := "}"
469	i := 0
470	for i = 0; i < maxExpansionDepth-1; i++ {
471		input += fmt.Sprintf("key%d=%skey%d%s\n", i+1, prefix, i, postfix)
472	}
473
474	p, err := Load([]byte(input), ISO_8859_1)
475	assert.Equal(t, err, nil)
476	p.Prefix = prefix
477	p.Postfix = postfix
478
479	assert.Equal(t, p.MustGet(fmt.Sprintf("key%d", i)), "value")
480
481	// Nudge input over the edge
482	input += fmt.Sprintf("key%d=%skey%d%s\n", i+1, prefix, i, postfix)
483
484	_, err = Load([]byte(input), ISO_8859_1)
485	assert.Equal(t, err != nil, true, "want error")
486	assert.Equal(t, strings.Contains(err.Error(), "expansion too deep"), true)
487}
488
489func TestDisableExpansion(t *testing.T) {
490	input := "key=value\nkey2=${key}"
491	p := mustParse(t, input)
492	p.DisableExpansion = true
493	assert.Equal(t, p.MustGet("key"), "value")
494	assert.Equal(t, p.MustGet("key2"), "${key}")
495
496	// with expansion disabled we can introduce circular references
497	p.MustSet("keyA", "${keyB}")
498	p.MustSet("keyB", "${keyA}")
499	assert.Equal(t, p.MustGet("keyA"), "${keyB}")
500	assert.Equal(t, p.MustGet("keyB"), "${keyA}")
501}
502
503func TestDisableExpansionStillUpdatesKeys(t *testing.T) {
504	p := NewProperties()
505	p.MustSet("p1", "a")
506	assert.Equal(t, p.Keys(), []string{"p1"})
507	assert.Equal(t, p.String(), "p1 = a\n")
508
509	p.DisableExpansion = true
510	p.MustSet("p2", "b")
511
512	assert.Equal(t, p.Keys(), []string{"p1", "p2"})
513	assert.Equal(t, p.String(), "p1 = a\np2 = b\n")
514}
515
516func TestMustGet(t *testing.T) {
517	input := "key = value\nkey2 = ghi"
518	p := mustParse(t, input)
519	assert.Equal(t, p.MustGet("key"), "value")
520	assert.Panic(t, func() { p.MustGet("invalid") }, "unknown property: invalid")
521}
522
523func TestGetBool(t *testing.T) {
524	for _, test := range boolTests {
525		p := mustParse(t, test.input)
526		assert.Equal(t, p.Len(), 1)
527		assert.Equal(t, p.GetBool(test.key, test.def), test.value)
528	}
529}
530
531func TestMustGetBool(t *testing.T) {
532	input := "key = true\nkey2 = ghi"
533	p := mustParse(t, input)
534	assert.Equal(t, p.MustGetBool("key"), true)
535	assert.Panic(t, func() { p.MustGetBool("invalid") }, "unknown property: invalid")
536}
537
538func TestGetDuration(t *testing.T) {
539	for _, test := range durationTests {
540		p := mustParse(t, test.input)
541		assert.Equal(t, p.Len(), 1)
542		assert.Equal(t, p.GetDuration(test.key, test.def), test.value)
543	}
544}
545
546func TestMustGetDuration(t *testing.T) {
547	input := "key = 123\nkey2 = ghi"
548	p := mustParse(t, input)
549	assert.Equal(t, p.MustGetDuration("key"), time.Duration(123))
550	assert.Panic(t, func() { p.MustGetDuration("key2") }, "strconv.ParseInt: parsing.*")
551	assert.Panic(t, func() { p.MustGetDuration("invalid") }, "unknown property: invalid")
552}
553
554func TestGetParsedDuration(t *testing.T) {
555	for _, test := range parsedDurationTests {
556		p := mustParse(t, test.input)
557		assert.Equal(t, p.Len(), 1)
558		assert.Equal(t, p.GetParsedDuration(test.key, test.def), test.value)
559	}
560}
561
562func TestMustGetParsedDuration(t *testing.T) {
563	input := "key = 123ms\nkey2 = ghi"
564	p := mustParse(t, input)
565	assert.Equal(t, p.MustGetParsedDuration("key"), 123*time.Millisecond)
566
567	// parse runtime.Version into major and minor version
568	var major, minor int
569	ver := strings.Split(runtime.Version(), ".")
570	devel := !strings.HasPrefix(ver[0], "go")
571	major, _ = strconv.Atoi(strings.TrimPrefix(ver[0], "go"))
572	if len(ver) > 1 {
573		minor, _ = strconv.Atoi(ver[1])
574	}
575
576	switch {
577	case devel || major == 1 && minor >= 15:
578		// go1.15 ... gotip
579		assert.Panic(t, func() { p.MustGetParsedDuration("key2") }, `time: invalid duration "ghi"`)
580	default:
581		// go1.x..go1.14
582		assert.Panic(t, func() { p.MustGetParsedDuration("key2") }, `time: invalid duration ghi`)
583	}
584	assert.Panic(t, func() { p.MustGetParsedDuration("invalid") }, "unknown property: invalid")
585}
586
587func TestGetFloat64(t *testing.T) {
588	for _, test := range floatTests {
589		p := mustParse(t, test.input)
590		assert.Equal(t, p.Len(), 1)
591		assert.Equal(t, p.GetFloat64(test.key, test.def), test.value)
592	}
593}
594
595func TestMustGetFloat64(t *testing.T) {
596	input := "key = 123\nkey2 = ghi"
597	p := mustParse(t, input)
598	assert.Equal(t, p.MustGetFloat64("key"), float64(123))
599	assert.Panic(t, func() { p.MustGetFloat64("key2") }, "strconv.ParseFloat: parsing.*")
600	assert.Panic(t, func() { p.MustGetFloat64("invalid") }, "unknown property: invalid")
601}
602
603func TestGetInt(t *testing.T) {
604	for _, test := range int64Tests {
605		p := mustParse(t, test.input)
606		assert.Equal(t, p.Len(), 1)
607		assert.Equal(t, p.GetInt(test.key, int(test.def)), int(test.value))
608	}
609}
610
611func TestMustGetInt(t *testing.T) {
612	input := "key = 123\nkey2 = ghi"
613	p := mustParse(t, input)
614	assert.Equal(t, p.MustGetInt("key"), int(123))
615	assert.Panic(t, func() { p.MustGetInt("key2") }, "strconv.ParseInt: parsing.*")
616	assert.Panic(t, func() { p.MustGetInt("invalid") }, "unknown property: invalid")
617}
618
619func TestGetInt64(t *testing.T) {
620	for _, test := range int64Tests {
621		p := mustParse(t, test.input)
622		assert.Equal(t, p.Len(), 1)
623		assert.Equal(t, p.GetInt64(test.key, test.def), test.value)
624	}
625}
626
627func TestMustGetInt64(t *testing.T) {
628	input := "key = 123\nkey2 = ghi"
629	p := mustParse(t, input)
630	assert.Equal(t, p.MustGetInt64("key"), int64(123))
631	assert.Panic(t, func() { p.MustGetInt64("key2") }, "strconv.ParseInt: parsing.*")
632	assert.Panic(t, func() { p.MustGetInt64("invalid") }, "unknown property: invalid")
633}
634
635func TestGetUint(t *testing.T) {
636	for _, test := range uint64Tests {
637		p := mustParse(t, test.input)
638		assert.Equal(t, p.Len(), 1)
639		assert.Equal(t, p.GetUint(test.key, uint(test.def)), uint(test.value))
640	}
641}
642
643func TestMustGetUint(t *testing.T) {
644	input := "key = 123\nkey2 = ghi"
645	p := mustParse(t, input)
646	assert.Equal(t, p.MustGetUint("key"), uint(123))
647	assert.Panic(t, func() { p.MustGetUint64("key2") }, "strconv.ParseUint: parsing.*")
648	assert.Panic(t, func() { p.MustGetUint64("invalid") }, "unknown property: invalid")
649}
650
651func TestGetUint64(t *testing.T) {
652	for _, test := range uint64Tests {
653		p := mustParse(t, test.input)
654		assert.Equal(t, p.Len(), 1)
655		assert.Equal(t, p.GetUint64(test.key, test.def), test.value)
656	}
657}
658
659func TestMustGetUint64(t *testing.T) {
660	input := "key = 123\nkey2 = ghi"
661	p := mustParse(t, input)
662	assert.Equal(t, p.MustGetUint64("key"), uint64(123))
663	assert.Panic(t, func() { p.MustGetUint64("key2") }, "strconv.ParseUint: parsing.*")
664	assert.Panic(t, func() { p.MustGetUint64("invalid") }, "unknown property: invalid")
665}
666
667func TestGetString(t *testing.T) {
668	for _, test := range stringTests {
669		p := mustParse(t, test.input)
670		assert.Equal(t, p.Len(), 1)
671		assert.Equal(t, p.GetString(test.key, test.def), test.value)
672	}
673}
674
675func TestMustGetString(t *testing.T) {
676	input := `key = value`
677	p := mustParse(t, input)
678	assert.Equal(t, p.MustGetString("key"), "value")
679	assert.Panic(t, func() { p.MustGetString("invalid") }, "unknown property: invalid")
680}
681
682func TestComment(t *testing.T) {
683	for _, test := range commentTests {
684		p := mustParse(t, test.input)
685		assert.Equal(t, p.MustGetString(test.key), test.value)
686		assert.Equal(t, p.GetComments(test.key), test.comments)
687		if test.comments != nil {
688			assert.Equal(t, p.GetComment(test.key), test.comments[len(test.comments)-1])
689		} else {
690			assert.Equal(t, p.GetComment(test.key), "")
691		}
692
693		// test setting comments
694		if len(test.comments) > 0 {
695			// set single comment
696			p.ClearComments()
697			assert.Equal(t, len(p.c), 0)
698			p.SetComment(test.key, test.comments[0])
699			assert.Equal(t, p.GetComment(test.key), test.comments[0])
700
701			// set multiple comments
702			p.ClearComments()
703			assert.Equal(t, len(p.c), 0)
704			p.SetComments(test.key, test.comments)
705			assert.Equal(t, p.GetComments(test.key), test.comments)
706
707			// clear comments for a key
708			p.SetComments(test.key, nil)
709			assert.Equal(t, p.GetComment(test.key), "")
710			assert.Equal(t, p.GetComments(test.key), ([]string)(nil))
711		}
712	}
713}
714
715func TestFilter(t *testing.T) {
716	for _, test := range filterTests {
717		p := mustParse(t, test.input)
718		pp, err := p.Filter(test.pattern)
719		if err != nil {
720			assert.Matches(t, err.Error(), test.err)
721			continue
722		}
723		assert.Equal(t, pp != nil, true, "want properties")
724		assert.Equal(t, pp.Len(), len(test.keys))
725		for _, key := range test.keys {
726			v1, ok1 := p.Get(key)
727			v2, ok2 := pp.Get(key)
728			assert.Equal(t, ok1, true)
729			assert.Equal(t, ok2, true)
730			assert.Equal(t, v1, v2)
731		}
732	}
733}
734
735func TestFilterPrefix(t *testing.T) {
736	for _, test := range filterPrefixTests {
737		p := mustParse(t, test.input)
738		pp := p.FilterPrefix(test.prefix)
739		assert.Equal(t, pp != nil, true, "want properties")
740		assert.Equal(t, pp.Len(), len(test.keys))
741		for _, key := range test.keys {
742			v1, ok1 := p.Get(key)
743			v2, ok2 := pp.Get(key)
744			assert.Equal(t, ok1, true)
745			assert.Equal(t, ok2, true)
746			assert.Equal(t, v1, v2)
747		}
748	}
749}
750
751func TestFilterStripPrefix(t *testing.T) {
752	for _, test := range filterStripPrefixTests {
753		p := mustParse(t, test.input)
754		pp := p.FilterPrefix(test.prefix)
755		assert.Equal(t, pp != nil, true, "want properties")
756		assert.Equal(t, pp.Len(), len(test.keys))
757		for _, key := range test.keys {
758			v1, ok1 := p.Get(key)
759			v2, ok2 := pp.Get(key)
760			assert.Equal(t, ok1, true)
761			assert.Equal(t, ok2, true)
762			assert.Equal(t, v1, v2)
763		}
764	}
765}
766
767func TestKeys(t *testing.T) {
768	for _, test := range keysTests {
769		p := mustParse(t, test.input)
770		assert.Equal(t, p.Len(), len(test.keys))
771		assert.Equal(t, len(p.Keys()), len(test.keys))
772		assert.Equal(t, p.Keys(), test.keys)
773	}
774}
775
776func TestSet(t *testing.T) {
777	for _, test := range setTests {
778		p := mustParse(t, test.input)
779		prev, ok, err := p.Set(test.key, test.value)
780		if test.err != "" {
781			assert.Matches(t, err.Error(), test.err)
782			continue
783		}
784
785		assert.Equal(t, err, nil)
786		assert.Equal(t, ok, test.ok)
787		if ok {
788			assert.Equal(t, prev, test.prev)
789		}
790		assert.Equal(t, p.Keys(), test.keys)
791	}
792}
793
794func TestSetValue(t *testing.T) {
795	tests := []interface{}{
796		true, false,
797		int8(123), int16(123), int32(123), int64(123), int(123),
798		uint8(123), uint16(123), uint32(123), uint64(123), uint(123),
799		float32(1.23), float64(1.23),
800		"abc",
801	}
802
803	for _, v := range tests {
804		p := NewProperties()
805		err := p.SetValue("x", v)
806		assert.Equal(t, err, nil)
807		assert.Equal(t, p.GetString("x", ""), fmt.Sprintf("%v", v))
808	}
809}
810
811func TestMustSet(t *testing.T) {
812	input := "key=${key}"
813	p := mustParse(t, input)
814	e := `circular reference in:\nkey=\$\{key\}`
815	assert.Panic(t, func() { p.MustSet("key", "${key}") }, e)
816}
817
818func TestWrite(t *testing.T) {
819	for _, test := range writeTests {
820		p, err := parse(test.input)
821
822		buf := new(bytes.Buffer)
823		var n int
824		switch test.encoding {
825		case "UTF-8":
826			n, err = p.Write(buf, UTF8)
827		case "ISO-8859-1":
828			n, err = p.Write(buf, ISO_8859_1)
829		}
830		assert.Equal(t, err, nil)
831		s := string(buf.Bytes())
832		assert.Equal(t, n, len(test.output), fmt.Sprintf("input=%q expected=%q obtained=%q", test.input, test.output, s))
833		assert.Equal(t, s, test.output, fmt.Sprintf("input=%q expected=%q obtained=%q", test.input, test.output, s))
834	}
835}
836
837func TestWriteComment(t *testing.T) {
838	for _, test := range writeCommentTests {
839		p, err := parse(test.input)
840
841		buf := new(bytes.Buffer)
842		var n int
843		switch test.encoding {
844		case "UTF-8":
845			n, err = p.WriteComment(buf, "# ", UTF8)
846		case "ISO-8859-1":
847			n, err = p.WriteComment(buf, "# ", ISO_8859_1)
848		}
849		assert.Equal(t, err, nil)
850		s := string(buf.Bytes())
851		assert.Equal(t, n, len(test.output), fmt.Sprintf("input=%q expected=%q obtained=%q", test.input, test.output, s))
852		assert.Equal(t, s, test.output, fmt.Sprintf("input=%q expected=%q obtained=%q", test.input, test.output, s))
853	}
854}
855
856func TestCustomExpansionExpression(t *testing.T) {
857	testKeyValuePrePostfix(t, "*[", "]*", "key=value\nkey2=*[key]*", "key", "value", "key2", "value")
858}
859
860func TestPanicOn32BitIntOverflow(t *testing.T) {
861	is32Bit = true
862	var min, max int64 = math.MinInt32 - 1, math.MaxInt32 + 1
863	input := fmt.Sprintf("min=%d\nmax=%d", min, max)
864	p := mustParse(t, input)
865	assert.Equal(t, p.MustGetInt64("min"), min)
866	assert.Equal(t, p.MustGetInt64("max"), max)
867	assert.Panic(t, func() { p.MustGetInt("min") }, ".* out of range")
868	assert.Panic(t, func() { p.MustGetInt("max") }, ".* out of range")
869}
870
871func TestPanicOn32BitUintOverflow(t *testing.T) {
872	is32Bit = true
873	var max uint64 = math.MaxUint32 + 1
874	input := fmt.Sprintf("max=%d", max)
875	p := mustParse(t, input)
876	assert.Equal(t, p.MustGetUint64("max"), max)
877	assert.Panic(t, func() { p.MustGetUint("max") }, ".* out of range")
878}
879
880func TestDeleteKey(t *testing.T) {
881	input := "#comments should also be gone\nkey=to-be-deleted\nsecond=key"
882	p := mustParse(t, input)
883	assert.Equal(t, len(p.m), 2)
884	assert.Equal(t, len(p.c), 1)
885	assert.Equal(t, len(p.k), 2)
886	p.Delete("key")
887	assert.Equal(t, len(p.m), 1)
888	assert.Equal(t, len(p.c), 0)
889	assert.Equal(t, len(p.k), 1)
890	assert.Equal(t, p.k[0], "second")
891	assert.Equal(t, p.m["second"], "key")
892}
893
894func TestDeleteUnknownKey(t *testing.T) {
895	input := "#comments should also be gone\nkey=to-be-deleted"
896	p := mustParse(t, input)
897	assert.Equal(t, len(p.m), 1)
898	assert.Equal(t, len(p.c), 1)
899	assert.Equal(t, len(p.k), 1)
900	p.Delete("wrong-key")
901	assert.Equal(t, len(p.m), 1)
902	assert.Equal(t, len(p.c), 1)
903	assert.Equal(t, len(p.k), 1)
904}
905
906func TestMerge(t *testing.T) {
907	input1 := "#comment\nkey=value\nkey2=value2"
908	input2 := "#another comment\nkey=another value\nkey3=value3"
909	p1 := mustParse(t, input1)
910	p2 := mustParse(t, input2)
911	p1.Merge(p2)
912	assert.Equal(t, len(p1.m), 3)
913	assert.Equal(t, len(p1.c), 1)
914	assert.Equal(t, len(p1.k), 3)
915	assert.Equal(t, p1.MustGet("key"), "another value")
916	assert.Equal(t, p1.GetComment("key"), "another comment")
917}
918
919func TestMap(t *testing.T) {
920	input := "key=value\nabc=def"
921	p := mustParse(t, input)
922	m := map[string]string{"key": "value", "abc": "def"}
923	assert.Equal(t, p.Map(), m)
924}
925
926func TestFilterFunc(t *testing.T) {
927	input := "key=value\nabc=def"
928	p := mustParse(t, input)
929	pp := p.FilterFunc(func(k, v string) bool {
930		return k != "abc"
931	})
932	m := map[string]string{"key": "value"}
933	assert.Equal(t, pp.Map(), m)
934}
935
936func TestLoad(t *testing.T) {
937	x := "key=${value}\nvalue=${key}"
938	p := NewProperties()
939	p.DisableExpansion = true
940	err := p.Load([]byte(x), UTF8)
941	assert.Equal(t, err, nil)
942}
943
944// ----------------------------------------------------------------------------
945
946// tests all combinations of delimiters, leading and/or trailing whitespace and newlines.
947func testWhitespaceAndDelimiterCombinations(t *testing.T, key, value string) {
948	whitespace := []string{"", " ", "\f", "\t"}
949	delimiters := []string{"", " ", "=", ":"}
950	newlines := []string{"", "\r", "\n", "\r\n"}
951	for _, dl := range delimiters {
952		for _, ws1 := range whitespace {
953			for _, ws2 := range whitespace {
954				for _, nl := range newlines {
955					// skip the one case where there is nothing between a key and a value
956					if ws1 == "" && dl == "" && ws2 == "" && value != "" {
957						continue
958					}
959
960					input := fmt.Sprintf("%s%s%s%s%s%s", key, ws1, dl, ws2, value, nl)
961					testKeyValue(t, input, key, value)
962				}
963			}
964		}
965	}
966}
967
968// tests whether key/value pairs exist for a given input.
969// keyvalues is expected to be an even number of strings of "key", "value", ...
970func testKeyValue(t *testing.T, input string, keyvalues ...string) {
971	testKeyValuePrePostfix(t, "${", "}", input, keyvalues...)
972}
973
974// tests whether key/value pairs exist for a given input.
975// keyvalues is expected to be an even number of strings of "key", "value", ...
976func testKeyValuePrePostfix(t *testing.T, prefix, postfix, input string, keyvalues ...string) {
977	p, err := Load([]byte(input), ISO_8859_1)
978	assert.Equal(t, err, nil)
979	p.Prefix = prefix
980	p.Postfix = postfix
981	assertKeyValues(t, input, p, keyvalues...)
982}
983
984// tests whether key/value pairs exist for a given input.
985// keyvalues is expected to be an even number of strings of "key", "value", ...
986func assertKeyValues(t *testing.T, input string, p *Properties, keyvalues ...string) {
987	assert.Equal(t, p != nil, true, "want properties")
988	assert.Equal(t, 2*p.Len(), len(keyvalues), "Odd number of key/value pairs.")
989
990	for i := 0; i < len(keyvalues); i += 2 {
991		key, value := keyvalues[i], keyvalues[i+1]
992		v, ok := p.Get(key)
993		if !ok {
994			t.Errorf("No key %q found (input=%q)", key, input)
995		}
996		if got, want := v, value; !reflect.DeepEqual(got, want) {
997			t.Errorf("Value %q does not match %q (input=%q)", v, value, input)
998		}
999	}
1000}
1001
1002func mustParse(t *testing.T, s string) *Properties {
1003	p, err := parse(s)
1004	if err != nil {
1005		t.Fatalf("parse failed with %s", err)
1006	}
1007	return p
1008}
1009
1010// prints to stderr if the -verbose flag was given.
1011func printf(format string, args ...interface{}) {
1012	if *verbose {
1013		fmt.Fprintf(os.Stderr, format, args...)
1014	}
1015}
1016