Files
swift-mirror/utils/swift-xcodegen/Tests/SwiftXcodeGenTest/CompileCommandsTests.swift
2025-11-20 14:32:13 +00:00

301 lines
7.4 KiB
Swift

//===--- CompileCommandsTests.swift ---------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2024 - 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 the list of Swift project authors
//
//===----------------------------------------------------------------------===//
import Testing
@testable import SwiftXcodeGen
fileprivate func assertParse(
_ str: String,
executable: String? = nil,
args: [Command.Argument],
knownCommandOnly: Bool = false,
sourceLocation: SourceLocation = #_sourceLocation
) {
do {
let command = try knownCommandOnly ? CommandParser.parseKnownCommandOnly(str)
: CommandParser.parseCommand(str)
guard let command else {
Issue.record("Failed to parse command")
return
}
if let executable {
#expect(executable == command.executable.rawPath, sourceLocation: sourceLocation)
}
#expect(args == command.args, sourceLocation: sourceLocation)
} catch {
Issue.record("\(error)", sourceLocation: sourceLocation)
}
}
@Suite
struct CompileCommandsTests {
@Test
func clangCommandParse() {
assertParse("x -a -b", executable: "x", args: [.value("-a"), .value("-b")])
assertParse("x -D -I", executable: "x", args: [.value("-D"), .value("-I")])
assertParse(
"x y clang -DX -I",
executable: "clang",
args: [.option(.D, spacing: .unspaced, value: "X"), .flag(.I)],
knownCommandOnly: true
)
assertParse(
"x y x/y/clang -DX -I",
executable: "x/y/clang",
args: [.option(.D, spacing: .unspaced, value: "X"), .flag(.I)],
knownCommandOnly: true
)
for op in ["&&", "||", ">", "<", ">>", ";", "(", ")"] {
assertParse(
"x y x/y/clang -DX -I \(op) ignored",
executable: "x/y/clang",
args: [.option(.D, spacing: .unspaced, value: "X"), .flag(.I)],
knownCommandOnly: true
)
assertParse(
"x y x/y/clang -DX -I x\(op) ignored",
executable: "x/y/clang",
args: [
.option(.D, spacing: .unspaced, value: "X"),
.option(.I, spacing: .spaced, value: "x")
],
knownCommandOnly: true
)
}
assertParse(
#"x/y/clang \< x\< "<""#,
executable: "x/y/clang",
args: [.value("<"), .value("x<"), .value("<")],
knownCommandOnly: true
)
assertParse(
"clang -DX -I",
args: [.option(.D, spacing: .unspaced, value: "X"), .flag(.I)]
)
assertParse("clang++ -D I", args: [
.option(.D, spacing: .spaced, value: "I")
])
assertParse("clang -DI", args: [
.option(.D, spacing: .unspaced, value: "I")
])
assertParse("clang -DIII", args: [
.option(.D, spacing: .unspaced, value: "III")
])
assertParse("clang -DIII I", args: [
.option(.D, spacing: .unspaced, value: "III"), .value("I")
])
assertParse(
#"clang -D"III" I"#, args: [
.option(.D, spacing: .unspaced, value: #"III"#), .value("I")
]
)
assertParse(
#"clang -D\"III\" -I"#, args: [
.option(.D, spacing: .unspaced, value: #""III""#), .flag(.I)
]
)
assertParse(
#"clang -D"a b" -I"#, args: [
.option(.D, spacing: .unspaced, value: #"a b"#), .flag(.I)
]
)
assertParse(
#"clang -Da\ b -I"#, args: [
.option(.D, spacing: .unspaced, value: #"a b"#), .flag(.I)
]
)
assertParse(
#"clang -I"III""#, args: [
.option(.I, spacing: .unspaced, value: #"III"#)
]
)
assertParse(
#"clang -I\"III\""#, args: [
.option(.I, spacing: .unspaced, value: #""III""#)
]
)
assertParse(
#"clang -I"a b""#, args: [
.option(.I, spacing: .unspaced, value: #"a b"#)
]
)
assertParse(
#"clang -Ia\ b"#, args: [
.option(.I, spacing: .unspaced, value: #"a b"#)
]
)
assertParse(
#"clang -I="III""#, args: [
.option(.I, spacing: .equals, value: #"III"#)
]
)
assertParse(
#"clang -I="#, args: [
.option(.I, spacing: .unspaced, value: #"="#)
]
)
assertParse(
#"clang -I=\"III\""#, args: [
.option(.I, spacing: .equals, value: #""III""#)
]
)
assertParse(
#"clang -I="a b""#, args: [
.option(.I, spacing: .equals, value: #"a b"#)
]
)
assertParse(
#"clang -I=a\ b"#, args: [
.option(.I, spacing: .equals, value: #"a b"#)
]
)
assertParse(
#"clang -Wnosomething"#, args: [
.option(.W, spacing: .unspaced, value: #"nosomething"#)
]
)
assertParse(
#"clang --I=a"#, args: [.value("--I=a")]
)
assertParse(
#"clang --Da"#, args: [.value("--Da")]
)
assertParse(
#"clang --Wa"#, args: [.value("--Wa")]
)
}
@Test
func swiftCommandParse() {
assertParse(
#"swiftc -FX"#,
args: [.option(.F, spacing: .unspaced, value: "X")]
)
assertParse(
#"swiftc -F X"#,
args: [.option(.F, spacing: .spaced, value: "X")]
)
assertParse(
#"swiftc -F=X"#,
args: [.option(.F, spacing: .equals, value: "X")]
)
assertParse(
#"swiftc -Fsystem X"#,
args: [.option(.Fsystem, spacing: .spaced, value: "X")]
)
}
@Test
func commandEscape() throws {
#expect(Command.Argument.flag(.I).printedArgs == ["-I"])
#expect(Command.Argument.value("hello").printedArgs == ["hello"])
#expect(Command.Argument.value("he llo").printedArgs == [#""he llo""#])
#expect(Command.Argument.value(#""hello""#).printedArgs == [#"\"hello\""#])
#expect(Command.Argument.value(#""he llo""#).printedArgs == [#""\"he llo\"""#])
#expect(
Command.Argument.option(
.I,
spacing: .unspaced,
value: "he llo"
).printedArgs == [#"-I"he llo""#]
)
#expect(
Command.Argument.option(
.I,
spacing: .spaced,
value: "he llo"
).printedArgs == ["-I", #""he llo""#]
)
#expect(
Command.Argument.option(
.I,
spacing: .unspaced,
value: #""he llo""#
).printedArgs == [#"-I"\"he llo\"""#]
)
#expect(
Command.Argument.option(
.I,
spacing: .spaced,
value: #""he llo""#
).printedArgs == ["-I", #""\"he llo\"""#]
)
#expect(
try CommandParser.parseCommand(#"swift \\ \ "#).printed == #"swift \\ " ""#
)
#expect(
try CommandParser.parseCommand(#"swift "\\ ""#).printed == #"swift "\\ ""#
)
}
@Test
func emptyArg() {
// The empty string immediately after '-I' is effectively ignored.
assertParse(#"swiftc -I"" """#, args: [
.option(.I, spacing: .spaced, value: ""),
])
assertParse(#"swiftc -I "" """#, args: [
.option(.I, spacing: .spaced, value: ""),
.value(""),
])
assertParse(#"swiftc -I "" "" "#, args: [
.option(.I, spacing: .spaced, value: ""),
.value(""),
])
assertParse(#"swiftc -I "#, args: [
.flag(.I),
])
}
@Test
func spaceBeforeCommand() {
assertParse(" swiftc ", executable: "swiftc", args: [])
assertParse("\t\tswiftc\t\ta b\t", executable: "swiftc", args: [
.value("a"),
.value("b"),
])
}
}