Dzisiaj pisząc kolejną porcję testów do Senpuu v5, po odpaleniu ich pod RCovem i Rubym 1.8.7 napotkałem na taki oto błąd:
Errno::EMFILE: Too many open files
Błąd ten związany jest z ilością otwartych jednocześnie plików (co ciekawe nie wystąpił podczas odpalania testów pod 1.9.2). Okazuje się, że tego typu wywołania:
file = File.new(File.join(Rails.root, "sciezka/do/pliku"), 'rb')
nie są nigdy zamykane, więc otwierając przykładowo 100 plików, żaden z nich nigdy nie zostanie zamknięty (przy zamykaniu aplikacji zostana). Z czasem ilość otwartych plików narasta i po pewnym czasie następuje zgłoszenie wyżej wspomnianego wyjątku. Rozwiązanie jest nader proste: zamykajmy otwierane pliki :)
W przypadku aplikacji jest to dość proste (robimy coś na pliku, kończymy i zamykamy). Jak jednak zapanować nad otwartymi plikami w testach? Przyznam szczerze, że jestem zbyt dużym leniem aby pamiętać o zamykaniu każdego pliku otworzonego w każdym teście. Z tego względu warto zbudować sobie prosty garbage collector który zajmie się tym za nas. Jak to zrobić pokaże na przykładzie podpięcia takiego kodu pod ActiveSupport::TestCase. Nic nie stoi na przeszkodzie aby tę metodę stosować także dla innych frameworków jak np. RSpec.
W metodze setup deklarujemy zmienną instancyjną, która przechowywać będzie nasze otwarte pliki:
def setup super # Jakis inny kod ... @opened_files = [] end
W metodzie teardown zamykamy wszystko:
def teardown super # Jakis inny kod @opened_files.each { |f| f.close } end
I ostatnia z metod - metoda otwierająca pliki i zapamiętująca je w naszej zmiennej:
def open_file(file = nil) return nil if filename.nil? file = File.new(File.join(Rails.root, file), 'rb') @opened_files << file file end
Od teraz, korzystając z metody open_file nie musimy się martwić otwartymi plikami. Zostaną automatycznie zamknięte podczas wywołania metody teardown.