mirror of https://github.com/sbt/sbt.git
starting to convert integration tests
This commit is contained in:
parent
286e8865cf
commit
ba8f43a23e
|
|
@ -21,8 +21,8 @@ final class EvalException(msg: String) extends RuntimeException(msg)
|
||||||
// not thread safe, since it reuses a Global instance
|
// not thread safe, since it reuses a Global instance
|
||||||
final class Eval(optionsNoncp: Seq[String], classpath: Seq[File], mkReporter: Settings => Reporter, parent: ClassLoader, backing: Option[File])
|
final class Eval(optionsNoncp: Seq[String], classpath: Seq[File], mkReporter: Settings => Reporter, parent: ClassLoader, backing: Option[File])
|
||||||
{
|
{
|
||||||
def this(mkReporter: Settings => Reporter) = this(Nil, IO.classLocationFile[ScalaObject] :: Nil, mkReporter, getClass.getClassLoader, None)
|
def this(mkReporter: Settings => Reporter, backing: Option[File]) = this(Nil, IO.classLocationFile[ScalaObject] :: Nil, mkReporter, getClass.getClassLoader, backing)
|
||||||
def this() = this(s => new ConsoleReporter(s))
|
def this() = this(s => new ConsoleReporter(s), None)
|
||||||
|
|
||||||
backing.foreach(IO.createDirectory)
|
backing.foreach(IO.createDirectory)
|
||||||
val classpathString = Path.makeString(classpath)
|
val classpathString = Path.makeString(classpath)
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ object EvalTest extends Properties("eval")
|
||||||
{
|
{
|
||||||
private[this] val reporter = new StoreReporter
|
private[this] val reporter = new StoreReporter
|
||||||
import reporter.{ERROR,Info,Severity}
|
import reporter.{ERROR,Info,Severity}
|
||||||
private[this] val eval = new Eval(_ => reporter)
|
private[this] val eval = new Eval(_ => reporter, None)
|
||||||
|
|
||||||
property("inferred integer") = forAll{ (i: Int) =>
|
property("inferred integer") = forAll{ (i: Int) =>
|
||||||
val result = eval.eval(i.toString)
|
val result = eval.eval(i.toString)
|
||||||
|
|
@ -33,8 +33,9 @@ object EvalTest extends Properties("eval")
|
||||||
}
|
}
|
||||||
|
|
||||||
property("backed local class") = forAll{ (i: Int) =>
|
property("backed local class") = forAll{ (i: Int) =>
|
||||||
IO.withTemporaryDirectory { dir =>
|
IO.withTemporaryDirectory { dir =>
|
||||||
val result = eval.eval(local(i), backing = Some(dir))
|
val eval = new Eval(_ => reporter, backing = Some(dir))
|
||||||
|
val result = eval.eval(local(i))
|
||||||
val value = result.value.asInstanceOf[{def i: Int}].i
|
val value = result.value.asInstanceOf[{def i: Int}].i
|
||||||
(label("Value", value) |: (value == i)) &&
|
(label("Value", value) |: (value == i)) &&
|
||||||
(label("Type", result.tpe) |: (result.tpe == LocalType)) &&
|
(label("Type", result.tpe) |: (result.tpe == LocalType)) &&
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
package xsbti
|
package xsbti
|
||||||
|
|
||||||
object f0
|
object g0
|
||||||
{
|
{
|
||||||
def apply[T](s: => T) = new F0[T] { def apply = s }
|
def apply[T](s: => T) = new F0[T] { def apply = s }
|
||||||
}
|
}
|
||||||
|
|
@ -40,13 +40,21 @@ final object Aggregation
|
||||||
{
|
{
|
||||||
case Implicit(false) => (Nil, false)
|
case Implicit(false) => (Nil, false)
|
||||||
case Implicit(true) => (projectAggregate(key, structure), true)
|
case Implicit(true) => (projectAggregate(key, structure), true)
|
||||||
case e: Explicit => (e.dependencies, e.transitive)
|
case e: Explicit => (subCurrentBuild(key, e.dependencies), e.transitive)
|
||||||
}
|
}
|
||||||
|
|
||||||
agg flatMap { a =>
|
agg flatMap { a =>
|
||||||
val newKey = ScopedKey(key.scope.copy(project = Select(a)), key.key)
|
val newKey = ScopedKey(key.scope.copy(project = Select(a)), key.key)
|
||||||
getTasks(newKey, structure, transitive)
|
getTasks(newKey, structure, transitive)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
private def subCurrentBuild(key: ScopedKey[_], refs: Seq[ProjectRef]): Seq[ProjectRef] =
|
||||||
|
key.scope.project match
|
||||||
|
{
|
||||||
|
case Select(ProjectRef(Some(current), _)) => refs map { ref => Scope.mapRefBuild(current, ref) }
|
||||||
|
case _ => refs
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def printSettings[T](xs: Seq[KeyValue[T]], log: Logger) =
|
def printSettings[T](xs: Seq[KeyValue[T]], log: Logger) =
|
||||||
xs match
|
xs match
|
||||||
|
|
|
||||||
|
|
@ -521,8 +521,10 @@ object Load
|
||||||
{
|
{
|
||||||
val subclassSet = subclasses.toSet
|
val subclassSet = subclasses.toSet
|
||||||
val ds = Discovery(subclassSet, Set.empty)(Test.allDefs(analysis))
|
val ds = Discovery(subclassSet, Set.empty)(Test.allDefs(analysis))
|
||||||
ds.flatMap { case (definition, Discovered(subs,_,_,true)) =>
|
ds.flatMap {
|
||||||
if((subs ** subclassSet).isEmpty) Nil else definition.name :: Nil
|
case (definition, Discovered(subs,_,_,true)) =>
|
||||||
|
if((subs ** subclassSet).isEmpty) Nil else definition.name :: Nil
|
||||||
|
case _ => Nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -320,7 +320,7 @@ object Default
|
||||||
)
|
)
|
||||||
lazy val packageConfig = Seq(
|
lazy val packageConfig = Seq(
|
||||||
JarName <<= (JarName, Config) { (n,c) => n.copy(config = c.name) },
|
JarName <<= (JarName, Config) { (n,c) => n.copy(config = c.name) },
|
||||||
PackageOptions in Package <<= MainClass in Package map ( _.map(sbt.Package.MainClass.apply).toList )
|
PackageOptions in Package <<= (PackageOptions, MainClass in Package) map { _ ++ _.map(sbt.Package.MainClass.apply).toList }
|
||||||
) ++
|
) ++
|
||||||
packageTasks(Package, "", packageBin) ++
|
packageTasks(Package, "", packageBin) ++
|
||||||
packageTasks(PackageSrc, "src", packageSrc) ++
|
packageTasks(PackageSrc, "src", packageSrc) ++
|
||||||
|
|
|
||||||
|
|
@ -103,7 +103,7 @@ object Project extends Init[Scope]
|
||||||
def mapScope(f: Scope => Scope) = new (ScopedKey ~> ScopedKey) { def apply[T](key: ScopedKey[T]) =
|
def mapScope(f: Scope => Scope) = new (ScopedKey ~> ScopedKey) { def apply[T](key: ScopedKey[T]) =
|
||||||
ScopedKey( f(key.scope), key.key)
|
ScopedKey( f(key.scope), key.key)
|
||||||
}
|
}
|
||||||
def resolveThis(thisScope: Scope) = mapScope(Scope.replaceThis(thisScope))
|
|
||||||
def transform(g: Scope => Scope, ss: Seq[Setting[_]]): Seq[Setting[_]] = {
|
def transform(g: Scope => Scope, ss: Seq[Setting[_]]): Seq[Setting[_]] = {
|
||||||
val f = mapScope(g)
|
val f = mapScope(g)
|
||||||
ss.map(_ mapKey f mapReferenced f)
|
ss.map(_ mapKey f mapReferenced f)
|
||||||
|
|
|
||||||
|
|
@ -354,15 +354,15 @@ class XSbt(info: ProjectInfo) extends ParentProject(info) with NoCrossPaths
|
||||||
override def normalizedName = "sbt"
|
override def normalizedName = "sbt"
|
||||||
override def testWithCompileClasspath = super.testWithCompileClasspath ++ Seq(scriptedSbtSub)
|
override def testWithCompileClasspath = super.testWithCompileClasspath ++ Seq(scriptedSbtSub)
|
||||||
override def testAction = super.testAction dependsOn(publishLocal)
|
override def testAction = super.testAction dependsOn(publishLocal)
|
||||||
def scriptedScalaVersions = "2.8.1.RC4"
|
def scriptedScalaVersions = "2.8.1"
|
||||||
lazy val scripted = task { args => task {
|
lazy val scripted = task { args => task {
|
||||||
val launcher = launchSub.outputJar.asFile
|
val launcher = launchSub.outputJar.asFile
|
||||||
val loader = ClasspathUtilities.toLoader(scriptedSbtSub.testClasspath, buildScalaInstance.loader)
|
val loader = ClasspathUtilities.toLoader(scriptedSbtSub.testClasspath, scriptedSbtSub.buildScalaInstance.loader)
|
||||||
val m = ModuleUtilities.getObject("sbt.test.ScriptedTests", loader)
|
val m = ModuleUtilities.getObject("sbt.test.ScriptedTests", loader)
|
||||||
val r = m.getClass.getMethod("run", classOf[File], classOf[Boolean], classOf[String], classOf[String], classOf[String], classOf[Array[String]], classOf[File])
|
val r = m.getClass.getMethod("run", classOf[File], classOf[Boolean], classOf[String], classOf[String], classOf[String], classOf[Array[String]], classOf[File])
|
||||||
try { r.invoke(m, sourcePath / "sbt-test" asFile, true: java.lang.Boolean, version.toString, buildScalaVersion, scriptedScalaVersions, args, launcher) }
|
try { r.invoke(m, sourcePath / "sbt-test" asFile, true: java.lang.Boolean, version.toString, buildScalaVersion, scriptedScalaVersions, args, launcher) }
|
||||||
catch { case e: java.lang.reflect.InvocationTargetException => throw e.getCause }
|
catch { case e: java.lang.reflect.InvocationTargetException => throw e.getCause }
|
||||||
None
|
None
|
||||||
} dependsOn(publishLocal, scriptedSbtSub.compile) }
|
} dependsOn(publishLocal, scriptedSbtSub.compile, testCompile) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +0,0 @@
|
||||||
import sbt._
|
|
||||||
|
|
||||||
class TestProject(info: ProjectInfo) extends DefaultProject(info) with Marker
|
|
||||||
{
|
|
||||||
lazy val interactiveTest = interactiveTask { mark() }
|
|
||||||
}
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
import sbt._
|
|
||||||
|
|
||||||
class TestProject(info: ProjectInfo) extends ParentProject(info) with Marker
|
|
||||||
{
|
|
||||||
val subA = project("a", "A")
|
|
||||||
lazy val interactiveTest = interactiveTask { mark() }
|
|
||||||
}
|
|
||||||
|
|
@ -1,10 +0,0 @@
|
||||||
import sbt._
|
|
||||||
|
|
||||||
class TestProject(info: ProjectInfo) extends ParentProject(info)
|
|
||||||
{
|
|
||||||
val subA = project("a", "A", new SubA(_))
|
|
||||||
class SubA(info: ProjectInfo) extends DefaultProject(info) with Marker
|
|
||||||
{
|
|
||||||
lazy val interactiveTest = interactiveTask { mark() }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,12 +0,0 @@
|
||||||
import sbt._
|
|
||||||
|
|
||||||
class TestProject(info: ProjectInfo) extends ParentProject(info) with Marker
|
|
||||||
{
|
|
||||||
val subA = project("a", "A", new SubA(_))
|
|
||||||
lazy val interactiveTest = interactiveTask { mark() }
|
|
||||||
|
|
||||||
class SubA(info: ProjectInfo) extends DefaultProject(info)
|
|
||||||
{
|
|
||||||
lazy val interactiveTest = interactiveTask { Some("Child interactive task should not be called.") }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,12 +0,0 @@
|
||||||
import sbt._
|
|
||||||
|
|
||||||
class TestProject(info: ProjectInfo) extends ParentProject(info)
|
|
||||||
{
|
|
||||||
val subA = project("a", "A", new SubA(_))
|
|
||||||
lazy val interactiveTest = task { Some("Parent task should not be called") }
|
|
||||||
|
|
||||||
class SubA(info: ProjectInfo) extends DefaultProject(info)
|
|
||||||
{
|
|
||||||
lazy val interactiveTest = interactiveTask { Some("Child task should not be called.") }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,12 +0,0 @@
|
||||||
import sbt._
|
|
||||||
|
|
||||||
class TestProject(info: ProjectInfo) extends ParentProject(info) with Marker
|
|
||||||
{
|
|
||||||
val subA = project("a", "A", new SubA(_))
|
|
||||||
lazy val interactiveTest = task { mark() }
|
|
||||||
|
|
||||||
class SubA(info: ProjectInfo) extends DefaultProject(info) with Marker
|
|
||||||
{
|
|
||||||
lazy val interactiveTest = task { mark() }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
import sbt._
|
||||||
|
import Keys._
|
||||||
|
import Project.Initialize
|
||||||
|
|
||||||
|
trait Marker
|
||||||
|
{
|
||||||
|
final lazy val Mark = TaskKey[Unit]("mark")
|
||||||
|
final def mark: Initialize[Task[Unit]] = mark(Base)
|
||||||
|
final def mark(project: ProjectRef): Initialize[Task[Unit]] = mark(Base in project)
|
||||||
|
final def mark(baseKey: ScopedSetting[File]): Initialize[Task[Unit]] = baseKey map { base =>
|
||||||
|
val toMark = base / "ran"
|
||||||
|
if(toMark.exists)
|
||||||
|
error("Already ran (" + toMark + " exists)")
|
||||||
|
else
|
||||||
|
IO touch toMark
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
import sbt._
|
||||||
|
|
||||||
|
object SingleBuild extends Build with Marker
|
||||||
|
{
|
||||||
|
lazy val projects = if(file("multi").exists) Seq(root, sub, sub2) else Seq(root)
|
||||||
|
lazy val root = Project("root", file("."), aggregate = if(file("aggregate").exists) Seq(sub) else Nil )
|
||||||
|
lazy val sub = Project("sub", file("sub"), aggregate = Seq(sub2))
|
||||||
|
lazy val sub2 = Project("sub2", file("sub") / "sub")
|
||||||
|
}
|
||||||
|
|
@ -1,4 +0,0 @@
|
||||||
#Project properties
|
|
||||||
#Fri Jan 30 20:49:57 EST 2009
|
|
||||||
project.name=Interactive Actions Test
|
|
||||||
project.version=1.0
|
|
||||||
|
|
@ -1,13 +0,0 @@
|
||||||
import sbt._
|
|
||||||
|
|
||||||
trait Marker extends NotNull
|
|
||||||
{ self: Project =>
|
|
||||||
def toMark: Path = "ran"
|
|
||||||
def mark() =
|
|
||||||
{
|
|
||||||
if(toMark.exists)
|
|
||||||
Some("Already ran")
|
|
||||||
else
|
|
||||||
FileUtilities.touch(toMark, log)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,6 +0,0 @@
|
||||||
import sbt._
|
|
||||||
|
|
||||||
class TestProject(info: ProjectInfo) extends DefaultProject(info) with Marker
|
|
||||||
{
|
|
||||||
lazy val interactiveTest = task { mark() }
|
|
||||||
}
|
|
||||||
|
|
@ -1,46 +1,102 @@
|
||||||
# This test verifies the behavior of actions declared interactive
|
# single project, 'mark' not defined
|
||||||
|
-> mark
|
||||||
|
$ absent ran
|
||||||
|
|
||||||
# Single project, non-interactive task
|
# single project, 'mark' defined
|
||||||
> interactive-test
|
> set Mark <<= mark
|
||||||
|
> mark
|
||||||
$ exists ran
|
$ exists ran
|
||||||
$ delete ran
|
$ delete ran
|
||||||
|
|
||||||
# Single project, interactive task
|
# single project, Aggregate = Enabled on Mark
|
||||||
$ copy-file changes/TestProject2.scala project/build/src/TestProject.scala
|
> set Aggregate in Mark :== false
|
||||||
> reload
|
> mark
|
||||||
> interactive-test
|
|
||||||
$ exists ran
|
$ exists ran
|
||||||
$ delete ran
|
$ delete ran
|
||||||
|
|
||||||
# Multi-project, single interactive task on parent project
|
# single project, Aggregate = Disabled on Mark
|
||||||
$ copy-file changes/TestProject3.scala project/build/src/TestProject.scala
|
> set Aggregate in Mark :== false
|
||||||
> reload
|
> mark
|
||||||
> interactive-test
|
|
||||||
$ exists ran
|
$ exists ran
|
||||||
$ delete ran
|
$ delete ran
|
||||||
|
|
||||||
# Multi-project, single interactive task on child project
|
# switch to multi-project, no aggregation yet. 'reload' will drop session settings
|
||||||
$ copy-file changes/TestProject4.scala project/build/src/TestProject.scala
|
$ touch multi
|
||||||
|
$ mkdir sub sub/sub
|
||||||
> reload
|
> reload
|
||||||
-> interactive-test
|
|
||||||
|
|
||||||
# Multi-project, two interactive tasks with same name, which is allowed because it is defined on parent
|
# define in root project only
|
||||||
$ copy-file changes/TestProject5.scala project/build/src/TestProject.scala
|
> set Mark <<= mark
|
||||||
> reload
|
> mark
|
||||||
> interactive-test
|
$ exists ran
|
||||||
$ exists "ran"
|
$ absent sub/ran
|
||||||
$ delete "ran"
|
$ delete ran
|
||||||
|
|
||||||
# Multi-project, interactive on subproject + non-interactive on parent, which cannot be run from parent
|
# define in sub project, but shouldn't run without aggregation
|
||||||
$ copy-file changes/TestProject6.scala project/build/src/TestProject.scala
|
> set Mark in sub <<= mark(sub)
|
||||||
> reload
|
> mark
|
||||||
-> interactive-test
|
$ exists ran
|
||||||
|
$ absent sub/ran
|
||||||
|
$ delete ran
|
||||||
|
|
||||||
# Multi-project, two non-interactive tasks with same name, which is allowed
|
# should be able to run it directly...
|
||||||
$ copy-file changes/TestProject7.scala project/build/src/TestProject.scala
|
> sub/mark
|
||||||
|
$ absent ran
|
||||||
|
$ exists sub/ran
|
||||||
|
$ delete sub/ran
|
||||||
|
|
||||||
|
# unset the root task. the sub task shouldn't be runnable from root
|
||||||
|
> session remove 1
|
||||||
|
-> mark
|
||||||
|
$ absent ran sub/ran
|
||||||
|
|
||||||
|
# enable aggregation. 'reload' resets session settings
|
||||||
|
$ touch aggregate
|
||||||
> reload
|
> reload
|
||||||
> interactive-test
|
|
||||||
$ exists "ran"
|
# add tasks to each subproject
|
||||||
$ exists "a/ran"
|
> set Mark in sub <<= mark(sub)
|
||||||
$ delete "ran"
|
> set Mark in sub2 <<= mark(sub2)
|
||||||
$ delete "a/ran"
|
|
||||||
|
# check that aggregation works when root project has no task
|
||||||
|
> mark
|
||||||
|
$ exists sub/ran sub/sub/ran
|
||||||
|
$ absent ran
|
||||||
|
$ delete sub/ran sub/sub/ran
|
||||||
|
|
||||||
|
# check that aggregation works on sub/
|
||||||
|
> sub/mark
|
||||||
|
$ exists sub/ran sub/sub/ran
|
||||||
|
$ absent ran
|
||||||
|
$ delete sub/ran sub/sub/ran
|
||||||
|
|
||||||
|
# add task to root project
|
||||||
|
> set Mark <<= mark
|
||||||
|
|
||||||
|
# disable aggregation for sub/mark so that sub2/mark doesn't run
|
||||||
|
> set Aggregate in (sub,Mark) :== false
|
||||||
|
> mark
|
||||||
|
$ exists ran sub/ran
|
||||||
|
$ absent sub/sub/ran
|
||||||
|
$ delete ran sub/ran
|
||||||
|
|
||||||
|
# check explicit aggregation. running on root should run root/mark and sub2/mark
|
||||||
|
> set Aggregate in Mark :== Aggregation(sub2 :: Nil)
|
||||||
|
> mark
|
||||||
|
$ exists ran sub/sub/ran
|
||||||
|
$ absent sub/ran
|
||||||
|
$ delete ran sub/sub/ran
|
||||||
|
|
||||||
|
# check intransitive aggregation. running on root should not continue to sub2/mark
|
||||||
|
> set Aggregate in Mark :== Aggregation(sub :: Nil, false)
|
||||||
|
> mark
|
||||||
|
$ exists ran sub/ran
|
||||||
|
$ absent sub/sub/ran
|
||||||
|
$ delete ran sub/ran
|
||||||
|
|
||||||
|
# the aggregation setting in a leaf shouldn't affect whether it can be run directly
|
||||||
|
> set Aggregate in (sub2, Mark) :== false
|
||||||
|
> sub2/mark
|
||||||
|
$ exists sub/sub/ran
|
||||||
|
$ absent ran sub/ran
|
||||||
|
$ delete sub/sub/ran
|
||||||
|
|
@ -39,7 +39,6 @@ final class ScriptedTests(resourceBaseDirectory: File, bufferLog: Boolean, sbtVe
|
||||||
IPC.pullServer( scriptedTest0(label, testDirectory, log) )
|
IPC.pullServer( scriptedTest0(label, testDirectory, log) )
|
||||||
private def scriptedTest0(label: String, testDirectory: File, log: Logger)(server: IPC.Server)
|
private def scriptedTest0(label: String, testDirectory: File, log: Logger)(server: IPC.Server)
|
||||||
{
|
{
|
||||||
FillProperties(testDirectory, sbtVersion, defScalaVersion, buildScalaVersions)
|
|
||||||
val buffered = new BufferedLogger(new FullLogger(log))
|
val buffered = new BufferedLogger(new FullLogger(log))
|
||||||
if(bufferLog)
|
if(bufferLog)
|
||||||
buffered.record()
|
buffered.record()
|
||||||
|
|
@ -114,25 +113,7 @@ object ScriptedTests
|
||||||
ScriptedTest(group,name)
|
ScriptedTest(group,name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
object FillProperties
|
|
||||||
{
|
|
||||||
def apply(projectDirectory: File, sbtVersion: String, defScalaVersion: String, buildScalaVersions: String): Unit =
|
|
||||||
{
|
|
||||||
import sbt.Path._
|
|
||||||
fill(projectDirectory / "project" / "build.properties", sbtVersion, defScalaVersion, buildScalaVersions)
|
|
||||||
}
|
|
||||||
def fill(properties: File, sbtVersion: String, defScalaVersion: String, buildScalaVersions: String)
|
|
||||||
{
|
|
||||||
val toAppend = extraProperties(sbtVersion, defScalaVersion, buildScalaVersions)
|
|
||||||
IO.write(properties, toAppend, Charset.forName("ISO-8859-1"), true)
|
|
||||||
}
|
|
||||||
def extraProperties(sbtVersion: String, defScalaVersion: String, buildScalaVersions: String) =
|
|
||||||
<x>
|
|
||||||
sbt.version={sbtVersion}
|
|
||||||
def.scala.version={defScalaVersion}
|
|
||||||
build.scala.versions={buildScalaVersions}
|
|
||||||
</x>.text
|
|
||||||
}
|
|
||||||
final case class ScriptedTest(group: String, name: String) extends NotNull
|
final case class ScriptedTest(group: String, name: String) extends NotNull
|
||||||
{
|
{
|
||||||
override def toString = group + "/" + name
|
override def toString = group + "/" + name
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue