Rebase #177 - Shared breakpoints support (#693)

* Resolves #173 - Shared breakpoints support

* Added breakpoints full documentation

* Invalid breakpoint just throw JSONUtilities decoding error.

* Use enumeration types instead of String for extensionIDs

* Remove a necessary line

* Remove unnecessary custom Equatable implementation

* Update CHANGELOG.md

* Ignore empty breakpoints

* Update Docs/ProjectSpec.md

Fix a typo

Co-Authored-By: Yonas Kolb <yonaskolb@users.noreply.github.com>

* Change some properties that should be Int to Int

* Create 2 typealiases

* Use BreakpointType where it is missing

* Remove unused Location

* Change some names

* Add Breakpoint.Scope

* Add Breakpoint.StopOnStyle

* Change the type of the raw value to String

* Remove some properties that may cause confusing

* Require filePah and line when the type is .file

* Add tests about decoding breakpoints

* Add Breakpoint.Action.ConveyanceType

* Add default value for waitUntilDone

* Add Breakpoint.Action.SoundName

* Add tests about decoding breakpoint actions

* Fix some issues in ProjectSpec.md

* Improve ProjectSpec.md

* Add missing condition

* Add breakpoints to project.yml

* Use unwarp

* Remove the Breakpoint suffix

* Refactor BreakpointType

* Refactor Breakpoint.Action

* Remove unnecessary properties

* Adjust the line wrapping style for BreakpointGenerator

* Support column breakpoints

---------

Co-authored-by: Alex Rupérez <alejandro.ruperez@intelygenz.com>
Co-authored-by: Yonas Kolb <yonaskolb@users.noreply.github.com>
This commit is contained in:
Jierong Li 2023-02-28 20:00:39 +09:00 committed by GitHub
parent aa7fed0f5b
commit d1dd93aac4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 832 additions and 1 deletions

View File

@ -2,6 +2,10 @@
## Next Version
### Added
- Added support for shared breakpoints #177 @alexruperez @myihsan
## 2.34.0
### Changed

View File

