mirror of
https://github.com/apple/pkl.git
synced 2026-05-27 00:59:16 +02:00
Report error on circular local dependencies (#731)
If a stack overflow is found during project evaluation, present any circular imports found in the dependency graph.
This commit is contained in:
@@ -21,9 +21,11 @@ import java.util.regex.Pattern
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.assertj.core.api.Assertions.assertThatCode
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.assertThrows
|
||||
import org.junit.jupiter.api.io.TempDir
|
||||
import org.pkl.commons.test.FileTestUtils
|
||||
import org.pkl.commons.test.PackageServer
|
||||
import org.pkl.commons.toPath
|
||||
import org.pkl.commons.writeString
|
||||
import org.pkl.core.*
|
||||
import org.pkl.core.evaluatorSettings.PklEvaluatorSettings
|
||||
@@ -188,4 +190,58 @@ class ProjectTest {
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `fails if project has cyclical dependencies`() {
|
||||
val projectPath = javaClass.getResource("projectCycle1/PklProject")!!.toURI().toPath()
|
||||
val e = assertThrows<PklException> { Project.loadFromPath(projectPath) }
|
||||
val cleanMsg = e.message!!.replace(Regex("file:///.*/resources/test"), "file://")
|
||||
assertThat(cleanMsg)
|
||||
.isEqualTo(
|
||||
"""
|
||||
–– Pkl Error ––
|
||||
Local project dependencies cannot be circular.
|
||||
|
||||
Cycle:
|
||||
┌─>
|
||||
│ file:///org/pkl/core/project/projectCycle2/PklProject
|
||||
│
|
||||
│ file:///org/pkl/core/project/projectCycle3/PklProject
|
||||
└─
|
||||
"""
|
||||
.trimIndent()
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `fails if a project has cyclical dependencies -- multiple cycles found`() {
|
||||
val projectPath = javaClass.getResource("projectCycle4/PklProject")!!.toURI().toPath()
|
||||
val e = assertThrows<PklException> { Project.loadFromPath(projectPath) }
|
||||
val cleanMsg = e.message!!.replace(Regex("file://.*/resources/test"), "file://")
|
||||
assertThat(cleanMsg)
|
||||
.isEqualTo(
|
||||
"""
|
||||
–– Pkl Error ––
|
||||
Local project dependencies cannot be circular.
|
||||
|
||||
The following circular imports were found.
|
||||
Not all of them are necessarily problematic.
|
||||
The problematic cycles are those declared as local dependencies.
|
||||
|
||||
Cycle 1:
|
||||
┌─>
|
||||
│ file:///org/pkl/core/project/projectCycle2/PklProject
|
||||
│
|
||||
│ file:///org/pkl/core/project/projectCycle3/PklProject
|
||||
└─
|
||||
|
||||
Cycle 2:
|
||||
┌─>
|
||||
│ file:///org/pkl/core/project/projectCycle4/PklProject
|
||||
└─
|
||||
|
||||
"""
|
||||
.trimIndent()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
/*
|
||||
* Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.pkl.core.util
|
||||
|
||||
import java.net.URI
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.pkl.core.ImportGraph
|
||||
|
||||
class ImportGraphUtilsTest {
|
||||
@Test
|
||||
fun basic() {
|
||||
val barUri = URI("file:///bar.pkl")
|
||||
val fooUri = URI("file:///foo.pkl")
|
||||
val graph =
|
||||
ImportGraph(
|
||||
mapOf(
|
||||
fooUri to setOf(ImportGraph.Import(barUri)),
|
||||
barUri to setOf(ImportGraph.Import(fooUri))
|
||||
),
|
||||
// resolved URIs is not important
|
||||
mapOf()
|
||||
)
|
||||
val cycles = ImportGraphUtils.findImportCycles(graph)
|
||||
assertThat(cycles).isEqualTo(listOf(listOf(fooUri, barUri)))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `two cycles`() {
|
||||
val barUri = URI("file:///bar.pkl")
|
||||
val fooUri = URI("file:///foo.pkl")
|
||||
val bizUri = URI("file:///biz.pkl")
|
||||
val quxUri = URI("file:///qux.pkl")
|
||||
val graph =
|
||||
ImportGraph(
|
||||
mapOf(
|
||||
fooUri to setOf(ImportGraph.Import(barUri)),
|
||||
barUri to setOf(ImportGraph.Import(fooUri)),
|
||||
bizUri to setOf(ImportGraph.Import(quxUri)),
|
||||
quxUri to setOf(ImportGraph.Import(bizUri))
|
||||
),
|
||||
// resolved URIs is not important
|
||||
mapOf()
|
||||
)
|
||||
val cycles = ImportGraphUtils.findImportCycles(graph)
|
||||
assertThat(cycles).isEqualTo(listOf(listOf(fooUri, barUri), listOf(bizUri, quxUri)))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `no cycles`() {
|
||||
val barUri = URI("file:///bar.pkl")
|
||||
val fooUri = URI("file:///foo.pkl")
|
||||
val bizUri = URI("file:///biz.pkl")
|
||||
val quxUri = URI("file:///qux.pkl")
|
||||
val graph =
|
||||
ImportGraph(
|
||||
mapOf(
|
||||
barUri to setOf(ImportGraph.Import(fooUri)),
|
||||
fooUri to setOf(ImportGraph.Import(bizUri)),
|
||||
bizUri to setOf(ImportGraph.Import(quxUri)),
|
||||
quxUri to setOf()
|
||||
),
|
||||
// resolved URIs is not important
|
||||
mapOf()
|
||||
)
|
||||
val cycles = ImportGraphUtils.findImportCycles(graph)
|
||||
assertThat(cycles).isEmpty()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `self-import`() {
|
||||
val fooUri = URI("file:///foo.pkl")
|
||||
val graph =
|
||||
ImportGraph(
|
||||
mapOf(fooUri to setOf(ImportGraph.Import(fooUri))),
|
||||
// resolved URIs is not important
|
||||
mapOf()
|
||||
)
|
||||
val cycles = ImportGraphUtils.findImportCycles(graph)
|
||||
assertThat(cycles).isEqualTo(listOf(listOf(fooUri)))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
amends "pkl:Project"
|
||||
|
||||
package {
|
||||
name = "projectCycle1"
|
||||
version = "1.0.0"
|
||||
packageZipUrl = "https://bogus.value"
|
||||
baseUri = "package://localhost:0/projectCycle1"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
["projectCycle2"] = import("../projectCycle2/PklProject")
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
amends "pkl:Project"
|
||||
|
||||
package {
|
||||
name = "projectCycle2"
|
||||
version = "1.0.0"
|
||||
packageZipUrl = "https://bogus.value"
|
||||
baseUri = "package://localhost:0/projectCycle2"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
["projectCycle3"] = import("../projectCycle3/PklProject")
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
amends "pkl:Project"
|
||||
|
||||
package {
|
||||
name = "projectCycle3"
|
||||
version = "1.0.0"
|
||||
packageZipUrl = "https://bogus.value"
|
||||
baseUri = "package://localhost:0/projectCycle3"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
["projectCycle2"] = import("../projectCycle2/PklProject")
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
amends "pkl:Project"
|
||||
|
||||
import "PklProject"
|
||||
|
||||
dependencies {
|
||||
["projectCycle1"] = import("../projectCycle1/PklProject")
|
||||
}
|
||||
Reference in New Issue
Block a user