diff options
Diffstat (limited to 'spec/syntax_suggest/integration')
-rw-r--r-- | spec/syntax_suggest/integration/exe_cli_spec.rb | 27 | ||||
-rw-r--r-- | spec/syntax_suggest/integration/ruby_command_line_spec.rb | 193 | ||||
-rw-r--r-- | spec/syntax_suggest/integration/syntax_suggest_spec.rb | 239 |
3 files changed, 459 insertions, 0 deletions
diff --git a/spec/syntax_suggest/integration/exe_cli_spec.rb b/spec/syntax_suggest/integration/exe_cli_spec.rb new file mode 100644 index 0000000000..b9a3173715 --- /dev/null +++ b/spec/syntax_suggest/integration/exe_cli_spec.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +require_relative "../spec_helper" + +module SyntaxSuggest + RSpec.describe "exe" do + def exe_path + if ruby_core? + root_dir.join("../libexec").join("syntax_suggest") + else + root_dir.join("exe").join("syntax_suggest") + end + end + + def exe(cmd) + ruby = ENV.fetch("RUBY", "ruby") + out = run!("#{ruby} #{exe_path} #{cmd}", raise_on_nonzero_exit: false) + puts out if ENV["SYNTAX_SUGGEST_DEBUG"] + out + end + + it "prints the version" do + out = exe("-v") + expect(out.strip).to include(SyntaxSuggest::VERSION) + end + end +end diff --git a/spec/syntax_suggest/integration/ruby_command_line_spec.rb b/spec/syntax_suggest/integration/ruby_command_line_spec.rb new file mode 100644 index 0000000000..c1ec4be54e --- /dev/null +++ b/spec/syntax_suggest/integration/ruby_command_line_spec.rb @@ -0,0 +1,193 @@ +# frozen_string_literal: true + +require_relative "../spec_helper" + +module SyntaxSuggest + ruby = ENV.fetch("RUBY", "ruby") + RSpec.describe "Requires with ruby cli" do + it "namespaces all monkeypatched methods" do + Dir.mktmpdir do |dir| + tmpdir = Pathname(dir) + script = tmpdir.join("script.rb") + script.write <<~EOM + puts Kernel.private_methods + EOM + + syntax_suggest_methods_file = tmpdir.join("syntax_suggest_methods.txt") + api_only_methods_file = tmpdir.join("api_only_methods.txt") + kernel_methods_file = tmpdir.join("kernel_methods.txt") + + d_pid = Process.spawn("#{ruby} -I#{lib_dir} -rsyntax_suggest #{script} 2>&1 > #{syntax_suggest_methods_file}") + k_pid = Process.spawn("#{ruby} #{script} 2>&1 >> #{kernel_methods_file}") + r_pid = Process.spawn("#{ruby} -I#{lib_dir} -rsyntax_suggest/api #{script} 2>&1 > #{api_only_methods_file}") + + Process.wait(k_pid) + Process.wait(d_pid) + Process.wait(r_pid) + + kernel_methods_array = kernel_methods_file.read.strip.lines.map(&:strip) + syntax_suggest_methods_array = syntax_suggest_methods_file.read.strip.lines.map(&:strip) + api_only_methods_array = api_only_methods_file.read.strip.lines.map(&:strip) + + # In ruby 3.1.0-preview1 the `timeout` file is already required + # we can remove it if it exists to normalize the output for + # all ruby versions + [syntax_suggest_methods_array, kernel_methods_array, api_only_methods_array].each do |array| + array.delete("timeout") + end + + methods = (syntax_suggest_methods_array - kernel_methods_array).sort + if methods.any? + expect(methods).to eq(["syntax_suggest_original_load", "syntax_suggest_original_require", "syntax_suggest_original_require_relative"]) + end + + methods = (api_only_methods_array - kernel_methods_array).sort + expect(methods).to eq([]) + end + end + + # Since Ruby 3.2 includes syntax_suggest as a default gem, we might accidentally + # be requiring the default gem instead of this library under test. Assert that's + # not the case + it "tests current version of syntax_suggest" do + Dir.mktmpdir do |dir| + tmpdir = Pathname(dir) + script = tmpdir.join("script.rb") + contents = <<~'EOM' + puts "suggest_version is #{SyntaxSuggest::VERSION}" + EOM + script.write(contents) + + out = `#{ruby} -I#{lib_dir} -rsyntax_suggest/version #{script} 2>&1` + + expect(out).to include("suggest_version is #{SyntaxSuggest::VERSION}").once + end + end + + it "detects require error and adds a message with auto mode" do + Dir.mktmpdir do |dir| + tmpdir = Pathname(dir) + script = tmpdir.join("script.rb") + script.write <<~EOM + describe "things" do + it "blerg" do + end + + it "flerg" + end + + it "zlerg" do + end + end + EOM + + require_rb = tmpdir.join("require.rb") + require_rb.write <<~EOM + load "#{script.expand_path}" + EOM + + out = `#{ruby} -I#{lib_dir} -rsyntax_suggest #{require_rb} 2>&1` + + expect($?.success?).to be_falsey + expect(out).to include('> 5 it "flerg"').once + end + end + + it "gem can be tested when executing on Ruby with default gem included" do + skip if Gem::Version.new(RUBY_VERSION) < Gem::Version.new("3.2") + + out = `#{ruby} -I#{lib_dir} -rsyntax_suggest -e "puts SyntaxError.instance_method(:detailed_message).source_location" 2>&1` + + expect($?.success?).to be_truthy + expect(out).to include(lib_dir.join("syntax_suggest").join("core_ext.rb").to_s).once + end + + it "annotates a syntax error in Ruby 3.2+ when require is not used" do + skip if Gem::Version.new(RUBY_VERSION) < Gem::Version.new("3.2") + + Dir.mktmpdir do |dir| + tmpdir = Pathname(dir) + script = tmpdir.join("script.rb") + script.write <<~EOM + describe "things" do + it "blerg" do + end + + it "flerg" + end + + it "zlerg" do + end + end + EOM + + out = `#{ruby} -I#{lib_dir} -rsyntax_suggest #{script} 2>&1` + + expect($?.success?).to be_falsey + expect(out).to include('> 5 it "flerg"').once + end + end + + it "does not load internals into memory if no syntax error" do + Dir.mktmpdir do |dir| + tmpdir = Pathname(dir) + script = tmpdir.join("script.rb") + script.write <<~EOM + class Dog + end + + if defined?(SyntaxSuggest::DEFAULT_VALUE) + puts "SyntaxSuggest is loaded" + else + puts "SyntaxSuggest is NOT loaded" + end + EOM + + require_rb = tmpdir.join("require.rb") + require_rb.write <<~EOM + load "#{script.expand_path}" + EOM + + out = `#{ruby} -I#{lib_dir} -rsyntax_suggest #{require_rb} 2>&1` + + expect($?.success?).to be_truthy + expect(out).to include("SyntaxSuggest is NOT loaded").once + end + end + + it "ignores eval" do + Dir.mktmpdir do |dir| + tmpdir = Pathname(dir) + script = tmpdir.join("script.rb") + script.write <<~EOM + $stderr = STDOUT + eval("def lol") + EOM + + out = `#{ruby} -I#{lib_dir} -rsyntax_suggest #{script} 2>&1` + + expect($?.success?).to be_falsey + expect(out).to match(/\(eval.*\):1/) + + expect(out).to_not include("SyntaxSuggest") + expect(out).to_not include("Could not find filename") + end + end + + it "does not say 'syntax ok' when a syntax error fires" do + Dir.mktmpdir do |dir| + tmpdir = Pathname(dir) + script = tmpdir.join("script.rb") + script.write <<~EOM + break + EOM + + out = `#{ruby} -I#{lib_dir} -rsyntax_suggest -e "require_relative '#{script}'" 2>&1` + + expect($?.success?).to be_falsey + expect(out.downcase).to_not include("syntax ok") + expect(out).to include("Invalid break") + end + end + end +end diff --git a/spec/syntax_suggest/integration/syntax_suggest_spec.rb b/spec/syntax_suggest/integration/syntax_suggest_spec.rb new file mode 100644 index 0000000000..9071d37c1b --- /dev/null +++ b/spec/syntax_suggest/integration/syntax_suggest_spec.rb @@ -0,0 +1,239 @@ +# frozen_string_literal: true + +require_relative "../spec_helper" + +module SyntaxSuggest + RSpec.describe "Integration tests that don't spawn a process (like using the cli)" do + it "does not timeout on massive files" do + next unless ENV["SYNTAX_SUGGEST_TIMEOUT"] + + file = fixtures_dir.join("syntax_tree.rb.txt") + lines = file.read.lines + lines.delete_at(768 - 1) + + io = StringIO.new + + benchmark = Benchmark.measure do + debug_perf do + SyntaxSuggest.call( + io: io, + source: lines.join, + filename: file + ) + end + end + + debug_display(io.string) + debug_display(benchmark) + + expect(io.string).to include(<<~EOM) + 6 class SyntaxTree < Ripper + 170 def self.parse(source) + 174 end + > 754 def on_args_add(arguments, argument) + > 776 class ArgsAddBlock + > 810 end + 9233 end + EOM + end + + it "re-checks all block code, not just what's visible issues/95" do + file = fixtures_dir.join("ruby_buildpack.rb.txt") + io = StringIO.new + + debug_perf do + benchmark = Benchmark.measure do + SyntaxSuggest.call( + io: io, + source: file.read, + filename: file + ) + end + debug_display(io.string) + debug_display(benchmark) + end + + expect(io.string).to_not include("def ruby_install_binstub_path") + expect(io.string).to include(<<~EOM) + > 1067 def add_yarn_binary + > 1068 return [] if yarn_preinstalled? + > 1069 | + > 1075 end + EOM + end + + it "returns good results on routes.rb" do + source = fixtures_dir.join("routes.rb.txt").read + + io = StringIO.new + SyntaxSuggest.call( + io: io, + source: source + ) + debug_display(io.string) + + expect(io.string).to include(<<~EOM) + 1 Rails.application.routes.draw do + > 113 namespace :admin do + > 116 match "/foobar(*path)", via: :all, to: redirect { |_params, req| + > 120 } + 121 end + EOM + end + + it "handles multi-line-methods issues/64" do + source = fixtures_dir.join("webmock.rb.txt").read + + io = StringIO.new + SyntaxSuggest.call( + io: io, + source: source + ) + debug_display(io.string) + + expect(io.string).to include(<<~EOM) + 1 describe "webmock tests" do + 22 it "body" do + 27 query = Cutlass::FunctionQuery.new( + > 28 port: port + > 29 body: body + 30 ).call + 34 end + 35 end + EOM + end + + it "handles derailed output issues/50" do + source = fixtures_dir.join("derailed_require_tree.rb.txt").read + + io = StringIO.new + SyntaxSuggest.call( + io: io, + source: source + ) + debug_display(io.string) + + expect(io.string).to include(<<~EOM) + 5 module DerailedBenchmarks + 6 class RequireTree + > 13 def initialize(name) + > 18 def self.reset! + > 25 end + 73 end + 74 end + EOM + end + + it "handles heredocs" do + lines = fixtures_dir.join("rexe.rb.txt").read.lines + lines.delete_at(85 - 1) + io = StringIO.new + SyntaxSuggest.call( + io: io, + source: lines.join + ) + + out = io.string + debug_display(out) + + expect(out).to include(<<~EOM) + 16 class Rexe + > 77 class Lookups + > 78 def input_modes + > 148 end + 551 end + EOM + end + + it "rexe" do + lines = fixtures_dir.join("rexe.rb.txt").read.lines + lines.delete_at(148 - 1) + source = lines.join + + io = StringIO.new + SyntaxSuggest.call( + io: io, + source: source + ) + out = io.string + expect(out).to include(<<~EOM) + 16 class Rexe + > 77 class Lookups + > 140 def format_requires + > 148 end + 551 end + EOM + end + + it "ambiguous end" do + source = <<~EOM + def call # 0 + print "lol" # 1 + end # one # 2 + end # two # 3 + EOM + io = StringIO.new + SyntaxSuggest.call( + io: io, + source: source + ) + out = io.string + expect(out).to include(<<~EOM) + > 1 def call # 0 + > 3 end # one # 2 + > 4 end # two # 3 + EOM + end + + it "simple regression" do + source = <<~EOM + class Dog + def bark + puts "woof" + end + EOM + io = StringIO.new + SyntaxSuggest.call( + io: io, + source: source + ) + out = io.string + expect(out).to include(<<~EOM) + > 1 class Dog + > 2 def bark + > 4 end + EOM + end + + it "empty else" do + source = <<~EOM + class Foo + def foo + if cond? + foo + else + + end + end + + # ... + + def bar + if @recv + end_is_missing_here + end + end + EOM + + io = StringIO.new + SyntaxSuggest.call( + io: io, + source: source + ) + out = io.string + expect(out).to include(<<~EOM) + end_is_missing_here + EOM + end + end +end |