From 742b723237866a22e72b68f558f789555a483f99 Mon Sep 17 00:00:00 2001 From: Sijawusz Pur Rahnama Date: Mon, 28 Dec 2020 22:17:23 +0100 Subject: [PATCH 01/23] Add Backtracer.configure --- src/backtracer.cr | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/backtracer.cr b/src/backtracer.cr index e11a616..6d0f507 100644 --- a/src/backtracer.cr +++ b/src/backtracer.cr @@ -1,6 +1,10 @@ module Backtracer class_getter(configuration) { Configuration.new } + def self.configure : Nil + yield configuration + end + def self.parse(backtrace : Array(String) | String, **options) : Backtrace Backtrace::Parser.parse(backtrace, **options) end From 8ad814c7c141097dc645e47b8a37087821250516 Mon Sep 17 00:00:00 2001 From: Sijawusz Pur Rahnama Date: Tue, 29 Dec 2020 18:45:29 +0100 Subject: [PATCH 02/23] Add documentation --- src/backtracer/backtrace/frame.cr | 44 +++++++++++++++++++++++- src/backtracer/backtrace/frame/parser.cr | 8 +++-- src/backtracer/backtrace/parser.cr | 8 +++++ src/backtracer/configuration.cr | 17 +++++++-- 4 files changed, 72 insertions(+), 5 deletions(-) diff --git a/src/backtracer/backtrace/frame.cr b/src/backtracer/backtrace/frame.cr index c7fd0fd..ad9ae5a 100644 --- a/src/backtracer/backtrace/frame.cr +++ b/src/backtracer/backtrace/frame.cr @@ -21,7 +21,7 @@ module Backtracer def_equals_and_hash @method, @path, @lineno, @column - # Reconstructs the frame in a readable fashion + # Reconstructs the frame in a readable fashion. def to_s(io : IO) : Nil io << '`' << @method << '`' if @path @@ -37,11 +37,24 @@ module Backtracer io << ')' end + # Returns `true` if `path` of this frame is within + # the `configuration.src_path`, `false` otherwise. + # + # See `Configuration#src_path` def under_src_path? : Bool return false unless src_path = configuration.src_path !!path.try(&.starts_with?(src_path)) end + # Returns: + # + # - `path` as is, unless it's absolute - i.e. starts with `/` + # - `path` relative to `configuration.src_path` when `under_src_path?` is `true` + # - `nil` otherwise + # + # NOTE: returned path is not required to be `under_src_path?` - see point no. 1 + # + # See `Configuration#src_path` def relative_path : String? return unless path = @path return path unless path.starts_with?('/') @@ -51,6 +64,13 @@ module Backtracer end end + # Returns: + # + # - `path` as is, if it's absolute - i.e. starts with `/` + # - `path` appended to `configuration.src_path` + # - `nil` otherwise + # + # See `Configuration#src_path` def absolute_path : String? return unless path = @path return path if path.starts_with?('/') @@ -59,16 +79,31 @@ module Backtracer end end + # Returns name of the shard from which this frame originated. + # + # See `Configuration#modules_path_pattern` def shard_name : String? relative_path .try(&.match(configuration.modules_path_pattern)) .try(&.["name"]) end + # Returns `true` if this frame originated from the app source code, + # `false` otherwise. + # + # See `Configuration#in_app_pattern` def in_app? : Bool !!(path.try(&.matches?(configuration.in_app_pattern))) end + # Returns a tuple consisting of 3 elements - an array of context lines + # before the `lineno`, line at `lineno`, and an array of context lines + # after the `lineno`. In case of failure it returns `nil`. + # + # Amount of returned context lines is taken from the *context_lines* + # argument if given, or `configuration.context_lines` otherwise. + # + # See `Configuration#context_lines` def context(context_lines : Int32? = nil) : {Array(String), String, Array(String)}? context_lines ||= configuration.context_lines @@ -86,6 +121,13 @@ module Backtracer end end + # Returns hash with context lines, where line numbers are keys and + # the lines itself are values. In case of failure it returns `nil`. + # + # Amount of returned context lines is taken from the *context_lines* + # argument if given, or `configuration.context_lines` otherwise. + # + # See `Configuration#context`, `Configuration#context_lines` def context_hash(context_lines : Int32? = nil) : Hash(Int32, String)? return unless context = self.context(context_lines) return unless lineno = @lineno diff --git a/src/backtracer/backtrace/frame/parser.cr b/src/backtracer/backtrace/frame/parser.cr index f771639..427d79d 100644 --- a/src/backtracer/backtrace/frame/parser.cr +++ b/src/backtracer/backtrace/frame/parser.cr @@ -2,10 +2,13 @@ module Backtracer module Backtrace::Frame::Parser extend self - # Parses a single line of a given backtrace, where *unparsed_line* is + # Parses a single line of a given backtrace, where *line* is # the raw line from `caller` or some backtrace. # - # Returns the parsed backtrace frame on success or `nil` otherwise. + # Accepts options: + # - `configuration`: `Configuration` object - uses `Backtracer.configuration` if `nil` + # + # Returns parsed `Backtrace::Frame` on success or `nil` otherwise. def parse?(line : String, **options) : Backtrace::Frame? return unless Configuration::LINE_PATTERNS.any? &.match(line) @@ -20,6 +23,7 @@ module Backtracer configuration: options[:configuration]? end + # Same as `parse` but raises `ArgumentError` on error. def parse(line : String, **options) : Backtrace::Frame parse?(line, **options) || raise ArgumentError.new("Error parsing line: #{line.inspect}") diff --git a/src/backtracer/backtrace/parser.cr b/src/backtracer/backtrace/parser.cr index ade656e..30d6744 100644 --- a/src/backtracer/backtrace/parser.cr +++ b/src/backtracer/backtrace/parser.cr @@ -2,6 +2,14 @@ module Backtracer module Backtrace::Parser extend self + # Parses *backtrace* (possibly obtained as a return value + # from `caller` or `Exception#backtrace` methods). + # + # Accepts options: + # - `configuration`: `Configuration` object - uses `Backtracer.configuration` if `nil` + # - `filters`: additional line filters - see `Configuration#line_filters` + # + # Returns parsed `Backtrace` object or raises `ArgumentError` otherwise. def parse(backtrace : Array(String), **options) : Backtrace configuration = options[:configuration]? || Backtracer.configuration diff --git a/src/backtracer/configuration.cr b/src/backtracer/configuration.cr index c944188..19fcd47 100644 --- a/src/backtracer/configuration.cr +++ b/src/backtracer/configuration.cr @@ -54,23 +54,36 @@ module Backtracer } # Used in `#in_app_pattern`. + # + # See `Frame#under_src_path?` property src_path : String? = {{ Process::INITIAL_PWD }} # Directories to be recognized as part of your app. e.g. if you # have an `engines` dir at the root of your project, you may want # to set this to something like `/(src|engines)/` + # + # See `Frame#in_app?` property app_dirs_pattern = /src/ - # `Regex` pattern matched against `Backtrace::Frame#file`. + # `Regex` pattern matched against `Backtrace::Frame#path`. + # + # See `Frame#in_app?` property in_app_pattern : Regex { /^(#{src_path}\/)?(#{app_dirs_pattern})/ } # Path pattern matching directories to be recognized as your app modules. # Defaults to standard Shards setup (`lib/shard-name/...`). + # + # See `Frame#shard_name` property modules_path_pattern = /^lib\/(?[^\/]+)\/(?:.+)/ - # Number of lines of code context to capture, or `nil` for none. + # Number of lines of code context to return by default, or `nil` for none. + # + # See `Frame#context` property context_lines : Int32? = 5 + # Array of procs used for filtering backtrace lines before parsing. + # Each filter is expected to return a string, which is then passed + # onto the next filter, or ignored althoghether if `nil` is returned. getter(line_filters) { [ ->(line : String) { line unless line.matches?(IGNORED_LINES_PATTERN) }, From c41fcb5d282699938f9ae34c58d59eb58c6d9f5d Mon Sep 17 00:00:00 2001 From: Sijawusz Pur Rahnama Date: Wed, 30 Dec 2020 22:13:51 +0100 Subject: [PATCH 03/23] Remove redundant Configuration#in_app_pattern --- src/backtracer/backtrace/frame.cr | 4 ++-- src/backtracer/configuration.cr | 9 ++------- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/src/backtracer/backtrace/frame.cr b/src/backtracer/backtrace/frame.cr index ad9ae5a..e99aed2 100644 --- a/src/backtracer/backtrace/frame.cr +++ b/src/backtracer/backtrace/frame.cr @@ -91,9 +91,9 @@ module Backtracer # Returns `true` if this frame originated from the app source code, # `false` otherwise. # - # See `Configuration#in_app_pattern` + # See `Configuration#app_dirs_pattern` def in_app? : Bool - !!(path.try(&.matches?(configuration.in_app_pattern))) + !!(relative_path.try(&.matches?(configuration.app_dirs_pattern))) end # Returns a tuple consisting of 3 elements - an array of context lines diff --git a/src/backtracer/configuration.cr b/src/backtracer/configuration.cr index 19fcd47..39184fe 100644 --- a/src/backtracer/configuration.cr +++ b/src/backtracer/configuration.cr @@ -53,7 +53,7 @@ module Backtracer /^(?.+?)$/, } - # Used in `#in_app_pattern`. + # Path considered as "root" of your project. # # See `Frame#under_src_path?` property src_path : String? = {{ Process::INITIAL_PWD }} @@ -63,12 +63,7 @@ module Backtracer # to set this to something like `/(src|engines)/` # # See `Frame#in_app?` - property app_dirs_pattern = /src/ - - # `Regex` pattern matched against `Backtrace::Frame#path`. - # - # See `Frame#in_app?` - property in_app_pattern : Regex { /^(#{src_path}\/)?(#{app_dirs_pattern})/ } + property app_dirs_pattern = /^src\// # Path pattern matching directories to be recognized as your app modules. # Defaults to standard Shards setup (`lib/shard-name/...`). From 57f5746d6e034e703a76d14d5b46a3d2a9b81c33 Mon Sep 17 00:00:00 2001 From: Sijawusz Pur Rahnama Date: Fri, 1 Jan 2021 16:12:57 +0100 Subject: [PATCH 04/23] Extend specs coverage --- .../backtracer/backtrace/frame/parser_spec.cr | 202 +++++++++++++++++ spec/backtracer/backtrace/frame_spec.cr | 207 +----------------- spec/backtracer/backtrace/parser_spec.cr | 33 +++ spec/backtracer/backtrace_spec.cr | 25 ++- spec/backtracer/configuration_spec.cr | 4 - spec/spec_helper.cr | 34 +++ 6 files changed, 287 insertions(+), 218 deletions(-) create mode 100644 spec/backtracer/backtrace/frame/parser_spec.cr create mode 100644 spec/backtracer/backtrace/parser_spec.cr diff --git a/spec/backtracer/backtrace/frame/parser_spec.cr b/spec/backtracer/backtrace/frame/parser_spec.cr new file mode 100644 index 0000000..7df2f9e --- /dev/null +++ b/spec/backtracer/backtrace/frame/parser_spec.cr @@ -0,0 +1,202 @@ +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 diff --git a/spec/backtracer/backtrace/frame_spec.cr b/spec/backtracer/backtrace/frame_spec.cr index 5f6032c..ac355da 100644 --- a/spec/backtracer/backtrace/frame_spec.cr +++ b/spec/backtracer/backtrace/frame_spec.cr @@ -1,207 +1,6 @@ require "../../spec_helper" -private def parse_frame(line) - Backtracer::Backtrace::Frame::Parser.parse(line) -end - -private def with_frame(method, path = nil, lineno = nil, column = nil) - line = String.build do |io| - if path - io << path - io << ':' << lineno if lineno - io << ':' << column if column - io << " in '" << method << '\'' - else - io << method - end - end - yield parse_frame(line) -end - -private def with_foo_frame( - method = "foo_bar?", - path = "#{__DIR__}/foo.cr", - lineno = 1, - column = 7 -) - with_frame(method, path, lineno, column) do |frame| - yield frame - end -end - describe Backtracer::Backtrace::Frame do - describe ".parse" do - it "fails to parse an empty string" do - expect_raises(ArgumentError) { parse_frame("") } - 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 - backtrace_line = "~proc2Proc(Fiber, (IO::FileDescriptor | Nil))@/usr/local/Cellar/crystal/0.27.2/src/fiber.cr: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("/usr/local/Cellar/crystal/0.27.2/src/fiber.cr") - 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 - backtrace_line = "~procProc(HTTP::Server::Context, String)@lib/kemal/src/kemal/route.cr: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("lib/kemal/src/kemal/route.cr") - frame.relative_path.should eq("lib/kemal/src/kemal/route.cr") - frame.under_src_path?.should be_false - frame.shard_name.should eq("kemal") - frame.in_app?.should be_false - 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.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 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.relative_path.should eq("spec/backtracer/backtrace/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 - 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.relative_path.should eq(path) - frame.under_src_path?.should be_false - frame.shard_name.should be_nil - frame.in_app?.should be_false - 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.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 - 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.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 - - 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.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 - 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.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 - - 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 - it "#inspect" do with_foo_frame do |frame| frame.inspect.should match(/Backtrace::Frame(.*)$/) @@ -209,7 +8,7 @@ describe Backtracer::Backtrace::Frame do end it "#to_s" do - with_foo_frame do |frame| + with_foo_frame(path: "#{__DIR__}/foo.cr") do |frame| frame.to_s.should eq "`foo_bar?` at #{__DIR__}/foo.cr:1:7" end end @@ -219,8 +18,8 @@ describe Backtracer::Backtrace::Frame do with_foo_frame do |frame2| frame.should eq(frame2) end - with_foo_frame(method: "other_method") do |frame2| - frame.should_not eq(frame2) + with_foo_frame(method: "other_method") do |frame3| + frame.should_not eq(frame3) end end end diff --git a/spec/backtracer/backtrace/parser_spec.cr b/spec/backtracer/backtrace/parser_spec.cr new file mode 100644 index 0000000..8ebbe75 --- /dev/null +++ b/spec/backtracer/backtrace/parser_spec.cr @@ -0,0 +1,33 @@ +require "../../spec_helper" + +describe Backtracer::Backtrace::Parser do + describe ".parse" do + it "handles `caller` as an input" do + with_backtrace(caller) do |backtrace| + backtrace.frames.should_not be_empty + + backtrace.frames.first + .tap(&.absolute_path.should eq(__FILE__)) + .tap(&.path.should eq("spec/backtracer/backtrace/parser_spec.cr")) + + backtrace.frames.last.method.should eq("main") + end + end + + it "handles `Exception#backtrace` as an input" do + begin + raise "Oh, no!" + rescue ex + with_backtrace(ex.backtrace) do |backtrace| + backtrace.frames.should_not be_empty + + backtrace.frames.first + .tap(&.absolute_path.should eq(__FILE__)) + .tap(&.path.should eq("spec/backtracer/backtrace/parser_spec.cr")) + + backtrace.frames.last.method.should eq("main") + end + end + end + end +end diff --git a/spec/backtracer/backtrace_spec.cr b/spec/backtracer/backtrace_spec.cr index 36bc340..3948c3e 100644 --- a/spec/backtracer/backtrace_spec.cr +++ b/spec/backtracer/backtrace_spec.cr @@ -1,24 +1,29 @@ require "../spec_helper" describe Backtracer::Backtrace do - backtrace = Backtracer.parse(caller) - it "#frames" do - backtrace.frames.should be_a(Array(Backtracer::Backtrace::Frame)) + with_backtrace(caller) do |backtrace| + backtrace.frames.should be_a(Array(Backtracer::Backtrace::Frame)) + backtrace.frames.should_not be_empty + end end it "#inspect" do - backtrace.inspect.should match(/#$/) + with_backtrace(caller) do |backtrace| + backtrace.inspect.should match(/#$/) + end end - {% unless flag?(:release) || !flag?(:debug) %} - it "#to_s" do - backtrace.to_s.should match(/backtrace_spec.cr:4/) + it "#to_s" do + with_backtrace(caller) do |backtrace| + backtrace.to_s.should match(/backtrace_spec.cr/) end - {% end %} + end it "#==" do - backtrace2 = Backtracer::Backtrace.new(backtrace.frames) - backtrace2.should eq(backtrace) + with_backtrace(caller) do |backtrace| + backtrace2 = Backtracer::Backtrace.new(backtrace.frames) + backtrace2.should eq(backtrace) + end end end diff --git a/spec/backtracer/configuration_spec.cr b/spec/backtracer/configuration_spec.cr index 76284c6..c192860 100644 --- a/spec/backtracer/configuration_spec.cr +++ b/spec/backtracer/configuration_spec.cr @@ -1,9 +1,5 @@ require "../spec_helper" -private def with_configuration - yield Backtracer::Configuration.new -end - describe Backtracer::Configuration do it "should set #src_path to current dir from default" do with_configuration do |configuration| diff --git a/spec/spec_helper.cr b/spec/spec_helper.cr index e0c7f38..11c6c86 100644 --- a/spec/spec_helper.cr +++ b/spec/spec_helper.cr @@ -1,2 +1,36 @@ require "spec" require "../src/backtracer" + +def with_configuration(shared = true) + yield shared ? Backtracer.configuration : Backtracer::Configuration.new +end + +def with_backtrace(backtrace, **options) + yield Backtracer::Backtrace::Parser.parse(backtrace, **options) +end + +def with_frame(method, path = nil, lineno = nil, column = nil, **options) + line = String.build do |io| + if path + io << path + io << ':' << lineno if lineno + io << ':' << column if column + io << " in '" << method << '\'' + else + io << method + end + end + yield Backtracer::Backtrace::Frame::Parser.parse(line, **options) +end + +def with_foo_frame( + method = "foo_bar?", + path = "#{__DIR__}/foo.cr", + lineno = 1, + column = 7, + **options +) + with_frame(method, path, lineno, column, **options) do |frame| + yield frame + end +end From 45b9cefe7356818f441dd734ad87a16901883db4 Mon Sep 17 00:00:00 2001 From: Sijawusz Pur Rahnama Date: Fri, 1 Jan 2021 23:53:55 +0100 Subject: [PATCH 05/23] Remove unreliable specs --- spec/backtracer/backtrace/parser_spec.cr | 12 ------------ spec/backtracer/backtrace_spec.cr | 10 ++++++---- 2 files changed, 6 insertions(+), 16 deletions(-) diff --git a/spec/backtracer/backtrace/parser_spec.cr b/spec/backtracer/backtrace/parser_spec.cr index 8ebbe75..7713983 100644 --- a/spec/backtracer/backtrace/parser_spec.cr +++ b/spec/backtracer/backtrace/parser_spec.cr @@ -5,12 +5,6 @@ describe Backtracer::Backtrace::Parser do it "handles `caller` as an input" do with_backtrace(caller) do |backtrace| backtrace.frames.should_not be_empty - - backtrace.frames.first - .tap(&.absolute_path.should eq(__FILE__)) - .tap(&.path.should eq("spec/backtracer/backtrace/parser_spec.cr")) - - backtrace.frames.last.method.should eq("main") end end @@ -20,12 +14,6 @@ describe Backtracer::Backtrace::Parser do rescue ex with_backtrace(ex.backtrace) do |backtrace| backtrace.frames.should_not be_empty - - backtrace.frames.first - .tap(&.absolute_path.should eq(__FILE__)) - .tap(&.path.should eq("spec/backtracer/backtrace/parser_spec.cr")) - - backtrace.frames.last.method.should eq("main") end end end diff --git a/spec/backtracer/backtrace_spec.cr b/spec/backtracer/backtrace_spec.cr index 3948c3e..c8d58f2 100644 --- a/spec/backtracer/backtrace_spec.cr +++ b/spec/backtracer/backtrace_spec.cr @@ -14,11 +14,13 @@ describe Backtracer::Backtrace do end end - it "#to_s" do - with_backtrace(caller) do |backtrace| - backtrace.to_s.should match(/backtrace_spec.cr/) + {% unless flag?(:release) || !flag?(:debug) %} + it "#to_s" do + with_backtrace(caller) do |backtrace| + backtrace.to_s.should match(/backtrace_spec.cr/) + end end - end + {% end %} it "#==" do with_backtrace(caller) do |backtrace| From 7d7192ec09bb1d9b0b64e7eeb5a4e9c47e348b65 Mon Sep 17 00:00:00 2001 From: Sijawusz Pur Rahnama Date: Fri, 1 Jan 2021 23:53:59 +0100 Subject: [PATCH 06/23] Fix doc comments --- src/backtracer/backtrace/frame/parser.cr | 2 +- src/backtracer/configuration.cr | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/backtracer/backtrace/frame/parser.cr b/src/backtracer/backtrace/frame/parser.cr index 427d79d..37f77ff 100644 --- a/src/backtracer/backtrace/frame/parser.cr +++ b/src/backtracer/backtrace/frame/parser.cr @@ -23,7 +23,7 @@ module Backtracer configuration: options[:configuration]? end - # Same as `parse` but raises `ArgumentError` on error. + # Same as `parse?` but raises `ArgumentError` on error. def parse(line : String, **options) : Backtrace::Frame parse?(line, **options) || raise ArgumentError.new("Error parsing line: #{line.inspect}") diff --git a/src/backtracer/configuration.cr b/src/backtracer/configuration.cr index 39184fe..1b91465 100644 --- a/src/backtracer/configuration.cr +++ b/src/backtracer/configuration.cr @@ -60,7 +60,7 @@ module Backtracer # Directories to be recognized as part of your app. e.g. if you # have an `engines` dir at the root of your project, you may want - # to set this to something like `/(src|engines)/` + # to set this to something like `/^(src|engines)\//` # # See `Frame#in_app?` property app_dirs_pattern = /^src\// From e3ee3a494df98dc9971f22349d881bb5a46010dc Mon Sep 17 00:00:00 2001 From: Sijawusz Pur Rahnama Date: Sat, 2 Jan 2021 19:22:18 +0100 Subject: [PATCH 07/23] Bump to v1.1.0 --- shard.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shard.yml b/shard.yml index 20cf883..3a9956d 100644 --- a/shard.yml +++ b/shard.yml @@ -1,5 +1,5 @@ name: backtracer -version: 1.0.0 +version: 1.1.0 authors: - Sijawusz Pur Rahnama From 0300476813b5e311dd477aeef13e359969f0bff3 Mon Sep 17 00:00:00 2001 From: Sijawusz Pur Rahnama Date: Sun, 3 Jan 2021 17:04:34 +0100 Subject: [PATCH 08/23] Refactor context tuple into a dedicated `Context` struct --- .../backtrace/frame/context_spec.cr | 33 +++++++++++++++ src/backtracer/backtrace/frame.cr | 41 +++++-------------- src/backtracer/backtrace/frame/context.cr | 36 ++++++++++++++++ 3 files changed, 80 insertions(+), 30 deletions(-) create mode 100644 spec/backtracer/backtrace/frame/context_spec.cr create mode 100644 src/backtracer/backtrace/frame/context.cr diff --git a/spec/backtracer/backtrace/frame/context_spec.cr b/spec/backtracer/backtrace/frame/context_spec.cr new file mode 100644 index 0000000..2ee0a82 --- /dev/null +++ b/spec/backtracer/backtrace/frame/context_spec.cr @@ -0,0 +1,33 @@ +require "../../../spec_helper" + +describe Backtracer::Backtrace::Frame::Context do + describe ".to_h" do + it "works with empty #pre and #post" do + context = Backtracer::Backtrace::Frame::Context.new( + lineno: 1, + pre: %w[], + line: "violent offender!", + post: %w[], + ) + context.to_h.should eq({1 => "violent offender!"}) + end + + it "returns hash with #pre, #line and #post strings" do + context = Backtracer::Backtrace::Frame::Context.new( + lineno: 10, + pre: %w[foo bar baz], + line: "violent offender!", + post: %w[boo far faz], + ) + context.to_h.should eq({ + 7 => "foo", + 8 => "bar", + 9 => "baz", + 10 => "violent offender!", + 11 => "boo", + 12 => "far", + 13 => "faz", + }) + end + end +end diff --git a/src/backtracer/backtrace/frame.cr b/src/backtracer/backtrace/frame.cr index e99aed2..cd44cfc 100644 --- a/src/backtracer/backtrace/frame.cr +++ b/src/backtracer/backtrace/frame.cr @@ -96,15 +96,18 @@ module Backtracer !!(relative_path.try(&.matches?(configuration.app_dirs_pattern))) end - # Returns a tuple consisting of 3 elements - an array of context lines + # Returns `Context` record consisting of 3 elements - an array of context lines # before the `lineno`, line at `lineno`, and an array of context lines # after the `lineno`. In case of failure it returns `nil`. # # Amount of returned context lines is taken from the *context_lines* # argument if given, or `configuration.context_lines` otherwise. # + # NOTE: amount of returned context lines might be lower than given + # in cases where `lineno` is near the start or the end of the file. + # # See `Configuration#context_lines` - def context(context_lines : Int32? = nil) : {Array(String), String, Array(String)}? + def context(context_lines : Int32? = nil) : Context? context_lines ||= configuration.context_lines return unless context_lines && (context_lines > 0) @@ -117,35 +120,13 @@ module Backtracer if context_line = lines[lineidx]? pre_context = lines[Math.max(0, lineidx - context_lines), context_lines] post_context = lines[Math.min(lines.size, lineidx + 1), context_lines] - {pre_context, context_line, post_context} - end - end - # Returns hash with context lines, where line numbers are keys and - # the lines itself are values. In case of failure it returns `nil`. - # - # Amount of returned context lines is taken from the *context_lines* - # argument if given, or `configuration.context_lines` otherwise. - # - # See `Configuration#context`, `Configuration#context_lines` - def context_hash(context_lines : Int32? = nil) : Hash(Int32, String)? - return unless context = self.context(context_lines) - return unless lineno = @lineno - - pre_context, context_line, post_context = context - - ({} of Int32 => String).tap do |hash| - pre_context.each_with_index do |code, index| - line = (lineno - pre_context.size) + index - hash[line] = code - end - - hash[lineno] = context_line - - post_context.each_with_index do |code, index| - line = lineno + (index + 1) - hash[line] = code - end + Context.new( + lineno: lineno, + pre: pre_context, + line: context_line, + post: post_context, + ) end end end diff --git a/src/backtracer/backtrace/frame/context.cr b/src/backtracer/backtrace/frame/context.cr new file mode 100644 index 0000000..12a9e1b --- /dev/null +++ b/src/backtracer/backtrace/frame/context.cr @@ -0,0 +1,36 @@ +module Backtracer + struct Backtrace::Frame::Context + # The line number this `Context` refers to. + getter lineno : Int32 + + # An array of lines before `lineno`. + getter pre : Array(String) + + # The line at `lineno`. + getter line : String + + # An array of lines after `lineno`. + getter post : Array(String) + + def initialize(@lineno, @pre, @line, @post) + end + + # Returns hash with context lines, where line numbers are + # the keys and the lines itself are the values. + def to_h : Hash(Int32, String) + ({} of Int32 => String).tap do |hash| + base_index = lineno - pre.size + pre.each_with_index do |code, index| + hash[base_index + index] = code + end + + hash[lineno] = line + + base_index = lineno + 1 + post.each_with_index do |code, index| + hash[base_index + index] = code + end + end + end + end +end From d88a17f3e646cb901dcd6cfd09232f373ad7b052 Mon Sep 17 00:00:00 2001 From: Sijawusz Pur Rahnama Date: Sun, 3 Jan 2021 17:04:59 +0100 Subject: [PATCH 09/23] Cache Frame#context results --- src/backtracer/backtrace/frame.cr | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/src/backtracer/backtrace/frame.cr b/src/backtracer/backtrace/frame.cr index cd44cfc..eea7a47 100644 --- a/src/backtracer/backtrace/frame.cr +++ b/src/backtracer/backtrace/frame.cr @@ -1,6 +1,8 @@ module Backtracer # An object representation of a stack frame. struct Backtrace::Frame + @context_cache = {} of Int32 => Context + # The method of this frame (such as `User.find`). getter method : String @@ -109,8 +111,11 @@ module Backtracer # See `Configuration#context_lines` def context(context_lines : Int32? = nil) : Context? context_lines ||= configuration.context_lines - return unless context_lines && (context_lines > 0) + + cached = @context_cache[context_lines]? + return cached if cached + return unless (lineno = @lineno) && (lineno > 0) return unless (path = @path) && File.readable?(path) @@ -121,12 +126,13 @@ module Backtracer pre_context = lines[Math.max(0, lineidx - context_lines), context_lines] post_context = lines[Math.min(lines.size, lineidx + 1), context_lines] - Context.new( - lineno: lineno, - pre: pre_context, - line: context_line, - post: post_context, - ) + @context_cache[context_lines] = + Context.new( + lineno: lineno, + pre: pre_context, + line: context_line, + post: post_context, + ) end end end From 0663fbfa01b7a91ff3901d91d3f30d87943036d3 Mon Sep 17 00:00:00 2001 From: Sijawusz Pur Rahnama Date: Sun, 3 Jan 2021 17:56:44 +0100 Subject: [PATCH 10/23] Add specs for Frame#context --- spec/backtracer/backtrace/frame_spec.cr | 35 +++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/spec/backtracer/backtrace/frame_spec.cr b/spec/backtracer/backtrace/frame_spec.cr index ac355da..53c3766 100644 --- a/spec/backtracer/backtrace/frame_spec.cr +++ b/spec/backtracer/backtrace/frame_spec.cr @@ -23,4 +23,39 @@ describe Backtracer::Backtrace::Frame do end end end + + {% unless flag?(:release) || !flag?(:debug) %} + describe "#context" do + it "returns proper lines" do + with_configuration do |configuration| + with_backtrace(caller) do |backtrace| + backtrace.frames.first.tap do |first_frame| + context_lines = configuration.context_lines.should_not be_nil + context = first_frame.context.should_not be_nil + + lines = File.read_lines(__FILE__) + lineidx = context.lineno - 1 + + context.pre + .should eq(lines[Math.max(0, lineidx - context_lines), context_lines]?) + context.line + .should eq(lines[lineidx]?) + context.post + .should eq(lines[Math.min(lines.size, lineidx + 1), context_lines]?) + end + end + end + end + + it "returns given amount of lines" do + with_backtrace(caller) do |backtrace| + backtrace.frames.first.tap do |first_frame| + context = first_frame.context(3).should_not be_nil + context.pre.size.should eq(3) + context.post.size.should eq(3) + end + end + end + end + {% end %} end From 521bf7ff28a71c6e015ab7b2fff221e2e0b18cf9 Mon Sep 17 00:00:00 2001 From: Sijawusz Pur Rahnama Date: Mon, 4 Jan 2021 03:24:44 +0100 Subject: [PATCH 11/23] Add Context#to_a --- .../backtrace/frame/context_spec.cr | 57 ++++++++++++++----- src/backtracer/backtrace/frame/context.cr | 10 ++++ 2 files changed, 52 insertions(+), 15 deletions(-) diff --git a/spec/backtracer/backtrace/frame/context_spec.cr b/spec/backtracer/backtrace/frame/context_spec.cr index 2ee0a82..1ec0a74 100644 --- a/spec/backtracer/backtrace/frame/context_spec.cr +++ b/spec/backtracer/backtrace/frame/context_spec.cr @@ -1,6 +1,37 @@ require "../../../spec_helper" +def with_foo_context + yield Backtracer::Backtrace::Frame::Context.new( + lineno: 10, + pre: %w[foo bar baz], + line: "violent offender!", + post: %w[boo far faz], + ) +end + describe Backtracer::Backtrace::Frame::Context do + describe ".to_a" do + it "works with empty #pre and #post" do + context = Backtracer::Backtrace::Frame::Context.new( + lineno: 1, + pre: %w[], + line: "violent offender!", + post: %w[], + ) + context.to_a.should eq(["violent offender!"]) + end + + it "returns array with #pre, #line and #post strings" do + with_foo_context do |context| + context.to_a.should eq([ + "foo", "bar", "baz", + "violent offender!", + "boo", "far", "faz", + ]) + end + end + end + describe ".to_h" do it "works with empty #pre and #post" do context = Backtracer::Backtrace::Frame::Context.new( @@ -13,21 +44,17 @@ describe Backtracer::Backtrace::Frame::Context do end it "returns hash with #pre, #line and #post strings" do - context = Backtracer::Backtrace::Frame::Context.new( - lineno: 10, - pre: %w[foo bar baz], - line: "violent offender!", - post: %w[boo far faz], - ) - context.to_h.should eq({ - 7 => "foo", - 8 => "bar", - 9 => "baz", - 10 => "violent offender!", - 11 => "boo", - 12 => "far", - 13 => "faz", - }) + with_foo_context do |context| + context.to_h.should eq({ + 7 => "foo", + 8 => "bar", + 9 => "baz", + 10 => "violent offender!", + 11 => "boo", + 12 => "far", + 13 => "faz", + }) + end end end end diff --git a/src/backtracer/backtrace/frame/context.cr b/src/backtracer/backtrace/frame/context.cr index 12a9e1b..d5d22aa 100644 --- a/src/backtracer/backtrace/frame/context.cr +++ b/src/backtracer/backtrace/frame/context.cr @@ -15,6 +15,16 @@ module Backtracer def initialize(@lineno, @pre, @line, @post) end + # Returns an array composed of context lines from `pre`, + # `line` and `post`. + def to_a : Array(String) + ([] of String).tap do |ary| + ary.concat(pre) + ary << line + ary.concat(post) + end + end + # Returns hash with context lines, where line numbers are # the keys and the lines itself are the values. def to_h : Hash(Int32, String) From 63ca71ba5648fa0597d62c27169ca96472773fbe Mon Sep 17 00:00:00 2001 From: Sijawusz Pur Rahnama Date: Mon, 4 Jan 2021 03:26:40 +0100 Subject: [PATCH 12/23] Bump to v1.2.0 --- shard.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shard.yml b/shard.yml index 3a9956d..f815ea3 100644 --- a/shard.yml +++ b/shard.yml @@ -1,5 +1,5 @@ name: backtracer -version: 1.1.0 +version: 1.2.0 authors: - Sijawusz Pur Rahnama From 1ff793f00b413903e9e4740dc17198b43248d8a1 Mon Sep 17 00:00:00 2001 From: Sijawusz Pur Rahnama Date: Sun, 10 Jan 2021 14:29:29 +0100 Subject: [PATCH 13/23] Fix edge case where lineno <= context_lines --- src/backtracer/backtrace/frame.cr | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/backtracer/backtrace/frame.cr b/src/backtracer/backtrace/frame.cr index eea7a47..9e0c8a7 100644 --- a/src/backtracer/backtrace/frame.cr +++ b/src/backtracer/backtrace/frame.cr @@ -123,8 +123,11 @@ module Backtracer lineidx = lineno - 1 if context_line = lines[lineidx]? - pre_context = lines[Math.max(0, lineidx - context_lines), context_lines] - post_context = lines[Math.min(lines.size, lineidx + 1), context_lines] + pre_context_lines = + (lineno <= context_lines) ? lineidx : context_lines + + pre_context = lines[(lineidx - context_lines).clamp(0..), pre_context_lines] + post_context = lines[(lineidx + 1).clamp(..lines.size), context_lines] @context_cache[context_lines] = Context.new( From 5e1ea0bd781bb7593aa4bd7e7acaee40aa85af6c Mon Sep 17 00:00:00 2001 From: Sijawusz Pur Rahnama Date: Sun, 10 Jan 2021 15:02:20 +0100 Subject: [PATCH 14/23] Refactor Frame#context to avoid reading whole file at once --- src/backtracer/backtrace/frame.cr | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/src/backtracer/backtrace/frame.cr b/src/backtracer/backtrace/frame.cr index 9e0c8a7..5826422 100644 --- a/src/backtracer/backtrace/frame.cr +++ b/src/backtracer/backtrace/frame.cr @@ -119,16 +119,22 @@ module Backtracer return unless (lineno = @lineno) && (lineno > 0) return unless (path = @path) && File.readable?(path) - lines = File.read_lines(path) - lineidx = lineno - 1 + context_line = nil + pre_context, post_context = %w[], %w[] - if context_line = lines[lineidx]? - pre_context_lines = - (lineno <= context_lines) ? lineidx : context_lines - - pre_context = lines[(lineidx - context_lines).clamp(0..), pre_context_lines] - post_context = lines[(lineidx + 1).clamp(..lines.size), context_lines] + i = 0 + File.each_line(path) do |line| + case i += 1 + when lineno - context_lines...lineno + pre_context << line + when lineno + context_line = line + when lineno + 1..lineno + context_lines + post_context << line + end + end + if context_line @context_cache[context_lines] = Context.new( lineno: lineno, From f18b0546f8d781af630a9ee39c2c2559c44cc9da Mon Sep 17 00:00:00 2001 From: Sijawusz Pur Rahnama Date: Sun, 10 Jan 2021 15:09:49 +0100 Subject: [PATCH 15/23] Bump to v1.2.1 --- shard.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shard.yml b/shard.yml index f815ea3..89b3887 100644 --- a/shard.yml +++ b/shard.yml @@ -1,5 +1,5 @@ name: backtracer -version: 1.2.0 +version: 1.2.1 authors: - Sijawusz Pur Rahnama From 5649a04adf66d7b90acf613817814e120eeed627 Mon Sep 17 00:00:00 2001 From: Sijawusz Pur Rahnama Date: Mon, 1 Mar 2021 14:47:39 +0100 Subject: [PATCH 16/23] Bump ameba dependency --- shard.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shard.yml b/shard.yml index 89b3887..b08af20 100644 --- a/shard.yml +++ b/shard.yml @@ -7,7 +7,7 @@ authors: development_dependencies: ameba: github: crystal-ameba/ameba - version: ~> 0.13.0 + version: ~> 0.14.0 crystal: ">= 0.35.0" From 14f5d77cdd7e656b89c7c0fefe44a1038e1920a8 Mon Sep 17 00:00:00 2001 From: Sijawusz Pur Rahnama Date: Wed, 10 Mar 2021 15:32:36 +0100 Subject: [PATCH 17/23] Migrate from Travis CI to GitHub Actions --- .github/workflows/ci.yml | 46 ++++++++++++++++++++++++++++++++++++++++ .travis.yml | 20 ----------------- README.md | 2 +- 3 files changed, 47 insertions(+), 21 deletions(-) create mode 100644 .github/workflows/ci.yml delete mode 100644 .travis.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..ac9ac2f --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,46 @@ +name: CI + +on: + push: + pull_request: + schedule: + - cron: "0 3 * * 1" # Every monday at 3 AM + +jobs: + test: + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, macos-latest] + crystal: [latest, nightly] + runs-on: ${{ matrix.os }} + + steps: + - name: Install Crystal + uses: oprypin/install-crystal@v1 + with: + crystal: ${{ matrix.crystal }} + + - name: Download source + uses: actions/checkout@v2 + + - name: Install dependencies + run: shards install + env: + SHARDS_OPTS: --ignore-crystal-version + + - name: Run specs + run: | + crystal spec + crystal spec --no-debug + + - name: Run specs (release) + run: | + crystal spec --release + crystal spec --release --no-debug + + - name: Check formatting + run: crystal tool format --check + + - name: Run ameba linter + run: bin/ameba diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 7521934..0000000 --- a/.travis.yml +++ /dev/null @@ -1,20 +0,0 @@ -language: crystal - -crystal: - - latest - - nightly - -jobs: - allow_failures: - - crystal: nightly - -install: - - shards install - -script: - - crystal spec - - crystal spec --no-debug - - crystal spec --release - - crystal spec --release --no-debug - - crystal tool format --check - - bin/ameba diff --git a/README.md b/README.md index 03ca6ae..8d17ade 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# backtracer.cr [![Build Status](https://travis-ci.com/Sija/backtracer.cr.svg?branch=master)](https://travis-ci.com/Sija/backtracer.cr) [![Releases](https://img.shields.io/github/release/Sija/backtracer.cr.svg)](https://github.com/Sija/backtracer.cr/releases) [![License](https://img.shields.io/github/license/Sija/backtracer.cr.svg)](https://github.com/Sija/backtracer.cr/blob/master/LICENSE) +# backtracer.cr [![CI](https://github.com/Sija/backtracer.cr/actions/workflows/ci.yml/badge.svg)](https://github.com/Sija/backtracer.cr/actions/workflows/ci.yml) [![Releases](https://img.shields.io/github/release/Sija/backtracer.cr.svg)](https://github.com/Sija/backtracer.cr/releases) [![License](https://img.shields.io/github/license/Sija/backtracer.cr.svg)](https://github.com/Sija/backtracer.cr/blob/master/LICENSE) Crystal shard aiming to assist with parsing backtraces into a structured form. From 30287e3025e7a3409a772254c96fa722bd3391a7 Mon Sep 17 00:00:00 2001 From: Sijawusz Pur Rahnama Date: Sat, 13 Aug 2022 23:30:25 +0200 Subject: [PATCH 18/23] Bump ameba to ~> 1.0.0 --- shard.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shard.yml b/shard.yml index b08af20..201bbd6 100644 --- a/shard.yml +++ b/shard.yml @@ -7,7 +7,7 @@ authors: development_dependencies: ameba: github: crystal-ameba/ameba - version: ~> 0.14.0 + version: ~> 1.0.0 crystal: ">= 0.35.0" From 07d6dc43817d16c7fb7f0ab51d7d67325a469534 Mon Sep 17 00:00:00 2001 From: Sijawusz Pur Rahnama Date: Sat, 13 Aug 2022 23:31:37 +0200 Subject: [PATCH 19/23] Bump to v1.2.2 --- shard.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shard.yml b/shard.yml index 201bbd6..4c89914 100644 --- a/shard.yml +++ b/shard.yml @@ -1,5 +1,5 @@ name: backtracer -version: 1.2.1 +version: 1.2.2 authors: - Sijawusz Pur Rahnama From 8410b1147869e5b902c76eb5dffeb4c552deb502 Mon Sep 17 00:00:00 2001 From: Sijawusz Pur Rahnama Date: Wed, 26 Oct 2022 02:19:07 +0200 Subject: [PATCH 20/23] Bump ameba dependency --- shard.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shard.yml b/shard.yml index 4c89914..2266835 100644 --- a/shard.yml +++ b/shard.yml @@ -7,7 +7,7 @@ authors: development_dependencies: ameba: github: crystal-ameba/ameba - version: ~> 1.0.0 + version: ~> 1.2.0 crystal: ">= 0.35.0" From a7eed4b2304376a4be8e87d142cd3c85e6709ae1 Mon Sep 17 00:00:00 2001 From: Sijawusz Pur Rahnama Date: Mon, 6 Mar 2023 05:58:15 +0100 Subject: [PATCH 21/23] Bump ameba dependency and fix found issues --- .ameba.yml | 3 +++ shard.yml | 2 +- spec/backtracer/backtrace/frame/context_spec.cr | 2 +- spec/spec_helper.cr | 9 +++++---- src/backtracer.cr | 2 +- 5 files changed, 11 insertions(+), 7 deletions(-) create mode 100644 .ameba.yml diff --git a/.ameba.yml b/.ameba.yml new file mode 100644 index 0000000..399b108 --- /dev/null +++ b/.ameba.yml @@ -0,0 +1,3 @@ +Lint/NotNil: + Excluded: + - spec/backtracer/backtrace/frame/parser_spec.cr diff --git a/shard.yml b/shard.yml index 2266835..c718818 100644 --- a/shard.yml +++ b/shard.yml @@ -7,7 +7,7 @@ authors: development_dependencies: ameba: github: crystal-ameba/ameba - version: ~> 1.2.0 + version: ~> 1.4.0 crystal: ">= 0.35.0" diff --git a/spec/backtracer/backtrace/frame/context_spec.cr b/spec/backtracer/backtrace/frame/context_spec.cr index 1ec0a74..b6fc3aa 100644 --- a/spec/backtracer/backtrace/frame/context_spec.cr +++ b/spec/backtracer/backtrace/frame/context_spec.cr @@ -1,6 +1,6 @@ require "../../../spec_helper" -def with_foo_context +def with_foo_context(&) yield Backtracer::Backtrace::Frame::Context.new( lineno: 10, pre: %w[foo bar baz], diff --git a/spec/spec_helper.cr b/spec/spec_helper.cr index 11c6c86..ed152bb 100644 --- a/spec/spec_helper.cr +++ b/spec/spec_helper.cr @@ -1,15 +1,15 @@ require "spec" require "../src/backtracer" -def with_configuration(shared = true) +def with_configuration(shared = true, &) yield shared ? Backtracer.configuration : Backtracer::Configuration.new end -def with_backtrace(backtrace, **options) +def with_backtrace(backtrace, **options, &) yield Backtracer::Backtrace::Parser.parse(backtrace, **options) end -def with_frame(method, path = nil, lineno = nil, column = nil, **options) +def with_frame(method, path = nil, lineno = nil, column = nil, **options, &) line = String.build do |io| if path io << path @@ -28,7 +28,8 @@ def with_foo_frame( path = "#{__DIR__}/foo.cr", lineno = 1, column = 7, - **options + **options, + & ) with_frame(method, path, lineno, column, **options) do |frame| yield frame diff --git a/src/backtracer.cr b/src/backtracer.cr index 6d0f507..c507a2d 100644 --- a/src/backtracer.cr +++ b/src/backtracer.cr @@ -1,7 +1,7 @@ module Backtracer class_getter(configuration) { Configuration.new } - def self.configure : Nil + def self.configure(&) : Nil yield configuration end From 7e92356a508682f1062a368a4c208ebecfea61ee Mon Sep 17 00:00:00 2001 From: Sijawusz Pur Rahnama Date: Wed, 11 Oct 2023 04:10:23 +0200 Subject: [PATCH 22/23] Bump ameba to ~> 1.5.0 --- shard.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shard.yml b/shard.yml index c718818..96ff072 100644 --- a/shard.yml +++ b/shard.yml @@ -7,7 +7,7 @@ authors: development_dependencies: ameba: github: crystal-ameba/ameba - version: ~> 1.4.0 + version: ~> 1.5.0 crystal: ">= 0.35.0" From abdac766c5106cf5e39068d7795beaf13116d14e Mon Sep 17 00:00:00 2001 From: Sijawusz Pur Rahnama Date: Thu, 12 Oct 2023 17:33:12 +0200 Subject: [PATCH 23/23] Bump ameba version to ~> 1.5.0 --- shard.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shard.yml b/shard.yml index c718818..96ff072 100644 --- a/shard.yml +++ b/shard.yml @@ -7,7 +7,7 @@ authors: development_dependencies: ameba: github: crystal-ameba/ameba - version: ~> 1.4.0 + version: ~> 1.5.0 crystal: ">= 0.35.0"