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