Skip to content
This repository
tree: c0e6a85a56
Fetching contributors…

Octocat-spinner-32-eaf2f5

Cannot retrieve contributors at this time

file 248 lines (221 sloc) 9.445 kb
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247
require 'active_support/core_ext/class/attribute'
require 'active_support/core_ext/module/delegation'
require 'active_support/core_ext/hash/reverse_merge'
require 'active_support/core_ext/kernel/reporting'
require 'rails/generators'
require 'fileutils'

module Rails
  module Generators
    # Disable color in output. Easier to debug.
    no_color!

    # This class provides a TestCase for testing generators. To setup, you need
    # just to configure the destination and set which generator is being tested:
    #
    # class AppGeneratorTest < Rails::Generators::TestCase
    # tests AppGenerator
    # destination File.expand_path("../tmp", File.dirname(__FILE__))
    # end
    #
    # If you want to ensure your destination root is clean before running each test,
    # you can set a setup callback:
    #
    # class AppGeneratorTest < Rails::Generators::TestCase
    # tests AppGenerator
    # destination File.expand_path("../tmp", File.dirname(__FILE__))
    # setup :prepare_destination
    # end
    #
    class TestCase < ActiveSupport::TestCase
      include FileUtils

      class_attribute :destination_root, :current_path, :generator_class, :default_arguments
      delegate :destination_root, :current_path, :generator_class, :default_arguments, :to => :'self.class'

      # Generators frequently change the current path using +FileUtils.cd+.
      # So we need to store the path at file load and revert back to it after each test.
      self.current_path = File.expand_path(Dir.pwd)
      self.default_arguments = []

      setup :destination_root_is_set?, :ensure_current_path
      teardown :ensure_current_path

      # Sets which generator should be tested:
      #
      # tests AppGenerator
      #
      def self.tests(klass)
        self.generator_class = klass
      end

      # Sets default arguments on generator invocation. This can be overwritten when
      # invoking it.
      #
      # arguments %w(app_name --skip-active-record)
      #
      def self.arguments(array)
        self.default_arguments = array
      end

      # Sets the destination of generator files:
      #
      # destination File.expand_path("../tmp", File.dirname(__FILE__))
      #
      def self.destination(path)
        self.destination_root = path
      end

      # Asserts a given file exists. You need to supply an absolute path or a path relative
      # to the configured destination:
      #
      # assert_file "config/environment.rb"
      #
      # You can also give extra arguments. If the argument is a regexp, it will check if the
      # regular expression matches the given file content. If it's a string, it compares the
      # file with the given string:
      #
      # assert_file "config/environment.rb", /initialize/
      #
      # Finally, when a block is given, it yields the file content:
      #
      # assert_file "app/controller/products_controller.rb" do |controller|
      # assert_instance_method :index, content do |index|
      # assert_match(/Product\.all/, index)
      # end
      # end
      #
      def assert_file(relative, *contents)
        absolute = File.expand_path(relative, destination_root)
        assert File.exists?(absolute), "Expected file #{relative.inspect} to exist, but does not"

        read = File.read(absolute) if block_given? || !contents.empty?
        yield read if block_given?

        contents.each do |content|
          case content
            when String
              assert_equal content, read
            when Regexp
              assert_match content, read
          end
        end
      end
      alias :assert_directory :assert_file

      # Asserts a given file does not exist. You need to supply an absolute path or a
      # path relative to the configured destination:
      #
      # assert_no_file "config/random.rb"
      #
      def assert_no_file(relative)
        absolute = File.expand_path(relative, destination_root)
        assert !File.exists?(absolute), "Expected file #{relative.inspect} to not exist, but does"
      end
      alias :assert_no_directory :assert_no_file

      # Asserts a given migration exists. You need to supply an absolute path or a
      # path relative to the configured destination:
      #
      # assert_migration "db/migrate/create_products.rb"
      #
      # This method manipulates the given path and tries to find any migration which
      # matches the migration name. For example, the call above is converted to:
      #
      # assert_file "db/migrate/003_create_products.rb"
      #
      # Consequently, assert_migration accepts the same arguments has assert_file.
      #
      def assert_migration(relative, *contents, &block)
        file_name = migration_file_name(relative)
        assert file_name, "Expected migration #{relative} to exist, but was not found"
        assert_file file_name, *contents, &block
      end

      # Asserts a given migration does not exist. You need to supply an absolute path or a
      # path relative to the configured destination:
      #
      # assert_no_migration "db/migrate/create_products.rb"
      #
      def assert_no_migration(relative)
        file_name = migration_file_name(relative)
        assert_nil file_name, "Expected migration #{relative} to not exist, but found #{file_name}"
      end

      # Asserts the given class method exists in the given content. This method does not detect
      # class methods inside (class << self), only class methods which starts with "self.".
      # When a block is given, it yields the content of the method.
      #
      # assert_migration "db/migrate/create_products.rb" do |migration|
      # assert_class_method :up, migration do |up|
      # assert_match(/create_table/, up)
      # end
      # end
      #
      def assert_class_method(method, content, &block)
        assert_instance_method "self.#{method}", content, &block
      end

      # Asserts the given method exists in the given content. When a block is given,
      # it yields the content of the method.
      #
      # assert_file "app/controller/products_controller.rb" do |controller|
      # assert_instance_method :index, content do |index|
      # assert_match(/Product\.all/, index)
      # end
      # end
      #
      def assert_instance_method(method, content)
        assert content =~ /def #{method}(\(.+\))?(.*?)\n end/m, "Expected to have method #{method}"
        yield $2.strip if block_given?
      end
      alias :assert_method :assert_instance_method

      # Asserts the given attribute type gets translated to a field type
      # properly:
      #
      # assert_field_type :date, :date_select
      #
      def assert_field_type(attribute_type, field_type)
        assert_equal(field_type, create_generated_attribute(attribute_type).field_type)
      end

      # Asserts the given attribute type gets a proper default value:
      #
      # assert_field_default_value :string, "MyString"
      #
      def assert_field_default_value(attribute_type, value)
        assert_equal(value, create_generated_attribute(attribute_type).default)
      end

      # Runs the generator configured for this class. The first argument is an array like
      # command line arguments:
      #
      # class AppGeneratorTest < Rails::Generators::TestCase
      # tests AppGenerator
      # destination File.expand_path("../tmp", File.dirname(__FILE__))
      # teardown :cleanup_destination_root
      #
      # test "database.yml is not created when skipping Active Record" do
      # run_generator %w(myapp --skip-active-record)
      # assert_no_file "config/database.yml"
      # end
      # end
      #
      # You can provide a configuration hash as second argument. This method returns the output
      # printed by the generator.
      def run_generator(args=self.default_arguments, config={})
        capture(:stdout) { self.generator_class.start(args, config.reverse_merge(:destination_root => destination_root)) }
      end

      # Instantiate the generator.
      def generator(args=self.default_arguments, options={}, config={})
        @generator ||= self.generator_class.new(args, options, config.reverse_merge(:destination_root => destination_root))
      end

      # Create a Rails::Generators::GeneratedAttribute by supplying the
      # attribute type and, optionally, the attribute name:
      #
      # create_generated_attribute(:string, 'name')
      #
      def create_generated_attribute(attribute_type, name = 'test', index = nil)
        Rails::Generators::GeneratedAttribute.parse([name, attribute_type, index].compact.join(':'))
      end

      protected

        def destination_root_is_set? #:nodoc:
          raise "You need to configure your Rails::Generators::TestCase destination root." unless destination_root
        end

        def ensure_current_path #:nodoc:
          cd current_path
        end

        def prepare_destination
          rm_rf(destination_root)
          mkdir_p(destination_root)
        end

        def migration_file_name(relative) #:nodoc:
          absolute = File.expand_path(relative, destination_root)
          dirname, file_name = File.dirname(absolute), File.basename(absolute).sub(/\.rb$/, '')
          Dir.glob("#{dirname}/[0-9]*_*.rb").grep(/\d+_#{file_name}.rb$/).first
        end
    end
  end
end
Something went wrong with that request. Please try again.