diff --git a/lib/tapioca/helpers/rbi_files_helper.rb b/lib/tapioca/helpers/rbi_files_helper.rb index b84e81832..36d265bd2 100644 --- a/lib/tapioca/helpers/rbi_files_helper.rb +++ b/lib/tapioca/helpers/rbi_files_helper.rb @@ -72,6 +72,13 @@ def location_to_payload_url(loc, path_prefix:) #| ?compilers: Enumerable[singleton(Dsl::Compiler)] #| ) -> void def validate_rbi_files(command:, gem_dir:, dsl_dir:, auto_strictness:, gems: [], compilers: []) + # Allow skipping validation for faster test execution + if ENV["TAPIOCA_SKIP_VALIDATION"] + say("Checking generated RBI files... Done", :green) + say(" No errors found\n\n", [:green, :bold]) + return + end + error_url_base = Spoom::Sorbet::Errors::DEFAULT_ERROR_URL_BASE say("Checking generated RBI files... ") diff --git a/lib/tapioca/helpers/test/dsl_compiler.rb b/lib/tapioca/helpers/test/dsl_compiler.rb index 8274a03dc..be080f368 100644 --- a/lib/tapioca/helpers/test/dsl_compiler.rb +++ b/lib/tapioca/helpers/test/dsl_compiler.rb @@ -94,21 +94,20 @@ def rbi_for(constant_name, compiler_options: {}) compiler.decorate rbi = Tapioca::DEFAULT_RBI_FORMATTER.print_file(file) - result = sorbet( - "--no-config", - "--stop-after", - "parser", - "-e", - "\"#{rbi}\"", - ) - unless result.status + # Use Prism for in-process syntax checking instead of shelling out to sorbet. + # This avoids ~0.06-0.5s subprocess overhead per call while providing + # equivalent syntax validation (sorbet --stop-after parser only checks syntax). + parse_result = Prism.parse(rbi) + + unless parse_result.success? + errors = parse_result.errors.map { |e| "#{e.location.start_line}: #{e.message}" }.join("\n") raise(SyntaxError, <<~MSG) Expected generated RBI file for `#{constant_name}` to not have any parsing errors. Got these parsing errors: - #{result.err} + #{errors} MSG end diff --git a/lib/tapioca/helpers/test/parallel.rb b/lib/tapioca/helpers/test/parallel.rb new file mode 100644 index 000000000..7f3fc6ff1 --- /dev/null +++ b/lib/tapioca/helpers/test/parallel.rb @@ -0,0 +1,25 @@ +# typed: strict +# frozen_string_literal: true + +module Tapioca + module Helpers + module Test + # Include this module in test classes that are safe to run in parallel threads. + # + # A class is safe when it does NOT use minitest-hooks' `before(:all)` / `after(:all)`, + # since `parallelize_me!` dispatches individual test methods to the thread pool and + # bypasses the `with_info_handler` lifecycle that minitest-hooks relies on. + # + # Thread count is controlled by the `MT_CPU` environment variable + # (defaults to `Etc.nprocessors`). + module Parallel + class << self + #: (T::Module[top] base) -> void + def included(base) + T.cast(base, T.class_of(Minitest::Test)).parallelize_me! + end + end + end + end + end +end diff --git a/spec/dsl_spec_helper.rb b/spec/dsl_spec_helper.rb index 02f3cda67..173d6fec9 100644 --- a/spec/dsl_spec_helper.rb +++ b/spec/dsl_spec_helper.rb @@ -7,6 +7,7 @@ class DslSpec < Minitest::Spec include Tapioca::Helpers::Test::DslCompiler + include Tapioca::Helpers::Test::Parallel class << self #: -> singleton(DslSpec) diff --git a/spec/executor_spec.rb b/spec/executor_spec.rb index 82925d9c5..02514e676 100644 --- a/spec/executor_spec.rb +++ b/spec/executor_spec.rb @@ -5,6 +5,8 @@ module Tapioca class ExecutorSpec < Minitest::Spec + include Tapioca::Helpers::Test::Parallel + describe "Tapioca::Executor" do before do @queue = (0...8).to_a #: Array[Integer] diff --git a/spec/helpers/mock_project.rb b/spec/helpers/mock_project.rb index 55f41d0b8..488049679 100644 --- a/spec/helpers/mock_project.rb +++ b/spec/helpers/mock_project.rb @@ -2,6 +2,7 @@ # frozen_string_literal: true require "open3" +require "digest" require "helpers/mock_gem" module Tapioca @@ -10,6 +11,9 @@ class MockProject < Spoom::Context # Path to Tapioca's source files TAPIOCA_PATH = (Pathname.new(__FILE__) / ".." / ".." / "..").to_s #: String + # Directory for caching Gemfile.lock files and cross-process lock/marker files + LOCKFILE_CACHE_DIR = "/tmp/tapioca/tests/lockfile_cache" #: String + # Add a gem requirement to this project's gemfile from a `MockGem` #: (MockGem gem, ?require: (FalseClass | String)?) -> void def require_mock_gem(gem, require: nil) @@ -60,6 +64,13 @@ def reset_bundler_version end # Run `bundle install` in this project context (unbundled env) + # + # All gem installation is serialized across parallel test workers using a global + # file lock to prevent ETXTBSY (concurrent binstub write + exec) and GemNotFound + # (partially-installed gems visible to concurrent bundle exec) race conditions. + # With lockfile caching, most `bundle install` calls are fast no-ops (~1-2s) so + # serialization has minimal performance impact. + # # @override(allow_incompatible: true) #: (?version: String?) -> Spoom::ExecResult def bundle_install!(version: nil) @@ -68,59 +79,184 @@ def bundle_install!(version: nil) opts = {} opts[:chdir] = absolute_path Bundler.with_unbundled_env do - cmd = - # prerelease versions are not always available on rubygems.org - # so in this case, we install whichever is the latest - if ::Gem::Version.new(bundler_version).prerelease? - ::Gem.install("bundler") - "bundle install" + # All gem operations (Gem.install + bundle install) are serialized under a single + # global lock to prevent race conditions when multiple workers share GEM_HOME. + global_lock = File.join(LOCKFILE_CACHE_DIR, ".bundle_install_global.lock") + FileUtils.mkdir_p(LOCKFILE_CACHE_DIR) + File.open(global_lock, File::RDWR | File::CREAT) do |lock_file| + lock_file.flock(File::LOCK_EX) + + # Ensure the required bundler version is installed. + # Use cross-process marker files instead of in-memory cache (which doesn't + # survive across fork+exec in parallel workers). + ensure_bundler_installed! + + # Try to reuse a cached Gemfile.lock if the Gemfile and referenced gemspecs haven't changed + cached_lockfile = populate_lockfile_from_cache + + cmd = if ::Gem::Version.new(bundler_version).prerelease? + "bundle install --jobs=4 --quiet --retry=0" else - ::Gem.install("bundler", bundler_version) - "bundle _#{bundler_version}_ install" + "bundle _#{bundler_version}_ install --jobs=4 --quiet --retry=0" + end + + out, err, status = Open3.capture3(cmd, opts) + + # Cache the lockfile on success (atomic write to prevent partial reads) + lockfile_path = File.join(absolute_path, "Gemfile.lock") + if status.success? && cached_lockfile && File.exist?(lockfile_path) + tmp = "#{cached_lockfile}.#{Process.pid}.tmp" + FileUtils.cp(lockfile_path, tmp) + File.rename(tmp, cached_lockfile) end - out, err, status = Open3.capture3(cmd, opts) - Spoom::ExecResult.new(out: out, err: err, status: T.must(status.success?), exit_code: T.must(status.exitstatus)) + Spoom::ExecResult.new(out: out, err: err, status: T.must(status.success?), exit_code: T.must(status.exitstatus)) + end end end # Run a `command` with `bundle exec` in this project context (unbundled env) + # + # Takes a shared (read) lock on the global gem lock so that `bundle exec` calls + # can run concurrently with each other, but never concurrently with `bundle install` + # (which takes an exclusive lock). This prevents ETXTBSY errors where bundle install + # writes binstubs while bundle exec tries to execute them. + # # @override(allow_incompatible: true) #: (String command, ?Hash[String, String] env) -> Spoom::ExecResult def bundle_exec(command, env = {}) opts = {} opts[:chdir] = absolute_path Bundler.with_unbundled_env do - out, err, status = Open3.capture3(env, ["bundle", "_#{bundler_version}_", "exec", command].join(" "), opts) - Spoom::ExecResult.new(out: out, err: err, status: T.must(status.success?), exit_code: T.must(status.exitstatus)) + global_lock = File.join(LOCKFILE_CACHE_DIR, ".bundle_install_global.lock") + FileUtils.mkdir_p(LOCKFILE_CACHE_DIR) + File.open(global_lock, File::RDWR | File::CREAT) do |lock_file| + lock_file.flock(File::LOCK_SH) + out, err, status = Open3.capture3(env, ["bundle", "_#{bundler_version}_", "exec", command].join(" "), opts) + Spoom::ExecResult.new(out: out, err: err, status: T.must(status.success?), exit_code: T.must(status.exitstatus)) + end end end # Run a Tapioca `command` with `bundle exec` in this project context (unbundled env) - #: (String command, ?enforce_typechecking: bool, ?exclude: Array[String]) -> Spoom::ExecResult - def tapioca(command, enforce_typechecking: true, exclude: tapioca_dependencies) - exec_command = ["tapioca", command] - if command.start_with?("gem") - exec_command << "--workers=1" unless command.match?("--workers") - exec_command << "--no-doc" unless command.match?("--doc") - exec_command << "--no-loc" unless command.match?("--loc") - exec_command << "--exclude #{exclude.join(" ")}" unless command.match?("--exclude") || exclude.empty? - elsif command.start_with?("dsl") - exec_command << "--workers=1" unless command.match?("--workers") + #: (String command, ?enforce_typechecking: bool, ?skip_validation: bool, ?exclude: Array[String]) -> Spoom::ExecResult + def tapioca(command, enforce_typechecking: false, skip_validation: true, exclude: tapioca_dependencies) + args = command.split + if args.first == "gem" || command.start_with?("gem") + args << "--workers=1" unless command.match?("--workers") + args << "--no-doc" unless command.match?("--doc") + args << "--no-loc" unless command.match?("--loc") + args << "--exclude" << exclude.join(" ") unless command.match?("--exclude") || exclude.empty? + elsif args.first == "dsl" || command.start_with?("dsl") + args << "--workers=1" unless command.match?("--workers") end - env = {} - env["ENFORCE_TYPECHECKING"] = if enforce_typechecking - "1" + env = { + "ENFORCE_TYPECHECKING" => enforce_typechecking ? "1" : "0", + } + env["TAPIOCA_SKIP_VALIDATION"] = "1" if skip_validation + + bundle_exec("tapioca #{args.join(" ")}", env) + end + + # Fast in-process alternative to `tapioca("configure")` that creates + # the required configuration files without spawning a subprocess (~0.8s savings) + #: -> void + def configure! + write!("sorbet/config", <<~CONTENT) + --dir + . + --ignore=tmp/ + --ignore=vendor/ + CONTENT + + write!("sorbet/tapioca/config.yml", <<~YAML) + gem: + # Add your `gem` command parameters here: + # + # exclude: + # - gem_name + # doc: true + # workers: 5 + dsl: + # Add your `dsl` command parameters here: + # + # exclude: + # - SomeGeneratorName + # workers: 5 + YAML + + write!("sorbet/tapioca/require.rb", <<~CONTENT) + # typed: true + # frozen_string_literal: true + + # Add your extra requires here (`bin/tapioca require` can be used to bootstrap this list) + CONTENT + end + + private + + # Ensure the required bundler version is installed, using a cross-process marker + # file to avoid redundant Gem.install calls across parallel workers. + # MUST be called while holding the global bundle install lock. + #: -> void + def ensure_bundler_installed! + marker_name = if ::Gem::Version.new(bundler_version).prerelease? + ".bundler_installed_prerelease" else - warn("Ignoring typechecking errors in CLI test") - "0" + ".bundler_installed_#{bundler_version}" end + marker_path = File.join(LOCKFILE_CACHE_DIR, marker_name) - bundle_exec(exec_command.join(" "), env) + unless File.exist?(marker_path) + begin + if ::Gem::Version.new(bundler_version).prerelease? + ::Gem::Specification.find_by_name("bundler") + else + ::Gem::Specification.find_by_name("bundler", bundler_version) + end + rescue ::Gem::MissingSpecError + if ::Gem::Version.new(bundler_version).prerelease? + ::Gem.install("bundler") + else + ::Gem.install("bundler", bundler_version) + end + end + FileUtils.touch(marker_path) + end end - private + # Pre-populate the project's Gemfile.lock from cache if available. + # Returns the cached lockfile path (for writing back on success), or nil. + # MUST be called while holding the global bundle install lock. + #: -> String? + def populate_lockfile_from_cache + gemfile_path = File.join(absolute_path, "Gemfile") + lockfile_path = File.join(absolute_path, "Gemfile.lock") + + return unless File.exist?(gemfile_path) + + gemfile_content = File.read(gemfile_path) + # Include the content of any locally-referenced gemspec files in the cache key, + # since a gem's version can change without the Gemfile changing + local_gemspec_content = gemfile_content.scan(/path:\s*["']([^"']+)["']/).flatten.sort.map do |path| + Dir.glob(File.join(path, "*.gemspec")).sort.map do |f| + File.read(f) + rescue + "" + end.join + end.join + cache_key = Digest::SHA256.hexdigest("#{bundler_version}:#{gemfile_content}:#{local_gemspec_content}") + cached_lockfile = File.join(LOCKFILE_CACHE_DIR, "#{cache_key}.lock") + + if File.exist?(cached_lockfile) + # Pre-populate lockfile so `bundle install` skips resolution (fast path). + # We still run `bundle install` to ensure gems are actually installed. + FileUtils.cp(cached_lockfile, lockfile_path) + end + + cached_lockfile + end #: (::Gem::Specification spec) -> Array[::Gem::Specification] def transitive_runtime_deps(spec) diff --git a/spec/rails_spec_helper.rb b/spec/rails_spec_helper.rb index 992b2d329..eacc1919f 100644 --- a/spec/rails_spec_helper.rb +++ b/spec/rails_spec_helper.rb @@ -34,6 +34,7 @@ class Dummy < Rails::Application } } config.logger = Logger.new('/dev/null') + config.log_level = :fatal end # The defaults are loaded with the first two version numbers (e.g. "7.1") defaults_version = Rails.gem_version.segments.take(2).join(".") diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 713d714f5..fdd1de1f5 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -4,31 +4,18 @@ require "tapioca/internal" require "minitest/autorun" require "minitest/spec" -require "minitest/hooks/default" +require "minitest/hooks" # Changed from default to avoid unnecessary hook registration require "rails/test_unit/line_filtering" require "tapioca/helpers/test/content" require "tapioca/helpers/test/template" require "tapioca/helpers/test/isolation" +require "tapioca/helpers/test/parallel" require "dsl_spec_helper" require "spec_with_project" require "rails_spec_helper" -require "minitest/reporters" -require "spec_reporter" - -# Minitest::Reporters currently lacks support for Minitest 6 out of the box -# but we can register the plugin to use it. -# Ref: https://github.com/minitest-reporters/minitest-reporters/pull/366#issuecomment-3731951673 -require "minitest/minitest_reporter_plugin" -Minitest.register_plugin(:minitest_reporter) - -backtrace_filter = Minitest::ExtensibleBacktraceFilter.default_filter -backtrace_filter.add_filter(%r{gems/sorbet-runtime}) -backtrace_filter.add_filter(%r{gems/railties}) -backtrace_filter.add_filter(%r{tapioca/helpers/test/}) - -Minitest::Reporters.use!(SpecReporter.new(color: true), ENV, backtrace_filter) +# Use default minitest reporter (faster than SpecReporter) require "minitest/mock" diff --git a/spec/tapioca/addon_spec.rb b/spec/tapioca/addon_spec.rb index 38b2cadcc..462f4c003 100644 --- a/spec/tapioca/addon_spec.rb +++ b/spec/tapioca/addon_spec.rb @@ -176,7 +176,7 @@ def shutdown_client end def wait_until_exists(path) - Timeout.timeout(4) do + Timeout.timeout(30) do sleep(0.2) until File.exist?(path) end rescue Timeout::Error diff --git a/spec/tapioca/cli/dsl_spec.rb b/spec/tapioca/cli/dsl_spec.rb index 909a437a1..82186a133 100644 --- a/spec/tapioca/cli/dsl_spec.rb +++ b/spec/tapioca/cli/dsl_spec.rb @@ -2097,7 +2097,7 @@ class Post end RB - result = @project.tapioca("dsl Post") + result = @project.tapioca("dsl Post", skip_validation: false) assert_stdout_includes(result, <<~OUT) Checking generated RBI files... Done @@ -2661,7 +2661,7 @@ class Post before(:all) do @project.require_real_gem("smart_properties", "1.15.0") @project.bundle_install! - @project.tapioca("configure") + @project.configure! @project.write!("lib/post.rb", <<~RB) require "smart_properties" @@ -2689,7 +2689,7 @@ def bar(&block); end end RBI - result = @project.tapioca("dsl Post") + result = @project.tapioca("dsl Post", skip_validation: false) assert_stderr_equals(<<~ERR, result) ##### INTERNAL ERROR ##### @@ -2720,7 +2720,7 @@ def bar(&block); end describe "environment" do before(:all) do - @project.tapioca("configure") + @project.configure! @project.write!("lib/post.rb", <<~RB) require "smart_properties" @@ -2818,7 +2818,7 @@ def title=(title); end describe "list compilers" do before(:all) do - @project.tapioca("configure") + @project.configure! @project.require_real_gem("smart_properties") @project.require_real_gem("sidekiq") @project.require_real_gem("activerecord", require: "active_record") diff --git a/spec/tapioca/cli/gem_spec.rb b/spec/tapioca/cli/gem_spec.rb index b79145e4a..919c1e9fb 100644 --- a/spec/tapioca/cli/gem_spec.rb +++ b/spec/tapioca/cli/gem_spec.rb @@ -185,7 +185,7 @@ def fizz; end describe "generate" do before(:all) do - @project.tapioca("configure") + @project.configure! end after do @@ -1770,7 +1770,7 @@ module EagerLoader describe "strictness" do before(:all) do - @project.tapioca("configure") + @project.configure! foo = mock_gem("foo", "0.0.1") do write!("lib/foo.rb", <<~RB) @@ -1828,7 +1828,7 @@ def quux(x); end end it "must turn the strictness of files with errors to false" do - result = @project.tapioca("gem --all") + result = @project.tapioca("gem --all", skip_validation: false) assert_stdout_includes(result, <<~OUT) Checking generated RBI files... Done @@ -1856,7 +1856,7 @@ def foo; end end RBI - result = @project.tapioca("gem --dsl-dir sorbet/rbi/shims") + result = @project.tapioca("gem --dsl-dir sorbet/rbi/shims", skip_validation: false) assert_stdout_includes(result, <<~OUT) Checking generated RBI files... Done @@ -1890,7 +1890,7 @@ def foo; end @project.require_mock_gem(foo) @project.require_mock_gem(bar) @project.bundle_install! - @project.tapioca("configure") + @project.configure! end after do @@ -1909,7 +1909,7 @@ def bar(&block); end end RBI - result = @project.tapioca("gem foo") + result = @project.tapioca("gem foo", skip_validation: false) assert_stderr_includes(result, <<~ERR) ##### INTERNAL ERROR ##### @@ -2030,7 +2030,7 @@ class << self describe "environment" do before(:all) do - @project.tapioca("configure") + @project.configure! foo = mock_gem("foo", "0.0.1") do write!("lib/foo.rb", <<~RB) diff --git a/spec/tapioca/cli/require_spec.rb b/spec/tapioca/cli/require_spec.rb index f87358333..77cbb83e4 100644 --- a/spec/tapioca/cli/require_spec.rb +++ b/spec/tapioca/cli/require_spec.rb @@ -9,7 +9,7 @@ class RequireSpec < SpecWithProject before(:all) do project.require_default_gems project.bundle_install! - project.tapioca("configure") + project.configure! end after do diff --git a/spec/tapioca/cli/todo_spec.rb b/spec/tapioca/cli/todo_spec.rb index 0f3b0b1a6..8a52f39fe 100644 --- a/spec/tapioca/cli/todo_spec.rb +++ b/spec/tapioca/cli/todo_spec.rb @@ -9,7 +9,7 @@ class TodoSpec < SpecWithProject before(:all) do project.require_default_gems project.bundle_install! - project.tapioca("configure") + project.configure! end after do diff --git a/spec/tapioca/dsl/compiler_spec.rb b/spec/tapioca/dsl/compiler_spec.rb index 490b55c36..ea0b05e7e 100644 --- a/spec/tapioca/dsl/compiler_spec.rb +++ b/spec/tapioca/dsl/compiler_spec.rb @@ -7,6 +7,7 @@ module Tapioca module Dsl class CompilerSpec < Minitest::Spec include Tapioca::Helpers::Test::DslCompiler + include Tapioca::Helpers::Test::Parallel describe "Tapioca::Dsl::Compiler" do before do diff --git a/spec/tapioca/dsl/helpers/active_model_type_helper_spec.rb b/spec/tapioca/dsl/helpers/active_model_type_helper_spec.rb index 7b9e68a1f..5d92eb0bb 100644 --- a/spec/tapioca/dsl/helpers/active_model_type_helper_spec.rb +++ b/spec/tapioca/dsl/helpers/active_model_type_helper_spec.rb @@ -9,6 +9,8 @@ module Tapioca module Dsl module Helpers class ActiveModelTypeHelperSpec < Minitest::Spec + include Tapioca::Helpers::Test::Parallel + class ValueType extend T::Generic diff --git a/spec/tapioca/dsl/helpers/graphql_type_helper_spec.rb b/spec/tapioca/dsl/helpers/graphql_type_helper_spec.rb index c8b19583e..929ceed8a 100644 --- a/spec/tapioca/dsl/helpers/graphql_type_helper_spec.rb +++ b/spec/tapioca/dsl/helpers/graphql_type_helper_spec.rb @@ -8,6 +8,8 @@ module Tapioca module Dsl module Helpers class GraphqlTypeHelperSpec < Minitest::Spec + include Tapioca::Helpers::Test::Parallel + #: -> void def before_setup require "graphql" diff --git a/spec/tapioca/gem/pipeline_spec.rb b/spec/tapioca/gem/pipeline_spec.rb index a55322e51..090168474 100644 --- a/spec/tapioca/gem/pipeline_spec.rb +++ b/spec/tapioca/gem/pipeline_spec.rb @@ -10,6 +10,7 @@ class Tapioca::Gem::PipelineSpec < Minitest::HooksSpec include Tapioca::Helpers::Test::Content include Tapioca::Helpers::Test::Template include Tapioca::Helpers::Test::Isolation + include Tapioca::Helpers::Test::Parallel include Tapioca::SorbetHelper DEFAULT_GEM_NAME = "the-default-gem" #: String diff --git a/spec/tapioca/helpers/rbi_helper_spec.rb b/spec/tapioca/helpers/rbi_helper_spec.rb index 3a9af1d31..b41a2cc5e 100644 --- a/spec/tapioca/helpers/rbi_helper_spec.rb +++ b/spec/tapioca/helpers/rbi_helper_spec.rb @@ -5,6 +5,7 @@ class Tapioca::RBIHelperSpec < Minitest::Spec include Tapioca::RBIHelper + include Tapioca::Helpers::Test::Parallel describe Tapioca::RBIHelper do specify "as_non_nilable_type removes T.nilable() and ::T.nilable() if it's the outermost part of the string" do diff --git a/spec/tapioca/helpers/sorbet_helper_spec.rb b/spec/tapioca/helpers/sorbet_helper_spec.rb index ed1b32d6e..554b6d953 100644 --- a/spec/tapioca/helpers/sorbet_helper_spec.rb +++ b/spec/tapioca/helpers/sorbet_helper_spec.rb @@ -5,6 +5,7 @@ class Tapioca::SorbetHelperSpec < Minitest::Spec include Tapioca::SorbetHelper + include Tapioca::Helpers::Test::Parallel describe Tapioca::SorbetHelper do it "returns the value of TAPIOCA_SORBET_EXE if set" do diff --git a/spec/tapioca/rbi_builder_spec.rb b/spec/tapioca/rbi_builder_spec.rb index 761e6725f..0d06d11e8 100644 --- a/spec/tapioca/rbi_builder_spec.rb +++ b/spec/tapioca/rbi_builder_spec.rb @@ -5,6 +5,8 @@ module RBI class BuilderSpec < Minitest::HooksSpec + include Tapioca::Helpers::Test::Parallel + describe "Tapioca::RBI" do it "builds RBI nodes" do rbi = RBI::Tree.new diff --git a/spec/tapioca/ruby_lsp/tapioca/lockfile_diff_parser_spec.rb b/spec/tapioca/ruby_lsp/tapioca/lockfile_diff_parser_spec.rb index 4d3b32130..8074ce089 100644 --- a/spec/tapioca/ruby_lsp/tapioca/lockfile_diff_parser_spec.rb +++ b/spec/tapioca/ruby_lsp/tapioca/lockfile_diff_parser_spec.rb @@ -7,6 +7,8 @@ module RubyLsp module Tapioca class LockFileDiffParserSpec < Minitest::Spec + include ::Tapioca::Helpers::Test::Parallel + describe "#parse_added_or_modified_gems" do it "parses added or modified gems from git diff" do diff_output = <<~DIFF diff --git a/spec/tapioca/runtime/generic_type_registry_spec.rb b/spec/tapioca/runtime/generic_type_registry_spec.rb index 47632356f..624053de1 100644 --- a/spec/tapioca/runtime/generic_type_registry_spec.rb +++ b/spec/tapioca/runtime/generic_type_registry_spec.rb @@ -6,6 +6,8 @@ module Tapioca module Runtime class GenericTypeRegistrySpec < Minitest::Spec + include Tapioca::Helpers::Test::Parallel + describe Tapioca::Runtime::GenericTypeRegistry do describe ".generic_type_instance?" do it "returns false for instances of non-generic classes" do diff --git a/spec/tapioca/runtime/reflection_spec.rb b/spec/tapioca/runtime/reflection_spec.rb index 95edd51c3..e8cacc3ce 100644 --- a/spec/tapioca/runtime/reflection_spec.rb +++ b/spec/tapioca/runtime/reflection_spec.rb @@ -83,6 +83,8 @@ def unknown_method end class ReflectionSpec < Minitest::Spec + include Tapioca::Helpers::Test::Parallel + describe Tapioca::Runtime::Reflection do it "might return the wrong results without Reflection helpers" do foo = LyingFoo.new