From c8d3049c76905bfbc738476fa7db79fccd8fd5e3 Mon Sep 17 00:00:00 2001 From: Andy Scott Date: Sat, 25 Jan 2020 16:43:28 -0800 Subject: [PATCH 01/17] Add defs.bzl with core rules --- scala/defs.bzl | 40 ++++++++++++++++++++++++++++++++++++++++ test/v2/BUILD | 27 +++++++++++++++++++++++++++ test/v2/binary.scala | 7 +++++++ test/v2/library.scala | 6 ++++++ test/v2/test.scala | 13 +++++++++++++ 5 files changed, 93 insertions(+) create mode 100644 scala/defs.bzl create mode 100644 test/v2/BUILD create mode 100644 test/v2/binary.scala create mode 100644 test/v2/library.scala create mode 100644 test/v2/test.scala diff --git a/scala/defs.bzl b/scala/defs.bzl new file mode 100644 index 000000000..b228e7d32 --- /dev/null +++ b/scala/defs.bzl @@ -0,0 +1,40 @@ +""" +Starlark rules for building Scala projects. + +These are the core rules under active development. Their APIs are +not guaranteed stable and we anticipate some breaking changes. + +We do not recommend using these APIs for production codebases. Instead, +use the stable rules exported by scala.bzl: + +``` +load( + "@io_bazel_rules_scala//scala:scala.bzl", + "scala_library", + "scala_binary", + "scala_test" +) +``` + +""" + +load( + "@io_bazel_rules_scala//scala/private:rules/scala_binary.bzl", + _make_scala_binary = "make_scala_binary", +) +load( + "@io_bazel_rules_scala//scala/private:rules/scala_library.bzl", + _make_scala_library = "make_scala_library", +) +load( + "@io_bazel_rules_scala//scala/private:rules/scala_test.bzl", + _make_scala_test = "make_scala_test", +) + +make_scala_library = _make_scala_library +make_scala_binary = _make_scala_binary +make_scala_test = _make_scala_test + +scala_library = _make_scala_library() +scala_binary = _make_scala_binary() +scala_test = _make_scala_test() diff --git a/test/v2/BUILD b/test/v2/BUILD new file mode 100644 index 000000000..3d12756ad --- /dev/null +++ b/test/v2/BUILD @@ -0,0 +1,27 @@ +load( + "//scala:defs.bzl", + "scala_binary", + "scala_library", + "scala_test", +) + +scala_binary( + name = "binary", + srcs = ["binary.scala"], + main_class = "test.v2.Binary", + deps = [":library"], +) + +scala_library( + name = "library", + srcs = ["library.scala"], + deps = [], +) + +scala_test( + name = "test", + srcs = ["test.scala"], + deps = [ + ":library", + ], +) diff --git a/test/v2/binary.scala b/test/v2/binary.scala new file mode 100644 index 000000000..56486bffa --- /dev/null +++ b/test/v2/binary.scala @@ -0,0 +1,7 @@ +package test.v2 + +object Binary { + def main(args: Array[String]): Unit = { + println(s"${Library.method1} ${Library.method2}") + } +} diff --git a/test/v2/library.scala b/test/v2/library.scala new file mode 100644 index 000000000..8048a67ca --- /dev/null +++ b/test/v2/library.scala @@ -0,0 +1,6 @@ +package test.v2 + +object Library { + def method1(): String = "hello" + def method2(): String = "world" +} diff --git a/test/v2/test.scala b/test/v2/test.scala new file mode 100644 index 000000000..88985c7fb --- /dev/null +++ b/test/v2/test.scala @@ -0,0 +1,13 @@ +package test.v2 + +import org.scalatest.FunSuite + +class Test extends FunSuite { + test("method1") { + assert(Library.method1 == "hello") + } + + test("method2") { + assert(Library.method2 == "world") + } +} From 3f5630fb9c5d9f19fa1d740561a17bad509fbb0f Mon Sep 17 00:00:00 2001 From: Andy Scott Date: Sun, 26 Jan 2020 13:14:08 -0800 Subject: [PATCH 02/17] begin new test rules --- scala/defs.bzl | 2 +- scala/private/macros/scala_repositories.bzl | 9 ++ scala/private/phases/phase_discover_tests.bzl | 3 + scala/private/phases/phases.bzl | 4 + scala/private/rules/unstable_scala_test.bzl | 90 +++++++++++++++++++ test/v2/BUILD | 1 + 6 files changed, 108 insertions(+), 1 deletion(-) create mode 100644 scala/private/phases/phase_discover_tests.bzl create mode 100644 scala/private/rules/unstable_scala_test.bzl diff --git a/scala/defs.bzl b/scala/defs.bzl index b228e7d32..3348963d9 100644 --- a/scala/defs.bzl +++ b/scala/defs.bzl @@ -27,7 +27,7 @@ load( _make_scala_library = "make_scala_library", ) load( - "@io_bazel_rules_scala//scala/private:rules/scala_test.bzl", + "@io_bazel_rules_scala//scala/private:rules/unstable_scala_test.bzl", _make_scala_test = "make_scala_test", ) diff --git a/scala/private/macros/scala_repositories.bzl b/scala/private/macros/scala_repositories.bzl index ff8017f37..7aed41587 100644 --- a/scala/private/macros/scala_repositories.bzl +++ b/scala/private/macros/scala_repositories.bzl @@ -130,6 +130,15 @@ def scala_repositories( server_urls = maven_servers, ) + # used by the experimental scala_test rule + _scala_maven_import_external( + name = "io_bazel_rules_scala_classgraph", + artifact = "io.github.classgraph:classgraph:jar:4.8.60", + artifact_sha256 = "dacf7d7fec4088e674ee98155adbb74f30af2f8b64f8990d37c223d8b9047b72", + licenses = ["notice"], + server_urls = maven_servers, + ) + if not native.existing_rule("com_google_protobuf"): http_archive( name = "com_google_protobuf", diff --git a/scala/private/phases/phase_discover_tests.bzl b/scala/private/phases/phase_discover_tests.bzl new file mode 100644 index 000000000..b89c6535c --- /dev/null +++ b/scala/private/phases/phase_discover_tests.bzl @@ -0,0 +1,3 @@ +def phase_discover_tests(ctx, p): + print('discover tests') + pass diff --git a/scala/private/phases/phases.bzl b/scala/private/phases/phases.bzl index 2cba42bde..ba3168975 100644 --- a/scala/private/phases/phases.bzl +++ b/scala/private/phases/phases.bzl @@ -60,6 +60,7 @@ load("@io_bazel_rules_scala//scala/private:phases/phase_declare_executable.bzl", load("@io_bazel_rules_scala//scala/private:phases/phase_merge_jars.bzl", _phase_merge_jars = "phase_merge_jars") load("@io_bazel_rules_scala//scala/private:phases/phase_jvm_flags.bzl", _phase_jvm_flags = "phase_jvm_flags") load("@io_bazel_rules_scala//scala/private:phases/phase_coverage_runfiles.bzl", _phase_coverage_runfiles = "phase_coverage_runfiles") +load("@io_bazel_rules_scala//scala/private:phases/phase_discover_tests.bzl", _phase_discover_tests = "phase_discover_tests") # API run_phases = _run_phases @@ -129,3 +130,6 @@ phase_runfiles_common = _phase_runfiles_common phase_default_info_binary = _phase_default_info_binary phase_default_info_library = _phase_default_info_library phase_default_info_scalatest = _phase_default_info_scalatest + + # discover_tests +phase_discover_tests = _phase_discover_tests diff --git a/scala/private/rules/unstable_scala_test.bzl b/scala/private/rules/unstable_scala_test.bzl new file mode 100644 index 000000000..31dca0454 --- /dev/null +++ b/scala/private/rules/unstable_scala_test.bzl @@ -0,0 +1,90 @@ +"""Rules for writing tests with ScalaTest""" + +load("@bazel_skylib//lib:dicts.bzl", _dicts = "dicts") +load( + "@io_bazel_rules_scala//scala/private:common_attributes.bzl", + "common_attrs", + "implicit_deps", + "launcher_template", +) +load("@io_bazel_rules_scala//scala/private:common.bzl", "sanitize_string_for_usage") +load("@io_bazel_rules_scala//scala/private:common_outputs.bzl", "common_outputs") +load( + "@io_bazel_rules_scala//scala/private:phases/phases.bzl", + "extras_phases", + "phase_collect_jars_common", + "phase_compile_scalatest", + "phase_coverage_runfiles", + "phase_declare_executable", + "phase_default_info_scalatest", + "phase_java_wrapper_common", + "phase_merge_jars", + "phase_runfiles_common", + "phase_scalac_provider", + "phase_unused_deps_checker", + "phase_discover_tests", + "phase_write_manifest", + "phase_write_executable_common", + "run_phases", +) + +def _scala_test_impl(ctx): + return run_phases( + ctx, + # customizable phases + [ + ("scalac_provider", phase_scalac_provider), + ("write_manifest", phase_write_manifest), + ("unused_deps_checker", phase_unused_deps_checker), + ("collect_jars", phase_collect_jars_common), + ("java_wrapper", phase_java_wrapper_common), + ("declare_executable", phase_declare_executable), + # no need to build an ijar for an executable + ("compile", phase_compile_scalatest), + ("merge_jars", phase_merge_jars), + ("discover_tests", phase_discover_tests), + ("runfiles", phase_runfiles_common), + ("coverage_runfiles", phase_coverage_runfiles), + ("write_executable", phase_write_executable_common), + ("default_info", phase_default_info_scalatest), + ], + ) + +_scala_test_attrs = { + "main_class": attr.string( + default = "io.bazel.rulesscala.scala_test.Runner", + ), + "colors": attr.bool(default = True), + "full_stacktraces": attr.bool(default = True), + "jvm_flags": attr.string_list(), + "_jacocorunner": attr.label( + default = Label("@bazel_tools//tools/jdk:JacocoCoverage"), + ), + "_lcov_merger": attr.label( + default = Label("@bazel_tools//tools/test/CoverageOutputGenerator/java/com/google/devtools/coverageoutputgenerator:Main"), + ), +} + +_scala_test_attrs.update(launcher_template) + +_scala_test_attrs.update(implicit_deps) + +_scala_test_attrs.update(common_attrs) + +def make_scala_test(*extras): + return rule( + attrs = _dicts.add( + _scala_test_attrs, + extras_phases(extras), + *[extra["attrs"] for extra in extras if "attrs" in extra] + ), + executable = True, + fragments = ["java"], + outputs = _dicts.add( + common_outputs, + *[extra["outputs"] for extra in extras if "outputs" in extra] + ), + test = True, + toolchains = ["@io_bazel_rules_scala//scala:toolchain_type"], + implementation = _scala_test_impl, + ) diff --git a/test/v2/BUILD b/test/v2/BUILD index 3d12756ad..8ae220b83 100644 --- a/test/v2/BUILD +++ b/test/v2/BUILD @@ -23,5 +23,6 @@ scala_test( srcs = ["test.scala"], deps = [ ":library", + "//external:io_bazel_rules_scala/dependency/scalatest/scalatest", ], ) From 68b613cd0da33bf5de628bb35c01f60c623e2583 Mon Sep 17 00:00:00 2001 From: Andy Scott Date: Sun, 26 Jan 2020 16:47:41 -0800 Subject: [PATCH 03/17] unify all default infos --- scala/private/phases/phase_compile.bzl | 1 + .../phases/phase_coverage_runfiles.bzl | 1 + .../phases/phase_declare_executable.bzl | 8 +- scala/private/phases/phase_default_info.bzl | 48 +++++---- scala/private/phases/phase_discover_tests.bzl | 26 ++++- scala/private/phases/phase_runfiles.bzl | 7 +- .../private/phases/phase_write_executable.bzl | 2 +- scala/private/phases/phases.bzl | 11 +- scala/private/rules/scala_binary.bzl | 4 +- scala/private/rules/scala_junit_test.bzl | 4 +- scala/private/rules/scala_library.bzl | 8 +- scala/private/rules/scala_repl.bzl | 4 +- scala/private/rules/scala_test.bzl | 4 +- scala/private/rules/unstable_scala_test.bzl | 7 +- src/java/io/bazel/rules_scala/worker/BUILD | 8 ++ .../io/bazel/rules_scala/worker/Worker.java | 102 ++++++++++++++++++ .../rules_scala/discover_tests_worker/BUILD | 12 +++ .../DiscoverTestsWorker.scala | 16 +++ 18 files changed, 218 insertions(+), 55 deletions(-) create mode 100644 src/java/io/bazel/rules_scala/worker/BUILD create mode 100644 src/java/io/bazel/rules_scala/worker/Worker.java create mode 100644 src/scala/io/bazel/rules_scala/discover_tests_worker/BUILD create mode 100644 src/scala/io/bazel/rules_scala/discover_tests_worker/DiscoverTestsWorker.scala diff --git a/scala/private/phases/phase_compile.bzl b/scala/private/phases/phase_compile.bzl index d5b72ea3c..c9ea6f4e5 100644 --- a/scala/private/phases/phase_compile.bzl +++ b/scala/private/phases/phase_compile.bzl @@ -165,6 +165,7 @@ def _phase_compile( class_jar = out.class_jar, coverage = out.coverage.external, full_jars = out.full_jars, + files = depset(out.full_jars), ijar = out.ijar, ijars = out.ijars, rjars = depset(out.full_jars, transitive = [rjars]), diff --git a/scala/private/phases/phase_coverage_runfiles.bzl b/scala/private/phases/phase_coverage_runfiles.bzl index 00e50cf44..74c8173d4 100644 --- a/scala/private/phases/phase_coverage_runfiles.bzl +++ b/scala/private/phases/phase_coverage_runfiles.bzl @@ -24,5 +24,6 @@ def phase_coverage_runfiles(ctx, p): coverage_runfiles = ctx.files._jacocorunner + ctx.files._lcov_merger + coverage_replacements.values() return struct( coverage_runfiles = coverage_runfiles, + runfiles = depset(coverage_runfiles), rjars = rjars, ) diff --git a/scala/private/phases/phase_declare_executable.bzl b/scala/private/phases/phase_declare_executable.bzl index c7bce9a20..749a20f2c 100644 --- a/scala/private/phases/phase_declare_executable.bzl +++ b/scala/private/phases/phase_declare_executable.bzl @@ -10,6 +10,10 @@ load( def phase_declare_executable(ctx, p): if (is_windows(ctx)): - return ctx.actions.declare_file("%s.exe" % ctx.label.name) + return struct( + executable = ctx.actions.declare_file("%s.exe" % ctx.label.name), + ) else: - return ctx.actions.declare_file(ctx.label.name) + return struct( + executable = ctx.actions.declare_file(ctx.label.name), + ) diff --git a/scala/private/phases/phase_default_info.bzl b/scala/private/phases/phase_default_info.bzl index 8847e8a35..cc789a3c4 100644 --- a/scala/private/phases/phase_default_info.bzl +++ b/scala/private/phases/phase_default_info.bzl @@ -3,34 +3,36 @@ # # DOCUMENT THIS # -def phase_default_info_binary(ctx, p): - return struct( - external_providers = { - "DefaultInfo": DefaultInfo( - executable = p.declare_executable, - files = depset([p.declare_executable] + p.compile.full_jars), - runfiles = p.runfiles.runfiles, - ), - }, - ) -def phase_default_info_library(ctx, p): - return struct( - external_providers = { - "DefaultInfo": DefaultInfo( - files = depset(p.compile.full_jars), - runfiles = p.runfiles.runfiles, - ), - }, - ) +def phase_default_info(ctx, p): + executable = None + files = [] + runfiles = [] + + phase_names = dir(p) + phase_names.remove("to_json") + phase_names.remove("to_proto") + for phase_name in phase_names: + phase = getattr(p, phase_name) + + if hasattr(phase, "executable"): + if executable == None: + executable = phase.executable + else: + fail("only one executable may be provided") + + if hasattr(phase, "files"): + files.append(phase.files) + + if hasattr(phase, "runfiles"): + runfiles.append(phase.runfiles) -def phase_default_info_scalatest(ctx, p): return struct( external_providers = { "DefaultInfo": DefaultInfo( - executable = p.declare_executable, - files = depset([p.declare_executable] + p.compile.full_jars), - runfiles = ctx.runfiles(p.coverage_runfiles.coverage_runfiles, transitive_files = p.runfiles.runfiles.files), + executable = executable, + files = depset(transitive = files), + runfiles = ctx.runfiles(transitive_files = depset(transitive = runfiles)), ), }, ) diff --git a/scala/private/phases/phase_discover_tests.bzl b/scala/private/phases/phase_discover_tests.bzl index b89c6535c..d564933dd 100644 --- a/scala/private/phases/phase_discover_tests.bzl +++ b/scala/private/phases/phase_discover_tests.bzl @@ -1,3 +1,27 @@ def phase_discover_tests(ctx, p): print('discover tests') - pass + + worker = ctx.attr._discover_tests_worker + worker_inputs, _, worker_input_manifests = ctx.resolve_command( + tools = [worker], + ) + + output = ctx.actions.declare_file("{}_discovered_tests.txt".format(ctx.label.name)) + + args = ctx.actions.args() + args.set_param_file_format("multiline") + args.use_param_file("@%s", use_always = True) + + ctx.actions.run( + mnemonic = "DiscoverTests", + inputs = [] + worker_inputs, + outputs = [output], + executable = worker.files_to_run.executable, + input_manifests = worker_input_manifests, + execution_requirements = {"supports-workers": "1"}, + arguments = [args], + ) + + return struct( + files = depset([output]), + ) diff --git a/scala/private/phases/phase_runfiles.bzl b/scala/private/phases/phase_runfiles.bzl index c2b164222..741c15293 100644 --- a/scala/private/phases/phase_runfiles.bzl +++ b/scala/private/phases/phase_runfiles.bzl @@ -38,7 +38,7 @@ def _phase_runfiles_default(ctx, p, _args = struct()): return _phase_runfiles( ctx, _args.transitive_files if hasattr(_args, "transitive_files") else depset( - [p.declare_executable, p.java_wrapper] + ctx.files._java_runtime, + [p.java_wrapper] + ctx.files._java_runtime, transitive = [p.compile.rjars], ), _args.args_file if hasattr(_args, "args_file") else None, @@ -49,10 +49,7 @@ def _phase_runfiles( transitive_files, args_file): return struct( - runfiles = ctx.runfiles( - transitive_files = transitive_files, - collect_data = True, - ), + runfiles = transitive_files, args_file = args_file, ) diff --git a/scala/private/phases/phase_write_executable.bzl b/scala/private/phases/phase_write_executable.bzl index bae55fa99..3c8c11fbc 100644 --- a/scala/private/phases/phase_write_executable.bzl +++ b/scala/private/phases/phase_write_executable.bzl @@ -67,7 +67,7 @@ def _phase_write_executable( jvm_flags, use_jacoco, main_class): - executable = p.declare_executable + executable = p.declare_executable.executable wrapper = p.java_wrapper if (is_windows(ctx)): diff --git a/scala/private/phases/phases.bzl b/scala/private/phases/phases.bzl index ba3168975..32c8e33c5 100644 --- a/scala/private/phases/phases.bzl +++ b/scala/private/phases/phases.bzl @@ -45,12 +45,7 @@ load( _phase_runfiles_library = "phase_runfiles_library", _phase_runfiles_scalatest = "phase_runfiles_scalatest", ) -load( - "@io_bazel_rules_scala//scala/private:phases/phase_default_info.bzl", - _phase_default_info_binary = "phase_default_info_binary", - _phase_default_info_library = "phase_default_info_library", - _phase_default_info_scalatest = "phase_default_info_scalatest", -) +load("@io_bazel_rules_scala//scala/private:phases/phase_default_info.bzl", _phase_default_info = "phase_default_info") load("@io_bazel_rules_scala//scala/private:phases/phase_scalac_provider.bzl", _phase_scalac_provider = "phase_scalac_provider") load("@io_bazel_rules_scala//scala/private:phases/phase_write_manifest.bzl", _phase_write_manifest = "phase_write_manifest") load("@io_bazel_rules_scala//scala/private:phases/phase_collect_srcjars.bzl", _phase_collect_srcjars = "phase_collect_srcjars") @@ -127,9 +122,7 @@ phase_runfiles_scalatest = _phase_runfiles_scalatest phase_runfiles_common = _phase_runfiles_common # default_info -phase_default_info_binary = _phase_default_info_binary -phase_default_info_library = _phase_default_info_library -phase_default_info_scalatest = _phase_default_info_scalatest +phase_default_info = _phase_default_info # discover_tests phase_discover_tests = _phase_discover_tests diff --git a/scala/private/rules/scala_binary.bzl b/scala/private/rules/scala_binary.bzl index ec380a1ba..07afd2574 100644 --- a/scala/private/rules/scala_binary.bzl +++ b/scala/private/rules/scala_binary.bzl @@ -15,7 +15,7 @@ load( "phase_collect_jars_common", "phase_compile_binary", "phase_declare_executable", - "phase_default_info_binary", + "phase_default_info", "phase_java_wrapper_common", "phase_merge_jars", "phase_runfiles_common", @@ -42,7 +42,7 @@ def _scala_binary_impl(ctx): ("merge_jars", phase_merge_jars), ("runfiles", phase_runfiles_common), ("write_executable", phase_write_executable_common), - ("default_info", phase_default_info_binary), + ("default_info", phase_default_info), ], ) diff --git a/scala/private/rules/scala_junit_test.bzl b/scala/private/rules/scala_junit_test.bzl index b98f8d9c3..8b58180b7 100644 --- a/scala/private/rules/scala_junit_test.bzl +++ b/scala/private/rules/scala_junit_test.bzl @@ -14,7 +14,7 @@ load( "phase_collect_jars_junit_test", "phase_compile_junit_test", "phase_declare_executable", - "phase_default_info_binary", + "phase_default_info", "phase_java_wrapper_common", "phase_jvm_flags", "phase_merge_jars", @@ -47,7 +47,7 @@ def _scala_junit_test_impl(ctx): ("runfiles", phase_runfiles_common), ("jvm_flags", phase_jvm_flags), ("write_executable", phase_write_executable_junit_test), - ("default_info", phase_default_info_binary), + ("default_info", phase_default_info), ], ) diff --git a/scala/private/rules/scala_library.bzl b/scala/private/rules/scala_library.bzl index 2098851f7..17356b843 100644 --- a/scala/private/rules/scala_library.bzl +++ b/scala/private/rules/scala_library.bzl @@ -26,7 +26,7 @@ load( "phase_compile_library", "phase_compile_library_for_plugin_bootstrapping", "phase_compile_macro_library", - "phase_default_info_library", + "phase_default_info", "phase_merge_jars", "phase_runfiles_library", "phase_scalac_provider", @@ -66,7 +66,7 @@ def _scala_library_impl(ctx): ("merge_jars", phase_merge_jars), ("runfiles", phase_runfiles_library), ("collect_exports_jars", phase_collect_exports_jars), - ("default_info", phase_default_info_library), + ("default_info", phase_default_info), ], ) @@ -142,7 +142,7 @@ def _scala_library_for_plugin_bootstrapping_impl(ctx): ("merge_jars", phase_merge_jars), ("runfiles", phase_runfiles_library), ("collect_exports_jars", phase_collect_exports_jars), - ("default_info", phase_default_info_library), + ("default_info", phase_default_info), ], ) @@ -197,7 +197,7 @@ def _scala_macro_library_impl(ctx): ("merge_jars", phase_merge_jars), ("runfiles", phase_runfiles_library), ("collect_exports_jars", phase_collect_exports_jars), - ("default_info", phase_default_info_library), + ("default_info", phase_default_info), ], ) diff --git a/scala/private/rules/scala_repl.bzl b/scala/private/rules/scala_repl.bzl index 1550b0c9d..3d508af45 100644 --- a/scala/private/rules/scala_repl.bzl +++ b/scala/private/rules/scala_repl.bzl @@ -15,7 +15,7 @@ load( "phase_collect_jars_repl", "phase_compile_repl", "phase_declare_executable", - "phase_default_info_binary", + "phase_default_info", "phase_java_wrapper_repl", "phase_merge_jars", "phase_runfiles_common", @@ -43,7 +43,7 @@ def _scala_repl_impl(ctx): ("merge_jars", phase_merge_jars), ("runfiles", phase_runfiles_common), ("write_executable", phase_write_executable_repl), - ("default_info", phase_default_info_binary), + ("default_info", phase_default_info), ], ) diff --git a/scala/private/rules/scala_test.bzl b/scala/private/rules/scala_test.bzl index d9d42ead9..1a6bd3e7d 100644 --- a/scala/private/rules/scala_test.bzl +++ b/scala/private/rules/scala_test.bzl @@ -16,7 +16,7 @@ load( "phase_compile_scalatest", "phase_coverage_runfiles", "phase_declare_executable", - "phase_default_info_scalatest", + "phase_default_info", "phase_java_wrapper_common", "phase_merge_jars", "phase_runfiles_scalatest", @@ -44,7 +44,7 @@ def _scala_test_impl(ctx): ("runfiles", phase_runfiles_scalatest), ("coverage_runfiles", phase_coverage_runfiles), ("write_executable", phase_write_executable_scalatest), - ("default_info", phase_default_info_scalatest), + ("default_info", phase_default_info), ], ) diff --git a/scala/private/rules/unstable_scala_test.bzl b/scala/private/rules/unstable_scala_test.bzl index 31dca0454..ad4d272ea 100644 --- a/scala/private/rules/unstable_scala_test.bzl +++ b/scala/private/rules/unstable_scala_test.bzl @@ -16,7 +16,7 @@ load( "phase_compile_scalatest", "phase_coverage_runfiles", "phase_declare_executable", - "phase_default_info_scalatest", + "phase_default_info", "phase_java_wrapper_common", "phase_merge_jars", "phase_runfiles_common", @@ -46,7 +46,7 @@ def _scala_test_impl(ctx): ("runfiles", phase_runfiles_common), ("coverage_runfiles", phase_coverage_runfiles), ("write_executable", phase_write_executable_common), - ("default_info", phase_default_info_scalatest), + ("default_info", phase_default_info), ], ) @@ -63,6 +63,9 @@ _scala_test_attrs = { "_lcov_merger": attr.label( default = Label("@bazel_tools//tools/test/CoverageOutputGenerator/java/com/google/devtools/coverageoutputgenerator:Main"), ), + "_discover_tests_worker": attr.label( + default = Label("@io_bazel_rules_scala//src/scala/io/bazel/rules_scala/discover_tests_worker"), + ), } _scala_test_attrs.update(launcher_template) diff --git a/src/java/io/bazel/rules_scala/worker/BUILD b/src/java/io/bazel/rules_scala/worker/BUILD new file mode 100644 index 000000000..afb2648be --- /dev/null +++ b/src/java/io/bazel/rules_scala/worker/BUILD @@ -0,0 +1,8 @@ +java_library( + name = "worker", + srcs = ["Worker.java"], + deps = [ + "@io_bazel_rules_scala//src/java/com/google/devtools/build/lib:worker", + ], + visibility = ["//src:__subpackages__"] +) \ No newline at end of file diff --git a/src/java/io/bazel/rules_scala/worker/Worker.java b/src/java/io/bazel/rules_scala/worker/Worker.java new file mode 100644 index 000000000..8eca8ec0a --- /dev/null +++ b/src/java/io/bazel/rules_scala/worker/Worker.java @@ -0,0 +1,102 @@ +package io.bazel.rules_scala.worker; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.PrintStream; +import java.lang.SecurityManager; +import java.security.Permission; +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import com.google.devtools.build.lib.worker.WorkerProtocol; + +public final class Worker { + + public static interface Interface { + public void work(String args[]); + } + + final static class ExitTrapped extends RuntimeException { + final int code; + ExitTrapped(int code) { + super(); + this.code = code; + } + } + + private static final Pattern exitPattern = + Pattern.compile("exitVM\\.(-?\\d+)"); + + public static void workerMain(String workerArgs[], Interface workerInterface) { + if (workerArgs.length > 0 && workerArgs[0].equals("--persistent_worker")) { + + System.setSecurityManager(new SecurityManager() { + @Override + public void checkPermission(Permission permission) { + Matcher matcher = exitPattern.matcher(permission.getName()); + if (matcher.find()) + throw new ExitTrapped(Integer.parseInt(matcher.group(1))); + } + }); + + InputStream stdin = System.in; + PrintStream stdout = System.out; + PrintStream stderr = System.err; + ByteArrayOutputStream outStream = new ByteArrayOutputStream(); + PrintStream out = new PrintStream(outStream); + + System.setIn(new ByteArrayInputStream(new byte[0])); + System.setOut(out); + System.setErr(out); + + try { + while (true) { + WorkerProtocol.WorkRequest request = + WorkerProtocol.WorkRequest.parseDelimitedFrom(stdin); + + int code = 0; + + try { + List argList = request.getArgumentsList(); + int numArgs = argList.size(); + String[] args = new String[numArgs]; + for (int i = 0; i < numArgs; i++) { + args[i] = argList.get(i); + } + workerInterface.work(args); + } catch (ExitTrapped e) { + code = e.code; + } catch (Exception e) { + System.err.println(e.getMessage()); + e.printStackTrace(); + code = 1; + } + + WorkerProtocol.WorkResponse.newBuilder() + .setOutput(outStream.toString()) + .setExitCode(code) + .build() + .writeDelimitedTo(stdout); + + out.flush(); + outStream.reset(); + } + } catch (IOException e) { + } finally { + System.setIn(stdin); + System.setOut(stdout); + System.setErr(stderr); + } + } else { + workerInterface.work(workerArgs); + } + } +} diff --git a/src/scala/io/bazel/rules_scala/discover_tests_worker/BUILD b/src/scala/io/bazel/rules_scala/discover_tests_worker/BUILD new file mode 100644 index 000000000..10a077f78 --- /dev/null +++ b/src/scala/io/bazel/rules_scala/discover_tests_worker/BUILD @@ -0,0 +1,12 @@ +load("//scala:defs.bzl", "scala_binary") + +scala_binary( + name = "discover_tests_worker", + srcs = ["DiscoverTestsWorker.scala"], + main_class = "io.bazelr.rules_scala.discover_tests_worker.DiscoverTestsWorker", + deps = [ + "@io_bazel_rules_scala_classgraph//jar:jar", + "@io_bazel_rules_scala//src/java/io/bazel/rules_scala/worker", + ], + visibility = ["//visibility:public"], +) diff --git a/src/scala/io/bazel/rules_scala/discover_tests_worker/DiscoverTestsWorker.scala b/src/scala/io/bazel/rules_scala/discover_tests_worker/DiscoverTestsWorker.scala new file mode 100644 index 000000000..802702884 --- /dev/null +++ b/src/scala/io/bazel/rules_scala/discover_tests_worker/DiscoverTestsWorker.scala @@ -0,0 +1,16 @@ +package io.bazel.rules_scala.test_discovery + +import io.bazel.rules_scala.worker.Worker + +import io.github.classgraph.ClassGraph + +object TestDiscoveryWorker extends Worker.Interface { + + def main(args: Array[String]): Unit = Worker.workerMain(args, TestDiscoveryWorker) + + def work(args: Array[String]): Unit = { + println("WORK IT") + + val cg = new ClassGraph() + } +} From 082f8154f7fa54fbdf78ed65d12484c996d1d0f6 Mon Sep 17 00:00:00 2001 From: Andy Scott Date: Sun, 26 Jan 2020 16:54:13 -0800 Subject: [PATCH 04/17] unify all of the default info phases implementations --- scala/private/phases/phase_compile.bzl | 1 + .../phases/phase_coverage_runfiles.bzl | 1 + .../phases/phase_declare_executable.bzl | 8 +++- scala/private/phases/phase_default_info.bzl | 48 ++++++++++--------- scala/private/phases/phase_runfiles.bzl | 9 ++-- .../private/phases/phase_write_executable.bzl | 2 +- scala/private/phases/phases.bzl | 11 +---- scala/private/rules/scala_binary.bzl | 4 +- scala/private/rules/scala_junit_test.bzl | 4 +- scala/private/rules/scala_library.bzl | 8 ++-- scala/private/rules/scala_repl.bzl | 4 +- scala/private/rules/scala_test.bzl | 4 +- 12 files changed, 51 insertions(+), 53 deletions(-) diff --git a/scala/private/phases/phase_compile.bzl b/scala/private/phases/phase_compile.bzl index d5b72ea3c..c9ea6f4e5 100644 --- a/scala/private/phases/phase_compile.bzl +++ b/scala/private/phases/phase_compile.bzl @@ -165,6 +165,7 @@ def _phase_compile( class_jar = out.class_jar, coverage = out.coverage.external, full_jars = out.full_jars, + files = depset(out.full_jars), ijar = out.ijar, ijars = out.ijars, rjars = depset(out.full_jars, transitive = [rjars]), diff --git a/scala/private/phases/phase_coverage_runfiles.bzl b/scala/private/phases/phase_coverage_runfiles.bzl index 00e50cf44..74c8173d4 100644 --- a/scala/private/phases/phase_coverage_runfiles.bzl +++ b/scala/private/phases/phase_coverage_runfiles.bzl @@ -24,5 +24,6 @@ def phase_coverage_runfiles(ctx, p): coverage_runfiles = ctx.files._jacocorunner + ctx.files._lcov_merger + coverage_replacements.values() return struct( coverage_runfiles = coverage_runfiles, + runfiles = depset(coverage_runfiles), rjars = rjars, ) diff --git a/scala/private/phases/phase_declare_executable.bzl b/scala/private/phases/phase_declare_executable.bzl index c7bce9a20..749a20f2c 100644 --- a/scala/private/phases/phase_declare_executable.bzl +++ b/scala/private/phases/phase_declare_executable.bzl @@ -10,6 +10,10 @@ load( def phase_declare_executable(ctx, p): if (is_windows(ctx)): - return ctx.actions.declare_file("%s.exe" % ctx.label.name) + return struct( + executable = ctx.actions.declare_file("%s.exe" % ctx.label.name), + ) else: - return ctx.actions.declare_file(ctx.label.name) + return struct( + executable = ctx.actions.declare_file(ctx.label.name), + ) diff --git a/scala/private/phases/phase_default_info.bzl b/scala/private/phases/phase_default_info.bzl index 8847e8a35..cc789a3c4 100644 --- a/scala/private/phases/phase_default_info.bzl +++ b/scala/private/phases/phase_default_info.bzl @@ -3,34 +3,36 @@ # # DOCUMENT THIS # -def phase_default_info_binary(ctx, p): - return struct( - external_providers = { - "DefaultInfo": DefaultInfo( - executable = p.declare_executable, - files = depset([p.declare_executable] + p.compile.full_jars), - runfiles = p.runfiles.runfiles, - ), - }, - ) -def phase_default_info_library(ctx, p): - return struct( - external_providers = { - "DefaultInfo": DefaultInfo( - files = depset(p.compile.full_jars), - runfiles = p.runfiles.runfiles, - ), - }, - ) +def phase_default_info(ctx, p): + executable = None + files = [] + runfiles = [] + + phase_names = dir(p) + phase_names.remove("to_json") + phase_names.remove("to_proto") + for phase_name in phase_names: + phase = getattr(p, phase_name) + + if hasattr(phase, "executable"): + if executable == None: + executable = phase.executable + else: + fail("only one executable may be provided") + + if hasattr(phase, "files"): + files.append(phase.files) + + if hasattr(phase, "runfiles"): + runfiles.append(phase.runfiles) -def phase_default_info_scalatest(ctx, p): return struct( external_providers = { "DefaultInfo": DefaultInfo( - executable = p.declare_executable, - files = depset([p.declare_executable] + p.compile.full_jars), - runfiles = ctx.runfiles(p.coverage_runfiles.coverage_runfiles, transitive_files = p.runfiles.runfiles.files), + executable = executable, + files = depset(transitive = files), + runfiles = ctx.runfiles(transitive_files = depset(transitive = runfiles)), ), }, ) diff --git a/scala/private/phases/phase_runfiles.bzl b/scala/private/phases/phase_runfiles.bzl index c2b164222..03ab539a8 100644 --- a/scala/private/phases/phase_runfiles.bzl +++ b/scala/private/phases/phase_runfiles.bzl @@ -24,7 +24,7 @@ def phase_runfiles_scalatest(ctx, p): args = struct( transitive_files = depset( - [p.declare_executable, p.java_wrapper] + ctx.files._java_runtime + runfiles_ext, + [p.java_wrapper] + ctx.files._java_runtime + runfiles_ext, transitive = [p.compile.rjars], ), args_file = args_file, @@ -38,7 +38,7 @@ def _phase_runfiles_default(ctx, p, _args = struct()): return _phase_runfiles( ctx, _args.transitive_files if hasattr(_args, "transitive_files") else depset( - [p.declare_executable, p.java_wrapper] + ctx.files._java_runtime, + [p.java_wrapper] + ctx.files._java_runtime, transitive = [p.compile.rjars], ), _args.args_file if hasattr(_args, "args_file") else None, @@ -49,10 +49,7 @@ def _phase_runfiles( transitive_files, args_file): return struct( - runfiles = ctx.runfiles( - transitive_files = transitive_files, - collect_data = True, - ), + runfiles = transitive_files, args_file = args_file, ) diff --git a/scala/private/phases/phase_write_executable.bzl b/scala/private/phases/phase_write_executable.bzl index bae55fa99..3c8c11fbc 100644 --- a/scala/private/phases/phase_write_executable.bzl +++ b/scala/private/phases/phase_write_executable.bzl @@ -67,7 +67,7 @@ def _phase_write_executable( jvm_flags, use_jacoco, main_class): - executable = p.declare_executable + executable = p.declare_executable.executable wrapper = p.java_wrapper if (is_windows(ctx)): diff --git a/scala/private/phases/phases.bzl b/scala/private/phases/phases.bzl index 2cba42bde..1cf5ab7f4 100644 --- a/scala/private/phases/phases.bzl +++ b/scala/private/phases/phases.bzl @@ -45,12 +45,7 @@ load( _phase_runfiles_library = "phase_runfiles_library", _phase_runfiles_scalatest = "phase_runfiles_scalatest", ) -load( - "@io_bazel_rules_scala//scala/private:phases/phase_default_info.bzl", - _phase_default_info_binary = "phase_default_info_binary", - _phase_default_info_library = "phase_default_info_library", - _phase_default_info_scalatest = "phase_default_info_scalatest", -) +load("@io_bazel_rules_scala//scala/private:phases/phase_default_info.bzl", _phase_default_info = "phase_default_info") load("@io_bazel_rules_scala//scala/private:phases/phase_scalac_provider.bzl", _phase_scalac_provider = "phase_scalac_provider") load("@io_bazel_rules_scala//scala/private:phases/phase_write_manifest.bzl", _phase_write_manifest = "phase_write_manifest") load("@io_bazel_rules_scala//scala/private:phases/phase_collect_srcjars.bzl", _phase_collect_srcjars = "phase_collect_srcjars") @@ -126,6 +121,4 @@ phase_runfiles_scalatest = _phase_runfiles_scalatest phase_runfiles_common = _phase_runfiles_common # default_info -phase_default_info_binary = _phase_default_info_binary -phase_default_info_library = _phase_default_info_library -phase_default_info_scalatest = _phase_default_info_scalatest +phase_default_info = _phase_default_info diff --git a/scala/private/rules/scala_binary.bzl b/scala/private/rules/scala_binary.bzl index ec380a1ba..07afd2574 100644 --- a/scala/private/rules/scala_binary.bzl +++ b/scala/private/rules/scala_binary.bzl @@ -15,7 +15,7 @@ load( "phase_collect_jars_common", "phase_compile_binary", "phase_declare_executable", - "phase_default_info_binary", + "phase_default_info", "phase_java_wrapper_common", "phase_merge_jars", "phase_runfiles_common", @@ -42,7 +42,7 @@ def _scala_binary_impl(ctx): ("merge_jars", phase_merge_jars), ("runfiles", phase_runfiles_common), ("write_executable", phase_write_executable_common), - ("default_info", phase_default_info_binary), + ("default_info", phase_default_info), ], ) diff --git a/scala/private/rules/scala_junit_test.bzl b/scala/private/rules/scala_junit_test.bzl index b98f8d9c3..8b58180b7 100644 --- a/scala/private/rules/scala_junit_test.bzl +++ b/scala/private/rules/scala_junit_test.bzl @@ -14,7 +14,7 @@ load( "phase_collect_jars_junit_test", "phase_compile_junit_test", "phase_declare_executable", - "phase_default_info_binary", + "phase_default_info", "phase_java_wrapper_common", "phase_jvm_flags", "phase_merge_jars", @@ -47,7 +47,7 @@ def _scala_junit_test_impl(ctx): ("runfiles", phase_runfiles_common), ("jvm_flags", phase_jvm_flags), ("write_executable", phase_write_executable_junit_test), - ("default_info", phase_default_info_binary), + ("default_info", phase_default_info), ], ) diff --git a/scala/private/rules/scala_library.bzl b/scala/private/rules/scala_library.bzl index 2098851f7..17356b843 100644 --- a/scala/private/rules/scala_library.bzl +++ b/scala/private/rules/scala_library.bzl @@ -26,7 +26,7 @@ load( "phase_compile_library", "phase_compile_library_for_plugin_bootstrapping", "phase_compile_macro_library", - "phase_default_info_library", + "phase_default_info", "phase_merge_jars", "phase_runfiles_library", "phase_scalac_provider", @@ -66,7 +66,7 @@ def _scala_library_impl(ctx): ("merge_jars", phase_merge_jars), ("runfiles", phase_runfiles_library), ("collect_exports_jars", phase_collect_exports_jars), - ("default_info", phase_default_info_library), + ("default_info", phase_default_info), ], ) @@ -142,7 +142,7 @@ def _scala_library_for_plugin_bootstrapping_impl(ctx): ("merge_jars", phase_merge_jars), ("runfiles", phase_runfiles_library), ("collect_exports_jars", phase_collect_exports_jars), - ("default_info", phase_default_info_library), + ("default_info", phase_default_info), ], ) @@ -197,7 +197,7 @@ def _scala_macro_library_impl(ctx): ("merge_jars", phase_merge_jars), ("runfiles", phase_runfiles_library), ("collect_exports_jars", phase_collect_exports_jars), - ("default_info", phase_default_info_library), + ("default_info", phase_default_info), ], ) diff --git a/scala/private/rules/scala_repl.bzl b/scala/private/rules/scala_repl.bzl index 1550b0c9d..3d508af45 100644 --- a/scala/private/rules/scala_repl.bzl +++ b/scala/private/rules/scala_repl.bzl @@ -15,7 +15,7 @@ load( "phase_collect_jars_repl", "phase_compile_repl", "phase_declare_executable", - "phase_default_info_binary", + "phase_default_info", "phase_java_wrapper_repl", "phase_merge_jars", "phase_runfiles_common", @@ -43,7 +43,7 @@ def _scala_repl_impl(ctx): ("merge_jars", phase_merge_jars), ("runfiles", phase_runfiles_common), ("write_executable", phase_write_executable_repl), - ("default_info", phase_default_info_binary), + ("default_info", phase_default_info), ], ) diff --git a/scala/private/rules/scala_test.bzl b/scala/private/rules/scala_test.bzl index d9d42ead9..1a6bd3e7d 100644 --- a/scala/private/rules/scala_test.bzl +++ b/scala/private/rules/scala_test.bzl @@ -16,7 +16,7 @@ load( "phase_compile_scalatest", "phase_coverage_runfiles", "phase_declare_executable", - "phase_default_info_scalatest", + "phase_default_info", "phase_java_wrapper_common", "phase_merge_jars", "phase_runfiles_scalatest", @@ -44,7 +44,7 @@ def _scala_test_impl(ctx): ("runfiles", phase_runfiles_scalatest), ("coverage_runfiles", phase_coverage_runfiles), ("write_executable", phase_write_executable_scalatest), - ("default_info", phase_default_info_scalatest), + ("default_info", phase_default_info), ], ) From 8f63bb6d1b342bc6e43d777442bf8f9dee61341a Mon Sep 17 00:00:00 2001 From: Andy Scott Date: Sun, 26 Jan 2020 17:01:50 -0800 Subject: [PATCH 05/17] collect_data = True --- scala/private/phases/phase_default_info.bzl | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/scala/private/phases/phase_default_info.bzl b/scala/private/phases/phase_default_info.bzl index cc789a3c4..53f056883 100644 --- a/scala/private/phases/phase_default_info.bzl +++ b/scala/private/phases/phase_default_info.bzl @@ -32,7 +32,10 @@ def phase_default_info(ctx, p): "DefaultInfo": DefaultInfo( executable = executable, files = depset(transitive = files), - runfiles = ctx.runfiles(transitive_files = depset(transitive = runfiles)), + # TODO: + # Per Bazel documentation, we should avoid using collect_data. The core phases need to be updated + # before we can make the adjustment. + runfiles = ctx.runfiles(transitive_files = depset(transitive = runfiles), collect_data = True), ), }, ) From 542d89aa95b08e1a83b2b34feb9ff7cad79b6552 Mon Sep 17 00:00:00 2001 From: Andy Scott Date: Sun, 26 Jan 2020 17:29:59 -0800 Subject: [PATCH 06/17] fix --- src/scala/io/bazel/rules_scala/discover_tests_worker/BUILD | 2 +- .../discover_tests_worker/DiscoverTestsWorker.scala | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/scala/io/bazel/rules_scala/discover_tests_worker/BUILD b/src/scala/io/bazel/rules_scala/discover_tests_worker/BUILD index 10a077f78..cacf34ab7 100644 --- a/src/scala/io/bazel/rules_scala/discover_tests_worker/BUILD +++ b/src/scala/io/bazel/rules_scala/discover_tests_worker/BUILD @@ -3,7 +3,7 @@ load("//scala:defs.bzl", "scala_binary") scala_binary( name = "discover_tests_worker", srcs = ["DiscoverTestsWorker.scala"], - main_class = "io.bazelr.rules_scala.discover_tests_worker.DiscoverTestsWorker", + main_class = "io.bazel.rules_scala.discover_tests_worker.DiscoverTestsWorker", deps = [ "@io_bazel_rules_scala_classgraph//jar:jar", "@io_bazel_rules_scala//src/java/io/bazel/rules_scala/worker", diff --git a/src/scala/io/bazel/rules_scala/discover_tests_worker/DiscoverTestsWorker.scala b/src/scala/io/bazel/rules_scala/discover_tests_worker/DiscoverTestsWorker.scala index 802702884..419b3c9ba 100644 --- a/src/scala/io/bazel/rules_scala/discover_tests_worker/DiscoverTestsWorker.scala +++ b/src/scala/io/bazel/rules_scala/discover_tests_worker/DiscoverTestsWorker.scala @@ -1,12 +1,12 @@ -package io.bazel.rules_scala.test_discovery +package io.bazel.rules_scala.discover_tests_worker import io.bazel.rules_scala.worker.Worker import io.github.classgraph.ClassGraph -object TestDiscoveryWorker extends Worker.Interface { +object DiscoverTestsWorker extends Worker.Interface { - def main(args: Array[String]): Unit = Worker.workerMain(args, TestDiscoveryWorker) + def main(args: Array[String]): Unit = Worker.workerMain(args, DiscoverTestsWorker) def work(args: Array[String]): Unit = { println("WORK IT") From 36a06f4ba64ca7da77cb7ef8b9e814e7f2e01350 Mon Sep 17 00:00:00 2001 From: Andy Scott Date: Sun, 26 Jan 2020 20:15:41 -0800 Subject: [PATCH 07/17] WIP --- scala/private/macros/scala_repositories.bzl | 9 +++ scala/private/phases/phase_discover_tests.bzl | 9 ++- .../private/phases/phase_write_executable.bzl | 2 +- scala/private/rules/unstable_scala_test.bzl | 4 +- .../rules_scala/discover_tests_runner/BUILD | 14 ++++ .../DiscoverTestsRunner.scala | 12 ++++ .../rules_scala/discover_tests_worker/BUILD | 14 ++++ .../DiscoverTestsWorker.scala | 69 ++++++++++++++++++- .../discovered_tests.proto | 25 +++++++ 9 files changed, 151 insertions(+), 7 deletions(-) create mode 100644 src/scala/io/bazel/rules_scala/discover_tests_runner/BUILD create mode 100644 src/scala/io/bazel/rules_scala/discover_tests_runner/DiscoverTestsRunner.scala create mode 100644 src/scala/io/bazel/rules_scala/discover_tests_worker/discovered_tests.proto diff --git a/scala/private/macros/scala_repositories.bzl b/scala/private/macros/scala_repositories.bzl index 7aed41587..a64d537ca 100644 --- a/scala/private/macros/scala_repositories.bzl +++ b/scala/private/macros/scala_repositories.bzl @@ -139,6 +139,15 @@ def scala_repositories( server_urls = maven_servers, ) + _scala_maven_import_external( + name = "io_bazel_rules_scala_test_interface", + artifact = "org.scala-sbt:test-interface:jar:1.0", + artifact_sha256 = "15f70b38bb95f3002fec9aea54030f19bb4ecfbad64c67424b5e5fea09cd749e", + licenses = ["notice"], + server_urls = maven_servers, + ) + + if not native.existing_rule("com_google_protobuf"): http_archive( name = "com_google_protobuf", diff --git a/scala/private/phases/phase_discover_tests.bzl b/scala/private/phases/phase_discover_tests.bzl index d564933dd..d3545a176 100644 --- a/scala/private/phases/phase_discover_tests.bzl +++ b/scala/private/phases/phase_discover_tests.bzl @@ -6,15 +6,20 @@ def phase_discover_tests(ctx, p): tools = [worker], ) - output = ctx.actions.declare_file("{}_discovered_tests.txt".format(ctx.label.name)) + output = ctx.actions.declare_file("{}_discovered_tests.bin".format(ctx.label.name)) args = ctx.actions.args() args.set_param_file_format("multiline") args.use_param_file("@%s", use_always = True) + args.add(output) + args.add_all(p.compile.full_jars) + args.add("--") + args.add_all(p.collect_jars.compile_jars) + ctx.actions.run( mnemonic = "DiscoverTests", - inputs = [] + worker_inputs, + inputs = worker_inputs + p.collect_jars.compile_jars.to_list() + p.compile.full_jars, outputs = [output], executable = worker.files_to_run.executable, input_manifests = worker_input_manifests, diff --git a/scala/private/phases/phase_write_executable.bzl b/scala/private/phases/phase_write_executable.bzl index 3c8c11fbc..861b9c83c 100644 --- a/scala/private/phases/phase_write_executable.bzl +++ b/scala/private/phases/phase_write_executable.bzl @@ -57,7 +57,7 @@ def _phase_write_executable_default(ctx, p, _args = struct()): _args.rjars if hasattr(_args, "rjars") else p.compile.rjars, _args.jvm_flags if hasattr(_args, "jvm_flags") else ctx.attr.jvm_flags, _args.use_jacoco if hasattr(_args, "use_jacoco") else False, - _args.main_class if hasattr(_args, "main_class") else ctx.attr.main_class, + _args.main_class if hasattr(_args, "main_class") else ctx.attr._main_class if hasattr(ctx.attr, "_main_class") else ctx.attr.main_class, ) def _phase_write_executable( diff --git a/scala/private/rules/unstable_scala_test.bzl b/scala/private/rules/unstable_scala_test.bzl index ad4d272ea..c86382697 100644 --- a/scala/private/rules/unstable_scala_test.bzl +++ b/scala/private/rules/unstable_scala_test.bzl @@ -51,8 +51,8 @@ def _scala_test_impl(ctx): ) _scala_test_attrs = { - "main_class": attr.string( - default = "io.bazel.rulesscala.scala_test.Runner", + "_main_class": attr.string( + default = "io.bazel.rules_scala.discover_tests_runner.DiscoverTestsRunner", ), "colors": attr.bool(default = True), "full_stacktraces": attr.bool(default = True), diff --git a/src/scala/io/bazel/rules_scala/discover_tests_runner/BUILD b/src/scala/io/bazel/rules_scala/discover_tests_runner/BUILD new file mode 100644 index 000000000..7d8b70e3d --- /dev/null +++ b/src/scala/io/bazel/rules_scala/discover_tests_runner/BUILD @@ -0,0 +1,14 @@ +load("//scala:defs.bzl", "scala_binary") + +scala_binary( + name = "discover_tests_runner", + srcs = ["DiscoverTestsRunner.scala"], + main_class = "io.bazel.rules_scala.discover_tests_worker.DiscoverTestsRunner", + deps = [ + "@io_bazel_rules_scala//src/java/io/bazel/rules_scala/worker", + "@io_bazel_rules_scala_test_interface//jar:jar", + "//src/scala/io/bazel/rules_scala/discover_tests_worker:discovered_tests_java_proto", + "//external:io_bazel_rules_scala/dependency/com_google_protobuf/protobuf_java", + ], + visibility = ["//visibility:public"], +) diff --git a/src/scala/io/bazel/rules_scala/discover_tests_runner/DiscoverTestsRunner.scala b/src/scala/io/bazel/rules_scala/discover_tests_runner/DiscoverTestsRunner.scala new file mode 100644 index 000000000..e0bad64ad --- /dev/null +++ b/src/scala/io/bazel/rules_scala/discover_tests_runner/DiscoverTestsRunner.scala @@ -0,0 +1,12 @@ +package io.bazel.rules_scala.discover_tests_runner + +import io.bazel.rules_scala.worker.Worker + +object DiscoverTestsRunner extends Worker.Interface { + + def main(args: Array[String]): Unit = Worker.workerMain(args, DiscoverTestsWorker) + + def work(args: Array[String]): Unit = { + println("WORK WORK WORK") + } +} diff --git a/src/scala/io/bazel/rules_scala/discover_tests_worker/BUILD b/src/scala/io/bazel/rules_scala/discover_tests_worker/BUILD index cacf34ab7..32c8f0ce0 100644 --- a/src/scala/io/bazel/rules_scala/discover_tests_worker/BUILD +++ b/src/scala/io/bazel/rules_scala/discover_tests_worker/BUILD @@ -7,6 +7,20 @@ scala_binary( deps = [ "@io_bazel_rules_scala_classgraph//jar:jar", "@io_bazel_rules_scala//src/java/io/bazel/rules_scala/worker", + "@io_bazel_rules_scala_test_interface//jar:jar", + ":discovered_tests_java_proto", + "//external:io_bazel_rules_scala/dependency/com_google_protobuf/protobuf_java", ], visibility = ["//visibility:public"], ) + +proto_library( + name = "discovered_tests_proto", + srcs = ["discovered_tests.proto"], +) + +java_proto_library( + name = "discovered_tests_java_proto", + deps = [":discovered_tests_proto"], + visibility = ["//visibility:public"], +) diff --git a/src/scala/io/bazel/rules_scala/discover_tests_worker/DiscoverTestsWorker.scala b/src/scala/io/bazel/rules_scala/discover_tests_worker/DiscoverTestsWorker.scala index 419b3c9ba..c1af300e1 100644 --- a/src/scala/io/bazel/rules_scala/discover_tests_worker/DiscoverTestsWorker.scala +++ b/src/scala/io/bazel/rules_scala/discover_tests_worker/DiscoverTestsWorker.scala @@ -1,16 +1,81 @@ package io.bazel.rules_scala.discover_tests_worker import io.bazel.rules_scala.worker.Worker +import io.bazel.rules_scala.discover_tests_worker.DiscoveredTests.Result +import io.bazel.rules_scala.discover_tests_worker.DiscoveredTests.FrameworkDiscovery +import io.bazel.rules_scala.discover_tests_worker.DiscoveredTests.AnnotatedDiscovery +import io.bazel.rules_scala.discover_tests_worker.DiscoveredTests.SubclassDiscovery import io.github.classgraph.ClassGraph +import io.github.classgraph.ClassInfo + +import sbt.testing.Framework +import sbt.testing.SubclassFingerprint +import sbt.testing.AnnotatedFingerprint + +import java.io.FileOutputStream +import java.net.URLClassLoader +import java.nio.file.Paths + +import scala.collection.JavaConverters._ object DiscoverTestsWorker extends Worker.Interface { def main(args: Array[String]): Unit = Worker.workerMain(args, DiscoverTestsWorker) def work(args: Array[String]): Unit = { - println("WORK IT") + val outputFile = Paths.get(args(0)).toFile + val (args0, args1) = args.tail.span(_ != "--") + val testJars = args0.map(f => Paths.get(f).toUri.toURL) + val frameworkJars = args1.tail.map(f => Paths.get(f).toUri.toURL) + + val frameworkClassloader = new URLClassLoader(frameworkJars) + val frameworkScanResult = (new ClassGraph) + .overrideClassLoaders(frameworkClassloader) + .ignoreParentClassLoaders + .enableClassInfo.scan + + val testScanResult = (new ClassGraph) + .overrideClassLoaders(new URLClassLoader(testJars ++ frameworkJars)) + .ignoreParentClassLoaders + .enableClassInfo + .enableMethodInfo + .scan + + val result: Result = frameworkScanResult + .getClassesImplementing("sbt.testing.Framework").asScala + .foldLeft(Result.newBuilder) { (resultBuilder, framework) => + val frameworkInstance = framework.loadClass.newInstance.asInstanceOf[Framework] + resultBuilder.addFrameworkDiscoveries( + frameworkInstance.fingerprints.foldLeft(FrameworkDiscovery.newBuilder)((b, f) => f match { + case fingerprint: SubclassFingerprint => + val tests = testScanResult + .getClassesImplementing(fingerprint.superclassName) + .exclude(frameworkScanResult.getClassesImplementing(fingerprint.superclassName)) + .asScala + .filter(_.isStandardClass) + .filter(_.getConstructorInfo.asScala.exists(_.getParameterInfo.isEmpty) == fingerprint.requireNoArgConstructor) + .map(_.getName) + .asJava + + b.addSubclassDiscoveries( + SubclassDiscovery.newBuilder + .setSubclassName(fingerprint.superclassName) + .setRequireNoArgConstructor(fingerprint.requireNoArgConstructor) + .addAllTests(tests) + .build) + case fingerprint: AnnotatedFingerprint => + b.addAnnotatedDiscoveries( + AnnotatedDiscovery.newBuilder + .build) + }).build) + }.build + + testScanResult.close() + frameworkScanResult.close() - val cg = new ClassGraph() + val os = new FileOutputStream(outputFile) + result.writeTo(os) + os.close() } } diff --git a/src/scala/io/bazel/rules_scala/discover_tests_worker/discovered_tests.proto b/src/scala/io/bazel/rules_scala/discover_tests_worker/discovered_tests.proto new file mode 100644 index 000000000..f3a7a53ab --- /dev/null +++ b/src/scala/io/bazel/rules_scala/discover_tests_worker/discovered_tests.proto @@ -0,0 +1,25 @@ +syntax = "proto3"; + +package io.bazel.rules_scala.discover_tests_worker; + +option java_package = "io.bazel.rules_scala.discover_tests_worker"; + +message Result { + repeated FrameworkDiscovery frameworkDiscoveries = 1; +} + +message FrameworkDiscovery { + string framework = 1; + repeated SubclassDiscovery subclassDiscoveries = 2; + repeated AnnotatedDiscovery annotatedDiscoveries = 3; +} + +message SubclassDiscovery { + string subclassName = 1; + bool requireNoArgConstructor = 2; + repeated string tests = 3; +} + +message AnnotatedDiscovery { + string empty = 1; +} \ No newline at end of file From 30aebd304a988629f7d60f1c407f4d3871f15b0b Mon Sep 17 00:00:00 2001 From: Andy Scott Date: Sun, 26 Jan 2020 23:52:02 -0800 Subject: [PATCH 08/17] works for now --- WORKSPACE | 15 ++++ scala/private/phases/phase_collect_jars.bzl | 19 ++++- scala/private/phases/phase_discover_tests.bzl | 4 + .../private/phases/phase_write_executable.bzl | 13 ++- scala/private/rules/unstable_scala_test.bzl | 13 ++- .../rules_scala/discover_tests_runner/BUILD | 19 ++--- .../DiscoverTestsRunner.scala | 83 +++++++++++++++++-- .../DiscoverTestsWorker.scala | 36 ++++++-- .../discovered_tests.proto | 7 +- test/v2/BUILD | 6 ++ test/v2/test.scala | 19 ++++- 11 files changed, 202 insertions(+), 32 deletions(-) diff --git a/WORKSPACE b/WORKSPACE index 4094d66e3..7fd9d4d8f 100644 --- a/WORKSPACE +++ b/WORKSPACE @@ -47,6 +47,21 @@ MAVEN_SERVER_URLS = [ "https://repo1.maven.org/maven2", ] +# test sbt testing frameworks +scala_maven_import_external( + name = "org_scalacheck_scalacheck", + artifact = scala_mvn_artifact( + "org.scalacheck:scalacheck:1.14.3", + default_scala_major_version(), + ), + artifact_sha256 = "3cbc95bb615f1a384b8c4406dfc42b225499f08adf7639de11566069e47d44cf", + licenses = ["notice"], # Apache 2.0 + server_urls = [ + "https://repo1.maven.org/maven2/", + "https://mirror.bazel.build/repo1.maven.org/maven2", + ], +) + # test adding a scala jar: jvm_maven_import_external( name = "com_twitter__scalding_date", diff --git a/scala/private/phases/phase_collect_jars.bzl b/scala/private/phases/phase_collect_jars.bzl index d3d4fce9b..fad2b4db7 100644 --- a/scala/private/phases/phase_collect_jars.bzl +++ b/scala/private/phases/phase_collect_jars.bzl @@ -56,11 +56,26 @@ def phase_collect_jars_common(ctx, p): return _phase_collect_jars_default(ctx, p) def _phase_collect_jars_default(ctx, p, _args = struct()): + extra_deps = [] + extra_runtime_deps = [] + + phase_names = dir(p) + phase_names.remove("to_json") + phase_names.remove("to_proto") + for phase_name in phase_names: + phase = getattr(p, phase_name) + + if hasattr(phase, "extra_deps"): + extra_deps.extend(phase.extra_deps) + + if hasattr(phase, "extra_runtime_deps"): + extra_runtime_deps.extend(phase.extra_runtime_deps) + return _phase_collect_jars( ctx, _args.base_classpath if hasattr(_args, "base_classpath") else p.scalac_provider.default_classpath, - _args.extra_deps if hasattr(_args, "extra_deps") else [], - _args.extra_runtime_deps if hasattr(_args, "extra_runtime_deps") else [], + (_args.extra_deps if hasattr(_args, "extra_deps") else []) + extra_deps, + (_args.extra_runtime_deps if hasattr(_args, "extra_runtime_deps") else []) + extra_runtime_deps, _args.unused_dependency_checker_mode if hasattr(_args, "unused_dependency_checker_mode") else p.unused_deps_checker, ) diff --git a/scala/private/phases/phase_discover_tests.bzl b/scala/private/phases/phase_discover_tests.bzl index d3545a176..9b3ace6ee 100644 --- a/scala/private/phases/phase_discover_tests.bzl +++ b/scala/private/phases/phase_discover_tests.bzl @@ -29,4 +29,8 @@ def phase_discover_tests(ctx, p): return struct( files = depset([output]), + jvm_flags = [ + "-DDiscoveredTestsResult={}".format(output.short_path), + ], + runfiles = depset([output]), ) diff --git a/scala/private/phases/phase_write_executable.bzl b/scala/private/phases/phase_write_executable.bzl index 861b9c83c..18243c115 100644 --- a/scala/private/phases/phase_write_executable.bzl +++ b/scala/private/phases/phase_write_executable.bzl @@ -51,11 +51,22 @@ def phase_write_executable_common(ctx, p): return _phase_write_executable_default(ctx, p) def _phase_write_executable_default(ctx, p, _args = struct()): + jvm_flags = [] + + phase_names = dir(p) + phase_names.remove("to_json") + phase_names.remove("to_proto") + for phase_name in phase_names: + phase = getattr(p, phase_name) + + if hasattr(phase, "jvm_flags"): + jvm_flags.extend(phase.jvm_flags) + return _phase_write_executable( ctx, p, _args.rjars if hasattr(_args, "rjars") else p.compile.rjars, - _args.jvm_flags if hasattr(_args, "jvm_flags") else ctx.attr.jvm_flags, + (_args.jvm_flags if hasattr(_args, "jvm_flags") else ctx.attr.jvm_flags) + jvm_flags, _args.use_jacoco if hasattr(_args, "use_jacoco") else False, _args.main_class if hasattr(_args, "main_class") else ctx.attr._main_class if hasattr(ctx.attr, "_main_class") else ctx.attr.main_class, ) diff --git a/scala/private/rules/unstable_scala_test.bzl b/scala/private/rules/unstable_scala_test.bzl index c86382697..66a86de17 100644 --- a/scala/private/rules/unstable_scala_test.bzl +++ b/scala/private/rules/unstable_scala_test.bzl @@ -28,6 +28,13 @@ load( "run_phases", ) +def _andy_hack_0(ctx, p): + return struct( + extra_runtime_deps = [ + ctx.attr._discover_tests_runner, + ], + ) + def _scala_test_impl(ctx): return run_phases( ctx, @@ -36,15 +43,16 @@ def _scala_test_impl(ctx): ("scalac_provider", phase_scalac_provider), ("write_manifest", phase_write_manifest), ("unused_deps_checker", phase_unused_deps_checker), + ("andy_hack_0", _andy_hack_0), ("collect_jars", phase_collect_jars_common), ("java_wrapper", phase_java_wrapper_common), ("declare_executable", phase_declare_executable), # no need to build an ijar for an executable ("compile", phase_compile_scalatest), ("merge_jars", phase_merge_jars), - ("discover_tests", phase_discover_tests), ("runfiles", phase_runfiles_common), ("coverage_runfiles", phase_coverage_runfiles), + ("discover_tests", phase_discover_tests), ("write_executable", phase_write_executable_common), ("default_info", phase_default_info), ], @@ -66,6 +74,9 @@ _scala_test_attrs = { "_discover_tests_worker": attr.label( default = Label("@io_bazel_rules_scala//src/scala/io/bazel/rules_scala/discover_tests_worker"), ), + "_discover_tests_runner": attr.label( + default = Label("@io_bazel_rules_scala//src/scala/io/bazel/rules_scala/discover_tests_runner"), + ), } _scala_test_attrs.update(launcher_template) diff --git a/src/scala/io/bazel/rules_scala/discover_tests_runner/BUILD b/src/scala/io/bazel/rules_scala/discover_tests_runner/BUILD index 7d8b70e3d..068be4078 100644 --- a/src/scala/io/bazel/rules_scala/discover_tests_runner/BUILD +++ b/src/scala/io/bazel/rules_scala/discover_tests_runner/BUILD @@ -1,14 +1,13 @@ load("//scala:defs.bzl", "scala_binary") scala_binary( - name = "discover_tests_runner", - srcs = ["DiscoverTestsRunner.scala"], - main_class = "io.bazel.rules_scala.discover_tests_worker.DiscoverTestsRunner", - deps = [ - "@io_bazel_rules_scala//src/java/io/bazel/rules_scala/worker", - "@io_bazel_rules_scala_test_interface//jar:jar", - "//src/scala/io/bazel/rules_scala/discover_tests_worker:discovered_tests_java_proto", - "//external:io_bazel_rules_scala/dependency/com_google_protobuf/protobuf_java", - ], - visibility = ["//visibility:public"], + name = "discover_tests_runner", + srcs = ["DiscoverTestsRunner.scala"], + main_class = "io.bazel.rules_scala.discover_tests_worker.DiscoverTestsRunner", + visibility = ["//visibility:public"], + deps = [ + "//external:io_bazel_rules_scala/dependency/com_google_protobuf/protobuf_java", + "//src/scala/io/bazel/rules_scala/discover_tests_worker:discovered_tests_java_proto", + "@io_bazel_rules_scala_test_interface//jar", + ], ) diff --git a/src/scala/io/bazel/rules_scala/discover_tests_runner/DiscoverTestsRunner.scala b/src/scala/io/bazel/rules_scala/discover_tests_runner/DiscoverTestsRunner.scala index e0bad64ad..3e6ef505a 100644 --- a/src/scala/io/bazel/rules_scala/discover_tests_runner/DiscoverTestsRunner.scala +++ b/src/scala/io/bazel/rules_scala/discover_tests_runner/DiscoverTestsRunner.scala @@ -1,12 +1,85 @@ package io.bazel.rules_scala.discover_tests_runner -import io.bazel.rules_scala.worker.Worker +import io.bazel.rules_scala.discover_tests_worker.DiscoveredTests.Result +import io.bazel.rules_scala.discover_tests_worker.DiscoveredTests.FrameworkDiscovery +import io.bazel.rules_scala.discover_tests_worker.DiscoveredTests.SubclassDiscovery -object DiscoverTestsRunner extends Worker.Interface { +import sbt.testing.Event +import sbt.testing.EventHandler +import sbt.testing.Framework +import sbt.testing.Logger +import sbt.testing.Runner +import sbt.testing.SubclassFingerprint +import sbt.testing.SuiteSelector +import sbt.testing.Task +import sbt.testing.TaskDef - def main(args: Array[String]): Unit = Worker.workerMain(args, DiscoverTestsWorker) +import java.io.FileInputStream +import java.nio.file.Paths + +import scala.collection.JavaConverters._ +import scala.annotation.tailrec + +object DiscoverTestsRunner { + + def main(args: Array[String]): Unit = { + val input = new FileInputStream(Paths.get(sys.props("DiscoveredTestsResult")).toFile) + val result: Result = Result.parseFrom(input) + input.close() + + result.getFrameworkDiscoveriesList.asScala + .foreach(frameworkDiscovery => handleFrameworkDiscovery(frameworkDiscovery, args)) + + sys.exit(0) + } + + def handleFrameworkDiscovery(frameworkDiscovery: FrameworkDiscovery, args: Array[String]): Unit = { + println(s"> beginning run of ${frameworkDiscovery.getFramework}") + val framework: Framework = Class.forName(frameworkDiscovery.getFramework).newInstance.asInstanceOf[Framework] + val runner: Runner = framework.runner(args, Array.empty, Thread.currentThread.getContextClassLoader) + + val subclassFingerprintMap: Map[(String, Boolean, Boolean), SubclassFingerprint] = framework.fingerprints.collect { + case fingerprint: SubclassFingerprint => (fingerprint.superclassName, fingerprint.isModule, fingerprint.requireNoArgConstructor) -> fingerprint + }.toMap + + frameworkDiscovery.getSubclassDiscoveriesList + .asScala + .foreach { subclassDiscovery => + val fingerprint: SubclassFingerprint = subclassFingerprintMap.get((subclassDiscovery.getSuperclassName, subclassDiscovery.getIsModule, subclassDiscovery.getRequireNoArgConstructor)) + .getOrElse(sys.error(s"Unable to resolve fingerprint instance for $subclassDiscovery")) + + handleTests(runner, fingerprint, subclassDiscovery.getTestsList.asScala.toList) + } + + println(runner.done()) + println(s"< run of ${frameworkDiscovery.getFramework} complete") + } + + def handleTests(runner: Runner, fingerprint: SubclassFingerprint, tests: List[String]): Unit = { + val eventHandler: EventHandler = new EventHandler { + def handle(event: Event): Unit = { + //println(s"- $event") + } + } + val loggers: Array[Logger] = Array(new Logger { + def ansiCodesSupported(): Boolean = true + def debug(msg: String): Unit = println(s"debug: $msg") + def error(msg: String): Unit = println(s"error: $msg") + def info(msg: String): Unit = println(s"info: $msg") + def trace(e: Throwable): Unit = e.printStackTrace + def warn(msg: String): Unit = println(s"warn: $msg") + }) + + @tailrec def execute(tasks: List[Task]): Unit = tasks match { + case head :: tail => + execute(head.execute(eventHandler, loggers) ++: tail) + case Nil => + () + } + + execute(runner + .tasks(tests.map(test => new TaskDef(test, fingerprint, true, Array(new SuiteSelector))).toArray).toList) - def work(args: Array[String]): Unit = { - println("WORK WORK WORK") } + } diff --git a/src/scala/io/bazel/rules_scala/discover_tests_worker/DiscoverTestsWorker.scala b/src/scala/io/bazel/rules_scala/discover_tests_worker/DiscoverTestsWorker.scala index c1af300e1..be5169f76 100644 --- a/src/scala/io/bazel/rules_scala/discover_tests_worker/DiscoverTestsWorker.scala +++ b/src/scala/io/bazel/rules_scala/discover_tests_worker/DiscoverTestsWorker.scala @@ -8,6 +8,8 @@ import io.bazel.rules_scala.discover_tests_worker.DiscoveredTests.SubclassDiscov import io.github.classgraph.ClassGraph import io.github.classgraph.ClassInfo +import io.github.classgraph.ClassInfoList +import io.github.classgraph.ScanResult import sbt.testing.Framework import sbt.testing.SubclassFingerprint @@ -47,22 +49,36 @@ object DiscoverTestsWorker extends Worker.Interface { .foldLeft(Result.newBuilder) { (resultBuilder, framework) => val frameworkInstance = framework.loadClass.newInstance.asInstanceOf[Framework] resultBuilder.addFrameworkDiscoveries( - frameworkInstance.fingerprints.foldLeft(FrameworkDiscovery.newBuilder)((b, f) => f match { + frameworkInstance.fingerprints.foldLeft(FrameworkDiscovery.newBuilder.setFramework(framework.getName))((b, f) => f match { case fingerprint: SubclassFingerprint => - val tests = testScanResult - .getClassesImplementing(fingerprint.superclassName) - .exclude(frameworkScanResult.getClassesImplementing(fingerprint.superclassName)) + + val getCandidates: ScanResult => ClassInfoList = + if (frameworkScanResult.getClassInfo(fingerprint.superclassName).isInterface) + _.getClassesImplementing(fingerprint.superclassName) + else + _.getSubclasses(fingerprint.superclassName) + + val candidates = getCandidates(testScanResult).exclude(getCandidates(frameworkScanResult)) .asScala .filter(_.isStandardClass) - .filter(_.getConstructorInfo.asScala.exists(_.getParameterInfo.isEmpty) == fingerprint.requireNoArgConstructor) - .map(_.getName) - .asJava + + val tests: Iterable[String] = + if (fingerprint.isModule) + candidates + .map(_.getName) + .filter(_.endsWith("$")).map(_.dropRight(1)) + else + candidates + .filter(_.getConstructorInfo.asScala.exists(_.getParameterInfo.isEmpty) == fingerprint.requireNoArgConstructor) + .map(_.getName) + .filterNot(_.endsWith("$")) b.addSubclassDiscoveries( SubclassDiscovery.newBuilder - .setSubclassName(fingerprint.superclassName) + .setSuperclassName(fingerprint.superclassName) + .setIsModule(fingerprint.isModule) .setRequireNoArgConstructor(fingerprint.requireNoArgConstructor) - .addAllTests(tests) + .addAllTests(tests.asJava) .build) case fingerprint: AnnotatedFingerprint => b.addAnnotatedDiscoveries( @@ -74,6 +90,8 @@ object DiscoverTestsWorker extends Worker.Interface { testScanResult.close() frameworkScanResult.close() + //println(result) + val os = new FileOutputStream(outputFile) result.writeTo(os) os.close() diff --git a/src/scala/io/bazel/rules_scala/discover_tests_worker/discovered_tests.proto b/src/scala/io/bazel/rules_scala/discover_tests_worker/discovered_tests.proto index f3a7a53ab..bc63d4367 100644 --- a/src/scala/io/bazel/rules_scala/discover_tests_worker/discovered_tests.proto +++ b/src/scala/io/bazel/rules_scala/discover_tests_worker/discovered_tests.proto @@ -15,9 +15,10 @@ message FrameworkDiscovery { } message SubclassDiscovery { - string subclassName = 1; - bool requireNoArgConstructor = 2; - repeated string tests = 3; + string superclassName = 1; + bool isModule = 2; + bool requireNoArgConstructor = 3; + repeated string tests = 4; } message AnnotatedDiscovery { diff --git a/test/v2/BUILD b/test/v2/BUILD index 8ae220b83..41e152168 100644 --- a/test/v2/BUILD +++ b/test/v2/BUILD @@ -23,6 +23,12 @@ scala_test( srcs = ["test.scala"], deps = [ ":library", + "@org_scalacheck_scalacheck//:org_scalacheck_scalacheck", "//external:io_bazel_rules_scala/dependency/scalatest/scalatest", ], + runtime_deps = [ + # this would normally get exported by scalatest if it was set up as a proper dep + # instead of just a jar + "//external:io_bazel_rules_scala/dependency/scala/scala_xml", + ] ) diff --git a/test/v2/test.scala b/test/v2/test.scala index 88985c7fb..712e82666 100644 --- a/test/v2/test.scala +++ b/test/v2/test.scala @@ -2,7 +2,10 @@ package test.v2 import org.scalatest.FunSuite -class Test extends FunSuite { +import org.scalacheck.Properties +import org.scalacheck.Prop._ + +final class TestSuiteClass extends FunSuite { test("method1") { assert(Library.method1 == "hello") } @@ -11,3 +14,17 @@ class Test extends FunSuite { assert(Library.method2 == "world") } } + +object TestSuiteObject extends FunSuite { + test("not-supported") { + assert("hello" == "world") + } +} + +final class TestPropertiesClass extends Properties("TestPropertiesClass") { + property("1") = 1 ?= 1 +} + +object TestPropertiesObject extends Properties("TestPropertiesObject") { + property("2") = 2 ?= 2 +} From 115280ccf9a72e1491b060e848115f5adad67297 Mon Sep 17 00:00:00 2001 From: Andy Scott Date: Mon, 27 Jan 2020 23:57:50 -0800 Subject: [PATCH 09/17] make it run junit too --- WORKSPACE | 11 +++++++ scala/private/phases/phase_discover_tests.bzl | 2 +- .../DiscoverTestsRunner.scala | 31 ++++++++++++++----- .../DiscoverTestsWorker.scala | 30 +++++++++++++++--- .../discovered_tests.proto | 4 ++- test/v2/BUILD | 4 +++ test/v2/test.scala | 10 ++++++ 7 files changed, 78 insertions(+), 14 deletions(-) diff --git a/WORKSPACE b/WORKSPACE index 7fd9d4d8f..cbade8c6c 100644 --- a/WORKSPACE +++ b/WORKSPACE @@ -62,6 +62,17 @@ scala_maven_import_external( ], ) +scala_maven_import_external( + name = "com_novocode_junit_interface", + artifact = "com.novocode:junit-interface:0.11", + artifact_sha256 = "29e923226a0d10e9142bbd81073ef52f601277001fcf9014389bf0af3dc33dc3", + licenses = ["notice"], # Apache 2.0 + server_urls = [ + "https://repo1.maven.org/maven2/", + "https://mirror.bazel.build/repo1.maven.org/maven2", + ], +) + # test adding a scala jar: jvm_maven_import_external( name = "com_twitter__scalding_date", diff --git a/scala/private/phases/phase_discover_tests.bzl b/scala/private/phases/phase_discover_tests.bzl index 9b3ace6ee..93d50749d 100644 --- a/scala/private/phases/phase_discover_tests.bzl +++ b/scala/private/phases/phase_discover_tests.bzl @@ -15,7 +15,7 @@ def phase_discover_tests(ctx, p): args.add(output) args.add_all(p.compile.full_jars) args.add("--") - args.add_all(p.collect_jars.compile_jars) + args.add_all(p.collect_jars.transitive_runtime_jars) ctx.actions.run( mnemonic = "DiscoverTests", diff --git a/src/scala/io/bazel/rules_scala/discover_tests_runner/DiscoverTestsRunner.scala b/src/scala/io/bazel/rules_scala/discover_tests_runner/DiscoverTestsRunner.scala index 3e6ef505a..d18ddfef1 100644 --- a/src/scala/io/bazel/rules_scala/discover_tests_runner/DiscoverTestsRunner.scala +++ b/src/scala/io/bazel/rules_scala/discover_tests_runner/DiscoverTestsRunner.scala @@ -4,8 +4,10 @@ import io.bazel.rules_scala.discover_tests_worker.DiscoveredTests.Result import io.bazel.rules_scala.discover_tests_worker.DiscoveredTests.FrameworkDiscovery import io.bazel.rules_scala.discover_tests_worker.DiscoveredTests.SubclassDiscovery +import sbt.testing.AnnotatedFingerprint import sbt.testing.Event import sbt.testing.EventHandler +import sbt.testing.Fingerprint import sbt.testing.Framework import sbt.testing.Logger import sbt.testing.Runner @@ -38,24 +40,39 @@ object DiscoverTestsRunner { val framework: Framework = Class.forName(frameworkDiscovery.getFramework).newInstance.asInstanceOf[Framework] val runner: Runner = framework.runner(args, Array.empty, Thread.currentThread.getContextClassLoader) - val subclassFingerprintMap: Map[(String, Boolean, Boolean), SubclassFingerprint] = framework.fingerprints.collect { - case fingerprint: SubclassFingerprint => (fingerprint.superclassName, fingerprint.isModule, fingerprint.requireNoArgConstructor) -> fingerprint - }.toMap + val subclassFingerprintMap: Map[(String, Boolean, Boolean), SubclassFingerprint] = + framework.fingerprints.collect { + case fingerprint: SubclassFingerprint => + (fingerprint.superclassName, fingerprint.isModule, fingerprint.requireNoArgConstructor) -> fingerprint + }.toMap - frameworkDiscovery.getSubclassDiscoveriesList - .asScala + val annotatedFingerprintMap: Map[(String, Boolean), AnnotatedFingerprint] = + framework.fingerprints.collect { + case fingerprint: AnnotatedFingerprint => + (fingerprint.annotationName, fingerprint.isModule) -> fingerprint + }.toMap + + frameworkDiscovery.getSubclassDiscoveriesList.asScala .foreach { subclassDiscovery => - val fingerprint: SubclassFingerprint = subclassFingerprintMap.get((subclassDiscovery.getSuperclassName, subclassDiscovery.getIsModule, subclassDiscovery.getRequireNoArgConstructor)) + val fingerprint = subclassFingerprintMap.get((subclassDiscovery.getSuperclassName, subclassDiscovery.getIsModule, subclassDiscovery.getRequireNoArgConstructor)) .getOrElse(sys.error(s"Unable to resolve fingerprint instance for $subclassDiscovery")) handleTests(runner, fingerprint, subclassDiscovery.getTestsList.asScala.toList) } + frameworkDiscovery.getAnnotatedDiscoveriesList.asScala + .foreach { annotatedDiscovery => + val fingerprint = annotatedFingerprintMap.get((annotatedDiscovery.getAnnotationName, annotatedDiscovery.getIsModule)) + .getOrElse(sys.error(s"Unable to resolve fingerprint instance for $annotatedDiscovery")) + + handleTests(runner, fingerprint, annotatedDiscovery.getTestsList.asScala.toList) + } + println(runner.done()) println(s"< run of ${frameworkDiscovery.getFramework} complete") } - def handleTests(runner: Runner, fingerprint: SubclassFingerprint, tests: List[String]): Unit = { + def handleTests(runner: Runner, fingerprint: Fingerprint, tests: List[String]): Unit = { val eventHandler: EventHandler = new EventHandler { def handle(event: Event): Unit = { //println(s"- $event") diff --git a/src/scala/io/bazel/rules_scala/discover_tests_worker/DiscoverTestsWorker.scala b/src/scala/io/bazel/rules_scala/discover_tests_worker/DiscoverTestsWorker.scala index be5169f76..d89164ac5 100644 --- a/src/scala/io/bazel/rules_scala/discover_tests_worker/DiscoverTestsWorker.scala +++ b/src/scala/io/bazel/rules_scala/discover_tests_worker/DiscoverTestsWorker.scala @@ -42,6 +42,7 @@ object DiscoverTestsWorker extends Worker.Interface { .ignoreParentClassLoaders .enableClassInfo .enableMethodInfo + .enableAnnotationInfo .scan val result: Result = frameworkScanResult @@ -58,9 +59,11 @@ object DiscoverTestsWorker extends Worker.Interface { else _.getSubclasses(fingerprint.superclassName) - val candidates = getCandidates(testScanResult).exclude(getCandidates(frameworkScanResult)) - .asScala - .filter(_.isStandardClass) + val candidates: Iterable[ClassInfo] = + getCandidates(testScanResult) + .exclude(getCandidates(frameworkScanResult)) + .asScala + .filter(_.isStandardClass) val tests: Iterable[String] = if (fingerprint.isModule) @@ -81,8 +84,27 @@ object DiscoverTestsWorker extends Worker.Interface { .addAllTests(tests.asJava) .build) case fingerprint: AnnotatedFingerprint => + + val candidates: Iterable[ClassInfo] = + testScanResult.getClassesWithAnnotation(fingerprint.annotationName) + .union(testScanResult.getClassesWithMethodAnnotation(fingerprint.annotationName)) + .asScala + + val tests: Iterable[String] = + if (fingerprint.isModule) + candidates + .map(_.getName) + .filter(_.endsWith("$")).map(_.dropRight(1)) + else + candidates + .map(_.getName) + .filterNot(_.endsWith("$")) + b.addAnnotatedDiscoveries( AnnotatedDiscovery.newBuilder + .setAnnotationName(fingerprint.annotationName) + .setIsModule(fingerprint.isModule) + .addAllTests(tests.asJava) .build) }).build) }.build @@ -90,8 +112,6 @@ object DiscoverTestsWorker extends Worker.Interface { testScanResult.close() frameworkScanResult.close() - //println(result) - val os = new FileOutputStream(outputFile) result.writeTo(os) os.close() diff --git a/src/scala/io/bazel/rules_scala/discover_tests_worker/discovered_tests.proto b/src/scala/io/bazel/rules_scala/discover_tests_worker/discovered_tests.proto index bc63d4367..1c9aaff38 100644 --- a/src/scala/io/bazel/rules_scala/discover_tests_worker/discovered_tests.proto +++ b/src/scala/io/bazel/rules_scala/discover_tests_worker/discovered_tests.proto @@ -22,5 +22,7 @@ message SubclassDiscovery { } message AnnotatedDiscovery { - string empty = 1; + string annotationName = 1; + bool isModule = 2; + repeated string tests = 3; } \ No newline at end of file diff --git a/test/v2/BUILD b/test/v2/BUILD index 41e152168..31e93617d 100644 --- a/test/v2/BUILD +++ b/test/v2/BUILD @@ -24,11 +24,15 @@ scala_test( deps = [ ":library", "@org_scalacheck_scalacheck//:org_scalacheck_scalacheck", + "@io_bazel_rules_scala_junit_junit//:io_bazel_rules_scala_junit_junit", "//external:io_bazel_rules_scala/dependency/scalatest/scalatest", ], runtime_deps = [ + "@com_novocode_junit_interface//:com_novocode_junit_interface", # this would normally get exported by scalatest if it was set up as a proper dep # instead of just a jar "//external:io_bazel_rules_scala/dependency/scala/scala_xml", + # same, but for junit + "@io_bazel_rules_scala_org_hamcrest_hamcrest_core//:io_bazel_rules_scala_org_hamcrest_hamcrest_core", ] ) diff --git a/test/v2/test.scala b/test/v2/test.scala index 712e82666..9aca30054 100644 --- a/test/v2/test.scala +++ b/test/v2/test.scala @@ -5,6 +5,9 @@ import org.scalatest.FunSuite import org.scalacheck.Properties import org.scalacheck.Prop._ +import org.junit.Test +import org.junit.Assert.assertEquals + final class TestSuiteClass extends FunSuite { test("method1") { assert(Library.method1 == "hello") @@ -28,3 +31,10 @@ final class TestPropertiesClass extends Properties("TestPropertiesClass") { object TestPropertiesObject extends Properties("TestPropertiesObject") { property("2") = 2 ?= 2 } + +final class JUnitTest { + @Test + def testFoo(): Unit = { + assertEquals("Test should pass", true, true) + } +} From 2abce778b2a054017c82fd98e3400414bb364a68 Mon Sep 17 00:00:00 2001 From: Andy Scott Date: Tue, 28 Jan 2020 00:00:55 -0800 Subject: [PATCH 10/17] tweak output --- .../discover_tests_runner/DiscoverTestsRunner.scala | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/scala/io/bazel/rules_scala/discover_tests_runner/DiscoverTestsRunner.scala b/src/scala/io/bazel/rules_scala/discover_tests_runner/DiscoverTestsRunner.scala index d18ddfef1..0bf2403c6 100644 --- a/src/scala/io/bazel/rules_scala/discover_tests_runner/DiscoverTestsRunner.scala +++ b/src/scala/io/bazel/rules_scala/discover_tests_runner/DiscoverTestsRunner.scala @@ -36,7 +36,7 @@ object DiscoverTestsRunner { } def handleFrameworkDiscovery(frameworkDiscovery: FrameworkDiscovery, args: Array[String]): Unit = { - println(s"> beginning run of ${frameworkDiscovery.getFramework}") + print(s"\n> beginning run of ${frameworkDiscovery.getFramework}\n") val framework: Framework = Class.forName(frameworkDiscovery.getFramework).newInstance.asInstanceOf[Framework] val runner: Runner = framework.runner(args, Array.empty, Thread.currentThread.getContextClassLoader) @@ -68,8 +68,8 @@ object DiscoverTestsRunner { handleTests(runner, fingerprint, annotatedDiscovery.getTestsList.asScala.toList) } - println(runner.done()) - println(s"< run of ${frameworkDiscovery.getFramework} complete") + print(runner.done()) + print(s"\n< run of ${frameworkDiscovery.getFramework} complete\n") } def handleTests(runner: Runner, fingerprint: Fingerprint, tests: List[String]): Unit = { From 07d9925e1d2e2c76b62eba1cbe3ce16de8d1b63c Mon Sep 17 00:00:00 2001 From: Andy Scott Date: Fri, 29 May 2020 11:06:58 -0700 Subject: [PATCH 11/17] post merge fixes --- scala/private/phases/phase_discover_tests.bzl | 4 ++-- scala/private/rules/unstable_scala_test.bzl | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/scala/private/phases/phase_discover_tests.bzl b/scala/private/phases/phase_discover_tests.bzl index 93d50749d..4a8d4506a 100644 --- a/scala/private/phases/phase_discover_tests.bzl +++ b/scala/private/phases/phase_discover_tests.bzl @@ -13,13 +13,13 @@ def phase_discover_tests(ctx, p): args.use_param_file("@%s", use_always = True) args.add(output) - args.add_all(p.compile.full_jars) + args.add_all(p.compile.files) args.add("--") args.add_all(p.collect_jars.transitive_runtime_jars) ctx.actions.run( mnemonic = "DiscoverTests", - inputs = worker_inputs + p.collect_jars.compile_jars.to_list() + p.compile.full_jars, + inputs = worker_inputs + p.collect_jars.compile_jars.to_list() + p.compile.files.to_list(), outputs = [output], executable = worker.files_to_run.executable, input_manifests = worker_input_manifests, diff --git a/scala/private/rules/unstable_scala_test.bzl b/scala/private/rules/unstable_scala_test.bzl index fd4e8e831..3ebf7f64b 100644 --- a/scala/private/rules/unstable_scala_test.bzl +++ b/scala/private/rules/unstable_scala_test.bzl @@ -13,7 +13,7 @@ load( "@io_bazel_rules_scala//scala/private:phases/phases.bzl", "extras_phases", "phase_collect_jars_common", - "phase_compile_scalatest", + "phase_compile_common", "phase_coverage_runfiles", "phase_declare_executable", "phase_default_info", @@ -48,7 +48,7 @@ def _scala_test_impl(ctx): ("java_wrapper", phase_java_wrapper_common), ("declare_executable", phase_declare_executable), # no need to build an ijar for an executable - ("compile", phase_compile_scalatest), + ("compile", phase_compile_common), ("merge_jars", phase_merge_jars), ("runfiles", phase_runfiles_common), ("coverage_runfiles", phase_coverage_runfiles), From 3e5ece60487efcd096f5113ee3251ba7aa6fdffb Mon Sep 17 00:00:00 2001 From: Andy Scott Date: Mon, 1 Jun 2020 14:07:24 -0700 Subject: [PATCH 12/17] fix/cleanup --- scala/private/phases/phase_collect_jars.bzl | 9 +++++++ scala/private/phases/phases.bzl | 2 ++ scala/private/rules/unstable_scala_test.bzl | 24 +++++++------------ .../rules_scala/discover_tests_runner/BUILD | 5 ++-- 4 files changed, 22 insertions(+), 18 deletions(-) diff --git a/scala/private/phases/phase_collect_jars.bzl b/scala/private/phases/phase_collect_jars.bzl index 73181680b..bdc33f47e 100644 --- a/scala/private/phases/phase_collect_jars.bzl +++ b/scala/private/phases/phase_collect_jars.bzl @@ -18,6 +18,15 @@ def phase_collect_jars_scalatest(ctx, p): ) return _phase_collect_jars_default(ctx, p, args) +def phase_collect_jars_unstable_scala_test(ctx, p): + args = struct( + base_classpath = p.scalac_provider.default_classpath, + extra_runtime_deps = [ + ctx.attr._discover_tests_runner + ], + ) + return _phase_collect_jars_default(ctx, p, args) + def phase_collect_jars_repl(ctx, p): args = struct( base_classpath = p.scalac_provider.default_repl_classpath, diff --git a/scala/private/phases/phases.bzl b/scala/private/phases/phases.bzl index 7d37e6fa3..579fb0804 100644 --- a/scala/private/phases/phases.bzl +++ b/scala/private/phases/phases.bzl @@ -26,6 +26,7 @@ load( _phase_collect_jars_macro_library = "phase_collect_jars_macro_library", _phase_collect_jars_repl = "phase_collect_jars_repl", _phase_collect_jars_scalatest = "phase_collect_jars_scalatest", + _phase_collect_jars_unstable_scala_test = "phase_collect_jars_unstable_scala_test", ) load( "@io_bazel_rules_scala//scala/private:phases/phase_compile.bzl", @@ -113,6 +114,7 @@ phase_java_wrapper_repl = _phase_java_wrapper_repl phase_java_wrapper_common = _phase_java_wrapper_common # collect_jars +phase_collect_jars_unstable_scala_test = _phase_collect_jars_unstable_scala_test phase_collect_jars_scalatest = _phase_collect_jars_scalatest phase_collect_jars_repl = _phase_collect_jars_repl phase_collect_jars_macro_library = _phase_collect_jars_macro_library diff --git a/scala/private/rules/unstable_scala_test.bzl b/scala/private/rules/unstable_scala_test.bzl index 3ebf7f64b..b29dabf37 100644 --- a/scala/private/rules/unstable_scala_test.bzl +++ b/scala/private/rules/unstable_scala_test.bzl @@ -12,29 +12,23 @@ load("@io_bazel_rules_scala//scala/private:common_outputs.bzl", "common_outputs" load( "@io_bazel_rules_scala//scala/private:phases/phases.bzl", "extras_phases", - "phase_collect_jars_common", + "phase_collect_jars_unstable_scala_test", "phase_compile_common", + "phase_coverage_common", "phase_coverage_runfiles", "phase_declare_executable", "phase_default_info", "phase_dependency_common", + "phase_discover_tests", "phase_java_wrapper_common", "phase_merge_jars", - "phase_runfiles_common", + "phase_runfiles_scalatest", "phase_scalac_provider", - "phase_discover_tests", + "phase_write_executable_scalatest", "phase_write_manifest", - "phase_write_executable_common", "run_phases", ) -def _andy_hack_0(ctx, p): - return struct( - extra_runtime_deps = [ - ctx.attr._discover_tests_runner, - ], - ) - def _scala_test_impl(ctx): return run_phases( ctx, @@ -43,17 +37,17 @@ def _scala_test_impl(ctx): ("scalac_provider", phase_scalac_provider), ("write_manifest", phase_write_manifest), ("dependency", phase_dependency_common), - ("andy_hack_0", _andy_hack_0), - ("collect_jars", phase_collect_jars_common), + ("collect_jars", phase_collect_jars_unstable_scala_test), ("java_wrapper", phase_java_wrapper_common), ("declare_executable", phase_declare_executable), # no need to build an ijar for an executable ("compile", phase_compile_common), + ("coverage", phase_coverage_common), ("merge_jars", phase_merge_jars), - ("runfiles", phase_runfiles_common), + ("runfiles", phase_runfiles_scalatest), ("coverage_runfiles", phase_coverage_runfiles), ("discover_tests", phase_discover_tests), - ("write_executable", phase_write_executable_common), + ("write_executable", phase_write_executable_scalatest), ("default_info", phase_default_info), ], ) diff --git a/src/scala/io/bazel/rules_scala/discover_tests_runner/BUILD b/src/scala/io/bazel/rules_scala/discover_tests_runner/BUILD index 068be4078..54081261c 100644 --- a/src/scala/io/bazel/rules_scala/discover_tests_runner/BUILD +++ b/src/scala/io/bazel/rules_scala/discover_tests_runner/BUILD @@ -1,9 +1,8 @@ -load("//scala:defs.bzl", "scala_binary") +load("//scala:defs.bzl", "scala_library") -scala_binary( +scala_library( name = "discover_tests_runner", srcs = ["DiscoverTestsRunner.scala"], - main_class = "io.bazel.rules_scala.discover_tests_worker.DiscoverTestsRunner", visibility = ["//visibility:public"], deps = [ "//external:io_bazel_rules_scala/dependency/com_google_protobuf/protobuf_java", From b1374034023ff5ee30e3ac363f7978a231fd0684 Mon Sep 17 00:00:00 2001 From: Andy Scott Date: Mon, 1 Jun 2020 16:08:53 -0700 Subject: [PATCH 13/17] comment cleanup --- scala/defs.bzl | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/scala/defs.bzl b/scala/defs.bzl index 3348963d9..03eb6b3ab 100644 --- a/scala/defs.bzl +++ b/scala/defs.bzl @@ -1,10 +1,10 @@ -""" -Starlark rules for building Scala projects. +"""Starlark rules for building Scala projects. -These are the core rules under active development. Their APIs are -not guaranteed stable and we anticipate some breaking changes. +These are the core rules (library, binary, test) under active +development. Their APIs are not guaranteed stable and we anticipate +some breaking changes. -We do not recommend using these APIs for production codebases. Instead, +We do not yet recommend using these APIs for production codebases. Instead, use the stable rules exported by scala.bzl: ``` From b70fad36fc210daeb69f0a90fde835cfa2978278 Mon Sep 17 00:00:00 2001 From: Andy Scott Date: Mon, 1 Jun 2020 16:09:50 -0700 Subject: [PATCH 14/17] rm print --- scala/private/phases/phase_discover_tests.bzl | 2 -- 1 file changed, 2 deletions(-) diff --git a/scala/private/phases/phase_discover_tests.bzl b/scala/private/phases/phase_discover_tests.bzl index 4a8d4506a..225b867b4 100644 --- a/scala/private/phases/phase_discover_tests.bzl +++ b/scala/private/phases/phase_discover_tests.bzl @@ -1,6 +1,4 @@ def phase_discover_tests(ctx, p): - print('discover tests') - worker = ctx.attr._discover_tests_worker worker_inputs, _, worker_input_manifests = ctx.resolve_command( tools = [worker], From 4d5a63cc022110dfd5735db5bf104f928d841ec3 Mon Sep 17 00:00:00 2001 From: Andy Scott Date: Mon, 1 Jun 2020 16:17:52 -0700 Subject: [PATCH 15/17] top level comments --- .../discover_tests_runner/DiscoverTestsRunner.scala | 4 ++++ .../discover_tests_worker/DiscoverTestsWorker.scala | 8 ++++++++ 2 files changed, 12 insertions(+) diff --git a/src/scala/io/bazel/rules_scala/discover_tests_runner/DiscoverTestsRunner.scala b/src/scala/io/bazel/rules_scala/discover_tests_runner/DiscoverTestsRunner.scala index 0bf2403c6..e03734547 100644 --- a/src/scala/io/bazel/rules_scala/discover_tests_runner/DiscoverTestsRunner.scala +++ b/src/scala/io/bazel/rules_scala/discover_tests_runner/DiscoverTestsRunner.scala @@ -22,6 +22,10 @@ import java.nio.file.Paths import scala.collection.JavaConverters._ import scala.annotation.tailrec +/** + * DiscoverTestsRunner is responsible for running tests discovered by + * the DiscoverTestsWorker. + */ object DiscoverTestsRunner { def main(args: Array[String]): Unit = { diff --git a/src/scala/io/bazel/rules_scala/discover_tests_worker/DiscoverTestsWorker.scala b/src/scala/io/bazel/rules_scala/discover_tests_worker/DiscoverTestsWorker.scala index 8c3434ee8..9802ec8ea 100644 --- a/src/scala/io/bazel/rules_scala/discover_tests_worker/DiscoverTestsWorker.scala +++ b/src/scala/io/bazel/rules_scala/discover_tests_worker/DiscoverTestsWorker.scala @@ -21,11 +21,19 @@ import java.nio.file.Paths import scala.collection.JavaConverters._ +/** + * DiscoverTestsWorker is responsible for scanning jars to indentify + * classes and modules that conform to the SBT testing interface. + * + * Identified tests are written to a protobuf output file so a separate + * test runner can handle test execution. + */ object DiscoverTestsWorker extends Worker.Interface { def main(args: Array[String]): Unit = Worker.workerMain(args, DiscoverTestsWorker) def work(args: Array[String]): Unit = { + // argument format: + -- + val outputFile = Paths.get(args(0)).toFile val (args0, args1) = args.tail.span(_ != "--") val testJars = args0.map(f => Paths.get(f).toUri.toURL) From 6de20bf9b1727a82f000937399cb69270710a9da Mon Sep 17 00:00:00 2001 From: Andy Scott Date: Tue, 2 Jun 2020 10:01:02 -0700 Subject: [PATCH 16/17] cleanup/comments --- .../DiscoverTestsWorker.scala | 150 ++++++++++-------- .../discovered_tests.proto | 10 ++ 2 files changed, 96 insertions(+), 64 deletions(-) diff --git a/src/scala/io/bazel/rules_scala/discover_tests_worker/DiscoverTestsWorker.scala b/src/scala/io/bazel/rules_scala/discover_tests_worker/DiscoverTestsWorker.scala index 9802ec8ea..5ef36d0b8 100644 --- a/src/scala/io/bazel/rules_scala/discover_tests_worker/DiscoverTestsWorker.scala +++ b/src/scala/io/bazel/rules_scala/discover_tests_worker/DiscoverTestsWorker.scala @@ -39,13 +39,16 @@ object DiscoverTestsWorker extends Worker.Interface { val testJars = args0.map(f => Paths.get(f).toUri.toURL) val frameworkJars = args1.tail.map(f => Paths.get(f).toUri.toURL) + // prep the scanner used to identify testing frameworks val frameworkClassloader = new URLClassLoader(frameworkJars) - val frameworkScanResult = (new ClassGraph) + val frameworkScanResult: ScanResult = (new ClassGraph) .overrideClassLoaders(frameworkClassloader) .ignoreParentClassLoaders .enableClassInfo.scan - val testScanResult = (new ClassGraph) + // prep the scanner used to find tests + // here we need the full classpath + val testScanResult: ScanResult = (new ClassGraph) .overrideClassLoaders(new URLClassLoader(testJars ++ frameworkJars)) .ignoreParentClassLoaders .enableClassInfo @@ -53,69 +56,14 @@ object DiscoverTestsWorker extends Worker.Interface { .enableAnnotationInfo .scan - val result: Result = frameworkScanResult + val resultBuilder: Result.Builder = Result.newBuilder + + // start identifying frameworks and tests + frameworkScanResult .getClassesImplementing("sbt.testing.Framework").asScala - .foldLeft(Result.newBuilder) { (resultBuilder, framework) => - val frameworkInstance = framework.loadClass.newInstance.asInstanceOf[Framework] - resultBuilder.addFrameworkDiscoveries( - frameworkInstance.fingerprints.foldLeft(FrameworkDiscovery.newBuilder.setFramework(framework.getName))((b, f) => f match { - case fingerprint: SubclassFingerprint => - - val getCandidates: ScanResult => ClassInfoList = - if (frameworkScanResult.getClassInfo(fingerprint.superclassName).isInterface) - _.getClassesImplementing(fingerprint.superclassName) - else - _.getSubclasses(fingerprint.superclassName) - - val candidates: Iterable[ClassInfo] = - getCandidates(testScanResult) - .exclude(getCandidates(frameworkScanResult)) - .asScala - .filter(_.isStandardClass) - - val tests: Iterable[String] = - if (fingerprint.isModule) - candidates - .map(_.getName) - .filter(_.endsWith("$")).map(_.dropRight(1)) - else - candidates - .filter(_.getConstructorInfo.asScala.exists(_.getParameterInfo.isEmpty) == fingerprint.requireNoArgConstructor) - .map(_.getName) - .filterNot(_.endsWith("$")) - - b.addSubclassDiscoveries( - SubclassDiscovery.newBuilder - .setSuperclassName(fingerprint.superclassName) - .setIsModule(fingerprint.isModule) - .setRequireNoArgConstructor(fingerprint.requireNoArgConstructor) - .addAllTests(tests.asJava) - .build) - case fingerprint: AnnotatedFingerprint => - - val candidates: Iterable[ClassInfo] = - testScanResult.getClassesWithAnnotation(fingerprint.annotationName) - .union(testScanResult.getClassesWithMethodAnnotation(fingerprint.annotationName)) - .asScala - - val tests: Iterable[String] = - if (fingerprint.isModule) - candidates - .map(_.getName) - .filter(_.endsWith("$")).map(_.dropRight(1)) - else - candidates - .map(_.getName) - .filterNot(_.endsWith("$")) - - b.addAnnotatedDiscoveries( - AnnotatedDiscovery.newBuilder - .setAnnotationName(fingerprint.annotationName) - .setIsModule(fingerprint.isModule) - .addAllTests(tests.asJava) - .build) - }).build) - }.build + .foreach(handleFramework(frameworkScanResult, testScanResult, resultBuilder, _)) + + val result: Result = resultBuilder.build testScanResult.close() frameworkScanResult.close() @@ -124,4 +72,78 @@ object DiscoverTestsWorker extends Worker.Interface { result.writeTo(os) os.close() } + + private[this] def handleFramework(frameworkScanResult: ScanResult, testScanResult: ScanResult, builder: Result.Builder, framework: ClassInfo): Unit = { + val frameworkInstance = framework.loadClass.newInstance.asInstanceOf[Framework] + + val frameworkDiscoveryBuilder = FrameworkDiscovery.newBuilder.setFramework(framework.getName) + frameworkInstance.fingerprints.foreach { + case sf: SubclassFingerprint => handleSubclassFingerprint(frameworkScanResult, testScanResult, frameworkDiscoveryBuilder, sf) + case af: AnnotatedFingerprint => handleAnnotatedFingerprint(frameworkScanResult, testScanResult, frameworkDiscoveryBuilder, af) + } + builder.addFrameworkDiscoveries(frameworkDiscoveryBuilder.build) + } + + private[this] def handleSubclassFingerprint(frameworkScanResult: ScanResult, testScanResult: ScanResult, builder: FrameworkDiscovery.Builder, fingerprint: SubclassFingerprint): Unit = { + // + // with the ClassGraph API we need to identify tests differently if they're implementing + // an interface instead of a class + // + // this logic is captured as a function so we can call it a few times + val getCandidates: ScanResult => ClassInfoList = + if (frameworkScanResult.getClassInfo(fingerprint.superclassName).isInterface) + _.getClassesImplementing(fingerprint.superclassName) + else + _.getSubclasses(fingerprint.superclassName) + + val candidates: Iterable[ClassInfo] = + getCandidates(testScanResult) + .exclude(getCandidates(frameworkScanResult)) + .asScala + .filter(_.isStandardClass) + + val tests: Iterable[String] = + if (fingerprint.isModule) + candidates + .map(_.getName) + .filter(_.endsWith("$")).map(_.dropRight(1)) + else + candidates + .filter(_.getConstructorInfo.asScala.exists(_.getParameterInfo.isEmpty) == fingerprint.requireNoArgConstructor) + .map(_.getName) + .filterNot(_.endsWith("$")) + + builder.addSubclassDiscoveries( + SubclassDiscovery.newBuilder + .setSuperclassName(fingerprint.superclassName) + .setIsModule(fingerprint.isModule) + .setRequireNoArgConstructor(fingerprint.requireNoArgConstructor) + .addAllTests(tests.asJava) + .build) + } + + private[this] def handleAnnotatedFingerprint(frameworkScanResult: ScanResult, testScanResult: ScanResult, builder: FrameworkDiscovery.Builder, fingerprint: AnnotatedFingerprint): Unit = { + val candidates: Iterable[ClassInfo] = + testScanResult.getClassesWithAnnotation(fingerprint.annotationName) + .union(testScanResult.getClassesWithMethodAnnotation(fingerprint.annotationName)) + .asScala + + // note: "$" is part of Scala's JVM encoding for modules + val tests: Iterable[String] = + if (fingerprint.isModule) + candidates + .map(_.getName) + .filter(_.endsWith("$")).map(_.dropRight(1)) + else + candidates + .map(_.getName) + .filterNot(_.endsWith("$")) + + builder.addAnnotatedDiscoveries( + AnnotatedDiscovery.newBuilder + .setAnnotationName(fingerprint.annotationName) + .setIsModule(fingerprint.isModule) + .addAllTests(tests.asJava) + .build) + } } diff --git a/src/scala/io/bazel/rules_scala/discover_tests_worker/discovered_tests.proto b/src/scala/io/bazel/rules_scala/discover_tests_worker/discovered_tests.proto index 1c9aaff38..1c00e2f34 100644 --- a/src/scala/io/bazel/rules_scala/discover_tests_worker/discovered_tests.proto +++ b/src/scala/io/bazel/rules_scala/discover_tests_worker/discovered_tests.proto @@ -4,6 +4,16 @@ package io.bazel.rules_scala.discover_tests_worker; option java_package = "io.bazel.rules_scala.discover_tests_worker"; +/* + * The types here are used to list/describe tests conforming + * to the SBT testing interface: + * + * https://github.com/sbt/test-interface/tree/master/src/main/java/sbt/testing + * + * A "result" lists tests associated with any number of testing frameworks + * implementing the SBT testing interface. + */ + message Result { repeated FrameworkDiscovery frameworkDiscoveries = 1; } From a3d7605e8d09f71fd8701463a1082eda12c7d150 Mon Sep 17 00:00:00 2001 From: Andy Scott Date: Tue, 2 Jun 2020 12:18:48 -0700 Subject: [PATCH 17/17] fix lint errors --- scala/private/macros/scala_repositories.bzl | 1 - scala/private/phases/phase_collect_jars.bzl | 2 +- scala/private/phases/phases.bzl | 2 +- .../rules_scala/discover_tests_worker/BUILD | 24 +++++++++---------- test/v2/BUILD | 20 ++++++++-------- 5 files changed, 24 insertions(+), 25 deletions(-) diff --git a/scala/private/macros/scala_repositories.bzl b/scala/private/macros/scala_repositories.bzl index ab459460b..41e07e49e 100644 --- a/scala/private/macros/scala_repositories.bzl +++ b/scala/private/macros/scala_repositories.bzl @@ -156,7 +156,6 @@ def scala_repositories( server_urls = maven_servers, ) - if not native.existing_rule("com_google_protobuf"): http_archive( name = "com_google_protobuf", diff --git a/scala/private/phases/phase_collect_jars.bzl b/scala/private/phases/phase_collect_jars.bzl index bdc33f47e..108f766f4 100644 --- a/scala/private/phases/phase_collect_jars.bzl +++ b/scala/private/phases/phase_collect_jars.bzl @@ -22,7 +22,7 @@ def phase_collect_jars_unstable_scala_test(ctx, p): args = struct( base_classpath = p.scalac_provider.default_classpath, extra_runtime_deps = [ - ctx.attr._discover_tests_runner + ctx.attr._discover_tests_runner, ], ) return _phase_collect_jars_default(ctx, p, args) diff --git a/scala/private/phases/phases.bzl b/scala/private/phases/phases.bzl index 579fb0804..929a59f7d 100644 --- a/scala/private/phases/phases.bzl +++ b/scala/private/phases/phases.bzl @@ -139,7 +139,7 @@ phase_runfiles_common = _phase_runfiles_common # default_info phase_default_info = _phase_default_info - # discover_tests +# discover_tests phase_discover_tests = _phase_discover_tests # scalafmt diff --git a/src/scala/io/bazel/rules_scala/discover_tests_worker/BUILD b/src/scala/io/bazel/rules_scala/discover_tests_worker/BUILD index f106d93fa..3e509a775 100644 --- a/src/scala/io/bazel/rules_scala/discover_tests_worker/BUILD +++ b/src/scala/io/bazel/rules_scala/discover_tests_worker/BUILD @@ -1,17 +1,17 @@ load("//scala:defs.bzl", "scala_binary") scala_binary( - name = "discover_tests_worker", - srcs = ["DiscoverTestsWorker.scala"], - main_class = "io.bazel.rules_scala.discover_tests_worker.DiscoverTestsWorker", - deps = [ - "@io_bazel_rules_scala_classgraph//jar:jar", - "@io_bazel_rules_scala//src/java/io/bazel/rulesscala/worker", - "@io_bazel_rules_scala_test_interface//jar:jar", - ":discovered_tests_java_proto", - "//external:io_bazel_rules_scala/dependency/com_google_protobuf/protobuf_java", - ], - visibility = ["//visibility:public"], + name = "discover_tests_worker", + srcs = ["DiscoverTestsWorker.scala"], + main_class = "io.bazel.rules_scala.discover_tests_worker.DiscoverTestsWorker", + visibility = ["//visibility:public"], + deps = [ + ":discovered_tests_java_proto", + "//external:io_bazel_rules_scala/dependency/com_google_protobuf/protobuf_java", + "@io_bazel_rules_scala//src/java/io/bazel/rulesscala/worker", + "@io_bazel_rules_scala_classgraph//jar", + "@io_bazel_rules_scala_test_interface//jar", + ], ) proto_library( @@ -21,6 +21,6 @@ proto_library( java_proto_library( name = "discovered_tests_java_proto", - deps = [":discovered_tests_proto"], visibility = ["//visibility:public"], + deps = [":discovered_tests_proto"], ) diff --git a/test/v2/BUILD b/test/v2/BUILD index 31e93617d..db8398ece 100644 --- a/test/v2/BUILD +++ b/test/v2/BUILD @@ -21,18 +21,18 @@ scala_library( scala_test( name = "test", srcs = ["test.scala"], + runtime_deps = [ + "@com_novocode_junit_interface", + # this would normally get exported by scalatest if it was set up as a proper dep + # instead of just a jar + "//external:io_bazel_rules_scala/dependency/scala/scala_xml", + # same, but for junit + "@io_bazel_rules_scala_org_hamcrest_hamcrest_core", + ], deps = [ ":library", - "@org_scalacheck_scalacheck//:org_scalacheck_scalacheck", - "@io_bazel_rules_scala_junit_junit//:io_bazel_rules_scala_junit_junit", "//external:io_bazel_rules_scala/dependency/scalatest/scalatest", + "@io_bazel_rules_scala_junit_junit", + "@org_scalacheck_scalacheck", ], - runtime_deps = [ - "@com_novocode_junit_interface//:com_novocode_junit_interface", - # this would normally get exported by scalatest if it was set up as a proper dep - # instead of just a jar - "//external:io_bazel_rules_scala/dependency/scala/scala_xml", - # same, but for junit - "@io_bazel_rules_scala_org_hamcrest_hamcrest_core//:io_bazel_rules_scala_org_hamcrest_hamcrest_core", - ] )