mirror of
https://github.com/digital-asset/daml.git
synced 2024-09-19 16:57:40 +03:00
LF: check dependencies are not compiled to newer version. (#7050)
This PR prevents a module to depend on an older version of LF than the one is compile to. CHANGELOG_BEGIN CHANGELOG_END
This commit is contained in:
parent
8bb4cccdd8
commit
a39b7cc003
@ -124,8 +124,11 @@ final class ConcurrentCompiledPackages extends MutableCompiledPackages {
|
||||
|
||||
// update the packageMaxLanguageVersions
|
||||
// If the package is empty, no update
|
||||
computePackageLanguageVersion(_packagesLanguageVersions, pkg)
|
||||
.foreach(_packagesLanguageVersions.update(pkgId, _))
|
||||
pkg.modules.values.headOption
|
||||
.foreach(
|
||||
mod =>
|
||||
// all modules of a package are compiled to the same LF version
|
||||
_packagesLanguageVersions.update(pkgId, mod.languageVersion))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,183 +0,0 @@
|
||||
// Copyright (c) 2020 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package com.daml.lf
|
||||
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
|
||||
import com.daml.lf.data.Ref
|
||||
import com.daml.lf.data.Ref.PackageId
|
||||
import com.daml.lf.engine.ConcurrentCompiledPackages
|
||||
import com.daml.lf.language.Ast.Package
|
||||
import com.daml.lf.language.{Ast, LanguageVersion, Util => AstUtil}
|
||||
import org.scalatest.prop.TableDrivenPropertyChecks
|
||||
import org.scalatest.{Matchers, WordSpec}
|
||||
|
||||
final class CompiledPackageSpec extends WordSpec with Matchers with TableDrivenPropertyChecks {
|
||||
|
||||
val Seq(v1_6, v1_7, v1_8) =
|
||||
Seq("6", "7", "8").map(minor =>
|
||||
LanguageVersion(LanguageVersion.Major.V1, LanguageVersion.Minor.Stable(minor)))
|
||||
|
||||
private[this] final class ModBuilder(languageVersion: LanguageVersion) {
|
||||
|
||||
import ModBuilder._
|
||||
|
||||
val name = Ref.ModuleName.assertFromString(s"module${counter.incrementAndGet()}")
|
||||
|
||||
private[this] var _deps = Set.empty[PackageId]
|
||||
private[this] var _body: Ast.Expr = AstUtil.EUnit
|
||||
private[this] var _built = false
|
||||
|
||||
def addDeps(deps: (Ref.PackageId, Ref.ModuleName)): this.type = {
|
||||
assert(!_built)
|
||||
val (pkgId, modName) = deps
|
||||
val id = Ref.Identifier(pkgId, Ref.QualifiedName(modName, noOpName))
|
||||
_deps = _deps + id.packageId
|
||||
_body = Ast.ELet(Ast.Binding(None, AstUtil.TUnit, Ast.EVal(id)), _body)
|
||||
this
|
||||
}
|
||||
|
||||
def build(): (Ast.Module, Set[PackageId]) = {
|
||||
_built = true
|
||||
Ast.Module(
|
||||
name,
|
||||
Map(noOpName -> Ast.DValue(AstUtil.TUnit, false, _body, false)),
|
||||
languageVersion,
|
||||
Ast.FeatureFlags.default
|
||||
) -> _deps
|
||||
}
|
||||
}
|
||||
|
||||
private[this] object ModBuilder {
|
||||
private val counter = new AtomicInteger()
|
||||
|
||||
private def noOpName = Ref.DottedName.assertFromString("noOp")
|
||||
|
||||
def apply(
|
||||
languageVersion: LanguageVersion,
|
||||
deps: (Ref.PackageId, Ref.ModuleName)*,
|
||||
): ModBuilder =
|
||||
deps.foldLeft(new ModBuilder(languageVersion))(_.addDeps(_))
|
||||
}
|
||||
|
||||
private[this] final class PkgBuilder {
|
||||
|
||||
import PkgBuilder._
|
||||
|
||||
val id = Ref.PackageId.assertFromString(s"package${counter.incrementAndGet()}")
|
||||
|
||||
private[this] var _modules = List.empty[Ast.Module]
|
||||
private[this] var _deps = Set.empty[PackageId]
|
||||
private[this] var _built = false
|
||||
|
||||
def addMod(modBuilder: ModBuilder): this.type = {
|
||||
assert(!_built)
|
||||
val (mod, deps) = modBuilder.build()
|
||||
_modules = mod :: _modules
|
||||
_deps = _deps | deps
|
||||
this
|
||||
}
|
||||
|
||||
def build: Package = {
|
||||
_built = true
|
||||
Package(_modules, _deps, None)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private[this] object PkgBuilder {
|
||||
private val counter = new AtomicInteger()
|
||||
|
||||
def apply(modBuilders: ModBuilder*): PkgBuilder =
|
||||
modBuilders.foldLeft(new PkgBuilder())(_.addMod(_))
|
||||
}
|
||||
|
||||
tests("PureCompiledPackages")(PureCompiledPackages(_).toOption.get)
|
||||
|
||||
tests("ConcurrentCompiledPackages") { packages =>
|
||||
val compiledPackages = ConcurrentCompiledPackages()
|
||||
packages.foreach {
|
||||
case (pkgId, pkg) =>
|
||||
compiledPackages
|
||||
.addPackage(pkgId, pkg)
|
||||
.consume(
|
||||
_ => sys.error("unexpected error"),
|
||||
packages.get,
|
||||
_ => sys.error("unexpected error"),
|
||||
)
|
||||
.toOption
|
||||
.get
|
||||
}
|
||||
compiledPackages
|
||||
}
|
||||
|
||||
def tests(name: String)(_compile: Map[Ref.PackageId, Ast.Package] => CompiledPackages) =
|
||||
s"$name#getMaxLanguageVersione" should {
|
||||
|
||||
def compile(packages: PkgBuilder*) =
|
||||
_compile(packages.map(b => b.id -> b.build).toMap)
|
||||
|
||||
"return None for empty Package" in {
|
||||
val pkg1 = PkgBuilder()
|
||||
val compiledPackages = compile(pkg1)
|
||||
|
||||
compiledPackages.packageLanguageVersion.lift(pkg1.id) shouldBe None
|
||||
}
|
||||
|
||||
"return None for non defined Package" in {
|
||||
val compiledPackages = compile()
|
||||
compiledPackages.packageLanguageVersion.lift(PkgBuilder().id) shouldBe None
|
||||
}
|
||||
|
||||
"return the newest language version of package with unique modules" in {
|
||||
val pkg = PkgBuilder(ModBuilder(v1_6))
|
||||
val compiledPackages = compile(pkg)
|
||||
compiledPackages.packageLanguageVersion.lift(pkg.id) shouldBe Some(v1_6)
|
||||
}
|
||||
|
||||
"return the newest language version of package with several modules" in {
|
||||
val Seq(mod6, mod7, mod8) = Seq(v1_6, v1_7, v1_8).map(ModBuilder(_))
|
||||
|
||||
Seq(mod6, mod7).permutations.foreach { mods =>
|
||||
val pkg = PkgBuilder(mods: _*)
|
||||
compile(pkg).packageLanguageVersion.lift(pkg.id) shouldBe Some(v1_7)
|
||||
}
|
||||
Seq(mod6, mod7, mod8).permutations.foreach { mods =>
|
||||
val pkg = PkgBuilder(mods: _*)
|
||||
compile(pkg).packageLanguageVersion.lift(pkg.id) shouldBe Some(v1_8)
|
||||
}
|
||||
}
|
||||
|
||||
"return the newest language version of package with several package" in {
|
||||
val Seq(mod6, mod7, mod8) = Seq(v1_6, v1_7, v1_8).map(ModBuilder(_))
|
||||
val Seq(pkg6, pkg7, pkg8) = Seq(mod6, mod7, mod8).map(PkgBuilder(_))
|
||||
val Seq(dep6, dep7, dep8) =
|
||||
Seq(pkg6.id -> mod6.name, pkg7.id -> mod7.name, pkg8.id -> mod8.name)
|
||||
|
||||
val testCases = Table(
|
||||
("pkgVersion", "dependencies", "output"),
|
||||
(v1_6, Seq(dep6), v1_6),
|
||||
(v1_6, Seq(dep7), v1_7),
|
||||
(v1_6, Seq(dep8), v1_8),
|
||||
(v1_6, Seq(dep6, dep7), v1_7),
|
||||
(v1_6, Seq(dep7, dep8), v1_8),
|
||||
(v1_6, Seq(dep6, dep7, dep8), v1_8),
|
||||
(v1_7, Seq(dep6), v1_7),
|
||||
(v1_7, Seq(dep8), v1_8),
|
||||
(v1_7, Seq(dep7, dep8), v1_8),
|
||||
(v1_7, Seq(dep6, dep7, dep8), v1_8),
|
||||
(v1_8, Seq(dep6), v1_8),
|
||||
(v1_8, Seq(dep7), v1_8),
|
||||
(v1_8, Seq(dep6, dep7), v1_8),
|
||||
)
|
||||
|
||||
testCases.forEvery {
|
||||
case (pkgVersion, dependencies, output) =>
|
||||
val pkg = PkgBuilder(ModBuilder(pkgVersion, dependencies: _*))
|
||||
compile(pkg, pkg6, pkg7, pkg8).packageLanguageVersion.lift(pkg.id) shouldBe Some(output)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -27,19 +27,6 @@ abstract class CompiledPackages {
|
||||
def profilingMode: Compiler.ProfilingMode
|
||||
|
||||
def compiler: Compiler = Compiler(packages, stackTraceMode, profilingMode)
|
||||
|
||||
// computes the newest language version used in `pkg` and all its dependencies.
|
||||
// assumes that `maxVersionOfDependencies` is defined for all dependencies of `pkg`
|
||||
// returns None iff the package is empty.
|
||||
protected def computePackageLanguageVersion(
|
||||
dependenciesPackageVersion: PartialFunction[PackageId, LanguageVersion],
|
||||
pkg: Package,
|
||||
): Option[LanguageVersion] = {
|
||||
import transaction.VersionTimeline.maxVersion
|
||||
val moduleVersions = pkg.modules.values.iterator.map(_.languageVersion)
|
||||
val dependencyVersions = pkg.directDeps.iterator.map(dependenciesPackageVersion)
|
||||
(moduleVersions ++ dependencyVersions).reduceOption(maxVersion[LanguageVersion])
|
||||
}
|
||||
}
|
||||
|
||||
final class PureCompiledPackages private (
|
||||
@ -54,20 +41,12 @@ final class PureCompiledPackages private (
|
||||
override def stackTraceMode = stacktracing
|
||||
override def profilingMode = profiling
|
||||
|
||||
private[this] def sortedPkgIds: List[PackageId] = {
|
||||
val dependencyGraph = packageIds.view
|
||||
.flatMap(pkgId => getPackage(pkgId).map(pkg => pkgId -> pkg.directDeps).toList)
|
||||
.toMap
|
||||
language.Graphs
|
||||
.topoSort(dependencyGraph)
|
||||
.getOrElse(throw new IllegalArgumentException("cyclic package definitions"))
|
||||
}
|
||||
|
||||
override val packageLanguageVersion: Map[PackageId, LanguageVersion] =
|
||||
sortedPkgIds.foldLeft(Map.empty[PackageId, LanguageVersion])(
|
||||
(acc, pkgId) =>
|
||||
computePackageLanguageVersion(acc, packages(pkgId)).fold(acc)(acc.updated(pkgId, _))
|
||||
)
|
||||
packages.foldLeft(Map.empty[PackageId, LanguageVersion]) {
|
||||
case (acc, (pkgId, pkg)) =>
|
||||
// all modules of a package are compiled to the same LF version
|
||||
pkg.modules.values.headOption.fold(acc)(mod => acc.updated(pkgId, mod.languageVersion))
|
||||
}
|
||||
}
|
||||
|
||||
object PureCompiledPackages {
|
||||
|
@ -1635,6 +1635,8 @@ Then, a collection of packages ``Ξ`` is well-formed if:
|
||||
package of ``Ξ``.
|
||||
* There are no cycles between type synonym definitions, modules, and
|
||||
packages references.
|
||||
* Each package ``p`` only depends on packages whose LF version is older
|
||||
than or the same as the LF version of ``p`` itself.
|
||||
|
||||
|
||||
Operational semantics
|
||||
|
@ -20,6 +20,7 @@ da_scala_library(
|
||||
deps = [
|
||||
"//daml-lf/data",
|
||||
"//daml-lf/language",
|
||||
"//daml-lf/transaction",
|
||||
"@maven//:org_scalaz_scalaz_core_2_12",
|
||||
],
|
||||
)
|
||||
@ -34,5 +35,6 @@ da_scala_test(
|
||||
"//daml-lf/data",
|
||||
"//daml-lf/language",
|
||||
"//daml-lf/parser",
|
||||
"@maven//:org_scalaz_scalaz_core_2_12",
|
||||
],
|
||||
)
|
||||
|
@ -10,8 +10,8 @@ import com.daml.lf.validation.Util._
|
||||
|
||||
private[validation] object Collision {
|
||||
|
||||
def checkPackage(pkgId: PackageId, modules: Traversable[(ModuleName, Ast.Module)]): Unit = {
|
||||
val entitiesMap = namedEntitiesFromPkg(modules).groupBy(_.fullyResolvedName)
|
||||
def checkPackage(pkgId: PackageId, pkg: Ast.Package): Unit = {
|
||||
val entitiesMap = namedEntitiesFromPkg(pkg.modules).groupBy(_.fullyResolvedName)
|
||||
entitiesMap.values.foreach(cs => checkCollisions(pkgId, cs.toList))
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,33 @@
|
||||
// Copyright (c) 2020 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package com.daml.lf.validation
|
||||
|
||||
import com.daml.lf.data.Ref._
|
||||
import com.daml.lf.language.Ast._
|
||||
import com.daml.lf.transaction.VersionTimeline
|
||||
|
||||
private[validation] object DependencyVersion {
|
||||
|
||||
@throws[ValidationError]
|
||||
def checkPackage(world: World, pkgId: PackageId, pkg: Package): Unit = {
|
||||
import VersionTimeline.Implicits._
|
||||
|
||||
for {
|
||||
pkgFirstModule <- pkg.modules.values.take(1)
|
||||
// all modules of a package are compiled to the same LF version
|
||||
pkgLangVersion = pkgFirstModule.languageVersion
|
||||
depPkgId <- pkg.directDeps
|
||||
depPkg = world.lookupPackage(NoContext, depPkgId)
|
||||
depFirstModule <- depPkg.modules.values.take(1)
|
||||
depLangVersion = depFirstModule.languageVersion
|
||||
} if (pkgLangVersion precedes depLangVersion)
|
||||
throw EModuleVersionDependencies(
|
||||
pkgId,
|
||||
pkgLangVersion,
|
||||
depPkgId,
|
||||
depLangVersion
|
||||
)
|
||||
}
|
||||
|
||||
}
|
@ -13,14 +13,14 @@ private[validation] object Recursion {
|
||||
/* Check there are no cycles in the module references */
|
||||
|
||||
@throws[ValidationError]
|
||||
def checkPackage(pkgId: PackageId, modules: Map[ModuleName, Module]): Unit = {
|
||||
val g = modules.map {
|
||||
def checkPackage(pkgId: PackageId, pkg: Package): Unit = {
|
||||
val g = pkg.modules.map {
|
||||
case (name, mod) => name -> (mod.definitions.values.flatMap(modRefs(pkgId, _)).toSet - name)
|
||||
}
|
||||
|
||||
Graphs.topoSort(g).left.foreach(cycle => throw EImportCycle(NoContext, cycle.vertices))
|
||||
|
||||
modules.foreach { case (modName, mod) => checkModule(pkgId, modName, mod) }
|
||||
pkg.modules.foreach { case (modName, mod) => checkModule(pkgId, modName, mod) }
|
||||
}
|
||||
|
||||
def modRefs(pkgId: PackageId, definition: Definition): Set[ModuleName] = {
|
||||
|
@ -21,17 +21,18 @@ object Validation {
|
||||
): Either[ValidationError, Unit] =
|
||||
runSafely {
|
||||
val world = new World(pkgs)
|
||||
unsafeCheckPackage(world, pkgId, world.lookupPackage(NoContext, pkgId).modules)
|
||||
unsafeCheckPackage(world, pkgId, world.lookupPackage(NoContext, pkgId))
|
||||
}
|
||||
|
||||
private def unsafeCheckPackage(
|
||||
world: World,
|
||||
pkgId: PackageId,
|
||||
modules: Map[ModuleName, Module]
|
||||
pkg: Package
|
||||
): Unit = {
|
||||
Collision.checkPackage(pkgId, modules)
|
||||
Recursion.checkPackage(pkgId, modules)
|
||||
modules.values.foreach(unsafeCheckModule(world, pkgId, _))
|
||||
Collision.checkPackage(pkgId, pkg)
|
||||
Recursion.checkPackage(pkgId, pkg)
|
||||
DependencyVersion.checkPackage(world, pkgId, pkg)
|
||||
pkg.modules.values.foreach(unsafeCheckModule(world, pkgId, _))
|
||||
}
|
||||
|
||||
def checkModule(
|
||||
|
@ -6,6 +6,7 @@ package com.daml.lf.validation
|
||||
import com.daml.lf.data.ImmArray
|
||||
import com.daml.lf.data.Ref._
|
||||
import com.daml.lf.language.Ast._
|
||||
import com.daml.lf.language.LanguageVersion
|
||||
|
||||
sealed abstract class LookupError extends Product with Serializable {
|
||||
def pretty: String
|
||||
@ -367,3 +368,20 @@ final case class ECollision(
|
||||
override protected def prettyInternal: String =
|
||||
s"collision between ${entity1.pretty} and ${entity2.pretty}"
|
||||
}
|
||||
|
||||
final case class EModuleVersionDependencies(
|
||||
pkgId: PackageId,
|
||||
pkgLangVersion: LanguageVersion,
|
||||
depPkgId: PackageId,
|
||||
dependencyLangVersion: LanguageVersion,
|
||||
) extends ValidationError {
|
||||
import com.daml.lf.transaction.VersionTimeline.Implicits._
|
||||
|
||||
assert(pkgId != depPkgId)
|
||||
assert(pkgLangVersion precedes dependencyLangVersion)
|
||||
|
||||
override protected def prettyInternal: String =
|
||||
s"package $pkgId using version $pkgLangVersion depends on package $depPkgId using newer version $dependencyLangVersion"
|
||||
|
||||
override def context: Context = NoContext
|
||||
}
|
||||
|
@ -11,7 +11,7 @@ import org.scalatest.{Matchers, WordSpec}
|
||||
class CollisionSpec extends WordSpec with Matchers {
|
||||
|
||||
def check(pkg: Package): Unit =
|
||||
Collision.checkPackage(defaultPackageId, pkg.modules)
|
||||
Collision.checkPackage(defaultPackageId, pkg)
|
||||
|
||||
"Collision validation" should {
|
||||
|
||||
|
@ -0,0 +1,85 @@
|
||||
// Copyright (c) 2020 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package com.daml.lf.validation
|
||||
|
||||
import com.daml.lf.data.Ref.{DottedName, Identifier, PackageId, QualifiedName}
|
||||
import com.daml.lf.language.Ast._
|
||||
import com.daml.lf.language.Util._
|
||||
import com.daml.lf.language.{LanguageVersion => LV}
|
||||
import org.scalatest.prop.TableDrivenPropertyChecks
|
||||
import org.scalatest.{Matchers, WordSpec}
|
||||
|
||||
class DependencyVersionSpec extends WordSpec with TableDrivenPropertyChecks with Matchers {
|
||||
|
||||
private[this] val v1_6 = LV(LV.Major.V1, LV.Minor.Stable("6"))
|
||||
private[this] val v1_7 = LV(LV.Major.V1, LV.Minor.Stable("7"))
|
||||
private[this] val v1_8 = LV(LV.Major.V1, LV.Minor.Stable("8"))
|
||||
private[this] val A = (PackageId.assertFromString("-pkg1-"), DottedName.assertFromString("A"))
|
||||
private[this] val B = (PackageId.assertFromString("-pkg2-"), DottedName.assertFromString("B"))
|
||||
private[this] val E = (PackageId.assertFromString("-pkg3-"), DottedName.assertFromString("E"))
|
||||
private[this] val u = DottedName.assertFromString("u")
|
||||
|
||||
"Dependency validation should detect cycles between modules" in {
|
||||
|
||||
def pkg(
|
||||
ref: (PackageId, DottedName),
|
||||
langVersion: LV,
|
||||
depRefs: (PackageId, DottedName)*,
|
||||
) = {
|
||||
val (pkgId, modName) = ref
|
||||
|
||||
val mod = Module(
|
||||
modName,
|
||||
(
|
||||
(u -> DValue(TUnit, true, EUnit, false)) +:
|
||||
depRefs.map {
|
||||
case (depPkgId, depModName) =>
|
||||
depModName -> DValue(
|
||||
TUnit,
|
||||
true,
|
||||
EVal(Identifier(depPkgId, QualifiedName(depModName, u))),
|
||||
false)
|
||||
}
|
||||
),
|
||||
langVersion,
|
||||
FeatureFlags.default
|
||||
)
|
||||
|
||||
pkgId -> Package(Map(modName -> mod), depRefs.iterator.map(_._1).toSet - pkgId, None)
|
||||
}
|
||||
|
||||
val negativeTestCases = Table(
|
||||
"valid packages",
|
||||
Map(pkg(A, v1_8, A, B, E), pkg(B, v1_7, B, E), pkg(E, v1_6, E)),
|
||||
Map(pkg(A, v1_8, A, B, E), pkg(B, v1_8, B, E), pkg(E, v1_6, E)),
|
||||
)
|
||||
|
||||
val postiveTestCase = Table(
|
||||
("invalid module", "packages"),
|
||||
A -> Map(pkg(A, v1_6, A, B, E), pkg(B, v1_7, B, E), pkg(E, v1_6, E)),
|
||||
A -> Map(pkg(A, v1_7, A, B, E), pkg(B, v1_7, B, E), pkg(E, v1_8, E)),
|
||||
B -> Map(pkg(A, v1_8, A, B, E), pkg(B, v1_6, B, E), pkg(E, v1_7, E)),
|
||||
)
|
||||
|
||||
forEvery(negativeTestCases) { pkgs =>
|
||||
pkgs.foreach {
|
||||
case (pkgId, pkg) =>
|
||||
DependencyVersion.checkPackage(new World(pkgs), pkgId, pkg)
|
||||
}
|
||||
}
|
||||
|
||||
forEvery(postiveTestCase) {
|
||||
case ((pkgdId, _), pkgs) =>
|
||||
val world = new World(pkgs)
|
||||
an[EModuleVersionDependencies] should be thrownBy
|
||||
DependencyVersion.checkPackage(
|
||||
world,
|
||||
pkgdId,
|
||||
world.lookupPackage(NoContext, pkgdId),
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -22,7 +22,7 @@ class RecursionSpec extends WordSpec with TableDrivenPropertyChecks with Matcher
|
||||
}
|
||||
"""
|
||||
|
||||
Recursion.checkPackage(defaultPackageId, p.modules)
|
||||
Recursion.checkPackage(defaultPackageId, p)
|
||||
|
||||
}
|
||||
|
||||
@ -61,11 +61,9 @@ class RecursionSpec extends WordSpec with TableDrivenPropertyChecks with Matcher
|
||||
${module("E", "E")}
|
||||
"""
|
||||
|
||||
Recursion.checkPackage(defaultPackageId, negativeCase.modules)
|
||||
an[EImportCycle] should be thrownBy
|
||||
Recursion.checkPackage(defaultPackageId, positiveCase1.modules)
|
||||
an[EImportCycle] should be thrownBy
|
||||
Recursion.checkPackage(defaultPackageId, positiveCase2.modules)
|
||||
Recursion.checkPackage(defaultPackageId, negativeCase)
|
||||
an[EImportCycle] should be thrownBy Recursion.checkPackage(defaultPackageId, positiveCase1)
|
||||
an[EImportCycle] should be thrownBy Recursion.checkPackage(defaultPackageId, positiveCase2)
|
||||
|
||||
}
|
||||
|
||||
@ -98,11 +96,9 @@ class RecursionSpec extends WordSpec with TableDrivenPropertyChecks with Matcher
|
||||
}
|
||||
"""
|
||||
|
||||
Recursion.checkPackage(defaultPackageId, negativeCase.modules)
|
||||
an[ETypeSynCycle] should be thrownBy
|
||||
Recursion.checkPackage(defaultPackageId, positiveCase1.modules)
|
||||
an[ETypeSynCycle] should be thrownBy
|
||||
Recursion.checkPackage(defaultPackageId, positiveCase2.modules)
|
||||
Recursion.checkPackage(defaultPackageId, negativeCase)
|
||||
an[ETypeSynCycle] should be thrownBy Recursion.checkPackage(defaultPackageId, positiveCase1)
|
||||
an[ETypeSynCycle] should be thrownBy Recursion.checkPackage(defaultPackageId, positiveCase2)
|
||||
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user