All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.split.qos.server.QOSServerBehaviour Maven / Gradle / Ivy

There is a newer version: 22.4.3
Show newest version
package io.split.qos.server;

import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;
import com.google.common.base.Strings;
import com.google.common.collect.Lists;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.ListeningScheduledExecutorService;
import com.google.common.util.concurrent.MoreExecutors;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import com.google.inject.name.Named;
import com.ullink.slack.simpleslackapi.SlackAttachment;
import io.split.qos.server.integrations.IntegrationServerFactory;
import io.split.qos.server.integrations.slack.broadcaster.SlackBroadcaster;
import io.split.qos.server.integrations.slack.commandintegration.SlackCommandIntegration;
import io.split.qos.server.modules.QOSPropertiesModule;
import io.split.qos.server.modules.QOSServerModule;
import io.split.qos.server.pausable.PausableScheduledThreadPoolExecutor;
import io.split.testrunner.junit.JUnitRunner;
import io.split.testrunner.junit.JUnitRunnerFactory;
import io.split.testrunner.junit.TestResult;
import io.split.qos.server.util.TestId;
import io.split.testrunner.util.TestsFinder;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.Callable;
import java.util.concurrent.CancellationException;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * Main Class that actually run the tests.
 */
@Singleton
public class QOSServerBehaviour implements Callable, AutoCloseable {
    private static final Logger LOG = LoggerFactory.getLogger(QOSServerBehaviour.class);
    private static final String SEPARATOR = "-------------------------------------------------------------";

    private final int parallelTests;
    private final PausableScheduledThreadPoolExecutor pausableExecutor;
    private final ListeningScheduledExecutorService executor;
    private final QOSServerState state;
    private final int shutdownWaitInMinutes;
    private final int delayBetweenInSeconds;
    private final boolean spreadTests;
    private final List suites;
    private final String suitesPackage;
    private final JUnitRunnerFactory testRunnerFactory;
    private final SlackCommandIntegration commandIntegration;
    private final SlackBroadcaster broadcastIntegration;
    private final String serverName;
    private final QOSTestsTracker tracker;
    private final Integer delayBetweenInSecondsWhenFail;
    private final boolean oneRun;
    private final TestsFinder testFinder;

    @Inject
    public QOSServerBehaviour(
            @Named(QOSPropertiesModule.DELAY_BETWEEN_IN_SECONDS) String delayBetweenInSeconds,
            @Named(QOSPropertiesModule.DELAY_BETWEEN_IN_SECONDS_WHEN_FAIL) String delayBetweenInSecondsWhenFail,
            @Named(QOSPropertiesModule.SPREAD_TESTS) String spreadTests,
            @Named(QOSPropertiesModule.ONE_RUN) String oneRun,
            @Named(QOSPropertiesModule.PARALLEL_TESTS) String parallelTests,
            @Named(QOSPropertiesModule.SHUTDOWN_WAIT_IN_MINUTES) String shutdownWaitInMinutes,
            @Named(QOSPropertiesModule.SUITES) String suites,
            @Named(QOSPropertiesModule.SUITES_PACKAGE) String suitesPackage,
            @Named(QOSServerModule.QOS_SERVER_NAME) String serverName,
            JUnitRunnerFactory testRunnerFactory,
            IntegrationServerFactory integrationServerFactory,
            TestsFinder testsFinder,
            QOSServerState state,
            QOSTestsTracker tracker) {

        this.delayBetweenInSeconds = Integer.valueOf(Preconditions.checkNotNull(delayBetweenInSeconds));
        this.delayBetweenInSecondsWhenFail = Integer.valueOf(Preconditions.checkNotNull(delayBetweenInSecondsWhenFail));
        this.spreadTests = Boolean.valueOf(Preconditions.checkNotNull(spreadTests));
        this.oneRun = Boolean.valueOf(Preconditions.checkNotNull(oneRun));
        this.shutdownWaitInMinutes = Integer.valueOf(Preconditions.checkNotNull(shutdownWaitInMinutes));
        this.parallelTests = Integer.valueOf(Preconditions.checkNotNull(parallelTests));
        this.pausableExecutor = new PausableScheduledThreadPoolExecutor(this.parallelTests);
        this.executor = MoreExecutors.listeningDecorator(pausableExecutor);
        this.state = Preconditions.checkNotNull(state);
        this.suites = Arrays.asList(Preconditions.checkNotNull(suites).split(","));
        this.suitesPackage = Preconditions.checkNotNull(suitesPackage);
        this.testRunnerFactory = Preconditions.checkNotNull(testRunnerFactory);
        this.commandIntegration = Preconditions.checkNotNull(integrationServerFactory).slackCommandIntegration();
        this.broadcastIntegration = Preconditions.checkNotNull(integrationServerFactory).slackBroadcastIntegration();
        this.serverName = Preconditions.checkNotNull(serverName);
        this.pause("Initialization");
        this.tracker = Preconditions.checkNotNull(tracker);
        this.testFinder = Preconditions.checkNotNull(testsFinder);
    }

