Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Various fixes 2 #47

Merged
merged 7 commits into from
Nov 6, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
5 changes: 3 additions & 2 deletions lib/gemstash/env.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
require "dalli"
require "fileutils"
require "sequel"
require "uri"

module Gemstash
# Storage for application-wide variables and configuration.
Expand Down Expand Up @@ -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])
Expand Down
5 changes: 2 additions & 3 deletions spec/gemstash/storage_spec.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
require "spec_helper"
require "securerandom"

describe Gemstash::Storage do
before do
Expand Down Expand Up @@ -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" }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

😆

42 is a perfect randomly generated number!

before do
storage.resource(resource_id).save(content)
end
Expand Down
23 changes: 12 additions & 11 deletions spec/integration_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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)
Expand All @@ -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)
Expand All @@ -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
Expand All @@ -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
Expand Down
1 change: 1 addition & 0 deletions spec/spec_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
96 changes: 72 additions & 24 deletions spec/support/exec_helpers.rb
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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
Expand All @@ -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
Expand Down
36 changes: 36 additions & 0 deletions spec/support/in_process_exec.rb
Original file line number Diff line number Diff line change
@@ -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
25 changes: 25 additions & 0 deletions spec/support/jruby_binstubs/bundle
Original file line number Diff line number Diff line change
@@ -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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if it makes sense to create a Random generator based on a SecureRandom seed and just keep it around.

No a blocker, though

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think it's necessary, this is only in use for JRuby to speed up the build there. MRI doesn't seem to have the same problem, and the random number being generated doesn't really have any benefit in our specs. André pointed out in chat, it's for matching requests, which we don't really care about in the specs (nothing is hitting the actual rubygems servers).


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")
10 changes: 10 additions & 0 deletions spec/support/jruby_binstubs/gem
Original file line number Diff line number Diff line change
@@ -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