mirror of
https://github.com/apple/pkl.git
synced 2026-03-23 09:31:06 +01:00
Polish test result running and reporting (#738)
Changes: * Move class `TestResults` to package `org.pkl.core`, because it is a public class (it's the result of `Evaluator#evaluateTest`) * Change examples to treat individual examples as assertions in the same test. Previously, they were considered different tests with an incrementing number. This better aligns with how facts are treated. * Change `TestResults` to be a record, and introduce builders. * Remove "module" test result section (it is not really a section). * Add javadoc to `TestResults` * Formatting fix: prefix ✍️ emoji just like we do the ❌ and ✅ emojis * Consider writing examples as failures, not successes. `pkl test` will exit with code 10 if the only failing tests are due to writing examples.
This commit is contained in:
@@ -409,6 +409,10 @@ Evaluate the given `<modules>` as _tests_, producing a test report and appropria
|
||||
|
||||
Renderers defined in test files will be ignored by the `test` command.
|
||||
|
||||
Tests that result in writing `pkl-expected.pcf` files are considered failing tests.
|
||||
If these are the only failures, the command exits with exit code 10.
|
||||
Otherwise, failures result in exit code 1.
|
||||
|
||||
<modules>::
|
||||
The absolute or relative URIs of the modules to test. Relative URIs are resolved against the working directory.
|
||||
|
||||
|
||||
@@ -60,12 +60,14 @@ constructor(
|
||||
val evaluator = builder.build()
|
||||
evaluator.use {
|
||||
var failed = false
|
||||
var isExampleWrittenFailure = true
|
||||
val moduleNames = mutableSetOf<String>()
|
||||
for ((idx, moduleUri) in sources.withIndex()) {
|
||||
try {
|
||||
val results = evaluator.evaluateTest(uri(moduleUri), testOptions.overwrite)
|
||||
if (!failed) {
|
||||
failed = results.failed()
|
||||
isExampleWrittenFailure = results.isExampleWrittenFailure.and(isExampleWrittenFailure)
|
||||
}
|
||||
SimpleReport().report(results, consoleWriter)
|
||||
if (sources.size > 1 && idx != sources.size - 1) {
|
||||
@@ -101,7 +103,8 @@ constructor(
|
||||
}
|
||||
}
|
||||
if (failed) {
|
||||
throw CliTestException(ErrorMessages.create("testsFailed"))
|
||||
val exitCode = if (isExampleWrittenFailure) 10 else 1
|
||||
throw CliTestException(ErrorMessages.create("testsFailed"), exitCode)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -212,6 +212,24 @@ class CliProjectPackagerTest {
|
||||
"""
|
||||
.trimIndent()
|
||||
)
|
||||
projectDir
|
||||
.resolve("myTest.pkl-expected.pcf")
|
||||
.writeString(
|
||||
"""
|
||||
examples {
|
||||
["Bird"] {
|
||||
new {
|
||||
name = "Finch"
|
||||
favoriteFruit {
|
||||
name = "Tangerine"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
"""
|
||||
.trimIndent()
|
||||
)
|
||||
projectDir
|
||||
.resolve("PklProject")
|
||||
.writeString(
|
||||
|
||||
@@ -18,6 +18,7 @@ package org.pkl.cli
|
||||
import com.github.ajalt.clikt.core.MissingArgument
|
||||
import com.github.ajalt.clikt.core.subcommands
|
||||
import java.io.StringWriter
|
||||
import java.io.Writer
|
||||
import java.net.URI
|
||||
import java.nio.file.Path
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
@@ -98,13 +99,13 @@ class CliTestRunnerTest {
|
||||
assertThat(out.toString().stripFileAndLines(tempDir))
|
||||
.isEqualTo(
|
||||
"""
|
||||
module test
|
||||
facts
|
||||
❌ fail
|
||||
4 == 9
|
||||
❌ 0.0% tests pass [1/1 failed], 50.0% asserts pass [1/2 failed]
|
||||
module test
|
||||
facts
|
||||
❌ fail
|
||||
4 == 9 (/tempDir/test.pkl, line xx)
|
||||
❌ 0.0% tests pass [1/1 failed], 50.0% asserts pass [1/2 failed]
|
||||
|
||||
"""
|
||||
"""
|
||||
.trimIndent()
|
||||
)
|
||||
assertThat(err.toString()).isEqualTo("")
|
||||
@@ -142,7 +143,7 @@ class CliTestRunnerTest {
|
||||
|
||||
5 | throw("uh oh")
|
||||
^^^^^^^^^^^^^^
|
||||
at test#facts["fail"][#1]
|
||||
at test#facts["fail"][#1] (/tempDir/test.pkl, line xx)
|
||||
❌ 0.0% tests pass [1/1 failed], 0.0% asserts pass [1/1 failed]
|
||||
|
||||
"""
|
||||
@@ -183,7 +184,7 @@ class CliTestRunnerTest {
|
||||
|
||||
5 | throw("uh oh")
|
||||
^^^^^^^^^^^^^^
|
||||
at test#examples["fail"][#1]
|
||||
at test#examples["fail"][#1] (/tempDir/test.pkl, line xx)
|
||||
❌ 0.0% tests pass [1/1 failed], 0.0% asserts pass [1/1 failed]
|
||||
|
||||
"""
|
||||
@@ -235,10 +236,10 @@ class CliTestRunnerTest {
|
||||
❌ fail
|
||||
–– Pkl Error ––
|
||||
uh oh
|
||||
|
||||
|
||||
5 | throw("uh oh")
|
||||
^^^^^^^^^^^^^^
|
||||
at test#examples["fail"][#1]
|
||||
at test#examples["fail"][#1] (/tempDir/test.pkl, line xx)
|
||||
❌ 0.0% tests pass [1/1 failed], 0.0% asserts pass [1/1 failed]
|
||||
|
||||
"""
|
||||
@@ -266,9 +267,10 @@ class CliTestRunnerTest {
|
||||
"""
|
||||
.trimIndent()
|
||||
val input = tempDir.resolve("test.pkl").writeString(code).toString()
|
||||
val noopWriter = noopWriter()
|
||||
val opts = CliBaseOptions(sourceModules = listOf(input.toUri()), settings = URI("pkl:settings"))
|
||||
val testOpts = CliTestOptions(junitDir = tempDir)
|
||||
val runner = CliTestRunner(opts, testOpts)
|
||||
val runner = CliTestRunner(opts, testOpts, noopWriter, noopWriter)
|
||||
assertThatCode { runner.run() }.hasMessageContaining("failed")
|
||||
|
||||
val junitReport = tempDir.resolve("test.xml").readString().stripFileAndLines(tempDir)
|
||||
@@ -279,7 +281,7 @@ class CliTestRunnerTest {
|
||||
<testsuite name="test" tests="2" failures="1">
|
||||
<testcase classname="test.facts" name="foo"></testcase>
|
||||
<testcase classname="test.facts" name="bar">
|
||||
<failure message="Fact Failure">5 == 9</failure>
|
||||
<failure message="Fact Failure">5 == 9 (/tempDir/test.pkl, line xx)</failure>
|
||||
</testcase>
|
||||
<system-err><![CDATA[9 = 9
|
||||
]]></system-err>
|
||||
@@ -308,9 +310,10 @@ class CliTestRunnerTest {
|
||||
"""
|
||||
.trimIndent()
|
||||
val input = tempDir.resolve("test.pkl").writeString(code).toString()
|
||||
val noopWriter = noopWriter()
|
||||
val opts = CliBaseOptions(sourceModules = listOf(input.toUri()), settings = URI("pkl:settings"))
|
||||
val testOpts = CliTestOptions(junitDir = tempDir)
|
||||
val runner = CliTestRunner(opts, testOpts)
|
||||
val runner = CliTestRunner(opts, testOpts, noopWriter, noopWriter)
|
||||
assertThatCode { runner.run() }.hasMessageContaining("failed")
|
||||
|
||||
val junitReport = tempDir.resolve("test.xml").readString().stripFileAndLines(tempDir)
|
||||
@@ -326,7 +329,7 @@ class CliTestRunnerTest {
|
||||
|
||||
9 | throw("uh oh")
|
||||
^^^^^^^^^^^^^^
|
||||
at test#facts["fail"][#1]
|
||||
at test#facts["fail"][#1] (/tempDir/test.pkl, line xx)
|
||||
</error>
|
||||
</testcase>
|
||||
<system-err><![CDATA[9 = 9
|
||||
@@ -369,13 +372,14 @@ class CliTestRunnerTest {
|
||||
.trimIndent()
|
||||
val input = tempDir.resolve("test.pkl").writeString(foo).toString()
|
||||
val input2 = tempDir.resolve("test.pkl").writeString(bar).toString()
|
||||
val noopWriter = noopWriter()
|
||||
val opts =
|
||||
CliBaseOptions(
|
||||
sourceModules = listOf(input.toUri(), input2.toUri()),
|
||||
settings = URI("pkl:settings")
|
||||
)
|
||||
val testOpts = CliTestOptions(junitDir = tempDir)
|
||||
val runner = CliTestRunner(opts, testOpts)
|
||||
val runner = CliTestRunner(opts, testOpts, noopWriter, noopWriter)
|
||||
assertThatCode { runner.run() }.hasMessageContaining("failed")
|
||||
}
|
||||
|
||||
@@ -392,6 +396,105 @@ class CliTestRunnerTest {
|
||||
assertThat(e1.message!!.replace("test", "eval")).isEqualTo(e2.helpMessage())
|
||||
}
|
||||
|
||||
private fun String.stripFileAndLines(tmpDir: Path) =
|
||||
replace(tmpDir.toUri().toString(), "/tempDir/").replace(Regex(""" \(.*, line \d+\)"""), "")
|
||||
@Test
|
||||
fun `example length mismatch`(@TempDir tempDir: Path) {
|
||||
val code =
|
||||
"""
|
||||
amends "pkl:test"
|
||||
|
||||
examples {
|
||||
["nums"] {
|
||||
1
|
||||
2
|
||||
}
|
||||
}
|
||||
"""
|
||||
.trimIndent()
|
||||
val input = tempDir.resolve("test.pkl").writeString(code).toString()
|
||||
tempDir
|
||||
.resolve("test.pkl-expected.pcf")
|
||||
.writeString(
|
||||
"""
|
||||
examples {
|
||||
["nums"] {
|
||||
1
|
||||
}
|
||||
}
|
||||
"""
|
||||
.trimIndent()
|
||||
)
|
||||
val out = StringWriter()
|
||||
val err = StringWriter()
|
||||
val opts = CliBaseOptions(sourceModules = listOf(input.toUri()), settings = URI("pkl:settings"))
|
||||
val testOpts = CliTestOptions()
|
||||
val runner = CliTestRunner(opts, testOpts, consoleWriter = out, errWriter = err)
|
||||
assertThatCode { runner.run() }.hasMessage("Tests failed.")
|
||||
|
||||
assertThat(out.toString().stripFileAndLines(tempDir))
|
||||
.isEqualToNormalizingNewlines(
|
||||
"""
|
||||
module test
|
||||
examples
|
||||
❌ nums
|
||||
(/tempDir/test.pkl, line xx)
|
||||
Output mismatch: Expected "nums" to contain 1 examples, but found 2
|
||||
❌ 0.0% tests pass [1/1 failed], 0.0% asserts pass [1/1 failed]
|
||||
|
||||
"""
|
||||
.trimIndent()
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `only written examples`(@TempDir tempDir: Path) {
|
||||
|
||||
val code =
|
||||
"""
|
||||
amends "pkl:test"
|
||||
|
||||
examples {
|
||||
["nums"] {
|
||||
1
|
||||
2
|
||||
}
|
||||
}
|
||||
"""
|
||||
.trimIndent()
|
||||
val input = tempDir.resolve("test.pkl").writeString(code).toString()
|
||||
val out = StringWriter()
|
||||
val err = StringWriter()
|
||||
val opts = CliBaseOptions(sourceModules = listOf(input.toUri()), settings = URI("pkl:settings"))
|
||||
val testOpts = CliTestOptions()
|
||||
val runner = CliTestRunner(opts, testOpts, consoleWriter = out, errWriter = err)
|
||||
val exception = assertThrows<CliException> { runner.run() }
|
||||
assertThat(exception.exitCode).isEqualTo(10)
|
||||
assertThat(out.toString())
|
||||
.isEqualTo(
|
||||
"""
|
||||
module test
|
||||
examples
|
||||
✍️ nums
|
||||
1 examples written
|
||||
|
||||
"""
|
||||
.trimIndent()
|
||||
)
|
||||
}
|
||||
|
||||
private fun String.stripFileAndLines(tmpDir: Path): String {
|
||||
// handle platform differences in handling of file URIs
|
||||
// (file:/// on *nix vs. file:/ on Windows)
|
||||
return replace(tmpDir.toFile().toURI().toString(), "/tempDir/")
|
||||
.replace(tmpDir.toUri().toString(), "/tempDir/")
|
||||
.replace(Regex("line \\d+"), "line xx")
|
||||
}
|
||||
|
||||
private fun noopWriter(): Writer =
|
||||
object : Writer() {
|
||||
override fun close() {}
|
||||
|
||||
override fun flush() {}
|
||||
|
||||
override fun write(cbuf: CharArray, off: Int, len: Int) {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,4 +15,4 @@
|
||||
*/
|
||||
package org.pkl.commons.cli
|
||||
|
||||
class CliTestException(msg: String) : CliException(msg, 1)
|
||||
class CliTestException(msg: String, exitCode: Int) : CliException(msg, exitCode)
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
package org.pkl.core;
|
||||
|
||||
import java.util.Map;
|
||||
import org.pkl.core.runtime.TestResults;
|
||||
import org.pkl.core.runtime.VmEvalException;
|
||||
|
||||
/**
|
||||
@@ -194,9 +193,9 @@ public interface Evaluator extends AutoCloseable {
|
||||
* <p>This requires that the target module be a test module; it must either amend or extend module
|
||||
* {@code "pkl:test"}. Otherwise, a type mismatch error is thrown.
|
||||
*
|
||||
* <p>This method will write possibly {@code pcf-expected.pkl} and {@code pcf-actual.pcf} files as
|
||||
* <p>This method will write possibly {@code pkl-expected.pcf} and {@code pkl-actual.pcf} files as
|
||||
* a sibling of the test module. The {@code overwrite} parameter causes the evaluator to overwrite
|
||||
* {@code pcf-expected.pkl} files if they currently exist.
|
||||
* {@code pkl-expected.pkl} files if they currently exist.
|
||||
*
|
||||
* @throws PklException if an error occurs during evaluation
|
||||
* @throws IllegalStateException if this evaluator has already been closed
|
||||
|
||||
@@ -40,7 +40,6 @@ import org.pkl.core.runtime.BaseModule;
|
||||
import org.pkl.core.runtime.Identifier;
|
||||
import org.pkl.core.runtime.ModuleResolver;
|
||||
import org.pkl.core.runtime.ResourceManager;
|
||||
import org.pkl.core.runtime.TestResults;
|
||||
import org.pkl.core.runtime.TestRunner;
|
||||
import org.pkl.core.runtime.VmContext;
|
||||
import org.pkl.core.runtime.VmException;
|
||||
|
||||
301
pkl-core/src/main/java/org/pkl/core/TestResults.java
Normal file
301
pkl-core/src/main/java/org/pkl/core/TestResults.java
Normal file
@@ -0,0 +1,301 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import org.pkl.core.util.Nullable;
|
||||
|
||||
/**
|
||||
* The results of testing a Pkl test module.
|
||||
*
|
||||
* <p>A test module is a module that amends {@code pkl:test}, and is evaluated with {@link
|
||||
* Evaluator#evaluateTest(ModuleSource, boolean)}.
|
||||
*
|
||||
* <p>Test results have two sections; facts, and examples. Each section has multiple individual
|
||||
* results, which themselves contain individual assertions.
|
||||
*
|
||||
* @since 0.27.0
|
||||
* @param moduleName The name of the module that was tested.
|
||||
* @param displayUri A location URI formatted to be displayed.
|
||||
* @param facts The result of testing facts.
|
||||
* @param examples The result of testing examples.
|
||||
* @param logs The log output resulting from running the test.
|
||||
* @param error An error that arose from evaluating the test module itself.
|
||||
* <p>If non-null, {@code facts} and {@code examples} are guaranteed to have 0 results.
|
||||
*/
|
||||
@SuppressWarnings("UnusedReturnValue")
|
||||
public record TestResults(
|
||||
String moduleName,
|
||||
String displayUri,
|
||||
TestSectionResults facts,
|
||||
TestSectionResults examples,
|
||||
String logs,
|
||||
@Nullable Error error) {
|
||||
|
||||
/** The total number of tests between facts and examples. */
|
||||
public int totalTests() {
|
||||
return facts.totalTests() + examples.totalTests();
|
||||
}
|
||||
|
||||
/**
|
||||
* The total number of failed {@linkplain TestResult test results}.
|
||||
*
|
||||
* <p>A {@link TestResult} has failed if any of its assertions have a {@link Failure} or an {@link
|
||||
* Error}.
|
||||
*/
|
||||
public int totalFailures() {
|
||||
return facts.totalFailures() + examples.totalFailures();
|
||||
}
|
||||
|
||||
/** The total number of assertions between facts and examples. */
|
||||
public int totalAsserts() {
|
||||
return facts.totalAsserts() + examples.totalAsserts();
|
||||
}
|
||||
|
||||
/**
|
||||
* The total number of individual assertions that have failed.
|
||||
*
|
||||
* <p>A single test can have multiple failed assertions.
|
||||
*/
|
||||
public int totalAssertsFailed() {
|
||||
return facts.totalAssertsFailedOrErrored() + examples.totalAssertsFailedOrErrored();
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the test in aggregate has failed or not.
|
||||
*
|
||||
* <p>An individual {@link TestResult} has failed if any of its assertions have a {@link Failure}
|
||||
* or an {@link Error}.
|
||||
*/
|
||||
public boolean failed() {
|
||||
return error != null || facts.failed() || examples.failed();
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the test result has failed due to examples being written.
|
||||
*
|
||||
* <p>Returns {@code true} if and only if there are failures, and all failures are due to examples
|
||||
* being written.
|
||||
*/
|
||||
public boolean isExampleWrittenFailure() {
|
||||
if (!failed() || !examples.failed()) return false;
|
||||
for (var testResult : examples.results) {
|
||||
if (!testResult.isExampleWritten) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static class Builder {
|
||||
|
||||
private final String moduleName;
|
||||
private final String displayUri;
|
||||
private TestSectionResults factsSection =
|
||||
new TestSectionResults(TestSectionName.FACTS, List.of());
|
||||
private TestSectionResults examplesSection =
|
||||
new TestSectionResults(TestSectionName.EXAMPLES, List.of());
|
||||
private String stdErr = "";
|
||||
private @Nullable Error error = null;
|
||||
|
||||
public Builder(String moduleName, String displayUri) {
|
||||
this.moduleName = moduleName;
|
||||
this.displayUri = displayUri;
|
||||
}
|
||||
|
||||
public Builder setError(Error error) {
|
||||
this.error = error;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setStdErr(String stdErr) {
|
||||
this.stdErr = stdErr;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setFactsSection(TestSectionResults factsSection) {
|
||||
assert factsSection.name() == TestSectionName.FACTS;
|
||||
this.factsSection = factsSection;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setExamplesSection(TestSectionResults examplesSection) {
|
||||
assert examplesSection.name() == TestSectionName.EXAMPLES;
|
||||
this.examplesSection = examplesSection;
|
||||
return this;
|
||||
}
|
||||
|
||||
public TestResults build() {
|
||||
return new TestResults(moduleName, displayUri, factsSection, examplesSection, stdErr, error);
|
||||
}
|
||||
}
|
||||
|
||||
public enum TestSectionName {
|
||||
FACTS("facts"),
|
||||
EXAMPLES("examples");
|
||||
|
||||
private final String name;
|
||||
|
||||
TestSectionName(final String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The results of either facts or examples.
|
||||
*
|
||||
* @param name The name of this section
|
||||
* @param results The results of the individual tests in this section.
|
||||
*/
|
||||
public record TestSectionResults(TestSectionName name, List<TestResult> results) {
|
||||
public int totalTests() {
|
||||
return results.size();
|
||||
}
|
||||
|
||||
public int totalAsserts() {
|
||||
var total = 0;
|
||||
for (var res : results) {
|
||||
total += res.totalAsserts();
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
public int totalAssertsFailedOrErrored() {
|
||||
var total = 0;
|
||||
for (var res : results) {
|
||||
total += res.totalAssertsFailedOrErrored();
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
public int totalFailures() {
|
||||
var total = 0;
|
||||
for (var res : results) {
|
||||
if (res.isFailure()) {
|
||||
total++;
|
||||
}
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
public boolean failed() {
|
||||
for (var res : results) {
|
||||
if (res.isFailure()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean hasError() {
|
||||
for (var res : results) {
|
||||
if (res.hasErrors()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The result of a single test.
|
||||
*
|
||||
* <p>A test can have multiple assertions, where the assertion itself can have failed, or have
|
||||
* thrown an exception.
|
||||
*
|
||||
* @param name The name of the test.
|
||||
* @param totalAsserts The total number of assertions in the test.
|
||||
* @param failures The number of assertion failures in the test.
|
||||
* @param errors The number of errors that were thrown when the test was run.
|
||||
* @param isExampleWritten Whether the test is considered as having its example written or not.
|
||||
*/
|
||||
public record TestResult(
|
||||
String name,
|
||||
int totalAsserts,
|
||||
List<Failure> failures,
|
||||
List<Error> errors,
|
||||
boolean isExampleWritten) {
|
||||
|
||||
public int totalAssertsFailedOrErrored() {
|
||||
return failures().size() + errors.size();
|
||||
}
|
||||
|
||||
public boolean isFailure() {
|
||||
return totalAssertsFailedOrErrored() > 0;
|
||||
}
|
||||
|
||||
public boolean hasErrors() {
|
||||
return !errors().isEmpty();
|
||||
}
|
||||
|
||||
public boolean hasFailures() {
|
||||
return !failures.isEmpty();
|
||||
}
|
||||
|
||||
public static class Builder {
|
||||
|
||||
private final String name;
|
||||
private final List<Failure> failures = new ArrayList<>();
|
||||
private final List<Error> errors = new ArrayList<>();
|
||||
private boolean isExampleWritten;
|
||||
private int count = 0;
|
||||
|
||||
public Builder(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public Builder addSuccess() {
|
||||
count++;
|
||||
return this;
|
||||
}
|
||||
|
||||
public int getCount() {
|
||||
return count;
|
||||
}
|
||||
|
||||
public Builder addFailure(Failure failure) {
|
||||
this.failures.add(failure);
|
||||
count++;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder addError(Error error) {
|
||||
this.errors.add(error);
|
||||
count++;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setExampleWritten(boolean exampleWritten) {
|
||||
this.isExampleWritten = exampleWritten;
|
||||
return this;
|
||||
}
|
||||
|
||||
public TestResult build() {
|
||||
return new TestResult(name, count, failures, errors, isExampleWritten);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public record Error(String message, PklException exception) {}
|
||||
|
||||
public record Failure(String kind, String message) {}
|
||||
}
|
||||
@@ -1,358 +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.core.runtime;
|
||||
|
||||
import com.oracle.truffle.api.source.SourceSection;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import org.pkl.core.PklException;
|
||||
import org.pkl.core.runtime.TestResults.TestSectionResults.TestSection;
|
||||
|
||||
/** Aggregate test results for a module. Used to verify test failures and generate reports. */
|
||||
public final class TestResults {
|
||||
public final String moduleName;
|
||||
public final String displayUri;
|
||||
public final TestSectionResults module = new TestSectionResults(TestSection.MODULE);
|
||||
public final TestSectionResults facts = new TestSectionResults(TestSection.FACTS);
|
||||
public final TestSectionResults examples = new TestSectionResults(TestSection.EXAMPLES);
|
||||
private String err = "";
|
||||
|
||||
public TestResults(String moduleName, String displayUri) {
|
||||
this.moduleName = moduleName;
|
||||
this.displayUri = displayUri;
|
||||
}
|
||||
|
||||
public int totalTests() {
|
||||
return module.totalTests() + facts.totalTests() + examples.totalTests();
|
||||
}
|
||||
|
||||
public int totalFailures() {
|
||||
return module.totalFailures() + facts.totalFailures() + examples.totalFailures();
|
||||
}
|
||||
|
||||
public int totalAsserts() {
|
||||
return module.totalAsserts() + facts.totalAsserts() + examples.totalAsserts();
|
||||
}
|
||||
|
||||
public int totalAssertsFailed() {
|
||||
return module.totalAssertsFailed() + facts.totalAssertsFailed() + examples.totalAssertsFailed();
|
||||
}
|
||||
|
||||
public boolean failed() {
|
||||
return module.failed() || facts.failed() || examples.failed();
|
||||
}
|
||||
|
||||
public String getErr() {
|
||||
return err;
|
||||
}
|
||||
|
||||
public void setErr(String err) {
|
||||
this.err = err;
|
||||
}
|
||||
|
||||
public static class TestSectionResults {
|
||||
public final TestSection name;
|
||||
private final List<TestResult> results = new ArrayList<>();
|
||||
private Error error;
|
||||
|
||||
public TestSectionResults(TestSection name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public void setError(Error error) {
|
||||
this.error = error;
|
||||
}
|
||||
|
||||
public Error getError() {
|
||||
return error;
|
||||
}
|
||||
|
||||
public boolean hasError() {
|
||||
return error != null;
|
||||
}
|
||||
|
||||
public List<TestResult> getResults() {
|
||||
return Collections.unmodifiableList(results);
|
||||
}
|
||||
|
||||
public TestResult newResult(String name) {
|
||||
var result = new TestResult(name, this.name == TestSection.EXAMPLES);
|
||||
results.add(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
public TestResult newResult(String name, Failure failure) {
|
||||
var result = new TestResult(name, this.name == TestSection.EXAMPLES);
|
||||
result.addFailure(failure);
|
||||
results.add(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
public TestResult newResult(String name, Error error) {
|
||||
var result = new TestResult(name, this.name == TestSection.EXAMPLES);
|
||||
result.addError(error);
|
||||
results.add(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
public int totalTests() {
|
||||
var total = results.size();
|
||||
return (hasError() ? ++total : total);
|
||||
}
|
||||
|
||||
public int totalAsserts() {
|
||||
int total = 0;
|
||||
for (var res : results) {
|
||||
total += res.totalAsserts();
|
||||
}
|
||||
return (hasError() ? ++total : total);
|
||||
}
|
||||
|
||||
public int totalAssertsFailed() {
|
||||
int total = 0;
|
||||
for (var res : results) {
|
||||
total += res.totalAssertsFailed();
|
||||
}
|
||||
return (hasError() ? ++total : total);
|
||||
}
|
||||
|
||||
public int totalFailures() {
|
||||
int total = 0;
|
||||
for (var res : results) {
|
||||
if (res.isFailure()) total++;
|
||||
}
|
||||
return (hasError() ? ++total : total);
|
||||
}
|
||||
|
||||
public boolean failed() {
|
||||
if (hasError()) return true;
|
||||
|
||||
for (var res : results) {
|
||||
if (res.isFailure()) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static class TestResult {
|
||||
public final String name;
|
||||
private int totalAsserts = 0;
|
||||
private int totalAssertsFailed = 0;
|
||||
private final List<Failure> failures = new ArrayList<>();
|
||||
private final List<Error> errors = new ArrayList<>();
|
||||
public final boolean isExample;
|
||||
private boolean isExampleWritten = false;
|
||||
|
||||
public TestResult(String name, boolean isExample) {
|
||||
this.name = name;
|
||||
this.isExample = isExample;
|
||||
}
|
||||
|
||||
public boolean isSuccess() {
|
||||
return failures.isEmpty() && errors.isEmpty();
|
||||
}
|
||||
|
||||
public boolean isFailure() {
|
||||
return !isSuccess();
|
||||
}
|
||||
|
||||
public boolean isExampleWritten() {
|
||||
return isExampleWritten;
|
||||
}
|
||||
|
||||
public void setExampleWritten(boolean exampleWritten) {
|
||||
isExampleWritten = exampleWritten;
|
||||
}
|
||||
|
||||
public List<Failure> getFailures() {
|
||||
return Collections.unmodifiableList(failures);
|
||||
}
|
||||
|
||||
public void addFailure(Failure description) {
|
||||
failures.add(description);
|
||||
totalAssertsFailed++;
|
||||
}
|
||||
|
||||
public List<Error> getErrors() {
|
||||
return Collections.unmodifiableList(errors);
|
||||
}
|
||||
|
||||
public void addError(Error err) {
|
||||
errors.add(err);
|
||||
totalAssertsFailed++;
|
||||
}
|
||||
|
||||
public int totalAsserts() {
|
||||
return totalAsserts;
|
||||
}
|
||||
|
||||
public void countAssert() {
|
||||
totalAsserts++;
|
||||
}
|
||||
|
||||
public int totalAssertsFailed() {
|
||||
return totalAssertsFailed;
|
||||
}
|
||||
}
|
||||
|
||||
public static class Failure {
|
||||
|
||||
private final String kind;
|
||||
private final String failure;
|
||||
private final String location;
|
||||
|
||||
private Failure(String kind, String failure, String location) {
|
||||
this.kind = kind;
|
||||
this.failure = failure;
|
||||
this.location = location;
|
||||
}
|
||||
|
||||
public String getKind() {
|
||||
return kind;
|
||||
}
|
||||
|
||||
public String getFailure() {
|
||||
return failure;
|
||||
}
|
||||
|
||||
public String getLocation() {
|
||||
return location;
|
||||
}
|
||||
|
||||
public static String renderLocation(String location) {
|
||||
return "(" + location + ")";
|
||||
}
|
||||
|
||||
public String getRendered() {
|
||||
String rendered;
|
||||
|
||||
if (kind == "Fact Failure") {
|
||||
rendered = failure + " " + renderLocation(getLocation());
|
||||
} else {
|
||||
rendered = renderLocation(getLocation()) + "\n" + failure;
|
||||
}
|
||||
|
||||
return rendered;
|
||||
}
|
||||
|
||||
public static Failure buildFactFailure(SourceSection sourceSection, String location) {
|
||||
return new Failure("Fact Failure", sourceSection.getCharacters().toString(), location);
|
||||
}
|
||||
|
||||
public static Failure buildExampleLengthMismatchFailure(
|
||||
String location, String property, int expectedLength, int actualLength) {
|
||||
var builder = new StringBuilder();
|
||||
builder
|
||||
.append("Output mismatch: Expected \"")
|
||||
.append(property)
|
||||
.append("\" to contain ")
|
||||
.append(expectedLength)
|
||||
.append(" examples, but found ")
|
||||
.append(actualLength);
|
||||
|
||||
return new Failure("Output Mismatch (Length)", builder.toString(), location);
|
||||
}
|
||||
|
||||
public static Failure buildExamplePropertyMismatchFailure(
|
||||
String location, String property, boolean isMissingInExpected) {
|
||||
|
||||
String exists_in;
|
||||
String missing_in;
|
||||
|
||||
if (isMissingInExpected) {
|
||||
exists_in = "actual";
|
||||
missing_in = "expected";
|
||||
} else {
|
||||
exists_in = "expected";
|
||||
missing_in = "actual";
|
||||
}
|
||||
|
||||
var builder = new StringBuilder();
|
||||
builder
|
||||
.append("Output mismatch: \"")
|
||||
.append(property)
|
||||
.append("\" exists in ")
|
||||
.append(exists_in)
|
||||
.append(" but not in ")
|
||||
.append(missing_in)
|
||||
.append(" output");
|
||||
|
||||
return new Failure("Output Mismatch", builder.toString(), location);
|
||||
}
|
||||
|
||||
public static Failure buildExampleFailure(
|
||||
String location,
|
||||
String expectedLocation,
|
||||
String expectedValue,
|
||||
String actualLocation,
|
||||
String actualValue) {
|
||||
var builder = new StringBuilder();
|
||||
builder
|
||||
.append("Expected: ")
|
||||
.append(renderLocation(expectedLocation))
|
||||
.append("\n")
|
||||
.append(expectedValue)
|
||||
.append("\n")
|
||||
.append("Actual: ")
|
||||
.append(renderLocation(actualLocation))
|
||||
.append("\n")
|
||||
.append(actualValue);
|
||||
|
||||
return new Failure("Example Failure", builder.toString(), location);
|
||||
}
|
||||
}
|
||||
|
||||
public static class Error {
|
||||
|
||||
private final String message;
|
||||
private final PklException exception;
|
||||
|
||||
public Error(String message, PklException exception) {
|
||||
this.message = message;
|
||||
this.exception = exception;
|
||||
}
|
||||
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
|
||||
public Exception getException() {
|
||||
return exception;
|
||||
}
|
||||
|
||||
public String getRendered() {
|
||||
return exception.getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
public enum TestSection {
|
||||
MODULE("module"),
|
||||
FACTS("facts"),
|
||||
EXAMPLES("examples");
|
||||
|
||||
private final String name;
|
||||
|
||||
TestSection(final String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,17 +15,23 @@
|
||||
*/
|
||||
package org.pkl.core.runtime;
|
||||
|
||||
import com.oracle.truffle.api.source.SourceSection;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import org.pkl.core.BufferedLogger;
|
||||
import org.pkl.core.StackFrameTransformer;
|
||||
import org.pkl.core.TestResults;
|
||||
import org.pkl.core.TestResults.Failure;
|
||||
import org.pkl.core.TestResults.TestResult;
|
||||
import org.pkl.core.TestResults.TestSectionName;
|
||||
import org.pkl.core.TestResults.TestSectionResults;
|
||||
import org.pkl.core.ast.member.ObjectMember;
|
||||
import org.pkl.core.module.ModuleKeys;
|
||||
import org.pkl.core.runtime.TestResults.TestSectionResults;
|
||||
import org.pkl.core.runtime.TestResults.TestSectionResults.Error;
|
||||
import org.pkl.core.runtime.TestResults.TestSectionResults.Failure;
|
||||
import org.pkl.core.stdlib.PklConverter;
|
||||
import org.pkl.core.stdlib.base.PcfRenderer;
|
||||
import org.pkl.core.util.EconomicMaps;
|
||||
@@ -48,31 +54,20 @@ public final class TestRunner {
|
||||
|
||||
public TestResults run(VmTyped testModule) {
|
||||
var info = VmUtils.getModuleInfo(testModule);
|
||||
var results = new TestResults(info.getModuleName(), getDisplayUri(info));
|
||||
var resultsBuilder = new TestResults.Builder(info.getModuleName(), getDisplayUri(info));
|
||||
|
||||
try {
|
||||
checkAmendsPklTest(testModule);
|
||||
} catch (VmException v) {
|
||||
var error = new Error(v.getMessage(), v.toPklException(stackFrameTransformer));
|
||||
results.module.setError(error);
|
||||
var error = new TestResults.Error(v.getMessage(), v.toPklException(stackFrameTransformer));
|
||||
return resultsBuilder.setError(error).build();
|
||||
}
|
||||
|
||||
try {
|
||||
runFacts(testModule, results.facts);
|
||||
} catch (VmException v) {
|
||||
var error = new Error(v.getMessage(), v.toPklException(stackFrameTransformer));
|
||||
results.facts.setError(error);
|
||||
}
|
||||
resultsBuilder.setFactsSection(runFacts(testModule));
|
||||
resultsBuilder.setExamplesSection(runExamples(testModule, info));
|
||||
|
||||
try {
|
||||
runExamples(testModule, info, results.examples);
|
||||
} catch (VmException v) {
|
||||
var error = new Error(v.getMessage(), v.toPklException(stackFrameTransformer));
|
||||
results.examples.setError(error);
|
||||
}
|
||||
|
||||
results.setErr(logger.getLogs());
|
||||
return results;
|
||||
resultsBuilder.setStdErr(logger.getLogs());
|
||||
return resultsBuilder.build();
|
||||
}
|
||||
|
||||
private void checkAmendsPklTest(VmTyped value) {
|
||||
@@ -86,41 +81,48 @@ public final class TestRunner {
|
||||
}
|
||||
}
|
||||
|
||||
private void runFacts(VmTyped testModule, TestSectionResults results) {
|
||||
private TestSectionResults runFacts(VmTyped testModule) {
|
||||
var facts = VmUtils.readMember(testModule, Identifier.FACTS);
|
||||
if (facts instanceof VmNull) return;
|
||||
if (facts instanceof VmNull) return new TestSectionResults(TestSectionName.FACTS, List.of());
|
||||
|
||||
var testResults = new ArrayList<TestResult>();
|
||||
var factsMapping = (VmMapping) facts;
|
||||
factsMapping.forceAndIterateMemberValues(
|
||||
(groupKey, groupMember, groupValue) -> {
|
||||
var listing = (VmListing) groupValue;
|
||||
var result = results.newResult(String.valueOf(groupKey));
|
||||
return listing.iterateMembers(
|
||||
var name = String.valueOf(groupKey);
|
||||
var resultBuilder = new TestResult.Builder(name);
|
||||
listing.iterateMembers(
|
||||
(idx, member) -> {
|
||||
if (member.isLocalOrExternalOrHidden()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
result.countAssert();
|
||||
|
||||
try {
|
||||
var factValue = VmUtils.readMember(listing, idx);
|
||||
if (factValue == Boolean.FALSE) {
|
||||
result.addFailure(
|
||||
Failure.buildFactFailure(member.getSourceSection(), getDisplayUri(member)));
|
||||
var failure = factFailure(member.getSourceSection(), getDisplayUri(member));
|
||||
resultBuilder.addFailure(failure);
|
||||
} else {
|
||||
resultBuilder.addSuccess();
|
||||
}
|
||||
} catch (VmException err) {
|
||||
result.addError(
|
||||
new Error(err.getMessage(), err.toPklException(stackFrameTransformer)));
|
||||
var error =
|
||||
new TestResults.Error(
|
||||
err.getMessage(), err.toPklException(stackFrameTransformer));
|
||||
resultBuilder.addError(error);
|
||||
}
|
||||
return true;
|
||||
});
|
||||
testResults.add(resultBuilder.build());
|
||||
return true;
|
||||
});
|
||||
return new TestSectionResults(TestSectionName.FACTS, Collections.unmodifiableList(testResults));
|
||||
}
|
||||
|
||||
private void runExamples(VmTyped testModule, ModuleInfo info, TestSectionResults results) {
|
||||
private TestSectionResults runExamples(VmTyped testModule, ModuleInfo info) {
|
||||
var examples = VmUtils.readMember(testModule, Identifier.EXAMPLES);
|
||||
if (examples instanceof VmNull) return;
|
||||
if (examples instanceof VmNull)
|
||||
return new TestSectionResults(TestSectionName.EXAMPLES, List.of());
|
||||
|
||||
var moduleUri = info.getModuleKey().getUri();
|
||||
if (!moduleUri.getScheme().equalsIgnoreCase("file")) {
|
||||
@@ -154,72 +156,59 @@ public final class TestRunner {
|
||||
}
|
||||
|
||||
if (Files.exists(expectedOutputFile)) {
|
||||
doRunAndValidateExamples(examplesMapping, expectedOutputFile, actualOutputFile, results);
|
||||
return doRunAndValidateExamples(examplesMapping, expectedOutputFile, actualOutputFile);
|
||||
} else {
|
||||
doRunAndWriteExamples(examplesMapping, expectedOutputFile, results);
|
||||
return doRunAndWriteExamples(examplesMapping, expectedOutputFile);
|
||||
}
|
||||
}
|
||||
|
||||
private void doRunAndValidateExamples(
|
||||
VmMapping examples,
|
||||
Path expectedOutputFile,
|
||||
Path actualOutputFile,
|
||||
TestSectionResults results) {
|
||||
private TestSectionResults doRunAndValidateExamples(
|
||||
VmMapping examples, Path expectedOutputFile, Path actualOutputFile) {
|
||||
var expectedExampleOutputs = loadExampleOutputs(expectedOutputFile);
|
||||
var actualExampleOutputs = new MutableReference<VmDynamic>(null);
|
||||
var allGroupsSucceeded = new MutableBoolean(true);
|
||||
var errored = new MutableBoolean(false);
|
||||
var testResults = new ArrayList<TestResult>();
|
||||
examples.forceAndIterateMemberValues(
|
||||
(groupKey, groupMember, groupValue) -> {
|
||||
var testName = String.valueOf(groupKey);
|
||||
var group = (VmListing) groupValue;
|
||||
var expectedGroup =
|
||||
(VmDynamic) VmUtils.readMemberOrNull(expectedExampleOutputs, groupKey);
|
||||
var testResultBuilder = new TestResult.Builder(testName);
|
||||
|
||||
if (expectedGroup == null) {
|
||||
results
|
||||
.newResult(
|
||||
testName,
|
||||
Failure.buildExamplePropertyMismatchFailure(
|
||||
getDisplayUri(groupMember), testName, true))
|
||||
.countAssert();
|
||||
testResultBuilder.addFailure(
|
||||
examplePropertyMismatchFailure(getDisplayUri(groupMember), testName, true));
|
||||
testResults.add(testResultBuilder.build());
|
||||
return true;
|
||||
}
|
||||
|
||||
if (group.getLength() != expectedGroup.getLength()) {
|
||||
results
|
||||
.newResult(
|
||||
testResultBuilder.addFailure(
|
||||
exampleLengthMismatchFailure(
|
||||
getDisplayUri(groupMember),
|
||||
testName,
|
||||
Failure.buildExampleLengthMismatchFailure(
|
||||
getDisplayUri(groupMember),
|
||||
testName,
|
||||
expectedGroup.getLength(),
|
||||
group.getLength()))
|
||||
.countAssert();
|
||||
expectedGroup.getLength(),
|
||||
group.getLength()));
|
||||
testResults.add(testResultBuilder.build());
|
||||
return true;
|
||||
}
|
||||
|
||||
var groupSucceeded = new MutableBoolean(true);
|
||||
group.iterateMembers(
|
||||
((exampleIndex, exampleMember) -> {
|
||||
if (exampleMember.isLocalOrExternalOrHidden()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
var exampleName =
|
||||
group.getLength() == 1 ? testName : testName + " #" + exampleIndex;
|
||||
|
||||
Object exampleValue;
|
||||
try {
|
||||
exampleValue = VmUtils.readMember(group, exampleIndex);
|
||||
} catch (VmException err) {
|
||||
errored.set(true);
|
||||
results
|
||||
.newResult(
|
||||
exampleName,
|
||||
new Error(err.getMessage(), err.toPklException(stackFrameTransformer)))
|
||||
.countAssert();
|
||||
groupSucceeded.set(false);
|
||||
testResultBuilder.addError(
|
||||
new TestResults.Error(
|
||||
err.getMessage(), err.toPklException(stackFrameTransformer)));
|
||||
return true;
|
||||
}
|
||||
var expectedValue = VmUtils.readMember(expectedGroup, exampleIndex);
|
||||
@@ -236,8 +225,6 @@ public final class TestRunner {
|
||||
actualExampleOutputs.set(loadExampleOutputs(actualOutputFile));
|
||||
}
|
||||
|
||||
groupSucceeded.set(false);
|
||||
|
||||
var expectedMember = VmUtils.findMember(expectedGroup, exampleIndex);
|
||||
assert expectedMember != null;
|
||||
|
||||
@@ -253,26 +240,22 @@ public final class TestRunner {
|
||||
.build();
|
||||
}
|
||||
|
||||
results
|
||||
.newResult(
|
||||
exampleName,
|
||||
Failure.buildExampleFailure(
|
||||
getDisplayUri(exampleMember),
|
||||
getDisplayUri(expectedMember),
|
||||
expectedValuePcf,
|
||||
getDisplayUri(actualMember),
|
||||
exampleValuePcf))
|
||||
.countAssert();
|
||||
testResultBuilder.addFailure(
|
||||
exampleFailure(
|
||||
getDisplayUri(exampleMember),
|
||||
getDisplayUri(expectedMember),
|
||||
expectedValuePcf,
|
||||
getDisplayUri(actualMember),
|
||||
exampleValuePcf,
|
||||
testResultBuilder.getCount()));
|
||||
} else {
|
||||
results.newResult(exampleName).countAssert();
|
||||
testResultBuilder.addSuccess();
|
||||
}
|
||||
|
||||
return true;
|
||||
}));
|
||||
|
||||
if (!groupSucceeded.get()) {
|
||||
allGroupsSucceeded.set(false);
|
||||
}
|
||||
testResults.add(testResultBuilder.build());
|
||||
|
||||
return true;
|
||||
});
|
||||
@@ -285,12 +268,12 @@ public final class TestRunner {
|
||||
if (examples.getCachedValue(groupKey) == null) {
|
||||
var testName = String.valueOf(groupKey);
|
||||
allGroupsSucceeded.set(false);
|
||||
results
|
||||
.newResult(
|
||||
testName,
|
||||
Failure.buildExamplePropertyMismatchFailure(
|
||||
getDisplayUri(groupMember), testName, false))
|
||||
.countAssert();
|
||||
var result =
|
||||
new TestResult.Builder(testName)
|
||||
.addFailure(
|
||||
examplePropertyMismatchFailure(getDisplayUri(groupMember), testName, false))
|
||||
.build();
|
||||
testResults.add(result);
|
||||
}
|
||||
return true;
|
||||
});
|
||||
@@ -298,49 +281,50 @@ public final class TestRunner {
|
||||
if (!allGroupsSucceeded.get() && actualExampleOutputs.isNull() && !errored.get()) {
|
||||
writeExampleOutputs(actualOutputFile, examples);
|
||||
}
|
||||
return new TestSectionResults(
|
||||
TestSectionName.EXAMPLES, Collections.unmodifiableList(testResults));
|
||||
}
|
||||
|
||||
private void doRunAndWriteExamples(
|
||||
VmMapping examples, Path outputFile, TestSectionResults results) {
|
||||
var allSucceeded =
|
||||
examples.forceAndIterateMemberValues(
|
||||
(groupKey, groupMember, groupValue) -> {
|
||||
var testName = String.valueOf(groupKey);
|
||||
var listing = (VmListing) groupValue;
|
||||
var success =
|
||||
listing.iterateMembers(
|
||||
(idx, member) -> {
|
||||
if (member.isLocalOrExternalOrHidden()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
var exampleName =
|
||||
listing.getLength() == 1 ? testName : testName + " #" + idx;
|
||||
|
||||
try {
|
||||
VmUtils.readMember(listing, idx);
|
||||
return true;
|
||||
} catch (VmException err) {
|
||||
results
|
||||
.newResult(
|
||||
exampleName,
|
||||
new Error(
|
||||
err.getMessage(), err.toPklException(stackFrameTransformer)))
|
||||
.countAssert();
|
||||
return false;
|
||||
}
|
||||
});
|
||||
if (!success) {
|
||||
return false;
|
||||
}
|
||||
var result = results.newResult(testName);
|
||||
result.countAssert();
|
||||
result.setExampleWritten(true);
|
||||
return true;
|
||||
});
|
||||
if (allSucceeded) {
|
||||
private TestSectionResults doRunAndWriteExamples(VmMapping examples, Path outputFile) {
|
||||
var testResults = new ArrayList<TestResult>();
|
||||
var allSucceeded = new MutableBoolean(true);
|
||||
examples.forceAndIterateMemberValues(
|
||||
(groupKey, groupMember, groupValue) -> {
|
||||
var testName = String.valueOf(groupKey);
|
||||
var listing = (VmListing) groupValue;
|
||||
var testResultBuilder = new TestResult.Builder(testName);
|
||||
var success = new MutableBoolean(true);
|
||||
listing.iterateMembers(
|
||||
(idx, member) -> {
|
||||
if (member.isLocalOrExternalOrHidden()) {
|
||||
return true;
|
||||
}
|
||||
try {
|
||||
VmUtils.readMember(listing, idx);
|
||||
return true;
|
||||
} catch (VmException err) {
|
||||
testResultBuilder.addError(
|
||||
new TestResults.Error(
|
||||
err.getMessage(), err.toPklException(stackFrameTransformer)));
|
||||
allSucceeded.set(false);
|
||||
success.set(false);
|
||||
return true;
|
||||
}
|
||||
});
|
||||
if (success.get()) {
|
||||
// treat writing an example as a message
|
||||
testResultBuilder.setExampleWritten(true);
|
||||
testResultBuilder.addFailure(
|
||||
writtenExampleOutputFailure(testName, getDisplayUri(groupMember)));
|
||||
}
|
||||
testResults.add(testResultBuilder.build());
|
||||
return true;
|
||||
});
|
||||
if (allSucceeded.get()) {
|
||||
writeExampleOutputs(outputFile, examples);
|
||||
}
|
||||
return new TestSectionResults(
|
||||
TestSectionName.EXAMPLES, Collections.unmodifiableList(testResults));
|
||||
}
|
||||
|
||||
private void writeExampleOutputs(Path outputFile, VmMapping examples) {
|
||||
@@ -402,4 +386,87 @@ public final class TestRunner {
|
||||
return VmUtils.getDisplayUri(
|
||||
moduleInfo.getModuleKey().getUri(), VmContext.get(null).getFrameTransformer());
|
||||
}
|
||||
|
||||
private static Failure factFailure(SourceSection sourceSection, String location) {
|
||||
String message = sourceSection.getCharacters().toString() + " " + renderLocation(location);
|
||||
return new Failure("Fact Failure", message);
|
||||
}
|
||||
|
||||
private static Failure exampleLengthMismatchFailure(
|
||||
String location, String property, int expectedLength, int actualLength) {
|
||||
String msg =
|
||||
renderLocation(location)
|
||||
+ "\n"
|
||||
+ "Output mismatch: Expected \""
|
||||
+ property
|
||||
+ "\" to contain "
|
||||
+ expectedLength
|
||||
+ " examples, but found "
|
||||
+ actualLength;
|
||||
|
||||
return new Failure("Output Mismatch (Length)", msg);
|
||||
}
|
||||
|
||||
private static Failure examplePropertyMismatchFailure(
|
||||
String location, String property, boolean isMissingInExpected) {
|
||||
|
||||
String existsIn;
|
||||
String missingIn;
|
||||
|
||||
if (isMissingInExpected) {
|
||||
existsIn = "actual";
|
||||
missingIn = "expected";
|
||||
} else {
|
||||
existsIn = "expected";
|
||||
missingIn = "actual";
|
||||
}
|
||||
|
||||
String message =
|
||||
renderLocation(location)
|
||||
+ "\n"
|
||||
+ "Output mismatch: \""
|
||||
+ property
|
||||
+ "\" exists in "
|
||||
+ existsIn
|
||||
+ " but not in "
|
||||
+ missingIn
|
||||
+ " output";
|
||||
|
||||
return new Failure("Output Mismatch", message);
|
||||
}
|
||||
|
||||
private static Failure exampleFailure(
|
||||
String location,
|
||||
String expectedLocation,
|
||||
String expectedValue,
|
||||
String actualLocation,
|
||||
String actualValue,
|
||||
int exampleNumber) {
|
||||
String err =
|
||||
"#"
|
||||
+ exampleNumber
|
||||
+ " "
|
||||
+ renderLocation(location)
|
||||
+ ":\n "
|
||||
+ "Expected: "
|
||||
+ renderLocation(expectedLocation)
|
||||
+ "\n "
|
||||
+ expectedValue.replaceAll("\n", "\n ")
|
||||
+ "\n "
|
||||
+ "Actual: "
|
||||
+ renderLocation(actualLocation)
|
||||
+ "\n "
|
||||
+ actualValue.replaceAll("\n", "\n ");
|
||||
|
||||
return new Failure("Example Failure", err);
|
||||
}
|
||||
|
||||
private static String renderLocation(String location) {
|
||||
return "(" + location + ")";
|
||||
}
|
||||
|
||||
private static Failure writtenExampleOutputFailure(String testName, String location) {
|
||||
var message = renderLocation(location) + "\n" + "Wrote expected output for test " + testName;
|
||||
return new Failure("Example Output Written", message);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,13 +20,13 @@ import java.io.Writer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.function.Consumer;
|
||||
import org.graalvm.collections.EconomicMap;
|
||||
import org.pkl.core.TestResults;
|
||||
import org.pkl.core.TestResults.Error;
|
||||
import org.pkl.core.TestResults.TestResult;
|
||||
import org.pkl.core.TestResults.TestSectionResults;
|
||||
import org.pkl.core.ast.member.ObjectMember;
|
||||
import org.pkl.core.runtime.BaseModule;
|
||||
import org.pkl.core.runtime.Identifier;
|
||||
import org.pkl.core.runtime.TestResults;
|
||||
import org.pkl.core.runtime.TestResults.TestSectionResults;
|
||||
import org.pkl.core.runtime.TestResults.TestSectionResults.Error;
|
||||
import org.pkl.core.runtime.TestResults.TestSectionResults.TestResult;
|
||||
import org.pkl.core.runtime.VmDynamic;
|
||||
import org.pkl.core.runtime.VmMapping;
|
||||
import org.pkl.core.runtime.VmTyped;
|
||||
@@ -44,44 +44,51 @@ public final class JUnitReport implements TestReport {
|
||||
}
|
||||
|
||||
private VmDynamic buildSuite(TestResults results) {
|
||||
var testCases = testCases(results.moduleName, results.facts);
|
||||
testCases.addAll(testCases(results.moduleName, results.examples));
|
||||
if (results.error() != null) {
|
||||
var testCase = rootTestCase(results, results.error());
|
||||
var attrs =
|
||||
buildAttributes(
|
||||
"name", results.moduleName(),
|
||||
"tests", 1,
|
||||
"failures", 1);
|
||||
return buildXmlElement("testsuite", attrs, testCase);
|
||||
}
|
||||
|
||||
if (!results.getErr().isBlank()) {
|
||||
var testCases = testCases(results.moduleName(), results.facts());
|
||||
testCases.addAll(testCases(results.moduleName(), results.examples()));
|
||||
|
||||
if (!results.logs().isBlank()) {
|
||||
var err =
|
||||
buildXmlElement(
|
||||
"system-err",
|
||||
VmMapping.empty(),
|
||||
members -> members.put("body", syntheticElement(makeCdata(results.getErr()))));
|
||||
members -> members.put("body", syntheticElement(makeCdata(results.logs()))));
|
||||
testCases.add(err);
|
||||
}
|
||||
|
||||
var attrs =
|
||||
buildAttributes(
|
||||
"name", results.moduleName,
|
||||
"name", results.moduleName(),
|
||||
"tests", (long) results.totalTests(),
|
||||
"failures", (long) results.totalFailures());
|
||||
|
||||
return buildXmlElement("testsuite", attrs, testCases.toArray(new VmDynamic[0]));
|
||||
}
|
||||
|
||||
private VmDynamic rootTestCase(TestResults results, TestResults.Error error) {
|
||||
var testCaseAttrs =
|
||||
buildAttributes("classname", results.moduleName(), "name", results.moduleName());
|
||||
var err = error(error);
|
||||
return buildXmlElement("testcase", testCaseAttrs, err.toArray(new VmDynamic[0]));
|
||||
}
|
||||
|
||||
private ArrayList<VmDynamic> testCases(String moduleName, TestSectionResults testSectionResults) {
|
||||
var elements = new ArrayList<VmDynamic>(testSectionResults.totalTests());
|
||||
|
||||
if (testSectionResults.hasError()) {
|
||||
var error = error(testSectionResults.getError());
|
||||
|
||||
var attrs =
|
||||
buildAttributes("classname", moduleName + "." + testSectionResults.name, "name", "error");
|
||||
var element = buildXmlElement("testcase", attrs, error.toArray(new VmDynamic[0]));
|
||||
|
||||
elements.add(element);
|
||||
}
|
||||
|
||||
for (var res : testSectionResults.getResults()) {
|
||||
for (var res : testSectionResults.results()) {
|
||||
var attrs =
|
||||
buildAttributes(
|
||||
"classname", moduleName + "." + testSectionResults.name, "name", res.name);
|
||||
"classname", moduleName + "." + testSectionResults.name(), "name", res.name());
|
||||
var failures = failures(res);
|
||||
failures.addAll(errors(res));
|
||||
var element = buildXmlElement("testcase", attrs, failures.toArray(new VmDynamic[0]));
|
||||
@@ -93,14 +100,12 @@ public final class JUnitReport implements TestReport {
|
||||
private ArrayList<VmDynamic> failures(TestResult res) {
|
||||
var list = new ArrayList<VmDynamic>();
|
||||
long i = 0;
|
||||
for (var fail : res.getFailures()) {
|
||||
var attrs = buildAttributes("message", fail.getKind());
|
||||
for (var fail : res.failures()) {
|
||||
var attrs = buildAttributes("message", fail.kind());
|
||||
long element = i++;
|
||||
list.add(
|
||||
buildXmlElement(
|
||||
"failure",
|
||||
attrs,
|
||||
members -> members.put(element, syntheticElement(fail.getRendered()))));
|
||||
"failure", attrs, members -> members.put(element, syntheticElement(fail.message()))));
|
||||
}
|
||||
return list;
|
||||
}
|
||||
@@ -108,27 +113,26 @@ public final class JUnitReport implements TestReport {
|
||||
private ArrayList<VmDynamic> errors(TestResult res) {
|
||||
var list = new ArrayList<VmDynamic>();
|
||||
long i = 0;
|
||||
for (var error : res.getErrors()) {
|
||||
var attrs = buildAttributes("message", error.getMessage());
|
||||
for (var error : res.errors()) {
|
||||
var attrs = buildAttributes("message", error.message());
|
||||
long element = i++;
|
||||
list.add(
|
||||
buildXmlElement(
|
||||
"error",
|
||||
attrs,
|
||||
members ->
|
||||
members.put(element, syntheticElement(error.getException().getMessage()))));
|
||||
members -> members.put(element, syntheticElement(error.exception().getMessage()))));
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
private ArrayList<VmDynamic> error(Error error) {
|
||||
var list = new ArrayList<VmDynamic>();
|
||||
var attrs = buildAttributes("message", error.getMessage());
|
||||
var attrs = buildAttributes("message", error.message());
|
||||
list.add(
|
||||
buildXmlElement(
|
||||
"error",
|
||||
attrs,
|
||||
members -> members.put(1, syntheticElement("\n" + error.getRendered()))));
|
||||
members -> members.put(1, syntheticElement("\n" + error.exception().getMessage()))));
|
||||
return list;
|
||||
}
|
||||
|
||||
|
||||
@@ -18,9 +18,9 @@ package org.pkl.core.stdlib.test.report;
|
||||
import java.io.IOException;
|
||||
import java.io.Writer;
|
||||
import java.util.stream.Collectors;
|
||||
import org.pkl.core.runtime.TestResults;
|
||||
import org.pkl.core.runtime.TestResults.TestSectionResults;
|
||||
import org.pkl.core.runtime.TestResults.TestSectionResults.TestResult;
|
||||
import org.pkl.core.TestResults;
|
||||
import org.pkl.core.TestResults.TestResult;
|
||||
import org.pkl.core.TestResults.TestSectionResults;
|
||||
import org.pkl.core.util.StringUtils;
|
||||
|
||||
public final class SimpleReport implements TestReport {
|
||||
@@ -29,10 +29,22 @@ public final class SimpleReport implements TestReport {
|
||||
public void report(TestResults results, Writer writer) throws IOException {
|
||||
var builder = new StringBuilder();
|
||||
|
||||
builder.append("module ").append(results.moduleName).append("\n");
|
||||
builder.append("module ").append(results.moduleName()).append("\n");
|
||||
|
||||
reportResults(results.facts, builder);
|
||||
reportResults(results.examples, builder);
|
||||
if (results.error() != null) {
|
||||
var rendered = results.error().exception().getMessage();
|
||||
appendPadded(builder, rendered, " ");
|
||||
builder.append('\n');
|
||||
} else {
|
||||
reportResults(results.facts(), builder);
|
||||
reportResults(results.examples(), builder);
|
||||
}
|
||||
|
||||
if (results.isExampleWrittenFailure()) {
|
||||
builder.append(results.examples().totalFailures()).append(" examples written\n");
|
||||
writer.append(builder);
|
||||
return;
|
||||
}
|
||||
|
||||
builder.append(results.failed() ? "❌ " : "✅ ");
|
||||
|
||||
@@ -51,16 +63,11 @@ public final class SimpleReport implements TestReport {
|
||||
}
|
||||
|
||||
private void reportResults(TestSectionResults section, StringBuilder builder) {
|
||||
if (!section.getResults().isEmpty()) {
|
||||
builder.append(" ").append(section.name).append("\n");
|
||||
if (!section.results().isEmpty()) {
|
||||
builder.append(" ").append(section.name()).append("\n");
|
||||
|
||||
StringUtils.joinToStringBuilder(
|
||||
builder, section.getResults(), "\n", res -> reportResult(res, builder));
|
||||
builder.append("\n");
|
||||
} else if (section.hasError()) {
|
||||
builder.append(" ").append(section.name).append("\n");
|
||||
var error = section.getError().getRendered();
|
||||
appendPadded(builder, error, " ");
|
||||
builder, section.results(), "\n", res -> reportResult(res, builder));
|
||||
builder.append("\n");
|
||||
}
|
||||
}
|
||||
@@ -69,23 +76,22 @@ public final class SimpleReport implements TestReport {
|
||||
builder.append(" ");
|
||||
|
||||
if (result.isExampleWritten()) {
|
||||
builder.append(result.name).append(" ✍️");
|
||||
builder.append("✍️ ").append(result.name());
|
||||
} else {
|
||||
builder.append(result.isFailure() ? "❌ " : "✅ ").append(result.name);
|
||||
|
||||
builder.append(result.isFailure() ? "❌ " : "✅ ").append(result.name());
|
||||
if (result.isFailure()) {
|
||||
var failurePadding = " ";
|
||||
builder.append("\n");
|
||||
StringUtils.joinToStringBuilder(
|
||||
builder,
|
||||
result.getFailures(),
|
||||
result.failures(),
|
||||
"\n",
|
||||
failure -> appendPadded(builder, failure.getRendered(), failurePadding));
|
||||
failure -> appendPadded(builder, failure.message(), failurePadding));
|
||||
StringUtils.joinToStringBuilder(
|
||||
builder,
|
||||
result.getErrors(),
|
||||
result.errors(),
|
||||
"\n",
|
||||
error -> appendPadded(builder, error.getException().getMessage(), failurePadding));
|
||||
error -> appendPadded(builder, error.exception().getMessage(), failurePadding));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -96,15 +102,15 @@ public final class SimpleReport implements TestReport {
|
||||
lines.lines().collect(Collectors.toList()),
|
||||
"\n",
|
||||
str -> {
|
||||
if (str.length() > 0) builder.append(padding).append(str);
|
||||
if (!str.isEmpty()) builder.append(padding).append(str);
|
||||
});
|
||||
}
|
||||
|
||||
private String makeStatsLine(String kind, int total, int failed, boolean isFailed) {
|
||||
var passed = total - failed;
|
||||
var pct_passed = total > 0 ? 100.0 * passed / total : 0.0;
|
||||
var passRate = total > 0 ? 100.0 * passed / total : 0.0;
|
||||
|
||||
String line = String.format("%.1f%% %s pass", pct_passed, kind);
|
||||
String line = String.format("%.1f%% %s pass", passRate, kind);
|
||||
|
||||
if (isFailed) {
|
||||
line += String.format(" [%d/%d failed]", failed, total);
|
||||
|
||||
@@ -21,7 +21,7 @@ import java.io.Writer;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Path;
|
||||
import org.pkl.core.PklBugException;
|
||||
import org.pkl.core.runtime.TestResults;
|
||||
import org.pkl.core.TestResults;
|
||||
import org.pkl.core.util.StringBuilderWriter;
|
||||
|
||||
public interface TestReport {
|
||||
|
||||
@@ -54,8 +54,8 @@ class EvaluateTestsTest {
|
||||
assertThat(results.displayUri).isEqualTo("repl:text")
|
||||
assertThat(results.totalTests()).isEqualTo(1)
|
||||
assertThat(results.failed()).isFalse
|
||||
assertThat(results.facts.results[0].name).isEqualTo("should pass")
|
||||
assertThat(results.err.isBlank()).isTrue
|
||||
assertThat(results.facts().results[0].name).isEqualTo("should pass")
|
||||
assertThat(results.logs().isBlank()).isTrue
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -82,16 +82,16 @@ class EvaluateTestsTest {
|
||||
assertThat(results.totalFailures()).isEqualTo(1)
|
||||
assertThat(results.failed()).isTrue
|
||||
|
||||
val res = results.facts.results[0]
|
||||
val res = results.facts().results[0]
|
||||
assertThat(res.name).isEqualTo("should fail")
|
||||
assertThat(results.facts.hasError()).isFalse
|
||||
assertThat(results.facts().hasError()).isFalse
|
||||
assertThat(res.failures.size).isEqualTo(2)
|
||||
|
||||
val fail1 = res.failures[0]
|
||||
assertThat(fail1.rendered).isEqualTo("1 == 2 (repl:text)")
|
||||
assertThat(fail1.message).isEqualTo("1 == 2 (repl:text)")
|
||||
|
||||
val fail2 = res.failures[1]
|
||||
assertThat(fail2.rendered).isEqualTo(""""foo" == "bar" (repl:text)""")
|
||||
assertThat(fail2.message).isEqualTo(""""foo" == "bar" (repl:text)""")
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -118,7 +118,7 @@ class EvaluateTestsTest {
|
||||
assertThat(results.totalFailures()).isEqualTo(1)
|
||||
assertThat(results.failed()).isTrue
|
||||
|
||||
val res = results.facts.results[0]
|
||||
val res = results.facts().results[0]
|
||||
assertThat(res.name).isEqualTo("should fail")
|
||||
assertThat(res.failures).hasSize(1)
|
||||
assertThat(res.errors).hasSize(1)
|
||||
@@ -180,7 +180,7 @@ class EvaluateTestsTest {
|
||||
assertThat(results.displayUri).startsWith("file:///").endsWith(".pkl")
|
||||
assertThat(results.totalTests()).isEqualTo(1)
|
||||
assertThat(results.failed()).isFalse
|
||||
assertThat(results.examples.results[0].name).isEqualTo("user")
|
||||
assertThat(results.examples().results[0].name).isEqualTo("user")
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -232,9 +232,9 @@ class EvaluateTestsTest {
|
||||
assertThat(results.totalFailures()).isEqualTo(1)
|
||||
assertThat(results.failed()).isTrue
|
||||
|
||||
assertThat(results.facts.results[0].name).isEqualTo("should fail")
|
||||
assertThat(results.facts.results[0].failures.size).isEqualTo(2)
|
||||
assertThat(results.examples.results[0].name).isEqualTo("user")
|
||||
assertThat(results.facts().results[0].name).isEqualTo("should fail")
|
||||
assertThat(results.facts().results[0].failures.size).isEqualTo(2)
|
||||
assertThat(results.examples().results[0].name).isEqualTo("user")
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -286,7 +286,7 @@ class EvaluateTestsTest {
|
||||
assertThat(results.totalFailures()).isEqualTo(1)
|
||||
assertThat(results.failed()).isTrue
|
||||
|
||||
val res = results.facts.results[0]
|
||||
val res = results.facts().results[0]
|
||||
assertThat(res.name).isEqualTo("should fail")
|
||||
assertThat(res.failures).hasSize(0)
|
||||
assertThat(res.errors).hasSize(1)
|
||||
@@ -294,7 +294,7 @@ class EvaluateTestsTest {
|
||||
val error = res.errors[0]
|
||||
assertThat(error.message).isEqualTo("exception")
|
||||
|
||||
assertThat(results.examples.results[0].name).isEqualTo("user")
|
||||
assertThat(results.examples().results[0].name).isEqualTo("user")
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -339,26 +339,26 @@ class EvaluateTestsTest {
|
||||
assertThat(results.failed()).isTrue
|
||||
assertThat(results.totalFailures()).isEqualTo(1)
|
||||
|
||||
val res = results.examples.results[0]
|
||||
val res = results.examples().results[0]
|
||||
assertThat(res.name).isEqualTo("user")
|
||||
assertFalse(results.examples.hasError())
|
||||
assertFalse(results.examples().hasError())
|
||||
|
||||
val fail1 = res.failures[0]
|
||||
assertThat(fail1.rendered.stripFileAndLines(tempDir))
|
||||
assertThat(fail1.message.stripFileAndLines(tempDir))
|
||||
.isEqualTo(
|
||||
"""
|
||||
(/tempDir/example.pkl)
|
||||
Expected: (/tempDir/example.pkl-expected.pcf)
|
||||
new {
|
||||
name = "Alice"
|
||||
age = 45
|
||||
}
|
||||
Actual: (/tempDir/example.pkl-actual.pcf)
|
||||
new {
|
||||
name = "Bob"
|
||||
age = 33
|
||||
}
|
||||
"""
|
||||
#0 (/tempDir/example.pkl):
|
||||
Expected: (/tempDir/example.pkl-expected.pcf)
|
||||
new {
|
||||
name = "Alice"
|
||||
age = 45
|
||||
}
|
||||
Actual: (/tempDir/example.pkl-actual.pcf)
|
||||
new {
|
||||
name = "Bob"
|
||||
age = 33
|
||||
}
|
||||
"""
|
||||
.trimIndent()
|
||||
)
|
||||
}
|
||||
|
||||
@@ -112,34 +112,33 @@ class TestsTest : AbstractTest() {
|
||||
assertThat(output.trimStart())
|
||||
.startsWith(
|
||||
"""
|
||||
> Task :evalTestGatherImports
|
||||
> Task :evalTestGatherImports
|
||||
|
||||
> Task :evalTest FAILED
|
||||
pkl: TRACE: 8 = 8 (file:///file, line x)
|
||||
module test
|
||||
facts
|
||||
✅ sum numbers
|
||||
✅ divide numbers
|
||||
❌ fail
|
||||
4 == 9 (file:///file, line x)
|
||||
"foo" == "bar" (file:///file, line x)
|
||||
examples
|
||||
✅ user 0
|
||||
✅ user 1 #0
|
||||
❌ user 1 #1
|
||||
(file:///file, line x)
|
||||
Expected: (file:///file, line x)
|
||||
new {
|
||||
name = "Parrot"
|
||||
age = 35
|
||||
}
|
||||
Actual: (file:///file, line x)
|
||||
new {
|
||||
name = "Welma"
|
||||
age = 35
|
||||
}
|
||||
❌ 66.7% tests pass [2/6 failed], 66.7% asserts pass [3/9 failed]
|
||||
"""
|
||||
> Task :evalTest FAILED
|
||||
pkl: TRACE: 8 = 8 (file:///file, line x)
|
||||
module test
|
||||
facts
|
||||
✅ sum numbers
|
||||
✅ divide numbers
|
||||
❌ fail
|
||||
4 == 9 (file:///file, line x)
|
||||
"foo" == "bar" (file:///file, line x)
|
||||
examples
|
||||
✅ user 0
|
||||
❌ user 1
|
||||
#1 (file:///file, line x):
|
||||
Expected: (file:///file, line x)
|
||||
new {
|
||||
name = "Parrot"
|
||||
age = 35
|
||||
}
|
||||
Actual: (file:///file, line x)
|
||||
new {
|
||||
name = "Welma"
|
||||
age = 35
|
||||
}
|
||||
❌ 60.0% tests pass [2/5 failed], 66.7% asserts pass [3/9 failed]
|
||||
"""
|
||||
.trimIndent()
|
||||
)
|
||||
}
|
||||
@@ -151,10 +150,10 @@ class TestsTest : AbstractTest() {
|
||||
|
||||
writeBuildFile("overwrite = true")
|
||||
|
||||
val output = runTask("evalTest").output
|
||||
val output = runTask("evalTest", expectFailure = true).output
|
||||
|
||||
assertThat(output).contains("user 0 ✍️")
|
||||
assertThat(output).contains("user 1 ✍️")
|
||||
assertThat(output).contains("✍️ user 0")
|
||||
assertThat(output).contains("✍️ user 1")
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -183,36 +182,36 @@ class TestsTest : AbstractTest() {
|
||||
assertThat(output.trimStart())
|
||||
.startsWith(
|
||||
"""
|
||||
> Task :evalTestGatherImports
|
||||
> Task :evalTestGatherImports
|
||||
|
||||
> Task :evalTest FAILED
|
||||
module test
|
||||
facts
|
||||
✅ should pass
|
||||
❌ error
|
||||
–– Pkl Error ––
|
||||
exception
|
||||
|
||||
9 | throw("exception")
|
||||
^^^^^^^^^^^^^^^^^^
|
||||
at test#facts["error"][#1] (file:///file, line x)
|
||||
examples
|
||||
✅ user 0
|
||||
❌ user 1
|
||||
#1 (file:///file, line x):
|
||||
Expected: (file:///file, line x)
|
||||
new {
|
||||
name = "Parrot"
|
||||
age = 35
|
||||
}
|
||||
Actual: (file:///file, line x)
|
||||
new {
|
||||
name = "Welma"
|
||||
age = 35
|
||||
}
|
||||
❌ 50.0% tests pass [2/4 failed], 66.7% asserts pass [2/6 failed]
|
||||
|
||||
> Task :evalTest FAILED
|
||||
module test
|
||||
facts
|
||||
✅ should pass
|
||||
❌ error
|
||||
–– Pkl Error ––
|
||||
exception
|
||||
|
||||
9 | throw("exception")
|
||||
^^^^^^^^^^^^^^^^^^
|
||||
at test#facts["error"][#1] (file:///file, line x)
|
||||
examples
|
||||
✅ user 0
|
||||
✅ user 1 #0
|
||||
❌ user 1 #1
|
||||
(file:///file, line x)
|
||||
Expected: (file:///file, line x)
|
||||
new {
|
||||
name = "Parrot"
|
||||
age = 35
|
||||
}
|
||||
Actual: (file:///file, line x)
|
||||
new {
|
||||
name = "Welma"
|
||||
age = 35
|
||||
}
|
||||
❌ 60.0% tests pass [2/5 failed], 66.7% asserts pass [2/6 failed]
|
||||
"""
|
||||
"""
|
||||
.trimIndent()
|
||||
)
|
||||
}
|
||||
@@ -232,34 +231,33 @@ class TestsTest : AbstractTest() {
|
||||
assertThat(report)
|
||||
.isEqualTo(
|
||||
"""
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<testsuite name="test" tests="6" failures="2">
|
||||
<testcase classname="test.facts" name="sum numbers"></testcase>
|
||||
<testcase classname="test.facts" name="divide numbers"></testcase>
|
||||
<testcase classname="test.facts" name="fail">
|
||||
<failure message="Fact Failure">4 == 9 (file:///file, line x)</failure>
|
||||
<failure message="Fact Failure">"foo" == "bar" (file:///file, line x)</failure>
|
||||
</testcase>
|
||||
<testcase classname="test.examples" name="user 0"></testcase>
|
||||
<testcase classname="test.examples" name="user 1 #0"></testcase>
|
||||
<testcase classname="test.examples" name="user 1 #1">
|
||||
<failure message="Example Failure">(file:///file, line x)
|
||||
Expected: (file:///file, line x)
|
||||
new {
|
||||
name = "Parrot"
|
||||
age = 35
|
||||
}
|
||||
Actual: (file:///file, line x)
|
||||
new {
|
||||
name = "Welma"
|
||||
age = 35
|
||||
}</failure>
|
||||
</testcase>
|
||||
<system-err><![CDATA[8 = 8
|
||||
]]></system-err>
|
||||
</testsuite>
|
||||
|
||||
"""
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<testsuite name="test" tests="5" failures="2">
|
||||
<testcase classname="test.facts" name="sum numbers"></testcase>
|
||||
<testcase classname="test.facts" name="divide numbers"></testcase>
|
||||
<testcase classname="test.facts" name="fail">
|
||||
<failure message="Fact Failure">4 == 9 (file:///file, line x)</failure>
|
||||
<failure message="Fact Failure">"foo" == "bar" (file:///file, line x)</failure>
|
||||
</testcase>
|
||||
<testcase classname="test.examples" name="user 0"></testcase>
|
||||
<testcase classname="test.examples" name="user 1">
|
||||
<failure message="Example Failure">#1 (file:///file, line x):
|
||||
Expected: (file:///file, line x)
|
||||
new {
|
||||
name = "Parrot"
|
||||
age = 35
|
||||
}
|
||||
Actual: (file:///file, line x)
|
||||
new {
|
||||
name = "Welma"
|
||||
age = 35
|
||||
}</failure>
|
||||
</testcase>
|
||||
<system-err><![CDATA[8 = 8
|
||||
]]></system-err>
|
||||
</testsuite>
|
||||
|
||||
"""
|
||||
.trimIndent()
|
||||
)
|
||||
}
|
||||
@@ -289,36 +287,35 @@ class TestsTest : AbstractTest() {
|
||||
assertThat(report)
|
||||
.isEqualTo(
|
||||
"""
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<testsuite name="test" tests="5" failures="2">
|
||||
<testcase classname="test.facts" name="should pass"></testcase>
|
||||
<testcase classname="test.facts" name="error">
|
||||
<error message="exception">–– Pkl Error ––
|
||||
exception
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<testsuite name="test" tests="4" failures="2">
|
||||
<testcase classname="test.facts" name="should pass"></testcase>
|
||||
<testcase classname="test.facts" name="error">
|
||||
<error message="exception">–– Pkl Error ––
|
||||
exception
|
||||
|
||||
9 | throw("exception")
|
||||
^^^^^^^^^^^^^^^^^^
|
||||
at test#facts["error"][#1] (file:///file, line x)
|
||||
</error>
|
||||
</testcase>
|
||||
<testcase classname="test.examples" name="user 0"></testcase>
|
||||
<testcase classname="test.examples" name="user 1">
|
||||
<failure message="Example Failure">#1 (file:///file, line x):
|
||||
Expected: (file:///file, line x)
|
||||
new {
|
||||
name = "Parrot"
|
||||
age = 35
|
||||
}
|
||||
Actual: (file:///file, line x)
|
||||
new {
|
||||
name = "Welma"
|
||||
age = 35
|
||||
}</failure>
|
||||
</testcase>
|
||||
</testsuite>
|
||||
|
||||
9 | throw("exception")
|
||||
^^^^^^^^^^^^^^^^^^
|
||||
at test#facts["error"][#1] (file:///file, line x)
|
||||
</error>
|
||||
</testcase>
|
||||
<testcase classname="test.examples" name="user 0"></testcase>
|
||||
<testcase classname="test.examples" name="user 1 #0"></testcase>
|
||||
<testcase classname="test.examples" name="user 1 #1">
|
||||
<failure message="Example Failure">(file:///file, line x)
|
||||
Expected: (file:///file, line x)
|
||||
new {
|
||||
name = "Parrot"
|
||||
age = 35
|
||||
}
|
||||
Actual: (file:///file, line x)
|
||||
new {
|
||||
name = "Welma"
|
||||
age = 35
|
||||
}</failure>
|
||||
</testcase>
|
||||
</testsuite>
|
||||
|
||||
"""
|
||||
"""
|
||||
.trimIndent()
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user