File Coverage

blib/lib/WWW/Link/Tester/Adaptive.pm
Criterion Covered Total %
statement 155 176 88.0
branch 64 98 65.3
condition 16 24 66.6
subroutine 21 24 87.5
pod 2 4 50.0
total 258 326 79.1


line stmt bran cond sub pod time code
1             package WWW::Link::Tester::Adaptive;
2             $REVISION=q$Revision: 1.12 $ ; $VERSION = sprintf ( "%d.%02d", $REVISION =~ /(\d+).(\d+)/ );
3              
4             =head1 NAME
5              
6             WWW::Link::Test - adaptive functions for testing links.
7              
8             =head1 SYNOPSIS
9              
10             use WWW::Link::Test
11             $ua=create_a_user_agent();
12             $link=get_a_link_object();
13             WWW::Link::Test::test_link($ua, $link);
14              
15             =head1 DESCRIPTION
16              
17             The adaptive tester uses either a simple or a complex tester depending
18             on which one has been working correctly.
19              
20             =cut
21              
22             # Only one method currently impemented. The others were done
23             # differently but may come back later..
24              
25             =head1 METHODS
26              
27             =head2 test_link
28              
29             This function tests a link by going out to the world and checking it
30             and then telling the associated link object what happened.
31              
32             =cut
33              
34 3     3   3203 use WWW::Link::Tester;
  3         8  
  3         221  
35 3     3   2070 use WWW::Link::Tester::Simple;
  3         6  
  3         162  
36 3     3   2440 use WWW::Link::Tester::Complex;
  3         12  
  3         222  
37              
38             @ISA=qw(WWW::Link::Tester);
39              
40 3     3   25 use strict;
  3         6  
  3         100  
41 3     3   19 use warnings;
  3         7  
  3         109  
42 3     3   17 use vars qw($mode);
  3         6  
  3         1959  
43              
44             sub new {
45 3     3 0 65 my $proto = shift;
46 3   33     24 my $class = ref($proto) || $proto;
47 3         6 my $self = {};
48 3         7 my $ua=shift;
49 3         95 $self->{"complex"}=new WWW::Link::Tester::Complex $ua;
50 3         31 $self->{"simple"}=new WWW::Link::Tester::Simple $ua;
51 3         13 bless $self, $class;
52             }
53              
54             sub test_link {
55 28     28 1 144 my $self=shift;
56 28         27 my $link=shift;
57 28         29 my $simple=undef;
58 28         64 my $verbose=$self->{"verbose"};
59              
60 28         49 my $cookie=$self->check_cookie($link);
61              
62 28         72 my $url=$link->url();
63              
64 28         65 my($mode, undef)=$cookie->calculate_test_state($link);
65              
66 28         32 my ($response, @redirects);
67             CASE: {
68 28 100       29 ($mode==WWW::Link::Tester::Adaptive::Cookie::MODE_SIMPLE() ) and do {
  28         40  
69 22         82 ($response, @redirects) = $self->{"simple"}->get_response($link);
70 22         41 last CASE;
71             };
72 6 50       11 ($mode==WWW::Link::Tester::Adaptive::Cookie::MODE_COMPLEX() ) and do {
73 6         20 ($response, @redirects) = $self->{"complex"}->get_response($link);
74 6         9 last CASE;
75             };
76 0         0 die "unknown testing mode $mode";
77             }
78              
79 28         76 $self->handle_response($link,$mode,$response,@redirects);
80              
81 28         108 return;
82             }
83              
84             =head2 handle_response
85              
86             The link tester has recieved a response, now the question is what do
87             we do with it. It depends on our testing mode.
88              
89             =cut
90              
91             sub handle_response {
92 29     29 1 321 my $self=shift;
93 29         64 my $link=shift;
94 29         35 my $mode=shift;
95 29         33 my $response=shift;
96 29         126 my @redirects=@_;
97              
98 29         257 my $cookie=$self->check_cookie($link);
99              
100 29         82 my $apply=$cookie->consider_test($link,$response,$mode);
101              
102              
103 29 100       48 if ( $apply ) {
104 26 50       68 print STDERR "applying response\n" if $self->{verbose};
105 26         92 $self->apply_response($link,$response,@redirects);
106             } else {
107 3 50       8 print STDERR "not applying response\n" if $self->{verbose};
108             }
109              
110 29         135 $link->store_response($response, time, ref $self, $mode);
111 29         74 $link->test_cookie($cookie);
112             }
113              
114             =head1 check_cookie
115              
116             This verifies that the given link has a useful test cookie, and gives
117             it an appropriate one if it doesn't.
118              
119             =cut
120              
121             sub check_cookie {
122 57     57 0 65 my $self=shift;
123 57         57 my $link=shift;
124 57         133 my $cookie=$link->test_cookie();
125              
126 57 100 66     245 CASE: {
127 57         66 defined $cookie && ref($cookie) =~ m/WWW::Link::Tester::Adaptive::Cookie/
128             and last;
129 18 50       41 defined $cookie and warn "replacing old cookie type: " . ref $cookie;
130 18         64 $cookie=WWW::Link::Tester::Adaptive::Cookie->new();
131             }
132 57         92 return $cookie;
133             }
134              
135              
136             package WWW::Link::Tester::Adaptive::Cookie;
137              
138 3     3   27 use warnings;
  3         12  
  3         102  
