Imported Upstream version 3.1.0
[debian/amanda] / installcheck / Amanda_Config.pl
1 # Copyright (c) 2007, 2008, 2009, 2010 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. Mathilda Ave., Suite 300
17 # Sunnyvale, CA 94086, USA, or: http://www.zmanda.com
18
19 use Test::More tests => 176;
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 string_to_boolean );
27 use Amanda::Debug;
28
29 my $testconf;
30 my $config_overrides;
31
32 Amanda::Debug::dbopen("installcheck");
33 Installcheck::log_test_output();
34
35 # utility function
36
37 sub diag_config_errors {
38     my ($level, @errors) = Amanda::Config::config_errors();
39     for my $errmsg (@errors) {
40         diag $errmsg;
41     }
42 }
43
44 ##
45 # Try starting with no configuration at all
46
47 is(config_init(0, ''), $CFGERR_OK,
48     "Initialize with no configuration")
49     or diag_config_errors();
50
51 config_uninit();
52 $config_overrides = new_config_overrides(1);
53 add_config_override($config_overrides, "tapedev", "null:TEST");
54 set_config_overrides($config_overrides);
55
56 is(config_init(0, undef), $CFGERR_OK,
57     "Initialize with no configuration, passing a NULL config name")
58     or diag_config_errors();
59
60 is(getconf($CNF_TAPEDEV), "null:TEST",
61     "config overwrites work with null config");
62
63 ##
64 # Check out error handling
65
66 $testconf = Installcheck::Config->new();
67 $testconf->add_param('tapebufs', '13'); # a deprecated keyword -> warning
68 $testconf->write();
69
70 {
71     is(config_init($CONFIG_INIT_EXPLICIT_NAME, "TESTCONF"), $CFGERR_WARNINGS,
72         "Deprecated keyword generates a warning");
73     my ($error_level, @errors) = Amanda::Config::config_errors();
74     like($errors[0], qr/is deprecated/, 
75         "config_get_errors returns the warning string");
76
77     Amanda::Config::config_clear_errors();
78     ($error_level, @errors) = Amanda::Config::config_errors();
79     is(scalar(@errors), 0, "config_clear_errors clears error list");
80 }
81
82 $testconf = Installcheck::Config->new();
83 $testconf->add_param('invalid-param', 'random-value'); # a deprecated keyword -> warning
84 $testconf->write();
85
86 is(config_init($CONFIG_INIT_EXPLICIT_NAME, "NO-SUCH-CONFIGURATION"), $CFGERR_ERRORS,
87     "Non-existent config generates an error");
88
89 is(config_init($CONFIG_INIT_EXPLICIT_NAME, "TESTCONF"), $CFGERR_ERRORS,
90     "Invalid keyword generates an error");
91
92 ##
93 # try a client configuration
94
95 # (note use of uppercase letters to test lower-casing of property names)
96 $testconf = Installcheck::Config->new();
97 $testconf->add_client_param('property', '"client-prop" "yep"');
98 $testconf->add_client_param('property', 'priority "clIent-prop1" "foo"');
99 $testconf->add_client_param('property', 'append "clieNt-prop" "bar"');
100 $testconf->write();
101
102 my $cfg_result = config_init($CONFIG_INIT_CLIENT, undef);
103 is($cfg_result, $CFGERR_OK,
104     "Load test client configuration")
105     or diag_config_errors();
106
107 is_deeply(getconf($CNF_PROPERTY), { "client-prop1" => { priority => 1,
108                                                         append   => 0,
109                                                         values => [ "foo" ]},
110                                     "client-prop" => { priority => 0,
111                                                        append   => 1,
112                                                        values => [ "yep", "bar" ] }},
113     "Client PROPERTY parameter parsed correctly");
114
115 ##
116 # Parse up a basic configuration
117
118 # invent a "large" unsigned number, and make $size_t_num 
119 # depend on the length of size_t
120 my $int64_num = '171801575472'; # 0xA000B000C000 / 1024
121 my $size_t_num;
122 if (Amanda::Tests::sizeof_size_t() > 4) {
123     $size_t_num = $int64_num;
124 } else {
125     $size_t_num = '2147483647'; # 0x7fffffff
126 }
127
128 $testconf = Installcheck::Config->new();
129 $testconf->add_param('reserve', '75');
130 $testconf->add_param('autoflush', 'yes');
131 $testconf->add_param('usetimestamps', '0');
132 $testconf->add_param('tapedev', '"/dev/foo"');
133 $testconf->add_param('bumpsize', $int64_num);
134 $testconf->add_param('bumpmult', '1.4');
135 $testconf->add_param('reserved_udp-port', '100,200'); # note use of '-' and '_'
136 $testconf->add_param('device_output_buffer_size', $size_t_num);
137 $testconf->add_param('taperalgo', 'last');
138 $testconf->add_param('device_property', '"foo" "bar"');
139 $testconf->add_param('device_property', '"blUE" "car" "tar"');
140 $testconf->add_param('autolabel', 'non-amanda empty');
141 $testconf->add_param('displayunit', '"m"');
142 $testconf->add_param('debug_auth', '1');
143 $testconf->add_tapetype('mytapetype', [
144     'comment' => '"mine"',
145     'length' => '128 M',
146 ]);
147 $testconf->add_dumptype('mydump-type', [    # note dash
148     'comment' => '"mine"',
149     'priority' => 'high',  # == 2
150     'bumpsize' => $int64_num,
151     'bumpmult' => 1.75,
152     'starttime' => 1829,
153     'holdingdisk' => 'required',
154     'compress' => 'client best',
155     'encrypt' => 'server',
156     'strategy' => 'incronly',
157     'comprate' => '0.25,0.75',
158     'exclude list' => '"foo" "bar"',
159     'exclude list append' => '"true" "star"',
160     'exclude file' => '"foolist"',
161     'include list' => '"bing" "ting"',
162     'include list append' => '"string" "fling"',
163     'include file optional' => '"rhyme"',
164     'property' => '"prop" "erty"',
165     'property' => '"DROP" "qwerty" "asdfg"',
166     'estimate' => 'server calcsize client'
167 ]);
168 $testconf->add_dumptype('second_dumptype', [ # note underscore
169     '' => 'mydump-type',
170     'comment' => '"refers to mydump-type with a dash"',
171 ]);
172 $testconf->add_dumptype('third_dumptype', [
173     '' => 'second_dumptype',
174     'comment' => '"refers to second_dumptype with an underscore"',
175 ]);
176 $testconf->add_interface('ethernet', [
177     'comment' => '"mine"',
178     'use' => '100',
179 ]);
180 $testconf->add_interface('nic', [
181     'comment' => '"empty"',
182 ]);
183 $testconf->add_holdingdisk('hd1', [
184     'comment' => '"mine"',
185     'directory' => '"/mnt/hd1"',
186     'use' => '100M',
187     'chunksize' => '1024k',
188 ]);
189 $testconf->add_holdingdisk('hd2', [
190     'comment' => '"empty"',
191 ]);
192 $testconf->add_application('my_app', [
193     'comment' => '"my_app_comment"',
194     'plugin' => '"amgtar"',
195 ]);
196 $testconf->add_script('my_script', [
197   'comment' => '"my_script_comment"',
198   'plugin' => '"script-email"',
199   'execute-on' => 'pre-host-backup, post-host-backup',
200   'execute-where' => 'client',
201   'property' => '"mailto" "amandabackup" "amanda"',
202 ]);
203 $testconf->add_device('my_device', [
204   'comment' => '"my device is mine, not yours"',
205   'tapedev' => '"tape:/dev/nst0"',
206   'device_property' => '"BLOCK_SIZE" "128k"',
207 ]);
208 $testconf->add_changer('my_changer', [
209   'comment' => '"my changer is mine, not yours"',
210   'tpchanger' => '"chg-foo"',
211   'changerdev' => '"/dev/sg0"',
212   'changerfile' => '"chg.state"',
213   'property' => '"testprop" "testval"',
214   'device_property' => '"testdprop" "testdval"',
215 ]);
216
217 $testconf->write();
218
219 $cfg_result = config_init($CONFIG_INIT_EXPLICIT_NAME, 'TESTCONF');
220 is($cfg_result, $CFGERR_OK,
221     "Load test configuration")
222     or diag_config_errors();
223
224 SKIP: {
225     skip "error loading config", 3 unless $cfg_result == $CFGERR_OK;
226
227     is(Amanda::Config::get_config_name(), "TESTCONF", 
228         "config_name set");
229     is(Amanda::Config::get_config_dir(), "$CONFIG_DIR/TESTCONF", 
230         "config_dir set");
231     is(Amanda::Config::get_config_filename(),
232         "$CONFIG_DIR/TESTCONF/amanda.conf", 
233         "config_filename set");
234 }
235
236 SKIP: { # global parameters
237     skip "error loading config", 13 unless $cfg_result == $CFGERR_OK;
238
239     is(getconf($CNF_RESERVE), 75,
240         "integer global confparm");
241     is(getconf($CNF_BUMPSIZE), $int64_num+0,
242         "int64 global confparm");
243     is(getconf($CNF_TAPEDEV), "/dev/foo",
244         "string global confparm");
245     is(getconf($CNF_DEVICE_OUTPUT_BUFFER_SIZE), $size_t_num+0,
246         "size global confparm");
247     ok(getconf($CNF_AUTOFLUSH),
248         "boolean global confparm");
249     is(getconf($CNF_USETIMESTAMPS), 0,
250         "boolean global confparm, passing an integer (0)");
251     is(getconf($CNF_TAPERALGO), $Amanda::Config::ALGO_LAST,
252         "taperalgo global confparam");
253     is_deeply([getconf($CNF_RESERVED_UDP_PORT)], [100,200],
254         "intrange global confparm");
255     is(getconf($CNF_DISPLAYUNIT), "M",
256         "displayunit is correctly uppercased");
257     is_deeply(getconf($CNF_DEVICE_PROPERTY),
258               { "foo" => { priority => 0, append => 0, values => ["bar"]},
259                 "blue" => { priority => 0, append => 0,
260                             values => ["car", "tar"]} },
261             "proplist global confparm");
262     is_deeply(getconf($CNF_AUTOLABEL),
263             { template => undef, other_config => '',
264               non_amanda => 1, volume_error => '', empty => 1 },
265             "'autolabel non-amanda empty' represented correctly");
266     ok(getconf_seen($CNF_TAPEDEV),
267         "'tapedev' parm was seen");
268     ok(!getconf_seen($CNF_CHANGERFILE),
269         "'changerfile' parm was not seen");
270 }
271
272 SKIP: { # derived values
273     skip "error loading config", 3 unless $cfg_result == $CFGERR_OK;
274
275     is(Amanda::Config::getconf_unit_divisor(), 1024, 
276         "correct unit divisor (from displayunit -> KB)");
277     ok($Amanda::Config::debug_auth, 
278         "debug_auth setting reflected in global variable");
279     ok(!$Amanda::Config::debug_amandad, 
280         "debug_amandad defaults to false");
281 }
282
283 SKIP: { # tapetypes
284     skip "error loading config", 6 unless $cfg_result == $CFGERR_OK;
285     my $ttyp = lookup_tapetype("mytapetype");
286     ok($ttyp, "found mytapetype");
287     is(tapetype_getconf($ttyp, $TAPETYPE_COMMENT), 'mine', 
288         "tapetype comment");
289     is(tapetype_getconf($ttyp, $TAPETYPE_LENGTH), 128 * 1024, 
290         "tapetype comment");
291
292     ok(tapetype_seen($ttyp, $TAPETYPE_COMMENT),
293         "tapetype comment was seen");
294     ok(!tapetype_seen($ttyp, $TAPETYPE_LBL_TEMPL),
295         "tapetype lbl_templ was not seen");
296
297     is_deeply([ sort(+getconf_list("tapetype")) ],
298               [ sort("mytapetype", "TEST-TAPE") ],
299         "getconf_list lists all tapetypes");
300 }
301
302 SKIP: { # dumptypes
303     skip "error loading config", 18 unless $cfg_result == $CFGERR_OK;
304
305     my $dtyp = lookup_dumptype("mydump-type");
306     ok($dtyp, "found mydump-type");
307     is(dumptype_getconf($dtyp, $DUMPTYPE_COMMENT), 'mine', 
308         "dumptype string");
309     is(dumptype_getconf($dtyp, $DUMPTYPE_PRIORITY), 2, 
310         "dumptype priority");
311     is(dumptype_getconf($dtyp, $DUMPTYPE_BUMPSIZE), $int64_num+0,
312         "dumptype size");
313     is(dumptype_getconf($dtyp, $DUMPTYPE_BUMPMULT), 1.75,
314         "dumptype real");
315     is(dumptype_getconf($dtyp, $DUMPTYPE_STARTTIME), 1829,
316         "dumptype time");
317     is(dumptype_getconf($dtyp, $DUMPTYPE_HOLDINGDISK), $HOLD_REQUIRED,
318         "dumptype holdingdisk");
319     is(dumptype_getconf($dtyp, $DUMPTYPE_COMPRESS), $COMP_BEST,
320         "dumptype compress");
321     is(dumptype_getconf($dtyp, $DUMPTYPE_ENCRYPT), $ENCRYPT_SERV_CUST,
322         "dumptype encrypt");
323     is(dumptype_getconf($dtyp, $DUMPTYPE_STRATEGY), $DS_INCRONLY,
324         "dumptype strategy");
325     is_deeply([dumptype_getconf($dtyp, $DUMPTYPE_COMPRATE)], [0.25, 0.75],
326         "dumptype comprate");
327     is_deeply(dumptype_getconf($dtyp, $DUMPTYPE_INCLUDE),
328         { 'file' => [ 'rhyme' ],
329           'list' => [ 'bing', 'ting', 'string', 'fling' ],
330           'optional' => 1 },
331         "dumptype include list");
332     is_deeply(dumptype_getconf($dtyp, $DUMPTYPE_EXCLUDE),
333         { 'file' => [ 'foolist' ],
334           'list' => [ 'foo', 'bar', 'true', 'star' ],
335           'optional' => 0 },
336         "dumptype exclude list");
337     is_deeply(dumptype_getconf($dtyp, $DUMPTYPE_ESTIMATELIST),
338               [ $ES_SERVER, $ES_CALCSIZE, $ES_CLIENT ],
339         "dumptype estimate list");
340     is_deeply(dumptype_getconf($dtyp, $DUMPTYPE_PROPERTY),
341               { "prop" => { priority => 0, append => 0, values => ["erty"]},
342                 "drop" => { priority => 0, append => 0,
343                             values => ["qwerty", "asdfg"] }},
344             "dumptype proplist");
345
346     ok(dumptype_seen($dtyp, $DUMPTYPE_EXCLUDE),
347         "'exclude' parm was seen");
348     ok(!dumptype_seen($dtyp, $DUMPTYPE_RECORD),
349         "'record' parm was not seen");
350
351     is_deeply([ sort(+getconf_list("dumptype")) ],
352               [ sort(qw(
353                 mydump-type second_dumptype third_dumptype 
354                 NO-COMPRESS COMPRESS-FAST COMPRESS-BEST COMPRESS-CUST
355                 SRVCOMPRESS BSD-AUTH NO-RECORD NO-HOLD
356                 NO-FULL
357                 )) ],
358         "getconf_list lists all dumptypes (including defaults)");
359 }
360
361 SKIP: { # interfaces
362     skip "error loading config", 8 unless $cfg_result == $CFGERR_OK;
363     my $iface = lookup_interface("ethernet");
364     ok($iface, "found ethernet");
365     is(interface_name($iface), "ethernet",
366         "interface knows its name");
367     is(interface_getconf($iface, $INTER_COMMENT), 'mine', 
368         "interface comment");
369     is(interface_getconf($iface, $INTER_MAXUSAGE), 100, 
370         "interface maxusage");
371
372     $iface = lookup_interface("nic");
373     ok($iface, "found nic");
374     ok(interface_seen($iface, $INTER_COMMENT),
375         "seen set for parameters that appeared");
376     ok(!interface_seen($iface, $INTER_MAXUSAGE),
377         "seen not set for parameters that did not appear");
378
379     is_deeply([ sort(+getconf_list("interface")) ],
380               [ sort('ethernet', 'nic', 'default') ],
381         "getconf_list lists all interfaces (in any order)");
382 }
383
384 SKIP: { # holdingdisks
385     skip "error loading config", 13 unless $cfg_result == $CFGERR_OK;
386     my $hdisk = lookup_holdingdisk("hd1");
387     ok($hdisk, "found hd1");
388     is(holdingdisk_name($hdisk), "hd1",
389         "hd1 knows its name");
390     is(holdingdisk_getconf($hdisk, $HOLDING_COMMENT), 'mine', 
391         "holdingdisk comment");
392     is(holdingdisk_getconf($hdisk, $HOLDING_DISKDIR), '/mnt/hd1',
393         "holdingdisk diskdir (directory)");
394     is(holdingdisk_getconf($hdisk, $HOLDING_DISKSIZE), 100*1024, 
395         "holdingdisk disksize (use)");
396     is(holdingdisk_getconf($hdisk, $HOLDING_CHUNKSIZE), 1024, 
397         "holdingdisk chunksize");
398
399     $hdisk = lookup_holdingdisk("hd2");
400     ok($hdisk, "found hd2");
401     ok(holdingdisk_seen($hdisk, $HOLDING_COMMENT),
402         "seen set for parameters that appeared");
403     ok(!holdingdisk_seen($hdisk, $HOLDING_CHUNKSIZE),
404         "seen not set for parameters that did not appear");
405
406     # only holdingdisks have this linked-list structure
407     # exposed
408     my $hdisklist = getconf($CNF_HOLDINGDISK);
409     my $first_disk = @$hdisklist[0];
410     $hdisk = lookup_holdingdisk($first_disk);
411     like(holdingdisk_name($hdisk), qr/hd[12]/,
412         "one disk is first in list of holdingdisks");
413     $hdisk = lookup_holdingdisk(@$hdisklist[1]);
414     like(holdingdisk_name($hdisk), qr/hd[12]/,
415         "another is second in list of holdingdisks");
416     ok($#$hdisklist == 1,
417         "no third holding disk");
418
419     is_deeply([ sort(+getconf_list("holdingdisk")) ],
420               [ sort('hd1', 'hd2') ],
421         "getconf_list lists all holdingdisks (in any order)");
422 }
423
424 SKIP: { # application
425     skip "error loading config", 5 unless $cfg_result == $CFGERR_OK;
426     my $app = lookup_application("my_app");
427     ok($app, "found my_app");
428     is(application_name($app), "my_app",
429         "my_app knows its name");
430     is(application_getconf($app, $APPLICATION_COMMENT), 'my_app_comment', 
431         "application comment");
432     is(application_getconf($app, $APPLICATION_PLUGIN), 'amgtar',
433         "application plugin (amgtar)");
434
435     is_deeply([ sort(+getconf_list("application-tool")) ],
436               [ sort("my_app") ],
437         "getconf_list lists all applications");
438     # test backward compatibility
439     is_deeply([ sort(+getconf_list("application")) ],
440               [ sort("my_app") ],
441         "getconf_list works for 'application-tool', too");
442 }
443
444 SKIP: { # script
445     skip "error loading config", 7 unless $cfg_result == $CFGERR_OK;
446     my $sc = lookup_pp_script("my_script");
447     ok($sc, "found my_script");
448     is(pp_script_name($sc), "my_script",
449         "my_script knows its name");
450     is(pp_script_getconf($sc, $PP_SCRIPT_COMMENT), 'my_script_comment', 
451         "script comment");
452     is(pp_script_getconf($sc, $PP_SCRIPT_PLUGIN), 'script-email',
453         "script plugin (script-email)");
454     is(pp_script_getconf($sc, $PP_SCRIPT_EXECUTE_WHERE), $ES_CLIENT,
455         "script execute_where (client)");
456     is(pp_script_getconf($sc, $PP_SCRIPT_EXECUTE_ON),
457         $EXECUTE_ON_PRE_HOST_BACKUP|$EXECUTE_ON_POST_HOST_BACKUP,
458         "script execute_on");
459
460     is_deeply([ sort(+getconf_list("script")) ],
461               [ sort("my_script") ],
462         "getconf_list lists all script");
463
464     is_deeply([ sort(+getconf_list("script-tool")) ],
465               [ sort("my_script") ],
466         "getconf_list works for 'script-tool', too");
467 }
468
469 SKIP: { # device
470     skip "error loading config", 6 unless $cfg_result == $CFGERR_OK;
471     my $dc = lookup_device_config("my_device");
472     ok($dc, "found my_device");
473     is(device_config_name($dc), "my_device",
474         "my_device knows its name");
475     is(device_config_getconf($dc, $DEVICE_CONFIG_COMMENT), 'my device is mine, not yours',
476         "device comment");
477     is(device_config_getconf($dc, $DEVICE_CONFIG_TAPEDEV), 'tape:/dev/nst0',
478         "device tapedev");
479     # TODO do we really need all of this equipment for device properties?
480     is_deeply(device_config_getconf($dc, $DEVICE_CONFIG_DEVICE_PROPERTY),
481           { "block_size" => { 'priority' => 0, 'values' => ["128k"], 'append' => 0 }, },
482         "device config proplist");
483
484     is_deeply([ sort(+getconf_list("device")) ],
485               [ sort("my_device") ],
486         "getconf_list lists all devices");
487 }
488
489 SKIP: { # changer
490     skip "error loading config", 7 unless $cfg_result == $CFGERR_OK;
491     my $dc = lookup_changer_config("my_changer");
492     ok($dc, "found my_changer");
493     is(changer_config_name($dc), "my_changer",
494         "my_changer knows its name");
495     is(changer_config_getconf($dc, $CHANGER_CONFIG_COMMENT), 'my changer is mine, not yours',
496         "changer comment");
497     is(changer_config_getconf($dc, $CHANGER_CONFIG_CHANGERDEV), '/dev/sg0',
498         "changer tapedev");
499     is_deeply(changer_config_getconf($dc, $CHANGER_CONFIG_PROPERTY),
500         { 'testprop' => {
501                 'priority' => 0,
502                 'values' => [ 'testval' ],
503                 'append' => 0,
504             }
505         }, "changer properties represented correctly");
506
507     is_deeply(changer_config_getconf($dc, $CHANGER_CONFIG_DEVICE_PROPERTY),
508         { 'testdprop' => {
509                 'priority' => 0,
510                 'values' => [ 'testdval' ],
511                 'append' => 0,
512             }
513         }, "changer device properties represented correctly");
514
515     is_deeply([ sort(+getconf_list("changer")) ],
516               [ sort("my_changer") ],
517         "getconf_list lists all changers");
518 }
519
520 ##
521 # Test config overwrites (using the config from above)
522
523 config_uninit();
524 $config_overrides = new_config_overrides(1); # note estimate is too small
525 add_config_override($config_overrides, "tapedev", "null:TEST");
526 add_config_override($config_overrides, "tpchanger", "chg-test");
527 add_config_override_opt($config_overrides, "org=KAOS");
528 set_config_overrides($config_overrides);
529 config_init($CONFIG_INIT_EXPLICIT_NAME, 'TESTCONF');
530
531 is(getconf($CNF_TAPEDEV), "null:TEST",
532     "config overwrites work with real config");
533 is(getconf($CNF_ORG), "KAOS",
534     "add_config_override_opt parsed correctly");
535
536 # introduce an error
537 config_uninit();
538 $config_overrides = new_config_overrides(1);
539 add_config_override($config_overrides, "bogusparam", "foo");
540 set_config_overrides($config_overrides);
541 config_init($CONFIG_INIT_EXPLICIT_NAME, 'TESTCONF');
542
543 my ($error_level, @errors) = Amanda::Config::config_errors();
544 is($error_level, $CFGERR_ERRORS, "bogus config overwrite flagged as an error");
545
546 ##
547 # Test configuration dumping
548
549 # (uses the config from the previous section)
550
551 # fork a child and capture its stdout
552 my $pid = open(my $kid, "-|");
553 die "Can't fork: $!" unless defined($pid);
554 if (!$pid) {
555     Amanda::Config::dump_configuration();
556     exit 1;
557 }
558 my $dump_first_line = <$kid>;
559 my $dump = join'', $dump_first_line, <$kid>;
560 close $kid;
561 waitpid $pid, 0;
562
563 my $fn = Amanda::Config::get_config_filename();
564 my $dump_filename = $dump_first_line;
565 chomp $dump_filename;
566 $dump_filename =~ s/^# AMANDA CONFIGURATION FROM FILE "//g;
567 $dump_filename =~ s/":$//g;
568 is($dump_filename, $fn, 
569     "config filename is included correctly");
570
571 like($dump, qr/DEVICE_PROPERTY\s+"foo" "bar"\n/i,
572     "DEVICE_PROPERTY appears in dump output");
573
574 like($dump, qr/AMRECOVER_CHECK_LABEL\s+(yes|no)/i,
575     "AMRECOVER_CHECK_LABEL has a trailing space");
576
577 like($dump, qr/AMRECOVER_CHECK_LABEL\s+(yes|no)/i,
578     "AMRECOVER_CHECK_LABEL has a trailing space");
579
580 like($dump, qr/EXCLUDE\s+LIST "foo" "bar" "true" "star"/i,
581     "EXCLUDE LIST is in the dump");
582 like($dump, qr/EXCLUDE\s+FILE "foolist"/i,
583     "EXCLUDE FILE is in the dump");
584 like($dump, qr/INCLUDE\s+LIST OPTIONAL "bing" "ting" "string" "fling"/i,
585     "INCLUDE LIST is in the dump");
586 like($dump, qr/INCLUDE\s+FILE OPTIONAL "rhyme"/i,
587     "INCLUDE FILE is in the dump");
588
589 ##
590 # Test nested definitions inside a dumptype
591
592 $testconf = Installcheck::Config->new();
593 $testconf->add_dumptype('nested_stuff', [
594     'comment' => '"contains a nested application, pp_script"',
595     'application' => '{
596         comment "my app"
597         plugin "amfun"
598 }',
599     'script' => '{
600         comment "my script"
601         plugin "ppfun"
602 }',
603 ]);
604
605 $testconf->write();
606
607 $cfg_result = config_init($CONFIG_INIT_EXPLICIT_NAME, "TESTCONF");
608 is($cfg_result, $CFGERR_OK, 
609     "parsing nested config loaded")
610     or diag_config_errors();
611 SKIP: {
612     skip "error loading config", 8 unless $cfg_result == $CFGERR_OK;
613
614     my $dtyp = lookup_dumptype("nested_stuff");
615     ok($dtyp, "found nested_stuff");
616
617     my $appname = dumptype_getconf($dtyp, $DUMPTYPE_APPLICATION);
618     like($appname, qr/^custom\(/,
619         "DUMPTYPE_APPLICATION is the generated name of an application subsection");
620
621     my $app = lookup_application($appname);
622     ok($app, ".. and that name leads to an application object");
623     is(application_getconf($app, $APPLICATION_COMMENT), "my app",
624         ".. that has the right comment");
625
626     my $sc = dumptype_getconf($dtyp, $DUMPTYPE_SCRIPTLIST);
627     ok(ref($sc) eq 'ARRAY' && @$sc == 1, "DUMPTYPE_SCRIPTLIST returns a 1-element list");
628     like($sc->[0], qr/^custom\(/,
629         ".. and the first element is the generated name of a script subsection");
630
631     $sc = lookup_pp_script($sc->[0]);
632     ok($sc, ".. and that name leads to a pp_script object");
633     is(pp_script_getconf($sc, $PP_SCRIPT_COMMENT), "my script",
634         ".. that has the right comment");
635 }
636
637 ##
638 # Explore a quirk of exinclude parsing.  Only the last
639 # exclude (or include) directive affects the 'optional' flag.
640 # We may want to change this, but we should do so intentionally.
641 # This is also tested by the 'amgetconf' installcheck.
642
643 $testconf = Installcheck::Config->new();
644 $testconf->add_dumptype('mydump-type', [
645     'exclude list' => '"foo" "bar"',
646     'exclude list optional append' => '"true" "star"',
647     'exclude list append' => '"true" "star"',
648 ]);
649 $testconf->write();
650
651 $cfg_result = config_init($CONFIG_INIT_EXPLICIT_NAME, "TESTCONF");
652 is($cfg_result, $CFGERR_OK, 
653     "first exinclude parsing config loaded")
654     or diag_config_errors();
655 SKIP: {
656     skip "error loading config", 2 unless $cfg_result == $CFGERR_OK;
657
658     my $dtyp = lookup_dumptype("mydump-type");
659     ok($dtyp, "found mydump-type");
660     is(dumptype_getconf($dtyp, $DUMPTYPE_EXCLUDE)->{'optional'}, 0,
661         "'optional' has no effect when not on the last occurrence");
662 }
663
664 ##
665 # Try an autolabel with a template and 'any'
666
667 $testconf = Installcheck::Config->new();
668 $testconf->add_param('autolabel', '"FOO%%%BAR" any');
669 $testconf->write();
670
671 $cfg_result = config_init($CONFIG_INIT_EXPLICIT_NAME, "TESTCONF");
672 is($cfg_result, $CFGERR_OK, 
673     "first exinclude parsing config loaded")
674     or diag_config_errors();
675 SKIP: {
676     skip "error loading config", 1 unless $cfg_result == $CFGERR_OK;
677     is_deeply(getconf($CNF_AUTOLABEL),
678             { template => "FOO%%%BAR", other_config => 1,
679               non_amanda => 1, volume_error => 1, empty => 1 },
680             "'autolabel \"FOO%%%BAR\" any' represented correctly");
681 }
682
683 ##
684 # Check out where quoting is and is not required.
685
686 $testconf = Installcheck::Config->new();
687
688 # make sure an unquoted tapetype is OK
689 $testconf->add_param('tapetype', 'TEST-TAPE'); # unquoted (Installcheck::Config uses quoted)
690
691 # strings can optionally be quoted
692 $testconf->add_param('dumporder', '"STSTST"');
693
694 # enumerations (e.g., taperalgo) must not be quoted; implicitly tested above
695
696 # definitions
697 $testconf->add_dumptype('"parent"', [ # note quotes
698     'bumpsize' => '10240',
699 ]);
700 $testconf->add_dumptype('child', [
701     '' => '"parent"', # note quotes
702 ]);
703 $testconf->add_dumptype('child2', [
704     '' => 'parent',
705 ]);
706 $testconf->write();
707
708 $cfg_result = config_init($CONFIG_INIT_EXPLICIT_NAME, "TESTCONF");
709 is($cfg_result, $CFGERR_OK,
710     "parsed config to test strings vs. identifiers")
711     or diag_config_errors();
712 SKIP: {
713     skip "error loading config", 3 unless $cfg_result == $CFGERR_OK;
714
715     my $dtyp = lookup_dumptype("parent");
716     ok($dtyp, "found parent");
717     $dtyp = lookup_dumptype("child");
718     ok($dtyp, "found child");
719     is(dumptype_getconf($dtyp, $DUMPTYPE_BUMPSIZE), 10240,
720         "child dumptype correctly inherited bumpsize");
721 }
722
723 ##
724 # Explore a quirk of read_int_or_str parsing.
725
726 $testconf = Installcheck::Config->new();
727 $testconf->add_dumptype('mydump-type1', [
728     'client_port' => '12345',
729 ]);
730 $testconf->add_dumptype('mydump-type2', [
731     'client_port' => '"newamanda"',
732 ]);
733 $testconf->add_dumptype('mydump-type3', [
734     'client_port' => '"67890"',
735 ]);
736 $testconf->write();
737
738 $cfg_result = config_init($CONFIG_INIT_EXPLICIT_NAME, "TESTCONF");
739 is($cfg_result, $CFGERR_OK, 
740     "read_int_or_str parsing config loaded")
741     or diag_config_errors();
742 SKIP: {
743     skip "error loading config", 6 unless $cfg_result == $CFGERR_OK;
744
745     my $dtyp = lookup_dumptype("mydump-type1");
746     ok($dtyp, "found mydump-type1");
747     is(dumptype_getconf($dtyp, $DUMPTYPE_CLIENT_PORT), "12345",
748         "client_port set to 12345");
749
750     $dtyp = lookup_dumptype("mydump-type2");
751     ok($dtyp, "found mydump-type1");
752     is(dumptype_getconf($dtyp, $DUMPTYPE_CLIENT_PORT), "newamanda",
753         "client_port set to \"newamanda\"");
754
755     $dtyp = lookup_dumptype("mydump-type3");
756     ok($dtyp, "found mydump-type1");
757     is(dumptype_getconf($dtyp, $DUMPTYPE_CLIENT_PORT), "67890",
758         "client_port set to \"67890\"");
759 }
760
761 ##
762 # Check property inheritance
763
764 $testconf = Installcheck::Config->new();
765 $testconf->add_application('app1', [
766     'property' => '"prop1" "val1"'
767 ]);
768 $testconf->add_application('app2', [
769     'property' => 'append "prop2" "val2"'
770 ]);
771 $testconf->add_application('app3', [
772     'property' => '"prop3" "val3"'
773 ]);
774 $testconf->add_application('app1a', [
775     'property' => '"prop4" "val4"',
776     'property' => '"prop1" "val1a"',
777     'app1' => undef
778 ]);
779 $testconf->add_application('app2a', [
780     'property' => '"prop5" "val5"',
781     'property' => '"prop2" "val2a"',
782     'app2' => undef
783 ]);
784 $testconf->add_application('app3a', [
785     'property' => '"prop6" "val6"',
786     'app3' => undef,
787     'property' => '"prop7" "val7"'
788 ]);
789 $testconf->add_application('app1b', [
790     'property' => '"prop4" "val4"',
791     'property' => '"prop1" "val1a"',
792     'app1' => undef,
793     'property' => '"prop1" "val1b"',
794 ]);
795 $testconf->add_application('app2b', [
796     'property' => '"prop5" "val5"',
797     'property' => '"prop2" "val2a"',
798     'app2' => undef,
799     'property' => 'append "prop2" "val2b"',
800 ]);
801 $testconf->write();
802
803 $cfg_result = config_init($CONFIG_INIT_EXPLICIT_NAME, "TESTCONF");
804 is($cfg_result, $CFGERR_OK, 
805     "application properties inheritance")
806     or diag_config_errors();
807 SKIP: {
808     skip "error loading config", 15 unless $cfg_result == $CFGERR_OK;
809
810     my $app = lookup_application("app1a");
811     ok($app, "found app1a");
812     is(application_name($app), "app1a",
813         "app1a knows its name");
814     my $prop = application_getconf($app, $APPLICATION_PROPERTY);
815     is_deeply($prop, { "prop4" => { priority => 0,
816                                     append   => 0,
817                                     values => [ "val4" ]},
818                        "prop1" => { priority => 0,
819                                     append   => 0,
820                                     values => [ "val1" ] }},
821     "PROPERTY parameter of app1a parsed correctly");
822
823     $app = lookup_application("app2a");
824     ok($app, "found app2a");
825     is(application_name($app), "app2a",
826         "app2a knows its name");
827     $prop = application_getconf($app, $APPLICATION_PROPERTY);
828     is_deeply($prop, { "prop5" => { priority => 0,
829                                     append   => 0,
830                                     values => [ "val5" ]},
831                        "prop2" => { priority => 0,
832                                     append   => 0,
833                                     values => [ "val2a", "val2" ] }},
834     "PROPERTY parameter of app2a parsed correctly");
835
836     $app = lookup_application("app3a");
837     ok($app, "found app3a");
838     is(application_name($app), "app3a",
839         "app3a knows its name");
840     $prop = application_getconf($app, $APPLICATION_PROPERTY);
841     is_deeply($prop, { "prop3" => { priority => 0,
842                                     append   => 0,
843                                     values => [ "val3" ]},
844                        "prop6" => { priority => 0,
845                                     append   => 0,
846                                     values => [ "val6" ] },
847                        "prop7" => { priority => 0,
848                                     append   => 0,
849                                     values => [ "val7" ] }},
850     "PROPERTY parameter of app3a parsed correctly");
851
852     $app = lookup_application("app1b");
853     ok($app, "found app1b");
854     is(application_name($app), "app1b",
855         "app1b knows its name");
856     $prop = application_getconf($app, $APPLICATION_PROPERTY);
857     is_deeply($prop, { "prop4" => { priority => 0,
858                                     append   => 0,
859                                     values => [ "val4" ]},
860                        "prop1" => { priority => 0,
861                                     append   => 0,
862                                     values => [ "val1b" ] }},
863     "PROPERTY parameter of app1b parsed correctly");
864
865     $app = lookup_application("app2b");
866     ok($app, "found app2b");
867     is(application_name($app), "app2b",
868         "app2b knows its name");
869     $prop = application_getconf($app, $APPLICATION_PROPERTY);
870     is_deeply($prop, { "prop5" => { priority => 0,
871                                     append   => 0,
872                                     values => [ "val5" ]},
873                        "prop2" => { priority => 0,
874                                     append   => 1,
875                                     values => [ "val2a", "val2", "val2b" ] }},
876     "PROPERTY parameter of app2b parsed correctly");
877 }
878
879
880 ##
881 # Check getconf_byname and getconf_byname_strs
882
883 $testconf = Installcheck::Config->new();
884 $testconf->add_param('tapedev', '"thats a funny name"');
885 $testconf->add_application('app1', [
886     'comment' => '"one"',
887 ]);
888 $testconf->add_script('scr1', [
889     'comment' => '"one"',
890 ]);
891 # check old names, too
892 $testconf->add_text(<<EOF);
893 define application-tool "app2" {
894     comment "two"
895 }
896 EOF
897 $testconf->add_text(<<EOF);
898 define script-tool "scr2" {
899     comment "two"
900 }
901 EOF
902 $testconf->write();
903
904 $cfg_result = config_init($CONFIG_INIT_EXPLICIT_NAME, "TESTCONF");
905 is($cfg_result, $CFGERR_OK,
906     "getconf_byname")
907     or diag_config_errors();
908 SKIP: {
909     skip "error loading config", 7 unless $cfg_result == $CFGERR_OK;
910
911     is(getconf_byname("Tapedev"), "thats a funny name",
912         "getconf_byname for global param");
913     is_deeply([ getconf_byname_strs("Tapedev", 1) ],
914         [ "\"thats a funny name\"" ],
915         "getconf_byname_strs for global param with quotes");
916     is_deeply([ getconf_byname_strs("Tapedev", 0) ],
917         [ "thats a funny name" ],
918         "getconf_byname_strs for global param without quotes");
919
920     # test * and *-tool (the old name)
921     is(getconf_byname("application-tool:app1:comment"), "one",
922         "getconf_byname for appplication-tool param");
923     is(getconf_byname("application:app2:comment"), "two",
924         "getconf_byname for application param");
925     is(getconf_byname("script-tool:scr1:comment"), "one",
926         "getconf_byname for appplication-tool param");
927     is(getconf_byname("script:scr2:comment"), "two",
928         "getconf_byname for script param");
929 }
930
931 my @boolean_vals = (
932     {'val' => '1', 'expected' => 1},
933     {'val' => '0', 'expected' => 0},
934     {'val' => 't', 'expected' => 1},
935     {'val' => 'true', 'expected' => 1},
936     {'val' => 'f', 'expected' => 0},
937     {'val' => 'false', 'expected' => 0},
938     {'val' => 'y', 'expected' => 1},
939     {'val' => 'yes', 'expected' => 1},
940     {'val' => 'n', 'expected' => 0},
941     {'val' => 'no', 'expected' => 0},
942     {'val' => 'on', 'expected' => 1},
943     {'val' => 'off', 'expected' => 0},
944     {'val' => 'oFf', 'expected' => 0},
945     {'val' => 'foo', 'expected' => undef},
946     );
947
948 for my $bv (@boolean_vals) {
949     is(string_to_boolean($bv->{'val'}), $bv->{'expected'},
950         "string_to_boolean('$bv->{'val'}') is right");
951 }