blob: 7d68f2f3d2e506d95f410d9c52c47abfd577ab35 [file] [log] [blame]
// RUN: %empty-directory(%t)
// RUN: %target-build-swift %s -swift-version 5 -DPTR_SIZE_%target-ptrsize -o %t/OSLogPrototypeExecTest
// RUN: %target-run %t/OSLogPrototypeExecTest
// REQUIRES: executable_test
// REQUIRES: OS=macosx || OS=ios || OS=tvos || OS=watchos
// Run-time tests for testing the new OS log APIs that accept string
// interpolations. The new APIs are still prototypes and must be used only in
// tests.
import OSLogPrototype
import StdlibUnittest
defer { runAllTests() }
internal var OSLogTestSuite = TestSuite("OSLogTest")
if #available(OSX 10.12, iOS 10.0, watchOS 3.0, tvOS 10.0, *) {
// Following tests check whether valid log calls execute without
// compile-time and run-time errors.
func logMessages(_ h: Logger) {
// Test logging of simple messages.
h.log("A message with no data")
// Test logging at specific levels.
h.log(level: .debug, "Minimum integer value: \(Int.min, format: .hex)")
h.log(level: .info, "Maximum integer value: \(Int.max, format: .hex)")
let privateID = 0x79abcdef
h.log(
level: .error,
"Private Identifier: \(privateID, format: .hex, privacy: .private)")
let addr = 0x7afebabe
h.log(
level: .fault,
"Invalid address: 0x\(addr, format: .hex, privacy: .public)")
// Test logging with multiple arguments.
let filePermissions = 0o777
let pid = 122225
h.log(
level: .error,
"""
Access prevented: process \(pid) initiated by \
user: \(privateID, privacy: .private) attempted resetting \
permissions to \(filePermissions, format: .octal)
""")
}
OSLogTestSuite.test("log with default logger") {
let h = Logger()
logMessages(h)
}
OSLogTestSuite.test("log with custom logger") {
let h =
Logger(subsystem: "com.swift.test", category: "OSLogAPIPrototypeTest")
logMessages(h)
}
OSLogTestSuite.test("escaping of percents") {
let h = Logger()
h.log("a = c % d")
h.log("Process failed after 99% completion")
h.log("Double percents: %%")
}
// A stress test that checks whether the log APIs handle messages with more
// than 48 interpolated expressions. Interpolated expressions beyond this
// limit must be ignored.
OSLogTestSuite.test("messages with too many arguments") {
let h = Logger()
h.log(
level: .error,
"""
\(1) \(1) \(1) \(1) \(1) \(1) \(1) \(1) \(1) \(1) \(1) \(1) \(1) \(1) \
\(1) \(1) \(1) \(1) \(1) \(1) \(1) \(1) \(1) \(1) \(1) \(1) \(1) \(1) \
\(1) \(1) \(1) \(1) \(1) \(1) \(1) \(1) \(1) \(1) \(1) \(1) \(1) \(1) \
\(1) \(1) \(1) \(1) \(1) \(48) \(49)
""") // The number 49 should not appear in the logged message.
}
OSLogTestSuite.test("escape characters") {
let h = Logger()
h.log("\"Imagination is more important than knowledge\" - Einstein")
h.log("\'Imagination is more important than knowledge\' - Einstein")
h.log("Imagination is more important than knowledge \n - Einstein")
h.log("Imagination is more important than knowledge - \\Einstein")
h.log("The log message will be truncated here.\0 You won't see this")
}
OSLogTestSuite.test("unicode characters") {
let h = Logger()
h.log("dollar sign: \u{24}")
h.log("black heart: \u{2665}")
h.log("sparkling heart: \u{1F496}")
}
OSLogTestSuite.test("raw strings") {
let h = Logger()
let x = 10
h.log(#"There is no \(interpolated) value in this string"#)
h.log(#"This is not escaped \n"#)
h.log(##"'\b' is a printf escape character but not in Swift"##)
h.log(##"The interpolated value is \##(x)"##)
h.log(#"Sparkling heart should appear in the next line. \#n \#u{1F496}"#)
}
}
// The following tests check the correctness of the format string and the
// byte buffer constructed by the APIs from a string interpolation.
// These tests do not perform logging and do not require the os_log ABIs to
// be available.
internal var InterpolationTestSuite = TestSuite("OSLogInterpolationTest")
internal let intPrefix = Int.bitWidth == CLongLong.bitWidth ? "ll" : ""
internal let bitsPerByte = 8
/// A struct that exposes methods for checking whether a given byte buffer
/// conforms to the format expected by the os_log ABI. This struct acts as
/// a specification of the byte buffer format.
internal struct OSLogBufferChecker {
internal let buffer: UnsafeBufferPointer<UInt8>
internal init(_ byteBuffer: UnsafeBufferPointer<UInt8>) {
buffer = byteBuffer
}
/// Bit mask for setting bits in the peamble. The bits denoted by the bit
/// mask indicate whether there is an argument that is private, and whether
/// there is an argument that is non-scalar: String, NSObject or Pointer.
internal enum PreambleBitMask: UInt8 {
case privateBitMask = 0x1
case nonScalarBitMask = 0x2
}
/// Check the summary bytes of the byte buffer.
/// - Parameters:
/// - argumentCount: number of arguments expected to be in the buffer.
/// - hasPrivate: true iff there exists a private argument
/// - hasNonScalar: true iff there exists a non-scalar argument
internal func checkSummaryBytes(
argumentCount: UInt8,
hasPrivate: Bool,
hasNonScalar: Bool
) {
let preamble = buffer[0]
let privateBit = preamble & PreambleBitMask.privateBitMask.rawValue
expectEqual(hasPrivate, privateBit != 0)
let nonScalarBit = preamble & PreambleBitMask.nonScalarBitMask.rawValue
expectEqual(hasNonScalar, nonScalarBit != 0)
expectEqual(argumentCount, buffer[1])
}
/// The possible values for the argument flag as defined by the os_log ABI.
/// This occupies four least significant bits of the first byte of the
/// argument header. Two least significant bits are used to indicate privacy
/// and the other two bits are reserved.
internal enum ArgumentFlag: UInt8 {
case privateFlag = 0x1
case publicFlag = 0x2
}
/// The possible values for the argument type as defined by the os_log ABI.
/// This occupies four most significant bits of the first byte of the
/// argument header.
internal enum ArgumentType: UInt8 {
case scalar = 0, count, string, pointer, object
// TODO: include wide string and errno here if needed.
}
/// Check the encoding of an argument in the byte buffer starting from the
/// `startIndex`.
/// - precondition: `T` must be a type that is accepted by os_log ABI.
private func checkArgument<T>(
startIndex: Int,
size: UInt8,
flag: ArgumentFlag,
type: ArgumentType,
expectedData: T
) {
let argumentHeader = buffer[startIndex]
expectEqual((type.rawValue << 4) | flag.rawValue, argumentHeader)
expectEqual(size, buffer[startIndex + 1])
// Check every byte of the payload.
withUnsafeBytes(of: expectedData) { expectedBytes in
for i in 0..<Int(size) {
// Argument data starts after the two header bytes.
expectEqual(
expectedBytes[i],
buffer[startIndex + 2 + i],
"mismatch at byte number \(i) of the expected value \(expectedData)")
}
}
}
/// Check whether the bytes starting from `startIndex` contain the encoding
/// for an Int.
internal func checkInt(
startIndex: Int,
flag: ArgumentFlag,
expectedInt: Int
) {
checkArgument(
startIndex: startIndex,
size: UInt8(MemoryLayout<Int>.size),
flag: flag,
type: .scalar,
expectedData: expectedInt)
}
/// Check the given assertions on the arguments stored in the byte buffer.
/// - Parameters:
/// - assertions: one assertion for each argument stored in the byte buffer.
internal func checkArguments(_ assertions: [(Int) -> Void]) {
var currentArgumentIndex = 2
for assertion in assertions {
expectTrue(currentArgumentIndex < buffer.count)
assertion(currentArgumentIndex)
// Advance the index to the next argument by adding the size of the
// current argument and the two bytes of headers.
currentArgumentIndex += 2 + Int(buffer[currentArgumentIndex + 1])
}
}
/// Check a sequence of assertions on the arguments stored in the byte buffer.
internal func checkArguments(_ assertions: (Int) -> Void ...) {
checkArguments(assertions)
}
}
InterpolationTestSuite.test("integer literal") {
_checkFormatStringAndBuffer(
"An integer literal \(10)",
with: { (formatString, buffer) in
expectEqual(
"An integer literal %{public}\(intPrefix)d",
formatString)
let bufferChecker = OSLogBufferChecker(buffer)
bufferChecker.checkSummaryBytes(
argumentCount: 1,
hasPrivate: false,
hasNonScalar: false)
bufferChecker.checkArguments({
bufferChecker.checkInt(startIndex: $0, flag: .publicFlag, expectedInt: 10)
})
})
}
InterpolationTestSuite.test("integer with formatting") {
_checkFormatStringAndBuffer(
"Minimum integer value: \(Int.min, format: .hex)",
with: { (formatString, buffer) in
expectEqual(
"Minimum integer value: %{public}\(intPrefix)x",
formatString)
let bufferChecker = OSLogBufferChecker(buffer)
bufferChecker.checkSummaryBytes(
argumentCount: 1,
hasPrivate: false,
hasNonScalar: false)
bufferChecker.checkArguments({
bufferChecker.checkInt(
startIndex: $0,
flag: .publicFlag,
expectedInt: Int.min)
})
})
}
InterpolationTestSuite.test("integer with privacy and formatting") {
let addr = 0x7afebabe
_checkFormatStringAndBuffer(
"Access to invalid address: \(addr, format: .hex, privacy: .private)",
with: { (formatString, buffer) in
expectEqual(
"Access to invalid address: %{private}\(intPrefix)x",
formatString)
let bufferChecker = OSLogBufferChecker(buffer)
bufferChecker.checkSummaryBytes(
argumentCount: 1,
hasPrivate: true,
hasNonScalar: false)
bufferChecker.checkArguments({
bufferChecker.checkInt(
startIndex: $0,
flag: .privateFlag,
expectedInt: addr)
})
})
}
InterpolationTestSuite.test("integer with privacy and formatting") {
let addr = 0x7afebabe
_checkFormatStringAndBuffer(
"Access to invalid address: \(addr, format: .hex, privacy: .private)",
with: { (formatString, buffer) in
expectEqual(
"Access to invalid address: %{private}\(intPrefix)x",
formatString)
let bufferChecker = OSLogBufferChecker(buffer)
bufferChecker.checkSummaryBytes(
argumentCount: 1,
hasPrivate: true,
hasNonScalar: false)
bufferChecker.checkArguments({
bufferChecker.checkInt(
startIndex: $0,
flag: .privateFlag,
expectedInt: addr)
})
})
}
InterpolationTestSuite.test("test multiple arguments") {
let filePerms = 0o777
let pid = 122225
let privateID = 0x79abcdef
_checkFormatStringAndBuffer(
"""
Access prevented: process \(pid) initiated by \
user: \(privateID, privacy: .private) attempted resetting \
permissions to \(filePerms, format: .octal)
""",
with: { (formatString, buffer) in
expectEqual(
"""
Access prevented: process %{public}\(intPrefix)d initiated by \
user: %{private}\(intPrefix)d attempted resetting permissions \
to %{public}\(intPrefix)o
""",
formatString)
let bufferChecker = OSLogBufferChecker(buffer)
bufferChecker.checkSummaryBytes(
argumentCount: 3,
hasPrivate: true,
hasNonScalar: false)
bufferChecker.checkArguments(
{
bufferChecker.checkInt(
startIndex: $0,
flag: .publicFlag,
expectedInt: pid)
},
{
bufferChecker.checkInt(
startIndex: $0,
flag: .privateFlag,
expectedInt: privateID)
},
{
bufferChecker.checkInt(
startIndex: $0,
flag: .publicFlag,
expectedInt: filePerms)
})
})
}
InterpolationTestSuite.test("interpolation of too many arguments") {
// The following string interpolation has 49 interpolated values. Only 48
// of these must be present in the generated format string and byte buffer.
_checkFormatStringAndBuffer(
"""
\(1) \(1) \(1) \(1) \(1) \(1) \(1) \(1) \(1) \(1) \(1) \(1) \(1) \(1) \
\(1) \(1) \(1) \(1) \(1) \(1) \(1) \(1) \(1) \(1) \(1) \(1) \(1) \(1) \
\(1) \(1) \(1) \(1) \(1) \(1) \(1) \(1) \(1) \(1) \(1) \(1) \(1) \(1) \
\(1) \(1) \(1) \(1) \(1) \(1) \(1)
""",
with: { (formatString, buffer) in
expectEqual(
String(
repeating: "%{public}\(intPrefix)d ",
count: maxOSLogArgumentCount),
formatString)
let bufferChecker = OSLogBufferChecker(buffer)
bufferChecker.checkSummaryBytes(
argumentCount: UInt8(maxOSLogArgumentCount),
hasPrivate: false,
hasNonScalar: false)
bufferChecker.checkArguments(
Array(
repeating: {
bufferChecker.checkInt(
startIndex: $0,
flag: .publicFlag,
expectedInt: 1) },
count: maxOSLogArgumentCount)
)
})
}
InterpolationTestSuite.test("string interpolations with percents") {
_checkFormatStringAndBuffer(
"a = (c % d)%%",
with: { (formatString, buffer) in
expectEqual("a = (c %% d)%%%%", formatString)
let bufferChecker = OSLogBufferChecker(buffer)
bufferChecker.checkSummaryBytes(
argumentCount: 0,
hasPrivate: false,
hasNonScalar: false)
})
}