bok_choy.rake 6.93 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14
# Run acceptance tests that use the bok-choy framework
# http://bok-choy.readthedocs.org/en/latest/
require 'dalli'


# Mongo databases that will be dropped before/after the tests run
BOK_CHOY_MONGO_DATABASE = "test"

# Control parallel test execution with environment variables
# Process timeout is the maximum amount of time to wait for results from a particular test case
BOK_CHOY_NUM_PARALLEL = ENV.fetch('NUM_PARALLEL', 1).to_i
BOK_CHOY_TEST_TIMEOUT = ENV.fetch("TEST_TIMEOUT", 300).to_f

# Ensure that we have a directory to put logs and reports
15 16
BOK_CHOY_DIR = File.join(REPO_ROOT, "common", "test", "acceptance")
BOK_CHOY_TEST_DIR = File.join(BOK_CHOY_DIR, "tests")
17 18 19
BOK_CHOY_LOG_DIR = File.join(REPO_ROOT, "test_root", "log")
directory BOK_CHOY_LOG_DIR

20 21 22 23 24
# Reports
BOK_CHOY_REPORT_DIR = report_dir_path("bok_choy")
BOK_CHOY_XUNIT_REPORT = File.join(BOK_CHOY_REPORT_DIR, "xunit.xml")
BOK_CHOY_COVERAGE_RC = File.join(BOK_CHOY_DIR, ".coveragerc")
directory BOK_CHOY_REPORT_DIR
25 26


27 28 29 30 31
BOK_CHOY_SERVERS = {
    :lms => { :port =>  8003, :log => File.join(BOK_CHOY_LOG_DIR, "bok_choy_lms.log") },
    :cms => { :port => 8031, :log => File.join(BOK_CHOY_LOG_DIR, "bok_choy_studio.log") }
}

32
BOK_CHOY_STUBS = {
33 34 35 36 37

    :xqueue => {
        :port => 8040,
        :log => File.join(BOK_CHOY_LOG_DIR, "bok_choy_xqueue.log")
    }
38 39 40 41 42 43 44
}

# For the time being, stubs are used by both the bok-choy and lettuce acceptance tests
# For this reason, the stubs package is currently located in the Django app called "terrain"
# where other lettuce configuration is stored.
BOK_CHOY_STUB_DIR = File.join(REPO_ROOT, "common", "djangoapps", "terrain")

45 46 47
BOK_CHOY_CACHE = Dalli::Client.new('localhost:11211')


48
# Start the servers we will run tests on
49 50 51
def start_servers()
    BOK_CHOY_SERVERS.each do | service, info |
        address = "0.0.0.0:#{info[:port]}"
52 53
        cmd = "coverage run --rcfile=#{BOK_CHOY_COVERAGE_RC} -m manage #{service} --settings bok_choy runserver #{address} --traceback --noreload"
        singleton_process(cmd, logfile=info[:log])
54
    end
55 56 57 58 59 60 61 62 63

    BOK_CHOY_STUBS.each do | service, info |
        Dir.chdir(BOK_CHOY_STUB_DIR) do
            singleton_process(
                "python -m stubs.start #{service} #{info[:port]}",
                logfile=info[:log]
            )
        end
    end
64

65
end
66 67 68

# Wait until we get a successful response from the servers or time out
def wait_for_test_servers()
69
    BOK_CHOY_SERVERS.merge(BOK_CHOY_STUBS).each do | service, info |
70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112
        ready = wait_for_server("0.0.0.0", info[:port])
        if not ready
            fail("Could not contact #{service} test server")
        end
    end
end


def is_mongo_running()
    # The mongo command will connect to the service,
    # failing with a non-zero exit code if it cannot connect.
    output = `mongo --eval "print('running')"`
    return (output and output.include? "running")
end


def is_memcache_running()
    # We use a Ruby memcache client to attempt to set a key
    # in memcache.  If we cannot do so because the service is not
    # available, then this will raise an exception.
    BOK_CHOY_CACHE.set('test', 'test')
    return true
rescue Dalli::DalliError
    return false
end


def is_mysql_running()
    # We use the MySQL CLI client and capture its stderr
    # If the client cannot connect successfully, stderr will be non-empty
    output = `mysql -e "" 2>&1`
    return output == ""
end


