Skip to content
Merged
20 changes: 17 additions & 3 deletions src/main/java/io/appium/java_client/proxy/Helpers.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
package io.appium.java_client.proxy;

import com.google.common.base.Preconditions;
import java.util.Map;
import java.util.WeakHashMap;
import lombok.Value;
import net.bytebuddy.ByteBuddy;
import net.bytebuddy.description.method.MethodDescription;
Expand All @@ -32,14 +34,15 @@
import java.util.Collection;
import java.util.Collections;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static java.util.Objects.requireNonNull;
import static net.bytebuddy.matcher.ElementMatchers.namedOneOf;

/**
* The type Helpers.
*/
public class Helpers {
public static final Set<String> OBJECT_METHOD_NAMES = Stream.of(Object.class.getMethods())
.map(Method::getName)
Expand All @@ -50,7 +53,17 @@ public class Helpers {
// the performance and to avoid extensive memory usage for our case, where
// the amount of instrumented proxy classes we create is low in comparison to the amount
// of proxy instances.
private static final ConcurrentMap<ProxyClassSignature, Class<?>> CACHED_PROXY_CLASSES = new ConcurrentHashMap<>();
private static final Map<ProxyClassSignature, Class<?>> CACHED_PROXY_CLASSES = Collections.synchronizedMap(new WeakHashMap<>());

/**
* Gets CACHED_PROXY_CLASSES size.
* Used for cache clear up tests.
*
* @return the cached proxy classes size
*/
public static int getCachedProxyClassesSize() {
return CACHED_PROXY_CLASSES.size();
}

private Helpers() {
}
Expand Down Expand Up @@ -115,6 +128,7 @@ public static <T> T createProxy(
@Nullable ElementMatcher<MethodDescription> extraMethodMatcher
) {
var signature = ProxyClassSignature.of(cls, constructorArgTypes, extraMethodMatcher);
System.out.println("CACHED_PROXY_CLASSES size = " + CACHED_PROXY_CLASSES.size());
var proxyClass = CACHED_PROXY_CLASSES.computeIfAbsent(signature, k -> {
Preconditions.checkArgument(constructorArgs.length == constructorArgTypes.length,
String.format(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
import io.appium.java_client.pagefactory_tests.widget.tests.AbstractStubWebDriver;
import io.appium.java_client.pagefactory_tests.widget.tests.DefaultStubWidget;
import io.appium.java_client.pagefactory_tests.widget.tests.android.DefaultAndroidWidget;
import io.appium.java_client.proxy.Helpers;
import org.junit.jupiter.api.RepeatedTest;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
Expand All @@ -20,6 +22,7 @@
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.lessThan;
import static org.openqa.selenium.support.PageFactory.initElements;


Expand All @@ -33,30 +36,31 @@ public class CombinedWidgetTest {
*/
public static Stream<Arguments> data() {
return Stream.of(
Arguments.of(new AppWithCombinedWidgets(),
new AbstractStubWebDriver.StubAndroidDriver(), DefaultAndroidWidget.class),
Arguments.of(new AppWithCombinedWidgets(),
new AbstractStubWebDriver.StubIOSXCUITDriver(), DefaultIosXCUITWidget.class),
Arguments.of(new AppWithCombinedWidgets(),
new AbstractStubWebDriver.StubBrowserDriver(), DefaultFindByWidget.class),
Arguments.of(new AppWithCombinedWidgets(),
new AbstractStubWebDriver.StubAndroidBrowserOrWebViewDriver(), DefaultFindByWidget.class),
Arguments.of(new AppWithPartiallyCombinedWidgets(),
new AbstractStubWebDriver.StubAndroidDriver(), DefaultAndroidWidget.class),
Arguments.of(new AppWithPartiallyCombinedWidgets(),
new AbstractStubWebDriver.StubIOSXCUITDriver(), DefaultStubWidget.class),
Arguments.of(new AppWithPartiallyCombinedWidgets(),
new AbstractStubWebDriver.StubWindowsDriver(), DefaultStubWidget.class),
Arguments.of(new AppWithPartiallyCombinedWidgets(),
new AbstractStubWebDriver.StubBrowserDriver(), DefaultFindByWidget.class),
Arguments.of(new AppWithPartiallyCombinedWidgets(),
new AbstractStubWebDriver.StubAndroidBrowserOrWebViewDriver(), DefaultFindByWidget.class)
Arguments.of(new AppWithCombinedWidgets(),
new AbstractStubWebDriver.StubAndroidDriver(), DefaultAndroidWidget.class),
Arguments.of(new AppWithCombinedWidgets(),
new AbstractStubWebDriver.StubIOSXCUITDriver(), DefaultIosXCUITWidget.class),
Arguments.of(new AppWithCombinedWidgets(),
new AbstractStubWebDriver.StubBrowserDriver(), DefaultFindByWidget.class),
Arguments.of(new AppWithCombinedWidgets(),
new AbstractStubWebDriver.StubAndroidBrowserOrWebViewDriver(), DefaultFindByWidget.class),
Arguments.of(new AppWithPartiallyCombinedWidgets(),
new AbstractStubWebDriver.StubAndroidDriver(), DefaultAndroidWidget.class),
Arguments.of(new AppWithPartiallyCombinedWidgets(),
new AbstractStubWebDriver.StubIOSXCUITDriver(), DefaultStubWidget.class),
Arguments.of(new AppWithPartiallyCombinedWidgets(),
new AbstractStubWebDriver.StubWindowsDriver(), DefaultStubWidget.class),
Arguments.of(new AppWithPartiallyCombinedWidgets(),
new AbstractStubWebDriver.StubBrowserDriver(), DefaultFindByWidget.class),
Arguments.of(new AppWithPartiallyCombinedWidgets(),
new AbstractStubWebDriver.StubAndroidBrowserOrWebViewDriver(), DefaultFindByWidget.class)
);
}

@ParameterizedTest
@MethodSource("data")
void checkThatWidgetsAreCreatedCorrectly(AbstractApp app, WebDriver driver, Class<?> widgetClass) {
assertProxyClassCacheGrowth();
initElements(new AppiumFieldDecorator(driver), app);
assertThat("Expected widget class was " + widgetClass.getName(),
app.getWidget().getSubWidget().getSelfReference().getClass(),
Expand Down Expand Up @@ -161,4 +165,22 @@ public List<PartiallyCombinedWidget> getWidgets() {
return multipleWidgets;
}
}


/**
* Assert proxy class cache growth for this test class.
* The (@link io.appium.java_client.proxy.Helpers#CACHED_PROXY_CLASSES) should be populated during these tests.
* Prior to the Caching issue being resolved
* - the CACHED_PROXY_CLASSES would grow indefinitely, resulting in an Out Of Memory exception.
* - this ParameterizedTest would have the CACHED_PROXY_CLASSES grow to 266 entries.
*/
private void assertProxyClassCacheGrowth() {
System.gc(); //Trying to force a collection for more accurate check numbers
int thresholdSize = 50;
assertThat(
"Proxy Class Cache threshold is " + thresholdSize,
Helpers.getCachedProxyClassesSize(),
lessThan(thresholdSize)
);
}
}
Loading