-
-
Couldn't load subscription status.
- Fork 289
Junit & Specs2 support #163
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
7e2e198
34620c8
41c0a48
60caa69
410018d
287921f
6fd76c9
87dbf01
ea10b40
9effdb9
f9f084c
3158e76
0033f21
d0e7089
5c57a83
5161db1
99a639c
eb7095f
30fef56
64a6404
97583a1
558640d
d52045a
c228907
0f6cd99
9dd6e56
3524c4e
5572178
66f5eb3
b8faa37
4cc722d
853679a
bb23f05
0325608
fe20920
22ccd22
7d1a915
845fb2f
39824a7
d71bc1d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| def junit_repositories(): | ||
| native.maven_jar( | ||
| name = "io_bazel_rules_scala_junit_junit", | ||
| artifact = "junit:junit:4.12", | ||
| sha1 = "2973d150c0dc1fefe998f834810d68f278ea58ec", | ||
| ) | ||
| native.bind(name = 'io_bazel_rules_scala/dependency/junit/junit', actual = '@io_bazel_rules_scala_junit_junit//jar') | ||
|
|
||
| native.maven_jar( | ||
| name = "io_bazel_rules_scala_org_hamcrest_hamcrest_core", | ||
| artifact = "org.hamcrest:hamcrest-core:1.3", | ||
| sha1 = "42a25dc3219429f0e5d060061f71acb49bf010a0", | ||
| ) | ||
| native.bind(name = 'io_bazel_rules_scala/dependency/hamcrest/hamcrest_core', actual = '@io_bazel_rules_scala_org_hamcrest_hamcrest_core//jar') |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -14,6 +14,7 @@ | |
|
|
||
| """Rules for supporting the Scala language.""" | ||
|
|
||
| load("//specs2:specs2_junit.bzl", "specs2_junit_dependencies") | ||
| _jar_filetype = FileType([".jar"]) | ||
| _java_filetype = FileType([".java"]) | ||
| _scala_filetype = FileType([".scala"]) | ||
|
|
@@ -535,8 +536,7 @@ def _scala_test_impl(ctx): | |
| jars = _collect_jars(deps) | ||
| (cjars, rjars) = (jars.compiletime, jars.runtime) | ||
| cjars += [ctx.file._scalareflect, ctx.file._scalatest, ctx.file._scalaxml] | ||
| rjars += [ | ||
| ctx.outputs.jar, | ||
| rjars += [ctx.outputs.jar, | ||
| ctx.file._scalalib, | ||
| ctx.file._scalareflect, | ||
| ctx.file._scalatest, | ||
|
|
@@ -561,6 +561,40 @@ def _scala_test_impl(ctx): | |
| ) | ||
| return _scala_binary_common(ctx, cjars, rjars) | ||
|
|
||
| def _gen_test_suite_flags_based_on_prefixes_and_suffixes(ctx, archive): | ||
| return struct(suite_class = "io.bazel.rulesscala.test_discovery.DiscoveredTestSuite", | ||
| archiveFlag = "-Dbazel.discover.classes.archive.file.path=%s" % archive.short_path, | ||
| prefixesFlag = "-Dbazel.discover.classes.prefixes=%s" % ",".join(ctx.attr.prefixes), | ||
| suffixesFlag = "-Dbazel.discover.classes.suffixes=%s" % ",".join(ctx.attr.suffixes), | ||
| printFlag = "-Dbazel.discover.classes.print.discovered=%s" % ctx.attr.print_discovered_classes) | ||
|
|
||
| def _scala_junit_test_impl(ctx): | ||
| if (not(ctx.attr.prefixes) and not(ctx.attr.suffixes)): | ||
| fail("Setting at least one of the attributes ('prefixes','suffixes') is required") | ||
| deps = ctx.attr.deps + [ctx.attr._suite] | ||
| jars = _collect_jars(deps) | ||
| (cjars, rjars) = (jars.compiletime, jars.runtime) | ||
| junit_deps = [ctx.file._junit,ctx.file._hamcrest] | ||
| cjars += junit_deps | ||
| rjars += [ctx.outputs.jar, | ||
| ctx.file._scalalib | ||
| ] + junit_deps | ||
| rjars += _collect_jars(ctx.attr.runtime_deps).runtime | ||
| test_suite = _gen_test_suite_flags_based_on_prefixes_and_suffixes(ctx, ctx.outputs.jar) | ||
| launcherJvmFlags = ["-ea", test_suite.archiveFlag, test_suite.prefixesFlag, test_suite.suffixesFlag, test_suite.printFlag] | ||
| _write_launcher( | ||
| ctx = ctx, | ||
| rjars = rjars, | ||
| main_class = "org.junit.runner.JUnitCore", | ||
| jvm_flags = launcherJvmFlags + ctx.attr.jvm_flags, | ||
| args = test_suite.suite_class, | ||
| run_before_binary = "", | ||
| run_after_binary = "", | ||
| ) | ||
|
|
||
| return _scala_binary_common(ctx, cjars, rjars) | ||
|
|
||
|
|
||
| _implicit_deps = { | ||
| "_ijar": attr.label(executable=True, cfg="host", default=Label("@bazel_tools//tools/jdk:ijar"), allow_files=True), | ||
| "_scalac": attr.label(executable=True, cfg="host", default=Label("//src/java/io/bazel/rulesscala/scalac"), allow_files=True), | ||
|
|
@@ -687,6 +721,30 @@ exports_files([ | |
| "lib/scala-xml_2.11-1.0.4.jar", | ||
| "lib/scalap-2.11.8.jar", | ||
| ]) | ||
|
|
||
| filegroup( | ||
| name = "scala-xml", | ||
| srcs = ["lib/scala-xml_2.11-1.0.4.jar"], | ||
| visibility = ["//visibility:public"], | ||
| ) | ||
|
|
||
| filegroup( | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why are these needed at all rather than |
||
| name = "scala-parser-combinators", | ||
| srcs = ["lib/scala-parser-combinators_2.11-1.0.4.jar"], | ||
| visibility = ["//visibility:public"], | ||
| ) | ||
|
|
||
| filegroup( | ||
| name = "scala-library", | ||
| srcs = ["lib/scala-library.jar"], | ||
| visibility = ["//visibility:public"], | ||
| ) | ||
|
|
||
| filegroup( | ||
| name = "scala-reflect", | ||
| srcs = ["lib/scala-reflect.jar"], | ||
| visibility = ["//visibility:public"], | ||
| ) | ||
| """ | ||
|
|
||
| def scala_repositories(): | ||
|
|
@@ -722,6 +780,14 @@ def scala_repositories(): | |
| server = "scalac_deps_maven_server", | ||
| ) | ||
|
|
||
| native.bind(name = 'io_bazel_rules_scala/dependency/scala/scala_xml', actual = '@scala//:scala-xml') | ||
|
|
||
| native.bind(name = 'io_bazel_rules_scala/dependency/scala/parser_combinators', actual = '@scala//:scala-parser-combinators') | ||
|
|
||
| native.bind(name = 'io_bazel_rules_scala/dependency/scala/scala_library', actual = '@scala//:scala-library') | ||
|
|
||
| native.bind(name = 'io_bazel_rules_scala/dependency/scala/scala_reflect', actual = '@scala//:scala-reflect') | ||
|
|
||
| def scala_export_to_java(name, exports, runtime_deps): | ||
| jars = [] | ||
| for target in exports: | ||
|
|
@@ -792,4 +858,26 @@ def scala_library_suite(name, | |
| ts.append(n) | ||
| scala_library(name = name, deps = ts, exports = exports + ts, visibility = visibility) | ||
|
|
||
| scala_junit_test = rule( | ||
| implementation=_scala_junit_test_impl, | ||
| attrs= _implicit_deps + _common_attrs + { | ||
| "prefixes": attr.string_list(default=[]), | ||
| "suffixes": attr.string_list(default=[]), | ||
| "print_discovered_classes": attr.bool(default=False, mandatory=False), | ||
| "_junit": attr.label(default=Label("//external:io_bazel_rules_scala/dependency/junit/junit"), single_file=True), | ||
| "_hamcrest": attr.label(default=Label("//external:io_bazel_rules_scala/dependency/hamcrest/hamcrest_core"), single_file=True), | ||
| "_suite": attr.label(default=Label("//src/java/io/bazel/rulesscala/test_discovery:test_discovery")), | ||
| }, | ||
| outputs={ | ||
| "jar": "%{name}.jar", | ||
| "deploy_jar": "%{name}_deploy.jar", | ||
| "manifest": "%{name}_MANIFEST.MF", | ||
| }, | ||
| test=True, | ||
| ) | ||
|
|
||
| def scala_specs2_junit_test(name, **kwargs): | ||
| scala_junit_test( | ||
| name = name, | ||
| deps = specs2_junit_dependencies() + kwargs.pop("deps",[]), | ||
| **kwargs) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,50 @@ | ||
| def specs2_version(): | ||
| return "3.8.8" | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. do we need this special function? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. answered in the other comment about it |
||
| def specs2_repositories(): | ||
|
|
||
| native.maven_jar( | ||
| name = "io_bazel_rules_scala_org_specs2_specs2_core", | ||
| artifact = "org.specs2:specs2-core_2.11:" + specs2_version(), | ||
| sha1 = "495bed00c73483f4f5f43945fde63c615d03e637", | ||
| ) | ||
| native.bind(name = 'io_bazel_rules_scala/dependency/specs2/specs2_core', actual = '@io_bazel_rules_scala_org_specs2_specs2_core//jar') | ||
|
|
||
| native.maven_jar( | ||
| name = "io_bazel_rules_scala_org_specs2_specs2_common", | ||
| artifact = "org.specs2:specs2-common_2.11:" + specs2_version(), | ||
| sha1 = "15bc009eaae3a574796c0f558d8696b57ae903c3", | ||
| ) | ||
| native.bind(name = 'io_bazel_rules_scala/dependency/specs2/specs2_common', actual = '@io_bazel_rules_scala_org_specs2_specs2_common//jar') | ||
|
|
||
| native.maven_jar( | ||
| name = "io_bazel_rules_scala_org_specs2_specs2_matcher", | ||
| artifact = "org.specs2:specs2-matcher_2.11:" + specs2_version(), | ||
| sha1 = "d2e967737abef7421e47b8994a8c92784e624d62", | ||
| ) | ||
| native.bind(name = 'io_bazel_rules_scala/dependency/specs2/specs2_matcher', actual = '@io_bazel_rules_scala_org_specs2_specs2_matcher//jar') | ||
|
|
||
| native.maven_jar( | ||
| name = "io_bazel_rules_scala_org_scalaz_scalaz_effect", | ||
| artifact = "org.scalaz:scalaz-effect_2.11:7.2.7", | ||
| sha1 = "824bbb83da12224b3537c354c51eb3da72c435b5", | ||
| ) | ||
| native.bind(name = 'io_bazel_rules_scala/dependency/scalaz/scalaz_effect', actual = '@io_bazel_rules_scala_org_scalaz_scalaz_effect//jar') | ||
|
|
||
| native.maven_jar( | ||
| name = "io_bazel_rules_scala_org_scalaz_scalaz_core", | ||
| artifact = "org.scalaz:scalaz-core_2.11:7.2.7", | ||
| sha1 = "ebf85118d0bf4ce18acebf1d8475ee7deb7f19f1", | ||
| ) | ||
| native.bind(name = 'io_bazel_rules_scala/dependency/scalaz/scalaz_core', actual = '@io_bazel_rules_scala_org_scalaz_scalaz_core//jar') | ||
|
|
||
| def specs2_dependencies(): | ||
| return [ | ||
| "//external:io_bazel_rules_scala/dependency/specs2/specs2_core", | ||
| "//external:io_bazel_rules_scala/dependency/specs2/specs2_common", | ||
| "//external:io_bazel_rules_scala/dependency/specs2/specs2_matcher", | ||
| "//external:io_bazel_rules_scala/dependency/scalaz/scalaz_effect", | ||
| "//external:io_bazel_rules_scala/dependency/scalaz/scalaz_core", | ||
| "//external:io_bazel_rules_scala/dependency/scala/scala_xml", | ||
| "//external:io_bazel_rules_scala/dependency/scala/parser_combinators", | ||
| "//external:io_bazel_rules_scala/dependency/scala/scala_library", | ||
| "//external:io_bazel_rules_scala/dependency/scala/scala_reflect"] | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| load("//specs2:specs2.bzl", "specs2_repositories", "specs2_dependencies", "specs2_version") | ||
| load("//junit:junit.bzl", "junit_repositories") | ||
|
|
||
| def specs2_junit_repositories(): | ||
| specs2_repositories() | ||
| junit_repositories() | ||
| # Aditional dependencies for specs2 junit runner | ||
| native.maven_jar( | ||
| name = "io_bazel_rules_scala_org_specs2_specs2_junit_2_11", | ||
| artifact = "org.specs2:specs2-junit_2.11:" + specs2_version(), | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't see how that function helps (specs2_version) since only one version will have the right sha1 below, no (unless someone generates a sha1 collision for specs2! ;) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As I mentioned in the PR notes the reason I added this was because I (mistakenly) used one version in the specs2.bzl file and a different one in the specs2_junit.bzl file. Finding that bug took me a few hours since the runtime error messages are really unclear. Once I found it out I realized my problem was code duplication so I solved it with a method. |
||
| sha1 = "1dc9e43970557c308ee313842d84094bc6c1c1b5", | ||
| ) | ||
| native.bind(name = 'io_bazel_rules_scala/dependency/specs2/specs2_junit', actual = '@io_bazel_rules_scala_org_specs2_specs2_junit_2_11//jar') | ||
|
|
||
| def specs2_junit_dependencies(): | ||
| return specs2_dependencies() + ["//external:io_bazel_rules_scala/dependency/specs2/specs2_junit"] | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| load("//scala:scala.bzl", "scala_library") | ||
|
|
||
|
|
||
| scala_library(name = "test_discovery", | ||
| srcs = ["DiscoveredTestSuite.scala"], | ||
| deps = ["//external:io_bazel_rules_scala/dependency/junit/junit"], | ||
| visibility = ["//visibility:public"], | ||
| ) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,128 @@ | ||
| package io.bazel.rulesscala.test_discovery | ||
|
|
||
| import org.junit.runner.RunWith | ||
| import org.junit.runners.Suite | ||
| import org.junit.runners.model.RunnerBuilder | ||
| import java.io.File | ||
| import java.io.FileInputStream | ||
| import java.util.jar.JarInputStream | ||
| import java.util.jar.JarEntry | ||
| /* | ||
| The test running and discovery mechanism works in the following manner: | ||
| - Bazel rule executes a JVM application to run tests (currently `JUnitCore`) and asks it to run | ||
| the `DiscoveredTestSuite` suite. | ||
| - When JUnit tries to run it, it uses the `PrefixSuffixTestDiscoveringSuite` runner | ||
| to know what tests exist in the suite. | ||
| - We know which tests to run by examining the entries of the target's archive. | ||
| - The archive's path is passed in a system property ("bazel.discover.classes.archive.file.path"). | ||
| - The entries of the archive are filtered to keep only classes | ||
| - Of those we filter again and keep only those which match either of the prefixes/suffixes supplied. | ||
| - Prefixes are supplied as a comma separated list. System property ("bazel.discover.classes.prefixes") | ||
| - Suffixes are supplied as a comma separated list. System property ("bazel.discover.classes.prefixes") | ||
| - We iterate over the remaining entries and format them into classes. | ||
| - At this point we tell JUnit (via the `RunnerBuilder`) what are the discovered test classes. | ||
| - W.R.T. discovery semantics this is similar to how maven surefire/failsafe plugins work. | ||
| - For debugging purposes one can ask to print the list of discovered classes. | ||
| - This is done via an `print_discovered_classes` attribute. | ||
| - The attribute is sent via "bazel.discover.classes.print.discovered" | ||
|
|
||
| Additional references: | ||
| - http://junit.org/junit4/javadoc/4.12/org/junit/runner/RunWith.html | ||
| - http://junit.org/junit4/javadoc/4.12/org/junit/runners/model/RunnerBuilder.html | ||
| - http://maven.apache.org/surefire/maven-surefire-plugin/examples/inclusion-exclusion.html | ||
| */ | ||
| @RunWith(classOf[PrefixSuffixTestDiscoveringSuite]) | ||
| class DiscoveredTestSuite | ||
|
|
||
| class PrefixSuffixTestDiscoveringSuite(testClass: Class[Any], builder: RunnerBuilder) | ||
| extends Suite(builder, testClass, PrefixSuffixTestDiscoveringSuite.discoverClasses()) | ||
|
|
||
| object PrefixSuffixTestDiscoveringSuite { | ||
|
|
||
| private def discoverClasses(): Array[Class[_]] = { | ||
|
|
||
| val archive = archiveInputStream() | ||
| val classes = discoverClasses(archive, prefixes, suffixesWithClassSuffix) | ||
| archive.close() | ||
| if (printDiscoveredClasses) { | ||
| println("Discovered classes:") | ||
| classes.foreach(c => println(c.getName)) | ||
| } | ||
| if (classes.isEmpty) | ||
| throw new IllegalStateException("Was not able to discover any classes " + | ||
| s"for archive=$archivePath, " + | ||
| s"prefixes=$prefixes, " + | ||
| s"suffixes=$suffixes") | ||
| classes | ||
| } | ||
|
|
||
| private def discoverClasses(archive: JarInputStream, | ||
| prefixes: Set[String], | ||
| suffixes: Set[String]): Array[Class[_]] = | ||
| matchingEntries(archive, prefixes, suffixes) | ||
| .map(dropFileSuffix) | ||
| .map(fileToClassFormat) | ||
| .map(Class.forName) | ||
| .toArray | ||
|
|
||
| private def matchingEntries(archive: JarInputStream, | ||
| prefixes: Set[String], | ||
| suffixes: Set[String]) = | ||
| entries(archive) | ||
| .filter(isClass) | ||
| .filter(entry => endsWith(suffixes)(entry) || startsWith(prefixes)(entry)) | ||
|
|
||
| private def startsWith(prefixes: Set[String])(entry: String): Boolean = { | ||
| val entryName = entryFileName(entry) | ||
| prefixes.exists(entryName.startsWith) | ||
| } | ||
|
|
||
| private def endsWith(suffixes: Set[String])(entry: String): Boolean = { | ||
| val entryName = entryFileName(entry) | ||
| suffixes.exists(entryName.endsWith) | ||
| } | ||
|
|
||
| private def entryFileName(entry: String): String = | ||
| new File(entry).getName | ||
|
|
||
| private def dropFileSuffix(classEntry: String): String = | ||
| classEntry.split("\\.").head | ||
|
|
||
| private def fileToClassFormat(classEntry: String): String = | ||
| classEntry.replace('/', '.') | ||
|
|
||
| private def isClass(entry: String): Boolean = | ||
| entry.endsWith(".class") | ||
|
|
||
| private def entries(jarInputStream: JarInputStream) = | ||
| Stream.continually(Option(jarInputStream.getNextJarEntry)) | ||
| .takeWhile(_.isDefined) | ||
| .flatten | ||
| .map(_.getName) | ||
| .toList | ||
|
|
||
| private def archiveInputStream() = | ||
| new JarInputStream(new FileInputStream(archivePath)) | ||
|
|
||
| private def archivePath: String = | ||
| System.getProperty("bazel.discover.classes.archive.file.path") | ||
|
|
||
| private def suffixesWithClassSuffix: Set[String] = | ||
| suffixes.map(_ + ".class") | ||
|
|
||
| private def suffixes: Set[String] = | ||
| parseProperty(System.getProperty("bazel.discover.classes.suffixes")) | ||
|
|
||
| private def prefixes: Set[String] = | ||
| parseProperty(System.getProperty("bazel.discover.classes.prefixes")) | ||
|
|
||
| private def parseProperty(potentiallyEmpty: String): Set[String] = | ||
| potentiallyEmpty.trim match { | ||
| case emptyStr if emptyStr.isEmpty => Set[String]() | ||
| case nonEmptyStr => nonEmptyStr.split(",").toSet | ||
| } | ||
|
|
||
| private def printDiscoveredClasses: Boolean = | ||
| System.getProperty("bazel.discover.classes.print.discovered").toBoolean | ||
|
|
||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we already have these here:
https://github.com/bazelbuild/rules_scala/blob/master/src/scala/BUILD#L1
Can we consolidate on those targets? Why were these needed?
I'm concerned about supporting 2.12 and 2.11 with all these direct references in different places.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we can, they were needed since using "@scala//:lib/scala-reflect.jar" as a label didn't work for me. If you know how I can skip that I'd love to know. I know my PR notes were long but this is one of the things I noted there.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
:( Out of luck.
Any idea why I'm getting this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
isn't the target
//src/scala:scala_xml? It seems you have:parser_xml.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
any comment about this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, moved to
//src/scala.Gave
@scala//:lib/scala-library.jaranother try but got the following message so am staying with the filegroup.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@johnynek
//src/scaladoesn't work when I try to use it in an external repository.I think it's because it's a macro.
If I prefix it with the name of the rules_scala repo in my workspace then it works but that doesn't sound like the right solution since people might name it otherwise, right?
Any thoughts?