6

I'm using Vimrunner to unit-test a Vim plugin. Everything is working, but I'm looking for a better/canonical way to execute script-local functions. Since they're not directly visible outside the script, I'm currently exposing the script's <SID> and prepending that to my calls in order to run them.

I have to add this code to my plugin to expose the SID:

function! s:SID()
  let fullname = expand("<sfile>")
  return matchstr(fullname, '<SNR>\d\+_')
endfunction
let g:my_plugin_SID = s:SID()

That will expose the SID as e.g. <SNR>18_. Since Vim functions are all global, and just name-munged, script-local functions can be invoked outside the script by prefixing the SID:

:call <SNR>18_some_function()

Then I do this in a spec:

describe "s:reverse_string" do
  let!(:sid) { VIM.command("echo g:my_plugin_SID") }

  def reverse_string(string)
    VIM.command("echo #{sid}reverse_string('#{string}')")
  end

  it "does something" do
    reverse_string("foo").should == "oof"
  end
end

Is there a better way to do this?

2 Answers 2

5

The easiest way is to simply expose script-local functions: Turn s:MyFunc into MyPlugin#MyFunc. After all, function visibility is only by convention, anyway; nothing (except the cumbersome name lookup) prevents script-local functions from being called, anyway.

Sometimes, I deviate from that and do want to invoke script-local functions from tests. My approach is quite similar to yours, but instead of exposing the <SID> from within the plugin, I've written helper functions to parse the <SID> from the :scriptnames output. It's slower (I don't care; my tests are integration tests), but I don't have to pollute the plugin itself with test code. Here's an example showing the Sid() and SidInvoke() helpers:

let s:SID = Sid('autoload/EditSimilar/Substitute.vim')
function! s:Is( input, expected, description )
    let l:got = SidInvoke(s:SID, printf("IsWildcardPathPattern('%s')", a:input))
    call vimtap#Is(l:got, a:expected, a:description)
endfunction
Sign up to request clarification or add additional context in comments.

4 Comments

I was headed in that direction, but I'm hesitant to up-scope things for testing. I know it doesn't really matter, but I like to keep the namespaces as meaningful as Vim allows. I like the idea of parsing it out of :scriptnames to avoid injecting test code into production. Still open to other suggestions.
I have been trying to replace my s:MyFunction() with MyPlugin#MyFunction(), but then a call to it in a mapping in the same file seems not to be working. Am I missing something here?
@Sathors: MyPlugin must be the exact name of your plugin, of course.
@IngoKarkat, can you share the implementation of Sid() and SidInvoke(), please?
2

Use this to get the script number:

let sid = matchlist(execute('scriptnames'), '\([0-9]\+\): [^ ]*autoload/my_vimscript.vim')[1]

and this to invoke a script-local function (s:Test() in this example)

execute("call <snr>" . sid . "_Test()")

1 Comment

Will this return the value from *_Test() function? I'm using Vader and I want to test the return value of an local function

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.