package rocks.inspectit.server.instrumentation.classcache; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.empty; import static org.hamcrest.Matchers.hasItem; import static org.hamcrest.Matchers.hasSize; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.not; import static org.hamcrest.Matchers.nullValue; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Modifier; import java.util.Arrays; import java.util.Collection; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.concurrent.Callable; import org.mockito.InjectMocks; import org.mockito.Matchers; import org.mockito.Mock; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; import org.slf4j.Logger; import org.testng.annotations.BeforeMethod; import org.testng.annotations.DataProvider; import org.testng.annotations.Test; import rocks.inspectit.server.instrumentation.classcache.events.Events; import rocks.inspectit.server.instrumentation.classcache.events.NodeEvent; import rocks.inspectit.server.instrumentation.classcache.events.NodeEvent.NodeEventDetails; import rocks.inspectit.server.instrumentation.classcache.events.NodeEvent.NodeEventType; import rocks.inspectit.server.instrumentation.classcache.events.ReferenceEvent; import rocks.inspectit.server.instrumentation.classcache.events.ReferenceEvent.ReferenceType; import rocks.inspectit.shared.all.instrumentation.classcache.AnnotationType; import rocks.inspectit.shared.all.instrumentation.classcache.ClassType; import rocks.inspectit.shared.all.instrumentation.classcache.ImmutableType; import rocks.inspectit.shared.all.instrumentation.classcache.InterfaceType; import rocks.inspectit.shared.all.instrumentation.classcache.MethodType; import rocks.inspectit.shared.all.instrumentation.classcache.Modifiers; import rocks.inspectit.shared.all.instrumentation.classcache.Type; import rocks.inspectit.shared.all.instrumentation.classcache.TypeWithAnnotations; import rocks.inspectit.shared.all.instrumentation.classcache.TypeWithMethods; import rocks.inspectit.shared.all.testbase.TestBase; /** * Dimensions we need to test * * - general problems, the class cache should find and return an exception - generic adding (where * the type is not interesting) - generic merging - adding of methods - merging of methods - * * - entities -- types with methods -- types with annotations -- classtype specifics -- * interfacetype specifics -- * * @author Stefan Siegl * */ @SuppressWarnings("PMD") public class ClassCacheModificationTest extends TestBase { @InjectMocks ClassCacheModification service; @Mock ClassCacheLookup lookup; @Mock ClassCache cache; @Mock Logger log; public class Merge extends ClassCacheModificationTest { @DataProvider public Object[][] types() { return new Object[][] { { ClassType.class }, { AnnotationType.class }, { InterfaceType.class } }; } @DataProvider public Object[][] typesWithMethods() { return new Object[][] { { ClassType.class }, { InterfaceType.class } }; } @DataProvider public Object[][] differentTypes() { return new Object[][] { { ClassType.class, AnnotationType.class }, { ClassType.class, InterfaceType.class }, { AnnotationType.class, ClassType.class }, { AnnotationType.class, InterfaceType.class }, { InterfaceType.class, ClassType.class }, { InterfaceType.class, AnnotationType.class } }; } @BeforeMethod public void setup() throws Exception { Answer<Object> locksAnswer = new Answer<Object>() { @Override public Object answer(InvocationOnMock invocation) throws Throwable { Callable<?> callable = (Callable<?>) invocation.getArguments()[0]; return callable.call(); } }; doAnswer(locksAnswer).when(cache).executeWithReadLock(Matchers.<Callable<?>> any()); doAnswer(locksAnswer).when(cache).executeWithWriteLock(Matchers.<Callable<?>> any()); doReturn(lookup).when(cache).getLookupService(); service.init(cache); } // // General error conditions // @Test(dataProvider = "types", expectedExceptions = { ClassCacheModificationException.class }) public void ensureThatFQNIsNotNull(Class<? extends Type> type) throws Exception { String fqn = null; String hash = "hash"; int modifiers = 0; Type theClass = construct(type, fqn, hash, modifiers); service.merge(theClass); } @Test(expectedExceptions = { ClassCacheModificationException.class }) public void ensureThatNonNull() throws Exception { service.merge(null); } @Test(expectedExceptions = { ClassCacheModificationException.class }) public void addingInvalidTypeFails() throws Exception { String fqn = "fqn"; Events events = service.merge(new Type(fqn) { @Override public void cleanUpBackReferences() { } @Override public void removeReferences() { } @Override public Collection<Type> getDependingTypes() { return null; } }); assertEventsEmpty(events); } // ------------------------------- // // // ------------------------------- // Adding and merging of base entities situations: <br /> // - not known entity that was initialized <br /> // - not known entity that was not initialized // - known entity that is known as uninitialized and given as uninitialized // - known entity that is known as uninitialized and given as initialized // - known entity that is known as initialized and given as uninitialized // - known entity that is known as initialized and given as initialized // - same type should not be added twice // - merge modifiers @Test(dataProvider = "types") public void addNewInitializedTypeThatWasNotKnown(Class<? extends Type> type) throws Exception { String fqn = "class"; String hash = "hash"; int modifiers = 0; when(lookup.findByFQN(fqn)).thenReturn(null); service.lookup = lookup; Type theClass = construct(type, fqn, hash, modifiers); Events events = service.merge(theClass); Events expected = new Events(); expected.addEvent(new NodeEvent(theClass, NodeEventType.NEW, NodeEventDetails.INITIALIZED)); assertEvents(events, expected); } @Test(dataProvider = "types", expectedExceptions = { ClassCacheModificationException.class }) public void addNewUnInitializedTypeThatWasNotKnown(Class<? extends Type> type) throws Exception { String fqn = "class"; when(lookup.findByFQN(fqn)).thenReturn(null); service.lookup = lookup; Type theClass = construct(type, fqn); Events events = service.merge(theClass); Events expected = new Events(); expected.addEvent(new NodeEvent(theClass, NodeEventType.NEW, NodeEventDetails.NOT_INITIALIZED)); assertEvents(events, expected); } @Test(dataProvider = "types") public void addNewInitializedTypeThatIsKnownUninitialized(Class<? extends Type> type) throws Exception { String fqn = "class"; String hash = "hash"; int modifiers = 0; Type notInitialized = construct(type, fqn); when(lookup.findByFQN(fqn)).thenReturn(notInitialized); service.lookup = lookup; Type initialized = construct(type, fqn, hash, modifiers); Events events = service.merge(initialized); Events expected = new Events(); expected.addEvent(new NodeEvent(notInitialized, NodeEventType.CHANGED, NodeEventDetails.INITIALIZED)); assertEvents(events, expected); } @Test(dataProvider = "types") public void addNewInitializedTypeThatIsKnownInitializedWithDifferentHash(Class<? extends Type> type) throws Exception { String fqn = "class"; String hash = "hash"; String hashStored = "hashStored"; int modifiers = 0; Type storedClass = construct(type, fqn, hashStored, modifiers); when(lookup.findByFQN(fqn)).thenReturn(storedClass); service.lookup = lookup; Type initialized = construct(type, fqn, hash, modifiers); Events events = service.merge(initialized); Events expected = new Events(); expected.addEvent(new NodeEvent(storedClass, NodeEventType.CHANGED, NodeEventDetails.HASH_ADDED)); assertEvents(events, expected); } @Test(dataProvider = "types") public void addNewInitializedTypeThatIsKnownInitializedWithSameHash(Class<? extends Type> type) throws Exception { String fqn = "class"; String hash = "hash"; int modifiers = 0; Type storedClass = construct(type, fqn, hash, modifiers); when(lookup.findByFQN(fqn)).thenReturn(storedClass); service.lookup = lookup; Events events = service.merge(storedClass); assertEventsEmpty(events); } @Test(dataProvider = "types") public void mergeModifiers(Class<? extends Type> type) throws Exception { String fqn = "fqn"; int m = Modifiers.getModifiers(Modifier.PUBLIC); String hash = "hash"; Type given = construct(type, fqn, hash, m); assertThat(Modifiers.isPublic(given.getModifiers()), is(true)); String hashStored = "stored"; int modStored = Modifiers.getModifiers(Modifier.PRIVATE); Type stored = construct(type, fqn, hashStored, modStored); assertThat(Modifiers.isPrivate(stored.getModifiers()), is(true)); when(lookup.findByFQN(fqn)).thenReturn(stored); service.lookup = lookup; Events events = service.merge(given); // so now we expect that the stored on is both public and private assertThat(Modifiers.isPublic(stored.getModifiers()), is(true)); assertThat(Modifiers.isPrivate(stored.getModifiers()), is(true)); Events expected = new Events(); expected.addEvent(new NodeEvent(stored, NodeEventType.CHANGED, NodeEventDetails.HASH_ADDED)); expected.addEvent(new NodeEvent(stored, NodeEventType.CHANGED, NodeEventDetails.MODIFIERS_CHANGED)); assertEvents(events, expected); } // ------------------------------------------------------------------------------------ // Reference section // // this sections provides tests for references, that are either there or not there or need // to be // merged. Please note that we moved methods stuff away from here in its own section, as // there // is a lot to do there // // facts // - the referred entities can only be given as uninitialized types, we will have a test // that // giving an initialized referred type will log a warning // // --> handleTwoLevelInstanceAreNotSupported // // - all references have a direction (although they are ensured to be bi-directional). It is // not possible for the parser to give me the subclass of a given class, only the // superclass. // Thus we have to ensure that giving us this "unmeaningful" references result in a log // output // but no action. // // --> handleGivenSubclassIsNotSupported // --> handleGivenSubInterfaceIsNotSupported // --> handleGivenAnnotatedTypeIsNotSupported // --> handleGivenReferredClassIsNotSupported // --> handleGivenMethodThrowingThisExceptionIsNotSupported // // - dimensions we need to test every reference from // -- base type (stored) is initialized / not initialized / not there // -- given provides new reference / existing reference // -- referred type in given is there / not there // // base - given provides existing/new - ref. type available - meaningful // init - new - yes - y - mergeWithInitializedNewReferenceReferredTypeThere // init - new - not - y - mergeWithInitializedNewReferenceReferredTypeNotThere // init - exi - yes - y - mergeWithInitializedExistingReferenceReferredTypeThere // init - exi - not - n // noti - new - yes - y - mergeWithNotInitializedNewReferenceReferredTypeThere // noti - new - not - y - mergeWithNotInitializedNewReferenceReferredTypeNotThere // noti - exi - yes - n // noti - exi - not - n // nott - new - yes - y - addNewReferenceReferredTypeThere // nott - new - not - y - addNewReferenceReferredTypeNotThere // nott - exi - yes - n // nott - exi - not - n // // we need to do that for: // - annotation // - superclass // - superinterface // - realizedinterface // // what we need to ensure in addition: // - all merges and adds must be bidirectional, that is the referred type must be checked to // have the reference to this type as well. // // ------------------------------------------------------------------------------------ // we keep this methods for easy template if another reference is to be added // @Test // public void mergeWithInitializedNewReferenceReferredTypeThere() throws // ClassCacheModificationException { // // } // // @Test // public void mergeWithInitializedNewReferenceReferredTypeNotThere() throws // ClassCacheModificationException { // // } // // @Test // public void mergeWithInitializedExistingReferenceReferredTypeThere() throws // ClassCacheModificationException { // // } // // @Test // public void mergeWithNotInitializedNewReferenceReferredTypeThere() throws // ClassCacheModificationException { // // } // // @Test // public void mergeWithNotInitializedNewReferenceReferredTypeNotThere() throws // ClassCacheModificationException { // // } // // @Test // public void addNewReferenceReferredTypeThere() throws Exception { // // } // // @Test // public void addNewReferenceReferredTypeNotThere() throws Exception // { // // } // // Superclasses // @Test public void mergeWithInitializedNewSuperclassReferenceReferredTypeThere() throws Exception { String fqn = "class"; String hash = "hash"; String hashStored = "hashStored"; int modifiers = 0; ClassType c = new ClassType(fqn, hash, modifiers); String fqnSuper = "fqnSuper"; ClassType s = new ClassType(fqnSuper); c.addSuperClass(s); ClassType stored = new ClassType(fqn, hashStored, modifiers); String fqnAnotherSuperOfStored = "anothersuperofstored"; ClassType anotherSuperOfStored = new ClassType(fqnAnotherSuperOfStored); stored.addSuperClass(anotherSuperOfStored); when(lookup.findByFQN(fqn)).thenReturn(stored); when(lookup.findByFQN(fqnSuper)).thenReturn(s); when(lookup.findByFQN(fqnAnotherSuperOfStored)).thenReturn(anotherSuperOfStored); service.lookup = lookup; Events events = service.merge(c); Events expected = new Events(); expected.addEvent(new NodeEvent(c, NodeEventType.CHANGED, NodeEventDetails.HASH_ADDED)); expected.addEvent(new ReferenceEvent(stored, s, ReferenceType.SUPERCLASS)); // assert bidirectional assertThat(stored.getSuperClasses().size(), is(2)); assertThat(stored.getSuperClasses(), hasItem(s)); assertThat(stored.getSuperClasses(), hasItem(anotherSuperOfStored)); assertThat(s.getSubClasses(), hasItem(stored)); assertThat(anotherSuperOfStored.getSubClasses(), hasItem(stored)); assertEvents(events, expected); } @Test public void mergeWithInitializedNewSuperclassReferenceReferredTypeNotThere() throws Exception { String fqn = "class"; String hash = "hash"; String hashStored = "hashStored"; int modifiers = 0; ClassType c = new ClassType(fqn, hash, modifiers); String fqnSuper = "fqnSuper"; ClassType s = new ClassType(fqnSuper); c.addSuperClass(s); ClassType stored = new ClassType(fqn, hashStored, modifiers); String fqnAnotherSuperOfStored = "anothersuperofstored"; ClassType anotherSuperOfStored = new ClassType(fqnAnotherSuperOfStored); stored.addSuperClass(anotherSuperOfStored); when(lookup.findByFQN(fqn)).thenReturn(stored); when(lookup.findByFQN(fqnSuper)).thenReturn(null); when(lookup.findByFQN(fqnAnotherSuperOfStored)).thenReturn(anotherSuperOfStored); service.lookup = lookup; Events events = service.merge(c); Events expected = new Events(); expected.addEvent(new NodeEvent(c, NodeEventType.CHANGED, NodeEventDetails.HASH_ADDED)); expected.addEvent(new NodeEvent(s, NodeEventType.NEW, NodeEventDetails.NOT_INITIALIZED)); expected.addEvent(new ReferenceEvent(stored, s, ReferenceType.SUPERCLASS)); // assert bidirectional assertThat(stored.getSuperClasses().size(), is(2)); assertThat(stored.getSuperClasses(), hasItem(s)); assertThat(stored.getSuperClasses(), hasItem(anotherSuperOfStored)); assertThat(s.getSubClasses(), hasItem(stored)); assertThat(anotherSuperOfStored.getSubClasses(), hasItem(stored)); assertEvents(events, expected); } @Test public void mergeWithInitializedExistingSuperclassReferenceReferredTypeThere() throws Exception { String fqn = "class"; String hash = "hash"; String hashStored = "hashStored"; int modifiers = 0; ClassType c = new ClassType(fqn, hash, modifiers); String fqnSuper = "fqnSuper"; ClassType s = new ClassType(fqnSuper); c.addSuperClass(s); ClassType stored = new ClassType(fqn, hashStored, modifiers); stored.addSuperClass(s); when(lookup.findByFQN(fqn)).thenReturn(stored); when(lookup.findByFQN(fqnSuper)).thenReturn(null); service.lookup = lookup; Events events = service.merge(c); Events expected = new Events(); expected.addEvent(new NodeEvent(c, NodeEventType.CHANGED, NodeEventDetails.HASH_ADDED)); // assert bidirectional assertThat(stored.getSuperClasses().size(), is(1)); assertThat(stored.getSuperClasses(), hasItem(s)); assertThat(s.getSubClasses(), hasItem(stored)); assertEvents(events, expected); } @Test public void mergeWithNotInitializedNewSuperclassReferenceReferredTypeThere() throws Exception { String fqn = "class"; String hash = "hash"; int modifiers = 0; ClassType c = new ClassType(fqn, hash, modifiers); String fqnSuper = "fqnSuper"; ClassType s = new ClassType(fqnSuper); c.addSuperClass(s); ClassType stored = new ClassType(fqn); when(lookup.findByFQN(fqn)).thenReturn(stored); when(lookup.findByFQN(fqnSuper)).thenReturn(s); service.lookup = lookup; Events events = service.merge(c); Events expected = new Events(); expected.addEvent(new NodeEvent(stored, NodeEventType.CHANGED, NodeEventDetails.INITIALIZED)); expected.addEvent(new ReferenceEvent(stored, s, ReferenceType.SUPERCLASS)); // assert bidirectional assertThat(stored.getSuperClasses().size(), is(1)); assertThat(stored.getSuperClasses(), hasItem(s)); assertThat(s.getSubClasses(), hasItem(stored)); assertEvents(events, expected); } @Test public void mergeWithNotInitializedNewSuperclassReferenceReferredTypeNotThere() throws Exception { String fqn = "class"; String hash = "hash"; int modifiers = 0; ClassType c = new ClassType(fqn, hash, modifiers); String fqnSuper = "fqnSuper"; ClassType s = new ClassType(fqnSuper); c.addSuperClass(s); ClassType stored = new ClassType(fqn); when(lookup.findByFQN(fqn)).thenReturn(stored); when(lookup.findByFQN(fqnSuper)).thenReturn(null); service.lookup = lookup; Events events = service.merge(c); Events expected = new Events(); expected.addEvent(new NodeEvent(stored, NodeEventType.CHANGED, NodeEventDetails.INITIALIZED)); expected.addEvent(new NodeEvent(s, NodeEventType.NEW, NodeEventDetails.NOT_INITIALIZED)); expected.addEvent(new ReferenceEvent(stored, s, ReferenceType.SUPERCLASS)); // assert bidirectional assertThat(stored.getSuperClasses().size(), is(1)); assertThat(stored.getSuperClasses(), hasItem(s)); assertThat(s.getSubClasses(), hasItem(stored)); assertEvents(events, expected); } @Test public void addNewSuperclassReferenceReferredTypeThere() throws Exception { String fqn = "class"; String hash = "hash"; int modifiers = 0; ClassType c = new ClassType(fqn, hash, modifiers); String fqnSuper = "fqnSuper"; ClassType s = new ClassType(fqnSuper); c.addSuperClass(s); when(lookup.findByFQN(fqn)).thenReturn(null); when(lookup.findByFQN(fqnSuper)).thenReturn(s); service.lookup = lookup; Events events = service.merge(c); Events expected = new Events(); expected.addEvent(new NodeEvent(c, NodeEventType.NEW, NodeEventDetails.INITIALIZED)); expected.addEvent(new ReferenceEvent(c, s, ReferenceType.SUPERCLASS)); // assert bidirectional assertThat(c.getSuperClasses().size(), is(1)); assertThat(c.getSuperClasses(), hasItem(s)); assertThat(s.getSubClasses(), hasItem(c)); assertEvents(events, expected); } @Test public void addNewSuperclassReferenceReferredTypeNotThere() throws Exception { String fqn = "class"; String hash = "hash"; int modifiers = 0; ClassType c = new ClassType(fqn, hash, modifiers); String fqnSuper = "fqnSuper"; ClassType s = new ClassType(fqnSuper); c.addSuperClass(s); when(lookup.findByFQN(fqn)).thenReturn(null); when(lookup.findByFQN(fqnSuper)).thenReturn(null); service.lookup = lookup; Events events = service.merge(c); Events expected = new Events(); expected.addEvent(new NodeEvent(c, NodeEventType.NEW, NodeEventDetails.INITIALIZED)); expected.addEvent(new NodeEvent(s, NodeEventType.NEW, NodeEventDetails.NOT_INITIALIZED)); expected.addEvent(new ReferenceEvent(c, s, ReferenceType.SUPERCLASS)); // assert bidirectional assertThat(c.getSuperClasses().size(), is(1)); assertThat(c.getSuperClasses(), hasItem(s)); assertThat(s.getSubClasses(), hasItem(c)); assertEvents(events, expected); } // // Super interface // @Test public void mergeWithInitializedNewSuperInterfaceReferredTypeThere() throws Exception { String fqn = "class"; String hash = "hash"; String hashStored = "hashStored"; int modifiers = 0; InterfaceType c = new InterfaceType(fqn, hash, modifiers); String fqnSuper = "fqnSuper"; InterfaceType s = new InterfaceType(fqnSuper); c.addSuperInterface(s); InterfaceType stored = new InterfaceType(fqn, hashStored, modifiers); String fqnAnotherSuperOfStored = "anothersuperofstored"; InterfaceType anotherSuperOfStored = new InterfaceType(fqnAnotherSuperOfStored); stored.addSuperInterface(anotherSuperOfStored); when(lookup.findByFQN(fqn)).thenReturn(stored); when(lookup.findByFQN(fqnSuper)).thenReturn(s); when(lookup.findByFQN(fqnAnotherSuperOfStored)).thenReturn(anotherSuperOfStored); service.lookup = lookup; Events events = service.merge(c); Events expected = new Events(); expected.addEvent(new NodeEvent(stored, NodeEventType.CHANGED, NodeEventDetails.HASH_ADDED)); expected.addEvent(new ReferenceEvent(stored, s, ReferenceType.SUPERINTERFACE)); // assert bidirectional assertThat(stored.getSuperInterfaces().size(), is(2)); assertThat(stored.getSuperInterfaces(), hasItem(s)); assertThat(stored.getSuperInterfaces(), hasItem(anotherSuperOfStored)); assertThat(s.getSubInterfaces(), hasItem(stored)); assertThat(anotherSuperOfStored.getSubInterfaces(), hasItem(stored)); assertEvents(events, expected); } @Test public void mergeWithInitializedNewSuperInterfaceReferredTypeNotThere() throws Exception { String fqn = "class"; String hash = "hash"; String hashStored = "hashStored"; int modifiers = 0; InterfaceType c = new InterfaceType(fqn, hash, modifiers); String fqnSuper = "fqnSuper"; InterfaceType s = new InterfaceType(fqnSuper); c.addSuperInterface(s); InterfaceType stored = new InterfaceType(fqn, hashStored, modifiers); String fqnAnotherSuperOfStored = "anothersuperofstored"; InterfaceType anotherSuperOfStored = new InterfaceType(fqnAnotherSuperOfStored); stored.addSuperInterface(anotherSuperOfStored); when(lookup.findByFQN(fqn)).thenReturn(stored); when(lookup.findByFQN(fqnSuper)).thenReturn(null); when(lookup.findByFQN(fqnAnotherSuperOfStored)).thenReturn(anotherSuperOfStored); service.lookup = lookup; Events events = service.merge(c); Events expected = new Events(); expected.addEvent(new NodeEvent(stored, NodeEventType.CHANGED, NodeEventDetails.HASH_ADDED)); expected.addEvent(new NodeEvent(s, NodeEventType.NEW, NodeEventDetails.NOT_INITIALIZED)); expected.addEvent(new ReferenceEvent(stored, s, ReferenceType.SUPERINTERFACE)); // assert bidirectional assertThat(stored.getSuperInterfaces().size(), is(2)); assertThat(stored.getSuperInterfaces(), hasItem(s)); assertThat(stored.getSuperInterfaces(), hasItem(anotherSuperOfStored)); assertThat(s.getSubInterfaces(), hasItem(stored)); assertThat(anotherSuperOfStored.getSubInterfaces(), hasItem(stored)); assertEvents(events, expected); } @Test public void mergeWithInitializedExistingSuperInterfaceReferredTypeThere() throws Exception { String fqn = "class"; String hash = "hash"; String hashStored = "hashStored"; int modifiers = 0; InterfaceType c = new InterfaceType(fqn, hash, modifiers); String fqnSuper = "fqnSuper"; InterfaceType s = new InterfaceType(fqnSuper); c.addSuperInterface(s); InterfaceType stored = new InterfaceType(fqn, hashStored, modifiers); String fqnAnotherSuperOfStored = "anothersuperofstored"; InterfaceType anotherSuperOfStored = new InterfaceType(fqnAnotherSuperOfStored); stored.addSuperInterface(anotherSuperOfStored); stored.addSuperInterface(s); when(lookup.findByFQN(fqn)).thenReturn(stored); when(lookup.findByFQN(fqnSuper)).thenReturn(s); when(lookup.findByFQN(fqnAnotherSuperOfStored)).thenReturn(anotherSuperOfStored); service.lookup = lookup; Events events = service.merge(c); Events expected = new Events(); expected.addEvent(new NodeEvent(stored, NodeEventType.CHANGED, NodeEventDetails.HASH_ADDED)); // assert bidirectional assertThat(stored.getSuperInterfaces().size(), is(2)); assertThat(stored.getSuperInterfaces(), hasItem(s)); assertThat(stored.getSuperInterfaces(), hasItem(anotherSuperOfStored)); assertThat(s.getSubInterfaces(), hasItem(stored)); assertThat(anotherSuperOfStored.getSubInterfaces(), hasItem(stored)); assertEvents(events, expected); } @Test public void mergeWithNotInitializedNewSuperInterfaceReferredTypeThere() throws Exception { String fqn = "class"; String hash = "hash"; int modifiers = 0; InterfaceType c = new InterfaceType(fqn, hash, modifiers); String fqnSuper = "fqnSuper"; InterfaceType s = new InterfaceType(fqnSuper); c.addSuperInterface(s); InterfaceType stored = new InterfaceType(fqn); when(lookup.findByFQN(fqn)).thenReturn(stored); when(lookup.findByFQN(fqnSuper)).thenReturn(s); service.lookup = lookup; Events events = service.merge(c); Events expected = new Events(); expected.addEvent(new NodeEvent(stored, NodeEventType.CHANGED, NodeEventDetails.INITIALIZED)); expected.addEvent(new ReferenceEvent(stored, s, ReferenceType.SUPERINTERFACE)); // assert bidirectional assertThat(stored.getSuperInterfaces().size(), is(1)); assertThat(stored.getSuperInterfaces(), hasItem(s)); assertThat(s.getSubInterfaces(), hasItem(stored)); assertEvents(events, expected); } @Test public void mergeWithNotInitializedNewSuperInterfaceReferredTypeNotThere() throws Exception { String fqn = "class"; String hash = "hash"; int modifiers = 0; InterfaceType c = new InterfaceType(fqn, hash, modifiers); String fqnSuper = "fqnSuper"; InterfaceType s = new InterfaceType(fqnSuper); c.addSuperInterface(s); InterfaceType stored = new InterfaceType(fqn); when(lookup.findByFQN(fqn)).thenReturn(stored); when(lookup.findByFQN(fqnSuper)).thenReturn(null); service.lookup = lookup; Events events = service.merge(c); Events expected = new Events(); expected.addEvent(new NodeEvent(stored, NodeEventType.CHANGED, NodeEventDetails.INITIALIZED)); expected.addEvent(new NodeEvent(s, NodeEventType.NEW, NodeEventDetails.NOT_INITIALIZED)); expected.addEvent(new ReferenceEvent(stored, s, ReferenceType.SUPERINTERFACE)); // assert bidirectional assertThat(stored.getSuperInterfaces().size(), is(1)); assertThat(stored.getSuperInterfaces(), hasItem(s)); assertThat(s.getSubInterfaces(), hasItem(stored)); assertEvents(events, expected); } @Test public void addNewSuperInterfaceReferredTypeThere() throws Exception { String fqn = "class"; String hash = "hash"; int modifiers = 0; InterfaceType c = new InterfaceType(fqn, hash, modifiers); String fqnSuper = "fqnSuper"; InterfaceType s = new InterfaceType(fqnSuper); c.addSuperInterface(s); when(lookup.findByFQN(fqnSuper)).thenReturn(s); service.lookup = lookup; Events events = service.merge(c); Events expected = new Events(); expected.addEvent(new NodeEvent(c, NodeEventType.NEW, NodeEventDetails.INITIALIZED)); expected.addEvent(new ReferenceEvent(c, s, ReferenceType.SUPERINTERFACE)); // assert bidirectional assertThat(c.getSuperInterfaces().size(), is(1)); assertThat(c.getSuperInterfaces(), hasItem(s)); assertThat(s.getSubInterfaces(), hasItem(c)); assertEvents(events, expected); } @Test public void addNewSuperInterfaceReferredTypeNotThere() throws Exception { String fqn = "class"; String hash = "hash"; int modifiers = 0; InterfaceType c = new InterfaceType(fqn, hash, modifiers); String fqnSuper = "fqnSuper"; InterfaceType s = new InterfaceType(fqnSuper); c.addSuperInterface(s); when(lookup.findByFQN(fqnSuper)).thenReturn(s); service.lookup = lookup; Events events = service.merge(c); Events expected = new Events(); expected.addEvent(new NodeEvent(c, NodeEventType.NEW, NodeEventDetails.INITIALIZED)); expected.addEvent(new ReferenceEvent(c, s, ReferenceType.SUPERINTERFACE)); // assert bidirectional assertThat(c.getSuperInterfaces().size(), is(1)); assertThat(c.getSuperInterfaces(), hasItem(s)); assertThat(s.getSubInterfaces(), hasItem(c)); assertEvents(events, expected); } // // Annotation // @Test(dataProvider = "types") public void mergeWithInitializedNewAnnotationReferredTypeThere(Class<? extends Type> clazz) throws Exception { String fqn = "class"; String hash = "hash"; String hashStored = "hashStored"; int modifiers = 0; TypeWithAnnotations c = construct(clazz, fqn, hash, modifiers); String annotationFQN = "fqnAnnotation"; AnnotationType annotation = new AnnotationType(annotationFQN); c.addAnnotation(annotation); TypeWithAnnotations stored = construct(clazz, fqn, hashStored, modifiers); String fqnAnnotherAnnotation = "fqnAnnotherAnnotation"; AnnotationType annotherAnnotation = new AnnotationType(fqnAnnotherAnnotation); stored.addAnnotation(annotherAnnotation); // we assume that nothing is there yet when(lookup.findByFQN(fqn)).thenReturn((Type) stored); when(lookup.findByFQN(annotationFQN)).thenReturn(annotation); service.lookup = lookup; Events events = service.merge((Type) c); // assert bidirectional assertThat(stored.getAnnotations().size(), is(2)); assertThat(stored.getAnnotations(), hasItem(annotation)); assertThat(stored.getAnnotations(), hasItem(annotherAnnotation)); assertThat(annotation.getAnnotatedTypes(), hasItem(stored)); assertThat(annotherAnnotation.getAnnotatedTypes(), hasItem(stored)); Events expected = new Events(); expected.addEvent(new NodeEvent((Type) stored, NodeEventType.CHANGED, NodeEventDetails.HASH_ADDED)); expected.addEvent(new ReferenceEvent((Type) stored, annotation, ReferenceType.ANNOTATION)); assertEvents(events, expected); } @Test(dataProvider = "types") public void mergeWithInitializedNewAnnotationReferredTypeNotThere(Class<? extends Type> clazz) throws Exception { String fqn = "class"; String hash = "hash"; String hashStored = "hashStored"; int modifiers = 0; TypeWithAnnotations c = construct(clazz, fqn, hash, modifiers); String annotationFQN = "fqnAnnotation"; AnnotationType annotation = new AnnotationType(annotationFQN); c.addAnnotation(annotation); TypeWithAnnotations stored = construct(clazz, fqn, hashStored, modifiers); String fqnAnnotherAnnotation = "fqnAnnotherAnnotation"; AnnotationType annotherAnnotation = new AnnotationType(fqnAnnotherAnnotation); stored.addAnnotation(annotherAnnotation); // we assume that nothing is there yet when(lookup.findByFQN(fqn)).thenReturn((Type) stored); when(lookup.findByFQN(annotationFQN)).thenReturn(null); service.lookup = lookup; Events events = service.merge((Type) c); // assert bidirectional assertThat(stored.getAnnotations().size(), is(2)); assertThat(stored.getAnnotations(), hasItem(annotation)); assertThat(stored.getAnnotations(), hasItem(annotherAnnotation)); assertThat(annotation.getAnnotatedTypes(), hasItem(stored)); assertThat(annotherAnnotation.getAnnotatedTypes(), hasItem(stored)); Events expected = new Events(); expected.addEvent(new NodeEvent((Type) stored, NodeEventType.CHANGED, NodeEventDetails.HASH_ADDED)); expected.addEvent(new NodeEvent(annotation, NodeEventType.NEW, NodeEventDetails.NOT_INITIALIZED)); expected.addEvent(new ReferenceEvent((Type) stored, annotation, ReferenceType.ANNOTATION)); assertEvents(events, expected); } @Test(dataProvider = "types") public void mergeWithInitializedExistingAnnotationReferredTypeThere(Class<? extends Type> clazz) throws Exception { String fqn = "class"; String hash = "hash"; String hashStored = "hashStored"; int modifiers = 0; TypeWithAnnotations c = construct(clazz, fqn, hash, modifiers); String annotationFQN = "fqnAnnotation"; AnnotationType annotation = new AnnotationType(annotationFQN); c.addAnnotation(annotation); TypeWithAnnotations stored = construct(clazz, fqn, hashStored, modifiers); String fqnAnnotherAnnotation = "fqnAnnotherAnnotation"; AnnotationType annotherAnnotation = new AnnotationType(fqnAnnotherAnnotation); stored.addAnnotation(annotherAnnotation); stored.addAnnotation(annotation); // we assume that nothing is there yet when(lookup.findByFQN(fqn)).thenReturn((Type) stored); when(lookup.findByFQN(annotationFQN)).thenReturn(annotation); service.lookup = lookup; Events events = service.merge((Type) c); // assert bidirectional assertThat(stored.getAnnotations().size(), is(2)); assertThat(stored.getAnnotations(), hasItem(annotation)); assertThat(stored.getAnnotations(), hasItem(annotherAnnotation)); assertThat(annotation.getAnnotatedTypes(), hasItem(stored)); assertThat(annotherAnnotation.getAnnotatedTypes(), hasItem(stored)); Events expected = new Events(); expected.addEvent(new NodeEvent((Type) stored, NodeEventType.CHANGED, NodeEventDetails.HASH_ADDED)); assertEvents(events, expected); } @Test(dataProvider = "types") public void mergeWithNotInitializedNewAnnotationReferredTypeThere(Class<? extends Type> clazz) throws Exception { String fqn = "class"; String hash = "hash"; int modifiers = 0; TypeWithAnnotations c = construct(clazz, fqn, hash, modifiers); String annotationFQN = "fqnAnnotation"; AnnotationType annotation = new AnnotationType(annotationFQN); c.addAnnotation(annotation); TypeWithAnnotations stored = construct(clazz, fqn); // we assume that nothing is there yet when(lookup.findByFQN(fqn)).thenReturn((Type) stored); when(lookup.findByFQN(annotationFQN)).thenReturn(annotation); service.lookup = lookup; Events events = service.merge((Type) c); // assert bidirectional assertThat(stored.getAnnotations().size(), is(1)); assertThat(stored.getAnnotations(), hasItem(annotation)); assertThat(annotation.getAnnotatedTypes(), hasItem(stored)); Events expected = new Events(); expected.addEvent(new NodeEvent((Type) stored, NodeEventType.CHANGED, NodeEventDetails.INITIALIZED)); expected.addEvent(new ReferenceEvent((Type) stored, annotation, ReferenceType.ANNOTATION)); assertEvents(events, expected); } @Test(dataProvider = "types") public void mergeWithNotInitializedNewAnnotationReferredTypeNotThere(Class<? extends Type> clazz) throws Exception { String fqn = "class"; String hash = "hash"; int modifiers = 0; TypeWithAnnotations c = construct(clazz, fqn, hash, modifiers); String annotationFQN = "fqnAnnotation"; AnnotationType annotation = new AnnotationType(annotationFQN); c.addAnnotation(annotation); TypeWithAnnotations stored = construct(clazz, fqn); // we assume that nothing is there yet when(lookup.findByFQN(fqn)).thenReturn((Type) stored); when(lookup.findByFQN(annotationFQN)).thenReturn(null); service.lookup = lookup; Events events = service.merge((Type) c); // assert bidirectional assertThat(stored.getAnnotations().size(), is(1)); assertThat(stored.getAnnotations(), hasItem(annotation)); assertThat(annotation.getAnnotatedTypes(), hasItem(stored)); Events expected = new Events(); expected.addEvent(new NodeEvent((Type) stored, NodeEventType.CHANGED, NodeEventDetails.INITIALIZED)); expected.addEvent(new NodeEvent(annotation, NodeEventType.NEW, NodeEventDetails.NOT_INITIALIZED)); expected.addEvent(new ReferenceEvent((Type) stored, annotation, ReferenceType.ANNOTATION)); assertEvents(events, expected); } @Test(dataProvider = "types") public void addNewAnnotationReferredTypeThere(Class<? extends Type> clazz) throws Exception { String fqn = "class"; String hash = "hash"; int modifiers = 0; TypeWithAnnotations c = construct(clazz, fqn, hash, modifiers); String annotationFQN = "fqnAnnotation"; AnnotationType annotation = new AnnotationType(annotationFQN); c.addAnnotation(annotation); // we assume that nothing is there yet when(lookup.findByFQN(fqn)).thenReturn((Type) null); when(lookup.findByFQN(annotationFQN)).thenReturn(annotation); service.lookup = lookup; Events events = service.merge((Type) c); // assert bidirectional assertThat(c.getAnnotations().size(), is(1)); assertThat(c.getAnnotations(), hasItem(annotation)); assertThat(annotation.getAnnotatedTypes(), hasItem(c)); Events expected = new Events(); expected.addEvent(new NodeEvent((Type) c, NodeEventType.NEW, NodeEventDetails.INITIALIZED)); expected.addEvent(new ReferenceEvent((Type) c, annotation, ReferenceType.ANNOTATION)); assertEvents(events, expected); } @Test(dataProvider = "types") public void addNewAnnotationReferredTypeNotThere(Class<? extends Type> clazz) throws Exception { String fqn = "class"; String hash = "hash"; int modifiers = 0; TypeWithAnnotations c = construct(clazz, fqn, hash, modifiers); String annotationFQN = "fqnAnnotation"; AnnotationType annotation = new AnnotationType(annotationFQN); c.addAnnotation(annotation); // we assume that nothing is there yet when(lookup.findByFQN(fqn)).thenReturn((Type) null); when(lookup.findByFQN(annotationFQN)).thenReturn(null); service.lookup = lookup; Events events = service.merge((Type) c); // assert bidirectional assertThat(c.getAnnotations().size(), is(1)); assertThat(c.getAnnotations(), hasItem(annotation)); assertThat(annotation.getAnnotatedTypes(), hasItem(c)); Events expected = new Events(); expected.addEvent(new NodeEvent((Type) c, NodeEventType.NEW, NodeEventDetails.INITIALIZED)); expected.addEvent(new NodeEvent(annotation, NodeEventType.NEW, NodeEventDetails.NOT_INITIALIZED)); expected.addEvent(new ReferenceEvent((Type) c, annotation, ReferenceType.ANNOTATION)); assertEvents(events, expected); } // // Realized interface // @Test public void mergeWithInitializedNewRealizedInterfaceReferredTypeThere() throws Exception { String fqn = "class"; String hash = "hash"; String hashStored = "hashStored"; int modifiers = 0; ClassType c = new ClassType(fqn, hash, modifiers); // note that the interface can only be non-initialized String fqnSuper = "fqnSuper"; InterfaceType s = new InterfaceType(fqnSuper); c.addInterface(s); ClassType stored = new ClassType(fqn, hashStored, modifiers); String fqnAnotherInterface = "fqnAnotherInterface"; InterfaceType anotherInterface = new InterfaceType(fqnAnotherInterface); stored.addInterface(anotherInterface); // we assume that nothing is there yet when(lookup.findByFQN(fqn)).thenReturn(stored); when(lookup.findByFQN(fqnSuper)).thenReturn(s); service.lookup = lookup; Events events = service.merge(c); Events expected = new Events(); expected.addEvent(new NodeEvent(stored, NodeEventType.CHANGED, NodeEventDetails.HASH_ADDED)); expected.addEvent(new ReferenceEvent(stored, s, ReferenceType.REALIZE_INTERFACE)); // assert bidirectional assertThat(stored.getRealizedInterfaces().size(), is(2)); assertThat(stored.getRealizedInterfaces(), hasItem(s)); assertThat(stored.getRealizedInterfaces(), hasItem(anotherInterface)); assertThat(s.getRealizingClasses(), hasItem(stored)); assertThat(anotherInterface.getRealizingClasses(), hasItem(stored)); assertEvents(events, expected); } @Test public void mergeWithInitializedNewRealizedInterfaceReferredTypeNotThere() throws Exception { String fqn = "class"; String hash = "hash"; String hashStored = "hashStored"; int modifiers = 0; ClassType c = new ClassType(fqn, hash, modifiers); // note that the interface can only be non-initialized String fqnSuper = "fqnSuper"; InterfaceType s = new InterfaceType(fqnSuper); c.addInterface(s); ClassType stored = new ClassType(fqn, hashStored, modifiers); String fqnAnotherInterface = "fqnAnotherInterface"; InterfaceType anotherInterface = new InterfaceType(fqnAnotherInterface); stored.addInterface(anotherInterface); // we assume that nothing is there yet when(lookup.findByFQN(fqn)).thenReturn(stored); when(lookup.findByFQN(fqnSuper)).thenReturn(null); service.lookup = lookup; Events events = service.merge(c); Events expected = new Events(); expected.addEvent(new NodeEvent(stored, NodeEventType.CHANGED, NodeEventDetails.HASH_ADDED)); expected.addEvent(new NodeEvent(s, NodeEventType.NEW, NodeEventDetails.NOT_INITIALIZED)); expected.addEvent(new ReferenceEvent(stored, s, ReferenceType.REALIZE_INTERFACE)); // assert bidirectional assertThat(stored.getRealizedInterfaces().size(), is(2)); assertThat(stored.getRealizedInterfaces(), hasItem(s)); assertThat(stored.getRealizedInterfaces(), hasItem(anotherInterface)); assertThat(s.getRealizingClasses(), hasItem(stored)); assertThat(anotherInterface.getRealizingClasses(), hasItem(stored)); assertEvents(events, expected); } @Test public void mergeWithInitializedExistingRealizedInterfaceReferredTypeThere() throws Exception { String fqn = "class"; String hash = "hash"; String hashStored = "hashStored"; int modifiers = 0; ClassType c = new ClassType(fqn, hash, modifiers); // note that the interface can only be non-initialized String fqnSuper = "fqnSuper"; InterfaceType s = new InterfaceType(fqnSuper); c.addInterface(s); ClassType stored = new ClassType(fqn, hashStored, modifiers); String fqnAnotherInterface = "fqnAnotherInterface"; InterfaceType anotherInterface = new InterfaceType(fqnAnotherInterface); stored.addInterface(anotherInterface); stored.addInterface(s); // we assume that nothing is there yet when(lookup.findByFQN(fqn)).thenReturn(stored); when(lookup.findByFQN(fqnSuper)).thenReturn(s); service.lookup = lookup; Events events = service.merge(c); Events expected = new Events(); expected.addEvent(new NodeEvent(stored, NodeEventType.CHANGED, NodeEventDetails.HASH_ADDED)); // assert bidirectional assertThat(stored.getRealizedInterfaces().size(), is(2)); assertThat(stored.getRealizedInterfaces(), hasItem(s)); assertThat(stored.getRealizedInterfaces(), hasItem(anotherInterface)); assertThat(s.getRealizingClasses(), hasItem(stored)); assertThat(anotherInterface.getRealizingClasses(), hasItem(stored)); assertEvents(events, expected); } @Test public void mergeWithNotInitializedNewRealizedInterfaceReferredTypeThere() throws Exception { String fqn = "class"; String hash = "hash"; int modifiers = 0; ClassType c = new ClassType(fqn, hash, modifiers); // note that the interface can only be non-initialized String fqnSuper = "fqnSuper"; InterfaceType s = new InterfaceType(fqnSuper); c.addInterface(s); ClassType stored = new ClassType(fqn); // we assume that nothing is there yet when(lookup.findByFQN(fqn)).thenReturn(stored); when(lookup.findByFQN(fqnSuper)).thenReturn(s); service.lookup = lookup; Events events = service.merge(c); Events expected = new Events(); expected.addEvent(new NodeEvent(stored, NodeEventType.CHANGED, NodeEventDetails.INITIALIZED)); expected.addEvent(new ReferenceEvent(stored, s, ReferenceType.REALIZE_INTERFACE)); // assert bidirectional assertThat(stored.getRealizedInterfaces().size(), is(1)); assertThat(stored.getRealizedInterfaces(), hasItem(s)); assertThat(s.getRealizingClasses(), hasItem(stored)); assertEvents(events, expected); } @Test public void mergeWithNotInitializedNewRealizedInterfaceReferredTypeNotThere() throws Exception { String fqn = "class"; String hash = "hash"; int modifiers = 0; ClassType c = new ClassType(fqn, hash, modifiers); // note that the interface can only be non-initialized String fqnSuper = "fqnSuper"; InterfaceType s = new InterfaceType(fqnSuper); c.addInterface(s); ClassType stored = new ClassType(fqn); // we assume that nothing is there yet when(lookup.findByFQN(fqn)).thenReturn(stored); when(lookup.findByFQN(fqnSuper)).thenReturn(null); service.lookup = lookup; Events events = service.merge(c); Events expected = new Events(); expected.addEvent(new NodeEvent(stored, NodeEventType.CHANGED, NodeEventDetails.INITIALIZED)); expected.addEvent(new NodeEvent(s, NodeEventType.NEW, NodeEventDetails.NOT_INITIALIZED)); expected.addEvent(new ReferenceEvent(stored, s, ReferenceType.REALIZE_INTERFACE)); // assert bidirectional assertThat(stored.getRealizedInterfaces().size(), is(1)); assertThat(stored.getRealizedInterfaces(), hasItem(s)); assertThat(s.getRealizingClasses(), hasItem(stored)); assertEvents(events, expected); } @Test public void addNewRealizedInterfaceReferredTypeThere() throws Exception { String fqn = "class"; String hash = "hash"; int modifiers = 0; ClassType c = new ClassType(fqn, hash, modifiers); // note that the interface can only be non-initialized String fqnSuper = "fqnSuper"; InterfaceType s = new InterfaceType(fqnSuper); c.addInterface(s); // we assume that nothing is there yet when(lookup.findByFQN(fqn)).thenReturn(null); when(lookup.findByFQN(fqnSuper)).thenReturn(s); service.lookup = lookup; Events events = service.merge(c); Events expected = new Events(); expected.addEvent(new NodeEvent(c, NodeEventType.NEW, NodeEventDetails.INITIALIZED)); expected.addEvent(new ReferenceEvent(c, s, ReferenceType.REALIZE_INTERFACE)); // assert bidirectional assertThat(c.getRealizedInterfaces().size(), is(1)); assertThat(c.getRealizedInterfaces(), hasItem(s)); assertThat(s.getRealizingClasses(), hasItem(c)); assertEvents(events, expected); } @Test public void addNewRealizedInterfaceReferredTypeNotThere() throws Exception { String fqn = "class"; String hash = "hash"; int modifiers = 0; ClassType c = new ClassType(fqn, hash, modifiers); // note that the interface can only be non-initialized String fqnSuper = "fqnSuper"; InterfaceType s = new InterfaceType(fqnSuper); c.addInterface(s); // we assume that nothing is there yet when(lookup.findByFQN(fqn)).thenReturn(null); when(lookup.findByFQN(fqnSuper)).thenReturn(null); service.lookup = lookup; Events events = service.merge(c); Events expected = new Events(); expected.addEvent(new NodeEvent(c, NodeEventType.NEW, NodeEventDetails.INITIALIZED)); expected.addEvent(new NodeEvent(s, NodeEventType.NEW, NodeEventDetails.NOT_INITIALIZED)); expected.addEvent(new ReferenceEvent(c, s, ReferenceType.REALIZE_INTERFACE)); // assert bidirectional assertThat(c.getRealizedInterfaces().size(), is(1)); assertThat(c.getRealizedInterfaces(), hasItem(s)); assertThat(s.getRealizingClasses(), hasItem(c)); assertEvents(events, expected); } @Test public void handleTwoLevelInstanceAreNotSupported() throws Exception { // that is a class, that has a superclass, that has a superclass String[] fqns = new String[] { "1", "2", "3" }; int[] modifiers = new int[] { 0, 1, 2 }; String[] hashes = new String[] { "a", "b", "c" }; ClassType base = new ClassType(fqns[0], hashes[0], modifiers[0]); ClassType firstSuper = new ClassType(fqns[1], hashes[1], modifiers[1]); ClassType secondSuper = new ClassType(fqns[2], hashes[2], modifiers[2]); base.addSuperClass(firstSuper); firstSuper.addSuperClass(secondSuper); // we assume that nothing is there yet when(lookup.findByFQN(fqns[0])).thenReturn(null); when(lookup.findByFQN(fqns[1])).thenReturn(null); when(lookup.findByFQN(fqns[2])).thenReturn(null); service.lookup = lookup; Events events = service.merge(base); // We do not support multi-level structures! Events expected = new Events(); expected.addEvent(new NodeEvent(base, NodeEventType.NEW, NodeEventDetails.INITIALIZED)); expected.addEvent(new NodeEvent(firstSuper, NodeEventType.NEW, NodeEventDetails.NOT_INITIALIZED)); expected.addEvent(new ReferenceEvent(base, firstSuper, ReferenceType.SUPERCLASS)); assertEvents(events, expected); } // // handle types coming from the lookup to be replaced // @Test(dataProvider = "types") public void annotationReplacedWithInitialized(Class<? extends Type> clazz) throws Exception { String fqn = "class"; String hash = "hash"; int modifiers = 0; TypeWithAnnotations t = construct(clazz, fqn, hash, modifiers); AnnotationType a = new AnnotationType("annotation"); t.addAnnotation(a); AnnotationType initialized = new AnnotationType("annotation", hash, modifiers); when(lookup.findByFQN(fqn)).thenReturn(null); when(lookup.findByFQN("annotation")).thenReturn(initialized); service.lookup = lookup; service.merge((ImmutableType) t); // make sure not initialized reference is gone assertThat(t.getAnnotations(), hasSize(1)); assertThat(t.getAnnotations().iterator().next().isInitialized(), is(true)); } @Test public void superClassReplacedWithInitialized() throws Exception { String fqn = "class"; String hash = "hash"; int modifiers = 0; ClassType c = new ClassType(fqn, hash, modifiers); ClassType s = new ClassType("superClass"); c.addSuperClass(s); ClassType initialized = new ClassType("superClass", hash, modifiers); when(lookup.findByFQN(fqn)).thenReturn(null); when(lookup.findByFQN("superClass")).thenReturn(initialized); service.lookup = lookup; service.merge(c); // make sure not initialized reference is gone assertThat(c.getSuperClasses(), hasSize(1)); assertThat(c.getSuperClasses().iterator().next().isInitialized(), is(true)); } @Test public void realizedInterfacesReplacedWithInitialized() throws Exception { String fqn = "class"; String hash = "hash"; int modifiers = 0; ClassType c = new ClassType(fqn, hash, modifiers); InterfaceType i = new InterfaceType("interface"); c.addInterface(i); InterfaceType initialized = new InterfaceType("interface", hash, modifiers); when(lookup.findByFQN(fqn)).thenReturn(null); when(lookup.findByFQN("interface")).thenReturn(initialized); service.lookup = lookup; service.merge(c); // make sure not initialized reference is gone assertThat(c.getRealizedInterfaces(), hasSize(1)); assertThat(c.getRealizedInterfaces().iterator().next().isInitialized(), is(true)); } @Test public void superInterfacesReplacedWithInitialized() throws Exception { String fqn = "class"; String hash = "hash"; int modifiers = 0; InterfaceType i = new InterfaceType(fqn, hash, modifiers); InterfaceType s = new InterfaceType("interface"); i.addSuperInterface(s); InterfaceType initialized = new InterfaceType("interface", hash, modifiers); when(lookup.findByFQN(fqn)).thenReturn(null); when(lookup.findByFQN("interface")).thenReturn(initialized); service.lookup = lookup; service.merge(i); // make sure not initialized reference is gone assertThat(i.getSuperInterfaces(), hasSize(1)); assertThat(i.getSuperInterfaces().iterator().next().isInitialized(), is(true)); } @Test(dataProvider = "typesWithMethods") public void methodAnnotatedReplacedWithWithInitialized(Class<? extends Type> clazz) throws Exception { String fqn = "class"; String hash = "hash"; int modifiers = 0; TypeWithMethods t = (TypeWithMethods) construct(clazz, fqn, hash, modifiers); AnnotationType a = new AnnotationType("annotation"); MethodType m = build("name", modifiers, null, null, null, new HashSet<>(Arrays.asList(new AnnotationType[] { a }))); t.addMethod(m); AnnotationType initialized = new AnnotationType("annotation", hash, modifiers); when(lookup.findByFQN(fqn)).thenReturn(null); when(lookup.findByFQN("annotation")).thenReturn(initialized); service.lookup = lookup; service.merge(t); assertThat(m.getAnnotations(), hasSize(1)); assertThat(m.getAnnotations().iterator().next().isInitialized(), is(true)); } @Test(dataProvider = "typesWithMethods") public void methodExceptionReplacedWithWithInitialized(Class<? extends Type> clazz) throws Exception { String fqn = "class"; String hash = "hash"; int modifiers = 0; TypeWithMethods t = (TypeWithMethods) construct(clazz, fqn, hash, modifiers); ClassType e = new ClassType("exception"); MethodType m = build("name", modifiers, null, null, new HashSet<>(Arrays.asList(new ClassType[] { e })), null); t.addMethod(m); ClassType initialized = new ClassType("exception", hash, modifiers); when(lookup.findByFQN(fqn)).thenReturn(null); when(lookup.findByFQN("exception")).thenReturn(initialized); service.lookup = lookup; service.merge(t); assertThat(m.getExceptions(), hasSize(1)); assertThat(m.getExceptions().iterator().next().isInitialized(), is(true)); } // // handle invalid references that exist in the given element // @Test public void handleGivenSubclassIsNotSupported() throws Exception { String fqn = "class"; String hash = "hash"; int modifiers = 0; ClassType c = new ClassType(fqn, hash, modifiers); ClassType sub = new ClassType("something"); c.addSubclass(sub); // this merge will - as there is nothing there in the storage - use the given reference service.merge(c); // we have to ensure that the subclasses are not passed on assertThat(c.getSubClasses(), is(empty())); } @Test public void handleGivenSubclassIsNotSupportedForInitialized() throws Exception { String fqn = "class"; String hash = "hash"; String hashStored = "hashStored"; int modifiers = 0; ClassType c = new ClassType(fqn, hash, modifiers); ClassType sub = new ClassType("something"); c.addSubclass(sub); ClassType stored = new ClassType(fqn, hashStored, modifiers); when(lookup.findByFQN(fqn)).thenReturn(stored); service.lookup = lookup; service.merge(c); // we have to ensure that the subclasses are not passed on assertThat(stored.getSubClasses(), is(empty())); } @Test public void handleGivenSubInterfaceIsNotSupported() throws Exception { String fqn = "class"; String hash = "hash"; int modifiers = 0; InterfaceType i = new InterfaceType(fqn, hash, modifiers); InterfaceType sub = new InterfaceType("somthing"); i.addSubInterface(sub); // this merge will - as there is nothing there in the storage - use the given reference service.merge(i); // we have to ensure that the subclasses are not passed on assertThat(i.getSubInterfaces(), is(empty())); } @Test public void handleGivenSubInterfaceIsNotSupportedForInitialized() throws Exception { String fqn = "class"; String hash = "hash"; String hashStored = "hashStored"; int modifiers = 0; InterfaceType i = new InterfaceType(fqn, hash, modifiers); InterfaceType sub = new InterfaceType("somthing"); i.addSubInterface(sub); InterfaceType stored = new InterfaceType(fqn, hashStored, modifiers); when(lookup.findByFQN(fqn)).thenReturn(stored); service.lookup = lookup; // this merge will - as there is nothing there in the storage - use the given reference service.merge(i); // we have to ensure that the subclasses are not passed on assertThat(stored.getSubInterfaces(), is(empty())); } @Test(dataProvider = "types") public void handleGivenAnnotatedTypeIsNotSupported(Class<? extends Type> clazz) throws Exception { String fqn = "class"; String hash = "hash"; int modifiers = 0; AnnotationType a = new AnnotationType(fqn, hash, modifiers); TypeWithAnnotations t = construct(clazz, "some"); a.addAnnotatedType(t); // this merge will - as there is nothing there in the storage - use the given reference service.merge(a); // we have to ensure that the subclasses are not passed on assertThat(a.getAnnotatedTypes(), is(empty())); } @Test(dataProvider = "types") public void handleGivenAnnotatedTypeIsNotSupportedForInitialized(Class<? extends Type> clazz) throws Exception { String fqn = "class"; String hash = "hash"; String hashStored = "hashStored"; int modifiers = 0; AnnotationType a = new AnnotationType(fqn, hash, modifiers); TypeWithAnnotations t = construct(clazz, "some"); a.addAnnotatedType(t); AnnotationType stored = new AnnotationType(fqn, hashStored, modifiers); when(lookup.findByFQN(fqn)).thenReturn(stored); // this merge will - as there is nothing there in the storage - use the given reference service.merge(a); // we have to ensure that the subclasses are not passed on assertThat(stored.getAnnotatedTypes(), is(empty())); } @Test public void handleGivenReferredClassIsNotSupported() throws Exception { String fqn = "class"; String hash = "hash"; int modifiers = 0; InterfaceType i = new InterfaceType(fqn, hash, modifiers); ClassType c = new ClassType("something"); i.addRealizingClass(c); // InterfaceType stored = new InterfaceType(fqn, hashStored, modifiers); // when(lookup.findByFQN(fqn)).thenReturn(stored); service.merge(i); assertThat(i.getRealizingClasses(), is(empty())); } @Test public void handleGivenReferredClassIsNotSupportedForInitialized() throws Exception { String fqn = "class"; String hash = "hash"; String hashStored = "hashStored"; int modifiers = 0; InterfaceType i = new InterfaceType(fqn, hash, modifiers); ClassType c = new ClassType("something"); i.addRealizingClass(c); InterfaceType stored = new InterfaceType(fqn, hashStored, modifiers); when(lookup.findByFQN(fqn)).thenReturn(stored); service.merge(i); assertThat(stored.getRealizingClasses(), is(empty())); } @Test public void handleGivenMethodThrowingThisExceptionIsNotSupported() throws Exception { String fqn = "class"; String hash = "hash"; int modifiers = 0; ClassType c = new ClassType(fqn, hash, modifiers); MethodType m = build("aa", modifiers, null, null, null, null); c.addMethodThrowingException(m); service.merge(c); // we have to ensure that the subclasses are not passed on assertThat(c.getMethodsThrowingThisException(), is(empty())); } @Test public void handleGivenMethodThrowingThisExceptionIsNotSupportedForInitialized() throws Exception { String fqn = "class"; String hash = "hash"; String hashStored = "hashStored"; int modifiers = 0; ClassType c = new ClassType(fqn, hash, modifiers); MethodType m = build("aa", modifiers, null, null, null, null); c.addMethodThrowingException(m); ClassType stored = new ClassType(fqn, hashStored, modifiers); when(lookup.findByFQN(fqn)).thenReturn(stored); service.lookup = lookup; service.merge(c); // we have to ensure that the subclasses are not passed on assertThat(stored.getMethodsThrowingThisException(), is(empty())); } @Test public void addTypeThatChangedTheOriginalClassType() throws Exception { String fqn = "class"; String hash = "hash"; int modifiers = 0; ClassType classType = new ClassType(fqn, hash, modifiers); when(lookup.findByFQN(fqn)).thenReturn(classType); InterfaceType interfaceType = new InterfaceType("interface"); ClassType superClass = new ClassType("superclass"); MethodType methodType = new MethodType(); AnnotationType annotationType = new AnnotationType("annotation"); classType.addInterface(interfaceType); classType.addSuperClass(superClass); classType.addMethodThrowingException(methodType); classType.addAnnotation(annotationType); String hash2 = "hash2"; InterfaceType newType = new InterfaceType(fqn, hash2, modifiers); Events events = service.merge(newType); // ensure all references are cleared assertThat(interfaceType.getRealizingClasses(), is(empty())); assertThat(superClass.getSuperClasses(), is(empty())); assertThat(superClass.getSubClasses(), is(empty())); assertThat(annotationType.getAnnotatedTypes(), is(empty())); assertThat(methodType.getExceptions(), is(empty())); // assert events Events expected = new Events(); expected.addEvent(new NodeEvent(classType, NodeEventType.REMOVED, null)); expected.addEvent(new NodeEvent(newType, NodeEventType.NEW, NodeEventDetails.INITIALIZED)); assertEvents(events, expected); } @Test public void addTypeThatChangedTheOriginalInterfaceType() throws Exception { String fqn = "class"; String hash = "hash"; int modifiers = 0; InterfaceType interfaceType = new InterfaceType(fqn, hash, modifiers); when(lookup.findByFQN(fqn)).thenReturn(interfaceType); InterfaceType superInterfaceType = new InterfaceType("interface"); ClassType classType = new ClassType("class"); AnnotationType annotationType = new AnnotationType("annotation"); interfaceType.addSuperInterface(superInterfaceType); interfaceType.addRealizingClass(classType); interfaceType.addAnnotation(annotationType); String hash2 = "hash2"; ClassType newType = new ClassType(fqn, hash2, modifiers); Events events = service.merge(newType); // ensure all references are cleared assertThat(superInterfaceType.getSubInterfaces(), is(empty())); assertThat(superInterfaceType.getSuperInterfaces(), is(empty())); assertThat(classType.getRealizedInterfaces(), is(empty())); assertThat(annotationType.getAnnotatedTypes(), is(empty())); // assert events Events expected = new Events(); expected.addEvent(new NodeEvent(interfaceType, NodeEventType.REMOVED, null)); expected.addEvent(new NodeEvent(newType, NodeEventType.NEW, NodeEventDetails.INITIALIZED)); assertEvents(events, expected); } @Test public void addTypeThatChangedTheOriginalAnnotationType() throws Exception { String fqn = "class"; String hash = "hash"; int modifiers = 0; AnnotationType annotation = new AnnotationType(fqn, hash, modifiers); when(lookup.findByFQN(fqn)).thenReturn(annotation); InterfaceType interfaceType = new InterfaceType("interface"); ClassType classType = new ClassType("class"); AnnotationType annotationType = new AnnotationType("annotation"); annotation.addRealizingClass(classType); annotation.addAnnotatedType(interfaceType); annotation.addAnnotation(annotationType); String hash2 = "hash2"; ClassType newType = new ClassType(fqn, hash2, modifiers); Events events = service.merge(newType); // ensure all references are cleared assertThat(interfaceType.getAnnotations(), is(empty())); assertThat(classType.getAnnotations(), is(empty())); assertThat(annotationType.getAnnotatedTypes(), is(empty())); // assert events Events expected = new Events(); expected.addEvent(new NodeEvent(annotation, NodeEventType.REMOVED, null)); expected.addEvent(new NodeEvent(newType, NodeEventType.NEW, NodeEventDetails.INITIALIZED)); assertEvents(events, expected); } // ------------------------------------------------------------------------------------ // Method section - additional references // // facts // - methods always belongs to a type // - methods are always initialized, as it does not make sense to have an uninitialized // method // - methods are identified by their name and parameter list (but not their return value, // annotation, exception) // // for stuff that is not a reference, the checks are smaller // - mergeReturnTypeThere, mergeReturnTypeNotThere // - mergeModifierThere, mergeModifierNotThere // // base - given // init - new - mergeWithInitializedNewMethodReturnType // init - exist - mergeWithInitializedExistingMethodReturnType // init - none - mergeWithInitializedNoMethodReturnType // noti - new - mergeWithNotInitializedNewMethodReturnType // // // - dimensions we need to test every reference from // -- base type (stored) is initialized / not initialized / not there // -- given provides new method / existing method // -- referred type (exception, annotation) of method of given is there / not // there // // base - given provides existing/new - ref. type available - meaningful // init - new - yes - y - mergeWithInitializedNewMethodReferredTypeThere // init - new - not - y - mergeWithInitializedNewMethodReferredTypeNotThere // init - exi - yes - y - mergeWithInitializedExistingMethodReferredTypeThere // init - exi - not - n // noti - new - yes - y - mergeWithNotInitializedNewMethodReferredTypeThere // noti - new - not - y - mergeWithNotInitializedNewMethodReferredTypeNotThere // noti - exi - yes - n // noti - exi - not - n // nott - new - yes - y - addNewMethodReferredTypeThere // nott - new - not - y - addNewMethodReferredTypeNotThere // nott - exi - yes - n // nott - exi - not - n @Test(dataProvider = "typesWithMethods") public void mergeWithInitializedNewMethodReturnType(Class<? extends Type> clazz) throws Exception { String fqn = "class"; String hash = "hash"; String hashStored = "hashStored"; int modifiers = 0; TypeWithMethods c = (TypeWithMethods) construct(clazz, fqn, hash, modifiers); String mName = "mName"; int mMod = 7; String returnType = "void"; List<String> params = Arrays.asList(new String[] { "java.util.List", "java.lang.String" }); c.addMethod(build(mName, mMod, returnType, params, null, null)); String returnTypeStored = "java.lang.String"; TypeWithMethods stored = (TypeWithMethods) construct(clazz, fqn, hashStored, modifiers); stored.addMethod(build(mName, mMod, returnTypeStored, params, null, null)); stored.addMethod(build("someothermethod", mMod, returnTypeStored, params, null, null)); when(lookup.findByFQN(fqn)).thenReturn(stored); service.lookup = lookup; Events events = service.merge(c); Events expected = new Events(); expected.addEvent(new NodeEvent(stored, NodeEventType.CHANGED, NodeEventDetails.HASH_ADDED)); expected.addEvent(new NodeEvent(stored, NodeEventType.CHANGED, NodeEventDetails.METHOD_CHANGED_OR_ADDED)); assertThat(stored.getMethods().size(), is(3)); assertThat(stored.getMethods(), hasItem(build(mName, mMod, returnType, params, null, null))); assertEvents(events, expected); } @Test(dataProvider = "typesWithMethods") public void mergeWithInitializedExistingMethodReturnType(Class<? extends Type> clazz) throws Exception { String fqn = "class"; String hash = "hash"; String hashStored = "hashStored"; int modifiers = 0; TypeWithMethods c = (TypeWithMethods) construct(clazz, fqn, hash, modifiers); String mName = "mName"; int mMod = 7; String returnType = "void"; List<String> params = Arrays.asList(new String[] { "java.util.List", "java.lang.String" }); c.addMethod(build(mName, mMod, returnType, params, null, null)); String returnTypeStored = "void"; TypeWithMethods stored = (TypeWithMethods) construct(clazz, fqn, hashStored, modifiers); stored.addMethod(build(mName, mMod, returnTypeStored, params, null, null)); stored.addMethod(build("someothermethod", mMod, returnTypeStored, params, null, null)); when(lookup.findByFQN(fqn)).thenReturn(stored); service.lookup = lookup; Events events = service.merge(c); Events expected = new Events(); expected.addEvent(new NodeEvent(c, NodeEventType.CHANGED, NodeEventDetails.HASH_ADDED)); assertThat(stored.getMethods().size(), is(2)); assertThat(stored.getMethods(), hasItem(build(mName, mMod, returnType, params, null, null))); assertEvents(events, expected); } @Test(dataProvider = "typesWithMethods") public void mergeWithNotInitializedNewMethodReturnType(Class<? extends Type> clazz) throws Exception { String fqn = "class"; String hash = "hash"; int modifiers = 0; TypeWithMethods c = (TypeWithMethods) construct(clazz, fqn, hash, modifiers); String mName = "mName"; int mMod = 7; String returnType = "void"; List<String> params = Arrays.asList(new String[] { "java.util.List", "java.lang.String" }); c.addMethod(build(mName, mMod, returnType, params, null, null)); TypeWithMethods stored = (TypeWithMethods) construct(clazz, fqn); when(lookup.findByFQN(fqn)).thenReturn(stored); service.lookup = lookup; Events events = service.merge(c); Events expected = new Events(); expected.addEvent(new NodeEvent(c, NodeEventType.CHANGED, NodeEventDetails.INITIALIZED)); expected.addEvent(new NodeEvent(stored, NodeEventType.CHANGED, NodeEventDetails.METHOD_CHANGED_OR_ADDED)); String returnTypeExpected = "void"; assertThat(stored.getMethods().size(), is(1)); assertThat(stored.getMethods(), hasItem(build(mName, mMod, returnTypeExpected, params, null, null))); assertEvents(events, expected); } // // Method modifiers // @Test(dataProvider = "typesWithMethods") public void mergeWithInitializedNewMethodModifier(Class<? extends Type> clazz) throws Exception { String fqn = "class"; String hash = "hash"; String hashStored = "hashStored"; int modifiers = 0; TypeWithMethods c = (TypeWithMethods) construct(clazz, fqn, hash, modifiers); String mName = "mName"; String mReturnType = "mReturnType"; int mMod = Modifiers.getModifiers(Modifier.PUBLIC | Modifier.STATIC); c.addMethod(build(mName, mMod, mReturnType, null, null, null)); int storedMod = Modifiers.getModifiers(Modifier.PRIVATE | Modifier.VOLATILE); TypeWithMethods stored = (TypeWithMethods) construct(clazz, fqn, hashStored, modifiers); stored.addMethod(build(mName, storedMod, mReturnType, null, null, null)); stored.addMethod(build("someothermethod", mMod, mReturnType, null, null, null)); when(lookup.findByFQN(fqn)).thenReturn(stored); service.lookup = lookup; Events events = service.merge(c); Events expected = new Events(); expected.addEvent(new NodeEvent(c, NodeEventType.CHANGED, NodeEventDetails.HASH_ADDED)); expected.addEvent(new NodeEvent(stored, NodeEventType.CHANGED, NodeEventDetails.METHOD_CHANGED_OR_ADDED)); assertThat(stored.getMethods().size(), is(2)); int expMod = Modifiers.getModifiers(Modifier.PUBLIC | Modifier.VOLATILE | Modifier.STATIC); expMod = Modifiers.mergeModifiers(expMod, Modifiers.getModifiers(Modifier.PRIVATE)); assertThat(stored.getMethods(), hasItem(build(mName, expMod, mReturnType, null, null, null))); assertEvents(events, expected); } @Test(dataProvider = "typesWithMethods") public void mergeWithInitializedExistingMethodModifier(Class<? extends Type> clazz) throws Exception { String fqn = "class"; String hash = "hash"; String hashStored = "hashStored"; int modifiers = 0; TypeWithMethods c = (TypeWithMethods) construct(clazz, fqn, hash, modifiers); String mName = "mName"; String mReturnType = "mReturnType"; int mMod = Modifiers.getModifiers(Modifier.PUBLIC | Modifier.STATIC); c.addMethod(build(mName, mMod, mReturnType, null, null, null)); int storedMod = Modifiers.getModifiers(Modifier.PUBLIC | Modifier.STATIC); TypeWithMethods stored = (TypeWithMethods) construct(clazz, fqn, hashStored, modifiers); stored.addMethod(build(mName, storedMod, mReturnType, null, null, null)); stored.addMethod(build("someothermethod", mMod, mReturnType, null, null, null)); when(lookup.findByFQN(fqn)).thenReturn(stored); service.lookup = lookup; Events events = service.merge(c); Events expected = new Events(); expected.addEvent(new NodeEvent(c, NodeEventType.CHANGED, NodeEventDetails.HASH_ADDED)); assertThat(stored.getMethods().size(), is(2)); assertThat(stored.getMethods(), hasItem(build(mName, Modifiers.getModifiers(Modifier.PUBLIC | Modifier.STATIC), mReturnType, null, null, null))); assertEvents(events, expected); } @Test(dataProvider = "typesWithMethods") public void mergeWithInitializedNoMethodModifier(Class<? extends Type> clazz) throws Exception { String fqn = "class"; String hash = "hash"; String hashStored = "hashStored"; int modifiers = 0; TypeWithMethods c = (TypeWithMethods) construct(clazz, fqn, hash, modifiers); String mName = "mName"; String mReturnType = "mReturnType"; int mMod = Modifiers.getModifiers(Modifier.PUBLIC | Modifier.STATIC); c.addMethod(build(mName, 0, mReturnType, null, null, null)); int storedMod = Modifiers.getModifiers(Modifier.PUBLIC | Modifier.STATIC); TypeWithMethods stored = (TypeWithMethods) construct(clazz, fqn, hashStored, modifiers); stored.addMethod(build(mName, storedMod, mReturnType, null, null, null)); stored.addMethod(build("someothermethod", mMod, mReturnType, null, null, null)); when(lookup.findByFQN(fqn)).thenReturn(stored); service.lookup = lookup; Events events = service.merge(c); Events expected = new Events(); expected.addEvent(new NodeEvent(c, NodeEventType.CHANGED, NodeEventDetails.HASH_ADDED)); assertThat(stored.getMethods().size(), is(2)); assertThat(stored.getMethods(), hasItem(build(mName, Modifiers.getModifiers(Modifier.PUBLIC | Modifier.STATIC), mReturnType, null, null, null))); assertEvents(events, expected); } @Test(dataProvider = "typesWithMethods") public void mergeWithNotInitializedNewMethodModifier(Class<? extends Type> clazz) throws Exception { String fqn = "class"; String hash = "hash"; int modifiers = 0; TypeWithMethods c = (TypeWithMethods) construct(clazz, fqn, hash, modifiers); String mName = "mName"; int mMod = Modifiers.getModifiers(Modifier.PUBLIC | Modifier.STATIC); c.addMethod(build(mName, mMod, null, null, null, null)); TypeWithMethods stored = (TypeWithMethods) construct(clazz, fqn); when(lookup.findByFQN(fqn)).thenReturn(stored); service.lookup = lookup; Events events = service.merge(c); Events expected = new Events(); expected.addEvent(new NodeEvent(c, NodeEventType.CHANGED, NodeEventDetails.INITIALIZED)); expected.addEvent(new NodeEvent(stored, NodeEventType.CHANGED, NodeEventDetails.METHOD_CHANGED_OR_ADDED)); assertThat(stored.getMethods().size(), is(1)); assertThat(stored.getMethods(), hasItem(build(mName, Modifiers.getModifiers(Modifier.PUBLIC + Modifier.STATIC), null, null, null, null))); assertEvents(events, expected); } // // Method exception // @Test(dataProvider = "typesWithMethods") public void mergeWithInitializedNewMethodExceptionThere(Class<? extends Type> clazz) throws Exception { String fqn = "class"; String hash = "hash"; String hashStored = "hashStored"; int modifiers = 0; TypeWithMethods given = (TypeWithMethods) construct(clazz, fqn, hash, modifiers); TypeWithMethods stored = (TypeWithMethods) construct(clazz, fqn, hashStored, modifiers); String exceptionName = "ex"; ClassType exception = new ClassType(exceptionName); String mName = "a"; String mReturnType = "mReturnType"; given.addMethod(build(mName, 0, mReturnType, null, new HashSet<>(Arrays.asList(exception)), null)); stored.addMethod(build("anothername", 0, mReturnType, null, null, null)); when(lookup.findByFQN(fqn)).thenReturn(stored); when(lookup.findByFQN(exceptionName)).thenReturn(exception); service.lookup = lookup; Events events = service.merge(given); // methods with different parameters are different methods and should not be merged. assertThat(stored.getMethods(), hasItem(build(mName, 0, mReturnType, null, new HashSet<>(Arrays.asList(exception)), null))); assertThat(stored.getMethods().size(), is(2)); Events expected = new Events(); expected.addEvent(new NodeEvent(stored, NodeEventType.CHANGED, NodeEventDetails.HASH_ADDED)); expected.addEvent(new NodeEvent(stored, NodeEventType.CHANGED, NodeEventDetails.METHOD_CHANGED_OR_ADDED)); assertEvents(events, expected); } @Test(dataProvider = "typesWithMethods") public void mergeWithInitializedNewMethodExceptionNotThere(Class<? extends Type> clazz) throws Exception { String fqn = "class"; String hash = "hash"; String hashStored = "hashStored"; int modifiers = 0; TypeWithMethods given = (TypeWithMethods) construct(clazz, fqn, hash, modifiers); TypeWithMethods stored = (TypeWithMethods) construct(clazz, fqn, hashStored, modifiers); String exceptionName = "ex"; ClassType exception = new ClassType(exceptionName); String mName = "a"; String mReturnType = "mReturnType"; MethodType m = build(mName, 0, mReturnType, null, new HashSet<>(Arrays.asList(exception)), null); given.addMethod(m); stored.addMethod(build("anothername", 0, mReturnType, null, null, null)); when(lookup.findByFQN(fqn)).thenReturn(stored); when(lookup.findByFQN(exceptionName)).thenReturn(null); service.lookup = lookup; Events events = service.merge(given); // methods with different parameters are different methods and should not be merged. assertThat(stored.getMethods(), hasItem(m)); assertThat(stored.getMethods().size(), is(2)); Events expected = new Events(); expected.addEvent(new NodeEvent(stored, NodeEventType.CHANGED, NodeEventDetails.HASH_ADDED)); expected.addEvent(new NodeEvent(exception, NodeEventType.NEW, NodeEventDetails.NOT_INITIALIZED)); expected.addEvent(new NodeEvent(stored, NodeEventType.CHANGED, NodeEventDetails.METHOD_CHANGED_OR_ADDED)); assertEvents(events, expected); } @Test(dataProvider = "typesWithMethods") public void mergeWithInitializedExistingMethodExceptionThere(Class<? extends Type> clazz) throws Exception { String fqn = "class"; String hash = "hash"; String hashStored = "hashStored"; int modifiers = 0; TypeWithMethods given = (TypeWithMethods) construct(clazz, fqn, hash, modifiers); TypeWithMethods stored = (TypeWithMethods) construct(clazz, fqn, hashStored, modifiers); String exceptionName = "ex"; ClassType exception = new ClassType(exceptionName); String mName = "a"; String mReturnType = "mReturnType"; MethodType m = build(mName, 0, mReturnType, null, new HashSet<>(Arrays.asList(exception)), null); given.addMethod(m); stored.addMethod(build("anothername", 0, mReturnType, null, null, null)); when(lookup.findByFQN(fqn)).thenReturn(stored); when(lookup.findByFQN(exceptionName)).thenReturn(exception); service.lookup = lookup; Events events = service.merge(given); // methods with different parameters are different methods and should not be merged. assertThat(stored.getMethods(), hasItem(m)); assertThat(stored.getMethods().size(), is(2)); Events expected = new Events(); expected.addEvent(new NodeEvent(stored, NodeEventType.CHANGED, NodeEventDetails.HASH_ADDED)); expected.addEvent(new NodeEvent(stored, NodeEventType.CHANGED, NodeEventDetails.METHOD_CHANGED_OR_ADDED)); assertEvents(events, expected); } @Test(dataProvider = "typesWithMethods") public void mergeWithNotInitializedNewMethodExceptionThere(Class<? extends Type> clazz) throws Exception { String fqn = "class"; String hash = "hash"; int modifiers = 0; TypeWithMethods given = (TypeWithMethods) construct(clazz, fqn, hash, modifiers); TypeWithMethods stored = (TypeWithMethods) construct(clazz, fqn); String exceptionName = "ex"; ClassType exception = new ClassType(exceptionName); String mName = "a"; String mReturnType = "mReturnType"; MethodType m = build(mName, 0, mReturnType, null, new HashSet<>(Arrays.asList(exception)), null); given.addMethod(m); when(lookup.findByFQN(fqn)).thenReturn(stored); when(lookup.findByFQN(exceptionName)).thenReturn(exception); service.lookup = lookup; Events events = service.merge(given); // methods with different parameters are different methods and should not be merged. assertThat(stored.getMethods(), hasItem(m)); assertThat(stored.getMethods().size(), is(1)); Events expected = new Events(); expected.addEvent(new NodeEvent(stored, NodeEventType.CHANGED, NodeEventDetails.INITIALIZED)); expected.addEvent(new NodeEvent(stored, NodeEventType.CHANGED, NodeEventDetails.METHOD_CHANGED_OR_ADDED)); assertEvents(events, expected); } @Test(dataProvider = "typesWithMethods") public void mergeWithNotInitializedNewMethodExceptionNotThere(Class<? extends Type> clazz) throws Exception { String fqn = "class"; String hash = "hash"; int modifiers = 0; TypeWithMethods given = (TypeWithMethods) construct(clazz, fqn, hash, modifiers); TypeWithMethods stored = (TypeWithMethods) construct(clazz, fqn); String exceptionName = "ex"; ClassType exception = new ClassType(exceptionName); String mName = "a"; String mReturnType = "mReturnType"; MethodType m = build(mName, 0, mReturnType, null, new HashSet<>(Arrays.asList(exception)), null); given.addMethod(m); when(lookup.findByFQN(fqn)).thenReturn(stored); when(lookup.findByFQN(exceptionName)).thenReturn(null); service.lookup = lookup; Events events = service.merge(given); // methods with different parameters are different methods and should not be merged. assertThat(stored.getMethods(), hasItem(m)); assertThat(stored.getMethods().size(), is(1)); Events expected = new Events(); expected.addEvent(new NodeEvent(stored, NodeEventType.CHANGED, NodeEventDetails.INITIALIZED)); expected.addEvent(new NodeEvent(exception, NodeEventType.NEW, NodeEventDetails.NOT_INITIALIZED)); expected.addEvent(new NodeEvent(stored, NodeEventType.CHANGED, NodeEventDetails.METHOD_CHANGED_OR_ADDED)); assertEvents(events, expected); } @Test(dataProvider = "typesWithMethods") public void addNewMethodExceptionThere(Class<? extends Type> clazz) throws Exception { String fqn = "class"; String hash = "hash"; int modifiers = 0; TypeWithMethods given = (TypeWithMethods) construct(clazz, fqn, hash, modifiers); String exceptionName = "ex"; ClassType exception = new ClassType(exceptionName); String mName = "a"; String mReturnType = "mReturnType"; MethodType m = build(mName, 0, mReturnType, null, new HashSet<>(Arrays.asList(exception)), null); given.addMethod(m); when(lookup.findByFQN(fqn)).thenReturn(null); when(lookup.findByFQN(exceptionName)).thenReturn(exception); service.lookup = lookup; Events events = service.merge(given); // methods with different parameters are different methods and should not be merged. assertThat(given.getMethods(), hasItem(m)); assertThat(given.getMethods().size(), is(1)); Events expected = new Events(); expected.addEvent(new NodeEvent(given, NodeEventType.NEW, NodeEventDetails.INITIALIZED)); assertEvents(events, expected); } @Test(dataProvider = "typesWithMethods") public void addNewMethodExceptionNotThere(Class<? extends Type> clazz) throws Exception { String fqn = "class"; String hash = "hash"; int modifiers = 0; TypeWithMethods given = (TypeWithMethods) construct(clazz, fqn, hash, modifiers); String exceptionName = "ex"; ClassType exception = new ClassType(exceptionName); String mName = "a"; String mReturnType = "mReturnType"; MethodType m = build(mName, 0, mReturnType, null, new HashSet<>(Arrays.asList(exception)), null); given.addMethod(m); when(lookup.findByFQN(fqn)).thenReturn(null); when(lookup.findByFQN(exceptionName)).thenReturn(null); service.lookup = lookup; Events events = service.merge(given); // methods with different parameters are different methods and should not be merged. assertThat(given.getMethods(), hasItem(m)); assertThat(given.getMethods().size(), is(1)); Events expected = new Events(); expected.addEvent(new NodeEvent(given, NodeEventType.NEW, NodeEventDetails.INITIALIZED)); expected.addEvent(new NodeEvent(exception, NodeEventType.NEW, NodeEventDetails.NOT_INITIALIZED)); assertEvents(events, expected); } // // Method annotation // @Test(dataProvider = "typesWithMethods") public void mergeWithInitializedNewMethodAnnotationThere(Class<? extends Type> clazz) throws Exception { String fqn = "class"; String hash = "hash"; String hashStored = "hashStored"; int modifiers = 0; TypeWithMethods c = (TypeWithMethods) construct(clazz, fqn, hash, modifiers); String mName = "mName"; String mReturnType = "mReturnType"; int mMod = 7; String aName = "aName"; AnnotationType a = new AnnotationType(aName); c.addMethod(build(mName, mMod, mReturnType, null, null, new HashSet<>(Arrays.asList(new AnnotationType[] { a })))); TypeWithMethods stored = (TypeWithMethods) construct(clazz, fqn, hashStored, modifiers); stored.addMethod(build("somename", 0, mReturnType, null, null, null)); when(lookup.findByFQN(fqn)).thenReturn(stored); when(lookup.findByFQN(aName)).thenReturn(a); service.lookup = lookup; Events events = service.merge(c); Events expected = new Events(); expected.addEvent(new NodeEvent(stored, NodeEventType.CHANGED, NodeEventDetails.HASH_ADDED)); expected.addEvent(new NodeEvent(stored, NodeEventType.CHANGED, NodeEventDetails.METHOD_CHANGED_OR_ADDED)); assertThat(stored.getMethods().size(), is(2)); assertThat(stored.getMethods(), hasItem(build(mName, mMod, mReturnType, null, null, new HashSet<>(Arrays.asList(new AnnotationType[] { a }))))); assertEvents(events, expected); } @Test(dataProvider = "typesWithMethods") public void mergeWithInitializedNewMethodAnnotationNotThere(Class<? extends Type> clazz) throws Exception { String fqn = "class"; String hash = "hash"; String hashStored = "hashStored"; int modifiers = 0; TypeWithMethods c = (TypeWithMethods) construct(clazz, fqn, hash, modifiers); String mName = "mName"; int mMod = 7; String aName = "aName"; String mReturnType = "mReturnType"; AnnotationType a = new AnnotationType(aName); c.addMethod(build(mName, mMod, mReturnType, null, null, new HashSet<>(Arrays.asList(new AnnotationType[] { a })))); TypeWithMethods stored = (TypeWithMethods) construct(clazz, fqn, hashStored, modifiers); stored.addMethod(build(mName, mMod, mReturnType, null, null, null)); stored.addMethod(build("somename", 0, mReturnType, null, null, null)); when(lookup.findByFQN(fqn)).thenReturn(stored); when(lookup.findByFQN(aName)).thenReturn(null); service.lookup = lookup; Events events = service.merge(c); Events expected = new Events(); expected.addEvent(new NodeEvent(stored, NodeEventType.CHANGED, NodeEventDetails.HASH_ADDED)); expected.addEvent(new NodeEvent(a, NodeEventType.NEW, NodeEventDetails.NOT_INITIALIZED)); expected.addEvent(new NodeEvent(stored, NodeEventType.CHANGED, NodeEventDetails.METHOD_CHANGED_OR_ADDED)); assertThat(stored.getMethods().size(), is(2)); assertThat(stored.getMethods(), hasItem(build(mName, mMod, mReturnType, null, null, new HashSet<>(Arrays.asList(new AnnotationType[] { a }))))); assertEvents(events, expected); } @Test(dataProvider = "typesWithMethods") public void mergeWithInitializedExistingMethodAnnotationThere(Class<? extends Type> clazz) throws Exception { String fqn = "class"; String hash = "hash"; String hashStored = "hashStored"; int modifiers = 0; TypeWithMethods c = (TypeWithMethods) construct(clazz, fqn, hash, modifiers); String mName = "mName"; String mReturnType = "mReturnType"; int mMod = 7; String aName = "aName"; AnnotationType a = new AnnotationType(aName); c.addMethod(build(mName, mMod, mReturnType, null, null, new HashSet<>(Arrays.asList(new AnnotationType[] { a })))); TypeWithMethods stored = (TypeWithMethods) construct(clazz, fqn, hashStored, modifiers); stored.addMethod(build(mName, mMod, mReturnType, null, null, new HashSet<>(Arrays.asList(new AnnotationType[] { a })))); stored.addMethod(build("somename", 0, mReturnType, null, null, null)); when(lookup.findByFQN(fqn)).thenReturn(stored); when(lookup.findByFQN(aName)).thenReturn(a); service.lookup = lookup; Events events = service.merge(c); Events expected = new Events(); expected.addEvent(new NodeEvent(stored, NodeEventType.CHANGED, NodeEventDetails.HASH_ADDED)); assertThat(stored.getMethods().size(), is(2)); assertThat(stored.getMethods(), hasItem(build(mName, mMod, mReturnType, null, null, new HashSet<>(Arrays.asList(new AnnotationType[] { a }))))); assertEvents(events, expected); } @Test(dataProvider = "typesWithMethods") public void mergeWithNotInitializedNewMethodAnnotationThere(Class<? extends Type> clazz) throws Exception { String fqn = "class"; String hash = "hash"; int modifiers = 0; TypeWithMethods c = (TypeWithMethods) construct(clazz, fqn, hash, modifiers); String mName = "mName"; String mReturnType = "mReturnType"; int mMod = 7; String aName = "aName"; AnnotationType a = new AnnotationType(aName); c.addMethod(build(mName, mMod, mReturnType, null, null, new HashSet<>(Arrays.asList(new AnnotationType[] { a })))); TypeWithMethods stored = (TypeWithMethods) construct(clazz, fqn); when(lookup.findByFQN(fqn)).thenReturn(stored); when(lookup.findByFQN(aName)).thenReturn(a); service.lookup = lookup; Events events = service.merge(c); Events expected = new Events(); expected.addEvent(new NodeEvent(stored, NodeEventType.CHANGED, NodeEventDetails.INITIALIZED)); expected.addEvent(new NodeEvent(stored, NodeEventType.CHANGED, NodeEventDetails.METHOD_CHANGED_OR_ADDED)); assertThat(stored.getMethods().size(), is(1)); assertThat(stored.getMethods(), hasItem(build(mName, mMod, mReturnType, null, null, new HashSet<>(Arrays.asList(new AnnotationType[] { a }))))); assertEvents(events, expected); } @Test(dataProvider = "typesWithMethods") public void mergeWithNotInitializedNewMethodAnnotationNotThere(Class<? extends Type> clazz) throws Exception { String fqn = "class"; String hash = "hash"; int modifiers = 0; TypeWithMethods c = (TypeWithMethods) construct(clazz, fqn, hash, modifiers); String mName = "mName"; String mReturnType = "mReturnType"; int mMod = 7; String aName = "aName"; AnnotationType a = new AnnotationType(aName); c.addMethod(build(mName, mMod, mReturnType, null, null, new HashSet<>(Arrays.asList(new AnnotationType[] { a })))); TypeWithMethods stored = (TypeWithMethods) construct(clazz, fqn); when(lookup.findByFQN(fqn)).thenReturn(stored); when(lookup.findByFQN(aName)).thenReturn(null); service.lookup = lookup; Events events = service.merge(c); Events expected = new Events(); expected.addEvent(new NodeEvent(stored, NodeEventType.CHANGED, NodeEventDetails.INITIALIZED)); expected.addEvent(new NodeEvent(a, NodeEventType.NEW, NodeEventDetails.NOT_INITIALIZED)); expected.addEvent(new NodeEvent(stored, NodeEventType.CHANGED, NodeEventDetails.METHOD_CHANGED_OR_ADDED)); assertThat(stored.getMethods().size(), is(1)); assertThat(stored.getMethods(), hasItem(build(mName, mMod, mReturnType, null, null, new HashSet<>(Arrays.asList(new AnnotationType[] { a }))))); assertEvents(events, expected); } @Test(dataProvider = "typesWithMethods") public void addNewMethodAnnotationThere(Class<? extends Type> clazz) throws Exception { String fqn = "class"; String hash = "hash"; int modifiers = 0; TypeWithMethods c = (TypeWithMethods) construct(clazz, fqn, hash, modifiers); String mName = "mName"; String mReturnType = "mReturnType"; int mMod = 7; String aName = "aName"; AnnotationType a = new AnnotationType(aName); c.addMethod(build(mName, mMod, mReturnType, null, null, new HashSet<>(Arrays.asList(new AnnotationType[] { a })))); when(lookup.findByFQN(fqn)).thenReturn(null); when(lookup.findByFQN(aName)).thenReturn(a); service.lookup = lookup; Events events = service.merge(c); Events expected = new Events(); expected.addEvent(new NodeEvent(c, NodeEventType.NEW, NodeEventDetails.INITIALIZED)); assertThat(c.getMethods().size(), is(1)); assertThat(c.getMethods(), hasItem(build(mName, mMod, mReturnType, null, null, new HashSet<>(Arrays.asList(new AnnotationType[] { a }))))); assertEvents(events, expected); } @Test(dataProvider = "typesWithMethods") public void addNewMethodAnnotationNotThere(Class<? extends Type> clazz) throws Exception { String fqn = "class"; String hash = "hash"; int modifiers = 0; TypeWithMethods c = (TypeWithMethods) construct(clazz, fqn, hash, modifiers); String mName = "mName"; String mReturnType = "mReturnType"; int mMod = 7; String aName = "aName"; AnnotationType a = new AnnotationType(aName); c.addMethod(build(mName, mMod, mReturnType, null, null, new HashSet<>(Arrays.asList(new AnnotationType[] { a })))); when(lookup.findByFQN(fqn)).thenReturn(null); when(lookup.findByFQN(aName)).thenReturn(null); service.lookup = lookup; Events events = service.merge(c); Events expected = new Events(); expected.addEvent(new NodeEvent(c, NodeEventType.NEW, NodeEventDetails.INITIALIZED)); expected.addEvent(new NodeEvent(a, NodeEventType.NEW, NodeEventDetails.NOT_INITIALIZED)); assertThat(c.getMethods().size(), is(1)); assertThat(c.getMethods(), hasItem(build(mName, mMod, mReturnType, null, null, new HashSet<>(Arrays.asList(new AnnotationType[] { a }))))); assertEvents(events, expected); } private Type construct(Class<? extends Type> type, String fqn) throws NoSuchMethodException, SecurityException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException { Constructor<? extends Type> c = type.getConstructor(String.class); return c.newInstance(fqn); } private Type construct(Class<? extends Type> type, String fqn, String hash, int modifiers) throws NoSuchMethodException, SecurityException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException { Constructor<? extends Type> c = type.getConstructor(String.class, String.class, int.class); return c.newInstance(fqn, hash, modifiers); } private void assertEvents(Events events, Events expected) { assertThat(events, is(not(nullValue()))); assertThat(events.getNodeEvents().size(), is(expected.getNodeEvents().size())); assertThat(events.getReferenceEvents().size(), is(expected.getReferenceEvents().size())); for (NodeEvent n : expected.getNodeEvents()) { assertThat(events.getNodeEvents(), hasItem(n)); } for (ReferenceEvent r : expected.getReferenceEvents()) { assertThat(events.getReferenceEvents(), hasItem(r)); } // also ensure that these events are passed to the notification for (NodeEvent nodeEvent : events.getNodeEvents()) { verify(cache, times(1)).informNodeChange(nodeEvent); } for (ReferenceEvent referenceEvent : events.getReferenceEvents()) { verify(cache, times(1)).informReferenceChange(referenceEvent); } } private void assertEventsEmpty(Events events) { assertThat(events, is(not(nullValue()))); assertThat(events.getNodeEvents(), is(empty())); assertThat(events.getReferenceEvents(), is(empty())); } private MethodType build(String name, int modifiers, String returnType, List<String> parameters, Set<ClassType> exceptions, Set<AnnotationType> annotations) { // bidirectional setting needs an correctly constructed MethodType, thus we cannot have // this in the constructor itself. MethodType type = new MethodType(); type.setName(name); type.setModifiers(modifiers); if (null != returnType) { type.setReturnType(returnType); } if (null != parameters) { type.setParameters(parameters); } if (null != annotations) { for (AnnotationType a : annotations) { type.addAnnotation(a); } } if (null != exceptions) { for (ClassType e : exceptions) { type.addException(e); } } return type; } } }