Bind PackageServer to ephemeral port to avoid port conflicts (#227)

This is a comprehensive solution to the "flaky PackageServer tests"
problem. It rules out port conflicts and imposes no limits on test
parallelism. The same solution can be used for other test servers
in the future.

Major changes:
- Turn `PackageServer` from a singleton into a class that is
  instantiated per test class or test method.
- Start the server the first time its `port` property is read.
  Bind the server to an ephemeral port instead of port 12110.
- For every test that uses `PackageServer`, pass the server port to
  `--test-port`, `HttpClient.Builder.setTestPort`, the `CliBaseOptions`
  or `ExecutorOptions` constructor, or the Gradle plugin's `testPort` property.
  Wire all of these to `RequestRewritingClient`'s `testPort` constructor parameter.
- Enhance `RequestRewritingClient` to replace port 12110 with `testPort`
  in request URIs unless `testPort` is -1 (its default).
- Introduce `ExecutorOptions.Builder`.
  This makes executor options more comfortable to create
  and allows to hide options such as `testPort`.
- Deprecate the `ExecutorOptions` constructor to steer users towards the builder.
- Get rid of `ExecutorOptions2`, which is no longer needed.
- Clean up `EmbeddedExecutorTest` with the help of the builder.
This commit is contained in:
translatenix
2024-03-13 10:40:55 -07:00
committed by GitHub
parent 1e608b2aae
commit 014b3a8816
26 changed files with 756 additions and 581 deletions

View File

@@ -15,47 +15,226 @@
*/
package org.pkl.executor;
import java.net.URI;
import java.nio.file.Path;
import java.time.Duration;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import org.pkl.executor.spi.v1.ExecutorSpiOptions;
import org.pkl.executor.spi.v1.ExecutorSpiOptions2;
/**
* Options for {@link Executor#evaluatePath}.
*
* <p>Note that subclasses of {@code ExecutorOptions} offer additional options.
* <p>To create {@code ExecutorOptions}, use its {@linkplain #builder builder}.
*/
public class ExecutorOptions {
protected final List<String> allowedModules;
public final class ExecutorOptions {
private final List<String> allowedModules;
protected final List<String> allowedResources;
private final List<String> allowedResources;
protected final Map<String, String> environmentVariables;
private final Map<String, String> environmentVariables;
protected final Map<String, String> externalProperties;
private final Map<String, String> externalProperties;
protected final List<Path> modulePath;
private final List<Path> modulePath;
protected final /* @Nullable */ Path rootDir;
private final /* @Nullable */ Path rootDir;
protected final /* @Nullable */ Duration timeout;
private final /* @Nullable */ Duration timeout;
protected final /* @Nullable */ String outputFormat;
private final /* @Nullable */ String outputFormat;
protected final /* @Nullable */ Path moduleCacheDir;
private final /* @Nullable */ Path moduleCacheDir;
protected final /* @Nullable */ Path projectDir;
private final /* @Nullable */ Path projectDir;
private final List<Path> certificateFiles;
private final List<URI> certificateUris;
private final int testPort; // -1 means disabled
private final int spiOptionsVersion; // -1 means use latest
/** Returns the module cache dir that the CLI uses by default. */
public static Path defaultModuleCacheDir() {
return Path.of(System.getProperty("user.home"), ".pkl", "cache");
}
public static Builder builder() {
return new Builder();
}
/**
* A builder of {@link ExecutorOptions}.
*
* <p>It is safe to create multiple options objects with the same builder.
*/
public static final class Builder {
private List<String> allowedModules = List.of();
private List<String> allowedResources = List.of();
private Map<String, String> environmentVariables = Map.of();
private Map<String, String> externalProperties = Map.of();
private List<Path> modulePath = List.of();
private /* @Nullable */ Path rootDir;
private /* @Nullable */ Duration timeout;
private /* @Nullable */ String outputFormat;
private /* @Nullable */ Path moduleCacheDir;
private /* @Nullable */ Path projectDir;
private List<Path> certificateFiles = List.of();
private List<URI> certificateUris = List.of();
private int testPort = -1; // -1 means disabled
private int spiOptionsVersion = -1; // -1 means use latest
private Builder() {}
/** API equivalent of the {@code --allowed-modules} CLI option. */
public Builder allowedModules(List<String> allowedModules) {
this.allowedModules = allowedModules;
return this;
}
/** API equivalent of the {@code --allowed-modules} CLI option. */
public Builder allowedModules(String... allowedModules) {
this.allowedModules = List.of(allowedModules);
return this;
}
/** API equivalent of the {@code --allowed-resources} CLI option. */
public Builder allowedResources(List<String> allowedResources) {
this.allowedResources = allowedResources;
return this;
}
/** API equivalent of the {@code --allowed-resources} CLI option. */
public Builder allowedResources(String... allowedResources) {
this.allowedResources = List.of(allowedResources);
return this;
}
/** API equivalent of the repeatable {@code --env-var} CLI option. */
public Builder environmentVariables(Map<String, String> environmentVariables) {
this.environmentVariables = environmentVariables;
return this;
}
/** API equivalent of the repeatable {@code --property} CLI option. */
public Builder externalProperties(Map<String, String> externalProperties) {
this.externalProperties = externalProperties;
return this;
}
/** API equivalent of the {@code --module-path} CLI option. */
public Builder modulePath(List<Path> modulePath) {
this.modulePath = modulePath;
return this;
}
/** API equivalent of the {@code --module-path} CLI option. */
public Builder modulePath(Path... modulePath) {
this.modulePath = List.of(modulePath);
return this;
}
/** API equivalent of the {@code --root-dir} CLI option. */
public Builder rootDir(/*Nullable*/ Path rootDir) {
this.rootDir = rootDir;
return this;
}
/** API equivalent of the {@code --timeout} CLI option. */
public Builder timeout(/*Nullable*/ Duration timeout) {
this.timeout = timeout;
return this;
}
/** API equivalent of the {@code --format} CLI option. */
public Builder outputFormat(/*Nullable*/ String outputFormat) {
this.outputFormat = outputFormat;
return this;
}
/**
* API equivalent of the {@code --cache-dir} CLI option. Passing {@code null} is equivalent to
* {@code --no-cache}.
*/
public Builder moduleCacheDir(/*Nullable*/ Path moduleCacheDir) {
this.moduleCacheDir = moduleCacheDir;
return this;
}
/**
* API equivalent of the {@code --project-dir} CLI option.
*
* <p>Unlike the CLI, this option only sets project dependencies. It does not set evaluator
* settings.
*/
public Builder projectDir(/*Nullable*/ Path projectDir) {
this.projectDir = projectDir;
return this;
}
/** API equivalent of the {@code --ca-certificates} CLI option. */
public Builder certificateFiles(List<Path> certificateFiles) {
this.certificateFiles = certificateFiles;
return this;
}
/** API equivalent of the {@code --ca-certificates} CLI option. */
public Builder certificateFiles(Path... certificateFiles) {
this.certificateFiles = List.of(certificateFiles);
return this;
}
/** API equivalent of the {@code --ca-certificates} CLI option. */
public Builder certificateUris(List<URI> certificateUris) {
this.certificateUris = certificateUris;
return this;
}
/** API equivalent of the {@code --ca-certificates} CLI option. */
public Builder certificateUris(URI... certificateUris) {
this.certificateUris = List.of(certificateUris);
return this;
}
/** Internal test option. -1 means disabled. */
Builder testPort(int testPort) {
this.testPort = testPort;
return this;
}
/** Internal test option. -1 means use latest. */
Builder spiOptionsVersion(int version) {
this.spiOptionsVersion = version;
return this;
}
public ExecutorOptions build() {
return new ExecutorOptions(
allowedModules,
allowedResources,
environmentVariables,
externalProperties,
modulePath,
rootDir,
timeout,
outputFormat,
moduleCacheDir,
projectDir,
certificateFiles,
certificateUris,
testPort,
spiOptionsVersion);
}
}
/**
* Constructs an options object.
*
* @deprecated use {@link #builder} instead
* @param allowedModules API equivalent of the {@code --allowed-modules} CLI option
* @param allowedResources API equivalent of the {@code --allowed-resources} CLI option
* @param environmentVariables API equivalent of the repeatable {@code --env-var} CLI option
@@ -69,6 +248,7 @@ public class ExecutorOptions {
* null} is equivalent to {@code --no-cache}.
* @param projectDir API equivalent of the {@code --project-dir} CLI option.
*/
@Deprecated(forRemoval = true)
public ExecutorOptions(
List<String> allowedModules,
List<String> allowedResources,
@@ -81,16 +261,53 @@ public class ExecutorOptions {
/* @Nullable */ Path moduleCacheDir,
/* @Nullable */ Path projectDir) {
this.allowedModules = allowedModules;
this.allowedResources = allowedResources;
this.environmentVariables = environmentVariables;
this.externalProperties = externalProperties;
this(
allowedModules,
allowedResources,
environmentVariables,
externalProperties,
modulePath,
rootDir,
timeout,
outputFormat,
moduleCacheDir,
projectDir,
List.of(),
List.of(),
-1,
-1);
}
private ExecutorOptions(
List<String> allowedModules,
List<String> allowedResources,
Map<String, String> environmentVariables,
Map<String, String> externalProperties,
List<Path> modulePath,
/* @Nullable */ Path rootDir,
/* @Nullable */ Duration timeout,
/* @Nullable */ String outputFormat,
/* @Nullable */ Path moduleCacheDir,
/* @Nullable */ Path projectDir,
List<Path> certificateFiles,
List<URI> certificateUris,
int testPort,
int spiOptionsVersion) {
this.allowedModules = List.copyOf(allowedModules);
this.allowedResources = List.copyOf(allowedResources);
this.environmentVariables = Map.copyOf(environmentVariables);
this.externalProperties = Map.copyOf(externalProperties);
this.modulePath = modulePath;
this.rootDir = rootDir;
this.timeout = timeout;
this.outputFormat = outputFormat;
this.moduleCacheDir = moduleCacheDir;
this.projectDir = projectDir;
this.certificateFiles = List.copyOf(certificateFiles);
this.certificateUris = List.copyOf(certificateUris);
this.testPort = testPort;
this.spiOptionsVersion = spiOptionsVersion;
}
/** API equivalent of the {@code --allowed-modules} CLI option. */
@@ -151,6 +368,16 @@ public class ExecutorOptions {
return projectDir;
}
/** API equivalent of the {@code --ca-certificates} CLI option. */
public List<Path> getCertificateFiles() {
return certificateFiles;
}
/** API equivalent of the {@code --ca-certificates} CLI option. */
public List<URI> getCertificateUris() {
return certificateUris;
}
@Override
public boolean equals(/* @Nullable */ Object obj) {
if (this == obj) return true;
@@ -166,7 +393,11 @@ public class ExecutorOptions {
&& Objects.equals(timeout, other.timeout)
&& Objects.equals(outputFormat, other.outputFormat)
&& Objects.equals(moduleCacheDir, other.moduleCacheDir)
&& Objects.equals(projectDir, other.projectDir);
&& Objects.equals(projectDir, other.projectDir)
&& Objects.equals(certificateFiles, other.certificateFiles)
&& Objects.equals(certificateUris, other.certificateUris)
&& testPort == other.testPort
&& spiOptionsVersion == other.spiOptionsVersion;
}
@Override
@@ -181,7 +412,11 @@ public class ExecutorOptions {
timeout,
outputFormat,
moduleCacheDir,
projectDir);
projectDir,
certificateFiles,
certificateUris,
testPort,
spiOptionsVersion);
}
@Override
@@ -207,20 +442,49 @@ public class ExecutorOptions {
+ moduleCacheDir
+ ", projectDir="
+ projectDir
+ ", certificateFiles="
+ certificateFiles
+ ", certificateUris="
+ certificateUris
+ ", testPort="
+ testPort
+ ", spiOptionsVersion="
+ spiOptionsVersion
+ '}';
}
ExecutorSpiOptions toSpiOptions() {
return new ExecutorSpiOptions(
allowedModules,
allowedResources,
environmentVariables,
externalProperties,
modulePath,
rootDir,
timeout,
outputFormat,
moduleCacheDir,
projectDir);
switch (spiOptionsVersion) {
case -1:
case 2:
return new ExecutorSpiOptions2(
allowedModules,
allowedResources,
environmentVariables,
externalProperties,
modulePath,
rootDir,
timeout,
outputFormat,
moduleCacheDir,
projectDir,
certificateFiles,
certificateUris,
testPort);
case 1: // for testing only
return new ExecutorSpiOptions(
allowedModules,
allowedResources,
environmentVariables,
externalProperties,
modulePath,
rootDir,
timeout,
outputFormat,
moduleCacheDir,
projectDir);
default:
throw new AssertionError("Unknown ExecutorSpiOptions version: " + spiOptionsVersion);
}
}
}

View File

@@ -1,174 +0,0 @@
/**
* 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.executor;
import java.net.URI;
import java.nio.file.Path;
import java.time.Duration;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import org.pkl.executor.spi.v1.ExecutorSpiOptions2;
/**
* Options for {@link Executor#evaluatePath}.
*
* <p>This class offers additional options not available in {@code ExecutorOptions}.
*/
public class ExecutorOptions2 extends ExecutorOptions {
protected final List<Path> certificateFiles;
protected final List<URI> certificateUris;
/**
* Constructs an options object.
*
* @param allowedModules API equivalent of the {@code --allowed-modules} CLI option
* @param allowedResources API equivalent of the {@code --allowed-resources} CLI option
* @param environmentVariables API equivalent of the repeatable {@code --env-var} CLI option
* @param externalProperties API equivalent of the repeatable {@code --property} CLI option
* @param modulePath API equivalent of the {@code --module-path} CLI option
* @param rootDir API equivalent of the {@code --root-dir} CLI option
* @param timeout API equivalent of the {@code --timeout} CLI option
* @param outputFormat API equivalent of the {@code --format} CLI option
* @param moduleCacheDir API equivalent of the {@code --cache-dir} CLI option. Passing {@link
* #defaultModuleCacheDir()} is equivalent to omitting {@code --cache-dir}. Passing {@code
* null} is equivalent to {@code --no-cache}.
* @param projectDir API equivalent of the {@code --project-dir} CLI option.
* @param certificateFiles API equivalent of the {@code --ca-certificates} CLI option
* @param certificateUris API equivalent of the {@code --ca-certificates} CLI option
*/
public ExecutorOptions2(
List<String> allowedModules,
List<String> allowedResources,
Map<String, String> environmentVariables,
Map<String, String> externalProperties,
List<Path> modulePath,
/* @Nullable */ Path rootDir,
/* @Nullable */ Duration timeout,
/* @Nullable */ String outputFormat,
/* @Nullable */ Path moduleCacheDir,
/* @Nullable */ Path projectDir,
List<Path> certificateFiles,
List<URI> certificateUris) {
super(
allowedModules,
allowedResources,
environmentVariables,
externalProperties,
modulePath,
rootDir,
timeout,
outputFormat,
moduleCacheDir,
projectDir);
this.certificateFiles = certificateFiles;
this.certificateUris = certificateUris;
}
/** API equivalent of the {@code --ca-certificates} CLI option. */
public List<Path> getCertificateFiles() {
return certificateFiles;
}
/** API equivalent of the {@code --ca-certificates} CLI option. */
public List<URI> getCertificateUris() {
return certificateUris;
}
@Override
public boolean equals(/* @Nullable */ Object obj) {
if (this == obj) return true;
if (obj.getClass() != ExecutorOptions2.class) return false;
var other = (ExecutorOptions2) obj;
return allowedModules.equals(other.allowedModules)
&& allowedResources.equals(other.allowedResources)
&& environmentVariables.equals(other.environmentVariables)
&& externalProperties.equals(other.externalProperties)
&& modulePath.equals(other.modulePath)
&& Objects.equals(rootDir, other.rootDir)
&& Objects.equals(timeout, other.timeout)
&& Objects.equals(outputFormat, other.outputFormat)
&& Objects.equals(moduleCacheDir, other.moduleCacheDir)
&& Objects.equals(projectDir, other.projectDir)
&& Objects.equals(certificateFiles, other.certificateFiles)
&& Objects.equals(certificateUris, other.certificateUris);
}
@Override
public int hashCode() {
return Objects.hash(
allowedModules,
allowedResources,
environmentVariables,
externalProperties,
modulePath,
rootDir,
timeout,
outputFormat,
moduleCacheDir,
projectDir,
certificateFiles,
certificateUris);
}
@Override
public String toString() {
return "ExecutorOptions2{"
+ "allowedModules="
+ allowedModules
+ ", allowedResources="
+ allowedResources
+ ", environmentVariables="
+ environmentVariables
+ ", externalProperties="
+ externalProperties
+ ", modulePath="
+ modulePath
+ ", rootDir="
+ rootDir
+ ", timeout="
+ timeout
+ ", outputFormat="
+ outputFormat
+ ", cacheDir="
+ moduleCacheDir
+ ", projectDir="
+ projectDir
+ ", certificateFiles="
+ certificateFiles
+ ", certificateUris="
+ certificateUris
+ '}';
}
ExecutorSpiOptions2 toSpiOptions() {
return new ExecutorSpiOptions2(
allowedModules,
allowedResources,
environmentVariables,
externalProperties,
modulePath,
rootDir,
timeout,
outputFormat,
moduleCacheDir,
projectDir,
certificateFiles,
certificateUris);
}
}

View File

@@ -26,6 +26,8 @@ public class ExecutorSpiOptions2 extends ExecutorSpiOptions {
private final List<URI> certificateUris;
private final int testPort;
public ExecutorSpiOptions2(
List<String> allowedModules,
List<String> allowedResources,
@@ -38,7 +40,8 @@ public class ExecutorSpiOptions2 extends ExecutorSpiOptions {
Path moduleCacheDir,
Path projectDir,
List<Path> certificateFiles,
List<URI> certificateUris) {
List<URI> certificateUris,
int testPort) {
super(
allowedModules,
allowedResources,
@@ -52,6 +55,7 @@ public class ExecutorSpiOptions2 extends ExecutorSpiOptions {
projectDir);
this.certificateFiles = certificateFiles;
this.certificateUris = certificateUris;
this.testPort = testPort;
}
public List<Path> getCertificateFiles() {
@@ -61,4 +65,8 @@ public class ExecutorSpiOptions2 extends ExecutorSpiOptions {
public List<URI> getCertificateUris() {
return certificateUris;
}
public int getTestPort() {
return testPort;
}
}

View File

@@ -20,36 +20,44 @@ import kotlin.io.path.exists
class EmbeddedExecutorTest {
/**
* A combination of ExecutorOptions version, pkl-executor version,
* and Pkl distribution version that parameterized tests should be run against.
* An executor that uses a particular combination of ExecutorSpiOptions version,
* pkl-executor version, and Pkl distribution version.
*/
data class ExecutionContext(
val executor: Executor,
val options: (ExecutorOptions) -> ExecutorOptions,
val name: String
class TestExecutor(
private val executor: Executor,
private val spiOptionsVersion: Int,
private val name: String
) {
fun evaluatePath(modulePath: Path, optionSpec: ExecutorOptions.Builder.() -> Unit): String {
val options = ExecutorOptions.builder()
.apply(optionSpec)
.spiOptionsVersion(spiOptionsVersion)
.build()
return executor.evaluatePath(modulePath, options)
}
override fun toString(): String = name
}
companion object {
@JvmStatic
private val allExecutionContexts: List<ExecutionContext> by lazy {
private val allTestExecutors: List<TestExecutor> by lazy {
listOf(
ExecutionContext(executor1_1.value, ::convertToOptions1, "Options1, Executor1, Distribution1"),
TestExecutor(executor1_1.value, 1, "SpiOptions1, Executor1, Distribution1"),
// This context has a pkl-executor version that is lower than the distribution version.
// It can be enabled once there is a distribution that includes pkl-executor.
ExecutionContext(executor1_2.value, ::convertToOptions1, "Options1, Executor1, Distribution2"),
TestExecutor(executor1_2.value, 1, "SpiOptions1, Executor1, Distribution2"),
ExecutionContext(executor2_1.value, ::convertToOptions1, "Options1, Executor2, Distribution1"),
ExecutionContext(executor2_1.value, ::convertToOptions2, "Options2, Executor2, Distribution1"),
TestExecutor(executor2_1.value, 1, "SpiOptions1, Executor2, Distribution1"),
TestExecutor(executor2_1.value, 2, "SpiOptions2, Executor2, Distribution1"),
ExecutionContext(executor2_2.value, ::convertToOptions1, "Options1, Executor2, Distribution2"),
ExecutionContext(executor2_2.value, ::convertToOptions2, "Options2, Executor2, Distribution2")
TestExecutor(executor2_2.value, 1, "SpiOptions1, Executor2, Distribution2"),
TestExecutor(executor2_2.value, 2, "SpiOptions2, Executor2, Distribution2")
)
}
private val currentExecutor: Executor by lazy { executor2_2.value }
private val currentExecutor: TestExecutor by lazy {
TestExecutor(executor2_2.value, -1, "currentExecutor")
}
// A pkl-executor library that supports ExecutorSpiOptions up to v1
// and a Pkl distribution that supports ExecutorSpiOptions up to v1.
@@ -101,11 +109,9 @@ class EmbeddedExecutorTest {
// a Pkl distribution that supports ExecutorSpiOptions up to v1
private val pklDistribution1: Path by lazy {
FileTestUtils.rootProjectDir.resolve("pkl-executor/build/pklHistoricalDistributions/pkl-config-java-all-0.25.0.jar").apply {
if (!exists()) {
throw AssertionError("Missing test fixture. " +
"To fix this problem, run `./gradlew :pkl-executor:prepareTest`.")
}
FileTestUtils.rootProjectDir
.resolve("pkl-executor/build/pklHistoricalDistributions/pkl-config-java-all-0.25.0.jar").apply {
if (!exists()) missingTestFixture()
}
}
@@ -114,40 +120,13 @@ class EmbeddedExecutorTest {
FileTestUtils.rootProjectDir
.resolve("pkl-config-java/build/libs/pkl-config-java-all-" +
"${Release.current().version().withBuild(null).toString().replaceFirst("dev", "SNAPSHOT")}.jar").apply {
if (!exists()) throw AssertionError("Missing test fixture. " +
"To fix this problem, run `./gradlew :pkl-executor:prepareTest`.")
if (!exists()) missingTestFixture()
}
}
private fun convertToOptions2(options: ExecutorOptions): ExecutorOptions2 =
if (options is ExecutorOptions2) options else ExecutorOptions2(
options.allowedModules,
options.allowedResources,
options.environmentVariables,
options.externalProperties,
options.modulePath,
options.rootDir,
options.timeout,
options.outputFormat,
options.moduleCacheDir,
options.projectDir,
listOf(),
listOf()
)
private fun convertToOptions1(options: ExecutorOptions): ExecutorOptions =
if (options.javaClass == ExecutorOptions::class.java) options else ExecutorOptions(
options.allowedModules,
options.allowedResources,
options.environmentVariables,
options.externalProperties,
options.modulePath,
options.rootDir,
options.timeout,
options.outputFormat,
options.moduleCacheDir,
options.projectDir
)
private fun missingTestFixture(): Nothing =
throw AssertionError("Missing test fixture. " +
"To fix this problem, run `./gradlew :pkl-executor:prepareTest`.")
}
@Test
@@ -233,8 +212,8 @@ class EmbeddedExecutorTest {
}
@ParameterizedTest
@MethodSource("getAllExecutionContexts")
fun `evaluate a module that is missing a ModuleInfo annotation`(context: ExecutionContext, @TempDir tempDir: Path) {
@MethodSource("getAllTestExecutors")
fun `evaluate a module that is missing a ModuleInfo annotation`(executor: TestExecutor, @TempDir tempDir: Path) {
val pklFile = tempDir.resolve("test.pkl")
pklFile.toFile().writeText(
"""
@@ -245,23 +224,11 @@ class EmbeddedExecutorTest {
)
val e = assertThrows<ExecutorException> {
context.executor.evaluatePath(
pklFile,
context.options(ExecutorOptions2(
listOf("file:"),
listOf("prop:"),
mapOf(),
mapOf(),
listOf(),
tempDir,
null,
null,
null,
null,
listOf(),
listOf()
)
))
executor.evaluatePath(pklFile) {
allowedModules("file:")
allowedResources("prop:")
rootDir(tempDir)
}
}
assertThat(e.message)
@@ -269,8 +236,8 @@ class EmbeddedExecutorTest {
}
@ParameterizedTest
@MethodSource("getAllExecutionContexts")
fun `evaluate a module that requests an incompatible Pkl version`(context: ExecutionContext, @TempDir tempDir: Path) {
@MethodSource("getAllTestExecutors")
fun `evaluate a module that requests an incompatible Pkl version`(executor: TestExecutor, @TempDir tempDir: Path) {
val pklFile = tempDir.resolve("test.pkl")
pklFile.toFile().writeText(
"""
@@ -282,23 +249,11 @@ class EmbeddedExecutorTest {
)
val e = assertThrows<ExecutorException> {
context.executor.evaluatePath(
pklFile,
context.options(ExecutorOptions2(
listOf("file:"),
listOf("prop:"),
mapOf(),
mapOf(),
listOf(),
tempDir,
null,
null,
null,
null,
listOf(),
listOf()
))
)
executor.evaluatePath(pklFile) {
allowedModules("file:")
allowedResources("prop:")
rootDir(tempDir)
}
}
assertThat(e.message)
@@ -306,8 +261,8 @@ class EmbeddedExecutorTest {
}
@ParameterizedTest
@MethodSource("getAllExecutionContexts")
fun `evaluate a module that reads environment variables and external properties`(context: ExecutionContext, @TempDir tempDir: Path) {
@MethodSource("getAllTestExecutors")
fun `evaluate a module that reads environment variables and external properties`(executor: TestExecutor, @TempDir tempDir: Path) {
val pklFile = tempDir.resolve("test.pkl")
pklFile.toFile().writeText(
"""
@@ -319,24 +274,13 @@ class EmbeddedExecutorTest {
""".trimIndent()
)
val result = context.executor.evaluatePath(
pklFile,
context.options(ExecutorOptions2(
listOf("file:"),
// should `prop:pkl.outputFormat` be allowed automatically?
listOf("prop:", "env:"),
mapOf("ENV_VAR" to "ENV_VAR"),
mapOf("property" to "property"),
listOf(),
null,
null,
null,
null,
null,
listOf(),
listOf()
))
)
val result = executor.evaluatePath(pklFile) {
allowedModules("file:")
// should `prop:pkl.outputFormat` be allowed automatically?
allowedResources("prop:", "env:")
environmentVariables(mapOf("ENV_VAR" to "ENV_VAR"))
externalProperties(mapOf("property" to "property"))
}
assertThat(result.trim()).isEqualTo(
"""
@@ -347,8 +291,8 @@ class EmbeddedExecutorTest {
}
@ParameterizedTest
@MethodSource("getAllExecutionContexts")
fun `evaluate a module that depends on another module`(context: ExecutionContext, @TempDir tempDir: Path) {
@MethodSource("getAllTestExecutors")
fun `evaluate a module that depends on another module`(executor: TestExecutor, @TempDir tempDir: Path) {
val pklFile = tempDir.resolve("test.pkl")
pklFile.toFile().writeText(
"""
@@ -372,24 +316,10 @@ class EmbeddedExecutorTest {
""".trimIndent()
)
val result = context.executor.evaluatePath(
pklFile,
context.options(ExecutorOptions2(
listOf("file:"),
listOf("prop:"),
mapOf(),
mapOf(),
listOf(),
null,
null,
null,
null,
null,
listOf(),
listOf()
)
)
)
val result = executor.evaluatePath(pklFile) {
allowedModules("file:")
allowedResources("prop:")
}
assertThat(result.trim()).isEqualTo(
"""
@@ -401,8 +331,8 @@ class EmbeddedExecutorTest {
}
@ParameterizedTest
@MethodSource("getAllExecutionContexts")
fun `evaluate a module whose evaluation fails`(context: ExecutionContext, @TempDir tempDir: Path) {
@MethodSource("getAllTestExecutors")
fun `evaluate a module whose evaluation fails`(executor: TestExecutor, @TempDir tempDir: Path) {
val pklFile = tempDir.resolve("test.pkl")
pklFile.toFile().writeText(
"""
@@ -414,23 +344,11 @@ class EmbeddedExecutorTest {
)
val e = assertThrows<ExecutorException> {
context.executor.evaluatePath(
pklFile,
context.options(ExecutorOptions2(
listOf("file:"),
listOf("prop:"),
mapOf(),
mapOf(),
listOf(),
tempDir,
null,
null,
null,
null,
listOf(),
listOf()
)
))
executor.evaluatePath(pklFile) {
allowedModules("file:")
allowedResources("prop:")
rootDir(tempDir)
}
}
assertThat(e.message)
@@ -441,8 +359,8 @@ class EmbeddedExecutorTest {
}
@ParameterizedTest
@MethodSource("getAllExecutionContexts")
fun `time out a module`(context: ExecutionContext, @TempDir tempDir: Path) {
@MethodSource("getAllTestExecutors")
fun `time out a module`(executor: TestExecutor, @TempDir tempDir: Path) {
val pklFile = tempDir.resolve("test.pkl")
pklFile.toFile().writeText(
"""
@@ -456,23 +374,12 @@ class EmbeddedExecutorTest {
)
val e = assertThrows<ExecutorException> {
context.executor.evaluatePath(
pklFile,
context.options(ExecutorOptions2(
listOf("file:"),
listOf("prop:"),
mapOf(),
mapOf(),
listOf(),
tempDir,
Duration.ofSeconds(1),
null,
null,
null,
listOf(),
listOf()
))
)
executor.evaluatePath(pklFile) {
allowedModules("file:")
allowedResources("prop:")
rootDir(tempDir)
timeout(Duration.ofSeconds(1))
}
}
assertThat(e.message)
@@ -493,22 +400,15 @@ class EmbeddedExecutorTest {
chirpy = new Bird { name = "Chirpy"; favoriteFruit { name = "Orange" } }
""".trimIndent()
)
PackageServer.ensureStarted()
val result = currentExecutor.evaluatePath(pklFile,
ExecutorOptions2(
listOf("file:", "package:", "https:"),
listOf("prop:", "package:", "https:"),
mapOf(),
mapOf(),
listOf(),
null,
null,
null,
cacheDir,
null,
listOf(FileTestUtils.selfSignedCertificate),
listOf())
)
val result = PackageServer().use { server ->
currentExecutor.evaluatePath(pklFile) {
allowedModules("file:", "package:", "https:")
allowedResources("prop:", "package:", "https:")
moduleCacheDir(cacheDir)
certificateFiles(FileTestUtils.selfSignedCertificate)
testPort(server.port)
}
}
assertThat(result.trim()).isEqualTo("""
chirpy {
name = "Chirpy"
@@ -523,8 +423,8 @@ class EmbeddedExecutorTest {
}
@ParameterizedTest
@MethodSource("getAllExecutionContexts")
fun `evaluate a project dependency`(context: ExecutionContext, @TempDir tempDir: Path) {
@MethodSource("getAllTestExecutors")
fun `evaluate a project dependency`(executor: TestExecutor, @TempDir tempDir: Path) {
val cacheDir = tempDir.resolve("packages")
PackageServer.populateCacheDir(cacheDir)
val projectDir = tempDir.resolve("project/")
@@ -569,21 +469,12 @@ class EmbeddedExecutorTest {
result = Swallow
""".trimIndent()
)
val result = context.executor.evaluatePath(pklFile,
context.options(ExecutorOptions2(
listOf("file:", "package:", "projectpackage:", "https:"),
listOf("prop:", "package:", "projectpackage:", "https:"),
mapOf(),
mapOf(),
listOf(),
null,
null,
null,
cacheDir,
projectDir,
listOf(),
listOf())
))
val result = executor.evaluatePath(pklFile) {
allowedModules("file:", "package:", "projectpackage:", "https:")
allowedResources("prop:", "package:", "projectpackage:", "https:")
moduleCacheDir(cacheDir)
projectDir(projectDir)
}
assertThat(result).isEqualTo("""
result {
name = "Swallow"