Skip to content

Commit

Permalink
Merge pull request #878 from Vlatombe/runMatchers
Browse files Browse the repository at this point in the history
  • Loading branch information
Vlatombe authored Nov 27, 2024
2 parents f2a982b + 8bd3f6c commit 8f64d70
Show file tree
Hide file tree
Showing 3 changed files with 219 additions and 1 deletion.
138 changes: 138 additions & 0 deletions src/main/java/jenkins/test/RunMatchers.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
/*
* The MIT License
*
* Copyright 2024 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 jenkins.test;

import edu.umd.cs.findbugs.annotations.NonNull;
import hudson.model.Result;
import hudson.model.Run;
import java.io.IOException;
import org.hamcrest.Description;
import org.hamcrest.Matcher;
import org.hamcrest.TypeSafeMatcher;
import org.jvnet.hudson.test.JenkinsRule;

/**
* Matchers for {@link Run} objects.
*/
public final class RunMatchers {
private RunMatchers() {}

/**
* Creates a matcher checking whether a build is successful.
*/
public static Matcher<Run<?,?>> isSuccessful() {
return new RunResultMatcher(Result.SUCCESS);
}

/**
* Creates a matcher checking whether a build has a specific outcome.
*/
public static Matcher<Run<?,?>> hasStatus(Result result) {
return new RunResultMatcher(result);
}

/**
* Creates a matcher checking whether build logs contain a specific message.
* @param message the expected message
*/
public static Matcher<Run<?,?>> logContains(String message) {
return new RunLogMatcher(message);
}

/**
* Creates a matcher checking whether a build has completed.
*/
public static Matcher<Run<?,?>> completed() {
return new CompletedRunMatcher();
}

private static class RunResultMatcher extends TypeSafeMatcher<Run<?,?>> {
@NonNull
private final Result expectedResult;

public RunResultMatcher(@NonNull Result expectedResult) {
this.expectedResult = expectedResult;
}

@Override
public void describeTo(Description description) {
description.appendText("a build with result " + expectedResult);
}

@Override
protected boolean matchesSafely(Run run) {
return run.getResult() == expectedResult;
}

@Override
protected void describeMismatchSafely(Run<?, ?> item, Description mismatchDescription) {
mismatchDescription.appendText("was ").appendValue(item.getResult());
}
}

private static class RunLogMatcher extends TypeSafeMatcher<Run<?, ?>> {
@NonNull
private final String message;

private RunLogMatcher(@NonNull String message) {
this.message = message;
}

@Override
protected boolean matchesSafely(Run<?, ?> run) {
try {
return JenkinsRule.getLog(run).contains(message);
} catch (IOException x) {
return false;
}
}

@Override
protected void describeMismatchSafely(Run<?, ?> item, Description mismatchDescription) {
mismatchDescription.appendText("was \n");
try {
mismatchDescription.appendText(JenkinsRule.getLog(item));
} catch (IOException e) {
mismatchDescription.appendText("<unreadable>");
}
}

@Override
public void describeTo(Description description) {
description.appendText("log containing ").appendValue(message);
}
}

private static class CompletedRunMatcher extends TypeSafeMatcher<Run<?, ?>> {
@Override
protected boolean matchesSafely(Run<?, ?> run) {
return !run.isLogUpdated();
}

@Override
public void describeTo(Description description) {
description.appendText("a completed build");
}
}
}
10 changes: 9 additions & 1 deletion src/main/java/org/jvnet/hudson/test/JenkinsRule.java
Original file line number Diff line number Diff line change
Expand Up @@ -1509,6 +1509,8 @@ public <C extends Cloud> C configRoundtrip(C cloud) throws Exception {

/**
* Asserts that the outcome of the build is a specific outcome.
* <p>
* Consider {@link jenkins.test.RunMatchers#hasStatus(Result)} as an alternative.
*/
public <R extends Run> R assertBuildStatus(Result status, R r) throws Exception {
if (status == r.getResult()) {
Expand Down Expand Up @@ -1583,13 +1585,17 @@ public FreeStyleBuild buildAndAssertSuccess(@NonNull FreeStyleProject job) throw

/**
* Asserts that the console output of the build contains the given substring.
* <p>
* Consider {@link jenkins.test.RunMatchers#logContains(String)} as an alternative.
*/
public void assertLogContains(String substring, Run run) throws IOException {
assertThat(getLog(run), containsString(substring));
}

/**
* Asserts that the console output of the build does not contain the given substring.
* <p>
* Consider {@link org.hamcrest.Matchers#not} and {@link jenkins.test.RunMatchers#logContains(String)} as an alternative.
*/
public void assertLogNotContains(String substring, Run run) throws IOException {
assertThat(getLog(run), not(containsString(substring)));
Expand All @@ -1613,6 +1619,9 @@ public static String getLog(Run run) throws IOException {
/**
* Waits for a build to complete.
* Useful in conjunction with {@link BuildWatcher}.
* <p>
* As an alternative, if using <a href="https://github.com/awaitility/awaitility">Awaitibility</a>, you can use {@code await().until(() -> r, RunMatchers.completed());}
*
* @return the same build, once done
* @since 1.607
*/
Expand All @@ -1629,7 +1638,6 @@ public <R extends Run<?,?>> R waitForCompletion(R r) throws InterruptedException
}
}
}
// Could be using com.jayway.awaitility:awaitility but it seems like overkill here.
while (r.isLogUpdated()) {
Thread.sleep(100);
}
Expand Down
72 changes: 72 additions & 0 deletions src/test/java/jenkins/test/RunMatchersTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/*
* The MIT License
*
* Copyright 2024 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 jenkins.test;

import static jenkins.test.RunMatchers.completed;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.allOf;
import static org.hamcrest.Matchers.not;
import static jenkins.test.RunMatchers.logContains;
import static jenkins.test.RunMatchers.hasStatus;
import static jenkins.test.RunMatchers.isSuccessful;

import hudson.Functions;
import hudson.model.Result;
import hudson.tasks.BatchFile;
import hudson.tasks.Shell;
import org.junit.Rule;
import org.junit.Test;
import org.jvnet.hudson.test.FailureBuilder;
import org.jvnet.hudson.test.JenkinsRule;
import org.jvnet.hudson.test.SleepBuilder;

public class RunMatchersTest {
@Rule
public JenkinsRule j = new JenkinsRule();

@Test
public void buildSuccessful() throws Exception {
var p = j.createFreeStyleProject();
p.getBuildersList().add(new SleepBuilder(1000));
var b = p.scheduleBuild2(0).waitForStart();
assertThat(j.waitForCompletion(b), allOf(completed(), isSuccessful()));
}

@Test
public void buildFailure() throws Exception {
var p = j.createFreeStyleProject();
p.getBuildersList().add(new FailureBuilder());
var b = p.scheduleBuild2(0).waitForStart();
assertThat(j.waitForCompletion(b), hasStatus(Result.FAILURE));
}

@Test
public void assertThatLogContains() throws Exception {
var p = j.createFreeStyleProject();
p.getBuildersList().add(Functions.isWindows() ? new BatchFile("echo hello") : new Shell("echo hello"));
var b = p.scheduleBuild2(0).get();
System.out.println(b.getDisplayName() + " completed");
assertThat(b, allOf(logContains("echo hello"), not(logContains("echo bye"))));
}
}

0 comments on commit 8f64d70

Please sign in to comment.