# -*-perl-*- # testscript for Config::General Classes by Thomas Linden # # needs to be invoked using the command "make test" from # the Config::General source directory. # # Under normal circumstances every test should succeed. use Data::Dumper; use Test::More tests => 78; #use Test::More qw(no_plan); # ahem, we deliver the test code with a local copy of # the Tie::IxHash module so we can do tests on sorted # hashes without dependency to Tie::IxHash. use lib qw(t); use Tie::IxHash; my @WARNINGS_FOUND; BEGIN { $SIG{__WARN__} = sub { diag( "WARN: ", join( '', @_ ) ); push @WARNINGS_FOUND, @_ }; } ### 1 BEGIN { use_ok "Config::General"}; require_ok( 'Config::General' ); ### 2 - 7 foreach my $num (2..7) { my $cfg = "t/cfg.$num"; open T, "<$cfg"; my @file = ; close T; my $fst = $file[0]; chomp $fst; $fst =~ s/\#\s*//g; eval { my $conf = new Config::General($cfg); my %hash = $conf->getall; }; ok(!$@, "$fst"); } ### 8 my $conf = new Config::General("t/cfg.8"); my %hash = $conf->getall; $conf->save_file("t/cfg.out"); my $copy = new Config::General("t/cfg.out"); my %copyhash = $copy->getall; is_deeply(\%hash, \%copyhash, "Writing Config Hash to disk and compare with original"); # 8a like($copyhash{nocomment}, qr/this should appear/, "C-comments not processed in here-doc"); ### 9 $conf = new Config::General( -ExtendedAccess => 1, -ConfigFile => "t/test.rc"); ok($conf, "Creating a new object from config file"); ### 10 my $conf2 = new Config::General( -ExtendedAccess => 1, -ConfigFile => "t/test.rc", -AllowMultiOptions => "yes" ); ok($conf2, "Creating a new object using the hash parameter way"); ### 11 my $domain = $conf->obj("domain"); ok($domain, "Creating a new object from a block"); ### 12 my $addr = $domain->obj("bar.de"); ok($addr, "Creating a new object from a sub block"); ### 13 my @keys = $conf->keys("domain"); ok($#keys > -1, "Getting values from the object"); ### 14 # test various OO methods my $a; if ($conf->is_hash("domain")) { my $domains = $conf->obj("domain"); foreach my $domain ($conf->keys("domain")) { my $domain_obj = $domains->obj($domain); foreach my $address ($domains->keys($domain)) { $a = $domain_obj->value($address); } } } ok($a, "Using keys() and values()"); ### 15 # test AUTOLOAD methods eval { my $conf3 = new Config::General( -ExtendedAccess => 1, -ConfigHash => { name => "Moser", prename => "Hannes"} ); my $n = $conf3->name; my $p = $conf3->prename; $conf3->name("Meier"); $conf3->prename("Max"); $conf3->save_file("t/test.cfg"); }; ok (!$@, "Using AUTOLOAD methods"); ### 16 # testing variable interpolation my $conf16 = new Config::General(-ConfigFile => "t/cfg.16", -InterPolateVars => 1, -StrictVars => 0); my %h16 = $conf16->getall(); if($h16{etc}->{log} eq "/usr/log/logfile" and $h16{etc}->{users}->{home} eq "/usr/home/max" and exists $h16{dir}->{teri}->{bl}) { pass("Testing variable interpolation"); } else { fail("Testing variable interpolation"); } ### 16.a # testing variable interpolation with %ENV use my $env = "/home/theunexistent"; $ENV{HOME} = $env; my $conf16a = new Config::General(-ConfigFile => "t/cfg.16a", -InterPolateVars => 1, -InterPolateEnv => 1, -StrictVars => 0); my %h16a = $conf16a->getall(); if($h16a{etc}->{log} eq "$env/log/logfile") { pass("Testing environment variable interpolation"); } else { fail("Testing environment variable interpolation"); } ### 17 # testing value pre-setting using a hash my $conf17 = new Config::General( -file => "t/cfg.17", -DefaultConfig => { home => "/exports/home", logs => "/var/backlog", foo => { bar => "quux" } }, -InterPolateVars => 1, -MergeDuplicateOptions => 1, -MergeDuplicateBlocks => 1 ); my %h17 = $conf17->getall(); ok ($h17{home} eq "/home/users" && $h17{foo}{quux} eq "quux", "Testing value pre-setting using a hash"); ### 18 # testing value pre-setting using a string my $conf18 = new Config::General( -file => "t/cfg.17", # reuse the file -DefaultConfig => "home = /exports/home\nlogs = /var/backlog", -MergeDuplicateOptions => 1, -MergeDuplicateBlocks => 1 ); my %h18 = $conf18->getall(); ok ($h18{home} eq "/home/users", "Testing value pre-setting using a string"); ### 19 # testing various otion/value assignment notations my $conf19 = new Config::General(-file => "t/cfg.19"); my %h19 = $conf19->getall(); my $works = 1; foreach my $key (keys %h19) { if ($key =~ /\s/) { $works = 0; } } ok ($works, "Testing various otion/value assignment notations"); ### 20 # testing files() method my $conf20 = Config::General->new( -file => "t/cfg.20.a", -MergeDuplicateOptions => 1 ); my %h20 = $conf20->getall(); my %files = map { $_ => 1 } $conf20->files(); my %expected_files = map { $_ => 1 } ( 't/cfg.20.a', 't/cfg.20.b', 't/cfg.20.c', ); is_deeply (\%files, \%expected_files, "testing files() method"); ### 22 # testing improved IncludeRelative option # First try without -IncludeRelative # this should fail eval { my $conf21 = Config::General->new( -file => "t/sub1/sub2/sub3/cfg.sub3", -MergeDuplicateOptions => 1, ); }; ok ($@, "prevented from loading relative cfgs without -IncludeRelative"); ### 23 # Now try with -IncludeRelative # this should fail my $conf22 = Config::General->new( -file => "t/sub1/sub2/sub3/cfg.sub3", -MergeDuplicateOptions => 1, -IncludeRelative => 1, ); my %h22 = $conf22->getall; my %expected_h22 = ( 'sub3_seen' => 'yup', 'sub2_seen' => 'yup', 'sub2b_seen' => 'yup', 'sub1_seen' => 'yup', 'sub1b_seen' => 'yup', 'fruit' => 'mango', ); is_deeply(\%h22, \%expected_h22, "loaded relative to included files"); ### 24 # Testing IncludeDirectories option my $conf23 = Config::General->new( -String => "<>", -IncludeDirectories => 1 ); my %h23 = $conf23->getall; my %expected_h23 = ( fruit => 'mango', sub1_seen => 'yup', sub1b_seen => 'yup', test => 'value', test2 => 'value2', test3 => 'value3' ); is_deeply(\%h23, \%expected_h23, "including a directory with -IncludeDirectories"); ### 24 # Testing IncludeGlob option my $conf24 = Config::General->new( -String => "<>", -IncludeGlob => 1 ); my %h24 = $conf24->getall; my %expected_h24 = ( test => 'value', test2 => 'value2', test3 => 'value3' ); is_deeply(\%h24, \%expected_h24, "including multiple files via glob pattern with -IncludeGlob"); ### 25 # Testing block and block name quoting my $conf25 = Config::General->new( -String => < opt1 val1 <"block2 /"> opt2 val2 <"block 3" "/"> opt3 val3 opt4 val4 TEST -SlashIsDirectory => 1 ); my %h25 = $conf25->getall; my %expected_h25 = ( block => { '/' => { opt1 => 'val1' } }, 'block2 /' => { opt2 => 'val2' }, 'block 3' => { '/' => { opt3 => 'val3' } }, block4 => { '/' => { opt4 => 'val4' } } ); is_deeply(\%h25, \%expected_h25, "block and block name quoting"); ### 26 # Testing 0-value handling my $conf26 = Config::General->new( -String => < 0 TEST ); my %h26 = $conf26->getall; my %expected_h26 = ( foo => { 0 => { 0 => undef } }, ); is_deeply(\%h26, \%expected_h26, "testing 0-values in block names"); # # look if invalid input gets rejected right # ### 27 # testing invalid parameter calls, expected to fail my @pt = ( { p => {-ConfigHash => "StringNotHash"}, t => "-ConfigHash HASH required" }, { p => {-String => {}}, t => "-String STRING required" }, { p => {-ConfigFile => {}}, t => "-ConfigFile STRING required" }, { p => {-ConfigFile => "NoFile"}, t => "-ConfigFile STRING File must exist and be readable" } ); foreach my $C (@pt) { eval { my $cfg = new Config::General(%{$C->{p}}); }; ok ($@, "check parameter failure handling $C->{t}"); } ### 32 # check Flagbits my $cfg28 = new Config::General( -String => "Mode = CLEAR | UNSECURE", -FlagBits => { Mode => { CLEAR => 1, STRONG => 1, UNSECURE => "32bit" } } ); my %cfg28 = $cfg28->getall(); is_deeply(\%cfg28, { 'Mode' => { 'STRONG' => undef, 'UNSECURE' => '32bit', 'CLEAR' => 1 }}, "Checking -Flagbits resolving"); ### 33 # checking functional interface eval { my %conf = Config::General::ParseConfig(-ConfigFile => "t/test.rc"); Config::General::SaveConfig("t/test.rc.out", \%conf); my %next = Config::General::ParseConfig(-ConfigFile => "t/test.rc.out"); my @a = sort keys %conf; my @b = sort keys %next; if (@a != @b) { die "Re-parsed result differs from original"; } }; ok(! $@, "Testing functional interface $@"); ### 34 # testing -AutoTrue my $cfg34 = new Config::General(-AutoTrue => 1, -ConfigFile => "t/cfg.34"); my %cfg34 = $cfg34->getall(); my %expect34 = ( 'a' => { 'var6' => 0, 'var3' => 1, 'var1' => 1, 'var4' => 0, 'var2' => 1, 'var5' => 0 }, 'b' => { 'var6' => 0, 'var3' => 1, 'var1' => 1, 'var4' => 0, 'var2' => 1, 'var5' => 0 } ); is_deeply(\%cfg34, \%expect34, "Using -AutoTrue"); ### 35 # testing -SplitPolicy my %conf35 = Config::General::ParseConfig( -String => qq(var1 :: alpha var2 :: beta var3 = gamma # use wrong delimiter by purpose), -SplitPolicy => 'custom', -SplitDelimiter => '\s*::\s*' ); my %expect35 = ( 'var3 = gamma' => undef, 'var1' => 'alpha', 'var2' => 'beta' ); is_deeply(\%conf35, \%expect35, "Using -SplitPolicy and custom -SplitDelimiter"); ### Include both my $conf36 = Config::General->new( -ConfigFile => "t/dual-include.conf", -IncludeAgain => 1 ); my %C36 = $conf36->getall; is_deeply( \%C36, { bit => { one => { honk=>'bonk' }, two => { honk=>'bonk' } } }, "Included twice" ); ### Include once { my @expected_warning; local $SIG{__WARN__} = sub { push @expected_warning, @_}; my $conf37 = Config::General->new( "t/dual-include.conf" ); my %C37 = $conf37->getall; is_deeply( \%C37, { bit => { one => { honk=>'bonk' }, two => {} } }, "Included once-only" ); is( @expected_warning, 1, "1 Expected warning" ); like( $expected_warning[0], qr/File .* already loaded. Use -IncludeAgain to load it again./ms, "Warns about a file already being loaded" ); } ### apache-style Include my $conf38 = Config::General->new( -ConfigFile => "t/apache-include.conf", -IncludeAgain => 1, -UseApacheInclude => 1 ); my %C38 = $conf38->getall; is_deeply( \%C38, { bit => { one => { honk=>'bonk' }, two => { honk=>'bonk' } } }, "Apache-style include" ); # verify fix for rt#107108, test support for IncludeOptional my $conf38n = Config::General->new( -ConfigFile => "t/apache-include-opt.conf", -IncludeAgain => 1, -IncludeGlob => 1, -UseApacheInclude => 1 ); my %C38n = $conf38n->getall; is_deeply( \%C38n, { bit => { one => { nink=>'ack' }, two => { honk=>'bonk' } } }, "Apache-style IncludeOptional" ); #### 39 verifies bug rt#27225 # testing variable scope. # a variable shall resolve to the value defined in the current # scope, not a previous outer scope. my $conf39 = new Config::General(-ConfigFile => "t/cfg.39", -InterPolateVars => 1, -StrictVars => 0); my %conf39 = $conf39->getall(); isnt($conf39{outer}->{b1}->{inner}->{ivar}, $conf39{outer}->{b2}->{inner}->{ivar}, "Variable scope test"); ### 40 - 42 verify if structural error checks are working foreach my $pos (40 .. 43) { eval { my $conf = new Config::General(-ConfigFile => "t/cfg.$pos"); }; ok($@ =~ /^Config::General/, "$pos: Structural error checks"); } my $conf44; eval { $conf44 = new Config::General(-String => [ 'foo bar' ]); }; ok(! $@, "-String arrayref"); is_deeply({ $conf44->getall }, { foo => 'bar' }, "-String arrayref contents"); # verifies bug rt#35122 my $conf45 = new Config::General(-ConfigFile => "t/cfg.45", -InterPolateVars => 1, -StrictVars => 0); my %conf45 = $conf45->getall(); my $expect45 = { 'block1' => { 'param5' => 'value3', 'param4' => 'value1', 'param2' => 'value3' }, 'block2' => { 'param7' => 'value2', 'param6' => 'value1' }, 'param2' => 'value2', 'param1' => 'value1' }; is_deeply($expect45, \%conf45, "Variable precedence"); # verifies bug rt#35766 my $conf46 = new Config::General(-ConfigFile => "t/cfg.46", -InterPolateVars => 1, -StrictVars => 0); my %conf46 = $conf46->getall(); my $expect46 = { 'blah' => 'blubber', 'test' => 'bar \'variable $blah should be kept\' and \'$foo too\'', 'foo' => 'bar' }; is_deeply($expect46, \%conf46, "Variables inside single quotes"); # complexity test # check the combination of various features my $conf47 = new Config::General( -ConfigFile => "t/complex.cfg", -InterPolateVars => 1, -DefaultConfig => { this => "that", default => "imported" }, -MergeDuplicateBlocks => 1, -MergeDuplicateOptions => 1, -StrictVars => 1, -SplitPolicy => 'custom', -SplitDelimiter => '\s*=\s*', -IncludeGlob => 1, -IncludeAgain => 1, -IncludeRelative => 1, -AutoTrue => 1, -FlagBits => { someflags => { LOCK => 1, RW => 2, TAINT => 3 } }, -StoreDelimiter => ' = ', -SlashIsDirectory => 1, -SaveSorted => 1 ); my %conf47 = $conf47->getall(); my $expect47 = { 'var3' => 'blah', 'z1' => { 'blak' => '11111', 'nando' => '9999' }, 'a' => { 'b' => { 'm' => { '9323' => { 'g' => '000', 'long' => 'another long line' } }, 'x' => '9323', 'z' => 'rewe' } }, 'onflag' => 1, 'var2' => 'zeppelin', 'ignore' => '$set', # escaped $ should get to plain $, not \\$! 'quote' => 'this should be \'kept: $set\' and not be \'$set!\'', 'x5' => { 'klack' => '11111' }, 'set' => 'blah', 'line' => 'along line', 'this' => 'that', 'imported' => 'got that from imported config', 'someflags' => { 'RW' => 2, 'LOCK' => 1, 'TAINT' => 3 }, 'var1' => 'zero', 'offflag' => 0, 'cmd' => 'mart@gw.intx.foo:22', 'default' => 'imported', 'host' => 'gw.intx.foo', 'nando' => '11111', 'auch ätzendes' => 'muss gehen', 'Directory' => { '/' => { 'mode' => '755' } }, 'hansa' => { 'z1' => { 'blak' => '11111', 'nando' => '9999' }, 'Directory' => { '/' => { 'mode' => '755' } }, 'block' => { '0' => { 'value' => 0 } }, 'x5' => { 'klack' => '11111' }, 'Files' => { '~/*.pl' => { 'Options' => '+Indexes' } }, 'nando' => '11111' }, 'block' => { '0' => { 'value' => 0 } }, 'Files' => { '~/*.pl' => { 'Options' => '+Indexes' } }, 'a [[weird]] heredoc' => 'has to work too!' }; #scip is_deeply($expect47, \%conf47, "complexity test"); # check if sorted save works $conf47->save_file("t/complex.out", \%conf47); open T, "; close T; my $sorted = qq( imported = got that from imported config line = along line nando = 11111 offflag = 0 onflag = 1); if ($got47 =~ /\Q$sorted\E/) { pass("Testing sorted save"); } else { fail("Testing sorted save"); } tie my %hash48, "Tie::IxHash"; my $ostr48 = "zeppelin 1 beach 2 anathem 3 mercury 4\n"; my $cfg48 = new Config::General( -String => $ostr48, -Tie => "Tie::IxHash" ); %hash48 = $cfg48->getall(); my $str48 = $cfg48->save_string(\%hash48); is( $str48, $ostr48, "tied hash test"); # check for undef and -w { my $ostr49 = "foo\n"; local $^W = 1; my $cfg49 = new Config::General( -String => $ostr49 ); my %hash49 = $cfg49->getall(); ok( exists $hash49{foo}, "value for undefined key found"); is( $hash49{foo}, undef, "value returned as expected - undef"); # repeat with interpolation turned on $cfg49 = new Config::General( -String => $ostr49, -InterPolateVars => 1 ); %hash49 = $cfg49->getall(); ok( exists $hash49{foo}, "value for undefined key found"); is( $hash49{foo}, undef, "value returned as expected - undef"); $^W = 0; } # verifies bug fix rt#54580 # Test handling of values containing *many* single-quoted strings # when -InterPolateVars option is set my $dupcount50 = 2000; my $ostr50; foreach my $counter ( reverse 1 .. $dupcount50 ) { $ostr50 .= " 'luck${counter}'"; } $ostr50 =~ s{\A }{}; my $cfgsrc50 = 'test_single_many ' . $ostr50; $cfg50 = new Config::General( -String => $cfgsrc50, -InterPolateVars => 1 ); %hash50 = $cfg50->getall(); is($hash50{test_single_many}, $ostr50, "value with single-quote strings is as expected" ); # check for escaped chars my $cfg51 = new Config::General( -ConfigFile => "t/cfg.51" ); my %hash51 = $cfg51->getall(); is($hash51{dollar}, '$foo', "keep escaped dollar character"); is($hash51{backslash}, 'contains \ backslash', "keep escaped backslash character"); is($hash51{prize}, '18 $', "keep un-escaped dollar character"); is($hash51{hostparam}, q("'wsh.dir'"), "keep escaped quote character"); is($hash51{bgcolor}, '#fff', "keep escaped number sign"); # now save it to a file and re-read it in and see if everything remains escaped $cfg51->save_file("t/cfg.51.out"); $cfg51 = new Config::General( -ConfigFile => "t/cfg.51.out", -InterPolateVars => 1 ); my %hash51new = $cfg51->getall(); is_deeply(\%hash51, \%hash51new, "compare saved config containing escaped chars"); # check if forced single value arrays remain my $cfg52 = new Config::General( -String => "habeas = [ corpus ]", -ForceArray => 1); my %hash52 = $cfg52->getall(); my @array52 = qw(corpus); is_deeply($hash52{habeas}, \@array52, "check -ForceArray single value arrays"); $cfg52->save_file("t/cfg.52.out"); $cfg52 = new Config::General( -ConfigFile => "t/cfg.52.out", -ForceArray => 1); my %hash52new = $cfg52->getall(); is_deeply(\%hash52new, \%hash52, "check -ForceArray single value arrays during save()"); my $cfg53 = new Config::General(-AllowSingleQuoteInterpolation => 1, -String => "got = 1\nhave = '\$got'", -InterPolateVars => 1 ); my %hash53 = $cfg53->getall(); is($hash53{have}, "'1'", "check -AllowSingleQuoteInterpolation"); # Make sure no warnings were seen during the test. ok( !@WARNINGS_FOUND, "No unexpected warnings seen" ); # check if disabling escape chars does work my $cfg54 = new Config::General(-NoEscape => 1, -String => qq(val = \\\$notavar:\\blah\n)); my %hash54 = $cfg54->getall(); is($hash54{val}, qq(\\\$notavar:\\blah), "check -NoEscape"); # check for line continuation followed by empty line (rt.cpan.org#39814) my $cfg55 = new Config::General( -ConfigFile => "t/cfg.55" ); my %hash55 = $cfg55->getall(); is($hash55{b}, "nochop", "check continuation followed by empty line"); my $cfg56 = Config::General->new(); eval { $cfg56->save_file("t/56.out", { "new\nline" => 9, "brack 8 }); }; ok($@, "catch special chars in keys"); # UTF8[BOM] tests my $cfg57 = "t/utf8_bom/foo.cfg"; my $expected57 = {foo => {"\x{e9}" => "\x{e8}", bar => {"\x{f4}" => "\x{ee}"}}}; for my $bool (0, 1) { my $conf = Config::General->new(-ConfigFile => $cfg57, -IncludeRelative => 1, -UTF8 => $bool); my %hash = $conf->getall; is_deeply \%hash, $expected57, "-UTF8 => $bool"; } # IFDEF tests my $cfg58 = "t/cfg.58"; my $expected58 = { level => "debug" }; my %defs = ( scalar => 'TEST', array => ['TEST'], hash => {'TEST' => 1} ); foreach my $def (keys %defs) { my $conf = Config::General->new(-ConfigFile => $cfg58, -UseApacheIfDefine => 1, -Define => $defs{$def}); my %hash = $conf->getall(); is_deeply \%hash, $expected58, "UseApacheIfDefine, -Define => $def"; }