Repository: incubator-brooklyn Updated Branches: refs/heads/master 48e4fe3ca -> a5040934d
Add EntityAsserts and HttpUtils. This are added to allow core code to make assertions on entities without without having a dependency on TestNG. For now, deprecate EntityTestUtils and HttpTestUtils, and in the future these can be removed in favour of the new classes. Project: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/repo Commit: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/commit/c316e217 Tree: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/tree/c316e217 Diff: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/diff/c316e217 Branch: refs/heads/master Commit: c316e2177b88b2f274637d812e5659e751aeea0d Parents: 6d00890 Author: Geoff Macartney <[email protected]> Authored: Wed Oct 28 12:10:37 2015 +0000 Committer: Geoff Macartney <[email protected]> Committed: Fri Oct 30 11:34:43 2015 +0000 ---------------------------------------------------------------------- .../brooklyn/core/entity/EntityAsserts.java | 192 ++++++ .../deserializingClassRenames.properties | 2 +- .../brooklyn/core/entity/EntityAssertsTest.java | 217 +++++++ .../apache/brooklyn/test/EntityTestUtils.java | 51 +- .../org/apache/brooklyn/test/HttpTestUtils.java | 4 + .../brooklyn/test/TrustingSslSocketFactory.java | 134 ---- .../java/org/apache/brooklyn/test/Asserts.java | 615 ++++++++++++++++++- .../apache/brooklyn/util/http/HttpUtils.java | 387 ++++++++++++ .../util/http/TrustingSslSocketFactory.java | 134 ++++ 9 files changed, 1577 insertions(+), 159 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/c316e217/core/src/main/java/org/apache/brooklyn/core/entity/EntityAsserts.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/brooklyn/core/entity/EntityAsserts.java b/core/src/main/java/org/apache/brooklyn/core/entity/EntityAsserts.java new file mode 100644 index 0000000..046ac52 --- /dev/null +++ b/core/src/main/java/org/apache/brooklyn/core/entity/EntityAsserts.java @@ -0,0 +1,192 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.brooklyn.core.entity; + +import com.google.common.annotations.Beta; +import com.google.common.base.Objects; +import com.google.common.base.Predicate; +import com.google.common.base.Predicates; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Maps; +import com.google.common.collect.Sets; +import org.apache.brooklyn.api.entity.Entity; +import org.apache.brooklyn.api.entity.Group; +import org.apache.brooklyn.api.mgmt.SubscriptionHandle; +import org.apache.brooklyn.api.sensor.AttributeSensor; +import org.apache.brooklyn.api.sensor.SensorEvent; +import org.apache.brooklyn.api.sensor.SensorEventListener; +import org.apache.brooklyn.config.ConfigKey; +import org.apache.brooklyn.test.Asserts; + +import java.util.Collection; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; + +import static org.apache.brooklyn.test.Asserts.assertEquals; + +/** + * Convenience class containing assertions that may be made about entities. + */ +public class EntityAsserts { + + + public static <T> void assertAttributeEquals(Entity entity, AttributeSensor<T> attribute, T expected) { + assertEquals(entity.getAttribute(attribute), expected, "entity=" + entity + "; attribute=" + attribute); + } + + public static <T> void assertConfigEquals(Entity entity, ConfigKey<T> configKey, T expected) { + assertEquals(entity.getConfig(configKey), expected, "entity=" + entity + "; configKey=" + configKey); + } + + public static <T> void assertAttributeEqualsEventually(final Entity entity, final AttributeSensor<T> attribute, final T expected) { + assertAttributeEqualsEventually(Maps.newLinkedHashMap(), entity, attribute, expected); + } + + public static <T> void assertAttributeEqualsEventually(Map<?,?> flags, final Entity entity, final AttributeSensor<T> attribute, final T expected) { + // Not using assertAttributeEventually(predicate) so get nicer error message + Asserts.succeedsEventually((Map) flags, new Runnable() { + @Override + public void run() { + assertAttributeEquals(entity, attribute, expected); + } + }); + } + + public static <T> T assertAttributeEventuallyNonNull(final Entity entity, final AttributeSensor<T> attribute) { + return assertAttributeEventuallyNonNull(Maps.newLinkedHashMap(), entity, attribute); + } + + public static <T> T assertAttributeEventuallyNonNull(Map<?,?> flags, final Entity entity, final AttributeSensor<T> attribute) { + return assertAttributeEventually(flags, entity, attribute, Predicates.notNull()); + } + + public static <T> T assertAttributeEventually(final Entity entity, final AttributeSensor<T> attribute, Predicate<? super T> predicate) { + return assertAttributeEventually(ImmutableMap.of(), entity, attribute, predicate); + } + + public static <T> T assertAttributeEventually(Map<?,?> flags, final Entity entity, final AttributeSensor<T> attribute, final Predicate<? super T> predicate) { + final AtomicReference<T> result = new AtomicReference<T>(); + Asserts.succeedsEventually((Map)flags, new Runnable() { + @Override public void run() { + T val = entity.getAttribute(attribute); + Asserts.assertTrue(predicate.apply(val), "val=" + val); + result.set(val); + }}); + return result.get(); + } + + public static <T> T assertAttribute(final Entity entity, final AttributeSensor<T> attribute, final Predicate<? super T> predicate) { + T val = entity.getAttribute(attribute); + Asserts.assertTrue(predicate.apply(val), "val=" + val); + return val; + } + + + public static <T extends Entity> void assertPredicateEventuallyTrue(final T entity, final Predicate<? super T> predicate) { + assertPredicateEventuallyTrue(Maps.newLinkedHashMap(), entity, predicate); + } + + public static <T extends Entity> void assertPredicateEventuallyTrue(Map<?,?> flags, final T entity, final Predicate<? super T> predicate) { + Asserts.succeedsEventually((Map)flags, new Runnable() { + @Override public void run() { + Asserts.assertTrue(predicate.apply(entity), "predicate unsatisfied"); + }}); + } + + public static <T> void assertAttributeEqualsContinually(final Entity entity, final AttributeSensor<T> attribute, final T expected) { + assertAttributeEqualsContinually(Maps.newLinkedHashMap(), entity, attribute, expected); + } + + public static <T> void assertAttributeEqualsContinually(Map<?,?> flags, final Entity entity, final AttributeSensor<T> attribute, final T expected) { + Asserts.succeedsContinually(flags, new Runnable() { + @Override public void run() { + assertAttributeEquals(entity, attribute, expected); + }}); + } + + public static void assertGroupSizeEqualsEventually(final Group group, int expected) { + assertGroupSizeEqualsEventually(ImmutableMap.of(), group, expected); + } + + public static void assertGroupSizeEqualsEventually(Map<?,?> flags, final Group group, final int expected) { + Asserts.succeedsEventually((Map)flags, new Runnable() { + @Override public void run() { + Collection<Entity> members = group.getMembers(); + assertEquals(members.size(), expected, "members=" + members); + }}); + } + + + /** + * Asserts that the entity's value for this attribute changes, by registering a subscription and checking the value. + * + * @param entity The entity whose attribute will be checked. + * @param attribute The attribute to check on the entity. + * + * @throws AssertionError if the assertion fails. + */ + public static void assertAttributeChangesEventually(final Entity entity, final AttributeSensor<?> attribute) { + final Object origValue = entity.getAttribute(attribute); + final AtomicBoolean changed = new AtomicBoolean(); + SubscriptionHandle handle = entity.subscriptions().subscribe(entity, attribute, new SensorEventListener<Object>() { + @Override public void onEvent(SensorEvent<Object> event) { + if (!Objects.equal(origValue, event.getValue())) { + changed.set(true); + } + }}); + try { + Asserts.succeedsEventually(new Runnable() { + @Override public void run() { + Asserts.assertTrue(changed.get(), entity + " -> " + attribute + " not changed"); + }}); + } finally { + entity.subscriptions().unsubscribe(entity, handle); + } + } + + + @Beta @SafeVarargs + public static <T> void assertAttributeNever(final Entity entity, final AttributeSensor<T> attribute, T... disallowed) { + final Set<T> reject = Sets.newHashSet(disallowed); + Asserts.succeedsContinually(new Runnable() { + @Override + public void run() { + T val = entity.getAttribute(attribute); + Asserts.assertFalse(reject.contains(val), + "Attribute " + attribute + " on " + entity + " has disallowed value " + val); + } + }); + } + + @Beta @SafeVarargs + public static <T> void assertAttributeNever(final Map<?,?> flags, final Entity entity, final AttributeSensor<T> attribute, T... disallowed) { + final Set<T> reject = Sets.newHashSet(disallowed); + Asserts.succeedsContinually(flags, new Runnable() { + @Override + public void run() { + T val = entity.getAttribute(attribute); + Asserts.assertFalse(reject.contains(val), + "Attribute " + attribute + " on " + entity + " has disallowed value " + val); + } + }); + } + +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/c316e217/core/src/main/resources/org/apache/brooklyn/core/mgmt/persist/deserializingClassRenames.properties ---------------------------------------------------------------------- diff --git a/core/src/main/resources/org/apache/brooklyn/core/mgmt/persist/deserializingClassRenames.properties b/core/src/main/resources/org/apache/brooklyn/core/mgmt/persist/deserializingClassRenames.properties index 2bd0c00..006b95a 100644 --- a/core/src/main/resources/org/apache/brooklyn/core/mgmt/persist/deserializingClassRenames.properties +++ b/core/src/main/resources/org/apache/brooklyn/core/mgmt/persist/deserializingClassRenames.properties @@ -1415,5 +1415,5 @@ brooklyn.management.internal.UsageManager brooklyn.policy.basic.ConfigMapImpl : org.apache.brooklyn.core.objs.AdjunctConfigMap brooklyn.util.internal.Repeater : org.apache.brooklyn.util.core.internal.Repeater brooklyn.entity.nosql.redis.RedisClusterImpl : org.apache.brooklyn.entity.nosql.redis.RedisClusterImpl -brooklyn.test.TrustingSslSocketFactory : org.apache.brooklyn.test.TrustingSslSocketFactory +brooklyn.test.TrustingSslSocketFactory : org.apache.brooklyn.util.http.TrustingSslSocketFactory brooklyn.util.crypto.TrustingSslSocketFactory : org.apache.brooklyn.util.crypto.TrustingSslSocketFactory http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/c316e217/core/src/test/java/org/apache/brooklyn/core/entity/EntityAssertsTest.java ---------------------------------------------------------------------- diff --git a/core/src/test/java/org/apache/brooklyn/core/entity/EntityAssertsTest.java b/core/src/test/java/org/apache/brooklyn/core/entity/EntityAssertsTest.java new file mode 100644 index 0000000..563b0ea --- /dev/null +++ b/core/src/test/java/org/apache/brooklyn/core/entity/EntityAssertsTest.java @@ -0,0 +1,217 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.brooklyn.core.entity; + +import com.google.common.base.Predicate; +import com.google.common.base.Predicates; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import org.apache.brooklyn.api.entity.EntitySpec; +import org.apache.brooklyn.api.location.LocationSpec; +import org.apache.brooklyn.api.sensor.AttributeSensor; +import org.apache.brooklyn.core.location.SimulatedLocation; +import org.apache.brooklyn.core.test.BrooklynAppUnitTestSupport; +import org.apache.brooklyn.core.test.entity.TestEntity; +import org.apache.brooklyn.entity.group.DynamicGroup; +import org.apache.brooklyn.util.time.Duration; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + +/** + * Tests on {@link EntityAsserts}. + */ +public class EntityAssertsTest extends BrooklynAppUnitTestSupport { + + private static final int TIMEOUT_MS = 10*1000; + private static final String STOOGE = "stooge"; + + private SimulatedLocation loc; + private TestEntity entity; + private ScheduledExecutorService executor; + private DynamicGroup stooges; + + @BeforeMethod(alwaysRun=true) + @Override + public void setUp() throws Exception { + super.setUp(); + loc = app.getManagementContext().getLocationManager().createLocation(LocationSpec.create(SimulatedLocation.class)); + entity = app.createAndManageChild(EntitySpec.create(TestEntity.class)); + stooges = app.createAndManageChild(EntitySpec.create(DynamicGroup.class)); + final EntitySpec<TestEntity> stooge = + EntitySpec.create(TestEntity.class).configure(TestEntity.CONF_NAME, STOOGE); + app.createAndManageChild(stooge); + app.createAndManageChild(stooge); + app.createAndManageChild(stooge); + app.start(ImmutableList.of(loc)); + executor = Executors.newScheduledThreadPool(3); + } + + @AfterMethod(alwaysRun=true) + public void tearDown() throws Exception { + if (executor != null) executor.shutdownNow(); + if (app != null) Entities.destroyAll(app.getManagementContext()); + super.tearDown(); + } + + + @Test + public void shouldAssertAttributeEquals() { + final String myName = "myname"; + entity.sensors().set(TestEntity.NAME, myName); + EntityAsserts.assertAttributeEquals(entity, TestEntity.NAME, myName); + } + + @Test(expectedExceptions = AssertionError.class) + public void shouldFailToAssertAttributeEquals() { + final String myName = "myname"; + entity.sensors().set(TestEntity.NAME, myName); + EntityAsserts.assertAttributeEquals(entity, TestEntity.NAME, "bogus"); + } + + @Test + public void shouldAssertConfigEquals() { + EntityAsserts.assertConfigEquals(entity, TestEntity.CONF_NAME, "defaultval"); + } + + @Test(expectedExceptions = AssertionError.class) + public void shouldFailToAssertConfigEquals() { + EntityAsserts.assertConfigEquals(entity, TestEntity.CONF_NAME, "bogus"); + } + + @Test + public void shouldAssertAttributeEqualsEventually() { + entity.sensors().set(TestEntity.NAME, "before"); + final String after = "after"; + setSensorValueLater(TestEntity.NAME, after, Duration.seconds(2)); + EntityAsserts.assertAttributeEqualsEventually(entity, TestEntity.NAME, after); + } + + + @Test(expectedExceptions = AssertionError.class) + public void shouldFailToAssertAttributeEqualsEventually() { + entity.sensors().set(TestEntity.NAME, "before"); + final String after = "after"; + setSensorValueLater(TestEntity.NAME, after, Duration.seconds(2)); + EntityAsserts.assertAttributeEqualsEventually(ImmutableMap.of("timeout", "1s"), entity, TestEntity.NAME, after); + } + + private <T> void setSensorValueLater(final AttributeSensor<T> sensor, final T value, final Duration delay) { + executor.schedule(new Runnable() { + @Override + public void run() { + entity.sensors().set(sensor, value); + } + }, delay.toUnit(TimeUnit.MILLISECONDS), TimeUnit.MILLISECONDS); + } + + @Test + public void shouldAssertAttributeEventuallyNonNull() { + EntityAsserts.assertAttributeEquals(entity, TestEntity.NAME, null); + setSensorValueLater(TestEntity.NAME, "something", Duration.seconds(1)); + EntityAsserts.assertAttributeEventuallyNonNull(entity, TestEntity.NAME); + } + + @Test + public void shouldAssertAttributeEventually() { + setSensorValueLater(TestEntity.NAME, "testing testing 123", Duration.seconds(1)); + EntityAsserts.assertAttributeEventually(entity, TestEntity.NAME, new Predicate<String>() { + @Override + public boolean apply(String input) { + return input.matches(".*\\d+"); + } + }); + } + + @Test + public void shouldAssertAttribute() { + final String before = "before"; + entity.sensors().set(TestEntity.NAME, before); + EntityAsserts.assertAttribute(entity, TestEntity.NAME, Predicates.equalTo(before)); + } + + @Test + public void shouldAssertPredicateEventuallyTrue() { + final int testVal = 987654321; + executor.schedule(new Runnable() { + @Override + public void run() { + entity.setSequenceValue(testVal); + } + }, 1, TimeUnit.SECONDS); + EntityAsserts.assertPredicateEventuallyTrue(entity, new Predicate<TestEntity>() { + @Override + public boolean apply(TestEntity input) { + return testVal == input.getSequenceValue() ; + } + }); + } + + @Test + public void shouldAssertAttributeEqualsContinually() { + final String myName = "myname"; + entity.sensors().set(TestEntity.NAME, myName); + EntityAsserts.assertAttributeEqualsContinually( + ImmutableMap.of("timeout", "2s"), entity, TestEntity.NAME, myName); + } + + @Test(expectedExceptions = AssertionError.class) + public void shouldFailAssertAttributeEqualsContinually() { + final String myName = "myname"; + entity.sensors().set(TestEntity.NAME, myName); + setSensorValueLater(TestEntity.NAME, "something", Duration.seconds(1)); + EntityAsserts.assertAttributeEqualsContinually( + ImmutableMap.of("timeout", "2s"), entity, TestEntity.NAME, myName); + } + + @Test + public void shouldAssertGroupSizeEqualsEventually() { + setGroupFilterLater(STOOGE, 1); + EntityAsserts.assertGroupSizeEqualsEventually(ImmutableMap.of("timeout", "2s"), stooges, 3); + setGroupFilterLater("Marx Brother", 1); + EntityAsserts.assertGroupSizeEqualsEventually(stooges, 0); + } + + private void setGroupFilterLater(final String conf, long delaySeconds) { + executor.schedule(new Runnable() { + @Override + public void run() { + stooges.setEntityFilter(EntityPredicates.configEqualTo(TestEntity.CONF_NAME, conf)); + } + }, delaySeconds, TimeUnit.SECONDS); + } + + @Test + public void shouldAssertAttributeChangesEventually () { + entity.sensors().set(TestEntity.NAME, "before"); + setSensorValueLater(TestEntity.NAME, "after", Duration.seconds(2)); + EntityAsserts.assertAttributeChangesEventually(entity, TestEntity.NAME); + } + + @Test + public void shouldAssertAttributeNever() { + entity.sensors().set(TestEntity.NAME, "ever"); + EntityAsserts.assertAttributeNever(ImmutableMap.of("timeout", "5s"), entity, TestEntity.NAME, "after"); + } + +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/c316e217/usage/test-support/src/main/java/org/apache/brooklyn/test/EntityTestUtils.java ---------------------------------------------------------------------- diff --git a/usage/test-support/src/main/java/org/apache/brooklyn/test/EntityTestUtils.java b/usage/test-support/src/main/java/org/apache/brooklyn/test/EntityTestUtils.java index 3baa26d..6a72324 100644 --- a/usage/test-support/src/main/java/org/apache/brooklyn/test/EntityTestUtils.java +++ b/usage/test-support/src/main/java/org/apache/brooklyn/test/EntityTestUtils.java @@ -36,7 +36,6 @@ import org.apache.brooklyn.api.sensor.AttributeSensor; import org.apache.brooklyn.api.sensor.SensorEvent; import org.apache.brooklyn.api.sensor.SensorEventListener; import org.apache.brooklyn.config.ConfigKey; -import org.apache.brooklyn.test.Asserts; import com.google.common.annotations.Beta; import com.google.common.base.Objects; @@ -46,6 +45,12 @@ import com.google.common.collect.ImmutableMap; import com.google.common.collect.Maps; import com.google.common.collect.Sets; +/** + * A utility class containing tests on Entities. + * + * @deprecated Prefer core assertions org.apache.brooklyn.entity.EntityAsserts. + */ +@Deprecated public class EntityTestUtils { // TODO would be nice to have this... perhaps moving this class, or perhaps this whole project, to core/src/test ? @@ -55,11 +60,11 @@ public class EntityTestUtils { // and deprecate methods in TestUtils until deleted). public static <T> void assertAttributeEquals(Entity entity, AttributeSensor<T> attribute, T expected) { - assertEquals(entity.getAttribute(attribute), expected, "entity="+entity+"; attribute="+attribute); + assertEquals(entity.getAttribute(attribute), expected, "entity=" + entity + "; attribute=" + attribute); } public static <T> void assertConfigEquals(Entity entity, ConfigKey<T> configKey, T expected) { - assertEquals(entity.getConfig(configKey), expected, "entity="+entity+"; configKey="+configKey); + assertEquals(entity.getConfig(configKey), expected, "entity=" + entity + "; configKey=" + configKey); } public static <T> void assertAttributeEqualsEventually(final Entity entity, final AttributeSensor<T> attribute, final T expected) { @@ -68,10 +73,12 @@ public class EntityTestUtils { public static <T> void assertAttributeEqualsEventually(Map<?,?> flags, final Entity entity, final AttributeSensor<T> attribute, final T expected) { // Not using assertAttributeEventually(predicate) so get nicer error message - Asserts.succeedsEventually((Map)flags, new Runnable() { - @Override public void run() { + Asserts.succeedsEventually((Map) flags, new Runnable() { + @Override + public void run() { assertAttributeEquals(entity, attribute, expected); - }}); + } + }); } public static <T> T assertAttributeEventuallyNonNull(final Entity entity, final AttributeSensor<T> attribute) { @@ -108,10 +115,12 @@ public class EntityTestUtils { } public static <T extends Entity> void assertPredicateEventuallyTrue(Map<?,?> flags, final T entity, final Predicate<? super T> predicate) { - Asserts.succeedsEventually((Map)flags, new Runnable() { - @Override public void run() { - assertTrue(predicate.apply(entity)); - }}); + Asserts.succeedsEventually((Map) flags, new Runnable() { + @Override + public void run() { + assertTrue(predicate.apply(entity)); + } + }); } public static <T> void assertAttributeEqualsContinually(final Entity entity, final AttributeSensor<T> attribute, final T expected) { @@ -120,9 +129,11 @@ public class EntityTestUtils { public static <T> void assertAttributeEqualsContinually(Map<?,?> flags, final Entity entity, final AttributeSensor<T> attribute, final T expected) { Asserts.succeedsContinually(flags, new Runnable() { - @Override public void run() { - assertAttributeEquals(entity, attribute, expected); - }}); + @Override + public void run() { + assertAttributeEquals(entity, attribute, expected); + } + }); } public static void assertGroupSizeEqualsEventually(final Group group, int expected) { @@ -130,11 +141,13 @@ public class EntityTestUtils { } public static void assertGroupSizeEqualsEventually(Map<?,?> flags, final Group group, final int expected) { - Asserts.succeedsEventually((Map)flags, new Runnable() { - @Override public void run() { + Asserts.succeedsEventually((Map) flags, new Runnable() { + @Override + public void run() { Collection<Entity> members = group.getMembers(); - assertEquals(members.size(), expected, "members="+members); - }}); + assertEquals(members.size(), expected, "members=" + members); + } + }); } /** checks that the entity's value for this attribute changes, by registering a subscription and checking the value */ @@ -161,8 +174,8 @@ public class EntityTestUtils { * with simpler code, for comparison */ @Beta public static <T> void assertAttributeChangesEventually2(final Entity entity, final AttributeSensor<T> attribute) { - assertAttributeEventually(entity, attribute, - Predicates.not(Predicates.equalTo(entity.getAttribute(attribute)))); + assertAttributeEventually(entity, attribute, + Predicates.not(Predicates.equalTo(entity.getAttribute(attribute)))); } @Beta http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/c316e217/usage/test-support/src/main/java/org/apache/brooklyn/test/HttpTestUtils.java ---------------------------------------------------------------------- diff --git a/usage/test-support/src/main/java/org/apache/brooklyn/test/HttpTestUtils.java b/usage/test-support/src/main/java/org/apache/brooklyn/test/HttpTestUtils.java index eed5ef4..c0089bd 100644 --- a/usage/test-support/src/main/java/org/apache/brooklyn/test/HttpTestUtils.java +++ b/usage/test-support/src/main/java/org/apache/brooklyn/test/HttpTestUtils.java @@ -43,6 +43,7 @@ import org.apache.brooklyn.test.Asserts; import org.apache.brooklyn.util.collections.MutableMap; import org.apache.brooklyn.util.crypto.SslTrustUtils; import org.apache.brooklyn.util.exceptions.Exceptions; +import org.apache.brooklyn.util.http.TrustingSslSocketFactory; import org.apache.brooklyn.util.stream.Streams; import org.apache.brooklyn.util.time.Time; import org.slf4j.Logger; @@ -59,7 +60,10 @@ import com.google.common.util.concurrent.ListeningExecutorService; * Utility methods to aid testing HTTP. * * @author aled + * + * @deprecated Prefer org.apache.brooklyn.util.http.HttpUtils which has no TestNG dependencies. */ +@Deprecated public class HttpTestUtils { // TODO Delete methods from TestUtils, to just have them here (or switch so TestUtils delegates here, http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/c316e217/usage/test-support/src/main/java/org/apache/brooklyn/test/TrustingSslSocketFactory.java ---------------------------------------------------------------------- diff --git a/usage/test-support/src/main/java/org/apache/brooklyn/test/TrustingSslSocketFactory.java b/usage/test-support/src/main/java/org/apache/brooklyn/test/TrustingSslSocketFactory.java deleted file mode 100644 index 58fb76c..0000000 --- a/usage/test-support/src/main/java/org/apache/brooklyn/test/TrustingSslSocketFactory.java +++ /dev/null @@ -1,134 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -package org.apache.brooklyn.test; - -import java.io.IOException; -import java.net.InetAddress; -import java.net.Socket; -import java.net.URLConnection; -import java.net.UnknownHostException; -import java.security.cert.X509Certificate; - -import javax.net.ssl.HttpsURLConnection; -import javax.net.ssl.SSLContext; -import javax.net.ssl.SSLSocketFactory; -import javax.net.ssl.TrustManager; -import javax.net.ssl.X509TrustManager; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.google.common.base.Throwables; - -// FIXME copied from brooklyn-core because core not visible here - -public class TrustingSslSocketFactory extends SSLSocketFactory { - - private static final Logger logger = LoggerFactory.getLogger(TrustingSslSocketFactory.class); - - private static TrustingSslSocketFactory INSTANCE; - public synchronized static TrustingSslSocketFactory getInstance() { - if (INSTANCE==null) INSTANCE = new TrustingSslSocketFactory(); - return INSTANCE; - } - - private static SSLContext sslContext; - static { - try { - sslContext = SSLContext.getInstance("TLS"); - } catch (Exception e) { - logger.error("Unable to set up SSLContext with TLS. Https activity will likely fail.", e); - } - } - - /** configures a connection to accept all certificates, if it is for https */ - public static <T extends URLConnection> T configure(T connection) { - if (connection instanceof HttpsURLConnection) { - ((HttpsURLConnection)connection).setSSLSocketFactory(getInstance()); - } - return connection; - } - - /** trusts all SSL certificates */ - public static final TrustManager TRUST_ALL = new X509TrustManager() { - public X509Certificate[] getAcceptedIssuers() { - return new X509Certificate[0]; - } - @Override - public void checkClientTrusted(X509Certificate[] chain, String authType) - throws java.security.cert.CertificateException { - - } - @Override - public void checkServerTrusted(X509Certificate[] chain, String authType) - throws java.security.cert.CertificateException { - } - }; - - // no reason this can't be public, but no reason it should be necessary; - // just use getInstance to get the shared INSTANCE - protected TrustingSslSocketFactory() { - super(); - try { - sslContext.init(null, new TrustManager[] { TRUST_ALL }, null); - } catch (Exception e) { - throw Throwables.propagate(e); - } - } - - @Override - public Socket createSocket(Socket socket, String host, int port, boolean autoClose) throws IOException, UnknownHostException { - return sslContext.getSocketFactory().createSocket(socket, host, port, autoClose); - } - - @Override - public Socket createSocket() throws IOException { - return sslContext.getSocketFactory().createSocket(); - } - - @Override - public String[] getDefaultCipherSuites() { - return sslContext.getSocketFactory().getDefaultCipherSuites(); - } - - @Override - public String[] getSupportedCipherSuites() { - return sslContext.getSocketFactory().getSupportedCipherSuites(); - } - - @Override - public Socket createSocket(String arg0, int arg1) throws IOException, UnknownHostException { - return sslContext.getSocketFactory().createSocket(arg0, arg1); - } - - @Override - public Socket createSocket(InetAddress arg0, int arg1) throws IOException { - return sslContext.getSocketFactory().createSocket(arg0, arg1); - } - - @Override - public Socket createSocket(String arg0, int arg1, InetAddress arg2, int arg3) throws IOException, UnknownHostException { - return sslContext.getSocketFactory().createSocket(arg0, arg1, arg2, arg3); - } - - @Override - public Socket createSocket(InetAddress arg0, int arg1, InetAddress arg2, int arg3) throws IOException { - return sslContext.getSocketFactory().createSocket(arg0, arg1, arg2, arg3); - } -} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/c316e217/utils/common/src/main/java/org/apache/brooklyn/test/Asserts.java ---------------------------------------------------------------------- diff --git a/utils/common/src/main/java/org/apache/brooklyn/test/Asserts.java b/utils/common/src/main/java/org/apache/brooklyn/test/Asserts.java index 0bf6936..678a181 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/test/Asserts.java +++ b/utils/common/src/main/java/org/apache/brooklyn/test/Asserts.java @@ -20,6 +20,7 @@ package org.apache.brooklyn.test; import groovy.lang.Closure; +import java.lang.reflect.Array; import java.util.Arrays; import java.util.Collection; import java.util.Enumeration; @@ -52,8 +53,14 @@ import com.google.common.collect.Sets; * TODO should move this to new package brooklyn.util.assertions * and TODO should add a repeating() method which returns an AssertingRepeater extending Repeater * and: + * <ul> * <li> adds support for requireAllIterationsTrue * <li> convenience run methods equivalent to succeedsEventually and succeedsContinually + * </ul> + * <p> + * NOTE Selected routines in this class are originally copied from <a href="http://testng.org">TestNG</a>, to allow us to make assertions without having to + * introduce a runtime dependency on TestNG. + * </p> */ @Beta public class Asserts { @@ -67,9 +74,608 @@ public class Asserts { private static final Logger log = LoggerFactory.getLogger(Asserts.class); private Asserts() {} + + private static final Character OPENING_CHARACTER = '['; + private static final Character CLOSING_CHARACTER = ']'; + + private static final String ASSERT_LEFT = "expected " + OPENING_CHARACTER; + private static final String ASSERT_MIDDLE = CLOSING_CHARACTER + " but found " + OPENING_CHARACTER; + private static final String ASSERT_RIGHT = Character.toString(CLOSING_CHARACTER); + + static String format(Object actual, Object expected, String message) { + String formatted = ""; + if (null != message) { + formatted = message + " "; + } + + return formatted + ASSERT_LEFT + expected + ASSERT_MIDDLE + actual + ASSERT_RIGHT; + } + + static private void failNotEquals(Object actual , Object expected, String message ) { + fail(format(actual, expected, message)); + } + + /** + * Assert that an object reference is null. + * + * @param object The object reference. + * + * @throws AssertionError if the assertion fails. + */ + static public void assertNull(final Object object) { + assertNull(object, null); + } + + /** + * Assert that an object reference is not null. + * + * @param object The object reference. + * + * @throws AssertionError if the assertion fails. + */ + static public void assertNotNull(final Object object) { + assertNotNull(object, null); + } + + /** + * Assert that an object reference is null. + * + * @param object The object reference. + * @param message The assertion error message. + * + * @throws AssertionError if the assertion fails. + */ + static public void assertNull(final Object object, final String message) { + if (null != object) { + throw new AssertionError(message == null ? "object reference is not null" : message); + } + } + + /** + * Assert that an object reference is not null. + * + * @param object The object reference. + * @param message The assertion error message. + * + * @throws AssertionError if the assertion fails. + */ + static public void assertNotNull(final Object object, final String message) { + if (null == object) { + throw new AssertionError(message == null ? "object reference is null" : message); + } + } + + /** + * Asserts that two collections contain the same elements in the same order. If they do not, + * an AssertionError is thrown. + * + * @param actual the actual value + * @param expected the expected value + */ + static public void assertEquals(Collection<?> actual, Collection<?> expected) { + assertEquals(actual, expected, null); + } + + /** + * Asserts that two collections contain the same elements in the same order. If they do not, + * an AssertionError, with the given message, is thrown. + * @param actual the actual value + * @param expected the expected value + * @param message the assertion error message + */ + static public void assertEquals(Collection<?> actual, Collection<?> expected, String message) { + if(actual == expected) { + return; + } + + if (actual == null || expected == null) { + if (message != null) { + fail(message); + } else { + fail("Collections not equal: expected: " + expected + " and actual: " + actual); + } + } + + assertEquals(actual.size(), expected.size(), message + ": lists don't have the same size"); + + Iterator<?> actIt = actual.iterator(); + Iterator<?> expIt = expected.iterator(); + int i = -1; + while(actIt.hasNext() && expIt.hasNext()) { + i++; + Object e = expIt.next(); + Object a = actIt.next(); + String explanation = "Lists differ at element [" + i + "]: " + e + " != " + a; + String errorMessage = message == null ? explanation : message + ": " + explanation; + + assertEquals(a, e, errorMessage); + } + } + + /** Asserts that two iterators return the same elements in the same order. If they do not, + * an AssertionError is thrown. + * Please note that this assert iterates over the elements and modifies the state of the iterators. + * @param actual the actual value + * @param expected the expected value + */ + static public void assertEquals(Iterator<?> actual, Iterator<?> expected) { + assertEquals(actual, expected, null); + } + + /** Asserts that two iterators return the same elements in the same order. If they do not, + * an AssertionError, with the given message, is thrown. + * Please note that this assert iterates over the elements and modifies the state of the iterators. + * @param actual the actual value + * @param expected the expected value + * @param message the assertion error message + */ + static public void assertEquals(Iterator<?> actual, Iterator<?> expected, String message) { + if(actual == expected) { + return; + } + + if(actual == null || expected == null) { + if(message != null) { + fail(message); + } else { + fail("Iterators not equal: expected: " + expected + " and actual: " + actual); + } + } + + int i = -1; + while(actual.hasNext() && expected.hasNext()) { + + i++; + Object e = expected.next(); + Object a = actual.next(); + String explanation = "Iterators differ at element [" + i + "]: " + e + " != " + a; + String errorMessage = message == null ? explanation : message + ": " + explanation; + + assertEquals(a, e, errorMessage); + + } + + if(actual.hasNext()) { + + String explanation = "Actual iterator returned more elements than the expected iterator."; + String errorMessage = message == null ? explanation : message + ": " + explanation; + fail(errorMessage); + + } else if(expected.hasNext()) { + + String explanation = "Expected iterator returned more elements than the actual iterator."; + String errorMessage = message == null ? explanation : message + ": " + explanation; + fail(errorMessage); + + } + + } + + /** Asserts that two iterables return iterators with the same elements in the same order. If they do not, + * an AssertionError is thrown. + * @param actual the actual value + * @param expected the expected value + */ + static public void assertEquals(Iterable<?> actual, Iterable<?> expected) { + assertEquals(actual, expected, null); + } + + /** Asserts that two iterables return iterators with the same elements in the same order. If they do not, + * an AssertionError, with the given message, is thrown. + * @param actual the actual value + * @param expected the expected value + * @param message the assertion error message + */ + static public void assertEquals(Iterable<?> actual, Iterable<?> expected, String message) { + if(actual == expected) { + return; + } + + if(actual == null || expected == null) { + if(message != null) { + fail(message); + } else { + fail("Iterables not equal: expected: " + expected + " and actual: " + actual); + } + } + + Iterator<?> actIt = actual.iterator(); + Iterator<?> expIt = expected.iterator(); + + assertEquals(actIt, expIt, message); + } + + + + /** + * Asserts that two sets are equal. + */ + static public void assertEquals(Set<?> actual, Set<?> expected) { + assertEquals(actual, expected, null); + } + + /** + * Assert set equals + */ + static public void assertEquals(Set<?> actual, Set<?> expected, String message) { + if (actual == expected) { + return; + } + + if (actual == null || expected == null) { + // Keep the back compatible + if (message == null) { + fail("Sets not equal: expected: " + expected + " and actual: " + actual); + } else { + failNotEquals(actual, expected, message); + } + } + + if (!actual.equals(expected)) { + if (message == null) { + fail("Sets differ: expected " + expected + " but got " + actual); + } else { + failNotEquals(actual, expected, message); + } + } + } + + /** + * Asserts that two maps are equal. + */ + static public void assertEquals(Map<?, ?> actual, Map<?, ?> expected) { + if (actual == expected) { + return; + } + + if (actual == null || expected == null) { + fail("Maps not equal: expected: " + expected + " and actual: " + actual); + } + + if (actual.size() != expected.size()) { + fail("Maps do not have the same size:" + actual.size() + " != " + expected.size()); + } + + Set<?> entrySet = actual.entrySet(); + for (Iterator<?> iterator = entrySet.iterator(); iterator.hasNext();) { + Map.Entry<?, ?> entry = (Map.Entry<?, ?>) iterator.next(); + Object key = entry.getKey(); + Object value = entry.getValue(); + Object expectedValue = expected.get(key); + assertEquals(value, expectedValue, "Maps do not match for key:" + key + " actual:" + value + + " expected:" + expectedValue); + } + + } + + + /** + * Asserts that two arrays contain the same elements in the same order. If they do not, + * an AssertionError, with the given message, is thrown. + * @param actual the actual value + * @param expected the expected value + * @param message the assertion error message + */ + static public void assertEquals(Object[] actual, Object[] expected, String message) { + if(actual == expected) { + return; + } + + if ((actual == null && expected != null) || (actual != null && expected == null)) { + if (message != null) { + fail(message); + } else { + fail("Arrays not equal: " + Arrays.toString(expected) + " and " + Arrays.toString(actual)); + } + } + assertEquals(Arrays.asList(actual), Arrays.asList(expected), message); + } + + /** + * Asserts that two arrays contain the same elements in the same order. If they do not, + * an AssertionError is thrown. + * + * @param actual the actual value + * @param expected the expected value + */ + static public void assertEquals(Object[] actual, Object[] expected) { + assertEquals(actual, expected, null); + } + + /** + * Asserts that two objects are equal. + * @param actual the actual value + * @param expected the expected value + * + * @throws AssertionError if the values are not equal. + */ + static public void assertEquals(Object actual, Object expected) { + assertEquals(actual, expected, null); + } - // --- selected routines from testng.Assert for visibility without needing that package - + /** + * Asserts that two objects are equal. + * + * @param actual the actual value + * @param expected the expected value + * @param message the assertion error message + * + * @throws AssertionError if the values are not equal. + */ + static public void assertEquals(Object actual, Object expected, String message) { + if((expected == null) && (actual == null)) { + return; + } + if(expected != null) { + if (expected.getClass().isArray()) { + assertArrayEquals(actual, expected, message); + return; + } else if (expected.equals(actual)) { + return; + } + } + failNotEquals(actual, expected, message); + } + + + /** + * Asserts that two objects are equal. It they are not, an AssertionError, + * with given message, is thrown. + * @param actual the actual value + * @param expected the expected value (should be an non-null array value) + * @param message the assertion error message + */ + private static void assertArrayEquals(Object actual, Object expected, String message) { + //is called only when expected is an array + if (actual.getClass().isArray()) { + int expectedLength = Array.getLength(expected); + if (expectedLength == Array.getLength(actual)) { + for (int i = 0 ; i < expectedLength ; i++) { + Object _actual = Array.get(actual, i); + Object _expected = Array.get(expected, i); + try { + assertEquals(_actual, _expected); + } catch (AssertionError ae) { + failNotEquals(actual, expected, message == null ? "" : message + + " (values at index " + i + " are not the same)"); + } + } + //array values matched + return; + } else { + failNotEquals(Array.getLength(actual), expectedLength, message == null ? "" : message + + " (Array lengths are not the same)"); + } + } + failNotEquals(actual, expected, message); + } + + + /** + * Asserts that two Strings are equal. If they are not, + * an AssertionError, with the given message, is thrown. + * @param actual the actual value + * @param expected the expected value + * @param message the assertion error message + */ + static public void assertEquals(String actual, String expected, String message) { + assertEquals((Object) actual, (Object) expected, message); + } + + /** + * Asserts that two Strings are equal. If they are not, + * an AssertionError is thrown. + * @param actual the actual value + * @param expected the expected value + */ + static public void assertEquals(String actual, String expected) { + assertEquals(actual, expected, null); + } + + /** + * Asserts that two doubles are equal within a delta. If they are not, + * an AssertionError, with the given message, is thrown. If the expected + * value is infinity then the delta value is ignored. + * @param actual the actual value + * @param expected the expected value + * @param delta the absolute tolerable difference between the actual and expected values + * @param message the assertion error message + */ + static public void assertEquals(double actual, double expected, double delta, String message) { + // handle infinity specially since subtracting to infinite values gives NaN and the + // the following test fails + if(Double.isInfinite(expected)) { + if(!(expected == actual)) { + failNotEquals(new Double(actual), new Double(expected), message); + } + } + else if(!(Math.abs(expected - actual) <= delta)) { // Because comparison with NaN always returns false + failNotEquals(new Double(actual), new Double(expected), message); + } + } + + /** + * Asserts that two doubles are equal within a delta. If they are not, + * an AssertionError is thrown. If the expected value is infinity then the + * delta value is ignored. + * @param actual the actual value + * @param expected the expected value + * @param delta the absolute tolerable difference between the actual and expected values + */ + static public void assertEquals(double actual, double expected, double delta) { + assertEquals(actual, expected, delta, null); + } + + /** + * Asserts that two floats are equal within a delta. If they are not, + * an AssertionError, with the given message, is thrown. If the expected + * value is infinity then the delta value is ignored. + * @param actual the actual value + * @param expected the expected value + * @param delta the absolute tolerable difference between the actual and expected values + * @param message the assertion error message + */ + static public void assertEquals(float actual, float expected, float delta, String message) { + // handle infinity specially since subtracting to infinite values gives NaN and the + // the following test fails + if(Float.isInfinite(expected)) { + if(!(expected == actual)) { + failNotEquals(new Float(actual), new Float(expected), message); + } + } + else if(!(Math.abs(expected - actual) <= delta)) { + failNotEquals(new Float(actual), new Float(expected), message); + } + } + + /** + * Asserts that two floats are equal within a delta. If they are not, + * an AssertionError is thrown. If the expected + * value is infinity then the delta value is ignored. + * @param actual the actual value + * @param expected the expected value + * @param delta the absolute tolerable difference between the actual and expected values + */ + static public void assertEquals(float actual, float expected, float delta) { + assertEquals(actual, expected, delta, null); + } + + /** + * Asserts that two longs are equal. If they are not, + * an AssertionError, with the given message, is thrown. + * @param actual the actual value + * @param expected the expected value + * @param message the assertion error message + */ + static public void assertEquals(long actual, long expected, String message) { + assertEquals(Long.valueOf(actual), Long.valueOf(expected), message); + } + + /** + * Asserts that two longs are equal. If they are not, + * an AssertionError is thrown. + * @param actual the actual value + * @param expected the expected value + */ + static public void assertEquals(long actual, long expected) { + assertEquals(actual, expected, null); + } + + /** + * Asserts that two booleans are equal. If they are not, + * an AssertionError, with the given message, is thrown. + * @param actual the actual value + * @param expected the expected value + * @param message the assertion error message + */ + static public void assertEquals(boolean actual, boolean expected, String message) { + assertEquals( Boolean.valueOf(actual), Boolean.valueOf(expected), message); + } + + /** + * Asserts that two booleans are equal. If they are not, + * an AssertionError is thrown. + * @param actual the actual value + * @param expected the expected value + */ + static public void assertEquals(boolean actual, boolean expected) { + assertEquals(actual, expected, null); + } + + /** + * Asserts that two bytes are equal. If they are not, + * an AssertionError, with the given message, is thrown. + * @param actual the actual value + * @param expected the expected value + * @param message the assertion error message + */ + static public void assertEquals(byte actual, byte expected, String message) { + assertEquals(Byte.valueOf(actual), Byte.valueOf(expected), message); + } + + /** + * Asserts that two bytes are equal. If they are not, + * an AssertionError is thrown. + * @param actual the actual value + * @param expected the expected value + */ + static public void assertEquals(byte actual, byte expected) { + assertEquals(actual, expected, null); + } + + /** + * Asserts that two chars are equal. If they are not, + * an AssertionFailedError, with the given message, is thrown. + * @param actual the actual value + * @param expected the expected value + * @param message the assertion error message + */ + static public void assertEquals(char actual, char expected, String message) { + assertEquals(Character.valueOf(actual), Character.valueOf(expected), message); + } + + /** + * Asserts that two chars are equal. If they are not, + * an AssertionError is thrown. + * @param actual the actual value + * @param expected the expected value + */ + static public void assertEquals(char actual, char expected) { + assertEquals(actual, expected, null); + } + + /** + * Asserts that two shorts are equal. If they are not, + * an AssertionFailedError, with the given message, is thrown. + * @param actual the actual value + * @param expected the expected value + * @param message the assertion error message + */ + static public void assertEquals(short actual, short expected, String message) { + assertEquals(Short.valueOf(actual), Short.valueOf(expected), message); + } + + /** + * Asserts that two shorts are equal. If they are not, + * an AssertionError is thrown. + * @param actual the actual value + * @param expected the expected value + */ + static public void assertEquals(short actual, short expected) { + assertEquals(actual, expected, null); + } + + /** + * Asserts that two ints are equal. If they are not, + * an AssertionFailedError, with the given message, is thrown. + * @param actual the actual value + * @param expected the expected value + * @param message the assertion error message + */ + static public void assertEquals(int actual, int expected, String message) { + assertEquals(Integer.valueOf(actual), Integer.valueOf(expected), message); + } + + /** + * Asserts that two ints are equal. If they are not, + * an AssertionError is thrown. + * @param actual the actual value + * @param expected the expected value + */ + static public void assertEquals(int actual, int expected) { + assertEquals(actual, expected, null); + } + + + + /** + * Asserts that a condition is true. If it isn't, an AssertionError is thrown. + * @param condition the condition to evaluate + */ + public static void assertTrue(boolean condition) { + if (!condition) fail(null); + } + /** * Asserts that a condition is true. If it isn't, * an AssertionError, with the given message, is thrown. @@ -214,7 +820,7 @@ public class Asserts { /** * Convenience method for cases where we need to test until something is true. * - * The runnable will be invoked periodically until it succesfully concludes. + * The Callable will be invoked periodically until it succesfully concludes. * <p> * The following flags are supported: * <ul> @@ -233,8 +839,7 @@ public class Asserts { * </ul> * * @param flags, accepts the flags listed above - * @param r - * @param finallyBlock + * @param c The callable to invoke */ public static <T> T succeedsEventually(Map<String,?> flags, Callable<T> c) { boolean abortOnException = get(flags, "abortOnException", false); http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/c316e217/utils/common/src/main/java/org/apache/brooklyn/util/http/HttpUtils.java ---------------------------------------------------------------------- diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/http/HttpUtils.java b/utils/common/src/main/java/org/apache/brooklyn/util/http/HttpUtils.java new file mode 100644 index 0000000..8eeef19 --- /dev/null +++ b/utils/common/src/main/java/org/apache/brooklyn/util/http/HttpUtils.java @@ -0,0 +1,387 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.brooklyn.util.http; + +import java.io.IOException; +import java.io.InputStream; +import java.net.HttpURLConnection; +import java.net.URL; +import java.net.URLConnection; +import java.util.List; +import java.util.Map; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; + +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.HttpsURLConnection; +import javax.net.ssl.SSLSession; + +import org.apache.brooklyn.test.Asserts; +import org.apache.brooklyn.util.collections.MutableMap; +import org.apache.brooklyn.util.crypto.SslTrustUtils; +import org.apache.brooklyn.util.exceptions.Exceptions; +import org.apache.brooklyn.util.stream.Streams; +import org.apache.brooklyn.util.time.Time; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.base.Throwables; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.ListeningExecutorService; + +/** + * Utility methods to aid testing HTTP. + * + * @author aled + */ +public class HttpUtils { + + // TODO Delete methods from TestUtils, to just have them here (or switch so TestUtils delegates here, + // and deprecate methods in TestUtils until deleted). + + private static final Logger LOG = LoggerFactory.getLogger(HttpUtils.class); + + static final ExecutorService executor = Executors.newCachedThreadPool(); + + /** + * Connects to the given url and returns the connection. + * Caller should {@code connection.getInputStream().close()} the result of this + * (especially if they are making heavy use of this method). + */ + public static URLConnection connectToUrl(String u) throws Exception { + final URL url = new URL(u); + final AtomicReference<Exception> exception = new AtomicReference<Exception>(); + + // sometimes openConnection hangs, so run in background + Future<URLConnection> f = executor.submit(new Callable<URLConnection>() { + public URLConnection call() { + try { + HttpsURLConnection.setDefaultHostnameVerifier(new HostnameVerifier() { + @Override public boolean verify(String s, SSLSession sslSession) { + return true; + } + }); + URLConnection connection = url.openConnection(); + TrustingSslSocketFactory.configure(connection); + connection.connect(); + + connection.getContentLength(); // Make sure the connection is made. + return connection; + } catch (Exception e) { + exception.set(e); + LOG.debug("Error connecting to url "+url+" (propagating): "+e, e); + } + return null; + } + }); + try { + URLConnection result = null; + try { + result = f.get(60, TimeUnit.SECONDS); + } catch (InterruptedException e) { + throw e; + } catch (Exception e) { + LOG.debug("Error connecting to url "+url+", probably timed out (rethrowing): "+e); + throw new IllegalStateException("Connect to URL not complete within 60 seconds, for url "+url+": "+e); + } + if (exception.get() != null) { + LOG.debug("Error connecting to url "+url+", thread caller of "+exception, new Throwable("source of rethrown error "+exception)); + throw exception.get(); + } else { + return result; + } + } finally { + f.cancel(true); + } + } + + public static void assertHealthyStatusCode(int code) { + if (code>=200 && code<=299) return; + Asserts.fail("Wrong status code: " + code); + } + + public static int getHttpStatusCode(String url) throws Exception { + URLConnection connection = connectToUrl(url); + long startTime = System.currentTimeMillis(); + int status = ((HttpURLConnection) connection).getResponseCode(); + + // read fully if possible, then close everything, trying to prevent cached threads at server + consumeAndCloseQuietly((HttpURLConnection) connection); + + if (LOG.isDebugEnabled()) + LOG.debug("connection to {} ({}ms) gives {}", new Object[] { url, (System.currentTimeMillis()-startTime), status }); + return status; + } + + /** + * Asserts that gets back any "valid" response - i.e. not an exception. This could be an unauthorized, + * a redirect, a 404, or anything else that implies there is web-server listening on that port. + */ + public static void assertUrlReachable(String url) { + try { + getHttpStatusCode(url); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new RuntimeException("Interrupted for "+url+" (in assertion that is reachable)", e); + } catch (Exception e) { + throw new IllegalStateException("Server at "+url+" Asserts.failed to respond (in assertion that is reachable): "+e, e); + } + } + + public static void assertUrlUnreachable(String url) { + try { + int statusCode = getHttpStatusCode(url); + Asserts.fail("Expected url " + url + " unreachable, but got status code " + statusCode); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new RuntimeException("Interrupted for "+url+" (in assertion that unreachable)", e); + } catch (Exception e) { + IOException cause = Exceptions.getFirstThrowableOfType(e, IOException.class); + if (cause != null) { + // success; clean shutdown transitioning from 400 to error + } else { + Throwables.propagate(e); + } + } + } + + public static void assertUrlUnreachableEventually(final String url) { + assertUrlUnreachableEventually(Maps.newLinkedHashMap(), url); + } + + public static void assertUrlUnreachableEventually(Map flags, final String url) { + Asserts.succeedsEventually(flags, new Runnable() { + public void run() { + assertUrlUnreachable(url); + } + }); + } + + public static void assertHttpStatusCodeEquals(String url, int... acceptableReturnCodes) { + List<Integer> acceptableCodes = Lists.newArrayList(); + for (int code : acceptableReturnCodes) { + acceptableCodes.add((Integer)code); + } + try { + int actualCode = getHttpStatusCode(url); + Asserts.assertTrue(acceptableCodes.contains(actualCode), "code=" + actualCode + "; expected=" + acceptableCodes + "; url=" + url); + + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new RuntimeException("Interrupted for "+url+" (in assertion that result code is "+acceptableCodes+")", e); + } catch (Exception e) { + throw new IllegalStateException("Server at "+url+" Asserts.failed to respond (in assertion that result code is "+acceptableCodes+"): "+e, e); + } + } + + public static void assertHttpStatusCodeEventuallyEquals(final String url, final int expectedCode) { + assertHttpStatusCodeEventuallyEquals(Maps.newLinkedHashMap(), url, expectedCode); + } + + public static void assertHttpStatusCodeEventuallyEquals(Map flags, final String url, final int expectedCode) { + Asserts.succeedsEventually(flags, new Runnable() { + public void run() { + assertHttpStatusCodeEquals(url, expectedCode); + } + }); + } + + public static void assertContentContainsText(final String url, final String phrase, final String ...additionalPhrases) { + try { + String contents = getContent(url); + Asserts.assertTrue(contents != null && contents.length() > 0); + for (String text: Lists.asList(phrase, additionalPhrases)) { + if (!contents.contains(text)) { + LOG.warn("CONTENTS OF URL "+url+" MISSING TEXT: "+text+"\n"+contents); + Asserts.fail("URL "+url+" does not contain text: "+text); + } + } + } catch (Exception e) { + throw Throwables.propagate(e); + } + } + + public static void assertContentNotContainsText(final String url, final String phrase, final String ...additionalPhrases) { + try { + String contents = getContent(url); + Asserts.assertTrue(contents != null); + for (String text: Lists.asList(phrase, additionalPhrases)) { + if (contents.contains(text)) { + LOG.warn("CONTENTS OF URL "+url+" HAS TEXT: "+text+"\n"+contents); + Asserts.fail("URL "+url+" contain text: "+text); + } + } + } catch (Exception e) { + throw Throwables.propagate(e); + } + } + + public static void assertErrorContentContainsText(final String url, final String phrase, final String ...additionalPhrases) { + try { + String contents = getErrorContent(url); + Asserts.assertTrue(contents != null && contents.length() > 0); + for (String text: Lists.asList(phrase, additionalPhrases)) { + if (!contents.contains(text)) { + LOG.warn("CONTENTS OF URL "+url+" MISSING TEXT: "+text+"\n"+contents); + Asserts.fail("URL "+url+" does not contain text: "+text); + } + } + } catch (Exception e) { + throw Throwables.propagate(e); + } + } + + + public static void assertErrorContentNotContainsText(final String url, final String phrase, final String ...additionalPhrases) { + try { + String err = getErrorContent(url); + Asserts.assertTrue(err != null); + for (String text: Lists.asList(phrase, additionalPhrases)) { + if (err.contains(text)) { + LOG.warn("CONTENTS OF URL "+url+" HAS TEXT: "+text+"\n"+err); + Asserts.fail("URL "+url+" contain text: "+text); + } + } + } catch (Exception e) { + throw Throwables.propagate(e); + } + } + + public static void assertContentEventuallyContainsText(final String url, final String phrase, final String ...additionalPhrases) { + assertContentEventuallyContainsText(MutableMap.of(), url, phrase, additionalPhrases); + } + + public static void assertContentEventuallyContainsText(Map flags, final String url, final String phrase, final String ...additionalPhrases) { + Asserts.succeedsEventually(flags, new Runnable() { + public void run() { + assertContentContainsText(url, phrase, additionalPhrases); + } + }); + } + + public static void assertContentMatches(String url, String regex) { + String contents = getContent(url); + Asserts.assertNotNull(contents); + Asserts.assertTrue(contents.matches(regex), "Contents does not match expected regex ("+regex+"): "+contents); + } + + public static void assertContentEventuallyMatches(final String url, final String regex) { + Asserts.succeedsEventually(new Runnable() { + @Override + public void run() { + assertContentMatches(url, regex); + } + }); + } + + public static String getErrorContent(String url) { + try { + HttpURLConnection connection = (HttpURLConnection) connectToUrl(url); + long startTime = System.currentTimeMillis(); + + String err; + int status; + try { + InputStream errStream = connection.getErrorStream(); + err = Streams.readFullyString(errStream); + status = connection.getResponseCode(); + } finally { + closeQuietly(connection); + } + + if (LOG.isDebugEnabled()) + LOG.debug("read of err {} ({}ms) complete; http code {}", new Object[] { url, Time.makeTimeStringRounded(System.currentTimeMillis()-startTime), status}); + return err; + + } catch (Exception e) { + throw Exceptions.propagate(e); + } + } + + public static String getContent(String url) { + try { + return Streams.readFullyString(SslTrustUtils.trustAll(new URL(url).openConnection()).getInputStream()); + } catch (Exception e) { + throw Throwables.propagate(e); + } + } + + /** + * Schedules (with the given executor) a poller that repeatedly accesses the given url, to confirm it always gives + * back the expected status code. + * + * Expected usage is to query the future, such as: + * + * <pre> + * {@code + * Future<?> future = assertAsyncHttpStatusCodeContinuallyEquals(executor, url, 200); + * // do other stuff... + * if (future.isDone()) future.get(); // get exception if it's Asserts.failed + * } + * </pre> + * + * For stopping it, you can either do future.cancel(true), or you can just do executor.shutdownNow(). + * + * TODO Look at difference between this and WebAppMonitor, to decide if this should be kept. + */ + public static ListenableFuture<?> assertAsyncHttpStatusCodeContinuallyEquals(ListeningExecutorService executor, final String url, final int expectedStatusCode) { + return executor.submit(new Runnable() { + @Override public void run() { + // TODO Need to drop logging; remove sleep when that's done. + while (!Thread.currentThread().isInterrupted()) { + assertHttpStatusCodeEquals(url, expectedStatusCode); + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + return; // graceful return + } + } + } + }); + } + + /** + * Consumes the input stream entirely and then cleanly closes the connection. + * Ignores all exceptions completely, not even logging them! + * + * Consuming the stream fully is useful for preventing idle TCP connections. + * @see <a href="http://docs.oracle.com/javase/8/docs/technotes/guides/net/http-keepalive.html">Persistent Connections</a> + */ + public static void consumeAndCloseQuietly(HttpURLConnection connection) { + try { Streams.readFully(connection.getInputStream()); } catch (Exception e) {} + closeQuietly(connection); + } + + /** + * Closes all streams of the connection, and disconnects it. Ignores all exceptions completely, + * not even logging them! + */ + public static void closeQuietly(HttpURLConnection connection) { + try { connection.disconnect(); } catch (Exception e) {} + try { connection.getInputStream().close(); } catch (Exception e) {} + try { connection.getOutputStream().close(); } catch (Exception e) {} + try { connection.getErrorStream().close(); } catch (Exception e) {} + } +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/c316e217/utils/common/src/main/java/org/apache/brooklyn/util/http/TrustingSslSocketFactory.java ---------------------------------------------------------------------- diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/http/TrustingSslSocketFactory.java b/utils/common/src/main/java/org/apache/brooklyn/util/http/TrustingSslSocketFactory.java new file mode 100644 index 0000000..41b753a --- /dev/null +++ b/utils/common/src/main/java/org/apache/brooklyn/util/http/TrustingSslSocketFactory.java @@ -0,0 +1,134 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.brooklyn.util.http; + +import java.io.IOException; +import java.net.InetAddress; +import java.net.Socket; +import java.net.URLConnection; +import java.net.UnknownHostException; +import java.security.cert.X509Certificate; + +import javax.net.ssl.HttpsURLConnection; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSocketFactory; +import javax.net.ssl.TrustManager; +import javax.net.ssl.X509TrustManager; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.base.Throwables; + +// FIXME copied from brooklyn-core because core not visible here + +public class TrustingSslSocketFactory extends SSLSocketFactory { + + private static final Logger logger = LoggerFactory.getLogger(TrustingSslSocketFactory.class); + + private static TrustingSslSocketFactory INSTANCE; + public synchronized static TrustingSslSocketFactory getInstance() { + if (INSTANCE==null) INSTANCE = new TrustingSslSocketFactory(); + return INSTANCE; + } + + private static SSLContext sslContext; + static { + try { + sslContext = SSLContext.getInstance("TLS"); + } catch (Exception e) { + logger.error("Unable to set up SSLContext with TLS. Https activity will likely fail.", e); + } + } + + /** configures a connection to accept all certificates, if it is for https */ + public static <T extends URLConnection> T configure(T connection) { + if (connection instanceof HttpsURLConnection) { + ((HttpsURLConnection)connection).setSSLSocketFactory(getInstance()); + } + return connection; + } + + /** trusts all SSL certificates */ + public static final TrustManager TRUST_ALL = new X509TrustManager() { + public X509Certificate[] getAcceptedIssuers() { + return new X509Certificate[0]; + } + @Override + public void checkClientTrusted(X509Certificate[] chain, String authType) + throws java.security.cert.CertificateException { + + } + @Override + public void checkServerTrusted(X509Certificate[] chain, String authType) + throws java.security.cert.CertificateException { + } + }; + + // no reason this can't be public, but no reason it should be necessary; + // just use getInstance to get the shared INSTANCE + protected TrustingSslSocketFactory() { + super(); + try { + sslContext.init(null, new TrustManager[] { TRUST_ALL }, null); + } catch (Exception e) { + throw Throwables.propagate(e); + } + } + + @Override + public Socket createSocket(Socket socket, String host, int port, boolean autoClose) throws IOException, UnknownHostException { + return sslContext.getSocketFactory().createSocket(socket, host, port, autoClose); + } + + @Override + public Socket createSocket() throws IOException { + return sslContext.getSocketFactory().createSocket(); + } + + @Override + public String[] getDefaultCipherSuites() { + return sslContext.getSocketFactory().getDefaultCipherSuites(); + } + + @Override + public String[] getSupportedCipherSuites() { + return sslContext.getSocketFactory().getSupportedCipherSuites(); + } + + @Override + public Socket createSocket(String arg0, int arg1) throws IOException, UnknownHostException { + return sslContext.getSocketFactory().createSocket(arg0, arg1); + } + + @Override + public Socket createSocket(InetAddress arg0, int arg1) throws IOException { + return sslContext.getSocketFactory().createSocket(arg0, arg1); + } + + @Override + public Socket createSocket(String arg0, int arg1, InetAddress arg2, int arg3) throws IOException, UnknownHostException { + return sslContext.getSocketFactory().createSocket(arg0, arg1, arg2, arg3); + } + + @Override + public Socket createSocket(InetAddress arg0, int arg1, InetAddress arg2, int arg3) throws IOException { + return sslContext.getSocketFactory().createSocket(arg0, arg1, arg2, arg3); + } +} \ No newline at end of file
