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