package hudson.model; import hudson.model.Descriptor.FormException; import hudson.model.LoadStatistics.LoadStatisticsUpdater; import hudson.model.MultiStageTimeSeries.TimeScale; import hudson.model.Node.Mode; import hudson.model.Queue.WaitingItem; import hudson.model.labels.LabelAssignmentAction; import hudson.model.queue.SubTask; import hudson.slaves.DumbSlave; import hudson.slaves.NodeProperty; import hudson.slaves.RetentionStrategy; import org.junit.After; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.jvnet.hudson.test.JenkinsRule; import java.io.IOException; import java.util.Collections; import static org.hamcrest.Matchers.greaterThan; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; /** * Test that a {@link Label}'s {@link LoadStatistics#queueLength} correctly * reflects the queued builds. */ public class LabelLoadStatisticsQueueLengthTest { private static final String LABEL_STRING = LabelLoadStatisticsQueueLengthTest.class .getSimpleName(); private static final String ALT_LABEL_STRING = LABEL_STRING + "alt"; private static final String PROJECT_NAME = LabelLoadStatisticsQueueLengthTest.class .getSimpleName(); private static final String PARAMETER_NAME = "parameter"; private static final Cause CAUSE = new Cause() { @Override public String getShortDescription() { return "Build caused by test."; } }; @Rule public JenkinsRule j = new JenkinsRule(); /** * Setup */ @Before public void createNodeWithLabels() throws IOException, FormException { // Node with this test's labels is required in order for the labels to // be considered valid. DumbSlave node = new DumbSlave( LabelLoadStatisticsQueueLengthTest.class.getSimpleName(), "", "", "1", Mode.NORMAL, LABEL_STRING + " " + ALT_LABEL_STRING, null, RetentionStrategy.NOOP, Collections.<NodeProperty<?>> emptyList()); j.getInstance().addNode(node); } /** * Teardown */ @After public void clearQueue() { j.getInstance().getQueue().clear(); } /** * Verify that when a {@link Label} is assigned to a queued build using a * {@link LabelAssignmentAction}, that label's * {@link LoadStatistics#queueLength} reflects the number of items in the * queue, and continues to do so if the {@link Project}'s label is changed. */ @Test public void queueLengthReflectsBuildableItemsAssignedLabel() throws Exception { final Label label = Label.get(LABEL_STRING); final Label altLabel = Label.get(ALT_LABEL_STRING); FreeStyleProject project = createTestProject(); // Before queueing the builds the rolling queue length should be 0. assertTrue( "Initially the rolling queue length for the label is 0.", label.loadStatistics.queueLength.getLatest(TimeScale.SEC10) == 0f); // Add the job to the build queue several times with an assigned label. for (int i = 0; i < 3; i++) { project.scheduleBuild(0, CAUSE, new LabelAssignmentActionImpl(), new ParametersAction(new StringParameterValue( PARAMETER_NAME, String.valueOf(i)))); } // Verify that the real queue length is 3. assertEquals("The job is queued as often as it was scheduled.", 3, j .getInstance().getQueue().getItems(project).size()); maintainQueueAndForceRunOfLoadStatisticsUpdater(project); assertEquals("The job is still queued as often as it was scheduled.", 3, j .getInstance().getQueue().getItems(project).size()); float labelQueueLength = label.loadStatistics.queueLength .getLatest(TimeScale.SEC10); assertThat("After LoadStatisticsUpdater runs, the queue length load statistic for the label is greater than 0.", labelQueueLength, greaterThan(0f)); // Assign an alternate label to the project and update the load stats. project.setAssignedLabel(altLabel); maintainQueueAndForceRunOfLoadStatisticsUpdater(project); // Verify that the queue length load stat continues to reflect the labels assigned to the items in the queue. float labelQueueLengthNew = label.loadStatistics.queueLength .getLatest(TimeScale.SEC10); assertThat("After assigning an alternate label to the job, the queue length load statistic for the " + "queued builds should not decrease.", labelQueueLengthNew, greaterThan(labelQueueLength)); } /** * Verify that when a {@link Label} is assigned to a {@link Project}, that * label's {@link LoadStatistics#queueLength} reflects the number of items * in the queue scheduled for that project, and updates if the project's * label is changed. */ @Test public void queueLengthReflectsJobsAssignedLabel() throws Exception { final Label label = Label.get(LABEL_STRING); final Label altLabel = Label.get(ALT_LABEL_STRING); FreeStyleProject project = createTestProject(); // Assign a label to the job. project.setAssignedLabel(label); // Before queueing the builds the rolling queue lengths should be 0. assertTrue( "Initially the rolling queue length for the label is 0.", label.loadStatistics.queueLength.getLatest(TimeScale.SEC10) == 0f); assertTrue( "Initially the rolling queue length for the alt label is 0.", altLabel.loadStatistics.queueLength.getLatest(TimeScale.SEC10) == 0f); // Add the job to the build queue several times. for (int i = 0; i < 3; i++) { project.scheduleBuild(0, CAUSE, new ParametersAction(new StringParameterValue( PARAMETER_NAME, String.valueOf(i)))); } // Verify that the real queue length is 3. assertEquals("The job is queued as often as it was scheduled.", 3, j .getInstance().getQueue().getItems(project).size()); maintainQueueAndForceRunOfLoadStatisticsUpdater(project); float labelQueueLength = label.loadStatistics.queueLength .getLatest(TimeScale.SEC10); assertTrue( "After LoadStatisticsUpdater runs, the queue length load statistic for the label is greater than 0.", labelQueueLength > 0f); // Assign an alternate label to the job and update the load stats. project.setAssignedLabel(altLabel); maintainQueueAndForceRunOfLoadStatisticsUpdater(project); // Verify that the queue length load stats of the labels reflect the newly project's newly assigned label. float labelQueueLengthNew = label.loadStatistics.queueLength .getLatest(TimeScale.SEC10); assertTrue( "After assigning an alternate label to the job, the queue length load statistic for the queued builds should decrease.", labelQueueLengthNew < labelQueueLength); float altLabelQueueLength = altLabel.loadStatistics.queueLength .getLatest(TimeScale.SEC10); assertTrue( "After assigning an alternate label to the job, the queue length load statistic for the alternate label should be greater than 0.", altLabelQueueLength > 0f); } private FreeStyleProject createTestProject() throws IOException { FreeStyleProject project = j.createFreeStyleProject(PROJECT_NAME); // In order to queue multiple builds of the job it needs to be // parameterised. project.addProperty(new ParametersDefinitionProperty( new StringParameterDefinition(PARAMETER_NAME, "0"))); // Prevent builds from being queued as blocked by allowing concurrent // builds. project.setConcurrentBuild(true); return project; } private void maintainQueueAndForceRunOfLoadStatisticsUpdater( FreeStyleProject project) throws InterruptedException { Queue queue = j.getInstance().getQueue(); // ensure the queued items are in the right state, i.e. buildable rather // than waiting int count = 0; while (queue.getItem(project) instanceof WaitingItem && ++count < 100) { queue.maintain(); Thread.sleep(10); } assertTrue( "After waiting there are buildable items in the build queue.", queue.getBuildableItems().size() > 0); // create a LoadStatisticsUpdater, and run it in order to update the // load stats for all the labels LoadStatisticsUpdater updater = new LoadStatisticsUpdater(); updater.doRun(); } private static class LabelAssignmentActionImpl implements LabelAssignmentAction { @Override public String getIconFileName() { return null; } @Override public String getDisplayName() { return LABEL_STRING + " LabelAssignmentAction"; } @Override public String getUrlName() { return null; } @Override public Label getAssignedLabel(SubTask p_task) { return Label.get(LABEL_STRING); } } }