# encoding: utf-8

require File.expand_path('../spec_helper', __FILE__)

describe ChildProcess do
  it "returns self when started" do
    process = sleeping_ruby

    expect(process.start).to eq process
    expect(process).to be_alive
  end

  # We can't detect failure to execve() when using posix_spawn() on Linux
  # without waiting for the child to exit with code 127.
  #
  # See e.g. http://repo.or.cz/w/glibc.git/blob/669704fd:/sysdeps/posix/spawni.c#l34
  #
  # We could work around this by doing the PATH search ourselves, but not sure
  # it's worth it.
  it "raises ChildProcess::LaunchError if the process can't be started", :posix_spawn_on_linux => false do
    expect { invalid_process.start }.to raise_error(ChildProcess::LaunchError)
  end

  it 'raises ArgumentError if given a non-string argument' do
    expect { ChildProcess.build(nil, "unlikelytoexist") }.to raise_error(ArgumentError)
    expect { ChildProcess.build("foo", 1) }.to raise_error(ArgumentError)
  end

  it "knows if the process crashed" do
    process = exit_with(1).start
    process.wait

    expect(process).to be_crashed
  end

  it "knows if the process didn't crash" do
    process = exit_with(0).start
    process.wait

    expect(process).to_not be_crashed
  end

  it "can wait for a process to finish" do
    process = exit_with(0).start
    return_value = process.wait

    expect(process).to_not be_alive
    expect(return_value).to eq 0
  end

  it 'ignores #wait if process already finished' do
    process = exit_with(0).start
    sleep 0.01 until process.exited?

    expect(process.wait).to eql 0
  end

  it "escalates if TERM is ignored" do
    process = ignored('TERM').start
    process.stop
    expect(process).to be_exited
  end

  it "accepts a timeout argument to #stop" do
    process = sleeping_ruby.start
    process.stop(exit_timeout)
  end

  it "lets child process inherit the environment of the current process" do
    Tempfile.open("env-spec") do |file|
      with_env('INHERITED' => 'yes') do
        process = write_env(file.path).start
        process.wait
      end

      child_env = eval rewind_and_read(file)
      expect(child_env['INHERITED']).to eql 'yes'
    end
  end

  it "can override env vars only for the current process" do
    Tempfile.open("env-spec") do |file|
      process = write_env(file.path)
      process.environment['CHILD_ONLY'] = '1'
      process.start

      expect(ENV['CHILD_ONLY']).to be_nil

      process.wait

      child_env = eval rewind_and_read(file)
      expect(child_env['CHILD_ONLY']).to eql '1'
    end
  end

  it "inherits the parent's env vars also when some are overridden" do
    Tempfile.open("env-spec") do |file|
      with_env('INHERITED' => 'yes', 'CHILD_ONLY' => 'no') do
        process = write_env(file.path)
        process.environment['CHILD_ONLY'] = 'yes'

        process.start
        process.wait

        child_env = eval rewind_and_read(file)

        expect(child_env['INHERITED']).to eq 'yes'
        expect(child_env['CHILD_ONLY']).to eq 'yes'
      end
    end
  end

  it "can unset env vars" do
    Tempfile.open("env-spec") do |file|
      ENV['CHILDPROCESS_UNSET'] = '1'
      process = write_env(file.path)
      process.environment['CHILDPROCESS_UNSET'] = nil
      process.start

      process.wait

      child_env = eval rewind_and_read(file)
      expect(child_env).to_not have_key('CHILDPROCESS_UNSET')
    end
  end

  it 'does not see env vars unset in parent' do
    Tempfile.open('env-spec') do |file|
      ENV['CHILDPROCESS_UNSET'] = nil
      process = write_env(file.path)
      process.start

      process.wait

      child_env = eval rewind_and_read(file)
      expect(child_env).to_not have_key('CHILDPROCESS_UNSET')
    end
  end


  it "passes arguments to the child" do
    args = ["foo", "bar"]

    Tempfile.open("argv-spec") do |file|
      process = write_argv(file.path, *args).start
      process.wait

      expect(rewind_and_read(file)).to eql args.inspect
    end
  end

  it "lets a detached child live on" do
    pending "how do we spec this?"
    fail
  end

  it "preserves Dir.pwd in the child" do
    Tempfile.open("dir-spec-out") do |file|
      process = ruby("print Dir.pwd")
      process.io.stdout = process.io.stderr = file

      expected_dir = nil
      Dir.chdir(Dir.tmpdir) do
        expected_dir = Dir.pwd
        process.start
      end

      process.wait

      expect(rewind_and_read(file)).to eq expected_dir
    end
  end

  it "can handle whitespace, special characters and quotes in arguments" do
    args = ["foo bar", 'foo\bar', "'i-am-quoted'", '"i am double quoted"']

    Tempfile.open("argv-spec") do |file|
      process = write_argv(file.path, *args).start
      process.wait

      expect(rewind_and_read(file)).to eq args.inspect
    end
  end

  it 'handles whitespace in the executable name' do
    path = File.expand_path('foo bar')

    with_executable_at(path) do |proc|
      expect(proc.start).to eq proc
      expect(proc).to be_alive
    end
  end

  it "times out when polling for exit" do
    process = sleeping_ruby.start
    expect { process.poll_for_exit(0.1) }.to raise_error(ChildProcess::TimeoutError)
  end

  it "can change working directory" do
    process = ruby "print Dir.pwd"

    with_tmpdir { |dir|
      process.cwd = dir

      orig_pwd = Dir.pwd

      Tempfile.open('cwd') do |file|
        process.io.stdout = file

        process.start
        process.wait

        expect(rewind_and_read(file)).to eq dir
      end

      expect(Dir.pwd).to eq orig_pwd
    }
  end

  it 'kills the full process tree', :process_builder => false do
    Tempfile.open('kill-process-tree') do |file|
      process = write_pid_in_sleepy_grand_child(file.path)
      process.leader = true
      process.start

      pid = wait_until(30) do
        Integer(rewind_and_read(file)) rescue nil
      end

      process.stop
      wait_until(3) { expect(alive?(pid)).to eql(false) }
    end
  end

  it 'releases the GIL while waiting for the process' do
    time = Time.now
    threads = []

    threads << Thread.new { sleeping_ruby(1).start.wait }
    threads << Thread.new(time) { expect(Time.now - time).to be < 0.5 }

    threads.each { |t| t.join }
  end
end
