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

io.github.martinschneider.justtestlah.junit.JustTestLahRunner Maven / Gradle / Ivy

package io.github.martinschneider.justtestlah.junit;

import cucumber.api.StepDefinitionReporter;
import cucumber.api.event.TestRunFinished;
import cucumber.api.event.TestRunStarted;
import cucumber.api.junit.Cucumber;
import cucumber.runner.EventBus;
import cucumber.runner.ThreadLocalRunnerSupplier;
import cucumber.runner.TimeService;
import cucumber.runner.TimeServiceEventBus;
import cucumber.runtime.BackendModuleBackendSupplier;
import cucumber.runtime.BackendSupplier;
import cucumber.runtime.ClassFinder;
import cucumber.runtime.FeaturePathFeatureSupplier;
import cucumber.runtime.RuntimeOptions;
import cucumber.runtime.filter.Filters;
import cucumber.runtime.filter.RerunFilters;
import cucumber.runtime.formatter.PluginFactory;
import cucumber.runtime.formatter.Plugins;
import cucumber.runtime.io.MultiLoader;
import cucumber.runtime.io.ResourceLoader;
import cucumber.runtime.io.ResourceLoaderClassFinder;
import cucumber.runtime.junit.Assertions;
import cucumber.runtime.junit.FeatureRunner;
import cucumber.runtime.junit.JUnitOptions;
import cucumber.runtime.model.CucumberFeature;
import cucumber.runtime.model.FeatureLoader;
import io.github.martinschneider.justtestlah.configuration.Platform;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import nu.pattern.OpenCV;
import org.junit.runner.Description;
import org.junit.runner.notification.RunNotifier;
import org.junit.runners.ParentRunner;
import org.junit.runners.model.InitializationError;
import org.junit.runners.model.Statement;
import org.opencv.core.Core;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.bridge.SLF4JBridgeHandler;

/** Custom JUnit runner to dynamically set cucumber.̰options.
 * Based on {@link Cucumber}. */
public class JustTestLahRunner extends ParentRunner {

  private static final Logger LOG = LoggerFactory.getLogger(JustTestLahRunner.class);

  private final List children = new ArrayList();
  private final EventBus bus;
  private final ThreadLocalRunnerSupplier runnerSupplier;
  private final Filters filters;
  private final JUnitOptions junitOptions;
  private Properties props;

  private static final String STEPS_PACKAGE_KEY = "steps.package";
  private static final String PLATFORM_KEY = "platform";
  private static final String CUCUMBER_OPTIONS_KEY = "cucumber.options";
  private static final String FEATURES_DIRECTORY_KEY = "features.directory";
  private static final String DEFAULT_JUST_TEST_LAH_PROPERTIES = "justtestlah.properties";
  private static final String JUST_TEST_LAH_LOCATION_KEY = "justtestlah.properties";
  private static final String SPRING_PROFILES_ACTIVE = "spring.profiles.active";
  private static final String OPENCV_ENABLED_KEY = "opencv.enabled";
  private static final String CUCUMBER_REPORT_DIRECTORY_KEY = "cucumber.report.directory";
  private static final String DEFAULT_CUCUMBER_REPORT_DIRECTORY = "target/report/cucumber";
  private static final String DEFAULT_PLATFORM = "web";

  /**
   * Constructs a new {@link JustTestLahRunner}.
   *
   * @param clazz test class
   * @throws InitializationError {@link InitializationError}
   * @throws IOException {@link IOException}
   */
  public JustTestLahRunner(Class clazz) throws InitializationError, IOException {
    super(clazz);

    // Initialize Spring profiles and settings
    init();

    bridgeLogging();

    // load OpenCV library
    if (Boolean.parseBoolean(getProperty(OPENCV_ENABLED_KEY, "false"))) { // load the opencv library
      OpenCV.loadShared();
      OpenCV.loadLocally();
      System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
    }

    String cucumberOptions =
        "--tags @"
            + getProperty(PLATFORM_KEY, DEFAULT_PLATFORM)
            + " --glue io.github.martinschneider.justtestlah.steps --glue "
            + getProperty(STEPS_PACKAGE_KEY)
            + " --plugin pretty --plugin html:report --plugin json:"
            + getProperty(CUCUMBER_REPORT_DIRECTORY_KEY, DEFAULT_CUCUMBER_REPORT_DIRECTORY)
            + "/cucumber.json"
            + " "
            + getProperty(FEATURES_DIRECTORY_KEY);
    LOG.info("Setting cucumber options ({}) to {}", CUCUMBER_OPTIONS_KEY, cucumberOptions);
    System.setProperty(CUCUMBER_OPTIONS_KEY, cucumberOptions);
    Assertions.assertNoCucumberAnnotatedMethods(clazz);

    ClassLoader classLoader = clazz.getClassLoader();
    Assertions.assertNoCucumberAnnotatedMethods(clazz);

    RuntimeOptions runtimeOptions = new RuntimeOptions(cucumberOptions);
    ResourceLoader resourceLoader = new MultiLoader(classLoader);
    FeatureLoader featureLoader = new FeatureLoader(resourceLoader);
    FeaturePathFeatureSupplier featureSupplier =
        new FeaturePathFeatureSupplier(featureLoader, runtimeOptions);
    // Parse the features early. Don't proceed when there are lexer errors
    final List features = featureSupplier.get();

    ClassFinder classFinder = new ResourceLoaderClassFinder(resourceLoader, classLoader);
    BackendSupplier backendSupplier =
        new BackendModuleBackendSupplier(resourceLoader, classFinder, runtimeOptions);
    this.bus = new TimeServiceEventBus(TimeService.SYSTEM);

    this.runnerSupplier = new ThreadLocalRunnerSupplier(runtimeOptions, bus, backendSupplier);
    RerunFilters rerunFilters = new RerunFilters(runtimeOptions, featureLoader);
    this.filters = new Filters(runtimeOptions, rerunFilters);
    this.junitOptions =
        new JUnitOptions(runtimeOptions.isStrict(), runtimeOptions.getJunitOptions());
    Plugins plugins = new Plugins(classLoader, new PluginFactory(), bus, runtimeOptions);
    final StepDefinitionReporter stepDefinitionReporter = plugins.stepDefinitionReporter();

    // Start the run before reading the features.
    // Allows the test source read events to be broadcast properly
    bus.send(new TestRunStarted(bus.getTime()));
    for (CucumberFeature feature : features) {
      feature.sendTestSourceRead(bus);
    }
    runnerSupplier.get().reportStepDefinitions(stepDefinitionReporter);

    addChildren(features);
  }

