diff --git a/core/src/main/java/hudson/cli/ListJobsCommand.java b/core/src/main/java/hudson/cli/ListJobsCommand.java index 8e52a9f449887a2a30b30ccd9ec093616d42ff54..cdce9d6b46b60cdb868e7f442706158636d90561 100644 --- a/core/src/main/java/hudson/cli/ListJobsCommand.java +++ b/core/src/main/java/hudson/cli/ListJobsCommand.java @@ -24,10 +24,14 @@ package hudson.cli; import java.util.Collection; +import java.util.LinkedHashSet; + import java.util.Collections; + import hudson.model.Item; import hudson.model.ItemGroup; import hudson.model.TopLevelItem; +import hudson.model.ViewGroup; import hudson.model.View; import hudson.Extension; import jenkins.model.Jenkins; @@ -57,7 +61,7 @@ public class ListJobsCommand extends CLICommand { View view = h.getView(name); if (view != null) { - jobs = view.getItems(); + jobs = view.getAllItems(); } // If no view was found, try with an item group. else { diff --git a/core/src/main/java/hudson/model/View.java b/core/src/main/java/hudson/model/View.java index 40a90b97d0a6669bf49c04f5b7b1bba5001767b4..d6d4ef4a5b6906ff1a1631d818198c7d64fc091c 100644 --- a/core/src/main/java/hudson/model/View.java +++ b/core/src/main/java/hudson/model/View.java @@ -93,6 +93,7 @@ import java.util.Comparator; import java.util.GregorianCalendar; import java.util.HashMap; import java.util.HashSet; +import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; @@ -176,6 +177,27 @@ public abstract class View extends AbstractModelObject implements AccessControll @Exported(name="jobs") public abstract Collection getItems(); + /** + * Gets all the items recursively contained in this collection in a read-only view. + * @since 1.520 + */ + public Collection getAllItems() { + + final Collection items = new LinkedHashSet( + getItems() + ); + + if (this instanceof ViewGroup) { + + for(final View view: ((ViewGroup) this).getViews()) { + + items.addAll(view.getAllItems()); + } + } + + return Collections.unmodifiableCollection(items); + } + /** * Gets the {@link TopLevelItem} of the given name. */ diff --git a/core/src/test/java/hudson/cli/ListJobsCommandTest.java b/core/src/test/java/hudson/cli/ListJobsCommandTest.java new file mode 100644 index 0000000000000000000000000000000000000000..f264777ed53cee5fdd000aeebe76af4dcb6ea0e7 --- /dev/null +++ b/core/src/test/java/hudson/cli/ListJobsCommandTest.java @@ -0,0 +1,187 @@ +package hudson.cli; + +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.not; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.powermock.api.mockito.PowerMockito.mock; +import static org.powermock.api.mockito.PowerMockito.mockStatic; +import static org.powermock.api.mockito.PowerMockito.when; +import hudson.model.TopLevelItem; +import hudson.model.ViewGroup; +import hudson.model.ViewTest.CompositeView; +import hudson.model.View; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; + +import jenkins.model.Jenkins; + +import org.hamcrest.Description; +import org.hamcrest.TypeSafeMatcher; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mockito; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.core.classloader.annotations.SuppressStaticInitializationFor; +import org.powermock.modules.junit4.PowerMockRunner; + +@RunWith(PowerMockRunner.class) +@PrepareForTest(Jenkins.class) +@SuppressStaticInitializationFor("hudson.cli.CLICommand") +public class ListJobsCommandTest { + + private /*final*/ Jenkins jenkins; + private /*final*/ ListJobsCommand command; + private final ByteArrayOutputStream stdout = new ByteArrayOutputStream(); + private final ByteArrayOutputStream stderr = new ByteArrayOutputStream(); + + @Before + public void setUp() { + + jenkins = mock(Jenkins.class); + mockStatic(Jenkins.class); + when(Jenkins.getInstance()).thenReturn(jenkins); + command = mock(ListJobsCommand.class, Mockito.CALLS_REAL_METHODS); + command.stdout = new PrintStream(stdout); + command.stderr = new PrintStream(stderr); + } + + @Test + public void getNullForNonexistingName() throws Exception { + + when(jenkins.getView(null)).thenReturn(null); + when(jenkins.getItemByFullName(null)).thenReturn(null); + + // TODO: One would expect -1 here + assertThat(runWith("NoSuchViewOrItemGroup"), equalTo(0)); + assertThat(stdout, is(empty())); + assertThat(stderr, is(not(empty()))); + } + + @Test + public void getAllJobsForEmptyName() throws Exception { + + final Collection jenkinsJobs = Arrays.asList( + job("some-job"), job("some-other-job") + ); + + when(jenkins.getItems()).thenReturn((List) jenkinsJobs); + + assertThat(runWith(null), equalTo(0)); + assertThat(stderr, is(empty())); + assertThat(stdout, listsJobs("some-job", "some-other-job")); + } + + @Test + public void getJobsFromView() throws Exception { + + final Collection viewJobs = Arrays.asList( + job("some-job"), job("some-other-job") + ); + + final View customView = view(); + when(customView.getItems()).thenReturn(viewJobs); + + when(jenkins.getView("CustomView")).thenReturn(customView); + + assertThat(runWith("CustomView"), equalTo(0)); + assertThat(stderr, is(empty())); + assertThat(stdout, listsJobs("some-job", "some-other-job")); + } + + @Test + public void getJobsRecursivelyFromViewGroup() throws Exception { + + final CompositeView rootView = mock(CompositeView.class); + when(rootView.getAllItems()).thenCallRealMethod(); + final View leftView = view(); + final View rightView = view(); + + final TopLevelItem rootJob = job("rootJob"); + final TopLevelItem leftJob = job("leftJob"); + final TopLevelItem rightJob = job("rightJob"); + final TopLevelItem sharedJob = job("sharedJob"); + + when(rootView.getViews()).thenReturn(Arrays.asList(leftView, rightView)); + when(rootView.getItems()).thenReturn(Arrays.asList(rootJob, sharedJob)); + when(leftView.getItems()).thenReturn(Arrays.asList(leftJob, sharedJob)); + when(rightView.getItems()).thenReturn(Arrays.asList(rightJob)); + + when(jenkins.getView("Root")).thenReturn(rootView); + + assertThat(runWith("Root"), equalTo(0)); + assertThat(stderr, is(empty())); + assertThat(stdout, listsJobs("rootJob", "leftJob", "rightJob", "sharedJob")); + } + + private View view() { + + final View view = mock(View.class); + + when(view.getAllItems()).thenCallRealMethod(); + + return view; + } + + private TopLevelItem job(final String name) { + + final TopLevelItem item = mock(TopLevelItem.class); + + when(item.getDisplayName()).thenReturn(name); + + return item; + } + + private int runWith(final String name) throws Exception { + + command.name = name; + + return command.run(); + } + + private TypeSafeMatcher empty() { + + return new TypeSafeMatcher() { + + @Override + protected boolean matchesSafely(ByteArrayOutputStream item) { + + return item.toString().isEmpty(); + } + + @Override + public void describeTo(Description description) { + + description.appendText("Empty output"); + } + }; + } + + private TypeSafeMatcher listsJobs(final String... expected) { + + return new TypeSafeMatcher() { + + @Override + protected boolean matchesSafely(ByteArrayOutputStream item) { + + final HashSet jobs = new HashSet( + Arrays.asList(item.toString().split("\n")) + ); + + return new HashSet(Arrays.asList(expected)).equals(jobs); + } + + @Override + public void describeTo(Description description) { + + description.appendText("Job listing of " + Arrays.toString(expected)); + } + }; + } +} diff --git a/core/src/test/java/hudson/model/ViewTest.java b/core/src/test/java/hudson/model/ViewTest.java index a336ce884f9caeb03a1b709c607176313e1bc6c0..85a44919471aa6d0c388bd002647e4749ff9fba2 100644 --- a/core/src/test/java/hudson/model/ViewTest.java +++ b/core/src/test/java/hudson/model/ViewTest.java @@ -5,6 +5,7 @@ import hudson.search.SearchIndexBuilder; import hudson.search.SearchItem; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.List; @@ -65,4 +66,45 @@ public class ViewTest { Assert.assertEquals(actual.getSearchName(), item2.getDisplayName()); Assert.assertEquals(actual.getSearchUrl(), item2.getSearchUrl()); } + + /* + * Get all items recursively when View implements ViewGroup at the same time + */ + @Test + public void getAllItems() throws Exception { + + final CompositeView rootView = Mockito.mock(CompositeView.class); + final View leftView = Mockito.mock(View.class); + final View rightView = Mockito.mock(View.class); + + Mockito.when(rootView.getAllItems()).thenCallRealMethod(); + Mockito.when(leftView.getAllItems()).thenCallRealMethod(); + Mockito.when(rightView.getAllItems()).thenCallRealMethod(); + + final TopLevelItem rootJob = Mockito.mock(TopLevelItem.class); + final TopLevelItem leftJob = Mockito.mock(TopLevelItem.class); + final TopLevelItem rightJob = Mockito.mock(TopLevelItem.class); + final TopLevelItem sharedJob = Mockito.mock(TopLevelItem.class); + + Mockito.when(rootJob.getDisplayName()).thenReturn("rootJob"); + Mockito.when(leftJob.getDisplayName()).thenReturn("leftJob"); + Mockito.when(rightJob.getDisplayName()).thenReturn("rightJob"); + Mockito.when(sharedJob.getDisplayName()).thenReturn("sharedJob"); + + Mockito.when(rootView.getViews()).thenReturn(Arrays.asList(leftView, rightView)); + Mockito.when(rootView.getItems()).thenReturn(Arrays.asList(rootJob, sharedJob)); + Mockito.when(leftView.getItems()).thenReturn(Arrays.asList(leftJob, sharedJob)); + Mockito.when(rightView.getItems()).thenReturn(Arrays.asList(rightJob)); + + final TopLevelItem[] expected = new TopLevelItem[] {rootJob, sharedJob, leftJob, rightJob}; + + Assert.assertArrayEquals(expected, rootView.getAllItems().toArray()); + } + + public static abstract class CompositeView extends View implements ViewGroup { + + protected CompositeView(final String name) { + super(name); + } + } }