src/debugAdapter: indicate that conditional breakpoints are supported

Set supportsConditionalBreakpoints to be true on initialization to let
clients of the debug adapter know that we provide this capability.

Test that the capability is true and run a simple test for stopping on
conditional breakpoints.

Updates golang/vscode-go#781

Change-Id: I7b1dd5156fd346bb293582914fd809fabed368d1
Reviewed-on: https://go-review.googlesource.com/c/vscode-go/+/262346
Trust: Suzy Mueller <suzmue@golang.org>
Run-TryBot: Suzy Mueller <suzmue@golang.org>
TryBot-Result: kokoro <noreply+kokoro@google.com>
Reviewed-by: Polina Sokolova <polina@google.com>
diff --git a/src/debugAdapter/goDebug.ts b/src/debugAdapter/goDebug.ts
index e6f952c..8dee3d8 100644
--- a/src/debugAdapter/goDebug.ts
+++ b/src/debugAdapter/goDebug.ts
@@ -825,7 +825,8 @@
 		args: DebugProtocol.InitializeRequestArguments
 	): void {
 		log('InitializeRequest');
-		// This debug adapter implements the configurationDoneRequest.
+		// Set the capabilities that this debug adapter supports.
+		response.body.supportsConditionalBreakpoints = true;
 		response.body.supportsConfigurationDoneRequest = true;
 		response.body.supportsSetVariable = true;
 		this.sendResponse(response);
diff --git a/test/fixtures/condbp/condbp.go b/test/fixtures/condbp/condbp.go
new file mode 100644
index 0000000..041170a
--- /dev/null
+++ b/test/fixtures/condbp/condbp.go
@@ -0,0 +1,9 @@
+package main
+
+import "fmt"
+
+func main() {
+	for i := 0; i < 4; i++ {
+		fmt.Printf("i = %d\n", i)
+	}
+}
diff --git a/test/fixtures/condbp/go.mod b/test/fixtures/condbp/go.mod
new file mode 100644
index 0000000..e127f2b
--- /dev/null
+++ b/test/fixtures/condbp/go.mod
@@ -0,0 +1,3 @@
+module github.com/microsoft/vscode-go/gofixtures/condbp
+
+go 1.14
diff --git a/test/integration/goDebug.test.ts b/test/integration/goDebug.test.ts
index bcda9fe..49b7624 100644
--- a/test/integration/goDebug.test.ts
+++ b/test/integration/goDebug.test.ts
@@ -3,7 +3,6 @@
 import * as path from 'path';
 import * as sinon from 'sinon';
 import {DebugClient} from 'vscode-debugadapter-testsupport';
-import { ILocation } from 'vscode-debugadapter-testsupport/lib/debugClient';
 import {DebugProtocol} from 'vscode-debugprotocol';
 import {
 	Delve,
@@ -313,7 +312,9 @@
 		test('should return supported features', () => {
 			return dc.initializeRequest().then((response) => {
 				response.body = response.body || {};
+				assert.strictEqual(response.body.supportsConditionalBreakpoints, true);
 				assert.strictEqual(response.body.supportsConfigurationDoneRequest, true);
+				assert.strictEqual(response.body.supportsSetVariable, true);
 			});
 		});
 
@@ -491,6 +492,184 @@
 
 	});
 
+	suite('conditionalBreakpoints', () => {
+		test('should stop on conditional breakpoint', () => {
+
+			const PROGRAM = path.join(DATA_ROOT, 'condbp');
+			const FILE = path.join(DATA_ROOT, 'condbp', 'condbp.go');
+			const BREAKPOINT_LINE = 7;
+			const location = getBreakpointLocation(FILE, BREAKPOINT_LINE);
+
+			const config = {
+				name: 'Launch',
+				type: 'go',
+				request: 'launch',
+				mode: 'auto',
+				program: PROGRAM,
+			};
+			const debugConfig = debugConfigProvider.resolveDebugConfiguration(undefined, config);
+			return Promise.all([
+
+				dc.waitForEvent('initialized').then(() => {
+					return dc.setBreakpointsRequest({
+						lines: [ location.line ],
+						breakpoints: [ { line: location.line, condition: 'i == 2' } ],
+						source: { path: location.path }
+					});
+				}).then(() => {
+					return dc.configurationDoneRequest();
+				}),
+
+				dc.launch(debugConfig),
+
+				dc.assertStoppedLocation('breakpoint', location)
+
+			]).then(() =>
+				// The program is stopped at the breakpoint, check to make sure 'i == 1'.
+				dc.threadsRequest().then((threadsResponse) =>
+					dc.stackTraceRequest({threadId: threadsResponse.body.threads[0].id}).then((stackTraceResponse) =>
+						dc.scopesRequest({frameId: stackTraceResponse.body.stackFrames[0].id}).then((scopesResponse) =>
+							dc.variablesRequest({variablesReference: scopesResponse.body.scopes[0].variablesReference})
+							.then((variablesResponse) => {
+								assert.strictEqual(variablesResponse.body.variables[0].name, 'i');
+								assert.strictEqual(variablesResponse.body.variables[0].value, '2');
+							})
+						)
+					)
+				)
+			);
+		});
+
+		test('should add breakpoint condition', async () => {
+
+			const PROGRAM = path.join(DATA_ROOT, 'condbp');
+			const FILE = path.join(DATA_ROOT, 'condbp', 'condbp.go');
+			const BREAKPOINT_LINE = 7;
+			const location = getBreakpointLocation(FILE, BREAKPOINT_LINE);
+
+			const config = {
+				name: 'Launch',
+				type: 'go',
+				request: 'launch',
+				mode: 'auto',
+				program: PROGRAM,
+			};
+			const debugConfig = debugConfigProvider.resolveDebugConfiguration(undefined, config);
+
+			return dc.hitBreakpoint(debugConfig, location).then(() =>
+				// The program is stopped at the breakpoint, check to make sure 'i == 0'.
+				dc.threadsRequest().then((threadsResponse) =>
+					dc.stackTraceRequest({threadId: threadsResponse.body.threads[0].id}).then((stackTraceResponse) =>
+						dc.scopesRequest({frameId: stackTraceResponse.body.stackFrames[0].id}).then((scopesResponse) =>
+							dc.variablesRequest({variablesReference: scopesResponse.body.scopes[0].variablesReference})
+							.then((variablesResponse) => {
+								assert.strictEqual(variablesResponse.body.variables[0].name, 'i');
+								assert.strictEqual(variablesResponse.body.variables[0].value, '0');
+							})
+						)
+					)
+				)
+			).then(() =>
+				// Add a condition to the breakpoint, and make sure it runs until 'i == 2'.
+				dc.setBreakpointsRequest({
+					lines: [ location.line ],
+					breakpoints: [ { line: location.line, condition: 'i == 2' } ],
+					source: { path: location.path }
+				}).then(() =>
+					Promise.all([
+						dc.continueRequest({threadId: 1}),
+						dc.assertStoppedLocation('breakpoint', location)
+					]).then(() =>
+						// The program is stopped at the breakpoint, check to make sure 'i == 2'.
+						dc.threadsRequest().then((threadsResponse) =>
+							dc.stackTraceRequest({threadId: threadsResponse.body.threads[0].id}).then((stackTraceResponse) =>
+								dc.scopesRequest({frameId: stackTraceResponse.body.stackFrames[0].id}).then((scopesResponse) =>
+									dc.variablesRequest({variablesReference: scopesResponse.body.scopes[0].variablesReference})
+									.then((variablesResponse) => {
+										assert.strictEqual(variablesResponse.body.variables[0].name, 'i');
+										assert.strictEqual(variablesResponse.body.variables[0].value, '2');
+									})
+								)
+							)
+						)
+					)
+				)
+			);
+		});
+
+		test('should remove breakpoint condition', () => {
+
+			const PROGRAM = path.join(DATA_ROOT, 'condbp');
+			const FILE = path.join(DATA_ROOT, 'condbp', 'condbp.go');
+			const BREAKPOINT_LINE = 7;
+			const location = getBreakpointLocation(FILE, BREAKPOINT_LINE);
+
+			const config = {
+				name: 'Launch',
+				type: 'go',
+				request: 'launch',
+				mode: 'auto',
+				program: PROGRAM,
+			};
+			const debugConfig = debugConfigProvider.resolveDebugConfiguration(undefined, config);
+			return Promise.all([
+
+				dc.waitForEvent('initialized').then(() => {
+					return dc.setBreakpointsRequest({
+						lines: [ location.line ],
+						breakpoints: [ { line: location.line, condition: 'i == 2' } ],
+						source: { path: location.path }
+					});
+				}).then(() => {
+					return dc.configurationDoneRequest();
+				}),
+
+				dc.launch(debugConfig),
+
+				dc.assertStoppedLocation('breakpoint', location)
+
+			]).then(() =>
+				// The program is stopped at the breakpoint, check to make sure 'i == 2'.
+				dc.threadsRequest().then((threadsResponse) =>
+					dc.stackTraceRequest({threadId: threadsResponse.body.threads[0].id}).then((stackTraceResponse) =>
+						dc.scopesRequest({frameId: stackTraceResponse.body.stackFrames[0].id}).then((scopesResponse) =>
+							dc.variablesRequest({variablesReference: scopesResponse.body.scopes[0].variablesReference})
+							.then((variablesResponse) => {
+								assert.strictEqual(variablesResponse.body.variables[0].name, 'i');
+								assert.strictEqual(variablesResponse.body.variables[0].value, '2');
+							})
+						)
+					)
+				)
+			).then(() =>
+				// Remove the breakpoint condition, and make sure the program runs until 'i == 3'.
+				dc.setBreakpointsRequest({
+					lines: [ location.line ],
+					breakpoints: [ { line: location.line } ],
+					source: { path: location.path }
+				}).then(() =>
+					Promise.all([
+						dc.continueRequest({threadId: 1}),
+						dc.assertStoppedLocation('breakpoint', location)
+					]).then(() =>
+						// The program is stopped at the breakpoint, check to make sure 'i == 3'.
+						dc.threadsRequest().then((threadsResponse) =>
+							dc.stackTraceRequest({threadId: threadsResponse.body.threads[0].id}).then((stackTraceResponse) =>
+								dc.scopesRequest({frameId: stackTraceResponse.body.stackFrames[0].id}).then((scopesResponse) =>
+									dc.variablesRequest({variablesReference: scopesResponse.body.scopes[0].variablesReference})
+									.then((variablesResponse) => {
+										assert.strictEqual(variablesResponse.body.variables[0].name, 'i');
+										assert.strictEqual(variablesResponse.body.variables[0].value, '3');
+									})
+								)
+							)
+						)
+					)
+				)
+			);
+		});
+	});
+
 	suite('panicBreakpoints', () => {
 
 		test('should stop on panic', () => {