1 /*
2  * config-test.c:  tests svn_config
3  *
4  * ====================================================================
5  *    Licensed to the Apache Software Foundation (ASF) under one
6  *    or more contributor license agreements.  See the NOTICE file
7  *    distributed with this work for additional information
8  *    regarding copyright ownership.  The ASF licenses this file
9  *    to you under the Apache License, Version 2.0 (the
10  *    "License"); you may not use this file except in compliance
11  *    with the License.  You may obtain a copy of the License at
12  *
13  *      http://www.apache.org/licenses/LICENSE-2.0
14  *
15  *    Unless required by applicable law or agreed to in writing,
16  *    software distributed under the License is distributed on an
17  *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18  *    KIND, either express or implied.  See the License for the
19  *    specific language governing permissions and limitations
20  *    under the License.
21  * ====================================================================
22  */
23 
24 /* ====================================================================
25    To add tests, look toward the bottom of this file.
26 
27 */
28 
29 
30 
31 #include <string.h>
32 
33 #include <apr_getopt.h>
34 #include <apr_pools.h>
35 
36 #include "svn_dirent_uri.h"
37 #include "svn_error.h"
38 #include "svn_config.h"
39 #include "private/svn_subr_private.h"
40 #include "private/svn_config_private.h"
41 
42 #include "../svn_test.h"
43 
44 
45 /* A quick way to create error messages.  */
46 static svn_error_t *
fail(apr_pool_t * pool,const char * fmt,...)47 fail(apr_pool_t *pool, const char *fmt, ...)
48 {
49   va_list ap;
50   char *msg;
51 
52   va_start(ap, fmt);
53   msg = apr_pvsprintf(pool, fmt, ap);
54   va_end(ap);
55 
56   return svn_error_create(SVN_ERR_TEST_FAILED, SVN_NO_ERROR, msg);
57 }
58 
59 static svn_error_t *
get_config_file_path(const char ** cfg_file,const svn_test_opts_t * opts,apr_pool_t * pool)60 get_config_file_path(const char **cfg_file,
61                      const svn_test_opts_t *opts,
62                      apr_pool_t *pool)
63 {
64   const char *srcdir;
65 
66   SVN_ERR(svn_test_get_srcdir(&srcdir, opts, pool));
67   *cfg_file = svn_dirent_join(srcdir, "config-test.cfg", pool);
68 
69   return SVN_NO_ERROR;
70 }
71 
72 static const char *config_keys[] = { "foo", "a", "b", "c", "d", "e", "f", "g",
73                                      "h", "i", "m", NULL };
74 static const char *config_values[] = { "bar", "Aa", "100", "bar",
75                                        "a %(bogus)s oyster bar",
76                                        "%(bogus)s shmoo %(",
77                                        "%Aa", "lyrical bard", "%(unterminated",
78                                        "Aa 100", "foo bar baz", NULL };
79 
80 static svn_error_t *
test_text_retrieval(const svn_test_opts_t * opts,apr_pool_t * pool)81 test_text_retrieval(const svn_test_opts_t *opts,
82                     apr_pool_t *pool)
83 {
84   svn_config_t *cfg;
85   int i;
86   const char *cfg_file;
87 
88   SVN_ERR(get_config_file_path(&cfg_file, opts, pool));
89   SVN_ERR(svn_config_read3(&cfg, cfg_file, TRUE, FALSE, FALSE, pool));
90 
91   /* Test values retrieved from our ConfigParser instance against
92      values retrieved using svn_config. */
93   for (i = 0; config_keys[i] != NULL; i++)
94     {
95       const char *key, *py_val, *c_val;
96 
97       key = config_keys[i];
98       py_val = config_values[i];
99       svn_config_get(cfg, &c_val, "section1", key, "default value");
100 #if 0
101       printf("Testing expected value '%s' against '%s' for "
102              "option '%s'\n", py_val, c_val, key);
103 #endif
104       /* Fail iff one value is null, or the strings don't match. */
105       if ((c_val == NULL) != (py_val == NULL)
106           || (c_val != NULL && py_val != NULL && strcmp(c_val, py_val) != 0))
107         return fail(pool, "Expected value '%s' not equal to '%s' for "
108                     "option '%s'", py_val, c_val, key);
109     }
110 
111   {
112     const char *value = svn_config_get_server_setting(cfg, "server group",
113                                                       "setting", "default");
114     if (value == NULL || strcmp(value, "default") != 0)
115       return svn_error_create(SVN_ERR_TEST_FAILED, SVN_NO_ERROR,
116                               "Expected a svn_config_get_server_setting()"
117                               "to return 'default'");
118   }
119 
120   return SVN_NO_ERROR;
121 }
122 
123 
124 static const char *true_keys[] = {"true1", "true2", "true3", "true4",
125                                   NULL};
126 static const char *false_keys[] = {"false1", "false2", "false3", "false4",
127                                    NULL};
128 
129 static svn_error_t *
test_boolean_retrieval(const svn_test_opts_t * opts,apr_pool_t * pool)130 test_boolean_retrieval(const svn_test_opts_t *opts,
131                        apr_pool_t *pool)
132 {
133   svn_config_t *cfg;
134   int i;
135   const char *cfg_file;
136 
137   SVN_ERR(get_config_file_path(&cfg_file, opts, pool));
138   SVN_ERR(svn_config_read3(&cfg, cfg_file, TRUE, FALSE, FALSE, pool));
139 
140   for (i = 0; true_keys[i] != NULL; i++)
141     {
142       svn_boolean_t value;
143       SVN_ERR(svn_config_get_bool(cfg, &value, "booleans", true_keys[i],
144                                   FALSE));
145       if (!value)
146         return fail(pool, "Value of option '%s' is not true", true_keys[i]);
147     }
148 
149   for (i = 0; false_keys[i] != NULL; i++)
150     {
151       svn_boolean_t value;
152       SVN_ERR(svn_config_get_bool(cfg, &value, "booleans", false_keys[i],
153                                   TRUE));
154       if (value)
155         return fail(pool, "Value of option '%s' is not true", false_keys[i]);
156     }
157 
158   {
159     svn_error_t *err;
160     svn_boolean_t value;
161 
162     svn_error_clear((err = svn_config_get_bool(cfg, &value,
163                                                "booleans", "bad_true",
164                                                TRUE)));
165     if (!err)
166       return fail(pool, "No error on bad truth value");
167 
168     svn_error_clear((err = svn_config_get_bool(cfg, &value,
169                                                "booleans", "bad_false",
170                                                FALSE)));
171     if (!err)
172       return fail(pool, "No error on bad truth value");
173   }
174 
175   {
176     svn_boolean_t value;
177     SVN_ERR(svn_config_get_server_setting_bool(cfg, &value, "server group",
178                                                "setting", FALSE));
179     if (value)
180       return svn_error_create(SVN_ERR_TEST_FAILED, SVN_NO_ERROR,
181                               "Expected a svn_config_get_server_setting_bool()"
182                               "to return FALSE, but it returned TRUE");
183   }
184 
185   return SVN_NO_ERROR;
186 }
187 
188 static svn_error_t *
test_has_section_case_insensitive(const svn_test_opts_t * opts,apr_pool_t * pool)189 test_has_section_case_insensitive(const svn_test_opts_t *opts,
190                                   apr_pool_t *pool)
191 {
192   svn_config_t *cfg;
193   const char *cfg_file;
194 
195   SVN_ERR(get_config_file_path(&cfg_file, opts, pool));
196   SVN_ERR(svn_config_read3(&cfg, cfg_file, TRUE, FALSE, FALSE, pool));
197 
198   if (! svn_config_has_section(cfg, "section1"))
199     return fail(pool, "Failed to find section1");
200 
201   if (! svn_config_has_section(cfg, "SECTION1"))
202     return fail(pool, "Failed to find SECTION1");
203 
204   if (! svn_config_has_section(cfg, "UpperCaseSection"))
205     return fail(pool, "Failed to find UpperCaseSection");
206 
207   if (! svn_config_has_section(cfg, "uppercasesection"))
208     return fail(pool, "Failed to find UpperCaseSection");
209 
210   if (svn_config_has_section(cfg, "notthere"))
211     return fail(pool, "Returned true on missing section");
212 
213   return SVN_NO_ERROR;
214 }
215 
216 static svn_error_t *
test_has_section_case_sensitive(const svn_test_opts_t * opts,apr_pool_t * pool)217 test_has_section_case_sensitive(const svn_test_opts_t *opts,
218                                 apr_pool_t *pool)
219 {
220   svn_config_t *cfg;
221   const char *cfg_file;
222 
223   SVN_ERR(get_config_file_path(&cfg_file, opts, pool));
224   SVN_ERR(svn_config_read3(&cfg, cfg_file, TRUE, TRUE, FALSE, pool));
225 
226   if (! svn_config_has_section(cfg, "section1"))
227     return fail(pool, "Failed to find section1");
228 
229   if (svn_config_has_section(cfg, "SECTION1"))
230     return fail(pool, "Returned true on missing section");
231 
232   if (! svn_config_has_section(cfg, "UpperCaseSection"))
233     return fail(pool, "Failed to find UpperCaseSection");
234 
235   if (svn_config_has_section(cfg, "uppercasesection"))
236     return fail(pool, "Returned true on missing section");
237 
238   if (svn_config_has_section(cfg, "notthere"))
239     return fail(pool, "Returned true on missing section");
240 
241   return SVN_NO_ERROR;
242 }
243 
244 static svn_error_t *
test_has_option_case_sensitive(const svn_test_opts_t * opts,apr_pool_t * pool)245 test_has_option_case_sensitive(const svn_test_opts_t *opts,
246                                apr_pool_t *pool)
247 {
248   svn_config_t *cfg;
249   const char *cfg_file;
250   apr_int64_t value;
251   int i;
252 
253   static struct test_dataset {
254     const char *option;
255     apr_int64_t value;
256   } const test_data[] = {
257     { "a", 1 },
258     { "A", 2 },
259     { "B", 3 },
260     { "b", 4 }
261   };
262   static const int test_data_size = sizeof(test_data)/sizeof(*test_data);
263 
264   SVN_ERR(get_config_file_path(&cfg_file, opts, pool));
265   SVN_ERR(svn_config_read3(&cfg, cfg_file, TRUE, TRUE, TRUE, pool));
266 
267   for (i = 0; i < test_data_size; ++i)
268     {
269       SVN_ERR(svn_config_get_int64(cfg, &value, "case-sensitive-option",
270                                    test_data[i].option, -1));
271       if (test_data[i].value != value)
272         return fail(pool,
273                     apr_psprintf(pool,
274                                  "case-sensitive-option.%s != %"
275                                  APR_INT64_T_FMT" but %"APR_INT64_T_FMT,
276                                  test_data[i].option,
277                                  test_data[i].value,
278                                  value));
279     }
280 
281   return SVN_NO_ERROR;
282 }
283 
284 static svn_error_t *
test_stream_interface(const svn_test_opts_t * opts,apr_pool_t * pool)285 test_stream_interface(const svn_test_opts_t *opts,
286                       apr_pool_t *pool)
287 {
288   svn_config_t *cfg;
289   const char *cfg_file;
290   svn_stream_t *stream;
291 
292   SVN_ERR(get_config_file_path(&cfg_file, opts, pool));
293   SVN_ERR(svn_stream_open_readonly(&stream, cfg_file, pool, pool));
294 
295   SVN_ERR(svn_config_parse(&cfg, stream, TRUE, TRUE, pool));
296 
297   /* nominal test to make sure cfg is populated with something since
298    * svn_config_parse will happily return an empty cfg if the stream is
299    * empty. */
300   if (! svn_config_has_section(cfg, "section1"))
301     return fail(pool, "Failed to find section1");
302 
303   return SVN_NO_ERROR;
304 }
305 
306 static svn_error_t *
test_ignore_bom(apr_pool_t * pool)307 test_ignore_bom(apr_pool_t *pool)
308 {
309   svn_config_t *cfg;
310   svn_string_t *cfg_string = svn_string_create("\xEF\xBB\xBF[s1]\nfoo=bar\n",
311                                                pool);
312   svn_stream_t *stream = svn_stream_from_string(cfg_string, pool);
313 
314   SVN_ERR(svn_config_parse(&cfg, stream, TRUE, TRUE, pool));
315 
316   if (! svn_config_has_section(cfg, "s1"))
317     return fail(pool, "failed to find section s1");
318 
319   return SVN_NO_ERROR;
320 }
321 
322 static svn_error_t *
test_read_only_mode(const svn_test_opts_t * opts,apr_pool_t * pool)323 test_read_only_mode(const svn_test_opts_t *opts,
324                     apr_pool_t *pool)
325 {
326   svn_config_t *cfg;
327   svn_config_t *cfg2;
328   const char *cfg_file;
329 
330   SVN_ERR(get_config_file_path(&cfg_file, opts, pool));
331   SVN_ERR(svn_config_read3(&cfg, cfg_file, TRUE, TRUE, FALSE, pool));
332 
333   /* setting CFG to r/o mode shall toggle the r/o mode and expand values */
334 
335   SVN_TEST_ASSERT(!svn_config__is_read_only(cfg));
336   SVN_TEST_ASSERT(!svn_config__is_expanded(cfg, "section1", "i"));
337 
338   svn_config__set_read_only(cfg, pool);
339 
340   SVN_TEST_ASSERT(svn_config__is_read_only(cfg));
341   SVN_TEST_ASSERT(svn_config__is_expanded(cfg, "section1", "i"));
342 
343   /* copies should be r/w with values */
344 
345   SVN_ERR(svn_config_dup(&cfg2, cfg, pool));
346   SVN_TEST_ASSERT(!svn_config__is_read_only(cfg2));
347 
348   return SVN_NO_ERROR;
349 }
350 
351 static svn_error_t *
test_expand(const svn_test_opts_t * opts,apr_pool_t * pool)352 test_expand(const svn_test_opts_t *opts,
353             apr_pool_t *pool)
354 {
355   svn_config_t *cfg;
356   const char *cfg_file, *val;
357 
358   SVN_ERR(get_config_file_path(&cfg_file, opts, pool));
359   SVN_ERR(svn_config_read3(&cfg, cfg_file, TRUE, TRUE, FALSE, pool));
360 
361   /* Get expanded "g" which requires expanding "c". */
362   svn_config_get(cfg, &val, "section1", "g", NULL);
363 
364   /* Get expanded "c". */
365   svn_config_get(cfg, &val, "section1", "c", NULL);
366 
367   /* With pool debugging enabled this ensures that the expanded value
368      of "c" was not created in a temporary pool when expanding "g". */
369   SVN_TEST_STRING_ASSERT(val, "bar");
370 
371   /* Get expanded "j" and "k" which have cyclic definitions.
372    * They must return empty values. */
373   svn_config_get(cfg, &val, "section1", "j", NULL);
374   SVN_TEST_STRING_ASSERT(val, "");
375   svn_config_get(cfg, &val, "section1", "k", NULL);
376   SVN_TEST_STRING_ASSERT(val, "");
377 
378   /* Get expanded "l" which depends on a cyclic definition.
379    * So, it also considered "undefined" and will be normalized to "". */
380   svn_config_get(cfg, &val, "section1", "l", NULL);
381   SVN_TEST_STRING_ASSERT(val, "");
382 
383   return SVN_NO_ERROR;
384 }
385 
386 static svn_error_t *
test_invalid_bom(apr_pool_t * pool)387 test_invalid_bom(apr_pool_t *pool)
388 {
389   svn_config_t *cfg;
390   svn_error_t *err;
391   svn_string_t *cfg_string;
392   svn_stream_t *stream;
393 
394   cfg_string = svn_string_create("\xEF", pool);
395   stream = svn_stream_from_string(cfg_string, pool);
396   err = svn_config_parse(&cfg, stream, TRUE, TRUE, pool);
397   SVN_TEST_ASSERT_ERROR(err, SVN_ERR_MALFORMED_FILE);
398 
399   cfg_string = svn_string_create("\xEF\xBB", pool);
400   stream = svn_stream_from_string(cfg_string, pool);
401   err = svn_config_parse(&cfg, stream, TRUE, TRUE, pool);
402   SVN_TEST_ASSERT_ERROR(err, SVN_ERR_MALFORMED_FILE);
403 
404   return SVN_NO_ERROR;
405 }
406 
407 static svn_error_t *
test_serialization(apr_pool_t * pool)408 test_serialization(apr_pool_t *pool)
409 {
410   svn_stringbuf_t *original_content;
411   svn_stringbuf_t *written_content;
412   svn_config_t *cfg;
413 
414   const struct
415     {
416       const char *section;
417       const char *option;
418       const char *value;
419     } test_data[] =
420     {
421       { "my section", "value1", "some" },
422       { "my section", "value2", "something" },
423       { "another Section", "value1", "one" },
424       { "another Section", "value2", "two" },
425       { "another Section", "value 3", "more" },
426     };
427   int i;
428 
429   /* Format the original with the same formatting that the writer will use. */
430   original_content = svn_stringbuf_create("\n[my section]\n"
431                                           "value1=some\n"
432                                           "value2=%(value1)sthing\n"
433                                           "\n[another Section]\n"
434                                           "value1=one\n"
435                                           "value2=two\n"
436                                           "value 3=more\n",
437                                           pool);
438   written_content = svn_stringbuf_create_empty(pool);
439 
440   SVN_ERR(svn_config_parse(&cfg,
441                            svn_stream_from_stringbuf(original_content, pool),
442                            TRUE, TRUE, pool));
443   SVN_ERR(svn_config__write(svn_stream_from_stringbuf(written_content, pool),
444                             cfg, pool));
445   SVN_ERR(svn_config_parse(&cfg,
446                            svn_stream_from_stringbuf(written_content, pool),
447                            TRUE, TRUE, pool));
448 
449   /* The serialized and re-parsed config must have the expected contents. */
450   for (i = 0; i < sizeof(test_data) / sizeof(test_data[0]); ++i)
451     {
452       const char *val;
453       svn_config_get(cfg, &val, test_data[i].section, test_data[i].option,
454                      NULL);
455       SVN_TEST_STRING_ASSERT(val, test_data[i].value);
456     }
457 
458   return SVN_NO_ERROR;
459 }
460 
461 /*
462    ====================================================================
463    If you add a new test to this file, update this array.
464 
465    (These globals are required by our included main())
466 */
467 
468 /* An array of all test functions */
469 
470 static int max_threads = 1;
471 
472 static struct svn_test_descriptor_t test_funcs[] =
473   {
474     SVN_TEST_NULL,
475     SVN_TEST_OPTS_PASS(test_text_retrieval,
476                        "test svn_config"),
477     SVN_TEST_OPTS_PASS(test_boolean_retrieval,
478                        "test svn_config boolean conversion"),
479     SVN_TEST_OPTS_PASS(test_has_section_case_insensitive,
480                        "test svn_config_has_section (case insensitive)"),
481     SVN_TEST_OPTS_PASS(test_has_section_case_sensitive,
482                        "test svn_config_has_section (case sensitive)"),
483     SVN_TEST_OPTS_PASS(test_has_option_case_sensitive,
484                        "test case-sensitive option name lookup"),
485     SVN_TEST_OPTS_PASS(test_stream_interface,
486                        "test svn_config_parse"),
487     SVN_TEST_PASS2(test_ignore_bom,
488                    "test parsing config file with BOM"),
489     SVN_TEST_OPTS_PASS(test_read_only_mode,
490                        "test r/o mode"),
491     SVN_TEST_OPTS_PASS(test_expand,
492                        "test variable expansion"),
493     SVN_TEST_PASS2(test_invalid_bom,
494                    "test parsing config file with invalid BOM"),
495     SVN_TEST_PASS2(test_serialization,
496                    "test writing a config"),
497     SVN_TEST_NULL
498   };
499 
500 SVN_TEST_MAIN
501