From 27c9599502f28bc0655f869a5765fb16f0e07d65 Mon Sep 17 00:00:00 2001
From: Wojciech Nagrodzki <278594+wnagrodzki@users.noreply.github.com>
Date: Sat, 25 Aug 2018 12:54:42 +0200
Subject: [PATCH 01/16] Added UnitTest target

---
 Logger.xcodeproj/project.pbxproj | 125 +++++++++++++++++++++++++++++++
 UnitTests/Info.plist             |  22 ++++++
 UnitTests/UnitTests.swift        |  33 ++++++++
 3 files changed, 180 insertions(+)
 create mode 100644 UnitTests/Info.plist
 create mode 100644 UnitTests/UnitTests.swift

diff --git a/Logger.xcodeproj/project.pbxproj b/Logger.xcodeproj/project.pbxproj
index 7355162..2b38cbb 100644
--- a/Logger.xcodeproj/project.pbxproj
+++ b/Logger.xcodeproj/project.pbxproj
@@ -7,6 +7,8 @@
 	objects = {
 
 /* Begin PBXBuildFile section */
+		2E58D35D21316C3500BEF81A /* UnitTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E58D35C21316C3500BEF81A /* UnitTests.swift */; };
+		2E58D35F21316C3500BEF81A /* libLogger.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 2EBF4B3A2122AA34008E4117 /* libLogger.a */; };
 		2EBF4B3E2122AA34008E4117 /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2EBF4B3D2122AA34008E4117 /* Logger.swift */; };
 		2EBF4B452122ACD6008E4117 /* LogStringConvertible.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2EBF4B442122ACD6008E4117 /* LogStringConvertible.swift */; };
 		2EBF4B4A2122AF53008E4117 /* AgregateLogger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2EBF4B472122AF53008E4117 /* AgregateLogger.swift */; };
@@ -18,6 +20,16 @@
 		2EBF4B592122B598008E4117 /* Logrotate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2EBF4B562122B598008E4117 /* Logrotate.swift */; };
 /* End PBXBuildFile section */
 
