// Should always give the same result for loadClass(string)
  @Test
  public void testLoadsClassesRepeatedly() throws ClassNotFoundException {
    final List<String> classNames = new ArrayList<>();

    classNames.add("java.lang.Object");

    classNames.add("instrumentertest.Outer");
    classNames.add("instrumentertest.Outer$Inner");

    classNames.addAll(IndividualClassLoader.alwaysRedefine);

    final List<Class<?>> loadedClasses = new ArrayList<>();

    // Reloading consecutively should work
    for (String className : classNames) {
      final Class<?> loadedClass = l1.loadClass(className);
      final Class<?> reLoadedClass = l1.loadClass(className);

      assertEquals(loadedClass, reLoadedClass);

      loadedClasses.add(loadedClass);
    }

    // Reloading in a different order should work
    for (int i = 0; i < classNames.size(); i++) {
      assertEquals(l1.loadClass(classNames.get(i)), loadedClasses.get(i));
    }
  }
  @Before
  public void resetIndividualClassLoader() throws Exception {
    sharedCache = new IndividualClassLoader.Cache();
    l1 = new IndividualClassLoader("instrumentertest", sharedCache);
    l2 = new IndividualClassLoader("instrumentertest", sharedCache);

    // Set up noop RobotMonitors.

    SandboxedRobotPlayer.Pauser pauser = () -> {};
    SandboxedRobotPlayer.Killer killer = () -> {};

    final Class<?> monitor1 = l1.loadClass("battlecode.instrumenter.inject.RobotMonitor");
    monitor1
        .getMethod(
            "init", SandboxedRobotPlayer.Pauser.class, SandboxedRobotPlayer.Killer.class, int.class)
        .invoke(null, pauser, killer, 0);
    monitor1.getMethod("setBytecodeLimit", int.class).invoke(null, Integer.MAX_VALUE);

    final Class<?> monitor2 = l2.loadClass("battlecode.instrumenter.inject.RobotMonitor");
    monitor2
        .getMethod(
            "init", SandboxedRobotPlayer.Pauser.class, SandboxedRobotPlayer.Killer.class, int.class)
        .invoke(null, pauser, killer, 0);
    monitor2.getMethod("setBytecodeLimit", int.class).invoke(null, Integer.MAX_VALUE);
  }
  @Test
  public void testLoadFromJar() throws Exception {
    File jar = Files.createTempFile("battlecode-test", ".jar").toFile();

    jar.deleteOnExit();

    ZipOutputStream z = new ZipOutputStream(new FileOutputStream(jar));

    ZipEntry classEntry = new ZipEntry("instrumentertest/Nothing.class");

    z.putNextEntry(classEntry);

    IOUtils.copy(
        getClass().getClassLoader().getResourceAsStream("instrumentertest/Nothing.class"), z);

    z.closeEntry();
    z.close();

    IndividualClassLoader jarLoader =
        new IndividualClassLoader(
            "instrumentertest", new IndividualClassLoader.Cache(jar.toURI().toURL()));

    Class<?> jarClass = jarLoader.loadClass("instrumentertest.Nothing");

    URL jarClassLocation = jarClass.getResource("Nothing.class");

    // EXTREMELY scientific

    assertTrue(jarClassLocation.toString().startsWith("jar:"));
    assertTrue(jarClassLocation.toString().contains(jar.toURI().toURL().toString()));
  }
  // If a player class overrides hashCode, hashCode should work normally.
  // If a player class *doesn't* override hashCode, we should replace calls to it
  // with a deterministic hash code function.
  @Test
  public void testHashCodeInstrumentation() throws Exception {
    final Class<?> overridesClass = l1.loadClass("instrumentertest.OverridesHashCode");
    final Method getHashCodeOverrides = overridesClass.getMethod("getHashCode");

    final Object overrides = overridesClass.newInstance();

    assertEquals(57, getHashCodeOverrides.invoke(overrides));
    assertEquals(57, getHashCodeOverrides.invoke(overrides));

    final Class<?> notOverridesClass1 = l1.loadClass("instrumentertest.DoesntOverrideHashCode");
    final Method getHashCodeNotOverrides1 = notOverridesClass1.getMethod("getHashCode");
    final Object notOverrides1a = notOverridesClass1.newInstance();
    final Object notOverrides1b = notOverridesClass1.newInstance();

    assertEquals(
        getHashCodeNotOverrides1.invoke(notOverrides1a),
        getHashCodeNotOverrides1.invoke(notOverrides1a));
    assertEquals(
        getHashCodeNotOverrides1.invoke(notOverrides1b),
        getHashCodeNotOverrides1.invoke(notOverrides1b));

    final Class<?> notOverridesClass2 = l2.loadClass("instrumentertest.DoesntOverrideHashCode");
    final Method getHashCodeNotOverrides2 = notOverridesClass2.getMethod("getHashCode");
    final Object notOverrides2a = notOverridesClass2.newInstance();
    final Object notOverrides2b = notOverridesClass2.newInstance();

    assertEquals(
        getHashCodeNotOverrides2.invoke(notOverrides2a),
        getHashCodeNotOverrides2.invoke(notOverrides2a));
    assertEquals(
        getHashCodeNotOverrides2.invoke(notOverrides2b),
        getHashCodeNotOverrides2.invoke(notOverrides2b));

    // hashCode should be deterministic across loaders (assuming it is called
    // in the same order.)
    assertEquals(
        getHashCodeNotOverrides1.invoke(notOverrides1a),
        getHashCodeNotOverrides2.invoke(notOverrides2a));
    assertEquals(
        getHashCodeNotOverrides1.invoke(notOverrides1b),
        getHashCodeNotOverrides2.invoke(notOverrides2b));
  }
  @Test
  public void testIllegalMethodsFail() throws Exception {
    final String[] classNames =
        new String[] {
          "instrumentertest.CallsIllegalMethods$CallsWait",
          "instrumentertest.CallsIllegalMethods$CallsClassForName",
          "instrumentertest.CallsIllegalMethods$CallsStringIntern",
          "instrumentertest.CallsIllegalMethods$CallsSystemNanoTime",
          "instrumentertest.CallsIllegalMethods$CreatesFilePrintStream",
        };

    for (String className : classNames) {
      try {
        l1.loadClass(className);
      } catch (InstrumentationException e) {
        // Reset teamsWithErrors.
        continue;
      }

      fail("Didn't outlaw illegal class: " + className);
    }
  }
 // Should reload always-reloadable classes between instances.
 @Test
 public void testReloadsAlwaysReloadClasses() throws ClassNotFoundException {
   for (String alwaysRedefine : IndividualClassLoader.alwaysRedefine) {
     assertNotEquals(l1.loadClass(alwaysRedefine), l2.loadClass(alwaysRedefine));
   }
 }
 // Should reload player classes between instances.
 @Test
 public void testReloadsPlayerClasses() throws ClassNotFoundException {
   assertNotEquals(l1.loadClass("instrumentertest.Outer"), l2.loadClass("instrumentertest.Outer"));
 }
 @Test
 public void testCanUseEnumMap() throws Exception {
   l1.loadClass("instrumentertest.UsesEnumMap");
 }
 @Test
 public void testMathRandom() throws Exception {
   l1.loadClass("instrumentertest.CallsMathRandom");
 }
 @Test
 public void testCanUseLambda() throws Exception {
   l1.loadClass("instrumentertest.LegalMethodReference");
 }
 @Test(expected = InstrumentationException.class)
 public void testCantReferenceIllegalMethod() throws Exception {
   l1.loadClass("instrumentertest.IllegalMethodReference");
 }
 @Test(expected = InstrumentationException.class)
 public void testCantReflect() throws Exception {
   l1.loadClass("instrumentertest.Reflection");
 }
  @Test
  public void testStringFormat() throws Exception {
    final Class<?> c = l1.loadClass("instrumentertest.StringFormat");

    c.getMethod("run").invoke(null);
  }
  @Test
  public void testZombieSpawnSchedule() throws Exception {
    final Class<?> c = l1.loadClass("instrumentertest.UsesSpawnSchedule");

    c.getMethod("run").invoke(null);
  }
  @Test
  public void testLambdas() throws Exception {
    final Class<?> c = l1.loadClass("instrumentertest.UsesLambda");

    c.getMethod("run").invoke(null);
  }
 // Should give already-loaded system classes for most things.
 @Test
 public void testNoUnnecessaryReloads() throws ClassNotFoundException {
   for (Class<?> theClass : NEVER_RELOAD) {
     assertEquals(theClass, l1.loadClass(theClass.getName()));
   }
 }