diff --git a/.travis.yml b/.travis.yml index f568488b..77eb0972 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,12 +2,14 @@ language: ruby matrix: allow_failures: - rvm: ruby-head + include: + - rvm: jruby-9.0.3.0 + env: JRUBY_OPTS="$JRUBY_OPTS -J-Xmx1536m -J-XX:MaxPermSize=192m" rvm: - 2.0 - 2.1 - 2.2 - ruby-head -- jruby-9.0.3.0 bundler_args: "--binstubs --jobs=3 --retry=3" before_install: gem install bundler -v 1.10.6 cache: bundler diff --git a/lib/gemstash/env.rb b/lib/gemstash/env.rb index 8a3acb2c..2eaa8373 100644 --- a/lib/gemstash/env.rb +++ b/lib/gemstash/env.rb @@ -2,6 +2,7 @@ require "dalli" require "fileutils" require "sequel" +require "uri" module Gemstash # Storage for application-wide variables and configuration. @@ -108,9 +109,9 @@ def db db_path = base_file("gemstash.db") if RUBY_PLATFORM == "java" - db = Sequel.connect("jdbc:sqlite:#{db_path}") + db = Sequel.connect("jdbc:sqlite:#{db_path}", max_connections: 1) else - db = Sequel.connect("sqlite://#{db_path}") + db = Sequel.connect("sqlite://#{URI.escape(db_path)}", max_connections: 1) end when "postgres" db = Sequel.connect(config[:db_url]) diff --git a/spec/gemstash/storage_spec.rb b/spec/gemstash/storage_spec.rb index 82cacb48..91759287 100644 --- a/spec/gemstash/storage_spec.rb +++ b/spec/gemstash/storage_spec.rb @@ -1,5 +1,4 @@ require "spec_helper" -require "securerandom" describe Gemstash::Storage do before do @@ -67,8 +66,8 @@ end context "with a previously stored resource" do - let(:resource_id) { SecureRandom.uuid } - let(:content) { SecureRandom.base64 } + let(:resource_id) { "42" } + let(:content) { "zapatito" } before do storage.resource(resource_id).save(content) end diff --git a/spec/integration_spec.rb b/spec/integration_spec.rb index 3b04e94a..16abce89 100644 --- a/spec/integration_spec.rb +++ b/spec/integration_spec.rb @@ -102,7 +102,7 @@ it "pushes valid gems to the server", :db_transaction => false do env = { "HOME" => env_dir } - expect(execute("gem push --key test --host '#{host}' '#{gem}'", env: env)).to exit_success + expect(execute("gem", ["push", "--key", "test", "--host", host, gem], env: env)).to exit_success expect(deps.fetch(%w(speaker))).to match_dependencies([speaker_deps]) expect(storage.resource("speaker-0.1.0").load.content).to eq(gem_contents) expect(http_client.get("gems/speaker-0.1.0")).to eq(gem_contents) @@ -118,7 +118,7 @@ it "removes valid gems from the server", :db_transaction => false do env = { "HOME" => env_dir, "RUBYGEMS_HOST" => host } - expect(execute("gem yank --key test '#{gem_name}' --version #{gem_version}", env: env)).to exit_success + expect(execute("gem", ["yank", "--key", "test", gem_name, "--version", gem_version], env: env)).to exit_success expect(deps.fetch(%w(speaker))).to match_dependencies([]) # It shouldn't actually delete the gem, to support unyank expect(storage.resource("speaker-0.1.0").load.content).to eq(gem_contents) @@ -137,7 +137,8 @@ it "adds valid gems back to the server", :db_transaction => false do env = { "HOME" => env_dir, "RUBYGEMS_HOST" => host } - expect(execute("gem yank --key test '#{gem_name}' --version #{gem_version} --undo", env: env)).to exit_success + expect(execute("gem", ["yank", "--key", "test", gem_name, "--version", gem_version, "--undo"], env: env)). + to exit_success expect(deps.fetch(%w(speaker))).to match_dependencies([speaker_deps]) expect(storage.resource("speaker-0.1.0").load.content).to eq(gem_contents) expect(http_client.get("gems/speaker-0.1.0")).to eq(gem_contents) @@ -163,27 +164,27 @@ shared_examples "a bundleable project" do it "successfully bundles" do expect(execute("bundle", dir: dir)).to exit_success - expect(execute("bundle exec speaker hi", dir: dir)). + expect(execute("bundle", %w(exec speaker hi), dir: dir)). to exit_success.and_output("Hello world, #{platform_message}\n") end it "can bundle with full index" do - expect(execute("bundle --full-index", dir: dir)).to exit_success - expect(execute("bundle exec speaker hi", dir: dir)). + expect(execute("bundle", %w(--full-index), dir: dir)).to exit_success + expect(execute("bundle", %w(exec speaker hi), dir: dir)). to exit_success.and_output("Hello world, #{platform_message}\n") end it "can bundle with prerelease versions" do env = { "SPEAKER_VERSION" => "= 0.2.0.pre" } expect(execute("bundle", dir: dir, env: env)).to exit_success - expect(execute("bundle exec speaker hi", dir: dir, env: env)). + expect(execute("bundle", %w(exec speaker hi), dir: dir, env: env)). to exit_success.and_output("Hello world, pre, #{platform_message}\n") end it "can bundle with prerelease versions with full index" do env = { "SPEAKER_VERSION" => "= 0.2.0.pre" } - expect(execute("bundle --full-index", dir: dir, env: env)).to exit_success - expect(execute("bundle exec speaker hi", dir: dir, env: env)). + expect(execute("bundle", %w(--full-index), dir: dir, env: env)).to exit_success + expect(execute("bundle", %w(exec speaker hi), dir: dir, env: env)). to exit_success.and_output("Hello world, pre, #{platform_message}\n") end end @@ -205,13 +206,13 @@ it "can successfully bundle twice" do expect(execute("bundle", dir: dir)).to exit_success - expect(execute("bundle exec speaker hi", dir: dir)). + expect(execute("bundle", %w(exec speaker hi), dir: dir)). to exit_success.and_output("Hello world, #{platform_message}\n") clean_bundle bundle expect(execute("bundle", dir: dir)).to exit_success - expect(execute("bundle exec speaker hi", dir: dir)). + expect(execute("bundle", %w(exec speaker hi), dir: dir)). to exit_success.and_output("Hello world, #{platform_message}\n") end end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 1711ee82..55fe6116 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -8,6 +8,7 @@ require "support/env_helpers" require "support/exec_helpers" require "support/file_helpers" +require "support/in_process_exec" require "support/log_helpers" require "support/matchers" require "support/simple_server" diff --git a/spec/support/exec_helpers.rb b/spec/support/exec_helpers.rb index aaea9b16..6ddbcb81 100644 --- a/spec/support/exec_helpers.rb +++ b/spec/support/exec_helpers.rb @@ -1,40 +1,51 @@ +require "bundler/version" require "open3" +require "pathname" # Helpers for executing commands and asserting the results. module ExecHelpers - def execute(command, dir: nil, env: {}) - bundle_gemfile = nil - - if dir - gemfile_path = File.join(dir, "Gemfile") - bundle_gemfile = gemfile_path if File.exist?(gemfile_path) + def execute(command, args = [], dir: nil, env: {}) + if RUBY_PLATFORM == "java" + JRubyResult.new(env, command, args, dir) + else + Result.new(env, command, args, dir) end - - env = { - "BUNDLE_GEMFILE" => bundle_gemfile, - "RUBYLIB" => nil, - "RUBYOPT" => nil, - "GEM_PATH" => ENV["_ORIGINAL_GEM_PATH"] - }.merge(env) - dir ||= File.expand_path(".") - Result.new(env, command, dir) end # Executes and stores the results for an external command. class Result - attr_reader :command, :dir, :output + attr_reader :command, :args, :dir, :output - def initialize(env, command, dir) + def initialize(env, command, args, dir) @command = command - @dir = dir - @output, @status = Open3.capture2e(env, command, chdir: dir) + @args = args + @env = env + @original_dir = dir + @dir = dir || File.expand_path("../../..", __FILE__) + exec fix_jruby_output end + def exec + @output, @status = Open3.capture2e(patched_env, command, *args, chdir: dir) + end + def successful? @status.success? end + def display_command + display_args = args.map do |x| + if x =~ /\s/ + "'#{x}'" + else + x + end + end + + ([command] + display_args).join(" ") + end + def matches_output?(expected) return true unless expected @output == expected @@ -46,12 +57,49 @@ def fix_jruby_output return unless RUBY_PLATFORM == "java" # Travis builds or runs JRuby in a way that outputs the following warning for some reason @output.gsub!(/^.*warning: unknown property jruby.cext.enabled\n/, "") + end + + def patched_env + @patched_env ||= clear_bundler_env.merge(clear_ruby_env).merge(@env) + end - if Gem::Requirement.new(">= 1.11.0").satisfied_by?(Gem::Version.new(Bundler::VERSION)) - raise "Please remove ExecHelpers#fix_jruby_output if the warning doesn't occur anymore" + def clear_ruby_env + { + "RUBYLIB" => nil, + "RUBYOPT" => nil, + "GEM_PATH" => ENV["_ORIGINAL_GEM_PATH"] + } + end + + def clear_bundler_env + if @original_dir + gemfile_path = File.join(@original_dir, "Gemfile") + bundle_gemfile = gemfile_path if File.exist?(gemfile_path) end - @output.gsub!(/^.*warning: unsupported exec option: close_others\n/, "") + { "BUNDLE_GEMFILE" => bundle_gemfile } + end + end + + # Executes and stores the results for an external command, but does it in + # process with a separate JRuby instance rather than a process. + class JRubyResult < Result + def exec + binstub_dir = File.expand_path("../jruby_binstubs", __FILE__) + binstub = File.join(binstub_dir, command) + raise "Missing binstub for #{command}" unless File.exist?(binstub) + exec_in_process(binstub) + end + + def successful? + @process.status == 0 + end + + private + + def exec_in_process(binstub) + @process = InProcessExec.new(patched_env, dir, [binstub] + args) + @output = @process.output end end end @@ -67,13 +115,13 @@ def fix_jruby_output failure_message do |actual| if actual.successful? - "expected '#{actual.command}' in '#{actual.dir}' to output: + "expected '#{actual.display_command}' in '#{actual.dir}' to output: #{@expected_output} but instead it output: #{actual.output}" else - "expected '#{actual.command}' in '#{actual.dir}' to exit with a success code, but it didn't. + "expected '#{actual.display_command}' in '#{actual.dir}' to exit with a success code, but it didn't. the command output was: #{actual.output}" end diff --git a/spec/support/in_process_exec.rb b/spec/support/in_process_exec.rb new file mode 100644 index 00000000..9afe9867 --- /dev/null +++ b/spec/support/in_process_exec.rb @@ -0,0 +1,36 @@ +# Run a JRuby program in process. +class InProcessExec + attr_reader :status, :output + + def initialize(env, dir, args) + raise "InProcessExec is only valid on JRuby!" unless RUBY_PLATFORM == "java" + @env = env + @dir = dir + @args = args + exec + end + +private + + def exec + prepare_streams + prepare_config + @status = org.jruby.Main.new(@config).run(@args.to_java(:String)).status + @output = @output_stream.to_string + end + + def prepare_streams + @input_stream = java.io.ByteArrayInputStream.new([].to_java(:byte)) + @output_stream = java.io.ByteArrayOutputStream.new + @output_print_stream = java.io.PrintStream.new(@output_stream) + end + + def prepare_config + @config = org.jruby.RubyInstanceConfig.new(JRuby.runtime.instance_config) + @config.environment = @env + @config.current_directory = @dir + @config.input = @input_stream + @config.output = @output_print_stream + @config.error = @output_print_stream + end +end diff --git a/spec/support/jruby_binstubs/bundle b/spec/support/jruby_binstubs/bundle new file mode 100644 index 00000000..1075bc33 --- /dev/null +++ b/spec/support/jruby_binstubs/bundle @@ -0,0 +1,25 @@ +require "rubygems" +gem "bundler" +require "bundler" +require "bundler/fetcher" +require_relative "../in_process_exec.rb" + +module Bundler + #:nodoc: + class Fetcher + # The Bundler user_agent uses SecureRandom, which causes the specs + # to run out of entropy and run a lot longer than they need to. + def user_agent + "gemstash spec" + end + end +end + +def Kernel.exec(*args) + args.pop if args.last.kind_of?(Hash) + process = InProcessExec.new(ENV, File.expand_path("."), args) + puts process.output + exit process.status +end + +load Gem.bin_path("bundler", "bundle") diff --git a/spec/support/jruby_binstubs/gem b/spec/support/jruby_binstubs/gem new file mode 100644 index 00000000..0634c6b5 --- /dev/null +++ b/spec/support/jruby_binstubs/gem @@ -0,0 +1,10 @@ +# Based on jgem +require "rubygems" +require "rubygems/gem_runner" +require "rubygems/exceptions" + +begin + Gem::GemRunner.new.run ARGV.clone +rescue Gem::SystemExitException => e + exit e.exit_code +end