/* * The MIT License * * Copyright (c) 2004-2011, Sun Microsystems, Inc., CloudBees, Inc. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package hudson.matrix; import com.gargoylesoftware.htmlunit.html.HtmlPage; import hudson.cli.CLICommandInvoker; import hudson.cli.DeleteBuildsCommand; import hudson.model.Cause; import hudson.model.Result; import hudson.slaves.DumbSlave; import hudson.slaves.RetentionStrategy; import hudson.tasks.Ant; import hudson.tasks.ArtifactArchiver; import hudson.tasks.Fingerprinter; import hudson.tasks.LogRotator; import hudson.tasks.Maven; import hudson.tasks.Shell; import hudson.tasks.BatchFile; import org.jvnet.hudson.test.Email; import org.jvnet.hudson.test.SingleFileSCM; import org.jvnet.hudson.test.ToolInstallations; import org.jvnet.hudson.test.UnstableBuilder; import org.jvnet.hudson.test.recipes.LocalData; import com.gargoylesoftware.htmlunit.html.HtmlTable; import com.gargoylesoftware.htmlunit.html.HtmlTableCell; import com.gargoylesoftware.htmlunit.html.HtmlTableRow; import hudson.FilePath; import org.jvnet.hudson.test.Issue; import org.jvnet.hudson.test.TestBuilder; import hudson.model.AbstractBuild; import hudson.Launcher; import hudson.model.BuildListener; import hudson.util.OneShotEvent; import java.io.IOException; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import hudson.model.JDK; import hudson.model.Slave; import hudson.Functions; import hudson.model.ParametersDefinitionProperty; import hudson.model.FileParameterDefinition; import hudson.model.Cause.LegacyCodeCause; import hudson.model.ParametersAction; import hudson.model.FileParameterValue; import hudson.model.StringParameterDefinition; import hudson.model.StringParameterValue; import java.util.List; import java.util.ArrayList; import java.util.concurrent.CountDownLatch; import static hudson.model.Node.Mode.EXCLUSIVE; import hudson.model.ParameterDefinition; import hudson.model.ParameterValue; import hudson.model.queue.QueueTaskFuture; import java.io.File; import java.lang.reflect.Method; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import jenkins.model.Jenkins; import static org.junit.Assert.*; import org.junit.Rule; import org.junit.Test; import org.jvnet.hudson.test.JenkinsRule; import org.junit.rules.TemporaryFolder; /** * * * @author Kohsuke Kawaguchi */ public class MatrixProjectTest { @Rule public JenkinsRule j = new JenkinsRule(); @Rule public TemporaryFolder tmp = new TemporaryFolder(); /** * Tests that axes are available as build variables in the Ant builds. */ @Test public void testBuildAxisInAnt() throws Exception { MatrixProject p = createMatrixProject(); Ant.AntInstallation ant = ToolInstallations.configureDefaultAnt(tmp); p.getBuildersList().add(new Ant("-Dprop=${db} test", ant.getName(), null, null, null)); // we need a dummy build script that echos back our property p.setScm(new SingleFileSCM("build.xml", "<project default='test'><target name='test'><echo>assertion ${prop}=${db}</echo></target></project>")); MatrixBuild build = p.scheduleBuild2(0, new Cause.UserCause()).get(); List<MatrixRun> runs = build.getRuns(); assertEquals(4,runs.size()); for (MatrixRun run : runs) { j.assertBuildStatus(Result.SUCCESS, run); String expectedDb = run.getParent().getCombination().get("db"); j.assertLogContains("assertion "+expectedDb+"="+expectedDb, run); } } /** * Tests that axes are available as build variables in the Maven builds. */ @Test public void testBuildAxisInMaven() throws Exception { MatrixProject p = createMatrixProject(); Maven.MavenInstallation maven = ToolInstallations.configureDefaultMaven(); p.getBuildersList().add(new Maven("-Dprop=${db} validate", maven.getName())); // we need a dummy build script that echos back our property p.setScm(new SingleFileSCM("pom.xml",getClass().getResource("echo-property.pom"))); MatrixBuild build = p.scheduleBuild2(0).get(); List<MatrixRun> runs = build.getRuns(); assertEquals(4,runs.size()); for (MatrixRun run : runs) { j.assertBuildStatus(Result.SUCCESS, run); String expectedDb = run.getParent().getCombination().get("db"); System.out.println(run.getLog()); j.assertLogContains("assertion "+expectedDb+"="+expectedDb, run); // also make sure that the variables are expanded at the command line level. assertFalse(run.getLog().contains("-Dprop=${db}")); } } /** * Test that configuration filters work */ @Test public void testConfigurationFilter() throws Exception { MatrixProject p = createMatrixProject(); p.setCombinationFilter("db==\"mysql\""); MatrixBuild build = p.scheduleBuild2(0).get(); assertEquals(2, build.getRuns().size()); } /** * Test that touch stone builds work */ @Test public void testTouchStone() throws Exception { MatrixProject p = createMatrixProject(); p.setTouchStoneCombinationFilter("db==\"mysql\""); p.setTouchStoneResultCondition(Result.SUCCESS); MatrixBuild build = p.scheduleBuild2(0).get(); assertEquals(4, build.getRuns().size()); p.getBuildersList().add(new UnstableBuilder()); build = p.scheduleBuild2(0).get(); assertEquals(2, build.getExactRuns().size()); } protected MatrixProject createMatrixProject() throws IOException { MatrixProject p = j.createProject(MatrixProject.class); // set up 2x2 matrix AxisList axes = new AxisList(); axes.add(new TextAxis("db","mysql","oracle")); axes.add(new TextAxis("direction","north","south")); p.setAxes(axes); return p; } /** * Fingerprinter failed to work on the matrix project. */ @Email("http://www.nabble.com/1.286-version-and-fingerprints-option-broken-.-td22236618.html") @Test public void testFingerprinting() throws Exception { MatrixProject p = createMatrixProject(); if (Functions.isWindows()) p.getBuildersList().add(new BatchFile("echo \"\" > p")); else p.getBuildersList().add(new Shell("touch p")); p.getPublishersList().add(new ArtifactArchiver("p",null,false, false)); p.getPublishersList().add(new Fingerprinter("",true)); j.buildAndAssertSuccess(p); } void assertRectangleTable(MatrixProject p) throws Exception { HtmlPage html = j.createWebClient().getPage(p); HtmlTable table = html.getFirstByXPath("id('matrix')/table"); // remember cells that are extended from rows above. Map<Integer,Integer> rowSpans = new HashMap<Integer,Integer>(); Integer masterWidth = null; for (HtmlTableRow r : table.getRows()) { int width = 0; for (HtmlTableCell c : r.getCells()) { width += c.getColumnSpan(); } for (Integer val : rowSpans.values()) { width += val; } if (masterWidth == null) { masterWidth = width; } else { assertEquals(masterWidth.intValue(), width); } for (HtmlTableCell c : r.getCells()) { int rowSpan = c.getRowSpan(); Integer val = rowSpans.get(rowSpan); rowSpans.put(rowSpan, (val != null ? val : 0) + c.getColumnSpan()); } // shift rowSpans by one Map<Integer,Integer> nrs = new HashMap<Integer,Integer>(); for (Map.Entry<Integer,Integer> entry : rowSpans.entrySet()) { if (entry.getKey() > 1) { nrs.put(entry.getKey() - 1, entry.getValue()); } } rowSpans = nrs; } } @Issue("JENKINS-4245") @Test public void testLayout1() throws Exception { // 5*5*5*5*5 matrix MatrixProject p = createMatrixProject(); List<Axis> axes = new ArrayList<Axis>(); for (String name : new String[] {"a", "b", "c", "d", "e"}) { axes.add(new TextAxis(name, "1", "2", "3", "4")); } p.setAxes(new AxisList(axes)); assertRectangleTable(p); } @Issue("JENKINS-4245") @Test public void testLayout2() throws Exception { // 2*3*4*5*6 matrix MatrixProject p = createMatrixProject(); List<Axis> axes = new ArrayList<Axis>(); for (int i = 2; i <= 6; i++) { List<String> vals = new ArrayList<String>(); for (int j = 1; j <= i; j++) { vals.add(Integer.toString(j)); } axes.add(new TextAxis("axis" + i, vals)); } p.setAxes(new AxisList(axes)); assertRectangleTable(p); } /** * Makes sure that the configuration correctly roundtrips. */ @Test public void testConfigRoundtrip() throws Exception { j.jenkins.getJDKs().addAll(Arrays.asList( new JDK("jdk1.7","somewhere"), new JDK("jdk1.6","here"), new JDK("jdk1.5","there"))); Slave[] slaves = {j.createSlave(), j.createSlave(), j.createSlave()}; MatrixProject p = createMatrixProject(); p.getAxes().add(new JDKAxis(Arrays.asList("jdk1.6", "jdk1.5"))); p.getAxes().add(new LabelAxis("label1", Arrays.asList(slaves[0].getNodeName(), slaves[1].getNodeName()))); p.getAxes().add(new LabelAxis("label2", Arrays.asList(slaves[2].getNodeName()))); // make sure single value handling works OK AxisList o = new AxisList(p.getAxes()); j.configRoundtrip(p); AxisList n = p.getAxes(); assertEquals(o.size(),n.size()); for (int i = 0; i < o.size(); i++) { Axis oi = o.get(i); Axis ni = n.get(i); assertSame(oi.getClass(), ni.getClass()); assertEquals(oi.name,ni.name); assertEquals(oi.values,ni.values); } DefaultMatrixExecutionStrategyImpl before = new DefaultMatrixExecutionStrategyImpl(true, "foo", Result.UNSTABLE, null); p.setExecutionStrategy(before); j.configRoundtrip(p); j.assertEqualDataBoundBeans(p.getExecutionStrategy(), before); before = new DefaultMatrixExecutionStrategyImpl(false, null, null, null); p.setExecutionStrategy(before); j.configRoundtrip(p); j.assertEqualDataBoundBeans(p.getExecutionStrategy(), before); } @Test public void testLabelAxes() throws Exception { MatrixProject p = createMatrixProject(); Slave[] slaves = {j.createSlave(), j.createSlave(), j.createSlave(), j.createSlave()}; p.getAxes().add(new LabelAxis("label1", Arrays.asList(slaves[0].getNodeName(), slaves[1].getNodeName()))); p.getAxes().add(new LabelAxis("label2", Arrays.asList(slaves[2].getNodeName(), slaves[3].getNodeName()))); System.out.println(p.getLabels()); assertEquals(4, p.getLabels().size()); assertTrue(p.getLabels().contains(j.jenkins.getLabel("slave0&&slave2"))); assertTrue(p.getLabels().contains(j.jenkins.getLabel("slave1&&slave2"))); assertTrue(p.getLabels().contains(j.jenkins.getLabel("slave0&&slave3"))); assertTrue(p.getLabels().contains(j.jenkins.getLabel("slave1&&slave3"))); } /** * Quiettng down Hudson causes a dead lock if the parent is running but children is in the queue */ @Issue("JENKINS-4873") @Test public void testQuietDownDeadlock() throws Exception { MatrixProject p = createMatrixProject(); p.setAxes(new AxisList(new TextAxis("foo","1","2"))); p.setRunSequentially(true); // so that we can put the 2nd one in the queue final OneShotEvent firstStarted = new OneShotEvent(); final OneShotEvent buildCanProceed = new OneShotEvent(); p.getBuildersList().add(new TestBuilder() { @Override public boolean perform(AbstractBuild<?, ?> build, Launcher launcher, BuildListener listener) throws InterruptedException, IOException { firstStarted.signal(); buildCanProceed.block(); return true; } }); QueueTaskFuture<MatrixBuild> f = p.scheduleBuild2(0); // have foo=1 block to make sure the 2nd configuration is in the queue firstStarted.block(); // enter into the quiet down while foo=2 is still in the queue j.jenkins.doQuietDown(); buildCanProceed.signal(); // make sure foo=2 still completes. use time out to avoid hang j.assertBuildStatusSuccess(f.get(10,TimeUnit.SECONDS)); // MatrixProject scheduled after the quiet down shouldn't start try { Future g = p.scheduleBuild2(0); g.get(3,TimeUnit.SECONDS); fail(); } catch (TimeoutException e) { // expected } } @Issue("JENKINS-9009") @Test public void testTrickyNodeName() throws Exception { List<String> names = new ArrayList<String>(); names.add(j.createSlave("Sean's Workstation", null).getNodeName()); names.add(j.createSlave("John\"s Workstation", null).getNodeName()); MatrixProject p = createMatrixProject(); p.setAxes(new AxisList(new LabelAxis("label", names))); j.configRoundtrip(p); LabelAxis a = (LabelAxis) p.getAxes().find("label"); assertEquals(new HashSet<String>(a.getValues()), new HashSet<String>(names)); } @Issue("JENKINS-10108") @Test public void testTwoFileParams() throws Exception { MatrixProject p = createMatrixProject(); p.setAxes(new AxisList(new TextAxis("foo","1","2","3","4"))); p.addProperty(new ParametersDefinitionProperty( new FileParameterDefinition("a.txt",""), new FileParameterDefinition("b.txt","") )); File dir = tmp.getRoot(); List<ParameterValue> params = new ArrayList<ParameterValue>(); for (final String n : new String[] {"aaa", "bbb"}) { params.add(new FileParameterValue(n + ".txt", File.createTempFile(n, "", dir), n)); } QueueTaskFuture<MatrixBuild> f = p.scheduleBuild2(0,new LegacyCodeCause(),new ParametersAction(params)); j.assertBuildStatusSuccess(f.get(10,TimeUnit.SECONDS)); } @Issue("JENKINS-34758") @Test public void testParametersAsEnvOnChildren() throws Exception { MatrixProject p = createMatrixProject(); p.setAxes(new AxisList(new TextAxis("foo","1"))); p.addProperty(new ParametersDefinitionProperty( new StringParameterDefinition("MY_PARAM","") )); // must fail if $MY_PARAM or $foo are not defined in children p.getBuildersList().add(new Shell("set -eux; echo $MY_PARAM; echo $foo")); List<ParameterValue> params = new ArrayList<ParameterValue>(); params.add(new StringParameterValue("MY_PARAM", "value1")); QueueTaskFuture<MatrixBuild> f = p.scheduleBuild2(0, new LegacyCodeCause(), new ParametersAction(params)); j.assertBuildStatusSuccess(f.get()); } /** * Verifies that the concurrent build feature works, and makes sure * that each gets its own unique workspace. */ @Test public void testConcurrentBuild() throws Exception { j.jenkins.setNumExecutors(10); Method m = Jenkins.class.getDeclaredMethod("updateComputerList"); // TODO is this really necessary? m.setAccessible(true); m.invoke(j.jenkins); MatrixProject p = createMatrixProject(); p.setAxes(new AxisList(new TextAxis("foo","1","2"))); p.setConcurrentBuild(true); final CountDownLatch latch = new CountDownLatch(4); final Set<String> dirs = Collections.synchronizedSet(new HashSet<String>()); p.getBuildersList().add(new TestBuilder() { @Override public boolean perform(AbstractBuild<?, ?> build, Launcher launcher, BuildListener listener) throws IOException, InterruptedException { dirs.add(build.getWorkspace().getRemote()); FilePath marker = build.getWorkspace().child("file"); String name = build.getFullDisplayName(); marker.write(name, "UTF-8"); latch.countDown(); latch.await(); assertEquals(name,marker.readToString()); return true; } }); // should have gotten all unique names QueueTaskFuture<MatrixBuild> f1 = p.scheduleBuild2(0); // get one going f1.waitForStart(); QueueTaskFuture<MatrixBuild> f2 = p.scheduleBuild2(0); j.assertBuildStatusSuccess(f1.get()); j.assertBuildStatusSuccess(f2.get()); assertEquals(4, dirs.size()); } /** * Test that Actions are passed to configurations */ @Test public void testParameterActions() throws Exception { MatrixProject p = createMatrixProject(); ParametersDefinitionProperty pdp = new ParametersDefinitionProperty( new StringParameterDefinition("PARAM_A","default_a"), new StringParameterDefinition("PARAM_B","default_b") ); p.addProperty(pdp); List<ParameterValue> values = new ArrayList<ParameterValue>(); for (ParameterDefinition def : pdp.getParameterDefinitions()) { values.add(def.getDefaultParameterValue()); } ParametersAction pa = new ParametersAction(values); MatrixBuild build = p.scheduleBuild2(0,new LegacyCodeCause(), pa).get(); assertEquals(4, build.getRuns().size()); for(MatrixRun run : build.getRuns()) { ParametersAction pa1 = run.getAction(ParametersAction.class); assertNotNull(pa1); assertNotNull(pa1.getParameter("PARAM_A")); assertNotNull(pa1.getParameter("PARAM_B")); } } /** * Test that other Actions are passed to configurations * requires supported version of subversion plugin 1.43+ */ //~ public void testMatrixChildActions() throws Exception { //~ MatrixProject p = createMatrixProject(); //~ ParametersDefinitionProperty pdp = new ParametersDefinitionProperty( //~ new StringParameterDefinition("PARAM_A","default_a"), //~ new StringParameterDefinition("PARAM_B","default_b"), //~ ); //~ p.addProperty(pdp); //~ List<Action> actions = new ArrayList<Action>(); //~ actions.add(new RevisionParameterAction(new SvnInfo("http://example.com/svn/repo/",1234))); //~ actions.add(new ParametersAction( pdp.getParameterDefinitions().collect { return it.getDefaultParameterValue() } )); //~ MatrixBuild build = p.scheduleBuild2(0,new LegacyCodeCause(), actions).get(); //~ assertEquals(4, build.getRuns().size()); //~ for(MatrixRun run : build.getRuns()) { //~ ParametersAction pa1 = run.getAction(ParametersAction.class); //~ assertNotNull(pa1); //~ ["PARAM_A","PARAM_B"].each{ assertNotNull(pa1.getParameter(it)) }; //~ assertNotNull(run.getAction(RevisionParameterAction.class)); //~ } //~ } @Issue("JENKINS-15271") @LocalData @Test public void testUpgrade() throws Exception { MatrixProject p = j.jenkins.getItemByFullName("x", MatrixProject.class); assertNotNull(p); MatrixExecutionStrategy executionStrategy = p.getExecutionStrategy(); assertEquals(DefaultMatrixExecutionStrategyImpl.class, executionStrategy.getClass()); DefaultMatrixExecutionStrategyImpl defaultExecutionStrategy = (DefaultMatrixExecutionStrategyImpl) executionStrategy; assertFalse(defaultExecutionStrategy.isRunSequentially()); assertNull(defaultExecutionStrategy.getTouchStoneCombinationFilter()); assertNull(defaultExecutionStrategy.getTouchStoneResultCondition()); assertNull(defaultExecutionStrategy.getSorter()); } @Issue("JENKINS-17337") @Test public void reload() throws Exception { MatrixProject p = j.createProject(MatrixProject.class); AxisList axes = new AxisList(); axes.add(new TextAxis("p", "only")); p.setAxes(axes); String n = p.getFullName(); j.buildAndAssertSuccess(p); j.jenkins.reload(); p = j.jenkins.getItemByFullName(n, MatrixProject.class); assertNotNull(p); MatrixConfiguration c = p.getItem("p=only"); assertNotNull(c); assertNotNull(c.getBuildByNumber(1)); } /** * Given a small master and a big exclusive slave, the fair scheduling would prefer running the flyweight job * in the slave. But if the scheduler honors the EXCLUSIVE flag, then we should see it built on the master. * * Since there's a chance that the fair scheduling just so happens to pick up the master by chance, * we try multiple jobs to reduce the chance of that happening. */ @Issue("JENKINS-5076") @Test public void dontRunOnExclusiveSlave() throws Exception { List<MatrixProject> projects = new ArrayList<MatrixProject>(); for (int i = 0; i <= 10; i++) { MatrixProject m = j.createProject(MatrixProject.class); AxisList axes = new AxisList(); axes.add(new TextAxis("p", "only")); m.setAxes(axes); projects.add(m); } DumbSlave s = new DumbSlave("big", "this is a big slave", j.createTmpDir().getPath(), "20", EXCLUSIVE, "", j.createComputerLauncher(null), RetentionStrategy.NOOP); j.jenkins.addNode(s); s.toComputer().connect(false).get(); // connect this guy for (MatrixProject p : projects) { MatrixBuild b = j.assertBuildStatusSuccess(p.scheduleBuild2(2)); assertSame(b.getBuiltOn(), j.jenkins); } } @Test @Issue("JENKINS-13554") public void deletedLockedParentBuild() throws Exception { MatrixProject p = j.jenkins.createProject(MatrixProject.class, "project"); p.setAxes(new AxisList(new TextAxis("AXIS", "VALUE"))); MatrixBuild build = p.scheduleBuild2(0).get(); MatrixConfiguration c = p.getItem("AXIS=VALUE"); build.keepLog(); build.delete(); assertEquals("parent build is deleted", 0, p.getBuilds().size()); assertEquals("child build is deleted", 0, c.getBuilds().size()); } @Test @Issue("JENKINS-13554") public void deletedParentBuildWithLockedChildren() throws Exception { MatrixProject p = j.jenkins.createProject(MatrixProject.class, "project"); p.setAxes(new AxisList(new TextAxis("AXIS", "VALUE"))); MatrixBuild build = p.scheduleBuild2(0).get(); p.scheduleBuild2(0).get(); MatrixConfiguration c = p.getItem("AXIS=VALUE"); c.getBuildByNumber(2).delete(); // delete newest run assertNotNull(c.getBuildByNumber(1).getWhyKeepLog()); build.delete(); assertEquals("last parent build should be kept", 1, p.getBuilds().size()); assertEquals("child builds are deleted", 0, c.getBuilds().size()); } @Test @Issue("JENKINS-13554") public void discardBuilds() throws Exception { MatrixProject p = j.jenkins.createProject(MatrixProject.class, "discarder"); p.setAxes(new AxisList(new TextAxis("AXIS", "VALUE"))); // Only last build p.setBuildDiscarder(new LogRotator("", "1", "", "")); p.scheduleBuild2(0).get(); p.scheduleBuild2(0).get(); MatrixConfiguration c = p.getItem("AXIS=VALUE"); assertEquals("parent builds are discarded", 1, p.getBuilds().size()); assertEquals("child builds are discarded", 1, c.getBuilds().size()); } @Test public void discardArtifacts() throws Exception { MatrixProject p = j.jenkins.createProject(MatrixProject.class, "discarder"); p.setAxes(new AxisList(new TextAxis("AXIS", "VALUE"))); // Only last artifacts p.setBuildDiscarder(new LogRotator("", "", "", "1")); p.getBuildersList().add(new TestBuilder() { @Override public boolean perform(AbstractBuild<?, ?> build, Launcher launcher, BuildListener listener) throws InterruptedException, IOException { build.getWorkspace().child("artifact.zip").write("content", "UTF-8"); return true; } }); p.getPublishersList().add(new ArtifactArchiver("artifact.zip", "", false)); p.scheduleBuild2(0).get(); MatrixRun rotated = p.getItem("AXIS=VALUE").getLastBuild(); p.scheduleBuild2(0).get(); MatrixRun last = p.getItem("AXIS=VALUE").getLastBuild(); assertTrue("Artifacts are discarded", rotated.getArtifacts().isEmpty()); assertEquals(1, last.getArtifacts().size()); } @Test @Issue("JENKINS-13554") public void deleteBuildWithChildrenOverCLI() throws Exception { MatrixProject p = j.jenkins.createProject(MatrixProject.class, "project"); p.setAxes(new AxisList(new TextAxis("AXIS", "VALUE"))); p.scheduleBuild2(0).get(); CLICommandInvoker invoker = new CLICommandInvoker(j, new DeleteBuildsCommand()); CLICommandInvoker.Result result = invoker.invokeWithArgs("project", "1"); assertThat(result, CLICommandInvoker.Matcher.succeeded()); assertEquals(0, p.getBuilds().size()); assertEquals(0, p.getItem("AXIS=VALUE").getBuilds().size()); } }