{"id":28929556,"url":"https://github.com/dubinsky/scalajs-gradle","last_synced_at":"2025-10-15T15:09:33.620Z","repository":{"id":49929392,"uuid":"472127599","full_name":"dubinsky/scalajs-gradle","owner":"dubinsky","description":"Gradle plugin for multi-backend Scala and sbt test frameworks","archived":false,"fork":false,"pushed_at":"2025-10-05T05:35:03.000Z","size":1540,"stargazers_count":10,"open_issues_count":5,"forks_count":2,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-10-05T07:21:24.237Z","etag":null,"topics":["airspec","crosscompile","gradle","hedgehog","junit4","munit","sbt","scala","scalacheck","scalajs","scalanative","scalaprops","scalatest","specs2","testing","utes","weaver","ziotest"],"latest_commit_sha":null,"homepage":"","language":"Scala","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/dubinsky.png","metadata":{"files":{"readme":"README.adoc","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2022-03-20T23:25:18.000Z","updated_at":"2025-10-05T05:33:51.000Z","dependencies_parsed_at":"2023-12-12T17:59:22.585Z","dependency_job_id":"709e677c-547b-458f-9f57-9ce8fe93de4d","html_url":"https://github.com/dubinsky/scalajs-gradle","commit_stats":null,"previous_names":[],"tags_count":26,"template":false,"template_full_name":null,"purl":"pkg:github/dubinsky/scalajs-gradle","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dubinsky%2Fscalajs-gradle","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dubinsky%2Fscalajs-gradle/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dubinsky%2Fscalajs-gradle/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dubinsky%2Fscalajs-gradle/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/dubinsky","download_url":"https://codeload.github.com/dubinsky/scalajs-gradle/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dubinsky%2Fscalajs-gradle/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":279085812,"owners_count":26100020,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-10-15T02:00:07.814Z","response_time":56,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["airspec","crosscompile","gradle","hedgehog","junit4","munit","sbt","scala","scalacheck","scalajs","scalanative","scalaprops","scalatest","specs2","testing","utes","weaver","ziotest"],"created_at":"2025-06-22T14:31:38.874Z","updated_at":"2025-10-15T15:09:33.612Z","avatar_url":"https://github.com/dubinsky.png","language":"Scala","funding_links":[],"categories":[],"sub_categories":[],"readme":"= Gradle plugin for multi-backend Scala and sbt test frameworks\n:toc:\n:toclevels: 4\n:toc: preamble\n:icons: font\n// INCLUDED ATTRIBUTES\n:version-plugin: 0.9.13\n:version-gradle: 9.2.0-rc-1\n:version-scala: 3.7.3\n:version-scala-213: 2.13.16\n:version-scala-212: 2.12.20\n:version-sbt-test-interface: 1.0\n:version-scalajs: 1.20.1\n:version-scalajs-dom: 2.8.1\n:version-scalajs-env-jsdom-nodejs: 1.1.0\n:version-scala-js-env-playwright: 0.1.18\n:version-node: 24.9.0\n:version-scalanative: 0.5.8\n:version-junit: 4.13.2\n:version-framework-junit4-jvm: 0.13.3\n:version-framework-junit4-scalajs: 1.20.1\n:version-framework-junit4-scalanative: 0.5.8\n:version-framework-airspec: 2025.1.19\n:version-framework-hedgehog: 0.13.0\n:version-framework-munit: 1.2.0\n:version-framework-scalacheck: 1.19.0\n:version-framework-scalaprops: 0.10.0\n:version-framework-scalatest: 3.2.19\n:version-framework-specs2: 5.6.4\n:version-framework-specs2-scala2: 4.21.0\n:version-framework-utest: 0.9.1\n:version-framework-weaver: 0.10.1\n:version-framework-zio-test: 2.1.21\n:attribute-pluginScalaBackendProperty: org.podval.tools.backend\n:attribute-gradleVersionForBadge: 9.2.0--rc--1\n// INCLUDED ATTRIBUTES\n\nimage:https://github.com/dubinsky/scalajs-gradle/actions/workflows/CI.yaml/badge.svg[]\nimage:https://img.shields.io/badge/Version-{version-plugin}-black[]\nimage:https://img.shields.io/badge/Gradle-{attribute-gradleVersionForBadge}-blue?logo=gradle[]\nimage:https://img.shields.io/badge/Scala.js-{version-scalajs}-blue[]\nimage:https://img.shields.io/badge/Scala_Native-{version-scalanative}-blue[]\nimage:https://img.shields.io/badge/Node-{version-node}-blue?logo=nodedotjs[]\n\n== Summary\n\nThis is a https://gradle.org/[Gradle] plugin that adds functionality to the\nhttps://docs.gradle.org/current/userguide/scala_plugin.html[Gradle Scala plugin]:\n\n- compile, run, and test https://www.scala-lang.org/[Scala] code using JVM,\nhttps://www.scala-js.org/[Scala.js], and\nhttps://scala-native.org/[Scala Native] backends;\n- shared (\"cross-compile\") code between backends;\n- include sources for specific Scala version;\n- use any test framework that works with https://github.com/sbt/test-interface[sbt];\n- access Scala backend and Scala version data from the build script.\n\nPlugin integrates with:\n\n- https://gradle.org/[Gradle] test task configuration, test filtering, tagging, logging and reporting;\n- https://www.jetbrains.com/idea/[IntelliJ] test running and reporting;\n- https://github.com/JetBrains/intellij-scala[IntelliJ Scala plugin]\nto correctly handle sources shared between backends.\n\nPlugin works with:\n\n- `Gradle` {version-gradle};\n- `Scala.js` {version-scalajs};\n- `Scala Native` {version-scalanative};\n- `Node.js` {version-node}.\n\nPlugin:\n\n- adds necessary backend-specific dependencies;\n- adds necessary backend-specific `Scala` compiler plugins (for main and test code);\n- adds necessary backend-specific `Scala` compiler parameters;\n- for `Scala.js` and `Scala Native`, adds `link` tasks;\n- for `Scala.js`, retrieves and installs the configured version of https://nodejs.org/[Node.js];\n- for `Scala.js`, installs the configured `Node.js` modules using `npm`;\n- augments the `test` task to work with sbt-enabled test frameworks;\n- includes sources and resources shared between backends;\n- includes sources and resources for specific Scala version;\n- configures project artifacts to include shared code when needed;\n- configures names of the project artifact in accordance with the accepted conventions;\n- exposes, via `scalaBackend` extension,\ndata about the Scala backend and Scala version\nfor use in the build script.\n\nPlugin is written in Scala 3,\nbut the project that the plugin is _applied_ to can use Scala 3, 2.13 or 2.12;\nhowever, plugin is _not_ compatible with Gradle _plugins_ written in Scala 2.12.\n\nGradle build file snippets below use the `Groovy` syntax, not the `Kotlin` one.\n\nAccompanying example project that shows off some of the plugin's capabilities\nis available: https://github.com/dubinsky/cross-compile-example[cross-compile-example].\n\n== Configuration\n\n=== Plugin Id\nPlugin is https://plugins.gradle.org/plugin/org.podval.tools.scalajs[published]\non the https://plugins.gradle.org/[Gradle Plugin Portal];\nto apply it to a Gradle project:\n\n[source,groovy,subs=\"+attributes\"]\n----\nplugins {\n  id 'org.podval.tools.scalajs' version '{version-plugin}'\n}\n----\n\nPlugin will automatically apply the `Scala` plugin to the project,\nso there is no need to manually list `id 'scala'` in the `plugins` block -\nbut there is no harm in it either.\n\n=== Scala Version\nProject using the plugin has to specify a version of `Scala` for the Scala Gradle plugin to use.\n\nOne way to do it is to add `Scala` library dependency explicitly,\nand let the `Scala` plugin infer the Scala version from it:\n[source,groovy,subs=\"+attributes\"]\n----\ndependencies {\n  implementation \"org.scala-lang:scala3-library_3:{version-scala}\"\n}\n----\n\nAnother way is to set the Scala version on the Scala plugin's extension `scala`,\nand let the Scala plugin add appropriate Scala library dependency automatically:\n[source,groovy,subs=\"+attributes\"]\n----\nscala.scalaVersion = scalaVersion\n----\n\nThe latter approach:\n\n- is cleaner;\n- is the future: the old, inference-based approach is going away (slowly; deprecated in Gradle 9);\n- allows the Scala version to be consistent across the modules of a multi-module project by using `gradle.properies` file:\n\n[source,properties,subs=\"+attributes\"]\n----\nscalaVersion={version-scala}\n----\n\n- allows the Scala version to be overridden from the command line:\n[source,shell,subs=\"+attributes\"]\n----\n$ ./graldew -PscalaVersion={version-scala}\n----\n\nPlugin assumes that the project uses the explicit approach; no assumptions are made about the name of the property.\n\n=== Building for Specific Scala Version\nPlugin does not support building for multiple Scala versions\n_at the same time_ using only Gradle\n(unlike the https://github.com/ADTRAN/gradle-scala-multiversion-plugin[Gradle Scala Multi-Version Plugin]):\nI believe that \"build matrix\" belongs in the Continuous Integration\ntools, not in build tools.\n\nPlugin _does_ provide, in a Gradle-native way,\nfunctionality that helps build\nfor different Scala versions _one at a time_ from outside Gradle;\nsee \u003c\u003cscala-version-specific-sources\u003e\u003e, \u003c\u003cscala-backend-extension\u003e\u003e.\n\n\nTo run tests for specific Scala version (for instance, in a CI pipeline):\n\n[source,shell,subs=\"+attributes\"]\n----\n./gradlew clean check -PscalaVersion={version-scala}\n----\n\nTo run tests for multiple Scala versions:\n\n[source,shell,subs=\"+attributes\"]\n----\nfor v in '{version-scala}' '{version-scala-213}' '{version-scala-212}'; do ./gradlew clean check -PscalaVersion=$v; done\n----\n\nTo publish artifacts for multiple Scala versions:\n\n[source,shell,subs=\"+attributes\"]\n----\nfor v in '{version-scala}' '{version-scala-213}' '{version-scala-212}'; do ./gradlew clean publish -PscalaVersion=$v; done\n----\n\n[#scala-version-specific-sources]\n=== Sources Specific to Scala Version\nAlongside the usual Scala source root `scala`,\nas in `src/main/scala` and `src/test/scala`,\nplugin includes sources from Scala source roots specific to the Scala version in use;\nfor Scala version `x.y.z`, additional Scala source roots are:\n\n- `scala-x.y.z`;\n- `scala-x.y`;\n- `scala-x`;.\n\nSimilarly, alongside the usual resource root `resources`,\nas in `src/main/resources` and `src/test/resources`,\nplugin includes resources from resource roots specific to the Scala version in use;\nfor Scala version `x.y.z`, additional resource roots are:\n\n- `resources-x.y.z`;\n- `resources-x.y`;\n- `resources-x`.\n\nAdditional Scala sources and resources are included both in Scala compilation and archives that package Scala sources.\n\nThis applies to Scala sources shared between the backends too.\n\nSince only sources and resources appropriate to the Scala version in use are added,\nto work on the version-specific sources and resources in the IDE,\nyou need to set the Gradle property that selects the Scala version\nand re-load the project in the IDE.\n\n=== Dependencies\nPlugin automatically adds to various Gradle configurations\ndependencies needed to support the backend used\n(if they were not added explicitly).\n\nUnless you want to override versions of some of those\ndependencies,\nthe only dependencies you need to add to the project are\nthe test framework(s) that you use.\n\nAs usual, artifact names have suffixes corresponding to the Scala version:\n`_3`, `_2.13` or `_2.12`. For the artifacts compiled by the non-JVM backends,\nbefore the Scala version another suffix indicating the backend is inserted:\nfor `Scala.js` - `_sjs1`, for `Scala Native` - `_native0.5`.\n\nFor details on what dependencies are relevant for which backend , see:\n\u003c\u003cscalajs-dependencies, Scala.js Dependencies\u003e\u003e,\n\u003c\u003cscalanative-dependencies, Scala Native Dependencies\u003e\u003e,\n\u003c\u003ctest-frameworks-dependencies, Test Frameworks Dependencies\u003e\u003e.\n\n[#scala-backend-extension]\n=== Scala Backend Extension\n\nPlugin exposes Scala version and Scala backend data\nvia the `scalaBackend` extension that it creates.\nThis data can be used in build scripts to declare dependencies appropriate\nfor the Scala backend and Scala version.\n\nExtension exposes:\n\n- boolean properties to conditionalize build scripts:\n`jvm`, `js`, `native`, `scala3`, `nonJvmJUnit4present`;\n- properties describing the Scala version used by the project:\n`scalaVersion`, `scalaBinaryVersion`, `scala2BinaryVersion`;\n- properties of the Scala backend:\n`name`, `suffix`, `backendVersion`;\n- method that constructs dependency notation for a test framework:\n`testFramework(org.podval.tools.test.framework.FrameworkDescriptor, version)`;\n- method that constructs dependency notation for a Scala dependency:\n`scalaDependency(group, artifact, version, transformer)`;\n\nTransformer can indicate that a particular dependency is:\n\n- only available for Scala 3: `scala3()`;\n- only available for Scala 2: `scala2()`;\n- only available for JVM: `jvm()`;\n- a Scala compiler plugin: `scalaCompilerPlugin()`;\n\n[#application-scenarios]\n== Scala Backends\nPlugin can be applied to:\n\n- JVM-only project (\u003c\u003cjvm-only\u003e\u003e);\n- `Scala.js` or `Scala Native` project (\u003c\u003csingle-backend\u003e\u003e);\n- mixed-backend project with some sources shared between the backends (\u003c\u003cmixed-backends\u003e\u003e).\n\n[#jvm-only]\n=== Single Backend: JVM\nPlugin, its name notwithstanding, provides benefits even if applied to a project\nthat uses only Scala, without Scala.js or Scala Native,\nnamely: ability to use any test frameworks(s) that support sbt test interface.\n\nFor the list of test frameworks supported by the plugin, see \u003c\u003ctest-frameworks\u003e\u003e.\n\nTo use the plugin in such a way, `build.gradle` file for the project,\nin addition to applying the plugin and setting the Scala version,\nneeds to list in the `dependencies.testImplementation` the test framework(s) used.\n\nConfiguration of the `test` task cannot have `useJUnit`.\n\nAny Gradle plugins providing integration with specific test frameworks must be removed from the project:\nplugin itself provides integration with test frameworks,\nin some cases - better than the dedicated test-framework-specific plugins ;)\n\n[#single-backend]\n=== Single Backend: Scala.js or Scala Native\nSources under `src` are processed with one specific backend;\nbackend used is selected by the project property `{attribute-pluginScalaBackendProperty}`.\n\nThe value of this property is treated as case-insensitive.\n\nThis property must be set in the `gradle.properties` file of the project\nthat applies the plugin: setting it in `build.gradle` won't work.\n\nIf this property is set to `Scala.js` or `js`, `Scala.js` backend is used.\n\nIf this property is set to `Scala Native` or `native`, `Scala.js` backend is used.\n\nIf this property is set to `JVM` or not set at all, `JVM` backend is used,\nmaking this setup equivalent to the \u003c\u003cjvm-only\u003e\u003e one.\n\nFor example, to use `Scala.js` backend for the project,\nput the following into the `gradle.properties` file of the project:\n\n[source,properties,subs=\"+attributes\"]\n----\n{attribute-pluginScalaBackendProperty}=js\n----\n\n[#mixed-backends]\n=== Mixed Backends\nPlugin supports using multiple backends in the same project with\nsome sources shared between some of them.\n\nThis mode is triggered when at least one of the directories\ncontaining backend-specific sources - `js`, `jvm`, `native` - exists.\nAll backends do not have to be used all the time;\nwith only one backend used, this setup is equivalent to the \u003c\u003csingle-backend\u003e\u003e one\n(and if that backend is `jvm` - to the \u003c\u003cjvm-only\u003e\u003e one).\nBackend-specific directories must also be\nincluded as _projects_ in the `settings.gradle` file.\n\nTo share sources (include them in the\nbackend-specific compilation _together_ with the backend-specific sources)\nbetween _all_ the backends, place them in the directory `shared` -\nor directly in the `src` directory of the overall mixed-backend project;\nto avoid confusion, only one of those locations should be used,\nalthough plugin currently does not enforce this restriction :)\n\nTo share sources between _some_ of the backends (partial sharing),\nplace them in a directory with the name listing the backends\nthe sources are to be shared between:\n\n- in any order;\n- separated by `-`;\n- with optional `shared-` prefix;\n- `jvm-js-native` and `shared-jvm-js-native` are not allowed -\nuse `shared` instead;\n- `shared-jvm`, `shared-js` and `shared-native` are not allowed -\nuse `jvm`, `js`, `native` instead;\n- `jvm-jvm` and other duplicate backend names are not allowed;\n- `js-native` and `native-js` and other pairs of directories that\nshare sources between the same set of backends are not allowed -\npick one ;)\n\nShared directories must also be\nincluded as _projects_ in the `settings.gradle` file;\nstrictly speaking, they do not have to be\nfor the _Gradle_ build to work correctly,\nbut for the shared sources to be recognized in _IntelliJ_ they must be;\nfor simplicity, plugin requires that they always are.\n\nGradle _project_ names of the subprojects can be changed,\nbut the _directory_ names\n(`js`, `jvm`, `native`, `shared`, `js-jvm`, `shared-js-native`)\ncannot: plugin looks up the subprojects\nby their _directory_ names, not by their _project_ names.\n\nBuild script for the overall project (or module) is where:\n\n- plugin is applied,\n- Scala version is set,\n- any build logic that applies to the overall project resides.\n\nBuild scripts in the backend-specific directories are where:\n\n- backend-specific dependencies (including test frameworks) are added,\n- backend-specific tasks (including `link` and `test`) are configured,\n- any build logic that applies only to specific backend resides.\n\nShared directories hold sources fully or partially shared between the backends.\nThere is no need (nor point) to have a `build.gradle` file in\nany of the shared directories:\nthey are just containers for the sources shared between the backends.\n\nIn this mode, plugin:\n\n- applies itself to subprojects, backend-specific and shared\n(so there is no need to apply it manually in the subproject's `build.gradle`);\n- propagates the Scala version set in the overall project's `build.gradle` to subprojects\n(so there is no need to set it manually in the subproject's `build.gradle`);\n- configures appropriate backend for each of the backend-specific subprojects;\n- disables all source and archive tasks and unregisters all Scala sources in the overall project;\n- disables all tasks in the `shared` subproject.\n\nProject layout for such setup is:\n[source]\n----\nproject \u003c7\u003e\n+--- settings.gradle \u003c1\u003e\n+--- build.gradle \u003c2\u003e\n+--- src \u003c4\u003e\n+--- shared\n|    \\--- src \u003c4\u003e\n+--- js-jvm\n|    \\--- src \u003c5\u003e\n+--- js-native\n|    \\--- src \u003c5\u003e\n+--- jvm-native\n|    \\--- src \u003c5\u003e\n+--- js\n|    +--- build.gradle \u003c3\u003e\n|    \\--- src \u003c6\u003e\n+--- jvm\n|    +--- build.gradle \u003c3\u003e\n|    \\--- src \u003c6\u003e\n\\--- native\n     +--- build.gradle \u003c3\u003e\n     \\--- src \u003c6\u003e\n----\n\u003c1\u003e settings file where backend-specific and shared subprojects are included\n\u003c2\u003e build script of the overall project\n\u003c3\u003e build scripts of the backend-specific projects\n\u003c4\u003e sources shared between all backends\n\u003c5\u003e sources shared between some backends\n\u003c6\u003e sources specific to a backend\n\u003c7\u003e there are no sources in the overall project\n\n== JVM\n\n=== Dependencies\n\nWhen running on JVM, plugin adds SBT Test Interface\n`org.scala-sbt:test-interface:1.0` to the `testRuntimeOnly`\nconfiguration: it is used by the plugin to run the tests,\nand is normally brought in by the test frameworks themselves,\nbut since `ScalaTest` does not bring it in,\nplugin adds it.\n\n[source,groovy,subs=\"+attributes\"]\n----\ndependencies {\n  testRuntimeOnly 'org.scala-sbt:test-interface:{version-sbt-test-interface}'\n}\n----\n\n== Scala.js\n\n[#scalajs-dependencies]\n=== Dependencies\n\nIf `org.scala-js:scalajs-library` dependency is specified explicitly,\nplugin uses its version for other Scala.js dependencies that it adds.\n\nPlugin creates `scalajs` configuration\nfor `Scala.js` dependencies used by the plugin itself.\n\nThe table below lists what is added to what configurations.\n\n[%autowidth]\n|===\n|Name |group:artifact |Backend |Configuration |Notes\n\n|Compiler Plugin\n|org.scala-js:scalajs-compiler\n|JVM Scala 2\n|scalaCompilerPlugins\n|only for Scala 2\n\n|JUnit Compiler Plugin\n|org.scala-js:scalajs-junit-test-plugin\n|JVM Scala 2\n|testScalaCompilerPlugins\n|only for Scala 2 and only if JUnit4 for Scala.js is used\n\n|Linker\n|org.scala-js:scalajs-linker\n|JVM Scala 2\n|scalajs\n|\n\n|Node.js JavaScript environment with JSDOM\n|org.scala-js:scalajs-env-jsdom-nodejs\n|JVM Scala 2\n|scalajs\n|\n\n|Test Adapter\n|org.scala-js:scalajs-sbt-test-adapter\n|JVM Scala 2\n|scalajs\n|\n\n|Scala Library for Scala.js\n|org.scala-lang:scala3-library\n|Scala.js\n|implementation\n|only for Scala 3\n\n|Library\n|org.scala-js:scalajs-library\n|JVM Scala 2\n|implementation\n|\n\n|DOM Library\n|org.scala-js:scalajs-dom\n|Scala.js\n|implementation\n|\n\n|Test Bridge\n|org.scala-js:scalajs-test-bridge\n|JVM Scala 2\n|testRuntimeOnly\n|\n\n|===\n\nThe following Gradle build script fragment manually adds\nall Scala.js dependencies that the plugin adds automatically:\n\n[source,groovy,subs=\"+attributes\"]\n----\ndependencies {\n  // if version of `scalajs-library` is specified explicitly, ${scalaBackend.backendVersion} is set to that value;\n  // if not, plugin uses default version:\n  implementation  \"org.scala-js:scalajs-library_${scalaBackend.scala2BinaryVersion}:{version-scalajs}\"\n  implementation  \"org.scala-js:scalajs-dom_sjs1_${scalaBackend.scalaBinaryVersion}:{version-scalajs-dom}\"\n  if (scalaBackend.scala3) {\n    implementation \"org.scala-lang:scala3-library_sjs1_${scalaBackend.scalaBinaryVersion}:${scalaBackend.scalaVersion}\"\n  }\n  scalajs \"org.scala-js:scalajs-linker_${scalaBackend.pluginScala2BinaryVersion}:${scalaBackend.backendVersion}\"\n  scalajs \"org.scala-js:scalajs-sbt-test-adapter_${scalaBackend.pluginScala2BinaryVersion}:${scalaBackend.backendVersion}\"\n  scalajs \"org.scala-js:scalajs-env-jsdom-nodejs_${scalaBackend.pluginScala2BinaryVersion}:{version-scalajs-env-jsdom-nodejs}\"\n  if (!scalaBackend.scala3) {\n    scalaCompilerPlugins \"org.scala-js:scalajs-compiler_${scalaBackend.scalaVersion}:${scalaBackend.backendVersion}\"\n  }\n  if (!scalaBackend.scala3 \u0026\u0026 scalaBackend.nonJvmJUnit4present) {\n    testScalaCompilerPlugins \"org.scala-js:scalajs-junit-test-plugin_${scalaBackend.scalaVersion}:${scalaBackend.backendVersion}\"\n  }\n  testRuntimeOnly \"org.scala-js:scalajs-test-bridge_${scalaBackend.scala2BinaryVersion}:${scalaBackend.backendVersion}\"\n}\n----\n\nPlugin provide methods for adding dependencies easier;\nthose can be used for your dependencies too :)\nThe following Gradle build script fragment manually adds\nall Scala.js dependencies that the plugin adds automatically\nusing these methods:\n\n[source,groovy,subs=\"+attributes\"]\n----\ndependencies {\n  implementation scalaBackend.scalaDependency('org.scala-js', 'scalajs-library', '{version-scalajs}', {it.scala2().jvm()}) // sets scalaBackend.backendVersion\n  implementation scalaBackend.scalaDependency('org.scala-js', 'scalajs-dom', '{version-scalajs-dom}')\n  if (scalaBackend.scala3) {\n    implementation scalaBackend.scalaDependency('org.scala-lang', 'scala3-library', scalaBackend.scalaVersion, {it.scala3()})\n  }\n  scalajs scalaBackend.pluginDependency('org.scala-js', 'scalajs-linker', scalaBackend.backendVersion, {it.scala2()})\n  scalajs scalaBackend.pluginDependency('org.scala-js', 'scalajs-sbt-test-adapter', scalaBackend.backendVersion, {it.scala2()})\n  scalajs scalaBackend.pluginDependency('org.scala-js', 'scalajs-env-jsdom-nodejs', '{version-scalajs-env-jsdom-nodejs}', {it.scala2()})\n  if (!scalaBackend.scala3) {\n    scalaCompilerPlugins scalaBackend.scalaDependency('org.scala-js', 'scalajs-compiler', scalaBackend.backendVersion, {it.scala2().scalaCompilerPlugin()})\n  }\n  if (!scalaBackend.scala3 \u0026\u0026 scalaBackend.nonJvmJUnit4present) {\n    testScalaCompilerPlugins scalaBackend.scalaDependency('org.scala-js', 'scalajs-junit-test-plugin', scalaBackend.backendVersion, {it.scala2().scalaCompilerPlugin()})\n  }\n  testRuntimeOnly scalaBackend.scalaDependency('org.scala-js', 'scalajs-test-bridge', scalaBackend.backendVersion, {it.scala2().jvm()})\n}\n----\n\n=== Compiling\nTo support Scala.js, Scala compiler needs to be configured to produce both the `class` _and_ `sjsir` files.\n\n==== Scala 3\n\nIf the project uses Scala 3, all it takes is to pass `-scalajs` option\nto the Scala compiler, since Scala 3 compiler has Scala.js support built in:\n\n[source,groovy]\n----\ntasks.withType(ScalaCompile) {\n  scalaCompileOptions.with {\n    additionalParameters = [ '-scalajs' ]\n  }\n}\n----\n\nPlugin automatically adds this option to the main and test\nScala compilation tasks if it is not present.\n\n==== Scala 2\nIf the project uses Scala 2, Scala.js compiler plugin dependency needs to be declared:\n\n[source,groovy,subs=\"+attributes\"]\n----\ndependencies {\n  scalaCompilerPlugins \"org.scala-js:scalajs-compiler_$scalaVersion:{version-scalajs}\"\n}\n----\n\nPlugin does this automatically unless a dependency on\n`org.scala-js:scalajs-compiler` is declared explicitly.\n\nIf the project uses Scala 2 _and_ JUnit 4 for Scala.js,\na JUnit Scala compiler plugin is also needed (\u003c\u003cjunit4-scalajs-scalanative\u003e\u003e):\n\n[source,groovy,subs=\"+attributes\"]\n----\ndependencies {\n  testScalaCompilerPlugins \"org.scala-js:scalajs-junit-test-plugin_$scalaVersion:{version-scalajs}\"\n}\n----\n\nPlugin adds this automatically also.\n\nThere is no need to add `-Xplugin:` Scala compiler parameters for the compiler plugins.\n\n=== Linking\n\nFor linking of the main code, plugin adds `link` task of type\nlink:src/main/scala/org/podval/tools/scalajs/ScalaJSLinkTask.scala[org.podval.tools.scalajs.ScalaJSLinkTask.Main];\nall tasks of this type automatically depend on the `classes` task.\n\nFor linking of the test code, plugin adds `testLink` task of type\nlink:src/main/scala/org/podval/tools/scalajs/ScalaJSLinkTask.scala[org.podval.tools.scalajs.ScalaJSLinkTask.Test];\nall tasks of this type automatically depend on the `testClasses` task.\n\nLink tasks exposes a property `JSDirectory` that points to a directory\nwith the resulting JavaScript, so that it can be, for example, copied where needed:\n\n[source,groovy]\n----\nlink.doLast {\n  project.sync {\n    from link.JSDirectory\n    into jsDirectory\n  }\n}\n----\n\nLink tasks have a number of properties that can be used to configure linking.\nConfigurable properties with their defaults are:\n\n[source,groovy]\n----\nlink {\n  optimization     = 'Fast'          // one of: 'Fast', 'Full'\n  moduleKind       = 'NoModule'      // one of: 'NoModule', 'ESModule', 'CommonJSModule'\n  moduleSplitStyle = 'FewestModules' // one of: 'FewestModules', 'SmallestModules'\n  // when using `specs2` testing framework, '2018' and later is required:\n  // it supports regular expressions used in many matchers using strings\n  esVersion        = '2015'          // one of '2015', '2016', '2017', '2018', '2019', '2020', '2021'\n  smallModulesFor  = []              // list of packages; relevant only when moduleSplitStyle = 'SmallModulesFor'\n  prettyPrint      = false\n  experimentalUseWebAssembly = false\n}\n----\n\nSetting `optimization` to `Full` enables:\n\n- `Semantics.optimized`;\n- `checkIR`;\n- Closure Compiler (if `moduleKind` is set to `ESModule`).\n\nFor `ScalaJSLinkMainTask` tasks, a list of module initializers may also be configured:\n\n[source,groovy]\n----\nmoduleInitializers {\n  main {\n    className = '\u003cfully qualified class name\u003e'\n    mainMethodName = 'main'\n    mainMethodHasArgs = false\n  }\n}\n----\n\nName of the module initializer ('main' in the example above) becomes the module id.\n\n=== Running\n\nPlugin adds `run` task for running the main code\n(if it is an application and not a library);\nthis task automatically depends on the `link` task.\n\nAdditional tasks of type\nlink:src/main/scala/org/podval/tools/scalajs/ScalaJSRunTask.scala[org.podval.tools.scalajs.ScalaJSRunTask.Main]\ncan be added manually;\ntheir dependency on a corresponding `ScalaJSLinkTask.Main` task must be set manually too.\n\n=== JavaScript Environment\nBoth `run` and `test` tasks have a property `jsEnv` that selects a JavaScript\nenvironment to use:\n\n[source,groovy]\n----\nrun {\n  jsEnv = 'Node.js' // one of: 'Node.js', 'Node.js+DOM'\n}\n----\n\nhttps://phantomjs.org/[PhantomJS] is not supported:\nthe project has been abandoned since 2018.\n\nhttps://github.com/scala-js/scala-js-env-selenium[Selenium] is not supported:\nthe project seems to be abandoned.\n\nhttps://github.com/gmkumar2005/scala-js-env-playwright[Playwright]\n('io.github.gmkumar2005:scala-js-env-playwright_2.13:{version-scala-js-env-playwright}')\nis not supported: the project publishes artifacts\nhttps://github.com/gmkumar2005/scala-js-env-playwright/issues/17[only]\nfor Scala 2.12.\n\nIf Playwright _was_ supported, property `browserName` would choose the browser:\n'chromium', 'chrome', 'firefox', 'webkit'.\n\n=== Node.js\n\nFor running `Scala.js` code and tests, plugin uses `Node.js`.\n\nPlugin adds `node` extension to the project.\nThis extension can be used to specify the version of Node.js to use and Node modules to install:\n\n[source,groovy,subs=\"+attributes\"]\n----\nnode {\n  version = '{version-node}'\n  modules = []\n}\n----\n\nIf Node.js version is not specified, plugin uses \"ambient\" Node.js -\nthe one installed on the machine where it is running,\nor, if none is available, installs the default version ({version-node}).\nIf Node.js version is specified, plugin installs the specified version.\n\nNode.js is installed under `~/.gradle/nodejs`.\n\nIf you are using `Node.js+DOM` JavaScript environment (`org.scala-js:scalajs-env-jsdom-nodejs`), you need 'jsdom' module.\n\nTo get better traces, one can add `source-map-support` module.\n\nNode.js modules for the project are installed in the `node_modules`\ndirectory in the project root.\n\nIf `package.json` file does not exist, plugin runs `npm init private`.\n\nPlugin adds tasks `node` and `npm` for executing `node` and `npm` commands\nusing the same version of Node.js that is used by the plugin;\nthose tasks can be used from the command line like this:\n\n[source,shell]\n----\n./gradlew npm --npm-arguments 'version'\n./gradlew node --node-arguments '...'\n----\n\n== Scala Native\n\n[#scalanative-dependencies]\n=== Dependencies\n\nIf `org.scala-native:scala3lib` (for Scala 3) or\n`org.scala-native:scalalib` (for Scala 2) dependency is specified explicitly,\nplugin uses its version for all the Scala Native dependencies that it adds.\n\nPlugin creates `scalanative` configuration\nfor `Scala Native` dependencies used by the plugin itself.\n\nThe table below lists what is added to what configurations.\n\n[%autowidth]\n|===\n|Name |group:artifact |Backend |Configuration |Notes\n\n|Compiler Plugin\n|org.scala-native:nscplugin\n|JVM\n|scalaCompilerPlugins\n|\n\n|JUnit Compiler Plugin\n|org.scala-native:junit-plugin\n|JVM\n|testScalaCompilerPlugins\n|only if JUnit4 for Scala Native is used\n\n|Linker\n|org.scala-native:tools\n|JVM\n|scalanative\n|\n\n|Test Adapter\n|org.scala-native:test-runner\n|JVM\n|scalanative\n|\n\n|Library\n|org.scala-native:scala3lib\n|Scala Native\n|implementation\n|only for Scala 3\n\n|Library\n|org.scala-native:scalalib\n|Scala Native\n|implementation\n|only for Scala 2\n\n|Test Bridge\n|org.scala-native:test-interface\n|Scala Native\n|testRuntimeOnly\n|\n\n|Native Library\n|org.scala-native:nativelib\n|Scala Native\n|implementation\n|\n\n|C Library\n|org.scala-native:clib\n|Scala Native\n|implementation\n|\n\n|Posix Library\n|org.scala-native:posixlib\n|Scala Native\n|implementation\n|\n\n|Windows Library\n|org.scala-native:windowslib\n|Scala Native\n|implementation\n|\n\n|Java Library\n|org.scala-native:javalib\n|Scala Native\n|implementation\n|\n\n|Aux Library\n|org.scala-native:auxlib\n|Scala Native\n|implementation\n|\n\n|===\n\nThe following Gradle build script fragment manually adds all Scala Native dependencies\nthat the plugin adds automatically:\n\n[source,groovy,subs=\"+attributes\"]\n----\ndependencies {\n  // if version of `scala3lib`/`scalalib` is specified explicitly, ${scalaBackend.backendVersion} is set to that value;\n  // if not, plugin uses default version:\n  if (scalaBackend.scala3) {\n    implementation \"org.scala-native:scala3lib_native0.5_${scalaBackend.scalaBinaryVersion}:${scalaBackend.version}+{version-scalanative}\"\n  } else {\n    implementation \"org.scala-native:scalalib_native0.5_${scalaBackend.scalaBinaryVersion}:${scalaBackend.version}+{version-scalanative}\"\n  }\n  implementation \"org.scala-native:nativelib_native0.5_${scalaBackend.scalaBinaryVersion}:${scalaBackend.backendVersion}\"\n  implementation \"org.scala-native:javalib_native0.5_${scalaBackend.scalaBinaryVersion}:${scalaBackend.backendVersion}\"\n  implementation \"org.scala-native:clib_native0.5_${scalaBackend.scalaBinaryVersion}:${scalaBackend.backendVersion}\"\n  implementation \"org.scala-native:posixlib_native0.5_${scalaBackend.scalaBinaryVersion}:${scalaBackend.backendVersion}\"\n  implementation \"org.scala-native:windowslib_native0.5_${scalaBackend.scalaBinaryVersion}:${scalaBackend.backendVersion}\"\n  implementation \"org.scala-native:auxlib_native0.5_${scalaBackend.scalaBinaryVersion}:${scalaBackend.backendVersion}\"\n\n  scalanative \"org.scala-native:tools_${scalaBackend.pluginScalaBinaryVersion}:${scalaBackend.backendVersion}\"\n  scalanative \"org.scala-native:test-runner_${scalaBackend.pluginScalaBinaryVersion}:${scalaBackend.backendVersion}\"\n\n  scalaCompilerPlugins \"org.scala-native:nscplugin_${scalaBackend.scalaVersion}:${scalaBackend.backendVersion}\"\n\n  if (scalaBackend.nonJvmJUnit4present) {\n    testScalaCompilerPlugins \"org.scala-native:junit-plugin_${scalaBackend.scalaVersion}:${scalaBackend.backendVersion}\"\n  }\n\n  testRuntimeOnly \"org.scala-native:test-interface_native0.5_${scalaBackend.scalaBinaryVersion}:${scalaBackend.backendVersion}\"\n}\n----\n\nPlugin provide methods for adding dependencies easier;\nthose can be used for your dependencies too :)\nThe following Gradle build script fragment manually adds\nall Scala Native dependencies that the plugin adds automatically\nusing these methods:\n\n[source,groovy,subs=\"+attributes\"]\n----\ndependencies {\n  if (scalaBackend.scala3) {\n    implementation scalaBackend.scalaDependency('org.scala-native', 'scala3lib', \"${scalaBackend.scalaVersion}+0.5.8\", {it.scala3()}) // sets scalaBackend.backendVersion\n  } else {\n    implementation scalaBackend.scalaDependency('org.scala-native', 'scalalib', \"${scalaBackend.scalaVersion}+0.5.8\", {it.scala2()}) // sets scalaBackend.backendVersion\n  }\n  implementation scalaBackend.scalaDependency('org.scala-native', 'nativelib', scalaBackend.backendVersion)\n  implementation scalaBackend.scalaDependency('org.scala-native', 'clib', scalaBackend.backendVersion)\n  implementation scalaBackend.scalaDependency('org.scala-native', 'posixlib', scalaBackend.backendVersion)\n  implementation scalaBackend.scalaDependency('org.scala-native', 'javalib', scalaBackend.backendVersion)\n  implementation scalaBackend.scalaDependency('org.scala-native', 'windowslib', scalaBackend.backendVersion)\n  implementation scalaBackend.scalaDependency('org.scala-native', 'auxlib', scalaBackend.backendVersion)\n\n  scalanative scalaBackend.pluginDependency('org.scala-native', 'tools', scalaBackend.backendVersion)\n  scalanative scalaBackend.pluginDependency('org.scala-native', 'test-runner', scalaBackend.backendVersion)\n\n  scalaCompilerPlugins scalaBackend.scalaDependency('org.scala-native', 'nscplugin', scalaBackend.backendVersion, {it.scalaCompilerPlugin()})\n\n  if (scalaBackend.nonJvmJUnit4present) {\n    testScalaCompilerPlugins scalaBackend.scalaDependency('org.scala-native', 'junit-plugin', scalaBackend.backendVersion, {it.scalaCompilerPlugin()})\n  }\n\n  testRuntimeOnly scalaBackend.scalaDependency('org.scala-native', 'test-interface', scalaBackend.backendVersion)\n}\n----\n\n\n=== Compiling\nTo support Scala Native, Scala compiler needs to be configured to produce both the `class` _and_ `nir` files.\n\n\nScala.js compiler plugin dependency needs to be declared:\n\n[source,groovy,subs=\"+attributes\"]\n----\ndependencies {\n  scalaCompilerPlugins \"org.scala-native:nscplugin_$scalaVersion:{version-scalanative}\"\n}\n----\n\nPlugin does this automatically unless a dependency on\n`org.scala-native:nscplugin` is declared explicitly.\n\nIf the project uses JUnit 4 for Scala Native,\na JUnit Scala compiler plugin is also needed (\u003c\u003cjunit4-scalajs-scalanative\u003e\u003e):\n\n[source,groovy,subs=\"+attributes\"]\n----\ndependencies {\n  testScalajsCompilerPlugins \"org.scala-native:junit-plugin_$scalaVersion:{version-scalajs}\"\n}\n----\n\nPlugin adds this automatically also.\n\nThere is no need to add `-Xplugin:` Scala compiler parameters for the compiler plugins.\n\n=== Linking\n\nFor linking of the main code, plugin adds `link` task of type\nlink:src/main/scala/org/podval/tools/scalanative/ScalaNativeLinkTask.scala[org.podval.tools.scalanative.ScalaNativeLinkTask.Main];\nall tasks of this type automatically depend on the `classes` task.\n\nFor linking of the test code, plugin adds `testLink` task of type\nlink:src/main/scala/org/podval/tools/scalanative/ScalaNativeLinkTask.scala[org.podval.tools.scalanative.ScalaNativeLinkTask.Test];\nall tasks of this type automatically depend on the `testClasses` task.\n\nLink tasks exposes a property `NativeDirectory` that points to a directory\nwith the Scala Native Linker output, so that it can be copied where needed.\n\nLink tasks have a number of properties that can be used to configure linking.\nConfigurable properties with their defaults are:\n\n[source,groovy]\n----\nlink {\n  mode     = 'debug' // one of: 'debug', 'release-fast', 'release-size', 'release-full'\n  lto      = 'none'  // one of: 'none', 'thin', 'full'\n  gx       = 'immix' // one of: 'none', 'boehm', 'immix', 'commix'\n  optimize = false\n}\n----\n\nIf not set explicitly, properties are set from the environment variables:\n\n- mode - `SCALANATIVE_MODE`\n- lto - `SCALANATIVE_LTO`\n- gc - `SCALANATIVE_GC`\n- optimize - `SCALANATIVE_OPTIMIZE`\n\nFor `ScalaNativeLinkMainTask` tasks, property `mainClass` may also be configured.\nThis is the class that will be run.\n\n=== Running\n\nPlugin adds `run` task for running the main code\n(if it is an application and not a library);\nthis task automatically depends on the `link` task.\n\nAdditional tasks of type\nlink:src/main/scala/org/podval/tools/scalanative/ScalaNativeRunTask.scala[org.podval.tools.scalanative.ScalaNativeRunTask.Main]\ncan be added manually;\ntheir dependency on a corresponding `ScalaNativeLinkTask.Main` task must be set manually too.\n\n== Testing\n\n=== Test Task\nTest task added by the plugin is derived from the normal Gradle `test` task,\nand can be configured  in the traditional way - with some limitations:\n\n- plugin applies its own Gradle test framework (`useSbt`) to each test task;\nre-configuring the Gradle test framework (via `useJUnit`, `useTestNG` or `useJUnitPlatform`) is not supported;\n- `isScanForTestClasses` must be at its default value `true`.\n- Scala.js and Scala Native tests _must_ run in the same JVM where they are discovered,\nso they are not forked, and forking configuration is ignored.\n\nDry run (`test.dryRun=true` or `--test-dry-run` command line option) is supported.\n\nTest filtering and tagging are supported to the extent that the individual\ntest frameworks support them; see \u003c\u003ctest-filtering\u003e\u003e, \u003c\u003ctest-tagging\u003e\u003e\nand \u003c\u003ctest-frameworks\u003e\u003e.\n\nIf there is a need to have test runs with different configurations,\nmore testing tasks can be added manually.\n\nFor JVM, the type of the test task is\nlink:src/main/scala/org/podval/tools/jvm/JvmTestTask.scala[org.podval.tools.jvm.JvmTestTask].\nAny such task will automatically depend on the `testClasses` task (and `testRuntimeClassPath`).\n\nFor Scala.js the type of the test task is\nlink:src/main/scala/org/podval/tools/scalajs/ScalaJSRunTask.scala[org.podval.tools.scalajs.ScalaJSRunTask.Test].\nSuch test tasks have to depend on a\n`ScalaJSLinkTask.Test` task.\nThe `test` task added by the plugin does it automatically;\nfor manually added tasks this dependency has to be added manually.\n\nFor Scala Native the type of the test task is\nlink:src/main/scala/org/podval/tools/scalanative/ScalaNativeRunTask.scala[org.podval.tools.scalanative.ScalaNativeRunTask.Test].\nSuch test tasks have to depend on a\n`ScalaNativeLinkTask.Test` task.\nThe `test` task added by the plugin does it automatically;\nfor manually added tasks this dependency has to be added manually.\n\n[#test-filtering]\n=== Test Filtering\n\nGradle uses three sets of patterns to filter tests by names;\ntwo of them - `includeTestsMatching` and `excludeTestsMatching` -\nare set in the Gradle build file:\n\n[source, groovy]\n----\ntest {\n  filter {\n    includeTestsMatching \"org.podval.tools.test.SomeTestClass.success\"\n    includeTestsMatching \"org.podval.tools.test.SomeTestClass.failure\"\n    excludeTestsMatching \"OtherTestClass\"\n  }\n}\n----\n\nThe third one is set via a command-line option `--tests`.\n\nInclusion rules are:\n\n- if both build file and the command line inclusions are specified,\nto be included, a test must match both.\n- if no inclusions nor exclusions are specified, all tests are included.\n- if only inclusions are specified, only tests matching one of them are included.\n- if only exclusions are specified, only tests not matching any of them are included.\n- if both inclusions and exclusions are specified, only tests matching one of the inclusions and not matching any of the exclusions are included.\n\nGradle inclusion/exclusion patterns can contain wildcards \"*\";\nsemantics of matching against those patterns is complicated,\nsometimes surprising and difficult (for me) to understand;\nthat is why I followed Gradle implementation as closely as possible.\nPlugin implements test _class_ inclusion/exclusion itself,\nbut individual test _case_ inclusion/exclusion is handled by the test framework used.\n\nSBT test interface that the plugin uses to communicate with the test frameworks\nhas means of expressing that a test case with specific name is to be included\n(https://github.com/sbt/test-interface/blob/master/src/main/java/sbt/testing/TestSelector.java[TestSelector])\nand that test cases whose names contain a specific string are to be included\n(https://github.com/sbt/test-interface/blob/master/src/main/java/sbt/testing/TestWildcardSelector.java[TestWildcardSelector]);\nit does not have any means of expressing which test cases are to be excluded.\n\nPlugin does not have access to the list of test case names\n(which are framework-dependent),\nso, even though I try to translate Gradle filtering to the SBT test interface filtering as close as possible, when test case filtering is involved,\nthis translation can in general case lose fidelity.\nMy immediate goal was to make sure the filtering scenarios that are used in practice\nwork as intended; turns out, infidelities in the implementation of test case filtering\nin specific test frameworks make even that impossible in some cases,\nas is detailed below.\n\nThe following patterns specify test classes to run:\n\n- `\"*\"`: all tests, just as if no includes are specified;\n- `\"*IntegrationTest\"`: classes whose named end with \"IntegrationTest\";\n- `\"Scala*\"`: classes whose name starts with \"Scala\";\n- `\"org.podval.tools.test.Scala*\"`: classes in specified package whose name starts with \"Scala\";\n- `\"org.podval.tools.test.*\"`: tests in specified package (used by IntelliJ Idea, see \u003c\u003ctesting-in-intellij\u003e\u003e);\n- `\"org.podval.tools.test.ScalaTest\"`: tests in specified class (used by IntelliJ Idea, see \u003c\u003ctesting-in-intellij\u003e\u003e).\n\nAll these patterns work as intended.\n\nThe following patterns specify test cases to run:\n\n- `\"org.podval.tools.test.SomeTestClass.success\"`: specified test case in specified class (used by IntelliJ Idea, see \u003c\u003ctesting-in-intellij\u003e\u003e);\n- `\"org.podval.tools.test.SomeTestClass.succ*\"`: test cases whose names start with \"succ\" in specified class.\n\nWith these patterns, what actually happens depends on the\nfidelity with which test framework used implements\neven the restricted test case selection means of the SBT test interface.\n\n[#test-tagging]\n=== Test Tagging\n\nNames of the tags to include and exclude in the run are specified in:\n\n[source,groovy]\n----\ntest {\n  useSbt {\n    includeCategories = [\"itag1\", \"itag2\"]\n    excludeCategories = [\"etag1\", \"etag2\"]\n  }\n}\n----\n\nInclusion rules are:\n\n- if no inclusions nor exclusions are specified, all tests are included.\n- if only inclusions are specified, only tests tagged with one of them are included.\n- if only exclusions are specified, only tests not tagged with any of them are included.\n- if both inclusions and exclusions are specified, only tests tagged with one of the inclusions and not tagged with any of the exclusions are included.\n\n=== Skipped Tests\nWhen running some test methods explicitly included by a filter,\nI do not want to see skipped methods mentioned in the test report\njust as I do not want to see other skipped test classes there.\n\nI do want to see tests explicitly ignored in code\n(e.g., in ScalaTest, or JUnit4's falsified assumptions).\n\nDuring a dry run, though, I want to see _everything_ that was skipped,\nincluding test classes that were skipped entirely;\nfor such, a test case named `dry run` is reported as skipped.\n\n=== Nested Test Suites\nSome test frameworks have a notion of _nested test suites_,\nwhere nesting test class aggregates nested test classes.\n\nPlugin supports such a scenario and,\nwhen test framework involved provides sufficient information about the tests run,\nattributes test cases from the nested suites to them:\ntest report will have no test cases for the nesting class;\ninstead, test cases will be reported for the nested classes they belong to.\n\n[#testing-in-intellij]\n=== Testing in IntelliJ\n\nIn the following, it is assumed that the IDE is configured to use Grade to run tests etc.\n\nOn JVM, whatever you can run from Idea you can also debug;\nScala.js code runs on Node.js, so there is no debugging it - breakpoints have no effect;\nnor do they on Scala Native.\n\nAs with any other Gradle project imported into Idea, you can run Gradle tasks.\n\nIntelliJ lets you run objects with main methods using either:\n\n- object node in the project tree or\n- gutter icon in the object's file\n\nOn Scala.js or Scala Native, objects can not be run this way:\nthe code needs to be compiled and linked for the appropriate backend.\nThis is what the `run` task added by the plugin is for.\n\nAs usual, when you run tests:\n\n- results are displayed in tree form\n- test counts are displayed.\n\nNote: if the test name in the `sbt.testing.Event`\nthat IntelliJ receives starts with the name of the type the test belongs to,\nIntelliJ drops this prefix - probably to accommodate JUnit4,\nwhich incorrectly prepends all test names with the name of their class.\nAs a result, for frameworks that have a notion of named suite\n(ZIO Test and ScalaCheck), if the name of the suite is the same as the\nname of the type, incorrectly IntelliJ drops it.\n\nAs usual, you can run all tests from the project tree using any of the nodes:\n\n[source]\n----\n\u003croot\u003e\n  src\n    test\n      scala\n----\n\nAs usual, you can run all tests from a package using the package's node in the project tree.\nIdea supplies Gradle test filter \"selected.package.*\".\n\nAs usual, you can run individual test class for _the frameworks Idea recognizes_ using either:\n\n- test's node in the project tree or\n- gutter icon in the test's file\n\nIdea supplies Gradle test filter \"fully.qualified.TestClass\".\n\nAs usual, you can run individual test in a test class for _the frameworks Idea recognizes_ using:\n\n- gutter icon in the test's file\n\nIdea supplies Gradle test filter \"fully.qualified.TestClass.test\".\n\nFrom the test frameworks this plugin supports, Idea recognizes:\n\n- JUnit4\n- JUnit4 for Scala.js\n- JUnit4 for Native\n\nScala plugin for Idea recognizes:\n\n- MUnit\n- ScalaTest\n- Specs2\n- uTest\n\n`Weaver Test` test objects _are_ recognized by IntelliJ as tests\n(because `weaver.RunnableSuite` is annotated with `org.junit.runner.RunWith`):\nyou get a gutter icon for the test object,\nwhich lets you run or debug it,\nand reflects the results of the previous run;\nthere are no gutter icons for the individual tests,\nand even if there were, `Weaver Test` ignores test selectors ;)\n\n`ScalaCheck` and `ZIO Test` are not recognized by the Scala Plugin:\nno gutter icon for the test class nor individual tests in it are available,\nRun and Debug commands are not available in the context menu\nof the test classes node in the Project tree\nand of the gutter icon of the test class.\n\nSince `Hedgehog` and `ZIO Test` tests are objects with main method,\nthey can be run from Idea (on JVM),\nbut there is no test result tree nor test counts displayed,\nand since Gradle is not involved, no test reports.\n\n[#test-frameworks]\n== Test Frameworks\nPlugin replaces the `test` task with one that supports running\nsbt-compatible test frameworks; multiple test frameworks can be used at the same time.\n\nVarious test frameworks are listed or recognized by:\n\n|===\n|Framework |Recognized by https://github.com/sbt/sbt/blob/develop/testing/src/main/scala/sbt/TestFramework.scala[sbt] |Recognized by IntelliJ IDEA |Recognized by https://github.com/JetBrains/intellij-scala/tree/idea252.x/scala/test-integration/testing-support/src/org/jetbrains/plugins/scala/testingSupport/test[IntelliJ Scala Plugin] |Listed by https://www.scala-js.org/libraries/testing.html[Scala.js] |Works with this Plugin\n\n|https://wvlet.org/airframe/docs/airspec[AirSpec]\n|no\n|no\n|no\n|yes\n|yes\n\n|https://github.com/greencatsoft/greenlight[Greenlight]\n|no\n|no\n|no\n|yes\n|no: defunct\n\n|https://hedgehogqa.github.io/scala-hedgehog/[Hedgehog]\n|yes\n|no\n|no\n|no\n|yes\n\n|https://junit.org/junit4/[JUnit4]\n|yes\n|yes\n|no\n|yes\n|yes; reimplemented for Scala.js and Scala Native\n\n|https://junit.org/[JUnit5]\n|no\n|yes\n|no\n|no\n|no: own test discovery; no point: JVM only\n\n|https://github.com/monix/minitest[MiniTest]\n|no\n|no\n|no\n|yes\n|no: defunct\n\n|https://scalameta.org/munit/[MUnit]\n|yes\n|\n|yes\n|yes\n|yes\n\n|https://github.com/japgolly/nyaya[Nyaya]\n|no\n|no\n|no\n|yes\n|no: defunct\n\n|https://scalacheck.org/[ScalaCheck]\n|yes\n|no\n|no\n|yes\n|yes\n\n|https://github.com/scalaprops/scalaprops[Scalaprops]\n|no\n|no\n|no\n|yes\n|yes\n\n|https://www.scalatest.org/[ScalaTest]\n|yes\n|no\n|yes\n|yes\n|yes\n\n|https://github.com/japgolly/test-state[Scala Test-State]\n|no\n|no\n|no\n|yes\n|no: defunct\n\n|specs\n|yes\n|no\n|no\n|no\n|no: defunct, use specs2\n\n|https://etorreborre.github.io/specs2/[specs2]\n|yes\n|no\n|yes\n|no\n|yes\n\n|https://testng.org/[TestNG]\n|no\n|yes\n|no\n|no\n|no: https://github.com/sbt/sbt-testng[SBT interface] defunct; no point: JVM only\n\n|https://github.com/com-lihaoyi/utest[uTest]\n|no\n|no\n|yes\n|yes\n|yes\n\n|https://github.com/typelevel/weaver-test[Weaver Test]\n|yes\n|yes\n|no\n|no\n|yes\n\n|https://zio.dev/reference/test/[ZIO test]\n|yes\n|no\n|no\n|no\n|yes\n\n|===\n\nFramework-specific information for the frameworks that _are_ supported follows.\n\n[#test-frameworks-dependencies]\n=== Dependencies\n\n[%autowidth]\n|===\n|Name |group:artifact |Backends |Version |Notes\n\n|JUnit4\n|com.github.sbt:junit-interface\n|jvm\n|{version-framework-junit4-jvm}\n|Java\n\n|JUnit4 for Scala.js\n|org.scala-js:scalajs-junit-test-runtime\n|js\n|{version-framework-junit4-scalajs}\n|Scala 2\n\n|JUni4 for Scala Native\n|org.scala-native:junit-runtime\n|native\n|{version-framework-junit4-scalanative}\n|\n\n|AirSpec\n|org.wvlet.airframe:airspec\n|jvm, js, native\n|{version-framework-airspec}\n|Scala Native only on Scala 3\n\n|Hedgehog\n|qa.hedgehog:hedgehog-sbt\n|jvm, js, native\n|{version-framework-hedgehog}\n|\n\n|MUnit\n|org.scalameta:munit\n|jvm, js, native\n|{version-framework-munit}\n|\n\n|ScalaCheck\n|org.scalacheck:scalacheck\n|jvm, js, native\n|{version-framework-scalacheck}\n|\n\n|Scalaprops\n|com.github.scalaprops:scalaprops\n|jvm\n|{version-framework-scalaprops}\n|currently not supported on Scala.js nor Scala Native\n\n|ScalaTest\n|org.scalatest:scalatest\n|jvm, js, native\n|{version-framework-scalatest}\n|\n\n|specs2\n|org.specs2:specs2-core\n|jvm, js, native\n|{version-framework-specs2}\n|latest that supports Scala 2 or Scala Native: {version-framework-specs2-scala2}\n\n|uTest\n|com.lihaoyi:utest\n|jvm, js, native\n|{version-framework-utest}\n|\n\n|Weaver Test\n|org.typelevel:weaver-cats\n|jvm\n|{version-framework-weaver}\n|support for Scala Native and Scala.js is currently broken\n\n|ZIO Test\n|dev.zio:zio-test-sbt\n|jvm, js, native\n|{version-framework-zio-test}\n|\n\n|===\n\nThe following Gradle build script fragment adds all test framework dependencies\nthat fit the Scala version and backend:\n\n[source,groovy,subs=\"+attributes\"]\n----\nfinal String scalaJSVersion = '{version-scalajs}'\nfinal String scalaNativeVersion = '{version-scalanative}'\n\ndependencies {\n  if (scalaBackend.backend.jvm) {\n    testImplementation \"com.github.sbt:junit-interface:{version-framework-junit4-jvm}\"\n  }\n  if (scalaBackend.backend.js) {\n    testImplementation \"org.scala-js:scalajs-junit-test-runtime_${scalaBackend.scala2BinaryVersion}:$scalaJSVersion\"\n  }\n  if (scalaBackend.backend.native) {\n    testImplementation \"org.scala-native:junit-runtime${scalaBackend.suffix}:$scalaNativeVersion\"\n  }\n  if (!scalaBackend.backend.native || scalaBackend.scala3) {\n    testImplementation \"org.wvlet.airframe:airspec${scalaBackend.suffix}:{version-framework-airspec}\"\n  }\n  testImplementation \"qa.hedgehog:hedgehog-sbt${scalaBackend.suffix}:{version-framework-hedgehog}\"\n  testImplementation \"org.scalameta:munit${scalaBackend.suffix}:{version-framework-munit}\"\n  testImplementation \"org.scalacheck:scalacheck${scalaBackend.suffix}:{version-framework-scalacheck}\"\n  if (scalaBackend.backend.jvm) {\n    testImplementation \"com.github.scalaprops:scalaprops:{version-framework-scalaprops}\"\n  }\n  testImplementation \"org.scalatest:scalatest${scalaBackend.suffix}:{version-framework-scalatest}\"\n  if (!scalaBackend.scala3 || scalaBackend.backend.native) {\n    testImplementation \"org.specs2:specs2-core${scalaBackend.suffix}:{version-framework-specs2-scala2}\"\n  } else {\n    testImplementation \"org.specs2:specs2-core${scalaBackend.suffix}:{version-framework-specs2}\"\n  }\n  testImplementation \"com.lihaoyi:utest${scalaBackend.suffix}:{version-framework-utest}\"\n  if (scalaBackend.backend.jvm) {\n    testImplementation \"org.typelevel:weaver-cats:{version-framework-weaver}\"\n  }\n  testImplementation \"dev.zio:zio-test-sbt${scalaBackend.suffix}:{version-framework-zio-test}\"\n}\n----\n\nPlugin provides a method for adding test framework dependencies easier.\nThe following Gradle build script fragment adds all test framework dependencies\nthat fit the Scala version and backend using this method:\n\n[source,groovy,subs=\"+attributes\"]\n----\nimport org.podval.tools.test.framework.*\n\ndependencies {\n  if (scalaBackend.backend.jvm) {\n    testImplementation scalaBackend.testFramework(JUnit4Jvm, '{version-framework-junit4-jvm}')\n  }\n  if (scalaBackend.backend.js) {\n    testImplementation scalaBackend.testFramework(JUnit4ScalaJS, '{version-scalajs}')\n  }\n  if (scalaBackend.backend.native) {\n    testImplementation scalaBackend.testFramework(JUnit4ScalaNative, '{version-scalanative}')\n  }\n  if (!scalaBackend.backend.native || scalaBackend.scala3) {\n    testImplementation scalaBackend.testFramework(AirSpec, '{version-framework-airspec}')\n  }\n  testImplementation scalaBackend.testFramework(Hedgehog, '{version-framework-hedgehog}')\n  testImplementation scalaBackend.testFramework(MUnit, '{version-framework-munit}')\n  testImplementation scalaBackend.testFramework(ScalaCheck, '{version-framework-scalacheck}')\n  if (scalaBackend.backend.jvm) {\n    testImplementation scalaBackend.testFramework(Scalaprops, '{version-framework-scalaprops}')\n  }\n  testImplementation scalaBackend.testFramework(ScalaTest, '{version-framework-scalatest}')\n if (!scalaBackend.scala3 || scalaBackend.backend.native) {\n    testImplementation scalaBackend.testFramework(Specs2, '{version-framework-specs2-scala2}')\n  } else {\n    testImplementation scalaBackend.testFramework(Specs2, '{version-framework-specs2}')\n  }\n  testImplementation scalaBackend.testFramework(UTest, '{version-framework-utest}')\n  if (scalaBackend.backend.jvm) {\n    testImplementation scalaBackend.testFramework(WeaverTest, '{version-framework-weaver}')\n  }\n  testImplementation scalaBackend.testFramework(ZioTest, '{version-framework-zio-test}')\n}\n----\n\nYou do not have to specify test framework versions explicitly;\nto use the latest versions available at the time the version of the plugin\nyou are using was released, above can be simplified further:\n\n[source,groovy,subs=\"+attributes\"]\n----\nimport org.podval.tools.test.framework.*\n\ndependencies {\n  if (scalaBackend.backend.jvm) {\n    testImplementation scalaBackend.testFramework(JUnit4Jvm)\n  }\n  if (scalaBackend.backend.js) {\n    testImplementation scalaBackend.testFramework(JUnit4ScalaJS)\n  }\n  if (scalaBackend.backend.native) {\n    testImplementation scalaBackend.testFramework(JUnit4ScalaNative)\n  }\n  if (!scalaBackend.backend.native || scalaBackend.scala3) {\n    testImplementation scalaBackend.testFramework(AirSpec)\n  }\n  testImplementation scalaBackend.testFramework(Hedgehog)\n  testImplementation scalaBackend.testFramework(MUnit)\n  testImplementation scalaBackend.testFramework(ScalaCheck)\n  if (scalaBackend.backend.jvm) {\n    testImplementation scalaBackend.testFramework(Scalaprops)\n  }\n  testImplementation scalaBackend.testFramework(ScalaTest)\n  testImplementation scalaBackend.testFramework(Specs2)\n  testImplementation scalaBackend.testFramework(UTest)\n  if (scalaBackend.backend.jvm) {\n    testImplementation scalaBackend.testFramework(WeaverTest)\n  }\n  testImplementation scalaBackend.testFramework(ZioTest)\n}\n----\n\n=== Junit4\nJUnit4 SBT interface (`com.github.sbt:junit-interface`)\nis a separate project from JUnit4 itself;\nSBT interface dependency brings in the underlying framework dependency\n`junit:junit` transitively;\nits version can be overridden in the Gradle build script.\n\n- test filtering: works fine;\n- ignoring a test: not supported;\n- assumptions: if falsified, result in a test being skipped: `org.junit.Assume.assumeTrue(false)`;\n\n==== Test Tagging\nTag tests with classes or traits\nthat do not have to be derived from anything `JUnit4`-specific;\nin the Gradle build file, `excludeCategories` and `includeCategories`\nlist fully-qualified names of tagging classes or traits:\n[source, scala]\n----\ntrait IncludedTest\ntrait ExcludedTest\n@org.junit.experimental.categories.Category(Array(\n  classOf[org.podval.tools.test.IncludedTest],\n  classOf[org.podval.tools.test.ExcludedTest]\n))\n@Test def excluded(): Unit = ()\n----\n\n==== Nested Suites\nJUnit4 uses an annotation on the nesting suite to indicate that it\ncontains nested suites:\n\n[source,scala]\n----\n@org.junit.runner.RunWith(classOf[org.junit.runners.Suite])\n----\n\nand another annotation that lists the nested suites:\n\n[source,scala]\n----\n@org.junit.runners.Suite.SuiteClasses(Array(\n  classOf[JUnit4Nested]\n))\n----\n\nFor example, `JUnit4Nesting` contains `JUnit4Nested`:\n\n[source,scala]\n----\n@org.junit.runner.RunWith(classOf[org.junit.runners.Suite])\n@org.junit.runners.Suite.SuiteClasses(Array(\n  classOf[JUnit4Nested]\n))\nclass JUnit4Nesting {\n}\n\nimport org.junit.Test\nimport org.junit.Assert.assertTrue\n\nfinal class JUnit4Nested {\n  @Test def success(): Unit = assertTrue(\"should be true\", true)\n  @Test def failure(): Unit = assertTrue(\"should be true\", false)\n}\n----\n\nBy default, `JUnit4` 's `sbt` framework\nhttps://github.com/sbt/junit-interface/blob/develop/src/main/java/com/novocode/junit/JUnitRunner.java#L39[ignores] the\n`org.junit.runners.Suite` runner; plugin supplies an appropriate\narguments to enable it.\n\nBy default, `JUnit4` does not produce summary of the test run;\nplugin supplies an appropriate arguments to enable it.\n\n=== JUnit4 for Scala.js\nJUnit4 for Scala.js is a framework distinct from JUnit4:\nit is a partial translation/re-implementation of JUnit4 circa 2015\nand has different capabilities.\n\n- test filtering: does not support test case selectors and runs all test cases in the class;\n- test tagging: not supported;\n- nested suites: not supported;\n- ignoring tests: not supported;\n- assumptions: not supported;\n\n=== JUnit4 for Scala Native\nJUnit4 for Scala Native is a framework distinct from JUnit4:\nit is a port of the JUnit4 for Scala.js,\nwhich is a partial translation/re-implementation of JUnit4 circa 2015\nand has different capabilities.\n\n- test filtering: does not support test case selectors and runs all test cases in the class;\n- test tagging: not supported;\n- nested suites: not supported;\n- ignoring tests: not supported;\n- assumptions: not supported;\n\n=== AirSpec\n- test filtering: does not support test case selectors and runs all test cases in the class;\n- test tagging: not supported;\n- nested suites: not supported;\n- assumptions: not supported;\n- ignoring a test: not supported;\n\n=== Hedgehog\n- test filtering: does not support test case selectors and runs all test cases in the class;\n- test tagging: not supported;\n- nested suites: not supported;\n- assumptions: not supported;\n- ignoring a test: not supported;\n\n=== MUnit\n- test filtering: works fine on `JVM`; on `Scala.js`, does not support test case selectors and runs all test cases in the class;\n- nested suites: not supported;\n- assumptions: not supported;\n- ignoring a test works: `test(\"test\".ignore) {}`;\n\nMUnit uses JUnit internally,\nand transitively brings in the underlying framework dependency\n(whose version can be overridden in the Gradle build script):\n\n- on JVM - `junit:junit`;\n- on Scala.js - `org.scala-js:scalajs-junit-test-runtime`;\n- on Scala Native - `org.scala-native:junit-runtime`.\n\nBy default, `MUnit` does not produce summary of the test run;\nplugin supplies an appropriate arguments to enable it.\n\n==== Test Tagging\nMUnit is based on JUnit4, so it supports the `Category`-based exclusion and inclusion;\nsince on Scala.js MUnit uses `JUnit4 for Scala.js`,\nwhich does not support this mechanism,\nMUnit does not support it either.\n\nPlugin does not use `Category`-based mechanism;\nMUnit provides a different, `Tag`-based mechanism,\nand that is what plugin uses.\n\nTag tests with values that are instances of `munit.Tag`:\n\n[source, scala]\n----\nval include = new munit.Tag(\"org.podval.tools.test.ExcludedTest\")\nval exclude = new munit.Tag(\"org.podval.tools.test.ExcludedTest\")\ntest(\"excluded\".tag(include).tag(exclude)) {}\n----\n\nWhen tagging classes used for inclusion/exclusion are not available,\nMUnit crashes with a `ClassNotFound`.\n\n=== ScalaCheck\n- test filtering functionality is not available;\n- test tagging: not supported, but if it is used via another test framework -\nlike `ScalaTest` or `specs2` - test tagging mechanisms provided by that\nframework can be used;\n- assumptions: not supported;\n- ignoring a test: not supported;\n\n==== Nested Suites\nIn ScalaCheck, nesting is accomplished by using\n`org.scalacheck.Properties.include()`:\n\n[source,scala]\n----\nobject ScalaCheckNesting extends org.scalacheck.Properties(\"ScalaCheckNesting\") {\n  include(ScalaCheckNested)\n}\n\nobject ScalaCheckNested extends org.scalacheck.Properties(\"ScalaCheckNested\") {\n  property(\"success\") = org.scalacheck.Prop.passed\n  property(\"failure\") = org.scalacheck.Prop.falsified\n}\n----\n\nWith ScalaCheck, nested test cases are attributed to the _nesting_ suite -\nand there is nothing that can be done about it,\nsince ScalaCheck itself does not keep information about which class a property belongs to.\n\n=== Scalaprops\n- test filtering: does not support test case selectors and runs all test cases in the class;\n- test tagging: not supported;\n- nested suites: not supported;\n- assumptions: not supported;\n- ignoring a test: `Property.forAll { ... }.ignore(\"...\")`;\n\n=== ScalaTest\n- test filtering: works fine;\n- assumptions: not supported;\n- ignoring a test: `ignore should \"be ignored\"`;\n\n==== Test Tagging\nTag tests with objects that extend `org.scalatest.Tag`:\n[source, scala]\n----\nobject Include extends org.scalatest.Tag(\"org.podval.tools.test.IncludedTest\")\nobject Exclude extends org.scalatest.Tag(\"org.podval.tools.test.ExcludedTest\")\n\"excluded\" should \"not run\" taggedAs(Include, Exclude) in {  true shouldBe false }\n----\n\n==== Nested Suites\nIn `ScalaTest`, nesting of the test suites is indicated by\nderiving the nesting class from `org.scalatest.Suites`\nand listing the nested suites in its constructor:\n\n[source,scala]\n----\nclass ScalaTestNesting extends org.scalatest.Suites(\n  new ScalaTestNested\n)\n----\n\n=== Specs2\n- test filtering: works fine;\n- nested suites: not supported;\n- assumptions: not supported;\n- ignoring a test: not supported;\n\n==== Test Tagging\nTag tests with tag names:\n[source,scala]\n----\nexclude tests tagged for exclusion $excludedTest ${tag(\n  \"org.podval.tools.test.IncludedTest\",\n  \"org.podval.tools.test.ExcludedTest\"\n)}\n----\n\n=== uTest\n- test filtering: does not support test case selectors and runs all test cases in the class.\n- test tagging: not supported;\n- assumptions: not supported;\n- ignoring a test: not supported;\n\n==== Nested Suites\nOnly test suites defined in the same test class can be nested:\n\n[source,scala]\n----\nimport utest._\n\nobject UTestNesting extends TestSuite {\n  val tests: Tests = Tests {\n    test(\"UTestNesting\") {\n      test(\"UTestNested\") {\n        test(\"success\") { assert(1 == 1) }\n        test(\"failure\") { assert(1 == 0) }\n      }\n    }\n  }\n}\n----\n\n=== Weaver Test\n- test filtering: does not support test case selectors and runs all test cases in the class;\n- test tagging: not supported;\n- nested suites: not supported;\n- assumptions: not supported;\n- ignoring a test: not supported;\n\n=== ZIO Test\n\n- test filtering: treats specific test case inclusions as wildcards,\nand instead of running just the named test cases runs all whose names contain\nthe specified string, because the only test case name-based filtering that ZIO Test supports is \"search terms\", which\nhttps://github.com/zio/zio/blob/series/2.x/test/shared/src/main/scala/zio/test/FilteredSpec.scala#L32[work as wildcards];\n- ignoring a test: `test(\"ignored\") { ... } @@ zio.test.TestAspect.ignore`;\n- assumption: `test(\"assumption\") { ... } @@ zio.test.TestAspect.ifProp(\"property\")(string =\u003e false)`\n\n==== Test Tagging\nTag tests with tag names using `TestAspect.tag`:\n[source, scala]\n----\ntest(\"tagged\") { ... } @@ TestAspect.tag(\n  \"org.podval.tools.test.IncludedTest\",\n  \"org.podval.tools.test.ExcludedTest\"\n)\n----\n\n==== Nested Suites\n\n[source,scala]\n----\nimport zio.test._\n\nobject ZIOTestNesting extends ZIOSpecDefault {\n  override def spec: Spec[TestEnvironment, Any] = suite(\"ZIOTestNesting\")(\n    ZIOTestNested.spec\n  )\n}\nobject ZIOTestNested extends ZIOSpecDefault {\n  override def spec: Spec[TestEnvironment, Any] = suite(\"ZIOTestNested\")(\n    test(\"success\") { assertTrue(1 == 1) },\n    test(\"failure\") { assertTrue(1 == 0) },\n  )\n}\n----\n\n== Implementation Notes\n\n=== Linking and Running Scala.js and Scala Native\nIt is reasonably easy, if repetitive, to configure the Scala compiler and add needed Scala.js dependencies by hand;\nwhat really pushed me to build this plugin is the difficulty and ugliness involved in\nmanually setting up Scala.js linking in a Gradle build script.\n\nFor Scala.js, I perused:\n\n- https://www.scala-js.org/doc/tutorial/basic[Scala.js Tutorial]\n- https://github.com/scala-js/scala-js/tree/main/linker-interface[Scala.js Linker]\n- https://github.com/scala-js/scala-js/tree/main/sbt-plugin/src/main/scala/org/scalajs/sbtplugin[Scala.js sbt plugin]\n- https://github.com/gtache/scalajs-gradle[Scala.js Gradle plugin] by https://github.com/gtache[gtache]\n- https://github.com/scala-js/scala-js-cli/tree/main/src/main/scala/org/scalajs/cli[Scala.js CLI]\n\nFor Scala.Native, I perused:\n\n- https://github.com/scala-native/scala-native/blob/main/sbt-scala-native/src/main/scala/scala/scalanative/sbtplugin/ScalaNativePluginInternal.scala[Scala Native sbt plugin]\n- https://github.com/com-lihaoyi/mill/blob/main/libs/scalanativelib/worker/0.5/src/mill/scalanativelib/worker/ScalaNativeWorkerImpl.scala[Mill] (a little)\n\n[#mixing-backends]\n=== Mixing Backends\nMy original approach was to use Gradle's _features_ to scope source sets and tasks\nbelonging to different backends within the same project;\nthis was implemented in the unpublished version `0.7.9`.\n\nThis approach was deemed too complicated to use and implement\nand was replaced with the current approach\nwhere backend-specific entities are scoped by backend-specific _subprojects_.\n\nSharing code between backends turned out more difficult than I thought.\nFor Gradle to treat shared sources correctly, they just need to be added to the\nappropriate source sets of the backend-specific subprojects.\n\nUnfortunately, when such a project is imported into IntelliJ Idea\nit triggers an infamous (12 years old)\nhttps://youtrack.jetbrains.com/issue/IDEABKL-6745/Cannot-define-two-identical-content-roots-in-different-module-within-a-single-project[issue]\nof \"Duplicate Content Roots\".\n\nSo, when running in IntelliJ Idea, plugin does not add shared directories to the source sets\nthey belong to at application time,\nallowing the project to be safely imported into IntelliJ Idea;\ninstead, plugin configures tasks that need shared sources\nto add them before execution, and remove them after the execution (the latter might not be necessary).\n\nOf course, with the shared sources not added to the source sets of the backend-specific projects,\nthose sources are not known to the IDE: one cannot click through from the use to definition and back etc.\nTo fix this, when running in IntelliJ Idea,\nplugin adds to backend-specific projects\nproject dependencies on the shared projects.\n\nOf course, these dependencies creep into the POMs\nof the artifacts published from within the IDE -\nso publishing should probably be done from the command line ;)\n\nSupport for sources shared between some but not all backends (partial sharing)\nwas inspired by similar feature of\nhttps://github.com/portable-scala/sbt-crossproject[sbt-crossproject];\nI did not see this feature documented anywhere,\nbut encountered its\nhttps://github.com/scalameta/munit/pull/646[use]\nwhile perusing the code of https://github.com/scalameta/munit[MUnit] ;)\n\n=== Building for Multiple Scala Versions\n\nI perused:\n\n- https://www.scala-sbt.org/1.x/docs/Cross-Build.html[sbt Cross-building] documentation\n- https://github.com/ADTRAN/gradle-scala-multiversion-plugin[Gradle Scala Multi-Version Plugin]\n\n=== Node.js\n\n`Node.js` support that the plugin provides\nis heavily inspired by (read: copied and reworked from :))\nhttps://github.com/srs/gradle-node-plugin[gradle-node-plugin].\n\nThat plugin is not used directly because its tasks are not reusable\nunless the plugin is applied to the project,\nand I do not want to apply Node Gradle plugin to every project that uses my\nScala.js Gradle plugin.\n\nAlso, I want to be able to run `npm` from within my code without creating tasks.\nAlso, I would like to be able to use Node available via GraalVM's polyglot support.\n\nMy simplified Node support is around 300 lines.\n\n=== Dynamic Dependencies\nI coded a neat way to add dependencies dynamically,\n\nCode to do this is in\nlink:src/main/scala/org/podval/tools/build/[org.podval.tools.build].\nIt can:\n\n- detect versions of Scala and specific dependencies;\n- add dependencies to configurations;\n- expand the classpath.\n\nThis allows the plugin to add dependencies\nwith correct versions and built for correct version of Scala\nwhich may be different from the one\nplugin uses, so that Scala 2.12 can be supported.\n\nClasspath expansion allows the plugin to use classes from dependencies\nthat are added dynamically, but since they become available only after\nclasspath is expanded, they can only be used indirectly;\nthat is why such classes are only mentioned by name in dedicated intermediate classes.\n\n=== Scala 2.12\nWhen running on JVM (and not on Scala.js nor Scala Native),\ntests are forked into a separate JVM.\nCode involved in this is running on the project's, not the plugin's, version of Scala.\n\nIf the project uses Scala 2.13, Scala 3 classes like `scala/runtime/LazyVals$`\nare missing; this is remedied by adding Scala 3 library to the\nworker's implementation classpath in `TestFramework`.\n\nIf that version is 2.12, any use of 2.13-exclusive features breaks the code,\nso I wrote it defensively,\nto support 2.12 even though the code was compiled by Scala 3.\nEssentially, I use arrays and my own implementations of the array operations\n(see link:src/main/scala/org/podval/tools/util/Scala212Collections.scala[Scala212Collections]).\n\nSome of the issues:\n\n- java.lang.NoClassDefFoundError: scala/collection/StringOps$\n- java.lang.NoClassDefFoundError: scala/collection/IterableOnce\n- java.lang.NoSuchMethodError: scala.Predef$.refArrayOps()\n- java.lang.NoSuchMethodError: scala.Predef$.wrapRefArray()\n- java.lang.NoSuchMethodError: scala.collection.immutable.Map.updated()\n\nSome of the affected code runs even when using Scala.js,\nand it works without those compatibility changes;\nthis is probably because within the JVM running Gradle,\nScala 2.13 library is on the classpath, even if the project uses Scala 2.12...\n\nI'd rather uglify my code a little than fight with the classpath though ;)\n\n=== AsciiDoc\nGitHub stupidly disables AsciDoc includes in README;\nsee https://github.com/github/markup/issues/1095[the discussion].\n\nOne include (of the `versions.adoc` in `README.adoc`)\nis not enough to bother with https://github.com/asciidoctor/asciidoctor-reducer[AsciiDoctor Reducer],\nso I just patch the Readme.adoc...\n\nI also write versions to `gradle.properties` and use them in `gradle.build`.\n\n=== Testing\n\nTo figure out how `sbt` itself integrates with testing frameworks, I had to untangle some `sbt` code, including:\n\n- `sbt.Defaults`\n- `sbt.Tests`\n- `sbt.TestRunner`\n- `sbt.ForkTests`\n- `org.scalajs.sbtplugin.ScalaJSPluginInternal`\n\nTurns out, internals of `sbt` are a maze of twisted (code) passages,\nall alike, where pieces of code are stored in key-value maps,\nand addition of such maps is used as an override mechanism.\nWhat a disaster!\n\nThere are _two_ testing interfaces in `org.scala-sbt:test-interface:1.0`;\nI use the one used by the Scala.js sbt plugin - presumably the \"new\" one ;)\n\nJust being able to run the tests with no integration with\nGradle or IntelliJ Idea seemed suboptimal,\nso I decided to look into proper integrations of things like\n`org.scala-js:scalajs-sbt-test-adapter` and\nhttps://github.com/sbt/test-interface[org.scala-sbt:test-interface].\n\nI perused:\n\n- https://github.com/gradle/gradle[Gradle]\n- https://github.com/JetBrains/intellij-community[IntelliJ Idea]\n- https://github.com/maiflai/gradle-scalatest[Gradle ScalaTest plugin]\n\nThis took _by far_ the most of my time\n(and takes up more than 3/4 of the plugin code),\nand uncovered a number of surprises.\n\nIntelliJ Idea instruments Gradle test task with its `IJTestEventLogger` -\nbut _only_ if the task is of type `org.gradle.api.tasks.testing.Test`,\nso that is what I derive my test task from.\n\nOnce I worked out how to integrate tests on Scala.js with Gradle and IntelliJ Idea,\nit was reasonably easy to re-use this integration to run tests\nusing sbt-compatible frameworks _without_ any Scala.js involved -\nin plain Scala projects.\n\n=== Testing the Tests\nI coded a neat way to test the plugin itself and\nvarious features of the various frameworks and their support by the plugin:\nlink:src/test/scala/org/podval/tools/test/testproject/Feature.scala[Feature],\nlink:src/test/scala/org/podval/tools/test/testproject/Fixture.scala[Fixture],\nlink:src/test/scala/org/podval/tools/test/testproject/ForClass.scala[ForClass],\nlink:src/test/scala/org/podval/tools/test/testproject/GroupingFunSpec.scala[GroupingFunSpec],\nlink:src/test/scala/org/podval/tools/test/testproject/SourceFile.scala[SourceFile],\nlink:src/test/scala/org/podval/tools/test/testproject/TestProject.scala[TestProject].\n\n[#test-detection]\n=== Test Detection\nPlugin needs to associate a test framework and a fingerprint with each test class,\nso it uses its own test detector.\n\nThis is why file-name based test scan is not supported\n(`isScanForTestClasses` must be at its default value `true`):\nname of the test class is not sufficient to determine which test framework\nthe class belongs to.\n\nThis is also why `JUnit5` is not supported:\nit insists on discovering the tests itself, as a\nhttps://github.com/sbt/sbt-jupiter-interface/blob/main/src/library/src/main/java/com/github/sbt/junit/jupiter/api/JupiterTestFingerprint.java#L42[comment]\non the `JupiterTestFingerprint.annotationName()` says:\n\n\u003e return The name of this class. This is to ensure that SBT does not find\n\u003e any tests so that we can use JUnit Jupiter's test discovery mechanism.\n\nWell, mission accomplished: my test detector does not find any tests either.\n\nOriginally, I coded a test detection mechanism that used\nanalysis file generated by the Scala compiler.\nThis code was later replaced with a traditional mechanism\nbased on scanning the class files,\nsimilar to the mechanism used by Gradle for test detection with `JUnit4` and `TestNG`.\n\nIf a class file is recognized by more than one framework\n(e.g. `MUnit` tests, which are also `JUnit4` tests),\nit is attributed to the framework whose fingerprint is closer to\nthe test class in the hierarchy (e.g. `MUnit`).\n\nIf a test class is encountered with more than one framework claiming it\nat the same distance in the hierarchy\n(which does not happen naturally, but can be constructed),\nmistake is assumed, a warning is issued, and the class is ignored.\n\nOn `Scala.js`, annotation are not available at runtime\n(Scala.js compiler does not add `RuntimeVisibleAnnotations` to the class file),\nso this mechanism alone does not detect tests that are marked as such\nusing annotations.\n\nCurrently, the only test framework that marks tests as tests using annotations\nis `JUnit4 for Scala.js`.\nWhen `JUnit4 for Scala.js` is on the classpath,\nfor each test class candidate\nplugin looks for the bootstrapper left behind by the Scala.js compiler\n(or, on Scala 2, Scala compiler plugin that generates bootstrappers).\nPresence of a bootstrapper `TestClass$scalajs$junit$bootstrapper$`\nis treated as a presence of the `@Test` annotation on `TestClass`,\nwhich marks it as a test belonging to the `JUnit4 for Scala.js` test framework.\n\n=== Test Run Data\nTest detection produces more information than just the class name:\n\n- framework that recognized the test\n- fingerprint\n- selectors\n\nI need to deliver this additional information to forked test processors.\n\nFor a while, I used modified serializer for this;\nof course, serializer is hard-coded in the Gradle code,\nso to use mine I had to modify three Gradle files...\n\nI even made a https://github.com/gradle/gradle/pull/24088[pull request]\nto add flexibility in this regard to Gradle -\nbut then I realized that I can encode additional information I need\nto get to the worker in the test class name!\n\n=== Test Events\nTurns out that IntelliJ Idea integration only works when all the calls to\nthe IJ listener happen from the same thread\n(it probably uses some thread-local variable to set up cross-process communications).\nSince some of the calls are caused by the call-back from the sbt testing interface's\nevent handler, I get \"Test events were not received\" in the Idea test UI.\nIt would have been nice if this fact was documented somewhere :(\nI coded an event queue with its own thread, but then discovered that:\n\n- Gradle provides a mechanism that ensures that all the calls are made from the same thread: `Actor.createActor.getProxy`;\n- when tests are forked, `MaxNParallelTestClassProcessor` is used, which already does that, so I do not need to;\n- when running on `Scala.js` everything is single-threaded anyway.\n\n=== Test Ids\n`org.gradle.internal.remote.internal.hub.DefaultMethodArgsSerializer`\nseems to make a decision which serializer registry to use based on the\noutcome of the `SerializerRegistry.canSerialize()` call\nfor the class of the first parameter of a method;\ntest id is the first parameter of the `TestResultProcessor.output()`, `completed()` and `failure()` calls.\nWithout some tricks like registering a serializer for `AnyRef` and disambiguating\nin the `SerializerRegistry.build()` call,\nneither `null` nor `String` are going to work as ids.\n\nThis is _probably_ the reason why Gradle:\n\n- makes all test ids `CompositeIdGenerator.CompositeId`\n- registers a `Serializer[CompositeIdGenerator.CompositeId]` in `TestEventSerializer`.\n\nGradle just wants to attract attention to its `TestEventSerializer`,\nso it registers serializers for the types\nof the first parameters of all methods - including the test ids ;)\n\nAnd since the minimum of composed is two,\nGradle uses test ids that are composite of two Longs.\n\nAbstractTestTask installs `StateTrackingTestResultProcessor`\nwhich keeps track of all tests that are executing in any `TestWorker`.\nThat means that test ids must be scoped per `TestWorker`.\nEach `TestWorker` has an `idGenerator` which it uses to generate `WorkerTestClassProcessor.workerSuiteId`;\nthat same `idGenerator` can be used to generate sequential ids\nfor the tests in the worker,\nsatisfying the uniqueness requirements - and resulting in the test ids always being\na composite of exactly two Longs!\n\nBecause tests are scoped by the workers, it does not seem possible to group test results by framework.\n\nSince I can not use the real `rootTestSuiteId` that `DefaultTestExecuter`\nsupplies to the `TestMainAction` - because it is a `String` -\nand I am not keen on second-guessing what it is anyway,\nI use a `RunTestClassProcessor.rootTestSuiteIdPlaceholder`\nand change it to the real one in `FixRootTestSuiteOutputTestResultProcessor`.\n\n=== Test Output\n\nAll output of tests and test frameworks,\nregardless if it goes through my plugin or not, printed or logged,\nends up delivered to Gradle as a `org.gradle.api.tasks.testing.TestOutputEvent` s.\n\nThere are various ways running the tests produces output:\n\n- output of the tests themselves, which goes to the standard output;\n- debug information from the plugin, which it packages\ninto `TestOutputEvent`;\n- progress of tests execution, which test frameworks log via an\n  `sbt.testing.Logger` to the plugin, which in turns it into `TestOutputEvent`;\n- test framework summary, which the plugin retrieves once the tests are done\n  and turns into `TestOutputEvent`;\n\n`uTest` exposes _two_ implementations of `sbt.testing.Framework`:\n\n- `utest.runner.Framework` and\n- `utest.runner.MillFramework`.\n\nSituation with test progress reporting is nuanced:\n\n- `JUnit4`, `JUnit4 for Scala.js`, `JUnit4 for Scala Native` do not report progress;\n\n- `AirSpec`, `Hedgehog`, `Weaver Test`, `ZIO Test`\nwrite progress to standard out;\n\n- `MUnit` on JVM logs the progress;\n- `MUnit` on Scala.js and Scala Native writes progress to standard out;\n\n- `ScalaCheck`, `ScalaTest`, `specs2` log the progress;\n\n- `uTest` writes to the standard out a header: \"--- Running Tests ---\", then:\n* `utest.runner.MillFramework` logs the progress while\n* `utest.runner.Framework` writes it to standard out;\n\nSituation with the summary is nuanced:\nsome test frameworks (`JUnit4`, `MUnit` on JVM, `uTest` s `utest.runner.MillFramework`, `ZIO Test` on JVM),\ninstead of logging the summary as they should,\nwrite it to the standard out,\nas a work-around for a\nhttps://github.com/sbt/sbt/issues/3510[\"bug\"] reported in 2017\nby https://github.com/lihaoyi[lihaoyi], author of `uTest`.\nThis \"bug\" only manifests when the test framework is instantiated twice:\nin the original process and in the forked one;\nsince Scala.js and Scala Native tests can not be forked,\nthe \"bug\" does not apply to them\n(but `uTest` still applies the unnecessary work-around).\nI do not know if this \"bug\" still exists (or ever existed) in sbt;\nI do know that with some work real test summary _can_ be returned\nas it is supposed to even on JVM:\nwitness `ScalaCheck` and `Scalatest`.\n\n- `JUnit4` and `MUnit` on JVM return empty summary, and,\nif enabled with \"--summary=1\", writes the real summary\n(`All tests passed/Some tests failed: _ failed, _ ignored, _ total, _._s`)\nto standard out - citing the\nhttps://github.com/sbt/junit-interface/blob/develop/src/main/java/com/novocode/junit/JUnitRunner.java#L126[\"bug\"];\n\n- `JUnit4 for Scala.js`, `JUnit4 for Scala Native`,\nand `MUnit` on Scala.js or Scala Native return empty summary;\n\n- `AirSpec` returns empty summary;\n\n- `Hedgehog` returns empty summary;\n\n- `ScalaCheck` does the right thing and returns the real summary\non all backends, \"bug\" notwithstanding - thanks to the\nhttps://github.com/typelevel/scalacheck/issues/185#issuecomment-372509235[work] done by https://github.com/retronym[retronym];\n\n- `ScalaTest` return the real summary on all backends,\n\"bug\" notwithstanding!\n\n- `specs2` returns empty summary; if there were failing tests, it logs them;\n\n- `uTest`:\n* `utest.runner.Framework` returns the real summary\n(`Tests: _, Passed: _, Failed: _`);\n* `utest.runner.MillFramework` returns empty summary,\nand writes the real summary to standard out - citing the\nhttps://github.com/com-lihaoyi/utest/blob/master/utest/src/utest/runner/MasterRunner.scala#L64[\"bug\"] -\neven on Scala.js and Scala Native, where the \"bug\" does not exist;\n\n- `Weaver Test`:\n* on JVM: returns empty summary;\n* on Scala.js and Scala Native: returns some with the list of failed tests and `_ test completed, _ failed`;\n\n- `ZIO Test`:\n* on JVM - returns a dummy summary \"Completed tests\" and\nwrites the real summary (`_ tests passed. _ tests failed. _ tests ignored.`)\nto standard out - citing the https://github.com/zio/zio/blob/series/2.x/test-sbt/jvm/src/main/scala/zio/test/sbt/ZTestRunnerJVM.scala#L67[\"bug\"];\n* on Scala.js and Scala Native - calculates the totals,\nthen promptly discards them and\nreturns as a summary list of failure details for failed tests (if any);\n\nGradle's test output listener\nprints test name and the name of the output stream on a separate line,\nand indents the output under it.\nWhich `TestOutputEvent` are logged depends on the\n`testLogging` configuration on the `test` task and\nthe log level of the Gradle run: `lifecycle` by default, `info`, etc.\n\nAs a result, Gradle does not show any test output,\nprogress reports or summaries by default;\nwhen run with informational logging enabled (`./gradlew -i`)\nit shows them all.\n\nIntelliJ Idea, in https://github.com/JetBrains/intellij-community/blob/master/plugins/gradle/tooling-extension-impl/resources/org/jetbrains/plugins/gradle/tooling/internal/init/IjTestEventLoggerInit.gradle[IjTestEventLoggerInit.gradle],\ndisables output logging by the Gradle test output listener\nby setting `testLogging.showStandardStreams` to `false` and, in https://github.com/JetBrains/intellij-community/blob/master/plugins/gradle/tooling-extension-impl/resources/org/jetbrains/plugins/gradle/tooling/internal/init/IjTestEventLogger.gradle[IjTestEventLogger.gradle],\ninstalls its own test output listener.\nThis listener does not batch, indent, or adds anything to the output.\n\nIntelliJ's TestOutputListener writes the output to the console\nregardless of the Gradle log level.\nWhen running in IntelliJ, plugin sends `TestOutputEvents`\ncarrying test progress reports and summaries regardless of the Gradle log level.\n\nAs a result, when running in IntelliJ, all kinds of test output are shown.\n\n=== Test Tagging\nAlthough it is tempting to help the test frameworks out by\nfiltering tests based on their tags\nreturned by the test framework in `task.tags`, it is:\n\n- unnecessary, since all the test frameworks plugin supports\nthat support tagging accept\narguments that allow them to do the filtering internally;\n- destructive, since none of the test frameworks plugin supports\npopulate `task.tags`, so with explicit tag inclusions, none of the tests run!\n\n=== Nested Tasks and Test Cases\n\n`sbt` test interface allows test framework to return nested tasks\nwhen executing a task;\nof the test frameworks supported by the plugin,\nonly `ScalaCheck` uses this mechanism:\nit returns test cases of the test class being executed\nas  nested tasks (with `TestSelector`).\n\nAll other frameworks run the test cases directly\nand report the results via event handler;\nwhat selector is reported depends on the test framework:\n\n- most test frameworks use `TestSelector`;\n- `uTest` uses `NestedTestSelector`;\n- `ScalaTest` uses `NestedTestSelector` for test cases from the nested suites;\n- `JUnit4`, `JUnit4 for Scala.js` and `MUnit` use `TestSelector`\neven for test cases from the nested suites,\nbut they prepend the name of the class to the test case name\n(both in the selector and in the event's `fullyQualifiedName`);\nplugin makes sure to attribute test cases to the correct test classes.\n\n=== Testing Scala.js and Scala Native\n\nScala.js and Scala Native tests must be run in the same JVM\nwhere their frameworks were instantiated\n(see\nhttps://github.com/scala-js/scala-js/blob/main/sbt-plugin/src/main/scala/org/scalajs/sbtplugin/ScalaJSPluginInternal.scala#L676[org.scalajs.sbtplugin.ScalaJSPluginInternal],\nhttps://github.com/scala-native/scala-native/blob/main/sbt-scala-native/src/main/scala/scala/scalanative/sbtplugin/ScalaNativePluginInternal.scala[scala.scalanative.sbtplugin.ScalaNativePluginInternal]\n).\n`TestExecuter` makes sure that the tests are not forked,\nand `TestTask` overrides\n`org.gradle.api.tasks.testing.Test.getMaxParallelForks()`\nto return `1` on `Scala.js` to prevent `MaxNParallelTestClassProcessor`\nfrom forking.\n\nOn JVM, exceptions are serialized in Gradle's `org.gradle.internal.serialize.ExceptionPlaceholder`, which contains lots of details;\non Scala.js, `org.scalajs.testing.common.Serializer.ThrowableSerializer`\nturns them all into `org.scalajs.testing.common.Serializer$ThrowableSerializer$$anon$3`;\nsince source mapping is used only on Scala.js,\nthere is no point trying to preserve the original exception:\nit is already lost;\nso just wrap what remains in `TestExecutionException`.\n\n[#junit4-scalajs-scalanative]\n=== JUnit4 for Scala.js and Scala Native\nTurns out, `JUnit4 for Scala.js` and `JUnit4 for Scala Native`\nassume existence of a `bootstrapper`\nin every test class - apparently, because test discovery for `JUnit4`\nis based on annotations, and reflection on `Scala.js` and `Scala Native`\nis not powerful enough, so tests are pre-discovered _at compile time_,\nand JUnit4-specific bootstrappers generated for them.\n\nWithout bootstrappers, we get errors like:\n[source]\n----\nError while loading test class ... failed:\njava.lang.ClassNotFoundException: Cannot find ...$scalajs$junit$bootstrapper$\n----\n\nFor `Scala.js` on Scala 3, bootstrappers are generated by the `Scala.js` compiler;\nfor `Scala.js` on Scala 2, and always for `Scala Native`,\nto get the bootsrappers generated,\na dedicated Scala compiler plugin has to be added:\nfor Scala.js - `org.scala-js:scalajs-junit-test-plugin`,\nfor Scala Native - `org.scala-native:junit-plugin`.\n\nThis compiler plugin can _only_ be added when `JUnit4`\nis actually on the classpath - or Scala compiler breaks ;)\n\n[source]\n----\nscala.reflect.internal.MissingRequirementError:\n  object org.junit.Test in compiler mirror not found.\n----\n\nIt thus is added only to the _test_ Scala compilation and not to the _main_ one;\nsince plugins added to the `scalaCompilerPlugins` configuration affect both\nthe _test_ and the _main_ Scala compilations,\nplugin creates a separate configuration `testScalaCompilerPlugins` just for this one plugin\n(even when the JVM backend, that does not need, is used) ;)\n\nsee:\n\n- https://github.com/scala-js/scala-js/issues/2937[scala-js/issues/2937]\n- https://github.com/scala-js/scala-js/commit/269d1aaf1fa20afbcc3940b9dba58e99ee010dc1[scala-js/commit/269d1aaf]\n- https://github.com/scala-js/scala-js/issues/4191[scala-js/issues/4191]\n\n=== Gradle Internals\nTo stop tests from being forked - which is needed to run tests\non Scala.js or Scala Native -\nI had to fork `org.gradle.api.internal.tasks.testing.detection.DefaultTestExecuter`\n(see link:src/main/scala/org/podval/tools/test/task/DefaultTestExecuter.scala[DefaultTestExecuter]).\nThis is suboptimal, since I now have to track changes to the forked class.\nMy proposal to expose an extension point that would allow to avoid\nforking Gradle code was rejected:\nhttps://github.com/gradle/gradle/issues/32666[32666],\nhttps://github.com/gradle/gradle/pull/32656[32656];\nthat made it pretty clear that other modifications to Gradle that would make my code\ncleaner would be too, so I did not even bother;\nhere are examples of resulting ugliness:\n\n- to add to the implementation class path of `WorkerProcessBuilder`,\nI had to use reflection in\nlink:src/main/scala/org/podval/tools/test/task/SbtTestFramework.scala[SbtTestFramework];\n- to set test framework on the test task, I had to use reflection\nin link:src/main/scala/org/podval/tools/test/task/TestTask.scala[TestTask];\n- to set options on the test framework, I copied\n`org.gradle.api.tasks.testing.Test.options`: it is private and too short to bother with reflection;\n- to call `ForkedTestClasspath.getApplicationClasspath()` I had to use reflection,\nsince it returns `org.gradle.internal.impldep.com.google.common.collect.ImmutableList`,\nwhich is not accessible from the plugin and results in `java.lang.NoSuchMethodError`;\n- since Gradle's internal copy of `org.ow2.asm:asm` is under `impldep` and is not accessible to the plugin,\nI had to add an explicit dependency on `org.ow2.asm:asm`;\n- `org.gradle.api.tasks.testing.Test.testsAreNotFiltered()` calls `Test.noCategoryOrTagOrGroupSpecified()`,\nwhich recognizes only the test frameworks explicitly supported by Gradle (`JUnit` and `TestNG`); since I can not override it, I just use\n`org.gradle.api.tasks.testing.junit.JUnitOptions` as `SbtTestFrameworkOptions`.\n\n== History\n\n=== 2022, March\nThis plugin was born out of necessity:\nI had to write some Javascript for my wife's project.\nI dislike untyped languages, so if I _have_ to write `Javascript`,\nI want to be able to do it in my preferred language - `Scala`;\nthanks to https://www.scala-js.org[Scala.js], this is possible.\n\nI http://dub.podval.org/2011/11/08/sbt-why.html[dislike]\nhttps://www.scala-sbt.org[sbt] -\nthe https://www.scala-js.org/doc/project[official build tool] of Scala.js,\nwhich uses\nhttps://github.com/scala-js/scala-js/tree/main/sbt-plugin/src/main/scala/org/scalajs/sbtplugin[Scala.js sbt plugin];\nI want to be able to use my preferred build tool - https://gradle.org[Gradle].\n\nExisting Scala.js Gradle https://github.com/gtache/scalajs-gradle[plugin]\nseems to be no longer maintained.\n\nHence, this plugin.\n\n=== 2022, June-August\n\n- running Scala.js code on Node.js;\n- testing Scala.js and JVM code using any sbt-equipped test framework;\n- support projects using Scala 2.12;\n\nFor years, I used https://github.com/maiflai/gradle-scalatest[Gradle ScalaTest plugin]\nto run my Scala Tests.\nSince my plugin integrates with Gradle - and through it, with IntelliJ Idea -\nsome of the issues that that plugin has my does not:\nhttps://github.com/maiflai/gradle-scalatest/issues/67[Test events were not received],\nhttps://github.com/maiflai/gradle-scalatest/issues/69[ASCII Control Characters Printed].\n\nI never tried an alternative ScalaTest integration\nhttps://github.com/helmethair-co/scalatest-junit-runner[scalatest-junit-runner],\nand if you need `JUnit5` _that_ is probably the way to go,\nsince my plugin does not support `JUnit5`\n(it does support `Scala.js` and `Scala Native` though :)).\n\n=== 2023, March\n\n- create extension `node` to configure `Node.js` version;\n- auto-install `Node.js`;\n- add tasks to run `npm` and `node` commands;\n- initialize Node project and install modules;\n\n=== 2025, February-September\n\nI lost my day job in January 2025 and spent more than half a year working on the plugin ;)\n\n- test tagging for all the supported test frameworks;\n- nested test suites;\n- test dry-run;\n- support `Scala Native`;\n- mixed-backend projects with some sources shared among some of the backends;\n- sources specific to the Scala version;\n- expose data about backend and Scala version via an extension;\n- support more test frameworks on more backends;\n\nAll I wanted was to cross-compile my code for JVM and Scala.js\nand test it with Scala Test and ZIO Test.\nAll of that already works ;)\n\nOf course, I plan to address bug reports and feature requests\nfrom the users of the plugin,\nand periodically update plugin's dependencies (including Gradle).\n\n== Small Open Source Contributions\n\nWhile working on the plugin, I identified (and sometimes fixed)\nissues and suggested improvements to various open source projects.\nOf course, those contributions benefit not just this plugin ;)\n\nI want to thank all those who worked with me on these issues and fixes.\n\n- https://www.scala-js.org[Scala.js]:\n* _https://github.com/scala-js/scala-js/pull/5132[pull/5132]_\n_JUnit: populate sbt.testing.Event.throwable on test failure._\nThank you to https://github.com/sjrd[sjrd] for working with me on this.\n* _https://github.com/scala-js/scala-js/pull/5134[pull/5134]_\n_JUnit: populate sbt.testing.Event.duration._\nThank you to https://github.com/sjrd[sjrd] for working with me on this.\n\n- https://www.scala-js.org[Scala.js website]:\n* _https://github.com/scala-js/scala-js-website/pull/658[pull/658]_\n_Mention build tools other than sbt._\nThank you to https://github.com/sjrd[sjrd] for approving.\n\n- https://github.com/scala-js/scala-js-env-jsdom-nodejs/[Scala.js JSDom Node.js Environment]:\n* https://github.com/scala-js/scala-js-env-jsdom-nodejs/issues/57[issues/57]\n_Support jsdom 27.0.0+_\n\n- https://github.com/gmkumar2005/scala-js-env-playwright[Playwright for Scala.js]:\n* https://github.com/gmkumar2005/scala-js-env-playwright/issues/17[issues/17]\n_Publish for Scala 2.13._\n\n- https://scala-native.org[Scala Native]:\n* _https://github.com/scala-native/scala-native/pull/4320[pull/4320]_\n_JUnit: populate sbt.testing.Event.throwable and duration._\nThank you to https://github.com/ekrich[ekrich] for the encouragement,\nto https://github.com/LeeTibbert[LeeTibbert] for encouraging my typo fixes,\nand to https://github.com/WojciechMazur[WojciechMazur]\nfor accepting my contribution.\n* https://github.com/scala-native/scala-native/issues/4323[issues/4323]\n_Expose a way to call Build.buildCached() synchronously._\nThank you to https://github.com/WojciechMazur[WojciechMazur]\nfor pointing me towards\nhttps://github.com/com-lihaoyi/mill/blob/main/libs/scalanativelib/worker/0.5/src/mill/scalanativelib/worker/ScalaNativeWorkerImpl.scala[Mill code]\nfor Scala Native\nand for https://github.com/scala-native/scala-native/pull/4326[adding]\na method I requested.\n* _https://github.com/scala-native/scala-native/pull/4342[pull/4342]_\n_Remove spurious dependency of test-interface on junit-runtime._\nThank you to https://github.com/WojciechMazur[WojciechMazur]\nfor accepting my contribution.\n* https://github.com/scala-native/scala-native/issues/4370[issues/4370]\n_Are dependency exclusions still necessary?_\n* _https://github.com/scala-native/scala-native/pull/4371[pull/4371]_\n_Mention build tools other than sbt._\nThank you to https://github.com/WojciechMazur[WojciechMazur] for approving.\n* https://github.com/scala-native/scala-native/issues/4372[issues/4372]\n_Link errors with ZIO._\nThank you to https://github.com/WojciechMazur[WojciechMazur] for\nlooking into the issue.\n* https://github.com/scala-native/scala-native/issues/4421[issues/4421]\n_Test output is lost._\nThank you to https://github.com/WojciechMazur[WojciechMazur] for\nlooking into the issue.\n* _https://github.com/scala-native/scala-native/pull/4427[pull/4427]_\n_Remove spurious dependencies of test-runner._\nThank you to https://github.com/ekrich[ekrich]\nfor working with me on the typo fixes included in this pull request\nand to https://github.com/WojciechMazur[WojciechMazur]\nfor accepting my contribution.\n\n- https://github.com/gradle/gradle[Gradle]:\n* _https://github.com/gradle/gradle/pull/32656[pull/32656]_\nhttps://github.com/gradle/gradle/issues/32666[issues/32666]\n_Allow alternatives to ForkingTestClassProcessor._\n\n- https://github.com/JetBrains/intellij-scala[IntelliJ IDEA Scala Plugin]:\n* https://youtrack.jetbrains.com/issue/SCL-24127/Scala-Test-Inconsistencies[24127]\n_Scala Test Inconsistencies._\n* https://youtrack.jetbrains.com/issue/SCL-24128/Support-shared-sources-for-Gradle-not-just-sbt[24128]\n_Support shared sources for Gradle, not just sbt._\n\n- https://zio.dev/[ZIO]:\n* https://github.com/zio/zio/issues/9629[issues/9629]\n_zio-test: Scala.js: no test events._\nThank you to https://github.com/jdegoes[jdegoes]\nfor setting a bounty on this issue\nand to https://github.com/kyri-petrou[kyri-petrou]\nfor encouraging my approach to fix it.\n* _https://github.com/zio/zio/pull/9979[pull/9979]_\n_[test-sbt]: emit sbt.testing.Events on Scala.js and Scala Native._\nThank you to https://github.com/kyri-petrou[kyri-petrou]\nfor accepting my contribution.\n* _https://github.com/zio/zio/pull/9680[pull/9680]_\n_test-sbt: treat TestWildcardSelector correctly._\nThank you to https://github.com/kyri-petrou[kyri-petrou]\nfor accepting my contribution.\n* _https://github.com/zio/zio/pull/9756[pull/9756]_\n_test-sbt: [bug] match tests on both short and prefixed names._\nThank you to https://github.com/kyri-petrou[kyri-petrou]\nfor working with me on this\nand to https://github.com/hearnadam[hearnadam]\nfor accepting my contribution.\n* https://github.com/zio/zio/issues/10037[issues/10037]\n_[zio-test] Relocate/suppress output.json._\n* _https://github.com/zio/zio/pull/10054[pull/10054]_\n_Enable build tools to relocate \"target/test-reports-zio/output.json\"._\nThank you to https://github.com/kyri-petrou[kyri-petrou]\nfor accepting my contribution.\n* https://github.com/zio/zio/pull/10053[pull/10053]\n_Mention Gradle plugin for Scala.js and Scala Native._\nThank you to https://github.com/kyri-petrou[kyri-petrou] for approving.\n* https://github.com/zio/zio/pull/10120[pull/10120]\n_[test-sbt] More uniformity._\nThank you to https://github.com/narma[narma],\nhttps://github.com/He-Pin[He-Pin],\nand https://github.com/khajavi[khajavi]\nfor encouragement;\nto https://github.com/kyri-petrou[kyri-petrou]\nand https://github.com/hearnadam[hearnadam]\nfor reviewing and accepting my contribution.\n\n- https://scalacheck.org[ScalaCheck]:\n* https://github.com/typelevel/scalacheck/issues/1105[issues/1105]\n_sbt ScalaCheckRunner: loss of test selection fidelity._\n* _https://github.com/typelevel/scalacheck/pull/1107[pull/1107]_\n_Increase Fidelity of the sbt.testing.Framework Implementation._\nThank you to https://github.com/Duhemm[Duhemm] for blazing the trail and\nto https://github.com/satorg[satorg] for accepting my contribution.\n* _https://github.com/typelevel/scalacheck/pull/1117[pull/1117]_\n_Mention Gradle plugin for Scala.js and Scala Native._\nThank you to https://github.com/SethTisue[SethTisue] for approving\nand to https://github.com/satorg[satorg] for merging.\n\n- https://www.scalatest.org[ScalaTest]:\n* https://github.com/scalatest/scalatest/issues/2357[issues/2357]\n_sbt.testing: Run the tests from the suites nested in the explicitly selected one._\nThank you to https://github.com/cheeseng[cheeseng]\nfor helping me understand the problem\nwith running nested ScalaTest suites using my plugin.\n\n- https://www.scalatest.org/[ScalaTest website]:\n* _https://github.com/scalatest/scalatest-website/pull/253[pull/253]_\n_Mention Gradle plugin for Scala.js and Scala Native._\n\n- https://scalameta.org/munit[MUnit]:\n* _https://github.com/scalameta/munit/pull/918[pull/918]_\n_Populate sbt","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdubinsky%2Fscalajs-gradle","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdubinsky%2Fscalajs-gradle","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdubinsky%2Fscalajs-gradle/lists"}