Skip to content

Commit

Permalink
Merge pull request #121 from WeTransfer/feature/directory-tree-node
Browse files Browse the repository at this point in the history
Directory Tree Reporter
  • Loading branch information
AvdLee authored Jul 28, 2022
2 parents 9355dac + 5242352 commit 38c2abb
Show file tree
Hide file tree
Showing 26 changed files with 675 additions and 60 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
//
// DirectoryTreeFactoryTests.swift
//
//
// Created by Antoine van der Lee on 27/07/2022.
//

import XCTest
@testable import Diagnostics

final class DirectoryTreeFactoryTests: XCTestCase {

func testPrettyStringForDocumentsTree() throws {
let targetURL = try createTestDirectory()

let directoryTree = try! DirectoryTreeFactory(
path: targetURL.path,
maxDepth: 10,
maxLength: 10
).make()

XCTAssertEqual(directoryTree.directories.count, 7)
XCTAssertEqual(directoryTree.files.count, 14)
XCTAssertEqual(directoryTree.prettyString(printFullPath: false).trimmingCharacters(in: .whitespacesAndNewlines),
"""
└── treetest
+-- Library
| +-- Preferences
| | └── group.com.wetransfer.app.plist
| └── Caches
| └── com.apple.nsurlsessiond
| └── Downloads
| └── com.wetransfer
+-- Coyote.sqlite
└── thumbnails
+-- 856F92AC-BB2E-4CDB-AC07-D911F98D7586.png
+-- 7777DF69-03F4-4947-B545-3D5618FD6466.jpeg
+-- DCCE00C8-4134-47F9-B7C3-53AB40FBF467.png
+-- D6F1D540-502B-4574-A439-1746E08C2D26.png
+-- 0A907756-CDD9-40F4-8BFD-CF6BF7FA6F02.png
+-- 983B7B8F-7C65-467E-86B7-CC4813D7A348.png
+-- 0FCC8A9D-C91E-49C8-B88B-FDD44501C120.png
+-- 74134C19-32BD-43A5-8A73-236555022723.jpeg
+-- 1FBF5079-120E-4E8A-8768-526CC9DB2B7A.png
+-- BAD3B266-DA9B-4F28-8CD7-73880877450E.jpeg
└── 3 more file(s)
""")

}

func testPrettyStringMaxDepth() throws {
let targetURL = try createTestDirectory()

let directoryTree = try! DirectoryTreeFactory(
path: targetURL.path,
maxDepth: 1
).make()

XCTAssertEqual(directoryTree.directories.count, 1)
XCTAssertEqual(directoryTree.files.count, 0)
XCTAssertEqual(directoryTree.prettyString(printFullPath: false).trimmingCharacters(in: .whitespacesAndNewlines), "└── treetest")
}

func testPrettyStringIncludingHiddenFiles() throws {
let targetURL = try createTestDirectory()

let directoryTree = try! DirectoryTreeFactory(
path: targetURL.path,
maxDepth: 2,
includeHiddenFiles: true
).make()

XCTAssertEqual(directoryTree.directories.count, 3)
XCTAssertEqual(directoryTree.files.count, 2)
XCTAssertEqual(directoryTree.prettyString(printFullPath: false).trimmingCharacters(in: .whitespacesAndNewlines),
"""
└── treetest
+-- Library
+-- Coyote.sqlite
+-- .git
└── thumbnails
""")
}

func createTestDirectory() throws -> URL {
let documentsURL = FileManager.default
.urls(for: .documentDirectory, in: .userDomainMask).first!
let baseURL = documentsURL.appendingPathComponent("treetest")
try FileManager.default.createDirectory(at: baseURL, withIntermediateDirectories: true, attributes: nil)

addTeardownBlock {
try? FileManager.default.removeItem(at: baseURL)
}

let nodes: [DirectoryTreeNode] = [
.file("", "Coyote.sqlite"),
.file("", ".git"),
.directory("", "thumbnails", [
.file("", "856F92AC-BB2E-4CDB-AC07-D911F98D7586.png"),
.file("", "7777DF69-03F4-4947-B545-3D5618FD6466.jpeg"),
.file("", "DCCE00C8-4134-47F9-B7C3-53AB40FBF467.png"),
.file("", "D6F1D540-502B-4574-A439-1746E08C2D26.png"),
.file("", "0A907756-CDD9-40F4-8BFD-CF6BF7FA6F02.png"),
.file("", "983B7B8F-7C65-467E-86B7-CC4813D7A348.png"),
.file("", "0FCC8A9D-C91E-49C8-B88B-FDD44501C120.png"),
.file("", "74134C19-32BD-43A5-8A73-236555022723.jpeg"),
.file("", "1FBF5079-120E-4E8A-8768-526CC9DB2B7A.png"),
.file("", "BAD3B266-DA9B-4F28-8CD7-73880877450E.jpeg"),
.file("", "8783969E-5190-4041-8AD8-AF9E60ACFCD2.png"),
.file("", "643D317B-C9A4-4528-87F5-4EA57045D82B.jpeg"),
.file("", "73C3984D-A400-4C34-8451-1875323808B9.png")
]),
.directory("", "Library", [
.directory("", "Preferences", [
.file("", "group.com.wetransfer.app.plist")
]),
.directory("", "Caches", [
.directory("", "com.apple.nsurlsessiond", [
.directory("", "Downloads", [
.file("", "com.wetransfer")
])
])
])
])
]

for node in nodes {
try createNode(node, at: baseURL)
}

return baseURL
}

func createNode(_ node: DirectoryTreeNode, at baseURL: URL) throws {
switch node {
case .file(_, let name):
let newBaseURL = baseURL.appendingPathComponent(name)
FileManager.default.createFile(atPath: newBaseURL.path, contents: Data())
case .symbolLink:
fatalError("Symbol links are not supported for now")
case .directory(_, let name, let childNodes):
let newBaseURL = baseURL.appendingPathComponent(name)
try FileManager.default.createDirectory(at: newBaseURL, withIntermediateDirectories: true, attributes: nil)
try childNodes.forEach { childNode in
try createNode(childNode, at: newBaseURL)
}
}
}

}
17 changes: 11 additions & 6 deletions DiagnosticsTests/Reporters/LogsReporterTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,28 +23,33 @@ final class LogsReporterTests: XCTestCase {

/// It should show logged messages.
func testMessagesLog() throws {
let message = UUID().uuidString
let identifier = UUID().uuidString
let message = "<b>\(identifier)</b>"
DiagnosticsLogger.log(message: message)
let diagnostics = LogsReporter().report().diagnostics as! String
XCTAssertTrue(diagnostics.contains(message), "Diagnostics is \(diagnostics)")
XCTAssertTrue(diagnostics.contains(identifier), "Diagnostics is \(diagnostics)")
XCTAssertEqual(diagnostics.debugLogs.count, 1)
let debugLog = try XCTUnwrap(diagnostics.debugLogs.first)
XCTAssertTrue(debugLog.contains("<span class=\"log-prefix\">LogsReporterTests.swift:L27</span>"), "Prefix should be added")
XCTAssertTrue(debugLog.contains("<span class=\"log-message\">\(message)</span>"), "Log message should be added")
XCTAssertTrue(debugLog.contains("<span class=\"log-prefix\">LogsReporterTests.swift:L28</span>"), "Prefix should be added")
XCTAssertTrue(debugLog.contains("<span class=\"log-message\">&lt;b&gt;\(identifier)&lt;/b&gt;</span>"), "Log message should be added to \(debugLog)")
}

/// It should show errors.
func testErrorLog() throws {
enum Error: Swift.Error {
enum Error: LocalizedError {
case testCase

var errorDescription: String? {
return "<b>example description</b>"
}
}

DiagnosticsLogger.log(error: Error.testCase)
let diagnostics = LogsReporter().report().diagnostics as! String
XCTAssertTrue(diagnostics.contains("testCase"))
XCTAssertEqual(diagnostics.errorLogs.count, 1)
let errorLog = try XCTUnwrap(diagnostics.errorLogs.first)
XCTAssertTrue(errorLog.contains("<span class=\"log-message\">ERROR: testCase"))
XCTAssertTrue(errorLog.contains("<span class=\"log-message\">ERROR: testCase | &lt;b&gt;example description&lt;/b&gt"))
}

/// It should reverse the order of sessions to have the most recent session on top.
Expand Down
22 changes: 22 additions & 0 deletions Example/Diagnostics-Example.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@
isa = PBXNativeTarget;
buildConfigurationList = 500B278823953F8E00C304D4 /* Build configuration list for PBXNativeTarget "Diagnostics-Example" */;
buildPhases = (
84E8B49F289179FC00A84288 /* SwiftLint */,
500B277023953F8C00C304D4 /* Sources */,
500B277123953F8C00C304D4 /* Frameworks */,
500B277223953F8C00C304D4 /* Resources */,
Expand Down Expand Up @@ -180,6 +181,27 @@
};
/* End PBXResourcesBuildPhase section */

/* Begin PBXShellScriptBuildPhase section */
84E8B49F289179FC00A84288 /* SwiftLint */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
);
name = SwiftLint;
outputFileListPaths = (
);
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "if [ -z \"$CI\" ]; then\n if [ \"${CONFIGURATION}\" == \"Debug\" ]; then\n if test -d \"/opt/homebrew/bin/\"; then\n PATH=\"/opt/homebrew/bin/:${PATH}\"\n fi\n\n export PATH\n swiftlint --config \"${SRCROOT}/../Submodules/WeTransfer-iOS-CI/BuildTools/.swiftlint.yml\"\n swiftlint lint --path ../Sources --config \"${SRCROOT}/../Submodules/WeTransfer-iOS-CI/BuildTools/.swiftlint.yml\"\n else\n echo \"Info: As we're not building for Debug, no SwiftLint is running.\"\n fi\nfi\n";
};
/* End PBXShellScriptBuildPhase section */