+/* Begin PBXContainerItemProxy section */
+		2E58D36021316C3500BEF81A /* PBXContainerItemProxy */ = {
+			isa = PBXContainerItemProxy;
+			containerPortal = 2EBF4B322122AA34008E4117 /* Project object */;
+			proxyType = 1;
+			remoteGlobalIDString = 2EBF4B392122AA34008E4117;
+			remoteInfo = Logger;
+		};
+/* End PBXContainerItemProxy section */
+
 /* Begin PBXCopyFilesBuildPhase section */
 		2EBF4B382122AA34008E4117 /* CopyFiles */ = {
 			isa = PBXCopyFilesBuildPhase;
@@ -31,6 +43,9 @@
 /* End PBXCopyFilesBuildPhase section */
 
 /* Begin PBXFileReference section */
+		2E58D35A21316C3500BEF81A /* UnitTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = UnitTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
+		2E58D35C21316C3500BEF81A /* UnitTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnitTests.swift; sourceTree = "<group>"; };
+		2E58D35E21316C3500BEF81A /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
 		2EBF4B3A2122AA34008E4117 /* libLogger.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libLogger.a; sourceTree = BUILT_PRODUCTS_DIR; };
 		2EBF4B3D2122AA34008E4117 /* Logger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Logger.swift; sourceTree = "<group>"; };
 		2EBF4B442122ACD6008E4117 /* LogStringConvertible.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LogStringConvertible.swift; sourceTree = "<group>"; };
@@ -46,6 +61,14 @@
 /* End PBXFileReference section */
 
 /* Begin PBXFrameworksBuildPhase section */
+		2E58D35721316C3500BEF81A /* Frameworks */ = {
+			isa = PBXFrameworksBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+				2E58D35F21316C3500BEF81A /* libLogger.a in Frameworks */,
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
 		2EBF4B372122AA34008E4117 /* Frameworks */ = {
 			isa = PBXFrameworksBuildPhase;
 			buildActionMask = 2147483647;
@@ -56,10 +79,20 @@
 /* End PBXFrameworksBuildPhase section */
 
 /* Begin PBXGroup section */
+		2E58D35B21316C3500BEF81A /* UnitTests */ = {
+			isa = PBXGroup;
+			children = (
+				2E58D35C21316C3500BEF81A /* UnitTests.swift */,
+				2E58D35E21316C3500BEF81A /* Info.plist */,
+			);
+			path = UnitTests;
+			sourceTree = "<group>";
+		};
 		2EBF4B312122AA34008E4117 = {
 			isa = PBXGroup;
 			children = (
 				2EBF4B3C2122AA34008E4117 /* Logger */,
+				2E58D35B21316C3500BEF81A /* UnitTests */,
 				2EBF4B3B2122AA34008E4117 /* Products */,
 			);
 			sourceTree = "<group>";
@@ -68,6 +101,7 @@
 			isa = PBXGroup;
 			children = (
 				2EBF4B3A2122AA34008E4117 /* libLogger.a */,
+				2E58D35A21316C3500BEF81A /* UnitTests.xctest */,
 			);
 			name = Products;
 			sourceTree = "<group>";
@@ -109,6 +143,24 @@
 /* End PBXGroup section */
 
 /* Begin PBXNativeTarget section */
+		2E58D35921316C3500BEF81A /* UnitTests */ = {
+			isa = PBXNativeTarget;
+			buildConfigurationList = 2E58D36221316C3500BEF81A /* Build configuration list for PBXNativeTarget "UnitTests" */;
+			buildPhases = (
+				2E58D35621316C3500BEF81A /* Sources */,
+				2E58D35721316C3500BEF81A /* Frameworks */,
+				2E58D35821316C3500BEF81A /* Resources */,
+			);
+			buildRules = (
+			);
+			dependencies = (
+				2E58D36121316C3500BEF81A /* PBXTargetDependency */,
+			);
+			name = UnitTests;
+			productName = UnitTests;
+			productReference = 2E58D35A21316C3500BEF81A /* UnitTests.xctest */;
+			productType = "com.apple.product-type.bundle.unit-test";
+		};
 		2EBF4B392122AA34008E4117 /* Logger */ = {
 			isa = PBXNativeTarget;
 			buildConfigurationList = 2EBF4B412122AA34008E4117 /* Build configuration list for PBXNativeTarget "Logger" */;
@@ -136,6 +188,9 @@
 				LastUpgradeCheck = 1000;
 				ORGANIZATIONNAME = "Wojciech Nagrodzki";
 				TargetAttributes = {
+					2E58D35921316C3500BEF81A = {
+						CreatedOnToolsVersion = 10.0;
+					};
 					2EBF4B392122AA34008E4117 = {
 						CreatedOnToolsVersion = 10.0;
 						LastSwiftMigration = 1000;
@@ -155,11 +210,30 @@
 			projectRoot = "";
 			targets = (
 				2EBF4B392122AA34008E4117 /* Logger */,
+				2E58D35921316C3500BEF81A /* UnitTests */,
 			);
 		};
 /* End PBXProject section */
 
+/* Begin PBXResourcesBuildPhase section */
+		2E58D35821316C3500BEF81A /* Resources */ = {
+			isa = PBXResourcesBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+/* End PBXResourcesBuildPhase section */
+
 /* Begin PBXSourcesBuildPhase section */
+		2E58D35621316C3500BEF81A /* Sources */ = {
+			isa = PBXSourcesBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+				2E58D35D21316C3500BEF81A /* UnitTests.swift in Sources */,
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
 		2EBF4B362122AA34008E4117 /* Sources */ = {
 			isa = PBXSourcesBuildPhase;
 			buildActionMask = 2147483647;
@@ -178,7 +252,49 @@
 		};
 /* End PBXSourcesBuildPhase section */
 
+/* Begin PBXTargetDependency section */
+		2E58D36121316C3500BEF81A /* PBXTargetDependency */ = {
+			isa = PBXTargetDependency;
+			target = 2EBF4B392122AA34008E4117 /* Logger */;
+			targetProxy = 2E58D36021316C3500BEF81A /* PBXContainerItemProxy */;
+		};
+/* End PBXTargetDependency section */
+
 /* Begin XCBuildConfiguration section */
+		2E58D36321316C3500BEF81A /* Debug */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				CODE_SIGN_STYLE = Automatic;
+				INFOPLIST_FILE = UnitTests/Info.plist;
+				LD_RUNPATH_SEARCH_PATHS = (
+					"$(inherited)",
+					"@executable_path/Frameworks",
+					"@loader_path/Frameworks",
+				);
+				PRODUCT_BUNDLE_IDENTIFIER = com.wnagrodzki.UnitTests;
+				PRODUCT_NAME = "$(TARGET_NAME)";
+				SWIFT_VERSION = 4.2;
+				TARGETED_DEVICE_FAMILY = "1,2";
+			};
+			name = Debug;
+		};
+		2E58D36421316C3500BEF81A /* Release */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				CODE_SIGN_STYLE = Automatic;
+				INFOPLIST_FILE = UnitTests/Info.plist;
+				LD_RUNPATH_SEARCH_PATHS = (
+					"$(inherited)",
+					"@executable_path/Frameworks",
+					"@loader_path/Frameworks",
+				);
+				PRODUCT_BUNDLE_IDENTIFIER = com.wnagrodzki.UnitTests;
+				PRODUCT_NAME = "$(TARGET_NAME)";
+				SWIFT_VERSION = 4.2;
+				TARGETED_DEVICE_FAMILY = "1,2";
+			};
+			name = Release;
+		};
 		2EBF4B3F2122AA34008E4117 /* Debug */ = {
 			isa = XCBuildConfiguration;
 			buildSettings = {
@@ -337,6 +453,15 @@
 /* End XCBuildConfiguration section */
 
 /* Begin XCConfigurationList section */
+		2E58D36221316C3500BEF81A /* Build configuration list for PBXNativeTarget "UnitTests" */ = {
+			isa = XCConfigurationList;
+			buildConfigurations = (
+				2E58D36321316C3500BEF81A /* Debug */,
+				2E58D36421316C3500BEF81A /* Release */,
+			);
+			defaultConfigurationIsVisible = 0;
+			defaultConfigurationName = Release;
+		};
 		2EBF4B352122AA34008E4117 /* Build configuration list for PBXProject "Logger" */ = {
 			isa = XCConfigurationList;
 			buildConfigurations = (
diff --git a/UnitTests/Info.plist b/UnitTests/Info.plist
new file mode 100644
index 0000000..6c40a6c
--- /dev/null
+++ b/UnitTests/Info.plist
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>CFBundleDevelopmentRegion</key>
+	<string>$(DEVELOPMENT_LANGUAGE)</string>
+	<key>CFBundleExecutable</key>
+	<string>$(EXECUTABLE_NAME)</string>
+	<key>CFBundleIdentifier</key>
+	<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
+	<key>CFBundleInfoDictionaryVersion</key>
+	<string>6.0</string>
+	<key>CFBundleName</key>
+	<string>$(PRODUCT_NAME)</string>
+	<key>CFBundlePackageType</key>
+	<string>BNDL</string>
+	<key>CFBundleShortVersionString</key>
+	<string>1.0</string>
+	<key>CFBundleVersion</key>
+	<string>1</string>
+</dict>
+</plist>
diff --git a/UnitTests/UnitTests.swift b/UnitTests/UnitTests.swift
new file mode 100644
index 0000000..5ff3dfe
--- /dev/null
+++ b/UnitTests/UnitTests.swift
@@ -0,0 +1,33 @@
+//
+//  UnitTests.swift
+//  UnitTests
+//
+//  Created by Wojciech Nagrodzki on 25/08/2018.
+//  Copyright © 2018 Wojciech Nagrodzki. All rights reserved.
+//
+
+import XCTest
+
+class UnitTests: XCTestCase {
+
+    override func setUp() {
+        // Put setup code here. This method is called before the invocation of each test method in the class.
+    }
+
+    override func tearDown() {
+        // Put teardown code here. This method is called after the invocation of each test method in the class.
+    }
+
+    func testExample() {
+        // This is an example of a functional test case.
+        // Use XCTAssert and related functions to verify your tests produce the correct results.
+    }
+
+    func testPerformanceExample() {
+        // This is an example of a performance test case.
+        self.measure {
+            // Put the code you want to measure the time of here.
+        }
+    }
+
+}

From 0d1abcdb54e57ab540bfa58891f3e88cc01879ff Mon Sep 17 00:00:00 2001
From: Wojciech Nagrodzki <278594+wnagrodzki@users.noreply.github.com>
Date: Sat, 25 Aug 2018 13:04:05 +0200
Subject: [PATCH 02/16] Removed UnitTests.swift file

---
 Logger.xcodeproj/project.pbxproj |  4 ----
 UnitTests/UnitTests.swift        | 33 --------------------------------
 2 files changed, 37 deletions(-)
 delete mode 100644 UnitTests/UnitTests.swift

diff --git a/Logger.xcodeproj/project.pbxproj b/Logger.xcodeproj/project.pbxproj
index 2b38cbb..8dc73dd 100644
--- a/Logger.xcodeproj/project.pbxproj
+++ b/Logger.xcodeproj/project.pbxproj
@@ -7,7 +7,6 @@
 	objects = {
 
 /* Begin PBXBuildFile section */
-		2E58D35D21316C3500BEF81A /* UnitTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E58D35C21316C3500BEF81A /* UnitTests.swift */; };
 		2E58D35F21316C3500BEF81A /* libLogger.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 2EBF4B3A2122AA34008E4117 /* libLogger.a */; };
 		2EBF4B3E2122AA34008E4117 /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2EBF4B3D2122AA34008E4117 /* Logger.swift */; };
 		2EBF4B452122ACD6008E4117 /* LogStringConvertible.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2EBF4B442122ACD6008E4117 /* LogStringConvertible.swift */; };
@@ -44,7 +43,6 @@
 
 /* Begin PBXFileReference section */
 		2E58D35A21316C3500BEF81A /* UnitTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = UnitTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
-		2E58D35C21316C3500BEF81A /* UnitTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnitTests.swift; sourceTree = "<group>"; };
 		2E58D35E21316C3500BEF81A /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
 		2EBF4B3A2122AA34008E4117 /* libLogger.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libLogger.a; sourceTree = BUILT_PRODUCTS_DIR; };
 		2EBF4B3D2122AA34008E4117 /* Logger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Logger.swift; sourceTree = "<group>"; };
@@ -82,7 +80,6 @@
 		2E58D35B21316C3500BEF81A /* UnitTests */ = {
 			isa = PBXGroup;
 			children = (
-				2E58D35C21316C3500BEF81A /* UnitTests.swift */,
 				2E58D35E21316C3500BEF81A /* Info.plist */,
 			);
 			path = UnitTests;
@@ -230,7 +227,6 @@
 			isa = PBXSourcesBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
-				2E58D35D21316C3500BEF81A /* UnitTests.swift in Sources */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
diff --git a/UnitTests/UnitTests.swift b/UnitTests/UnitTests.swift
deleted file mode 100644
index 5ff3dfe..0000000
--- a/UnitTests/UnitTests.swift
+++ /dev/null
@@ -1,33 +0,0 @@
-//
-//  UnitTests.swift
-//  UnitTests
-//
-//  Created by Wojciech Nagrodzki on 25/08/2018.
-//  Copyright © 2018 Wojciech Nagrodzki. All rights reserved.
-//
-
-import XCTest
-
-class UnitTests: XCTestCase {
-
-    override func setUp() {
-        // Put setup code here. This method is called before the invocation of each test method in the class.
-    }
-
-    override func tearDown() {
-        // Put teardown code here. This method is called after the invocation of each test method in the class.
-    }
-
-    func testExample() {
-        // This is an example of a functional test case.
-        // Use XCTAssert and related functions to verify your tests produce the correct results.
-    }
-
-    func testPerformanceExample() {
-        // This is an example of a performance test case.
-        self.measure {
-            // Put the code you want to measure the time of here.
-        }
-    }
-
-}

From 3a6c531dad07a6511d12313ba59a690cf0236dc3 Mon Sep 17 00:00:00 2001
From: Wojciech Nagrodzki <278594+wnagrodzki@users.noreply.github.com>
Date: Sat, 25 Aug 2018 13:12:29 +0200
Subject: [PATCH 03/16] Made Logger scheme shared

---
 .../xcshareddata/xcschemes/Logger.xcscheme    | 99 +++++++++++++++++++
 1 file changed, 99 insertions(+)
 create mode 100644 Logger.xcodeproj/xcshareddata/xcschemes/Logger.xcscheme

diff --git a/Logger.xcodeproj/xcshareddata/xcschemes/Logger.xcscheme b/Logger.xcodeproj/xcshareddata/xcschemes/Logger.xcscheme
new file mode 100644
index 0000000..1b5258f
--- /dev/null
+++ b/Logger.xcodeproj/xcshareddata/xcschemes/Logger.xcscheme
@@ -0,0 +1,99 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Scheme
+   LastUpgradeVersion = "1000"
+   version = "1.3">
+   <BuildAction
+      parallelizeBuildables = "YES"
+      buildImplicitDependencies = "YES">
+      <BuildActionEntries>
+         <BuildActionEntry
+            buildForTesting = "YES"
+            buildForRunning = "YES"
+            buildForProfiling = "YES"
+            buildForArchiving = "YES"
+            buildForAnalyzing = "YES">
+            <BuildableReference
+               BuildableIdentifier = "primary"
+               BlueprintIdentifier = "2EBF4B392122AA34008E4117"
+               BuildableName = "libLogger.a"
+               BlueprintName = "Logger"
+               ReferencedContainer = "container:Logger.xcodeproj">
+            </BuildableReference>
+         </BuildActionEntry>
+      </BuildActionEntries>
+   </BuildAction>
+   <TestAction
+      buildConfiguration = "Debug"
+      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+      shouldUseLaunchSchemeArgsEnv = "YES">
+      <Testables>
+         <TestableReference
+            skipped = "NO">
+            <BuildableReference
+               BuildableIdentifier = "primary"
+               BlueprintIdentifier = "2E58D35921316C3500BEF81A"
+               BuildableName = "UnitTests.xctest"
+               BlueprintName = "UnitTests"
+               ReferencedContainer = "container:Logger.xcodeproj">
+            </BuildableReference>
+         </TestableReference>
+      </Testables>
+      <MacroExpansion>
+         <BuildableReference
+            BuildableIdentifier = "primary"
+            BlueprintIdentifier = "2EBF4B392122AA34008E4117"
+            BuildableName = "libLogger.a"
+            BlueprintName = "Logger"
+            ReferencedContainer = "container:Logger.xcodeproj">
+         </BuildableReference>
+      </MacroExpansion>
+      <AdditionalOptions>
+      </AdditionalOptions>
+   </TestAction>
+   <LaunchAction
+      buildConfiguration = "Debug"
+      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+      launchStyle = "0"
+      useCustomWorkingDirectory = "NO"
+      ignoresPersistentStateOnLaunch = "NO"
+      debugDocumentVersioning = "YES"
+      debugServiceExtension = "internal"
+      allowLocationSimulation = "YES">
+      <MacroExpansion>
+         <BuildableReference
+            BuildableIdentifier = "primary"
+            BlueprintIdentifier = "2EBF4B392122AA34008E4117"
+            BuildableName = "libLogger.a"
+            BlueprintName = "Logger"
+            ReferencedContainer = "container:Logger.xcodeproj">
+         </BuildableReference>
+      </MacroExpansion>
+      <AdditionalOptions>
+      </AdditionalOptions>
+   </LaunchAction>
+   <ProfileAction
+      buildConfiguration = "Release"
+      shouldUseLaunchSchemeArgsEnv = "YES"
+      savedToolIdentifier = ""
+      useCustomWorkingDirectory = "NO"
+      debugDocumentVersioning = "YES">
+      <MacroExpansion>
+         <BuildableReference
+            BuildableIdentifier = "primary"
+            BlueprintIdentifier = "2EBF4B392122AA34008E4117"
+            BuildableName = "libLogger.a"
+            BlueprintName = "Logger"
+            ReferencedContainer = "container:Logger.xcodeproj">
+         </BuildableReference>
+      </MacroExpansion>
+   </ProfileAction>
+   <AnalyzeAction
+      buildConfiguration = "Debug">
+   </AnalyzeAction>
+   <ArchiveAction
+      buildConfiguration = "Release"
+      revealArchiveInOrganizer = "YES">
+   </ArchiveAction>
+</Scheme>

From f032da4ed83eb71668678bbad7a08d2b2b469cc4 Mon Sep 17 00:00:00 2001
From: Wojciech Nagrodzki <278594+wnagrodzki@users.noreply.github.com>
Date: Sat, 25 Aug 2018 13:28:09 +0200
Subject: [PATCH 04/16] Added LogStringConvertibleTests

---
 Logger.xcodeproj/project.pbxproj          |  4 ++
 UnitTests/LogStringConvertibleTests.swift | 50 +++++++++++++++++++++++
 2 files changed, 54 insertions(+)
 create mode 100644 UnitTests/LogStringConvertibleTests.swift

diff --git a/Logger.xcodeproj/project.pbxproj b/Logger.xcodeproj/project.pbxproj
index 8dc73dd..684a132 100644
--- a/Logger.xcodeproj/project.pbxproj
+++ b/Logger.xcodeproj/project.pbxproj
@@ -7,6 +7,7 @@
 	objects = {
 
 /* Begin PBXBuildFile section */
+		2E58D35D21316C3500BEF81A /* LogStringConvertibleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E58D35C21316C3500BEF81A /* LogStringConvertibleTests.swift */; };
 		2E58D35F21316C3500BEF81A /* libLogger.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 2EBF4B3A2122AA34008E4117 /* libLogger.a */; };
 		2EBF4B3E2122AA34008E4117 /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2EBF4B3D2122AA34008E4117 /* Logger.swift */; };
 		2EBF4B452122ACD6008E4117 /* LogStringConvertible.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2EBF4B442122ACD6008E4117 /* LogStringConvertible.swift */; };
@@ -43,6 +44,7 @@
 
 /* Begin PBXFileReference section */
 		2E58D35A21316C3500BEF81A /* UnitTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = UnitTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
+		2E58D35C21316C3500BEF81A /* LogStringConvertibleTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogStringConvertibleTests.swift; sourceTree = "<group>"; };
 		2E58D35E21316C3500BEF81A /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
 		2EBF4B3A2122AA34008E4117 /* libLogger.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libLogger.a; sourceTree = BUILT_PRODUCTS_DIR; };
 		2EBF4B3D2122AA34008E4117 /* Logger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Logger.swift; sourceTree = "<group>"; };
@@ -80,6 +82,7 @@
 		2E58D35B21316C3500BEF81A /* UnitTests */ = {
 			isa = PBXGroup;
 			children = (
+				2E58D35C21316C3500BEF81A /* LogStringConvertibleTests.swift */,
 				2E58D35E21316C3500BEF81A /* Info.plist */,
 			);
 			path = UnitTests;
@@ -227,6 +230,7 @@
 			isa = PBXSourcesBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
+				2E58D35D21316C3500BEF81A /* LogStringConvertibleTests.swift in Sources */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
diff --git a/UnitTests/LogStringConvertibleTests.swift b/UnitTests/LogStringConvertibleTests.swift
new file mode 100644
index 0000000..58bc9b2
--- /dev/null
+++ b/UnitTests/LogStringConvertibleTests.swift
@@ -0,0 +1,50 @@
+//
+// 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 LogStringConvertibleTests: XCTestCase {
+
+    func testZeroObjectsDescription() {
+        let objects = [Int]()
+        XCTAssertEqual(objects.logDescription, "[]")
+    }
+    
+    func testOneObjectDescription() {
+        let objects = [1]
+        XCTAssertEqual(objects.logDescription, "[1]")
+    }
+    
+    func testTwoObjectsDescription() {
+        let objects = [1, 2]
+        XCTAssertEqual(objects.logDescription, "[1, 2]")
+    }
+}
+
+extension Int: LogStringConvertible {
+    public var logDescription: String {
+        return String(describing: self)
+    }
+}

From 4df5b125a80bc70bfc1a43845e13abc364006c91 Mon Sep 17 00:00:00 2001
From: Wojciech Nagrodzki <278594+wnagrodzki@users.noreply.github.com>
Date: Sun, 26 Aug 2018 10:51:36 +0200
Subject: [PATCH 05/16] Added LoggerTests

---
 Logger.xcodeproj/project.pbxproj |  4 +++
 UnitTests/LoggetTests.swift      | 55 ++++++++++++++++++++++++++++++++
 2 files changed, 59 insertions(+)
 create mode 100644 UnitTests/LoggetTests.swift

diff --git a/Logger.xcodeproj/project.pbxproj b/Logger.xcodeproj/project.pbxproj
index 684a132..14a8da4 100644
--- a/Logger.xcodeproj/project.pbxproj
+++ b/Logger.xcodeproj/project.pbxproj
@@ -18,6 +18,7 @@
 		2EBF4B572122B598008E4117 /* DiskLogger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2EBF4B542122B598008E4117 /* DiskLogger.swift */; };
 		2EBF4B582122B598008E4117 /* FileWriter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2EBF4B552122B598008E4117 /* FileWriter.swift */; };
 		2EBF4B592122B598008E4117 /* Logrotate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2EBF4B562122B598008E4117 /* Logrotate.swift */; };
