1 ///////////////////////////////////////////////////////////////////////////////
2 // Name:        tests/fileconf/fileconf.cpp
3 // Purpose:     wxFileConf unit test
4 // Author:      Vadim Zeitlin
5 // Created:     2004-09-19
6 // Copyright:   (c) 2004 Vadim Zeitlin
7 ///////////////////////////////////////////////////////////////////////////////
8 
9 // ----------------------------------------------------------------------------
10 // headers
11 // ----------------------------------------------------------------------------
12 
13 #include "testprec.h"
14 
15 
16 #if wxUSE_FILECONFIG
17 
18 #ifndef WX_PRECOMP
19 #endif // WX_PRECOMP
20 
21 #include "wx/fileconf.h"
22 #include "wx/sstream.h"
23 #include "wx/log.h"
24 
25 static const char *testconfig =
26 "[root]\n"
27 "entry=value\n"
28 "[root/group1]\n"
29 "[root/group1/subgroup]\n"
30 "subentry=subvalue\n"
31 "subentry2=subvalue2\n"
32 "[root/group2]\n"
33 ;
34 
35 // ----------------------------------------------------------------------------
36 // local functions
37 // ----------------------------------------------------------------------------
38 
Dump(wxFileConfig & fc)39 static wxString Dump(wxFileConfig& fc)
40 {
41     wxStringOutputStream sos;
42     fc.Save(sos);
43     return wxTextFile::Translate(sos.GetString(), wxTextFileType_Unix);
44 }
45 
46 // helper macro to test wxFileConfig contents
47 #define wxVERIFY_FILECONFIG(t, fc) CHECK(Dump(fc) == t)
48 
ChangePath(wxFileConfig & fc,const char * path)49 static wxString ChangePath(wxFileConfig& fc, const char *path)
50 {
51     fc.SetPath(path);
52 
53     return fc.GetPath();
54 }
55 
56 // ----------------------------------------------------------------------------
57 // test class
58 // ----------------------------------------------------------------------------
59 
60 TEST_CASE("wxFileConfig::Path", "[fileconfig][config]")
61 {
62     wxStringInputStream sis(testconfig);
63     wxFileConfig fc(sis);
64 
65     CHECK( ChangePath(fc, "") == "" );
66     CHECK( ChangePath(fc, "/") == "" );
67     CHECK( ChangePath(fc, "root") == "/root" );
68     CHECK( ChangePath(fc, "/root") == "/root" );
69     CHECK( ChangePath(fc, "/root/group1/subgroup") == "/root/group1/subgroup" );
70     CHECK( ChangePath(fc, "/root/group2") == "/root/group2" );
71 }
72 
73 TEST_CASE("wxFileConfig::AddEntries", "[fileconfig][config]")
74 {
75     wxFileConfig fc;
76 
77     wxVERIFY_FILECONFIG( "", fc  );
78 
79     fc.Write("/Foo", "foo");
80     wxVERIFY_FILECONFIG( "Foo=foo\n", fc  );
81 
82     fc.Write("/Bar/Baz", "baz");
83     wxVERIFY_FILECONFIG( "Foo=foo\n[Bar]\nBaz=baz\n", fc  );
84 
85     fc.DeleteAll();
86     wxVERIFY_FILECONFIG( "", fc  );
87 
88     fc.Write("/Bar/Baz", "baz");
89     wxVERIFY_FILECONFIG( "[Bar]\nBaz=baz\n", fc  );
90 
91     fc.Write("/Foo", "foo");
92     wxVERIFY_FILECONFIG( "Foo=foo\n[Bar]\nBaz=baz\n", fc  );
93 }
94 
95 namespace
96 {
97 
98 void
CheckGroupEntries(const wxFileConfig & fc,const char * path,size_t nEntries,...)99 CheckGroupEntries(const wxFileConfig& fc,
100                   const char *path,
101                   size_t nEntries,
102                   ...)
103 {
104     wxConfigPathChanger change(&fc, wxString(path) + "/");
105 
106     CHECK( fc.GetNumberOfEntries() == nEntries );
107 
108     va_list ap;
109     va_start(ap, nEntries);
110 
111     long cookie;
112     wxString name;
113     for ( bool cont = fc.GetFirstEntry(name, cookie);
114           cont;
115           cont = fc.GetNextEntry(name, cookie), nEntries-- )
116     {
117         CHECK( name == va_arg(ap, char *) );
118     }
119 
120     CHECK( nEntries == 0 );
121 
122     va_end(ap);
123 }
124 
125 void
CheckGroupSubgroups(const wxFileConfig & fc,const char * path,size_t nGroups,...)126 CheckGroupSubgroups(const wxFileConfig& fc,
127                     const char *path,
128                     size_t nGroups,
129                     ...)
130 {
131     wxConfigPathChanger change(&fc, wxString(path) + "/");
132 
133     CHECK( fc.GetNumberOfGroups() == nGroups );
134 
135     va_list ap;
136     va_start(ap, nGroups);
137 
138     long cookie;
139     wxString name;
140     for ( bool cont = fc.GetFirstGroup(name, cookie);
141           cont;
142           cont = fc.GetNextGroup(name, cookie), nGroups-- )
143     {
144         CHECK( name == va_arg(ap, char *) );
145     }
146 
147     CHECK( nGroups == 0 );
148 
149     va_end(ap);
150 }
151 
152 } // anonymous namespace
153 
154 TEST_CASE("wxFileConfig::GetEntries", "[fileconfig][config]")
155 {
156     wxStringInputStream sis(testconfig);
157     wxFileConfig fc(sis);
158 
159     CheckGroupEntries(fc, "", 0);
160     CheckGroupEntries(fc, "/root", 1, "entry");
161     CheckGroupEntries(fc, "/root/group1", 0);
162     CheckGroupEntries(fc, "/root/group1/subgroup",
163                         2, "subentry", "subentry2");
164 }
165 
166 TEST_CASE("wxFileConfig::GetGroups", "[fileconfig][config]")
167 {
168     wxStringInputStream sis(testconfig);
169     wxFileConfig fc(sis);
170 
171     CheckGroupSubgroups(fc, "", 1, "root");
172     CheckGroupSubgroups(fc, "/root", 2, "group1", "group2");
173     CheckGroupSubgroups(fc, "/root/group1", 1, "subgroup");
174     CheckGroupSubgroups(fc, "/root/group2", 0);
175 }
176 
177 TEST_CASE("wxFileConfig::HasEntry", "[fileconfig][config]")
178 {
179     wxStringInputStream sis(testconfig);
180     wxFileConfig fc(sis);
181 
182     CHECK( !fc.HasEntry("root") );
183     CHECK( fc.HasEntry("root/entry") );
184     CHECK( fc.HasEntry("/root/entry") );
185     CHECK( fc.HasEntry("root/group1/subgroup/subentry") );
186     CHECK( !fc.HasEntry("") );
187     CHECK( !fc.HasEntry("root/group1") );
188     CHECK( !fc.HasEntry("subgroup/subentry") );
189     CHECK( !fc.HasEntry("/root/no_such_group/entry") );
190     CHECK( !fc.HasGroup("/root/no_such_group") );
191 }
192 
193 TEST_CASE("wxFileConfig::HasGroup", "[fileconfig][config]")
194 {
195     wxStringInputStream sis(testconfig);
196     wxFileConfig fc(sis);
197 
198     CHECK( fc.HasGroup("root") );
199     CHECK( fc.HasGroup("root/group1") );
200     CHECK( fc.HasGroup("root/group1/subgroup") );
201     CHECK( fc.HasGroup("root/group2") );
202     CHECK( !fc.HasGroup("") );
203     CHECK( !fc.HasGroup("root/group") );
204     CHECK( !fc.HasGroup("root//subgroup") );
205     CHECK( !fc.HasGroup("foot/subgroup") );
206     CHECK( !fc.HasGroup("foot") );
207 }
208 
209 TEST_CASE("wxFileConfig::Binary", "[fileconfig][config]")
210 {
211     wxStringInputStream sis(
212         "[root]\n"
213         "binary=Zm9vCg==\n"
214     );
215     wxFileConfig fc(sis);
216 
217     wxMemoryBuffer buf;
218     fc.Read("/root/binary", &buf);
219 
220     CHECK( memcmp("foo\n", buf.GetData(), buf.GetDataLen()) == 0 );
221 
222     buf.SetDataLen(0);
223     buf.AppendData("\0\1\2", 3);
224     fc.Write("/root/012", buf);
225     wxVERIFY_FILECONFIG(
226         "[root]\n"
227         "binary=Zm9vCg==\n"
228         "012=AAEC\n",
229         fc
230     );
231 }
232 
233 TEST_CASE("wxFileConfig::Save", "[fileconfig][config]")
234 {
235     wxStringInputStream sis(testconfig);
236     wxFileConfig fc(sis);
237     wxVERIFY_FILECONFIG( testconfig, fc );
238 }
239 
240 TEST_CASE("wxFileConfig::DeleteEntry", "[fileconfig][config]")
241 {
242     wxStringInputStream sis(testconfig);
243     wxFileConfig fc(sis);
244 
245     CHECK( !fc.DeleteEntry("foo") );
246 
247     CHECK( fc.DeleteEntry("root/group1/subgroup/subentry") );
248     wxVERIFY_FILECONFIG( "[root]\n"
249                          "entry=value\n"
250                          "[root/group1]\n"
251                          "[root/group1/subgroup]\n"
252                          "subentry2=subvalue2\n"
253                          "[root/group2]\n",
254                          fc );
255 
256     // group should be deleted now as well as it became empty
257     wxConfigPathChanger change(&fc, "root/group1/subgroup/subentry2");
258     CHECK( fc.DeleteEntry("subentry2") );
259     wxVERIFY_FILECONFIG( "[root]\n"
260                          "entry=value\n"
261                          "[root/group1]\n"
262                          "[root/group2]\n",
263                          fc );
264 }
265 
266 TEST_CASE("wxFileConfig::DeleteAndWriteEntry", "[fileconfig][config]")
267 {
268     wxStringInputStream sis(
269             "[root/group1]\n"
270             "subentry=subvalue\n"
271             "subentry2=subvalue2\n"
272             "subentry3=subvalue3\n"
273     );
274 
275     wxFileConfig fc(sis);
276 
277     fc.DeleteEntry("/root/group1/subentry2");
278     fc.Write("/root/group1/subentry2", "testvalue");
279     fc.DeleteEntry("/root/group2/subentry2");
280     fc.Write("/root/group2/subentry2", "testvalue2");
281     fc.DeleteEntry("/root/group1/subentry2");
282     fc.Write("/root/group1/subentry2", "testvalue");
283     fc.DeleteEntry("/root/group2/subentry2");
284     fc.Write("/root/group2/subentry2", "testvalue2");
285 
286     wxVERIFY_FILECONFIG( "[root/group1]\n"
287                          "subentry=subvalue\n"
288                          "subentry3=subvalue3\n"
289                          "subentry2=testvalue\n"
290                          "[root/group2]\n"
291                          "subentry2=testvalue2\n",
292                          fc );
293 
294     fc.DeleteEntry("/root/group2/subentry2");
295     wxVERIFY_FILECONFIG( "[root/group1]\n"
296                          "subentry=subvalue\n"
297                          "subentry3=subvalue3\n"
298                          "subentry2=testvalue\n",
299                          fc );
300 
301     fc.DeleteEntry("/root/group1/subentry2");
302     fc.DeleteEntry("/root/group1/subentry");
303     fc.DeleteEntry("/root/group1/subentry3");
304     wxVERIFY_FILECONFIG( "", fc );
305 }
306 
307 TEST_CASE("wxFileConfig::DeleteLastRootEntry", "[fileconfig][config]")
308 {
309     // This tests for the bug which occurred when the last entry of the root
310     // group was deleted: this corrupted internal state and resulted in a crash
311     // after trying to write the just deleted entry again.
312     wxStringInputStream sis("");
313     wxFileConfig fc(sis);
314 
315     fc.Write("key", "value");
316     wxVERIFY_FILECONFIG( "key=value\n", fc );
317 
318     fc.DeleteEntry("key");
319     wxVERIFY_FILECONFIG( "", fc );
320 
321     fc.Write("key", "value");
322     wxVERIFY_FILECONFIG( "key=value\n", fc );
323 }
324 
325 TEST_CASE("wxFileConfig::DeleteGroup", "[fileconfig][config]")
326 {
327     wxStringInputStream sis(testconfig);
328     wxFileConfig fc(sis);
329 
330     CHECK( !fc.DeleteGroup("foo") );
331 
332     CHECK( fc.DeleteGroup("root/group1") );
333     wxVERIFY_FILECONFIG( "[root]\n"
334                          "entry=value\n"
335                          "[root/group2]\n",
336                          fc );
337 
338     // notice trailing slash: it should be ignored
339     CHECK( fc.DeleteGroup("root/group2/") );
340     wxVERIFY_FILECONFIG( "[root]\n"
341                          "entry=value\n",
342                          fc );
343 
344     CHECK( fc.DeleteGroup("root") );
345     CHECK( Dump(fc).empty() );
346 }
347 
348 TEST_CASE("wxFileConfig::DeleteAll", "[fileconfig][config]")
349 {
350     wxStringInputStream sis(testconfig);
351     wxFileConfig fc(sis);
352 
353     CHECK( fc.DeleteAll() );
354     CHECK( Dump(fc).empty() );
355 }
356 
357 TEST_CASE("wxFileConfig::RenameEntry", "[fileconfig][config]")
358 {
359     wxStringInputStream sis(testconfig);
360     wxFileConfig fc(sis);
361 
362     fc.SetPath("root");
363     CHECK( fc.RenameEntry("entry", "newname") );
364     wxVERIFY_FILECONFIG( "[root]\n"
365                          "newname=value\n"
366                          "[root/group1]\n"
367                          "[root/group1/subgroup]\n"
368                          "subentry=subvalue\n"
369                          "subentry2=subvalue2\n"
370                          "[root/group2]\n",
371                          fc );
372 
373     fc.SetPath("group1/subgroup");
374     CHECK( !fc.RenameEntry("entry", "newname") );
375     CHECK( !fc.RenameEntry("subentry", "subentry2") );
376 
377     CHECK( fc.RenameEntry("subentry", "subentry1") );
378     wxVERIFY_FILECONFIG( "[root]\n"
379                          "newname=value\n"
380                          "[root/group1]\n"
381                          "[root/group1/subgroup]\n"
382                          "subentry2=subvalue2\n"
383                          "subentry1=subvalue\n"
384                          "[root/group2]\n",
385                          fc );
386 }
387 
388 TEST_CASE("wxFileConfig::RenameGroup", "[fileconfig][config]")
389 {
390     wxStringInputStream sis(testconfig);
391     wxFileConfig fc(sis);
392 
393     CHECK( fc.RenameGroup("root", "foot") );
394     wxVERIFY_FILECONFIG( "[foot]\n"
395                          "entry=value\n"
396                          "[foot/group1]\n"
397                          "[foot/group1/subgroup]\n"
398                          "subentry=subvalue\n"
399                          "subentry2=subvalue2\n"
400                          "[foot/group2]\n",
401                          fc );
402 
403     // renaming a path doesn't work, it must be the immediate group
404     CHECK( !fc.RenameGroup("foot/group1", "group2") );
405 
406 
407     fc.SetPath("foot");
408 
409     // renaming to a name of existing group doesn't work
410     CHECK( !fc.RenameGroup("group1", "group2") );
411 
412     // try exchanging the groups names and then restore them back
413     CHECK( fc.RenameGroup("group1", "groupTmp") );
414     wxVERIFY_FILECONFIG( "[foot]\n"
415                          "entry=value\n"
416                          "[foot/groupTmp]\n"
417                          "[foot/groupTmp/subgroup]\n"
418                          "subentry=subvalue\n"
419                          "subentry2=subvalue2\n"
420                          "[foot/group2]\n",
421                          fc );
422 
423     CHECK( fc.RenameGroup("group2", "group1") );
424     wxVERIFY_FILECONFIG( "[foot]\n"
425                          "entry=value\n"
426                          "[foot/groupTmp]\n"
427                          "[foot/groupTmp/subgroup]\n"
428                          "subentry=subvalue\n"
429                          "subentry2=subvalue2\n"
430                          "[foot/group1]\n",
431                          fc );
432 
433     CHECK( fc.RenameGroup("groupTmp", "group2") );
434     wxVERIFY_FILECONFIG( "[foot]\n"
435                          "entry=value\n"
436                          "[foot/group2]\n"
437                          "[foot/group2/subgroup]\n"
438                          "subentry=subvalue\n"
439                          "subentry2=subvalue2\n"
440                          "[foot/group1]\n",
441                          fc );
442 
443     CHECK( fc.RenameGroup("group1", "groupTmp") );
444     wxVERIFY_FILECONFIG( "[foot]\n"
445                          "entry=value\n"
446                          "[foot/group2]\n"
447                          "[foot/group2/subgroup]\n"
448                          "subentry=subvalue\n"
449                          "subentry2=subvalue2\n"
450                          "[foot/groupTmp]\n",
451                          fc );
452 
453     CHECK( fc.RenameGroup("group2", "group1") );
454     wxVERIFY_FILECONFIG( "[foot]\n"
455                          "entry=value\n"
456                          "[foot/group1]\n"
457                          "[foot/group1/subgroup]\n"
458                          "subentry=subvalue\n"
459                          "subentry2=subvalue2\n"
460                          "[foot/groupTmp]\n",
461                          fc );
462 
463     CHECK( fc.RenameGroup("groupTmp", "group2") );
464     wxVERIFY_FILECONFIG( "[foot]\n"
465                          "entry=value\n"
466                          "[foot/group1]\n"
467                          "[foot/group1/subgroup]\n"
468                          "subentry=subvalue\n"
469                          "subentry2=subvalue2\n"
470                          "[foot/group2]\n",
471                          fc );
472 }
473 
474 TEST_CASE("wxFileConfig::CreateSubgroupAndEntries", "[fileconfig][config]")
475 {
476     wxFileConfig fc;
477     fc.Write("sub/sub_first", "sub_one");
478     fc.Write("first", "one");
479 
480     wxVERIFY_FILECONFIG( "first=one\n"
481                          "[sub]\n"
482                          "sub_first=sub_one\n",
483                          fc );
484 }
485 
486 TEST_CASE("wxFileConfig::CreateEntriesAndSubgroup", "[fileconfig][config]")
487 {
488     wxFileConfig fc;
489     fc.Write("first", "one");
490     fc.Write("second", "two");
491     fc.Write("sub/sub_first", "sub_one");
492 
493     wxVERIFY_FILECONFIG( "first=one\n"
494                          "second=two\n"
495                          "[sub]\n"
496                          "sub_first=sub_one\n",
497                          fc );
498 }
499 
EmptyConfigAndWriteKey()500 static void EmptyConfigAndWriteKey()
501 {
502     wxFileConfig fc("deleteconftest");
503 
504     const wxString groupPath = "/root";
505 
506     if ( fc.Exists(groupPath) )
507     {
508         // using DeleteGroup exposes the problem, using DeleteAll doesn't
509         CHECK( fc.DeleteGroup(groupPath) );
510     }
511 
512     // the config must be empty for the problem to arise
513     CHECK( !fc.GetNumberOfEntries(true) );
514     CHECK( !fc.GetNumberOfGroups(true) );
515 
516 
517     // this crashes on second call of this function
518     CHECK( fc.Write(groupPath + "/entry", "value") );
519 }
520 
521 TEST_CASE("wxFileConfig::DeleteLastGroup", "[fileconfig][config]")
522 {
523     /*
524     We make 2 of the same calls, first to create a file config with a single
525     group and key...
526     */
527     ::EmptyConfigAndWriteKey();
528 
529     /*
530     ... then the same but this time the key's group is deleted before the
531     key is written again. This causes a crash.
532     */
533     ::EmptyConfigAndWriteKey();
534 
535 
536     // clean up
537     wxLogNull noLogging;
538     (void) ::wxRemoveFile(wxFileConfig::GetLocalFileName("deleteconftest"));
539 }
540 
541 TEST_CASE("wxFileConfig::DeleteAndRecreateGroup", "[fileconfig][config]")
542 {
543     static const char *confInitial =
544         "[First]\n"
545         "Value1=Foo\n"
546         "[Second]\n"
547         "Value2=Bar\n";
548 
549     wxStringInputStream sis(confInitial);
550     wxFileConfig fc(sis);
551 
552     fc.DeleteGroup("Second");
553     wxVERIFY_FILECONFIG( "[First]\n"
554                          "Value1=Foo\n",
555                          fc );
556 
557     fc.Write("Second/Value2", "New");
558     wxVERIFY_FILECONFIG( "[First]\n"
559                          "Value1=Foo\n"
560                          "[Second]\n"
561                          "Value2=New\n",
562                          fc );
563 }
564 
565 TEST_CASE("wxFileConfig::AddToExistingRoot", "[fileconfig][config]")
566 {
567     static const char *confInitial =
568         "[Group]\n"
569         "value1=foo\n";
570 
571     wxStringInputStream sis(confInitial);
572     wxFileConfig fc(sis);
573 
574     fc.Write("/value1", "bar");
575     wxVERIFY_FILECONFIG(
576         "value1=bar\n"
577         "[Group]\n"
578         "value1=foo\n",
579         fc
580     );
581 }
582 
583 TEST_CASE("wxFileConfig::ReadNonExistent", "[fileconfig][config]")
584 {
585     static const char *confTest =
586         "community=censored\n"
587         "[City1]\n"
588         "URL=www.fake1.na\n"
589         "[City1/A1]\n"
590         "[City1/A1/1]\n"
591         "IP=192.168.1.66\n"
592         "URL=www.fake2.na\n"
593     ;
594 
595     wxStringInputStream sis(confTest);
596     wxFileConfig fc(sis);
597 
598     wxString url;
599     CHECK( !fc.Read("URL", &url) );
600 }
601 
602 TEST_CASE("wxFileConfig::ReadEmpty", "[fileconfig][config]")
603 {
604     static const char *confTest = "";
605 
606     wxStringInputStream sis(confTest);
607     wxFileConfig fc(sis);
608 }
609 
610 TEST_CASE("wxFileConfig::ReadFloat", "[fileconfig][config]")
611 {
612     static const char *confTest =
613         "x=1.234\n"
614         "y=-9876.5432\n"
615         "z=2e+308\n"
616     ;
617 
618     wxStringInputStream sis(confTest);
619     wxFileConfig fc(sis);
620 
621     float f;
622     CHECK( fc.Read("x", &f) );
623     CHECK( f == 1.234f );
624 
625     CHECK( fc.Read("y", &f) );
626     CHECK( f == -9876.5432f );
627 }
628 
629 TEST_CASE("wxFileConfig::LongLong", "[fileconfig][config][longlong]")
630 {
631     wxFileConfig fc("", "", "", "", 0); // Don't use any files.
632 
633     // See comment near val64 definition in regconf.cpp.
634     const wxLongLong_t val = wxLL(0x8000000000000008);
635     REQUIRE( fc.Write("ll", val) );
636 
637     wxLongLong_t ll;
638     REQUIRE( fc.Read("ll", &ll) );
639     CHECK( ll == val );
640 }
641 
642 #endif // wxUSE_FILECONFIG
643 
644