blob: ad0938f812b835c6ba5e01d4fb3df68c2b4fed27 [file] [log] [blame]
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
use strict;
use warnings;
use Test::More tests => 52;
use Clownfish::Class;
use Clownfish::Parser;
my $parser = Clownfish::Parser->new;
my $thing = Clownfish::Variable->new(
parcel => 'Neato',
class_name => 'Foo',
type => $parser->type('Thing*'),
micro_sym => 'thing',
);
my $widget = Clownfish::Variable->new(
class_name => 'Widget',
type => $parser->type('Widget*'),
micro_sym => 'widget',
);
my $tread_water = Clownfish::Function->new(
parcel => 'Neato',
class_name => 'Foo',
return_type => $parser->type('void'),
micro_sym => 'tread_water',
param_list => $parser->param_list('()'),
);
my %foo_create_args = (
parcel => 'Neato',
class_name => 'Foo',
);
my $foo = Clownfish::Class->create(%foo_create_args);
$foo->add_function($tread_water);
$foo->add_member_var($thing);
$foo->add_inert_var($widget);
eval { Clownfish::Class->create(%foo_create_args) };
like( $@, qr/conflict/i,
"Can't call create for the same class more than once" );
my $should_be_foo = Clownfish::Class->fetch_singleton(
parcel => 'Neato',
class_name => 'Foo',
);
is( $foo, $should_be_foo, "fetch_singleton" );
my $foo_jr = Clownfish::Class->create(
parcel => 'Neato',
class_name => 'Foo::FooJr',
parent_class_name => 'Foo',
);
$foo_jr->add_attribute( dumpable => 1 );
ok( $foo_jr->has_attribute('dumpable'), 'has_attribute' );
is( $foo_jr->get_struct_sym, 'FooJr', "struct_sym" );
is( $foo_jr->full_struct_sym, 'neato_FooJr', "full_struct_sym" );
my $final_foo = Clownfish::Class->create(
parcel => 'Neato',
class_name => 'Foo::FooJr::FinalFoo',
parent_class_name => 'Foo::FooJr',
source_class => 'Foo::FooJr',
final => 1,
);
$final_foo->add_attribute( dumpable => 1 );
ok( $final_foo->final, "final" );
is( $final_foo->include_h, 'Foo/FooJr.h', "inlude_h uses source_class" );
is( $final_foo->get_parent_class_name, 'Foo::FooJr',
"get_parent_class_name" );
my $do_stuff
= $parser->subroutine_declaration_statement( 'void Do_Stuff(Foo *self);',
0, class => 'Foo' )->{declared}
or die "parsing failure";
$foo->add_method($do_stuff);
my $inert_do_stuff
= $parser->subroutine_declaration_statement(
'void Do_Stuff(InertFoo *self);',
0, class => 'InertFoo' )->{declared}
or die "parsing failure";
my %inert_args = (
parcel => 'Neato',
class_name => 'InertFoo',
inert => 1,
);
eval {
my $class = Clownfish::Class->create(%inert_args);
$class->add_method($inert_do_stuff);
};
like(
$@,
qr/inert class/i,
"Error out on conflict between inert attribute and object method"
);
$foo->add_child($foo_jr);
$foo_jr->add_child($final_foo);
$foo->grow_tree;
eval { $foo->grow_tree };
like( $@, qr/grow_tree/, "call grow_tree only once." );
eval { $foo_jr->add_method($do_stuff) };
like( $@, qr/grow_tree/, "Forbid add_method after grow_tree." );
is( $foo_jr->get_parent, $foo, "grow_tree, one level" );
is( $final_foo->get_parent, $foo_jr, "grow_tree, two levels" );
is( $foo->novel_method("Do_Stuff"), $do_stuff, 'novel_method' );
is( $foo_jr->method("Do_Stuff"), $do_stuff, "inherited method" );
ok( !$foo_jr->novel_method("Do_Stuff"), 'inherited method not novel' );
ok( $final_foo->method("Do_Stuff")->final, "Finalize inherited method" );
ok( !$foo_jr->method("Do_Stuff")->final, "Don't finalize method in parent" );
is_deeply( $foo->inert_vars, [$widget], "inert vars" );
is_deeply( $foo->functions, [$tread_water], "inert funcs" );
is_deeply( $foo->methods, [$do_stuff], "methods" );
is_deeply( $foo->novel_methods, [$do_stuff], "novel_methods" );
is_deeply( $foo->novel_member_vars, [$thing], "novel_member_vars" );
is_deeply( $foo_jr->member_vars, [$thing], "inherit member vars" );
is_deeply( $foo_jr->functions, [], "don't inherit inert funcs" );
is_deeply( $foo_jr->novel_member_vars, [], "novel_member_vars" );
is_deeply( $foo_jr->inert_vars, [], "don't inherit inert vars" );
is_deeply( $final_foo->novel_methods, [], "novel_methods" );
like( $foo_jr->get_autocode, qr/load/i, "autogenerate Dump/Load" );
is_deeply( $foo->tree_to_ladder, [ $foo, $foo_jr, $final_foo ],
'tree_to_ladder' );
ok( $parser->class_modifier($_), "class_modifier: $_" )
for (qw( abstract inert ));
ok( $parser->class_inheritance($_), "class_inheritance: $_" )
for ( 'inherits Foo', 'inherits Foo::FooJr::FooIII' );
my $class_content
= 'public class Foo::FooJr cnick FooJr inherits Foo { private int num; }';
my $class = $parser->class_declaration($class_content);
isa_ok( $class, "Clownfish::Class", "class_declaration FooJr" );
ok( ( scalar grep { $_->micro_sym eq 'num' } @{ $class->member_vars } ),
"parsed private member var" );
$class_content = q|
/**
* Bow wow.
*
* Wow wow wow.
*/
public class Animal::Dog inherits Animal : lovable : drooly {
public inert Dog* init(Dog *self, CharBuf *name, CharBuf *fave_food);
inert uint32_t count();
inert uint64_t num_dogs;
private CharBuf *name;
private bool_t likes_to_go_fetch;
private void Chase_Tail(Dog *self);
ChewToy *squishy;
void Destroy(Dog *self);
public CharBuf* Bark(Dog *self);
public void Eat(Dog *self);
public void Bite(Dog *self, Enemy *enemy);
public Thing *Fetch(Dog *self, Thing *thing);
public final void Bury(Dog *self, Bone *bone);
public Owner *mom;
public abstract incremented nullable Thing*
Scratch(Dog *self);
int32_t[1] flexible_array_at_end_of_struct;
}
|;
$class = $parser->class_declaration($class_content);
isa_ok( $class, "Clownfish::Class", "class_declaration Dog" );
ok( ( scalar grep { $_->micro_sym eq 'num_dogs' } @{ $class->inert_vars } ),
"parsed inert var" );
ok( ( scalar grep { $_->micro_sym eq 'mom' } @{ $class->member_vars } ),
"parsed public member var" );
ok( ( scalar grep { $_->micro_sym eq 'squishy' } @{ $class->member_vars } ),
"parsed parcel member var" );
ok( ( scalar grep { $_->micro_sym eq 'init' } @{ $class->functions } ),
"parsed function" );
ok( ( scalar grep { $_->micro_sym eq 'chase_tail' } @{ $class->methods } ),
"parsed private method" );
ok( ( scalar grep { $_->micro_sym eq 'destroy' } @{ $class->methods } ),
"parsed parcel method" );
ok( ( scalar grep { $_->micro_sym eq 'bury' } @{ $class->methods } ),
"parsed public method" );
ok( ( scalar grep { $_->micro_sym eq 'scratch' } @{ $class->methods } ),
"parsed public abstract nullable method" );
for my $method ( @{ $class->methods } ) {
if ( $method->micro_sym eq 'scratch' ) {
ok( $method->get_return_type->nullable,
"public abstract incremented nullable flagged as nullable" );
}
}
is( ( scalar grep { $_->public } @{ $class->methods } ),
6, "pass acl to Method constructor" );
ok( $class->has_attribute('lovable'), "parsed class attribute" );
ok( $class->has_attribute('drooly'), "parsed second class attribute" );
$class_content = qq|
parcel inert class Rigor::Mortis cnick Mort {
parcel inert void lie_still();
}|;
$class = $parser->class_declaration($class_content);
isa_ok( $class, "Clownfish::Class", "inert class_declaration" );
ok( $class->inert, "inert modifier parsed and passed to constructor" );
$class_content = qq|
final class Ultimo {
/** Throws an error.
*/
void Say_Never(Ultimo *self);
}|;
$class = $parser->class_declaration($class_content);
ok( $class->final, "final class_declaration" );