+		2ED077D721329CA30058EEFC /* LoggetTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2ED077D621329CA30058EEFC /* LoggetTests.swift */; };
 /* End PBXBuildFile section */
 
 /* Begin PBXContainerItemProxy section */
@@ -58,6 +59,7 @@
 		2EBF4B542122B598008E4117 /* DiskLogger.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DiskLogger.swift; sourceTree = "<group>"; };
 		2EBF4B552122B598008E4117 /* FileWriter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FileWriter.swift; sourceTree = "<group>"; };
 		2EBF4B562122B598008E4117 /* Logrotate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Logrotate.swift; sourceTree = "<group>"; };
+		2ED077D621329CA30058EEFC /* LoggetTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoggetTests.swift; sourceTree = "<group>"; };
 /* End PBXFileReference section */
 
 /* Begin PBXFrameworksBuildPhase section */
@@ -83,6 +85,7 @@
 			isa = PBXGroup;
 			children = (
 				2E58D35C21316C3500BEF81A /* LogStringConvertibleTests.swift */,
+				2ED077D621329CA30058EEFC /* LoggetTests.swift */,
 				2E58D35E21316C3500BEF81A /* Info.plist */,
 			);
 			path = UnitTests;
@@ -231,6 +234,7 @@
 			buildActionMask = 2147483647;
 			files = (
 				2E58D35D21316C3500BEF81A /* LogStringConvertibleTests.swift in Sources */,
+				2ED077D721329CA30058EEFC /* LoggetTests.swift in Sources */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
diff --git a/UnitTests/LoggetTests.swift b/UnitTests/LoggetTests.swift
new file mode 100644
index 0000000..a753ca7
--- /dev/null
+++ b/UnitTests/LoggetTests.swift
@@ -0,0 +1,55 @@
+//
+// 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 LoggetTests: XCTestCase {
+
+    func testLocation() {
+        let mock = LoggerMock()
+        mock.log("", level: .critical)
+        XCTAssertEqual(mock.location, "LoggetTests:32 testLocation()")
+    }
+    
+    func testLogLevelLogDescription() {
+        XCTAssertEqual(LogLevel.emergency.logDescription, "emerg")
+        XCTAssertEqual(LogLevel.alert.logDescription, "alert")
+        XCTAssertEqual(LogLevel.critical.logDescription, "crit")
+        XCTAssertEqual(LogLevel.error.logDescription, "err")
+        XCTAssertEqual(LogLevel.warning.logDescription, "warning")
+        XCTAssertEqual(LogLevel.notice.logDescription, "notice")
+        XCTAssertEqual(LogLevel.informational.logDescription, "info")
+        XCTAssertEqual(LogLevel.debug.logDescription, "debug")
+    }
+}
+
+private class LoggerMock: Logger {
+    
+    var location: String?
+    
+    func log(time: Date, level: LogLevel, location: String, message: @autoclosure () -> String) {
+        self.location = location
+    }
+}

From ce546d00726b5fbe89454cde0d8fd15f48099e2c Mon Sep 17 00:00:00 2001
From: Wojciech Nagrodzki <278594+wnagrodzki@users.noreply.github.com>
Date: Sun, 26 Aug 2018 11:24:15 +0200
Subject: [PATCH 06/16] Added AgregateLoggerTests

---
 Logger.xcodeproj/project.pbxproj    |  4 ++
 UnitTests/AgregateLoggerTests.swift | 68 +++++++++++++++++++++++++++++
 2 files changed, 72 insertions(+)
 create mode 100644 UnitTests/AgregateLoggerTests.swift

diff --git a/Logger.xcodeproj/project.pbxproj b/Logger.xcodeproj/project.pbxproj
index 14a8da4..69657b4 100644
--- a/Logger.xcodeproj/project.pbxproj
+++ b/Logger.xcodeproj/project.pbxproj
@@ -19,6 +19,7 @@
 		2EBF4B582122B598008E4117 /* FileWriter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2EBF4B552122B598008E4117 /* FileWriter.swift */; };
 		2EBF4B592122B598008E4117 /* Logrotate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2EBF4B562122B598008E4117 /* Logrotate.swift */; };
 		2ED077D721329CA30058EEFC /* LoggetTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2ED077D621329CA30058EEFC /* LoggetTests.swift */; };
+		2ED077D92132A4820058EEFC /* AgregateLoggerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2ED077D82132A4820058EEFC /* AgregateLoggerTests.swift */; };
 /* End PBXBuildFile section */
 
 /* Begin PBXContainerItemProxy section */
@@ -60,6 +61,7 @@
 		2EBF4B552122B598008E4117 /* FileWriter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FileWriter.swift; sourceTree = "<group>"; };
 		2EBF4B562122B598008E4117 /* Logrotate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Logrotate.swift; sourceTree = "<group>"; };
 		2ED077D621329CA30058EEFC /* LoggetTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoggetTests.swift; sourceTree = "<group>"; };
+		2ED077D82132A4820058EEFC /* AgregateLoggerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AgregateLoggerTests.swift; sourceTree = "<group>"; };
 /* End PBXFileReference section */
 
 /* Begin PBXFrameworksBuildPhase section */
@@ -86,6 +88,7 @@
 			children = (
 				2E58D35C21316C3500BEF81A /* LogStringConvertibleTests.swift */,
 				2ED077D621329CA30058EEFC /* LoggetTests.swift */,
+				2ED077D82132A4820058EEFC /* AgregateLoggerTests.swift */,
 				2E58D35E21316C3500BEF81A /* Info.plist */,
 			);
 			path = UnitTests;
@@ -234,6 +237,7 @@
 			buildActionMask = 2147483647;
 			files = (
 				2E58D35D21316C3500BEF81A /* LogStringConvertibleTests.swift in Sources */,
+				2ED077D92132A4820058EEFC /* AgregateLoggerTests.swift in Sources */,
 				2ED077D721329CA30058EEFC /* LoggetTests.swift in Sources */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
diff --git a/UnitTests/AgregateLoggerTests.swift b/UnitTests/AgregateLoggerTests.swift
new file mode 100644
index 0000000..c3c2704
--- /dev/null
+++ b/UnitTests/AgregateLoggerTests.swift
@@ -0,0 +1,68 @@
+//
+// 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 AgregateLoggerTests: XCTestCase {
+
+    func testCallForwarding() {
+        let loggerA = LoggerMock()
+        let loggerB = LoggerMock()
+        let agregateLogger = AgregateLogger(loggers: [loggerA, loggerB])
+        agregateLogger.log("0", level: .emergency)
+        agregateLogger.log("1", level: .alert)
+        agregateLogger.log("2", level: .critical)
+        agregateLogger.log("3", level: .error)
+        agregateLogger.log("4", level: .warning)
+        agregateLogger.log("5", level: .notice)
+        agregateLogger.log("6", level: .informational)
+        agregateLogger.log("7", level: .debug)
+        XCTAssertEqual(loggerA, loggerB)
+    }
+}
+
+private class LoggerMock: Logger {
+    
+    struct Log: Equatable {
+        let time: Date
+        let level: LogLevel
+        let location: String
+        let message: String
+    }
+    
+    private var logs = [Log]()
+    
+    func log(time: Date, level: LogLevel, location: String, message: @autoclosure () -> String) {
+        let log = Log(time: time, level: level, location: location, message: message())
+        logs.append(log)
+    }
+}
+
+extension LoggerMock: Equatable {
+    
+    static func == (lhs: LoggerMock, rhs: LoggerMock) -> Bool {
+        return lhs.logs == rhs.logs
+    }
+}

