private Future<?> executeTestJob(DependencyGraphExecutorFactory<?> factory) {
    final InMemoryLKVMarketDataProvider marketDataProvider = new InMemoryLKVMarketDataProvider();
    final MarketDataProviderResolver marketDataProviderResolver =
        new SingleMarketDataProviderResolver(
            new SingletonMarketDataProviderFactory(marketDataProvider));
    final InMemoryFunctionRepository functionRepository = new InMemoryFunctionRepository();
    _functionCount.set(0);
    final MockFunction mockFunction =
        new MockFunction(new ComputationTarget("Foo")) {

          @Override
          public Set<ComputedValue> execute(
              FunctionExecutionContext executionContext,
              FunctionInputs inputs,
              ComputationTarget target,
              Set<ValueRequirement> desiredValues) {
            try {
              Thread.sleep(JOB_FINISH_TIME / (JOB_SIZE * 2));
            } catch (InterruptedException e) {
              throw new OpenGammaRuntimeException("Function interrupted", e);
            }
            _functionCount.incrementAndGet();
            return super.execute(executionContext, inputs, target, desiredValues);
          }
        };
    functionRepository.addFunction(mockFunction);
    final FunctionCompilationContext compilationContext = new FunctionCompilationContext();
    final CompiledFunctionService compilationService =
        new CompiledFunctionService(
            functionRepository, new CachingFunctionRepositoryCompiler(), compilationContext);
    compilationService.initialize();
    final FunctionResolver functionResolver = new DefaultFunctionResolver(compilationService);
    final MockSecuritySource securitySource = new MockSecuritySource();
    final MockPositionSource positionSource = new MockPositionSource();
    final ViewComputationCacheSource computationCacheSource =
        new InMemoryViewComputationCacheSource(FudgeContext.GLOBAL_DEFAULT);
    final FunctionInvocationStatisticsGatherer functionInvocationStatistics =
        new DiscardingInvocationStatisticsGatherer();
    final ViewProcessorQueryReceiver viewProcessorQueryReceiver = new ViewProcessorQueryReceiver();
    final ViewProcessorQuerySender viewProcessorQuerySender =
        new ViewProcessorQuerySender(InMemoryRequestConduit.create(viewProcessorQueryReceiver));
    final FunctionExecutionContext executionContext = new FunctionExecutionContext();
    final ComputationTargetResolver targetResolver =
        new DefaultComputationTargetResolver(securitySource, positionSource);
    final JobDispatcher jobDispatcher =
        new JobDispatcher(
            new LocalNodeJobInvoker(
                new LocalCalculationNode(
                    computationCacheSource,
                    compilationService,
                    executionContext,
                    targetResolver,
                    viewProcessorQuerySender,
                    Executors.newCachedThreadPool(),
                    functionInvocationStatistics)));
    final ViewPermissionProvider viewPermissionProvider = new DefaultViewPermissionProvider();
    final GraphExecutorStatisticsGathererProvider graphExecutorStatisticsProvider =
        new DiscardingGraphStatisticsGathererProvider();

    ViewDefinition viewDefinition = new ViewDefinition("TestView", UserPrincipal.getTestUser());
    viewDefinition.addViewCalculationConfiguration(
        new ViewCalculationConfiguration(viewDefinition, "default"));
    MockViewDefinitionRepository viewDefinitionRepository = new MockViewDefinitionRepository();
    viewDefinitionRepository.addDefinition(viewDefinition);

    final ViewProcessContext vpc =
        new ViewProcessContext(
            viewDefinitionRepository,
            viewPermissionProvider,
            marketDataProviderResolver,
            compilationService,
            functionResolver,
            positionSource,
            securitySource,
            new DefaultCachingComputationTargetResolver(
                new DefaultComputationTargetResolver(securitySource, positionSource),
                EHCacheUtils.createCacheManager()),
            computationCacheSource,
            jobDispatcher,
            viewProcessorQueryReceiver,
            factory,
            graphExecutorStatisticsProvider);
    final DependencyGraph graph = new DependencyGraph("Default");
    DependencyNode previous = null;
    for (int i = 0; i < JOB_SIZE; i++) {
      DependencyNode node = new DependencyNode(new ComputationTarget("Foo"));
      node.setFunction(mockFunction);
      if (previous != null) {
        node.addInputNode(previous);
      }
      graph.addDependencyNode(node);
      previous = node;
    }
    final Map<String, DependencyGraph> graphs = new HashMap<String, DependencyGraph>();
    graphs.put(graph.getCalculationConfigurationName(), graph);
    CompiledViewDefinitionWithGraphsImpl viewEvaluationModel =
        new CompiledViewDefinitionWithGraphsImpl(
            viewDefinition, graphs, new SimplePortfolio("Test Portfolio"), 0);
    ViewCycleExecutionOptions cycleOptions = new ViewCycleExecutionOptions();
    cycleOptions.setValuationTime(Instant.ofEpochMillis(1));
    cycleOptions.setMarketDataSpecification(new MarketDataSpecification());
    final SingleComputationCycle cycle =
        new SingleComputationCycle(
            UniqueId.of("Test", "Cycle1"),
            UniqueId.of("Test", "ViewProcess1"),
            vpc,
            viewEvaluationModel,
            cycleOptions,
            VersionCorrection.of(Instant.ofEpochMillis(1), Instant.ofEpochMillis(1)));
    return cycle.getDependencyGraphExecutor().execute(graph, cycle.getStatisticsGatherer());
  }
  @Test
  public void testCycleSimpleGraph() {
    ViewProcessorTestEnvironment env = new ViewProcessorTestEnvironment();
    env.init();
    CompiledViewDefinitionWithGraphsImpl compiledViewDefinition =
        env.compileViewDefinition(Instant.now(), VersionCorrection.LATEST);
    DependencyGraph graph =
        compiledViewDefinition.getDependencyGraph(
            ViewProcessorTestEnvironment.TEST_CALC_CONFIG_NAME);
    DependencyGraph cycledGraph = cycleObject(DependencyGraph.class, graph);

    assertEquals(
        graph.getCalculationConfigurationName(), cycledGraph.getCalculationConfigurationName());
    assertEquals(graph.getAllComputationTargets(), cycledGraph.getAllComputationTargets());
    assertEquals(graph.getOutputSpecifications(), cycledGraph.getOutputSpecifications());
    assertEquals(graph.getSize(), cycledGraph.getSize());
    assertEquals(
        graph.getTerminalOutputSpecifications(), cycledGraph.getTerminalOutputSpecifications());

    for (DependencyNode node : graph.getDependencyNodes()) {
      boolean isRoot = graph.getRootNodes().contains(node);
      for (ValueSpecification spec : node.getOutputValues()) {
        DependencyNode equivalentNode = cycledGraph.getNodeProducing(spec);
        assertEquals(isRoot, cycledGraph.getRootNodes().contains(equivalentNode));
        assertEquals(node.getInputValues(), equivalentNode.getInputValues());
        assertEquals(node.getOutputValues(), equivalentNode.getOutputValues());
        assertEquals(node.getTerminalOutputValues(), equivalentNode.getTerminalOutputValues());
      }
    }
  }
  /**
   * Determines whether to run, and runs if required, a single computation cycle using the following
   * rules:
   *
   * <ul>
   *   <li>A computation cycle can only be triggered if the relevant minimum computation period has
   *       passed since the start of the previous cycle.
   *   <li>A computation cycle will be forced if the relevant maximum computation period has passed
   *       since the start of the previous cycle.
   *   <li>A full computation is preferred over a delta computation if both are possible.
   *   <li>Performing a full computation also updates the times to the next delta computation; i.e.
   *       a full computation is considered to be as good as a delta.
   * </ul>
   */
  @Override
  protected void runOneCycle() {
    // Exception handling is important here to ensure that computation jobs do not just die quietly
    // while consumers are
    // potentially blocked, waiting for results.

    ViewCycleType cycleType;
    try {
      cycleType = waitForNextCycle();
    } catch (InterruptedException e) {
      return;
    }

    ViewCycleExecutionOptions executionOptions = null;
    try {
      if (!getExecutionOptions().getExecutionSequence().isEmpty()) {
        executionOptions =
            getExecutionOptions()
                .getExecutionSequence()
                .getNext(getExecutionOptions().getDefaultExecutionOptions());
        s_logger.debug("Next cycle execution options: {}", executionOptions);
      }
      if (executionOptions == null) {
        s_logger.info("No more view cycle execution options");
        processCompleted();
        return;
      }
    } catch (Exception e) {
      s_logger.error(
          "Error obtaining next view cycle execution options from sequence for view process "
              + getViewProcess(),
          e);
      return;
    }

    if (executionOptions.getMarketDataSpecification() == null) {
      s_logger.error("No market data specification for cycle");
      cycleExecutionFailed(
          executionOptions,
          new OpenGammaRuntimeException("No market data specification for cycle"));
      return;
    }

    MarketDataSnapshot marketDataSnapshot;
    try {
      if (getMarketDataProvider() == null
          || !getMarketDataProvider().isCompatible(executionOptions.getMarketDataSpecification())) {
        // A different market data provider is required. We support this because we can, but
        // changing provider is not the
        // most efficient operation.
        if (getMarketDataProvider() != null) {
          s_logger.info("Replacing market data provider between cycles");
        }
        replaceMarketDataProvider(executionOptions.getMarketDataSpecification());
      }

      // Obtain the snapshot in case it is needed, but don't explicitly initialise it until the data
      // is required
      marketDataSnapshot =
          getMarketDataProvider().snapshot(executionOptions.getMarketDataSpecification());
    } catch (Exception e) {
      s_logger.error("Error with market data provider", e);
      cycleExecutionFailed(
          executionOptions, new OpenGammaRuntimeException("Error with market data provider", e));
      return;
    }

    Instant compilationValuationTime;
    try {
      if (executionOptions.getValuationTime() != null) {
        compilationValuationTime = executionOptions.getValuationTime();
      } else {
        // Neither the cycle-specific options nor the defaults have overridden the valuation time so
        // use the time
        // associated with the market data snapshot. To avoid initialising the snapshot perhaps
        // before the required
        // inputs are known or even subscribed to, only ask for an indication at the moment.
        compilationValuationTime = marketDataSnapshot.getSnapshotTimeIndication();
        if (compilationValuationTime == null) {
          throw new OpenGammaRuntimeException(
              "Market data snapshot "
                  + marketDataSnapshot
                  + " produced a null indication of snapshot time");
        }
      }
    } catch (Exception e) {
      s_logger.error("Error obtaining compilation valuation time", e);
      cycleExecutionFailed(
          executionOptions,
          new OpenGammaRuntimeException("Error obtaining compilation valuation time", e));
      return;
    }

    VersionCorrection versionCorrection = getResolvedVersionCorrection();
    final CompiledViewDefinitionWithGraphsImpl compiledViewDefinition;
    try {
      compiledViewDefinition =
          getCompiledViewDefinition(compilationValuationTime, versionCorrection);
      if (isTerminated()) {
        return; // [PLAT-1904]
      }
    } catch (Exception e) {
      String message =
          MessageFormat.format(
              "Error obtaining compiled view definition {0} for time {1} at version-correction {2}",
              getViewProcess().getDefinitionId(), compilationValuationTime, versionCorrection);
      s_logger.error(message);
      cycleExecutionFailed(executionOptions, new OpenGammaRuntimeException(message, e));
      return;
    }

    try {
      if (getExecutionOptions().getFlags().contains(ViewExecutionFlags.AWAIT_MARKET_DATA)) {
        marketDataSnapshot.init(
            compiledViewDefinition.getMarketDataRequirements().keySet(),
            MARKET_DATA_TIMEOUT_MILLIS,
            TimeUnit.MILLISECONDS);
      } else {
        marketDataSnapshot.init();
      }
      if (executionOptions.getValuationTime() == null) {
        executionOptions.setValuationTime(marketDataSnapshot.getSnapshotTime());
      }
    } catch (Exception e) {
      s_logger.error("Error initializing snapshot {}", marketDataSnapshot);
      cycleExecutionFailed(
          executionOptions,
          new OpenGammaRuntimeException("Error initializing snapshot" + marketDataSnapshot, e));
    }

    EngineResourceReference<SingleComputationCycle> cycleReference;
    try {
      cycleReference = createCycle(executionOptions, compiledViewDefinition, versionCorrection);
    } catch (Exception e) {
      s_logger.error("Error creating next view cycle for view process " + getViewProcess(), e);
      return;
    }

    if (_executeCycles) {
      try {
        final SingleComputationCycle singleComputationCycle = cycleReference.get();
        final Set<String> configurationNames =
            singleComputationCycle.getAllCalculationConfigurationNames();

        final HashMap<String, Collection<ComputationTarget>> configToComputationTargets =
            new HashMap<String, Collection<ComputationTarget>>();
        for (String configName : configurationNames) {
          DependencyGraph dependencyGraph =
              singleComputationCycle.getExecutableDependencyGraph(configName);
          configToComputationTargets.put(configName, dependencyGraph.getAllComputationTargets());
        }

        final HashMap<String, Map<ValueSpecification, Set<ValueRequirement>>>
            configToTerminalOutputs =
                new HashMap<String, Map<ValueSpecification, Set<ValueRequirement>>>();
        for (String configName : configurationNames) {
          DependencyGraph dependencyGraph =
              singleComputationCycle.getExecutableDependencyGraph(configName);
          configToTerminalOutputs.put(configName, dependencyGraph.getTerminalOutputs());
        }

        cycleInitiated(
            new SimpleCycleInfo(
                marketDataSnapshot.getUniqueId(),
                compiledViewDefinition.getViewDefinition().getUniqueId(),
                versionCorrection,
                executionOptions.getValuationTime(),
                configurationNames,
                configToComputationTargets,
                configToTerminalOutputs));
        executeViewCycle(
            cycleType,
            cycleReference,
            marketDataSnapshot,
            getViewProcess().getCalcJobResultExecutorService());
      } catch (InterruptedException e) {
        // Execution interrupted - don't propagate as failure
        s_logger.info("View cycle execution interrupted for view process {}", getViewProcess());
        cycleReference.release();
        return;
      } catch (Exception e) {
        // Execution failed
        s_logger.error("View cycle execution failed for view process " + getViewProcess(), e);
        cycleReference.release();
        cycleExecutionFailed(executionOptions, e);
        return;
      }
    }

    // Don't push the results through if we've been terminated, since another computation job could
    // be running already
    // and the fact that we've been terminated means the view is no longer interested in the result.
    // Just die quietly.
    if (isTerminated()) {
      cycleReference.release();
      return;
    }

    if (_executeCycles) {
      cycleCompleted(cycleReference.get());
    }

    if (getExecutionOptions().getExecutionSequence().isEmpty()) {
      processCompleted();
    }

    if (_executeCycles) {
      if (_previousCycleReference != null) {
        _previousCycleReference.release();
      }
      _previousCycleReference = cycleReference;
    }
  }