Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Supporting methods without Retry #85

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,12 @@

import io.appium.java_client.AppiumDriver;
import io.qameta.atlas.appium.context.AppiumDriverContext;
import io.qameta.atlas.appium.extension.AppiumDriverProviderExtension;
import io.qameta.atlas.appium.extension.AppiumFindByExtension;
import io.qameta.atlas.appium.extension.LongPressExtension;
import io.qameta.atlas.appium.extension.SwipeDownOnExtension;
import io.qameta.atlas.appium.extension.SwipeUpOnExtension;
import io.qameta.atlas.appium.extension.*;
import io.qameta.atlas.core.context.RetryerContext;
import io.qameta.atlas.core.internal.Configuration;
import io.qameta.atlas.core.internal.DefaultMethodExtension;
import io.qameta.atlas.core.internal.EmptyRetryer;
import io.qameta.atlas.webdriver.extension.FilterCollectionExtension;
import io.qameta.atlas.webdriver.extension.FindByCollectionExtension;
import io.qameta.atlas.webdriver.extension.ShouldMethodExtension;
import io.qameta.atlas.webdriver.extension.ToStringMethodExtension;
import io.qameta.atlas.webdriver.extension.WaitUntilMethodExtension;
import io.qameta.atlas.webdriver.extension.WrappedElementMethodExtension;
import io.qameta.atlas.webdriver.extension.*;


/**
Expand All @@ -29,6 +20,7 @@ public AppiumDriverConfiguration(final AppiumDriver appiumDriver) {
registerContext(new AppiumDriverContext(appiumDriver));
registerContext(new RetryerContext(new EmptyRetryer()));
registerExtension(new AppiumDriverProviderExtension());
registerExtension(new RetryAnnotationExtension());
registerExtension(new DefaultMethodExtension());
registerExtension(new AppiumFindByExtension());
registerExtension(new ToStringMethodExtension());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ public void retryChildFind() {
* Parent mobile element.
*/
private interface ParentElement extends AtlasMobileElement {
@Retry(timeout = 1000L)
@Retry(timeout = 1000L, polling = 100)
@AndroidFindBy(xpath = "//div")
NestedElement child();
}
Expand Down
13 changes: 8 additions & 5 deletions atlas-core/src/main/java/io/qameta/atlas/core/Atlas.java
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
package io.qameta.atlas.core;

import io.qameta.atlas.core.api.Context;
import io.qameta.atlas.core.api.Listener;
import io.qameta.atlas.core.api.MethodExtension;
import io.qameta.atlas.core.api.MethodInvoker;
import io.qameta.atlas.core.api.Target;
import io.qameta.atlas.core.api.*;
import io.qameta.atlas.core.context.RetryerContext;
import io.qameta.atlas.core.context.TargetContext;
import io.qameta.atlas.core.internal.*;
import io.qameta.atlas.core.target.HardcodedTarget;
Expand All @@ -14,12 +11,14 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;

import static io.qameta.atlas.core.util.ReflectionUtils.getMethods;

