/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package gobblin.config.common.impl; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; import java.util.List; import org.mockito.Mockito; import org.testng.Assert; import org.testng.annotations.Test; import gobblin.config.store.api.ConfigKeyPath; import gobblin.config.store.api.ConfigStore; @Test(groups = { "gobblin.config.common.impl" }) public class TestCircularDependency { private final String version = "V1.0"; List<ConfigKeyPath> emptyList = Collections.emptyList(); public static void addConfigStoreChildren(ConfigStore mockup, String version, ConfigKeyPath parent, ConfigKeyPath... configKeyPaths) { List<ConfigKeyPath> children = new ArrayList<ConfigKeyPath>(); for (ConfigKeyPath p : configKeyPaths) { children.add(p); } when(mockup.getChildren(parent, version)).thenReturn(children); } public static void addConfigStoreImports(ConfigStore mockup, String version, ConfigKeyPath self, ConfigKeyPath... configKeyPaths) { List<ConfigKeyPath> ownImports = new ArrayList<ConfigKeyPath>(); for (ConfigKeyPath p : configKeyPaths) { ownImports.add(p); } when(mockup.getOwnImports(self, version)).thenReturn(ownImports); } @Test public void testSelfImportSelf() { ConfigKeyPath tag = SingleLinkedListConfigKeyPath.ROOT.createChild("tag"); ConfigStore mockConfigStore = mock(ConfigStore.class, Mockito.RETURNS_SMART_NULLS); when(mockConfigStore.getCurrentVersion()).thenReturn(version); addConfigStoreChildren(mockConfigStore, version, SingleLinkedListConfigKeyPath.ROOT, tag); // self import self addConfigStoreImports(mockConfigStore, version, tag, tag); ConfigStoreBackedTopology csTopology = new ConfigStoreBackedTopology(mockConfigStore, this.version); InMemoryTopology inMemory = new InMemoryTopology(csTopology); try { inMemory.getImportsRecursively(tag); Assert.fail("Did not catch expected CircularDependencyException"); } catch (CircularDependencyException e) { Assert.assertTrue(e.getMessage().indexOf("/tag") > 0); } } @Test public void testSelfImportChild() { ConfigKeyPath tag = SingleLinkedListConfigKeyPath.ROOT.createChild("tag"); ConfigKeyPath highPriorityTag = tag.createChild("highPriorityTag"); ConfigStore mockConfigStore = mock(ConfigStore.class, Mockito.RETURNS_SMART_NULLS); when(mockConfigStore.getCurrentVersion()).thenReturn(version); addConfigStoreChildren(mockConfigStore, version, SingleLinkedListConfigKeyPath.ROOT, tag); addConfigStoreChildren(mockConfigStore, version, tag, highPriorityTag); // parent import direct child addConfigStoreImports(mockConfigStore, version, tag, highPriorityTag); ConfigStoreBackedTopology csTopology = new ConfigStoreBackedTopology(mockConfigStore, this.version); InMemoryTopology inMemory = new InMemoryTopology(csTopology); try { inMemory.getImportsRecursively(tag); Assert.fail("Did not catch expected CircularDependencyException"); } catch (CircularDependencyException e) { Assert.assertTrue(e.getMessage().indexOf("/tag/highPriorityTag") > 0 && e.getMessage().indexOf("/tag ") > 0); } } @Test public void testSelfImportDescendant() { ConfigKeyPath tag = SingleLinkedListConfigKeyPath.ROOT.createChild("tag"); ConfigKeyPath highPriorityTag = tag.createChild("highPriorityTag"); ConfigKeyPath nertzHighPriorityTag = highPriorityTag.createChild("nertzHighPriorityTag"); ConfigStore mockConfigStore = mock(ConfigStore.class, Mockito.RETURNS_SMART_NULLS); when(mockConfigStore.getCurrentVersion()).thenReturn(version); addConfigStoreChildren(mockConfigStore, version, SingleLinkedListConfigKeyPath.ROOT, tag); addConfigStoreChildren(mockConfigStore, version, tag, highPriorityTag); addConfigStoreChildren(mockConfigStore, version, highPriorityTag, nertzHighPriorityTag); // self import descendant // formed the loop /tag -> /tag/highPriorityTag/nertzHighPriorityTag -> /tag/highPriorityTag -> /tag addConfigStoreImports(mockConfigStore, version, tag, nertzHighPriorityTag); ConfigStoreBackedTopology csTopology = new ConfigStoreBackedTopology(mockConfigStore, this.version); InMemoryTopology inMemory = new InMemoryTopology(csTopology); try { inMemory.getImportsRecursively(tag); Assert.fail("Did not catch expected CircularDependencyException"); } catch (CircularDependencyException e) { Assert.assertTrue(e.getMessage().indexOf("/tag/highPriorityTag/nertzHighPriorityTag") > 0 && e.getMessage().indexOf("/tag/highPriorityTag ") > 0 && e.getMessage().indexOf("/tag ") > 0); } } @Test public void testSelfIndirectlyImportDescendant() { ConfigKeyPath tag = SingleLinkedListConfigKeyPath.ROOT.createChild("tag"); ConfigKeyPath highPriorityTag = tag.createChild("highPriorityTag"); ConfigKeyPath nertzHighPriorityTag = highPriorityTag.createChild("nertzHighPriorityTag"); ConfigKeyPath tag2 = SingleLinkedListConfigKeyPath.ROOT.createChild("tag2"); ConfigStore mockConfigStore = mock(ConfigStore.class, Mockito.RETURNS_SMART_NULLS); when(mockConfigStore.getCurrentVersion()).thenReturn(version); addConfigStoreChildren(mockConfigStore, version, SingleLinkedListConfigKeyPath.ROOT, tag, tag2); addConfigStoreChildren(mockConfigStore, version, tag, highPriorityTag); addConfigStoreChildren(mockConfigStore, version, highPriorityTag, nertzHighPriorityTag); // self import descendant // formed the loop /tag -> /tag2 -> /tag/highPriorityTag/nertzHighPriorityTag -> /tag/highPriorityTag -> /tag addConfigStoreImports(mockConfigStore, version, tag, tag2); addConfigStoreImports(mockConfigStore, version, tag2, nertzHighPriorityTag); ConfigStoreBackedTopology csTopology = new ConfigStoreBackedTopology(mockConfigStore, this.version); InMemoryTopology inMemory = new InMemoryTopology(csTopology); try { inMemory.getImportsRecursively(tag); Assert.fail("Did not catch expected CircularDependencyException"); } catch (CircularDependencyException e) { Assert.assertTrue(e.getMessage().indexOf("/tag/highPriorityTag/nertzHighPriorityTag") > 0 && e.getMessage().indexOf("/tag/highPriorityTag ") > 0 && e.getMessage().indexOf("/tag ") > 0 && e.getMessage().indexOf("/tag2 ") > 0); } } @Test public void testLoops() { ConfigKeyPath tag = SingleLinkedListConfigKeyPath.ROOT.createChild("tag"); ConfigKeyPath subTag1 = tag.createChild("subTag1"); ConfigKeyPath subTag2 = tag.createChild("subTag2"); ConfigKeyPath subTag3 = tag.createChild("subTag3"); ConfigStore mockConfigStore = mock(ConfigStore.class, Mockito.RETURNS_SMART_NULLS); when(mockConfigStore.getCurrentVersion()).thenReturn(version); addConfigStoreChildren(mockConfigStore, version, SingleLinkedListConfigKeyPath.ROOT, tag); addConfigStoreChildren(mockConfigStore, version, tag, subTag1, subTag2, subTag3); // self import descendant // formed loop /tag/subTag1 -> /tag/subTag2 -> /tag/subTag3 -> /tag/subTag1 addConfigStoreImports(mockConfigStore, version, subTag1, subTag2); addConfigStoreImports(mockConfigStore, version, subTag2, subTag3); addConfigStoreImports(mockConfigStore, version, subTag3, subTag1); ConfigStoreBackedTopology csTopology = new ConfigStoreBackedTopology(mockConfigStore, this.version); InMemoryTopology inMemory = new InMemoryTopology(csTopology); try { inMemory.getImportsRecursively(subTag1); Assert.fail("Did not catch expected CircularDependencyException"); } catch (CircularDependencyException e) { Assert.assertTrue(e.getMessage().indexOf("/tag/subTag1") > 0 && e.getMessage().indexOf("/tag/subTag2") > 0 && e.getMessage().indexOf("/tag/subTag3") > 0); } } @Test public void testNoCircular() { ConfigKeyPath tag = SingleLinkedListConfigKeyPath.ROOT.createChild("tag"); ConfigKeyPath highPriorityTag = tag.createChild("highPriorityTag"); ConfigKeyPath nertzHighPriorityTag = highPriorityTag.createChild("nertzHighPriorityTag"); ConfigKeyPath tag2 = SingleLinkedListConfigKeyPath.ROOT.createChild("tag2"); ConfigStore mockConfigStore = mock(ConfigStore.class, Mockito.RETURNS_SMART_NULLS); when(mockConfigStore.getCurrentVersion()).thenReturn(version); addConfigStoreChildren(mockConfigStore, version, SingleLinkedListConfigKeyPath.ROOT, tag, tag2); addConfigStoreChildren(mockConfigStore, version, tag, highPriorityTag); addConfigStoreChildren(mockConfigStore, version, highPriorityTag, nertzHighPriorityTag); // mock up imports, point to same node but without circular addConfigStoreImports(mockConfigStore, version, nertzHighPriorityTag, tag2); addConfigStoreImports(mockConfigStore, version, tag2, tag); ConfigStoreBackedTopology csTopology = new ConfigStoreBackedTopology(mockConfigStore, this.version); InMemoryTopology inMemory = new InMemoryTopology(csTopology); List<ConfigKeyPath> result = inMemory.getImportsRecursively(nertzHighPriorityTag); Assert.assertTrue(result.size() == 2); Iterator<ConfigKeyPath> it = result.iterator(); Assert.assertEquals(it.next(), tag2); Assert.assertEquals(it.next(), tag); } }