diff --git a/Logger.xcodeproj/project.pbxproj b/Logger.xcodeproj/project.pbxproj index 6a86dba..0b36d05 100644 --- a/Logger.xcodeproj/project.pbxproj +++ b/Logger.xcodeproj/project.pbxproj @@ -23,6 +23,7 @@ 2ED077DB2132B0320058EEFC /* FileSystem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2ED077DA2132B0320058EEFC /* FileSystem.swift */; }; 2ED103E12135C61100EB3683 /* FileRotateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2ED103E02135C61100EB3683 /* FileRotateTests.swift */; }; 2ED103E32135D3FB00EB3683 /* FileWriterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2ED103E22135D3FB00EB3683 /* FileWriterTests.swift */; }; + 2ED103E52138553B00EB3683 /* DiskLoggerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2ED103E42138553B00EB3683 /* DiskLoggerTests.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -68,6 +69,7 @@ 2ED077DA2132B0320058EEFC /* FileSystem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileSystem.swift; sourceTree = ""; }; 2ED103E02135C61100EB3683 /* FileRotateTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileRotateTests.swift; sourceTree = ""; }; 2ED103E22135D3FB00EB3683 /* FileWriterTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileWriterTests.swift; sourceTree = ""; }; + 2ED103E42138553B00EB3683 /* DiskLoggerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiskLoggerTests.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -95,6 +97,7 @@ 2E58D35C21316C3500BEF81A /* LogStringConvertibleTests.swift */, 2ED077D621329CA30058EEFC /* LoggetTests.swift */, 2ED077D82132A4820058EEFC /* AgregateLoggerTests.swift */, + 2ED103E42138553B00EB3683 /* DiskLoggerTests.swift */, 2ED103E22135D3FB00EB3683 /* FileWriterTests.swift */, 2ED103E02135C61100EB3683 /* FileRotateTests.swift */, 2E58D35E21316C3500BEF81A /* Info.plist */, @@ -247,6 +250,7 @@ files = ( 2E58D35D21316C3500BEF81A /* LogStringConvertibleTests.swift in Sources */, 2ED103E32135D3FB00EB3683 /* FileWriterTests.swift in Sources */, + 2ED103E52138553B00EB3683 /* DiskLoggerTests.swift in Sources */, 2ED077D92132A4820058EEFC /* AgregateLoggerTests.swift in Sources */, 2ED103E12135C61100EB3683 /* FileRotateTests.swift in Sources */, 2ED077D721329CA30058EEFC /* LoggetTests.swift in Sources */, diff --git a/UnitTests/DiskLoggerTests.swift b/UnitTests/DiskLoggerTests.swift new file mode 100644 index 0000000..aaededc --- /dev/null +++ b/UnitTests/DiskLoggerTests.swift @@ -0,0 +1,241 @@ +// +// MIT License +// +// Copyright (c) 2018 Wojciech Nagrodzki +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// + +import XCTest +@testable import Logger + +class DiskLoggerTests: XCTestCase { + + let logURL = URL(fileURLWithPath: "/var/log/application.log") + + func testLoggingMessageToFile() { + let expectation = XCTestExpectation(description: "write(_:) was called on SizeLimitedFile") + expectation.expectedFulfillmentCount = 1 + + let filesystem = FileSystemStub() + let sizeLimitedFileFactory = SizeLimitedFileMockFactory(writeCall: expectation) + let logrotateFactory = LogrotateMockFactory() + let logger = DiskLogger(fileURL: logURL, + fileSizeLimit: 1024, + rotations: 1, + fileSystem: filesystem, + sizeLimitedFileFactory: sizeLimitedFileFactory, + logrotateFactory: logrotateFactory) + + logger.log("message", level: .critical) + + let result = XCTWaiter().wait(for: [expectation], timeout: 1.0) + XCTAssertEqual(result, .completed) + + XCTAssertEqual(sizeLimitedFileFactory.files.count, 1) + + // "2018-08-30 17:23:11.514 DiskLoggerTests:46 testWritingMessageToFile() a message\n" + let loggedMessage = String(decoding: sizeLimitedFileFactory.files[0].data, as: UTF8.self) + let expectedSuffix = " DiskLoggerTests:46 testLoggingMessageToFile() message\n" + + XCTAssertTrue(loggedMessage.hasSuffix(expectedSuffix)) + } + + func testExceedingFileSizeLimit() { + let expectation = XCTestExpectation(description: "write(_:) was called on SizeLimitedFile") + expectation.expectedFulfillmentCount = 2 + + let filesystem = FileSystemStub() + let sizeLimitedFileFactory = SizeLimitedFileMockFactory(writeCall: expectation) + let logrotateFactory = LogrotateMockFactory() + let logger = DiskLogger(fileURL: logURL, + fileSizeLimit: 91, + rotations: 1, + fileSystem: filesystem, + sizeLimitedFileFactory: sizeLimitedFileFactory, + logrotateFactory: logrotateFactory) + + logger.log("1st message", level: .critical) + logger.log("2st message", level: .critical) + + let result = XCTWaiter().wait(for: [expectation], timeout: 1.0) + XCTAssertEqual(result, .completed) + + XCTAssertEqual(sizeLimitedFileFactory.files.count, 2) + + // "2018-08-31 18:29:34.748 DiskLoggerTests:75 testExceedingFileSizeLimit() 2st message\n" + let loggedMessage = String(decoding: sizeLimitedFileFactory.files[1].data, as: UTF8.self) + let expectedSuffix = " DiskLoggerTests:75 testExceedingFileSizeLimit() 2st message\n" + + XCTAssertTrue(loggedMessage.hasSuffix(expectedSuffix)) + } + + func testErrorDuringLogProcess() { + let expectation = XCTestExpectation(description: "write(_:) was called on SizeLimitedFile") + expectation.expectedFulfillmentCount = 2 + + let filesystem = FileSystemStub() + let sizeLimitedFileFactory = UnwritableFileStubFactory(writeCall: expectation) + let logrotateFactory = LogrotateMockFactory() + let logger = DiskLogger(fileURL: logURL, + fileSizeLimit: 91, + rotations: 1, + fileSystem: filesystem, + sizeLimitedFileFactory: sizeLimitedFileFactory, + logrotateFactory: logrotateFactory) + + logger.log("1st message", level: .critical) + logger.log("2st message", level: .critical) + + let result = XCTWaiter().wait(for: [expectation], timeout: 1.0) + XCTAssertEqual(result, .completed) + + XCTAssertEqual(sizeLimitedFileFactory.files.count, 1) + + // "2018-08-31 18:29:34.748 DiskLoggerTests:75 testExceedingFileSizeLimit() 2st message\n" + let loggedMessage = String(decoding: sizeLimitedFileFactory.files[0].data, as: UTF8.self) + let expectedOccurence = " WriteFailed()" + + XCTAssertTrue(loggedMessage.contains(expectedOccurence)) + } +} + +private class FileSystemStub: FileSystem { + func itemExists(at URL: URL) -> Bool { + return false + } + + func removeItem(at URL: URL) throws { + + } + + func moveItem(at srcURL: URL, to dstURL: URL) throws { + + } + + func createFile(at URL: URL) -> Bool { + return true + } +} + +private class SizeLimitedFileMockFactory: SizeLimitedFileFactory { + + private(set) var files = [SizeLimitedFileMock]() + private let writeCall: XCTestExpectation + + init(writeCall: XCTestExpectation) { + self.writeCall = writeCall + } + + func makeInstance(fileURL: URL, fileSizeLimit: UInt64) throws -> SizeLimitedFile { + let mock = SizeLimitedFileMock(writeCall: writeCall, fileSizeLimit: fileSizeLimit) + files.append(mock) + return mock + } +} + +private class SizeLimitedFileMock: SizeLimitedFile { + + private(set) var data = Data() + private(set) var synchronizeAndCloseFileCallCount = 0 + private let writeCall: XCTestExpectation + private let fileSizeLimit: UInt64 + + init(writeCall: XCTestExpectation, fileSizeLimit: UInt64) { + self.writeCall = writeCall + self.fileSizeLimit = fileSizeLimit + } + + func write(_ data: Data) throws { + guard data.count + self.data.count <= fileSizeLimit else { + throw SizeLimitedFileQuotaReached() + } + self.data.append(data) + writeCall.fulfill() + } + + func synchronizeAndCloseFile() { + synchronizeAndCloseFileCallCount += 1 + } +} + +private class UnwritableFileStubFactory: SizeLimitedFileFactory { + + private(set) var files = [UnwritableFileStub]() + private let writeCall: XCTestExpectation + + init(writeCall: XCTestExpectation) { + self.writeCall = writeCall + } + + func makeInstance(fileURL: URL, fileSizeLimit: UInt64) throws -> SizeLimitedFile { + let file = UnwritableFileStub(writeCall: writeCall, fileSizeLimit: fileSizeLimit) + files.append(file) + return file + } +} + +private class UnwritableFileStub: SizeLimitedFile { + + struct WriteFailed: Error {} + + private(set) var data = Data() + private let writeCall: XCTestExpectation + private var didThrowError = false + + init(writeCall: XCTestExpectation, fileSizeLimit: UInt64) { + self.writeCall = writeCall + } + + func write(_ data: Data) throws { + if didThrowError { + self.data.append(data) + writeCall.fulfill() + } + else { + didThrowError = true + writeCall.fulfill() + throw WriteFailed() + } + } + + func synchronizeAndCloseFile() { + + } +} + +private class LogrotateMockFactory: LogrotateFactory { + + private(set) var logrotates = [LogrotateMock]() + + func makeInstance(fileURL: URL, rotations: Int) -> Logrotate { + let mock = LogrotateMock() + logrotates.append(mock) + return mock + } +} + +private class LogrotateMock: Logrotate { + + private(set) var rotateCallCount = 0 + + func rotate() throws { + rotateCallCount += 1 + } +}