/**
* @author Artem Eroshenko.
*/
@SuppressWarnings("checkstyle:ClassDataAbstractionCoupling")
public class Atlas {

private final Configuration configuration;
Expand All @@ -30,6 +29,10 @@ public Atlas() {

public Atlas(final Configuration configuration) {
this.configuration = configuration;
final Optional<RetryerContext> context = this.configuration.getContext(RetryerContext.class);
if (!context.isPresent()) {
configuration.registerContext(new RetryerContext(new EmptyRetryer()));
}
}

public Atlas listener(final Listener listener) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
*/
public interface Listener extends Extension {

void beforeMethodCall(MethodInfo methodInfo, Configuration configuration);
Configuration beforeMethodCall(MethodInfo methodInfo, Configuration configuration);

void afterMethodCall(MethodInfo methodInfo, Configuration configuration);

Expand Down
4 changes: 2 additions & 2 deletions atlas-core/src/main/java/io/qameta/atlas/core/api/Retry.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@
@java.lang.annotation.Target(ElementType.METHOD)
public @interface Retry {

long timeout() default 5000L;
long timeout() default -1;

long polling() default 1000L;
long polling() default -1;

Class<? extends Throwable>[] ignoring() default {Throwable.class};

Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,14 @@
package io.qameta.atlas.core.internal;

import io.qameta.atlas.core.api.MethodInvoker;
import io.qameta.atlas.core.api.Retry;
import io.qameta.atlas.core.api.Timeout;
import io.qameta.atlas.core.context.RetryerContext;
import io.qameta.atlas.core.util.MethodInfo;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Map;
import java.util.Optional;
import java.util.function.Consumer;

/**
* Atlas method handler.
Expand All @@ -34,38 +32,41 @@ public AtlasMethodHandler(final Configuration configuration,
@Override
public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable {
final MethodInfo methodInfo = new MethodInfo(method, args);

notifier.beforeMethodCall(methodInfo, configuration);
final Configuration runConfig = notifier.beforeMethodCall(methodInfo, this.configuration);
try {
final MethodInvoker handler = handlers.get(method);
final Object result = invokeWithRetry(handler, proxy, methodInfo);
notifier.onMethodReturn(methodInfo, configuration, result);
notifier.onMethodReturn(methodInfo, runConfig, result);
return result;
} catch (Throwable e) {
notifier.onMethodFailure(methodInfo, configuration, e);
notifier.onMethodFailure(methodInfo, runConfig, e);
throw e;
} finally {
notifier.afterMethodCall(methodInfo, configuration);
notifier.afterMethodCall(methodInfo, runConfig);
}
}

private Object invokeWithRetry(final MethodInvoker invoker,
final Object proxy,
final MethodInfo methodInfo) throws Throwable {
final Retryer retryer = Optional.ofNullable(methodInfo.getMethod().getAnnotation(Retry.class))
.map(retry -> (Retryer) new DefaultRetryer(retry.timeout(), retry.polling(),
Arrays.asList(retry.ignoring())))
.orElseGet(() -> configuration.getContext(RetryerContext.class)
.orElseGet(() -> new RetryerContext(new EmptyRetryer())).getValue());
methodInfo.getParameter(Integer.class, Timeout.class).ifPresent(retryer::timeoutInSeconds);
final Retryer retryer = getRetryer(configuration, methodInfo);
Throwable lastException;
do {
try {
return invoker.invoke(proxy, methodInfo, configuration);
} catch (Throwable e) {
lastException = e;
}
} while (retryer.shouldRetry(lastException));
} while (retryer.shouldRetry(lastException, methodInfo));
throw lastException;
}

private Retryer getRetryer(final Configuration configuration, final MethodInfo methodInfo) {
final Retryer retryer = configuration.requireContext(RetryerContext.class).getValue();
if (retryer instanceof TimeBasedRetryer) {
final Consumer<Integer> time = ((TimeBasedRetryer) retryer)::setTimeOutInSeconds;
methodInfo.getParameter(Integer.class, Timeout.class).ifPresent(time);
}
return retryer;
}
}
Original file line number Diff line number Diff line change
@@ -1,49 +1,27 @@
package io.qameta.atlas.core.internal;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.Set;


/**
* Retryer.
* @deprecated
* class constructor will be removed in the next release.
* Now the default implementation always used from the Atlas context
* see reference
*/
@SuppressWarnings("PMD.AvoidFieldNameMatchingMethodName")
public class DefaultRetryer implements Retryer {

private final List<Class<? extends Throwable>> ignoring;

private final Long start;

private Long timeout;

private Long polling;
@Deprecated
public class DefaultRetryer extends TimeBasedRetryer {

public DefaultRetryer(final Long timeout, final Long polling, final List<Class<? extends Throwable>> ignoring) {
this.ignoring = new ArrayList<>(ignoring);
this.start = System.currentTimeMillis();
this.timeout = timeout;
this.polling = polling;
}

public void ignore(final Class<? extends Throwable> throwable) {
this.ignoring.add(throwable);
}

public void timeoutInMillis(final Long millis) {
this.timeout = millis;
this(timeout, polling, new HashSet<>(ignoring));
}

@Override
public void timeoutInSeconds(final int seconds) {
this.timeout = TimeUnit.SECONDS.toMillis(seconds);
private DefaultRetryer(final Long timeout, final Long polling, final Set<Class<? extends Throwable>> ignoring) {
setTimeOut(timeout);
setPolling(polling);
addAllToIgnore(ignoring);
}

public void polling(final Long polling) {
this.polling = polling;
}

@Override
public boolean shouldRetry(final Throwable e) {
return shouldRetry(start, timeout, polling, ignoring, e);
}
}
Original file line number Diff line number Diff line change
@@ -1,35 +1,17 @@
package io.qameta.atlas.core.internal;

import java.util.Collections;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
* Retryer with default values.
*/
public class EmptyRetryer implements Retryer {

private final Long start;
private final Long polling;
private final List<Class<? extends Throwable>> ignoring;
private Long timeout;
public class EmptyRetryer extends TimeBasedRetryer {

public EmptyRetryer() {
this.start = System.currentTimeMillis();
this.timeout = 5000L;
this.polling = 1000L;
this.ignoring = Collections.singletonList(Throwable.class);
}

@Override
public boolean shouldRetry(final Throwable e) {
return shouldRetry(start, timeout, polling, ignoring, e);

}

@Override
public void timeoutInSeconds(final int seconds) {
this.timeout = TimeUnit.SECONDS.toMillis(seconds);
setTimeOutInSeconds(5);
setPolling(1000);
addAllToIgnore(Stream.of(Throwable.class).collect(Collectors.toSet()));
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,15 @@ public void addListeners(final Listener... listeners) {
}

@Override
public void beforeMethodCall(final MethodInfo methodInfo, final Configuration configuration) {
public Configuration beforeMethodCall(final MethodInfo methodInfo, final Configuration configuration) {
for (Listener listener : listeners) {
try {
listener.beforeMethodCall(methodInfo, configuration);
return listener.beforeMethodCall(methodInfo, configuration);
} catch (Exception e) {
LOGGER.error("Error during listener {} beforeMethodCall", listener, e);
}
}
return configuration;
}

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -1,28 +1,12 @@
package io.qameta.atlas.core.internal;

import java.util.List;
import io.qameta.atlas.core.util.MethodInfo;

/**
* Retryer.
*/
public interface Retryer {

boolean shouldRetry(Throwable e) throws Throwable;

default boolean shouldRetry(final Long start, final Long timeout, final Long polling,
final List<Class<? extends Throwable>> ignoring, final Throwable e) {
final long current = System.currentTimeMillis();
if (!(ignoring.stream().anyMatch(clazz -> clazz.isInstance(e)) && start + timeout < current)) {
try {
Thread.sleep(polling);
return true;
} catch (InterruptedException i) {
Thread.currentThread().interrupt();
}
}
return false;
}

void timeoutInSeconds(int seconds);
boolean shouldRetry(Throwable e, MethodInfo methodInfo) throws Throwable;

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package io.qameta.atlas.core.internal;

import io.qameta.atlas.core.util.MethodInfo;

import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.TimeUnit;

/**
* Default Retryer based on timeout.
*/
public abstract class TimeBasedRetryer implements Retryer {

private final Set<Class<? extends Throwable>> ignoring = new HashSet<>();
private final Long start = System.currentTimeMillis();
private Long timeout = 0L;
private Long polling = 0L;

@Override
public boolean shouldRetry(final Throwable e, final MethodInfo methodInfo) {
return shouldRetry(start, e);
}

public boolean shouldRetry(final Long start, final Throwable e) {
return shouldRetry(start, getTimeout(), getPolling(), getIgnoring(), e);
}

public boolean shouldRetry(final Long start, final Long timeout, final Long polling,
final Set<Class<? extends Throwable>> ignoring, final Throwable e) {
final long current = System.currentTimeMillis();
if (!(ignoring.stream().anyMatch(clazz -> clazz.isInstance(e)) && start + timeout < current)) {
try {
Thread.sleep(polling);
return true;
} catch (InterruptedException i) {
Thread.currentThread().interrupt();
}
}
return false;
}

public void setTimeOutInSeconds(final int seconds) {
setTimeOut(TimeUnit.SECONDS.toMillis(seconds));
}

public void setTimeOut(final long ms) {
this.timeout = ms;
}

public long getTimeout() {
return this.timeout;
}

public long getPolling() {
return polling;
}

public void setPolling(final long ms) {
this.polling = ms;
}

public Set<Class<? extends Throwable>> getIgnoring() {
return ignoring;
}

public void addAllToIgnore(final Set<Class<? extends Throwable>> toIgnoring) {
ignoring.addAll(toIgnoring);
}

}
Loading