Run unit tests with truffle-compiler (#8467)

This commit is contained in:
Pavel Marek 2023-12-18 18:22:16 +01:00 committed by GitHub
parent b2a1cd55d2
commit 21d164ec3e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
47 changed files with 900 additions and 472 deletions

439
build.sbt
View File

@ -12,9 +12,14 @@ import src.main.scala.licenses.{
DistributionDescription,
SBTDistributionComponent
}
import com.sandinh.javamodule.moduleinfo.JpmsModule
import sbt.librarymanagement.DependencyFilter
// This import is unnecessary, but bit adds a proper code completion features
// to IntelliJ.
import JPMSPlugin.autoImport.*
import java.io.File
import scala.collection.mutable.ListBuffer
// ============================================================================
// === Global Configuration ===================================================
@ -295,8 +300,6 @@ lazy val enso = (project in file("."))
`runtime-instrument-id-execution`,
`runtime-instrument-repl-debugger`,
`runtime-instrument-runtime-server`,
`runtime-with-instruments`,
`runtime-with-polyglot`,
`runtime-version-manager`,
`runtime-version-manager-test`,
editions,
@ -519,6 +522,8 @@ lazy val componentModulesPaths =
)
}
lazy val compileModuleInfo = taskKey[Unit]("Compiles `module-info.java`")
// ============================================================================
// === Internal Libraries =====================================================
// ============================================================================
@ -879,6 +884,7 @@ lazy val `refactoring-utils` = project
.dependsOn(testkit % Test)
lazy val `project-manager` = (project in file("lib/scala/project-manager"))
.enablePlugins(JPMSPlugin)
.settings(
(Compile / mainClass) := Some("org.enso.projectmanager.boot.ProjectManager")
)
@ -907,6 +913,8 @@ lazy val `project-manager` = (project in file("lib/scala/project-manager"))
"org.typelevel" %% "kind-projector" % kindProjectorVersion cross CrossVersion.full
)
)
/** Fat jar assembly settings
*/
.settings(
assembly / assemblyJarName := "project-manager.jar",
assembly / test := {},
@ -934,26 +942,42 @@ lazy val `project-manager` = (project in file("lib/scala/project-manager"))
case "application.conf" => MergeStrategy.concat
case "reference.conf" => MergeStrategy.concat
case _ => MergeStrategy.first
}
)
/** JPMS related settings for tests
*/
.settings(
Test / fork := true,
// These dependencies are here so that we can use them in `--module-path` later on.
libraryDependencies ++= {
val necessaryModules =
GraalVM.modules.map(_.withConfigurations(Some(Test.name))) ++
GraalVM.langsPkgs.map(_.withConfigurations(Some(Test.name)))
necessaryModules
},
Test / javaOptions ++=
Seq(
"-Dpolyglot.engine.WarnInterpreterOnly=false",
"-Dpolyglotimpl.DisableClassPathIsolation=true",
s"-Dconfig.file=${sourceDirectory.value}/test/resources/application.conf",
"-Dslf4j.provider=ch.qos.logback.classic.spi.LogbackServiceProvider"
Test / addModules := Seq(
(`runtime-fat-jar` / javaModuleName).value
),
// Append enso language on the class-path
Test / unmanagedClasspath :=
(LocalProject(
"runtime-with-instruments"
) / Compile / fullClasspath).value,
// In project-manager tests, we test installing projects and for that, we need
// to launch engine-runner properly. For that, we need all the JARs that we
// normally use in engine distribution. That is why there is dependency on
// `buildEngineDistributionNoIndex`.
(Test / test) := (Test / test)
.dependsOn(buildEngineDistributionNoIndex)
.value,
Test / modulePath := {
val updateReport = (Test / update).value
val requiredModIds =
GraalVM.modules ++ GraalVM.langsPkgs ++ logbackPkg ++ Seq(
"org.slf4j" % "slf4j-api" % slf4jVersion
)
val requiredMods = JPMSUtils.filterModulesFromUpdate(
updateReport,
requiredModIds,
streams.value.log,
shouldContainAll = true
)
val runtimeMod =
(`runtime-fat-jar` / Compile / productDirectories).value.head
requiredMods ++ Seq(runtimeMod)
},
Test / javaOptions ++= testLogProviderOptions
)
.settings(
rebuildNativeImage := NativeImage
.buildNativeImage(
"project-manager",
@ -1201,7 +1225,7 @@ lazy val `polyglot-api` = project
// Append enso language on the class-path
Test / unmanagedClasspath :=
(LocalProject(
"runtime-with-instruments"
"runtime-fat-jar"
) / Compile / fullClasspath).value,
libraryDependencies ++= Seq(
"org.graalvm.sdk" % "polyglot-tck" % graalMavenPackagesVersion % "provided",
@ -1220,6 +1244,7 @@ lazy val `polyglot-api` = project
.dependsOn(testkit % Test)
lazy val `language-server` = (project in file("engine/language-server"))
.enablePlugins(JPMSPlugin)
.settings(
commands += WithDebugCommand.withDebug,
frgaalJavaCompilerSetting,
@ -1237,7 +1262,11 @@ lazy val `language-server` = (project in file("engine/language-server"))
"org.scalatest" %% "scalatest" % scalatestVersion % Test,
"org.scalacheck" %% "scalacheck" % scalacheckVersion % Test,
"org.graalvm.sdk" % "polyglot-tck" % graalMavenPackagesVersion % "provided",
"org.eclipse.jgit" % "org.eclipse.jgit" % jgitVersion
"org.eclipse.jgit" % "org.eclipse.jgit" % jgitVersion,
"org.bouncycastle" % "bcutil-jdk18on" % "1.76" % Test,
"org.bouncycastle" % "bcpkix-jdk18on" % "1.76" % Test,
"org.bouncycastle" % "bcprov-jdk18on" % "1.76" % Test,
"org.apache.tika" % "tika-core" % tikaVersion % Test
),
Test / testOptions += Tests
.Argument(TestFrameworks.ScalaCheck, "-minSuccessfulTests", "1000"),
@ -1256,17 +1285,93 @@ lazy val `language-server` = (project in file("engine/language-server"))
)
)
.settings(
// These settings are needed by language-server tests that create a runtime context.
Test / fork := true,
Test / javaOptions ++= testLogProviderOptions ++ Seq(
"-Dpolyglot.engine.WarnInterpreterOnly=false",
"-Dpolyglotimpl.DisableClassPathIsolation=true"
// These dependencies are here so that we can use them in `--module-path` later on.
libraryDependencies ++= {
val necessaryModules =
GraalVM.modules.map(_.withConfigurations(Some(Test.name))) ++
GraalVM.langsPkgs.map(_.withConfigurations(Some(Test.name)))
necessaryModules
},
Test / addModules := Seq(
(`runtime-fat-jar` / javaModuleName).value
),
// Append enso language on the class-path
Test / unmanagedClasspath :=
Test / modulePath := {
val updateReport = (Test / update).value
// org.bouncycastle is a module required by `org.enso.runtime` module.
val requiredModIds =
GraalVM.modules ++ GraalVM.langsPkgs ++ logbackPkg ++ Seq(
"org.slf4j" % "slf4j-api" % slf4jVersion,
"org.bouncycastle" % "bcutil-jdk18on" % "1.76",
"org.bouncycastle" % "bcpkix-jdk18on" % "1.76",
"org.bouncycastle" % "bcprov-jdk18on" % "1.76"
)
val requiredMods = JPMSUtils.filterModulesFromUpdate(
updateReport,
requiredModIds,
streams.value.log,
shouldContainAll = true
)
val runtimeMod =
(`runtime-fat-jar` / Compile / productDirectories).value.head
requiredMods ++ Seq(runtimeMod)
},
Test / javaOptions ++= testLogProviderOptions,
Test / patchModules := {
/** All these modules will be in --patch-module cmdline option to java, which means that
* for the JVM, it will appear that all the classes contained in these sbt projects are contained
* in the `org.enso.runtime` module. In this way, we do not have to assembly the `runtime.jar`
* fat jar.
*/
val modulesToPatchIntoRuntime: Seq[File] =
(LocalProject(
"runtime-with-instruments"
) / Compile / fullClasspath).value,
"runtime-instrument-common"
) / Compile / productDirectories).value ++
(LocalProject(
"runtime-instrument-id-execution"
) / Compile / productDirectories).value ++
(LocalProject(
"runtime-instrument-repl-debugger"
) / Compile / productDirectories).value ++
(LocalProject(
"runtime-instrument-runtime-server"
) / Compile / productDirectories).value ++
(LocalProject(
"runtime-language-epb"
) / Compile / productDirectories).value ++
(LocalProject(
"runtime-compiler"
) / Compile / productDirectories).value ++
(LocalProject("runtime-parser") / Compile / productDirectories).value ++
(LocalProject(
"interpreter-dsl"
) / Compile / productDirectories).value ++
// We have to patch the `runtime` project as well, as it contains BuiltinTypes.metadata in
// runtime/target/classes/META-INF directory
(LocalProject("runtime") / Compile / productDirectories).value ++
(LocalProject(
"syntax-rust-definition"
) / Compile / productDirectories).value
val extraModsToPatch = JPMSUtils.filterModulesFromUpdate(
(Test / update).value,
Seq(
"org.apache.tika" % "tika-core" % tikaVersion
),
streams.value.log,
shouldContainAll = true
)
Map(
(`runtime-fat-jar` / javaModuleName).value -> (modulesToPatchIntoRuntime ++ extraModsToPatch)
)
},
Test / addReads := {
Map(
(`runtime-fat-jar` / javaModuleName).value -> Seq("ALL-UNNAMED")
)
}
)
.settings(
Test / compile := (Test / compile)
.dependsOn(LocalProject("enso") / updateLibraryManifests)
.value,
@ -1388,8 +1493,37 @@ lazy val `runtime-language-epb` =
)
)
/** `runtime-test-instruments` project contains Truffle instruments that are used solely for testing.
* It is compiled into an explicit Java module. Note that this project cannot have compile-time dependency on `runtime`
* project, so if you need access to classes from `runtime`, you need to use reflection.
*/
lazy val `runtime-test-instruments` =
(project in file("engine/runtime-test-instruments"))
.enablePlugins(JPMSPlugin)
.settings(
inConfig(Compile)(truffleRunOptionsSettings),
truffleDslSuppressWarnsSetting,
instrumentationSettings,
javaModuleName := "org.enso.runtime.test",
modulePath := {
JPMSUtils.filterModulesFromUpdate(
update.value,
GraalVM.modules ++ Seq(
"org.netbeans.api" % "org-openide-util-lookup" % netbeansApiVersion
),
streams.value.log,
shouldContainAll = true
)
},
libraryDependencies ++= GraalVM.modules,
libraryDependencies ++= Seq(
"org.netbeans.api" % "org-openide-util-lookup" % netbeansApiVersion % "provided"
)
)
lazy val runtime = (project in file("engine/runtime"))
.configs(Benchmark)
.enablePlugins(JPMSPlugin)
.settings(
frgaalJavaCompilerSetting,
truffleDslSuppressWarnsSetting,
@ -1426,7 +1560,8 @@ lazy val runtime = (project in file("engine/runtime"))
"junit" % "junit" % junitVersion % Test,
"com.github.sbt" % "junit-interface" % junitIfVersion % Test,
"org.hamcrest" % "hamcrest-all" % hamcrestVersion % Test,
"org.slf4j" % "slf4j-nop" % slf4jVersion % Benchmark
"org.slf4j" % "slf4j-nop" % slf4jVersion % Benchmark,
"org.slf4j" % "slf4j-api" % slf4jVersion % Test
),
// Add all GraalVM packages with Runtime scope - we don't need them for compilation,
// just provide them at runtime (in module-path).
@ -1438,10 +1573,87 @@ lazy val runtime = (project in file("engine/runtime"))
val tools =
GraalVM.toolsPkgs.map(_.withConfigurations(Some(Runtime.name)))
necessaryModules ++ langs ++ tools
},
Test / javaOptions ++= testLogProviderOptions ++ Seq(
"-Dpolyglotimpl.DisableClassPathIsolation=true"
}
)
.settings(
Test / unmanagedClasspath := (LocalProject(
"runtime-fat-jar"
) / Compile / exportedProducts).value,
Test / addModules := Seq(
(`runtime-test-instruments` / javaModuleName).value,
(`runtime-fat-jar` / javaModuleName).value
),
Test / modulePath := {
val updateReport = (Test / update).value
val requiredModIds =
GraalVM.modules ++ GraalVM.langsPkgs ++ GraalVM.insightPkgs ++ logbackPkg ++ Seq(
"org.slf4j" % "slf4j-api" % slf4jVersion,
"org.netbeans.api" % "org-openide-util-lookup" % netbeansApiVersion
)
val requiredMods = JPMSUtils.filterModulesFromUpdate(
updateReport,
requiredModIds,
streams.value.log,
shouldContainAll = true
)
val runtimeTestInstrumentsMod =
(`runtime-test-instruments` / Compile / exportedProducts).value.head.data
val runtimeMod =
(`runtime-fat-jar` / Compile / exportedProducts).value.head.data
requiredMods ++
Seq(runtimeTestInstrumentsMod) ++
Seq(runtimeMod)
},
Test / patchModules := {
/** All these modules will be in --patch-module cmdline option to java, which means that
* for the JVM, it will appear that all the classes contained in these sbt projects are contained
* in the `org.enso.runtime` module. In this way, we do not have to assembly the `runtime.jar`
* fat jar.
*/
val modulesToPatchIntoRuntime: Seq[File] =
(LocalProject(
"runtime-instrument-common"
) / Compile / productDirectories).value ++
(LocalProject(
"runtime-instrument-id-execution"
) / Compile / productDirectories).value ++
(LocalProject(
"runtime-instrument-repl-debugger"
) / Compile / productDirectories).value ++
(LocalProject(
"runtime-instrument-runtime-server"
) / Compile / productDirectories).value ++
(LocalProject(
"runtime-language-epb"
) / Compile / productDirectories).value ++
(LocalProject(
"runtime-compiler"
) / Compile / productDirectories).value ++
(LocalProject("refactoring-utils") / Compile / productDirectories).value
// Patch test-classes into the runtime module. This is standard way to deal with the
// split package problem in unit tests. For example, Maven's surefire plugin does this.
val testClassesDir = (Test / productDirectories).value.head
Map(
(`runtime-fat-jar` / javaModuleName).value -> (modulesToPatchIntoRuntime ++ Seq(
testClassesDir
))
)
},
Test / addReads := {
val runtimeModName = (`runtime-fat-jar` / javaModuleName).value
val testInstrumentsModName =
(`runtime-test-instruments` / javaModuleName).value
Map(
// We patched the test-classes into the runtime module. These classes access some stuff from
// unnamed module. Thus, let's add ALL-UNNAMED.
runtimeModName -> Seq("ALL-UNNAMED", testInstrumentsModName),
testInstrumentsModName -> Seq(runtimeModName)
)
},
Test / javaOptions ++= testLogProviderOptions
)
.settings(
Test / fork := true,
Test / envVars ++= distributionEnvironmentOverrides ++ Map(
"ENSO_TEST_DISABLE_IR_CACHE" -> "false"
@ -1479,7 +1691,7 @@ lazy val runtime = (project in file("engine/runtime"))
// runtime.jar fat jar needs to be assembled as it is used in the
// benchmarks. This dependency is here so that `runtime/bench` works
// after clean build.
LocalProject("runtime-with-instruments") / assembly
LocalProject("runtime-fat-jar") / assembly
)
.value,
benchOnly := Def.inputTaskDyn {
@ -1519,6 +1731,7 @@ lazy val runtime = (project in file("engine/runtime"))
.dependsOn(`connected-lock-manager`)
.dependsOn(testkit % Test)
.dependsOn(`logging-service-logback` % "test->test")
.dependsOn(`runtime-test-instruments` % "test->compile")
lazy val `runtime-parser` =
(project in file("engine/runtime-parser"))
@ -1583,7 +1796,9 @@ lazy val `runtime-instrument-common` =
)
.dependsOn(`refactoring-utils`)
.dependsOn(
runtime % "compile->compile;test->test;runtime->runtime;bench->bench"
LocalProject(
"runtime"
) % "compile->compile;test->test;runtime->runtime;bench->bench"
)
lazy val `runtime-instrument-id-execution` =
@ -1593,7 +1808,7 @@ lazy val `runtime-instrument-id-execution` =
inConfig(Compile)(truffleRunOptionsSettings),
instrumentationSettings
)
.dependsOn(runtime)
.dependsOn(LocalProject("runtime"))
.dependsOn(`runtime-instrument-common`)
lazy val `runtime-instrument-repl-debugger` =
@ -1602,7 +1817,7 @@ lazy val `runtime-instrument-repl-debugger` =
inConfig(Compile)(truffleRunOptionsSettings),
instrumentationSettings
)
.dependsOn(runtime)
.dependsOn(LocalProject("runtime"))
.dependsOn(`runtime-instrument-common`)
lazy val `runtime-instrument-runtime-server` =
@ -1611,50 +1826,21 @@ lazy val `runtime-instrument-runtime-server` =
inConfig(Compile)(truffleRunOptionsSettings),
instrumentationSettings
)
.dependsOn(runtime)
.dependsOn(LocalProject("runtime"))
.dependsOn(`runtime-instrument-common`)
lazy val `runtime-with-instruments` =
(project in file("engine/runtime-with-instruments"))
.configs(Benchmark)
/** A "meta" project that exists solely to provide logic for assembling the `runtime.jar` fat Jar.
* We do not want to put this task into any other existing project, as it internally copies some
* classes from other projects into the `classes` directory, therefore, pollutes the build.
* There is only one Java source in this project - `module-info.java`. During the assembling of the
* fat jar, all the classes from the dependent projects are copied into the `classes` directory of
* this project and then, a custom task is invoked to compile the `module-info.java`.
*/
lazy val `runtime-fat-jar` =
(project in file("engine/runtime-fat-jar"))
.enablePlugins(JPMSPlugin)
.settings(
frgaalJavaCompilerSetting,
inConfig(Compile)(truffleRunOptionsSettings),
commands += WithDebugCommand.withDebug,
Test / javaOptions ++= testLogProviderOptions ++ Seq(
"-Dpolyglotimpl.DisableClassPathIsolation=true"
),
Test / fork := true,
Test / envVars ++= distributionEnvironmentOverrides ++ Map(
"ENSO_TEST_DISABLE_IR_CACHE" -> "false"
),
libraryDependencies ++= Seq(
"org.scalatest" %% "scalatest" % scalatestVersion % Test,
"org.graalvm.truffle" % "truffle-api" % graalMavenPackagesVersion % Test,
"org.graalvm.truffle" % "truffle-dsl-processor" % graalMavenPackagesVersion % Test,
"org.slf4j" % "slf4j-nop" % slf4jVersion % Benchmark
),
// Add all GraalVM packages with Runtime scope - we don't need them for compilation,
// just provide them at runtime (in module-path).
libraryDependencies ++= {
val necessaryModules =
GraalVM.modules.map(_.withConfigurations(Some(Runtime.name)))
val langs =
GraalVM.langsPkgs.map(_.withConfigurations(Some(Runtime.name)))
necessaryModules ++ langs
},
// Note [Unmanaged Classpath]
Test / unmanagedClasspath += (baseDirectory.value / ".." / ".." / "app" / "gui" / "view" / "graph-editor" / "src" / "builtin" / "visualization" / "native" / "inc"),
// Filter module-info.java from the compilation
excludeFilter := excludeFilter.value || "module-info.java",
moduleInfos := Seq(
JpmsModule("org.enso.runtime")
)
)
/** Assembling Uber Jar */
.settings(
assembly := assembly
.dependsOn(
Compile / compileModuleInfo := {
JPMSUtils.compileModuleInfo(
copyDepsFilter = ScopeFilter(
inProjects(
@ -1669,7 +1855,35 @@ lazy val `runtime-with-instruments` =
),
modulePath = JPMSUtils.componentModules
)
}
.dependsOn(Compile / compile)
.value,
// Filter module-info.java from the compilation
excludeFilter := excludeFilter.value || "module-info.java",
javaModuleName := "org.enso.runtime",
compileOrder := CompileOrder.JavaThenScala
)
/** The following libraryDependencies are provided in Runtime scope.
* Later, we will collect them into --module-path option.
* We don't collect them in Compile scope as it does not even make sense
* to run `compile` task in this project.
*/
.settings(
libraryDependencies ++= {
val graalMods =
GraalVM.modules.map(_.withConfigurations(Some(Runtime.name)))
val langMods =
GraalVM.langsPkgs.map(_.withConfigurations(Some(Runtime.name)))
val logbackMods =
logbackPkg.map(_.withConfigurations(Some(Runtime.name)))
graalMods ++ langMods ++ logbackMods
}
)
/** Assembling Uber Jar */
.settings(
assembly := assembly
.dependsOn(Compile / compile)
.dependsOn(Compile / compileModuleInfo)
.value,
assembly / assemblyJarName := "runtime.jar",
assembly / test := {},
@ -1704,61 +1918,12 @@ lazy val `runtime-with-instruments` =
case _ => MergeStrategy.first
}
)
/** Benchmark settings */
.settings(
inConfig(Benchmark)(Defaults.testSettings),
Benchmark / javacOptions --= Seq(
"-source",
frgaalSourceLevel,
"--enable-preview"
),
(Benchmark / javaOptions) :=
(LocalProject("std-benchmarks") / Benchmark / run / javaOptions).value
)
.dependsOn(runtime % "compile->compile;test->test;runtime->runtime")
.dependsOn(`runtime-instrument-common`)
.dependsOn(`runtime-instrument-id-execution`)
.dependsOn(`runtime-instrument-repl-debugger`)
.dependsOn(`runtime-instrument-runtime-server`)
.dependsOn(`runtime-language-epb`)
.dependsOn(`logging-service-logback` % "test->test")
/* runtime-with-polyglot
* ~~~~~~~~~~~~~~~~~~~~~
* Unlike `runtime`, this project includes the truffle language JARs on the
* class-path.
*/
lazy val `runtime-with-polyglot` =
(project in file("engine/runtime-with-polyglot"))
.configs(Benchmark)
.settings(
frgaalJavaCompilerSetting,
inConfig(Compile)(truffleRunOptionsSettings),
inConfig(Benchmark)(Defaults.testSettings),
commands += WithDebugCommand.withDebug,
Benchmark / javacOptions --= Seq(
"-source",
frgaalSourceLevel,
"--enable-preview"
),
Test / javaOptions ++= testLogProviderOptions ++ Seq(
"-Dpolyglotimpl.DisableClassPathIsolation=true"
),
Test / fork := true,
Test / envVars ++= distributionEnvironmentOverrides ++ Map(
"ENSO_TEST_DISABLE_IR_CACHE" -> "false"
),
libraryDependencies ++= GraalVM.langsPkgs ++ Seq(
"org.graalvm.polyglot" % "polyglot" % graalMavenPackagesVersion % "provided",
"org.graalvm.tools" % "insight-tool" % graalMavenPackagesVersion % "provided",
"org.scalatest" %% "scalatest" % scalatestVersion % Test
),
(Benchmark / javaOptions) :=
(LocalProject("std-benchmarks") / Benchmark / run / javaOptions).value
)
.dependsOn(runtime % "compile->compile;test->test;runtime->runtime")
.dependsOn(`runtime-with-instruments`)
.dependsOn(LocalProject("runtime"))
/* Note [Unmanaged Classpath]
* ~~~~~~~~~~~~~~~~~~~~~~~~~~
@ -1833,7 +1998,7 @@ lazy val `engine-runner` = project
)
.settings(
assembly := assembly
.dependsOn(`runtime-with-instruments` / assembly)
.dependsOn(`runtime-fat-jar` / assembly)
.value,
rebuildNativeImage :=
NativeImage
@ -2016,7 +2181,7 @@ lazy val `bench-processor` = (project in file("lib/scala/bench-processor"))
),
// Append enso language on the class-path
(Test / unmanagedClasspath) :=
(LocalProject("runtime-with-instruments") / Compile / fullClasspath).value
(LocalProject("runtime-fat-jar") / Compile / fullClasspath).value
)
.dependsOn(`polyglot-api`)
.dependsOn(runtime)
@ -2055,11 +2220,11 @@ lazy val `std-benchmarks` = (project in file("std-bits/benchmarks"))
(Benchmark / parallelExecution) := false,
(Benchmark / run / fork) := true,
(Benchmark / run / connectInput) := true,
// This ensures that the full class-path of runtime-with-instruments is put on
// This ensures that the full class-path of runtime-fat-jar is put on
// class-path of the Java compiler (and thus the benchmark annotation processor).
(Benchmark / compile / unmanagedClasspath) ++=
(LocalProject(
"runtime-with-instruments"
"runtime-fat-jar"
) / Compile / fullClasspath).value,
(Benchmark / compile / javacOptions) ++= Seq(
"-s",
@ -2083,13 +2248,13 @@ lazy val `std-benchmarks` = (project in file("std-bits/benchmarks"))
.map(_.data.getAbsolutePath)
val runtimeJar =
(LocalProject(
"runtime-with-instruments"
"runtime-fat-jar"
) / assembly / assemblyOutputPath).value.getAbsolutePath
val allModulePaths = requiredModulesPaths ++ Seq(runtimeJar)
val runtimeModuleName =
(LocalProject(
"runtime-with-instruments"
) / moduleInfos).value.head.moduleName
"runtime-fat-jar"
) / javaModuleName).value
Seq(
// To enable logging in benchmarks, add ch.qos.logback module on the modulePath
"-Dslf4j.provider=org.slf4j.nop.NOPServiceProvider",

View File

@ -0,0 +1,12 @@
module org.enso.runtime.test {
requires org.graalvm.truffle;
requires org.openide.util.lookup.RELEASE180;
exports org.enso.interpreter.test.instruments;
exports org.enso.interpreter.test.instruments.service;
provides com.oracle.truffle.api.instrumentation.provider.TruffleInstrumentProvider with
org.enso.interpreter.test.instruments.CodeIdsTestInstrumentProvider,
org.enso.interpreter.test.instruments.CodeLocationsTestInstrumentProvider,
org.enso.interpreter.test.instruments.NodeCountingTestInstrumentProvider;
}

View File

@ -1,13 +1,14 @@
package org.enso.interpreter.test;
package org.enso.interpreter.test.instruments;
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.instrumentation.*;
import com.oracle.truffle.api.nodes.Node;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.UUID;
import org.enso.interpreter.node.ExpressionNode;
import org.enso.interpreter.runtime.control.TailCallException;
import org.enso.interpreter.test.instruments.service.RuntimeTestService;
import org.openide.util.Lookup;
/**
* A debug instrument used to test code locations.
@ -20,8 +21,13 @@ import org.enso.interpreter.runtime.control.TailCallException;
services = CodeIdsTestInstrument.class)
public class CodeIdsTestInstrument extends TruffleInstrument {
public static final String INSTRUMENT_ID = "ids-test";
private static final RuntimeTestService runtimeTestService;
private Env env;
static {
runtimeTestService = Lookup.getDefault().lookup(RuntimeTestService.class);
}
/**
* Initializes the instrument. Substitute for a constructor, called by the Truffle framework.
*
@ -94,7 +100,6 @@ public class CodeIdsTestInstrument extends TruffleInstrument {
/**
* Checks if the node to be executed is the node this listener was created to observe.
*
* @param context current execution context
* @param frame current execution frame
* @param result the result of executing the node
*/
@ -104,11 +109,11 @@ public class CodeIdsTestInstrument extends TruffleInstrument {
return;
}
Node node = context.getInstrumentedNode();
if (!(node instanceof ExpressionNode)) {
if (!runtimeTestService.isExpressionNode(node)) {
return;
}
nodes.put(this, result);
UUID id = ((ExpressionNode) node).getId();
UUID id = runtimeTestService.getNodeID(node);
if (id == null || !id.equals(expectedId)) {
return;
}
@ -120,19 +125,18 @@ public class CodeIdsTestInstrument extends TruffleInstrument {
/**
* Checks if the specified was called, if its execution triggered TCO.
*
* @param context current execution context.
* @param frame current execution frame.
* @param exception the exception thrown from this node's execution.
*/
@Override
public void onReturnExceptional(VirtualFrame frame, Throwable exception) {
if (!(exception instanceof TailCallException)) {
if (!runtimeTestService.isTailCallException(exception)) {
return;
}
if (!(context.getInstrumentedNode() instanceof ExpressionNode)) {
if (!runtimeTestService.isExpressionNode(context.getInstrumentedNode())) {
return;
}
UUID id = ((ExpressionNode) context.getInstrumentedNode()).getId();
UUID id = runtimeTestService.getNodeID(context.getInstrumentedNode());
if (expectedResult == null) {
successful = true;
}
@ -142,8 +146,9 @@ public class CodeIdsTestInstrument extends TruffleInstrument {
public String toString() {
var sb = new StringBuilder();
sb.append(context.getInstrumentedNode().getClass().getSimpleName());
if (context.getInstrumentedNode() instanceof ExpressionNode expr) {
sb.append("@").append(expr.getId());
if (runtimeTestService.isExpressionNode(context.getInstrumentedNode())) {
UUID id = runtimeTestService.getNodeID(context.getInstrumentedNode());
sb.append("@").append(id);
}
sb.append(" ");
sb.append(context.getInstrumentedSourceSection());

View File

@ -1,4 +1,4 @@
package org.enso.interpreter.test;
package org.enso.interpreter.test.instruments;
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.instrumentation.EventBinding;

View File

@ -1,6 +1,4 @@
package org.enso.interpreter.test;
import static org.junit.Assert.fail;
package org.enso.interpreter.test.instruments;
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.instrumentation.EventContext;
@ -9,18 +7,17 @@ import com.oracle.truffle.api.instrumentation.ExecutionEventNodeFactory;
import com.oracle.truffle.api.instrumentation.SourceSectionFilter;
import com.oracle.truffle.api.instrumentation.TruffleInstrument;
import com.oracle.truffle.api.nodes.Node;
import com.oracle.truffle.api.nodes.RootNode;
import com.oracle.truffle.api.source.SourceSection;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.function.Function;
import org.enso.interpreter.node.MethodRootNode;
import org.enso.interpreter.node.callable.FunctionCallInstrumentationNode;
import org.enso.pkg.QualifiedName;
import org.enso.interpreter.test.instruments.service.FunctionCallInfo;
import org.enso.interpreter.test.instruments.service.RuntimeTestService;
import org.openide.util.Lookup;
/** Testing instrument to control newly created nodes. */
@TruffleInstrument.Registration(
@ -28,12 +25,17 @@ import org.enso.pkg.QualifiedName;
services = NodeCountingTestInstrument.class)
public class NodeCountingTestInstrument extends TruffleInstrument {
public static final String INSTRUMENT_ID = "node-count-test";
private static final RuntimeTestService runtimeTestService;
private final Map<Node, Node> all = new ConcurrentHashMap<>();
private Map<Class, List<Node>> counter = new ConcurrentHashMap<>();
private final Map<UUID, FunctionCallInfo> calls = new ConcurrentHashMap<>();
private Env env;
static {
runtimeTestService = Lookup.getDefault().lookup(RuntimeTestService.class);
}
@Override
protected void onCreate(Env env) {
env.registerService(this);
@ -75,10 +77,10 @@ public class NodeCountingTestInstrument extends TruffleInstrument {
};
if (value < min) {
fail(dump.apply(msg + ". Minimal size should be " + min + ", but was: " + value + " in"));
throw new AssertionError(dump.apply(msg + ". Minimal size should be " + min + ", but was: " + value + " in"));
}
if (value > max) {
fail(dump.apply(msg + ". Maximal size should be " + max + ", but was: " + value + " in"));
throw new AssertionError(dump.apply(msg + ". Maximal size should be " + max + ", but was: " + value + " in"));
}
counter = new ConcurrentHashMap<>();
return prev;
@ -120,73 +122,16 @@ public class NodeCountingTestInstrument extends TruffleInstrument {
public void onReturnValue(VirtualFrame frame, Object result) {
Node node = context.getInstrumentedNode();
if (node instanceof FunctionCallInstrumentationNode instrumentableNode
&& result instanceof FunctionCallInstrumentationNode.FunctionCall functionCall) {
onFunctionReturn(instrumentableNode, functionCall);
if (runtimeTestService.isFunctionCallInstrumentationNode(node)
&& runtimeTestService.isFunctionCall(result)) {
UUID nodeId = runtimeTestService.getNodeID(node);
if (nodeId != null) {
var funcCallInfo = runtimeTestService.extractFunctionCallInfo(result);
calls.put(nodeId, funcCallInfo);
}
}
private void onFunctionReturn(
FunctionCallInstrumentationNode node, FunctionCallInstrumentationNode.FunctionCall result) {
if (node.getId() != null) {
calls.put(node.getId(), new FunctionCallInfo(result));
}
}
}
public static class FunctionCallInfo {
private final QualifiedName moduleName;
private final QualifiedName typeName;
private final String functionName;
public FunctionCallInfo(FunctionCallInstrumentationNode.FunctionCall call) {
RootNode rootNode = call.getFunction().getCallTarget().getRootNode();
if (rootNode instanceof MethodRootNode methodNode) {
moduleName = methodNode.getModuleScope().getModule().getName();
typeName = methodNode.getType().getQualifiedName();
functionName = methodNode.getMethodName();
} else {
moduleName = null;
typeName = null;
functionName = rootNode.getName();
}
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
FunctionCallInfo that = (FunctionCallInfo) o;
return Objects.equals(moduleName, that.moduleName)
&& Objects.equals(typeName, that.typeName)
&& Objects.equals(functionName, that.functionName);
}
@Override
public int hashCode() {
return Objects.hash(moduleName, typeName, functionName);
}
@Override
public String toString() {
return moduleName + "::" + typeName + "::" + functionName;
}
public QualifiedName getModuleName() {
return moduleName;
}
public QualifiedName getTypeName() {
return typeName;
}
public String getFunctionName() {
return functionName;
}
}
}

View File

@ -0,0 +1,13 @@
package org.enso.interpreter.test.instruments.service;
public record FunctionCallInfo(
String moduleName,
String typeName,
String functionName
) {
@Override
public String toString() {
return moduleName + "::" + typeName + "::" + functionName;
}
}

View File

@ -0,0 +1,20 @@
package org.enso.interpreter.test.instruments.service;
import com.oracle.truffle.api.nodes.Node;
import com.oracle.truffle.api.nodes.RootNode;
import java.util.UUID;
import org.enso.interpreter.test.instruments.service.FunctionCallInfo;
/**
* A service that provides information from the `runtime` project to the instruments in this project
* (`runtime-test-instruments`). Note that this project cannot have a compile time dependency on
* `runtime`, thus, we need to use a service provider mechanism.
*/
public interface RuntimeTestService {
UUID getNodeID(Node node);
boolean isExpressionNode(Object node);
boolean isTailCallException(Object obj);
boolean isFunctionCallInstrumentationNode(Object node);
boolean isFunctionCall(Object obj);
FunctionCallInfo extractFunctionCallInfo(Object functionCall);
}

View File

@ -1,77 +0,0 @@
package org.enso.interpreter.test.instrument;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import java.nio.file.Paths;
import java.util.logging.Level;
import org.enso.interpreter.test.MockLogHandler;
import org.enso.polyglot.MethodNames;
import org.enso.polyglot.RuntimeOptions;
import org.graalvm.polyglot.Context;
import org.graalvm.polyglot.Source;
import org.graalvm.polyglot.io.IOAccess;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
public class VerifyLanguageAvailabilityTest {
private static Context ctx;
private static MockLogHandler handler;
@BeforeClass
public static void initEnsoContext() {
handler = new MockLogHandler();
ctx =
Context.newBuilder()
.allowExperimentalOptions(true)
.allowIO(IOAccess.ALL)
.option(RuntimeOptions.PREINITIALIZE, "js")
.option(
RuntimeOptions.LANGUAGE_HOME_OVERRIDE,
Paths.get("../../distribution/component").toFile().getAbsolutePath())
.option(RuntimeOptions.LOG_LEVEL, Level.FINE.getName())
.logHandler(handler)
.allowAllAccess(true)
.build();
assertNotNull("Enso language is supported", ctx.getEngine().getLanguages().get("enso"));
var fourtyTwo =
ctx.eval("enso", "mul x y = x * y").invokeMember("eval_expression", "mul").execute(6, 7);
assertEquals(42, fourtyTwo.asInt());
}
@AfterClass
public static void closeEnsoContext() throws Exception {
ctx.close();
var args =
handler.assertMessage("epb.org.enso.interpreter.epb.EpbContext", "Parsing foreign script");
assertEquals("js", args[0]);
assertEquals("mul.mul", args[1]);
}
@Test
public void javaScriptIsPresent() throws Exception {
var js = ctx.getEngine().getLanguages().get("js");
assertNotNull("JavaScript is available", js);
var src =
Source.newBuilder(
"enso",
"""
foreign js mul a b = \"\"\"
return a * b
run = mul 6 7
""",
"mul.enso")
.build();
var fourtyTwo = ctx.eval(src).invokeMember(MethodNames.Module.EVAL_EXPRESSION, "run");
assertEquals(42, fourtyTwo.asInt());
}
@Test
public void ensoIsPresent() {
var enso = ctx.getEngine().getLanguages().get("enso");
assertNotNull("Enso is available", enso);
}
}

View File

@ -85,36 +85,6 @@ public class BigNumberTest extends TestBase {
return powers;
}
@Test
public void averageOfMixedArrayOverDouble() throws Exception {
boolean assertsOn = false;
assert assertsOn = true;
if (assertsOn) {
// skip this test when asserts are on
return;
}
var code =
"""
from Standard.Base.Data.Vector import Vector
polyglot java import org.enso.example.TestClass
powers n =
go x v b = if x > n then b.to_vector else
b.append v
@Tail_Call go x+1 v*2 b
go 1 1 Vector.new_builder
avg n = TestClass.doubleArrayAverage (powers n)
""";
var fn = evalCode(code, "avg");
var avg = fn.execute(200);
assertTrue("Got a number back " + avg, avg.isNumber());
assertFalse("It's not a long", avg.fitsInLong());
assertTrue("It's a double", avg.fitsInDouble());
assertEquals("It is big enough", Math.pow(2, 200) / 200, avg.asDouble(), 300);
}
@Test
public void averageOfMixedArrayOverNumber() throws Exception {
var code =

View File

@ -42,7 +42,6 @@ import org.graalvm.polyglot.io.IOAccess;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
public class DebuggingEnsoTest {
@ -58,7 +57,7 @@ public class DebuggingEnsoTest {
.option(
RuntimeOptions.LANGUAGE_HOME_OVERRIDE,
Paths.get("../../distribution/component").toFile().getAbsolutePath())
.option(RuntimeOptions.LOG_LEVEL, Level.FINEST.getName())
.option(RuntimeOptions.LOG_LEVEL, Level.WARNING.getName())
.logHandler(System.err)
.build();
@ -504,9 +503,7 @@ public class DebuggingEnsoTest {
bar 42 # 6
end = 0 # 7
""");
// Steps into line 2 - declaration of the method, which is fine.
// (5, 6, 7) would be better.
List<Integer> expectedLineNumbers = List.of(5, 6, 2, 7);
List<Integer> expectedLineNumbers = List.of(5, 6, 7);
Queue<SuspendedCallback> steps = createStepOverEvents(expectedLineNumbers.size());
testStepping(src, "foo", new Object[] {0}, steps, expectedLineNumbers);
}
@ -514,12 +511,7 @@ public class DebuggingEnsoTest {
/**
* Use some methods from Vector in stdlib. Stepping over methods from different modules might be
* problematic.
*
* <p>TODO[pm] This test is ignored, because the current behavior of step over is that it first
* steps into the declaration (name) of the method that is being stepped over and then steps back.
* So there would be weird line numbers from std lib.
*/
@Ignore
@Test
public void testSteppingOverUseStdLib() {
Source src =
@ -562,7 +554,7 @@ public class DebuggingEnsoTest {
bar 42 # 4
end = 0 # 5
""");
List<Integer> expectedLineNumbers = List.of(3, 4, 2, 1, 5);
List<Integer> expectedLineNumbers = List.of(3, 4, 2, 1, 2, 4, 5);
Queue<SuspendedCallback> steps =
new ArrayDeque<>(
Collections.nCopies(expectedLineNumbers.size(), (event) -> event.prepareStepInto(1)));
@ -581,7 +573,7 @@ public class DebuggingEnsoTest {
bar (baz x) # 4
end = 0 # 5
""");
List<Integer> expectedLineNumbers = List.of(3, 4, 1, 2, 5);
List<Integer> expectedLineNumbers = List.of(3, 4, 1, 4, 2, 4, 5);
Queue<SuspendedCallback> steps =
new ArrayDeque<>(
Collections.nCopies(expectedLineNumbers.size(), (event) -> event.prepareStepInto(1)));

View File

@ -47,6 +47,7 @@ public class MetaIsATest extends TestBase {
public static void disposeCtx() {
if (generator != null) {
generator.dispose();
generator = null;
}
ctx.close();
}

View File

@ -36,6 +36,7 @@ public class MetaObjectTest extends TestBase {
public static void disposeCtx() {
if (generator != null) {
generator.dispose();
generator = null;
}
ctx.close();
}

View File

@ -0,0 +1,62 @@
package org.enso.interpreter.test;
import com.oracle.truffle.api.nodes.Node;
import com.oracle.truffle.api.nodes.RootNode;
import java.util.UUID;
import org.enso.interpreter.node.ExpressionNode;
import org.enso.interpreter.node.MethodRootNode;
import org.enso.interpreter.node.callable.FunctionCallInstrumentationNode;
import org.enso.interpreter.node.callable.FunctionCallInstrumentationNode.FunctionCall;
import org.enso.interpreter.runtime.control.TailCallException;
import org.enso.interpreter.test.instruments.service.FunctionCallInfo;
import org.enso.interpreter.test.instruments.service.RuntimeTestService;
import org.openide.util.lookup.ServiceProvider;
@ServiceProvider(service = RuntimeTestService.class)
public class RuntimeTestServiceImpl implements RuntimeTestService {
@Override
public UUID getNodeID(Node node) {
if (node instanceof ExpressionNode exprNode) {
return exprNode.getId();
} else if (node instanceof FunctionCallInstrumentationNode funcNode) {
return funcNode.getId();
}
return null;
}
@Override
public boolean isExpressionNode(Object node) {
return node instanceof ExpressionNode;
}
@Override
public boolean isTailCallException(Object obj) {
return obj instanceof TailCallException;
}
@Override
public boolean isFunctionCallInstrumentationNode(Object node) {
return node instanceof FunctionCallInstrumentationNode;
}
@Override
public boolean isFunctionCall(Object obj) {
return obj instanceof FunctionCall;
}
@Override
public FunctionCallInfo extractFunctionCallInfo(Object functionCallObj) {
if (functionCallObj instanceof FunctionCall functionCall) {
RootNode rootNode = functionCall.getFunction().getCallTarget().getRootNode();
if (rootNode instanceof MethodRootNode methodNode) {
String moduleName = methodNode.getModuleScope().getModule().getName().toString();
String typeName = methodNode.getType().getQualifiedName().toString();
String functionName = methodNode.getMethodName();
return new FunctionCallInfo(moduleName, typeName, functionName);
} else {
return new FunctionCallInfo(null, null, rootNode.getName());
}
}
return null;
}
}

View File

@ -16,7 +16,7 @@ import java.util.logging.Level;
import org.enso.interpreter.node.ClosureRootNode;
import org.enso.interpreter.runtime.tag.AvoidIdInstrumentationTag;
import org.enso.interpreter.runtime.tag.IdentifiedTag;
import org.enso.interpreter.test.NodeCountingTestInstrument;
import org.enso.interpreter.test.instruments.NodeCountingTestInstrument;
import org.enso.polyglot.RuntimeOptions;
import org.graalvm.polyglot.Context;
import org.graalvm.polyglot.Language;

View File

@ -14,8 +14,8 @@ import org.enso.compiler.core.ir.Literal;
import org.enso.interpreter.node.expression.literal.LiteralNode;
import org.enso.interpreter.runtime.type.ConstantsGen;
import org.enso.interpreter.test.Metadata;
import org.enso.interpreter.test.NodeCountingTestInstrument;
import org.enso.interpreter.test.instrument.RuntimeServerTest.TestContext;
import org.enso.interpreter.test.instruments.NodeCountingTestInstrument;
import org.enso.polyglot.runtime.Runtime$Api$CreateContextRequest;
import org.enso.polyglot.runtime.Runtime$Api$CreateContextResponse;
import org.enso.polyglot.runtime.Runtime$Api$EditFileNotification;
@ -35,6 +35,7 @@ import org.enso.polyglot.runtime.Runtime$Api$StackItem$ExplicitCall;
import org.enso.polyglot.runtime.Runtime$Api$StackItem$LocalCall;
import org.enso.text.editing.model;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import scala.Option;
@ -77,7 +78,7 @@ public class IncrementalUpdatesTest {
var m = context.languageContext().findModule(MODULE_NAME).orElse(null);
assertNotNull("Module found", m);
var numbers = m.getIr().preorder().filter((v1) -> v1 instanceof Literal.Number);
assertEquals("One number found: " + numbers, 1, numbers.size());
Assert.assertEquals("One number found: " + numbers, 1, numbers.size());
if (numbers.head() instanceof Literal.Number n) {
assertEquals("updated to 5", "5", n.value());
}
@ -100,10 +101,10 @@ public class IncrementalUpdatesTest {
sendUpdatesWhenFunctionBodyIsChangedBySettingValue(
"4", ConstantsGen.INTEGER, "4", "1000", "1000", LiteralNode.class);
sendExpressionValue("1000", "333");
assertEquals(List.newBuilder().addOne("333"), context.consumeOut());
Assert.assertEquals(List.newBuilder().addOne("333"), context.consumeOut());
nodeCountingInstrument.assertNewNodes("No execution on 333, no nodes yet", 0, 0);
sendExpressionValue("333", "22");
assertEquals(List.newBuilder().addOne("22"), context.consumeOut());
Assert.assertEquals(List.newBuilder().addOne("22"), context.consumeOut());
nodeCountingInstrument.assertNewNodes("No execution on 22, no nodes yet", 0, 0);
}
@ -127,7 +128,7 @@ public class IncrementalUpdatesTest {
assertTrue(
"Execution succeeds: " + result,
result.head().payload() instanceof Runtime$Api$ExecutionComplete);
assertEquals(
Assert.assertEquals(
"Error is printed as a result",
List.newBuilder().addOne("(Error: Uninitialized value)"),
context.consumeOut());
@ -273,7 +274,7 @@ public class IncrementalUpdatesTest {
Vector$.MODULE$.empty())),
TestMessages.update(contextId, mainRes, ConstantsGen.NOTHING),
context.executionComplete(contextId));
assertEquals(List.newBuilder().addOne(originalOutput), context.consumeOut());
Assert.assertEquals(List.newBuilder().addOne(originalOutput), context.consumeOut());
var allNodesAfterException =
nodeCountingInstrument.assertNewNodes("Execution creates some nodes", 20, 35);
@ -290,7 +291,7 @@ public class IncrementalUpdatesTest {
TestMessages.update(contextId, fooX, exprType),
TestMessages.update(contextId, fooRes, exprType),
context.executionComplete(contextId));
assertEquals(List.newBuilder().addOne(originalOutput), context.consumeOut());
Assert.assertEquals(List.newBuilder().addOne(originalOutput), context.consumeOut());
nodeCountingInstrument.assertNewNodes("No new nodes created", 0, 0);
var literalNode = findLiteralNode(truffleNodeType, allNodesAfterException);
@ -302,7 +303,7 @@ public class IncrementalUpdatesTest {
var executionCompleteEvents = sendEdit.apply(originalText, newText);
if (executionOutput != null) {
assertSameElements(executionCompleteEvents, context.executionComplete(contextId));
assertEquals(List.newBuilder().addOne(executionOutput), context.consumeOut());
Assert.assertEquals(List.newBuilder().addOne(executionOutput), context.consumeOut());
nodeCountingInstrument.assertNewNodes("No new nodes created", 0, 0);
assertEquals(

View File

@ -1,6 +1,7 @@
package org.enso.interpreter.test.instrument;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import com.oracle.truffle.api.instrumentation.SourceSectionFilter;
import com.oracle.truffle.api.instrumentation.StandardTags;
@ -10,7 +11,7 @@ import java.util.logging.Level;
import org.enso.interpreter.runtime.tag.AvoidIdInstrumentationTag;
import org.enso.interpreter.runtime.tag.IdentifiedTag;
import org.enso.interpreter.test.Metadata;
import org.enso.interpreter.test.NodeCountingTestInstrument;
import org.enso.interpreter.test.instruments.NodeCountingTestInstrument;
import org.enso.polyglot.RuntimeOptions;
import org.graalvm.polyglot.Context;
import org.graalvm.polyglot.Language;
@ -92,9 +93,9 @@ public class WarningInstrumentationTest {
var calls = instrument.registeredCalls();
assertEquals(calls.keySet().size(), 3);
assertEquals(calls.get(idOp1).getFunctionName(), "new");
assertEquals(calls.get(idOp2).getFunctionName(), "attach");
assertEquals(calls.get(idOp3).getTypeName().item(), "Table");
assertEquals(calls.get(idOp3).getFunctionName(), "get");
assertEquals(calls.get(idOp1).functionName(), "new");
assertEquals(calls.get(idOp2).functionName(), "attach");
assertTrue(calls.get(idOp3).typeName().contains("Table"));
assertEquals(calls.get(idOp3).functionName(), "get");
}
}

View File

@ -1,8 +1,12 @@
package org.enso.interpreter.test
import com.oracle.truffle.api.instrumentation.EventBinding
import org.enso.interpreter.test.CodeIdsTestInstrument.IdEventListener
import org.enso.interpreter.test.CodeLocationsTestInstrument.LocationsEventListener
import org.enso.interpreter.test.instruments.CodeIdsTestInstrument.IdEventListener
import org.enso.interpreter.test.instruments.{
CodeIdsTestInstrument,
CodeLocationsTestInstrument
}
import org.enso.interpreter.test.instruments.CodeLocationsTestInstrument.LocationsEventListener
import org.enso.polyglot.debugger.{
DebugServerInfo,
DebuggerSessionManagerEndpoint,

View File

@ -2,9 +2,9 @@ package org.enso.interpreter.test.instrument
import org.enso.polyglot.{LanguageInfo, RuntimeOptions}
import org.graalvm.polyglot.{Context, PolyglotException}
import org.scalatest.{BeforeAndAfterEach, Suite}
import org.scalatest.matchers.should.Matchers
import org.scalatest.wordspec.AnyWordSpec
import org.scalatest.{BeforeAndAfterEach, Suite}
import java.nio.file.Paths
import java.util.logging.Level
@ -44,7 +44,6 @@ class RuntimeProjectContextTest
.toFile
.getAbsolutePath
)
.option("engine.WarnInterpreterOnly", "false")
.option(RuntimeOptions.EDITION_OVERRIDE, "0.0.0-dev")
.option(RuntimeOptions.LOG_LEVEL, Level.WARNING.getName)
.logHandler(System.err)

View File

@ -5,9 +5,9 @@ import org.enso.interpreter.runtime.`type`.ConstantsGen
import org.enso.interpreter.test.Metadata
import org.enso.polyglot._
import org.enso.polyglot.runtime.Runtime.Api
import org.enso.text.{ContentBasedVersioning, Sha3_224VersionCalculator}
import org.enso.text.editing.model
import org.enso.text.editing.model.TextEdit
import org.enso.text.{ContentBasedVersioning, Sha3_224VersionCalculator}
import org.graalvm.polyglot.Context
import org.scalatest.BeforeAndAfterEach
import org.scalatest.flatspec.AnyFlatSpec

View File

@ -1,7 +1,8 @@
package org.enso.interpreter.test.instrument
import org.enso.interpreter.runtime.`type`.{Constants, ConstantsGen, Types}
import org.apache.commons.io.output.TeeOutputStream
import org.enso.interpreter.runtime.EnsoContext
import org.enso.interpreter.runtime.`type`.{Constants, ConstantsGen, Types}
import org.enso.interpreter.test.Metadata
import org.enso.polyglot._
import org.enso.polyglot.data.TypeGraph
@ -16,7 +17,6 @@ import org.scalatest.matchers.should.Matchers
import java.io.{ByteArrayOutputStream, File}
import java.nio.file.{Files, Paths}
import java.util.UUID
import org.apache.commons.io.output.TeeOutputStream
@scala.annotation.nowarn("msg=multiarg infix syntax")
class RuntimeServerTest

View File

@ -83,7 +83,11 @@ object GraalVM {
"org.graalvm.tools" % "dap-tool" % version
)
val toolsPkgs = chromeInspectorPkgs ++ debugAdapterProtocolPkgs
val insightPkgs = Seq(
"org.graalvm.tools" % "insight-tool" % version
)
val toolsPkgs = chromeInspectorPkgs ++ debugAdapterProtocolPkgs ++ insightPkgs
val langsPkgs = jsPkgs ++ pythonPkgs

182
project/JPMSPlugin.scala Normal file
View File

@ -0,0 +1,182 @@
import sbt.*
import sbt.Keys.*
import sbt.internal.inc.{CompileOutput, PlainVirtualFile}
import sbt.util.CacheStore
import sbtassembly.Assembly.{Dependency, JarEntry, Project}
import sbtassembly.{CustomMergeStrategy, MergeStrategy}
import xsbti.compile.IncToolOptionsUtil
import java.io.File
/** An automatic plugin that handles everything related to JPMS modules. One needs to explicitly
* enable this plugin in a project with `.enablePlugins(JPMSPlugin)`. The keys and tasks provided by this plugin
* corresponds to the module-related options of `javac` and `java` commands.
*
* This plugin injects all the module-specific options to `javaOptions`, based on
* the settings of this plugin.
*
* If this plugin is enabled, and no settings/tasks from this plugin are used, then the plugin will
* not inject anything into `javaOptions` or `javacOptions`.
*/
object JPMSPlugin extends AutoPlugin {
object autoImport {
val javaModuleName =
settingKey[String]("The name of the Java (JPMS) module")
val addModules = settingKey[Seq[String]](
"Module names that will be added to --add-modules option"
)
val modulePath = taskKey[Seq[File]](
"Directories (Jar archives or expanded Jar archives) that will be put into " +
"--module-path option"
)
val patchModules = taskKey[Map[String, Seq[File]]](
"""
|A map of module names to directories (Jar archives or expanded Jar archives) that will be
|put into --patch-module option.
|""".stripMargin
)
val addExports = taskKey[Map[String, Seq[String]]](
"""
|A map of module names to packages that will be put into --add-exports option.
|The format of `--add-exports` option is `module/package=target-module(,target-module)*`
|The key in the map is `module/package` and the value is a sequence of target modules
|""".stripMargin
)
val addReads = taskKey[Map[String, Seq[String]]](
"""
|A map of module names to modules that will be put into --add-reads option.
|When a module A reads a module B, it means that it "depends" on it - it has the same
|effect as if module A would have `requires B` in its module-info.java file.
|""".stripMargin
)
val compileModuleInfo = taskKey[Unit]("Compile module-info.java")
val modulePathTestOptions_ = taskKey[Seq[String]](
"Assembles options for the JVM for running tests with all the required modules. " +
"Including truffle-compiler and org.enso.runtime modules and all their dependencies."
)
}
import autoImport.*
override lazy val projectSettings: Seq[Setting[_]] = Seq(
addModules := Seq.empty,
modulePath := Seq.empty,
patchModules := Map.empty,
addExports := Map.empty,
addReads := Map.empty,
compileModuleInfo := {},
// javacOptions only inject --module-path and --add-modules, not the rest of the
// options.
Compile / javacOptions ++= {
constructOptions(
streams.value.log,
modulePath = (Compile / modulePath).value,
addModules = (Compile / addModules).value
)
},
Compile / javaOptions ++= {
constructOptions(
streams.value.log,
(Compile / modulePath).value,
(Compile / addModules).value,
(Compile / patchModules).value,
(Compile / addExports).value,
(Compile / addReads).value
)
},
Test / javacOptions ++= {
constructOptions(
streams.value.log,
modulePath = (Test / modulePath).value,
addModules = (Test / addModules).value
)
},
Test / javaOptions ++= {
constructOptions(
streams.value.log,
(Test / modulePath).value,
(Test / addModules).value,
(Test / patchModules).value,
(Test / addExports).value,
(Test / addReads).value
)
}
)
def constructOptions(
log: Logger,
modulePath: Seq[File],
addModules: Seq[String] = Seq.empty,
patchModules: Map[String, Seq[File]] = Map.empty,
addExports: Map[String, Seq[String]] = Map.empty,
addReads: Map[String, Seq[String]] = Map.empty
): Seq[String] = {
val patchOpts: Seq[String] = patchModules.flatMap {
case (moduleName, dirsToPatch) =>
ensureDirectoriesExist(dirsToPatch, log)
val patchStr = dirsToPatch
.map(_.getAbsolutePath)
.mkString(File.pathSeparator)
Seq(
"--patch-module",
s"$moduleName=$patchStr"
)
}.toSeq
ensureDirectoriesExist(modulePath, log)
val addExportsOpts: Seq[String] = addExports.flatMap {
case (modPkgName, targetModules) =>
if (!modPkgName.contains("/")) {
log.error(s"JPMSPlugin: Invalid module/package name: $modPkgName")
}
Seq(
"--add-exports",
modPkgName + "=" + targetModules.mkString(",")
)
}.toSeq
val modulePathOpts = if (modulePath.isEmpty) {
Seq.empty
} else {
Seq(
"--module-path",
modulePath.map(_.getAbsolutePath).mkString(File.pathSeparator)
)
}
val addModsOpts = if (addModules.isEmpty) {
Seq.empty
} else {
Seq(
"--add-modules",
addModules.mkString(",")
)
}
val addReadsOpts = addReads.flatMap { case (modName, targetModules) =>
Seq(
"--add-reads",
modName + "=" + targetModules.mkString(",")
)
}.toSeq
modulePathOpts ++ addModsOpts ++ patchOpts ++ addExportsOpts ++ addReadsOpts
}
/** Java does not mandate that the directories specified in the module path or
* in --patch-module exist, but it is usefull to report at least warnings.
* @param dirs
* @param log
*/
private def ensureDirectoriesExist(
dirs: Seq[File],
log: Logger
): Unit = {
dirs.foreach { dir =>
if (!dir.exists()) {
log.warn(s"JPMSPlugin: Directory $dir does not exist.")
}
}
}
}

View File

@ -1,21 +1,22 @@
import JPMSPlugin.autoImport.javaModuleName
import sbt.*
import sbt.Keys.*
import sbtassembly.Assembly.{Dependency, JarEntry, Library, Project}
import sbtassembly.MergeStrategy
import java.io.{File, FilenameFilter}
import sbtassembly.CustomMergeStrategy
import xsbti.compile.IncToolOptionsUtil
import sbt.internal.inc.{CompileOutput, PlainVirtualFile}
import sbt.util.CacheStore
import sbtassembly.Assembly.{Dependency, JarEntry, Project}
import sbtassembly.{CustomMergeStrategy, MergeStrategy}
import xsbti.compile.IncToolOptionsUtil
import java.io.File
import java.nio.file.attribute.BasicFileAttributes
import java.nio.file.{
FileVisitOption,
FileVisitResult,
FileVisitor,
Files,
Path,
SimpleFileVisitor
}
import scala.collection.mutable
/** Collection of utility methods dealing with JPMS modules.
* The motivation comes from the update of GraalVM to
@ -40,6 +41,7 @@ object JPMSUtils {
)
/** Filters modules by their IDs from the given classpath.
*
* @param cp The classpath to filter
* @param modules These modules are looked for in the class path, can be duplicated.
* @param shouldContainAll If true, the method will throw an exception if not all modules were found
@ -76,6 +78,43 @@ object JPMSUtils {
ret
}
/** Filters all the requested modules from the given [[UpdateReport]].
*
* @param updateReport The update report to filter. This is the result of `update.value`.
* @param modules The modules to filter from the update report. Can be duplicated.
* @param log The logger to use for logging.
* @param shouldContainAll If true, the method will log an error if not all modules were found.
* @return The list of files (Jar archives, directories, etc.) that were found in the update report.
*/
def filterModulesFromUpdate(
updateReport: UpdateReport,
modules: Seq[ModuleID],
log: sbt.util.Logger,
shouldContainAll: Boolean = false
): Seq[File] = {
val distinctModules = modules.distinct
def shouldFilterModule(module: ModuleID): Boolean = {
distinctModules.exists(m =>
m.organization == module.organization &&
m.name == module.name &&
m.revision == module.revision
)
}
val foundFiles = updateReport.select(
module = shouldFilterModule
)
if (shouldContainAll) {
if (foundFiles.size < distinctModules.size) {
log.error("Not all modules from update were found")
log.error(s"Returned (${foundFiles.size}): $foundFiles")
log.error(s"Expected: (${distinctModules.size}): $distinctModules")
}
}
foundFiles
}
def filterTruffleAndGraalArtifacts(
classPath: Def.Classpath
): Def.Classpath = {
@ -130,17 +169,23 @@ object JPMSUtils {
* Note that sbt is not able to correctly handle `module-info.java` files when
* compilation order is defined to mixed order.
*
* Compilation of `module-info.java` is skipped iff none of all the classes from all the dependencies
* changed and if the `module-info.java` itself have not changed.
*
* @param copyDepsFilter The filter of scopes of the projects from which the class files are first
* copied into the `target` directory before `module-info.java` is compiled.
* @param modulePath IDs of dependencies that should be put on the module path. The modules
* put into `modulePath` are filtered away from class-path, so that module-path
* and class-path passed to the `javac` are exclusive.
* @param modulePathExtra More directories that should be added on `--module-path`. This parameter is of
* type [[File]], because this is how inter project dependencies are gathered.
*
* @see https://users.scala-lang.org/t/scala-jdk-11-and-jpms/6102/19
*/
def compileModuleInfo(
copyDepsFilter: ScopeFilter,
modulePath: Seq[ModuleID] = Seq()
modulePath: Seq[ModuleID] = Seq(),
modulePathExtra: Seq[File] = Seq()
): Def.Initialize[Task[Unit]] =
Def
.task {
@ -153,47 +198,47 @@ object JPMSUtils {
// copied to.
val outputPath: Path = output.getSingleOutputAsPath
.get()
// Class directories of all the dependencies.
val sourceProducts =
productDirectories.all(copyDepsFilter).value.flatten
/** Copy classes into the target directory from all the dependencies */
log.debug(s"Copying classes to $output")
val sourceProducts = products.all(copyDepsFilter).value.flatten
if (!(outputPath.toFile.exists())) {
Files.createDirectory(outputPath)
}
val outputLangProvider =
outputPath / "META-INF" / "services" / "com.oracle.truffle.api.provider.TruffleLanguageProvider"
sourceProducts.foreach { sourceProduct =>
log.debug(s"Copying ${sourceProduct} to ${output}")
val sourceLangProvider =
sourceProduct / "META-INF" / "services" / "com.oracle.truffle.api.provider.TruffleLanguageProvider"
if (
outputLangProvider.toFile.exists() && sourceLangProvider.exists()
) {
log.debug(
s"Merging ${sourceLangProvider} into ${outputLangProvider}"
val moduleName = javaModuleName.value
val cacheStore = streams.value.cacheStoreFactory
val repoRootDir = (LocalProject("enso") / baseDirectory).value
var someDepChanged = false
sourceProducts.foreach(sourceProduct => {
if (!sourceProduct.exists()) {
log.error(s"Source product ${sourceProduct} does not exist")
log.error(
"This means that the Compile/compile task was probably not run in " +
"the corresponding project"
)
val sourceLines = IO.readLines(sourceLangProvider)
val destLines = IO.readLines(outputLangProvider.toFile)
val outLines = (sourceLines ++ destLines).distinct
IO.writeLines(outputLangProvider.toFile, outLines)
log.error("Run Compile/compile before this task")
}
// Copy the rest of the directory - don't override META-INF.
IO.copyDirectory(
sourceProduct,
outputPath.toFile,
CopyOptions(
overwrite = false,
preserveLastModified = true,
preserveExecutable = true
)
)
val relPath = repoRootDir.toPath.relativize(sourceProduct.toPath)
val cache = cacheStore.make("cache-" + relPath.toString)
val depChanged = copyClasses(sourceProduct, output, cache, log)
if (depChanged) {
someDepChanged = true
}
})
log.info("Compiling module-info.java with javac")
val baseJavacOpts = (Compile / javacOptions).value
val fullCp = (Compile / fullClasspath).value
val javaCompiler =
(Compile / compile / compilers).value.javaTools.javac()
// Skip module-info.java compilation if the source have not changed
// Force the compilation if some class file from one of the dependencies changed,
// just to be sure that we don't cause any weird compilation errors.
val moduleInfoCache = cacheStore.make("cache-module-info-" + moduleName)
Tracked.diffInputs(moduleInfoCache, FileInfo.lastModified)(
Set(moduleInfo)
) { changeReport =>
if (
someDepChanged || changeReport.modified.nonEmpty || changeReport.added.nonEmpty
) {
log.info(s"Compiling $moduleInfo with javac")
val (mp, cp) = fullCp.partition(file => {
val moduleID =
file.metadata.get(AttributeKey[ModuleID]("moduleID")).get
@ -203,17 +248,18 @@ object JPMSUtils {
mod.revision == moduleID.revision
})
})
val allDirsOnMp = mp.map(_.data) ++ modulePathExtra
val allOpts = baseJavacOpts ++ Seq(
"--class-path",
cp.map(_.data.getAbsolutePath).mkString(File.pathSeparator),
"--module-path",
mp.map(_.data.getAbsolutePath).mkString(File.pathSeparator),
allDirsOnMp.map(_.getAbsolutePath).mkString(File.pathSeparator),
"-d",
outputPath.toAbsolutePath().toString()
outputPath.toAbsolutePath.toString
)
val javaCompiler =
(Compile / compile / compilers).value.javaTools.javac()
log.debug(s"javac options: $allOpts")
val succ = javaCompiler.run(
Array(PlainVirtualFile(moduleInfo.toPath)),
allOpts.toArray,
@ -223,9 +269,82 @@ object JPMSUtils {
log
)
if (!succ) {
sys.error(s"Compilation of ${moduleInfo} failed")
log.error(s"Compilation of ${moduleInfo} failed")
}
}
}
}
.dependsOn(Compile / compile)
/** Copies all classes from all the dependencies `classes` directories into the target directory.
* @param sourceClassesDir Directory from where the classes will be copied.
* @param output Target directory where all the classes from all the dependencies
* will be copied to.
* @param log
* @return True iff some of the dependencies changed, i.e., if there is a modified class file, or
* some class file was added, or removed
*/
private def copyClasses(
sourceClassesDir: File,
output: xsbti.compile.Output,
cache: CacheStore,
log: Logger
): Boolean = {
require(sourceClassesDir.isDirectory)
val outputPath: Path = output.getSingleOutputAsPath.get()
val outputDir = outputPath.toFile
val filesToCopy = mutable.HashSet.empty[File]
val fileVisitor = new SimpleFileVisitor[Path] {
override def visitFile(
path: Path,
attrs: BasicFileAttributes
): FileVisitResult = {
if (!path.toFile.isDirectory) {
filesToCopy.add(path.toFile)
}
FileVisitResult.CONTINUE
}
override def preVisitDirectory(
dir: Path,
attrs: BasicFileAttributes
): FileVisitResult = {
// We do not care about files in META-INF directory. Everything should be described
// in the `module-info.java`.
if (dir.getFileName.toString == "META-INF") {
FileVisitResult.SKIP_SUBTREE
} else {
FileVisitResult.CONTINUE
}
}
}
Files.walkFileTree(sourceClassesDir.toPath, fileVisitor)
if (!outputDir.exists()) {
IO.createDirectory(outputDir)
}
var someDependencyChanged = false
Tracked.diffInputs(cache, FileInfo.lastModified)(filesToCopy.toSet) {
changeReport =>
for (f <- changeReport.removed) {
val relPath = sourceClassesDir.toPath.relativize(f.toPath)
val dest = outputDir.toPath.resolve(relPath)
IO.delete(dest.toFile)
someDependencyChanged = true
}
for (f <- changeReport.modified -- changeReport.removed) {
val relPath = sourceClassesDir.toPath.relativize(f.toPath)
val dest = outputDir.toPath.resolve(relPath)
IO.copyFile(f, dest.toFile)
someDependencyChanged = true
}
for (f <- changeReport.unmodified) {
val relPath = sourceClassesDir.toPath.relativize(f.toPath)
val dest = outputDir.toPath.resolve(relPath)
if (!dest.toFile.exists()) {
IO.copyFile(f, dest.toFile)
someDependencyChanged = true
}
}
}
someDependencyChanged
}
}

View File

@ -61,11 +61,13 @@ object WithDebugCommand {
val (debugFlags, prefixedRunArgs) = args.span(_ != argSeparator)
val runArgs = " " + prefixedRunArgs.drop(1).mkString(" ")
val taskKey =
if (debugFlags.contains(benchOnlyCommandName)) BenchTasks.benchOnly
else if (debugFlags.contains(runCommandName)) Compile / Keys.run
else if (debugFlags.contains(testOnlyCommandName)) Test / Keys.testOnly
else throw new IllegalArgumentException("Invalid command name.")
val taskKeyOpt =
if (debugFlags.contains(benchOnlyCommandName))
Some(BenchTasks.benchOnly)
else if (debugFlags.contains(runCommandName)) Some(Compile / Keys.run)
else if (debugFlags.contains(testOnlyCommandName))
Some(Test / Keys.testOnly)
else None
val dumpGraphsOpts =
if (debugFlags.contains(dumpGraphsOption)) truffleDumpGraphsOptions
@ -90,6 +92,13 @@ object WithDebugCommand {
debuggerOpts
).flatten
taskKeyOpt match {
case None =>
state.log.error(
s"Invalid command name. Expected one of $benchOnlyCommandName, $runCommandName, or $testOnlyCommandName"
)
state.fail
case Some(taskKey) =>
val extracted = Project.extract(state)
val withJavaOpts = extracted.appendWithoutSession(
Seq(Compile / Keys.javaOptions ++= javaOpts),
@ -100,4 +109,5 @@ object WithDebugCommand {
.runInputTask(taskKey, runArgs, withJavaOpts)
state
}
}
}

View File

@ -1,4 +1,3 @@
addSbtPlugin("com.sandinh" % "sbt-java-module-info" % "0.4.0")
addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "2.1.3")
addSbtPlugin("ch.epfl.scala" % "sbt-bloop" % "1.5.6")
addSbtPlugin("com.github.sbt" % "sbt-license-report" % "1.5.0")