# Run the bok choy tests
# `test_spec` is a nose-style test specifier relative to the test directory
# Examples:
# - path/to/test.py
# - path/to/test.py:TestFoo
# - path/to/test.py:TestFoo.test_bar
# It can also be left blank to run all tests in the suite.
def run_bok_choy(test_spec)
113 114

    # Default to running all tests if no specific test is specified
115
    if test_spec.nil?
116
        test_spec = BOK_CHOY_TEST_DIR
117
    else
118
        test_spec = File.join(BOK_CHOY_TEST_DIR, test_spec)
119
    end
120 121 122 123 124 125 126 127 128 129 130 131 132 133

    # Construct the nosetests command, specifying where to save screenshots and XUnit XML reports
    cmd = [
        "SCREENSHOT_DIR='#{BOK_CHOY_LOG_DIR}'", "nosetests", test_spec,
        "--with-xunit", "--xunit-file=#{BOK_CHOY_XUNIT_REPORT}"
    ]

    # Configure parallel test execution, if specified
    if BOK_CHOY_NUM_PARALLEL > 1
        cmd += ["--processes=#{BOK_CHOY_NUM_PARALLEL}", "--process-timeout=#{BOK_CHOY_TEST_TIMEOUT}"]
    end

    # Run the nosetests command
    sh(cmd.join(" "))
134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168
end


def clear_mongo()
    sh("mongo #{BOK_CHOY_MONGO_DATABASE} --eval 'db.dropDatabase()' > /dev/null")
end


# Clean up data we created in the databases
def cleanup()
    sh(django_admin('lms', 'bok_choy', 'flush', '--noinput'))
    clear_mongo()
end


namespace :'test:bok_choy' do

    # Check that required services are running
    task :check_services do
        if not is_mongo_running()
            fail("Mongo is not running locally.")
        end

        if not is_memcache_running()
            fail("Memcache is not running locally.")
        end

        if not is_mysql_running()
            fail("MySQL is not running locally.")
        end
    end

    desc "Process assets and set up database for bok-choy tests"
    task :setup => [:check_services, :install_prereqs, BOK_CHOY_LOG_DIR] do

169 170
        # Reset the database
        sh("#{REPO_ROOT}/scripts/reset-test-db.sh")
171 172 173 174 175 176 177 178

        # Collect static assets
        Rake::Task["gather_assets"].invoke('lms', 'bok_choy')
        Rake::Task["gather_assets"].reenable
        Rake::Task["gather_assets"].invoke('cms', 'bok_choy')
    end

    desc "Run acceptance tests that use the bok-choy framework but skip setup"
179 180 181
    task :fast, [:test_spec] => [
        :check_services, BOK_CHOY_LOG_DIR, BOK_CHOY_REPORT_DIR, :clean_reports_dir
    ] do |t, args|
182

183 184 185 186 187
        # Clear any test data already in Mongo or MySQL and invalidate the cache
        clear_mongo()
        BOK_CHOY_CACHE.flush()
        sh(django_admin('lms', 'bok_choy', 'flush', '--noinput'))

188
        # Ensure the test servers are available
189
        puts "Starting test servers...".green
190
        start_servers()
191
        puts "Waiting for servers to start...".green
192 193 194
        wait_for_test_servers()

        begin
195
            puts "Running test suite...".green
196 197 198 199 200
            run_bok_choy(args.test_spec)
        rescue
            puts "Tests failed!".red
            exit 1
        ensure
201
            puts "Cleaning up databases...".green
202 203 204 205
            cleanup()
        end
    end

206 207 208 209 210 211 212 213 214 215 216
    desc "Generate coverage reports for bok-choy tests"
    task :coverage => BOK_CHOY_REPORT_DIR do | t, args |
        puts "Combining coverage reports".red
        sh("coverage combine --rcfile=#{BOK_CHOY_COVERAGE_RC}")

        puts "Generating coverage reports".red
        sh("coverage html --rcfile=#{BOK_CHOY_COVERAGE_RC}")
        sh("coverage xml --rcfile=#{BOK_CHOY_COVERAGE_RC}")
        sh("coverage report --rcfile=#{BOK_CHOY_COVERAGE_RC}")
    end

217 218 219 220 221 222 223 224
end


# Default: set up and run the tests
desc "Run acceptance tests that use the bok-choy framework"
task :'test:bok_choy', [:test_spec] => [:'test:bok_choy:setup'] do |t, args|
    Rake::Task["test:bok_choy:fast"].invoke(args.test_spec)
end