require "helper"

class TestRegenerator < JekyllUnitTest
  context "The site regenerator" do
    setup do
      FileUtils.rm_rf(source_dir(".jekyll-metadata"))

      @site = fixture_site({
        "collections" => {
          "methods" => {
            "output" => true
          }
        },
        "incremental" => true
      })

      @site.read
      @page = @site.pages.first
      @post = @site.posts.first
      @document = @site.docs_to_write.first
      @asset_file = @site.pages.find(&:asset_file?)
      @regenerator = @site.regenerator
    end

    should "regenerate documents and assets if changed or not in metadata" do
      assert @regenerator.regenerate?(@page)
      assert @regenerator.regenerate?(@post)
      assert @regenerator.regenerate?(@document)
      assert @regenerator.regenerate?(@asset_file)
    end

    should "not regenerate if not changed" do
      # Process files
      @regenerator.regenerate?(@page)
      @regenerator.regenerate?(@post)
      @regenerator.regenerate?(@document)
      @regenerator.regenerate?(@asset_file)

      # we need to create the destinations for these files,
      # because regenerate? checks if the destination exists
      [@page, @post, @document, @asset_file].each do |item|
        next unless item.respond_to?(:destination)
        dest = item.destination(@site.dest)
        FileUtils.mkdir_p(File.dirname(dest))
        FileUtils.touch(dest)
      end
      @regenerator.write_metadata
      @regenerator = Regenerator.new(@site)

      # these should pass, since nothing has changed, and the
      # loop above made sure the designations exist
      assert !@regenerator.regenerate?(@page)
      assert !@regenerator.regenerate?(@post)
      assert !@regenerator.regenerate?(@document)
    end

    should "regenerate if destination missing" do
      # Process files
      @regenerator.regenerate?(@page)
      @regenerator.regenerate?(@post)
      @regenerator.regenerate?(@document)
      @regenerator.regenerate?(@asset_file)

      @regenerator.write_metadata
      @regenerator = Regenerator.new(@site)

      # make sure the files don't actually exist
      [@page, @post, @document, @asset_file].each do |item|
        if item.respond_to?(:destination)
          dest = item.destination(@site.dest)
          File.unlink(dest) if File.exist?(dest)
        end
      end

      # while nothing has changed, the output files were not
      # generated, so they still need to be regenerated
      assert @regenerator.regenerate?(@page)
      assert @regenerator.regenerate?(@post)
      assert @regenerator.regenerate?(@document)
    end

    should "always regenerate asset files" do
      assert @regenerator.regenerate?(@asset_file)
    end

    should "always regenerate objects that don't respond to :path" do
      assert @regenerator.regenerate?(Object.new)
    end
  end

  context "The site regenerator" do
    setup do
      FileUtils.rm_rf(source_dir(".jekyll-metadata"))
      @site = fixture_site({
        "incremental" => true
      })

      @site.read
      @post = @site.posts.first
      @regenerator = @site.regenerator
      @regenerator.regenerate?(@post)

      @layout_path = source_dir("_layouts/default.html")
    end

    teardown do
      File.rename(@layout_path + ".tmp", @layout_path)
    end

    should "handle deleted/nonexistent dependencies" do
      assert_equal 1, @regenerator.metadata.size
      path = @regenerator.metadata.keys[0]

      assert_exist @layout_path
      @regenerator.add_dependency(path, @layout_path)

      File.rename(@layout_path, @layout_path + ".tmp")
      refute File.exist?(@layout_path)

      @regenerator.clear_cache
      assert @regenerator.regenerate?(@post)
    end
  end

  context "The site metadata" do
    setup do
      FileUtils.rm_rf(source_dir(".jekyll-metadata"))

      @site = Site.new(Jekyll.configuration({
        "source"      => source_dir,
        "destination" => dest_dir,
        "incremental" => true
      }))

      @site.process
      @path = @site.in_source_dir(@site.pages.first.path)
      @regenerator = @site.regenerator
    end

    should "store modification times" do
      assert_equal File.mtime(@path), @regenerator.metadata[@path]["mtime"]
    end

    should "cache processed entries" do
      assert @regenerator.cache[@path]
    end

    should "clear the cache on clear_cache" do
      # @path will be in the cache because the
      # site will have processed it
      assert @regenerator.cache[@path]

      @regenerator.clear_cache
      assert_equal @regenerator.cache, {}
    end

    should "write to the metadata file" do
      @regenerator.clear
      @regenerator.add(@path)
      @regenerator.write_metadata
      assert File.file?(source_dir(".jekyll-metadata"))
    end

    should "read from the metadata file" do
      @regenerator = Regenerator.new(@site)
      assert_equal File.mtime(@path), @regenerator.metadata[@path]["mtime"]
    end

    should "read legacy YAML metadata" do
      metadata_file = source_dir(".jekyll-metadata")
      @regenerator = Regenerator.new(@site)

      File.open(metadata_file, "w") do |f|
        f.write(@regenerator.metadata.to_yaml)
      end

      @regenerator = Regenerator.new(@site)
      assert_equal File.mtime(@path), @regenerator.metadata[@path]["mtime"]
    end

    should "not crash when reading corrupted marshal file" do
      metadata_file = source_dir(".jekyll-metadata")
      File.open(metadata_file, "w") do |file|
        file.puts Marshal.dump({ :foo => "bar" })[0, 5]
      end

      @regenerator = Regenerator.new(@site)
      assert_equal({}, @regenerator.metadata)
    end

    # Methods

    should "be able to add a path to the metadata" do
      @regenerator.clear
      @regenerator.add(@path)
      assert_equal File.mtime(@path), @regenerator.metadata[@path]["mtime"]
      assert_equal [], @regenerator.metadata[@path]["deps"]
      assert @regenerator.cache[@path]
    end

    should "return true on nonexistent path" do
      @regenerator.clear
      assert @regenerator.add("/bogus/path.md")
      assert @regenerator.modified?("/bogus/path.md")
    end

    should "be able to force a path to regenerate" do
      @regenerator.clear
      @regenerator.force(@path)
      assert @regenerator.cache[@path]
      assert @regenerator.modified?(@path)
    end

    should "be able to clear metadata and cache" do
      @regenerator.clear
      @regenerator.add(@path)
      assert_equal 1, @regenerator.metadata.length
      assert_equal 1, @regenerator.cache.length
      @regenerator.clear
      assert_equal 0, @regenerator.metadata.length
      assert_equal 0, @regenerator.cache.length
    end

    should "not regenerate a path if it is not modified" do
      @regenerator.clear
      @regenerator.add(@path)
      @regenerator.write_metadata
      @regenerator = Regenerator.new(@site)

      assert !@regenerator.modified?(@path)
    end

    should "not regenerate if path in cache is false" do
      @regenerator.clear
      @regenerator.add(@path)
      @regenerator.write_metadata
      @regenerator = Regenerator.new(@site)

      assert !@regenerator.modified?(@path)
      assert !@regenerator.cache[@path]
      assert !@regenerator.modified?(@path)
    end

    should "regenerate if path in not in metadata" do
      @regenerator.clear
      @regenerator.add(@path)

      assert @regenerator.modified?(@path)
    end

    should "regenerate if path in cache is true" do
      @regenerator.clear
      @regenerator.add(@path)

      assert @regenerator.modified?(@path)
      assert @regenerator.cache[@path]
      assert @regenerator.modified?(@path)
    end

    should "regenerate if file is modified" do
      @regenerator.clear
      @regenerator.add(@path)
      @regenerator.metadata[@path]["mtime"] = Time.at(0)
      @regenerator.write_metadata
      @regenerator = Regenerator.new(@site)

      refute_same File.mtime(@path), @regenerator.metadata[@path]["mtime"]
      assert @regenerator.modified?(@path)
    end

    should "regenerate if dependency is modified" do
      @regenerator.clear
      @regenerator.add(@path)
      @regenerator.write_metadata
      @regenerator = Regenerator.new(@site)

      @regenerator.add_dependency(@path, "new.dependency")
      assert_equal ["new.dependency"], @regenerator.metadata[@path]["deps"]
      assert @regenerator.modified?("new.dependency")
      assert @regenerator.modified?(@path)
    end

    should "not regenerate again if multiple dependencies" do
      multi_deps = @regenerator.metadata.select { |_k, v| v["deps"].length > 2 }
      multi_dep_path = multi_deps.keys.first

      assert @regenerator.metadata[multi_dep_path]["deps"].length > 2

      assert @regenerator.modified?(multi_dep_path)

      @site.process
      @regenerator.clear_cache

      refute @regenerator.modified?(multi_dep_path)
    end

    should "regenerate everything if metadata is disabled" do
      @site.config["incremental"] = false
      @regenerator.clear
      @regenerator.add(@path)
      @regenerator.write_metadata
      @regenerator = Regenerator.new(@site)

      assert @regenerator.modified?(@path)
    end
  end

  context "when incremental regeneration is disabled" do
    setup do
      FileUtils.rm_rf(source_dir(".jekyll-metadata"))
      @site = Site.new(Jekyll.configuration({
        "source"      => source_dir,
        "destination" => dest_dir,
        "incremental" => false
      }))

      @site.process
      @path = @site.in_source_dir(@site.pages.first.path)
      @regenerator = @site.regenerator
    end

    should "not create .jekyll-metadata" do
      refute File.file?(source_dir(".jekyll-metadata"))
    end
  end
end
