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 )