Really slow testing with file uploads
Asked Answered
F

5

11

I just added validations for a carrierwave image to a model and now tests run really slow. How can I speed up this process? I feel like there must be a better way.


I've been running without validations and used to be able to run through my rspec tests in about 140 seconds, but since i now validate presence of :display_pic I've had to add real file uploads to my project factory. This has upped it to 240 seconds! 140 was already on the heavy side, this is just crazy.

This is how the carrierwave github page recommends setting up Factory Girl:

FactoryGirl.define do
  factory :project do
    display_pic { File.open(File.join(Rails.root, 'spec', 'support', 'projects', 'display_pics', 'test.jpg')) }
  end
end

I made the above test.jpg just an empty text file, so its essentially as small a file as possible.

I also followed the carrierwave recommendation to setup testing:

CarrierWave.configure do |config|
  config.storage = :file
  config.enable_processing = false
end
Fala answered 8/3, 2012 at 9:16 Comment(0)
P
6

With validation happening now always that a instance is created the attribute display_pic is accessed and the code inside the brackets

{ File.open(File.join(Rails.root, 'spec', 'support', 'projects', 'display_pics', 'test.jpg')) } 

will be executed (it is lazily executed). This is causing the difference in time.

An option to avoid this is to set to_create for the factory definition what i don't recommend:

FactoryGirl.define do
  factory :project do
    display_pic { File.open(File.join(Rails.root, 'spec', 'support', 'projects', 'display_pics', 'test.jpg')) }

    to_create do |instance|
      instance.save!(:validate => false)
    end 
  end
end
Palmapalmaceous answered 14/3, 2012 at 22:36 Comment(4)
I would prefer not to bypass validations. In paperclip you can fill the pic field with a string, I was hoping for something along those lines.Fala
Thanks for the response, I do understand this is logical that it would take longer to load, the load time however is too much for my testing purposes, hoping for some way to minimize it.Fala
This is the best solution i've seen so far, and it works, thank youFala
My bad, I was MIA when the bounty expired, unless someone comes up with a way to mock file uploads with carrierwave ill just pass this 100 to you.Fala
R
9

Are your uploads stored locally or are they going to a cloud service like S3? If the latter, that's probably what's killing your test performance.

For tests, it's always good practice to mock out any external dependencies. If this is your problem, you should use a tool to mock the connection. I think the fog gem comes with built-in support for this.

Update: Regarding the answer by Jefferson Girao, I've used a trick to avoid opening a test file and instead using StringIO to simulate tests files for factory purposes. This performs better because it avoids the disk access:

def test_file_stream(filename = 'test.jpg', mime_type='image/jpg', content = '')
  StringIO.new(content).tap do |s|
    s.content_type = mime_type
    s.original_filename = filename
  end
end
Rachele answered 11/3, 2012 at 5:27 Comment(5)
Yes thanks, This is already done config.storage = :file line in the carrier wave configure block forces that, in production it is set to fogFala
Interesting. I'm out of guesses then. Sorry.Rachele
This happened (the 140 seconds of difference) b/c as factory girl evaluates it lazily it was never executed before. Now as you have the validation running the attribute will be acessed always.Palmapalmaceous
This didn't work for me. StringIO has neither a 'content_type' nor an 'original_filename' attribute.Blasphemous
@PabloMarambio -- I think Paperclip mixes these in.Rachele
P
6

With validation happening now always that a instance is created the attribute display_pic is accessed and the code inside the brackets

{ File.open(File.join(Rails.root, 'spec', 'support', 'projects', 'display_pics', 'test.jpg')) } 

will be executed (it is lazily executed). This is causing the difference in time.

An option to avoid this is to set to_create for the factory definition what i don't recommend:

FactoryGirl.define do
  factory :project do
    display_pic { File.open(File.join(Rails.root, 'spec', 'support', 'projects', 'display_pics', 'test.jpg')) }

    to_create do |instance|
      instance.save!(:validate => false)
    end 
  end
end
Palmapalmaceous answered 14/3, 2012 at 22:36 Comment(4)
I would prefer not to bypass validations. In paperclip you can fill the pic field with a string, I was hoping for something along those lines.Fala
Thanks for the response, I do understand this is logical that it would take longer to load, the load time however is too much for my testing purposes, hoping for some way to minimize it.Fala
This is the best solution i've seen so far, and it works, thank youFala
My bad, I was MIA when the bounty expired, unless someone comes up with a way to mock file uploads with carrierwave ill just pass this 100 to you.Fala
C
3

With inspiration from @jeffersongirao and @Wolfram Arnold:

FactoryGirl.define do
  sequence(:image) do |n|
    {
      tempfile: StringIO.new('{・㉨・}'),
      filename: "#{n}.jpeg",
      content_type: 'image/jpeg'
    }
  end

  factory :user do
    avatar { generate(:image) }
  end
end

Two key things here:

  1. CarrierWave's upload setters can make sense of a whole bunch of things. They do it by wrapping what they get in a CarrierWave::SanitizedFile. One of the things it can take is a hash, as here.

  2. CarrierWave::SanitizedFile cares whether the file is empty. I spent way too long wondering why it wouldn't accept my StringIO.new. It needs there to be something in there, but it doesn't care what. I gave it a koala.

Cochin answered 26/11, 2013 at 22:54 Comment(0)
T
1

You've probably considered this already, but is the 100 sec slowdown related to a single file upload? If it's an aggregate over all the tests, there must be a way to structure them so that you have a single test (or a few) that validate presence of the file, and the rest of the time you just mock it out.

Tinfoil answered 17/3, 2012 at 16:14 Comment(1)
Would you suggest something different from Jefferson Girao or agree with that?Fala
V
1

I just hit this problem, it may help to solve it by storing the file in memory rather than letting the factory read the image from disk every single test. I am using something like the following.

DUMMY_IMAGE = File.open(File.join(Rails.root, 'spec', 'support', 'projects', 'display_pics', 'test.jpg'))
FactoryGirl.define do
  factory :project do
    display_pic DUMMY_IMAGE
  end
end

I think I have saved about 10% time on my test suite, but my test suite is quiet variable so I can't be absolutely sure. Anyway just something to consider if anybody hits this issue.

I don't really like the solution that requires you to not validate the model. I guess this is similar to the StringIO answer by Wolfram Arnold, although I think this way is a bit easier to understand though.

Valma answered 16/1, 2013 at 23:23 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.