From: <pau...@us...> - 2007-06-06 10:01:06
|
Revision: 965 http://svn.sourceforge.net/everydevel/?rev=965&view=rev Author: paul_the_nomad Date: 2007-06-06 03:01:02 -0700 (Wed, 06 Jun 2007) Log Message: ----------- Compare nodeball to nodebase code and tests Modified Paths: -------------- trunk/ebase/lib/Everything/Storage/Nodeball.pm trunk/ebase/lib/Everything/Storage/Test/Nodeball.pm Property Changed: ---------------- trunk/ebase/ Property changes on: trunk/ebase ___________________________________________________________________ Name: svk:merge - 16c2b9cb-492b-4d64-9535-64d4e875048d:/wip/ebase:998 a6810612-c0f9-0310-9d3e-a9e4af8c5745:/ebase/offline:17930 + 16c2b9cb-492b-4d64-9535-64d4e875048d:/wip/ebase:1014 a6810612-c0f9-0310-9d3e-a9e4af8c5745:/ebase/offline:17930 Modified: trunk/ebase/lib/Everything/Storage/Nodeball.pm =================================================================== --- trunk/ebase/lib/Everything/Storage/Nodeball.pm 2007-05-22 23:15:52 UTC (rev 964) +++ trunk/ebase/lib/Everything/Storage/Nodeball.pm 2007-06-06 10:01:02 UTC (rev 965) @@ -741,129 +741,330 @@ return $result; } -=cut -=head2 C<update_nodeball> +sub build_new_nodes { -We already have this nodeball in the system, and we need to figure out which -files to add, remove, and update. + my ( $self ) = @_; + my $select_cb ||= sub { 1 }; + my $iterator = $self->make_node_iterator(); + + my @nodes; + while ( my $xmlnode = $iterator->() ) { + my $node = xml2node( $xmlnode->get_raw_xml, 'nofinal' ); + print "@$node\n"; + push @nodes, @$node; + } + + return @nodes; + +} + +sub get_conflicting_nodes { + my ( $self ) = @_; + + +} + +sub update_node_to_nodebase { + my ( $self, $node, $handle_conflict_cb ) = @_; + + my $oldnode = $node->existingNodeMatches(); + + ## default behaviour is to clobber nodes + $handle_conflict_cb ||= sub{ $oldnode->updateFromImport( $node, -1 ) }; + + if ( $oldnode->conflictsWith( $node ) ){ + $handle_conflict_cb->(); + } else { + $oldnode->updateFromImport( $node, -1 ); + } + + + +} + + + +=head2 C<verify_nodes> + +Cycles through the nodeball and checks each node against what is in the nodebase. the Checks that a node in the nodeball is the same as the one in the nodebase. +Returns an array ref of array refs. + +Each of the inner array refs are as follows: + +If the node is in the nodeball but not in the nodebase: + +[ $xmlnode, undef ] + +If the node is in the nodebase (and listed as a member of the nodeball), but not in the nodeball: + +[ undef, $node ] + +If the node is in the nodebase and nodeball, but there are differences: + +[ $xmlnode, $diff_hash ] + +$diff_hash is a hash of the difference as returned by verify_node(). + =cut -sub update_nodeball { - my ( $self, $OLDBALL, $dir ) = @_; - my $DB = $self->get_nodebase - || Everything::Exception::NoNodeBase->throw("No nodebase here!"); - $dir ||= $self->get_nodeball_dir; - my $NEWBALL = $self->nodeball_xml; +sub verify_nodes { + my ( $self ) = @_; - my $script_dir = $dir . "/scripts"; - my $preinst = $script_dir . "/preupdate.pl"; - require $preinst if -f $preinst; + my $nb = $self->get_nodebase; - #check the tables and make sure that they're compatable + my $iterator = $self->make_node_iterator; - $self->insert_sql_tables($dir); + my @diffs; - my $nodesdir = $dir . "/nodes"; - my @nodes = (); - my @conflictnodes = (); + my $nodebase_nodeball_group = $nb->getNode( $self->nodeball_vars->{title}, 'nodeball')->selectGroupArray; - use File::Find; + ## get nodes in group; - # XXX: for this to work we need to split XML::xml2node so that - # inserting into the database and creating functions from nodes - # are not going through the same function + my %nodebase_group = map { my $n = $nb->getNode( $_ ); + ("$$n{title},$$n{type}{title}" => 1); + } + @$nodebase_nodeball_group; - # XXXX: xmlFinal also calls update - find sub { - my $file = $File::Find::name; - return unless $file =~ /\.xml$/; - ## no final means we don't insert the node into the db. - my $info = xmlfile2node( $file, 'nofinal' ); - push @nodes, @$info if $info; - }, $nodesdir; + XMLNODE: + while (my $xmlnode = $iterator->() ) { - #check to make sure all dependencies are installed + my $title = $xmlnode->get_title; + my $type = $xmlnode->get_nodetype; + my $node = $self->get_nodebase->getNode( $title, $type ); - # create a hash of the old nodegroup -- better lookup times - my (%oldgroup); - foreach my $id ( @{ $$OLDBALL{group} } ) { - $oldgroup{$id} = $DB->getNode($id); + delete $nodebase_group{ "$title,$type" }; + + if ( ! $node) { + + push @diffs, [ $xmlnode, undef ]; + next XMLNODE; + } + + if ( my $diff = $self->verify_node( $xmlnode, $node) ) { + + push @diffs, [ $xmlnode, $diff ]; + next XMLNODE; + } + } - ### get all the old noball members. - my $nbmembers = buildNodeballMembers($OLDBALL); - my $new_nbfile; - foreach my $node_id (@nodes) { - my $N = $DB->getNode($node_id); - next - if $$N{type}{title} eq 'nodeball' - and $$N{title} eq $$NEWBALL{title}; + foreach ( keys %nodebase_group ) { + my ( $title, $type ) = split /,/, $_; + push @diffs, [ undef, $nb->getNode( $title, $type ) ]; + } - # XXX: According to Node.pm, this is supposed to get called on - # a dummy node, but here we're calling it on a node retrieved - # from the DB. Something won't work. + return \@diffs; +} - my $OLDNODE = $N->existingNodeMatches(); - if ($OLDNODE) { - next if $$N{type}{title} eq 'nodeball'; - if ( $oldgroup{ $OLDNODE->getId() } ) { - delete $oldgroup{ $OLDNODE->getId() }; +=head2 C<verify_node> + +Checks that a node in the nodeball is the same as the one in the nodebase. + +First argument in the XML::Node object, the second one is the Everything::Node object. + +Returns a hash ref of differences. It has the the following structure. + +{ + + attribute => { attributename => [ $xmlnode, $node, $attributetype ], + attributename2 => [ $xmlnode, $node, 'noderef', $referenced_node ], + .... + }, + + var => { varname => [ $xmlnode, $node, 'literal_value' ], + varname2 => [ $xmlnode, $node, 'noderef', $referenced_node ], + .... + }, + + + groupmember => { membernodetype,nodetype => [ $xmlnode, $node, $referenced_node ], + .... + } + +} + +=cut + +## Arguably, the return value is a little confusing and difficult to +## unpack, so we should do itwith an Everything::Nodeball::Difference +## object or something + + +sub verify_node { + + my ( $self, $xmlnode, $node ) = @_; + + + ### XXX: if we want to turn this into a function, $nb can be the + ### nodebae stored in $node + my $nb = $self->get_nodebase; + + my %differences; + ## verify attributes + my $atts = $xmlnode->get_attributes; + + + + my $node_title = $xmlnode->get_title; + my $node_type = $xmlnode->get_nodetype; + + my %attribute_differences; + + foreach (@$atts) { + my $att_name = $_->get_name; + + my $att_type = $_->get_type; + + if ( $att_type eq 'literal_value' ) { + + ## the line below makes undef an empty string to deal + ## with the way database tables are created at the + ## moment. + my $content = defined $_->get_content ? $_->get_content : ''; + + unless ( $node->{$att_name} eq $content ) { + $attribute_differences{ $att_name } = [ $xmlnode, $node, $att_type ]; + } + } + else { - if ( $$nbmembers{ $OLDNODE->getId() } ) { - my $OTHERNB = $DB->getNode( $$nbmembers{ $OLDNODE->getId() } ); - next - unless confirmYN( -"$$OLDNODE{title} ($$OLDNODE{type}{title}) is also included in the \"$$OTHERNB{title}\" nodeball. Do you want to replace it (N/y)?" - ); + my ($type_name) = split /,/, $_->get_type_nodetype; + my $node_name = $_->get_content; + + my $wanted = $nb->getNode( $node_name, $type_name ); + + unless ( $node->{$att_name} == $wanted->{node_id} ) { + $attribute_differences{ $att_name } = [ $xmlnode, $node, $att_type, $wanted ]; + } + } - if ( not $OLDNODE->conflictsWith($N) ) { - $OLDNODE->updateFromImport( $N, -1 ); + $differences{ attributes } = \%attribute_differences if %attribute_differences; + } + + + ### verify vars + + my $vars = $xmlnode->get_vars; + + if (@$vars) { + + my $db_vars = $node->getVars; + + my %var_differences; + + foreach (@$vars) { + + my $var_name = $_->get_name; + + my $var_type = $_->get_type; + + + if ( $var_type eq 'literal_value' ) { + + ## the line below makes undef an empty string to deal + ## with the way database tables are created at the + ## moment. + my $content = defined $_->get_content ? $_->get_content : ''; + + + unless ( $db_vars->{$var_name} eq $content ) { + $var_differences{ $var_name } = [ $xmlnode, $node, $var_type ]; + } + } else { - push @conflictnodes, $N; + + my ($type_name) = split /,/, $_->get_type_nodetype; + my $node_name = $_->get_content; + + my $wanted = $nb->getNode( $node_name, $type_name ); + + unless ( $db_vars->{$var_name} == $wanted->{node_id} ) { + $var_differences{ $var_name } = [ $xmlnode, $node, $var_type, $wanted ]; + } + + } } - else { - if ( $$N{type}{title} eq 'nodeball' ) { - print -"shoot! Your nodeball says it needs $$N{title}. You need to go get that."; - die unless $self->FORCE; - } - $N->xmlFinal(); - } + $differences{ vars } = \%var_differences if %var_differences; + + } + ## verify group members - fixNodes(0); - #fix broken dependancies + my $members = $xmlnode->get_group_members; - handleConflicts( \@conflictnodes, $NEWBALL ); + if ( @$members ) { + + my %db_members = map { $_ => 1 } @{ $node->selectGroupArray }; - #insert the new nodeball - $OLDBALL->updateFromImport( $NEWBALL, -1 ); + my %member_differences; - #find the unused nodes and remove them - foreach ( values %oldgroup ) { - my $NODE = $DB->getNode($_); + foreach (@$members) { - next unless ($NODE); + my ($type_name) = split /,/, $_->get_type_nodetype; + my $node_name = $_->get_name; - #we should probably confirm this - #$NODE->nuke(-1); + my $wanted = $nb->getNode( $node_name, $type_name ); + + unless ( $db_members{ $wanted->{node_id} } ) { + $member_differences{"$node_name,$type_name" } = [ $xmlnode, $node, $wanted ]; + } + } + + $differences{groupmembers} = \%member_differences if %member_differences; + } - fixNodes(1); + return \%differences if %differences; + return; +} - my $postinst = $script_dir . "/postupdate.pl"; - require $postinst if -f $postinst; +=head2 C<update_nodeball> - installModules($dir); +We already have this nodeball in the system, and we need to figure out which +files to add, remove, and update. - print "$$OLDBALL{title} updated.\n"; +=cut + +sub update_nodeball { + my ( $self, $dir ) = @_; + + my $DB = $self->get_nodebase + || Everything::Exception::NoNodeBase->throw("No nodebase here!"); + + $dir ||= $self->get_nodeball_dir; + + my $NEWBALLXML = $self->nodeball_xml; + + my $vars = $self->nodeball_vars; + + my $OLDBALL = $self->get_nodebase->getNode( $vars->{title}, 'nodeball'); + + #check the tables and make sure that they're compatable + + $self->insert_sql_tables($dir); + + my @nodes = $self->build_new_nodes; # list of new nodes + + foreach my $N (@nodes) { + print "$N\n"; + $self->update_node_to_nodebase( $N ); + } + + fixNodes(0); + + #insert the new nodeball + my $nodelist = xml2node( $NEWBALLXML, 'nofinal' ); + $OLDBALL->updateFromImport( $$nodelist[0], -1 ); + + fixNodes(1); + } =cut Modified: trunk/ebase/lib/Everything/Storage/Test/Nodeball.pm =================================================================== --- trunk/ebase/lib/Everything/Storage/Test/Nodeball.pm 2007-05-22 23:15:52 UTC (rev 964) +++ trunk/ebase/lib/Everything/Storage/Test/Nodeball.pm 2007-06-06 10:01:02 UTC (rev 965) @@ -32,7 +32,7 @@ my ($self) = @_; my $dir = get_temp_dir(); - + mkdir $dir; chdir $dir; @@ -810,22 +810,6 @@ } -sub test_check_nodeball_against_nodebase :Test(1) { - local $TODO = "Unimplemented"; - - ok( undef, '...runs through each node in the nodeball and checks for type, attributes and values against the node stored in the nodebase.'); - - -} - -sub test_check_nodebase_against_nodeball :Test(1) { - local $TODO = "Unimplemented"; - - ok( undef, '...runs through each node in the nodebase and checks for type, attributes and values against the xmlnode stored in the nodeball.'); - - -} - sub test_check_nodeball_integrity :Test(4) { my $self = shift; @@ -880,44 +864,184 @@ } -sub test_check_nodeball_presence :Test(4) { - local $TODO = "Unimplemented."; +sub test_verify_nodes : Test(1) { + my $self = shift; - my $self = shift; my $instance = $self->{instance}; - return "unimplemented"; - my $dir = get_temp_dir(); - mkdir $dir; - my $mock = Test::MockObject->new; - $instance->set_nodebase( $mock ); - $mock->set_always( selectNodegroupFlat => [ { title => "Duplicate Found", type => { title => 'superdoc' }}, { title => "Create a new user", type => { title => 'htmlcode' }}, { title => "thingo", type => { title => 'thingtype' }} ] ); + my $mock = $self->{mock}; - $instance->set_nodeball_dir( $dir ); + $instance->set_nodebase($mock); - my $mefile = File::Spec->catfile ($dir, 'ME'); - my $fh = IO::File->new( $mefile, 'w' ) || die "Can't open $mefile, $!"; - print $fh <<HERE; -<NODE export_version="0.5" nodetype="nodeball" title="core system"> - <group> - <member name="group_node" type="noderef" type_nodetype="theme,nodetype">default theme</member> - <member name="group_node" type="noderef" type_nodetype="superdoc,nodetype">Create a new user</member> - <member name="group_node" type="noderef" type_nodetype="superdoc,nodetype">Duplicates Found</member> -</group> -</NODE> -HERE + local *Everything::Storage::Nodeball::verify_node; + *Everything::Storage::Nodeball::verify_node = sub { 'verified' }; - my ($not_in_nodebase, $not_in_nodeball) = $instance->check_nodeball_integrity; - my @sorted = sort { $a->get_title cmp $b->get_title } @{ $not_in_nodebase || [] }; - is($sorted[0]->get_title, 'Create a new user', '...not in nodebase when titles same but types are different.'); - is($sorted[1]->get_title, 'default theme', '...not in nodebase when title not presnet.'); + local *Everything::Storage::Nodeball::nodeball_vars; + *Everything::Storage::Nodeball::nodeball_vars = + sub { { title => 'nodeballname' } }; - @sorted = sort { $a->{title} cmp $b->{title} } @{ $not_in_nodeball || [] }; - is($sorted[0]->get_title, 'Create a new user', '...not in nodeball when titles same but types are different.'); - is($sorted[1]->get_title, 'default theme', '...not in nodeball when title not presnet.'); + my @returns = ( $mock, $mock, $mock ); + local *Everything::Storage::Nodeball::make_node_iterator; + *Everything::Storage::Nodeball::make_node_iterator = sub { + sub { shift @returns } + }; + $mock->set_always( selectGroupArray => [ 1 .. 4 ] ); + $mock->set_series( + getNode => $mock, + $mock, $mock, $mock, $mock, undef, $mock, $mock, $mock, $mock, $mock, + $mock, $mock + ); + $mock->set_always( get_nodetype => 'anodetype' ); + $mock->set_series( get_title => qw/title1 title2 title3 title4/ ); + $mock->{title} = 'node title'; + $mock->{type} = $mock; + my $rv = $instance->verify_nodes; + + is_deeply( + $rv, + [ + [ $mock, undef ], + [ $mock, 'verified' ], + [ $mock, 'verified' ], + [ undef, $mock ] + ], + '...returns an array ref.' + ); } +sub test_verify_node : Test(4) { + my $self = shift; + my $instance = $self->{instance}; + my $xmlnode = Test::MockObject->new; + my $node = Test::MockObject->new; + my $mock = $self->{mock}; + + $instance->set_nodebase( $self->{mock} ); + $self->{mock}->set_always( getNode => $self->{mock} ); + $self->{mock}->{title} = 3; + $self->{mock}->{node_id} = 123; + + $xmlnode->set_always( 'get_attributes', [ $xmlnode, $xmlnode ] ); + + $xmlnode->set_always( get_vars => [] ); + $xmlnode->set_always( get_group_members => [] ); + + $xmlnode->set_always( get_title => 'node name' ); + $xmlnode->set_always( get_nodetype => 'a nodetype' ); + $xmlnode->set_series( get_name => 'attribute name', 'att2' ); + $xmlnode->set_always( get_content => 'attribute content' ); + $xmlnode->set_always( get_type_nodetype => 'anodetype,nodetype' ); + $xmlnode->set_series( get_type => 'literal_value', 'noderef' ); + + $node->{'attribute name'} = 'attribute content'; + $node->{'att2'} = 123; + my $rv = $instance->verify_node( $xmlnode, $node ); + + is( $rv, undef, '...if the same returns nothing.' ); + + + + $xmlnode->set_series( get_name => 'attribute name', 'att2' ); + $xmlnode->set_series( get_type => 'literal_value', 'noderef' ); + $node->{'attribute name'} = 'different content'; + $node->{'att2'} = 456; + $rv = $instance->verify_node( $xmlnode, $node ); + + is_deeply( + $rv->{attributes}, + { + 'attribute name' => + [ + $xmlnode, + $node, + 'literal_value' + ], + 'att2' => + [ + $xmlnode, + $node, + 'noderef', + $mock + ] + + }, + '...returns a hash ref explaining differences if doesn\'t match.' + ); + + + #Now test vars + + $node->set_always( getVars => { varname1 => 'varvalue', varname2 => 123 } ); + $xmlnode->set_series( get_name => 'varname1', 'varname2' ); + $xmlnode->set_always( 'get_attributes' => [] ); + $xmlnode->set_always( get_vars => [ $xmlnode, $xmlnode] ); + $xmlnode->set_always( get_group_members => [] ); + $xmlnode->set_series( get_type => 'literal_value', 'noderef' ); + $self->{mock}->{title} = "Title of a node retrieved from db."; + $self->{mock}->{node_id} = 456; + $rv = $instance->verify_node( $xmlnode, $node ); + + is_deeply( + $rv->{vars}, + { + 'varname1' => + [ + $xmlnode, + $node, + 'literal_value' + ], + 'varname2' => + [ + $xmlnode, + $node, + 'noderef', + $mock + ] + + }, + '...returns a hash ref explaining var differences if no match.' + ); + + + #Now test group members + + $node->set_always( selectGroupArray => [ 1, 2 ] ); + $xmlnode->set_series( get_name => 'member1', 'member2' ); + $xmlnode->set_always( 'get_attributes' => [] ); + $xmlnode->set_always( get_vars => [] ); + $xmlnode->set_always( get_group_members => [ $xmlnode, $xmlnode ] ); + $xmlnode->set_series( get_type => 'literal_value', 'noderef' ); + $self->{mock}->{title} = "Title of a node retrieved from db."; + $self->{mock}->{node_id} = 123; + $rv = $instance->verify_node( $xmlnode, $node ); + + is_deeply( + $rv->{groupmembers}, + { + 'member1,anodetype' => + [ + $xmlnode, + $node, + $mock + ], + + 'member2,anodetype' => + [ + $xmlnode, + $node, + $mock + ] + + }, + '...returns a hash ref explaining var differences if no match.' + ); + + + +} + + sub parse_sql_file_returns { ( This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |