/** * 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; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; import org.junit.runners.Suite; import org.neo4j.consistency.report.ConsistencyReport; import org.neo4j.kernel.impl.nioneo.store.AbstractDynamicStore; import org.neo4j.kernel.impl.nioneo.store.DynamicRecord; import org.neo4j.kernel.impl.nioneo.store.RecordStore; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @RunWith(Suite.class) @Suite.SuiteClasses({DynamicRecordCheckTest.StringStore.class, DynamicRecordCheckTest.ArrayStore.class}) public abstract class DynamicRecordCheckTest extends RecordCheckTestBase<DynamicRecord,ConsistencyReport.DynamicConsistencyReport,DynamicRecordCheck> { private final int blockSize; private DynamicRecordCheckTest( DynamicRecordCheck check, int blockSize ) { super( check, ConsistencyReport.DynamicConsistencyReport.class ); this.blockSize = blockSize; } @Test public void shouldNotReportAnythingForRecordNotInUse() throws Exception { // given DynamicRecord property = notInUse( record( 42 ) ); // when ConsistencyReport.DynamicConsistencyReport report = check( property ); // then verifyOnlyReferenceDispatch( report ); } @Test public void shouldNotReportAnythingForRecordThatDoesNotReferenceOtherRecords() throws Exception { // given DynamicRecord property = inUse( fill( record( 42 ), blockSize / 2 ) ); // when ConsistencyReport.DynamicConsistencyReport report = check( property ); // then verifyOnlyReferenceDispatch( report ); } @Test public void shouldNotReportAnythingForRecordWithConsistentReferences() throws Exception { // given DynamicRecord property = inUse( fill( record( 42 ) ) ); DynamicRecord next = add( inUse( fill( record( 7 ), blockSize / 2 ) ) ); property.setNextBlock( next.getId() ); // when ConsistencyReport.DynamicConsistencyReport report = check( property ); // then verifyOnlyReferenceDispatch( report ); } @Test public void shouldReportNextRecordNotInUse() throws Exception { // given DynamicRecord property = inUse( fill( record( 42 ) ) ); DynamicRecord next = add( notInUse( record( 7 ) ) ); property.setNextBlock( next.getId() ); // when ConsistencyReport.DynamicConsistencyReport report = check( property ); // then verify( report ).nextNotInUse( next ); verifyOnlyReferenceDispatch( report ); } @Test public void shouldReportSelfReferentialNext() throws Exception { // given DynamicRecord property = add( inUse( fill( record( 42 ) ) ) ); property.setNextBlock( property.getId() ); // when ConsistencyReport.DynamicConsistencyReport report = check( property ); // then verify( report ).selfReferentialNext(); verifyOnlyReferenceDispatch( report ); } @Test public void shouldReportNonFullRecordWithNextReference() throws Exception { // given DynamicRecord property = inUse( fill( record( 42 ), blockSize - 1 ) ); DynamicRecord next = add( inUse( fill( record( 7 ), blockSize / 2 ) ) ); property.setNextBlock( next.getId() ); // when ConsistencyReport.DynamicConsistencyReport report = check( property ); // then verify( report ).recordNotFullReferencesNext(); verifyOnlyReferenceDispatch( report ); } @Test public void shouldReportInvalidDataLength() throws Exception { // given DynamicRecord property = inUse( record( 42 ) ); property.setLength( -1 ); // when ConsistencyReport.DynamicConsistencyReport report = check( property ); // then verify( report ).invalidLength(); verifyOnlyReferenceDispatch( report ); } @Test public void shouldReportEmptyRecord() throws Exception { // given DynamicRecord property = inUse( record( 42 ) ); // when ConsistencyReport.DynamicConsistencyReport report = check( property ); // then verify( report ).emptyBlock(); verifyOnlyReferenceDispatch( report ); } @Test public void shouldReportRecordWithEmptyNext() throws Exception { // given DynamicRecord property = inUse( fill( record( 42 ) ) ); DynamicRecord next = add( inUse( record( 7 ) ) ); property.setNextBlock( next.getId() ); // when ConsistencyReport.DynamicConsistencyReport report = check( property ); // then verify( report ).emptyNextBlock( next ); verifyOnlyReferenceDispatch( report ); } // change checking @Test public void shouldNotReportAnythingForConsistentlyChangedRecord() throws Exception { // given DynamicRecord oldProperty = fill( inUse( record( 42 ) ) ); DynamicRecord newProperty = fill( inUse( record( 42 ) ) ); DynamicRecord oldNext = fill( inUse( record( 10 ) ), 1 ); addChange( oldNext, notInUse( record( 10 ) ) ); DynamicRecord newNext = addChange( notInUse( record( 20 ) ), fill( inUse( record( 20 ) ), 1 ) ); oldProperty.setNextBlock( oldNext.getId() ); newProperty.setNextBlock( newNext.getId() ); // when ConsistencyReport.DynamicConsistencyReport report = checkChange( oldProperty, newProperty ); // then verifyOnlyReferenceDispatch( report ); } @Test public void shouldReportProblemsWithTheNewStateWhenCheckingChanges() throws Exception { // given DynamicRecord oldProperty = notInUse( record( 42 ) ); DynamicRecord newProperty = inUse( record( 42 ) ); // when ConsistencyReport.DynamicConsistencyReport report = checkChange( oldProperty, newProperty ); // then verify( report ).emptyBlock(); verifyOnlyReferenceDispatch( report ); } @Test public void shouldReportNextReferenceChangedButPreviouslyReferencedRecordNotUpdated() throws Exception { // given DynamicRecord oldProperty = fill( inUse( record( 42 ) ) ); DynamicRecord newProperty = fill( inUse( record( 42 ) ) ); oldProperty.setNextBlock( add( fill( inUse( record( 1 ) ) ) ).getId() ); newProperty.setNextBlock( addChange( notInUse( record( 2 ) ), fill( inUse( record( 2 ) ) ) ).getId() ); // when ConsistencyReport.DynamicConsistencyReport report = checkChange( oldProperty, newProperty ); // then verify( report ).nextNotUpdated(); verifyOnlyReferenceDispatch( report ); } // utilities DynamicRecord fill( DynamicRecord record ) { return fill( record, blockSize ); } abstract DynamicRecord fill( DynamicRecord record, int size ); abstract DynamicRecord record( long id ); @RunWith(JUnit4.class) public static class StringStore extends DynamicRecordCheckTest { public StringStore() { super( new DynamicRecordCheck( configureDynamicStore( 66 ), DynamicStore.STRING ), 66 ); } @Override DynamicRecord record( long id ) { return string( new DynamicRecord( id ) ); } @Override DynamicRecord fill( DynamicRecord record, int size ) { record.setLength( size ); return record; } } @RunWith(JUnit4.class) public static class ArrayStore extends DynamicRecordCheckTest { public ArrayStore() { super( new DynamicRecordCheck( configureDynamicStore( 66 ), DynamicStore.ARRAY ), 66 ); } @Override DynamicRecord record( long id ) { return array( new DynamicRecord( id ) ); } @Override DynamicRecord fill( DynamicRecord record, int size ) { record.setLength( size ); return record; } } public static RecordStore<DynamicRecord> configureDynamicStore( int blockSize ) { @SuppressWarnings( "unchecked" ) RecordStore<DynamicRecord> mock = mock( RecordStore.class ); when( mock.getRecordSize() ).thenReturn( blockSize + AbstractDynamicStore.BLOCK_HEADER_SIZE ); when( mock.getRecordHeaderSize() ).thenReturn( AbstractDynamicStore.BLOCK_HEADER_SIZE ); return mock; } }