AndrewRadev / vimrunner

Control a vim instance through ruby code
MIT License
238 stars 13 forks source link

Provide a vimrunner/rspec helper #14

Closed mudge closed 11 years ago

mudge commented 11 years ago

As previously discussed at https://gist.github.com/mudge/4971520, provide a helper that can be used in conjunction with RSpec to remove boilerplate when test-driving Vim script.

Ideally, it should:

AndrewRadev commented 11 years ago

I implemented a first draft of this in the rspec-integration branch. I simply copied the gist and tweaked it a bit. Notable changes:

This results in client code that would look like this:

require 'vimrunner'
require 'vimrunner/rspec'

Vimrunner::RSpec.configure do |config|
  config.reuse_server = true

  config.after_start = lambda do |vim|
    vim.add_plugin(File.expand_path('.'), 'plugin/myplugin.vim')
  end
end

A few directions we could go from here:

mudge commented 11 years ago

We should definitely make it more natural to specify after_start callbacks, perhaps even with:

module Vimrunner
  module RSpec
    class Configuration < OpenStruct
      def after_start(&blk)
        self.after_start_callback = blk
      end
    end
  end
end
AndrewRadev commented 11 years ago

Yeah, that seems quite good. I pushed a commit with that change, although I didn't inherit from OpenStruct, since I think it might be better to have the accessors easily visible from the class definition.

Another problem I'm seeing is the :filesystem => true check to limit execution only to specifically tagged tests. This sounded really good to me, but when I ran the splitjoin tests, I got a lot of leftover files in the directory :). I don't really have any use case in which I'd want to not create a temporary file and edit it, so to me, this is definitely the more common case. More importantly, if the user forgets the group, the results could be fairly dangerous -- files might be created and deleted at the root of the project. Can you think of a way to opt out instead of opting in?

mudge commented 11 years ago

Mmm, we should look into why the :filesystem => true filter didn't run: it just takes advantage of RSpec 2's Filter Hooks.

Hopefully I can be more helpful after Tuesday!

AndrewRadev commented 11 years ago

I know why it didn't run -- I forgot to put a :type => :filesystem tag on the it clauses :). That's my point, that it's possible to forget to do it for a test which uses the filesystem, and that can be annoying and maybe even slightly dangerous.

Hopefully I can be more helpful after Tuesday!

No rush :). Good luck with the talk.

mudge commented 11 years ago

The public_send needed for specifying the start_method makes me uneasy: what if we provided a single hook to start and return a Vimrunner::Client, thereby combining the after_start and start_method into one block:

Vimrunner::RSpec.configure do |config|

  # By default, this would just be Vimrunner.start
  config.start_vim do
    vim = Vimrunner.start
    vim.add_plugin("~/Desktop/foo.vim", "plugin/foo.vim")

    vim
  end
end

This means that Vimrunner::RSpec::Configuration becomes very dumb indeed: just an object that allows specification of blocks by name. Perhaps something like:

module Vimrunner
  module RSpec
    class Configuration
      attr_accessor :reuse_server

      def start_vim(&block)
        @start_vim_method = block
      end

      def start_vim_method
        @start_vim_method || lambda { Vimrunner.start }
      end
    end

    def self.configuration
      @configuration ||= Configuration.new
    end

    def self.configure
      yield configuration
    end
  end

  module Testing
    class << self
      attr_accessor :instance
    end

    def vim
      Testing.instance ||= Vimrunner::RSpec.configuration.start_vim_method.call
    end
  end
end

Vimrunner::RSpec.configure do |config|
  config.reuse_server = false
end

RSpec.configure do |config|
  config.include(Vimrunner::Testing)

  config.before(:each) do
    unless Vimrunner::RSpec.configuration.reuse_server
      Vimrunner::Testing.instance.kill if Vimrunner::Testing.instance
      Vimrunner::Testing.instance = nil
    end
  end

  config.after(:suite) do
    Vimrunner::Testing.instance.kill if Vimrunner::Testing.instance
  end
end

I've moved some things into Testing as you're right, we needn't couple ourselves too tightly to RSpec.

mudge commented 11 years ago

Also, I agree that the value of Testing is an odd one: it smells like a generic utility class that actually belongs in another object...

AndrewRadev commented 11 years ago

The start_vim call looks much better to me, too. I've pushed your proposed changes. I've also made Vim use a separate directory for each test regardless of the filesystem tag. It's safer that way, since you might easily forget to do it and end up writing files in the main directory of the project. It does seem like a waste for some kinds of tests, but I don't think the overhead is significant enough. If you can figure out a way to enable people to opt-out of the separate-directory-per-test, I'd be okay with that, though.

The only thing I'm still wondering about before merging this into master is the Vimrunner::Testing module. I'm not sure I want to include it by default, since this effectively adds a couple of functions to the tests' namespace, and might lead to conflicts. I'm leaning towards just letting people include Vimrunner::Testing if they want to -- they can take a look at the helpers and decide whether they'll have conflicts or not. Alternatively, we could have a TestingClient instance, which delegates to Client, but also adds some testing-related methods, and we can move out some of the more general-purpose helpers to rspec matchers. Something like:

vim.write

File.read('foo.txt').should match_content <<-EOF
  bar
EOF

I'll think about it some more tomorrow, but let me know if you have any ideas.

AndrewRadev commented 11 years ago

I merged the branch in master, but I'm still having problems with error checking and output capturing... I also need to write some info about this in the README, but I'll tackle that after figuring out the error handling problems. For now, closing this issue.