pkl-swift/Tests/PklSwiftTests/EvaluatorTest.swift

348 lines
12 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//===----------------------------------------------------------------------===//
// Copyright © 2024-2025 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.
//===----------------------------------------------------------------------===//
@testable import MessagePack
@testable import PklSwift
import SemanticVersion
import XCTest
class TestLogger: Logger {
var logLines: [String] = []
func trace(message: String, frameUri: String) {
self.logLines.append(formatLogMessage(level: "TRACE", message: message, frameUri: frameUri))
}
func warn(message: String, frameUri: String) {
self.logLines.append(formatLogMessage(level: "WARN", message: message, frameUri: frameUri))
}
}
struct VirtualModuleReader: ModuleReader {
func read(url: URL) async throws -> String {
try await self.read(url)
}
func listElements(uri: URL) async throws -> [PathElement] {
try await self.listElements(uri)
}
var read: (URL) async throws -> String
var listElements: (URL) async throws -> [PathElement]
var scheme: String
var isGlobbable: Bool
var hasHierarchicalUris: Bool
var isLocal: Bool
}
struct VirtualResourceReader: ResourceReader {
var scheme: String
var isGlobbable: Bool
var hasHierarchicalUris: Bool
var read: (URL) async throws -> [UInt8]
var listElements: (URL) async throws -> [PathElement]
func read(url: URL) async throws -> [UInt8] {
try await self.read(url)
}
func listElements(uri: URL) async throws -> [PathElement] {
try await self.listElements(uri)
}
}
final class PklSwiftTests: XCTestCase {
var manager: EvaluatorManager!
override func setUp() {
self.manager = EvaluatorManager()
}
override func tearDown() async throws {
await self.manager.close()
}
func testBasicEvaluation() async throws {
let evaluator = try await manager.newEvaluator(options: EvaluatorOptions.preconfigured)
let output = try await evaluator.evaluateOutputText(
source: ModuleSource(uri: URL(string: "repl:text")!, text: "foo = 1"))
XCTAssertEqual(output, "foo = 1\n")
}
func testVersionCoverage() async throws {
let output = try await SemanticVersion(EvaluatorManager().getVersion())!
XCTAssert(supportedPklVersions.contains { $0.major == output.major && $0.minor == output.minor })
}
func testCustomProxyOptions() async throws {
let version = try await SemanticVersion(EvaluatorManager().getVersion())!
let expected = version < pklVersion0_26
? "http options are not supported on Pkl versions lower than 0.26"
: "ConnectException: Error connecting to host `example.com`"
var options = EvaluatorOptions.preconfigured
options.http = .init(
caCertificates: nil,
proxy: .init(
address: "http://localhost:1",
noProxy: ["myhost.com:1337", "myotherhost.org:42"]
)
)
do {
let evaluator = try await manager.newEvaluator(options: options)
let _ = try await evaluator.evaluateOutputText(source: .uri("https://example.com")!)
XCTFail("Should have thrown an error")
} catch {
XCTAssert("\(error)".contains(expected))
}
}
func testCustomModuleReader() async throws {
let reader = VirtualModuleReader(
read: { _ in
"""
name = "Swallow"
numberOfEggs = 8
"""
},
listElements: { _ in [] },
scheme: "birds",
isGlobbable: true,
hasHierarchicalUris: true,
isLocal: true
)
let options = EvaluatorOptions.preconfigured.withModuleReader(reader)
let evaluator = try await manager.newEvaluator(options: options)
let output = try await evaluator.evaluateOutputText(source: .text("""
import "birds:/catalog/swallow.pkl"
result = swallow
"""))
XCTAssertEqual(output, """
result {
name = "Swallow"
numberOfEggs = 8
}
""")
}
func testGlobCustomModuleReader() async throws {
let reader = VirtualModuleReader(
read: { url in
switch url {
case URL(string: "birds:/swallow.pkl"):
return
"""
name = "Swallow"
numberOfEggs = 8
"""
case URL(string: "birds:/penguin.pkl"):
return
"""
name = "Penguin"
numberOfEggs = 1
"""
default:
throw PklError("File not found")
}
},
listElements: { _ in [.init(name: "swallow.pkl", isDirectory: false), .init(name: "penguin.pkl", isDirectory: false)] },
scheme: "birds",
isGlobbable: true,
hasHierarchicalUris: true,
isLocal: true
)
let options = EvaluatorOptions.preconfigured.withModuleReader(reader)
let evaluator = try await manager.newEvaluator(options: options)
let result = try await evaluator.evaluateOutputText(source: .text("""
result = import*("birds:/*.pkl")
"""))
XCTAssertEqual(result, """
result {
["birds:/penguin.pkl"] {
name = "Penguin"
numberOfEggs = 1
}
["birds:/swallow.pkl"] {
name = "Swallow"
numberOfEggs = 8
}
}
""")
}
func testTripleDotImports() async throws {
let reader = VirtualModuleReader(
read: { url in
switch url.path {
case "/dir1/dir2/dir3/Bird.pkl":
return #"amends "...""#
case "/Bird.pkl":
return #"name = "Birdy""#
default:
throw NSError(domain: NSCocoaErrorDomain, code: NSFileReadNoSuchFileError)
}
},
listElements: { url in
switch url.path {
case "/dir1/dir2":
return [.init(name: "dir3", isDirectory: true)]
case "/dir1":
return [.init(name: "dir2", isDirectory: true)]
case "/":
return [.init(name: "Bird.pkl", isDirectory: false), .init(name: "dir1", isDirectory: true)]
default:
throw NSError(domain: NSCocoaErrorDomain, code: NSFileNoSuchFileError)
}
},
scheme: "birds",
isGlobbable: true,
hasHierarchicalUris: true,
isLocal: true
)
let options = EvaluatorOptions.preconfigured.withModuleReader(reader)
let evaluator = try await manager.newEvaluator(options: options)
let result = try await evaluator.evaluateOutputText(source: ModuleSource.uri("birds:/dir1/dir2/dir3/Bird.pkl")!)
XCTAssertEqual(result, #"name = "Birdy"\#n"#)
}
func testConcurrenctEvaluations() async throws {
async let evalResult1 = try Task {
let evaluator = try await self.manager.newEvaluator(options: .preconfigured)
return try await evaluator.evaluateOutputText(source: .text("foo = 1"))
}.result.get()
async let evalResult2 = try Task {
let evaluator = try await self.manager.newEvaluator(options: .preconfigured)
return try await evaluator.evaluateOutputText(source: .text("foo = 2"))
}.result.get()
async let evalResult3 = try Task {
let evaluator = try await self.manager.newEvaluator(options: .preconfigured)
return try await evaluator.evaluateOutputText(source: .text("foo = 3"))
}.result.get()
let results = try await (evalResult1, evalResult2, evalResult3)
XCTAssertEqual(results.0, "foo = 1\n")
XCTAssertEqual(results.1, "foo = 2\n")
XCTAssertEqual(results.2, "foo = 3\n")
}
func testCustomResourceReader() async throws {
let reader = VirtualResourceReader(
scheme: "pizza",
isGlobbable: false,
hasHierarchicalUris: false,
read: { _ in [UInt8]("yes pizza".utf8) },
listElements: { _ in [] }
)
let options = EvaluatorOptions.preconfigured.withResourceReader(reader)
let evaluator = try await manager.newEvaluator(options: options)
let output = try await evaluator.evaluateOutputText(source: .text("""
result = read("pizza:pizza").text
"""))
XCTAssertEqual(output, #"result = "yes pizza"\#n"#)
}
func testFailedEvaluation() async throws {
let evaluator = try await manager.newEvaluator(options: EvaluatorOptions.preconfigured)
do {
_ = try await evaluator.evaluateOutputText(source: .text(#"foo = throw("uh oh")"#))
} catch {
XCTAssertTrue(error is PklError)
let error = error as! PklError
self.assertStartsWith(error.message, """
Pkl Error
uh oh
1 | foo = throw("uh oh")
^^^^^^^^^^^^^^
at text#foo (repl:text)
""")
}
}
private func assertStartsWith(
_ message: String,
_ prefix: String,
file: StaticString = #filePath,
line: UInt = #line
) {
if !message.starts(with: prefix) {
XCTFail("Expected \(message) to start with \(prefix)", file: file, line: line)
}
}
func testLogger() async throws {
var options = EvaluatorOptions.preconfigured
let logger = TestLogger()
options.logger = logger
let evaluator = try await manager.newEvaluator(options: options)
_ = try await evaluator.evaluateOutputText(source: .text(#"result = let (_ = trace("Hello there")) 1"#))
XCTAssertEqual(logger.logLines, [#"pkl: TRACE: "Hello there" = "Hello there" (repl:text)\#n"#])
}
// TODO: re-enable this test when packages are available
//
// func testWithProject() async throws {
// let project1Dir = (try tempDir()).appendingPathComponent("project1")
// try FileManager.default.createDirectory(at: project1Dir, withIntermediateDirectories: true)
// try """
// amends "pkl:Project"
//
// dependencies {
// ["uri"] { uri = "package://pkg.pkl-lang.org/pkl-pantry/pkl.experimental.uri@1.0.0" }
// }
// """.write(to: project1Dir.appendingPathComponent("PklProject"), atomically: true, encoding: .utf8)
// try """
// {
// "schemaVersion": 1,
// "resolvedDependencies": {
// "package://pkg.pkl-lang.org/pkl-pantry/pkl.experimental.uri@1": {
// "type": "remote",
// "uri": "projectpackage://pkg.pkl-lang.org/pkl-pantry/pkl.experimental.uri@1.0.0",
// "checksums": {
// "sha256": "12a42da6a2933a802cc79cea7f5541513b5106070ca5f1236009ebefeb3d81b3"
// }
// }
// }
// }
// """.write(to: project1Dir.appendingPathComponent("PklProject.deps.json"), atomically: true, encoding: .utf8)
// try """
// import "@uri/URI.pkl"
//
// uri = URI.parse("https://www.example.com").toString()
// """.write(to: project1Dir.appendingPathComponent("main.pkl"), atomically: true, encoding: .utf8)
// try await withProjectEvaluator(projectDir: project1Dir.path) { evaluator in
// let output = try await evaluator.evaluateOutputText(source: .url(project1Dir.appendingPathComponent("main.pkl")))
// XCTAssertEqual("""
// uri = "https://www.example.com"
//
// """, output)
// }
// }
}