diff --git a/configure.ac b/configure.ac index ab9ef75..75edda5 100644 --- a/configure.ac +++ b/configure.ac @@ -78,6 +78,7 @@ m4_foreach_w([_m], [ File::Spec::Functions File::Temp List::Util + re ], [AX_PROG_PERL_MODULES([_m], [], [AC_MSG_WARN([some tests will fail due to missing module _m])])]) diff --git a/ddclient.in b/ddclient.in index a645032..4a2c91b 100755 --- a/ddclient.in +++ b/ddclient.in @@ -597,7 +597,13 @@ our %variables = ( 'password' => setv(T_PASSWD,1, 0, undef, undef), 'host' => setv(T_STRING,1, 1, undef, undef), - 'use' => setv(T_USE, 0, 0, 'ip', undef), + 'use' => setv(T_USE, 0, 0, sub { + my ($h) = @_; + return "'disabled' if '--usev4' or '--usev6' is enabled, otherwise 'ip'" + if ($h // '') eq ''; + return 'disabled' if opt('usev4', $h) ne 'disabled' || opt('usev6', $h) ne 'disabled'; + return 'ip'; + }, undef), 'usev4' => setv(T_USEV4, 0, 0, 'disabled', undef), 'usev6' => setv(T_USEV6, 0, 0, 'disabled', undef), 'if' => setv(T_IF, 0, 0, 'ppp0', undef), @@ -1092,7 +1098,7 @@ $variables{'merged'} = { }; # This will hold the processed args. -my %opt = (); +our %opt; my $deprecated_handler = sub { warning("'-$_[0]' is deprecated and does nothing"); }; $opt{'fw-banlocal'} = $deprecated_handler; $opt{'if-skip'} = $deprecated_handler; @@ -1937,7 +1943,7 @@ sub usage { for (@_) { if (ref $_) { my ($key, $specifier, $arg_usage) = @$_; - my $value = default($key); + my $value = default($key, ''); next unless $arg_usage; $usage .= " $arg_usage"; if (defined($value) && $value ne '') { @@ -2220,15 +2226,17 @@ sub split_by_comma { } sub default { my ($v, $h) = @_; + my $var; if (defined($h) && $config{$h}) { my $proto = $protocols{opt('protocol', $v eq 'protocol' ? undef : $h)}; - my $var = $proto->{variables}{$v} if $proto; - return $var->{default} if $var; + $var = $proto->{variables}{$v} if $proto; } - return undef if !defined($variables{'merged'}{$v}); # TODO: This might grab an arbitrary protocol-specific variable definition, which could cause # surprising behavior. - return $variables{'merged'}{$v}{'default'}; + $var //= $variables{'merged'}{$v}; + return undef if !defined($var); + return $var->{'default'}($h) if ref($var->{default}) eq 'CODE'; + return $var->{'default'}; } sub opt { my $v = shift; diff --git a/t/update_nics.pl b/t/update_nics.pl index e5d0ee0..5b48061 100644 --- a/t/update_nics.pl +++ b/t/update_nics.pl @@ -70,7 +70,6 @@ my @test_cases = ( desc => "legacy, fresh, $desc", cfg => { 'protocol' => 'legacy', - 'use' => 'disabled', %cfg, }, want_update => 1, @@ -136,7 +135,6 @@ my @test_cases = ( ipv6 => 1, cfg => { 'protocol' => 'legacy', - 'use' => 'disabled', 'usev6' => 'webv6', }, want_update => 1, @@ -168,7 +166,6 @@ my @test_cases = ( ipv6 => 1, cfg => { 'protocol' => 'legacy', - 'use' => 'disabled', 'usev4' => 'webv4', 'usev6' => 'webv6', }, @@ -209,7 +206,6 @@ my @test_cases = ( }, cfg => { 'protocol' => 'legacy', - 'use' => 'disabled', %cfg, }, want_cfg_changes => { @@ -238,7 +234,6 @@ my @test_cases = ( }, cfg => { 'protocol' => 'legacy', - 'use' => 'disabled', %cfg, }, want_cfg_changes => { @@ -267,7 +262,6 @@ my @test_cases = ( }, cfg => { 'protocol' => 'legacy', - 'use' => 'disabled', %cfg, }, want_recap_changes => { @@ -299,7 +293,6 @@ my @test_cases = ( }, cfg => { 'protocol' => 'legacy', - 'use' => 'disabled', %cfg, }, want_update => 1, @@ -345,7 +338,6 @@ my @test_cases = ( }, cfg => { 'protocol' => 'legacy', - 'use' => 'disabled', %cfg, }, want_recap_changes => { @@ -374,7 +366,6 @@ my @test_cases = ( }, cfg => { 'protocol' => 'legacy', - 'use' => 'disabled', %cfg, }, want_update => 1, diff --git a/t/variable_defaults.pl b/t/variable_defaults.pl index b1ac203..8d82347 100644 --- a/t/variable_defaults.pl +++ b/t/variable_defaults.pl @@ -1,4 +1,5 @@ use Test::More; +use re qw(is_regexp); SKIP: { eval { require Test::Warnings; } or skip($@, 1); } eval { require 'ddclient'; } or BAIL_OUT($@); @@ -23,14 +24,78 @@ for my $tc (@test_cases) { if ($tc->{def}{required}) { is($tc->{def}{default}, undef, "'$tc->{desc}' (required) has no default"); } else { - local %ddclient::variables = (merged => {var => $tc->{def}}); + # Preserve all existing variables in $variables{merged} so that variables with dynamic + # defaults can reference them. + local %ddclient::variables = (merged => { + %{$ddclient::variables{merged}}, + 'var for test' => $tc->{def}, + }); + # Variables with dynamic defaults will need their own unit tests, but we can still check the + # clean-slate hostless default. + local %ddclient::config; + local %ddclient::opt; + local %ddclient::globals; my $norm; - my $default = ddclient::default('var'); + my $default = ddclient::default('var for test'); diag("'$tc->{desc}' default: " . ($default // '')); - is($default, $tc->{def}{default}, "'$tc->{desc}' default() return value matches default"); + is($default, $tc->{def}{default}, "'$tc->{desc}' default() return value matches default") + if ref($tc->{def}{default}) ne 'CODE'; my $valid = eval { $norm = ddclient::check_value($default, $tc->{def}); 1; } or diag($@); ok($valid, "'$tc->{desc}' (optional) has a valid default"); is($norm, $default, "'$tc->{desc}' default normalizes to itself") if $valid; } } + +my @use_test_cases = ( + { + desc => 'clean slate hostless default', + want => 'ip', + }, + { + desc => 'usage string', + host => '', + want => qr/disabled.*ip|ip.*disabled/, + }, + { + desc => 'usev4 disables use by default', + host => 'host', + cfg => {usev4 => 'webv4'}, + want => 'disabled', + }, + { + desc => 'usev6 disables use by default', + host => 'host', + cfg => {usev4 => 'webv4'}, + want => 'disabled', + }, + { + desc => 'explicitly setting use re-enables it', + host => 'host', + cfg => {use => 'web', usev4 => 'webv4'}, + want => 'web', + }, +); +for my $tc (@use_test_cases) { + my $desc = "'use' dynamic default: $tc->{desc}"; + local %ddclient::protocols = + (protocol => {variables => $ddclient::variables{'protocol-common-defaults'}}); + local %ddclient::variables = (merged => { + 'protocol' => $ddclient::variables{'merged'}{'protocol'}, + 'use' => $ddclient::variables{'protocol-common-defaults'}{'use'}, + 'usev4' => $ddclient::variables{'merged'}{'usev4'}, + 'usev6' => $ddclient::variables{'merged'}{'usev6'}, + }); + local %ddclient::config = (host => {protocol => 'protocol', %{$tc->{cfg} // {}}}); + local %ddclient::opt; + local %ddclient::globals; + + my $got = ddclient::opt('use', $tc->{host}); + + if (is_regexp($tc->{want})) { + like($got, $tc->{want}, $desc); + } else { + is($got, $tc->{want}, $desc); + } +} + done_testing();