Skip to content

Commit

Permalink
feat: Add support for hit count breakpoints (#69)
Browse files Browse the repository at this point in the history
* Add support for hit count breakpoints

* Implement hit condition in new breakpoint subsystem.

* Added hit condition to function breakpoints.

* Changelog.

* Added test cases for hit condition (line) breakpoints.

* Added function breakpoints with hit condition coverage.

* Also cover invalid hit condition.
  • Loading branch information
felixfbecker authored Sep 15, 2021
1 parent 36fcdd0 commit 5b12f9e
Show file tree
Hide file tree
Showing 8 changed files with 219 additions and 27 deletions.
6 changes: 3 additions & 3 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
"NODE_ENV": "development"
},
"sourceMaps": true,
"outDir": "${workspaceRoot}/out"
"outFiles": ["${workspaceRoot}/out/**/*.js"]
},
{
"name": "Launch Extension",
Expand All @@ -21,7 +21,7 @@
"runtimeExecutable": "${execPath}",
"args": ["--extensionDevelopmentPath=${workspaceRoot}"],
"sourceMaps": true,
"outDir": "${workspaceRoot}/out"
"outFiles": ["${workspaceRoot}/out/**/*.js"]
},
{
"name": "Mocha",
Expand All @@ -31,7 +31,7 @@
"args": ["out/test", "--no-timeouts", "--colors"],
"cwd": "${workspaceRoot}",
"sourceMaps": true,
"outDir": "${workspaceRoot}/out"
"outFiles": ["${workspaceRoot}/out/**/*.js"]
}
]
}
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/).

## [1.18.0]

- Added hit count breakpoint condition.

## [1.17.0]

## Added
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ Options specific to CLI debugging:

- Line breakpoints
- Conditional breakpoints
- Hit count breakpoints: supports the conditions like `>=n`, `==n` and `%n`
- Function breakpoints
- Step over, step in, step out
- Break on entry
Expand Down
46 changes: 43 additions & 3 deletions src/breakpoints.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,14 +47,35 @@ export class BreakpointManager extends EventEmitter {

vscodeBreakpoints = breakpoints.map(sourceBreakpoint => {
let xdebugBreakpoint: xdebug.Breakpoint
let hitValue: number | undefined
let hitCondition: xdebug.HitCondition | undefined
if (sourceBreakpoint.hitCondition) {
const match = sourceBreakpoint.hitCondition.match(/^\s*(>=|==|%)?\s*(\d+)\s*$/)
if (match) {
hitCondition = (match[1] as xdebug.HitCondition) || '=='
hitValue = parseInt(match[2])
} else {
let vscodeBreakpoint: VSCodeDebugProtocol.Breakpoint = {
verified: false,
line: sourceBreakpoint.line,
source: source,
// id: this._nextId++,
message:
'Invalid hit condition. Specify a number, optionally prefixed with one of the operators >= (default), == or %',
}
return vscodeBreakpoint
}
}
if (sourceBreakpoint.condition) {
xdebugBreakpoint = new xdebug.ConditionalBreakpoint(
sourceBreakpoint.condition,
fileUri,
sourceBreakpoint.line
sourceBreakpoint.line,
hitCondition,
hitValue
)
} else {
xdebugBreakpoint = new xdebug.LineBreakpoint(fileUri, sourceBreakpoint.line)
xdebugBreakpoint = new xdebug.LineBreakpoint(fileUri, sourceBreakpoint.line, hitCondition, hitValue)
}

let vscodeBreakpoint: VSCodeDebugProtocol.Breakpoint = {
Expand Down Expand Up @@ -124,9 +145,28 @@ export class BreakpointManager extends EventEmitter {
this._callBreakpoints.clear()

vscodeBreakpoints = breakpoints.map(functionBreakpoint => {
let hitValue: number | undefined
let hitCondition: xdebug.HitCondition | undefined
if (functionBreakpoint.hitCondition) {
const match = functionBreakpoint.hitCondition.match(/^\s*(>=|==|%)?\s*(\d+)\s*$/)
if (match) {
hitCondition = (match[1] as xdebug.HitCondition) || '=='
hitValue = parseInt(match[2])
} else {
let vscodeBreakpoint: VSCodeDebugProtocol.Breakpoint = {
verified: false,
// id: this._nextId++,
message:
'Invalid hit condition. Specify a number, optionally prefixed with one of the operators >= (default), == or %',
}
return vscodeBreakpoint
}
}
let xdebugBreakpoint: xdebug.Breakpoint = new xdebug.CallBreakpoint(
functionBreakpoint.name,
functionBreakpoint.condition
functionBreakpoint.condition,
hitCondition,
hitValue
)

let vscodeBreakpoint: VSCodeDebugProtocol.Breakpoint = {
Expand Down
1 change: 1 addition & 0 deletions src/phpDebug.ts
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,7 @@ class PhpDebugSession extends vscode.DebugSession {
supportsConditionalBreakpoints: true,
supportsFunctionBreakpoints: true,
supportsLogPoints: true,
supportsHitConditionalBreakpoints: true,
exceptionBreakpointFilters: [
{
filter: 'Notice',
Expand Down
111 changes: 111 additions & 0 deletions src/test/adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -389,6 +389,117 @@ describe('PHP Debug Adapter', () => {
await assertStoppedLocation('breakpoint', program, 5)
})
})

describe('hit count breakpoints', () => {
const program = path.join(TEST_PROJECT, 'hit.php')

async function testHits(condition: string, hits: string[], verified: boolean = true): Promise<void> {
client.launch({ program })
await client.waitForEvent('initialized')
const breakpoint = (
await client.setBreakpointsRequest({
breakpoints: [{ line: 4, hitCondition: condition }],
source: { path: program },
})
).body.breakpoints[0]
await client.configurationDoneRequest()
if (verified) {
await waitForBreakpointUpdate(breakpoint)
} else {
assert.strictEqual(
breakpoint.message,
'Invalid hit condition. Specify a number, optionally prefixed with one of the operators >= (default), == or %'
)
}
assert.strictEqual(breakpoint.verified, verified)
for (const hitVal of hits) {
const { threadId, frame } = await assertStoppedLocation('breakpoint', program, 4)
const result = (
await client.evaluateRequest({
context: 'watch',
frameId: frame.id,
expression: '$i',
})
).body.result
assert.equal(result, hitVal)
await client.continueRequest({ threadId })
}
await client.waitForEvent('terminated')
}

async function testFunctionHits(
condition: string,
hits: string[],
verified: boolean = true
): Promise<void> {
client.launch({ program })
await client.waitForEvent('initialized')
const breakpoint = (
await client.setFunctionBreakpointsRequest({
breakpoints: [{ name: 'f1', hitCondition: condition }],
})
).body.breakpoints[0]
await client.configurationDoneRequest()
if (verified) {
await waitForBreakpointUpdate(breakpoint)
} else {
assert.strictEqual(
breakpoint.message,
'Invalid hit condition. Specify a number, optionally prefixed with one of the operators >= (default), == or %'
)
}
assert.strictEqual(breakpoint.verified, verified)
for (const hitVal of hits) {
const { threadId, frame } = await assertStoppedLocation('breakpoint', program, 9)
const result = (
await client.evaluateRequest({
context: 'watch',
frameId: frame.id,
expression: '$i',
})
).body.result
assert.equal(result, hitVal)
await client.continueRequest({ threadId })
}
await client.waitForEvent('terminated')
}

describe('hit count line breakpoints', () => {
it('should not stop for broken condition "a"', async () => {
await testHits('a', [], false)
})
it('should stop when the hit count is gte than 3 with condition "3"', async () => {
await testHits('3', ['3'])
})
it('should stop when the hit count is gte than 3 with condition ">=3"', async () => {
await testHits('>=3', ['3', '4', '5'])
})
it('should stop when the hit count is equal to 3 with condition "==3"', async () => {
await testHits('==3', ['3'])
})
it('should stop on every 2nd hit with condition "%2"', async () => {
await testHits('%2', ['2', '4'])
})
})

describe('hit count function breakpoints', () => {
it('should not stop for broken condition "a"', async () => {
await testFunctionHits('a', [], false)
})
it('should stop when the hit count is gte than 3 with condition "3"', async () => {
await testFunctionHits('3', ['3'])
})
it('should stop when the hit count is gte than 3 with condition ">=3"', async () => {
await testFunctionHits('>=3', ['3', '4', '5'])
})
it('should stop when the hit count is equal to 3 with condition "==3"', async () => {
await testFunctionHits('==3', ['3'])
})
it('should stop on every 2nd hit with condition "%2"', async () => {
await testFunctionHits('%2', ['2', '4'])
})
})
})
})

describe('variables', () => {
Expand Down
Loading

0 comments on commit 5b12f9e

Please sign in to comment.