@ -55,6 +55,7 @@ You can also use environment variables in your configuration file, by using `${S
- [ ] **include**: **[Include](#include)** - One or more paths to other specs
- [ ] **options**: **[Options](#options)** - Various options to override default behaviour
- [ ] **attributes**: **[String: Any]** - The PBXProject attributes. This is for advanced use. If no value is set for `LastUpgradeCheck`, it will be defaulted to ``{"LastUpgradeCheck": "XcodeVersion"}`` with `xcodeVersion` being set by [Options](#options)`.xcodeVersion`
- [ ] **breakpoints**: [Breakpoints](#breakpoints) - Add shared breakpoints to the generated project
- [ ] **configs**: **[Configs](#configs)** - Project build configurations. Defaults to `Debug` and `Release` configs
- [ ] **configFiles**: **[Config Files](#config-files)** - `.xcconfig` files per config
- [ ] **settings**: **[Settings](#settings)** - Project specific settings. Default base and config type settings will be applied first before any settings defined here
@ -181,6 +182,81 @@ Default settings for file extensions. See [Sources](#sources) for more documenta
- [ ] **resourceTags**: **[String]** - On Demand Resource Tags that will be applied to any resources. This also adds to the project attribute's knownAssetTags.
- [ ] **compilerFlags**: **[String]** - A list of compiler flags to add.
### Breakpoints
- [x] **type**: **String** - Breakpoint type
- `File`: file breakpoint
- `Exception`: exception breakpoint
- `SwiftError`: swift error breakpoint
- `OpenGLError`: OpenGL breakpoint
- `Symbolic`: symbolic breakpoint
- `IDEConstraintError`: IDE constraint breakpoint
- `IDETestFailure`: IDE test failure breakpoint
- [ ] **enabled**: **Bool** - Indicates whether it should be active. Default to `true`
- [ ] **ignoreCount**: **Int** - Indicates how many times it should be ignored before stopping, Default to `0`
- [ ] **continueAfterRunningActions**: **Bool** - Indicates if should automatically continue after evaluating actions, Default to `false`
- [ ] **path**: **String** - Breakpoint file path (only required by file breakpoints)
- [ ] **line**: **Int** - Breakpoint line (only required by file breakpoints)
- [ ] **symbol**: **String** - Breakpoint symbol (only used by symbolic breakpoints)
- [ ] **module**: **String** - Breakpoint module (only used by symbolic breakpoints)
- [ ] **scope**: **String** - Breakpoint scope (only used by exception breakpoints)
- `All`
- `Objective-C` (default)
- `C++`
- [ ] **stopOnStyle**: **String** - Indicates if should stop on style (only used by exception breakpoints)
-`throw` (default)
-`catch`
- [ ] **condition**: **String** - Breakpoint condition
- [ ] **actions**: **[[Breakpoint Action](#breakpoint-action)]** - breakpoint actions
```yaml
breakpoints:
- type: ExceptionBreakpoint
enabled: true
ignoreCount: 0
continueAfterRunningActions: false
```
#### Breakpoint Action
- [x] **type**: **String** - Breakpoint action type
- `DebuggerCommand`: execute debugger command
- `Log`: log message
- `ShellCommand`: execute shell command
- `GraphicsTrace`: capture GPU frame
- `AppleScript`: execute AppleScript
- `Sound`: play sound
- [ ] **command**: **String** - Debugger command (only used by debugger command breakpoint action)
- [ ] **message**: **String** - Log message (only used log message breakpoint action)
- [ ] **conveyanceType**: **String** - Conveyance type (only used by log message breakpoint action)
- `console`: log message to console (default)
- `speak`: speak message
- [ ] **path**: **String** - Shell command file path (only used by shell command breakpoint action)
- [ ] **arguments**: **String** - Shell command arguments (only used by shell command breakpoint action)
- [ ] **waitUntilDone**: **Bool** - Indicates whether it should wait until done (only used by shell command breakpoint action). Default to `false`
- [ ] **script**: **String** - AppleScript (only used by AppleScript breakpoint action)
- [ ] **sound**: **String** - Sound name (only used by sound breakpoint action)
- `Basso` (default)
- `Blow`
- `Bottle`
- `Frog`
- `Funk`
- `Glass`
- `Hero`
- `Morse`
- `Ping`
- `Pop`
- `Purr`
- `Sosumi`
- `Submarine`
- `Tink`
```yaml
actions:
- type: Sound
sound: Blow
```
### Configs
Each config maps to a build type of either `debug` or `release` which will then apply default build settings to the project. Any value other than `debug` or `release` (for example `none`), will mean no default build settings will be applied to the project.

View File

@ -0,0 +1,261 @@
import Foundation
import XcodeProj
import JSONUtilities
public typealias BreakpointActionExtensionID = XCBreakpointList.BreakpointProxy.BreakpointContent.BreakpointActionProxy.ActionExtensionID
public typealias BreakpointExtensionID = XCBreakpointList.BreakpointProxy.BreakpointExtensionID
public struct Breakpoint: Equatable {
public enum BreakpointType: Equatable {
public struct Exception: Equatable {
public enum Scope: String, Equatable {
case all = "0"
case objectiveC = "1"
case cpp = "2"
}
public enum StopOnStyle: String, Equatable {
case `throw` = "0"
case `catch` = "1"
}
public var scope: Scope
public var stopOnStyle: StopOnStyle
public init(scope: Breakpoint.BreakpointType.Exception.Scope = .objectiveC,
stopOnStyle: Breakpoint.BreakpointType.Exception.StopOnStyle = .throw) {
self.scope = scope
self.stopOnStyle = stopOnStyle
}
}
case file(path: String, line: Int, column: Int?)
case exception(Exception)
case swiftError
case openGLError
case symbolic(symbol: String?, module: String?)
case ideConstraintError
case ideTestFailure
}
public enum Action: Equatable {
public struct Log: Equatable {
public enum ConveyanceType: String, Equatable {
case console = "0"
case speak = "1"
}
public var message: String?
public var conveyanceType: ConveyanceType
public init(message: String? = nil, conveyanceType: Breakpoint.Action.Log.ConveyanceType = .console) {
self.message = message
self.conveyanceType = conveyanceType
}
}
public enum Sound: String, Equatable {
case basso = "Basso"
case blow = "Blow"
case bottle = "Bottle"
case frog = "Frog"
case funk = "Funk"
case glass = "Glass"
case hero = "Hero"
case morse = "Morse"
case ping = "Ping"
case pop = "Pop"
case purr = "Purr"
case sosumi = "Sosumi"
case submarine = "Submarine"
case tink = "Tink"
}
case debuggerCommand(String?)
case log(Log)
case shellCommand(path: String?, arguments: String?, waitUntilDone: Bool = false)
case graphicsTrace
case appleScript(String?)
case sound(Sound)
}
public var type: BreakpointType
public var enabled: Bool
public var ignoreCount: Int
public var continueAfterRunningActions: Bool
public var condition: String?
public var actions: [Breakpoint.Action]
public init(type: BreakpointType,
enabled: Bool = true,
ignoreCount: Int = 0,
continueAfterRunningActions: Bool = false,
filePath: String? = nil,
line: Int? = nil,
condition: String? = nil,
actions: [Breakpoint.Action] = []) {
self.type = type
self.enabled = enabled
self.ignoreCount = ignoreCount
self.continueAfterRunningActions = continueAfterRunningActions
self.condition = condition
self.actions = actions
}
}
extension Breakpoint.BreakpointType.Exception.Scope {
public init(string: String) throws {
let string = string.lowercased()
switch string {
case "all":
self = .all
case "objective-c":
self = .objectiveC
case "c++":
self = .cpp
default:
throw SpecParsingError.unknownBreakpointScope(string)
}
}
}
extension Breakpoint.BreakpointType.Exception.StopOnStyle {
public init(string: String) throws {
let string = string.lowercased()
switch string {
case "throw":
self = .throw
case "catch":
self = .catch
default:
throw SpecParsingError.unknownBreakpointStopOnStyle(string)
}
}
}
extension Breakpoint.Action.Log.ConveyanceType {
init(string: String) throws {
let string = string.lowercased()
switch string {
case "console":
self = .console
case "speak":
self = .speak
default:
throw SpecParsingError.unknownBreakpointActionConveyanceType(string)
}
}
}
extension Breakpoint.Action.Sound {
init(name: String) throws {
guard let sound = Self.init(rawValue: name) else {
throw SpecParsingError.unknownBreakpointActionSoundName(name)
}
self = sound
}
}
extension Breakpoint.Action: JSONObjectConvertible {
public init(jsonDictionary: JSONDictionary) throws {
let idString: String = try jsonDictionary.json(atKeyPath: "type")
let id = try BreakpointActionExtensionID(string: idString)
switch id {
case .debuggerCommand:
let command: String? = jsonDictionary.json(atKeyPath: "command")
self = .debuggerCommand(command)
case .log:
let message: String? = jsonDictionary.json(atKeyPath: "message")
let conveyanceType: Log.ConveyanceType
if jsonDictionary["conveyanceType"] != nil {
let conveyanceTypeString: String = try jsonDictionary.json(atKeyPath: "conveyanceType")
conveyanceType = try .init(string: conveyanceTypeString)
} else {
conveyanceType = .console
}
self = .log(.init(message: message, conveyanceType: conveyanceType))
case .shellCommand:
let path: String? = jsonDictionary.json(atKeyPath: "path")
let arguments: String? = jsonDictionary.json(atKeyPath: "arguments")
let waitUntilDone = jsonDictionary.json(atKeyPath: "waitUntilDone") ?? false
self = .shellCommand(path: path, arguments: arguments, waitUntilDone: waitUntilDone)
case .graphicsTrace:
self = .graphicsTrace
case .appleScript:
let script: String? = jsonDictionary.json(atKeyPath: "script")
self = .appleScript(script)
case .sound:
let sound: Sound
if jsonDictionary["sound"] != nil {
let name: String = try jsonDictionary.json(atKeyPath: "sound")
sound = try .init(name: name)
} else {
sound = .basso
}
self = .sound(sound)
case .openGLError:
throw SpecParsingError.unknownBreakpointActionType(idString)
}
}
}
extension Breakpoint: JSONObjectConvertible {
public init(jsonDictionary: JSONDictionary) throws {
let idString: String = try jsonDictionary.json(atKeyPath: "type")
let id = try BreakpointExtensionID(string: idString)
switch id {
case .file:
let path: String = try jsonDictionary.json(atKeyPath: "path")
let line: Int = try jsonDictionary.json(atKeyPath: "line")
let column: Int? = jsonDictionary.json(atKeyPath: "column")
type = .file(path: path, line: line, column: column)
case .exception:
let scope: BreakpointType.Exception.Scope
if jsonDictionary["scope"] != nil {
let scopeString: String = try jsonDictionary.json(atKeyPath: "scope")
scope = try .init(string: scopeString)
} else {
scope = .objectiveC
}
let stopOnStyle: BreakpointType.Exception.StopOnStyle
if jsonDictionary["stopOnStyle"] != nil {
let stopOnStyleString: String = try jsonDictionary.json(atKeyPath: "stopOnStyle")
stopOnStyle = try .init(string: stopOnStyleString)
} else {
stopOnStyle = .throw
}
type = .exception(.init(scope: scope, stopOnStyle: stopOnStyle))
case .swiftError:
type = .swiftError
case .openGLError:
type = .openGLError
case .symbolic:
let symbol: String? = jsonDictionary.json(atKeyPath: "symbol")
let module: String? = jsonDictionary.json(atKeyPath: "module")
type = .symbolic(symbol: symbol, module: module)
case .ideConstraintError:
type = .ideConstraintError
case .ideTestFailure:
type = .ideTestFailure
}
enabled = jsonDictionary.json(atKeyPath: "enabled") ?? true
ignoreCount = jsonDictionary.json(atKeyPath: "ignoreCount") ?? 0
continueAfterRunningActions = jsonDictionary.json(atKeyPath: "continueAfterRunningActions") ?? false
condition = jsonDictionary.json(atKeyPath: "condition")
if jsonDictionary["actions"] != nil {
actions = try jsonDictionary.json(atKeyPath: "actions", invalidItemBehaviour: .fail)
} else {
actions = []
}
}
}

View File

@ -25,6 +25,7 @@ public struct Project: BuildSettingsContainer {
public var settingGroups: [String: Settings]
public var configs: [Config]
public var schemes: [Scheme]
public var breakpoints: [Breakpoint]
public var options: SpecOptions
public var attributes: [String: Any]
public var fileGroups: [String]
@ -49,6 +50,7 @@ public struct Project: BuildSettingsContainer {
settings: Settings = .empty,
settingGroups: [String: Settings] = [:],
schemes: [Scheme] = [],
breakpoints: [Breakpoint] = [],
packages: [String: SwiftPackage] = [:],
options: SpecOptions = SpecOptions(),
fileGroups: [String] = [],
@ -66,6 +68,7 @@ public struct Project: BuildSettingsContainer {
self.settings = settings
self.settingGroups = settingGroups
self.schemes = schemes
self.breakpoints = breakpoints
self.packages = packages
self.options = options
self.fileGroups = fileGroups
@ -144,6 +147,7 @@ extension Project: Equatable {
lhs.settingGroups == rhs.settingGroups &&
lhs.configs == rhs.configs &&
lhs.schemes == rhs.schemes &&
lhs.breakpoints == rhs.breakpoints &&
lhs.fileGroups == rhs.fileGroups &&
lhs.configFiles == rhs.configFiles &&
lhs.options == rhs.options &&
@ -179,6 +183,11 @@ extension Project {
aggregateTargets = try jsonDictionary.json(atKeyPath: "aggregateTargets").sorted { $0.name < $1.name }
projectReferences = try jsonDictionary.json(atKeyPath: "projectReferences").sorted { $0.name < $1.name }
schemes = try jsonDictionary.json(atKeyPath: "schemes")
if jsonDictionary["breakpoints"] != nil {
breakpoints = try jsonDictionary.json(atKeyPath: "breakpoints", invalidItemBehaviour: .fail)
} else {
breakpoints = []
}
fileGroups = jsonDictionary.json(atKeyPath: "fileGroups") ?? []
configFiles = jsonDictionary.json(atKeyPath: "configFiles") ?? [:]
attributes = jsonDictionary.json(atKeyPath: "attributes") ?? [:]

View File

@ -8,6 +8,12 @@ public enum SpecParsingError: Error, CustomStringConvertible {
case invalidSourceBuildPhase(String)
case invalidTargetReference(String)
case invalidVersion(String)
case unknownBreakpointType(String)
case unknownBreakpointScope(String)
case unknownBreakpointStopOnStyle(String)
case unknownBreakpointActionType(String)
case unknownBreakpointActionConveyanceType(String)
case unknownBreakpointActionSoundName(String)
public var description: String {
switch self {
@ -25,6 +31,18 @@ public enum SpecParsingError: Error, CustomStringConvertible {
return "Invalid version: \(version)"
case let .unknownPackageRequirement(package):
return "Unknown package requirement: \(package)"
case let .unknownBreakpointType(type):
return "Unknown Breakpoint type: \(type)"
case let .unknownBreakpointScope(scope):
return "Unknown Breakpoint scope: \(scope)"
case let .unknownBreakpointStopOnStyle(stopOnStyle):
return "Unknown Breakpoint stopOnStyle: \(stopOnStyle)"
case let .unknownBreakpointActionType(type):
return "Unknown Breakpoint Action type: \(type)"
case let .unknownBreakpointActionConveyanceType(type):
return "Unknown Breakpoint Action conveyance type: \(type)"
case let .unknownBreakpointActionSoundName(name):
return "Unknown Breakpoint Action sound name: \(name)"
}
}
}

View File

@ -106,3 +106,30 @@ extension XCScheme.CommandLineArguments {
self.init(arguments: args)
}
}
extension BreakpointExtensionID {
init(string: String) throws {
if let id = BreakpointExtensionID(rawValue: "Xcode.Breakpoint.\(string)Breakpoint") {
self = id
} else if let id = BreakpointExtensionID(rawValue: string) {
self = id
} else {
throw SpecParsingError.unknownBreakpointType(string)
}
}
}
extension BreakpointActionExtensionID {
init(string: String) throws {
if let type = BreakpointActionExtensionID(rawValue: "Xcode.BreakpointAction.\(string)") {
self = type
} else if let type = BreakpointActionExtensionID(rawValue: string) {
self = type
} else {
throw SpecParsingError.unknownBreakpointActionType(string)
}
}
}

View File

@ -0,0 +1,124 @@
import Foundation
import ProjectSpec
import XcodeProj
public class BreakpointGenerator {
let project: Project
public init(project: Project) {
self.project = project
}
func generateBreakpointList() throws -> XCBreakpointList? {
let breakpoints = project.breakpoints
guard !breakpoints.isEmpty else {
return nil
}
return XCBreakpointList(type: "4", version: "2.0", breakpoints: try breakpoints.map({ try generateBreakpointProxy($0) }))
}
private func generateBreakpointProxy(_ breakpoint: Breakpoint) throws -> XCBreakpointList.BreakpointProxy {
let breakpointExtensionID: BreakpointExtensionID
var filePath: String?
var line: String?
var column: String?
var scope: String?
var stopOnStyle: String?
var symbol: String?
var module: String?
switch breakpoint.type {
case let .file(path, lineNumber, columnNumber):
breakpointExtensionID = .file
filePath = path
line = String(lineNumber)
column = columnNumber.map(String.init)
case let .exception(exception):
breakpointExtensionID = .exception
scope = exception.scope.rawValue
stopOnStyle = exception.stopOnStyle.rawValue
case .swiftError:
breakpointExtensionID = .swiftError
case .openGLError:
breakpointExtensionID = .openGLError
case let .symbolic(symbolName, moduleName):
breakpointExtensionID = .symbolic
symbol = symbolName
module = moduleName
case .ideConstraintError:
breakpointExtensionID = .ideConstraintError
case .ideTestFailure:
breakpointExtensionID = .ideTestFailure
}
let xcbreakpoint = XCBreakpointList.BreakpointProxy.BreakpointContent(
enabled: breakpoint.enabled,
ignoreCount: String(breakpoint.ignoreCount),
continueAfterRunningActions: breakpoint.continueAfterRunningActions,
filePath: filePath,
startingColumn: column,
endingColumn: column,
startingLine: line,
endingLine: line,
symbol: symbol,
module: module,
scope: scope,
stopOnStyle: stopOnStyle,
condition: breakpoint.condition,
actions: try breakpoint.actions.map { try generateBreakpointActionProxy($0) }
)
return XCBreakpointList.BreakpointProxy(
breakpointExtensionID: breakpointExtensionID,
breakpointContent: xcbreakpoint
)
}
private func generateBreakpointActionProxy(_ breakpointAction: Breakpoint.Action) throws -> XCBreakpointList.BreakpointProxy.BreakpointContent.BreakpointActionProxy {
let actionExtensionID: BreakpointActionExtensionID
var consoleCommand: String?
var message: String?
var conveyanceType: String?
var command: String?
var arguments: String?
var waitUntilDone: Bool?
var script: String?
var soundName: String?
switch breakpointAction {
case let .debuggerCommand(command):
actionExtensionID = .debuggerCommand
consoleCommand = command
case let .log(log):
actionExtensionID = .log
message = log.message
conveyanceType = log.conveyanceType.rawValue
case let .shellCommand(commandPath, commandArguments, waitUntilCommandDone):
actionExtensionID = .shellCommand
command = commandPath
arguments = commandArguments
waitUntilDone = waitUntilCommandDone
case .graphicsTrace:
actionExtensionID = .graphicsTrace
case let .appleScript(appleScript):
actionExtensionID = .appleScript
script = appleScript
case let .sound(sound):
actionExtensionID = .sound
soundName = sound.rawValue
}
let xcaction = XCBreakpointList.BreakpointProxy.BreakpointContent.BreakpointActionProxy.ActionContent(
consoleCommand: consoleCommand,
message: message,
conveyanceType: conveyanceType,
command: command,
arguments: arguments,
waitUntilDone: waitUntilDone,
script: script,
soundName: soundName
)
return XCBreakpointList.BreakpointProxy.BreakpointContent.BreakpointActionProxy(
actionExtensionID: actionExtensionID,
actionContent: xcaction
)
}
}

View File

@ -27,8 +27,12 @@ public class ProjectGenerator {
let schemeGenerator = SchemeGenerator(project: project, pbxProj: pbxProj)
let (sharedSchemes, userSchemes, schemeManagement) = try schemeGenerator.generateSchemes()
// generate Breakpoints
let breakpointGenerator = BreakpointGenerator(project: project)
let xcbreakpointlist = try breakpointGenerator.generateBreakpointList()
// generate shared data
let sharedData = XCSharedData(schemes: sharedSchemes)
let sharedData = XCSharedData(schemes: sharedSchemes, breakpoints: xcbreakpointlist)
// generate user data
let userData = userSchemes.isEmpty && schemeManagement == nil ? [] : [

View File

@ -0,0 +1,154 @@
<?xml version="1.0" encoding="UTF-8"?>
<Bucket
type = "4"
version = "2.0">
<Breakpoints>
<BreakpointProxy
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
<BreakpointContent
shouldBeEnabled = "Yes"
ignoreCount = "0"
continueAfterRunningActions = "No"
condition = "launchOptions == nil"
endingLineNumber = "7"
filePath = "App_iOS/AppDelegate.swift"
startingLineNumber = "7">
<Actions>
<BreakpointActionProxy
ActionExtensionID = "Xcode.BreakpointAction.Log">
<ActionContent
message = "message"
conveyanceType = "1">
</ActionContent>
</BreakpointActionProxy>
</Actions>
<Locations>
</Locations>
</BreakpointContent>
</BreakpointProxy>
<BreakpointProxy
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
<BreakpointContent
shouldBeEnabled = "Yes"
ignoreCount = "0"
continueAfterRunningActions = "No"
endingColumnNumber = "13"
endingLineNumber = "11"
filePath = "App_iOS/AppDelegate.swift"
startingColumnNumber = "13"
startingLineNumber = "11">
<Actions>
</Actions>
<Locations>
</Locations>
</BreakpointContent>
</BreakpointProxy>
<BreakpointProxy
BreakpointExtensionID = "Xcode.Breakpoint.ExceptionBreakpoint">
<BreakpointContent
shouldBeEnabled = "Yes"
ignoreCount = "0"
continueAfterRunningActions = "No"
scope = "0"
stopOnStyle = "0">
<Actions>
<BreakpointActionProxy
ActionExtensionID = "Xcode.BreakpointAction.DebuggerCommand">
<ActionContent
consoleCommand = "po $arg1">
</ActionContent>
</BreakpointActionProxy>
<BreakpointActionProxy
ActionExtensionID = "Xcode.BreakpointAction.AppleScript">
<ActionContent
script = "display alert &quot;Exception happened!&quot;">
</ActionContent>
</BreakpointActionProxy>
<BreakpointActionProxy
ActionExtensionID = "Xcode.BreakpointAction.Sound">
<ActionContent
soundName = "Blow">
</ActionContent>
</BreakpointActionProxy>
</Actions>
<Locations>
</Locations>
</BreakpointContent>
</BreakpointProxy>
<BreakpointProxy
BreakpointExtensionID = "Xcode.Breakpoint.SwiftErrorBreakpoint">
<BreakpointContent
shouldBeEnabled = "No"
ignoreCount = "0"
continueAfterRunningActions = "No">
<Actions>
</Actions>
<Locations>
</Locations>
</BreakpointContent>
</BreakpointProxy>
<BreakpointProxy
BreakpointExtensionID = "Xcode.Breakpoint.OpenGLErrorBreakpoint">
<BreakpointContent
shouldBeEnabled = "Yes"
ignoreCount = "2"
continueAfterRunningActions = "No">
<Actions>
<BreakpointActionProxy
ActionExtensionID = "Xcode.BreakpointAction.ShellCommand">
<ActionContent
arguments = "argument1, argument2"
command = "script.sh"
waitUntilDone = "YES">
</ActionContent>
</BreakpointActionProxy>
</Actions>
<Locations>
</Locations>
</BreakpointContent>
</BreakpointProxy>
<BreakpointProxy
BreakpointExtensionID = "Xcode.Breakpoint.SymbolicBreakpoint">
<BreakpointContent
shouldBeEnabled = "Yes"
ignoreCount = "0"
continueAfterRunningActions = "No"
symbolName = "UIViewAlertForUnsatisfiableConstraints"
moduleName = "UIKitCore">
<Actions>
<BreakpointActionProxy
ActionExtensionID = "Xcode.BreakpointAction.GraphicsTrace">
<ActionContent>
</ActionContent>
</BreakpointActionProxy>
</Actions>
<Locations>
</Locations>
</BreakpointContent>
</BreakpointProxy>
<BreakpointProxy
BreakpointExtensionID = "Xcode.Breakpoint.IDEConstraintErrorBreakpoint">
<BreakpointContent
shouldBeEnabled = "Yes"
ignoreCount = "0"
continueAfterRunningActions = "Yes">
<Actions>
</Actions>
<Locations>
</Locations>
</BreakpointContent>
</BreakpointProxy>
<BreakpointProxy
BreakpointExtensionID = "Xcode.Breakpoint.IDETestFailureBreakpoint">
<BreakpointContent
shouldBeEnabled = "Yes"
ignoreCount = "0"
continueAfterRunningActions = "No">
<Actions>
</Actions>
<Locations>
</Locations>
</BreakpointContent>
</BreakpointProxy>
</Breakpoints>
</Bucket>

View File

@ -31,6 +31,46 @@ packages:
Swinject:
url: https://github.com/Swinject/Swinject
version: 2.8.0
breakpoints:
- type: File
path: App_iOS/AppDelegate.swift
line: 7
condition: launchOptions == nil
actions:
- type: Log
message: message
conveyanceType: speak
- type: File
path: App_iOS/AppDelegate.swift
line: 11
column: 13
- type: Exception
scope: All
stopOnStype: Catch
actions:
- type: DebuggerCommand
command: po $arg1
- type: AppleScript
script: display alert "Exception happened!"
- type: Sound
sound: Blow
- type: SwiftError
enabled: false
- type: OpenGLError
ignoreCount: 2
actions:
- type: ShellCommand
path: script.sh
arguments: argument1, argument2
waitUntilDone: true
- type: Symbolic
symbol: UIViewAlertForUnsatisfiableConstraints
module: UIKitCore
actions:
- type: GraphicsTrace
- type: IDEConstraintError
continueAfterRunningActions: true
- type: IDETestFailure
targets:
Legacy:
type: ""

View File

@ -379,6 +379,7 @@ class SpecLoadingTests: XCTestCase {
func testProjectSpecParser() {
let validTarget: [String: Any] = ["type": "application", "platform": "iOS"]
let validBreakpoint: [String: Any] = ["type": "Exception", "scope": "All", "stopOnStyle": "Catch"]
let invalid = "invalid"
describe {
@ -401,6 +402,93 @@ class SpecLoadingTests: XCTestCase {
try expectTargetError(target, .invalidDependency([invalid: "name"]))
}
$0.it("fails with incorrect breakpoint type") {
var breakpoint = validBreakpoint
breakpoint["type"] = invalid
try expectBreakpointError(breakpoint, .unknownBreakpointType(invalid))
}
$0.it("fails with incorrect breakpoint scope") {
var target = validBreakpoint
target["scope"] = invalid
try expectBreakpointError(target, .unknownBreakpointScope(invalid))
}
$0.it("fails with incorrect breakpoint stop on style") {
var target = validBreakpoint
target["stopOnStyle"] = invalid
try expectBreakpointError(target, .unknownBreakpointStopOnStyle(invalid))
}
$0.it("fails with incorrect breakpoint action type") {
var breakpoint = validBreakpoint
breakpoint["actions"] = [["type": invalid]]
try expectBreakpointError(breakpoint, .unknownBreakpointActionType(invalid))
}
$0.it("fails with incorrect breakpoint action conveyance type") {
var breakpoint = validBreakpoint
breakpoint["actions"] = [["type": "Log", "conveyanceType": invalid]]
try expectBreakpointError(breakpoint, .unknownBreakpointActionConveyanceType(invalid))
}
$0.it("fails with incorrect breakpoint action sound name") {
var breakpoint = validBreakpoint
breakpoint["actions"] = [["type": "Sound", "sound": invalid]]
try expectBreakpointError(breakpoint, .unknownBreakpointActionSoundName(invalid))
}
$0.it("parses breakpoints") {
let breakpointDictionaries = [
["type": "File", "path": "Foo.swift", "line": 7, "column": 14, "condition": "bar == nil"],
["type": "Exception", "scope": "All", "stopOnStyle": "Catch"],
["type": "SwiftError", "enabled": false],
["type": "OpenGLError", "ignoreCount": 2],
["type": "Symbolic", "symbol": "UIViewAlertForUnsatisfiableConstraints", "module": "UIKitCore"],
["type": "IDEConstraintError", "continueAfterRunningActions": true],
["type": "IDETestFailure"],
]
let project = try getProjectSpec(["breakpoints": breakpointDictionaries])
let expectedBreakpoints = [
Breakpoint(type: .file(path: "Foo.swift", line: 7, column: 14), condition: "bar == nil"),
Breakpoint(type: .exception(.init(scope: .all, stopOnStyle: .catch))),
Breakpoint(type: .swiftError, enabled: false),
Breakpoint(type: .openGLError, ignoreCount: 2),
Breakpoint(type: .symbolic(symbol: "UIViewAlertForUnsatisfiableConstraints", module: "UIKitCore")),
Breakpoint(type: .ideConstraintError, continueAfterRunningActions: true),
Breakpoint(type: .ideTestFailure),
]
try expect(project.breakpoints) == expectedBreakpoints
}
$0.it("parses breakpoint actions") {
var breakpointDicationary = validBreakpoint
breakpointDicationary["actions"] = [
["type": "DebuggerCommand", "command": "po $arg1"],
["type": "Log", "message": "message", "conveyanceType": "speak"],
["type": "ShellCommand", "path": "script.sh", "arguments": "argument1, argument2", "waitUntilDone": true],
["type": "GraphicsTrace"],
["type": "AppleScript", "script": #"display alert "Hello!""#],
["type": "Sound", "sound": "Hero"],
]
let breakpoint = try Breakpoint(jsonDictionary: breakpointDicationary)
let expectedActions: [Breakpoint.Action] = [
.debuggerCommand("po $arg1"),
.log(.init(message: "message", conveyanceType: .speak)),
.shellCommand(path: "script.sh", arguments: "argument1, argument2", waitUntilDone: true),
.graphicsTrace,
.appleScript(#"display alert "Hello!""#),
.sound(.hero),
]
try expect(breakpoint.actions) == expectedActions
}
$0.it("parses sources") {
var targetDictionary1 = validTarget
targetDictionary1["sources"] = [
@ -1482,3 +1570,9 @@ private func expectTargetError(_ target: [String: Any], _ expectedError: SpecPar
_ = try Target(name: "test", jsonDictionary: target)
}
}
private func expectBreakpointError(_ breakpoint: [String: Any], _ expectedError: SpecParsingError, file: String = #file, line: Int = #line) throws {
try expectError(expectedError, file: file, line: line) {
_ = try Breakpoint(jsonDictionary: breakpoint)
}
}

View File

@ -0,0 +1,20 @@
import ProjectSpec
import Spectre
import TestSupport
import XCTest
class BreakpointGeneratorTests: XCTestCase {
func testBreakpoints() {
describe {
$0.it("generates breakpoint") {
let breakpoint = Breakpoint(type: .exception(.init()))
let project = Project(basePath: "", name: "test", targets: [], breakpoints: [breakpoint])
let xcodeProject = try project.generateXcodeProject()
let xcbreakpoint = try unwrap(xcodeProject.sharedData?.breakpoints?.breakpoints.first)
try expect(xcbreakpoint.breakpointExtensionID.rawValue) == "Xcode.Breakpoint.ExceptionBreakpoint"
}
}
}
}