blob: 406fb1196c9198d99762c016f597590beea42af0 [file] [log] [blame]
package com.smartcodeltd.jenkinsci.plugins.buildmonitor.viewmodel;
import com.smartcodeltd.jenkinsci.plugins.buildmonitor.facade.RelativeLocation;
import com.smartcodeltd.jenkinsci.plugins.buildmonitor.viewmodel.plugins.Augmentation;
import com.smartcodeltd.jenkinsci.plugins.buildmonitor.viewmodel.plugins.BuildAugmentor;
import com.smartcodeltd.jenkinsci.plugins.buildmonitor.viewmodel.plugins.bfa.Analysis;
import com.smartcodeltd.jenkinsci.plugins.buildmonitor.viewmodel.plugins.claim.Claim;
import com.smartcodeltd.jenkinsci.plugins.buildmonitor.viewmodel.syntacticsugar.BuildStateRecipe;
import com.smartcodeltd.jenkinsci.plugins.buildmonitor.viewmodel.syntacticsugar.JobStateRecipe;
import hudson.model.Job;
import hudson.model.Result;
import org.junit.Ignore;
import org.junit.Test;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;
import static com.smartcodeltd.jenkinsci.plugins.buildmonitor.viewmodel.syntacticsugar.Loops.asFollows;
import static com.smartcodeltd.jenkinsci.plugins.buildmonitor.viewmodel.syntacticsugar.TimeMachine.assumeThat;
import static com.smartcodeltd.jenkinsci.plugins.buildmonitor.viewmodel.syntacticsugar.TimeMachine.assumeThatCurrentTime;
import static hudson.model.Result.*;
import static org.hamcrest.CoreMatchers.hasItems;
import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.hasItem;
import static org.hamcrest.collection.IsCollectionWithSize.hasSize;
import static org.hamcrest.core.Is.is;
import static org.junit.Assert.assertThat;
import static org.mockito.Mockito.*;
/**
* @author Jan Molak
*/
public class JobViewTest {
private static final String theName = "some-TLAs-followed-by-a-project-name";
private static final String displayName = "Pretty name that has some actual meaning";
private RelativeLocation relative = mock(RelativeLocation.class);
private JobView view;
/*
* By the way, if you were not aware of this: the configuration page of each job has an "Advanced Project Options"
* section, where you can set a user-friendly "Display Name"
*/
@Test
public void delegates_the_process_of_determining_the_relative_job_name() {
when(relative.name()).thenReturn(theName);
view = JobView.of(a(job().withName(theName)), relative);
assertThat(view.name(), is(theName));
assertThat(view.toString(), is(theName));
verify(relative, times(2)).name();
}
@Test
public void delegates_the_process_of_determining_the_relative_url() {
String expectedUrl = "job/" + theName;
when(relative.url()).thenReturn(expectedUrl);
view = JobView.of(a(job().withName(theName).withDisplayName(displayName)), relative);
assertThat(view.url(), is(expectedUrl));
verify(relative, times(1)).url();
}
@Test
public void should_know_current_build_number() {
view = JobView.of(a(job().whereTheLast(build().numberIs(5))));
assertThat(view.lastBuildName(), is("#5"));
}
@Test
public void should_use_build_name_if_its_known() {
view = JobView.of(a(job().whereTheLast(build().nameIs("1.3.4+build.15"))));
assertThat(view.lastBuildName(), is("1.3.4+build.15"));
}
@Test
public void should_know_the_url_of_the_last_build() {
view = JobView.of(
a(job().whereTheLast(build().numberIs(22))),
locatedAt("job/project-name")
);
assertThat(view.lastBuildUrl(), is("job/project-name/22/"));
}
/*
* Should be able to measure the progress
*/
@Test
public void progress_of_a_not_started_job_should_be_zero() {
view = JobView.of(a(job()));
assertThat(view.progress(), is(0));
}
@Test
public void progress_of_a_finished_job_should_be_zero() {
view = JobView.of(a(job().whereTheLast(build().finishedWith(SUCCESS))));
assertThat(view.progress(), is(0));
}
@Test
public void progress_of_a_nearly_finished_job_should_be_100() throws Exception {
view = JobView.of(
a(job().whereTheLast(build().isStillBuilding().startedAt("12:00:00").andUsuallyTakes(0))),
assumingThatCurrentTimeIs("12:00:00")
);
assertThat(view.progress(), is(100));
}
@Test
public void progress_of_a_job_thats_taking_longer_than_expected_should_be_100() throws Exception {
view = JobView.of(
a(job().whereTheLast(build().isStillBuilding().startedAt("12:00:00").andUsuallyTakes(5))),
assumingThatCurrentTimeIs("12:20:00")
);
assertThat(view.progress(), is(100));
}
@Test
public void should_calculate_the_progress_of_a_running_job() throws Exception {
view = JobView.of(
a(job().whereTheLast(build().isStillBuilding().startedAt("13:10:00").andUsuallyTakes(5))),
assumingThatCurrentTimeIs("13:11:00")
);
assertThat(view.progress(), is(20));
}
/*
* Elapsed time
*/
@Test
public void should_know_how_long_a_build_has_been_running_for() throws Exception {
String startTime = "13:10:00",
sixSecondsLater = "13:10:06",
twoAndHalfMinutesLater = "13:12:30",
anHourAndHalfLater = "14:40:00";
Date currentTime = assumeThatCurrentTime().is(startTime);
view = JobView.of(
a(job().whereTheLast(build().startedAt(startTime).isStillBuilding())),
currentTime
);
assumeThat(currentTime).is(sixSecondsLater);
assertThat(view.lastBuildDuration(), is("6s"));
assumeThat(currentTime).is(twoAndHalfMinutesLater);
assertThat(view.lastBuildDuration(), is("2m 30s"));
assumeThat(currentTime).is(anHourAndHalfLater);
assertThat(view.lastBuildDuration(), is("1h 30m 0s"));
}
@Test
public void should_know_how_long_the_last_build_took_once_its_finished() throws Exception {
view = JobView.of(a(job().whereTheLast(build().finishedWith(SUCCESS).andTook(3))));
assertThat(view.lastBuildDuration(), is("3m 0s"));
}
@Test
public void should_not_say_anything_about_the_duration_if_the_build_hasnt_run_yet() throws Exception {
view = JobView.of(a(job()));
assertThat(view.lastBuildDuration(), is(""));
}
@Test
public void should_know_how_long_the_next_build_is_supposed_to_take() throws Exception {
view = JobView.of(a(job().whereTheLast(build().finishedWith(SUCCESS).andUsuallyTakes(5))));
assertThat(view.estimatedDuration(), is("5m 0s"));
}
@Test
public void should_not_say_anything_if_it_doesnt_know_how_long_the_next_build_is_supposed_to_take() throws Exception {
view = JobView.of(a(job()));
assertThat(view.estimatedDuration(), is(""));
}
/*
* Should produce a meaningful status description that can be used in the CSS
*/
@Test
public void should_describe_the_job_as_successful_if_the_last_build_succeeded() {
view = JobView.of(a(job().whereTheLast(build().finishedWith(SUCCESS))));
assertThat(view.status(), containsString("successful"));
}
@Test
public void should_describe_the_job_as_failing_if_the_last_build_failed() {
for (Result result : asFollows(FAILURE, ABORTED)) {
view = JobView.of(a(job().whereTheLast(build().finishedWith(result))));
assertThat(view.status(), containsString("failing"));
}
}
@Test
public void should_describe_the_job_as_unstable_if_the_last_build_is_unstable() {
view = JobView.of(a(job().whereTheLast(build().finishedWith(UNSTABLE))));
assertThat(view.status(), containsString("unstable"));
}
@Test
public void should_describe_the_state_of_the_job_as_unknown_when_it_is_yet_to_be_determined() {
view = JobView.of(a(job()));
assertThat(view.status(), containsString("unknown"));
}
@Test
public void should_describe_the_job_as_running_if_it_is_running() {
List<JobView> views = asFollows(
JobView.of(a(job().whereTheLast(build().hasntStartedYet()))),
JobView.of(a(job().whereTheLast(build().isStillBuilding()))),
JobView.of(a(job().whereTheLast(build().isStillUpdatingTheLog())))
);
for (JobView view : views) {
assertThat(view.status(), containsString("running"));
}
}
@Test
public void should_describe_the_job_as_running_and_successful_if_it_is_running_and_the_previous_build_succeeded() {
List<JobView> views = asFollows(
JobView.of(a(job().
whereTheLast(build().hasntStartedYet()).
andThePrevious(build().finishedWith(SUCCESS)))),
JobView.of(a(job().
whereTheLast(build().isStillBuilding()).
andThePrevious(build().finishedWith(SUCCESS)))),
JobView.of(a(job().
whereTheLast(build().isStillUpdatingTheLog()).
andThePrevious(build().finishedWith(SUCCESS))))
);
// I could do this instead of having two assertions:
// assertThat(view.status(), both(containsString("successful")).and(containsString("running")));
// but then it would require Java 7
for (JobView view : views) {
assertThat(view.status(), containsString("successful"));
assertThat(view.status(), containsString("running"));
}
}
@Test
public void should_describe_the_job_as_running_and_failing_if_it_is_running_and_the_previous_build_failed() {
List<JobView> views = asFollows(
JobView.of(a(job().
whereTheLast(build().hasntStartedYet()).
andThePrevious(build().finishedWith(FAILURE)))),
JobView.of(a(job().
whereTheLast(build().isStillBuilding()).
andThePrevious(build().finishedWith(FAILURE)))),
JobView.of(a(job().
whereTheLast(build().isStillUpdatingTheLog()).
andThePrevious(build().finishedWith(FAILURE))))
);
for (JobView view : views) {
assertThat(view.status(), containsString("failing"));
assertThat(view.status(), containsString("running"));
}
}
/*
* Parallel build execution handling
*/
@Test
public void should_describe_the_job_as_successful_when_there_are_several_builds_running_in_parallel_and_the_last_completed_was_successful() {
view = JobView.of(a(job().
whereTheLast(build().isStillBuilding()).
andThePrevious(build().isStillBuilding()).
andThePrevious(build().finishedWith(SUCCESS))));
assertThat(view.status(), containsString("successful"));
}
@Test
public void should_describe_the_job_as_failing_when_there_are_several_builds_running_in_parallel_and_the_last_completed_failed() {
view = JobView.of(a(job().
whereTheLast(build().isStillBuilding()).
andThePrevious(build().isStillBuilding()).
andThePrevious(build().finishedWith(FAILURE))));
assertThat(view.status(), containsString("failing"));
}
@Test
public void should_describe_the_job_as_claimed_if_someone_claimed_last_build_failures() {
view = JobView.of(
a(job().whereTheLast(build().finishedWith(FAILURE).andWasClaimedBy("Adam", "sorry, I broke it, fixing now"))),
augmentedWith(Claim.class)
);
assertThat(view.status(), containsString("claimed"));
}
/*
* Should know who broke the build
*/
@Test
public void should_know_who_broke_the_build() {
view = JobView.of(a(job().whereTheLast(build().wasBrokenBy("Adam", "Ben"))));
assertThat(view.culprits(), hasSize(2));
assertThat(view.culprits(), hasItems("Adam", "Ben"));
}
@Test
public void should_know_who_has_been_committing_over_broken_build() {
view = JobView.of(a(job().
whereTheLast(build().wasBrokenBy("Adam")).
andThePrevious(build().wasBrokenBy("Ben", "Connor")).
andThePrevious(build().wasBrokenBy("Daniel")).
andThePrevious(build().succeededThanksTo("Errol"))));
assertThat(view.culprits(), hasSize(4));
assertThat(view.culprits(), hasItems("Adam", "Ben", "Connor", "Daniel"));
assertThat(view.culprits(), not(hasItem("Errol")));
}
@Test
public void should_only_mention_each_culprit_once() {
view = JobView.of(a(job().
whereTheLast(build().wasBrokenBy("Adam")).
andThePrevious(build().wasBrokenBy("Adam", "Ben")).
andThePrevious(build().wasBrokenBy("Ben", "Connor"))));
assertThat(view.culprits(), hasSize(3));
assertThat(view.culprits(), hasItems("Adam", "Ben", "Connor"));
}
@Test
public void should_not_mention_any_culprits_if_the_build_was_successful() {
view = JobView.of(a(job().whereTheLast(build().succeededThanksTo("Adam"))));
assertThat(view.culprits(), hasSize(0));
}
@Test
public void should_not_mention_any_culprits_if_the_build_was_successful_and_is_still_running() {
view = JobView.of(a(job().
whereTheLast(build().isStillBuilding()).
andThePrevious(build().succeededThanksTo("Adam"))));
assertThat(view.culprits(), hasSize(0));
}
@Test
public void should_indicate_culprits_if_the_build_is_failing_and_not_claimed() {
view = JobView.of(a(job().
whereTheLast(build().wasBrokenBy("Adam"))),
augmentedWith(Claim.class));
assertThat(view.shouldIndicateCulprits(), is(true));
assertThat(view.culprits(), hasSize(1));
}
@Test
public void should_not_indicate_any_culprits_if_the_build_was_failing_but_is_now_claimed() {
view = JobView.of(a(job().
whereTheLast(build().wasBrokenBy("Adam").andWasClaimedBy("Ben", "Helping out Adam"))),
augmentedWith(Claim.class));
assertThat(view.shouldIndicateCulprits(), is(false));
assertThat(view.culprits(), hasSize(1));
}
/*
* Should know who claimed a broken build
*/
@Test
public void should_know_if_a_failing_build_has_been_claimed() throws Exception {
String ourPotentialHero = "Adam",
theReason = "I broke it, sorry, fixing now";
view = JobView.of(
a(job().whereTheLast(build().finishedWith(FAILURE).andWasClaimedBy(ourPotentialHero, theReason))),
augmentedWith(Claim.class)
);
assertThat(view.isClaimed(), is(true));
assertThat(view.claimAuthor(), is(ourPotentialHero));
assertThat(view.claimReason(), is(theReason));
}
@Test
public void should_describe_known_failures() {
String rogueAi = "Pod bay doors didn't open";
view = JobView.of(
a(job().whereTheLast(build().finishedWith(FAILURE).andKnownFailures(rogueAi))),
augmentedWith(Analysis.class));
assertThat(view.hasKnownFailures(), is(true));
assertThat(view.knownFailures(), contains(rogueAi));
}
@Test
public void public_api_should_return_reasonable_defaults_for_jobs_that_never_run() throws Exception {
view = JobView.of(a(job().thatHasNeverRun()));
assertThat(view.lastBuildName(), is(""));
assertThat(view.lastBuildUrl(), is(""));
assertThat(view.lastBuildDuration(), is(""));
assertThat(view.estimatedDuration(), is(""));
assertThat(view.progress(), is(0));
assertThat(view.shouldIndicateCulprits(), is(false));
assertThat(view.culprits(), hasSize(0));
assertThat(view.status(), is("unknown"));
assertThat(view.isClaimed(), is(false));
assertThat(view.hasKnownFailures(), is(false));
}
/*
* Syntactic sugar
*/
private JobStateRecipe job() {
return new JobStateRecipe();
}
private Job<?, ?> a(JobStateRecipe recipe) {
return recipe.execute();
}
private BuildStateRecipe build() {
return new BuildStateRecipe();
}
private Date assumingThatCurrentTimeIs(String currentTime) throws ParseException {
Date currentDate = new SimpleDateFormat("H:m:s").parse(currentTime);
Date systemTime = mock(Date.class);
when(systemTime.getTime()).thenReturn(currentDate.getTime());
return systemTime;
}
private RelativeLocation locatedAt(String url) {
RelativeLocation location = mock(RelativeLocation.class);
when(location.url()).thenReturn(url);
return location;
}
private BuildAugmentor augmentedWith(Class<? extends Augmentation>... augmentationsToSupport) {
BuildAugmentor augmentor = new BuildAugmentor();
for (Class<? extends Augmentation> augmentation : augmentationsToSupport) {
augmentor.support(augmentation);
}
return augmentor;
}
}