summaryrefslogtreecommitdiff
path: root/scripts/lib/perl5/QtQA/Proc/Reliable/Strategy/GenericRegex.pm
blob: 7a0a82029bc7956f18e9649185198306a261ea88 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
# 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<push_stdout_patterns>( 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<push_stderr_patterns>( LIST )

Append to the list of patterns applied to STDERR of the command.

=item B<push_patterns>( LIST )

Append to the list of patterns applied to both STDOUT and STDERR of the command.

=item B<num_tries>

=item B<num_tries>( 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;