From 52bed533a0b1377565c9a5b10fbe3ce87aff16ce Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Tue, 8 Nov 2022 16:41:43 -0500 Subject: [PATCH] Increase test coverage --- lib/syntax_tree/cli.rb | 12 ++-- lib/syntax_tree/pattern.rb | 6 +- lib/syntax_tree/visitor/environment.rb | 11 ++-- lib/syntax_tree/visitor/field_visitor.rb | 8 --- test/cli_test.rb | 66 +++++++++++++++++++- test/language_server_test.rb | 18 ++++++ test/rake_test.rb | 20 +++++- test/search_test.rb | 79 +++++++++++++++++++++--- test/test_helper.rb | 7 +-- test/visitor_with_environment_test.rb | 40 ++++++++++++ 10 files changed, 233 insertions(+), 34 deletions(-) diff --git a/lib/syntax_tree/cli.rb b/lib/syntax_tree/cli.rb index 62e8ab68..11c93537 100644 --- a/lib/syntax_tree/cli.rb +++ b/lib/syntax_tree/cli.rb @@ -497,10 +497,14 @@ def run(argv) Dir .glob(pattern) .each do |filepath| - if File.readable?(filepath) && - options.ignore_files.none? { File.fnmatch?(_1, filepath) } - queue << FileItem.new(filepath) - end + # Skip past invalid filepaths by default. + next unless File.readable?(filepath) + + # Skip past any ignored filepaths. + next if options.ignore_files.any? { File.fnmatch(_1, filepath) } + + # Otherwise, a new file item for the given filepath to the list. + queue << FileItem.new(filepath) end end diff --git a/lib/syntax_tree/pattern.rb b/lib/syntax_tree/pattern.rb index 439d573f..ca49c6bf 100644 --- a/lib/syntax_tree/pattern.rb +++ b/lib/syntax_tree/pattern.rb @@ -142,11 +142,11 @@ def compile_binary(node) def compile_const(node) value = node.value - if SyntaxTree.const_defined?(value) + if SyntaxTree.const_defined?(value, false) clazz = SyntaxTree.const_get(value) ->(other) { clazz === other } - elsif Object.const_defined?(value) + elsif Object.const_defined?(value, false) clazz = Object.const_get(value) ->(other) { clazz === other } @@ -179,7 +179,7 @@ def compile_dyna_symbol(node) ->(other) { symbol === other } else - compile_error(root) + compile_error(node) end end diff --git a/lib/syntax_tree/visitor/environment.rb b/lib/syntax_tree/visitor/environment.rb index dfcf0a80..b07a5203 100644 --- a/lib/syntax_tree/visitor/environment.rb +++ b/lib/syntax_tree/visitor/environment.rb @@ -4,10 +4,6 @@ module SyntaxTree # The environment class is used to keep track of local variables and arguments # inside a particular scope class Environment - # [Array[Local]] The local variables and arguments defined in this - # environment - attr_reader :locals - # This class tracks the occurrences of a local variable or argument class Local # [Symbol] The type of the local (e.g. :argument, :variable) @@ -38,6 +34,13 @@ def add_usage(location) end end + # [Array[Local]] The local variables and arguments defined in this + # environment + attr_reader :locals + + # [Environment | nil] The parent environment + attr_reader :parent + # initialize: (Environment | nil parent) -> void def initialize(parent = nil) @locals = {} diff --git a/lib/syntax_tree/visitor/field_visitor.rb b/lib/syntax_tree/visitor/field_visitor.rb index 01c7de4e..b56d771c 100644 --- a/lib/syntax_tree/visitor/field_visitor.rb +++ b/lib/syntax_tree/visitor/field_visitor.rb @@ -388,14 +388,6 @@ def visit_excessed_comma(node) visit_token(node, "excessed_comma") end - def visit_fcall(node) - node(node, "fcall") do - field("value", node.value) - field("arguments", node.arguments) if node.arguments - comments(node) - end - end - def visit_field(node) node(node, "field") do field("parent", node.parent) diff --git a/test/cli_test.rb b/test/cli_test.rb index b4ef0afc..9740806d 100644 --- a/test/cli_test.rb +++ b/test/cli_test.rb @@ -41,6 +41,7 @@ def test_ast_ignore def test_ast_syntax_error result = run_cli("ast", contents: "foo\n<>\nbar\n") assert_includes(result.stderr, "syntax error") + refute_equal(0, result.status) end def test_check @@ -51,6 +52,7 @@ def test_check def test_check_unformatted result = run_cli("check", contents: "foo") assert_includes(result.stderr, "expected") + refute_equal(0, result.status) end def test_check_print_width @@ -59,6 +61,17 @@ def test_check_print_width assert_includes(result.stdio, "match") end + def test_check_target_ruby_version + previous = Formatter::OPTIONS[:target_ruby_version] + + begin + result = run_cli("check", "--target-ruby-version=2.6.0") + assert_includes(result.stdio, "match") + ensure + Formatter::OPTIONS[:target_ruby_version] = previous + end + end + def test_debug result = run_cli("debug") assert_includes(result.stdio, "idempotently") @@ -71,6 +84,7 @@ def test_debug_non_idempotent_format SyntaxTree.stub(:format, formatting) do result = run_cli("debug") assert_includes(result.stderr, "idempotently") + refute_equal(0, result.status) end end @@ -84,6 +98,12 @@ def test_expr assert_includes(result.stdio, "SyntaxTree::Ident") end + def test_expr_more_than_one + result = run_cli("expr", contents: "1; 2") + assert_includes(result.stderr, "single expression") + refute_equal(0, result.status) + end + def test_format result = run_cli("format") assert_equal("test\n", result.stdio) @@ -104,6 +124,17 @@ def test_search assert_equal(2, result.stdio.lines.length) end + def test_search_multi_line + result = run_cli("search", "Binary", contents: "1 +\n2") + assert_equal(1, result.stdio.lines.length) + end + + def test_search_invalid + result = run_cli("search", "FooBar") + assert_includes(result.stderr, "unable") + refute_equal(0, result.status) + end + def test_version result = run_cli("version") assert_includes(result.stdio, SyntaxTree::VERSION.to_s) @@ -120,6 +151,29 @@ def test_write def test_write_syntax_tree result = run_cli("write", contents: "<>") assert_includes(result.stderr, "syntax error") + refute_equal(0, result.status) + end + + def test_write_script + args = ["write", "-e", "1 + 2"] + stdout, stderr = capture_io { SyntaxTree::CLI.run(args) } + + assert_includes stdout, "script" + assert_empty stderr + end + + def test_write_stdin + previous = $stdin + $stdin = StringIO.new("1 + 2") + + begin + stdout, stderr = capture_io { SyntaxTree::CLI.run(["write"]) } + + assert_includes stdout, "stdin" + assert_empty stderr + ensure + $stdin = previous + end end def test_help @@ -128,8 +182,10 @@ def test_help end def test_help_default - *, stderr = capture_io { SyntaxTree::CLI.run(["foobar"]) } + status = 0 + *, stderr = capture_io { status = SyntaxTree::CLI.run(["foobar"]) } assert_includes(stderr, "stree help") + refute_equal(0, status) end def test_no_arguments @@ -215,6 +271,7 @@ def test_print_width_args_with_config_file_override result = run_cli("check", "--print-width=82", contents: contents) assert_includes(result.stderr, "expected") + refute_equal(0, result.status) end end @@ -251,7 +308,12 @@ def run_cli(command, *args, contents: :default) status = nil stdio, stderr = capture_io do - status = SyntaxTree::CLI.run([command, *args, tempfile.path]) + status = + begin + SyntaxTree::CLI.run([command, *args, tempfile.path]) + rescue SystemExit => error + error.status + end end Result.new(status: status, stdio: stdio, stderr: stderr) diff --git a/test/language_server_test.rb b/test/language_server_test.rb index 8e1ed9a7..2fe4e60a 100644 --- a/test/language_server_test.rb +++ b/test/language_server_test.rb @@ -159,6 +159,24 @@ def test_inlay_hint assert_equal(3, responses.dig(1, :result).size) end + def test_inlay_hint_invalid + responses = run_server([ + Initialize.new(1), + TextDocumentDidOpen.new("file:///path/to/file.rb", "<>"), + TextDocumentInlayHint.new(2, "file:///path/to/file.rb"), + Shutdown.new(3) + ]) + + shape = LanguageServer::Request[[ + { id: 1, result: { capabilities: Hash } }, + { id: 2, result: :any }, + { id: 3, result: {} } + ]] + + assert_operator(shape, :===, responses) + assert_equal(0, responses.dig(1, :result).size) + end + def test_visualizing responses = run_server([ Initialize.new(1), diff --git a/test/rake_test.rb b/test/rake_test.rb index bd315cc6..d07fc49c 100644 --- a/test/rake_test.rb +++ b/test/rake_test.rb @@ -8,12 +8,28 @@ module Rake class CheckTaskTest < Minitest::Test Invocation = Struct.new(:args) + def test_task_command + assert_raises(NotImplementedError) { Task.new.command } + end + def test_check_task source_files = "{app,config,lib}/**/*.rb" - CheckTask.new { |t| t.source_files = source_files } + + CheckTask.new do |t| + t.source_files = source_files + t.print_width = 100 + t.target_ruby_version = Gem::Version.new("2.6.0") + end + + expected = [ + "check", + "--print-width=100", + "--target-ruby-version=2.6.0", + source_files + ] invocation = invoke("stree:check") - assert_equal(["check", source_files], invocation.args) + assert_equal(expected, invocation.args) end def test_write_task diff --git a/test/search_test.rb b/test/search_test.rb index 314142e3..9f7d89b8 100644 --- a/test/search_test.rb +++ b/test/search_test.rb @@ -4,6 +4,50 @@ module SyntaxTree class SearchTest < Minitest::Test + def test_search_invalid_syntax + assert_raises(Pattern::CompilationError) { search("", "<>") } + end + + def test_search_invalid_constant + assert_raises(Pattern::CompilationError) { search("", "Foo") } + end + + def test_search_invalid_nested_constant + assert_raises(Pattern::CompilationError) { search("", "Foo::Bar") } + end + + def test_search_regexp_with_interpolation + assert_raises(Pattern::CompilationError) { search("", "/\#{foo}/") } + end + + def test_search_string_with_interpolation + assert_raises(Pattern::CompilationError) { search("", '"#{foo}"') } + end + + def test_search_symbol_with_interpolation + assert_raises(Pattern::CompilationError) { search("", ":\"\#{foo}\"") } + end + + def test_search_invalid_node + assert_raises(Pattern::CompilationError) { search("", "Int[^foo]") } + end + + def test_search_self + assert_raises(Pattern::CompilationError) { search("", "self") } + end + + def test_search_array_pattern_no_constant + results = search("1 + 2", "[Int, Int]") + + assert_equal 1, results.length + end + + def test_search_array_pattern + results = search("1 + 2", "Binary[Int, Int]") + + assert_equal 1, results.length + end + def test_search_binary_or results = search("Foo + Bar + 1", "VarRef | Int") @@ -18,12 +62,24 @@ def test_search_const assert_equal %w[Bar Baz Foo], results.map { |node| node.value.value }.sort end + def test_search_object_const + results = search("1 + 2 + 3", "Int[value: String]") + + assert_equal 3, results.length + end + def test_search_syntax_tree_const results = search("Foo + Bar + Baz", "SyntaxTree::VarRef") assert_equal 3, results.length end + def test_search_hash_pattern_no_constant + results = search("Foo + Bar + Baz", "{ value: Const }") + + assert_equal 3, results.length + end + def test_search_hash_pattern_string results = search("Foo + Bar + Baz", "VarRef[value: Const[value: 'Foo']]") @@ -39,13 +95,25 @@ def test_search_hash_pattern_regexp end def test_search_string_empty - results = search("''", "StringLiteral[parts: []]") + results = search("", "''") - assert_equal 1, results.length + assert_empty results end def test_search_symbol_empty - results = search(":''", "DynaSymbol[parts: []]") + results = search("", ":''") + + assert_empty results + end + + def test_search_symbol_plain + results = search("1 + 2", "Binary[operator: :'+']") + + assert_equal 1, results.length + end + + def test_search_symbol + results = search("1 + 2", "Binary[operator: :+]") assert_equal 1, results.length end @@ -53,10 +121,7 @@ def test_search_symbol_empty private def search(source, query) - pattern = Pattern.new(query).compile - program = SyntaxTree.parse(source) - - Search.new(pattern).scan(program).to_a + SyntaxTree.search(source, query).to_a end end end diff --git a/test/test_helper.rb b/test/test_helper.rb index 1683e7cf..b2cd6787 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -2,10 +2,9 @@ require "simplecov" SimpleCov.start do - unless ENV["CI"] - add_filter("accept_methods_test.rb") - add_filter("idempotency_test.rb") - end + add_filter("idempotency_test.rb") unless ENV["CI"] + add_group("lib", "lib") + add_group("test", "test") end $LOAD_PATH.unshift(File.expand_path("../lib", __dir__)) diff --git a/test/visitor_with_environment_test.rb b/test/visitor_with_environment_test.rb index b37bad16..cc4007fe 100644 --- a/test/visitor_with_environment_test.rb +++ b/test/visitor_with_environment_test.rb @@ -615,5 +615,45 @@ def test_double_nested_arguments assert_equal(1, argument.definitions[0].start_line) assert_equal(5, argument.usages[0].start_line) end + + class Resolver < Visitor + include WithEnvironment + + attr_reader :locals + + def initialize + @locals = [] + end + + def visit_assign(node) + level = 0 + environment = current_environment + level += 1 until (environment = environment.parent).nil? + + locals << [node.target.value.value, level] + super + end + end + + def test_class + source = <<~RUBY + module Level0 + level0 = 0 + + module Level1 + level1 = 1 + + class Level2 + level2 = 2 + end + end + end + RUBY + + visitor = Resolver.new + SyntaxTree.parse(source).accept(visitor) + + assert_equal [["level0", 0], ["level1", 1], ["level2", 2]], visitor.locals + end end end