Logo Search packages:      
Sourcecode: passenger version File versions  Download package

ApplicationPoolTest.cpp

#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <cstring>
#include <cstdlib>
#include <cerrno>
#include <signal.h>

/**
 * This file is used as a template to test the different ApplicationPool implementations.
 * It is #included in StandardApplicationPoolTest.cpp and ApplicationServer_ApplicationPoolTest.cpp
 */
#ifdef USE_TEMPLATE

      static string createRequestHeaders(const char *uri = "/foo/new") {
            string headers;
            #define ADD_HEADER(name, value) \
                  headers.append(name); \
                  headers.append(1, '\0'); \
                  headers.append(value); \
                  headers.append(1, '\0')
            ADD_HEADER("HTTP_HOST", "www.test.com");
            ADD_HEADER("QUERY_STRING", "");
            ADD_HEADER("REQUEST_URI", uri);
            ADD_HEADER("REQUEST_METHOD", "GET");
            ADD_HEADER("REMOTE_ADDR", "localhost");
            return headers;
      }
      
      static string readAll(int fd) {
            string result;
            char buf[1024 * 32];
            ssize_t ret;
            while (true) {
                  do {
                        ret = read(fd, buf, sizeof(buf));
                  } while (ret == -1 && errno == EINTR);
                  if (ret == 0) {
                        break;
                  } else if (ret == -1) {
                        throw SystemException("Cannot read from socket", errno);
                  } else {
                        result.append(buf, ret);
                  }
            }
            return result;
      }
      
      static Application::SessionPtr spawnRackApp(ApplicationPoolPtr pool, const char *appRoot) {
            return pool->get(appRoot, true, "nobody", "production", "smart", "rack");
      }
      
      static Application::SessionPtr spawnWsgiApp(ApplicationPoolPtr pool, const char *appRoot) {
            return pool->get(appRoot, true, "nobody", "production", "smart", "wsgi");
      }

      TEST_METHOD(1) {
            // Calling ApplicationPool.get() once should return a valid Session.
            Application::SessionPtr session(pool->get("stub/railsapp"));
            session->sendHeaders(createRequestHeaders());
            session->shutdownWriter();

            int reader = session->getStream();
            string result(readAll(reader));
            session->closeStream();
            ensure(result.find("hello world") != string::npos);
      }
      
      TEST_METHOD(2) {
            // Verify that the pool spawns a new app, and that
            // after the session is closed, the app is kept around.
            Application::SessionPtr session(pool->get("stub/railsapp"));
            ensure_equals("Before the session was closed, the app was busy", pool->getActive(), 1u);
            ensure_equals("Before the session was closed, the app was in the pool", pool->getCount(), 1u);
            session.reset();
            ensure_equals("After the session is closed, the app is no longer busy", pool->getActive(), 0u);
            ensure_equals("After the session is closed, the app is kept around", pool->getCount(), 1u);
      }
      
      TEST_METHOD(3) {
            // If we call get() with an application root, then we close the session,
            // and then we call get() again with the same application root,
            // then the pool should not have spawned more than 1 app in total.
            Application::SessionPtr session(pool->get("stub/railsapp"));
            session.reset();
            session = pool->get("stub/railsapp");
            ensure_equals(pool->getCount(), 1u);
      }
      
      TEST_METHOD(4) {
            // If we call get() with an application root, then we call get() again before closing
            // the session, then the pool should have spawned 2 apps in total.
            Application::SessionPtr session(pool->get("stub/railsapp"));
            Application::SessionPtr session2(pool2->get("stub/railsapp"));
            ensure_equals(pool->getCount(), 2u);
      }
      
      TEST_METHOD(5) {
            // If we call get() twice with different application roots,
            // then the pool should spawn two different apps.
            Application::SessionPtr session(pool->get("stub/railsapp"));
            Application::SessionPtr session2(pool2->get("stub/railsapp2"));
            ensure_equals("Before the sessions were closed, both apps were busy", pool->getActive(), 2u);
            ensure_equals("Before the sessions were closed, both apps were in the pool", pool->getCount(), 2u);
            
            session->sendHeaders(createRequestHeaders());
            string result(readAll(session->getStream()));
            ensure("Session 1 belongs to the correct app", result.find("hello world"));
            session.reset();
            
            session2->sendHeaders(createRequestHeaders());
            result = readAll(session2->getStream());
            ensure("Session 2 belongs to the correct app", result.find("this is railsapp2"));
            session2.reset();
      }
      
      TEST_METHOD(6) {
            // If we call get() twice with different application roots,
            // and we close both sessions, then both 2 apps should still
            // be in the pool.
            Application::SessionPtr session(pool->get("stub/railsapp"));
            Application::SessionPtr session2(pool->get("stub/railsapp2"));
            session.reset();
            session2.reset();
            ensure_equals(pool->getActive(), 0u);
            ensure_equals(pool->getCount(), 2u);
      }
      
      TEST_METHOD(7) {
            // If we call get() even though the pool is already full
            // (active == max), and the application root is already
            // in the pool, then the pool must wait until there's an
            // inactive application.
            pool->setMax(1);
            // TODO: How do we test this?
      }
      
      TEST_METHOD(8) {
            // If ApplicationPool spawns a new instance,
            // and we kill it, then the next get() with the
            // same application root should throw an exception.
            // But the get() thereafter should not:
            // ApplicationPool should have spawned a new instance
            // after detecting that the original one died.
            Application::SessionPtr session(pool->get("stub/railsapp"));
            kill(session->getPid(), SIGTERM);
            session.reset();
            try {
                  session = pool->get("stub/railsapp");
                  fail("ApplicationPool::get() is supposed to "
                        "throw an exception because we killed "
                        "the app instance.");
            } catch (const exception &e) {
                  session = pool->get("stub/railsapp");
                  // Should not throw.
            }
      }
      
      struct TestThread1 {
            ApplicationPoolPtr pool;
            Application::SessionPtr &m_session;
            bool &m_done;
            
            TestThread1(const ApplicationPoolPtr &pool,
                  Application::SessionPtr &session,
                  bool &done)
            : m_session(session), m_done(done) {
                  this->pool = pool;
                  done = false;
            }
            
            void operator()() {
                  m_session = spawnWsgiApp(pool, "stub/wsgi");
                  m_done = true;
            }
      };

      TEST_METHOD(9) {
            // If we call get() even though the pool is already full
            // (active == max), and the application root is *not* already
            // in the pool, then the pool will wait until enough sessions
            // have been closed.
            pool->setMax(2);
            Application::SessionPtr session1(spawnRackApp(pool, "stub/rack"));
            Application::SessionPtr session2(spawnRackApp(pool2, "stub/rack"));
            Application::SessionPtr session3;
            bool done;
            
            thread *thr = new thread(TestThread1(pool2, session3, done));
            usleep(500000);
            ensure("ApplicationPool is waiting", !done);
            ensure_equals(pool->getActive(), 2u);
            ensure_equals(pool->getCount(), 2u);
            
            session1.reset();
            
            // Wait at most 10 seconds.
            time_t begin = time(NULL);
            while (!done && time(NULL) - begin < 10) {
                  usleep(100000);
            }
            
            ensure("Session 3 is openend", done);
            ensure_equals(pool->getActive(), 2u);
            ensure_equals(pool->getCount(), 2u);
            
            thr->join();
            delete thr;
      }
      
      TEST_METHOD(10) {
            // If we call get(), and:
            // * the pool is already full, but there are inactive apps
            //   (active < count && count == max)
            // and
            // * the application root is *not* already in the pool
            // then the an inactive app should be killed in order to
            // satisfy this get() command.
            pool->setMax(2);
            Application::SessionPtr session1(pool->get("stub/railsapp"));
            Application::SessionPtr session2(pool->get("stub/railsapp"));
            session1.reset();
            session2.reset();
            
            ensure_equals(pool->getActive(), 0u);
            ensure_equals(pool->getCount(), 2u);
            session1 = pool2->get("stub/railsapp2");
            ensure_equals(pool->getActive(), 1u);
            ensure_equals(pool->getCount(), 2u);
      }
      
      TEST_METHOD(11) {
            // Test whether Session is still usable after the Application has been destroyed.
            Application::SessionPtr session(pool->get("stub/railsapp"));
            pool->clear();
            pool.reset();
            pool2.reset();
            
            session->sendHeaders(createRequestHeaders());
            session->shutdownWriter();
            
            int reader = session->getStream();
            string result(readAll(reader));
            session->closeStream();
            ensure(result.find("hello world") != string::npos);
      }
      
      TEST_METHOD(12) {
            // If tmp/restart.txt is present, then the applications under app_root
            // should be restarted.
            struct stat buf;
            Application::SessionPtr session1 = pool->get("stub/railsapp");
            Application::SessionPtr session2 = pool2->get("stub/railsapp");
            session1.reset();
            session2.reset();
            
            system("touch stub/railsapp/tmp/restart.txt");
            pool->get("stub/railsapp");
            
            ensure_equals("No apps are active", pool->getActive(), 0u);
            ensure_equals("Both apps are killed, and a new one was spawned",
                  pool->getCount(), 1u);
            ensure("Restart file has been deleted",
                  stat("stub/railsapp/tmp/restart.txt", &buf) == -1
                  && errno == ENOENT);
      }
      
      TEST_METHOD(13) {
            // If tmp/restart.txt is present, but cannot be deleted, then
            // the applications under app_root should still be restarted.
            // However, a subsequent get() should not result in a restart.
            pid_t old_pid, pid;
            struct stat buf;
            Application::SessionPtr session1 = pool->get("stub/railsapp");
            Application::SessionPtr session2 = pool2->get("stub/railsapp");
            session1.reset();
            session2.reset();
            
            system("mkdir -p stub/railsapp/tmp/restart.txt");
            
            old_pid = pool->get("stub/railsapp")->getPid();
            try {
                  ensure("Restart file has not been deleted",
                        stat("stub/railsapp/tmp/restart.txt", &buf) == 0);
                  system("rmdir stub/railsapp/tmp/restart.txt");
            } catch (...) {
                  system("rmdir stub/railsapp/tmp/restart.txt");
                  throw;
            }
            
            pid = pool->get("stub/railsapp")->getPid();
            ensure_equals("The app was not restarted", pid, old_pid);
            unlink("stub/railsapp/tmp/restart.txt");
      }
      
      TEST_METHOD(14) {
            // If tmp/restart.txt is present, but cannot be deleted, then
            // the applications under app_root should still be restarted.
            // A subsequent get() should only restart if we've changed
            // restart.txt's mtime.
            pid_t old_pid;
            Application::SessionPtr session1 = pool->get("stub/railsapp");
            Application::SessionPtr session2 = pool2->get("stub/railsapp");
            session1.reset();
            session2.reset();
            
            setenv("nextRestartTxtDeletionShouldFail", "1", 1);
            system("touch stub/railsapp/tmp/restart.txt");
            old_pid = pool->get("stub/railsapp")->getPid();
            ensure_equals(pool->getActive(), 0u);
            ensure_equals(pool->getCount(), 1u);

            sleep(1); // Allow the next mtime to be different.
            system("touch stub/railsapp/tmp/restart.txt");
            ensure("The app is restarted, and the last app instance was not reused",
                  pool2->get("stub/railsapp")->getPid() != old_pid);
            
            unlink("stub/railsapp/tmp/restart.txt");
      }
      
      TEST_METHOD(15) {
            // Test whether restarting really results in code reload.
            system("cp -f stub/railsapp/app/controllers/bar_controller_1.rb "
                  "stub/railsapp/app/controllers/bar_controller.rb");
            Application::SessionPtr session = pool->get("stub/railsapp");
            session->sendHeaders(createRequestHeaders("/bar"));
            string result = readAll(session->getStream());
            ensure(result.find("bar 1!"));
            session.reset();
            
            system("cp -f stub/railsapp/app/controllers/bar_controller_2.rb "
                  "stub/railsapp/app/controllers/bar_controller.rb");
            system("touch stub/railsapp/tmp/restart.txt");
            session = pool->get("stub/railsapp");
            session->sendHeaders(createRequestHeaders("/bar"));
            result = readAll(session->getStream());
            ensure("App code has been reloaded", result.find("bar 2!"));
            unlink("stub/railsapp/app/controllers/bar_controller.rb");
      }
      
      TEST_METHOD(16) {
            // The cleaner thread should clean idle applications without crashing.
            pool->setMaxIdleTime(1);
            spawnRackApp(pool, "stub/rack");
            
            time_t begin = time(NULL);
            while (pool->getCount() == 1u && time(NULL) - begin < 10) {
                  usleep(100000);
            }
            ensure_equals("App should have been cleaned up", pool->getCount(), 0u);
      }
      
      TEST_METHOD(17) {
            // MaxPerApp must be respected.
            pool->setMax(3);
            pool->setMaxPerApp(1);
            // TODO: how do we test this?
      }

#endif /* USE_TEMPLATE */

Generated by  Doxygen 1.6.0   Back to index