-
Notifications
You must be signed in to change notification settings - Fork 3
Debug JUnit tests directly form VIM and Offline management of breakpoints #8
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,5 +1,7 @@ | ||
| # vim-jdb | ||
|
|
||
| This is a fork of Dica-Developer/vim-jdb so checkout the main branch as well. | ||
|
|
||
| Its a JAVA debugger frontend plugin for VIM. It allows to debug a JAVA program via the JDB debugger. It allows remote debugging via attach parameter. | ||
| It marks by vim-jdb setted breakpoints and shows the current file and line the debugger stays in. | ||
|
|
||
|
|
@@ -18,6 +20,10 @@ It requires VIM >= 8.0 and VIM compiled with `channel`, `signs` and `job` suppor | |
| 8. use the command `:JDBStepOver` to execute to the next line | ||
| 9. with `:JDBCommand` you can send any JDB command to the JDB JAVA process, e.g. you want to see all locals do `:JDBCommands locals` | ||
| 10. with `:JDBContinue` you can resume the execution until the next breakpoint is hits | ||
| 11. with `:JDBDebugProcess` will (currently) pick the function name you are currently in if it is annontated with "@Test". It will then construct the the path to the class from the class name and package name in the file. | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. duplicate "the"
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The command name is misleading in my opinion. I would prefer JDBDebugTest or JDBDebugMethode |
||
| finally it will start jdb like this e.g.: jdb -Dtest.single=functionmane org.junit.runner.JUnitCore yourpackage.classname | ||
| for this to work properly you need to set the env var CLASSPATH with your projects dependencies. For me I use gradle for dev so have a task named "classpath" and can do this from with in gradle before starting. | ||
| :let $CLASSPATH=system("gradle -q classpath") . ":build/classes/main:build/classes/test" | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This will fail in a multi project gradle environment. I am wondering why if gradle is always there not starting the single test via gradle and then debug via this plugin. It could also start the gradle process before i debug mode. |
||
|
|
||
| ## Commands | ||
| |Command|Description| | ||
|
|
@@ -32,8 +38,11 @@ It requires VIM >= 8.0 and VIM compiled with `channel`, `signs` and `job` suppor | |
| |JDBStepUp|steps a level up in the stack| | ||
| |JDBStepI|steps to the next instruction| | ||
| |JDBCommand|send any JDB command to the application under debug| | ||
| |JDBDebugProcess|While in a JUnit test this will start a debug process using junit -Dtest.single| | ||
|
|
||
| ## Global variables | ||
|
|
||
| To specify the JDB command to use you can overwrite the following variable `g:vimjdb_jdb_command`. The default is `jdb`. | ||
| For starting debug process for Unit tests you can override the following variables. | ||
| g:vimjdb_unit_test_class by default it is set tp 'org.junit.runnint.JUnitCore' | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -26,10 +26,12 @@ if !has('signs') | |
| finish | ||
| endif | ||
|
|
||
| command! -nargs=? JDBDebugProcess call s:createDebugProcess(<f-args>) | ||
| command! -nargs=? JDBAttach call s:attach(<f-args>) | ||
| command! JDBDebugUnit call s:startUnitTest() | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Where is startUnitTest defined? |
||
| command! JDBDetach call s:detach() | ||
| command! JDBBreakpointOnLine call s:breakpointOnLine(expand('%:~:.'), line('.')) | ||
| command! JDBClearBreakpointOnLine call s:clearBreakpointOnLine(expand('%:~:.'), line('.')) | ||
| command! JDBBreakpointOnLine call s:setBreakpoint() | ||
| command! JDBClearBreakpointOnLine call s:removeBreakpoint() | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please give me more context why you changed that. |
||
| command! JDBContinue call s:continue() | ||
| command! JDBStepOver call s:stepOver() | ||
| command! JDBStepIn call s:stepIn() | ||
|
|
@@ -49,14 +51,17 @@ sign define currentline text=-> texthl=Search | |
|
|
||
| let s:job = '' | ||
| let s:channel = '' | ||
| let s:running = 0 | ||
| let s:org_win_id = 0 | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. please use camel case and not underscores |
||
| let s:currentfile = '' | ||
|
|
||
| function! s:hash(name, linenumber) | ||
| let l:result = 1 | ||
| for c in split(a:name, '\zs') | ||
| let l:result = (l:result * 2) + char2nr(c) | ||
| endfor | ||
| let l:result = (l:result * 2) + a:linenumber | ||
| return l:result | ||
| return strpart(l:result, 4) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What is the reason for that change? |
||
| endfunction | ||
|
|
||
| function! s:getClassNameFromFile(filename) | ||
|
|
@@ -72,36 +77,54 @@ endfunction | |
|
|
||
| function! JdbOutHandler(channel, msg) | ||
| "TODO make debug logging out of it | ||
| "echom a:msg | ||
| echom a:msg | ||
| let l:breakpoint = '' | ||
| if -1 < stridx(a:msg, 'Breakpoint hit:') | ||
| if -1 < stridx(a:msg, 'Breakpoint hit: "thread') | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What is the reason for that change? |
||
| echom "breakpoint hit" | ||
| let l:breakpoint = split(a:msg, ',') | ||
| let l:filename = l:breakpoint[1] | ||
| let l:filename = substitute(l:filename, '\.\a*()$', '', '') | ||
| let l:filename = substitute(l:filename, '\.\w*()$', '', '') | ||
| let l:filename = substitute(l:filename, ' ', '', 'g') | ||
| let l:filename = join(split(l:filename, '\.'), '/') | ||
| let l:filenamefrag= split(l:filename, '\.') | ||
| let l:filename = findfile(filenamefrag[-1]) | ||
| echom 'found file ' . l:filename | ||
| let l:linenumber = substitute(l:breakpoint[2], ',\|\.\| \|bci=\d*\|line=', '', 'g') | ||
| " TODO only open when current buffer is not the file to open | ||
| exe 'e +%foldopen! **/'. l:filename .'.java' | ||
| exe l:linenumber | ||
| exe 'sign unplace 2' | ||
| exe 'sign place 2 line='. l:linenumber .' name=currentline file='. expand("%:p") | ||
| " only open when current buffer is not the file to open | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. if the todo is fixed please remove the comment |
||
| if l:filename != s:currentfile | ||
| if filereadable(l:filename) | ||
| exe 'e +%foldopen! **/'. l:filename | ||
| let s:currentfile = l:filename | ||
| exe l:linenumber | ||
| exe 'sign unplace 2' | ||
| exe 'sign place 2 line='. l:linenumber .' name=currentline file='. expand("%:p") | ||
| else | ||
| echom 'tried to open file ' . l:filename | ||
| endif | ||
| endif | ||
| endif | ||
| if -1 < stridx(a:msg, 'Step completed:') | ||
| if -1 < stridx(a:msg, 'Step completed: "thread') | ||
| " TODO handle ClassName$3.get() | ||
| echom "Step completed" | ||
| let l:breakpoint = split(a:msg, ',') | ||
| let l:filename = l:breakpoint[1] | ||
| let l:filename = substitute(l:filename, '\.\a*()$', '', '') | ||
| let l:filename = substitute(l:filename, '\.\w*()$', '', '') | ||
| let l:filename = substitute(l:filename, ' ', '', 'g') | ||
| let l:filename = join(split(l:filename, '\.'), '/') | ||
| let l:filenamefrag= split(l:filename, '\.') | ||
| let l:filename = findfile(filenamefrag[-1]) | ||
| echom 'found file ' . l:filename | ||
| let l:linenumber = substitute(l:breakpoint[2], ',\|\.\| \|bci=\d*\|line=', '', 'g') | ||
| " TODO only open when current buffer is not the file to open | ||
| exe 'e +%foldopen! **/'. l:filename .'.java' | ||
| exe l:linenumber | ||
| exe 'sign unplace 2' | ||
| exe 'sign place 2 line='. l:linenumber .' name=currentline file='. expand("%:p") | ||
| " only open when current buffer is not the file to open | ||
| " if l:filename != l:currentfile | ||
| if filereadable(l:filename) | ||
| exe 'e +%foldopen! **/'. l:filename | ||
| let s:currentfile = l:filename | ||
| exe l:linenumber | ||
| exe 'sign unplace 2' | ||
| exe 'sign place 2 line='. l:linenumber .' name=currentline file='. expand("%:p") | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You no longer call this for the file open case. How is the sign placed in this case? |
||
| else | ||
| echom 'tried to open file ' . l:filename | ||
| endif | ||
| " endif | ||
| endif | ||
| if -1 < stridx(a:msg, 'Set breakpoint ') || -1 < stridx(a:msg, 'Set deferred breakpoint ') | ||
| echom "Set breakpoint" | ||
|
|
@@ -121,12 +144,64 @@ function! JdbOutHandler(channel, msg) | |
| echom "Continue" | ||
| exe 'sign unplace 2' | ||
| endif | ||
| if -1 < stridx(a:msg, 'The application exited') | ||
| exe 'sign unplace 2' | ||
| let s:channel = '' | ||
| let s:job = '' | ||
| let s:running = 0 | ||
| let win = bufwinnr('_JDB_SHELL_') | ||
| if win != -1 | ||
| exe win . 'wincmd w' | ||
| exe 'close' | ||
| endif | ||
| endif | ||
| endfunction | ||
|
|
||
| function! JdbErrHandler(channel, msg) | ||
| let s:running = 0 | ||
| echoe 'Error on JDB communication: '. a:msg | ||
| endfunction | ||
|
|
||
| let s:signCounter = 10 | ||
| let s:signtype = 'breakpoint' | ||
|
|
||
| function! s:applyBreakPoints(channel, name) | ||
| let l:points = split(execute('sign place'), '\n') | ||
| let l:fileName = '' | ||
| for line in l:points | ||
| " Start of new file, listing all signs for this | ||
| if -1 < stridx(line, 'Signs for') | ||
| let l:lineparts = split(line, ' ') | ||
| let l:fileName = l:lineparts[-1] | ||
| let l:fileName = substitute(l:fileName ,':$', '', 'g') | ||
| endif | ||
| " if sign found with given name | ||
| if -1 < stridx(line, 'name='. a:name) | ||
| let l:lineparts = split(line, ' ') | ||
| let l:lineNumber = substitute(l:lineparts[0], 'line=','','g') | ||
| let l:className = s:getClassNameFromFile(l:fileName) | ||
| echom 'breakpoint line: ' . l:fileName .':'. l:lineNumber | ||
| call ch_sendraw(a:channel, "stop at " . l:className . ":" . l:lineNumber . "\n") | ||
| endif | ||
| endfor | ||
| endfunction | ||
|
|
||
| function! s:setBreakpoint() | ||
| let l:fileName = expand('%:p') | ||
| let l:lineNumber = line('.') | ||
| let l:currentBuffer = bufnr('%') | ||
| exe 'sign place ' . s:signCounter . ' line=' . l:lineNumber . ' name=' . s:signtype . ' buffer=' . l:currentBuffer | ||
| let s:signCounter = s:signCounter + 1 | ||
| if s:running == 1 | ||
| call s:breakpointOnLine(l:fileName, l:lineNumber) | ||
| endif | ||
| endfunction | ||
|
|
||
| function! s:removeBreakpoint() | ||
| sign unplace | ||
| call s:clearBreakpointOnLine(expand('%:p'), line('.')) | ||
| endfunction | ||
|
|
||
| function! s:openWindow(name, mode, size) | ||
| let win = bufwinnr(a:name) | ||
| if win == -1 | ||
|
|
@@ -160,13 +235,27 @@ endfunction | |
|
|
||
| function! s:attach(...) | ||
| if s:job == '' || job_status(s:job) != 'run' | ||
| let l:hostAndPort = get(a:, 1, 'localhost:5005') | ||
| " save current window state | ||
| let s:org_win_id = win_getid() | ||
| let l:winview = winsaveview() | ||
|
|
||
| let l:arg1 = get(a:, 1, 'localhost:5005') | ||
| let l:jdbCommand = get(g:, 'vimjdb_jdb_command', 'jdb') | ||
| call s:openWindow('_JDB_SHELL_', '', 15) | ||
| let s:job = job_start(l:jdbCommand .' -attach '. l:hostAndPort, {"out_modifiable": 0, "out_io": "buffer", "out_name": "_JDB_SHELL_", "out_cb": "JdbOutHandler", "err_modifiable": 0, "err_io": "buffer", "err_name": "_JDB_SHELL_", "err_cb": "JdbErrHandler"}) | ||
| echom "Attaching to java process" | ||
| let s:job = job_start(l:jdbCommand .' -attach '. l:arg1, {"out_modifiable": 0, "out_io": "buffer", "out_name": "_JDB_SHELL_", "out_cb": "JdbOutHandler", "err_modifiable": 0, "err_io": "buffer", "err_name": "_JDB_SHELL_", "err_cb": "JdbErrHandler"}) | ||
| let s:channel = job_getchannel(s:job) | ||
|
|
||
| call ch_sendraw(s:channel, "run\n") | ||
| call ch_sendraw(s:channel, "monitor where\n") | ||
|
|
||
| " apply breakpoint into jdb process from defined signs | ||
| call s:applyBreakPoints(s:channel, 'breakpoint') | ||
| let s:running = 1 | ||
|
|
||
| " Set cursor in where you started the process, in the correct buffer | ||
| call win_gotoid(s:org_win_id) | ||
| call winrestview(l:winview) | ||
| else | ||
| echom 'There is already a JDB session running. Detach first before you start a new one.' | ||
| endif | ||
|
|
@@ -177,6 +266,7 @@ function! s:detach() | |
| call ch_sendraw(s:channel, "exit\n") | ||
| let s:channel = '' | ||
| let s:job = '' | ||
| let s:running = 0 | ||
| let win = bufwinnr('_JDB_SHELL_') | ||
| if win != -1 | ||
| exe win . 'wincmd w' | ||
|
|
@@ -186,36 +276,57 @@ function! s:detach() | |
| endfunction | ||
|
|
||
| function! s:breakpointOnLine(fileName, lineNumber) | ||
| "TODO check if we are on a java file and fail if not | ||
| let fileName = s:getClassNameFromFile(a:fileName) | ||
| "TODO store command temporary if not already connected | ||
| call ch_sendraw(s:channel, "stop at " . fileName . ":" . a:lineNumber . "\n") | ||
| if s:channel != '' | ||
| let l:fileName = s:getClassNameFromFile(a:fileName) | ||
| call ch_sendraw(s:channel, "stop at " . l:fileName . ":" . a:lineNumber . "\n") | ||
| endif | ||
| endfunction | ||
|
|
||
| function! s:clearBreakpointOnLine(fileName, lineNumber) | ||
| "TODO check if we are on a java file and fail if not | ||
| let fileName = s:getClassNameFromFile(a:fileName) | ||
| "TODO store command temporary if not already connected | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For me, both todo's are not fixed. I would like to keep them until they are fixed or decided not to be implemented. |
||
| call ch_sendraw(s:channel, "clear " . fileName . ":" . a:lineNumber . "\n") | ||
| if s:channel != '' | ||
| let l:fileName = s:getClassNameFromFile(a:fileName) | ||
| call ch_sendraw(s:channel, "clear " . l:fileName . ":" . a:lineNumber . "\n") | ||
| endif | ||
| endfunction | ||
|
|
||
| " for all driver options check if running=0 if so please start the process by sending run first | ||
| function! s:continue() | ||
| if s:running == 0 | ||
| call ch_sendraw(s:channel, "run\n") | ||
| let s:running = 1 | ||
| endif | ||
| call ch_sendraw(s:channel, "resume\n") | ||
| endfunction | ||
|
|
||
| function! s:stepOver() | ||
| if s:running == 0 | ||
| call ch_sendraw(s:channel, "run\n") | ||
| let s:running = 1 | ||
| endif | ||
| call ch_sendraw(s:channel, "next\n") | ||
| endfunction | ||
|
|
||
| function! s:stepUp() | ||
| if s:running == 0 | ||
| call ch_sendraw(s:channel, "run\n") | ||
| let s:running = 1 | ||
| endif | ||
| call ch_sendraw(s:channel, "step up\n") | ||
| endfunction | ||
|
|
||
| function! s:stepIn() | ||
| if s:running == 0 | ||
| call ch_sendraw(s:channel, "run\n") | ||
| let s:running = 1 | ||
| endif | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this code should be moved to its own method to avoid the duplication. |
||
| call ch_sendraw(s:channel, "step in\n") | ||
| endfunction | ||
|
|
||
| function! s:stepI() | ||
| if s:running == 0 | ||
| call ch_sendraw(s:channel, "run\n") | ||
| let s:running = 1 | ||
| endif | ||
| call ch_sendraw(s:channel, "stepi\n") | ||
| endfunction | ||
|
|
||
|
|
@@ -233,3 +344,53 @@ function! s:toggleWatchWindow() | |
| endif | ||
| endfunction | ||
|
|
||
| function! s:createDebugProcess() | ||
| let l:running = 0 | ||
|
|
||
| " save current window state | ||
| let s:org_win_id = win_getid() | ||
| let l:winview = winsaveview() | ||
| " TODO Figure out a way to properly detect if the current function is a test | ||
| " or not, if so start the debugging wih this otherwise fall back to setting | ||
| " main | ||
| " get current function name | ||
| echom 'Finding java process and starting it' | ||
| " Backwards search for @Test annotation and grab the function it defines | ||
| call search('@Test','bce') | ||
| execute "normal! wwwyt(" | ||
| let l:fnname = @" | ||
| echom 'Function name: '. l:fnname | ||
|
|
||
| " get package name | ||
| execute "normal! ggwyt;" | ||
| let l:pcname = @" | ||
|
|
||
| " get class name | ||
| call search("public class", 'e') | ||
| execute "normal! wyiW" | ||
| let l:clname = @" | ||
|
|
||
| " Build unit test process string | ||
| let l:appArgs = '' | ||
| if l:fnname != '' | ||
| let l:appArgs .= ' -Dtest.single=' . l:fnname | ||
| endif | ||
| let l:appArgs .= ' ' . get(g:, 'vimjdb_unit_test_class', 'org.junit.runner.JUnitCore') | ||
| let l:appArgs .= ' ' . l:pcname . '.' . l:clname | ||
|
|
||
| " start jdb process in a new buffer | ||
| let l:jdbCommand = get(g:, 'vimjdb_jdb_command', 'jdb') | ||
| let l:process = l:jdbCommand .' '. l:appArgs | ||
| echom 'starting job: ' . l:process | ||
| call s:openWindow('_JDB_SHELL_', '', 15) | ||
| let s:job = job_start(l:process , {"out_modifiable": 0, "out_io": "buffer", "out_name": "_JDB_SHELL_", "out_cb": "JdbOutHandler", "err_modifiable": 0, "err_io": "buffer", "err_name": "_JDB_SHELL_","err_cb": "JdbErrHandler"}) | ||
| let s:channel = job_getchannel(s:job) | ||
|
|
||
| " Apply breakpoints stored in sign's into the debug process | ||
| call s:applyBreakPoints(s:channel, 'breakpoint') | ||
|
|
||
| " Set cursor in where you started the process, in the correct buffer | ||
| call win_gotoid(s:org_win_id) | ||
| call winrestview(l:winview) | ||
|
|
||
| endfunction | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This needs to be removed before I can merge.