提交 c24a2b1e 编写于 作者: N Nicolas De loof

Merge pull request #294 from stephenc/master

Nice improvement to Jenkins testability
......@@ -25,7 +25,7 @@
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<artifactId>junit-dep</artifactId>
<scope>test</scope>
</dependency>
<dependency>
......
......@@ -422,7 +422,7 @@ THE SOFTWARE.
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<artifactId>junit-dep</artifactId>
<scope>test</scope>
</dependency>
<dependency><!-- needed by Jelly -->
......
......@@ -353,7 +353,7 @@ THE SOFTWARE.
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<artifactId>junit-dep</artifactId>
<scope>test</scope>
</dependency>
<dependency>
......
......@@ -66,7 +66,7 @@
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<artifactId>junit-dep</artifactId>
<version>4.10</version>
<scope>test</scope>
</dependency>
......
......@@ -130,10 +130,22 @@ THE SOFTWARE.
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<artifactId>junit-dep</artifactId>
<version>4.10</version>
</dependency>
<dependency>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-core</artifactId>
<version>1.2.1</version>
</dependency>
<dependency>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-library</artifactId>
<version>1.2.1</version>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
......
......@@ -89,7 +89,11 @@ THE SOFTWARE.
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<artifactId>junit-dep</artifactId>
</dependency>
<dependency>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-library</artifactId>
</dependency>
<dependency>
<groupId>org.jenkins-ci</groupId>
......
/*
* The MIT License
*
* Copyright (c) 2010, InfraDNA, 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 org.jvnet.hudson.test;
import hudson.Extension;
import hudson.model.AbstractDescribableImpl;
import hudson.model.Descriptor;
import hudson.slaves.ComputerConnector;
import hudson.slaves.ComputerConnectorDescriptor;
import org.kohsuke.stapler.StaplerRequest;
import javax.servlet.ServletException;
import java.io.IOException;
import java.util.List;
/**
* Test bed to verify the configuration roundtripness of the {@link ComputerConnector}.
*
* @author Kohsuke Kawaguchi
* @see HudsonTestCase#computerConnectorTester
*/
public class JenkinsComputerConnectorTester extends AbstractDescribableImpl<JenkinsComputerConnectorTester> {
public final JenkinsRule jenkinsRule;
public ComputerConnector connector;
public JenkinsComputerConnectorTester(JenkinsRule testCase) {
this.jenkinsRule = testCase;
}
public void doConfigSubmit(StaplerRequest req) throws IOException, ServletException {
connector = req.bindJSON(ComputerConnector.class, req.getSubmittedForm().getJSONObject("connector"));
}
public List getConnectorDescriptors() {
return ComputerConnectorDescriptor.all();
}
@Extension
public static class DescriptorImpl extends Descriptor<JenkinsComputerConnectorTester> {
@Override
public String getDisplayName() {
return "";
}
}
}
package org.jvnet.hudson.test;
import hudson.util.FormValidation;
import org.hamcrest.BaseMatcher;
import org.hamcrest.CoreMatchers;
import org.hamcrest.Description;
import org.hamcrest.Matcher;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Modifier;
import static org.hamcrest.Matchers.allOf;
/**
* Some handy matchers.
*
* @author Stephen Connolly
*/
public class JenkinsMatchers {
public static Matcher<Class> privateConstructorThrows(Class<? extends Throwable> cause) {
return new PrivateConstructorThrows(cause);
}
public static Matcher<FormValidation> causedBy(Class<? extends Throwable> cause) {
return new FormValidationCauseMatcher(cause);
}
public static Matcher<FormValidation> hasKind(FormValidation.Kind kind) {
return new FormValidationKindMatcher(kind);
}
private static class PrivateConstructorThrows extends BaseMatcher<Class> {
private final Class<? extends Throwable> cause;
public PrivateConstructorThrows(Class<? extends Throwable> cause) {
this.cause = cause;
}
public boolean matches(Object o) {
Class<?> clazz = (Class<?>) o;
try {
Constructor<?> c = clazz.getDeclaredConstructor();
if (!Modifier.isPrivate(c.getModifiers())) {
return false;
}
boolean accessible = c.isAccessible();
try {
c.setAccessible(true);
c.newInstance();
} catch (InvocationTargetException e) {
Throwable t = e;
while (t != null) {
if (cause.isInstance(t)) {
return true;
}
t = t.getCause();
}
} catch (InstantiationException e) {
// ignore
} catch (IllegalAccessException e) {
// ignore
} finally {
c.setAccessible(accessible);
}
} catch (NoSuchMethodException e) {
// ignore
}
return false;
}
public void describeTo(Description description) {
description.appendText("with a private constructor that throws ");
description.appendValue(cause);
}
}
private static class FormValidationCauseMatcher extends BaseMatcher<FormValidation> {
private final Class<? extends Throwable> cause;
public FormValidationCauseMatcher(Class<? extends Throwable> cause) {
cause.getClass();
this.cause = cause;
}
public boolean matches(Object o) {
FormValidation v = (FormValidation) o;
return v.getMessage().contains(cause.getName());
}
public void describeTo(Description description) {
description.appendText("caused by ");
description.appendValue(cause);
}
}
private static class FormValidationKindMatcher extends BaseMatcher<FormValidation> {
private final FormValidation.Kind kind;
public FormValidationKindMatcher(FormValidation.Kind kind) {
kind.getClass();
this.kind = kind;
}
public boolean matches(Object o) {
FormValidation v = (FormValidation) o;
return v.kind == kind;
}
public void describeTo(Description description) {
description.appendText("of kind ");
description.appendValue(kind);
}
}
/**
* A matcher which checks that the class is a Utility class (i.e. is final and has only private constructors).
*
* @return A matcher which checks that the class is a Utility class (i.e. is final and has only private
* constructors).
*/
public static Matcher<Class<?>> isUtilityClass() {
return CoreMatchers.allOf(isFinalClass(), isClassWithOnlyPrivateConstructors());
}
/**
* A matcher which checks that the class has only private constructors.
*
* @return A matcher which checks that the class has only private constructors.
*/
public static Matcher<Class<?>> isClassWithOnlyPrivateConstructors() {
return new IsClassWithOnlyPrivateConstructors();
}
/**
* A matcher which checks that the class is final.
*
* @return A matcher which checks that the class is final.
*/
public static Matcher<Class<?>> isFinalClass() {
return new IsFinalClass();
}
/**
* A matcher which checks that the class has the default constructor.
*
* @return A matcher which checks that the class has the default constructor.
*/
public static Matcher<Class<?>> hasDefaultConstructor() {
return new HasDefaultConstructor();
}
/**
* A matcher that verifies that the a root cause of an exception is of the specified type.
*
* @param cause the type of exception that caused this.
* @return A matcher that verifies that the a root cause of an exception is of the specified type.
*/
public static Matcher<Throwable> hasCause(Class<? extends Throwable> cause) {
return new HasCause(cause);
}
public static Matcher<Object> hasImplementedEquals() {
return new HasImplementedEquals();
}
public static Matcher<Object> hasReflexiveEquals() {
return new HasReflexiveEquals();
}
public static Matcher<Object> hasNonEqualityWithNulls() {
return new HasNonNullEquals();
}
public static Matcher<Object> hasSymmetricEquals(Object other) {
return new HasSymmetricEquals(other);
}
public static Matcher<Object> hasConsistentEquals(Object other) {
return new HasConsistentEquals(other);
}
public static Matcher<Object> hasTransitiveEquals(Object a, Object b) {
return new HasTransitiveEquals(a, b);
}
public static Matcher<Object> hasImplementedHashCode() {
return new HasImplementedHashCode();
}
public static Matcher<Object> hasHashCodeContract(Object other) {
return new HasHashCodeContract(other);
}
private static class HasDefaultConstructor
extends BaseMatcher<Class<?>> {
public boolean matches(Object item) {
Class<?> clazz = (Class<?>) item;
try {
Constructor<?> constructor = clazz.getConstructor();
return Modifier.isPublic(constructor.getModifiers());
} catch (NoSuchMethodException e) {
return false;
}
}
public void describeTo(Description description) {
description.appendText("a class with the default constructor");
}
}
private static class HasImplementedEquals
extends BaseMatcher<Object> {
public boolean matches(Object item) {
if (item == null) {
return false;
}
Class<?> clazz = item instanceof Class ? (Class<?>) item : item.getClass();
try {
return !clazz.getMethod("equals", Object.class).equals(Object.class.getMethod("equals", Object.class));
} catch (NoSuchMethodException e) {
return false;
}
}
public void describeTo(Description description) {
description.appendText("has overridden the default equals(Object) method");
}
}
private static class HasImplementedHashCode
extends BaseMatcher<Object> {
public boolean matches(Object item) {
if (item == null) {
return false;
}
Class<?> clazz = item instanceof Class ? (Class<?>) item : item.getClass();
try {
return !clazz.getMethod("hashCode").equals(Object.class.getMethod("hashCode"));
} catch (NoSuchMethodException e) {
return false;
}
}
public void describeTo(Description description) {
description.appendText("has overridden the default hashCode() method");
}
}
private static class IsClassWithOnlyPrivateConstructors
extends BaseMatcher<Class<?>> {
public boolean matches(Object item) {
Class<?> clazz = (Class<?>) item;
for (Constructor<?> c : clazz.getConstructors()) {
if (!Modifier.isPrivate(c.getModifiers())) {
return false;
}
}
return true;
}
public void describeTo(Description description) {
description.appendText("a class with only private constructors");
}
}
private static class IsFinalClass
extends BaseMatcher<Class<?>> {
public boolean matches(Object item) {
Class<?> clazz = (Class<?>) item;
return Modifier.isFinal(clazz.getModifiers());
}
public void describeTo(Description description) {
description.appendText("a final class");
}
}
private static class HasCause
extends BaseMatcher<Throwable> {
private final Class<? extends Throwable> cause;
public HasCause(Class<? extends Throwable> cause) {
this.cause = cause;
}
public boolean matches(Object item) {
Throwable throwable = (Throwable) item;
while (throwable != null && !cause.isInstance(throwable)) {
throwable = throwable.getCause();
}
return cause.isInstance(throwable);
}
public void describeTo(Description description) {
description.appendText("was caused by a ").appendValue(cause).appendText(" being thrown");
}
}
private static class HasNonNullEquals extends BaseMatcher<Object> {
public boolean matches(Object o) {
return o != null && !o.equals(null);
}
public void describeTo(Description description) {
description.appendText("has an equals(Object) method that returns false for null");
}
}
private static class HasReflexiveEquals extends BaseMatcher<Object> {
public boolean matches(Object o) {
return o != null && o.equals(o);
}
public void describeTo(Description description) {
description.appendText("has a reflexive equals(Object) method");
}
}
private static class HasSymmetricEquals extends BaseMatcher<Object> {
private final Object other;
public HasSymmetricEquals(Object other) {
this.other = other;
}
public boolean matches(Object o) {
return o == null ? other == null : (o.equals(other) ? other.equals(o) : !other.equals(o));
}
public void describeTo(Description description) {
description.appendText("has a symmetric equals(Object) method with ");
description.appendValue(other);
}
}
private static class HasConsistentEquals extends BaseMatcher<Object> {
private final Object other;
public HasConsistentEquals(Object other) {
this.other = other;
}
public boolean matches(Object o) {
return o == null ? other == null : (o.equals(other) ? o.equals(other) : !o.equals(other));
}
public void describeTo(Description description) {
description.appendText("has a symmetric equals(Object) method with ");
description.appendValue(other);
}
}
private static class HasTransitiveEquals extends BaseMatcher<Object> {
private final Object a;
private final Object b;
public HasTransitiveEquals(Object a, Object b) {
this.a = a;
this.b = b;
}
public boolean matches(Object o) {
return o != null && (!(o.equals(a) && a.equals(b)) || o.equals(b));
}
public void describeTo(Description description) {
description.appendText("has a transitive equals(Object) method with ");
description.appendValue(a);
description.appendText(" and ");
description.appendValue(b);
}
}
private static class HasHashCodeContract extends BaseMatcher<Object> {
private final Object other;
public HasHashCodeContract(Object other) {
this.other = other;
}
public boolean matches(Object o) {
return o == null ? other == null : (!o.equals(other) || o.hashCode() == other.hashCode());
}
public void describeTo(Description description) {
description.appendText("follows the hashCode contract when compared to ");
description.appendValue(other);
}
}
}
package org.jvnet.hudson.test;
import java.lang.annotation.Documented;
import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
import java.lang.annotation.Retention;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Target;
import java.lang.annotation.Annotation;
import java.io.File;
import junit.framework.TestCase;
/**
* Meta-annotation for recipe annotations, which controls
* the test environment set up.
*
* @author Kohsuke Kawaguchi
*/
@Retention(RUNTIME)
@Documented
@Target(ANNOTATION_TYPE)
public @interface JenkinsRecipe {
/**
* Specifies the class that sets up the test environment.
*
* <p>
* When a recipe annotation is placed on a test method,
*/
Class<? extends Runner> value();
/**
* The code that implements the recipe semantics.
*
* @param <T>
* The recipe annotation associated with this runner.
*/
public abstract class Runner<T extends Annotation> {
/**
* Called during {@link TestCase#setUp()} to prepare the test environment.
*/
public void setup(JenkinsRule jenkinsRule, T recipe) throws Exception {}
/**
* Called right before {@link jenkins.model.Jenkins#Jenkins(java.io.File, javax.servlet.ServletContext)} is invoked
* to decorate the hudson home directory.
*/
public void decorateHome(JenkinsRule jenkinsRule, File home) throws Exception {}
/**
* Called during {@link TestCase#tearDown()} to shut down the test environment.
*/
public void tearDown(JenkinsRule jenkinsRule, T recipe) throws Exception {}
}
}
package org.jvnet.hudson.test;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
/**
* An annotation for test methods that do not require the {@link JenkinsRule} to create and tear down the jenkins
* instance.
*/
@Retention(RUNTIME)
@Documented
@Target(METHOD)
public @interface WithoutJenkins {
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册