diff --git a/CHANGELOG.md b/CHANGELOG.md index 8d7ddb8..24f2b19 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ ## HEAD (unreleased) +- Fix performance issue when evaluating multiple block combinations (https://github.com/zombocom/dead_end/pull/35) + ## 1.0.0 - Gem name changed from `syntax_search` to `dead_end` (https://github.com/zombocom/syntax_search/pull/30) diff --git a/lib/dead_end/code_frontier.rb b/lib/dead_end/code_frontier.rb index e5659d6..1ba6291 100644 --- a/lib/dead_end/code_frontier.rb +++ b/lib/dead_end/code_frontier.rb @@ -143,7 +143,7 @@ def self.combination(array) # Given that we know our syntax error exists somewhere in our frontier, we want to find # the smallest possible set of blocks that contain all the syntax errors def detect_invalid_blocks - self.class.combination(@frontier).detect do |block_array| + self.class.combination(@frontier.select(&:invalid?)).detect do |block_array| holds_all_syntax_errors?(block_array) end || [] end diff --git a/lib/dead_end/internals.rb b/lib/dead_end/internals.rb index 92afbcc..ddd5023 100644 --- a/lib/dead_end/internals.rb +++ b/lib/dead_end/internals.rb @@ -53,8 +53,9 @@ def self.call(source: , filename: , terminal: false, record_dir: nil, timeout: T invalid_obj: invalid_type(source), io: io ).call - rescue Timeout::Error + rescue Timeout::Error => e io.puts "Search timed out DEAD_END_TIMEOUT=#{timeout}, run with DEBUG=1 for more info" + io.puts e.backtrace.first(3).join($/) end # Used for counting spaces diff --git a/spec/fixtures/routes.txt.rb b/spec/fixtures/routes.txt.rb new file mode 100644 index 0000000..c4c7a16 --- /dev/null +++ b/spec/fixtures/routes.txt.rb @@ -0,0 +1,121 @@ +Rails.application.routes.draw do + constraints -> { Rails.application.config.non_production } do + namespace :foo do + resource :bar + end + end + constraints -> { Rails.application.config.non_production } do + namespace :bar do + resource :baz + end + end + constraints -> { Rails.application.config.non_production } do + namespace :bar do + resource :baz + end + end + constraints -> { Rails.application.config.non_production } do + namespace :bar do + resource :baz + end + end + constraints -> { Rails.application.config.non_production } do + namespace :bar do + resource :baz + end + end + constraints -> { Rails.application.config.non_production } do + namespace :bar do + resource :baz + end + end + constraints -> { Rails.application.config.non_production } do + namespace :bar do + resource :baz + end + end + constraints -> { Rails.application.config.non_production } do + namespace :bar do + resource :baz + end + end + constraints -> { Rails.application.config.non_production } do + namespace :bar do + resource :baz + end + end + constraints -> { Rails.application.config.non_production } do + namespace :bar do + resource :baz + end + end + constraints -> { Rails.application.config.non_production } do + namespace :bar do + resource :baz + end + end + constraints -> { Rails.application.config.non_production } do + namespace :bar do + resource :baz + end + end + constraints -> { Rails.application.config.non_production } do + namespace :bar do + resource :baz + end + end + constraints -> { Rails.application.config.non_production } do + namespace :bar do + resource :baz + end + end + constraints -> { Rails.application.config.non_production } do + namespace :bar do + resource :baz + end + end + constraints -> { Rails.application.config.non_production } do + namespace :bar do + resource :baz + end + end + constraints -> { Rails.application.config.non_production } do + namespace :bar do + resource :baz + end + end + constraints -> { Rails.application.config.non_production } do + namespace :bar do + resource :baz + end + end + constraints -> { Rails.application.config.non_production } do + namespace :bar do + resource :baz + end + end + constraints -> { Rails.application.config.non_production } do + namespace :bar do + resource :baz + end + end + constraints -> { Rails.application.config.non_production } do + namespace :bar do + resource :baz + end + end + constraints -> { Rails.application.config.non_production } do + namespace :bar do + resource :baz + end + end + + namespace :admin do + resource :session + + match "/out_of_office(*path)", via: :all, to: redirect { |_params, req| + uri = URI(req.path.gsub("out_of_office", "in_office")) + uri.query = req.query_string.presence + uri.to_s + } +end diff --git a/spec/perf/perf_spec.rb b/spec/perf/perf_spec.rb new file mode 100644 index 0000000..ffdc956 --- /dev/null +++ b/spec/perf/perf_spec.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +require_relative "../spec_helper.rb" +require "benchmark" + +module DeadEnd + RSpec.describe "perf" do + it "doesnt timeout" do + source = fixtures_dir.join("routes.txt.rb").read + + io = StringIO.new + bench = Benchmark.measure do + DeadEnd.call( + io: io, + source: source, + filename: "none", + ) + end + + expect(io.string).to include(<<~'EOM'.indent(4)) + 1 Rails.application.routes.draw do + 107 constraints -> { Rails.application.config.non_production } do + 111 end + ❯ 113 namespace :admin do + ❯ 116 match "/out_of_office(*path)", via: :all, to: redirect { |_params, req| + ❯ 120 } + 121 end + EOM + + expect(bench.real).to be < 1 # second + end + end +end