## Set Up a Development Environment

1. `$ git clone git@github.com:ruby/debug.git`
2. `$ bundle install`
3. `$ rake` - this will
    - Compile the C extension locally (which can also be done solely with `rake compile`).
    - Run tests.
    - Re-generate `README.md`.

If you spot any problem, please open an issue.

## Run Tests

### Run all tests

```bash
$ rake test_all
```

### Run all console tests

```bash
$ rake test_console
```

### Run all protocol (DAP & CDP) tests

```bash
$ rake test_protocol
```

### Run specific test(s)


```bash
$ ruby test/console/break_test.rb # run all tests in the specified file
$ ruby test/console/break_test.rb -h # to see all the test options
```

## Generate Tests

There is a test generator in `debug.rb` project to make it easier to write tests.

### Quickstart

This section shows you how to create test file by test generator. For more advanced information on creating tests, please take a look at [gentest options](#gentest-options). (You can also check by `$bin/gentest -h`)

#### 1. Create a target file for debuggee.

Let's say, we created `target.rb` which is located in top level directory of debugger.

```ruby
module Foo
  class Bar
    def self.a
      "hello"
    end
  end
  Bar.a
  bar = Bar.new
end
```

#### 2. Run `gentest` as shown in the example below.

```shell
$ bin/gentest target.rb
```

#### 3. Debugger will be executed. You can type any debug commands.

```shell
$ bin/gentest target.rb
DEBUGGER: Session start (pid: 11139)
[1, 9] in ~/workspace/debug/target.rb
=>   1| module Foo
     2|   class Bar
     3|     def self.a
     4|       "hello"
     5|     end
     6|   end
     7|   Bar.a
     8|   bar = Bar.new
     9| end
=>#0	<main> at ~/workspace/debug/target.rb:1
INTERNAL_INFO: {"location":"~/workspace/debug/target.rb:1","line":1}
(rdbg)s
 s
[1, 9] in ~/workspace/debug/target.rb
     1| module Foo
=>   2|   class Bar
     3|     def self.a
     4|       "hello"
     5|     end
     6|   end
     7|   Bar.a
     8|   bar = Bar.new
     9| end
=>#0	<module:Foo> at ~/workspace/debug/target.rb:2
  #1	<main> at ~/workspace/debug/target.rb:1
INTERNAL_INFO: {"location":"~/workspace/debug/target.rb:2","line":2}
(rdbg)n
 n
[1, 9] in ~/workspace/debug/target.rb
     1| module Foo
     2|   class Bar
=>   3|     def self.a
     4|       "hello"
     5|     end
     6|   end
     7|   Bar.a
     8|   bar = Bar.new
     9| end
=>#0	<class:Bar> at ~/workspace/debug/target.rb:3
  #1	<module:Foo> at ~/workspace/debug/target.rb:2
  # and 1 frames (use `bt' command for all frames)
INTERNAL_INFO: {"location":"~/workspace/debug/target.rb:3","line":3}
(rdbg)b 7
 b 7
#0  BP - Line  /Users/naotto/workspace/debug/target.rb:7 (line)
INTERNAL_INFO: {"location":"~/workspace/debug/target.rb:3","line":3}
(rdbg)c
 c
[2, 9] in ~/workspace/debug/target.rb
     2|   class Bar
     3|     def self.a
     4|       "hello"
     5|     end
     6|   end
=>   7|   Bar.a
     8|   bar = Bar.new
     9| end
=>#0	<module:Foo> at ~/workspace/debug/target.rb:7
  #1	<main> at ~/workspace/debug/target.rb:1

Stop by #0  BP - Line  /Users/naotto/workspace/debug/target.rb:7 (line)
INTERNAL_INFO: {"location":"~/workspace/debug/target.rb:7","line":7}
(rdbg)q!
 q!
created: /Users/naotto/workspace/debug/test/tool/../debug/foo_test.rb
    class: FooTest
    method: test_1629720194
```

#### 4. The test file will be created as `test/debug/foo_test.rb`.

If the file already exists, **only method** will be added to it.

```ruby
# frozen_string_literal: true

require_relative '../support/console_test_case'

module DEBUGGER__
  class FooTest < ConsoleTestCase
    def program
      <<~RUBY
        1| module Foo
        2|   class Bar
        3|     def self.a
        4|       "hello"
        5|     end
        6|   end
        7|   Bar.a
        8|   bar = Bar.new
        9| end
      RUBY
    end

    def test_1629720194
      debug_code(program) do
        type 's'
        assert_line_num 2
        assert_line_text([
          /\[1, 9\] in .*/,
          /     1\| module Foo/,
          /=>   2\|   class Bar/,
          /     3\|     def self\.a/,
          /     4\|       "hello"/,
          /     5\|     end/,
          /     6\|   end/,
          /     7\|   Bar\.a/,
          /     8\|   bar = Bar\.new/,
          /     9\| end/,
          /=>\#0\t<module:Foo> at .*/,
          /  \#1\t<main> at .*/
        ])
        type 'n'
        assert_line_num 3
        assert_line_text([
          /\[1, 9\] in .*/,
          /     1\| module Foo/,
          /     2\|   class Bar/,
          /=>   3\|     def self\.a/,
          /     4\|       "hello"/,
          /     5\|     end/,
          /     6\|   end/,
          /     7\|   Bar\.a/,
          /     8\|   bar = Bar\.new/,
          /     9\| end/,
          /=>\#0\t<class:Bar> at .*/,
          /  \#1\t<module:Foo> at .*/,
          /  \# and 1 frames \(use `bt' command for all frames\)/
        ])
        type 'b 7'
        assert_line_text(/\#0  BP \- Line  .*/)
        type 'c'
        assert_line_num 7
        assert_line_text([
          /\[2, 9\] in .*/,
          /     2\|   class Bar/,
          /     3\|     def self\.a/,
          /     4\|       "hello"/,
          /     5\|     end/,
          /     6\|   end/,
          /=>   7\|   Bar\.a/,
          /     8\|   bar = Bar\.new/,
          /     9\| end/,
          /=>\#0\t<module:Foo> at .*/,
          /  \#1\t<main> at .*/,
          //,
          /Stop by \#0  BP \- Line  .*/
        ])
        type 'q!'
      end
    end
  end
end
```

#### gentest options

You can get more information about `gentest` here.

The default method name is `test_#{some integer numbers}`, the class name is `FooTest#{some integer numbers}`, and the file name will be `foo_test.rb`.
The following table shows examples of the gentest options.

| Command | Description | File | Class | Method |
| --- | --- | --- | --- | --- |
| `$ bin/gentest target.rb` | Run without any options | `foo_test.rb` | `FooTest...` | `test_...` |
| `$ bin/gentest target.rb --open=vscode` | Run the debugger with VScode | `foo_test.rb` | `FooTest...` | `test_...` |
| `$ bin/gentest target.rb -c step` | Specify the class name | `step_test.rb` | `StepTest...` | `test_...` |
| `$ bin/gentest target.rb -m test_step` | Specify the method name | `foo_test.rb` | `FooTest...` | `test_step` |
| `$ bin/gentest target.rb -c step -m test_step` | Specify the class name and the method name | `step_test.rb` | `StepTest...` | `test_step` |

### Assertions

- assert_line_num(expected)

Passes if `expected` is equal to the location where debugger stops.

- assert_line_text(text)

Passes if `text` is included in the last debugger log.

- assert_no_line_text(text)

Passes if `text` is not included in the last debugger log.

- assert_debuggee_line_text(text)

Passes if `text` is included in the debuggee log.

### Tests for DAP and CDP

Currently, there are 2 kinds of test frameworks for DAP and CDP.

1. Protocol-based tests

If you want to write protocol-based tests, you should use the test generator.
To run the test generator, you can enter `$ bin/gentest target.rb --open=vscode` in the terminal, VSCode will be executed.
Also, if you enter `$ bin/gentest target.rb --open=chrome` there, Chrome will be executed.
If you need to modify existing tests, it is basically a good idea to regenerate them by the test generator instead of rewriting them directly.
Please refer to [the Microsoft "Debug Adapter Protocol" article](https://microsoft.github.io/debug-adapter-protocol/specification) to learn more about DAP formats.
Please refer to [the "Chrome DevTools Protocol" official documentation](https://chromedevtools.github.io/devtools-protocol/) to learn more about CDP formats.

2. High-level tests

High-level tests are designed to test both DAP and CDP for a single method.
You can write tests as follows:
**NOTE:** Use `req_terminate_debuggee` to finish debugging. You can't use any methods such as `req_continue`, `req_next` and so on.

```ruby
require_relative '../support/test_case'
module DEBUGGER__
  class BreakTest < TestCase
    # PROGRAM is the target script.
    PROGRAM = <<~RUBY
      1| module Foo
      2|   class Bar
      3|     def self.a
      4|       "hello"
      5|     end
      6|   end
      7|   Bar.a
      8|   bar = Bar.new
      9| end
    RUBY

    def test_break1
      run_protocol_scenario PROGRAM do # Start debugging with DAP and CDP
        req_add_breakpoint 5 # Set a breakpoint on line 5.
        req_add_breakpoint 8 # Set a breakpoint on line 8.
        req_continue # Resume the program.
        assert_line_num 5 # Check if debugger stops at line 5.
        req_continue # Resume the program.
        assert_line_num 8 # Check if debugger stops at line 8.
        req_terminate_debuggee # Terminate debugging.
      end
    end
  end
end
```

#### API

- run_protocol_scenario program, dap: true, cdp: true, &scenario

Execute debugging `program` with `&scenario`. If you want to test it only for DAP, you can write as follows:

`run_protocol_scenario program, cdp: false ...`

- attach_to_dap_server(terminate_debuggee:)

Attach to the running DAP server through UNIX Domain Socket.

- attach_to_cdp_server

Attach to the running CDP server through TCP/IP.

- req_dap_disconnect

Disconnect from the currently connected DAP server.

- req_cdp_disconnect

Disconnect from the currently connected CDP server.

- req_add_breakpoint(lineno, path: temp_file_path, cond: nil)

Sends request to rdbg to add a breakpoint.

- req_delete_breakpoint bpnum

Sends request to rdbg to delete a breakpoint.

- req_set_exception_breakpoints(breakpoints)

Sends request to rdbg to set exception breakpoints. e.g.

```rb
req_set_exception_breakpoints([{ name: "RuntimeError", condition: "a == 1" }])
```

Please note that `setExceptionBreakpoints` resets all exception breakpoints in every request.

So the following code will only set breakpoint for `Exception`.

```rb
req_set_exception_breakpoints([{ name: "RuntimeError" }])
req_set_exception_breakpoints([{ name: "Exception" }])
```

This means you can also use

```rb
req_set_exception_breakpoints([])
```

to clear all exception breakpoints.

- req_continue

Sends request to rdbg to resume the program.

- req_step

Sends request to rdbg to step into next method.

- req_next

Sends request to rdbg to step over next method.

- req_finish

Sends request to rdbg to step out of current method.

- req_step_back

Sends request to rdbg to step back from current method.

- req_terminate_debuggee

Sends request to rdbg to terminate the debuggee.

- assert_hover_result(expected, expression)

Passes if result of `expression` matches `expected`.

`expected` need to be a Hash object as follows:

`assert_hover_result({value: '2', type: 'Integer'}, 'a')`

NOTE: `value` and `type` need to be strings.

- assert_repl_result(expected, expression)

Passes if result of `expression` matches `expected`.

`expected` need to be a Hash object as follows:

`assert_repl_result({value: '2', type: 'Integer'}, 'a')`

NOTE: `value` and `type` need to be strings.

- assert_watch_result(expected, expression)

Passes if result of `expression` matches `expected`.

`expected` need to be a Hash object as follows:

`assert_watch_result({value: '2', type: 'Integer'}, 'a')`

NOTE: `value` and `type` need to be strings.

- assert_line_num(expected)

Passes if `expected` is equal to the location where debugger stops.

- assert_locals_result(expected)

Passes if all of `expected` local variable entries match the ones returned by debugger.

An variable entry looks like this: `{ name: "bar", value: "nil", type: "NilClass" }`.

Please note that both `value` and `type` need to be strings.

- assert_threads_result(expected)

Passes if both conditions are true:

1. The number of expected patterns matches the number of threads.
2. Every pattern matches a thread name. Notice that the order of threads info is not guaranteed.

Example:

```
assert_threads_result(
  [
    /\.rb:\d:in `<main>'/,
    /\.rb:\d:in `block in foo'/
  ]
)
```

## To Update README

This project generates `README.md` from the template `misc/README.md.erb`

So **do not** directly update `README.md`. Instead, you should update the template's source and run

```bash
$ rake
```

to reflect the changes on `README.md`.


### When to re-generate `README.md`

- After updating `misc/README.md.erb`.
- After updating `rdbg` executable's options.
- After updating comments of debugger's commands.

## Manually Test Your Changes

You can manually test your changes with a simple Ruby script + a line of command. The following example will help you check:

- Breakpoint insertion.
- Resume from the breakpoint.
- Backtrace display.
- Information (local variables, ivars..etc.) display.
- Debugger exit.


### Script

```ruby
# target.rb
class Foo
  def first_call
    second_call(20)
  end

  def second_call(num)
    third_call_with_block do |ten|
      forth_call(num, ten)
    end
  end

  def third_call_with_block(&block)
    @ivar1 = 10; @ivar2 = 20

    yield(10)
  end

  def forth_call(num1, num2)
    num1 + num2
  end
end

Foo.new.first_call
```

### Command

```
$ exe/rdbg -e 'b 20;; c ;; bt ;; info ;; q!' -e c target.rb
```

### Expect Result

```
❯ exe/rdbg -e 'b 20;; c ;; bt ;; info ;; q!' -e c target.rb
DEBUGGER: Session start (pid: 9815)
[1, 10] in target.rb
=>    1| class Foo
      2|   def first_call
      3|     second_call(20)
      4|   end
      5|
      6|   def second_call(num)
      7|     third_call_with_block do |ten|
      8|       forth_call(num, ten)
      9|     end
     10|   end
=>#0    <main> at target.rb:1
(rdbg:commands) b 20
#0  BP - Line  /PATH_TO_PROJECT/target.rb:20 (return)
(rdbg:commands) c
[15, 24] in target.rb
     15|     yield(10)
     16|   end
     17|
     18|   def forth_call(num1, num2)
     19|     num1 + num2
=>   20|   end
     21| end
     22|
     23| Foo.new.first_call
     24|
=>#0    Foo#forth_call(num1=20, num2=10) at target.rb:20 #=> 30
  #1    block {|ten=10|} in second_call at target.rb:8
  # and 4 frames (use `bt' command for all frames)

Stop by #0  BP - Line  /PATH_TO_PROJECT/target.rb:20 (return)
(rdbg:commands) bt
=>#0    Foo#forth_call(num1=20, num2=10) at target.rb:20 #=> 30
  #1    block {|ten=10|} in second_call at target.rb:8
  #2    Foo#third_call_with_block(block=#<Proc:0x00007f9283101568 target.rb:7>) at target.rb:15
  #3    Foo#second_call(num=20) at target.rb:7
  #4    Foo#first_call at target.rb:3
  #5    <main> at target.rb:23
(rdbg:commands) info
=>#0    Foo#forth_call(num1=20, num2=10) at target.rb:20 #=> 30
%self => #<Foo:0x00007f92831016d0 @ivar1=10, @ivar2=20>
%return => 30
num1 => 20
num2 => 10
@ivar1 => 10
@ivar2 => 20
(rdbg:commands) q!
```
