Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
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
20 changes: 18 additions & 2 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ If you have commit access, you can run the required tests by commenting the foll

If you do not have commit access, please ask one of the code owners to trigger them for you.
For more details on Swift-DocC's continuous integration, see the
[Continous Integration](#continuous-integration) section below.
[Continuous Integration](#continuous-integration) section below.

### Introducing source breaking changes

Expand Down Expand Up @@ -207,7 +207,23 @@ by navigating to the root of the repository and running the following:
By running tests locally with the `test` script you will be best prepared for
automated testing in CI as well.

### Testing in Xcode
### Adding new tests

We recommend that you use [Swift Testing](https://developer.apple.com/documentation/testing) when you add new tests.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
We recommend that you use [Swift Testing](https://developer.apple.com/documentation/testing) when you add new tests.
Please use [Swift Testing](https://developer.apple.com/documentation/testing) when you add new tests.

Maybe make this stronger? I wasn't sure how heavily you wanted to lean into swift-testing.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure what amount of assertiveness (if that's the correct word) is right for this messaging. That's precisely what I hoped to get feedback on in this PR.

The team has previously talked about using Swift Testing for new tests and to slowly and incrementally transition existing tests.

Currently there are few existing tests to draw inspiration from, so here are a few recommendations:

- Prefer small test inputs that ideally use a virtual file system for both reading and writing.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we have any notes or internal docs on what functions to use, or examples to reference, that use the virtual file system setup? That would be great to point to, if we can.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can probably update each bullet to include an example that's both using Swift Testing and one that uses XCTest.

- Consider using parameterized tests if you're making the same verifications in multiple configurations or on multiple elements.
- Think about what information would be helpful to someone else who might debug that test case if it fails in the future.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wasn't quite sure what it is that I should do with the thinking about this. I get the reason you're asking for the consideration, but am unsure of how to apply that consideration into a test. Any suggestions?

- Use `#require` rather that force unwrapping for behaviors that would change due to unexpected bugs in the code you're testing.

If you're updating an existing test case with additional logic, we appreciate it if you also modernize that test, but we don't expect it.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
If you're updating an existing test case with additional logic, we appreciate it if you also modernize that test, but we don't expect it.
If you're updating an existing test case with additional logic, we appreciate if you also modernize that test while updating it, but we don't expect it.

If the test case is part of a large file, you can create new test suite which contains just the test case that you're modernizing.

If you modernize an existing test case, consider not only the syntactical differences between Swift Testing and XCTest,
but also if there are any Swift Testing features or other changes that would make the test case easier to read, maintain, or debug.

### Testing DocC's integration with Xcode

You can test a locally built version of Swift-DocC in Xcode 13 or later by setting
the `DOCC_EXEC` build setting to the path of your local `docc`:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,30 +8,31 @@
See https://swift.org/CONTRIBUTORS.txt for Swift project authors
*/

import XCTest
import Testing
import Markdown
@testable import SwiftDocC
import SwiftDocCTestUtilities

class NonInclusiveLanguageCheckerTests: XCTestCase {

struct NonInclusiveLanguageCheckerTests {
@Test
func testMatchTermInTitle() throws {
let source = """
# A Whitelisted title
"""
let document = Document(parsing: source)
var checker = NonInclusiveLanguageChecker(sourceFile: nil)
checker.visit(document)
XCTAssertEqual(checker.problems.count, 1)

let problem = try XCTUnwrap(checker.problems.first)
let range = try XCTUnwrap(problem.diagnostic.range)
XCTAssertEqual(range.lowerBound.line, 1)
XCTAssertEqual(range.lowerBound.column, 5)
XCTAssertEqual(range.upperBound.line, 1)
XCTAssertEqual(range.upperBound.column, 16)
#expect(checker.problems.count == 1)

let problem = try #require(checker.problems.first)
let range = try #require(problem.diagnostic.range)
#expect(range.lowerBound.line == 1)
#expect(range.lowerBound.column == 5)
#expect(range.upperBound.line == 1)
#expect(range.upperBound.column == 16)
}

@Test
func testMatchTermWithSpaces() throws {
let source = """
# A White listed title
Expand All @@ -41,30 +42,31 @@ class NonInclusiveLanguageCheckerTests: XCTestCase {
let document = Document(parsing: source)
var checker = NonInclusiveLanguageChecker(sourceFile: nil)
checker.visit(document)
XCTAssertEqual(checker.problems.count, 3)

let problem = try XCTUnwrap(checker.problems.first)
let range = try XCTUnwrap(problem.diagnostic.range)
XCTAssertEqual(range.lowerBound.line, 1)
XCTAssertEqual(range.lowerBound.column, 5)
XCTAssertEqual(range.upperBound.line, 1)
XCTAssertEqual(range.upperBound.column, 18)

let problemTwo = try XCTUnwrap(checker.problems[1])
let rangeTwo = try XCTUnwrap(problemTwo.diagnostic.range)
XCTAssertEqual(rangeTwo.lowerBound.line, 2)
XCTAssertEqual(rangeTwo.lowerBound.column, 5)
XCTAssertEqual(rangeTwo.upperBound.line, 2)
XCTAssertEqual(rangeTwo.upperBound.column, 20)

let problemThree = try XCTUnwrap(checker.problems[2])
let rangeThree = try XCTUnwrap(problemThree.diagnostic.range)
XCTAssertEqual(rangeThree.lowerBound.line, 3)
XCTAssertEqual(rangeThree.lowerBound.column, 5)
XCTAssertEqual(rangeThree.upperBound.line, 3)
XCTAssertEqual(rangeThree.upperBound.column, 17)
#expect(checker.problems.count == 3)

let problem = try #require(checker.problems.first)
let range = try #require(problem.diagnostic.range)
#expect(range.lowerBound.line == 1)
#expect(range.lowerBound.column == 5)
#expect(range.upperBound.line == 1)
#expect(range.upperBound.column == 18)

let problemTwo = try #require(checker.problems.dropFirst(1).first)
let rangeTwo = try #require(problemTwo.diagnostic.range)
#expect(rangeTwo.lowerBound.line == 2)
#expect(rangeTwo.lowerBound.column == 5)
#expect(rangeTwo.upperBound.line == 2)
#expect(rangeTwo.upperBound.column == 20)

let problemThree = try #require(checker.problems.dropFirst(2).first)
let rangeThree = try #require(problemThree.diagnostic.range)
#expect(rangeThree.lowerBound.line == 3)
#expect(rangeThree.lowerBound.column == 5)
#expect(rangeThree.upperBound.line == 3)
#expect(rangeThree.upperBound.column == 17)
}

@Test
func testMatchTermInAbstract() throws {
let source = """
# Title
Expand All @@ -74,16 +76,17 @@ The blacklist is in the abstract.
let document = Document(parsing: source)
var checker = NonInclusiveLanguageChecker(sourceFile: nil)
checker.visit(document)
XCTAssertEqual(checker.problems.count, 1)

let problem = try XCTUnwrap(checker.problems.first)
let range = try XCTUnwrap(problem.diagnostic.range)
XCTAssertEqual(range.lowerBound.line, 3)
XCTAssertEqual(range.lowerBound.column, 5)
XCTAssertEqual(range.upperBound.line, 3)
XCTAssertEqual(range.upperBound.column, 14)
#expect(checker.problems.count == 1)

let problem = try #require(checker.problems.first)
let range = try #require(problem.diagnostic.range)
#expect(range.lowerBound.line == 3)
#expect(range.lowerBound.column == 5)
#expect(range.upperBound.line == 3)
#expect(range.upperBound.column == 14)
}

@Test
func testMatchTermInParagraph() throws {
let source = """
# Title
Expand All @@ -98,16 +101,17 @@ master branch is the default.
let document = Document(parsing: source)
var checker = NonInclusiveLanguageChecker(sourceFile: nil)
checker.visit(document)
XCTAssertEqual(checker.problems.count, 1)

let problem = try XCTUnwrap(checker.problems.first)
let range = try XCTUnwrap(problem.diagnostic.range)
XCTAssertEqual(range.lowerBound.line, 8)
XCTAssertEqual(range.lowerBound.column, 1)
XCTAssertEqual(range.upperBound.line, 8)
XCTAssertEqual(range.upperBound.column, 7)
#expect(checker.problems.count == 1)

let problem = try #require(checker.problems.first)
let range = try #require(problem.diagnostic.range)
#expect(range.lowerBound.line == 8)
#expect(range.lowerBound.column == 1)
#expect(range.upperBound.line == 8)
#expect(range.upperBound.column == 7)
}

@Test
func testMatchTermInList() throws {
let source = """
- Item 1 is ok
Expand All @@ -117,33 +121,35 @@ master branch is the default.
let document = Document(parsing: source)
var checker = NonInclusiveLanguageChecker(sourceFile: nil)
checker.visit(document)
XCTAssertEqual(checker.problems.count, 1)

let problem = try XCTUnwrap(checker.problems.first)
let range = try XCTUnwrap(problem.diagnostic.range)
XCTAssertEqual(range.lowerBound.line, 2)
XCTAssertEqual(range.lowerBound.column, 13)
XCTAssertEqual(range.upperBound.line, 2)
XCTAssertEqual(range.upperBound.column, 24)
#expect(checker.problems.count == 1)

let problem = try #require(checker.problems.first)
let range = try #require(problem.diagnostic.range)
#expect(range.lowerBound.line == 2)
#expect(range.lowerBound.column == 13)
#expect(range.upperBound.line == 2)
#expect(range.upperBound.column == 24)
}

@Test
func testMatchTermInInlineCode() throws {
let source = """
The name `MachineSlave` is unacceptable.
"""
let document = Document(parsing: source)
var checker = NonInclusiveLanguageChecker(sourceFile: nil)
checker.visit(document)
XCTAssertEqual(checker.problems.count, 1)

let problem = try XCTUnwrap(checker.problems.first)
let range = try XCTUnwrap(problem.diagnostic.range)
XCTAssertEqual(range.lowerBound.line, 1)
XCTAssertEqual(range.lowerBound.column, 18)
XCTAssertEqual(range.upperBound.line, 1)
XCTAssertEqual(range.upperBound.column, 23)
#expect(checker.problems.count == 1)

let problem = try #require(checker.problems.first)
let range = try #require(problem.diagnostic.range)
#expect(range.lowerBound.line == 1)
#expect(range.lowerBound.column == 18)
#expect(range.upperBound.line == 1)
#expect(range.upperBound.column == 23)
}

@Test
func testMatchTermInCodeBlock() throws {
let source = """
A code block:
Expand All @@ -158,13 +164,13 @@ func aBlackListedFunc() {
let document = Document(parsing: source)
var checker = NonInclusiveLanguageChecker(sourceFile: nil)
checker.visit(document)
XCTAssertEqual(checker.problems.count, 1)
let problem = try XCTUnwrap(checker.problems.first)
let range = try XCTUnwrap(problem.diagnostic.range)
XCTAssertEqual(range.lowerBound.line, 5)
XCTAssertEqual(range.lowerBound.column, 7)
XCTAssertEqual(range.upperBound.line, 5)
XCTAssertEqual(range.upperBound.column, 18)
#expect(checker.problems.count == 1)
let problem = try #require(checker.problems.first)
let range = try #require(problem.diagnostic.range)
#expect(range.lowerBound.line == 5)
#expect(range.lowerBound.column == 7)
#expect(range.upperBound.line == 5)
#expect(range.upperBound.column == 18)
}

private let nonInclusiveContent = """
Expand All @@ -177,36 +183,32 @@ func aBlackListedFunc() {
- item three
"""

@Test
func testDisabledByDefault() async throws {
// Create a test bundle with some non-inclusive content.
let catalog = Folder(name: "unit-test.docc", content: [
TextFile(name: "Root.md", utf8Content: nonInclusiveContent)
])
let (_, context) = try await loadBundle(catalog: catalog)
let context = try await load(catalog: catalog)

XCTAssertEqual(context.problems.count, 0) // Non-inclusive content is an info-level diagnostic, so it's filtered out.
#expect(context.problems.isEmpty) // Non-inclusive content is an info-level diagnostic, so it's filtered out.
}

func testEnablingTheChecker() async throws {
// The expectations of the checker being run, depending on the diagnostic level
// set to to the documentation context for the compilation.
let expectations: [(DiagnosticSeverity, Bool)] = [
(.hint, true),
(.information, true),
(.warning, false),
(.error, false),
]

for (severity, enabled) in expectations {
let catalog = Folder(name: "unit-test.docc", content: [
TextFile(name: "Root.md", utf8Content: nonInclusiveContent)
])
var configuration = DocumentationContext.Configuration()
configuration.externalMetadata.diagnosticLevel = severity
let (_, context) = try await loadBundle(catalog: catalog, diagnosticFilterLevel: severity, configuration: configuration)

// Verify that checker diagnostics were emitted or not, depending on the diagnostic level set.
XCTAssertEqual(context.problems.contains(where: { $0.diagnostic.identifier == "org.swift.docc.NonInclusiveLanguage" }), enabled)
}
@Test(arguments: [
DiagnosticSeverity.hint: true,
DiagnosticSeverity.information: true,
DiagnosticSeverity.warning: false,
DiagnosticSeverity.error: false,
])
func testEnablingTheChecker(configuredDiagnosticSeverity: DiagnosticSeverity, expectsToIncludeANonInclusiveDiagnostic: Bool) async throws {
let catalog = Folder(name: "unit-test.docc", content: [
TextFile(name: "Root.md", utf8Content: nonInclusiveContent)
])
var configuration = DocumentationContext.Configuration()
configuration.externalMetadata.diagnosticLevel = configuredDiagnosticSeverity
let context = try await load(catalog: catalog, diagnosticFilterLevel: configuredDiagnosticSeverity, configuration: configuration)

// Verify that checker diagnostics were emitted or not, depending on the diagnostic level set.
#expect(context.problems.contains(where: { $0.diagnostic.identifier == "org.swift.docc.NonInclusiveLanguage" }) == expectsToIncludeANonInclusiveDiagnostic)
}
}
Original file line number Diff line number Diff line change
@@ -1,53 +1,36 @@
/*
This source file is part of the Swift.org open source project

Copyright (c) 2021 Apple Inc. and the Swift project authors
Copyright (c) 2021-2025 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 Swift project authors
*/

import Foundation
import XCTest

import Testing
import Markdown

@testable import SwiftDocC

class ParseDirectiveArgumentsTests: XCTestCase {
func testEmitsWarningForMissingExpectedCharacter() throws {
let diagnostic = try XCTUnwrap(
parse(rawDirective: "@Directive(argument: multiple words)").first
)

XCTAssertEqual(diagnostic.identifier, "org.swift.docc.Directive.MissingExpectedCharacter")
XCTAssertEqual(diagnostic.severity, .warning)
}

func testEmitsWarningForUnexpectedCharacter() throws {
let diagnostic = try XCTUnwrap(
parse(rawDirective: "@Directive(argumentA: value, argumentB: multiple words").first
)

XCTAssertEqual(diagnostic.identifier, "org.swift.docc.Directive.MissingExpectedCharacter")
XCTAssertEqual(diagnostic.severity, .warning)
}

func testEmitsWarningsForDuplicateArgument() throws {
let diagnostic = try XCTUnwrap(
parse(rawDirective: "@Directive(argumentA: value, argumentA: value").first
)

XCTAssertEqual(diagnostic.identifier, "org.swift.docc.Directive.DuplicateArgument")
XCTAssertEqual(diagnostic.severity, .warning)
}

func parse(rawDirective: String) -> [Diagnostic] {
let document = Document(parsing: rawDirective, options: .parseBlockDirectives)

struct ParseDirectiveArgumentsTests {
@Test(arguments: [
// Missing quotation marks around string parameter
"@Directive(argument: multiple words)": "org.swift.docc.Directive.MissingExpectedCharacter",
// Missing quotation marks around string parameter in 2nd parameter
"@Directive(argumentA: value, argumentB: multiple words)": "org.swift.docc.Directive.MissingExpectedCharacter",
// Duplicate argument
"@Directive(argumentA: value, argumentA: value)": "org.swift.docc.Directive.DuplicateArgument",
])
func testEmitsWarningsForInvalidMarkup(invalidMarkup: String, expectedDiagnosticID: String) throws {
let document = Document(parsing: invalidMarkup, options: .parseBlockDirectives)
var problems = [Problem]()
_ = (document.child(at: 0) as? BlockDirective)?.arguments(problems: &problems)
return problems.map(\.diagnostic)

let diagnostic = try #require(problems.first?.diagnostic)

#expect(diagnostic.identifier == expectedDiagnosticID)
#expect(diagnostic.severity == .warning)
}
}
Loading