  private void bridgeLogging() {
    SLF4JBridgeHandler.removeHandlersForRootLogger();
    SLF4JBridgeHandler.install();
  }

  @Override
  public List getChildren() {
    return children;
  }

  @Override
  protected Description describeChild(FeatureRunner child) {
    return child.getDescription();
  }

  @Override
  protected void runChild(FeatureRunner child, RunNotifier notifier) {
    child.run(notifier);
  }

  @Override
  protected Statement childrenInvoker(RunNotifier notifier) {
    final Statement features = super.childrenInvoker(notifier);
    return new Statement() {
      @Override
      public void evaluate() throws Throwable {
        features.evaluate();
        bus.send(new TestRunFinished(bus.getTime()));
      }
    };
  }

  private void addChildren(List cucumberFeatures) throws InitializationError {
    for (CucumberFeature cucumberFeature : cucumberFeatures) {
      FeatureRunner featureRunner =
          new FeatureRunner(cucumberFeature, filters, runnerSupplier, junitOptions);
      if (!featureRunner.isEmpty()) {
        children.add(featureRunner);
      }
    }
  }

  private String getProperty(String key, String defaultValue) {
    initProperties();
    String value = props.getProperty(key);
    if (value != null && !value.isEmpty()) {
      LOG.debug("Reading property {} = {}", key, value);
      return value;
    }
    LOG.warn("Property {} not set in justtestlah.properties. Using default value: {}", key, defaultValue);
    return defaultValue;
  }

  private String getProperty(String key) {
    initProperties();
    String value = props.getProperty(key);
    if (value != null && !value.isEmpty()) {
      LOG.info("Reading property {} = {}", key, value);
      return value;
    }
    throw new RuntimeException("Mandatory property " + key + " not set in justtestlah.properties.");
  }

  private void initProperties() {
    if (props == null) {
      props = new Properties();
      loadProperties();
    }
  }

  private void loadProperties() {

    String propertiesLocation = System.getProperty(JUST_TEST_LAH_LOCATION_KEY);
    try {
      if (propertiesLocation != null) {
        LOG.info("Loading JustTestLah properties from {}", propertiesLocation);
        props.load(new FileInputStream(propertiesLocation));
      } else {
        propertiesLocation = DEFAULT_JUST_TEST_LAH_PROPERTIES;
        LOG.info("Loading JustTestLah properties from classpath ({})", propertiesLocation);
        props.load(JustTestLahTest.class.getClassLoader().getResourceAsStream(propertiesLocation));
      }
    } catch (NullPointerException | IOException e) {
      LOG.warn("Error loading settings from {}", propertiesLocation);
    }
  }

  private void init() {
    // set the active Spring profile to the current platform
    String platform = getProperty(PLATFORM_KEY);
    if (platform == null || platform.isEmpty()) {
      LOG.info("No platform specified. Using default ({})", Platform.DEFAULT);
      platform = Platform.DEFAULT;
      System.setProperty(PLATFORM_KEY, platform);
    }
    String[] platforms = platform.split(",");
    if (platforms.length > 1) {
      throw new UnsupportedOperationException(
          "Please specify exactly one spring profile (ANDROID, IOS or WEB).");
    }
    platform = platforms[0].trim();
    String springProfiles = System.getProperty(SPRING_PROFILES_ACTIVE);
    if (springProfiles != null && !springProfiles.isEmpty()) {
      springProfiles += "," + platform;
    } else {
      springProfiles = platform;
    }
    LOG.info("Setting platform to {}", platform);
    System.setProperty(SPRING_PROFILES_ACTIVE, springProfiles);
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy