| /* |
| * 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.geode.cache.client; |
| |
| import static org.apache.geode.distributed.ConfigurationProperties.CACHE_XML_FILE; |
| import static org.apache.geode.distributed.ConfigurationProperties.LOCATORS; |
| import static org.apache.geode.distributed.ConfigurationProperties.LOG_FILE; |
| import static org.apache.geode.distributed.ConfigurationProperties.LOG_LEVEL; |
| import static org.apache.geode.distributed.ConfigurationProperties.MCAST_PORT; |
| import static org.assertj.core.api.Assertions.assertThat; |
| import static org.assertj.core.api.Assertions.assertThatThrownBy; |
| import static org.junit.runners.MethodSorters.NAME_ASCENDING; |
| |
| import java.io.ByteArrayInputStream; |
| import java.io.DataInputStream; |
| import java.io.File; |
| import java.io.FileWriter; |
| import java.io.IOException; |
| import java.io.PrintWriter; |
| import java.net.InetAddress; |
| import java.net.InetSocketAddress; |
| import java.net.URL; |
| import java.nio.charset.Charset; |
| import java.util.Collections; |
| import java.util.Properties; |
| |
| import org.apache.commons.io.FileUtils; |
| import org.jgroups.util.UUID; |
| import org.junit.After; |
| import org.junit.AfterClass; |
| import org.junit.FixMethodOrder; |
| import org.junit.Rule; |
| import org.junit.Test; |
| import org.junit.experimental.categories.Category; |
| import org.junit.rules.TemporaryFolder; |
| import org.junit.rules.TestName; |
| |
| import org.apache.geode.DataSerializer; |
| import org.apache.geode.cache.RegionService; |
| import org.apache.geode.cache.client.internal.ProxyCache; |
| import org.apache.geode.cache.client.internal.UserAttributes; |
| import org.apache.geode.cache.client.proxy.SniProxySocketFactory; |
| import org.apache.geode.cache.server.CacheServer; |
| import org.apache.geode.distributed.DistributedSystem; |
| import org.apache.geode.distributed.internal.InternalDistributedSystem; |
| import org.apache.geode.distributed.internal.membership.InternalDistributedMember; |
| import org.apache.geode.internal.HeapDataOutputStream; |
| import org.apache.geode.internal.cache.GemFireCacheImpl; |
| import org.apache.geode.internal.cache.tier.sockets.ClientProxyMembershipID; |
| import org.apache.geode.internal.cache.xmlcache.CacheXmlGenerator; |
| import org.apache.geode.internal.cache.xmlcache.ClientCacheCreation; |
| import org.apache.geode.internal.serialization.KnownVersion; |
| import org.apache.geode.internal.serialization.VersionedDataInputStream; |
| import org.apache.geode.pdx.ReflectionBasedAutoSerializer; |
| import org.apache.geode.test.junit.categories.ClientServerTest; |
| import org.apache.geode.test.junit.rules.serializable.SerializableTestName; |
| |
| /** |
| * Unit test for the ClientCacheFactory class |
| * |
| * @since GemFire 6.5 |
| */ |
| @FixMethodOrder(NAME_ASCENDING) |
| @Category(ClientServerTest.class) |
| public class ClientCacheFactoryJUnitTest { |
| private ClientCache clientCache; |
| |
| @Rule |
| public TestName testName = new SerializableTestName(); |
| |
| @Rule |
| public TemporaryFolder temporaryFolder = new TemporaryFolder(); |
| |
| @After |
| public void tearDown() throws Exception { |
| if (clientCache != null && !clientCache.isClosed()) { |
| clientCache.close(); |
| } |
| } |
| |
| @AfterClass |
| public static void afterClass() { |
| InternalDistributedSystem ids = InternalDistributedSystem.getAnyInstance(); |
| if (ids != null) { |
| ids.disconnect(); |
| } |
| } |
| |
| @Test |
| public void test000Defaults() throws Exception { |
| clientCache = new ClientCacheFactory().create(); |
| GemFireCacheImpl gfc = (GemFireCacheImpl) clientCache; |
| assertThat(gfc.isClient()).isTrue(); |
| |
| Properties dsProps = clientCache.getDistributedSystem().getProperties(); |
| assertThat(dsProps.getProperty(MCAST_PORT)).isEqualTo("0"); |
| assertThat(dsProps.getProperty(LOCATORS)).isEqualTo(""); |
| |
| Pool defPool = gfc.getDefaultPool(); |
| assertThat(defPool.getName()).isEqualTo("DEFAULT"); |
| assertThat(defPool.getLocators()).isEqualTo(Collections.emptyList()); |
| assertThat(defPool.getSocketConnectTimeout()) |
| .isEqualTo(PoolFactory.DEFAULT_SOCKET_CONNECT_TIMEOUT); |
| assertThat(defPool.getServers()).isEqualTo(Collections.singletonList( |
| new InetSocketAddress(InetAddress.getLocalHost(), CacheServer.DEFAULT_PORT))); |
| |
| ClientCache cc2 = new ClientCacheFactory().create(); |
| assertThat(cc2).as("expected cc2 and cc to be == " + cc2 + clientCache) |
| .isSameAs(clientCache); |
| assertThatThrownBy(() -> new ClientCacheFactory().set(LOG_LEVEL, "severe").create()) |
| .isInstanceOf(IllegalStateException.class); |
| assertThatThrownBy(() -> new ClientCacheFactory().addPoolLocator("127.0.0.1", 36666).create()) |
| .isInstanceOf(IllegalStateException.class); |
| } |
| |
| @Test |
| public void test001FindDefaultPoolFromXML() throws Exception { |
| File cacheXmlFile = temporaryFolder.newFile("ClientCacheFactoryJUnitTest.xml"); |
| URL url = ClientCacheFactoryJUnitTest.class |
| .getResource("ClientCacheFactoryJUnitTest_single_pool.xml"); |
| FileUtils.copyFile(new File(url.getFile()), cacheXmlFile); |
| |
| clientCache = |
| new ClientCacheFactory().set(CACHE_XML_FILE, cacheXmlFile.getAbsolutePath()).create(); |
| GemFireCacheImpl gfc = (GemFireCacheImpl) clientCache; |
| assertThat(gfc.isClient()).isTrue(); |
| |
| Properties dsProps = clientCache.getDistributedSystem().getProperties(); |
| assertThat(dsProps.getProperty(MCAST_PORT)).isEqualTo("0"); |
| assertThat(dsProps.getProperty(LOCATORS)).isEqualTo(""); |
| |
| Pool defPool = gfc.getDefaultPool(); |
| assertThat(defPool.getName()).isEqualTo("my_pool_name"); |
| assertThat(defPool.getLocators()).isEqualTo(Collections.emptyList()); |
| assertThat(defPool.getSocketConnectTimeout()) |
| .isEqualTo(PoolFactory.DEFAULT_SOCKET_CONNECT_TIMEOUT); |
| assertThat(defPool.getServers()).isEqualTo( |
| Collections.singletonList(new InetSocketAddress("localhost", CacheServer.DEFAULT_PORT))); |
| |
| // verify that the SocketCreator settings were correctly picked up from the xml file |
| SocketFactory factory = defPool.getSocketFactory(); |
| assertThat(factory).isInstanceOf(SniProxySocketFactory.class); |
| SniProxySocketFactory sniProxySocketFactory = (SniProxySocketFactory) factory; |
| assertThat(sniProxySocketFactory.getPort()).isEqualTo(40404); |
| assertThat(sniProxySocketFactory.getHostname()).isEqualTo("localhost"); |
| } |
| |
| /** |
| * Make sure if we have a single pool that it will be used as the default |
| */ |
| @Test |
| public void test002DPsinglePool() throws Exception { |
| Properties dsProps = new Properties(); |
| dsProps.setProperty(MCAST_PORT, "0"); |
| DistributedSystem.connect(dsProps); |
| Pool p = PoolManager.createFactory().addServer(InetAddress.getLocalHost().getHostName(), 7777) |
| .setSocketConnectTimeout(1400).create("singlePool"); |
| |
| clientCache = new ClientCacheFactory().create(); |
| GemFireCacheImpl gfc = (GemFireCacheImpl) clientCache; |
| assertThat(gfc.isClient()).isTrue(); |
| |
| Pool defPool = gfc.getDefaultPool(); |
| assertThat(defPool).isEqualTo(p); |
| assertThat(defPool.getSocketConnectTimeout()).isEqualTo(1400); |
| |
| // make sure if we can not create a secure user cache when one pool exists that is not multiuser |
| // enabled |
| Properties suProps = new Properties(); |
| suProps.setProperty("user", "foo"); |
| assertThatThrownBy(() -> clientCache.createAuthenticatedView(suProps)) |
| .isInstanceOf(IllegalStateException.class); |
| |
| // however we should be to to create it by configuring a pool |
| Pool pool = PoolManager.createFactory() |
| .addServer(InetAddress.getLocalHost().getHostName(), CacheServer.DEFAULT_PORT) |
| .setMultiuserAuthentication(true).setSocketConnectTimeout(2345).create("pool1"); |
| RegionService cc = clientCache.createAuthenticatedView(suProps, pool.getName()); |
| ProxyCache pc = (ProxyCache) cc; |
| UserAttributes ua = pc.getUserAttributes(); |
| Pool proxyDefPool = ua.getPool(); |
| assertThat(proxyDefPool.getServers()).isEqualTo(Collections.singletonList( |
| new InetSocketAddress(InetAddress.getLocalHost(), CacheServer.DEFAULT_PORT))); |
| assertThat(proxyDefPool.getMultiuserAuthentication()).isTrue(); |
| assertThat(proxyDefPool.getSocketConnectTimeout()).isEqualTo(2345); |
| } |
| |
| /** |
| * Make sure if we have more than one pool that we do not have a default |
| */ |
| @Test |
| public void test003DPmultiplePool() throws Exception { |
| Properties dsProps = new Properties(); |
| dsProps.setProperty(MCAST_PORT, "0"); |
| DistributedSystem.connect(dsProps); |
| PoolManager.createFactory().addServer(InetAddress.getLocalHost().getHostName(), 7777) |
| .setSocketConnectTimeout(2500).create("p7"); |
| PoolManager.createFactory().addServer(InetAddress.getLocalHost().getHostName(), 6666) |
| .setSocketConnectTimeout(5200).create("p6"); |
| |
| clientCache = new ClientCacheFactory().create(); |
| GemFireCacheImpl gfc = (GemFireCacheImpl) clientCache; |
| assertThat(gfc.isClient()).isTrue(); |
| |
| Pool defPool = gfc.getDefaultPool(); |
| assertThat(defPool).isNull(); |
| assertThat(PoolManager.find("p7").getSocketConnectTimeout()).isEqualTo(2500); |
| assertThat(PoolManager.find("p6").getSocketConnectTimeout()).isEqualTo(5200); |
| |
| // make sure if we can not create a secure user cache when more than one pool exists that is not |
| // multiuser enabled |
| Properties suProps = new Properties(); |
| suProps.setProperty("user", "foo"); |
| assertThatThrownBy(() -> clientCache.createAuthenticatedView(suProps)) |
| .isInstanceOf(IllegalStateException.class); |
| |
| // however we should be to to create it by configuring a pool |
| Pool pool = PoolManager.createFactory() |
| .addServer(InetAddress.getLocalHost().getHostName(), CacheServer.DEFAULT_PORT) |
| .setMultiuserAuthentication(true).create("pool1"); |
| RegionService cc = clientCache.createAuthenticatedView(suProps, pool.getName()); |
| ProxyCache pc = (ProxyCache) cc; |
| UserAttributes ua = pc.getUserAttributes(); |
| Pool proxyDefPool = ua.getPool(); |
| assertThat(proxyDefPool.getServers()).isEqualTo(Collections.singletonList( |
| new InetSocketAddress(InetAddress.getLocalHost(), CacheServer.DEFAULT_PORT))); |
| assertThat(proxyDefPool.getMultiuserAuthentication()).isTrue(); |
| assertThat(proxyDefPool.getSocketConnectTimeout()) |
| .isEqualTo(PoolFactory.DEFAULT_SOCKET_CONNECT_TIMEOUT); |
| } |
| |
| @Test |
| public void test004SetMethod() { |
| clientCache = |
| new ClientCacheFactory().set(LOG_LEVEL, "severe").setPoolSocketConnectTimeout(0).create(); |
| GemFireCacheImpl gfc = (GemFireCacheImpl) clientCache; |
| assertThat(gfc.isClient()).isTrue(); |
| |
| Properties dsProps = clientCache.getDistributedSystem().getProperties(); |
| assertThat(dsProps.getProperty(MCAST_PORT)).isEqualTo("0"); |
| assertThat(dsProps.getProperty(LOCATORS)).isEqualTo(""); |
| assertThat(dsProps.getProperty(LOG_LEVEL)).isEqualTo("severe"); |
| assertThat(clientCache.getDefaultPool().getSocketConnectTimeout()).isEqualTo(0); |
| |
| assertThatThrownBy(() -> new ClientCacheFactory().setPoolSocketConnectTimeout(-1).create()) |
| .isInstanceOf(IllegalArgumentException.class); |
| } |
| |
| @Test |
| public void test005SecureUserDefaults() throws Exception { |
| Properties suProps = new Properties(); |
| suProps.setProperty("user", "foo"); |
| GemFireCacheImpl gfc = |
| (GemFireCacheImpl) new ClientCacheFactory().setPoolMultiuserAuthentication(true).create(); |
| clientCache = gfc; |
| |
| RegionService cc1 = clientCache.createAuthenticatedView(suProps); |
| assertThat(gfc.isClient()).isTrue(); |
| Properties dsProps = clientCache.getDistributedSystem().getProperties(); |
| assertThat(dsProps.getProperty(MCAST_PORT)).isEqualTo("0"); |
| assertThat(dsProps.getProperty(LOCATORS)).isEqualTo(""); |
| |
| Pool defPool = gfc.getDefaultPool(); |
| assertThat(defPool.getName()).isEqualTo("DEFAULT"); |
| assertThat(defPool.getMultiuserAuthentication()).isTrue(); |
| assertThat(defPool.getLocators()).isEqualTo(Collections.emptyList()); |
| assertThat(defPool.getServers()).isEqualTo(Collections.singletonList( |
| new InetSocketAddress(InetAddress.getLocalHost(), CacheServer.DEFAULT_PORT))); |
| |
| // make sure we can create another secure user cache |
| RegionService cc2 = clientCache.createAuthenticatedView(suProps); |
| assertThat(gfc.isClient()).isTrue(); |
| assertThat(dsProps.getProperty(MCAST_PORT)).isEqualTo("0"); |
| assertThat(dsProps.getProperty(LOCATORS)).isEqualTo(""); |
| |
| defPool = gfc.getDefaultPool(); |
| assertThat(defPool.getName()).isEqualTo("DEFAULT"); |
| assertThat(defPool.getMultiuserAuthentication()).isTrue(); |
| assertThat(defPool.getLocators()).isEqualTo(Collections.emptyList()); |
| assertThat(defPool.getServers()).isEqualTo(Collections.singletonList( |
| new InetSocketAddress(InetAddress.getLocalHost(), CacheServer.DEFAULT_PORT))); |
| |
| assertThat(cc1).as("expected two different secure user caches").isNotSameAs(cc2); |
| } |
| |
| @Test |
| public void test006NonDefaultPool() throws Exception { |
| clientCache = new ClientCacheFactory() |
| .addPoolServer(InetAddress.getLocalHost().getHostName(), 55555).create(); |
| GemFireCacheImpl gfc = (GemFireCacheImpl) clientCache; |
| assertThat(gfc.isClient()).isTrue(); |
| |
| Properties dsProps = clientCache.getDistributedSystem().getProperties(); |
| assertThat(dsProps.getProperty(LOCATORS)).isEqualTo(""); |
| assertThat(dsProps.getProperty(MCAST_PORT)).isEqualTo("0"); |
| |
| Pool defPool = gfc.getDefaultPool(); |
| assertThat(defPool.getName()).isEqualTo("DEFAULT"); |
| assertThat(defPool.getLocators()).isEqualTo(Collections.emptyList()); |
| assertThat(defPool.getServers()).isEqualTo( |
| Collections.singletonList(new InetSocketAddress(InetAddress.getLocalHost(), 55555))); |
| |
| gfc = (GemFireCacheImpl) clientCache; |
| assertThat(gfc.isClient()).isTrue(); |
| |
| dsProps = clientCache.getDistributedSystem().getProperties(); |
| assertThat(dsProps.getProperty(LOCATORS)).isEqualTo(""); |
| assertThat(dsProps.getProperty(MCAST_PORT)).isEqualTo("0"); |
| |
| defPool = gfc.getDefaultPool(); |
| assertThat(defPool.getName()).isEqualTo("DEFAULT"); |
| assertThat(defPool.getLocators()).isEqualTo(Collections.emptyList()); |
| assertThat(defPool.getServers()).isEqualTo( |
| Collections.singletonList(new InetSocketAddress(InetAddress.getLocalHost(), 55555))); |
| |
| assertThatThrownBy(() -> new ClientCacheFactory() |
| .addPoolServer(InetAddress.getLocalHost().getHostName(), 44444).create()) |
| .isInstanceOf(IllegalStateException.class); |
| } |
| |
| @Test |
| public void test007Bug44907() { |
| new ClientCacheFactory().setPdxSerializer(new ReflectionBasedAutoSerializer()).create(); |
| clientCache = |
| new ClientCacheFactory().setPdxSerializer(new ReflectionBasedAutoSerializer()).create(); |
| } |
| |
| @Test |
| public void testDefaultPoolTimeoutMultiplier() throws Exception { |
| clientCache = new ClientCacheFactory().setPoolSubscriptionTimeoutMultiplier(2) |
| .addPoolServer(InetAddress.getLocalHost().getHostName(), 7777).create(); |
| Pool defaultPool = clientCache.getDefaultPool(); |
| assertThat(defaultPool.getSubscriptionTimeoutMultiplier()).isEqualTo(2); |
| } |
| |
| @Test |
| public void testOldClientIDDeserialization() throws Exception { |
| // during a HandShake a clientID is read w/o knowing the client's version |
| clientCache = new ClientCacheFactory().create(); |
| InternalDistributedMember memberID = |
| (InternalDistributedMember) clientCache.getDistributedSystem().getDistributedMember(); |
| memberID.setVersionForTest(KnownVersion.GFE_81); |
| assertThat(memberID.getVersion()).isEqualTo(KnownVersion.GFE_81); |
| |
| ClientProxyMembershipID clientID = ClientProxyMembershipID.getClientId(memberID); |
| HeapDataOutputStream out = new HeapDataOutputStream(KnownVersion.GFE_81); |
| DataSerializer.writeObject(clientID, out); |
| |
| DataInputStream in = |
| new VersionedDataInputStream(new ByteArrayInputStream(out.toByteArray()), |
| KnownVersion.CURRENT); |
| ClientProxyMembershipID newID = DataSerializer.readObject(in); |
| InternalDistributedMember newMemberID = |
| (InternalDistributedMember) newID.getDistributedMember(); |
| assertThat(newMemberID.getVersion()).isEqualTo(KnownVersion.GFE_81); |
| assertThat(newID.getClientVersion()).isEqualTo(KnownVersion.GFE_81); |
| |
| assertThat(newMemberID.getUuidLeastSignificantBits()).isEqualTo(0); |
| assertThat(newMemberID.getUuidMostSignificantBits()).isEqualTo(0); |
| |
| memberID.setUUID(new UUID(1234L, 5678L)); |
| memberID.setVersionForTest(KnownVersion.CURRENT); |
| clientID = ClientProxyMembershipID.getClientId(memberID); |
| out = new HeapDataOutputStream(KnownVersion.CURRENT); |
| DataSerializer.writeObject(clientID, out); |
| |
| in = new VersionedDataInputStream(new ByteArrayInputStream(out.toByteArray()), |
| KnownVersion.CURRENT); |
| newID = DataSerializer.readObject(in); |
| newMemberID = (InternalDistributedMember) newID.getDistributedMember(); |
| assertThat(newMemberID.getVersion()).isEqualTo(KnownVersion.CURRENT); |
| assertThat(newID.getClientVersion()).isEqualTo(KnownVersion.CURRENT); |
| |
| assertThat(newMemberID.getUuidLeastSignificantBits()) |
| .isEqualTo(memberID.getUuidLeastSignificantBits()); |
| assertThat(newMemberID.getUuidMostSignificantBits()) |
| .isEqualTo(memberID.getUuidMostSignificantBits()); |
| } |
| |
| @Test |
| public void configuringPdxPersistenceThroughAPIShouldLogWarningMessage() throws IOException { |
| File logFile = temporaryFolder.newFile(testName.getMethodName() + ".log"); |
| clientCache = new ClientCacheFactory() |
| .set(LOG_LEVEL, "warn") |
| .set(LOG_FILE, logFile.getAbsolutePath()) |
| .setPdxPersistent(true) |
| .setPdxSerializer(new ReflectionBasedAutoSerializer()) |
| .create(); |
| |
| assertThat(FileUtils.readFileToString(logFile, Charset.defaultCharset()) |
| .contains("PDX persistence is not supported on client side.")).isTrue(); |
| } |
| |
| @Test |
| public void configuringPdxDiskStoreThroughAPIShouldLogWarningMessage() throws IOException { |
| File logFile = temporaryFolder.newFile(testName.getMethodName() + ".log"); |
| clientCache = new ClientCacheFactory() |
| .set(LOG_LEVEL, "warn") |
| .set(LOG_FILE, logFile.getAbsolutePath()) |
| .setPdxDiskStore("pdxDiskStore") |
| .setPdxSerializer(new ReflectionBasedAutoSerializer()) |
| .create(); |
| |
| assertThat(FileUtils.readFileToString(logFile, Charset.defaultCharset()) |
| .contains("PDX persistence is not supported on client side.")).isTrue(); |
| } |
| |
| @Test |
| public void configuringPdxPersistenceThroughXMLShouldLogWarningMessage() throws IOException { |
| ClientCacheCreation clientCacheCreation = new ClientCacheCreation(); |
| clientCacheCreation.setPdxPersistent(true); |
| clientCacheCreation.setPdxSerializer(new ReflectionBasedAutoSerializer()); |
| |
| File logFile = temporaryFolder.newFile(testName.getMethodName() + ".log"); |
| File cacheXmlFile = temporaryFolder.newFile(testName.getMethodName() + ".xml"); |
| |
| try (PrintWriter printWriter = new PrintWriter(new FileWriter(cacheXmlFile), true)) { |
| CacheXmlGenerator.generate(clientCacheCreation, printWriter, false, false); |
| clientCache = new ClientCacheFactory() |
| .set(LOG_LEVEL, "warn") |
| .set(LOG_FILE, logFile.getAbsolutePath()) |
| .set(CACHE_XML_FILE, cacheXmlFile.getAbsolutePath()) |
| .create(); |
| |
| assertThat(FileUtils.readFileToString(logFile, Charset.defaultCharset()) |
| .contains("PDX persistence is not supported on client side.")).isTrue(); |
| } |
| } |
| |
| @Test |
| public void configuringPdxDiskStoreThroughXMLShouldLogWarningMessage() throws IOException { |
| ClientCacheCreation clientCacheCreation = new ClientCacheCreation(); |
| clientCacheCreation.setPdxDiskStore("pdxDiskStore"); |
| clientCacheCreation.setPdxSerializer(new ReflectionBasedAutoSerializer()); |
| |
| File logFile = temporaryFolder.newFile(testName.getMethodName() + ".log"); |
| File cacheXmlFile = temporaryFolder.newFile(testName.getMethodName() + ".xml"); |
| try (PrintWriter printWriter = new PrintWriter(new FileWriter(cacheXmlFile), true)) { |
| CacheXmlGenerator.generate(clientCacheCreation, printWriter, false, false); |
| clientCache = new ClientCacheFactory() |
| .set(LOG_LEVEL, "warn") |
| .set(LOG_FILE, logFile.getAbsolutePath()) |
| .set(CACHE_XML_FILE, cacheXmlFile.getAbsolutePath()) |
| .create(); |
| |
| assertThat(FileUtils.readFileToString(logFile, Charset.defaultCharset()) |
| .contains("PDX persistence is not supported on client side.")).isTrue(); |
| } |
| } |
| } |