@Test
  public void testInitializationSecondFailed() throws Exception {
    DateTime expectedStartTime = new DateTime(0);
    DateTime falseStartTime = new DateTime(1);
    DateTime expectedEndTime = new DateTime(100);
    DateTime falseEndTime = new DateTime(99);

    EasyMock.expect(mockFlow1.getName()).andReturn("a").once();
    EasyMock.expect(mockFlow2.getName()).andReturn("b").once();

    EasyMock.expect(mockFlow1.getStatus()).andReturn(Status.READY).times(2);
    EasyMock.expect(mockFlow2.getStatus()).andReturn(Status.FAILED).times(2);

    EasyMock.expect(mockFlow1.getStartTime()).andReturn(expectedStartTime).once();
    EasyMock.expect(mockFlow1.getEndTime()).andReturn(falseEndTime).once();
    EasyMock.expect(mockFlow2.getStartTime()).andReturn(falseStartTime).once();
    EasyMock.expect(mockFlow2.getEndTime()).andReturn(expectedEndTime).once();

    EasyMock.expect(mockFlow2.getParentProps()).andReturn(props).once();

    EasyMock.expect(mockFlow1.getName()).andReturn("1").once();
    EasyMock.expect(mockFlow2.getName()).andReturn("2").once();

    EasyMock.replay(mockFlow1, mockFlow2, props);

    flow = new GroupedExecutableFlow("blah", mockFlow1, mockFlow2);

    Assert.assertEquals(Status.FAILED, flow.getStatus());
    Assert.assertEquals(expectedStartTime, flow.getStartTime());
    Assert.assertEquals(expectedEndTime, flow.getEndTime());
    Assert.assertEquals(emptyExceptions, flow.getExceptions());
    Assert.assertEquals(props, flow.getParentProps());
  }
  @Test
  public void testInitializationSecondSucceeded() throws Exception {
    DateTime expectedStartTime = new DateTime(0);

    EasyMock.expect(mockFlow1.getName()).andReturn("a").once();
    EasyMock.expect(mockFlow2.getName()).andReturn("b").once();

    EasyMock.expect(mockFlow1.getStatus()).andReturn(Status.READY).times(3);
    EasyMock.expect(mockFlow2.getStatus()).andReturn(Status.SUCCEEDED).times(3);

    EasyMock.expect(mockFlow1.getStartTime()).andReturn(null).once();
    EasyMock.expect(mockFlow2.getStartTime()).andReturn(expectedStartTime).once();

    EasyMock.expect(mockFlow2.getParentProps()).andReturn(props).once();

    EasyMock.expect(mockFlow1.getName()).andReturn("1").once();
    EasyMock.expect(mockFlow2.getName()).andReturn("2").once();

    EasyMock.replay(mockFlow1, mockFlow2, props);

    flow = new GroupedExecutableFlow("blah", mockFlow1, mockFlow2);

    Assert.assertEquals(Status.READY, flow.getStatus());
    Assert.assertEquals(expectedStartTime, flow.getStartTime());
    Assert.assertEquals(null, flow.getEndTime());
    // <<<<<<< HEAD:azkaban/src/unit/azkaban/flow/GroupedExecutableFlowTest.java
    Assert.assertEquals(emptyExceptions, flow.getExceptions());
    Assert.assertEquals(props, flow.getParentProps());
  }
  @Before
  public void setUp() throws Exception {
    props = EasyMock.createStrictMock(Props.class);
    mockFlow1 = EasyMock.createMock(ExecutableFlow.class);
    mockFlow2 = EasyMock.createMock(ExecutableFlow.class);

    EasyMock.expect(mockFlow1.getName()).andReturn("a").once();
    EasyMock.expect(mockFlow2.getName()).andReturn("b").once();

    EasyMock.expect(mockFlow1.getStatus()).andReturn(Status.READY).times(3);
    EasyMock.expect(mockFlow2.getStatus()).andReturn(Status.READY).times(3);

    EasyMock.expect(mockFlow1.getStartTime()).andReturn(null).once();
    EasyMock.expect(mockFlow2.getStartTime()).andReturn(null).once();

    EasyMock.expect(mockFlow1.getName()).andReturn("1").once();
    EasyMock.expect(mockFlow2.getName()).andReturn("2").once();
    EasyMock.replay(mockFlow1, mockFlow2, props);

    flow = new GroupedExecutableFlow("blah", mockFlow1, mockFlow2);
    Assert.assertEquals("1 + 2", flow.getName());

    EasyMock.verify(mockFlow1, mockFlow2, props);
    EasyMock.reset(mockFlow1, mockFlow2, props);
  }
  @Test
  public void testInitializationSecondRunning() throws Exception {
    DateTime expectedStartTime = new DateTime(0);

    EasyMock.expect(mockFlow1.getName()).andReturn("a").once();
    EasyMock.expect(mockFlow2.getName()).andReturn("b").once();

    EasyMock.expect(mockFlow1.getStatus()).andReturn(Status.READY).times(3);
    EasyMock.expect(mockFlow2.getStatus()).andReturn(Status.RUNNING).times(3);

    EasyMock.expect(mockFlow1.getStartTime()).andReturn(null).once();
    EasyMock.expect(mockFlow2.getStartTime()).andReturn(expectedStartTime).once();

    Capture<FlowCallback> callbackCapture = new Capture<FlowCallback>();
    mockFlow2.execute(EasyMock.eq(props), EasyMock.capture(callbackCapture));

    EasyMock.expect(mockFlow1.getName()).andReturn("1").once();
    EasyMock.expect(mockFlow2.getName()).andReturn("2").once();

    EasyMock.expect(mockFlow2.getParentProps()).andReturn(props).once();

    EasyMock.replay(mockFlow1, mockFlow2, props);

    flow = new GroupedExecutableFlow("blah", mockFlow1, mockFlow2);

    Assert.assertEquals(Status.READY, flow.getStatus());
    Assert.assertEquals(expectedStartTime, flow.getStartTime());
    Assert.assertEquals(null, flow.getEndTime());
    Assert.assertEquals(props, flow.getParentProps());

    EasyMock.verify(mockFlow1, mockFlow2, props);
    EasyMock.reset(mockFlow1, mockFlow2, props);

    EasyMock.expect(mockFlow1.getStatus()).andReturn(Status.READY).once();
    EasyMock.expect(mockFlow2.getStatus()).andReturn(Status.SUCCEEDED).once();

    EasyMock.replay(mockFlow1, mockFlow2, props);

    callbackCapture.getValue().completed(Status.SUCCEEDED);

    Assert.assertEquals(Status.READY, flow.getStatus());
    Assert.assertEquals(expectedStartTime, flow.getStartTime());
    Assert.assertEquals(null, flow.getEndTime());
    Assert.assertEquals(emptyExceptions, flow.getExceptions());
  }
  public GroupedExecutableFlow(String id, ExecutableFlow... flows) {
    this.id = id;
    this.flows = flows;
    this.sortedFlows = Arrays.copyOf(this.flows, this.flows.length);
    Arrays.sort(
        this.sortedFlows,
        new Comparator<ExecutableFlow>() {
          @Override
          public int compare(ExecutableFlow o1, ExecutableFlow o2) {
            return o1.getName().compareTo(o2.getName());
          }
        });

    String[] names = new String[flows.length];
    for (int i = 0; i < flows.length; i++) {
      names[i] = flows[i].getName();
    }
    name = StringUtils.join(names, " + ");

    jobState = Status.READY;
    updateState();
    callbacksToCall = new ArrayList<FlowCallback>();

    theGroupCallback = new GroupedFlowCallback();

    switch (jobState) {
      case SUCCEEDED:
      case COMPLETED:
      case FAILED:
        DateTime theStartTime = new DateTime();
        DateTime theEndTime = new DateTime(0);
        for (ExecutableFlow flow : flows) {
          final DateTime subFlowStartTime = flow.getStartTime();
          if (theStartTime.isAfter(subFlowStartTime)) {
            theStartTime = subFlowStartTime;
          }

          final DateTime subFlowEndTime = flow.getEndTime();
          if (subFlowEndTime != null && subFlowEndTime.isAfter(theEndTime)) {
            theEndTime = subFlowEndTime;
          }
        }

        setAndVerifyParentProps();
        startTime = theStartTime;
        endTime = theEndTime;
        break;
      default:
        // Check for Flows that are "RUNNING"
        boolean allRunning = true;
        List<ExecutableFlow> runningFlows = new ArrayList<ExecutableFlow>();
        DateTime thisStartTime = null;

        for (ExecutableFlow flow : flows) {
          if (flow.getStatus() != Status.RUNNING) {
            allRunning = false;

            final DateTime subFlowStartTime = flow.getStartTime();
            if (subFlowStartTime != null && subFlowStartTime.isBefore(thisStartTime)) {
              thisStartTime = subFlowStartTime;
            }
          } else {
            runningFlows.add(flow);
          }
        }

        if (allRunning) {
          jobState = Status.RUNNING;
        }

        for (ExecutableFlow runningFlow : runningFlows) {
          final DateTime subFlowStartTime = runningFlow.getStartTime();
          if (subFlowStartTime != null && subFlowStartTime.isBefore(thisStartTime)) {
            thisStartTime = subFlowStartTime;
          }
        }
        setAndVerifyParentProps();

        startTime = thisStartTime;
        endTime = null;

        // Make sure everything is initialized before leaking the pointer to "this".
        // This is just installing the callback in an already running flow.
        for (ExecutableFlow runningFlow : runningFlows) {
          runningFlow.execute(parentProps, theGroupCallback);
        }
    }
  }
  @Test
  public void testInitializationBothRunning() throws Exception {
    DateTime expectedStartTime = new DateTime(0);
    DateTime falseStartTime = new DateTime(1);

    EasyMock.expect(mockFlow1.getName()).andReturn("a").once();
    EasyMock.expect(mockFlow2.getName()).andReturn("b").once();

    EasyMock.expect(mockFlow1.getStatus()).andReturn(Status.RUNNING).times(3);
    EasyMock.expect(mockFlow2.getStatus()).andReturn(Status.RUNNING).times(3);

    EasyMock.expect(mockFlow1.getStartTime()).andReturn(falseStartTime).once();
    EasyMock.expect(mockFlow2.getStartTime()).andReturn(expectedStartTime).once();

    Capture<FlowCallback> callbackCapture1 = new Capture<FlowCallback>();
    Capture<FlowCallback> callbackCapture2 = new Capture<FlowCallback>();
    mockFlow1.execute(EasyMock.eq(props), EasyMock.capture(callbackCapture1));
    mockFlow2.execute(EasyMock.eq(props), EasyMock.capture(callbackCapture2));

    EasyMock.expect(mockFlow1.getParentProps()).andReturn(props).once();
    EasyMock.expect(mockFlow2.getParentProps()).andReturn(props).once();
    EasyMock.expect(props.equalsProps(props)).andReturn(true).once();

    EasyMock.expect(mockFlow1.getName()).andReturn("1").once();
    EasyMock.expect(mockFlow2.getName()).andReturn("2").once();

    EasyMock.replay(mockFlow1, mockFlow2, props);

    flow = new GroupedExecutableFlow("blah", mockFlow1, mockFlow2);

    Assert.assertEquals(Status.RUNNING, flow.getStatus());
    Assert.assertEquals(expectedStartTime, flow.getStartTime());
    Assert.assertEquals(null, flow.getEndTime());

    EasyMock.verify(mockFlow1, mockFlow2, props);
    EasyMock.reset(mockFlow1, mockFlow2, props);

    EasyMock.expect(mockFlow1.getStatus()).andReturn(Status.RUNNING).once();
    EasyMock.expect(mockFlow2.getStatus()).andReturn(Status.SUCCEEDED).once();

    EasyMock.replay(mockFlow1, mockFlow2, props);

    Assert.assertSame(callbackCapture1.getValue(), callbackCapture2.getValue());

    callbackCapture1.getValue().completed(Status.SUCCEEDED);

    Assert.assertEquals(Status.RUNNING, flow.getStatus());
    Assert.assertEquals(expectedStartTime, flow.getStartTime());
    Assert.assertEquals(null, flow.getEndTime());
    Assert.assertEquals(emptyExceptions, flow.getExceptions());
    Assert.assertEquals(props, flow.getParentProps());

    EasyMock.verify(mockFlow1, mockFlow2, props);
    EasyMock.reset(mockFlow1, mockFlow2, props);

    EasyMock.expect(mockFlow1.getStatus()).andReturn(Status.SUCCEEDED).once();
    EasyMock.expect(mockFlow2.getStatus()).andReturn(Status.SUCCEEDED).once();

    EasyMock.expect(mockFlow1.getReturnProps()).andReturn(new Props()).once();
    EasyMock.expect(mockFlow2.getReturnProps()).andReturn(new Props()).once();

    EasyMock.replay(mockFlow1, mockFlow2, props);

    DateTime beforeTheEnd = new DateTime();
    callbackCapture2.getValue().completed(Status.SUCCEEDED);

    Assert.assertEquals(Status.SUCCEEDED, flow.getStatus());
    Assert.assertEquals(expectedStartTime, flow.getStartTime());
    Assert.assertFalse(
        String.format(
            "flow's end time[%s] should be after beforeTheEnd[%s]",
            flow.getEndTime(), beforeTheEnd),
        beforeTheEnd.isAfter(flow.getEndTime()));
    Assert.assertEquals(emptyExceptions, flow.getExceptions());
  }