require "../../../spec_helper"

describe Backtracer::Backtrace::Frame::Parser do
  describe ".parse" do
    it "fails to parse an empty string" do
      expect_raises(ArgumentError) { with_frame("", &.itself) }
    end

    context "when --no-debug flag is set" do
      it "parses frame with any value as method" do
        backtrace_line = "__crystal_main"

        with_frame(backtrace_line) do |frame|
          frame.lineno.should be_nil
          frame.column.should be_nil
          frame.method.should eq(backtrace_line)
          frame.path.should be_nil
          frame.relative_path.should be_nil
          frame.under_src_path?.should be_false
          frame.shard_name.should be_nil
          frame.in_app?.should be_false
        end
      end
    end

    context "with ~proc signature" do
      it "parses absolute path outside of src/ dir" do
        path = "/usr/local/Cellar/crystal/0.27.2/src/fiber.cr"
        backtrace_line = "~proc2Proc(Fiber, (IO::FileDescriptor | Nil))@#{path}:72"

        with_frame(backtrace_line) do |frame|
          frame.lineno.should eq(72)
          frame.column.should be_nil
          frame.method.should eq("~proc2Proc(Fiber, (IO::FileDescriptor | Nil))")
          frame.path.should eq(path)
          frame.absolute_path.should eq(frame.path)
          frame.relative_path.should be_nil
          frame.under_src_path?.should be_false
          frame.shard_name.should be_nil
          frame.in_app?.should be_false
        end
      end

      it "parses relative path inside of lib/ dir" do
        with_configuration do |configuration|
          path = "lib/kemal/src/kemal/route.cr"
          backtrace_line = "~procProc(HTTP::Server::Context, String)@#{path}:11"

          with_frame(backtrace_line) do |frame|
            frame.lineno.should eq(11)
            frame.column.should be_nil
            frame.method.should eq("~procProc(HTTP::Server::Context, String)")
            frame.path.should eq(path)
            frame.absolute_path.should eq(
              File.join(configuration.src_path.not_nil!, path)
            )
            frame.relative_path.should eq(frame.path)
            frame.under_src_path?.should be_false
            frame.shard_name.should eq("kemal")
            frame.in_app?.should be_false
          end
        end
      end
    end

    it "parses absolute path outside of configuration.src_path" do
      path = "/some/absolute/path/to/foo.cr"

      with_foo_frame(path: path) do |frame|
        frame.lineno.should eq(1)
        frame.column.should eq(7)
        frame.method.should eq("foo_bar?")
        frame.path.should eq(path)
        frame.absolute_path.should eq(frame.path)
        frame.relative_path.should be_nil
        frame.under_src_path?.should be_false
        frame.shard_name.should be_nil
        frame.in_app?.should be_false
      end
    end

    context "with in_app? = false" do
      it "parses absolute path outside of src/ dir" do
        with_foo_frame(path: "#{__DIR__}/foo.cr") do |frame|
          frame.lineno.should eq(1)
          frame.column.should eq(7)
          frame.method.should eq("foo_bar?")
          frame.path.should eq("#{__DIR__}/foo.cr")
          frame.absolute_path.should eq(frame.path)
          frame.relative_path.should eq("spec/backtracer/backtrace/frame/foo.cr")
          frame.under_src_path?.should be_true
          frame.shard_name.should be_nil
          frame.in_app?.should be_false
        end
      end

      it "parses relative path outside of src/ dir" do
        with_configuration do |configuration|
          path = "some/relative/path/to/foo.cr"

          with_foo_frame(path: path) do |frame|
            frame.lineno.should eq(1)
            frame.column.should eq(7)
            frame.method.should eq("foo_bar?")
            frame.path.should eq(path)
            frame.absolute_path.should eq(
              File.join(configuration.src_path.not_nil!, path)
            )
            frame.relative_path.should eq(frame.path)
            frame.under_src_path?.should be_false
            frame.shard_name.should be_nil
            frame.in_app?.should be_false
          end
        end
      end
    end

    context "with in_app? = true" do
      it "parses absolute path inside of src/ dir" do
        src_path = File.expand_path("../../../../src", __DIR__)
        path = "#{src_path}/foo.cr"

        with_foo_frame(path: path) do |frame|
          frame.lineno.should eq(1)
          frame.column.should eq(7)
          frame.method.should eq("foo_bar?")
          frame.path.should eq(path)
          frame.absolute_path.should eq(frame.path)
          frame.relative_path.should eq("src/foo.cr")
          frame.under_src_path?.should be_true
          frame.shard_name.should be_nil
          frame.in_app?.should be_true
        end
      end

      it "parses relative path inside of src/ dir" do
        with_configuration do |configuration|
          path = "src/foo.cr"

          with_foo_frame(path: path) do |frame|
            frame.lineno.should eq(1)
            frame.column.should eq(7)
            frame.method.should eq("foo_bar?")
            frame.path.should eq(path)
            frame.absolute_path.should eq(
              File.join(configuration.src_path.not_nil!, path)
            )
            frame.relative_path.should eq(path)
            frame.under_src_path?.should be_false
            frame.shard_name.should be_nil
            frame.in_app?.should be_true
          end
        end
      end
    end

    context "with shard path" do
      it "parses absolute path inside of lib/ dir" do
        lib_path = File.expand_path("../../../../lib/bar", __DIR__)
        path = "#{lib_path}/src/bar.cr"

        with_foo_frame(path: path) do |frame|
          frame.lineno.should eq(1)
          frame.column.should eq(7)
          frame.method.should eq("foo_bar?")
          frame.path.should eq(path)
          frame.absolute_path.should eq(frame.path)
          frame.relative_path.should eq("lib/bar/src/bar.cr")
          frame.under_src_path?.should be_true
          frame.shard_name.should eq "bar"
          frame.in_app?.should be_false
        end
      end

      it "parses relative path inside of lib/ dir" do
        with_configuration do |configuration|
          path = "lib/bar/src/bar.cr"

          with_foo_frame(path: path) do |frame|
            frame.lineno.should eq(1)
            frame.column.should eq(7)
            frame.method.should eq("foo_bar?")
            frame.path.should eq(path)
            frame.absolute_path.should eq(
              File.join(configuration.src_path.not_nil!, path)
            )
            frame.relative_path.should eq(path)
            frame.under_src_path?.should be_false
            frame.shard_name.should eq "bar"
            frame.in_app?.should be_false
          end
        end
      end

      it "uses only folders for shard names" do
        with_foo_frame(path: "lib/bar.cr") do |frame|
          frame.shard_name.should be_nil
        end
      end
    end
  end
end
