package org.robolectric;

import android.os.Build;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.Description;
import org.junit.runner.notification.Failure;
import org.junit.runner.notification.RunListener;
import org.junit.runner.notification.RunNotifier;
import org.robolectric.RobolectricTestRunner.RobolectricFrameworkMethod;
import org.robolectric.annotation.Config;
import org.robolectric.internal.ParallelUniverse;
import org.robolectric.internal.ParallelUniverseInterface;
import org.robolectric.internal.SdkConfig;
import org.robolectric.internal.SdkEnvironment;
import org.robolectric.manifest.AndroidManifest;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;

import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.Assert.fail;
import static org.mockito.Mockito.mock;
import static org.robolectric.util.ReflectionHelpers.ClassParameter.from;
import static org.robolectric.util.ReflectionHelpers.callConstructor;
import static org.robolectric.util.ReflectionHelpers.newInstance;

public class RobolectricTestRunnerTest {

  private RunNotifier notifier;
  private List<String> events;

  @Before
  public void setUp() throws Exception {
    notifier = new RunNotifier();
    events = new ArrayList<>();
    notifier.addListener(new RunListener() {
      @Override
      public void testIgnored(Description description) throws Exception {
        events.add("ignored: " + description.getDisplayName());
      }

      @Override
      public void testFailure(Failure failure) throws Exception {
        events.add("failure: " + failure.getMessage());
      }
    });
  }

  @Test public void ignoredTestCanSpecifyUnsupportedSdkWithoutExploding() throws Exception {
    RobolectricTestRunner runner = new RobolectricTestRunner(TestWithOldSdk.class);
    runner.run(notifier);
    assertThat(events).containsOnly(
        "failure: Robolectric does not support API level 11.",
        "ignored: ignoredOldSdkMethod(org.robolectric.RobolectricTestRunnerTest$TestWithOldSdk)"
    );
  }

  @Test
  public void failureInResetterDoesntBreakAllTests() throws Exception {
    RobolectricTestRunner runner = new RobolectricTestRunner(TestWithTwoMethods.class) {
      @Override
      ParallelUniverseInterface getHooksInterface(SdkEnvironment sdkEnvironment) {
        Class<? extends ParallelUniverseInterface> clazz = sdkEnvironment.bootstrappedClass(MyParallelUniverse.class);
        return callConstructor(clazz, from(RobolectricTestRunner.class, this));
      }
    };
    runner.run(notifier);
    assertThat(events).containsExactly(
        "failure: java.lang.RuntimeException: fake error in resetStaticState",
        "failure: java.lang.RuntimeException: fake error in resetStaticState"
    );
  }

  @Test
  public void equalityOfRobolectricFrameworkMethod() throws Exception {
    Method method = TestWithTwoMethods.class.getMethod("first");
    RobolectricFrameworkMethod rfm16 = new RobolectricFrameworkMethod(method, mock(AndroidManifest.class), new SdkConfig(16), mock(Config.class));
    RobolectricFrameworkMethod rfm17 = new RobolectricFrameworkMethod(method, mock(AndroidManifest.class), new SdkConfig(17), mock(Config.class));
    RobolectricFrameworkMethod rfm16b = new RobolectricFrameworkMethod(method, mock(AndroidManifest.class), new SdkConfig(16), mock(Config.class));

    assertThat(rfm16).isEqualTo(rfm16);
    assertThat(rfm16).isNotEqualTo(rfm17);
    assertThat(rfm16).isEqualTo(rfm16b);

    assertThat(rfm16.hashCode()).isEqualTo((rfm16b.hashCode()));
  }

  /////////////////////////////

  public static class MyParallelUniverse extends ParallelUniverse {
    public MyParallelUniverse(RobolectricTestRunner robolectricTestRunner) {
      super(robolectricTestRunner);
    }

    @Override
    public void resetStaticState(Config config) {
      throw new RuntimeException("fake error in resetStaticState");
    }
  }

  @Ignore
  public static class TestWithOldSdk {
    @Config(sdk = Build.VERSION_CODES.HONEYCOMB)
    @Test
    public void oldSdkMethod() throws Exception {
      fail("I should not be run!");
    }

    @Ignore("This test shouldn't run, and shouldn't cause the test runner to fail")
    @Config(sdk = Build.VERSION_CODES.HONEYCOMB)
    @Test
    public void ignoredOldSdkMethod() throws Exception {
      fail("I should not be run!");
    }
  }

  @Ignore
  public static class TestWithTwoMethods {
    @Test
    public void first() throws Exception {
    }

    @Test
    public void second() throws Exception {
    }
  }
}
@@ -44,16 +44,58 @@
 .Build;%0A
+import org.jetbrains.annotations.NotNull;%0A
 import o
@@ -338,24 +338,76 @@
 unNotifier;%0A
+import org.junit.runners.model.InitializationError;%0A
 import org.r
@@ -839,24 +839,149 @@
 .util.List;%0A
+import java.util.Properties;%0A%0Aimport static android.os.Build.VERSION_CODES.JELLY_BEAN;%0Aimport static java.util.Arrays.asList;
 %0Aimport stat
@@ -1251,74 +1251,8 @@
 tor;
-%0Aimport static org.robolectric.util.ReflectionHelpers.newInstance;
 %0A%0Apu
@@ -1963,32 +1963,34 @@
 er runner = new 
+My
 RobolectricTestR
@@ -2368,24 +2368,26 @@
 unner = new 
+My
 RobolectricT
@@ -4743,10 +4743,367 @@
    %7D%0A  %7D
+%0A%0A  private static class MyRobolectricTestRunner extends RobolectricTestRunner %7B%0A    public MyRobolectricTestRunner(Class%3C?%3E testClass) throws InitializationError %7B%0A      super(testClass);%0A    %7D%0A%0A    @NotNull%0A    @Override%0A    protected SdkPicker createSdkPicker() %7B%0A      return new SdkPicker(asList(new SdkConfig(JELLY_BEAN)), new Properties());%0A    %7D%0A  %7D
 %0A%7D

Fix RobolectricTestRunnerTest.