@Override
    public void run() {
      try {
        log.debug("run!");

        this.root.setState(SubstepTestState.RUNNING);
        eventsConsumer.onEvent(new StateChangedEvent(this.root));

        RootNode resultNode = getRootNodeFromBytes(jmxClient.runAsBytes());

        // lets wait for any notifications
        try {
          countDownLatch.await();
        } catch (InterruptedException e) {
          if (countDownLatch.getCount() > 0) {
            log.error("premature interupted ex waiting for complete notification");
          }
        }

        if (resultNode.getResult().getResult()
            == com.technophobia.substeps.execution.ExecutionResult.PASSED) {
          this.root.setState(SubstepTestState.PASSED);
        } else if (resultNode.getResult().getResult()
            == com.technophobia.substeps.execution.ExecutionResult.FAILED) {
          this.root.setState(SubstepTestState.FAILED);
        }
        eventsConsumer.onEvent(new StateChangedEvent(this.root));

        log.debug("done run, shutting down!");
      } finally {
        jmxClient.shutdown();
        log.debug("shut down done!");
      }
    }
    @Override
    public void handleNotification(ExecutionNodeResult result) {
      // convert to a SubstepsTestProxy and a TestEvent class

      Long executionNodeId = Long.valueOf(result.getExecutionNodeId());

      SubstepsTestProxy testProxy = proxyMap.get(executionNodeId);

      log.debug("got notification: " + executionNodeId + " result: " + result.getResult());

      switch (result.getResult()) {
        case FAILED:
          {
            testProxy.setState(SubstepTestState.FAILED);
            break;
          }
        case IGNORED:
          {
            testProxy.setState(SubstepTestState.SKIPPED);
            break;
          }
        case NOT_INCLUDED:
          {
            testProxy.setState(SubstepTestState.SKIPPED);
            break;
          }
        case NOT_RUN:
          {
            testProxy.setState(SubstepTestState.NOT_RUN);
            break;
          }
        case RUNNING:
          {
            testProxy.setState(SubstepTestState.RUNNING);
            break;
          }
        case PASSED:
          {
            testProxy.setState(SubstepTestState.PASSED);
            break;
          }
        case NON_CRITICAL_FAILURE:
          {
            testProxy.setState(SubstepTestState.FAILED);
            break;
          }
        case PARSE_FAILURE:
          {
            testProxy.setState(SubstepTestState.FAILED);
            break;
          }
        case SETUP_TEARDOWN_FAILURE:
          {
            testProxy.setState(SubstepTestState.FAILED);
            break;
          }
      }

      eventsConsumer.onEvent(new StateChangedEvent(testProxy));
    }
  @Override
  @NotNull
  public ExecutionResult execute(
      @NotNull final Executor executor, @NotNull final ProgramRunner runner)
      throws ExecutionException {

    log.debug("execute: about to call startProcess");

    OSProcessHandler processHandler = startProcess();

    ConsoleView consoleView =
        TextConsoleBuilderFactory.getInstance()
            .createBuilder(this.getEnvironment().getProject())
            .getConsole();

    consoleView.print("Starting substeps test...", ConsoleViewContentType.NORMAL_OUTPUT);

    SubstepsRunConfiguration runConfig =
        (SubstepsRunConfiguration) this.getEnvironment().getRunProfile();

    boolean substepsServerLogsToConsole = true;
    if (substepsServerLogsToConsole) {
      consoleView.attachToProcess(processHandler);
    } else {

      Semaphore consoleSemaphore = new Semaphore(1, true);
      InputStreamConsumer consumer =
          new InputStreamConsumer(
              processHandler.getProcess().getInputStream(),
              log,
              false,
              consoleView,
              consoleSemaphore);
      final Thread t = new Thread(consumer);
      t.start();

      InputStreamConsumer errorConsumer =
          new InputStreamConsumer(
              processHandler.getProcess().getErrorStream(),
              log,
              true,
              consoleView,
              consoleSemaphore);
      final Thread t2 = new Thread(errorConsumer);
      t2.start();
    }
    processHandler.startNotify();
    //
    //
    //
    //        boolean exceptionThrown = false;
    //        try {
    //            this.log.info("waiting for process to start...");
    //            processStarted.await(30, TimeUnit.SECONDS);
    //
    //            this.log.info("waited..");
    //
    //            if (!processStartedOk.get()) {
    //                exceptionThrown = true;
    //                throw new ExecutionException("Unable to launch VM process");
    //            }
    //
    //            this.log.info("process started");
    //        } catch (final InterruptedException e) {
    //
    //            e.printStackTrace();
    //        }

    //        try {
    //            Thread.currentThread().sleep(5000);
    //        } catch (InterruptedException e) {
    //            e.printStackTrace();
    //        }

    log.debug("startProcess called");

    SubstepsRunnerConfigurationModel model = runConfig.getModel();

    boolean actualRunnerStarted = false;

    // TODO - this is a bit messy, concerns not well separated
    SubstepsJMXClient jmxClient = null;
    try {
      jmxClient = new SubstepsJMXClient();

      if (!jmxClient.init(jmxPort)) {
        log.error("jmx init failed");
        processHandler.destroyProcess();
        return new DefaultExecutionResult();
      }

      SubstepsExecutionConfig substepsExecutionConfig = new SubstepsExecutionConfig();

      substepsExecutionConfig.setFeatureFile(model.getPathToFeature());

      String[] stepImplsArray =
          model
              .getStepImplentationClassNames(); // .toArray(new
                                                // String[model.getStepImplentationClassNames().size()]);

      substepsExecutionConfig.setDescription("Substeps Tests");

      substepsExecutionConfig.setStepImplementationClassNames(stepImplsArray);

      substepsExecutionConfig.setSubStepsFileName(model.getSubStepDefinitionDirectory());

      substepsExecutionConfig.setScenarioName(model.getScenarioName());

      log.debug(
          "SubstepsExecutionConfig details\nFeature: "
              + model.getPathToFeature()
              + "\nsubstep dir: "
              + model.getSubStepDefinitionDirectory()
              + " scenarioName: "
              + model.getScenarioName());

      for (String s : model.getStepImplentationClassNames()) {
        log.debug("step impl classname: " + s);
      }
      /*
      private String description;
      private String tags;
      private String nonFatalTags;
      private String subStepsFileName;
      private boolean strict = true;
      private boolean fastFailParseErrors = true;
      private Properties systemProperties;
      private String[] nonStrictKeywordPrecedence;
      private String[] stepImplementationClassNames;
      private String[] initialisationClass;
      private List<Class<?>> stepImplementationClasses;
      private Class<?>[] initialisationClasses;
      private String[] executionListeners;
           */

      log.debug("preparing config");

      byte[] bytes = jmxClient.prepareExecutionConfigAsBytes(substepsExecutionConfig);

      RootNode rn = getRootNodeFromBytes(bytes);

      log.debug("got root node description: " + rn.getDescription());

      final SubstepsTestProxy unboundOutputRoot = new SubstepsTestProxy(rn);

      final SubstepsConsoleProperties consoleProperties =
          new SubstepsConsoleProperties(runConfig, executor);
      final SubstepsConsoleView substepsConsoleView =
          new SubstepsConsoleView(
              consoleView, consoleProperties, this.getEnvironment(), unboundOutputRoot);

      DefaultExecutionResult execResult =
          new DefaultExecutionResult(
              substepsConsoleView,
              processHandler,
              createActions(substepsConsoleView, processHandler, executor));

      Disposer.register(this.getEnvironment().getProject(), substepsConsoleView);
      substepsConsoleView.initUI();
      substepsConsoleView.attachToProcess(processHandler);
      unboundOutputRoot.setPrinter(substepsConsoleView.getPrinter());
      Disposer.register(substepsConsoleView, unboundOutputRoot);

      SubstepsRunningModel runModel =
          new SubstepsRunningModel(unboundOutputRoot, consoleProperties);

      SubstepsListenersNotifier eventsConsumer = unboundOutputRoot.getEventsConsumer();

      substepsConsoleView.attachToModel(runModel);

      RunningTestTracker.install(runModel);

      log.debug("rootNode result from prepare config: " + rn.getResult().getResult());

      if (rn.getResult().getResult().isFailure()) {

        // bail out early
        unboundOutputRoot.setState(SubstepTestState.FAILED);
        eventsConsumer.onEvent(new StateChangedEvent(unboundOutputRoot));

        jmxClient.shutdown();
        log.debug("shut down done!");

      } else {
        log.debug("config prepared");

        List<SubstepsTestProxy> allTestNodes = unboundOutputRoot.getAllTests();
        Map<Long, SubstepsTestProxy> proxyMap = new HashMap<>();
        for (SubstepsTestProxy proxy : allTestNodes) {
          proxyMap.put(proxy.getExecutionNodeId(), proxy);
        }

        ActualRunner actualRunner =
            new ActualRunner(jmxClient, log, eventsConsumer, proxyMap, unboundOutputRoot);

        new Thread(actualRunner).start();
        actualRunnerStarted = true;
      }

      return execResult;
    } finally {
      if (!actualRunnerStarted && jmxClient != null) {

        // if we've got to the end and not actually kicked off the runner, make sure we shut down.

        jmxClient.shutdown();
      }
    }
  }