Errno::ENOENT:功能测试后没有这样的文件或目录@rb_file_s_mtime
Errno::ENOENT: No such file or directory @ rb_file_s_mtime after feature tests
当 运行ning 功能 Rspecs 时,我收到以下错误(此消息底部的完整跟踪)
Puma 输出
Rack app error handling request { GET /rails/active_storage/disk/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaDdDRG9JYTJWNVNTSm5kbUZ5YVdGdWRITXZSM0IxTkRGRFRWQlFaVXRNYVUxaVpuQjZXVmw1UTNBMUx6RXhNR1V3TVRka01UWTVNalZrWXpkak5UQTNNamhqT1dNeE5UUmhOREl3TURjNVlXRTJaVFZtWTJNME16VmtZak5sTm1VNU4ySXhNemd3WldObFl6Z0dPZ1pGVkRvUVpHbHpjRzl6YVhScGIyNUpJajFwYm14cGJtVTdJR1pwYkdWdVlXMWxQU0pwYldGblpTNXdibWNpT3lCbWFXeGxibUZ0WlNvOVZWUkdMVGduSjJsdFlXZGxMbkJ1WndZN0JsUTZFV052Ym5SbGJuUmZkSGx3WlVraURtbHRZV2RsTDNCdVp3WTdCbFE9IiwiZXhwIjoiMjAxOS0wMS0yNVQxMToxNjoyOS40NDBaIiwicHVyIjoiYmxvYl9rZXkifX0=--db9451afdc95b292aa6a77c40e00ab0ceb687766/image.png }
#<Errno::ENOENT: No such file or directory @ rb_file_s_mtime - /path/to/rails/app/tmp/storage/va/ri/variants/Gpu41CMPPeKLiMbfpzYYyCp5/110e017d16925dc7c50728c9c154a420079aa6e5fcc435db3e6e97b1380ecec8>
(followed by the full trace at the end of this message)
Rspec输出
Failure/Error: last_modified = ::File.mtime(path).httpdate
Errno::ENOENT:
No such file or directory @ rb_file_s_mtime - /path/to/rails/app/tmp/storage/va/ri/variants/Gpu41CMPPeKLiMbfpzYYyCp5/110e017d16925dc7c50728c9c154a420079aa6e5fcc435db3e6e97b1380ecec8
(followed by the full trace at the end of this message)
上下文
product.image
是一个 ActiveStorage 附件。在测试期间,"products/_form" 部分被渲染并显示附件文件(即 png 图像),代码如下:= image_tag url_for(product.image.variant(resize: "120x120"))
。如果我注释掉这一行,错误就不会再发生了。
测试本身每次都成功,错误似乎是在测试后触发的。我验证了我所有的 after
块,它似乎发生在我所有的块之后。
跟踪表明 ActiveStorage::DiskController.serve_file
触发了错误。是不是 ActiveStorage 应用程序没有正常关闭?
我正在使用 active_storage.service = :test
配置,但 :file
给出了同样的错误。
图像损坏
虽然测试失败,但我可以在 "remote controlled" 浏览器中看到图像没有显示,而是有一个损坏的图像图标。
运行 测试
有一组 4 个特色测试正在通过 "products/_form" 部分的渲染。当我 运行 以随机顺序进行 4 次测试时,错误随机发生。有时我可以看到错误 2 次,有时 3 次。
当我 运行 单独测试时,错误永远不会出现。
我如何附上图片
这是我在进行测试之前用来将文件附加到模型的助手:
path = Rails.root.join 'spec', 'support', 'fixtures', 'img1.png'
raise 'Image does not exist' unless File.exist? path
product.image.attach io: File.open(path), filename: 'image.png'
我也试过用这个助手:
path = Rails.root.join 'spec', 'support', 'fixtures', 'img1.png'
raise 'Image does not exist' unless File.exist? path
blob = ActiveStorage::Blob.create_after_upload! io: path.open, \
filename: 'img1.png', content_type: 'image/png'
product.images.attach blob
还有这个帮手:
path = Rails.root.join 'spec', 'support', 'fixtures', 'img1.png'
raise 'Image does not exist' unless File.exist? path
byte_size = path.size
checksum = Digest::MD5.file(path).base64digest
blob = ActiveStorage::Blob.create_before_direct_upload!(filename: 'img1.png', \
byte_size: byte_size, checksum: checksum, content_type: 'image/png').tap do |blob|
ActiveStorage::Blob.service.upload(blob.key, path.open)
end
product.images.attach blob
系统配置
Rails 版本: 5.2.2
Ruby 版本:ruby 2.5.3p105(2018-10-18 修订版 65156)[x86_64-linux]
Rbenv 版本:rbenv 1.1.1-39-g59785f6
Rspec 相关版本: rspec 3.8.0 - 水豚 3.12.0 - selenium-webdriver 3.141.0
完整跟踪
# /opt/rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/rack-2.0.6/lib/rack/file.rb:63:in `mtime'
# /opt/rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/rack-2.0.6/lib/rack/file.rb:63:in `serving'
# /opt/rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/activestorage-5.2.2/app/controllers/active_storage/disk_controller.rb:42:in `serve_file'
# /opt/rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/activestorage-5.2.2/app/controllers/active_storage/disk_controller.rb:12:in `show'
# /opt/rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/actionpack-5.2.2/lib/action_controller/metal/basic_implicit_render.rb:6:in `send_action'
# /opt/rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/actionpack-5.2.2/lib/abstract_controller/base.rb:194:in `process_action'
# /opt/rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/actionpack-5.2.2/lib/action_controller/metal/rendering.rb:30:in `process_action'
# /opt/rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/actionpack-5.2.2/lib/abstract_controller/callbacks.rb:42:in `block in process_action'
# /opt/rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/activesupport-5.2.2/lib/active_support/callbacks.rb:132:in `run_callbacks'
# /opt/rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/actionpack-5.2.2/lib/abstract_controller/callbacks.rb:41:in `process_action'
# /opt/rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/actionpack-5.2.2/lib/action_controller/metal/rescue.rb:22:in `process_action'
# /opt/rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/actionpack-5.2.2/lib/action_controller/metal/instrumentation.rb:34:in `block in process_action'
# /opt/rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/activesupport-5.2.2/lib/active_support/notifications.rb:168:in `block in instrument'
# /opt/rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/activesupport-5.2.2/lib/active_support/notifications/instrumenter.rb:23:in `instrument'
# /opt/rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/activesupport-5.2.2/lib/active_support/notifications.rb:168:in `instrument'
# /opt/rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/actionpack-5.2.2/lib/action_controller/metal/instrumentation.rb:32:in `process_action'
# /opt/rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/actionpack-5.2.2/lib/action_controller/metal/params_wrapper.rb:256:in `process_action'
# /opt/rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/activerecord-5.2.2/lib/active_record/railties/controller_runtime.rb:24:in `process_action'
# /opt/rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/actionpack-5.2.2/lib/abstract_controller/base.rb:134:in `process'
# /opt/rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/actionview-5.2.2/lib/action_view/rendering.rb:32:in `process'
# /opt/rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/actionpack-5.2.2/lib/action_controller/metal.rb:191:in `dispatch'
# /opt/rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/actionpack-5.2.2/lib/action_controller/metal.rb:252:in `dispatch'
# /opt/rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/actionpack-5.2.2/lib/action_dispatch/routing/route_set.rb:52:in `dispatch'
# /opt/rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/actionpack-5.2.2/lib/action_dispatch/routing/route_set.rb:34:in `serve'
# /opt/rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/actionpack-5.2.2/lib/action_dispatch/journey/router.rb:52:in `block in serve'
# /opt/rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/actionpack-5.2.2/lib/action_dispatch/journey/router.rb:35:in `each'
# /opt/rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/actionpack-5.2.2/lib/action_dispatch/journey/router.rb:35:in `serve'
# /opt/rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/actionpack-5.2.2/lib/action_dispatch/routing/route_set.rb:840:in `call'
# /opt/rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/rack-2.0.6/lib/rack/tempfile_reaper.rb:15:in `call'
# /opt/rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/rack-2.0.6/lib/rack/etag.rb:25:in `call'
# /opt/rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/rack-2.0.6/lib/rack/conditional_get.rb:25:in `call'
# /opt/rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/rack-2.0.6/lib/rack/head.rb:12:in `call'
# /opt/rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/actionpack-5.2.2/lib/action_dispatch/http/content_security_policy.rb:18:in `call'
# /opt/rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/rack-2.0.6/lib/rack/session/abstract/id.rb:232:in `context'
# /opt/rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/rack-2.0.6/lib/rack/session/abstract/id.rb:226:in `call'
# /opt/rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/actionpack-5.2.2/lib/action_dispatch/middleware/cookies.rb:670:in `call'
# /opt/rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/actionpack-5.2.2/lib/action_dispatch/middleware/callbacks.rb:28:in `block in call'
# /opt/rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/activesupport-5.2.2/lib/active_support/callbacks.rb:98:in `run_callbacks'
# /opt/rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/actionpack-5.2.2/lib/action_dispatch/middleware/callbacks.rb:26:in `call'
# /opt/rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/actionpack-5.2.2/lib/action_dispatch/middleware/debug_exceptions.rb:61:in `call'
# /opt/rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/actionpack-5.2.2/lib/action_dispatch/middleware/show_exceptions.rb:33:in `call'
# /opt/rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/railties-5.2.2/lib/rails/rack/logger.rb:38:in `call_app'
# /opt/rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/railties-5.2.2/lib/rails/rack/logger.rb:26:in `block in call'
# /opt/rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/activesupport-5.2.2/lib/active_support/tagged_logging.rb:71:in `block in tagged'
# /opt/rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/activesupport-5.2.2/lib/active_support/tagged_logging.rb:28:in `tagged'
# /opt/rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/activesupport-5.2.2/lib/active_support/tagged_logging.rb:71:in `tagged'
# /opt/rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/railties-5.2.2/lib/rails/rack/logger.rb:26:in `call'
# /opt/rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/actionpack-5.2.2/lib/action_dispatch/middleware/remote_ip.rb:81:in `call'
# /opt/rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/actionpack-5.2.2/lib/action_dispatch/middleware/request_id.rb:27:in `call'
# /opt/rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/rack-2.0.6/lib/rack/method_override.rb:22:in `call'
# /opt/rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/rack-2.0.6/lib/rack/runtime.rb:22:in `call'
# /opt/rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/activesupport-5.2.2/lib/active_support/cache/strategy/local_cache_middleware.rb:29:in `call'
# /opt/rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/actionpack-5.2.2/lib/action_dispatch/middleware/executor.rb:14:in `call'
# /opt/rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/actionpack-5.2.2/lib/action_dispatch/middleware/static.rb:127:in `call'
# /opt/rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/rack-2.0.6/lib/rack/sendfile.rb:111:in `call'
# /opt/rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/railties-5.2.2/lib/rails/engine.rb:524:in `call'
# /opt/rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/rack-2.0.6/lib/rack/urlmap.rb:68:in `block in call'
# /opt/rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/rack-2.0.6/lib/rack/urlmap.rb:53:in `each'
# /opt/rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/rack-2.0.6/lib/rack/urlmap.rb:53:in `call'
# /opt/rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/capybara-3.12.0/lib/capybara/server/middleware.rb:48:in `call'
# /opt/rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/puma-3.12.0/lib/puma/configuration.rb:225:in `call'
# /opt/rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/puma-3.12.0/lib/puma/server.rb:658:in `handle_request'
# /opt/rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/puma-3.12.0/lib/puma/server.rb:472:in `process_client'
# /opt/rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/puma-3.12.0/lib/puma/server.rb:332:in `block in run'
# /opt/rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/puma-3.12.0/lib/puma/thread_pool.rb:133:in `block in spawn_thread'
#
# Showing full backtrace because every line was filtered out.
# See docs for RSpec::Configuration#backtrace_exclusion_patterns and
# RSpec::Configuration#backtrace_inclusion_patterns for more information.
# ------------------
# --- Caused by: ---
# Capybara::CapybaraError:
# Your application server raised an error - It has been raised in your test code because Capybara.raise_server_errors == true
# /opt/rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/capybara-3.12.0/lib/capybara/session.rb:147:in `raise_server_error!'
更新
(抱歉失言,我有急事要处理)
这是一个有趣的信息。我在this line之后插入了这个代码:puts "SERVING #{path} - Exist ? #{File.exist? path}"
这是输出(观察文件名和每行末尾的布尔值):
SERVING /tmp/active_storage_tests20190129-10797-1rebdf7/Me/GK/MeGKkqgbb7Sg4RbPoCYWR6pZ - Exist ? true
SERVING /tmp/active_storage_tests20190129-10797-1rebdf7/sG/3v/sG3vd2viGWmaf69pNXSChZPa - Exist ? true
SERVING /tmp/active_storage_tests20190129-10797-1rebdf7/fy/2C/fy2C8PpaR96YgCzwt5WVBLrQ - Exist ? true
SERVING /tmp/active_storage_tests20190129-10797-1rebdf7/sG/3v/sG3vd2viGWmaf69pNXSChZPa - Exist ? true
. (green dot)
SERVING /tmp/active_storage_tests20190129-10797-1rebdf7/Me/GK/MeGKkqgbb7Sg4RbPoCYWR6pZ - Exist ? false
SERVING /tmp/active_storage_tests20190129-10797-1rebdf7/sG/3v/sG3vd2viGWmaf69pNXSChZPa - Exist ? false
SERVING /tmp/active_storage_tests20190129-10797-1rebdf7/Me/GK/MeGKkqgbb7Sg4RbPoCYWR6pZ - Exist ? false
SERVING /tmp/active_storage_tests20190129-10797-1rebdf7/fy/2C/fy2C8PpaR96YgCzwt5WVBLrQ - Exist ? false
F (red F)
SERVING /tmp/active_storage_tests20190129-10797-1rebdf7/Me/GK/MeGKkqgbb7Sg4RbPoCYWR6pZ - Exist ? false
SERVING /tmp/active_storage_tests20190129-10797-1rebdf7/sG/3v/sG3vd2viGWmaf69pNXSChZPa - Exist ? false
SERVING /tmp/active_storage_tests20190129-10797-1rebdf7/Me/GK/MeGKkqgbb7Sg4RbPoCYWR6pZ - Exist ? false
SERVING /tmp/active_storage_tests20190129-10797-1rebdf7/fy/2C/fy2C8PpaR96YgCzwt5WVBLrQ - Exist ? false
F (red F)
SERVING /tmp/active_storage_tests20190129-10797-1rebdf7/Tx/DL/TxDLPEk3ykf8ETkURY5YZPgY - Exist ? true
正如我们所见,文件路径是相同的,即使在测试之间也是如此。每次测试之间都会清理数据库。所以有 "somewhere" 保存文件校验和的地方,以便 activestorage 重用相同的 blob 记录。结果它重用了相同的文件路径,这就是我遇到 FileNotFound 错误的原因。
有什么想法吗?请注意,除了 DatabaseCleaner 之外,我还有一个像这样定义的后块:
config.after(:each) do
ActiveStorage::Current.reset
ActiveStorage::Attachment.all.each(&:delete)
ActiveStorage::Blob.all.each(&:delete)
end
我怀疑有错误
已填写 PR 并申请重现 https://github.com/rails/rails/issues/34989
等待他们的回应
我不确定你的意思是测试后发生异常?
测试通常应该是原子的和独立的,因此您创建的所有资源都应该在测试完成后立即删除。
此外,测试 运行 以随机顺序排列,因此相互依赖的测试有时会失败 - 有时不会失败,具体取决于 运行 顺序。
你能分享失败的输出或测试吗?
您设置测试图像的方式可能有问题。您可能希望将其更新为 Rails 团队在他们自己的测试中处理它的方式:
module ActiveStorageHelpers
# ported from https://github.com/rails/rails/blob/5-2-stable/activestorage/test/test_helper.rb#L57
def create_file_blob(filename: "image.jpg", content_type: "image/jpeg", metadata: nil)
ActiveStorage::Blob.create_after_upload! io: file_fixture(filename).open, filename: filename, content_type: content_type, metadata: metadata
end
end
RSpec.configure do |config|
config.include ActiveStorageHelpers
end
然后在 spec/fixtures/file/images.jpg
中放置一个小图像文件(file_fixture
方法将在此处查找它)。
设置好后,您可以在功能测试中使用类似以下内容在模型上设置图像:
instance_of_model.images.attach(create_file_blob)
测试通过的事实仅意味着测试不依赖于找到的图像。损坏的图像 link 通常不会导致测试失败,事实上,通常情况下,测试编写者甚至懒得提供模拟图像,因为它(被认为)工作量太大而且与测试无关正在测试的东西。
各位看官,这就是我们ask for an example. Now that the OP has posted an example的原因,可以看出他的问题是浏览器缓存问题,通过更换
来解决
product.images.attach io: File.open(path), filename: 'image.png'
和
product.images.attach io: File.open(path), filename: "image-#{Time.now.strftime("%s%L")}.png"
在执行附件的助手中。如果没有这个例子,我不会解决这个问题。
说明
ActiveStorage 处理文件的保存和服务。 OP 用它来保存和提供图像,这绝对是它的目的。为了允许不同的服务以不同的方式提供文件,ActiveStorage 将发布的 URL 与实际的图像服务 URL.
分开
已发布的 URL 几乎是永久的link:它是附件数据库密钥的编码版本。 ActiveStorage 通过查找文件的存储位置并将 302 临时重定向发送到可用于访问文件的 URL 来处理对 URL 的请求,称为 "service URL"。当使用 AWS S3 存储文件时,服务 URL 可以是已签名的 URL,它会很快过期,但仍会将浏览器直接连接到 S3,而不必通过作为中介的 Web 服务器。
默认情况下,服务 URL 有效 5 分钟,ActiveRecord 显式设置
Cache-Control: max-age=300, private
在 302 重定向上。浏览器缓存此重定向响应,在接下来的 5 分钟内,浏览器甚至不会尝试使用或验证已发布的 URL,它会立即将 public URL 替换为缓存的服务 URL.
不幸的是,虽然 public URL 可以预见地重新创建,但服务 URL 是随机生成的,因此当浏览器进行自动重定向时,之前有效的服务 URL 不再有效。解决方案(或解决方法,取决于您的观点)是通过在文件名中包含时间戳来区分 public URL,这样测试就不会重复使用 public URLs.
旁注
顺便说一句,您不应该将 url_for
与 image_tag
一起使用。将您的 ERB 更改为
= image_tag url_for(product.image.variant(resize: "120x120"))
至
= image_tag(product.image.variant(resize: "120x120"))
Rails 足够聪明,可以处理任何一种情况,但后一种方式是推荐的方式。
当 运行ning 功能 Rspecs 时,我收到以下错误(此消息底部的完整跟踪)
Puma 输出
Rack app error handling request { GET /rails/active_storage/disk/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaDdDRG9JYTJWNVNTSm5kbUZ5YVdGdWRITXZSM0IxTkRGRFRWQlFaVXRNYVUxaVpuQjZXVmw1UTNBMUx6RXhNR1V3TVRka01UWTVNalZrWXpkak5UQTNNamhqT1dNeE5UUmhOREl3TURjNVlXRTJaVFZtWTJNME16VmtZak5sTm1VNU4ySXhNemd3WldObFl6Z0dPZ1pGVkRvUVpHbHpjRzl6YVhScGIyNUpJajFwYm14cGJtVTdJR1pwYkdWdVlXMWxQU0pwYldGblpTNXdibWNpT3lCbWFXeGxibUZ0WlNvOVZWUkdMVGduSjJsdFlXZGxMbkJ1WndZN0JsUTZFV052Ym5SbGJuUmZkSGx3WlVraURtbHRZV2RsTDNCdVp3WTdCbFE9IiwiZXhwIjoiMjAxOS0wMS0yNVQxMToxNjoyOS40NDBaIiwicHVyIjoiYmxvYl9rZXkifX0=--db9451afdc95b292aa6a77c40e00ab0ceb687766/image.png }
#<Errno::ENOENT: No such file or directory @ rb_file_s_mtime - /path/to/rails/app/tmp/storage/va/ri/variants/Gpu41CMPPeKLiMbfpzYYyCp5/110e017d16925dc7c50728c9c154a420079aa6e5fcc435db3e6e97b1380ecec8>
(followed by the full trace at the end of this message)
Rspec输出
Failure/Error: last_modified = ::File.mtime(path).httpdate
Errno::ENOENT:
No such file or directory @ rb_file_s_mtime - /path/to/rails/app/tmp/storage/va/ri/variants/Gpu41CMPPeKLiMbfpzYYyCp5/110e017d16925dc7c50728c9c154a420079aa6e5fcc435db3e6e97b1380ecec8
(followed by the full trace at the end of this message)
上下文
product.image
是一个 ActiveStorage 附件。在测试期间,"products/_form" 部分被渲染并显示附件文件(即 png 图像),代码如下:= image_tag url_for(product.image.variant(resize: "120x120"))
。如果我注释掉这一行,错误就不会再发生了。
测试本身每次都成功,错误似乎是在测试后触发的。我验证了我所有的 after
块,它似乎发生在我所有的块之后。
跟踪表明 ActiveStorage::DiskController.serve_file
触发了错误。是不是 ActiveStorage 应用程序没有正常关闭?
我正在使用 active_storage.service = :test
配置,但 :file
给出了同样的错误。
图像损坏
虽然测试失败,但我可以在 "remote controlled" 浏览器中看到图像没有显示,而是有一个损坏的图像图标。
运行 测试
有一组 4 个特色测试正在通过 "products/_form" 部分的渲染。当我 运行 以随机顺序进行 4 次测试时,错误随机发生。有时我可以看到错误 2 次,有时 3 次。
当我 运行 单独测试时,错误永远不会出现。
我如何附上图片
这是我在进行测试之前用来将文件附加到模型的助手:
path = Rails.root.join 'spec', 'support', 'fixtures', 'img1.png'
raise 'Image does not exist' unless File.exist? path
product.image.attach io: File.open(path), filename: 'image.png'
我也试过用这个助手:
path = Rails.root.join 'spec', 'support', 'fixtures', 'img1.png'
raise 'Image does not exist' unless File.exist? path
blob = ActiveStorage::Blob.create_after_upload! io: path.open, \
filename: 'img1.png', content_type: 'image/png'
product.images.attach blob
还有这个帮手:
path = Rails.root.join 'spec', 'support', 'fixtures', 'img1.png'
raise 'Image does not exist' unless File.exist? path
byte_size = path.size
checksum = Digest::MD5.file(path).base64digest
blob = ActiveStorage::Blob.create_before_direct_upload!(filename: 'img1.png', \
byte_size: byte_size, checksum: checksum, content_type: 'image/png').tap do |blob|
ActiveStorage::Blob.service.upload(blob.key, path.open)
end
product.images.attach blob
系统配置
Rails 版本: 5.2.2
Ruby 版本:ruby 2.5.3p105(2018-10-18 修订版 65156)[x86_64-linux]
Rbenv 版本:rbenv 1.1.1-39-g59785f6
Rspec 相关版本: rspec 3.8.0 - 水豚 3.12.0 - selenium-webdriver 3.141.0
完整跟踪
# /opt/rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/rack-2.0.6/lib/rack/file.rb:63:in `mtime'
# /opt/rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/rack-2.0.6/lib/rack/file.rb:63:in `serving'
# /opt/rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/activestorage-5.2.2/app/controllers/active_storage/disk_controller.rb:42:in `serve_file'
# /opt/rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/activestorage-5.2.2/app/controllers/active_storage/disk_controller.rb:12:in `show'
# /opt/rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/actionpack-5.2.2/lib/action_controller/metal/basic_implicit_render.rb:6:in `send_action'
# /opt/rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/actionpack-5.2.2/lib/abstract_controller/base.rb:194:in `process_action'
# /opt/rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/actionpack-5.2.2/lib/action_controller/metal/rendering.rb:30:in `process_action'
# /opt/rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/actionpack-5.2.2/lib/abstract_controller/callbacks.rb:42:in `block in process_action'
# /opt/rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/activesupport-5.2.2/lib/active_support/callbacks.rb:132:in `run_callbacks'
# /opt/rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/actionpack-5.2.2/lib/abstract_controller/callbacks.rb:41:in `process_action'
# /opt/rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/actionpack-5.2.2/lib/action_controller/metal/rescue.rb:22:in `process_action'
# /opt/rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/actionpack-5.2.2/lib/action_controller/metal/instrumentation.rb:34:in `block in process_action'
# /opt/rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/activesupport-5.2.2/lib/active_support/notifications.rb:168:in `block in instrument'
# /opt/rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/activesupport-5.2.2/lib/active_support/notifications/instrumenter.rb:23:in `instrument'
# /opt/rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/activesupport-5.2.2/lib/active_support/notifications.rb:168:in `instrument'
# /opt/rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/actionpack-5.2.2/lib/action_controller/metal/instrumentation.rb:32:in `process_action'
# /opt/rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/actionpack-5.2.2/lib/action_controller/metal/params_wrapper.rb:256:in `process_action'
# /opt/rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/activerecord-5.2.2/lib/active_record/railties/controller_runtime.rb:24:in `process_action'
# /opt/rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/actionpack-5.2.2/lib/abstract_controller/base.rb:134:in `process'
# /opt/rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/actionview-5.2.2/lib/action_view/rendering.rb:32:in `process'
# /opt/rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/actionpack-5.2.2/lib/action_controller/metal.rb:191:in `dispatch'
# /opt/rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/actionpack-5.2.2/lib/action_controller/metal.rb:252:in `dispatch'
# /opt/rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/actionpack-5.2.2/lib/action_dispatch/routing/route_set.rb:52:in `dispatch'
# /opt/rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/actionpack-5.2.2/lib/action_dispatch/routing/route_set.rb:34:in `serve'
# /opt/rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/actionpack-5.2.2/lib/action_dispatch/journey/router.rb:52:in `block in serve'
# /opt/rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/actionpack-5.2.2/lib/action_dispatch/journey/router.rb:35:in `each'
# /opt/rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/actionpack-5.2.2/lib/action_dispatch/journey/router.rb:35:in `serve'
# /opt/rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/actionpack-5.2.2/lib/action_dispatch/routing/route_set.rb:840:in `call'
# /opt/rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/rack-2.0.6/lib/rack/tempfile_reaper.rb:15:in `call'
# /opt/rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/rack-2.0.6/lib/rack/etag.rb:25:in `call'
# /opt/rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/rack-2.0.6/lib/rack/conditional_get.rb:25:in `call'
# /opt/rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/rack-2.0.6/lib/rack/head.rb:12:in `call'
# /opt/rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/actionpack-5.2.2/lib/action_dispatch/http/content_security_policy.rb:18:in `call'
# /opt/rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/rack-2.0.6/lib/rack/session/abstract/id.rb:232:in `context'
# /opt/rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/rack-2.0.6/lib/rack/session/abstract/id.rb:226:in `call'
# /opt/rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/actionpack-5.2.2/lib/action_dispatch/middleware/cookies.rb:670:in `call'
# /opt/rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/actionpack-5.2.2/lib/action_dispatch/middleware/callbacks.rb:28:in `block in call'
# /opt/rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/activesupport-5.2.2/lib/active_support/callbacks.rb:98:in `run_callbacks'
# /opt/rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/actionpack-5.2.2/lib/action_dispatch/middleware/callbacks.rb:26:in `call'
# /opt/rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/actionpack-5.2.2/lib/action_dispatch/middleware/debug_exceptions.rb:61:in `call'
# /opt/rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/actionpack-5.2.2/lib/action_dispatch/middleware/show_exceptions.rb:33:in `call'
# /opt/rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/railties-5.2.2/lib/rails/rack/logger.rb:38:in `call_app'
# /opt/rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/railties-5.2.2/lib/rails/rack/logger.rb:26:in `block in call'
# /opt/rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/activesupport-5.2.2/lib/active_support/tagged_logging.rb:71:in `block in tagged'
# /opt/rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/activesupport-5.2.2/lib/active_support/tagged_logging.rb:28:in `tagged'
# /opt/rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/activesupport-5.2.2/lib/active_support/tagged_logging.rb:71:in `tagged'
# /opt/rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/railties-5.2.2/lib/rails/rack/logger.rb:26:in `call'
# /opt/rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/actionpack-5.2.2/lib/action_dispatch/middleware/remote_ip.rb:81:in `call'
# /opt/rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/actionpack-5.2.2/lib/action_dispatch/middleware/request_id.rb:27:in `call'
# /opt/rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/rack-2.0.6/lib/rack/method_override.rb:22:in `call'
# /opt/rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/rack-2.0.6/lib/rack/runtime.rb:22:in `call'
# /opt/rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/activesupport-5.2.2/lib/active_support/cache/strategy/local_cache_middleware.rb:29:in `call'
# /opt/rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/actionpack-5.2.2/lib/action_dispatch/middleware/executor.rb:14:in `call'
# /opt/rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/actionpack-5.2.2/lib/action_dispatch/middleware/static.rb:127:in `call'
# /opt/rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/rack-2.0.6/lib/rack/sendfile.rb:111:in `call'
# /opt/rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/railties-5.2.2/lib/rails/engine.rb:524:in `call'
# /opt/rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/rack-2.0.6/lib/rack/urlmap.rb:68:in `block in call'
# /opt/rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/rack-2.0.6/lib/rack/urlmap.rb:53:in `each'
# /opt/rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/rack-2.0.6/lib/rack/urlmap.rb:53:in `call'
# /opt/rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/capybara-3.12.0/lib/capybara/server/middleware.rb:48:in `call'
# /opt/rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/puma-3.12.0/lib/puma/configuration.rb:225:in `call'
# /opt/rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/puma-3.12.0/lib/puma/server.rb:658:in `handle_request'
# /opt/rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/puma-3.12.0/lib/puma/server.rb:472:in `process_client'
# /opt/rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/puma-3.12.0/lib/puma/server.rb:332:in `block in run'
# /opt/rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/puma-3.12.0/lib/puma/thread_pool.rb:133:in `block in spawn_thread'
#
# Showing full backtrace because every line was filtered out.
# See docs for RSpec::Configuration#backtrace_exclusion_patterns and
# RSpec::Configuration#backtrace_inclusion_patterns for more information.
# ------------------
# --- Caused by: ---
# Capybara::CapybaraError:
# Your application server raised an error - It has been raised in your test code because Capybara.raise_server_errors == true
# /opt/rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/capybara-3.12.0/lib/capybara/session.rb:147:in `raise_server_error!'
更新
(抱歉失言,我有急事要处理)
这是一个有趣的信息。我在this line之后插入了这个代码:puts "SERVING #{path} - Exist ? #{File.exist? path}"
这是输出(观察文件名和每行末尾的布尔值):
SERVING /tmp/active_storage_tests20190129-10797-1rebdf7/Me/GK/MeGKkqgbb7Sg4RbPoCYWR6pZ - Exist ? true
SERVING /tmp/active_storage_tests20190129-10797-1rebdf7/sG/3v/sG3vd2viGWmaf69pNXSChZPa - Exist ? true
SERVING /tmp/active_storage_tests20190129-10797-1rebdf7/fy/2C/fy2C8PpaR96YgCzwt5WVBLrQ - Exist ? true
SERVING /tmp/active_storage_tests20190129-10797-1rebdf7/sG/3v/sG3vd2viGWmaf69pNXSChZPa - Exist ? true
. (green dot)
SERVING /tmp/active_storage_tests20190129-10797-1rebdf7/Me/GK/MeGKkqgbb7Sg4RbPoCYWR6pZ - Exist ? false
SERVING /tmp/active_storage_tests20190129-10797-1rebdf7/sG/3v/sG3vd2viGWmaf69pNXSChZPa - Exist ? false
SERVING /tmp/active_storage_tests20190129-10797-1rebdf7/Me/GK/MeGKkqgbb7Sg4RbPoCYWR6pZ - Exist ? false
SERVING /tmp/active_storage_tests20190129-10797-1rebdf7/fy/2C/fy2C8PpaR96YgCzwt5WVBLrQ - Exist ? false
F (red F)
SERVING /tmp/active_storage_tests20190129-10797-1rebdf7/Me/GK/MeGKkqgbb7Sg4RbPoCYWR6pZ - Exist ? false
SERVING /tmp/active_storage_tests20190129-10797-1rebdf7/sG/3v/sG3vd2viGWmaf69pNXSChZPa - Exist ? false
SERVING /tmp/active_storage_tests20190129-10797-1rebdf7/Me/GK/MeGKkqgbb7Sg4RbPoCYWR6pZ - Exist ? false
SERVING /tmp/active_storage_tests20190129-10797-1rebdf7/fy/2C/fy2C8PpaR96YgCzwt5WVBLrQ - Exist ? false
F (red F)
SERVING /tmp/active_storage_tests20190129-10797-1rebdf7/Tx/DL/TxDLPEk3ykf8ETkURY5YZPgY - Exist ? true
正如我们所见,文件路径是相同的,即使在测试之间也是如此。每次测试之间都会清理数据库。所以有 "somewhere" 保存文件校验和的地方,以便 activestorage 重用相同的 blob 记录。结果它重用了相同的文件路径,这就是我遇到 FileNotFound 错误的原因。
有什么想法吗?请注意,除了 DatabaseCleaner 之外,我还有一个像这样定义的后块:
config.after(:each) do
ActiveStorage::Current.reset
ActiveStorage::Attachment.all.each(&:delete)
ActiveStorage::Blob.all.each(&:delete)
end
我怀疑有错误
已填写 PR 并申请重现 https://github.com/rails/rails/issues/34989
等待他们的回应
我不确定你的意思是测试后发生异常?
测试通常应该是原子的和独立的,因此您创建的所有资源都应该在测试完成后立即删除。 此外,测试 运行 以随机顺序排列,因此相互依赖的测试有时会失败 - 有时不会失败,具体取决于 运行 顺序。
你能分享失败的输出或测试吗?
您设置测试图像的方式可能有问题。您可能希望将其更新为 Rails 团队在他们自己的测试中处理它的方式:
module ActiveStorageHelpers
# ported from https://github.com/rails/rails/blob/5-2-stable/activestorage/test/test_helper.rb#L57
def create_file_blob(filename: "image.jpg", content_type: "image/jpeg", metadata: nil)
ActiveStorage::Blob.create_after_upload! io: file_fixture(filename).open, filename: filename, content_type: content_type, metadata: metadata
end
end
RSpec.configure do |config|
config.include ActiveStorageHelpers
end
然后在 spec/fixtures/file/images.jpg
中放置一个小图像文件(file_fixture
方法将在此处查找它)。
设置好后,您可以在功能测试中使用类似以下内容在模型上设置图像:
instance_of_model.images.attach(create_file_blob)
测试通过的事实仅意味着测试不依赖于找到的图像。损坏的图像 link 通常不会导致测试失败,事实上,通常情况下,测试编写者甚至懒得提供模拟图像,因为它(被认为)工作量太大而且与测试无关正在测试的东西。
各位看官,这就是我们ask for an example. Now that the OP has posted an example的原因,可以看出他的问题是浏览器缓存问题,通过更换
来解决product.images.attach io: File.open(path), filename: 'image.png'
和
product.images.attach io: File.open(path), filename: "image-#{Time.now.strftime("%s%L")}.png"
在执行附件的助手中。如果没有这个例子,我不会解决这个问题。
说明
ActiveStorage 处理文件的保存和服务。 OP 用它来保存和提供图像,这绝对是它的目的。为了允许不同的服务以不同的方式提供文件,ActiveStorage 将发布的 URL 与实际的图像服务 URL.
分开已发布的 URL 几乎是永久的link:它是附件数据库密钥的编码版本。 ActiveStorage 通过查找文件的存储位置并将 302 临时重定向发送到可用于访问文件的 URL 来处理对 URL 的请求,称为 "service URL"。当使用 AWS S3 存储文件时,服务 URL 可以是已签名的 URL,它会很快过期,但仍会将浏览器直接连接到 S3,而不必通过作为中介的 Web 服务器。
默认情况下,服务 URL 有效 5 分钟,ActiveRecord 显式设置
Cache-Control: max-age=300, private
在 302 重定向上。浏览器缓存此重定向响应,在接下来的 5 分钟内,浏览器甚至不会尝试使用或验证已发布的 URL,它会立即将 public URL 替换为缓存的服务 URL.
不幸的是,虽然 public URL 可以预见地重新创建,但服务 URL 是随机生成的,因此当浏览器进行自动重定向时,之前有效的服务 URL 不再有效。解决方案(或解决方法,取决于您的观点)是通过在文件名中包含时间戳来区分 public URL,这样测试就不会重复使用 public URLs.
旁注
顺便说一句,您不应该将 url_for
与 image_tag
一起使用。将您的 ERB 更改为
= image_tag url_for(product.image.variant(resize: "120x120"))
至
= image_tag(product.image.variant(resize: "120x120"))
Rails 足够聪明,可以处理任何一种情况,但后一种方式是推荐的方式。