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:
271
zonemaster-cli/lib/Zonemaster/CLI/TestCaseSet.pm
Normal file
271
zonemaster-cli/lib/Zonemaster/CLI/TestCaseSet.pm
Normal 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;
|
||||
Reference in New Issue
Block a user