c0bdbd22c06115a5ddab0e166a7cddf5c58ec818
[debian/amanda] / installcheck / Amanda_Config.pl
1 # Copyright (c) 2005-2008 Zmanda Inc.  All Rights Reserved.
2 #
3 # This program is free software; you can redistribute it and/or modify it
4 # under the terms of the GNU General Public License version 2 as published
5 # by the Free Software Foundation.
6 #
7 # This program is distributed in the hope that it will be useful, but
8 # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
9 # or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
10 # for more details.
11 #
12 # You should have received a copy of the GNU General Public License along
13 # with this program; if not, write to the Free Software Foundation, Inc.,
14 # 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
15 #
16 # Contact information: Zmanda Inc, 465 S Mathlida Ave, Suite 300
17 # Sunnyvale, CA 94086, USA, or: http://www.zmanda.com
18
19 use Test::More tests => 113;
20 use strict;
21
22 use lib "@amperldir@";
23 use Installcheck::Config;
24 use Amanda::Paths;
25 use Amanda::Tests;
26 use Amanda::Config qw( :init :getconf );
27 use Amanda::Debug;
28
29 my $testconf;
30 my $config_overwrites;
31
32 Amanda::Debug::dbopen("installcheck");
33
34 # utility function
35
36 sub diag_config_errors {
37     my ($level, @errors) = Amanda::Config::config_errors();
38     for my $errmsg (@errors) {
39         diag $errmsg;
40     }
41 }
42
43 ##
44 # Try starting with no configuration at all
45
46 is(config_init(0, ''), $CFGERR_OK,
47     "Initialize with no configuration")
48     or diag_config_errors();
49
50 is(config_init(0, undef), $CFGERR_OK,
51     "Initialize with no configuration, passing a NULL config name")
52     or diag_config_errors();
53
54 $config_overwrites = new_config_overwrites(1);
55 add_config_overwrite($config_overwrites, "tapedev", "null:TEST");
56 apply_config_overwrites($config_overwrites);
57
58 is(getconf($CNF_TAPEDEV), "null:TEST",
59     "config overwrites work with null config");
60
61 ##
62 # Check out error handling
63
64 $testconf = Installcheck::Config->new();
65 $testconf->add_param('rawtapedev', '"/dev/medium-rare-please"'); # a deprecated keyword -> warning
66 $testconf->write();
67
68 {
69     is(config_init($CONFIG_INIT_EXPLICIT_NAME, "TESTCONF"), $CFGERR_WARNINGS,
70         "Deprecated keyword generates a warning");
71     my ($error_level, @errors) = Amanda::Config::config_errors();
72     like($errors[0], qr/is deprecated/, 
73         "config_get_errors returns the warning string");
74
75     Amanda::Config::config_clear_errors();
76     ($error_level, @errors) = Amanda::Config::config_errors();
77     is(scalar(@errors), 0, "config_clear_errors clears error list");
78 }
79
80 $testconf = Installcheck::Config->new();
81 $testconf->add_param('invalid-param', 'random-value'); # a deprecated keyword -> warning
82 $testconf->write();
83
84 is(config_init($CONFIG_INIT_EXPLICIT_NAME, "NO-SUCH-CONFIGURATION"), $CFGERR_ERRORS,
85     "Non-existent config generates an error");
86
87 is(config_init($CONFIG_INIT_EXPLICIT_NAME, "TESTCONF"), $CFGERR_ERRORS,
88     "Invalid keyword generates an error");
89
90 ##
91 # try a client configuration
92
93 $testconf = Installcheck::Config->new();
94 $testconf->add_client_param('property', '"client-prop" "yep"');
95 $testconf->add_client_param('property', 'priority "client-prop1" "foo"');
96 $testconf->add_client_param('property', 'append "client-prop" "bar"');
97 $testconf->write();
98
99 my $cfg_result = config_init($CONFIG_INIT_CLIENT, undef);
100 is($cfg_result, $CFGERR_OK,
101     "Load test client configuration")
102     or diag_config_errors();
103
104 is_deeply(getconf($CNF_PROPERTY), { "client-prop1" => { priority => 1,
105                                                         append   => 0,
106                                                         values => [ "foo" ]},
107                                     "client-prop" => { priority => 0,
108                                                        append   => 1,
109                                                        values => [ "yep", "bar" ] }},
110     "Client PROPERTY parameter parsed correctly");
111
112 ##
113 # Parse up a basic configuration
114
115 # invent a "large" unsigned number, and make $size_t_num 
116 # depend on the length of size_t
117 my $int64_num = '171801575472'; # 0xA000B000C000 / 1024
118 my $size_t_num;
119 if (Amanda::Tests::sizeof_size_t() > 4) {
120     $size_t_num = $int64_num;
121 } else {
122     $size_t_num = '2147483647'; # 0x7fffffff
123 }
124
125 $testconf = Installcheck::Config->new();
126 $testconf->add_param('reserve', '75');
127 $testconf->add_param('autoflush', 'yes');
128 $testconf->add_param('tapedev', '"/dev/foo"');
129 $testconf->add_param('bumpsize', $int64_num);
130 $testconf->add_param('bumpmult', '1.4');
131 $testconf->add_param('reserved_udp-port', '100,200'); # note use of '-' and '_'
132 $testconf->add_param('device_output_buffer_size', $size_t_num);
133 $testconf->add_param('taperalgo', 'last');
134 $testconf->add_param('device_property', '"foo" "bar"');
135 $testconf->add_param('device_property', '"blue" "car" "tar"');
136 $testconf->add_param('displayunit', '"m"');
137 $testconf->add_param('debug_auth', '1');
138 $testconf->add_tapetype('mytapetype', [
139     'comment' => '"mine"',
140     'length' => '128 M',
141 ]);
142 $testconf->add_dumptype('mydump-type', [    # note dash
143     'comment' => '"mine"',
144     'priority' => 'high',  # == 2
145     'bumpsize' => $int64_num,
146     'bumpmult' => 1.75,
147     'starttime' => 1829,
148     'holdingdisk' => 'required',
149     'compress' => 'client best',
150     'encrypt' => 'server',
151     'strategy' => 'incronly',
152     'comprate' => '0.25,0.75',
153     'exclude list' => '"foo" "bar"',
154     'exclude list append' => '"true" "star"',
155     'exclude file' => '"foolist"',
156     'include list' => '"bing" "ting"',
157     'include list append' => '"string" "fling"',
158     'include file optional' => '"rhyme"',
159     'property' => '"prop" "erty"',
160     'property' => '"drop" "qwerty" "asdfg"',
161 ]);
162 $testconf->add_dumptype('second_dumptype', [ # note underscore
163     '' => 'mydump-type',
164     'comment' => '"refers to mydump-type with a dash"',
165 ]);
166 $testconf->add_dumptype('third_dumptype', [
167     '' => 'second_dumptype',
168     'comment' => '"refers to second_dumptype with an underscore"',
169 ]);
170 $testconf->add_interface('ethernet', [
171     'comment' => '"mine"',
172     'use' => '100',
173 ]);
174 $testconf->add_interface('nic', [
175     'comment' => '"empty"',
176 ]);
177 $testconf->add_holdingdisk('hd1', [
178     'comment' => '"mine"',
179     'directory' => '"/mnt/hd1"',
180     'use' => '100M',
181     'chunksize' => '1024k',
182 ]);
183 $testconf->add_holdingdisk('hd2', [
184     'comment' => '"empty"',
185 ]);
186 $testconf->add_application('my_app', [
187     'comment' => '"my_app_comment"',
188     'plugin' => '"amgtar"',
189 ]);
190 $testconf->add_script('my_script', [
191   'comment' => '"my_script_comment"',
192   'plugin' => '"script-email"',
193   'execute-on' => 'pre-host-backup, post-host-backup',
194   'execute-where' => 'client',
195   'property' => '"mailto" "amandabackup" "amanda"',
196 ]);
197 $testconf->add_device('my_device', [
198   'comment' => '"my device is mine, not yours"',
199   'tapedev' => '"tape:/dev/nst0"',
200   'device_property' => '"BLOCK_SIZE" "128k"',
201 ]);
202 $testconf->add_changer('my_changer', [
203   'comment' => '"my changer is mine, not yours"',
204   'tpchanger' => '"chg-foo"',
205   'changerdev' => '"/dev/sg0"',
206   'changerfile' => '"chg.state"',
207 ]);
208
209 $testconf->write();
210
211 $cfg_result = config_init($CONFIG_INIT_EXPLICIT_NAME, 'TESTCONF');
212 is($cfg_result, $CFGERR_OK,
213     "Load test configuration")
214     or diag_config_errors();
215
216 SKIP: {
217     skip "error loading config", 3 unless $cfg_result == $CFGERR_OK;
218
219     is(Amanda::Config::get_config_name(), "TESTCONF", 
220         "config_name set");
221     is(Amanda::Config::get_config_dir(), "$CONFIG_DIR/TESTCONF", 
222         "config_dir set");
223     is(Amanda::Config::get_config_filename(),
224         "$CONFIG_DIR/TESTCONF/amanda.conf", 
225         "config_filename set");
226 }
227
228 SKIP: { # global parameters
229     skip "error loading config", 11 unless $cfg_result == $CFGERR_OK;
230
231     is(getconf($CNF_RESERVE), 75,
232         "integer global confparm");
233     is(getconf($CNF_BUMPSIZE), $int64_num+0,
234         "int64 global confparm");
235     is(getconf($CNF_TAPEDEV), "/dev/foo",
236         "string global confparm");
237     is(getconf($CNF_DEVICE_OUTPUT_BUFFER_SIZE), $size_t_num+0,
238         "size global confparm");
239     ok(getconf($CNF_AUTOFLUSH),
240         "boolean global confparm");
241     is(getconf($CNF_TAPERALGO), $Amanda::Config::ALGO_LAST,
242         "taperalgo global confparam");
243     is_deeply([getconf($CNF_RESERVED_UDP_PORT)], [100,200],
244         "intrange global confparm");
245     is(getconf($CNF_DISPLAYUNIT), "M",
246         "displayunit is correctly uppercased");
247     is_deeply(getconf($CNF_DEVICE_PROPERTY),
248               { "foo" => { priority => 0, append => 0, values => ["bar"]},
249                 "blue" => { priority => 0, append => 0,
250                             values => ["car", "tar"]} },
251             "proplist global confparm");
252     ok(getconf_seen($CNF_TAPEDEV),
253         "'tapedev' parm was seen");
254     ok(!getconf_seen($CNF_CHANGERFILE),
255         "'changerfile' parm was not seen");
256 }
257
258 SKIP: { # derived values
259     skip "error loading config", 3 unless $cfg_result == $CFGERR_OK;
260
261     is(Amanda::Config::getconf_unit_divisor(), 1024, 
262         "correct unit divisor (from displayunit -> KB)");
263     ok($Amanda::Config::debug_auth, 
264         "debug_auth setting reflected in global variable");
265     ok(!$Amanda::Config::debug_amandad, 
266         "debug_amandad defaults to false");
267 }
268
269 SKIP: { # tapetypes
270     skip "error loading config", 6 unless $cfg_result == $CFGERR_OK;
271     my $ttyp = lookup_tapetype("mytapetype");
272     ok($ttyp, "found mytapetype");
273     is(tapetype_getconf($ttyp, $TAPETYPE_COMMENT), 'mine', 
274         "tapetype comment");
275     is(tapetype_getconf($ttyp, $TAPETYPE_LENGTH), 128 * 1024, 
276         "tapetype comment");
277
278     ok(tapetype_seen($ttyp, $TAPETYPE_COMMENT),
279         "tapetype comment was seen");
280     ok(!tapetype_seen($ttyp, $TAPETYPE_LBL_TEMPL),
281         "tapetype lbl_templ was not seen");
282
283     is_deeply([ sort(+getconf_list("tapetype")) ],
284               [ sort("mytapetype", "TEST-TAPE") ],
285         "getconf_list lists all tapetypes");
286 }
287
288 SKIP: { # dumptypes
289     skip "error loading config", 17 unless $cfg_result == $CFGERR_OK;
290
291     my $dtyp = lookup_dumptype("mydump-type");
292     ok($dtyp, "found mydump-type");
293     is(dumptype_getconf($dtyp, $DUMPTYPE_COMMENT), 'mine', 
294         "dumptype string");
295     is(dumptype_getconf($dtyp, $DUMPTYPE_PRIORITY), 2, 
296         "dumptype priority");
297     is(dumptype_getconf($dtyp, $DUMPTYPE_BUMPSIZE), $int64_num+0,
298         "dumptype size");
299     is(dumptype_getconf($dtyp, $DUMPTYPE_BUMPMULT), 1.75,
300         "dumptype real");
301     is(dumptype_getconf($dtyp, $DUMPTYPE_STARTTIME), 1829,
302         "dumptype time");
303     is(dumptype_getconf($dtyp, $DUMPTYPE_HOLDINGDISK), $HOLD_REQUIRED,
304         "dumptype holdingdisk");
305     is(dumptype_getconf($dtyp, $DUMPTYPE_COMPRESS), $COMP_BEST,
306         "dumptype compress");
307     is(dumptype_getconf($dtyp, $DUMPTYPE_ENCRYPT), $ENCRYPT_SERV_CUST,
308         "dumptype encrypt");
309     is(dumptype_getconf($dtyp, $DUMPTYPE_STRATEGY), $DS_INCRONLY,
310         "dumptype strategy");
311     is_deeply([dumptype_getconf($dtyp, $DUMPTYPE_COMPRATE)], [0.25, 0.75],
312         "dumptype comprate");
313     is_deeply(dumptype_getconf($dtyp, $DUMPTYPE_INCLUDE),
314         { 'file' => [ 'rhyme' ],
315           'list' => [ 'bing', 'ting', 'string', 'fling' ],
316           'optional' => 1 },
317         "dumptype include list");
318     is_deeply(dumptype_getconf($dtyp, $DUMPTYPE_EXCLUDE),
319         { 'file' => [ 'foolist' ],
320           'list' => [ 'foo', 'bar', 'true', 'star' ],
321           'optional' => 0 },
322         "dumptype exclude list");
323     is_deeply(dumptype_getconf($dtyp, $DUMPTYPE_PROPERTY),
324               { "prop" => { priority => 0, append => 0, values => ["erty"]},
325                 "drop" => { priority => 0, append => 0,
326                             values => ["qwerty", "asdfg"] }},
327             "dumptype proplist");
328
329     ok(dumptype_seen($dtyp, $DUMPTYPE_EXCLUDE),
330         "'exclude' parm was seen");
331     ok(!dumptype_seen($dtyp, $DUMPTYPE_RECORD),
332         "'record' parm was not seen");
333
334     is_deeply([ sort(+getconf_list("dumptype")) ],
335               [ sort(qw(
336                 mydump-type second_dumptype third_dumptype 
337                 NO-COMPRESS COMPRESS-FAST COMPRESS-BEST COMPRESS-CUST
338                 SRVCOMPRESS BSD-AUTH KRB4-AUTH NO-RECORD NO-HOLD
339                 NO-FULL
340                 )) ],
341         "getconf_list lists all dumptypes (including defaults)");
342 }
343
344 SKIP: { # interfaces
345     skip "error loading config", 8 unless $cfg_result == $CFGERR_OK;
346     my $iface = lookup_interface("ethernet");
347     ok($iface, "found ethernet");
348     is(interface_name($iface), "ethernet",
349         "interface knows its name");
350     is(interface_getconf($iface, $INTER_COMMENT), 'mine', 
351         "interface comment");
352     is(interface_getconf($iface, $INTER_MAXUSAGE), 100, 
353         "interface maxusage");
354
355     $iface = lookup_interface("nic");
356     ok($iface, "found nic");
357     ok(interface_seen($iface, $INTER_COMMENT),
358         "seen set for parameters that appeared");
359     ok(!interface_seen($iface, $INTER_MAXUSAGE),
360         "seen not set for parameters that did not appear");
361
362     is_deeply([ sort(+getconf_list("interface")) ],
363               [ sort('ethernet', 'nic', 'default') ],
364         "getconf_list lists all interfaces (in any order)");
365 }
366
367 SKIP: { # holdingdisks
368     skip "error loading config", 13 unless $cfg_result == $CFGERR_OK;
369     my $hdisk = lookup_holdingdisk("hd1");
370     ok($hdisk, "found hd1");
371     is(holdingdisk_name($hdisk), "hd1",
372         "hd1 knows its name");
373     is(holdingdisk_getconf($hdisk, $HOLDING_COMMENT), 'mine', 
374         "holdingdisk comment");
375     is(holdingdisk_getconf($hdisk, $HOLDING_DISKDIR), '/mnt/hd1',
376         "holdingdisk diskdir (directory)");
377     is(holdingdisk_getconf($hdisk, $HOLDING_DISKSIZE), 100*1024, 
378         "holdingdisk disksize (use)");
379     is(holdingdisk_getconf($hdisk, $HOLDING_CHUNKSIZE), 1024, 
380         "holdingdisk chunksize");
381
382     $hdisk = lookup_holdingdisk("hd2");
383     ok($hdisk, "found hd2");
384     ok(holdingdisk_seen($hdisk, $HOLDING_COMMENT),
385         "seen set for parameters that appeared");
386     ok(!holdingdisk_seen($hdisk, $HOLDING_CHUNKSIZE),
387         "seen not set for parameters that did not appear");
388
389     # only holdingdisks have this linked-list structure
390     # exposed
391     $hdisk = getconf_holdingdisks();
392     like(holdingdisk_name($hdisk), qr/hd[12]/,
393         "one disk is first in list of holdingdisks");
394     $hdisk = holdingdisk_next($hdisk);
395     like(holdingdisk_name($hdisk), qr/hd[12]/,
396         "another is second in list of holdingdisks");
397     ok(!holdingdisk_next($hdisk),
398         "no third holding disk");
399
400     is_deeply([ sort(+getconf_list("holdingdisk")) ],
401               [ sort('hd1', 'hd2') ],
402         "getconf_list lists all holdingdisks (in any order)");
403 }
404
405 SKIP: { # application
406     skip "error loading config", 5 unless $cfg_result == $CFGERR_OK;
407     my $app = lookup_application("my_app");
408     ok($app, "found my_app");
409     is(application_name($app), "my_app",
410         "my_app knows its name");
411     is(application_getconf($app, $APPLICATION_COMMENT), 'my_app_comment', 
412         "application comment");
413     is(application_getconf($app, $APPLICATION_PLUGIN), 'amgtar',
414         "application plugin (amgtar)");
415
416     is_deeply([ sort(+getconf_list("application-tool")) ],
417               [ sort("my_app") ],
418         "getconf_list lists all application-tool");
419 }
420
421 SKIP: { # script
422     skip "error loading config", 7 unless $cfg_result == $CFGERR_OK;
423     my $sc = lookup_pp_script("my_script");
424     ok($sc, "found my_script");
425     is(pp_script_name($sc), "my_script",
426         "my_script knows its name");
427     is(pp_script_getconf($sc, $PP_SCRIPT_COMMENT), 'my_script_comment', 
428         "script comment");
429     is(pp_script_getconf($sc, $PP_SCRIPT_PLUGIN), 'script-email',
430         "script plugin (script-email)");
431     is(pp_script_getconf($sc, $PP_SCRIPT_EXECUTE_WHERE), $ES_CLIENT,
432         "script execute_where (client)");
433     is(pp_script_getconf($sc, $PP_SCRIPT_EXECUTE_ON),
434         $EXECUTE_ON_PRE_HOST_BACKUP|$EXECUTE_ON_POST_HOST_BACKUP,
435         "script execute_on");
436
437     is_deeply([ sort(+getconf_list("script-tool")) ],
438               [ sort("my_script") ],
439         "getconf_list lists all script-tool");
440 }
441
442 SKIP: { # device
443     skip "error loading config", 7 unless $cfg_result == $CFGERR_OK;
444     my $dc = lookup_device_config("my_device");
445     ok($dc, "found my_device");
446     is(device_config_name($dc), "my_device",
447         "my_device knows its name");
448     is(device_config_getconf($dc, $DEVICE_CONFIG_COMMENT), 'my device is mine, not yours',
449         "device comment");
450     is(device_config_getconf($dc, $DEVICE_CONFIG_TAPEDEV), 'tape:/dev/nst0',
451         "device tapedev");
452     # TODO do we really need all of this equipment for device properties?
453     is_deeply(device_config_getconf($dc, $DEVICE_CONFIG_DEVICE_PROPERTY),
454           { "BLOCK_SIZE" => { 'priority' => 0, 'values' => ["128k"], 'append' => 0 }, },
455         "device config proplist");
456
457     is_deeply([ sort(+getconf_list("device")) ],
458               [ sort("my_device") ],
459         "getconf_list lists all devices");
460 }
461
462 SKIP: { # changer
463     skip "error loading config", 7 unless $cfg_result == $CFGERR_OK;
464     my $dc = lookup_changer_config("my_changer");
465     ok($dc, "found my_changer");
466     is(changer_config_name($dc), "my_changer",
467         "my_changer knows its name");
468     is(changer_config_getconf($dc, $CHANGER_CONFIG_COMMENT), 'my changer is mine, not yours',
469         "changer comment");
470     is(changer_config_getconf($dc, $CHANGER_CONFIG_CHANGERDEV), '/dev/sg0',
471         "changer tapedev");
472
473     is_deeply([ sort(+getconf_list("changer")) ],
474               [ sort("my_changer") ],
475         "getconf_list lists all changers");
476 }
477
478 ##
479 # Test config overwrites (using the config from above)
480
481 $config_overwrites = new_config_overwrites(1); # note estimate is too small
482 add_config_overwrite($config_overwrites, "tapedev", "null:TEST");
483 add_config_overwrite($config_overwrites, "tpchanger", "chg-test");
484 add_config_overwrite_opt($config_overwrites, "org=KAOS");
485 apply_config_overwrites($config_overwrites);
486
487 is(getconf($CNF_TAPEDEV), "null:TEST",
488     "config overwrites work with real config");
489 is(getconf($CNF_ORG), "KAOS",
490     "add_config_overwrite_opt parsed correctly");
491
492 # introduce an error
493 $config_overwrites = new_config_overwrites(1);
494 add_config_overwrite($config_overwrites, "bogusparam", "foo");
495 apply_config_overwrites($config_overwrites);
496
497 my ($error_level, @errors) = Amanda::Config::config_errors();
498 is($error_level, $CFGERR_ERRORS, "bogus config overwrite flagged as an error");
499
500 ##
501 # Test configuration dumping
502
503 # (uses the config from the previous section)
504
505 # fork a child and capture its stdout
506 my $pid = open(my $kid, "-|");
507 die "Can't fork: $!" unless defined($pid);
508 if (!$pid) {
509     Amanda::Config::dump_configuration();
510     exit 1;
511 }
512 my $dump_first_line = <$kid>;
513 my $dump = join'', $dump_first_line, <$kid>;
514 close $kid;
515 waitpid $pid, 0;
516
517 my $fn = Amanda::Config::get_config_filename();
518 my $dump_filename = $dump_first_line;
519 chomp $dump_filename;
520 $dump_filename =~ s/^# AMANDA CONFIGURATION FROM FILE "//g;
521 $dump_filename =~ s/":$//g;
522 is($dump_filename, $fn, 
523     "config filename is included correctly");
524
525 like($dump, qr/DEVICE_PROPERTY\s+"foo" "bar"\n/i,
526     "DEVICE_PROPERTY appears in dump output");
527
528 like($dump, qr/AMRECOVER_CHECK_LABEL\s+(yes|no)/i,
529     "AMRECOVER_CHECK_LABEL has a trailing space");
530
531 like($dump, qr/AMRECOVER_CHECK_LABEL\s+(yes|no)/i,
532     "AMRECOVER_CHECK_LABEL has a trailing space");
533
534 like($dump, qr/EXCLUDE\s+LIST "foo" "bar" "true" "star"/i,
535     "EXCLUDE LIST is in the dump");
536 like($dump, qr/EXCLUDE\s+FILE "foolist"/i,
537     "EXCLUDE FILE is in the dump");
538 like($dump, qr/INCLUDE\s+LIST OPTIONAL "bing" "ting" "string" "fling"/i,
539     "INCLUDE LIST is in the dump");
540 like($dump, qr/INCLUDE\s+FILE OPTIONAL "rhyme"/i,
541     "INCLUDE FILE is in the dump");
542
543 ##
544 # Explore a quirk of exinclude parsing.  Only the last
545 # exclude (or include) directive affects the 'optional' flag.
546 # We may want to change this, but we should do so intentionally.
547 # This is also tested by the 'amgetconf' installcheck.
548
549 $testconf = Installcheck::Config->new();
550 $testconf->add_dumptype('mydump-type', [
551     'exclude list' => '"foo" "bar"',
552     'exclude list optional append' => '"true" "star"',
553     'exclude list append' => '"true" "star"',
554 ]);
555 $testconf->write();
556
557 $cfg_result = config_init($CONFIG_INIT_EXPLICIT_NAME, "TESTCONF");
558 is($cfg_result, $CFGERR_OK, 
559     "first exinclude parsing config loaded")
560     or diag_config_errors();
561 SKIP: {
562     skip "error loading config", 2 unless $cfg_result == $CFGERR_OK;
563
564     my $dtyp = lookup_dumptype("mydump-type");
565     ok($dtyp, "found mydump-type");
566     is(dumptype_getconf($dtyp, $DUMPTYPE_EXCLUDE)->{'optional'}, 0,
567         "'optional' has no effect when not on the last occurrence");
568 }
569
570 ##
571 # Check out where quoting is and is not required.
572
573 $testconf = Installcheck::Config->new();
574
575 # make sure an unquoted tapetype is OK
576 $testconf->add_param('tapetype', 'TEST-TAPE'); # unquoted (Installcheck::Config uses quoted)
577
578 # strings can optionally be quoted
579 $testconf->add_param('org', '"MyOrg"');
580
581 # enumerations (e.g., taperalgo) must not be quoted; implicitly tested above
582
583 # definitions
584 $testconf->add_dumptype('"parent"', [ # note quotes
585     'bumpsize' => '10240',
586 ]);
587 $testconf->add_dumptype('child', [
588     '' => '"parent"', # note quotes
589 ]);
590 $testconf->add_dumptype('child2', [
591     '' => 'parent',
592 ]);
593 $testconf->write();
594
595 $cfg_result = config_init($CONFIG_INIT_EXPLICIT_NAME, "TESTCONF");
596 is($cfg_result, $CFGERR_OK,
597     "parsed config to test strings vs. identifiers")
598     or diag_config_errors();
599 SKIP: {
600     skip "error loading config", 3 unless $cfg_result == $CFGERR_OK;
601
602     my $dtyp = lookup_dumptype("parent");
603     ok($dtyp, "found parent");
604     $dtyp = lookup_dumptype("child");
605     ok($dtyp, "found child");
606     is(dumptype_getconf($dtyp, $DUMPTYPE_BUMPSIZE), 10240,
607         "child dumptype correctly inherited bumpsize");
608 }