From 4e934655730a131e528a3dc97e302a53dc00ff0e Mon Sep 17 00:00:00 2001 From: Aleksey Berezka Date: Mon, 20 May 2024 12:56:47 +0500 Subject: [PATCH 1/2] Improved classes search speed --- Sources/DBXCResultParser-Sonar/FSIndex.swift | 68 +++++++++++++++++++ ...rGenericTestExecutionReportFormatter.swift | 18 ++--- .../DBXCResultParser-Sonar/URL+Helpers.swift | 38 +++++++++++ .../UnwrapOrThrow.swift | 11 +++ ...ricTestExecutionReportFormatterTests.swift | 18 ++--- 5 files changed, 132 insertions(+), 21 deletions(-) create mode 100644 Sources/DBXCResultParser-Sonar/FSIndex.swift create mode 100644 Sources/DBXCResultParser-Sonar/URL+Helpers.swift create mode 100644 Sources/DBXCResultParser-Sonar/UnwrapOrThrow.swift diff --git a/Sources/DBXCResultParser-Sonar/FSIndex.swift b/Sources/DBXCResultParser-Sonar/FSIndex.swift new file mode 100644 index 0000000..7036b56 --- /dev/null +++ b/Sources/DBXCResultParser-Sonar/FSIndex.swift @@ -0,0 +1,68 @@ +// +// File.swift +// +// +// Created by Aleksey Berezka on 20.05.2024. +// + +import Foundation + +struct FSIndex { + let classes: [String: String] + + init(path: URL) throws { + self.classes = try Self.classes(in: path) + } +} + +extension FSIndex { + private static func classes(in path: URL) throws -> [String: String] { + let fileManager = FileManager.default + + var classDictionary: [String: String] = [:] + + // Create a DirectoryEnumerator to recursively search for .swift files + let enumerator = fileManager.enumerator( + at: URL(fileURLWithPath: path.relativePath), + includingPropertiesForKeys: [.isRegularFileKey], + options: [.skipsHiddenFiles] + ) { (url, error) -> Bool in + DBLogger.logWarning("Directory enumeration error at \(url)") + DBLogger.logWarning(error.localizedDescription) + return true + } + + // Regular expression to find class names + let regex = try NSRegularExpression(pattern: "class\\s+([A-Za-z_][A-Za-z_0-9]*)", options: []) + + // Iterate over each file found by the enumerator + while let element = enumerator?.nextObject() as? URL { + let isFile = try element.resourceValues(forKeys: [.isRegularFileKey]).isRegularFile ?? false + guard isFile, + element.pathExtension == "swift" else { + continue + } + + let fileContent = try String(contentsOf: element, encoding: .utf8) + + // Search for class definitions + let nsRange = NSRange(fileContent.startIndex.. String { let testsPath = URL(fileURLWithPath: testsPath) + let fsIndex = try FSIndex(path: testsPath) let sonarFiles = try report .modules .flatMap { $0.files } .sorted { $0.name < $1.name } - .concurrentMap { try testExecutions.file($0, testsPath: testsPath) } + .concurrentMap { try testExecutions.file($0, index: fsIndex) } let dto = testExecutions(file: sonarFiles) @@ -151,14 +152,14 @@ extension testExecutions.file.testCase { } extension testExecutions.file { - init(_ file: DBXCReportModel.Module.File, testsPath: URL) throws { + init(_ file: DBXCReportModel.Module.File, index: FSIndex) throws { DBLogger.logDebug("Formatting \(file.name)") let testCases = file.repeatableTests .sorted { $0.name < $1.name } .map { testExecutions.file.testCase.init($0) } - let path = try Self.path(toFileWithClass: file.name, in: testsPath) + let path = try index.classes[file.name] ?! Error.missingFile(file.name) self.init( path: path, @@ -166,15 +167,8 @@ extension testExecutions.file { ) } - private static func path(toFileWithClass className: String, in path: URL) throws -> String { - let testsPath = path.relativePath - let command = "find \(testsPath) -name '*.swift' -exec grep -l 'class \(className)' {} + | head -n 1" - let absoluteFilePath = try DBShell.execute(command) - if absoluteFilePath.isEmpty { - DBLogger.logWarning("Can't find file for class \(className)") - } - let relativeFilePath = absoluteFilePath.replacingOccurrences(of: testsPath, with: ".") - return relativeFilePath + enum Error: Swift.Error { + case missingFile(String) } } diff --git a/Sources/DBXCResultParser-Sonar/URL+Helpers.swift b/Sources/DBXCResultParser-Sonar/URL+Helpers.swift new file mode 100644 index 0000000..e0483aa --- /dev/null +++ b/Sources/DBXCResultParser-Sonar/URL+Helpers.swift @@ -0,0 +1,38 @@ +import Foundation + +extension URL { + var isRegularFile: Bool { + get throws { + try resourceValues(forKeys: [.isRegularFileKey]).isRegularFile ?! Error.noResourceValues + } + } + + enum Error: Swift.Error { + case noResourceValues + } +} + +extension URL { + /// Returns a relative path from a base URL + /// - Parameter baseURL: The base URL to calculate the relative path from. + /// - Returns: A relative path if possible, otherwise nil. + func relativePath(from baseURL: URL) -> String? { + // Check if both URLs are file URLs and that the base URL is a directory + guard self.isFileURL, baseURL.isFileURL, baseURL.hasDirectoryPath else { + return nil + } + + // Remove/replace "." and "..", make sure URLs are absolute: + let pathComponents = standardized.pathComponents + let basePathComponents = baseURL.standardized.pathComponents + + // Find the number of common path components + let commonPart = zip(pathComponents, basePathComponents).prefix { $0 == $1 }.count + + // Build the relative path + let relativeComponents = Array(repeating: "..", count: basePathComponents.count - commonPart) + + pathComponents.dropFirst(commonPart) + + return relativeComponents.joined(separator: "/") + } +} diff --git a/Sources/DBXCResultParser-Sonar/UnwrapOrThrow.swift b/Sources/DBXCResultParser-Sonar/UnwrapOrThrow.swift new file mode 100644 index 0000000..7932e70 --- /dev/null +++ b/Sources/DBXCResultParser-Sonar/UnwrapOrThrow.swift @@ -0,0 +1,11 @@ +import Foundation + +infix operator ?!: NilCoalescingPrecedence + +/// Throws the right hand side error if the left hand side optional is `nil`. +func ?!(value: T?, error: @autoclosure () -> Error) throws -> T { + guard let value = value else { + throw error() + } + return value +} diff --git a/Tests/DBXCResultParser-SonarTests/SonarGenericTestExecutionReportFormatterTests.swift b/Tests/DBXCResultParser-SonarTests/SonarGenericTestExecutionReportFormatterTests.swift index bbb0340..0fa4ed5 100644 --- a/Tests/DBXCResultParser-SonarTests/SonarGenericTestExecutionReportFormatterTests.swift +++ b/Tests/DBXCResultParser-SonarTests/SonarGenericTestExecutionReportFormatterTests.swift @@ -31,7 +31,7 @@ class SonarGenericTestExecutionReportFormatterTests: XCTestCase { let result = try formatter.sonarTestReport(from: report) XCTAssertEqual(result, """ - + @@ -42,14 +42,14 @@ class SonarGenericTestExecutionReportFormatterTests: XCTestCase { - - - - - - - - + + + + + + + + """ ) From 611d426647de19762a8c4d4c1a5131384a18cd3c Mon Sep 17 00:00:00 2001 From: Aleksey Berezka Date: Mon, 20 May 2024 14:16:47 +0500 Subject: [PATCH 2/2] Removed comments --- Sources/DBXCResultParser-Sonar/FSIndex.swift | 7 ------- Sources/DBXCResultParser-Sonar/Logger.swift | 7 ------- .../SonarGenericTestExecutionReportFormatter.swift | 8 +------- 3 files changed, 1 insertion(+), 21 deletions(-) diff --git a/Sources/DBXCResultParser-Sonar/FSIndex.swift b/Sources/DBXCResultParser-Sonar/FSIndex.swift index 7036b56..0bc389d 100644 --- a/Sources/DBXCResultParser-Sonar/FSIndex.swift +++ b/Sources/DBXCResultParser-Sonar/FSIndex.swift @@ -1,10 +1,3 @@ -// -// File.swift -// -// -// Created by Aleksey Berezka on 20.05.2024. -// - import Foundation struct FSIndex { diff --git a/Sources/DBXCResultParser-Sonar/Logger.swift b/Sources/DBXCResultParser-Sonar/Logger.swift index b27682b..b56c62d 100644 --- a/Sources/DBXCResultParser-Sonar/Logger.swift +++ b/Sources/DBXCResultParser-Sonar/Logger.swift @@ -1,10 +1,3 @@ -// -// Logger.swift -// -// -// Created by Aleksey Berezka on 19.12.2023. -// - import Foundation class DBLogger { diff --git a/Sources/DBXCResultParser-Sonar/SonarGenericTestExecutionReportFormatter.swift b/Sources/DBXCResultParser-Sonar/SonarGenericTestExecutionReportFormatter.swift index 35c78ed..b2bac08 100644 --- a/Sources/DBXCResultParser-Sonar/SonarGenericTestExecutionReportFormatter.swift +++ b/Sources/DBXCResultParser-Sonar/SonarGenericTestExecutionReportFormatter.swift @@ -1,10 +1,3 @@ -// -// SonarGenericTestExecutionReportFormatter.swift -// -// -// Created by Aleksey Berezka on 15.12.2023. -// - import Foundation import DBXCResultParser import XMLCoder @@ -46,6 +39,7 @@ public class SonarGenericTestExecutionReportFormatter: ParsableCommand { public func sonarTestReport(from report: DBXCReportModel) throws -> String { let testsPath = URL(fileURLWithPath: testsPath) let fsIndex = try FSIndex(path: testsPath) + DBLogger.logDebug("Test classes: \(fsIndex.classes)") let sonarFiles = try report .modules