Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Content directory symlinks for #264; also permit some content errors, consider configurable suffixes #265

Open
wants to merge 14 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
symlinks+lint: ContentFinderTestSuite renames
Now no swift-lint errors:
- Suite permits _ in composite variable names for clarity

Also:
- Using Ignite.ContentFinder for case-insensitive suffix check
- MdHeaderList avoids [String] extension for markdown output
  • Loading branch information
wti committed Feb 3, 2025
commit 53abf56f8332f700e52701b59be72f15aee45927
68 changes: 37 additions & 31 deletions Tests/IgniteTesting/Publishing/ContentFinder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,24 +19,24 @@ actor ContentFinderTests {
try run(test)
} catch {
if let exp = test.expectError {
let m = "\(error)"
#expect(m.contains(exp))
let message = "\(error)"
#expect(message.contains(exp))
} else {
#expect(Bool(false), "\(error)")
}
}
}

func run(_ test: Tst) throws {
guard let baseDir = try Self.makeSuiteBaseDir(test.i) else {
guard let baseDir = try Self.makeSuiteBaseDir(test.index) else {
#expect(Bool(false), "Unable to make suite base dir")
return
}
defer { Self.removeSuiteBaseDir(baseDir) }

// Configure directory tree
let root = try test.setupRootPath(baseDir, index: test.i)
//defer { try? test.tearDown(baseDir: root) } // deleting suiteDir above
let root = try test.setupRootPath(baseDir, index: test.index)
// defer { try? test.tearDown(baseDir: root) } // deleted baseDir above

// Visit directory tree
var found = [ContentFinder.DeployContent]()
Expand All @@ -56,28 +56,30 @@ actor ContentFinderTests {
let extra = ("extra", foundPaths.subtracting(both.1))
let missing = ("missing", expectPaths.subtracting(both.1))
let all = [extra, missing, both]
let alls = all.map { Array($0.1).mdLine(head: $0.0, sort: true) }
let m = "\n# \(test.i)/\(test.makeRoots)\(alls.joined(separator: ""))"
let emit = MdHeaderList.h2SortQuiet
let alls = all.map { emit.md($0.0, Array($0.1)) }
let allsJoined = alls.joined(separator: "")
let message = "\n# \(test.index)/\(test.makeRoots)\(allsJoined)"
enum Err: Error, CustomStringConvertible {
case m(String)
case message(String)
var description: String {
switch self {
case .m(let s): s
case .message(let result): result
}
}
}
Issue.record(Err.m(m))
Issue.record(Err.message(message))
}

static func makeSuiteBaseDir(_ i: Int) throws -> URL? {
let name = "ContentFinder.find_\(i)"
var r = try Files.makeTempDir(name: name)
if !r.found {
return r.url
static func makeSuiteBaseDir(_ index: Int) throws -> URL? {
let name = "ContentFinder.find_\(index)"
var foundUrl = try Files.makeTempDir(name: name)
if !foundUrl.found {
return foundUrl.url
}
try Files.delete(url: r.url)
r = try Files.makeTempDir(name: name)
return r.found ? nil : r.url
try Files.delete(url: foundUrl.url)
foundUrl = try Files.makeTempDir(name: name)
return foundUrl.found ? nil : foundUrl.url
}

static func removeSuiteBaseDir(_ baseDir: URL, caller: String = #function) {
Expand All @@ -96,7 +98,7 @@ actor ContentFinderTests {
}

struct Tst: CustomStringConvertible {
let i: Int
let index: Int
let suffixes: [String]
let items: [FileItem]
let makeRoots: [String]
Expand All @@ -105,14 +107,14 @@ actor ContentFinderTests {
let expectError: String? // urk: very weak check for errors
let inputError: InputError?
init(
_ i: Int,
_ index: Int,
_ makeRoots: [String],
suffixes: [String] = [".md"],
items: [FileItem],
expect: [String]? = nil,
expectError: String? = nil
) {
var err: InputError? = nil
var err: InputError?
if makeRoots.isEmpty {
err = .emptyRoots
} else if let index = makeRoots.firstIndex(where: { $0.isEmpty }) {
Expand All @@ -122,7 +124,7 @@ actor ContentFinderTests {
} else if let index = suffixes.firstIndex(where: { $0.isEmpty }) {
err = .emptySuffix(index)
}
self.i = i
self.index = index
self.makeRoots = makeRoots
self.suffixes = suffixes
self.items = items
Expand Down Expand Up @@ -151,20 +153,23 @@ actor ContentFinderTests {
/// - Parameter suiteBaseDir: Parent directory for test directory
/// - Returns: URL for test directory, populated per ``items`` ``FileItem``
func setupRootPath(_ suiteBaseDir: URL, index: Int) throws -> URL {
// swiftlint:disable:next nesting
typealias ItemURL = (item: FileItem, url: URL)
// swiftlint:disable:next nesting
enum Err: Error {
case foundTarget(_ url: URL, item: FileItem)
case foundTestBaseDir(URL)
case duplicateTargetFindDir(path: String, next: URL, prior: URL)
case noTargetFindDir(path: String, itemUrls: [ItemURL])
case noTargetFindDir(path: String,
itemUrls: [ItemURL])
}
let testBaseDir = try makeTestBaseDir(suiteBaseDir, index: index)
guard !testBaseDir.found else {
throw Err.foundTestBaseDir(testBaseDir.url)
}
let maker = FileItemMaker(baseDir: testBaseDir.url)
let targetPath = makeRoots.first ?? "Never have no roots"
var targetFindDir: URL? = nil
var targetFindDir: URL?
var itemUrls = [ItemURL]()
for item in items {
let foundUrl = try maker.make(item)
Expand Down Expand Up @@ -205,19 +210,19 @@ actor ContentFinderTests {

var description: String { // terrible labels
let root = 1 == makeRoots.count ? makeRoots.first! : "\(makeRoots)"
let prefix = "[\(i)] \(root)"
let n = " "
let prefix = "[\(index)] \(root)"
let eol = " " // single-line (test label); use "\n" when debugging
if let inErr = inputError {
return "\(prefix) input error:\(n)\(inErr)"
return "\(prefix) input error:\(eol)\(inErr)"
}
if let err = expectError {
return "\(prefix) error: \(err)"
}
let sf = suffixes == [".md"] ? "" : " suffixes: \(suffixes)"
let ep = expectPathsWereDerived ? " expect(derived)" : "expect"
let (pre, sep) = " " == n ? (" ", ", ") : ("\n- ", "\n- ")
let sfx = suffixes == [".md"] ? "" : " suffixes: \(suffixes)"
let exp = expectPathsWereDerived ? " expect(derived)" : "expect"
let (pre, sep) = " " == eol ? (" ", ", ") : ("\n- ", "\n- ")
let eps = Array(expectPaths).sorted().joined(separator: sep)
return "\(prefix) \(sf)\(ep)\(pre)\(eps)"
return "\(prefix) \(sfx)\(exp)\(pre)\(eps)"
}

/// Expected deploy path removes the ``findRootPath`` prefix
Expand All @@ -239,6 +244,7 @@ actor ContentFinderTests {
return String(path[start..<end])
}

// swiftlint:disable:next nesting
enum InputError: Error {
case emptyRoots
case emptyRoot(Int)
Expand Down
114 changes: 74 additions & 40 deletions Tests/IgniteTesting/Publishing/ContentFinderTestHelp.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,28 +7,53 @@

import Foundation

extension [String] {
func mdLine(
head: String? = nil,
hn: Int = 2,
ul: String = "\n- ",
sort: Bool = false,
skipHeadIfEmpty: Bool = true
) -> String {
if isEmpty && skipHeadIfEmpty { return "" }
var out = ""
if let head, !head.isEmpty {
let h = hn < 1 ? 1 : hn > 6 ? 6 : hn
out += "\n"
out += String(repeating: "#", count: h)
out += " "
out += head
}
if isEmpty { return out }
out += ul
let target = sort ? sorted() : self
out += target.joined(separator: ul)
return out
@testable import Ignite

extension ContentFinderTests {

enum MdHeaderList {
case head(level: Int, sort: Bool, skipEmpty: Bool)
case h2Quiet
case h2SortQuiet

func md<T: StringProtocol>(
_ header: T? = nil,
_ list: [T]
) -> String {
let (_, sort, skipEmpty) = lvlSrtSkp
if list.isEmpty && skipEmpty { return "" }
let (headPrefix, listPrefix) = prefixes(header, list.isEmpty)
var out = ""
if let headPrefix, let header {
out += headPrefix
out += header
}
guard let listPrefix, !list.isEmpty else { return out }
out += listPrefix // initial
let target = sort ? list.sorted() : list
out += target.joined(separator: listPrefix)
return out
}
// swiftlint:disable:next large_tuple
private var lvlSrtSkp: (level: Int, sort: Bool, skipEmpty: Bool) {
switch self {
case let .head(level, sort, skip): (level, sort, skip)
case .h2Quiet: (2, false, true)
case .h2SortQuiet: (2, true, true)
}
}
private func prefixes<T: StringProtocol>(
_ header: T?,
_ hasList: Bool
) -> (head: String?, list: String?) {
let (level, _, skipEmpty) = lvlSrtSkp
if skipEmpty && !hasList { return (nil, nil) }
var head: String?
if let header, !header.isEmpty {
head = "\n\(String(repeating: "#", count: level)) "
}
return (head, hasList ? "\n- " : nil)
}
}
}
extension ContentFinderTests {
Expand All @@ -48,9 +73,9 @@ extension ContentFinderTests {
return try Files.makeDir(url: url)
case let .link(source, dest):
let src = Files.makeUrlToFile(baseDir, path: source.path)
let d = Files.makeUrlToFile(baseDir, path: dest.path)
let dst = Files.makeUrlToFile(baseDir, path: dest.path)
do {
try Files.createSymlink(source: src, dest: d)
try Files.createSymlink(source: src, dest: dst)
} catch Files.SymlinkErr.symlinkError(let src, _, _) {
return (true, src)
}
Expand All @@ -62,51 +87,58 @@ extension ContentFinderTests {

extension ContentFinderTests.FileItem: CustomStringConvertible {
var description: String {
"\(label)(\(name)\(parent.map{" in: \($0.path)"} ?? ""))"
"\(label)(\(name)\(parent.map {" in: \($0.path)"} ?? ""))"
}
}
extension ContentFinderTests.FileItem {
var isFile: Bool { Self.fn == index0 }
static let (rn, fn, dn, ln) = (0, 1, 2, 3)
var isFile: Bool { Self.fileN == index0 }
static let (rootN, fileN, dirN, linkN) = (0, 1, 2, 3)
static let labels = ["root", "file", "dir", "link"]
private var index0: Int {
switch self {
case .root: Self.rn
case .file: Self.fn
case .dir: Self.dn
case .link: Self.ln
case .root: Self.rootN
case .file: Self.fileN
case .dir: Self.dirN
case .link: Self.linkN
}
}

var name: String { nameParent.name }
var parent: Self? { nameParent.parent }
var nameParent: (name: String, parent: Self?) {
switch self {
case .root(let n): (n, nil)
case let .file(n, p), let .dir(n, p): (n, p)
case .root(let name): (name, nil)
case let .file(name, parnt), let .dir(name, parnt): (name, parnt)
case .link(let source, _): source.nameParent
}
}
var label: String { Self.labels[index0] }

func firstMatchingFileSuffix(_ suffixes: [String]) -> String? {
!isFile ? nil : suffixes.first { hasFileSuffix($0) }
}
func hasFileSuffix(_ suffix: String) -> Bool {
isFile && nameParent.name.hasSuffix(suffix)
// TODO add case-insensitive check
guard
isFile,
let index = ContentFinder.suffixStart(
name: nameParent.name,
suffixes: suffixes)
else {
return nil
}
return String(nameParent.name[index...])
}

var path: String {
switch self {
case .root(let n): n
case let .file(n, p), let .dir(n, p): "\(p.path)/\(n)"
case .root(let name): name
case let .file(name, parent): "\(parent.path)/\(name)"
case let .dir(name, parent): "\(parent.path)/\(name)"
case let .link(source, _): source.path
}
}
}

extension ContentFinderTests {
enum Files {
// swiftlint:disable:next nesting
typealias FoundURL = (found: Bool, url: URL)

static func isReachable(_ url: URL, check: Bool = true) -> Bool {
Expand Down Expand Up @@ -172,6 +204,7 @@ extension ContentFinderTests {
return try makeDir(url: url)
}
static func createSymlink(source: URL, dest: URL) throws {
// swiftlint:disable:next nesting
typealias Err = SymlinkErr
if !source.isFileURL {
throw Err.sourceNotFileScheme(source)
Expand All @@ -192,6 +225,7 @@ extension ContentFinderTests {
throw Err.symlinkError(source: source, dest: dest, error: error)
}
}
// swiftlint:disable:next nesting
enum SymlinkErr: Error {
case sourceNotFileScheme(URL), destNotFileScheme(URL)
case sourceExists(URL), destNotReachable(URL)
Expand Down
Loading