139 3     3   21 use WWW::Link::Tester;
  3         7  
  3         5570  
140              
141             our ( $verbose );
142              
143             =head1 TESTING MODES
144              
145             We have two different ways of testing. One us network efficient, but
146             is less likely to give a fully correct answer. The other is less
147             network efficient, but more tests more carefully.
148              
149             We assume that the complex testing system is correct. We will allow
150             the simple testing system to be used only as long as we have no
151             reason to suspect inaccuracy we will test with the simple tester.
152              
153             =head2 verifying testing..
154              
155             Every now and then (after 20 tests) we will verify that our method of
156             testing is consistent. We do this by trying simple then complex
157             testing in order. If they are inconsistent then we finally try simple
158             testing one more time then mark the simple testing as wrong.
159              
160             =head2 testing at status changes.
161              
162             If a link changes status then we will try to verify it sooner..
163              
164             =head2 statuses
165              
166             There are two flags that can be set.
167              
168             OKAY_SIMPLE_WORKING - link tests correctly with both complex and
169             simple testers when it is working.
170              
171             OKAY_SIMPLE_BROKEN- link tests correctly with both complex and
172             simple testers when it is broken.
173              
174             As long as these are both set then most of our testing should be done
175             in simple mode.
176              
177             =cut
178              
179             # Occasionally we try complex mode. We see if this gives a different
180             # result. If it does then we switch over to complex testing. This
181             # means that we will be somewhat delayed in finding link problems that
182             # only complex mode discovers
183              
184             # =cut
185              
186              
187             sub BANNED_SIMPLE () {1<<1;}
188             sub BANNED_COMPLEX () {1<<2;}
189              
190             #sub CHECK_UNDETERMINED () {1<<3;}
191              
192             sub CHECK_VERIFY_WORKING_SIMPLE () {1<<6;}
193             sub CHECK_VERIFY_BROKEN_SIMPLE () {1<<6;}
194              
195             sub UNTESTABLE_SIMPLE () {1<<7;}
196             sub UNTESTABLE_COMPLEX () {1<<8;}
197              
198             sub LAST_SIMPLE_WORKING () {1<<8;}
199              
200             #testing modes to return
201 82     82   522 sub MODE_SIMPLE {1;}
202 72     72   204 sub MODE_COMPLEX {2;}
203              
204 4     4   24 sub TIME_SHORT {1;}
205 24     24   82 sub TIME_NORMAL {2;}
206              
207 0     0   0 sub UPDATE_LINK {1;}
208 0     0   0 sub TRY_ONLY {2;}
209              
210             #switch over triggers. We mostly want to test complex
211             sub TRY_COMPLEX_EVERY_SIMPLE () {11;}
212             sub TRY_SIMPLE_EVERY_COMPLEX () {5;}
213              
214             #how many inconsistent tests before we decide it's a sure problem
215             sub STABLE_INCONSISTENT () {3;}
216             sub PART_INCONSISTENT () {1;}
217              
218             sub new {
219 18     18   40 my $s=shift;
220 18   33     80 my $class = ref($s) || $s;
221 18         30 my $self={};
222 18         42 $self->{settings}=0 ;
223 18         71 bless $self, $class;
224             }
225              
226             #fixme: special cases
227             #link is suspicious (others on server look broken)
228             #link has just changed status?
229              
230             sub calculate_test_state {
231 28     28   30 my $self=shift;
232 28         26 my $link=shift;
233              
234 28 50       69 defined $self->{'simple'} && do {
235 0         0 warn 'deleting $self->{simple} from cookie';
236 0         0 delete $self->{'simple'};
237             };
238              
239 28 50       61 defined $self->{'complex'} && do {
240 0         0 warn 'deleting $self->{complex} from cookie';
241 0         0 delete $self->{'complex'};
242             };
243              
244 28 50       52 defined $self->{settings} or $self->{settings}=0;
245 28         36 my $settings=$self->{settings};
246              
247             #user controlled cases
248 28 50 33     66 $settings & BANNED_SIMPLE && $settings & BANNED_COMPLEX and
249             die "banned from both simple and complex testing";
250 28 50       56 $settings & BANNED_SIMPLE && return MODE_SIMPLE;
251 28 50       55 $settings & BANNED_COMPLEX && return MODE_COMPLEX;
252              
253 28         67 my $url=$link->url();
254 28         87 my $count=$link->testcount();
255              
256             #now look at history..
257 28         40 my @responses=@_;
258              
259 28         69 for (my $i=0; @responses < 4; $i++) {
260 82         183 my @resp=$link->recover_response($i);
261 82 100       175 @resp or last;
262 70 50       107 defined $resp[2] or do {
263 0         0 warn "last tester not recorded";
264 0         0 last;
265             };
266 70 50       116 last unless $resp[2] eq "WWW::Link::Tester::Adaptive";
267 70         173 push @responses, \@resp;
268             }
269 28         37 my $inconsistency = 0;
270 28         30 foreach (@{$self->{test_consistency}}) {
  28         68  
271 45 100       98 next unless defined $_;
272 17 100       39 $inconsistency = $_ if $_ > $inconsistency;
273             }
274              
275 28 50       57 print STDERR "link inconsistency $inconsistency\n"
276             if $verbose;
277              
278             CASE: {
279              
280 28 100       27 $inconsistency <= 0 && do {
  28         55  
281 19 100       57 ( $count % TRY_COMPLEX_EVERY_SIMPLE )
282             == ( TRY_COMPLEX_EVERY_SIMPLE - 1 )
283             and return MODE_COMPLEX, TIME_NORMAL;
284 18 50       28 print STDERR "returning simple for normal testing\n"
285             if $verbose;
286 18         51 return MODE_SIMPLE, TIME_NORMAL;
287             };
288              
289 9 100       18 $inconsistency < STABLE_INCONSISTENT && do {
290 4 100       7 if ( $responses[0][3] == MODE_COMPLEX){
291 2 50       5 print STDERR "returning simple for instability testing\n"
292             if $verbose;
293 2         4 return MODE_SIMPLE, TIME_SHORT;
294             } else {
295 2 50       5 print STDERR "returning complex for instability testing\n"
296             if $verbose;
297 2         4 return MODE_COMPLEX, TIME_NORMAL;
298             }
299             };
300              
301 5 50       12 $inconsistency >= STABLE_INCONSISTENT && do {
302             ( $count % TRY_SIMPLE_EVERY_COMPLEX )
303             == ( TRY_SIMPLE_EVERY_COMPLEX - 1 )
304 5 100       8 and do {
305 2 50       5 print STDERR "returning simple incase it's working again\n"
306             if $verbose;
307 2         5 return MODE_SIMPLE, TIME_SHORT;
308             };
309 3 50       7 print STDERR "returning complex for careful testing\n"
310             if $verbose;
311 3         6 return MODE_COMPLEX, TIME_NORMAL;
312             };
313 0         0 die "no inconsistency value";
314             }
315              
316 0         0 die "shouldn't get here";
317             }
318              
319             # =comment
320              
321             # we've just done a test
322             # we aren't going to apply it to the link.
323             # we should see if it changes anything about our opionion
324              
325             # we return 1 if we think that this response is good for applying to the link.
326              
327             # =cut
328              
329             sub consider_test {
330 29     29   29 my $self=shift;
331 29         27 my $link=shift;
332              
333 29         30 my $response=shift;
334 29         32 my $mode=shift;
335              
336 29         99 my ($old_response,$old_time,$old_tester,$old_mode)
337             = $link->recover_response(0);
338              
339 29 100       72 defined $old_response or return 1;
340 20 50       34 defined $old_mode or do {
341 0         0 warn "old mode not defined; treating as first test";
342 0         0 return 1;
343             };
344              
345             # ($old_response,$old_time,$old_tester,$old_mode)
346             # = ($self->{try_response}, $self->{try_time}, undef, $self->{try_mode})
347             # if defined $self->{try_response} and $self->{try_time} > $old_time;
348              
349 20         22 my $apply=0;
350              
351 20 50       30 die "mode not defined" unless defined $mode;
352              
353 20 50       46 $self->{test_consistency} = [] unless defined $self->{test_consistency};
354              
355 20 50       33 print STDERR "mode is $mode and old mode is $old_mode\n"
356             if $verbose;
357 20 50       32 print STDERR "simple is ". MODE_SIMPLE
358             . " and complex is " . MODE_COMPLEX . "\n" if $verbose;
359              
360              
361             #unsupported protocols should always be handled by simple by
362             #default. It's also possible for simple to handle some protocols
363             #that complex doesn't although this is not a good situation.
364              
365             ( $mode == MODE_COMPLEX and $response->code == RC_PROTOCOL_UNSUPPORTED )
366 20 100 100     34 and do {
367 1         25 $self->{test_consistency} = [];
368 1         3 return 1;
369             };
370              
371             ( $mode == MODE_SIMPLE and $old_mode == MODE_COMPLEX )
372 19 100 100     81 || ( $mode == MODE_COMPLEX and $old_mode == MODE_SIMPLE ) and do {
      100        
      66        
373 7 50       80 print STDERR "considering consistency of last two tests\n"
374             if $verbose;
375 7 100       9 my $scode = short_code( ($mode == MODE_SIMPLE)
376             ? $old_response->code
377             : $old_response->code );
378 7 100       20 $self->{test_consistency}[$scode]=0
379             unless defined $self->{test_consistency}[$scode];
380 7 50       14 if ( $self->responses_are_equivalent( $response, $old_response) ) {
381 0 0       0 if ( $self->{test_consistency}[$scode] > STABLE_INCONSISTENT) {
382 0         0 $self->{test_consistency}[$scode] = PART_INCONSISTENT;
383             } else {
384 0         0 $self->{test_consistency}[$scode] = 0;
385             }
386             } else {
387 7         20 $self->{test_consistency}[short_code($old_response->code)]++;
388             # inconsistent test so we don't want to reduce consistency of other
389             # codes; return now
390 7         10 return $mode == MODE_COMPLEX ;
391             }
392             };
393              
394              
395             # inconsistency can be caused by intermittent network problems...
396             # we decay it away if it isn't confirmed.
397              
398             # FIXME: maybe we shouldn't do this when we have a known
399             # inconsistent link?
400              
401 12         14 foreach my $inconsistency (@{$self->{test_consistency}}) {
  12         26  
402 15 100       32 next unless defined $inconsistency;
403 6 100       13 $inconsistency -= 0.3 if $inconsistency < STABLE_INCONSISTENT;
404 6 50       35 $inconsistency = 0 if $inconsistency < 0;
405             }
406              
407 12         26 return 1;
408             }
409              
410             sub INTER_TEST_DELAY() {60 * 60 * 1};
411              
412             sub time_want_test {
413 0     0   0 my ($self, $link)=@_;
414 0 0       0 die 'usage $cookie->time_want_test($link)' unless ref $link;
415 0         0 my (undef, $time)=$self->calculate_test_state;
416 0 0       0 return INTER_TEST_DELAY if $time & TIME_SHORT;
417 0         0 return undef;
418             }
419              
420             # =scode
421              
422             # short code - returns the first digit of a response code. The first
423             # digit represents the class.
424              
425             # =cut
426              
427             sub short_code {
428 28     28   185 my $code=shift;
429 28 50       69 die "invalid code $code" unless
430             $code =~ m/^[1-9][0-9][0-9]$/;
431 28         93 $code =~ s/^([1-9])[0-9][0-9]$/$1/;
432 28         49 return $code;
433             }
434              
435             # =comment
436              
437             # responses_are_equivalent - returns true if two responses can be
438             # considered equivalent from the point of view of testing.
439              
440             # =cut
441              
442             sub responses_are_equivalent {
443 7     7   10 my ($self, $resp_a, $resp_b)=@_;
444              
445 7         16 my $scode_a=short_code($resp_a->code);
446 7         16 my $scode_b=short_code($resp_b->code);
447              
448 7         18 return $scode_a == $scode_b;
449              
450             }
451              
452              
453             1; #kEEp rEqUIrE HaPpY.