/* Begin PBXSourcesBuildPhase section */
500B277023953F8C00C304D4 /* Sources */ = {
isa = PBXSourcesBuildPhase;
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"object": {
"pins": [
{
"package": "ExceptionCatcher",
"repositoryURL": "https://github.com/sindresorhus/ExceptionCatcher",
"state": {
"branch": null,
"revision": "9d99a736e17f67ab3aa333c0dad7ed2ee9d6b2b9",
"version": "2.0.0"
}
}
]
},
"version": 1
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
// Created by Antoine van der Lee on 02/12/2019.
// Copyright © 2019 WeTransfer. All rights reserved.
//

// swiftlint:disable line_length
import UIKit
import Diagnostics

Expand All @@ -32,7 +32,6 @@ final class AppDelegate: UIResponder, UIApplicationDelegate {
}

DiagnosticsLogger.log(message: "Application started")
DiagnosticsLogger.log(message: "Log <a href=\"https://www.wetransfer.com\">HTML</a>")
DiagnosticsLogger.log(error: ExampleError.missingData)
DiagnosticsLogger.log(error: ExampleLocalizedError.missingLocalizedData)
DiagnosticsLogger.log(message: "A very long string: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Quisque condimentum facilisis arcu, at fermentum diam fermentum in. Nullam lectus libero, tincidunt et risus vel, feugiat vulputate nunc. Nunc malesuada congue risus fringilla lacinia. Aliquam suscipit nulla nec faucibus mattis. Suspendisse quam nunc, interdum vel dapibus in, vulputate ac enim. Morbi placerat commodo leo, nec condimentum eros dictum sit amet. Vivamus maximus neque in dui rutrum, vel consectetur metus mollis. Nulla ultricies sodales viverra. Etiam ut velit consectetur, consectetur turpis eu, volutpat purus. Maecenas vitae consectetur tortor, at eleifend lacus. Nullam sed augue vel purus mollis sagittis at sed dui. Quisque faucibus fermentum lectus eget porttitor. Phasellus efficitur aliquet lobortis. Suspendisse at lectus imperdiet, sollicitudin arcu non, interdum diam. Sed ornare ante dolor. In pretium auctor sem, id vestibulum sem molestie in.")
Expand Down
33 changes: 1 addition & 32 deletions Example/Diagnostics-Example/Supporting Files/SceneDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,42 +8,11 @@

import UIKit

class SceneDelegate: UIResponder, UIWindowSceneDelegate {
final class SceneDelegate: UIResponder, UIWindowSceneDelegate {

var window: UIWindow?

func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
// Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`.
// If using a storyboard, the `window` property will automatically be initialized and attached to the scene.
// This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead).
guard (scene as? UIWindowScene) != nil else { return }
}

func sceneDidDisconnect(_ scene: UIScene) {
// Called as the scene is being released by the system.
// This occurs shortly after the scene enters the background, or when its session is discarded.
// Release any resources associated with this scene that can be re-created the next time the scene connects.
// The scene may re-connect later, as its session was not neccessarily discarded (see `application:didDiscardSceneSessions` instead).
}

func sceneDidBecomeActive(_ scene: UIScene) {
// Called when the scene has moved from an inactive state to an active state.
// Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive.
}

func sceneWillResignActive(_ scene: UIScene) {
// Called when the scene will move from an active state to an inactive state.
// This may occur due to temporary interruptions (ex. an incoming phone call).
}

func sceneWillEnterForeground(_ scene: UIScene) {
// Called as the scene transitions from the background to the foreground.
// Use this method to undo the changes made on entering the background.
}

func sceneDidEnterBackground(_ scene: UIScene) {
// Called as the scene transitions from the foreground to the background.
// Use this method to save data, release shared resources, and store enough scene-specific state information
// to restore the scene back to its current state.
}
}
18 changes: 17 additions & 1 deletion Example/Diagnostics-Example/ViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,23 @@ final class ViewController: UIViewController {
/// Create the report.
var reporters = DiagnosticsReporter.DefaultReporter.allReporters
reporters.insert(CustomReporter(), at: 1)
let report = DiagnosticsReporter.create(using: reporters, filters: [DiagnosticsDictionaryFilter.self, DiagnosticsStringFilter.self], smartInsightsProvider: SmartInsightsProvider())

let documentsURL = try! FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: false)
let directoryTreesReporter = DirectoryTreesReporter(
directories: [
documentsURL
]
)
reporters.insert(directoryTreesReporter, at: 2)

let report = DiagnosticsReporter.create(
using: reporters,
filters: [
DiagnosticsDictionaryFilter.self,
DiagnosticsStringFilter.self
],
smartInsightsProvider: SmartInsightsProvider()
)

guard MFMailComposeViewController.canSendMail() else {
/// For debugging purposes you can save the report to desktop when testing on the simulator.
Expand Down
47 changes: 47 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,53 @@ DiagnosticsLogger.log(error: ExampleError.missingData)

The error logger will make use of the localized description if available which you can add by making your error conform to `LocalizedError`.

### Adding a directory tree report
It's possible to add a directory tree report for a given set of URL, resulting in the following output:

```
└── Documents
+-- contents
| +-- B3F2F9AD-AB8D-4825-8369-181DEAAFF940.png
| +-- 5B9C090E-6CE1-4A2F-956B-15897AB4B0A1.png
| +-- 739416EF-8FF8-4502-9B36-CEB778385BBF.png
| +-- 27A3C96B-1813-4553-A6B7-436E6F3DBB20.png
| +-- 8F176CEE-B28F-49EB-8802-CC0438879FBE.png
| +-- 340C2371-A81A-4188-8E04-BC19E94F9DAE.png
| +-- E63AFEBC-B7E7-46D3-BC92-E34A53C0CE0A.png
| +-- 6B363F44-AB69-4A60-957E-710494381739.png
| +-- 9D31CA40-D152-45D9-BDCE-9BB09CCB825E.png
| +-- 304E2E41-9697-4F9A-9EE0-8D487ED60C45.jpeg
| └── 7 more file(s)
+-- diagnostics_log.txt
+-- Okapi.sqlite
+-- Library
| +-- Preferences
| | └── group.com.wetransfer.app.plist
| └── Caches
| └── com.apple.nsurlsessiond
| └── Downloads
| └── com.wetransfer
+-- Coyote.sqlite-shm
+-- Coyote.sqlite
+-- Coyote.sqlite-wal
+-- Okapi.sqlite-shm
+-- Okapi.sqlite-wal
└── 1 more file(s)
```

You can do this by adding the `DirectoryTreesReporter`:

```swift
var reporters = DiagnosticsReporter.DefaultReporter.allReporters
let documentsURL = try! FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: false)
let directoryTreesReporter = DirectoryTreesReporter(
directories: [
documentsURL
]
)
reporters.insert(directoryTreesReporter, at: 1)
```

### Adding your own custom report
To add your own report you need to make use of the `DiagnosticsReporting` protocol.

Expand Down
Loading

0 comments on commit 38c2abb

Please sign in to comment.