File: FileManagerPlayground.swift

package info (click to toggle)
swiftlang 6.0.3-2
  • links: PTS, VCS
  • area: main
  • in suites: trixie
  • size: 2,519,992 kB
  • sloc: cpp: 9,107,863; ansic: 2,040,022; asm: 1,135,751; python: 296,500; objc: 82,456; f90: 60,502; lisp: 34,951; pascal: 19,946; sh: 18,133; perl: 7,482; ml: 4,937; javascript: 4,117; makefile: 3,840; awk: 3,535; xml: 914; fortran: 619; cs: 573; ruby: 573
file content (143 lines) | stat: -rw-r--r-- 5,422 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2024 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//

#if canImport(TestSupport)
import TestSupport
#endif

#if FOUNDATION_FRAMEWORK
@testable import Foundation
#else
@testable import FoundationEssentials
#endif

private protocol Buildable {
    func build(in path: String, using fileManager: FileManager) throws
}

struct File : ExpressibleByStringLiteral, Buildable {
    private let name: String
    private let attributes: [FileAttributeKey : Any]?
    private let contents: Data?
    
    init(_ name: String, attributes: [FileAttributeKey : Any]? = nil, contents: Data? = nil) {
        self.name = name
        self.attributes = attributes
        self.contents = contents
    }
    
    init(stringLiteral value: String) {
        self.init(value)
    }
    
    fileprivate func build(in path: String, using fileManager: FileManager) throws {
        guard fileManager.createFile(atPath: path.appendingPathComponent(name), contents: contents, attributes: attributes) else {
            throw CocoaError(.fileWriteUnknown)
        }
    }
}

struct SymbolicLink : Buildable {
    fileprivate let name: String
    private let destination: String
    
    init(_ name: String, destination: String) {
        self.name = name
        self.destination = destination
    }
    
    fileprivate func build(in path: String, using fileManager: FileManager) throws {
        let linkPath = path.appendingPathComponent(name)
        let destPath = path.appendingPathComponent(destination)
        try fileManager.createSymbolicLink(atPath: linkPath, withDestinationPath: destPath)
    }
}

struct Directory : Buildable {
    fileprivate let name: String
    private let attributes: [FileAttributeKey : Any]?
    private let contents: [FileManagerPlayground.Item]
    
    init(_ name: String, attributes: [FileAttributeKey : Any]? = nil, @FileManagerPlayground.DirectoryBuilder _ contentsClosure: () -> [FileManagerPlayground.Item]) {
        self.name = name
        self.attributes = attributes
        self.contents = contentsClosure()
    }
    
    fileprivate func build(in path: String, using fileManager: FileManager) throws {
        let dirPath = path.appendingPathComponent(name)
        try fileManager.createDirectory(atPath: dirPath, withIntermediateDirectories: true, attributes: attributes)
        for item in contents {
            try item.build(in: dirPath, using: fileManager)
        }
    }
}

struct FileManagerPlayground {
    enum Item : Buildable {
        case file(File)
        case directory(Directory)
        case symbolicLink(SymbolicLink)
        
        fileprivate func build(in path: String, using fileManager: FileManager) throws {
            switch self {
            case let .file(file): try file.build(in: path, using: fileManager)
            case let .directory(dir): try dir.build(in: path, using: fileManager)
            case let .symbolicLink(symlink): try symlink.build(in: path, using: fileManager)
            }
        }
    }
    
    @resultBuilder
    enum DirectoryBuilder {
        static func buildBlock(_ components: Item...) -> [Item] {
            components
        }
        
        static func buildExpression(_ expression: File) -> Item {
            .file(expression)
        }
        
        static func buildExpression(_ expression: Directory) -> Item {
            .directory(expression)
        }
        
        static func buildExpression(_ expression: SymbolicLink) -> Item {
            .symbolicLink(expression)
        }
    }
    
    private let directory: Directory
    
    init(@DirectoryBuilder _ contentsClosure: () -> [Item]) {
        self.directory = Directory("FileManagerPlayground_\(UUID().uuidString)", contentsClosure)
    }
    
    func test(captureDelegateCalls: Bool = false, file: StaticString = #filePath, line: UInt = #line, _ tester: (FileManager) throws -> Void) throws {
        let capturingDelegate = CapturingFileManagerDelegate()
        try withExtendedLifetime(capturingDelegate) {
            let fileManager = FileManager()
            let tempDir = String.temporaryDirectoryPath
            try directory.build(in: tempDir, using: fileManager)
            let previousCWD = fileManager.currentDirectoryPath
            if captureDelegateCalls {
                // Add the delegate after the call to `build` to ensure that the builder doesn't mutate the delegate
                fileManager.delegate = capturingDelegate
            }
            let createdDir = tempDir.appendingPathComponent(directory.name)
            XCTAssertTrue(fileManager.changeCurrentDirectoryPath(createdDir), "Failed to change CWD to the newly created playground directory", file: file, line: line)
            try tester(fileManager)
            XCTAssertTrue(fileManager.changeCurrentDirectoryPath(previousCWD), "Failed to change CWD back to the original directory", file: file, line: line)
            try fileManager.removeItem(atPath: createdDir)
        }
    }
}