From 11cdd72a04db9c0932831118f613f3f946ab1f28 Mon Sep 17 00:00:00 2001
From: Wojciech Nagrodzki <278594+wnagrodzki@users.noreply.github.com>
Date: Sun, 26 Aug 2018 12:00:10 +0200
Subject: [PATCH 07/16] Uncoupled Logrotate from FileManager

---
 Logger.xcodeproj/project.pbxproj           |  4 +++
 Logger/Loggers/DiskLogger/DiskLogger.swift |  2 +-
 Logger/Loggers/DiskLogger/FileSystem.swift | 38 ++++++++++++++++++++++
 Logger/Loggers/DiskLogger/Logrotate.swift  | 12 ++++---
 4 files changed, 50 insertions(+), 6 deletions(-)
 create mode 100644 Logger/Loggers/DiskLogger/FileSystem.swift

diff --git a/Logger.xcodeproj/project.pbxproj b/Logger.xcodeproj/project.pbxproj
index 69657b4..11a519e 100644
--- a/Logger.xcodeproj/project.pbxproj
+++ b/Logger.xcodeproj/project.pbxproj
@@ -20,6 +20,7 @@
 		2EBF4B592122B598008E4117 /* Logrotate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2EBF4B562122B598008E4117 /* Logrotate.swift */; };
 		2ED077D721329CA30058EEFC /* LoggetTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2ED077D621329CA30058EEFC /* LoggetTests.swift */; };
 		2ED077D92132A4820058EEFC /* AgregateLoggerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2ED077D82132A4820058EEFC /* AgregateLoggerTests.swift */; };
+		2ED077DB2132B0320058EEFC /* FileSystem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2ED077DA2132B0320058EEFC /* FileSystem.swift */; };
 /* End PBXBuildFile section */
 
 /* Begin PBXContainerItemProxy section */
@@ -62,6 +63,7 @@
 		2EBF4B562122B598008E4117 /* Logrotate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Logrotate.swift; sourceTree = "<group>"; };
 		2ED077D621329CA30058EEFC /* LoggetTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoggetTests.swift; sourceTree = "<group>"; };
 		2ED077D82132A4820058EEFC /* AgregateLoggerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AgregateLoggerTests.swift; sourceTree = "<group>"; };
+		2ED077DA2132B0320058EEFC /* FileSystem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileSystem.swift; sourceTree = "<group>"; };
 /* End PBXFileReference section */
 
 /* Begin PBXFrameworksBuildPhase section */
@@ -142,6 +144,7 @@
 				2EBF4B562122B598008E4117 /* Logrotate.swift */,
 				2EBF4B4F2122B06E008E4117 /* NSFileHandle+Swift.h */,
 				2EBF4B502122B06E008E4117 /* NSFileHandle+Swift.m */,
+				2ED077DA2132B0320058EEFC /* FileSystem.swift */,
 			);
 			path = DiskLogger;
 			sourceTree = "<group>";
@@ -252,6 +255,7 @@
 				2EBF4B3E2122AA34008E4117 /* Logger.swift in Sources */,
 				2EBF4B512122B06E008E4117 /* NSFileHandle+Swift.m in Sources */,
 				2EBF4B4B2122AF53008E4117 /* ConsoleLogger.swift in Sources */,
+				2ED077DB2132B0320058EEFC /* FileSystem.swift in Sources */,
 				2EBF4B4A2122AF53008E4117 /* AgregateLogger.swift in Sources */,
 				2EBF4B592122B598008E4117 /* Logrotate.swift in Sources */,
 				2EBF4B452122ACD6008E4117 /* LogStringConvertible.swift in Sources */,
diff --git a/Logger/Loggers/DiskLogger/DiskLogger.swift b/Logger/Loggers/DiskLogger/DiskLogger.swift
index d4cb80f..20e864e 100644
--- a/Logger/Loggers/DiskLogger/DiskLogger.swift
+++ b/Logger/Loggers/DiskLogger/DiskLogger.swift
@@ -97,7 +97,7 @@ public final class DiskLogger: Logger {
     }
     
     private func rotateLogFiles() throws {
-        let logrotate = Logrotate(fileURL: fileURL, rotations: rotations)
+        let logrotate = Logrotate(fileURL: fileURL, rotations: rotations, fileSystem: FileManager.default)
         try logrotate.rotate()
     }
 }
