/** {@inheritDoc} */
  @Override
  public WorkerRunnable createWorkerRunnable(final Object testRunner) throws EngineException {

    if (testRunner instanceof PyObject) {
      final PyObject pyTestRunner = (PyObject) testRunner;

      if (pyTestRunner.isCallable()) {
        return new JythonWorkerRunnable(pyTestRunner);
      }
    }

    throw new JythonScriptExecutionException("testRunner object is not callable");
  }
  /** {@inheritDoc} */
  @Override
  public WorkerRunnable createWorkerRunnable() throws EngineException {

    final PyObject pyTestRunner;

    try {
      // Script does per-thread initialisation here and
      // returns a callable object.
      pyTestRunner = m_testRunnerFactory.__call__();
    } catch (final PyException e) {
      throw new JythonScriptExecutionException("creating per-thread TestRunner object", e);
    }

    if (!pyTestRunner.isCallable()) {
      throw new JythonScriptExecutionException(
          "The result of '" + TEST_RUNNER_CALLABLE_NAME + "()' is not callable");
    }

    return new JythonWorkerRunnable(pyTestRunner);
  }
  /**
   * Constructor for JythonScriptEngine.
   *
   * @param pySystemState Python system state.
   * @throws EngineException If the script engine could not be created.
   */
  public JythonScriptEngine(final ScriptLocation script) throws EngineException {

    // Work around Jython issue 1894900.
    // If the python.cachedir has not been specified, and Jython is loaded
    // via the manifest classpath or the jar in the lib directory is
    // explicitly mentioned in the CLASSPATH, then set the cache directory to
    // be alongside jython.jar.
    if (System.getProperty(PYTHON_HOME) == null && System.getProperty(PYTHON_CACHEDIR) == null) {
      final String classpath = System.getProperty("java.class.path");

      final File grinderJar = findFileInPath(classpath, "grinder.jar");
      final File grinderJarDirectory =
          grinderJar != null ? grinderJar.getParentFile() : new File(".");

      final File jythonJar = findFileInPath(classpath, "jython.jar");
      final File jythonHome = jythonJar != null ? jythonJar.getParentFile() : grinderJarDirectory;

      if (grinderJarDirectory == null && jythonJar == null
          || grinderJarDirectory != null && grinderJarDirectory.equals(jythonHome)) {
        final File cacheDir = new File(jythonHome, CACHEDIR_DEFAULT_NAME);
        System.setProperty("python.cachedir", cacheDir.getAbsolutePath());
      }
    }

    m_systemState = new PySystemState();
    m_interpreter = new PythonInterpreter(null, m_systemState);

    m_interpreter.exec("class ___DieQuietly___: pass");
    m_dieQuietly = (PyClass) m_interpreter.get("___DieQuietly___");

    String version;

    try {
      version = PySystemState.class.getField("version").get(null).toString();
    } catch (final Exception e) {
      version = "Unknown";
    }

    m_version = version;

    // Prepend the script directory to the Python path. This matches the
    // behaviour of the Jython interpreter.
    m_systemState.path.insert(0, new PyString(script.getFile().getParent()));

    // Additionally, add the working directory to the Python path. I think
    // this will always be the same as the worker's CWD. Users expect to be
    // able to import from the directory the agent is running in or (when the
    // script has been distributed), the distribution directory.
    m_systemState.path.insert(1, new PyString(script.getDirectory().getFile().getPath()));

    try {
      // Run the test script, script does global set up here.
      m_interpreter.execfile(script.getFile().getPath());
    } catch (final PyException e) {
      throw new JythonScriptExecutionException("initialising test script", e);
    }

    // Find the callable that acts as a factory for test runner instances.
    m_testRunnerFactory = m_interpreter.get(TEST_RUNNER_CALLABLE_NAME);

    if (m_testRunnerFactory == null || !m_testRunnerFactory.isCallable()) {
      throw new JythonScriptExecutionException(
          "There is no callable (class or function) named '"
              + TEST_RUNNER_CALLABLE_NAME
              + "' in "
              + script);
    }
  }