add builder tests for annotation processing
diff --git a/Makefile b/Makefile
index 261925c..6825372 100644
--- a/Makefile
+++ b/Makefile
@@ -17,3 +17,6 @@
 
 proto.regen:
 	scripts/gen_proto_jars
+
+install.tools:
+	go get github.com/bazelbuild/buildtools/buildifier
diff --git a/scripts/reflow_skylark b/scripts/reflow_skylark
index 3489993..6e5c6f4 100755
--- a/scripts/reflow_skylark
+++ b/scripts/reflow_skylark
@@ -15,13 +15,13 @@
 # Note: Written on a mac please make it compatible with linux if needed.
 #!/usr/bin/env bash
 
-buildifier -mode=fix -v $(find kotlin -type f \
+buildifier -showlog -mode=fix -v $(find src -type f \
     -iname "*.bzl" -or \
     -iname "BUILD" -or \
     -iname "BUILD.com_github_jetbrains_kotlin"
 )
 
-buildifier -mode=fix -v \
+buildifier -showlog -mode=fix -v \
     "third_party/BUILD" \
     "WORKSPACE" \
     "BUILD"
\ No newline at end of file
diff --git a/src/main/kotlin/BUILD b/src/main/kotlin/BUILD
index ede4bea..c4b9698 100644
--- a/src/main/kotlin/BUILD
+++ b/src/main/kotlin/BUILD
@@ -23,6 +23,7 @@
         "@com_github_jetbrains_kotlin//:kotlin-annotation-processing",
         "@com_github_jetbrains_kotlin//:kotlin-script-runtime",
     ],
+    visibility = ["//src/test/kotlin/io/bazel/kotlin/builder:__subpackages__"],
 )
 
 # The builder artifact.
@@ -54,11 +55,11 @@
         "//src/test:__subpackages__",
     ],
     exports = [
-        "//src/main/kotlin/io/bazel/kotlin/builder:builder",
-        "//third_party/jvm/com/google/truth",
-        "//third_party/jvm/junit",
+        "//src/main/kotlin/io/bazel/kotlin/builder",
         "//src/main/protobuf:deps",
         "//src/main/protobuf:kotlin_model",
+        "//third_party/jvm/com/google/truth",
+        "//third_party/jvm/junit",
         "@io_bazel_rules_kotlin_com_google_guava_guava//jar",
         "@io_bazel_rules_kotlin_com_google_protobuf_protobuf_java//jar",
     ],
diff --git a/src/main/kotlin/bootstrap.bzl b/src/main/kotlin/bootstrap.bzl
index 02e184b..5901cda 100644
--- a/src/main/kotlin/bootstrap.bzl
+++ b/src/main/kotlin/bootstrap.bzl
@@ -27,7 +27,7 @@
     else:
         return d
 
