Merge pull request #161 from rhansen/test-framework

Build system and unit test infrastructure
This commit is contained in:
Richard Hansen 2020-06-29 11:09:12 -04:00 committed by GitHub
commit f360860378
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 1399 additions and 115 deletions

16
.gitignore vendored
View file

@ -3,3 +3,19 @@ release
.svn .svn
.cvsignore .cvsignore
*~ *~
/Makefile
/Makefile.in
/aclocal.m4
/autom4te.cache/
/build-aux/install-sh
/build-aux/missing
/config.log
/config.status
/configure
/ddclient
/ddclient-*.tar.gz
/ddclient.conf
/t/*.log
/t/*.trs
/t/version.pl
/test-suite.log

View file

@ -46,6 +46,44 @@ If you are not very comfortable with Git, we encourage you to read
[Pro Git](https://git-scm.com/book) by Scott Chacon and Ben Straub [Pro Git](https://git-scm.com/book) by Scott Chacon and Ben Straub
(freely available online). (freely available online).
## Unit tests
Always add tests for your changes when feasible.
To run the ddclient test suite:
1. Install GNU Autoconf and Automake
2. Run: `./autogen && ./configure && make check`
To add a new test script:
1. Create a new `t/*.pl` file with contents like this:
```perl
use Test::More;
eval { require 'ddclient'; ddclient->import(); 1; } or die($@);
# Your tests go here.
done_testing();
```
See the documentation for
[Test::More](https://perldoc.perl.org/Test/More.html) for
details.
2. Add your script to the `handwritten_tests` variable in
`Makefile.am`.
3. If your test script requires 3rd party modules, add the modules
to the list of test modules in `configure.ac` and re-run
`./autogen && ./configure`. Be sure to skip the tests if the
module is not available. For example:
```perl
eval { require Foo::Bar; Foo::Bar->import(); 1 } or plan(skip_all => $@);
```
## Compatibility ## Compatibility
We strive to make ddclient usable on a wide variety of platforms. We strive to make ddclient usable on a wide variety of platforms.
@ -64,9 +102,14 @@ on the following platforms:
See https://pkgs.org for available modules and versions. See https://pkgs.org for available modules and versions.
Exception: You may depend on modern language features or modules for Exceptions:
new functionality when no feasible alternative exists, as long as the * You may depend on modern language features or modules for new
new dependency does not break existing functionality on old plaforms. functionality when no feasible alternative exists, as long as the
new dependency does not break existing functionality on old
plaforms.
* Test scripts may depend on arbitrary modules as long as the tests
are skipped if the modules are not available. Effort should be
taken to only use modules that are broadly available.
All shell scripts should conform with [POSIX Issue 7 (2018 All shell scripts should conform with [POSIX Issue 7 (2018
edition)](https://pubs.opengroup.org/onlinepubs/9699919799/) or later. edition)](https://pubs.opengroup.org/onlinepubs/9699919799/) or later.

View file

@ -9,6 +9,10 @@ repository history](https://github.com/ddclient/ddclient/commits/master).
* Added support for OVH DynHost. * Added support for OVH DynHost.
* Added support for ClouDNS. * Added support for ClouDNS.
* Added a build system to make it easier for distributions to package
ddclient:
./autogen && ./configure && make && make check && make install
### Bug fixes ### Bug fixes

72
Makefile.am Normal file
View file

@ -0,0 +1,72 @@
ACLOCAL_AMFLAGS = -I m4
EXTRA_DIST = \
CONTRIBUTING.md \
COPYING \
COPYRIGHT \
ChangeLog.md \
README.cisco \
README.md \
README.ssl \
TODO \
UPGRADE \
autogen \
sample-ddclient-wrapper.sh \
sample-etc_cron.d_ddclient \
sample-etc_dhclient-exit-hooks \
sample-etc_dhcpc_dhcpcd-eth0.exe \
sample-etc_ppp_ip-up.local \
sample-etc_rc.d_init.d_ddclient \
sample-etc_rc.d_init.d_ddclient.alpine \
sample-etc_rc.d_init.d_ddclient.lsb \
sample-etc_rc.d_init.d_ddclient.redhat \
sample-etc_rc.d_init.d_ddclient.ubuntu \
sample-etc_systemd.service \
sample-get-ip-from-fritzbox
CLEANFILES =
# Command that replaces substitution variables with their values.
subst = sed \
-e 's|@PACKAGE_VERSION[@]|$(PACKAGE_VERSION)|g' \
-e 's|@PERL[@]|$(PERL)|g' \
-e 's|@localstatedir[@]|$(localstatedir)|g' \
-e 's|@runstatedir[@]|$(runstatedir)|g' \
-e 's|@sysconfdir[@]|$(sysconfdir)|g'
# Files that will be generated by passing their *.template file
# through $(subst).
subst_files = ddclient ddclient.conf
EXTRA_DIST += $(subst_files:=.template)
CLEANFILES += $(subst_files)
$(subst_files): Makefile
rm -f '$@' '$@'.tmp
in='$@'.template; \
test -f "$${in}" || in='$(srcdir)/'$${in}; \
$(subst) "$${in}" >'$@'.tmp && \
{ ! test -x "$${in}" || chmod +x '$@'.tmp; }
mv '$@'.tmp '$@'
ddclient: $(srcdir)/ddclient.template
ddclient.conf: $(srcdir)/ddclient.conf.template
bin_SCRIPTS = ddclient
sysconf_DATA = ddclient.conf
install-data-local:
$(MKDIR_P) '$(DESTDIR)$(localstatedir)'/cache/ddclient
LOG_DRIVER = env AM_TAP_AWK='$(AWK)' $(SHELL) \
$(top_srcdir)/build-aux/tap-driver.sh
TEST_EXTENSIONS = .pl
PL_LOG_DRIVER = $(LOG_DRIVER)
PL_LOG_COMPILER = $(PERL)
AM_PL_LOG_FLAGS = -Mstrict -w \
-I'$(abs_top_builddir)' \
-I'$(abs_top_srcdir)'/t/lib
handwritten_tests =
generated_tests = \
t/version.pl
TESTS = $(handwritten_tests) $(generated_tests)
EXTRA_DIST += $(handwritten_tests)

View file

@ -44,25 +44,48 @@ through github.com. Please check out http://ddclient.net
## REQUIREMENTS ## REQUIREMENTS
- one or more accounts from one of the dynamic DNS services * An account from a supported dynamic DNS service provider
* Perl v5.10.1 or later
* `IO::Socket::SSL` perl library for ssl-support
* `JSON::PP` perl library for JSON support
* `IO::Socket:INET6` perl library for ipv6-support
* Linux, macOS, or any other Unix-ish system
* An implementation of `make` (such as [GNU
Make](https://www.gnu.org/software/make/))
* If you are installing from a clone of the Git repository, you will
also need [GNU Autoconf](https://www.gnu.org/software/autoconf/)
and [GNU Automake](https://www.gnu.org/software/automake/).
- Perl v5.10.1 or later ## DOWNLOAD
- `IO::Socket::SSL` perl library for ssl-support
- `JSON::PP` perl library for JSON support
- `IO::Socket:INET6` perl library for ipv6-support
- Linux or probably any common Unix system See https://github.com/ddclient/ddclient/releases
-------------------------------------------------------------------------------
## INSTALLATION ## INSTALLATION
cp ddclient /usr/sbin/ 1. Extract the distribution tarball (`.tar.gz` file) and `cd` into
mkdir /etc/ddclient /var/cache/ddclient the directory:
cp sample-etc_ddclient.conf /etc/ddclient/ddclient.conf
vi /etc/ddclient/ddclient.conf
and change hostnames, logins, and passwords appropriately ```shell
tar xvfa ddclient-3.9.1.tar.gz
cd ddclient-3.9.1
```
(If you are installing from a clone of the Git repository, you
must run `./autogen` before continuing to the next step.)
2. Run the following commands to build and install:
```shell
./configure \
--prefix=/usr \
--sysconfdir=/etc/ddclient \
--localstatedir=/var
make
make check
sudo make install
```
3. Edit `/etc/ddclient/ddclient.conf`.
### systemd ### systemd

5
autogen Executable file
View file

@ -0,0 +1,5 @@
#!/bin/sh
cd "${0%/*}" || exit 1
mkdir -p m4 build-aux || exit 1
exec autoreconf -fviW all

651
build-aux/tap-driver.sh Executable file
View file

@ -0,0 +1,651 @@
#! /bin/sh
# Copyright (C) 2011-2020 Free Software Foundation, Inc.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2, or (at your option)
# any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
# As a special exception to the GNU General Public License, if you
# distribute this file as part of a program that contains a
# configuration script generated by Autoconf, you may include it under
# the same distribution terms that you use for the rest of that program.
# This file is maintained in Automake, please report
# bugs to <bug-automake@gnu.org> or send patches to
# <automake-patches@gnu.org>.
scriptversion=2013-12-23.17; # UTC
# Make unconditional expansion of undefined variables an error. This
# helps a lot in preventing typo-related bugs.
set -u
me=tap-driver.sh
fatal ()
{
echo "$me: fatal: $*" >&2
exit 1
}
usage_error ()
{
echo "$me: $*" >&2
print_usage >&2
exit 2
}
print_usage ()
{
cat <<END
Usage:
tap-driver.sh --test-name=NAME --log-file=PATH --trs-file=PATH
[--expect-failure={yes|no}] [--color-tests={yes|no}]
[--enable-hard-errors={yes|no}] [--ignore-exit]
[--diagnostic-string=STRING] [--merge|--no-merge]
[--comments|--no-comments] [--] TEST-COMMAND
The '--test-name', '-log-file' and '--trs-file' options are mandatory.
END
}
# TODO: better error handling in option parsing (in particular, ensure
# TODO: $log_file, $trs_file and $test_name are defined).
test_name= # Used for reporting.
log_file= # Where to save the result and output of the test script.
trs_file= # Where to save the metadata of the test run.
expect_failure=0
color_tests=0
merge=0
ignore_exit=0
comments=0
diag_string='#'
while test $# -gt 0; do
case $1 in
--help) print_usage; exit $?;;
--version) echo "$me $scriptversion"; exit $?;;
--test-name) test_name=$2; shift;;
--log-file) log_file=$2; shift;;
--trs-file) trs_file=$2; shift;;
--color-tests) color_tests=$2; shift;;
--expect-failure) expect_failure=$2; shift;;
--enable-hard-errors) shift;; # No-op.
--merge) merge=1;;
--no-merge) merge=0;;
--ignore-exit) ignore_exit=1;;
--comments) comments=1;;
--no-comments) comments=0;;
--diagnostic-string) diag_string=$2; shift;;
--) shift; break;;
-*) usage_error "invalid option: '$1'";;
esac
shift
done
test $# -gt 0 || usage_error "missing test command"
case $expect_failure in
yes) expect_failure=1;;
*) expect_failure=0;;
esac
if test $color_tests = yes; then
init_colors='
color_map["red"]="" # Red.
color_map["grn"]="" # Green.
color_map["lgn"]="" # Light green.
color_map["blu"]="" # Blue.
color_map["mgn"]="" # Magenta.
color_map["std"]="" # No color.
color_for_result["ERROR"] = "mgn"
color_for_result["PASS"] = "grn"
color_for_result["XPASS"] = "red"
color_for_result["FAIL"] = "red"
color_for_result["XFAIL"] = "lgn"
color_for_result["SKIP"] = "blu"'
else
init_colors=''
fi
# :; is there to work around a bug in bash 3.2 (and earlier) which
# does not always set '$?' properly on redirection failure.
# See the Autoconf manual for more details.
:;{
(
# Ignore common signals (in this subshell only!), to avoid potential
# problems with Korn shells. Some Korn shells are known to propagate
# to themselves signals that have killed a child process they were
# waiting for; this is done at least for SIGINT (and usually only for
# it, in truth). Without the `trap' below, such a behaviour could
# cause a premature exit in the current subshell, e.g., in case the
# test command it runs gets terminated by a SIGINT. Thus, the awk
# script we are piping into would never seen the exit status it
# expects on its last input line (which is displayed below by the
# last `echo $?' statement), and would thus die reporting an internal
# error.
# For more information, see the Autoconf manual and the threads:
# <https://lists.gnu.org/archive/html/bug-autoconf/2011-09/msg00004.html>
# <http://mail.opensolaris.org/pipermail/ksh93-integration-discuss/2009-February/004121.html>
trap : 1 3 2 13 15
if test $merge -gt 0; then
exec 2>&1
else
exec 2>&3
fi
"$@"
echo $?
) | LC_ALL=C ${AM_TAP_AWK-awk} \
-v me="$me" \
-v test_script_name="$test_name" \
-v log_file="$log_file" \
-v trs_file="$trs_file" \
-v expect_failure="$expect_failure" \
-v merge="$merge" \
-v ignore_exit="$ignore_exit" \
-v comments="$comments" \
-v diag_string="$diag_string" \
'
# TODO: the usages of "cat >&3" below could be optimized when using
# GNU awk, and/on on systems that supports /dev/fd/.
# Implementation note: in what follows, `result_obj` will be an
# associative array that (partly) simulates a TAP result object
# from the `TAP::Parser` perl module.
## ----------- ##
## FUNCTIONS ##
## ----------- ##
function fatal(msg)
{
print me ": " msg | "cat >&2"
exit 1
}
function abort(where)
{
fatal("internal error " where)
}
# Convert a boolean to a "yes"/"no" string.
function yn(bool)
{
return bool ? "yes" : "no";
}
function add_test_result(result)
{
if (!test_results_index)
test_results_index = 0
test_results_list[test_results_index] = result
test_results_index += 1
test_results_seen[result] = 1;
}
# Whether the test script should be re-run by "make recheck".
function must_recheck()
{
for (k in test_results_seen)
if (k != "XFAIL" && k != "PASS" && k != "SKIP")
return 1
return 0
}
# Whether the content of the log file associated to this test should
# be copied into the "global" test-suite.log.
function copy_in_global_log()
{
for (k in test_results_seen)
if (k != "PASS")
return 1
return 0
}
function get_global_test_result()
{
if ("ERROR" in test_results_seen)
return "ERROR"
if ("FAIL" in test_results_seen || "XPASS" in test_results_seen)
return "FAIL"
all_skipped = 1
for (k in test_results_seen)
if (k != "SKIP")
all_skipped = 0
if (all_skipped)
return "SKIP"
return "PASS";
}
function stringify_result_obj(result_obj)
{
if (result_obj["is_unplanned"] || result_obj["number"] != testno)
return "ERROR"
if (plan_seen == LATE_PLAN)
return "ERROR"
if (result_obj["directive"] == "TODO")
return result_obj["is_ok"] ? "XPASS" : "XFAIL"
if (result_obj["directive"] == "SKIP")
return result_obj["is_ok"] ? "SKIP" : COOKED_FAIL;
if (length(result_obj["directive"]))
abort("in function stringify_result_obj()")
return result_obj["is_ok"] ? COOKED_PASS : COOKED_FAIL
}
function decorate_result(result)
{
color_name = color_for_result[result]
if (color_name)
return color_map[color_name] "" result "" color_map["std"]
# If we are not using colorized output, or if we do not know how
# to colorize the given result, we should return it unchanged.
return result
}
function report(result, details)
{
if (result ~ /^(X?(PASS|FAIL)|SKIP|ERROR)/)
{
msg = ": " test_script_name
add_test_result(result)
}
else if (result == "#")
{
msg = " " test_script_name ":"
}
else
{
abort("in function report()")
}
if (length(details))
msg = msg " " details
# Output on console might be colorized.
print decorate_result(result) msg
# Log the result in the log file too, to help debugging (this is
# especially true when said result is a TAP error or "Bail out!").
print result msg | "cat >&3";
}
function testsuite_error(error_message)
{
report("ERROR", "- " error_message)
}
function handle_tap_result()
{
details = result_obj["number"];
if (length(result_obj["description"]))
details = details " " result_obj["description"]
if (plan_seen == LATE_PLAN)
{
details = details " # AFTER LATE PLAN";
}
else if (result_obj["is_unplanned"])
{
details = details " # UNPLANNED";
}
else if (result_obj["number"] != testno)
{
details = sprintf("%s # OUT-OF-ORDER (expecting %d)",
details, testno);
}
else if (result_obj["directive"])
{
details = details " # " result_obj["directive"];
if (length(result_obj["explanation"]))
details = details " " result_obj["explanation"]
}
report(stringify_result_obj(result_obj), details)
}
# `skip_reason` should be empty whenever planned > 0.
function handle_tap_plan(planned, skip_reason)
{
planned += 0 # Avoid getting confused if, say, `planned` is "00"
if (length(skip_reason) && planned > 0)
abort("in function handle_tap_plan()")
if (plan_seen)
{
# Error, only one plan per stream is acceptable.
testsuite_error("multiple test plans")
return;
}
planned_tests = planned
# The TAP plan can come before or after *all* the TAP results; we speak
# respectively of an "early" or a "late" plan. If we see the plan line
# after at least one TAP result has been seen, assume we have a late
# plan; in this case, any further test result seen after the plan will
# be flagged as an error.
plan_seen = (testno >= 1 ? LATE_PLAN : EARLY_PLAN)
# If testno > 0, we have an error ("too many tests run") that will be
# automatically dealt with later, so do not worry about it here. If
# $plan_seen is true, we have an error due to a repeated plan, and that
# has already been dealt with above. Otherwise, we have a valid "plan
# with SKIP" specification, and should report it as a particular kind
# of SKIP result.
if (planned == 0 && testno == 0)
{
if (length(skip_reason))
skip_reason = "- " skip_reason;
report("SKIP", skip_reason);
}
}
function extract_tap_comment(line)
{
if (index(line, diag_string) == 1)
{
# Strip leading `diag_string` from `line`.
line = substr(line, length(diag_string) + 1)
# And strip any leading and trailing whitespace left.
sub("^[ \t]*", "", line)
sub("[ \t]*$", "", line)
# Return what is left (if any).
return line;
}
return "";
}
# When this function is called, we know that line is a TAP result line,
# so that it matches the (perl) RE "^(not )?ok\b".
function setup_result_obj(line)
{
# Get the result, and remove it from the line.
result_obj["is_ok"] = (substr(line, 1, 2) == "ok" ? 1 : 0)
sub("^(not )?ok[ \t]*", "", line)
# If the result has an explicit number, get it and strip it; otherwise,
# automatically assing the next progresive number to it.
if (line ~ /^[0-9]+$/ || line ~ /^[0-9]+[^a-zA-Z0-9_]/)
{
match(line, "^[0-9]+")
# The final `+ 0` is to normalize numbers with leading zeros.
result_obj["number"] = substr(line, 1, RLENGTH) + 0
line = substr(line, RLENGTH + 1)
}
else
{
result_obj["number"] = testno
}
if (plan_seen == LATE_PLAN)
# No further test results are acceptable after a "late" TAP plan
# has been seen.
result_obj["is_unplanned"] = 1
else if (plan_seen && testno > planned_tests)
result_obj["is_unplanned"] = 1
else
result_obj["is_unplanned"] = 0
# Strip trailing and leading whitespace.
sub("^[ \t]*", "", line)
sub("[ \t]*$", "", line)
# This will have to be corrected if we have a "TODO"/"SKIP" directive.
result_obj["description"] = line
result_obj["directive"] = ""
result_obj["explanation"] = ""
if (index(line, "#") == 0)
return # No possible directive, nothing more to do.
# Directives are case-insensitive.
rx = "[ \t]*#[ \t]*([tT][oO][dD][oO]|[sS][kK][iI][pP])[ \t]*"
# See whether we have the directive, and if yes, where.
pos = match(line, rx "$")
if (!pos)
pos = match(line, rx "[^a-zA-Z0-9_]")
# If there was no TAP directive, we have nothing more to do.
if (!pos)
return
# Let`s now see if the TAP directive has been escaped. For example:
# escaped: ok \# SKIP
# not escaped: ok \\# SKIP
# escaped: ok \\\\\# SKIP
# not escaped: ok \ # SKIP
if (substr(line, pos, 1) == "#")
{
bslash_count = 0
for (i = pos; i > 1 && substr(line, i - 1, 1) == "\\"; i--)
bslash_count += 1
if (bslash_count % 2)
return # Directive was escaped.
}
# Strip the directive and its explanation (if any) from the test
# description.
result_obj["description"] = substr(line, 1, pos - 1)
# Now remove the test description from the line, that has been dealt
# with already.
line = substr(line, pos)
# Strip the directive, and save its value (normalized to upper case).
sub("^[ \t]*#[ \t]*", "", line)
result_obj["directive"] = toupper(substr(line, 1, 4))
line = substr(line, 5)
# Now get the explanation for the directive (if any), with leading
# and trailing whitespace removed.
sub("^[ \t]*", "", line)
sub("[ \t]*$", "", line)
result_obj["explanation"] = line
}
function get_test_exit_message(status)
{
if (status == 0)
return ""
if (status !~ /^[1-9][0-9]*$/)
abort("getting exit status")
if (status < 127)
exit_details = ""
else if (status == 127)
exit_details = " (command not found?)"
else if (status >= 128 && status <= 255)
exit_details = sprintf(" (terminated by signal %d?)", status - 128)
else if (status > 256 && status <= 384)
# We used to report an "abnormal termination" here, but some Korn
# shells, when a child process die due to signal number n, can leave
# in $? an exit status of 256+n instead of the more standard 128+n.
# Apparently, both behaviours are allowed by POSIX (2008), so be
# prepared to handle them both. See also Austing Group report ID
# 0000051 <http://www.austingroupbugs.net/view.php?id=51>
exit_details = sprintf(" (terminated by signal %d?)", status - 256)
else
# Never seen in practice.
exit_details = " (abnormal termination)"
return sprintf("exited with status %d%s", status, exit_details)
}
function write_test_results()
{
print ":global-test-result: " get_global_test_result() > trs_file
print ":recheck: " yn(must_recheck()) > trs_file
print ":copy-in-global-log: " yn(copy_in_global_log()) > trs_file
for (i = 0; i < test_results_index; i += 1)
print ":test-result: " test_results_list[i] > trs_file
close(trs_file);
}
BEGIN {
## ------- ##
## SETUP ##
## ------- ##
'"$init_colors"'
# Properly initialized once the TAP plan is seen.
planned_tests = 0
COOKED_PASS = expect_failure ? "XPASS": "PASS";
COOKED_FAIL = expect_failure ? "XFAIL": "FAIL";
# Enumeration-like constants to remember which kind of plan (if any)
# has been seen. It is important that NO_PLAN evaluates "false" as
# a boolean.
NO_PLAN = 0
EARLY_PLAN = 1
LATE_PLAN = 2
testno = 0 # Number of test results seen so far.
bailed_out = 0 # Whether a "Bail out!" directive has been seen.
# Whether the TAP plan has been seen or not, and if yes, which kind
# it is ("early" is seen before any test result, "late" otherwise).
plan_seen = NO_PLAN
## --------- ##
## PARSING ##
## --------- ##
is_first_read = 1
while (1)
{
# Involutions required so that we are able to read the exit status
# from the last input line.
st = getline
if (st < 0) # I/O error.
fatal("I/O error while reading from input stream")
else if (st == 0) # End-of-input
{
if (is_first_read)
abort("in input loop: only one input line")
break
}
if (is_first_read)
{
is_first_read = 0
nextline = $0
continue
}
else
{
curline = nextline
nextline = $0
$0 = curline
}
# Copy any input line verbatim into the log file.
print | "cat >&3"
# Parsing of TAP input should stop after a "Bail out!" directive.
if (bailed_out)
continue
# TAP test result.
if ($0 ~ /^(not )?ok$/ || $0 ~ /^(not )?ok[^a-zA-Z0-9_]/)
{
testno += 1
setup_result_obj($0)
handle_tap_result()
}
# TAP plan (normal or "SKIP" without explanation).
else if ($0 ~ /^1\.\.[0-9]+[ \t]*$/)
{
# The next two lines will put the number of planned tests in $0.
sub("^1\\.\\.", "")
sub("[^0-9]*$", "")
handle_tap_plan($0, "")
continue
}
# TAP "SKIP" plan, with an explanation.
else if ($0 ~ /^1\.\.0+[ \t]*#/)
{
# The next lines will put the skip explanation in $0, stripping
# any leading and trailing whitespace. This is a little more
# tricky in truth, since we want to also strip a potential leading
# "SKIP" string from the message.
sub("^[^#]*#[ \t]*(SKIP[: \t][ \t]*)?", "")
sub("[ \t]*$", "");
handle_tap_plan(0, $0)
}
# "Bail out!" magic.
# Older versions of prove and TAP::Harness (e.g., 3.17) did not
# recognize a "Bail out!" directive when preceded by leading
# whitespace, but more modern versions (e.g., 3.23) do. So we
# emulate the latter, "more modern" behaviour.
else if ($0 ~ /^[ \t]*Bail out!/)
{
bailed_out = 1
# Get the bailout message (if any), with leading and trailing
# whitespace stripped. The message remains stored in `$0`.
sub("^[ \t]*Bail out![ \t]*", "");
sub("[ \t]*$", "");
# Format the error message for the
bailout_message = "Bail out!"
if (length($0))
bailout_message = bailout_message " " $0
testsuite_error(bailout_message)
}
# Maybe we have too look for dianogtic comments too.
else if (comments != 0)
{
comment = extract_tap_comment($0);
if (length(comment))
report("#", comment);
}
}
## -------- ##
## FINISH ##
## -------- ##
# A "Bail out!" directive should cause us to ignore any following TAP
# error, as well as a non-zero exit status from the TAP producer.
if (!bailed_out)
{
if (!plan_seen)
{
testsuite_error("missing test plan")
}
else if (planned_tests != testno)
{
bad_amount = testno > planned_tests ? "many" : "few"
testsuite_error(sprintf("too %s tests run (expected %d, got %d)",
bad_amount, planned_tests, testno))
}
if (!ignore_exit)
{
# Fetch exit status from the last line.
exit_message = get_test_exit_message(nextline)
if (exit_message)
testsuite_error(exit_message)
}
}
write_test_results()
exit 0
} # End of "BEGIN" block.
'
# TODO: document that we consume the file descriptor 3 :-(
} 3>"$log_file"
test $? -eq 0 || fatal "I/O or internal error"
# Local Variables:
# mode: shell-script
# sh-indentation: 2
# eval: (add-hook 'before-save-hook 'time-stamp)
# time-stamp-start: "scriptversion="
# time-stamp-format: "%:y-%02m-%02d.%02H"
# time-stamp-time-zone: "UTC0"
# time-stamp-end: "; # UTC"
# End:

39
configure.ac Normal file
View file

@ -0,0 +1,39 @@
AC_PREREQ([2.63])
AC_INIT([ddclient], [3.9.1])
AC_CONFIG_SRCDIR([ddclient.template])
AC_CONFIG_AUX_DIR([build-aux])
AC_CONFIG_MACRO_DIR([m4])
AC_REQUIRE_AUX_FILE([tap-driver.sh])
# If the automake dependency is bumped to v1.12 or newer, remove
# build-aux/tap-driver.sh from the repository. Automake 1.12+ comes
# with tap-driver.sh, and autoreconf will copy in the version
# distributed with automake. (Automake 1.11 and older don't come with
# tap-driver.sh, so build-aux/tap-driver.sh is checked in to keep the
# above AC_REQUIRE_AUX_FILE line from causing configure to complain
# about a mising file if the user has Automake 1.11.)
AM_INIT_AUTOMAKE([1.11 -Wall -Werror foreign subdir-objects])
AM_SILENT_RULES
AC_PROG_MKDIR_P
AX_WITH_PROG([PERL], perl)
AX_PROG_PERL_VERSION([5.10.1], [],
[AC_MSG_ERROR([Perl 5.10.1 or newer not found])])
AC_SUBST([PERL])
# Perl modules required to run ddclient. Core modules may be omitted;
# they are assumed to always exist.
m4_foreach_w([_m], [
], [AX_PROG_PERL_MODULES([_m], [],
[AC_MSG_ERROR([missing required Perl module _m])])])
# Perl modules required for tests. Only prints a warning.
m4_foreach_w([_m], [
], [AX_PROG_PERL_MODULES([_m], [],
[AC_MSG_WARN([some tests may be skipped due to missing module _m])])])
AC_CONFIG_FILES([
Makefile
t/version.pl
])
AC_OUTPUT

View file

@ -20,7 +20,7 @@ daemon=300 # check every 300 seconds
syslog=yes # log update msgs to syslog syslog=yes # log update msgs to syslog
mail=root # mail all msgs to root mail=root # mail all msgs to root
mail-failure=root # mail failed update msgs to root mail-failure=root # mail failed update msgs to root
pid=/var/run/ddclient.pid # record PID in file. pid=@runstatedir@/ddclient.pid # record PID in file.
ssl=yes # use ssl-support. Works with ssl=yes # use ssl-support. Works with
# ssl-library # ssl-library
# postscript=script # run script after updating. The # postscript=script # run script after updating. The

View file

@ -1,4 +1,4 @@
#!/usr/bin/perl #!@PERL@
###################################################################### ######################################################################
# #
# DDCLIENT - a Perl client for updating DynDNS information # DDCLIENT - a Perl client for updating DynDNS information
@ -18,6 +18,7 @@
# #
# #
###################################################################### ######################################################################
package ddclient;
require v5.10.1; require v5.10.1;
use strict; use strict;
use warnings; use warnings;
@ -25,16 +26,33 @@ use Getopt::Long;
use Sys::Hostname; use Sys::Hostname;
use IO::Socket; use IO::Socket;
my $version = "3.9.1"; use version 0.77; our $VERSION = version->declare('v3.9.1');
(my $version = $VERSION->stringify()) =~ s/^v//;
my $programd = $0; my $programd = $0;
$programd =~ s%^.*/%%; $programd =~ s%^.*/%%;
my $program = $programd; my $program = $programd;
$program =~ s/d$//; $program =~ s/d$//;
my $now = time; my $now = time;
my $hostname = hostname(); my $hostname = hostname();
my $etc = ($program =~ /test/i) ? './' : '/etc/ddclient/';
my $cachedir = ($program =~ /test/i) ? './' : '/var/cache/ddclient/'; # subst_var(subst, default) returns subst unless it looks like @foo@ in which case it returns
my $savedir = ($program =~ /test/i) ? 'URL/' : '/tmp/'; # default. The @foo@ strings are expected to be replaced by make; this function makes it possible
# to run this file as a Perl script before those substitutions are made.
sub subst_var {
my ($subst, $default) = @_;
return $default if $subst =~ /^@\w+@$/a;
return $subst;
}
my $etc = subst_var('@sysconfdir@', '/etc/ddclient');
my $cachedir = subst_var('@localstatedir@', '/var') . '/cache/ddclient';
my $savedir = '/tmp';
if ($program =~ /test/i) {
$etc = '.';
$cachedir = '.';
$savedir = 'URL';
}
my $msgs = ''; my $msgs = '';
my $last_msgs = ''; my $last_msgs = '';
@ -48,6 +66,11 @@ local $lineno = '';
$ENV{'PATH'} = (exists($ENV{PATH}) ? "$ENV{PATH}:" : "") . "/sbin:/usr/sbin:/bin:/usr/bin:/etc:/usr/lib:"; $ENV{'PATH'} = (exists($ENV{PATH}) ? "$ENV{PATH}:" : "") . "/sbin:/usr/sbin:/bin:/usr/bin:/etc:/usr/lib:";
my ($result, %config, %globals, %cache);
my $saved_cache;
my %saved_opt;
my $daemon;
sub T_ANY { 'any' } sub T_ANY { 'any' }
sub T_STRING { 'string' } sub T_STRING { 'string' }
sub T_EMAIL { 'e-mail address' } sub T_EMAIL { 'e-mail address' }
@ -321,8 +344,8 @@ my %variables = (
'global-defaults' => { 'global-defaults' => {
'daemon' => setv(T_DELAY, 0, 0, $daemon_default, interval('60s')), 'daemon' => setv(T_DELAY, 0, 0, $daemon_default, interval('60s')),
'foreground' => setv(T_BOOL, 0, 0, 0, undef), 'foreground' => setv(T_BOOL, 0, 0, 0, undef),
'file' => setv(T_FILE, 0, 0, "$etc$program.conf", undef), 'file' => setv(T_FILE, 0, 0, "$etc/$program.conf", undef),
'cache' => setv(T_FILE, 0, 0, "$cachedir$program.cache", undef), 'cache' => setv(T_FILE, 0, 0, "$cachedir/$program.cache", undef),
'pid' => setv(T_FILE, 0, 0, "", undef), 'pid' => setv(T_FILE, 0, 0, "", undef),
'proxy' => setv(T_FQDNP, 0, 0, '', undef), 'proxy' => setv(T_FQDNP, 0, 0, '', undef),
'protocol' => setv(T_PROTO, 0, 0, 'dyndns2', undef), 'protocol' => setv(T_PROTO, 0, 0, 'dyndns2', undef),
@ -813,105 +836,105 @@ my @opt = (
" project now maintained on https://github.com/ddclient/ddclient" " project now maintained on https://github.com/ddclient/ddclient"
); );
## process args sub main {
my $opt_usage = process_args(@opt); ## process args
my ($result, %config, %globals, %cache); my $opt_usage = process_args(@opt);
my $saved_cache = ''; $saved_cache = '';
my %saved_opt = %opt; %saved_opt = %opt;
$result = 'OK'; $result = 'OK';
test_geturl(opt('geturl')) if opt('geturl'); test_geturl(opt('geturl')) if opt('geturl');
if (opt('help')) { if (opt('help')) {
printf "%s\n", $opt_usage; printf "%s\n", $opt_usage;
exit 0;
}
## read config file because 'daemon' mode may be defined there.
read_config($opt{'file'} // default('file'), \%config, \%globals);
init_config();
test_possible_ip() if opt('query');
my $caught_hup = 0;
my $caught_term = 0;
my $caught_int = 0;
$SIG{'HUP'} = sub { $caught_hup = 1; };
$SIG{'TERM'} = sub { $caught_term = 1; };
$SIG{'INT'} = sub { $caught_int = 1; };
# don't fork() if foreground
if (opt('foreground')) {
;
} elsif (opt('daemon')) {
$SIG{'CHLD'} = 'IGNORE';
my $pid = fork;
if ($pid < 0) {
print STDERR "${program}: can not fork ($!)\n";
exit -1;
} elsif ($pid) {
exit 0; exit 0;
} }
$SIG{'CHLD'} = 'DEFAULT';
open(STDOUT, ">/dev/null");
open(STDERR, ">/dev/null");
open(STDIN, "</dev/null");
write_pid();
}
umask 077;
my $daemon;
do {
$now = time;
$result = 'OK';
%opt = %saved_opt;
if (opt('help')) {
*STDERR = *STDOUT;
printf("Help found");
}
## read config file because 'daemon' mode may be defined there.
read_config($opt{'file'} // default('file'), \%config, \%globals); read_config($opt{'file'} // default('file'), \%config, \%globals);
init_config(); init_config();
read_cache(opt('cache'), \%cache); test_possible_ip() if opt('query');
print_info() if opt('debug') && opt('verbose');
fatal("invalid argument '-use %s'; possible values are:\n%s", $opt{'use'}, join("\n", ip_strategies_usage())) my $caught_hup = 0;
unless exists $ip_strategies{lc opt('use')}; my $caught_term = 0;
my $caught_int = 0;
$daemon = opt('daemon'); $SIG{'HUP'} = sub { $caught_hup = 1; };
$SIG{'TERM'} = sub { $caught_term = 1; };
update_nics(); $SIG{'INT'} = sub { $caught_int = 1; };
# don't fork() if foreground
if ($daemon) { if (opt('foreground')) {
debug("sleep %s", $daemon); ;
sendmail(); } elsif (opt('daemon')) {
$SIG{'CHLD'} = 'IGNORE';
my $left = $daemon; my $pid = fork;
while (($left > 0) && !$caught_hup && !$caught_term && !$caught_int) { if ($pid < 0) {
my $delay = $left > 10 ? 10 : $left; print STDERR "${program}: can not fork ($!)\n";
exit -1;
$0 = sprintf("%s - sleeping for %s seconds", $program, $left); } elsif ($pid) {
$left -= sleep $delay; exit 0;
# preventing deep sleep - see [bugs:#46]
if ($left > $daemon) {
$left = $daemon;
}
} }
$caught_hup = 0; $SIG{'CHLD'} = 'DEFAULT';
$result = 0; open(STDOUT, ">/dev/null");
open(STDERR, ">/dev/null");
} elsif (!scalar(%config)) { open(STDIN, "</dev/null");
warning("no hosts to update.") unless !opt('quiet') || opt('verbose') || !$daemon; write_pid();
$result = 1;
} else {
$result = $result eq 'OK' ? 0 : 1;
} }
} while ($daemon && !$result && !$caught_term && !$caught_int);
warning("caught SIGINT; exiting") if $caught_int; umask 077;
unlink_pid(); do {
sendmail(); $now = time;
$result = 'OK';
%opt = %saved_opt;
if (opt('help')) {
*STDERR = *STDOUT;
printf("Help found");
}
exit($result); read_config($opt{'file'} // default('file'), \%config, \%globals);
init_config();
read_cache(opt('cache'), \%cache);
print_info() if opt('debug') && opt('verbose');
fatal("invalid argument '-use %s'; possible values are:\n%s", $opt{'use'}, join("\n", ip_strategies_usage()))
unless exists $ip_strategies{lc opt('use')};
$daemon = opt('daemon');
update_nics();
if ($daemon) {
debug("sleep %s", $daemon);
sendmail();
my $left = $daemon;
while (($left > 0) && !$caught_hup && !$caught_term && !$caught_int) {
my $delay = $left > 10 ? 10 : $left;
$0 = sprintf("%s - sleeping for %s seconds", $program, $left);
$left -= sleep $delay;
# preventing deep sleep - see [bugs:#46]
if ($left > $daemon) {
$left = $daemon;
}
}
$caught_hup = 0;
$result = 0;
} elsif (!scalar(%config)) {
warning("no hosts to update.") unless !opt('quiet') || opt('verbose') || !$daemon;
$result = 1;
} else {
$result = $result eq 'OK' ? 0 : 1;
}
} while ($daemon && !$result && !$caught_term && !$caught_int);
warning("caught SIGINT; exiting") if $caught_int;
unlink_pid();
sendmail();
exit($result);
}
###################################################################### ######################################################################
## runpostscript ## runpostscript
@ -2157,9 +2180,9 @@ sub geturl {
my $filename = "$server/$url"; my $filename = "$server/$url";
$filename =~ s|/|%2F|g; $filename =~ s|/|%2F|g;
if (opt('exec')) { if (opt('exec')) {
$reply = save_file("${savedir}$filename", $reply, 'unique'); $reply = save_file("$savedir/$filename", $reply, 'unique');
} else { } else {
$reply = load_file("${savedir}$filename"); $reply = load_file("$savedir/$filename");
} }
} }
@ -5224,6 +5247,12 @@ sub nic_cloudns_update {
} }
} }
# Execute main() if this file is run as a script or run via PAR (https://metacpan.org/pod/PAR),
# otherwise do nothing. This "modulino" pattern makes it possible to import this file as a module
# and test its functions directly; there's no need for test-only command-line arguments or stdout
# parsing.
__PACKAGE__->main() unless caller() && caller() ne 'PAR';
###################################################################### ######################################################################
# vim: ai et ts=4 sw=4 tw=78: # vim: ai et ts=4 sw=4 tw=78:

177
m4/ax_compare_version.m4 Normal file
View file

@ -0,0 +1,177 @@
# ===========================================================================
# https://www.gnu.org/software/autoconf-archive/ax_compare_version.html
# ===========================================================================
#
# SYNOPSIS
#
# AX_COMPARE_VERSION(VERSION_A, OP, VERSION_B, [ACTION-IF-TRUE], [ACTION-IF-FALSE])
#
# DESCRIPTION
#
# This macro compares two version strings. Due to the various number of
# minor-version numbers that can exist, and the fact that string
# comparisons are not compatible with numeric comparisons, this is not
# necessarily trivial to do in a autoconf script. This macro makes doing
# these comparisons easy.
#
# The six basic comparisons are available, as well as checking equality
# limited to a certain number of minor-version levels.
#
# The operator OP determines what type of comparison to do, and can be one
# of:
#
# eq - equal (test A == B)
# ne - not equal (test A != B)
# le - less than or equal (test A <= B)
# ge - greater than or equal (test A >= B)
# lt - less than (test A < B)
# gt - greater than (test A > B)
#
# Additionally, the eq and ne operator can have a number after it to limit
# the test to that number of minor versions.
#
# eq0 - equal up to the length of the shorter version
# ne0 - not equal up to the length of the shorter version
# eqN - equal up to N sub-version levels
# neN - not equal up to N sub-version levels
#
# When the condition is true, shell commands ACTION-IF-TRUE are run,
# otherwise shell commands ACTION-IF-FALSE are run. The environment
# variable 'ax_compare_version' is always set to either 'true' or 'false'
# as well.
#
# Examples:
#
# AX_COMPARE_VERSION([3.15.7],[lt],[3.15.8])
# AX_COMPARE_VERSION([3.15],[lt],[3.15.8])
#
# would both be true.
#
# AX_COMPARE_VERSION([3.15.7],[eq],[3.15.8])
# AX_COMPARE_VERSION([3.15],[gt],[3.15.8])
#
# would both be false.
#
# AX_COMPARE_VERSION([3.15.7],[eq2],[3.15.8])
#
# would be true because it is only comparing two minor versions.
#
# AX_COMPARE_VERSION([3.15.7],[eq0],[3.15])
#
# would be true because it is only comparing the lesser number of minor
# versions of the two values.
#
# Note: The characters that separate the version numbers do not matter. An
# empty string is the same as version 0. OP is evaluated by autoconf, not
# configure, so must be a string, not a variable.
#
# The author would like to acknowledge Guido Draheim whose advice about
# the m4_case and m4_ifvaln functions make this macro only include the
# portions necessary to perform the specific comparison specified by the
# OP argument in the final configure script.
#
# LICENSE
#
# Copyright (c) 2008 Tim Toolan <toolan@ele.uri.edu>
#
# Copying and distribution of this file, with or without modification, are
# permitted in any medium without royalty provided the copyright notice
# and this notice are preserved. This file is offered as-is, without any
# warranty.
#serial 13
dnl #########################################################################
AC_DEFUN([AX_COMPARE_VERSION], [
AC_REQUIRE([AC_PROG_AWK])
# Used to indicate true or false condition
ax_compare_version=false
# Convert the two version strings to be compared into a format that
# allows a simple string comparison. The end result is that a version
# string of the form 1.12.5-r617 will be converted to the form
# 0001001200050617. In other words, each number is zero padded to four
# digits, and non digits are removed.
AS_VAR_PUSHDEF([A],[ax_compare_version_A])
A=`echo "$1" | sed -e 's/\([[0-9]]*\)/Z\1Z/g' \
-e 's/Z\([[0-9]]\)Z/Z0\1Z/g' \
-e 's/Z\([[0-9]][[0-9]]\)Z/Z0\1Z/g' \
-e 's/Z\([[0-9]][[0-9]][[0-9]]\)Z/Z0\1Z/g' \
-e 's/[[^0-9]]//g'`
AS_VAR_PUSHDEF([B],[ax_compare_version_B])
B=`echo "$3" | sed -e 's/\([[0-9]]*\)/Z\1Z/g' \
-e 's/Z\([[0-9]]\)Z/Z0\1Z/g' \
-e 's/Z\([[0-9]][[0-9]]\)Z/Z0\1Z/g' \
-e 's/Z\([[0-9]][[0-9]][[0-9]]\)Z/Z0\1Z/g' \
-e 's/[[^0-9]]//g'`
dnl # In the case of le, ge, lt, and gt, the strings are sorted as necessary
dnl # then the first line is used to determine if the condition is true.
dnl # The sed right after the echo is to remove any indented white space.
m4_case(m4_tolower($2),
[lt],[
ax_compare_version=`echo "x$A
x$B" | sed 's/^ *//' | sort -r | sed "s/x${A}/false/;s/x${B}/true/;1q"`
],
[gt],[
ax_compare_version=`echo "x$A
x$B" | sed 's/^ *//' | sort | sed "s/x${A}/false/;s/x${B}/true/;1q"`
],
[le],[
ax_compare_version=`echo "x$A
x$B" | sed 's/^ *//' | sort | sed "s/x${A}/true/;s/x${B}/false/;1q"`
],
[ge],[
ax_compare_version=`echo "x$A
x$B" | sed 's/^ *//' | sort -r | sed "s/x${A}/true/;s/x${B}/false/;1q"`
],[
dnl Split the operator from the subversion count if present.
m4_bmatch(m4_substr($2,2),
[0],[
# A count of zero means use the length of the shorter version.
# Determine the number of characters in A and B.
ax_compare_version_len_A=`echo "$A" | $AWK '{print(length)}'`
ax_compare_version_len_B=`echo "$B" | $AWK '{print(length)}'`
# Set A to no more than B's length and B to no more than A's length.
A=`echo "$A" | sed "s/\(.\{$ax_compare_version_len_B\}\).*/\1/"`
B=`echo "$B" | sed "s/\(.\{$ax_compare_version_len_A\}\).*/\1/"`
],
[[0-9]+],[
# A count greater than zero means use only that many subversions
A=`echo "$A" | sed "s/\(\([[0-9]]\{4\}\)\{m4_substr($2,2)\}\).*/\1/"`
B=`echo "$B" | sed "s/\(\([[0-9]]\{4\}\)\{m4_substr($2,2)\}\).*/\1/"`
],
[.+],[
AC_WARNING(
[invalid OP numeric parameter: $2])
],[])
# Pad zeros at end of numbers to make same length.
ax_compare_version_tmp_A="$A`echo $B | sed 's/./0/g'`"
B="$B`echo $A | sed 's/./0/g'`"
A="$ax_compare_version_tmp_A"
# Check for equality or inequality as necessary.
m4_case(m4_tolower(m4_substr($2,0,2)),
[eq],[
test "x$A" = "x$B" && ax_compare_version=true
],
[ne],[
test "x$A" != "x$B" && ax_compare_version=true
],[
AC_WARNING([invalid OP parameter: $2])
])
])
AS_VAR_POPDEF([A])dnl
AS_VAR_POPDEF([B])dnl
dnl # Execute ACTION-IF-TRUE / ACTION-IF-FALSE.
if test "$ax_compare_version" = "true" ; then
m4_ifvaln([$4],[$4],[:])dnl
m4_ifvaln([$5],[else $5])dnl
fi
]) dnl AX_COMPARE_VERSION