    /**
     * Steps are:
     * 
    *
  • Do all the initialization
  • *
  • Find all the classes that are annotated with the suite, then find all the tests of those classes
  • *
  • Add each test to an executor. The tests will be spread if spreadTests is enabled
  • *
  • When each tests finished, it will be readded to the executor, with some delay specificed in delayBetweenInSeconds
  • *
*/ @Override public Void call() throws Exception { LOG.info(String.format("STARTING QOS Server for suites %s, running %s tests in parallel", suites, parallelTests)); if (commandIntegration.isEnabled()) { commandIntegration.initialize(); commandIntegration.startBotListener(); } if (broadcastIntegration.isEnabled()) { broadcastIntegration.initialize(); } List sheduled = scheduleTests(); if (sheduled.size() == 0) { LOG.error("Could not find tests to run on " + suites + " package " + suitesPackage); if (broadcastIntegration.isEnabled()) { String message = String.format("No tests found for %s, suites %s, package %s", serverName, suites, suitesPackage); SlackAttachment slackAttachment = new SlackAttachment("NO TESTS WILL RUN FOR " + serverName, "", message, null); slackAttachment .setColor("warning"); broadcastIntegration.broadcastVerbose("", slackAttachment); } return null; } if (broadcastIntegration.isEnabled()) { String message = String.format("QOS Server '%s' is up", serverName); SlackAttachment slackAttachment = new SlackAttachment( String.format("[%s] UP", serverName.toUpperCase()), "", message, null); slackAttachment .setColor("good"); broadcastIntegration.broadcastVerbose("", slackAttachment); } resume("Initialization"); return null; } @VisibleForTesting public List scheduleTests() throws Exception { List methodsToTest = testFinder.getTestMethodsOfPackage(suites, suitesPackage); int total = methodsToTest.size(); int schedule = 0; if (total == 0) { return Lists.newArrayList(); } int step = (spreadTests) ? delayBetweenInSeconds / total : 1; LOG.info(String.format("Test Methods to run: %s, total tests to run %s, delay %s seconds, step between each test %s seconds", methodsToTest, total, delayBetweenInSeconds, step)); for (Method method : methodsToTest) { state.registerTest(method); JUnitRunner testRunner = testRunnerFactory.create(method, Optional.empty()); ListenableFuture future = executor.schedule( testRunner, schedule, TimeUnit.SECONDS); tracker.track(method, testRunner, future); Futures.addCallback(future, createCallback( method, delayBetweenInSeconds, delayBetweenInSecondsWhenFail, delayBetweenInSeconds)); schedule += step; } return methodsToTest; } public void pause(String who) { pausableExecutor.pause(); state.pause(who); } public void resume(String who) { pausableExecutor.resume(); state.resume(who); } private FutureCallback createCallback(Method method, int when, int ifFailed, int afterFirst) { return new FutureCallback() { /** * This is where tests are readded to the executor. */ @Override public void onSuccess(TestResult result) { // If test failed or oneRun was false... run the test if (!result.getResult().wasSuccessful() || !oneRun) { int triggerAgain = result.getResult().wasSuccessful()? when : ifFailed; LOG.info(String.format("%s finished, rerunning in %s seconds", method.getName(), triggerAgain)); JUnitRunner runner = testRunnerFactory.create(method, Optional.empty()); ListenableFuture future = executor.schedule( runner, triggerAgain, TimeUnit.SECONDS); tracker.track(method, runner, future); Futures.addCallback(future, createCallback(method, afterFirst, ifFailed, afterFirst)); } processOutput(result.getOut()); } /** * For debugging, if it was cancelled do not print anything. * * @param t the throwable that caused the error. */ @Override public void onFailure(Throwable t) { if (t instanceof CancellationException) { return; } System.out.println(SEPARATOR); System.out.println(SEPARATOR); System.out.println(SEPARATOR); System.out.println("UNEXPECTED FAILURE"); System.out.println(String.format("FAILED %s#%s", method.getDeclaringClass(), method.getName())); System.out.println("REASON: " + t.getMessage()); System.out.println("STACKTRACE:"); System.out.println(ExceptionUtils.getStackTrace(t)); System.out.println(SEPARATOR); System.out.println(SEPARATOR); System.out.println(SEPARATOR); } private void processOutput(ByteArrayOutputStream outputStream) { ByteArrayInputStream inputStream = new ByteArrayInputStream(outputStream.toByteArray()); int oneByte; synchronized (System.out) { while ((oneByte = inputStream.read()) != -1) { System.out.write(oneByte); } } } }; } @Override public void close() throws Exception { // Doesnt disconnect /** if (commandIntegration.isEnabled()) { commandIntegration.close(); } if (!commandIntegration.isEnabled() && broadcastIntegration.isEnabled()) { broadcastIntegration.close(); } */ if (pausableExecutor != null) { pausableExecutor.resume(); pausableExecutor.shutdownNow(); pausableExecutor.awaitTermination(shutdownWaitInMinutes, TimeUnit.MINUTES); } } public List runAllNow() { Map tests = tracker.tests(); return runTests(tests.values()); } public List runTestsNow(Optional fuzzyClass, String fuzzyClassOrName) { Preconditions.checkArgument(!Strings.isNullOrEmpty(fuzzyClassOrName)); Preconditions.checkNotNull(fuzzyClass); Map toRun = null; if (fuzzyClass.isPresent()) { toRun = tracker .tests(fuzzyClass.get(), fuzzyClassOrName); } else { toRun = tracker .tests(fuzzyClassOrName); } return runTests(toRun.values()); } private List runTests(Collection toRun) { Preconditions.checkNotNull(toRun); if (toRun.isEmpty()) { return Lists.newArrayList(); } List orderedByTime = toRun .stream() .sorted((firstTracked, secondTracked) -> { TestId firstId = TestId.fromMethod(firstTracked.method()); TestId secondId = TestId.fromMethod(secondTracked.method()); QOSServerState.TestStatus firstStatus = state.tests().get(firstId); QOSServerState.TestStatus secondStatus = state.tests().get(secondId); if (firstStatus == null || firstStatus.when() == null) { return -1; } if (secondStatus == null || secondStatus.when() == null) { return 1; } return firstStatus.when().compareTo(secondStatus.when()); }) .collect(Collectors.toList()); List running = Lists.newArrayList(); int step = (spreadTests) ? delayBetweenInSeconds / orderedByTime.size() : 1; int schedule = step; for(QOSTestsTracker.Tracked track : orderedByTime) { if (!track.runner().isRunning()) { track.future().cancel(true); LOG.info(String.format("%s canceled, rerunning now", TestId.fromMethod(track.method()))); JUnitRunner runner = testRunnerFactory.create(track.method(), Optional.empty()); ListenableFuture future = executor.schedule( runner, 0, TimeUnit.SECONDS); tracker.track(track.method(), runner, future); Futures.addCallback(future, createCallback(track.method(), schedule, delayBetweenInSecondsWhenFail, delayBetweenInSeconds)); schedule += step; } running.add(track.method()); } return running; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy