Running your SugarCRM PHPUnit tests from vim

If you’ve ever had to go through some major version updates for SugarCRM, you’ve probably experienced the fact that each major version update brings a large quantity of new features and changes to the core codebase. This is always awesome, but it’s also a time when many customizations built on top are going to break. While reading through the changelog will help a lot in figuring out what things are susceptible, having good unit tests covering your customizations will also help quite a lot in addition.

Thankfully, PHPUnit actually comes pre-installed with SugarCRM so the installation for PHPUnit itself will not be covered here. The executable should be located in /bin/phpunit

Much like custom code, tests testing custom code also live in the /custom folder. All we need to do is go to the xml configuration file for PHPUnit, and define a custom testing suite.

vim <sugar>/tests/unit-php/phpunit.xml.dist

Here you will find the “default” testing suite. This should be left alone as it contains tests related to core functionalities.

    <testsuite name="all">
        <directory>./<directory>
        <directory>modules/<directory>
    </testsuite>

Just below that closing tag, we can define and add a custom test suite

    <testsuite name="custom">
        <directory>../../custom/tests/unit-php<directory>
    </testsuite>

Where your custom test files would then go to /custom/tests/unit-php (create it, if it does not exist)

With that established, you’re ready to use PHPUnit with SugarCRM. However, if your system is heavily customized, and you end up creating and running a LOT of tests, you’ll find that constantly having to switch context in-and-out of vim gets very tiring very fast.

The problem statement

With that in mind, a simple way to ease up on the amount of context switching would be to find a way to run tests from vim, and see the output of PHPUnit without having to close vim. In an ideal world, you could just say, use some tiling window manager. But you might be in the unfortunate situation of not given access to a linux machine and having to do your work from PuTTY. (The beginning of the pandemic was a turbulent time indeed, with the offices being closed)

That means we’d like the result of running PHPUnit to be visible in vim, without closing the file we are working on.

Building the solution

So here, we basically have two main problems to solve: find a way to run PHPUnit from vim while capturing its output, and then display that output in vim somehow.

Output capture

For making this tutorial simpler to follow, this will be demonstrated with capturing awk’s output. Let’s make a new file at /tmp/a.txt with some contents like

a
a
b
c
d

While there is the ! command to run external commands from vim, it’s not very convenient if we want to easily store the external commands’ output. For that reason, it’s more convenient to use vimscript’s system() function. This function takes a command string as an argumentn, and returns the output of whatever command we ran as a string.

:let test=system(awk '!/a/' /tmp/a.txt)
:echo test

And that will print

b
c
d

back to the screen.

Now that’s nice, but this requires that we supply the currently open file’s name into the function ourselves, which is less than ideal. We can actually capture the name of the currently open file with vimscript’s built in bufname() command. This command returns the name of a buffer. For displaying the list of all buffers, you can issue the :ls command. In a nutshell, the currently open file is usually going to be the current buffer, denoted by %. That means, capturing the name of the buffer % gives us the name of the file we are working on currently. We can now modify the previous function to read:

:let test=system("awk '!/a/' " . bufname("%"))

And get the same result. Only that this time, we didn’t need to remember the name of the currently open file. This will be useful later.

Displaying the results

Now that we have a vague idea on capturing results, we need something better than crudely echoing results onto the screen.

This part is actually fairly straight forward, the :split built in will create a new window (split horizontally), and for our purtposes, that is good enough. It’s possible to name a new window when calling split, e.g. :split mywindow will name the new window as mywindow. Calling split by itself will duplicate the current window.

To fill data into the newly created window, we can use the append() built in vimscript function. Append requires two variables. A line number, and some string that should be appended. Given that we’ve figured out how to capture output, we can use this function to paste the output into the newly created window.

Let’s try it out:

:let test=system("awk '!/a/' " . bufname("%"))
:split mywindow
:call append(0, test)

The output you should see is b^@c^@d^@ in a new window.

As you can see, newlines get all garbled up. This is quite easy to fix, though somewhat confusingly, the function we need here is split(). This function has nothing to do with windows, this function allows us to specify an expression, and split the string wherever the given expression appears, and remove expression from the results. Modifying the above snippet of code:

:let test=system("awk '!/a/' " . bufname("%"))
:split mywindow
:call append(0, split(test, '\v\n' ))  

Gives us the desired output.

Circling back to PHPUnit

There’s one more thing we should consider before building up the function. We might not always want to just execute PHPUnit on the entire file, as that will simply execute unit tests for everything. This is not particularly useful if we just added a brand new test, and want to see the results of said brand new test. PHPUnit has the --filter flag, which allows a regular expression to be used to determine which functions to run. (PHPUnit will take care of adding delimiters, we do not need to do that ourselves)

In order to use the filter flag, we have to be able to somehow select something in our text file, and provide it as an input argument when calling through the system() function.

The easiest way is to search for something, and then find some convenient way to yank the searched term. Yanking something will put it in the @” register, which we can simply paste back into the system function.

Outline

So the following steps more or less describe what we want

Add different branches to the function, 
depending on whether we have used a search pattern or not. 

If yes, use phpunit with the --filter flag and
run the matching tests from the current file.

If no, use phpunit just as is, 
meaning run tests for everything on the current file.

Capture output from phpunit, 
create a new window, and paste the output into it.

That final point brings up one tiny thing. We probably aren’t interested in keeping the contents of the new window, as we most likely just want to see and acknowledge the results, but don’t need to save it.

This can be done by overriding the default behaviour of vim. Normally, when the user wants to close a file using :q that has had its buffer modified but not written to file, it’ll say that changes have been made since last write. We can tell vim not to want to save things to file, and be okay with us discarding changes by issuing the following setting to the newly created window:

:setlocal buftype=nofile 

The Function

function RunPHPUnitTest(filter)
    cd %:p:h
    if a:filter
        normal! T yw
        let result = system("../../../vendor/bin/phpunit --filter " . @" . " " . bufname("%"))
    else
        let result = system("../../../vendor/bin/phpunit " . bufname("%"))
    endif
    split __PHPUnit_Result__
    normal! ggdG
    setlocal buftype=nofile
    call append(0, split(result, '\v\n'))
    cd -
endfunction

Here we’ve made a simple choice. If we pass a value to the RunPHPUnitTest function, assume that we have a search pattern, and run phpunit with the --filter flag using the contents of the @” register.

If we do not pass a value to the RunPHPUnitTest function, then just run all the tests in the file. As always, this function can be mapped to some keybinding.

nnoremap <leader>x :call RunPHPUnitTest(1)<CR>
nnoremap <leader>y :call RunPHPUnitTest(0)<CR>