require File.expand_path("../../../base", __FILE__)
require 'vagrant/util/file_mutex'

describe Vagrant::Util::FileMutex do
  include_context "unit"

  let(:temp_dir) { Dir.mktmpdir("vagrant-test-util-mutex_test") }
  let(:mutex_path) { File.join(temp_dir, "test.lock") }
  let(:subject) { described_class.new(mutex_path) }

  after do
    FileUtils.rm_rf(temp_dir)
  end

  it "should create a lock file" do
    subject.lock
    expect(File).to exist(mutex_path)
  end

  it "should create and delete lock file" do
    subject.lock
    expect(File).to exist(mutex_path)
    subject.unlock
    expect(File).to_not exist(mutex_path)
  end

  it "should not raise an error if the lock file does not exist" do
    subject.unlock
    expect(File).to_not exist(mutex_path)
  end

  it "should run a function with a lock" do
    subject.with_lock { true }
    expect(File).to_not exist(mutex_path)
  end

  it "should fail running a function when locked" do
    # create a lock
    subject.lock
    # create a new lock that will run a function
    instance = described_class.new(mutex_path)
    # lock should persist for multiple runs
    expect {instance.with_lock { true }}.
      to raise_error(Vagrant::Errors::VagrantLocked)
    expect {instance.with_lock { true }}.
      to raise_error(Vagrant::Errors::VagrantLocked)
    # mutex should exist until its unlocked
    expect(File).to exist(mutex_path)
    subject.unlock
    expect(File).to_not exist(mutex_path)
  end

  it "should fail running a function within a locked" do
    # create a new lock that will run a function
    instance = described_class.new(mutex_path)
    expect {
      subject.with_lock { instance.with_lock{true} }
    }.to raise_error(Vagrant::Errors::VagrantLocked)
    expect(File).to_not exist(mutex_path)
  end

  it "should delete the lock even when the function fails" do
    expect {
      subject.with_lock { raise Vagrant::Errors::VagrantError.new }
    }.to raise_error(Vagrant::Errors::VagrantError)
    expect(File).to_not exist(mutex_path)
  end

  it "should unlock file before deletion" do
    lock_file = double(:lock_file)
    allow(subject).to receive(:lock_file).and_return(lock_file)
    allow(lock_file).to receive(:flock).and_return(true)

    expect(lock_file).to receive(:flock).with(File::LOCK_UN)
    expect(lock_file).to receive(:close)

    subject.with_lock { true }
  end
end