View file

@ -0,0 +1,77 @@
# ===========================================================================
# https://www.gnu.org/software/autoconf-archive/ax_prog_perl_modules.html
# ===========================================================================
#
# SYNOPSIS
#
# AX_PROG_PERL_MODULES([MODULES], [ACTION-IF-TRUE], [ACTION-IF-FALSE])
#
# DESCRIPTION
#
# Checks to see if the given perl modules are available. If true the shell
# commands in ACTION-IF-TRUE are executed. If not the shell commands in
# ACTION-IF-FALSE are run. Note if $PERL is not set (for example by
# calling AC_CHECK_PROG, or AC_PATH_PROG), AC_CHECK_PROG(PERL, perl, perl)
# will be run.
#
# MODULES is a space separated list of module names. To check for a
# minimum version of a module, append the version number to the module
# name, separated by an equals sign.
#
# Example:
#
# AX_PROG_PERL_MODULES( Text::Wrap Net::LDAP=1.0.3, ,
# AC_MSG_WARN(Need some Perl modules)
#
# LICENSE
#
# Copyright (c) 2009 Dean Povey <povey@wedgetail.com>
#
# Copying and distribution of this file, with or without modification, are
# permitted in any medium without royalty provided the copyright notice
# and this notice are preserved. This file is offered as-is, without any
# warranty.
#serial 8
AU_ALIAS([AC_PROG_PERL_MODULES], [AX_PROG_PERL_MODULES])
AC_DEFUN([AX_PROG_PERL_MODULES],[dnl
m4_define([ax_perl_modules])
m4_foreach([ax_perl_module], m4_split(m4_normalize([$1])),
[
m4_append([ax_perl_modules],
[']m4_bpatsubst(ax_perl_module,=,[ ])[' ])
])
# Make sure we have perl
if test -z "$PERL"; then
AC_CHECK_PROG(PERL,perl,perl)
fi
if test "x$PERL" != x; then
ax_perl_modules_failed=0
for ax_perl_module in ax_perl_modules; do
AC_MSG_CHECKING(for perl module $ax_perl_module)
# Would be nice to log result here, but can't rely on autoconf internals
$PERL -e "use $ax_perl_module; exit" > /dev/null 2>&1
if test $? -ne 0; then
AC_MSG_RESULT(no);
ax_perl_modules_failed=1
else
AC_MSG_RESULT(ok);
fi
done
# Run optional shell commands
if test "$ax_perl_modules_failed" = 0; then
:
$2
else
:
$3
fi
else
AC_MSG_WARN(could not find perl)
fi])dnl

