/** * Copyright (c) 2002-2012 "Neo Technology," * Network Engine for Objects in Lund AB [http://neotechnology.com] * * This file is part of Neo4j. * * Neo4j is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package org.neo4j.consistency.checking.full; import java.io.StringWriter; import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.neo4j.consistency.RecordType; import org.neo4j.consistency.report.ConsistencySummaryStatistics; import org.neo4j.graphdb.DynamicRelationshipType; import org.neo4j.graphdb.GraphDatabaseService; import org.neo4j.graphdb.Node; import org.neo4j.helpers.UTF8; import org.neo4j.helpers.progress.ProgressMonitorFactory; import org.neo4j.kernel.impl.nioneo.store.DynamicRecord; import org.neo4j.kernel.impl.nioneo.store.NeoStoreRecord; import org.neo4j.kernel.impl.nioneo.store.NodeRecord; import org.neo4j.kernel.impl.nioneo.store.PropertyBlock; import org.neo4j.kernel.impl.nioneo.store.PropertyRecord; import org.neo4j.kernel.impl.nioneo.store.PropertyType; import org.neo4j.kernel.impl.nioneo.store.RelationshipRecord; import org.neo4j.kernel.impl.nioneo.store.RelationshipTypeRecord; import org.neo4j.kernel.impl.nioneo.store.StoreAccess; import org.neo4j.kernel.impl.util.StringLogger; import org.neo4j.test.GraphStoreFixture; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import static org.neo4j.consistency.checking.full.ExecutionOrderIntegrationTest.config; import static org.neo4j.test.Property.property; import static org.neo4j.test.Property.set; public class FullCheckIntegrationTest { @Rule public final GraphStoreFixture fixture = new GraphStoreFixture() { @Override protected void generateInitialData( GraphDatabaseService graphDb ) { org.neo4j.graphdb.Transaction tx = graphDb.beginTx(); try { Node node1 = set( graphDb.createNode() ); Node node2 = set( graphDb.createNode(), property( "key", "value" ) ); node1.createRelationshipTo( node2, DynamicRelationshipType.withName( "C" ) ); tx.success(); } finally { tx.finish(); } } }; private final StringWriter log = new StringWriter(); private ConsistencySummaryStatistics check() throws ConsistencyCheckIncompleteException { return check( fixture.storeAccess() ); } private ConsistencySummaryStatistics check( StoreAccess access ) throws ConsistencyCheckIncompleteException { FullCheck checker = new FullCheck( config( TaskExecutionOrder.SINGLE_THREADED ), ProgressMonitorFactory.NONE ); return checker.execute( access, StringLogger.wrap( log ) ); } private void verifyInconsistency( RecordType recordType, ConsistencySummaryStatistics stats ) { int count = stats.getInconsistencyCountForRecordType( recordType ); assertTrue( "Expected inconsistencies for records of type " + recordType, count > 0 ); assertEquals( "Expected only inconsistencies of type " + recordType + ", got:\n" + log, count, stats.getTotalInconsistencyCount() ); } @Test public void shouldCheckConsistencyOfAConsistentStore() throws Exception { // when ConsistencySummaryStatistics result = check(); // then assertEquals( 0, result.getTotalInconsistencyCount() ); } @Test @Ignore("Support for checking NeoStore needs to be added") public void shouldReportNeoStoreInconsistencies() throws Exception { // given fixture.apply( new GraphStoreFixture.Transaction() { @Override protected void transactionData( GraphStoreFixture.TransactionDataBuilder tx, GraphStoreFixture.IdGenerator next ) { NeoStoreRecord record = new NeoStoreRecord(); record.setNextProp( next.property() ); tx.update( record ); // We get exceptions when only the above happens in a transaction... tx.create( new NodeRecord( next.node(), -1, -1 ) ); } } ); // when ConsistencySummaryStatistics stats = check(); // then verifyInconsistency( RecordType.NEO_STORE, stats ); } @Test public void shouldReportNodeInconsistencies() throws Exception { // given fixture.apply( new GraphStoreFixture.Transaction() { @Override protected void transactionData( GraphStoreFixture.TransactionDataBuilder tx, GraphStoreFixture.IdGenerator next ) { tx.create( new NodeRecord( next.node(), next.relationship(), -1 ) ); } } ); // when ConsistencySummaryStatistics stats = check(); // then verifyInconsistency( RecordType.NODE, stats ); } @Test public void shouldReportRelationshipInconsistencies() throws Exception { // given fixture.apply( new GraphStoreFixture.Transaction() { @Override protected void transactionData( GraphStoreFixture.TransactionDataBuilder tx, GraphStoreFixture.IdGenerator next ) { tx.create( new RelationshipRecord( next.relationship(), 1, 2, 0 ) ); } } ); // when ConsistencySummaryStatistics stats = check(); // then verifyInconsistency( RecordType.RELATIONSHIP, stats ); } @Test public void shouldReportPropertyInconsistencies() throws Exception { // given fixture.apply( new GraphStoreFixture.Transaction() { @Override protected void transactionData( GraphStoreFixture.TransactionDataBuilder tx, GraphStoreFixture.IdGenerator next ) { PropertyRecord property = new PropertyRecord( next.property() ); property.setPrevProp( next.property() ); PropertyBlock block = new PropertyBlock(); block.setSingleBlock( 1 | (((long) PropertyType.INT.intValue()) << 24) | (666 << 28) ); property.addPropertyBlock( block ); tx.create( property ); } } ); // when ConsistencySummaryStatistics stats = check(); // then verifyInconsistency( RecordType.PROPERTY, stats ); } @Test public void shouldReportStringPropertyInconsistencies() throws Exception { // given fixture.apply( new GraphStoreFixture.Transaction() { @Override protected void transactionData( GraphStoreFixture.TransactionDataBuilder tx, GraphStoreFixture.IdGenerator next ) { DynamicRecord string = new DynamicRecord( next.stringProperty() ); string.setInUse( true ); string.setCreated(); string.setType( PropertyType.STRING.intValue() ); string.setNextBlock( next.stringProperty() ); string.setData( UTF8.encode( "hello world" ) ); PropertyBlock block = new PropertyBlock(); block.setSingleBlock( (((long) PropertyType.STRING.intValue()) << 24) | (string.getId() << 28) ); block.addValueRecord( string ); PropertyRecord property = new PropertyRecord( next.property() ); property.addPropertyBlock( block ); tx.create( property ); } } ); // when ConsistencySummaryStatistics stats = check(); // then verifyInconsistency( RecordType.STRING_PROPERTY, stats ); } @Test public void shouldReportArrayPropertyInconsistencies() throws Exception { // given fixture.apply( new GraphStoreFixture.Transaction() { @Override protected void transactionData( GraphStoreFixture.TransactionDataBuilder tx, GraphStoreFixture.IdGenerator next ) { DynamicRecord array = new DynamicRecord( next.arrayProperty() ); array.setInUse( true ); array.setCreated(); array.setType( PropertyType.ARRAY.intValue() ); array.setNextBlock( next.arrayProperty() ); array.setData( UTF8.encode( "hello world" ) ); PropertyBlock block = new PropertyBlock(); block.setSingleBlock( (((long) PropertyType.ARRAY.intValue()) << 24) | (array.getId() << 28) ); block.addValueRecord( array ); PropertyRecord property = new PropertyRecord( next.property() ); property.addPropertyBlock( block ); tx.create( property ); } } ); // when ConsistencySummaryStatistics stats = check(); // then verifyInconsistency( RecordType.ARRAY_PROPERTY, stats ); } @Test public void shouldReportRelationshipLabelNameInconsistencies() throws Exception { // given final Reference<Integer> inconsistentName = new Reference<Integer>(); fixture.apply( new GraphStoreFixture.Transaction() { @Override protected void transactionData( GraphStoreFixture.TransactionDataBuilder tx, GraphStoreFixture.IdGenerator next ) { inconsistentName.set( next.relationshipType() ); tx.relationshipType( inconsistentName.get(), "FOO" ); } } ); StoreAccess access = fixture.storeAccess(); DynamicRecord record = access.getTypeNameStore().forceGetRecord( inconsistentName.get() ); record.setNextBlock( record.getId() ); access.getTypeNameStore().updateRecord( record ); // when ConsistencySummaryStatistics stats = check( access ); // then verifyInconsistency( RecordType.RELATIONSHIP_LABEL_NAME, stats ); } @Test public void shouldReportPropertyKeyNameInconsistencies() throws Exception { // given final Reference<Integer> inconsistentName = new Reference<Integer>(); fixture.apply( new GraphStoreFixture.Transaction() { @Override protected void transactionData( GraphStoreFixture.TransactionDataBuilder tx, GraphStoreFixture.IdGenerator next ) { inconsistentName.set( next.propertyKey() ); tx.propertyKey( inconsistentName.get(), "FOO" ); } } ); StoreAccess access = fixture.storeAccess(); DynamicRecord record = access.getPropertyKeyStore().forceGetRecord( inconsistentName.get() ); record.setNextBlock( record.getId() ); access.getPropertyKeyStore().updateRecord( record ); // when ConsistencySummaryStatistics stats = check( access ); // then verifyInconsistency( RecordType.PROPERTY_KEY_NAME, stats ); } @Test public void shouldReportRelationshipLabelInconsistencies() throws Exception { // given StoreAccess access = fixture.storeAccess(); RelationshipTypeRecord record = access.getRelationshipTypeStore().forceGetRecord( 1 ); record.setNameId( 20 ); record.setInUse( true ); access.getRelationshipTypeStore().updateRecord( record ); // when ConsistencySummaryStatistics stats = check( access ); // then verifyInconsistency( RecordType.RELATIONSHIP_LABEL, stats ); } @Test public void shouldReportPropertyKeyInconsistencies() throws Exception { // given final Reference<Integer> inconsistentKey = new Reference<Integer>(); fixture.apply( new GraphStoreFixture.Transaction() { @Override protected void transactionData( GraphStoreFixture.TransactionDataBuilder tx, GraphStoreFixture.IdGenerator next ) { inconsistentKey.set( next.propertyKey() ); tx.propertyKey( inconsistentKey.get(), "FOO" ); } } ); StoreAccess access = fixture.storeAccess(); DynamicRecord record = access.getPropertyKeyStore().forceGetRecord( inconsistentKey.get() ); record.setInUse( false ); access.getPropertyKeyStore().updateRecord( record ); // when ConsistencySummaryStatistics stats = check( access ); // then verifyInconsistency( RecordType.PROPERTY_KEY, stats ); } private static class Reference<T> { private T value; void set(T value) { this.value = value; } T get() { return value; } @Override public String toString() { return String.valueOf( value ); } } }