@Test
  public void shouldRunReadOnlyLdbcWorkloadWithNothingDbAndCrashInSaneManner()
      throws InterruptedException, DbException, WorkloadException, IOException,
          MetricsCollectionException, CompletionTimeException, DriverConfigurationException,
          ExecutionException {
    int threadCount = 4;
    long operationCount = 100000;

    ControlService controlService = null;
    Db db = null;
    Workload workload = null;
    MetricsService metricsService = null;
    CompletionTimeService completionTimeService = null;
    ConcurrentErrorReporter errorReporter = new ConcurrentErrorReporter();
    try {
      Map<String, String> paramsMap =
          LdbcSnbInteractiveWorkloadConfiguration.defaultReadOnlyConfigSF1();
      paramsMap.put(
          LdbcSnbInteractiveWorkloadConfiguration.PARAMETERS_DIRECTORY,
          TestUtils.getResource("/snb/interactive/").getAbsolutePath());
      paramsMap.put(
          LdbcSnbInteractiveWorkloadConfiguration.UPDATES_DIRECTORY,
          TestUtils.getResource("/snb/interactive/").getAbsolutePath());
      // Driver-specific parameters
      String name = null;
      String dbClassName = DummyLdbcSnbInteractiveDb.class.getName();
      String workloadClassName = LdbcSnbInteractiveWorkload.class.getName();
      int statusDisplayInterval = 1;
      TimeUnit timeUnit = TimeUnit.NANOSECONDS;
      String resultDirPath = temporaryFolder.newFolder().getAbsolutePath();
      double timeCompressionRatio = 0.0000001;
      Set<String> peerIds = new HashSet<>();
      ConsoleAndFileDriverConfiguration.ConsoleAndFileValidationParamOptions validationParams =
          null;
      String dbValidationFilePath = null;
      boolean calculateWorkloadStatistics = false;
      long spinnerSleepDuration = 0l;
      boolean printHelp = false;
      boolean ignoreScheduledStartTimes = false;
      long warmupCount = 100;

      ConsoleAndFileDriverConfiguration configuration =
          new ConsoleAndFileDriverConfiguration(
              paramsMap,
              name,
              dbClassName,
              workloadClassName,
              operationCount,
              threadCount,
              statusDisplayInterval,
              timeUnit,
              resultDirPath,
              timeCompressionRatio,
              peerIds,
              validationParams,
              dbValidationFilePath,
              calculateWorkloadStatistics,
              spinnerSleepDuration,
              printHelp,
              ignoreScheduledStartTimes,
              warmupCount);

      configuration =
          (ConsoleAndFileDriverConfiguration)
              configuration.applyArgs(
                  MapUtils.loadPropertiesToMap(
                      TestUtils.getResource("/snb/interactive/updateStream.properties")));

      controlService =
          new LocalControlService(
              timeSource.nowAsMilli(),
              configuration,
              new Log4jLoggingServiceFactory(false),
              timeSource);
      LoggingService loggingService =
          new Log4jLoggingServiceFactory(false).loggingServiceFor("Test");

      GeneratorFactory gf = new GeneratorFactory(new RandomDataGeneratorFactory(42L));
      boolean returnStreamsWithDbConnector = true;
      Tuple3<WorkloadStreams, Workload, Long> workloadStreamsAndWorkload =
          WorkloadStreams.createNewWorkloadWithOffsetAndLimitedWorkloadStreams(
              configuration,
              gf,
              returnStreamsWithDbConnector,
              configuration.warmupCount(),
              configuration.operationCount(),
              LOGGING_SERVICE_FACTORY);

      workload = workloadStreamsAndWorkload._2();

      WorkloadStreams workloadStreams =
          WorkloadStreams.timeOffsetAndCompressWorkloadStreams(
              workloadStreamsAndWorkload._1(),
              controlService.workloadStartTimeAsMilli(),
              configuration.timeCompressionRatio(),
              gf);

      File resultsLog = temporaryFolder.newFile();
      SimpleCsvFileWriter csvResultsLogWriter =
          new SimpleCsvFileWriter(resultsLog, SimpleCsvFileWriter.DEFAULT_COLUMN_SEPARATOR);
      metricsService =
          ThreadedQueuedMetricsService.newInstanceUsingBlockingBoundedQueue(
              timeSource,
              errorReporter,
              configuration.timeUnit(),
              ThreadedQueuedMetricsService.DEFAULT_HIGHEST_EXPECTED_RUNTIME_DURATION_AS_NANO,
              csvResultsLogWriter,
              workload.operationTypeToClassMapping(),
              LOGGING_SERVICE_FACTORY);

      completionTimeService =
          completionTimeServiceAssistant.newSynchronizedConcurrentCompletionTimeServiceFromPeerIds(
              controlService.configuration().peerIds());

      db = new DummyLdbcSnbInteractiveDb();
      db.init(
          configuration
              .applyArg(DummyLdbcSnbInteractiveDb.CRASH_ON_ARG, LdbcQuery4.class.getName())
              .asMap(),
          loggingService,
          workload.operationTypeToClassMapping());

      int boundedQueueSize = DefaultQueues.DEFAULT_BOUND_1000;
      WorkloadRunner runner =
          new WorkloadRunner(
              timeSource,
              db,
              workloadStreams,
              metricsService,
              errorReporter,
              completionTimeService,
              controlService.loggingServiceFactory(),
              controlService.configuration().threadCount(),
              controlService.configuration().statusDisplayIntervalAsSeconds(),
              controlService.configuration().spinnerSleepDurationAsMilli(),
              controlService.configuration().ignoreScheduledStartTimes(),
              boundedQueueSize);

      runner.getFuture().get();
      csvResultsLogWriter.close();
    } finally {
      try {
        controlService.shutdown();
      } catch (Throwable e) {
        System.out.println(
            format(
                "Unclean %s shutdown -- but it's OK", controlService.getClass().getSimpleName()));
      }
      try {
        db.close();
      } catch (Throwable e) {
        System.out.println(
            format("Unclean %s shutdown -- but it's OK", db.getClass().getSimpleName()));
      }
      try {
        workload.close();
      } catch (Throwable e) {
        System.out.println(
            format("Unclean %s shutdown -- but it's OK", workload.getClass().getSimpleName()));
      }
      try {
        metricsService.shutdown();
      } catch (Throwable e) {
        System.out.println(
            format(
                "Unclean %s shutdown -- but it's OK", metricsService.getClass().getSimpleName()));
      }
      try {
        completionTimeService.shutdown();
      } catch (Throwable e) {
        System.out.println(
            format(
                "Unclean %s shutdown -- but it's OK",
                completionTimeService.getClass().getSimpleName()));
      }
      System.out.println(errorReporter.toString());
      assertTrue(errorReporter.errorEncountered());
    }
  }
  public void
      doShouldRunReadWriteLdbcWorkloadWithNothingDbAndReturnExpectedMetricsIncludingResultsLog(
          int threadCount, long operationCount)
          throws InterruptedException, DbException, WorkloadException, IOException,
              MetricsCollectionException, CompletionTimeException, DriverConfigurationException,
              ExecutionException {
    ControlService controlService = null;
    Db db = null;
    Workload workload = null;
    MetricsService metricsService = null;
    CompletionTimeService completionTimeService = null;
    ConcurrentErrorReporter errorReporter = new ConcurrentErrorReporter();
    try {
      Map<String, String> paramsMap = LdbcSnbInteractiveWorkloadConfiguration.defaultConfigSF1();
      paramsMap.put(
          LdbcSnbInteractiveWorkloadConfiguration.PARAMETERS_DIRECTORY,
          TestUtils.getResource("/snb/interactive/").getAbsolutePath());
      paramsMap.put(
          LdbcSnbInteractiveWorkloadConfiguration.UPDATES_DIRECTORY,
          TestUtils.getResource("/snb/interactive/").getAbsolutePath());
      // Driver-specific parameters
      String name = null;
      String dbClassName = DummyLdbcSnbInteractiveDb.class.getName();
      String workloadClassName = LdbcSnbInteractiveWorkload.class.getName();
      int statusDisplayInterval = 1;
      TimeUnit timeUnit = TimeUnit.NANOSECONDS;
      String resultDirPath = temporaryFolder.newFolder().getAbsolutePath();
      double timeCompressionRatio = 0.000001;
      Set<String> peerIds = new HashSet<>();
      ConsoleAndFileDriverConfiguration.ConsoleAndFileValidationParamOptions validationParams =
          null;
      String dbValidationFilePath = null;
      boolean calculateWorkloadStatistics = false;
      long spinnerSleepDuration = 0l;
      boolean printHelp = false;
      boolean ignoreScheduledStartTimes = false;
      long warmupCount = 100;

      ConsoleAndFileDriverConfiguration configuration =
          new ConsoleAndFileDriverConfiguration(
              paramsMap,
              name,
              dbClassName,
              workloadClassName,
              operationCount,
              threadCount,
              statusDisplayInterval,
              timeUnit,
              resultDirPath,
              timeCompressionRatio,
              peerIds,
              validationParams,
              dbValidationFilePath,
              calculateWorkloadStatistics,
              spinnerSleepDuration,
              printHelp,
              ignoreScheduledStartTimes,
              warmupCount);

      configuration =
          (ConsoleAndFileDriverConfiguration)
              configuration.applyArgs(
                  MapUtils.loadPropertiesToMap(
                      TestUtils.getResource("/snb/interactive/updateStream.properties")));

      controlService =
          new LocalControlService(
              timeSource.nowAsMilli(),
              configuration,
              new Log4jLoggingServiceFactory(false),
              timeSource);
      LoggingService loggingService =
          new Log4jLoggingServiceFactory(false).loggingServiceFor("Test");

      GeneratorFactory gf = new GeneratorFactory(new RandomDataGeneratorFactory(42L));
      boolean returnStreamsWithDbConnector = true;
      Tuple3<WorkloadStreams, Workload, Long> workloadStreamsAndWorkload =
          WorkloadStreams.createNewWorkloadWithOffsetAndLimitedWorkloadStreams(
              configuration,
              gf,
              returnStreamsWithDbConnector,
              configuration.warmupCount(),
              configuration.operationCount(),
              LOGGING_SERVICE_FACTORY);

      workload = workloadStreamsAndWorkload._2();

      WorkloadStreams workloadStreams =
          WorkloadStreams.timeOffsetAndCompressWorkloadStreams(
              workloadStreamsAndWorkload._1(),
              controlService.workloadStartTimeAsMilli(),
              configuration.timeCompressionRatio(),
              gf);

      File resultsLog = temporaryFolder.newFile();
      SimpleCsvFileWriter csvResultsLogWriter =
          new SimpleCsvFileWriter(resultsLog, SimpleCsvFileWriter.DEFAULT_COLUMN_SEPARATOR);
      metricsService =
          ThreadedQueuedMetricsService.newInstanceUsingBlockingBoundedQueue(
              timeSource,
              errorReporter,
              configuration.timeUnit(),
              ThreadedQueuedMetricsService.DEFAULT_HIGHEST_EXPECTED_RUNTIME_DURATION_AS_NANO,
              csvResultsLogWriter,
              workload.operationTypeToClassMapping(),
              LOGGING_SERVICE_FACTORY);

      db = new DummyLdbcSnbInteractiveDb();
      db.init(configuration.asMap(), loggingService, workload.operationTypeToClassMapping());

      completionTimeService =
          completionTimeServiceAssistant.newSynchronizedConcurrentCompletionTimeServiceFromPeerIds(
              controlService.configuration().peerIds());

      int boundedQueueSize = DefaultQueues.DEFAULT_BOUND_1000;
      WorkloadRunner runner =
          new WorkloadRunner(
              timeSource,
              db,
              workloadStreams,
              metricsService,
              errorReporter,
              completionTimeService,
              controlService.loggingServiceFactory(),
              controlService.configuration().threadCount(),
              controlService.configuration().statusDisplayIntervalAsSeconds(),
              controlService.configuration().spinnerSleepDurationAsMilli(),
              controlService.configuration().ignoreScheduledStartTimes(),
              boundedQueueSize);

      runner.getFuture().get();

      WorkloadResultsSnapshot workloadResults = metricsService.getWriter().results();

      SimpleDetailedWorkloadMetricsFormatter metricsFormatter =
          new SimpleDetailedWorkloadMetricsFormatter();

      assertThat(
          errorReporter.toString() + "\n" + metricsFormatter.format(workloadResults),
          errorReporter.errorEncountered(),
          is(false));
      assertThat(
          errorReporter.toString() + "\n" + metricsFormatter.format(workloadResults),
          workloadResults.startTimeAsMilli() >= controlService.workloadStartTimeAsMilli(),
          is(true));
      assertThat(
          errorReporter.toString() + "\n" + metricsFormatter.format(workloadResults),
          workloadResults.latestFinishTimeAsMilli() >= workloadResults.startTimeAsMilli(),
          is(true));
      // GREATER THAN or equal because number of Short Reads is operation result-dependent
      assertThat(
          errorReporter.toString() + "\n" + metricsFormatter.format(workloadResults),
          workloadResults.totalOperationCount(),
          allOf(
              greaterThanOrEqualTo(percent(operationCount, 0.9)),
              lessThanOrEqualTo(percent(operationCount, 1.1))));

      WorkloadResultsSnapshot workloadResultsFromJson =
          WorkloadResultsSnapshot.fromJson(workloadResults.toJson());

      assertThat(errorReporter.toString(), workloadResults, equalTo(workloadResultsFromJson));
      assertThat(
          errorReporter.toString(),
          workloadResults.toJson(),
          equalTo(workloadResultsFromJson.toJson()));

      csvResultsLogWriter.close();
      SimpleCsvFileReader csvResultsLogReader =
          new SimpleCsvFileReader(
              resultsLog, SimpleCsvFileReader.DEFAULT_COLUMN_SEPARATOR_REGEX_STRING);
      // NOT + 1 because I didn't add csv headers
      // GREATER THAN or equal because number of Short Reads is operation result-dependent
      assertThat(
          (long) Iterators.size(csvResultsLogReader),
          allOf(
              greaterThanOrEqualTo(percent(configuration.operationCount(), 0.9)),
              lessThanOrEqualTo(percent(configuration.operationCount(), 1.1))));
      csvResultsLogReader.close();

      operationCount = metricsService.getWriter().results().totalOperationCount();
      double operationsPerSecond =
          Math.round(
              ((double) operationCount / workloadResults.totalRunDurationAsNano())
                  * ONE_SECOND_AS_NANO);
      double microSecondPerOperation =
          (double) TimeUnit.NANOSECONDS.toMicros(workloadResults.totalRunDurationAsNano())
              / operationCount;
      System.out.println(
          format(
              "[%s threads] Completed %s operations in %s = %s op/sec = 1 op/%s us",
              threadCount,
              numberFormatter.format(operationCount),
              TEMPORAL_UTIL.nanoDurationToString(workloadResults.totalRunDurationAsNano()),
              doubleNumberFormatter.format(operationsPerSecond),
              doubleNumberFormatter.format(microSecondPerOperation)));
    } finally {
      System.out.println(errorReporter.toString());
      if (null != controlService) {
        controlService.shutdown();
      }
      if (null != db) {
        db.close();
      }
      if (null != workload) {
        workload.close();
      }
      if (null != metricsService) {
        metricsService.shutdown();
      }
      if (null != completionTimeService) {
        completionTimeService.shutdown();
      }
    }
  }