代码之家  ›  专栏  ›  技术社区  ›  Benjamin Bouchet

errno::enent:功能测试后没有这样的文件或目录@rb_file_s_mtime

  •  6
  • Benjamin Bouchet  · 技术社区  · 6 年前

    运行功能rspecs时,我收到以下错误(此消息底部的完整跟踪)

    彪马输出

    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/\u form”部分,并显示带有以下代码的附件文件(即PNG图像): = image_tag url_for(product.image.variant(resize: "120x120")) . 如果我对这行进行注释,错误就不会再发生了。

    测试本身每次都成功,错误似乎是在测试后触发的。我核实了我所有的 after 街区,似乎发生在我所有的街区之后。

    痕迹表明 ActiveStorage::DiskController.serve_file 触发错误。是不是ActiveStorage应用程序没有正常关闭?

    我在用 active_storage.service = :test 配置,但是 :file 给出相同的错误。

    图像破碎

    当测试失败时,我可以在“遥控”浏览器中看到图像没有显示,而是有一个损坏的图像图标。

    运行测试

    有一组由4个特征测试组成的测试正在进行“products/\u 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
    

    系统配置

    轨道版本 5.2.2

    红宝石版 :ruby 2.5.3p105(2018-10-18版本65156)[x86_-linux]

    RBEV版本 :rbenv 1.1.1-39-G59785F6

    RSPEC相关版本 :rspec 3.8.0-capybara 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
    

    正如我们所看到的,即使在测试之间,文件路径也是相同的。很难在每个测试之间清理数据库。所以在“某处”保存文件校验和,以便ActiveStorage重用相同的blob记录。结果,它重用了相同的文件路径,这就是为什么我有一个fileNotFound错误的原因。

    有什么想法吗?请注意,除了databasecleaner之外,我还有一个如下定义的after块:

    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

    等待他们的回应

    3 回复  |  直到 6 年前
        1
  •  0
  •   Old Pro    6 年前

    测试通过的事实仅仅意味着测试不依赖于所找到的图像。断开的图像链接通常不会导致测试失败,事实上,通常情况下,测试编写人员甚至不费心提供模拟图像,因为它(被认为)工作太多,与被测试的对象无关。

    伙计们,这就是为什么我们 ask for an example . 现在行动已经 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几乎是永久链接:它是附件数据库键的编码版本。ActiveStorage通过查找存储文件的位置并向可用于访问文件的URL(称为“服务URL”)发送302临时重定向来处理对URL的请求。当使用AWS S3存储文件时,服务URL可以是一个签名的URL,它可以很快过期,但是可以将浏览器直接连接到S3,而不必通过Web服务器作为中介。

    默认情况下,服务URL有效期为5分钟,并且ActiveRecord显式设置

    Cache-Control: max-age=300, private
    

    在302重定向上。浏览器缓存此重定向响应,在接下来的5分钟内,浏览器甚至不会尝试使用或验证发布的URL,它将立即用缓存的服务URL替换公共URL。

    不幸的是,虽然可以预见地重新创建了公共URL,但服务URL是随机生成的,因此当浏览器自动重定向时,以前有效的服务URL将不再工作。解决方案(或解决方法,取决于您的观点)是通过在文件名中包含时间戳来区分公共URL,这样测试就不会重用公共URL。

    旁白

    顺便说一下,你不应该使用 url_for 具有 image_tag . 把你的erb从

    = image_tag url_for(product.image.variant(resize: "120x120"))
    

    = image_tag(product.image.variant(resize: "120x120"))
    

    Rails足够智能,可以处理任何一种情况,但推荐使用后一种方式。

        2
  •  3
  •   codenamev    6 年前

    很可能是由于你如何设置你正在测试的图像。您可能希望将其更新为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)
    
        3
  •  0
  •   BvuRVKyUVlViVIc7    6 年前

    我不知道你的意思是什么,测试后会发生异常?

    测试通常应该是原子的和独立的,所以您创建的所有资源应该在测试完成后立即删除。 另外,测试是以随机顺序运行的,因此相互依赖的测试有时会失败,有时不会,这取决于运行顺序。

    你能分享失败的输出或测试吗?

    推荐文章