View file

@ -0,0 +1,70 @@
# ===========================================================================
# https://www.gnu.org/software/autoconf-archive/ax_prog_perl_version.html
# ===========================================================================
#
# SYNOPSIS
#
# AX_PROG_PERL_VERSION([VERSION],[ACTION-IF-TRUE],[ACTION-IF-FALSE])
#
# DESCRIPTION
#
# Makes sure that perl supports the version indicated. If true the shell
# commands in ACTION-IF-TRUE are executed. If not the shell commands in
# ACTION-IF-FALSE are run. Note if $PERL is not set (for example by
# running AC_CHECK_PROG or AC_PATH_PROG) the macro will fail.
#
# Example:
#
# AC_PATH_PROG([PERL],[perl])
# AX_PROG_PERL_VERSION([5.8.0],[ ... ],[ ... ])
#
# This will check to make sure that the perl you have supports at least
# version 5.8.0.
#
# NOTE: This macro uses the $PERL variable to perform the check.
# AX_WITH_PERL can be used to set that variable prior to running this
# macro. The $PERL_VERSION variable will be valorized with the detected
# version.
#
# LICENSE
#
# Copyright (c) 2009 Francesco Salvestrini <salvestrini@users.sourceforge.net>
#
# Copying and distribution of this file, with or without modification, are
# permitted in any medium without royalty provided the copyright notice
# and this notice are preserved. This file is offered as-is, without any
# warranty.
#serial 13
AC_DEFUN([AX_PROG_PERL_VERSION],[
AC_REQUIRE([AC_PROG_SED])
AC_REQUIRE([AC_PROG_GREP])
AS_IF([test -n "$PERL"],[
ax_perl_version="$1"
AC_MSG_CHECKING([for perl version])
changequote(<<,>>)
perl_version=`$PERL --version 2>&1 \
| $SED -n -e '/This is perl/b inspect
b
: inspect
s/.* (\{0,1\}v\([0-9]*\.[0-9]*\.[0-9]*\))\{0,1\} .*/\1/;p'`
changequote([,])
AC_MSG_RESULT($perl_version)
AC_SUBST([PERL_VERSION],[$perl_version])
AX_COMPARE_VERSION([$ax_perl_version],[le],[$perl_version],[
:
$2
],[
:
$3
])
],[
AC_MSG_WARN([could not find the perl interpreter])
$3
])
])

