2019-10-12 23:23:16 +08:00
|
|
|
# frozen_string_literal: true
|
2022-01-13 17:05:07 +08:00
|
|
|
|
2023-12-14 17:47:17 +08:00
|
|
|
require "test_helper"
|
|
|
|
require "tempfile"
|
|
|
|
require "tmpdir"
|
|
|
|
require "fileutils"
|
2017-01-19 00:28:16 +08:00
|
|
|
|
|
|
|
class CompileCacheKeyFormatTest < Minitest::Test
|
2021-02-03 19:34:16 +08:00
|
|
|
FILE = File.expand_path(__FILE__)
|
2023-12-14 17:47:17 +08:00
|
|
|
include CompileCacheISeqHelper
|
|
|
|
include TmpdirHelper
|
2017-01-19 00:28:16 +08:00
|
|
|
|
|
|
|
R = {
|
2019-02-07 02:29:01 +08:00
|
|
|
version: 0...4,
|
2019-12-05 05:51:11 +08:00
|
|
|
ruby_platform: 4...8,
|
2017-05-24 12:06:48 +08:00
|
|
|
compile_option: 8...12,
|
2019-02-07 02:29:01 +08:00
|
|
|
ruby_revision: 12...16,
|
|
|
|
size: 16...24,
|
|
|
|
mtime: 24...32,
|
|
|
|
data_size: 32...40,
|
2022-01-13 17:05:07 +08:00
|
|
|
}.freeze
|
2017-01-19 00:28:16 +08:00
|
|
|
|
2024-01-31 16:38:11 +08:00
|
|
|
def teardown
|
|
|
|
Bootsnap::CompileCache::Native.revalidation = false
|
|
|
|
super
|
|
|
|
end
|
|
|
|
|
2017-01-19 00:28:16 +08:00
|
|
|
def test_key_version
|
2021-02-03 19:34:16 +08:00
|
|
|
key = cache_key_for_file(FILE)
|
2024-02-02 00:56:27 +08:00
|
|
|
exp = [6].pack("L")
|
2017-05-24 12:06:48 +08:00
|
|
|
assert_equal(exp, key[R[:version]])
|
2017-01-19 00:28:16 +08:00
|
|
|
end
|
|
|
|
|
|
|
|
def test_key_compile_option_stable
|
2021-02-03 19:34:16 +08:00
|
|
|
k1 = cache_key_for_file(FILE)
|
|
|
|
k2 = cache_key_for_file(FILE)
|
2022-01-13 17:05:07 +08:00
|
|
|
RubyVM::InstructionSequence.compile_option = {tailcall_optimization: true}
|
2021-02-03 19:34:16 +08:00
|
|
|
k3 = cache_key_for_file(FILE)
|
2017-01-19 00:28:16 +08:00
|
|
|
assert_equal(k1[R[:compile_option]], k2[R[:compile_option]])
|
|
|
|
refute_equal(k1[R[:compile_option]], k3[R[:compile_option]])
|
|
|
|
ensure
|
2022-01-13 17:05:07 +08:00
|
|
|
RubyVM::InstructionSequence.compile_option = {tailcall_optimization: false}
|
2017-01-19 00:28:16 +08:00
|
|
|
end
|
|
|
|
|
|
|
|
def test_key_ruby_revision
|
2021-02-03 19:34:16 +08:00
|
|
|
key = cache_key_for_file(FILE)
|
2019-10-12 23:23:16 +08:00
|
|
|
exp = if RUBY_REVISION.is_a?(String)
|
|
|
|
[Help.fnv1a_64(RUBY_REVISION) >> 32].pack("L")
|
2019-07-11 14:57:33 +08:00
|
|
|
else
|
2019-10-12 23:23:16 +08:00
|
|
|
[RUBY_REVISION].pack("L")
|
2019-07-11 14:57:33 +08:00
|
|
|
end
|
2017-01-19 00:28:16 +08:00
|
|
|
assert_equal(exp, key[R[:ruby_revision]])
|
|
|
|
end
|
|
|
|
|
2017-05-24 12:06:48 +08:00
|
|
|
def test_key_size
|
2021-02-03 19:34:16 +08:00
|
|
|
key = cache_key_for_file(FILE)
|
|
|
|
exp = File.size(FILE)
|
Drop Ruby 2.3 support.
Ref: https://github.com/Shopify/bootsnap/pull/402
Ref: https://bugs.ruby-lang.org/issues/10222
Ref: https://github.com/ruby/ruby/commit/5754f15975d82896c2566ca01b9ce7b3122d67ec
Ref: https://github.com/ruby/ruby/commit/b6d3927e16357408720203a949cfa8741b9ebf6c
Up to Ruby 2.3, `require` would resolve symlinks, but `require_relative` wouldn't:
```ruby
require 'fileutils'
FileUtils.mkdir_p("realpath")
File.write("realpath/a.rb", "p :a_loaded")
File.symlink("realpath", "symlink") rescue nil
$LOAD_PATH.unshift(File.realpath(__dir__) + "/symlink")
require "a.rb" # load symlink/a.rb in 2.3 and older, load realpath/a.rb on 2.4 and newer
require_relative "realpath/a.rb" # noop on 2.4+
```
This would easily cause double loading issue when `require` and `require_relative`
were mixed, but was fixed in 2.4 (https://bugs.ruby-lang.org/issues/10222).
The problem is that `Bootsnap` kinda negated this fix, because `realpath()`
wouldn't be applied to absolute paths:
```ruby
require 'fileutils'
FileUtils.mkdir_p("realpath")
File.write("realpath/a.rb", "p :a_loaded")
File.symlink("realpath", "symlink") rescue nil
$LOAD_PATH.unshift(File.realpath(__dir__) + "/symlink")
require File.expand_path("symlink/a.rb") # load symlink/a.rb in 3.0 and older, load realpath/a.rb on 3.1 and newer
require_relative "realpath/a.rb" # noop on 3.1+
```
And for performance reasons, Bootsnap tried really hard not to call `realpath`,
as it's a syscall, instead it used `expand_path`, which is entirely in use
space and doesn't reach to the file system. So if you had a `symlink` in
`$LOAD_PATH`, `bootcsnap` would perpetuate this bug, which led to the
addition of https://github.com/Shopify/bootsnap/pull/136.
This was ultimately fixed in Ruby 3.1 (https://bugs.ruby-lang.org/issues/17885),
now `realpath` is applied even on absolute paths.
While `realpath` is indeed expensive, I think the performance impact is ok if
we only call it for `$LOAD_PATH` members, rather than for all requirable files.
So if you have X gems, it's going to be more or less X `realpath` calls.
It would stay a problem if a gem actually contained symlinks and used
`require_relative`, but it's quite the stretch, and with 3.1 now
handling it, it's not worth keeping such workaround.
See: https://github.com/Shopify/bootsnap/pull/402
2022-02-07 20:14:21 +08:00
|
|
|
act = key[R[:size]].unpack1("Q")
|
2017-05-24 12:06:48 +08:00
|
|
|
assert_equal(exp, act)
|
2017-01-19 00:28:16 +08:00
|
|
|
end
|
|
|
|
|
|
|
|
def test_key_mtime
|
2021-02-03 19:34:16 +08:00
|
|
|
key = cache_key_for_file(FILE)
|
|
|
|
exp = File.mtime(FILE).to_i
|
Drop Ruby 2.3 support.
Ref: https://github.com/Shopify/bootsnap/pull/402
Ref: https://bugs.ruby-lang.org/issues/10222
Ref: https://github.com/ruby/ruby/commit/5754f15975d82896c2566ca01b9ce7b3122d67ec
Ref: https://github.com/ruby/ruby/commit/b6d3927e16357408720203a949cfa8741b9ebf6c
Up to Ruby 2.3, `require` would resolve symlinks, but `require_relative` wouldn't:
```ruby
require 'fileutils'
FileUtils.mkdir_p("realpath")
File.write("realpath/a.rb", "p :a_loaded")
File.symlink("realpath", "symlink") rescue nil
$LOAD_PATH.unshift(File.realpath(__dir__) + "/symlink")
require "a.rb" # load symlink/a.rb in 2.3 and older, load realpath/a.rb on 2.4 and newer
require_relative "realpath/a.rb" # noop on 2.4+
```
This would easily cause double loading issue when `require` and `require_relative`
were mixed, but was fixed in 2.4 (https://bugs.ruby-lang.org/issues/10222).
The problem is that `Bootsnap` kinda negated this fix, because `realpath()`
wouldn't be applied to absolute paths:
```ruby
require 'fileutils'
FileUtils.mkdir_p("realpath")
File.write("realpath/a.rb", "p :a_loaded")
File.symlink("realpath", "symlink") rescue nil
$LOAD_PATH.unshift(File.realpath(__dir__) + "/symlink")
require File.expand_path("symlink/a.rb") # load symlink/a.rb in 3.0 and older, load realpath/a.rb on 3.1 and newer
require_relative "realpath/a.rb" # noop on 3.1+
```
And for performance reasons, Bootsnap tried really hard not to call `realpath`,
as it's a syscall, instead it used `expand_path`, which is entirely in use
space and doesn't reach to the file system. So if you had a `symlink` in
`$LOAD_PATH`, `bootcsnap` would perpetuate this bug, which led to the
addition of https://github.com/Shopify/bootsnap/pull/136.
This was ultimately fixed in Ruby 3.1 (https://bugs.ruby-lang.org/issues/17885),
now `realpath` is applied even on absolute paths.
While `realpath` is indeed expensive, I think the performance impact is ok if
we only call it for `$LOAD_PATH` members, rather than for all requirable files.
So if you have X gems, it's going to be more or less X `realpath` calls.
It would stay a problem if a gem actually contained symlinks and used
`require_relative`, but it's quite the stretch, and with 3.1 now
handling it, it's not worth keeping such workaround.
See: https://github.com/Shopify/bootsnap/pull/402
2022-02-07 20:14:21 +08:00
|
|
|
act = key[R[:mtime]].unpack1("Q")
|
2017-05-24 12:06:48 +08:00
|
|
|
assert_equal(exp, act)
|
2017-01-19 00:28:16 +08:00
|
|
|
end
|
|
|
|
|
2017-05-24 12:06:48 +08:00
|
|
|
def test_fetch
|
2024-01-30 17:21:45 +08:00
|
|
|
target = Help.set_file("a.rb", "foo = 1")
|
2020-09-05 10:23:09 +08:00
|
|
|
|
2024-01-30 17:21:45 +08:00
|
|
|
cache_dir = File.join(@tmp_dir, "compile_cache")
|
|
|
|
actual = Bootsnap::CompileCache::Native.fetch(cache_dir, target, TestHandler, nil)
|
2020-09-05 10:23:09 +08:00
|
|
|
assert_equal("NEATO #{target.upcase}", actual)
|
|
|
|
|
2024-01-30 17:21:45 +08:00
|
|
|
entries = Dir["#{cache_dir}/**/*"].select { |f| File.file?(f) }
|
|
|
|
assert_equal 1, entries.size
|
|
|
|
cache_file = entries.first
|
|
|
|
|
|
|
|
data = File.read(cache_file)
|
2022-11-09 08:42:30 +08:00
|
|
|
assert_equal("neato #{target}", data.force_encoding(Encoding::BINARY)[64..])
|
2020-09-05 10:23:09 +08:00
|
|
|
|
2024-01-30 17:21:45 +08:00
|
|
|
actual = Bootsnap::CompileCache::Native.fetch(cache_dir, target, TestHandler, nil)
|
2020-09-05 10:23:09 +08:00
|
|
|
assert_equal("NEATO #{target.upcase}", actual)
|
2017-01-19 00:28:16 +08:00
|
|
|
end
|
|
|
|
|
2024-01-31 16:38:11 +08:00
|
|
|
def test_revalidation
|
|
|
|
Bootsnap::CompileCache::Native.revalidation = true
|
|
|
|
cache_dir = File.join(@tmp_dir, "compile_cache")
|
|
|
|
|
|
|
|
target = Help.set_file("a.rb", "foo = 1")
|
|
|
|
|
|
|
|
actual = Bootsnap::CompileCache::Native.fetch(cache_dir, target, TestHandler, nil)
|
|
|
|
assert_equal("NEATO #{target.upcase}", actual)
|
|
|
|
|
|
|
|
10.times do
|
|
|
|
FileUtils.touch(target, mtime: File.mtime(target) + 42)
|
|
|
|
actual = Bootsnap::CompileCache::Native.fetch(cache_dir, target, TestHandler, nil)
|
|
|
|
assert_equal("NEATO #{target.upcase}", actual)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2018-01-13 07:15:55 +08:00
|
|
|
def test_unexistent_fetch
|
|
|
|
assert_raises(Errno::ENOENT) do
|
2022-01-13 17:05:07 +08:00
|
|
|
Bootsnap::CompileCache::Native.fetch(@tmp_dir, "123", Bootsnap::CompileCache::ISeq, nil)
|
2018-01-13 07:15:55 +08:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2017-05-24 12:06:48 +08:00
|
|
|
private
|
2017-01-19 00:28:16 +08:00
|
|
|
|
2017-05-24 12:06:48 +08:00
|
|
|
def cache_key_for_file(file)
|
2021-01-27 17:31:01 +08:00
|
|
|
Bootsnap::CompileCache::Native.fetch(@tmp_dir, file, TestHandler, nil)
|
2021-02-03 19:34:16 +08:00
|
|
|
data = File.binread(Help.cache_path(@tmp_dir, file))
|
|
|
|
data.byteslice(0..31)
|
2017-01-19 00:28:16 +08:00
|
|
|
end
|
|
|
|
end
|