-def kt_bootstrap_library(name, srcs, deps = [], neverlink_deps = [], runtime_deps = []):
+def kt_bootstrap_library(name, srcs, visibility = [], deps = [], neverlink_deps = [], runtime_deps = []):
     """
     Simple compilation of a kotlin library using a non-persistent worker. The target is a JavaInfo provider.
 
@@ -87,7 +87,7 @@
         jars = [jar_label],
         tags = ["no-ide"],
         runtime_deps = deps + runtime_deps,
-        visibility = ["//visibility:private"],
+        visibility = visibility,
     )
 
     # hsyed todo this part of the graph should not be wired up outside of development.
diff --git a/src/main/kotlin/io/bazel/kotlin/builder/BUILD b/src/main/kotlin/io/bazel/kotlin/builder/BUILD
index 6957022..0983523 100644
--- a/src/main/kotlin/io/bazel/kotlin/builder/BUILD
+++ b/src/main/kotlin/io/bazel/kotlin/builder/BUILD
@@ -21,10 +21,10 @@
         "utils/**/*.kt",
     ]),
     deps = [
-        "@com_github_jetbrains_kotlin//:kotlin-preloader",
         "//src/main/protobuf:deps",
         "//src/main/protobuf:kotlin_model",
         "//src/main/protobuf:worker",
+        "@com_github_jetbrains_kotlin//:kotlin-preloader",
         "@io_bazel_rules_kotlin_com_google_protobuf_protobuf_java//jar",
         "@io_bazel_rules_kotlin_com_google_protobuf_protobuf_java_util//jar",
         "@io_bazel_rules_kotlin_javax_inject_javax_inject//jar",
@@ -34,7 +34,7 @@
 java_library(
     name = "builder",
     srcs = glob(["*.java"]),
-    visibility = ["//src/main/kotlin:__subpackages__"],
+    visibility = ["//src:__subpackages__"],
     exports = [":builder_kt"],
     runtime_deps = [
         "@com_github_jetbrains_kotlin//:kotlin-stdlib-jdk7",
diff --git a/src/main/kotlin/io/bazel/kotlin/builder/tasks/jvm/KotlinJvmTaskExecutor.kt b/src/main/kotlin/io/bazel/kotlin/builder/tasks/jvm/KotlinJvmTaskExecutor.kt
index bad7f60..10bf26b 100644
--- a/src/main/kotlin/io/bazel/kotlin/builder/tasks/jvm/KotlinJvmTaskExecutor.kt
+++ b/src/main/kotlin/io/bazel/kotlin/builder/tasks/jvm/KotlinJvmTaskExecutor.kt
@@ -38,28 +38,26 @@
     fun execute(context: CompilationTaskContext, task: JvmCompilationTask) {
         // TODO fix error handling
         try {
-            val preprocessedTask = task.preprocessingSteps(context)
+            val preprocessedTask = task.preProcessingSteps(context)
             context.execute("compile classes") { preprocessedTask.compileAll(context) }
             context.execute("create jar") { preprocessedTask.createOutputJar() }
             context.execute("produce src jar") { preprocessedTask.produceSourceJar() }
-            context.execute("generate jdeps") { preprocessedTask.generateJDeps() }
+            context.execute("generate jdeps") { jDepsGenerator.generateJDeps(preprocessedTask) }
         } catch (ex: Throwable) {
             throw RuntimeException(ex)
         }
     }
 
-    private fun JvmCompilationTask.preprocessingSteps(context: CompilationTaskContext): JvmCompilationTask {
+    private fun JvmCompilationTask.preProcessingSteps(context: CompilationTaskContext): JvmCompilationTask {
         ensureDirectories(
             directories.temp,
             directories.generatedSources,
             directories.generatedClasses
         )
         val taskWithAdditionalSources = context.execute("expand sources") { expandWithSourceJarSources() }
-        return context.execute("kapt") { taskWithAdditionalSources.runAnnotationProcessors(context) }
-    }
-
-    private fun JvmCompilationTask.generateJDeps() {
-        jDepsGenerator.generateJDeps(this)
+        return context.execute({
+            "kapt (${info.plugins.annotationProcessorsList.joinToString(", ") { it.processorClass }})"
+        }) { taskWithAdditionalSources.runAnnotationProcessors(context) }
     }
 
     private fun JvmCompilationTask.produceSourceJar() {
@@ -129,7 +127,7 @@
             this
         } else {
             runAnnotationProcessor(context, printOnSuccess = !context.isTracing).let { outputLines ->
-                // if tracing is enabled the output should be formated in a special way, if we aren't tracing then any
+                // if tracing is enabled the output should be formatted in a special way, if we aren't tracing then any
                 // compiler output would make it's way to the console as is.
                 if (context.isTracing) {
                     context.printLines("kapt output", outputLines)
diff --git a/src/main/kotlin/io/bazel/kotlin/builder/utils/CompilationTaskContext.kt b/src/main/kotlin/io/bazel/kotlin/builder/utils/CompilationTaskContext.kt
index 2c626bd..9351233 100644
--- a/src/main/kotlin/io/bazel/kotlin/builder/utils/CompilationTaskContext.kt
+++ b/src/main/kotlin/io/bazel/kotlin/builder/utils/CompilationTaskContext.kt
@@ -15,7 +15,6 @@
  */
 package io.bazel.kotlin.builder.utils
 
-
 import com.google.protobuf.MessageOrBuilder
 import com.google.protobuf.TextFormat
 import io.bazel.kotlin.builder.toolchain.CompilationStatusException
@@ -27,8 +26,10 @@
 import java.nio.file.Paths
 
 class CompilationTaskContext(val info: CompilationTaskInfo, private val out: PrintStream) {
+    private val start = System.currentTimeMillis()
     private val executionRoot: String = Paths.get("").toAbsolutePath().toString() + File.separator
-    private val timings: MutableList<String>?
+    private var timings: MutableList<String>?
+    private var level = -1
     @PublishedApi
     internal val isTracing: Boolean
 
@@ -42,7 +43,11 @@
         throwable.printStackTrace(out)
     }
 
-    fun print(msg: String) { out.println(msg) }
+    @Suppress("unused")
+    fun print(msg: String) {
+        out.println(msg)
+    }
+
     /**
      * Print a list of debugging lines.
      *
@@ -113,7 +118,7 @@
                 printCompilerOutput(output)
             }
             throw CompilationStatusException("compile phase failed", result, output)
-        } else if(printOnSuccess) {
+        } else if (printOnSuccess) {
             printCompilerOutput(output)
         }
         return output
@@ -122,28 +127,46 @@
     /**
      * Runs a task and records the timings.
      */
-    fun <T> execute(name: String, task: () -> T): T {
+    fun <T> execute(name: String, task: () -> T): T = execute({ name }, task)
+
+    /**
+     * Runs a task and records the timings.
+     */
+    @Suppress("MemberVisibilityCanBePrivate")
+    fun <T> execute(name: () -> String, task: () -> T): T {
         return if (timings == null) {
             task()
-        } else {
-            val start = System.currentTimeMillis()
-            try {
-                task()
-            } finally {
-                val stop = System.currentTimeMillis()
-                timings += "$name: ${stop - start} ms"
+        } else pushTimedTask(name(), task)
+    }
+
+    private inline fun <T> pushTimedTask(name: String, task: () -> T): T {
+        level += 1
+        val previousTimings = timings
+        timings = mutableListOf()
+        return try {
+            System.currentTimeMillis().let { start ->
+                task().also {
+                    val stop = System.currentTimeMillis()
+                    previousTimings!! += "${"  ".repeat(level)} * $name: ${stop - start} ms"
+                    previousTimings.addAll(timings!!)
+                }
             }
+        } finally {
+            level -= 1
+            timings = previousTimings
         }
     }
 
     /**
      * This method should be called at the end of builder invocation.
      *
-     * @param succesfull true if the task finished succesfully.
+     * @param successful true if the task finished successfully.
      */
-    fun finalize(succesfull: Boolean) {
-        if (succesfull) {
-            timings?.also { printLines("Task timings", it, prefix = "  * ") }
+    fun finalize(successful: Boolean) {
+        if (successful) {
+            timings?.also {
+                printLines("Task timings for ${info.label} (total: ${System.currentTimeMillis() - start} ms)", it)
+            }
         }
     }
 }
diff --git a/src/test/data/jvm/basic/BUILD b/src/test/data/jvm/basic/BUILD
index 16a016c..ebfdc39 100644
--- a/src/test/data/jvm/basic/BUILD
+++ b/src/test/data/jvm/basic/BUILD
@@ -11,7 +11,8 @@
 # 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(default_visibility=["//visibility:private"])
+package(default_visibility = ["//visibility:private"])
+
 load("//kotlin:kotlin.bzl", "kt_jvm_binary", "kt_jvm_library", "kt_jvm_test")
 
 kt_jvm_binary(
@@ -104,7 +105,7 @@
 kt_jvm_library(
     name = "test_friends_library",
     srcs = ["test_friends/Service.kt"],
-    visibility = ["//src/test/kotlin:__subpackages__"]
+    visibility = ["//src/test/kotlin:__subpackages__"],
 )
 
 filegroup(
diff --git a/src/test/kotlin/io/bazel/kotlin/BUILD b/src/test/kotlin/io/bazel/kotlin/BUILD
index d0b9697..03df859 100644
--- a/src/test/kotlin/io/bazel/kotlin/BUILD
+++ b/src/test/kotlin/io/bazel/kotlin/BUILD
@@ -11,7 +11,7 @@
 # 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(default_visibility=["//visibility:private"])
+package(default_visibility = ["//visibility:private"])
 
 load("//kotlin:kotlin.bzl", "kt_jvm_library")
 load(":defs.bzl", "kt_rules_e2e_test", "kt_rules_test")
@@ -21,62 +21,62 @@
     testonly = 1,
     srcs = ["KotlinAssertionTestCase.kt"],
     visibility = ["//visibility:public"],
+    exports = [
+        "//third_party/jvm/com/google/truth",
+        "//third_party/jvm/junit",
+        "@com_github_jetbrains_kotlin//:kotlin-test",
+        "@io_bazel_rules_kotlin_com_google_guava_guava//jar",
+    ],
     deps = [
         "@com_github_jetbrains_kotlin//:kotlin-test",
         "@io_bazel_rules_kotlin_com_google_guava_guava//jar",
     ],
-    exports = [
-        "//third_party/jvm/com/google/truth",
-        "//third_party/jvm/junit:junit",
-        "@com_github_jetbrains_kotlin//:kotlin-test",
-        "@io_bazel_rules_kotlin_com_google_guava_guava//jar",
-    ]
 )
 
 kt_rules_e2e_test(
     name = "KotlinJvmBasicAssertionTest",
     srcs = ["KotlinJvmBasicAssertionTest.kt"],
-    data = [ "//src/test/data/jvm/basic"]
+    data = ["//src/test/data/jvm/basic"],
 )
 
 kt_rules_e2e_test(
     name = "KotlinNormalizationAssertionTest",
-    srcs =["KotlinNormalizationAssertionTest.kt"],
+    srcs = ["KotlinNormalizationAssertionTest.kt"],
     data = ["//src/test/data/jvm/basic"],
 )
 
 kt_rules_e2e_test(
     name = "KotlinJvmKaptAssertionTest",
     srcs = ["KotlinJvmKaptAssertionTest.kt"],
-    data = ["//src/test/data/jvm/kapt"]
+    data = ["//src/test/data/jvm/kapt"],
 )
 
 kt_rules_e2e_test(
     name = "KotlinJvmDaggerExampleTest",
-    srcs =["KotlinJvmDaggerExampleTest.kt"],
+    srcs = ["KotlinJvmDaggerExampleTest.kt"],
     data = ["//examples/dagger:coffee_app"],
 )
 
 kt_rules_e2e_test(
     name = "KotlinJvmFriendsVisibilityTest",
     srcs = ["KotlinJvmFriendsVisibilityTest.kt"],
-    friends = ["//src/test/data/jvm/basic:test_friends_library"]
+    friends = ["//src/test/data/jvm/basic:test_friends_library"],
 )
 
 test_suite(
     name = "assertion_tests",
     tests = [
         "KotlinJvmBasicAssertionTest",
-        "KotlinJvmKaptAssertionTest",
         "KotlinJvmDaggerExampleTest",
         "KotlinJvmFriendsVisibilityTest",
-    ]
+        "KotlinJvmKaptAssertionTest",
+    ],
 )
 
 test_suite(
     name = "local_assertion_tests",
     tests = [
+        ":KotlinNormalizationAssertionTest",
         ":assertion_tests",
-        ":KotlinNormalizationAssertionTest"
-    ]
+    ],
 )
diff --git a/src/test/kotlin/io/bazel/kotlin/builder/BUILD b/src/test/kotlin/io/bazel/kotlin/builder/BUILD
index 599ac4a..3c7a9f2 100644
--- a/src/test/kotlin/io/bazel/kotlin/builder/BUILD
+++ b/src/test/kotlin/io/bazel/kotlin/builder/BUILD
@@ -11,15 +11,38 @@
 # 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(default_visibility=["//visibility:private"])
+package(default_visibility = ["//visibility:private"])
+
 load("//src/test/kotlin/io/bazel/kotlin:defs.bzl", "kt_rules_test")
 
+_COMMON_DEPS = [
+    "//src/main/kotlin/io/bazel/kotlin/builder",
+    "//src/main/protobuf:kotlin_model",
+    "//third_party/jvm/com/google/truth",
+    "//third_party/jvm/junit",
+    "@io_bazel_rules_kotlin_com_google_guava_guava//jar",
+]
+
 java_library(
     name = "test_lib",
     testonly = 1,
-    srcs = [":KotlinBuilderTestCase.java"],
-    exports = ["//src/main/kotlin:builder_lib_for_tests"],
-    deps = ["//src/main/kotlin:builder_lib_for_tests"],
+    srcs = [
+        "KotlinBuilderJvmTestTask.java",
+        "KotlinBuilderResource.java",
+    ],
+    data = [
+        "//src/main/kotlin:compiler_lib",
+        "@com_github_jetbrains_kotlin//:home",
+    ],
+    exports = _COMMON_DEPS + [
+        "@io_bazel_rules_kotlin_com_google_protobuf_protobuf_java//jar",
+        "//src/main/protobuf:deps",
+    ],
+    runtime_deps = [
+        "@com_github_jetbrains_kotlin//:kotlin-reflect",
+        "@com_github_jetbrains_kotlin//:kotlin-stdlib",
+    ],
+    deps = _COMMON_DEPS,
 )
 
 kt_rules_test(
@@ -35,15 +58,17 @@
 kt_rules_test(
     name = "KotlinBuilderJvmTest",
     srcs = ["tasks/jvm/KotlinBuilderJvmTest.java"],
+    data = [
+        "//third_party/jvm/com/google/auto/value:auto_value",
+    ],
 )
 
 test_suite(
     name = "builder_tests",
     tests = [
-        ":SourceJarCreatorTest",
         ":JdepsParserTest",
-        ":KotlinBuilderJvmTest"
+        ":KotlinBuilderJvmTest",
+        ":SourceJarCreatorTest",
     ],
-    visibility = ["//visibility:public"]
+    visibility = ["//visibility:public"],
 )
-
diff --git a/src/test/kotlin/io/bazel/kotlin/builder/KotlinBuilderJvmTestTask.java b/src/test/kotlin/io/bazel/kotlin/builder/KotlinBuilderJvmTestTask.java
new file mode 100644
index 0000000..e3c1f97
--- /dev/null
+++ b/src/test/kotlin/io/bazel/kotlin/builder/KotlinBuilderJvmTestTask.java
@@ -0,0 +1,111 @@
+package io.bazel.kotlin.builder;
+
+import io.bazel.kotlin.builder.utils.CompilationTaskContext;
+import io.bazel.kotlin.model.*;
+
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.nio.file.Path;
+import java.util.Arrays;
+import java.util.function.BiConsumer;
+import java.util.function.BiFunction;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+public final class KotlinBuilderJvmTestTask extends KotlinBuilderResource {
+
+  private static final JvmCompilationTask.Builder taskBuilder = JvmCompilationTask.newBuilder();
+
+  @Override
+  CompilationTaskInfo.Builder infoBuilder() {
+    return taskBuilder.getInfoBuilder();
+  }
+
+  @Override
+  protected void before() throws Throwable {
+    super.before();
+    taskBuilder.clear();
+
+    taskBuilder
+        .getInfoBuilder()
+        .setLabel("//some/bogus:" + label())
+        .setModuleName("some_bogus_module")
+        .setPlatform(Platform.JVM)
+        .setRuleKind(RuleKind.LIBRARY)
+        .setToolchainInfo(
+            KotlinToolchainInfo.newBuilder()
+                .setCommon(
+                    KotlinToolchainInfo.Common.newBuilder()
+                        .setApiVersion("1.2")
+                        .setCoroutines("enabled")
+                        .setLanguageVersion("1.2"))
+                .setJvm(KotlinToolchainInfo.Jvm.newBuilder().setJvmTarget("1.8")));
+    taskBuilder
+        .getDirectoriesBuilder()
+        .setClasses(directory(DirectoryType.CLASSES).toAbsolutePath().toString())
+        .setGeneratedSources(directory(DirectoryType.SOURCE_GEN).toAbsolutePath().toString())
+        .setTemp(directory(DirectoryType.TEMP).toAbsolutePath().toString())
+        .setGeneratedClasses(
+            directory(DirectoryType.GENERATED_CLASSES).toAbsolutePath().toString());
+    taskBuilder
+        .getOutputsBuilder()
+        .setJar(instanceRoot().resolve("jar_file.jar").toAbsolutePath().toString())
+        .setJdeps(instanceRoot().resolve("jdeps_file.jdeps").toAbsolutePath().toString())
+        .setSrcjar(instanceRoot().resolve("jar_file-sources.jar").toAbsolutePath().toString());
+  }
+
+  public void addSource(String filename, String... lines) {
+    Path path = directory(DirectoryType.SOURCES).resolve(filename).toAbsolutePath();
+    try (FileOutputStream fos = new FileOutputStream(path.toFile())) {
+      fos.write(String.join("\n", lines).getBytes(UTF_8));
+    } catch (IOException e) {
+      throw new UncheckedIOException(e);
+    }
+
+    String pathAsString = path.toString();
+    if (pathAsString.endsWith(".kt")) {
+      taskBuilder.getInputsBuilder().addKotlinSources(pathAsString);
+    } else if (pathAsString.endsWith(".java")) {
+      taskBuilder.getInputsBuilder().addJavaSources(pathAsString);
+    } else {
+      throw new RuntimeException("unhandled file type: " + path.toString());
+    }
+  }
+
+  public void addAnnotationProcessors(AnnotationProcessor... annotationProcessors) {
+    taskBuilder
+        .getInfoBuilder()
+        .getPluginsBuilder()
+        .addAllAnnotationProcessors(Arrays.asList(annotationProcessors));
+  }
+
+  public void addDirectDependencies(Dep... dependencies) {
+    Dep.merge(dependencies)
+        .compileJars()
+        .forEach(
+            (dependency) -> {
+              assert dependency.toFile().exists();
+              String depString = dependency.toString();
+              taskBuilder.getInputsBuilder().addClasspath(depString);
+            });
+  }
+
+  public void runCompileTask(BiConsumer<CompilationTaskContext, JvmCompilationTask> operation) {
+    JvmCompilationTask task = taskBuilder.build();
+    super.runCompileTask(
+        new CompilationTaskContext(task.getInfo(), System.err),
+        task,
+        (ctx, t) -> {
+          operation.accept(ctx, t);
+          return null;
+        });
+  }
+
+  @SuppressWarnings("unused")
+  public <R> R runCompileTask(BiFunction<CompilationTaskContext, JvmCompilationTask, R> operation) {
+    JvmCompilationTask task = taskBuilder.build();
+    return super.runCompileTask(
+        new CompilationTaskContext(task.getInfo(), System.err), task, operation);
+  }
+}
diff --git a/src/test/kotlin/io/bazel/kotlin/builder/KotlinBuilderResource.java b/src/test/kotlin/io/bazel/kotlin/builder/KotlinBuilderResource.java
new file mode 100644
index 0000000..02db955
--- /dev/null
+++ b/src/test/kotlin/io/bazel/kotlin/builder/KotlinBuilderResource.java
@@ -0,0 +1,241 @@
+package io.bazel.kotlin.builder;
+
+import io.bazel.kotlin.builder.utils.CompilationTaskContext;
+import io.bazel.kotlin.model.CompilationTaskInfo;
+import org.junit.rules.ExternalResource;
+
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.*;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.BiFunction;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+public abstract class KotlinBuilderResource extends ExternalResource {
+  public enum DirectoryType {
+    INSTANCE_ROOT("test root", null),
+    EXTERNAL("bazel external directory", null),
+    /** The rest of the paths are instance relative. */
+    SOURCES("sources", Paths.get("sources")),
+    CLASSES("compiled classes", Paths.get("classes")),
+    GENERATED_CLASSES("generated classes", Paths.get("generated_classes")),
+    TEMP("temp directory", Paths.get("temp")),
+    SOURCE_GEN("generated sources directory", Paths.get("generated_sources"));
+
+    private static final EnumSet<DirectoryType> INSTANCE_TYPES =
+        EnumSet.of(SOURCES, CLASSES, SOURCE_GEN, GENERATED_CLASSES, TEMP);
+
+    final String name;
+    private final Path relativePath;
+
+    DirectoryType(String name, Path relativePath) {
+      this.name = name;
+      this.relativePath = relativePath;
+    }
+  }
+
+  public abstract static class Dep {
+    private Dep() {}
+
+    abstract Stream<Path> compileJars();
+
+    private static class Simple extends Dep {
+      private final List<Path> compileJars;
+
+      private Simple(List<Path> compileJars) {
+        this.compileJars = compileJars;
+      }
+
+      @Override
+      Stream<Path> compileJars() {
+        return compileJars.stream();
+      }
+    }
+
+    static Dep merge(Dep... dependencies) {
+      return new Simple(
+          Stream.of(dependencies).flatMap(Dep::compileJars).collect(Collectors.toList()));
+    }
+
+    public static List<String> classpathOf(Dep... dependencies) {
+      return merge(dependencies).compileJars().map(Path::toString).collect(Collectors.toList());
+    }
+
+    public static Dep simpleOf(String compileJar) {
+      return new Simple(Collections.singletonList(toPlatformPath(compileJar).toAbsolutePath()));
+    }
+
+    @SuppressWarnings("unused")
+    static Dep simpleOf(Path compileJar) {
+      return new Simple(Collections.singletonList(compileJar));
+    }
+  }
+
+  private static final Path
+      BAZEL_TEST_DIR = Paths.get(Objects.requireNonNull(System.getenv("TEST_TMPDIR"))),
+      EXTERNAL_PATH = Paths.get("external");
+  @SuppressWarnings("unused")
+  public static Dep
+      KOTLIN_ANNOTATIONS =
+          Dep.simpleOf("external/com_github_jetbrains_kotlin/lib/annotations-13.0.jar"),
+      KOTLIN_STDLIB = Dep.simpleOf("external/com_github_jetbrains_kotlin/lib/kotlin-stdlib.jar"),
+      KOTLIN_STDLIB_JDK7 =
+          Dep.simpleOf("external/com_github_jetbrains_kotlin/lib/kotlin-stdlib-jdk7.jar"),
+      KOTLIN_STDLIB_JDK8 =
+          Dep.simpleOf("external/com_github_jetbrains_kotlin/lib/kotlin-stdlib-jdk8.jar");
+
+  private static final AtomicInteger counter = new AtomicInteger(0);
+  private Path instanceRoot = null;
+  private String label = null;
+
+  KotlinBuilderResource() {}
+
+  abstract CompilationTaskInfo.Builder infoBuilder();
+
+  String label() {
+    return Objects.requireNonNull(label);
+  }
+
+  Path instanceRoot() {
+    return Objects.requireNonNull(instanceRoot);
+  }
+
+  @Override
+  protected void before() throws Throwable {
+    label = "a_test_" + counter.incrementAndGet();
+    setTimeout(DEFAULT_TIMEOUT);
+    try {
+      this.instanceRoot = Files.createDirectory(BAZEL_TEST_DIR.resolve(Paths.get(label)));
+    } catch (IOException e) {
+      throw new UncheckedIOException(e);
+    }
+
+    for (DirectoryType instanceType : DirectoryType.INSTANCE_TYPES) {
+      try {
+        Files.createDirectory(instanceRoot.resolve(instanceType.relativePath));
+      } catch (IOException e) {
+        throw new RuntimeException("could not create instance directory: " + instanceType.name, e);
+      }
+    }
+  }
+
+  Path directory(DirectoryType type) {
+    switch (type) {
+      case INSTANCE_ROOT:
+        return instanceRoot;
+      case EXTERNAL:
+        return KotlinBuilderResource.EXTERNAL_PATH;
+      case SOURCES:
+      case CLASSES:
+      case GENERATED_CLASSES:
+      case TEMP:
+      case SOURCE_GEN:
+        return instanceRoot.resolve(type.relativePath);
+      default:
+        throw new IllegalStateException(type.toString());
+    }
+  }
+
+  @SuppressWarnings("unused")
+  public final void setDebugTags(String... tags) {
+    infoBuilder().addAllDebug(Arrays.asList(tags));
+  }
+
+  private int DEFAULT_TIMEOUT = 10;
+  private int timeoutSeconds = DEFAULT_TIMEOUT;
+
+  /**
+   * sets the timeout for the builder tasks.
+   *
+   * @param timeoutSeconds a timeout in seconds. For debugging purposes it can be set to <= 0 which
+   *     means wait indefinitely.
+   */
+  @SuppressWarnings("WeakerAccess")
+  public final void setTimeout(int timeoutSeconds) {
+    this.timeoutSeconds = timeoutSeconds;
+  }
+
+  <T, R> R runCompileTask(
+      CompilationTaskContext context, T task, BiFunction<CompilationTaskContext, T, R> operation) {
+    String curDir = System.getProperty("user.dir");
+    System.setProperty("user.dir", instanceRoot().toAbsolutePath().toString());
+    try {
+      CompletableFuture<R> future =
+          CompletableFuture.supplyAsync(() -> operation.apply(context, task));
+      return timeoutSeconds > 0 ? future.get(timeoutSeconds, TimeUnit.SECONDS) : future.get();
+
+    } catch (TimeoutException e) {
+      throw new AssertionError("did not complete in: " + timeoutSeconds);
+    } catch (Exception e) {
+      throw new RuntimeException(e);
+    } finally {
+      System.setProperty("user.dir", curDir);
+    }
+  }
+
+  public final void assertFilesExist(DirectoryType dir, String... paths) {
+    assertFileExistence(resolved(dir, paths), true);
+  }
+
+  public void assertFilesExist(String... paths) {
+    assertFileExistence(Stream.of(paths).map(Paths::get), true);
+  }
+
+  @SuppressWarnings("unused")
+  public final void assertFilesDoNotExist(DirectoryType dir, String... filePath) {
+    assertFileExistence(resolved(dir, filePath), false);
+  }
+
+  private static void assertFileExistence(Stream<Path> pathStream, boolean shouldexist) {
+    pathStream.forEach(
+        path -> {
+          if (shouldexist)
+            assertWithMessage("file did not exist: " + path).that(path.toFile().exists()).isTrue();
+          else assertWithMessage("file existed: " + path).that(path.toFile().exists()).isFalse();
+        });
+  }
+
+  private Stream<Path> resolved(DirectoryType dir, String... filePath) {
+    Path directory = directory(dir);
+    return Stream.of(filePath).map(f -> directory.resolve(toPlatformPath(f)));
+  }
+
+  /**
+   * Normalize a path string.
+   *
+   * @param path a path using '/' as the seperator.
+   * @return a path string suitable for the target platform.
+   */
+  private static Path toPlatformPath(String path) {
+    String[] parts = path.split("/");
+    return parts.length == 1
+        ? Paths.get(parts[0])
+        : Paths.get(parts[0], Arrays.copyOfRange(parts, 1, parts.length));
+  }
+
+  @SuppressWarnings("unused")
+  private Stream<Path> directoryContents(DirectoryType type) {
+    try {
+      return Files.walk(directory(type)).map(p -> directory(type).relativize(p));
+    } catch (IOException e) {
+      throw new UncheckedIOException(e);
+    }
+  }
+
+  @SuppressWarnings("unused")
+  public final void logDirectoryContents(DirectoryType type) {
+    System.out.println(
+        directoryContents(type)
+            .map(Path::toString)
+            .collect(Collectors.joining("\n", "directory " + type.name + " contents:\n", "")));
+  }
+}
diff --git a/src/test/kotlin/io/bazel/kotlin/builder/KotlinBuilderTestCase.java b/src/test/kotlin/io/bazel/kotlin/builder/KotlinBuilderTestCase.java
deleted file mode 100644
index 3a3b7de..0000000
--- a/src/test/kotlin/io/bazel/kotlin/builder/KotlinBuilderTestCase.java
+++ /dev/null
@@ -1,181 +0,0 @@
-package io.bazel.kotlin.builder;
-
-import com.google.common.base.Joiner;
-import com.google.common.base.Preconditions;
-import io.bazel.kotlin.builder.toolchain.KotlinToolchain;
-import io.bazel.kotlin.builder.utils.CompilationTaskContext;
-import io.bazel.kotlin.model.JvmCompilationTask;
-import io.bazel.kotlin.model.KotlinToolchainInfo;
-import io.bazel.kotlin.model.Platform;
-import io.bazel.kotlin.model.RuleKind;
-import org.junit.Before;
-
-import java.io.BufferedWriter;
-import java.io.File;
-import java.io.IOException;
-import java.io.UncheckedIOException;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.util.concurrent.atomic.AtomicInteger;
-
-import static com.google.common.base.Charsets.UTF_8;
-import static com.google.common.truth.Truth.assertWithMessage;
-
-public abstract class KotlinBuilderTestCase {
-  private static final Path BAZEL_TEST_DIR =
-      Paths.get(Preconditions.checkNotNull(System.getenv("TEST_TMPDIR")));
-  private static final AtomicInteger counter = new AtomicInteger(0);
-
-  private final JvmCompilationTask.Builder builder = JvmCompilationTask.newBuilder();
-  private final KotlinBuilderComponent component =
-      DaggerKotlinBuilderComponent.builder().toolchain(KotlinToolchain.createToolchain()).build();
-
-  private String label = null;
-  private Path inputSourceDir = null;
-
-  @Before
-  public void setupNext() {
-    resetTestContext("a_test_" + counter.incrementAndGet());
-  }
-
-  protected JvmCompilationTask.Outputs outputs() {
-    return builder.getOutputs();
-  }
-
-  protected JvmCompilationTask.Directories directories() {
-    return builder.getDirectories();
-  }
-
-  protected String label() {
-    return Preconditions.checkNotNull(label);
-  }
-
-  private Path classDir() {
-    return Paths.get(directories().getClasses());
-  }
-
-  protected CompilationTaskContext context() {
-    return new CompilationTaskContext(builder.getInfo(), System.err);
-  }
-
-  protected JvmCompilationTask builderCommand() {
-    return builder.build();
-  }
-
-  protected KotlinBuilderComponent component() {
-    return component;
-  }
-
-  protected void addSource(String filename, String... lines) {
-    Path file =
-        Preconditions.checkNotNull(inputSourceDir, "initialize test context").resolve(filename);
-    try (BufferedWriter writer = com.google.common.io.Files.newWriter(file.toFile(), UTF_8)) {
-      writer.write(Joiner.on("\n").join(lines));
-      String f = file.toString();
-      if (f.endsWith(".kt")) {
-        builder.getInputsBuilder().addKotlinSources(f);
-      } else if (f.endsWith(".java")) {
-        builder.getInputsBuilder().addJavaSources(f);
-      } else {
-        throw new RuntimeException("unhandled file type: " + f);
-      }
-
-    } catch (IOException e) {
-      throw new UncheckedIOException(e);
-    }
-  }
-
-  protected void resetTestContext(String label) {
-    this.label = label;
-    Path prefixPath = Paths.get(label);
-
-    createTestOuputDirectory(prefixPath);
-    inputSourceDir = Paths.get(createTestOuputDirectory(prefixPath.resolve("input_sources")));
-
-    builder.clear();
-    builder
-        .getInfoBuilder()
-        .setLabel("//some/bogus:" + label)
-        .setModuleName("some_bogus_module")
-        .setPlatform(Platform.JVM)
-        .setRuleKind(RuleKind.LIBRARY)
-        .setToolchainInfo(
-            KotlinToolchainInfo.newBuilder()
-                .setCommon(
-                    KotlinToolchainInfo.Common.newBuilder()
-                        .setApiVersion("1.2")
-                        .setCoroutines("enabled")
-                        .setLanguageVersion("1.2"))
-                .setJvm(KotlinToolchainInfo.Jvm.newBuilder().setJvmTarget("1.8")));
-    builder
-        .getDirectoriesBuilder()
-        .setClasses(prefixPath.resolve("classes").toAbsolutePath().toString())
-        .setGeneratedSources(prefixPath.resolve("sources").toAbsolutePath().toString())
-        .setTemp(prefixPath.resolve("temp").toAbsolutePath().toString())
-        .setGeneratedClasses(prefixPath.resolve("generated_classes").toAbsolutePath().toString());
-    builder
-        .getOutputsBuilder()
-        .setJar(prefixPath.resolve("jar_file.jar").toAbsolutePath().toString())
-        .setJdeps(prefixPath.resolve("jdeps_file.jdeps").toAbsolutePath().toString())
-        .setSrcjar(prefixPath.resolve("jar_file-sources.jar").toAbsolutePath().toString());
-  }
-
-  private static String createTestOuputDirectory(Path path) {
-    try {
-      return Files.createDirectory(BAZEL_TEST_DIR.resolve(path)).toAbsolutePath().toString();
-    } catch (IOException e) {
-      throw new UncheckedIOException(e);
-    }
-  }
-
-  private static String createTestOutputFile(Path path) {
-    try {
-      return Files.createFile(BAZEL_TEST_DIR.resolve(path)).toAbsolutePath().toString();
-    } catch (IOException e) {
-      throw new UncheckedIOException(e);
-    }
-  }
-
-  protected enum DirectoryType {
-    ROOT,
-    CLASSES,
-    GENERATED_CLASSES,
-    TEMP,
-    SOURCE_GEN;
-
-    public static Path select(DirectoryType type, JvmCompilationTask command) {
-      Path ret;
-      switch (type) {
-        case CLASSES:
-          ret = Paths.get(command.getDirectories().getClasses());
-          break;
-        case GENERATED_CLASSES:
-          ret = Paths.get(command.getDirectories().getGeneratedClasses());
-          break;
-        case TEMP:
-          ret = Paths.get(command.getDirectories().getTemp());
-          break;
-        case SOURCE_GEN:
-          ret = Paths.get(command.getDirectories().getGeneratedSources());
-          break;
-        default:
-          throw new RuntimeException("unhandled type: " + type);
-      }
-      return ret;
-    }
-  }
-
-  protected void assertFileExists(DirectoryType dir, String filePath) {
-    Path file = DirectoryType.select(dir, builderCommand()).resolve(filePath);
-    assertFileExists(file.toString());
-  }
-
-  void assertFileDoesNotExist(String filePath) {
-    assertWithMessage("file exisst: " + filePath).that(new File(filePath).exists()).isFalse();
-  }
-
-  protected void assertFileExists(String filePath) {
-    assertWithMessage("file did not exist: " + filePath).that(new File(filePath).exists()).isTrue();
-  }
-}
diff --git a/src/test/kotlin/io/bazel/kotlin/builder/tasks/jvm/KotlinBuilderJvmTest.java b/src/test/kotlin/io/bazel/kotlin/builder/tasks/jvm/KotlinBuilderJvmTest.java
index e1e4373..386bf8c 100644
--- a/src/test/kotlin/io/bazel/kotlin/builder/tasks/jvm/KotlinBuilderJvmTest.java
+++ b/src/test/kotlin/io/bazel/kotlin/builder/tasks/jvm/KotlinBuilderJvmTest.java
@@ -1,71 +1,177 @@
 package io.bazel.kotlin.builder.tasks.jvm;
 
-import com.google.common.truth.Truth;
-import com.google.devtools.build.lib.view.proto.Deps;
-import io.bazel.kotlin.builder.KotlinBuilderTestCase;
+import io.bazel.kotlin.builder.DaggerKotlinBuilderComponent;
+import io.bazel.kotlin.builder.KotlinBuilderComponent;
+import io.bazel.kotlin.builder.KotlinBuilderJvmTestTask;
+import io.bazel.kotlin.builder.KotlinBuilderResource.Dep;
+import io.bazel.kotlin.builder.KotlinBuilderResource.DirectoryType;
+import io.bazel.kotlin.builder.toolchain.KotlinToolchain;
 import io.bazel.kotlin.builder.utils.CompilationTaskContext;
+import io.bazel.kotlin.model.AnnotationProcessor;
 import io.bazel.kotlin.model.JvmCompilationTask;
+import org.junit.Ignore;
+import org.junit.Rule;
 import org.junit.Test;
 
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.io.UncheckedIOException;
-import java.nio.file.Files;
-import java.nio.file.Paths;
-import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
+import static io.bazel.kotlin.builder.KotlinBuilderResource.KOTLIN_ANNOTATIONS;
+import static io.bazel.kotlin.builder.KotlinBuilderResource.KOTLIN_STDLIB;
 
-public class KotlinBuilderJvmTest extends KotlinBuilderTestCase {
+public class KotlinBuilderJvmTest {
+  private static Dep AUTO_VALUE =
+      Dep.simpleOf(
+          "external/io_bazel_rules_kotlin_com_google_auto_value_auto_value"
+              + "/jar/io_bazel_rules_kotlin_com_google_auto_value_auto_value.jar");
+
+  private static final AnnotationProcessor AUTO_VALUE_ANNOTATION_PROCESSOR =
+      AnnotationProcessor.newBuilder()
+          .setLabel("autovalue")
+          .setProcessorClass("com.google.auto.value.processor.AutoValueProcessor")
+          .addAllClasspath(Dep.classpathOf(AUTO_VALUE, KOTLIN_ANNOTATIONS))
+          .build();
+
+  private static final KotlinBuilderComponent component =
+      DaggerKotlinBuilderComponent.builder().toolchain(KotlinToolchain.createToolchain()).build();
+
+  @Rule public KotlinBuilderJvmTestTask ctx = new KotlinBuilderJvmTestTask();
+
   @Test
-  public void testSimpleCompile() {
-    addSource("AClass.kt", "package something;" + "class AClass{}");
-    runCompileTask();
-    assertFileExists(DirectoryType.CLASSES, "something/AClass.class");
+  public void testSimpleMixedModeCompile() {
+    ctx.addSource("AClass.kt", "package something;" + "class AClass{}");
+    ctx.addSource("AnotherClass.java", "package something;", "", "class AnotherClass{}");
+    ctx.runCompileTask(this::jvmCompilationTask);
+    ctx.assertFilesExist(
+        DirectoryType.CLASSES, "something/AClass.class", "something/AnotherClass.class");
   }
 
   @Test
-  public void testMixedModeCompile() {
-    addSource("AClass.kt", "package something;" + "class AClass{}");
-    addSource("AnotherClass.java", "package something;", "", "class AnotherClass{}");
-    runCompileTask();
-    assertFileExists(DirectoryType.CLASSES, "something/AClass.class");
-    assertFileExists(DirectoryType.CLASSES, "something/AnotherClass.class");
-    assertFileExists(outputs().getJar());
+  public void testMixedBiReferences() {
+    ctx.addSource(
+        "AClass.java",
+        "package a;",
+        "",
+        "import b.BClass;",
+        "",
+        "public class AClass {",
+        "  static BClass b = new BClass();",
+        "}");
+    ctx.addSource(
+        "BClass.kt",
+        "package b",
+        "",
+        "import a.AClass",
+        "",
+        "class BClass() {",
+        "  val a = AClass()",
+        "}");
+    ctx.runCompileTask(this::jvmCompilationTask);
+    ctx.assertFilesExist(DirectoryType.CLASSES, "a/AClass.class", "b/BClass.class");
   }
 
-  private void runCompileTask() {
-    JvmCompilationTask command = builderCommand();
-    for (DirectoryType directoryType : DirectoryType.values()) {
-      try {
-        if (directoryType != DirectoryType.ROOT) {
-          Files.createDirectories(DirectoryType.select(directoryType, command));
-        }
-      } catch (IOException e) {
-        throw new UncheckedIOException(e);
-      }
-    }
-    int timeoutSeconds = 10;
-    try {
+  @Test
+  public void testKaptKt() {
+    ctx.addSource(
+        "TestKtValue.kt",
+        "package autovalue\n"
+            + "\n"
+            + "import com.google.auto.value.AutoValue\n"
+            + "\n"
+            + "@AutoValue\n"
+            + "abstract class TestKtValue {\n"
+            + "    abstract fun name(): String\n"
+            + "    fun builder(): Builder = AutoValue_TestKtValue.Builder()\n"
+            + "\n"
+            + "    @AutoValue.Builder\n"
+            + "    abstract class Builder {\n"
+            + "        abstract fun setName(name: String): Builder\n"
+            + "        abstract fun build(): TestKtValue\n"
+            + "    }\n"
+            + "}");
+    ctx.addAnnotationProcessors(AUTO_VALUE_ANNOTATION_PROCESSOR);
+    ctx.addDirectDependencies(AUTO_VALUE, KOTLIN_STDLIB);
+    ctx.runCompileTask(this::jvmCompilationTask);
+    ctx.assertFilesExist(
+        DirectoryType.CLASSES,
+        "autovalue/TestKtValue.class",
+        "autovalue/AutoValue_TestKtValue.class");
+    ctx.assertFilesExist(DirectoryType.SOURCE_GEN, "autovalue/AutoValue_TestKtValue.java");
+  }
 
-      CompletableFuture.runAsync(
-              () ->
-                  component()
-                      .jvmTaskExecutor()
-                      .execute(new CompilationTaskContext(command.getInfo(), System.err), command))
-          .get(timeoutSeconds, TimeUnit.SECONDS);
-    } catch (TimeoutException e) {
-      throw new AssertionError("did not complete in: " + timeoutSeconds);
-    } catch (Exception e) {
-      throw new RuntimeException(e);
-    }
-    assertFileExists(outputs().getJar());
-    assertFileExists(outputs().getJdeps());
-    try (FileInputStream fs = new FileInputStream(Paths.get(outputs().getJdeps()).toFile())) {
-      Deps.Dependencies dependencies = Deps.Dependencies.parseFrom(fs);
-      Truth.assertThat(dependencies.getRuleLabel()).endsWith(label());
-    } catch (IOException e) {
-      throw new UncheckedIOException(e);
-    }
+  @Test
+  public void testMixedKaptBiReferences() {
+    ctx.addSource(
+        "TestKtValue.kt",
+        "package autovalue.a\n"
+            + "\n"
+            + "import com.google.auto.value.AutoValue\n"
+            + "import autovalue.b.TestAutoValue\n"
+            + "\n"
+            + "@AutoValue\n"
+            + "abstract class TestKtValue {\n"
+            + "    abstract fun name(): String\n"
+            + "    fun builder(): Builder = AutoValue_TestKtValue.Builder()\n"
+            + "\n"
+            + "    @AutoValue.Builder\n"
+            + "    abstract class Builder {\n"
+            + "        abstract fun setName(name: String): Builder\n"
+            + "        abstract fun build(): TestKtValue\n"
+            + "    }\n"
+            + "}");
+    ctx.addSource(
+        "TestAutoValue.java",
+        "package autovalue.b;\n"
+            + "\n"
+            + "import com.google.auto.value.AutoValue;\n"
+            + "import autovalue.a.TestKtValue;\n"
+            + "\n"
+            + "@AutoValue\n"
+            + "public abstract class TestAutoValue {\n"
+            + "    abstract String name();\n"
+            + "\n"
+            + "\n"
+            + "    static Builder builder() {\n"
+            + "        return new AutoValue_TestAutoValue.Builder();\n"
+            + "    }\n"
+            + "\n"
+            + "    @AutoValue.Builder\n"
+            + "    abstract static class Builder {\n"
+            + "        abstract Builder setName(String name);\n"
+            + "        abstract TestAutoValue build();\n"
+            + "    }\n"
+            + "\n"
+            + "}");
+
+    ctx.addAnnotationProcessors(AUTO_VALUE_ANNOTATION_PROCESSOR);
+    ctx.addDirectDependencies(AUTO_VALUE, KOTLIN_STDLIB);
+    ctx.runCompileTask(this::jvmCompilationTask);
+    ctx.assertFilesExist(
+        DirectoryType.SOURCE_GEN,
+        "autovalue/a/AutoValue_TestKtValue.java",
+        "autovalue/b/AutoValue_TestAutoValue.java");
+    ctx.assertFilesExist(
+        DirectoryType.CLASSES,
+        "autovalue/a/AutoValue_TestKtValue.class",
+        "autovalue/b/AutoValue_TestAutoValue.class");
+  }
+
+  @Test
+  @Ignore("The Kotlin compiler expects a single kotlin file at least.")
+  public void testCompileSingleJavaFile() {
+    ctx.addSource("AnotherClass.java", "package something;", "", "class AnotherClass{}");
+    ctx.runCompileTask(this::jvmCompilationTask);
+  }
+
+  @Test
+  @Ignore(
+      "This test needs to test the output of error handling by the compiler, when the kotlin compiler compiles java "
+          + "this test needs to validate it.")
+  public void testWithJavaError() {
+    ctx.addSource("AClass.kt", "package something;" + "class AClass{}");
+    ctx.addSource("AnotherClass.java", "package something;", "", "class AnotherClass{");
+    ctx.runCompileTask(this::jvmCompilationTask);
+  }
+
+  private void jvmCompilationTask(CompilationTaskContext taskContext, JvmCompilationTask task) {
+    component.jvmTaskExecutor().execute(taskContext, task);
+    ctx.assertFilesExist(task.getOutputs().getJar(), task.getOutputs().getJdeps());
   }
 }
diff --git a/src/test/kotlin/io/bazel/kotlin/defs.bzl b/src/test/kotlin/io/bazel/kotlin/defs.bzl
index 3272c8e..0415cc6 100644
--- a/src/test/kotlin/io/bazel/kotlin/defs.bzl
+++ b/src/test/kotlin/io/bazel/kotlin/defs.bzl
@@ -36,4 +36,4 @@
     kwargs.setdefault("size", "small")
     kwargs["deps"] = kwargs.setdefault("deps", []) + ["//src/test/kotlin/io/bazel/kotlin:assertion_test_case"]
     kwargs.setdefault("test_class", _get_class_name(kwargs))
-    kt_jvm_test(name = name, **kwargs)
\ No newline at end of file
+    kt_jvm_test(name = name, **kwargs)