feat: add full Zonemaster stack with Docker and Spanish UI

- Clone all 5 Zonemaster component repos (LDNS, Engine, CLI, Backend, GUI)
- Dockerfile.backend: 8-stage multi-stage build LDNS→Engine→CLI→Backend
- Dockerfile.gui: Astro static build served via nginx
- docker-compose.yml: backend (internal) + frontend (port 5353)
- nginx.conf: root redirects to /es/, /api/ proxied to backend
- zonemaster-gui/config.ts: defaultLanguage set to 'es' (Spanish)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-21 08:19:24 +02:00
commit 8d4eaa1489
1567 changed files with 204155 additions and 0 deletions

View File

@@ -0,0 +1,15 @@
## Purpose
This PR...
## Context
(e.g. Fixes #9999, Follow-up to #9999, etc.)
## Changes
...
## How to test this PR
...

112
zonemaster-cli/.github/workflows/ci.yml vendored Normal file
View File

@@ -0,0 +1,112 @@
---
name: CI
on:
push:
branches:
- develop
- master
- 'release/**'
pull_request:
branches:
- develop
- master
- 'release/**'
jobs:
run-tests:
strategy:
matrix:
compatibility:
- develop
# - latest
perl:
- '5.40'
- '5.36'
- '5.26'
runner:
- ubuntu-22.04
runs-on: ${{ matrix.runner }}
steps:
- uses: actions/checkout@v2
- uses: shogo82148/actions-setup-perl@v1
with:
perl-version: ${{ matrix.perl }}
- name: Install binary dependencies
run: |
# * These were taken from the installation instruction.
# * Gettext was added so we can run cpanm . on the Engine sources.
# * The Perl modules were left out because I couldn't get all of them
# to work with custom Perl versions.
# * Cpanminus was left out because actions-setup-perl installs it.
sudo apt-get install -y \
autoconf \
automake \
build-essential \
gettext \
libidn2-dev \
libssl-dev \
libtool \
m4 \
- name: Install Zonemaster dependencies (latest)
if: ${{ matrix.compatibility == 'latest' }}
run: |
cpanm --sudo --notest \
Module::Install \
ExtUtils::PkgConfig \
Zonemaster::Engine
- name: Install Zonemaster dependencies (develop)
if: ${{ matrix.compatibility == 'develop' }}
run: |
cpanm --sudo --notest \
Devel::CheckLib \
Module::Install \
ExtUtils::PkgConfig \
Module::Install::XSUtil
git clone --branch=develop --depth=1 \
https://github.com/zonemaster/zonemaster-ldns.git
perl Makefile.PL # Generate MYMETA.yml to appease cpanm .
( cd zonemaster-ldns ; cpanm --sudo --notest . )
rm -rf zonemaster-ldns
git clone --branch=develop --depth=1 \
https://github.com/zonemaster/zonemaster-engine.git
perl Makefile.PL # Generate MYMETA.yml to appease cpanm .
( cd zonemaster-engine ; cpanm --sudo --notest . )
rm -rf zonemaster-engine
# Installing Zonemaster::Engine requires root privileges, because of a
# bug in Mail::SPF preventing normal installation with cpanm as
# non-root user (see link below [1]).
#
# The alternative, if one still wishes to install Zonemaster::Engine
# as non-root user, is to install Mail::SPF first with a command like:
#
# % cpanm --notest \
# --install-args="--install_path sbin=$HOME/.local/sbin" \
# Mail::SPF
#
# For the sake of consistency, other Perl packages installed from CPAN
# are also installed as root.
#
# [1]: https://rt.cpan.org/Public/Bug/Display.html?id=34768
- name: Install remaining dependencies
run: |
cpanm --sudo --verbose --notest --installdeps .
- name: Install Zonemaster::CLI
run: |
cpanm --sudo --verbose --notest .
- name: Show content of log files
if: ${{ failure() }}
run: cat /home/runner/.cpanm/work/*/build.log
- name: Test
run: |
prove -lv t

25
zonemaster-cli/.gitignore vendored Normal file
View File

@@ -0,0 +1,25 @@
/Makefile
Makefile.old
Build
Build.bat
META.*
MYMETA.*
.build/
_build/
cover_db/
blib/
inc/
share/locale/
share/*.mo
.lwpcookies
.last_cover_stats
nytprof.out
pod2htm*.tmp
pm_to_blib
Zonemaster-CLI-*
Zonemaster-CLI-*.tar.gz
share/Zonemaster-CLI.pot
# Emacs and other backup files
*.bak
*~

View File

@@ -0,0 +1,17 @@
-ole=unix
-se
-bext=~
-l=120
-i=4
-ci=2
-nsbl
-cti=0
-pt=0
-sbt=1
-bt=1
-bbt=0
-sfs
-tso
-csc
-csci=20
-nbbc

View File

@@ -0,0 +1,30 @@
# Contributing to Zonemaster::CLI
Contribution to this repository is welcome. Contribution can be either an issue
report or a code or a documentation update. Also see the information in the
[main README][Zonemaster/Zonemaster README] in the main Zonemaster respository.
## Issue
First search for a similar issue in the [issues list]. If a relevant issue is
found, add your information as a comment. If no relevant issue is found, create
[a new issue][create issue]. Give as many details as you have and describe, if
possible, how the issue can be reproduced.
## Pull request
If you would like to contribute an update, first please look for issues and open
[pull requests] that are about the same thing. If nothing relevant is found or
you have a different solution, create [a new pull request][create pull request].
Creating a pull request assumes that you have your proposal in a fork repository.
When you create a pull request, please always start with the `develop` branch
and create the pull request against the same branch.
[issues list]: https://github.com/zonemaster/zonemaster-cli/issues
[create issue]: https://github.com/zonemaster/zonemaster-cli/issues/new
[pull requests]: https://github.com/zonemaster/zonemaster-cli/pulls
[create pull request]: https://github.com/zonemaster/zonemaster-cli/compare
[Zonemaster/Zonemaster README]: https://github.com/zonemaster/zonemaster#readme

431
zonemaster-cli/Changes Normal file
View File

@@ -0,0 +1,431 @@
Release history for Zonemaster component Zonemaster-CLI
v8.0.1 2025-12-17 (part of Zonemaster v2025.2 release)
[Fixes]
- This version has no changes besides requiring updated
Zonemaster-LDNS and Zonemaster-Engine.
v8.0.0 2025-06-26 (part of Zonemaster v2025.1 release)
[Breaking changes]
- Makes the --test option more flexible #359
[Features]
- Expands the --nstimes option #421
- Expands the --count option #424
[Fixes]
- Updates translations #444, #445
- Slows down the spinner a bit #419
v7.2.0 2025-03-04 (part of Zonemaster v2024.2.1 release)
[Release information]
- Translations have not been fully updated in this release. They will
be updated in an upcoming extra release.
[Features]
- Adds translation to Slovenian language (#384)
[Fixes]
- Updates translations (#418, #425, #415, #422, #414)
- Minor code cleanup (#420)
- Fixes to make early messages not to be lost (#416)
v7.1.0 2024-12-09 (part of Zonemaster v2024.2 release)
[Release information]
- Translations have not been fully updated in this release. They will
be updated in an upcoming extra release.
[Features]
- Corrects display when running 'zonemaster-cli' with '--ns' or '--ds'
(#382)
- Allows explicitly setting '--stop-level' to empty string (#395)
[Fixes]
- Provides a useful link in man page (#401)
- Write “AS” in uppercase in Dockerfile (#400)
- Corrects gettext code (#398)
- Organizes "zonemaster-cli --help" text (#389)
- Make "zonemaster-cli" usage documentation consistent and also remove
dependency on the Moose library (#371)
- Fixes the handling of invalid --hints argument (#386)
- Moves the "zonemaster-info" utility from this repository (#385)
- Clean-up exit statuses (#381)
v7.0.0 2024-07-01 (part of Zonemaster v2024.1 release)
[Release information]
- Translations have not been fully updated in this release. They will
be updated in an upcoming release.
[Breaking change]
- Removes deprecated '--sourceaddr' option. Breaks custom 'cli.args'
using this option. Use '--sourceaddr4' and '--sourceaddr6'
instead. (#370)
[Fixes]
- Fixes command line argument handling of domain names (#365)
- Speeds up Docker build (#374)
v6.1.0 2024-03-18 (public release version)
[Features]
- Extends "--test" option to allow passing of test case only (#333)
- Updates "--list_tests" option (#354)
- Adds input name normalization (#357)
[Fixes]
- Fixes the "--raw" output (#360)
v6.0.3 2023-09-08 (public fix version)
[Fixes]
- Fixes a spelling error (text) in the zonemaster-cli script (#351).
- This version contains no real code changes, but it requires a higher
(fixed) version of Zonemaster-Engine.
v6.0.2 2023-08-07 (public fix version)
[Fixes]
- This version contains no real changes. It has been created to
require a higher (fixed) version of Zonemaster-Engine.
v6.0.1 2023-07-24 (public fix version)
[Fixes]
- Updates the translation to Norwegian (#342)
v6.0.0 2023-06-21 (public release version)
[Breaking changes]
- Adds display of Zonemaster::LDNS and libldns versions
when --version has been selected (#306)
[Features]
- Updates various report options to zonemaster-cli script
(#318, #337, #309, #308)
- Adds options "--sourceaddr4" and "--sourceaddr6" to
zonemaster-cli script (#317)
[Fixes]
- Updates translations (#338, #328, #329, #331, #327, #322)
- Updates documentation in zonemaster-cli script (#336)
- Fixes table layout in zonemaster-cli output (#335)
- Removes documentation moved to the zonemaster/zonemaster
repository (#332)
- Removes some untranslatable characters from msgids (#307)
v5.0.2 2023-03-01 (public fix version)
[Fixes]
- Updates translations (#303)
v5.0.1 2023-01-31 (public fix version)
[Fixes]
- Updates translations (#298, #297)
v5.0.0 2022-12-19 (public release version)
[Breaking changes]
- Makes zonemaster-cli fail if there are multiple domain
names in entry (#287, #286)
[Features]
- Adds --hints option (#284, #293, #292)
[Fixes]
- Updates installation instruction (#291, #289)
- Corrects the license statement in CLI.pm (#288)
- Sets lowercase fragments to refer to internal headings in
markdown documents (#282)
- Cleans-up unused import (#283)
- Adds Mac note on USING.md when it comes to Docker (#281)
v4.0.1 2022-07-08 (public fix version)
[Fixes]
- Adds missing installation instructions for CentOS Linux 7
(#275)
- Updates translation to Danish (#271)
v4.0.0 2022-06-09 (public release version)
[Breaking changes]
- Updates how trailing dots of domain name or name
server name is handled. Trailing double dot is now
always an error. (#253)
[Features]
- Uses pre-built packages for ubuntu (#268)
- Adds support for global configuration file (#260, #252)
[Fixes]
- Updates translation (#263, #267, #266, #265, #269, #264, #261)
- Fixes a bug where a trailing dot on name server name
gives a crash (#253)
- Fixes warning message (#255)
- Uses libidn2 instead of libidn (#254)
- Updates documentation for users (#241, #244)
- Updates installation document (#243)
v3.2.0 2021-12-20 (public fix version)
[Features]
- Adds translation to Spanish language (#219)
[Fixes]
- Clarifies IPv6 limitations in Docker (#238)
- Updates Danish translation (#236, #233)
- Updates French translation (#237, #225)
v3.1.1 2021-12-03 (public release version)
[Features]
- Deb packages are available for Debian (#233)
- Adds support for Docker (#221, #218, #216)
- Replaces CentOS with Rocky Linux (#220)
[Fixes]
- Updates Finnish translation (#230, #224)
- Updates Norwegian translation (#228, #226)
- Updates Swedish translation (#227)
- Updates dependencies (#217)
- Improves DEBUG3 output (#215, #111)
- Improves output when option is incorrect (#214, #160)
- Clean-up removing unused options (#213, #145)
- More user-friendly options (#212, #159)
v3.1.0 2021-05-28 (public release version)
[Features]
- Adds Finnish translation (PO file) (#192, #191)
[Fixes]
- Updates the installation instructions (#193, #191)
- Updates translations (#204, #201, #205, #200,
#207, #202)
v3.0.4 2021-02-10 (public release version)
[Features]
- Updates --raw mode output in debug level (#113)
[Fixes]
- Updates the installation instructions (#188)
- Prevents PO files to be updated unintentionally (#186)
- Fixes how command line options of profile and IP
interact (#185, #183, #182)
- Makes --sourceaddr behavior more reasonable when
selected address is not reasonable (#110, #38)
v3.0.3 (never released)
v3.0.2 (never released)
v3.0.1 2020-11-09 (public release version)
[Fixes]
- Fixed an version specification error in Makefile.PL
(#178)
v3.0.0 2020-11-06 (public release version)
[Breaking changes]
- Added "--show_testcase" and changed format when "--raw"
is selected (#157)
[Features]
- Added Norwegian language support to match added
translation to Norwegian in Zonemaster-Engine (#164, #155,
#161)
[Fixes]
- Updated/corrected translations/PO files (#174, #173)
- Corrected documentation (#172)
- Rename share/Makefile to share/GNUmakefile and
create wrapper for FreeBSD. Make FreeBSD use gmake
for share/GNUmakefile (#169)
- Corrected MANIFEST (#168)
- Updated share/Makefile (#167)
- Clean-up (#166, #158)
- Correcting --nstimes option (#163, #148)
- Updated translation/PO files handling (#151, #132, #147,
#150, #149)
v2.0.4 2020-05-22
[Fixes]
- Bumping the version of Zonemaster::CLI to be able to
upload a new package to CPAN to avoid the confusion
that the version v2.0.3.1 created. There are no
changes compared to v2.0.3.
v2.0.3.1 2020-05-15
[Fixes]
- This release fixes inconsitency between the branches in the
repository. See the release notes for v2.0.3 for the real
changes.
- There is no change in data so this release has not been
published on CPAN.
v2.0.3 2020-04-30
[Features]
- (none)
[Fixes]
- Update installation instructions (#119, #117, #137)
- Translation into Danish (#109, #61)
v2.0.2 2019-05-31 (public fix version)
[Fixes]
- Corrects in Makefile.PL the versions of Zonemaster::Engine
and Zonemaster::LDNS that this version of Zonemaster::CLI
depends on (#105)
v2.0.1 2019-05-22 (public release version)
[Status]
- This a public release fully tested before release. This version
will be available on CPAN.
[Fixes]
- Updated installation instructions for FreeBSD (#101)
- Dropped support for Ubuntu 14.04 (#99)
- Made Travis use the equivalent branch in Zonemaster-Engine (#98)
v2.0.0 2019-01-25 (pre-release version)
- Status
- This is a pre-release version not fully tested on all supported
OS's and Perl versions. This version will not be available on
CPAN.
- API change
- Replaced separate config and policy with a unified profile
(see below)
- Some changes in printout that can affect scripts using
zonemaster-cli (see below)
- Features
- Changes in printout
- Print information on STDERR if --json has been selected #84
- Diagnostics #85
- Only show spinner in human readable output mode #86
- Make --level and --stop_level case-insensitive #87
- Replaced separate config and policy with a unified profile
- Removed config and policy and added profile #70, #93
- Fixes
- All link references on Github now to zonemaster/zonemaster instead
of old dotse/zonemaster #81
- Clarify documentation of --progress option #88
- Travis tests against develop branch of Zonemaster-Engine instead
of fetching from CPAN #92
- Update travis configuration when it comes to Perl versions. #95
v1.1.3 2018-06-25
Fixed:
- Move license from Makefile.PL to main module (#67)
- Initialize gettext according to gettext documentation (#71), which
solves issues with translations in Linux (#46) and FreeBSD (#64)
- Updated the install instructions for debian and centos (#75)
- Update Installation.md for FreeBSD (uses cpan instead of cpanm) (#78)
v1.1.2 2018-01-12
Natural Language support:
- Adding support for Danish translation. (#62)
Fixed:
- Fixes issues with pre-delegation testing ("fake delegation") (#63)
v1.1.1 2017-11-02 Public release
Fixed:
- Update licensing (#58)
- Specify smallest version of Locale::TextDomain i Makefile.PL. (#57)
- Updated installation instruction (#53)
- Changed dependency from Net::LDNS to Zonemaster::LDNS due to
name space change (#51)
- Various updates to packaging (#52)
- Fix Commonmark rendering on Github. Replace NBSP with SPACE. (#49)
- Changed dependency from Zonemaster to Zonemaster::Engine due
to name space change (#43)
v1.1.0 2017-04-04 Public pre-release
- This release will not be published on CPAN since it contains updates that have
not been fully tested. Do not update production systems without verification.
- Update install instructions (#34)
- Fixes packaging issue with version numbering (#35)
- Solved --sourceaddr is not correctly implemented (#36)
- Updates so that "fake delegation" is tested correctly (#42)
1.0.5 2016-10-14
- Correcting version in CLI.pm. Update missing in 1.0.4.
1.0.4 2016-10-14
- Updating README.md and USING.md
- Better way to check ExtUtils::MakeMaker version
- Introduced MANIFEST.SKIP
1.0.3 2015-12-22
- Added JSON streaming
- Changed all instances from .SE to IIS
1.0.2 2015-06-25
- Allow lookup of nameservers for undelegated tests
- Net::LDNS::to_idn() takes Perl characters, not octets with utf-8 data
- Fixed display bug when using --nstimes with --no-ipv6 or --no-ipv4
1.0.1 2015-04-07
- State clearly that a given name is not a domain when it is not a domain
- Send "Net::LDNS not compiled with libidn" to STDERR, not STDOUT
1.0.0 2014-12-11 Release version
1.000000 2014-12-11 Public beta release.
0.001002 2014-11-26
0.001001 2014-11-19
0.001000 2014-11-17
0.03 2014-10-30
0.01 2014-05-25
- initial CPAN release

37
zonemaster-cli/Dockerfile Normal file
View File

@@ -0,0 +1,37 @@
FROM zonemaster/engine:local AS build
RUN apk add --no-cache \
make \
perl-app-cpanminus \
perl-json-xs \
perl-lwp-protocol-https \
perl-mojolicious \
perl-test-deep \
perl-test-differences \
perl-try-tiny \
&& cpanm --notest --no-wget --from https://cpan.metacpan.org/ \
JSON::Validator
ARG version
COPY ./Zonemaster-CLI-${version}.tar.gz ./Zonemaster-CLI-${version}.tar.gz
RUN cpanm --notest --no-wget \
./Zonemaster-CLI-${version}.tar.gz
FROM zonemaster/engine:local
RUN apk add --no-cache \
perl-json-xs \
perl-try-tiny
COPY --from=build /usr/local/bin/zonemaster-cli /usr/local/bin/zonemaster-cli
# Include all the Perl modules we built
COPY --from=build /usr/local/lib/perl5/site_perl /usr/local/lib/perl5/site_perl
COPY --from=build /usr/local/share/perl5/site_perl /usr/local/share/perl5/site_perl
USER nobody:nogroup
ENTRYPOINT [ "zonemaster-cli" ]
CMD [ "--help" ]

42
zonemaster-cli/LICENSE Normal file
View File

@@ -0,0 +1,42 @@
### Code license
Copyright (c) The Swedish Internet Foundation (<https://internetstiftelsen.se/en/>)
Copyright (c) AFNIC (<https://www.afnic.fr/en/>)
All rights reserved.
Copyright belongs to external contributor where applicable.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
Redistributions in binary form must reproduce the above copyright notice, this
list of conditions and the following disclaimer in the documentation and/or
other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
### Documentation license
Copyright (c) The Swedish Internet Foundation (<https://internetstiftelsen.se/en/>)
Copyright (c) AFNIC (<https://www.afnic.fr/en/>)
All rights reserved.
Copyright belongs to external contributor where applicable.
Creative Commons Attribution 4.0 International License
You should have received a copy of the license along with this
work. If not, see <https://creativecommons.org/licenses/by/4.0/>.

40
zonemaster-cli/MANIFEST Normal file
View File

@@ -0,0 +1,40 @@
Changes
CONTRIBUTING.md
inc/Module/Install.pm
inc/Module/Install/Base.pm
inc/Module/Install/Can.pm
inc/Module/Install/External.pm
inc/Module/Install/Fetch.pm
inc/Module/Install/Makefile.pm
inc/Module/Install/Metadata.pm
inc/Module/Install/Scripts.pm
inc/Module/Install/Share.pm
inc/Module/Install/Win32.pm
inc/Module/Install/WriteAll.pm
lib/Zonemaster/CLI.pm
lib/Zonemaster/CLI/TestCaseSet.pm
LICENSE
Makefile.PL
MANIFEST This list of files
META.yml
README.md
script/zonemaster-cli
share/GNUmakefile
share/Makefile
share/locale/da/LC_MESSAGES/Zonemaster-CLI.mo
share/locale/es/LC_MESSAGES/Zonemaster-CLI.mo
share/locale/fi/LC_MESSAGES/Zonemaster-CLI.mo
share/locale/fr/LC_MESSAGES/Zonemaster-CLI.mo
share/locale/nb/LC_MESSAGES/Zonemaster-CLI.mo
share/locale/sl/LC_MESSAGES/Zonemaster-CLI.mo
share/locale/sv/LC_MESSAGES/Zonemaster-CLI.mo
t/00-load.t
t/pod.t
t/test_case_set.t
t/usage.fake-data.data
t/usage.fake-root.data
t/usage.hints
t/usage.normal.data
t/usage.profile
t/usage.t
t/usage.wrapper.pl

View File

@@ -0,0 +1,83 @@
^\.perltidyrc$
^\.perlcriticrc$
^\.travis\.yml$
^MANIFEST\.SKIP$
^Dockerfile$
# PO files are not present in the distribution package, tests of those are irrelevant there.
t/po-files.t
\.tar\.gz$
\.bak$
\.po$
# Skip MO files directly under share
^share/[^/]*\.mo$
^share/Zonemaster-CLI.pot$
^share/update-po$ # PO files are exluded from dist, which makes this script meaningless in dist
# Exclude Github control files
^\.github/
#!start included /usr/share/perl/5.20/ExtUtils/MANIFEST.SKIP
# Avoid version control files.
\bRCS\b
\bCVS\b
\bSCCS\b
,v$
\B\.svn\b
\B\.git\b
\B\.gitignore\b
\b_darcs\b
\B\.cvsignore$
# Avoid VMS specific MakeMaker generated files
\bDescrip.MMS$
\bDESCRIP.MMS$
\bdescrip.mms$
# Avoid Makemaker generated and utility files.
\bMANIFEST\.bak
\bMakefile$
\bblib/
\bMakeMaker-\d
\bpm_to_blib\.ts$
\bpm_to_blib$
\bblibdirs\.ts$ # 6.18 through 6.25 generated this
# Avoid Module::Build generated and utility files.
\bBuild$
\b_build/
\bBuild.bat$
\bBuild.COM$
\bBUILD.COM$
\bbuild.com$
# Avoid temp and backup files.
~$
\.old$
\#$
\b\.#
\.bak$
\.tmp$
\.#
\.rej$
# Avoid OS-specific files/dirs
# Mac OSX metadata
\B\.DS_Store
# Mac OSX SMB mount metadata files
\B\._
# Avoid Devel::Cover and Devel::CoverX::Covered files.
\bcover_db\b
\bcovered\b
# Avoid MYMETA files
^MYMETA\.
#!end included /usr/share/perl/5.20/ExtUtils/MANIFEST.SKIP
# Development mode aid for File::ShareDir
^lib/auto/share/dist/Zonemaster-CLI
# Avoid MANIFEST test
t/manifest.t

View File

@@ -0,0 +1,76 @@
use 5.014002;
use strict;
use warnings FATAL => 'all';
use inc::Module::Install;
use ExtUtils::MakeMaker ();
name 'Zonemaster-CLI';
all_from 'lib/Zonemaster/CLI.pm';
resources(
repository => 'https://github.com/zonemaster/zonemaster-cli',
bugtracker => 'https://github.com/zonemaster/zonemaster-cli/issues',
);
tests_recursive( 't' );
# "2.1.0" could be declared as "2.001" but not as "2.1"
# (see Zonemaster::LDNS below)
requires(
'Readonly' => 0,
'Net::IP::XS' => 0,
'JSON::XS' => 0,
'Locale::TextDomain' => 1.23,
'Try::Tiny' => 0,
'Zonemaster::LDNS' => 5.000001, # v5.0.1
'Zonemaster::Engine' => 8.001000, # v8.1.0
);
test_requires(
'JSON::Validator' => 0,
'Test::Differences' => 0,
);
# Make all platforms include inc/Module/Install/External.pm
requires_external_bin 'find';
if ($^O eq "freebsd") {
requires_external_bin 'gmake';
};
sub MY::postamble {
my $pure_all;
my $sharemakefile = 'share/GNUmakefile';
if ($^O eq "freebsd") {
# Make FreeBSD use gmake for share
$pure_all = "GMAKE ?= \"gmake\"\n"
. "pure_all :: $sharemakefile\n"
. "\tcd share && \$(GMAKE) all\n";
} else {
# Here Linux and GNU Make is assumed
$pure_all = "pure_all :: $sharemakefile\n"
. "\tcd share && \$(MAKE) all\n";
};
my $docker = <<'END_DOCKER';
docker-build:
docker build --tag zonemaster/cli:local --build-arg version=$(VERSION) .
docker-tag-version:
docker tag zonemaster/cli:local zonemaster/cli:$(VERSION)
docker-tag-latest:
docker tag zonemaster/cli:local zonemaster/cli:latest
END_DOCKER
return $pure_all . $docker;
};
install_script 'zonemaster-cli';
install_share;
WriteAll;

70
zonemaster-cli/README.md Normal file
View File

@@ -0,0 +1,70 @@
# Zonemaster-CLI
## Purpose
This Git repository is one of the components of the Zonemaster software and
contains the source for the Zonemaster-CLI utility.
For an overview of the Zonemaster software, please see the
[Zonemaster repository].
## Prerequisite
Before you install the Zonemaster-CLI utility, you need the Zonemaster-Engine
test framework installed. Please see the [Zonemaster Engine installation
instructions][Zonemaster-Engine installation].
## Installation
For installation, see the [installation] document.
## Configuration
This repository does not need any specific configuration.
## Docker
Zonemaster-CLI is available on [Docker Hub], and can be conveniently downloaded
and run without any installation. See [USING] Zonemaster-CLI for how to run
Zonemaster-CLI on Docker.
To build your own Docker image, see the [Docker Image Creation] documentation.
## Documentation
Run `zonemaster-cli --help` to get brief descriptions of a selection of the most
important command line options.
For complete reference documentation, see the manual page by running `man
zonemaster-cli`.
Additional end-user documentation is available in the [USING] document.
When developing Zonemaster-CLI, refer to the [development documentation].
## Participation, Contact and Bug reporting
For participation, contact and bug reporting, please see the main
[Zonemaster README].
## License
This is free software under a 2-clause BSD license. The full text of the license can
be found in the [LICENSE](LICENSE) file included in this respository.
[Development documentation]: https://github.com/zonemaster/zonemaster/blob/master/docs/public/development/cli.md
[Docker Image Creation]: https://github.com/zonemaster/zonemaster/blob/master/docs/internal/maintenance/ReleaseProcess-create-docker-image.md
[Docker Hub]: https://hub.docker.com/u/zonemaster
[Installation]: https://github.com/zonemaster/zonemaster/blob/master/docs/public/installation/zonemaster-cli.md
[USING]: https://github.com/zonemaster/zonemaster/blob/master/docs/public/using/cli.md
[Zonemaster-Engine installation]: https://github.com/zonemaster/zonemaster/blob/master/docs/public/installation/zonemaster-engine.md
[Zonemaster README]: https://github.com/zonemaster/zonemaster/blob/master/README.md
[Zonemaster repository]: https://github.com/zonemaster/zonemaster

View File

@@ -0,0 +1,973 @@
# Brief help module to define the exception we use for early exits.
package Zonemaster::Engine::Exception::NormalExit;
use v5.26;
use warnings;
use parent 'Zonemaster::Engine::Exception';
# The actual interesting module.
package Zonemaster::CLI;
use v5.26;
use warnings;
use version; our $VERSION = version->declare( "v8.0.1" );
use Locale::TextDomain 'Zonemaster-CLI';
use Encode;
use File::Slurp;
use Getopt::Long qw[GetOptionsFromArray :config gnu_compat bundling no_auto_abbrev];
use JSON::XS;
use List::Util qw[max uniq];
use Net::IP::XS;
use Pod::Usage;
use POSIX qw[setlocale LC_MESSAGES LC_CTYPE];
use Readonly;
use Scalar::Util qw[blessed];
use Time::HiRes;
use Try::Tiny;
use Zonemaster::CLI::TestCaseSet;
use Zonemaster::Engine::Exception;
use Zonemaster::Engine::Logger::Entry;
use Zonemaster::Engine::Normalization qw[normalize_name];
use Zonemaster::Engine::Translator;
use Zonemaster::Engine::Util qw[parse_hints];
use Zonemaster::Engine::Validation qw[validate_ipv4 validate_ipv6];
use Zonemaster::Engine;
use Zonemaster::LDNS;
our %numeric = Zonemaster::Engine::Logger::Entry->levels;
our $JSON = JSON::XS->new->allow_blessed->convert_blessed->canonical;
our $SCRIPT = $0;
Readonly our $EXIT_SUCCESS => 0;
Readonly our $EXIT_GENERIC_ERROR => 1;
Readonly our $EXIT_USAGE_ERROR => 2;
Readonly our $DS_RE => qr/^(?:[[:digit:]]+,){3}[[:xdigit:]]+$/;
STDOUT->autoflush( 1 );
sub my_pod2usage {
my ( %opts ) = @_;
pod2usage(
-input => $SCRIPT,
-output => $opts{output},
-verbose => $opts{verbosity},
-exitcode => 'NOEXIT',
);
return;
}
# Returns an integer representing an OS exit status.
sub run {
my ( $class, @argv ) = @_;
my $opt_count = 0;
my @opt_ds = ();
my $opt_dump_profile = 0;
my $opt_elapsed = 0;
my $opt_encoding = undef;
my $opt_help = 0;
my $opt_hints;
my $opt_ipv4 = undef;
my $opt_ipv6 = undef;
my $opt_json = undef;
my $opt_json_stream = 0;
my $opt_json_translate = undef;
my $opt_level = 'NOTICE';
my $opt_list_tests = 0;
my $opt_locale = undef;
my @opt_ns = ();
my $opt_nstimes = 0;
my $opt_profile;
my $opt_progress = undef;
my $opt_raw;
my $opt_restore;
my $opt_save;
my $opt_show_level = 1;
my $opt_show_module = 0;
my $opt_show_testcase = 0;
my $opt_sourceaddr4;
my $opt_sourceaddr6;
my $opt_stop_level = '';
my @opt_test = ();
my $opt_time = 1;
my $opt_version = 0;
{
local $SIG{__WARN__} = sub { print STDERR $_[0] };
GetOptionsFromArray(
\@argv,
'count!' => \$opt_count,
'ds=s' => \@opt_ds,
'dump-profile!' => \$opt_dump_profile,
'dump_profile!' => \$opt_dump_profile,
'elapsed!' => \$opt_elapsed,
'encoding=s' => \$opt_encoding,
'hints=s' => \$opt_hints,
'help|h|usage|?!' => \$opt_help,
'ipv4!' => \$opt_ipv4,
'ipv6!' => \$opt_ipv6,
'json!' => \$opt_json,
'json-stream!' => \$opt_json_stream,
'json_stream!' => \$opt_json_stream,
'json-translate!' => \$opt_json_translate,
'json_translate!' => \$opt_json_translate,
'level=s' => \$opt_level,
'list-tests!' => \$opt_list_tests,
'list_tests!' => \$opt_list_tests,
'locale=s' => \$opt_locale,
'ns=s' => \@opt_ns,
'nstimes!' => \$opt_nstimes,
'profile=s' => \$opt_profile,
'progress!' => \$opt_progress,
'raw!' => \$opt_raw,
'restore=s' => \$opt_restore,
'save=s' => \$opt_save,
'show-level!' => \$opt_show_level,
'show_level!' => \$opt_show_level,
'show-module!' => \$opt_show_module,
'show_module!' => \$opt_show_module,
'show-testcase!' => \$opt_show_testcase,
'show_testcase!' => \$opt_show_testcase,
'sourceaddr4=s' => \$opt_sourceaddr4,
'sourceaddr6=s' => \$opt_sourceaddr6,
'stop-level=s' => \$opt_stop_level,
'stop_level=s' => \$opt_stop_level,
'test=s' => \@opt_test,
'time!' => \$opt_time,
'version!' => \$opt_version,
)
or do {
my_pod2usage( verbosity => 0, output => \*STDERR );
return 2;
};
}
if ( $opt_help ) {
my_pod2usage( verbosity => 1, output => \*STDOUT );
say "Severity levels from highest to lowest:";
say " CRITICAL, ERROR, WARNING, NOTICE, INFO, DEBUG, DEBUG2, DEBUG3";
return 0;
}
$opt_level = uc $opt_level;
$opt_stop_level = uc $opt_stop_level;
my @accumulator;
my %counter;
my $printed_something;
if ( $opt_locale ) {
undef $ENV{LANGUAGE};
$ENV{LC_ALL} = $opt_locale;
}
# Set LC_MESSAGES and LC_CTYPE separately
# (https://www.gnu.org/software/gettext/manual/html_node/Triggering.html#Triggering)
if ( not defined setlocale( LC_MESSAGES, "" ) ) {
my $locale = ( $ENV{LANGUAGE} || $ENV{LC_ALL} || $ENV{LC_MESSAGES} );
say STDERR __x(
"Warning: setting locale category LC_MESSAGES to {locale} failed -- is it installed on this system?",
locale => $locale ) . "\n\n";
}
if ( not defined setlocale( LC_CTYPE, "" ) ) {
my $locale = ( $ENV{LC_ALL} || $ENV{LC_CTYPE} );
say STDERR __x(
"Warning: setting locale category LC_CTYPE to {locale} failed -- is it installed on this system?",
locale => $locale ) . "\n\n";
}
if ( $opt_version ) {
print_versions();
return $EXIT_SUCCESS;
}
if ( $opt_list_tests ) {
print_test_list();
return $EXIT_SUCCESS;
}
# errors and warnings
if ( defined $opt_encoding ) {
say STDERR __( "Warning: deprecated --encoding, simply remove it from your usage." );
}
if ( $opt_json_stream and defined $opt_json and not $opt_json ) {
say STDERR __( "Error: --json-stream and --no-json cannot be used together." );
return $EXIT_USAGE_ERROR;
}
if ( defined $opt_json_translate ) {
unless ( $opt_json or $opt_json_stream ) {
printf STDERR __( "Warning: --json-translate has no effect without either --json or --json-stream." )
. "\n";
}
if ( $opt_json_translate ) {
printf STDERR __( "Warning: deprecated --json-translate, use --no-raw instead." ) . "\n";
}
else {
printf STDERR __( "Warning: deprecated --no-json-translate, use --raw instead." ) . "\n";
}
}
# align values
$opt_json = 1 if $opt_json_stream;
$opt_raw //= defined $opt_json_translate ? !$opt_json_translate : 0;
# Filehandle for diagnostics output
my $fh_diag = ( $opt_json or $opt_raw or $opt_dump_profile )
? *STDERR # Structured output mode (e.g. JSON)
: *STDOUT; # Human readable output mode
my $show_progress = $opt_progress // !!-t STDOUT && !$opt_json && !$opt_raw;
if ( $opt_profile ) {
say $fh_diag __x( "Loading profile from {path}.", path => $opt_profile );
my $json = read_file( $opt_profile );
my $foo = Zonemaster::Engine::Profile->from_json( $json );
my $profile = Zonemaster::Engine::Profile->default;
$profile->merge( $foo );
Zonemaster::Engine::Profile->effective->merge( $profile );
}
if ( defined $opt_sourceaddr4 ) {
local $@;
eval {
Zonemaster::Engine::Profile->effective->set( q{resolver.source4}, $opt_sourceaddr4 );
1;
} or do {
say STDERR __x( "Error: invalid value for --sourceaddr4: {reason}", reason => $@ );
return $EXIT_USAGE_ERROR;
};
}
if ( defined $opt_sourceaddr6 ) {
local $@;
eval {
Zonemaster::Engine::Profile->effective->set( q{resolver.source6}, $opt_sourceaddr6 );
1;
} or do {
say STDERR __x( "Error: invalid value for --sourceaddr6: {reason}", reason => $@ );
return $EXIT_USAGE_ERROR;
};
}
{
my %all_methods = Zonemaster::Engine->all_methods;
my $cases = Zonemaster::CLI::TestCaseSet->new( #
Zonemaster::Engine::Profile->effective->get( q{test_cases} ),
\%all_methods,
);
for my $test ( @opt_test ) {
my @modifiers = Zonemaster::CLI::TestCaseSet->parse_modifier_expr( $test );
while ( @modifiers ) {
my $op = shift @modifiers;
my $term = shift @modifiers;
if ( !$cases->apply_modifier( $op, $term ) ) {
say STDERR __x( "Error: unrecognized term '{term}' in --test.", term => $term ) . "\n";
return $EXIT_USAGE_ERROR;
}
}
}
Zonemaster::Engine::Profile->effective->set( q{test_cases}, [ $cases->to_list ] ),
}
# These two must come after any profile from command line has been loaded
# to make any IPv4/IPv6 option override the profile setting.
if ( defined( $opt_ipv4 ) ) {
Zonemaster::Engine::Profile->effective->set( q{net.ipv4}, $opt_ipv4 );
}
if ( defined( $opt_ipv6 ) ) {
Zonemaster::Engine::Profile->effective->set( q{net.ipv6}, $opt_ipv6 );
}
if ( $opt_dump_profile ) {
do_dump_profile();
return $EXIT_SUCCESS;
}
if ( $opt_stop_level and not defined( $numeric{$opt_stop_level} ) ) {
say STDERR __x( "Failed to recognize stop level '{level}'.", level => $opt_stop_level );
return $EXIT_USAGE_ERROR;
}
if ( not defined $numeric{$opt_level} ) {
say STDERR __( "--level must be one of CRITICAL, ERROR, WARNING, NOTICE, INFO, DEBUG, DEBUG2 or DEBUG3." );
return $EXIT_USAGE_ERROR;
}
if ( $opt_restore ) {
Zonemaster::Engine->preload_cache( $opt_restore );
}
my $level_width = 0;
foreach ( keys %numeric ) {
if ( $numeric{$opt_level} <= $numeric{$_} ) {
my $width_l10n = length( decode_utf8( translate_severity( $_ ) ) );
$level_width = $width_l10n if $width_l10n > $level_width;
}
}
my $translator;
my %field_width = (
seconds => 7,
level => $level_width,
module => 12,
testcase => 14
);
my %header_names = ();
my %remaining_space = ();
# Callback defined here so it closes over the setup above.
# But we cant use it right now because the translator isnt initialized.
my $message_printer = sub {
my ( $entry ) = @_;
print_spinner() if $show_progress;
my $entry_level = $entry->level;
$counter{ uc $entry_level } += 1;
if ( $numeric{ uc $entry_level } >= $numeric{$opt_level} ) {
$printed_something = 1;
if ( $opt_json and $opt_json_stream ) {
my %r;
$r{timestamp} = $entry->timestamp if $opt_time;
$r{module} = $entry->module if $opt_show_module;
$r{testcase} = $entry->testcase if $opt_show_testcase;
$r{tag} = $entry->tag;
$r{level} = $entry_level if $opt_show_level;
$r{args} = $entry->args if $entry->args;
$r{message} = $translator->translate_tag( $entry ) unless $opt_raw;
say $JSON->encode( \%r );
}
elsif ( $opt_json and not $opt_json_stream ) {
# Don't do anything
}
else {
my $prefix = q{};
if ( $opt_time ) {
$prefix .= sprintf "%*.2f ", ${ field_width { seconds } }, $entry->timestamp;
}
if ( $opt_show_level ) {
$prefix .= $opt_raw ? $entry->level : translate_severity( $entry->level );
my $space_l10n =
${ field_width { level } } - length( decode_utf8( translate_severity( $entry_level ) ) ) + 1;
$prefix .= ' ' x $space_l10n;
}
if ( $opt_show_module ) {
$prefix .= sprintf "%-*s ", ${ field_width { module } }, $entry->module;
}
if ( $opt_show_testcase ) {
$prefix .= sprintf "%-*s ", ${ field_width { testcase } }, $entry->testcase;
}
if ( $opt_raw ) {
$prefix .= $entry->tag;
my $message = $entry->argstr;
my @lines = split /\n/, $message;
printf "%s%s %s\n", $prefix, ' ', @lines ? shift @lines : '';
for my $line ( @lines ) {
printf "%s%s %s\n", $prefix, '>', $line;
}
}
else {
if ( $entry_level eq q{DEBUG3}
and scalar( keys %{ $entry->args } ) == 1
and defined $entry->args->{packet} )
{
my $packet = $entry->args->{packet};
my $padding = q{ } x length $prefix;
$entry->args->{packet} = q{};
printf "%s%s\n", $prefix, $translator->translate_tag( $entry );
foreach my $line ( split /\n/, $packet ) {
printf "%s%s\n", $padding, $line;
}
}
else {
printf "%s%s\n", $prefix, $translator->translate_tag( $entry );
}
}
} ## end else [ if ( $opt_json and $opt_json_stream)]
} ## end if ( $numeric{ uc $entry_level...})
if ( $opt_stop_level and $numeric{ uc $entry->level } >= $numeric{$opt_stop_level} ) {
die(
Zonemaster::Engine::Exception::NormalExit->new(
{ message => "Saw message at level " . $entry->level }
)
);
}
};
# Instead, hold early messages in a temporary queue and switch to the
# actual callback when we are ready.
my @held_messages;
Zonemaster::Engine->logger->callback(
sub {
my ( $entry ) = @_;
push @held_messages, @_;
}
);
if ( @argv > 1 ) {
say STDERR __(
"Only one domain can be given for testing. Did you forget to prepend an option with '--<OPTION>'?" );
return $EXIT_USAGE_ERROR;
}
elsif ( @argv < 1 ) {
say STDERR __( "Must give the name of a domain to test." );
return $EXIT_USAGE_ERROR;
}
my ( $domain ) = @argv;
( my $errors, $domain ) = normalize_name( decode( 'utf8', $domain ) );
if ( @opt_ns ) {
local $@;
eval {
check_fake_delegation( $domain, @opt_ns );
1;
} or do {
print STDERR $@;
return $EXIT_USAGE_ERROR;
};
}
if ( @opt_ds ) {
check_fake_ds( @opt_ds );
}
if ( scalar @$errors > 0 ) {
my $error_message;
foreach my $err ( @$errors ) {
$error_message .= $err->string . "\n";
}
print STDERR $error_message;
return $EXIT_USAGE_ERROR;
}
if ( defined $opt_hints ) {
my $hints_data;
my $error = undef;
try {
my $hints_text = read_file( $opt_hints ) // die "read_file failed\n";
local $SIG{__WARN__} = \&die;
$hints_data = parse_hints( $hints_text )
}
catch {
$error = $_;
};
if ( defined $error ) {
print STDERR __x( "Error loading hints file: {message}", message => $error );
return $EXIT_USAGE_ERROR;
}
Zonemaster::Engine::Recursor->remove_fake_addresses( '.' );
Zonemaster::Engine::Recursor->add_fake_addresses( '.', $hints_data );
} ## end if ( defined $opt_hints)
# This can generate early log messages.
if ( @opt_ns ) {
local $@;
eval {
add_fake_delegation( $domain, @opt_ns );
1;
} or do {
print STDERR $@;
return $EXIT_USAGE_ERROR;
};
}
if ( @opt_ds ) {
add_fake_ds( $domain, @opt_ds );
}
if ( not $opt_raw ) {
$translator = Zonemaster::Engine::Translator->new;
$translator->locale( $opt_locale )
if $opt_locale;
%header_names = (
seconds => __( 'Seconds' ),
level => __( 'Level' ),
module => __( 'Module' ),
testcase => __( 'Testcase' ),
message => __( 'Message' )
);
foreach ( keys %header_names ) {
$field_width{$_} = _max( $field_width{$_}, length( decode_utf8( $header_names{$_} ) ) );
$remaining_space{$_} = $field_width{$_} - length( decode_utf8( $header_names{$_} ) );
}
}
if ( $opt_profile or @opt_test ) {
# Separate initialization from main output in human readable output mode
print "\n" if $fh_diag eq *STDOUT;
}
if ( not $opt_raw and not $opt_json ) {
my $header = q{};
if ( $opt_time ) {
$header .= sprintf "%s%s ", $header_names{seconds}, " " x $remaining_space{seconds};
}
if ( $opt_show_level ) {
$header .= sprintf "%s%s ", $header_names{level}, " " x $remaining_space{level};
}
if ( $opt_show_module ) {
$header .= sprintf "%s%s ", $header_names{module}, " " x $remaining_space{module};
}
if ( $opt_show_testcase ) {
$header .= sprintf "%s%s ", $header_names{testcase}, " " x $remaining_space{testcase};
}
$header .= sprintf "%s\n", $header_names{message};
if ( $opt_time ) {
$header .= sprintf "%s ", "=" x $field_width{seconds};
}
if ( $opt_show_level ) {
$header .= sprintf "%s ", "=" x $field_width{level};
}
if ( $opt_show_module ) {
$header .= sprintf "%s ", "=" x $field_width{module};
}
if ( $opt_show_testcase ) {
$header .= sprintf "%s ", "=" x $field_width{testcase};
}
$header .= sprintf "%s\n", "=" x $field_width{message};
print $header;
} ## end if ( not $opt_raw and ...)
# Now we are ready to actually print messages, including those that are
# currently in the hold queue.
while ( my $entry = pop @held_messages ) {
$message_printer->( $entry );
}
Zonemaster::Engine->logger->callback( $message_printer );
# Actually run tests!
eval { Zonemaster::Engine->test_zone( $domain ); };
if ( $@ ) {
my $err = $@;
if ( blessed $err and $err->isa( "Zonemaster::Engine::Exception::NormalExit" ) ) {
say STDERR "Exited early: " . $err->message;
}
else {
die $err; # Don't know what it is, rethrow
}
}
if ( not $opt_raw and not $opt_json ) {
if ( not $printed_something ) {
say __( "Looks OK." );
}
}
my $json_output = {};
if ( $opt_count ) {
my %entries;
foreach my $e ( @{ Zonemaster::Engine->logger->entries } ) {
$entries{$e->level}{$e->tag} += 1;
}
if ( $opt_json ) {
$json_output->{count} = {};
foreach my $level ( sort { $numeric{$b} <=> $numeric{$a} } keys %counter ) {
$json_output->{count}{by_level}{$level} = $counter{$level};
}
foreach my $level ( sort { $numeric{$b} <=> $numeric{$a} } keys %entries ) {
foreach my $tag ( sort keys %{ $entries{$level} } ) {
$json_output->{count}{by_message_tag}{$level}{$tag} = $entries{$level}{$tag};
}
}
}
else {
my $header1 = __( 'Level' );
my $max1 = length $header1;
my $header2 = __( 'Number of log entries' );
my $max2 = length $header2;
foreach my $level ( sort { $numeric{$b} <=> $numeric{$a} } keys %counter ) {
my $len = length translate_severity( $level );
$max1 = $len if $len > $max1;
}
printf "\n\n%${max1}s\t%${max2}s", $header1, $header2;
printf "\n%s\t%s\n", '=' x $max1, '=' x $max2;
foreach my $level ( sort { $numeric{$b} <=> $numeric{$a} } keys %counter ) {
printf "%${max1}s\t%${max2}d\n", translate_severity( $level ), $counter{$level};
}
my $header3 = __( 'Message tag' );
my $max3 = max map { length "$_" } ( ( map { keys %{ $_ } } ( values %entries ) ), $header3 );
my $header4 = __( 'Count' );
my $max4 = max map { length "$_" } ( ( map { values %{ $_ } } ( values %entries ) ), $header4 );
printf "\n%${max1}s\t%${max3}s\t%${max4}s", $header1, $header3, $header4;
printf "\n%${max1}s\t%${max3}s\t%${max4}s\n", '=' x $max1, '=' x $max3, '=' x $max4;
foreach my $level ( sort { $numeric{$b} <=> $numeric{$a} } keys %entries ) {
foreach my $tag ( sort keys %{ $entries{$level} } ) {
printf "%${max1}s\t%${max3}s\t%${max4}s\n", $level, $tag, $entries{$level}{$tag};
}
}
}
}
if ( $opt_nstimes ) {
my $zone = Zonemaster::Engine->zone( $domain );
my %all_nss = %{ Zonemaster::Engine::Nameserver::object_cache };
my @child_nss = @{ $zone->ns };
my @parent_nss = @{ $zone->parent->ns };
my @all_responded_nss;
foreach my $ns_name ( keys %all_nss ) {
foreach my $ns ( values %{ $all_nss{$ns_name} } ) {
push @all_responded_nss, $ns if scalar @{ $ns->times } > 0;
}
}
my %nss_filter = map { $_ => undef } ( @child_nss, @parent_nss );
my @other_nss = grep { ! exists $nss_filter{$_} } @all_responded_nss;
if ( $opt_json ) {
my @times;
my sub json_nstimes {
my ( $ns ) = @_;
return {
'ns' => $ns->string,
'max' => 1000 * $ns->max_time,
'min' => 1000 * $ns->min_time,
'avg' => 1000 * $ns->average_time,
'stddev' => 1000 * $ns->stddev_time,
'median' => 1000 * $ns->median_time,
'total' => 1000 * $ns->sum_time,
'count' => scalar @{ $ns->times }
};
}
my %section_mapping = (
'child' => \@child_nss,
'parent' => \@parent_nss,
'other' => \@other_nss
);
foreach my $section_name ( sort keys %section_mapping ) {
my @entries = map { json_nstimes( $_ ) } sort @{ $section_mapping{$section_name} };
push @times, { $section_name => \@entries };
}
$json_output->{nstimes} = \@times;
}
else {
my $header = __( 'Name servers' );
my $max = max map { length( "$_" ) } ( ( @child_nss, @parent_nss, @all_responded_nss ), $header );
printf "\n%${max}s %s\n", $header, ' Max Min Avg Stddev Median Total Count';
printf "%${max}s %s\n", '=' x $max, ' ========== ========== ========== ========== ========== =========== ===========';
my $total_queries_count = 0;
my $total_queries_times = 0;
my %nss_already_processed;
my sub print_nstimes {
my ( $ns, $max, $total_queries_count, $total_queries_times, $nss_already_processed_ref ) = @_;
my %nss_already_processed = %{ $nss_already_processed_ref };
printf "%${max}s ", $ns->string;
printf "%11.2f ", 1000 * $ns->max_time;
printf "%10.2f ", 1000 * $ns->min_time;
printf "%10.2f ", 1000 * $ns->average_time;
printf "%10.2f ", 1000 * $ns->stddev_time;
printf "%10.2f ", 1000 * $ns->median_time;
printf "%11.2f ", 1000 * $ns->sum_time;
printf "%11d\n", scalar @{ $ns->times };
$total_queries_count += scalar @{ $ns->times } unless $nss_already_processed{$ns};
$total_queries_times += ( 1000 * $ns->sum_time ) unless $nss_already_processed{$ns};
return $total_queries_count, $total_queries_times;
}
my %section_mapping = (
1 => { __( 'Child zone' ) => \@child_nss },
2 => { __( 'Parent zone' ) => \@parent_nss },
3 => { __( 'Other' ) => \@other_nss }
);
foreach my $section_order ( sort keys %section_mapping ) {
foreach my $section_header ( keys % { $section_mapping{$section_order} } ) {
printf "%s %s\n", $section_header, '-' x ( ( $max - length $section_header ) - 1 );
foreach my $section_nss ( sort @{ $section_mapping{$section_order}{$section_header} } ) {
( $total_queries_count, $total_queries_times ) =
print_nstimes( $section_nss, $max, $total_queries_count, $total_queries_times, \%nss_already_processed );
$nss_already_processed{$section_nss} = 1;
}
}
}
printf "%${max}s %s\n", '=' x $max, ' ========== ========== ========== ========== ========== =========== ===========';
printf "%${max}s %67.2f %11s\n", __( 'Grand total' ), $total_queries_times, $total_queries_count;
}
} ## end if ( $opt_nstimes )
if ( $opt_elapsed ) {
my $last = Zonemaster::Engine->logger->entries->[-1];
if ( $opt_json ) {
$json_output->{elapsed} = $last->timestamp;
}
else {
printf "\nTotal test run time: %0.1f seconds.\n", $last->timestamp;
}
}
if ( $opt_json and not $opt_json_stream ) {
my $res = Zonemaster::Engine->logger->json( $opt_level );
$res = $JSON->decode( $res );
foreach ( @$res ) {
unless ( $opt_raw ) {
my %e = %$_;
my $entry = Zonemaster::Engine::Logger::Entry->new( \%e );
$_->{message} = $translator->translate_tag( $entry );
}
delete $_->{timestamp} unless $opt_time;
delete $_->{level} unless $opt_show_level;
delete $_->{module} unless $opt_show_module;
delete $_->{testcase} unless $opt_show_testcase;
}
$json_output->{results} = $res;
}
if ( scalar keys %$json_output ) {
say $JSON->encode( $json_output );
}
if ( $opt_save ) {
Zonemaster::Engine->save_cache( $opt_save );
}
return $EXIT_SUCCESS;
} ## end sub run
sub check_fake_delegation {
my ( $domain, @ns ) = @_;
foreach my $pair ( @ns ) {
my ( $name, $ip ) = split( '/', $pair, 2 );
if ( $pair =~ tr/\/// > 1 or not $name ) {
die __( "--ns must be a name or a name/ip pair." ) . "\n";
}
( my $errors, $name ) = normalize_name( decode( 'utf8', $name ) );
if ( scalar @$errors > 0 ) {
my $error_message = "Invalid name in --ns argument:\n";
foreach my $err ( @$errors ) {
$error_message .= "\t" . $err->string . "\n";
}
die $error_message;
}
if ( $ip ) {
my $net_ip = Net::IP::XS->new( $ip );
unless ( validate_ipv4( $ip ) or validate_ipv6( $ip ) ) {
die Net::IP::XS::Error()
? "Invalid IP address in --ns argument:\n\t" . Net::IP::XS::Error() . "\n"
: "Invalid IP address in --ns argument.\n";
}
}
} ## end foreach my $pair ( @ns )
return;
} ## end sub check_fake_delegation
sub check_fake_ds {
my ( @ds ) = @_;
foreach my $str ( @ds ) {
unless ( $str =~ /$DS_RE/ ) {
say STDERR __(
"--ds ds data must be in the form \"keytag,algorithm,type,digest\". E.g. space is not permitted anywhere in the string."
);
exit( 1 );
}
}
return;
}
sub add_fake_delegation {
my ( $domain, @ns ) = @_;
my @ns_with_no_ip;
my %data;
foreach my $pair ( @ns ) {
my ( $name, $ip ) = split( '/', $pair, 2 );
( my $errors, $name ) = normalize_name( decode( 'utf8', $name ) );
if ( $ip ) {
push @{ $data{$name} }, $ip;
}
else {
push @ns_with_no_ip, $name;
}
}
foreach my $ns ( @ns_with_no_ip ) {
if ( not exists $data{$ns} ) {
$data{$ns} = undef;
}
}
return Zonemaster::Engine->add_fake_delegation( $domain => \%data );
} ## end sub add_fake_delegation
sub add_fake_ds {
my ( $domain, @ds ) = @_;
my @data;
foreach my $str ( @ds ) {
my ( $tag, $algo, $type, $digest ) = split( /,/, $str );
push @data, { keytag => $tag, algorithm => $algo, type => $type, digest => $digest };
}
Zonemaster::Engine->add_fake_ds( $domain => \@data );
return;
}
sub print_versions {
say 'Zonemaster-CLI version ' . __PACKAGE__->VERSION;
say 'Zonemaster-Engine version ' . $Zonemaster::Engine::VERSION;
say 'Zonemaster-LDNS version ' . $Zonemaster::LDNS::VERSION;
say 'NL NetLabs LDNS version ' . Zonemaster::LDNS::lib_version();
return;
}
my @spinner_strings = ( ' | ', ' / ', ' - ', ' \\ ' );
sub print_spinner {
state $counter = 0;
state $last_spin = [ 0, 0 ];
my $time = [ Time::HiRes::gettimeofday() ];
if ( Time::HiRes::tv_interval( $last_spin, $time ) > 0.1 ) {
$last_spin = $time;
printf "%s\r", $spinner_strings[ $counter++ % 4 ];
}
}
sub print_test_list {
my %methods = Zonemaster::Engine->all_methods;
my $maxlen = max map {
map { length( $_ ) }
@$_
} values %methods;
foreach my $module ( sort keys %methods ) {
say $module;
foreach my $method ( sort @{ $methods{$module} } ) {
printf " %${maxlen}s\n", $method;
}
print "\n";
}
return;
}
sub do_dump_profile {
my $json = JSON::XS->new->canonical->pretty;
print $json->encode( Zonemaster::Engine::Profile->effective->{q{profile}} );
return;
}
sub translate_severity {
my $severity = shift;
if ( $severity eq "DEBUG" ) {
return __( "DEBUG" );
}
elsif ( $severity eq "INFO" ) {
return __( "INFO" );
}
elsif ( $severity eq "NOTICE" ) {
return __( "NOTICE" );
}
elsif ( $severity eq "WARNING" ) {
return __( "WARNING" );
}
elsif ( $severity eq "ERROR" ) {
return __( "ERROR" );
}
elsif ( $severity eq "CRITICAL" ) {
return __( "CRITICAL" );
}
else {
return $severity;
}
} ## end sub translate_severity
sub _max {
my ( $a, $b ) = @_;
$a //= 0;
$b //= 0;
return ( $a > $b ? $a : $b );
}
1;
__END__
=pod
=encoding UTF-8
=head1 NAME
Zonemaster::CLI - run Zonemaster tests from the command line
=head1 AUTHORS
Vincent Levigneron <vincent.levigneron at nic.fr>
- Current maintainer
Calle Dybedahl <calle at init.se>
- Original author
=head1 LICENSE
This is free software under a 2-clause BSD license. The full text of the license can
be found in the F<LICENSE> file included with this distribution.
=cut

View File

@@ -0,0 +1,271 @@
package Zonemaster::CLI::TestCaseSet;
use 5.014;
use warnings;
use utf8;
use Carp qw( croak );
=head1 NAME
Zonemaster::CLI::TestCaseSet - Manage and modify Zonemaster test case selections
=head1 SYNOPSIS
use Zonemaster::CLI::TestCaseSet;
# Define the names of the available test modules and their test cases
my $schema = {
alpha => [qw( alpha01 alpha02 alpha03 )],
beta => [qw( beta01 beta02 )],
};
# Construct an initial selection of test cases
my $selection = Zonemaster::CLI::TestCaseSet->new(
[qw( alpha01 alpha02 alpha03 beta01 )],
$schema,
);
# Parse and apply a modifier expression
my @modifiers = Zonemaster::CLI::TestCaseSet->parse_modifier_expr( '-alpha+alpha02' );
while ( @modifiers ) {
my ( $op, $term ) = splice @modifiers, 0, 2;
$selection->apply_modifier( $op, $term )
or die "Error: Unrecognized term '$term'.\n";
}
# Output final test case selection
print join( ' ', $selection->to_list ); # alpha02 beta01
=head1 DESCRIPTION
Zonemaster::CLI::TestCaseSet represents a mutable selection of test cases,
together with an immutable schema defining available test modules and their
associated test cases.
The schema is defined as a mapping of test module names to their associated test
case names.
The selection can be adjusted using modifier expressions.
=head2 MODIFIER EXPRESSIONS
A modifier expression describes a change to the current selection.
Expressions combine terms using operators, e.g., C<'-alpha+alpha02'>.
These operators are supported:
=over 4
=item C<'+'> (union)
Add test cases to the current selection.
The set of test cases to add is the expansion of C<$term>.
=item C<'-'> (difference)
Remove test cases from the current selection.
The set of test cases to remove is the expansion of C<$term>.
=item C<''> (replace)
Replace the current selection.
The new selection is the set of test cases expanded from C<$term>.
=back
Terms expand into sets of test cases in one of three ways:
=over 4
=item C<all>
Expands to all available test cases defined by the schema.
=item Test module name
Expands to all test cases associated with the test module.
=item Test case name
Expands directly to the specified test case itself.
Test cases may be specified plainly (e.g., C<Case10>) or fully qualified
(module/testcase, e.g., C<Case/Case10>).
=back
Term matching is case-insensitive.
=cut
=head1 CONSTRUCTORS
=head2 new( $selection, $schema )
Construct a new TestCaseSet object.
=over 4
=item C<$selection> (arrayref)
Initial selection of test case names.
=item C<$schema> (hashref)
A hash mapping test module names to arrays of their associated test case names.
=back
Dies if:
- Any test case name in C<$schema> is repeated.
- C<$selection> contains names not found in C<$schema>.
=cut
sub new {
my ( $class, $selection, $schema ) = @_;
my %cases = map { lc $_ => 1 } map { @{$_} } values %$schema;
for my $case ( @$selection ) {
if ( !exists $cases{ lc $case } ) {
croak "Unrecognized initial test case '$case'";
}
}
my $obj = {
_selection => { map { lc $_ => 1 } @$selection },
_terms => _get_schema_terms( $schema ),
};
bless $obj, $class;
return $obj;
}
=head1 CLASS METHODS
parse_modifier_expr( $modifier_expr )
Parse a string containing a modifier expression and returns a list of
alternating operators and terms.
The returned list always starts with an operator.
For example, parsing C<'-alpha+beta02'> returns:
('-', 'alpha', '+', 'beta02')
=cut
sub parse_modifier_expr {
my ( $class, $modifier_expr ) = @_;
my @modifiers;
for my $op_and_term ( split /(?=[+-])/, $modifier_expr ) {
$op_and_term =~ /([+-]?)(.*)/;
my ( $op, $term ) = ( $1, $2 );
push @modifiers, ( $op, $term );
}
return @modifiers;
}
=head1 INSTANCE METHODS
=head2 apply_modifier( $operator, $term )
Update the selection using the given operator and term.
Returns true if successful, or false if the term could not be expanded based on
the schema.
Dies if the operator is invalid.
=head3 Example:
$selection->apply_modifier('+', 'beta')
or die "Unrecognized term";
=cut
sub apply_modifier {
my ( $self, $op, $term ) = @_;
my $cases_ref = $self->{_terms}{ lc $term };
if ( !defined $cases_ref ) {
return 0;
}
if ( $op eq '' ) {
$self->{_selection} = {};
$op = '+';
}
if ( $op eq '-' ) {
for my $case ( @$cases_ref ) {
delete $self->{_selection}{$case};
}
}
elsif ( $op eq '+' ) {
for my $case ( @$cases_ref ) {
$self->{_selection}{$case} = 1;
}
}
else {
croak "Unrecognized operator '$op'";
}
return 1;
} ## end sub apply_modifier
=head2 to_list
Return a lowercase list of the currently selected test case names.
=cut
sub to_list {
my ( $self ) = @_;
return sort keys %{ $self->{_selection} };
}
sub _get_schema_terms {
my ( $schema ) = @_;
my $terms = {};
$terms->{all} = [];
for my $module ( keys %$schema ) {
if ( lc $module eq 'all' ) {
croak "test module name must not be 'all'";
}
if ( $module =~ qr{/} ) {
croak "test module name contains forbidden character '/': '$module'";
}
if ( exists $terms->{ lc $module } ) {
croak "found test module with same name as another test case or test module: '$module'";
}
$terms->{ lc $module } = [];
for my $case ( @{ $schema->{$module} } ) {
if ( lc $case eq 'all' ) {
croak "test case name must not be 'all'";
}
if ( $case =~ qr{/} ) {
croak "test case name contains forbidden character '/': '$case'";
}
if ( exists $terms->{ lc $case } ) {
croak "found test case with same name as another test case or test module: '$case'";
}
$terms->{ lc $case } = [$case];
$terms->{ lc "$module/$case" } = [$case];
push @{ $terms->{ lc $module } }, $case;
push @{ $terms->{all} }, $case;
}
} ## end for my $module ( keys %$schema)
return $terms;
} ## end sub _get_schema_terms
1;

View File

@@ -0,0 +1 @@
../../../../share/

View File

@@ -0,0 +1,472 @@
#!/usr/bin/env perl
use 5.14.2;
use warnings;
use Zonemaster::CLI;
use File::Spec;
use autodie;
sub read_conf_file {
# Returns list of command line parameters. List can be empty.
my ( $conf_file ) = @_;
my @lines;
open my $fh, '<', $conf_file;
while ( <$fh> ) {
chomp;
next if /^\s*$/;
next if /^\s*#/;
push @lines, $_;
}
return @lines;
}
# Load default arguments from file in home directory, if any
# This must be loaded before any global file to make the local
# file take precedence
my $home_dir = ( ( getpwuid( $< ) )[7] ) || $ENV{HOME};
my $home_conf_file = File::Spec->catfile( $home_dir, '.zonemaster', 'cli.args' );
if ( -r $home_conf_file ) {
my @lines = read_conf_file( $home_conf_file );
unshift @ARGV, @lines;
}
# Load default arguments from global file, if any
my @global_conf = ( '/etc/zonemaster/cli.args', '/usr/local/etc/zonemaster/cli.args' ); # Order is significant.
my $global_conf_file;
for my $p ( @global_conf ) {
if ( -e $p and -r $p ) {
$global_conf_file = $p;
last;
}
}
if ( defined $global_conf_file ) {
my @lines = read_conf_file( $global_conf_file );
unshift @ARGV, @lines;
}
eval {
my $exitstatus = Zonemaster::CLI->run( @ARGV );
exit $exitstatus;
};
print STDERR $@;
exit $Zonemaster::CLI::EXIT_GENERIC_ERROR;
=head1 NAME
zonemaster-cli - run Zonemaster tests from the command line
=head1 SYNOPSIS
zonemaster-cli [--help | --version | --list-tests]
zonemaster-cli [OPTIONS] --dump-profile
zonemaster-cli [OPTIONS] DOMAINNAME
=head1 DESCRIPTION
L<zonemaster-cli> is a command-line interface to the Zonemaster test engine.
It takes instructions the user provides as command line arguments, transforms
them into suitable API calls to the engine, runs the test suite and prints the
resulting messages. By default, the messages will be translated by the engine's
translation module, with the corresponding timestamp and logging level when
printed. See the available options below.
=head1 OPTIONS
=head2 Special Options
=over 4
=item B<-h>, B<--help>
Print brief usage information and exit.
(run `man zonemaster-cli` for the full manual page)
=item B<--version>
Print version information and exit.
=for :man The printed version numbers are the versions of this program as well as the ones from the underlying
Zonemaster test engine.
=item B<--list-tests>
Print all test cases listed in the test modules, then exit.
=item B<--dump-profile>
Print the effective profile used in JSON format, then exit.
=back
=head2 Testing Options
=over 4
=item B<--test>=EXPR
Specify which test cases to run by setting the C<test_cases> property in the profile.
The EXPR is a flexible expression consisting of terms (individual test cases, test
modules, or all) and operators (C<+> to add, C<-> to remove).
For details, see C<man zonemaster-cli>.
=begin :man
An EXPR starting with a term (e.g., C<all>, C<nameserver01>) I<replaces>
the current selection.
An EXPR starting with an operator (C<+> or C<->) I<modifies> the current selection.
An empty EXPR (C<--test=>) does not modify the selection (i.e., it is a no-op).
Multiple C<--test> options may be specified; they are applied in the order they are given.
When used together with the C<--profile>, the profile option is applied first.
Use C<--list-tests> to list all test cases and test modules in the default profile.
Examples:
=over 4
=item --test=B<all>
selects all test cases in the default profile, i.e. overrides any custom profile.
=item --test=B<nameserver>
selects all test cases in the Nameserver test module in the default profile.
=item --test=B<nameserver01>
selects only the Nameserver01 test case.
=item --test=B<dnssec-dnssec05>
selects all DNSSEC test cases except DNSSEC05.
=item --test=B<all-nameserver+nameserver03>
selects all test cases, excludes the Nameserver test cases, and adds back
Nameserver03.
=item --test=B<-dnssec>
removes all DNSSEC test cases from the selection.
=item --test=B<+syntax01+syntax02>
adds the Syntax01 and Syntax02 test cases to the selection.
=item --test=
empty value is valid, but does not change the selection of test cases
Specifying C<--test=> without an argument is a no-op.
Having this possibility may come in handy when invoking zonemaster-cli from scripts.
=back
=end :man
=item B<--level>=LEVEL
Specify the minimum level of a message to be printed.
(default: NOTICE)
=for :man Messages with this level
(or higher) will be printed. The levels are, from highest to lowest:
CRITICAL, ERROR, WARNING, NOTICE, INFO, DEBUG, DEBUG2 and DEBUG3.
The lowest three levels (DEBUG) add a significant amount of messages to be shown.
They reveal some of the internal workings of the test engine, and are probably
not useful for most users.
=begin :man
=item B<--stop-level>=LEVEL
Specify the minimum severity level after which the testing suite is terminated.
(default: the empty string)
=for :man When set to the empty string, testing is allowed to complete normally
no matter what messages are emitted.
=for :man The levels are, from highest to lowest: CRITICAL, ERROR, WARNING, NOTICE,
INFO, DEBUG, DEBUG2 and DEBUG3.
=end :man
=item B<--[no-]progress>
Print an activity indicator ("spinner").
(default: enabled if the process' standard output is a TTY)
=for :man Useful to know that something is happening during a run.
=item B<--[no-]ipv4>, B<--[no-]ipv6>
Enable or disable queries over IPv4 or IPv6.
(default: both enabled)
=begin :man
=item B<--sourceaddr4>=IPADDR, B<--sourceaddr6>=IPADDR
Set IPv4 or IPv6 source address for DNS queries.
=for :man Setting an address not correctly configured on a local network interface
fails silently.
=end :man
=item B<--profile>=FILE
Override the Zonemaster Engine default profile data with values from
the given profile JSON file.
=back
=head2 Formatting Options
=over 4
=item B<--[no-]json>
Print results as JSON instead of human language.
(default: disabled)
=begin :man
=item B<--[no-]json-stream>
Stream the results as JSON.
(default: disabled)
=for :man Useful to follow the progress in a machine-readable way.
=item B<--[no-]raw>
Print messages as raw dumps (message identifiers) instead of translating them
to human language.
=end :man
=item B<--locale>=LOCALE
Specify which locale to be used by the translation system.
(default: system locale or English)
=for :man If not given, the
translation system itself will look at environment variables to try and guess.
If the requested translation does not exist, it will fall back to the local
locale, and if that does not exist either, to English.
=begin :man
=item B<--[no-]time>
Print the timestamp for each message.
(default: enabled)
=item B<--[no-]show-level>
Print the severity level for each message.
(default: enabled)
=item B<--[no-]show-module>
Print the name of the module which produced the message.
(default: disabled)
=item B<--[no-]show-testcase>
Print the name of the test case (test case identifier) which produced the message.
(default: disabled)
=end :man
=back
=begin :man
=head2 Summary Options
=over 4
=item B<--[no-]count>
Print a summary, at the end of a run, of the numbers of messages for each severity
level that were logged during the run, as well as a count of each message tag.
(default: disabled)
=item B<--[no-]nstimes>
Print a summary, at the end of a run, of various metrics related to the times
(in milliseconds) each name server took to answer, as well as a count of the
number of sent queries per name server.
(default: disabled)
=item B<--[no-]elapsed>
Print elapsed time (in seconds) at end of a run.
(default: disabled)
=back
=head2 Undelegated Test Options
=over 4
=item B<--ns>=DOMAINNAME, B<--ns>=DOMAINNAME/IPADDR
Provide information about a nameserver, for undelegated tests.
=for :man The argument
must be either: (i) a domain name and an IP address, separated by a single
slash character (/), or (ii) only a domain name, in which case a A and AAAA
records lookup for that name is done in the live global DNS tree (unless
overridden by --hints) and from which the results of that lookup will be used.
=for :man This switch can be given multiple times. As long as any of these switches
are present, their aggregated content will be used as the
entirety of the parent-side delegation information.
=item B<--ds>=KEYTAG,ALGORITHM,TYPE,DIGEST
Provide a DS record for undelegated testing (that is, a test where the
delegating nameserver information is given via --ns switches).
=for :man The four pieces
of data (keytag, algorithm, type, digest) should be in the same format they would
have in a zone file.
=item B<--hints>=FILE
Name of a root hints file to override the defaults.
=back
=head2 Cache Options
=over 4
=item B<--save>=FILE
Write the contents of the accumulated DNS packet cache to a file with the given name
after the testing suite has finished running.
=item B<--restore>=FILE
Prime the DNS packet cache with the contents from the file with the given name
before starting the testing suite.
=for :man The format of the file should be from one produced by the --save
switch.
=back
=head2 Deprecated Options
=over 4
=item B<--encoding>=ENCODING
Deprecated: Simply remove it from your usage. It is ignored.
=item B<--[no-]json-translate>
Deprecated since v2023.1, use --no-raw instead.
=for :man For streaming JSON output, include the translated message of the tag.
=back
=head2 Option Aliases
These options are provided for compatibility with older scripts.
The first two are aliases for C<--help>.
The rest are aliases for their namesakes spelled with dash C<-> instead of
underscore C<_>.
=over 4
=item B<-?>
=item B<--usage>
=item B<--dump_profile>
=item B<--[no-]json_stream>
=item B<--[no-]json_translate>
=item B<--list_tests>
=item B<--[no-]show_level>
=item B<--[no-]show_module>
=item B<--[no-]show_testcase>
=item B<--stop_level>=LEVEL
=back
=end :man
=head1 EXAMPLES
zonemaster-cli zonemaster.net
zonemaster-cli --test=delegation --level=info --no-time zonemaster.net
zonemaster-cli --test=delegation01 --level=debug zonemaster.net
zonemaster-cli --list-tests
=head1 PROFILES
The testing and result analysis performed by Zonemaster Engine is always
guided by a profile.
Zonemaster Engine has a default profile with sensible defaults.
Zonemaster CLI allows users to override the default profile data with
values from a profile JSON file with the C<--profile> option.
For details on profiles and how they are represented in files, see
L<Zonemaster::Engine::Profile>.
=head1 CONFIGURATION
If there is a readable file F</etc/zonemaster/cli.args> (Linux style), each line
in that file will be prepended as an argument on the command line. If no
F</etc/zonemaster/cli.args> is found (or is not readable) but
F</usr/local/etc/zonemaster/cli.args> (FreeBSD style) is found and readable then
that file will be used instead. Only one global file is loaded.
If there is a readable file F<.zonemaster/cli.args> in the user's home
directory, it will be used in the same way even when a global file has been
loaded. Any argument in user's F<cli.args> will override the same argument in the
global config file.
For example, if one would like to by default run with the log
level set to DEBUG and with translation to human-readable messages turned off,
one could put this in the config file:
--raw
--level=DEBUG
Only one argument per line. If the argument has a value there must be a "="
between argument and value. A line starting with "#" is a comment. Comments
cannot be added on lines with arguments.
Any arguments actually given on the command line will override what is in any of
the loaded config files.
=head1 SEE ALSO
More complete documentation on Zonemaster and its tests can be found on
L<https://doc.zonemaster.net>.
=head1 AUTHOR
Calle Dybedahl <calle@init.se> and others from the Zonemaster project

View File

@@ -0,0 +1,52 @@
.POSIX:
.SUFFIXES: .po .mo
.PHONY: all check-msg-args dist extract-pot tidy-po show-fuzzy update-po new-po check-po
POFILES := $(shell find . -maxdepth 1 -type f -name '*.po' -exec basename {} \;)
MOFILES := $(POFILES:%.po=%.mo)
POTFILE = Zonemaster-CLI.pot
PMFILES := $(shell find ../lib -type f -name '*.pm' | sort)
all: $(MOFILES)
# Tidy the formatting of all PO files
tidy-po:
@tmpdir="`mktemp -d tidy-po.XXXXXXXX`" ;\
trap 'rm -rf "$$tmpdir"' EXIT ;\
for f in $(POFILES) ; do msgcat $$f -o $$tmpdir/$$f && mv -f $$tmpdir/$$f $$f ; done
update-po: extract-pot
@for f in $(POFILES) ; do msgmerge --update --backup=none --quiet --no-location $(MSGMERGE_OPTS) $$f $(POTFILE) ; done
# Create a new empty PO file with basename provided with the POLANG variable
# Update the Language field in the header
new-po: extract-pot
@[ -n "$(POLANG)" ] || ( echo "Usage: make POLANG=xx new-po" && exit 1 )
@cp $(POTFILE) $(POLANG).po
@perl -pi -e 's/^("Project-Id-Version:) .+(\\n)/$$1 1.0.0$$2/;' \
-e 's/^("Language-Team:) .+(\\n)/$$1 Zonemaster Team$$2/;' \
-e 's/^"Language: /$$&$(POLANG)/;' \
-e 's/^("Content-Type:.+charset=)CHARSET/$${1}UTF-8/;' $(POLANG).po
@perl -ni -e 'print unless /^#( |$$)/' $(POLANG).po
# Check the msgid/msgstr pair for some inconsistencies between them in the
# selected PO file and report on standard error any errors found. The PO file
# is not updated.
check-po:
@for f in $(POFILES) ; do msgfmt -c $$f ; done
extract-pot:
@xgettext --output $(POTFILE) --sort-by-file --add-comments --language=Perl --from-code=UTF-8 -k__ -k\$$__ -k%__ -k__x -k__n:1,2 -k__nx:1,2 -k__xn:1,2 -kN__ -kN__n:1,2 -k__p:1c,2 -k__np:1c,2,3 -kN__p:1c,2 -kN__np:1c,2,3 $(PMFILES)
$(POTFILE): extract-pot
.po.mo:
@msgfmt -o $@ $<
@mkdir -p locale/`basename $@ .mo`/LC_MESSAGES
@ln -vf $@ locale/`basename $@ .mo`/LC_MESSAGES/Zonemaster-CLI.mo
show-fuzzy:
@for f in $(POFILES) ; do msgattrib --only-fuzzy $$f ; done
check-msg-args:
@for f in $(POFILES) ; do ../util/check-msg-args $$f ; done

View File

@@ -0,0 +1,15 @@
# This is a wrapper for BSD Make (FreeBSD) to execute
# GNU Make (gmake) and the primary makefile GNUmakefile.
GNUMAKE?= gmake
FILES != ls *
# File targets should be evaluated by gmake.
.PHONY: all $(FILES)
all:
@${GNUMAKE} $@
.DEFAULT:
@${GNUMAKE} $@

180
zonemaster-cli/share/da.po Normal file
View File

@@ -0,0 +1,180 @@
msgid ""
msgstr ""
"Project-Id-Version: 0.0.5\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-02-26 17:21+0100\n"
"PO-Revision-Date: 2023-06-02 11:12+0200\n"
"Last-Translator: haarbo@dk-hostmaster.dk\n"
"Language-Team: Zonemaster Team\n"
"Language: da\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Generator: Poedit 3.3.1\n"
#, perl-brace-format
msgid ""
"Warning: setting locale category LC_MESSAGES to {locale} failed -- is it "
"installed on this system?"
msgstr ""
"Advarsel: Kunne ikke sætte locale kategori LC_MESSAGES til {locale} (er det "
"installeret på dette system?)."
#, perl-brace-format
msgid ""
"Warning: setting locale category LC_CTYPE to {locale} failed -- is it "
"installed on this system?"
msgstr ""
"Advarsel: Kunne ikke sætte locale kategori LC_CTYPE til {locale} (er det "
"installeret på dette system?)."
msgid "Warning: deprecated --encoding, simply remove it from your usage."
msgstr "Advarsel: --encoding er forældet, fjern det blot fra din brug."
msgid "Error: --json-stream and --no-json cannot be used together."
msgstr "Fejl: --json-stream og --no-json kan ikke bruges samtidigt."
msgid ""
"Warning: --json-translate has no effect without either --json or --json-"
"stream."
msgstr ""
"Advarsel: --json-translate har ingen virkning uden enten --json eller --json-"
"stream."
msgid "Warning: deprecated --json-translate, use --no-raw instead."
msgstr "Advarsel: --json-translate er forældet, brug --no-raw i stedet."
msgid "Warning: deprecated --no-json-translate, use --raw instead."
msgstr "Advarsel: --no-json-translate er forældet, brug --raw i stedet."
#, perl-brace-format
msgid "Loading profile from {path}."
msgstr "Indlæser profil fra {path}."
#, perl-brace-format
msgid "Error: invalid value for --sourceaddr4: {reason}"
msgstr "Fejl: ugyldig værdi for --sourceaddr4: {reason}"
#, perl-brace-format
msgid "Error: invalid value for --sourceaddr6: {reason}"
msgstr "Fejl: ugyldig værdi for --sourceaddr6: {reason}"
#, perl-brace-format
msgid ""
"Error: Invalid input '{cli_arg}' in --test. There must be at most one slash "
"('/') character."
msgstr ""
"Fejl: Ugyldigt input '{cli_arg}' i --test. Der må højst være ét skråstreg "
"('/') tegn."
#, perl-brace-format
msgid ""
"Error: Unrecognized test case '{testcase}' in --test. Use --list-tests for a "
"list of valid choices."
msgstr ""
"Fejl: Ugenkendt test-case '{testcase}' i --test. Brug --list-tests for en "
"liste over gyldige valg."
#, perl-brace-format
msgid ""
"Error: Unrecognized test module '{module}' in --test. Use --list-tests for a "
"list of valid choices."
msgstr ""
"Fejl: Ugenkendt testmodul '{module}' i --test. Brug --list-tests for en "
"liste over gyldige valg."
#, perl-brace-format
msgid "Error: Invalid input '{cli_arg}' in --test."
msgstr "Fejl: Ugyldigt input '{cli_arg}' i --test."
#, perl-brace-format
msgid ""
"Notice: Engine does not have test case '{testcase}' enabled in the profile. "
"Forcing..."
msgstr ""
"Bemærk: Motoren har ikke test-case '{testcase}' aktiveret i profilen. "
"Tvinger..."
#, perl-brace-format
msgid "Failed to recognize stop level '{level}'."
msgstr "Kunne ikke genkende stopniveau '{level}'."
msgid ""
"--level must be one of CRITICAL, ERROR, WARNING, NOTICE, INFO, DEBUG, DEBUG2 "
"or DEBUG3."
msgstr ""
"--level skal være enten CRITICAL, ERROR, WARNING, NOTICE, INFO, DEBUG, "
"DEBUG2 eller DEBUG3."
msgid ""
"Only one domain can be given for testing. Did you forget to prepend an "
"option with '--<OPTION>'?"
msgstr ""
"Der kan kun angives ét domæne til test. Har du glemt at sætte '--<OPTION>' "
"foran en indstilling?"
msgid "Must give the name of a domain to test."
msgstr "Domænenavn, der skal testes, skal angives."
#, perl-brace-format
msgid "Error loading hints file: {message}"
msgstr "Fejl ved indlæsning af hints-fil: {message}"
msgid "Seconds"
msgstr "Sekunder"
msgid "Level"
msgstr "Niveau"
msgid "Module"
msgstr "Modul"
msgid "Testcase"
msgstr "Testcase"
msgid "Message"
msgstr "Besked"
msgid "Looks OK."
msgstr "Ser OK ud."
msgid ""
"\n"
"\n"
" Level\tNumber of log entries"
msgstr ""
"\n"
"\n"
" Niveau\tAntal logbeskeder"
#, perl-format
msgid "%8s\t%5d entries.\n"
msgstr "%8s\t%5d beskeder.\n"
msgid "--ns must be a name or a name/ip pair."
msgstr "--ns skal være et navn eller et navn/IP-adresse par."
msgid ""
"--ds ds data must be in the form \"keytag,algorithm,type,digest\". E.g. "
"space is not permitted anywhere in the string."
msgstr ""
"--ds ds-data skal være i formatet \"keytag,algorithm,type,digest\". F.eks. "
"er mellemrum ikke tilladt noget sted i strengen."
msgid "DEBUG"
msgstr "FEJLSØG"
msgid "INFO"
msgstr "INFO"
msgid "NOTICE"
msgstr "NOTER"
msgid "WARNING"
msgstr "ADVARSEL"
msgid "ERROR"
msgstr "FEJL"
msgid "CRITICAL"
msgstr "KRITISK"

387
zonemaster-cli/share/es.po Normal file
View File

@@ -0,0 +1,387 @@
msgid ""
msgstr ""
"Project-Id-Version: 0.0.5\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-12-16 19:52-0300\n"
"PO-Revision-Date: 2024-12-16 20:05-0300\n"
"Last-Translator: hsalgado@vulcano.cl\n"
"Language-Team: Zonemaster Team\n"
"Language: es\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Generator: Poedit 3.2.2\n"
#, perl-brace-format
msgid ""
"Warning: setting locale category LC_MESSAGES to {locale} failed -- is it "
"installed on this system?"
msgstr ""
"Advertencia: fallo en definir la categoría de localización LC_MESSAGES como "
"{locale} (¿está instalada en este sistema?)"
#, perl-brace-format
msgid ""
"Warning: setting locale category LC_CTYPE to {locale} failed -- is it "
"installed on this system?"
msgstr ""
"Advertencia: fallo en definir la categoría de localización LC_CTYPE como "
"{locale} (¿está instalada en este sistema?)"
msgid "Warning: deprecated --encoding, simply remove it from your usage."
msgstr ""
"Advertencia: --encoding está obsoleto, puede eliminarlo de las opciones."
msgid "Error: --json-stream and --no-json cannot be used together."
msgstr "Error: --json-stream y --no-json no pueden usarse juntas."
msgid ""
"Warning: --json-translate has no effect without either --json or --json-"
"stream."
msgstr ""
"Advertencia: --json-translate no tiene ningún efecto sin tener además --json "
"o --json-stream."
msgid "Warning: deprecated --json-translate, use --no-raw instead."
msgstr "Advertencia: --json-translate está obsoleta, utilice mejor --no-raw."
msgid "Warning: deprecated --no-json-translate, use --raw instead."
msgstr "Advertencia: --no-json-translate está obsoleta, utilice mejor --raw."
#, perl-brace-format
msgid "Loading profile from {path}."
msgstr "Cargando perfil desde {path}."
#, perl-brace-format
msgid "Error: invalid value for --sourceaddr4: {reason}"
msgstr "Error: valor inválido para --sourceaddr4: {reason}"
#, perl-brace-format
msgid "Error: invalid value for --sourceaddr6: {reason}"
msgstr "Error: valor inválido para --sourceaddr6: {reason}"
#, perl-brace-format
msgid ""
"Error: Invalid input '{cli_arg}' in --test. There must be at most one slash "
"('/') character."
msgstr ""
"Error: entrada inválida '{cli_arg}' en --test. Debe haber al menos una barra "
"diagonal ('/')."
#, perl-brace-format
msgid ""
"Error: Unrecognized test case '{testcase}' in --test. Use --list-tests for a "
"list of valid choices."
msgstr ""
"Error: caso de prueba no reconocido '{testcase}' en --test. Use --list-tests "
"para una lista de las alternativas válidas."
#, perl-brace-format
msgid ""
"Error: Unrecognized test module '{module}' in --test. Use --list-tests for a "
"list of valid choices."
msgstr ""
"Error: módulo de prueba no reconocido '{module}' en --test. Use --list-tests "
"para una lista de las alternativas válidas."
#, perl-brace-format
msgid "Error: Invalid input '{cli_arg}' in --test."
msgstr "Error: entrada inválida '{cli_arg}' en --test."
#, perl-brace-format
msgid ""
"Notice: Engine does not have test case '{testcase}' enabled in the profile. "
"Forcing..."
msgstr ""
"Aviso: El motor no tiene el caso de prueba '{testcase}' habilitado en el "
"perfil. Forzando..."
#, perl-brace-format
msgid "Failed to recognize stop level '{level}'."
msgstr "Fallo en identificar el nivel de detención '{level}'."
msgid ""
"--level must be one of CRITICAL, ERROR, WARNING, NOTICE, INFO, DEBUG, DEBUG2 "
"or DEBUG3."
msgstr ""
"--level debe ser alguno entre CRITICAL, ERROR, WARNING, NOTICE, INFO, DEBUG, "
"DEBUG2 o DEBUG3."
msgid ""
"Only one domain can be given for testing. Did you forget to prepend an "
"option with '--<OPTION>'?"
msgstr ""
"Solo puede indicarse 1 dominio para las pruebas. ¿Quizás incluyó una opción "
"y olvidó el prefijo '--<OPCIÓN>'?"
msgid "Must give the name of a domain to test."
msgstr "Debe indicar el nombre de un dominio a probar."
#, perl-brace-format
msgid "Error loading hints file: {message}"
msgstr "Error al cargar el archivo 'hints': {message}"
msgid "Seconds"
msgstr "Segundos"
msgid "Level"
msgstr "Nivel"
msgid "Module"
msgstr "Módulo"
msgid "Testcase"
msgstr "Caso de prueba"
msgid "Message"
msgstr "Mensaje"
msgid "Looks OK."
msgstr "Se ve bien."
msgid ""
"\n"
"\n"
" Level\tNumber of log entries"
msgstr ""
"\n"
"\n"
" Nivel\tNúmero de registros"
#, perl-format
msgid "%8s\t%5d entries.\n"
msgstr "%8s\t%5d registros.\n"
msgid "--ns must be a name or a name/ip pair."
msgstr "--ns debe ser un nombre o un par nombre/ip."
msgid ""
"--ds ds data must be in the form \"keytag,algorithm,type,digest\". E.g. "
"space is not permitted anywhere in the string."
msgstr ""
"--ds los datos para DS deben estar en el formato \"keytag,algoritmo,tipo,"
"digest\". No se permiten espacios dentro del valor."
msgid "DEBUG"
msgstr "DEPURACIÓN"
msgid "INFO"
msgstr "INFO"
msgid "NOTICE"
msgstr "AVISO"
msgid "WARNING"
msgstr "ADVERTENCIA"
msgid "ERROR"
msgstr "ERROR"
msgid "CRITICAL"
msgstr "CRÍTICO"
#~ msgid "Print version information and exit."
#~ msgstr "Despliega información de la versión y termina."
#~ msgid ""
#~ "The minimum severity level to display. Must be one of CRITICAL, ERROR, "
#~ "WARNING, NOTICE, INFO or DEBUG."
#~ msgstr ""
#~ "El nivel de severidad mínimo para mostrar. Debe ser alguno entre "
#~ "CRITICAL, ERROR, WARNING, NOTICE, INFO o DEBUG."
#~ msgid "The locale to use for messages translation."
#~ msgstr "Localización (locale) para la traducción de los mensajes."
#~ msgid "Flag indicating if output should be in JSON or not."
#~ msgstr "Opción que indica si la salida será en JSON o no."
#~ msgid "Flag indicating if output should be streaming JSON or not."
#~ msgstr ""
#~ "Opción que indica si la salida debiera ser transmisión de JSON o no."
#~ msgid ""
#~ "Deprecated. Flag indicating if JSON output should include the translated "
#~ "message of the tag or not."
#~ msgstr ""
#~ "Obsoleta. Opción que indica si la salida de transmisión de JSON debiera "
#~ "incluir el mensaje traducido del tag o no."
#~ msgid ""
#~ "Flag indicating if output should be translated to human language or "
#~ "dumped raw."
#~ msgstr ""
#~ "Opción que indica si la salida se traducirá a idioma humano o en formato "
#~ "crudo."
#~ msgid "Print timestamp on entries."
#~ msgstr "Despliega marcas de tiempo en los registros."
#~ msgid "Print level on entries."
#~ msgstr "Despliega el nivel en los registros."
#~ msgid "Print the name of the module on entries."
#~ msgstr "Despliega el nombre del módulo en los registros."
#~ msgid "Print the name of the test case on entries."
#~ msgstr "Despliega el nombre del caso de prueba en los registros."
#~ msgid ""
#~ "A name/ip string giving a nameserver for undelegated tests, or just a "
#~ "name which will be looked up for IP addresses. Can be given multiple "
#~ "times."
#~ msgstr ""
#~ "Una etiqueta nombre/IP para el servidor de nombres en las pruebas no-"
#~ "delegadas, o solo un nombre que será resuelto para buscar la dirección "
#~ "IP. Puede repetirse varias veces."
#~ msgid "Name of a root hints file to override the defaults."
#~ msgstr ""
#~ "Nombre del archivo 'root hints' para reemplazar los valores por defecto."
#~ msgid "Name of a file to save DNS data to after running tests."
#~ msgstr ""
#~ "Nombre de un archivo para guardar los datos DNS después de ejecutar las "
#~ "pruebas."
#~ msgid "Name of a file to restore DNS data from before running test."
#~ msgstr ""
#~ "Nombre de un archivo para recuperar los datos DNS antes de ejecutar la "
#~ "prueba."
#~ msgid ""
#~ "Flag to permit or deny queries being sent via IPv4. --ipv4 permits IPv4 "
#~ "traffic, --no-ipv4 forbids it."
#~ msgstr ""
#~ "Opción para permitir o prohibir el envío de consultas vía IPv4. --ipv4 "
#~ "autoriza el tráfico IPv4, --no-ipv4 lo prohíbe."
#~ msgid ""
#~ "Flag to permit or deny queries being sent via IPv6. --ipv6 permits IPv6 "
#~ "traffic, --no-ipv6 forbids it."
#~ msgstr ""
#~ "Opción para permitir o prohibir el envío de consultas vía IPv6. --ipv6 "
#~ "autoriza el tráfico IPv6, --no-ipv6 lo prohíbe."
#~ msgid "Instead of running a test, list all available tests."
#~ msgstr ""
#~ "En vez de ejecutar una prueba, despliega todas las pruebas disponibles."
#~ msgid ""
#~ "Specify test to run. Should be either the name of a module, or the name "
#~ "of a module and the name of a method in that module separated by a \"/\" "
#~ "character (Example: \"Basic/basic1\"). The method specified must be one "
#~ "that takes a zone object as its single argument. This switch can be "
#~ "repeated."
#~ msgstr ""
#~ "Indica la prueba a ejecutar. Debe ser el nombre de un módulo, o el nombre "
#~ "de un módulo y el nombre de un método de ese módulo separado por el "
#~ "caracter \"/\" (por ejemplo: \"Basic/basic1\"). El método especificado "
#~ "debe ser uno que recibe un objecto de zona como su único argumento. Esta "
#~ "opción puede repetirse."
#~ msgid ""
#~ "As soon as a message at this level or higher is logged, execution will "
#~ "stop. Must be one of CRITICAL, ERROR, WARNING, NOTICE, INFO or DEBUG."
#~ msgstr ""
#~ "Tan pronto como se registra un mensaje en este nivel o uno superior, la "
#~ "ejecución se detendrá. Debe ser alguno entre CRITICAL, ERROR, WARNING, "
#~ "NOTICE, INFO o DEBUG."
#~ msgid "Name of profile file to load. (DEFAULT)"
#~ msgstr "Nombre del archivo de perfiles a cargar. (DEFAULT)"
#~ msgid "Strings with DS data on the form \"keytag,algorithm,type,digest\""
#~ msgstr ""
#~ "Etiquetas con los datos DS en la forma \"tag,algoritmo,tipo,"
#~ "resumen\" (\"keytag,algorithm,type,digest\")"
#~ msgid "Print a count of the number of messages at each level"
#~ msgstr "Despliega un contador de la cantidad de mensajes en cada nivel"
#~ msgid ""
#~ "Boolean flag for activity indicator. Defaults to on if STDOUT is a tty, "
#~ "off if it is not. Disable with --no-progress."
#~ msgstr ""
#~ "Opción booleana para el indicador de actividad. El predeterminado es "
#~ "encendido si la salida estándar (STDOUT) es un terminal, apagado si no lo "
#~ "es. Se puede deshabilitar con --no-progress."
#~ msgid "Name of the character encoding used for command line arguments"
#~ msgstr ""
#~ "Nombre de la codificación de caracteres (\"encoding\") que se usa para "
#~ "los argumentos en la línea de comandos"
#~ msgid ""
#~ "At the end of a run, print a summary of the times (in milliseconds) the "
#~ "zone's name servers took to answer."
#~ msgstr ""
#~ "Al finalizar una ejecución, despliega un resumen del tiempo (en "
#~ "milisegundos) que le tomó responder a los servidores de nombre de la zona."
#~ msgid "Print the effective profile used in JSON format, then exit."
#~ msgstr ""
#~ "Despliega el perfil definitivo usado en formato JSON, luego termina."
#~ msgid ""
#~ "Deprecated (planned removal: v2024.1). Use --sourceaddr4 and/or --"
#~ "sourceaddr6. Source IP address used to send queries. Setting an IP "
#~ "address not correctly configured on a local network interface causes "
#~ "cryptic error messages."
#~ msgstr ""
#~ "Obsoleta (eliminación planificada para v2024.1). Use --sourceaddr4 y/o .."
#~ "sourceaddr6. Dirección IP de origen usada para enviar las consultas. "
#~ "Indicar una dirección IP que no esté correctamente configurada en una "
#~ "interfaz de red local puede causar mensajes de error confusos."
#~ msgid ""
#~ "Source IPv4 address used to send queries. Setting an IPv4 address not "
#~ "correctly configured on a local network interface fails silently. Can not "
#~ "be combined with --sourceaddr."
#~ msgstr ""
#~ "Dirección IPv4 de origen usada para enviar las consultas. Indicar una "
#~ "dirección IPv4 que no esté correctamente configurada en una interfaz de "
#~ "red local fallará silenciosamente. No puede usarse en combinación con --"
#~ "sourceaddr."
#~ msgid ""
#~ "Source IPv6 address used to send queries. Setting an IPv6 address not "
#~ "correctly configured on a local network interface fails silently. Can not "
#~ "be combined with --sourceaddr."
#~ msgstr ""
#~ "Dirección IPv6 de origen usada para enviar las consultas. Indicar una "
#~ "dirección IPv6 que no esté correctamente configurada en una interfaz de "
#~ "red local fallará silenciosamente. No puede usarse en combinación con --"
#~ "sourceaddr."
#~ msgid "Print elapsed time (in seconds) at end of run."
#~ msgstr ""
#~ "Imprime el tiempo transcurrido (en segundos) al finalizar la ejecución."
#~ msgid ""
#~ "Error: --sourceaddr can't be combined with --sourceaddr4 or --sourceaddr6."
#~ msgstr ""
#~ "Error: --sourceaddr no puede usarse al mismo tiempo que --sourceaddr4 o --"
#~ "sourceaddr6."
#~ msgid ""
#~ "Warning: --sourceaddr is deprecated (planned removal: v2024.1). Use --"
#~ "sourceaddr4 and/or --sourceaddr6 instead."
#~ msgstr ""
#~ "Advertencia: --sourceaddr está obsoleta (eliminación planificada para "
#~ "v2024.1). Utilice mejor --sourceaddr4 y/o --sourceaddr6."
#~ msgid "The domain name contains consecutive dots.\n"
#~ msgstr "El nombre de dominio contiene puntos consecutivos.\n"
#, perl-brace-format
#~ msgid "The name of the nameserver '{nsname}' contains consecutive dots."
#~ msgstr ""
#~ "El nombre del servidor de nombres '{nsname}' contiene puntos consecutivos."
#~ msgid ""
#~ "Warning: Zonemaster::LDNS not compiled with IDN support, cannot handle "
#~ "non-ASCII names correctly."
#~ msgstr ""
#~ "Advertencia: Zonemaster::LDNS no fue compilado con soporte IDN, no puede "
#~ "manejar correctamente nombres no-ASCII."

316
zonemaster-cli/share/fi.po Normal file
View File

@@ -0,0 +1,316 @@
msgid ""
msgstr ""
"Project-Id-Version: 0.0.5\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-05-23 13:09+0000\n"
"PO-Revision-Date: 2023-05-22 16:12+0300\n"
"Last-Translator: sami.salmensuo@traficom.fi\n"
"Language-Team: Traficom domain team\n"
"Language: fi\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Generator: Poedit 3.2.2\n"
msgid "Print version information and exit."
msgstr "Tulosta versiotiedot ja poistu."
msgid ""
"The minimum severity level to display. Must be one of CRITICAL, ERROR, "
"WARNING, NOTICE, INFO or DEBUG."
msgstr ""
"Näytettävä minimivakavuusaste. Sen on oltava yksi näistä: CRITICAL "
"(kriittinen), ERROR (virhe), WARNING (varoitus), NOTICE (huomio), INFO "
"(info) tai DEBUG (virheenkorjaus)."
msgid "The locale to use for messages translation."
msgstr "Viestien kääntämisessä käytettävä lokaali."
msgid "Flag indicating if output should be in JSON or not."
msgstr "Lippu, joka ilmoittaa pitääkö tulosteen olla JSON-muodossa vai ei."
msgid "Flag indicating if output should be streaming JSON or not."
msgstr ""
"Lippu, joka ilmoittaa pitääkö tulosteen olla suoratoistettavassa JSON-"
"muodossa vai ei."
msgid ""
"Deprecated. Flag indicating if JSON output should include the translated "
"message of the tag or not."
msgstr ""
"Käytöstä poistettu. Lippu joka ilmaisee pitäisikö JSON-tulostuksen sisältää "
"tagin käännetty viesti vai ei."
msgid ""
"Flag indicating if output should be translated to human language or dumped "
"raw."
msgstr ""
"Lippu, joka ilmoittaa pitääkö tuloste kääntää ihmiskielelle vai annetaanko "
"se raakamuodossa."
msgid "Print timestamp on entries."
msgstr "Tulosta merkintöihin aikaleima."
msgid "Print level on entries."
msgstr "Tulosta merkintöihin taso."
msgid "Print the name of the module on entries."
msgstr "Tulosta merkintöihin moduulin nimi."
msgid "Print the name of the test case on entries."
msgstr "Tulosta merkintöihin testitapauksen nimi."
msgid ""
"A name/ip string giving a nameserver for undelegated tests, or just a name "
"which will be looked up for IP addresses. Can be given multiple times."
msgstr ""
"Nimi/IP-merkkijono, joka antaa nimipalvelimen testeille, joita ei ole "
"delegoitu, tai pelkkä nimi, jolla haetaan IP-osoitteita. Voidaan antaa "
"useita kertoja."
msgid "Name of a root hints file to override the defaults."
msgstr "'Root hints' tiedoston nimi jolla ohitetaan oletusasetukset."
msgid "Name of a file to save DNS data to after running tests."
msgstr ""
"Sen tiedoston nimi, johon DNS-tiedot tallennetaan testien ajamisen jälkeen."
msgid "Name of a file to restore DNS data from before running test."
msgstr ""
"Sen tiedoston nimi, josta testiä edeltävät DNS-tiedot voidaan palauttaa."
msgid ""
"Flag to permit or deny queries being sent via IPv4. --ipv4 permits IPv4 "
"traffic, --no-ipv4 forbids it."
msgstr ""
"Lippu, joka sallii tai kieltää IPv4:n kautta lähetetyt kyselyt. --ipv4 "
"sallii IPv4-liikenteen, --no-ipv4 kieltää sen."
msgid ""
"Flag to permit or deny queries being sent via IPv6. --ipv6 permits IPv6 "
"traffic, --no-ipv6 forbids it."
msgstr ""
"Lippu, joka sallii tai kieltää IPv6:n kautta lähetetyt kyselyt. --ipv6 "
"sallii IPv6-liikenteen, --no-ipv6 kieltää sen."
msgid "Instead of running a test, list all available tests."
msgstr "Listaa kaikki käytettävissä olevat testit testin ajamisen sijasta."
msgid ""
"Specify test to run. Should be either the name of a module, or the name of a "
"module and the name of a method in that module separated by a \"/\" "
"character (Example: \"Basic/basic1\"). The method specified must be one that "
"takes a zone object as its single argument. This switch can be repeated."
msgstr ""
"Määritä ajettava testi. Sen tulisi olla joko moduulin nimi tai moduulin nimi "
"ja moduulissa olevan metodin nimi erotettuna ”/”-merkillä (esimerkki: "
"\"Basic/basic1\"). Määritetyn metodin on otettava ainoaksi parametrikseen "
"vyöhykeobjekti. Tämä vaihto voidaan toistaa."
msgid ""
"As soon as a message at this level or higher is logged, execution will stop. "
"Must be one of CRITICAL, ERROR, WARNING, NOTICE, INFO or DEBUG."
msgstr ""
"Heti kun lokiin kirjataan tämän tasoinen tai tätä korkeamman tasoinen "
"viesti, suoritus lakkaa. Sen on oltava yksi näistä: CRITICAL (kriittinen), "
"ERROR (virhe), WARNING (varoitus), NOTICE (huomio), INFO (info) tai DEBUG "
"(virheenkorjaus)."
msgid "Name of profile file to load. (DEFAULT)"
msgstr "Ladattavan profiilitiedoston nimi. (DEFAULT (oletus))"
msgid "Strings with DS data on the form \"keytag,algorithm,type,digest\""
msgstr ""
"Merkkijonot, jotka sisältävät DS-tietoja muodossa \"keytag,algorithm,type,"
"digest” (avaintunniste, algoritmi, tyyppi, tiiviste)"
msgid "Print a count of the number of messages at each level"
msgstr "Tulosta viestien lukumäärä kullakin tasolla"
msgid ""
"Boolean flag for activity indicator. Defaults to on if STDOUT is a tty, off "
"if it is not. Disable with --no-progress."
msgstr ""
"Looginen lippu toiminnan merkitsemiseen. Oletusarvoisesti päällä, jos STDOUT "
"on tty, muussa tapauksessa poissa päältä. Poistetaan käytöstä parametrilla --"
"no-progress."
msgid "Name of the character encoding used for command line arguments"
msgstr "Komentorivin parametreissä käytetty merkistökoodaus"
msgid ""
"At the end of a run, print a summary of the times (in milliseconds) the "
"zone's name servers took to answer."
msgstr ""
"Tulosta ajon lopussa yhteenveto ajasta (millisekunteina), joka vyöhykkeen "
"nimipalvelimilla kesti vastata."
msgid "Print the effective profile used in JSON format, then exit."
msgstr "Tulosta käytössä oleva profiili JSON-muodossa ja poistu."
msgid ""
"Deprecated (planned removal: v2024.1). Use --sourceaddr4 and/or --"
"sourceaddr6. Source IP address used to send queries. Setting an IP address "
"not correctly configured on a local network interface causes cryptic error "
"messages."
msgstr ""
"Käytöstä poistettu (suunniteltu poisto v2024.1) Kyselyjen lähettämiseen "
"käytetty IP-lähdeosoite. IP-osoitetta ei ole määritetty oikein määriteltynä "
"paikalliseen verkkoliitäntään aiheuttaa poikkeuksellisia virheitä."
msgid ""
"Source IPv4 address used to send queries. Setting an IPv4 address not "
"correctly configured on a local network interface fails silently. Can not be "
"combined with --sourceaddr."
msgstr ""
"Lähde IPv4-osoite jota käytetään kyselyjen lähettämiseen. IPv4-osoitteen "
"määrittäminen jota ei ole määritetty oikein paikalliseen verkkoliitäntään, "
"epäonnistuu äänettömästi. Ei voida yhdistää --sourceaddr kanssa."
msgid ""
"Source IPv6 address used to send queries. Setting an IPv6 address not "
"correctly configured on a local network interface fails silently. Can not be "
"combined with --sourceaddr."
msgstr ""
"Lähde IPv6-osoite, jota käytetään kyselyjen lähettämiseen. IPv6-osoitteen "
"määrittäminen, jota ei ole määritetty oikein paikalliseen verkkoliitäntään, "
"epäonnistuu äänettömästi. Ei voida yhdistää --sourceaddr kanssa ."
msgid "Print elapsed time (in seconds) at end of run."
msgstr "Tulosta kulunut aika (sekunteina) ajon lopussa."
#, perl-format
msgid ""
"Warning: setting locale category LC_MESSAGES to %s failed (is it installed "
"on this system?)."
msgstr ""
"Varoitus: arvoa %s ei voitu asettaa lokaalikategorian LC_MESSAGES arvoksi "
"(onko sitä asennettu tähän järjestelmään?)."
#, perl-format
msgid ""
"Warning: setting locale category LC_CTYPE to %s failed (is it installed on "
"this system?)."
msgstr ""
"Varoitus: arvoa %s ei voitu asettaa lokaalikategorian LC_CTYPE arvoksi (onko "
"sitä asennettu tähän järjestelmään?)."
msgid ""
"Error: --sourceaddr can't be combined with --sourceaddr4 or --sourceaddr6."
msgstr ""
"Virhe: --sourceaddr ei voi yhdistää --sourceaddr4:n tai --sourceaddr6:n "
"kanssa."
msgid ""
"Warning: --sourceaddr is deprecated (planned removal: v2024.1). Use --"
"sourceaddr4 and/or --sourceaddr6 instead."
msgstr ""
"Varoitus: --sourceaddr on vanhentunut (suunniteltu poisto: v2024.1). Käytä "
"sen sijaan --sourceaddr4 ja/tai --sourceaddr6."
msgid "Error: --json-stream and --no-json can't be used together."
msgstr "Virhe: --json-streamia ja --no-jsonia ei voida käyttää yhdessä."
msgid ""
"Warning: --json-translate has no effect without either --json or --json-"
"stream."
msgstr ""
"Varoitus: --json-translatella ei ole vaikutusta ilman --json- tai --json-"
"streamia."
msgid "Warning: deprecated --json-translate, use --no-raw instead."
msgstr "Varoitus: vanhentunut --json-translate, käytä sen sijaan --no-raw."
msgid "Warning: deprecated --no-json-translate, use --raw instead."
msgstr "Varoitus: vanhentunut --no-json-translate, käytä sen sijaan --raw:ta."
#, perl-brace-format
msgid "Loading profile from {path}."
msgstr "Ladataan profiili kohteesta {path}."
msgid "Failed to recognize stop level '"
msgstr "Ei tunnistettu lopetustasoa '"
msgid ""
"--level must be one of CRITICAL, ERROR, WARNING, NOTICE, INFO, DEBUG, DEBUG2 "
"or DEBUG3.\n"
msgstr ""
"--level (taso) täytyy olla yksi seuraavista: CRITICAL (kriittinen), ERROR "
"(virhe), WARNING (varoitus), NOTICE (huomautus), INFO (tiedotus), DEBUG "
"(virheenkorjaus), DEBUG2 (virheenkorjaus2) tai DEBUG3 (virheenkorjaus3).\n"
msgid "Seconds"
msgstr "Sekuntia"
msgid "Level"
msgstr "Taso"
msgid "Module"
msgstr "Moduuli"
msgid "Testcase"
msgstr "Testitapaus"
msgid "Message"
msgstr "Viesti"
msgid ""
"Only one domain can be given for testing. Did you forget to prepend an "
"option with '--<OPTION>'?\n"
msgstr ""
"Vain yksi verkkotunnus voidaan antaa testattavaksi kerrallaan. Unohditko "
"lisätä määrityksen eteen '--<OPTION>'?\n"
msgid "Must give the name of a domain to test.\n"
msgstr "Testattavan verkkoalueen nimi on annettava.\n"
msgid "The domain name contains consecutive dots.\n"
msgstr "Verkkotunnus sisältää peräkkäisiä pisteitä.\n"
msgid "Looks OK."
msgstr "Näyttää olevan OK."
msgid ""
"\n"
"\n"
" Level\tNumber of log entries"
msgstr ""
"\n"
"\n"
" Taso\tLokimerkintöjen lukumäärä"
#, perl-format
msgid "%8s\t%5d entries.\n"
msgstr "%8s\t%5d merkintää.\n"
msgid "--ns must be a name or a name/ip pair."
msgstr "--ns:n on oltava nimi tai nimi/ip-pari."
#, perl-brace-format
msgid "The name of the nameserver '{nsname}' contains consecutive dots."
msgstr "Nimipalvelimen nimi \"{nsname}\" sisältää peräkkäisiä pisteitä."
msgid ""
"Warning: Zonemaster::LDNS not compiled with IDN support, cannot handle non-"
"ASCII names correctly."
msgstr ""
"Varoitus: Zonemaster::LDNS ei ole käännetty IDN tuella, joten se ei pysty "
"käsittelemään Ei ASCII-muodossa olevia nimiä oikein."
msgid "DEBUG"
msgstr "DEBUG"
msgid "INFO"
msgstr "INFO"
msgid "NOTICE"
msgstr "NOTICE"
msgid "WARNING"
msgstr "WARNING"
msgid "ERROR"
msgstr "ERROR"
msgid "CRITICAL"
msgstr "CRITICAL"

165
zonemaster-cli/share/fr.po Normal file
View File

@@ -0,0 +1,165 @@
msgid ""
msgstr ""
"Project-Id-Version: 0.0.5\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-06-24 13:54+0200\n"
"PO-Revision-Date: 2025-06-24 14:00+0200\n"
"Last-Translator: thomas.green@afnic.fr\n"
"Language-Team: Zonemaster Team\n"
"Language: fr\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Generator: Poedit 3.6\n"
#, perl-brace-format
msgid ""
"Warning: setting locale category LC_MESSAGES to {locale} failed -- is it "
"installed on this system?"
msgstr ""
"Attention : positionner la catégorie de localisation LC_MESSAGES pour "
"{locale} a échoué -- est-elle installée sur ce système ?"
#, perl-brace-format
msgid ""
"Warning: setting locale category LC_CTYPE to {locale} failed -- is it "
"installed on this system?"
msgstr ""
"Attention : positionner la catégorie de localisation LC_TYPE pour {locale} a "
"échoué -- est-elle installée sur ce système ?"
msgid "Warning: deprecated --encoding, simply remove it from your usage."
msgstr ""
"Attention : --encoding est dépréciée, retirez-le simplement de votre ligne "
"de commande."
msgid "Error: --json-stream and --no-json cannot be used together."
msgstr ""
"Erreur : --json-stream et --no-json ne peuvent pas être utilisés "
"simultanément."
msgid ""
"Warning: --json-translate has no effect without either --json or --json-"
"stream."
msgstr ""
"Avertissement : --json-translate n'a pas d'effet sans --json ou --json-"
"stream."
msgid "Warning: deprecated --json-translate, use --no-raw instead."
msgstr ""
"Avertissement : --json-translate est dépréciée, utilisez plutôt --no-raw."
msgid "Warning: deprecated --no-json-translate, use --raw instead."
msgstr ""
"Avertissement : --no-json-translate est dépréciée, utilisez plutôt --raw."
#, perl-brace-format
msgid "Loading profile from {path}."
msgstr "Chargement du profil depuis le fichier {path}."
#, perl-brace-format
msgid "Error: invalid value for --sourceaddr4: {reason}"
msgstr "Erreur : valeur incorrecte pour --sourceaddr4: {reason}"
#, perl-brace-format
msgid "Error: invalid value for --sourceaddr6: {reason}"
msgstr "Erreur : valeur incorrecte pour --sourceaddr6: {reason}"
#, perl-brace-format
msgid "Error: unrecognized term '{term}' in --test."
msgstr "Erreur : terme non reconnu '{term}' pour --test."
#, perl-brace-format
msgid "Failed to recognize stop level '{level}'."
msgstr "Impossible de reconnaître le niveau de gravité d'arrêt '{level}'."
msgid ""
"--level must be one of CRITICAL, ERROR, WARNING, NOTICE, INFO, DEBUG, DEBUG2 "
"or DEBUG3."
msgstr ""
"--level doit avoir pour valeur CRITICAL, ERROR, WARNING, NOTICE, INFO, "
"DEBUG, DEBUG2 ou DEBUG3."
msgid ""
"Only one domain can be given for testing. Did you forget to prepend an "
"option with '--<OPTION>'?"
msgstr ""
"Seul un domaine peut être soumis à un test. Auriez-vous oublié de préfixer "
"une option avec '--<OPTION>'?"
msgid "Must give the name of a domain to test."
msgstr "Il est nécessaire d'indiquer un nom de domaine à tester."
#, perl-brace-format
msgid "Error loading hints file: {message}"
msgstr "Erreur de chargement du fichier d'indication de la racine : {message}"
msgid "Seconds"
msgstr "Durée"
msgid "Level"
msgstr "Niveau"
msgid "Module"
msgstr "Module"
msgid "Testcase"
msgstr "Cas de test"
msgid "Message"
msgstr "Message"
msgid "Looks OK."
msgstr "Rien de particulier à signaler."
msgid "Number of log entries"
msgstr "Nombre d'entrées journal"
msgid "Message tag"
msgstr "Message"
msgid "Count"
msgstr "Compte"
msgid "Name servers"
msgstr "Serveurs de nom"
msgid "Child zone"
msgstr "Zone fille"
msgid "Parent zone"
msgstr "Zone parente"
msgid "Other"
msgstr "Autre"
msgid "Grand total"
msgstr "Grand total"
msgid "--ns must be a name or a name/ip pair."
msgstr "--ns doit être un nom ou un couple nom/ip."
msgid ""
"--ds ds data must be in the form \"keytag,algorithm,type,digest\". E.g. "
"space is not permitted anywhere in the string."
msgstr ""
"Les données passées à --ds doivent être de la forme \"keytag,algorithme,type,"
"condensat\". Par exemple, un espace n'est pas permis dans cette chaîne."
msgid "DEBUG"
msgstr "DEBOGUE"
msgid "INFO"
msgstr "INFO"
msgid "NOTICE"
msgstr "NOTE"
msgid "WARNING"
msgstr "AVERTISSEMENT"
msgid "ERROR"
msgstr "ERREUR"
msgid "CRITICAL"
msgstr "CRITIQUE"

303
zonemaster-cli/share/nb.po Normal file
View File

@@ -0,0 +1,303 @@
msgid ""
msgstr ""
"Project-Id-Version: 0.0.5\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-06-27 14:38+0200\n"
"PO-Revision-Date: 2023-06-27 14:54+0200\n"
"Last-Translator: Richard Persson <richard.persson@norid.no>\n"
"Language-Team: Zonemaster Team\n"
"Language: nb\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Generator: Poedit 3.3.2\n"
msgid "Print version information and exit."
msgstr "Vis versjonsinformasjon og avslutt."
msgid ""
"The minimum severity level to display. Must be one of CRITICAL, ERROR, "
"WARNING, NOTICE, INFO or DEBUG."
msgstr ""
"Minimumsnivå på feilmeldinger. Må være satt til en av CRITICAL, ERROR, "
"WARNING, NOTICE, INFO eller DEBUG."
msgid "The locale to use for messages translation."
msgstr "Språk for oversetting av meldinger."
msgid "Flag indicating if output should be in JSON or not."
msgstr "Flagg som indikerer om output skal være formattert som JSON."
msgid "Flag indicating if output should be streaming JSON or not."
msgstr "Flagg som indikerer om output skal strømme JSON."
msgid ""
"Deprecated. Flag indicating if JSON output should include the translated "
"message of the tag or not."
msgstr ""
"Foreldet. Flag som indikerer om JSON-output skal inneholde den oversatte "
"meldingen."
msgid ""
"Flag indicating if output should be translated to human language or dumped "
"raw."
msgstr "Flagg som indikerer om output skal oversettes til vanlig språk."
msgid "Print timestamp on entries."
msgstr "Skriv tidsstempel på innslag."
msgid "Print level on entries."
msgstr "Skriv nivå på innslag."
msgid "Print the name of the module on entries."
msgstr "Skriv modulnavn på innslag."
msgid "Print the name of the test case on entries."
msgstr "Skriv testnavn på innslag."
msgid ""
"A name/ip string giving a nameserver for undelegated tests, or just a name "
"which will be looked up for IP addresses. Can be given multiple times."
msgstr ""
"Et navn/IP-nummer for en navnetjener for udelegerte tester eller bare et "
"navn som blir slått opp i DNS. Kan angis flere ganger."
msgid "Name of a root hints file to override the defaults."
msgstr "Navn på en 'root hints'-fil for å overstyre standardinnstillingene."
msgid "Name of a file to save DNS data to after running tests."
msgstr "Filnavn på fil der DNS-data blir lagret etter at testene er kjørt."
msgid "Name of a file to restore DNS data from before running test."
msgstr "Filnavn på fil med DNS-data som leses inn innen testene startes."
msgid ""
"Flag to permit or deny queries being sent via IPv4. --ipv4 permits IPv4 "
"traffic, --no-ipv4 forbids it."
msgstr ""
"Flagg som angir om spørringer skal sendes vha. IPv4. \"--ipv4\" slår på og "
"\"--no-ipv4\" slår av."
msgid ""
"Flag to permit or deny queries being sent via IPv6. --ipv6 permits IPv6 "
"traffic, --no-ipv6 forbids it."
msgstr ""
"Flagg som angir om spørringer skal sendes vha. IPv6. \"--ipv6\" slår på og "
"\"--no-ipv6\" slår av."
msgid "Instead of running a test, list all available tests."
msgstr "Liste tilgjengelige tester."
msgid ""
"Specify test to run. Should be either the name of a module, or the name of a "
"module and the name of a method in that module separated by a \"/\" "
"character (Example: \"Basic/basic1\"). The method specified must be one that "
"takes a zone object as its single argument. This switch can be repeated."
msgstr ""
"Angi test som skal kjøres. Kan angis som \"modulnavn\" eller \"modulnavn/"
"metode\". Eksempel: \"Basic/basic1\". Den angitte metoden må være en som "
"bare tar et sone-objekt som argument. Denne parameteren kan angis flere "
"ganger."
msgid ""
"As soon as a message at this level or higher is logged, execution will stop. "
"Must be one of CRITICAL, ERROR, WARNING, NOTICE, INFO or DEBUG."
msgstr ""
"Avslutt testkjørningen når en melding med denna nivå eller høyere "
"registreres."
msgid "Name of profile file to load. (DEFAULT)"
msgstr "Filnavn på fil med profildata som skal leses. (DEFAULT)"
msgid "Strings with DS data on the form \"keytag,algorithm,type,digest\""
msgstr ""
"En tekststreng med DS-data på formatet: \"keytag,algorithm,type,digest\""
msgid "Print a count of the number of messages at each level"
msgstr "Skriv en summering av antall meldinger på hvert nivå"
msgid ""
"Boolean flag for activity indicator. Defaults to on if STDOUT is a tty, off "
"if it is not. Disable with --no-progress."
msgstr ""
"Flagg som angir om fremdriftsindikator skal vises. Normalt på dersom STDOUT "
"er en TTY, ellers av. Slå av med --no-progress."
msgid "Name of the character encoding used for command line arguments"
msgstr "Navn på tegnsett som er brukt for kommandolinjeargumenter"
msgid ""
"At the end of a run, print a summary of the times (in milliseconds) the "
"zone's name servers took to answer."
msgstr ""
"Etter kjøring, skriv ut en oppsummering av svarstider (i millisekunder) fra "
"sonens navnetjenere."
msgid "Print the effective profile used in JSON format, then exit."
msgstr "Skriv ut profilen som er brukt i JSON-format."
msgid ""
"Deprecated (planned removal: v2024.1). Use --sourceaddr4 and/or --"
"sourceaddr6. Source IP address used to send queries. Setting an IP address "
"not correctly configured on a local network interface causes cryptic error "
"messages."
msgstr ""
"Foreldet (planlagt fjerning: v2024.1). Bruk --sourceaddr4 og/eller --"
"sourceaddr6. Fra-IP-adresse brukt til å sende spørringer. Ved å bruke en IP-"
"adresse som ikke er korrekt satt opp på et lokalt nett forårsakes kryptiske "
"feilmeldinger."
msgid ""
"Source IPv4 address used to send queries. Setting an IPv4 address not "
"correctly configured on a local network interface fails silently. Can not be "
"combined with --sourceaddr."
msgstr ""
"Fra-IPv4-adresse brukt til å sende spørringer. Hvis en bruker en IPv4-"
"adresse som ikke er korrekt satt opp på et lokalt nett feiler testen uten "
"meldinger. Kan ikke kombineres med --sourceaddr."
msgid ""
"Source IPv6 address used to send queries. Setting an IPv6 address not "
"correctly configured on a local network interface fails silently. Can not be "
"combined with --sourceaddr."
msgstr ""
"Fra-IPv6-adresse brukt til å sende spørringer. Hvis en bruker en IPv6-"
"adresse som ikke er korrekt satt opp på et lokalt nett feiler testen uten "
"meldinger. Kan ikke kombineres med --sourceaddr."
msgid "Print elapsed time (in seconds) at end of run."
msgstr "Skriv ut medgått tid (i sekunder) ved slutten av kjøringen."
#, perl-format
msgid ""
"Warning: setting locale category LC_MESSAGES to %s failed (is it installed "
"on this system?)."
msgstr ""
"Advarsel: misslyktes med at sette locale category LC_MESSAGES til %s. Er den "
"installert på dette system?"
#, perl-format
msgid ""
"Warning: setting locale category LC_CTYPE to %s failed (is it installed on "
"this system?)."
msgstr ""
"Advarsel: misslyktes med at sette locale category LC_CTYPE til %s. Er den "
"installert på dette system?"
msgid ""
"Error: --sourceaddr can't be combined with --sourceaddr4 or --sourceaddr6."
msgstr ""
"Feil: --sourceaddr kan ikke kombineres med --sourceaddr4 eller --sourceaddr6."
msgid ""
"Warning: --sourceaddr is deprecated (planned removal: v2024.1). Use --"
"sourceaddr4 and/or --sourceaddr6 instead."
msgstr ""
"Advarsel: --sourceaddr er foreldet (planlagt fjerning: v2024.1). Bruk --"
"sourceaddr4 og/eller --sourceaddr6 i stedet."
msgid "Error: --json-stream and --no-json can't be used together."
msgstr "Feil: --json-stream og --no-json kan ikke brukes sammen."
msgid ""
"Warning: --json-translate has no effect without either --json or --json-"
"stream."
msgstr ""
"Advarsel: --json-translate har ingen effekt uten verken --json eller --json-"
"stream."
msgid "Warning: deprecated --json-translate, use --no-raw instead."
msgstr "Advarsel: foreldet --json-translate, bruk --no-raw i stedet."
msgid "Warning: deprecated --no-json-translate, use --raw instead."
msgstr "Advarsel: utdatert --no-json-translate, bruk --raw i stedet."
#, perl-brace-format
msgid "Loading profile from {path}."
msgstr "Laster profil fra {path}."
msgid "Failed to recognize stop level '"
msgstr "Kunne ikke gjenkjenne stop-nivå '"
msgid ""
"--level must be one of CRITICAL, ERROR, WARNING, NOTICE, INFO, DEBUG, DEBUG2 "
"or DEBUG3.\n"
msgstr ""
"--level må være en av CRITICAL, ERROR, WARNING, NOTICE, INFO, DEBUG, DEBUG2 "
"eller DEBUG3.\n"
msgid "Seconds"
msgstr "Sekunder"
msgid "Level"
msgstr "Nivå"
msgid "Module"
msgstr "Modul"
msgid "Testcase"
msgstr "Test"
msgid "Message"
msgstr "Melding"
msgid ""
"Only one domain can be given for testing. Did you forget to prepend an "
"option with '--<OPTION>'?\n"
msgstr ""
"Bare ett domene kan gis for testing. Glemte du å sette '--<OPTION>' foran en "
"opsjon?\n"
msgid "Must give the name of a domain to test.\n"
msgstr "Må angi navnet på domenet som skal testes.\n"
msgid "The domain name contains consecutive dots.\n"
msgstr "Domenenavnet inneholder flere punkter på rad.\n"
msgid "Looks OK."
msgstr "Ser OK ut."
msgid ""
"\n"
"\n"
" Level\tNumber of log entries"
msgstr ""
"\n"
"\n"
" Nivå Antall loggmeldinger"
#, perl-format
msgid "%8s\t%5d entries.\n"
msgstr "%8s\t%5d meldinger.\n"
msgid "--ns must be a name or a name/ip pair."
msgstr "--ns må være et navn eller et navn/ip par."
#, perl-brace-format
msgid "The name of the nameserver '{nsname}' contains consecutive dots."
msgstr "Navnet til navnetjeneren '{nsname}' inneholder flere punkter på rad."
msgid ""
"Warning: Zonemaster::LDNS not compiled with IDN support, cannot handle non-"
"ASCII names correctly."
msgstr ""
"Advarsel: Zonemaster::LDNS er ikke kompilert med IDN-støtte. Kan bare "
"håndtere ASCII-navn."
msgid "DEBUG"
msgstr "DEBUG"
msgid "INFO"
msgstr "INFO"
msgid "NOTICE"
msgstr "NOTIS"
msgid "WARNING"
msgstr "ADVARSEL"
msgid "ERROR"
msgstr "FEIL"
msgid "CRITICAL"
msgstr "KRITISK"

176
zonemaster-cli/share/sl.po Normal file
View File

@@ -0,0 +1,176 @@
msgid ""
msgstr ""
"Project-Id-Version: 0.0.5\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-02-26 15:58+0100\n"
"PO-Revision-Date: 2025-02-26 14:58+0200\n"
"Last-Translator: milijan@arnes.si\n"
"Language-Team: register.si\n"
"Language: sl\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Generator: Poedit 3.5\n"
#, perl-brace-format
msgid ""
"Warning: setting locale category LC_MESSAGES to {locale} failed -- is it "
"installed on this system?"
msgstr ""
"Opozorilo: Neuspešno nastavljanje jezika {locale} za LC_MESSAGES(Je jezik "
"nameščen na sistemu?)."
#, perl-brace-format
msgid ""
"Warning: setting locale category LC_CTYPE to {locale} failed -- is it "
"installed on this system?"
msgstr ""
"Opozorilo: Neuspešno nastavljanje jezika {locale} za LC_CTYPE (Je jezik "
"nameščen na sistemu?)."
msgid "Warning: deprecated --encoding, simply remove it from your usage."
msgstr ""
msgid "Error: --json-stream and --no-json cannot be used together."
msgstr "Napaka : --json-stream in --no-json ne možno uporabiti hkrati."
msgid ""
"Warning: --json-translate has no effect without either --json or --json-"
"stream."
msgstr "Opozorilo: --json-translate nima učinka brez --json ali --json-stream."
msgid "Warning: deprecated --json-translate, use --no-raw instead."
msgstr "Opozorilo: --json-translate je zastarelo, uporabi --no-raw."
msgid "Warning: deprecated --no-json-translate, use --raw instead."
msgstr "Opozorilo: --no-json-translate je zastarelo, uporabi --raw."
#, perl-brace-format
msgid "Loading profile from {path}."
msgstr "Nalagam profil iz {path}."
#, perl-brace-format
msgid "Error: invalid value for --sourceaddr4: {reason}"
msgstr "Napačna vrednost za --sourceaddr4: {reason}"
#, perl-brace-format
msgid "Error: invalid value for --sourceaddr6: {reason}"
msgstr "Napačna vrednost za --sourceaddr6: {reason}"
#, perl-brace-format
msgid ""
"Error: Invalid input '{cli_arg}' in --test. There must be at most one slash "
"('/') character."
msgstr ""
"Napaka: Napačen vnos '{cli_arg}' v --test. Zahtevana je najmanj ena "
"poševnica znak ('/')."
#, perl-brace-format
msgid ""
"Error: Unrecognized test case '{testcase}' in --test. Use --list-tests for a "
"list of valid choices."
msgstr ""
"Napaka: Test '{testcase}' v --test ni prepoznan, Uporabite --list-tests za "
"spisek veljavnih izbir."
#, perl-brace-format
msgid ""
"Error: Unrecognized test module '{module}' in --test. Use --list-tests for a "
"list of valid choices."
msgstr ""
"Napaka: Modul '{module}' v --test ni prepoznan. Uporabite --list-tests za "
"spisek veljavnih izbir."
#, perl-brace-format
msgid "Error: Invalid input '{cli_arg}' in --test."
msgstr "Napaka: Napačen vnos '{cli_arg}' v --test."
#, perl-brace-format
msgid ""
"Notice: Engine does not have test case '{testcase}' enabled in the profile. "
"Forcing..."
msgstr ""
"Pozor: Orodje ne podpira testa '{testcase}', ki je omogočeno v profilu. "
"Vklopljen..."
#, perl-brace-format
msgid "Failed to recognize stop level '{level}'."
msgstr "Ni mogoče prepoznati stopnjo resnosti {level}, ki povzroči ustavitev '"
msgid ""
"--level must be one of CRITICAL, ERROR, WARNING, NOTICE, INFO, DEBUG, DEBUG2 "
"or DEBUG3."
msgstr ""
"--level mora biti eden izmed KRITIČNO, NAPAKA, OPOZORILO, OBVESTILO, INFO, "
"DEBUG, DEBUG2 ali DEBUG3."
msgid ""
"Only one domain can be given for testing. Did you forget to prepend an "
"option with '--<OPTION>'?"
msgstr "Lahko se preverja le ena domena. Ste pozabili dodati '--<OPTION>'?"
msgid "Must give the name of a domain to test."
msgstr "Vnesite ime domene za preverjanje."
#, perl-brace-format
msgid "Error loading hints file: {message}"
msgstr "Napaka pri nalaganju hint datoteke: {message}"
msgid "Seconds"
msgstr "Sekund"
msgid "Level"
msgstr "Stopnja"
msgid "Module"
msgstr "Modul"
msgid "Testcase"
msgstr "Vrsta testa"
msgid "Message"
msgstr "Sporočilo"
msgid "Looks OK."
msgstr "Videti je v redu."
msgid ""
"\n"
"\n"
" Level\tNumber of log entries"
msgstr ""
"\n"
"\n"
" Stopnja\tŠtevilo dnevniških vnosov"
#, perl-format
msgid "%8s\t%5d entries.\n"
msgstr "%8s\t%5d vnosov.\n"
msgid "--ns must be a name or a name/ip pair."
msgstr "--ns mora biti ime ali ime/IP."
msgid ""
"--ds ds data must be in the form \"keytag,algorithm,type,digest\". E.g. "
"space is not permitted anywhere in the string."
msgstr ""
"--ds ds mora biti v formatu \"keytag,algorithm,type,digest\". Npr. presledek "
"ni dovoljen nikjer v nizu."
msgid "DEBUG"
msgstr "DEBUG"
msgid "INFO"
msgstr "INFO"
msgid "NOTICE"
msgstr "OBVESTILO"
msgid "WARNING"
msgstr "OPOZORILO"
msgid "ERROR"
msgstr "NAPAKA"
msgid "CRITICAL"
msgstr "KRITIČNO"

171
zonemaster-cli/share/sv.po Normal file
View File

@@ -0,0 +1,171 @@
msgid ""
msgstr ""
"Project-Id-Version: 0.0.5\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-12-12 10:17+0100\n"
"PO-Revision-Date: 2023-05-31 08:42+0000\n"
"Last-Translator: mattias.paivarinta@iis.se\n"
"Language-Team: Zonemaster Team\n"
"Language: sv\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
#, perl-brace-format
msgid ""
"Warning: setting locale category LC_MESSAGES to {locale} failed -- is it "
"installed on this system?"
msgstr ""
"Varning: misslyckades med att sätta locale kategori LC_MESSAGES till "
"'{locale}' finns den installerad på det här systemet?"
#, perl-brace-format
msgid ""
"Warning: setting locale category LC_CTYPE to {locale} failed -- is it "
"installed on this system?"
msgstr ""
"Varning: misslyckades med att sätta locale kategori LC_CTYPE till '{locale}' "
" finns den installerad på det här systemet?"
msgid "Warning: deprecated --encoding, simply remove it from your usage."
msgstr ""
msgid "Error: --json-stream and --no-json cannot be used together."
msgstr "Fel: --json-stream och --no-json kan inte användas tillsammans."
msgid ""
"Warning: --json-translate has no effect without either --json or --json-"
"stream."
msgstr ""
"Varning: --json-translate är verkningslös utan samtidig --json eller --json-"
"stream."
msgid "Warning: deprecated --json-translate, use --no-raw instead."
msgstr ""
"Varning: --json-translate är under utfasning. Använd --no-raw instället."
msgid "Warning: deprecated --no-json-translate, use --raw instead."
msgstr ""
"Varning: --no-json-translate är under utfasning. Använd --raw instället."
#, perl-brace-format
msgid "Loading profile from {path}."
msgstr "Laddar profil från {path}."
#, perl-brace-format
msgid "Error: invalid value for --sourceaddr4: {reason}"
msgstr ""
#, perl-brace-format
msgid "Error: invalid value for --sourceaddr6: {reason}"
msgstr ""
#, perl-brace-format
msgid ""
"Error: Invalid input '{cli_arg}' in --test. There must be at most one slash "
"('/') character."
msgstr ""
#, perl-brace-format
msgid ""
"Error: Unrecognized test case '{testcase}' in --test. Use --list-tests for a "
"list of valid choices."
msgstr ""
#, perl-brace-format
msgid ""
"Error: Unrecognized test module '{module}' in --test. Use --list-tests for a "
"list of valid choices."
msgstr ""
#, perl-brace-format
msgid "Error: Invalid input '{cli_arg}' in --test."
msgstr ""
#, perl-brace-format
msgid ""
"Notice: Engine does not have test case '{testcase}' enabled in the profile. "
"Forcing..."
msgstr ""
#, perl-brace-format
msgid "Failed to recognize stop level '{level}'."
msgstr "Kände inte igen avslutsnivån '{level}'."
msgid ""
"--level must be one of CRITICAL, ERROR, WARNING, NOTICE, INFO, DEBUG, DEBUG2 "
"or DEBUG3."
msgstr ""
"--level måste vara en av CRITICAL, ERROR, WARNING, NOTICE, INFO, DEBUG, "
"DEBUG2 eller DEBUG3."
msgid ""
"Only one domain can be given for testing. Did you forget to prepend an "
"option with '--<OPTION>'?"
msgstr ""
"Endast ett domännamn kan anges för testning. Missade du att sätta '--"
"<OPTION>' före en optionsparameter?"
msgid "Must give the name of a domain to test."
msgstr "Namnet på en domän att testa måste anges."
#, perl-brace-format
msgid "Error loading hints file: {message}"
msgstr ""
msgid "Seconds"
msgstr "Sekund"
msgid "Level"
msgstr "Nivå"
msgid "Module"
msgstr "Modul"
msgid "Testcase"
msgstr "Testfall"
msgid "Message"
msgstr "Meddelande"
msgid "Looks OK."
msgstr "Ser OK ut."
msgid ""
"\n"
"\n"
" Level\tNumber of log entries"
msgstr ""
"\n"
"\n"
" Nivå \tAntal loggmeddelanden"
#, perl-format
msgid "%8s\t%5d entries.\n"
msgstr "%8s\t%5d meddelanden.\n"
msgid "--ns must be a name or a name/ip pair."
msgstr "--ns måste vara ett namn eller namn/ip i det formatet.\""
msgid ""
"--ds ds data must be in the form \"keytag,algorithm,type,digest\". E.g. "
"space is not permitted anywhere in the string."
msgstr ""
msgid "DEBUG"
msgstr "DEBUG"
msgid "INFO"
msgstr "INFO"
msgid "NOTICE"
msgstr "NOTIS"
msgid "WARNING"
msgstr "VARNING"
msgid "ERROR"
msgstr "FEL"
msgid "CRITICAL"
msgstr "KRITISKT"

9
zonemaster-cli/share/update-po Executable file
View File

@@ -0,0 +1,9 @@
#!/bin/sh
if [ -z "$1" ] ; then
echo "error: No PO file specified." >&2
exit 2
fi
po_file="$1" ; shift
make update-po POFILES="$po_file"

View File

@@ -0,0 +1,12 @@
use 5.014002;
use strict;
use warnings FATAL => 'all';
use Test::More;
plan tests => 1;
BEGIN {
use_ok( 'Zonemaster::CLI' ) || print "Bail out!\n";
}
diag( "Testing Zonemaster::CLI $Zonemaster::CLI::VERSION, Perl $], $^X" );

View File

@@ -0,0 +1,37 @@
#!perl
use v5.14.2;
use strict;
use warnings;
use utf8;
use Test::More tests => 2;
use Test::NoWarnings;
use File::Basename qw( dirname );
chdir dirname( dirname( __FILE__ ) ) or BAIL_OUT( "chdir: $!" );
my $makebin = 'make';
sub make {
my @make_args = @_;
undef $ENV{MAKEFLAGS};
my $command = join( ' ', $makebin, '-s', @make_args );
my $output = `$command 2>&1`;
if ( $? == -1 ) {
BAIL_OUT( "failed to execute: $!" );
}
elsif ( $? & 127 ) {
BAIL_OUT( "child died with signal %d, %s coredump\n", ( $? & 127 ), ( $? & 128 ) ? 'with' : 'without' );
}
return $output, $? >> 8;
}
subtest "distcheck" => sub {
my ( $output, $status ) = make "distcheck";
is $status, 0, $makebin . ' distcheck exits with value 0';
is $output, "", $makebin . ' distcheck gives empty output';
};

View File

@@ -0,0 +1,64 @@
#!perl
use v5.14.2;
use strict;
use warnings;
use utf8;
use Test::More; # see done_testing()
use File::Basename qw( dirname );
chdir dirname( dirname( __FILE__ ) ) or BAIL_OUT( "chdir: $!" );
chdir 'share' or BAIL_OUT( "chdir: $!" );
my $makebin = 'make';
sub make {
my @make_args = @_;
undef $ENV{MAKEFLAGS};
my $command = join( ' ', $makebin, '--silent', '--no-print-directory', @make_args );
my $output = `$command`;
if ( $? == -1 ) {
BAIL_OUT( "failed to execute: $!" );
}
elsif ( $? & 127 ) {
BAIL_OUT( "child died with signal %d, %s coredump\n", ( $? & 127 ), ( $? & 128 ) ? 'with' : 'without' );
}
return $output, $? >> 8;
}
subtest "no fuzzy marks" => sub {
my ( $output, $status ) = make "show-fuzzy";
is $status, 0, $makebin . ' show-fuzzy exits with value 0';
is $output, "", $makebin . ' show-fuzzy gives empty output';
};
subtest "check po files" => sub {
my ( $output, $status ) = make "check-po";
is $status, 0, $makebin . ' check-po exits with value 0';
is $output, "", $makebin . ' check-po gives empty output';
};
subtest "tidy po files" => sub {
SKIP: {
my ( $output, $status );
$output = `git diff --numstat`;
skip 'git repo should be clean to run this test', 3 if $output ne '';
diag "Using msgcat version: " . `msgcat --version | head -n1`;
( $output, $status ) = make "tidy-po";
is $status, 0, $makebin . ' tidy-po exits with value 0';
is $output, "", $makebin . ' tidy-po gives empty output';
$output = `git diff --numstat`;
is $output, "", 'all files are tidied (if not run "make tidy-po")';
}
};
done_testing();

18
zonemaster-cli/t/pod.t Normal file
View File

@@ -0,0 +1,18 @@
#!perl -T
use 5.006;
use strict;
use warnings FATAL => 'all';
use Test::More;
# Ensure a recent version of Test::Pod
my $min_tp = 1.22;
eval "use Test::Pod $min_tp";
plan skip_all => "Test::Pod $min_tp required for testing POD" if $@;
my @poddirs;
push @poddirs, ( -e 'lib' ? 'lib' : 'blib' );
push @poddirs, 'script';
all_pod_files_ok( all_pod_files( @poddirs ) );
done_testing;

View File

@@ -0,0 +1,320 @@
#!perl
use 5.14.2;
use utf8;
use warnings;
use Test::More;
use Log::Any::Test;
use Log::Any qw( $log );
use Test::Differences;
use Test::Exception;
use Zonemaster::Engine;
use Zonemaster::Engine::Profile;
use Zonemaster::CLI::TestCaseSet;
require Test::NoWarnings;
lives_ok { # Make sure we get to print log messages in case of errors.
subtest 'parse_modifier_expr' => sub {
my @cases = (
{
name => 'empty',
expr => '',
expected => [],
},
{
name => 'absolute term',
expr => 'term',
expected => [ '', 'term' ],
},
{
name => 'absolute additive',
expr => 'term',
expected => [ '', 'term' ],
},
{
name => 'absolute subtractive',
expr => 'term',
expected => [ '', 'term' ],
},
{
name => 'absolute multiple modifiers',
expr => 'term1+term2',
expected => [ '', 'term1', '+', 'term2' ],
},
{
name => 'relative multiple modifiers',
expr => '-term1+term2',
expected => [ '-', 'term1', '+', 'term2' ],
},
);
for my $case ( @cases ) {
subtest $case->{name} => sub {
my @actual = Zonemaster::CLI::TestCaseSet->parse_modifier_expr( $case->{expr} );
eq_or_diff \@actual, $case->{expected};
};
}
};
subtest 'new' => sub {
my @cases = (
{
name => 'empty',
schema => {},
selection => [],
expect_ok => {
terms => ['all'],
methods => [],
},
},
{
name => 'multiple test modules and test cases',
schema => { 'alpha' => [ 'bravo', 'charlie' ], 'delta' => ['echo'] },
selection => [ 'bravo', 'echo' ],
expect_ok => {
terms => [
'all', 'alpha', 'alpha/bravo', 'alpha/charlie',
'bravo', 'charlie', 'delta', 'delta/echo',
'echo'
],
methods => [ 'bravo', 'echo' ],
},
},
{
name => 'mixed cases',
schema => { 'alpha' => [ 'BRAVO', 'charlie' ] },
selection => [ 'bravo', 'CHARLIE' ],
expect_ok => {
terms => [ 'all', 'alpha', 'alpha/bravo', 'alpha/charlie', 'bravo', 'charlie' ],
methods => [ 'bravo', 'charlie' ],
},
},
{
name => 'illegal test module name 1',
schema => { 'all' => [] },
selection => [],
expect_err => qr/must not be 'all'/i,
},
{
name => 'illegal test module name 2',
schema => { 'ALL' => [] },
selection => [],
expect_err => qr/must not be 'all'/i,
},
{
name => 'illegal test module name 3',
schema => { 'alpha/bravo' => [] },
selection => [],
expect_err => qr{contains forbidden character '/'}i,
},
{
name => 'illegal test case name 1',
schema => { 'alpha' => ['all'] },
selection => [],
expect_err => qr/must not be 'all'/i,
},
{
name => 'illegal test case name 2',
schema => { 'alpha' => ['ALL'] },
selection => [],
expect_err => qr/must not be 'all'/i,
},
{
name => 'illegal test case name 3',
schema => { 'alpha' => ['bravo/charlie'] },
selection => [],
expect_err => qr{contains forbidden character '/'}i,
},
{
name => 'duplicate term 1',
schema => { 'alpha' => ['alpha'] },
selection => [],
expect_err => qr/same name/i,
},
{
name => 'duplicate term 2',
schema => { 'alpha' => ['ALPHA'] },
selection => [],
expect_err => qr/same name/i,
},
{
name => 'duplicate term 3',
schema => { 'ALPHA' => ['alpha'] },
selection => [],
expect_err => qr/same name/i,
},
{
name => 'duplicate term 4',
schema => { 'alpha' => [], 'bravo' => ['alpha'] },
selection => [],
expect_err => qr/same name/i,
},
{
name => 'duplicate term 5',
schema => { 'alpha' => [ 'bravo', 'bravo' ] },
selection => [],
expect_err => qr/same name/i,
},
{
name => 'duplicate term 6',
schema => { 'alpha' => ['bravo'], 'charlie' => ['bravo'] },
selection => [],
expect_err => qr/same name/i,
},
{
name => 'unrecognized test case 1',
schema => { 'alpha' => [] },
selection => ['all'],
expect_err => qr/unrecognized/i,
},
{
name => 'unrecognized test case 2',
schema => { 'alpha' => [] },
selection => ['alpha'],
expect_err => qr/unrecognized/i,
},
);
for my $case ( @cases ) {
subtest $case->{name} => sub {
my $test_case_set;
local $@;
eval {
$test_case_set = Zonemaster::CLI::TestCaseSet->new( #
$case->{selection},
$case->{schema},
);
};
my $err = $@;
my $actual;
if ( !$err ) {
$actual = {
terms => [ sort keys %{ $test_case_set->{_terms} } ],
methods => [ $test_case_set->to_list ],
};
}
if ( defined $case->{expect_err} ) {
like $err, $case->{expect_err}, "error";
}
else {
is $err, "", "no error";
}
if ( defined $case->{expect_ok} ) {
eq_or_diff $actual, $case->{expect_ok}, "result";
}
else {
eq_or_diff $actual, undef, "no result";
}
}; ## end sub
} ## end for my $case ( @cases )
}; ## end 'new' => sub
subtest 'apply_modifier' => sub {
my @cases = (
{
name => 'empty',
schema => {},
selection => [],
modifiers => [],
expected => [],
},
{
name => 'no modifiers',
schema => { basic => [ 'basic01', 'basic02' ] },
selection => ['basic01'],
modifiers => [],
expected => ['basic01'],
},
{
name => 'add a new case',
schema => { basic => [ 'basic01', 'basic02' ] },
selection => ['basic01'],
modifiers => [ '+', 'basic02' ],
expected => [ 'basic01', 'basic02' ],
},
{
name => 'add the same case',
schema => { basic => [ 'basic01', 'basic02' ] },
selection => ['basic01'],
modifiers => [ '+', 'basic01' ],
expected => ['basic01'],
},
{
name => 'replace',
schema => { basic => [ 'basic01', 'basic02' ] },
selection => ['basic01'],
modifiers => [ '', 'basic02' ],
expected => ['basic02'],
},
{
name => 'module expansion',
schema => { basic => ['basic01'], extra => [ 'extra01', 'extra02' ] },
selection => ['basic01'],
modifiers => [ '', 'extra' ],
expected => [ 'extra01', 'extra02' ],
},
{
name => 'all',
schema => { basic => ['basic01'], extra => [ 'extra01', 'extra02' ] },
selection => ['basic01'],
modifiers => [ '', 'all' ],
expected => [ 'basic01', 'extra01', 'extra02' ],
},
{
name => 'multiple modifiers',
schema => { basic => ['basic01'], extra => [ 'extra01', 'extra02' ] },
selection => ['basic01'],
modifiers => [ '', 'all', '-', 'basic' ],
expected => [ 'extra01', 'extra02' ],
},
{
name => 'invalid operator',
schema => { basic => [ 'basic01', 'basic02' ] },
selection => ['basic01'],
modifiers => [ '*', 'basic02' ],
error => qr{unrecognized operator}i,
},
);
for my $case ( @cases ) {
subtest $case->{name} => sub {
my $test_case_set = Zonemaster::CLI::TestCaseSet->new( #
$case->{selection},
$case->{schema},
);
local $@ = '';
eval {
while ( @{ $case->{modifiers} } ) {
my $op = shift @{ $case->{modifiers} };
my $term = shift @{ $case->{modifiers} };
$test_case_set->apply_modifier( $op, $term );
}
};
my $error = $@;
if ( exists $case->{expected} ) {
is $error, '';
eq_or_diff [ $test_case_set->to_list ], $case->{expected};
}
else {
like $error, $case->{error};
}
};
} ## end for my $case ( @cases )
};
};
for my $msg ( @{ $log->msgs } ) {
my $text = sprintf( "%s: %s", $msg->{level}, $msg->{message} );
if ( $msg->{level} =~ /trace|debug|info|notice/ ) {
note $text;
}
else {
diag $text;
}
}
Test::NoWarnings::had_no_warnings();
done_testing;

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
ns1.test 192.0.2.1 {"9kfShNTLVy4wDehBPdpCXg":null}

View File

@@ -0,0 +1,2 @@
. NS ns1.test
ns1.test A 192.0.2.1

View File

@@ -0,0 +1,26 @@
b.root-servers.net 2001:0500:0200:0000:0000:0000:0000:000b {}
b.root-servers.net 199.9.14.201 {"9kfShNTLVy4wDehBPdpCXg":{"Zonemaster::Engine::Packet":{"Zonemaster::LDNS::Packet":{"data":"Q/iEAAABAAEAAAABAAAGAAEAAAYAAQABUYAAQAFhDHJvb3Qtc2VydmVycwNuZXQABW5zdGxkDHZlcmlzaWduLWdycwNjb20AeKV9KAAABwgAAAOEAAk6gAABUYAAACkE0AAAAAAAAA==","timestamp":1731580714.30933,"answerfrom":"199.9.14.201","querytime":0}}}}
l.root-servers.net 2001:0500:009f:0000:0000:0000:0000:0042 {}
l.root-servers.net 199.7.83.42 {"9kfShNTLVy4wDehBPdpCXg":{"Zonemaster::Engine::Packet":{"Zonemaster::LDNS::Packet":{"data":"72GEAAABAAEADQAOAAAGAAEAAAYAAQABUYAAQAFhDHJvb3Qtc2VydmVycwNuZXQABW5zdGxkDHZlcmlzaWduLWdycwNjb20AeKV9KAAABwgAAAOEAAk6gAABUYAAAAIAAQAH6QAAAsAcAAACAAEAB+kAAAQBYsAeAAACAAEAB+kAAAQBY8AeAAACAAEAB+kAAAQBZMAeAAACAAEAB+kAAAQBZcAeAAACAAEAB+kAAAQBZsAeAAACAAEAB+kAAAQBZ8AeAAACAAEAB+kAAAQBaMAeAAACAAEAB+kAAAQBacAeAAACAAEAB+kAAAQBasAeAAACAAEAB+kAAAQBa8AeAAACAAEAB+kAAAQBbMAeAAACAAEAB+kAAAQBbcAewBwAAQABAAfpAAAExikABMB0AAEAAQAH6QAABKr3qgLAgwABAAEAB+kAAATAIQQMwJIAAQABAAfpAAAExwdbDcChAAEAAQAH6QAABMDL5grAsAABAAEAB+kAAATABQXxwL8AAQABAAfpAAAEwHAkBMDOAAEAAQAH6QAABMZhvjXA3QABAAEAB+kAAATAJJQRwOwAAQABAAfpAAAEwDqAHsD7AAEAAQAH6QAABMEADoHBCgABAAEAB+kAAATHB1MqwRkAAQABAAfpAAAEygwbIQAAKRAAAAAAAAAA","querytime":0,"answerfrom":"199.7.83.42","timestamp":1731580714.6482}}}}
h.root-servers.net 198.97.190.53 {"9kfShNTLVy4wDehBPdpCXg":{"Zonemaster::Engine::Packet":{"Zonemaster::LDNS::Packet":{"querytime":0,"timestamp":1731580714.53711,"answerfrom":"198.97.190.53","data":"jU+EAAABAAEADQAOAAAGAAEAAAYAAQABUYAAQAFhDHJvb3Qtc2VydmVycwNuZXQABW5zdGxkDHZlcmlzaWduLWdycwNjb20AeKV9KAAABwgAAAOEAAk6gAABUYAAAAIAAQAH6QAAAsAcAAACAAEAB+kAAAQBYsAeAAACAAEAB+kAAAQBY8AeAAACAAEAB+kAAAQBZMAeAAACAAEAB+kAAAQBZcAeAAACAAEAB+kAAAQBZsAeAAACAAEAB+kAAAQBZ8AeAAACAAEAB+kAAAQBaMAeAAACAAEAB+kAAAQBacAeAAACAAEAB+kAAAQBasAeAAACAAEAB+kAAAQBa8AeAAACAAEAB+kAAAQBbMAeAAACAAEAB+kAAAQBbcAewBwAAQABAAfpAAAExikABMB0AAEAAQAH6QAABKr3qgLAgwABAAEAB+kAAATAIQQMwJIAAQABAAfpAAAExwdbDcChAAEAAQAH6QAABMDL5grAsAABAAEAB+kAAATABQXxwL8AAQABAAfpAAAEwHAkBMDOAAEAAQAH6QAABMZhvjXA3QABAAEAB+kAAATAJJQRwOwAAQABAAfpAAAEwDqAHsD7AAEAAQAH6QAABMEADoHBCgABAAEAB+kAAATHB1MqwRkAAQABAAfpAAAEygwbIQAAKQTQAAAAAAAA"}}}}
h.root-servers.net 2001:0500:0001:0000:0000:0000:0000:0053 {}
j.root-servers.net 2001:0503:0c27:0000:0000:0000:0002:0030 {}
j.root-servers.net 192.58.128.30 {"9kfShNTLVy4wDehBPdpCXg":{"Zonemaster::Engine::Packet":{"Zonemaster::LDNS::Packet":{"querytime":0,"answerfrom":"192.58.128.30","timestamp":1731580714.58295,"data":"R7KEAAABAAEADQAOAAAGAAEAAAYAAQABUYAAQAFhDHJvb3Qtc2VydmVycwNuZXQABW5zdGxkDHZlcmlzaWduLWdycwNjb20AeKV9KAAABwgAAAOEAAk6gAABUYAAAAIAAQAH6QAAAsAcAAACAAEAB+kAAAQBYsAeAAACAAEAB+kAAAQBY8AeAAACAAEAB+kAAAQBZMAeAAACAAEAB+kAAAQBZcAeAAACAAEAB+kAAAQBZsAeAAACAAEAB+kAAAQBZ8AeAAACAAEAB+kAAAQBaMAeAAACAAEAB+kAAAQBacAeAAACAAEAB+kAAAQBasAeAAACAAEAB+kAAAQBa8AeAAACAAEAB+kAAAQBbMAeAAACAAEAB+kAAAQBbcAewBwAAQABAAfpAAAExikABMB0AAEAAQAH6QAABKr3qgLAgwABAAEAB+kAAATAIQQMwJIAAQABAAfpAAAExwdbDcChAAEAAQAH6QAABMDL5grAsAABAAEAB+kAAATABQXxwL8AAQABAAfpAAAEwHAkBMDOAAEAAQAH6QAABMZhvjXA3QABAAEAB+kAAATAJJQRwOwAAQABAAfpAAAEwDqAHsD7AAEAAQAH6QAABMEADoHBCgABAAEAB+kAAATHB1MqwRkAAQABAAfpAAAEygwbIQAAKQXAAAAAAAAA"}}}}
f.root-servers.net 192.5.5.241 {"9kfShNTLVy4wDehBPdpCXg":{"Zonemaster::Engine::Packet":{"Zonemaster::LDNS::Packet":{"querytime":0,"timestamp":1731580714.41004,"answerfrom":"192.5.5.241","data":"1CWEAAABAAEADQAbAAAGAAEAAAYAAQABUYAAQAFhDHJvb3Qtc2VydmVycwNuZXQABW5zdGxkDHZlcmlzaWduLWdycwNjb20AeKV9KAAABwgAAAOEAAk6gAABUYAAAAIAAQAH6QAABAFjwB4AAAIAAQAH6QAABAFlwB4AAAIAAQAH6QAABAFrwB4AAAIAAQAH6QAABAFswB4AAAIAAQAH6QAABAFkwB4AAAIAAQAH6QAAAsAcAAACAAEAB+kAAAQBYsAeAAACAAEAB+kAAAQBacAeAAACAAEAB+kAAAQBZsAeAAACAAEAB+kAAAQBaMAeAAACAAEAB+kAAAQBZ8AeAAACAAEAB+kAAAQBasAeAAACAAEAB+kAAAQBbcAewGcAAQABAAfpAAAEwCEEDMBnABwAAQAH6QAAECABBQAAAgAAAAAAAAAAAAzAdgABAAEAB+kAAATAy+YKwHYAHAABAAfpAAAQIAEFAACoAAAAAAAAAAAADsCFAAEAAQAH6QAABMEADoHAhQAcAAEAB+kAABAgAQf9AAAAAAAAAAAAAAABwJQAAQABAAfpAAAExwdTKsCUABwAAQAH6QAAECABBQAAnwAAAAAAAAAAAELAowABAAEAB+kAAATHB1sNwKMAHAABAAfpAAAQIAEFAAAtAAAAAAAAAAAADcAcAAEAAQAH6QAABMYpAATAHAAcAAEAB+kAABAgAQUDuj4AAAAAAAAAAgAwwL8AAQABAAfpAAAEqveqAsC/ABwAAQAH6QAAECgBAbgAEAAAAAAAAAAAAAvAzgABAAEAB+kAAATAJJQRwM4AHAABAAfpAAAQIAEH/gAAAAAAAAAAAAAAU8DdAAEAAQAH6QAABMAFBfHA3QAcAAEAB+kAABAgAQUAAC8AAAAAAAAAAAAPwOwAAQABAAfpAAAExmG+NcDsABwAAQAH6QAAECABBQAAAQAAAAAAAAAAAFPA+wABAAEAB+kAAATAcCQEwPsAHAABAAfpAAAQIAEFAAASAAAAAAAAAAANDcEKAAEAAQAH6QAABMA6gB7BCgAcAAEAB+kAABAgAQUDDCcAAAAAAAAAAgAwwRkAAQABAAfpAAAEygwbIcEZABwAAQAH6QAAECABDcMAAAAAAAAAAAAAADUAACn//wAAAAAAAA=="}}}}
f.root-servers.net 2001:0500:002f:0000:0000:0000:0000:000f {}
k.root-servers.net 193.0.14.129 {"9kfShNTLVy4wDehBPdpCXg":{"Zonemaster::Engine::Packet":{"Zonemaster::LDNS::Packet":{"data":"EJGEAAABAAEAAAABAAAGAAEAAAYAAQABUYAAQAFhDHJvb3Qtc2VydmVycwNuZXQABW5zdGxkDHZlcmlzaWduLWdycwNjb20AeKV9KAAABwgAAAOEAAk6gAABUYAAACkE0AAAAAAAAA==","timestamp":1731580714.61398,"answerfrom":"193.0.14.129","querytime":0}}}}
k.root-servers.net 2001:07fd:0000:0000:0000:0000:0000:0001 {}
e.root-servers.net 192.203.230.10 {"9kfShNTLVy4wDehBPdpCXg":{"Zonemaster::Engine::Packet":{"Zonemaster::LDNS::Packet":{"data":"mDyEAAABAAEADQAOAAAGAAEAAAYAAQABUYAAQAFhDHJvb3Qtc2VydmVycwNuZXQABW5zdGxkDHZlcmlzaWduLWdycwNjb20AeKV9KAAABwgAAAOEAAk6gAABUYAAAAIAAQAH6QAAAsAcAAACAAEAB+kAAAQBYsAeAAACAAEAB+kAAAQBY8AeAAACAAEAB+kAAAQBZMAeAAACAAEAB+kAAAQBZcAeAAACAAEAB+kAAAQBZsAeAAACAAEAB+kAAAQBZ8AeAAACAAEAB+kAAAQBaMAeAAACAAEAB+kAAAQBacAeAAACAAEAB+kAAAQBasAeAAACAAEAB+kAAAQBa8AeAAACAAEAB+kAAAQBbMAeAAACAAEAB+kAAAQBbcAewBwAAQABAAfpAAAExikABMB0AAEAAQAH6QAABKr3qgLAgwABAAEAB+kAAATAIQQMwJIAAQABAAfpAAAExwdbDcChAAEAAQAH6QAABMDL5grAsAABAAEAB+kAAATABQXxwL8AAQABAAfpAAAEwHAkBMDOAAEAAQAH6QAABMZhvjXA3QABAAEAB+kAAATAJJQRwOwAAQABAAfpAAAEwDqAHsD7AAEAAQAH6QAABMEADoHBCgABAAEAB+kAAATHB1MqwRkAAQABAAfpAAAEygwbIQAAKRAAAAAAAAAA","querytime":0,"timestamp":1731580714.3888,"answerfrom":"192.203.230.10"}}}}
e.root-servers.net 2001:0500:00a8:0000:0000:0000:0000:000e {}
d.root-servers.net 199.7.91.13 {"9kfShNTLVy4wDehBPdpCXg":{"Zonemaster::Engine::Packet":{"Zonemaster::LDNS::Packet":{"answerfrom":"199.7.91.13","timestamp":1731580714.37701,"querytime":0,"data":"ZOyEAAABAAEADQAOAAAGAAEAAAYAAQABUYAAQAFhDHJvb3Qtc2VydmVycwNuZXQABW5zdGxkDHZlcmlzaWduLWdycwNjb20AeKV9KAAABwgAAAOEAAk6gAABUYAAAAIAAQAH6QAAAsAcAAACAAEAB+kAAAQBYsAeAAACAAEAB+kAAAQBY8AeAAACAAEAB+kAAAQBZMAeAAACAAEAB+kAAAQBZcAeAAACAAEAB+kAAAQBZsAeAAACAAEAB+kAAAQBZ8AeAAACAAEAB+kAAAQBaMAeAAACAAEAB+kAAAQBacAeAAACAAEAB+kAAAQBasAeAAACAAEAB+kAAAQBa8AeAAACAAEAB+kAAAQBbMAeAAACAAEAB+kAAAQBbcAewBwAAQABAAfpAAAExikABMB0AAEAAQAH6QAABKr3qgLAgwABAAEAB+kAAATAIQQMwJIAAQABAAfpAAAExwdbDcChAAEAAQAH6QAABMDL5grAsAABAAEAB+kAAATABQXxwL8AAQABAAfpAAAEwHAkBMDOAAEAAQAH6QAABMZhvjXA3QABAAEAB+kAAATAJJQRwOwAAQABAAfpAAAEwDqAHsD7AAEAAQAH6QAABMEADoHBCgABAAEAB+kAAATHB1MqwRkAAQABAAfpAAAEygwbIQAAKQWqAAAAAAAA"}}}}
d.root-servers.net 2001:0500:002d:0000:0000:0000:0000:000d {}
g.root-servers.net 2001:0500:0012:0000:0000:0000:0000:0d0d {}
g.root-servers.net 192.112.36.4 {"9kfShNTLVy4wDehBPdpCXg":{"Zonemaster::Engine::Packet":{"Zonemaster::LDNS::Packet":{"querytime":0,"timestamp":1731580714.42995,"answerfrom":"192.112.36.4","data":"8aWEAAABAAEAAAABAAAGAAEAAAYAAQABUYAAQAFhDHJvb3Qtc2VydmVycwNuZXQABW5zdGxkDHZlcmlzaWduLWdycwNjb20AeKV9KAAABwgAAAOEAAk6gAABUYAAACkE0AAAAAAAAA=="}}}}
m.root-servers.net 2001:0dc3:0000:0000:0000:0000:0000:0035 {}
m.root-servers.net 202.12.27.33 {"9kfShNTLVy4wDehBPdpCXg":{"Zonemaster::Engine::Packet":{"Zonemaster::LDNS::Packet":{"timestamp":1731580714.65964,"answerfrom":"202.12.27.33","querytime":0,"data":"reuEAAABAAEAAAABAAAGAAEAAAYAAQABUYAAQAFhDHJvb3Qtc2VydmVycwNuZXQABW5zdGxkDHZlcmlzaWduLWdycwNjb20AeKV9KAAABwgAAAOEAAk6gAABUYAAACkE0AAAAAAAAA=="}}}}
a.root-servers.net 198.41.0.4 {"9kfShNTLVy4wDehBPdpCXg":{"Zonemaster::Engine::Packet":{"Zonemaster::LDNS::Packet":{"answerfrom":"198.41.0.4","timestamp":1731580714.27699,"querytime":0,"data":"9WaEAAABAAEADQALAAAGAAEAAAYAAQABUYAAQAFhDHJvb3Qtc2VydmVycwNuZXQABW5zdGxkDHZlcmlzaWduLWdycwNjb20AeKV9KAAABwgAAAOEAAk6gAABUYAAAAIAAQAH6QAABAFswB4AAAIAAQAH6QAABAFqwB4AAAIAAQAH6QAABAFmwB4AAAIAAQAH6QAABAFowB4AAAIAAQAH6QAABAFkwB4AAAIAAQAH6QAABAFiwB4AAAIAAQAH6QAABAFrwB4AAAIAAQAH6QAABAFpwB4AAAIAAQAH6QAABAFtwB4AAAIAAQAH6QAABAFlwB4AAAIAAQAH6QAABAFnwB4AAAIAAQAH6QAABAFjwB4AAAIAAQAH6QAAAsAcwGcAAQABAAfpAAAExwdTKsBnABwAAQAH6QAAECABBQAAnwAAAAAAAAAAAELAdgABAAEAB+kAAATAOoAewHYAHAABAAfpAAAQIAEFAwwnAAAAAAAAAAIAMMCFAAEAAQAH6QAABMAFBfHAhQAcAAEAB+kAABAgAQUAAC8AAAAAAAAAAAAPwJQAAQABAAfpAAAExmG+NcCUABwAAQAH6QAAECABBQAAAQAAAAAAAAAAAFPAowABAAEAB+kAAATHB1sNwLIAAQABAAfpAAAEqveqAgAAKRAAAAAAAAAA"}}},"UzfnoYKACE71u6V/QQ17nw":{"Zonemaster::Engine::Packet":{"Zonemaster::LDNS::Packet":{"data":"UCOEAAABAA0AAAANAAACAAEAAAIAAQAH6QAAFAFsDHJvb3Qtc2VydmVycwNuZXQAAAACAAEAB+kAAAQBasAeAAACAAEAB+kAAAQBZsAeAAACAAEAB+kAAAQBaMAeAAACAAEAB+kAAAQBZMAeAAACAAEAB+kAAAQBYsAeAAACAAEAB+kAAAQBa8AeAAACAAEAB+kAAAQBacAeAAACAAEAB+kAAAQBbcAeAAACAAEAB+kAAAQBZcAeAAACAAEAB+kAAAQBZ8AeAAACAAEAB+kAAAQBY8AeAAACAAEAB+kAAAQBYcAewBwAAQABAAfpAAAExwdTKsAcABwAAQAH6QAAECABBQAAnwAAAAAAAAAAAELAOwABAAEAB+kAAATAOoAewDsAHAABAAfpAAAQIAEFAwwnAAAAAAAAAAIAMMBKAAEAAQAH6QAABMAFBfHASgAcAAEAB+kAABAgAQUAAC8AAAAAAAAAAAAPwFkAAQABAAfpAAAExmG+NcBZABwAAQAH6QAAECABBQAAAQAAAAAAAAAAAFPAaAABAAEAB+kAAATHB1sNwGgAHAABAAfpAAAQIAEFAAAtAAAAAAAAAAAADcB3AAEAAQAH6QAABKr3qgLAdwAcAAEAB+kAABAoAQG4ABAAAAAAAAAAAAALAAApEAAAAAAAAAA=","timestamp":1731580714.23954,"answerfrom":"198.41.0.4","querytime":0}}}}
a.root-servers.net 2001:0503:ba3e:0000:0000:0000:0002:0030 {}
i.root-servers.net 2001:07fe:0000:0000:0000:0000:0000:0053 {}
i.root-servers.net 192.36.148.17 {"9kfShNTLVy4wDehBPdpCXg":{"Zonemaster::Engine::Packet":{"Zonemaster::LDNS::Packet":{"data":"tQyEAAABAAEAAAABAAAGAAEAAAYAAQABUYAAQAFhDHJvb3Qtc2VydmVycwNuZXQABW5zdGxkDHZlcmlzaWduLWdycwNjb20AeKV9KAAABwgAAAOEAAk6gAABUYAAACkE0AAAAAAAAA==","querytime":0,"timestamp":1731580714.57324,"answerfrom":"192.36.148.17"}}}}
c.root-servers.net 192.33.4.12 {"9kfShNTLVy4wDehBPdpCXg":{"Zonemaster::Engine::Packet":{"Zonemaster::LDNS::Packet":{"data":"b2iEAAABAAEAAAABAAAGAAEAAAYAAQABUYAAQAFhDHJvb3Qtc2VydmVycwNuZXQABW5zdGxkDHZlcmlzaWduLWdycwNjb20AeKV9KAAABwgAAAOEAAk6gAABUYAAACkE0AAAAAAAAA==","querytime":0,"answerfrom":"192.33.4.12","timestamp":1731580714.34358}}}}
c.root-servers.net 2001:0500:0002:0000:0000:0000:0000:000c {}

View File

@@ -0,0 +1,20 @@
{
"net":{
"ipv4": false,
"ipv6": false
},
"resolver":{
"source4": "192.0.2.1",
"source6": "2001:db8::1"
},
"test_levels" : {
"BASIC" : {
"B01_CHILD_FOUND" : "NOTICE",
"B01_ROOT_HAS_NO_PARENT" : "WARNING",
"B02_AUTH_RESPONSE_SOA" : "ERROR"
},
"SYSTEM" : {
"GLOBAL_VERSION" : "INFO"
}
}
}

712
zonemaster-cli/t/usage.t Normal file
View File

@@ -0,0 +1,712 @@
#!perl
use 5.16.0;
use warnings;
use utf8;
use Test::More;
use Config '%Config';
use Encode qw( decode_utf8 );
use File::Basename qw( dirname );
use File::Slurp qw( read_file write_file );
use File::Spec::Functions qw( catfile );
use File::Temp qw( tempdir );
use IPC::Open3;
use JSON::XS;
use POSIX;
use Readonly;
use Symbol qw( gensym );
use Test::Differences;
use Zonemaster::CLI;
use JSON::Validator;
# Force locale C for these unit tests. They depend on the print outs not being
# translated. See zonemaster/zonemaster-cli/issues/438 and
# https://github.com/zonemaster/zonemaster-cli/issues/438#issuecomment-2996235684
$ENV{LC_ALL} = "C.UTF-8";
delete $ENV{LANG};
delete $ENV{LANGUAGE};
# CONSTANTS
Readonly::Array my @SIG_NAMES => do {
my @sig_names;
@sig_names[ split ' ', $Config{sig_num} ] = split ' ', $Config{sig_name};
@sig_names;
};
Readonly::Scalar my $PATH_WRAPPER => catfile( dirname( __FILE__ ), 'usage.wrapper.pl' );
Readonly::Scalar my $PATH_NORMAL_DATAFILE => catfile( dirname( __FILE__ ), 'usage.normal.data' );
Readonly::Scalar my $PATH_FAKE_DATA_DATAFILE => catfile( dirname( __FILE__ ), 'usage.fake-data.data' );
Readonly::Scalar my $PATH_FAKE_ROOT_DATAFILE => catfile( dirname( __FILE__ ), 'usage.fake-root.data' );
Readonly::Array my @PERL => do {
# Detect whether Devel::Cover is running
my $is_covering = !!( eval 'Devel::Cover::get_coverage()' );
note $is_covering ? 'Devel::Cover running' : 'Devel::Cover not covering';
( $^X, $is_covering ? ( '-MDevel::Cover=-silent,1' ) : () )
};
# MUTABLE GLOBAL VARIABLES
our $test_datafile;
# SETUP
if ( $ENV{ZONEMASTER_RECORD} ) {
write_file $PATH_NORMAL_DATAFILE, '';
write_file $PATH_FAKE_DATA_DATAFILE, '';
write_file $PATH_FAKE_ROOT_DATAFILE, '';
}
# HELPERS
sub json_schema {
my ( $schema ) = @_;
my $validator = JSON::Validator->new;
$validator->schema( $schema );
return $validator;
}
sub check_success {
my ( $name, $args, $predicate ) = @_;
subtest $name => sub {
my $result = _run_zonemaster_cli( $test_datafile, @$args );
my $stdout = delete $result->{stdout};
my $stderr = delete $result->{stderr};
my $exitstatus = delete $result->{exitstatus};
if ( $stderr ne '' ) {
note "stderr:\n$stderr" =~ s/\n/\n /gr;
}
if ( ref $predicate eq 'CODE' ) {
if ( $predicate->( $stdout ) ) {
pass 'expected stdout (sub)';
}
else {
fail 'expected stdout (sub)';
diag "actual stdout:\n$stdout" =~ s/\n/\n /gr;
}
}
elsif ( ref $predicate eq 'Regexp' ) {
like $stdout, $predicate, 'expected stdout (regex)';
}
elsif ( blessed $predicate && blessed $predicate eq 'JSON::Validator' ) {
my @items = parse_json_stream( $stdout );
my @errors = $predicate->validate( [@items] );
if ( !eq_or_diff \@errors, [], "schema validation" ) {
diag "actual stdout:\n$stdout" =~ s/\n/\n /gr;
}
}
else {
BAIL_OUT( "unrecognized predicate type" );
}
is $exitstatus, $Zonemaster::CLI::EXIT_SUCCESS, 'success exit status';
}; ## end sub
} ## end sub check_success
sub check_success_report {
my ( $name, $args, $predicates ) = @_;
subtest $name => sub {
check_success 'normal mode', $args, $predicates->{text};
check_success 'raw mode', $args, $predicates->{text};
check_success 'json mode', [ '--json', @$args ],
json_schema(
{
type => "array",
items => $predicates->{json},
}
);
check_success 'json-stream mode', [ '--json-stream', @$args ],
json_schema(
{
type => "array",
contains => $predicates->{json},
}
);
};
}
sub check_usage_error {
my ( $name, $args, $error_pattern ) = @_;
subtest $name => sub {
my $result = _run_zonemaster_cli( undef, @$args );
my $stderr = delete $result->{stderr};
like $stderr, $error_pattern, 'expected error message';
eq_or_diff(
$result,
{
stdout => '',
exitstatus => $Zonemaster::CLI::EXIT_USAGE_ERROR,
},
'no stdout and usage error exit code'
) or diag "stderr:\n$stderr" =~ s/\n/\n /gr;
};
}
sub parse_json_stream {
my ( $text ) = @_;
my $decoder = JSON::XS->new;
my @items;
while ( 1 ) {
$text =~ s/^\s+//;
if ( $text eq '' ) {
last;
}
my ( $item, $len ) = $decoder->decode_prefix( $text );
push @items, $item;
$text = substr $text, $len;
$text =~ s/^\s+//;
}
return @items;
} ## end sub parse_json_stream
sub has_locale {
my ( $locale ) = @_;
my $old_locale = setlocale( LC_CTYPE );
my $success = defined setlocale( LC_CTYPE, $locale );
setlocale( LC_CTYPE, $old_locale );
return $success;
}
sub _run_zonemaster_cli {
my ( $datafile, @args ) = @_;
my @cmd = ( @PERL, $PATH_WRAPPER );
if ( defined $datafile ) {
if ( $ENV{ZONEMASTER_RECORD} ) {
push @cmd, '--record';
}
push @cmd, $datafile;
}
push @cmd, '--', @args;
my $pid = open3( my $stdin, my $stdout, my $stderr = gensym, @cmd );
waitpid( $pid, 0 );
my $exitcode = $?;
if ( POSIX::WIFEXITED( $exitcode ) ) {
local $/ = undef;
return {
stdout => scalar <$stdout>,
stderr => scalar <$stderr>,
exitstatus => POSIX::WEXITSTATUS( $exitcode ),
};
}
elsif ( POSIX::WIFSIGNALED( $exitcode ) ) {
die "child process terminated by signal: " . $SIG_NAMES[ POSIX::WTERMSIG( $exitcode ) ];
}
elsif ( POSIX::WIFSTOPPED( $exitcode ) ) {
die "child process stopped by signal: " . $SIG_NAMES[ POSIX::WSTOPSIG( $exitcode ) ];
}
else {
die "unrecognized exit code $exitcode";
}
} ## end sub _run_zonemaster_cli
# TESTS
do {
local $test_datafile = $PATH_NORMAL_DATAFILE;
note "TESTS USING $test_datafile FOR RECORDED DATA:";
check_success 'normal table output', [ '--test=basic01', '--level=INFO', '.' ], qr{
^
Seconds \s+ Level \s+ Message \n
=+ \s =+ \s =+ \n
\s* \Q0.00\E \s+ INFO \s+ .* \s [v0-9.]+ \s .* \n
}msx;
check_success 'normal table output, no optional fields',
[ '--test=basic01', '--level=INFO', '--no-time', '--no-show-level', '.' ], qr{
^
Message \n
=+ \n
Using .* \n
}msx;
check_success 'normal table output, no optional fields, using underscore alias',
[ '--test=basic01', '--level=INFO', '--no-time', '--no-show_level', '.' ], qr{
^
Message \n
=+ \n
Using .* \n
}msx;
check_success 'normal table output, all fields',
[ '--test=basic01', '--level=INFO', '--show-module', '--show-testcase', '.' ], qr{
^
Seconds \s+ Level \s+ Module \s+ Testcase \s+ Message \n
=+ \s =+ \s =+ \s =+ \s =+ \n
\s* \Q0.00\E \s+ INFO \s+ System \s+ Unspecified \s+ Using .* \n
}msx;
check_success 'normal table output, all fields, using underscore aliases',
[ '--test=basic01', '--level=INFO', '--show_module', '--show_testcase', '.' ], qr{
^
Seconds \s+ Level \s+ Module \s+ Testcase \s+ Message \n
=+ \s =+ \s =+ \s =+ \s =+ \n
\s* \Q0.00\E \s+ INFO \s+ System \s+ Unspecified \s+ Using .* \n
}msx;
check_success '--encoding', [ '--test=basic01', '--json', '--encoding', 'foobar', '.' ], qr{
\Q{"results":[]}\E
}msx;
check_success '--json', [ '--test=basic01', '--json', '.' ], qr{
\Q{"results":[]}\E
}msx;
check_success '--json-stream', [ '--test=basic01', '--json-stream', '--level=INFO', '.' ], sub {
my $found = 0;
for my $item ( parse_json_stream( $_[0] ) ) {
if ( $item->{tag} eq 'GLOBAL_VERSION' ) {
if ( $item->{message} !~ /^Using / ) {
return 0;
}
$found = 1;
}
}
return $found;
};
check_success '--json_stream', [ '--test=basic01', '--json_stream', '--level=INFO', '.' ], sub {
my $found = 0;
for my $item ( parse_json_stream( $_[0] ) ) {
if ( $item->{tag} eq 'GLOBAL_VERSION' ) {
if ( $item->{message} !~ /^Using / ) {
return 0;
}
$found = 1;
}
}
return $found;
};
check_success '--raw', [ '--test=basic01', '--level=INFO', '--raw', '.' ], qr{
^
\s* \Q0.00\E \s+ INFO \s+ GLOBAL_VERSION \s+ version= [v0-9.]+ \n
}msx;
SKIP: {
skip 'sv_SE.UTF-8 locale is unavailable', 5
if !has_locale( 'sv_SE.UTF-8' );
check_success '--json-stream --no-raw',
[ '--test=basic01', '--json-stream', '--no-raw', '--locale=sv_SE.UTF-8', '--level=INFO', '.' ], sub {
my $found = 0;
for my $item ( parse_json_stream( decode_utf8( $_[0] ) ) ) {
if ( $item->{tag} eq 'GLOBAL_VERSION' ) {
if ( $item->{message} !~ qr{^Använder } ) {
return 0;
}
$found = 1;
}
}
return $found;
};
check_success '--json-stream --json-translate',
[ '--test=basic01', '--json-stream', '--json-translate', '--locale=sv_SE.UTF-8', '--level=INFO', '.' ], sub {
my $found = 0;
for my $item ( parse_json_stream( decode_utf8( $_[0] ) ) ) {
if ( $item->{tag} eq 'GLOBAL_VERSION' ) {
if ( $item->{message} !~ qr{^Använder } ) {
return 0;
}
$found = 1;
}
}
return $found;
};
check_success '--json-stream --no-raw --locale',
[ '--test=basic01', '--json-stream', '--no-raw', '--locale=sv_SE.UTF-8', '--level=INFO', '.' ], sub {
my $found = 0;
for my $item ( parse_json_stream( decode_utf8( $_[0] ) ) ) {
if ( $item->{tag} eq 'GLOBAL_VERSION' ) {
if ( $item->{message} !~ qr{^Använder } ) {
return 0;
}
$found = 1;
}
}
return $found;
};
check_success '--json-stream --json-translate --locale',
[ '--test=basic01', '--json-stream', '--no-raw', '--locale=sv_SE.UTF-8', '--level=INFO', '.' ], sub {
my $found = 0;
for my $item ( parse_json_stream( decode_utf8( $_[0] ) ) ) {
if ( $item->{tag} eq 'GLOBAL_VERSION' ) {
if ( $item->{message} !~ qr{^Använder } ) {
return 0;
}
$found = 1;
}
}
return $found;
};
check_success '--locale', [ '--test=basic01', '--locale=sv_SE.UTF-8', '.' ], qr{
\QSer OK ut.\E
}msx;
} ## end SKIP:
check_success_report '--count', [ '--test=basic01', '--count', '.' ], {
text => qr{
\QLooks OK.\E
.*
Level \s+ \QNumber of log entries\E
.*
INFO \s+ \d+
.*
DEBUG \s+ \d+
.*
Level \s+ \QMessage tag\E \s+ \QCount\E
.*
INFO \s+ \w+ \s+ \d+
.*
}msx,
json => {
type => "object",
required => ["count"],
patternProperties => {
'^[A-Z]+[0-9]*$' => {
type => "integer",
},
},
},
};
check_success_report '--nstimes', [ '--test=basic01', '--nstimes', '.' ], {
text => qr{
\QLooks OK.\E
.*
\QName servers\E \s+ Max \s+ Min \s+ Avg \s+ Stddev \s+ Median \s+ Total \s+ Count
.*
\QChild zone\E
.*
\QParent zone\E
.*
Other
.*
\QGrand total\E \s+ \d+
}msx,
json => {
type => "object",
required => ["nstimes"],
properties => {
nstimes => {
type => "array",
items => {
type => "object",
properties => {
child => {
type => "array",
items => {
type => "object",
required => [qw( avg max median min ns stddev total count)],
},
},
parent => {
type => "array",
items => {
type => "object",
required => [qw( avg max median min ns stddev total count)],
},
},
other => {
type => "array",
items => {
type => "object",
required => [qw( avg max median min ns stddev total count)],
},
},
},
},
},
},
},
};
check_success_report '--elapsed', [ '--test=basic01', '--elapsed', '.' ], {
text => qr{
\QLooks OK.\E
.*
\QTotal test run time:\E
}msx,
json => {
type => "object",
required => ["elapsed"],
properties => {
elapsed => {
type => "number",
},
},
},
};
check_success '--level',
[ '--profile=t/usage.profile', '--ipv4', '--sourceaddr4', '', '--test=basic', '--raw', '--level=notice', '.' ],
sub {
my $stdout = $_[0];
return ( $stdout =~ qr{NOTICE .* WARNING .* ERROR}msx )
&& ( $stdout !~ qr{INFO}msx );
};
check_success '--stop-level=', [ '--profile=t/usage.profile', '--stop-level=', '.' ], qr{Looks OK}i;
check_success '--stop-level=warning',
[
'--profile=t/usage.profile', '--ipv4', '--sourceaddr4', '',
'--test=basic', '--raw', '--stop-level=warning', '.'
],
sub {
my $stdout = $_[0];
return ( $stdout =~ qr{NOTICE .* WARNING}msx )
&& ( $stdout !~ qr{ERROR}m );
};
check_success '--stop_level',
[
'--profile=t/usage.profile', '--ipv4', '--sourceaddr4', '',
'--test=basic', '--raw', '--stop_level=warning', '.'
],
sub {
my $stdout = $_[0];
return ( $stdout =~ qr{NOTICE .* WARNING}msx )
&& ( $stdout !~ qr{ERROR}m );
};
my $tempdir = tempdir( CLEANUP => 1 );
my $savefile = catfile( $tempdir, 'saved.data' );
check_success 'run command', [ "--save=$savefile", '--test=basic01', '.' ], sub {
my @saved_lines = read_file $savefile;
my @expected_lines = read_file $PATH_NORMAL_DATAFILE;
return scalar( @saved_lines ) == scalar( @expected_lines );
};
};
do {
local $test_datafile = $PATH_FAKE_DATA_DATAFILE;
note "TESTS USING $test_datafile FOR RECORDED DATA:";
SKIP: {
skip 'crashing test that has never worked on replay (FIXME)', 2
if not $ENV{ZONEMASTER_RECORD};
check_success '--ns', [ '--noipv6', '--raw', '--ns=ns1.a.example/9.9.9.9', 'a.se' ], qr{B02_NO_WORKING_NS};
check_success '--ds',
[
'--noipv6', '--raw', '--test=dnssec02',
'--ds=0,8,2,0000000000000000000000000000000000000000000000000000000000000000',
'zonemaster.net'
],
qr{DS02_NO_DNSKEY_FOR_DS};
}
};
do {
local $test_datafile = $PATH_FAKE_ROOT_DATAFILE;
note "TESTS USING $test_datafile FOR RECORDED DATA:";
check_success '--hints', [ '--noipv6', '--raw', '--hints=t/usage.hints', 'example.' ], qr{CANNOT_CONTINUE}i;
};
do {
local $test_datafile = undef;
note "TESTS USING NO NETWORK AND NO FILE FOR RECORDED DATA:";
check_usage_error 'no domain', [], qr{must give the name of a domain to test}i;
check_usage_error 'too many domains', [ 'example.com', 'example.net' ],
qr{only one domain can be given for testing}i;
check_usage_error 'invalid domain', ['!%~&'], qr{character not permitted}i;
check_usage_error 'unrecognized option', ['--foobar'], qr{unknown option}i;
check_usage_error '--test BAD_MODULE', [ '--test', '!%~&', 'example.' ], qr{unrecognized term '!%~&' in --test}i;
check_usage_error '--test UNKNOWN_MODULE/TESTCASE', [ '--test', 'foobar/foobar01', 'example.' ],
qr{unrecognized term 'foobar/foobar01' in --test}i;
check_usage_error '--test MODULE/UNKNOWN_TESTCASE', [ '--test', 'basic/foobar01', 'example.' ],
qr{unrecognized term 'basic/foobar01' in --test}i;
check_usage_error '--test MODULE//TESTCASE', [ '--test', 'basic//basic01', 'example.' ],
qr{unrecognized term 'basic//basic01' in --test}i;
check_usage_error '--ns BAD_NAME', [ '--ns', '!%~&', 'example.' ], qr{invalid name}i;
check_usage_error '--ns NAME//IP', [ '--ns', 'ns1.example//192.0.2.1', 'example.' ], qr{--ns}i;
check_usage_error '--ns NAME/BAD_IP', [ '--ns', 'ns1.example/foobar', 'example.' ], qr{invalid ip address}i;
check_usage_error '--sourceaddr4', [ '--sourceaddr4', 'foobar', 'example.' ], qr{invalid value}i;
check_usage_error '--sourceaddr6', [ '--sourceaddr6', 'foobar', 'example.' ], qr{invalid value}i;
check_usage_error '--level BAD_LEVEL', [ '--level', 'foobar', 'example.' ], qr{--level}i;
check_usage_error '--stop-level BAD_LEVEL', [ '--stop-level', 'foobar', 'example.' ],
qr{failed to recognize stop level}i;
check_usage_error '--json-stream and --no-json', [ '--json-stream', '--no-json', 'example.' ],
qr{cannot be used together}i;
check_usage_error 'Bad --hints (directory)', [ '--hints', '/', 'example.' ],
qr{error loading hints file}i;
check_usage_error 'Bad --hints (syntax)', [ '--hints', 't/usage.t', 'example.' ],
qr{error loading hints file}i;
check_success '--help', ['--help'], qr{
^Usage:$
.*
zonemaster-cli
.*
^Options:$
.*
--test
}msx;
check_success '-h', ['-h'], qr{
^Usage:$
.*
zonemaster-cli
.*
^Options:$
.*
--test
}msx;
check_success 'Single-character option bundling', ['-h?'], qr{
--test
}msx;
check_success '--version', ['--version'], qr{
^\QZonemaster-CLI version\E .*
^\QZonemaster-Engine version\E .*
^\QZonemaster-LDNS version\E .*
^\QNL NetLabs LDNS version\E .*
}msx;
check_success '--list-tests', ['--list-tests'], qr{
Basic
.*
basic01
}msx;
check_success '--list_tests', ['--list_tests'], qr{
Basic
.*
basic01
}msx;
SKIP: {
skip 'test that hang on FreeBSD (FIXME, see #388)', 2;
check_success '--dump-profile', ['--dump-profile'], qr{
"no_network"
}msx;
check_success '--dump_profile', ['--dump_profile'], qr{
"no_network"
}msx;
};
check_success 'override profile', [ '--dump-profile', '--profile=t/usage.profile' ], sub {
my ( $profile ) = parse_json_stream( $_[0] );
my $ipv4 = exists $profile->{net}{ipv4} ? ( $profile->{net}{ipv4} ? '1' : '0' ) : '<missing>';
my $ipv6 = exists $profile->{net}{ipv6} ? ( $profile->{net}{ipv6} ? '1' : '0' ) : '<missing>';
my $source4 = $profile->{resolver}{source4} // '<missing>';
my $source6 = $profile->{resolver}{source6} // '<missing>';
return
( $ipv4 eq '0' )
&& ( $ipv6 eq '0' )
&& ( $source4 eq '192.0.2.1' )
&& ( $source6 eq '2001:db8::1' );
};
check_success 'override net.ipv4', [ '--dump-profile', '--profile=t/usage.profile', '--ipv4' ], sub {
my ( $profile ) = parse_json_stream( $_[0] );
my $ipv4 = exists $profile->{net}{ipv4} ? ( $profile->{net}{ipv4} ? '1' : '0' ) : '<missing>';
return $ipv4 eq '1';
};
check_success 'override net.ipv6', [ '--dump-profile', '--profile=t/usage.profile', '--ipv6' ], sub {
my ( $profile ) = parse_json_stream( $_[0] );
my $ipv6 = exists $profile->{net}{ipv6} ? ( $profile->{net}{ipv6} ? '1' : '0' ) : '<missing>';
return $ipv6 eq '1';
};
check_success 'override resolver.source4',
[ '--dump-profile', '--profile=t/usage.profile', '--sourceaddr4', '192.0.2.2' ], sub {
my ( $profile ) = parse_json_stream( $_[0] );
my $source4 = $profile->{resolver}{source4} // '<missing>';
return $source4 eq '192.0.2.2';
};
check_success 'override resolver.source6',
[ '--dump-profile', '--profile=t/usage.profile', '--sourceaddr6', '2001:db8::2' ], sub {
my ( $profile ) = parse_json_stream( $_[0] );
my $source6 = $profile->{resolver}{source6} // '<missing>';
return $source6 eq '2001:db8::2';
};
check_success 'override test_cases',
[ '--dump-profile', '--profile=t/usage.profile', '--test=basic01' ], sub {
my ( $profile ) = parse_json_stream( $_[0] );
return
ref $profile->{test_cases} eq 'ARRAY'
&& scalar @{ $profile->{test_cases} } == 1
&& $profile->{test_cases}[0] eq 'basic01';
};
check_success 'override test_cases twice',
[ '--dump-profile', '--profile=t/usage.profile', '--test=-all', '--test=+basic01' ], sub {
my ( $profile ) = parse_json_stream( $_[0] );
return
ref $profile->{test_cases} eq 'ARRAY'
&& scalar @{ $profile->{test_cases} } == 1
&& $profile->{test_cases}[0] eq 'basic01';
};
check_success '--restore', [ "--restore=$PATH_NORMAL_DATAFILE", '--test=basic01', '--level=INFO', '--raw', '.' ],
qr{B01_CHILD_FOUND};
};
done_testing;

View File

@@ -0,0 +1,85 @@
#!/usr/bin/env perl
use v5.16;
use warnings;
use File::Basename qw( dirname );
use File::Spec::Functions qw( catfile );
use Zonemaster::CLI;
use Zonemaster::Engine::Nameserver;
use Zonemaster::Engine::Profile;
use lib catfile( dirname( dirname( __FILE__ ) ), 'script' );
# Help Zonemaster::CLI find zonemaster-cli in test context
$Zonemaster::CLI::SCRIPT = catfile( dirname( dirname( __FILE__ ) ), 'script', 'zonemaster-cli' );
# Parse command line options upto and including '--'.
my $opt_record = 0;
my $opt_datafile;
while ( @ARGV ) {
my $arg = shift @ARGV;
if ( substr( $arg, 0, 2 ) eq '--' ) {
if ( $arg eq '--' ) {
last;
}
elsif ( $arg eq '--record' ) {
$opt_record = 1;
}
else {
die "unrecognized option '$arg'";
}
}
else {
if ( defined $opt_datafile ) {
die "too many data files provided";
}
$opt_datafile = $arg;
}
} ## end while ( @ARGV )
if ( $opt_record && !defined $opt_datafile ) {
die "must not specify --record without also specifying a data file";
}
# Prime Zonemaster Engine before letting zonemaster-cli do its thing
if ( !$opt_record ) {
Zonemaster::Engine::Profile->effective->set( q{no_network}, 1 );
}
if ( $opt_datafile ) {
Zonemaster::Engine::Nameserver->restore( $opt_datafile );
}
our $EXIT_STATUS;
our $EMITTED_WARNING = 0;
do {
# Intercept warn()
local $SIG{__WARN__} = sub {
print STDERR "__WARN__: " . $_[0];
$EMITTED_WARNING = 1;
};
# Run Zonemaster::CLI
eval {
$EXIT_STATUS = Zonemaster::CLI->run( @ARGV );
1;
} or do {
print STDERR $@;
$EXIT_STATUS = $Zonemaster::CLI::EXIT_GENERIC_ERROR;
};
};
# Wrap up and terminate
if ( $opt_record ) {
Zonemaster::Engine::Nameserver->save( $opt_datafile );
}
if ( $EMITTED_WARNING ) {
say STDERR "EXIT 125: one or more warnings were emitted";
exit 125;
}
exit $EXIT_STATUS;