diff --git a/ElementX/Sources/UnitTests/UnitTestsAppCoordinator.swift b/ElementX/Sources/UnitTests/UnitTestsAppCoordinator.swift index 09ce17498..b66727079 100644 --- a/ElementX/Sources/UnitTests/UnitTestsAppCoordinator.swift +++ b/ElementX/Sources/UnitTests/UnitTestsAppCoordinator.swift @@ -8,6 +8,10 @@ import SwiftUI class UnitTestsAppCoordinator: AppCoordinatorProtocol { + private let targetConfiguration: Target.ConfigurationResult + static let targetRageshakeURL = RemotePreference(.url("bugs.example.com/submit")) + static let targetAppHooks = AppHooks() + let windowManager: SecureWindowManagerProtocol init(appDelegate: AppDelegate) { @@ -23,6 +27,17 @@ class UnitTestsAppCoordinator: AppCoordinatorProtocol { analyticsClient.isRunning = false ServiceLocator.shared.register(analytics: AnalyticsService(client: analyticsClient, appSettings: ServiceLocator.shared.settings)) + + // As the tests take advantage of Rust's ability to redirect the log files, there is + // often some debris left from the previous run, so we wipe the entire directory. + // This is an NOT an advised way to delete logs in production, `Tracing.deleteLogFiles` + // exists for that purpose. + try? FileManager.default.removeItem(at: .appGroupLogsDirectory) + targetConfiguration = Target.tests.configure(logLevel: .info, + traceLogPacks: [], + sentryURL: nil, + rageshakeURL: Self.targetRageshakeURL, + appHooks: Self.targetAppHooks) } func start() { } diff --git a/Enterprise b/Enterprise index 7761fe61c..33a280ecb 160000 --- a/Enterprise +++ b/Enterprise @@ -1 +1 @@ -Subproject commit 7761fe61cac3a2d27de3b62f67b11bfe38ee53ed +Subproject commit 33a280ecbef4831b4c5031f3139ac5c7bff00b53 diff --git a/UnitTests/Sources/LoggingTests.swift b/UnitTests/Sources/LoggingTests.swift index eb9b67ce9..be6534cbf 100644 --- a/UnitTests/Sources/LoggingTests.swift +++ b/UnitTests/Sources/LoggingTests.swift @@ -10,45 +10,22 @@ import XCTest class LoggingTests: XCTestCase { - static var targetConfiguration: Target.ConfigurationResult? - private enum Constants { static let genericFailure = "Test failed" } - - override func setUpWithError() throws { - Tracing.deleteLogFiles(in: Tracing.logsDirectory) + + override func tearDown() async throws { + Tracing.logsDirectoryOverride = nil + try reloadTracingFileWriter(configuration: .init(path: URL.appGroupLogsDirectory.path(percentEncoded: false), + filePrefix: "console-tests", + fileSuffix: "log", + maxFiles: 100)) } - func testLogging() async throws { - let target = "tests" - XCTAssertTrue(Tracing.logFiles.isEmpty) + func testFileLogging() throws { + try setupTest() - if Self.targetConfiguration == nil { - Self.targetConfiguration = Target.tests.configure(logLevel: .info, - traceLogPacks: [], - sentryURL: nil, - rageshakeURL: ServiceLocator.shared.settings.bugReportRageshakeURL, - appHooks: AppHooks()) - } - - // There is something weird with Rust logging where the file writing handle doesn't - // notice that the file it is writing to was deleted, so we can't run these checks - // as separate tests. So instead we need to make sure we run all the tests that - // write logs in this single test case after configuring the log system. - - try validateFileLogging() - try validateLogLevels() - try validateTargetName(target) - - try validateRoomSummaryContentIsRedacted() - try await validateTimelineContentIsRedacted() - try validateRustMessageContentIsRedacted() - } - - func validateFileLogging() throws { let infoLog = UUID().uuidString - MXLog.info(infoLog) guard let logFile = Tracing.logFiles.first else { @@ -59,10 +36,12 @@ class LoggingTests: XCTestCase { try XCTAssertTrue(String(contentsOf: logFile).contains(infoLog)) } - func validateLogLevels() throws { - let verboseLog = UUID().uuidString + func testLogLevels() throws { + try setupTest() + let verboseLog = UUID().uuidString MXLog.verbose(verboseLog) + guard let logFile = Tracing.logFiles.first else { XCTFail(Constants.genericFailure) return @@ -70,18 +49,23 @@ class LoggingTests: XCTestCase { try XCTAssertFalse(String(contentsOf: logFile).contains(verboseLog)) } - - func validateTargetName(_ target: String) throws { + + /// This is meant to test the `Target.tests.configure(…)`, but at this stage the test is somewhat pointless + /// as it is unlikely to have been called before `tearDown` has manually set the file prefix 😕. + func testTargetName() throws { MXLog.info(UUID().uuidString) guard let logFile = Tracing.logFiles.first else { XCTFail(Constants.genericFailure) return } + let target = "tests" XCTAssertTrue(logFile.lastPathComponent.contains(target)) } - func validateRoomSummaryContentIsRedacted() throws { + func testRoomSummaryContentIsRedacted() throws { + try setupTest() + // Given a room summary that contains sensitive information let roomName = "Private Conversation" let lastMessage = "Secret information" @@ -123,7 +107,9 @@ class LoggingTests: XCTestCase { XCTAssertFalse(content.contains(heroName)) } - func validateTimelineContentIsRedacted() async throws { + func testTimelineContentIsRedacted() async throws { + try setupTest() + // Given timeline items that contain text let textAttributedString = "TextAttributed" let textMessage = TextRoomTimelineItem(id: .randomEvent, @@ -183,14 +169,6 @@ class LoggingTests: XCTestCase { contentType: nil)) // When logging that value - if Self.targetConfiguration == nil { - Self.targetConfiguration = Target.tests.configure(logLevel: .info, - traceLogPacks: [], - sentryURL: nil, - rageshakeURL: ServiceLocator.shared.settings.bugReportRageshakeURL, - appHooks: AppHooks()) - } - MXLog.info(textMessage) MXLog.info(noticeMessage) MXLog.info(emoteMessage) @@ -227,7 +205,9 @@ class LoggingTests: XCTestCase { XCTAssertFalse(content.contains(fileMessage.body)) } - func validateRustMessageContentIsRedacted() throws { + func testRustMessageContentIsRedacted() throws { + try setupTest() + // Given message content that contain text let textString = "TextString" let rustTextMessage = TextMessageContent(body: "", @@ -292,6 +272,8 @@ class LoggingTests: XCTestCase { } func testLogFileSorting() async throws { + try setupTest(redirectTracingFileWriter: false) + // Given a collection of log files. XCTAssertTrue(Tracing.logFiles.isEmpty) @@ -340,4 +322,26 @@ class LoggingTests: XCTestCase { "console.3.log", "console.2.log"]) } + + // MARK: - Helpers + + /// There is something weird with Rust logging where the file writing handle won't notice that the file it is writing + /// to has been deleted. So in order to run the tests that validate the file output, we must use a new directory + /// to start with a fresh state (as calling ``Tracing.deleteLogFiles`` would trigger the bug). + private func setupTest(name: String = #function, redirectTracingFileWriter: Bool = true) throws { + let testDirectory = URL.appGroupLogsDirectory.appending(component: name, directoryHint: .isDirectory) + Tracing.logsDirectoryOverride = testDirectory + try? FileManager.default.createDirectory(at: testDirectory, withIntermediateDirectories: true) + + // Make an assertion before redirecting the logs as it the SDK is likely to put an empty file + // in the directory, ready to be written to. + XCTAssertTrue(Tracing.logFiles.isEmpty) + + if redirectTracingFileWriter { + try reloadTracingFileWriter(configuration: .init(path: testDirectory.path(percentEncoded: false), + filePrefix: "console", + fileSuffix: "log", + maxFiles: 100)) + } + } }