/*
 * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
 * agreements. See the NOTICE file distributed with this work for additional information regarding
 * copyright ownership. The ASF licenses this file to You under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance with the License. You may obtain a
 * copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the License
 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
 * or implied. See the License for the specific language governing permissions and limitations under
 * the License.
 */
package org.apache.geode.test.junit.runners;

import static java.util.Arrays.asList;
import static java.util.Objects.hash;
import static org.assertj.core.api.Assertions.assertThat;

import java.util.List;
import java.util.stream.Collectors;

import org.assertj.core.util.Streams;
import org.junit.runner.JUnitCore;
import org.junit.runner.Request;
import org.junit.runner.Result;
import org.junit.runner.notification.Failure;

/**
 * Used by JUnit rule unit tests to execute inner test cases.
 */
public class TestRunner {

  private TestRunner() {
    // do not instantiate
  }

  public static Result runTest(Class<?> test) {
    JUnitCore junitCore = new JUnitCore();
    return junitCore.run(Request.aClass(test).getRunner());
  }

  public static Result runTestWithValidation(Class<?> test) {
    JUnitCore junitCore = new JUnitCore();
    Result result = junitCore.run(Request.aClass(test).getRunner());

    List<Failure> failures = result.getFailures();
    if (!failures.isEmpty()) {
      Failure firstFailure = failures.get(0);
      throw new AssertionError(firstFailure.getException());
    }

    assertThat(result.wasSuccessful()).isTrue();

    return result;
  }

  public static Failure runTestWithExpectedFailure(Class<?> test) {
    JUnitCore junitCore = new JUnitCore();
    Result result = junitCore.run(Request.aClass(test).getRunner());

    List<Failure> failures = result.getFailures();
    assertThat(failures).hasSize(1);

    return failures.get(0);
  }

  public static List<Failure> runTestWithExpectedFailures(Class<?> test,
      Throwable... expectedThrowables) {
    return runTestWithExpectedFailures(test, asList(expectedThrowables));
  }

  public static List<Failure> runTestWithExpectedFailures(Class<?> test,
      List<Throwable> expectedThrowables) {
    List<FailureInfo> expectedFailures = Streams.stream(expectedThrowables)
        .map(f -> new FailureInfo(f.getClass(), f.getMessage()))
        .collect(Collectors.toList());

    JUnitCore junitCore = new JUnitCore();
    Result result = junitCore.run(Request.aClass(test).getRunner());

    List<Failure> failures = result.getFailures();
    assertThat(failures).as("Actual failures").hasSameSizeAs(expectedFailures);

    List<FailureInfo> actualFailures = Streams.stream(failures)
        .map(f -> new FailureInfo(f.getException().getClass(), f.getMessage()))
        .collect(Collectors.toList());

    assertThat(actualFailures).as("Actual failures info (Throwable and message)").hasSameElementsAs(
        expectedFailures);

    return failures;
  }

  private static class FailureInfo {

    final Class<? extends Throwable> thrownClass;
    final String expectedMessage;

    FailureInfo(Class<? extends Throwable> thrownClass, String expectedMessage) {
      this.thrownClass = thrownClass;
      this.expectedMessage = expectedMessage;
    }

    @Override
    public int hashCode() {
      return hash(thrownClass, expectedMessage);
    }

    @Override
    public boolean equals(Object obj) {
      if (this == obj) {
        return true;
      }
      if (obj == null || getClass() != obj.getClass()) {
        return false;
      }
      FailureInfo that = (FailureInfo) obj;
      return thrownClass.equals(that.thrownClass) &&
          (expectedMessage.contains(that.expectedMessage) ||
              that.expectedMessage.contains(expectedMessage));
    }
  }
}

@@ -965,16 +965,44 @@
 tThat;%0A%0A
+import java.util.ArrayList;%0A
 import j
@@ -1012,24 +1012,24 @@
 .util.List;%0A
-
 import java.
@@ -1237,16 +1237,74 @@
 ailure;%0A
+import org.junit.runners.model.MultipleFailureException;%0A%0A
 %0A/**%0A * 
@@ -1872,108 +1872,285 @@
     
-Failure firstFailure = 
+List%3CThrowable%3E errors = new ArrayList%3C%3E();%0A      for (Failure failure : failures) %7B%0A        errors.add(
 failure
-s
 .get
-(0);%0A      throw new AssertionError(firstFailure.getException());
+Exception());%0A      %7D%0A      try %7B%0A        MultipleFailureException.assertEmpty(errors);%0A      %7D catch (Exception e) %7B%0A        throw new AssertionError(e);%0A      %7D
 %0A   

Use MultipleFailureException in runTestWithValidation