import XCTest
import Foundation
@testable import Workspace
class WorkspaceFileTests: XCTestCase {
func testMatchesPatterns() {
let url1 = URL(fileURLWithPath: "/path/to/file.swift")
let url2 = URL(fileURLWithPath: "/path/to/.git")
let patterns = [".git", ".svn"]
XCTAssertTrue(WorkspaceFile.matchesPatterns(url2, patterns: patterns))
XCTAssertFalse(WorkspaceFile.matchesPatterns(url1, patterns: patterns))
}
func testIsXCWorkspace() throws {
let tmpDir = try createTemporaryDirectory()
defer {
deleteDirectoryIfExists(at: tmpDir)
}
do {
let xcworkspaceURL = try createSubdirectory(in: tmpDir, withName: "myWorkspace.xcworkspace")
XCTAssertFalse(WorkspaceFile.isXCWorkspace(xcworkspaceURL))
let xcworkspaceDataURL = try createFile(in: xcworkspaceURL, withName: "contents.xcworkspacedata", contents: "")
XCTAssertTrue(WorkspaceFile.isXCWorkspace(xcworkspaceURL))
} catch {
throw error
}
}
func testIsXCProject() throws {
let tmpDir = try createTemporaryDirectory()
defer {
deleteDirectoryIfExists(at: tmpDir)
}
do {
let xcprojectURL = try createSubdirectory(in: tmpDir, withName: "myProject.xcodeproj")
XCTAssertFalse(WorkspaceFile.isXCProject(xcprojectURL))
let xcprojectDataURL = try createFile(in: xcprojectURL, withName: "project.pbxproj", contents: "")
XCTAssertTrue(WorkspaceFile.isXCProject(xcprojectURL))
} catch {
throw error
}
}
func testGetFilesInActiveProject() throws {
let tmpDir = try createTemporaryDirectory()
do {
let xcprojectURL = try createXCProjectFolder(in: tmpDir, withName: "myProject.xcodeproj")
_ = try createFile(in: tmpDir, withName: "file1.swift", contents: "")
_ = try createFile(in: tmpDir, withName: "file2.swift", contents: "")
_ = try createSubdirectory(in: tmpDir, withName: ".git")
let files = WorkspaceFile.getFilesInActiveWorkspace(workspaceURL: xcprojectURL, workspaceRootURL: tmpDir)
let fileNames = files.map { $0.url.lastPathComponent }
XCTAssertEqual(files.count, 2)
XCTAssertTrue(fileNames.contains("file1.swift"))
XCTAssertTrue(fileNames.contains("file2.swift"))
} catch {
deleteDirectoryIfExists(at: tmpDir)
throw error
}
deleteDirectoryIfExists(at: tmpDir)
}
func testGetFilesInActiveWorkspace() throws {
let tmpDir = try createTemporaryDirectory()
defer {
deleteDirectoryIfExists(at: tmpDir)
}
do {
let myWorkspaceRoot = try createSubdirectory(in: tmpDir, withName: "myWorkspace")
let xcWorkspaceURL = try createXCWorkspaceFolder(in: myWorkspaceRoot, withName: "myWorkspace.xcworkspace", fileRefs: [
"container:myProject.xcodeproj",
"group:../notExistedDir/notExistedProject.xcodeproj",
"group:../myDependency",])
let xcprojectURL = try createXCProjectFolder(in: myWorkspaceRoot, withName: "myProject.xcodeproj")
let myDependencyURL = try createSubdirectory(in: tmpDir, withName: "myDependency")
// Files under workspace should be included
_ = try createFile(in: myWorkspaceRoot, withName: "file1.swift", contents: "")
// unsupported patterns and file extension should be excluded
_ = try createFile(in: myWorkspaceRoot, withName: "unsupportedFileExtension.xyz", contents: "")
_ = try createSubdirectory(in: myWorkspaceRoot, withName: ".git")
// Files under project metadata folder should be excluded
_ = try createFile(in: xcprojectURL, withName: "fileUnderProjectMetadata.swift", contents: "")
// Files under dependency should be included
_ = try createFile(in: myDependencyURL, withName: "depFile1.swift", contents: "")
// Should be excluded
_ = try createSubdirectory(in: myDependencyURL, withName: ".git")
// Files under unrelated directories should be excluded
_ = try createFile(in: tmpDir, withName: "unrelatedFile1.swift", contents: "")
let files = WorkspaceFile.getFilesInActiveWorkspace(workspaceURL: xcWorkspaceURL, workspaceRootURL: myWorkspaceRoot)
let fileNames = files.map { $0.url.lastPathComponent }
XCTAssertEqual(files.count, 2)
XCTAssertTrue(fileNames.contains("file1.swift"))
XCTAssertTrue(fileNames.contains("depFile1.swift"))
} catch {
throw error
}
}
func testGetSubprojectURLsFromXCWorkspace() throws {
let tmpDir = try createTemporaryDirectory()
defer {
deleteDirectoryIfExists(at: tmpDir)
}
let workspaceDir = try createSubdirectory(in: tmpDir, withName: "workspace")
// Create tryapp directory and project
let tryappDir = try createSubdirectory(in: tmpDir, withName: "tryapp")
_ = try createXCProjectFolder(in: tryappDir, withName: "tryapp.xcodeproj")
// Create Copilot for Xcode project
_ = try createXCProjectFolder(in: workspaceDir, withName: "Copilot for Xcode.xcodeproj")
// Create Test1 directory
let test1Dir = try createSubdirectory(in: tmpDir, withName: "Test1")
// Create Test2 directory and project
let test2Dir = try createSubdirectory(in: tmpDir, withName: "Test2")
_ = try createXCProjectFolder(in: test2Dir, withName: "project2.xcodeproj")
// Create the workspace data file with our references
let xcworkspaceData = """
"""
let workspaceURL = try createXCWorkspaceFolder(in: workspaceDir, withName: "workspace.xcworkspace", xcworkspacedata: xcworkspaceData)
let subprojectURLs = WorkspaceFile.getSubprojectURLs(in: workspaceURL)
XCTAssertEqual(subprojectURLs.count, 4)
let resolvedPaths = subprojectURLs.map { $0.path }
let expectedPaths = [
tryappDir.path,
workspaceDir.path, // For Copilot for Xcode.xcodeproj
test1Dir.path,
test2Dir.path
]
XCTAssertEqual(resolvedPaths, expectedPaths)
}
func testGetSubprojectURLsFromEmbeddedXCWorkspace() throws {
let tmpDir = try createTemporaryDirectory()
defer {
deleteDirectoryIfExists(at: tmpDir)
}
// Create the workspace data file with a self reference
let xcworkspaceData = """
"""
// Create the MyApp directory structure
let myAppDir = try createSubdirectory(in: tmpDir, withName: "MyApp")
let xcodeProjectDir = try createXCProjectFolder(in: myAppDir, withName: "MyApp.xcodeproj")
let embeddedWorkspaceDir = try createXCWorkspaceFolder(in: xcodeProjectDir, withName: "MyApp.xcworkspace", xcworkspacedata: xcworkspaceData)
let subprojectURLs = WorkspaceFile.getSubprojectURLs(in: embeddedWorkspaceDir)
XCTAssertEqual(subprojectURLs.count, 1)
XCTAssertEqual(subprojectURLs[0].lastPathComponent, "MyApp")
XCTAssertEqual(subprojectURLs[0].path, myAppDir.path)
}
func testGetSubprojectURLsFromXCWorkspaceOrganizedByGroup() throws {
let tmpDir = try createTemporaryDirectory()
defer {
deleteDirectoryIfExists(at: tmpDir)
}
// Create directories for the projects and groups
let tryappDir = try createSubdirectory(in: tmpDir, withName: "tryapp")
_ = try createXCProjectFolder(in: tryappDir, withName: "tryapp.xcodeproj")
let webLibraryDir = try createSubdirectory(in: tmpDir, withName: "WebLibrary")
// Create the group directories
let group1Dir = try createSubdirectory(in: tmpDir, withName: "group1")
let group2Dir = try createSubdirectory(in: group1Dir, withName: "group2")
_ = try createSubdirectory(in: group2Dir, withName: "group3")
_ = try createSubdirectory(in: group1Dir, withName: "group4")
// Create the MyProjects directory
let myProjectsDir = try createSubdirectory(in: tmpDir, withName: "MyProjects")
// Create the copilot-xcode directory and project
let copilotXcodeDir = try createSubdirectory(in: myProjectsDir, withName: "copilot-xcode")
_ = try createXCProjectFolder(in: copilotXcodeDir, withName: "Copilot for Xcode.xcodeproj")
// Create the SwiftLanguageWeather directory and project
let swiftWeatherDir = try createSubdirectory(in: myProjectsDir, withName: "SwiftLanguageWeather")
_ = try createXCProjectFolder(in: swiftWeatherDir, withName: "SwiftWeather.xcodeproj")
// Create the workspace data file with a complex group structure
let xcworkspaceData = """
"""
// Create a test workspace structure
let workspaceURL = try createXCWorkspaceFolder(in: tmpDir, withName: "workspace.xcworkspace", xcworkspacedata: xcworkspaceData)
let subprojectURLs = WorkspaceFile.getSubprojectURLs(in: workspaceURL)
XCTAssertEqual(subprojectURLs.count, 4)
let expectedPaths = [
tryappDir.path,
webLibraryDir.path,
copilotXcodeDir.path,
swiftWeatherDir.path
]
for expectedPath in expectedPaths {
XCTAssertTrue(subprojectURLs.contains { $0.path == expectedPath }, "Expected path not found: \(expectedPath)")
}
}
func deleteDirectoryIfExists(at url: URL) {
if FileManager.default.fileExists(atPath: url.path) {
do {
try FileManager.default.removeItem(at: url)
} catch {
print("Failed to delete directory at \(url.path)")
}
}
}
func createTemporaryDirectory() throws -> URL {
let temporaryDirectoryURL = FileManager.default.temporaryDirectory
let directoryName = UUID().uuidString
let directoryURL = temporaryDirectoryURL.appendingPathComponent(directoryName)
try FileManager.default.createDirectory(at: directoryURL, withIntermediateDirectories: true, attributes: nil)
#if DEBUG
print("Create temp directory \(directoryURL.path)")
#endif
return directoryURL
}
func createSubdirectory(in directory: URL, withName name: String) throws -> URL {
let subdirectoryURL = directory.appendingPathComponent(name)
try FileManager.default.createDirectory(at: subdirectoryURL, withIntermediateDirectories: true, attributes: nil)
return subdirectoryURL
}
func createFile(in directory: URL, withName name: String, contents: String) throws -> URL {
let fileURL = directory.appendingPathComponent(name)
let data = contents.data(using: .utf8)
FileManager.default.createFile(atPath: fileURL.path, contents: data, attributes: nil)
return fileURL
}
func createXCProjectFolder(in baseDirectory: URL, withName projectName: String) throws -> URL {
let projectURL = try createSubdirectory(in: baseDirectory, withName: projectName)
if projectName.hasSuffix(".xcodeproj") {
_ = try createFile(in: projectURL, withName: "project.pbxproj", contents: "// Project file contents")
}
return projectURL
}
func createXCWorkspaceFolder(in baseDirectory: URL, withName workspaceName: String, fileRefs: [String]?) throws -> URL {
let xcworkspaceURL = try createSubdirectory(in: baseDirectory, withName: workspaceName)
if let fileRefs {
_ = try createXCworkspacedataFile(directory: xcworkspaceURL, fileRefs: fileRefs)
}
return xcworkspaceURL
}
func createXCWorkspaceFolder(in baseDirectory: URL, withName workspaceName: String, xcworkspacedata: String) throws -> URL {
let xcworkspaceURL = try createSubdirectory(in: baseDirectory, withName: workspaceName)
_ = try createFile(in: xcworkspaceURL, withName: "contents.xcworkspacedata", contents: xcworkspacedata)
return xcworkspaceURL
}
func createXCworkspacedataFile(directory: URL, fileRefs: [String]) throws -> URL {
let contents = generateXCWorkspacedataContents(fileRefs: fileRefs)
return try createFile(in: directory, withName: "contents.xcworkspacedata", contents: contents)
}
func generateXCWorkspacedataContents(fileRefs: [String]) -> String {
var contents = """
"""
for fileRef in fileRefs {
contents += """
"""
}
contents += ""
return contents
}
func testIsValidFile() throws {
let tmpDir = try createTemporaryDirectory()
defer {
deleteDirectoryIfExists(at: tmpDir)
}
do {
// Test valid Swift file
let swiftFileURL = try createFile(in: tmpDir, withName: "ValidFile.swift", contents: "// Swift code")
XCTAssertTrue(try WorkspaceFile.isValidFile(swiftFileURL))
// Test valid files with different supported extensions
let jsFileURL = try createFile(in: tmpDir, withName: "script.js", contents: "// JavaScript")
XCTAssertTrue(try WorkspaceFile.isValidFile(jsFileURL))
let mdFileURL = try createFile(in: tmpDir, withName: "README.md", contents: "# Markdown")
XCTAssertTrue(try WorkspaceFile.isValidFile(mdFileURL))
let jsonFileURL = try createFile(in: tmpDir, withName: "config.json", contents: "{}")
XCTAssertTrue(try WorkspaceFile.isValidFile(jsonFileURL))
// Test case insensitive extension matching
let swiftUpperURL = try createFile(in: tmpDir, withName: "File.SWIFT", contents: "// Swift")
XCTAssertTrue(try WorkspaceFile.isValidFile(swiftUpperURL))
// Test unsupported file extension
let unsupportedFileURL = try createFile(in: tmpDir, withName: "file.xyz", contents: "unsupported")
XCTAssertFalse(try WorkspaceFile.isValidFile(unsupportedFileURL))
// Test files matching skip patterns
let gitFileURL = try createFile(in: tmpDir, withName: ".git", contents: "")
XCTAssertFalse(try WorkspaceFile.isValidFile(gitFileURL))
let dsStoreURL = try createFile(in: tmpDir, withName: ".DS_Store", contents: "")
XCTAssertFalse(try WorkspaceFile.isValidFile(dsStoreURL))
let nodeModulesURL = try createFile(in: tmpDir, withName: "node_modules", contents: "")
XCTAssertFalse(try WorkspaceFile.isValidFile(nodeModulesURL))
// Test directory (should return false)
let subdirURL = try createSubdirectory(in: tmpDir, withName: "subdir")
XCTAssertFalse(try WorkspaceFile.isValidFile(subdirURL))
// Test Xcode workspace (should return false)
let xcworkspaceURL = try createSubdirectory(in: tmpDir, withName: "test.xcworkspace")
_ = try createFile(in: xcworkspaceURL, withName: "contents.xcworkspacedata", contents: "")
XCTAssertFalse(try WorkspaceFile.isValidFile(xcworkspaceURL))
// Test Xcode project (should return false)
let xcprojectURL = try createSubdirectory(in: tmpDir, withName: "test.xcodeproj")
_ = try createFile(in: xcprojectURL, withName: "project.pbxproj", contents: "")
XCTAssertFalse(try WorkspaceFile.isValidFile(xcprojectURL))
} catch {
throw error
}
}
func testIsValidFileWithCustomExclusionFilter() throws {
let tmpDir = try createTemporaryDirectory()
defer {
deleteDirectoryIfExists(at: tmpDir)
}
do {
let swiftFileURL = try createFile(in: tmpDir, withName: "TestFile.swift", contents: "// Swift code")
let jsFileURL = try createFile(in: tmpDir, withName: "script.js", contents: "// JavaScript")
// Test without custom exclusion filter
XCTAssertTrue(try WorkspaceFile.isValidFile(swiftFileURL))
XCTAssertTrue(try WorkspaceFile.isValidFile(jsFileURL))
// Test with custom exclusion filter that excludes Swift files
let excludeSwiftFilter: (URL) -> Bool = { url in
return url.pathExtension.lowercased() == "swift"
}
XCTAssertFalse(try WorkspaceFile.isValidFile(swiftFileURL, shouldExcludeFile: excludeSwiftFilter))
XCTAssertTrue(try WorkspaceFile.isValidFile(jsFileURL, shouldExcludeFile: excludeSwiftFilter))
// Test with custom exclusion filter that excludes files with "Test" in name
let excludeTestFilter: (URL) -> Bool = { url in
return url.lastPathComponent.contains("Test")
}
XCTAssertFalse(try WorkspaceFile.isValidFile(swiftFileURL, shouldExcludeFile: excludeTestFilter))
XCTAssertTrue(try WorkspaceFile.isValidFile(jsFileURL, shouldExcludeFile: excludeTestFilter))
} catch {
throw error
}
}
func testIsValidFileWithAllSupportedExtensions() throws {
let tmpDir = try createTemporaryDirectory()
defer {
deleteDirectoryIfExists(at: tmpDir)
}
do {
let supportedExtensions = supportedFileExtensions
for (index, ext) in supportedExtensions.enumerated() {
let fileName = "testfile\(index).\(ext)"
let fileURL = try createFile(in: tmpDir, withName: fileName, contents: "test content")
XCTAssertTrue(try WorkspaceFile.isValidFile(fileURL), "File with extension .\(ext) should be valid")
}
} catch {
throw error
}
}
}