Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cleaning up unused cassettes #283

Closed
axsuul opened this issue Mar 19, 2013 · 14 comments
Closed

Cleaning up unused cassettes #283

axsuul opened this issue Mar 19, 2013 · 14 comments

Comments

@axsuul
Copy link

axsuul commented Mar 19, 2013

Anyone know of a good way to clean up cassettes that are not being used in any specs? Often, I find myself renaming cassettes due to refactoring of specs and lose track of which ones are no longer in use.

@nbibler
Copy link
Collaborator

nbibler commented Apr 14, 2013

Good question. Personally, I explicitly set the name of my cassettes when I use them so that spec-nesting or verbiage changes of spec descriptions don't affect them. As far as I know, the library does not provide a means to identify unused cassettes. It would probably be rather difficult to ascertain that information in any reliable way, since it depends on whether or not you ran your entire suite and whether any conditionals (or RSpec filters) might have been in play during the previous run which would affect the loaded/used cassettes.

That being said, you can do it yourself with something like this:

# In spec_helper.rb, for example:

require 'set'
USED_CASSETTES = Set.new

module CassetteReporter
  def insert_cassette(name, options = {})
    USED_CASSETTES << VCR::Cassette.new(name, options).file
    super
  end
end
VCR.extend(CassetteReporter)

RSpec.configure do |config|
  config.after(:all) {
    puts (Dir['spec/cassettes/**/*.yml'].map { |d| File.expand_path(d) } - USED_CASSETTES.to_a).inspect
  }
end

And then run your entire suite. That would output a list of all cassettes which were defined in your spec/cassettes directory but were never loaded by your spec suite.

Does that help?

@myronmarston
Copy link
Member

@nbibler's suggestion above is a good one.

Closing, since there's nothing actionable to do here.

@axsuul
Copy link
Author

axsuul commented Sep 23, 2013

@nbibler Great, thanks for the suggestion :) That looks wonderful

@dmitry
Copy link

dmitry commented Jan 13, 2014

Maybe it should be formed as a rake task or a module, that can be easily included into any project without copy-pasting?

@douglascamata
Copy link

Can't get this to work. Do anyone have a new solution?

@dmitry
Copy link

dmitry commented Oct 15, 2014

Little modified solution: https://gist.github.com/dmitry/f4c7e4dbcf89673f5ede

@douglascamata
Copy link

@dmitry can you confirm this works in VCR latest version, please?

@dideler
Copy link

dideler commented Nov 25, 2014

@douglascamata yes it works, but you might have to change the directory it searches for cassettes. Note that @dmitry's version deletes the files for you.

Here's my version: https://gist.github.com/dideler/062a6d6722e137db55b2

@fatuhoku
Copy link

fatuhoku commented Apr 4, 2017

I sometimes run subsets of tests — I wonder how we could improve the script to handle these cases rather than output the whole list of 'unused' cassettes when in fact the tests have simply not been chosen to run?

@BookOfGreg
Copy link

BookOfGreg commented Jul 13, 2018

Slightly more general version of @dideler 's solution here.
This one finds all cassettes in subfolders of fixtures, and uses the VCR setting for cassette directory so no need to input it.

require 'set'
USED_CASSETTES = Set.new

RSpec.configure do |config|
  config.after(:suite) do
    cassettes = Dir[File.join(VCR.configuration.cassette_library_dir, '**/*.yml')].map { |d| File.expand_path(d) } - USED_CASSETTES.to_a
    if cassettes.any?
      puts "\nUnused cassettes:"
      puts cassettes.map { |f| f.sub(Rails.root.to_s, '') }
    end
  end
end

module CassetteReporter
  def insert_cassette(name, options = {})
    USED_CASSETTES << VCR::Cassette.new(name, options).file
    super
  end
end
VCR.extend(CassetteReporter)

@BenAkroyd
Copy link

Thanks for these ideas! I'm about to implement something similar, It looks as though the posted ideas will only work if the entire test suite is run. It would be great if this could run dependably every time our CI runs, but as we use concurrence, I don't think it would. Any ideas on how to make a solution work in that situation?

