Skip to content
This repository has been archived by the owner on Feb 12, 2022. It is now read-only.

Framework for debug adapter and mock ui #1088

Closed
wants to merge 9 commits into from
Closed

Framework for debug adapter and mock ui #1088

wants to merge 9 commits into from

Conversation

jwli229
Copy link
Contributor

@jwli229 jwli229 commented Oct 16, 2017

Release note: none
Issue: #907
Summary:

  • Mock UI as a CLI to communicate with the adapter
  • Starter code for the debugger adapter with an initialize request

Test:

  • Usage: node lib/debugger/mock-ui/debugger-cli.js --adapterPath <path_to_debug_adapter> --prepack <command_to_start_prepack>
    e.g. node lib/debugger/mock-ui/debugger-cli.js --adapterPath lib/debugger/adapter/DebugAdapter.js --prepack "lib/prepack-cli.js test/serializer/basic/Date.js"
  • Order of requests on start-up of UI according to protocol:
    1. initialize (UI to adapter): UI tells the adapter to send debugger configuration details (e.g. whether lines should start at 0 or 1)
    2. initialize (adapter to UI): Adapter tells the UI to send any persisted debug information (e.g. persisted breakpoints from previous usages of the debugger)
    3. configurationDone (UI to adapter): UI tells the adapter that it is done sending persisted debug information
  • Supported commands thus far:
    • init <clientID> <adapterID>: sends an initialize request to the adapter and receives a response printed to console
    • configDone: sends a configurationDone request to the adapter and receives a response printed to console
    • exit: quit the UI
      Note: for testing purposes, init and configDone are made into user commands. These can be done without user input.

@hermanventer
Copy link
Contributor

Your test plan is not reproducible. Ideally, another person should be able to get this pull request and verify that the test plan works on their setup.

You should also strive get have automated tests from day 1.

