Files
SwiftLint/Tests/IntegrationTests/ConfigPathResolutionTests.swift
Danny Mösch 90915d9497 Adapt build scripts for Swift Testing
* Disable tests on macOS 14/Xcode 16.2
* Remove `--parallel` which is the default
* Set reasonable test timeouts
2026-06-06 11:07:04 +02:00

232 lines
8.1 KiB
Swift

import Foundation
import SourceKittenFramework
import SwiftLintFramework
import TestHelpers
import Testing
@testable import SwiftLintCore
@Suite(.rulesRegistered)
struct ConfigPathResolutionTests {
private func fixturePath(_ scenario: String) -> URL {
#filePath.url(directoryHint: .isDirectory)
.deletingLastPathComponent()
.appending(path: "Resources", directoryHint: .isDirectory)
.appending(path: scenario, directoryHint: .isDirectory)
}
/// Returns the paths of lintable files relative to the fixture directory.
private func lintableFilePaths(in scenario: String, configFile: String? = nil, inPath: String? = nil) -> [String] {
let scenarioPath = fixturePath(scenario)
return CurrentWorkingDirectory.$url.withValue(scenarioPath) {
let config = Configuration(configurationFiles: configFile.map { [$0.url()] } ?? [])
let files = config.lintableFiles(
inPath: inPath.map { $0.url() } ?? URL.cwd,
forceExclude: false,
excludeByPrefix: false
)
// swiftlint:disable:next force_try
return files.map { $0.path!.path.replacing(try! Regex(".+/\(scenario)/"), with: "") }.sorted()
}
}
@Test
func parentChildSameDirectory() {
#expect(
lintableFilePaths(in: "_1_parent_child_same_dir", configFile: "parent.yml") == ["Sources/CoreFile.swift"]
)
}
@Test
func parentChildDifferentDirectories() {
#expect(
lintableFilePaths(
in: "_2_parent_child_different_dirs",
configFile: "project/.swiftlint.yml",
inPath: "project"
) == ["project/Sources/Core/Service.swift"]
)
}
@Test
func childOverridesParentExclusion() {
#expect(
lintableFilePaths(
in: "_3_child_overrides_parent_exclusion",
configFile: "project/.swiftlint.yml",
inPath: "project"
) == ["project/Vendor/Critical/Important.swift"]
)
}
@Test
func parentIncludesChildExcludes() {
#expect(
lintableFilePaths(in: "_1_parent_child_same_dir", configFile: "parent.yml") == ["Sources/CoreFile.swift"]
)
}
@Test
func nestedConfigurationBasic() {
#expect(
lintableFilePaths(in: "_4_nested_basic", configFile: ".swiftlint.yml")
== ["ModuleA/File.swift", "ModuleA/Generated/File.swift", "ModuleB/File.swift"]
)
}
@Test
func wildcardPatternCount() {
#expect(
lintableFilePaths(
in: "_5_wildcard_patterns",
configFile: "project/.swiftlint.yml",
inPath: "project"
) == ["project/Sources/Models/User.swift"]
)
}
@Test
func lintChildFolder() {
#expect(
lintableFilePaths(
in: "_2_parent_child_different_dirs",
configFile: "project/.swiftlint.yml",
inPath: "project"
) == ["project/Sources/Core/Service.swift"]
)
}
@Test
func emptyIncludedDefaultsToAll() {
#expect(
lintableFilePaths(
in: "_6_wildcards_from_nested_folder",
configFile: ".swiftlint-exclude-thirdparty.yml"
) == [
"Generated/API.swift",
"MyProject/Package.swift",
"MyProject/Sources/App.swift",
"MyProject/SubModule/Package.swift",
]
)
}
@Test
func multipleLevelsOfExclusion() {
#expect(
lintableFilePaths(in: "_1_parent_child_same_dir", configFile: "parent.yml") == ["Sources/CoreFile.swift"]
)
}
@Test
func configFromParentFolder() {
#expect(
lintableFilePaths(in: "_6_wildcards_from_nested_folder", configFile: ".swiftlint.yml")
== ["MyProject/Sources/App.swift"]
)
#expect(
lintableFilePaths(in: "_6_wildcards_from_nested_folder/MyProject", configFile: "../.swiftlint.yml")
== ["Sources/App.swift"]
)
}
@Test
func nestedConfigurationAppliesOnlyToSubdirectory() throws {
let scenarioPath = fixturePath("_4_nested_basic")
try CurrentWorkingDirectory.$url.withValue(scenarioPath) {
let config = Configuration(configurationFiles: [])
let moduleAFile = try #require(
SwiftLintFile(path: scenarioPath.appending(path: "ModuleA/File.swift"))
)
let moduleBFile = try #require(
SwiftLintFile(path: scenarioPath.appending(path: "ModuleB/File.swift"))
)
#expect(
config.configuration(for: moduleAFile).rules
.map { type(of: $0).identifier }
.contains("explicit_type_interface")
)
#expect(
!config.configuration(for: moduleBFile).rules
.map { type(of: $0).identifier }
.contains("explicit_type_interface")
)
}
}
@Test
func nestedConfigurationDisabledByConfigFlag() {
let scenarioPath = fixturePath("_4_nested_basic")
let moduleAFile = SwiftLintFile(
path: scenarioPath.appending(path: "ModuleB/File.swift")
)!
#expect(
!Configuration(configurationFiles: [scenarioPath.appending(path: "root.yml")])
.configuration(for: moduleAFile)
.rules
.map { type(of: $0).identifier }
.contains("explicit_type_interface")
)
}
#if !os(Windows)
@Test(.enabled(
if: ProcessInfo.processInfo.environment["SWIFTLINT_BAZEL_TEST"] == nil,
"Bazel's sandboxed environment uses symlinks heavily breaking the fixture setup"
))
func symlinkedFileAndFolderAreFollowed() throws {
let expectedPaths = ["Real/Folder/Nested.swift", "Real/Target.swift"]
// With symlinks
#expect(lintableFilePaths(in: "_9_symlinked_paths", configFile: ".swiftlint.yml") == expectedPaths)
let fixture = fixturePath("_9_symlinked_paths")
let fileLink = fixture.appending(path: "LinkToFile.swift", directoryHint: .notDirectory)
var folderLink = fixture.appending(path: "LinkToFolder", directoryHint: .isDirectory)
let targetFile = fixture.appending(path: "Real/Target.swift", directoryHint: .notDirectory)
let targetFolder = fixture.appending(path: "Real/Folder", directoryHint: .isDirectory)
let fileManager = FileManager.default
#expect(fileManager.fileExists(atPath: fileLink.filepath))
#expect(fileManager.fileExists(atPath: folderLink.filepath))
#expect(try fileLink.resourceValues(forKeys: [.isSymbolicLinkKey]).isSymbolicLink == true)
#expect(try folderLink.resourceValues(forKeys: [.isSymbolicLinkKey]).isSymbolicLink == true)
#expect(fileLink.resolvingSymlinksInPath() == targetFile)
#expect(folderLink != targetFile)
folderLink.resolveSymlinksInPath()
#expect(try folderLink.resourceValues(forKeys: [.isSymbolicLinkKey]).isSymbolicLink == false)
#expect(folderLink == targetFolder)
// Without symlinks
#expect(lintableFilePaths(in: "_9_symlinked_paths", configFile: ".swiftlint.yml") == expectedPaths)
}
#endif
#if !os(Windows)
@Test
func unicodePrivateUseAreaCharacterInPath() throws {
let fixture = fixturePath("_8_unicode_private_use_area")
let process = Process()
process.executableURL = URL(filePath: "/usr/bin/env", directoryHint: .notDirectory)
process.arguments = ["unzip", "-o", fixture.appending(path: "app.zip").filepath, "-d", fixture.filepath]
try process.run()
process.waitUntilExit()
defer { try? FileManager.default.removeItem(at: fixture.appending(path: "App")) }
#expect(
lintableFilePaths(in: "_8_unicode_private_use_area/App")
== ["Resources/Settings.bundle/androidx.core:core-bundle.swift"]
)
}
#endif
}