@Test
  public void shouldNotExhaustThreads() throws Exception {
    final ScheduledExecutorService executorService =
        Executors.newScheduledThreadPool(2, testingThreadFactory);
    final GremlinExecutor gremlinExecutor =
        GremlinExecutor.build()
            .executorService(executorService)
            .scheduledExecutorService(executorService)
            .create();

    final AtomicInteger count = new AtomicInteger(0);
    assertTrue(
        IntStream.range(0, 1000)
            .mapToObj(i -> gremlinExecutor.eval("1+1"))
            .allMatch(
                f -> {
                  try {
                    return (Integer) f.get() == 2;
                  } catch (Exception ex) {
                    throw new RuntimeException(ex);
                  } finally {
                    count.incrementAndGet();
                  }
                }));

    assertEquals(1000, count.intValue());

    executorService.shutdown();
    executorService.awaitTermination(30000, TimeUnit.MILLISECONDS);
  }
  @Test
  public void shouldCallFail() throws Exception {
    final AtomicBoolean timeoutCalled = new AtomicBoolean(false);
    final AtomicBoolean successCalled = new AtomicBoolean(false);
    final AtomicBoolean failureCalled = new AtomicBoolean(false);
    final GremlinExecutor gremlinExecutor =
        GremlinExecutor.build()
            .afterFailure((b, e) -> failureCalled.set(true))
            .afterSuccess((b) -> successCalled.set(true))
            .afterTimeout((b) -> timeoutCalled.set(true))
            .create();
    try {
      gremlinExecutor.eval("10/0").get();
      fail();
    } catch (Exception ignored) {
    }

    // need to wait long enough for the callback to register
    Thread.sleep(500);

    assertFalse(timeoutCalled.get());
    assertFalse(successCalled.get());
    assertTrue(failureCalled.get());
    gremlinExecutor.close();
  }
 @Test
 public void shouldCompileScript() throws Exception {
   final GremlinExecutor gremlinExecutor = GremlinExecutor.build().create();
   final CompiledScript script = gremlinExecutor.compile("1+1").get();
   assertEquals(2, script.eval());
   gremlinExecutor.close();
 }
  @Test
  public void shouldEvalScriptWithMapBindingsAndLanguageThenConsume() throws Exception {
    final GremlinExecutor gremlinExecutor =
        GremlinExecutor.build()
            .addEngineSettings(
                "nashorn",
                Collections.emptyList(),
                Collections.emptyList(),
                Collections.emptyList(),
                Collections.emptyMap())
            .create();
    final Map<String, Object> b = new HashMap<>();
    b.put("x", 1);

    final CountDownLatch latch = new CountDownLatch(1);
    final AtomicInteger result = new AtomicInteger(0);
    assertEquals(
        2.0,
        gremlinExecutor
            .eval(
                "1+x",
                "nashorn",
                b,
                r -> {
                  result.set(((Double) r).intValue() * 2);
                  latch.countDown();
                })
            .get());

    latch.await();
    assertEquals(4, result.get());
    gremlinExecutor.close();
  }
  @Test
  public void shouldTimeoutSleepingScript() throws Exception {
    final AtomicBoolean successCalled = new AtomicBoolean(false);
    final AtomicBoolean failureCalled = new AtomicBoolean(false);

    final CountDownLatch timeOutCount = new CountDownLatch(1);

    final GremlinExecutor gremlinExecutor =
        GremlinExecutor.build()
            .scriptEvaluationTimeout(250)
            .afterFailure((b, e) -> failureCalled.set(true))
            .afterSuccess((b) -> successCalled.set(true))
            .afterTimeout((b) -> timeOutCount.countDown())
            .create();
    try {
      gremlinExecutor.eval("Thread.sleep(1000);10").get();
      fail("This script should have timed out with an exception");
    } catch (Exception ex) {
      assertEquals(TimeoutException.class, ex.getCause().getClass());
    }

    assertTrue(timeOutCount.await(2000, TimeUnit.MILLISECONDS));

    assertFalse(successCalled.get());
    assertFalse(failureCalled.get());
    assertEquals(0, timeOutCount.getCount());
    gremlinExecutor.close();
  }
 @Test
 public void shouldEvalScriptWithGlobalBindings() throws Exception {
   final Bindings b = new SimpleBindings();
   b.put("x", 1);
   final GremlinExecutor gremlinExecutor = GremlinExecutor.build().globalBindings(b).create();
   assertEquals(2, gremlinExecutor.eval("1+x").get());
   gremlinExecutor.close();
 }
 @Test
 public void shouldEvalScriptWithMapBindings() throws Exception {
   final GremlinExecutor gremlinExecutor = GremlinExecutor.build().create();
   final Map<String, Object> b = new HashMap<>();
   b.put("x", 1);
   assertEquals(2, gremlinExecutor.eval("1+x", b).get());
   gremlinExecutor.close();
 }
 @Test
 public void shouldGetGlobalBindings() throws Exception {
   final Bindings b = new SimpleBindings();
   final Object bound = new Object();
   b.put("x", bound);
   final GremlinExecutor gremlinExecutor = GremlinExecutor.build().globalBindings(b).create();
   assertEquals(bound, gremlinExecutor.getGlobalBindings().get("x"));
   gremlinExecutor.close();
 }
  @Test
  public void shouldGetScheduledExecutorService() throws Exception {
    final ScheduledExecutorService service =
        Executors.newScheduledThreadPool(4, testingThreadFactory);
    final GremlinExecutor gremlinExecutor =
        GremlinExecutor.build().executorService(service).scheduledExecutorService(service).create();

    assertSame(service, gremlinExecutor.getScheduledExecutorService());
    gremlinExecutor.close();
  }
 @Test
 public void shouldEvalScriptWithLocalOverridingGlobalBindings() throws Exception {
   final Bindings g = new SimpleBindings();
   g.put("x", 1);
   final GremlinExecutor gremlinExecutor = GremlinExecutor.build().globalBindings(g).create();
   final Bindings b = new SimpleBindings();
   b.put("x", 10);
   assertEquals(11, gremlinExecutor.eval("x+1", b).get());
   gremlinExecutor.close();
 }
  @Test
  public void shouldNotShutdownExecutorServicesSuppliedToGremlinExecutor() throws Exception {
    final ScheduledExecutorService service =
        Executors.newScheduledThreadPool(4, testingThreadFactory);
    final GremlinExecutor gremlinExecutor =
        GremlinExecutor.build().executorService(service).scheduledExecutorService(service).create();

    gremlinExecutor.close();
    assertFalse(service.isShutdown());
    service.shutdown();
    service.awaitTermination(30000, TimeUnit.MILLISECONDS);
  }
 @Test
 public void shouldEvalFailingAssertionScript() throws Exception {
   final GremlinExecutor gremlinExecutor = GremlinExecutor.build().create();
   try {
     gremlinExecutor.eval("assert 1==0").get();
     fail("Should have thrown an exception");
   } catch (Exception ex) {
     assertThat(ex.getCause(), instanceOf(AssertionError.class));
   } finally {
     gremlinExecutor.close();
   }
 }
  @Test
  public void shouldFailUntilImportExecutes() throws Exception {
    final GremlinExecutor gremlinExecutor = GremlinExecutor.build().create();

    final Set<String> imports =
        new HashSet<String>() {
          {
            add("import java.awt.Color");
          }
        };

    final AtomicInteger successes = new AtomicInteger(0);
    final AtomicInteger failures = new AtomicInteger(0);

    // issue 1000 scripts in one thread using a class that isn't imported.  this will result in
    // failure.
    // while that thread is running start a new thread that issues an addImports to include that
    // class.
    // this should block further evals in the first thread until the import is complete at which
    // point
    // evals in the first thread will resume and start to succeed
    final Thread t1 =
        new Thread(
            () ->
                IntStream.range(0, 1000)
                    .mapToObj(i -> gremlinExecutor.eval("Color.BLACK"))
                    .forEach(
                        f -> {
                          f.exceptionally(t -> failures.incrementAndGet()).join();
                          if (!f.isCompletedExceptionally()) successes.incrementAndGet();
                        }));

    final Thread t2 =
        new Thread(
            () -> {
              while (failures.get() < 500) {}
              gremlinExecutor.getScriptEngines().addImports(imports);
            });

    t1.start();
    t2.start();

    t1.join();
    t2.join();

    assertTrue(successes.intValue() > 0);
    assertTrue(failures.intValue() >= 500);

    gremlinExecutor.close();
  }
 @Test
 public void shouldEvalScriptWithMapBindingsAndLanguage() throws Exception {
   final GremlinExecutor gremlinExecutor =
       GremlinExecutor.build()
           .addEngineSettings(
               "nashorn",
               Collections.emptyList(),
               Collections.emptyList(),
               Collections.emptyList(),
               Collections.emptyMap())
           .create();
   final Map<String, Object> b = new HashMap<>();
   b.put("x", 1);
   assertEquals(2.0, gremlinExecutor.eval("1+x", "nashorn", b).get());
   gremlinExecutor.close();
 }
  @Test
  public void shouldInitializeWithScript() throws Exception {
    final GremlinExecutor gremlinExecutor =
        GremlinExecutor.build()
            .addEngineSettings(
                "gremlin-groovy",
                Collections.emptyList(),
                Collections.emptyList(),
                Arrays.asList(PATHS.get("GremlinExecutorInit.groovy")),
                Collections.emptyMap())
            .create();

    assertEquals(2, gremlinExecutor.eval("add(1,1)").get());

    gremlinExecutor.close();
  }
  @Test
  public void shouldEvalInMultipleThreads() throws Exception {
    final GremlinExecutor gremlinExecutor = GremlinExecutor.build().create();

    final CyclicBarrier barrier = new CyclicBarrier(2);
    final AtomicInteger i1 = new AtomicInteger(0);
    final AtomicBoolean b1 = new AtomicBoolean(false);
    final Thread t1 =
        new Thread(
            () -> {
              try {
                barrier.await();
                i1.set((Integer) gremlinExecutor.eval("1+1").get());
              } catch (Exception ex) {
                b1.set(true);
              }
            });

    final AtomicInteger i2 = new AtomicInteger(0);
    final AtomicBoolean b2 = new AtomicBoolean(false);
    final Thread t2 =
        new Thread(
            () -> {
              try {
                barrier.await();
                i2.set((Integer) gremlinExecutor.eval("1+1").get());
              } catch (Exception ex) {
                b2.set(true);
              }
            });

    t1.start();
    t2.start();

    t1.join();
    t2.join();

    assertEquals(2, i1.get());
    assertEquals(2, i2.get());
    assertFalse(b1.get());
    assertFalse(b2.get());

    gremlinExecutor.close();
  }
  @Test
  public void shouldContinueToEvalScriptsEvenWithTimedInterrupt() throws Exception {
    final Map<String, List<Object>> compilerCustomizerConfig = new HashMap<>();
    final List<Object> args = new ArrayList<>();
    args.add(250);
    compilerCustomizerConfig.put(TimedInterruptCustomizerProvider.class.getName(), args);

    final Map<String, Object> config = new HashMap<>();
    config.put("compilerCustomizerProviders", compilerCustomizerConfig);

    final GremlinExecutor gremlinExecutor =
        GremlinExecutor.build()
            .addEngineSettings(
                "gremlin-groovy",
                Collections.emptyList(),
                Collections.emptyList(),
                Arrays.asList(PATHS.get("GremlinExecutorInit.groovy")),
                config)
            .create();

    for (int ix = 0; ix < 5; ix++) {
      try {
        // this script takes significantly longer than the interruptionTimeout
        gremlinExecutor
            .eval(
                "s = System.currentTimeMillis();\nwhile((System.currentTimeMillis() - s) < 10000) {}")
            .get();
        fail("This should have timed out");
      } catch (Exception se) {
        assertEquals(TimeoutException.class, se.getCause().getClass());
      }

      // this script takes significantly less than the interruptionTimeout
      assertEquals(
          "test",
          gremlinExecutor
              .eval(
                  "s = System.currentTimeMillis();\nwhile((System.currentTimeMillis() - s) < 20) {};'test'")
              .get());
    }

    gremlinExecutor.close();
  }
  @Test
  public void shouldOverrideAfterSuccess() throws Exception {
    final AtomicInteger called = new AtomicInteger(0);
    final GremlinExecutor gremlinExecutor =
        GremlinExecutor.build().afterSuccess(b -> called.set(1)).create();
    assertEquals(
        2,
        gremlinExecutor
            .eval(
                "1+1",
                null,
                new SimpleBindings(),
                GremlinExecutor.LifeCycle.build().afterSuccess(b -> called.set(200)).create())
            .get());

    // need to wait long enough for the callback to register
    Thread.sleep(500);

    assertEquals(200, called.get());
  }
  @Test
  public void shouldInterruptWhile() throws Exception {
    final Map<String, List<Object>> compilerCustomizerConfig = new HashMap<>();
    compilerCustomizerConfig.put(
        ThreadInterruptCustomizerProvider.class.getName(), new ArrayList<>());

    final Map<String, Object> config = new HashMap<>();
    config.put("compilerCustomizerProviders", compilerCustomizerConfig);

    final GremlinExecutor gremlinExecutor =
        GremlinExecutor.build()
            .addEngineSettings(
                "gremlin-groovy",
                Collections.emptyList(),
                Collections.emptyList(),
                Arrays.asList(PATHS.get("GremlinExecutorInit.groovy")),
                config)
            .create();
    final AtomicBoolean asserted = new AtomicBoolean(false);

    final Thread t =
        new Thread(
            () -> {
              try {
                gremlinExecutor
                    .eval(
                        "s = System.currentTimeMillis();\nwhile((System.currentTimeMillis() - s) < 10000) {}")
                    .get();
              } catch (Exception se) {
                asserted.set(se instanceof InterruptedException);
              }
            });

    t.start();
    Thread.sleep(100);
    t.interrupt();
    while (t.isAlive()) {}

    assertTrue(asserted.get());
  }
  @Test
  public void shouldOverrideAfterFailure() throws Exception {
    final AtomicInteger called = new AtomicInteger(0);
    final GremlinExecutor gremlinExecutor =
        GremlinExecutor.build().afterFailure((b, t) -> called.set(1)).create();
    try {
      gremlinExecutor
          .eval(
              "10/0",
              null,
              new SimpleBindings(),
              GremlinExecutor.LifeCycle.build().afterFailure((b, t) -> called.set(200)).create())
          .get();
      fail("Should have failed with division by zero");
    } catch (Exception ignored) {

    }

    // need to wait long enough for the callback to register
    Thread.sleep(500);

    assertEquals(200, called.get());
  }
 @Test
 public void shouldEvalMultipleScripts() throws Exception {
   final GremlinExecutor gremlinExecutor = GremlinExecutor.build().create();
   assertEquals(2, gremlinExecutor.eval("1+1").get());
   assertEquals(3, gremlinExecutor.eval("1+2").get());
   assertEquals(4, gremlinExecutor.eval("1+3").get());
   assertEquals(5, gremlinExecutor.eval("1+4").get());
   assertEquals(6, gremlinExecutor.eval("1+5").get());
   assertEquals(7, gremlinExecutor.eval("1+6").get());
   gremlinExecutor.close();
 }
  @Test
  public void shouldInitializeWithScriptAndMakeGlobalBinding() throws Exception {
    final GremlinExecutor gremlinExecutor =
        GremlinExecutor.build()
            .addEngineSettings(
                "gremlin-groovy",
                Collections.emptyList(),
                Collections.emptyList(),
                Arrays.asList(PATHS.get("GremlinExecutorInit.groovy")),
                Collections.emptyMap())
            .create();

    assertEquals(2, gremlinExecutor.eval("add(1,1)").get());
    assertThat(gremlinExecutor.getGlobalBindings().keySet(), not(contains("someSet")));
    assertThat(gremlinExecutor.getGlobalBindings().keySet(), contains("name"));

    assertEquals("stephen", gremlinExecutor.getGlobalBindings().get("name"));

    gremlinExecutor.close();
  }
 @Test
 public void shouldEvalSuccessfulAssertionScript() throws Exception {
   final GremlinExecutor gremlinExecutor = GremlinExecutor.build().create();
   assertNull(gremlinExecutor.eval("assert 1==1").get());
   gremlinExecutor.close();
 }
 @Test
 public void shouldEvalScript() throws Exception {
   final GremlinExecutor gremlinExecutor = GremlinExecutor.build().create();
   assertEquals(2, gremlinExecutor.eval("1+1").get());
   gremlinExecutor.close();
 }