// Start Prepack in a child process
_startPrepack() {
if (this._prepackCommand.length === 0) {
console.error("No command given to start Prepack in adapter");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it OK to just carry one in this case?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Terminated the adapter process in this case.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also note that if no second argument was given, _prepackCommand will be undefined.

if (arg.startsWith("--")) {
arg = arg.slice(2);
if (arg === "prepack") {
this._prepackCommand = args.shift();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this work if you want to specify arguments to prepack itself?

Copy link
Contributor Author

@jwli229 jwli229 Oct 17, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, the entire command is passed in as one argument. See example above. We can eventually make the command to be read from a file like the launch.json in vscode.

});

this._prepackProcess.on("exit", () => {
// Prepack is finished
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should the adapter stay alive in this case?

}

/**
* The 'initialize' request is the first request called by the frontend
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"frontend"? I assume that is the UI.

import { Session } from "./session.js";

function run(process, console) {
let adapterPath: string = "";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need for a type annotation here. Flow is very good about inferring the types of local variables.

// reset the content length to ensure it is extracted for the next message
this._contentLength = -1;
// process the message
if (message.length > 0) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

a comment to explain why the message could be length 0 would be helpful.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It should not be, I removed this check.

}

_processEvent(event: DebugProtocol.Event) {
// to be implemented
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like you're not quite done with this.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This and _processEvent will be implemented in the next PR to not make this one too big. For now, they just confirm a message is received and parsed by printing it.

// they can be done without user input
if (command === "init") {
let args: DebugProtocol.InitializeRequestArguments = {
clientID: "CLI",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this a value prescribed by the protocol?

if (command === "init") {
let args: DebugProtocol.InitializeRequestArguments = {
clientID: "CLI",
adapterID: "Prepack",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Clearly this is not prescribed by the protocol. It does seem a terse. Could there in the future be another adapter that involves Prepack?

//separator for messages according to the protocol
const TWO_CRLF = "\r\n\r\n";

export class Session {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is not clear to me what a Session is and why it is distinct from the adapter. Mostly, I'm bemused by the logic that deals with parsing commands and buffering.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I moved the parsing and buffering into a separate class.

@jwli229
Copy link
Contributor Author

jwli229 commented Oct 17, 2017

I will set up testing for the debugger in a separate PR afterwards to avoid making this one too big.

// Start Prepack in a child process
_startPrepack() {
if (this._prepackCommand.length === 0) {
console.error("No command given to start Prepack in adapter");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also note that if no second argument was given, _prepackCommand will be undefined.

//set up message queue
this._messageQueue = new Queue();

this._prepackProcess = child_process.spawn("node", this._prepackCommand.split(" "));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

spawn or spawnSync?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we need the asynchronous version here. The sychronous one will not return until the Prepack process has exited. But the adapter may need to run while Prepack is still running (e.g. passing state info to UI or passing new user commands to Prepack).

for (let i = 0; i < lines.length; i++) {
let pair = lines[i].split(/: +/);
if (pair[0] === "Content-Length") {
this._contentLength = +pair[1];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unary + is confusing

// if we know what length we are expecting
if (this._contentLength >= 0) {
// we have enough data to check for the expected message
if (this._rawData.length >= this._contentLength) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Request Changes: Strictly speaking this just means that the buffer has allocated that much storage not necessarily that there are that many characters in the buffer. Please either make a comment or invariant around this if the behavior is intended.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed to compare against actual length in the buffer.

let parts = input.split(" ");
let command = parts[0];

// for testing purposes, init and configDone are made into user commands
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe pose this comment as a TODO

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the CLI, we can keep these two commands as being from user input. This way we can control tests on them using the CLI. In an actual IDE, these will be done without user input.


// for testing purposes, init and configDone are made into user commands
// they can be done from the adapter without user input
if (command === "init") {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Once we get more commands, a switch statement is probably better.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed.


// tell the adapter that configuration is done so it can expect other commands
_sendConfigDoneRequest(args: DebugProtocol.ConfigurationDoneArguments) {
this._sequenceNum++;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could move the sequence num increment and stringify into the packageAndSend to make sure you can't forget to increment the sequence num.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved the increment into _packageAndSend. The stringify is harder to move because each request has a different type of arguments, so all the possible types would need to be specified in _packageAndSend.

Copy link
Contributor

@jeffreytan81 jeffreytan81 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cool, looks good to me from high level, I will let @mostafaeweda to take a further look.

this._invalidCount = 0;
this._dataHandler = new DataHandler();
}
// the parent (i.e. ui) process
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is not the parent process, it is mock UI process itself.

process.exit(1);
}
//set up message queue
this._messageQueue = new Queue();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this holding all the pending commands from UI? Did not see any code using it. Will be used in later PR?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, this will be used in the next PR when all 3 parts (DebugServer in Prepack, DebugAdapter, UI) are connected.

// Filename aliases
declare module 'vscode-debugprotocol/lib/debugProtocol.js' {
declare module.exports: $Exports<'vscode-debugprotocol/lib/debugProtocol'>;
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This file doesn't seem very useful (it's mostly all any) & I can't find it in https://github.com/flowtype/flow-typed -- which should be the source of truth for any npm module types.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This file was autogenerated using the CLI from https://github.com/flowtype/flow-typed because it's not there already. It is needed to satisfy flow checks.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the file doesn't add any value, you should just add it to the ignore section in .flowconfig.

* of patent rights can be found in the PATENTS file in the same directory.
*/

/* @flow */

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: I see this project is setup with prettier in the devDependencies - but don't see the @format --> did you use prettier to format this file?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Our prettier seems to run off of directory structure instead of @Format annotations, everything matching src/**/*.js or scripts/**/*.js should get formatted.

this._prepackProcess = child_process.spawn("node", this._prepackCommand.split(" "));

process.on("exit", () => {
if (this._prepackProcess) this._prepackProcess.kill();

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this._prepackProcess is never set to null --> so, no need to check for null -- OR (change the flow type to nullable and set to null here)


response.body = response.body || {};
response.body.supportsConfigurationDoneRequest = true;
//Respond back to the UI with the configurations. Will add more configurations gradually as needed.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit space after //

break;
}
}
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is duplicating Nuclide's V8Protocol.js & VsDebugSession.js (https://github.com/facebook/nuclide/blob/master/pkg/nuclide-debugger-common/lib/V8Protocol.js)
Also, there's the npm package: vscode-debugadapter-testsupport which'd help you setup and send individual events to the adapter in a promise-based fashion.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What would be the cleanest way to reuse those files and keep them in sync between the two repos?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We discussed offline that it'd be best to switch to using the vscode npm package.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will use the npm package to set up automated testing.

this._adapterProcess.kill();
this._proc.exit(0);
}
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again, this is all duplicated work


/* @flow */

import { UISession } from "./UISession.js";

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should better wrap Nuclide's VsDebugSession or use VSCode's utils instead.

Summary:
- Mock UI as a CLI to communicate with the adapter
- Starter code for the debugger adapter with an initialize request

Test:
- Ran the mock-ui, can send the initialize request and receive a response on the CLI
- fix check for buffer size
- change if to switch for commands
Copy link

@facebook-github-bot facebook-github-bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@JWZ2018 is landing this pull request. If you are a Facebook employee, you can view this diff on Phabricator.

@jwli229 jwli229 deleted the debugadapter branch October 20, 2017 23:10
jwli229 added a commit that referenced this pull request Oct 23, 2017
Summary:
Release note: none
Issue: #907

- Mock UI as a CLI to communicate with the adapter
- Starter code for the debugger adapter with an initialize request
Closes #1088

Differential Revision: D6108024

Pulled By: JWZ2018

fbshipit-source-id: 506ec039dcb1ff540872cb2e1a87a165cfd22e4d
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants