# Copyright (C) 2017 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 package QtQA::Proc::Reliable::Strategy::GenericRegex; use strict; use warnings; use base qw( QtQA::Proc::Reliable::Strategy ); use Carp; use List::MoreUtils qw( firstval ); use Readonly; use Text::Trim; # Default maximum amount of junk errors before we give up Readonly my $NUM_TRIES => 10; sub new { my ($class) = @_; my $self = $class->SUPER::new( ); $self->{ junk_error_count } = 0; $self->{ num_tries } = $NUM_TRIES; $self->{ patterns } = []; $self->{ stderr_patterns } = []; $self->{ stdout_patterns } = []; return bless $self, $class; } sub push_stdout_patterns { my ($self, @patterns) = @_; push @{$self->{ stdout_patterns }}, @patterns; return; } sub push_stderr_patterns { my ($self, @patterns) = @_; push @{$self->{ stderr_patterns }}, @patterns; return; } sub push_patterns { my ($self, @patterns) = @_; push @{$self->{ patterns }}, @patterns; return; } sub num_tries { my ($self, $num_tries) = @_; if (defined $num_tries) { $self->{ num_tries } = $num_tries; } return $self->{ num_tries }; } sub _process_text { my ($self, $text, @patterns) = @_; # If already found a problem, no need to parse return if $self->_junk_error( ); confess 'internal error: undefined $text' unless defined( $text ); my $match = firstval { $text =~ $_ } @patterns; return if !$match; ++$self->{ junk_error_count }; $self->_set_junk_error({ text => $text, pattern => $match, }); return; } sub process_stdout { my ($self, $text) = @_; return $self->_process_text( $text, @{$self->{ stdout_patterns }}, @{$self->{ patterns }}, ); } sub process_stderr { my ($self, $text) = @_; return $self->_process_text( $text, @{$self->{ stderr_patterns }}, @{$self->{ patterns }}, ); } sub should_retry { my ($self) = @_; return if $self->{ junk_error_count } > $self->{ num_tries }; my $junk_error = $self->_junk_error( ); return if !$junk_error; return "this error:\n ".trim($junk_error->{ text })."\n" ."...was considered possibly junk due to matching $junk_error->{ pattern }"; } # Basic sanity check to warn if someone didn't set up the object correctly sub _sanity_check { my ($self) = @_; return if ($self->{ done_sanity_check }); $self->{ done_sanity_check } = 1; my @all_patterns = ( @{$self->{ patterns }}, @{$self->{ stdout_patterns }}, @{$self->{ stderr_patterns }}, ); if (scalar(@all_patterns) == 0) { carp 'useless use of ' . __PACKAGE__ . ' with no patterns'; } return; } sub about_to_run { my ($self) = @_; $self->_sanity_check( ); # Starting a new run, so discard current error (if any) $self->_set_junk_error( undef ); return; } sub _junk_error { my ($self) = @_; return $self->{ junk_error }; } sub _set_junk_error { my ($self, $error) = @_; $self->{ junk_error } = $error; return; } =head1 NAME QtQA::Proc::Reliable::Strategy::GenericRegex - generic retry strategy based on parsing with regexes =head1 SYNOPSIS package QtQA::Proc::Reliable::Strategy::GCC; use base qw(QtQA::Proc::Reliable::Strategy::GenericRegex); sub new { my ($class) = @_; my $self = $class->SUPER::new( ); $self->push_stderr_patterns( qr{^internal compiler error: }ms, # retry on all ICEs ); return bless $self, $class; } Easily implement a reliable strategy based on parsing the output of a command for a certain set of patterns. =head1 DESCRIPTION Most reliable strategies conceptually want to do the same few things: =over =item * As the command runs, parse its stdout and/or stderr. =item * If the command output matches some predefined set of patterns, consider it as a transient error and retry the command. =item * If the command has been retried a certain number of times already, give up. =back This base class implements the above in an easy-to-use way. To use this class, subclass it, then call any of the following methods (usually in your subclass's constructor): =over =item B( LIST ) Append to the list of patterns applied to STDOUT of the command (if any of these match, the command will be retried). =item B( LIST ) Append to the list of patterns applied to STDERR of the command. =item B( LIST ) Append to the list of patterns applied to both STDOUT and STDERR of the command. =item B =item B( NUMBER ) Get or set the maximum amount of times the command will be allowed to retry (optional, defaults to 10). =back Any mixture of stdout, stderr and combined stdout/stderr patterns may be given, as long as at least one of them is present. =cut 1;