@dmitry
Copy link

dmitry commented Aug 12, 2019

@BenAkroyd write everything into the database/file, once all the parallel concurrency specs/tests are finished just combine everything into one array and check. Or you can make a hook on_all_specs_finished where you can get all the written files for the specs (from parallel tests) and then go through all the VCR fixtures. Another approach could be - run all the specs locally (at least once a month it could be done), and remove unused.

@Mekajiki
Copy link

Mekajiki commented Feb 13, 2021

Slightly modified version of @BookOfGreg 's solution.
The changes are;

  • Skip report if the RSpec is invoked to a single file to mute the long list of cassettes used by other specs.
  • Define and register as a formatter of RSpec so that the output under the profile.
class UnusedCassetteFormatter
  def initialize(output)
    @output = output
  end

  def dump_profile(_)
    return if (unused = list_unused).blank?

    print unused
  end

  def self.use(cassette_name, options = {})
    used_cassettes << VCR::Cassette.new(cassette_name, options).file
  end

  def self.used_cassettes
    @used_cassettes ||= Set.new
  end

  private

  def print(unused)
    @output.puts "\nUnused cassettes:"
    unused.each do |f|
      @output.puts f.sub(Rails.root.to_s, "")
    end
  end

  def list_unused
    all = Dir[File.join(VCR.configuration.cassette_library_dir, "**/*.yml")].map do |d|
      File.expand_path(d)
    end

    all - self.class.used_cassettes.to_a
  end
end

RSpec.configure do |config|
  unless config.files_to_run.one?
    config.reporter.register_listener(UnusedCassetteFormatter.new(config.output_stream), :dump_profile)
  end
end

module CassetteReporter
  def insert_cassette(name, options = {})
    UnusedCassetteFormatter.use name, options
    super
  end
end

VCR.extend(CassetteReporter)

@fizvlad
Copy link

fizvlad commented Jun 3, 2023

I've decided to turn the solution above into custom RSpec formatter, so it would be possible to do rspec --format VCRFormatter:

require 'rspec/core/formatters/base_text_formatter'

class VCRFormatter < RSpec::Core::Formatters::BaseTextFormatter
  # Track list of used cassettes
  module CassetteTracker
    def self.use(cassette_name, options = {})
      used_cassettes << VCR::Cassette.new(cassette_name, options).file
    end

    def self.used_cassettes
      @used_cassettes ||= Set.new
    end

    def self.spec(group)
      folders << group.described_class.to_s.gsub('::', '_')
    end

    def self.folders
      @folders ||= Set.new
    end

    def self.unused_cassettes
      Dir.glob("#{VCR.configuration.cassette_library_dir}/**/*.yml").filter_map do |full_path|
        next if used_cassettes.include?(full_path)

        path = full_path.delete_prefix("#{VCR.configuration.cassette_library_dir}/")
        folder = path.split('/', 2).first
        next unless folders.include?(folder)

        path
      end
    end

    def self.unused_folders
      Dir.glob("#{VCR.configuration.cassette_library_dir}/*/").filter_map do |full_path|
        path = full_path.delete_prefix("#{VCR.configuration.cassette_library_dir}/")
        folder = path.delete_suffix('/')
        next if folders.include?(folder)

        path
      end
    end
  end

  # Extension of VCR to notify about cassettes
  module CassetteReporter
    def insert_cassette(name, options = {})
      CassetteTracker.use(name, options)
      super
    end
  end

  def start(_notification)
    VCR.extend(CassetteReporter)
  end

  def example_group_started(notification)
    CassetteTracker.spec(notification.group)
  end

  def dump_summary(_notification)
    output.puts '', 'List of unused cassettes in visited folders:'
    CassetteTracker.unused_cassettes.each { |c| output.puts " - #{c}" }
    output.puts '', 'List of unvisited folders:'
    CassetteTracker.unused_folders.each { |c| output.puts " - #{c}" }
  end
end

RSpec::Core::Formatters.register VCRFormatter

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests