#!/usr/bin/perl
BEGIN
{
use
open
':std'
=>
':utf8'
;
use_ok(
'Module::Generic::Scalar'
) || BAIL_OUT(
"Unable to load Module::Generic::Scalar"
);
our
$DEBUG
=
exists
(
$ENV
{AUTHOR_TESTING} ) ?
$ENV
{AUTHOR_TESTING} : 0;
};
my
$str
=
"Hello world"
;
my
$s
= Module::Generic::Scalar->new(
$str
) || BAIL_OUT(
"Unable to instantiate an object."
);
isa_ok(
$s
,
'Module::Generic::Scalar'
,
'Scalar object'
);
is(
"$s"
,
$str
,
'Stringification'
);
my
$s2
=
$s
->clone;
isa_ok(
$s2
,
'Module::Generic::Scalar'
,
'Scalar object'
);
is(
"$s2"
,
$str
,
'Cloning'
);
$s
.=
"\n"
;
isa_ok(
$s
,
'Module::Generic::Scalar'
,
'Object after concatenation'
);
is(
$s
,
"$str\n"
,
'Checking updated string object'
);
my
$a1
=
$s
->clone(
"Prefix; "
);
$a1
.=
$s
;
my
$s3
= Module::Generic::Scalar->new(
'A'
);
my
$res
=
$s3
x 12;
is(
$res
,
'AAAAAAAAAAAA'
,
'Multiplying string'
);
isa_ok(
$res
,
'Module::Generic::Scalar'
,
'Multiplied string class object'
);
$res
->replace(
qr/A{2}$/
,
''
);
isa_ok( Module::Generic::Scalar->new(
'true'
)->as_boolean,
'Module::Generic::Boolean'
,
'Scalar to boolean'
);
my
$bool_1
= Module::Generic::Scalar->new(
'true'
)->as_boolean;
ok(
$bool_1
== 1,
'Scalar value to true boolean'
);
ok( !Module::Generic::Scalar->new( 0 )->as_boolean,
'Scalar value to false boolean'
);
$s
->
chomp
;
is(
$s
,
'Hello world'
,
'chomp'
);
$s
->
chop
;
is(
$s
,
'Hello worl'
,
'chop'
);
SKIP:
{
if
( $^O eq
'openbsd'
||
$Config
{libs} !~ /\b\-lcrypt\b/ )
{
skip(
"crypt unsupported on $^O"
, 1 );
}
is(
$s
->
crypt
(
'key'
),
'keqUNAuo7.kCQ'
,
'crypt'
);
};
is(
$s
->fc(
'Hello worl'
), 1,
'fc'
);
is( Module::Generic::Scalar->new(
'0xAf'
)->
hex
, 175,
'hex'
);
isa_ok( Module::Generic::Scalar->new(
'0xAf'
)->
hex
,
'Module::Generic::Number'
);
is(
$s
->
index
(
'wo'
), 6,
'index'
);
is(
$s
->
index
(
'world'
), -1,
'index not found'
);
ok( !
$s
->is_alpha,
'Is alpha'
);
ok( Module::Generic::Scalar->new(
'Hello'
)->is_alpha,
'Is alpha ok'
);
ok( Module::Generic::Scalar->new(
'Front242'
)->is_alpha_numeric,
'Is alpha numeric'
);
ok( !
$s
->is_empty,
'Is empty'
);
my
$empty
= Module::Generic::Scalar->new(
'Hello'
)->
undef
;
isa_ok(
$empty
,
'Module::Generic::Scalar'
);
ok( !
$empty
->
defined
,
'Is undefined'
);
ok( !
$s
->is_lower,
'Is lower (false)'
);
ok(
lc
(
$s
),
'Is lower (true)'
);
ok( !Module::Generic::Scalar->new(
'Front242'
)->is_numeric,
'Looks like a number'
);
ok( Module::Generic::Scalar->new(
'Hello'
)->
uc
->is_upper,
'Is all caps'
);
is( Module::Generic::Scalar->new(
'Hello'
)->
lc
,
'hello'
,
'Small caps'
);
is( Module::Generic::Scalar->new(
'HELLO'
)->
lcfirst
,
'hELLO'
,
'lcfirst'
);
is( Module::Generic::Scalar->new(
'Hello'
)->left( 2 ),
'He'
,
'left'
);
is(
$s
->
length
, 10,
'length'
);
is( Module::Generic::Scalar->new(
' Hello '
)->trim,
'Hello'
,
'trim'
);
is( Module::Generic::Scalar->new(
' Hello '
)->ltrim,
'Hello '
,
'ltrim'
);
ok(
$s
->match(
qr/[[:blank:]]+worl/
),
'Regexp match'
);
is( Module::Generic::Scalar->new(
'J'
)->
ord
, 74,
'ord'
);
$s
->trim;
is(
$s
->pad( 3,
'x'
),
'xxxHello worl'
,
'pad at start'
);
is(
$s
->pad( -3,
'z'
),
'xxxHello worlzzz'
,
'pad at end'
);
$s
->replace(
'xxx'
,
''
);
is(
$s
,
'Hello worlzzz'
,
'Replace'
);
my
$rv
=
$s
->replace(
qr/(z{3})/
,
''
);
is(
$s
,
'Hello worl'
,
'Replace2'
);
isa_ok(
$rv
,
'Module::Generic::RegexpCapture'
,
'replace returns a Module::Generic::RegexpCapture object'
);
is(
"$rv"
, 1,
'replaced 1 occurrence'
);
diag(
"Capture contains: '"
,
$rv
->capture->
join
(
"', '"
),
"'."
)
if
(
$DEBUG
);
is(
$rv
->capture->first,
'zzz'
,
'get capture value No 1'
);
my
$test_str
= Module::Generic::Scalar->new(
'I am John'
);
my
$re_false
;
if
(
$re_false
=
$test_str
->replace(
qr/(Jean)/
,
'Paul'
) )
{
fail(
"replace produced false positive. Result object is '$re_false'"
);
}
else
{
pass(
"replace with no match returned false"
);
}
if
( !
$test_str
->replace(
qr/(Jean)/
,
'Paul'
)->matched )
{
pass(
"replace return result object in object context"
);
}
else
{
fail(
"replace failed to return object in object context"
);
}
my
$test_named
= Module::Generic::Scalar->new(
q{GET /some/where HTTP/1.1}
);
diag(
"Testing named regexp: "
,
$test_named
=~ /^(?<method>\w+)[[:blank:]\h]+(?<uri>\S+)[[:blank:]\h]+(?<proto>HTTP\/\d+\.\d+)/ ?
'ok'
:
'nope'
)
if
(
$DEBUG
);
my
$re_named
;
if
(
$re_named
=
$test_named
->match(
qr/^(?<method>\w+)[[:blank:]\h]+(?<uri>\S+)[[:blank:]\h]+(?<proto>HTTP\/
\d+\.\d+)/ ) )
{
diag(
"method is '"
,
$re_named
->name->method,
"', uri is '"
,
$re_named
->name->uri,
"' and proto is '"
,
$re_named
->name->proto,
"'"
)
if
(
$DEBUG
);
ok(
$re_named
->name->method eq
'GET'
&&
$re_named
->name->uri eq
'/some/where'
&&
$re_named
->name->proto eq
'HTTP/1.1'
,
'named capture'
);
}
else
{
diag(
"Named regular expression failed. Object is '$re_named' ("
, overload::StrVal(
$re_named
),
")"
)
if
(
$DEBUG
);
diag(
"method is '"
,
$re_named
->name->method,
"', uri is '"
,
$re_named
->name->uri,
"' and proto is '"
,
$re_named
->name->proto,
"'"
)
if
(
$DEBUG
);
fail(
'named capture'
);
}
is(
$s
->
quotemeta
,
'Hello\ worl'
,
'quotemeta'
);
is(
$s
->
reset
->
length
, 0,
'reset'
);
$s
.=
'I disapprove of what you say, but I will defend to the death your right to say it'
;
isa_ok(
$s
,
'Module::Generic::Scalar'
,
'Scalar assignment'
);
is(
$s
->clone->capitalise,
'I Disapprove of What You Say, but I Will Defend to the Death Your Right to Say It'
,
'Capitalise'
);
is( Module::Generic::Scalar->new(
'Hello'
)->
reverse
,
'olleH'
,
'reverse'
);
is(
$s
->
rindex
(
'I'
), 34,
'rindex'
);
is(
$s
->
rindex
(
'I'
, 40 ), 34,
'rindex with position'
);
is( Module::Generic::Scalar->new(
'Hello world%%%%'
)->rtrim(
'%'
),
'Hello world'
,
'rtrim'
);
is(
$s
->clone->set(
'Bonjour'
),
'Bonjour'
,
'set'
);
isa_ok(
$s
->
split
(
qr/[[:blank:]]+/
),
'Module::Generic::Array'
,
'split -> array'
);
is( Module::Generic::Scalar->new(
'Hello Ms %s.'
)->
sprintf
(
'Jones'
),
'Hello Ms Jones.'
,
'sprintf'
);
is(
$s
->
substr
( 2, 13 ),
'disapprove of'
,
'substr'
);
is(
$s
->
substr
( 2, 13,
'really do not approve'
),
'disapprove of'
,
'substr substituted part'
);
is(
$s
,
'I really do not approve what you say, but I will defend to the death your right to say it'
,
'substr -> substitution'
);
my
$sz
= Module::Generic::Scalar->new(
"I am not so sure"
);
is(
$sz
->
tr
(
'[a-j]'
,
'[0-9]'
),
'I 0m not so sur4'
,
'tr'
);
ok(
$s
->like(
qr/\bapprove[[:blank:]\h]+what\b/
),
'like'
);
my
$undef
= Module::Generic::Scalar->new(
undef
() );
ok(
defined
(
$undef
),
'Undefined variable object -> defined'
);
no
warnings
'uninitialized'
;
is(
$undef
->
scalar
,
undef
(),
'Undefined variable object using stringification -> undefined'
);
ok( !
$undef
->
defined
,
'Object value is undefined using method -> undefined'
);
my
$var
=
'test'
;
$var
=
$s
;
isa_ok(
$var
,
'Module::Generic::Scalar'
,
'Regular var assigned becomes object'
);
my
$var2
=
"Je n'approuve rien"
;
$s
=
$var2
;
ok( !
ref
(
$s
),
'Object lose class after assignment'
);
my
$obj
= MyObject->new({
name
=>
'Dave'
,
type
=>
undef
() });
isa_ok(
$obj
->name,
'Module::Generic::Scalar'
,
'object field is a Module::Generic::Scalar object'
);
is(
$obj
->type,
undef
(),
'Test object type property is undef()'
);
is(
$obj
->name->
uc
,
'DAVE'
,
'Object chain method ok'
);
is(
$obj
->type->
length
,
undef
(),
'Chained, but eventually undef'
);
is(
$obj
->name,
'Dave'
,
'Overloaded scalar object in scalar context'
);
my
$s4
= Module::Generic::Scalar->new(
'10'
);
isa_ok(
$s4
->as_number,
'Module::Generic::Number'
,
'as_number'
);
ok(
$s4
->as_number == 10,
'number value'
);
my
$s5
= Module::Generic::Scalar->new(
'+10'
);
isa_ok(
$s5
->as_number,
'Module::Generic::Number'
,
'as_number (2)'
);
ok(
$s5
->as_number == 10,
'number value (2)'
);
my
$s6
= Module::Generic::Scalar->new(
'world'
);
$s6
->prepend(
'Hello '
);
is(
"$s6"
,
'Hello world'
,
'prepend'
);
my
$a6
=
$s6
->as_array;
isa_ok(
$a6
,
'Module::Generic::Array'
,
'as_array => Module::Generic::Array'
);
is(
$a6
->[0],
'Hello world'
,
'as_array'
);
my
$s7
= Module::Generic::Scalar->new(
'Jack John Paul Peter'
);
my
$j
= JSON->new->convert_blessed;
eval
{
my
$json
=
$j
->encode(
$s7
);
is(
$json
,
'"Jack John Paul Peter"'
,
'TO_JSON'
);
};
if
( $@ )
{
fail(
'TO_JSON'
);
}
my
$res8
=
$s7
->
split
(
qr/[[:blank:]]+/
)->
join
(
', '
)->
join
(
', '
,
qw( Gabriel Raphael Emmanuel )
);
is(
"$res8"
,
'Jack, John, Paul, Peter, Gabriel, Raphael, Emmanuel'
,
'join'
);
my
$s8
= Module::Generic::Scalar->new(
'Hello'
);
my
$s9
= Module::Generic::Scalar->new(
'world'
);
is(
$s8
->
join
(
' '
,
$s9
),
'Hello world'
,
'join (2)'
);
subtest
'scalar io'
=>
sub
{
my
$text
=
<<EOT;
Mignonne, allons voir si la rose
Qui ce matin avoit desclose
Sa robe de pourpre au Soleil,
A point perdu cette vesprée
Les plis de sa robe pourprée,
Et son teint au vostre pareil.
EOT
my
$s
= Module::Generic::Scalar->new;
my
$io
=
$s
->
open
( {
debug
=>
$DEBUG
,
fatal
=> 0 } ) ||
die
(
$s
->error );
isa_ok(
$io
,
'Module::Generic::Scalar::IO'
,
'open'
);
diag(
"File handle is: '$io'"
)
if
(
$DEBUG
);
ok(
$io
->opened,
'opened'
);
is(
$io
->
fileno
, -1,
'fileno'
);
ok(
$io
->flush,
'flush'
);
my
$rv
=
$io
->
print
(
$text
);
diag(
"Error printing to scalar: "
,
$io
->error )
if
(
$DEBUG
&& !
defined
(
$rv
) );
is(
"$s"
,
$text
,
'print'
);
$io
->
printf
(
"Author: %s\n"
,
'Pierre de Ronsard'
);
is(
$io
->
getc
,
undef
(),
'getc'
);
ok(
$io
->
eof
,
'eof'
);
$text
.=
sprintf
(
"Author: %s\n"
,
'Pierre de Ronsard'
);
is(
$io
->
tell
,
length
(
$text
),
'tell -> end of text'
);
ok(
$io
->
seek
(0,0),
'seek'
);
is(
$io
->
tell
, 0,
'tell -> start of text'
);
is(
$io
->
getc
,
'M'
,
'getc'
);
is(
$io
->getline,
"ignonne, allons voir si la rose\n"
,
'getline'
);
my
$buff
;
my
$n
=
$io
->
read
(
$buff
,
length
( [
split
(/\n/,
$text
)]->[1] ) + 1 );
is(
$buff
, [
split
(/\n/,
$text
)]->[1] .
"\n"
,
'read buffer check'
);
my
@lines
=
$io
->getlines;
is(
join
(
''
,
@lines
),
join
(
"\n"
, (
split
(/\n/,
$text
, -1))[2..7] ),
'getlines'
);
$io
->
seek
(
$io
->
length
- 1, 0 );
my
$pos
=
$io
->
tell
;
$n
=
$io
->
write
(
', Les Odes'
, 10 );
is(
$n
, 10,
'write'
);
substr
(
$text
, -1, 0,
', Les Odes'
);
$io
->
seek
(0,0);
@lines
=
$io
->getlines;
is(
$lines
[-1],
"Author: Pierre de Ronsard, Les Odes"
,
'write resulting value'
);
$io
->
seek
(
$io
->
length
-
length
(
$lines
[-1] ), 0 );
my
$len
=
$io
->
truncate
(
$io
->
tell
);
diag(
"Error trying to truncate: "
,
$io
->error )
if
(
$DEBUG
&& !
defined
(
$len
) );
is(
$len
,
length
(
$lines
[-1] ),
'truncate returned length'
);
$io
->
seek
(0,0);
@lines
=
$io
->getlines;
is(
scalar
(
@lines
), 6,
'truncate'
);
diag(
"String now is:\n$io"
)
if
(
$DEBUG
);
ok(
$io
->
close
,
'close'
);
ok( !
tied
(
$io
),
'untied'
);
ok( !
$io
->opened,
'opened'
);
my
$s2
= Module::Generic::Scalar->new( \
$text
);
$io
=
$s2
->
open
(
'<'
);
isa_ok(
$io
=>
'Module::Generic::Scalar::IO'
);
$rv
=
$io
->
print
(
"print should not work\n"
);
ok( !
$rv
,
'cannot print in read-only mode'
);
$rv
=
$io
->
write
(
"write should not work either\n"
);
ok( !
$rv
,
'cannot write in read-only mode'
);
$rv
=
$io
->
syswrite
(
"syswrite should not work either\n"
);
ok( !
$rv
,
'cannot syswrite in read-only mode'
);
SKIP:
{
eval
{
skip(
"Fcntl constants not loaded."
, 1 )
if
( !
defined
(
&F_GETFL
) || !
defined
(
&F_SETFL
) );
diag(
"F_GETFL is '"
, F_GETFL,
"' and F_SETFL is '"
, F_SETFL,
"'"
)
if
(
$DEBUG
);
my
$bit
=
$io
->
fcntl
( F_GETFL, 0 );
diag(
"Bit value returned is '$bit' and O_RDONLY is '"
, O_RDONLY,
"'"
)
if
(
$DEBUG
);
if
( !
defined
(
$bit
) )
{
diag(
"Error getting bitwise value: "
,
$io
->error )
if
(
$DEBUG
);
skip(
'failed getting bitwise value'
, 1 );
}
elsif
(
$bit
!~ /^\d+$/ )
{
diag(
"Bit value returned is not an integer -> '$bit'"
)
if
(
$DEBUG
);
}
ok( ( (
$bit
> 0 &&
$bit
& O_RDONLY ) ||
$bit
== O_RDONLY ),
'scalar io has read-only bit'
);
ok( !(
$bit
& O_RDWR ),
'scalar io does not have write bit'
);
};
if
( $@ )
{
skip(
"Fcntl is not available on $^O"
, 1 );
}
};
};
subtest
'unpack and pack'
=>
sub
{
my
$unpack_data
= Module::Generic::Scalar->new(
q{2021/09/19 Camel rides to tourists €235.00}
);
my
(
$date
,
$desc
,
$income
,
$expense
) =
$unpack_data
->
unpack
(
"A10xA28xA8A*"
);
is(
$date
,
'2021/09/19'
,
'unpack -> date'
);
is(
$desc
,
'Camel rides to tourists'
,
'unpack -> description'
);
is(
$income
,
'€235.00'
,
'unpack -> income'
);
is(
$expense
,
''
,
'unpack -> expense'
);
my
$unpack
=
$unpack_data
->
unpack
(
"A10xA28xA8A*"
)->object;
isa_ok(
$unpack
,
'Module::Generic::Array'
,
'unpack returns Module::Generic::Array in scalar context'
);
is(
$unpack
->
length
, 4,
'has 4 elements'
);
is(
$unpack
->first,
'2021/09/19'
,
'unpack -> date'
);
is(
$unpack
->second,
'Camel rides to tourists'
,
'unpack -> description'
);
is(
$unpack
->third,
'€235.00'
,
'unpack -> income'
);
is(
$unpack
->fourth,
''
,
'unpack -> expense'
);
is(
$unpack_data
->
unpack
(
"A10xA28xA8A*"
)->third,
'€235.00'
,
'object context'
);
my
$str2pack
= Module::Generic::Scalar->new( 0x20AC );
my
$pack_data
=
$str2pack
->
pack
(
'U'
);
is(
$pack_data
,
'€'
,
'pack'
);
};
subtest
'callback'
=>
sub
{
$Module::Generic::Scalar::DEBUG
=
$DEBUG
;
diag(
"Setting \$Module::Generic::Scalar::DEBUG to '$Module::Generic::Scalar::DEBUG'"
)
if
(
$DEBUG
);
my
$test
= Module::Generic::Scalar->new(
q{Allons enfants de la Patrie !}
);
is(
$test
->
length
, 29,
'init'
);
ok( !
tied
(
$$test
),
'not tied'
);
$test
->callback(
add
=>
sub
{
my
$this
=
shift
(
@_
);
my
$new
=
$this
->{added};
diag(
"Adding "
,
length
(
$$new
),
" bytes of data ('$$new')"
)
if
(
$DEBUG
);
is(
length
(
$$new
), 59,
'append'
);
return
(1);
});
$test
->append(
"\nLe jour de gloire est arrivé."
);
diag(
"String is: '"
,
$test
->
scalar
,
"'"
)
if
(
$DEBUG
);
is(
$test
->
substr
( -7, 6 ),
'arrivé'
,
'append (2)'
);
$test
->callback(
add
=>
sub
{
my
$this
=
shift
(
@_
);
my
$new
=
$this
->{added};
diag(
"Adding "
,
length
(
$$new
),
" bytes of data ('$$new')"
)
if
(
$DEBUG
);
is(
length
(
$$new
), 62,
'substr'
);
return
(1);
});
$test
->
substr
( 31, 6,
'a journée'
);
is(
$test
->
scalar
,
"Allons enfants de la Patrie !\nLa journée de gloire est arrivé."
,
'substr (2)'
);
my
$copy
=
$$test
;
diag(
"Blocking modification."
)
if
(
$DEBUG
);
my
$try
= 0;
$test
->callback(
add
=>
sub
{
my
$this
=
shift
(
@_
);
my
$new
=
$this
->{added};
diag(
"Attempting to add "
,
length
(
$$new
),
" bytes of data ('$$new') "
)
if
(
$DEBUG
);
$try
++;
return
;
});
$test
->append(
"Contre nous de la tyrannie,\nL’étendard sanglant est levé !\n"
);
is(
$try
, 1,
'addition rejected'
);
is(
$$test
,
$copy
,
'addition rejected'
);
$try
= 0;
$test
->callback(
remove
=>
sub
{
my
$this
=
shift
(
@_
);
my
(
$old
,
$new
) =
@$this
{
qw( removed added )
};
diag(
"Attempting to remove "
,
length
(
$$old
),
" bytes of data ('$$old')"
)
if
(
$DEBUG
);
$try
++;
return
;
});
$test
->
reset
;
is(
$try
, 1,
'removal rejected'
);
is(
$$test
,
$copy
,
'removal rejected'
);
$test
->callback(
remove
=>
sub
{
my
$this
=
shift
(
@_
);
my
(
$old
,
$new
) =
@$this
{
qw( removed added )
};
diag(
"Removing data from "
,
length
(
$$old
),
" bytes to "
,
length
(
$$new
),
" bytes: '"
,
$$old
,
"' -> '"
,
$$new
,
"'"
)
if
(
$DEBUG
);
is(
length
(
$$old
), 62,
'undef'
);
is(
length
(
$$new
), 0,
'undef (1)'
);
return
(1);
});
$test
->
reset
;
diag(
"Removing callbacks"
)
if
(
$DEBUG
);
$test
->callback(
add
=>
undef
);
$test
->callback(
remove
=>
undef
);
ok( !
tied
(
$$test
),
'callbacks removed'
);
};
done_testing();
package
MyObject;
BEGIN
{
};
sub
new
{
my
$this
=
shift
(
@_
);
my
$hash
= {};
$hash
=
shift
(
@_
);
return
(
bless
(
$hash
=> (
ref
(
$this
) ||
$this
) ) );
}
sub
name {
return
(
shift
->_set_get_scalar_as_object(
'name'
,
@_
) ); }
sub
type {
return
(
shift
->_set_get_scalar_as_object(
'type'
,
@_
) ); }
sub
AUTOLOAD
{
my
(
$method
) =
our
$AUTOLOAD
=~ /([^:]+)$/;
my
$self
=
shift
(
@_
) ||
return
;
return
(
$self
->{
$method
} );
}