diff --git a/Logger/Loggers/DiskLogger/FileSystem.swift b/Logger/Loggers/DiskLogger/FileSystem.swift
new file mode 100644
index 0000000..d78c5f4
--- /dev/null
+++ b/Logger/Loggers/DiskLogger/FileSystem.swift
@@ -0,0 +1,38 @@
+//
+// 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 Foundation
+
+protocol FileSystem {
+    func itemExists(at URL: URL) -> Bool
+    func removeItem(at URL: URL) throws
+    func moveItem(at srcURL: URL, to dstURL: URL) throws
+}
+
+extension FileManager: FileSystem {
+    
+    func itemExists(at URL: URL) -> Bool {
+        return fileExists(atPath: URL.path)
+    }
+}
diff --git a/Logger/Loggers/DiskLogger/Logrotate.swift b/Logger/Loggers/DiskLogger/Logrotate.swift
index c377d2e..d2a97e4 100644
--- a/Logger/Loggers/DiskLogger/Logrotate.swift
+++ b/Logger/Loggers/DiskLogger/Logrotate.swift
@@ -29,16 +29,18 @@ final class Logrotate {
     
     private let fileURL: URL
     private let rotations: Int
+    private let fileSystem: FileSystem
     
     /// Initializes new Logrotate instance.
     ///
     /// - Parameters:
     ///   - fileURL: URL of the log file.
     ///   - rotations: Number of times log files are rotated before being removed.
-    init(fileURL: URL, rotations: Int) {
+    init(fileURL: URL, rotations: Int, fileSystem: FileSystem) {
         precondition(rotations > 0)
         self.fileURL = fileURL
         self.rotations = rotations
+        self.fileSystem = fileSystem
     }
     
     /// Rotates log files `rotations` number of times.
@@ -58,12 +60,12 @@ final class Logrotate {
         let toDelete = rotatedURLs.last!
         let toMove = zip(allURLs, rotatedURLs).reversed()
         
-        if FileManager.default.fileExists(atPath: toDelete.path) {
-            try FileManager.default.removeItem(at: toDelete)
+        if fileSystem.itemExists(at: toDelete) {
+            try fileSystem.removeItem(at: toDelete)
         }
         for (oldURL, newURL) in toMove {
-            guard FileManager.default.fileExists(atPath: oldURL.path) else { continue }
-            try FileManager.default.moveItem(at: oldURL, to: newURL)
+            guard fileSystem.itemExists(at: oldURL) else { continue }
+            try fileSystem.moveItem(at: oldURL, to: newURL)
         }
     }
 }

From 7c58a507787a58cc3541f31ceea4b2d6819902a8 Mon Sep 17 00:00:00 2001
From: Wojciech Nagrodzki <278594+wnagrodzki@users.noreply.github.com>
Date: Sun, 26 Aug 2018 12:08:07 +0200
Subject: [PATCH 08/16] Uncoupled DiskLogger from FileManager

---
 Logger/Loggers/DiskLogger/DiskLogger.swift | 10 ++++++----
 Logger/Loggers/DiskLogger/FileSystem.swift |  9 +++++++--
 2 files changed, 13 insertions(+), 6 deletions(-)

diff --git a/Logger/Loggers/DiskLogger/DiskLogger.swift b/Logger/Loggers/DiskLogger/DiskLogger.swift
index 20e864e..dfc6859 100644
--- a/Logger/Loggers/DiskLogger/DiskLogger.swift
+++ b/Logger/Loggers/DiskLogger/DiskLogger.swift
@@ -30,6 +30,7 @@ public final class DiskLogger: Logger {
     private let fileURL: URL
     private let fileSizeLimit: UInt64
     private let rotations: Int
+    private let fileSystem: FileSystem
     private let formatter: DateFormatter
     private let queue = DispatchQueue(label: "com.wnagrodzki.DiskLogger", qos: .background, attributes: [], autoreleaseFrequency: .workItem, target: nil)
     private var buffer = Data()
@@ -41,10 +42,11 @@ public final class DiskLogger: Logger {
     ///   - fileURL: URL of the log file.
     ///   - fileSizeLimit: Maximum size log file can reach in bytes. Attempt to exceeding that limit triggers log files rotation.
     ///   - rotations: Number of times log files are rotated before being removed.
-    public init(fileURL: URL, fileSizeLimit: UInt64, rotations: Int) {
+    public init(fileURL: URL, fileSizeLimit: UInt64, rotations: Int, fileSystem: FileSystem) {
         self.fileURL = fileURL
         self.fileSizeLimit = fileSizeLimit
         self.rotations = rotations
+        self.fileSystem = fileSystem
         formatter = DateFormatter()
         formatter.dateFormat = "yyyy-MM-dd HH:mm:ss.SSS"
         formatter.timeZone = TimeZone(secondsFromGMT: 0)
@@ -80,8 +82,8 @@ public final class DiskLogger: Logger {
     
     private func openFileWriter() throws {
         guard fileWriter == nil else { return }
-        if FileManager.default.fileExists(atPath: fileURL.path) == false {
-            FileManager.default.createFile(atPath: fileURL.path, contents: nil, attributes: nil)
+        if fileSystem.itemExists(at: fileURL) == false {
+            _ = fileSystem.createFile(at: fileURL)
         }
         fileWriter = try FileWriter(fileURL: fileURL, fileSizeLimit: fileSizeLimit)
     }
@@ -97,7 +99,7 @@ public final class DiskLogger: Logger {
     }
     
     private func rotateLogFiles() throws {
-        let logrotate = Logrotate(fileURL: fileURL, rotations: rotations, fileSystem: FileManager.default)
+        let logrotate = Logrotate(fileURL: fileURL, rotations: rotations, fileSystem: fileSystem)
         try logrotate.rotate()
     }
 }
diff --git a/Logger/Loggers/DiskLogger/FileSystem.swift b/Logger/Loggers/DiskLogger/FileSystem.swift
index d78c5f4..b47b316 100644
--- a/Logger/Loggers/DiskLogger/FileSystem.swift
+++ b/Logger/Loggers/DiskLogger/FileSystem.swift
@@ -24,15 +24,20 @@
 
 import Foundation
 
-protocol FileSystem {
+public protocol FileSystem {
     func itemExists(at URL: URL) -> Bool
     func removeItem(at URL: URL) throws
     func moveItem(at srcURL: URL, to dstURL: URL) throws
+    func createFile(at URL: URL) -> Bool
 }
 
 extension FileManager: FileSystem {
     
-    func itemExists(at URL: URL) -> Bool {
+    public func itemExists(at URL: URL) -> Bool {
         return fileExists(atPath: URL.path)
     }
+    
+    public func createFile(at URL: URL) -> Bool {
+        return createFile(atPath: URL.path, contents: nil, attributes: nil)
+    }
 }

From d333992ca6bdcfebac58d33f264ac45656ecd4ab Mon Sep 17 00:00:00 2001
From: Wojciech Nagrodzki <278594+wnagrodzki@users.noreply.github.com>
Date: Sun, 26 Aug 2018 13:24:48 +0200
Subject: [PATCH 09/16] Renamed Logrotate to FileRotate

---
 Logger.xcodeproj/project.pbxproj                          | 8 ++++----
 Logger/Loggers/DiskLogger/DiskLogger.swift                | 2 +-
 .../DiskLogger/{Logrotate.swift => Filerotate.swift}      | 2 +-
 3 files changed, 6 insertions(+), 6 deletions(-)
 rename Logger/Loggers/DiskLogger/{Logrotate.swift => Filerotate.swift} (99%)

diff --git a/Logger.xcodeproj/project.pbxproj b/Logger.xcodeproj/project.pbxproj
index 11a519e..a26581b 100644
--- a/Logger.xcodeproj/project.pbxproj
+++ b/Logger.xcodeproj/project.pbxproj
@@ -17,7 +17,7 @@
 		2EBF4B512122B06E008E4117 /* NSFileHandle+Swift.m in Sources */ = {isa = PBXBuildFile; fileRef = 2EBF4B502122B06E008E4117 /* NSFileHandle+Swift.m */; };
 		2EBF4B572122B598008E4117 /* DiskLogger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2EBF4B542122B598008E4117 /* DiskLogger.swift */; };
 		2EBF4B582122B598008E4117 /* FileWriter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2EBF4B552122B598008E4117 /* FileWriter.swift */; };
-		2EBF4B592122B598008E4117 /* Logrotate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2EBF4B562122B598008E4117 /* Logrotate.swift */; };
+		2EBF4B592122B598008E4117 /* FileRotate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2EBF4B562122B598008E4117 /* FileRotate.swift */; };
 		2ED077D721329CA30058EEFC /* LoggetTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2ED077D621329CA30058EEFC /* LoggetTests.swift */; };
 		2ED077D92132A4820058EEFC /* AgregateLoggerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2ED077D82132A4820058EEFC /* AgregateLoggerTests.swift */; };
 		2ED077DB2132B0320058EEFC /* FileSystem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2ED077DA2132B0320058EEFC /* FileSystem.swift */; };
@@ -60,7 +60,7 @@
 		2EBF4B532122B2AA008E4117 /* Logger-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Logger-Bridging-Header.h"; sourceTree = "<group>"; };
 		2EBF4B542122B598008E4117 /* DiskLogger.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DiskLogger.swift; sourceTree = "<group>"; };
 		2EBF4B552122B598008E4117 /* FileWriter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FileWriter.swift; sourceTree = "<group>"; };
-		2EBF4B562122B598008E4117 /* Logrotate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Logrotate.swift; sourceTree = "<group>"; };
+		2EBF4B562122B598008E4117 /* FileRotate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FileRotate.swift; sourceTree = "<group>"; };
 		2ED077D621329CA30058EEFC /* LoggetTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoggetTests.swift; sourceTree = "<group>"; };
 		2ED077D82132A4820058EEFC /* AgregateLoggerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AgregateLoggerTests.swift; sourceTree = "<group>"; };
 		2ED077DA2132B0320058EEFC /* FileSystem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileSystem.swift; sourceTree = "<group>"; };
@@ -141,7 +141,7 @@
 			children = (
 				2EBF4B542122B598008E4117 /* DiskLogger.swift */,
 				2EBF4B552122B598008E4117 /* FileWriter.swift */,
-				2EBF4B562122B598008E4117 /* Logrotate.swift */,
+				2EBF4B562122B598008E4117 /* FileRotate.swift */,
 				2EBF4B4F2122B06E008E4117 /* NSFileHandle+Swift.h */,
 				2EBF4B502122B06E008E4117 /* NSFileHandle+Swift.m */,
 				2ED077DA2132B0320058EEFC /* FileSystem.swift */,
@@ -257,7 +257,7 @@
 				2EBF4B4B2122AF53008E4117 /* ConsoleLogger.swift in Sources */,
 				2ED077DB2132B0320058EEFC /* FileSystem.swift in Sources */,
 				2EBF4B4A2122AF53008E4117 /* AgregateLogger.swift in Sources */,
-				2EBF4B592122B598008E4117 /* Logrotate.swift in Sources */,
+				2EBF4B592122B598008E4117 /* FileRotate.swift in Sources */,
 				2EBF4B452122ACD6008E4117 /* LogStringConvertible.swift in Sources */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
diff --git a/Logger/Loggers/DiskLogger/DiskLogger.swift b/Logger/Loggers/DiskLogger/DiskLogger.swift
index dfc6859..89e46b8 100644
--- a/Logger/Loggers/DiskLogger/DiskLogger.swift
+++ b/Logger/Loggers/DiskLogger/DiskLogger.swift
@@ -99,7 +99,7 @@ public final class DiskLogger: Logger {
     }
     
     private func rotateLogFiles() throws {
-        let logrotate = Logrotate(fileURL: fileURL, rotations: rotations, fileSystem: fileSystem)
+        let logrotate = FileRotate(fileURL: fileURL, rotations: rotations, fileSystem: fileSystem)
         try logrotate.rotate()
     }
 }
diff --git a/Logger/Loggers/DiskLogger/Logrotate.swift b/Logger/Loggers/DiskLogger/Filerotate.swift
similarity index 99%
rename from Logger/Loggers/DiskLogger/Logrotate.swift
rename to Logger/Loggers/DiskLogger/Filerotate.swift
index d2a97e4..bda34b9 100644
--- a/Logger/Loggers/DiskLogger/Logrotate.swift
+++ b/Logger/Loggers/DiskLogger/Filerotate.swift
@@ -25,7 +25,7 @@
 import Foundation
 
 /// Allows log files rotation.
-final class Logrotate {
+final class FileRotate {
     
     private let fileURL: URL
     private let rotations: Int

From 1144d6a90fbad06e9da0fa545e5e6f5a9186282e Mon Sep 17 00:00:00 2001
From: Wojciech Nagrodzki <278594+wnagrodzki@users.noreply.github.com>
Date: Sun, 26 Aug 2018 13:30:53 +0200
Subject: [PATCH 10/16] Uncoupled DiskLogger from FileRotate

---
 Logger/Loggers/DiskLogger/DiskLogger.swift | 40 ++++++++++++++++++++--
 Logger/Loggers/DiskLogger/Filerotate.swift | 12 ++-----
 2 files changed, 41 insertions(+), 11 deletions(-)

diff --git a/Logger/Loggers/DiskLogger/DiskLogger.swift b/Logger/Loggers/DiskLogger/DiskLogger.swift
index 89e46b8..4088a62 100644
--- a/Logger/Loggers/DiskLogger/DiskLogger.swift
+++ b/Logger/Loggers/DiskLogger/DiskLogger.swift
@@ -24,6 +24,30 @@
 
 import Foundation
 
+/// Allows log files rotation.
+protocol Logrotate {
+    
+    /// Rotates log files `rotations` number of times.
+    ///
+    /// First deletes file at `<fileURL>.<rotations>`.
+    /// Next moves files located at:
+    ///
+    /// `<fileURL>, <fileURL>.1, <fileURL>.2 ... <fileURL>.<rotations - 1>`
+    ///
+    /// to `<fileURL>.1, <fileURL>.2 ... <fileURL>.<rotations>`
+    func rotate() throws
+}
+
+protocol LogrotateFactory {
+    
+    /// Returns newly initialized Logrotate instance.
+    ///
+    /// - Parameters:
+    ///   - fileURL: URL of the log file.
+    ///   - rotations: Number of times log files are rotated before being removed.
+    func makeInstance(fileURL: URL, rotations: Int) -> Logrotate
+}
+
 /// Logger that writes messages into the file at specified URL with log rotation support.
 public final class DiskLogger: Logger {
     
@@ -31,6 +55,7 @@ public final class DiskLogger: Logger {
     private let fileSizeLimit: UInt64
     private let rotations: Int
     private let fileSystem: FileSystem
+    private let logrotateFactory: LogrotateFactory
     private let formatter: DateFormatter
     private let queue = DispatchQueue(label: "com.wnagrodzki.DiskLogger", qos: .background, attributes: [], autoreleaseFrequency: .workItem, target: nil)
     private var buffer = Data()
@@ -42,11 +67,16 @@ public final class DiskLogger: Logger {
     ///   - fileURL: URL of the log file.
     ///   - fileSizeLimit: Maximum size log file can reach in bytes. Attempt to exceeding that limit triggers log files rotation.
     ///   - rotations: Number of times log files are rotated before being removed.
-    public init(fileURL: URL, fileSizeLimit: UInt64, rotations: Int, fileSystem: FileSystem) {
+    public convenience init(fileURL: URL, fileSizeLimit: UInt64, rotations: Int) {
+        self.init(fileURL: fileURL, fileSizeLimit: fileSizeLimit, rotations: rotations, fileSystem: FileManager.default, logrotateFactory: FileRotateFactory())
+    }
+    
+    init(fileURL: URL, fileSizeLimit: UInt64, rotations: Int, fileSystem: FileSystem, logrotateFactory: LogrotateFactory) {
         self.fileURL = fileURL
         self.fileSizeLimit = fileSizeLimit
         self.rotations = rotations
         self.fileSystem = fileSystem
+        self.logrotateFactory = logrotateFactory
         formatter = DateFormatter()
         formatter.dateFormat = "yyyy-MM-dd HH:mm:ss.SSS"
         formatter.timeZone = TimeZone(secondsFromGMT: 0)
@@ -99,7 +129,13 @@ public final class DiskLogger: Logger {
     }
     
     private func rotateLogFiles() throws {
-        let logrotate = FileRotate(fileURL: fileURL, rotations: rotations, fileSystem: fileSystem)
+        let logrotate = logrotateFactory.makeInstance(fileURL: fileURL, rotations: rotations)
         try logrotate.rotate()
     }
 }
+
+private class FileRotateFactory: LogrotateFactory {
+    func makeInstance(fileURL: URL, rotations: Int) -> Logrotate {
+        return FileRotate(fileURL: fileURL, rotations: rotations, fileSystem: FileManager.default)
+    }
+}
diff --git a/Logger/Loggers/DiskLogger/Filerotate.swift b/Logger/Loggers/DiskLogger/Filerotate.swift
index bda34b9..0963cfa 100644
--- a/Logger/Loggers/DiskLogger/Filerotate.swift
+++ b/Logger/Loggers/DiskLogger/Filerotate.swift
@@ -24,7 +24,6 @@
 
 import Foundation
 
-/// Allows log files rotation.
 final class FileRotate {
     
     private let fileURL: URL
@@ -42,15 +41,10 @@ final class FileRotate {
         self.rotations = rotations
         self.fileSystem = fileSystem
     }
+}
+
+extension FileRotate: Logrotate {
     
-    /// Rotates log files `rotations` number of times.
-    ///
-    /// First deletes file at `<fileURL>.<rotations>`.
-    /// Next moves files located at:
-    ///
-    /// `<fileURL>, <fileURL>.1, <fileURL>.2 ... <fileURL>.<rotations - 1>`
-    ///
-    /// to `<fileURL>.1, <fileURL>.2 ... <fileURL>.<rotations>`
     func rotate() throws {
         let range = 1...rotations
         let pathExtensions = range.map { "\($0)" }

From 74fe9ba0d2ec276adcfdf0d7d13491469b9a9b5c Mon Sep 17 00:00:00 2001
From: Wojciech Nagrodzki <278594+wnagrodzki@users.noreply.github.com>
Date: Tue, 28 Aug 2018 19:14:55 +0200
Subject: [PATCH 11/16] Uncoupled DiskLogger from FileWriter

---
 Logger/Loggers/DiskLogger/DiskLogger.swift | 62 +++++++++++++++++-----
 Logger/Loggers/DiskLogger/FileWriter.swift | 14 ++---
 2 files changed, 53 insertions(+), 23 deletions(-)

diff --git a/Logger/Loggers/DiskLogger/DiskLogger.swift b/Logger/Loggers/DiskLogger/DiskLogger.swift
index 4088a62..5eebbb5 100644
--- a/Logger/Loggers/DiskLogger/DiskLogger.swift
+++ b/Logger/Loggers/DiskLogger/DiskLogger.swift
@@ -24,6 +24,34 @@
 
 import Foundation
 
+/// Write failed as allowed size limit would be exceeded.
+public struct SizeLimitedFileQuotaReached: Error {}
+
+/// Allows writing to a file while respecting allowed size limit.
+protocol SizeLimitedFile {
+    
+    /// Synchronously writes `data` at the end of the file.
+    ///
+    /// - Parameter data: The data to be written.
+    /// - Throws: Throws an error if no free space is left on the file system, or if any other writing error occurs.
+    ///           Throws `SizeLimitedFileQuotaReached` if allowed size limit would be exceeded.
+    func write(_ data: Data) throws
+    
+    /// Writes all in-memory data to permanent storage and closes the file.
+    func synchronizeAndCloseFile()
+}
+
+protocol SizeLimitedFileFactory {
+    
+    /// Returns newly initialized SizeLimitedFile instance.
+    ///
+    /// - Parameters:
+    ///   - fileURL: URL of the file.
+    ///   - fileSizeLimit: Maximum size the file can reach in bytes.
+    /// - Throws: An error that may occur while the file is being opened for writing.
+    func makeInstance(fileURL: URL, fileSizeLimit: UInt64) throws -> SizeLimitedFile
+}
+
 /// Allows log files rotation.
 protocol Logrotate {
     
@@ -55,11 +83,12 @@ public final class DiskLogger: Logger {
     private let fileSizeLimit: UInt64
     private let rotations: Int
     private let fileSystem: FileSystem
+    private let sizeLimitedFileFactory: SizeLimitedFileFactory
     private let logrotateFactory: LogrotateFactory
     private let formatter: DateFormatter
     private let queue = DispatchQueue(label: "com.wnagrodzki.DiskLogger", qos: .background, attributes: [], autoreleaseFrequency: .workItem, target: nil)
     private var buffer = Data()
-    private var fileWriter: FileWriter!
+    private var sizeLimitedFile: SizeLimitedFile!
     
     /// Initializes new DiskLogger instance.
     ///
@@ -68,14 +97,15 @@ public final class DiskLogger: Logger {
     ///   - fileSizeLimit: Maximum size log file can reach in bytes. Attempt to exceeding that limit triggers log files rotation.
     ///   - rotations: Number of times log files are rotated before being removed.
     public convenience init(fileURL: URL, fileSizeLimit: UInt64, rotations: Int) {
-        self.init(fileURL: fileURL, fileSizeLimit: fileSizeLimit, rotations: rotations, fileSystem: FileManager.default, logrotateFactory: FileRotateFactory())
+        self.init(fileURL: fileURL, fileSizeLimit: fileSizeLimit, rotations: rotations, fileSystem: FileManager.default, sizeLimitedFileFactory: FileWriterFactory(),  logrotateFactory: FileRotateFactory())
     }
     
-    init(fileURL: URL, fileSizeLimit: UInt64, rotations: Int, fileSystem: FileSystem, logrotateFactory: LogrotateFactory) {
+    init(fileURL: URL, fileSizeLimit: UInt64, rotations: Int, fileSystem: FileSystem, sizeLimitedFileFactory: SizeLimitedFileFactory, logrotateFactory: LogrotateFactory) {
         self.fileURL = fileURL
         self.fileSizeLimit = fileSizeLimit
         self.rotations = rotations
         self.fileSystem = fileSystem
+        self.sizeLimitedFileFactory = sizeLimitedFileFactory
         self.logrotateFactory = logrotateFactory
         formatter = DateFormatter()
         formatter.dateFormat = "yyyy-MM-dd HH:mm:ss.SSS"
@@ -93,12 +123,12 @@ public final class DiskLogger: Logger {
             self.buffer.append(data)
             
             do {
-                try self.openFileWriter()
+                try self.openSizeLimitedFile()
                 do {
                     try self.writeBuffer()
                 }
-                catch is FileWriter.FileSizeLimitReached {
-                    self.closeFileWriter()
+                catch is SizeLimitedFileQuotaReached {
+                    self.closeSizeLimitedFile()
                     try self.rotateLogFiles()
                 }
             }
@@ -110,22 +140,22 @@ public final class DiskLogger: Logger {
         }
     }
     
-    private func openFileWriter() throws {
-        guard fileWriter == nil else { return }
+    private func openSizeLimitedFile() throws {
+        guard sizeLimitedFile == nil else { return }
         if fileSystem.itemExists(at: fileURL) == false {
             _ = fileSystem.createFile(at: fileURL)
         }
-        fileWriter = try FileWriter(fileURL: fileURL, fileSizeLimit: fileSizeLimit)
+        sizeLimitedFile = try sizeLimitedFileFactory.makeInstance(fileURL: fileURL, fileSizeLimit: fileSizeLimit)
     }
     
     private func writeBuffer() throws {
-        try fileWriter.write(buffer)
+        try sizeLimitedFile.write(buffer)
         buffer.removeAll()
     }
     
-    private func closeFileWriter() {
-        self.fileWriter.synchronizeAndCloseFile()
-        self.fileWriter = nil
+    private func closeSizeLimitedFile() {
+        self.sizeLimitedFile.synchronizeAndCloseFile()
+        self.sizeLimitedFile = nil
     }
     
     private func rotateLogFiles() throws {
@@ -139,3 +169,9 @@ private class FileRotateFactory: LogrotateFactory {
         return FileRotate(fileURL: fileURL, rotations: rotations, fileSystem: FileManager.default)
     }
 }
+
+private class FileWriterFactory: SizeLimitedFileFactory {
+    func makeInstance(fileURL: URL, fileSizeLimit: UInt64) throws -> SizeLimitedFile {
+        return try FileWriter(fileURL: fileURL, fileSizeLimit: fileSizeLimit)
+    }
+}
diff --git a/Logger/Loggers/DiskLogger/FileWriter.swift b/Logger/Loggers/DiskLogger/FileWriter.swift
index 1083812..471703d 100644
--- a/Logger/Loggers/DiskLogger/FileWriter.swift
+++ b/Logger/Loggers/DiskLogger/FileWriter.swift
@@ -27,9 +27,6 @@ import Foundation
 /// Allows writing to a file while respecting allowed size limit.
 final class FileWriter {
     
-    /// Write failed as allowed size limit would be exceeded for the file.
-    struct FileSizeLimitReached: Error {}
-    
     private let handle: FileHandle
     private let sizeLimit: UInt64
     private var currentSize: UInt64
@@ -45,22 +42,19 @@ final class FileWriter {
         self.sizeLimit = fileSizeLimit
         currentSize = handle.seekToEndOfFile()
     }
+}
+
+extension FileWriter: SizeLimitedFile {
     
-    /// Synchronously writes `data` at the end of the file.
-    ///
-    /// - Parameter data: The data to be written.
-    /// - Throws: Throws an error if no free space is left on the file system, or if any other writing error occurs.
-    ///           Throws `FileSizeLimitReached` if allowed size limit would be exceeded for the file.
     func write(_ data: Data) throws {
         let dataSize = UInt64(data.count)
         guard currentSize + dataSize <= sizeLimit else {
-            throw FileSizeLimitReached()
+            throw SizeLimitedFileQuotaReached()
         }
         try handle.swift_write(data)
         currentSize += dataSize
     }
     
-    /// Writes all in-memory data to permanent storage and closes the file.
     func synchronizeAndCloseFile() {
         handle.synchronizeFile()
         handle.closeFile()

From ab7ec33418fe89cf0f07d82ac1b26d8fca58b810 Mon Sep 17 00:00:00 2001
From: Wojciech Nagrodzki <278594+wnagrodzki@users.noreply.github.com>
Date: Tue, 28 Aug 2018 20:01:26 +0200
Subject: [PATCH 12/16] Uncoupled FileWriter from FileHandle

---
 Logger/Loggers/DiskLogger/DiskLogger.swift | 12 ++++++++++-
 Logger/Loggers/DiskLogger/FileWriter.swift | 25 ++++++++++++++++------
 2 files changed, 29 insertions(+), 8 deletions(-)

diff --git a/Logger/Loggers/DiskLogger/DiskLogger.swift b/Logger/Loggers/DiskLogger/DiskLogger.swift
index 5eebbb5..6718d09 100644
--- a/Logger/Loggers/DiskLogger/DiskLogger.swift
+++ b/Logger/Loggers/DiskLogger/DiskLogger.swift
@@ -172,6 +172,16 @@ private class FileRotateFactory: LogrotateFactory {
 
 private class FileWriterFactory: SizeLimitedFileFactory {
     func makeInstance(fileURL: URL, fileSizeLimit: UInt64) throws -> SizeLimitedFile {
-        return try FileWriter(fileURL: fileURL, fileSizeLimit: fileSizeLimit)
+        return try FileWriter(fileURL: fileURL, fileSizeLimit: fileSizeLimit, fileFactory: FileHandleFactory())
     }
 }
+
+private class FileHandleFactory: FileFactory {
+    func makeInstance(forWritingTo: URL) throws -> File {
+        return try FileHandle(forWritingTo: forWritingTo)
+    }
+}
+
+extension FileHandle: File {
+    
+}
diff --git a/Logger/Loggers/DiskLogger/FileWriter.swift b/Logger/Loggers/DiskLogger/FileWriter.swift
index 471703d..be1ff30 100644
--- a/Logger/Loggers/DiskLogger/FileWriter.swift
+++ b/Logger/Loggers/DiskLogger/FileWriter.swift
@@ -24,10 +24,21 @@
 
 import Foundation
 
+protocol File {
+    func seekToEndOfFile() -> UInt64
+    func swift_write(_ data: Data) throws
+    func synchronizeFile()
+    func closeFile()
+}
+
+protocol FileFactory {
+    func makeInstance(forWritingTo: URL) throws -> File
+}
+
 /// Allows writing to a file while respecting allowed size limit.
 final class FileWriter {
     
-    private let handle: FileHandle
+    private let file: File
     private let sizeLimit: UInt64
     private var currentSize: UInt64
     
@@ -37,10 +48,10 @@ final class FileWriter {
     ///   - fileURL: URL of the file.
     ///   - fileSizeLimit: Maximum size the file can reach in bytes.
     /// - Throws: An error that may occur while the file is being opened for writing.
-    init(fileURL: URL, fileSizeLimit: UInt64) throws {
-        handle = try FileHandle(forWritingTo: fileURL)
+    init(fileURL: URL, fileSizeLimit: UInt64, fileFactory: FileFactory) throws {
+        file = try fileFactory.makeInstance(forWritingTo: fileURL)
         self.sizeLimit = fileSizeLimit
-        currentSize = handle.seekToEndOfFile()
+        currentSize = file.seekToEndOfFile()
     }
 }
 
@@ -51,12 +62,12 @@ extension FileWriter: SizeLimitedFile {
         guard currentSize + dataSize <= sizeLimit else {
             throw SizeLimitedFileQuotaReached()
         }
-        try handle.swift_write(data)
+        try file.swift_write(data)
         currentSize += dataSize
     }
     
     func synchronizeAndCloseFile() {
-        handle.synchronizeFile()
-        handle.closeFile()
+        file.synchronizeFile()
+        file.closeFile()
     }
 }

From 9253166f1dddb11a88ec9f732054837075224367 Mon Sep 17 00:00:00 2001
From: Wojciech Nagrodzki <278594+wnagrodzki@users.noreply.github.com>
Date: Tue, 28 Aug 2018 21:02:51 +0200
Subject: [PATCH 13/16] Added FileRotateTests

---
 Logger.xcodeproj/project.pbxproj |   4 +
 UnitTests/FileRotateTests.swift  | 181 +++++++++++++++++++++++++++++++
 2 files changed, 185 insertions(+)
 create mode 100644 UnitTests/FileRotateTests.swift

diff --git a/Logger.xcodeproj/project.pbxproj b/Logger.xcodeproj/project.pbxproj
index a26581b..076fc31 100644
--- a/Logger.xcodeproj/project.pbxproj
+++ b/Logger.xcodeproj/project.pbxproj
@@ -21,6 +21,7 @@
 		2ED077D721329CA30058EEFC /* LoggetTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2ED077D621329CA30058EEFC /* LoggetTests.swift */; };
 		2ED077D92132A4820058EEFC /* AgregateLoggerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2ED077D82132A4820058EEFC /* AgregateLoggerTests.swift */; };
 		2ED077DB2132B0320058EEFC /* FileSystem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2ED077DA2132B0320058EEFC /* FileSystem.swift */; };
+		2ED103E12135C61100EB3683 /* FileRotateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2ED103E02135C61100EB3683 /* FileRotateTests.swift */; };
 /* End PBXBuildFile section */
 
 /* Begin PBXContainerItemProxy section */
@@ -64,6 +65,7 @@
 		2ED077D621329CA30058EEFC /* LoggetTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoggetTests.swift; sourceTree = "<group>"; };
 		2ED077D82132A4820058EEFC /* AgregateLoggerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AgregateLoggerTests.swift; sourceTree = "<group>"; };
 		2ED077DA2132B0320058EEFC /* FileSystem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileSystem.swift; sourceTree = "<group>"; };
+		2ED103E02135C61100EB3683 /* FileRotateTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileRotateTests.swift; sourceTree = "<group>"; };
 /* End PBXFileReference section */
 
 /* Begin PBXFrameworksBuildPhase section */
@@ -91,6 +93,7 @@
 				2E58D35C21316C3500BEF81A /* LogStringConvertibleTests.swift */,
 				2ED077D621329CA30058EEFC /* LoggetTests.swift */,
 				2ED077D82132A4820058EEFC /* AgregateLoggerTests.swift */,
+				2ED103E02135C61100EB3683 /* FileRotateTests.swift */,
 				2E58D35E21316C3500BEF81A /* Info.plist */,
 			);
 			path = UnitTests;
@@ -241,6 +244,7 @@
 			files = (
 				2E58D35D21316C3500BEF81A /* LogStringConvertibleTests.swift in Sources */,
 				2ED077D92132A4820058EEFC /* AgregateLoggerTests.swift in Sources */,
+				2ED103E12135C61100EB3683 /* FileRotateTests.swift in Sources */,
 				2ED077D721329CA30058EEFC /* LoggetTests.swift in Sources */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
diff --git a/UnitTests/FileRotateTests.swift b/UnitTests/FileRotateTests.swift
new file mode 100644
index 0000000..13ca7c0
--- /dev/null
+++ b/UnitTests/FileRotateTests.swift
@@ -0,0 +1,181 @@
+//
+// 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 FileRotateTests: XCTestCase {
+    
+    let logURL = URL(fileURLWithPath: "/var/log/application.log")
+    let log1URL = URL(fileURLWithPath: "/var/log/application.log.1")
+    let log2URL = URL(fileURLWithPath: "/var/log/application.log.2")
+    let log3URL = URL(fileURLWithPath: "/var/log/application.log.3")
+    
+    func test_1rotation_0files() {
+        let fileSystem = FileSystemMock(files: [])
+        let logrotate = FileRotate(fileURL: logURL, rotations: 1, fileSystem: fileSystem)
+        try? logrotate.rotate()
+        
+        let actual = fileSystem.files
+        let expected = Set<URL>()
+        XCTAssertEqual(actual, expected)
+    }
+    
+    func test_1rotation_1file() {
+        let fileSystem = FileSystemMock(files: [logURL])
+        let logrotate = FileRotate(fileURL: logURL, rotations: 1, fileSystem: fileSystem)
+        try? logrotate.rotate()
+        
+        let actual = fileSystem.files
+        let expected = Set<URL>([log1URL])
+        XCTAssertEqual(actual, expected)
+    }
+    
+    func test_1rotation_2files() {
+        let fileSystem = FileSystemMock(files: [logURL, log1URL])
+        let logrotate = FileRotate(fileURL: logURL, rotations: 1, fileSystem: fileSystem)
+        try? logrotate.rotate()
+        
+        let actual = fileSystem.files
+        let expected = Set<URL>([log1URL])
+        XCTAssertEqual(actual, expected)
+    }
+    
+    func test_1rotation_3files() {
+        let fileSystem = FileSystemMock(files: [logURL, log1URL, log2URL])
+        let logrotate = FileRotate(fileURL: logURL, rotations: 1, fileSystem: fileSystem)
+        try? logrotate.rotate()
+        
+        let actual = fileSystem.files
+        let expected = Set<URL>([log1URL, log2URL])
+        XCTAssertEqual(actual, expected)
+    }
+    
+    func test_2rotations_0files() {
+        let fileSystem = FileSystemMock(files: [])
+        let logrotate = FileRotate(fileURL: logURL, rotations: 2, fileSystem: fileSystem)
+        try? logrotate.rotate()
+        
+        let actual = fileSystem.files
+        let expected = Set<URL>()
+        XCTAssertEqual(actual, expected)
+    }
+    
+    func test_2rotations_1file() {
+        let fileSystem = FileSystemMock(files: [logURL])
+        let logrotate = FileRotate(fileURL: logURL, rotations: 2, fileSystem: fileSystem)
+        try? logrotate.rotate()
+        
+        let actual = fileSystem.files
+        let expected = Set<URL>([log1URL])
+        XCTAssertEqual(actual, expected)
+    }
+    
+    func test_2rotations_2files() {
+        let fileSystem = FileSystemMock(files: [logURL, log1URL])
+        let logrotate = FileRotate(fileURL: logURL, rotations: 2, fileSystem: fileSystem)
+        try? logrotate.rotate()
+        
+        let actual = fileSystem.files
+        let expected = Set<URL>([log1URL, log2URL])
+        XCTAssertEqual(actual, expected)
+    }
+    
+    func test_2rotations_3files() {
+        let fileSystem = FileSystemMock(files: [logURL, log1URL, log2URL])
+        let logrotate = FileRotate(fileURL: logURL, rotations: 2, fileSystem: fileSystem)
+        try? logrotate.rotate()
+        
+        let actual = fileSystem.files
+        let expected = Set<URL>([log1URL, log2URL])
+        XCTAssertEqual(actual, expected)
+    }
+    
+    func test_2rotations_4files() {
+        let fileSystem = FileSystemMock(files: [logURL, log1URL, log2URL, log3URL])
+        let logrotate = FileRotate(fileURL: logURL, rotations: 2, fileSystem: fileSystem)
+        try? logrotate.rotate()
+        
+        let actual = fileSystem.files
+        let expected = Set<URL>([log1URL, log2URL, log3URL])
+        XCTAssertEqual(actual, expected)
+    }
+    
+    func testErrorPropagation() {
+        let fileSystem = BrokenFileSystem()
+        let logrotate = FileRotate(fileURL: logURL, rotations: 1, fileSystem: fileSystem)
+        
+        XCTAssertThrowsError(try logrotate.rotate(), "An error when removing or moving an item") { (error) in
+            XCTAssertTrue(error is BrokenFileSystem.IOError)
+        }
+    }
+}
+
+private class FileSystemMock: FileSystem {
+    
+    private(set) var files = Set<URL>()
+    
+    init(files: Set<URL>) {
+        self.files = files
+    }
+    
+    func itemExists(at URL: URL) -> Bool {
+        return files.contains(URL)
+    }
+    
+    func removeItem(at URL: URL) throws {
+        files.remove(URL)
+    }
+    
+    func moveItem(at srcURL: URL, to dstURL: URL) throws {
+        files.remove(srcURL)
+        files.insert(dstURL)
+    }
+    
+    func createFile(at URL: URL) -> Bool {
+        files.insert(URL)
+        return true
+    }
+}
+
+private class BrokenFileSystem: FileSystem {
+    
+    struct IOError: Error { }
+    
+    func itemExists(at URL: URL) -> Bool {
+        return true
+    }
+    
+    func removeItem(at URL: URL) throws {
+        throw IOError()
+    }
+    
+    func moveItem(at srcURL: URL, to dstURL: URL) throws {
+        throw IOError()
+    }
+    
+    func createFile(at URL: URL) -> Bool {
+        return false
+    }
+}

From b224674222ea3114f9411956db960aebc3e64565 Mon Sep 17 00:00:00 2001
From: Wojciech Nagrodzki <278594+wnagrodzki@users.noreply.github.com>
Date: Thu, 30 Aug 2018 18:38:53 +0200
Subject: [PATCH 14/16] Added FileWriterTests

---
 Logger.xcodeproj/project.pbxproj |   4 ++
 UnitTests/FileWriterTests.swift  | 107 +++++++++++++++++++++++++++++++
 2 files changed, 111 insertions(+)
 create mode 100644 UnitTests/FileWriterTests.swift

diff --git a/Logger.xcodeproj/project.pbxproj b/Logger.xcodeproj/project.pbxproj
index 076fc31..6a86dba 100644
--- a/Logger.xcodeproj/project.pbxproj
+++ b/Logger.xcodeproj/project.pbxproj
@@ -22,6 +22,7 @@
 		2ED077D92132A4820058EEFC /* AgregateLoggerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2ED077D82132A4820058EEFC /* AgregateLoggerTests.swift */; };
 		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 */; };
 /* End PBXBuildFile section */
 
 /* Begin PBXContainerItemProxy section */
@@ -66,6 +67,7 @@
 		2ED077D82132A4820058EEFC /* AgregateLoggerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AgregateLoggerTests.swift; sourceTree = "<group>"; };
 		2ED077DA2132B0320058EEFC /* FileSystem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileSystem.swift; sourceTree = "<group>"; };
 		2ED103E02135C61100EB3683 /* FileRotateTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileRotateTests.swift; sourceTree = "<group>"; };
+		2ED103E22135D3FB00EB3683 /* FileWriterTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileWriterTests.swift; sourceTree = "<group>"; };
 /* End PBXFileReference section */
 
 /* Begin PBXFrameworksBuildPhase section */
@@ -93,6 +95,7 @@
 				2E58D35C21316C3500BEF81A /* LogStringConvertibleTests.swift */,
 				2ED077D621329CA30058EEFC /* LoggetTests.swift */,
 				2ED077D82132A4820058EEFC /* AgregateLoggerTests.swift */,
+				2ED103E22135D3FB00EB3683 /* FileWriterTests.swift */,
 				2ED103E02135C61100EB3683 /* FileRotateTests.swift */,
 				2E58D35E21316C3500BEF81A /* Info.plist */,
 			);
@@ -243,6 +246,7 @@
 			buildActionMask = 2147483647;
 			files = (
 				2E58D35D21316C3500BEF81A /* LogStringConvertibleTests.swift in Sources */,
+				2ED103E32135D3FB00EB3683 /* FileWriterTests.swift in Sources */,
 				2ED077D92132A4820058EEFC /* AgregateLoggerTests.swift in Sources */,
 				2ED103E12135C61100EB3683 /* FileRotateTests.swift in Sources */,
 				2ED077D721329CA30058EEFC /* LoggetTests.swift in Sources */,
diff --git a/UnitTests/FileWriterTests.swift b/UnitTests/FileWriterTests.swift
new file mode 100644
index 0000000..cb3ebc7
--- /dev/null
+++ b/UnitTests/FileWriterTests.swift
@@ -0,0 +1,107 @@
+//
+// 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 FileWriterTests: XCTestCase {
+    
+    let logURL = URL(fileURLWithPath: "/var/log/application.log")
+    
+    func testFileOpeningFailure() {
+        let factory = UnopenableFileFactory()
+        XCTAssertThrowsError(try FileWriter(fileURL: logURL, fileSizeLimit: 0, fileFactory: factory), "file open failure") { (error) in
+            XCTAssertTrue(error is UnopenableFileFactory.OpenFileError)
+        }
+    }
+    
+    func testKeepingFileSizeLimit() throws {
+        let factory = FileMockFactory()
+        let writer = try FileWriter(fileURL: logURL, fileSizeLimit: 1, fileFactory: factory)
+        let data = Data(bytes: [0])
+        try writer.write(data)
+        
+        XCTAssertEqual(factory.mock.writtenData, data)
+    }
+    
+    func testExceedingFileSizeLimit() throws {
+        let factory = FileMockFactory()
+        let writer = try FileWriter(fileURL: logURL, fileSizeLimit: 1, fileFactory: factory)
+        let data = Data(bytes: [0, 0])
+        
+        XCTAssertThrowsError(try writer.write(data), "file size limit exceeded") { (error) in
+            XCTAssertTrue(error is SizeLimitedFileQuotaReached)
+        }
+        
+        XCTAssertEqual(factory.mock.writtenData.count, 0)
+    }
+    
+    func testSynchronizingAndClosingFile() throws {
+        let factory = FileMockFactory()
+        let writer = try FileWriter(fileURL: logURL, fileSizeLimit: 1, fileFactory: factory)
+        writer.synchronizeAndCloseFile()
+        XCTAssertTrue(factory.mock.synchronizeFileCallCount == 1 && factory.mock.closeFileCallCount == 1)
+    }
+}
+
+private class UnopenableFileFactory: FileFactory {
+    
+    struct OpenFileError: Error {}
+    
+    func makeInstance(forWritingTo: URL) throws -> File {
+        throw OpenFileError()
+    }
+}
+
+private class FileMockFactory: FileFactory {
+    
+    let mock = FileMock()
+    
+    func makeInstance(forWritingTo: URL) throws -> File {
+        return mock
+    }
+}
+
+private class FileMock: File {
+    
+    private(set) var writtenData = Data()
+    private(set) var synchronizeFileCallCount = 0
+    private(set) var closeFileCallCount = 0
+    
+    func seekToEndOfFile() -> UInt64 {
+        return 0
+    }
+    
+    func swift_write(_ data: Data) throws {
+        self.writtenData.append(data)
+    }
+    
+    func synchronizeFile() {
+        synchronizeFileCallCount += 1
+    }
+    
+    func closeFile() {
+        closeFileCallCount += 1
+    }
+}

From cedf3a2b52472cd5d6c75284a6c83f0d9835629b Mon Sep 17 00:00:00 2001
From: Wojciech Nagrodzki <278594+wnagrodzki@users.noreply.github.com>
Date: Sat, 1 Sep 2018 08:57:49 +0200
Subject: [PATCH 15/16] Added DiskLoggerTests

---
 Logger.xcodeproj/project.pbxproj |   4 +
 UnitTests/DiskLoggerTests.swift  | 241 +++++++++++++++++++++++++++++++
 2 files changed, 245 insertions(+)
 create mode 100644 UnitTests/DiskLoggerTests.swift

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 = "<group>"; };
 		2ED103E02135C61100EB3683 /* FileRotateTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileRotateTests.swift; sourceTree = "<group>"; };
 		2ED103E22135D3FB00EB3683 /* FileWriterTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileWriterTests.swift; sourceTree = "<group>"; };
+		2ED103E42138553B00EB3683 /* DiskLoggerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiskLoggerTests.swift; sourceTree = "<group>"; };
 /* 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 <crit> DiskLoggerTests:46 testWritingMessageToFile() a message\n"
+        let loggedMessage = String(decoding: sizeLimitedFileFactory.files[0].data, as: UTF8.self)
+        let expectedSuffix = " <crit> 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 <crit> DiskLoggerTests:75 testExceedingFileSizeLimit() 2st message\n"
+        let loggedMessage = String(decoding: sizeLimitedFileFactory.files[1].data, as: UTF8.self)
+        let expectedSuffix = " <crit> 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 <crit> DiskLoggerTests:75 testExceedingFileSizeLimit() 2st message\n"
+        let loggedMessage = String(decoding: sizeLimitedFileFactory.files[0].data, as: UTF8.self)
+        let expectedOccurence = " <warning> 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
+    }
+}

From 677db8a73a8d7266564f74f517fb9b6380a58367 Mon Sep 17 00:00:00 2001
From: Wojciech Nagrodzki <278594+wnagrodzki@users.noreply.github.com>
Date: Sat, 1 Sep 2018 08:59:25 +0200
Subject: [PATCH 16/16] Fixed two bugs caught by unit tests in DiskLogger

---
 Logger/Loggers/DiskLogger/DiskLogger.swift | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/Logger/Loggers/DiskLogger/DiskLogger.swift b/Logger/Loggers/DiskLogger/DiskLogger.swift
index 6718d09..c2a44d0 100644
--- a/Logger/Loggers/DiskLogger/DiskLogger.swift
+++ b/Logger/Loggers/DiskLogger/DiskLogger.swift
@@ -130,10 +130,12 @@ public final class DiskLogger: Logger {
                 catch is SizeLimitedFileQuotaReached {
                     self.closeSizeLimitedFile()
                     try self.rotateLogFiles()
+                    try self.openSizeLimitedFile()
+                    try self.writeBuffer()
                 }
             }
             catch {
-                let message = self.formatter.string(from: Date()) + " <" + LogLevel.warning.logDescription + "> " + String(describing: error)
+                let message = self.formatter.string(from: Date()) + " <" + LogLevel.warning.logDescription + "> " + String(describing: error) + "\n"
                 let data = Data(message.utf8)
                 self.buffer.append(data)
             }