XML-Parser
view release on metacpan or search on metacpan
t/expat_xs_coverage.t view on Meta::CPAN
use strict;
use warnings;
use Test::More;
use XML::Parser;
use XML::Parser::Expat;
# Tests targeting specific uncovered code paths in Expat.xs identified by gcov.
# Baseline coverage: 93.19% â these tests exercise the remaining reachable gaps.
plan tests => 26;
# ===== skip_until with Char handler (suspend L1237 / resume L1278) =====
# skip_until() calls suspend_callbacks() then resume_callbacks() after the
# target index is reached. The Char handler branches in these functions are
# only hit when a Char handler is registered during skip_until.
{
my @chars;
my $xml = '<r><a>text1</a><b>text2</b><c>text3</c></r>';
my $p = XML::Parser->new(
Handlers => {
Start => sub {
my ($xp, $el) = @_;
if ($el eq 'a') {
# Skip from 'a' to beyond 'b' â element_index of 'c' should be 4
$xp->skip_until(4);
}
},
Char => sub { push @chars, $_[1] },
},
);
$p->parse($xml);
# 'text1' and 'text2' should be suppressed by skip_until,
# 'text3' should appear after resume
my $text = join('', @chars);
like($text, qr/text3/, 'skip_until + Char: text after resume point is delivered');
unlike($text, qr/text2/, 'skip_until + Char: text during skip is suppressed');
}
# ===== skip_until with CdataSection handlers (suspend L1253 / resume L1291) =====
{
my @cdata_starts;
my $xml = '<r><a/><b><![CDATA[skipped]]></b><c><![CDATA[seen]]></c></r>';
my $p = XML::Parser->new(
Handlers => {
Start => sub {
my ($xp, $el) = @_;
if ($el eq 'a') {
$xp->skip_until(4); # Skip past 'b'
}
},
CdataStart => sub { push @cdata_starts, 1 },
Char => sub { }, # suppress output
},
);
$p->parse($xml);
# The CDATA in <b> should be skipped, the one in <c> should fire
is(scalar @cdata_starts, 1, 'skip_until + CdataStart: only post-skip CDATA fires');
}
# ===== skip_until with Unparsed and Notation handlers (suspend L1259,1264 / resume L1295,1299) =====
# DTD events fire before document body, so skip_until from Init skips them.
{
my @notation_names;
my @unparsed_names;
my $xml = <<'XML';
<!DOCTYPE doc [
<!NOTATION gif SYSTEM "image/gif">
<!ENTITY pic SYSTEM "pic.gif" NDATA gif>
]>
<doc><a/><b/></doc>
XML
my $p = XML::Parser->new(
Handlers => {
Init => sub { $_[0]->skip_until(1) },
Notation => sub { push @notation_names, $_[1] },
Unparsed => sub { push @unparsed_names, $_[1] },
Start => sub { },
},
);
$p->parse($xml);
# DTD declarations happen before any elements, so skip_until(1) should
# skip them. After resume, Notation/Unparsed are restored for future events.
# The key coverage goal: suspend_callbacks and resume_callbacks exercise
# the Notation and Unparsed branches simply by being registered.
pass('skip_until + Notation/Unparsed: suspend/resume completed without crash');
}
# ===== skip_until with ExternEnt handler (suspend L1268 / resume L1303) =====
{
my @ext_calls;
my $xml = <<'XML';
<!DOCTYPE doc [
<!ENTITY ext SYSTEM "ext.xml">
]>
<doc><a/><b>&ext;</b><c/></doc>
XML
my $p = XML::Parser->new(
Handlers => {
Start => sub {
my ($xp, $el) = @_;
if ($el eq 'a') {
$xp->skip_until(4); # Skip past 'b' and its entity ref
}
},
ExternEnt => sub {
push @ext_calls, 1;
return '';
},
},
);
$p->parse($xml);
# Entity in <b> should be skipped
is(scalar @ext_calls, 0, 'skip_until + ExternEnt: entity ref during skip is suppressed');
}
( run in 1.400 second using v1.01-cache-2.11-cpan-e1769b4cff6 )