5

在 rspec 执行的每个 poltergeist 测试中,如果我使用以下命令创建新会话:

Capybara.session_name="some_session_name"

phantomjs 实例作为子进程启动,并且在测试结束之前永远不会退出,从而导致我的构建服务器上出现 OOM。

我相信这是由于未能调用 driver.quit,如Poltergeist 自述文件中所述:

如果您手动运行几个 capybara 会话,请确保您在不再需要会话时调用了 session.driver.quit。忘记这一点会导致内存泄漏,并且您的系统资源可能会比您预期的更早耗尽。

但是,我调用page.driver.quitafter我的测试块。

下面是我的后块代码。 $adhoc_sessions是我每次设置时填充的全局变量Capybara.session_name,其值与设置的值匹配Capybara.session_name

config.after(:each) do
  if example.metadata[:js]

    $adhoc_sessions.each do |session_name|
      Capybara.using_session( session_name ) do
      page.driver.quit
      end
    end
    $adhoc_sessions.clear
  end

关于我可以在这里做得更好的任何建议?我没有调用一些清理命令吗?

4

1 回答 1

1

我找到了一个来自两个约束的解决方案:

  1. 我不认为您可以在 Capybara 中安全地调用driver.quit而不获得对私有@session_pool的访问权限,因为 Capybara 无法让用户一旦将会话从池中删除。因此,如果您调用 driver.quit 。退出会话,您无法从池中删除该会话,最终 Capybara 将尝试重置!会话,导致 Poltergeist 抛出 IOError,因为它通过 websockets 的内部通信未连接。
  2. 如果您在每次测试运行后都敲击整个会话池,并在执行此操作时退出每个会话中的每个 poltergeist 驱动程序,最终您将遇到TOO MANY OPEN FILES错误。IE,:

重新创建 TOO MANY OPEN FILES 错误的方法——不要使用这个!!

# you have to do quite a few test runs to cause the open files error
config.append_after(:each) do
  session_pool = Capybara.instance_variable_get("@session_pool")
  session_pool.each do | key, value |
    value.driver.quit
  end
  session_pool.clear
end    

我相信这是一个真正的 poltergeist 错误,但我不在乎......这就是为什么......在运行上面的代码时,我注意到创建一个 poltergeist 会话是一个明显缓慢且资源密集型的操作。所以,我决定我宁愿有一个永远不会消失的会话池……Capybara 的设计方式。

这种方法的唯一问题是像我一样使用 Capybara.session_name ,即在每个测试的基础上提出任意测试名称。也许在一个测试中,我希望每个 session_name 与用户的数据库 ID 相同。或者也许我想出了我在整个测试中使用的 5 个常量,以及 5 个不同的常量用于不同的测试。换句话说,我可能会在我的测试套件中使用 100 个 session_name,但对于任何给定的测试,我最多只有少数几个会话。所以一个好的解决方案会重用 poltergeist 会话,但让我在每次测试运行时使用任意会话名称。

这是我的解决方案

规范/实用程序.rb

# holds a single test's session name's, mapped to pooled session names
$capybara_session_mapper = {}

# called after each test,
# to make sure each test run has it's own map of session names
def reset_session_mapper
  $capybara_session_mapper.clear
end

# manages the mapped session name
def mapped_session_name(session_name)
  return :default if session_name == :default # special treatment for the built-in session
  $capybara_session_mapper[session_name] ||= $capybara_session_mapper.length
end

# in place of ever using Capybara.session_name directly, 
# this utility is used to handle the mapping of session names in a way across all tests runs
def in_client(name)  
  Capybara.session_name = mapped_session_name(session_name)

  yield
end

在 *spec_helper.rb* 中:

config.after(:each) do
  Capybara.reset_sessions!
  reset_session_mapper
end

直接使用 in_client 而不是 Capybara.session_name 的示例测试:

it "can't see a private thing until it is made public" do

  in_client(user1.id) do
    visit '/some/private/thing'
    expect(page).to have_selector('#private-notice')
  end

  in_client(user2.id) do
    visit '/expose/some/private/thing'
  end

  in_client(user1.id) do
    visit '/some/private/thing`
    expect(page).to have_selector('#private-content')
  end
end

- 从我的 github 答案中复制

于 2013-12-21T18:03:16.283 回答