70
m4/ax_with_prog.m4 Normal file
View file

@ -0,0 +1,70 @@
# ===========================================================================
# https://www.gnu.org/software/autoconf-archive/ax_with_prog.html
# ===========================================================================
#
# SYNOPSIS
#
# AX_WITH_PROG([VARIABLE],[program],[VALUE-IF-NOT-FOUND],[PATH])
#
# DESCRIPTION
#
# Locates an installed program binary, placing the result in the precious
# variable VARIABLE. Accepts a present VARIABLE, then --with-program, and
# failing that searches for program in the given path (which defaults to
# the system path). If program is found, VARIABLE is set to the full path
# of the binary; if it is not found VARIABLE is set to VALUE-IF-NOT-FOUND
# if provided, unchanged otherwise.
#
# A typical example could be the following one:
#
# AX_WITH_PROG(PERL,perl)
#
# NOTE: This macro is based upon the original AX_WITH_PYTHON macro from
# Dustin J. Mitchell <dustin@cs.uchicago.edu>.
#
# LICENSE
#
# Copyright (c) 2008 Francesco Salvestrini <salvestrini@users.sourceforge.net>
# Copyright (c) 2008 Dustin J. Mitchell <dustin@cs.uchicago.edu>
#
# Copying and distribution of this file, with or without modification, are
# permitted in any medium without royalty provided the copyright notice
# and this notice are preserved. This file is offered as-is, without any
# warranty.
#serial 17
AC_DEFUN([AX_WITH_PROG],[
AC_PREREQ([2.61])
pushdef([VARIABLE],$1)
pushdef([EXECUTABLE],$2)
pushdef([VALUE_IF_NOT_FOUND],$3)
pushdef([PATH_PROG],$4)
AC_ARG_VAR(VARIABLE,Absolute path to EXECUTABLE executable)
AS_IF(test -z "$VARIABLE",[
AC_MSG_CHECKING(whether EXECUTABLE executable path has been provided)
AC_ARG_WITH(EXECUTABLE,AS_HELP_STRING([--with-EXECUTABLE=[[[PATH]]]],absolute path to EXECUTABLE executable), [
AS_IF([test "$withval" != yes && test "$withval" != no],[
VARIABLE="$withval"
AC_MSG_RESULT($VARIABLE)
],[
VARIABLE=""
AC_MSG_RESULT([no])
AS_IF([test "$withval" != no], [
AC_PATH_PROG([]VARIABLE[],[]EXECUTABLE[],[]VALUE_IF_NOT_FOUND[],[]PATH_PROG[])
])
])
],[
AC_MSG_RESULT([no])
AC_PATH_PROG([]VARIABLE[],[]EXECUTABLE[],[]VALUE_IF_NOT_FOUND[],[]PATH_PROG[])
])
])
popdef([PATH_PROG])
popdef([VALUE_IF_NOT_FOUND])
popdef([EXECUTABLE])
popdef([VARIABLE])
])

8
t/version.pl.in Normal file
View file

@ -0,0 +1,8 @@
use Test::More;
use version;
eval { require 'ddclient'; ddclient->import(); 1; } or die($@);
is(ddclient->VERSION(), version->parse('v@PACKAGE_VERSION@'), "version matches Autoconf config");
done_testing();