diff --git a/CHANGELOG.md b/CHANGELOG.md index 3b4bea5103..41b708b1ac 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -48,6 +48,10 @@ environments, such as in Swift Package Manager plugins. [Juozas Valancius](https://github.com/juozasvalancius) +* Add ability to run only one (focused) example + [PaulTaykalo](https://github.com/PaulTaykalo) + [#3911](https://github.com/realm/SwiftLint/issues/3911) + #### Bug Fixes * Extend `class_delegate_protocol` to correctly identify cases with the protocol diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 844b06187e..013039eb43 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -72,6 +72,19 @@ over time. This way adding a unit test for your new Rule is just a matter of adding a test case in `RulesTests.swift` which simply calls `verifyRule(YourNewRule.description)`. +For debugging purposes examples can be marked as `focused`. If there are any +focused examples found, then only those will be run when running tests. +``` +nonTriggeringExamples: [ + Example("let x: [Int]"), + Example("let x: [Int: String]").focused() // only this one will be run in tests +], +triggeringExamples: [ + Example("let x: ↓Array"), + Example("let x: ↓Dictionary") +] +``` + ### `ConfigurationProviderRule` If your rule supports user-configurable options via `.swiftlint.yml`, you can diff --git a/Source/SwiftLintFramework/Models/Example.swift b/Source/SwiftLintFramework/Models/Example.swift index f7de795945..ae0126400d 100644 --- a/Source/SwiftLintFramework/Models/Example.swift +++ b/Source/SwiftLintFramework/Models/Example.swift @@ -32,6 +32,9 @@ public struct Example { /// pathological use cases which are indeed important to test but not helpful for understanding can be /// hidden from the documentation with this option. let excludeFromDocumentation: Bool + + /// Specifies whether the test example should be the only example run during the current test case execution. + var isFocused: Bool } public extension Example { @@ -55,6 +58,7 @@ public extension Example { self.file = file self.line = line self.excludeFromDocumentation = excludeFromDocumentation + self.isFocused = false } /// Returns the same example, but with the `code` that is passed in @@ -69,6 +73,13 @@ public extension Example { func removingViolationMarkers() -> Example { return with(code: code.replacingOccurrences(of: "↓", with: "")) } + + /// Makes the current example focused. + func focused() -> Example { + var new = self + new.isFocused = true + return new + } } extension Example: Hashable { diff --git a/Tests/SwiftLintFrameworkTests/TestHelpers.swift b/Tests/SwiftLintFrameworkTests/TestHelpers.swift index 126018a914..ace7b93026 100644 --- a/Tests/SwiftLintFrameworkTests/TestHelpers.swift +++ b/Tests/SwiftLintFrameworkTests/TestHelpers.swift @@ -356,7 +356,11 @@ extension XCTestCase { requiresFileOnDisk: ruleDescription.requiresFileOnDisk, file: file, line: line) } + func makeViolations(_ example: Example) -> [StyleViolation] { + return violations(example, config: config, requiresFileOnDisk: ruleDescription.requiresFileOnDisk) + } + let ruleDescription = ruleDescription.focused() let triggers = ruleDescription.triggeringExamples let nonTriggers = ruleDescription.nonTriggeringExamples verify(triggers: triggers, nonTriggers: nonTriggers) @@ -369,10 +373,6 @@ extension XCTestCase { verify(triggers: triggers.map(addShebang), nonTriggers: nonTriggers.map(addShebang)) } - func makeViolations(_ example: Example) -> [StyleViolation] { - return violations(example, config: config, requiresFileOnDisk: ruleDescription.requiresFileOnDisk) - } - // Comment doesn't violate if !skipCommentTests { XCTAssertEqual( @@ -401,6 +401,8 @@ extension XCTestCase { func verifyCorrections(_ ruleDescription: RuleDescription, config: Configuration, disableCommands: [String], testMultiByteOffsets: Bool) { + let ruleDescription = ruleDescription.focused() + parserDiagnosticsDisabledForTests = true // corrections @@ -509,3 +511,37 @@ extension XCTestCase { } } } + +private struct FocusedRuleDescription { + let nonTriggeringExamples: [Example] + let triggeringExamples: [Example] + let corrections: [Example: Example] + + init(rule: RuleDescription) { + let nonTriggering = rule.nonTriggeringExamples.filter(\.isFocused) + let triggering = rule.triggeringExamples.filter(\.isFocused) + let corrections = rule.corrections.filter { _, value in value.isFocused } + let anyFocused = nonTriggering.isNotEmpty || triggering.isNotEmpty || corrections.isNotEmpty + + if anyFocused { + self.nonTriggeringExamples = nonTriggering + self.triggeringExamples = triggering + self.corrections = corrections +#if DISABLE_FOCUSED_EXAMPLES + (nonTriggering + triggering + corrections.values).forEach { example in + XCTFail("Focused examples are disabled", file: example.file, line: example.line) + } +#endif + } else { + self.nonTriggeringExamples = rule.nonTriggeringExamples + self.triggeringExamples = rule.triggeringExamples + self.corrections = rule.corrections + } + } +} + +private extension RuleDescription { + func focused() -> FocusedRuleDescription { + return FocusedRuleDescription(rule: self) + } +} diff --git a/azure-pipelines.yml b/azure-pipelines.yml index f2d38ceed3..3e56554785 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -14,7 +14,7 @@ jobs: containerImage: swift:5.6 container: $[ variables['containerImage'] ] steps: - - script: swift test --parallel + - script: swift test --parallel -Xswiftc -DDISABLE_FOCUSED_EXAMPLES displayName: swift test - job: Xcode @@ -32,7 +32,7 @@ jobs: sw_vers xcodebuild -version displayName: Version Informations - - script: xcodebuild -scheme swiftlint test -destination "platform=macOS" + - script: xcodebuild -scheme swiftlint test -destination "platform=macOS" OTHER_SWIFT_FLAGS="-D DISABLE_FOCUSED_EXAMPLES" displayName: xcodebuild test - job: SwiftPM @@ -50,7 +50,7 @@ jobs: sw_vers xcodebuild -version displayName: Version Informations - - script: swift test --parallel --enable-code-coverage + - script: swift test --parallel --enable-code-coverage -Xswiftc -DDISABLE_FOCUSED_EXAMPLES displayName: swift test - script: | xcrun llvm-cov export -format="lcov" .build/debug/SwiftLintPackageTests.xctest/Contents/MacOS/SwiftLintPackageTests -instr-profile .build/debug/codecov/default.profdata > coverage.lcov