Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor #12

Merged
merged 4 commits into from
Jan 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 3 additions & 12 deletions LeaksDetector/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,12 @@ A executable program to checks for leaks in a running program
Just need to run the script:

```bash
$ leaksdetector $PROGRAM_NAME $MAESTRO_PATH_FILE
$ leaksdetector maestro -p $PROGRAM_NAME -maestroFlowPath $MAESTRO_PATH_FILE -dangerFilePath $DANGER_FILE_PATH
```

## How to run local

1. Open `LeaksDetector.swift`, change `processName`, `executorType`, `maestroFlowPath`, `dangerPath` to a hardcoded value
1. Open `MaestroCommand.swift`, change `processName`, `maestroFlowPath`, `dangerPath` to a hardcoded value
2. Run:

```bash
Expand All @@ -31,18 +31,9 @@ Just need to run the script:
```bash
$ swift build -c release
$ cd .build/release
$ cp -f leaksdetector $LeaksDetectorPath
$ cp -f leaksdetector ./../../../
```

## Todo

1. Investigate how to integrate with XCUITest
2. Integrate with DocC

## Why Maestro?

- TBA

### References:

- https://www.fivestars.blog/articles/ultimate-guide-swift-executables/
Expand Down
70 changes: 70 additions & 0 deletions LeaksDetector/Sources/LeaksDetector/CommandSteps/CheckLeaks.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
//
// File.swift
//
//
// Created by Tuan Hoang on 6/1/24.
//

import Foundation
import ShellOut

enum LeaksCheckError: Error {
case leaksCommandFailed
case incorectOutputFormat
}

struct CheckLeaks: RunCommandStep {
let executor: Executor

private let regex: String = ".*(\\d+) leaks for (\\d+) total leaked bytes.*"

func run() throws {
do {
log(message: "Start checking for leaks... ⚙️")
let memgraphPath = executor.getMemgraphPath()

/// Running this script always throw error (somehow the leak tool throw error here) => So we need to process the memgraph in the `catch` block.
try shellOut(to: "leaks", arguments: ["\(memgraphPath) -q"])
} catch {
let error = error as! ShellOutError
if error.output.isEmpty {
log(message: "❌ Leaks command run failed! Please open an issue on Github for me to check!", color: .red)
throw LeaksCheckError.leaksCommandFailed
}

let inputs = error.output.components(separatedBy: "\n")
guard let numberOfLeaksMessage = inputs.first(where: { $0.matches(regex) }) else {
log(message: "❌ Generated leaks output is incorrect! Please open an issue on Github for me to check!", color: .red)
throw LeaksCheckError.incorectOutputFormat
}

let numberOfLeaks = getNumberOfLeaks(from: numberOfLeaksMessage)

if numberOfLeaks < 1 {
log(message: "Scan successfully. Don't find any leaks in the program! ✅", color: .green)
return
}

for message in inputs {
let updatedMessage = "\"\(message)\""
try shellOut(to: "echo \(updatedMessage) >> \(Constants.leaksReportFileName)")
}

log(message: "Founded leaks. Generating reports... ⚙️")
}
}

private func getNumberOfLeaks(from message: String) -> Int {
if let regex = try? NSRegularExpression(pattern: regex, options: []) {
// Find the first match in the input string
if let match = regex.firstMatch(in: message, options: [], range: NSRange(message.startIndex..., in: message)) {
// Extract the "d" value from the first capture group
if let dRange = Range(match.range(at: 1), in: message), let dValue = Int(message[dRange]) {
return dValue
}
}
}

return 0
}
}
19 changes: 19 additions & 0 deletions LeaksDetector/Sources/LeaksDetector/CommandSteps/CleanUp.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
//
// File.swift
//
//
// Created by Tuan Hoang on 6/1/24.
//

import Foundation
import ShellOut

struct CleanUp: RunCommandStep {
let executor: Executor

func run() throws {
log(message: "Cleaning... 🧹")
_ = try? shellOut(to: "rm \(executor.getMemgraphPath())")
_ = try? shellOut(to: "rm \(Constants.leaksReportFileName)")
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
//
// File.swift
//
//
// Created by Tuan Hoang on 6/1/24.
//

import Foundation

struct GenerateMemgraphFile: RunCommandStep {
let executor: Executor
let processName: String

func run() throws {
do {
try executor.generateMemgraph(for: processName)
log(message: "Generate memgraph successfully for process 🚀", color: .green)
} catch let error {
log(message: "❌ Can not find any process with name: \(processName)", color: .red)
throw error
}
}
}
22 changes: 22 additions & 0 deletions LeaksDetector/Sources/LeaksDetector/CommandSteps/Report.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
//
// File.swift
//
//
// Created by Tuan Hoang on 6/1/24.
//

import Foundation
import ShellOut

struct DangerReportStep: RunCommandStep {
let dangerFilePath: String

func run() throws {
do {
try shellOut(to: "bundle exec danger --dangerfile=\(dangerFilePath) --danger_id=LeaksReport")
} catch let error {
log(message: "❌ Can not execute Danger", color: .red)
throw error
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
//
// File.swift
//
//
// Created by Tuan Hoang on 6/1/24.
//

import Foundation

protocol RunCommandStep {
func run() throws
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
//
// File.swift
//
//
// Created by Tuan Hoang on 6/1/24.
//

import ShellOut
import Foundation

struct SimulateUIFlow: RunCommandStep {
let executor: Executor

func run() throws {
log(message: "Start running ui flow... 🎥")
do {
try executor.simulateUI()
} catch let error {
let error = error as! ShellOutError
log(message: "❌ Something went wrong when trying to capture ui flow. \(error.message)", color: .red)
throw error
}
}
}
44 changes: 44 additions & 0 deletions LeaksDetector/Sources/LeaksDetector/Commands/MaestroCommand.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
//
// File.swift
//
//
// Created by Tuan Hoang on 6/1/24.
//

import ArgumentParser
import Foundation

struct MaestroCommand: ParsableCommand {
static let configuration = CommandConfiguration(
commandName: "maestro",
abstract: "Perform memory leaks check by using Maestro testing tool."
)

#if DEBUG
private var processName = "MemoryLeaksCheck"
private var maestroFlowPath: String = "/Users/hoanganhtuan/Desktop/MemoryLeaksCheck/maestro/leaksCheckFlow.yaml"
private var dangerFilePath: String = "Dangerfile.leaksReport"
#else
@Option(name: .shortAndLong, help: "The name of the running process")
private var processName: String

@Option(name: .long, help: "The path to the maestro ui testing yaml file")
private var maestroFlowPath: String

@Option(name: .long, help: "The path to the Dangerfile")
private var dangerFilePath: String?
#endif

public func run() throws {
let executor = MaestroExecutor(flowPath: maestroFlowPath)
let processor = Processor(
configuration: .init(
processName: processName,
dangerFilePath: dangerFilePath
),
executor: executor
)

processor.start()
}
}
53 changes: 53 additions & 0 deletions LeaksDetector/Sources/LeaksDetector/Commands/Processor.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
//
// File.swift
//
//
// Created by Tuan Hoang on 6/1/24.
//

import Foundation

struct Configuration {
let processName: String
let dangerFilePath: String?
}

struct Processor {
let configuration: Configuration
let executor: Executor
let steps: [RunCommandStep]

init(configuration: Configuration, executor: Executor) {
self.configuration = configuration
self.executor = executor
var steps: [RunCommandStep] = [
SimulateUIFlow(executor: executor),
GenerateMemgraphFile(executor: executor, processName: configuration.processName),
CheckLeaks(executor: executor)
]

if let dangerFilePath = configuration.dangerFilePath {
steps.append(DangerReportStep(dangerFilePath: dangerFilePath))
}

steps.append(CleanUp(executor: executor))

self.steps = steps
}

private var processName: String {
configuration.processName
}

func start() {
do {
try steps.forEach { step in
try step.run()
}
log(message: "✅ The process finish successfully", color: .green)
} catch {
log(message: "❌ End the process with error", color: .red)
Darwin.exit(EXIT_FAILURE)
}
}
}
12 changes: 12 additions & 0 deletions LeaksDetector/Sources/LeaksDetector/Constants.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
//
// File.swift
//
//
// Created by Tuan Hoang on 6/1/24.
//

import Foundation

enum Constants {
static let leaksReportFileName = "leaksReport.txt"
}
39 changes: 0 additions & 39 deletions LeaksDetector/Sources/LeaksDetector/ExecutorFactory.swift

This file was deleted.

14 changes: 14 additions & 0 deletions LeaksDetector/Sources/LeaksDetector/Helpers/String+.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
//
// File.swift
//
//
// Created by Tuan Hoang on 6/1/24.
//

import Foundation

extension String {
func matches(_ regex: String) -> Bool {
return self.range(of: regex, options: .regularExpression, range: nil, locale: nil) != nil
}
}
Loading
Loading