test/test_trace.rb
# frozen_string_literal: true

__END__

require_relative 'helper'
require 'polyphony/core/debug'

class TraceTest < MiniTest::Test
  def test_tracing_enabled
    events = []

    Thread.backend.trace_proc = proc { |*e|
      case e[0]
      when :schedule
        e = e[0..3]
      when :block
        e = e[0..1]
      when :unblock
        e = e[0..2]
      end
      events << e
    }
    snooze

    assert_equal [
      [:schedule, Fiber.current, nil, false],
      [:block, Fiber.current],
      [:unblock, Fiber.current, nil]
    ], events
  ensure
    Thread.backend.trace_proc = nil
  end

  def test_2_fiber_trace
    events = []
    Thread.backend.trace_proc = proc { |*e|
      case e[0]
      when :schedule
        e = e[0..3]
      when :spin, :block
        e = e[0..1]
      when :unblock
        e = e[0..2]
      end
      events << e
    }

    f = spin { sleep 0; :byebye }
    l0 = __LINE__ + 1
    suspend
    sleep 0

    Thread.backend.trace_proc = nil

    assert_equal [
      [:spin, f],
      [:schedule, f, nil, false],
      [:block, Fiber.current],
      [:unblock, f, nil],
      [:block, f],
      [:enter_poll, f],
      [:schedule, f, nil, false],
      [:leave_poll, f],
      [:unblock, f, nil],
      [:terminate, f, :byebye],
      [:block, f],
      [:block, Fiber.current],
      [:enter_poll, Fiber.current],
      [:schedule, Fiber.current, nil, false],
      [:leave_poll, Fiber.current],
      [:unblock, Fiber.current, nil]
    ], events
  ensure
    Thread.backend.trace_proc = nil
  end

  def test_event_firehose
    buffer = []
    Polyphony::Trace.start_event_firehose { |e| buffer << e }

    f1 = spin(:f1) do
      receive
    end

    f1 << :foo
    f1.await

    Thread.backend.trace_proc = nil

    buffer.each { |e| e.delete(:stamp); e.delete(:caller) }

    main = Fiber.current
    assert_equal(
      [
        { event: :spin,       fiber: f1,                  source_fiber: main  },
        { event: :schedule,   fiber: f1,    value: nil,   source_fiber: main  },
        { event: :block,      fiber: main                                     },
        { event: :unblock,    fiber: f1,    value: nil                        },
        { event: :schedule,   fiber: f1,    value: nil,   source_fiber: f1    },
        { event: :block,      fiber: f1,                                      },
        { event: :enter_poll                                                  },
        { event: :leave_poll                                                  },
        { event: :unblock,    fiber: f1,    value: nil                        },
        { event: :terminate,  fiber: f1,    value: :foo                       },
        { event: :schedule,   fiber: main,  value: nil,   source_fiber: f1    },
        { event: :block,      fiber: f1                                       },
        { event: :unblock,    fiber: main,  value: nil                        }
      ], buffer
    )
  ensure
    Thread.backend.trace_proc = nil
  end

  def test_event_firehose_with_io
    r, w = IO.pipe
    Polyphony::Trace.start_event_firehose(w)

    f1 = spin(:f1) do
      receive
    end

    f1 << :foo
    f1.await

    Thread.backend.trace_proc = nil
    w.close

    log = r.read
    assert_equal 13, log.lines.size

    # TODO: make sure log is formatted correctly
  ensure
    Thread.backend.trace_proc = nil
  end

  def test_event_firehose_with_threaded_receiver
    buffer = []
    this = Fiber.current
    receiver = Thread.new {
      this << :ok
      loop {
        e = receive
        break if e == :stop
        buffer << e
      }
    }
    receive

    Polyphony::Trace.start_event_firehose { |e| receiver << e }

    f1 = spin(:f1) do
      receive
    end

    f1 << :foo
    f1.await

    Thread.backend.trace_proc = nil
    receiver << :stop
    receiver.await

    buffer.each { |e| e.delete(:stamp); e.delete(:caller) }

    main = Fiber.current
    assert_equal(
      [
        { event: :spin,       fiber: f1,                  source_fiber: main  },
        { event: :schedule,   fiber: f1,    value: nil,   source_fiber: main  },
        { event: :block,      fiber: main                                     },
        { event: :unblock,    fiber: f1,    value: nil                        },
        { event: :schedule,   fiber: f1,    value: nil,   source_fiber: f1    },
        { event: :block,      fiber: f1,                                      },
        { event: :enter_poll                                                  },
        { event: :leave_poll                                                  },
        { event: :unblock,    fiber: f1,    value: nil                        },
        { event: :terminate,  fiber: f1,    value: :foo                       },
        { event: :schedule,   fiber: main,  value: nil,   source_fiber: f1    },
        { event: :block,      fiber: f1                                       },
        { event: :unblock,    fiber: main,  value: nil                        }
      ], buffer
    )
  ensure
    Thread.backend.trace_proc = nil
  end

  # def test_event_firehose_with_reentrancy
  #   buffer = []
  #   Polyphony::Trace.start_event_firehose { |e| buffer << e }

  #   f1 = spin(:f1) do
  #     receive
  #   end

  #   f1 << :foo
  #   f1.await

  #   Thread.backend.trace_proc = nil
  #   buffer.each { |e| e.delete(:stamp); e.delete(:caller) }

  #   main = Fiber.current
  #   assert_equal(
  #     [
  #       { event: :spin,       fiber: f1,                  source_fiber: main  },
  #       { event: :schedule,   fiber: f1,    value: nil,   source_fiber: main  },
  #       { event: :block,      fiber: main                                     },
  #       { event: :unblock,    fiber: f1,    value: nil                        },
  #       { event: :schedule,   fiber: f1,    value: nil,   source_fiber: f1    },
  #       { event: :block,      fiber: f1,                                      },
  #       { event: :enter_poll                                                  },
  #       { event: :leave_poll                                                  },
  #       { event: :unblock,    fiber: f1,    value: nil                        },
  #       { event: :terminate,  fiber: f1,    value: :foo                       },
  #       { event: :schedule,   fiber: main,  value: nil,   source_fiber: f1    },
  #       { event: :block,      fiber: f1                                       },
  #       { event: :unblock,    fiber: main,  value: nil                        }
  #     ], buffer
  #   )
  # ensure
  #   Thread.backend.trace_proc = nil
  # end

end
