From 3e3e07749a5d1817097fba9f91b757984a6b1d5b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mattias=20P=C3=A4iv=C3=A4rinta?= Date: Thu, 2 May 2024 12:56:07 +0200 Subject: [PATCH] doc wip --- lib/Zonemaster/CLI/TestCaseSet.pm | 139 +++++++++++++++++++++++++++--- 1 file changed, 127 insertions(+), 12 deletions(-) diff --git a/lib/Zonemaster/CLI/TestCaseSet.pm b/lib/Zonemaster/CLI/TestCaseSet.pm index 7763013..6fbad1c 100644 --- a/lib/Zonemaster/CLI/TestCaseSet.pm +++ b/lib/Zonemaster/CLI/TestCaseSet.pm @@ -7,10 +7,71 @@ use Carp qw( croak ); =head1 NAME + Zonemaster::CLI::TestCaseSet - A mutable set of test methods names. + =head1 SYNOPSIS + use Zoneamster::Engine::TestCaseSet; + + # Construct a working subset of test methods {alpha01, alpha02, alpha03, + # beta01} of test methods out of the full set {alpha01, alpha02, alpha03, + # beta01, beta02} distributed across the test modules {alpha, beta}. + my $working_set = Zonemaster::CLI::TestCaseSet->new( + \qw( alpha01 alpha02 alpha03 beta01 ), + { + alpha => \qw( alpha01 alpha02 alpha03 ), + beta => \qw( beta01 beta02 ), + }, + ); + + # Parse a modifier expression into a list of modifiers. + my @modifiers = Zonemaster::CLI::TestCaseSet->parse_modifier_expr( '-alpha+alpha02' ); + + # Traverse the list of modifiers, chunked into (operator, term) pairs. + while ( @modifiers ) { + my $op = shift @modifiers; + my $term = shift @modifiers; + + # Modify the working subset by applying each operator and term. + if ( !$working_set->apply_modifier( $op, $term ) ) { + die __( "Error: Unrecognized term '$term'.\n" ); + } + } + + # Make sure the working subset ends up in the expected state. + if ( join(' ', $working_set->to_list) ne 'alpha02 beta01' ) { + die; + } + =head1 DESCRIPTION +A TestCaseSet primarily represents an immutable full set of test methods and a +mutable subset thereof. The full set of test methods is distributed across the +set of test modules. + +=head2 TERM EXPANSION + +Terms are expanded in one of three ways. + +=over 4 + +=item The full set of all test methods. + +The term matching the string C<'all'>. + +=item The set of all test methods inside one test module. + +Terms matching the name of a test module. + +=item The singleton set of a single test methods + +Terms matching the name of a test methods or the concatenation of a test module, +a slash and a test methodsbelonging to that test module. + +=back + +Term names are matched case insensitively. + =head1 SUBROUTINES =cut @@ -31,14 +92,26 @@ sub parse_modifier_expr { =head1 CONSTRUCTORS +=head2 new() + +In the full set of test methods, methods names must not share the same name as +other test methods or test modules. + =cut sub new { - my ( $class, $initial_cases, %all_methods ) = @_; + my ( $class, $initial_methods, %all_methods ) = @_; + + my %flattened_methods = map { $_ => 1 } map { @{ $_ } } values %all_methods; + for my $method ( @initial_methods ) { + if ( !exists $flattened_methods ) { + die "Undefined initial method '$method'"; + } + } my $obj = { - _cur_cases => { map { $_ => 1 } @$initial_cases }, - _all_term_cases => _get_all_term_cases( \%all_methods ), + _cur_methods => { map { $_ => 1 } @$initial_methods }, + _all_term_methods => _get_all_term_methods( \%all_methods ), }; bless $obj, $class; @@ -48,29 +121,62 @@ sub new { =head1 INSTANCE METHODS +=head2 apply_modifier() + +Update the working subset. + +The given operator is applied to two operands and the result is assigned to the +working subset. The left hand side operand is the current value of the working +subset. The right hand side operand is calculated by L the given term to a subset of test methods. + +Three operators are supported. + +=over 4 + +=item C<'+'> + +Returns the union of the left and right hand side operands + +=item C<'-'> + +Returns the set difference of the left and right hand side operands + +=item C<''> + +Ignores the left hand side operand and returns the right hand side operand. + +=back + +Returns true if the operation is successful. + +Returns false if the term could not be expanded. + +Dies if the operator is not recognized. + =cut sub apply_modifier { my ( $self, $op, $term ) = @_; - my $cases_ref = $self->{_all_term_cases}{ lc $term }; + my $methods_ref = $self->{_all_term_methods}{ lc $term }; - if ( !defined $cases_ref ) { + if ( !defined $methods_ref ) { return 0; } if ( $op eq '' ) { - $self->{_cur_cases} = {}; + $self->{_cur_methods} = {}; } if ( $op eq '-' ) { - for my $case ( @$cases_ref ) { - delete $self->{_cur_cases}{$case}; + for my $method ( @$methods_ref ) { + delete $self->{_cur_methods}{$method}; } } elsif ( $op eq '+' ) { - for my $case ( @$cases_ref ) { - $self->{_cur_cases}{$case} = 1; + for my $method ( @$methods_ref ) { + $self->{_cur_methods}{$method} = 1; } } else { @@ -83,18 +189,27 @@ sub apply_modifier { sub to_list { my ( $self ) = @_; - return sort keys %{ $self->{_cur_cases} }; + return sort keys %{ $self->{_cur_methods} }; } -sub _get_all_term_cases { +sub _get_all_term_methods { my ( $all_methods ) = @_; my $terms = {}; $terms->{all} = []; for my $module ( keys %$all_methods ) { + if ( lc $module eq 'all' ) { + croak "module name must not be 'all'"; + } $terms->{ lc $module } = []; for my $method ( @{ $all_methods->{$module} } ) { + if ( lc $method eq 'all' ) { + croak "method name must not be 'all'"; + } + if ( exists $terms->{lc $method} ) { + croak "found method with same name as another method or module: '$method'"; + } $terms->{ lc $method } = [$method]; $terms->{ lc "$module/$method" } = [$method]; push @{$terms->{ lc $module }}, $method;