use
vars
qw( $MODE_DUMMY $MODE_INTERNAL $MODE_EXTERNAL $MODE_UPDATE )
;
use
vars
qw( $TEST_DUMMY $TEST_EXACTLY $TEST_BY_COUNTING $TEST_GREATER_THAN $TEST_RANGE )
;
use
vars
qw( $iTest $oSearch $sEngine )
;
use
vars
qw( $sSaveOnError )
;
@EXPORT
=
qw( eval_test test
no_test not_working not_working_with_tests not_working_and_abandoned
$MODE_DUMMY $MODE_INTERNAL $MODE_EXTERNAL $MODE_UPDATE
$TEST_DUMMY $TEST_EXACTLY $TEST_BY_COUNTING $TEST_GREATER_THAN $TEST_RANGE
new_engine run_test run_gui_test skip_test count_results
tm_new_engine tm_run_test tm_run_test_no_approx
)
;
use
vars
qw( $VERSION $bogus_query $websearch )
;
$VERSION
= 2.294;
$bogus_query
=
"Bogus"
. $$ .
"NoSuchWord"
.
time
;
(
$MODE_DUMMY
,
$MODE_INTERNAL
,
$MODE_EXTERNAL
,
$MODE_UPDATE
) =
qw(dummy internal external update)
;
(
$TEST_DUMMY
,
$TEST_EXACTLY
,
$TEST_BY_COUNTING
,
$TEST_GREATER_THAN
,
$TEST_RANGE
) = (1..10);
sub
find_websearch
{
unless
(
$websearch
)
{
my
$sProg
=
'WebSearch'
;
my
@asTry
= (
$sProg
);
push
@asTry
, catfile(curdir,
$sProg
);
push
@asTry
, catfile(
qw( blib script )
,
$sProg
);
push
@asTry
,
map
{ (
"$_.bat"
,
"$Config{perlpath} $_"
) }
@asTry
;
DEBUG &&
print
STDERR Dumper(\
@asTry
);
WEBSEARCH_TRY:
foreach
my
$sTry
(
@asTry
)
{
my
$sCmd
=
"$sTry --VERSION"
;
DEBUG &&
print
STDERR
" + W::S::T::find_websearch() cmd ==$sCmd==\n"
;
local
$^W = 0;
my
@as
=
split
(/\s/,
eval
{`
$sCmd
`});
$websearch
=
shift
(
@as
) ||
undef
;
last
WEBSEARCH_TRY
if
$websearch
;
}
$websearch
||=
''
;
undef
$websearch
unless
(
$websearch
=~ m/WebSearch/);
}
return
$websearch
;
}
sub
new
{
my
$class
=
shift
;
my
$sEngines
=
join
(
','
,
''
,
@_
,
''
);
return
bless
{
debug
=> 0,
engines
=>
$sEngines
,
error_count
=> 0,
mode
=>
$MODE_DUMMY
,
verbose
=> 0,
},
$class
;
}
sub
mode
{
my
$self
=
shift
;
my
$new_mode
=
shift
;
if
(
$new_mode
)
{
$self
->{
'mode'
} =
$new_mode
;
}
return
$self
->{
'mode'
};
}
sub
relevant_test
{
my
$self
=
shift
;
return
1
if
(
$self
->{engines} eq
',,'
);
my
$e
=
','
.
shift
().
','
;
return
(
$self
->{engines} =~ m/
$e
/);
}
sub
eval_test
{
my
$self
=
shift
;
my
$sSE
=
shift
;
return
unless
$self
->relevant_test(
$sSE
);
my
$o
= new WWW::Search(
$sSE
);
my
$iVersion
=
$o
->version;
my
$code
=
$o
->test_cases;
$code
||=
''
;
unless
(
$code
ne
''
)
{
print
" $sSE version $iVersion contains no TEST_CASES\n"
;
$self
->{error_count}++;
}
$code
=~ s!
&test
\(!\
$self
->test\(!g;
$code
=~ s/
&no_test
\(/\
$self
->no_test\(/g;
$code
=~ s/
¬_working
\(/\
$self
->not_working\(/g;
$code
=~ s/
¬_working_and_abandoned
\(/\
$self
->not_working_and_abandoned\(/g;
$code
=~ s/
¬_working_with_tests
\(/\
$self
->not_working_with_tests\(/g;
print
"\n"
;
eval
$code
;
warn
$@
if
$@;
}
sub
test
{
my
$self
=
shift
;
my
$sSE
=
shift
;
my
$sM
=
shift
;
my
$file
=
shift
;
my
$query
=
shift
;
my
$test_method
=
shift
;
print
STDERR
" + test($sSE,$sM,$file,$query,$test_method)\n"
if
$self
->{debug};
my
(
$low_end
,
$high_end
) =
@_
;
$low_end
||= 0;
$high_end
||= 0;
my
$sExpected
=
$low_end
;
if
(
$test_method
==
$TEST_GREATER_THAN
)
{
$low_end
++;
$sExpected
=
"$low_end.."
;
}
if
(0 <
$high_end
)
{
$sExpected
=
"$low_end..$high_end"
;
}
return
if
(!
$self
->relevant_test(
$sSE
));
print
" trial $file ("
,
$self
->{
'mode'
},
")\n"
;
if
((
$self
->{
'mode'
} eq
$MODE_INTERNAL
) && (
$query
=~ m/
$bogus_query
/))
{
print
" skipping test on this platform.\n"
;
return
;
}
my
$pwd
= curdir();
my
@asSE
=
split
(/::/,
$sSE
);
my
$path
= catdir(
$pwd
,
'Test-Pages'
,
@asSE
);
mkpath
$path
;
if
(
$self
->{
'mode'
} eq
$MODE_UPDATE
)
{
opendir
DIR,
$path
;
foreach
my
$afile
(
readdir
DIR)
{
unlink
catfile(
$path
,
$afile
)
if
$afile
=~ m/^
$file
/;
}
closedir
DIR;
}
my
(
$v
,
$d
,
$f
) = splitpath(
$file
);
if
(
$d
eq
''
)
{
$file
= catfile(
$path
,
$file
);
}
my
$o
= new WWW::Search(
$sSE
);
my
$version
=
$o
->version;
print
" ($sSE $version, $sM)\n"
;
print
STDERR
" expect to find results in $file\n"
if
$self
->{debug};
my
%src
= (
$MODE_INTERNAL
=>
"--option search_from_file=$file"
,
$MODE_EXTERNAL
=>
''
,
$MODE_UPDATE
=>
"--option search_to_file=$file"
,
);
my
$websearch
=
&find_websearch
;
$websearch
||= catfile(
$pwd
,
'blib'
,
'script'
,
'WebSearch'
);
my
$cmd
=
$Config
{
'perlpath'
} .
" -MExtUtils::testlib $websearch "
;
$cmd
.=
$self
->{debug} ?
'--debug '
.
$self
->{debug} :
''
;
$cmd
.=
" --max 209 --engine $sSE "
.
$src
{
$self
->{
'mode'
}} .
" -- $query"
;
print
" $cmd\n"
if
(
$self
->{verbose} ||
$self
->{debug});
open
(TRIALSTREAM,
"$cmd|"
) ||
die
"$0: cannot run test ($!)\n"
;
open
(TRIALFILE,
">$file.trial"
) ||
die
"$0: cannot open $file.trial for writing ($!)\n"
;
open
(OUTFILE,
">$file.out"
) ||
die
"$0: cannot open $file.out for writing ($!)\n"
if
(
$self
->{
'mode'
} eq
$MODE_UPDATE
);
my
$iActual
= 0;
while
(<TRIALSTREAM>)
{
print
TRIALFILE
$_
;
$iActual
++;
print
OUTFILE
$_
if
(
$self
->{
'mode'
} eq
$MODE_UPDATE
);
}
close
TRIALSTREAM;
close
TRIALFILE;
if
(
$self
->{
'mode'
} eq
$MODE_UPDATE
)
{
close
OUTFILE;
if
(
open
TS,
">$file.README"
)
{
print
TS
"This set of test-result pages was created on "
,
scalar
(
localtime
(
time
)),
"\n"
;
close
TS;
}
my
$iPageCount
=
&wc_l
(
$file
);
my
$iURLCount
=
&wc_l
(
"$file.out"
);
print
" $query --> $iURLCount urls (should be $sExpected) on $iPageCount pages\n"
;
return
;
}
if
(-f
"$file.out"
)
{
my
(
$e
,
$sMsg
) = (0,
''
);
if
(
$test_method
==
$TEST_GREATER_THAN
)
{
if
(
$iActual
<=
$low_end
)
{
$sMsg
.=
"expected more than $low_end, but got $iActual; "
;
$e
= 1;
}
}
elsif
(
$test_method
==
$TEST_RANGE
)
{
$sMsg
.=
"INTERNAL ERROR, low_end has no value; "
unless
defined
(
$low_end
);
$sMsg
.=
"INTERNAL ERROR, high_end has no value; "
unless
defined
(
$high_end
);
$sMsg
.=
"INTERNAL ERROR, high_end is zero; "
unless
0 <
$high_end
;
if
(
$iActual
<
$low_end
)
{
$sMsg
.=
"expected $low_end..$high_end, but got $iActual; "
;
$e
= 1;
}
if
(
$high_end
<
$iActual
)
{
$sMsg
.=
"expected $low_end..$high_end, but got $iActual; "
;
$e
= 1;
}
}
elsif
(
$test_method
==
$TEST_EXACTLY
)
{
$e
=
&diff
(
"$file.out"
,
"$file.trial"
) ? 1 : 0;
}
elsif
(
$test_method
==
$TEST_BY_COUNTING
)
{
my
$iExpected
=
shift
;
my
$iActual
=
&wc_l
(
"$file.trial"
);
if
(
$iActual
!=
$iExpected
)
{
$sMsg
.=
"expected $iExpected, but got $iActual; "
;
$e
= 1;
}
}
else
{
$e
= 0;
$sMsg
=
"INTERNAL ERROR, unknown test method $test_method; "
;
}
if
(
$e
== 0)
{
print
" ok.\n"
;
unlink
(
"$file.trial"
);
}
elsif
(
$e
== 1)
{
print
"DIFFERENCE DETECTED: $query --> $sMsg\n"
;
$self
->{error_count}++;
}
else
{
print
"INTERNAL ERROR $query --> e is $e.\n"
;
$self
->{error_count}++;
}
}
else
{
print
"NO SAVED OUTPUT, can not evaluate test results.\n"
;
$self
->{error_count}++;
}
}
sub
no_test
{
my
$self
=
shift
;
my
(
$engine
,
$maint
) =
@_
;
return
unless
(
$self
->relevant_test(
$engine
));
print
<<"NONE";
trial none ($engine)
This search engine does not have any tests,
but report problems with it to $maint.
NONE
}
sub
not_working
{
my
$self
=
shift
;
my
(
$engine
,
$maint
) =
@_
;
return
unless
(
$self
->relevant_test(
$engine
));
print
<<"BROKEN";
trial none ($engine)
This search engine is known to be non-functional.
You are encouraged to investigate the problem and email its maintainer,
$maint.
BROKEN
}
sub
not_working_with_tests
{
my
$self
=
shift
;
my
(
$engine
,
$maint
) =
@_
;
return
if
(!
$self
->relevant_test(
$engine
));
print
<<"KNOWNFAILURE";
trial none ($engine)
Test cases for this search engine are known to fail.
You are encouraged to investigate the problem and email its maintainer,
$maint.
KNOWNFAILURE
}
sub
not_working_and_abandoned
{
my
$self
=
shift
;
my
(
$engine
,
$maint
) =
@_
;
return
if
(!
$self
->relevant_test(
$engine
));
print
<<"ADOPT";
trial none ($engine)
This search engine is known to be non-functional.
You are encouraged to adopt it from its last known maintainer,
$maint.
ADOPT
}
sub
reset_error_count
{
my
$self
=
shift
;
$self
->{error_count} = 0;
}
sub
wc_l
{
open
WC,
shift
or
return
0;
$/ =
"\n"
;
my
$i
= 0;
while
(<WC>)
{
last
if
/Nothing found./;
$i
++;
}
return
$i
;
}
sub
diff
{
open
DIFF1,
shift
or
return
91;
open
DIFF2,
shift
or
return
92;
my
$iResult
= 0;
$/ =
"\n"
;
while
((
defined
(
my
$s1
= <DIFF1>)) &&
(
$iResult
ne 1))
{
my
$s2
= <DIFF2>;
unless
(
defined
(
$s2
))
{
$iResult
= 1;
last
;
}
chomp
$s1
;
chomp
$s2
;
if
(
$s1
ne
$s2
)
{
$iResult
= 1;
last
;
}
}
close
DIFF1;
close
DIFF2;
return
$iResult
;
}
sub
new_engine
{
$iTest
++;
$sEngine
=
shift
;
$oSearch
= new WWW::Search(
$sEngine
);
print
ref
(
$oSearch
) ?
''
:
'not '
;
print
"ok $iTest\n"
;
$oSearch
->env_proxy(
'yes'
);
}
sub
tm_new_engine
{
my
$sEngine
=
shift
;
$oSearch
= new WWW::Search(
$sEngine
);
Test::More::ok(
ref
(
$oSearch
),
"instantiate WWW::Search::$sEngine object"
);
$oSearch
->env_proxy(
'yes'
);
}
sub
run_test
{
return
&_run_our_test
(
'normal'
,
@_
);
}
sub
run_gui_test
{
return
&_run_our_test
(
'gui'
,
@_
);
}
sub
tm_run_test
{
_tm_run_test(
@_
, 1);
}
sub
_tm_run_test
{
my
$iApprox
=
pop
(
@_
) || 0;
my
(
$sType
,
$sQuery
,
$iMin
,
$iMax
) =
@_
;
my
$iCount
= count_results(
@_
);
my
$iAnyFailure
= 0;
$iAnyFailure
++
unless
Test::More::is(
$oSearch
->response->code, 200,
'got valid HTTP response'
);
if
(
defined
$iMin
)
{
$iAnyFailure
++
unless
Test::More::cmp_ok(
$iMin
,
'<='
,
$iCount
,
qq{lower-bound num-hits for query=$sQuery}
);
if
(
$iApprox
)
{
$iAnyFailure
++
unless
Test::More::isnt(
$oSearch
->approximate_result_count,
undef
,
qq{approximate_result_count is defined}
);
$iAnyFailure
++
unless
Test::More::cmp_ok(
$iMin
,
'<='
,
$oSearch
->approximate_result_count,
qq{lower-bound approximate_result_count}
);
}
}
if
(
defined
$iMax
)
{
$iAnyFailure
++
unless
Test::More::cmp_ok(
$iCount
,
'<='
,
$iMax
,
qq{upper-bound num-hits for query=$sQuery}
);
if
(
$iApprox
)
{
$iAnyFailure
++
unless
Test::More::isnt(
$oSearch
->approximate_result_count,
undef
,
qq{approximate_result_count is defined}
);
$iAnyFailure
++
unless
Test::More::cmp_ok(
$oSearch
->approximate_result_count,
'<='
,
$iMax
,
qq{upper-bound approximate_result_count}
);
}
}
$sSaveOnError
||=
q''
;
if
(
$iAnyFailure
&& (
$sSaveOnError
ne
q''
))
{
write_file(
$sSaveOnError
, {
err_mode
=>
'quiet'
},
$oSearch
->response->content);
Test::More::diag(
qq'HTML was saved in $sSaveOnError'
);
}
}
sub
tm_run_test_no_approx
{
_tm_run_test(
@_
, 0);
}
sub
count_results
{
my
(
$sType
,
$sQuery
,
$iMin
,
$iMax
,
$iDebug
,
$iPrintResults
,
$rh
,
$iDoNotEscape
) =
@_
;
$iDebug
||= 0;
$iPrintResults
||= 0;
$rh
->{
'search_debug'
} =
$iDebug
;
carp
' --- min/max values out of order?'
if
(
defined
(
$iMin
) &&
defined
(
$iMax
) && (
$iMax
<
$iMin
));
$oSearch
->reset_search;
$iMin
||= 0;
my
$iMaxAbs
;
if
(!
defined
(
$iMax
))
{
$iMaxAbs
=
$iMin
+ 1;
}
else
{
$iMaxAbs
=
$iMax
+ 1;
}
$oSearch
->maximum_to_retrieve(
$iMaxAbs
);
$iTest
++;
$sQuery
= WWW::Search::escape_query(
$sQuery
)
unless
$iDoNotEscape
;
if
(
$sType
eq
'gui'
)
{
$oSearch
->gui_query(
$sQuery
,
$rh
);
}
else
{
$oSearch
->native_query(
$sQuery
,
$rh
);
}
$oSearch
->login(
$ENV
{WWW_SEARCH_USERNAME},
$ENV
{WWW_SEARCH_PASSWORD});
my
@aoResults
=
$oSearch
->results();
if
(
$iPrintResults
)
{
my
$i
= 1;
foreach
my
$oResult
(
@aoResults
)
{
print
$i
++,
'. '
,
$oResult
->url,
"\n"
;
foreach
my
$sField
(
qw( title description score change_date index_date size company location source )
)
{
print
" $sField=="
,
$oResult
->
$sField
,
"==\n"
if
defined
(
$oResult
->
$sField
);
}
}
}
return
scalar
(
@aoResults
);
}
sub
_run_our_test
{
my
(
$sType
,
$sQuery
,
$iMin
,
$iMax
,
$iDebug
,
$iPrintResults
) =
@_
;
my
$iResults
=
&count_results
(
@_
);
my
$sExpect
;
if
(!
defined
(
$iMax
))
{
$sExpect
=
"more than $iMin"
;
}
elsif
(!
defined
(
$iMin
))
{
$sExpect
=
"fewer than $iMax"
;
}
else
{
$sExpect
=
"$iMin..$iMax"
;
}
$iMax
= 999999
unless
defined
(
$iMax
);
if
((
$iResults
<
$iMin
) || (
$iMax
<
$iResults
))
{
print
STDERR
" --- got $iResults results for $sType $sEngine query '$sQuery', but expected $sExpect\n"
;
print
STDOUT
'not '
;
}
print
STDOUT
"ok $iTest\n"
;
}
sub
skip_test
{
$iTest
++;
print
STDOUT
"skip $iTest\n"
;
}
sub
test_most_results
{
my
$rara
=
shift
;
my
$fPct
=
shift
|| 0.80;
my
$iCount
=
scalar
(
@$rara
);
my
$iAnyFailed
=
my
$iResult
= 0;
my
%hioExemplar
;
my
%hiiFailed
;
my
$oV
= new Bit::Vector(
$iCount
);
$oV
->Fill;
my
$iVall
=
$oV
->to_Dec;
my
$sCodeAll
=
q{}
;
my
$iTest
= 0;
TEST:
foreach
my
$ra
(
@$rara
)
{
my
(
$sField
,
$sCmp
,
$sValue
,
$sDesc
) =
@$ra
;
$sDesc
||=
qq{test #$iTest}
;
my
$sCode
;
if
(
$sCmp
eq
'like'
)
{
$sCode
=
"(\$oResult->$sField =~ m!$sValue!)"
;
}
elsif
(
$sCmp
eq
'unlike'
)
{
$sCode
=
"(\$oResult->$sField !~ m!$sValue!)"
;
}
elsif
(
$sCmp
eq
'date'
)
{
$sCode
=
"((ParseDate(\$oResult->$sField) || '') ne q{})"
;
}
else
{
$sCode
=
"(\$oResult->$sField $sCmp $sValue)"
;
}
$sCode
=
<<"ENDCODE";
if (! $sCode)
{
\$oV->Bit_Off($iTest);
\$hiiFailed{'$sDesc'}++;
} # if
ENDCODE
$sCodeAll
.=
$sCode
;
$iTest
++;
}
$sCodeAll
.=
"1;\n"
;
RESULT:
foreach
my
$oResult
(
$oSearch
->results())
{
$iResult
++;
$oV
->Fill;
if
(!
eval
$sCodeAll
)
{
print
STDERR $@;
}
my
$iV
=
$oV
->to_Dec;
if
(
$iV
<
$iVall
)
{
$hioExemplar
{
$iV
} =
$oResult
;
$iAnyFailed
++;
}
}
ok(
$iResult
,
qq{got more than zero results ($iResult, to be exact)}
);
while
(
my
(
$sItem
,
$iFailed
) =
each
%hiiFailed
)
{
my
$fPctFailed
= (
$iFailed
/
$iResult
);
ok(
$fPctFailed
< (1 -
$fPct
),
sprintf
(
qq{%0.1f%% of '%s' tests failed}
,
$fPctFailed
* 100,
$sItem
));
}
if
(
$iAnyFailed
)
{
Test::More::diag(
" Here are result(s) that exemplify test failure(s):"
);
foreach
my
$oResult
(
values
%hioExemplar
)
{
Test::More::diag(Dumper(
$oResult
));
}
}
}
1;